|
|
<template> <view> <view class="upload-images" :style="{'overflow-x': overOnePage ? 'hidden' : 'initial'}"> <!-- 上传按钮 TODO 应该移到和图片同一列 --> <view class="upload-image-item" :style="{order: uploadButton == 'front' ? '1' : '2', height: '222rpx', 'margin-bottom': '6px'}" @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="item-wrap" :style="{'height': itemWrapHeight +'px', order: uploadButton == 'front' ? '2' : '1'}"> <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)]" @longpress="longPress" @touchmove.stop="touchMove" @touchend.stop="touchEnd"> <view class="info"> <view class="upload-image-item" @click="lookImage(index)"> <image :src="item.data" 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> </view> </view> </view> <view v-if="overOnePage" class="indicator"> <view>滑动此区域滚动页面</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, default: false }, padding: { type: Number, // 图片与图片之间的间距,默认5像素,单位px
default: 6 }, width: { type: String, // 每张图片的宽度,支持百分比,像素,bisection表示等分
default: 'bisection' }, height: { type: String, // 每张图片高度
default: '222rpx' } }, 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, image_list: [], itemWrapHeight: 0 } }, computed: { itemStyle(){ let that = this; return function(index, item){ let style_obj = { height: that.$props.height } let width = that.$props.width; if(width == 'bisection'){ width = `calc(${100 / that.columns +'%'} - 4px)` } style_obj.width = width; 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(){ // 'https://picsum.photos/200',
// 'https://picsum.photos/200/300'
this.init(); }, 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; } }) this.image_list.push(...image_list); if(overstep){ uni.showModal({ title: '温馨提示', content: '您上传的图片含有超出10M大小的限制,请优化大小后再上传!', showCancel: false }) }else{ this.init(); // TODO 优化每次上传都会造成重新初始化导致闪烁
} } }) }, // 预览图片
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); // 移除定位的元素(重新初始化)
let list = this.image_list.map((item, index) => { return { key: index, tranX: 0, tranY: 0, data: item } }) || []; this.list = list; this.getPosition(list, false); // 重新定位
// 重新计算大盒子高度
let rows = Math.ceil(list.length / this.columns); let itemWrapHeight = rows * this.item.height; itemWrapHeight += rows * this.$props.padding - this.$props.padding; this.itemWrapHeight = itemWrapHeight; }, // 返回已上传的图片列表
getUploadImage(){ return this.image_list; }, // 长按触发移动排序
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; 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) => { // X轴距离
let tranX = this.item.width * (item.key % this.columns); let rows = Math.ceil((item.key+1) / this.columns); let numX = item.key; for(let i=0; i<rows-1; i++){ numX -= this.columns; } numX = numX * this.$props.padding; tranX = tranX != 0 ? tranX + numX : tranX; item.tranX = tranX; // Y轴距离
let tranY = Math.floor(item.key / this.columns) * this.item.height; if(rows > 1){ let numY = (rows - 1) * this.$props.padding; tranY = tranY + numY; } item.tranY = tranY; return item }); this.list = list; if(!vibrate) return; this.itemTransition = true; // #ifndef H5
uni.vibrateShort(); // #endif
let image_list = []; list.forEach((item) => { image_list[item.key] = item.data }); // 元素位置发生改变了,触发change事件告诉父元素更新
this.$emit('change', {image_list: image_list}); }, // 拖拽结束
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() { // 拦截image_list为空未上传图片的情况
if(!this.image_list.length) return; // 遍历数据源增加扩展项, 以用作排序使用
let list = this.image_list.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; itemWrapHeight += rows * this.$props.padding - this.$props.padding; 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"> .upload-images{ display: flex; flex-wrap: wrap; .upload-image-item{ // width: 220rpx;
// height: 220rpx;
background: #DDDDDD; position: relative; // margin-right: 12rpx;
width: 100%; height: 100%; &: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; } } .item-wrap { position: relative; width: 100%; .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;
width: auto; height: 100%; &: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>
|