Browse Source

feat: datepicker 初始化开发

songchenglin3 5 years ago
parent
commit
e03a6d912f

+ 1 - 1
package.json

@@ -117,4 +117,4 @@
     "last 2 versions",
     "not dead"
   ]
-}
+}

+ 10 - 0
src/config.js

@@ -279,6 +279,16 @@ module.exports = {
           show: true,
           desc: '评分组件',
           author: 'undo'
+        },
+        {
+          version: '3.0.0',
+          name: 'Calendar',
+          type: 'component',
+          cName: '日历',
+          desc: '日历组件',
+          sort: 5,
+          show: true,
+          author: ''
         }
       ]
     },

+ 137 - 0
src/packages/calendar/demo.vue

@@ -0,0 +1,137 @@
+<template>
+  <div class="demo">
+    <h2>基础用法</h2>
+    <div>
+      <nut-cell
+        :showIcon="true"
+        title="选择单个日期"
+        :desc="date ? `${date} ${dateWeek}` : '请选择'"
+        @click.native="openSwitch('isVisible')"
+      >
+      </nut-cell
+      ><div class="test-calendar-wrapper">
+        <nut-calendar
+          :is-visible="isVisible"
+          :default-value="date"
+          @close="closeSwitch('isVisible')"
+          @choose="setChooseValue"
+          :start-date="`2019-10-11`"
+          :end-date="`2022-11-11`"
+        >
+        </nut-calendar
+      ></div>
+    </div>
+
+    <div>
+      <nut-cell
+        :showIcon="true"
+        title="选择日期区间"
+        :desc="date1 ? `${date1[0]}至${date1[1]}` : '请选择'"
+        @click.native="openSwitch('isVisible1')"
+      >
+      </nut-cell
+      ><div class="test-calendar-wrapper">
+        <nut-calendar
+          :is-visible="isVisible1"
+          :default-value="date1"
+          type="range"
+          :start-date="`2019-12-22`"
+          :end-date="`2021-01-08`"
+          @close="closeSwitch('isVisible1')"
+          @choose="setChooseValue1"
+        >
+        </nut-calendar
+      ></div>
+    </div>
+    <h2>自定义日历</h2>
+    <h2>平铺展示</h2>
+    <div class="test-calendar-wrapper">
+      <nut-calendar
+        :poppable="false"
+        :is-visible="isVisible2"
+        :default-value="date2"
+        @choose="setChooseValue2"
+      >
+      </nut-calendar>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+import { createComponent } from '@/utils/create';
+
+const { createDemo } = createComponent('calendar');
+import Utils from '@/utils/date';
+
+interface TestCalendarState {
+  isVisible: boolean;
+  date: string;
+  dateWeek: string;
+
+  date2: string;
+  isVisible2: boolean;
+
+  isVisible1: boolean;
+  date1: string[];
+}
+export default createDemo({
+  props: {},
+  setup() {
+    const state: TestCalendarState = reactive({
+      isVisible: false,
+      date: '',
+      dateWeek: '',
+
+      date2: '2020-07-08',
+      isVisible2: true,
+
+      isVisible1: false,
+      date1: ['2019-12-23', '2019-12-26']
+    });
+    const openSwitch = param => {
+      state[`${param}`] = true;
+    };
+
+    const closeSwitch = param => {
+      state[`${param}`] = false;
+    };
+
+    const setChooseValue = param => {
+      state.date = param[3];
+      state.dateWeek = param[4];
+    };
+
+    const setChooseValue2 = param => {
+      state.date2 = param[3];
+      console.log(state.date2);
+    };
+
+    const setChooseValue1 = param => {
+      state.date1 = [...[param[0][3], param[1][3]]];
+    };
+
+    setTimeout(() => {
+      state.date = '2021-01-03';
+    }, 3000);
+
+    return {
+      ...toRefs(state),
+      openSwitch,
+      closeSwitch,
+      setChooseValue,
+      setChooseValue2,
+      setChooseValue1
+    };
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.test-calendar-wrapper {
+  display: flex;
+  width: 100%;
+  height: 613px;
+  overflow: hidden;
+}
+</style>

+ 34 - 0
src/packages/calendar/doc.md

@@ -0,0 +1,34 @@
+#  calendar组件
+
+    ### 介绍
+    
+    基于 xxxxxxx
+    
+    ### 安装
+    
+    
+    
+    ## 代码演示
+    
+    ### 基础用法1
+    
+
+    
+    ## API
+    
+    ### Props
+    
+    | 参数         | 说明                             | 类型   | 默认值           |
+    |--------------|----------------------------------|--------|------------------|
+    | name         | 图标名称或图片链接               | String | -                |
+    | color        | 图标颜色                         | String | -                |
+    | size         | 图标大小,如 '20px' '2em' '2rem' | String | -                |
+    | class-prefix | 类名前缀,用于使用自定义图标     | String | 'nutui-iconfont' |
+    | tag          | HTML 标签                        | String | 'i'              |
+    
+    ### Events
+    
+    | 事件名 | 说明           | 回调参数     |
+    |--------|----------------|--------------|
+    | click  | 点击图标时触发 | event: Event |
+    

+ 188 - 0
src/packages/calendar/index.scss

@@ -0,0 +1,188 @@
+.nut-calendar {
+  position: relative;
+  display: flex;
+  flex: 1;
+  height: 100%;
+  padding-top: 132px;
+  padding-bottom: 78px;
+  color: $calendar-base-color;
+  font-size: $calendar-base-font;
+  background-color: $white;
+  overflow: hidden;
+
+  &.nut-calendar-tile {
+    padding-top: 46px;
+    padding-bottom: 0;
+
+    .nut-calendar-header {
+      .calendar-title {
+        font-size: $calendar-base-font;
+      }
+    }
+  }
+
+  // 头部导航
+  .nut-calendar-header {
+    position: absolute;
+    top: -1px;
+    left: 0;
+    right: 0;
+    display: flex;
+    flex-direction: column;
+    text-align: center;
+    padding-top: 1px;
+    background-color: $white;
+    z-index: 1;
+
+    .calendar-title {
+      padding-top: 22px;
+      font-size: $calendar-title-font;
+      line-height: 25px;
+      border-radius: 12px 12px 0 0;
+    }
+
+    .calendar-curr-month {
+      padding: 10px 0 7px;
+      line-height: 22px;
+    }
+
+    .calendar-weeks {
+      display: flex;
+      align-items: center;
+      justify-content: space-around;
+      height: 46px;
+      border-radius: 0px 0px 12px 12px;
+      box-shadow: 0px 4px 10px 0px rgba($color: #000000, $alpha: 0.06);
+
+      .calendar-week-item {
+        &:first-of-type,
+        &:last-of-type {
+          color: $calendar-primary-color;
+        }
+      }
+    }
+  }
+
+  // 月份
+  .nut-calendar-content {
+    flex: 1;
+
+    .calendar-months-panel {
+      position: relative;
+      width: 100%;
+      height: auto;
+      display: block;
+
+      .calendar-month {
+        display: flex;
+        flex-direction: column;
+        text-align: center;
+      }
+
+      div:nth-of-type(2) {
+        .calendar-month-title {
+          padding-top: 0;
+        }
+      }
+
+      .calendar-loading-tip {
+        height: 50px;
+        line-height: 50px;
+        text-align: center;
+        position: absolute;
+        top: -50px;
+        left: 0;
+        right: 0;
+        font-size: $calendar-text-font;
+        color: $text-color;
+      }
+
+      .calendar-month-title {
+        height: 23px;
+        line-height: 23px;
+        margin: 8px 0;
+      }
+
+      .calendar-month-con {
+        overflow: hidden;
+
+        .calendar-month-item {
+          .calendar-month-day:nth-child(7n + 0),
+          .calendar-month-day:nth-child(7n + 1) {
+            color: $calendar-primary-color;
+          }
+        }
+
+        .calendar-month-day {
+          float: left;
+          width: 14.28%;
+          height: 64px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          flex-direction: column;
+          position: relative;
+
+          .curr-tips,
+          .calendar-day-tip {
+            position: absolute;
+            top: 10px;
+            width: 100%;
+            font-size: 11px;
+            line-height: 12px;
+            color: $calendar-primary-color;
+          }
+
+          &-active {
+            background-color: $calendar-primary-color;
+            color: $white !important;
+
+            .curr-tips {
+              visibility: hidden;
+            }
+
+            .calendar-day-tip {
+              color: $white;
+            }
+          }
+
+          &-disabled {
+            color: $calendar-disable-color !important;
+          }
+
+          &-choose {
+            background-color: $calendar-choose-color;
+          }
+
+          .calendar-day {
+            padding: 4px 0;
+            font-size: $calendar-day-font;
+          }
+        }
+      }
+    }
+  }
+
+  // 底部导航
+  .nut-calendar-footer {
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: -1px;
+    display: flex;
+    height: 78px;
+    width: 100%;
+    background-color: $white;
+
+    .calendar-confirm-btn {
+      height: 44px;
+      width: 100%;
+      margin: 14px 18px;
+      border-radius: 22px;
+      background: $button-primary-background-color;
+      color: $white;
+      text-align: center;
+      line-height: 44px;
+    }
+  }
+}

+ 633 - 0
src/packages/calendar/index.vue

@@ -0,0 +1,633 @@
+<template>
+  <view class="nut-calendar" :class="{ 'nut-calendar-tile': !poppable }">
+    <!-- header -->
+    <view
+      class="nut-calendar-header"
+      :class="{ 'nut-calendar-header-tile': !poppable }"
+    >
+      <template v-if="poppable">
+        <view class="calendar-title">{{ title }}</view>
+        <view class="calendar-curr-month">2020年11月</view>
+      </template>
+      <view class="calendar-weeks">
+        <view
+          class="calendar-week-item"
+          v-for="(item, index) of weeks"
+          :key="index"
+          >{{ item }}</view
+        >
+      </view>
+    </view>
+    <!-- content-->
+    <view
+      class="nut-calendar-content"
+      ref="months"
+      @touchstart.stop="touchStart"
+      @touchmove.stop.prevent="touchMove"
+      @touchend.stop="touchEnd"
+    >
+      <view class="calendar-months-panel" ref="monthsPanel">
+        <view class="calendar-loading-tip">{{
+          !unLoadPrev ? '加载上一个月' : '没有更早月份'
+        }}</view>
+        <view
+          class="calendar-month"
+          v-for="(month, index) of monthsData"
+          :key="index"
+        >
+          <view class="calendar-month-title">{{ month.title }}</view>
+          <view class="calendar-month-con">
+            <view
+              class="calendar-month-item"
+              :class="type === 'range' ? 'month-item-range' : ''"
+            >
+              <template v-for="(day, i) of month.monthData" :key="i">
+                <view
+                  class="calendar-month-day"
+                  :class="getClass(day, month)"
+                  @click="chooseDay(day, month)"
+                >
+                  <view class="calendar-day">{{
+                    day.type == 'curr' ? day.day : ''
+                  }}</view>
+                  <view class="curr-tips" v-if="isCurrDay(month, day.day)"
+                    >今天</view
+                  >
+                  <view
+                    class="calendar-day-tip"
+                    v-if="isStartTip(day, month)"
+                    >{{ '开始' }}</view
+                  >
+                  <view
+                    class="calendar-day-tip"
+                    v-else-if="isEndTip(day, month)"
+                    >{{ '结束' }}</view
+                  >
+                </view>
+              </template>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <!-- footer-->
+    <view class="nut-calendar-footer" v-if="poppable">
+      <view class="calendar-confirm-btn" @click="confirm">确定</view>
+    </view>
+  </view>
+</template>
+<script lang="ts">
+// import {
+//   PropType,
+//   computed,
+//   watch,
+//   reactive,
+//   ref,
+//   toRefs,
+//   readonly
+// } from 'vue';
+import {
+  PropType,
+  computed,
+  reactive,
+  ref,
+  watch,
+  toRefs,
+  onMounted
+} from 'vue';
+import { createComponent } from '@/utils/create';
+const { componentName, create } = createComponent('calendar');
+import Utils from '@/utils/date';
+type InputDate = string | string[];
+interface CalendarState {
+  childIsVisible: boolean;
+  currDate: InputDate;
+  unLoadPrev: boolean;
+  touchParams: any;
+  transformY: number;
+  translateY: number;
+  scrollDistance: number;
+  defaultData: InputDate;
+  chooseData: any;
+  monthsData: any[];
+  dayPrefix: string;
+  startData: InputDate;
+  endData: InputDate;
+  isRange: boolean;
+}
+
+export default create({
+  props: {
+    type: {
+      type: String,
+      default: 'one'
+    },
+    isAutoBackFill: {
+      type: Boolean,
+      default: false
+    },
+    poppable: {
+      type: Boolean,
+      default: true
+    },
+    title: {
+      type: String,
+      default: '日历选择'
+    },
+    defaultValue: {
+      type: String as PropType<InputDate>,
+      default: null
+    },
+    startDate: {
+      type: String,
+      default: Utils.getDay(0)
+    },
+    endDate: {
+      type: String,
+      default: Utils.getDay(365)
+    }
+  },
+  components: {},
+  emits: ['choose', 'update', 'close'],
+
+  setup(props, { emit, slots }) {
+    const weeks = ref(['日', '一', '二', '三', '四', '五', '六']);
+    // element refs
+    const months = ref<null | HTMLElement>(null);
+    const monthsPanel = ref<null | HTMLElement>(null);
+    // state
+    let state: CalendarState = reactive({
+      childIsVisible: false,
+      currDate: '',
+      unLoadPrev: false,
+      touchParams: {
+        startY: 0,
+        endY: 0,
+        startTime: 0,
+        endTime: 0,
+        lastY: 0,
+        lastTime: 0
+      },
+      transformY: 0,
+      translateY: 0,
+      scrollDistance: 0,
+      defaultData: [],
+      chooseData: [],
+      monthsData: [],
+      dayPrefix: 'calendar-month-day',
+      startData: '',
+      endData: '',
+      isRange: props.type === 'range'
+    });
+
+    // 日期转化成数组
+    const splitDate = date => {
+      return date.split('-');
+    };
+
+    const isStart = currDate => {
+      return Utils.isEqual(state.currDate[0], currDate);
+    };
+
+    const isEnd = currDate => {
+      return Utils.isEqual(state.currDate[1], currDate);
+    };
+
+    // 获取当前数据
+    const getCurrDate = (day, month, isRange?) => {
+      return isRange
+        ? month.curData[3] +
+            '-' +
+            month.curData[4] +
+            '-' +
+            Utils.getNumTwoBit(day.day)
+        : month.curData[0] +
+            '-' +
+            month.curData[1] +
+            '-' +
+            Utils.getNumTwoBit(day.day);
+    };
+
+    // 获取样式
+    const getClass = (day, month, isRange?) => {
+      let currDate = getCurrDate(day, month, isRange);
+      if (day.type == 'curr') {
+        if (
+          (!state.isRange && Utils.isEqual(state.currDate, currDate)) ||
+          (state.isRange && (isStart(currDate) || isEnd(currDate)))
+        ) {
+          return `${state.dayPrefix}-active`;
+        } else if (
+          (props.startDate && Utils.compareDate(currDate, props.startDate)) ||
+          (props.endDate && Utils.compareDate(props.endDate, currDate))
+        ) {
+          return `${state.dayPrefix}-disabled`;
+        } else if (
+          state.isRange &&
+          Array.isArray(state.currDate) &&
+          Object.values(state.currDate).length == 2 &&
+          Utils.compareDate(state.currDate[0], currDate) &&
+          Utils.compareDate(currDate, state.currDate[1])
+        ) {
+          return `${state.dayPrefix}-choose`;
+        } else {
+          return null;
+        }
+      } else {
+        return `${state.dayPrefix}-disabled`;
+      }
+    };
+
+    // 选中数据
+    const chooseDay = (day, month, isFirst, isRange?) => {
+      if (getClass(day, month, isRange) != `${state.dayPrefix}-disabled`) {
+        let days = [...month.curData];
+        days = isRange ? days.splice(3) : days.splice(0, 3);
+        days[2] =
+          typeof day.day == 'number' ? Utils.getNumTwoBit(day.day) : day.day;
+        days[3] = `${days[0]}-${days[1]}-${days[2]}`;
+        days[4] = Utils.getWhatDay(days[0], days[1], days[2]);
+        if (!state.isRange) {
+          state.currDate = days[3];
+          state.chooseData = [...days];
+        } else {
+          if (Object.values(state.currDate).length == 2) {
+            state.currDate = [days[3]];
+          } else {
+            if (Utils.compareDate(state.currDate[0], days[3])) {
+              Array.isArray(state.currDate) && state.currDate.push(days[3]);
+            } else {
+              Array.isArray(state.currDate) && state.currDate.unshift(days[3]);
+            }
+          }
+          if (state.chooseData.length == 2 || !state.chooseData.length) {
+            state.chooseData = [...days];
+          } else {
+            if (Utils.compareDate(state.chooseData[3], days[3])) {
+              state.chooseData = [[...state.chooseData], [...days]];
+            } else {
+              state.chooseData = [[...days], [...state.chooseData]];
+            }
+          }
+        }
+
+        if (props.isAutoBackFill && !isFirst) {
+          confirm();
+        }
+      }
+    };
+
+    // 获取当前月数据
+    const getCurrData = type => {
+      let monthData =
+        type == 'prev'
+          ? state.monthsData[0]
+          : state.monthsData[state.monthsData.length - 1];
+      let year = parseInt(monthData.curData[0]);
+      let month = parseInt(monthData.curData[1].toString().replace(/^0/, ''));
+      switch (type) {
+        case 'prev':
+          month == 1 && (year -= 1);
+          month = month == 1 ? 12 : --month;
+          break;
+        case 'next':
+          month == 12 && (year += 1);
+          month = month == 12 ? 1 : ++month;
+          break;
+      }
+      return [year, Utils.getNumTwoBit(month), monthData.curData[2]];
+    };
+
+    // 获取日期状态
+    const getDaysStatus = (days, type) => {
+      // 修复:当某个月的1号是周日时,月份下方会空出来一行
+      if (type == 'prev' && days >= 7) {
+        days -= 7;
+      }
+      return Array.from(Array(days), (v, k) => {
+        return {
+          day: k + 1,
+          type: type
+        };
+      });
+    };
+
+    // 获取月数据
+    const getMonth = (curData, type) => {
+      const preMonthDays = Utils.getMonthPreDay(curData[0], curData[1]);
+      const currMonthDays = Utils.getMonthDays(curData[0], curData[1]);
+      const nextMonthDays = 42 - preMonthDays - currMonthDays;
+      const title = {
+        year: curData[0],
+        month: curData[1]
+      };
+      const monthInfo = {
+        curData: curData,
+        title: `${title.year}年${title.month}月`,
+        monthData: [
+          ...getDaysStatus(preMonthDays, 'prev'),
+          ...getDaysStatus(currMonthDays, 'curr')
+        ]
+      };
+      if (type == 'next') {
+        if (
+          !state.endData ||
+          !Utils.compareDate(
+            `${state.endData[0]}-${state.endData[1]}-${Utils.getMonthDays(
+              state.endData[0],
+              state.endData[1]
+            )}`,
+            `${curData[0]}-${curData[1]}-${curData[2]}`
+          )
+        ) {
+          state.monthsData.push(monthInfo);
+        }
+      } else {
+        if (
+          !state.startData ||
+          !Utils.compareDate(
+            `${curData[0]}-${curData[1]}-${curData[2]}`,
+            `${state.startData[0]}-${state.startData[1]}-01`
+          )
+        ) {
+          state.monthsData.unshift(monthInfo);
+        } else {
+          state.unLoadPrev = true;
+        }
+      }
+    };
+
+    // 初始化数据
+    const initData = () => {
+      // 初始化开始结束数据
+      state.startData = props.startDate ? splitDate(props.startDate) : null;
+      state.endData = props.endDate ? splitDate(props.endDate) : null;
+
+      // 初始化当前日期
+      if (!props.defaultValue) {
+        state.currDate = state.isRange
+          ? [Utils.date2Str(new Date()), Utils.getDay(1)]
+          : Utils.date2Str(new Date());
+      } else {
+        state.currDate = state.isRange
+          ? [...props.defaultValue]
+          : props.defaultValue;
+      }
+
+      // 日期转化为数组
+      if (state.isRange && Array.isArray(state.currDate)) {
+        if (
+          props.startDate &&
+          Utils.compareDate(state.currDate[0], props.startDate)
+        ) {
+          state.currDate.splice(0, 1, props.startDate);
+        }
+        if (
+          props.endDate &&
+          Utils.compareDate(props.endDate, state.currDate[1])
+        ) {
+          state.currDate.splice(1, 1, props.endDate);
+        }
+        state.defaultData = [
+          ...splitDate(state.currDate[0]),
+          ...splitDate(state.currDate[1])
+        ];
+      } else {
+        if (
+          props.startDate &&
+          Utils.compareDate(state.currDate, props.startDate)
+        ) {
+          state.currDate = props.startDate;
+        } else if (
+          props.endDate &&
+          !Utils.compareDate(state.currDate, props.endDate)
+        ) {
+          state.currDate = props.endDate;
+        }
+
+        state.defaultData = [...splitDate(state.currDate)];
+      }
+
+      getMonth(state.defaultData, 'next');
+
+      let i = 1;
+      do {
+        getMonth(getCurrData('next'), 'next');
+      } while (i++ < 4);
+
+      if (state.isRange) {
+        chooseDay(
+          { day: state.defaultData[2], type: 'curr' },
+          state.monthsData[0],
+          true
+        );
+        chooseDay(
+          { day: state.defaultData[5], type: 'curr' },
+          state.monthsData[0],
+          true,
+          true
+        );
+      } else {
+        chooseDay(
+          { day: state.defaultData[2], type: 'curr' },
+          state.monthsData[0],
+          true
+        );
+      }
+      console.log(state.currDate, 'state.currDate');
+    };
+
+    // 区间选择&&当前月&&选中态
+    const isActive = (day, month) => {
+      return (
+        state.isRange &&
+        day.type == 'curr' &&
+        getClass(day, month) == 'calendar-month-day-active'
+      );
+    };
+
+    const isStartTip = (day, month) => {
+      if (isActive(day, month)) {
+        return isStart(getCurrDate(day, month));
+      } else {
+        return false;
+      }
+    };
+
+    const isEndTip = (day, month) => {
+      return isActive(day, month);
+    };
+
+    const isCurrDay = (month, day) => {
+      let date = `${month.curData[0]}-${month.curData[1]}-${day}`;
+      return Utils.isEqual(date, Utils.date2Str(new Date()));
+    };
+
+    const confirm = () => {
+      if ((state.isRange && state.chooseData.length == 2) || !state.isRange) {
+        emit('choose', state.chooseData);
+        if (props.poppable) {
+          state.childIsVisible = false;
+          emit('update');
+        }
+      }
+    };
+
+    const touchStart = event => {
+      let changedTouches = event.changedTouches[0];
+      state.touchParams.startY = changedTouches.pageY;
+      state.touchParams.startTime = event.timestamp || Date.now();
+      state.transformY = state.scrollDistance;
+    };
+
+    const touchMove = event => {
+      //event.preventDefault();
+      let changedTouches = event.changedTouches[0];
+      state.touchParams.lastY = changedTouches.pageY;
+      state.touchParams.lastTime = event.timestamp || Date.now();
+      let move = state.touchParams.lastY - state.touchParams.startY;
+      if (Math.abs(move) < 5) {
+        return false;
+      }
+      setMove(move);
+    };
+
+    const touchEnd = event => {
+      let changedTouches = event.changedTouches[0];
+      state.touchParams.lastY = changedTouches.pageY;
+      state.touchParams.lastTime = event.timestamp || Date.now();
+      let move = state.touchParams.lastY - state.touchParams.startY;
+      if (Math.abs(move) < 5) {
+        return false;
+      }
+      let updateMove = move + state.transformY;
+      let h = months.value?.offsetHeight || 0;
+      let offsetHeight = monthsPanel.value?.offsetHeight || 0;
+
+      if (updateMove > 0) {
+        getMonth(getCurrData('prev'), 'prev');
+      } else if (updateMove < -offsetHeight + h * 2) {
+        getMonth(getCurrData('next'), 'next');
+        if (Math.abs(move) >= 300) {
+          getMonth(getCurrData('next'), 'next');
+        }
+      }
+
+      let moveTime = state.touchParams.lastTime - state.touchParams.startTime;
+      if (moveTime <= 300) {
+        move = move * 2;
+        moveTime = moveTime + 1000;
+        setMove(move, 'end', moveTime);
+      } else {
+        setMove(move, 'end');
+      }
+    };
+
+    const setTransform = (translateY = 0, type?, time = 1000) => {
+      if (type === 'end') {
+        monthsPanel?.value &&
+          (monthsPanel.value.style.webkitTransition = `transform ${time}ms cubic-bezier(0.19, 1, 0.22, 1)`);
+      } else {
+        monthsPanel?.value && (monthsPanel.value.style.webkitTransition = '');
+      }
+
+      monthsPanel?.value &&
+        (monthsPanel.value.style.webkitTransform = `translateY(${translateY}px)`);
+      state.scrollDistance = translateY;
+    };
+
+    const setMove = (move, type?, time?) => {
+      let updateMove = move + state.transformY;
+      let h = months.value?.offsetHeight || 0;
+
+      let offsetHeight = monthsPanel.value?.offsetHeight || 0;
+
+      if (type === 'end') {
+        // 限定滚动距离
+        if (updateMove > 0) {
+          updateMove = 0;
+        }
+        if (updateMove < 0 && updateMove < -offsetHeight + h) {
+          updateMove = -offsetHeight + h;
+        }
+        if (offsetHeight <= h && state.monthsData.length == 1) {
+          updateMove = 0;
+        }
+        let endMove = updateMove;
+        setTransform(endMove, type, time);
+      } else {
+        if (updateMove > 0 && updateMove > 100) {
+          updateMove = 100;
+        }
+        if (
+          updateMove < -offsetHeight + h - 100 &&
+          state.monthsData.length > 1
+        ) {
+          updateMove = -offsetHeight + h - 100;
+        }
+        if (
+          updateMove < 0 &&
+          updateMove < -100 &&
+          state.monthsData.length == 1
+        ) {
+          updateMove = -100;
+        }
+        setTransform(updateMove);
+      }
+    };
+
+    const resetRender = () => {
+      state.chooseData.splice(0);
+      state.monthsData.splice(0);
+      state.scrollDistance = 0;
+      state.translateY = 0;
+      setTransform(state.scrollDistance);
+      initData();
+    };
+
+    const closeActionSheet = () => {
+      if (props.poppable) {
+        state.childIsVisible = false;
+        emit('update');
+        emit('close');
+      }
+      resetRender();
+    };
+
+    // 初始化
+    initData();
+
+    //监听 默认值更改
+    watch(
+      () => props.defaultValue,
+      (val, prevVal) => {
+        if (val) {
+          console.log(val, 'init');
+          resetRender();
+        }
+      }
+    );
+
+    return {
+      weeks,
+      touchStart,
+      touchMove,
+      touchEnd,
+      getClass,
+      isStartTip,
+      isEndTip,
+      chooseDay,
+      isCurrDay,
+      confirm,
+      monthsPanel,
+      months,
+      ...toRefs(state),
+      ...toRefs(props)
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+@import 'index.scss';
+</style>

+ 20 - 4
src/packages/cell/index.vue

@@ -1,7 +1,11 @@
 <template>
   <view :class="classes" @click="handleClick">
     <slot>
-      <view class="nut-cell__title" :class="{ icon: icon }" v-if="title || subTitle || icon">
+      <view
+        class="nut-cell__title"
+        :class="{ icon: icon }"
+        v-if="title || subTitle || icon"
+      >
         <nut-icon v-if="icon" class="icon" :name="icon"></nut-icon>
         <template v-if="subTitle">
           <view class="title">{{ title }}</view>
@@ -11,8 +15,18 @@
           {{ title }}
         </template>
       </view>
-      <view v-if="desc" class="nut-cell__value" :style="{ 'text-align': descTextAlign }">{{ desc }}</view>
-      <nut-icon v-if="isLink || to" size="14px" color="#979797" name="right"></nut-icon>
+      <view
+        v-if="desc"
+        class="nut-cell__value"
+        :style="{ 'text-align': descTextAlign }"
+        >{{ desc }}</view
+      >
+      <nut-icon
+        v-if="isLink || to"
+        size="14px"
+        color="#979797"
+        name="right"
+      ></nut-icon>
     </slot>
   </view>
 </template>
@@ -54,7 +68,9 @@ export default create({
       if (props.to && router) {
         router[props.replace ? 'replace' : 'push'](props.to);
       } else if (props.url) {
-        props.replace ? location.replace(props.url) : (location.href = props.url);
+        props.replace
+          ? location.replace(props.url)
+          : (location.href = props.url);
       }
     };
 

+ 10 - 0
src/styles/variables.scss

@@ -109,3 +109,13 @@ $inputnumber-input-background-color: $help-color;
 $inputnumber-input-border-radius: 8px;
 $inputnumber-input-width: 40px;
 $inputnumber-input-height: 20px;
+
+// calendar
+$calendar-primary-color: $primary-color;
+$calendar-choose-color: #fef6f6;
+$calendar-base-color: #333333;
+$calendar-disable-color: #d1d0d0;
+$calendar-title-font: $font-size-4;
+$calendar-base-font: $font-size-3;
+$calendar-text-font: $font-size-1;
+$calendar-day-font: 18px;

+ 159 - 0
src/utils/date.ts

@@ -0,0 +1,159 @@
+const Utils = {
+  /**
+   * 是否为闫年
+   * @return {Boolse} true|false
+   */
+  isLeapYear: function(y: number) {
+    return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
+  },
+
+  /**
+   * 返回星期数
+   * @return {Number}
+   */
+  getWhatDay: function(year, month, day) {
+    const date = new Date(year + '/' + month + '/' + day);
+    const index = date.getDay();
+    const dayNames = [
+      '星期日',
+      '星期一',
+      '星期二',
+      '星期三',
+      '星期四',
+      '星期五',
+      '星期六'
+    ];
+    return dayNames[index];
+  },
+
+  /**
+   * 返回星期数
+   * @return {Number}
+   */
+  getMonthPreDay: function(year, month) {
+    const date = new Date(year + '/' + month + '/01');
+    let day = date.getDay();
+    if (day == 0) {
+      day = 7;
+    }
+    return day;
+  },
+
+  /**
+   * 返回月份天数
+   * @return {Number}
+   */
+  getMonthDays: function(year, month) {
+    if (/^0/.test(month)) {
+      month = month.split('')[1];
+    }
+    return [
+      0,
+      31,
+      this.isLeapYear(year) ? 29 : 28,
+      31,
+      30,
+      31,
+      30,
+      31,
+      31,
+      30,
+      31,
+      30,
+      31
+    ][month];
+  },
+
+  /**
+   * 补齐数字位数
+   * @return {string}
+   */
+  getNumTwoBit: function(n) {
+    n = Number(n);
+    return (n > 9 ? '' : '0') + n;
+  },
+
+  /**
+   * 日期对象转成字符串
+   * @return {string}
+   */
+  date2Str: function(date, split?: string) {
+    if (typeof date == 'string') return date;
+    split = split || '-';
+    const y = date.getFullYear();
+    const m = this.getNumTwoBit(date.getMonth() + 1);
+    const d = this.getNumTwoBit(date.getDate());
+    return [y, m, d].join(split);
+  },
+
+  /**
+   * 返回日期格式字符串
+   * @param {Number} 0返回今天的日期、1返回明天的日期,2返回后天得日期,依次类推
+   * @return {string} '2014-12-31'
+   */
+  getDay: function(i) {
+    i = i || 0;
+    let date = new Date();
+    const diff = i * (1000 * 60 * 60 * 24);
+    date = new Date(date.getTime() + diff);
+    return this.date2Str(date);
+  },
+
+  /**
+   * 时间戳转换为日期格式
+   * @return {String}
+   */
+  timestampToDate: function(timestamp) {
+    const date = new Date(timestamp);
+    return (
+      date.getFullYear() +
+      '-' +
+      this.getNumTwoBit(date.getMonth() + 1) +
+      '-' +
+      this.getNumTwoBit(date.getDate())
+    );
+  },
+
+  /**
+   * 时间比较
+   * @return {Boolean}
+   */
+  compareDate: function(date1, date2) {
+    const startTime = new Date(date1.replace('-', '/').replace('-', '/'));
+    const endTime = new Date(date2.replace('-', '/').replace('-', '/'));
+    if (startTime >= endTime) {
+      return false;
+    }
+    return true;
+  },
+  /**
+   * 时间比较
+   * @return {Boolean}
+   */
+  compareDateArr: function(date1, date2) {
+    const startTime = new Date();
+    startTime.setFullYear(date1[0], date1[1], date1[2]);
+    startTime.setHours(date1[3], date1[4]);
+    const endTime = new Date();
+    endTime.setFullYear(date2[0], date2[1], date2[2]);
+    endTime.setHours(date2[3], date2[4]);
+    if (startTime >= endTime) {
+      return false;
+    }
+    return true;
+  },
+  /**
+   * 时间是否相等
+   * @return {Boolean}
+   */
+  isEqual: function(date1, date2) {
+    const startTime = new Date(date1).getTime();
+    const endTime = new Date(date2).getTime();
+    if (startTime == endTime) {
+      return true;
+    }
+    return false;
+  }
+};
+
+export default Utils;

+ 6 - 16
tsconfig.json

@@ -13,29 +13,19 @@
     "sourceMap": true,
     "noImplicitAny": false,
     "baseUrl": ".",
-    "types": [
-      "webpack-env"
-    ],
+    "types": ["webpack-env"],
     "paths": {
-      "@/*": [
-        "src/*"
-      ]
+      "@/*": ["src/*"]
     },
-    "lib": [
-      "esnext",
-      "dom",
-      "dom.iterable",
-      "scripthost"
-    ]
+    "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
   },
   "include": [
     "src/**/*.ts",
     "src/**/*.tsx",
     "src/**/*.vue",
     "tests/**/*.ts",
-    "tests/**/*.tsx", "src/config.js"
+    "tests/**/*.tsx",
+    "src/config.js"
   ],
-  "exclude": [
-    "node_modules"
-  ]
+  "exclude": ["node_modules"]
 }