609 lines
14 KiB
Vue
609 lines
14 KiB
Vue
<template>
|
||
<view class="calendar-screen">
|
||
<view class="calendar-texture"></view>
|
||
|
||
<!-- 固定头部 -->
|
||
<view class="calendar-fixed-header">
|
||
<!-- 状态栏占位 -->
|
||
<view class="status-bar-placeholder"></view>
|
||
|
||
<!-- Header -->
|
||
<view class="calendar-header">
|
||
<view class="calendar-back" @click="$emit('back')">‹</view>
|
||
<view class="calendar-title">
|
||
<text class="calendar-year">{{ year }}年 {{ lunarMonths[month] }}</text>
|
||
<text class="calendar-subtitle">Year of the Dragon</text>
|
||
</view>
|
||
<view class="calendar-header-spacer"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 头部占位 -->
|
||
<view class="calendar-header-placeholder"></view>
|
||
|
||
<!-- Content -->
|
||
<scroll-view scroll-y class="calendar-scroll">
|
||
<!-- Controls -->
|
||
<view class="calendar-controls">
|
||
<button class="calendar-nav-button" @click="prevMonth">‹</button>
|
||
<text class="calendar-month">
|
||
{{ month + 1 }} <text class="calendar-month-unit">月</text>
|
||
</text>
|
||
<button class="calendar-nav-button" @click="nextMonth">›</button>
|
||
</view>
|
||
|
||
<!-- Weekdays -->
|
||
<view class="calendar-weekdays">
|
||
<text v-for="(d, i) in weekdays" :key="i" class="calendar-weekday"
|
||
:class="{ 'calendar-weekday-weekend': i === 0 || i === 6 }">
|
||
{{ d }}
|
||
</text>
|
||
</view>
|
||
|
||
<!-- Calendar Grid -->
|
||
<view class="calendar-grid">
|
||
<view v-for="cell in calendarCells" :key="cell.key" class="calendar-cell"
|
||
:class="cell.day ? ['calendar-cell-filled', cellSelected(cell.day) ? 'is-selected' : '', cellToday(cell.day) ? 'is-today' : ''] : 'calendar-cell-empty'"
|
||
@click="cell.day && selectDay(cell.day)">
|
||
<template v-if="cell.day">
|
||
<text class="calendar-cell-day"
|
||
:class="{ 'is-selected': cellSelected(cell.day), 'is-today': cellToday(cell.day) }">
|
||
{{ cell.day }}
|
||
</text>
|
||
<text class="calendar-cell-lunar" :class="{ 'is-selected': cellSelected(cell.day) }">
|
||
{{ lunarDay(cell.day) }}
|
||
</text>
|
||
<view v-if="cellToday(cell.day) && !cellSelected(cell.day)" class="calendar-today-dot"></view>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Detail Card -->
|
||
<view class="calendar-detail-wrapper">
|
||
<view class="calendar-detail">
|
||
<view class="calendar-detail-corner"></view>
|
||
<view class="calendar-detail-header">
|
||
<view class="calendar-detail-date">
|
||
<text class="calendar-detail-day">{{ selected.getDate() }}</text>
|
||
<view class="calendar-detail-meta">
|
||
<text class="calendar-detail-lunar">{{ lunarDay(selected.getDate()) }}</text>
|
||
<text class="calendar-detail-weekday">{{ weekdayText(selected.getDay()) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="calendar-detail-badge">今日运势</view>
|
||
</view>
|
||
|
||
<view class="calendar-detail-body">
|
||
<view class="calendar-detail-row">
|
||
<view class="calendar-detail-icon calendar-detail-icon-yi">宜</view>
|
||
<view class="calendar-detail-tags">
|
||
<text v-for="(item, i) in yiJi.yi" :key="i" class="calendar-detail-tag">{{ item }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="calendar-detail-row">
|
||
<view class="calendar-detail-icon calendar-detail-icon-ji">忌</view>
|
||
<view class="calendar-detail-tags">
|
||
<text v-for="(item, i) in yiJi.ji" :key="i" class="calendar-detail-tag calendar-detail-tag-ji">{{ item
|
||
}}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- CTA -->
|
||
<view class="calendar-cta">
|
||
<button class="calendar-cta-card" @click="$emit('auspicious')">
|
||
<view class="calendar-cta-glow"></view>
|
||
<view class="calendar-cta-content">
|
||
<view>
|
||
<text class="calendar-cta-title">精准八字择吉</text>
|
||
<text class="calendar-cta-subtitle">结婚 · 开业 · 乔迁 · 动土</text>
|
||
</view>
|
||
<view class="calendar-cta-arrow">›</view>
|
||
</view>
|
||
<view class="calendar-cta-tags">
|
||
<text class="calendar-cta-tag">个人定制</text>
|
||
<text class="calendar-cta-tag">避讳冲煞</text>
|
||
</view>
|
||
</button>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref } from "vue";
|
||
|
||
const props = defineProps<{
|
||
onBack?: () => void;
|
||
onNavigateToAuspicious?: () => void;
|
||
}>();
|
||
|
||
const weekdays = ["日", "一", "二", "三", "四", "五", "六"];
|
||
const lunarMonths = ["正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "腊月"];
|
||
const lunarDays = [
|
||
"初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
|
||
"十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十",
|
||
"廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"
|
||
];
|
||
const yiJiData = [
|
||
{ yi: ["出行", "开市", "交易", "裁衣", "安床"], ji: ["动土", "安葬", "破土", "作灶", "入宅"] },
|
||
{ yi: ["嫁娶", "订盟", "纳采", "祭祀", "祈福"], ji: ["开仓", "出货", "盖屋", "造桥", "破土"] },
|
||
{ yi: ["解除", "扫舍", "整手足甲", "沐浴"], ji: ["安门", "分居", "修造", "动土"] },
|
||
{ yi: ["塑绘", "开光", "进人口", "纳畜"], ji: ["嫁娶", "安葬", "行丧", "伐木"] },
|
||
{ yi: ["祭祀", "会亲友", "纳财", "捕捉"], ji: ["嫁娶", "开市", "安床", "探病"] }
|
||
];
|
||
|
||
const current = ref(new Date());
|
||
const selected = ref(new Date());
|
||
|
||
const year = computed(() => current.value.getFullYear());
|
||
const month = computed(() => current.value.getMonth());
|
||
|
||
const daysInMonth = computed(() => new Date(year.value, month.value + 1, 0).getDate());
|
||
const firstDayOfMonth = computed(() => new Date(year.value, month.value, 1).getDay());
|
||
|
||
const calendarCells = computed(() => {
|
||
const cells: { key: string; day?: number }[] = [];
|
||
for (let i = 0; i < firstDayOfMonth.value; i++) {
|
||
cells.push({ key: `empty-${i}` });
|
||
}
|
||
for (let d = 1; d <= daysInMonth.value; d++) {
|
||
cells.push({ key: `d-${d}`, day: d });
|
||
}
|
||
return cells;
|
||
});
|
||
|
||
const lunarDay = (d: number) => lunarDays[(d - 1) % 30];
|
||
const getYiJi = (d: number) => yiJiData[d % yiJiData.length];
|
||
const yiJi = computed(() => getYiJi(selected.value.getDate()));
|
||
const weekdayText = (d: number) => ["周日", "周一", "周二", "周三", "周四", "周五", "周六"][d];
|
||
|
||
const selectDay = (d: number) => {
|
||
selected.value = new Date(year.value, month.value, d);
|
||
};
|
||
const prevMonth = () => {
|
||
current.value = new Date(year.value, month.value - 1, 1);
|
||
if (month.value === selected.value.getMonth()) {
|
||
selected.value = new Date(year.value, month.value, 1);
|
||
}
|
||
};
|
||
const nextMonth = () => {
|
||
current.value = new Date(year.value, month.value + 1, 1);
|
||
if (month.value === selected.value.getMonth()) {
|
||
selected.value = new Date(year.value, month.value + 1, 1);
|
||
}
|
||
};
|
||
|
||
const cellSelected = (d: number) => selected.value.getDate() === d && selected.value.getMonth() === month.value;
|
||
const cellToday = (d: number) => {
|
||
const today = new Date();
|
||
return today.getFullYear() === year.value && today.getMonth() === month.value && today.getDate() === d;
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.calendar-screen {
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #fdfbf7;
|
||
font-family: SimSun, "Songti SC", "Songti TC", "Noto Serif SC", STSong, serif;
|
||
position: relative;
|
||
overflow: hidden;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 固定头部容器 */
|
||
.calendar-fixed-header {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 100;
|
||
background: #fdfbf7;
|
||
}
|
||
|
||
/* 状态栏占位 */
|
||
.status-bar-placeholder {
|
||
height: var(--status-bar-height, 0);
|
||
width: 100%;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* 头部占位,防止内容被固定头部遮挡 */
|
||
.calendar-header-placeholder {
|
||
height: calc(var(--status-bar-height, 0) + 120rpx);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.calendar-texture {
|
||
position: absolute;
|
||
inset: 0;
|
||
opacity: 0.1;
|
||
pointer-events: none;
|
||
background-image: url("https://www.transparenttextures.com/patterns/rice-paper.png");
|
||
}
|
||
|
||
.calendar-header {
|
||
position: relative;
|
||
z-index: 10;
|
||
padding: 28rpx 32rpx;
|
||
border-bottom: 1px solid #eaddcf;
|
||
background: rgba(255, 253, 249, 0.85);
|
||
backdrop-filter: blur(8px);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.calendar-back {
|
||
padding: 16rpx;
|
||
margin-left: -12rpx;
|
||
color: #5a5a5a;
|
||
background: transparent;
|
||
border: none;
|
||
}
|
||
|
||
.calendar-title {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.calendar-year {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #2c2c2c;
|
||
letter-spacing: 0.3em;
|
||
}
|
||
|
||
.calendar-subtitle {
|
||
font-size: 10px;
|
||
color: #8a8a8a;
|
||
letter-spacing: 0.2em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.calendar-header-spacer {
|
||
width: 48rpx;
|
||
}
|
||
|
||
.calendar-scroll {
|
||
flex: 1;
|
||
position: relative;
|
||
z-index: 10;
|
||
padding-bottom: 80rpx;
|
||
height: 0;
|
||
}
|
||
|
||
.calendar-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 24rpx 48rpx 16rpx;
|
||
}
|
||
|
||
.calendar-nav-button {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 50%;
|
||
border: 1px solid #eaddcf;
|
||
color: #8b2323;
|
||
background: #fffdf9;
|
||
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.04);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 36rpx;
|
||
line-height: 1;
|
||
padding: 0;
|
||
}
|
||
|
||
.calendar-month {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #2c2c2c;
|
||
}
|
||
|
||
.calendar-month-unit {
|
||
font-size: 12px;
|
||
color: #8a8a8a;
|
||
margin-left: 8rpx;
|
||
}
|
||
|
||
.calendar-weekdays {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
text-align: center;
|
||
padding: 0 32rpx;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.calendar-weekday {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #5a5a5a;
|
||
letter-spacing: 0.1em;
|
||
}
|
||
|
||
.calendar-weekday-weekend {
|
||
color: #8b2323;
|
||
}
|
||
|
||
.calendar-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
padding: 0 32rpx;
|
||
gap: 12rpx 0;
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.calendar-cell {
|
||
height: 112rpx;
|
||
}
|
||
|
||
.calendar-cell-filled {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
border-radius: 12rpx;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.calendar-cell-filled.is-selected {
|
||
background: #8b2323;
|
||
box-shadow: 0 6rpx 12rpx rgba(139, 35, 35, 0.2);
|
||
}
|
||
|
||
.calendar-cell-filled.is-today:not(.is-selected) {
|
||
background: rgba(139, 35, 35, 0.05);
|
||
}
|
||
|
||
.calendar-cell-day {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #2c2c2c;
|
||
letter-spacing: 0.1em;
|
||
}
|
||
|
||
.calendar-cell-day.is-selected {
|
||
color: #fdfbf7;
|
||
}
|
||
|
||
.calendar-cell-day.is-today:not(.is-selected) {
|
||
color: #8b2323;
|
||
}
|
||
|
||
.calendar-cell-lunar {
|
||
font-size: 10px;
|
||
color: #8a8a8a;
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.calendar-cell-lunar.is-selected {
|
||
color: rgba(253, 251, 247, 0.8);
|
||
}
|
||
|
||
.calendar-today-dot {
|
||
position: absolute;
|
||
bottom: 6rpx;
|
||
width: 8rpx;
|
||
height: 8rpx;
|
||
border-radius: 50%;
|
||
background: #8b2323;
|
||
}
|
||
|
||
.calendar-detail-wrapper {
|
||
padding: 0 32rpx;
|
||
}
|
||
|
||
.calendar-detail {
|
||
background: #fffdf9;
|
||
border: 1px solid #eaddcf;
|
||
border-radius: 20rpx;
|
||
box-shadow: 0 12rpx 20rpx -8rpx rgba(0, 0, 0, 0.12);
|
||
padding: 32rpx 32rpx 28rpx;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.calendar-detail-corner {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 96rpx;
|
||
height: 96rpx;
|
||
background: rgba(139, 35, 35, 0.05);
|
||
border-bottom-left-radius: 160rpx;
|
||
}
|
||
|
||
.calendar-detail-header {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: space-between;
|
||
margin-bottom: 24rpx;
|
||
border-bottom: 1px solid #eaddcf;
|
||
padding-bottom: 16rpx;
|
||
}
|
||
|
||
.calendar-detail-date {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.calendar-detail-day {
|
||
font-size: 44px;
|
||
font-weight: bold;
|
||
color: #2c2c2c;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
.calendar-detail-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
}
|
||
|
||
.calendar-detail-lunar {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #2c2c2c;
|
||
}
|
||
|
||
.calendar-detail-weekday {
|
||
font-size: 12px;
|
||
color: #8a8a8a;
|
||
}
|
||
|
||
.calendar-detail-badge {
|
||
display: inline-block;
|
||
padding: 6rpx 12rpx;
|
||
background: #8b2323;
|
||
color: #fdfbf7;
|
||
font-size: 10px;
|
||
letter-spacing: 0.2em;
|
||
border-radius: 6rpx;
|
||
}
|
||
|
||
.calendar-detail-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.calendar-detail-row {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.calendar-detail-icon {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
border-radius: 50%;
|
||
border: 1px solid #2c2c2c;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.calendar-detail-icon-yi {
|
||
color: #2c2c2c;
|
||
}
|
||
|
||
.calendar-detail-icon-ji {
|
||
border-color: #8b2323;
|
||
color: #8b2323;
|
||
}
|
||
|
||
.calendar-detail-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12rpx;
|
||
padding-top: 6rpx;
|
||
}
|
||
|
||
.calendar-detail-tag {
|
||
font-size: 14px;
|
||
color: #5a5a5a;
|
||
padding: 6rpx 12rpx;
|
||
background: #f7f2ea;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.calendar-detail-tag-ji {
|
||
color: #8b2323;
|
||
background: rgba(139, 35, 35, 0.08);
|
||
}
|
||
|
||
.calendar-cta {
|
||
padding: 24rpx 32rpx 64rpx;
|
||
}
|
||
|
||
.calendar-cta-card {
|
||
width: 100%;
|
||
background: #2c2c2c;
|
||
border-radius: 20rpx;
|
||
padding: 32rpx;
|
||
position: relative;
|
||
overflow: hidden;
|
||
box-shadow: 0 12rpx 24rpx rgba(0, 0, 0, 0.18);
|
||
text-align: left;
|
||
}
|
||
|
||
.calendar-cta-glow {
|
||
position: absolute;
|
||
top: -20rpx;
|
||
right: -20rpx;
|
||
width: 180rpx;
|
||
height: 180rpx;
|
||
background: #d4af37;
|
||
opacity: 0.25;
|
||
filter: blur(40rpx);
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.calendar-cta-content {
|
||
position: relative;
|
||
z-index: 2;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.calendar-cta-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #d4af37;
|
||
letter-spacing: 0.1em;
|
||
display: block;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
|
||
.calendar-cta-subtitle {
|
||
font-size: 12px;
|
||
color: rgba(242, 230, 216, 0.8);
|
||
display: block;
|
||
}
|
||
|
||
.calendar-cta-arrow {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
border-radius: 50%;
|
||
background: rgba(212, 175, 55, 0.15);
|
||
color: #d4af37;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.calendar-cta-tags {
|
||
position: relative;
|
||
z-index: 2;
|
||
display: flex;
|
||
gap: 12rpx;
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.calendar-cta-tag {
|
||
font-size: 10px;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
background: rgba(255, 255, 255, 0.08);
|
||
padding: 6rpx 12rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
</style>
|