Tinymce富文本编辑器二次开发电子病历时解决的bug
<template>
<div :class="prefixCls" :style="{ width: containerWidth }">
<ImgUpload
:fullscreen="fullscreen"
@uploading="handleImageUploading"
@done="handleDone"
v-if="showImageUpload && !props.isHide"
v-show="editorRef"
:disabled="disabled"
:uploadParams="props.uploadParams"
/>
<textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden' }" v-if="!initOptions.inline"></textarea>
<slot v-else></slot>
<signModal @register="signatureModal" @success="handleSignature" @exportSign="getSign" />
<recordModal @register="recorderModal" @success="handleGetText" />
</div>
</template>
<script lang="ts">
import sign from '/@/assets/svg/sign.svg';
import recordSvg from '/@/assets/svg/record.svg';
import type { Editor, RawEditorSettings, BodyComponentSpec } from 'tinymce';
import { useMessage } from '/@/hooks/web/useMessage';
import tinymce from 'tinymce/tinymce';
import 'tinymce/themes/silver';
import 'tinymce/icons/default/icons';
import 'tinymce/plugins/advlist';
import 'tinymce/plugins/anchor';
import 'tinymce/plugins/autolink';
import 'tinymce/plugins/autosave';
import 'tinymce/plugins/code';
import 'tinymce/plugins/codesample';
import 'tinymce/plugins/directionality';
import 'tinymce/plugins/fullscreen';
import 'tinymce/plugins/hr';
import 'tinymce/plugins/insertdatetime';
import 'tinymce/plugins/link';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/media';
import 'tinymce/plugins/nonbreaking';
import 'tinymce/plugins/noneditable';
import 'tinymce/plugins/pagebreak';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/preview';
import 'tinymce/plugins/print';
import 'tinymce/plugins/save';
import 'tinymce/plugins/searchreplace';
import 'tinymce/plugins/spellchecker';
import 'tinymce/plugins/tabfocus';
import 'tinymce/plugins/table';
import 'tinymce/plugins/template';
import 'tinymce/plugins/textpattern';
import 'tinymce/plugins/visualblocks';
import 'tinymce/plugins/visualchars';
import 'tinymce/plugins/wordcount';
// import '/@/components/MedicalTinymce/plugins/control/index.js';
import { defineComponent, computed, nextTick, ref, unref, watch, onDeactivated, onBeforeUnmount, toRaw } from 'vue';
import ImgUpload from './ImgUpload.vue';
import { toolbar, plugins } from './tinymce';
import { buildShortUUID } from '/@/utils/uuid';
import { bindHandlers } from './helper';
import { useModal } from '/@/components/Modal';
import { ActionEnum, VALIDATE_API } from '/@/enums/commonEnum';
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
import { useDesign } from '/@/hooks/web/useDesign';
import { isNumber } from '/@/utils/is';
import { useLocale } from '/@/locales/useLocale';
import { useAppStore } from '/@/store/modules/app';
import { asyncFindDefUrlById, asyncFindUrlById } from '/@/api/lamp/file/upload';
import signModal from '/@/components/Signature/components/signModal/index.vue';
import recordModal from '/@/components/CustomRecorder/index.vue';
const tinymceProps = {
options: {
type: Object as PropType<Partial<RawEditorSettings>>,
default: () => ({}),
},
value: {
type: String,
},
toolbar: {
type: Array as PropType<string[]>,
default: toolbar,
},
plugins: {
type: Array as PropType<string[]>,
default: plugins,
},
modelValue: {
type: String,
},
height: {
type: [Number, String] as PropType<string | number>,
required: false,
default: 400,
},
width: {
type: [Number, String] as PropType<string | number>,
required: false,
default: 'auto',
},
showImageUpload: {
type: Boolean,
default: true,
},
isDef: {
type: Boolean,
default: false,
},
uploadParams: {
type: Object as PropType<any>,
default: {},
},
isHide: {
type: Boolean,
default: false,
},
isPrint: {
type: Boolean,
default: false,
},
templatevalue: {
type: Array,
},
getParams: {
type: Boolean,
default: false,
},
};
export default defineComponent({
name: 'Tinymce',
components: { ImgUpload, signModal, recordModal },
inheritAttrs: false,
props: tinymceProps,
emits: ['change', 'update:modelValue', 'inited', 'init-error', 'templateparams'],
setup(props, { emit, attrs }) {
const { createMessage } = useMessage();
const editorRef = ref<Nullable<Editor>>(null);
const fullscreen = ref(false);
const tinymceId = ref<string>(buildShortUUID('tiny-vue'));
const elRef = ref<Nullable<HTMLElement>>(null);
let dialogConfig = ref(null);
const { prefixCls } = useDesign('tinymce-container');
const [signatureModal, { openModal: openSignModal }] = useModal();
const [recorderModal, { openModal: openRecord }] = useModal();
const appStore = useAppStore();
const appEnv = import.meta.env.MODE;
let currentBookMark = ref<any>('');
const tinymceContent = computed(() => props.modelValue);
const childBtn = {
type: 'grid', // component type
columns: 1, // number of columns
items: [
{
type: 'button',
name: 'add',
text: '添加子项',
},
{
type: 'button',
name: 'del',
text: '删除子项',
},
{
type: 'collection', // component type
name: 'collection', // identifier
label: '',
},
{
type: 'collection', // component type
name: 'collection1', // identifier
label: '',
},
], // array of panel components
};
let childItem = {
type: 'grid', // component type
columns: 1, // number of columns
items: [
{
type: 'grid',
columns: 2,
items: [
{
type: 'input',
name: 'label1',
label: '标签1',
},
{
type: 'input',
name: 'value1',
label: '值1',
},
],
},
], // array of panel components
};
const containerWidth = computed(() => {
const width = props.width;
if (isNumber(width)) {
return `${width}px`;
}
return width;
});
const skinName = computed(() => {
return appStore.getDarkMode === 'light' ? 'oxide' : 'oxide-dark';
});
const langName = computed(() => {
const lang = useLocale().getLocale.value;
return ['zh_CN', 'en'].includes(lang) ? lang : 'zh_CN';
});
const initOptions = computed((): RawEditorSettings => {
const { height, options, toolbar, plugins } = props;
const publicPath = import.meta.env.VITE_PUBLIC_PATH || '/';
return {
selector: `#${unref(tinymceId)}`,
height,
// toolbar: appEnv === 'development' ? [...toolbar, 'HtmlBtn'] : toolbar,
toolbar: !!props.isHide ? false : !!props.isPrint ? false : toolbar,
menubar: !!props.isHide ? false : !!props.isPrint ? 'print' : 'file edit insert view format table',
menu: {
print: {
title: '打印',
items: 'print',
},
},
plugins,
fontsize_formats: '8pt 10pt 12pt 14pt 16pt 18pt 20pt 22pt 24pt 36pt',
font_formats: `微软雅黑='微软雅黑';宋体='宋体';黑体='黑体';仿宋='仿宋';楷体='楷体';隶书='隶书';幼圆='幼圆';
Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;
Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,
courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,
arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet
ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings`,
language_url: publicPath + 'resource/tinymce/langs/' + langName.value + '.js',
language: langName.value,
branding: false,
default_link_target: '_blank',
link_title: false,
object_resizing: false,
auto_focus: true,
skin: skinName.value,
skin_url: publicPath + 'resource/tinymce/skins/ui/' + skinName.value,
content_css: publicPath + 'resource/tinymce/skins/ui/' + skinName.value + '/content.min.css',
...options,
extended_valid_elements: 'a[class|target|href|onclick],div[class|onclick|id|style],link[rel|href]',
setup: (editor: Editor) => {
console.log(editor, 'editoreditoreditoreditoreditor');
editorRef.value = editor;
editor.on('init', (e) => initSetup(e));
// 注册一个icon
editor.ui.registry.addIcon(
'shopping-cart',
`<svg viewBox="0 0 1024 1024" data-icon="shopping-cart" width="1.5em" height="1.5em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M922.9 701.9H327.4l29.9-60.9 496.8-.9c16.8 0 31.2-12 34.2-28.6l68.8-385.1c1.8-10.1-.9-20.5-7.5-28.4a34.99 34.99 0 0 0-26.6-12.5l-632-2.1-5.4-25.4c-3.4-16.2-18-28-34.6-28H96.5a35.3 35.3 0 1 0 0 70.6h125.9L246 312.8l58.1 281.3-74.8 122.1a34.96 34.96 0 0 0-3 36.8c6 11.9 18.1 19.4 31.5 19.4h62.8a102.43 102.43 0 0 0-20.6 61.7c0 56.6 46 102.6 102.6 102.6s102.6-46 102.6-102.6c0-22.3-7.4-44-20.6-61.7h161.1a102.43 102.43 0 0 0-20.6 61.7c0 56.6 46 102.6 102.6 102.6s102.6-46 102.6-102.6c0-22.3-7.4-44-20.6-61.7H923c19.4 0 35.3-15.8 35.3-35.3a35.42 35.42 0 0 0-35.4-35.2zM305.7 253l575.8 1.9-56.4 315.8-452.3.8L305.7 253zm96.9 612.7c-17.4 0-31.6-14.2-31.6-31.6 0-17.4 14.2-31.6 31.6-31.6s31.6 14.2 31.6 31.6a31.6 31.6 0 0 1-31.6 31.6zm325.1 0c-17.4 0-31.6-14.2-31.6-31.6 0-17.4 14.2-31.6 31.6-31.6s31.6 14.2 31.6 31.6a31.6 31.6 0 0 1-31.6 31.6z"></path></svg>`,
);
// 注册获取html以及数据的按钮
registerSignBtn(editor);
},
// 生命周期:挂载后回调
init_instance_callback: (editor: Editor) => {
// 修改编辑器默认字体和字号
editor.getBody().style.fontSize = '16pt';
editor.getBody().style.fontFamily = '宋体';
},
};
});
// 注册获取html以及数据的按钮
function registerSignBtn(editor: Editor) {
editor.ui.registry.addButton('CardBtn', {
type: 'button',
// icon: `shopping-cart`,
text: '获取并保存html',
onAction: function (_) {
//按钮事件:组装 html + data数据
getControlValue();
saveTemplate(editor.getContent(), getControlValue());
},
});
}
// 获取控件数据值
function getControlValue() {
let dom = tinymce.activeEditor.dom;
let controls = dom.select('.control');
let data = controls.map((item) => {
// console.log('item', item);
let dataControl = JSON.parse(item.getAttribute('data-control'));
let controlValue = item.getAttribute('data-value');
//文本框 没有data-value
console.log(controlValue, item.firstElementChild.innerHTML, 'item.firstElementChild.innerHTML');
if (!controlValue) {
if (dataControl.initialData.select == 'input') {
if (!!item.firstElementChild.innerHTML) {
controlValue = item.firstElementChild.innerHTML;
} else {
controlValue = '';
}
}
// controlValue = item.firstElementChild.innerHTML;
}
return {
controlType: dataControl.initialData.select,
fieldName: dataControl.initialData.name,
fieldKey: dataControl.initialData.fieldKey,
controlValue,
};
});
console.log(data);
return data;
}
// 保存模板
async function saveTemplate(doc: string, data: any) {
try {
const params: any = { doc, data };
emit('templateparams', params);
} finally {
}
}
const disabled = computed(() => {
const { options } = props;
const getdDisabled = options && Reflect.get(options, 'readonly');
const editor =