319 lines
9.1 KiB
Vue
319 lines
9.1 KiB
Vue
<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>
|
|
<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 class="guide-info">
|
|
<text class="guide-title">{{ item.title }}</text>
|
|
<text class="guide-sub">{{ item.sub }}</text>
|
|
<view class="guide-tag">官方指南</view>
|
|
</view>
|
|
</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: [
|
|
{
|
|
title: '怀化社保卡惠民活动参加指南',
|
|
sub: '本地文旅场馆、商户惠民福利',
|
|
bg: 'https://aliyunoss.hn12301.net/hhrs/hhrs_cjzn.png',
|
|
url: '/pages/guide/join'
|
|
},
|
|
{
|
|
title: '社保卡领取及使用指南',
|
|
sub: '全流程办理及激活指引',
|
|
bg: 'https://aliyunoss.hn12301.net/hhrs/hhrs_syzn.png',
|
|
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 {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 252rpx;
|
|
border-radius: 16rpx;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.guide-bg {
|
|
width: 100%;
|
|
height: 252rpx;
|
|
border-radius: 16rpx;
|
|
}
|
|
|
|
.guide-info {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 0 30rpx;
|
|
}
|
|
|
|
.guide-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #0088FF;
|
|
margin-bottom: 10rpx;
|
|
}
|
|
|
|
.guide-sub {
|
|
font-size: 12px;
|
|
color: #949AA0;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
|
|
.guide-tag {
|
|
display: inline-block;
|
|
width: fit-content;
|
|
padding: 4px 10px;
|
|
background-color: #e6f2ff;
|
|
color: #0088FF;
|
|
font-size: 12px;
|
|
border-radius: 6rpx;
|
|
}
|
|
|
|
|
|
</style>
|