完善团课前后端交互
This commit is contained in:
@@ -45,6 +45,40 @@ export function getTypeLabels(typeId, options = { cache: true, cacheTime: 5 * 60
|
||||
return request.get(`/groupCourse/types/${typeId}/labels`, {}, options)
|
||||
}
|
||||
|
||||
export function searchGroupCourse(params = {}, options = {}) {
|
||||
const {
|
||||
courseName,
|
||||
courseType,
|
||||
startDate,
|
||||
endDate,
|
||||
timePeriod,
|
||||
priceSort,
|
||||
remainingMost,
|
||||
page = 0,
|
||||
size = 10
|
||||
} = params
|
||||
|
||||
const requestBody = { page, size }
|
||||
|
||||
if (courseName) requestBody.courseName = courseName
|
||||
if (courseType) requestBody.courseType = courseType
|
||||
if (startDate) requestBody.startDate = formatDateTime(startDate)
|
||||
if (endDate) requestBody.endDate = formatDateTime(endDate, true)
|
||||
if (timePeriod) requestBody.timePeriod = timePeriod
|
||||
if (priceSort) requestBody.priceSort = priceSort
|
||||
if (remainingMost !== undefined) requestBody.remainingMost = remainingMost
|
||||
|
||||
return request.post('/groupCourse/search', requestBody, options)
|
||||
}
|
||||
|
||||
function formatDateTime(dateStr, isEnd = false) {
|
||||
if (!dateStr) return dateStr
|
||||
if (dateStr.includes('T')) return dateStr
|
||||
return isEnd
|
||||
? `${dateStr}T23:59:59`
|
||||
: `${dateStr}T00:00:00`
|
||||
}
|
||||
|
||||
export function bookGroupCourse(params) {
|
||||
return request.post('/groupCourse/book', params)
|
||||
}
|
||||
@@ -60,6 +94,7 @@ export function getMemberBookings(memberId, options = {}) {
|
||||
export default {
|
||||
getGroupCourseList,
|
||||
getGroupCoursePage,
|
||||
searchGroupCourse,
|
||||
getGroupCourseById,
|
||||
getGroupCourseDetail,
|
||||
createGroupCourse,
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
<template>
|
||||
<view class="filter-section">
|
||||
<!-- 课程类型筛选 -->
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="courseTypeOptions"
|
||||
range-key="label"
|
||||
:value="courseTypeIndex"
|
||||
@change="onCourseTypeChange"
|
||||
>
|
||||
<view class="filter-item">
|
||||
<uni-icons type="apps" size="18" color="#5E6F8D" class="filter-icon" />
|
||||
<text class="filter-text">{{ courseTypeOptions[courseTypeIndex]?.label || '全部类型' }}</text>
|
||||
<uni-icons type="right" size="20" color="#A0AEC0" class="filter-arrow" />
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<!-- 时间区间筛选 -->
|
||||
<view class="filter-item" @click="handleTimePick">
|
||||
<uni-icons type="calendar" size="18" color="#5E6F8D" class="filter-icon" />
|
||||
@@ -25,7 +40,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
timeRangeText: {
|
||||
@@ -35,46 +50,90 @@ const props = defineProps({
|
||||
sortOptions: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ label: '默认排序', value: 'default' },
|
||||
{ label: '价格从低到高', value: 'priceAsc' },
|
||||
{ label: '价格从高到低', value: 'priceDesc' },
|
||||
{ label: '剩余名额最多', value: 'spotsDesc' },
|
||||
{ label: '仅次数卡', value: 'pointCardOnly' },
|
||||
{ label: '仅储值卡', value: 'storedValueOnly' },
|
||||
{ label: '两种支付', value: 'bothPayment' }
|
||||
{ label: '默认排序', value: 'default', priceSort: null, remainingMost: false },
|
||||
{ label: '价格从低到高', value: 'priceAsc', priceSort: 'asc', remainingMost: false },
|
||||
{ label: '价格从高到低', value: 'priceDesc', priceSort: 'desc', remainingMost: false },
|
||||
{ label: '剩余名额最多', value: 'remainingMost', priceSort: null, remainingMost: true }
|
||||
]
|
||||
},
|
||||
sortIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
courseTypes: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
currentCourseTypeId: {
|
||||
type: [Number, String, null],
|
||||
default: null,
|
||||
validator: (val) => val === null || val === '' || !isNaN(Number(val))
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:sortIndex', 'timePick'])
|
||||
const emit = defineEmits(['update:sortIndex', 'timePick', 'courseTypeChange'])
|
||||
|
||||
const localSortIndex = ref(props.sortIndex)
|
||||
|
||||
const courseTypeOptions = computed(() => {
|
||||
return [{ id: null, label: '全部类型' }, ...props.courseTypes]
|
||||
})
|
||||
|
||||
const courseTypeIndex = computed(() => {
|
||||
if (props.currentCourseTypeId === null || props.currentCourseTypeId === '') return 0
|
||||
const currentId = Number(props.currentCourseTypeId)
|
||||
const index = courseTypeOptions.value.findIndex(item => Number(item.id) === currentId)
|
||||
return index >= 0 ? index : 0
|
||||
})
|
||||
|
||||
watch(() => props.sortIndex, (val) => {
|
||||
localSortIndex.value = val
|
||||
})
|
||||
|
||||
const onSortChange = (e) => {
|
||||
localSortIndex.value = e.detail.value
|
||||
const sortOption = props.sortOptions[localSortIndex.value]
|
||||
|
||||
console.log('[FilterSection] 排序方式变更:', {
|
||||
index: localSortIndex.value,
|
||||
value: props.sortOptions[localSortIndex.value]
|
||||
value: sortOption
|
||||
})
|
||||
|
||||
if (sortOption.priceSort && sortOption.remainingMost) {
|
||||
console.warn('[FilterSection] 排序参数冲突警告: priceSort和remainingMost不能同时设置')
|
||||
}
|
||||
|
||||
emit('update:sortIndex', localSortIndex.value)
|
||||
}
|
||||
|
||||
const onCourseTypeChange = (e) => {
|
||||
const index = e.detail.value
|
||||
const selectedType = courseTypeOptions.value[index]
|
||||
// 确保返回数字类型而非字符串
|
||||
const typeId = selectedType.id !== null ? Number(selectedType.id) : null
|
||||
console.log('[FilterSection] 课程类型变更:', {
|
||||
index,
|
||||
id: typeId,
|
||||
label: selectedType.label
|
||||
})
|
||||
emit('courseTypeChange', typeId)
|
||||
}
|
||||
|
||||
const handleTimePick = () => {
|
||||
console.log('[FilterSection] 触发时间选择器')
|
||||
emit('timePick')
|
||||
}
|
||||
|
||||
const getFilterParams = () => {
|
||||
const sortOption = props.sortOptions[localSortIndex.value]
|
||||
const selectedType = courseTypeOptions.value[courseTypeIndex.value]
|
||||
|
||||
const params = {
|
||||
sortType: props.sortOptions[localSortIndex.value].value,
|
||||
sortType: sortOption.value,
|
||||
priceSort: sortOption.priceSort,
|
||||
remainingMost: sortOption.remainingMost,
|
||||
courseTypeId: selectedType.id !== null ? Number(selectedType.id) : null,
|
||||
courseTypeName: selectedType.label,
|
||||
timeRangeText: props.timeRangeText
|
||||
}
|
||||
console.log('[FilterSection] 获取筛选参数:', params)
|
||||
@@ -173,18 +232,20 @@ defineExpose({
|
||||
<style lang="scss" scoped>
|
||||
.filter-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
min-width: calc(33.33% - 12rpx);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
gap: 8rpx;
|
||||
padding: 18rpx 16rpx;
|
||||
background: #F5F7FA;
|
||||
border-radius: 16rpx;
|
||||
font-size: 26rpx;
|
||||
font-size: 24rpx;
|
||||
color: #5E6F8D;
|
||||
|
||||
.filter-icon {
|
||||
@@ -197,6 +258,13 @@ defineExpose({
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-text {
|
||||
max-width: 100rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { getGroupCoursePage, getTypeLabels } from '@/api/groupCourse.js'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { getGroupCoursePage, getTypeLabels, searchGroupCourse } from '@/api/groupCourse.js'
|
||||
|
||||
export function useGroupCourseList() {
|
||||
const pageNum = ref(0)
|
||||
@@ -14,11 +14,14 @@ export function useGroupCourseList() {
|
||||
const searchKeyword = ref('')
|
||||
const hotKeywords = ref(['燃脂', '瑜伽', '单车', '普拉提', '高强度'])
|
||||
|
||||
const courseType = ref(null)
|
||||
const courseTypes = ref([])
|
||||
|
||||
const sortOptions = ref([
|
||||
{ label: '默认排序', value: 'id', order: 'asc' },
|
||||
{ label: '价格从低到高', value: 'storedValueAmount', order: 'asc' },
|
||||
{ label: '价格从高到低', value: 'storedValueAmount', order: 'desc' },
|
||||
{ label: '剩余名额最多', value: 'currentMembers', order: 'asc' }
|
||||
{ label: '默认排序', value: 'default', priceSort: null, remainingMost: false },
|
||||
{ label: '价格从低到高', value: 'priceAsc', priceSort: 'asc', remainingMost: false },
|
||||
{ label: '价格从高到低', value: 'priceDesc', priceSort: 'desc', remainingMost: false },
|
||||
{ label: '剩余名额最多', value: 'remainingMost', priceSort: null, remainingMost: true }
|
||||
])
|
||||
const sortIndex = ref(0)
|
||||
|
||||
@@ -73,6 +76,13 @@ export function useGroupCourseList() {
|
||||
return result
|
||||
})
|
||||
|
||||
// 监听排序方式变化,重新获取数据
|
||||
watch(sortIndex, () => {
|
||||
console.log('[useGroupCourseList] 排序方式变化,触发重新查询')
|
||||
pageNum.value = 0
|
||||
fetchCourseList()
|
||||
})
|
||||
|
||||
const getAllSearchParams = (searchBarRef, filterSectionRef, timePeriodRef, timeRangePickerRef) => {
|
||||
const searchParams = searchBarRef?.getSearchParams?.() || { keyword: searchKeyword.value }
|
||||
const filterParams = filterSectionRef?.getFilterParams?.() || { sortType: sortOptions.value[sortIndex.value].value }
|
||||
@@ -99,6 +109,8 @@ export function useGroupCourseList() {
|
||||
|
||||
const onTimePeriodChange = (option) => {
|
||||
console.log('[useGroupCourseList] 时间段选择:', option)
|
||||
pageNum.value = 0
|
||||
fetchCourseList()
|
||||
}
|
||||
|
||||
const onTimeRangeConfirm = (params) => {
|
||||
@@ -108,6 +120,19 @@ export function useGroupCourseList() {
|
||||
timeRangeText.value = params.timeRangeText
|
||||
}
|
||||
|
||||
const onCourseTypeChange = (typeId) => {
|
||||
console.log('[useGroupCourseList] 课程类型选择:', typeId)
|
||||
courseType.value = typeId
|
||||
pageNum.value = 0
|
||||
fetchCourseList()
|
||||
}
|
||||
|
||||
const clearCourseType = () => {
|
||||
courseType.value = null
|
||||
pageNum.value = 0
|
||||
fetchCourseList()
|
||||
}
|
||||
|
||||
const handleBooking = (course) => {
|
||||
console.log('[useGroupCourseList] 预约课程:', course)
|
||||
uni.showToast({
|
||||
@@ -123,33 +148,75 @@ export function useGroupCourseList() {
|
||||
})
|
||||
}
|
||||
|
||||
const buildSearchParams = () => {
|
||||
const sortOption = sortOptions.value[sortIndex.value]
|
||||
const timePeriod = timePeriodOptions.value[timePeriodIndex.value]
|
||||
|
||||
const params = {
|
||||
page: pageNum.value,
|
||||
size: pageSize.value
|
||||
}
|
||||
|
||||
if (searchKeyword.value) {
|
||||
params.courseName = searchKeyword.value
|
||||
}
|
||||
|
||||
if (courseType.value) {
|
||||
params.courseType = courseType.value
|
||||
}
|
||||
|
||||
if (startDate.value) {
|
||||
params.startDate = startDate.value
|
||||
}
|
||||
|
||||
if (endDate.value) {
|
||||
params.endDate = endDate.value
|
||||
}
|
||||
|
||||
if (timePeriod.value && timePeriod.value !== 'all') {
|
||||
params.timePeriod = timePeriod.value
|
||||
}
|
||||
|
||||
if (sortOption.priceSort) {
|
||||
params.priceSort = sortOption.priceSort
|
||||
}
|
||||
|
||||
if (sortOption.remainingMost) {
|
||||
params.remainingMost = sortOption.remainingMost
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
const hasActiveFilters = () => {
|
||||
return searchKeyword.value ||
|
||||
courseType.value ||
|
||||
startDate.value ||
|
||||
endDate.value ||
|
||||
timePeriodOptions.value[timePeriodIndex.value].value !== 'all' ||
|
||||
sortIndex.value !== 0
|
||||
}
|
||||
|
||||
const fetchCourseList = async (isLoadMore = false) => {
|
||||
if (loading.value) return
|
||||
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const sortOption = sortOptions.value[sortIndex.value]
|
||||
console.log('[useGroupCourseList] 请求参数:', {
|
||||
page: isLoadMore ? pageNum.value + 1 : pageNum.value,
|
||||
size: pageSize.value,
|
||||
sort: sortOption.value,
|
||||
order: sortOption.order,
|
||||
keyword: searchKeyword.value
|
||||
})
|
||||
|
||||
const result = await getGroupCoursePage({
|
||||
page: isLoadMore ? pageNum.value + 1 : pageNum.value,
|
||||
size: pageSize.value,
|
||||
sort: sortOption.value,
|
||||
order: sortOption.order,
|
||||
keyword: searchKeyword.value
|
||||
})
|
||||
const currentPage = isLoadMore ? pageNum.value + 1 : pageNum.value
|
||||
pageNum.value = currentPage
|
||||
|
||||
const searchParams = buildSearchParams()
|
||||
searchParams.page = currentPage
|
||||
|
||||
console.log('[useGroupCourseList] 请求参数:', JSON.stringify(searchParams, null, 2))
|
||||
|
||||
const result = await searchGroupCourse(searchParams)
|
||||
|
||||
console.log('[useGroupCourseList] 响应结果:', JSON.stringify(result, null, 2))
|
||||
|
||||
if (result && result.content) {
|
||||
const { content: list, totalElements: totalCount, currentPage, totalPages: pages } = result
|
||||
if (result && result.data && result.data.content) {
|
||||
const { content: list, totalElements: totalCount, currentPage: respPage, totalPages: pages } = result.data
|
||||
|
||||
if (isLoadMore) {
|
||||
courseList.value = [...courseList.value, ...list]
|
||||
@@ -158,7 +225,6 @@ export function useGroupCourseList() {
|
||||
}
|
||||
|
||||
total.value = totalCount
|
||||
pageNum.value = currentPage
|
||||
totalPages.value = pages
|
||||
hasMore.value = currentPage < pages - 1
|
||||
|
||||
@@ -213,6 +279,8 @@ export function useGroupCourseList() {
|
||||
courseList,
|
||||
searchKeyword,
|
||||
hotKeywords,
|
||||
courseType,
|
||||
courseTypes,
|
||||
sortOptions,
|
||||
sortIndex,
|
||||
timePeriodOptions,
|
||||
@@ -227,9 +295,13 @@ export function useGroupCourseList() {
|
||||
handleSearch,
|
||||
onTimePeriodChange,
|
||||
onTimeRangeConfirm,
|
||||
onCourseTypeChange,
|
||||
clearCourseType,
|
||||
handleBooking,
|
||||
goDetail,
|
||||
fetchCourseList,
|
||||
buildSearchParams,
|
||||
hasActiveFilters,
|
||||
loadMore,
|
||||
onScrollToLower
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
3. [团课管理接口](#团课管理接口)
|
||||
- [获取所有团课](#获取所有团课)
|
||||
- [分页获取团课](#分页获取团课)
|
||||
- [多条件查询团课](#多条件查询团课)
|
||||
- [根据ID获取团课详情](#根据ID获取团课详情)
|
||||
- [创建团课](#创建团课)
|
||||
- [更新团课](#更新团课)
|
||||
@@ -154,6 +155,149 @@
|
||||
|
||||
---
|
||||
|
||||
### 多条件查询团课
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| **HTTP方法** | POST |
|
||||
| **接口路径** | `/api/groupCourse/search` |
|
||||
| **所属文件** | `GroupCourseHandler.java` |
|
||||
|
||||
**功能说明**: 支持团课名称模糊查询、类型筛选、日期范围、时间段、价格排序、剩余名额排序等多条件组合查询,默认不查询不可预约的团课(已取消或已结束)。
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"courseName": "瑜伽",
|
||||
"courseType": 1,
|
||||
"startDate": "2026-06-01T00:00:00",
|
||||
"endDate": "2026-06-30T23:59:59",
|
||||
"timePeriod": "morning",
|
||||
"priceSort": "asc",
|
||||
"remainingMost": true,
|
||||
"page": 0,
|
||||
"size": 10
|
||||
}
|
||||
```
|
||||
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| courseName | String | 否 | - | 课程名称(模糊查询,不区分大小写) |
|
||||
| courseType | Long | 否 | - | 课程类型ID |
|
||||
| startDate | LocalDateTime | 否 | - | 查询开始日期 |
|
||||
| endDate | LocalDateTime | 否 | - | 查询结束日期 |
|
||||
| timePeriod | String | 否 | - | 时间段:`morning`(6:00-12:00)、`afternoon`(12:00-18:00)、`evening`(18:00-24:00) |
|
||||
| priceSort | String | 否 | - | 价格排序:`asc`(从低到高)、`desc`(从高到低) |
|
||||
| remainingMost | Boolean | 否 | false | 是否按剩余名额最多排序 |
|
||||
| page | Integer | 否 | 0 | 页码,从0开始 |
|
||||
| size | Integer | 否 | 10 | 每页数量,最大100 |
|
||||
|
||||
**查询条件优先级说明**:
|
||||
|
||||
| 优先级 | 条件类型 | 说明 |
|
||||
|--------|----------|------|
|
||||
| 1 | 默认过滤 | 自动过滤已删除和不可预约的团课(status != 0) |
|
||||
| 2 | 基础筛选 | courseName、courseType、startDate、endDate、timePeriod |
|
||||
| 3 | 排序规则 | remainingMost优先,其次priceSort,默认按startTime升序 |
|
||||
|
||||
**成功响应** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "查询成功",
|
||||
"data": {
|
||||
"content": [
|
||||
{
|
||||
"id": 1,
|
||||
"courseName": "瑜伽入门",
|
||||
"coachId": 1,
|
||||
"courseType": 1,
|
||||
"startTime": "2026-06-15T09:00:00",
|
||||
"endTime": "2026-06-15T10:00:00",
|
||||
"maxMembers": 20,
|
||||
"currentMembers": 5,
|
||||
"status": 0,
|
||||
"location": "健身房A区",
|
||||
"coverImage": "https://example.com/yoga.jpg",
|
||||
"description": "适合初学者的瑜伽课程",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 50.00,
|
||||
"createdAt": "2026-06-01T10:00:00",
|
||||
"updatedAt": "2026-06-01T10:00:00"
|
||||
}
|
||||
],
|
||||
"totalPages": 3,
|
||||
"totalElements": 25,
|
||||
"currentPage": 0,
|
||||
"pageSize": 10,
|
||||
"first": true,
|
||||
"last": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**失败响应** (400 Bad Request):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "查询失败的原因"
|
||||
}
|
||||
```
|
||||
|
||||
**使用示例**:
|
||||
|
||||
1. **查询瑜伽课程**
|
||||
```json
|
||||
{
|
||||
"courseName": "瑜伽"
|
||||
}
|
||||
```
|
||||
|
||||
2. **查询特定类型的早晨课程**
|
||||
```json
|
||||
{
|
||||
"courseType": 1,
|
||||
"timePeriod": "morning"
|
||||
}
|
||||
```
|
||||
|
||||
3. **查询下周的课程,按价格从低到高排序**
|
||||
```json
|
||||
{
|
||||
"startDate": "2026-06-16T00:00:00",
|
||||
"endDate": "2026-06-22T23:59:59",
|
||||
"priceSort": "asc"
|
||||
}
|
||||
```
|
||||
|
||||
4. **查询剩余名额最多的晚间课程**
|
||||
```json
|
||||
{
|
||||
"timePeriod": "evening",
|
||||
"remainingMost": true
|
||||
}
|
||||
```
|
||||
|
||||
5. **多条件组合查询**
|
||||
```json
|
||||
{
|
||||
"courseName": "瑜伽",
|
||||
"courseType": 1,
|
||||
"startDate": "2026-06-01T00:00:00",
|
||||
"endDate": "2026-06-30T23:59:59",
|
||||
"timePeriod": "morning",
|
||||
"priceSort": "asc",
|
||||
"remainingMost": true,
|
||||
"page": 0,
|
||||
"size": 10
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 根据ID获取团课详情
|
||||
|
||||
| 属性 | 值 |
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
[API] 响应数据: {
|
||||
"data": {
|
||||
"content": [
|
||||
{
|
||||
"id": "1",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T11:00:00",
|
||||
"updatedAt": "2026-06-01T11:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "极速燃脂单车",
|
||||
"coachId": "104",
|
||||
"courseType": "2",
|
||||
"startTime": "2026-06-02T16:45:00",
|
||||
"endTime": "2026-06-15T20:20:00",
|
||||
"maxMembers": 25,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "单车房",
|
||||
"coverImage": "/images/spinning.jpg",
|
||||
"description": "跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感,一节课消耗800大卡。",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "8",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T10:00:00",
|
||||
"updatedAt": "2026-06-01T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "燃脂搏击_次数卡课程",
|
||||
"coachId": "102",
|
||||
"courseType": "2",
|
||||
"startTime": "2026-06-10T19:30:00",
|
||||
"endTime": "2026-06-10T20:30:00",
|
||||
"maxMembers": 20,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "综合训练区",
|
||||
"coverImage": null,
|
||||
"description": "高强度间歇训练,配合音乐快速燃脂,消耗1次",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "11",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-02T10:00:00",
|
||||
"updatedAt": "2026-06-02T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "时间冲突测试_A_13点-15点",
|
||||
"coachId": "102",
|
||||
"courseType": "2",
|
||||
"startTime": "2026-06-15T13:00:00",
|
||||
"endTime": "2026-06-15T15:00:00",
|
||||
"maxMembers": 20,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "综合训练区",
|
||||
"coverImage": null,
|
||||
"description": "测试用团课A,用于验证时间冲突检测",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "10",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-02T10:00:00",
|
||||
"updatedAt": "2026-06-02T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "晚间瑜伽_取消测试",
|
||||
"coachId": "101",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-15T19:00:00",
|
||||
"endTime": "2026-06-15T20:00:00",
|
||||
"maxMembers": 20,
|
||||
"currentMembers": 3,
|
||||
"status": "0",
|
||||
"location": "瑜伽教室",
|
||||
"coverImage": null,
|
||||
"description": "适合所有级别的瑜伽课程,用于测试取消预约功能",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 30
|
||||
},
|
||||
{
|
||||
"id": "12",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-02T10:00:00",
|
||||
"updatedAt": "2026-06-02T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "时间冲突测试_B_14点-16点",
|
||||
"coachId": "103",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-15T14:00:00",
|
||||
"endTime": "2026-06-15T16:00:00",
|
||||
"maxMembers": 15,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "普拉提教室",
|
||||
"coverImage": null,
|
||||
"description": "测试用团课B,与团课A时间重叠(14:00-15:00)",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "9",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T10:00:00",
|
||||
"updatedAt": "2026-06-01T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "高端普拉提_储值卡课程",
|
||||
"coachId": "103",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-11T19:00:00",
|
||||
"endTime": "2026-06-11T20:00:00",
|
||||
"maxMembers": 15,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "普拉提教室",
|
||||
"coverImage": null,
|
||||
"description": "精准训练核心肌群,消耗储值50元",
|
||||
"pointCardAmount": 0,
|
||||
"storedValueAmount": 20
|
||||
},
|
||||
{
|
||||
"id": "13",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-02T10:00:00",
|
||||
"updatedAt": "2026-06-02T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "时间冲突测试_C_10点-12点",
|
||||
"coachId": "101",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-15T10:00:00",
|
||||
"endTime": "2026-06-15T12:00:00",
|
||||
"maxMembers": 15,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "瑜伽教室",
|
||||
"coverImage": null,
|
||||
"description": "测试用团课C,与团课A/B不冲突",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T10:00:00",
|
||||
"updatedAt": "2026-06-01T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "清晨流瑜伽",
|
||||
"coachId": "101",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-12T09:00:00",
|
||||
"endTime": "2026-06-12T10:30:00",
|
||||
"maxMembers": 15,
|
||||
"currentMembers": 5,
|
||||
"status": "0",
|
||||
"location": "A座3楼瑜伽教室",
|
||||
"coverImage": "/images/yoga_flow.jpg",
|
||||
"description": "适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"createBy": "coach_li",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T08:00:00",
|
||||
"updatedAt": "2026-06-01T08:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "哈他瑜伽",
|
||||
"coachId": "101",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-01T15:20:00",
|
||||
"endTime": "2026-06-01T16:50:00",
|
||||
"maxMembers": 12,
|
||||
"currentMembers": 3,
|
||||
"status": "0",
|
||||
"location": "瑜伽教室B",
|
||||
"coverImage": "/images/hatha_yoga.jpg",
|
||||
"description": "基础哈他瑜伽,适合所有级别。距开始不足30分钟,已停止预约。",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "3001",
|
||||
"createBy": null,
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T10:00:00",
|
||||
"updatedAt": "2026-06-01T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "瑜伽入门",
|
||||
"coachId": "1",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-09T08:00:00",
|
||||
"endTime": "2026-06-09T09:00:00",
|
||||
"maxMembers": 20,
|
||||
"currentMembers": 15,
|
||||
"status": "0",
|
||||
"location": "健身房A区",
|
||||
"coverImage": "https://example.com/yoga.jpg",
|
||||
"description": "适合初学者的瑜伽课程",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
}
|
||||
],
|
||||
"totalPages": 2,
|
||||
"totalElements": "13",
|
||||
"currentPage": 0,
|
||||
"pageSize": 10,
|
||||
"first": true,
|
||||
"last": false
|
||||
},
|
||||
"success": true,
|
||||
"message": "查询成功"
|
||||
} request.js:185:17
|
||||
[useGroupCourseList] 响应结果: {
|
||||
"data": {
|
||||
"content": [
|
||||
{
|
||||
"id": "1",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T11:00:00",
|
||||
"updatedAt": "2026-06-01T11:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "极速燃脂单车",
|
||||
"coachId": "104",
|
||||
"courseType": "2",
|
||||
"startTime": "2026-06-02T16:45:00",
|
||||
"endTime": "2026-06-15T20:20:00",
|
||||
"maxMembers": 25,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "单车房",
|
||||
"coverImage": "/images/spinning.jpg",
|
||||
"description": "跟随音乐节奏变换阻力和速度,体验爬坡与冲刺的快感,一节课消耗800大卡。",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "8",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T10:00:00",
|
||||
"updatedAt": "2026-06-01T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "燃脂搏击_次数卡课程",
|
||||
"coachId": "102",
|
||||
"courseType": "2",
|
||||
"startTime": "2026-06-10T19:30:00",
|
||||
"endTime": "2026-06-10T20:30:00",
|
||||
"maxMembers": 20,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "综合训练区",
|
||||
"coverImage": null,
|
||||
"description": "高强度间歇训练,配合音乐快速燃脂,消耗1次",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "11",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-02T10:00:00",
|
||||
"updatedAt": "2026-06-02T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "时间冲突测试_A_13点-15点",
|
||||
"coachId": "102",
|
||||
"courseType": "2",
|
||||
"startTime": "2026-06-15T13:00:00",
|
||||
"endTime": "2026-06-15T15:00:00",
|
||||
"maxMembers": 20,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "综合训练区",
|
||||
"coverImage": null,
|
||||
"description": "测试用团课A,用于验证时间冲突检测",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "10",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-02T10:00:00",
|
||||
"updatedAt": "2026-06-02T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "晚间瑜伽_取消测试",
|
||||
"coachId": "101",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-15T19:00:00",
|
||||
"endTime": "2026-06-15T20:00:00",
|
||||
"maxMembers": 20,
|
||||
"currentMembers": 3,
|
||||
"status": "0",
|
||||
"location": "瑜伽教室",
|
||||
"coverImage": null,
|
||||
"description": "适合所有级别的瑜伽课程,用于测试取消预约功能",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 30
|
||||
},
|
||||
{
|
||||
"id": "12",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-02T10:00:00",
|
||||
"updatedAt": "2026-06-02T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "时间冲突测试_B_14点-16点",
|
||||
"coachId": "103",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-15T14:00:00",
|
||||
"endTime": "2026-06-15T16:00:00",
|
||||
"maxMembers": 15,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "普拉提教室",
|
||||
"coverImage": null,
|
||||
"description": "测试用团课B,与团课A时间重叠(14:00-15:00)",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "9",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T10:00:00",
|
||||
"updatedAt": "2026-06-01T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "高端普拉提_储值卡课程",
|
||||
"coachId": "103",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-11T19:00:00",
|
||||
"endTime": "2026-06-11T20:00:00",
|
||||
"maxMembers": 15,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "普拉提教室",
|
||||
"coverImage": null,
|
||||
"description": "精准训练核心肌群,消耗储值50元",
|
||||
"pointCardAmount": 0,
|
||||
"storedValueAmount": 20
|
||||
},
|
||||
{
|
||||
"id": "13",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-02T10:00:00",
|
||||
"updatedAt": "2026-06-02T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "时间冲突测试_C_10点-12点",
|
||||
"coachId": "101",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-15T10:00:00",
|
||||
"endTime": "2026-06-15T12:00:00",
|
||||
"maxMembers": 15,
|
||||
"currentMembers": 0,
|
||||
"status": "0",
|
||||
"location": "瑜伽教室",
|
||||
"coverImage": null,
|
||||
"description": "测试用团课C,与团课A/B不冲突",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"createBy": "admin",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T10:00:00",
|
||||
"updatedAt": "2026-06-01T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "清晨流瑜伽",
|
||||
"coachId": "101",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-12T09:00:00",
|
||||
"endTime": "2026-06-12T10:30:00",
|
||||
"maxMembers": 15,
|
||||
"currentMembers": 5,
|
||||
"status": "0",
|
||||
"location": "A座3楼瑜伽教室",
|
||||
"coverImage": "/images/yoga_flow.jpg",
|
||||
"description": "适合有一定基础的学员,通过流畅的体式连接呼吸,唤醒身体能量。",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"createBy": "coach_li",
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T08:00:00",
|
||||
"updatedAt": "2026-06-01T08:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "哈他瑜伽",
|
||||
"coachId": "101",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-01T15:20:00",
|
||||
"endTime": "2026-06-01T16:50:00",
|
||||
"maxMembers": 12,
|
||||
"currentMembers": 3,
|
||||
"status": "0",
|
||||
"location": "瑜伽教室B",
|
||||
"coverImage": "/images/hatha_yoga.jpg",
|
||||
"description": "基础哈他瑜伽,适合所有级别。距开始不足30分钟,已停止预约。",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
},
|
||||
{
|
||||
"id": "3001",
|
||||
"createBy": null,
|
||||
"updateBy": null,
|
||||
"createdAt": "2026-06-01T10:00:00",
|
||||
"updatedAt": "2026-06-01T10:00:00",
|
||||
"deletedAt": null,
|
||||
"courseName": "瑜伽入门",
|
||||
"coachId": "1",
|
||||
"courseType": "1",
|
||||
"startTime": "2026-06-09T08:00:00",
|
||||
"endTime": "2026-06-09T09:00:00",
|
||||
"maxMembers": 20,
|
||||
"currentMembers": 15,
|
||||
"status": "0",
|
||||
"location": "健身房A区",
|
||||
"coverImage": "https://example.com/yoga.jpg",
|
||||
"description": "适合初学者的瑜伽课程",
|
||||
"pointCardAmount": 1,
|
||||
"storedValueAmount": 0
|
||||
}
|
||||
],
|
||||
"totalPages": 2,
|
||||
"totalElements": "13",
|
||||
"currentPage": 0,
|
||||
"pageSize": 10,
|
||||
"first": true,
|
||||
"last": false
|
||||
},
|
||||
"success": true,
|
||||
"message": "查询成功"
|
||||
}
|
||||
@@ -107,7 +107,6 @@
|
||||
<view class="course-tags">
|
||||
<view class="tags-label">
|
||||
<uni-icons type="tag" size="20" color="#8A99B4" />
|
||||
<text>课程标签</text>
|
||||
</view>
|
||||
<view class="tags-list">
|
||||
<view
|
||||
@@ -212,8 +211,8 @@
|
||||
|
||||
<!-- 免费课程或单一支付方式 -->
|
||||
<view v-else class="price-display">
|
||||
<text v-if="course.storedValueAmount === 0 && course.pointCardAmount === 0" class="price free">免费</text>
|
||||
<text v-else-if="course.storedValueAmount > 0" class="price">
|
||||
<text v-if="Number(course.storedValueAmount) === 0 && Number(course.pointCardAmount) === 0" class="price free">免费</text>
|
||||
<text v-else-if="Number(course.storedValueAmount) > 0" class="price">
|
||||
<text class="currency">¥</text>{{ course.storedValueAmount }}
|
||||
</text>
|
||||
<text v-else class="price">
|
||||
@@ -233,7 +232,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { groupCourseService } from '@/request_api/groupCourse.mock.js'
|
||||
import { getGroupCourseDetail, bookGroupCourse } from '@/api/groupCourse.js'
|
||||
import PageHeader from '@/components/index/PageHeader.vue'
|
||||
|
||||
// 加载状态
|
||||
@@ -305,7 +304,9 @@ const canBook = computed(() => {
|
||||
|
||||
// 是否有多种支付方式
|
||||
const hasMultiplePayment = computed(() => {
|
||||
return course.value.storedValueAmount > 0 && course.value.pointCardAmount > 0
|
||||
const storedValue = Number(course.value.storedValueAmount)
|
||||
const pointCard = Number(course.value.pointCardAmount)
|
||||
return storedValue > 0 && pointCard > 0
|
||||
})
|
||||
|
||||
// 当前选中的支付方式
|
||||
@@ -313,17 +314,20 @@ const selectedPayment = ref('storedValue')
|
||||
|
||||
// 当前选中的支付方式价格显示
|
||||
const selectedPrice = computed(() => {
|
||||
const storedValue = Number(course.value.storedValueAmount)
|
||||
const pointCard = Number(course.value.pointCardAmount)
|
||||
|
||||
if (selectedPayment.value === 'storedValue') {
|
||||
return { type: 'storedValue', amount: course.value.storedValueAmount }
|
||||
return { type: 'storedValue', amount: storedValue }
|
||||
} else if (selectedPayment.value === 'pointCard') {
|
||||
return { type: 'pointCard', amount: course.value.pointCardAmount }
|
||||
} else if (course.value.storedValueAmount > 0 && course.value.pointCardAmount > 0) {
|
||||
return { type: 'pointCard', amount: pointCard }
|
||||
} else if (storedValue > 0 && pointCard > 0) {
|
||||
// 默认优先显示储值卡
|
||||
return { type: 'storedValue', amount: course.value.storedValueAmount }
|
||||
} else if (course.value.storedValueAmount > 0) {
|
||||
return { type: 'storedValue', amount: course.value.storedValueAmount }
|
||||
} else if (course.value.pointCardAmount > 0) {
|
||||
return { type: 'pointCard', amount: course.value.pointCardAmount }
|
||||
return { type: 'storedValue', amount: storedValue }
|
||||
} else if (storedValue > 0) {
|
||||
return { type: 'storedValue', amount: storedValue }
|
||||
} else if (pointCard > 0) {
|
||||
return { type: 'pointCard', amount: pointCard }
|
||||
}
|
||||
return { type: 'free', amount: 0 }
|
||||
})
|
||||
@@ -378,20 +382,29 @@ const handleBooking = () => {
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: '预约中...' })
|
||||
groupCourseService.book({
|
||||
bookGroupCourse({
|
||||
courseId: course.value.id,
|
||||
memberId: '1' // 模拟会员ID
|
||||
}).then(() => {
|
||||
memberId: '1', // 模拟会员ID
|
||||
memberCardRecordId: '1' // 模拟会员卡记录ID
|
||||
}).then((result) => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '预约成功',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}).catch(() => {
|
||||
if (result.success) {
|
||||
uni.showToast({
|
||||
title: '预约成功',
|
||||
icon: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.message || '预约失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}).catch((error) => {
|
||||
uni.hideLoading()
|
||||
console.error('[detail.vue] 预约失败:', error)
|
||||
uni.showToast({
|
||||
title: '预约失败',
|
||||
icon: 'none'
|
||||
@@ -405,12 +418,16 @@ const handleBooking = () => {
|
||||
// 获取课程详情
|
||||
const fetchCourseDetail = async (id) => {
|
||||
try {
|
||||
const result = await groupCourseService.getDetail(id)
|
||||
console.log('[detail.vue] 开始获取课程详情, id:', id)
|
||||
const result = await getGroupCourseDetail(id)
|
||||
course.value = result
|
||||
console.log('[detail.vue] 课程详情获取成功:', course.value)
|
||||
|
||||
// 获取课程类型标签
|
||||
await fetchCourseLabels(course.value.courseType)
|
||||
// 从完整信息中提取标签
|
||||
if (result.labels && Array.isArray(result.labels)) {
|
||||
courseLabels.value = result.labels
|
||||
}
|
||||
console.log('[detail.vue] 课程标签获取成功:', courseLabels.value)
|
||||
} catch (error) {
|
||||
console.error('[detail.vue] 获取课程详情失败:', error)
|
||||
uni.showToast({
|
||||
@@ -422,19 +439,6 @@ const fetchCourseDetail = async (id) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取课程标签
|
||||
const fetchCourseLabels = async (courseType) => {
|
||||
try {
|
||||
const result = await groupCourseService.getLabelsByCourseType(courseType)
|
||||
if (result.code === 0) {
|
||||
courseLabels.value = result.data
|
||||
}
|
||||
console.log('[detail.vue] 课程标签获取成功:', courseLabels.value)
|
||||
} catch (error) {
|
||||
console.error('[detail.vue] 获取课程标签失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 页面挂载时获取课程详情
|
||||
onMounted(() => {
|
||||
const pages = getCurrentPages()
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
:time-range-text="timeRangeText"
|
||||
:sort-options="sortOptions"
|
||||
v-model:sort-index="sortIndex"
|
||||
:course-types="courseTypes"
|
||||
:current-course-type-id="courseType"
|
||||
@time-pick="showTimePicker = true"
|
||||
@course-type-change="onCourseTypeChange"
|
||||
ref="filterSectionRef"
|
||||
/>
|
||||
|
||||
@@ -79,7 +82,7 @@ import TimePeriodSelector from '@/components/groupCourse/TimePeriodSelector.vue'
|
||||
import TimeRangePicker from '@/components/groupCourse/TimeRangePicker.vue'
|
||||
import PageHeader from '@/components/index/PageHeader.vue'
|
||||
import { useGroupCourseList } from '@/composables/useGroupCourseList.js'
|
||||
import { getTypeLabels } from '@/api/groupCourse.js'
|
||||
import { getTypeLabels, getGroupCourseTypes } from '@/api/groupCourse.js'
|
||||
|
||||
// 组件引用
|
||||
const searchBarRef = ref(null)
|
||||
@@ -97,6 +100,8 @@ const {
|
||||
hasMore,
|
||||
searchKeyword,
|
||||
hotKeywords,
|
||||
courseType,
|
||||
courseTypes,
|
||||
sortOptions,
|
||||
sortIndex,
|
||||
timePeriodOptions,
|
||||
@@ -112,6 +117,8 @@ const {
|
||||
handleSearch,
|
||||
onTimePeriodChange,
|
||||
onTimeRangeConfirm,
|
||||
onCourseTypeChange,
|
||||
clearCourseType,
|
||||
handleBooking,
|
||||
goDetail,
|
||||
fetchCourseList,
|
||||
@@ -122,7 +129,11 @@ const {
|
||||
// 组件挂载时调用接口获取团课列表
|
||||
onMounted(async () => {
|
||||
console.log('[list.vue] 页面组件已挂载,开始获取团课列表')
|
||||
await fetchCourseList()
|
||||
// 并行获取课程类型列表和课程列表
|
||||
await Promise.all([
|
||||
fetchCourseTypes(),
|
||||
fetchCourseList()
|
||||
])
|
||||
// 获取所有团课的类型标签
|
||||
await fetchAllCourseTypeLabels()
|
||||
console.log('[list.vue] 可用的搜索参数获取方法:')
|
||||
@@ -133,9 +144,24 @@ onMounted(async () => {
|
||||
console.log(' - getAllSearchParams() 获取所有参数')
|
||||
})
|
||||
|
||||
const fetchCourseTypes = async () => {
|
||||
try {
|
||||
const result = await getGroupCourseTypes()
|
||||
if (result && Array.isArray(result)) {
|
||||
courseTypes.value = result.map(type => ({
|
||||
id: type.id,
|
||||
label: type.typeName
|
||||
}))
|
||||
console.log('[list.vue] 获取课程类型成功:', courseTypes.value)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[list.vue] 获取课程类型失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchAllCourseTypeLabels = async () => {
|
||||
const courseTypes = [...new Set(filteredCourseList.value.map(c => c.courseType))]
|
||||
for (const type of courseTypes) {
|
||||
const types = [...new Set(filteredCourseList.value.map(c => c.courseType))]
|
||||
for (const type of types) {
|
||||
if (!courseTypeLabelsCache.value[type]) {
|
||||
try {
|
||||
const result = await getTypeLabels(type)
|
||||
|
||||
Reference in New Issue
Block a user