完成一键登录和支付功能
This commit is contained in:
@@ -138,10 +138,10 @@ import { ref, computed } from 'vue'
|
||||
import MemberInfoSubNav from '@/components/memberInfo/MemberInfoSubNav.vue'
|
||||
import { PAGE, navigateToPage, backToMemberCenter } from '@/common/constants/routes.js'
|
||||
import {
|
||||
loadMemberStore,
|
||||
cancelOngoingBooking,
|
||||
formatUpcomingAlert
|
||||
} from '@/common/memberInfo/store.js'
|
||||
import { getMemberId } from '@/utils/request.js'
|
||||
import { canCancelBooking, canSigninCourse } from '@/common/memberInfo/bookingStore.js'
|
||||
import { signinGroupCourse, cancelBooking as cancelBookingApi } from '@/api/main.js'
|
||||
|
||||
@@ -184,8 +184,7 @@ function setActiveTab(tab) {
|
||||
|
||||
// 签到团课
|
||||
async function signinCourse(item) {
|
||||
const store = loadMemberStore()
|
||||
const memberId = store.memberProfile?.id || store.profile?.id
|
||||
const memberId = getMemberId()
|
||||
if (!memberId) {
|
||||
uni.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<view class="scroll-container theme-light">
|
||||
<view class="card-detail-page">
|
||||
<view v-if="card" class="card-detail-page__body">
|
||||
<view class="cd-hero">
|
||||
<view class="cd-hero__card">
|
||||
<view class="cd-hero__card-header">
|
||||
<text class="cd-hero__card-name">{{ card.memberCardName || card.cardName }}</text>
|
||||
<view class="cd-hero__card-tag" :class="'cd-hero__card-tag--' + getTagClass(card.memberCardType || card.cardType)">
|
||||
<text class="cd-hero__card-tag-text">{{ getCardTypeName(card.memberCardType || card.cardType) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cd-hero__card-body">
|
||||
<view class="cd-hero__card-item" v-if="card.memberCardValidityDays || card.validityDays">
|
||||
<text class="cd-hero__card-label">有效期</text>
|
||||
<text class="cd-hero__card-value">{{ card.memberCardValidityDays || card.validityDays }}天</text>
|
||||
</view>
|
||||
<view class="cd-hero__card-item" v-if="(card.memberCardType === 'COUNT_CARD' || card.cardType === 'COUNT') && (card.memberCardTotalTimes || card.totalTimes)">
|
||||
<text class="cd-hero__card-label">总次数</text>
|
||||
<text class="cd-hero__card-value">{{ card.memberCardTotalTimes || card.totalTimes }}次</text>
|
||||
</view>
|
||||
<view class="cd-hero__card-item" v-if="(card.memberCardType === 'STORED_VALUE_CARD' || card.cardType === 'BALANCE') && (card.memberCardAmount || card.amount)">
|
||||
<text class="cd-hero__card-label">储值金额</text>
|
||||
<text class="cd-hero__card-value">{{ card.memberCardAmount || card.amount }}元</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cd-hero__card-footer">
|
||||
<text class="cd-hero__card-price">¥{{ card.memberCardPrice || card.price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="cd-section">
|
||||
<text class="cd-section__title">会员卡权益</text>
|
||||
<view class="cd-benefits">
|
||||
<view class="cd-benefit">
|
||||
<view class="cd-benefit__icon">💪</view>
|
||||
<view class="cd-benefit__content">
|
||||
<text class="cd-benefit__title">全场器械使用</text>
|
||||
<text class="cd-benefit__desc">免费使用所有健身器械和设备</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cd-benefit">
|
||||
<view class="cd-benefit__icon">🏋️</view>
|
||||
<view class="cd-benefit__content">
|
||||
<text class="cd-benefit__title">免费团课</text>
|
||||
<text class="cd-benefit__desc">预约参加各类团课(次卡除外)</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cd-benefit">
|
||||
<view class="cd-benefit__icon">🧴</view>
|
||||
<view class="cd-benefit__content">
|
||||
<text class="cd-benefit__title">淋浴服务</text>
|
||||
<text class="cd-benefit__desc">免费使用淋浴间和储物柜</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cd-benefit">
|
||||
<view class="cd-benefit__icon">📱</view>
|
||||
<view class="cd-benefit__content">
|
||||
<text class="cd-benefit__title">在线预约</text>
|
||||
<text class="cd-benefit__desc">APP在线预约课程和签到</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="cd-section" v-if="card.extraConfig">
|
||||
<text class="cd-section__title">详细解读</text>
|
||||
<view class="cd-desc">
|
||||
<text class="cd-desc__text">{{ parseExtraConfig(card.extraConfig) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="cd-section">
|
||||
<text class="cd-section__title">购买须知</text>
|
||||
<view class="cd-notes">
|
||||
<text class="cd-notes__item">1. 会员卡一经购买,非特殊情况不予退款</text>
|
||||
<text class="cd-notes__item">2. 请在有效期内使用,过期自动失效</text>
|
||||
<text class="cd-notes__item">3. 会员卡仅限本人使用,不得转借他人</text>
|
||||
<text class="cd-notes__item">4. 支付订单有效时间为15分钟,超时自动取消</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="cd-bottom">
|
||||
<view class="cd-bottom__total">
|
||||
<text class="cd-bottom__total-label">价格</text>
|
||||
<text class="cd-bottom__total-price">¥{{ card.memberCardPrice || card.price }}</text>
|
||||
</view>
|
||||
<view class="cd-bottom__btn" @tap="handleSelectCard">
|
||||
<text class="cd-bottom__btn-text">选择该卡</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="cd-loading">
|
||||
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { getMemberCardById } from '@/api/main.js'
|
||||
|
||||
const card = ref(null)
|
||||
const loading = ref(false)
|
||||
|
||||
function getCardTypeName(type) {
|
||||
const map = {
|
||||
TIME_CARD: '时长卡',
|
||||
DURATION: '时长卡',
|
||||
COUNT_CARD: '次卡',
|
||||
COUNT: '次卡',
|
||||
STORED_VALUE_CARD: '储值卡',
|
||||
BALANCE: '储值卡'
|
||||
}
|
||||
return map[type] || type
|
||||
}
|
||||
|
||||
function getTagClass(type) {
|
||||
if (type === 'TIME_CARD' || type === 'DURATION') return 'time'
|
||||
if (type === 'COUNT_CARD' || type === 'COUNT') return 'count'
|
||||
if (type === 'STORED_VALUE_CARD' || type === 'BALANCE') return 'value'
|
||||
return 'default'
|
||||
}
|
||||
|
||||
function parseExtraConfig(config) {
|
||||
if (!config) return ''
|
||||
try {
|
||||
const obj = JSON.parse(config)
|
||||
return obj.description || obj.desc || config
|
||||
} catch (e) {
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectCard() {
|
||||
if (!card.value) return
|
||||
uni.navigateTo({ url: `/pages/memberInfo/purchaseCard?cardId=${card.value.memberCardId || card.value.id}` })
|
||||
}
|
||||
|
||||
async function loadCardDetail(cardId) {
|
||||
if (!cardId) return
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getMemberCardById(cardId)
|
||||
if (res && res.memberCardId) {
|
||||
card.value = res
|
||||
} else if (res && res.code === 200 && res.data) {
|
||||
card.value = res.data
|
||||
} else {
|
||||
uni.showToast({ title: res?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载会员卡详情失败:', e)
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const options = currentPage.options || {}
|
||||
|
||||
const cardId = options.cardId || options.id
|
||||
if (cardId) {
|
||||
loadCardDetail(cardId)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/common/style/base.css';
|
||||
@import '@/common/style/memberInfo/pages/page-reset.css';
|
||||
@import '@/common/style/memberInfo/pages/sub-page-base.css';
|
||||
@import '@/common/style/memberInfo/member-info-component-reset.css';
|
||||
@import '@/common/style/memberInfo/member-info-sub-nav.css';
|
||||
|
||||
.card-detail-page {
|
||||
min-height: 100vh;
|
||||
background: #F5F7FA;
|
||||
}
|
||||
|
||||
.card-detail-page__body {
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
.cd-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 60vh;
|
||||
font-size: 14px;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.cd-hero {
|
||||
background: linear-gradient(135deg, #1A365D 0%, #2C5282 100%);
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.cd-hero__card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.cd-hero__card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cd-hero__card-name {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.cd-hero__card-tag {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
&--time { background: rgba(66, 153, 225, 0.3); color: #90CDF4; }
|
||||
&--count { background: rgba(237, 137, 54, 0.3); color: #FBD38D; }
|
||||
&--value { background: rgba(56, 161, 105, 0.3); color: #9AE6B4; }
|
||||
&--default { background: rgba(160, 174, 192, 0.3); color: #CBD5E0; }
|
||||
}
|
||||
|
||||
.cd-hero__card-body {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.cd-hero__card-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.cd-hero__card-label {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.cd-hero__card-value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.cd-hero__card-footer {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.cd-hero__card-price {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #F6E05E;
|
||||
}
|
||||
|
||||
.cd-hero__card-original {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.cd-section {
|
||||
margin: 16px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.cd-section__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1A202C;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cd-benefits {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.cd-benefit {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cd-benefit__icon {
|
||||
font-size: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cd-benefit__content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.cd-benefit__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #2D3748;
|
||||
}
|
||||
|
||||
.cd-benefit__desc {
|
||||
font-size: 12px;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.cd-desc__text {
|
||||
font-size: 14px;
|
||||
color: #4A5568;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.cd-notes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cd-notes__item {
|
||||
font-size: 13px;
|
||||
color: #718096;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cd-bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 24px;
|
||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.05);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.cd-bottom__total {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cd-bottom__total-label {
|
||||
font-size: 14px;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.cd-bottom__total-price {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #E53E3E;
|
||||
}
|
||||
|
||||
.cd-bottom__btn {
|
||||
background: linear-gradient(135deg, #1677FF 0%, #0958D9 100%);
|
||||
border-radius: 12px;
|
||||
padding: 16px 48px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.cd-bottom__btn-text {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,48 +20,42 @@
|
||||
<text class="phone-display">验证码登录</text>
|
||||
<text class="auth-tip">认证服务由中国移动提供</text>
|
||||
|
||||
<view class="form-group">
|
||||
<view class="input-wrapper" :class="{ focused: isSmsPhoneFocused }">
|
||||
<input
|
||||
v-model="smsPhone"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
class="phone-input"
|
||||
confirm-type="next"
|
||||
@focus="isSmsPhoneFocused = true"
|
||||
@blur="isSmsPhoneFocused = false"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-group phone-form-group">
|
||||
<input
|
||||
v-model="smsPhone"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
class="underline-input"
|
||||
confirm-type="next"
|
||||
@focus="isSmsPhoneFocused = true"
|
||||
@blur="isSmsPhoneFocused = false"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<view class="input-wrapper code-input" :class="{ focused: isCodeFocused }">
|
||||
<input
|
||||
v-model="code"
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
maxlength="6"
|
||||
class="phone-input"
|
||||
confirm-type="done"
|
||||
@focus="isCodeFocused = true"
|
||||
@blur="isCodeFocused = false"
|
||||
/>
|
||||
<button
|
||||
class="send-code-btn"
|
||||
:class="{ disabled: countdown > 0 || isLoading }"
|
||||
@tap="handleSendCode"
|
||||
:disabled="countdown > 0 || isLoading"
|
||||
>
|
||||
<text>{{ countdown > 0 ? `${countdown}秒后重发` : '发送验证码' }}</text>
|
||||
</button>
|
||||
</view>
|
||||
<view class="form-group code-form-group">
|
||||
<VerifyCodeInput
|
||||
ref="codeInputRef"
|
||||
v-model="code"
|
||||
:length="4"
|
||||
:mask="false"
|
||||
type="underline"
|
||||
activeColor="#FF8C42"
|
||||
@complete="handleCodeComplete"
|
||||
@focus="onInputFocus"
|
||||
@blur="onInputBlur"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<button class="login-btn primary" :class="{ disabled: !canSmsSubmit, loading: isLoading }" @tap="handleSmsLogin" :disabled="!canSmsSubmit || isLoading">
|
||||
<text>{{ isLoading ? '登录中...' : '验证码登录' }}</text>
|
||||
|
||||
<button
|
||||
class="login-btn primary"
|
||||
:class="{ disabled: countdown > 0 || isLoading }"
|
||||
@tap="handleSendCode"
|
||||
:disabled="countdown > 0 || isLoading"
|
||||
>
|
||||
<text>{{ countdown > 0 ? `${countdown}秒后重发` : '发送验证码' }}</text>
|
||||
</button>
|
||||
|
||||
|
||||
<button class="login-btn secondary" @tap="switchToPhone">
|
||||
<text>其他登录方式</text>
|
||||
</button>
|
||||
@@ -84,22 +78,7 @@
|
||||
<text class="phone-display">本机号码登录</text>
|
||||
<text class="auth-tip">认证服务由中国移动提供</text>
|
||||
|
||||
<view class="form-group phone-login">
|
||||
<view class="input-wrapper" :class="{ focused: isInputFocused }">
|
||||
<input
|
||||
v-model="phone"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
class="phone-input"
|
||||
confirm-type="done"
|
||||
@focus="isInputFocused = true"
|
||||
@blur="isInputFocused = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="login-btn primary" :class="{ disabled: !canSubmit, loading: isLoading }" @tap="handlePhoneLogin" :disabled="!canSubmit || isLoading">
|
||||
<button class="login-btn primary" :class="{ disabled: isLoading, loading: isLoading }" @tap="handlePhoneLogin" :disabled="isLoading">
|
||||
<text>{{ isLoading ? '登录中...' : '一键登录' }}</text>
|
||||
</button>
|
||||
|
||||
@@ -123,17 +102,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
import { setToken } from '@/utils/request.js'
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { setToken, setRefreshToken } from '@/utils/request.js'
|
||||
import { loginWithPhone, oneClickLogin, sendCode } from '@/api/main.js'
|
||||
|
||||
// 调用真实后端API,不再使用测试数据
|
||||
import VerifyCodeInput from '@/components/global/VerifyCodeInput.vue'
|
||||
|
||||
const activeCard = ref('phone')
|
||||
const phone = ref('')
|
||||
const isLoading = ref(false)
|
||||
const isInputFocused = ref(false)
|
||||
const agreed = ref(false)
|
||||
const agreed = ref(true)
|
||||
const shakeAgreement = ref(false)
|
||||
|
||||
const smsPhone = ref('')
|
||||
@@ -157,13 +133,27 @@ function showToast(msg) {
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
const canSubmit = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(phone.value) && !isLoading.value
|
||||
})
|
||||
|
||||
const canSmsSubmit = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(smsPhone.value) && /^\d{6}$/.test(code.value) && !isLoading.value
|
||||
})
|
||||
/**
|
||||
* 保存登录信息到本地存储
|
||||
*/
|
||||
function saveLoginInfo(loginInfo) {
|
||||
if (!loginInfo) return
|
||||
|
||||
// 保存 token (已经在 request.js 中通过 setToken 保存)
|
||||
|
||||
// 保存会员信息
|
||||
if (loginInfo.memberId || loginInfo.id) {
|
||||
const memberInfo = {
|
||||
id: loginInfo.memberId || loginInfo.id,
|
||||
memberId: loginInfo.memberId || loginInfo.id,
|
||||
phone: loginInfo.phone || '',
|
||||
nickname: loginInfo.nickname || '',
|
||||
avatar: loginInfo.avatar || ''
|
||||
}
|
||||
uni.setStorageSync('loginMemberInfo', memberInfo)
|
||||
console.log('[login] 登录信息已保存:', memberInfo)
|
||||
}
|
||||
}
|
||||
|
||||
function onAgreementChange(e) {
|
||||
agreed.value = e.detail.value.includes('agreed')
|
||||
@@ -175,8 +165,6 @@ function switchToSms() {
|
||||
|
||||
function switchToPhone() {
|
||||
activeCard.value = 'phone'
|
||||
phone.value = ''
|
||||
phoneError.value = ''
|
||||
}
|
||||
|
||||
function showAgreement() {
|
||||
@@ -187,16 +175,20 @@ function showPrivacy() {
|
||||
uni.showModal({ title: '隐私政策', content: '这里是隐私政策内容...', showCancel: false })
|
||||
}
|
||||
|
||||
function validatePhone() {
|
||||
if (!phone.value) {
|
||||
showToast('请输入手机号')
|
||||
return false
|
||||
function handleCodeComplete(val) {
|
||||
if (!isLoading.value) {
|
||||
setTimeout(() => {
|
||||
handleSmsLogin()
|
||||
}, 100)
|
||||
}
|
||||
if (!/^1[3-9]\d{9}$/.test(phone.value)) {
|
||||
showToast('请输入正确的手机号')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function onInputFocus() {
|
||||
isCodeFocused.value = true
|
||||
}
|
||||
|
||||
function onInputBlur() {
|
||||
isCodeFocused.value = false
|
||||
}
|
||||
|
||||
function validateSmsPhone() {
|
||||
@@ -217,34 +209,146 @@ async function handlePhoneLogin() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!validatePhone()) return
|
||||
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
const res = await oneClickLogin({
|
||||
accessToken: phone.value,
|
||||
openid: 'uniapp_phone_login',
|
||||
nickname: '',
|
||||
avatar: ''
|
||||
await new Promise((resolve, reject) => {
|
||||
uni.preLogin({
|
||||
provider: 'univerify',
|
||||
success: resolve,
|
||||
fail: (err) => {
|
||||
console.warn('预登录失败,切换到验证码登录:', err)
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (res && res.accessToken) {
|
||||
setToken(res.accessToken)
|
||||
uni.setStorageSync('memberInfo', res)
|
||||
uni.setStorageSync('isLogin', true)
|
||||
showToast('登录成功')
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/memberInfo/memberInfo' })
|
||||
}, 1500)
|
||||
} else {
|
||||
showToast('登录失败,请重试')
|
||||
}
|
||||
uni.login({
|
||||
provider: 'univerify',
|
||||
loginBtnText: '本机号码一键登录',
|
||||
univerifyStyle: {
|
||||
fullScreen: true,
|
||||
backgroundColor: '#ffffff',
|
||||
icon: {
|
||||
path: '/static/logo.png',
|
||||
width: '60px',
|
||||
height: '60px'
|
||||
},
|
||||
phoneNum: {
|
||||
color: '#333333'
|
||||
},
|
||||
slogan: {
|
||||
color: '#999999'
|
||||
},
|
||||
authButton: {
|
||||
normalColor: '#FF8C42',
|
||||
highlightColor: '#E07A38',
|
||||
disabledColor: '#FFB88A',
|
||||
textColor: '#ffffff',
|
||||
title: '本机号码一键登录',
|
||||
borderRadius: '24px'
|
||||
},
|
||||
otherLoginButton: {
|
||||
visible: true,
|
||||
normalColor: '#ffffff',
|
||||
highlightColor: '#f5f5f5',
|
||||
textColor: '#666666',
|
||||
title: '其他登录方式',
|
||||
borderColor: '#e5e5e5',
|
||||
borderRadius: '24px'
|
||||
},
|
||||
privacyTerms: {
|
||||
defaultCheckBoxState: true,
|
||||
textColor: '#999999',
|
||||
termsColor: '#FF8C42',
|
||||
prefix: '我已阅读并同意',
|
||||
suffix: '并使用本机号码登录',
|
||||
privacyItems: [
|
||||
{
|
||||
url: 'https://example.com/agreement',
|
||||
title: '用户服务协议'
|
||||
},
|
||||
{
|
||||
url: 'https://example.com/privacy',
|
||||
title: '隐私政策'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
success: async (loginRes) => {
|
||||
uni.closeAuthView()
|
||||
|
||||
try {
|
||||
const authResult = loginRes.authResult
|
||||
if (!authResult) {
|
||||
console.error('authResult为空:', loginRes)
|
||||
showToast('一键登录失败,请使用验证码登录')
|
||||
switchToSms()
|
||||
isLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const accessToken = authResult.accessToken || authResult.access_token || authResult.token
|
||||
const openid = authResult.openid || authResult.openId
|
||||
|
||||
console.log('accessToken:', accessToken, 'openid:', openid)
|
||||
|
||||
if (!accessToken) {
|
||||
console.error('accessToken为空,authResult:', authResult)
|
||||
showToast('一键登录失败,请使用验证码登录')
|
||||
switchToSms()
|
||||
isLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const res = await oneClickLogin({
|
||||
accessToken: accessToken,
|
||||
openid: openid,
|
||||
nickname: '',
|
||||
avatar: ''
|
||||
})
|
||||
|
||||
if (res && res.accessToken) {
|
||||
setToken(res.accessToken)
|
||||
if (res.refreshToken) {
|
||||
setRefreshToken(res.refreshToken)
|
||||
}
|
||||
saveLoginInfo(res)
|
||||
showToast('登录成功')
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}, 1500)
|
||||
} else {
|
||||
showToast('一键登录失败,请使用验证码登录')
|
||||
switchToSms()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('一键登录处理失败:', e)
|
||||
showToast(e?.message || '一键登录失败,请使用验证码登录')
|
||||
switchToSms()
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.closeAuthView()
|
||||
|
||||
console.error('一键登录失败:', err)
|
||||
isLoading.value = false
|
||||
|
||||
if (err.code === 30008 || err.errMsg && err.errMsg.includes('其他登录方式')) {
|
||||
switchToSms()
|
||||
} else {
|
||||
showToast('一键登录失败,请使用验证码登录')
|
||||
switchToSms()
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('一键登录失败:', err)
|
||||
showToast(err?.message || '登录失败,请重试')
|
||||
} finally {
|
||||
console.error('一键登录调用失败:', err)
|
||||
showToast('一键登录失败,请使用验证码登录')
|
||||
isLoading.value = false
|
||||
switchToSms()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,17 +404,13 @@ async function handleSmsLogin() {
|
||||
triggerAgreementShake()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (!validateSmsPhone()) return
|
||||
if (!code.value) {
|
||||
showToast('请输入验证码')
|
||||
if (code.value.length !== 4) {
|
||||
showToast('请输入4位验证码')
|
||||
return
|
||||
}
|
||||
if (!/^\d{6}$/.test(code.value)) {
|
||||
showToast('请输入6位验证码')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
@@ -321,8 +421,10 @@ async function handleSmsLogin() {
|
||||
|
||||
if (res && res.accessToken) {
|
||||
setToken(res.accessToken)
|
||||
uni.setStorageSync('memberInfo', res)
|
||||
uni.setStorageSync('isLogin', true)
|
||||
if (res.refreshToken) {
|
||||
setRefreshToken(res.refreshToken)
|
||||
}
|
||||
saveLoginInfo(res)
|
||||
showToast('登录成功')
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/memberInfo/memberInfo' })
|
||||
@@ -418,12 +520,12 @@ button::after {
|
||||
.card-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 620rpx;
|
||||
height: 500rpx;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
position: absolute;
|
||||
height: 700rpx;
|
||||
height: 620rpx;
|
||||
background: $bg-white;
|
||||
border-radius: $radius-lg;
|
||||
padding: 48rpx 40rpx;
|
||||
@@ -431,13 +533,19 @@ button::after {
|
||||
will-change: transform, z-index, filter;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 560rpx;
|
||||
}
|
||||
|
||||
.sms-card {
|
||||
z-index: 1;
|
||||
top: -60rpx;
|
||||
left: -100rpx;
|
||||
box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.12);
|
||||
filter: blur(10rpx);
|
||||
|
||||
|
||||
&.active {
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
@@ -453,7 +561,7 @@ button::after {
|
||||
left: 0;
|
||||
box-shadow: 0 24rpx 60rpx rgba(0, 0, 0, 0.18), 0 8rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
filter: blur(0);
|
||||
|
||||
|
||||
&:not(.active) {
|
||||
z-index: 1;
|
||||
top: -60rpx;
|
||||
@@ -485,45 +593,45 @@ button::after {
|
||||
.form-group {
|
||||
width: 100%;
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.phone-login{
|
||||
margin-bottom: 150rpx;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
.phone-form-group {
|
||||
height: 100rpx;
|
||||
width: 360rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
padding: 0 32rpx;
|
||||
height: 100rpx;
|
||||
border-bottom: 2rpx solid $border-light;
|
||||
|
||||
&.code-input {
|
||||
padding-right: 16rpx;
|
||||
}
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.phone-input {
|
||||
flex: 1;
|
||||
font-size: 34rpx;
|
||||
.underline-input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
color: $text-dark;
|
||||
letter-spacing: 2rpx;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2rpx solid $border-light;
|
||||
padding: 0 20rpx;
|
||||
transition: border-color 0.3s ease;
|
||||
|
||||
&:focus {
|
||||
border-bottom-color: $accent-orange;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: $text-light;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.send-code-btn {
|
||||
width: 200rpx;
|
||||
height: 72rpx;
|
||||
background: $accent-orange;
|
||||
color: $text-inverse;
|
||||
font-size: 26rpx;
|
||||
border-radius: $radius-sm;
|
||||
border: none;
|
||||
|
||||
&.disabled {
|
||||
background: $border-light;
|
||||
color: $text-light;
|
||||
}
|
||||
.code-form-group {
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -89,6 +89,48 @@
|
||||
|
||||
<view class="member-page__body">
|
||||
<view class="member-page__sections">
|
||||
<!-- 快过期会员卡提醒 -->
|
||||
<view class="expiring-cards-section" v-if="expiringCards.length > 0">
|
||||
<view class="expiring-cards__inner">
|
||||
<view class="expiring-cards__header">
|
||||
<view class="expiring-cards__header-inner">
|
||||
<view class="expiring-cards__title-row">
|
||||
<image class="expiring-cards__warn-icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/clock1.png" mode="aspectFit" />
|
||||
<text class="expiring-cards__title">会员卡即将到期</text>
|
||||
</view>
|
||||
<view class="expiring-cards__link" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="goMemberCard">
|
||||
<text class="expiring-cards__link-text">查看全部</text>
|
||||
<image class="expiring-cards__link-arrow" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright12.png" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="expiring-cards__list">
|
||||
<view
|
||||
v-for="(card, index) in expiringCards"
|
||||
:key="card.id || index"
|
||||
class="expiring-card-item"
|
||||
hover-class="mi-tap-row--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="goMemberCard"
|
||||
>
|
||||
<view class="expiring-card-item__inner">
|
||||
<view class="expiring-card-item__icon-wrap">
|
||||
<image class="expiring-card-item__icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/ticket.png" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="expiring-card-item__info">
|
||||
<text class="expiring-card-item__name">{{ card.name }}</text>
|
||||
<text class="expiring-card-item__validity">{{ card.validity }}</text>
|
||||
</view>
|
||||
<view class="expiring-card-item__days">
|
||||
<text class="expiring-card-item__days-num">{{ computeRemainingDays(card.validityEnd) }}</text>
|
||||
<text class="expiring-card-item__days-unit">天后到期</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 会员卡区域 -->
|
||||
<view class="member-card-section">
|
||||
<view class="member-card-section__inner">
|
||||
@@ -470,14 +512,18 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { PAGE, navigateToPage } from '@/common/constants/routes.js'
|
||||
import { loadMemberStore, getCenterPageData, renewMemberCard } from '@/common/memberInfo/store.js'
|
||||
import { getCenterPageData, renewMemberCard, computeRemainingDays, saveMemberStore } from '@/common/memberInfo/store.js'
|
||||
import { getMemberId } from '@/utils/request.js'
|
||||
import { getMyMemberCards } from '@/api/main.js'
|
||||
import TabBar from '@/components/TabBar.vue'
|
||||
|
||||
// 页面数据
|
||||
const userInfo = ref({})
|
||||
const stats = ref({})
|
||||
const cardInfo = ref({})
|
||||
const expiringCards = ref([])
|
||||
const bookingPreview = ref([])
|
||||
const checkIns = ref([])
|
||||
const bodyReport = ref({})
|
||||
@@ -579,6 +625,7 @@ function refreshFromStore() {
|
||||
userInfo.value = pageData.userInfo
|
||||
stats.value = pageData.stats
|
||||
cardInfo.value = pageData.cardInfo
|
||||
expiringCards.value = pageData.expiringCards || []
|
||||
bookingPreview.value = pageData.bookingPreview
|
||||
checkIns.value = pageData.checkIns
|
||||
bodyReport.value = pageData.bodyReport
|
||||
@@ -586,6 +633,64 @@ function refreshFromStore() {
|
||||
referral.value = pageData.referral
|
||||
}
|
||||
|
||||
async function refreshCardsFromServer() {
|
||||
const memberId = getMemberId()
|
||||
if (!memberId) return
|
||||
|
||||
try {
|
||||
const res = await getMyMemberCards(memberId)
|
||||
let cards = []
|
||||
if (Array.isArray(res)) {
|
||||
cards = res
|
||||
} else if (res.code === 200 && res.data) {
|
||||
cards = res.data
|
||||
} else if (Array.isArray(res.data)) {
|
||||
cards = res.data
|
||||
}
|
||||
|
||||
if (cards.length > 0) {
|
||||
const allCards = cards.map(myCard => {
|
||||
const cardName = myCard.memberCardName || myCard.cardName || myCard.name || `会员卡#${myCard.memberCardId || myCard.id}`
|
||||
const validityEnd = myCard.validityEnd || myCard.expireTime
|
||||
const validityStart = myCard.validityStart || myCard.purchaseTime || myCard.createdAt
|
||||
return {
|
||||
id: myCard.id,
|
||||
name: cardName,
|
||||
status: myCard.status || 'active',
|
||||
validity: validityEnd ? `有效期至 ${validityEnd}` : '永久有效',
|
||||
validityEnd: validityEnd,
|
||||
validityStart: validityStart
|
||||
}
|
||||
})
|
||||
|
||||
store.cards = allCards
|
||||
|
||||
const myCard = cards[0]
|
||||
const cardName = myCard.memberCardName || myCard.cardName || myCard.name || `会员卡#${myCard.memberCardId || myCard.id}`
|
||||
const validityEnd = myCard.validityEnd || myCard.expireTime
|
||||
const validityStart = myCard.validityStart || myCard.purchaseTime || myCard.createdAt
|
||||
|
||||
store.card = {
|
||||
id: myCard.id,
|
||||
name: cardName,
|
||||
status: myCard.status || 'active',
|
||||
validity: validityEnd ? `有效期至 ${validityEnd}` : '永久有效',
|
||||
validityEnd: validityEnd,
|
||||
validityStart: validityStart
|
||||
}
|
||||
store.cardInfo = {
|
||||
expireDate: validityEnd ? `有效期至 ${validityEnd}` : '永久有效',
|
||||
remainingDays: computeRemainingDays(validityEnd)
|
||||
}
|
||||
|
||||
saveMemberStore(store)
|
||||
refreshFromStore()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[MemberCenter] 刷新会员卡数据失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
function goUserInfo() {
|
||||
navigateToPage(PAGE.USER_INFO)
|
||||
}
|
||||
@@ -717,6 +822,10 @@ onMounted(() => {
|
||||
syncNavSafeArea()
|
||||
refreshFromStore()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
refreshCardsFromServer()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -755,4 +864,126 @@ onMounted(() => {
|
||||
font-size: 14px;
|
||||
color: #8A99B4;
|
||||
}
|
||||
|
||||
.expiring-cards-section {
|
||||
margin: 0 16px 12px;
|
||||
background: linear-gradient(135deg, #FFF7E6 0%, #FFE4CC 100%);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.expiring-cards__inner {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.expiring-cards__header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.expiring-cards__header-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.expiring-cards__title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.expiring-cards__warn-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.expiring-cards__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #D46B08;
|
||||
}
|
||||
|
||||
.expiring-cards__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.expiring-cards__link-text {
|
||||
font-size: 13px;
|
||||
color: #D46B08;
|
||||
}
|
||||
|
||||
.expiring-cards__link-arrow {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.expiring-cards__list {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 12px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.expiring-card-item {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.expiring-card-item__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.expiring-card-item__icon-wrap {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.expiring-card-item__icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.expiring-card-item__info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.expiring-card-item__name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1A202C;
|
||||
}
|
||||
|
||||
.expiring-card-item__validity {
|
||||
font-size: 12px;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.expiring-card-item__days {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.expiring-card-item__days-num {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #E74C3C;
|
||||
}
|
||||
|
||||
.expiring-card-item__days-unit {
|
||||
font-size: 11px;
|
||||
color: #E74C3C;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<view class="member-page__body">
|
||||
<view class="member-page__sections">
|
||||
<MemberInfoMemberCard
|
||||
v-if="isLogin"
|
||||
:card-info="cardInfo"
|
||||
@view-all="goMemberCard"
|
||||
@renew="onRenewCard"
|
||||
@@ -46,7 +47,7 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 固定 TabBar -->
|
||||
<view class="tabbar-fixed">
|
||||
<TabBar />
|
||||
@@ -59,12 +60,12 @@ import { ref, onMounted } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { PAGE, navigateToPage } from '@/common/constants/routes.js'
|
||||
import {
|
||||
loadMemberStore,
|
||||
getCenterPageData,
|
||||
renewMemberCard,
|
||||
saveMemberStore,
|
||||
getDefaultStore
|
||||
} from '@/common/memberInfo/store.js'
|
||||
import { getMemberId } from '@/utils/request.js'
|
||||
import { login as miniappLogin, getUserInfo, getMemberDetail, getCheckInStats, updateUserInfo } from '@/api/main.js'
|
||||
import { setToken, getToken, clearToken } from '@/utils/request.js'
|
||||
import MemberInfoHeader from '@/components/memberInfo/MemberInfoHeader.vue'
|
||||
@@ -87,117 +88,79 @@ const loading = ref(false)
|
||||
const hasActiveCard = ref(false)
|
||||
const isLogin = ref(false)
|
||||
|
||||
// 页面加载时检查token
|
||||
onMounted(() => {
|
||||
const token = getToken()
|
||||
const isLoginStorage = uni.getStorageSync('isLogin')
|
||||
if (token || isLoginStorage) {
|
||||
const token = uni.getStorageSync('token')
|
||||
const loginMember = uni.getStorageSync('loginMemberInfo')
|
||||
if (token || loginMember) {
|
||||
isLogin.value = true
|
||||
}
|
||||
})
|
||||
|
||||
// 处理访客登录点击(跳转到登录页面)
|
||||
function handleGuestLogin() {
|
||||
console.log('[memberInfo] 用户点击登录区域')
|
||||
uni.navigateTo({
|
||||
url: '/pages/memberInfo/login'
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchMemberInfo() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
console.log('[memberInfo] fetchMemberInfo 开始执行')
|
||||
try {
|
||||
const res = await getUserInfo({ cache: false })
|
||||
console.log('[memberInfo] API返回,res =', res)
|
||||
const apiData = res.data || res
|
||||
console.log('[memberInfo] apiData =', apiData)
|
||||
|
||||
if (apiData && (apiData.nickname !== undefined || apiData.phone !== undefined || apiData.id !== undefined)) {
|
||||
console.log('[memberInfo] 收到有效数据,更新store')
|
||||
const store = loadMemberStore()
|
||||
// 更新memberProfile
|
||||
store.memberProfile = {
|
||||
...store.memberProfile,
|
||||
// 先从 loginMemberInfo 获取用户信息
|
||||
const loginMember = uni.getStorageSync('loginMemberInfo')
|
||||
if (loginMember?.userInfo) {
|
||||
console.log('[memberInfo] 从loginMemberInfo获取用户信息:', loginMember.userInfo)
|
||||
const apiData = loginMember.userInfo
|
||||
// 更新userInfo
|
||||
userInfo.value = {
|
||||
id: apiData.id,
|
||||
name: apiData.nickname || apiData.name,
|
||||
phone: apiData.phone,
|
||||
avatar: apiData.avatar,
|
||||
memberLevel: apiData.memberLevel || '普通会员'
|
||||
}
|
||||
// 更新profile
|
||||
store.profile = {
|
||||
...store.profile,
|
||||
id: apiData.id,
|
||||
name: apiData.nickname || apiData.name,
|
||||
phone: apiData.phone,
|
||||
avatar: apiData.avatar,
|
||||
gender: apiData.gender,
|
||||
genderDesc: apiData.genderDesc,
|
||||
birthday: apiData.birthday,
|
||||
height: apiData.height,
|
||||
weight: apiData.weight,
|
||||
hasPhone: apiData.hasPhone,
|
||||
isSubscribed: apiData.isSubscribed,
|
||||
memberLevel: apiData.memberLevel || '普通会员'
|
||||
}
|
||||
// 获取会员详细信息(会员卡、积分)
|
||||
try {
|
||||
const detailRes = await getMemberDetail()
|
||||
console.log('[memberInfo] getMemberDetail 返回:', JSON.stringify(detailRes, null, 2))
|
||||
const detailData = detailRes.data || {}
|
||||
store.card = detailData.memberCards ? detailData.memberCards[0] || {} : {}
|
||||
store.cardInfo = {
|
||||
name: detailData.memberCards?.[0]?.memberCardName || '暂无会员卡',
|
||||
type: detailData.memberCards?.[0]?.memberCardTypeDesc || '',
|
||||
remainingTimes: detailData.memberCards?.[0]?.memberCardTotalTimes || 0,
|
||||
status: detailData.memberCards?.[0]?.memberCardStatusDesc || ''
|
||||
}
|
||||
hasActiveCard.value = (detailData.activeCardCount || 0) > 0
|
||||
store.stats = {
|
||||
...store.stats,
|
||||
pointsBalance: detailData.pointsBalance || 0
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[memberInfo] 获取会员详情失败:', e)
|
||||
}
|
||||
// 获取签到统计
|
||||
try {
|
||||
const statsRes = await getCheckInStats()
|
||||
console.log('[memberInfo] getCheckInStats 返回:', JSON.stringify(statsRes, null, 2))
|
||||
const statsData = statsRes.data || {}
|
||||
store.stats.checkInCount = statsData.totalCount || 0
|
||||
} catch (e) {
|
||||
console.error('[memberInfo] 获取签到统计失败:', e)
|
||||
store.stats.checkInCount = 0
|
||||
}
|
||||
saveMemberStore(store)
|
||||
refreshFromStore()
|
||||
console.log('[memberInfo] store已更新')
|
||||
} else {
|
||||
console.log('[memberInfo] 未登录或数据无效,使用默认空数据')
|
||||
const store = loadMemberStore()
|
||||
store.memberProfile = {}
|
||||
store.profile = {}
|
||||
store.stats.checkInCount = 0
|
||||
hasActiveCard.value = false
|
||||
saveMemberStore(store)
|
||||
refreshFromStore()
|
||||
}
|
||||
|
||||
// 获取积分信息
|
||||
try {
|
||||
const detailRes = await getMemberDetail()
|
||||
const detailData = detailRes.data || {}
|
||||
stats.value = {
|
||||
...stats.value,
|
||||
pointsBalance: detailData.pointsBalance || 0
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[memberInfo] 获取积分信息失败:', e)
|
||||
}
|
||||
|
||||
// 获取签到统计
|
||||
try {
|
||||
const statsRes = await getCheckInStats()
|
||||
console.log('[memberInfo] getCheckInStats 返回:', JSON.stringify(statsRes, null, 2))
|
||||
const statsData = statsRes.data || {}
|
||||
stats.value.checkInCount = statsData.totalCount || 0
|
||||
} catch (e) {
|
||||
console.error('[memberInfo] 获取签到统计失败:', e)
|
||||
stats.value.checkInCount = 0
|
||||
}
|
||||
|
||||
// 刷新store中的业务数据
|
||||
refreshFromStore()
|
||||
console.log('[memberInfo] fetchMemberInfo 执行完成')
|
||||
} catch (err) {
|
||||
console.error('[memberInfo] 获取会员信息失败:', err)
|
||||
// 失败时使用默认空数据
|
||||
const store = loadMemberStore()
|
||||
store.memberProfile = {}
|
||||
store.profile = {}
|
||||
store.stats.checkInCount = 0
|
||||
hasActiveCard.value = false
|
||||
saveMemberStore(store)
|
||||
refreshFromStore()
|
||||
} finally {
|
||||
loading.value = false
|
||||
console.log('[memberInfo] fetchMemberInfo 执行完成')
|
||||
}
|
||||
}
|
||||
|
||||
function refreshFromStore() {
|
||||
const store = loadMemberStore()
|
||||
const store = getDefaultStore()
|
||||
const pageData = getCenterPageData(store)
|
||||
console.log('[memberInfo] refreshFromStore - pageData.userInfo:', JSON.stringify(pageData.userInfo))
|
||||
userInfo.value = pageData.userInfo
|
||||
console.log('[memberInfo] refreshFromStore - pageData:', JSON.stringify(pageData))
|
||||
stats.value = pageData.stats
|
||||
cardInfo.value = pageData.cardInfo
|
||||
bookingPreview.value = pageData.bookingPreview
|
||||
@@ -210,13 +173,6 @@ function goUserInfo() {
|
||||
navigateToPage(PAGE.USER_INFO)
|
||||
}
|
||||
|
||||
function handleGuestLogin() {
|
||||
console.log('[memberInfo] 用户点击登录区域')
|
||||
uni.navigateTo({
|
||||
url: '/pages/memberInfo/login'
|
||||
})
|
||||
}
|
||||
|
||||
function goMemberCard() {
|
||||
navigateToPage(PAGE.MEMBER_CARD)
|
||||
}
|
||||
@@ -235,9 +191,6 @@ function onRenewCard() {
|
||||
content: '确认续费 90 天?',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return
|
||||
const store = loadMemberStore()
|
||||
renewMemberCard(store, 90)
|
||||
refreshFromStore()
|
||||
uni.showToast({ title: '续费成功', icon: 'success' })
|
||||
}
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,405 @@
|
||||
<template>
|
||||
<view class="scroll-container theme-light">
|
||||
<view class="recharge-page">
|
||||
<MemberInfoSubNav title="储值卡充值" @back="goBack" />
|
||||
<view class="recharge-page__body">
|
||||
<!-- 当前余额 -->
|
||||
<view class="rc-balance-card">
|
||||
<view class="rc-balance-card__inner">
|
||||
<text class="rc-balance-card__label">当前余额</text>
|
||||
<view class="rc-balance-card__amount">
|
||||
<text class="rc-balance-card__symbol">¥</text>
|
||||
<text class="rc-balance-card__num">{{ currentBalance }}</text>
|
||||
</view>
|
||||
<text class="rc-balance-card__card-name">{{ currentCardName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 充值档位 -->
|
||||
<view class="rc-section">
|
||||
<text class="rc-section__title">选择充值金额</text>
|
||||
<view class="rc-grid">
|
||||
<view
|
||||
v-for="(item, index) in rechargeOptions"
|
||||
:key="index"
|
||||
class="rc-grid-item"
|
||||
:class="{ 'rc-grid-item--selected': selectedIndex === index }"
|
||||
@tap="selectRecharge(index)"
|
||||
>
|
||||
<view class="rc-grid-item__top">
|
||||
<text class="rc-grid-item__amount">¥{{ item.amount }}</text>
|
||||
</view>
|
||||
<view class="rc-grid-item__bottom">
|
||||
<text class="rc-grid-item__bonus">赠¥{{ item.bonus }}</text>
|
||||
</view>
|
||||
<view class="rc-grid-item__check" v-if="selectedIndex === index">
|
||||
<text>✓</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 充值说明 -->
|
||||
<view class="rc-rules">
|
||||
<text class="rc-rules__title">充值说明</text>
|
||||
<view class="rc-rules__list">
|
||||
<view class="rc-rules__item">
|
||||
<text>1. 充值金额实时到账,赠送金额同步到账</text>
|
||||
</view>
|
||||
<view class="rc-rules__item">
|
||||
<text>2. 余额可用于购买会员卡、消费等</text>
|
||||
</view>
|
||||
<view class="rc-rules__item">
|
||||
<text>3. 充值金额一经到账,不予退还</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="rc-bottom">
|
||||
<view class="rc-bottom__info">
|
||||
<text class="rc-bottom__label">实付金额</text>
|
||||
<view class="rc-bottom__amount">
|
||||
<text class="rc-bottom__symbol">¥</text>
|
||||
<text class="rc-bottom__num">{{ selectedAmount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="rc-bottom__btn"
|
||||
:class="{ 'rc-bottom__btn--disabled': loading }"
|
||||
@tap="handleRecharge"
|
||||
>
|
||||
<text>{{ loading ? '处理中...' : '立即充值' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import MemberInfoSubNav from '@/components/memberInfo/MemberInfoSubNav.vue'
|
||||
import { getMemberId } from '@/utils/request.js'
|
||||
import { checkPayPasswordSet, getStoredCardInfo } from '@/api/main.js'
|
||||
import { navigateToPage, PAGE } from '@/common/constants/routes.js'
|
||||
|
||||
const currentBalance = ref('0.00')
|
||||
const selectedIndex = ref(0)
|
||||
const loading = ref(false)
|
||||
|
||||
const rechargeOptions = ref([
|
||||
{ amount: 100, bonus: 10, payAmount: 100 },
|
||||
{ amount: 200, bonus: 20, payAmount: 200 },
|
||||
{ amount: 300, bonus: 30, payAmount: 300 }
|
||||
])
|
||||
|
||||
const selectedAmount = computed(() => {
|
||||
return rechargeOptions.value[selectedIndex.value]?.payAmount || 0
|
||||
})
|
||||
|
||||
onLoad(() => {
|
||||
loadBalance()
|
||||
})
|
||||
|
||||
async function loadBalance() {
|
||||
const memberId = getMemberId()
|
||||
if (!memberId) return
|
||||
|
||||
try {
|
||||
const res = await getStoredCardInfo(memberId)
|
||||
const data = res.data || res
|
||||
currentBalance.value = ((data.balance || 0).toFixed(2))
|
||||
} catch (e) {
|
||||
console.error('获取储值卡余额失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
function selectRecharge(index) {
|
||||
selectedIndex.value = index
|
||||
}
|
||||
|
||||
async function handleRecharge() {
|
||||
if (loading.value) return
|
||||
|
||||
const memberId = getMemberId()
|
||||
|
||||
if (!memberId) {
|
||||
uni.showToast({ title: '用户未登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 检查支付密码
|
||||
uni.showLoading({ title: '检查中...' })
|
||||
try {
|
||||
const checkRes = await checkPayPasswordSet(memberId)
|
||||
uni.hideLoading()
|
||||
|
||||
const isSet = checkRes.isSet || checkRes.data?.isSet || false
|
||||
|
||||
if (!isSet) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '您尚未设置支付密码,请先设置支付密码后再进行充值',
|
||||
confirmText: '去设置',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
navigateToPage(PAGE.SET_PAY_PASSWORD)
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 确认充值
|
||||
const option = rechargeOptions.value[selectedIndex.value]
|
||||
uni.showModal({
|
||||
title: '确认充值',
|
||||
content: `确认充值 ${option.amount} 元?\n到账金额:${option.amount + option.bonus} 元\n(含赠送 ${option.bonus} 元)`,
|
||||
confirmText: '确认充值',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
await initiatePayment(option)
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
console.error('检查支付密码失败:', e)
|
||||
uni.showToast({ title: '检查失败,请稍后重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
async function initiatePayment(option) {
|
||||
const amount = option.payAmount
|
||||
const desc = `储值卡充值:充${option.amount}得${option.amount + option.bonus}`
|
||||
const remark = `rechargeAmount=${option.amount}&bonus=${option.bonus}`
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/memberInfo/confirmPayment?orderType=STORED_CARD_RECHARGE&amount=${amount}&desc=${encodeURIComponent(desc)}&remark=${encodeURIComponent(remark)}&returnPage=memberCard&cardType=stored`,
|
||||
fail: (err) => {
|
||||
console.error('跳转支付页面失败:', err)
|
||||
uni.showToast({ title: '跳转支付页面失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/common/style/base.css';
|
||||
@import '@/common/style/memberInfo/pages/page-reset.css';
|
||||
@import '@/common/style/memberInfo/pages/sub-page-base.css';
|
||||
|
||||
.recharge-page {
|
||||
min-height: 100vh;
|
||||
background: #F5F7FA;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.recharge-page__body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.rc-balance-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.rc-balance-card__inner {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.rc-balance-card__label {
|
||||
font-size: 13px;
|
||||
opacity: 0.8;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.rc-balance-card__amount {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.rc-balance-card__symbol {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.rc-balance-card__num {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rc-balance-card__card-name {
|
||||
font-size: 13px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.rc-section {
|
||||
background: #FFFFFF;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.rc-section__title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1A202C;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.rc-grid {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.rc-grid-item {
|
||||
width: calc(33.33% - 8px);
|
||||
background: #F7FAFC;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 12px;
|
||||
padding: 16px 8px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
transition: all 0.2s;
|
||||
|
||||
&--selected {
|
||||
background: #F0F4FF;
|
||||
border-color: #1677FF;
|
||||
}
|
||||
}
|
||||
|
||||
.rc-grid-item__top {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.rc-grid-item__amount {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #1A202C;
|
||||
}
|
||||
|
||||
.rc-grid-item__bottom {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rc-grid-item__bonus {
|
||||
font-size: 12px;
|
||||
color: #E53E3E;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.rc-grid-item__check {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #1677FF;
|
||||
border-radius: 0 10px 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
text {
|
||||
color: #FFFFFF;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.rc-rules {
|
||||
background: #FFFFFF;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.rc-rules__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1A202C;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.rc-rules__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.rc-rules__item {
|
||||
font-size: 13px;
|
||||
color: #718096;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.rc-bottom {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #FFFFFF;
|
||||
padding: 12px 16px;
|
||||
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.rc-bottom__info {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.rc-bottom__label {
|
||||
font-size: 13px;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.rc-bottom__amount {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.rc-bottom__symbol {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #E53E3E;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.rc-bottom__num {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #E53E3E;
|
||||
}
|
||||
|
||||
.rc-bottom__btn {
|
||||
padding: 12px 32px;
|
||||
background: #1677FF;
|
||||
border-radius: 24px;
|
||||
color: #FFFFFF;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<view class="scroll-container theme-light">
|
||||
<view class="set-pay-password-page">
|
||||
<MemberInfoSubNav :title="isReset ? '修改支付密码' : '设置支付密码'" @back="goBack" />
|
||||
<view class="set-pay-password-page__body">
|
||||
<view class="spp-intro">
|
||||
<image class="spp-intro__icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/lock.png" mode="aspectFit" />
|
||||
</view>
|
||||
|
||||
<VerifyCodeInput
|
||||
ref="verifyInputRef"
|
||||
v-model="password"
|
||||
:title="currentTitle"
|
||||
:desc="currentDesc"
|
||||
:length="6"
|
||||
:mask="true"
|
||||
type="box"
|
||||
:errorMsg="errorMsg"
|
||||
@complete="handleComplete"
|
||||
/>
|
||||
|
||||
<view class="spp-forgot" v-if="isReset && step === 1" @tap="goForgotPassword">
|
||||
<text>忘记密码?</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import MemberInfoSubNav from '@/components/memberInfo/MemberInfoSubNav.vue'
|
||||
import VerifyCodeInput from '@/components/global/VerifyCodeInput.vue'
|
||||
import { getMemberId } from '@/utils/request.js'
|
||||
import { setPayPassword, verifyPayPassword, resetPayPassword } from '@/api/main.js'
|
||||
|
||||
const password = ref('')
|
||||
const step = ref(1)
|
||||
const firstPassword = ref('')
|
||||
const isReset = ref(false)
|
||||
const errorMsg = ref('')
|
||||
const verifyInputRef = ref(null)
|
||||
|
||||
const currentTitle = computed(() => {
|
||||
if (isReset.value && step.value === 1) {
|
||||
return '请输入原6位支付密码'
|
||||
}
|
||||
if (step.value === 1) {
|
||||
return isReset.value ? '请输入新的6位支付密码' : '请设置6位支付密码'
|
||||
}
|
||||
return '请再次输入6位支付密码'
|
||||
})
|
||||
|
||||
const currentDesc = computed(() => {
|
||||
if (isReset.value && step.value === 1) {
|
||||
return '验证原密码后可设置新密码'
|
||||
}
|
||||
return '支付密码用于充值、消费等资金操作,请妥善保管'
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
if (options && options.reset === 'true') {
|
||||
isReset.value = true
|
||||
}
|
||||
})
|
||||
|
||||
function handleComplete(pwd) {
|
||||
errorMsg.value = ''
|
||||
|
||||
if (isReset.value && step.value === 1) {
|
||||
verifyOldPassword(pwd)
|
||||
return
|
||||
}
|
||||
|
||||
if (step.value === 1) {
|
||||
firstPassword.value = pwd
|
||||
step.value = 2
|
||||
password.value = ''
|
||||
nextTick(() => {
|
||||
verifyInputRef.value?.focusInput()
|
||||
})
|
||||
} else {
|
||||
if (pwd !== firstPassword.value) {
|
||||
errorMsg.value = '两次密码输入不一致,请重新输入'
|
||||
password.value = ''
|
||||
step.value = 1
|
||||
firstPassword.value = ''
|
||||
nextTick(() => {
|
||||
verifyInputRef.value?.focusInput()
|
||||
})
|
||||
return
|
||||
}
|
||||
submitPassword(pwd)
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyOldPassword(pwd) {
|
||||
const memberId = getMemberId()
|
||||
|
||||
if (!memberId) {
|
||||
uni.showToast({ title: '用户未登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '验证中...' })
|
||||
|
||||
try {
|
||||
const res = await verifyPayPassword(memberId, pwd)
|
||||
uni.hideLoading()
|
||||
|
||||
if (res && (res.valid === true || res.data?.valid === true)) {
|
||||
step.value = 2
|
||||
password.value = ''
|
||||
firstPassword.value = ''
|
||||
nextTick(() => {
|
||||
verifyInputRef.value?.focusInput()
|
||||
})
|
||||
} else {
|
||||
errorMsg.value = '原密码错误,请重新输入'
|
||||
password.value = ''
|
||||
nextTick(() => {
|
||||
verifyInputRef.value?.focusInput()
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
errorMsg.value = '验证失败,请稍后重试'
|
||||
password.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function submitPassword(pwd) {
|
||||
const memberId = getMemberId()
|
||||
|
||||
if (!memberId) {
|
||||
uni.showToast({ title: '用户未登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '设置中...' })
|
||||
|
||||
try {
|
||||
let res
|
||||
if (isReset.value) {
|
||||
res = await resetPayPassword(memberId, firstPassword.value, pwd)
|
||||
} else {
|
||||
res = await setPayPassword(memberId, pwd)
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (res && (res.code === 200 || res.code === 200 || res.message === '设置成功' || res.message === '重置成功')) {
|
||||
uni.showToast({ title: isReset.value ? '修改成功' : '设置成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '设置失败', icon: 'none' })
|
||||
password.value = ''
|
||||
step.value = 1
|
||||
firstPassword.value = ''
|
||||
}
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '设置失败,请稍后重试', icon: 'none' })
|
||||
password.value = ''
|
||||
step.value = 1
|
||||
firstPassword.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function goForgotPassword() {
|
||||
uni.showToast({ title: '请联系客服重置密码', icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/common/style/base.css';
|
||||
@import '@/common/style/memberInfo/pages/page-reset.css';
|
||||
@import '@/common/style/memberInfo/pages/sub-page-base.css';
|
||||
|
||||
.set-pay-password-page {
|
||||
min-height: 100vh;
|
||||
background: #F5F7FA;
|
||||
}
|
||||
|
||||
.set-pay-password-page__body {
|
||||
padding: 40px 24px;
|
||||
}
|
||||
|
||||
.spp-intro {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.spp-intro__icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.spp-forgot {
|
||||
text-align: right;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.spp-forgot text {
|
||||
font-size: 13px;
|
||||
color: #1677FF;
|
||||
}
|
||||
</style>
|
||||
@@ -162,50 +162,38 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="Pixso-frame-2_858"></view>
|
||||
<view class="Pixso-frame-2_859">
|
||||
<view class="frame-content-2_859">
|
||||
<text class="Pixso-paragraph-2_860">微信</text>
|
||||
<text class="Pixso-paragraph-2_861">已授权绑定</text>
|
||||
<view class="stroke-wrapper-2_862">
|
||||
<view class="Pixso-frame-2_862">
|
||||
<text class="Pixso-paragraph-2_863">已绑定</text>
|
||||
<view class="Pixso-frame-2_864">
|
||||
<view class="frame-content-2_864">
|
||||
<text class="Pixso-paragraph-2_865">健身目标</text>
|
||||
<view class="goal-tags">
|
||||
<view
|
||||
v-for="goal in fitnessGoalOptions"
|
||||
:key="goal"
|
||||
class="goal-tag"
|
||||
:class="{ 'goal-tag--selected': isGoalSelected(goal) }"
|
||||
hover-class="mi-tap-btn--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="toggleGoal(goal)"
|
||||
>
|
||||
<text class="goal-tag__text">{{ goal }}</text>
|
||||
</view>
|
||||
<view class="stroke-2_862"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="Pixso-frame-2_864">
|
||||
<view class="frame-content-2_864">
|
||||
<text class="Pixso-paragraph-2_865">健身目标</text>
|
||||
<view class="goal-tags">
|
||||
<view
|
||||
v-for="goal in fitnessGoalOptions"
|
||||
:key="goal"
|
||||
class="goal-tag"
|
||||
:class="{ 'goal-tag--selected': isGoalSelected(goal) }"
|
||||
hover-class="mi-tap-btn--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="toggleGoal(goal)"
|
||||
>
|
||||
<text class="goal-tag__text">{{ goal }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="user-info-save-bar">
|
||||
<view
|
||||
class="user-info-save-bar__btn"
|
||||
hover-class="mi-tap-save--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="handleSave"
|
||||
>
|
||||
<text class="user-info-save-bar__text">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="user-info-save-bar">
|
||||
<view
|
||||
class="user-info-save-bar__btn"
|
||||
hover-class="mi-tap-save--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="handleSave"
|
||||
>
|
||||
<text class="user-info-save-bar__text">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -217,6 +205,7 @@ import MemberInfoSubNav from '@/components/memberInfo/MemberInfoSubNav.vue'
|
||||
import { getUserInfo, updateUserInfo } from '@/api/main.js'
|
||||
import { previewImage, persistChosenImage } from '@/common/memberInfo/media.js'
|
||||
import { maskPhone, normalizePhoneForStore } from '@/common/memberInfo/format.js'
|
||||
import { getOssUrl, OSS_BASE_URL } from '@/utils/request.js'
|
||||
import {
|
||||
validateName,
|
||||
validatePhoneForRebind,
|
||||
@@ -229,7 +218,8 @@ import {
|
||||
} from '@/common/memberInfo/validate.js'
|
||||
import { backToMemberCenter } from '@/common/constants/routes.js'
|
||||
|
||||
const DEFAULT_AVATAR = 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/AvatarEditWrap.png'
|
||||
const DEFAULT_AVATAR = getOssUrl('/static/headerImg/default.png')
|
||||
const DEFAULT_NAME = '活氧舱用户'
|
||||
|
||||
const name = ref('')
|
||||
const phone = ref('')
|
||||
@@ -278,7 +268,7 @@ function mapApiToProfile(apiData) {
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: apiData.nickname || '',
|
||||
name: apiData.nickname || DEFAULT_NAME,
|
||||
phone: apiData.phone || '',
|
||||
gender: genderMap[apiData.gender] || 'female',
|
||||
birthday: birthdayVal,
|
||||
|
||||
Reference in New Issue
Block a user