hldy_app_mini/component/public/calendar.vue

348 lines
8.0 KiB
Vue
Raw Normal View History

2025-11-05 15:59:48 +08:00
<template>
<view class="calendar">
<view class="header">
<view class="header-title">
2025-11-17 16:28:02 +08:00
<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>
2025-11-05 15:59:48 +08:00
<view class="year-month">{{ year }}{{ month + 1 }}</view>
2025-11-17 16:28:02 +08:00
<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" />
2025-11-05 15:59:48 +08:00
</view>
2025-11-17 16:28:02 +08:00
2025-11-05 15:59:48 +08:00
</view>
2025-11-17 16:28:02 +08:00
2025-11-05 15:59:48 +08:00
<view class="weekdays">
<view v-for="(day, idx) in weekdays" :key="idx" class="weekday">{{ day }}</view>
</view>
</view>
<view class="days">
2025-11-17 16:28:02 +08:00
<view v-for="cell in cells" :key="cell.key" class="day-cell" :class="{
'prev-month': cell.prev,
'next-month': cell.next,
'start': isStart(cell.key),
'end': isEnd(cell.key),
'in-range': isInRange(cell.dateNumber)
}" @click="selectDate(cell)">
2025-11-05 15:59:48 +08:00
<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
2025-11-17 16:28:02 +08:00
// 初始当前年月
2025-11-05 15:59:48 +08:00
const now = new Date();
const year = ref(now.getFullYear());
2025-11-17 16:28:02 +08:00
const month = ref(now.getMonth()); // 0-based
// 选中开始 / 结束 key (格式: YYYY-MM-DD)
const startKey = ref(null);
const endKey = ref(null);
// 把年月日转成便于比较的数字 YYYYMMDD
function toDateNumber(y, m, d) {
// m 是 0-base
const mm = m + 1;
return y * 10000 + mm * 100 + d;
}
2025-11-05 15:59:48 +08:00
2025-11-17 16:28:02 +08:00
// 格式化 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}`;
}
2025-11-05 15:59:48 +08:00
2025-11-17 16:28:02 +08:00
// 星期一..日(与之前一致)
2025-11-05 15:59:48 +08:00
const weekdays = ['一', '二', '三', '四', '五', '六', '日'];
2025-11-17 16:28:02 +08:00
// 计算某月第一天是周几(调整为 Monday = 0
2025-11-05 15:59:48 +08:00
const firstWeekdayOfMonth = (y, m) => {
2025-11-17 16:28:02 +08:00
const d = new Date(y, m, 1).getDay(); // 0 (Sun) - 6 (Sat)
return (d + 6) % 7; // 转成 Monday=0 .. Sunday=6
2025-11-05 15:59:48 +08:00
};
2025-11-17 16:28:02 +08:00
// 生成 6*7 (42) 个格子的 cells包含前后月份
function buildCells(y, m) {
2025-11-05 15:59:48 +08:00
const list = [];
2025-11-17 16:28:02 +08:00
// 上个月信息
const prevDate = new Date(y, m, 0); // 上个月最后一天
const prevYear = prevDate.getFullYear();
const prevMonth = prevDate.getMonth();
const prevTotal = prevDate.getDate();
const startOffset = firstWeekdayOfMonth(y, m);
// 前置填充
2025-11-05 15:59:48 +08:00
for (let i = 0; i < startOffset; i++) {
const day = prevTotal - startOffset + i + 1;
const lunar = solarlunar.solar2lunar(prevYear, prevMonth + 1, day);
2025-11-17 16:28:02 +08:00
const dateNumber = toDateNumber(prevYear, prevMonth, day);
2025-11-05 15:59:48 +08:00
list.push({
key: `prev-${prevYear}-${prevMonth + 1}-${day}`,
dateText: day,
2025-11-17 16:28:02 +08:00
lunarText: lunar ? lunar.dayCn : '',
2025-11-05 15:59:48 +08:00
prev: true,
next: false,
2025-11-17 16:28:02 +08:00
year: prevYear,
month: prevMonth,
day,
dateNumber,
2025-11-05 15:59:48 +08:00
});
}
2025-11-17 16:28:02 +08:00
// 当前月
const totalDays = new Date(y, m + 1, 0).getDate();
2025-11-05 15:59:48 +08:00
for (let d = 1; d <= totalDays; d++) {
2025-11-17 16:28:02 +08:00
const lunar = solarlunar.solar2lunar(y, m + 1, d);
const dateNumber = toDateNumber(y, m, d);
2025-11-05 15:59:48 +08:00
list.push({
2025-11-17 16:28:02 +08:00
key: formatKey(y, m, d),
2025-11-05 15:59:48 +08:00
dateText: d,
2025-11-17 16:28:02 +08:00
lunarText: lunar ? lunar.dayCn : '',
2025-11-05 15:59:48 +08:00
prev: false,
next: false,
2025-11-17 16:28:02 +08:00
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,
2025-11-05 15:59:48 +08:00
});
}
2025-11-17 16:28:02 +08:00
2025-11-05 15:59:48 +08:00
return list;
2025-11-17 16:28:02 +08:00
}
// 当前视图的 cells
const cells = computed(() => buildCells(year.value, month.value));
// start/end 转成数值便于比较
const startNumber = computed(() => {
if (!startKey.value) return null;
const [y, m, d] = startKey.value.split('-').map(Number);
return toDateNumber(y, m - 1, d);
});
const endNumber = computed(() => {
if (!endKey.value) return null;
const [y, m, d] = endKey.value.split('-').map(Number);
return toDateNumber(y, m - 1, d);
2025-11-05 15:59:48 +08:00
});
2025-11-17 16:28:02 +08:00
// 判断是否 start / end
function isStart(key) {
return startKey.value === key;
}
function isEnd(key) {
return endKey.value === key;
2025-11-05 15:59:48 +08:00
}
2025-11-17 16:28:02 +08:00
// 判断是否在范围内(包含边界)
function isInRange(dateNumber) {
if (!startNumber.value || !endNumber.value) return false;
return dateNumber >= startNumber.value && dateNumber <= endNumber.value;
}
const emit = defineEmits(["datachange"]); // 定义事件名
// 选择日期逻辑:支持任意先后顺序的两次点击来形成区间
2025-11-05 15:59:48 +08:00
function selectDate(cell) {
2025-11-17 16:28:02 +08:00
// console.log("ZZZZ",isInRange(cell.dateNumber))
if (cell.prev || cell.next) return
const key = formatKey(cell.year, cell.month, cell.day);
const num = cell.dateNumber;
// 如果没有选 start -> 设为 start
if (!startKey.value) {
startKey.value = key;
endKey.value = null;
return;
2025-11-05 15:59:48 +08:00
}
2025-11-17 16:28:02 +08:00
// 已有 start 但无 end
if (startKey.value && !endKey.value) {
// 如果第二次选择晚于或等于 start -> 设为 end
if (num >= startNumber.value) {
endKey.value = key;
// console.log("开始,结束", startKey.value, endKey.value)
emit("datachange", {
start: startKey.value,
end: endKey.value,
});
return;
}
// 如果第二次选择早于 start -> 把早的设为 start晚的设为 end即交换
endKey.value = startKey.value;
startKey.value = key;
// console.log("开始,结束", startKey.value, endKey.value)
emit("datachange", {
start: startKey.value,
end: endKey.value,
});
return;
}
// 已有 start 和 end再次点击任意日期 -> 以该日期作为新的 start清除 end
startKey.value = key;
endKey.value = null;
2025-11-05 15:59:48 +08:00
}
2025-11-17 16:28:02 +08:00
// 年/月 切换
2025-11-05 15:59:48 +08:00
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++;
}
}
2025-11-17 16:28:02 +08:00
function prevYear() {
year.value--;
}
function nextYear() {
year.value++;
}
2025-11-05 15:59:48 +08:00
</script>
<style scoped lang="less">
.calendar {
padding: 16px;
2025-11-17 16:28:02 +08:00
background: #fff;
border-radius: 30rpx;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
// max-width: 720rpx;
margin: 0 auto;
2025-11-05 15:59:48 +08:00
}
.header {
2025-11-17 16:28:02 +08:00
width: 100%;
2025-11-05 15:59:48 +08:00
display: flex;
flex-direction: column;
}
.header-title {
2025-11-17 16:28:02 +08:00
width: 100%;
2025-11-05 15:59:48 +08:00
display: flex;
justify-content: space-between;
align-items: center;
2025-11-17 16:28:02 +08:00
margin-bottom: 10rpx;
2025-11-05 15:59:48 +08:00
}
.year-month {
font-size: 18px;
font-weight: bold;
}
.weekdays {
display: flex;
2025-11-17 16:28:02 +08:00
background-color: #F8F8FA;
border-radius: 18rpx;
padding: 8rpx;
margin-top: 8rpx;
2025-11-05 15:59:48 +08:00
}
.weekday {
flex: 1;
text-align: center;
2025-11-17 16:28:02 +08:00
font-size: 12px;
2025-11-05 15:59:48 +08:00
}
.days {
display: flex;
flex-wrap: wrap;
2025-11-17 16:28:02 +08:00
padding: 8rpx 0;
2025-11-05 15:59:48 +08:00
}
.day-cell {
2025-11-17 16:28:02 +08:00
width: calc(100% / 7);
height: 78rpx;
2025-11-05 15:59:48 +08:00
text-align: center;
padding-top: 8rpx;
box-sizing: border-box;
2025-11-17 16:28:02 +08:00
position: relative;
2025-11-05 15:59:48 +08:00
}
2025-11-17 16:28:02 +08:00
/* 前后月份灰色显示 */
2025-11-05 15:59:48 +08:00
.day-cell.prev-month .gregorian,
.day-cell.next-month .gregorian {
color: #ccc;
}
2025-11-17 16:28:02 +08:00
/* 范围内(中间)样式 */
.day-cell.in-range {
background-color: #E6F7FF;
}
/* 开始/结束 的样式 */
.day-cell.start,
.day-cell.end {
2025-11-05 15:59:48 +08:00
background-color: #0B98DC;
2025-11-17 16:28:02 +08:00
border-radius: 8rpx;
2025-11-05 15:59:48 +08:00
}
2025-11-17 16:28:02 +08:00
.day-cell.start .gregorian,
.day-cell.end .gregorian,
.day-cell.start .lunar,
.day-cell.end .lunar {
2025-11-05 15:59:48 +08:00
color: #fff;
}
.gregorian {
font-size: 14px;
}
.lunar {
font-size: 10px;
color: #888;
}
2025-11-17 16:28:02 +08:00
.head-left{
width: 100rpx;
display: flex;
justify-content: space-between;
.head-img{
width: 40rpx;
height: 40rpx;
}
}
2025-11-05 15:59:48 +08:00
</style>