
实现在使用el-date-picker的 type="datetimerange"和type="datetime"的情况下 在时分秒输入框进行自定义输入内容转换为时间格式
如输入120000转换成12:00:00
输入123456转换成12:34:56
输入12自动补零位12:00:00
创建公共方法registerDateTimeInput
/**
* 时间格式化:将数字串转换为时间格式
* @param digits 数字字符串
* @returns 格式化后的时间字符串
* @example
* formatTimeDigits('12') // '12:00:00'
* formatTimeDigits('123') // '12:30:00'
* formatTimeDigits('1234') // '12:34:00'
* formatTimeDigits('12345') // '12:34:50'
* formatTimeDigits('123456') // '12:34:56'
*
* cleanUpTime = registerDateTimeInput({
* keepDatePart: true, // 如果是纯时间就 false,日期+时间就 true
* maxDigits: 6 //如果是时分秒就是6位,时分就是4位
*})
*/
let isRegistered = false
let registeredHandler: ((e: KeyboardEvent) => void) | null = null
let registeredBlurHandler: ((e: FocusEvent) => void) | null = null
// 根据 maxDigits 自动格式化 4/6 位
export const formatTimeDigits = (digits: string, maxDigits: number): string => {
if (digits.length === 0) return ''
if (digits.length === 1) return digits
if (digits.length === 2) return digits
// 4位:HH:mm
if (maxDigits === 4) {
if (digits.length >= 3) {
return `${digits.slice(0, 2)}:${digits.slice(2)}`
}
return digits
}
// 6位:HH:mm:ss
if (digits.length === 3) return `${digits.slice(0, 2)}:${digits.slice(2)}`
if (digits.length === 4) return `${digits.slice(0, 2)}:${digits.slice(2, 4)}`
if (digits.length === 5) return `${digits.slice(0, 2)}:${digits.slice(2, 4)}:${digits.slice(4)}`
if (digits.length >= 6) return `${digits.slice(0, 2)}:${digits.slice(2, 4)}:${digits.slice(4, 6)}`
return digits
}
// 失焦补0(自动识别 4/6 位)
export const formatTimeDigitsWithPad = (digits: string, maxDigits: number): string => {
const padded = digits.padEnd(maxDigits, '0').slice(0, maxDigits)
return formatTimeDigits(padded, maxDigits)
}
export const getPositionAfterNthDigit = (formatted: string, n: number): number => {
let digitCount = 0
for (let i = 0; i < formatted.length; i++) {
if (/\d/.test(formatted[i])) {
digitCount++
if (digitCount === n) return i + 1
}
}
return formatted.length
}
export const splitDateTime = (value: string): { datePart: string; timePart: string } => {
if (value.includes(' ')) {
const parts = value.split(' ')
return { datePart: parts[0], timePart: parts[1] || '' }
}
return { datePart: '', timePart: value }
}
export const combineDateTime = (datePart: string, timePart: string): string => {
return datePart ? `${datePart} ${timePart}` : timePart
}
export const syncToComponent = (input: HTMLInputElement): void => {
requestAnimationFrame(() => {
input.dispatchEvent(new Event('input', { bubbles: true }))
})
}
interface DateTimeInputOptions {
maxDigits?: number
keepDatePart?: boolean
}
// 🔥 核心:同时匹配单时间和范围时间的 el-date-picker 输入框
const isTargetTimeInput = (el: HTMLElement): boolean => {
return (
// 单时间选择器结构
!!el.closest('.el-date-picker__time-header') ||
// 范围时间选择器结构
!!el.closest('.el-date-range-picker__time-header')
)
}
export const handleDateTime = (
e: KeyboardEvent,
options: DateTimeInputOptions = {}
): void => {
const { maxDigits = 6, keepDatePart = true } = options
const el = document.activeElement
if (!(el instanceof HTMLInputElement) || !el.classList.contains('el-input__inner')) return
// 🔥 只对目标时间输入框生效(单时间/范围时间都覆盖)
if (!isTargetTimeInput(el)) return
const key = e.key
const isNumber = /^\d$/.test(key)
const isBackspace = key === 'Backspace'
const isControl = ['ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End'].includes(key)
if (isControl || e.ctrlKey || e.metaKey) return
if (!isNumber && !isBackspace) {
e.preventDefault()
return
}
e.preventDefault()
const input = el
const start = input.selectionStart || 0
const end = input.selectionEnd || 0
const oldValue = input.value
let { datePart, timePart } = keepDatePart ? splitDateTime(oldValue) : { datePart: '', timePart: oldValue }
let digits = timePart.replace(/\D/g, '')
let digitPos = 0
const timeStartPos = keepDatePart && datePart ? datePart.length + 1 : 0
for (let i = timeStartPos; i < start && i < oldValue.length; i++) {
if (/\d/.test(oldValue[i])) digitPos++
}
if (isNumber) {
if (start !== end) {
let selected = 0
for (let i = start; i < end; i++) {
if (/\d/.test(oldValue[i])) selected++
}
digits = digits.slice(0, digitPos) + digits.slice(digitPos + selected)
}
const newDigits = digits.slice(0, digitPos) + key + digits.slice(digitPos)
if (newDigits.length > maxDigits) return
const formattedTime = formatTimeDigits(newDigits, maxDigits)
const newValue = keepDatePart ? combineDateTime(datePart, formattedTime) : formattedTime
input.value = newValue
syncToComponent(input)
requestAnimationFrame(() => {
const posInTime = getPositionAfterNthDigit(formattedTime, digitPos + 1)
const finalPos = keepDatePart && datePart ? datePart.length + 1 + posInTime : posInTime
input.setSelectionRange(finalPos, finalPos)
})
}
if (isBackspace) {
if (start !== end) {
let selected = 0
for (let i = start; i < end; i++) {
if (/\d/.test(oldValue[i])) selected++
}
digits = digits.slice(0, digitPos) + digits.slice(digitPos + selected)
} else if (digitPos > 0) {
digits = digits.slice(0, digitPos - 1) + digits.slice(digitPos)
digitPos--
}
const formattedTime = formatTimeDigits(digits, maxDigits)
const newValue = keepDatePart ? combineDateTime(datePart, formattedTime) : formattedTime
input.value = newValue
syncToComponent(input)
requestAnimationFrame(() => {
const posInTime = getPositionAfterNthDigit(formattedTime, digitPos)
const finalPos = keepDatePart && datePart ? datePart.length + 1 + posInTime : posInTime
input.setSelectionRange(finalPos, finalPos)
})
}
}
// 失焦统一补0(仅目标输入框生效)
export const completeTimeOnBlur = (input: HTMLInputElement, keepDatePart: boolean, maxDigits: number) => {
if (!isTargetTimeInput(input)) return
const val = input.value.trim()
const { datePart, timePart } = splitDateTime(val)
const digits = timePart.replace(/\D/g, '')
if (digits.length < maxDigits) {
const fullTime = formatTimeDigitsWithPad(digits, maxDigits)
input.value = keepDatePart ? combineDateTime(datePart, fullTime) : fullTime
syncToComponent(input)
}
}
// 注册(防重复)
export const registerDateTimeInput = (options?: DateTimeInputOptions): (() => void) => {
if (isRegistered && registeredHandler && registeredBlurHandler) {
return () => {
document.removeEventListener('keydown', registeredHandler!)
document.removeEventListener('focusout', registeredBlurHandler!)
isRegistered = false
registeredHandler = null
registeredBlurHandler = null
}
}
const { keepDatePart = true, maxDigits = 6 } = options || {}
const handler = (e: KeyboardEvent) => handleDateTime(e, options)
function handleFocusOut(e: FocusEvent) {
const tar = e.target as HTMLInputElement
if (tar?.classList.contains('el-input__inner')) {
if (!isTargetTimeInput(tar)) return
completeTimeOnBlur(tar, keepDatePart, maxDigits)
setTimeout(() => tar.dispatchEvent(new Event('change', { bubbles: true })), 20)
}
}
registeredHandler = handler
registeredBlurHandler = handleFocusOut
isRegistered = true
document.addEventListener('keydown', handler, true)
document.addEventListener('focusout', handleFocusOut)
return () => {
document.removeEventListener('keydown', handler, true)
document.removeEventListener('focusout', handleFocusOut)
isRegistered = false
registeredHandler = null
registeredBlurHandler = null
}
}
如何使用
在页面或者组件引入registerDateTimeInput方法
registerDateTimeInput参数
keepDatePart----如果是纯时间就 false,日期+时间就 true 但是基本上都是只有日期+时间会出现
maxDigits----如果是时分秒就6位,时分就是4位
import { registerDateTimeInput } from '@/utils/dateAutoFormat'
let cleanUpTime: (() => void) | null = null
onMounted(() => {
cleanUpTime = registerDateTimeInput({
keepDatePart: true,
maxDigits: 6
})
})
onUnmounted(() => {
cleanUpTime?.()
cleanUpTime = null
})


被折叠的 条评论
为什么被折叠?



