buddhism/pages/nearbystores/index.vue
2025-08-14 11:22:53 +08:00

748 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<!-- 加载状态指示器 -->
<view v-if="loading" class="loading-overlay">
<view class="loading-content">
<text>正在加载配置...</text>
</view>
</view>
<!-- 背景图 -->
<image class="bj" :src="templeData.imgUrl" mode="aspectFill"></image>
<!-- 公告 -->
<view class="announcement">
<image class="ggimg" :src="pageConfig.announcement.icon" mode="" />
<view class="marquee-wrap">
<view class="marquee" :style="{ transform: `translateX(${marqueeX}rpx)` }">
{{ templeData.bulletinContent }}
</view>
</view>
</view>
<!-- 音频与VR图标 -->
<view class="tubiao">
<view class="audio-controls">
<image
v-if="pageConfig.topIcons.leftIcon.hidden"
:src="pageConfig.topIcons.leftIcon.img"
mode=""
@click="playAudio"
:class="{ playing: isAudioPlaying }"
></image>
<image
:src="pageConfig.topIcons.rightIcon.img"
mode=""
@click="stopAudio"
:class="{ stopped: !isAudioPlaying }"
></image>
</view>
<view class="tubiao-item">
<image :src="pageConfig.topIcons.bottomIcon.img" mode=""></image>
</view>
</view>
<!-- 底部 -->
<view class="bot">
<view class="name">
<image :src="bottomSection.openingTime.decorationImg" mode=""></image>
<text>{{ bottomSection.openingTime.title }}</text>
<image :src="bottomSection.openingTime.decorationImg" mode=""></image>
</view>
<view class="time"> {{ templeData.startTime }} - {{ templeData.endTime }} </view>
<view class="hua">
<image
src="https://api.ccttiot.com/smartmeter/img/static/uyz1LDPTjPqeOzBMjLZ7"
mode=""
></image>
</view>
<view class="list-scroll">
<view class="list">
<!-- 导航项目列表 -->
<view
class="li"
v-for="(item, index) in navigationItems"
:key="index"
@click="navigateToPage(item, index)"
>
<image :src="item.img" mode=""></image>
<view class="da">
{{ item.title }}
</view>
<view class="xiao">
{{ item.subtitle }}
</view>
</view>
<!-- 空状态提示 -->
<view v-if="!loading && navigationItems.length === 0" class="empty-state">
<text>暂无功能项目</text>
</view>
</view>
</view>
<view
class="bottom"
v-if="bottomSection.prayerSection && bottomSection.prayerSection.backgroundImg"
>
<image :src="bottomSection.prayerSection.backgroundImg" mode=""></image>
<view class="rixing">
{{ bottomSection.prayerSection.title }}
</view>
<view class="qifu" @click="goToPray">
<image :src="bottomSection.prayerSection.prayerButton.icon" mode=""></image>
<view class="zaixian">
<view class="da">
{{ bottomSection.prayerSection.prayerButton.title }}
</view>
<view class="xiao">
{{ bottomSection.prayerSection.prayerButton.subtitle }}
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { navigateToPage } from '../../utils/router.js'
import { getHomeConfig, getTempleIndex } from '../../api/index/index.js'
import { getArticleById } from '../../api/article/article.js'
export default {
data() {
return {
marqueeX: '', // 初始位置和marquee-wrap宽度一致
timer: null,
loading: true, // 默认为true等待API数据加载
// 页面配置数据完全依赖API
pageConfig: {
background: { img: '' },
announcement: { icon: '', text: '' },
topIcons: {
leftIcon: { hidden: false, img: '' },
rightIcon: { img: '' },
bottomIcon: { img: '' },
},
},
navigationItems: [],
bottomSection: {
openingTime: {
title: '',
time: '',
decorationImg: '',
},
},
templeData: {
imgUrl: '',
bulletinContent: '',
startTime: '',
endTime: '',
audioUrl: '',
},
isAudioPlaying: false,
audioContext: null,
}
},
onLoad() {
// 页面加载时获取配置
this.loadHomeConfig()
// 获取寺庙数据
this.loadTempleData()
},
// 添加下拉刷新支持
onPullDownRefresh() {
console.log('用户触发下拉刷新')
Promise.all([this.loadHomeConfig(), this.loadTempleData()]).finally(() => {
uni.stopPullDownRefresh()
})
},
onShow() {
// 启动跑马灯(方法内部会检查是否有文本)
this.startMarquee()
},
onUnload() {
this.stopMarquee()
// 停止音频播放
this.stopAudio()
},
methods: {
/**
* 获取首页配置
*/
async loadHomeConfig() {
console.log('开始获取首页配置...')
this.loading = true
// 重试机制
const maxRetries = 3
let retryCount = 0
while (retryCount < maxRetries) {
try {
console.log(`第 ${retryCount + 1} 次尝试获取配置...`)
const response = await getHomeConfig()
console.log('API响应:', response)
// 简化验证:只检查关键字段
if (response?.code === 200 && response?.data?.[0]?.body) {
const parsedConfig = JSON.parse(response.data[0].body)
// 更新页面配置
this.updatePageConfig(parsedConfig)
// 重新启动跑马灯
this.stopMarquee()
this.startMarquee()
console.log('配置加载成功')
break
} else {
throw new Error('API响应数据无效')
}
} catch (error) {
retryCount++
console.error(`第 ${retryCount} 次尝试失败:`, error.message)
if (retryCount >= maxRetries) {
console.error('所有重试都失败了')
this.useDefaultConfig()
break
} else {
await new Promise(resolve => setTimeout(resolve, retryCount * 1000))
}
}
}
this.loading = false
console.log('配置加载完成')
},
/**
* 处理配置加载失败
*/
useDefaultConfig() {
console.log('API获取失败无法加载配置')
// 不设置任何默认配置,保持初始化的空状态
// 数据保持为初始化时的空结构
// 显示提示信息
uni.showModal({
title: '加载失败',
content: '无法获取页面配置,请检查网络连接后重试',
showCancel: true,
cancelText: '取消',
confirmText: '重试',
success: res => {
if (res.confirm) {
this.loadHomeConfig()
}
},
})
},
/**
* 更新页面配置
*/
updatePageConfig(parsedConfig) {
// 直接更新配置使用默认值防止undefined
this.pageConfig = parsedConfig.pageConfig || this.pageConfig
this.navigationItems = parsedConfig.navigationItems || []
this.bottomSection = parsedConfig.bottomSection || this.bottomSection
console.log('配置更新完成')
},
/**
* 获取寺庙数据
*/
async loadTempleData() {
try {
console.log('开始获取寺庙数据...')
const response = await getTempleIndex()
console.log('寺庙数据API响应:', response)
if (response?.code === 200 && response?.data) {
this.templeData = {
imgUrl: response.data.imgUrl || '',
bulletinContent: response.data.bulletinContent || '',
startTime: response.data.startTime || '',
endTime: response.data.endTime || '',
audioUrl: response.data.audioUrl || '',
}
uni.setStorageSync('abbotId', response.data.abbotId)
// 重新启动跑马灯
this.stopMarquee()
this.startMarquee()
console.log('寺庙数据加载成功')
} else {
throw new Error('寺庙数据API响应无效')
}
} catch (error) {
console.error('获取寺庙数据失败:', error)
uni.showToast({
title: '获取寺庙数据失败',
icon: 'none',
})
}
},
startMarquee() {
// 简单检查公告文本是否存在
const announcementText =
this.templeData?.bulletinContent || this.pageConfig?.announcement?.text
if (!announcementText) {
return
}
// 停止之前的定时器
this.stopMarquee()
// 估算文字宽度每个字24rpx
const textWidth = announcementText.length * 24
this.timer = setInterval(() => {
this.marqueeX--
if (this.marqueeX < -textWidth) {
this.marqueeX = 600
}
}, 16)
},
stopMarquee() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
},
// 兼容两种跳转方式page路由跳转 或 aid文章详情跳转
async navigateToPage(item, index) {
try {
console.log('导航项信息:', item)
console.log('导航项索引:', index)
// 优先使用page字段进行路由跳转
if (item.page) {
console.log('使用page字段进行路由跳转:', item.page)
// 导入路由工具
const { navigateToPage: routerNavigate } = await import('../../utils/router.js')
routerNavigate(item.page)
return
}
// 如果没有page字段则使用aid进行文章详情跳转
if (item.aid) {
console.log('使用aid字段获取文章详情:', item.aid)
// 显示加载提示
uni.showLoading({
title: '加载中...',
mask: true,
})
// 调用API获取文章详情
const response = await getArticleById(item.aid)
// 隐藏加载提示
uni.hideLoading()
if (response.code === 200 && response.data) {
console.log('获取文章详情成功:', response.data)
// 将文章数据存储到本地,供目标页面使用
uni.setStorageSync('currentArticle', response.data)
// 直接跳转到文章详情页面
uni.navigateTo({
url: '/pages/article/article-detail',
fail: err => {
console.error('跳转到文章详情页面失败:', err)
uni.showToast({
title: '页面跳转失败',
icon: 'none',
})
},
})
} else {
console.error('获取文章详情失败:', response)
uni.showToast({
title: '获取内容失败',
icon: 'none',
})
}
} else {
console.error('导航项既没有page字段也没有aid字段:', item)
uni.showToast({
title: '配置错误',
icon: 'none',
})
}
} catch (error) {
// 隐藏加载提示
uni.hideLoading()
console.error('导航跳转出错:', error)
uni.showToast({
title: '网络错误',
icon: 'none',
})
}
},
/**
* 跳转到祈福页面
*/
goToPray() {
try {
console.log('跳转到祈福页面')
// 使用navigateToPage方法传入参数pray
navigateToPage('pray')
} catch (error) {
console.error('跳转到祈福页面失败:', error)
uni.showToast({
title: '页面跳转失败',
icon: 'none',
})
}
},
/**
* 播放音频
*/
playAudio() {
if (!this.templeData.audioUrl) {
uni.showToast({
title: '暂无音频资源',
icon: 'none',
})
return
}
try {
// 如果已经在播放,先停止
if (this.audioContext) {
this.stopAudio()
}
// 创建音频上下文
this.audioContext = uni.createInnerAudioContext()
this.audioContext.src = this.templeData.audioUrl
this.audioContext.loop = true // 循环播放
// 监听播放状态
this.audioContext.onPlay(() => {
console.log('音频开始播放')
this.isAudioPlaying = true
uni.showToast({
title: '音频已开启',
icon: 'none',
})
})
this.audioContext.onError(err => {
console.error('音频播放错误:', err)
this.isAudioPlaying = false
uni.showToast({
title: '音频播放失败',
icon: 'none',
})
})
this.audioContext.onEnded(() => {
console.log('音频播放结束')
this.isAudioPlaying = false
})
// 开始播放
this.audioContext.play()
} catch (error) {
console.error('音频播放失败:', error)
this.isAudioPlaying = false
uni.showToast({
title: '音频播放失败',
icon: 'none',
})
}
},
/**
* 停止音频
*/
stopAudio() {
if (this.audioContext) {
this.audioContext.stop()
this.audioContext.destroy()
this.audioContext = null
}
this.isAudioPlaying = false
uni.showToast({
title: '音频已关闭',
icon: 'none',
})
},
},
}
</script>
<style lang="scss">
page {
background-color: #fff;
}
.bot {
position: fixed;
left: 50%;
transform: translateX(-50%);
bottom: 56rpx;
width: 100%;
max-width: 750rpx;
/* 确保不会限制子元素的滚动 */
overflow: visible;
.bottom {
margin-top: 64rpx;
display: flex;
padding: 0 118rpx;
box-sizing: border-box;
image {
width: 64rpx;
height: 64rpx;
}
.rixing {
width: 206rpx;
height: 64rpx;
background: #522510;
border-radius: 45rpx 45rpx 45rpx 45rpx;
text-align: center;
line-height: 64rpx;
font-weight: 600;
font-size: 24rpx;
color: #ffffff;
border-radius: 50rpx;
margin-left: 26rpx;
margin-right: 26rpx;
}
.qifu {
width: 194rpx;
height: 64rpx;
border-radius: 41rpx 41rpx 41rpx 41rpx;
border: 1rpx solid #522510;
display: flex;
align-items: center;
justify-content: center;
image {
width: 32rpx;
height: 32rpx;
}
.zaixian {
margin-left: 12rpx;
.da {
font-size: 24rpx;
color: #522510;
font-weight: 600;
}
.xiao {
font-size: 12rpx;
color: #522510;
}
}
}
}
.list-scroll {
width: 100%;
margin-top: 20rpx;
height: 120rpx;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
/* 确保滚动容器不被父元素限制 */
position: relative;
z-index: 1;
/* 隐藏滚动条 */
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
.list {
display: flex;
width: 1600rpx; /* 更大的宽度确保滚动 */
padding: 0 20rpx;
box-sizing: border-box;
/* 确保内容不被压缩 */
flex-shrink: 0;
.li {
width: 150rpx;
flex: 0 0 150rpx;
text-align: center;
margin-right: 20rpx;
image {
width: 56rpx;
height: 56rpx;
}
.da {
font-size: 24rpx;
color: #522510;
margin-top: 8rpx;
}
.xiao {
font-size: 12rpx;
color: #522510;
}
}
.empty-state {
width: 100%;
text-align: center;
padding: 40rpx;
color: #999;
font-size: 28rpx;
}
}
.hua {
width: 100%;
text-align: center;
margin-top: 30rpx;
image {
width: 656rpx;
height: 108rpx;
}
}
.time {
font-weight: 600;
font-size: 32rpx;
color: #522510;
margin-top: 8rpx;
width: 100%;
text-align: center;
}
.name {
display: flex;
align-items: center;
width: 100%;
justify-content: center;
text {
font-weight: 600;
font-size: 32rpx;
color: #522510;
margin-left: 24rpx;
margin-right: 24rpx;
}
image {
width: 12rpx;
height: 12rpx;
}
}
}
.tubiao {
width: 100%;
display: flex;
padding: 0 40rpx;
box-sizing: border-box;
justify-content: space-between;
.audio-controls {
display: flex;
align-items: center;
gap: 26rpx;
}
image {
width: 82rpx;
height: 82rpx;
display: block;
margin-top: 50rpx;
transition: all 0.3s ease;
&:active {
transform: scale(0.9);
opacity: 0.8;
}
&.playing {
animation: pulse 2s infinite;
}
&.stopped {
opacity: 0.6;
}
}
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
.announcement {
width: 100%;
margin-top: 184rpx;
padding: 0 48rpx;
box-sizing: border-box;
display: flex;
align-items: center;
white-space: nowrap;
.ggimg {
width: 32rpx;
height: 32rpx;
margin-right: 14rpx;
}
.marquee-wrap {
width: 600rpx;
overflow: hidden;
position: relative;
height: 32rpx;
display: flex;
align-items: center;
}
.marquee {
white-space: nowrap;
position: absolute;
left: 0;
top: 0;
font-size: 24rpx;
color: #522510;
}
}
.bj {
width: 100%;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: -1;
}
/* 加载状态指示器样式 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-content {
background-color: rgba(255, 255, 255, 0.9);
padding: 40rpx 60rpx;
border-radius: 20rpx;
text-align: center;
}
.loading-content text {
font-size: 28rpx;
color: #333;
}
</style>