公司的要求,需要开发一个精美的日历组件(IOS , 安卓, PC 的 IE9 + 都能运行),写完后想把它分享出来,希望大家批评()。
先来个截图 代码已经分享到 https://github.com/zhangKunUserGit/vue-component
根据需求先说一下怎么用吧 (上面是:html, 下面是 JS )
- <data-picker
- v-if="showDatePicker"
- :date="date"
- :min-date="minDate"
- :max-date="maxDate"
- @confirm="confirm"
- @cancel="cancel"
- ></data-picker>
- import DataPicker from './components/DatePicker.vue';
- import './style.sCSS';
- new Vue({
- el: '#app',
- data() {
- return {
- date: '2017-09-11',
- minDate: '2000-09-11',
- maxDate: '2020-09-11',
- showDatePicker: false,
- selectedDate: '点击选择日期',
- };
- },
- methods: {
- openDatePicker() {
- this.showDatePicker = true;
- },
- confirm(value) {
- this.showDatePicker = false;
- this.selectedDate = value;
- },
- cancel() {
- this.showDatePicker = false;
- },
- },
- components: {
- DataPicker,
- },
- });
我们提供了最大值、最小值和初始值,唯一不足的地方是时间格式只能是 YYYY-MM-DD (2017-12-12) ,大家可以从 github 上拉取代码运行看一下(由于没有仔细测试,可能会有 bug 和性能问题,希望指出)。
(一)先画好界面这个不是重点,HTML 和 CSS,应该很简单,大家看我的 css 可能感觉我的命名太长了,只因为我们公司用,担心其他样式影响它(可能有其他方式吧,希望大神指出)
(二)组装日期列表先看代码:
- rows() {
- const { year, month } = this.showDate;
- const months = (new Date(year, month, 0)).getDate();
- const result = [];
- let row = [];
- let weekValue;
- // 按照星期分组
- for (let i = 1; i <= months; i += 1) {
- // 根据日期获取星期,并让开头是1,而非0
- weekValue = (new Date(year, month, i)).getDay() + 1;
- // 判断月第一天在星期几,并填充前面的空白区域
- if (i === 1 && weekValue !== 1) {
- this.addRowEmptyValue(row, weekValue);
- this.addRowDayValue(row, i);
- } else {
- this.addRowDayValue(row, i);
- // 判断月最后一天在星期几,并填充后面的空白区域
- if (i === months && weekValue !== 7) {
- this.addRowEmptyValue(row, (7 - weekValue) + 1);
- }
- }
- // 按照一周分组
- if (weekValue % 7 === 0 || i === months) {
- result.push(row);
- row = [];
- }
- }
- this.showDate.monthStr = monthJson[this.showDate.month];
- return result;
- },
我的思路是:
(1)获取月份天数,并按照星期分组;
(2)如果月份第一天不在星期一,前面填充空值。同理,如何月份最后一天不在周日,最后面填充空值,目的是:让分的组 长度都是 7,也就是一周。这样可以用 flex 布局方式快速开发了;
(3)里面也包含一些限制,比如小于 minDate 和大于 maxDate, 不让点击等等
(三)切换月份 (1)上一个月份
- /**
- * 切换到上一个月
- */
- prevMonth() {
- if (this.prevMonthClick) {
- return;
- }
- this.prevMonthClick = true;
- setTimeout(() => {
- this.prevMonthClick = false;
- }, 500);
- this.fadeXType = 'fadeX_Prev';
- // 如何当前月份已经小于等于minMonth 就不让其在执行
- if (this.isMinLimitMonth()) {
- return;
- }
- const { year, month } = this.showDate;
- // 判断当前月份,如果已经等于1(1就是一月,而不是二月)
- if (month <= 1) {
- this.showDate.year = year - 1;
- this.showDate.month = 12;
- } else {
- this.showDate.month -= 1;
- }
- },
setTimeout() 主要是让其显示动画后自动消失。 fadeXType 是动画类型
(2)下一个月份
- /**
- * 切换到下一个月
- */
- nextMonth() {
- if (this.nextMonthClick) {
- return;
- }
- this.nextMonthClick = true;
- setTimeout(() => {
- this.nextMonthClick = false;
- }, 500);
- this.fadeXType = 'fadeX_Next';
- // 如何当前月份已经大于等于maxMonth 就不让其在执行
- if (this.isMaxLimitMonth()) {
- return;
- }
- const { year, month } = this.showDate;
- // 判断当前月份,如果已经等于12(12就是十二月)
- if (month >= 12) {
- this.showDate.year = year + 1;
- this.showDate.month = 1;
- } else {
- this.showDate.month += 1;
- }
- },
这里面的 setTimeout() 和 prevMonth 方法的原理一样。
上面两种切换月份的功能主要注意:
a. 因为有 minDate 和 maxDate, 所以首先考虑的是不能超出这个限制。
b. 要考虑切换月份后年的变化,当月份大于 12 后,年加 1 ,月变成 1。
(四)选择年份 (1)点击最上面的年,显示年份列表(2)选择年份
- openYearList() {
- if (this.showYear) {
- this.showYear = false;
- return;
- }
- const index = this.yearList.indexOf(this.selectDate.year);
- this.showYear = true;
- // 打开年列表,让其定位到选中的位置上
- setTimeout(() = >{
- this.$refs.yearList.scrollTop = (index - 3) * 40;
- });
- },
- selectYear(value) {
- this.showYear = false;
- this.showDate.year = value;
- let type;
- // 当日期在最小值之外,月份换成最小值月份 或者 当日期在最大值之外,月份换成最大值月份
- if (this.isMinLimitMonth()) {
- type = 'copyMinDate';
- } else if (this.isMaxLimitMonth()) { // 当日期在最大值之外,月份换成最大值月份
- type = 'copyMaxDate';
- }
- if (type) {
- this.showDate.month = this[type].month;
- this.showDate.day = this[type].day;
- this.resetSelectDate(this.showDate.day);
- return;
- }
- let dayValue = this.selectDate.day;
- // 判断日是最大值,防止另一个月没有这个日期
- if (this.selectDate.day > 28) {
- const months = (new Date(this.showDate.year, this.showDate.month, 0)).getDate();
- // 当前月份没有这么多天,就把当前月份最大值赋值给day
- dayValue = months < dayValue ? months: dayValue;
- }
- this.resetSelectDate(dayValue);
- },
在切换年份时注意一下方面:
a. 考虑 minDate 和 maxDate, 因为如果之前你选择的月份是 1 月,但是限制是 9 月,在大于 minDate(比如 2017) 年份没有问题,但是到了 minDate 的具体年份(比如 2010),那么月份最小值只能是九月,需要修改月份,maxDate 同理。
b. 如何之前你选择的 day 是 31,由于切换年份后,这个月只有 30 天,记得把 day 换成这个月最大值,也就是 30。
(五)处理原始数据其实这一条正常情况下,应该放在第一步讲,但是我是根据我的开发习惯来写步骤的。我一般都是先写功能,数据是模拟的,等写好了,再考虑原始数据格式和暴露具体的方法等等,因为这样不会改来改去,影响开发和心情。
- initDatePicker() {
- this.showDate = { ...this.splitDate(this.date, true) };
- this.copyMinDate = { ...this.splitDate(this.minDate) };
- this.copyMaxDate = { ...this.splitDate(this.maxDate) };
- this.selectDate = { ...this.showDate };
- },
- splitDate(date, addStr) {
- let result = {};
- const splitValue = date.split('-');
- try {
- if (!splitValue || splitValue.length < 3) {
- throw new Error('时间格式不正确');
- }
- result = {
- year: Number(splitValue[0]),
- month: Number(splitValue[1]),
- day: Number(splitValue[2]),
- };
- if (addStr) {
- result.week = (new Date(result.year, result.month, result.day)).getDay() + 1;
- result.monthStr = monthJson[result.month];
- result.weekStr = weekJson[result.week];
- }
- } catch (error) {
- console.error(error);
- }
- return result;
- },
这里目的是:
a. 处理原始数据,把原始数据查分,用 json 缓存下来,这样方便后面操作和显示。这里面我只兼容 YYYY-MM-DD 的格式,其他的都不兼容,如果你想兼容其他格式,你可以修改其代码,或者用 moment.js 等其他库帮你做这件事情。
b. 拆分后的格式如下:
总结:
- year: '',
- month: '',
- day: '',
- week: '',
- weekStr: '',
- monthStr: '',
上面就是开发这个组件的详细讲解,如有不妥,请指出批评。 仔细想想,其实这个不难,就是有点琐碎。仔细就好
这里的所有动画都是用的 Vue 的 transition,大家可以看看官网 ,非常详细。
来源: https://www.cnblogs.com/zhangkunweb/p/date_picker.html