在日常工作中需要填写日期的时候,会用到日期选择器,来方便的进行日、月、年的选择。这里我们会用Vue来实现一个日期选择器,效果如下:

实现功能:日期选择弹出层

选择天面板

选择月面版

选择年面版

支持用户输入

CSS样式美化

组件的使用方式很简单,只需要传入对应的日期对象value即可:

export default {

name: 'DatePicker',

data () {

return {

value: undefined

};

},

};

下面就开始一步步实现组件吧 !

日期选择弹出层

当用户点击输入框时,会弹出日期选择面板。在组件内部,会通过visible来控制弹出层的显示隐藏:

class="go-date-picker-input"

@focus="visible=true"

prefix="calendar"

placeholder="请选择时间"

>

export default {

name: 'GoDatePicker',

props: {

value: {

type: Date,

default: () => new Date()

}

},

data () {

return {

visible: false,

};

},

mounted () {

document.body.addEventListener('click', this.onClickBody);

},

beforeDestroy () {

document.body.removeEventListener('click', this.onClickBody);

},

methods: {

onClickBody (e) { // Vue内部会自动帮我们修改this指向 const { picker} = this.$refs;

// 过滤掉弹出层和日期选择器内的元素 if (picker.contains(e.target)) {

return;

}

this.visible = false;

},

}

};

当输入框激活时,显示弹出层,当点击外部区域时,会隐藏弹出层。需要注意的是当点击date-picker内部,弹出层并不会隐藏。

Node.contains(otherNode)可以用来判断otherNode是否是Node的后代节点(包括Node本身),返回Boolean。这里我们通过这个api来判断点击的元素e.target是否在date-picker内部,如果是的话不会隐藏弹出层,可以让用户在date-picker中进行相应的操作。

展示天面板

当用户点击输入框后,首先弹出的是天面板,面板头部会显示当前的年月信息。面板主体有 6 行,会分别包括上月、当前月、下月的天数:

显示头部信息

我们会对传入的value进行拷贝,在内部通过tempValue来进行保存,并且监听value的变化,保证tempValue可以获取到value的最新值。当我们在内部切换日期面板而没有选中某个日期时,就不会更新value,而只是更新内部的tempValue属性:

export default {

name: 'GoDatePicker',

props: {

value: {

type: Date,

default: () => new Date()

}

},

components: { PickerDays, PickerMonths, PickerYears },

data () {

return {

visible: false,

mode: 'picker-days',

tempValue: cloneDate(this.value),

};

},

computed: {

formatDate () {

const [year, month, day] = getYearMonthDay(this.tempValue);

return { year, month: month + 1, day };

},

},

watch: {

value (val) {

this.tempValue = cloneDate(val);

}

},

// some code ...};

formatDate计算属性会通过tempValue计算出当前的年、月、日,方便展示。

显示内容区域

内容区域的展示会复杂很多,实现的思路如下:获取当前月第一天是星期几,推导出前一个月展示的天数

获取当月的展示总天数

总共要展示的天数为 42,减去前一个月和当前月展示的天数即为下个月展示的天数

export default {

name: 'PickerDays',

data () {

return {

weeks: ['一', '二', '三', '四', '五', '六', '日']

};

},

// some code ... computed: {

getDays () {

const [year, month] = getYearMonthDay(this.tempValue);

// 0 ~ 6, 需要将0转换为7 let startWeek = new Date(year, month, 1).getDay();

if (startWeek === 0) {

startWeek = 7;

}

const prevLastDay = getPrevMonthLastDay(year, month);

const curLastDay = getCurrentMonthLastDay(year, month);

const days = [...this.getPrevMonthDays(prevLastDay, startWeek), ...this.getCurrentMonthDays(curLastDay), ...this.getNextMonthDays(curLastDay, startWeek)];

// 转换成二维数组 return toMatrix(days, 7);

},

},

methods: {

// 获取前一个月天数 getPrevMonthDays (prevLastDay, startWeek) {

const [year, month] = getYearMonthDay(this.tempValue);

const prevMonthDays = [];

for (let i = prevLastDay - startWeek + 1; i <= prevLastDay; i++) {

prevMonthDays.push({

date: new Date(year, month - 1, i),

status: 'prev'

});

}

return prevMonthDays;

},

// 获取当前月天数 getCurrentMonthDays (curLastDay) {

const [year, month] = getYearMonthDay(this.tempValue);

const curMonthDays = [];

for (let i = 1; i <= curLastDay; i++) {

curMonthDays.push({

date: new Date(year, month, i),

status: 'current'

});

}

return curMonthDays;

},

// 获取下一个月天数 getNextMonthDays (curLastDay, startWeek) {

const [year, month] = getYearMonthDay(this.tempValue);

const nextMonthDays = [];

for (let i = 1; i <= 42 - startWeek - curLastDay; i++) {

nextMonthDays.push({

date: new Date(year, month + 1, i),

status: 'next'

});

}

return nextMonthDays;

},

getDay (cell) {

return cell.date.getDate();

},

}

};

我们将前一个月、当前月、下一个月的日期信息组成一个数组,然后转换位为拥有 6 个子数组,每个子数组中有 7 条信息的二维数组,方便遍历展示:

class="go-date-picker-days-cell"

v-for="(cell,j) in row"

:key="`${cell}-${j}`"

>

{{ getDay(cell) }}

数组的格式如下:

在计算日期时,如果传入的天数为 0,则表示前一个月的最后一天。利用这个特性,可以节省我们很多的计算逻辑:

export const getCurrentMonthLastDay = (year, month) => {

return new Date(year, month + 1, 0).getDate();

};

export const getPrevMonthLastDay = (year, month) => {

return new Date(year, month, 0).getDate();

};

在遍历展示的天的过程中,还可以通过日期信息来为其设置样式:

class="go-date-picker-days-cell"

:class="dayClasses(cell)"

v-for="(cell,j) in row"

:key="`${cell}-${j}`"

>

{{ getDay(cell) }}

export default {

// some code ... methods: {

dayClasses (cell) {

return {

prev: cell.status === 'prev',

next: cell.status === 'next',

active: this.isSameDay(cell.date, this.value),

today: this.isToday(cell.date)

};

},

// 是否是选中的天 isSameDay (date1, date2) {

const [y1, m1, d1] = getYearMonthDay(date1);

const [y2, m2, d2] = getYearMonthDay(date2);

return y1 === y2 && m1 === m2 && d1 === d2;

},

// 是否是今天 isToday (date) {

const [y1, m1, d1] = getYearMonthDay(date);

const [y2, m2, d2] = getYearMonthDay();

return y1 === y2 && m1 === m2 && d1 === d2;

}

}

};

}

通过dayClasses方法,我们分别添加如下class:prev: 前一个月

next: 下一个月

active: 选中的日期

today: 今天

之后便可以根据class来为这些不同状态分别添加不同的样式了。

月份切换

在面板的头部,支持点击左右箭头进行月份切换。其实现利用了Date.prototype.setMonth方法:

{{ formatDate.year }}年{{ formatDate.month }}月{{ formatDate.day }}日

export default {

name: 'PickerDays',

methods: {

changeMonth (value) {

const [, month] = getYearMonthDay(this.tempValue);

const timestamp = cloneDate(this.tempValue).setMonth(month + value);

// 通过.sync修饰符绑定,使用update:xxx来进行修改值 this.$emit('update:tempValue', new Date(timestamp));

}

}

};

内部会传入设置的月份,如果值为-1 或者 13 的话,会自动切换到前一年或后一年,而不用担心时间混乱。

选择天

当点击面板中的某天后,需要更新用户传入的value。而在value更新后,由于在组件内我们watch了value,所以也会同时更新tempValue,使页面中的数据和value保持一致:

class="go-date-picker-days-cell"

:class="dayClasses(cell)"

v-for="(cell,j) in row"

:key="`${cell}-${j}`"

@click="onClickDay(cell)"

>

{{ getDay(cell) }}

export default {

name: 'PickerDays',

// 引入混合器 mixins: [emitter],

// some code ... methods: {

onClickDay (cell) {

this.dispatch('input', cell.date, 'GoDatePicker');

},

// some code... }

};

这里进行了跨组件调用this.$emit('input')事件,需要从子到父一直通过@进行事件监听,并使用this.$emit('input')继续向上触发事件。为了简化这个过程,在混合器内封装了dispatch方法,方便跨组件之间的方法触发:

// src/mixins/emitter.jsconst emitter = {

methods: {

dispatch (event, params, componentName) {

let parent = this.$parent;

while (parent) {

if (parent.$options.name === componentName) {

return parent.$emit(event, params);

}

parent = parent.$parent;

}

}

}

};

export default emitter;如果不理解dispatch的实现过程的话,可以参考笔者的

展示月面板代码中将年月日面板分别拆分成了不同的组件,然后通过动态组件来进行展示。

月面板的界面效果如下:

我们在代码内部定义了数组months来代表所有月份,并且通过toMatrix将其转换为拥有 3 个子数组的二维数组,方便进行遍历:

class="go-date-picker-months-cell"

v-for="(cell,j) in row" :key="`${cell}-${j}`"

:class="monthClasses(i,j)"

@click="onClickMonth(i,j)"

>

{{ cell }}

const MONTHS = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];

export default {

name: 'PickerMonths',

data () {

return {

months: toMatrix(MONTHS, 4)

};

},

methods: {

monthClasses (i, j) {

const month = j + i * 4;

return {

active: this.isSameMonth(month),

current: this.isCurrentMonth(month)

};

},

onClickMonth (i, j) {

const month = j + i * 4;

const { year, day } = this.formatDate;

this.dispatch('input', new Date(year, month, day), 'GoDatePicker');

this.$emit('mode-change', 'picker-years');

},

isCurrentMonth (month) {

const year = this.formatDate.year;

const [year2, month2] = getYearMonthDay(new Date());

return year === year2 && month === month2;

},

isSameMonth (month) {

const year = this.formatDate.year;

const [year2, month2] = getYearMonthDay(this.value);

return year === year2 && month === month2;

}

}

};

在遍历过程中可以通过i,j来获取到对应项的真实月份,根据月份和formatDate得到的tempValue所对应的当前面板的年份,可以添加不同的类名,从而设置不同的样式。

在点击月份后,会更新用户传入的value,然后跳转到年面板,下面我们来介绍年面板的实现。

展示年面板

年面板会展示 10 年的年份列表,可以通过左右箭头后退或前进 10 年,其效果如下:

我们需要计算出开始年份和结束年份,然后生成拥有 4 个子数组的二维数组在页面中遍历展示:

{{ startYear }}-{{ endYear }}

class="go-date-picker-years-cell"

v-for="(cell,j) in row" :key="`${cell}-${j}`"

:class="yearClasses(cell)"

@click="onClickYear(cell)"

>

{{ cell }}

export default {

name: 'PickerYears',

computed: {

startYear () {

const { year } = this.formatDate;

return year - year % 10;

},

endYear () {

return this.startYear + 9;

},

years () {

const arr = [];

for (let i = this.startYear; i <= this.endYear; i++) {

arr.push(i);

}

return toMatrix(arr, 4);

}

},

};

在生成年份列表后,可以根据列表中的年份信息来为其设置不同的样式:

export default {

methods: {

yearClasses (year) {

return {

active: this.isSameYear(year),

current: this.isCurrentYear(year)

};

},

// 当前所处年分 isCurrentYear (year) {

const [year2] = getYearMonthDay(new Date());

return year === year2;

},

// 与用户传入的value相同的激活年份 isSameYear (year) {

const [year2] = getYearMonthDay(this.value);

return year === year2;

}

}

}

当点击左右箭头时,会调用Date.prototype.setFullYear来进行年份的切换:

export default {

methods: {

changeYear (value) {

const [year] = getYearMonthDay(this.tempValue);

const timestamp = cloneDate(this.tempValue).setFullYear(year + value);

this.$emit('update:tempValue', new Date(timestamp));

},

}

}

在点击对应的年份后,会更新value并切换到选择天面板:

export default {

methods: {

onClickYear (year) {

const { month, day } = this.formatDate;

this.dispatch('input', new Date(year, month, day), 'GoDatePicker');

this.$emit('mode-change', 'picker-days');

},

}

}

到这里我们已经实现年、月、天的选择,日期选择器的基本功能已经全部实现 。

输入当前日期

用户不仅可以通过面板选择时间,也可以通过输入框来输入时间。

当用户在输入框中输入内容后,会将用户输入的内容与正则进行匹配,如果匹配不成功将会忽略用户的输入内容。如果匹配成功,会通过正则单元将用户填写的年月日拿到,然后用它们更新用户传入的value,进而更新整个日期选择器的数据。

上述逻辑的代码如下:

class="go-date-picker-input"

@focus="visible=true"

v-model="displayValue"

prefix="calendar"

placeholder="请选择时间"

>

export default {

name: 'GoDatePicker',

computed: {

displayValue: {

get () {

const [year, month, day] = getYearMonthDay(this.value);

return `${year}-${month + 1}-${day}`;

},

set (e) { // 为计算属性绑定set方法,在更新值的时候会调用 if (e?.target?.value) {

const reg = /(\d+)-(\d+)-(\d+)/;

const value = e.target.value;

const matched = value.match(reg);

if (matched) { // 如果匹配到的话,通过正则单元获取到年月日更新value const [, year, month, day] = matched;

this.$emit('input', new Date(year, month - 1, day));

}

}

}

},

},

};

在输入框中输入内容的时候,由于为计算属性displayValue设置了v-model,所以需要为其设置set方法。在set方法中通过String.prototype.match获取匹配结果,进而更新value。

这个功能可以让我们直接输入日期信息,而不用为了选择某个跨度比较大的时间而进行不停的前进后退操作。

结语

日期选择器的难点在于年、月、天列表的展示,需要我们对Date的一些api有一定的了解,否则会导致很多没有必要的计算逻辑。剩下的一些CSS样式比较简单,需要花些耐心多去调试。

希望这篇文章能够帮助你了解日期选择器的实现原理,在工作和面试时更加游刃有余!

vue 日期面板_Vue实战:日期选择器相关推荐

  1. vue 日期面板_VUE项目中如何方便的转换日期和时间

    做项目开发时对各种不同的时间进行处理是一件不可避免的事情,比如Unix时间戳需要转换为具体的时间来显示,或者需要根据给出的时间格式化成想要的形式,接下来就拿目前最流行的VUE框架来详细阐述如何对时间进 ...

  2. iis vue history 配置_Vue实战——vueRouter路由的添加与配置

    接上文:vue实战--自定义基础路径,端口号,继续我们的实战项目讲解之旅.本文讲解vue核心插件--vue router. 在本项目中,使用了vue-cli3.10创建的,所以默认带了router,那 ...

  3. vue assets图片_Vue实战—如何细化Vue项目目录设计(2)

    通过上一篇文章我们了解了Vue项目核心文件(src)以及在内的各个文件的职能. 接下来我们进一步细化Vue项目的目录设计: 在开发项目的时候前端避免不了请求后端接口.为了同时开发,我们知道的通常会用到 ...

  4. vue @click 赋值_vue 手写一个时间选择器

    vue 手写一个时间选择器 最近研究了 DatePicker 的实现原理后做了一个 vue 的 DatePicker 组件,今天带大家一步一步实现 DatePicker 的 vue 组件. 原理 Da ...

  5. pandas将dataframe数据列中的年、月、日列组合成单一的日期数据列实战

    pandas将dataframe数据列中的年.月.日列组合成单一的日期数据列实战 目录 pandas将dataframe数据列中的年.月.日列组合成单一的日期数据列实战

  6. vue 日期前面加0_vue日期组件 支持vue1.0和2.0

    vue-datetime 使用vue编写的时间组件,小巧实用,支持vue1.0,vue2.0 v1.0 功能: 1.支持同时展开多个日期选择框 2.支持单击选中和取消,可配置单选和多选 3.支持双击启 ...

  7. 小程序日期(日历)时间 选择器组件

    封装一个小程序日期(日历)时间 选择器组件 简要说明: 一共两个版本 ,date-time-picker 和 date-time-picker-plus. date-time-picker 弹窗层是 ...

  8. react RangePicker 日期选择器,可选择的日期范围是选中日期的前后三个月

    需求:在日期的选择范围中,当选择开始日期时,结束日期只能是开始日期的前后3个月的范围内. 需要用到rangePicker的onCalendarChange回调函数. 同时把已经选择的开始日期 (fir ...

  9. 【ElementUI】日期选择器,只能选今天之前的时间,或者是只能选今天之后的时间。今天是否可以选。限制结束日期不能小于开始日期

    一个日期选择器的范围限制 <el-date-pickerv-model="value1"type="date"placeholder="选择日期 ...

最新文章

  1. Mac下使用crontab来实现定时任务
  2. 人才招聘丨 清华大学精准医学研究院招聘启事
  3. Eclipse插件开发中File和IFile的转换
  4. OpenGL波浪模拟
  5. 使用反射处理Java批注
  6. 楼主考南师计算机学硕,【图片】2019南师大新传学硕考研经验贴【南京师范大学研究生吧】_百度贴吧...
  7. LInux:shell 命令:字符串截取
  8. 如何衡量研发效能?阿里资深技术专家提出了5组指标
  9. oracle创建多个游标,Oracle——游标的创建和使用
  10. 机器视觉与Tesseract介绍
  11. (引用)Python 元素、元组、列表、字典的区别
  12. 基于大数据平台的毕业设计
  13. 计算机考研2017真题408,2017计算机408考研真题‌.pdf
  14. Python 各种画图
  15. Windows服务器基本安全策略配置
  16. 江苏咪咕MGV3000_YST代工_S905L3_线刷固件包
  17. 初识计算机网络||概述
  18. 游戏统计中一些常用的专业 术语和计算公式
  19. (3)数据链数层——计算机网络复习笔记
  20. 【Vue】通过computed为筛选列表数组进行排序(图文+完整代码示例)

热门文章

  1. 四节1.5V的5号电池、一个电容、一个12V的报警蜂鸣器、铜线和螺母,在螺母所栓的铜线触发接通电源后,缓慢放电10秒,制作一个简易震动报警器,需要用什么样的电容合适?...
  2. 机器学习之正则化(Regularization)
  3. Python:实现pancake sort煎饼排序算法(附完整源码)
  4. myeclipse常用快捷键简介
  5. 网站建设时如何提高网站打开速度?
  6. js基础及相关面试题
  7. Python之简易Web框架搭建
  8. ABP入门教程(六)ABP支持DataTable
  9. ACM题目百钱百鸡-N钱N鸡
  10. SSL证书与Https应用部署小结