Browse Source

feat(calendar): 日历范围中的开始结束日期在同一月份,日历空白问题 (#1405)

* feat: 添加range组件、calendar组件在线文档

* fix: 文档调整

* fix: 重构calendar组件

* feat: 日历组件重构,文档修改,功能完善

* fix: 格式化

* fix: 代码格式化调整。

* fix: 去除无用代码

* fix: 文档调整

* fix:  文档调整

* fix: taro  demo 样式修改

* feat: range组件功能完善,新增 竖向操作,刻度展示。

* fix: 冲突解决

* feat: taro功能新增,兼容处理,文档修改

* feat: 添加range组件,jdt主题色

* fix: 修改组件初始化逻辑

* feat: 新增h5 日期多选功能

* feat: taro版本添加 日期多选功能

* fix: 修复多选,无法选中开头结尾日期问题

* fix: 文档修改,添加en-US 文档

Co-authored-by: lkjh3214 <13121007159@163.com>
Co-authored-by: love_forever <1039168735@qq.com>
lkjh3214 3 years ago
parent
commit
7c9f4e45e6

+ 124 - 30
src/packages/__VUE/calendar/demo.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="demo">
-    <h2>基础用法</h2>
+    <h2>{{ translate('title') }}</h2>
     <div>
       <nut-cell
         :show-icon="true"
-        title="选择单个日期"
-        :desc="date ? `${date} ${dateWeek}` : '请选择'"
+        :title="translate('single')"
+        :desc="date ? `${date} ${dateWeek}` : translate('please')"
         @click="openSwitch('isVisible')"
       >
       </nut-cell>
@@ -15,7 +15,7 @@
         @close="closeSwitch('isVisible')"
         @choose="setChooseValue"
         :start-date="`2022-01-11`"
-        :end-date="`2022-11-11`"
+        :end-date="`2022-11-30`"
       >
       </nut-calendar>
     </div>
@@ -23,8 +23,8 @@
     <div>
       <nut-cell
         :show-icon="true"
-        title="选择日期区间"
-        :desc="date1 ? `${date1[0]}至${date1[1]}` : '请选择'"
+        :title="translate('range')"
+        :desc="date1 ? `${date1[0]}${translate('conjunction')}${date1[1]}` : translate('please')"
         @click="openSwitch('isVisible1')"
       >
       </nut-cell>
@@ -40,13 +40,32 @@
       >
       </nut-calendar>
     </div>
+    <div>
+      <nut-cell
+        :show-icon="true"
+        :title="translate('multiple')"
+        :desc="date7 && date7.length ? `${translate('selected')}${date7.length}` : translate('please')"
+        @click="openSwitch('isVisible7')"
+      >
+      </nut-cell>
+      <nut-calendar
+        v-model:visible="isVisible7"
+        :default-value="date7"
+        type="multiple"
+        :start-date="`2022-01-01`"
+        :end-date="`2022-09-10`"
+        @close="closeSwitch('isVisible7')"
+        @choose="setChooseValue7"
+      >
+      </nut-calendar>
+    </div>
 
-    <h2>快捷选择</h2>
+    <h2>{{ translate('title1') }}</h2>
     <div>
       <nut-cell
         :show-icon="true"
-        title="选择单个日期"
-        :desc="date3 ? date3 : '请选择'"
+        :title="translate('single')"
+        :desc="date3 ? date3 : translate('please')"
         @click="openSwitch('isVisible3')"
       >
       </nut-cell>
@@ -64,9 +83,9 @@
     <div>
       <nut-cell
         :show-icon="true"
-        title="选择日期范围"
+        :title="translate('range')"
         @click="openSwitch('isVisible4')"
-        :desc="date4 ? `${date4[0]}至${date4[1]}` : '请选择'"
+        :desc="date4 ? `${date4[0]}${translate('conjunction')}${date4[1]}` : translate('please')"
       >
       </nut-cell>
       <nut-calendar
@@ -81,16 +100,17 @@
       >
       </nut-calendar>
     </div>
-    <h2>自定义日历</h2>
+    <h2>{{ translate('title2') }}</h2>
     <div>
       <nut-cell
         :show-icon="true"
-        title="自定义按钮"
-        :desc="date5 && date5[0] ? `${date5[0]}至${date5[1]}` : '请选择'"
+        :title="translate('custom_btn')"
+        :desc="date5 && date5[0] ? `${date5[0]}${translate('conjunction')}${date5[1]}` : translate('please')"
         @click="openSwitch('isVisible5')"
       >
       </nut-cell>
       <nut-calendar
+        ref="calendarRef"
         v-model:visible="isVisible5"
         :default-value="date5"
         type="range"
@@ -101,8 +121,15 @@
       >
         <template v-slot:btn>
           <div class="wrapper">
-            <div class="d_div"> <span class="d_btn" @click="clickBtn">最近七天</span></div>
-            <div class="d_div"> <span class="d_btn" @click="clickBtn1">当月</span></div>
+            <div class="d_div">
+              <span class="d_btn" @click="goDate">{{ translate('goDate') }}</span></div
+            >
+            <div class="d_div">
+              <span class="d_btn" @click="clickBtn">{{ translate('seven') }}</span></div
+            >
+            <div class="d_div">
+              <span class="d_btn" @click="clickBtn1">{{ translate('current') }}</span></div
+            >
           </div>
         </template>
         <template v-slot:day="date">
@@ -113,8 +140,8 @@
     <div>
       <nut-cell
         :show-icon="true"
-        title="自定义时间文案"
-        :desc="date6 && date6[0] ? `${date6[0]}至${date6[1]}` : '请选择'"
+        :title="translate('timeText')"
+        :desc="date6 && date6[0] ? `${date6[0]}${translate('conjunction')}${date6[1]}` : translate('please')"
         @click="openSwitch('isVisible6')"
       >
       </nut-cell>
@@ -127,21 +154,21 @@
         :start-date="`2022-01-01`"
         :end-date="`2022-12-31`"
         confirm-text="submit"
-        start-text="入店"
-        end-text="离店"
-        title="日期选择"
+        :start-text="translate('enter')"
+        :end-text="translate('leave')"
+        :title="translate('please')"
       >
         <template v-slot:day="date">
           <span>{{ date.date.day <= 9 ? '0' + date.date.day : date.date.day }}</span>
         </template>
         <template v-slot:bottomInfo="date">
           <span class="info">{{
-            date.date ? (date.date.day <= 10 ? '上旬' : date.date.day <= 20 ? '中旬' : '下旬') : ''
+            date.date ? (date.date.day <= 10 ? '' : date.date.day <= 20 ? translate('mid') : '') : ''
           }}</span>
         </template>
       </nut-calendar>
     </div>
-    <h2>平铺展示</h2>
+    <h2>{{ translate('title3') }}</h2>
     <div class="test-calendar-wrapper">
       <nut-calendar :poppable="false" :default-value="date2" :is-auto-back-fill="true" @choose="setChooseValue2">
       </nut-calendar>
@@ -150,12 +177,60 @@
 </template>
 
 <script lang="ts">
-import { reactive, toRefs } from 'vue';
+import { reactive, toRefs, ref } from 'vue';
 import { createComponent } from '@/packages/utils/create';
 import Utils from '@/packages/utils/date';
+import { useTranslate } from '@/sites/assets/util/useTranslate';
 
-const { createDemo } = createComponent('calendar');
+const { createDemo, translate } = createComponent('calendar');
+useTranslate({
+  'zh-CN': {
+    title: '基础用法',
+    title1: '快捷选择',
+    title2: '自定义日历',
+    title3: '平铺展示',
 
+    please: '请选择',
+    single: '选择单个日期',
+    range: '选择日期区间',
+    multiple: '选择多个日期',
+
+    conjunction: '至',
+    custom_btn: '自定义按钮',
+    timeText: '自定义时间文案',
+
+    goDate: '去某个月',
+    seven: '最近七天',
+    current: '当月',
+    enter: '入店',
+    leave: '离店',
+    mid: '中旬',
+    selected: '已选择:'
+  },
+  'en-US': {
+    title: 'Basic Usage',
+    title1: 'Quick Select',
+    title2: 'Custom Calendar',
+    title3: 'Tiled Display',
+
+    please: 'Please Select Date',
+    single: 'Select Single Date',
+    range: 'Select Date Range',
+    multiple: 'Select Multiple Date',
+
+    conjunction: '-',
+    custom_btn: 'Custom Button',
+    timeText: 'Custom Date Text',
+
+    goDate: 'Go Date',
+    seven: 'Last Seven Days',
+    current: 'This Month',
+    enter: 'enter',
+    leave: 'leave',
+    mid: 'mid',
+    selected: 'selected:'
+  }
+});
 interface TestCalendarState {
   isVisible: boolean;
   date: string;
@@ -166,32 +241,37 @@ interface TestCalendarState {
   isVisible4: boolean;
   isVisible5: boolean;
   isVisible6: boolean;
+  isVisible7: boolean;
   date1: string[];
   date2: string;
   date3: string;
   date4: string[];
   date5: string[];
   date6: string[];
+  date7: string[];
 }
 export default createDemo({
   props: {},
   setup() {
+    const calendarRef = ref(null);
     const state: TestCalendarState = reactive({
       isVisible: false,
-      date: '',
+      date: '2022-02-01',
       dateWeek: '',
-      date1: ['2019-12-23', '2019-12-26'],
+      date1: ['2020-01-23', '2020-01-26'],
       date2: '2020-07-08',
       date3: '',
       date4: ['2021-12-23', '2021-12-26'],
       date5: ['2021-12-23', '2021-12-26'],
       date6: [],
+      date7: [],
       isVisible1: false,
       isVisible2: false,
       isVisible3: false,
       isVisible4: false,
       isVisible5: false,
-      isVisible6: false
+      isVisible6: false,
+      isVisible7: false
     });
     const openSwitch = (param: string) => {
       state[`${param}`] = true;
@@ -212,7 +292,6 @@ export default createDemo({
     const setChooseValue1 = (param: string) => {
       state.date1 = [...[param[0][3], param[1][3]]];
     };
-
     const setChooseValue2 = (param: string) => {
       state.date2 = param[3];
     };
@@ -230,6 +309,12 @@ export default createDemo({
     const setChooseValue6 = (param: string) => {
       state.date6 = [...[param[0][3], param[1][3]]];
     };
+    const setChooseValue7 = (chooseData: any) => {
+      let dateArr = chooseData.map((item: any) => {
+        return item[3];
+      });
+      state.date7 = [...dateArr];
+    };
     const clickBtn = (param: string) => {
       let date = [Utils.date2Str(new Date()), Utils.getDay(6)];
       state.date5 = date;
@@ -243,12 +328,18 @@ export default createDemo({
       let currMonthDays = Utils.getMonthDays(year + '', month + '');
       state.date5 = [`${yearMonth}-01`, `${yearMonth}-${currMonthDays}`];
     };
+    const goDate = () => {
+      if (calendarRef.value) {
+        calendarRef.value.scrollToDate('2022-04-01');
+      }
+    };
     return {
       ...toRefs(state),
       openSwitch,
       closeSwitch,
       setChooseValue,
       setChooseValue1,
+      setChooseValue7,
       setChooseValue2,
       setChooseValue3,
       setChooseValue4,
@@ -256,7 +347,10 @@ export default createDemo({
       setChooseValue6,
       clickBtn,
       clickBtn1,
-      select
+      goDate,
+      calendarRef,
+      select,
+      translate
     };
   }
 });

+ 589 - 0
src/packages/__VUE/calendar/doc.en-US.md

@@ -0,0 +1,589 @@
+# Calendar
+
+### Intro
+
+Calendar, tileable/pop-up display
+
+### Install
+
+```javascript
+import { createApp } from 'vue';
+// vue
+import { Calendar,Popup } from '@nutui/nutui';
+// taro
+import { Calendar,Popup } from '@nutui/nutui-taro';
+
+const app = createApp();
+app.use(Calendar);
+app.use(Popup);
+
+```
+
+
+### Basic Usage
+:::demo
+```html
+<template>
+  <nut-cell
+    :showIcon="true"
+    title="Select Single Date"
+    :desc="date ? `${date} ${dateWeek}` : 'Please Select Date'"
+    @click="openSwitch('isVisible')"
+  >
+  </nut-cell>
+  <nut-calendar
+    v-model:visible="isVisible"
+    :default-value="date"
+    @close="closeSwitch('isVisible')"
+    @choose="setChooseValue"
+    :start-date="`2019-10-11`"
+    :end-date="`2022-11-11`"
+  >
+  </nut-calendar>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const state = reactive({
+      isVisible: false,
+      date: '',
+      dateWeek: ''
+    });
+    const openSwitch = param => {
+      state[`${param}`] = true;
+    };
+    const closeSwitch = param => {
+      state[`${param}`] = false;
+    };
+    const setChooseValue = param => {
+      state.date = param[3];
+      state.dateWeek = param[4];
+    };
+    return {
+      ...toRefs(state),
+      openSwitch,
+      closeSwitch,
+      setChooseValue
+    };
+  }
+};
+</script>
+```
+:::
+### Select Date Range
+:::demo
+```html
+<template>
+  <nut-cell
+    :showIcon="true"
+    title="Select Date Range"
+    :desc="date ? `${date[0]}-${date[1]}` : 'Please Select Date'"
+    @click="openSwitch('isVisible')"
+  >
+  </nut-cell>
+  <nut-calendar
+    v-model:visible="isVisible"
+    :default-value="date"
+    type="range"
+    :start-date="`2019-12-22`"
+    :end-date="`2021-01-08`"
+    @close="closeSwitch('isVisible')"
+    @choose="setChooseValue"
+    @select="select"
+  >
+  </nut-calendar>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const state = reactive({
+      date: ['2019-12-23', '2019-12-26'],
+      isVisible: false
+    });
+    const openSwitch = param => {
+      state[`${param}`] = true;
+    };
+    const closeSwitch = param => {
+      state[`${param}`] = false;
+    };
+    const setChooseValue= param => {
+      state.date = [...[param[0][3], param[1][3]]];
+    };
+    const select = (param: string) => {
+      console.log(param);
+    };
+    return {
+      ...toRefs(state),
+      openSwitch,
+      closeSwitch,
+      setChooseValue,
+      select,
+    };
+  }  
+};
+</script>
+```
+:::
+
+### Select Multiple Date
+:::demo
+```html
+<template>
+ <nut-cell
+    :show-icon="true"
+    title="Select Multiple Date"
+    :desc="date7 && date7.length ? `${date7.length} dates selected` : 'Please Select Date'"
+    @click="openSwitch('isVisible7')"
+  >
+  </nut-cell>
+  <nut-calendar
+    v-model:visible="isVisible7"
+    :default-value="date7"
+    type="multiple"
+    :start-date="`2022-01-01`"
+    :end-date="`2022-09-10`"
+    @close="closeSwitch('isVisible7')"
+    @choose="setChooseValue7"
+  >
+  </nut-calendar>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const state = reactive({
+      date7: [],
+      isVisible7: false
+    });
+    const openSwitch = param => {
+      state[`${param}`] = true;
+    };
+    const closeSwitch = param => {
+      state[`${param}`] = false;
+    };
+     const setChooseValue7 = (chooseData: any) => {
+      let dateArr = chooseData.map((item: any) => {
+        return item[3];
+      });
+      state.date7 = [...dateArr];
+    };
+    const select = (param: string) => {
+      console.log(param);
+    };
+    return {
+      ...toRefs(state),
+      openSwitch,
+      closeSwitch,
+      setChooseValue,
+      select,
+    };
+  }  
+};
+</script>
+```
+:::
+
+### Quick Select Single Date
+:::demo
+```html
+<template>
+  <nut-cell
+    :showIcon="true"
+    title="Select Single Date"
+    :desc="date ? date : 'Please Select Date'"
+    @click="openSwitch('isVisible')"
+  >
+  </nut-cell>
+  <nut-calendar
+    v-model:visible="isVisible"
+    @close="closeSwitch('isVisible')"
+    @choose="setChooseValue"
+    :default-value="date"
+    :start-date="null"
+    :end-date="null"
+    :is-auto-back-fill="true"
+  >
+  </nut-calendar>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const state = reactive({
+      date: '',
+      isVisible: false
+    });
+    const openSwitch = param => {
+      state[`${param}`] = true;
+    };
+    const closeSwitch = param => {
+      state[`${param}`] = false;
+    };
+     const setChooseValue = param => {
+      state.date= param[3];
+    };
+    return {
+      ...toRefs(state),
+      setChooseValue,
+      openSwitch,
+      closeSwitch
+    };
+  }
+}
+</script>
+```
+:::
+
+### Quick Select Date Range
+:::demo
+```html
+<template>
+  <nut-cell
+    :showIcon="true"
+    title="Select Date Range"
+    :desc="date ? `${date[0]}-${date[1]}` : 'Please Select Date'"
+    @click="openSwitch('isVisible')"
+  >
+  </nut-cell>
+  <nut-calendar
+    v-model:visible="isVisible"
+    :default-value="date"
+    type="range"
+    :start-date="`2022-01-01`"
+    :end-date="`2022-12-31`"
+    @close="closeSwitch('isVisible')"
+    @choose="setChooseValue"
+    :is-auto-back-fill="true"
+  >
+  </nut-calendar>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const state = reactive({
+      date: ['2021-12-23', '2021-12-26'],
+      isVisible: false
+    });
+    const openSwitch = param => {
+      state[`${param}`] = true;
+    };
+    const closeSwitch = param => {
+      state[`${param}`] = false;
+    };
+     const setChooseValue = param => {
+      state.date= param[3];
+    };
+    return {
+      ...toRefs(state),
+      setChooseValue,
+      openSwitch,
+      closeSwitch
+    };
+  }
+}
+</script>
+```
+:::
+
+### Custom Button
+:::demo
+```html
+<template>
+  <nut-cell
+    :showIcon="true"
+    title="Custom Button"
+    :desc="date ? `${date[0]}-${date[1]}` : 'Please Select Date'"
+    @click="openSwitch('isVisible')"
+  >
+  </nut-cell>
+  <nut-calendar
+    v-model:visible="isVisible"
+    :default-value="date"
+    type="range"
+    :start-date="`2021-12-22`"
+    :end-date="`2022-12-31`"
+    @close="closeSwitch('isVisible')"
+    @choose="setChooseValue"
+  >
+    <template v-slot:btn>
+      <div class="wrapper">
+        <div class="d_div"> <span class="d_btn" @click="goDate">Go Date</span></div>
+        <div class="d_div"> <span class="d_btn" @click="clickBtn">Last Seven Days</span></div>
+        <div class="d_div"> <span class="d_btn" @click="clickBtn1">This Month</span></div>
+      </div>
+    </template>
+    <template v-slot:day="date">
+      <span>{{ date.date.day }}</span>
+    </template>
+  </nut-calendar>
+</template>
+
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const calendarRef = ref(null);
+    const state = reactive({
+      date: ['2021-12-23', '2021-12-26'],
+      isVisible: false
+    });
+    const getNumTwoBit = function(n: number): string {
+      n = Number(n);
+      return (n > 9 ? '' : '0') + n;
+    };
+    const date2Str =  function(date: Date, split?: string): string {
+      split = split || '-';
+      const y = date.getFullYear();
+      const m = getNumTwoBit(date.getMonth() + 1);
+      const d = getNumTwoBit(date.getDate());
+      return [y, m, d].join(split);
+    };
+    const getDay = function(i: number): string {
+      i = i || 0;
+      let date = new Date();
+      const diff = i * (1000 * 60 * 60 * 24);
+      date = new Date(date.getTime() + diff);
+      return date2Str(date);
+    };
+    const isLeapYear= function(y: number): boolean {
+      return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
+    };
+    const getMonthDays= function(year: string, month: string): number {
+      if (/^0/.test(month)) {
+        month = month.split('')[1];
+      }
+      return ([
+        0,
+        31,
+        isLeapYear(Number(year)) ? 29 : 28,
+        31,
+        30,
+        31,
+        30,
+        31,
+        31,
+        30,
+        31,
+        30,
+        31
+      ] as number[])[month as any];
+    };
+    const openSwitch = param => {
+      state[`${param}`] = true;
+    };
+    const closeSwitch = param => {
+      state[`${param}`] = false;
+    };
+     const setChooseValue = param => {
+      state.date= param[3];
+    };
+    const clickBtn = (param: string) => {
+      let date = [date2Str(new Date()), getDay(6)];
+      state.date5 = date;
+    };
+    const clickBtn1 = (param: string) => {
+      let date = new Date();
+      let year = date.getFullYear();
+      let month: any = date.getMonth() + 1;
+      month = month < 10 ? '0' + month : month + '';
+      let yearMonth = `${year}-${month}`;
+      let currMonthDays = getMonthDays(year + '', month + '');
+      state.date5 = [`${yearMonth}-01`, `${yearMonth}-${currMonthDays}`];
+    };
+    const goDate = () => {
+      if (calendarRef.value) {
+        calendarRef.value.scrollToDate('2022-04-01');
+      }
+    };
+    return {
+      ...toRefs(state),
+      setChooseValue,
+      openSwitch,
+      closeSwitch,
+      clickBtn,
+      clickBtn1,
+      goDate
+    };
+  }
+}
+</script>
+<style lang="scss" scoped>
+.wrapper {
+  display: flex;
+  padding: 0 40px;
+  justify-content: center;
+}
+.d_div {
+  margin: 0px 5px;
+  .d_btn {
+    background: #fa3f19;
+    color: #fff;
+    font-size: 12px;
+    padding: 2px 8px;
+    border-radius: 4px;
+    display: inline-block;
+    height: 16px;
+  }
+}
+
+</style>
+```
+:::
+
+### Custom Date Text
+:::demo
+```html
+<template>
+  <nut-cell
+    :showIcon="true"
+    title="Custom Date Text"
+    :desc="date && date[0] ? `${date[0]}-${date[1]}` : 'Please Select Date'"
+    @click="openSwitch('isVisible')"
+  >
+  </nut-cell>
+  <nut-calendar
+    v-model:visible="isVisible"
+    :default-value="date"
+    type="range"
+    @close="closeSwitch('isVisible')"
+    @choose="setChooseValue"
+    :start-date="`2022-01-01`"
+    :end-date="`2022-12-31`"
+    confirm-text="submit"
+    start-text="Enter"
+    end-text="Leave"
+    title="Select Date"
+  >
+    <template v-slot:day="date">
+      <span>{{ date.date.day <= 9 ? '0' + date.date.day : date.date.day }}</span>
+    </template>
+    <template v-slot:bottomInfo="date">
+      <span class="info" style="fontSize:12px;lineHeight:14px">{{
+        date.date ? (date.date.day == 10 ? '十' :  '') : ''
+      }}</span>
+    </template>
+  </nut-calendar>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const state = reactive({
+      date: [],
+      isVisible: false
+    });
+    const openSwitch = param => {
+      state[`${param}`] = true;
+    };
+    const closeSwitch = param => {
+      state[`${param}`] = false;
+    };
+     const setChooseValue = param => {
+      state.date = param[3];
+    };
+    return {
+      ...toRefs(state),
+      setChooseValue,
+      openSwitch,
+      closeSwitch
+    };
+  }
+}
+</script>
+
+```
+:::
+### Tiled Display
+:::demo
+```html
+<template>
+  <div class="test-calendar-wrapper" >
+    <nut-calendar
+        :poppable="false"
+        :default-value="date"
+        :is-auto-back-fill="true"
+        @choose="setChooseValue"
+    >
+    </nut-calendar>
+  </div>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const state = reactive({
+      date: '2022-07-08'
+    });
+    const setChooseValue = param => {
+      state.date = param[3];
+    };
+    return {
+      ...toRefs(state),
+      setChooseValue
+    };
+  }
+}
+</script>
+<style lang="scss" scoped>
+.test-calendar-wrapper {
+  display: flex;
+  width: 100%;
+  height: 560px;
+  overflow: hidden;
+}
+</style>
+
+```
+:::
+
+
+
+### Props
+
+| Attribute              | Description                                  | Type            | Default  |
+|-------------------|---------------------------------------------------|-----------------|-----------------|
+| v-model:visible   | whether to show                  | Boolean         | false           |
+| type              | Calendar type :'one' 'range' 'multiple'    | String          | 'one'           |
+| poppable          | Whether to display the pop-up window                                  | Boolean         | true            |
+| is-auto-back-fill | Automatic backfill                                          | Boolean         | false           |
+| title             | whether to show title                                          | String          | ‘Calendar’      |
+| default-value     | Default value, select single date : `String`,other: `Array` | String 、 Array | null            |
+| start-date        | The start date, or null if the start date is not limited             | String          | Today            |
+| end-date          | The end date, or null if the end date is not limited              | String          | 365 days from today |
+| show-today          | Whether to show today's mark               | Boolean          | true |
+| start-text         | Range selection, start part of the text              | String          | Start |
+| end-text         | Range selection, end part of the text            | String          | End |
+| confirm-text          | Bottom confirm button text               | String          | ’Confirm‘ |
+| show-title          | Whether to show the calendar title               | Boolean          | true |
+| show-sub-title          | Whether to display the date title              | Boolean          | true |
+
+### Events
+
+| Event | Description                         | Arguments                     |
+|--------|------------------------------|------------------------------|
+| choose | Triggered after selection or by clicking the confirm button | Array of dates (including year, month, day and week) |
+| close  | Triggered when closed                   | -                            |
+| select  | Triggered after click/select             |  Day:object                          |
+
+
+
+### Slots
+
+| Name    | Description         |
+|---------|--------------|
+| btn | 	Below the custom calendar header, you can add custom actions |
+| day | 	Date information |
+| topInfo | 	Date top information |
+| bottomInfo | 	Date bottom information |
+
+### Methods
+
+Through [ref](https://vuejs.org/guide/essentials/template-refs.html), you can get the Calendar instance and call the instance method.
+
+
+| Name | Description             | Arguments          |
+|--------|------------------|---------------|
+| scrollToDate   | Scroll to the month of the specified date | string:'2021-12-30' |

+ 70 - 3
src/packages/__VUE/calendar/doc.md

@@ -72,7 +72,7 @@ export default {
 </script>
 ```
 :::
-### 区间选择
+### 选择日期区间
 :::demo
 ```html
 <template>
@@ -127,6 +127,64 @@ export default {
 </script>
 ```
 :::
+
+### 选择多个日期
+:::demo
+```html
+<template>
+ <nut-cell
+    :show-icon="true"
+    title="选择多个日期"
+    :desc="date7 && date7.length ? `已选择${date7.length}个日期` : '请选择'"
+    @click="openSwitch('isVisible7')"
+  >
+  </nut-cell>
+  <nut-calendar
+    v-model:visible="isVisible7"
+    :default-value="date7"
+    type="multiple"
+    :start-date="`2022-01-01`"
+    :end-date="`2022-09-10`"
+    @close="closeSwitch('isVisible7')"
+    @choose="setChooseValue7"
+  >
+  </nut-calendar>
+</template>
+<script lang="ts">
+import { reactive, toRefs } from 'vue';
+export default {
+  setup() {
+    const state = reactive({
+      date7: [],
+      isVisible7: false
+    });
+    const openSwitch = param => {
+      state[`${param}`] = true;
+    };
+    const closeSwitch = param => {
+      state[`${param}`] = false;
+    };
+     const setChooseValue7 = (chooseData: any) => {
+      let dateArr = chooseData.map((item: any) => {
+        return item[3];
+      });
+      state.date7 = [...dateArr];
+    };
+    const select = (param: string) => {
+      console.log(param);
+    };
+    return {
+      ...toRefs(state),
+      openSwitch,
+      closeSwitch,
+      setChooseValue,
+      select,
+    };
+  }  
+};
+</script>
+```
+:::
 ### 快捷选择-单选
 :::demo
 ```html
@@ -481,11 +539,11 @@ export default {
 | 字段              | 说明                                              | 类型            | 默认值          |
 |-------------------|---------------------------------------------------|-----------------|-----------------|
 | v-model:visible   | 是否可见                                          | Boolean         | false           |
-| type              | 类型,日期选择'one',区间选择'range'              | String          | 'one'           |
+| type              | 类型,日期单择'one',区间选择'range',日期多选'multiple'    | String          | 'one'           |
 | poppable          | 是否弹窗状态展示                                  | Boolean         | true            |
 | is-auto-back-fill | 自动回填                                          | Boolean         | false           |
 | title             | 显示标题                                          | String          | ‘日期选择’      |
-| default-value     | 默认值,日期选择 String 格式,区间选择 Array 格式 | String 、 Array | null            |
+| default-value     | 默认值,单个日期选择 String,其他为 Array  | String 、 Array | null            |
 | start-date        | 开始日期, 如果不限制开始日期传 null              | String          | 今天            |
 | end-date          | 结束日期,如果不限制结束日期传 null               | String          | 距离今天 365 天 |
 | show-today          | 是否展示今天标记               | Boolean          | true |
@@ -513,3 +571,12 @@ export default {
 | day | 	日期信息 |
 | topInfo | 	日期顶部信息 |
 | bottomInfo | 	日期底部信息 |
+
+### Methods
+
+通过 [ref](https://vuejs.org/guide/essentials/template-refs.html) 可以获取到 Calendar 实例并调用实例方法。
+
+
+| 方法名 | 说明             | 参数          |
+|--------|------------------|---------------|
+| scrollToDate   | 滚动到指定日期所在月 | string:'2021-12-30' |

+ 10 - 1
src/packages/__VUE/calendar/index.vue

@@ -65,6 +65,7 @@
     :show-today="showToday"
     :show-title="showTitle"
     :show-sub-title="showSubTitle"
+    ref="calendarRef"
   >
     <template v-slot:btn v-if="showTopBtn">
       <slot name="btn"> </slot>
@@ -86,6 +87,8 @@ import { createComponent } from '@/packages/utils/create';
 const { create } = createComponent('calendar');
 import CalendarItem from '../calendaritem/index.vue';
 import Utils from '@/packages/utils/date';
+import { useExpose } from '@/packages/utils/useExpose/index';
+
 type InputDate = string | string[];
 export default create({
   components: {
@@ -164,7 +167,13 @@ export default create({
     });
     // element refs
     const calendarRef = ref<null | HTMLElement>(null);
-
+    const scrollToDate = (date: string) => {
+      console.log(calendarRef.value);
+      calendarRef.value?.scrollToDate(date);
+    };
+    useExpose({
+      scrollToDate
+    });
     // methods
     const update = () => {
       emit('update:visible', false);

+ 196 - 71
src/packages/__VUE/calendaritem/index.taro.vue

@@ -72,6 +72,7 @@ import { createComponent } from '@/packages/utils/create';
 const { create, translate } = createComponent('calendar-item');
 import Taro from '@tarojs/taro';
 import Utils from '@/packages/utils/date';
+import { useExpose } from '@/packages/utils/useExpose/index';
 import requestAniFrame from '@/packages/utils/raf';
 let TARO_ENV = process.env.TARO_ENV;
 
@@ -125,6 +126,10 @@ export default create({
       type: Boolean,
       default: false
     },
+    toDateAnimation: {
+      type: Boolean,
+      default: true
+    },
     poppable: {
       type: Boolean,
       default: true
@@ -178,10 +183,6 @@ export default create({
     const scalePx = ref(2);
     const viewHeight = ref(0);
     const months = ref<null | HTMLElement>(null);
-
-    const compConthsData = computed(() => {
-      return state.monthsData.slice(state.defaultRange[0], state.defaultRange[1]);
-    });
     const showTopBtn = computed(() => {
       return slots.btn;
     });
@@ -237,28 +238,38 @@ export default create({
     const isEnd = (currDate: string) => {
       return Utils.isEqual(state.currDate[1], currDate);
     };
-
+    const isMultiple = (currDate: string) => {
+      if (state.currDate.length > 0) {
+        return state.currDate.some((item: any) => {
+          return Utils.isEqual(item, currDate);
+        });
+      } else {
+        return false;
+      }
+    };
     // 获取当前数据
-    const getCurrDate = (day: Day, month: MonthInfo, isRange?: boolean) => {
+    const getCurrDate = (day: Day, month: MonthInfo) => {
       return month.curData[0] + '-' + month.curData[1] + '-' + Utils.getNumTwoBit(+day.day);
     };
 
     // 获取样式
-    const getClass = (day: Day, month: MonthInfo, isRange?: boolean) => {
-      const currDate = getCurrDate(day, month, isRange);
+    const getClass = (day: Day, month: MonthInfo) => {
+      const currDate = getCurrDate(day, month);
+      const { type } = props;
       if (day.type == 'curr') {
         if (
-          (!state.isRange && Utils.isEqual(state.currDate as string, currDate)) ||
-          (state.isRange && (isStart(currDate) || isEnd(currDate)))
+          Utils.isEqual(state.currDate as string, currDate) ||
+          (type == 'range' && (isStart(currDate) || isEnd(currDate))) ||
+          (type == 'multiple' && isMultiple(currDate))
         ) {
           return `${state.dayPrefix}-active`;
         } else if (
-          (props.startDate && Utils.compareDate(currDate, props.startDate)) ||
-          (props.endDate && Utils.compareDate(props.endDate, currDate))
+          (state.propStartDate && Utils.compareDate(currDate, state.propStartDate)) ||
+          (state.propEndDate && Utils.compareDate(state.propEndDate, currDate))
         ) {
           return `${state.dayPrefix}-disabled`;
         } else if (
-          state.isRange &&
+          type == 'range' &&
           Array.isArray(state.currDate) &&
           Object.values(state.currDate).length == 2 &&
           Utils.compareDate(state.currDate[0], currDate) &&
@@ -274,8 +285,10 @@ export default create({
     };
 
     const confirm = () => {
-      if ((state.isRange && state.chooseData.length == 2) || !state.isRange) {
-        emit('choose', state.chooseData);
+      const { type } = props;
+      if ((type == 'range' && state.chooseData.length == 2) || type != 'range') {
+        let chooseData = state.chooseData.slice(0);
+        emit('choose', chooseData);
         if (props.poppable) {
           emit('update');
         }
@@ -284,17 +297,38 @@ export default create({
 
     // 选中数据
     const chooseDay = (day: Day, month: MonthInfo, isFirst: boolean, isRange?: boolean) => {
-      if (getClass(day, month, isRange) != `${state.dayPrefix}-disabled`) {
+      if (getClass(day, month) != `${state.dayPrefix}-disabled`) {
+        const { type } = props;
         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) {
+        if (type == 'multiple') {
+          if (state.currDate.length > 0) {
+            let hasIndex: any = '';
+            state.currDate.forEach((item: any, index: number) => {
+              if (item == days[3]) {
+                hasIndex = index;
+              }
+            });
+            if (isFirst) {
+              state.chooseData.push([...days]);
+            } else {
+              if (hasIndex !== '') {
+                state.currDate.splice(hasIndex, 1);
+                state.chooseData.splice(hasIndex, 1);
+              } else {
+                state.currDate.push(days[3]);
+                state.chooseData.push([...days]);
+              }
+            }
+          } else {
+            state.currDate = [days[3]];
+            state.chooseData = [[...days]];
+          }
+        } else if (type == 'range') {
+          let curDataLength = Object.values(state.currDate).length;
+          if (curDataLength == 2 || curDataLength == 0) {
             state.currDate = [days[3]];
           } else {
             if (Utils.compareDate(state.currDate[0], days[3])) {
@@ -303,21 +337,25 @@ export default create({
               Array.isArray(state.currDate) && state.currDate.unshift(days[3]);
             }
           }
+
           if (state.chooseData.length == 2 || !state.chooseData.length) {
-            state.chooseData = [...days];
+            state.chooseData = [[...days]];
           } else {
-            if (Utils.compareDate(state.chooseData[3], days[3])) {
-              state.chooseData = [[...state.chooseData], [...days]];
+            if (Utils.compareDate(state.chooseData[0][3], days[3])) {
+              state.chooseData = [...state.chooseData, [...days]];
             } else {
-              state.chooseData = [[...days], [...state.chooseData]];
+              state.chooseData = [[...days], ...state.chooseData];
             }
           }
+        } else {
+          state.currDate = days[3];
+          state.chooseData = [...days];
         }
 
         if (!isFirst) {
           // 点击日期 触发
           emit('select', state.chooseData);
-          if (props.isAutoBackFill) {
+          if (props.isAutoBackFill || !props.poppable) {
             confirm();
           }
         }
@@ -456,11 +494,10 @@ export default create({
       state.propEndDate = propEndDate;
       state.startData = splitDate(propStartDate);
       state.endData = splitDate(propEndDate);
+
       // 根据是否存在默认时间,初始化当前日期,
-      if (!props.defaultValue || (Array.isArray(props.defaultValue) && props.defaultValue.length <= 0)) {
-        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 (props.defaultValue || (Array.isArray(props.defaultValue) && props.defaultValue.length > 0)) {
+        state.currDate = props.type != 'one' ? [...props.defaultValue] : props.defaultValue;
       }
       // 判断时间范围内存在多少个月
       const startDate = {
@@ -475,7 +512,9 @@ export default create({
       if (endDate.year - startDate.year > 0) {
         monthsNum = monthsNum + 12 * (endDate.year - startDate.year);
       }
-
+      if (monthsNum <= 0) {
+        monthsNum = 1;
+      }
       // 设置月份数据
       getMonth(state.startData, 'next');
 
@@ -486,45 +525,84 @@ export default create({
       state.monthsNum = monthsNum;
 
       // 日期转化为数组,限制初始日期。判断时间范围
-      if (state.isRange && Array.isArray(state.currDate)) {
-        if (propStartDate && Utils.compareDate(state.currDate[0], propStartDate)) {
-          state.currDate.splice(0, 1, propStartDate);
+      if (props.type == 'range' && Array.isArray(state.currDate)) {
+        if (state.currDate.length > 0) {
+          if (propStartDate && Utils.compareDate(state.currDate[0], propStartDate)) {
+            state.currDate.splice(0, 1, propStartDate);
+          }
+          if (propEndDate && Utils.compareDate(propEndDate, state.currDate[1])) {
+            state.currDate.splice(1, 1, propEndDate);
+          }
+          state.defaultData = [...splitDate(state.currDate[0]), ...splitDate(state.currDate[1])];
         }
-        if (propEndDate && Utils.compareDate(propEndDate, state.currDate[1])) {
-          state.currDate.splice(1, 1, propEndDate);
+      } else if (props.type == 'multiple' && Array.isArray(state.currDate)) {
+        if (state.currDate.length > 0) {
+          let defaultArr: string[] = [];
+          let obj: any = {};
+          state.currDate.forEach((item: string, index: number) => {
+            if (
+              propStartDate &&
+              !Utils.compareDate(item, propStartDate) &&
+              propEndDate &&
+              !Utils.compareDate(propEndDate, item)
+            ) {
+              if (!obj.hasOwnProperty(item)) {
+                defaultArr.push(item);
+                obj[item] = item;
+              }
+            }
+          });
+          state.currDate = [...defaultArr];
+          state.defaultData = [...splitDate(defaultArr[0])];
         }
-        state.defaultData = [...splitDate(state.currDate[0]), ...splitDate(state.currDate[1])];
       } else {
-        if (propStartDate && Utils.compareDate(state.currDate as string, propStartDate)) {
-          state.currDate = propStartDate;
-        } else if (propEndDate && !Utils.compareDate(state.currDate as string, propEndDate)) {
-          state.currDate = propEndDate;
+        if (state.currDate) {
+          if (propStartDate && Utils.compareDate(state.currDate as string, propStartDate)) {
+            state.currDate = propStartDate;
+          } else if (propEndDate && !Utils.compareDate(state.currDate as string, propEndDate)) {
+            state.currDate = propEndDate;
+          }
+          state.defaultData = [...splitDate(state.currDate as string)];
         }
-        state.defaultData = [...splitDate(state.currDate as string)];
       }
 
+      // 设置默认可见区域
       let current = 0;
       let lastCurrent = 0;
-      state.monthsData.forEach((item, index) => {
-        if (item.title == translate('monthTitle', state.defaultData[0], state.defaultData[1])) {
-          current = index;
-        }
-        if (state.isRange) {
-          if (item.title == translate('monthTitle', state.defaultData[3], state.defaultData[4])) {
-            lastCurrent = index;
+      if (state.defaultData.length > 0) {
+        state.monthsData.forEach((item, index) => {
+          if (item.title == translate('monthTitle', state.defaultData[0], state.defaultData[1])) {
+            current = index;
           }
-        }
-      });
+          if (props.type == 'range') {
+            if (item.title == translate('monthTitle', state.defaultData[3], state.defaultData[4])) {
+              lastCurrent = index;
+            }
+          }
+        });
+      }
       setDefaultRange(monthsNum, current);
       state.currentIndex = current;
       state.yearMonthTitle = state.monthsData[state.currentIndex].title;
-
-      // 设置当前选中日期
-      if (state.isRange) {
-        chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true);
-        chooseDay({ day: state.defaultData[5], type: 'curr' }, state.monthsData[lastCurrent], true, true);
-      } else {
-        chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true);
+      if (state.defaultData.length > 0) {
+        // 设置当前选中日期
+        if (state.isRange) {
+          chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true);
+          chooseDay({ day: state.defaultData[5], type: 'curr' }, state.monthsData[lastCurrent], true);
+        } else if (props.type == 'multiple') {
+          [...state.currDate].forEach((item: any) => {
+            let dateArr = splitDate(item);
+            let current = state.currentIndex;
+            state.monthsData.forEach((item, index) => {
+              if (item.title == translate('monthTitle', dateArr[0], dateArr[1])) {
+                current = index;
+              }
+            });
+            chooseDay({ day: dateArr[2], type: 'curr' }, state.monthsData[current], true);
+          });
+        } else {
+          chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true);
+        }
       }
 
       let lastItem = state.monthsData[state.monthsData.length - 1];
@@ -537,17 +615,53 @@ export default create({
       if (months?.value) {
         viewHeight.value = months.value.clientHeight;
       }
-      if (TARO_ENV === 'h5') {
-        Taro.nextTick(() => {
-          Taro.createSelectorQuery()
-            .select('.nut-calendar-content')
-            .boundingClientRect((res) => {
-              viewHeight.value = res.height;
-            })
-            .exec();
-        });
+    };
+    const scrollToDate = (date: string) => {
+      if (Utils.compareDate(date, state.propStartDate)) {
+        date = state.propStartDate;
+      } else if (!Utils.compareDate(date, state.propEndDate)) {
+        date = state.propEndDate;
       }
+      console.log(date);
+      let dateArr = splitDate(date);
+      state.monthsData.forEach((item, index) => {
+        if (item.title == translate('monthTitle', dateArr[0], dateArr[1])) {
+          // if (months.value) {
+
+          if (props.toDateAnimation) {
+            Taro.createSelectorQuery()
+              .select('.nut-calendar-content')
+              .scrollOffset((res) => {
+                let scrollTop = res.scrollTop;
+                let distance = state.monthsData[index].cssScrollHeight - scrollTop;
+                let flag = 0;
+                let interval = setInterval(() => {
+                  flag++;
+                  if (months.value) {
+                    let offset = distance / 10;
+                    console.log('scrolltodate ', distance, offset);
+
+                    state.scrollTop = state.scrollTop + offset;
+                  }
+                  if (flag >= 10) {
+                    clearInterval(interval);
+                    if (months.value) {
+                      state.scrollTop = state.monthsData[index].cssScrollHeight;
+                    }
+                  }
+                }, 40);
+              })
+              .exec();
+          } else {
+            state.scrollTop = state.monthsData[index].cssScrollHeight;
+          }
+          // }
+        }
+      });
     };
+    useExpose({
+      scrollToDate
+    });
     const setDefaultRange = (monthsNum: number, current: number) => {
       let rangeArr: any[] = [];
       if (monthsNum >= 3) {
@@ -559,7 +673,7 @@ export default create({
           rangeArr = [current - 2, current + 2];
         }
       } else {
-        rangeArr = [0, monthsNum + 1];
+        rangeArr = [0, monthsNum + 2];
       }
       if (JSON.stringify(state.defaultRange) !== JSON.stringify(rangeArr)) {
         state.defaultRange[0] = rangeArr[0];
@@ -571,7 +685,7 @@ export default create({
     };
     // 区间选择&&当前月&&选中态
     const isActive = (day: Day, month: MonthInfo) => {
-      return state.isRange && day.type == 'curr' && getClass(day, month) == 'calendar-month-day-active';
+      return props.type == 'range' && day.type == 'curr' && getClass(day, month) == 'calendar-month-day-active';
     };
 
     // 是否有开始提示
@@ -599,6 +713,9 @@ export default create({
     };
 
     const mothsViewScroll = (e: any) => {
+      if (state.monthsData.length <= 1) {
+        return;
+      }
       const currentScrollTop = e.target.scrollTop;
       let current = Math.floor(currentScrollTop / state.avgHeight);
       if (current == 0) {
@@ -613,6 +730,14 @@ export default create({
           current -= 1;
         }
       } else {
+        if (!viewHeight.value || viewHeight.value < 0) {
+          Taro.createSelectorQuery()
+            .select('.nut-calendar-content')
+            .boundingClientRect((res) => {
+              viewHeight.value = res.height;
+            })
+            .exec();
+        }
         const viewPosition = Math.round(currentScrollTop + viewHeight.value);
         if (
           viewPosition < state.monthsData[current].cssScrollHeight + state.monthsData[current].cssHeight &&
@@ -626,7 +751,7 @@ export default create({
         ) {
           current += 1;
         }
-        if (currentScrollTop < state.monthsData[current - 1].cssScrollHeight) {
+        if (current >= 1 && currentScrollTop < state.monthsData[current - 1].cssScrollHeight) {
           current -= 1;
         }
       }

+ 178 - 55
src/packages/__VUE/calendaritem/index.vue

@@ -70,6 +70,8 @@ import { createComponent } from '@/packages/utils/create';
 const { create, translate } = createComponent('calendar-item');
 import Utils from '@/packages/utils/date';
 import requestAniFrame from '@/packages/utils/raf';
+import { useExpose } from '@/packages/utils/useExpose/index';
+
 type InputDate = string | string[];
 interface CalendarState {
   yearMonthTitle: string;
@@ -117,6 +119,10 @@ export default create({
       type: Boolean,
       default: false
     },
+    toDateAnimation: {
+      type: Boolean,
+      default: true
+    },
     poppable: {
       type: Boolean,
       default: true
@@ -227,19 +233,29 @@ export default create({
     const isEnd = (currDate: string) => {
       return Utils.isEqual(state.currDate[1], currDate);
     };
-
+    const isMultiple = (currDate: string) => {
+      if (state.currDate.length > 0) {
+        return state.currDate.some((item: any) => {
+          return Utils.isEqual(item, currDate);
+        });
+      } else {
+        return false;
+      }
+    };
     // 获取当前数据
-    const getCurrDate = (day: Day, month: MonthInfo, isRange?: boolean) => {
+    const getCurrDate = (day: Day, month: MonthInfo) => {
       return month.curData[0] + '-' + month.curData[1] + '-' + Utils.getNumTwoBit(+day.day);
     };
 
     // 获取样式
-    const getClass = (day: Day, month: MonthInfo, isRange?: boolean) => {
-      const currDate = getCurrDate(day, month, isRange);
+    const getClass = (day: Day, month: MonthInfo) => {
+      const currDate = getCurrDate(day, month);
+      const { type } = props;
       if (day.type == 'curr') {
         if (
-          (!state.isRange && Utils.isEqual(state.currDate as string, currDate)) ||
-          (state.isRange && (isStart(currDate) || isEnd(currDate)))
+          Utils.isEqual(state.currDate as string, currDate) ||
+          (type == 'range' && (isStart(currDate) || isEnd(currDate))) ||
+          (type == 'multiple' && isMultiple(currDate))
         ) {
           return `${state.dayPrefix}-active`;
         } else if (
@@ -248,7 +264,7 @@ export default create({
         ) {
           return `${state.dayPrefix}-disabled`;
         } else if (
-          state.isRange &&
+          type == 'range' &&
           Array.isArray(state.currDate) &&
           Object.values(state.currDate).length == 2 &&
           Utils.compareDate(state.currDate[0], currDate) &&
@@ -264,8 +280,10 @@ export default create({
     };
     // 确认选择时触发
     const confirm = () => {
-      if ((state.isRange && state.chooseData.length == 2) || !state.isRange) {
-        emit('choose', state.chooseData);
+      const { type } = props;
+      if ((type == 'range' && state.chooseData.length == 2) || type != 'range') {
+        let chooseData = state.chooseData.slice(0);
+        emit('choose', chooseData);
         if (props.poppable) {
           emit('update');
         }
@@ -273,17 +291,39 @@ export default create({
     };
 
     // 选中数据
-    const chooseDay = (day: Day, month: MonthInfo, isFirst: boolean, isRange?: boolean) => {
-      if (getClass(day, month, isRange) != `${state.dayPrefix}-disabled`) {
+    const chooseDay = (day: Day, month: MonthInfo, isFirst: boolean) => {
+      if (getClass(day, month) != `${state.dayPrefix}-disabled`) {
+        const { type } = props;
         let days = [...month.curData];
         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) {
+        if (type == 'multiple') {
+          if (state.currDate.length > 0) {
+            let hasIndex: any = '';
+            state.currDate.forEach((item: any, index: number) => {
+              if (item == days[3]) {
+                hasIndex = index;
+              }
+            });
+            if (isFirst) {
+              state.chooseData.push([...days]);
+            } else {
+              if (hasIndex !== '') {
+                state.currDate.splice(hasIndex, 1);
+                state.chooseData.splice(hasIndex, 1);
+              } else {
+                state.currDate.push(days[3]);
+                state.chooseData.push([...days]);
+              }
+            }
+          } else {
+            state.currDate = [days[3]];
+            state.chooseData = [[...days]];
+          }
+        } else if (type == 'range') {
+          let curDataLength = Object.values(state.currDate).length;
+          if (curDataLength == 2 || curDataLength == 0) {
             state.currDate = [days[3]];
           } else {
             if (Utils.compareDate(state.currDate[0], days[3])) {
@@ -292,20 +332,24 @@ export default create({
               Array.isArray(state.currDate) && state.currDate.unshift(days[3]);
             }
           }
+
           if (state.chooseData.length == 2 || !state.chooseData.length) {
-            state.chooseData = [...days];
+            state.chooseData = [[...days]];
           } else {
-            if (Utils.compareDate(state.chooseData[3], days[3])) {
-              state.chooseData = [[...state.chooseData], [...days]];
+            if (Utils.compareDate(state.chooseData[0][3], days[3])) {
+              state.chooseData = [...state.chooseData, [...days]];
             } else {
-              state.chooseData = [[...days], [...state.chooseData]];
+              state.chooseData = [[...days], ...state.chooseData];
             }
           }
+        } else {
+          state.currDate = days[3];
+          state.chooseData = [...days];
         }
         if (!isFirst) {
           // 点击日期 触发
           emit('select', state.chooseData);
-          if (props.isAutoBackFill) {
+          if (props.isAutoBackFill || !props.poppable) {
             confirm();
           }
         }
@@ -437,10 +481,8 @@ export default create({
       state.endData = splitDate(propEndDate);
 
       // 根据是否存在默认时间,初始化当前日期,
-      if (!props.defaultValue || (Array.isArray(props.defaultValue) && props.defaultValue.length <= 0)) {
-        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 (props.defaultValue || (Array.isArray(props.defaultValue) && props.defaultValue.length > 0)) {
+        state.currDate = props.type != 'one' ? [...props.defaultValue] : props.defaultValue;
       }
 
       // 判断时间范围内存在多少个月
@@ -456,7 +498,9 @@ export default create({
       if (endDate.year - startDate.year > 0) {
         monthsNum = monthsNum + 12 * (endDate.year - startDate.year);
       }
-
+      if (monthsNum <= 0) {
+        monthsNum = 1;
+      }
       // 设置月份数据
       getMonth(state.startData, 'next');
 
@@ -464,48 +508,87 @@ export default create({
       do {
         getMonth(getCurrData('next'), 'next');
       } while (i++ < monthsNum);
-
       state.monthsNum = monthsNum;
 
       // 日期转化为数组,限制初始日期。判断时间范围
-      if (state.isRange && Array.isArray(state.currDate)) {
-        if (propStartDate && Utils.compareDate(state.currDate[0], propStartDate)) {
-          state.currDate.splice(0, 1, propStartDate);
+      if (props.type == 'range' && Array.isArray(state.currDate)) {
+        if (state.currDate.length > 0) {
+          if (propStartDate && Utils.compareDate(state.currDate[0], propStartDate)) {
+            state.currDate.splice(0, 1, propStartDate);
+          }
+          if (propEndDate && Utils.compareDate(propEndDate, state.currDate[1])) {
+            state.currDate.splice(1, 1, propEndDate);
+          }
+          state.defaultData = [...splitDate(state.currDate[0]), ...splitDate(state.currDate[1])];
         }
-        if (propEndDate && Utils.compareDate(propEndDate, state.currDate[1])) {
-          state.currDate.splice(1, 1, propEndDate);
+      } else if (props.type == 'multiple' && Array.isArray(state.currDate)) {
+        if (state.currDate.length > 0) {
+          let defaultArr: string[] = [];
+          let obj: any = {};
+          state.currDate.forEach((item: string, index: number) => {
+            if (
+              propStartDate &&
+              !Utils.compareDate(item, propStartDate) &&
+              propEndDate &&
+              !Utils.compareDate(propEndDate, item)
+            ) {
+              if (!obj.hasOwnProperty(item)) {
+                defaultArr.push(item);
+                obj[item] = item;
+              }
+            }
+          });
+          state.currDate = [...defaultArr];
+          state.defaultData = [...splitDate(defaultArr[0])];
         }
-        state.defaultData = [...splitDate(state.currDate[0]), ...splitDate(state.currDate[1])];
       } else {
-        if (propStartDate && Utils.compareDate(state.currDate as string, propStartDate)) {
-          state.currDate = propStartDate;
-        } else if (propEndDate && !Utils.compareDate(state.currDate as string, propEndDate)) {
-          state.currDate = propEndDate;
+        if (state.currDate) {
+          if (propStartDate && Utils.compareDate(state.currDate as string, propStartDate)) {
+            state.currDate = propStartDate;
+          } else if (propEndDate && !Utils.compareDate(state.currDate as string, propEndDate)) {
+            state.currDate = propEndDate;
+          }
+          state.defaultData = [...splitDate(state.currDate as string)];
         }
-        state.defaultData = [...splitDate(state.currDate as string)];
       }
       // 设置默认可见区域
       let current = 0;
       let lastCurrent = 0;
-      state.monthsData.forEach((item, index) => {
-        if (item.title == translate('monthTitle', state.defaultData[0], state.defaultData[1])) {
-          current = index;
-        }
-        if (state.isRange) {
-          if (item.title == translate('monthTitle', state.defaultData[3], state.defaultData[4])) {
-            lastCurrent = index;
+      if (state.defaultData.length > 0) {
+        state.monthsData.forEach((item, index) => {
+          if (item.title == translate('monthTitle', state.defaultData[0], state.defaultData[1])) {
+            current = index;
           }
-        }
-      });
+          if (props.type == 'range') {
+            if (item.title == translate('monthTitle', state.defaultData[3], state.defaultData[4])) {
+              lastCurrent = index;
+            }
+          }
+        });
+      }
+
       setDefaultRange(monthsNum, current);
       state.currentIndex = current;
       state.yearMonthTitle = state.monthsData[state.currentIndex].title;
-      // 设置当前选中日期
-      if (state.isRange) {
-        chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true);
-        chooseDay({ day: state.defaultData[5], type: 'curr' }, state.monthsData[lastCurrent], true, true);
-      } else {
-        chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true);
+      if (state.defaultData.length > 0) {
+        // 设置当前选中日期
+        if (props.type == 'range') {
+          chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true);
+          chooseDay({ day: state.defaultData[5], type: 'curr' }, state.monthsData[lastCurrent], true);
+        } else if (props.type == 'multiple') {
+          [...state.currDate].forEach((item: any) => {
+            let dateArr = splitDate(item);
+            let current = state.currentIndex;
+            state.monthsData.forEach((item, index) => {
+              if (item.title == translate('monthTitle', dateArr[0], dateArr[1])) {
+                current = index;
+              }
+            });
+            chooseDay({ day: dateArr[2], type: 'curr' }, state.monthsData[current], true);
+          });
+        } else {
+          chooseDay({ day: state.defaultData[2], type: 'curr' }, state.monthsData[state.currentIndex], true);
+        }
       }
 
       let lastItem = state.monthsData[state.monthsData.length - 1];
@@ -520,6 +603,43 @@ export default create({
       });
       state.avgHeight = Math.floor(containerHeight / (monthsNum + 1));
     };
+    const scrollToDate = (date: string) => {
+      if (Utils.compareDate(date, state.propStartDate)) {
+        date = state.propStartDate;
+      } else if (!Utils.compareDate(date, state.propEndDate)) {
+        date = state.propEndDate;
+      }
+      let dateArr = splitDate(date);
+      state.monthsData.forEach((item, index) => {
+        if (item.title == translate('monthTitle', dateArr[0], dateArr[1])) {
+          if (months.value) {
+            let distance = state.monthsData[index].cssScrollHeight - months.value.scrollTop;
+            if (props.toDateAnimation) {
+              let flag = 0;
+              let interval = setInterval(() => {
+                flag++;
+                if (months.value) {
+                  let offset = distance / 10;
+                  months.value.scrollTop = months.value.scrollTop + offset;
+                }
+
+                if (flag >= 10) {
+                  clearInterval(interval);
+                  if (months.value) {
+                    months.value.scrollTop = state.monthsData[index].cssScrollHeight;
+                  }
+                }
+              }, 40);
+            } else {
+              months.value.scrollTop = state.monthsData[index].cssScrollHeight;
+            }
+          }
+        }
+      });
+    };
+    useExpose({
+      scrollToDate
+    });
     // 设置当前可见月份
     const setDefaultRange = (monthsNum: number, current: number) => {
       if (monthsNum >= 3) {
@@ -538,7 +658,7 @@ export default create({
     };
     // 区间选择&&当前月&&选中态
     const isActive = (day: Day, month: MonthInfo) => {
-      return state.isRange && day.type == 'curr' && getClass(day, month) == 'calendar-month-day-active';
+      return props.type == 'range' && day.type == 'curr' && getClass(day, month) == 'calendar-month-day-active';
     };
 
     // 是否有开始提示
@@ -566,6 +686,9 @@ export default create({
     };
     // 滚动处理事件
     const mothsViewScroll = (e: any) => {
+      if (state.monthsData.length <= 1) {
+        return;
+      }
       const currentScrollTop = e.target.scrollTop;
       let current = Math.floor(currentScrollTop / state.avgHeight);
       if (current == 0) {
@@ -593,7 +716,7 @@ export default create({
         ) {
           current += 1;
         }
-        if (currentScrollTop < state.monthsData[current - 1].cssScrollHeight) {
+        if (current >= 1 && currentScrollTop < state.monthsData[current - 1].cssScrollHeight) {
           current -= 1;
         }
       }

+ 47 - 5
src/sites/mobile-taro/vue/src/dentry/pages/calendar/index.vue

@@ -15,7 +15,7 @@
         @close="closeSwitch('isVisible')"
         @choose="setChooseValue"
         :start-date="`2022-01-11`"
-        :end-date="`2022-11-11`"
+        :end-date="`2022-11-30`"
       >
       </nut-calendar>
     </div>
@@ -41,6 +41,26 @@
       </nut-calendar>
     </div>
 
+    <div>
+      <nut-cell
+        :show-icon="true"
+        title="选择多个日期"
+        :desc="date7 && date7.length ? `已选择${date7.length}个日期` : '请选择'"
+        @click="openSwitch('isVisible7')"
+      >
+      </nut-cell>
+      <nut-calendar
+        v-model:visible="isVisible7"
+        :default-value="date7"
+        type="multiple"
+        :start-date="`2022-01-01`"
+        :end-date="`2022-09-10`"
+        @close="closeSwitch('isVisible7')"
+        @choose="setChooseValue7"
+      >
+      </nut-calendar>
+    </div>
+
     <h2>快捷选择</h2>
     <div>
       <nut-cell
@@ -91,6 +111,7 @@
       >
       </nut-cell>
       <nut-calendar
+        ref="calendarRef"
         v-model:visible="isVisible5"
         :default-value="date5"
         type="range"
@@ -101,6 +122,7 @@
       >
         <template v-slot:btn>
           <view class="wrapper">
+            <div class="d_div"> <span class="d_btn" @click="goDate">去某个时间</span></div>
             <view class="d_div"> <span class="d_btn" @click="clickBtn">最近七天</span></view>
             <view class="d_div"> <span class="d_btn" @click="clickBtn1">当月</span></view>
           </view>
@@ -148,7 +170,7 @@
 </template>
 
 <script lang="ts">
-import { reactive, toRefs } from 'vue';
+import { reactive, toRefs, ref } from 'vue';
 import Utils from '../../../../../../../packages/utils/date';
 
 interface TestCalendarState {
@@ -167,26 +189,30 @@ interface TestCalendarState {
   date4: string[];
   date5: string[];
   date6: string[];
+  date7: string[];
 }
 export default {
   props: {},
   setup() {
+    const calendarRef = ref(null);
     const state: TestCalendarState = reactive({
       isVisible: false,
-      date: '',
+      date: '2022-02-01',
       dateWeek: '',
-      date1: ['2019-12-23', '2019-12-26'],
+      date1: ['2020-01-23', '2020-01-26'],
       date2: '2020-07-08',
       date3: '',
       date4: ['2021-12-23', '2021-12-26'],
       date5: ['2021-12-23', '2021-12-26'],
       date6: [],
+      date7: [],
       isVisible1: false,
       isVisible2: false,
       isVisible3: false,
       isVisible4: false,
       isVisible5: false,
-      isVisible6: false
+      isVisible6: false,
+      isVisible7: false
     });
     const openSwitch = (param: string) => {
       state[`${param}`] = true;
@@ -225,6 +251,13 @@ export default {
     const setChooseValue6 = (param: string) => {
       state.date6 = [...[param[0][3], param[1][3]]];
     };
+    const setChooseValue7 = (chooseData: any) => {
+      let dateArr = chooseData.map((item: any) => {
+        return item[3];
+      });
+      console.log('changevalue 7 ', chooseData, dateArr);
+      state.date7 = [...dateArr];
+    };
     const clickBtn = (param: string) => {
       let date = [Utils.date2Str(new Date()), Utils.getDay(6)];
       state.date5 = date;
@@ -238,6 +271,12 @@ export default {
       let currMonthDays = Utils.getMonthDays(year + '', month + '');
       state.date5 = [`${yearMonth}-01`, `${yearMonth}-${currMonthDays}`];
     };
+    const goDate = () => {
+      console.log(calendarRef.value);
+      if (calendarRef.value) {
+        calendarRef.value.calendarRef.scrollToDate('2022-04-01');
+      }
+    };
     return {
       ...toRefs(state),
       openSwitch,
@@ -251,6 +290,9 @@ export default {
       setChooseValue6,
       clickBtn,
       clickBtn1,
+      setChooseValue7,
+      goDate,
+      calendarRef,
       select
     };
   }