为了满足产品需求,在日历中可以添加排班信息,点击日期可以获取排班日期详细数据,自定义日历样式,并且支持手动折叠和展开,折叠时只显示当前周的日期,由于现有组件库修改起来比较麻烦,自己就手写了一个日历组件,下面是我代码及思路。
代码写的不好仅供参考,如有异议欢迎评论指正,感谢。
一个完整日历组价包括,标题年月,上个月和下个月,内容分为星期标题,'日', '一', '二', '三', '四', '五', '六',以及对应的日期排列。
日历每月日期生成排列
-
首先,通过
computed
函数创建一个计算属性days即每个月的日期
,这个属性的值会根据当前日期的变化而变化。 -
然后,获取当前日期信息,包括当前年份 (
year
)、当前月份 (month
)、当前日期 (currentDay
)。 -
获取今天的日期信息,包括今天的年份 (
todayYear
)、今天的月份 (todayMonth
)。 -
判断当前月份是否为当前年份的当月,并计算本月的天数 (
daysInMonth
)、上个月的天数 (daysInLastMonth
)、本月第一天是星期几 (firstDayOfMonth
)。 -
根据需要生成日历的显示数据:
- 如果当前月份是当前年份的当月,并且状态为折叠状态 (
state.isCollapsed
),则生成本月的一个部分日历,以当前日期所在的周为基准,从当前周的开始日到结束日。 - 否则,生成完整的一个月的日历。
- 如果当前月份是当前年份的当月,并且状态为折叠状态 (
-
对于生成的日历数据,如果是上个月的日期,设置
isLastMonth
为 true,如果是下个月的日期,设置isNextMonth
为 true,如果是当月的日期,设置isLastMonth
和isNextMonth
为 false。同时,判断该日期是否有课程安排 (isClassesDate
),并将日期、日期对应的数字、是否为上个月、是否为下个月、是否有课程安排等信息存入days
数组中。 -
最后返回生成的日期数据数组
days。
代码:
const days = computed(() => {
const today = new Date()
const year = currentDate.value.getFullYear()
const month = currentDate.value.getMonth()
const currentDay = currentDate.value.getDate()
const todayYear = today.getFullYear()
const todayMonth = today.getMonth()
const isCurrentMonth = year === todayYear && month === todayMonth
const daysInMonth = new Date(year, month + 1, 0).getDate()
const daysInLastMonth = new Date(year, month, 0).getDate()
const firstDayOfMonth = new Date(year, month, 1).getDay()
const days = []
let day = 1
let lastMonthDay = daysInLastMonth - firstDayOfMonth + 1
let nextMonthDay = 1
if (isCurrentMonth && state.isCollapsed) {
const todayDate = today.getDate()
const startOfWeek = todayDate - today.getDay() // 当前周的开始日
const endOfWeek = startOfWeek + 6 // 当前周的结束日
for (let day = startOfWeek; day <= endOfWeek; day++) {
const date = new Date(year, month, day)
let isClassesDate = props.isClassesDataList.some(
(item) => item.classesDate === dayjs(date).format('YYYY-MM-DD') && item.isClasses
)
days.push({ date, day: date.getDate(), isLastMonth: false, isNextMonth: false, isClassesDate })
}
} else {
for (let i = 0; i < 5 * 7; i++) {
if (i < firstDayOfMonth) {
days.push({
date: new Date(year, month - 1, lastMonthDay),
day: lastMonthDay,
isLastMonth: true,
isNextMonth: false,
isClassesDate: false
})
lastMonthDay++
} else if (i >= firstDayOfMonth + daysInMonth) {
days.push({
date: new Date(year, month + 1, nextMonthDay),
day: nextMonthDay,
isLastMonth: false,
isNextMonth: true,
isClassesDate: false
})
nextMonthDay++
} else {
const date = new Date(year, month, day)
// dayjs().format('YYYY-MM-DD')
// console.log(date, 'datedatedate')
// days.push({ date, day, isLastMonth: false, isNextMonth: false })
let isClassesDate = props.isClassesDataList.some(
(item) => item.classesDate === dayjs(date).format('YYYY-MM-DD') && item.isClasses == 1
)
// console.log(date, 'datedatedate')
// console.log(props.isClassesDataList)
// console.log(dayjs(date).format('YYYY-MM-DD'))
// console.log(isClassesDate)
days.push({ date, day, isLastMonth: false, isNextMonth: false, isClassesDate })
day++
}
}
}
console.log(days)
return days
})
注:此代码有使用时间库dayjs,需另外安装引用
安装:
npm install dayjs --save
使用:
// 引入
dayjs import dayjs from 'dayjs'
// 输出当前时间
console.log(dayjs())
标题生成
我这里标题格式是yyyy年mm月,根据currentDate更新后的日期获得年月,代码如下:
const title = computed(() => {
const year = currentDate.value.getFullYear()
const month = currentDate.value.getMonth() + 1 // getMonth() 返回的是 0-11 的值,需要加1
// 使用模板字符串来格式化日期,确保月份和日期为两位数
return `${year}年${month.toString().padStart(2, '0')}月`
})
点击上下月按钮,更改月份,以及请求每天排班信息数据:
const prevMonth = () => {
currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1, 1)
emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })
}
const nextMonth = () => {
currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() + 1, 1)
emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })
}
// 主要代码如上,其余代码是一些转换日期方法,日期选择器弹出框代码逻辑以及日历样式仅供参考。
<template>
<view class="calendar">
<view class="header">
<image
class=""
style="width: 32rpx; height: 32rpx; margin-left: 20rpx"
src="../../static/left.png"
@click="prevMonth"
>
</image>
<!-- @click="datePickerShow" -->
<view class="title" @click="datePickerShow">{{ title }}</view>
<image
class=""
style="width: 32rpx; height: 32rpx; margin-left: 20rpx"
src="../../static/right.png"
@click="nextMonth"
>
</image>
</view>
<view class="weekdays">
<view v-for="day in daysOfWeek" :key="day" class="day">{{ day }}</view>
</view>
<view class="days">
<view
v-for="(day, dayIndex) in days"
class="daysItem"
:key="dayIndex"
v-show="!isCollapsed || dayIndex < 7"
:class="{
today: isToday(day),
selected: isSelected(day),
notCurrentMonth: isNotCurrentMonth(day)
}"
@click="select(day)"
>
<span :class="{ 'today-class': isToday(day) }">{{ isToday(day) ? '今日' : day.day }}</span>
<span class="isClasses" v-if="day.isClassesDate">有排班</span>
</view>
</view>
<view @click="toggleCollapse" class="expand-btn">
<image
style="width: 32rpx; height: 32rpx"
src="../../static/top.png"
class="arrow"
:class="{ 'arrow-up': isCollapsed }"
/>
</view>
<!-- <m-popup v-model:visible="showDatePicker" height="auto" position="bottom" title="选择年月"> -->
<!-- <m-date-picker
:modelValue="isSelectDate"
:min-date="minDate"
:max-date="maxDate"
v-model:visible="showDatePicker"
poppable
type="yearmonth"
@cancel="cancelPicker"
@confirm="confirmPicker"
@handleConfirm="handleConfirm"
/> -->
<!-- </m-popup> -->
<PickerDate
:isOpen="showDatePicker"
modelValue="yyyy-MM"
:currentDate="isSelectDate"
@onConfirmPickerDate="onConfirmPickerDate"
@onClosePickerDate="onClosePickerDate"
/>
</view>
</template>
<script lang="ts">
import { ref, computed, reactive, toRefs, watch } from 'vue'
import { onl oad, onShow } from '@dcloudio/uni-app'
import dayjs from 'dayjs'
import PickerDate from './pickerdate'
import { userBehaviorTrackAjax } from '@/utils/track'
export default {
name: 'FeiCalendar',
components: {
PickerDate
},
props: {
selectedDate: Date,
isClassesDataList: {
type: Array,
default: () => []
}
},
emits: ['update:selectedDate', 'monthSelectClasses', 'getData'],
setup(props, { emit }) {
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
const currentDate = ref(new Date())
const selectedDate = ref(props.selectedDate || currentDate.value)
const daysOfWeek = computed(() => {
return weekdays
})
const state = reactive({
isCollapsed: true,
showDatePicker: false,
selectDate: '',
isSelectDate: ref(new Date()).value
})
const days = computed(() => {
const today = new Date()
const year = currentDate.value.getFullYear()
const month = currentDate.value.getMonth()
const currentDay = currentDate.value.getDate()
const todayYear = today.getFullYear()
const todayMonth = today.getMonth()
const isCurrentMonth = year === todayYear && month === todayMonth
const daysInMonth = new Date(year, month + 1, 0).getDate()
const daysInLastMonth = new Date(year, month, 0).getDate()
const firstDayOfMonth = new Date(year, month, 1).getDay()
const days = []
let day = 1
let lastMonthDay = daysInLastMonth - firstDayOfMonth + 1
let nextMonthDay = 1
if (isCurrentMonth && state.isCollapsed) {
const todayDate = today.getDate()
const startOfWeek = todayDate - today.getDay() // 当前周的开始日
const endOfWeek = startOfWeek + 6 // 当前周的结束日
for (let day = startOfWeek; day <= endOfWeek; day++) {
const date = new Date(year, month, day)
let isClassesDate = props.isClassesDataList.some(
(item) => item.classesDate === dayjs(date).format('YYYY-MM-DD') && item.isClasses
)
days.push({ date, day: date.getDate(), isLastMonth: false, isNextMonth: false, isClassesDate })
}
} else {
for (let i = 0; i < 5 * 7; i++) {
if (i < firstDayOfMonth) {
days.push({
date: new Date(year, month - 1, lastMonthDay),
day: lastMonthDay,
isLastMonth: true,
isNextMonth: false,
isClassesDate: false
})
lastMonthDay++
} else if (i >= firstDayOfMonth + daysInMonth) {
days.push({
date: new Date(year, month + 1, nextMonthDay),
day: nextMonthDay,
isLastMonth: false,
isNextMonth: true,
isClassesDate: false
})
nextMonthDay++
} else {
const date = new Date(year, month, day)
let isClassesDate = props.isClassesDataList.some(
(item) => item.classesDate === dayjs(date).format('YYYY-MM-DD') && item.isClasses == 1
)
days.push({ date, day, isLastMonth: false, isNextMonth: false, isClassesDate })
day++
}
}
}
console.log(days)
return days
})
const title = computed(() => {
const year = currentDate.value.getFullYear()
const month = currentDate.value.getMonth() + 1 // getMonth() 返回的是 0-11 的值,需要加1
// const day = currentDate.value.getDate()
// 使用模板字符串来格式化日期,确保月份和日期为两位数
return `${year}年${month.toString().padStart(2, '0')}月`
})
const prevMonth = () => {
currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() - 1, 1)
console.log(currentDate.value)
emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })
}
const nextMonth = () => {
currentDate.value = new Date(currentDate.value.getFullYear(), currentDate.value.getMonth() + 1, 1)
emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })
}
const isToday = (day: any) => {
const today = new Date()
return day.date.toDateString() === today.toDateString()
}
const isSelected = (day: any) => {
// console.log(day.date.toDateString() === selectedDate.value.toDateString())
return day.date.toDateString() === selectedDate.value.toDateString()
}
const isNotCurrentMonth = (day: any) => {
return day.isLastMonth || day.isNextMonth
}
// 是否折叠日历
const toggleCollapse = () => {
state.isCollapsed = !state.isCollapsed
userBehaviorTrackAjax({
page_name: 'doctor_scheduling',
event_name: 'click_view_more',
result: state.isCollapsed ? 1 : 0
})
}
const formattedDate = computed(() => {
const date = selectedDate.value
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
return `${year}-${month}-${day}`
})
const firstDayOfMonth = computed(() => {
const year = currentDate.value.getFullYear()
const month = currentDate.value.getMonth()
const firstDayOfMonth = new Date(year, month, 1)
// 获取当月第一天是周几
const dayOfWeek = firstDayOfMonth.getDay()
// 计算日历页第一个日期
const firstDate = new Date(firstDayOfMonth)
firstDate.setDate(firstDayOfMonth.getDate() - dayOfWeek + 1)
return firstDate.toISOString().substring(0, 10)
})
const lastDayOfMonth = computed(() => {
const year = currentDate.value.getFullYear()
const month = currentDate.value.getMonth()
const lastDayOfMonth = new Date(year, month + 1, 0)
// 获取当月最后一天是周几
const dayOfWeek = lastDayOfMonth.getDay()
// 计算日历页最后一个日期
const lastDate = new Date(lastDayOfMonth)
lastDate.setDate(lastDayOfMonth.getDate() + (6 - dayOfWeek) + 1)
return lastDate.toISOString().substring(0, 10)
})
// 获取选择日期的排班医生信息
const select = (day: any) => {
selectedDate.value = day.date
emit('update:selectedDate', day.date)
emit('getActivityData', formattedDate)
emit('getData', formattedDate)
}
// 打开时间选择器
const datePickerShow = () => {
state.isSelectDate = currentDate.value
state.showDatePicker = true
}
// 将选择器的时间转为可使用的中国标准时间格式
const formatDate = computed(() => {
const dateParts = state.selectDate.match(/(\d+)/g)
if (dateParts) {
const year = dateParts[0]
const month = dateParts[1]
// const day = dateParts[2]
const day = '1'
const date = new Date(year, month - 1, day)
const options = {
weekday: 'short',
month: 'short',
day: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short'
}
return date.toLocaleString('en-US', options).replace(',', '')
} else {
return ''
}
})
// 时间选择器
const onConfirmPickerDate = (props) => {
state.selectDate = `${props.year}年${props.month}月`
currentDate.value = new Date(formatDate.value)
emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })
state.showDatePicker = false
}
const onClosePickerDate = (props) => {
state.showDatePicker = false
}
// 监听 currentDate.value 的变化
watch(
() => currentDate,
() => {
// console.log(currentDate.value, '监听数据222!')
},
{ deep: true, immediate: true }
)
onLoad(() => {
emit('monthSelectClasses', { startDate: firstDayOfMonth.value, endDate: lastDayOfMonth.value })
})
return {
daysOfWeek,
datePickerShow,
// cancelPicker,
// handleConfirm,
// confirmPicker,
onClosePickerDate,
onConfirmPickerDate,
// isToday,
days,
years: '2024',
month: '03',
chineseStandardTimeString: '',
...toRefs(state),
formattedDate,
formatDate,
firstDayOfMonth,
lastDayOfMonth,
toggleCollapse,
title,
prevMonth,
nextMonth,
isToday,
isSelected,
isNotCurrentMonth,
select,
minDate: new Date(2020, 0, 1),
maxDate: new Date(3024, 5, 1),
columnsType: ['year', 'month']
}
}
}
</script>
<style lang="scss" scoped>
.calendar {
max-width: 500px;
margin: 0 auto;
font-family: Arial, sans-serif;
background: #ffffff;
border-radius: 32rpx;
padding: 20rpx 10rpx;
/* z-index: 999999999 !important; */
position: relative;
top: 0;
left: 0;
}
::v-deep {
.picker__bar {
color: #333333 !important;
}
.picker__ok {
color: #333333 !important;
}
.icon-close {
color: #333333 !important;
}
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding: 0 10px;
}
.title {
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
.weekdays {
display: flex;
justify-content: space-around;
margin-bottom: 10px;
font-weight: 400;
font-size: 24rpx;
color: #999999;
}
.day {
width: 30px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
}
.days {
display: grid;
grid-template-columns: repeat(7, 1fr);
grid-gap: 10px;
.daysItem {
display: flex;
align-items: center;
flex-direction: column;
justify-content: flex-start;
padding-top: 10rpx;
border: 1rpx solid #e6e6e6;
border-radius: 16rpx;
/* justify-content: space-evenly; */
height: 94rpx;
color: #333333;
font-weight: 600;
.isClasses {
color: #109cac;
font-size: 20rpx;
font-weight: 500;
margin-top: 10rpx;
}
}
.today-class {
font-size: 24rpx;
}
}
.expand-btn {
text-align: center;
margin-top: 20rpx;
/* margin-bottom: 5rpx; */
.arrow-up {
transform: rotate(180deg);
}
}
.today {
/* background-color: lightblue; */
border-radius: 16rpx;
}
.selected {
background: #2ca4ad;
border-radius: 16rpx;
color: white !important;
span {
color: #ffffff !important;
}
}
.notCurrentMonth {
color: #ccc !important;
}
::v-deep {
.picker__ok {
color: rgb(159, 159, 160);
}
.icon-close {
font-size: 30rpx !important;
}
}
/* .date-picker {
position: absolute;
bottom: 0;
} */
</style>
标签:uniapp,const,value,month,date,vue3,currentDate,手写,day From: https://blog.csdn.net/weixin_44045991/article/details/137461123