vue/uni-app之空手撕日曆

前言

項目須要,沒有合適的輪子,因此,,,css

效果(紅圈部分): html

需求

  • 日期對應星期顯示
  • 可選的默認選中日期
  • 今天包括今天以前禁用
  • 某產品的最短預約時間內禁用,如提早三天,則今天明天后天禁用
  • 日期下面顯示對應的價格
  • 簡易版:若是要當成徹底的日曆,還需完善

思路

  • 傳入年份,月份建立日曆;日期價格數組;日期禁用數組web

  • 建立一個二維數組,第一維存放一月的週數,第二維存放一週的天數;一維的週數最多有6個數組數組

  • 每一個日期包含年月日三個屬性,價格對應的日期也是年月日,禁用的日期頁按照年月日,因此匹配一下就能夠了函數

  • 每個月天數使用 js 原生判斷,即:
    js 中new Date('2019/06/21')傳入一個日期,能夠用來判斷某個月是否有某一天。
    沒有的話,返回下一個月一號的構造函數;有的話返回這一天的構造函數;超出31號的則是Invalid。flex

    // 根據傳入的日期構造函數獲取年月日
      getYMD(time = new Date()) {
        return {
          _year: time.getFullYear(),
          _month: time.getMonth() + 1,
          _date: time.getDate(),
        }
      },
      // 驗證日期是否有效:對應的月份是否有這一天
      validDate(year, month, date) {
        let time = new Date(`${year}/${month}/${date}`);
        const { _year, _month, _date } = this.getYMD(time);
        
        return _year === year && month === _month && date === _date
      },
    複製代碼

    生成當月天數按周分割的數組ui

    // 生成一個月的天數
      generateCalendar(year, month) {
        let that = this;
        let weekLen = new Array(7).fill(''); // 一週七天
        let weekday = new Date(`${year}/${month}/01`).getDay(); // 1號星期幾
        
        // 重置
        this.monthDay = [[], [], [], [], [], []];
        
        // 生成一月對齊星期的天數,一週以週日開始
        weekLen.map((item, index) => {
          that.monthDay[0].push(
            index < weekday 
            ? '' 
            : (index === weekday) 
              ? {year, month, date: 1}
              : {year, month, date: that.monthDay[0][index-1].date+1}
          );
          that.monthDay[1].push({year, month, date: index + (7 - weekday + 1)});
          that.monthDay[2].push({year, month, date: that.monthDay[1][index].date + 7});
          that.monthDay[3].push({year, month, date: that.monthDay[2][index].date + 7});
          if (
            that.monthDay[3][index].date + 7 <= 31 && 
            that.validDate(year, month, that.monthDay[3][index].date + 7)
          ) {
            that.monthDay[4].push({year, month, date: that.monthDay[3][index].date + 7});
          } else {
            that.monthDay[4].push('');
          }
          if (
            that.monthDay[4][index].date + 7 <= 31 && 
            that.validDate(year, month, that.monthDay[4][index].date + 7)
          ) {
            that.monthDay[5].push({year, month, date: that.monthDay[4][index].date + 7});
          }
        })
      }
    複製代碼

遇到的問題:

  • 這裏在安卓App上面(webview),new Date('2019/06/21')的參數須要用斜槓'/',用短橫線new Date('2019-06-21')的話會是 Invalid Date,貌似IOS也是隻能使用'/'來獲取,兼容性上'/'最靠譜了。。。

代碼

父組件使用this

<cus-calendar class="calendar" :year="monthActive.year" :month="monthActive.month" :dateActiveDefault="activeDate" :saleList="saleList" :disabledDate="disabledDate" @check-date="checkDate" />
複製代碼

其餘:spa

this.disabledDate = ['2019-06-21','2019-06-22','2019-06-23'];
this.saleList = [
    { date: '2019-06-24', price: 6188 },
    { date: '2019-06-25', price: 6188 },
    { date: '2019-06-26', price: 6188 },
    { date: '2019-07-26', price: 6188 },
    { date: '2019-06-27', price: 6188 },
    { date: '2019-06-28', price: 6188 },
  ];
複製代碼

template

<template>
  <view class="calendar-container">
    <view class="month-bg">
      {{ month }}
    </view>
    <view class="week-title">
      <text class="weekend"></text>
      <text></text>
      <text></text>
      <text></text>
      <text></text>
      <text></text>
      <text class="weekend"></text>
    </view>
    
    <view class="calendar-content">
      <view v-for="(week, weekindex) in monthDay" :key="weekindex" class="week-month" >
        <view class="date" v-for="(date, dateindex) in week" :key="dateindex" :class="{'date-disabled': !date || beforeToday(date) || disabledDateFn(date)}" @tap="dateTap(date)" >
          <view class="date-item" :class="{ 'date-active': showPrice(date) && isActiveDate(date), 'date-active date-active2': !showPrice(date) && isActiveDate(date) }" >
            <text v-if="isToday(date)">今天</text>
            <text v-else>{{ date.date }}</text>
            <view class="price" v-if="showPrice(date)">¥{{ showPrice(date) }}</view>						
          </view>
        </view>
      </view>
    </view>
  </view>
</template>
複製代碼

js

// <script>
  // 旅遊產品 - 日曆
  export default {
    name: 'cus-calendar',
    props: {
      theme: {
        type: String,
        default: '#F87D72'
      },
      // 日期下面的價格
      saleList: {
        type: Array,
        default: () => []
      },
      // 禁用的日期
      disabledDate: {
        type: Array,
        default: () => []
      },
      // 默認選中的日期, 如2019-06-24
      dateActiveDefault: {
        type: String,
        default: ''
      },
      year: {
        type: Number,
        default: () => new Date().getFullYear()
      },
      month: {
        type: Number,
        default: () => new Date().getMonth()+1
      }
    },
    data() {
      return {
        newYear: '',
        newMonth: '',
        monthDay: [[], [], [], [], [], []],
        dateActive: {
          year: '',
          month: '',
          date: ''
        }
      }
    },
    mounted() {
      // 默認選中
      if (this.dateActiveDefault) {
        const [year, month, date] = this.dateActiveDefault.split('-');
        this.dateActive = {
          year: +year,
          month: +month,
          date: +date
        }
      }
    },
    watch: {
      month: {
        handler(val) {
          this.newYear = this.year;
          this.newMonth = this.month;
          this.generateCalendar(this.newYear, this.newMonth);
        },
        immediate: true
      }
    },
    methods: {
      // 根據傳入的日期構造函數獲取年月日
      getYMD(time = new Date()) {
        return {
          _year: time.getFullYear(),
          _month: time.getMonth() + 1,
          _date: time.getDate(),
        }
      },
      // 驗證日期是否有效:對應的月份是否有這一天
      validDate(year, month, date) {
        let time = new Date(`${year}/${month}/${date}`);
        const { _year, _month, _date } = this.getYMD(time);
        
        return _year === year && month === _month && date === _date
      },
      // 是否今天
      isToday({ year, month, date }) {
        let time = new Date();
        const { _year, _month, _date } = this.getYMD(time);
        
        return year === _year && month === _month && date === _date
      },
      // 今天以前
      beforeToday({ year, month, date }) {
        let time = new Date();
        const { _year, _month, _date } = this.getYMD(time);
        
        return year <= _year && month <= _month && date <= _date
      },
      // 禁用的日期
      disabledDateFn({ year, month, date }) {
        month = (month+'').padStart(2, '0');
        date = (date+'').padStart(2, '0');
        
        return this.disabledDate.includes(`${year}-${month}-${date}`);
      },
      // 是否選中
      isActiveDate({ year, month, date }) {
        const { year: _year, month: _month, date: _date } = this.dateActive;
        return year === _year && month === _month && date === _date;
      },
      // 點擊有效的一天
      dateTap({ year, month, date }) {
        this.dateActive = {
          year,
          month,
          date
        };
        this.$emit('check-date', this.dateActive);
      },
      // 日期下面顯示價格
      showPrice({ year, month, date }) {
        if (!year) return;
        
        month = (month+'').padStart(2, '0');
        date = (date+'').padStart(2, '0');
        
        let obj = this.saleList.find(item => item.date === `${year}-${month}-${date}`);
        return obj && obj.price
      },
      // 生成一個月的天數
      generateCalendar(year, month) {
        let that = this;
        let weekLen = new Array(7).fill(''); // 一週七天
        let weekday = new Date(`${year}/${month}/01`).getDay(); // 1號星期幾
        
        // 重置
        that.monthDay = [[], [], [], [], [], []];
        
        // 生成一月對齊星期的天數,一週以週日開始
        weekLen.map((item, index) => {
          that.monthDay[0].push(
            index < weekday 
            ? '' 
            : (index === weekday) 
              ? {year, month, date: 1}
              : {year, month, date: that.monthDay[0][index-1].date+1}
          );
          that.monthDay[1].push({year, month, date: index + (7 - weekday + 1)});
          that.monthDay[2].push({year, month, date: that.monthDay[1][index].date + 7});
          that.monthDay[3].push({year, month, date: that.monthDay[2][index].date + 7});
          if (
            that.monthDay[3][index].date + 7 <= 31 && 
            that.validDate(year, month, that.monthDay[3][index].date + 7)
          ) {
            that.monthDay[4].push({year, month, date: that.monthDay[3][index].date + 7});
          } else {
            that.monthDay[4].push('');
          }
          if (
            that.monthDay[4][index].date + 7 <= 31 && 
            that.validDate(year, month, that.monthDay[4][index].date + 7)
          ) {
            that.monthDay[5].push({year, month, date: that.monthDay[4][index].date + 7});
          }
        })
      }
    }
  }
// </script>
複製代碼

樣式

/* <style lang="scss" scoped> */
  .calendar-container {
    width: 100%;
    position: relative;
    color: #999;
  }
  .month-bg {
    position: absolute;
    top: 50%;
    left: 50%;
    color: #f6f6f6;
    font-size: 60px;
    transform: translate(-50%, -50%);
    z-index: -1;
  }
  .week-title {
    padding: 20upx 40upx;
    display: flex;
    justify-content: space-between;
    &>text {
      flex: 1;
      text-align: center;
    }
  }
  .weekend {
    color: #F87D72;
  }
  .week-month {
    display: flex;
    justify-content: flex-start;
    padding: 20upx 40upx;
    color: #2b2b2b;
    &>.date {
      flex: 14.285% 0 0;
      text-align: center;
    }
  }
  .date-item {
    width: 60upx;
    height: 60upx;
    position: relative;
    left: 50%;
    margin-left: -30upx;
    line-height: 1;
  }
  .date-disabled {
    color: #999;
    pointer-events: none;
  }
  .price {
    color: #F87D72;
    font-size: 18upx;
  }
  .date-active {
    color: #fff;
    &::after {
      content: '';
      width: 140%;
      height: 140%;
      display: block;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      border-radius: 50px;
      background: linear-gradient(right, #F87D72, #F29E97);
      box-shadow: 0 6upx 16upx -6upx #F97C71;
      z-index: -1;
    }
    &>.price {
      color: #fff;
    }
  }
  .date-active2::after {
    transform: translate(-50%, -68%);
  }
/* </style> */
複製代碼
相關文章
相關標籤/搜索