Files
gym-manage/gym-manage-uniapp/pages/memberInfo/courseList.vue
T
2026-06-11 14:26:49 +08:00

218 lines
9.2 KiB
Vue

<template>
<view class="scroll-container theme-light">
<view class="bt-page mi-course-list">
<MemberInfoSubNav title="预约课程" @back="goBack" />
<view class="mi-course-list__filters">
<view class="mi-course-list__date-bar">
<view
class="mi-course-list__mode"
hover-class="mi-tap--hover"
@tap="toggleDateMode"
>
<text class="mi-course-list__mode-text">{{ dateMode === 'day' ? '按天' : '按周' }}</text>
</view>
<scroll-view scroll-x class="mi-course-list__dates">
<view
v-for="d in dateOptions"
:key="d.value"
class="mi-course-list__date"
:class="{ 'mi-course-list__date--active': selectedDate === d.value }"
@tap="selectedDate = d.value"
>
<text class="mi-course-list__date-week">{{ d.week }}</text>
<text class="mi-course-list__date-day">{{ d.day }}</text>
</view>
</scroll-view>
</view>
<scroll-view scroll-x class="mi-course-list__chips">
<view
v-for="t in typeOptions"
:key="t.key"
class="bt-tab"
:class="{ 'bt-tab--active': courseType === t.key }"
@tap="courseType = t.key"
>
<text class="bt-tab__text">{{ t.label }}</text>
</view>
</scroll-view>
<view class="mi-course-list__row">
<picker :range="coaches" @change="onCoachChange">
<view class="mi-course-list__picker">
<text>{{ coach }}</text>
<image class="mi-course-list__arrow" src="/static/images/chevronright3.png" mode="aspectFit" />
</view>
</picker>
<picker :range="periodLabels" @change="onPeriodChange">
<view class="mi-course-list__picker">
<text>{{ periodLabel }}</text>
<image class="mi-course-list__arrow" src="/static/images/chevronright3.png" mode="aspectFit" />
</view>
</picker>
</view>
</view>
<view class="bt-page__body">
<view
v-for="course in displayCourses"
:key="course.id"
class="mi-course-card"
hover-class="mi-tap-card--hover"
@tap="openDetail(course)"
>
<image class="mi-course-card__banner" :src="course.banner" mode="aspectFill" />
<view class="mi-course-card__body">
<view class="mi-course-card__head">
<text class="mi-course-card__title">{{ course.title }}</text>
<view class="mi-course-card__type" :class="'mi-course-card__type--' + course.type">
<text>{{ course.type === 'group' ? '团课' : '私教' }}</text>
</view>
</view>
<view class="mi-course-card__coach">
<image class="mi-course-card__avatar" :src="course.coachAvatar" mode="aspectFill" />
<text>{{ course.coach }}</text>
</view>
<text class="mi-course-card__meta">
{{ course.startTime }}-{{ course.endTime }} · {{ course.location }}
</text>
<view class="mi-course-card__capacity">
<view class="mi-course-card__bar">
<view class="mi-course-card__bar-fill" :style="{ width: course.percent + '%' }"></view>
</view>
<text class="mi-course-card__cap-text">{{ course.enrolled }}/{{ course.capacity }}</text>
<text v-if="course.scarcityLabel" class="mi-course-card__scarcity">{{ course.scarcityLabel }}</text>
</view>
<view class="mi-course-card__footer">
<text class="mi-course-card__price">{{ course.price }}</text>
<view
class="mi-course-card__btn"
:class="{ 'mi-course-card__btn--disabled': course.full }"
@tap.stop="quickBook(course)"
>
<text>{{ course.full ? '已约满' : '预约' }}</text>
</view>
</view>
</view>
</view>
<view v-if="!displayCourses.length" class="bt-empty">
<text class="bt-empty__text">暂无符合条件的课程</text>
</view>
</view>
<view
class="mi-course-list__fab"
hover-class="mi-tap-btn--hover"
@tap="goMyBooking"
>
<image class="mi-course-list__fab-icon" src="/static/images/clock.png" mode="aspectFit" />
<text class="mi-course-list__fab-text">我的预约</text>
</view>
</view>
</view>
</template>
<script>
import MemberInfoSubNav from '@/components/memberInfo/MemberInfoSubNav.vue'
import { PAGE, navigateToPage } from '@/common/constants/routes.js'
import { loadMemberStore, persistMemberStore } from '@/common/memberInfo/store.js'
import {
courseCatalogMock,
filterCourses,
enrichCourseForDisplay,
getWeekDates,
bookCourse
} from '@/common/memberInfo/bookingStore.js'
import { subPageMixin } from '@/common/memberInfo/mixins.js'
const WEEK = ['日', '一', '二', '三', '四', '五', '六']
export default {
components: { MemberInfoSubNav },
mixins: [subPageMixin],
data() {
return {
dateMode: 'day',
selectedDate: '2024-07-15',
courseType: 'all',
coach: '全部',
period: 'all',
coaches: courseCatalogMock.coaches,
typeOptions: courseCatalogMock.typeOptions,
periodOptions: courseCatalogMock.periodOptions,
courses: []
}
},
computed: {
periodLabels() {
return this.periodOptions.map((p) => p.label)
},
periodLabel() {
return this.periodOptions.find((p) => p.key === this.period)?.label || '全部时段'
},
weekDates() {
return getWeekDates(this.selectedDate)
},
dateOptions() {
const dates = this.dateMode === 'week' ? this.weekDates : this.weekDates
return dates.map((value) => {
const d = new Date(value.replace(/-/g, '/'))
return {
value,
week: WEEK[d.getDay()],
day: `${d.getMonth() + 1}/${d.getDate()}`
}
})
},
displayCourses() {
const filters = {
date: this.dateMode === 'day' ? this.selectedDate : '',
weekDates: this.dateMode === 'week' ? this.weekDates : [],
type: this.courseType,
coach: this.coach,
period: this.period
}
return filterCourses(this.courses, filters).map(enrichCourseForDisplay)
}
},
onShow() {
const store = loadMemberStore()
this.courses = store.courseCatalog.map((c) => ({ ...c }))
if (!this.selectedDate && this.courses.length) {
this.selectedDate = this.courses[0].date
}
},
methods: {
toggleDateMode() {
this.dateMode = this.dateMode === 'day' ? 'week' : 'day'
},
onCoachChange(e) {
this.coach = this.coaches[e.detail.value]
},
onPeriodChange(e) {
this.period = this.periodOptions[e.detail.value].key
},
openDetail(course) {
navigateToPage(`${PAGE.COURSE_DETAIL}?id=${course.id}`)
},
quickBook(course) {
if (course.full) return
navigateToPage(`${PAGE.COURSE_DETAIL}?id=${course.id}`)
},
goMyBooking() {
navigateToPage(PAGE.BOOKING)
}
}
}
</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';
@import '@/common/style/memberInfo/pages/course-list-page.css';
</style>