合并:中奖名单公示页搜索修复(feature/venue-discount-rights-filter → xuchao)
冲突解决:venue 列表灰底间距与卡片内边距取 feature 侧设计修正;card-address 保留 min-width:0;index 页面取页级滚动方案。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+50
-57
@@ -19,14 +19,10 @@
|
||||
<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 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>
|
||||
@@ -34,6 +30,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import account from '@/common/account.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -45,32 +43,54 @@
|
||||
],
|
||||
guideList: [
|
||||
{
|
||||
title: '怀化社保卡惠民活动参加指南',
|
||||
sub: '本地文旅场馆、商户惠民福利',
|
||||
bg: 'https://aliyunoss.hn12301.net/hhrs/hhrs_cjzn.png',
|
||||
// xuchao: ?v= 为图片版本参数,运营换图必须同步更新日期,否则各平台 WebView/CDN 会继续命中旧缓存
|
||||
bg: 'https://aliyunoss.hn12301.net/hhrs/hhrs_cjzn.png?v=20260928',
|
||||
url: '/pages/guide/join'
|
||||
},
|
||||
{
|
||||
title: '社保卡领取及使用指南',
|
||||
sub: '全流程办理及激活指引',
|
||||
bg: 'https://aliyunoss.hn12301.net/hhrs/hhrs_syzn.png',
|
||||
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 自动登录
|
||||
// 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)
|
||||
@@ -85,9 +105,18 @@
|
||||
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' })
|
||||
@@ -122,6 +151,11 @@
|
||||
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' })
|
||||
@@ -142,15 +176,11 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* xuchao: 原 page/.page 固定 100vh + overflow:hidden,小屏下活动指南第二张卡片会被裁掉且无法滚动;
|
||||
改为 min-height 撑满、超出可滚动,视觉在正常屏上保持不变 */
|
||||
.page {
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -202,7 +232,6 @@
|
||||
flex: 1;
|
||||
background-color: #ffffff;
|
||||
margin-top: 10rpx;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -236,7 +265,6 @@
|
||||
}
|
||||
|
||||
.guide-card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 252rpx;
|
||||
border-radius: 16rpx;
|
||||
@@ -249,40 +277,5 @@
|
||||
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>
|
||||
|
||||
+43
-14
@@ -43,6 +43,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from '@/common/config.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -63,6 +65,24 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// xuchao: open-api/hhService/login、sendLoginVerifyCode 返回 OpenapiResponse{code,msg,data},
|
||||
// 被网关再包一层后外层 errcode 恒为 0,真正的业务码在 data.code 里。
|
||||
// 只判 errcode 会把「验证码错误/过期、短信被限流」全部当成成功。
|
||||
// 宽松兜底:后端若改成不返回 data.code,仍按 errcode 判定,不会把成功误判为失败。
|
||||
isBizOk(res) {
|
||||
if (!res || res.errcode !== 0) return false
|
||||
const d = res.data
|
||||
if (!d || d.code === undefined || d.code === null) {
|
||||
// xuchao: 没有业务码时,还要排除“网关把 404/错误页包成 HTTP 200”的情况
|
||||
// (实测未部署的新地址会返回 {data:{timestamp,status:404,error:'Not Found'}} 且 errcode=0)
|
||||
return !d || (d.status === undefined && d.error === undefined)
|
||||
}
|
||||
return d.code === 200
|
||||
},
|
||||
// xuchao: 失败原因优先取业务 msg,其次取网关 errmsg
|
||||
bizMsg(res) {
|
||||
return (res && res.data && res.data.msg) || (res && res.errmsg) || ''
|
||||
},
|
||||
sendCode() {
|
||||
if (this.countdown > 0) return
|
||||
if (!this.phone || !/^1\d{10}$/.test(this.phone)) {
|
||||
@@ -70,22 +90,24 @@ export default {
|
||||
return
|
||||
}
|
||||
this.$u.api.sendLoginVerifyCode({ smsType: 'CIB_LOGIN', mobile: this.phone }).then(res => {
|
||||
if (res.errcode === 0) {
|
||||
if (this.isBizOk(res)) {
|
||||
uni.showToast({ title: '验证码已发送', icon: 'success' })
|
||||
// xuchao: 倒计时移到发送成功之后(原来无条件启动,发送失败也会被白锁 60 秒无法重试)
|
||||
this.countdown = 60
|
||||
this.timer = setInterval(() => {
|
||||
this.countdown--
|
||||
if (this.countdown <= 0) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}, 1000)
|
||||
} else {
|
||||
uni.showToast({ title: res.errmsg || '发送失败', icon: 'none' })
|
||||
// xuchao: 透出后端真实原因,如「操作频繁,请稍后再试」
|
||||
uni.showToast({ title: this.bizMsg(res) || '发送失败', icon: 'none' })
|
||||
}
|
||||
}).catch(() => {
|
||||
uni.showToast({ title: '发送失败', icon: 'none' })
|
||||
})
|
||||
this.countdown = 60
|
||||
this.timer = setInterval(() => {
|
||||
this.countdown--
|
||||
if (this.countdown <= 0) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
onLogin() {
|
||||
if (!this.phone || !/^1\d{10}$/.test(this.phone)) {
|
||||
@@ -100,9 +122,14 @@ export default {
|
||||
uni.showToast({ title: '请同意用户协议和隐私协议', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.$u.api.checkerLogin({ password: '1232321', mobile: this.phone, verifyCode: this.code }).then(res => {
|
||||
if (res.errcode === 0) {
|
||||
uni.setStorageSync('token', res.data.token)
|
||||
// xuchao: password 为后端要求的固定旁路口令,集中取自 config.loginFixedPassword
|
||||
this.$u.api.checkerLogin({ password: config.loginFixedPassword, mobile: this.phone, verifyCode: this.code }).then(res => {
|
||||
if (this.isBizOk(res)) {
|
||||
// xuchao: 只认"非空字符串"凭证。后端若把 null 序列化成 {}(实测 tokenDecode 就是这个形状),
|
||||
// 用 || 兜底会把对象存进缓存,之后每个请求都变成 Bearer [object Object]
|
||||
const payload = (res.data && res.data.data) || {}
|
||||
const issued = [payload.accessToken, payload.token].find(v => typeof v === 'string' && v.length > 0)
|
||||
uni.setStorageSync('token', issued || '')
|
||||
uni.setStorageSync('phone', this.phone)
|
||||
this.$u.api.getEligibility({ phone: this.phone, identityNo: '', name: '' }).then(eRes => {
|
||||
if (eRes.errcode === 0) {
|
||||
@@ -125,7 +152,9 @@ export default {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
uni.showToast({ title: res.errmsg || '登录失败', icon: 'none' })
|
||||
// xuchao: 透出后端真实原因,如「验证码错误」「验证码已过期,请重新获取」;
|
||||
// 失败时绝不写 phone,避免留下“假登录态”让后续页面以为已登录
|
||||
uni.showToast({ title: this.bizMsg(res) || '登录失败', icon: 'none' })
|
||||
}
|
||||
}).catch(() => {
|
||||
uni.showToast({ title: '登录失败', icon: 'none' })
|
||||
|
||||
Reference in New Issue
Block a user