hookehuyr

feat(教师端): 优化作业详情显示和日历组件交互

重构作业详情显示逻辑,提取格式化函数复用
调整日历组件布局,增加打卡规则查看功能
修改任务筛选器宽度和对齐方式
1 <template> 1 <template>
2 - <div class="inline-block"> 2 + <div class="inline-block" style="width: 60%;">
3 - <div @click="show = true" class="text-sm text-gray-500 flex items-center cursor-pointer ml-2"> 3 + <div @click="show = true" class="text-sm text-gray-500 flex items-center justify-end cursor-pointer ml-2">
4 <span class="mr-1">{{ selectedLabel }}</span> 4 <span class="mr-1">{{ selectedLabel }}</span>
5 <van-icon name="arrow-down" /> 5 <van-icon name="arrow-down" />
6 </div> 6 </div>
......
1 <!-- 1 <!--
2 * @Date: 2025-01-25 15:34:17 2 * @Date: 2025-01-25 15:34:17
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-14 22:08:03 4 + * @LastEditTime: 2025-12-18 22:06:48
5 * @FilePath: /mlaj/src/components/ui/CollapsibleCalendar.vue 5 * @FilePath: /mlaj/src/components/ui/CollapsibleCalendar.vue
6 * @Description: 可折叠日历组件 6 * @Description: 可折叠日历组件
7 --> 7 -->
...@@ -20,15 +20,16 @@ ...@@ -20,15 +20,16 @@
20 <div class="calendar-date-main">{{ formattedCurrentDate }}</div> 20 <div class="calendar-date-main">{{ formattedCurrentDate }}</div>
21 <div class="calendar-weekday">{{ formattedWeekday }}</div> 21 <div class="calendar-weekday">{{ formattedWeekday }}</div>
22 </div> --> 22 </div> -->
23 - <div class="calendar-title-wrapper" @click="expandCalendar"> 23 + <div class="calendar-title-wrapper">
24 <div class="calendar-title">{{ title }}</div> 24 <div class="calendar-title">{{ title }}</div>
25 - <div class="calendar-subtitle">点击切换日期</div> 25 + <!-- <div class="text-xs text-gray-500 mt-1">点击查看打卡规则</div> -->
26 </div> 26 </div>
27 </div> 27 </div>
28 <div class="calendar-content"> 28 <div class="calendar-content">
29 - <div class="calendar-date-display"> 29 + <div class="calendar-date-display" @click="expandCalendar">
30 <div class="calendar-date-main">{{ formattedCurrentDate }}</div> 30 <div class="calendar-date-main">{{ formattedCurrentDate }}</div>
31 <div class="calendar-weekday">{{ formattedWeekday }}</div> 31 <div class="calendar-weekday">{{ formattedWeekday }}</div>
32 + <div class="text-xs text-gray-500 mt-1">点击切换日期</div>
32 </div> 33 </div>
33 <!-- <div class="calendar-action"> 34 <!-- <div class="calendar-action">
34 <div class="calendar-action-text">指定日期</div> 35 <div class="calendar-action-text">指定日期</div>
...@@ -48,6 +49,7 @@ ...@@ -48,6 +49,7 @@
48 </svg> 49 </svg>
49 </div> 50 </div>
50 </div> 51 </div>
52 + <div v-if="selectedSubtask" class="text-xs text-gray-500 mt-1 cursor-pointer hover:text-green-600 transition-colors" @click.stop="openRulesPopup">点击查看打卡规则</div>
51 </div> 53 </div>
52 </div> 54 </div>
53 </div> 55 </div>
...@@ -93,6 +95,25 @@ ...@@ -93,6 +95,25 @@
93 :default-index="0" 95 :default-index="0"
94 /> 96 />
95 </van-popup> 97 </van-popup>
98 + <!-- 打卡规则弹窗 -->
99 + <van-popup
100 + v-model:show="showRulesPopup"
101 + round
102 + position="bottom"
103 + closeable
104 + :style="{ maxHeight: '50%' }"
105 + >
106 + <div class="p-4">
107 + <div class="text-lg font-bold mb-4 text-center text-gray-800">打卡规则</div>
108 + <div class="details text-sm text-gray-600 pl-4 border-l-4 border-green-500" v-if="formattedRules">
109 + <div class="mb-2">周期:{{ formattedRules.cycle }}</div>
110 + <div class="mb-2">频次:{{ formattedRules.frequency }}</div>
111 + <div v-if="formattedRules.begin_date" class="mb-2">开始时间:{{ formattedRules.begin_date }}</div>
112 + <div v-if="formattedRules.end_date" class="mb-2">截止时间:{{ formattedRules.end_date }}</div>
113 + <div v-if="formattedRules.attachment_type" class="mb-2">附件类型:{{ formattedRules.attachment_type }}</div>
114 + </div>
115 + </div>
116 + </van-popup>
96 </div> 117 </div>
97 </template> 118 </template>
98 119
...@@ -131,6 +152,9 @@ const currentDate = ref(props.modelValue || new Date()) ...@@ -131,6 +152,9 @@ const currentDate = ref(props.modelValue || new Date())
131 // 作业筛选相关 152 // 作业筛选相关
132 const showCoursePicker = ref(false) 153 const showCoursePicker = ref(false)
133 const selectedCourseText = ref('全部作业') 154 const selectedCourseText = ref('全部作业')
155 +const selectedCourseId = ref('')
156 +const showRulesPopup = ref(false)
157 +
134 const courseColumns = computed(() => { 158 const courseColumns = computed(() => {
135 return [ 159 return [
136 { text: '全部作业', value: '' }, 160 { text: '全部作业', value: '' },
...@@ -141,14 +165,58 @@ const courseColumns = computed(() => { ...@@ -141,14 +165,58 @@ const courseColumns = computed(() => {
141 ] 165 ]
142 }) 166 })
143 167
168 +const selectedSubtask = computed(() => {
169 + if (!selectedCourseId.value) return null
170 + return props.subtaskList.find(item => item.id === selectedCourseId.value)
171 +})
172 +
173 +const formattedRules = computed(() => {
174 + if (!selectedSubtask.value) return null
175 + const data = selectedSubtask.value
176 +
177 + const cycleMap = {
178 + '0': '本周期',
179 + '30': '每月',
180 + '7': '每周',
181 + '1': '每日'
182 + }
183 + const cycleText = cycleMap[data.cycle] || data.cycle || '每日'
184 +
185 + let attachmentText = '文本'
186 + if (Array.isArray(data.attachment_type)) {
187 + const typeMap = {
188 + 'text': '文本',
189 + 'image': '图片',
190 + 'video': '视频',
191 + 'audio': '音频'
192 + }
193 + attachmentText = data.attachment_type.map(t => typeMap[t] || t).join('/')
194 + } else if (data.attachment_type) {
195 + attachmentText = data.attachment_type
196 + }
197 +
198 + return {
199 + cycle: cycleText,
200 + frequency: `每周期${data.frequency || 1}次`,
201 + begin_date: data.begin_date ? dayjs(data.begin_date).format('YYYY-MM-DD') : '',
202 + end_date: data.end_date ? dayjs(data.end_date).format('YYYY-MM-DD') : '',
203 + attachment_type: attachmentText
204 + }
205 +})
206 +
144 const openCoursePicker = () => { 207 const openCoursePicker = () => {
145 showCoursePicker.value = true 208 showCoursePicker.value = true
146 } 209 }
147 210
211 +const openRulesPopup = () => {
212 + showRulesPopup.value = true
213 +}
214 +
148 const onConfirmCourse = (result) => { 215 const onConfirmCourse = (result) => {
149 // 兼容 Vant 3/4 216 // 兼容 Vant 3/4
150 const option = result.selectedOptions ? result.selectedOptions[0] : result 217 const option = result.selectedOptions ? result.selectedOptions[0] : result
151 selectedCourseText.value = option.text 218 selectedCourseText.value = option.text
219 + selectedCourseId.value = option.value
152 showCoursePicker.value = false 220 showCoursePicker.value = false
153 emit('select-course', option.value) 221 emit('select-course', option.value)
154 } 222 }
...@@ -298,12 +366,6 @@ defineExpose({ ...@@ -298,12 +366,6 @@ defineExpose({
298 // text-overflow: ellipsis; 366 // text-overflow: ellipsis;
299 // white-space: nowrap; 367 // white-space: nowrap;
300 } 368 }
301 -
302 - .calendar-subtitle {
303 - font-size: 12px;
304 - color: #7f8c8d;
305 - font-weight: 400;
306 - }
307 } 369 }
308 370
309 .calendar-date-display { 371 .calendar-date-display {
...@@ -322,6 +384,12 @@ defineExpose({ ...@@ -322,6 +384,12 @@ defineExpose({
322 color: #4caf50; 384 color: #4caf50;
323 font-weight: 500; 385 font-weight: 500;
324 } 386 }
387 +
388 + .calendar-subtitle {
389 + font-size: 12px;
390 + color: #7f8c8d;
391 + font-weight: 400;
392 + }
325 } 393 }
326 } 394 }
327 395
......
1 <!-- 1 <!--
2 * @Date: 2025-11-19 21:00:00 2 * @Date: 2025-11-19 21:00:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-15 18:18:51 4 + * @LastEditTime: 2025-12-18 21:54:18
5 * @FilePath: /mlaj/src/views/teacher/taskHomePage.vue 5 * @FilePath: /mlaj/src/views/teacher/taskHomePage.vue
6 * @Description: 教师端作业主页(头部介绍、统计、日历与学生完成情况) 6 * @Description: 教师端作业主页(头部介绍、统计、日历与学生完成情况)
7 --> 7 -->
...@@ -27,11 +27,11 @@ ...@@ -27,11 +27,11 @@
27 27
28 <div class="intro text-base text-gray-700 leading-relaxed mb-3" v-html="task_intro"></div> 28 <div class="intro text-base text-gray-700 leading-relaxed mb-3" v-html="task_intro"></div>
29 <div class="details text-sm text-gray-600" v-if="!subtask_list.length || selectedSubtaskId"> 29 <div class="details text-sm text-gray-600" v-if="!subtask_list.length || selectedSubtaskId">
30 - <div class="detailItem">周期:{{ task_details.cycle }}</div> 30 + <div class="detailItem">周期:{{ display_details.cycle }}</div>
31 - <div class="detailItem">频次:{{ task_details.frequency }}</div> 31 + <div class="detailItem">频次:{{ display_details.frequency }}</div>
32 - <div v-if="task_details.begin_date" class="detailItem">开始时间:{{ task_details.begin_date }}</div> 32 + <div v-if="display_details.begin_date" class="detailItem">开始时间:{{ display_details.begin_date }}</div>
33 - <div v-if="task_details.end_date" class="detailItem">截止时间:{{ task_details.end_date }}</div> 33 + <div v-if="display_details.end_date" class="detailItem">截止时间:{{ display_details.end_date }}</div>
34 - <div v-if="task_details.attachment_type.length" class="detailItem">附件类型:{{ task_details.attachment_type 34 + <div v-if="display_details.attachment_type && display_details.attachment_type.length" class="detailItem">附件类型:{{ display_details.attachment_type
35 }}</div> 35 }}</div>
36 <div v-if="task_type === 'checkin'" class="detailItem">类型:打卡签到</div> 36 <div v-if="task_type === 'checkin'" class="detailItem">类型:打卡签到</div>
37 </div> 37 </div>
...@@ -304,56 +304,66 @@ const on_confirm_subtask = ({ selectedOptions }) => { ...@@ -304,56 +304,66 @@ const on_confirm_subtask = ({ selectedOptions }) => {
304 show_subtask_picker.value = false 304 show_subtask_picker.value = false
305 } 305 }
306 306
307 -/** 307 +const formatTaskDetails = (data) => {
308 - * 获取作业详情和学生完成情况
309 - */
310 -const fetchData = async () => {
311 - try {
312 - const res = await getTeacherTaskDetailAPI({
313 - id: task_id,
314 - subtask_id: selectedSubtaskId.value,
315 - date: selected_date.value
316 - })
317 -
318 - if (res.code) {
319 - task_title.value = res.data.title
320 - task_intro.value = res.data.note
321 - task_type.value = res.data.task_type || ''
322 - // 小作业列表
323 - subtask_list.value = res.data.subtask_list || []
324 -
325 - // 格式化周期显示
326 const cycleMap = { 308 const cycleMap = {
327 '0': '本周期', 309 '0': '本周期',
328 '30': '每月', 310 '30': '每月',
329 '7': '每周', 311 '7': '每周',
330 - '1': '每日' // 假设 1 代表每日 312 + '1': '每日'
331 } 313 }
332 - // 如果后端返回的是数字字符串,尝试映射,否则直接显示 314 + const cycleText = cycleMap[data.cycle] || data.cycle || '每日'
333 - const cycleText = cycleMap[res.data.cycle] || res.data.cycle || '每日'
334 315
335 - // 格式化附件类型
336 let attachmentText = '文本' 316 let attachmentText = '文本'
337 - if (Array.isArray(res.data.attachment_type)) { 317 + if (Array.isArray(data.attachment_type)) {
338 const typeMap = { 318 const typeMap = {
339 'text': '文本', 319 'text': '文本',
340 'image': '图片', 320 'image': '图片',
341 'video': '视频', 321 'video': '视频',
342 'audio': '音频' 322 'audio': '音频'
343 } 323 }
344 - attachmentText = res.data.attachment_type.map(t => typeMap[t] || t).join('/') 324 + attachmentText = data.attachment_type.map(t => typeMap[t] || t).join('/')
345 - } else if (res.data.attachment_type) { 325 + } else if (data.attachment_type) {
346 - attachmentText = res.data.attachment_type 326 + attachmentText = data.attachment_type
347 } 327 }
348 328
349 - task_details.value = { 329 + return {
350 cycle: cycleText, 330 cycle: cycleText,
351 - frequency: `每周期${res.data.frequency || 1}次`, 331 + frequency: `每周期${data.frequency || 1}次`,
352 - begin_date: res.data.begin_date ? dayjs(res.data.begin_date).format('YYYY-MM-DD') : '', 332 + begin_date: data.begin_date ? dayjs(data.begin_date).format('YYYY-MM-DD') : '',
353 - end_date: res.data.end_date ? dayjs(res.data.end_date).format('YYYY-MM-DD') : '', 333 + end_date: data.end_date ? dayjs(data.end_date).format('YYYY-MM-DD') : '',
354 - // time_range: `${res.data.begin_date || '00:00'} ~ ${res.data.end_date || '23:59'}`, // 注意:API返回的begin_date/end_date可能是日期也可能是时间,这里暂且直接展示
355 attachment_type: attachmentText 334 attachment_type: attachmentText
356 } 335 }
336 +}
337 +
338 +const display_details = computed(() => {
339 + if (selectedSubtaskId.value && subtask_list.value.length) {
340 + const sub = subtask_list.value.find(item => item.id === selectedSubtaskId.value)
341 + if (sub) {
342 + return formatTaskDetails(sub)
343 + }
344 + }
345 + return task_details.value
346 +})
347 +
348 +/**
349 + * 获取作业详情和学生完成情况
350 + */
351 +const fetchData = async () => {
352 + try {
353 + const res = await getTeacherTaskDetailAPI({
354 + id: task_id,
355 + subtask_id: selectedSubtaskId.value,
356 + date: selected_date.value
357 + })
358 +
359 + if (res.code) {
360 + task_title.value = res.data.title
361 + task_intro.value = res.data.note
362 + task_type.value = res.data.task_type || ''
363 + // 小作业列表
364 + subtask_list.value = res.data.subtask_list || []
365 +
366 + task_details.value = formatTaskDetails(res.data)
357 367
358 user_list.value = res.data.user_list || [] 368 user_list.value = res.data.user_list || []
359 369
......