Files
hhrs/pages/index/index.vue
T

276 lines
8.4 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">
<!-- 顶部 Banner -->
<view class="banner">
<image class="banner-img" src="https://aliyunoss.hn12301.net/hhrs/hhrs_header.png" mode="widthFix"></image>
</view>
<!-- 金刚区 -->
<view class="king-area">
<view class="king-item" v-for="(item, index) in kingList" :key="index" @click="item.isRights ? goRights() : goPage(item.url)">
<image class="king-icon" :src="item.icon" mode="widthFix"></image>
<text class="king-text">{{ item.text }}</text>
</view>
</view>
<!-- 活动指南 -->
<view class="guide-section">
<view class="section-title">
<view class="title-line"></view>
<text class="title-text">活动指南</text>
</view>
<!-- xuchao: 图内已带标题/副标题/官方指南角标,不再叠加文字层,只保留图片 -->
<view class="guide-list">
<view class="guide-card" v-for="(item, index) in guideList" :key="index" @click="goPage(item.url)">
<image class="guide-bg" :src="item.bg" mode="scaleToFill"></image>
</view>
</view>
</view>
</view>
</template>
<script>
import account from '@/common/account.js'
export default {
data() {
return {
kingList: [
{ icon: 'https://aliyunoss.hn12301.net/hhrs/hhrs_wlcg.png', text: '文旅场馆', url: '/pages/venue/venue' },
{ icon: 'https://aliyunoss.hn12301.net/hhrs/hhrs_shzk.png', text: '商户折扣', url: '/pages/discount/discount' },
{ icon: 'https://aliyunoss.hn12301.net/hhrs/hhrs_qyb.png', text: '权益包', url: '/pages/rights/rights', isRights: true },
{ icon: 'https://aliyunoss.hn12301.net/hhrs/hhrs_zjmd.png', text: '中奖名单', url: '/pages/winners/winners' }
],
guideList: [
{
// xuchao: ?v= 为图片版本参数,运营换图必须同步更新日期,否则各平台 WebView/CDN 会继续命中旧缓存
bg: 'https://aliyunoss.hn12301.net/hhrs/hhrs_cjzn.png?v=20260928',
url: '/pages/guide/join'
},
{
bg: 'https://aliyunoss.hn12301.net/hhrs/hhrs_syzn.png?v=20260928',
url: '/pages/guide/use'
}
],
// xuchao: 云闪付免密登录是否仍在进行中 —— 返回前不放行任何依赖身份的跳转
tokenLoginPending: false,
tokenLoginTimer: null
}
},
onLoad(options) {
// xuchao: 云闪付入口带 token 进来 —— 直接免密换取会话,不经过手机号验证码
if (options.token) {
this.handleTokenLogin(options.token)
}
this.getLocation()
},
// xuchao: 离开首页时收尾,避免看门狗与遮罩残留
onUnload() {
this.finishTokenLogin()
},
methods: {
// xuchao: 云闪付入口 token 自动登录(免密换人)
handleTokenLogin(token) {
// xuchao: 云闪付入口是"免密换人",本机上一个账号的会话与权益缓存必须先丢弃:
// 一是请求不能再带旧 accessToken(后端"会话优先"会把旧账号数据返给新用户),
// 二是权益页只在 onLoad 取一次账号快照、不会自我纠正,所以清缓存必须赶在
// 发请求之前 —— 解码返回前的窗口期里最多读到"未登录",绝不会读到别人的数据
account.clearAccountCache()
this.tokenLoginPending = true
uni.showLoading({ title: '登录中', mask: true })
// xuchao: 兜底看门狗,网络异常时不让遮罩卡死(只收遮罩,不改变登录结果)
this.tokenLoginTimer = setTimeout(() => {
this.finishTokenLogin()
}, 10000)
this.$u.api.tokenDecode({ token }).then(res => {
this.finishTokenLogin()
if (res.errcode === 0 && res.data && res.data.mobile) {
uni.setStorageSync('phone', res.data.mobile)
// xuchao: 后端已为云闪付入口签发会员会话,存下 accessToken,后续接口才认得这个用户。
// 只认"非空字符串":后端字段缺失/为 null 时会被序列化成 {},直接存进缓存
// 就会变成 Bearer [object Object],把所有鉴权接口全打挂
const issued = typeof res.data.accessToken === 'string' ? res.data.accessToken : ''
if (issued) uni.setStorageSync('token', issued)
this.$u.api.getEligibility({ phone: res.data.mobile, identityNo: res.data.idNo || '', name: res.data.name || '' }).then(eRes => {
if (eRes.errcode === 0) {
uni.setStorageSync('isAuth', eRes.data.isAuth)
uni.setStorageSync('hasWin', eRes.data.hasWin)
uni.setStorageSync('cardNumber', eRes.data.cardNumber)
uni.setStorageSync('expiresTime', eRes.data.expiresTime)
}
})
// xuchao: 清除地址栏 token,防止分享泄露
this.clearUrlToken()
} else {
this.degradeToPhoneLogin()
}
}).catch(() => {
this.finishTokenLogin()
this.degradeToPhoneLogin()
})
},
// xuchao: 免密登录收尾(收起遮罩、撤看门狗),成功/失败/超时/离开页面都会走到
finishTokenLogin() {
if (!this.tokenLoginPending) return
this.tokenLoginPending = false
clearTimeout(this.tokenLoginTimer)
this.tokenLoginTimer = null
uni.hideLoading()
},
// xuchao: 解码失败兜底,提示并跳手机号登录
degradeToPhoneLogin() {
uni.showToast({ title: '登录凭证已失效,请使用手机号登录', icon: 'none' })
setTimeout(() => {
uni.navigateTo({ url: '/pages/index/login' })
}, 800)
},
// xuchao: 从 URL hash 中移除 token 参数
clearUrlToken() {
try {
const hash = window.location.hash || ''
const qIndex = hash.indexOf('?')
if (qIndex === -1) return
const params = new URLSearchParams(hash.substring(qIndex + 1))
if (!params.has('token')) return
params.delete('token')
const rest = params.toString()
const base = hash.substring(0, qIndex)
window.history.replaceState(null, '', rest ? `${base}?${rest}` : base)
} catch (e) {}
},
getLocation() {
import('@/common/location.js').then(module => {
module.default.getLocation().then(({ lng, lat }) => {
console.log('定位成功:', lng, lat)
}).catch(err => {
console.log('定位失败:', err)
})
})
},
goPage(url) {
uni.navigateTo({ url })
},
goRights() {
// xuchao: 免密登录还没返回时不放行,避免带着空/旧身份进权益页
if (this.tokenLoginPending) {
uni.showToast({ title: '正在登录,请稍候', icon: 'none' })
return
}
const phone = uni.getStorageSync('phone')
if (!phone) {
uni.navigateTo({ url: '/pages/index/login?redirect=rights' })
return
}
const isAuth = uni.getStorageSync('isAuth')
const hasWin = uni.getStorageSync('hasWin')
// xuchao: 最近发起过认证流程(含取消)时忽略旧认证缓存,一律回认证页
const authFlowStarted = uni.getStorageSync('authFlowStarted')
let status = 'auth'
if (isAuth && !authFlowStarted) {
status = hasWin ? 'info' : 'noWin'
}
uni.navigateTo({ url: `/pages/rights/rights?status=${status}` })
}
}
}
</script>
<style>
/* xuchao: 原 page/.page 固定 100vh + overflow:hidden,小屏下活动指南第二张卡片会被裁掉且无法滚动;
改为 min-height 撑满、超出可滚动,视觉在正常屏上保持不变 */
.page {
min-height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
.banner-img {
width: 100%;
display: block;
}
/* 金刚区 */
.king-area {
width: 100%;
display: flex;
justify-content: space-between;
align-items: flex-start;
background-color: #ffffff;
padding: 24rpx;
box-sizing: border-box;
}
.king-item {
width: 25%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.king-icon {
width: 100rpx;
height: 132rpx;
margin-bottom: 12rpx;
}
.king-text {
font-size: 24rpx;
color: #333333;
text-align: center;
}
/* 活动指南 */
.guide-section {
flex: 1;
background-color: #ffffff;
margin-top: 10rpx;
display: flex;
flex-direction: column;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 20rpx;
padding: 24rpx 20rpx 0 24px;
}
.title-line {
width: 6rpx;
height: 32rpx;
background-color: #1890ff;
border-radius: 3rpx;
margin-right: 12rpx;
}
.title-text {
font-size: 14px;
font-weight: 600;
color: #333333;
}
.guide-list {
display: flex;
flex-direction: column;
gap: 12px;
padding: 0 20rpx 30rpx;
}
.guide-card {
width: 100%;
height: 252rpx;
border-radius: 16rpx;
overflow: hidden;
}
.guide-bg {
width: 100%;
height: 252rpx;
border-radius: 16rpx;
}
</style>