hookehuyr

feat(teacher): 新增教师模块打卡管理和我的班级功能

- 添加打卡管理页面,包含日历视图、打卡统计和学生动态
- 新增我的班级页面,实现班级成员管理和数据统计展示
- 更新路由配置和组件类型声明
- 优化页面交互和样式细节
...@@ -36,6 +36,7 @@ declare module 'vue' { ...@@ -36,6 +36,7 @@ declare module 'vue' {
36 VanCell: typeof import('vant/es')['Cell'] 36 VanCell: typeof import('vant/es')['Cell']
37 VanCellGroup: typeof import('vant/es')['CellGroup'] 37 VanCellGroup: typeof import('vant/es')['CellGroup']
38 VanCheckbox: typeof import('vant/es')['Checkbox'] 38 VanCheckbox: typeof import('vant/es')['Checkbox']
39 + VanCircle: typeof import('vant/es')['Circle']
39 VanCol: typeof import('vant/es')['Col'] 40 VanCol: typeof import('vant/es')['Col']
40 VanConfigProvider: typeof import('vant/es')['ConfigProvider'] 41 VanConfigProvider: typeof import('vant/es')['ConfigProvider']
41 VanDatePicker: typeof import('vant/es')['DatePicker'] 42 VanDatePicker: typeof import('vant/es')['DatePicker']
...@@ -61,9 +62,12 @@ declare module 'vue' { ...@@ -61,9 +62,12 @@ declare module 'vue' {
61 VanRate: typeof import('vant/es')['Rate'] 62 VanRate: typeof import('vant/es')['Rate']
62 VanRow: typeof import('vant/es')['Row'] 63 VanRow: typeof import('vant/es')['Row']
63 VanSearch: typeof import('vant/es')['Search'] 64 VanSearch: typeof import('vant/es')['Search']
65 + VanSticky: typeof import('vant/es')['Sticky']
64 VanSwipe: typeof import('vant/es')['Swipe'] 66 VanSwipe: typeof import('vant/es')['Swipe']
65 VanSwipeItem: typeof import('vant/es')['SwipeItem'] 67 VanSwipeItem: typeof import('vant/es')['SwipeItem']
66 VanTab: typeof import('vant/es')['Tab'] 68 VanTab: typeof import('vant/es')['Tab']
69 + VanTabbar: typeof import('vant/es')['Tabbar']
70 + VanTabbarItem: typeof import('vant/es')['TabbarItem']
67 VanTabs: typeof import('vant/es')['Tabs'] 71 VanTabs: typeof import('vant/es')['Tabs']
68 VanTag: typeof import('vant/es')['Tag'] 72 VanTag: typeof import('vant/es')['Tag']
69 VanUploader: typeof import('vant/es')['Uploader'] 73 VanUploader: typeof import('vant/es')['Uploader']
......
1 /* 1 /*
2 * @Date: 2025-06-17 16:46:50 2 * @Date: 2025-06-17 16:46:50
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-06-18 11:34:36 4 + * @LastEditTime: 2025-06-19 13:23:04
5 * @FilePath: /mlaj/src/router/teacher.js 5 * @FilePath: /mlaj/src/router/teacher.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
8 export default [ 8 export default [
9 { 9 {
10 - path: '/teacher/index', 10 + path: '/teacher/checkin',
11 name: 'Teacher', 11 name: 'Teacher',
12 - component: () => import('../views/teacher/testPage.vue'), 12 + component: () => import('../views/teacher/checkinPage.vue'),
13 meta: { 13 meta: {
14 - title: '教师', 14 + title: '打卡管理',
15 requiresAuth: true 15 requiresAuth: true
16 }, 16 },
17 }, 17 },
...@@ -24,4 +24,13 @@ export default [ ...@@ -24,4 +24,13 @@ export default [
24 requiresAuth: true 24 requiresAuth: true
25 }, 25 },
26 }, 26 },
27 + {
28 + path: '/teacher/myClass',
29 + name: 'MyClass',
30 + component: () => import('../views/teacher/myClassPage.vue'),
31 + meta: {
32 + title: '我的班级',
33 + requiresAuth: true
34 + },
35 + },
27 ] 36 ]
......
...@@ -90,6 +90,15 @@ ...@@ -90,6 +90,15 @@
90 90
91 <FrostedGlass class="rounded-xl overflow-hidden mb-5"> 91 <FrostedGlass class="rounded-xl overflow-hidden mb-5">
92 <MenuItem 92 <MenuItem
93 + v-for="item in menuItems4"
94 + :key="item.path"
95 + v-bind="item"
96 + @click="handleMenuClick(item.path)"
97 + />
98 + </FrostedGlass>
99 +
100 + <FrostedGlass class="rounded-xl overflow-hidden mb-5">
101 + <MenuItem
93 v-for="item in menuItems3" 102 v-for="item in menuItems3"
94 :key="item.path" 103 :key="item.path"
95 v-bind="item" 104 v-bind="item"
...@@ -295,6 +304,19 @@ const menuItems3 = ref([ ...@@ -295,6 +304,19 @@ const menuItems3 = ref([
295 }, 304 },
296 ]); 305 ]);
297 306
307 +const menuItems4 = ref([
308 + {
309 + icon: "book",
310 + title: "打卡管理",
311 + path: "/teacher/checkin",
312 + },
313 + {
314 + icon: "user",
315 + title: "我的班级",
316 + path: "/teacher/myClass",
317 + },
318 +]);
319 +
298 const handleCheckin = () => { 320 const handleCheckin = () => {
299 if (checkinData.value.length) { 321 if (checkinData.value.length) {
300 showCheckInDialog.value = true; 322 showCheckInDialog.value = true;
......
1 <!-- 1 <!--
2 * @Date: 2025-05-29 15:34:17 2 * @Date: 2025-05-29 15:34:17
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-06-17 20:08:40 4 + * @LastEditTime: 2025-06-19 14:49:38
5 - * @FilePath: /mlaj/src/views/teacher/testPage.vue 5 + * @FilePath: /mlaj/src/views/teacher/checkinPage.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
8 <template> 8 <template>
9 <AppLayout :hasTitle="false"> 9 <AppLayout :hasTitle="false">
10 <van-config-provider :theme-vars="themeVars"> 10 <van-config-provider :theme-vars="themeVars">
11 - <van-dropdown-menu active-color="#4caf50"> 11 + <van-sticky>
12 - <van-dropdown-item v-model="value1" :options="option1" /> 12 + <van-dropdown-menu active-color="#4caf50">
13 - <van-dropdown-item v-model="value2" :options="option2" /> 13 + <van-dropdown-item v-model="value1" :options="option1" @change="handleChange1" />
14 - <van-dropdown-item v-model="value3" :options="option3" /> 14 + <van-dropdown-item v-model="value2" :options="option2" @change="handleChange2" />
15 - </van-dropdown-menu> 15 + <van-dropdown-item v-model="value3" :options="option3" @change="handleChange3" />
16 + </van-dropdown-menu>
17 + </van-sticky>
16 18
17 <van-calendar ref="myRefCalendar" :title="taskDetail.title" :poppable="false" :show-confirm="false" :style="{ height: calendarHeight }" 19 <van-calendar ref="myRefCalendar" :title="taskDetail.title" :poppable="false" :show-confirm="false" :style="{ height: calendarHeight }"
18 switch-mode="year-month" color="#4caf50" :formatter="formatter" row-height="50" :show-mark="false" 20 switch-mode="year-month" color="#4caf50" :formatter="formatter" row-height="50" :show-mark="false"
...@@ -22,21 +24,21 @@ ...@@ -22,21 +24,21 @@
22 <div style="padding: 0 1rem;"> 24 <div style="padding: 0 1rem;">
23 <van-row gutter="15"> 25 <van-row gutter="15">
24 <van-col span="12"> 26 <van-col span="12">
25 - <van-button type="primary" block icon="add-square">主要按钮</van-button> 27 + <van-button type="primary" block icon="photo" @click="handleAdd">安排打卡</van-button>
26 </van-col> 28 </van-col>
27 <van-col span="12"> 29 <van-col span="12">
28 - <van-button type="primary" block icon="video">主要按钮</van-button> 30 + <van-button type="primary" block icon="video">设置作业</van-button>
29 </van-col> 31 </van-col>
30 </van-row> 32 </van-row>
31 </div> 33 </div>
32 34
33 <div v-if="showProgress" class="text-wrapper"> 35 <div v-if="showProgress" class="text-wrapper">
34 - <div class="text-header">目标进度</div> 36 + <div class="text-header">打卡统计</div>
35 <div style="background-color: #FFF; margin-top: 1rem;"> 37 <div style="background-color: #FFF; margin-top: 1rem;">
36 <div class="grade-percentage-main"> 38 <div class="grade-percentage-main">
37 <van-row justify="space-between" style="margin: 0.5rem 0; font-size: 0.9rem;"> 39 <van-row justify="space-between" style="margin: 0.5rem 0; font-size: 0.9rem;">
38 <van-col span="12"> 40 <van-col span="12">
39 - <span>作业目标</span> 41 + <span>年级目标</span>
40 </van-col> 42 </van-col>
41 <van-col span="12" style="text-align: right;"> 43 <van-col span="12" style="text-align: right;">
42 <span style="font-weight: bold;">{{ progress1 }}%</span> 44 <span style="font-weight: bold;">{{ progress1 }}%</span>
...@@ -65,13 +67,8 @@ ...@@ -65,13 +67,8 @@
65 </div> 67 </div>
66 </div> 68 </div>
67 69
68 - <van-tabs v-model:active="active" style="margin: 0 1rem;"> 70 + <div style="padding: 0 1rem; color: #4caf50;">
69 - <van-tab title="标签 1"></van-tab> 71 + <div class="text-header">学生动态</div>
70 - <van-tab title="标签 2"></van-tab>
71 - <van-tab title="标签 3"></van-tab>
72 - </van-tabs>
73 -
74 - <div v-if="active === 0" style="padding: 0 1rem; color: #4caf50;">
75 <van-list 72 <van-list
76 v-if="checkinDataList.length" 73 v-if="checkinDataList.length"
77 v-model:loading="loading" 74 v-model:loading="loading"
...@@ -92,12 +89,12 @@ ...@@ -92,12 +89,12 @@
92 <div class="post-time">{{ post.user.time }}</div> 89 <div class="post-time">{{ post.user.time }}</div>
93 </div> 90 </div>
94 </van-col> 91 </van-col>
95 - <van-col span="3"> 92 + <!-- <van-col span="3">
96 <div v-if="post.is_my" class="post-menu"> 93 <div v-if="post.is_my" class="post-menu">
97 <van-icon name="edit" @click="editCheckin(post)" /> 94 <van-icon name="edit" @click="editCheckin(post)" />
98 <van-icon name="delete-o" @click="delCheckin(post)" /> 95 <van-icon name="delete-o" @click="delCheckin(post)" />
99 </div> 96 </div>
100 - </van-col> 97 + </van-col> -->
101 </van-row> 98 </van-row>
102 </div> 99 </div>
103 <div class="post-content"> 100 <div class="post-content">
...@@ -189,21 +186,33 @@ const value1 = ref(0); ...@@ -189,21 +186,33 @@ const value1 = ref(0);
189 const value2 = ref('a'); 186 const value2 = ref('a');
190 const value3 = ref('v'); 187 const value3 = ref('v');
191 const option1 = [ 188 const option1 = [
192 - { text: '全部商品', value: 0 }, 189 + { text: '全部年级', value: 0 },
193 - { text: '新款商品', value: 1 }, 190 + { text: '一年级', value: 1 },
194 - { text: '活动商品', value: 2 }, 191 + { text: '二年级', value: 2 },
195 ]; 192 ];
196 const option2 = [ 193 const option2 = [
197 - { text: '默认排序', value: 'a' }, 194 + { text: '全部班级', value: 'a' },
198 - { text: '好评排序', value: 'b' }, 195 + { text: '一班级', value: 'b' },
199 - { text: '销量排序', value: 'c' }, 196 + { text: '二班级', value: 'c' },
200 ]; 197 ];
201 const option3 = [ 198 const option3 = [
202 - { text: '默认排序', value: 'v' }, 199 + { text: '全部课程', value: 'v' },
203 - { text: '好评排序', value: 'b' }, 200 + { text: '一课程', value: 'b' },
204 - { text: '销量排序', value: 'c' }, 201 + { text: '二课程', value: 'c' },
205 ]; 202 ];
206 203
204 +const handleChange1 = (val) => {
205 + console.log('val', val);
206 +}
207 +
208 +const handleChange2 = (val) => {
209 + console.log('val', val);
210 +}
211 +
212 +const handleChange3 = (val) => {
213 + console.log('val', val);
214 +}
215 +
207 const active = ref(0); 216 const active = ref(0);
208 217
209 const myRefCalendar = ref(null); 218 const myRefCalendar = ref(null);
...@@ -426,11 +435,12 @@ const stopAllVideos = () => { ...@@ -426,11 +435,12 @@ const stopAllVideos = () => {
426 }); 435 });
427 }; 436 };
428 437
429 -const themeVars = { 438 +const themeVars = reactive({
430 calendarSelectedDayBackground: '#4caf50', 439 calendarSelectedDayBackground: '#4caf50',
431 calendarHeaderShadow: 'rgba(0, 0, 0, 0.1)', 440 calendarHeaderShadow: 'rgba(0, 0, 0, 0.1)',
432 calendarInfoLineHeight: '0.3rem', 441 calendarInfoLineHeight: '0.3rem',
433 -} 442 + buttonNormalFontSize: '1rem',
443 +})
434 444
435 const progress1 = ref(0); 445 const progress1 = ref(0);
436 // const progress2 = ref(76); 446 // const progress2 = ref(76);
...@@ -467,9 +477,16 @@ const formatter = (day) => { ...@@ -467,9 +477,16 @@ const formatter = (day) => {
467 477
468 // 检查是否已签到 478 // 检查是否已签到
469 if (checkin_days.includes(formattedDate)) { 479 if (checkin_days.includes(formattedDate)) {
470 - day.className = 'calendar-checkin'; 480 + // 如果是当前选中的已签到日期,使用特殊样式
471 - day.type = 'selected'; 481 + if (selectedDate.value === formattedDate) {
472 - day.bottomInfo = '已签到'; 482 + day.className = 'calendar-selected';
483 + day.type = 'selected';
484 + day.bottomInfo = '已签到';
485 + } else {
486 + day.className = 'calendar-checkin';
487 + day.type = 'selected';
488 + day.bottomInfo = '已签到';
489 + }
473 } 490 }
474 } 491 }
475 492
...@@ -483,14 +500,22 @@ const formatter = (day) => { ...@@ -483,14 +500,22 @@ const formatter = (day) => {
483 return day; 500 return day;
484 } 501 }
485 502
503 +// 添加一个响应式变量来存储当前选中的日期
504 +const selectedDate = ref('');
505 +
486 const onSelectDay = (day) => { 506 const onSelectDay = (day) => {
487 getTaskDetail(dayjs(day).format('YYYY-MM')); 507 getTaskDetail(dayjs(day).format('YYYY-MM'));
508 +
509 + // 更新当前选中的日期
510 + const currentSelectedDate = dayjs(day).format('YYYY-MM-DD');
511 + selectedDate.value = currentSelectedDate;
512 +
488 // 修改浏览器地址把当前的date加入地址栏, 页面不刷新 513 // 修改浏览器地址把当前的date加入地址栏, 页面不刷新
489 router.push({ 514 router.push({
490 path: route.path, 515 path: route.path,
491 query: { 516 query: {
492 ...route.query, 517 ...route.query,
493 - date: dayjs(day).format('YYYY-MM-DD') 518 + date: currentSelectedDate
494 } 519 }
495 }) 520 })
496 // 重置分页参数 521 // 重置分页参数
...@@ -498,9 +523,10 @@ const onSelectDay = (day) => { ...@@ -498,9 +523,10 @@ const onSelectDay = (day) => {
498 checkinDataList.value = [] 523 checkinDataList.value = []
499 finished.value = false 524 finished.value = false
500 // 重新加载数据 525 // 重新加载数据
501 - onLoad(dayjs(day).format('YYYY-MM-DD')) 526 + onLoad(currentSelectedDate)
502 } 527 }
503 528
529 +
504 const onClickSubtitle = (evt) => { 530 const onClickSubtitle = (evt) => {
505 console.warn('点击了日期标题'); 531 console.warn('点击了日期标题');
506 } 532 }
...@@ -685,6 +711,17 @@ const formatData = (data) => { ...@@ -685,6 +711,17 @@ const formatData = (data) => {
685 }) 711 })
686 return formattedData; 712 return formattedData;
687 } 713 }
714 +
715 +const handleAdd = () => {
716 + router.push({
717 + path: '/teacher/form',
718 + query: {
719 + post_id: route.query.id,
720 + type: 'image',
721 + status: 'add',
722 + }
723 + })
724 +}
688 </script> 725 </script>
689 726
690 <style lang="less"> 727 <style lang="less">
......
1 +<!--
2 + * @Author: hookehuyr hookehuyr@gmail.com
3 + * @Date: 2025-01-20 10:00:00
4 + * @LastEditors: hookehuyr hookehuyr@gmail.com
5 + * @LastEditTime: 2025-06-19 16:46:06
6 + * @FilePath: /mlaj/src/views/teacher/myClassPage.vue
7 + * @Description: 我的班级页面
8 +-->
9 +<template>
10 + <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen">
11 + <!-- 用户信息卡片 -->
12 + <div>
13 + <div class="bg-white p-4">
14 + <div class="flex items-center mb-4">
15 + <van-image round width="3rem" height="3rem"
16 + :src="userInfo.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'" fit="cover" class="mr-3" />
17 + <div class="flex-1">
18 + <h2 class="text-lg font-bold text-gray-800">{{ userInfo.name }}</h2>
19 + <div class="flex items-center mt-1">
20 + <van-icon name="clock-o" size="20" color="#10b981" class="mr-1" />
21 + <van-icon name="chat-o" size="20" color="#10b981" class="mr-1" />
22 + <van-icon name="comment-circle-o" size="20" color="#10b981" />
23 + </div>
24 + </div>
25 + </div>
26 + </div>
27 + </div>
28 +
29 + <!-- 筛选器 -->
30 + <div>
31 + <van-sticky>
32 + <van-dropdown-menu active-color="#10b981">
33 + <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="courseFilter" :options="courseOptions" @change="handleCourseChange" />
36 + </van-dropdown-menu>
37 + </van-sticky>
38 + </div>
39 +
40 + <!-- 统计数据 -->
41 + <div>
42 + <van-row>
43 + <!-- 出勤率 -->
44 + <van-col span="8">
45 + <div class="bg-white p-4 text-center">
46 + <div class="relative w-16 h-16 mx-auto mb-2">
47 + <van-circle v-model:current-rate="attendanceRate" :rate="attendanceRate" :speed="100"
48 + :text="attendanceRate + '%'" stroke-width="50" color="#10b981" size="64" />
49 + </div>
50 + <div class="text-sm text-gray-600">出勤率</div>
51 + </div>
52 + </van-col>
53 + <!-- 任务完成 -->
54 + <van-col span="8">
55 + <div class="bg-white p-4 text-center">
56 + <div class="relative w-16 h-16 mx-auto mb-2">
57 + <van-circle v-model:current-rate="taskCompletionRate" :rate="taskCompletionRate" :speed="100"
58 + :text="taskCompletionRate + '%'" stroke-width="50" color="#3b82f6" size="64" />
59 + </div>
60 + <div class="text-sm text-gray-600">任务完成</div>
61 + </div>
62 + </van-col>
63 + <!-- 学习进度 -->
64 + <van-col span="8">
65 + <div class="bg-white p-4 text-center">
66 + <div class="relative w-16 h-16 mx-auto mb-2">
67 + <van-circle v-model:current-rate="learningProgress" :rate="learningProgress" :speed="100"
68 + :text="learningProgress + '%'" stroke-width="50" color="#f59e0b" size="64" />
69 + </div>
70 + <div class="text-sm text-gray-600">学习进度</div>
71 + </div>
72 + </van-col>
73 + </van-row>
74 + </div>
75 +
76 + <!-- 班级成员 -->
77 + <div class="">
78 + <div class="">
79 + <!-- 标题和搜索 -->
80 + <div class="p-4 border-b border-gray-100 bg-white">
81 + <div class="flex items-center justify-between mb-3">
82 + <h3 class="text-lg font-bold text-gray-800">班级成员 ({{ studentList.length }})</h3>
83 + <div @click="showSortPopup = true" class="flex items-center text-sm text-gray-600 cursor-pointer">
84 + <span>{{ sortFilter }}</span>
85 + <van-icon name="arrow-down" size="14" class="ml-1" />
86 + </div>
87 + </div>
88 + <van-search v-model="searchKeyword" placeholder="请搜索" @search="handleSearch" @input="handleSearch" />
89 + </div>
90 +
91 + <!-- 学生列表 -->
92 + <div class="p-4">
93 + <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
94 + <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"
96 + @click="handleStudentClick(student)">
97 + <div class="flex items-center flex-1">
98 + <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" />
100 + <div class="flex-1">
101 + <div class="flex items-center">
102 + <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" />
104 + <van-icon v-else name="like-o" color="#ec4899" size="14" />
105 + </div>
106 + <div class="text-sm text-gray-500" style="text-align: left;">{{ student.className }}</div>
107 + </div>
108 + </div>
109 + <div class="text-right">
110 + <div class="flex items-center text-sm text-gray-500 mb-1">
111 + <van-icon name="phone-o" size="12" class="mr-1" />
112 + <span>{{ formatPhone(student.phone) }}</span>
113 + </div>
114 + <div class="text-xs text-gray-400">{{ student.lastActiveTime }}</div>
115 + </div>
116 + <van-icon name="arrow" color="#d1d5db" size="16" class="ml-2" />
117 + </div>
118 + </van-list>
119 + </div>
120 + </div>
121 + </div>
122 +
123 + <!-- 排序弹窗 -->
124 + <van-popup v-model:show="showSortPopup" position="bottom" round>
125 + <div class="p-4">
126 + <div class="text-center text-lg font-bold mb-4">选择排序方式</div>
127 + <van-cell-group>
128 + <van-cell
129 + v-for="option in sortOptions"
130 + :key="option.value"
131 + :title="option.text"
132 + clickable
133 + @click="onSortSelect(option)"
134 + :border="false"
135 + :class="{ 'text-green-600': sortFilter === option.value }"
136 + >
137 + <template #right-icon>
138 + <van-icon v-if="sortFilter === option.value" name="success" color="#10b981" />
139 + </template>
140 + </van-cell>
141 + </van-cell-group>
142 + <div class="mt-4">
143 + <van-button block @click="showSortPopup = false">取消</van-button>
144 + </div>
145 + </div>
146 + </van-popup>
147 + </div>
148 +</template>
149 +
150 +<script setup>
151 +import { ref, computed, onMounted } from 'vue'
152 +import { useRouter } from 'vue-router'
153 +import AppLayout from '@/layouts/AppLayout.vue'
154 +
155 +const router = useRouter()
156 +
157 +// 用户信息
158 +const userInfo = ref({
159 + name: '我的班级',
160 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
161 +})
162 +
163 +// 筛选器数据
164 +const gradeFilter = ref('全部年级')
165 +const classFilter = ref('全部班级')
166 +const courseFilter = ref('全部课程')
167 +const sortFilter = ref('按活跃度')
168 +const searchKeyword = ref('')
169 +const showSortPopup = ref(false)
170 +
171 +// 筛选器选项
172 +const gradeOptions = ref([
173 + { text: '全部年级', value: '全部年级' },
174 + { text: '高一年级', value: '高一年级' },
175 + { text: '高二年级', value: '高二年级' },
176 + { text: '高三年级', value: '高三年级' }
177 +])
178 +
179 +const classOptions = ref([
180 + { text: '全部班级', value: '全部班级' },
181 + { text: '高一(1)班', value: '高一(1)班' },
182 + { text: '高一(2)班', value: '高一(2)班' },
183 + { text: '高一(3)班', value: '高一(3)班' }
184 +])
185 +
186 +const courseOptions = ref([
187 + { text: '全部课程', value: '全部课程' },
188 + { text: '语文', value: '语文' },
189 + { text: '数学', value: '数学' },
190 + { text: '英语', value: '英语' }
191 +])
192 +
193 +const sortOptions = ref([
194 + { text: '按活跃度', value: '按活跃度' },
195 + { text: '按姓名', value: '按姓名' },
196 + { text: '按成绩', value: '按成绩' }
197 +])
198 +
199 +// 统计数据
200 +const attendanceRate = ref(85)
201 +const taskCompletionRate = ref(76)
202 +const learningProgress = ref(92)
203 +
204 +// 学生列表
205 +const studentList = ref([
206 + {
207 + id: 1,
208 + name: '张明',
209 + gender: 'male',
210 + className: '高一(3)班',
211 + phone: '13812345678',
212 + lastActiveTime: '5分钟前活跃',
213 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
214 + },
215 + {
216 + id: 2,
217 + name: '李华',
218 + gender: 'female',
219 + className: '高一(3)班',
220 + phone: '13987654321',
221 + lastActiveTime: '10分钟前活跃',
222 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
223 + },
224 + {
225 + id: 3,
226 + name: '王强',
227 + gender: 'male',
228 + className: '高一(2)班',
229 + phone: '13512349876',
230 + lastActiveTime: '15分钟前活跃',
231 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
232 + },
233 + {
234 + id: 4,
235 + name: '赵敏',
236 + gender: 'female',
237 + className: '高一(1)班',
238 + phone: '13643214321',
239 + lastActiveTime: '30分钟前活跃',
240 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
241 + }
242 +])
243 +
244 +// 列表加载状态
245 +const loading = ref(false)
246 +const finished = ref(false)
247 +
248 +/**
249 + * 过滤后的学生列表
250 + */
251 +const filteredStudentList = computed(() => {
252 + let filtered = studentList.value
253 +
254 + // 按班级筛选
255 + if (classFilter.value !== '全部班级') {
256 + filtered = filtered.filter(student => student.className === classFilter.value)
257 + }
258 +
259 + // 按搜索关键词筛选
260 + if (searchKeyword.value) {
261 + filtered = filtered.filter(student =>
262 + student.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
263 + )
264 + }
265 +
266 + // 排序
267 + if (sortFilter.value === '按姓名') {
268 + filtered.sort((a, b) => a.name.localeCompare(b.name))
269 + }
270 +
271 + return filtered
272 +})
273 +
274 +/**
275 + * 格式化手机号
276 + * @param {string} phone - 手机号
277 + * @returns {string} 格式化后的手机号
278 + */
279 +const formatPhone = (phone) => {
280 + if (!phone) return ''
281 + return phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3')
282 +}
283 +
284 +/**
285 + * 处理年级筛选变化
286 + * @param {string} value - 选中的年级
287 + */
288 +const handleGradeChange = (value) => {
289 + console.log('年级筛选:', value)
290 + // 这里可以根据年级筛选更新班级选项
291 +}
292 +
293 +/**
294 + * 处理班级筛选变化
295 + * @param {string} value - 选中的班级
296 + */
297 +const handleClassChange = (value) => {
298 + console.log('班级筛选:', value)
299 +}
300 +
301 +/**
302 + * 处理课程筛选变化
303 + * @param {string} value - 选中的课程
304 + */
305 +const handleCourseChange = (value) => {
306 + console.log('课程筛选:', value)
307 +}
308 +
309 +/**
310 + * 处理排序变化
311 + * @param {string} value - 选中的排序方式
312 + */
313 +const handleSortChange = (value) => {
314 + console.log('排序变化:', value)
315 +}
316 +
317 +/**
318 + * 处理排序选择
319 + * @param {Object} option - 选中的排序选项
320 + */
321 +const onSortSelect = (option) => {
322 + sortFilter.value = option.value
323 + showSortPopup.value = false
324 + handleSortChange(option.value)
325 +}
326 +
327 +/**
328 + * 处理搜索
329 + * @param {string} value - 搜索关键词
330 + */
331 +const handleSearch = (value) => {
332 + console.log('搜索:', value)
333 +}
334 +
335 +/**
336 + * 处理学生点击
337 + * @param {Object} student - 学生信息
338 + */
339 +const handleStudentClick = (student) => {
340 + console.log('点击学生:', student)
341 + // 这里可以跳转到学生详情页
342 +}
343 +
344 +/**
345 + * 加载更多数据
346 + */
347 +const onLoad = () => {
348 + setTimeout(() => {
349 + loading.value = false
350 + finished.value = true
351 + }, 1000)
352 +}
353 +
354 +/**
355 + * 组件挂载时初始化数据
356 + */
357 +onMounted(() => {
358 + // 这里可以调用API获取实际数据
359 + console.log('我的班级页面已加载')
360 +})
361 +</script>
362 +
363 +<style scoped>
364 +/* 自定义样式 */
365 +.van-dropdown-menu {
366 + background-color: white;
367 + border-radius: 0.75rem;
368 + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
369 +}
370 +
371 +.van-circle {
372 + font-size: 12px;
373 + font-weight: bold;
374 +}
375 +
376 +.van-search {
377 + background-color: #f9fafb;
378 + border-radius: 0.5rem;
379 +}
380 +
381 +.van-list {
382 + min-height: 200px;
383 +}
384 +</style>