欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

element-ui源码解析——你不知道的el-time-picker

最编程 2024-02-09 16:07:30
...

1.源自一个小小的需求变动

小编写的页面中有一个时间字段,原来是 HH:mm:ss (时分秒)的形式,现在要改成只有小时,不要分秒。如下图所示:

小编想想了像这还不简单,看看文档,控制一下组件显示的形式以及点击确定按钮后得到的值的形式。于是乎,看看element-ui的文档,el-time-picker 中有控制显示格式的value-format属性

2.尝试修改

修改之前的代码:

<el-time-picker
    is-range
    value-format="HH:mm:ss"
    v-model="timeValue"
    @change="changeTime"
    range-separator="至"
    start-placeholder="开始时间"
    end-placeholder="结束时间"
    placeholder="选择时间范围">
</el-time-picker>

修改之后的代码:

<el-time-picker
    is-range
    value-format="HH"
    v-model="timeValue"
    @change="changeTime"
    range-separator="至"
    start-placeholder="开始时间"
    end-placeholder="结束时间"
    placeholder="选择时间范围">
</el-time-picker>

这样修改之后,虽然值的形式改变了,如下图只显示小时:

但是时间弹窗中还显示分秒:

网上资料说要把format属性也限制一下,TimePicker并没有介绍format属性,但是DatePicker介绍了format属性:

<el-time-picker
    is-range
    format="HH"
    value-format="HH"
    v-model="timeValue"
    @change="changeTime"
    range-separator="至"
    start-placeholder="开始时间"
    end-placeholder="结束时间"
    placeholder="选择时间范围">
</el-time-picker>

这样修改之后,显示效果如下,秒对应的列去掉了,可是分钟对应的列还在

到底怎么回事儿?去看看element的源码吧!

3.查看element-ui 中time-picker的源码

3.1 TimePicker 源码

3.1.1 time-picker模块拿来主义,实际是date-picker中的内容

time-picker 源码的位置在:element-ui\packages\time-picker,内容如下:

import TimePicker from '../date-picker/src/picker/time-picker';

/* istanbul ignore next */
TimePicker.install = function(Vue) {
  Vue.component(TimePicker.name, TimePicker);
};

export default TimePicker;

可以看到time-picker实际使用的是date-picker中的time-picker组件。真正的源码位置在 element-ui\packages\date-picker\src\picker\time-picker.js, 内容如下:

import Picker from '../picker';
import TimePanel from '../panel/time';
import TimeRangePanel from '../panel/time-range';

export default {
  mixins: [Picker],

  name: 'ElTimePicker',

  props: {
    isRange: Boolean,
    arrowControl: Boolean
  },

  data() {
    return {
      type: ''
    };
  },

  watch: {
    isRange(isRange) {
      if (this.picker) {
        this.unmountPicker();
        this.type = isRange ? 'timerange' : 'time';
        this.panel = isRange ? TimeRangePanel : TimePanel;
        this.mountPicker();
      } else {
        this.type = isRange ? 'timerange' : 'time';
        this.panel = isRange ? TimeRangePanel : TimePanel;
      }
    }
  },

  created() {
    this.type = this.isRange ? 'timerange' : 'time';
    this.panel = this.isRange ? TimeRangePanel : TimePanel;
  }
};

这里主要是引入了Picker, TimePanel, TimeRangePanel。那么它们都是什么呢?看下面的图就明白了:

有一点值得注意,就是这里把Picker混入了!因为我们在日常开发的时候混入的都是一个js文件,而这里混入的是vue文件。

3.1.2 time-picker中混入的用法

element-ui中混入的用法:

我们日常开发的混入用法:

小编也仿照element-ui的方法写了一下:

test-page目录中三个文件,引用关系如下图所示

mixin.vue引用了testMixin.js, testMixin.js引用了MixinCom。三个文件的内容很简单,如下代码所示:

mixin.vue:

<template>
  <Test></Test>
</template>
<script>
import Test from './testMixin'
  export default {
    components: {Test}
  }
</script>

testMixin.js:

import MixinCom from './MixinCom.vue'

export default {
  mixins: [MixinCom],
  data() {
   return  {
      a: 1,
      b: 2
    }
  },
  mounted() {
    console.log('mixin',this)
  }
}

MixinCom.vue:

<template>
  <div>这是测试mixin页面222</div>
</template>
<script>
export default {
    data() {
      return {
        test: ''
      }
    }
  }
</script>

运行效果如下:

至于什么时候混入一个vue文件?这么混入有什么好处?vue内部是怎么处理混入的?留给大家一起探讨,欢迎在评论区留言!下面分别看看引入的三个组件的源码。

3.2 Picker源码

源码位置: element-ui\packages\date-picker\src\picker.vue

在picker.vue中有一段代码:

computed: {
	ranged() {
    return this.type.indexOf('range') > -1;
  },
}

我们在使用组件的时候DatePicker组件的时候会传入type参数,其中有的参数中有range,如下入所示:

type中含有range的时候,就表示时间范围从 xxx至 xxx, 我们看一下模板部分的实现(只保留关键部分):

<template>
  <!-- 一个时间 -->
  <el-input
    v-if="!ranged"     
   >
   </el-input>
  <div
   v-else
   >
    <!-- 一个时间 -->
    <input>
    <!-- 分隔符 -->
    <slot name="range-separator">
      <span class="el-range-separator">{{ rangeSeparator }}</span>
    </slot>
    <!-- 另外一个时间 -->
    <input>
  </div>
</template>

下面看TimePanel的源码

3.3 TimePanel源码

element-ui\packages\date-picker\src\panel\time.vue

主要看模板部分

<template>
  <transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
    <div
      v-show="visible"
      class="el-time-panel el-popper"
      :class="popperClass">
      <div class="el-time-panel__content" :class="{ 'has-seconds': showSeconds }">
        <time-spinner
          ref="spinner"
          @change="handleChange"
          :arrow-control="useArrow"
          :show-seconds="showSeconds"
          :am-pm-mode="amPmMode"
          @select-range="setSelectionRange"
          :date="date">
        </time-spinner>
      </div>
      <div class="el-time-panel__footer">
        <button
          type="button"
          class="el-time-panel__btn cancel"
          @click="handleCancel">{{ t('el.datepicker.cancel') }}</button>
        <button
          type="button"
          class="el-time-panel__btn"
          :class="{confirm: !disabled}"
          @click="handleConfirm()">{{ t('el.datepicker.confirm') }}</button>
      </div>
    </div>
  </transition>
</template>

主要是引入了一个TimeSpinner组件,并且定义了取消和确定按钮,渲染后就是下面的样子:

下面看TimeRangePanel的源码

3.4 TimeRangePanel源码

node_modules\element-ui\packages\date-picker\src\panel\time-range.vue

我们看一下模板部分的实现(只保留关键部分)

<template>
  <transition
    name="el-zoom-in-top"
    @after-leave="$emit('dodestroy')">
    <divv-show="visible">
      <div class="el-time-range-picker__content">
        <div class="el-time-range-picker__cell">
          <!-- 国际化的开始时间文言 -->
          <div class="el-time-range-picker__header">{{ t('el.datepicker.startTime') }}</div>
          <div>
            <!-- 使用time-spinner  注意 showSeconds -->
            <time-spinner
              ref="minSpinner"
              :show-seconds="showSeconds"
              :am-pm-mode="amPmMode"
              @change="handleMinChange"
              :arrow-control="arrowControl"
              @select-range="setMinSelectionRange"
              :date="minDate">
            </time-spinner>
          </div>
        </div>
        <div class="el-time-range-picker__cell">
          <!-- 国际化的结束时间文言 -->
          <div class="el-time-range-picker__header">{{ t('el.datepicker.endTime') }}</div>
          <div>
            <!-- 使用time-spinner(属性略) -->
            <time-spinner>
            </time-spinner>
          </div>
        </div>
      </div>
      <div class="el-time-panel__footer">
         <!-- 取消和确定按钮(省略) -->
      </div>
    </div>
  </transition>
</template>

在布局上是左右布局,这里主要是使用了两次time-spinner组件,(注意 给time-spinner 传的showSeconds 属性,下文会提到)。time-spinne呈现如下图的效果:

最后看一下time-spinner组件:

3.5 TimeSpinne源码

element-ui\packages\date-picker\src\basic\time-spinner.vue (只保留关键部分):

<template>
  <div class="el-time-spinner" :class="{ 'has-seconds': showSeconds }">
    <template v-if="!arrowControl">
      <!-- 小时列 -->
      <el-scrollbar
        tag="ul"
        ref="hours">
        <li
          @click="handleClick('hours', { value: hour, disabled: disabled })"
          v-for="(disabled, hour) in hoursList"
          class="el-time-spinner__item"
          :key="hour"
          :class="{ 'active': hour === hours, 'disabled': disabled }">{{ ('0' + (amPmMode ? (hour % 12 || 12) : hour )).slice(-2) }}{{ amPm(hour) }}</li>
      </el-scrollbar>
      <!-- 分钟列 -->
      <el-scrollbar
        tag="ul"
        ref="minutes">
        <li
          @click="handleClick('minutes', { value: key, disabled: false })"
          v-for="(enabled, key) in minutesList"
          :key="key"
          class="el-time-spinner__item"
          :class="{ 'active': key === minutes, disabled: !enabled }">{{ ('0' + key).slice(-2) }}</li>
      </el-scrollbar>
      <!-- 秒列 -->
      <el-scrollbar
        v-show="showSeconds"
        ref="seconds">
        <li
          @click="handleClick('seconds', { value: key, disabled: false })"
          v-for="(second, key) in 60"
          class="el-time-spinner__item"
          :class="{ 'active': key === seconds }"
          :key="key">{{ ('0' + key).slice(-2) }}</li>
      </el-scrollbar>
    </template>
  </div>
</template>

上面模板代码分别定义了小时列、分钟列和秒列,注意在秒列上有一个v-show="showSeconds",定义如下:

 export default {
    props: {
      showSeconds: {
        type: Boolean,
        default: true
      },
    },
 }

可以看到showSeconds是从父组件传来的属性,也就是从time-range.vue 传过来的。回到time-range.vue看一下其定义:

export default {
    computed: {
      showSeconds() {
        return (this.format || '').indexOf('ss') !== -1;
      },
    }
}

showSecond是计算属性,由format计算得来,下面看看format:

data() {
  return {
    format: 'HH:mm:ss',
  };
},

format为data中定义的数据,默认值为'HH:mm:ss'

综上,秒列是可以被隐藏掉的,而分钟列上没有定义showMinute,所以隐藏不掉,这也就是为什么尽管我给format和value-format属性传值为"HH"的时候仍然显示分钟。

4.总结

本文从工作中使用el-time-picker遇到的问题出发,简单研究了一下其源码,但也是颇有收获。

(1)TimePicker组件和DatePicker组件属于同一个家族,因此关键代码都放在了一起。TimePicker只是把DatePicker目录下用到的部分引入过来;

(2)TimePicker引入Picker并作为混入(mixin),让我开了眼界,原来不但可以混入js,也可以混入vue文件;

(3)TimePicker从UI的表现层面分成两部分:picker 和 panel。其中panel根据用户是否传入了带有range的type决定使用TimePanel还是TimeRangePanel ;

(4)无论是TimePanel还是TimeRangePanel ,最终使用的是time-spinner组件,而这个组件定义了时分秒三列,而只能隐藏秒列,不可以隐藏分钟列。

正所谓“上班用element-ui,下班用饿了么”,element-ui是前端vue开发的好帮手,相信你在使用过程中也会有心得和体会,可以说一说啊。

喜欢的话可以留言,点赞,在看,转发~, 喜欢小编文章的话也可以关注公众号“重温新知”查看更多内容~