文章目录
背景
背景
:ElementUI
的DatePicker
组件是不能设置时区的,组件显示的时间所使用的时区是打开页面的客户端所在的系统所使用的时区。
需求
:我在本地打开页面,使用DatePicker
组件筛选日志,组件中超过今天的时间置灰不可选。这个“今天”
所使用的时区是特定的时区(比如美国纽约)。
譬如
:本地(中国)现在是2020-08-03 08:51:29,而美国纽约现在是2020-08-02 20:51:29,若DatePicker
组件设置的时区是America/New_York
,那么组件中则需要置灰3号以及之后的日期了(如图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
组件的引入和使用
- 打开
\src\components\date-picker\src\picker\date-picker.js
//省略若干代码...
export default {
mixins: [Picker],
name: 'MyDatePicker', // 这里原来是ElDatePicker,现在修改为MyDatePicker
//省略若干代码...
}
- 打开
\src\main.js
//省略若干代码...
import MyDatePicker from '@/components/date-picker'
Vue.use(MyDatePicker)
- 组件中可以这样用
(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
,则isToday
为true
,则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属性值
重点来了,到这步我们不得不稍微读一下源码了。
- 从入口文件开始:
\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
模块。
\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);
}
}
可以看到这个模块
mixins
了Picker
,我们再接着看Picker
。
\src\components\date-picker\src\picker.vue
date-picker.js
mixins 了picker.vue
,两者合起来可看成是一个组件,这个组件也就是我们暴露出去的MyDatePicker
组件,也就是说,我们通过<my-date-picker :timezone=''></my-date-picker>
传进来的timezone
属性值可以在picker.vue
的props
接收到。于是picker.vue
修改后的代码如下:
<template>
...
</template>
<script>
// 省略若干import
import moment from 'moment-timezone'
// 省略若干代码
export default {
props: {
// 省略若干代码
timezone: {
type: String,
default: moment.tz.guess() // 没有传则使用本地的时区
}
},
// 省略若干代码
}
</script>
MyDatePicker
组件根据不同的type
选择不同的子组件(DatePanel
、DateRangePanel
、MonthRangePanel
等)进行挂载,我们可以在它挂载的时候把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
或是DateRangePanel
的data
中都包含了timezone
,而它们都调用了子组件DateTable
,只要在调用子组件DateTable
的时候把timezone
传进去,Step3
的问题就迎刃而解了。继续……
\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>
-
\src\components\date-picker\src\panel\date-range.vue
这里和上一步一样,我就不赘述了,只是注意,DateRangePanel
组件调用了两次DateTable
组件,记得每一次使用<date-talbe>
的时候把timezone
传进去。 -
\src\components\date-picker\src\basic\date-table.vue
timezone
通过属性传给DateTable
,DateTable
自然是通过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
属性值这个问题,我们继续解决“今天”高亮
这个问题。
前面提到,关键在于now
,now
要和时区
相关。而现在我们已经拿到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;
结语
如果你也遇到了类似的问题,希望这篇文章能帮到你,若是哪位大佬有更好的解决办法,也望不吝赐教,谢谢!