Files
EmailBill/Web/src/components/DateSelectHeader.vue
SunCheng 3e18283e52 1
2026-02-09 19:25:51 +08:00

184 lines
3.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<header class="calendar-header">
<!-- 左箭头 -->
<button
class="nav-btn"
aria-label="上一个周期"
@click="emit('prev')"
>
<van-icon name="arrow-left" />
</button>
<!-- 标题内容可点击跳转 -->
<div
class="header-content"
@click="emit('jump')"
>
<h1 class="header-title">
{{ formattedTitle }}
</h1>
</div>
<!-- 右箭头 -->
<button
class="nav-btn"
aria-label="下一个周期"
@click="emit('next')"
>
<van-icon name="arrow" />
</button>
<!-- 通知按钮 -->
<button
v-if="showNotification"
class="notif-btn"
aria-label="通知"
@click="emit('notification')"
>
<van-icon name="bell" />
</button>
</header>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
type: {
type: String,
required: true,
validator: (value) => ['week', 'month', 'year'].includes(value)
},
currentDate: {
type: Date,
required: true
},
showNotification: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['prev', 'next', 'jump', 'notification'])
/**
* 计算 ISO 8601 标准的周数
* @param date 目标日期
* @returns 周数 (1-53)
*/
const getISOWeek = (date) => {
const target = new Date(date.valueOf())
const dayNr = (date.getDay() + 6) % 7 // 周一为0周日为6
target.setDate(target.getDate() - dayNr + 3) // 本周四
const firstThursday = new Date(target.getFullYear(), 0, 4) // 该年第一个周四
const weekDiff = Math.floor((target.valueOf() - firstThursday.valueOf()) / 86400000)
return 1 + Math.floor(weekDiff / 7)
}
/**
* 计算 ISO 8601 标准的年份(用于周数)
* 注意:年末/年初的周可能属于相邻年份
*/
const getISOYear = (date) => {
const target = new Date(date.valueOf())
const dayNr = (date.getDay() + 6) % 7
target.setDate(target.getDate() - dayNr + 3) // 本周四
return target.getFullYear()
}
// 格式化标题
const formattedTitle = computed(() => {
const date = props.currentDate
const year = date.getFullYear()
const month = date.getMonth() + 1
switch (props.type) {
case 'week': {
const isoYear = getISOYear(date)
const weekNum = getISOWeek(date)
return `${isoYear}年第${weekNum}`
}
case 'month':
return `${year}${month}`
case 'year':
return `${year}`
default:
return ''
}
})
</script>
<style scoped lang="scss">
@import '@/assets/theme.css';
/* ========== 头部 ========== */
.calendar-header {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 8px 24px;
gap: 8px;
background: transparent !important;
position: relative;
z-index: 1;
}
.header-content {
display: flex;
flex-direction: column;
gap: 4px;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.header-title {
font-family: var(--font-primary);
font-size: var(--font-2xl);
font-weight: var(--font-medium);
color: var(--text-primary);
margin: 0;
transition: opacity 0.2s;
}
.header-content:active .header-title {
opacity: 0.6;
}
.notif-btn {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border-radius: var(--radius-full);
background-color: var(--bg-button);
border: none;
cursor: pointer;
transition: opacity 0.2s;
margin-left: auto;
}
.notif-btn:active {
opacity: 0.7;
}
.nav-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 18px;
background-color: transparent;
border: none;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s;
}
.nav-btn:active {
background-color: var(--bg-tertiary);
}
</style>