312 lines
9.5 KiB
Vue
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>
|