Files
hhrs/pages/rights/rights.vue
T
jacobxu666 15936574ba 优化首页、场馆、折扣、权益、我的、登录页面UI及交互体验
- 首页:金刚区添加文字标签、活动指南卡片自适应宽度、字体调整
- 场馆列表:去掉导航栏、添加返回按钮、优化加载提示
- 折扣列表:样式与场馆列表统一
- 场馆详情:合并信息与地址容器、添加分割线和返回按钮
- 权益页面:banner渐变圆角、统计卡片优化、空状态引导、Tab列表优化
- 我的页面:主色调统一、渐变背景、菜单样式优化
- 登录页面:协议链接可跳转、添加返回按钮
- 移除VConsole调试按钮
- 底部导航栏线条颜色调整
2026-07-17 09:54:22 +08:00

925 lines
22 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" :class="{ 'page-auth': status === 'auth' }">
<!-- 返回按钮(顶部,非auth状态) -->
<view class="back-btn" @click="goBack" v-if="status !== 'auth'">
<u-icon name="arrow-left" color="#ffffff" size="40"></u-icon>
<text class="back-text">返回</text>
</view>
<!-- 返回按钮(底部,auth状态) -->
<view class="back-btn-bottom" @click="goBack" v-if="status === 'auth'">
<image class="back-icon" src="https://zhihuiyou.oss-cn-shenzhen.aliyuncs.com/hhrs/hhrs_back.png" mode="aspectFit"></image>
<text class="back-text-bottom">返回上一页</text>
</view>
<!-- 状态1: 权益资格认证 -->
<view v-if="status === 'auth'" class="auth-container">
<view class="section-title">
<view class="title-bar"></view>
<text class="title-text">权益资格认证</text>
</view>
<view class="form-area">
<view class="form-item">
<text class="form-label required">真实姓名</text>
<input class="form-input" v-model="form.realName" placeholder="请输入真实姓名" />
</view>
<view class="form-item">
<text class="form-label required">手机号码</text>
<text class="form-value phone-value">{{ form.phone }}</text>
</view>
<view class="form-item" @click="showIdTypePicker = true">
<text class="form-label required">证件类型</text>
<text class="form-value">{{ form.idType }}</text>
<text class="form-arrow">&#xe602;</text>
</view>
<view class="form-item">
<text class="form-label required">证件号码</text>
<input class="form-input" v-model="form.idNumber" placeholder="请输入真实证件号码" />
</view>
</view>
<view class="auth-btn" @click="onAuth">开始认证</view>
<view class="tips-section">
<view class="section-title">
<view class="title-bar"></view>
<text class="title-text">温馨提示</text>
</view>
<view class="tips-list">
<text class="tip-item">1. 请输入真实的身份信息用来进行活动权益资格认证</text>
<text class="tip-item">2. 认证通过后系统会显示出您的所获得的权益信息</text>
<text class="tip-item">3. 手机号码为您登录的手机,无法修改。若号码对,请更换登录手机。</text>
</view>
</view>
</view>
<!-- 状态2: 未中奖 -->
<view v-if="status === 'noWin'" class="empty-container">
<image class="empty-img" src="https://aliyunoss.hn12301.net/hhrs/hhrs_wzj.png" mode="widthFix" />
<text class="empty-text">您暂未中奖</text>
</view>
<!-- 状态3: 权益信息 -->
<view v-if="status === 'info'">
<!-- 蓝色头部横幅 -->
<view style="background: #fff;">
<view class="banner-area">
<text class="banner-title">恭喜您!获得怀化市社保卡权益包</text>
<view class="banner-expire">
<text class="expire-icon">!</text>
<text class="expire-text">2026年12月31日</text>
<text class="expire-label">到期</text>
</view>
</view>
<!-- 权益统计卡片 -->
<view class="stats-card">
<view class="stats-item">
<text class="stats-label">总权益价值</text>
<view class="stats-value-row">
<text class="stats-currency">¥</text>
<text class="stats-number">1500</text>
<text class="stats-unit">元</text>
</view>
</view>
<view class="stats-divider"></view>
<view class="stats-item">
<text class="stats-label">可用权益数</text>
<view class="stats-value-row">
<text class="stats-number-large">10</text>
<text class="stats-unit">个</text>
</view>
</view>
</view>
<!-- Tab 切换 -->
<view class="tab-bar">
<view v-for="(tab, index) in tabs" :key="index" class="tab-item"
:class="{ active: currentTab === index }" @click="switchTab(index)">
<text class="tab-text">{{ tab }}</text>
</view>
</view>
</view>
<!-- 搜索栏 -->
<view class="search-area" v-if="currentTab==0">
<view class="search-bar">
<u-icon name="search" color="#cccccc" size="24"></u-icon>
<input class="search-input" v-model="keyword" placeholder="搜索权益商家" @confirm="onSearch" />
</view>
</view>
<!-- 权益列表 -->
<scroll-view class="list-area" scroll-y @scrolltolower="loadMore" :lower-threshold="100"
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">
<view class="card-name">{{ item.name }}</view>
<view class="card-category">{{ item.category }}</view>
<view class="card-address">{{ item.address }}</view>
</view>
<view class="card-distance" v-if="item.distance !== null">
<u-icon name="map" color="#999999" size="24"></u-icon>
<text
class="distance-text">{{ item.distance < 1 ? Math.round(item.distance * 1000) + 'm' : Math.round(item.distance) + 'km' }}</text>
</view>
</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>
<text class="empty-state-text">暂无内容</text>
</view>
</scroll-view>
<scroll-view class="list-area" scroll-y @scrolltolower="loadAppointmentMore" :lower-threshold="100"
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] }}
<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>
<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 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;">
<text>{{ item.resourceName }}</text><text style="color: #FF383C;">已使用</text></view>
<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>
<text class="empty-state-text">暂无内容</text>
</view>
<view class="loading" v-if="checkLoading">
<text class="loading-text">加载中...</text>
</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>
</template>
<script>
export default {
data() {
return {
status: 'auth',
form: {
realName: '',
phone: '',
idType: '居民身份证',
idNumber: ''
},
showIdTypePicker: false,
tabs: ['待使用', '已预约', '已使用'],
currentTab: 0,
keyword: '',
rightsList: [],
pageNo: 1,
pageSize: 1000,
total: 0,
lng: '',
lat: '',
loading: false,
appointmentList: [],
appointmentPageNo: 1,
appointmentTotal: 0,
appointmentLoading: false,
checkList: [],
checkPageNo: 1,
checkTotal: 0,
checkLoading: false
}
},
onLoad(options) {
if (options.status) {
this.status = options.status
}
this.form.phone = uni.getStorageSync('phone') || ''
const loc = uni.getStorageSync('lng') && uni.getStorageSync('lat') ? {
lng: uni.getStorageSync('lng'),
lat: uni.getStorageSync('lat')
} :
null
if (loc) {
this.lng = loc.lng
this.lat = loc.lat
}
this.checkAuthResult()
if (this.status === 'info') {
this.loadRightsList()
}
},
methods: {
checkAuthResult() {
const url = new URL(window.location.href)
let responseStr = url.searchParams.get('response')
console.log('responseStr:', responseStr)
if (!responseStr) return
let response
try {
let parms = JSON.parse(decodeURIComponent(responseStr))
console.log('parms:', parms)
response = typeof parms === 'string' ? JSON.parse(parms) : parms
console.log('response:', response)
} catch (e) {
try {
response = JSON.parse(responseStr)
} catch (e2) {
return
}
}
const code = response.code
const certifyId = response.extInfo ? response.extInfo.certifyId : ''
console.log('code:', code, 'certifyId:', certifyId)
if (code == 1000 && certifyId) {
this.$u.api.describeFaceVerify({
certifyId: certifyId
}).then(res => {
console.log('describeFaceVerify res:', res)
if (res.errcode === 0 && res.data.body && res.data.body.resultObject) {
const verifyResult = res.data.body.resultObject
console.log('verifyResult:', verifyResult)
if (String(verifyResult.passed) === 'T' || String(verifyResult.Passed) === 'T') {
const phone = uni.getStorageSync('phone') || ''
const name = uni.getStorageSync('authName') || ''
const identityNo = uni.getStorageSync('authIdNo') || ''
this.$u.api.getEligibility({
phone: phone,
identityNo: identityNo,
name: name
}).then(eRes => {
console.log('eligibility res:', 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)
if (eRes.data.isAuth) {
this.status = eRes.data.hasWin ? 'info' : 'noWin'
if (this.status === 'info') {
this.loadRightsList()
}
}
}
})
} else {
uni.showToast({
title: '认证未通过',
icon: 'none'
})
}
} else {
uni.showToast({
title: '认证结果查询失败',
icon: 'none'
})
}
}).catch(() => {
uni.showToast({
title: '认证结果查询失败',
icon: 'none'
})
})
} else {
uni.showToast({
title: '认证未通过',
icon: 'none'
})
}
},
loadAliyunSDK() {
return new Promise((resolve, reject) => {
if (window.getMetaInfo) {
resolve()
return
}
const script = document.createElement('script')
script.src = 'https://o.alicdn.com/yd-cloudauth/cloudauth-cdn/jsvm_all.js'
script.onload = () => resolve()
script.onerror = () => reject(new Error('加载阿里云SDK失败'))
document.head.appendChild(script)
})
},
onAuth() {
if (!this.form.realName) {
uni.showToast({
title: '请输入真实姓名',
icon: 'none'
})
return
}
if (!this.form.idNumber) {
uni.showToast({
title: '请输入证件号码',
icon: 'none'
})
return
}
uni.setStorageSync('authName', this.form.realName)
uni.setStorageSync('authIdNo', this.form.idNumber)
this.loadAliyunSDK().then(() => {
const metaInfo = JSON.stringify(window.getMetaInfo())
const returnUrl = window.location.origin + window.location.pathname +
'?response=%7B%7D#/pages/rights/rights?status=auth'
this.$u.api.initFaceVerify({
certName: this.form.realName,
certNo: this.form.idNumber,
metaInfo: metaInfo,
returnUrl: returnUrl
}).then(res => {
if (res.errcode === 0 && res.data.body && res.data.body.resultObject && res.data
.body.resultObject.certifyUrl) {
window.location.href = res.data.body.resultObject.certifyUrl
} else {
uni.showToast({
title: res.errmsg || '认证初始化失败',
icon: 'none'
})
}
}).catch(() => {
uni.showToast({
title: '认证初始化失败',
icon: 'none'
})
})
}).catch(() => {
uni.showToast({
title: '加载认证组件失败',
icon: 'none'
})
})
},
loadRightsList() {
const cardNumber = uni.getStorageSync('cardNumber') || ''
this.fetchRightsList(cardNumber)
},
fetchRightsList(cardNumber) {
if (this.loading) return
this.loading = true
this.$u.api.getRightsList({
pageNo: this.pageNo,
pageSize: this.pageSize,
keyword: this.keyword,
lng: this.lng,
lat: this.lat,
cardNumber: cardNumber
}).then(res => {
if (res.errcode === 0 && res.data) {
const list = res.data.list || []
if (this.pageNo === 1) {
this.rightsList = list
} else {
this.rightsList = this.rightsList.concat(list)
}
this.total = res.data.total || 0
}
}).finally(() => {
this.loading = false
})
},
loadMore() {
if (this.rightsList.length < this.total && !this.loading) {
this.pageNo++
this.loadRightsList()
}
},
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()
}
},
onSearch() {
this.pageNo = 1
this.loadRightsList()
},
goDetail(item) {
uni.navigateTo({
url: '/pages/rights/detail?id=' + item.ruleId
})
},
goBack() {
uni.navigateBack()
}
}
}
</script>
<style>
page {
background-color: #f2f2f7;
}
</style>
<style scoped lang="less">
.page-auth {
background-color: #ffffff;
}
/* 认证表单 */
.auth-container {
min-height: 100vh;
padding: 16px;
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 16px;
}
.title-bar {
width: 4px;
height: 16px;
background-color: #0088ff;
border-radius: 2px;
margin-right: 8px;
}
.title-text {
font-size: 16px;
font-weight: 600;
color: #1a1818;
}
.form-area {
background-color: #ffffff;
border-radius: 8px;
padding: 0 16px;
}
.form-item {
display: flex;
align-items: center;
height: 64px;
width: 100%;
border-bottom: 1px solid #f0f0f0;
position: relative;
}
.form-label {
font-size: 14px;
color: #333333;
width: 80px;
flex-shrink: 0;
}
.form-label.required::before {
content: '*';
color: #ff4d4f;
margin-right: 4px;
}
.form-input {
flex: 1;
font-size: 14px;
color: #333333;
}
.form-value {
flex: 1;
font-size: 14px;
color: #333333;
}
.phone-value {
color: #999999;
}
.form-arrow {
font-size: 14px;
color: #cccccc;
}
.auth-btn {
margin: 24px 0;
background-color: #0088ff;
color: #ffffff;
text-align: center;
font-size: 16px;
font-weight: 500;
height: 48px;
line-height: 48px;
border-radius: 24px;
}
.tips-section {
margin-top: 8px;
}
.tips-list {
display: flex;
flex-direction: column;
gap: 12px;
padding: 0 4px;
}
.tip-item {
font-size: 13px;
color: #ff4d4f;
line-height: 1.6;
}
/* 未中奖空状态 */
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 160px;
}
.empty-img {
width: 200px;
}
.empty-text {
font-size: 18px;
color: #999999;
margin-top: 24px;
}
/* 权益信息 - 蓝色横幅 */
.banner-area {
background-color: #0088ff;
padding: 80px 16px 96px;
border-radius: 0 0 50px 50px;
}
.banner-title {
font-size: 24px;
color: #ffffff;
font-weight: 500;
display: block;
text-align: center;
margin-bottom: 12px;
}
.banner-expire {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.expire-icon {
width: 18px;
height: 18px;
background-color: #ffffff;
color: #0088ff;
border-radius: 50%;
text-align: center;
line-height: 18px;
font-size: 12px;
font-weight: 700;
}
.expire-text {
font-size: 24px;
color: #ffffff;
font-weight: 600;
}
.expire-label {
font-size: 24px;
color: #ffffff;
}
/* 统计卡片 */
.stats-card {
display: flex;
align-items: center;
background: #FFFFFF;
margin: -60px 12px 0;
border-radius: 10px;
padding: 16px 24px;
box-shadow: 0px 4px 4px 0px #00000019;
position: relative;
z-index: 1;
}
.stats-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.stats-label {
font-size: 12px;
color: #999999;
margin-bottom: 8px;
}
.stats-value-row {
display: flex;
align-items: baseline;
}
.stats-currency {
font-size: 14px;
color: #1a1818;
font-weight: 600;
}
.stats-number {
font-size: 28px;
color: #1a1818;
font-weight: 700;
}
.stats-number-large {
font-size: 28px;
color: #1a1818;
font-weight: 700;
}
.stats-unit {
font-size: 12px;
color: #999999;
margin-left: 2px;
}
.stats-divider {
width: 1px;
height: 36px;
background-color: #eeeeee;
}
/* Tab 切换 */
.tab-bar {
display: flex;
background-color: #ffffff;
margin-top: 12px;
padding: 0 16px;
}
.tab-item {
flex: 1;
text-align: center;
padding: 12px 0;
position: relative;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 24px;
height: 3px;
background-color: #0088ff;
border-radius: 2px;
}
.tab-text {
font-size: 14px;
color: #999999;
}
.tab-item.active .tab-text {
color: #0088ff;
font-weight: 600;
}
/* 搜索栏 */
.search-area {
padding: 12px;
}
.search-bar {
display: flex;
align-items: center;
height: 36px;
background-color: #ffffff;
border-radius: 18px;
padding: 0 12px;
gap: 8px;
}
.search-input {
flex: 1;
font-size: 14px;
color: #333333;
}
/* 权益列表 */
.list-area {
width: 750rpx;
display: flex;
flex-direction: column;
gap: 10px;
}
.rights-card {
width: 702rpx;
background-color: #fff;
margin-left: 24rpx;
padding: 24rpx;
display: flex;
position: relative;
margin-bottom: 24rpx;
}
.card-img {
width: 150rpx;
height: 150rpx;
border-radius: 6px;
flex-shrink: 0;
}
.card-info {
width: 490rpx;
margin-left: 14rpx;
display: flex;
flex-direction: column;
height: 150rpx;
justify-content: space-between;
}
.card-name {
font-size: 15px;
font-weight: 500;
color: #1a1818;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-category {
font-size: 13px;
color: #999999;
margin-top: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-address {
width: 300rpx;
font-size: 13px;
color: #999999;
margin-top: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-distance {
display: flex;
align-items: center;
gap: 2px;
flex-shrink: 0;
position: absolute;
bottom: 24rpx;
right: 24rpx;
}
.distance-text {
font-size: 12px;
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;
align-items: center;
justify-content: center;
padding-top: 80px;
}
.empty-state-img {
width: 200px;
}
.empty-state-text {
font-size: 16px;
color: #999999;
margin-top: 16px;
}
/* 返回按钮 */
.back-btn {
position: fixed;
top: 24px;
left: 24px;
display: flex;
align-items: center;
gap: 2px;
z-index: 10;
}
.back-text {
font-size: 14px;
color: #ffffff;
}
/* 底部返回按钮 */
.back-btn-bottom {
position: fixed;
left: 24px;
bottom: 50px;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
z-index: 10;
}
.back-icon {
width: 44px;
height: 44px;
}
.back-text-bottom {
font-size: 12px;
color: #666666;
}
</style>