js 手搓带农历的日历组件,项目直接可复用-小记

73 阅读2分钟
    <div class="modal">
      <div class="calendar">
        <div class="serivce_title">
          <span>预约体验</span>
          <span class="cancel_bottom" @click="cancelDate">取消</span>
        </div>
        <div class="date_day">
          <div class="date_week_day_list">
            <div class="date_mouth">
              <span :class="{active: choseMouthIndex == index}" v-for="item, index in getMonthsIncludingNextFour()" :key="index" @click="choseMouth(item, index)">{{ item.name }}</span>
            </div>
            <div class="date_week_list">
              <span v-for="(item, key) in Array.from(week_list)" :key="key">{{ item[1] }}</span>
            </div>
            <div class="date_day_list">
              <p v-for="(item, key) in date_day_list" :key="key">
                <span
                  :class="[
                    item.is_chose
                      ? choseDay == item.day
                        ? 'choseDay'
                        : item.is_today
                          ? 'today'
                          : 'chose'
                      : 'grey'
                  ]"
                  @click="choseDateDay(item)"
                  >{{ item.day }}</span
                >
              </p>
            </div>
          </div>
          <div class="date_time_list">
            <slot name="timeList"></slot>
          </div>
        </div>
      </div>
    </div>
  </template>

  export default {
    name: 'calendar',
    props: {
      choseDate: {
        type: String,
        default: ''
      }
    },
    data() {
        return {
            year: '',
            mouth: '',
            choseDay: '',
            week: '',
            nowMouth: '',
            nowDay: '',
            selectedDate: '',
            week_list: '',
            date_day: '',
            date_day_list: '',
            firstDayWeek: '',
            lastMouthDay: '',
            choseMouthIndex: '0'
        }
    },
    created() {
        this.year = this.choseDate.split('-')[0]
        this.mouth = this.choseDate.split('-')[1]
        this.choseDay = this.choseDate.split('-')[2]
        this.week = new Date(this.choseDate).getDay()
        this.nowMouth = new Date().getMonth() + 1 > 9 ? new Date().getMonth() + 1 : '0' + (new Date().getMonth() + 1)
        this.nowDay = new Date().getDate()
        this.week_list = new Map([
          [0, '日'],
          [1, '一'],
          [2, '二'],
          [3, '三'],
          [4, '四'],
          [5, '五'],
          [6, '六']
        ])
    },
    methods: {
        // 获取输入月份的天数
        getMonthDay (mouth) {
            this.date_day = new Date(this.year, mouth, 0).getDate()
            this.firstDayWeek = new Date(this.year, mouth - 1, 1).getDay()
            this.date_day_list = Array.from({ length: this.date_day }, (v, i) => {
            return {
                day: i + 1,
                week: this.week,
                is_today: mouth < this.nowMouth ? false : i + 1 === new Date().getDate(),
                is_chose: new Date(this.year, mouth - 1, i + 1, 23, 59, 59) >= new Date(),
                date: `${this.year}-${mouth}-${i + 1 > 9 ? i + 1 : '0' + (i + 1)}`,
                zhDate: this.getLunarDate(`${this.year}-${mouth}-${i + 1 > 9 ? i + 1 : '0' + (i + 1)}`)
            }
            })
            for (let i = 0; i < this.firstDayWeek; i++) {
                this.date_day_list.unshift({
                    day: '',
                    week: '',
                    is_today: false
                })
            }
        },
        // 下个月
        nextMonth () {
            if (this.mouth === 12) {
            this.mouth = 1
            this.year = this.year + 1
            } else {
            this.mouth = Number(this.mouth) + 1 < 10 ? '0' + (Number(this.mouth) + 1) : Number(this.mouth) + 1
            }
        },
        choseDateDay (item) {
            if (item.is_chose) {
              this.choseDay = item.day
              this.selectedDate = item.date
              this.$emit('choseDateDay', this.selectedDate || this.choseDate)
            }
        },
        // 切换月份
        choseMouth (item, index) {
            this.mouth = item.mouth.slice(0, -1)
            this.year = item.year
            this.choseMouthIndex = index
        },
        cancelDate () {
          this.$emit('cancelDate')
        },
        confirmDate () {
          this.$emit('confirmDate', this.selectedDate || this.choseDate)
        },
        getMonthsIncludingNextFour() {
          const now = new Date();
          const currentYear = now.getFullYear();
          const currentMonth = now.getMonth() + 1; // Months are zero-indexed in JavaScript
          const monthNames = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
          const months = []
      
          for (let i = 0; i < 4; i++) {
              let nextMonth = currentMonth + i;
              let nextYear = currentYear;
      
              if (nextMonth > 12) {
                  nextMonth -= 12;
                  nextYear += 1;
              }
      
              const monthString = (nextYear === currentYear || i === 0) ? {mouth:monthNames[nextMonth - 1], year: currentYear, name: monthNames[nextMonth - 1]} : monthNames[nextMonth - 1] == '1月' ? {name:`${nextYear}年${monthNames[nextMonth - 1]}`, year:  nextYear, mouth: monthNames[nextMonth - 1]} : {mouth:monthNames[nextMonth - 1], year: nextYear, name: monthNames[nextMonth - 1]};
              months.push(monthString);
          }
      
          return months;
        },
        // 判断时间段是否在区间
        isCurrentDateAfterFixedDate(fixedDateStr) {
            // 获取当前日期和时间
            const now = new Date();

            // 将日期字符串转换为 Date 对象
            // 注意:这里假设日期字符串是有效的,并且符合 ISO 8601 格式或 JavaScript 能够解析的其他格式
            const fixedDate = new Date(fixedDateStr);

            // 检查转换是否有效(即日期字符串是否被正确解析)
            if (isNaN(fixedDate.getTime())) {
                throw new Error('Invalid date string');
            }

            // 比较两个日期
            return now < fixedDate;
        },
        // 获取农历
        getLunarDate(zhdate) {
          const date = new Date(zhdate);
          const formatter = new Intl.DateTimeFormat('zh-Hans-CN', {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            calendar: 'chinese' // 注意:并非所有环境都支持 'chinese' 日历
          });

          const parts = formatter.formatToParts(date);
          let day = null;

          parts.forEach(({ type, value }) => {
            if (type === 'day') {
              day = parseInt(value, 10);
            }
          });
          return day;
        },
    },
    watch: {
        choseDate: {
            handler(newValue) {
                this.week = new Date(`${this.year}-${this.mouth}-${newValue}`).getDay()
            }
        },
        mouth: {
            handler(newValue) {
                this.getMonthDay(newValue)
            }
        }
    }
}
  </script>
  .modal {
    width: 100%;
    height: 100%;
    position: fixed;
    top: 0;
    left: 0;
    background: rgba(0, 0, 0, 0.3);
    z-index: 999;
  }
  .calendar {
    width: 100%;
    min-height: 170px;
    background: #F5F6F6;
    border-radius: 20px;
    overflow: hidden;
    position: absolute;
    bottom: 0;
    left: 0;
    display: flex;
    flex-direction: column;
  }
  .date_week_day_list {
    width: calc(100% - 24px);
    background-color: #fff;
    border-radius: 6px;
    margin: 0 auto;
    padding-bottom: 6px;
  }
  .date_week_list {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
  }
  .date_week_list > span {
    display: flex;
    justify-content: center;
    align-items: center;
    font-weight: 500;
    font-size: 12px;
    color: #AEB0B7;
    line-height: 17px;
  }
  .date_day {
    max-height: 70vh;
    overflow-y: auto;
  }
  .date_day_list {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
  }
  .date_day_list > p {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 6px 0;
    font-weight: 500;
    font-size: 14px;
    color: #1F2023;
  }
  .date_day_list p span {
    display: flex;
    width: 40px;
    height: 40px;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .date_day_list span.grey {
    color: #c5c5c5;
  }
  .date_day_list span.chose {
    color: #212121;
  }
  .date_day_list span.choseDay {
    background: #7E98F2;
    border-radius: 6px;
    color: #fff;
  }
  .date_day_list span.today {
    border: 2px solid #7E98F2;
    border-radius: 50%;
    color: #212121;
    box-sizing: border-box;
  }
  .serivce_title {
    width: 100%;
    height: 56px;
    background: linear-gradient( 114deg, #EEF7FF 0%, #F6F1F5 80%, #FFE9E7 100%);
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
  }
  .serivce_title .cancel_bottom {
    position: absolute;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 14px;
    color: #5E7ADE;
    line-height: 20px;
  }
  .date_mouth {
    height: 74px;
    padding: 0 12px;
    font-size: 14px;
    color: #54575D;
    line-height: 20px;
    display: flex;
    align-items: center;
  }
  .date_mouth > span {
    margin-right: 24px;
  }
  .date_mouth .active {
    font-weight: 600;
    font-size: 16px;
    color: #5E7ADE;
    position: relative;
  }
  .date_mouth .active::after {
    content: '';
    display: inline-block;
    width: 24px;
    height: 4px;
    background-color: #7E98F2;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: -8px;
    border-bottom-left-radius: 3px;
    border-top-right-radius: 3px;
  }
  .date_time_list {
    width: calc(100% - 24px);
    background-color: #fff;
    border-radius: 6px;
    margin: 0 auto;
    padding-bottom: 6px;
    margin-top: 12px;
  }
  </style>