手?jǐn)]Element源碼,完善日期選擇器功能,支持范圍選擇器周、季、年

背景

在做后臺(tái)系統(tǒng)的項(xiàng)目中,日期組件用的是比較頻繁的,但是一些常用的組件庫(kù)沒(méi)那么全。特別使用選擇日期范圍組件就格外明顯。

功能

根據(jù) element-ui(vue2) 進(jìn)行完善、改造,功能有:

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

使用

lp-date-picker使用文檔

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

準(zhǔn)備工作

element中的date-picker源碼復(fù)制一份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>

這個(gè)時(shí)候會(huì)報(bào)錯(cuò),代碼無(wú)法解析jsx語(yǔ)法錯(cuò)誤。原因是這個(gè)源碼中包含時(shí)間選擇器,這個(gè)組件中引入滾動(dòng)條ElScrollbar所以會(huì)報(bào)錯(cuò),這里只需要日期選擇器,所以注釋掉這幾行代碼不會(huì)影響功能,可以自行處理這個(gè)問(wèn)題。

注釋掉 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加入到忽略文件中不然會(huì)報(bào)出很多代碼格式的錯(cuò)誤和警告 。

周選擇器完善

解決面板不顯示周數(shù)問(wèn)題

src/components/date-picker/src/basic/date-table.vueprops中接收一個(gè)參數(shù)showWeekNumber這個(gè)就是用來(lái)顯示周數(shù),只是父組件沒(méi)有傳入。

修改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. 修改表頭顯示周數(shù),官方使用了國(guó)際化(但都注釋掉了),這里直接寫死

可以在main.js中引用import locale from 'element-ui/lib/locale/lang/zh-CN'重新改寫里面的內(nèi)容即可

<!-- 第11行 -->
<tr>
    <th v-if="showWeekNumber">周數(shù)</th>
    <th v-for="(week, key) in WEEKS" :key="key">{{ t('el.datepicker.weeks.' + week) }}</th>
</tr>
  1. 設(shè)置行樣式激活(行內(nèi)陰影),第17行,多了一列周,所以取數(shù)2
:class="{ current: isWeekActive(row[showWeekNumber ? 2 : 1]) }"
  1. 解決選擇周后日期錯(cuò)誤

使用周選擇器時(shí),選擇后的日期默認(rèn)是星期一,在西方國(guó)家周日則為星期一,所以想要調(diào)整可以是使用官方提供的方法 :picker-options="{firstDayOfWeek:1}"即可。

  1. 解決周數(shù)不對(duì)問(wèn)題。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

解決周選擇器無(wú)法設(shè)置value-format

描述: 周選擇器設(shè)置value-format后value格式是正確了,但是控制臺(tái)報(bào)錯(cuò),組件不回顯。

原因: 從報(bào)錯(cuò)信息可以看出來(lái),都是顯示獲取操作時(shí)間函數(shù),因?yàn)楝F(xiàn)在的值是字符串當(dāng)然沒(méi)有這些方法,官方?jīng)]有對(duì)周模式進(jìn)行兼容。

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

經(jīng)過(guò)問(wèn)題排查發(fā)現(xiàn),在這個(gè)文件中有一個(gè)計(jì)算屬性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

周選擇日期范圍

思路: 通過(guò)這個(gè)src\components\date-picker\src\picker\date-picker.js文件可以發(fā)現(xiàn)element做了幾個(gè)模式然后通過(guò)不同的type獲取不同的日期面板,所以可以把日模式日期選擇改造一下作為一個(gè)新模式。

為了不影響現(xiàn)有功能,把代碼復(fù)制一份進(jìn)行改造。

復(fù)制文件并新增weekrange模式

  1. 復(fù)制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;
};

新增基礎(chǔ)配置數(shù)據(jù)

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

  • 添加周范圍默認(rèn)時(shí)間格式,110行
const DEFAULT_FORMATS = {
  weekrange: 'yyyywWW'
};
  • 添加觸發(fā)類型,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這個(gè)文件中,最外層有個(gè)樣式是通過(guò)type生成的,所以生成的el-date-editor--weekrange沒(méi)有這個(gè)樣式,所以文本框就很短。

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

開啟周數(shù)顯示

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

date-table中添加showWeekNumber=true,記得兩個(gè)都要添加

3.png

支持value-format

根據(jù)上文的處理方法,現(xiàn)在是數(shù)組數(shù)據(jù)處理。

//提取處理方法
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)))
}
//改造計(jì)算屬性中的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);
      }
},

周高亮

思路:在單個(gè)周選擇器中發(fā)現(xiàn)el-date-table這個(gè)層級(jí)上啟用is-week-mode,在el-date-table__row樣式行中加上current這個(gè)類名就可以高亮顯示

  • 復(fù)制文件重新修改邏輯

復(fù)制src\components\date-picker\src\basic\date-table.vue命名為week-range-table.vueweek-range.vue中引用這個(gè)組件

  • 顯示is-week-mode

src\components\date-picker\src\basic\week-range-table.vue第一行,以為這是復(fù)制出的一份,不做處理直接寫死

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

這個(gè)樣式由isWeekActive這個(gè)方法控制,所以需要重寫邏輯。很簡(jiǎn)單,傳入行,判斷當(dāng)前行有沒(méi)有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

書洞筆記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容