perf: 完善新用户引导

This commit is contained in:
richarjiang
2026-04-06 15:50:22 +08:00
parent 66d47ec162
commit f8268cb6f6
15 changed files with 483 additions and 119 deletions

View File

@@ -114,10 +114,7 @@ async function handleLogin() {
if (loading.value) return
loading.value = true
try {
await userStore.login()
await userStore.fetchMemberships()
// 登录成功后跳转到个人中心,让用户完善信息
uni.navigateTo({ url: '/pages/profile/info' })
await userStore.loginWithSetup()
} catch {
uni.showToast({ title: '登录失败,请重试', icon: 'none' })
} finally {
@@ -338,14 +335,36 @@ const lowestRemainingTimes = computed(() => {
}
}
/* Warning triangle */
/* 闪电 icon — 更有能量感 */
.icon-warning {
position: relative;
width: 0;
height: 0;
border-left: 12rpx solid transparent;
border-right: 12rpx solid transparent;
border-bottom: 20rpx solid #e8a87c;
width: 22rpx;
height: 22rpx;
&::before {
content: '';
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%) rotate(-15deg);
width: 0;
height: 0;
border-left: 8rpx solid transparent;
border-right: 8rpx solid transparent;
border-bottom: 18rpx solid #ffffff;
}
&::after {
content: '';
position: absolute;
bottom: 2rpx;
left: 50%;
transform: translateX(-50%);
width: 10rpx;
height: 3rpx;
background: rgba(255, 255, 255, 0.5);
border-radius: 2rpx;
}
}
.entry-text {
@@ -430,42 +449,83 @@ const lowestRemainingTimes = computed(() => {
}
.low-badge {
background: #e74c3c;
background: linear-gradient(135deg, #FF6B35 0%, #FF8E53 100%);
color: #ffffff;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.35);
}
/* Renew tip bar */
/* Renew tip bar — 充满能量的激励配色 */
.renew-tip {
display: flex;
align-items: center;
gap: 12rpx;
gap: 16rpx;
margin-top: 16rpx;
padding: 20rpx 24rpx;
background: #fff8f0;
border-radius: 12rpx;
border: 1rpx solid rgba(240, 180, 100, 0.3);
padding: 22rpx 28rpx;
background: linear-gradient(135deg, #FF6B35 0%, #FF8E53 60%, #FFAA5C 100%);
border-radius: 16rpx;
box-shadow: 0 6rpx 20rpx rgba(255, 107, 53, 0.3);
position: relative;
overflow: hidden;
/* 右上角光晕装饰 */
&::before {
content: '';
position: absolute;
top: -20rpx;
right: -20rpx;
width: 120rpx;
height: 120rpx;
background: radial-gradient(circle, rgba(255, 255, 255, 0.25) 0%, transparent 70%);
pointer-events: none;
}
/* 底部微光 */
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 40%;
background: linear-gradient(to top, rgba(0, 0, 0, 0.08) 0%, transparent 100%);
pointer-events: none;
}
}
.renew-tip-icon {
width: 36rpx;
height: 36rpx;
width: 52rpx;
height: 52rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
z-index: 1;
backdrop-filter: blur(4rpx);
}
.renew-tip-text {
flex: 1;
font-size: 24rpx;
color: #a0622a;
line-height: 1.4;
font-size: 26rpx;
color: #ffffff;
line-height: 1.5;
font-weight: 500;
position: relative;
z-index: 1;
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.1);
}
.renew-tip-action {
font-size: 24rpx;
color: $primary-dark;
font-weight: 600;
font-size: 26rpx;
color: #ffffff;
font-weight: 700;
flex-shrink: 0;
position: relative;
z-index: 1;
letter-spacing: 1rpx;
/* 箭头增强感 */
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.15);
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<view class="slot-card" :class="{ 'slot-card--booked': timeSlot.isBookedByMe }">
<view class="slot-card" :class="{ 'slot-card--booked': timeSlot.isBookedByMe, 'slot-card--past': isPast && !timeSlot.isBookedByMe }">
<!-- Booked accent bar -->
<view v-if="timeSlot.isBookedByMe" class="booked-bar" />
@@ -27,8 +27,14 @@
<!-- Right: Action -->
<view class="slot-action">
<template v-if="isPast && !timeSlot.isBookedByMe">
<view class="badge-expired">
<text class="badge-expired-text">已过期</text>
</view>
</template>
<!-- OPEN + not booked -->
<template v-if="timeSlot.status === TimeSlotStatus.OPEN && !timeSlot.isBookedByMe">
<template v-else-if="timeSlot.status === TimeSlotStatus.OPEN && !timeSlot.isBookedByMe">
<view class="btn btn-book" @tap.stop="emit('book', timeSlot)">
<text class="btn-text">预约</text>
</view>
@@ -68,6 +74,7 @@
import type { TimeSlotWithBookingStatus } from '@mp-pilates/shared'
import { TimeSlotStatus } from '@mp-pilates/shared'
import { computed } from 'vue'
import { isSlotPast } from '../utils/format'
interface Props {
timeSlot: TimeSlotWithBookingStatus
@@ -100,6 +107,8 @@ const capacityClass = computed(() => {
if (bookedCount >= capacity * 0.8) return 'cap-almost'
return 'cap-open'
})
const isPast = computed(() => isSlotPast(props.timeSlot.date, props.timeSlot.startTime))
</script>
<style lang="scss" scoped>
@@ -119,6 +128,19 @@ const capacityClass = computed(() => {
background: #f0f7fb;
box-shadow: 0 4rpx 24rpx rgba($primary-dark, 0.12);
}
&--past {
background: #f8f8f8;
opacity: 0.7;
.slot-start {
color: #bbb;
}
.slot-title {
color: #bbb;
}
}
}
.booked-bar {
@@ -308,6 +330,22 @@ const capacityClass = computed(() => {
}
}
.badge-expired {
height: 52rpx;
padding: 0 24rpx;
background: #f0f0f0;
border-radius: 26rpx;
display: flex;
align-items: center;
justify-content: center;
.badge-expired-text {
font-size: 24rpx;
color: #999;
font-weight: 600;
}
}
.btn-cancel {
padding: 4rpx 8rpx;
display: flex;