返回

Vue Datepicker 选中过去日期自动滚动/定位的解决方案

vue.js

Vue Datepicker 选中过去日期时如何自动滚动?

使用 Vue Datepicker 组件构建日期选择器时,遇到一个问题:当选择一个过去的日期范围时,日历视图不会自动滚动到所选范围的开始日期,需要手动点击月份切换按钮才能看到。例如,如果当前是12月5日,选择“上一季度”(7月1日至9月30日),日期范围会被正确选中,但日历依然显示当前月份,需要手动向前翻几个月。

怎样让日历在选中日期后,“自动滚动” 或 “自动定位” 到选定日期的位置,使选中的日期范围始终可见?

问题原因分析

Vue Datepicker 默认行为是在日期选择后不会自动调整月份视图。v-model 绑定了选中的日期范围,但 multi-calendars 模式下,组件并不会主动计算并切换到选中范围所在的月份。组件本身没有提供直接的 API 来实现这个“自动滚动”的功能。

解决方案

要实现自动滚动,核心思路是:在日期范围更新后,手动设置 Vue Datepicker 组件显示的月份

可以通过监听 @update:model-value 事件 (或者相应的范围选择事件),获取选中的日期范围,然后计算出起始日期所在的月份,并通过修改控制日历显示的月份的属性值来实现自动滚动。以下提供几种具体实现方案:

方案一:利用 month 属性 (推荐)

Vue Datepicker 组件的 month 属性可以直接控制显示的月份。我们可以在日期改变的时候,同步更新month属性的值。

  1. 原理:
    month 属性绑定一个响应式变量,该变量代表当前显示的月份。当选中新的日期范围后,计算起始日期的月份,并更新这个响应式变量,Vue Datepicker 会自动重新渲染并显示对应的月份。

  2. 代码示例:

<template>
  <div>
    <VueDatePicker
      range
      :multi-calendars="!isMobile"
      inline
      :enable-time-picker="false"
      select-text="Apply"
      :ui="customStyles"
      @update:model-value="handleDate"
      week-start="0"
      v-model="date"
      :max-date="add(new Date(), { days: -1 })"
      month-name-format="long"
      :month="displayMonth"
    />
  </div>
</template>

<script>
import { ref, watch, nextTick } from 'vue';
import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css';
import { add, getMonth, getYear } from 'date-fns';

export default {
  components: {
    VueDatePicker,
  },
  data() {
    return {
      date: null, // 选中的日期范围
      isMobile: false, // 根据实际情况设置
      customStyles: {},//根据实际情况设置
       displayMonth: [], // 用于控制显示的月份,需要是数组,每个元素代表一个日历面板的月份。
    };
  },
 watch: {
   date(newDate) {
       //日期更新后 触发
       if(newDate && newDate[0]){
          nextTick(()=>{
               this.displayMonth = [{month:getMonth(newDate[0]), year: getYear(newDate[0]) }];

               // 如果是双日历面板
              if (!this.isMobile) {
                     this.displayMonth.push({
                         month: getMonth(add(newDate[0], { months: 1 })),
                         year: getYear(add(newDate[0], { months: 1 })),
                    });
               }
           })
      }
   }
  },
  methods: {
    handleDate(newDate) {
      //  什么也不需要做了,交给watcher去执行。
      // 本身@update:model-value="handleDate" 就可以不要, 可以用watch完全代替, 但是如果你需要在回调里处理别的逻辑,可以留着。
    },
      onRangeStart(){
       // ... 可以选择是否保留
    },
      onRangeEnd(){
       // ... 可以选择是否保留
    }
  },
  mounted(){
    //首次加载也要设定初始月份
       this.displayMonth = [{month:getMonth(new Date()), year: getYear(new Date()) }];
      //如果是双日历模式
      if(!this.isMobile){
            this.displayMonth.push({
                  month: getMonth(add(new Date(), { months: 1 })),
                  year: getYear(add(new Date(), { months: 1 })),
           });
       }
  }
};
</script>
  1. 代码说明:
    • 使用了 date-fns库做日期运算(如果没有安装请自行安装npm install date-fns)。
    • displayMonth:这个响应式变量用于控制 Vue Datepicker 显示的月份,它是一个数组。每个元素代表日历显示的一个面板月份,单日历时只有一个元素,双日历时则有两个元素。
    • watch: 当选择的日期date更改时,这个侦听器会被触发。计算选中范围的起始日期,将开始日期的年月,设置到displayMonth中,触发日历组件的显示更新。
    • nextTick: 在dom更新后执行。
    • mounted钩子: 用于初始化日历面板显示的月份.

方案二: 使用 scroll-to-date prop (低版本可能不适用)

Vue Datepicker(版本需较新)提供了一个scroll-to-date prop,可以直接用于滚动到指定的日期。这个方法更简洁。

  1. 原理:

    scroll-to-date 属性接受一个 Date 对象,当该属性的值发生变化时,Vue Datepicker 会自动滚动到该日期所在的月份。

  2. 代码示例:

 <template>
  <div>
    <VueDatePicker
      range
      :multi-calendars="!isMobile"
      inline
      :enable-time-picker="false"
      select-text="Apply"
      :ui="customStyles"
      @update:model-value="handleDate"
      week-start="0"
      v-model="date"
      :max-date="add(new Date(), { days: -1 })"
      month-name-format="long"
      :scroll-to-date="scrollToDate"
    />
  </div>
</template>

<script>
import { ref, watch } from 'vue';
import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css';
import { add} from 'date-fns';

export default {
  components: {
    VueDatePicker,
  },
 data() {
    return {
       date: null, // 选中的日期范围
       scrollToDate: new Date(),  //用于自动滚动的日期。
        isMobile: false,
      customStyles: {},//根据实际情况设置

     }
  },
  watch:{
     date(newVal){
       if (newVal && newVal[0]) {
          this.scrollToDate = newVal[0]; //更新滚动到的目标日期.
        }
     }
  },
  methods: {
    handleDate(newDate) {
     // 同样的,不需要额外操作.
    },
    onRangeStart(){
      //
     },
     onRangeEnd(){
     //
    }
  },

};
</script>

  1. 代码说明:

    • scrollToDate:这个响应式变量用作 scroll-to-date 属性的值,初始设置为当前日期.
    • watch: 当日期更新,就更新 scrollToDate 的值为新的起始日期,这样就能直接触发日历的自动滚动。

方案三: 使用 action-month 方法 (进阶,仅适用特定版本)

一些较旧版本的Vue Datepicker (v3.x版本适用, 最新版本v4.x 已经删除了这个api) 可能没有 scroll-to-date prop, 但可能有一个名为action-month 的方法(通过ref引用访问)。这种方式不太推荐,但如果你的版本确实只支持此API,可以考虑:

  1. 原理:

    通过 ref 获取 Vue Datepicker 组件实例,然后直接调用它的 action-month 方法,传入要显示的月份。

  2. 代码示例:

<template>
   <div>
        <VueDatePicker
          ref="datepicker"
          range
          :multi-calendars="!isMobile"
          inline
          :enable-time-picker="false"
           select-text="Apply"
           :ui="customStyles"
           @update:model-value="handleDate"
            week-start="0"
          v-model="date"
         :max-date="add(new Date(), { days: -1 })"
          month-name-format="long"
        />
</div>
</template>

 <script>
    import {  ref,watch} from 'vue';
   import VueDatePicker from '@vuepic/vue-datepicker';
   import '@vuepic/vue-datepicker/dist/main.css';
    import { add,getMonth, getYear } from 'date-fns';

     export default {
          components: {
          VueDatePicker,
      },
  data() {
        return {
               date: null,
          datepicker: null, // 用于保存组件的引用
           isMobile: false, // 根据实际情况设置
           customStyles: {} //根据实际情况设置

              };
       },
  watch: {
   date(newDate) {
            if (newDate && newDate[0]) {
                this.scrollToMonth(newDate[0])
          }
  }
  },
        methods: {
          handleDate(newDate) {
          },
           onRangeStart(){
            //...
           },
        onRangeEnd(){
          //...
      },
      scrollToMonth(date){
           if(this.$refs.datepicker){

             this.$refs.datepicker.actionMonth({month: getMonth(date), year:getYear(date)});

             // 如果是双日历
                if(!this.isMobile){
                 this.$refs.datepicker.actionMonth({month: getMonth(add(date,{months:1})), year:getYear(add(date,{months:1}))});
            }
         }
        }
   },

 };
</script>
  1. 代码说明:
  • ref="datepicker":为 Vue Datepicker 组件添加 ref 属性,以便在 Vue 实例中访问。
  • $refs.datepicker:通过 $refs 访问组件实例。
  • actionMonth():调用组件的内部方法来改变月份(注意,此方法可能随时更改或弃用)。

总结

以上三种方法都能解决 Vue Datepicker 选中过去日期时不自动滚动的问题。 优先选择 方案一或方案二 ,这两种方案利用公开的API,更加可靠和易于维护。 方案三依赖于内部API,稳定性较差,更适合作为备选方案。