@@ -1,98 +1,54 @@
< template >
< view class = "quick-entry" >
<!-- ① Not logged in -- >
< view v-if = "!userStore.loggedIn" class="entry-card login-card " @tap="handleLogin" >
< view class = "entry-content" >
< view class = "entry-left" >
< view class = "entry-icon-wrap login-ico n" >
< view class = "icon-user" / >
< / view >
< view class = "entry-text" >
< text class = "entry-title" > 欢迎来到工作室 < / text >
< text class = "entry-subtitle" > 登录后即可预约课程 < / text >
< / view >
< / view >
< view class = "entry-btn login-btn" >
< text class = "entry-btn-text" > 微信登录 < / text >
< / view >
< view v-if = "!userStore.loggedIn" class="entry-pill pill-login " @tap="handleLogin" >
< view class = "pill-dot dot-login" / >
< text class = "pill-label" > 欢迎来到工作室 < / text >
< view class = "pill-action action-logi n" >
< text class = "pill-action-text" > 微信登录 < / text >
< / view >
< / view >
<!-- ② Logged in , no memberships at all → new user -- >
<!-- ② Logged in , no memberships → new user -- >
< view
v-else-if = "userStore.loggedIn && userStore.memberships.length === 0"
class = "entry-card trial-card "
class = "entry-pill pill-trial "
@tap ="handleTrialEntry"
>
< view class = "entry-content" >
< view class = "entry-left" >
< view class = "entry-icon-wrap trial-icon " >
< view class = "icon-star" / >
< / view >
< view class = "entry-text" >
< text class = "entry-title" > 初次体验 < / text >
< text class = "entry-subtitle" > 专属体验课 , 了解普拉提 < / text >
< / view >
< / view >
< view class = "entry-btn trial-btn" >
< text class = "entry-btn-text" > 预约体验课 < / text >
< / view >
< view class = "pill-tag tag-trial" > 体验 < / view >
< text class = "pill-label" > 首次体验专属课程 < / text >
< view class = "pill-action action-trial " >
< text class = "pill-action-text" > 预约体验课 < / text >
< / view >
< view class = "card-badge trial-badge" > 新会员专享 < / view >
< / view >
<!-- ③ Has valid active card + running low warning -->
<!-- ③ Has valid active card -- >
< template v-else-if = "userStore.hasValidMembership" >
< view class = "entry-card active-card " @tap ="handleBooking" >
< view class = "entry-content" >
< view class = "entry-left" >
< view class = "entry-icon-wrap active-icon " >
< view class = "icon-clock" / >
< / view >
< view class = "entry-text" >
< text class = "entry-title" > 一键约课 < / text >
< text class = "entry-subtitle" > { { activeMembershipLabel } } < / text >
< / view >
< / view >
< view class = "entry-btn book-btn" >
< text class = "entry-btn-text" > 立即预约 < / text >
< / view >
< / view >
<!-- Running low badge -- >
< view v-if = "isRunningLow" class="card-badge low-badge" >
仅剩 {{ lowestRemainingTimes }} 次
< view class = "entry-pill pill- active" @tap ="handleBooking" >
< view class = "pill-dot dot-active" / >
< text class = "pill-label pill-label-active" > { { activeMembershipLabel } } < / text >
< view class = "pill-action action-book " >
< text class = "pill-action-text" > 约课 < / text >
< / view >
< / view >
< ! - - Renew reminder if running low - - >
< view v-if = "isRunningLow" class="renew-t ip" @tap="scrollToCardShop" >
< view class = "renew-tip-icon" >
< view class = "icon-warning" / >
< / view >
< text class = "renew-tip-text" > 课次即将用完 , 点击续卡保持练习节奏 < / text >
< text class = "renew-tip-action" > 续卡 › < / text >
<!-- Running low : thin accent strip -- >
< view v-if = "isRunningLow" class="renew-str ip" @tap="scrollToCardShop" >
< text class = "renew-strip-text" > 仅剩 { { lowestRemainingTimes } } 次 · 续卡保持节奏 < / text >
< text class = "renew-strip-arrow" > › < / text >
< / view >
< / template >
<!-- ④ Has memberships but none active → buy card -- >
< view
v-else
class = "entry-card expired-card "
class = "entry-pill pill- expired"
@tap ="scrollToCardShop"
>
< view class = "entry-content" >
< view class = "entry-left" >
< view class = "entry-icon-wrap expired-icon " >
< view class = "icon-card" / >
< / view >
< view class = "entry-text" >
< text class = "entry-title" > 续费会员卡 < / text >
< text class = "entry-subtitle" > 您的卡已到期 , 续卡继续练习 < / text >
< / view >
< / view >
< view class = "entry-btn renew-btn" >
< text class = "entry-btn-text" > 购买会员卡 < / text >
< / view >
< view class = "pill-dot dot-expired" / >
< text class = "pill-label" > 会员卡已到期 < / text >
< view class = "pill-action action-renew " >
< text class = "pill-action-text" > 续卡 < / text >
< / view >
< / view >
< / view >
@@ -123,7 +79,6 @@ async function handleLogin() {
}
function handleTrialEntry ( ) {
// Navigate to the first TRIAL card detail page
uni . navigateTo ( { url : '/pages/card/detail?trial=1' } )
}
@@ -135,22 +90,20 @@ function scrollToCardShop() {
emit ( 'scroll-to-card-shop' )
}
// Computed: label for the active membership
const activeMembershipLabel = computed ( ( ) => {
const active = userStore . activeMemberships
if ( ! active . length ) return ''
const m = active [ 0 ]
const cardName = m . cardType . name
if ( m . cardType . type === CardTypeCategory . TIMES && m . remainingTimes !== null ) {
return ` ${ cardName } · 剩余 ${ m . remainingTimes } 次 `
return ` ${ cardName } · 剩余 ${ m . remainingTimes } 次 `
}
const expire = new Date ( m . expireDate )
const today = new Date ( )
const daysLeft = Math . ceil ( ( expire . getTime ( ) - today . getTime ( ) ) / 86400000 )
return ` ${ cardName } · 剩余 ${ daysLeft } 天 `
return ` ${ cardName } · 剩余 ${ daysLeft } 天 `
} )
// Check if any TIMES card has ≤ 2 remaining
const isRunningLow = computed ( ( ) => {
return userStore . activeMemberships . some (
( m ) =>
@@ -174,358 +127,159 @@ const lowestRemainingTimes = computed(() => {
< style lang = "scss" scoped >
. quick - entry {
marg in: 24 rpx 24 rpx 0 ;
padd ing : 20 rpx 24 rpx 0 ;
}
. entry - card {
position : relative ;
border - radius : 16 rp x;
padding : 36 rpx 32 rpx ;
box - shadow : 0 4 rpx 24 rpx rgba ( 0 , 0 , 0 , 0.08 ) ;
overflow : hidden ;
/* ── Pill base ── */
. entry - pill {
display : fle x;
align - items : center ;
height : 8 0rpx ;
border - radius : 40 rpx ;
padding : 0 8 rpx 0 24 rpx ;
gap : 16 rpx ;
box - shadow : 0 2 rpx 12 rpx rgba ( 0 , 0 , 0 , 0.06 ) ;
}
. login - card {
background : linear - gradient ( 135 deg , # 1 a1a2e 0 % , # 2 d2d5e 100 % ) ;
/* ── Pill variants ── */
. pill - login {
background : # 1 a1a2e ;
}
. tria l- card {
. pil l- trial {
background : linear - gradient ( 135 deg , # 2 d2d5e 0 % , # 4 a3f7a 100 % ) ;
}
. active - card {
background : linear - gradient ( 135 deg , # 2 a3a4a 0 % , # 1 a2a3a 100 % ) ;
. pill - active {
background : # ffffff ;
border : 1 rpx solid rgba ( 0 , 0 , 0 , 0.06 ) ;
box - shadow : 0 2 rpx 16 rpx rgba ( 0 , 0 , 0 , 0.05 ) ;
}
. expired - car d {
background : linear - gradient ( 135 deg , # 4 a4a4a 0 % , # 2 a2a2a 100 % ) ;
. pill - expire d {
background : # f5f5f5 ;
border : 1 rpx solid rgba ( 0 , 0 , 0 , 0.04 ) ;
box - shadow : none ;
}
. entry - content {
display : flex ;
align - items : center ;
justify - content : space - between ;
gap : 24 rpx ;
}
. entry - left {
display : flex ;
align - items : center ;
gap : 28 rpx ;
flex : 1 ;
}
. entry - icon - wrap {
width : 88 rpx ;
height : 88 rpx ;
/* ── Status dot ── */
. pill - dot {
width : 14 rpx ;
height : 14 rpx ;
border - radius : 50 % ;
display : flex ;
align - items : center ;
justify - content : center ;
flex - shrink : 0 ;
position : relative ;
}
. login - ico n {
background : rgba ( 255 , 255 , 255 , 0.12 ) ;
. dot - logi n {
background : $primary - color ;
box - shadow : 0 0 8 rpx rgba ( $primary - color , 0.6 ) ;
}
. trial - icon {
background : rgba ( 255 , 215 , 0 , 0.2 ) ;
. dot - active {
background : # 34 c759 ;
box - shadow : 0 0 8 rpx rgba ( 52 , 199 , 89 , 0.5 ) ;
}
. active - icon {
background : rgba ( 168 , 196 , 206 , 0.25 ) ;
. dot - expired {
background : # aaa ;
}
. expired - icon {
background : rgba ( 255 , 255 , 255 , 0.12 ) ;
}
/* ── Icon shapes (pure CSS) ── */
/* User icon: head + shoulders */
. icon - user {
position : relative ;
width : 36 rpx ;
height : 36 rpx ;
& : : before {
content : '' ;
position : absolute ;
top : 0 ;
left : 50 % ;
transform : translateX ( - 50 % ) ;
width : 20 rpx ;
height : 20 rpx ;
border - radius : 50 % ;
background : # fff ;
}
& : : after {
content : '' ;
position : absolute ;
bottom : 0 ;
left : 50 % ;
transform : translateX ( - 50 % ) ;
width : 28 rpx ;
height : 14 rpx ;
border - radius : 14 rpx 14 rpx 0 0 ;
background : # fff ;
}
}
/* Star icon - diamond shape */
. icon - star {
position : relative ;
width : 32 rpx ;
height : 32 rpx ;
& : : before {
content : '' ;
position : absolute ;
top : 50 % ;
left : 50 % ;
transform : translate ( - 50 % , - 50 % ) rotate ( 45 deg ) ;
width : 24 rpx ;
height : 24 rpx ;
background : # ffd700 ;
}
}
/* Clock icon - circle with dot */
. icon - clock {
position : relative ;
width : 36 rpx ;
height : 36 rpx ;
border - radius : 50 % ;
border : 3 rpx solid # fff ;
& : : before {
content : '' ;
position : absolute ;
top : 50 % ;
left : 50 % ;
transform : translate ( - 50 % , - 50 % ) ;
width : 8 rpx ;
height : 8 rpx ;
border - radius : 50 % ;
background : # fff ;
}
}
/* Card icon */
. icon - card {
position : relative ;
width : 36 rpx ;
height : 26 rpx ;
border - radius : 4 rpx ;
border : 3 rpx solid # fff ;
& : : before {
content : '' ;
position : absolute ;
top : 50 % ;
left : 50 % ;
transform : translate ( - 50 % , - 50 % ) ;
width : 12 rpx ;
height : 6 rpx ;
border - radius : 2 rpx ;
background : # fff ;
}
}
/* 闪电 icon — 更有能量感 */
. icon - warning {
position : relative ;
width : 22 rpx ;
height : 22 rpx ;
& : : before {
content : '' ;
position : absolute ;
top : 0 ;
left : 50 % ;
transform : translateX ( - 50 % ) rotate ( - 15 deg ) ;
width : 0 ;
height : 0 ;
border - left : 8 rpx solid transparent ;
border - right : 8 rpx solid transparent ;
border - bottom : 18 rpx solid # ffffff ;
}
& : : after {
content : '' ;
position : absolute ;
bottom : 2 rpx ;
left : 50 % ;
transform : translateX ( - 50 % ) ;
width : 10 rpx ;
height : 3 rpx ;
background : rgba ( 255 , 255 , 255 , 0.5 ) ;
border - radius : 2 rpx ;
}
}
. entry - text {
flex : 1 ;
min - width : 0 ;
}
. entry - title {
display : block ;
font - size : 34 rpx ;
font - weight : 600 ;
color : # ffffff ;
margin - bottom : 8 rpx ;
/* ── Tag (trial only) ── */
. pill - tag {
font - size : 20 rpx ;
font - weight : 700 ;
padding : 4 rpx 14 rpx ;
border - radius : 20 rpx ;
flex - shrink : 0 ;
letter - spacing : 1 rpx ;
}
. entry - subtitle {
display : block ;
font - size : 24 rpx ;
color : rgba ( 255 , 255 , 255 , 0.6 ) ;
line - height : 1.4 ;
. tag - trial {
background : rgba ( 255 , 215 , 0 , 0.25 ) ;
color : # ffd700 ;
}
. entry - btn {
/* ── Label text ── */
. pill - label {
flex : 1 ;
font - size : 26 rpx ;
font - weight : 500 ;
color : rgba ( 255 , 255 , 255 , 0.85 ) ;
overflow : hidden ;
text - overflow : ellipsis ;
white - space : nowrap ;
line - height : 1 ;
}
. pill - label - active {
color : # 333 ;
}
. pill - expired . pill - label {
color : # 888 ;
}
/* ── Action button ── */
. pill - action {
flex - shrink : 0 ;
padding : 18 rpx 36 rpx ;
border - radius : 40 rpx ;
height : 60 rpx ;
padding : 0 28 rpx ;
border - radius : 30 rpx ;
display : flex ;
align - items : center ;
justify - content : center ;
position : relative ;
overflow : hidden ;
& : : before {
content : '' ;
position : absolute ;
inset : 0 ;
background : linear - gradient ( 180 deg , rgba ( 255 , 255 , 255 , 0.2 ) 0 % , transparent 100 % ) ;
opacity : 0.5 ;
}
}
. entry - bt n- text {
font - size : 28 rpx ;
. pill - actio n- text {
font - size : 24 rpx ;
font - weight : 600 ;
white - space : nowrap ;
position : relative ;
z - index : 1 ;
line - height : 1 ;
}
. logi n- btn ,
. trial - btn ,
. book - btn {
. actio n- login {
background : $primary - color ;
. pill - action - text { color : # 1 a1a2e ; }
}
. renew - btn {
background : # 666 ;
. action - trial {
background : rgba ( 255 , 215 , 0 , 0.2 ) ;
. pill - action - text { color : # ffd700 ; }
}
. logi n- btn . entry - btn - text ,
. trial - btn . entry - btn - text ,
. book - btn . entry - btn - text ,
. renew - btn . entry - btn - text {
color : # 1 a1a2e ;
. actio n- book {
background : # 1 a1a2e ;
. pill - action - text { color : # fff ; }
}
/* Corner badge */
. card - badge {
position : absolute ;
top : 0 ;
right : 0 ;
padding : 8 rpx 20 rpx ;
font - size : 20 rpx ;
font - weight : 600 ;
border - radius : 0 16 rpx 0 16 rpx ;
. action - renew {
background : # e0e0e0 ;
. pill - action - text { color : # 555 ; }
}
. trial - badge {
background : linear - gradient ( 135 deg , # ffd700 , # ffaa00 ) ;
color : # 1 a1a2e ;
}
. low - badge {
background : linear - gradient ( 135 deg , # FF6B35 0 % , # FF8E53 100 % ) ;
color : # ffffff ;
box - shadow : 0 4 rpx 12 rpx rgba ( 255 , 107 , 53 , 0.35 ) ;
}
/* Renew tip bar — 充满能量的激励配色 */
. renew - tip {
display : flex ;
align - items : center ;
gap : 16 rpx ;
margin - top : 16 rpx ;
padding : 22 rpx 28 rpx ;
background : linear - gradient ( 135 deg , # FF6B35 0 % , # FF8E53 60 % , # FFAA5C 100 % ) ;
border - radius : 16 rpx ;
box - shadow : 0 6 rpx 20 rpx rgba ( 255 , 107 , 53 , 0.3 ) ;
position : relative ;
overflow : hidden ;
/* 右上角光晕装饰 */
& : : before {
content : '' ;
position : absolute ;
top : - 20 rpx ;
right : - 20 rpx ;
width : 120 rpx ;
height : 120 rpx ;
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 : 52 rpx ;
height : 52 rpx ;
border - radius : 50 % ;
background : rgba ( 255 , 255 , 255 , 0.2 ) ;
/* ── Renew strip (running low) ── */
. renew - strip {
display : flex ;
align - items : center ;
justify - content : center ;
flex - shrink : 0 ;
position : relative ;
z - index : 1 ;
backd rop - filter : blur ( 4 rpx ) ;
gap : 8 rpx ;
margin - top : 12 rpx ;
padding : 14 rpx 24 rpx ;
backg round : linear - gradient ( 135 deg , # FF6B35 , # FF8E53 ) ;
border - radius : 24 rpx ;
}
. renew - t ip- text {
flex : 1 ;
font - size : 26 rpx ;
color : # ffffff ;
line - height : 1.5 ;
. renew - str ip- text {
font - size : 22 rpx ;
font - weight : 500 ;
position : relative ;
z - index : 1 ;
text - shadow : 0 1 rpx 2 rpx rgba ( 0 , 0 , 0 , 0.1 ) ;
color : # fff ;
letter - spacing : 0.5 rpx ;
}
. renew - t ip- action {
font - size : 26 rpx ;
color : # ffffff ;
. renew - str ip- arrow {
font - size : 28 rpx ;
font - weight : 700 ;
flex - shrink : 0 ;
position : relative ;
z - index : 1 ;
letter - spacing : 1 rpx ;
/* 箭头增强感 */
text - shadow : 0 1 rpx 2 rpx rgba ( 0 , 0 , 0 , 0.15 ) ;
color : rgba ( 255 , 255 , 255 , 0.8 ) ;
line - height : 1 ;
}
< / style >