44 changed files with 6409 additions and 1444 deletions
@ -0,0 +1,84 @@ |
|||
<template> |
|||
<view class="music-player" :class="{'playing': isPlaying}" @click="togglePlay"> |
|||
<text :class="['player-icon', isPlaying ? 'rotate' : '']">{{ isPlaying ? '🎵' : '🔇' }}</text> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import audioManager from '@/utils/audioManager'; |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
isPlaying: audioManager.getPlayingStatus() |
|||
}; |
|||
}, |
|||
mounted() { |
|||
audioManager.init(); |
|||
uni.$on('audioStateChanged', this.updateState); |
|||
this.isPlaying = audioManager.getPlayingStatus(); |
|||
uni.$on('playBackgroundMusic', () => { |
|||
if (!audioManager.getUserDisabled()) { |
|||
audioManager.play(); |
|||
} |
|||
}); |
|||
}, |
|||
beforeDestroy() { |
|||
uni.$off('audioStateChanged', this.updateState); |
|||
}, |
|||
methods: { |
|||
updateState(state) { |
|||
this.isPlaying = state.isPlaying; |
|||
}, |
|||
togglePlay() { |
|||
audioManager.togglePlay(); |
|||
this.isPlaying = audioManager.getPlayingStatus(); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.music-player { |
|||
position: fixed; |
|||
right: 30rpx; |
|||
bottom: 30rpx; |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
background-color: rgba(255, 255, 255, 0.9); |
|||
border-radius: 50%; |
|||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2); |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
z-index: 9; |
|||
|
|||
&:active { |
|||
transform: scale(0.95); |
|||
} |
|||
|
|||
&.playing { |
|||
background-color: rgba(245, 163, 204, 0.9); |
|||
} |
|||
} |
|||
|
|||
.player-icon { |
|||
font-size: 40rpx; |
|||
line-height: 1; |
|||
|
|||
&.rotate { |
|||
display: inline-block; |
|||
animation: rotate 3s linear infinite; |
|||
} |
|||
} |
|||
|
|||
@keyframes rotate { |
|||
from { |
|||
transform: rotate(0deg); |
|||
} |
|||
|
|||
to { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,39 @@ |
|||
<template> |
|||
<view> |
|||
<image @click="buyTaozi" class="taozi" src="https://static.ticket.sz-trip.com/epicSoul/taozi/taozi.png" mode=""></image> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
methods: { |
|||
buyTaozi() { |
|||
uni.setClipboardData({ |
|||
data: 'https://www.xiaohongshu.com/goods-detail/684beba28a6cef0001a9ed2c', |
|||
success: function() { |
|||
uni.showToast({ |
|||
title: '链接已复制,请打开小红书APP查看', |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.taozi { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
position: fixed; |
|||
right: 0; |
|||
top: 25%; |
|||
z-index: 9; |
|||
|
|||
&:active { |
|||
transform: scale(0.9); |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,106 @@ |
|||
<template> |
|||
<view class="title-header-box" :style="{height:`${height}px`}"> |
|||
<view class="title-header" :style="{paddingTop:`${padHeight}px`,height:`${height}px`}"> |
|||
<view class="left" @click="goBack"><uni-icons type="left" size="30"></uni-icons></view> |
|||
<view class="center"> |
|||
<view class="title">三个桃子</view> |
|||
<view class="subtitle">时间里的约定</view> |
|||
</view> |
|||
<view class="right"></view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: "SearchHeader", |
|||
props: ['title', 'icon'], |
|||
watch: { |
|||
'title'(newVal, oldVal) { |
|||
this.title = newVal |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
keywords: '', |
|||
padHeight: 60, |
|||
height: 88 |
|||
} |
|||
}, |
|||
created() { |
|||
const systemInfo = uni.getSystemInfoSync(); |
|||
const statusBarHeight = systemInfo.statusBarHeight; |
|||
const rect = uni.getMenuButtonBoundingClientRect(); |
|||
this.height = statusBarHeight + 55 |
|||
uni.setStorageSync('titleHeight', this.height) |
|||
this.padHeight = systemInfo.statusBarHeight - 15 |
|||
this.right = (systemInfo.screenWidth - rect.right) + rect.width |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.title-header-box { |
|||
background-color: #FFF; |
|||
color: #000000; |
|||
} |
|||
|
|||
.title-header { |
|||
/* background-color: #FFC825; */ |
|||
background: white; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 88rpx; |
|||
position: fixed; |
|||
left: 0; |
|||
right: 0; |
|||
top: 0; |
|||
z-index: 1000; |
|||
padding-top: 60rpx; |
|||
} |
|||
|
|||
.title-header .left { |
|||
padding-left: 30rpx; |
|||
width: 58rpx; |
|||
display: flex; |
|||
position: absolute; |
|||
left: 0; |
|||
} |
|||
|
|||
.title-header .search-box { |
|||
width: 502rpx; |
|||
height: 58rpx; |
|||
border-radius: 29rpx; |
|||
background: #F0F0F0; |
|||
padding: 0 26rpx; |
|||
font-size: 26rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.title-header .search-box .iconfont { |
|||
flex-shrink: 0; |
|||
color: #999999; |
|||
font-size: 30rpx; |
|||
margin-right: 10; |
|||
} |
|||
|
|||
.title-header .search-box .input { |
|||
flex: 1; |
|||
border: none; |
|||
outline: none; |
|||
background: none; |
|||
color: #666; |
|||
} |
|||
|
|||
.title{ |
|||
font-size: 30rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.subtitle{ |
|||
font-size: 22rpx; |
|||
text-align: center; |
|||
} |
|||
</style> |
@ -0,0 +1,575 @@ |
|||
<template> |
|||
<view> |
|||
<view class="message-board"> |
|||
<view class="header"> |
|||
<view class="title-container"> |
|||
<text class="title">留言板</text> |
|||
<text class="subtitle">查看所有留言</text> |
|||
</view> |
|||
<view class="decor decor-1"></view> |
|||
<view class="decor decor-2"></view> |
|||
</view> |
|||
|
|||
<view class="content"> |
|||
<view class="stats-card"> |
|||
<view class="stats-header"> |
|||
<view class="stats-icon-container"> |
|||
<view class="stats-icon"> |
|||
<text class="icon-text">💬</text> |
|||
</view> |
|||
</view> |
|||
<text class="stats-title">全部留言</text> |
|||
<view class="stats-count"> |
|||
<text class="count-text">共 {{ total }} 条</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="message-list"> |
|||
<view v-if="loading && messageList.length === 0" class="empty-state"> |
|||
<view class="loading-container"> |
|||
<view class="loading-spinner"></view> |
|||
<text class="loading-text">加载中...</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view v-else-if="messageList.length === 0" class="empty-state"> |
|||
<view class="empty-container"> |
|||
<view class="empty-icon">📭</view> |
|||
<text class="empty-title">暂无留言</text> |
|||
<text class="empty-desc">还没有人留言呢</text> |
|||
</view> |
|||
</view> |
|||
<view v-else class="message-items"> |
|||
<view v-for="(item, index) in messageList" :key="item.id" class="message-item" |
|||
:style="{ animationDelay: `${index * 100}ms` }"> |
|||
<view class="user-info"> |
|||
<view class="avatar"> |
|||
<text class="avatar-text">{{ getAvatarText(item.nickname) }}</text> |
|||
</view> |
|||
|
|||
<view class="user-details"> |
|||
<view class="user-header"> |
|||
<text class="username">{{ item.nickname }}</text> |
|||
<text class="message-id">#{{ item.id }}</text> |
|||
</view> |
|||
<text class="message-time">{{ formatTime(item.createtime) }}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="message-content"> |
|||
<text class="message-text">{{ item.content }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view v-if="pages > 1" class="pagination"> |
|||
<view class="pagination-container"> |
|||
<button @click="changePage(page - 1)" :disabled="page <= 1" class="page-button prev-button"> |
|||
<text class="button-icon">◀</text> |
|||
<text>上一页</text> |
|||
</button> |
|||
|
|||
<view class="page-info"> |
|||
<text class="current-page">{{ page }}</text> |
|||
<text class="page-divider">/</text> |
|||
<text class="total-pages">{{ pages }}</text> |
|||
</view> |
|||
|
|||
<button @click="changePage(page + 1)" :disabled="page >= pages" class="page-button next-button"> |
|||
<text>下一页</text> |
|||
<text class="button-icon">▶</text> |
|||
</button> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="refresh-container"> |
|||
<button @click="refresh" :disabled="loading" class="refresh-button"> |
|||
<text class="refresh-icon" :class="{ 'rotating': loading }">🔄</text> |
|||
<text>{{ loading ? '刷新中...' : '刷新' }}</text> |
|||
</button> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { |
|||
getMessageList |
|||
} from '@/static/js/common'; |
|||
|
|||
export default { |
|||
props: { |
|||
isActive: { |
|||
type: Boolean, |
|||
default: true |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
loading: false, |
|||
messageList: [], |
|||
total: 0, |
|||
page: 1, |
|||
limit: 10, |
|||
pages: 0 |
|||
}; |
|||
}, |
|||
created() { |
|||
if (this.isActive) { |
|||
this.fetchMessages(); |
|||
} |
|||
}, |
|||
watch: { |
|||
isActive(newVal) { |
|||
if (newVal) { |
|||
this.fetchMessages(); |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
getAvatarText(nickname) { |
|||
return nickname ? nickname.charAt(0).toUpperCase() : '?'; |
|||
}, |
|||
formatTime(timestamp) { |
|||
if (!timestamp) return '未知时间'; |
|||
|
|||
const date = new Date(timestamp * 1000); |
|||
const now = new Date(); |
|||
const diff = now - date; |
|||
|
|||
if (diff < 60000) { |
|||
return '刚刚'; |
|||
} |
|||
if (diff < 3600000) { |
|||
return `${Math.floor(diff / 60000)}分钟前`; |
|||
} |
|||
if (diff < 86400000) { |
|||
return `${Math.floor(diff / 3600000)}小时前`; |
|||
} |
|||
|
|||
const month = date.getMonth() + 1; |
|||
const day = date.getDate(); |
|||
const hour = date.getHours().toString().padStart(2, '0'); |
|||
const minute = date.getMinutes().toString().padStart(2, '0'); |
|||
|
|||
return `${month}月${day}日 ${hour}:${minute}`; |
|||
}, |
|||
async fetchMessages(pageNum = 1) { |
|||
try { |
|||
if (this.loading) return; |
|||
this.loading = true; |
|||
|
|||
const params = { |
|||
page: pageNum, |
|||
limit: this.limit |
|||
}; |
|||
|
|||
const res = await getMessageList(params, { |
|||
loading: { |
|||
title: '加载留言中...' |
|||
}, |
|||
error: true |
|||
}); |
|||
const data = res.data || {}; |
|||
this.messageList = data.list || []; |
|||
this.total = data.total || 0; |
|||
this.page = data.page || 1; |
|||
this.pages = data.pages || 0; |
|||
|
|||
} catch (error) { |
|||
console.error('获取留言异常:', error); |
|||
} finally { |
|||
this.loading = false; |
|||
} |
|||
}, |
|||
changePage(pageNum) { |
|||
if (pageNum < 1 || pageNum > this.pages || pageNum === this.page) return; |
|||
this.fetchMessages(pageNum); |
|||
uni.pageScrollTo({ |
|||
scrollTop: 0, |
|||
duration: 300 |
|||
}); |
|||
}, |
|||
refresh() { |
|||
if (this.loading) return; |
|||
this.fetchMessages(this.page); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.message-board { |
|||
min-height: 100vh; |
|||
background-color: #d76388; |
|||
} |
|||
|
|||
.header { |
|||
position: relative; |
|||
padding: 180rpx 30rpx 40rpx; |
|||
} |
|||
|
|||
.title-container { |
|||
text-align: center; |
|||
} |
|||
|
|||
.title { |
|||
font-size: 48rpx; |
|||
font-weight: bold; |
|||
color: #ffffff; |
|||
margin-bottom: 10rpx; |
|||
display: block; |
|||
} |
|||
|
|||
.subtitle { |
|||
font-size: 26rpx; |
|||
color: rgba(255, 255, 255, 0.8); |
|||
display: block; |
|||
} |
|||
|
|||
.decor { |
|||
position: absolute; |
|||
border-radius: 50%; |
|||
|
|||
&.decor-1 { |
|||
top: 40rpx; |
|||
left: 30rpx; |
|||
width: 100rpx; |
|||
height: 100rpx; |
|||
background-color: rgba(255, 255, 255, 0.1); |
|||
filter: blur(30rpx); |
|||
} |
|||
|
|||
&.decor-2 { |
|||
top: 80rpx; |
|||
right: 40rpx; |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
background-color: rgba(255, 192, 203, 0.2); |
|||
filter: blur(20rpx); |
|||
} |
|||
} |
|||
|
|||
.content { |
|||
padding: 0 30rpx 40rpx; |
|||
} |
|||
|
|||
.stats-card { |
|||
background-color: rgba(255, 255, 255, 0.9); |
|||
backdrop-filter: blur(10rpx); |
|||
border-radius: 30rpx; |
|||
padding: 30rpx; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.stats-header { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.stats-icon-container { |
|||
margin-right: 16rpx; |
|||
} |
|||
|
|||
.stats-icon { |
|||
width: 50rpx; |
|||
height: 50rpx; |
|||
background-color: #4299e1; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.icon-text { |
|||
color: #ffffff; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.stats-title { |
|||
color: #4a5568; |
|||
font-weight: 500; |
|||
font-size: 28rpx; |
|||
flex: 1; |
|||
} |
|||
|
|||
.stats-count { |
|||
background-color: #f7fafc; |
|||
border-radius: 50rpx; |
|||
padding: 8rpx 20rpx; |
|||
} |
|||
|
|||
.count-text { |
|||
color: #718096; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.message-list { |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.empty-state { |
|||
background-color: rgba(255, 255, 255, 0.9); |
|||
backdrop-filter: blur(10rpx); |
|||
border-radius: 30rpx; |
|||
padding: 60rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.loading-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
} |
|||
|
|||
.loading-spinner { |
|||
width: 60rpx; |
|||
height: 60rpx; |
|||
border: 6rpx solid #e2e8f0; |
|||
border-top-color: #9f7aea; |
|||
border-radius: 50%; |
|||
animation: spin 1s linear infinite; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.loading-text { |
|||
color: #718096; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.empty-container { |
|||
text-align: center; |
|||
} |
|||
|
|||
.empty-icon { |
|||
width: 100rpx; |
|||
height: 100rpx; |
|||
background-color: #f7fafc; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
margin: 0 auto 30rpx; |
|||
font-size: 50rpx; |
|||
} |
|||
|
|||
.empty-title { |
|||
color: #2d3748; |
|||
font-size: 34rpx; |
|||
font-weight: 500; |
|||
margin-bottom: 10rpx; |
|||
display: block; |
|||
} |
|||
|
|||
.empty-desc { |
|||
color: #718096; |
|||
font-size: 26rpx; |
|||
display: block; |
|||
} |
|||
|
|||
.message-items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.message-item { |
|||
background-color: rgba(255, 255, 255, 0.9); |
|||
backdrop-filter: blur(10rpx); |
|||
border-radius: 30rpx; |
|||
padding: 30rpx; |
|||
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.05); |
|||
transition: box-shadow 0.2s; |
|||
animation: fadeIn 0.6s ease-out forwards; |
|||
|
|||
&:active { |
|||
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.1); |
|||
} |
|||
} |
|||
|
|||
.user-info { |
|||
display: flex; |
|||
align-items: flex-start; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
background: linear-gradient(135deg, #a78bfa, #ec4899); |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
margin-right: 20rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.avatar-text { |
|||
color: #ffffff; |
|||
font-size: 32rpx; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.user-details { |
|||
flex: 1; |
|||
min-width: 0; |
|||
} |
|||
|
|||
.user-header { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
margin-bottom: 6rpx; |
|||
} |
|||
|
|||
.username { |
|||
font-weight: 500; |
|||
color: #2d3748; |
|||
font-size: 28rpx; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.message-id { |
|||
font-size: 22rpx; |
|||
color: #a0aec0; |
|||
background-color: #f7fafc; |
|||
padding: 4rpx 16rpx; |
|||
border-radius: 50rpx; |
|||
} |
|||
|
|||
.message-time { |
|||
font-size: 22rpx; |
|||
color: #a0aec0; |
|||
} |
|||
|
|||
.message-content { |
|||
margin-left: 100rpx; |
|||
} |
|||
|
|||
.message-text { |
|||
color: #4a5568; |
|||
font-size: 28rpx; |
|||
line-height: 1.6; |
|||
word-break: break-word; |
|||
} |
|||
|
|||
.pagination { |
|||
background-color: rgba(255, 255, 255, 0.9); |
|||
backdrop-filter: blur(10rpx); |
|||
border-radius: 30rpx; |
|||
padding: 20rpx; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.pagination-container { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.page-button { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 10rpx; |
|||
padding: 0 30rpx; |
|||
height: 70rpx; |
|||
font-size: 26rpx; |
|||
font-weight: 500; |
|||
color: #4a5568; |
|||
background-color: #ffffff; |
|||
border: 2rpx solid #e2e8f0; |
|||
border-radius: 12rpx; |
|||
|
|||
&:active { |
|||
background-color: #f7fafc; |
|||
} |
|||
|
|||
&[disabled] { |
|||
opacity: 0.5; |
|||
} |
|||
} |
|||
|
|||
.page-info { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 10rpx; |
|||
padding: 0 20rpx; |
|||
} |
|||
|
|||
.current-page, |
|||
.total-pages { |
|||
font-size: 26rpx; |
|||
color: #4a5568; |
|||
} |
|||
|
|||
.page-divider { |
|||
color: #cbd5e0; |
|||
font-size: 26rpx; |
|||
} |
|||
|
|||
.refresh-container { |
|||
text-align: center; |
|||
} |
|||
|
|||
.refresh-button { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
gap: 10rpx; |
|||
padding: 0 40rpx; |
|||
height: 80rpx; |
|||
background-color: rgba(255, 255, 255, 0.2); |
|||
color: #ffffff; |
|||
border: none; |
|||
border-radius: 16rpx; |
|||
font-size: 28rpx; |
|||
font-weight: 500; |
|||
|
|||
&:active { |
|||
background-color: rgba(255, 255, 255, 0.3); |
|||
} |
|||
|
|||
&[disabled] { |
|||
opacity: 0.5; |
|||
} |
|||
} |
|||
|
|||
.refresh-icon { |
|||
font-size: 28rpx; |
|||
|
|||
&.rotating { |
|||
animation: spin 1s linear infinite; |
|||
} |
|||
} |
|||
|
|||
@keyframes spin { |
|||
from { |
|||
transform: rotate(0deg); |
|||
} |
|||
|
|||
to { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
|||
|
|||
@keyframes fadeIn { |
|||
from { |
|||
opacity: 0; |
|||
transform: translateY(40rpx); |
|||
} |
|||
|
|||
to { |
|||
opacity: 1; |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,508 @@ |
|||
<template> |
|||
<view> |
|||
<image @click="togglePopup" class="suspension-img" :class="{'rotated': isOpen}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/suspension-icon.png" mode=""></image> |
|||
|
|||
<view v-if="isOpen" class="mask" @click="closeMessage"></view> |
|||
|
|||
<view v-if="isOpen" class="message-popup" :style="popupStyle"> |
|||
<view class="popup-content"> |
|||
<view class="popup-header"> |
|||
<text class="popup-title">我要留言</text> |
|||
<view class="header-line"></view> |
|||
</view> |
|||
<input v-model="nickname" class="input-field" placeholder="我的昵称" maxlength="20" :disabled="loading" /> |
|||
<input v-model="email" type="text" class="input-field" placeholder="我的邮箱" :disabled="loading" /> |
|||
<textarea v-model="messageText" class="textarea-field" placeholder="我想对TA说:" maxlength="500" |
|||
:disabled="loading"></textarea> |
|||
<view class="char-count"> |
|||
<text>{{ messageText.length }}/500</text> |
|||
</view> |
|||
<view class="button-group"> |
|||
<button @click="closeMessage" :disabled="loading" class="btn-cancel"> |
|||
返回 |
|||
</button> |
|||
<button @click="handleSubmit" :disabled="loading" class="btn-submit" |
|||
:class="{'btn-disabled': !canSubmit}"> |
|||
<view v-if="loading" class="loading-icon"></view> |
|||
<text>{{ loading ? '发布中...' : '发布' }}</text> |
|||
</button> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view v-if="toast.show" class="toast" :class="toast.type"> |
|||
<view class="toast-icon" :class="toast.type"> |
|||
<view v-if="toast.type === 'success'" class="success-icon"></view> |
|||
<view v-if="toast.type === 'error'" class="error-icon"></view> |
|||
</view> |
|||
<text class="toast-message">{{ toast.message }}</text> |
|||
<view class="toast-progress"> |
|||
<view class="progress-bar" :style="{animationDuration: '3s'}"></view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { |
|||
sendMessage |
|||
} from '@/static/js/common'; |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
isOpen: false, |
|||
popupStyle: { |
|||
opacity: 0, |
|||
transform: 'translate(-50%, -50%) scale(0.9)' |
|||
}, |
|||
nickname: "", |
|||
email: "", |
|||
messageText: "", |
|||
loading: false, |
|||
toast: { |
|||
show: false, |
|||
type: 'success', |
|||
message: '' |
|||
} |
|||
}; |
|||
}, |
|||
computed: { |
|||
canSubmit() { |
|||
const hasNickname = this.nickname.trim().length > 0; |
|||
const hasMessage = this.messageText.trim().length > 0; |
|||
const validEmail = this.isValidEmail(this.email); |
|||
|
|||
return hasNickname && hasMessage && validEmail; |
|||
} |
|||
}, |
|||
watch: { |
|||
nickname() { |
|||
// 可以在这里添加日志输出 |
|||
}, |
|||
email() { |
|||
// 可以在这里添加日志输出 |
|||
}, |
|||
messageText() { |
|||
// 可以在这里添加日志输出 |
|||
} |
|||
}, |
|||
methods: { |
|||
togglePopup() { |
|||
if (this.isOpen) { |
|||
this.closeMessage(); |
|||
} else { |
|||
this.openPop(); |
|||
} |
|||
}, |
|||
openPop() { |
|||
this.isOpen = true; |
|||
setTimeout(() => { |
|||
this.popupStyle = { |
|||
opacity: 1, |
|||
transform: 'translate(-50%, -50%) scale(1)' |
|||
}; |
|||
}, 50); |
|||
}, |
|||
closeMessage() { |
|||
this.popupStyle = { |
|||
opacity: 0, |
|||
transform: 'translate(-50%, -50%) scale(0.9)' |
|||
}; |
|||
setTimeout(() => { |
|||
this.isOpen = false; |
|||
}, 400); |
|||
}, |
|||
isValidEmail(email) { |
|||
if (!email.trim()) return false; |
|||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; |
|||
return emailRegex.test(email); |
|||
}, |
|||
showToast(type, message) { |
|||
this.toast.show = true; |
|||
this.toast.type = type; |
|||
this.toast.message = message; |
|||
setTimeout(() => { |
|||
this.toast.show = false; |
|||
}, 3000); |
|||
}, |
|||
handleSubmit() { |
|||
if (!this.nickname.trim()) { |
|||
this.showToast('error', '请填写昵称'); |
|||
return; |
|||
} |
|||
|
|||
if (!this.isValidEmail(this.email)) { |
|||
this.showToast('error', '请填写正确的邮箱地址'); |
|||
return; |
|||
} |
|||
|
|||
if (!this.messageText.trim()) { |
|||
this.showToast('error', '请填写留言内容'); |
|||
return; |
|||
} |
|||
this.submitMessage(); |
|||
}, |
|||
async submitMessage() { |
|||
try { |
|||
this.loading = true; |
|||
const res = await sendMessage({ |
|||
nickname: this.nickname.trim(), |
|||
email: this.email.trim(), |
|||
content: this.messageText.trim() |
|||
}); |
|||
|
|||
if (res && res.code === 1) { |
|||
this.nickname = ""; |
|||
this.email = ""; |
|||
this.messageText = ""; |
|||
this.showToast('success', '留言发布成功!'); |
|||
setTimeout(() => { |
|||
this.closeMessage(); |
|||
}, 1500); |
|||
} else { |
|||
this.showToast('error', res?.msg || '发布失败,请重试'); |
|||
} |
|||
} catch (error) { |
|||
console.error('发布留言失败:', error); |
|||
this.showToast('error', '网络错误,请重试'); |
|||
} finally { |
|||
this.loading = false; |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.suspension-img { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
position: fixed; |
|||
right: 0; |
|||
top: 18%; |
|||
z-index: 9; |
|||
transition: transform 0.3s ease; |
|||
|
|||
&.rotated { |
|||
transform: rotate(180deg); |
|||
} |
|||
|
|||
&:active { |
|||
opacity: 0.8; |
|||
} |
|||
} |
|||
|
|||
.mask { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
z-index: 10; |
|||
animation: fadeIn 0.3s ease forwards; |
|||
} |
|||
|
|||
.message-popup { |
|||
position: fixed; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translate(-50%, -50%) scale(0.9); |
|||
z-index: 11; |
|||
width: 75%; |
|||
max-width: 650rpx; |
|||
transition: opacity 0.4s ease, transform 0.4s ease; |
|||
} |
|||
|
|||
.popup-content { |
|||
background-color: #fff; |
|||
border-radius: 20rpx; |
|||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.2); |
|||
padding: 30rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
max-height: 80vh; |
|||
} |
|||
|
|||
.popup-header { |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.popup-title { |
|||
font-size: 40rpx; |
|||
font-weight: bold; |
|||
color: #ec4899; |
|||
margin-bottom: 10rpx; |
|||
} |
|||
|
|||
.header-line { |
|||
height: 2rpx; |
|||
background-color: #ec4899; |
|||
width: 100%; |
|||
} |
|||
|
|||
.input-field { |
|||
height: 90rpx; |
|||
padding: 0 20rpx; |
|||
border: 2rpx solid #ccc; |
|||
border-radius: 10rpx; |
|||
margin-bottom: 20rpx; |
|||
font-size: 28rpx; |
|||
|
|||
&:focus { |
|||
border-color: #ec4899; |
|||
} |
|||
} |
|||
|
|||
.textarea-field { |
|||
text-indent: 10rpx; |
|||
width: 100%; |
|||
height: 300rpx; |
|||
border: 2rpx solid #ccc; |
|||
border-radius: 10rpx; |
|||
margin-bottom: 20rpx; |
|||
font-size: 28rpx; |
|||
|
|||
&:focus { |
|||
border-color: #ec4899; |
|||
} |
|||
} |
|||
|
|||
.char-count { |
|||
text-align: right; |
|||
font-size: 24rpx; |
|||
color: #888; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.button-group { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.btn-cancel { |
|||
flex: 1; |
|||
background-color: #fff; |
|||
color: #ec4899; |
|||
height: 80rpx; |
|||
line-height: 80rpx; |
|||
border-radius: 40rpx; |
|||
border: 2rpx solid #ccc; |
|||
font-size: 28rpx; |
|||
text-align: center; |
|||
|
|||
&:active { |
|||
background-color: #f5f5f5; |
|||
} |
|||
} |
|||
|
|||
.btn-submit { |
|||
flex: 1; |
|||
background-color: #f5a3cc; |
|||
color: #fff; |
|||
height: 80rpx; |
|||
line-height: 80rpx; |
|||
border-radius: 40rpx; |
|||
border: none; |
|||
font-size: 28rpx; |
|||
text-align: center; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
|
|||
&:active { |
|||
background-color: darken(#ec4899, 10%); |
|||
} |
|||
} |
|||
|
|||
.loading-icon { |
|||
width: 30rpx; |
|||
height: 30rpx; |
|||
border: 4rpx solid #fff; |
|||
border-top-color: transparent; |
|||
border-radius: 50%; |
|||
margin-right: 10rpx; |
|||
animation: spin 1s linear infinite; |
|||
} |
|||
|
|||
@keyframes spin { |
|||
from { |
|||
transform: rotate(0deg); |
|||
} |
|||
|
|||
to { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
|||
|
|||
.toast { |
|||
position: fixed; |
|||
top: 160rpx; |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
z-index: 100000; |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 20rpx 30rpx; |
|||
border-radius: 16rpx; |
|||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.3); |
|||
backdrop-filter: blur(10rpx); |
|||
max-width: 80%; |
|||
animation: toastIn 0.5s ease forwards; |
|||
|
|||
&.success { |
|||
background-color: rgba(34, 197, 94, 0.9); |
|||
} |
|||
|
|||
&.error { |
|||
background-color: rgba(239, 68, 68, 0.9); |
|||
} |
|||
} |
|||
|
|||
@keyframes fadeIn { |
|||
from { |
|||
opacity: 0; |
|||
} |
|||
|
|||
to { |
|||
opacity: 1; |
|||
} |
|||
} |
|||
|
|||
@keyframes toastIn { |
|||
from { |
|||
opacity: 0; |
|||
transform: translateX(-50%) translateY(-20rpx); |
|||
} |
|||
|
|||
to { |
|||
opacity: 1; |
|||
transform: translateX(-50%) translateY(0); |
|||
} |
|||
} |
|||
|
|||
.toast-icon { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
margin-right: 20rpx; |
|||
position: relative; |
|||
} |
|||
|
|||
.success-icon { |
|||
width: 100%; |
|||
height: 100%; |
|||
border-radius: 50%; |
|||
border: 4rpx solid #fff; |
|||
position: relative; |
|||
|
|||
&:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 40%; |
|||
left: 28%; |
|||
width: 45%; |
|||
height: 25%; |
|||
border-left: 4rpx solid #fff; |
|||
border-bottom: 4rpx solid #fff; |
|||
transform: rotate(-45deg); |
|||
animation: checkmark 0.8s ease-out forwards; |
|||
} |
|||
} |
|||
|
|||
.error-icon { |
|||
width: 100%; |
|||
height: 100%; |
|||
border-radius: 50%; |
|||
border: 4rpx solid #fff; |
|||
position: relative; |
|||
|
|||
&:before, |
|||
&:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
width: 60%; |
|||
height: 4rpx; |
|||
background-color: #fff; |
|||
animation: crossmark 0.8s ease-out forwards; |
|||
} |
|||
|
|||
&:before { |
|||
transform: translate(-50%, -50%) rotate(45deg); |
|||
} |
|||
|
|||
&:after { |
|||
transform: translate(-50%, -50%) rotate(-45deg); |
|||
} |
|||
} |
|||
|
|||
.toast-message { |
|||
color: #fff; |
|||
font-size: 28rpx; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.toast-progress { |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
height: 4rpx; |
|||
background-color: rgba(255, 255, 255, 0.2); |
|||
border-radius: 0 0 16rpx 16rpx; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.progress-bar { |
|||
height: 100%; |
|||
background-color: rgba(255, 255, 255, 0.5); |
|||
width: 100%; |
|||
animation: progress 3s linear forwards; |
|||
} |
|||
|
|||
@keyframes progress { |
|||
from { |
|||
width: 100%; |
|||
} |
|||
|
|||
to { |
|||
width: 0%; |
|||
} |
|||
} |
|||
|
|||
@keyframes checkmark { |
|||
0% { |
|||
opacity: 0; |
|||
transform: scale(0) rotate(-45deg); |
|||
} |
|||
|
|||
50% { |
|||
opacity: 1; |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: scale(1) rotate(-45deg); |
|||
} |
|||
} |
|||
|
|||
@keyframes crossmark { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translate(-50%, -50%) scale(0) rotate(var(--rotation, 45deg)); |
|||
} |
|||
|
|||
50% { |
|||
opacity: 1; |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translate(-50%, -50%) scale(1) rotate(var(--rotation, 45deg)); |
|||
} |
|||
} |
|||
</style> |
Binary file not shown.
@ -0,0 +1,36 @@ |
|||
import { |
|||
get, |
|||
post |
|||
} from '@/utils/request'; |
|||
|
|||
const API = { |
|||
MESSAGE: { |
|||
LIST: '/api/questionnaire/index?page=1&limit=10', //获取问卷列表
|
|||
DETAIL: '/api/questionnaire/detail', //获取问卷详情
|
|||
ANSWER:'/api/questionnaire/answer', //提交答案
|
|||
STATISTICS:'/api/questionnaire/statistics', //获取统计选项
|
|||
ADD: '/api/liuyan/add', |
|||
LISTS: '/api/liuyan/lists' |
|||
} |
|||
}; |
|||
|
|||
export const getQuestionnaireList = (params, options = {}) => { |
|||
return get(API.MESSAGE.LIST, params, options); |
|||
}; |
|||
export const getQuestionnaireDetail = (params, options = {}) => { |
|||
return get(API.MESSAGE.DETAIL, params, options); |
|||
}; |
|||
export const getStatistics = (params, options = {}) => { |
|||
return get(API.MESSAGE.STATISTICS, params, options); |
|||
}; |
|||
export const sendAnswer = (data) => { |
|||
return post(API.MESSAGE.ANSWER, data); |
|||
}; |
|||
export const sendMessage = (data) => { |
|||
return post(API.MESSAGE.ADD, data); |
|||
}; |
|||
export const getMessageList = (params, options = {}) => { |
|||
return get(API.MESSAGE.LISTS, params, options); |
|||
}; |
|||
|
|||
export const ApiPath = API; |
@ -0,0 +1,912 @@ |
|||
<template> |
|||
<view class="bg" v-if="info"> |
|||
<view class="address box" @click="changeAddressPopup('open', '', key)" v-if="!contacts && info[0].is_post == 1"> |
|||
<!-- <image src="https://kunshan.xmainc.com/uploads/20230105/870e4ce1d5a661986d8a54e9f3c2b0b3.png" mode="aspectFill"></image> --> |
|||
<view class="text">+ 添加邮寄地址</view> |
|||
</view> |
|||
<view class="contacts box" v-if="contacts && info[0].is_post == 1"> |
|||
<view class="contacts-left"> |
|||
<view class="name-phone"> |
|||
<view class="name">{{ contacts.name }}</view> |
|||
<view class="phone">{{ contacts.tel }}</view> |
|||
</view> |
|||
<view class="adds text-overflowRows">{{ contacts.province_text + contacts.city_text + contacts.district_text + contacts.detail_addr }}</view> |
|||
</view> |
|||
<image @click="changeAddressPopup('open', '', key)" src="https://static.ticket.sz-trip.com/taizhou/images/detail/edit.png" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="commodity box"> |
|||
<view class="merchant-name"> |
|||
{{info[0].merchant_name}} |
|||
</view> |
|||
<view style="margin-top: 20rpx;" class="" v-for="(item,index) in info" :key="item.id"> |
|||
<view class="goods"> |
|||
<image class="img" :src="showImg(item.specifications_image)" mode=""></image> |
|||
<view class="" style="display: flex;flex-direction: column;justify-content: space-between;"> |
|||
<view class=""> |
|||
{{item.good_name}} |
|||
</view> |
|||
<!-- <view> |
|||
<span class="info-tags" v-for="(itemT,indexT) in item.goods_new_tag.split(',').splice(0,2)" :key="indexT">{{itemT}}</span> |
|||
</view> --> |
|||
</view> |
|||
</view> |
|||
<view class="sku-info"> |
|||
<view class="title"> |
|||
<view class="text-overflowRows" style="font-size: 31rpx;font-weight: 500;color: #000;">{{ item.specifications_name }}</view> |
|||
<view class="price-list"> |
|||
<view class="price-r">¥{{ item.Specifications_money / 100 }}</view> |
|||
<!-- <view class="price-g">¥{{ info.skuInfo.price / 100 }}</view> --> |
|||
<view class="price-g" v-if="item.post_money">¥{{ item.post_money / 100 }}</view> |
|||
</view> |
|||
</view> |
|||
<view class="num-box"> |
|||
<view class="ctrl" @click="reduce(item)">-</view> |
|||
<view class="num">{{item.num}}</view> |
|||
<!-- <input class="num" :disabled="true" type="text" v-model="item.num" @input="rge($event)" @blur="setV()" /> --> |
|||
<view class="ctrl" @click="plus(item)">+</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="person-info box" v-if="info[0].is_post == 0 && info[0].is_card == 1"> |
|||
<view class="person-title">购买信息</view> |
|||
<view class="p_name line flex"> |
|||
<view class="left">用户姓名:</view> |
|||
<input class="input" type="text" placeholder="请输入姓名" v-model="reserve_name" /> |
|||
</view> |
|||
<view class="p_id line flex"> |
|||
<view class="left">身份证号:</view> |
|||
<input class="input" type="text" placeholder="请输入身份证号" v-model="reserve_idcard" /> |
|||
</view> |
|||
<view class="p_tel flex"> |
|||
<view class="left">手机号码:</view> |
|||
<input class="input" type="text" placeholder="接受预定信息的号码" v-model="reserve_phone" /> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 优惠 --> |
|||
<view class="youhui"> |
|||
<view class="" style="font-size: 32rpx;font-weight: bold;color: #000;">优惠</view> |
|||
<view class="youhui-price"> |
|||
<view class="" style="color: #4D526C;font-weight: 500;"> |
|||
活动促销: |
|||
</view> |
|||
<view class="" style="color: #FC5109;font-weight: bold;"> |
|||
-¥{{delPrice}} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- <view class="remark"> |
|||
<view class="">备注:</view> |
|||
<input type="text" placeholder="订单备注" v-model="remark" maxlength="50"/> |
|||
</view> --> |
|||
<!-- <view class="" style="font-size: 24rpx;font-weight: 500;color: #FC5109;margin-left: 26.7rpx;margin-top: 27.33rpx;"> |
|||
*温馨提示:本商品不支持退换货 |
|||
</view> --> |
|||
<view class="btn-list"> |
|||
<view class="price-box"> |
|||
<view class="text">合计:</view> |
|||
<view class="price">{{ (allPrice + post)/100}}</view> |
|||
<!-- <view class="price">{{ getAllPrice() - disPrice + (post / 100) }}</view> --> |
|||
<view class="post" v-if="post">含邮费:¥{{ post / 100 }}</view> |
|||
</view> |
|||
<view class="btn" @click="order()">立即购买</view> |
|||
</view> |
|||
|
|||
<!-- 选择收货地址弹窗 --> |
|||
<uni-popup ref="addressPopup" type="bottom" backgroundColor="#F4F4F4"> |
|||
<view class="people-popup"> |
|||
<view class="top-box"> |
|||
<view class="top flex-between"> |
|||
<text class="text-overflow" @click="changeAddressPopup('close')">取消</text> |
|||
<text class="confirm" @click="changeAddressPopup('close', 'confirm')">确定</text> |
|||
</view> |
|||
</view> |
|||
<navigator url="/subPackages/user/myAddressAdd" class="button">添加收货地址</navigator> |
|||
<view class="popup-list" v-if="addressList.length > 0"> |
|||
<view class="popup-item" v-for="(item, index) in addressList" :key="index" @click="seldThisAddress(item)"> |
|||
<view class="item-top flex-between"> |
|||
<view> |
|||
<view class="name flex-start"> |
|||
{{ item.name }} |
|||
<text>{{ item.tel }}</text> |
|||
<text class="tag" v-if="item.is_default == 1">默认</text> |
|||
</view> |
|||
<view class="subtitle">{{ item.address }}</view> |
|||
</view> |
|||
<navigator :url="`/subPackages/user/myAddressAdd?id=${item.id}`"> |
|||
<img src="https://static.ticket.sz-trip.com/taizhou/images/detail/edit.png" alt="" /> |
|||
</navigator> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</uni-popup> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
contacts: null, |
|||
info: null, |
|||
num: 1, |
|||
post: 0, |
|||
flag: true, |
|||
addressList: [], |
|||
reserve_name: '', |
|||
reserve_idcard: '', |
|||
reserve_phone: '', |
|||
remark: '', |
|||
allPrice: 0, // 折扣后总价 |
|||
discounts:null, |
|||
disPrice: {}, // 折扣对象 |
|||
delPrice:0 |
|||
// disPrice: 0 |
|||
}; |
|||
}, |
|||
onLoad() { |
|||
this.info = this.$store.state.user.sshoppingCart |
|||
console.log('info',this.info); |
|||
|
|||
this.info.forEach(item => { |
|||
this.getDiscount(item) |
|||
}) |
|||
if (!this.info) { |
|||
uni.navigateBack(); |
|||
} |
|||
console.log('----***info***------',this.info); |
|||
}, |
|||
onShow() { |
|||
this.getContacts(); |
|||
this.getAllAddressList(); |
|||
// this.getAllPrice() |
|||
// this.getTotal() |
|||
}, |
|||
methods: { |
|||
// 获取总价 |
|||
getAllPrice() { |
|||
this.info.forEach(item => { |
|||
if ((item.Specifications_money * item.num) - (this.disPrice[item.specifications_id]*100) <= 0) { |
|||
item.price1 = this.disPrice[item.specifications_id] |
|||
item.price = 0 |
|||
} else{ |
|||
item.price1 = this.disPrice[item.specifications_id] |
|||
let price = (item.Specifications_money * item.num) - (this.disPrice[item.specifications_id]*100) |
|||
item.price = +price.toFixed(2) |
|||
} |
|||
}) |
|||
let allPrice = 0 |
|||
let delPrice = 0 |
|||
this.info.forEach(item => { |
|||
delPrice += item.price1 |
|||
allPrice += item.price |
|||
}) |
|||
this.delPrice = delPrice |
|||
console.log('delPrice',this.delPrice); |
|||
this.allPrice = allPrice |
|||
}, |
|||
|
|||
getContacts() { |
|||
if (this.info[0].is_post == 0) { |
|||
return; |
|||
} |
|||
this.Post({}, '/api/user/getDefaultConsignee').then(res => { |
|||
if (res) { |
|||
this.contacts = res.data; |
|||
this.getPost(); |
|||
} |
|||
}); |
|||
}, |
|||
getPost() { |
|||
if (this.info[0].is_post == 0 || !this.contacts) { |
|||
return; |
|||
} |
|||
console.log(this.info,55555555,this.contacts); |
|||
this.flag = false; |
|||
let data = this.info.map((item) => { |
|||
return { specifications_id:item.specifications_id, num: item.num, consignee_id: this.contacts.id } |
|||
}) |
|||
// let data = [{ specifications_id: this.info.skuInfo.id, num: this.num, consignee_id: this.contacts.id }]; |
|||
console.log(data); |
|||
this.Post({ data:JSON.stringify(data)}, '/api/order/getNewPost').then(res => { |
|||
if (res) { |
|||
this.post = 0 |
|||
res.data.forEach((item)=> { |
|||
this.post += item.post_money |
|||
const postItem = this.info.find(i => i.specifications_id == item.specifications_id) |
|||
if(postItem) postItem.post_money = item.post_money |
|||
}) |
|||
this.flag = true; |
|||
} |
|||
}); |
|||
}, |
|||
rge(val) { |
|||
this.$nextTick(() => { |
|||
this.num = val.detail.value.replace(/^(0+)|[^\d]+/g, ''); |
|||
}); |
|||
}, |
|||
setV() { |
|||
if (!this.num) { |
|||
this.$nextTick(() => { |
|||
this.num = 1; |
|||
if (this.flag) { |
|||
this.getPost(); |
|||
} |
|||
}); |
|||
} else { |
|||
if (this.flag) { |
|||
this.getPost(); |
|||
} |
|||
} |
|||
}, |
|||
plus(item) { |
|||
// this.num = Number(this.num); |
|||
this.$nextTick(() => { |
|||
item.num += 1; |
|||
this.getDiscount(item) |
|||
// this.disPrice = this.calculateTotalPrice(this.discounts,this.num) |
|||
// console.log(this.disPrice); |
|||
if (this.flag) { |
|||
this.getPost(); |
|||
} |
|||
}); |
|||
}, |
|||
reduce(item) { |
|||
if (item.num > 1) { |
|||
this.$nextTick(() => { |
|||
item.num -= 1; |
|||
this.getDiscount(item) |
|||
// this.disPrice = this.calculateTotalPrice(item.discounts,item.num) |
|||
console.log(this.disPrice); |
|||
if (this.flag) { |
|||
this.getPost(); |
|||
} |
|||
}); |
|||
} |
|||
// this.num = Number(this.num); |
|||
// if (this.num > 1) { |
|||
// this.$nextTick(() => { |
|||
// this.num -= 1; |
|||
// this.disPrice = this.calculateTotalPrice(this.discounts,this.num) |
|||
// console.log(this.disPrice); |
|||
// if (this.flag) { |
|||
// this.getPost(); |
|||
// } |
|||
// }); |
|||
// } |
|||
}, |
|||
//获取数量折扣 |
|||
getDiscount(item) { |
|||
this.Post({ specifications_id: item.specifications_id }, '/api/goods/getSpeNumDiscount').then(res => { |
|||
this.discounts = res.data.discount_rule; |
|||
if (this.discounts) { |
|||
let disPrice_item = this.calculateTotalPrice(this.discounts,item.num) |
|||
this.disPrice[item.specifications_id] = +disPrice_item |
|||
} else { |
|||
this.disPrice[item.specifications_id] = 0 |
|||
} |
|||
this.getAllPrice() |
|||
}); |
|||
}, |
|||
//计算折扣价 |
|||
calculateTotalPrice(discounts, selectedQuantity) { |
|||
if (discounts) { |
|||
let totalPrice = 0; |
|||
for (const discount of discounts) { |
|||
if (selectedQuantity >= discount.num) { |
|||
totalPrice = discount.money; |
|||
} |
|||
} |
|||
return totalPrice; |
|||
} else { |
|||
return 0; |
|||
} |
|||
}, |
|||
// 选择收货地址弹窗 |
|||
changeAddressPopup(type, confirm, index) { |
|||
if (type == 'open') { |
|||
this.$refs.addressPopup.open('bottom'); |
|||
} |
|||
else this.$refs.addressPopup.close(); |
|||
this.$forceUpdate(); |
|||
}, |
|||
// 获取收货地址列表 |
|||
getAllAddressList() { |
|||
this.Post({}, '/api/user/consigneeList').then(res => { |
|||
if (res.code === 200) this.addressList = res.data; |
|||
}); |
|||
}, |
|||
// 选择收货地址 |
|||
seldThisAddress(item) { |
|||
if (!this.contacts) this.contacts = {}; |
|||
Object.assign(this.contacts, item); |
|||
if (this.flag) { |
|||
this.getPost(); |
|||
} |
|||
this.$refs.addressPopup.close(); |
|||
this.$forceUpdate(); |
|||
}, |
|||
// 预定 |
|||
order() { |
|||
if (this.info[0].is_post == 1 && !this.contacts) { |
|||
uni.showToast({ |
|||
title: '请选择收货地址', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
if (this.info[0].is_post == 0 && this.info[0].is_card == 1) { |
|||
if (!this.reserve_name) { |
|||
uni.showToast({ |
|||
title: '请输入姓名', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
if (!this.reserve_phone) { |
|||
uni.showToast({ |
|||
title: '请输入电话', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
if (!this.reserve_idcard) { |
|||
uni.showToast({ |
|||
title: '请输入身份证', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
} |
|||
// let goods = []; |
|||
let goods = this.info.map((item) => { |
|||
return { specifications_id:item.specifications_id, num: item.num, consignee_id: item.is_post == 1 ? this.contacts.id : null } |
|||
}) |
|||
// let goodsItem = { |
|||
// specifications_id: this.info.skuInfo.id, |
|||
// num: this.num, |
|||
// consignee_id: this.info.skuInfo.is_post == 1 ? this.contacts.id : null |
|||
// }; |
|||
// goods.push(goodsItem); |
|||
let data = { |
|||
goods: goods, |
|||
coupon: null, |
|||
remark: this.remark, |
|||
is_post: this.info[0].is_post, |
|||
reserve_name: this.reserve_name, |
|||
reserve_phone: this.reserve_phone, |
|||
reserve_idcard: this.reserve_idcard |
|||
}; |
|||
this.Post( |
|||
{ |
|||
method: 'POST', |
|||
data: JSON.stringify(data) |
|||
}, |
|||
'/api/order/place' |
|||
).then(resT => { |
|||
if (resT.code == 200) { |
|||
this.Post( |
|||
{ |
|||
order_id: resT.data.order_id, |
|||
type: "miniprogram", |
|||
// #ifdef MP-WEIXIN |
|||
platform: 'miniprogram', |
|||
// #endif |
|||
// #ifdef H5 |
|||
platform: 'JSAPI', |
|||
// #endif |
|||
}, |
|||
'/api/pay/unify' |
|||
).then(res => { |
|||
if (res.data) { |
|||
// #ifdef MP-WEIXIN |
|||
uni.requestPayment({ |
|||
nonceStr: res.data.nonceStr, |
|||
package: res.data.package, |
|||
paySign: res.data.paySign, |
|||
signType: res.data.signType, |
|||
timeStamp: res.data.timeStamp, |
|||
complete() { |
|||
|
|||
}, |
|||
success(){ |
|||
uni.showLoading({ |
|||
title:'加载中...' |
|||
}) |
|||
setTimeout(()=>{ |
|||
uni.hideLoading() |
|||
uni.navigateTo({ |
|||
url: '/subPackages/order/detail?id='+resT.data.order_id |
|||
}); |
|||
},2000) |
|||
} |
|||
}); |
|||
// #endif |
|||
// #ifdef H5 |
|||
WeixinJSBridge.invoke('getBrandWCPayRequest', { |
|||
appId: res.data.appId, |
|||
timeStamp: res.data.timeStamp, |
|||
nonceStr: res.data.nonceStr, |
|||
package: res.data.package, |
|||
signType: res.data.signType, |
|||
paySign: res.data.paySign |
|||
}, function(Twores) { |
|||
if (Twores.err_msg === 'get_brand_wcpay_request:ok') { |
|||
uni.showLoading({ |
|||
title:'加载中...' |
|||
}) |
|||
setTimeout(()=>{ |
|||
uni.hideLoading() |
|||
uni.navigateTo({ |
|||
url: '/subPackages/order/detail?id='+resT.data.order_id |
|||
}); |
|||
},2000) |
|||
} else { |
|||
|
|||
} |
|||
}); |
|||
// #endif |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.bg { |
|||
min-height: 100vh; |
|||
overflow-x: hidden; |
|||
background: #f2f4f7; |
|||
padding-bottom: 200rpx; |
|||
} |
|||
view { |
|||
box-sizing: border-box; |
|||
} |
|||
.box { |
|||
width: 710rpx; |
|||
min-height: 100rpx; |
|||
padding: 30rpx; |
|||
background: #ffffff; |
|||
border-radius: 16rpx; |
|||
margin: 0 auto; |
|||
margin-top: 20rpx; |
|||
} |
|||
.address { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
image { |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
} |
|||
.text { |
|||
font-size: 28rpx; |
|||
font-family: PingFangSC-Regular, PingFang SC; |
|||
font-weight: 400; |
|||
color: #6c7a94; |
|||
margin-left: 18rpx; |
|||
} |
|||
} |
|||
.commodity { |
|||
// display: flex; |
|||
// align-items: center; |
|||
.img { |
|||
width: 140rpx; |
|||
height: 140rpx; |
|||
background: #f2f4f7; |
|||
border-radius: 10rpx; |
|||
margin-right: 18rpx; |
|||
} |
|||
.title { |
|||
width: 300rpx; |
|||
margin-left: 20rpx; |
|||
font-size: 30rpx; |
|||
font-family: PingFangSC-Medium, PingFang SC; |
|||
font-weight: 500; |
|||
color: #000000; |
|||
|
|||
.price-list { |
|||
width: 600rpx; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
margin-top: 18rpx; |
|||
align-items: center; |
|||
.price-r { |
|||
font-size: 32rpx; |
|||
font-family: PingFangSC-Regular, PingFang SC; |
|||
font-weight: 500; |
|||
color: #FC5109; |
|||
&:before { |
|||
content: '小计:'; |
|||
display: inline-block; |
|||
color: #000; |
|||
font-size: 24rpx; |
|||
} |
|||
} |
|||
.price-g { |
|||
font-size: 32rpx; |
|||
font-family: PingFangSC-Regular, PingFang SC; |
|||
font-weight: 500; |
|||
color: #FC5109; |
|||
&:before { |
|||
content: '运费:'; |
|||
display: inline-block; |
|||
color: #000; |
|||
font-size: 24rpx; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.num-box { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-left: 20rpx; |
|||
width: 200rpx; |
|||
justify-content: space-between; |
|||
.num { |
|||
text-align: center; |
|||
width: 50rpx; |
|||
} |
|||
.ctrl { |
|||
width: 70rpx; |
|||
height: 60rpx; |
|||
line-height: 60rpx; |
|||
text-align: center; |
|||
font-weight: bold; |
|||
font-size: 36rpx; |
|||
} |
|||
} |
|||
} |
|||
.btn-list { |
|||
width: 750rpx; |
|||
height: 166rpx; |
|||
background: #ffffff; |
|||
box-shadow: 0rpx -3rpx 9rpx 1rpx rgba(227, 229, 232, 0.5); |
|||
display: flex; |
|||
position: fixed; |
|||
bottom: 0; |
|||
padding: 20rpx 50rpx; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
.btn { |
|||
width: 250rpx; |
|||
height: 80rpx; |
|||
background: #60989E; |
|||
border-radius: 44rpx; |
|||
text-align: center; |
|||
line-height: 80rpx; |
|||
font-size: 32rpx; |
|||
font-family: PingFangSC-Medium, PingFang SC; |
|||
font-weight: 500; |
|||
color: #ffffff; |
|||
} |
|||
.price-box { |
|||
display: flex; |
|||
align-items: baseline; |
|||
.text { |
|||
font-size: 28rpx; |
|||
font-family: PingFangSC-Regular, PingFang SC; |
|||
font-weight: 400; |
|||
color: #393b3e; |
|||
} |
|||
.price { |
|||
margin-left: 15rpx; |
|||
font-size: 48rpx; |
|||
font-family: PingFangSC-Regular, PingFang SC; |
|||
font-weight: 400; |
|||
color: #FC5109; |
|||
&:before { |
|||
content: '¥'; |
|||
display: inline-block; |
|||
color: #FC5109; |
|||
font-size: 24rpx; |
|||
} |
|||
} |
|||
.post { |
|||
margin-left: 15rpx; |
|||
color: #FC5109; |
|||
font-size: 24rpx; |
|||
} |
|||
} |
|||
} |
|||
.contacts { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
image { |
|||
width: 36rpx; |
|||
height: 36rpx; |
|||
} |
|||
.contacts-left { |
|||
.name-phone { |
|||
display: flex; |
|||
align-items: center; |
|||
.name { |
|||
font-size: 32rpx; |
|||
font-family: PingFangSC-Medium, PingFang SC; |
|||
font-weight: 500; |
|||
color: #222222; |
|||
} |
|||
.phone { |
|||
margin-left: 27rpx; |
|||
font-size: 28rpx; |
|||
font-family: PingFangSC-Regular, PingFang SC; |
|||
font-weight: 400; |
|||
color: #303030; |
|||
} |
|||
} |
|||
.adds { |
|||
font-size: 28rpx; |
|||
font-family: PingFangSC-Regular, PingFang SC; |
|||
font-weight: 400; |
|||
color: #6c7a94; |
|||
margin-top: 20rpx; |
|||
max-width: 500rpx; |
|||
} |
|||
} |
|||
} |
|||
.people-popup { |
|||
padding: 26rpx; |
|||
min-height: 800rpx; |
|||
|
|||
.top-box { |
|||
height: 80rpx; |
|||
|
|||
.top { |
|||
position: fixed; |
|||
left: 0; |
|||
right: 0; |
|||
color: #000; |
|||
height: 80rpx; |
|||
font-size: 0; |
|||
overflow: hidden; |
|||
padding: 0 26rpx; |
|||
|
|||
text { |
|||
text-align: left; |
|||
font-size: 30rpx; |
|||
font-weight: 400; |
|||
color: #000000; |
|||
} |
|||
|
|||
.confirm { |
|||
font-weight: 400; |
|||
color: #000000; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.popup-list { |
|||
height: 666rpx; |
|||
overflow: scroll; |
|||
|
|||
.popup-item { |
|||
border-radius: 12rpx; |
|||
padding: 0 20rpx; |
|||
margin-top: 24rpx; |
|||
font-size: 24rpx; |
|||
color: #333333; |
|||
font-weight: 400; |
|||
background-color: #ffffff; |
|||
|
|||
.item-top { |
|||
padding: 32rpx; |
|||
|
|||
img { |
|||
color: #666666; |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
} |
|||
|
|||
.name { |
|||
font-size: 30rpx; |
|||
color: #000000; |
|||
overflow: hidden; |
|||
|
|||
text { |
|||
color: #999999; |
|||
} |
|||
|
|||
.tag { |
|||
width: 70rpx; |
|||
height: 32rpx; |
|||
background: #00d7ed; |
|||
border-radius: 7rpx; |
|||
line-height: 32rpx; |
|||
text-align: center; |
|||
font-size: 21rpx; |
|||
font-family: PingFang SC; |
|||
font-weight: 500; |
|||
color: #ffffff; |
|||
} |
|||
} |
|||
|
|||
.com-flex-start { |
|||
margin: 0 0 30rpx; |
|||
} |
|||
|
|||
.subtitle { |
|||
font-weight: 400; |
|||
flex: 1; |
|||
text-align: left; |
|||
margin-top: 33rpx; |
|||
|
|||
.mobile { |
|||
margin-bottom: 36rpx; |
|||
} |
|||
} |
|||
|
|||
.status { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
line-height: 40rpx; |
|||
border-radius: 50%; |
|||
text-align: center; |
|||
box-sizing: border-box; |
|||
|
|||
img { |
|||
width: 27rpx; |
|||
height: 21rpx; |
|||
} |
|||
} |
|||
.statuss { |
|||
background: linear-gradient(90deg, #fa2b66, #ff9834); |
|||
border: none; |
|||
} |
|||
.noSelect { |
|||
border: 1rpx solid #999999; |
|||
} |
|||
} |
|||
|
|||
// .item-site { |
|||
// color: #666666; |
|||
// display: flex; |
|||
// align-items: center; |
|||
// padding: 36rpx 0; |
|||
|
|||
// view { |
|||
// width: 23rpx; |
|||
// height: 23rpx; |
|||
// margin-right: 10rpx; |
|||
// border: 1rpx solid #999999; |
|||
// border-radius: 50%; |
|||
|
|||
// view { |
|||
// width: 8rpx; |
|||
// height: 8rpx; |
|||
// background: #000000; |
|||
// border-radius: 50%; |
|||
// margin: auto; |
|||
// } |
|||
// } |
|||
// } |
|||
} |
|||
} |
|||
|
|||
.button { |
|||
font-size: 30rpx; |
|||
font-weight: 400; |
|||
color: #000000; |
|||
text-align: center; |
|||
width: 100%; |
|||
height: 80rpx; |
|||
line-height: 80rpx; |
|||
background-color: #ffffff; |
|||
border-radius: 60rpx; |
|||
} |
|||
} |
|||
|
|||
.person-info { |
|||
padding: 30rpx 30rpx 15rpx 30rpx; |
|||
background: #fff; |
|||
margin-top: 30rpx; |
|||
border-radius: 16rpx; |
|||
} |
|||
.person-title { |
|||
font-size: 32rpx; |
|||
font-weight: bold; |
|||
color: #000; |
|||
} |
|||
.line { |
|||
border-bottom: 1px solid #e3e5e8; |
|||
} |
|||
.flex { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.left { |
|||
width: 140rpx; |
|||
font-size: 28rpx; |
|||
font-weight: 500; |
|||
color: #4d526c; |
|||
height: 104rpx; |
|||
line-height: 104rpx; |
|||
} |
|||
.input { |
|||
font-size: 28rpx; |
|||
font-weight: 400; |
|||
} |
|||
.remark { |
|||
padding: 30rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
width: 710rpx; |
|||
min-height: 150rpx; |
|||
background: #ffffff; |
|||
border-radius: 16rpx; |
|||
margin: 0 auto; |
|||
margin-top: 30rpx; |
|||
view { |
|||
color: #4d526c; |
|||
font-size: 28rpx; |
|||
} |
|||
input { |
|||
margin-left: 64rpx; |
|||
width: 500rpx; |
|||
font-size: 28rpx; |
|||
} |
|||
} |
|||
|
|||
.info-tags { |
|||
padding: 8rpx 7rpx; |
|||
text-align: center; |
|||
line-height: 35rpx; |
|||
font-size: 20rpx; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.info-tags:first-child { |
|||
background: rgba(252,81,9,.08); |
|||
color: #FC5109; |
|||
} |
|||
|
|||
.info-tags:last-child { |
|||
background: rgba(73,143,239,.1); |
|||
color: #498FEF; |
|||
} |
|||
|
|||
.merchant-name { |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.goods { |
|||
display: flex; |
|||
} |
|||
|
|||
.sku-info { |
|||
display: flex; |
|||
margin-top: 26rpx; |
|||
align-items: baseline; |
|||
justify-content: space-between; |
|||
border-bottom: 1rpx solid #ccc; |
|||
padding-bottom: 25.3rpx; |
|||
} |
|||
|
|||
.sku-info:last-child { |
|||
border:none !important; |
|||
} |
|||
|
|||
.youhui { |
|||
width: 710rpx; |
|||
margin: 0 auto; |
|||
margin-top: 22rpx; |
|||
padding: 32.67rpx 19.33rpx 32.67rpx 24rpx; |
|||
background: #FFFFFF; |
|||
border-radius: 13rpx; |
|||
} |
|||
|
|||
.youhui-price { |
|||
margin-top: 52.67rpx; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
margin-left: .67rpx; |
|||
font-size: 27rpx; |
|||
} |
|||
|
|||
</style> |
@ -0,0 +1,173 @@ |
|||
<template> |
|||
<view class="bg"> |
|||
<view class="search-box"> |
|||
<view class="search"> |
|||
<image src="https://static.ticket.sz-trip.com/taizhou/images/search.png" mode="widthFix"></image> |
|||
<input type="text" v-model="keywords" @confirm="getList" placeholder="请输入搜索关键词" /> |
|||
</view> |
|||
<view class="text" @click="goBack">取消</view> |
|||
</view> |
|||
|
|||
<view class="box flex-between" v-if="list && list.length > 0"> |
|||
<view v-for="(item,index) in list" :key="index" class="item" v-if="item.search_data" @click="gotoDetail(item)"> |
|||
<image :src="showImg(item.search_data.image)" mode="aspectFill" class="headimg"></image> |
|||
<view class="content flex-column"> |
|||
<view class="title text-overflowRows">{{item.title}}</view> |
|||
<view class="price" v-if="type == 'goods'">{{item.search_data.money / 100}}</view> |
|||
<view class="user-info" v-if="type == 'article'"> |
|||
<image :src="showImg(item.search_data.author_img)" mode="aspectFill" class="userImg"></image> |
|||
{{item.search_data.author}} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view v-else class="noData">暂无数据</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
keywords: '', |
|||
list: [], |
|||
type: '' |
|||
} |
|||
}, |
|||
onLoad(option) { |
|||
if(option.type) this.type = option.type |
|||
}, |
|||
methods: { |
|||
getList() { |
|||
this.Post({ |
|||
name: this.keywords.trim(), |
|||
offset: 0, |
|||
type: this.type, |
|||
limit: 100, |
|||
}, '/api/search/search').then(res => { |
|||
this.list = res.data |
|||
}) |
|||
}, |
|||
gotoDetail(item) { |
|||
if(this.type == 'goods') { |
|||
uni.navigateTo({ |
|||
url: `/subPackages/techan/detail?id=${item.search_data.id}` |
|||
}) |
|||
}else if(this.type == 'article') { |
|||
this.gotoUrlNew(item.search_data) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.bg { |
|||
min-height: 100vh; |
|||
background-color: #f7f7f7; |
|||
} |
|||
|
|||
.search-box { |
|||
width: 100%; |
|||
padding: 20rpx; |
|||
border-bottom: 1rpx solid #ccc; |
|||
display: flex; |
|||
align-items: center; |
|||
background-color: #fff; |
|||
|
|||
.search { |
|||
flex: 1; |
|||
padding: 0 20rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
height: 65rpx; |
|||
border-radius: 33rpx; |
|||
background-color: rgb(245, 245, 245); |
|||
|
|||
image { |
|||
display: block; |
|||
width: 35rpx; |
|||
margin-right: 30rpx; |
|||
} |
|||
|
|||
input { |
|||
flex: 1; |
|||
} |
|||
} |
|||
|
|||
.text { |
|||
font-size: 30rpx; |
|||
color: #b9b9b9; |
|||
margin-left: 20rpx; |
|||
} |
|||
} |
|||
|
|||
.box { |
|||
flex-wrap: wrap; |
|||
padding: 30rpx 20rpx; |
|||
|
|||
.item { |
|||
width: 342rpx; |
|||
height: 498rpx; |
|||
background: #FFFFFF; |
|||
border-radius: 11rpx; |
|||
margin-bottom: 20rpx; |
|||
|
|||
.headimg { |
|||
width: 343rpx; |
|||
height: 327rpx; |
|||
border-radius: 11rpx 11rpx 0rpx 0rpx; |
|||
} |
|||
|
|||
.content { |
|||
height: 170rpx; |
|||
justify-content: space-between; |
|||
padding: 15rpx 15rpx 20rpx 15rpx; |
|||
|
|||
.title { |
|||
font-weight: bold; |
|||
font-size: 30rpx; |
|||
color: #000000; |
|||
} |
|||
|
|||
.price { |
|||
font-weight: bold; |
|||
font-size: 33rpx; |
|||
color: #FF2D3B; |
|||
margin-left: auto; |
|||
} |
|||
.price::before { |
|||
font-size: 20rpx; |
|||
content: '¥'; |
|||
} |
|||
.price::after { |
|||
font-size: 20rpx; |
|||
content: '起'; |
|||
color: rgba(153, 153, 153, 1); |
|||
} |
|||
|
|||
.user-info { |
|||
font-weight: 500; |
|||
font-size: 28rpx; |
|||
color: #999999; |
|||
display: flex; |
|||
align-items: center; |
|||
|
|||
.userImg { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
border-radius: 50%; |
|||
margin-right: 10rpx; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.noData { |
|||
padding: 30rpx 0; |
|||
text-align: center; |
|||
font-size: 30rpx; |
|||
color: #ccc; |
|||
} |
|||
</style> |
@ -0,0 +1,286 @@ |
|||
<template> |
|||
<view> |
|||
<TitleHeader /> |
|||
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" |
|||
:duration="300" :style="{ height: `calc(100vh - ${titleHeight}px)` }"> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[0]"> |
|||
<image v-show="shouldShowContent(0)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg1s.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<image v-show="shouldShowContent(0)" class="layer-img1" |
|||
:class="{'slide-in-from-left': animationStates[0], 'hidden': !animationStates[0]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg1-info.png" :lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[1]"> |
|||
<image v-show="shouldShowContent(1)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg2.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<image v-show="shouldShowContent(1)" class="layer-img2" |
|||
:class="{'slide-in-from-left': animationStates[1], 'hidden': !animationStates[1]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg2-info.png" :lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[2]"> |
|||
<image v-show="shouldShowContent(2)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg3.jpg" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view class="content-layer" v-show="shouldShowContent(2)"> |
|||
<image class="layer-img3" |
|||
:class="{'slide-in-from-right': animationStates[2], 'hidden': !animationStates[2]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg3-info.png" :lazy-load="true" mode="aspectFill"></image> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[3]"> |
|||
<image v-show="shouldShowContent(3)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg4.jpg" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<image v-show="shouldShowContent(3)" class="layer-img4" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg4-info.gif" |
|||
:lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[4]"> |
|||
<image v-show="shouldShowContent(4)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg5.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<image :class="{'slide-in-from-right': animationStates[4], 'hidden': !animationStates[4]}" |
|||
v-show="shouldShowContent(4)" class="layer-img5" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg5-info.png" |
|||
:lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[5]"> |
|||
<image v-show="shouldShowContent(5)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg6.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[6]"> |
|||
<image v-show="shouldShowContent(6)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter1/bg7.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view @click="goBack" class="back-btn" type="default"> |
|||
<image class="back-icon" src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png" mode="aspectFill"></image> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import TitleHeader from '@/components/TitleHeader.vue'; |
|||
export default { |
|||
components: { |
|||
TitleHeader |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
loadedPages: { |
|||
0: false, |
|||
1: false, |
|||
2: false, |
|||
3: false, |
|||
4: false, |
|||
5: false, |
|||
6: false |
|||
}, |
|||
animationStates: { |
|||
0: false, |
|||
1: false, |
|||
2: false, |
|||
3: false, |
|||
4: false, |
|||
5: false, |
|||
6: false |
|||
}, |
|||
preloadBuffer: 1, |
|||
titleHeight: 0 |
|||
}; |
|||
}, |
|||
watch: { |
|||
currentIndex(newIndex) { |
|||
for (let i = Math.max(0, newIndex - this.preloadBuffer); i <= Math.min(9, newIndex + this |
|||
.preloadBuffer); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.titleHeight = uni.getStorageSync('titleHeight') |
|||
for (let i = 0; i <= Math.min(1 + this.preloadBuffer, 9); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
setTimeout(() => { |
|||
this.animationStates[this.currentIndex] = true; |
|||
}, 50); |
|||
}, |
|||
methods: { |
|||
handleSwiperChange(e) { |
|||
const newIndex = e.detail.current; |
|||
this.currentIndex = newIndex; |
|||
this.animationStates[newIndex] = false; |
|||
setTimeout(() => { |
|||
this.animationStates[newIndex] = true; |
|||
}, 50); |
|||
}, |
|||
shouldShowContent(index) { |
|||
return Math.abs(index - this.currentIndex) <= this.preloadBuffer; |
|||
}, |
|||
goBack() { |
|||
uni.navigateTo({ |
|||
url: '/taozi/home/home?targetIndex=5' |
|||
}); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.main-swiper { |
|||
width: 100%; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.page-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 100%; |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.bg-image { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.layer-img1 { |
|||
width: 500rpx; |
|||
z-index: 2; |
|||
margin-right: 180rpx; |
|||
margin-top: 50rpx; |
|||
} |
|||
|
|||
.layer-img2 { |
|||
z-index: 2; |
|||
position: absolute; |
|||
bottom: 18%; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 350rpx; |
|||
} |
|||
|
|||
.content-layer { |
|||
margin: 130rpx 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
} |
|||
|
|||
.layer-img3 { |
|||
height: 100%; |
|||
width: 280rpx; |
|||
z-index: 2; |
|||
margin-top: 10rpx; |
|||
margin-right: 30rpx; |
|||
} |
|||
|
|||
.layer-img4 { |
|||
z-index: 2; |
|||
width: 400rpx; |
|||
height: 400rpx; |
|||
margin-top: 400rpx; |
|||
margin-left: 80rpx; |
|||
} |
|||
|
|||
.layer-img5 { |
|||
z-index: 2; |
|||
position: absolute; |
|||
bottom: 90rpx; |
|||
right: 30rpx; |
|||
width: 320rpx; |
|||
height: 500rpx; |
|||
} |
|||
|
|||
.back-btn { |
|||
position: absolute; |
|||
top: 50rpx; |
|||
left: 50rpx; |
|||
z-index: 2; |
|||
background-color: rgb(0 0 0 / 0.3); |
|||
border-radius: 50%; |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.back-icon { |
|||
width: 50rpx; |
|||
height: 50rpx; |
|||
} |
|||
|
|||
.hidden { |
|||
opacity: 0; |
|||
} |
|||
|
|||
.slide-in-from-left { |
|||
animation: slideInLeft 1.2s ease-out forwards; |
|||
} |
|||
|
|||
@keyframes slideInLeft { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateX(-100px); |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateX(0); |
|||
} |
|||
} |
|||
|
|||
.slide-in-from-right { |
|||
animation: slideInRight 1.2s ease-out forwards; |
|||
} |
|||
|
|||
@keyframes slideInRight { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateX(100px); |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateX(0); |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,336 @@ |
|||
<template> |
|||
<view> |
|||
<TitleHeader /> |
|||
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" |
|||
:duration="300" :style="{ height: `calc(100vh - ${titleHeight}px)` }"> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[0]"> |
|||
<image v-show="shouldShowContent(0)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter2/bg1.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<image v-show="shouldShowContent(0)" class="layer-img1" |
|||
:class="{'slide-in-from-left': animationStates[0], 'hidden': !animationStates[0]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter2/bg1-info.png" :lazy-load="true" mode="aspectFill" /> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<view class="loadedPages-three-title"> |
|||
<view class="txt"> |
|||
#Chapter 贰 |
|||
</view> |
|||
|
|||
<view class="txt"> |
|||
IP Art Exhibition |
|||
</view> |
|||
</view> |
|||
<template v-if="loadedPages[1]"> |
|||
<image v-show="shouldShowContent(1)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter2/bg2.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<image v-show="shouldShowContent(1)" class="layer-img2" |
|||
:class="{'slide-in-from-left': animationStates[1], 'hidden': !animationStates[1]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter2/bg2-info.png" :lazy-load="true" mode="aspectFill" /> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[2]"> |
|||
<image v-show="shouldShowContent(2)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter2/bg3.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<image v-show="shouldShowContent(2)" class="layer-img3" |
|||
:class="{'slide-in-from-left': animationStates[2], 'hidden': !animationStates[2]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter2/bg3-info.png" :lazy-load="true" mode="aspectFill" /> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[3]"> |
|||
<image v-show="shouldShowContent(3)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter2/bg4.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<image v-show="shouldShowContent(3)" class="layer-img4" |
|||
:class="{'slide-in-from-left': animationStates[3], 'hidden': !animationStates[3]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter2/bg4-info.png" :lazy-load="true" mode="aspectFill" /> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[4]"> |
|||
<image v-show="shouldShowContent(4)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter2/bg5.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<video v-show="shouldShowContent(4)" ref="videoPlayer" id="videoPlayer" |
|||
class="loadedPages-video" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter2/bg5-video.mp4" |
|||
:autoplay="currentIndex === 4" :loop="true" :controls="true" :show-play-btn="true" |
|||
:enable-progress-gesture="true" @error="handleVideoError"></video> |
|||
<view @click="goBack" class="back-btn" type="default"> |
|||
<image class="back-icon" src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png" mode="aspectFill"></image> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import TitleHeader from '@/components/TitleHeader.vue'; |
|||
export default { |
|||
components: { |
|||
MusicControl, |
|||
TitleHeader |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
videoPlayer: null, |
|||
videoContext: null, |
|||
loadedPages: { |
|||
0: false, |
|||
1: false, |
|||
2: false, |
|||
3: false, |
|||
4: false |
|||
}, |
|||
animationStates: { |
|||
0: false, |
|||
1: false, |
|||
2: false, |
|||
3: false, |
|||
4: false |
|||
}, |
|||
preloadBuffer: 1, |
|||
titleHeight: 0 |
|||
}; |
|||
}, |
|||
watch: { |
|||
currentIndex(newIndex) { |
|||
for (let i = Math.max(0, newIndex - this.preloadBuffer); i <= Math.min(9, newIndex + this |
|||
.preloadBuffer); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.titleHeight = uni.getStorageSync('titleHeight') |
|||
for (let i = 0; i <= Math.min(1 + this.preloadBuffer, 9); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
setTimeout(() => { |
|||
this.animationStates[this.currentIndex] = true; |
|||
}, 50); |
|||
this.$nextTick(() => { |
|||
try { |
|||
this.videoContext = uni.createVideoContext('videoPlayer'); |
|||
} catch (error) { |
|||
console.error('初始化视频上下文失败:', error); |
|||
} |
|||
}); |
|||
}, |
|||
methods: { |
|||
handleSwiperChange(e) { |
|||
const newIndex = e.detail.current; |
|||
const oldIndex = this.currentIndex; |
|||
this.currentIndex = newIndex; |
|||
this.animationStates[newIndex] = false; |
|||
setTimeout(() => { |
|||
this.animationStates[newIndex] = true; |
|||
}, 50); |
|||
this.$nextTick(() => { |
|||
this.handleVideoPlayback(oldIndex, newIndex); |
|||
}); |
|||
}, |
|||
handleVideoPlayback(oldIndex, newIndex) { |
|||
if (!this.videoContext) { |
|||
try { |
|||
this.videoContext = uni.createVideoContext('videoPlayer'); |
|||
} catch (error) {} |
|||
} |
|||
|
|||
if (!this.videoContext) return; |
|||
if (newIndex === 4) { |
|||
try { |
|||
this.videoContext.play(); |
|||
} catch (error) {} |
|||
} else if (oldIndex === 4) { |
|||
try { |
|||
this.videoContext.pause(); |
|||
this.videoContext.seek(0); |
|||
} catch (error) { |
|||
console.error('视频暂停失败:', error); |
|||
} |
|||
} |
|||
}, |
|||
handleVideoError(e) { |
|||
uni.showToast({ |
|||
title: '视频加载失败', |
|||
icon: 'none' |
|||
}); |
|||
}, |
|||
shouldShowContent(index) { |
|||
return Math.abs(index - this.currentIndex) <= this.preloadBuffer; |
|||
}, |
|||
goBack() { |
|||
uni.navigateTo({ |
|||
url: '/taozi/home/home?targetIndex=6' |
|||
}); |
|||
} |
|||
}, |
|||
onShow() { |
|||
if (this.currentIndex === 4 && this.videoContext) { |
|||
this.$nextTick(() => { |
|||
this.videoContext.play(); |
|||
}); |
|||
} |
|||
}, |
|||
onHide() { |
|||
if (this.videoContext) { |
|||
this.videoContext.pause(); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.main-swiper { |
|||
width: 100%; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.page-container { |
|||
height: 100%; |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.bg-image { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.layer-img1 { |
|||
position: absolute; |
|||
width: 500rpx; |
|||
height: 800rpx; |
|||
z-index: 2; |
|||
top: 120rpx; |
|||
left: 60rpx; |
|||
} |
|||
|
|||
.layer-img2 { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 640rpx; |
|||
height: 750rpx; |
|||
z-index: 2; |
|||
} |
|||
|
|||
.layer-img3 { |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 30rpx; |
|||
width: 550rpx; |
|||
height: 1200rpx; |
|||
z-index: 2; |
|||
} |
|||
|
|||
.layer-img4 { |
|||
position: absolute; |
|||
bottom: 50rpx; |
|||
left: 30rpx; |
|||
width: 400rpx; |
|||
height: 900rpx; |
|||
z-index: 2; |
|||
} |
|||
|
|||
.hidden { |
|||
opacity: 0; |
|||
} |
|||
|
|||
.slide-in-from-left { |
|||
animation: slideInLeft 1.2s ease-out forwards; |
|||
} |
|||
|
|||
@keyframes slideInLeft { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateX(-100px); |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateX(0); |
|||
} |
|||
} |
|||
|
|||
@font-face { |
|||
font-family: 'SourceHanSerif-Regular'; |
|||
src: url(https://static.ticket.sz-trip.com/epicSoul/taozi/fonts/SourceHanSerifSC-Regular.otf); |
|||
} |
|||
|
|||
.loadedPages-three-title { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
width: 100%; |
|||
margin-top: 30rpx; |
|||
z-index: 9; |
|||
position: absolute; |
|||
|
|||
.txt { |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
font-family: SourceHanSerif-Regular; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
|
|||
text { |
|||
display: block; |
|||
} |
|||
} |
|||
|
|||
.txt:first-child { |
|||
margin-left: 30rpx; |
|||
} |
|||
|
|||
.txt:last-child { |
|||
margin-right: 30rpx; |
|||
} |
|||
} |
|||
|
|||
.loadedPages-video { |
|||
width: 100%; |
|||
height: 350rpx; |
|||
z-index: 9; |
|||
margin-top: 150rpx; |
|||
} |
|||
|
|||
.back-btn { |
|||
position: absolute; |
|||
top: 50rpx; |
|||
left: 50rpx; |
|||
z-index: 2; |
|||
background-color: rgb(0 0 0 / 0.3); |
|||
border-radius: 50%; |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.back-icon { |
|||
width: 50rpx; |
|||
height: 50rpx; |
|||
} |
|||
</style> |
@ -0,0 +1,369 @@ |
|||
<template> |
|||
<view> |
|||
<TitleHeader /> |
|||
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" |
|||
:duration="300" :style="{ height: `calc(100vh - ${titleHeight}px)` }"> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[0]"> |
|||
<image v-show="shouldShowContent(0)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter3/bg1.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<image v-show="shouldShowContent(0)" class="layer-img1" |
|||
:class="{'rotate-bounce-in': animationStates[0], 'hidden': !animationStates[0]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter3/bg1-info.png" :lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
<image v-show="shouldShowContent(0)" class="layer-info" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter3/bg1-info2.png" |
|||
mode="aspectFill"></image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[1]"> |
|||
<image v-show="shouldShowContent(1)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter3/bg2.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view v-show="shouldShowContent(1)" class="layer-content"> |
|||
<view class="item"> |
|||
<image class="item-gif" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter3/liu.gif" mode="aspectFill"></image> |
|||
<view class="layer-item-txt"> |
|||
来!备的草编席垫送你,<br />咱席地而坐,边吃边唠,<br />便是人间好时节。 |
|||
</view> |
|||
<view class="layer-item-tx2"> |
|||
刘备 / 挚友款 · 同心桃 |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[2]"> |
|||
<image v-show="shouldShowContent(2)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter3/bg3.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view v-show="shouldShowContent(2)" class="layer-content"> |
|||
<view class="item"> |
|||
<image class="item-gif" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter3/guan.gif" mode="aspectFill"></image> |
|||
<view class="layer-item-txt"> |
|||
某卖的不是桃,<br />是当年与兄长、三弟<br />在桃林下对饮的春秋。 |
|||
</view> |
|||
<view class="layer-item-tx2"> |
|||
关羽 / 挚知己款 · 对饮桃 |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[3]"> |
|||
<image v-show="shouldShowContent(3)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter3/bg4.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view v-show="shouldShowContent(3)" class="layer-content"> |
|||
<view class="item"> |
|||
<image class="item-gif" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter3/zhang.gif" mode="aspectFill"></image> |
|||
<view class="layer-item-txt"> |
|||
瞧这桃!跟俺张飞的脾气<br />一样爽利,咬下去<br />「噗嗤」爆汁,<br />比俺大笑还痛快! |
|||
</view> |
|||
<view class="layer-item-tx2"> |
|||
张飞 / 热辣款款 · 快哉桃 |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[4]"> |
|||
<image v-show="shouldShowContent(4)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter3/bg5.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view class="layer2-content" |
|||
:class="{'fade-slide-up': animationStates[4], 'hidden': !animationStates[4]}"> |
|||
<view class="item"> |
|||
<view class="bottom-tit"> |
|||
一起回桃园 |
|||
</view> |
|||
<view class="bottom-tit2"> |
|||
桃园不是起点,而是时间里的约定 |
|||
</view> |
|||
<image class="bottom-img" src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/forewordThree_icon.png" mode="aspectFill"></image> |
|||
</view> |
|||
|
|||
</view> |
|||
<view @click="goBack" class="back-btn" type="default"> |
|||
<image class="back-icon" src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png" mode="aspectFill"></image> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import TitleHeader from '@/components/TitleHeader.vue'; |
|||
export default { |
|||
components: { |
|||
TitleHeader |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
loadedPages: { |
|||
0: false, |
|||
1: false, |
|||
2: false, |
|||
3: false |
|||
}, |
|||
animationStates: { |
|||
0: false, |
|||
1: false, |
|||
2: false, |
|||
3: false |
|||
}, |
|||
preloadBuffer: 1, |
|||
titleHeight: 0 |
|||
}; |
|||
}, |
|||
watch: { |
|||
currentIndex(newIndex) { |
|||
for (let i = Math.max(0, newIndex - this.preloadBuffer); i <= Math.min(9, newIndex + this |
|||
.preloadBuffer); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.titleHeight = uni.getStorageSync('titleHeight') |
|||
for (let i = 0; i <= Math.min(1 + this.preloadBuffer, 9); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
setTimeout(() => { |
|||
this.animationStates[this.currentIndex] = true; |
|||
}, 50); |
|||
}, |
|||
methods: { |
|||
handleSwiperChange(e) { |
|||
const newIndex = e.detail.current; |
|||
this.currentIndex = newIndex; |
|||
this.animationStates[newIndex] = false; |
|||
setTimeout(() => { |
|||
this.animationStates[newIndex] = true; |
|||
}, 50); |
|||
}, |
|||
shouldShowContent(index) { |
|||
return Math.abs(index - this.currentIndex) <= this.preloadBuffer; |
|||
}, |
|||
goBack() { |
|||
uni.navigateTo({ |
|||
url: '/taozi/home/home?targetIndex=7' |
|||
}); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.main-swiper { |
|||
width: 100%; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.page-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 100%; |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.bg-image { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.layer-img1 { |
|||
z-index: 2; |
|||
position: absolute; |
|||
bottom: 300rpx; |
|||
right: 60rpx; |
|||
width: 300rpx; |
|||
height: 300rpx; |
|||
} |
|||
|
|||
.layer-info { |
|||
width: 100%; |
|||
height: 280rpx; |
|||
z-index: 2; |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
} |
|||
|
|||
.layer-content { |
|||
position: absolute; |
|||
top: 36%; |
|||
left: 50%; |
|||
transform: translate(-50%, -50%); |
|||
z-index: 2; |
|||
|
|||
.item { |
|||
position: relative; |
|||
|
|||
.item-gif { |
|||
width: 600rpx; |
|||
height: 650rpx; |
|||
} |
|||
|
|||
.layer-item-txt { |
|||
position: absolute; |
|||
top: -50rpx; |
|||
left: 0; |
|||
font-size: 46rpx; |
|||
color: #e5007f; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.layer-item-tx2 { |
|||
position: absolute; |
|||
right: 0; |
|||
top: -40rpx; |
|||
font-size: 36rpx; |
|||
writing-mode: vertical-rl; |
|||
text-orientation: mixed; |
|||
color: #e5007f; |
|||
font-weight: 600; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.layer2-content { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
z-index: 2; |
|||
|
|||
.item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
width: calc(100% - 100rpx); |
|||
margin: 80rpx 0; |
|||
|
|||
.bottom-img { |
|||
width: 100%; |
|||
height: 190rpx; |
|||
margin-top: 50rpx; |
|||
} |
|||
|
|||
.bottom-tit { |
|||
width: 100%; |
|||
text-align: justify; |
|||
text-align-last: justify; |
|||
font-size: 100rpx; |
|||
display: inline-block; |
|||
} |
|||
|
|||
.bottom-tit2 { |
|||
width: 100%; |
|||
color: #e40080; |
|||
text-align: justify; |
|||
text-align-last: justify; |
|||
display: inline-block; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.back-btn { |
|||
position: absolute; |
|||
top: 50rpx; |
|||
left: 50rpx; |
|||
z-index: 2; |
|||
background-color: rgb(0 0 0 / 0.3); |
|||
border-radius: 50%; |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.back-icon { |
|||
width: 50rpx; |
|||
height: 50rpx; |
|||
} |
|||
|
|||
.rotate-bounce-in { |
|||
animation: rotateBounceIn 1.2s cubic-bezier(0.215, 0.610, 0.355, 1.000) forwards; |
|||
} |
|||
|
|||
@keyframes rotateBounceIn { |
|||
0% { |
|||
opacity: 0; |
|||
transform: rotate(-180deg) scale(0.3); |
|||
} |
|||
|
|||
40% { |
|||
opacity: 0.6; |
|||
transform: rotate(25deg) scale(0.9); |
|||
} |
|||
|
|||
60% { |
|||
opacity: 0.8; |
|||
transform: rotate(-15deg) scale(1.1); |
|||
} |
|||
|
|||
80% { |
|||
opacity: 0.9; |
|||
transform: rotate(5deg) scale(0.95); |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: rotate(0deg) scale(1); |
|||
} |
|||
} |
|||
|
|||
.slide-in-from-right { |
|||
animation: slideInRight 1.2s ease-out forwards; |
|||
} |
|||
|
|||
@keyframes slideInRight { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateX(100px); |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateX(0); |
|||
} |
|||
} |
|||
|
|||
.fade-slide-up { |
|||
animation: fadeSlideUp 1s ease-out forwards; |
|||
} |
|||
|
|||
@keyframes fadeSlideUp { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateY(30px); |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,297 @@ |
|||
<template> |
|||
<view> |
|||
<TitleHeader /> |
|||
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" |
|||
:duration="300" :style="{ height: `calc(100vh - ${titleHeight}px)` }"> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[0]"> |
|||
<image v-show="shouldShowContent(0)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg1.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view v-show="shouldShowContent(0)" class="content-layer"> |
|||
<image class="layer-img" |
|||
:class="{'blur-to-clear': animationStates[0], 'hidden': !animationStates[0]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg1-infos.png" :lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
<image class="btn-img" @click="gotoPath('/subPackages/techan/detail?id=32')" |
|||
:class="{'blur-to-clear': animationStates[0], 'hidden': !animationStates[0]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/btn1.png" :lazy-load="true" mode="widthFix"> |
|||
</image> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[1]"> |
|||
<image v-show="shouldShowContent(1)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg2.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view v-show="shouldShowContent(1)" class="content-layer"> |
|||
<image class="layer-img" |
|||
:class="{'blur-to-clear': animationStates[1], 'hidden': !animationStates[1]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg2-infos.png" :lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
<image class="btn-img" style="right: 30rpx;top: 75vh;" @click="gotoPath('/subPackages/techan/detail?id=32')" |
|||
:class="{'blur-to-clear': animationStates[1], 'hidden': !animationStates[1]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/btn2.png" :lazy-load="true" mode="widthFix"> |
|||
</image> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[2]"> |
|||
<image v-show="shouldShowContent(2)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg3.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view v-show="shouldShowContent(2)" class="content-layer"> |
|||
<image class="layer-img" |
|||
:class="{'blur-to-clear': animationStates[2], 'hidden': !animationStates[2]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg3-infos.png" :lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
<image class="btn-img" style="right: 40vw;top: 65vh;" @click="gotoPath('/subPackages/techan/detail?id=32')" |
|||
:class="{'blur-to-clear': animationStates[1], 'hidden': !animationStates[1]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/btn3.png" :lazy-load="true" mode="widthFix"> |
|||
</image> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[3]"> |
|||
<image v-show="shouldShowContent(3)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg4.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view v-show="shouldShowContent(3)" class="content-layer2"> |
|||
<image class="layer-img2" |
|||
:class="{'slide-in-from-left': animationStates[3], 'hidden': !animationStates[3]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg4-info.png" :lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[4]"> |
|||
<image v-show="shouldShowContent(4)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg5.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[5]"> |
|||
<image v-show="shouldShowContent(5)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg6.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view v-show="shouldShowContent(5)" class="content-layer3"> |
|||
<image class="layer-img3" |
|||
:class="{'slide-in-from-left': animationStates[5], 'hidden': !animationStates[5]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/chapter4/bg6-info.png" :lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
</view> |
|||
<view @click="goBack" class="back-btn" type="default"> |
|||
<image class="back-icon" src="https://static.ticket.sz-trip.com/epicSoul/taozi/back.png" mode="aspectFill"></image> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import TitleHeader from '@/components/TitleHeader.vue'; |
|||
export default { |
|||
components: { |
|||
TitleHeader |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
loadedPages: { |
|||
0: false, |
|||
1: false, |
|||
2: false, |
|||
3: false, |
|||
4: false, |
|||
5: false |
|||
}, |
|||
animationStates: { |
|||
0: false, |
|||
1: false, |
|||
2: false, |
|||
3: false, |
|||
4: false, |
|||
5: false |
|||
}, |
|||
preloadBuffer: 1, |
|||
titleHeight: 0 |
|||
}; |
|||
}, |
|||
watch: { |
|||
currentIndex(newIndex) { |
|||
for (let i = Math.max(0, newIndex - this.preloadBuffer); i <= Math.min(9, newIndex + this |
|||
.preloadBuffer); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.titleHeight = uni.getStorageSync('titleHeight') |
|||
for (let i = 0; i <= Math.min(1 + this.preloadBuffer, 9); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
setTimeout(() => { |
|||
this.animationStates[this.currentIndex] = true; |
|||
}, 50); |
|||
}, |
|||
methods: { |
|||
handleSwiperChange(e) { |
|||
const newIndex = e.detail.current; |
|||
this.currentIndex = newIndex; |
|||
this.animationStates[newIndex] = false; |
|||
setTimeout(() => { |
|||
this.animationStates[newIndex] = true; |
|||
}, 50); |
|||
}, |
|||
shouldShowContent(index) { |
|||
return Math.abs(index - this.currentIndex) <= this.preloadBuffer; |
|||
}, |
|||
goBack() { |
|||
uni.navigateTo({ |
|||
url: '/taozi/home/home?targetIndex=7' |
|||
}); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.main-swiper { |
|||
width: 100%; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.page-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 100%; |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.content-layer { |
|||
width: 100%; |
|||
height: 100%; |
|||
position: relative; |
|||
z-index: 2; |
|||
} |
|||
|
|||
.content-layer2 { |
|||
width: calc(100% - 100rpx); |
|||
height: 100%; |
|||
position: relative; |
|||
z-index: 2; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: flex-end; |
|||
margin: 0 50rpx 400rpx; |
|||
} |
|||
|
|||
.content-layer3 { |
|||
width: calc(100% - 100rpx); |
|||
height: 100%; |
|||
z-index: 2; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.bg-image { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.layer-img { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.layer-img2 { |
|||
width: 100%; |
|||
height: 350rpx; |
|||
} |
|||
|
|||
.layer-img3 { |
|||
width: 500rpx; |
|||
height: 300rpx; |
|||
} |
|||
|
|||
.back-btn { |
|||
position: absolute; |
|||
top: 50rpx; |
|||
left: 50rpx; |
|||
z-index: 2; |
|||
background-color: rgb(0 0 0 / 0.3); |
|||
border-radius: 50%; |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.back-icon { |
|||
width: 50rpx; |
|||
height: 50rpx; |
|||
} |
|||
|
|||
.blur-to-clear { |
|||
animation: blurToClear .8s ease-out forwards; |
|||
} |
|||
|
|||
@keyframes blurToClear { |
|||
0% { |
|||
filter: blur(10px); |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
100% { |
|||
filter: blur(0); |
|||
opacity: 1; |
|||
} |
|||
} |
|||
|
|||
.hidden { |
|||
opacity: 0; |
|||
} |
|||
|
|||
.slide-in-from-left { |
|||
animation: slideInLeft 1.2s ease-out forwards; |
|||
} |
|||
|
|||
@keyframes slideInLeft { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateX(-100px); |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateX(0); |
|||
} |
|||
} |
|||
|
|||
.btn-img { |
|||
position: fixed; |
|||
top: 14vh; |
|||
right: 30rpx; |
|||
width: 20vw; |
|||
} |
|||
</style> |
@ -0,0 +1,786 @@ |
|||
<template> |
|||
<view> |
|||
<TitleHeader /> |
|||
<swiper class="main-swiper" :vertical="true" :current="currentIndex" @change="handleSwiperChange" |
|||
:duration="300" :style="{ height: `calc(100vh - ${titleHeight}px)` }"> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[0]"> |
|||
<image v-show="shouldShowContent(0)" class="bg-image" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/foreword-bg1.gif" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<view v-show="shouldShowContent(0)" class="content-layer"> |
|||
<image class="layer-img" |
|||
:class="{'blur-to-clear': animationStates[0], 'hidden': !animationStates[0]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/foreword-bg1-text.png" |
|||
:lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
<view class="arrow-content"> |
|||
<image class="arrow-down" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/arrow-icon.png" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[1]"> |
|||
<image v-show="shouldShowContent(1)" class="bg-image" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/prologue1s.png" :lazy-load="true" |
|||
mode="aspectFill"></image> |
|||
<!-- <view v-show="shouldShowContent(1)" class="content-layer2"> |
|||
<image class="layer-icon" |
|||
:class="{'bounce-in': animationStates[1], 'hidden': !animationStates[1]}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/prologue1-icon.png" |
|||
:lazy-load="true" mode="aspectFill"> |
|||
</image> |
|||
</view> --> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[2]"> |
|||
<image v-show="shouldShowContent(2)" class="bg-image" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/foreword-bg2.gif" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<template v-if="loadedPages[3]"> |
|||
<view class="loadedPages-three"> |
|||
<view class="loadedPages-three-content"> |
|||
<view class="loadedPages-three-title"> |
|||
<view class="txt"> |
|||
DAYUN |
|||
</view> |
|||
<view class="txt"> |
|||
<text>IP Art</text> |
|||
<text>Exhibition</text> |
|||
</view> |
|||
<view class="txt"> |
|||
issue/01 |
|||
</view> |
|||
</view> |
|||
<view class="loadedPages-three-center"> |
|||
<view class="desc"> |
|||
<text>时间里</text> |
|||
<text>的</text> |
|||
<text>约定</text> |
|||
</view> |
|||
<view class="en-desc"> |
|||
<text>Agreements</text> |
|||
<text>Within</text> |
|||
<text>Time</text> |
|||
</view> |
|||
</view> |
|||
<view class="loadedPages-three-bottom" |
|||
:class="{'fade-slide-up': animationStates[3], 'hidden': !animationStates[3]}"> |
|||
<image class="bottom-img" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/forewordThree_icon.png" |
|||
mode="aspectFill"></image> |
|||
<view class="bottom-tit"> |
|||
「三个桃子」IP美数展 |
|||
</view> |
|||
<view class="bottom-txt"> |
|||
Three Peaches IP Art Exhibition |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</swiper-item> |
|||
<swiper-item v-for="index in [4, 5, 6, 7]" :key="index"> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[index]"> |
|||
<image v-show="shouldShowContent(index)" class="bg-image" |
|||
:src="`https://static.ticket.sz-trip.com/epicSoul/taozi/home/chapterCover${index-3}.png`" |
|||
:lazy-load="true" mode="aspectFill"></image> |
|||
<image @click="goChapter" class="chapterCover-btn" v-show="shouldShowContent(index)" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/toggle.png" :lazy-load="true" |
|||
mode="aspectFill"></image> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
|
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[8]"> |
|||
<image v-show="shouldShowContent(8)" class="bg-image" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/footer.png" :lazy-load="true" |
|||
mode="aspectFill"></image> |
|||
<!-- <image class="qrcode-txt" v-show="shouldShowContent(8)" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/qrcode-txt.png" :lazy-load="true" |
|||
mode="aspectFill" :show-menu-by-longpress="true"></image> --> |
|||
<image class="qrcode-txt" v-show="shouldShowContent(8)" @click="gotoPath('/subPackages/techan/detail?id=32')" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/qrcode-btn.png" :lazy-load="true" |
|||
mode="widthFix" :show-menu-by-longpress="true"></image> |
|||
<!-- <image class="qrcode-txts" v-show="shouldShowContent(8)" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/home/qrcode-txtss.png" :lazy-load="true" |
|||
mode="widthFix"></image> --> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<template v-if="loadedPages[9]"> |
|||
<image v-show="shouldShowContent(9)" class="bg-image" src="https://static.ticket.sz-trip.com/epicSoul/footers.png" :lazy-load="true" mode="aspectFill"></image> |
|||
<image v-show="shouldShowContent(9)" class="qrCode-image" src="https://static.ticket.sz-trip.com/epicSoul/qrCode.png" |
|||
:lazy-load="true" mode="widthFix" :show-menu-by-longpress="true"></image> |
|||
</template> |
|||
</swiper-item> |
|||
<swiper-item> |
|||
<view class="page-container"> |
|||
<template v-if="loadedPages[10]"> |
|||
<messageBoard class="message-board" /> |
|||
</template> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
<view class="overlay" v-if="showMenu" @click="closeMenu"></view> |
|||
<view class="fixed-nav" :class="{'hidden': showMenu}" @click="showNavMenu"> |
|||
<image class="nav-icon" :class="{'rotated': iconRotated, 'bounce-back': iconBounceBack}" |
|||
src="https://static.ticket.sz-trip.com/epicSoul/taozi/nav-icon.png" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="nav-menu" :class="{'show': showMenu}"> |
|||
<view class="nav-item" :class="{'item-active': isItemActive(item)}" v-for="item in menuItems" |
|||
:key="item.targetIndex" @click="() => jumpToPage(item.targetIndex)"> |
|||
<view v-if="item.text.includes('#Chapter')" class="chapter-text"> |
|||
<text class="chapter-title">#Chapter</text> |
|||
<text :class="{'active': isItemActive(item)}" class="chapter-number"> |
|||
{{ item.text.replace('#Chapter', '') }} |
|||
</text> |
|||
</view> |
|||
<text v-else :class="{'active': isItemActive(item)}">{{ item.text }}</text> |
|||
</view> |
|||
</view> |
|||
<!-- <BuyPeaches /> --> |
|||
<messagePop /> |
|||
<!-- <BackgroundMusic /> --> |
|||
<MusicControl /> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import BuyPeaches from '@/components/BuyPeaches.vue'; |
|||
import messagePop from '@/components/messagePop.vue'; |
|||
// import BackgroundMusic from '@/components/BackgroundMusic.vue'; |
|||
import messageBoard from '@/components/messageBoard.vue'; |
|||
import MusicControl from '@/components/MusicControl.vue'; |
|||
import TitleHeader from '@/components/TitleHeader.vue'; |
|||
|
|||
export default { |
|||
components: { |
|||
BuyPeaches, |
|||
messagePop, |
|||
// BackgroundMusic, |
|||
messageBoard, |
|||
MusicControl, |
|||
TitleHeader |
|||
}, |
|||
data() { |
|||
return { |
|||
currentIndex: 0, |
|||
loadedPages: { |
|||
0: false, |
|||
1: false, |
|||
2: false, |
|||
3: false, |
|||
4: false, |
|||
5: false, |
|||
6: false, |
|||
7: false, |
|||
8: false, |
|||
9: false, |
|||
10: false |
|||
}, |
|||
animationStates: { |
|||
0: false, |
|||
1: false, |
|||
2: false, |
|||
3: false, |
|||
4: false, |
|||
5: false, |
|||
6: false, |
|||
7: false, |
|||
8: false, |
|||
9: false |
|||
}, |
|||
preloadBuffer: 1, |
|||
isFirstSwipe: true, |
|||
showMenu: false, |
|||
iconRotated: false, |
|||
iconBounceBack: false, |
|||
menuItems: [{ |
|||
text: 'Intro序曲', |
|||
targetIndex: 0 |
|||
}, |
|||
{ |
|||
text: '#Chapter 壹', |
|||
targetIndex: 4 |
|||
}, |
|||
{ |
|||
text: '#Chapter 贰', |
|||
targetIndex: 5 |
|||
}, |
|||
{ |
|||
text: '#Chapter 叁', |
|||
targetIndex: 6 |
|||
}, |
|||
{ |
|||
text: '#Chapter 肆', |
|||
targetIndex: 7 |
|||
}, |
|||
{ |
|||
text: 'GuestBook', |
|||
targetIndex: 10 |
|||
} |
|||
], |
|||
chapterPaths: { |
|||
4: '/taozi/chapter1/chapter1', |
|||
5: '/taozi/chapter2/chapter2', |
|||
6: '/taozi/chapter3/chapter3', |
|||
7: '/taozi/chapter4/chapter4' |
|||
}, |
|||
titleHeight: 0 |
|||
}; |
|||
}, |
|||
computed: { |
|||
shouldShowContent() { |
|||
return (index) => { |
|||
return Math.abs(index - this.currentIndex) <= this.preloadBuffer; |
|||
}; |
|||
} |
|||
}, |
|||
watch: { |
|||
currentIndex(newIndex) { |
|||
for (let i = Math.max(0, newIndex - this.preloadBuffer); i <= Math.min(10, newIndex + this |
|||
.preloadBuffer); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
handleSwiperChange(e) { |
|||
const newIndex = e.detail.current; |
|||
this.currentIndex = newIndex; |
|||
this.animationStates[newIndex] = false; |
|||
setTimeout(() => { |
|||
this.animationStates[newIndex] = true; |
|||
}, 50); |
|||
return; |
|||
if (this.isFirstSwipe && newIndex > 0) { |
|||
this.isFirstSwipe = false; |
|||
uni.$emit('playBackgroundMusic'); |
|||
} |
|||
}, |
|||
goChapter() { |
|||
const path = this.chapterPaths[this.currentIndex]; |
|||
uni.navigateTo({ |
|||
url: path |
|||
}); |
|||
}, |
|||
showNavMenu() { |
|||
this.iconRotated = true; |
|||
setTimeout(() => { |
|||
this.showMenu = true; |
|||
}, 300); |
|||
}, |
|||
closeMenu() { |
|||
this.showMenu = false; |
|||
setTimeout(() => { |
|||
this.iconBounceBack = true; |
|||
this.iconRotated = false; |
|||
setTimeout(() => { |
|||
this.iconBounceBack = false; |
|||
}, 500); |
|||
}, 300); |
|||
}, |
|||
jumpToPage(idx) { |
|||
const targetIndex = idx; |
|||
this.currentIndex = targetIndex; |
|||
this.closeMenu(); |
|||
this.animationStates[targetIndex] = false; |
|||
setTimeout(() => { |
|||
this.animationStates[targetIndex] = true; |
|||
}, 50); |
|||
}, |
|||
isItemActive(item) { |
|||
return this.currentIndex === item.targetIndex; |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.titleHeight = uni.getStorageSync('titleHeight') |
|||
console.log(this.titleHeight) |
|||
const app = getApp(); |
|||
app.updateMusicSrc('https://static.ticket.sz-trip.com/epicSoul/taozi/bg.m4a'); |
|||
app.initBackgroundMusic(); // 初始化背景音乐 |
|||
uni.$bgMusic.play(); // 播放音乐 |
|||
for (let i = 0; i <= Math.min(1 + this.preloadBuffer, 6); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
for (let i = 0; i <= Math.min(1 + this.preloadBuffer, 10); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
setTimeout(() => { |
|||
this.animationStates[this.currentIndex] = true; |
|||
}, 50); |
|||
}, |
|||
onUnload() { |
|||
uni.$bgMusic.pause(); // 停止音乐 |
|||
}, |
|||
onLoad(options) { |
|||
if (options && options.targetIndex) { |
|||
const targetIndex = parseInt(options.targetIndex); |
|||
this.currentIndex = targetIndex; |
|||
for (let i = Math.max(0, targetIndex - this.preloadBuffer); i <= Math.min(10, targetIndex + this |
|||
.preloadBuffer); i++) { |
|||
this.loadedPages[i] = true; |
|||
} |
|||
this.animationStates[targetIndex] = false; |
|||
setTimeout(() => { |
|||
this.animationStates[targetIndex] = true; |
|||
}, 50); |
|||
} |
|||
}, |
|||
// #ifdef MP-WEIXIN |
|||
onShareAppMessage() { |
|||
return { |
|||
title: '三个桃子·时间里的约定|「Epic Soul」阅读体 issue01', |
|||
mpId: 'wx8954209bb3ad489e', |
|||
path: '/taozi/home/home', |
|||
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/taozi/home/taoziShare.png' |
|||
}; |
|||
}, |
|||
onShareTimeline() { |
|||
return { |
|||
title: '三个桃子·时间里的约定|「Epic Soul」阅读体 issue01', |
|||
query: '', |
|||
imageUrl: 'https://static.ticket.sz-trip.com/epicSoul/taozi/home/taoziShare.png' |
|||
}; |
|||
} |
|||
// #endif |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
@font-face { |
|||
font-family: 'SourceHanSerif-Regular'; |
|||
src: url(/static/fonts/SourceHanSerifSC-Regular.otf); |
|||
} |
|||
|
|||
.main-swiper { |
|||
width: 100%; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.page-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 100%; |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.loadedPages-three { |
|||
height: 100%; |
|||
position: relative; |
|||
background: #fff; |
|||
} |
|||
|
|||
.loadedPages-three-content { |
|||
display: flex; |
|||
align-items: center; |
|||
flex-direction: column; |
|||
justify-content: space-between; |
|||
height: 100%; |
|||
|
|||
.loadedPages-three-title { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
width: 100%; |
|||
margin-top: 30rpx; |
|||
|
|||
.txt { |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
font-family: SourceHanSerif-Regular; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
|
|||
text { |
|||
display: block; |
|||
} |
|||
} |
|||
|
|||
.txt:first-child { |
|||
margin-left: 30rpx; |
|||
} |
|||
|
|||
.txt:last-child { |
|||
margin-right: 30rpx; |
|||
} |
|||
} |
|||
|
|||
.loadedPages-three-center { |
|||
position: relative; |
|||
|
|||
.desc { |
|||
display: flex; |
|||
flex-direction: column; |
|||
font-family: SourceHanSerif-Regular; |
|||
font-size: 90rpx; |
|||
color: #ec4899; |
|||
|
|||
text { |
|||
display: block; |
|||
} |
|||
} |
|||
|
|||
.en-desc { |
|||
display: flex; |
|||
flex-direction: column; |
|||
position: absolute; |
|||
top: 50%; |
|||
right: 0; |
|||
transform: translate(-25%, -50%); |
|||
font-size: 24rpx; |
|||
font-style: italic; |
|||
color: #4b5563; |
|||
|
|||
text { |
|||
display: block; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.loadedPages-three-bottom { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
margin-bottom: 30rpx; |
|||
|
|||
.bottom-img { |
|||
width: 400rpx; |
|||
height: 120rpx; |
|||
} |
|||
|
|||
.bottom-tit { |
|||
font-size: 38rpx; |
|||
} |
|||
|
|||
.bottom-txt { |
|||
font-size: 24rpx; |
|||
font-style: italic; |
|||
color: #4b5563; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.bg-image { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.content-layer { |
|||
position: relative; |
|||
z-index: 2; |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.content-layer2 { |
|||
z-index: 2; |
|||
position: absolute; |
|||
bottom: 5%; |
|||
right: 5%; |
|||
} |
|||
|
|||
.layer-img { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.arrow-content { |
|||
width: 100%; |
|||
position: absolute; |
|||
bottom: 5%; |
|||
left: 50%; |
|||
transform: translate(-50%, 0); |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.arrow-down { |
|||
width: 100rpx; |
|||
height: 40rpx; |
|||
animation: bounce 1.5s infinite; |
|||
} |
|||
|
|||
.layer-icon { |
|||
width: 100rpx; |
|||
height: 100rpx; |
|||
animation: bounce 1.5s infinite; |
|||
} |
|||
|
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: rgba(0, 0, 0, 0.3); |
|||
z-index: 10; |
|||
} |
|||
|
|||
.fixed-nav { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
background-color: rgb(0 0 0 / 0.7); |
|||
border-radius: 10rpx 0 0 10rpx; |
|||
position: fixed; |
|||
right: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
margin: auto 0; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
z-index: 9; |
|||
transition: transform 0.3s ease, opacity 0.3s ease; |
|||
} |
|||
|
|||
.fixed-nav.hidden { |
|||
transform: translateX(100%); |
|||
opacity: 0; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
.nav-icon { |
|||
width: 35rpx; |
|||
height: 35rpx; |
|||
transition: transform 0.3s ease; |
|||
} |
|||
|
|||
.nav-icon.rotated { |
|||
transform: rotate(180deg); |
|||
} |
|||
|
|||
.nav-icon.bounce-back { |
|||
animation: bounceRotation 0.5s ease; |
|||
} |
|||
|
|||
@keyframes bounceRotation { |
|||
0% { |
|||
transform: rotate(180deg); |
|||
} |
|||
|
|||
50% { |
|||
transform: rotate(-20deg); |
|||
} |
|||
|
|||
75% { |
|||
transform: rotate(10deg); |
|||
} |
|||
|
|||
100% { |
|||
transform: rotate(0deg); |
|||
} |
|||
} |
|||
|
|||
.nav-menu { |
|||
position: fixed; |
|||
top: 50%; |
|||
right: 0; |
|||
transform: translate(100%, -50%); |
|||
z-index: 11; |
|||
background-color: rgb(0 0 0 / 0.5); |
|||
border-radius: 16rpx 0 0 16rpx; |
|||
box-shadow: -4px 0 15px rgba(0, 0, 0, 0.1); |
|||
transition: transform 0.3s ease; |
|||
} |
|||
|
|||
.nav-menu.show { |
|||
transform: translate(0, -50%); |
|||
} |
|||
|
|||
.nav-item { |
|||
padding: 20rpx; |
|||
text-align: center; |
|||
|
|||
text { |
|||
color: #fff; |
|||
opacity: 0.7; |
|||
font-size: 28rpx; |
|||
} |
|||
} |
|||
|
|||
.item-active { |
|||
background-color: rgba(0, 0, 0, 0.718); |
|||
} |
|||
|
|||
.nav-item .active { |
|||
color: #fff; |
|||
opacity: 1; |
|||
} |
|||
|
|||
.chapter-text { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
line-height: 1.3; |
|||
} |
|||
|
|||
.chapter-title { |
|||
color: #fff; |
|||
opacity: 0.7; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.chapter-number { |
|||
color: #fff; |
|||
opacity: 0.7; |
|||
font-size: 28rpx; |
|||
margin-top: 8rpx; |
|||
} |
|||
|
|||
.item-active .chapter-title, |
|||
.item-active .chapter-number.active { |
|||
opacity: 1; |
|||
} |
|||
|
|||
@keyframes bounce { |
|||
|
|||
0%, |
|||
20%, |
|||
50%, |
|||
80%, |
|||
100% { |
|||
transform: translateY(0); |
|||
} |
|||
|
|||
40% { |
|||
transform: translateY(-20rpx); |
|||
} |
|||
|
|||
60% { |
|||
transform: translateY(-10rpx); |
|||
} |
|||
} |
|||
|
|||
.blur-to-clear { |
|||
animation: blurToClear 1.5s ease-out forwards; |
|||
} |
|||
|
|||
@keyframes blurToClear { |
|||
0% { |
|||
filter: blur(10px); |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
100% { |
|||
filter: blur(0); |
|||
opacity: 1; |
|||
} |
|||
} |
|||
|
|||
.hidden { |
|||
opacity: 0; |
|||
} |
|||
|
|||
.bounce-in { |
|||
animation: bounceIn 1s ease forwards; |
|||
} |
|||
|
|||
@keyframes bounceIn { |
|||
0% { |
|||
opacity: 0; |
|||
transform: scale(0.3) translateY(100px); |
|||
} |
|||
|
|||
50% { |
|||
opacity: 1; |
|||
transform: scale(1.05) translateY(-10px); |
|||
} |
|||
|
|||
70% { |
|||
transform: scale(0.9) translateY(5px); |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: scale(1) translateY(0); |
|||
} |
|||
} |
|||
|
|||
.fade-slide-up { |
|||
animation: fadeSlideUp 1s ease-out forwards; |
|||
} |
|||
|
|||
@keyframes fadeSlideUp { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateY(30px); |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
|
|||
.chapterCover-btn { |
|||
position: absolute; |
|||
left: 50%; |
|||
bottom: 10%; |
|||
transform: translate(-50%, -50%); |
|||
width: 300rpx; |
|||
height: 100rpx; |
|||
z-index: 2; |
|||
} |
|||
|
|||
.qrcode-txt { |
|||
width: 30vw; |
|||
z-index: 2; |
|||
position: fixed; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 100rpx auto 0; |
|||
} |
|||
.qrcode-txts { |
|||
width: 28vw; |
|||
z-index: 2; |
|||
position: fixed; |
|||
left: 0; |
|||
right: 0; |
|||
margin: 335rpx auto 0; |
|||
} |
|||
|
|||
.message-board { |
|||
width: 100%; |
|||
} |
|||
|
|||
.qrCode-image { |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 192rpx; |
|||
margin: 0 auto; |
|||
z-index: 2; |
|||
width: 30vw; |
|||
} |
|||
</style> |
@ -0,0 +1,108 @@ |
|||
let instance = null; |
|||
|
|||
class AudioManager { |
|||
constructor() { |
|||
if (instance) { |
|||
return instance; |
|||
} |
|||
|
|||
this.audioContext = null; |
|||
this.isPlaying = false; |
|||
this.userDisabled = false; |
|||
this.initialized = false; |
|||
|
|||
try { |
|||
const musicState = uni.getStorageSync('musicState') || {}; |
|||
this.userDisabled = musicState.userDisabled === true; |
|||
} catch (e) { |
|||
console.error('读取音乐状态失败:', e); |
|||
} |
|||
|
|||
instance = this; |
|||
} |
|||
|
|||
init() { |
|||
if (this.initialized) return this; |
|||
|
|||
this.audioContext = uni.createInnerAudioContext(); |
|||
this.audioContext.src = 'https://static.ticket.sz-trip.com/epicSoul/taozi/bg.m4a'; |
|||
this.audioContext.loop = true; |
|||
|
|||
this.audioContext.onPlay(() => { |
|||
this.isPlaying = true; |
|||
this._notifyStateChange(); |
|||
}); |
|||
|
|||
this.audioContext.onPause(() => { |
|||
this.isPlaying = false; |
|||
this._notifyStateChange(); |
|||
}); |
|||
|
|||
this.audioContext.onStop(() => { |
|||
this.isPlaying = false; |
|||
this._notifyStateChange(); |
|||
}); |
|||
|
|||
this.audioContext.onError((res) => { |
|||
this.isPlaying = false; |
|||
this._notifyStateChange(); |
|||
}); |
|||
|
|||
this.initialized = true; |
|||
return this; |
|||
} |
|||
|
|||
play() { |
|||
if (this.userDisabled) return; |
|||
|
|||
this.init(); |
|||
if (!this.isPlaying) { |
|||
this.audioContext.play(); |
|||
} |
|||
} |
|||
|
|||
pause() { |
|||
if (!this.initialized || !this.isPlaying) return; |
|||
|
|||
this.audioContext.pause(); |
|||
} |
|||
|
|||
togglePlay() { |
|||
this.init(); |
|||
|
|||
if (this.isPlaying) { |
|||
this.pause(); |
|||
this.userDisabled = true; |
|||
} else { |
|||
this.userDisabled = false; |
|||
this.play(); |
|||
} |
|||
|
|||
try { |
|||
uni.setStorageSync('musicState', { |
|||
userDisabled: this.userDisabled |
|||
}); |
|||
} catch (e) { |
|||
console.error('保存音乐状态失败:', e); |
|||
} |
|||
|
|||
return this.isPlaying; |
|||
} |
|||
|
|||
_notifyStateChange() { |
|||
uni.$emit('audioStateChanged', { |
|||
isPlaying: this.isPlaying, |
|||
userDisabled: this.userDisabled |
|||
}); |
|||
} |
|||
|
|||
getPlayingStatus() { |
|||
return this.isPlaying; |
|||
} |
|||
|
|||
getUserDisabled() { |
|||
return this.userDisabled; |
|||
} |
|||
} |
|||
|
|||
export default new AudioManager(); |
Loading…
Reference in new issue