会员个人中心页面初步完成

This commit is contained in:
时舟年
2026-06-04 14:18:53 +08:00
parent 8e7c8f52f6
commit 7350293d0e
170 changed files with 18092 additions and 35 deletions
@@ -0,0 +1,225 @@
<template>
<view class="scroll-container theme-light">
<view class="bt-page">
<MemberInfoSubNav title="我的课程" @back="goBack" />
<view class="mi-mod-tabs">
<view
v-for="tab in tabs"
:key="tab.key"
class="bt-tab"
:class="{ 'bt-tab--active': activeTab === tab.key }"
hover-class="mi-tap-tab--hover"
@tap="activeTab = tab.key"
>
<text class="bt-tab__text">{{ tab.label }}</text>
</view>
</view>
<view class="bt-page__body">
<!-- 团课 -->
<template v-if="activeTab === 'group'">
<view class="mi-mod-subtabs">
<text
class="mi-mod-subtab"
:class="{ 'mi-mod-subtab--on': groupSub === 'ongoing' }"
@tap="groupSub = 'ongoing'"
>进行中</text>
<text
class="mi-mod-subtab"
:class="{ 'mi-mod-subtab--on': groupSub === 'completed' }"
@tap="groupSub = 'completed'"
>已完成</text>
</view>
<view
v-for="item in groupList"
:key="item.id"
class="mi-mod-course-card"
hover-class="mi-tap-card--hover"
@tap="onGroupCourse(item)"
>
<image class="mi-mod-course-card__banner" :src="item.banner" mode="aspectFill" />
<view class="mi-mod-course-card__content">
<text class="mi-mod-course-card__title">{{ item.title }}</text>
<text class="mi-mod-course-card__coach">{{ item.coach }}</text>
<view class="mi-mod-course-card__progress">
<view class="mi-mod-course-card__progress-bar">
<view class="mi-mod-course-card__progress-fill" :style="{ width: pct(item) + '%' }"></view>
</view>
<text class="mi-mod-course-card__progress-text">{{ item.progress }}/{{ item.total }}</text>
</view>
<text class="mi-mod-course-card__meta">{{ item.schedule }} · {{ item.location }}</text>
<view v-if="groupSub === 'ongoing' && item.canCancel" class="mi-mod-course-card__action" @tap.stop="goBooking">
<text>取消预约</text>
</view>
<view v-if="groupSub === 'completed' && item.canEvaluate" class="mi-mod-course-card__action" @tap.stop="evaluate(item)">
<text>去评价</text>
</view>
</view>
</view>
</template>
<!-- 私教 -->
<template v-else-if="activeTab === 'private'">
<view class="bt-card">
<text class="bt-card__title">剩余课时 {{ privateData.remaining }} </text>
<view class="mi-detail-coach">
<image class="mi-detail-coach__avatar" :src="privateData.coachAvatar" mode="aspectFill" />
<view>
<text class="mi-detail-coach__name">{{ privateData.coach }}</text>
<text class="mi-detail-coach__rating">下次 {{ privateData.nextClass }}</text>
</view>
</view>
</view>
<view class="bt-card">
<text class="bt-card__title">已约课程</text>
<view v-for="b in privateData.bookings" :key="b.id" class="mi-mod-session">
<text class="mi-mod-session__title">{{ b.title }}</text>
<text class="mi-mod-session__meta">{{ b.time }} · {{ b.location }} · {{ b.status }}</text>
</view>
</view>
</template>
<!-- 线上课 -->
<template v-else-if="activeTab === 'online'">
<view
v-for="item in onlineList"
:key="item.id"
class="mi-mod-course-card"
hover-class="mi-tap-card--hover"
@tap="goOnline(item)"
>
<image class="mi-mod-course-card__banner" :src="item.cover" mode="aspectFill" />
<view class="mi-mod-course-card__content">
<text class="mi-mod-course-card__title">{{ item.title }}</text>
<text class="mi-mod-course-card__meta">{{ item.duration }} · 进度 {{ item.progress }}%</text>
<text v-if="item.type === 'live'" class="mi-mod-course-card__next">直播 {{ item.liveTime }}</text>
</view>
</view>
</template>
<!-- 训练营 -->
<template v-else>
<view
v-for="item in packageList"
:key="item.id"
class="mi-mod-course-card"
>
<image class="mi-mod-course-card__banner" :src="item.banner" mode="aspectFill" />
<view class="mi-mod-course-card__content">
<text class="mi-mod-course-card__title">{{ item.title }}</text>
<text class="mi-mod-course-card__coach">{{ item.coach }}</text>
<view class="mi-mod-course-card__progress">
<view class="mi-mod-course-card__progress-bar">
<view class="mi-mod-course-card__progress-fill" :style="{ width: pct(item) + '%' }"></view>
</view>
<text class="mi-mod-course-card__progress-text">{{ item.progress }}/{{ item.total }} </text>
</view>
<text class="mi-mod-course-card__meta">{{ item.schedule }}</text>
</view>
</view>
</template>
<view v-if="isEmpty" class="bt-empty">
<text class="bt-empty__text">暂无课程</text>
</view>
</view>
</view>
</view>
</template>
<script>
import MemberInfoSubNav from '@/components/memberInfo/MemberInfoSubNav.vue'
import { PAGE, navigateToPage } from '@/common/constants/routes.js'
import { moduleMock, getMyCoursesData } from '@/common/memberInfo/moduleStore.js'
import { loadMemberStore } from '@/common/memberInfo/store.js'
import { subPageMixin } from '@/common/memberInfo/mixins.js'
export default {
components: { MemberInfoSubNav },
mixins: [subPageMixin],
data() {
return {
tabs: moduleMock.myCourseTabs,
activeTab: 'group',
groupSub: 'ongoing',
privateData: {},
onlineList: [],
packageList: [],
groupData: { ongoing: [], completed: [] }
}
},
computed: {
groupList() {
return this.groupData[this.groupSub] || []
},
isEmpty() {
if (this.activeTab === 'group') return !this.groupList.length
if (this.activeTab === 'private') return !this.privateData.bookings?.length
if (this.activeTab === 'online') return !this.onlineList.length
return !this.packageList.length
}
},
onShow() { this.refresh() },
methods: {
refresh() {
const store = loadMemberStore()
this.groupData = getMyCoursesData(store, 'group')
this.privateData = getMyCoursesData(store, 'private')
this.onlineList = getMyCoursesData(store, 'online').list || []
this.packageList = getMyCoursesData(store, 'package').list || []
},
pct(item) {
return item.total ? Math.min(100, Math.round((item.progress / item.total) * 100)) : 0
},
goBooking() { navigateToPage(PAGE.BOOKING) },
goOnline(item) { navigateToPage(`${PAGE.ONLINE_COURSE}?id=${item.id}`) },
evaluate(item) {
navigateToPage(`${PAGE.COURSE_EVALUATE}?title=${encodeURIComponent(item.title)}`)
},
onGroupCourse(item) {
uni.showModal({
title: item.title,
content: `${item.coach}\n${item.schedule}\n${item.location}`,
showCancel: false
})
}
}
}
</script>
<style>
@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';
@import '@/common/style/memberInfo/member-info-tap.css';
@import '@/common/style/memberInfo/pages/body-test-common.css';
@import '@/common/style/memberInfo/pages/module-pages-common.css';
.mi-mod-subtabs {
display: flex;
flex-direction: row;
gap: 16px;
margin-bottom: 12px;
}
.mi-mod-subtab {
font-size: 14px;
color: var(--text-muted, #5E6F8D);
}
.mi-mod-subtab--on {
color: var(--primary-dark, #0B2B4B);
font-weight: 700;
}
.mi-mod-course-card__action {
margin-top: 6px;
}
.mi-mod-course-card__action text {
font-size: 12px;
font-weight: 600;
color: var(--accent-orange, #FF6B35);
}
</style>