fix: 修复登录失败被当成功、列表假分页与首页裁切等隐患
1. 登录/验证码改判业务码 data.code(外层 errcode 恒为0),失败不再写入 phone 造成假登录态,并透出后端真实原因;倒计时改为发送成功后才启动;token 按 accessToken 兜底读取,不再写入 undefined 2. 权益包三 Tab 由 pageSize=1000 的假分页改为真实分页 + 页面级触底加载,各 Tab 独立游标与请求序号,合并去重兜底;中奖名单页码改为按期维护,修复切期后重复追加 3. 首页去掉整页 overflow:hidden,小屏不再裁切活动指南卡片;接口表去重并清理 7 个废弃端点;基址/客服电话集中到 common/config.js;删除权益详情不可达的假成功分支
This commit is contained in:
+132
-141
@@ -107,8 +107,8 @@
|
||||
</view>
|
||||
|
||||
<!-- 权益列表 -->
|
||||
<scroll-view class="list-area" scroll-y @scrolltolower="loadMore" :lower-threshold="100"
|
||||
v-if="currentTab==0">
|
||||
<!-- xuchao: 三 Tab 列表统一改普通容器 + 页面级触底分页(scroll-view 无固定高度时 scrolltolower 永不触发) -->
|
||||
<view class="list-area" v-if="currentTab==0">
|
||||
<view class="rights-card" v-for="item in rightsList" :key="item.ruleId" @click="goDetail(item)">
|
||||
<image class="card-img" :src="item.image" mode="aspectFill" />
|
||||
<view class="card-info">
|
||||
@@ -124,28 +124,33 @@
|
||||
</view>
|
||||
|
||||
<view class="empty-state" v-if="rightsList.length === 0 && !loading">
|
||||
<image class="empty-state-img" src="https://zhihuiyou.oss-cn-shenzhen.aliyuncs.com/hhrs/hhrs_wzj.png" mode="widthFix"></image>
|
||||
<image class="empty-state-img" src="https://aliyunoss.hn12301.net/hhrs/hhrs_wzj.png" mode="widthFix"></image>
|
||||
<text class="empty-state-text">暂无内容</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-if="pager[0].hasScrolled" class="nomore-area">
|
||||
<u-loadmore :status="loadStatuses[0]" :load-text="{ loadmore: '', loading: '加载中...', nomore: '我也是有底线的' }" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="list-area" scroll-y @scrolltolower="loadAppointmentMore" :lower-threshold="100"
|
||||
v-if="currentTab==1">
|
||||
<view class="list-area" v-if="currentTab==1">
|
||||
<view class="rights-card" style="margin-top: 24rpx;flex-direction: column;" v-for="item in appointmentList"
|
||||
:key="item.id" >
|
||||
<view style="width: 654rpx;display: flex;justify-content: space-between;">
|
||||
<text>{{ item.resourceName }}</text><text style="color: #34C759;">已预约</text></view>
|
||||
<view style="font-size: 24rpx;color: #727272;margin-top: 24rpx;">预约游玩日期:{{ item.appTime.split(' ')[0] }}
|
||||
<!-- xuchao: appTime 为空时不再抛错拖垮整页渲染 -->
|
||||
<view style="font-size: 24rpx;color: #727272;margin-top: 24rpx;">预约游玩日期:{{ (item.appTime || '').split(' ')[0] }}
|
||||
<text v-if="item.timeName">({{ item.timeName }})</text></view>
|
||||
</view>
|
||||
|
||||
<view class="empty-state" v-if="appointmentList.length === 0 && !appointmentLoading">
|
||||
<image class="empty-state-img" src="https://zhihuiyou.oss-cn-shenzhen.aliyuncs.com/hhrs/hhrs_wzj.png" mode="widthFix"></image>
|
||||
<image class="empty-state-img" src="https://aliyunoss.hn12301.net/hhrs/hhrs_wzj.png" mode="widthFix"></image>
|
||||
<text class="empty-state-text">暂无内容</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<scroll-view class="list-area" scroll-y @scrolltolower="loadCheckMore" :lower-threshold="100"
|
||||
v-if="currentTab==2">
|
||||
<view v-if="pager[1].hasScrolled" class="nomore-area">
|
||||
<u-loadmore :status="loadStatuses[1]" :load-text="{ loadmore: '', loading: '加载中...', nomore: '我也是有底线的' }" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-area" v-if="currentTab==2">
|
||||
<view class="rights-card" style="margin-top: 24rpx;flex-direction: column;" v-for="item in checkList"
|
||||
:key="item.id" >
|
||||
<view style="width: 654rpx;display: flex;justify-content: space-between;">
|
||||
@@ -153,16 +158,14 @@
|
||||
<view style="font-size: 24rpx;color: #727272;margin-top: 24rpx;">核销时间:{{ item.checkTime }}</view>
|
||||
</view>
|
||||
<view class="empty-state" v-if="checkList.length === 0 && !checkLoading">
|
||||
<image class="empty-state-img" src="https://zhihuiyou.oss-cn-shenzhen.aliyuncs.com/hhrs/hhrs_wzj.png" mode="widthFix"></image>
|
||||
<image class="empty-state-img" src="https://aliyunoss.hn12301.net/hhrs/hhrs_wzj.png" mode="widthFix"></image>
|
||||
<text class="empty-state-text">暂无内容</text>
|
||||
</view>
|
||||
<view class="loading" v-if="checkLoading">
|
||||
<text class="loading-text">加载中...</text>
|
||||
<!-- xuchao: 原先自写的“加载中/没有更多”与另两个 Tab 不一致,统一换成 u-loadmore -->
|
||||
<view v-if="pager[2].hasScrolled" class="nomore-area">
|
||||
<u-loadmore :status="loadStatuses[2]" :load-text="{ loadmore: '', loading: '加载中...', nomore: '我也是有底线的' }" />
|
||||
</view>
|
||||
<view class="no-more" v-else-if="checkList.length >= checkTotal && checkList.length > 0">
|
||||
<text class="no-more-text">没有更多了</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -187,20 +190,18 @@
|
||||
currentTab: 0,
|
||||
keyword: '',
|
||||
rightsList: [],
|
||||
pageNo: 1,
|
||||
pageSize: 1000,
|
||||
total: 0,
|
||||
appointmentList: [],
|
||||
checkList: [],
|
||||
// xuchao: 三个 Tab 各自独立的分页游标/加载态。原 pageSize=1000 是“一次拉全量 +
|
||||
// scroll-view 永不触发的假分页”,现改真实分页(与场馆/折扣页口径一致)
|
||||
pageSize: 10,
|
||||
pager: [
|
||||
{ pageNo: 1, total: 0, loading: false, reqSeq: 0, hasScrolled: false },
|
||||
{ pageNo: 1, total: 0, loading: false, reqSeq: 0, hasScrolled: false },
|
||||
{ pageNo: 1, total: 0, loading: false, reqSeq: 0, hasScrolled: false }
|
||||
],
|
||||
lng: '',
|
||||
lat: '',
|
||||
loading: false,
|
||||
appointmentList: [],
|
||||
appointmentPageNo: 1,
|
||||
appointmentTotal: 0,
|
||||
appointmentLoading: false,
|
||||
checkList: [],
|
||||
checkPageNo: 1,
|
||||
checkTotal: 0,
|
||||
checkLoading: false
|
||||
lat: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -208,6 +209,24 @@
|
||||
totalValueText() {
|
||||
const yuan = this.totalValueFen / 100
|
||||
return Number.isInteger(yuan) ? String(yuan) : yuan.toFixed(2)
|
||||
},
|
||||
// xuchao: 模板沿用原有 loading / appointmentLoading / checkLoading 字段名
|
||||
loading() {
|
||||
return this.pager[0].loading
|
||||
},
|
||||
appointmentLoading() {
|
||||
return this.pager[1].loading
|
||||
},
|
||||
checkLoading() {
|
||||
return this.pager[2].loading
|
||||
},
|
||||
// xuchao: 各 Tab 底部“加载中/没有更多”独立计算,避免并行请求互相覆盖
|
||||
loadStatuses() {
|
||||
return this.pager.map((p, i) => {
|
||||
if (p.loading) return 'loading'
|
||||
const len = this.listOf(i).length
|
||||
return (len === 0 || len >= p.total) ? 'nomore' : 'loadmore'
|
||||
})
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
@@ -231,6 +250,17 @@
|
||||
this.loadRightsList()
|
||||
}
|
||||
},
|
||||
// xuchao: 页面级触底加载 —— 只作用于当前 Tab
|
||||
onReachBottom() {
|
||||
if (this.status !== 'info') return
|
||||
const tab = this.currentTab
|
||||
const p = this.pager[tab]
|
||||
p.hasScrolled = true
|
||||
if (!p.loading && this.listOf(tab).length < p.total) {
|
||||
p.pageNo++
|
||||
this.loadList(tab)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// xuchao: 缓存的权益状态可能过期(中奖/权益包会变化),每次进页面仅凭手机号向服务端
|
||||
// 复查最新状态并覆盖缓存(不带证件号,避免触发被动认证)
|
||||
@@ -427,110 +457,86 @@
|
||||
})
|
||||
})
|
||||
},
|
||||
loadRightsList() {
|
||||
const cardNumber = uni.getStorageSync('cardNumber') || ''
|
||||
this.fetchRightsList(cardNumber)
|
||||
// xuchao: Tab 索引 <-> 列表字段的唯一映射
|
||||
listOf(tab) {
|
||||
return tab === 1 ? this.appointmentList : tab === 2 ? this.checkList : this.rightsList
|
||||
},
|
||||
fetchRightsList(cardNumber) {
|
||||
if (this.loading) return
|
||||
this.loading = true
|
||||
this.$u.api.getRightsList({
|
||||
pageNo: this.pageNo,
|
||||
setList(tab, list) {
|
||||
if (tab === 1) {
|
||||
this.appointmentList = list
|
||||
} else if (tab === 2) {
|
||||
this.checkList = list
|
||||
} else {
|
||||
this.rightsList = list
|
||||
}
|
||||
},
|
||||
// xuchao: 三个 Tab 的统一分页加载器(reset=true 回到第一页重拉)
|
||||
loadList(tab, reset) {
|
||||
const p = this.pager[tab]
|
||||
if (reset) {
|
||||
p.pageNo = 1
|
||||
p.total = 0
|
||||
this.setList(tab, [])
|
||||
}
|
||||
// xuchao: 请求序号 —— 新请求取代在途请求,过期响应整体丢弃(治搜索/切 Tab 竞态)
|
||||
const seq = ++p.reqSeq
|
||||
p.loading = true
|
||||
const params = {
|
||||
pageNo: p.pageNo,
|
||||
pageSize: this.pageSize,
|
||||
keyword: this.keyword,
|
||||
lng: this.lng,
|
||||
lat: this.lat,
|
||||
cardNumber: cardNumber
|
||||
}).then(res => {
|
||||
cardNumber: uni.getStorageSync('cardNumber') || ''
|
||||
}
|
||||
let request
|
||||
if (tab === 1) {
|
||||
request = this.$u.api.getAppointmentList(params)
|
||||
} else if (tab === 2) {
|
||||
request = this.$u.api.getCheckList(params)
|
||||
} else {
|
||||
params.keyword = this.keyword
|
||||
params.lng = this.lng
|
||||
params.lat = this.lat
|
||||
request = this.$u.api.getRightsList(params)
|
||||
}
|
||||
request.then(res => {
|
||||
if (seq !== p.reqSeq) return
|
||||
if (res.errcode === 0 && res.data) {
|
||||
const list = res.data.list || []
|
||||
if (this.pageNo === 1) {
|
||||
this.rightsList = list
|
||||
const prev = this.listOf(tab)
|
||||
// xuchao: 按 id/ruleId 去重合并 —— 兜住后端忽略 pageNo 重复返回同一页的情况
|
||||
const seen = {}
|
||||
const merged = (p.pageNo === 1 ? list : prev.concat(list)).filter(item => {
|
||||
const k = (item.id === undefined || item.id === null) ? item.ruleId : item.id
|
||||
if (k === undefined || k === null) return true
|
||||
if (seen[k]) return false
|
||||
seen[k] = true
|
||||
return true
|
||||
})
|
||||
// xuchao: 本页不足一页 或 整页都是重复 => 视为已到尾部,钉住 total,
|
||||
// 防止 total 中途变大时反复空翻/空转
|
||||
if (list.length < this.pageSize || (p.pageNo !== 1 && merged.length === prev.length)) {
|
||||
p.total = merged.length
|
||||
} else {
|
||||
this.rightsList = this.rightsList.concat(list)
|
||||
p.total = res.data.total || 0
|
||||
}
|
||||
this.total = res.data.total || 0
|
||||
this.setList(tab, merged)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
if (seq !== p.reqSeq) return
|
||||
p.loading = false
|
||||
})
|
||||
},
|
||||
loadMore() {
|
||||
if (this.rightsList.length < this.total && !this.loading) {
|
||||
this.pageNo++
|
||||
this.loadRightsList()
|
||||
}
|
||||
loadRightsList() {
|
||||
this.loadList(0, true)
|
||||
},
|
||||
switchTab(index) {
|
||||
this.currentTab = index
|
||||
if (index === 1 && this.appointmentList.length === 0) {
|
||||
this.appointmentPageNo = 1
|
||||
this.loadAppointmentList()
|
||||
}
|
||||
if (index === 2 && this.checkList.length === 0) {
|
||||
this.checkPageNo = 1
|
||||
this.loadCheckList()
|
||||
}
|
||||
},
|
||||
loadAppointmentList() {
|
||||
const cardNumber = uni.getStorageSync('cardNumber') || ''
|
||||
if (this.appointmentLoading) return
|
||||
this.appointmentLoading = true
|
||||
this.$u.api.getAppointmentList({
|
||||
pageNo: this.appointmentPageNo,
|
||||
pageSize: this.pageSize,
|
||||
cardNumber: cardNumber
|
||||
}).then(res => {
|
||||
if (res.errcode === 0 && res.data) {
|
||||
const list = res.data.list || []
|
||||
if (this.appointmentPageNo === 1) {
|
||||
this.appointmentList = list
|
||||
} else {
|
||||
this.appointmentList = this.appointmentList.concat(list)
|
||||
}
|
||||
this.appointmentTotal = res.data.total || 0
|
||||
}
|
||||
}).finally(() => {
|
||||
this.appointmentLoading = false
|
||||
})
|
||||
},
|
||||
loadAppointmentMore() {
|
||||
if (this.appointmentList.length < this.appointmentTotal && !this.appointmentLoading) {
|
||||
this.appointmentPageNo++
|
||||
this.loadAppointmentList()
|
||||
}
|
||||
},
|
||||
loadCheckList() {
|
||||
const cardNumber = uni.getStorageSync('cardNumber') || ''
|
||||
if (this.checkLoading) return
|
||||
this.checkLoading = true
|
||||
this.$u.api.getCheckList({
|
||||
pageNo: this.checkPageNo,
|
||||
pageSize: this.pageSize,
|
||||
cardNumber: cardNumber
|
||||
}).then(res => {
|
||||
if (res.errcode === 0 && res.data) {
|
||||
const list = res.data.list || []
|
||||
if (this.checkPageNo === 1) {
|
||||
this.checkList = list
|
||||
} else {
|
||||
this.checkList = this.checkList.concat(list)
|
||||
}
|
||||
this.checkTotal = res.data.total || 0
|
||||
}
|
||||
}).finally(() => {
|
||||
this.checkLoading = false
|
||||
})
|
||||
},
|
||||
loadCheckMore() {
|
||||
if (this.checkList.length < this.checkTotal && !this.checkLoading) {
|
||||
this.checkPageNo++
|
||||
this.loadCheckList()
|
||||
// xuchao: 首次进入该 Tab 才拉首页;已有数据保留各自游标继续分页
|
||||
if (this.listOf(index).length === 0 && !this.pager[index].loading) {
|
||||
this.loadList(index, true)
|
||||
}
|
||||
},
|
||||
onSearch() {
|
||||
this.pageNo = 1
|
||||
this.loadRightsList()
|
||||
this.loadList(0, true)
|
||||
},
|
||||
goDetail(item) {
|
||||
uni.navigateTo({
|
||||
@@ -847,12 +853,17 @@
|
||||
}
|
||||
|
||||
/* 权益列表 */
|
||||
/* xuchao: 原 scroll-view 下 gap 不生效(卡片被包在 scroll-view-content 内),
|
||||
改普通容器后去掉 gap,卡片间距仍由 .rights-card 的 margin-bottom 提供,视觉不变 */
|
||||
.list-area {
|
||||
|
||||
width: 750rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.nomore-area {
|
||||
width: 100%;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.rights-card {
|
||||
@@ -924,26 +935,6 @@
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.no-more-text {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user