diff --git a/gym-manage-uniapp/api/groupCourse.js b/gym-manage-uniapp/api/groupCourse.js
index 89c5787..801d2d2 100644
--- a/gym-manage-uniapp/api/groupCourse.js
+++ b/gym-manage-uniapp/api/groupCourse.js
@@ -54,6 +54,7 @@ export function searchGroupCourse(params = {}, options = {}) {
timePeriod,
priceSort,
remainingMost,
+ isRecurring,
page = 0,
size = 10
} = params
@@ -67,6 +68,7 @@ export function searchGroupCourse(params = {}, options = {}) {
if (timePeriod) requestBody.timePeriod = timePeriod
if (priceSort) requestBody.priceSort = priceSort
if (remainingMost !== undefined) requestBody.remainingMost = remainingMost
+ if (isRecurring !== undefined) requestBody.isRecurring = isRecurring
return request.post('/groupCourse/search', requestBody, options)
}
@@ -91,14 +93,24 @@ export function getMemberBookings(memberId, options = {}) {
return request.get(`/groupCourse/bookings/member/${memberId}`, {}, options)
}
-export function getActiveRecommendCourses(options = { cache: true, cacheTime: 5 * 60 * 1000 }) {
+export function getActiveRecommendCourses(options = { cache: false }) {
return request.get('/groupCourse/recommend/active', {}, options)
}
-export function getGroupCourseRecommendList(params = {}, options = { cache: true, cacheTime: 5 * 60 * 1000 }) {
+export function getGroupCourseRecommendList(params = {}, options = { cache: false }) {
return request.get('/groupCourse/recommend/list', params, options)
}
+/**
+ * 扫码签到
+ * 扫描团课二维码后签到,将预约状态更新为已出席
+ * @param {number} courseId - 团课ID
+ * @param {number} memberId - 会员ID
+ */
+export function qrSignInGroupCourse(courseId, memberId) {
+ return request.post(`/groupCourse/${courseId}/qrsignin`, { memberId })
+}
+
export default {
getGroupCourseList,
getGroupCoursePage,
diff --git a/gym-manage-uniapp/api/main.js b/gym-manage-uniapp/api/main.js
index 916aa2a..6656265 100644
--- a/gym-manage-uniapp/api/main.js
+++ b/gym-manage-uniapp/api/main.js
@@ -328,6 +328,17 @@ export function getStoredCardRechargeRecords(memberId, page = 1, size = 20, opti
return request.get(`/stored-card/${memberId}/recharges?page=${page}&size=${size}`, {}, options)
}
+// ========== 系统配置相关API ==========
+
+/**
+ * 根据配置键获取配置值
+ * @param {string} configKey - 配置键
+ * @param {object} options - 请求选项
+ */
+export function getConfigByKey(configKey, options = {}) {
+ return request.get(`/config/key/${configKey}`, {}, options)
+}
+
// ========== 支付相关API ==========
/**
@@ -482,6 +493,16 @@ export function getCourseBookings(courseId, options = { cache: false }) {
return request.get(`/groupCourse/bookings/course/${courseId}`, {}, options)
}
+// ========== 轮播图相关API ==========
+
+/**
+ * 获取启用的轮播图列表
+ * @param {object} options - 请求选项 { cache: boolean, cacheTime: number }
+ */
+export function getActiveBanners(options = { cache: true, cacheTime: 10 * 60 * 1000 }) {
+ return request.get('/banner/active', {}, options)
+}
+
export default {
login,
sendCode,
@@ -520,5 +541,6 @@ export default {
cancelBooking,
signinGroupCourse,
getMemberBookings,
- getCourseBookings
+ getCourseBookings,
+ getActiveBanners
}
diff --git a/gym-manage-uniapp/common/constants/routes.js b/gym-manage-uniapp/common/constants/routes.js
index 003f0be..574be66 100644
--- a/gym-manage-uniapp/common/constants/routes.js
+++ b/gym-manage-uniapp/common/constants/routes.js
@@ -80,6 +80,7 @@ export function navigateToPage(url) {
// 这种情况应该使用 switchToTabPage(会清空页面栈)
if (TAB_PAGES.has(path)) {
console.warn('[navigateToPage] 不应该用 navigateTo 跳转 TabBar 页面,请使用 switchToTabPage')
+ uni.hideLoading()
switchToTabPage(path)
return
}
@@ -90,6 +91,7 @@ export function navigateToPage(url) {
url,
fail: (err) => {
console.error('[navigateTo]', url, err)
+ uni.hideLoading()
// 页面栈满时降级使用 redirectTo
if (err.errMsg && err.errMsg.includes('limit')) {
uni.redirectTo({ url })
@@ -97,11 +99,11 @@ export function navigateToPage(url) {
uni.showToast({ title: '页面跳转失败', icon: 'none' })
}
},
- success: () => {
- setTimeout(() => {
- uni.hideLoading()
- },3000)
- }
+ complete: () => {
+ // 页面已发起跳转,隐藏 loading
+ // 目标页面的 onLoad/onReady 也会调用 hideLoading 做兜底
+ uni.hideLoading()
+ }
})
}
@@ -122,19 +124,21 @@ export function switchToTabPage(url) {
console.log('[switchToTabPage] 跳转到 TabBar:', path)
tabNavigating = true
- uni.switchTab({ // ✅ 改用 switchTab,而不是 reLaunch
+ uni.switchTab({
url: path,
complete: () => {
+ uni.hideLoading()
setTimeout(() => {
tabNavigating = false
}, 320)
},
fail: (err) => {
console.error('[switchTab]', path, err)
- // 降级使用 reLaunch
+ uni.hideLoading()
uni.reLaunch({
url: path,
complete: () => {
+ uni.hideLoading()
setTimeout(() => {
tabNavigating = false
}, 320)
diff --git a/gym-manage-uniapp/common/memberInfo/store.js b/gym-manage-uniapp/common/memberInfo/store.js
index ec234c8..6042a12 100644
--- a/gym-manage-uniapp/common/memberInfo/store.js
+++ b/gym-manage-uniapp/common/memberInfo/store.js
@@ -54,7 +54,9 @@ function applyCardInfo(store) {
export function syncStats(store) {
store.stats = {
...store.stats,
- pointsBalance: store.stats.pointsBalance ?? 1250
+ checkInCount: store.stats.checkInCount ?? 0,
+ courseCount: store.stats.courseCount ?? 0,
+ pointsBalance: store.stats.pointsBalance ?? 0
}
return store
}
@@ -230,7 +232,7 @@ export function getCenterPageData(store) {
return {
stats: {
checkInCount: store.stats?.checkInCount ?? 0,
- trainingHours: store.stats?.trainingHours ?? 0,
+ courseCount: store.stats?.courseCount ?? 0,
pointsBalance: store.stats?.pointsBalance ?? 0
},
cardInfo: { ...store.cardInfo },
diff --git a/gym-manage-uniapp/common/style/memberInfo/member-info-member-card.css b/gym-manage-uniapp/common/style/memberInfo/member-info-member-card.css
index 7374cb6..83802d3 100644
--- a/gym-manage-uniapp/common/style/memberInfo/member-info-member-card.css
+++ b/gym-manage-uniapp/common/style/memberInfo/member-info-member-card.css
@@ -362,27 +362,29 @@
}
.member-card-section__count {
- display: flex;
+ display: inline-flex;
flex-direction: row;
align-items: center;
- gap: 4px;
- padding: 4px 12px;
- border-radius: 12px;
- background-color: rgba(255, 255, 255, 0.2);
+ gap: 4rpx;
+ padding: 4rpx 14rpx;
+ border-radius: 20rpx;
+ background: rgba(255, 255, 255, 0.25);
+ backdrop-filter: blur(4px);
+ flex-shrink: 0;
}
.member-card-section__count-num {
- font-size: var(--font-size-md);
- font-family: var(--font-family);
+ font-size: 26rpx;
font-weight: 700;
- color: var(--text-inverse);
+ color: #333;
+ line-height: 1;
}
.member-card-section__count-text {
- font-size: var(--font-size-xs);
- font-family: var(--font-family);
+ font-size: 22rpx;
font-weight: 400;
- color: rgba(255, 212, 184, 1);
+ color: #333;
+ line-height: 1;
}
.member-card-preview__tag--expiring {
diff --git a/gym-manage-uniapp/components/TabBar.vue b/gym-manage-uniapp/components/TabBar.vue
index 33933f4..f768edc 100644
--- a/gym-manage-uniapp/components/TabBar.vue
+++ b/gym-manage-uniapp/components/TabBar.vue
@@ -183,9 +183,7 @@ function onTabTap(index) {
emit('tab-change', index)
// 设置导航标志,阻止轮询覆盖状态
isNavigating = true
- let timer = setTimeout(() => {
- uni.showLoading({ title: '加载中...', mask: true })
- }, 50)
+ uni.showLoading({ title: '加载中...', mask: true })
isSwitching = true
uni.switchTab({
url: targetPath,
@@ -195,7 +193,6 @@ function onTabTap(index) {
uni.reLaunch({ url: targetPath })
},
complete: () => {
- clearTimeout(timer)
uni.hideLoading()
setTimeout(() => {
isSwitching = false
diff --git a/gym-manage-uniapp/components/groupCourse/CourseCard.vue b/gym-manage-uniapp/components/groupCourse/CourseCard.vue
index 4a2588a..fbb8a0d 100644
--- a/gym-manage-uniapp/components/groupCourse/CourseCard.vue
+++ b/gym-manage-uniapp/components/groupCourse/CourseCard.vue
@@ -103,7 +103,7 @@
- {{ canBook ? '立即预约' : (course.currentMembers >= course.maxMembers ? '已满员' : '已结束') }}
+ {{ buttonText }}
@@ -123,6 +123,10 @@ const props = defineProps({
courseTypeLabels: {
type: Array,
default: () => []
+ },
+ booked: {
+ type: Boolean,
+ default: false
}
})
@@ -147,7 +151,14 @@ const statusClass = computed(() => {
const canBook = computed(() => {
const status = props.course.status
const isFull = props.course.currentMembers >= props.course.maxMembers
- return status === '0' && !isFull
+ return status === '0' && !isFull && !props.booked
+})
+
+const buttonText = computed(() => {
+ if (props.booked) return '已预约'
+ if (!canBook.value && props.course.currentMembers >= props.course.maxMembers) return '已满员'
+ if (!canBook.value && Number(props.course.status) !== 0) return '已结束'
+ return '立即预约'
})
const formatTime = (dateStr) => {
@@ -191,7 +202,7 @@ const formatDuration = (startStr, endStr) => {
}
const goDetail = () => {
- emit('detail', props.course.id)
+ emit('detail', Number(props.course.id))
}
const handleBooking = () => {
diff --git a/gym-manage-uniapp/components/index/BannerSwiper.vue b/gym-manage-uniapp/components/index/BannerSwiper.vue
index 9a6e529..c27fd7d 100644
--- a/gym-manage-uniapp/components/index/BannerSwiper.vue
+++ b/gym-manage-uniapp/components/index/BannerSwiper.vue
@@ -45,9 +45,11 @@
\ No newline at end of file
diff --git a/gym-manage-uniapp/components/index/QuickEntry.vue b/gym-manage-uniapp/components/index/QuickEntry.vue
index b477306..a5d857f 100644
--- a/gym-manage-uniapp/components/index/QuickEntry.vue
+++ b/gym-manage-uniapp/components/index/QuickEntry.vue
@@ -12,26 +12,12 @@
{{ item.title }}
{{ item.desc }}
-
-
diff --git a/gym-manage-uniapp/components/memberInfo/MemberInfoCheckInList.vue b/gym-manage-uniapp/components/memberInfo/MemberInfoCheckInList.vue
index 97d1a95..d12ef87 100644
--- a/gym-manage-uniapp/components/memberInfo/MemberInfoCheckInList.vue
+++ b/gym-manage-uniapp/components/memberInfo/MemberInfoCheckInList.vue
@@ -20,7 +20,11 @@
-
+
+
+ 暂无签到记录
+
+
diff --git a/gym-manage-uniapp/components/memberInfo/MemberInfoHeader.vue b/gym-manage-uniapp/components/memberInfo/MemberInfoHeader.vue
index e3a3e03..5196a3c 100644
--- a/gym-manage-uniapp/components/memberInfo/MemberInfoHeader.vue
+++ b/gym-manage-uniapp/components/memberInfo/MemberInfoHeader.vue
@@ -1,89 +1,50 @@
@@ -94,10 +55,9 @@ import { computed } from 'vue'
const props = defineProps({
userInfo: { type: Object, required: true },
stats: { type: Object, required: true },
- isLogin: { type: Boolean }
+ isLogin: { type: Boolean }
})
-
const emit = defineEmits(['user-info', 'guest-login'])
const displayAvatar = computed(() => {
@@ -115,8 +75,112 @@ function handleGuestLogin() {
}
-
diff --git a/gym-manage-uniapp/components/memberInfo/MemberInfoMemberCard.vue b/gym-manage-uniapp/components/memberInfo/MemberInfoMemberCard.vue
index 9ad1c04..08854c8 100644
--- a/gym-manage-uniapp/components/memberInfo/MemberInfoMemberCard.vue
+++ b/gym-manage-uniapp/components/memberInfo/MemberInfoMemberCard.vue
@@ -1,98 +1,82 @@
+
-
- 我的会员卡
-
-
- {{ cardInfo.activeCount || 0 }}
+ 我的会员卡
+
+ {{ activeCount }}
张有效
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ cardInfo.hasActiveCard ? cardInfo.name : '暂无会员卡' }}
-
-
-
-
- {{ getTagText() }}
-
-
-
+
+
+
+
+
+
+
-
- 有效期至 {{ cardInfo.expireDate }}
-
-
+
+
+
+
+
+ 次数卡
+ {{ countCard.remainingTimes }}次
+ 未购买
+ 剩余
+
+
+
+
+
+
+
+ 时长卡
+ {{ timeCard.remainingDays }}天
+ 未购买
+ 剩余
+
+
+
+
+
+
+ 查看卡片
+
+
+ 购买新卡
@@ -100,176 +84,260 @@
-
diff --git a/gym-manage-uniapp/composables/useGroupCourseList.js b/gym-manage-uniapp/composables/useGroupCourseList.js
index 0897dc4..d2e62ce 100644
--- a/gym-manage-uniapp/composables/useGroupCourseList.js
+++ b/gym-manage-uniapp/composables/useGroupCourseList.js
@@ -33,6 +33,8 @@ export function useGroupCourseList() {
])
const timePeriodIndex = ref(0)
+ const isRecurring = ref(false) // 常态化团课筛选
+
const showTimePicker = ref(false)
const startDate = ref('')
const endDate = ref('')
@@ -138,9 +140,8 @@ export function useGroupCourseList() {
const handleBooking = (course) => {
console.log('[useGroupCourseList] 预约课程:', course)
- uni.showToast({
- title: `预约课程:${course.courseName}`,
- icon: 'success'
+ uni.navigateTo({
+ url: `/pages/groupCourse/detail?id=${course.id}`
})
}
@@ -180,6 +181,10 @@ export function useGroupCourseList() {
params.timePeriod = timePeriod.value
}
+ if (isRecurring.value) {
+ params.isRecurring = true
+ }
+
if (sortOption.priceSort) {
params.priceSort = sortOption.priceSort
}
@@ -291,6 +296,7 @@ export function useGroupCourseList() {
sortIndex,
timePeriodOptions,
timePeriodIndex,
+ isRecurring,
showTimePicker,
startDate,
endDate,
diff --git a/gym-manage-uniapp/groupcourse-recommend-api.md b/gym-manage-uniapp/groupcourse-recommend-api.md
new file mode 100644
index 0000000..ffcb613
--- /dev/null
+++ b/gym-manage-uniapp/groupcourse-recommend-api.md
@@ -0,0 +1,530 @@
+# 团课推荐模块 API 文档
+
+> **文档版本**: v1.0
+> **创建日期**: 2026-06-15
+> **作者**: 张翔
+> **状态**: 正式发布
+
+---
+
+## 📋 目录
+
+1. [概述](#概述)
+2. [基础路径](#基础路径)
+3. [团课推荐管理接口](#团课推荐管理接口)
+ - [获取所有团课推荐](#获取所有团课推荐)
+ - [获取所有启用的团课推荐](#获取所有启用的团课推荐)
+ - [根据ID获取团课推荐](#根据ID获取团课推荐)
+ - [根据团课ID获取推荐](#根据团课ID获取推荐)
+ - [创建团课推荐](#创建团课推荐)
+ - [更新团课推荐](#更新团课推荐)
+ - [删除团课推荐](#删除团课推荐)
+ - [启用团课推荐](#启用团课推荐)
+ - [禁用团课推荐](#禁用团课推荐)
+4. [数据模型](#数据模型)
+ - [GroupCourseRecommend(团课推荐)](#GroupCourseRecommend团课推荐)
+5. [状态码说明](#状态码说明)
+6. [业务规则](#业务规则)
+
+---
+
+## 概述
+
+团课推荐模块提供团课推荐信息的创建、编辑、查询、删除和状态管理功能。推荐信息包含团课ID、推荐标题、推荐内容、推荐理由、优先级等必要信息,支持按优先级排序展示。
+
+## 基础路径
+
+所有接口的基础路径为: `http://{host}:{port}/api/groupCourse/recommend`
+
+---
+
+## 团课推荐管理接口
+
+### 获取所有团课推荐
+
+| 属性 | 值 |
+|------|-----|
+| **HTTP方法** | GET |
+| **接口路径** | `/api/groupCourse/recommend/list` |
+| **所属文件** | `GroupCourseRecommendHandler.java` |
+
+**请求参数**:
+
+| 参数名 | 类型 | 必填 | 默认值 | 说明 |
+|--------|------|------|--------|------|
+| sortBy | string | 否 | priority | 排序字段(支持:priority、createdAt、updatedAt) |
+| sortOrder | string | 否 | desc | 排序方式(asc-升序,desc-降序) |
+
+**成功响应** (200 OK):
+
+```json
+[
+ {
+ "id": 1,
+ "courseId": 1,
+ "recommendTitle": "本周热门课程",
+ "recommendContent": "这是一门非常棒的课程,快来参加吧!",
+ "recommendReason": "教练专业,课程内容丰富",
+ "priority": 10,
+ "isActive": true,
+ "groupCourse": {
+ "id": 1,
+ "courseName": "瑜伽入门",
+ "coachId": 1,
+ "courseType": 1,
+ "startTime": "2026-06-15T09:00:00",
+ "endTime": "2026-06-15T10:00:00",
+ "maxMembers": 20,
+ "currentMembers": 15,
+ "status": 0,
+ "location": "健身房A区",
+ "coverImage": "https://example.com/yoga.jpg",
+ "description": "适合初学者的瑜伽课程"
+ },
+ "createdAt": "2026-06-15T10:00:00",
+ "updatedAt": "2026-06-15T10:00:00"
+ }
+]
+```
+
+---
+
+### 获取所有启用的团课推荐
+
+| 属性 | 值 |
+|------|-----|
+| **HTTP方法** | GET |
+| **接口路径** | `/api/groupCourse/recommend/active` |
+| **所属文件** | `GroupCourseRecommendHandler.java` |
+
+**功能说明**: 获取系统中所有已启用的团课推荐列表,按优先级从高到低排序。
+
+**成功响应** (200 OK):
+
+```json
+[
+ {
+ "id": 2,
+ "courseId": 3,
+ "recommendTitle": "新学员推荐",
+ "recommendContent": "专为新学员设计的入门课程,轻松上手",
+ "recommendReason": "零基础友好,教练耐心指导",
+ "priority": 20,
+ "isActive": true,
+ "groupCourse": {
+ "id": 3,
+ "courseName": "基础有氧",
+ "coachId": 2,
+ "courseType": 2,
+ "startTime": "2026-06-16T18:00:00",
+ "endTime": "2026-06-16T19:00:00",
+ "maxMembers": 30,
+ "currentMembers": 8,
+ "status": 0,
+ "location": "健身房B区",
+ "coverImage": "https://example.com/aerobic.jpg",
+ "description": "适合所有健身水平的有氧课程"
+ },
+ "createdAt": "2026-06-15T11:00:00",
+ "updatedAt": "2026-06-15T11:00:00"
+ }
+]
+```
+
+---
+
+### 根据ID获取团课推荐
+
+| 属性 | 值 |
+|------|-----|
+| **HTTP方法** | GET |
+| **接口路径** | `/api/groupCourse/recommend/{id}` |
+| **所属文件** | `GroupCourseRecommendHandler.java` |
+
+**路径参数**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | Long | 是 | 团课推荐ID |
+
+**成功响应** (200 OK):
+
+```json
+{
+ "id": 1,
+ "courseId": 1,
+ "recommendTitle": "本周热门课程",
+ "recommendContent": "这是一门非常棒的课程,快来参加吧!",
+ "recommendReason": "教练专业,课程内容丰富",
+ "priority": 10,
+ "isActive": true,
+ "groupCourse": {
+ "id": 1,
+ "courseName": "瑜伽入门",
+ "coachId": 1,
+ "courseType": 1,
+ "startTime": "2026-06-15T09:00:00",
+ "endTime": "2026-06-15T10:00:00",
+ "maxMembers": 20,
+ "currentMembers": 15,
+ "status": 0,
+ "location": "健身房A区",
+ "coverImage": "https://example.com/yoga.jpg",
+ "description": "适合初学者的瑜伽课程"
+ },
+ "createdAt": "2026-06-15T10:00:00",
+ "updatedAt": "2026-06-15T10:00:00"
+}
+```
+
+**失败响应** (404 Not Found):
+
+```json
+{}
+```
+
+---
+
+### 根据团课ID获取推荐
+
+| 属性 | 值 |
+|------|-----|
+| **HTTP方法** | GET |
+| **接口路径** | `/api/groupCourse/recommend/course/{courseId}` |
+| **所属文件** | `GroupCourseRecommendHandler.java` |
+
+**路径参数**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| courseId | Long | 是 | 团课ID |
+
+**成功响应** (200 OK):
+
+```json
+[
+ {
+ "id": 1,
+ "courseId": 1,
+ "recommendTitle": "本周热门课程",
+ "recommendContent": "这是一门非常棒的课程,快来参加吧!",
+ "recommendReason": "教练专业,课程内容丰富",
+ "priority": 10,
+ "isActive": true,
+ "groupCourse": {
+ "id": 1,
+ "courseName": "瑜伽入门",
+ "coachId": 1,
+ "courseType": 1,
+ "startTime": "2026-06-15T09:00:00",
+ "endTime": "2026-06-15T10:00:00",
+ "maxMembers": 20,
+ "currentMembers": 15,
+ "status": 0,
+ "location": "健身房A区"
+ },
+ "createdAt": "2026-06-15T10:00:00",
+ "updatedAt": "2026-06-15T10:00:00"
+ }
+]
+```
+
+---
+
+### 创建团课推荐
+
+| 属性 | 值 |
+|------|-----|
+| **HTTP方法** | POST |
+| **接口路径** | `/api/groupCourse/recommend` |
+| **所属文件** | `GroupCourseRecommendHandler.java` |
+
+**请求体**:
+
+```json
+{
+ "courseId": 1,
+ "recommendTitle": "本周热门课程",
+ "recommendContent": "这是一门非常棒的课程,快来参加吧!",
+ "recommendReason": "教练专业,课程内容丰富",
+ "priority": 10,
+ "isActive": true
+}
+```
+
+| 参数名 | 类型 | 必填 | 默认值 | 说明 |
+|--------|------|------|--------|------|
+| courseId | Long | **是** | - | 团课ID(必须是有效的团课) |
+| recommendTitle | String | 否 | - | 推荐标题 |
+| recommendContent | String | 否 | - | 推荐内容 |
+| recommendReason | String | 否 | - | 推荐理由 |
+| priority | Integer | 否 | 0 | 优先级(数字越大优先级越高) |
+| isActive | Boolean | 否 | true | 是否启用 |
+
+**成功响应** (200 OK):
+
+```json
+{
+ "success": true,
+ "message": "团课推荐创建成功",
+ "data": {
+ "id": 1,
+ "courseId": 1,
+ "recommendTitle": "本周热门课程",
+ "recommendContent": "这是一门非常棒的课程,快来参加吧!",
+ "recommendReason": "教练专业,课程内容丰富",
+ "priority": 10,
+ "isActive": true,
+ "createdAt": "2026-06-15T10:00:00",
+ "updatedAt": "2026-06-15T10:00:00"
+ }
+}
+```
+
+**失败响应** (400 Bad Request):
+
+```json
+{
+ "success": false,
+ "message": "团课ID不能为空"
+}
+```
+
+---
+
+### 更新团课推荐
+
+| 属性 | 值 |
+|------|-----|
+| **HTTP方法** | PUT |
+| **接口路径** | `/api/groupCourse/recommend/{id}` |
+| **所属文件** | `GroupCourseRecommendHandler.java` |
+
+**路径参数**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | Long | 是 | 团课推荐ID |
+
+**请求体**:
+
+```json
+{
+ "recommendTitle": "本周热门课程(更新)",
+ "recommendContent": "更新后的推荐内容",
+ "recommendReason": "更新后的推荐理由",
+ "priority": 15,
+ "isActive": true
+}
+```
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| courseId | Long | 否 | 团课ID |
+| recommendTitle | String | 否 | 推荐标题 |
+| recommendContent | String | 否 | 推荐内容 |
+| recommendReason | String | 否 | 推荐理由 |
+| priority | Integer | 否 | 优先级 |
+| isActive | Boolean | 否 | 是否启用 |
+
+**成功响应** (200 OK):
+
+```json
+{
+ "success": true,
+ "message": "团课推荐更新成功",
+ "data": {
+ "id": 1,
+ "courseId": 1,
+ "recommendTitle": "本周热门课程(更新)",
+ "recommendContent": "更新后的推荐内容",
+ "recommendReason": "更新后的推荐理由",
+ "priority": 15,
+ "isActive": true,
+ "updatedAt": "2026-06-15T12:00:00"
+ }
+}
+```
+
+**失败响应** (400 Bad Request):
+
+```json
+{
+ "success": false,
+ "message": "团课推荐不存在"
+}
+```
+
+---
+
+### 删除团课推荐
+
+| 属性 | 值 |
+|------|-----|
+| **HTTP方法** | DELETE |
+| **接口路径** | `/api/groupCourse/recommend/{id}` |
+| **所属文件** | `GroupCourseRecommendHandler.java` |
+
+**路径参数**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | Long | 是 | 团课推荐ID |
+
+**成功响应** (200 OK):
+
+```json
+{
+ "success": true,
+ "message": "团课推荐删除成功"
+}
+```
+
+**失败响应** (400 Bad Request):
+
+```json
+{
+ "success": false,
+ "message": "团课推荐不存在"
+}
+```
+
+---
+
+### 启用团课推荐
+
+| 属性 | 值 |
+|------|-----|
+| **HTTP方法** | POST |
+| **接口路径** | `/api/groupCourse/recommend/{id}/enable` |
+| **所属文件** | `GroupCourseRecommendHandler.java` |
+
+**路径参数**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | Long | 是 | 团课推荐ID |
+
+**成功响应** (200 OK):
+
+```json
+{
+ "success": true,
+ "message": "团课推荐启用成功",
+ "data": {
+ "id": 1,
+ "courseId": 1,
+ "recommendTitle": "本周热门课程",
+ "isActive": true
+ }
+}
+```
+
+**失败响应** (400 Bad Request):
+
+```json
+{
+ "success": false,
+ "message": "团课推荐不存在"
+}
+```
+
+---
+
+### 禁用团课推荐
+
+| 属性 | 值 |
+|------|-----|
+| **HTTP方法** | POST |
+| **接口路径** | `/api/groupCourse/recommend/{id}/disable` |
+| **所属文件** | `GroupCourseRecommendHandler.java` |
+
+**路径参数**:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| id | Long | 是 | 团课推荐ID |
+
+**成功响应** (200 OK):
+
+```json
+{
+ "success": true,
+ "message": "团课推荐禁用成功",
+ "data": {
+ "id": 1,
+ "courseId": 1,
+ "recommendTitle": "本周热门课程",
+ "isActive": false
+ }
+}
+```
+
+**失败响应** (400 Bad Request):
+
+```json
+{
+ "success": false,
+ "message": "团课推荐不存在"
+}
+```
+
+---
+
+## 数据模型
+
+### GroupCourseRecommend(团课推荐)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | Long | 主键ID |
+| courseId | Long | 团课ID(关联group_course.id) |
+| recommendTitle | String | 推荐标题 |
+| recommendContent | String | 推荐内容 |
+| recommendReason | String | 推荐理由 |
+| priority | Integer | 优先级(数字越大优先级越高),默认0 |
+| isActive | Boolean | 是否启用,默认true |
+| groupCourse | GroupCourse | 关联的团课信息(查询时自动填充) |
+| createdBy | String | 创建人 |
+| updatedBy | String | 更新人 |
+| createdAt | LocalDateTime | 创建时间 |
+| updatedAt | LocalDateTime | 更新时间 |
+| deletedAt | LocalDateTime | 删除时间(软删除) |
+
+---
+
+## 状态码说明
+
+### 推荐状态
+
+| 状态值 | 含义 |
+|--------|------|
+| true | 启用 |
+| false | 禁用 |
+
+---
+
+## 业务规则
+
+### 团课推荐管理
+1. **创建推荐**:团课ID为必填项,且必须是有效的团课
+2. **优先级排序**:获取启用的推荐列表时,按优先级从高到低排序
+3. **删除推荐**:采用软删除机制,数据保留可恢复
+4. **状态管理**:支持启用/禁用推荐状态,禁用的推荐不会在推荐列表中显示
+
+---
+
+## 附录:错误响应格式
+
+所有接口的错误响应统一格式:
+
+```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 e591e70..14be932 100644
--- a/gym-manage-uniapp/pages.json
+++ b/gym-manage-uniapp/pages.json
@@ -39,6 +39,7 @@
{
"path": "pages/memberInfo/memberInfo",
"style": {
+ "navigationStyle": "custom",
"navigationBarTitleText": "我的",
"app-plus": {
"animationType": "fade-in",
diff --git a/gym-manage-uniapp/pages/checkIn/checkIn.vue b/gym-manage-uniapp/pages/checkIn/checkIn.vue
index 97a1240..d41727d 100644
--- a/gym-manage-uniapp/pages/checkIn/checkIn.vue
+++ b/gym-manage-uniapp/pages/checkIn/checkIn.vue
@@ -110,7 +110,7 @@ import { getMemberId } from '@/utils/request.js'
const STQRC = ref(false)//是否扫码
const isCheckIn = ref(false)
const isHaveCard = ref(true)
- const webSoketURL = "ws://localhost:8084/webSocket/checkIn"
+ const webSoketURL = "ws://192.168.5.15:8084/webSocket/checkIn"
const qrcode = ref("")
diff --git a/gym-manage-uniapp/pages/course/index.vue b/gym-manage-uniapp/pages/course/index.vue
index dcbbaba..466406c 100644
--- a/gym-manage-uniapp/pages/course/index.vue
+++ b/gym-manage-uniapp/pages/course/index.vue
@@ -130,6 +130,7 @@ function handleTabActive(index) {
}
onLoad(() => {
+ uni.hideLoading()
// 优先显示缓存
const hasCache = loadFromCache()
if (!hasCache) {
@@ -143,6 +144,7 @@ onLoad(() => {
})
onShow(() => {
+ uni.hideLoading()
// 每次显示时尝试刷新数据(后台静默更新)
if (!loading.value) {
loadFromNetwork()
diff --git a/gym-manage-uniapp/pages/groupCourse/detail.vue b/gym-manage-uniapp/pages/groupCourse/detail.vue
index c6d71cf..05b4414 100644
--- a/gym-manage-uniapp/pages/groupCourse/detail.vue
+++ b/gym-manage-uniapp/pages/groupCourse/detail.vue
@@ -228,16 +228,36 @@
+
+
+
@@ -963,7 +1022,10 @@ onMounted(() => {
box-shadow: none;
}
-/* 骨架屏样式 */
+.booking-btn.disabled text {
+ color: #c8c8c8;
+}
+
.skeleton {
padding-bottom: calc(140rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
diff --git a/gym-manage-uniapp/pages/groupCourse/list.vue b/gym-manage-uniapp/pages/groupCourse/list.vue
index c39b163..afde16d 100644
--- a/gym-manage-uniapp/pages/groupCourse/list.vue
+++ b/gym-manage-uniapp/pages/groupCourse/list.vue
@@ -32,6 +32,18 @@
@change="onTimePeriodChange"
ref="timePeriodRef"
/>
+
+
+
+
+ {{ isRecurring ? '✓' : '' }}
+ 常态化团课
+
+
@@ -46,6 +58,7 @@
:key="course.id"
:course="course"
:courseTypeLabels="getCourseLabels(course)"
+ :booked="isCourseBooked(course.id)"
@booking="handleBooking"
@detail="goDetail"
>
@@ -74,6 +87,7 @@
@@ -167,18 +87,6 @@ onMounted(() => {
padding-bottom: 160rpx;
}
-/* 固定白色块 */
-.hand {
- position: fixed;
- top: 0;
- left: 0;
- z-index: 100;
- background-color: white;
- width: 100%;
- text-align: center;
- font-size: 34rpx;
-}
-
/* 固定 TabBar */
.tabbar-fixed {
position: fixed;
diff --git a/gym-manage-uniapp/pages/memberInfo/booking.vue b/gym-manage-uniapp/pages/memberInfo/booking.vue
index bd79181..8eef01e 100644
--- a/gym-manage-uniapp/pages/memberInfo/booking.vue
+++ b/gym-manage-uniapp/pages/memberInfo/booking.vue
@@ -1,7 +1,7 @@
-
+
-
-
- 预约课程
-
-
-
-
-
- {{ upcomingAlert }}
+
+
+
+ {{ activeTab === 'ongoing' ? '暂无进行中的预约' : '暂无历史预约' }}
+
-
@@ -97,7 +81,7 @@
-
diff --git a/gym-manage-uniapp/pages/memberInfo/checkInHistory.vue b/gym-manage-uniapp/pages/memberInfo/checkInHistory.vue
index c9fb638..1ab47c9 100644
--- a/gym-manage-uniapp/pages/memberInfo/checkInHistory.vue
+++ b/gym-manage-uniapp/pages/memberInfo/checkInHistory.vue
@@ -1,7 +1,7 @@
-
+
- 共 {{ filteredList.length }} 条记录
+ 共 {{ filteredList.length }} 条记录
{{ item.tag }}
-
+
暂无签到记录
@@ -47,44 +47,89 @@
diff --git a/gym-manage-uniapp/pages/memberInfo/memberCenter.vue b/gym-manage-uniapp/pages/memberInfo/memberCenter.vue
index 9ec0537..e350cb1 100644
--- a/gym-manage-uniapp/pages/memberInfo/memberCenter.vue
+++ b/gym-manage-uniapp/pages/memberInfo/memberCenter.vue
@@ -3,85 +3,43 @@