当产品说elementUI的datepicker要加上时区……

背景

背景ElementUIDatePicker组件是不能设置时区的,组件显示的时间所使用的时区是打开页面的客户端所在的系统所使用的时区。
需求:我在本地打开页面,使用DatePicker组件筛选日志,组件中超过今天的时间置灰不可选。这个“今天”所使用的时区是特定的时区(比如美国纽约)。
譬如:本地(中国)现在是2020-08-03 08:51:29,而美国纽约现在是2020-08-02 20:51:29,若DatePicker组件设置的时区是America/New_York,那么组件中则需要置灰3号以及之后的日期了(如图2)。
图1
图2

那么现在来看看我是怎么实现这个需求的吧(就这?)

看我

我不能直接修改node_modules\element-ui下的DatePicker组件代码呀,因为若是项目挪到其他环境,运行npm install,拿到的DatePicker代码还是它原来的代码,并不是你修改过的代码。

于是,我找到node_modules\element-ui\packages\date-picker这个文件夹,右键点击date-picker选择copy,然后来到项目名\src\components(这里选择你放组件的目录,不一定跟我的一样),右键点击components选择paste,这样就把DatePicker的组件代码拷贝到我们的组件目录下了。

我们可以在DatePicker组件的基础上实现一个自己的DatePicker,这样你就可以想怎么改就怎么改,简直就是为所欲为了哈哈。

对了,涉及到时区数据和时区转换,我用了moment-timezone包,所以你需要预先装好这个包:npm install moment-timezone -S

Step1 自定义DatePicker组件的引入和使用

  1. 打开\src\components\date-picker\src\picker\date-picker.js
//省略若干代码...
export default {
  mixins: [Picker],
  name: 'MyDatePicker', // 这里原来是ElDatePicker,现在修改为MyDatePicker
  //省略若干代码...
}
  1. 打开\src\main.js
//省略若干代码...
import MyDatePicker from '@/components/date-picker'
Vue.use(MyDatePicker)
  1. 组件中可以这样用(test.vue)
<template>
  <my-date-picker
    v-model="dates"
    type="daterange"
    :clearable="false"
    start-placeholder="Begin Date"
    end-placeholder="End Date"
    :default-time="['00:00:00', '23:59:59']"
    :timezone="getTimeZone()"
    :picker-options="{disabledDate: time => {return time.getTime() > today.getTime()}}"
  >
  </my-date-picker>
</template>
<!-- today是一个Date对象,picker-options属性设置了超过“今天”的日期都置灰 -->
<!-- timezone属性是向组件传递时区信息的,后面我们将修改组件代码以支持这个用法 -->

Step2 超过“今天”的日期都置灰

我们是通过:picker-options="{disabledDate: time => {return time.getTime() > today.getTime()}}"这一行代码来实现这个功能的。
关键在于today这个Date对象,若是time > today则置灰。比如按照America/New_York时区,现在应该是2020-08-02 20:51:29,也就是说只要大于2020-08-02 23:59:59的时间,都要置灰。于是

<template>
  <my-date-picker
    v-model="dates"
    type="daterange"
    :clearable="false"
    start-placeholder="Begin Date"
    end-placeholder="End Date"
    :default-time="['00:00:00', '23:59:59']"
    :timezone="getTimeZone()"
    :picker-options="{disabledDate: time => {return time.getTime() > today.getTime()}}"
  >
  </my-date-picker>
</template>

<script>
import moment from 'moment-timezone'
export default {
  name: 'MyCompo',
  data () {
    return {
      today: ''
    }
  },
  mounted () {
    // this.getTimeZone() 获取你想要使用的时区,这里我获取到的是`America/New_York`
    let str = moment().tz(this.getTimeZone()).set({'hour': 23, 'minute': 59, 'second': 59}).format('YYYY/MM/DD HH:mm:ss')
    this.today = new Date(str)
  }
}
</script>

效果如图:

在这里插入图片描述

Emmm,置灰没问题了,但是好像还有点不OK。3被高亮了(“今天”会高亮显示),按照America/New_York时区,“今天”应该是2号才对。继续……

Step3 “今天”高亮

我们右键点击“3”选择检查,看一下dom元素

在这里插入图片描述
可以看到,它之所以会高亮,就是因为它使用了today类。
接下来我们康康代码中在哪里添加的today类,组件目录下搜索today

在这里插入图片描述
在这里插入图片描述
搜出来有3个文件,分别对应于日面板月面板年面板,由于时区只会影响到,不会影响到,所以我们只需要看date-table.vue就可以了。
我把关键的几行代码摘出来:

const now = getDateTimestamp(new Date());
const isToday = time === now;
if (isToday) {
  cell.type = 'today';
}

if ((cell.type === 'normal' || cell.type === 'today') && !cell.disabled) {
  classes.push('available');
  if (cell.type === 'today') {
    classes.push('today');
  }
} else {
  classes.push(cell.type);
}

可以看到关键在于now,若是time === now,则isTodaytrue,则cell.type的值为today,那么classes.push(cell.type)等价于classes.push('today'),即添加了today类。

也就是说,now要和时区相关。按照America/New_York时区,now应该是2020-08-02,这样才会高亮2号,而不是3号。
注意,date-table.vue是组件内部代码,所以代码里用到的时区信息应该是在使用组件的时候由timezone属性传入,这样才能使组件和外部代码的耦合度最低。

OK,那么我们来看下,date-table.vue文件里的代码要如何拿到<my-date-picker :timezone=''></my-date-picker>这里传进来的timezone属性值。

Step4 传递timezone属性值

重点来了,到这步我们不得不稍微读一下源码了。

  1. 从入口文件开始:\src\components\date-picker\index.js
import DatePicker from './src/picker/date-picker';

/* istanbul ignore next */
DatePicker.install = function install(Vue) {
  Vue.component(DatePicker.name, DatePicker);
};

export default DatePicker;

可以看到入口文件主要就是加载了DatePicker模块,为它加了一个安装函数install之后把它暴露出去。所以重点是DatePicker模块。

  1. \src\components\date-picker\src\picker\date-picker.js
import Picker from '../picker';
import DatePanel from '../panel/date';
import DateRangePanel from '../panel/date-range';
import MonthRangePanel from '../panel/month-range';

const getPanel = function(type) {...}
export default {
  mixins: [Picker],
  name: 'MyDatePicker',
  props: {...},
  watch: {
    type(type) {
      if (this.picker) {
        this.unmountPicker();
        this.panel = getPanel(type);
        this.mountPicker();
      } else {
        this.panel = getPanel(type);
      }
    }
  },

  created() {
    this.panel = getPanel(this.type);
  }
}

可以看到这个模块mixinsPicker,我们再接着看Picker

  1. \src\components\date-picker\src\picker.vue

date-picker.jsmixins 了picker.vue,两者合起来可看成是一个组件,这个组件也就是我们暴露出去的MyDatePicker组件,也就是说,我们通过<my-date-picker :timezone=''></my-date-picker>传进来的timezone属性值可以在picker.vueprops接收到。于是picker.vue修改后的代码如下:

<template>
...
</template>
<script>
// 省略若干import
import moment from 'moment-timezone'
// 省略若干代码
export default {
  props: {
    // 省略若干代码
    timezone: {
      type: String,
      default: moment.tz.guess() // 没有传则使用本地的时区
    }
  },
  // 省略若干代码
}
</script>

MyDatePicker组件根据不同的type选择不同的子组件(DatePanelDateRangePanelMonthRangePanel等)进行挂载,我们可以在它挂载的时候把timezone传给子组件:

// picker.vue
// ...
<script>
//...
export default {
  //...
  methods: {
    //...
    mountPicker () {
      let tempFun = this.panel.data
      this.panel.data = () => {return {
        timezone: this.timezone, // add timezone to panel component
        ...tempFun()
      }}
      this.picker = new Vue(this.panel).$mount();
      //...
    }
    //...
  }
}
</script>

现在DatePanel或是DateRangePaneldata中都包含了timezone,而它们都调用了子组件DateTable,只要在调用子组件DateTable的时候把timezone传进去,Step3的问题就迎刃而解了。继续……

  1. \src\components\date-picker\src\panel\date.vue
<template>
  <!-- 省略很多代码 -->
  <date-table
    v-show="currentView === 'date'"
    @pick="handleDatePick"
    :selection-mode="selectionMode"
    :first-day-of-week="firstDayOfWeek"
    :value="value"
    :default-value="defaultValue ? new Date(defaultValue) : null"
    :date="date"
    :cell-class-name="cellClassName"
    :disabled-date="disabledDate"
    :timezone="timezone"> <!-- 把timezone传给DateTable子组件 -->
  </date-table>
  <!-- 省略很多代码 -->
</template>
  1. \src\components\date-picker\src\panel\date-range.vue
    这里和上一步一样,我就不赘述了,只是注意,DateRangePanel组件调用了两次DateTable组件,记得每一次使用<date-talbe>的时候把timezone传进去。

  2. \src\components\date-picker\src\basic\date-table.vue
    timezone通过属性传给DateTableDateTable自然是通过props来接收了:

<template>
...
</template>
<script>
// 省略若干import
import moment from 'moment-timezone'
// 省略若干代码
export default {
  props: {
    // 省略若干代码
    timezone: {
      type: String,
      default: moment.tz.guess() // 没有传则使用本地的时区
    }
  },
  // 省略若干代码
}
</script>

Step5 “今天”高亮

解决了Step3中提到的date-table.vue文件里的代码要如何拿到<my-date-picker :timezone=''></my-date-picker>这里传进来的timezone属性值这个问题,我们继续解决“今天”高亮这个问题。

前面提到,关键在于nownow要和时区相关。而现在我们已经拿到timezone了,所以,找到now,修改now,即可。于是代码如下:

// src\components\date-picker\src\basic\date-table.vue
// ...
<script>
// ...
export default {
  // ...
  computed: {
    rows() {
      // ...
      const now = getDateTimestamp(new Date( moment().tz(this.timezone).format('YYYY/MM/DD HH:mm:ss') )); // 这一行原来是 const now = getDateTimestamp(new Date());
      // ...
    }
  }
}
</script>

效果如下:

在这里插入图片描述

这样,就让日期组件按照你传的时区来高亮或置灰了👍

Step6 设置组件所使用的语言

我前面的截图都是已经设置过语言为英语了的,因为不是每个人都需要设置语言,所以我把这一点放到最后说。
如果要设置MyDatePicker组件的语言,可以这样做:
打开\src\components\date-picker\index.js

import DatePicker from './src/picker/date-picker';
import locale from 'element-ui/lib/locale/lang/en'
import { use } from 'element-ui/src/locale/'

/* istanbul ignore next */
DatePicker.install = function install(Vue) {
  use(locale);
  Vue.component(DatePicker.name, DatePicker);
};

export default DatePicker;

结语

如果你也遇到了类似的问题,希望这篇文章能帮到你,若是哪位大佬有更好的解决办法,也望不吝赐教,谢谢!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值