使用el-date-picker的 type=“datetimerange“和type=“datetime“的情况下 在时分秒输入框进行自定义输入内容转换为时间格式

实现在使用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
})

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值