<!-- 谣言查证卡片 叠加 --> <template> <view class="flipcard" id="flipcard" ref="flipcard"> <view class="flipcard-item" v-for="(item, index) in pages" :class="index%2 ? 'flipcard-odd' : 'flipcard-even'" :key="index" :style="[transformIndex(index),transform(index)]" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend" @touchcancel="touchend" @transitionend="onTransitionEnd(index,'transitionend')" > <view class="flipcard-item-stack-info"> <view style="display: flex;justify-content: space-between;"> <view class="t3">一起来识谣</view> <!-- <view class="t4">{{isVoteFlag ? '已投票' : '未投票'}}</view> --> <view></view> </view> <!-- 谣言 --> <view class="yy-content"> <view class="text">谣言:</view> <view class="title"> {{currentShow.title}} </view> <view class="source"> 来源:{{currentShow.source}} </view> </view> <!-- 投票 --> <view class="tp"> <view class="real" v-if="Number.parseFloat(currentShow.yesNum) > 0" :style="{width: 50 + '%' }" @click="vote(true)"> <text>真</text> <text>{{`${yesRoate}%`}}</text> </view> <view class="fail" v-if="Number.parseFloat(currentShow.noNum) > 0" :style="{width: 50 + '%' }" @click="vote(false)"> <text>假</text> <text>{{`${noRoate}%`}}</text> </view> </view> </view> </view> </view> </template> <script> import { getTouchPoint, getElSize } from '@/common/utils.js' import { isVote, addVote } from '@/api/rumor.js' import { decryption } from '../api/mine.js' export default { props: { pages: { type: Array, default: [] }, stackInit: { type: Object, default: {} }, }, data() { return { el: {}, basicdata: { // 触摸位置信息 start: {}, end: {} }, isVoteFlag:false, // 是否投票标识 temporaryData: { isStackClick: true, // 多了一个参数 offsetY: '', // 手指相对于卡片顶部的距离 poswidth: 0, // 水平滑动距离 posheight: 0, // 垂直滑动距离 lastPosWidth: '', // 最终水平滑动距离 lastPosHeight: '', // 最终垂直滑动距离 lastZindex: '', rotate: 0, lastRotate: 0, visible: this.stackInit.visible || 3, // 展示几个 tracking: false, // 是否是一个手指在触摸 animation: false, // 是否展示动画 currentPage: this.stackInit.currentPage || 0, // 当前第几个 opacity: 1, lastOpacity: 0, swipe: false, // 是否轮播,是否翻页 zIndex: 10 }, id:'' } }, computed: { // 划出面积比例 offsetRatio(e) { let width = this.el.width // 卡片宽 let height = this.el.height // 卡片高 let offsetWidth = width - Math.abs(this.temporaryData.poswidth) // 剩余宽度 = 宽 - 已滑动的距离 let offsetHeight = height - Math.abs(this.temporaryData.posheight) // 剩余高度 let ratio = 1 - (offsetWidth * offsetHeight) / (width * height) || 0 // 计算画出的面积比例 return ratio > 1 ? 1 : ratio }, // 划出宽度比例 offsetWidthRatio() { let width = this.el.offsetWidth let offsetWidth = width - Math.abs(this.temporaryData.poswidth) let ratio = 1 - offsetWidth / width || 0 return ratio }, // 当前展示的谣言信息 currentShow() { return this.pages[this.temporaryData.currentPage] }, // 真比例 yesRoate() { return Math.round(Number(this.currentShow.yesNum) / (Number(this.currentShow.yesNum) + Number(this.currentShow.noNum)) *100) }, noRoate() { return Math.round(Number(this.currentShow.noNum) / (Number(this.currentShow.yesNum) + Number(this.currentShow.noNum)) *100) } }, created() { this.$nextTick(async e => { this.el = await getElSize('flipcard', this) // 卡片的位置对象,上下左右,宽高 console.log('----', this.el) }) const that = this // 获取匿名用户唯一标识 wx.login({ success(res){ console.log('code====',res.code); wx.request({ url: 'https://api.weixin.qq.com/sns/jscode2session', data:{ appid: 'wxab7dd0a14272badf', secret: '1ff1d2119715e82db1adda4fe143eedb', js_code: res.code, grant_type:'authorization_code' }, method:"GET", success(res){ that.id = res.data.openid console.log('openid=====',res.data.openid); // 得到openid console.log('session_key====', res.data.session_key); // 得到 session_key } }) } }) }, watch:{ 'currentShow':{ immediate:true, deep:true, async handler(newVal){ if (this.currentShow) { const userinfo = wx.getStorageSync('userInfoxj') let phone = '' if (userinfo?.phone) { phone = await decryption(userinfo.phone) } const params = { rumorId:this.currentShow.id, // id votePerson:phone ? phone : this.id // 用户唯一标识 } // 查询用户是否投票 // isVote(params).then(res => { // console.log(res, '查询用户是否投票') // this.isVoteFlag = res // }) } } } }, methods: { // 点击真假进行投票 async vote(flag) { // 已经投票 // if(this.isVoteFlag) { // uni.$u.toast('您已投过票') // // this.next() // return // } // 未投票 const userinfo = wx.getStorageSync('userInfoxj') let phone = '' if (userinfo?.phone) { phone = await decryption(userinfo.phone) } const params = { contentId:this.currentShow.contentId, // votePerson:phone ? phone : this.id, // 用户唯一标识 flag: flag ? '1' : '2', deviceUniqueCode: phone ? phone : this.id } addVote(params).then(res => { console.log(res, '投票结果') uni.$u.toast('投票成功') // 投票成功修改 投票状态 // this.isVoteFlag = true // this.next() }) }, // 触摸开始 touchstart(e) { if (this.temporaryData.tracking) { // 多个手指触摸 return } // 是否为touch if (e.type === 'touchstart') { if (e.touches.length > 1) { // 有多个手指在触摸 this.temporaryData.tracking = false return } else { // 只有一个手指 // 记录起始位置 const point = getTouchPoint(e) // 获取触摸坐标 console.log(point) this.basicdata.start.t = new Date().getTime() this.basicdata.start.x = point.x // 触摸横坐标 this.basicdata.start.y = point.y // 触摸纵坐标 this.basicdata.end.x = point.x this.basicdata.end.y = point.y // 触摸纵坐标-卡片相对于屏幕顶部距离=手指相对于卡片顶部距离 this.temporaryData.offsetY = point.y - this.el.top // 手指相对于卡片顶部的距离 } } this.temporaryData.isStackClick = true // 多了一个参数 this.temporaryData.tracking = true // 一个手指 this.temporaryData.animation = false // 展示动画 }, // 触摸滑动 touchmove(e) { this.temporaryData.isStackClick = false // 多了一个参数 // 记录滑动位置 if (this.temporaryData.tracking && !this.temporaryData.animation) { // 一个手指且不展示动画 const point = getTouchPoint(e) // 获取触摸点 // if (e.type === 'touchmove') { // e.preventDefault() // this.basicdata.end.x = point.x // this.basicdata.end.y = point.y // } else { e.preventDefault() this.basicdata.end.x = point.x this.basicdata.end.y = point.y // } // 计算滑动值 滑动距离 = 滑动位置 - 初始位置 this.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.x // 水平滑动距离 this.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.y // 垂直滑动距离 let rotateDirection = this.rotateDirection() // 水平滑动距离是否小于0 左滑< 0 右滑>0 // let angleRatio = this.angleRatio() // this.temporaryData.rotate = rotateDirection * this.offsetWidthRatio * 15 * angleRatio } }, touchend(e, index) { // if(this.temporaryData.isStackClick) { // this.$emit('click', index) // 多了一个参数,触发上层传递事件 // this.temporaryData.isStackClick = false // } // this.temporaryData.isStackClick = true // 多了一个参数 this.temporaryData.tracking = false // 滑动结束几个手指触摸赋予原值 this.temporaryData.animation = true // 滑动结束开启动画 // 滑动结束,触发判断 // 判断划出面积是否大于0.4 if (this.offsetRatio >= 0.4) { // 计算划出后最终位置 let ratio = Math.abs(this.temporaryData.posheight / this.temporaryData.poswidth) // 划出宽高比 this.temporaryData.poswidth = this.temporaryData.poswidth >= 0 ? this.temporaryData.poswidth + 200 : this.temporaryData.poswidth - 200 // 右滑+200 左滑-200 ---- 把水平滑动距离增大 this.temporaryData.posheight = this.temporaryData.posheight >= 0 ? Math.abs(this.temporaryData .poswidth * ratio) : -Math.abs(this.temporaryData.poswidth * ratio) // 通过宽高比例计算出垂直滑动比例 this.temporaryData.opacity = 0 //透明 this.temporaryData.swipe = true this.nextTick() // 不满足条件则滑入 } else { // 划出面积比例小于0.4的话 this.temporaryData.poswidth = 0 // 水平滑动距离置为0 this.temporaryData.posheight = 0 // 垂直滑动距离置为0 this.temporaryData.swipe = false // 是否轮播,是否翻页 this.temporaryData.rotate = 0 // 翻页角度为0 } }, nextTick() { // 记录最终滑动距离 this.temporaryData.lastPosWidth = this.temporaryData.poswidth this.temporaryData.lastPosHeight = this.temporaryData.posheight this.temporaryData.lastRotate = this.temporaryData.rotate this.temporaryData.lastZindex = 20 // 循环currentPage 如果是最后一张的话,就显示第一张,不是的话就显示下一张 this.temporaryData.currentPage = this.temporaryData.currentPage === this.pages.length - 1 ? 0 : this .temporaryData.currentPage + 1 // currentPage切换,整体dom进行变化,把第一层滑动置最低 this.$nextTick(() => { // 数据切换完成正常显示卡片 this.temporaryData.poswidth = 0 this.temporaryData.posheight = 0 this.temporaryData.opacity = 1 this.temporaryData.rotate = 0 }) }, // css完成过渡后触发,就是动画结束触发 onTransitionEnd(index, log) { let lastPage = this.temporaryData.currentPage === 0 ? this.pages.length - 1 : this.temporaryData .currentPage - 1 // dom发生变化正在执行的动画滑动序列已经变为上一层 if (this.temporaryData.swipe && index === lastPage) { this.temporaryData.animation = true this.temporaryData.lastPosWidth = 0 this.temporaryData.lastPosHeight = 0 this.temporaryData.lastOpacity = 0 this.temporaryData.lastRotate = 0 this.temporaryData.swipe = false this.temporaryData.lastZindex = -1 } }, prev() { this.temporaryData.tracking = false // 一根手指 this.temporaryData.animation = true // 开启动画 // 计算划出后最终位置 let width = this.el.width this.temporaryData.poswidth = -width this.temporaryData.posheight = 0 this.temporaryData.opacity = 0 this.temporaryData.rotate = '-3' this.temporaryData.swipe = true this.nextTick() }, next() { this.temporaryData.tracking = false this.temporaryData.animation = true // 计算划出后最终位置 let width = this.el.width this.temporaryData.poswidth = width this.temporaryData.posheight = 0 this.temporaryData.opacity = 0 this.temporaryData.rotate = '3' this.temporaryData.swipe = true this.nextTick() }, // 计算滑动距离是否小于0 rotateDirection() { if (this.temporaryData.poswidth <= 0) { // 滑动水平距离<0 -左滑 return -1 } else { // >0 右滑 return 1 } }, angleRatio() { let height = this.el.height // 卡片高度 let offsetY = this.temporaryData.offsetY // 手指到卡片顶部距离 let ratio = -1 * (2 * offsetY / height - 1) // return ratio || 0 }, inStack(index, currentPage) { let stack = [] let visible = this.temporaryData.visible let length = this.pages.length for (let i = 0; i < visible; i++) { if (currentPage + i < length) { stack.push(currentPage + i) } else { stack.push(currentPage + i - length) } } return stack.indexOf(index) >= 0 }, // 非首页样式切换 transform(index) { let currentPage = this.temporaryData.currentPage let length = this.pages.length let lastPage = currentPage === 0 ? this.pages.length - 1 : currentPage - 1 let style = {} let visible = this.temporaryData.visible if (index === this.temporaryData.currentPage) { return } if (this.inStack(index, currentPage)) { let perIndex = index - currentPage > 0 ? index - currentPage : index - currentPage + length style['opacity'] = '1' style['transform'] = 'translate3D(0,0,' + -1 * 60 * (perIndex - this.offsetRatio) + 'px' + ')' style['zIndex'] = visible - perIndex if (!this.temporaryData.tracking) { style['transitionTimingFunction'] = 'ease' style['transitionDuration'] = 300 + 'ms' } } else if (index === lastPage) { style['transform'] = 'translate3D(' + this.temporaryData.lastPosWidth + 'px' + ',' + this.temporaryData .lastPosHeight + 'px' + ',0px) ' + 'rotate(' + this.temporaryData.lastRotate + 'deg)' style['opacity'] = this.temporaryData.lastOpacity style['zIndex'] = this.temporaryData.lastZindex style['transitionTimingFunction'] = 'ease' style['transitionDuration'] = 300 + 'ms' } else { style['zIndex'] = '-1' style['transform'] = 'translate3D(0,0,' + -1 * visible * 60 + 'px' + ')' } return style }, // 首页样式切换 transformIndex(index) { if (index === this.temporaryData.currentPage) { let style = {} style['transform'] = 'translate3D(' + this.temporaryData.poswidth + 'px' + ',' + this.temporaryData .posheight + 'px' + ',0px) ' + 'rotate(' + this.temporaryData.rotate + 'deg)' style['opacity'] = this.temporaryData.opacity style['zIndex'] = 10 if (this.temporaryData.animation) { style['transitionTimingFunction'] = 'ease' style['transitionDuration'] = (this.temporaryData.animation ? 300 : 0) + 'ms' } return style } }, } } </script> <style lang="scss" scoped> @import '../common/variables.scss'; .flipcard { width: 100%; // height: 100%; height: 600rpx; position: relative; // 层叠效果 perspective: 2000rpx; perspective-origin: 50% 150%; -webkit-perspective: 1000px; -webkit-perspective-origin: 50% 150%; margin: 0; padding: 0; &-odd { background: linear-gradient(#bed5fe 0%, #eef1fd 92%, #fff 100%) !important; } &-even { background: linear-gradient(#3c72d2 0%, #eef1fd 92%, #fff 100%) !important; } &-item { // background: linear-gradient(#bed5fe 0%, #eef1fd 92%, #fff 100%); height: 100%; width: 100%; border-radius: 30rpx; overflow: hidden; position: absolute; opacity: 0; display: -webkit-flex; display: flex; -webkit-flex-direction: column; flex-direction: column; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; pointer-events: auto; padding: 30rpx; /* #ifndef APP-NVUE */ box-sizing: border-box; /* #endif */ box-shadow: 0px 2rpx 30rpx 0px rgba(94, 114, 153, 0.3); &-stack-info { .t3 { padding: 10rpx 30rpx; background-color: #fff; color: #0043ac; font-weight: 600; width: fit-content; border-radius: 16rpx; margin-bottom: 32rpx; } .t4 { padding: 10rpx 30rpx; background-color: #ccc; color: #0043ac; font-weight: 600; width: fit-content; border-radius: 16rpx; margin-bottom: 32rpx; } .yy-content { background-color: red; padding: 10rpx 30rpx; border-radius: 16rpx; background-color: #fff; .text { color: #0043ac; font-weight: 600; } .title { color: #000; font-size: 36rpx; font-weight: 600; margin-top: 32rpx; @include exceeding-line-overflow(3); } .source { margin-top: 20rpx; font-size: 26rpx; color: #c1c1c1; margin-bottom: 20rpx; } } // 投票 .tp { display: flex; width: 100%; margin-top: 32rpx; border-radius: 16rpx; overflow: hidden; color: #fff; background-color: #fff; .real { display: flex; justify-content: space-between; // flex: 1; padding: 0 10rpx; // border: 2rpx solid green; border-right: none; background-color: #21a366; } .fail { display: flex; justify-content: space-between; // flex: 1; text-align: right; background-color: #cc463d; padding: 0 10rpx; // border: 1rpx solid red; border-left: none; } } .problem { height: 180rpx; overflow: hidden; text-overflow: ellipsis; .problem-text { font-size: 32rpx; color: #333333; line-height: 46rpx; } } .desc { margin-top: 36rpx; .desc-text { font-size: 26rpx; color: #999999; } } .bottom-box { bottom: 30rpx; left: 30rpx; .avatar { width: 80rpx; height: 80rpx; border-radius: 50%; margin-right: 20rpx; } .name { font-size: 28rpx; color: #AAAAAA; } } } } } </style>