hookehuyr

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

添加今日/昨日快捷选择功能,并支持特定日期选择弹窗
为 TaskCalendar 组件添加 noDefaultSelect 属性和 reset_selection 方法
优化日期解析逻辑和面板年月初始化方式
......@@ -57,19 +57,45 @@ import { ref, computed, watch } from 'vue'
* 组件对外暴露的 v-model 值:选中日期(YYYY-MM-DD)
*/
const props = defineProps({
modelValue: { type: String, default: '' }
modelValue: { type: String, default: '' },
// 是否不在初始化时默认选中“今日”,用于弹窗场景
noDefaultSelect: { type: Boolean, default: false }
})
const emit = defineEmits(['update:modelValue', 'select'])
//
// 状态:当前面板年月与选中日期
//
const selected_date = ref(props.modelValue || format_date(new Date()))
const panel_year = ref(Number(selected_date.value.slice(0, 4)))
const panel_month = ref(Number(selected_date.value.slice(5, 7)))
/**
* 解析 YYYY-MM-DD 字符串为日期对象
* @param {string} str - 日期字符串
* @returns {Date} 日期对象
*/
function parse_date_str(str) {
if (!str) return new Date()
const parts = String(str).split('-')
const y = Number(parts[0]) || new Date().getFullYear()
const m = Number(parts[1]) || (new Date().getMonth() + 1)
const d = Number(parts[2]) || 1
return new Date(y, m - 1, d)
}
// 选中日期:根据 noDefaultSelect 决定是否默认高亮“今日”
const selected_date = ref(
props.noDefaultSelect ? (props.modelValue || '') : (props.modelValue || format_date(new Date()))
)
// 面板年月:优先使用传入值,否则使用当前日期
const base_d = parse_date_str(props.modelValue)
const panel_year = ref(base_d.getFullYear())
const panel_month = ref(base_d.getMonth() + 1)
watch(() => props.modelValue, (val) => {
if (val) selected_date.value = val
if (val) {
selected_date.value = val
const d = parse_date_str(val)
panel_year.value = d.getFullYear()
panel_month.value = d.getMonth() + 1
}
})
/**
......@@ -201,6 +227,19 @@ function on_confirm_year_month() {
panel_month.value = Number(m)
show_date_picker.value = false
}
/**
* 重置选中状态:清空选中日期,用于父组件“今日/昨日”切换后取消高亮
* @returns {void}
*/
function reset_selection() {
selected_date.value = ''
}
// 暴露方法给父组件
defineExpose({
reset_selection
})
</script>
<style lang="less" scoped>
......
<!--
* @Date: 2025-11-19 21:00:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-11-20 11:31:17
* @LastEditTime: 2025-11-20 21:30:48
* @FilePath: /mlaj/src/views/teacher/taskHomePage.vue
* @Description: 教师端作业主页(头部介绍、统计、日历与学生完成情况;数据Mock)
-->
......@@ -52,8 +52,33 @@
<!-- 日历:选择日期后展示该日期的学生完成情况 -->
<div class="calendarCard bg-white rounded-lg shadow px-4 py-4 mt-4">
<div class="text-base font-semibold text-gray-800 mb-2">选择日期查看完成情况</div>
<TaskCalendar v-model="selected_date" @select="on_date_select" />
<div class="text-base font-semibold text-gray-800 mb-2">选择日期</div>
<!-- 快捷方式:今日 / 昨日 / 某个日期(弹出日历) -->
<div class="QuickDateRow flex items-center gap-3">
<div class="quickChip inline-flex items-center rounded-full px-3 py-1 text-sm font-medium cursor-pointer"
: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'"
@click="select_today">
<!-- <van-icon v-if="selected_mode === 'today'" name="success" size="14" class="mr-1" /> -->
今日
</div>
<div class="quickChip inline-flex items-center rounded-full px-3 py-1 text-sm font-medium cursor-pointer"
: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'"
@click="select_yesterday">
<!-- <van-icon v-if="selected_mode === 'yesterday'" name="success" size="14" class="mr-1" /> -->
昨日
</div>
<div class="quickChip inline-flex items-center rounded-full px-3 py-1 text-sm font-medium cursor-pointer"
: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'"
@click="open_specific_date_picker">
<!-- <van-icon v-if="selected_mode === 'specific'" name="success" size="14" class="mr-1" /> -->
{{ specific_label }}
</div>
</div>
<!-- 选中日期展示 -->
<!-- <div class="mt-3 text-sm text-gray-600">当前选择:{{ current_date_text }}</div> -->
<!-- 日历弹窗:点击“某个日期”弹出 -->
<van-calendar v-model:show="show_calendar_popup" :default-date="calendar_default_date" color="#10b981"
:show-confirm="true" switch-mode="month" @confirm="on_date_select" />
</div>
<!-- 学生完成情况(参考图片2样式) -->
......@@ -61,7 +86,7 @@
<div class="flex items-center justify-between mb-3">
<div class="text-base font-semibold text-gray-800">完成情况({{ completed_count }}/{{ students.length }})
</div>
<div class="text-xs text-gray-500">当前日期:{{ current_date_text }}</div>
<!-- <div class="text-xs text-gray-500">当前日期:{{ current_date_text }}</div> -->
</div>
<div class="grid grid-cols-5 gap-3 StudentsGrid">
<div v-for="(stu, idx) in students_status" :key="stu.id"
......@@ -81,13 +106,55 @@
import { ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTitle } from '@vueuse/core'
import TaskCalendar from '@/components/ui/TaskCalendar.vue'
import checkCorner from '@/assets/images/dui.png'
const $route = useRoute()
const $router = useRouter()
useTitle('作业主页')
// 弹窗显示状态:是否展示“某个日期”选择日历
const show_calendar_popup = ref(false)
// Calendar 默认选中日期(为 null 时不预选)
const calendar_default_date = ref(null)
// 快捷项当前选中模式:today | yesterday | specific
const selected_mode = ref('today')
// 特定日期按钮的文字展示,选择后替换为实际日期
const specific_label = ref('特定日期')
/**
* 选择“今日”,设置为今日日期,并触发查询逻辑
* @returns {void}
*/
function select_today() {
const now = new Date()
selected_date.value = format_date(now)
selected_mode.value = 'today'
specific_label.value = '特定日期'
// 清空弹窗内 Calendar 的默认选中状态
calendar_default_date.value = null
}
/**
* 选择“昨日”,设置为昨日日期,并触发查询逻辑
* @returns {void}
*/
function select_yesterday() {
const y = new Date(Date.now() - 24 * 60 * 60 * 1000)
selected_date.value = format_date(y)
selected_mode.value = 'yesterday'
specific_label.value = '特定日期'
// 清空弹窗内 Calendar 的默认选中状态
calendar_default_date.value = null
}
/**
* 打开“某个日期”选择器弹窗
* @returns {void}
*/
function open_specific_date_picker() {
show_calendar_popup.value = true
}
//
// Mock:作业基础信息
//
......@@ -166,9 +233,33 @@ function format_date(d) {
* @returns {void}
* 注释:更新选中日期,并联动学生完成情况。
*/
/**
* 字符串转日期对象(YYYY-MM-DD -> Date)
* @param {string} s - 日期字符串
* @returns {Date} 日期对象
*/
function parse_date_text(s) {
const parts = String(s).split('-')
const y = Number(parts[0]) || new Date().getFullYear()
const m = Number(parts[1]) || (new Date().getMonth() + 1)
const d = Number(parts[2]) || new Date().getDate()
return new Date(y, m - 1, d)
}
/**
* 处理日历选择事件(兼容字符串与Date对象)
* @param {string|Date} val - 选中的日期
* @returns {void}
*/
function on_date_select(val) {
// 自定义日历组件返回 YYYY-MM-DD 字符串
selected_date.value = val
const is_date_obj = val instanceof Date
const text = is_date_obj ? format_date(val) : String(val)
selected_date.value = text
show_calendar_popup.value = false
selected_mode.value = 'specific'
specific_label.value = text
// 将所选日期作为下次弹窗的默认选中
calendar_default_date.value = is_date_obj ? val : parse_date_text(text)
}
/**
......@@ -226,6 +317,7 @@ function go_student_record(stu) {
.details {
border-left: 0.15rem solid #10b981;
padding-left: 0.75rem;
.detailItem {
margin-bottom: 0.25rem;
}
......@@ -233,20 +325,24 @@ function go_student_record(stu) {
}
.studentsCard {
// 兜底:强制学生列表为5列栅格,避免一行仅1个的问题
.StudentsGrid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 0.75rem; // 等同于 tailwind 的 gap-3
}
.grid>div {
transition: all 0.2s ease-in-out;
}
.studentItem {
min-height: 4rem;
// 卡片圆角裁切,配合右上角图片效果
overflow: hidden;
}
.cornerIcon {
// 右上角图片样式(对勾角标),贴边显示
position: absolute;
......@@ -256,5 +352,19 @@ function go_student_record(stu) {
height: 18px;
}
}
.calendarCard {
.QuickDateRow {
margin-top: 0.25rem;
.quickChip {
transition: all 0.2s ease-in-out;
&:active {
opacity: 0.85;
}
}
}
}
}
</style>
......