feat(signing): 签署任务固化文书 HTML 并交互化渲染

This commit is contained in:
yelan
2026-09-14 09:13:07 +08:00
parent 1b576c6095
commit 545326bd81
10 changed files with 328 additions and 116 deletions
@@ -6,7 +6,6 @@ import type { CampusRecord, DepartmentRecord } from '@/api/management/types'
import EditorCkPane from './editors/EditorCkPane.vue'
import EditorTipTapPane from './editors/EditorTipTapPane.vue'
import EditorTinyPane from './editors/EditorTinyPane.vue'
import EditorWangPane from './editors/EditorWangPane.vue'
import type { DocumentTemplate, TemplateEditorForm } from '../types'
@@ -26,7 +25,7 @@ const emit = defineEmits<{
// Word 自维护方案:科室把文书从 Word/WPS 直接粘贴进来(保留表格与基本格式),
// 或上传 .docx 由 mammoth 转换;签署时由交互管线识别"勾选"表并转为签署域。
// 富文本引擎做成可切换的三套(对比 Word 粘贴保真度),存储统一为编辑器产出的 HTML。
type EditorEngine = 'wangeditor' | 'ckeditor5' | 'tinymce' | 'tiptap'
type EditorEngine = 'wangeditor' | 'ckeditor5' | 'tiptap'
interface EditorPaneHandle {
getText: () => string
@@ -36,7 +35,6 @@ interface EditorPaneHandle {
const engineComponents = {
wangeditor: EditorWangPane,
ckeditor5: EditorCkPane,
tinymce: EditorTinyPane,
tiptap: EditorTipTapPane,
}
@@ -51,11 +49,6 @@ const engineOptions: Array<{ key: EditorEngine; label: string; note: string }> =
label: 'CKEditor 5',
note: 'PasteFromOffice 插件,Word 粘贴保真最强(GPL2)',
},
{
key: 'tinymce',
label: 'TinyMCE',
note: '均衡之选(MIT)',
},
{
key: 'tiptap',
label: 'TipTap',
@@ -1,82 +0,0 @@
<script setup lang="ts">
import { shallowRef } from 'vue'
// 副作用导入:注册全局 window.tinymce(@tinymce/tinymce-vue 会检测全局实例,从而走本地包而非 CDN)。
import 'tinymce/tinymce'
import 'tinymce/themes/silver'
import 'tinymce/icons/default/icons'
import 'tinymce/models/dom'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/table'
import 'tinymce/skins/ui/oxide/skin.min.css'
import TinyEditor from '@tinymce/tinymce-vue'
// TinyMCE 8(GPL 开源模式需声明 license_key: 'gpl')。
// skin: false / content_css: false —— 皮肤样式由打包引入,iframe 内容样式用 content_style 内联。
const editorRef = shallowRef<TinyMceApi | null>(null)
interface TinyMceApi {
getContent: (options?: { format?: string }) => string
setContent: (content: string) => void
insertContent: (content: string) => void
}
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const init = {
license_key: 'gpl',
menubar: false,
placeholder: '将 Word / WPS 中的文书内容直接粘贴到此处(保留表格与基本格式)。',
// TinyMCE 8:paste 已并入核心,不能再声明(声明会导致插件加载失败)。
plugins: 'lists table',
toolbar:
'undo redo | bold italic underline | alignleft aligncenter alignright alignjustify | fontsizeselect forecolor backcolor | bullist numlist | table',
skin: false,
content_css: false,
content_style: `
body { font-family: 'SimSun', 'Songti SC', serif; font-size: 14px; line-height: 1.8; color: #1c2b33; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #52646e; padding: 5px 7px; }
`,
paste_data_images: true,
height: 460,
// tinymce-vue 不派发 init 事件,编辑器实例只能从这里拿。
init_instance_callback: (editor: TinyMceApi) => {
editorRef.value = editor
},
}
function getText(): string {
return editorRef.value?.getContent({ format: 'text' }).trim() ?? ''
}
function insertText(text: string) {
editorRef.value?.insertContent(text)
}
defineExpose({ getText, insertText })
</script>
<template>
<div class="tiny-pane">
<TinyEditor
:model-value="props.modelValue"
:init="init"
@update:model-value="emit('update:modelValue', $event)"
/>
</div>
</template>
<style scoped>
.tiny-pane {
overflow: hidden;
border: 1px solid var(--line);
border-radius: 8px;
}
</style>