hldy_app_mini/component/public/calendarsimple.vue

299 lines
6.4 KiB
Vue
Raw Normal View History

2026-01-09 17:17:45 +08:00
<template>
<view class="calendar">
<view class="header">
<view class="header-title">
<view class="head-left">
<image class="head-img" src="/static/index/calendar/superleft.png" @click="prevYear" />
<image class="head-img" src="/static/index/calendar/left.png" @click="prevMonth" />
</view>
<view class="year-month">{{ year }}{{ month + 1 }}</view>
<view class="head-left">
<image class="head-img" src="/static/index/calendar/right.png" @click="nextMonth" />
<image class="head-img" src="/static/index/calendar/superright.png" @click="nextYear" />
</view>
</view>
<view class="weekdays">
<view v-for="(day, idx) in weekdays" :key="idx" class="weekday">{{ day }}</view>
</view>
</view>
<view class="days">
<view v-for="cell in cells" :key="cell.key" class="day-cell" :class="{
'prev-month': cell.prev,
'next-month': cell.next,
'selected': isSelected(cell.key)
}" @click="selectDate(cell)">
<view class="gregorian">{{ cell.dateText }}</view>
<view class="lunar" v-if="cell.lunarText">{{ cell.lunarText }}</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
computed
} from 'vue';
import solarlunar from 'solarlunar'; // npm install solarlunar
// 初始当前年月
const now = new Date();
const year = ref(now.getFullYear());
const month = ref(now.getMonth()); // 0-based
// 选中单个 key (格式: YYYY-MM-DD)
const selectedKey = ref(null);
// 把年月日转成便于比较的数字 YYYYMMDD
function toDateNumber(y, m, d) {
// m 是 0-base
const mm = m + 1;
return y * 10000 + mm * 100 + d;
}
// 格式化 keym 0-base => 输出两位)
function formatKey(y, m, d) {
const mm = String(m + 1).padStart(2, '0');
const dd = String(d).padStart(2, '0');
return `${y}-${mm}-${dd}`;
}
// 星期一..日(与之前一致)
const weekdays = ['一', '二', '三', '四', '五', '六', '日'];
// 计算某月第一天是周几(调整为 Monday = 0
const firstWeekdayOfMonth = (y, m) => {
const d = new Date(y, m, 1).getDay(); // 0 (Sun) - 6 (Sat)
return (d + 6) % 7; // 转成 Monday=0 .. Sunday=6
};
// 生成 6*7 (42) 个格子的 cells包含前后月份
function buildCells(y, m) {
const list = [];
// 上个月信息
const prevDate = new Date(y, m, 0); // 上个月最后一天
const prevYear = prevDate.getFullYear();
const prevMonth = prevDate.getMonth();
const prevTotal = prevDate.getDate();
const startOffset = firstWeekdayOfMonth(y, m);
// 前置填充
for (let i = 0; i < startOffset; i++) {
const day = prevTotal - startOffset + i + 1;
const lunar = solarlunar.solar2lunar(prevYear, prevMonth + 1, day);
const dateNumber = toDateNumber(prevYear, prevMonth, day);
list.push({
key: `prev-${prevYear}-${prevMonth + 1}-${day}`,
dateText: day,
lunarText: lunar ? lunar.dayCn : '',
prev: true,
next: false,
year: prevYear,
month: prevMonth,
day,
dateNumber,
});
}
// 当前月
const totalDays = new Date(y, m + 1, 0).getDate();
for (let d = 1; d <= totalDays; d++) {
const lunar = solarlunar.solar2lunar(y, m + 1, d);
const dateNumber = toDateNumber(y, m, d);
list.push({
key: formatKey(y, m, d),
dateText: d,
lunarText: lunar ? lunar.dayCn : '',
prev: false,
next: false,
year: y,
month: m,
day: d,
dateNumber,
});
}
// 尾部补位到 42
const need = 42 - list.length;
for (let i = 1; i <= need; i++) {
const nd = new Date(y, m + 1, i);
const ny = nd.getFullYear();
const nm = nd.getMonth();
const lunar = solarlunar.solar2lunar(ny, nm + 1, i);
const dateNumber = toDateNumber(ny, nm, i);
list.push({
key: `next-${ny}-${nm + 1}-${i}`,
dateText: i,
lunarText: lunar ? lunar.dayCn : '',
prev: false,
next: true,
year: ny,
month: nm,
day: i,
dateNumber,
});
}
return list;
}
// 当前视图的 cells
const cells = computed(() => buildCells(year.value, month.value));
const emit = defineEmits(["datachange"]); // 定义事件名
// 判断是否选中
function isSelected(key) {
return selectedKey.value === key;
}
// 选择日期逻辑(单选,重复点击取消)
function selectDate(cell) {
// 不允许选择前/后月份的格子(如果需要支持跨月选择,可修改这里)
if (cell.prev || cell.next) return;
const key = formatKey(cell.year, cell.month, cell.day);
// 点击同一天 -> 取消选择
if (selectedKey.value === key) {
selectedKey.value = null;
emit("datachange", {
date: null
});
return;
}
// 否则选中该日期
selectedKey.value = key;
emit("datachange", {
date: key
});
}
// 年/月 切换
function prevMonth() {
if (month.value === 0) {
year.value--;
month.value = 11;
} else {
month.value--;
}
}
function nextMonth() {
if (month.value === 11) {
year.value++;
month.value = 0;
} else {
month.value++;
}
}
function prevYear() {
year.value--;
}
function nextYear() {
year.value++;
}
</script>
<style scoped lang="less">
.calendar {
padding: 16px;
background: #fff;
border-radius: 30rpx;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
margin: 0 auto;
overflow: hidden;
}
.header {
width: 100%;
display: flex;
flex-direction: column;
}
.header-title {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
}
.year-month {
font-size: 18px;
font-weight: bold;
}
.weekdays {
display: flex;
background-color: #F8F8FA;
border-radius: 18rpx;
padding: 8rpx;
margin-top: 8rpx;
}
.weekday {
flex: 1;
text-align: center;
font-size: 12px;
}
.days {
display: flex;
flex-wrap: wrap;
padding: 8rpx 0;
}
.day-cell {
width: calc(100% / 7);
height: 78rpx;
text-align: center;
padding-top: 8rpx;
box-sizing: border-box;
position: relative;
}
/* 前后月份灰色显示 */
.day-cell.prev-month .gregorian,
.day-cell.next-month .gregorian {
color: #ccc;
}
/* 选中(单选)样式 */
.day-cell.selected {
background-color: #0B98DC;
border-radius: 8rpx;
}
.day-cell.selected .gregorian,
.day-cell.selected .lunar {
color: #fff;
}
.gregorian {
font-size: 14px;
}
.lunar {
font-size: 10px;
color: #888;
}
.head-left {
width: 100rpx;
display: flex;
justify-content: space-between;
.head-img {
width: 40rpx;
height: 40rpx;
}
}
</style>