Skip to content

提升 Element Plus el-date-picker 输入体验:自动跳转输入光标

一、背景介绍

在我们的财务审核模块中,审核人员每天都需要频繁选择过去某一时间点进行对账或复审。由于每天处理的数据量很大,如果能提高日期时间的手动输入体验,哪怕只快一两秒,积累起来也是对工作效率的提升

在使用 Element Plus 作为 UI 框架的项目中,el-date-picker 用于选择日期时间。虽然它支持手动输入日期时间字符串(type="datetime"),但默认输入体验仍有不足:

  • 当用户修改某个字段(如月份)后,光标不会自动跳转到下一个字段并选中(如日期)。
  • 这导致用户需要频繁用键盘移动光标,降低输入效率。

因此,本文介绍如何通过监听原生输入事件,结合微任务调度,手动设置光标,实现输入时自动跳转光标到下一个时间段的体验优化。

二、问题描述

假设当前选中时间为:

js
2025-06-19 10:51:58

用户想将月份 06 修改为 07,期望在输入完成「7」后,光标能够自动跳转到日期 19 后面并选中19,方便继续输入。

但 Element Plus 默认行为是:

  • 输入 07 后光标依然停留在「7」后面,
  • 需要手动用方向键或鼠标切换光标位置,
  • 输入体验不流畅。

三、解决方案概述

1. 利用 el-date-picker 支持的 id 属性

el-date-picker 允许给组件绑定 id,这个 id 会自动赋给其内部的原生 <input> 输入框。这样我们可以通过 document.getElementById() 直接获取到真实的输入元素。

2. 监听原生 inputclick 事件

通过监听原生事件,实时获取用户输入的新值,并与上次值对比,定位发生变化的时间字段。

3. 拆分时间字符串,精准判断修改字段

将输入的时间字符串拆解成「年、月、日、时、分、秒」数组,去遍历比较变化,判断用户修改的是哪一部分。

4. 根据修改字段,计算下一个光标跳转位置

根据时间字段的固定长度和分隔符,计算下一个时间段字符串在输入框中的起止索引。

5. 手动调用 inputEl.setSelectionRange(start, end) 设置光标

通过原生方法设置光标,使光标自动跳转到下一个字段开始位置。

四、关键代码示例

js
<template>
  <el-date-picker
    type="datetime"
    v-model="date"
    placeholder="请选择或输入时间"
    id="datePickerDatetime"
  />
</template>

<script setup>
import { onMounted, ref, nextTick } from 'vue'

const date = ref()

onMounted(async () => {
  await nextTick() 

  // 获取原生 input 元素
  const inputEl = document.getElementById('datePickerDatetime')
  if (!inputEl) return

  let lastValue = inputEl.value // 记录上次值

  const inputEvent = (event) => {
    const newValue = event.target.value

    if (lastValue) {
      /**
       * 将 datetime 字符串拆分为 [年, 月, 日, 时, 分, 秒]
       */
      function splitDateTime(stringTime) {
        try {
          const [datePart, timePart] = stringTime?.split(' ')
          if (!datePart || !timePart) return []
          const date = datePart.split('-')
          const time = timePart.split(':')
          return [...date, ...time]
        } catch (e) {
          console.log(e)
        }
      }

      const diffLengthMap = {
        0: 4, // 年
        1: 2, // 月
        2: 2, // 日
        3: 2, // 时
        4: 2, // 分
        5: 2, // 秒
      }

      const splitNewTime = splitDateTime(newValue)
      const splitLastTime = splitDateTime(lastValue)

      // 找出变化的字段索引(年、月、日、时、分、秒)
      let diffIndex
      for (const index in splitNewTime) {
        diffIndex = undefined
        // 本次输入的值和定义的每个时间长度不一致,不触发跳转
        if (splitNewTime[index].length !== diffLengthMap[index]) break
        if (splitNewTime[index] !== splitLastTime[index]) {
          diffIndex = index
          break
        }
      }

      if (!diffIndex) return

      /**
       * 根据变化字段,计算下一字段在字符串中的位置范围
       * 返回 [start, end],用于设置光标区间
       */
      const findTimeIndex = (mapLen) => {
        let totalIndex = 0
        for (let key in diffLengthMap) {
          if (key <= mapLen) {
            totalIndex += diffLengthMap[key]
          }
        }
        // 每段之间包含一个分隔符(`-`, ` `, `:`),所以 +1
        const lastIndex = totalIndex + Number(mapLen) + 1
        return [lastIndex, lastIndex + diffLengthMap[Number(mapLen) + 1]]
      }

      let rangeIndex = findTimeIndex(diffIndex)

      // 若是最后一段(秒),则将光标跳转至末尾
      if (diffIndex === '5') {
        rangeIndex = [newValue.length, newValue.length]
      }

      // 设置光标位置
      inputEl.setSelectionRange(rangeIndex[0], rangeIndex[1])
    }

    // 更新上一次的值
    lastValue = newValue
  }

  // 绑定 input 和 click 两种交互事件
  inputEl.addEventListener('input', inputEvent)
  inputEl.addEventListener('click', inputEvent)
})
</script>

五、为什么需要 await nextTick()

Element Plus 的 el-date-picker 组件内部,真实的 <input> 元素是异步渲染的,不是同步挂载到 DOM。

js
/* 
  Element Plus 的 el-date-picker 组件内部 input 元素的挂载是异步完成的,
  所以我们需要将获取 DOM 的操作延后执行。
  使用 await nextTick() 会隐式将后续代码包装成微任务,
  保证它排在 el-date-picker 的DOM挂载任务完成之后执行
*/

onMounted 中直接获取 document.getElementById('datePickerDatetime') 会导致拿不到

六、总结

  • 通过给 el-date-picker 设置 id,可以轻松获取内部原生输入框元素。
  • 利用原生事件监听和字符串拆分判断字段变更,实现自动跳转光标逻辑。
  • 结合 Vue 的 nextTick(),保证能正确拿到异步渲染的 DOM。
  • 该方案可以优化高频使用场景手动输入日期时间的用户体验,如财务审核、对账等后台系统。

最后更新于: