手撸Element源码,完善日期选择器功能,支持范围选择器周、季、年

背景

在做后台系统的项目中,日期组件用的是比较频繁的,但是一些常用的组件库没那么全。特别使用选择日期范围组件就格外明显。

功能

根据 element-ui(vue2) 进行完善、改造,功能有:

  1. 周选择器完善
  2. 周选择日期范围
  3. 季度范围选择
  4. 季度多选
  5. 季度范围选择
  6. 年度范围选择
  7. 增加【Q/QQ】季度日期格式

使用

lp-date-picker使用文档

import {DatePicker} from 'lp-vue'
Vue.use(DatePicker)

准备工作

element中的date-picker源码复制一份node_modules\element-ui\packages\date-picker,然后在main.js中使用

import DatePicker from './components/date-picker';
Vue.component('DatePicker', DatePicker);
<!--使用 -->
<DatePicker
    v-model="value"
    type="week"
    format="yyyy 第 WW 周"
    placeholder="选择周">
</DatePicker>

这个时候会报错,代码无法解析jsx语法错误。原因是这个源码中包含时间选择器,这个组件中引入滚动条ElScrollbar所以会报错,这里只需要日期选择器,所以注释掉这几行代码不会影响功能,可以自行处理这个问题。

注释掉 src/components/date-picker/src/basic/time-spinner.vue line105 和 line109

// import ElScrollbar from 'element-ui/packages/scrollbar';
// components: { ElScrollbar },

注释掉 src/components/date-picker/src/panel/time-select.vue line22 和 line77

// import ElScrollbar from 'element-ui/packages/scrollbar';
// components: { ElScrollbar },

如果有eslint代码检查,需要把src/components/date-picker加入到忽略文件中不然会报出很多代码格式的错误和警告 。

周选择器完善

解决面板不显示周数问题

src/components/date-picker/src/basic/date-table.vueprops中接收一个参数showWeekNumber这个就是用来显示周数,只是父组件没有传入。

修改src\components\date-picker\src\panel\date.vue

  1. showWeekNumber绑定到DateTable组件中
<date-table
    v-show="currentView === 'date'"
    @pick="handleDatePick"
    :selection-mode="selectionMode"
    :first-day-of-week="firstDayOfWeek"
    :showWeekNumber="showWeekNumber"
    :value="value"
    :default-value="defaultValue ? new Date(defaultValue) : null"
    :date="date"
    :cell-class-name="cellClassName"
    :disabled-date="disabledDate">
</date-table>
  1. showWeekNumber值跟随模式变化

代码路径 watch > selectionMode

selectionMode(newVal) {
if (newVal === 'month') {
    //省略
    }  else if(newVal === 'week'){
        this.showWeekNumber = true
    }
}

修改src\components\date-picker\src\basic\date-table.vue

  1. 修改表头显示周数,官方使用了国际化(但都注释掉了),这里直接写死

可以在main.js中引用import locale from 'element-ui/lib/locale/lang/zh-CN'重新改写里面的内容即可

<!-- 第11行 -->
<tr>
    <th v-if="showWeekNumber">周数</th>
    <th v-for="(week, key) in WEEKS" :key="key">{{ t('el.datepicker.weeks.' + week) }}</th>
</tr>
  1. 设置行样式激活(行内阴影),第17行,多了一列周,所以取数2
:class="{ current: isWeekActive(row[showWeekNumber ? 2 : 1]) }"
  1. 解决选择周后日期错误

使用周选择器时,选择后的日期默认是星期一,在西方国家周日则为星期一,所以想要调整可以是使用官方提供的方法 :picker-options="{firstDayOfWeek:1}"即可。

  1. 解决周数不对问题。152行
if (this.showWeekNumber) {
    row[0] = { type: 'week', text: getWeekNumber(nextDate(startDate, i * 7 + 1)) };
}
  1. 解决选中周不高亮
<style lang="scss" scoped>
.el-date-table.is-week-mode tr{
  &.el-date-table__row {
    @mixin week () {
      margin-left: 2px;
      margin-right: 2px;
      border-top-left-radius: 0;
      border-bottom-left-radius: 0;
    }
 
    &.current td.week div {
      font-weight: bold;
      background: #fff;
      color: #fff;
      span {
        background: #409EFF;
        border-radius: 2px;
      }
    }
 
    &:hover td {
      &.week div {
        @include week;
      }
 
      &:nth-of-type(2) div {
        margin-left: 5px;
        border-top-left-radius: 15px;
        border-bottom-left-radius: 15px;
      }
    }
 
    td {
      &.week {
        cursor: unset;
        div {
          @include week;
        }
      }
    }
  }
}
</style>
1.png

解决周选择器无法设置value-format

描述: 周选择器设置value-format后value格式是正确了,但是控制台报错,组件不回显。

原因: 从报错信息可以看出来,都是显示获取操作时间函数,因为现在的值是字符串当然没有这些方法,官方没有对周模式进行兼容。

修改src\components\date-picker\src\picker.vue

经过问题排查发现,在这个文件中有一个计算属性parsedValue用于处理value值,所以从写此方法。549行。

parsedValue() {
    const yearStartIndex = this.valueFormat ? this.valueFormat.indexOf('yyyy') : -1;
    const weekStartIndex = this.valueFormat ? this.valueFormat.indexOf('WW') : -1;
    const weekStartIndex2 = this.valueFormat ? this.valueFormat.indexOf('W') : -1;
    if (this.value && this.type === 'week' && yearStartIndex > -1 && (weekStartIndex > -1 || weekStartIndex2 > -1)) {
        const year = parseInt(this.value.substring(yearStartIndex, yearStartIndex + 4))
        const week = parseInt(this.value.substring((weekStartIndex > -1 ? weekStartIndex : weekStartIndex2)).replace(/(\d{1,2}).*/g, '$1'))
        const firstWeekDayOfYear = new Date(year, 0, 1).getDay()
        let firstThursday = null
        if (firstWeekDayOfYear <= 4) {
            firstThursday = new Date(year, 0, 1 + (4 - firstWeekDayOfYear))
        } else {
          firstThursday = new Date(year, 0, 1 + (11 - firstWeekDayOfYear))
        }
        const weekOfThursday = new Date(firstThursday.getTime() + (1000 * 60 * 60 * 24 * 7 * (week - 1)))
        return weekOfThursday
      }else{
        if (!this.value) return this.value; // component value is not set
        if (this.type === 'time-select') return this.value; // time-select does not require parsing, this might change in next major version

        const valueIsDateObject = isDateObject(this.value) || (Array.isArray(this.value) && this.value.every(isDateObject));
        if (valueIsDateObject) {
          return this.value;
        }

        if (this.valueFormat) {
          return parseAsFormatAndType(this.value, this.valueFormat, this.type, this.rangeSeparator) || this.value;
        }

        // NOTE: deal with common but incorrect usage, should remove in next major version
        // user might provide string / timestamp without value-format, coerce them into date (or array of date)
        return Array.isArray(this.value) ? this.value.map(val => new Date(val)) : new Date(this.value);
      }
}

重写后支持value-format属性

2.png

周选择日期范围

思路: 通过这个src\components\date-picker\src\picker\date-picker.js文件可以发现element做了几个模式然后通过不同的type获取不同的日期面板,所以可以把日模式日期选择改造一下作为一个新模式。

为了不影响现有功能,把代码复制一份进行改造。

复制文件并新增weekrange模式

  1. 复制src\components\date-picker\src\panel\date-range.vue 命名为week-range.vue
  2. src\components\date-picker\src\picker\date-picker.js中新增weekrange模式
import WeekRange from '../panel/week-range'

const getPanel = function(type) {
  if (type === 'daterange' || type === 'datetimerange') {
    return DateRangePanel;
  } else if (type === 'monthrange') {
    return MonthRangePanel;
  }else if(type === 'weekrange'){
    return WeekRange
  }
  return DatePanel;
};

新增基础配置数据

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

  • 添加周范围默认时间格式,110行
const DEFAULT_FORMATS = {
  weekrange: 'yyyywWW'
};
  • 添加触发类型,124行
const HAVE_TRIGGER_TYPES = [
  'weekrange'
];
  • 添加类型值映射解析器,171行
const TYPE_VALUE_RESOLVER_MAP = {
    weekrange: {
        formatter(value, format) {
            if (Array.isArray(value) && value.length === 2) {
                return [WEEK_FORMATTER(value[0], format), WEEK_FORMATTER(value[1], format)];
            }
            return '';
        },
        parser(value, format) {
            if(Array.isArray(value) && value.length === 2){
                return [TYPE_VALUE_RESOLVER_MAP.date.parser(value[0], format),TYPE_VALUE_RESOLVER_MAP.date.parser(value[1], format)];
        }
            return []
        },
  },
}

const WEEK_FORMATTER = function (value, format) {
  let week = getWeekNumber(value);
  let month = value.getMonth();
  const trueDate = new Date(value);
  if (week === 1 && month === 11) {
    trueDate.setHours(0, 0, 0, 0);
    trueDate.setDate(trueDate.getDate() + 3 - ((trueDate.getDay() + 6) % 7));
  }
  let date = formatDate(trueDate, format);

  date = /WW/.test(date) ? date.replace(/WW/, week < 10 ? '0' + week : week) : date.replace(/W/, week);
  return date;
};
  • 修改样式

src\components\date-picker\src\picker.vue这个文件中,最外层有个样式是通过type生成的,所以生成的el-date-editor--weekrange没有这个样式,所以文本框就很短。

<style lang="scss" scoped>
.el-date-editor--weekrange.el-input__inner{
  width: 350px;
}
</style>

开启周数显示

修改:src\components\date-picker\src\panel\week-range.vue

date-table中添加showWeekNumber=true,记得两个都要添加

3.png

支持value-format

根据上文的处理方法,现在是数组数据处理。

//提取处理方法
parseWeekValue(year,week){
    const firstWeekDayOfYear = new Date(year, 0, 1).getDay()
    let firstThursday = null
    if (firstWeekDayOfYear <= 4) {
        firstThursday = new Date(year, 0, 1 + (4 - firstWeekDayOfYear))
    } else {
        firstThursday = new Date(year, 0, 1 + (11 - firstWeekDayOfYear))
    }
    return new Date(firstThursday.getTime() + (1000 * 60 * 60 * 24 * 7 * (week - 1)))
}
//改造计算属性中的parsedValue方法
parsedValue() {
    const yearStartIndex = this.valueFormat ? this.valueFormat.indexOf('yyyy') : -1;
    const weekStartIndex = this.valueFormat ? this.valueFormat.indexOf('WW') : -1;
    const weekStartIndex2 = this.valueFormat ? this.valueFormat.indexOf('W') : -1;
    if (this.value && this.type === 'week' && yearStartIndex > -1 && (weekStartIndex > -1 || weekStartIndex2 > -1)) {
        const year = parseInt(this.value.substring(yearStartIndex, yearStartIndex + 4))
        const week = parseInt(this.value.substring((weekStartIndex > -1 ? weekStartIndex : weekStartIndex2)).replace(/(\d{1,2}).*/g, '$1'))
        return this.parseWeekValue(year,week)
    }else if(Array.isArray(this.value) && this.type === 'weekrange' && yearStartIndex > -1 && (weekStartIndex > -1 || weekStartIndex2 > -1)){
        return this.value.map(date=>{
            const year = parseInt(date.substring(yearStartIndex, yearStartIndex + 4))
            const week = parseInt(date.substring((weekStartIndex > -1 ? weekStartIndex : weekStartIndex2)).replace(/(\d{1,2}).*/g, '$1'))
            return this.parseWeekValue(year,week)
        })
    }else{
        if (!this.value) return this.value; // component value is not set
        if (this.type === 'time-select') return this.value; // time-select does not require parsing, this might change in next major version

        const valueIsDateObject = isDateObject(this.value) || (Array.isArray(this.value) && this.value.every(isDateObject));
        if (valueIsDateObject) {
            return this.value;
        }

        if (this.valueFormat) {
          return parseAsFormatAndType(this.value, this.valueFormat, this.type, this.rangeSeparator) || this.value;
        }

        // NOTE: deal with common but incorrect usage, should remove in next major version
        // user might provide string / timestamp without value-format, coerce them into date (or array of date)
        return Array.isArray(this.value) ? this.value.map(val => new Date(val)) : new Date(this.value);
      }
},

周高亮

思路:在单个周选择器中发现el-date-table这个层级上启用is-week-mode,在el-date-table__row样式行中加上current这个类名就可以高亮显示

  • 复制文件重新修改逻辑

复制src\components\date-picker\src\basic\date-table.vue命名为week-range-table.vueweek-range.vue中引用这个组件

  • 显示is-week-mode

src\components\date-picker\src\basic\week-range-table.vue第一行,以为这是复制出的一份,不做处理直接写死

<table cellspacing="0" cellpadding="0" class="el-date-table is-week-mode" @click="handleClick" @mousemove="handleMouseMove"></table>
  • 显示current

这个样式由isWeekActive这个方法控制,所以需要重写逻辑。很简单,传入行,判断当前行有没有start或者end。

:class="{ current: isWeekActive(row) }"

isWeekActive(row) {
    return row.find(v=>v.start) || row.find(v=>v.end)
}
4.png

季选择日期范围

季选择日期范围

季度多选

季度多选

季度范围选择

季度范围选择

Q/QQ日期格式

Q/QQ日期格式

年选择日期范围

2.png

完整功能展示

13fc923f8911473a82d612b5249e2c8c.gif

书洞笔记

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容