233 lines
5.4 KiB
Vue
233 lines
5.4 KiB
Vue
|
|
<template>
|
||
|
|
<view
|
||
|
|
v-show="ts.visible && isStackTop"
|
||
|
|
class="gnp-wrap"
|
||
|
|
:class="{ 'gnp-wrap--touch': gnpTouching }"
|
||
|
|
:style="gnpWrapDragStyle"
|
||
|
|
@touchstart.stop="gnpOnTouchStart"
|
||
|
|
@touchmove.stop="gnpOnTouchMove"
|
||
|
|
@touchend.stop="gnpOnTouchEnd"
|
||
|
|
@touchcancel.stop="gnpOnTouchCancel"
|
||
|
|
>
|
||
|
|
<view class="gnp-card" :class="{ 'gnp-card--in': ts.enterActive }">
|
||
|
|
<view class="gnp-left">
|
||
|
|
<text class="gnp-time">{{ ts.displayTime }}</text>
|
||
|
|
<text class="gnp-text">{{ ts.displayText }}</text>
|
||
|
|
</view>
|
||
|
|
<view v-if="ts.displayPicture" class="gnp-icon-box">
|
||
|
|
<image class="gnp-thumb" :src="ts.displayPicture" mode="aspectFill" />
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</view>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
import { noticeTopState, hideTopNotice } from '@/common/utils/noticeTopShared.js'
|
||
|
|
|
||
|
|
const INDEX_ROUTE = 'pages/index/index'
|
||
|
|
const UP_SWIPE = 50
|
||
|
|
const TAP = 24
|
||
|
|
const RE_STACK_MS = 300
|
||
|
|
|
||
|
|
export default {
|
||
|
|
name: 'AppTopPushNotice',
|
||
|
|
data() {
|
||
|
|
return {
|
||
|
|
gnpTouchStartY: 0,
|
||
|
|
gnpTouchStartX: 0,
|
||
|
|
gnpDragOffset: 0,
|
||
|
|
gnpTouching: false,
|
||
|
|
_stackTick: 0,
|
||
|
|
_stackTimer: null,
|
||
|
|
}
|
||
|
|
},
|
||
|
|
computed: {
|
||
|
|
ts() {
|
||
|
|
// 依赖页面栈:非栈顶不展示本实例
|
||
|
|
// eslint-disable-next-line no-unused-expressions
|
||
|
|
this._stackTick
|
||
|
|
// 显式读 observable 字段,否则子模板不随推送更新
|
||
|
|
const {
|
||
|
|
visible,
|
||
|
|
enterActive,
|
||
|
|
displayTime,
|
||
|
|
displayText,
|
||
|
|
displayPicture,
|
||
|
|
} = noticeTopState
|
||
|
|
return { visible, enterActive, displayTime, displayText, displayPicture }
|
||
|
|
},
|
||
|
|
isStackTop() {
|
||
|
|
// eslint-disable-next-line no-unused-expressions
|
||
|
|
this._stackTick
|
||
|
|
try {
|
||
|
|
const stack = getCurrentPages()
|
||
|
|
if (!stack || !stack.length) return true
|
||
|
|
const top = stack[stack.length - 1]
|
||
|
|
return top && top.$vm === this.$root
|
||
|
|
} catch (e) {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
},
|
||
|
|
gnpWrapDragStyle() {
|
||
|
|
const o = this.gnpDragOffset
|
||
|
|
if (o === 0) {
|
||
|
|
return {}
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
transform: `translateY(${o}px)`,
|
||
|
|
transition: this.gnpTouching ? 'none' : 'transform 0.2s ease-out',
|
||
|
|
}
|
||
|
|
},
|
||
|
|
},
|
||
|
|
watch: {
|
||
|
|
'ts.visible'(v) {
|
||
|
|
if (v) {
|
||
|
|
this.gnpDragOffset = 0
|
||
|
|
this.gnpTouching = false
|
||
|
|
}
|
||
|
|
},
|
||
|
|
},
|
||
|
|
mounted() {
|
||
|
|
this._stackTimer = setInterval(() => {
|
||
|
|
this._stackTick += 1
|
||
|
|
}, RE_STACK_MS)
|
||
|
|
},
|
||
|
|
beforeDestroy() {
|
||
|
|
if (this._stackTimer) {
|
||
|
|
clearInterval(this._stackTimer)
|
||
|
|
this._stackTimer = null
|
||
|
|
}
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
gnpOnTouchStart(e) {
|
||
|
|
if (!this.ts.visible || !this.isStackTop || !e.touches || !e.touches[0]) return
|
||
|
|
this.gnpTouching = true
|
||
|
|
this.gnpTouchStartY = e.touches[0].clientY
|
||
|
|
this.gnpTouchStartX = e.touches[0].clientX
|
||
|
|
},
|
||
|
|
gnpOnTouchMove(e) {
|
||
|
|
if (!this.ts.visible || !this.gnpTouching || !e.touches || !e.touches[0]) return
|
||
|
|
const y = e.touches[0].clientY
|
||
|
|
const dy = y - this.gnpTouchStartY
|
||
|
|
if (dy < 0) {
|
||
|
|
this.gnpDragOffset = Math.max(dy, -220)
|
||
|
|
} else {
|
||
|
|
this.gnpDragOffset = 0
|
||
|
|
}
|
||
|
|
},
|
||
|
|
gnpOnTouchEnd(e) {
|
||
|
|
if (!this.ts.visible) return
|
||
|
|
const t = e.changedTouches && e.changedTouches[0]
|
||
|
|
const dragAtEnd = this.gnpDragOffset
|
||
|
|
this.gnpTouching = false
|
||
|
|
this.gnpDragOffset = 0
|
||
|
|
if (!t) return
|
||
|
|
const y = t.clientY
|
||
|
|
const x = t.clientX
|
||
|
|
const totalDy = y - this.gnpTouchStartY
|
||
|
|
const totalDx = x - this.gnpTouchStartX
|
||
|
|
if (totalDy < -UP_SWIPE || dragAtEnd < -UP_SWIPE) {
|
||
|
|
hideTopNotice()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if (Math.abs(totalDy) <= TAP && Math.abs(totalDx) <= TAP) {
|
||
|
|
hideTopNotice()
|
||
|
|
this._openNotificationTab()
|
||
|
|
}
|
||
|
|
},
|
||
|
|
gnpOnTouchCancel() {
|
||
|
|
if (!this.ts.visible) return
|
||
|
|
this.gnpTouching = false
|
||
|
|
this.gnpDragOffset = 0
|
||
|
|
},
|
||
|
|
_openNotificationTab() {
|
||
|
|
try {
|
||
|
|
const stack = getCurrentPages()
|
||
|
|
const cur = stack && stack.length ? stack[stack.length - 1] : null
|
||
|
|
const route = cur && (cur.route || (cur.$page && cur.$page.fullPath))
|
||
|
|
if (route && String(route).replace(/^\//, '') === INDEX_ROUTE) {
|
||
|
|
uni.$emit('indexOpenNotificationTab')
|
||
|
|
return
|
||
|
|
}
|
||
|
|
} catch (e) {}
|
||
|
|
uni.reLaunch({ url: '/pages/index/index?tab=2' })
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.gnp-wrap {
|
||
|
|
position: fixed;
|
||
|
|
left: 0;
|
||
|
|
right: 0;
|
||
|
|
top: 100rpx;
|
||
|
|
z-index: 10050;
|
||
|
|
padding-left: 24rpx;
|
||
|
|
padding-right: 24rpx;
|
||
|
|
padding-bottom: 8rpx;
|
||
|
|
box-sizing: border-box;
|
||
|
|
touch-action: none;
|
||
|
|
}
|
||
|
|
.gnp-wrap--touch {
|
||
|
|
z-index: 10051;
|
||
|
|
}
|
||
|
|
.gnp-card {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: row;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
background: #ffffff;
|
||
|
|
border-radius: 24rpx;
|
||
|
|
padding: 30rpx 24rpx;
|
||
|
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
|
||
|
|
transform: translateY(-120%);
|
||
|
|
opacity: 0;
|
||
|
|
transition: transform 0.32s ease-out, opacity 0.28s ease;
|
||
|
|
}
|
||
|
|
.gnp-card--in {
|
||
|
|
transform: translateY(0);
|
||
|
|
opacity: 1;
|
||
|
|
}
|
||
|
|
.gnp-left {
|
||
|
|
flex: 1;
|
||
|
|
min-width: 0;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
padding-right: 20rpx;
|
||
|
|
}
|
||
|
|
.gnp-time {
|
||
|
|
font-size: 24rpx;
|
||
|
|
color: #f59a23;
|
||
|
|
line-height: 1.3;
|
||
|
|
margin-bottom: 8rpx;
|
||
|
|
}
|
||
|
|
.gnp-text {
|
||
|
|
font-size: 28rpx;
|
||
|
|
color: #333333;
|
||
|
|
line-height: 1.45;
|
||
|
|
word-break: break-all;
|
||
|
|
display: -webkit-box;
|
||
|
|
-webkit-box-orient: vertical;
|
||
|
|
-webkit-line-clamp: 3;
|
||
|
|
line-clamp: 3;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
.gnp-icon-box {
|
||
|
|
width: 100rpx;
|
||
|
|
height: 100rpx;
|
||
|
|
border-radius: 20rpx;
|
||
|
|
background: #f2f2f2;
|
||
|
|
flex-shrink: 0;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
.gnp-thumb {
|
||
|
|
width: 100%;
|
||
|
|
height: 100%;
|
||
|
|
display: block;
|
||
|
|
}
|
||
|
|
</style>
|