vue3二次封装ElDatePicker

灯火 Lv3

前言

时间组件可以手动输入时间如 2025-01-01 或者 2025/01/01,enter后会对输入值进行格式化,但是对于 20250101这样省略间隔符的却不行,查看文档以及issue都没有类似的说明后,便决定对ElDatePicker进行二次封装

下载源码

到GitHub下载element-plus源码

element-plus-download

查找相关逻辑位置

date-picker组件位置
date-picker组件源码位置

date-picker组件介绍
CommonPicker 包裹 date-picker的子组件,组件根据type展示不同子组件

  • date-picker 位置:packages\components\date-picker\src\date-picker.tsx
    • CommonPicker 位置:packages\components\time-picker\src\common\picker.vue
      • date-picker子组件位置:packages\components\date-picker\src\date-picker-com

date-picker介绍

CommonPicker
CommonPicker组件700多行代码,代码太多直接搜索enter,定位607行代码,这里对enter事件进行了监听,当条件符合后执行handleChange以及关闭picker 。
再查看handleChange当输入值有效时就会对输入值使用parseUserInputToDayjs进行格式化覆盖。
parseUserInputToDayjs函数调用了 pickerOptions.value.parseUserInput,对parseUserInput进行溯源发现来自子组件。
CommonPicker
CommonPicker
CommonPicker
CommonPicker

Date-Picker下 子组件
panel-date-picker是date-picker 其中的一个组件。(其他组件对于parseUserInput的赋值都类同)
parseUserInput来自公共函数correctlyParseUserInput,观察函数发现只需要在 if (isString(value)) 添加我所需要的逻辑就行了
packages\components\date-picker\src\utils.ts
panel-date-picker01

编写组件

自身项目下src/components/DatePicker,我只改了DatePickPanel.vue子组件,其他暂时没改

1
2
3
4
5
6
7
8
9
10
11
-DatePicker
-index.ts
-src
-date-picker.tsx
// -utils.ts
// -panel-utils.ts
-components
-DatePickPanel.vue
// -DateRangePickPanel.vue
// -MonthRangePickPanel.vue
// -YearRangePickPanel.vue

DatePickPanel.vue 其他子组件类同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<template>
<PanelDatePick v-bind="{ ...$attrs, ...props }" @set-picker-option="onSetPickerOption">
<template v-for="name in Object.keys($slots)" :key="name" #[name]="form">
<slot :name="name" v-bind="form"></slot>
</template>
></PanelDatePick>
</template>

<script lang="ts" setup>
import PanelDatePick from 'element-plus/es/components/date-picker/src/date-picker-com/panel-date-pick.mjs'
import { panelDatePickProps } from 'element-plus/es/components/date-picker/src/props/panel-date-pick.mjs'
import { PickerOptions } from 'element-plus/es/components/time-picker/src/common/props.mjs'
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import { isString } from '@/utils/is'

const props = defineProps(panelDatePickProps)
// const contextEmit = defineEmits(['pick', 'set-picker-option', 'panel-change'])
const contextEmit = defineEmits(['set-picker-option'])

const onSetPickerOption = <T extends keyof PickerOptions>(e: [T, PickerOptions[T]]) => {
// 重写 parseUserInput
if (e[0] === 'parseUserInput') {
contextEmit('set-picker-option', [
'parseUserInput',
(value: Dayjs | string) => {
console.log('进入parseUserInput')
const format = props.format || 'YYYY-MM-DD HH:mm:ss'
if (isString(value)) {
console.log(value, 'value.')
// 判断为8个数字的格式,如:20250102
if (/^[0-9]{8}$/.test(value)) {
console.log('符合格式')
value = value.slice(0, 4) + '-' + value.slice(4, 6) + '-' + value.slice(6)
}
const dayjsValue = dayjs(value, format)
if (!dayjsValue.isValid()) {
// return directly if not valid
return dayjsValue
}
}
return dayjs(value, format)
}
])
} else {
contextEmit('set-picker-option', e)
}
}
</script>

date-picker.tsx
就是把原本组件复制了过来再改了引入,以及该使用哪个子组件的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { computed, defineComponent, provide, reactive, ref, toRef } from 'vue'
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
import advancedFormat from 'dayjs/plugin/advancedFormat.js'
import localeData from 'dayjs/plugin/localeData.js'
import weekOfYear from 'dayjs/plugin/weekOfYear.js'
import weekYear from 'dayjs/plugin/weekYear.js'
import dayOfYear from 'dayjs/plugin/dayOfYear.js'
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter.js'
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore.js'
import { useNamespace } from 'element-plus/es/hooks/use-namespace/index.mjs'
import {
CommonPicker,
DEFAULT_FORMATS_DATE,
DEFAULT_FORMATS_DATEPICKER,
type DateModelType,
type SingleOrRange,
} from 'element-plus/es/components/time-picker/index.mjs'
import { UPDATE_MODEL_EVENT } from 'element-plus/es/constants/index.mjs'
import { ROOT_PICKER_INJECTION_KEY } from 'element-plus/es/components/date-picker/src/constants.mjs'
import { datePickerProps } from 'element-plus/es/components/date-picker/src/props/date-picker.mjs'
import { getPanel } from 'element-plus/es/components/date-picker/src/panel-utils.mjs'
import type { DatePickerExpose } from 'element-plus/es/components/date-picker/src/instance.mjs'
import DatePickPanel from './components/PanelDatePick.vue'


dayjs.extend(localeData)
dayjs.extend(advancedFormat)
dayjs.extend(customParseFormat)
dayjs.extend(weekOfYear)
dayjs.extend(weekYear)
dayjs.extend(dayOfYear)
dayjs.extend(isSameOrAfter)
dayjs.extend(isSameOrBefore)

export default defineComponent({
name: 'ElDatePicker',
install: null,
props: datePickerProps,
emits: [UPDATE_MODEL_EVENT],
setup(props, { expose, emit, slots }) {
const ns = useNamespace('picker-panel')
const isDefaultFormat = computed(() => {
return !props.format
})
provide('ElIsDefaultFormat', isDefaultFormat)
provide('ElPopperOptions', reactive(toRef(props, 'popperOptions')))
provide(ROOT_PICKER_INJECTION_KEY, {
slots,
pickerNs: ns,
})

const commonPicker = ref<InstanceType<typeof CommonPicker>>()
const refProps: DatePickerExpose = {
focus: () => {
commonPicker.value?.focus()
},
blur: () => {
commonPicker.value?.blur()
},
handleOpen: () => {
commonPicker.value?.handleOpen()
},
handleClose: () => {
commonPicker.value?.handleClose()
},
}

expose(refProps)

const onModelValueUpdated = (val: SingleOrRange<DateModelType> | null) => {
emit(UPDATE_MODEL_EVENT, val)
}

return () => {
// since props always have all defined keys on it, {format, ...props} will always overwrite format
// pick props.format or provide default value here before spreading
const format =
props.format ??
(DEFAULT_FORMATS_DATEPICKER[props.type] || DEFAULT_FORMATS_DATE)
let Component = getPanel(props.type)
if (!['daterange', 'datetimerange', 'monthrange', 'yearrange'].includes(props.type)) {
Component = DatePickPanel as any
}

return (
<CommonPicker
{...props}
format={format}
type={props.type}
ref={commonPicker}
onUpdate:modelValue={onModelValueUpdated}
>
{{
default: (scopedProps: /**FIXME: remove any type */ any) => (
<Component {...scopedProps}>
{{
'prev-month': slots['prev-month'],
'next-month': slots['next-month'],
'prev-year': slots['prev-year'],
'next-year': slots['next-year'],
}}
</Component>
),
'range-separator': slots['range-separator'],
}}
</CommonPicker>
)
}
},
})

index.ts

1
2
3
import DatePicker from './src/date-picker'

export { DatePicker }

效果

参考

vue3中修改element plus组件源码并使用,比如自定义额外逻辑的解决思路方式(亲测有效)

  • 标题: vue3二次封装ElDatePicker
  • 作者: 灯火
  • 创建于 : 2025-03-31 14:37:05
  • 更新于 : 2025-03-31 09:16:18
  • 链接: https://blog.juniverse.top/2025/03/31/ElDatePicker-Secondary-packaging/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论