Compare commits

..
10 Commits
Author SHA1 Message Date
jacobxu666andClaude Fable 5 cdc09b9f8b fix: 修正 u-loadmore 文案写法为 load-text 对象,"我也是有底线的"恢复生效
本项目 uview 的 u-loadmore 只认 load-text 对象 prop,原先分开传的
loading-text/loadmore-text/nomore-text 被组件静默忽略,footer 一直显示内置默认文案。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-03 10:28:46 +08:00
jacobxu666andClaude Fable 5 1f1b86feb3 fix: 折扣/场馆列表改用页面级触底加载,修复加载更多永不触发
- scroll-view 无固定高度导致 scrolltolower 永不触发,列表卡在首页 10 条;改为普通 view + onReachBottom 页面级触底
- 新增请求序号取代机制:翻页在途时搜索不再被吞、过期响应整体丢弃
- 本页不足一页即钉住 total,防止翻页途中数据变动导致无限空翻

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-03 09:56:27 +08:00
jacobxu666 4ac98a30fb fix: 折扣页无定位缓存时主动获取定位,保证商铺距离展示 2026-09-02 22:43:35 +08:00
jacobxu666 742973b31b fix: 折扣页列表容器与卡片改 border-box,消除页面横向溢出 2026-09-02 22:37:48 +08:00
jacobxu666 a5caf4e9a1 fix: 折扣页地址省略生效不再撑出容器(flex 子项补 min-width:0) 2026-09-02 22:35:52 +08:00
jacobxu666 567dd99d62 docs: 用户协议与隐私协议补充客服电话0731-88912301 2026-09-02 21:20:04 +08:00
jacobxu666 f05681001e docs: 用户协议与隐私协议生效日期定为2026年9月1日 2026-09-02 21:15:09 +08:00
jacobxu666 2eee458802 feat: 补全登录页用户协议与隐私协议内容(基于项目实际功能与数据采集) 2026-09-02 20:56:56 +08:00
jacobxu666 d0ca2d961b style: 商户折扣页名称单行撑满省略,地址与距离合并为同一模块 2026-09-02 20:56:56 +08:00
jacobxu666andClaude Fable 5 80838143fb feat: 权益详情页到期时间/总权益价值/可用权益数改为接口数据展示
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 17:18:42 +08:00
5 changed files with 326 additions and 48 deletions
+114 -6
View File
@@ -1,14 +1,100 @@
<template> <template>
<view class="page"> <view class="page">
<view class="content"> <text class="heading">怀化社保卡惠民活动隐私协议</text>
<text class="heading">隐私协议</text> <text class="meta">生效日期:2026年9月1日</text>
<text class="body">请在此处填写隐私协议的具体内容。</text> <view class="section" v-for="(s, i) in sections" :key="i">
<text class="section-title">{{ s.h }}</text>
<text class="para" v-for="(p, j) in s.p" :key="j">{{ p }}</text>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default {} export default {
data() {
return {
sections: [
{
h: '一、引言',
p: [
'怀化社保卡惠民活动运营方(以下简称"我们")深知个人信息对您的重要性,并会严格按照《中华人民共和国个人信息保护法》《中华人民共和国网络安全法》等法律法规的要求,采取相应的安全保护措施,保护您的个人信息安全。',
'本隐私协议旨在向您说明本平台(网址:hh.hn12301.net,以下简称"本平台")如何收集、使用、存储、共享和保护您的个人信息,以及您如何管理自己的信息。请您在使用本平台服务前,仔细阅读并充分理解本隐私协议,特别是以粗体标识的涉及敏感个人信息的条款。您勾选同意并开始使用服务,即表示我们将按照本隐私协议处理您的个人信息。'
]
},
{
h: '二、我们收集的个人信息',
p: [
'2.1 为向您提供本平台各项服务,我们遵循合法、正当、必要的原则收集以下信息:',
'(1)注册与登录信息:您的手机号码及短信验证码,用于身份验证和创建平台登录状态。您通过云闪付App内活动入口进入的,我们会从该渠道的授权凭证中解析您的手机号、姓名、证件号码,用于免登录识别您的活动资格。',
'(2)实名认证信息:您申请权益包资格认证时填写的真实姓名、证件类型、证件号码。该类信息属于敏感个人信息,仅用于核验您的社保卡持卡人身份及活动参与资格。',
'(3)人脸信息:实名认证过程中,您需通过第三方身份核验服务完成活体人脸核验。人脸信息属于敏感个人信息,由核验服务商依据其服务规范采集与比对,我们仅接收核验结果(通过/未通过),不存储您的面部图像或人脸特征数据。拒绝进行人脸核验的,将无法完成实名认证并领取权益包,但不影响您浏览场馆、商户等公开信息。',
'(4)位置信息:为向您展示文旅场馆、商户、权益门店与您的距离并按就近原则排序,我们会经您的设备/浏览器授权后收集您的粗略地理位置(经纬度)。您可以在浏览器定位权限中随时关闭授权;关闭后您仍可正常使用平台,仅距离展示功能不可用。',
'(5)交易与服务记录:您预约、使用权益产生的预约记录和核销记录,用于向您展示权益状态和提供客服协助。',
'(6)设备与日志信息:为保障网络与活动安全,我们的服务器会自动记录访问日志,包括IP地址、访问日期与时间、浏览器类型等。'
]
},
{
h: '三、我们如何使用个人信息',
p: [
'3.1 我们将收集的个人信息用于以下目的:(1)验证用户身份,提供登录服务;(2)核验社保卡持卡人活动参与资格,发放、管理权益包;(3)处理权益的预约与核销;(4)计算并展示场馆、商户与您的距离;(5)依法开展中奖名单公示、活动审计与监管报送;(6)保障平台与账号安全,防范作弊、冒用等风险;(7)响应您的咨询与客服请求。',
'3.2 中奖名单公示:依据活动规则,参与活动结果需向公众公示。公示内容中的姓名、证件号码、手机号码均经脱敏处理(如隐藏部分字符)后展示,我们不会以公示方式披露您的完整个人身份信息。'
]
},
{
h: '四、个人信息的共享、转让与公开披露',
p: [
'4.1 共享:除以下情形外,我们不会与任何公司、组织和个人共享您的个人信息:(1)获取您的单独同意后共享;(2)与完成服务所必需的第三方服务商共享:短信发送服务商(共享手机号用于发送验证码)、第三方人脸/身份核验服务商(共享姓名、证件号码、人脸影像用于核验证照真伪)、云闪付服务提供方(依据您的授权回传必要信息用于资格识别)、为权益核销提供支持的场馆及商户(在您使用对应权益时共享核销所必需的权益凭证信息);(3)根据法律法规、行政或司法机关的要求提供;(4)向人力资源社会保障等相关主管部门报送活动资格核验及监管所需的数据。',
'4.2 转让:我们不会将您的个人信息转让给任何公司、组织和个人,但涉及合并、收购或破产清算时,如涉及个人信息转让,我们会要求新的持有方继续受本隐私协议约束,否则将重新征得您的授权同意。',
'4.3 公开披露:我们仅会对中奖名单按第3.2条所述进行脱敏公示,或在获得您明确同意或基于法律强制性要求时公开披露您的个人信息。'
]
},
{
h: '五、个人信息的存储',
p: [
'5.1 存储地点:我们在中华人民共和国境内运营中收集和产生的个人信息,存储于境内服务器,不会向境外传输。',
'5.2 存储期限:我们仅在实现本隐私协议所述目的所必需的最短期限内保留您的个人信息,法律法规另有规定的除外。活动结束后,超出留存期限的个人信息我们将予以删除或匿名化处理。登录状态凭证保存在您的设备本地,清除浏览器存储即可失效。',
'5.3 如我们停止运营本平台,我们将及时停止收集您个人信息的活动,并将该终止运营的情况在本平台进行公告。'
]
},
{
h: '六、我们如何保护个人信息',
p: [
'6.1 我们采用传输层加密(HTTPS)、访问权限控制、数据脱敏展示等业界通行的安全技术措施和管理制度,防止您的个人信息遭到未经授权的访问、公开披露、使用、修改、损坏或丢失。',
'6.2 我们仅允许有必要知晓这些信息的员工、合作伙伴在权限范围内访问个人信息,并要求其履行相应的保密义务。',
'6.3 若不幸发生个人信息安全事件,我们将按照法律法规的要求,及时向您告知事件基本情况、可能的影响、我们已采取的处置措施等,并向有关主管部门报告。'
]
},
{
h: '七、您的权利',
p: [
'7.1 查阅与更正:您可以在本平台"我的"等页面查阅您的登录手机号、权益状态、预约与核销记录。如发现信息有误,或需要更新您的个人信息,可通过本隐私协议第十条所载方式联系我们更正。',
'7.2 删除:有下列情形的,您可以通过联系我们请求删除您的个人信息:(1)我们违反法律、行政法规规定收集、使用个人信息的;(2)我们未征得您的同意收集、使用个人信息的;(3)您不再使用本平台服务,或我们终止了服务或活动的;(4)我们违反约定处理您的个人信息的。法律法规规定的存储期限届满前或存在法定留存事由的,我们可能无法立即响应您的删除请求,届时将向您说明原因。',
'7.3 撤回同意:您可以通过设备/浏览器设置关闭地理位置授权、清除本地存储的登录凭证等方式撤回相应授权。撤回同意不影响撤回前基于同意已进行的信息处理活动。',
'7.4 注销账号:您可以通过联系客服申请注销您的账号。注销后,我们将删除或匿名化处理您的个人信息,法律法规要求留存的除外。'
]
},
{
h: '八、未成年人保护',
p: [
'8.1 本平台面向具备完全民事行为能力的成年人提供服务。若您为未满18周岁的未成年人,请在监护人的陪同和同意下使用本平台并接受相关权益服务。我们重视对未成年人个人信息的保护,如监护人发现我们在未获取监护人同意的情况下收集了未成年人的个人信息,可通过客服联系我们,我们将尽快删除相关数据。'
]
},
{
h: '九、本隐私协议的更新',
p: [
'9.1 我们的服务及业务情况可能不断变化。如该等变化导致本隐私协议所述处理目的、处理方式、个人信息种类或保存期限发生变更的,我们将以页面公告等显著方式通知您,并在必要时重新征得您的同意。'
]
},
{
h: '十、如何联系我们',
p: [
'如您对本隐私协议或个人信息处理有任何疑问、意见或投诉,可通过本平台"我的—联系客服"入口,或拨打客服电话 0731-88912301 与我们联系,我们将在收到您的反馈后的15个工作日内予以答复。'
]
}
]
}
}
}
</script> </script>
<style> <style>
@@ -24,12 +110,34 @@ export default {}
font-weight: 600; font-weight: 600;
color: #333333; color: #333333;
display: block; display: block;
margin-bottom: 16px; margin-bottom: 8px;
} }
.body { .meta {
font-size: 12px;
color: #999999;
display: block;
margin-bottom: 20px;
}
.section {
display: block;
margin-bottom: 18px;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #333333;
display: block;
margin-bottom: 8px;
}
.para {
font-size: 14px; font-size: 14px;
color: #666666; color: #666666;
line-height: 1.8; line-height: 1.8;
display: block;
margin-bottom: 6px;
} }
</style> </style>
+110 -6
View File
@@ -1,14 +1,96 @@
<template> <template>
<view class="page"> <view class="page">
<view class="content"> <text class="heading">怀化社保卡惠民活动用户协议</text>
<text class="heading">用户协议</text> <text class="meta">生效日期:2026年9月1日</text>
<text class="body">请在此处填写用户协议的具体内容。</text> <view class="section" v-for="(s, i) in sections" :key="i">
<text class="section-title">{{ s.h }}</text>
<text class="para" v-for="(p, j) in s.p" :key="j">{{ p }}</text>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default {} export default {
data() {
return {
sections: [
{
h: '一、协议的范围与接受',
p: [
'1.1 本协议是您与怀化社保卡惠民活动运营方(以下简称"我们")之间,就您使用"怀化社保卡惠民活动"H5平台(网址:hh.hn12301.net,以下简称"本平台")及相关服务所订立的协议。',
'1.2 您点击"我已阅读并同意《用户协议》和《隐私协议》"、完成登录或以任何方式使用本平台服务的,即表示您已充分阅读、理解并接受本协议全部内容。如您不同意,请立即停止使用。',
'1.3 本协议内容同时包括本平台已经发布的或将来可能发布并采取合理途径公示的各类活动规则、须知、公告,一经生效即构成本协议的组成部分。'
]
},
{
h: '二、服务说明',
p: [
'2.1 本平台是怀化社保卡惠民活动的线上服务入口,主要提供以下服务:(1)文旅场馆信息与优惠内容查询;(2)合作商户及折扣信息查询;(3)社保卡权益包的资格认证、权益领取、预约与核销记录查询;(4)活动中奖名单公示查询;(5)活动参加指南与社保卡使用指引等信息展示。',
'2.2 您可以通过手机浏览器或云闪付App内的活动入口访问本平台。云闪付入口下,您的登录手机号等信息以该渠道传递的授权凭证为准。',
'2.3 本平台展示的文娱场馆、商户的折扣内容、服务价格、地址等信息,由对应场馆、商户提供并对真实性负责,平台仅作信息展示。'
]
},
{
h: '三、账号与登录',
p: [
'3.1 本平台使用真实手机号并通过短信验证码方式登录。您应使用本人实名登记的手机号,并对账号项下的一切操作负责。',
'3.2 您的登录凭证(token)在设备本地保存以维持登录状态。请妥善保管您的手机与登录状态,勿将手机出借他人或向他人泄露验证码;因您保管不善造成的损失由您自行承担。',
'3.3 若您发现账号被冒用或验证码被他人获取,请立即通过平台客服渠道反馈。'
]
},
{
h: '四、权益包资格认证',
p: [
'4.1 权益包服务面向符合活动条件的怀化市社保卡持卡人员。申请认证时,您须填写真实姓名、证件类型及证件号码,并按流程完成人脸核验。',
'4.2 您承诺所提交的身份信息真实、准确、完整且为您本人所有。以虚假、冒用他人信息等方式通过认证的,我们有权取消认证结果、收回已发放权益,并依法追究相应责任。',
'4.3 认证结果、权益领取资格、权益状态以平台系统核定为准。权益包存在有效期限,到期未使用的权益自动失效,不予折现或补发,活动规则另有说明的除外。',
'4.4 权益的预约、核销须按对应商户的使用规则在有效期内操作;到店消费时请按商户要求出示相应凭证并配合核销。'
]
},
{
h: '五、用户行为规范',
p: [
'5.1 您不得实施下列行为:(1)伪造、冒用他人身份信息参与活动或认证;(2)通过机器脚本、群控设备等技术手段批量注册、恶意抢占预约名额或套取、倒卖权益;(3)干扰、破坏本平台正常运营秩序,或对平台系统进行攻击、破解;(4)利用平台发布、传播违法违规信息;(5)其他违反法律法规或活动规则的行为。',
'5.2 若您存在上述行为,我们有权视情节采取限制或取消活动资格、收回权益、中止或终止服务、封禁账号等措施,并保留追究法律责任的权利。'
]
},
{
h: '六、活动的变更、中止与终止',
p: [
'6.1 因活动安排、政策调整、不可抗力、系统故障、网络或安全问题等原因,本平台可能调整、中止或终止部分或全部服务,我们将尽可能提前公告或以适当方式通知您。',
'6.2 本平台提供的内容与服务"按现状"提供。因下列原因导致服务中断、数据丢失或您未能正常享受权益的,我们不承担赔偿责任,但将尽力减少由此给您造成的损失:(1)不可抗力;(2)基础运营商或第三方服务(短信通道、云资源、身份核验渠道等)的故障;(3)您自身网络、设备或操作原因。',
'6.3 您在使用本平台过程中所联系的场馆、商户等第三方,其与本平台之间为独立民事主体关系。您在第三方处接受服务发生的消费纠纷,由您与该第三方依据法律法规及消费约定解决,平台将视情况提供必要的信息协助。'
]
},
{
h: '七、知识产权',
p: [
'7.1 本平台的页面设计、文字、图片、标识、软件等内容的知识产权归我们或相关权利人所有,未经许可,您不得复制、修改、翻译、镜像、出租、出售或以其他商业方式使用。'
]
},
{
h: '八、协议的变更',
p: [
'8.1 我们可能在活动时间、政策、服务功能发生变化时修订本协议,修订后的协议将在本页面公布并标注新的生效日期。自公布之日起您继续使用本平台服务的,视为接受修订后的协议;不同意的,应停止使用并可申请注销账号。'
]
},
{
h: '九、法律适用与争议解决',
p: [
'9.1 本协议的订立、执行与解释均适用中华人民共和国法律。',
'9.2 因本协议或服务产生的争议,双方应友好协商解决;协商不成的,任何一方可向我们所在地有管辖权的人民法院提起诉讼。'
]
},
{
h: '十、联系我们',
p: [
'如您对本协议或活动服务有任何疑问、意见或建议,可通过本平台"我的—联系客服"入口,或拨打客服电话 0731-88912301 与我们联系。'
]
}
]
}
}
}
</script> </script>
<style> <style>
@@ -24,12 +106,34 @@ export default {}
font-weight: 600; font-weight: 600;
color: #333333; color: #333333;
display: block; display: block;
margin-bottom: 16px; margin-bottom: 8px;
} }
.body { .meta {
font-size: 12px;
color: #999999;
display: block;
margin-bottom: 20px;
}
.section {
display: block;
margin-bottom: 18px;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #333333;
display: block;
margin-bottom: 8px;
}
.para {
font-size: 14px; font-size: 14px;
color: #666666; color: #666666;
line-height: 1.8; line-height: 1.8;
display: block;
margin-bottom: 6px;
} }
</style> </style>
+53 -16
View File
@@ -16,23 +16,26 @@
</view> </view>
<!-- 商户列表 --> <!-- 商户列表 -->
<scroll-view class="list-area" scroll-y @scrolltolower="loadMore" :lower-threshold="100"> <view class="list-area">
<view class="venue-card" v-for="item in list" :key="item.shopId" @click="goDetail(item)"> <view class="venue-card" v-for="item in list" :key="item.shopId" @click="goDetail(item)">
<image class="card-img" :src="item.image" mode="aspectFill" /> <image class="card-img" :src="item.image" mode="aspectFill" />
<view class="card-info"> <view class="card-info">
<text class="card-name">{{ item.name }}</text> <text class="card-name">{{ item.name }}</text>
<text class="card-category">{{ item.category }}</text> <text class="card-category">{{ item.category }}</text>
<!-- xuchao: 地址与距离同属一个模块 -->
<view class="card-address-row">
<text class="card-address">{{ item.address }}</text> <text class="card-address">{{ item.address }}</text>
</view>
<view class="card-distance" v-if="item.distance !== null"> <view class="card-distance" v-if="item.distance !== null">
<u-icon name="map" color="#999999" size="24"></u-icon> <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> <text class="distance-text">{{ item.distance < 1 ? Math.round(item.distance * 1000) + 'm' : Math.round(item.distance) + 'km' }}</text>
</view> </view>
</view> </view>
<view v-if="hasScrolled" class="nomore-area">
<u-loadmore :status="loadStatus" :loading-text="'加载中...'" :loadmore-text="''" :nomore-text="'我也是有底线的'" />
</view> </view>
</scroll-view> </view>
<view v-if="hasScrolled" class="nomore-area">
<u-loadmore :status="loadStatus" :load-text="{ loadmore: '', loading: '加载中...', nomore: '我也是有底线的' }" />
</view>
</view>
</view> </view>
</template> </template>
@@ -49,22 +52,35 @@ export default {
lng: '', lng: '',
lat: '', lat: '',
loadStatus: 'loadmore', loadStatus: 'loadmore',
hasScrolled: false hasScrolled: false,
reqSeq: 0
} }
}, },
onLoad() { onLoad() {
const loc = uni.getStorageSync('lng') && uni.getStorageSync('lat') const hasLoc = uni.getStorageSync('lng') && uni.getStorageSync('lat')
? { lng: uni.getStorageSync('lng'), lat: uni.getStorageSync('lat') } if (hasLoc) {
: null this.lng = uni.getStorageSync('lng')
if (loc) { this.lat = uni.getStorageSync('lat')
this.lng = loc.lng
this.lat = loc.lat
}
this.loadList() this.loadList()
} else {
// xuchao: 定位仅在首页触发过,直接进本页无缓存时主动获取一次,保证距离列可展示
import('@/common/location.js').then(module => module.default.getLocation())
.then(({ lng, lat }) => {
this.lng = lng
this.lat = lat
})
.catch(() => {})
.finally(() => this.loadList())
}
},
// xuchao: 改用页面级触底加载(scroll-view 无固定高度时 scrolltolower 永不触发)
onReachBottom() {
this.loadMore()
}, },
methods: { methods: {
loadList() { loadList() {
if (this.loading) return // xuchao: 请求序号 —— 新请求直接取代在途请求,过期响应整体丢弃(修复搜索竞态,故不再用 loading 提前 return)
const seq = ++this.reqSeq
this.loading = true this.loading = true
this.loadStatus = 'loading' this.loadStatus = 'loading'
const params = { const params = {
@@ -76,6 +92,7 @@ export default {
if (this.lng) params.lng = this.lng if (this.lng) params.lng = this.lng
if (this.lat) params.lat = this.lat if (this.lat) params.lat = this.lat
this.$u.api.getStoreList(params).then(res => { this.$u.api.getStoreList(params).then(res => {
if (seq !== this.reqSeq) return
if (res.errcode === 0 && res.data) { if (res.errcode === 0 && res.data) {
const list = res.data.list || [] const list = res.data.list || []
if (this.pageNo === 1) { if (this.pageNo === 1) {
@@ -84,8 +101,11 @@ export default {
this.list = this.list.concat(list) this.list = this.list.concat(list)
} }
this.total = res.data.total || 0 this.total = res.data.total || 0
// xuchao: 本页不足一页即已到尾部,钉住 total,防止 total 中途变大时空翻
if (list.length < this.pageSize) this.total = this.list.length
} }
}).finally(() => { }).finally(() => {
if (seq !== this.reqSeq) return
this.loading = false this.loading = false
if (this.list.length === 0) { if (this.list.length === 0) {
this.loadStatus = 'nomore' this.loadStatus = 'nomore'
@@ -185,11 +205,14 @@ export default {
.list-area { .list-area {
padding: 12px; padding: 12px;
// xuchao: border-box 防止 padding 撑出页面横向滚动
box-sizing: border-box;
} }
.venue-card { .venue-card {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
box-sizing: border-box;
background-color: #ffffff; background-color: #ffffff;
border: 1px solid #f2f2f7; border: 1px solid #f2f2f7;
border-radius: 6px; border-radius: 6px;
@@ -209,6 +232,8 @@ export default {
.card-info { .card-info {
flex: 1; flex: 1;
// xuchao: min-width:0 允许 flex 子项收缩到内容宽度以下,配合 nowrap 文本出省略号
min-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0; gap: 0;
@@ -218,6 +243,10 @@ export default {
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
color: #000000; color: #000000;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.card-category { .card-category {
@@ -226,14 +255,22 @@ export default {
margin-top: 8px; margin-top: 8px;
} }
.card-address-row {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
margin-top: 8px;
}
.card-address { .card-address {
flex: 1;
min-width: 0;
font-size: 14px; font-size: 14px;
color: #727272; color: #727272;
margin-top: 8px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
max-width: 200px;
} }
.card-distance { .card-distance {
+31 -12
View File
@@ -63,9 +63,9 @@
<view style="background: #fff;"> <view style="background: #fff;">
<view class="banner-area"> <view class="banner-area">
<text class="banner-title">恭喜您!获得怀化市社保卡权益包</text> <text class="banner-title">恭喜您!获得怀化市社保卡权益包</text>
<view class="banner-expire"> <view class="banner-expire" v-if="expiresTime">
<text class="expire-icon">!</text> <text class="expire-icon">!</text>
<text class="expire-text">2026年12月31日</text> <text class="expire-text">{{ expiresTime }}</text>
<text class="expire-label">到期</text> <text class="expire-label">到期</text>
</view> </view>
</view> </view>
@@ -76,7 +76,7 @@
<text class="stats-label">总权益价值</text> <text class="stats-label">总权益价值</text>
<view class="stats-value-row"> <view class="stats-value-row">
<text class="stats-currency">¥</text> <text class="stats-currency">¥</text>
<text class="stats-number">1500</text> <text class="stats-number">{{ totalValueText }}</text>
<text class="stats-unit">元</text> <text class="stats-unit">元</text>
</view> </view>
</view> </view>
@@ -84,7 +84,7 @@
<view class="stats-item"> <view class="stats-item">
<text class="stats-label">可用权益数</text> <text class="stats-label">可用权益数</text>
<view class="stats-value-row"> <view class="stats-value-row">
<text class="stats-number-large">10</text> <text class="stats-number-large">{{ rightsCount }}</text>
<text class="stats-unit">个</text> <text class="stats-unit">个</text>
</view> </view>
</view> </view>
@@ -172,6 +172,10 @@
data() { data() {
return { return {
status: 'auth', status: 'auth',
// xuchao: 权益概览三字段,首屏用缓存兜底,随后由服务端复查刷新
expiresTime: uni.getStorageSync('expiresTime') || '',
rightsCount: uni.getStorageSync('rightsCount') || 0,
totalValueFen: uni.getStorageSync('totalValueFen') || 0,
form: { form: {
realName: '', realName: '',
phone: '', phone: '',
@@ -199,6 +203,13 @@
checkLoading: false checkLoading: false
} }
}, },
computed: {
// xuchao: 总权益价值分转元,整除去小数,否则保留两位
totalValueText() {
const yuan = this.totalValueFen / 100
return Number.isInteger(yuan) ? String(yuan) : yuan.toFixed(2)
}
},
onLoad(options) { onLoad(options) {
if (options.status) { if (options.status) {
this.status = options.status this.status = options.status
@@ -238,10 +249,7 @@
phone: phone phone: phone
}).then(res => { }).then(res => {
if (res.errcode !== 0 || !res.data) return if (res.errcode !== 0 || !res.data) return
uni.setStorageSync('isAuth', res.data.isAuth) this.applyEligibility(res.data)
uni.setStorageSync('hasWin', res.data.hasWin)
uni.setStorageSync('cardNumber', res.data.cardNumber)
uni.setStorageSync('expiresTime', res.data.expiresTime)
// xuchao: 服务端已认证则清除取消标记,后续按真实状态展示 // xuchao: 服务端已认证则清除取消标记,后续按真实状态展示
if (res.data.isAuth) uni.removeStorageSync('authFlowStarted') if (res.data.isAuth) uni.removeStorageSync('authFlowStarted')
const newStatus = res.data.isAuth ? (res.data.hasWin ? 'info' : 'noWin') : 'auth' const newStatus = res.data.isAuth ? (res.data.hasWin ? 'info' : 'noWin') : 'auth'
@@ -253,12 +261,26 @@
} }
}) })
}, },
// xuchao: 资格查询结果统一写入缓存并回填页面(到期时间/总权益价值/可用权益数)
applyEligibility(data) {
uni.setStorageSync('isAuth', data.isAuth)
uni.setStorageSync('hasWin', data.hasWin)
uni.setStorageSync('cardNumber', data.cardNumber)
uni.setStorageSync('expiresTime', data.expiresTime)
uni.setStorageSync('rightsCount', data.rightsCount || 0)
uni.setStorageSync('totalValueFen', data.totalValueFen || 0)
this.expiresTime = data.expiresTime || ''
this.rightsCount = data.rightsCount || 0
this.totalValueFen = data.totalValueFen || 0
},
// xuchao: 认证取消/未通过:清理旧认证缓存并标记流程已发起,取消后应停留认证页 // xuchao: 认证取消/未通过:清理旧认证缓存并标记流程已发起,取消后应停留认证页
markAuthCanceled() { markAuthCanceled() {
uni.removeStorageSync('isAuth') uni.removeStorageSync('isAuth')
uni.removeStorageSync('hasWin') uni.removeStorageSync('hasWin')
uni.removeStorageSync('cardNumber') uni.removeStorageSync('cardNumber')
uni.removeStorageSync('expiresTime') uni.removeStorageSync('expiresTime')
uni.removeStorageSync('rightsCount')
uni.removeStorageSync('totalValueFen')
uni.setStorageSync('authFlowStarted', true) uni.setStorageSync('authFlowStarted', true)
}, },
checkAuthResult() { checkAuthResult() {
@@ -301,10 +323,7 @@
}).then(eRes => { }).then(eRes => {
console.log('eligibility res:', eRes) console.log('eligibility res:', eRes)
if (eRes.errcode === 0) { if (eRes.errcode === 0) {
uni.setStorageSync('isAuth', eRes.data.isAuth) this.applyEligibility(eRes.data)
uni.setStorageSync('hasWin', eRes.data.hasWin)
uni.setStorageSync('cardNumber', eRes.data.cardNumber)
uni.setStorageSync('expiresTime', eRes.data.expiresTime)
// xuchao: 认证成功,清除流程标记 // xuchao: 认证成功,清除流程标记
uni.removeStorageSync('authFlowStarted') uni.removeStorageSync('authFlowStarted')
if (eRes.data.isAuth) { if (eRes.data.isAuth) {
+15 -5
View File
@@ -16,7 +16,7 @@
</view> </view>
<!-- 场馆列表 --> <!-- 场馆列表 -->
<scroll-view class="list-area" scroll-y @scrolltolower="loadMore" :lower-threshold="100"> <view class="list-area">
<view class="venue-card" v-for="item in list" :key="item.shopId" @click="goDetail(item)"> <view class="venue-card" v-for="item in list" :key="item.shopId" @click="goDetail(item)">
<image class="card-img" :src="item.image" mode="aspectFill" /> <image class="card-img" :src="item.image" mode="aspectFill" />
<view class="card-info"> <view class="card-info">
@@ -30,9 +30,9 @@
</view> </view>
</view> </view>
<view v-if="hasScrolled" class="nomore-area"> <view v-if="hasScrolled" class="nomore-area">
<u-loadmore :status="loadStatus" :loading-text="'加载中...'" :loadmore-text="''" :nomore-text="'我也是有底线的'" /> <u-loadmore :status="loadStatus" :load-text="{ loadmore: '', loading: '加载中...', nomore: '我也是有底线的' }" />
</view>
</view> </view>
</scroll-view>
</view> </view>
</template> </template>
@@ -49,7 +49,8 @@ export default {
lng: '', lng: '',
lat: '', lat: '',
loadStatus: 'loadmore', loadStatus: 'loadmore',
hasScrolled: false hasScrolled: false,
reqSeq: 0
} }
}, },
onLoad() { onLoad() {
@@ -62,9 +63,14 @@ export default {
} }
this.loadList() this.loadList()
}, },
// xuchao: 改用页面级触底加载(scroll-view 无固定高度时 scrolltolower 永不触发)
onReachBottom() {
this.loadMore()
},
methods: { methods: {
loadList() { loadList() {
if (this.loading) return // xuchao: 请求序号 —— 新请求直接取代在途请求,过期响应整体丢弃(修复搜索竞态,故不再用 loading 提前 return)
const seq = ++this.reqSeq
this.loading = true this.loading = true
this.loadStatus = 'loading' this.loadStatus = 'loading'
const params = { const params = {
@@ -76,6 +82,7 @@ export default {
if (this.lng) params.lng = this.lng if (this.lng) params.lng = this.lng
if (this.lat) params.lat = this.lat if (this.lat) params.lat = this.lat
this.$u.api.getStoreList(params).then(res => { this.$u.api.getStoreList(params).then(res => {
if (seq !== this.reqSeq) return
if (res.errcode === 0 && res.data) { if (res.errcode === 0 && res.data) {
const list = res.data.list || [] const list = res.data.list || []
if (this.pageNo === 1) { if (this.pageNo === 1) {
@@ -84,8 +91,11 @@ export default {
this.list = this.list.concat(list) this.list = this.list.concat(list)
} }
this.total = res.data.total || 0 this.total = res.data.total || 0
// xuchao: 本页不足一页即已到尾部,钉住 total,防止 total 中途变大时空翻
if (list.length < this.pageSize) this.total = this.list.length
} }
}).finally(() => { }).finally(() => {
if (seq !== this.reqSeq) return
this.loading = false this.loading = false
if (this.list.length === 0) { if (this.list.length === 0) {
this.loadStatus = 'nomore' this.loadStatus = 'nomore'