hookehuyr

feat(日历组件): 增加日期选择快捷方式和重置功能

添加今日/昨日快捷选择功能,并支持特定日期选择弹窗
为 TaskCalendar 组件添加 noDefaultSelect 属性和 reset_selection 方法
优化日期解析逻辑和面板年月初始化方式
...@@ -57,19 +57,45 @@ import { ref, computed, watch } from 'vue' ...@@ -57,19 +57,45 @@ import { ref, computed, watch } from 'vue'
57 * 组件对外暴露的 v-model 值:选中日期(YYYY-MM-DD) 57 * 组件对外暴露的 v-model 值:选中日期(YYYY-MM-DD)
58 */ 58 */
59 const props = defineProps({ 59 const props = defineProps({
60 - modelValue: { type: String, default: '' } 60 + modelValue: { type: String, default: '' },
61 + // 是否不在初始化时默认选中“今日”,用于弹窗场景
62 + noDefaultSelect: { type: Boolean, default: false }
61 }) 63 })
62 const emit = defineEmits(['update:modelValue', 'select']) 64 const emit = defineEmits(['update:modelValue', 'select'])
63 65
64 // 66 //
65 // 状态:当前面板年月与选中日期 67 // 状态:当前面板年月与选中日期
66 // 68 //
67 -const selected_date = ref(props.modelValue || format_date(new Date())) 69 +/**
68 -const panel_year = ref(Number(selected_date.value.slice(0, 4))) 70 + * 解析 YYYY-MM-DD 字符串为日期对象
69 -const panel_month = ref(Number(selected_date.value.slice(5, 7))) 71 + * @param {string} str - 日期字符串
72 + * @returns {Date} 日期对象
73 + */
74 +function parse_date_str(str) {
75 + if (!str) return new Date()
76 + const parts = String(str).split('-')
77 + const y = Number(parts[0]) || new Date().getFullYear()
78 + const m = Number(parts[1]) || (new Date().getMonth() + 1)
79 + const d = Number(parts[2]) || 1
80 + return new Date(y, m - 1, d)
81 +}
82 +
83 +// 选中日期:根据 noDefaultSelect 决定是否默认高亮“今日”
84 +const selected_date = ref(
85 + props.noDefaultSelect ? (props.modelValue || '') : (props.modelValue || format_date(new Date()))
86 +)
87 +// 面板年月:优先使用传入值,否则使用当前日期
88 +const base_d = parse_date_str(props.modelValue)
89 +const panel_year = ref(base_d.getFullYear())
90 +const panel_month = ref(base_d.getMonth() + 1)
70 91
71 watch(() => props.modelValue, (val) => { 92 watch(() => props.modelValue, (val) => {
72 - if (val) selected_date.value = val 93 + if (val) {
94 + selected_date.value = val
95 + const d = parse_date_str(val)
96 + panel_year.value = d.getFullYear()
97 + panel_month.value = d.getMonth() + 1
98 + }
73 }) 99 })
74 100
75 /** 101 /**
...@@ -201,6 +227,19 @@ function on_confirm_year_month() { ...@@ -201,6 +227,19 @@ function on_confirm_year_month() {
201 panel_month.value = Number(m) 227 panel_month.value = Number(m)
202 show_date_picker.value = false 228 show_date_picker.value = false
203 } 229 }
230 +
231 +/**
232 + * 重置选中状态:清空选中日期,用于父组件“今日/昨日”切换后取消高亮
233 + * @returns {void}
234 + */
235 +function reset_selection() {
236 + selected_date.value = ''
237 +}
238 +
239 +// 暴露方法给父组件
240 +defineExpose({
241 + reset_selection
242 +})
204 </script> 243 </script>
205 244
206 <style lang="less" scoped> 245 <style lang="less" scoped>
......
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-11-20 11:31:17 4 + * @LastEditTime: 2025-11-20 21:30:48
5 * @FilePath: /mlaj/src/views/teacher/taskHomePage.vue 5 * @FilePath: /mlaj/src/views/teacher/taskHomePage.vue
6 * @Description: 教师端作业主页(头部介绍、统计、日历与学生完成情况;数据Mock) 6 * @Description: 教师端作业主页(头部介绍、统计、日历与学生完成情况;数据Mock)
7 --> 7 -->
...@@ -52,8 +52,33 @@ ...@@ -52,8 +52,33 @@
52 52
53 <!-- 日历:选择日期后展示该日期的学生完成情况 --> 53 <!-- 日历:选择日期后展示该日期的学生完成情况 -->
54 <div class="calendarCard bg-white rounded-lg shadow px-4 py-4 mt-4"> 54 <div class="calendarCard bg-white rounded-lg shadow px-4 py-4 mt-4">
55 - <div class="text-base font-semibold text-gray-800 mb-2">选择日期查看完成情况</div> 55 + <div class="text-base font-semibold text-gray-800 mb-2">选择日期</div>
56 - <TaskCalendar v-model="selected_date" @select="on_date_select" /> 56 + <!-- 快捷方式:今日 / 昨日 / 某个日期(弹出日历) -->
57 + <div class="QuickDateRow flex items-center gap-3">
58 + <div class="quickChip inline-flex items-center rounded-full px-3 py-1 text-sm font-medium cursor-pointer"
59 + :class="selected_mode === 'today' ? 'bg-green-500 text-white border border-green-500 hover:bg-green-600' : 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'"
60 + @click="select_today">
61 + <!-- <van-icon v-if="selected_mode === 'today'" name="success" size="14" class="mr-1" /> -->
62 + 今日
63 + </div>
64 + <div class="quickChip inline-flex items-center rounded-full px-3 py-1 text-sm font-medium cursor-pointer"
65 + :class="selected_mode === 'yesterday' ? 'bg-green-500 text-white border border-green-500 hover:bg-green-600' : 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'"
66 + @click="select_yesterday">
67 + <!-- <van-icon v-if="selected_mode === 'yesterday'" name="success" size="14" class="mr-1" /> -->
68 + 昨日
69 + </div>
70 + <div class="quickChip inline-flex items-center rounded-full px-3 py-1 text-sm font-medium cursor-pointer"
71 + :class="selected_mode === 'specific' ? 'bg-green-500 text-white border border-green-500 hover:bg-green-600' : 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'"
72 + @click="open_specific_date_picker">
73 + <!-- <van-icon v-if="selected_mode === 'specific'" name="success" size="14" class="mr-1" /> -->
74 + {{ specific_label }}
75 + </div>
76 + </div>
77 + <!-- 选中日期展示 -->
78 + <!-- <div class="mt-3 text-sm text-gray-600">当前选择:{{ current_date_text }}</div> -->
79 + <!-- 日历弹窗:点击“某个日期”弹出 -->
80 + <van-calendar v-model:show="show_calendar_popup" :default-date="calendar_default_date" color="#10b981"
81 + :show-confirm="true" switch-mode="month" @confirm="on_date_select" />
57 </div> 82 </div>
58 83
59 <!-- 学生完成情况(参考图片2样式) --> 84 <!-- 学生完成情况(参考图片2样式) -->
...@@ -61,7 +86,7 @@ ...@@ -61,7 +86,7 @@
61 <div class="flex items-center justify-between mb-3"> 86 <div class="flex items-center justify-between mb-3">
62 <div class="text-base font-semibold text-gray-800">完成情况({{ completed_count }}/{{ students.length }}) 87 <div class="text-base font-semibold text-gray-800">完成情况({{ completed_count }}/{{ students.length }})
63 </div> 88 </div>
64 - <div class="text-xs text-gray-500">当前日期:{{ current_date_text }}</div> 89 + <!-- <div class="text-xs text-gray-500">当前日期:{{ current_date_text }}</div> -->
65 </div> 90 </div>
66 <div class="grid grid-cols-5 gap-3 StudentsGrid"> 91 <div class="grid grid-cols-5 gap-3 StudentsGrid">
67 <div v-for="(stu, idx) in students_status" :key="stu.id" 92 <div v-for="(stu, idx) in students_status" :key="stu.id"
...@@ -81,13 +106,55 @@ ...@@ -81,13 +106,55 @@
81 import { ref, computed } from 'vue' 106 import { ref, computed } from 'vue'
82 import { useRoute, useRouter } from 'vue-router' 107 import { useRoute, useRouter } from 'vue-router'
83 import { useTitle } from '@vueuse/core' 108 import { useTitle } from '@vueuse/core'
84 -import TaskCalendar from '@/components/ui/TaskCalendar.vue'
85 import checkCorner from '@/assets/images/dui.png' 109 import checkCorner from '@/assets/images/dui.png'
86 110
87 const $route = useRoute() 111 const $route = useRoute()
88 const $router = useRouter() 112 const $router = useRouter()
89 useTitle('作业主页') 113 useTitle('作业主页')
90 114
115 +// 弹窗显示状态:是否展示“某个日期”选择日历
116 +const show_calendar_popup = ref(false)
117 +// Calendar 默认选中日期(为 null 时不预选)
118 +const calendar_default_date = ref(null)
119 +// 快捷项当前选中模式:today | yesterday | specific
120 +const selected_mode = ref('today')
121 +// 特定日期按钮的文字展示,选择后替换为实际日期
122 +const specific_label = ref('特定日期')
123 +
124 +/**
125 + * 选择“今日”,设置为今日日期,并触发查询逻辑
126 + * @returns {void}
127 + */
128 +function select_today() {
129 + const now = new Date()
130 + selected_date.value = format_date(now)
131 + selected_mode.value = 'today'
132 + specific_label.value = '特定日期'
133 + // 清空弹窗内 Calendar 的默认选中状态
134 + calendar_default_date.value = null
135 +}
136 +
137 +/**
138 + * 选择“昨日”,设置为昨日日期,并触发查询逻辑
139 + * @returns {void}
140 + */
141 +function select_yesterday() {
142 + const y = new Date(Date.now() - 24 * 60 * 60 * 1000)
143 + selected_date.value = format_date(y)
144 + selected_mode.value = 'yesterday'
145 + specific_label.value = '特定日期'
146 + // 清空弹窗内 Calendar 的默认选中状态
147 + calendar_default_date.value = null
148 +}
149 +
150 +/**
151 + * 打开“某个日期”选择器弹窗
152 + * @returns {void}
153 + */
154 +function open_specific_date_picker() {
155 + show_calendar_popup.value = true
156 +}
157 +
91 // 158 //
92 // Mock:作业基础信息 159 // Mock:作业基础信息
93 // 160 //
...@@ -166,9 +233,33 @@ function format_date(d) { ...@@ -166,9 +233,33 @@ function format_date(d) {
166 * @returns {void} 233 * @returns {void}
167 * 注释:更新选中日期,并联动学生完成情况。 234 * 注释:更新选中日期,并联动学生完成情况。
168 */ 235 */
236 +/**
237 + * 字符串转日期对象(YYYY-MM-DD -> Date)
238 + * @param {string} s - 日期字符串
239 + * @returns {Date} 日期对象
240 + */
241 +function parse_date_text(s) {
242 + const parts = String(s).split('-')
243 + const y = Number(parts[0]) || new Date().getFullYear()
244 + const m = Number(parts[1]) || (new Date().getMonth() + 1)
245 + const d = Number(parts[2]) || new Date().getDate()
246 + return new Date(y, m - 1, d)
247 +}
248 +
249 +/**
250 + * 处理日历选择事件(兼容字符串与Date对象)
251 + * @param {string|Date} val - 选中的日期
252 + * @returns {void}
253 + */
169 function on_date_select(val) { 254 function on_date_select(val) {
170 - // 自定义日历组件返回 YYYY-MM-DD 字符串 255 + const is_date_obj = val instanceof Date
171 - selected_date.value = val 256 + const text = is_date_obj ? format_date(val) : String(val)
257 + selected_date.value = text
258 + show_calendar_popup.value = false
259 + selected_mode.value = 'specific'
260 + specific_label.value = text
261 + // 将所选日期作为下次弹窗的默认选中
262 + calendar_default_date.value = is_date_obj ? val : parse_date_text(text)
172 } 263 }
173 264
174 /** 265 /**
...@@ -226,6 +317,7 @@ function go_student_record(stu) { ...@@ -226,6 +317,7 @@ function go_student_record(stu) {
226 .details { 317 .details {
227 border-left: 0.15rem solid #10b981; 318 border-left: 0.15rem solid #10b981;
228 padding-left: 0.75rem; 319 padding-left: 0.75rem;
320 +
229 .detailItem { 321 .detailItem {
230 margin-bottom: 0.25rem; 322 margin-bottom: 0.25rem;
231 } 323 }
...@@ -233,20 +325,24 @@ function go_student_record(stu) { ...@@ -233,20 +325,24 @@ function go_student_record(stu) {
233 } 325 }
234 326
235 .studentsCard { 327 .studentsCard {
328 +
236 // 兜底:强制学生列表为5列栅格,避免一行仅1个的问题 329 // 兜底:强制学生列表为5列栅格,避免一行仅1个的问题
237 .StudentsGrid { 330 .StudentsGrid {
238 display: grid; 331 display: grid;
239 grid-template-columns: repeat(5, 1fr); 332 grid-template-columns: repeat(5, 1fr);
240 gap: 0.75rem; // 等同于 tailwind 的 gap-3 333 gap: 0.75rem; // 等同于 tailwind 的 gap-3
241 } 334 }
335 +
242 .grid>div { 336 .grid>div {
243 transition: all 0.2s ease-in-out; 337 transition: all 0.2s ease-in-out;
244 } 338 }
339 +
245 .studentItem { 340 .studentItem {
246 min-height: 4rem; 341 min-height: 4rem;
247 // 卡片圆角裁切,配合右上角图片效果 342 // 卡片圆角裁切,配合右上角图片效果
248 overflow: hidden; 343 overflow: hidden;
249 } 344 }
345 +
250 .cornerIcon { 346 .cornerIcon {
251 // 右上角图片样式(对勾角标),贴边显示 347 // 右上角图片样式(对勾角标),贴边显示
252 position: absolute; 348 position: absolute;
...@@ -256,5 +352,19 @@ function go_student_record(stu) { ...@@ -256,5 +352,19 @@ function go_student_record(stu) {
256 height: 18px; 352 height: 18px;
257 } 353 }
258 } 354 }
355 +
356 + .calendarCard {
357 + .QuickDateRow {
358 + margin-top: 0.25rem;
359 +
360 + .quickChip {
361 + transition: all 0.2s ease-in-out;
362 +
363 + &:active {
364 + opacity: 0.85;
365 + }
366 + }
367 + }
368 + }
259 } 369 }
260 </style> 370 </style>
......