可折叠,可标记日历
样式:
实现代码
index.tsx
import { Image } from '@tarojs/components'; import View from '@/components/GymComponents/GymView'; import { useState } from 'react'; import classnames from 'classnames'; import dayjs from 'dayjs'; import styles from './index.module.scss'; const weekdaysList = ['日', '一', '二', '三', '四', '五', '六']; // 获取当月天数据 const getWeekListByMonth = (monthObj: dayjs.Dayjs, selectedDay: string) => { const firstMonthDay = monthObj.startOf('month'); const lastMonthDay = monthObj.endOf('month'); const firstShowDay = firstMonthDay.startOf('week'); const lastShowDay = lastMonthDay.endOf('week'); const weekList = []; let selectedDayListIndex = 0; let weekListIndex = 0; for (let day = firstShowDay; day.isBefore(lastShowDay); day = day.add(7, 'day')) { const dayList = []; for (let index = 0; index < 7; index++) { const currentDay = day.add(index, 'day'); dayList.push(currentDay); // 获取选中行 if (currentDay.format('YYYY-MM-DD') == selectedDay) { selectedDayListIndex = weekListIndex; } } weekListIndex++; weekList.push(dayList); } return { weekList, selectedDayListIndex }; }; // 单元格处理 const MonthCell = props => { const { day, currentMonth, selectedDay, onClick, historyDateList } = props; const currentMonthText = currentMonth.format('YYYY.MM'); const dayMonthText = day.format('YYYY.MM'); if (currentMonthText !== dayMonthText) { return <View className={styles.monthCell} />; } const todayText = dayjs().format('YYYY-MM-DD'); const dayText: string = day.format('YYYY-MM-DD'); const disable = day.isBefore(dayjs(), 'day'); const onHandleItem = (disable: boolean) => { if (disable) { return; } onClick && onClick(dayText); }; return ( <View onClick={() => onHandleItem(disable)} className={styles.monthCell}> <View className={classnames(styles.monthCellValue, { [styles.monthCellValueGrey]: disable, [styles.monthCellValueToday]: todayText === dayText, [styles.monthCellSelected]: selectedDay === dayText, })} > {todayText === dayText ? '今' : day.format('D')} </View> {historyDateList.includes(dayText) && !disable && <View className={styles.monthCellPoint} />} </View> ); }; interface calenderProps { onChangeMonth?: (val: dayjs.Dayjs) => void; onChangeSelect?: (val: string) => void; historyDateList?: string[]; focusDay?: string; } const CourseCalender = (props: calenderProps) => { const { onChangeSelect, historyDateList = [], onChangeMonth, focusDay = dayjs().format('YYYY-MM-DD'), } = props; const today = dayjs().hour(0).minute(0).second(0).millisecond(0); const todayMonth = today.clone().date(1); const [currentMonth, setCurrentMonth] = useState(todayMonth); const [selectedDay, setSelectedDay] = useState(focusDay); const [showOneLine, setShowOneLine] = useState(true); const hasPreMonth = todayMonth.isBefore(currentMonth); const { weekList = [], selectedDayListIndex } = getWeekListByMonth(currentMonth, selectedDay); const handlePreMonth = () => { if (hasPreMonth) { const preMonth = currentMonth.clone().subtract(1, 'month'); setCurrentMonth(preMonth); onChangeMonth && onChangeMonth(preMonth); // 切换月份日期默认全展示 setShowOneLine(false); } }; const handleNextMonth = () => { const nextMonth = currentMonth.clone().add(1, 'month'); setCurrentMonth(nextMonth); onChangeMonth && onChangeMonth(nextMonth); // 切换月份日期默认全展示 setShowOneLine(false); }; const onHandleMonthCell = (dayText: string) => { setSelectedDay(dayText); onChangeSelect && onChangeSelect(dayText); }; const onHandleIcon = (showIcon: boolean) => { if (!showIcon) { return; } setShowOneLine(!showOneLine); }; return ( <View className={styles.courseCalender}> <View className={styles.calenderHead}> <View className={styles.headLeft}>{currentMonth.format('YYYY年MM月')}</View> <View className={styles.headRight}> <View className={classnames(styles.leftIcon, { [styles.leftIconGrey]: !hasPreMonth })} onClick={handlePreMonth} /> <View className={classnames(styles.rightIcon, { [styles.rightIconGrey]: false })} onClick={handleNextMonth} /> </View> </View> <View className={styles.calenderContent}> <View className={styles.calenderWeek}> {weekdaysList.map((item, index) => ( <View className={styles.calenderWeekItem} key={index}> {item} </View> ))} </View> <View className={styles.calenderMonth}> {(showOneLine ? [weekList[selectedDayListIndex]] : weekList).map( (dayList, dayListIndex) => ( <View className={styles.calenderMonthRow} key={dayListIndex}> {dayList.map(day => ( <MonthCell key={day} day={day} currentMonth={currentMonth} historyDateList={historyDateList} selectedDay={selectedDay} onClick={(dayText: string) => onHandleMonthCell(dayText)} /> ))} </View> ), )} </View> </View> <View className={styles.calenderFooter} onClick={() => onHandleIcon(currentMonth.format('YYYY-MM') == selectedDay.slice(0, 7))} > {currentMonth.format('YYYY-MM') == selectedDay.slice(0, 7) && ( <Image className={styles.footerIcon} webp mode="aspectFill" src={ showOneLine ? 'https://img.alicdn.com/imgextra/i3/O1CN015sI3l8283imvg6YPJ_!!6000000007877-2-tps-48-48.png' : 'https://img.alicdn.com/imgextra/i4/O1CN01U8KCQe1DlINDEFEQq_!!6000000000256-2-tps-48-48.png' } lazyLoad /> )} </View> </View> ); }; export default CourseCalender;
index.less
.courseCalender { width: 686px; background-color: #fff; border-radius: 8px; overflow: hidden; .calenderHead { width: 100%; height: 72px; background: #222; display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 0 16px 0 24px; .headLeft { font-family: Akrobat-ExtraBold; font-size: 32px; color: #fff; } .headRight { display: flex; flex-direction: row; .leftIcon, .rightIcon { width: 48px; height: 48px; background-repeat: no-repeat; background-size: contain; margin-left: 8px; } .leftIcon { background-image: url('https://img.alicdn.com/imgextra/i2/O1CN01buoF9i1Ntfm2Xi8Vx_!!6000000001628-2-tps-48-48.png'); } .leftIconGrey { background-image: url('https://img.alicdn.com/imgextra/i2/O1CN01W9Cdbb29VUPwL5opO_!!6000000008073-2-tps-48-48.png'); } .rightIcon { background-image: url('https://img.alicdn.com/imgextra/i3/O1CN01lgfwCS1CE1zRUe9X7_!!6000000000048-2-tps-48-48.png'); } .rightIconGrey { background-image: url('https://img.alicdn.com/imgextra/i1/O1CN01INUsTs1zv0KCwndMy_!!6000000006775-2-tps-48-48.png'); } } } .calenderContent { width: 100%; padding: 0 12px; display: flex; flex-direction: column; align-items: center; .calenderWeek { width: 100%; height: 82px; display: flex; flex-direction: row; justify-content: space-around; align-items: center; .calenderWeekItem { width: 64px; height: 64px; font-family: PingFangSC-Regular; line-height: 64px; text-align: center; font-size: 22px; color: #666; flex-shrink: 0; } } .calenderMonth { width: 100%; .calenderMonthRow { width: 100%; height: 82px; display: flex; flex-direction: row; justify-content: space-around; align-items: center; } } } .calenderFooter { width: 100%; height: 48px; display: flex; align-items: center; justify-content: center; .footerIcon { width: 48px; height: 48px; } } } .monthCell { width: 64px; height: 64px; flex-shrink: 0; position: relative; .monthCellValue { width: 100%; height: 100%; font-family: PingFangSC-Regular; line-height: 64px; text-align: center; font-size: 32px; color: #666; border-radius: 50%; } .monthCellValueGrey { color: rgba(102, 102, 102, 0.3); } .monthCellValueToday { font-family: PingFangSC-Regular; font-size: 28px; color: #666; background-color: #f5f5f5; } .monthCellSelected { color: #f5f5f5; background-color: #ff6022; } .monthCellPoint { width: 8px; height: 8px; border-radius: 50%; background-color: #ff6022; position: absolute; bottom: 6px; left: 28px; } }