Files
2025-05-15 14:17:51 +08:00

312 lines
9.5 KiB
Vue

<template>
<AppLayout title="选择日期" :use-scroll-view="false" back-gorund-color="#FAFAFA">
<template #headerleft>
<view class="btn">
<image src="@/static/icon/back.png" @click="navBack"></image>
</view>
</template>
<template #headerright>
<view class="btn mar_ri10 button-click" @click="backParams">确认</view>
</template>
<view class="content">
<view class="top-date">
<view
class="weekText"
:class="{ color_256BFA: item === '日' || item === '六' }"
v-for="(item, index) in weekMap"
:key="index"
>
{{ item }}
</view>
</view>
<scroll-view scroll-y class="main-scroll" @scrolltolower="onScrollBottom">
<view class="date-list" v-for="(vItem, vIndex) in calendarData" :key="vIndex">
<view class="list-title">{{ vItem.title }}</view>
<view class="list-item">
<view
class="item button-click"
:class="{
noOptional: !item.isThisMonth,
active: current.date === item.date && item.isThisMonth,
}"
v-for="(item, index) in vItem.list"
:key="index"
@click="selectDay(item)"
>
<view class="item-top">{{ item.day }}</view>
<view class="item-nong">{{ item.nl }}</view>
</view>
</view>
</view>
</scroll-view>
</view>
</AppLayout>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
const { $api, navTo, navBack } = inject('globalFunction');
const weekMap = ['日', '一', '二', '三', '四', '五', '六'];
const calendarData = ref([]);
const current = ref({});
import { Solar, Lunar } from '@/lib/lunar-javascript@1.7.2.js';
const isRecord = ref(false);
const recordNum = ref(4);
const pages = reactive({
year: 0,
month: 0,
});
onLoad((options) => {
if (options.date) {
current.value = {
date: options?.date || null,
};
}
if (options.record) {
isRecord.value = true;
initOldPagesDate();
new Array(recordNum.value + 1).fill(null).map(() => {
addMonth();
});
} else {
initPagesDate();
new Array(recordNum.value).fill(null).map(() => {
addMonth();
});
}
});
function backParams() {
if (isValidDateString(current.value.date)) {
navBack({
data: current.value,
});
} else {
$api.msg('请选择日期');
}
}
function isValidDateString(dateStr) {
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (!regex.test(dateStr)) return false;
const date = new Date(dateStr);
if (isNaN(date.getTime())) return false;
// 检查日期部分是否一致(防止 "2020-02-31" 这种被 Date 自动修正)
const [year, month, day] = dateStr.split('-').map(Number);
return date.getFullYear() === year && date.getMonth() + 1 === month && date.getDate() === day;
}
function onScrollBottom() {
if (isRecord.value) return;
addMonth();
}
function selectDay(item) {
current.value = item;
}
function initOldPagesDate() {
const d = new Date();
const yue = d.getMonth();
if (yue < recordNum.value) {
pages.month = 12 + (yue - recordNum.value);
pages.year = d.getFullYear() - 1;
} else {
pages.month = yue - recordNum.value;
pages.year = d.getFullYear();
}
}
function initPagesDate() {
const d = new Date();
pages.month = d.getMonth();
pages.year = d.getFullYear();
}
function addMonth() {
if (pages.month >= 12) {
pages.year += 1;
pages.month = 1;
} else {
pages.month += 1;
}
const month = getMonthCalendarData(pages);
calendarData.value.push(month);
}
/**
*
* @param {Array<string>} selectedDates - 选中的日期数组 ['2025-04-21', ...]
* @returns {Array<Object>} 六个月日历数据
*/
function getMonthCalendarData({ year, month, selectableDates = [] }) {
const todayStr = new Date().toISOString().split('T')[0];
const isSelected = (y, m, d) =>
selectableDates.includes(`${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`);
const list = [];
const firstDay = new Date(year, month - 1, 1);
const firstWeekday = firstDay.getDay(); // 0 是周日
const lastDate = new Date(year, month, 0).getDate();
// 补全前一月天数(上月最后几天)
if (firstWeekday !== 0) {
const prevLastDate = new Date(year, month - 1, 0).getDate();
for (let i = firstWeekday - 1; i >= 0; i--) {
const d = prevLastDate - i;
const prevMonth = month - 1 <= 0 ? 12 : month - 1;
const prevYear = month - 1 <= 0 ? year - 1 : year;
const solar = Solar.fromYmd(prevYear, prevMonth, d);
const lunar = Lunar.fromSolar(solar);
const dateStr = `${prevYear}-${String(prevMonth).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
list.push({
day: d,
nl: lunar.getDayInChinese(),
isThisMonth: false,
isToday: dateStr === todayStr,
isSelectable: isSelected(prevYear, prevMonth, d),
week: weekMap[new Date(prevYear, prevMonth - 1, d).getDay()],
date: dateStr,
});
}
}
// 当前月天数
for (let d = 1; d <= lastDate; d++) {
const solar = Solar.fromYmd(year, month, d);
const lunar = Lunar.fromSolar(solar);
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
list.push({
day: d,
nl: lunar.getDayInChinese(),
isThisMonth: true,
isToday: dateStr === todayStr,
isSelectable: isSelected(year, month, d),
week: weekMap[new Date(year, month - 1, d).getDay()],
date: dateStr,
});
}
// 补全下个月天数(让最后一行完整 7 个)
const remaining = 7 - (list.length % 7);
if (remaining < 7) {
for (let d = 1; d <= remaining; d++) {
const nextMonth = month + 1 > 12 ? 1 : month + 1;
const nextYear = month + 1 > 12 ? year + 1 : year;
const solar = Solar.fromYmd(nextYear, nextMonth, d);
const lunar = Lunar.fromSolar(solar);
const dateStr = `${nextYear}-${String(nextMonth).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
list.push({
day: d,
nl: lunar.getDayInChinese(),
isThisMonth: false,
isToday: dateStr === todayStr,
isSelectable: isSelected(nextYear, nextMonth, d),
week: weekMap[new Date(nextYear, nextMonth - 1, d).getDay()],
date: dateStr,
});
}
}
return {
title: `${year}${month}`,
list,
};
}
</script>
<style lang="stylus" scoped>
.btn {
display: flex;
justify-content: space-between;
align-items: center;
white-space: nowrap
width: 60rpx;
height: 60rpx;
image {
height: 100%;
width: 100%;
}
}
.content{
height: 100%
display: flex
flex-direction: column
border-radius: 40rpx 40rpx 0 0
background: #FFFFFF;
overflow: hidden
margin-top: 20rpx
.top-date{
display: flex
justify-content: space-between
padding: 28rpx
.weekText{
text-align: center
width: 99rpx;
}
}
.main-scroll{
flex: 1
overflow: hidden
}
.date-list{
.list-title{
height: 84rpx;
line-height: 84rpx
background: #FAFAFA;
font-weight: 600;
font-size: 32rpx;
color: #1A1A1A;
padding: 0 28rpx;
box-sizing: border-box
}
.list-item{
padding: 16rpx 28rpx 20rpx 28rpx;
display: grid
grid-template-columns: repeat(7, 1fr)
grid-gap: 0rpx
.item{
display: flex
flex-direction: column
align-items: center
justify-content: center
border-radius: 20rpx 20rpx 20rpx 20rpx;
padding: 16rpx 0
margin-bottom: 20rpx
.item-top{
font-weight: 600;
font-size: 32rpx;
}
.item-nong{
font-size: 16rpx;
color: #666666
margin-top: 2rpx
}
}
.optional{
// height: 96rpx;
border: 2rpx solid #92B5FC;
}
.noOptional{
color: #B8B8B8;
}
.active{
background: linear-gradient( 225deg, #9E74FD 0%, #256BFA 100%);
color: #FFFFFF
.item-nong{
font-size: 16rpx;
color: #FFFFFF
margin-top: 2rpx
}
}
}
}
}
</style>