239 lines
6.2 KiB
Markdown
239 lines
6.2 KiB
Markdown
# 课程详情页报名状态最终实现说明
|
||
|
||
## 🎯 功能概述
|
||
|
||
实现了完整的课程详情页报名状态管理,确保只有**同时满足登录和报名**的用户才能看到彩色可点击的课程章节,其他情况都显示灰色不可点击状态。
|
||
|
||
## 📋 状态判断逻辑
|
||
|
||
### 三种状态
|
||
1. **未登录** → 🔒 灰色不可点击
|
||
2. **已登录但未报名** → 🔒 灰色不可点击
|
||
3. **已登录且已报名** → 🎉 彩色可点击
|
||
|
||
### 核心逻辑
|
||
```javascript
|
||
// 报名状态管理
|
||
const isEnrolled = ref(false) // 用户是否已报名该课程
|
||
const enrollmentLoading = ref(false) // 报名加载状态
|
||
|
||
// 计算用户是否已报名 - 关键逻辑
|
||
const isUserEnrolled = computed(() => {
|
||
// 必须同时满足:用户已登录 AND 已报名该课程
|
||
return userStore.isLoggedIn && isEnrolled.value
|
||
})
|
||
```
|
||
|
||
## 🎨 视觉效果
|
||
|
||
### 未报名状态(灰色不可点击)
|
||
```css
|
||
/* 未报名状态的灰色样式 */
|
||
.lesson-content.unregistered {
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.lesson-title.disabled {
|
||
color: #999;
|
||
}
|
||
|
||
.lesson-type-badge.disabled {
|
||
background: #d9d9d9 !important;
|
||
color: #999 !important;
|
||
}
|
||
|
||
.lesson-action-btn.disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.lesson-action-btn.disabled svg {
|
||
color: #d9d9d9 !important;
|
||
}
|
||
```
|
||
|
||
### 已报名状态(彩色可点击)
|
||
- 课程类型标识:蓝色、绿色等彩色显示
|
||
- 课程标题:正常黑色文字
|
||
- 操作按钮:彩色图标,可正常点击
|
||
- 完成状态:绿色完成图标
|
||
|
||
## 🔧 交互逻辑
|
||
|
||
### 课程章节点击处理
|
||
```html
|
||
<div class="lesson-content"
|
||
:class="{ 'unregistered': !isUserEnrolled }"
|
||
@click="isUserEnrolled ? handleSectionClick(section) : handleUnregisteredClick(section)">
|
||
|
||
<!-- 课程类型标识 -->
|
||
<div class="lesson-type-badge"
|
||
:class="[getLessonTypeBadgeClass(section), { 'disabled': !isUserEnrolled }]">
|
||
{{ getLessonTypeText(section) }}
|
||
</div>
|
||
|
||
<!-- 课程标题 -->
|
||
<div class="lesson-info">
|
||
<span class="lesson-title" :class="{ 'disabled': !isUserEnrolled }">
|
||
{{ section.name }}
|
||
</span>
|
||
</div>
|
||
|
||
<!-- 操作按钮 -->
|
||
<button class="lesson-action-btn"
|
||
:class="{ 'disabled': !isUserEnrolled }"
|
||
:disabled="!isUserEnrolled"
|
||
@click.stop="isUserEnrolled ? handleSectionClick(section) : handleUnregisteredClick(section)">
|
||
<!-- 图标 -->
|
||
</button>
|
||
</div>
|
||
```
|
||
|
||
### 报名流程
|
||
```javascript
|
||
// 处理课程报名
|
||
const handleEnrollCourse = () => {
|
||
if (!userStore.isLoggedIn) {
|
||
// 未登录,显示登录弹窗
|
||
showLoginModal()
|
||
return
|
||
}
|
||
|
||
if (isEnrolled.value) {
|
||
// 已报名,直接跳转到学习页面
|
||
router.push(`/course/${courseId.value}/study`)
|
||
return
|
||
}
|
||
|
||
// 未报名,显示报名确认弹窗
|
||
enrollConfirmVisible.value = true
|
||
}
|
||
|
||
// 确认报名
|
||
const confirmEnrollment = async () => {
|
||
try {
|
||
enrollmentLoading.value = true
|
||
|
||
// 模拟API调用
|
||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||
|
||
// 报名成功 - 关键:设置报名状态为true
|
||
isEnrolled.value = true
|
||
enrollConfirmVisible.value = false
|
||
enrollSuccessVisible.value = true
|
||
|
||
// 2秒后自动跳转
|
||
setTimeout(() => {
|
||
enrollSuccessVisible.value = false
|
||
router.push(`/course/${courseId.value}/study`)
|
||
}, 2000)
|
||
|
||
} catch (error) {
|
||
console.error('报名失败:', error)
|
||
} finally {
|
||
enrollmentLoading.value = false
|
||
}
|
||
}
|
||
```
|
||
|
||
## 🎯 章节头部样式
|
||
|
||
根据图片标准更新的章节头部:
|
||
```html
|
||
<div class="sections-header">
|
||
<div class="header-left">
|
||
<h3 class="sections-title">课程章节</h3>
|
||
</div>
|
||
<div class="header-right">
|
||
<button class="sort-btn">
|
||
<svg class="sort-icon">...</svg>
|
||
<span class="sort-text">正序</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
```css
|
||
.sections-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16px 20px;
|
||
background: white;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.sections-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin: 0;
|
||
}
|
||
```
|
||
|
||
## 📱 报名确认弹窗
|
||
|
||
### 确认弹窗
|
||
```html
|
||
<div v-if="enrollConfirmVisible" class="modal-overlay" @click="cancelEnrollment">
|
||
<div class="modal-content" @click.stop>
|
||
<div class="modal-header">
|
||
<h3>确认报名</h3>
|
||
<button class="modal-close" @click="cancelEnrollment">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>确定要报名《{{ course?.title }}》课程吗?</p>
|
||
<p class="modal-tip">报名后您将获得完整的学习权限</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn-cancel" @click="cancelEnrollment">取消</button>
|
||
<button class="btn-confirm" @click="confirmEnrollment" :disabled="enrollmentLoading">
|
||
{{ enrollmentLoading ? '报名中...' : '确认报名' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
### 成功提示
|
||
```html
|
||
<div v-if="enrollSuccessVisible" class="modal-overlay">
|
||
<div class="modal-content success-modal">
|
||
<div class="success-icon">✓</div>
|
||
<h3>报名成功!</h3>
|
||
<p>正在跳转到学习页面...</p>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
## 🧪 测试状态
|
||
|
||
为了方便测试,在 `initializeMockState` 函数中:
|
||
|
||
```javascript
|
||
// 模拟用户已登录
|
||
userStore.user = { ... }
|
||
userStore.token = 'mock-token'
|
||
|
||
// 模拟用户已报名(您可以改为false来测试未报名状态)
|
||
isEnrolled.value = true // 改为false可测试未报名状态
|
||
```
|
||
|
||
### 测试不同状态
|
||
1. **测试未登录状态**:注释掉用户登录模拟代码
|
||
2. **测试未报名状态**:设置 `isEnrolled.value = false`
|
||
3. **测试已报名状态**:设置 `isEnrolled.value = true`
|
||
|
||
## ✅ 最终效果
|
||
|
||
### 状态流转
|
||
1. **未登录** → 点击报名 → 登录弹窗 → 登录成功 → 报名确认 → 报名成功 → 彩色可点击
|
||
2. **已登录未报名** → 点击报名 → 报名确认 → 报名成功 → 彩色可点击
|
||
3. **已登录已报名** → 直接显示彩色可点击状态
|
||
|
||
### 视觉反馈
|
||
- **未报名**:所有课程章节显示为灰色,不可点击
|
||
- **已报名**:所有课程章节显示为彩色,可正常点击学习
|
||
|
||
现在课程详情页面具备完整的报名状态管理,确保只有真正有学习权限的用户才能看到彩色可点击的课程内容!🎉
|