【EMR 编辑器 · Vue 3 集成详解】
安装 @emr/vue · 模板写法与属性映射 · ref 获取 API · 完整页面示例 · 常见坑
本篇面向 Vue 3 工程(Vite / Vue CLI / Nuxt 均可)。@emr/vue 提供的组件与 React 版props、事件、命令式 API 完全一致,因此《React 集成详解》里的所有思路(保存闭环、只读查看器、静默流水线)都可直接照搬,只是写法换成 Vue 模板语法。本篇讲清楚「映射规则 + 差异点」,并给出完整示例。
【一、安装:为什么会带 react?】
npm install ./emr-core-x.y.z.tgz ./emr-editor-x.y.z.tgz ./emr-vue-x.y.z.tgz react react-dom
很多 Vue 开发者看到要装 react / react-dom 会疑惑,解释一下:@emr/vue 内部渲染的是 React 版组件(这样内置工具栏、打印对话框等 UI 只需维护一份)。react / react-dom 只是它的内部渲染引擎,就像车子里的变速箱——你开车(写 Vue)不需要懂它。业务代码里一行 React 都不会出现,打包时也会被正常 tree-shaking 进 vendor chunk,与你的 Vue 应用互不干扰。
【二、模板写法与属性映射规则】
组件在模板中使用时,遵循 Vue 的标准约定,组件内部已做好桥接:
React 写法
Vue 模板写法
说明
initialDoc={doc}
:initial-doc="doc"
camelCase prop 自动映射 kebab-case
viewMode="preview"
view-mode="preview"
静态字符串 prop
onChange={fn}
@change="fn"
onXxx 回调映射为 xxx 事件
ref={api}(useRef)
ref="api"(ref())
拿到的都是命令式 API 句柄
onSelectionChange 对应 @selection-change;
没有 v-model:文档不是「受控值」而是「挂载时消费」的初始值(原因见 React 篇),运行中读写请走 api;
toolbar 传对象时用 :toolbar="{ ... }",与 React 的对象写法一致。
【三、完整页面示例(打开 → 编辑 → 保存)】
一个完整的 EditorPage.vue:加载病历时 fetch 文档,:key 随 recordId 变化重建编辑器,保存按钮调 api。可直接复制到工程里跑:
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { EmrEditor, type EmrEditorHandle } from '@emr/vue';
const props = defineProps<{ recordId: string }>();
const api = ref<EmrEditorHandle | null>(null);
const doc = ref<any>(null);
const dirty = ref(false);
onMounted(async () => {
const r = await fetch(`/api/emr/load?id=${props.recordId}`);
doc.value = await r.json();
});
async function save() {
await fetch('/api/emr/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: api.value!.getJSON(),
});
dirty.value = false;
}
</script>
<template>
<div style="display:flex;flex-direction:column;height:100vh">
<button :disabled="!dirty" @click="save">保存</button>
<EmrEditor v-if="doc" :key="recordId" ref="api"
:initial-doc="doc" toolbar :user="{ id: '1001', name: '张医生' }"
@change="() => (dirty = true)"
style="flex:1;min-height:0" />
</div>
</template>
:key="recordId" 的作用与 React 的 key 相同:切换病历时整页重建,避免内容串页;
容器高度问题同 React:flex:1 + min-height:0(flex 子项默认不收缩);
api.value 即命令式句柄:api.value?.getJSON()、api.value?.undo()……全部方法与 React 版一致(见《API 速查》)。
【四、数据回填示例(Vue 写法)】
模板 + 数据分离的场景在 Vue 里同样三步走(概念详见《数据源与录入域详解》):
// 1. 加载模板
api.value!.setDoc(templateDoc);
// 2. 回填患者数据
api.value!.setDataValues({ patient: { name: '张三' } });
// 3. 导出 PDF
const bytes = await api.value!.exportPDF({ fonts: { regular: url } });
【五、常见坑速查】
报「react is not defined / 找不到 react」:漏装 react / react-dom(见第一节);
在 setup 顶层立刻调 api.value?.xxx 报 null:组件挂载后 ref 才有值,请在 onMounted 之后或事件回调里调用;
改了 :initial-doc 页面没变:initialDoc 仅首次挂载消费——用 :key 重建或 api.value.setDoc();
@change 每次按键都触发:与 React 相同,置脏标记 + 节流保存,别直接发请求;
Vue Devtools 里看到组件树有一层 React 组件:正常现象,那就是编辑器本体。