本文将介绍如何实现一个高效、用户友好的病历写回功能,支持多输入框内容插入与焦点管理。该解决方案已在医疗系统中广泛应用,可大幅提升医生书写病历的效率。
实现效果如下

痛点分析与解决方案
医疗系统常见痛点
- 🚫 焦点迷失:在多个输入区域(主诉、现病史、体格检查等)间切换时容易失去位置
- 🔁 重复输入:常见症状描述和诊断结论需要反复输入
- ⏳ 效率低下:医疗工作者需要花费大量的时间去完成病例写回工作
我们的创新解决方案
- ✅ 智能焦点追踪:自动记录当前和最后激活的输入框
- ⚡ 模板快速插入:一键插入预设模板内容
- 🎯 光标精确定位:确保插入内容后光标位置正确
- 💡 可视化状态:实时显示当前激活区域
核心实现代码解析
一、关键变量解析 🧠
js
const activeInput: Ref<InputElement | null> = ref(null) // 当前获得焦点的输入框
const lastActiveInput: Ref<InputElement | null> = ref(null) // 最后获得焦点的输入框(用于恢复)
const registerEls: Ref<InputElement[]> = ref([]) // 所有注册的输入框实例
二. 核心方法解析 ✨
1. registerInput(el: InputElement)
-
功能:注册输入框并绑定事件监听
-
绑定事件:
mousedown/focus
→ 触发handleFocus
记录活跃输入框blur
→ 触发handleBlur
清除当前焦点
-
存储 :将元素添加到注册列表
registerEls
2. insertContent(content: string)
-
插入逻辑:
- 优先使用
activeInput
,若不存在则使用lastActiveInput
- 若无有效输入框 → 显示警告提示
- 尝试获取焦点 → 计算光标位置
- 在光标处插入内容 → 更新光标位置
- 触发
input
事件通知Vue更新数据
- 优先使用
-
关键技术点:
js// 在光标位置插入内容 target.value = target.value.slice(0, start) + content + target.value.slice(end) // 设置新光标位置 target.setSelectionRange(newPos, newPos) // 触发数据绑定更新 target.dispatchEvent(new Event('input', { bubbles: true }))
3. cleanup()
-
资源清理:
- 移除所有注册元素的事件监听
- 重置所有响应式变量
-
使用场景:组件卸载时调用防止内存泄漏
4. 事件处理函数
js
function handleFocus(e: Event) {
const target = e.target as InputElement
activeInput.value = target // 更新当前焦点
lastActiveInput.value = target // 更新最后焦点备份
}
function handleBlur() {
activeInput.value = null // 清除当前焦点
}
三、工作流程 🔧

四、设计特点 🌟
- 双重焦点备份 :
activeInput
+lastActiveInput
确保即使输入框失焦也能定位 - 自动数据同步 :通过派发
input
事件触发Vue数据绑定更新 - 安全恢复机制 :插入前尝试
target.focus()
恢复焦点 - 内存管理:提供显式清理接口避免内存泄漏
五、使用场景示例
js
<script setup lang="ts">
import { cleanup, insertContent, registerInput } from '../hooks/useWriteBackHistory'
const dialogVisible = ref(false)
function onCancel() {
dialogVisible.value = false
cleanup()
}
const content = ref('你很好,你很优秀,你是最棒的!')
function onload() {
insertContent(content.value)
}
const formInline = reactive({
user: '',
region: '',
})
/**
* @description 写回病历-注册
* @param el
*/
function setInputRef(el: any) {
if (el && el.$el) {
registerInput(el.$el)
}
}
</script>
<template>
<el-dialog
v-model="dialogVisible"
title="Tips"
width="800"
>
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="主诉">
<el-input :ref="setInputRef" v-model="formInline.user" placeholder="主诉" clearable type="textarea" />
</el-form-item>
<el-form-item label="诊断">
<el-input :ref="setInputRef" v-model="formInline.user" placeholder="诊断" clearable type="textarea" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="onCancel">
取消
</el-button>
<el-button type="primary" @click="onload">
写回病例
</el-button>
</div>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
</style>
六、潜在问题及改进
- 移动端兼容性 :
mousedown
事件在移动端可能需要替换为touchstart
- 多实例冲突:全局变量在多个组件实例中使用时可能相互干扰
- 性能优化:大量输入框注册时考虑弱引用(WeakRef)管理
- 富文本支持:当前仅支持原生输入框,如需富文本编辑器需扩展
技术创造价值,代码改善生活 - 通过前端技术创新,我们可以为医疗行业带来真正的变革
欢迎在评论区交流你的想法和实践经验!