7 changed files with 517 additions and 3 deletions
-
1App.vue
-
22common/styles/sharedIconFont.css
-
154components/lf-uploadImage/lf-uploadImage.vue
-
6pages.json
-
2pages/my/index.vue
-
325pages/test/drag.vue
-
10pages/test/test.vue
@ -0,0 +1,22 @@ |
|||
@font-face { |
|||
font-family: "lf-iconfont"; /* Project id 2758976 */ |
|||
src: url('//at.alicdn.com/t/font_2758976_9bezuvselm.woff2?t=1629452232587') format('woff2'), |
|||
url('//at.alicdn.com/t/font_2758976_9bezuvselm.woff?t=1629452232587') format('woff'), |
|||
url('//at.alicdn.com/t/font_2758976_9bezuvselm.ttf?t=1629452232587') format('truetype'); |
|||
} |
|||
|
|||
.lf-iconfont { |
|||
font-family: "lf-iconfont" !important; |
|||
font-size: 16px; |
|||
font-style: normal; |
|||
-webkit-font-smoothing: antialiased; |
|||
-moz-osx-font-smoothing: grayscale; |
|||
} |
|||
|
|||
.lf-icon-shanchu:before { |
|||
content: "\e674"; |
|||
} |
|||
|
|||
.lf-icon-jia:before { |
|||
content: "\e715"; |
|||
} |
|||
@ -0,0 +1,154 @@ |
|||
<template> |
|||
<view class="upload-images"> |
|||
<view class="upload-image-item" |
|||
:style="{order: uploadButton == 'front' ? '1' : '2'}" |
|||
@click="uploadImage" |
|||
v-if="(count == -1) || (image_list.length < count)"> |
|||
<text class="lf-iconfont lf-icon-jia upload-image-item-after"></text> |
|||
</view> |
|||
<view class="upload-image-item" |
|||
:style="{order: uploadButton == 'front' ? '2' : '1'}" |
|||
v-for="(item, index) in image_list" :key="index" |
|||
@click="lookImage(index)"> |
|||
<image :src="item" mode="aspectFill"></image> |
|||
<view class="remove-image" @click.stop="removeInage(index)" v-if="showDelete"> |
|||
<text class="lf-iconfont lf-icon-shanchu"></text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
uploadButton: { |
|||
type: String, // 上传按钮显示在哪里,front图片前面,behind图片后面 |
|||
default: 'front' |
|||
}, |
|||
showDelete: { |
|||
type: Boolean, // 是否可删除图片 |
|||
default: true |
|||
}, |
|||
count: { |
|||
type: Number, // 可上传多少张图, 默认6张 |
|||
default: 6 |
|||
}, |
|||
size: { |
|||
type: Number, // 限制单张图上传大小,单位M |
|||
default: 10 |
|||
}, |
|||
drag: { |
|||
type: Boolean, // TODO 是否可拖拽排序图片 |
|||
default: false |
|||
} |
|||
}, |
|||
data(){ |
|||
return { |
|||
image_list: [] |
|||
} |
|||
}, |
|||
onLoad(){ |
|||
|
|||
}, |
|||
methods: { |
|||
// 上传凭证图片 |
|||
uploadImage(){ |
|||
let current_count = this.$props.count - this.image_list.length; |
|||
if(this.$props.count == -1){ |
|||
current_count = 9; |
|||
} |
|||
if(current_count == 0) return; |
|||
uni.chooseImage({ |
|||
count: current_count, |
|||
complete: result => { |
|||
if(result.errMsg == "chooseImage:fail cancel"){ |
|||
return; // 取消选择图片 |
|||
} |
|||
let tempFiles = result.tempFiles; |
|||
let image_list = []; |
|||
let overstep = false; |
|||
tempFiles.map(item => { |
|||
// 上传的图片大小 |
|||
let size = Math.floor((Number(this.$props.size) || 1) * 1024 * 1024); |
|||
if(item.size < size){ |
|||
image_list.push(item.path); |
|||
}else{ |
|||
overstep = true; |
|||
} |
|||
}) |
|||
if(overstep){ |
|||
uni.showModal({ |
|||
title: '温馨提示', |
|||
content: '您上传的图片含有超出10M大小的限制,请优化大小后再上传!', |
|||
showCancel: false |
|||
}) |
|||
} |
|||
this.image_list.push(...image_list); |
|||
} |
|||
}) |
|||
}, |
|||
// 预览图片 |
|||
lookImage(current){ |
|||
if(this.image_list.length <= 0) return; |
|||
this.$u.throttle(() => { |
|||
uni.previewImage({ |
|||
urls: this.image_list, |
|||
current: current |
|||
}); |
|||
}, 500); |
|||
}, |
|||
// 移除图片 |
|||
removeInage(current){ |
|||
this.image_list.splice(current, 1); |
|||
}, |
|||
// 返回已上传的图片列表 |
|||
getUploadImage(){ |
|||
return this.image_list; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped="scoped"> |
|||
.upload-images{ |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
margin-top: 30rpx; |
|||
margin-bottom: 18rpx; |
|||
.upload-image-item{ |
|||
width: 220rpx; |
|||
height: 220rpx; |
|||
background: #DDDDDD; |
|||
position: relative; |
|||
margin-right: 12rpx; |
|||
&:nth-child(3n){ |
|||
margin-right: 0rpx; |
|||
} |
|||
&:nth-child(n+4){ |
|||
margin-top: 12rpx; |
|||
} |
|||
image{ |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
.remove-image{ |
|||
position: absolute; |
|||
right: -4rpx; |
|||
top: -18rpx; |
|||
color: #e74c3c; |
|||
font-size: 40rpx; |
|||
padding: 8rpx; |
|||
} |
|||
} |
|||
.upload-image-item-after{ |
|||
position: absolute; |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
font-size: 60rpx; |
|||
color: #999999; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,325 @@ |
|||
<template> |
|||
<view> |
|||
<view :style="{'overflow-x': overOnePage ? 'hidden' : 'initial'}"> |
|||
<view class="item-wrap" :style="{'height': itemWrapHeight +'px'}"> |
|||
<view class="item" |
|||
:class="{'cur': cur == index, 'zIndex': curZ == index, 'itemTransition': itemTransition}" |
|||
v-for="(item, index) in list" |
|||
:key="index" |
|||
:id="'item'+index" |
|||
:data-key="item.key" |
|||
:data-index="index" |
|||
:style="[itemStyle(index, item, item.key)]" |
|||
@longpress="longPress" |
|||
@touchmove.stop="touchMove" |
|||
@touchend.stop="touchEnd"> |
|||
<view class="info"> |
|||
<view> |
|||
<image :src="item.data"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view v-if="overOnePage" class="indicator"> |
|||
<view>滑动此区域滚动页面</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
|
|||
}, |
|||
data(){ |
|||
return { |
|||
touch: false, |
|||
startX: 0, |
|||
startY: 0, |
|||
columns: 3, |
|||
item: {}, |
|||
tranX: 0, |
|||
tranConstX: 0, |
|||
itemWrap: {}, |
|||
tranY: 0, |
|||
teanConstY: 0, |
|||
overOnePage: false, |
|||
windowHeight: 0, |
|||
originKey: null, |
|||
list: [], |
|||
cur: -1, |
|||
curZ: -1, |
|||
listData: [ |
|||
'https://picsum.photos/200', |
|||
'https://picsum.photos/200', |
|||
'https://picsum.photos/200', |
|||
'https://picsum.photos/200', |
|||
'https://picsum.photos/200/300'], |
|||
itemWrapHeight: 0 |
|||
} |
|||
}, |
|||
computed: { |
|||
itemStyle(){ |
|||
let that = this; |
|||
return function(index, item, key){ |
|||
let style_obj = { |
|||
width: Math.floor(100 / that.columns) +'%', |
|||
'margin-top': Math.ceil((key+1) / that.columns) * 5 +'px' |
|||
} |
|||
let str = 'translate3d('; |
|||
if(index === that.cur){ |
|||
str += that.tranX +'px'; |
|||
str += ', '; |
|||
str += that.tranY +'px'; |
|||
}else{ |
|||
str += item.tranX +'px'; |
|||
str += ', '; |
|||
str += item.tranY +'px'; |
|||
} |
|||
str += ', 0px)'; |
|||
style_obj.transform = str; |
|||
return style_obj; |
|||
} |
|||
} |
|||
}, |
|||
mounted(){ |
|||
this.init(); |
|||
}, |
|||
methods: { |
|||
// 长按触发移动排序 |
|||
longPress(e) { |
|||
this.touch = true; |
|||
this.startX = e.changedTouches[0].pageX |
|||
this.startY = e.changedTouches[0].pageY |
|||
let index = e.currentTarget.dataset.index; |
|||
if(this.columns === 1) { // 单列时候X轴初始不做位移 |
|||
this.tranConstX = 0; |
|||
} else { // 多列的时候计算X轴初始位移, 使 item 水平中心移动到点击处 |
|||
this.tranConstX = this.startX - this.item.width / 2 - this.itemWrap.left; |
|||
} |
|||
// 计算Y轴初始位移, 使 item 垂直中心移动到点击处 |
|||
this.teanConstY = this.startY - this.item.height / 2 - this.itemWrap.top; |
|||
this.tranY = this.teanConstY; |
|||
this.tranX = this.tranConstX; |
|||
this.cur = index; |
|||
this.curZ = index; |
|||
// #ifndef H5 |
|||
uni.vibrateShort(); |
|||
// #endif |
|||
}, |
|||
// 拖拽中 |
|||
touchMove(e) { |
|||
if (!this.touch) return; |
|||
let tranX = e.touches[0].pageX - this.startX + this.tranConstX; // TODO 原作者写的逻辑,但不知道为什么计算不对??? |
|||
let tranY = e.touches[0].pageY - this.startY + this.teanConstY; |
|||
let overOnePage = this.overOnePage; |
|||
|
|||
// 判断是否超过一屏幕, 超过则需要判断当前位置动态滚动page的位置 |
|||
if(overOnePage) { |
|||
if(e.touches[0].clientY > this.windowHeight - this.item.height) { |
|||
uni.pageScrollTo({ |
|||
scrollTop: e.touches[0].pageY + this.item.height - this.windowHeight, |
|||
duration: 300 |
|||
}); |
|||
} else if(e.touches[0].clientY < this.item.height) { |
|||
uni.pageScrollTo({ |
|||
scrollTop: e.touches[0].pageY - this.item.height, |
|||
duration: 300 |
|||
}); |
|||
} |
|||
} |
|||
|
|||
this.tranX = tranX; |
|||
this.tranY = tranY; |
|||
let originKey = e.currentTarget.dataset.key; |
|||
let endKey = this.calculateMoving(tranX, tranY); |
|||
|
|||
// 防止拖拽过程中发生乱序问题 |
|||
if (originKey == endKey || this.originKey == originKey) return; |
|||
this.originKey = originKey; |
|||
this.insert(originKey, endKey); |
|||
}, |
|||
// 根据当前的手指偏移量计算目标key |
|||
calculateMoving(tranX, tranY) { |
|||
let rows = Math.ceil(this.list.length / this.columns) - 1; |
|||
let i = Math.round(tranX / this.item.width); |
|||
let j = Math.round(tranY / this.item.height); |
|||
|
|||
i = i > (this.columns - 1) ? (this.columns - 1) : i; |
|||
i = i < 0 ? 0 : i; |
|||
|
|||
j = j < 0 ? 0 : j; |
|||
j = j > rows ? rows : j; |
|||
|
|||
let endKey = i + this.columns * j; |
|||
endKey = endKey >= this.list.length ? this.list.length - 1 : endKey; |
|||
return endKey |
|||
}, |
|||
// 根据起始key和目标key去重新计算每一项的新的key |
|||
insert(origin, end) { |
|||
let list; |
|||
if (origin < end) { |
|||
list = this.list.map((item) => { |
|||
if (item.key > origin && item.key <= end) { |
|||
item.key = item.key - 1; |
|||
} else if (item.key == origin) { |
|||
item.key = end; |
|||
} |
|||
return item |
|||
}); |
|||
this.getPosition(list); |
|||
} else if (origin > end) { |
|||
list = this.list.map((item) => { |
|||
if (item.key >= end && item.key < origin) { |
|||
item.key = item.key + 1; |
|||
} else if (item.key == origin) { |
|||
item.key = end; |
|||
} |
|||
return item |
|||
}); |
|||
this.getPosition(list); |
|||
} |
|||
}, |
|||
// 根据排序后 list 数据进行位移计算 |
|||
getPosition(data, vibrate = true) { |
|||
let list = data.map((item, index) => { |
|||
item.tranX = this.item.width * (item.key % this.columns); |
|||
item.tranY = Math.floor(item.key / this.columns) * this.item.height; |
|||
return item |
|||
}); |
|||
|
|||
this.list = list; |
|||
if(!vibrate) return; |
|||
this.itemTransition = true; |
|||
// #ifndef H5 |
|||
uni.vibrateShort(); |
|||
// #endif |
|||
|
|||
let listData = []; |
|||
list.forEach((item) => { |
|||
listData[item.key] = item.data |
|||
}); |
|||
|
|||
// 元素位置发生改变了,触发change事件告诉父元素更新 |
|||
this.$emit('change', {listData: listData}); |
|||
}, |
|||
// 拖拽结束 |
|||
touchEnd() { |
|||
if (!this.touch) return; |
|||
this.clearData(); |
|||
}, |
|||
// 清除参数 |
|||
clearData() { |
|||
this.originKey = -1; |
|||
this.touch = false; |
|||
this.cur = -1; |
|||
this.tranX = 0; |
|||
this.tranY = 0; |
|||
|
|||
// 延迟清空 |
|||
setTimeout(() => { |
|||
this.curZ = -1; |
|||
}, 300) |
|||
}, |
|||
// 初始化加载 |
|||
init() { |
|||
// 遍历数据源增加扩展项, 以用作排序使用 |
|||
let list = this.listData.map((item, index) => { |
|||
return { |
|||
key: index, |
|||
tranX: 0, |
|||
tranY: 0, |
|||
data: item |
|||
} |
|||
}); |
|||
this.list = list; |
|||
this.itemTransition = false; |
|||
this.windowHeight = uni.getSystemInfoSync().windowHeight; |
|||
|
|||
setTimeout(() => { |
|||
// 获取每一项的宽高等属性 |
|||
this.createSelectorQuery().select(".item").boundingClientRect((res) => { |
|||
let rows = Math.ceil(this.list.length / this.columns); |
|||
this.item = res; |
|||
this.getPosition(this.list, false); |
|||
let itemWrapHeight = rows * res.height; |
|||
this.itemWrapHeight = itemWrapHeight; |
|||
|
|||
this.createSelectorQuery().select(".item-wrap").boundingClientRect((res) => { |
|||
this.itemWrap = res; |
|||
let overOnePage = itemWrapHeight + res.top > this.windowHeight; |
|||
this.overOnePage = overOnePage; |
|||
}).exec(); |
|||
}).exec(); |
|||
}, 300) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped="scoped"> |
|||
.item-wrap { |
|||
position: relative; |
|||
.item { |
|||
position: absolute; |
|||
width: 100%; |
|||
z-index: 1; |
|||
&.itemTransition { |
|||
transition: transform 0.3s; |
|||
} |
|||
&.zIndex { |
|||
z-index: 2; |
|||
} |
|||
&.cur { |
|||
background: #1998FE; |
|||
transition: initial; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.info { |
|||
position: relative; |
|||
padding-top: 100%; |
|||
background: #ffffff; |
|||
margin-right: 5px; |
|||
&:nth-child(3n){ |
|||
margin-right: 0px; |
|||
} |
|||
& > view { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
overflow: hidden; |
|||
box-sizing: border-box; |
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.indicator { |
|||
position: fixed; |
|||
z-index: 99999; |
|||
right: 0rpx; |
|||
top: 50%; |
|||
margin-top: -250rpx; |
|||
padding: 20rpx; |
|||
& > view { |
|||
width: 36rpx; |
|||
height: 500rpx; |
|||
background: #ffffff; |
|||
border-radius: 30rpx; |
|||
box-shadow: 0 0 10rpx -4rpx rgba(0, 0, 0, 0.5); |
|||
color: pink; |
|||
padding-top: 90rpx; |
|||
box-sizing: border-box; |
|||
font-size: 24rpx; |
|||
text-align: center; |
|||
opacity: 0.8; |
|||
} |
|||
} |
|||
</style> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue