You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
7.9 KiB
315 lines
7.9 KiB
<template>
|
|
<view>
|
|
<swiper class="swiper" :animation="animationData"
|
|
:style="{ 'opacity': animation ? '0' : '1',
|
|
'background-color': perspective ? 'rgba(0,0,0,0.5)' : '#000000'}"
|
|
:current="swiper_current" :circular="circular"
|
|
v-if="show_assembly" @click="hideAssembly"
|
|
@transition="transition"
|
|
@touchmove.stop.prevent="touchmove"
|
|
@change="e => switchItem(e.detail.current)">
|
|
<swiper-item v-for="(item, index) in images" :key="index"
|
|
class="swiper-item">
|
|
<!-- 可移动缩放操作的容器 -->
|
|
<movable-area class="movable-area" :scale-area="true">
|
|
<movable-view class="movable-view" direction="all" :scale="true" :inertia="true" @scale="scale" :scale-value="scale_values[swiper_current]">
|
|
<image :src="item" class="swiper-image" mode="aspectFit" @mousewheel.stop="mousewheel" @longpress="longpress"></image>
|
|
</movable-view>
|
|
</movable-area>
|
|
<!-- 控制器 -->
|
|
<view class="controls" v-if="controls">
|
|
<view>
|
|
<view @click.stop="switchItem(swiper_current - 1)" v-if="swiper_current != 0">
|
|
<uni-icons type="arrowleft" size="36" color="#fff"></uni-icons>
|
|
</view>
|
|
<view v-else></view>
|
|
</view>
|
|
<view>
|
|
<view @click.stop="switchItem(swiper_current + 1)" v-if="swiper_current != images.length - 1">
|
|
<uni-icons type="arrowright" size="36" color="#fff"></uni-icons>
|
|
</view>
|
|
<view v-else></view>
|
|
</view>
|
|
</view>
|
|
<!-- 指示器 -->
|
|
<view class="indicators" v-if="indicators && images.length > 1">
|
|
<view class="number" v-if="indicatorType == 'number'">{{ swiper_current + 1 }} / {{ images.length }}</view>
|
|
<view class="square" v-else-if="indicatorType == 'square'">
|
|
<view v-for="(item, index) in images" :key="index" class="indicators-icon" :style="swiper_current == index ? 'background-color:'+ themeColor : ''"></view>
|
|
</view>
|
|
<view class="dot" v-else-if="indicatorType == 'dot'">
|
|
<view v-for="(item, index) in images" :key="index" class="indicators-icon" :style="swiper_current == index ? 'background-color:'+ themeColor : ''"></view>
|
|
</view>
|
|
</view>
|
|
<!-- 底部菜单弹出层 -->
|
|
<view class="menu-popup" v-if="menu && show_menu" @click.stop>
|
|
<button @click="download">保存图片</button>
|
|
</view>
|
|
</swiper-item>
|
|
</swiper>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
props: {
|
|
controls: {
|
|
type: Boolean, // 是否显示左右箭头控制
|
|
default: true
|
|
},
|
|
indicators: {
|
|
type: Boolean,
|
|
default: true // 是否显示指示器
|
|
},
|
|
indicatorType: {
|
|
type: String, // 指示器类型,dot圆点,square方形,number数字
|
|
default: 'number'
|
|
},
|
|
perspective: {
|
|
type: Boolean, // 是否开启背景透视,遮罩透明可以看见底下其他元素
|
|
default: true
|
|
},
|
|
circular: {
|
|
type: Boolean, // 是否可以衔接滑动
|
|
default: false
|
|
},
|
|
themeColor: {
|
|
type: String, // 主题色
|
|
default: '#1833F2'
|
|
},
|
|
animation: {
|
|
type: Boolean, // 是否开启动画
|
|
default: false
|
|
},
|
|
menu: {
|
|
type: Boolean, // 长按图片时是否显示菜单按钮,H5不支持,传参无效果
|
|
default: true
|
|
}
|
|
},
|
|
data(){
|
|
return {
|
|
swiper_current: 0, // 当前显示的图片下标
|
|
images: [],
|
|
show_assembly: false, // 显示预览图片
|
|
duration: 500, // 动画持续时间
|
|
animationObj: {}, // 动画配置对象
|
|
animationData: {}, // 动画导出执行对象
|
|
scale_values: [], // 每张图的缩放比例
|
|
clientY: 0,
|
|
show_menu: false
|
|
}
|
|
},
|
|
created(){
|
|
// TODO PC端屏蔽遮罩滚动穿透
|
|
// TODO PC端滚轮放大缩小
|
|
// TODO 双击时会被关掉
|
|
},
|
|
methods: {
|
|
// 初始化加载动画
|
|
initAnimation(){
|
|
let animationObj = uni.createAnimation({
|
|
duration: this.duration
|
|
});
|
|
this.animationObj = animationObj;
|
|
animationObj.opacity(0).step();
|
|
animationObj.opacity(1).step();
|
|
this.animationData = animationObj.export();
|
|
},
|
|
// swiper touchmove事件
|
|
touchmove(){
|
|
return false;
|
|
},
|
|
// swiper 位置发生变化
|
|
transition(){
|
|
if(this.show_menu){
|
|
this.show_menu = false;
|
|
}
|
|
},
|
|
// swiper被改变
|
|
switchItem(current){
|
|
this.swiper_current = current;
|
|
// this.clientY = 0; // 切换时需要复位
|
|
},
|
|
// movable 缩放事件
|
|
scale(event){
|
|
let scale = parseInt(event.detail.scale * 100) +'%';
|
|
uni.showToast({
|
|
title: scale,
|
|
icon: 'none',
|
|
position: 'bottom'
|
|
})
|
|
},
|
|
// 鼠标滚动缩放事件,目前会触发浏览器页面滚动事件,暂时先不要这个功能
|
|
mousewheel(event){
|
|
return;
|
|
let scale = this.scale_values[this.swiper_current];
|
|
if(this.clientY > event.clientY){
|
|
if(scale < 10){
|
|
scale += 0.5;
|
|
}
|
|
}else{
|
|
if(scale > 0.5){
|
|
scale -= 0.5;
|
|
}
|
|
}
|
|
this.clientY = event.clientY;
|
|
this.scale_values.splice(this.swiper_current, 1, scale);
|
|
},
|
|
// 显示图片
|
|
show(options){
|
|
this.images = options.images || [];
|
|
this.swiper_current = options.current || 0;
|
|
this.scale_values = this.images.map(item => 1);
|
|
this.show_assembly = true;
|
|
if(this.$props.animation){
|
|
this.initAnimation();
|
|
}
|
|
},
|
|
// 关闭隐藏图片
|
|
hideAssembly(){
|
|
if(this.show_menu){
|
|
this.show_menu = false;
|
|
return;
|
|
}
|
|
if(this.$props.animation){
|
|
this.animationObj.opacity(0).step();
|
|
this.animationData = this.animationObj.export();
|
|
setTimeout(() => {
|
|
this.show_assembly = false;
|
|
}, this.duration);
|
|
}else{
|
|
this.show_assembly = false;
|
|
}
|
|
},
|
|
// 长按事件
|
|
longpress(event){
|
|
// #ifndef H5
|
|
if(this.$props.menu){
|
|
this.show_menu = true;
|
|
}
|
|
// #endif
|
|
},
|
|
// 保存图片
|
|
download(){
|
|
uni.showLoading({
|
|
title: '正在保存'
|
|
})
|
|
this.show_menu = false;
|
|
uni.downloadFile({
|
|
url: this.images[this.swiper_current],
|
|
success: res => {
|
|
let tempFilePath = res.tempFilePath;
|
|
uni.saveFile({
|
|
tempFilePath,
|
|
success: result => {
|
|
uni.showToast({
|
|
title: '保存成功',
|
|
icon: 'success'
|
|
})
|
|
},
|
|
fail: err => {
|
|
uni.showToast({
|
|
title: '保存失败',
|
|
icon: 'none'
|
|
})
|
|
},
|
|
complete: () => uni.hideLoading()
|
|
})
|
|
},
|
|
complete: () => uni.hideLoading()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped="scoped">
|
|
.swiper{
|
|
width: 100vw;
|
|
height: 100vh;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
z-index: 1000;
|
|
.swiper-item{
|
|
position: relative;
|
|
.movable-area, .movable-view, .swiper-image{
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.controls{
|
|
position: absolute;
|
|
padding: 0 16rpx;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
top: 47vh;
|
|
left: 0;
|
|
font-size: 40rpx;
|
|
width: 100%;
|
|
z-index: 1002;
|
|
}
|
|
.indicators{
|
|
width: 100%;
|
|
position: absolute;
|
|
left: 0;
|
|
bottom: 10vh;
|
|
z-index: 1002;
|
|
display: flex;
|
|
justify-content: center;
|
|
.number, .square, .dot{
|
|
width: max-content;
|
|
height: max-content;
|
|
background-color: #dfe4ea;
|
|
}
|
|
.number{
|
|
padding: 4rpx 26rpx;
|
|
border-radius: 40rpx;
|
|
color: #555555;
|
|
}
|
|
.square{
|
|
border-radius: 40rpx;
|
|
display: flex;
|
|
padding: 2rpx 4rpx;
|
|
view{
|
|
border-radius: 50%;
|
|
}
|
|
}
|
|
.dot{
|
|
display: flex;
|
|
padding: 4rpx 4rpx;
|
|
}
|
|
.indicators-icon{
|
|
width: 20rpx;
|
|
height: 20rpx;
|
|
background-color: #bdc5bd;
|
|
margin: 2rpx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.menu-popup{
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
height: max-content;
|
|
width: 100%;
|
|
background-color: #FFFFFF;
|
|
z-index: 1004;
|
|
padding-bottom: constant(safe-area-inset-bottom);
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
button{
|
|
height: 100rpx;
|
|
line-height: 100rpx;
|
|
background-color: transparent;
|
|
border-bottom: 1rpx solid #e5e5e5;
|
|
&:last-child{
|
|
border-bottom: none;
|
|
}
|
|
}
|
|
}
|
|
// 轻提示框样式
|
|
/deep/.uni-sample-toast{
|
|
z-index: 1002;
|
|
}
|
|
</style>
|