可摺疊,可標記日曆

喻佳文發表於2024-08-15

樣式:

實現程式碼

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;
  }

}

相關文章