hookehuyr

feat(study): 新增课程学习页面及相关组件

新增课程学习页面,包含用户信息展示、学习记录和学习统计功能。添加了CourseList组件用于展示课程列表,并在设置页面中添加了跳转至学习页面的入口。同时更新了组件类型声明文件以支持新组件。
...@@ -15,6 +15,7 @@ declare module 'vue' { ...@@ -15,6 +15,7 @@ declare module 'vue' {
15 CheckInDialog: typeof import('./components/ui/CheckInDialog.vue')['default'] 15 CheckInDialog: typeof import('./components/ui/CheckInDialog.vue')['default']
16 ConfirmDialog: typeof import('./components/ui/ConfirmDialog.vue')['default'] 16 ConfirmDialog: typeof import('./components/ui/ConfirmDialog.vue')['default']
17 CourseCard: typeof import('./components/ui/CourseCard.vue')['default'] 17 CourseCard: typeof import('./components/ui/CourseCard.vue')['default']
18 + CourseList: typeof import('./components/courses/CourseList.vue')['default']
18 FrostedGlass: typeof import('./components/ui/FrostedGlass.vue')['default'] 19 FrostedGlass: typeof import('./components/ui/FrostedGlass.vue')['default']
19 GradientHeader: typeof import('./components/ui/GradientHeader.vue')['default'] 20 GradientHeader: typeof import('./components/ui/GradientHeader.vue')['default']
20 LiveStreamCard: typeof import('./components/ui/LiveStreamCard.vue')['default'] 21 LiveStreamCard: typeof import('./components/ui/LiveStreamCard.vue')['default']
...@@ -27,8 +28,10 @@ declare module 'vue' { ...@@ -27,8 +28,10 @@ declare module 'vue' {
27 TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default'] 28 TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default']
28 UploadVideoPopup: typeof import('./components/ui/UploadVideoPopup.vue')['default'] 29 UploadVideoPopup: typeof import('./components/ui/UploadVideoPopup.vue')['default']
29 VanButton: typeof import('vant/es')['Button'] 30 VanButton: typeof import('vant/es')['Button']
31 + VanCell: typeof import('vant/es')['Cell']
30 VanCellGroup: typeof import('vant/es')['CellGroup'] 32 VanCellGroup: typeof import('vant/es')['CellGroup']
31 VanCheckbox: typeof import('vant/es')['Checkbox'] 33 VanCheckbox: typeof import('vant/es')['Checkbox']
34 + VanCol: typeof import('vant/es')['Col']
32 VanDatePicker: typeof import('vant/es')['DatePicker'] 35 VanDatePicker: typeof import('vant/es')['DatePicker']
33 VanField: typeof import('vant/es')['Field'] 36 VanField: typeof import('vant/es')['Field']
34 VanForm: typeof import('vant/es')['Form'] 37 VanForm: typeof import('vant/es')['Form']
...@@ -40,6 +43,7 @@ declare module 'vue' { ...@@ -40,6 +43,7 @@ declare module 'vue' {
40 VanPopup: typeof import('vant/es')['Popup'] 43 VanPopup: typeof import('vant/es')['Popup']
41 VanProgress: typeof import('vant/es')['Progress'] 44 VanProgress: typeof import('vant/es')['Progress']
42 VanRate: typeof import('vant/es')['Rate'] 45 VanRate: typeof import('vant/es')['Rate']
46 + VanRow: typeof import('vant/es')['Row']
43 VanTab: typeof import('vant/es')['Tab'] 47 VanTab: typeof import('vant/es')['Tab']
44 VanTabs: typeof import('vant/es')['Tabs'] 48 VanTabs: typeof import('vant/es')['Tabs']
45 VanToast: typeof import('vant/es')['Toast'] 49 VanToast: typeof import('vant/es')['Toast']
......
1 +<!--
2 + * @Date: 2025-04-07
3 + * @Description: 课程列表组件
4 +-->
5 +<template>
6 + <div class="course-list">
7 + <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
8 + <div v-for="course in courses" :key="course.id"
9 + class="course-item bg-white mb-3 rounded-lg overflow-hidden">
10 + <van-cell :title="course.title" :label="course.type" class="pb-2">
11 + <template #icon>
12 + <van-image :src="course.thumbnail" width="120" height="68" fit="cover" class="mr-3 rounded" />
13 + </template>
14 + </van-cell>
15 + <div class="px-4 pb-4">
16 + <div class="flex justify-between items-center text-sm text-gray-600 mb-2">
17 + <span>已学习 {{ formatDuration(course.studyTime) }}</span>
18 + <span>{{ course.progress }}%</span>
19 + </div>
20 + <van-progress :percentage="course.progress" :stroke-width="4" color="#4080ff" />
21 + </div>
22 + </div>
23 + </van-list>
24 + </div>
25 +</template>
26 +
27 +<script setup>
28 +import { ref } from 'vue'
29 +
30 +// 接收课程列表数据
31 +const props = defineProps({
32 + courses: {
33 + type: Array,
34 + default: () => []
35 + }
36 +})
37 +
38 +// 列表加载状态
39 +const loading = ref(false)
40 +const finished = ref(true) // 由于使用的是传入的静态数据,所以直接设置为完成状态
41 +
42 +// 加载更多
43 +const onLoad = () => {
44 + loading.value = false
45 +}
46 +
47 +// 格式化时长显示
48 +const formatDuration = (seconds) => {
49 + const hours = Math.floor(seconds / 3600)
50 + const minutes = Math.floor((seconds % 3600) / 60)
51 + if (hours > 0) {
52 + return `${hours}小时${minutes}分钟`
53 + }
54 + return `${minutes}分钟`
55 +}
56 +</script>
57 +
58 +<style scoped>
59 +.course-list {
60 + padding: 0.5rem;
61 +}
62 +
63 +.course-item {
64 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
65 +}
66 +</style>
1 /* 1 /*
2 * @Date: 2025-03-20 20:36:36 2 * @Date: 2025-03-20 20:36:36
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-03-28 09:39:27 4 + * @LastEditTime: 2025-04-08 12:45:20
5 * @FilePath: /mlaj/src/router/routes.js 5 * @FilePath: /mlaj/src/router/routes.js
6 * @Description: 路由地址映射配置 6 * @Description: 路由地址映射配置
7 */ 7 */
...@@ -198,5 +198,12 @@ export const routes = [ ...@@ -198,5 +198,12 @@ export const routes = [
198 title: '微信授权页面', 198 title: '微信授权页面',
199 } 199 }
200 }, 200 },
201 + {
202 + path: '/study',
203 + component: () => import('@/views/study/studyPage.vue'),
204 + meta: {
205 + title: '学习页面',
206 + }
207 + },
201 ...checkinRoutes, 208 ...checkinRoutes,
202 ] 209 ]
......
1 <!-- 1 <!--
2 * @Date: 2025-03-24 13:04:21 2 * @Date: 2025-03-24 13:04:21
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-03-26 13:09:59 4 + * @LastEditTime: 2025-04-08 11:21:01
5 * @FilePath: /mlaj/src/views/profile/SettingsPage.vue 5 * @FilePath: /mlaj/src/views/profile/SettingsPage.vue
6 * @Description: 用户设置页面 6 * @Description: 用户设置页面
7 --> 7 -->
...@@ -75,6 +75,17 @@ ...@@ -75,6 +75,17 @@
75 <ChevronRightIcon class="w-5 h-5 text-gray-400" /> 75 <ChevronRightIcon class="w-5 h-5 text-gray-400" />
76 </div> 76 </div>
77 </div> 77 </div>
78 +
79 + <!-- 课程学习 -->
80 + <div class="p-4" @click="router.push('/study')">
81 + <div class="flex items-center justify-between">
82 + <div>
83 + <h3 class="text-base font-medium text-gray-900">课程学习</h3>
84 + <p class="text-sm text-gray-500">课程学习</p>
85 + </div>
86 + <ChevronRightIcon class="w-5 h-5 text-gray-400" />
87 + </div>
88 + </div>
78 </div> 89 </div>
79 </FrostedGlass> 90 </FrostedGlass>
80 </div> 91 </div>
......
1 +<!--
2 + * @Date: 2025-04-07
3 + * @Description: 课程学习页面
4 +-->
5 +<template>
6 + <div class="study-page">
7 + <!-- 用户信息区域 -->
8 + <div class="user-info bg-green-50/70 p-4 flex flex-col">
9 + <div class="flex items-center mb-3">
10 + <div class="flex-shrink-0">
11 + <van-image round width="3rem" height="3rem" :src="userInfo.avatar" :alt="userInfo.name" />
12 + </div>
13 + <div class="text-lg text-black font-bold" style="margin-left: 1rem">
14 + {{ userInfo.name }}
15 + </div>
16 + </div>
17 + <div class="text-sm text-gray-500 mb-2">
18 + 本周第 <span class="text-black font-bold">{{ userInfo.weeklyRank }}</span> 名
19 + </div>
20 + <div class="text-sm text-gray-500">
21 + 已加入 <span class="text-black font-bold">{{ userInfo.joinDays }}</span> 天
22 + <span class="mx-2">·</span>
23 + 连续学习
24 + <span class="text-black font-bold">{{ userInfo.continuousDays }}</span> 天
25 + </div>
26 + </div>
27 +
28 + <!-- 学习记录和统计标签页 -->
29 + <van-tabs v-model:active="activeTab" sticky shrink>
30 + <!-- 学习记录标签 -->
31 + <van-tab title="学习记录" name="record">
32 + <div class="p-4">
33 + <div class="flex space-x-4 mb-4">
34 + <button
35 + class="rounded-full px-6 py-2 text-sm transition-colors"
36 + :class="courseType === 'column' ? 'bg-green-500 text-white' : 'bg-gray-100 text-gray-600'"
37 + @click="courseType = 'column'"
38 + >
39 + 专栏
40 + </button>
41 + <button
42 + class="rounded-full px-6 py-2 text-sm transition-colors"
43 + :class="courseType === 'single' ? 'bg-green-500 text-white' : 'bg-gray-100 text-gray-600'"
44 + @click="courseType = 'single'"
45 + >
46 + 单课
47 + </button>
48 + </div>
49 + <course-list v-if="courseType === 'column'" :courses="columnCourses" />
50 + <course-list v-else :courses="singleCourses" />
51 + </div>
52 + </van-tab>
53 +
54 + <!-- 学习统计标签 -->
55 + <van-tab title="学习统计" name="stats">
56 + <div class="p-4">
57 + <van-cell-group inset>
58 + <van-cell title="总学习时长" :value="formatDuration(stats.totalDuration)" />
59 + <van-cell title="已完成课程" :value="`${stats.completedCourses}门`" />
60 + <van-cell title="学习天数" :value="`${stats.studyDays}天`" />
61 + <van-cell title="平均每日学习" :value="formatDuration(stats.avgDailyDuration)" />
62 + </van-cell-group>
63 + </div>
64 + </van-tab>
65 + </van-tabs>
66 + </div>
67 +</template>
68 +
69 +<script setup>
70 +import { ref } from "vue";
71 +import { useTitle } from "@vueuse/core";
72 +import CourseList from "@/components/courses/CourseList.vue";
73 +
74 +// 设置页面标题
75 +useTitle("课程学习");
76 +
77 +// 用户信息
78 +const userInfo = ref({
79 + avatar: "https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg",
80 + name: "王鸿军",
81 + weeklyRank: 11,
82 + joinDays: 448,
83 + continuousDays: 1,
84 +});
85 +
86 +// 标签页状态
87 +const activeTab = ref("record");
88 +const courseType = ref("column");
89 +
90 +// 课程列表数据
91 +const columnCourses = ref([
92 + {
93 + id: 1,
94 + title: "考前赋能冥想",
95 + type: "视频",
96 + thumbnail: "https://cdn.ipadbiz.cn/mlaj/images/zMRLZh40kms.jpg",
97 + progress: 100,
98 + duration: 1200, // 20分钟
99 + studyTime: 1200,
100 + },
101 + {
102 + id: 2,
103 + title: "开学礼·让的智慧·心法老师·20241001(上)",
104 + type: "视频",
105 + thumbnail: "https://cdn.ipadbiz.cn/mlaj/images/zMRLZh40kms.jpg",
106 + progress: 1,
107 + duration: 3600, // 1小时
108 + studyTime: 10,
109 + },
110 + {
111 + id: 1,
112 + title: "考前赋能冥想",
113 + type: "视频",
114 + thumbnail: "https://cdn.ipadbiz.cn/mlaj/images/zMRLZh40kms.jpg",
115 + progress: 100,
116 + duration: 1200, // 20分钟
117 + studyTime: 1200,
118 + },
119 + {
120 + id: 2,
121 + title: "开学礼·让的智慧·心法老师·20241001(上)",
122 + type: "视频",
123 + thumbnail: "https://cdn.ipadbiz.cn/mlaj/images/zMRLZh40kms.jpg",
124 + progress: 1,
125 + duration: 3600, // 1小时
126 + studyTime: 10,
127 + },
128 +]);
129 +
130 +const singleCourses = ref([
131 + {
132 + id: 3,
133 + title: "冬季课·影响孩子命运的家族六要素·心法老师20250207",
134 + type: "视频",
135 + thumbnail: "https://cdn.ipadbiz.cn/mlaj/images/zMRLZh40kms.jpg",
136 + progress: 1,
137 + duration: 3600,
138 + studyTime: 1,
139 + },
140 +]);
141 +
142 +// 学习统计数据
143 +const stats = ref({
144 + totalDuration: 7200, // 2小时
145 + completedCourses: 1,
146 + studyDays: 30,
147 + avgDailyDuration: 1800, // 30分钟
148 +});
149 +
150 +// 格式化时长显示
151 +const formatDuration = (seconds) => {
152 + const hours = Math.floor(seconds / 3600);
153 + const minutes = Math.floor((seconds % 3600) / 60);
154 + if (hours > 0) {
155 + return `${hours}小时${minutes}分钟`;
156 + }
157 + return `${minutes}分钟`;
158 +};
159 +</script>
160 +
161 +<style scoped>
162 +.study-page {
163 + min-height: 100vh;
164 + background-color: #fff;
165 +}
166 +
167 +.bg-primary {
168 + background-color: #4080ff;
169 +}
170 +</style>