hookehuyr

feat(teacher): 添加课程选择和样式优化

- 在学生页面添加课程单选功能
- 优化班级页面样式和交互细节
- 增加主题配置和搜索框样式调整
...@@ -2,157 +2,169 @@ ...@@ -2,157 +2,169 @@
2 * @Author: hookehuyr hookehuyr@gmail.com 2 * @Author: hookehuyr hookehuyr@gmail.com
3 * @Date: 2025-01-20 10:00:00 3 * @Date: 2025-01-20 10:00:00
4 * @LastEditors: hookehuyr hookehuyr@gmail.com 4 * @LastEditors: hookehuyr hookehuyr@gmail.com
5 - * @LastEditTime: 2025-06-19 16:46:06 5 + * @LastEditTime: 2025-06-20 00:09:46
6 * @FilePath: /mlaj/src/views/teacher/myClassPage.vue 6 * @FilePath: /mlaj/src/views/teacher/myClassPage.vue
7 * @Description: 我的班级页面 7 * @Description: 我的班级页面
8 --> 8 -->
9 <template> 9 <template>
10 - <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen"> 10 + <van-config-provider :theme-vars="themeVars">
11 - <!-- 用户信息卡片 --> 11 + <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen">
12 - <div> 12 + <!-- 用户信息卡片 -->
13 - <div class="bg-white p-4"> 13 + <div>
14 - <div class="flex items-center mb-4"> 14 + <div class="bg-white p-4">
15 - <van-image round width="3rem" height="3rem" 15 + <div class="flex items-center mb-4">
16 - :src="userInfo.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'" fit="cover" class="mr-3" /> 16 + <van-image round width="3rem" height="3rem"
17 - <div class="flex-1"> 17 + :src="userInfo.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'" fit="cover" class="mr-3" />
18 - <h2 class="text-lg font-bold text-gray-800">{{ userInfo.name }}</h2> 18 + <div class="flex-1">
19 - <div class="flex items-center mt-1"> 19 + <h2 class="text-lg font-bold text-gray-800">{{ userInfo.name }}</h2>
20 - <van-icon name="clock-o" size="20" color="#10b981" class="mr-1" /> 20 + <div class="flex items-center mt-1">
21 - <van-icon name="chat-o" size="20" color="#10b981" class="mr-1" /> 21 + <van-icon name="clock-o" size="20" color="#10b981" class="mr-1" />
22 - <van-icon name="comment-circle-o" size="20" color="#10b981" /> 22 + <van-icon name="chat-o" size="20" color="#10b981" class="mr-1" />
23 + <van-icon name="comment-circle-o" size="20" color="#10b981" />
24 + </div>
23 </div> 25 </div>
24 </div> 26 </div>
25 </div> 27 </div>
26 </div> 28 </div>
27 - </div>
28 29
29 - <!-- 筛选器 --> 30 + <!-- 筛选器 -->
30 - <div> 31 + <div>
31 - <van-sticky> 32 + <van-sticky>
32 - <van-dropdown-menu active-color="#10b981"> 33 + <van-dropdown-menu active-color="#10b981">
33 - <van-dropdown-item v-model="gradeFilter" :options="gradeOptions" @change="handleGradeChange" /> 34 + <van-dropdown-item v-model="gradeFilter" :options="gradeOptions" @change="handleGradeChange" />
34 - <van-dropdown-item v-model="classFilter" :options="classOptions" @change="handleClassChange" /> 35 + <van-dropdown-item v-model="classFilter" :options="classOptions" @change="handleClassChange" />
35 - <van-dropdown-item v-model="courseFilter" :options="courseOptions" @change="handleCourseChange" /> 36 + <van-dropdown-item v-model="courseFilter" :options="courseOptions" @change="handleCourseChange" />
36 - </van-dropdown-menu> 37 + </van-dropdown-menu>
37 - </van-sticky> 38 + </van-sticky>
38 - </div> 39 + </div>
39 40
40 - <!-- 统计数据 --> 41 + <div style="height: 0.5rem;"></div>
41 - <div> 42 +
42 - <van-row> 43 + <!-- 统计数据 -->
43 - <!-- 出勤率 --> 44 + <div>
44 - <van-col span="8"> 45 + <van-row>
45 - <div class="bg-white p-4 text-center"> 46 + <!-- 出勤率 -->
46 - <div class="relative w-16 h-16 mx-auto mb-2"> 47 + <van-col span="8">
47 - <van-circle v-model:current-rate="attendanceRate" :rate="attendanceRate" :speed="100" 48 + <div class="bg-white p-4 text-center">
48 - :text="attendanceRate + '%'" stroke-width="50" color="#10b981" size="64" /> 49 + <div class="relative w-16 h-16 mx-auto mb-2">
50 + <van-circle v-model:current-rate="attendanceRate" :rate="attendanceRate" :speed="100"
51 + :text="attendanceRate + '%'" stroke-width="70" color="#10b981" size="64" />
52 + </div>
53 + <div class="text-sm text-gray-600">出勤率</div>
49 </div> 54 </div>
50 - <div class="text-sm text-gray-600">出勤率</div> 55 + </van-col>
51 - </div> 56 + <!-- 任务完成 -->
52 - </van-col> 57 + <van-col span="8">
53 - <!-- 任务完成 --> 58 + <div class="bg-white p-4 text-center">
54 - <van-col span="8"> 59 + <div class="relative w-16 h-16 mx-auto mb-2">
55 - <div class="bg-white p-4 text-center"> 60 + <van-circle v-model:current-rate="taskCompletionRate" :rate="taskCompletionRate" :speed="100"
56 - <div class="relative w-16 h-16 mx-auto mb-2"> 61 + :text="taskCompletionRate + '%'" stroke-width="70" color="#3b82f6" size="64" />
57 - <van-circle v-model:current-rate="taskCompletionRate" :rate="taskCompletionRate" :speed="100" 62 + </div>
58 - :text="taskCompletionRate + '%'" stroke-width="50" color="#3b82f6" size="64" /> 63 + <div class="text-sm text-gray-600">任务完成</div>
59 </div> 64 </div>
60 - <div class="text-sm text-gray-600">任务完成</div> 65 + </van-col>
61 - </div> 66 + <!-- 学习进度 -->
62 - </van-col> 67 + <van-col span="8">
63 - <!-- 学习进度 --> 68 + <div class="bg-white p-4 text-center">
64 - <van-col span="8"> 69 + <div class="relative w-16 h-16 mx-auto mb-2">
65 - <div class="bg-white p-4 text-center"> 70 + <van-circle v-model:current-rate="learningProgress" :rate="learningProgress" :speed="100"
66 - <div class="relative w-16 h-16 mx-auto mb-2"> 71 + :text="learningProgress + '%'" stroke-width="70" color="#f59e0b" size="64" />
67 - <van-circle v-model:current-rate="learningProgress" :rate="learningProgress" :speed="100" 72 + </div>
68 - :text="learningProgress + '%'" stroke-width="50" color="#f59e0b" size="64" /> 73 + <div class="text-sm text-gray-600">学习进度</div>
69 </div> 74 </div>
70 - <div class="text-sm text-gray-600">学习进度</div> 75 + </van-col>
71 - </div> 76 + </van-row>
72 - </van-col> 77 + </div>
73 - </van-row>
74 - </div>
75 78
76 - <!-- 班级成员 --> 79 + <!-- 班级成员 -->
77 - <div class="">
78 <div class=""> 80 <div class="">
79 - <!-- 标题和搜索 --> 81 + <div class="">
80 - <div class="p-4 border-b border-gray-100 bg-white"> 82 + <!-- 标题和搜索 -->
81 - <div class="flex items-center justify-between mb-3"> 83 + <div class="p-4 border-b border-gray-100 bg-white">
82 - <h3 class="text-lg font-bold text-gray-800">班级成员 ({{ studentList.length }})</h3> 84 + <div class="flex items-center justify-between mb-3">
83 - <div @click="showSortPopup = true" class="flex items-center text-sm text-gray-600 cursor-pointer"> 85 + <h3 class="text-lg font-bold text-gray-800">班级成员 ({{ studentList.length }})</h3>
84 - <span>{{ sortFilter }}</span> 86 + <div @click="showSortPopup = true" class="flex items-center text-sm text-gray-600 cursor-pointer">
85 - <van-icon name="arrow-down" size="14" class="ml-1" /> 87 + <span>{{ sortFilter }}</span>
88 + <van-icon name="arrow-down" size="14" class="ml-1" />
89 + </div>
86 </div> 90 </div>
91 + <van-search v-model="searchKeyword" placeholder="请搜索" shape="round" @search="handleSearch" @input="handleSearch" />
87 </div> 92 </div>
88 - <van-search v-model="searchKeyword" placeholder="请搜索" @search="handleSearch" @input="handleSearch" />
89 - </div>
90 93
91 - <!-- 学生列表 --> 94 + <!-- 学生列表 -->
92 - <div class="p-4"> 95 + <div class="p-4">
93 - <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad"> 96 + <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
94 - <div v-for="student in filteredStudentList" :key="student.id" 97 + <div v-for="student in filteredStudentList" :key="student.id"
95 - class="flex items-center justify-between py-3 border-gray-50 bg-white rounded-xl p-4 text-center shadow-sm mb-4" 98 + class="flex items-center justify-between py-3 border-gray-50 bg-white rounded-xl p-4 text-center shadow-sm mb-4"
96 - @click="handleStudentClick(student)"> 99 + @click="handleStudentClick(student)">
97 - <div class="flex items-center flex-1"> 100 + <div class="flex items-center flex-1">
98 - <van-image round width="2.5rem" height="2.5rem" 101 + <van-image round width="2.5rem" height="2.5rem"
99 - :src="student.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'" fit="cover" class="mr-3" /> 102 + :src="student.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'" fit="cover" class="mr-3" />
100 - <div class="flex-1"> 103 + <div class="flex-1">
101 - <div class="flex items-center"> 104 + <div class="flex items-center">
102 - <span class="font-medium text-gray-800 mr-2">{{ student.name }}</span> 105 + <span class="font-medium text-gray-800 mr-2">{{ student.name }}</span>
103 - <van-icon v-if="student.gender === 'male'" name="friends-o" color="#3b82f6" size="14" /> 106 + <van-icon v-if="student.gender === 'male'" name="friends-o" color="#3b82f6" size="14" />
104 - <van-icon v-else name="like-o" color="#ec4899" size="14" /> 107 + <van-icon v-else name="like-o" color="#ec4899" size="14" />
108 + </div>
109 + <div class="text-sm text-gray-500" style="text-align: left;">{{ student.className }}</div>
105 </div> 110 </div>
106 - <div class="text-sm text-gray-500" style="text-align: left;">{{ student.className }}</div>
107 </div> 111 </div>
108 - </div> 112 + <div class="text-right">
109 - <div class="text-right"> 113 + <div class="flex items-center text-sm text-gray-500 mb-1">
110 - <div class="flex items-center text-sm text-gray-500 mb-1"> 114 + <van-icon name="phone-o" size="12" class="mr-1" />
111 - <van-icon name="phone-o" size="12" class="mr-1" /> 115 + <span>{{ formatPhone(student.phone) }}</span>
112 - <span>{{ formatPhone(student.phone) }}</span> 116 + </div>
117 + <div class="text-xs text-gray-400">{{ student.lastActiveTime }}</div>
113 </div> 118 </div>
114 - <div class="text-xs text-gray-400">{{ student.lastActiveTime }}</div> 119 + <van-icon name="arrow" color="#d1d5db" size="16" class="ml-2" />
115 </div> 120 </div>
116 - <van-icon name="arrow" color="#d1d5db" size="16" class="ml-2" /> 121 + </van-list>
117 - </div> 122 + </div>
118 - </van-list>
119 </div> 123 </div>
120 </div> 124 </div>
121 - </div>
122 125
123 - <!-- 排序弹窗 --> 126 + <!-- 排序弹窗 -->
124 - <van-popup v-model:show="showSortPopup" position="bottom" round> 127 + <van-popup v-model:show="showSortPopup" position="bottom" round>
125 - <div class="p-4"> 128 + <div class="p-4">
126 - <div class="text-center text-lg font-bold mb-4">选择排序方式</div> 129 + <div class="text-center text-lg font-bold mb-4">选择排序方式</div>
127 - <van-cell-group> 130 + <van-cell-group>
128 - <van-cell 131 + <van-cell
129 - v-for="option in sortOptions" 132 + v-for="option in sortOptions"
130 - :key="option.value" 133 + :key="option.value"
131 - :title="option.text" 134 + :title="option.text"
132 - clickable 135 + clickable
133 - @click="onSortSelect(option)" 136 + @click="onSortSelect(option)"
134 - :border="false" 137 + :border="false"
135 - :class="{ 'text-green-600': sortFilter === option.value }" 138 + :class="{ 'text-green-600': sortFilter === option.value }"
136 - > 139 + >
137 - <template #right-icon> 140 + <template #right-icon>
138 - <van-icon v-if="sortFilter === option.value" name="success" color="#10b981" /> 141 + <van-icon v-if="sortFilter === option.value" name="success" color="#10b981" />
139 - </template> 142 + </template>
140 - </van-cell> 143 + </van-cell>
141 - </van-cell-group> 144 + </van-cell-group>
142 - <div class="mt-4"> 145 + <div class="mt-4">
143 - <van-button block @click="showSortPopup = false">取消</van-button> 146 + <van-button block @click="showSortPopup = false">取消</van-button>
147 + </div>
144 </div> 148 </div>
145 - </div> 149 + </van-popup>
146 - </van-popup> 150 + </div>
147 - </div> 151 + </van-config-provider>
148 </template> 152 </template>
149 153
150 <script setup> 154 <script setup>
151 import { ref, computed, onMounted } from 'vue' 155 import { ref, computed, onMounted } from 'vue'
152 import { useRouter } from 'vue-router' 156 import { useRouter } from 'vue-router'
153 import AppLayout from '@/layouts/AppLayout.vue' 157 import AppLayout from '@/layouts/AppLayout.vue'
158 +import { useTitle } from '@vueuse/core';
154 159
155 const router = useRouter() 160 const router = useRouter()
161 +const route = useRoute()
162 +useTitle(route.meta.title);
163 +
164 +const themeVars = ref({
165 + 'dropdownMenuShadow': '#fff',
166 + searchInputHeight: '2.5rem',
167 +})
156 168
157 // 用户信息 169 // 用户信息
158 const userInfo = ref({ 170 const userInfo = ref({
...@@ -238,6 +250,42 @@ const studentList = ref([ ...@@ -238,6 +250,42 @@ const studentList = ref([
238 phone: '13643214321', 250 phone: '13643214321',
239 lastActiveTime: '30分钟前活跃', 251 lastActiveTime: '30分钟前活跃',
240 avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg' 252 avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
253 + },
254 + {
255 + id: 1,
256 + name: '张明',
257 + gender: 'male',
258 + className: '高一(3)班',
259 + phone: '13812345678',
260 + lastActiveTime: '5分钟前活跃',
261 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
262 + },
263 + {
264 + id: 2,
265 + name: '李华',
266 + gender: 'female',
267 + className: '高一(3)班',
268 + phone: '13987654321',
269 + lastActiveTime: '10分钟前活跃',
270 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
271 + },
272 + {
273 + id: 3,
274 + name: '王强',
275 + gender: 'male',
276 + className: '高一(2)班',
277 + phone: '13512349876',
278 + lastActiveTime: '15分钟前活跃',
279 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
280 + },
281 + {
282 + id: 4,
283 + name: '赵敏',
284 + gender: 'female',
285 + className: '高一(1)班',
286 + phone: '13643214321',
287 + lastActiveTime: '30分钟前活跃',
288 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
241 } 289 }
242 ]) 290 ])
243 291
...@@ -360,7 +408,7 @@ onMounted(() => { ...@@ -360,7 +408,7 @@ onMounted(() => {
360 }) 408 })
361 </script> 409 </script>
362 410
363 -<style scoped> 411 +<style lang="less">
364 /* 自定义样式 */ 412 /* 自定义样式 */
365 .van-dropdown-menu { 413 .van-dropdown-menu {
366 background-color: white; 414 background-color: white;
...@@ -374,8 +422,8 @@ onMounted(() => { ...@@ -374,8 +422,8 @@ onMounted(() => {
374 } 422 }
375 423
376 .van-search { 424 .van-search {
377 - background-color: #f9fafb; 425 + // background-color: #f9fafb;
378 - border-radius: 0.5rem; 426 + // border-radius: 0.5rem;
379 } 427 }
380 428
381 .van-list { 429 .van-list {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 * @Author: hookehuyr hookehuyr@gmail.com 2 * @Author: hookehuyr hookehuyr@gmail.com
3 * @Date: 2025-06-19 17:12:19 3 * @Date: 2025-06-19 17:12:19
4 * @LastEditors: hookehuyr hookehuyr@gmail.com 4 * @LastEditors: hookehuyr hookehuyr@gmail.com
5 - * @LastEditTime: 2025-06-19 23:27:57 5 + * @LastEditTime: 2025-06-20 00:11:37
6 * @FilePath: /mlaj/src/views/teacher/studentPage.vue 6 * @FilePath: /mlaj/src/views/teacher/studentPage.vue
7 * @Description: 学生详情页面 7 * @Description: 学生详情页面
8 --> 8 -->
...@@ -43,7 +43,16 @@ ...@@ -43,7 +43,16 @@
43 <span class="text-sm font-medium text-gray-700">所学课程</span> 43 <span class="text-sm font-medium text-gray-700">所学课程</span>
44 </div> 44 </div>
45 <div class="flex flex-wrap gap-2"> 45 <div class="flex flex-wrap gap-2">
46 - <van-tag v-for="course in studentInfo.courses" :key="course" type="primary" size="large"> 46 + <van-tag
47 + v-for="course in studentInfo.courses"
48 + :key="course"
49 + :type="selectedCourses.includes(course) ? 'primary' : 'default'"
50 + :color="selectedCourses.includes(course) ? '#10b981' : '#f0f0f0'"
51 + :text-color="selectedCourses.includes(course) ? '#ffffff' : '#666666'"
52 + size="large"
53 + @click="toggleCourseSelection(course)"
54 + class="cursor-pointer transition-all duration-200 hover:opacity-80"
55 + >
47 {{ course }} 56 {{ course }}
48 </van-tag> 57 </van-tag>
49 </div> 58 </div>
...@@ -263,6 +272,9 @@ const studentInfo = ref({ ...@@ -263,6 +272,9 @@ const studentInfo = ref({
263 courses: ['讲师训2025', '亲子学堂课程'] 272 courses: ['讲师训2025', '亲子学堂课程']
264 }) 273 })
265 274
275 +// 选中的课程列表(默认选中第一个课程)
276 +const selectedCourses = ref([studentInfo.value.courses[0] || ''])
277 +
266 // 统计数据 278 // 统计数据
267 const studentStats = ref({ 279 const studentStats = ref({
268 attendanceRate: 92, 280 attendanceRate: 92,
...@@ -277,6 +289,23 @@ const activeTab = ref('homework') ...@@ -277,6 +289,23 @@ const activeTab = ref('homework')
277 const statusFilter = ref('按状态') 289 const statusFilter = ref('按状态')
278 const showStatusPopup = ref(false) 290 const showStatusPopup = ref(false)
279 291
292 +/**
293 + * 切换课程选中状态(单选模式)
294 + * @param {string} course - 课程名称
295 + */
296 +const toggleCourseSelection = (course) => {
297 + if (selectedCourses.value.includes(course)) {
298 + // 如果已选中,则取消选中
299 + selectedCourses.value = []
300 + } else {
301 + // 如果未选中,则设置为当前选中的课程(单选)
302 + selectedCourses.value = [course]
303 + }
304 +
305 + // 可以在这里添加其他业务逻辑,比如筛选相关数据
306 + console.log('当前选中的课程:', selectedCourses.value)
307 +}
308 +
280 // 状态选项 309 // 状态选项
281 const statusOptions = ref([ 310 const statusOptions = ref([
282 { text: '按状态', value: '按状态' }, 311 { text: '按状态', value: '按状态' },
......