Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 4m47s
Docker Build & Deploy / Deploy to Production (push) Has been skipped
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
- TransactionDetail, CategoryBillPopup 移入 Transaction/ - BudgetTypeTabs 移入 Budget/ - GlassBottomNav, ModernEmpty 移入 Global/ - Icon, IconSelector, ClassifySelector 等 8 个通用组件移入 Common/ - 更新所有相关引用路径
185 lines
3.9 KiB
Vue
185 lines
3.9 KiB
Vue
<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;
|
||
min-height: 60px; /* 与 balance-header 保持一致,防止切换抖动 */
|
||
}
|
||
|
||
.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>
|