diff --git a/gym-manage-uniapp/api/groupCourse.js b/gym-manage-uniapp/api/groupCourse.js new file mode 100644 index 0000000..8fb0d6e --- /dev/null +++ b/gym-manage-uniapp/api/groupCourse.js @@ -0,0 +1,75 @@ +import request from "@/utils/request.js" + +export function getGroupCourseList(params = {}, options = {}) { + return request.get('/groupCourse/list', params, options) +} + +export function getGroupCoursePage(params = {}, options = { cache: true, cacheTime: 5 * 60 * 1000 }) { + const { page = 0, size = 10, sort = 'id', order = 'asc', keyword } = params + return request.post('/groupCourse/page', { page, size, sort, order, keyword }, options) +} + +export function getGroupCourseById(id, options = { cache: true, cacheTime: 15 * 60 * 1000 }) { + return request.get(`/groupCourse/${id}`, {}, options) +} + +export function getGroupCourseDetail(id, options = { cache: true, cacheTime: 15 * 60 * 1000 }) { + return request.get(`/groupCourse/${id}/detail`, {}, options) +} + +export function createGroupCourse(params) { + return request.post('/groupCourse', params) +} + +export function updateGroupCourse(id, params) { + return request.put(`/groupCourse/${id}`, params) +} + +export function cancelGroupCourse(id) { + return request.post(`/groupCourse/${id}/cancel`) +} + +export function deleteGroupCourse(id) { + return request.delete(`/groupCourse/${id}`) +} + +export function getGroupCourseTypes(params = {}, options = { cache: true, cacheTime: 10 * 60 * 1000 }) { + return request.get('/groupCourse/types', params, options) +} + +export function getGroupCourseTypeById(id, options = { cache: true, cacheTime: 10 * 60 * 1000 }) { + return request.get(`/groupCourse/types/${id}`, {}, options) +} + +export function getTypeLabels(typeId, options = { cache: true, cacheTime: 5 * 60 * 1000 }) { + return request.get(`/groupCourse/types/${typeId}/labels`, {}, options) +} + +export function bookGroupCourse(params) { + return request.post('/groupCourse/book', params) +} + +export function cancelBooking(bookingId, params) { + return request.post(`/groupCourse/booking/${bookingId}/cancel`, params) +} + +export function getMemberBookings(memberId, options = {}) { + return request.get(`/groupCourse/bookings/member/${memberId}`, {}, options) +} + +export default { + getGroupCourseList, + getGroupCoursePage, + getGroupCourseById, + getGroupCourseDetail, + createGroupCourse, + updateGroupCourse, + cancelGroupCourse, + deleteGroupCourse, + getGroupCourseTypes, + getGroupCourseTypeById, + getTypeLabels, + bookGroupCourse, + cancelBooking, + getMemberBookings +} \ No newline at end of file diff --git a/gym-manage-uniapp/components/TabBar.vue b/gym-manage-uniapp/components/TabBar.vue index 1ad9eb8..9b8d90b 100644 --- a/gym-manage-uniapp/components/TabBar.vue +++ b/gym-manage-uniapp/components/TabBar.vue @@ -91,11 +91,6 @@ function checkShouldShow() { } const shouldHide = HIDE_TABBAR_PAGES.includes(routePath) shouldShowTabBar.value = !shouldHide - console.log('=== TabBar 显示控制 ===') - console.log('原始路径:', getCurrentRoutePath()) - console.log('标准化路径:', routePath) - console.log('是否隐藏:', shouldHide) - console.log('是否显示 TabBar:', shouldShowTabBar.value) } let routeWatcher = null diff --git a/gym-manage-uniapp/components/groupCourse/CourseCard.vue b/gym-manage-uniapp/components/groupCourse/CourseCard.vue index 2ff731e..4a2588a 100644 --- a/gym-manage-uniapp/components/groupCourse/CourseCard.vue +++ b/gym-manage-uniapp/components/groupCourse/CourseCard.vue @@ -47,9 +47,19 @@ - - - {{ formatDuration(course.startTime, course.endTime) }} + + + + {{ formatDuration(course.startTime, course.endTime) }} + + + {{ label.labelName }} + @@ -109,6 +119,10 @@ const props = defineProps({ course: { type: Object, required: true + }, + courseTypeLabels: { + type: Array, + default: () => [] } }) @@ -150,9 +164,29 @@ const formatTime = (dateStr) => { const formatDuration = (startStr, endStr) => { if (!startStr || !endStr) return '' + const start = new Date(startStr) const end = new Date(endStr) - const minutes = Math.floor((end.getTime() - start.getTime()) / 60000) + + if (isNaN(start.getTime()) || isNaN(end.getTime())) { + console.warn(`[CourseCard] 无效的时间格式: startTime=${startStr}, endTime=${endStr}`) + return '' + } + + const diffMs = end.getTime() - start.getTime() + if (diffMs <= 0) { + console.warn(`[CourseCard] 结束时间小于或等于开始时间: startTime=${startStr}, endTime=${endStr}`) + return '' + } + + const maxDurationMinutes = 24 * 60 + const minutes = Math.floor(diffMs / 60000) + + if (minutes > maxDurationMinutes) { + console.warn(`[CourseCard] 课程时长超过24小时,已修正: ${minutes}分钟 -> ${maxDurationMinutes}分钟`) + return `${maxDurationMinutes}分钟以上` + } + return `${minutes}分钟` } @@ -340,19 +374,35 @@ const handleBooking = () => { flex: 1; } -/* 课程时长 */ -.course-duration { +/* 课程标签区域 */ +.course-tags { + display: flex; + flex-wrap: wrap; + gap: 12rpx; +} + +/* 标签项 */ +.tag-item { display: inline-flex; align-items: center; - gap: 8rpx; + gap: 6rpx; padding: 10rpx 20rpx; - background: linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%); border-radius: 20rpx; font-size: 24rpx; - color: #3B82F6; font-weight: 500; } +/* 时长标签 */ +.duration-tag { + background: linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%); + color: #3B82F6; +} + +/* 类型标签 */ +.type-tag { + font-weight: 600; +} + /* 卡片底部区域 */ .card-footer { padding: 24rpx 32rpx 28rpx; diff --git a/gym-manage-uniapp/components/index/CourseCard.vue b/gym-manage-uniapp/components/index/CourseCard.vue new file mode 100644 index 0000000..c125221 --- /dev/null +++ b/gym-manage-uniapp/components/index/CourseCard.vue @@ -0,0 +1,273 @@ + + + + + \ No newline at end of file diff --git a/gym-manage-uniapp/components/index/PageHeader.vue b/gym-manage-uniapp/components/index/PageHeader.vue new file mode 100644 index 0000000..8fe980f --- /dev/null +++ b/gym-manage-uniapp/components/index/PageHeader.vue @@ -0,0 +1,114 @@ + + + + + \ No newline at end of file diff --git a/gym-manage-uniapp/components/index/RecommendCourses.vue b/gym-manage-uniapp/components/index/RecommendCourses.vue index b02fc15..64d9d3f 100644 --- a/gym-manage-uniapp/components/index/RecommendCourses.vue +++ b/gym-manage-uniapp/components/index/RecommendCourses.vue @@ -6,7 +6,7 @@ 推荐课程 - + 查看更多 @@ -19,57 +19,12 @@ - - - - - - - - - {{ course.tag }} - - - - {{ course.name }} - - - - - - - - {{ course.duration }} - - - - - - - {{ course.level }} - - - - - - - - - - - - {{ course.participants }}人参与 - - - - 去参与 - - - + :course="course" + @join="handleJoinCourse" + /> @@ -78,6 +33,7 @@ \ No newline at end of file diff --git a/gym-manage-uniapp/composables/useGroupCourseList.js b/gym-manage-uniapp/composables/useGroupCourseList.js index d923875..8d70904 100644 --- a/gym-manage-uniapp/composables/useGroupCourseList.js +++ b/gym-manage-uniapp/composables/useGroupCourseList.js @@ -1,32 +1,27 @@ import { ref, computed } from 'vue' -import { groupCourseService } from '@/request_api/groupCourse.mock.js' +import { getGroupCoursePage, getTypeLabels } from '@/api/groupCourse.js' export function useGroupCourseList() { - // 分页相关 - const pageNum = ref(1) + const pageNum = ref(0) const pageSize = ref(10) const total = ref(0) const totalPages = ref(0) const loading = ref(false) const hasMore = ref(true) - // 团课列表数据 const courseList = ref([]) - // 搜索相关 const searchKeyword = ref('') const hotKeywords = ref(['燃脂', '瑜伽', '单车', '普拉提', '高强度']) - // 排序相关 const sortOptions = ref([ - { label: '默认排序', value: 'default' }, - { label: '价格从低到高', value: 'priceAsc' }, - { label: '价格从高到低', value: 'priceDesc' }, - { label: '剩余名额最多', value: 'spotsDesc' } + { label: '默认排序', value: 'id', order: 'asc' }, + { label: '价格从低到高', value: 'storedValueAmount', order: 'asc' }, + { label: '价格从高到低', value: 'storedValueAmount', order: 'desc' }, + { label: '剩余名额最多', value: 'currentMembers', order: 'asc' } ]) const sortIndex = ref(0) - // 时间段选择相关 const timePeriodOptions = ref([ { label: '全部', value: 'all' }, { label: '早上 (6-12 点)', value: 'morning', startHour: 6, endHour: 12 }, @@ -35,24 +30,20 @@ export function useGroupCourseList() { ]) const timePeriodIndex = ref(0) - // 时间筛选相关 const showTimePicker = ref(false) const startDate = ref('') const endDate = ref('') const timeRangeText = ref('') - // 筛选后的课程列表 const filteredCourseList = computed(() => { let result = [...courseList.value] - // 关键词搜索 if (searchKeyword.value) { result = result.filter(course => course.courseName.includes(searchKeyword.value) ) } - // 时间范围筛选 if (startDate.value || endDate.value) { result = result.filter(course => { const courseDate = course.startTime.split('T')[0] @@ -62,7 +53,6 @@ export function useGroupCourseList() { }) } - // 时间段筛选 const timePeriod = timePeriodOptions.value[timePeriodIndex.value] if (timePeriod.value !== 'all') { result = result.filter(course => { @@ -71,20 +61,18 @@ export function useGroupCourseList() { }) } - // 排序 - const sortType = sortOptions.value[sortIndex.value].value - if (sortType === 'priceAsc') { - result.sort((a, b) => (a.storedValueAmount || a.pointCardAmount) - (b.storedValueAmount || b.pointCardAmount)) - } else if (sortType === 'priceDesc') { - result.sort((a, b) => (b.storedValueAmount || b.pointCardAmount) - (a.storedValueAmount || a.pointCardAmount)) - } else if (sortType === 'spotsDesc') { - result.sort((a, b) => (b.maxMembers - b.currentMembers) - (a.maxMembers - a.currentMembers)) + const sortType = sortOptions.value[sortIndex.value] + if (sortType.value !== 'id' || sortType.order !== 'asc') { + result.sort((a, b) => { + const valA = a[sortType.value] || 0 + const valB = b[sortType.value] || 0 + return sortType.order === 'asc' ? valA - valB : valB - valA + }) } return result }) - // 获取所有搜索参数 const getAllSearchParams = (searchBarRef, filterSectionRef, timePeriodRef, timeRangePickerRef) => { const searchParams = searchBarRef?.getSearchParams?.() || { keyword: searchKeyword.value } const filterParams = filterSectionRef?.getFilterParams?.() || { sortType: sortOptions.value[sortIndex.value].value } @@ -102,27 +90,24 @@ export function useGroupCourseList() { return allParams } - // 搜索处理 const handleSearch = (params) => { console.log('[useGroupCourseList] 搜索触发:', params) - uni.showToast({ - title: params.keyword ? `搜索:${params.keyword}` : '请输入关键词', - icon: 'none' - }) + searchKeyword.value = params.keyword || '' + pageNum.value = 0 + fetchCourseList() } - // 时间段变化处理 const onTimePeriodChange = (option) => { console.log('[useGroupCourseList] 时间段选择:', option) } - // 时间范围确认处理 const onTimeRangeConfirm = (params) => { console.log('[useGroupCourseList] 时间范围确认:', params) + startDate.value = params.startDate || '' + endDate.value = params.endDate || '' timeRangeText.value = params.timeRangeText } - // 预约处理 const handleBooking = (course) => { console.log('[useGroupCourseList] 预约课程:', course) uni.showToast({ @@ -131,7 +116,6 @@ export function useGroupCourseList() { }) } - // 跳转详情 const goDetail = (courseId) => { console.log('[useGroupCourseList] 跳转到课程详情:', courseId) uni.navigateTo({ @@ -139,20 +123,33 @@ export function useGroupCourseList() { }) } - // 获取团课列表 const fetchCourseList = async (isLoadMore = false) => { if (loading.value) return loading.value = true try { - const result = await groupCourseService.getList({ - pageNum: pageNum.value, - pageSize: pageSize.value + 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 }) - if (result.code === 0 && result.data) { - const { list, total: totalCount, pageNum: currentPage, totalPages: pages } = result.data + console.log('[useGroupCourseList] 响应结果:', JSON.stringify(result, null, 2)) + + if (result && result.content) { + const { content: list, totalElements: totalCount, currentPage, totalPages: pages } = result if (isLoadMore) { courseList.value = [...courseList.value, ...list] @@ -163,7 +160,7 @@ export function useGroupCourseList() { total.value = totalCount pageNum.value = currentPage totalPages.value = pages - hasMore.value = pageNum.value < totalPages.value + hasMore.value = currentPage < pages - 1 console.log('[useGroupCourseList] 团课列表获取成功:', { total: total.value, @@ -172,29 +169,41 @@ export function useGroupCourseList() { hasMore: hasMore.value }) } else { - console.error('[useGroupCourseList] 获取团课列表失败:', result.message) + console.error('[useGroupCourseList] 获取团课列表失败:', { + result: result, + message: result?.message || '未知错误', + code: result?.code, + success: result?.success + }) } } catch (error) { - console.error('[useGroupCourseList] 获取团课列表异常:', error) + console.error('[useGroupCourseList] 获取团课列表异常 - 错误详情:', { + error: error, + message: error?.message || '无错误信息', + code: error?.code, + statusCode: error?.statusCode, + response: error?.response, + stack: error?.stack + }) + uni.showToast({ + title: `获取课程列表失败: ${error?.message || '网络错误'}`, + icon: 'none' + }) } finally { loading.value = false } } - // 加载更多 const loadMore = () => { if (!hasMore.value || loading.value) return - pageNum.value++ fetchCourseList(true) } - // 滚动到底部触发 const onScrollToLower = () => { loadMore() } return { - // 状态 pageNum, pageSize, total, @@ -214,7 +223,6 @@ export function useGroupCourseList() { timeRangeText, filteredCourseList, - // 方法 getAllSearchParams, handleSearch, onTimePeriodChange, @@ -225,4 +233,4 @@ export function useGroupCourseList() { loadMore, onScrollToLower } -} +} \ No newline at end of file diff --git a/gym-manage-uniapp/groupcourse-api.md b/gym-manage-uniapp/groupcourse-api.md new file mode 100644 index 0000000..cad1497 --- /dev/null +++ b/gym-manage-uniapp/groupcourse-api.md @@ -0,0 +1,1518 @@ +# 团课管理模块 API 文档 + +> **文档版本**: v1.0 +> **创建日期**: 2026-06-02 +> **作者**: 张翔 +> **状态**: 正式发布 + +--- + +## 📋 目录 + +1. [概述](#概述) +2. [基础路径](#基础路径) +3. [团课管理接口](#团课管理接口) + - [获取所有团课](#获取所有团课) + - [分页获取团课](#分页获取团课) + - [根据ID获取团课详情](#根据ID获取团课详情) + - [创建团课](#创建团课) + - [更新团课](#更新团课) + - [取消团课](#取消团课) + - [团课签到](#团课签到) + - [删除团课](#删除团课) +4. [团课预约接口](#团课预约接口) + - [预约团课](#预约团课) + - [取消预约](#取消预约) + - [查询会员预约记录](#查询会员预约记录) + - [查询预约详情](#查询预约详情) + - [查询课程预约记录](#查询课程预约记录) +5. [团课类型管理接口](#团课类型管理接口) + - [获取所有团课类型](#获取所有团课类型) + - [根据ID获取团课类型](#根据ID获取团课类型) + - [搜索团课类型](#搜索团课类型) + - [根据分类获取团课类型](#根据分类获取团课类型) + - [获取所有分类](#获取所有分类) + - [创建团课类型](#创建团课类型) + - [更新团课类型](#更新团课类型) + - [删除团课类型](#删除团课类型) +6. [团课标签管理接口](#团课标签管理接口) + - [获取所有标签](#获取所有标签) + - [根据ID获取标签](#根据ID获取标签) + - [搜索标签](#搜索标签) + - [获取类型的标签](#获取类型的标签) + - [创建标签](#创建标签) + - [更新标签](#更新标签) + - [删除标签](#删除标签) + - [为类型添加标签](#为类型添加标签) + - [从类型移除标签](#从类型移除标签) + - [清空类型标签](#清空类型标签) +7. [数据模型](#数据模型) + - [GroupCourse(团课)](#GroupCourse团课) + - [GroupCourseBooking(团课预约)](#GroupCourseBooking团课预约) + - [GroupCourseType(团课类型)](#GroupCourseType团课类型) +7. [状态码说明](#状态码说明) +8. [业务规则](#业务规则) + +--- + +## 概述 + +团课管理模块提供团课的创建、编辑、查询、取消和签到功能,以及团课预约相关操作。采用 Spring WebFlux 响应式编程,支持高并发场景。 + +## 基础路径 + +所有接口的基础路径为: `http://{host}:{port}/api/groupCourse` + +--- + +## 团课管理接口 + +### 获取所有团课 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/list` | +| **所属文件** | `GroupCourseHandler.java` | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| includeDeleted | boolean | 否 | false | 是否包含已删除的团课 | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "courseName": "瑜伽入门", + "coachId": 1, + "courseType": 1, + "startTime": "2026-06-02T09:00:00", + "endTime": "2026-06-02T10:00:00", + "maxMembers": 20, + "currentMembers": 15, + "status": 0, + "location": "健身房A区", + "coverImage": "https://example.com/yoga.jpg", + "description": "适合初学者的瑜伽课程", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" + } +] +``` + +--- + +### 分页获取团课 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/page` | +| **所属文件** | `GroupCourseHandler.java` | + +**请求体**: + +```json +{ + "page": 0, + "size": 10, + "sort": "id", + "order": "asc", + "keyword": "瑜伽" +} +``` + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| page | int | 否 | 0 | 页码,从0开始 | +| size | int | 否 | 10 | 每页数量,最大100 | +| sort | string | 否 | id | 排序字段 | +| order | string | 否 | asc | 排序方式(asc/desc) | +| keyword | string | 否 | - | 搜索关键词 | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| includeDeleted | boolean | 否 | false | 是否包含已删除的团课 | + +**成功响应** (200 OK): + +```json +{ + "data": [...], + "totalPages": 5, + "totalElements": 45, + "page": 0, + "size": 10 +} +``` + +--- + +### 根据ID获取团课详情 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/{id}` | +| **所属文件** | `GroupCourseHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课ID | + +**成功响应** (200 OK): + +```json +{ + "id": 1, + "courseName": "瑜伽入门", + "coachId": 1, + "courseType": 1, + "startTime": "2026-06-02T09:00:00", + "endTime": "2026-06-02T10:00:00", + "maxMembers": 20, + "currentMembers": 15, + "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" +} +``` + +**失败响应** (404 Not Found): + +```json +{} +``` + +--- + +### 根据ID获取团课完整信息 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/{id}/detail` | +| **所属文件** | `GroupCourseHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课ID | + +**成功响应** (200 OK): + +```json +{ + "id": 1, + "courseName": "瑜伽入门", + "coachId": 1, + "courseType": 1, + "startTime": "2026-06-02T09:00:00", + "endTime": "2026-06-02T10:00:00", + "maxMembers": 20, + "currentMembers": 15, + "status": 0, + "location": "健身房A区", + "coverImage": "https://example.com/yoga.jpg", + "description": "适合初学者的瑜伽课程", + "pointCardAmount": 1, + "storedValueAmount": 50.00, + "typeName": "瑜伽入门", + "typeCategory": "柔韧与平衡类", + "baseDifficulty": 2, + "difficultyLevel": "初级", + "calculatedDifficulty": 2, + "typeInfo": { + "id": 1, + "typeName": "瑜伽入门", + "baseDifficulty": 2, + "calculatedDifficulty": 2, + "difficultyLevel": "初级", + "description": "适合初学者的瑜伽课程,注重基础体式", + "category": "柔韧与平衡类", + "labels": [ + {"id": 1, "labelName": "适合新手", "color": "#52c41a"}, + {"id": 3, "labelName": "减压放松", "color": "#1890ff"} + ] + }, + "labels": [ + {"id": 1, "labelName": "适合新手", "color": "#52c41a"}, + {"id": 3, "labelName": "减压放松", "color": "#1890ff"} + ], + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" +} +``` + +**失败响应** (404 Not Found): + +```json +{} +``` + +**说明**: 此接口返回团课的完整信息,包括: +- 团课基础信息 +- 团课对应的类型信息(包含基础难度、综合难度、难度等级等) +- 该类型的所有标签信息 + +--- + +### 创建团课 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse` | +| **所属文件** | `GroupCourseHandler.java` | + +**请求体**: + +```json +{ + "courseName": "动感单车", + "coachId": 2, + "courseType": 2, + "startTime": "2026-06-05T18:00:00", + "endTime": "2026-06-05T19:00:00", + "maxMembers": 25, + "location": "健身房B区", + "coverImage": "https://example.com/spinning.jpg", + "description": "高强度有氧运动课程", + "pointCardAmount": 1, + "storedValueAmount": 50.00 +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| courseName | String | **是** | 课程名称 | +| coachId | Long | 否 | 教练ID | +| courseType | Long | 否 | 课程类型 | +| startTime | LocalDateTime | 否 | 开始时间 | +| endTime | LocalDateTime | 否 | 结束时间 | +| maxMembers | Integer | 否 | 最大参与人数,默认20 | +| location | String | 否 | 上课地点 | +| coverImage | String | 否 | 封面图URL | +| description | String | 否 | 课程描述 | +| pointCardAmount | Integer | 否 | 点卡额度(消耗次数),默认1 | +| storedValueAmount | BigDecimal | 否 | 储值卡额度(消耗金额),默认0 | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "团课创建成功", + "data": { + "id": 2, + "courseName": "动感单车", + "coachId": 2, + "courseType": 2, + "startTime": "2026-06-05T18:00:00", + "endTime": "2026-06-05T19:00:00", + "maxMembers": 25, + "currentMembers": 0, + "status": 0, + "location": "健身房B区", + "coverImage": "https://example.com/spinning.jpg", + "description": "高强度有氧运动课程", + "pointCardAmount": 1, + "storedValueAmount": 50.00 + } +} +``` + +**失败响应** (400 Bad Request): + +```json +{ + "success": false, + "message": "课程名称不能为空" +} +``` + +--- + +### 更新团课 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | PUT | +| **接口路径** | `/api/groupCourse/{id}` | +| **所属文件** | `GroupCourseHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课ID | + +**请求体**: + +```json +{ + "courseName": "动感单车升级版", + "coachId": 2, + "maxMembers": 30, + "description": "升级版高强度有氧运动课程", + "pointCardAmount": 2, + "storedValueAmount": 80.00 +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| courseName | String | 否 | 课程名称 | +| coachId | Long | 否 | 教练ID | +| courseType | Long | 否 | 课程类型 | +| startTime | LocalDateTime | 否 | 开始时间 | +| endTime | LocalDateTime | 否 | 结束时间 | +| maxMembers | Integer | 否 | 最大参与人数 | +| location | String | 否 | 上课地点 | +| coverImage | String | 否 | 封面图URL | +| description | String | 否 | 课程描述 | +| pointCardAmount | Integer | 否 | 点卡额度(消耗次数) | +| storedValueAmount | BigDecimal | 否 | 储值卡额度(消耗金额) | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "团课更新成功", + "data": { + "id": 2, + "courseName": "动感单车升级版", + "coachId": 2, + "maxMembers": 30, + "description": "升级版高强度有氧运动课程", + "pointCardAmount": 2, + "storedValueAmount": 80.00 + } +} +``` + +**失败响应** (400 Bad Request): + +```json +{ + "success": false, + "message": "团课不存在" +} +``` + +--- + +### 取消团课 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/{id}/cancel` | +| **所属文件** | `GroupCourseHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "团课取消成功", + "data": { + "id": 2, + "status": 1 + } +} +``` + +**失败响应** (400 Bad Request): + +```json +{ + "success": false, + "message": "课程取消需提前24小时" +} +``` + +--- + +### 团课签到 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/{courseId}/signin` | +| **所属文件** | `GroupCourseHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| courseId | Long | 是 | 团课ID | + +**请求体**: + +```json +{ + "memberId": 1001 +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| memberId | Long | **是** | 会员ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "签到成功", + "data": { + "id": 2, + "currentMembers": 16 + } +} +``` + +**失败响应** (400 Bad Request): + +```json +{ + "success": false, + "message": "课程已满员" +} +``` + +--- + +### 删除团课 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | DELETE | +| **接口路径** | `/api/groupCourse/{id}` | +| **所属文件** | `GroupCourseHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "团课删除成功" +} +``` + +--- + +## 团课预约接口 + +### 预约团课 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/book` | +| **所属文件** | `GroupCourseBookingHandler.java` | + +**请求体**: + +```json +{ + "courseId": 1, + "memberId": 1001, + "memberCardRecordId": 5001 +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| courseId | Long | **是** | 团课ID | +| memberId | Long | **是** | 会员ID | +| memberCardRecordId | Long | **是** | 会员卡记录ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "预约成功", + "data": { + "id": 100, + "courseId": 1, + "memberId": 1001, + "memberCardRecordId": 5001, + "bookingTime": "2026-06-02T08:00:00", + "status": "0" + } +} +``` + +--- + +### 取消预约 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/booking/{bookingId}/cancel` | +| **所属文件** | `GroupCourseBookingHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| bookingId | Long | 是 | 预约ID | + +**请求体**: + +```json +{ + "memberId": 1001 +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| memberId | Long | **是** | 会员ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "取消成功", + "data": { + "id": 100, + "status": "1", + "cancelTime": "2026-06-02T09:00:00" + } +} +``` + +--- + +### 查询会员预约记录 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/bookings/member/{memberId}` | +| **所属文件** | `GroupCourseBookingHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| memberId | Long | 是 | 会员ID | + +**成功响应** (200 OK): + +```json +[ + { + "id": 100, + "courseId": 1, + "courseName": "瑜伽入门", + "memberId": 1001, + "memberCardRecordId": 5001, + "bookingTime": "2026-06-02T08:00:00", + "status": "0", + "courseStartTime": "2026-06-02T09:00:00", + "location": "健身房A区" + } +] +``` + +--- + +### 查询预约详情 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/bookings/{bookingId}` | +| **所属文件** | `GroupCourseBookingHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| bookingId | Long | 是 | 预约ID | + +**成功响应** (200 OK): + +```json +{ + "id": 100, + "courseId": 1, + "courseName": "瑜伽入门", + "memberId": 1001, + "memberCardRecordId": 5001, + "bookingTime": "2026-06-02T08:00:00", + "status": "0", + "courseStartTime": "2026-06-02T09:00:00", + "courseEndTime": "2026-06-02T10:00:00", + "location": "健身房A区" +} +``` + +**失败响应** (404 Not Found): + +```json +{} +``` + +--- + +### 查询课程预约记录 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/bookings/course/{courseId}` | +| **所属文件** | `GroupCourseBookingHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| courseId | Long | 是 | 团课ID | + +**成功响应** (200 OK): + +```json +[ + { + "id": 100, + "courseId": 1, + "courseName": "瑜伽入门", + "memberId": 1001, + "bookingTime": "2026-06-02T08:00:00", + "status": "0" + } +] +``` + +--- + +## 团课类型管理接口 + +### 获取所有团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| includeDeleted | boolean | 否 | false | 是否包含已删除的类型 | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "typeName": "瑜伽入门", + "baseDifficulty": 2, + "calculatedDifficulty": 2, + "difficultyLevel": "初级", + "description": "适合初学者的瑜伽课程,注重基础体式", + "category": "柔韧与平衡类", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" + } +] +``` + +--- + +### 根据ID获取团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types/{id}` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课类型ID | + +**成功响应** (200 OK): + +```json +{ + "id": 1, + "typeName": "瑜伽入门", + "baseDifficulty": 2, + "calculatedDifficulty": 2, + "difficultyLevel": "初级", + "description": "适合初学者的瑜伽课程,注重基础体式", + "category": "柔韧与平衡类", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" +} +``` + +**失败响应** (404 Not Found): + +```json +{} +``` + +--- + +### 搜索团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types/search` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| keyword | string | 否 | - | 搜索关键词(匹配类型名称) | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "typeName": "瑜伽入门", + "baseDifficulty": 2, + "difficultyLevel": "初级", + "category": "柔韧与平衡类" + } +] +``` + +--- + +### 根据分类获取团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types/category/{category}` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| category | String | 是 | 分类名称 | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| keyword | string | 否 | - | 搜索关键词 | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "typeName": "瑜伽入门", + "baseDifficulty": 2, + "difficultyLevel": "初级", + "category": "柔韧与平衡类" + } +] +``` + +--- + +### 获取所有分类 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types/categories` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**成功响应** (200 OK): + +```json +["基础有氧与热身", "固定器械训练", "自重基础动作", "自由重量杠铃/哑铃", "高强度与爆发力", "柔韧与平衡类"] +``` + +--- + +### 创建团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/types` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**请求体**: + +```json +{ + "typeName": "核心力量训练", + "baseDifficulty": 4, + "description": "针对核心肌群的专项训练课程", + "category": "自重基础动作" +} +``` + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| typeName | String | **是** | - | 类型名称 | +| baseDifficulty | Integer | 否 | 1 | 基础难度(1-10) | +| description | String | 否 | - | 类型描述 | +| category | String | 否 | - | 分类 | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "团课类型创建成功", + "data": { + "id": 40, + "typeName": "核心力量训练", + "baseDifficulty": 4, + "calculatedDifficulty": 4, + "difficultyLevel": "中级", + "description": "针对核心肌群的专项训练课程", + "category": "自重基础动作" + } +} +``` + +**失败响应** (400 Bad Request): + +```json +{ + "success": false, + "message": "类型名称不能为空" +} +``` + +--- + +### 更新团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | PUT | +| **接口路径** | `/api/groupCourse/types/{id}` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课类型ID | + +**请求体**: + +```json +{ + "typeName": "核心力量训练进阶", + "baseDifficulty": 6, + "description": "进阶核心训练课程" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| typeName | String | 否 | 类型名称 | +| baseDifficulty | Integer | 否 | 基础难度(1-10) | +| description | String | 否 | 类型描述 | +| category | String | 否 | 分类 | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "团课类型更新成功", + "data": { + "id": 40, + "typeName": "核心力量训练进阶", + "baseDifficulty": 6, + "calculatedDifficulty": 6, + "difficultyLevel": "中高级", + "description": "进阶核心训练课程", + "category": "自重基础动作" + } +} +``` + +--- + +### 删除团课类型 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | DELETE | +| **接口路径** | `/api/groupCourse/types/{id}` | +| **所属文件** | `GroupCourseTypeHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 团课类型ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "团课类型删除成功" +} +``` + +--- + +## 团课标签管理接口 + +### 获取所有标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/labels` | +| **所属文件** | `CourseLabelHandler.java` | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "labelName": "适合新手", + "color": "#52c41a", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" + }, + { + "id": 2, + "labelName": "中级过渡", + "color": "#faad14", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" + } +] +``` + +--- + +### 根据ID获取标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/labels/{id}` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 标签ID | + +**成功响应** (200 OK): + +```json +{ + "id": 1, + "labelName": "适合新手", + "color": "#52c41a", + "createdAt": "2026-06-01T10:00:00", + "updatedAt": "2026-06-01T10:00:00" +} +``` + +**失败响应** (404 Not Found): + +```json +{} +``` + +--- + +### 搜索标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/labels/search` | +| **所属文件** | `CourseLabelHandler.java` | + +**请求参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| keyword | string | 否 | - | 搜索关键词(匹配标签名称) | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "labelName": "适合新手", + "color": "#52c41a" + } +] +``` + +--- + +### 获取类型的标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | GET | +| **接口路径** | `/api/groupCourse/types/{typeId}/labels` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| typeId | Long | 是 | 团课类型ID | + +**成功响应** (200 OK): + +```json +[ + { + "id": 1, + "labelName": "适合新手", + "color": "#52c41a" + }, + { + "id": 3, + "labelName": "减压放松", + "color": "#1890ff" + } +] +``` + +--- + +### 创建标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/labels` | +| **所属文件** | `CourseLabelHandler.java` | + +**请求体**: + +```json +{ + "labelName": "燃脂塑形", + "color": "#f5222d" +} +``` + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| labelName | String | **是** | - | 标签名称(最大50字符) | +| color | String | 否 | #1890ff | 标签颜色(十六进制颜色值) | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签创建成功", + "data": { + "id": 15, + "labelName": "燃脂塑形", + "color": "#f5222d" + } +} +``` + +**失败响应** (400 Bad Request): + +```json +{ + "success": false, + "message": "标签名称已存在" +} +``` + +--- + +### 更新标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | PUT | +| **接口路径** | `/api/groupCourse/labels/{id}` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 标签ID | + +**请求体**: + +```json +{ + "labelName": "燃脂塑形进阶", + "color": "#fa541c" +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| labelName | String | 否 | 标签名称(最大50字符) | +| color | String | 否 | 标签颜色(十六进制颜色值) | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签更新成功", + "data": { + "id": 15, + "labelName": "燃脂塑形进阶", + "color": "#fa541c" + } +} +``` + +--- + +### 删除标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | DELETE | +| **接口路径** | `/api/groupCourse/labels/{id}` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | Long | 是 | 标签ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签删除成功" +} +``` + +--- + +### 为类型添加标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | POST | +| **接口路径** | `/api/groupCourse/types/{typeId}/labels` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| typeId | Long | 是 | 团课类型ID | + +**请求体**: + +```json +{ + "labelIds": [1, 3, 5] +} +``` + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| labelIds | List\ | **是** | 要添加的标签ID列表 | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签添加成功" +} +``` + +--- + +### 从类型移除标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | DELETE | +| **接口路径** | `/api/groupCourse/types/{typeId}/labels/{labelId}` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| typeId | Long | 是 | 团课类型ID | +| labelId | Long | 是 | 标签ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签移除成功" +} +``` + +--- + +### 清空类型标签 + +| 属性 | 值 | +|------|-----| +| **HTTP方法** | DELETE | +| **接口路径** | `/api/groupCourse/types/{typeId}/labels` | +| **所属文件** | `CourseLabelHandler.java` | + +**路径参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| typeId | Long | 是 | 团课类型ID | + +**成功响应** (200 OK): + +```json +{ + "success": true, + "message": "标签清空成功" +} +``` + +--- + +## 数据模型 + +### GroupCourse(团课) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 主键ID | +| courseName | String | 课程名称 | +| coachId | Long | 教练ID | +| courseType | Long | 课程类型 | +| startTime | LocalDateTime | 开始时间 | +| endTime | LocalDateTime | 结束时间 | +| maxMembers | Integer | 最大参与人数 | +| currentMembers | Integer | 当前参与人数 | +| status | Long | 状态(0-正常,1-已取消,2-已结束) | +| location | String | 上课地点 | +| coverImage | String | 封面图URL | +| description | String | 课程描述 | +| pointCardAmount | Integer | 点卡额度(消耗次数),默认1 | +| storedValueAmount | BigDecimal | 储值卡额度(消耗金额),默认0 | +| createdBy | String | 创建人 | +| updatedBy | String | 更新人 | +| createdAt | LocalDateTime | 创建时间 | +| updatedAt | LocalDateTime | 更新时间 | +| deletedAt | LocalDateTime | 删除时间(软删除) | + +### GroupCourseBooking(团课预约) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 主键ID | +| courseId | Long | 团课ID | +| courseName | String | 团课名称 | +| memberId | Long | 会员ID | +| memberCardRecordId | Long | 会员卡记录ID | +| bookingTime | LocalDateTime | 预约时间 | +| status | String | 状态(0-已预约,1-已取消,2-已出席,3-缺席) | +| cancelTime | LocalDateTime | 取消时间 | +| courseStartTime | LocalDateTime | 课程开始时间 | +| courseEndTime | LocalDateTime | 课程结束时间 | +| location | String | 上课地点 | +| createdBy | String | 创建人 | +| updatedBy | String | 更新人 | +| createdAt | LocalDateTime | 创建时间 | +| updatedAt | LocalDateTime | 更新时间 | +| deletedAt | LocalDateTime | 删除时间(软删除) | + +### GroupCourseType(团课类型) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 主键ID | +| typeName | String | 类型名称 | +| baseDifficulty | Integer | 基础难度(1-10) | +| calculatedDifficulty | Integer | 综合难度系数(预留扩展字段) | +| difficultyLevel | String | 难度等级描述(初级/中级/中高级/高级/专家级) | +| description | String | 类型描述 | +| category | String | 分类(如:有氧、力量、柔韧等) | +| labels | List\ | 标签列表 | +| createdBy | String | 创建人 | +| updatedBy | String | 更新人 | +| createdAt | LocalDateTime | 创建时间 | +| updatedAt | LocalDateTime | 更新时间 | +| deletedAt | LocalDateTime | 删除时间(软删除) | + +### CourseLabel(团课标签) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 主键ID | +| labelName | String | 标签名称(最大50字符) | +| color | String | 标签颜色(十六进制颜色值,默认#1890ff) | +| createdBy | String | 创建人 | +| updatedBy | String | 更新人 | +| createdAt | LocalDateTime | 创建时间 | +| updatedAt | LocalDateTime | 更新时间 | +| deletedAt | LocalDateTime | 删除时间(软删除) | + +### GroupCourseDetail(团课完整信息) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 主键ID | +| courseName | String | 课程名称 | +| coachId | Long | 教练ID | +| courseType | Long | 课程类型ID | +| startTime | LocalDateTime | 开始时间 | +| endTime | LocalDateTime | 结束时间 | +| maxMembers | Integer | 最大参与人数 | +| currentMembers | Integer | 当前参与人数 | +| status | Long | 状态(0-正常,1-已取消,2-已结束) | +| location | String | 上课地点 | +| coverImage | String | 封面图URL | +| description | String | 课程描述 | +| pointCardAmount | Integer | 点卡额度(消耗次数) | +| storedValueAmount | BigDecimal | 储值卡额度(消耗金额) | +| typeName | String | 类型名称(快捷访问) | +| typeCategory | String | 类型分类(快捷访问) | +| baseDifficulty | Integer | 基础难度(快捷访问) | +| difficultyLevel | String | 难度等级描述(快捷访问) | +| calculatedDifficulty | Integer | 综合难度系数(快捷访问) | +| typeInfo | GroupCourseType | 类型信息 | +| labels | List\ | 标签列表 | +| createdAt | LocalDateTime | 创建时间 | +| updatedAt | LocalDateTime | 更新时间 | + +**难度等级对应关系**: + +| 基础难度 | 难度等级 | +|----------|----------| +| 1-2 | 初级 | +| 3-4 | 中级 | +| 5-6 | 中高级 | +| 7-8 | 高级 | +| 9-10 | 专家级 | + +**难度扩展说明**: + +`calculatedDifficulty` 字段为预留扩展字段,当前实现仅返回 `baseDifficulty`。未来可扩展的影响因素包括: + +1. **课程时长系数**:时长越长难度越高 +2. **教练难度调整系数**:教练可根据实际情况微调 +3. **会员等级适配系数**:根据会员等级动态调整显示难度 +4. **课程强度系数**:高强度课程难度加成 + +--- + +## 状态码说明 + +### 团课状态 + +| 状态码 | 含义 | +|--------|------| +| 0 | 正常 | +| 1 | 已取消 | +| 2 | 已结束 | + +### 预约状态 + +| 状态码 | 含义 | +|--------|------| +| 0 | 已预约 | +| 1 | 已取消 | +| 2 | 已出席 | +| 3 | 缺席 | + +--- + +## 业务规则 + +### 团课管理 +1. **创建团课**:课程名称为必填项 +2. **取消团课**:需提前24小时通知,否则拒绝操作 +3. **团课签到**:验证课程状态必须为正常,且未达最大人数限制 +4. **删除团课**:采用软删除机制,数据保留可恢复 + +### 团课预约 +1. **预约团课**:需验证会员卡有效性和课程名额 +2. **取消预约**:需在课程开始前至少2小时取消 + +--- + +## 附录:错误响应格式 + +所有接口的错误响应统一格式: + +```json +{ + "success": false, + "message": "错误描述信息" +} +``` + +--- + +*文档结束* \ No newline at end of file diff --git a/gym-manage-uniapp/pages.json b/gym-manage-uniapp/pages.json index 378467d..152613b 100644 --- a/gym-manage-uniapp/pages.json +++ b/gym-manage-uniapp/pages.json @@ -14,6 +14,7 @@ { "path": "pages/course/index", "style": { + "navigationStyle": "custom", "navigationBarTitleText": "课程", "app-plus": { "animationType": "fade-in", @@ -244,6 +245,7 @@ { "path": "pages/groupCourse/list", "style": { + "navigationStyle": "custom", "navigationBarTitleText": "团课列表" } }, @@ -266,6 +268,13 @@ "navigationBarTitleText": "搜索课程" } }, + { + "path": "pages/recommendCourses/index", + "style": { + "navigationBarTitleText": "推荐课程", + "navigationStyle": "custom" + } + }, { "path": "components/global/GlobalLoading", "style": { diff --git a/gym-manage-uniapp/pages/course/index.vue b/gym-manage-uniapp/pages/course/index.vue index 8ae02bd..297f288 100644 --- a/gym-manage-uniapp/pages/course/index.vue +++ b/gym-manage-uniapp/pages/course/index.vue @@ -3,10 +3,7 @@ - - 课程 - 精品团课 · 私教 · 线上课 - + @@ -44,6 +41,7 @@ import { ref } from 'vue' import { onLoad, onShow } from '@dcloudio/uni-app' import RecommendCourses from '@/components/index/RecommendCourses.vue' +import PageHeader from '@/components/index/PageHeader.vue' import TabBar from '@/components/TabBar.vue' import { PAGE, navigateToPage } from '@/common/constants/routes.js' @@ -136,25 +134,10 @@ function goMyCourses() { padding-bottom: 160rpx; position: relative; z-index: 2; + background: #F5F7FA; } -.tab-page__header { - padding: 48rpx 32rpx 16rpx; -} -.tab-page__title { - display: block; - font-size: 40rpx; - font-weight: $font-weight-bold; - color: $text-dark; -} - -.tab-page__subtitle { - display: block; - margin-top: 8rpx; - font-size: 24rpx; - color: $text-muted; -} .tab-page__actions { display: flex; diff --git a/gym-manage-uniapp/pages/groupCourse/detail.vue b/gym-manage-uniapp/pages/groupCourse/detail.vue index 38c5df9..846ad1d 100644 --- a/gym-manage-uniapp/pages/groupCourse/detail.vue +++ b/gym-manage-uniapp/pages/groupCourse/detail.vue @@ -1,7 +1,7 @@