前言
本文是在Tinymce富文本编辑器添加自定义toolbar,二级菜单,自定义表单,签名的基础之上进行一些bug记录,功能添加,以及模版的应用和打印
项目描述
- 建立电子病历模版—录入(电子病历模版和电子病历打印模版)—查看电子病历和打印病历模版
- 建立电子病历----添加一个电子病历可以添加多个电子病历模版----并输入设置的值提交以key和value的组成的数组结构,提交,并在编辑时返显电子病历模版和数据----点击打印按钮,调出打印模版,赋值进行打印。
一、模版应用及打印
- 模版的应用
点击新增电子病历,选择用户,选择电子模版,展示。此时电子病历只填值,此处需要优化。
选择完顾客,顾客的信息需要返显在电子模版上,
-
解决的问题:
模版返显—拿到模版的key和value组成的数组—然后赋值—更新模版—在页面中展示
-
引出的问题:
1)输入后每次光标会到最左侧,输入出错。
原因封装的编辑器每次更新之后都会通过模版的key和value组成的数组,然后重新设置模版,所以光标返回最左侧
解决方法,定义变量只有一次拿到模版的key和value组成的数组,其他变更不触发emit返回新的数组,不重新给模版赋值
编辑组件的应用<Editorref="editorTinymce"height="900"v-model="templateData" // 模版的html:templatevalue="templatevalue" // 模版的key和value组成的数组:isHide="true" // 是否展示菜单栏和toolbar栏v-if="!!templateData" // 模版展示条件:templateData="templateData" // 模版的html:getParams="getParams" // 是否需要获取key和value组成的数组@templateparams="handleTemplateParams" // 模版返回的key和value组成的数组/>
- 模版的赋值
点击左侧已添加的病历,返显模版以及将模版之前输入的值进行返显
- 引出的问题:
1)切换模版,赋值有时不更新
赋值分2种,1.初始化赋值,2.变更赋值。
解决方法,第一次点击为初始化赋值,之后的点击为变更赋值,切换已添加的模版并没有销毁编辑器,所以是变更。
- 模版的打印
点击编辑页打印—调出打印模版—将录入模版的值赋值给打印模版
首先:输入模版和打印模版对应的值需要设置对应的key,如果不一样会赋值失败,此处属于初化始化赋值。
点击列表操作列的打印—弹出所有关联的模版—选中模版,给打印模版赋值,进行打印预览
- 模版的销毁
关闭弹窗之后,需要销毁模版,否者再次打开时之前打开的模版还在。
封装组件代码
<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 typecolumns: 1, // number of columnsitems: [{type: 'button',name: 'add',text: '添加子项',},{type: 'button',name: 'del',text: '删除子项',},{type: 'collection', // component typename: 'collection', // identifierlabel: '',},{type: 'collection', // component typename: 'collection1', // identifierlabel: '',},], // array of panel components};let childItem = {type: 'grid', // component typecolumns: 1, // number of columnsitems: [{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=trebuchetms,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));// 注册一个iconeditor.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-valueconsole.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 = unref(editorRef);if (editor) {editor.setMode(getdDisabled ? 'readonly' : 'design');}return getdDisabled ?? false;});watch(() => attrs.disabled,() => {const editor = unref(editorRef);if (!editor) {return;}editor.setMode(attrs.disabled ? 'readonly' : 'design');},);onMountedOrActivated(() => {if (!initOptions.value.inline) {tinymceId.value = buildShortUUID('tiny-vue');}nextTick(() => {setTimeout(() => {initEditor();}, 30);});});onBeforeUnmount(() => {destory();});onDeactivated(() => {destory();});function destory() {if (tinymce !== null) {tinymce?.remove?.(unref(initOptions).selector!);}}// 弹框配置let Dialog = (editor) => {return {title: '添加控件', // The dialog's title - displayed in the dialog headerbody: {type: 'panel', // The root body type - a Panel or TabPanelitems: [// A list of panel components{type: 'selectbox',name: 'select',label: '控件',items: [{ value: 'input', text: '输入框' },{ value: 'date', text: '日期' },{ value: 'radio', text: '单选框' },{ value: 'checkbox', text: '多选框' },{ value: 'select', text: '下拉' },{ value: 'sign', text: '签名' },{ value: 'record', text: '语音转换' },// { value: 'textarea', text: 'textarea' },],},{type: 'input', //类型可以是 checkbox, input, selectbox, textarea and urlinputname: 'name',label: '字段名称',},{type: 'input', //类型可以是 checkbox, input, selectbox, textarea and urlinputname: 'fieldKey',label: '字段key值',},{type: 'selectbox', //类型可以是 checkbox, input, selectbox, textarea and urlinputname: 'isShow',label: '字段名称是否展示',items: [{ value: '1', text: '展示' },{ value: '2', text: '不展示' },],},// {// type: 'checkbox',// name:'checkbox',// label:'checkbox'// },// {// type: 'button',// name:'tianjia',// text:'添加子项',// disabled:false// },// {// type: 'htmlpanel', // A HTML panel component// html: '11'// },],},//初始值// initialData: {// name: '2'// },buttons: [// A list of footer buttons{type: 'cancel',name: 'closeButton',text: '取消',},{type: 'submit',primary: true,text: '确认',},],// radio select checkbox 添加子项onAction: dialogFn.onAction(editor),// 切换表单控件onChange: dialogFn.onChange(editor),onSubmit: dialogFn.onSubmit(editor),};};function initEditor() {const el = unref(elRef);if (el) {el.style.visibility = '';}tinymce.init(unref(initOptions)).then((editor) => {emit('inited', editor);}).catch((err) => {emit('init-error', err);});tinymce.PluginManager.add('control', function (editor) {dialogConfig.value = Dialog(editor);const dialogOpener = () => {return editor.windowManager.open(dialogConfig.value);};editor.ui.registry.addButton('control', {icon: 'non-breaking', //图标tooltip: '插入控件', //提示text: '控件库',onAction: function () {dialogOpener();},});setupButtons(editor);if (!props.isHide) {addToEditor(editor);}initBindEvent(editor);});}// 为初始化内容中控件绑定事件 和 控件值回填let initBindEvent = (editor) => {console.log(props.templatevalue, 'propspropspropsprops');let templateValue = toRaw(props.templatevalue);editor.on('init', () => {//绑定事件bindEvent(editor);//控件值回填let dom = tinymce.activeEditor.dom;let controls = dom.select('.control');controls.forEach((item) => {let dataControl = JSON.parse(item.getAttribute('data-control'));let controlValue = item.getAttribute('data-value');if (!!templateValue && templateValue.length > 0) {templateValue.forEach((i) => {if (i.fieldKey == dataControl.initialData.fieldKey) {controlValue = i.controlValue;switch (dataControl.initialData.select) {case 'input':if (!!controlValue) {console.log(item, item.firstElementChild, 'itemitemitemitemitem');if (!!item.firstElementChild) {item.firstElementChild.innerText = controlValue;}// item.setAttribute('data-value', controlValue);}break;case 'radio':if (!!controlValue) {item.querySelector('[value="' + controlValue + '"]').checked = true;}break;case 'checkbox':let checkboxs = item.querySelectorAll('input');checkboxs.forEach((it) => {if (controlValue.split(',').includes(it.value)) {it.checked = true;}});break;case 'select':item.querySelector('[value=' + controlValue + ']').selected = true;break;}}});} else {switch (dataControl.initialData.select) {case 'radio':if (!!controlValue) {item.querySelector('[value="' + controlValue + '"]').checked = true;}break;case 'checkbox':let checkboxs = item.querySelectorAll('input');checkboxs.forEach((it) => {if (controlValue.split(',').includes(it.value)) {it.checked = true;}});break;case 'select':item.querySelector('[value=' + controlValue + ']').selected = true;break;}}console.log(controlValue);});});};let addToEditor = (editor) => {// 添加悬浮 上下文工具栏editor.ui.registry.addContextToolbar('editcontrol', {//触发条件predicate: function (node) {console.log(node, props.isHide, 'isHideisHideisHide');// alert(node);// if (node.className == 'c-sign') {// openSignModal(true, {// type: ActionEnum.ADD,// });// }return !props.isHide && node.className === 'control';// return !props.isHide && node.className === 'control' && node.nodeName.toLowerCase() === 'span';},items: 'changecontrol removecontrol', //显示的工具列表position: 'selection', //工具栏放置位置 selection node line// scope: 'node',});};// 弹框中的方法let dialogFn = {// radio select checkbox 添加子项编辑控件onAction: (edntor) => (dialogApi, details) => {let data = dialogApi.getData();if (details.name == 'add') {addChildItem(dialogApi);}if (details.name == 'del') {let items = dialogConfig.value.body.items;items[items.length - 1].items.pop();}dialogConfig.value.initialData = data;dialogApi.redial(dialogConfig.value);},// 控件弹窗选择控件Change事件onChange: (editor) => (dialogApi, details) => {console.log(dialogConfig.value.body, details, dialogApi);if (dialogConfig.value.title == '编辑控件') return;let data = dialogApi.getData();// dialogConfig.body.items[4].html = formControl[data.select]if (data.select == 'input' || data.select == 'date' || data.select == 'textarea') {dialogConfig.value.body.items.splice(4);// dialogApi.redial(dialogConfig.value);// dialogApi.setData(data);}if (data.select != 'input' &&data.select != 'date' &&data.select != 'textarea' &&data.select != 'sign' &&data.select != 'record' &&!dialogConfig.value.body.items[4]) {let btns = JSON.parse(JSON.stringify(childBtn));let items = JSON.parse(JSON.stringify(childItem));dialogConfig.value.body.items.splice(4, 0, btns, items);dialogApi.redial(dialogConfig.value);dialogApi.setData(data);}// dialogApi.redial(dialogConfig.value);// if (details.name == 'select') dialogApi.redial(dialogConfig.value); //重新渲染dialog// dialogApi.setData(data);// dialogApi.focus(); // 聚焦console.log('dataTdataTdataT', data);},// 控件弹窗确认回调事件onSubmit: (editor) => (api) => {let control = '';let data = api.getData();let controlName = data.select + getId();// 输入框if (data.select == 'input') {control = `<span contenteditable="true" style="display:inline-block;min-width:100px;border-bottom:1px solid black;outline: none;padding: 0" name=${controlName} type="${data.select}"> </span>`;} else if (data.select == 'date') {// 日期control = `<input class="c-form" contenteditable="true" style='border: none;border-bottom: 1px solid' name=${controlName} type="datetime-local" value="${data.name}" />`;} else if (data.select == 'textarea') {//文本御control = `<textarea class="c-form" contenteditable="true" rows="2" cols="30" style="display:inline-block;min-width:100px;border-bottom:1px solid;" name=${controlName}></textarea>`;}// if(data.select == 'input' || data.select == 'date'){// control = `<input contenteditable="true" name=${controlName} type="${data.select}" value="${data.name}" />`// }else if (data.select == 'radio' || data.select == 'checkbox') {let l = dialogConfig.value.body.items[dialogConfig.value.body.items.length - 1].items.length;for (let i = 1; i <= l; i++) {control =control +`<label contenteditable="true"><input class="c-form" name=${controlName} type=${data.select} value=${data['value' + i]} /> ${data['label' + i]}</label>`;}} else if (data.select == 'select') {// 下拉let l = dialogConfig.value.body.items[dialogConfig.value.body.items.length - 1].items.length;for (let i = 1; i <= l; i++) {control = control + `<option value=${data['value' + i]} label=${data['label' + i]}></option>`;}control = `<select class="c-form" contenteditable="true" style='border: none;border-bottom: 1px solid;padding: 5px 0px 5px 5px' name=${controlName}>${control}</select>`;} else if (data.select === 'sign') {control = `<img style="width: 32px;height: 32px;" class='c-sign' src='${sign}'></img>`;} else if (data.select === 'record') {control = `<img style="width: 32px;height: 32px;" class='c-record' src='${recordSvg}'></img>`;}// 通用 dom 结构和样式control = `${!!data.name && data.isShow == '1' && dialogConfig.value.title != '编辑控件' ? `<span>${data.name}</span>:` : ``}<span class="control" id="span1"style="display:inline-block;margin:0 5px;background-color: #f1f1f1;"contenteditable="false" data-control=${JSON.stringify({ body: dialogConfig.value.body, initialData: data })} data-value="">${control}<span class="c-menu" style="padding:0 5px;"></span></span></span>`;// console.log(editor.selection.getNode())if (editor.selection.getContent()) {//编辑控件// editor.selection.getNode().parentNode.removeChild(editor.selection.getNode());editor.selection.setContent(control);// editor.insertContent(control)} else {//添加控件editor.insertContent(control);}bindEvent(editor);api.close();},};// 为控件绑定事件let bindEvent = (editor) => {console.log(navigator.userAgent, 'editoreditoreditor');let dom = tinymce.activeEditor.dom;// console.log(dom.select('#span1'))setTimeout(() => {// dom.bind(dom.select('.c-menu'), 'click', (e) => {// // 显示指定的上下文菜单// editor.dispatch('contexttoolbar-show', { toolbarKey: 'editcontrol' });// // 隐藏指定的上下文菜单// editor.dispatch('contexttoolbar-hide', { toolbarKey: 'editcontrol' });// e.stopPropagation();// });dom.bind(dom.select('.c-form'), 'change', (e) => {if (e.target.type == 'date') {e.target.parentNode.setAttribute('data-value', e.target.value);e.target.setAttribute('value', e.target.value);}if (e.target.type == 'radio') {e.target.parentNode.parentNode.querySelectorAll('label').forEach((item) => {item.querySelector('input').removeAttribute('checked');});e.target.parentNode.parentNode.setAttribute('data-value', e.target.value);e.target.setAttribute('checked', 'checked');}if (e.target.type == 'checkbox') {let checkedArr = [];let parentSpan = e.target.parentNode.parentNode;// ---------------------- 值响应有问题parentSpan.querySelectorAll('label').forEach((item) => {let checkbox = item.querySelector('input');// checkbox.removeAttribute('checked')if (checkbox.checked) {checkedArr.push(checkbox);// checkbox.setAttribute('checked','checked')}});parentSpan.setAttribute('data-value', checkedArr.toString());}if (e.target.localName == 'select') {e.target.querySelectorAll('option').forEach((item) => {item.removeAttribute('selected');if (item.selected) {item.setAttribute('selected', 'selected');}});e.target.parentNode.setAttribute('data-value', e.target.value);}});// 单独为 日期 控件绑定失焦事件,change不好使dom.bind(dom.select('.c-form'), 'blur', (e) => {if (e.target.type == 'datetime-local') {const time = new Date(e.target.value).toLocaleString();e.target.parentNode.setAttribute('data-value', time);e.target.setAttribute('value', e.target.value);}});//签名点击事件dom.bind(dom.select('.c-sign'), `click`, () => {currentBookMark.value = editor.selection.getBookmark(); //记录点击的位置,弹框结束后插入签名openSignModal(true, {type: ActionEnum.ADD,});// editor.selection.setContent(sign); //把签名插入编辑器});// 签名ipad touch事件dom.bind(dom.select('.c-sign'), `touchstart`, (e) => {console.log(e);currentBookMark.value = editor.selection.getBookmark(); //记录点击的位置,弹框结束后插入签名openSignModal(true, {type: ActionEnum.ADD,});// editor.selection.setContent(sign); //把签名插入编辑器});// 语音转换事件dom.bind(dom.select('.c-record'), `click`, () => {currentBookMark.value = editor.selection.getBookmark(); //记录点击的位置,弹框结束后插入文字openRecord(true, {type: ActionEnum.ADD,});// editor.selection.setContent(sign); //把签名插入编辑器});// ipad touch事件dom.bind(dom.select('.c-record'), `touchstart`, (e) => {console.log(e);currentBookMark.value = editor.selection.getBookmark();openRecord(true, {type: ActionEnum.ADD,});// editor.selection.setContent(sign); //把签名插入编辑器});}, 100);};// 添加子项 key value 方法function addChildItem(dialogApi) {console.log(dialogApi, 'dialogApidialogApidialogApidialogApi');let childItems = dialogConfig.value.body.items[dialogConfig.value.body.items.length - 1].items;childItems.push({type: 'grid', // component typecolumns: 2, // number of columnsitems: [{type: 'input',name: 'label' + (childItems.length + 1),label: '标签' + (childItems.length + 1),},{type: 'input',name: 'value' + (childItems.length + 1),label: '值' + (childItems.length + 1),},], // array of panel components});}// 添加上下文工具栏let setupButtons = (editor) => {editor.ui.registry.addButton('changecontrol', {icon: 'edit-block',tooltip: '编辑控件',onAction: () => {console.log(editor);// editor.windowManager.open(dialogConfig)// editor.setContent('1213')// editor.selection.select(123)// editor.selection.setContent('<span>123</span>')// editor.selection.getNode()console.log(editor.selection.getNode());let data = editor.selection.getNode().getAttribute('data-control');// let name = editor.selection.getNode().querySelector('input').getAttribute('name')// let val = editor.selection.getNode().querySelectorAll('[name='+name+']').valuelet dataJson = JSON.parse(data);dialogConfig.value = Dialog(editor);// dialogConfig.value.body = data.body;dialogConfig.value.title = '编辑控件';dialogConfig.value.initialData = dataJson.initialData;if (dataJson.initialData.select == 'radio' || dataJson.initialData.select == 'checkbox') {let btns = JSON.parse(JSON.stringify(childBtn));var keyArr: string[] = [];for (var key in dataJson.initialData) {if (key.indexOf('label') > -1) {keyArr.push(key);}}console.log(keyArr);let obj = {type: 'grid', // component typecolumns: 1, // number of columnsitems: [],};keyArr.forEach((i) => {let index = i.indexOf('label');let strI = i.substring(5);console.log(i, index, strI, 'strIstrI');let itemObj = {type: 'grid',columns: 2,items: [{type: 'input',name: i,label: '标签' + strI,},{type: 'input',name: 'value' + strI,label: '值' + strI,},],};obj.items.push(itemObj);});let items = JSON.parse(JSON.stringify(obj));dialogConfig.value.body.items.splice(4, 0, btns, items);}console.log(dialogConfig.value, 'dialogConfig.valuedialogConfig.valuedialogConfig.value');editor.windowManager.open(dialogConfig.value);},});editor.ui.registry.addButton('removecontrol', {icon: 'remove',tooltip: '删除控件',onAction: (editor) => {editor.selection.setContent('');editor.dispatch('contexttoolbar-hide', { toolbarKey: 'editcontrol' });console.log(arguments);},});};// 获取id namefunction getId() {return '_' + new Date().getTime();}function initSetup(e) {const editor = unref(editorRef);console.log(editor, 'editoreditoreditoreditor');if (!editor) {return;}const value = props.modelValue || '';editor.setContent(value);bindModelHandlers(editor);bindHandlers(e, attrs, unref(editorRef));}function setValue(editor: Recordable, val: string, prevVal?: string) {// console.log(editor, val, props.templatevalue, 'valvalvalval1111111111111111111111111');// console.log(controls, 'controlscontrolscontrols');if (editor && typeof val === 'string' && val !== prevVal && val !== editor.getContent({ format: attrs.outputFormat })) {editor.setContent(val);}if (!!props.getParams) {saveTemplate(editor.getContent(), getControlValue());}}function bindModelHandlers(editor: any) {const modelEvents = attrs.modelEvents ? attrs.modelEvents : null;const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;watch(() => props.modelValue,(val: string, prevVal: string) => {console.log(val, '00000000000000000000');setValue(editor, val, prevVal);},);watch(() => props.value,(val, prevVal) => {console.log(val, prevVal, 'vvvvvvvvvvvvvvvvvvvv');setValue(editor, val, prevVal);},{immediate: true,},);watch(() => props.templatevalue,(val, prevVal) => {console.log(val, prevVal, '1111111111111111');// setTemplateData(editor, val);},{immediate: true,},);editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {const content = editor.getContent({ format: attrs.outputFormat });emit('update:modelValue', content);emit('change', content);});editor.on('FullscreenStateChanged', (e) => {fullscreen.value = e.state;});}function setTemplateData(arr) {let templateValue = toRaw(arr);let dom = tinymce.activeEditor.dom;let controls = dom.select('.control');controls.forEach((item) => {let dataControl = JSON.parse(item.getAttribute('data-control'));let controlValue = item.getAttribute('data-value');if (!!templateValue && templateValue.length > 0) {templateValue.forEach((i) => {if (i.fieldKey == dataControl.initialData.fieldKey) {controlValue = i.controlValue;switch (dataControl.initialData.select) {case 'input':if (!!controlValue) {console.log(item, item.firstElementChild, 'itemitemitemitemitem');if (!!item.firstElementChild) {item.firstElementChild.innerText = controlValue;}// item.setAttribute('data-value', controlValue);}break;case 'radio':if (!!controlValue) {item.querySelector('[value="' + controlValue + '"]').checked = true;}break;case 'checkbox':let checkboxs = item.querySelectorAll('input');checkboxs.forEach((it) => {if (controlValue.split(',').includes(it.value)) {it.checked = true;}});break;case 'select':item.querySelector('[value=' + controlValue + ']').selected = true;break;}}});} else {switch (dataControl.initialData.select) {case 'radio':if (!!controlValue) {item.querySelector('[value="' + controlValue + '"]').checked = true;}break;case 'checkbox':let checkboxs = item.querySelectorAll('input');checkboxs.forEach((it) => {if (controlValue.split(',').includes(it.value)) {it.checked = true;}});break;case 'select':item.querySelector('[value=' + controlValue + ']').selected = true;break;}}console.log(controlValue);});// console.log(data, '000000000000001111111111111111111');// editor.setContent(content);// console.log(dom, '99999999999999999');console.log(controls[0], '666666666666666666666666');}function handleImageUploading(name: string) {const editor = unref(editorRef);if (!editor) {return;}editor.execCommand('mceInsertContent', false, getUploadingImgName(name));const content = editor?.getContent() ?? '';setValue(editor, content);}function handleDone(name: string, fileId: string) {const editor = unref(editorRef);if (!editor) {return;}const content = editor?.getContent() ?? '';if (fileId) {const api = props.isDef ? asyncFindDefUrlById : asyncFindUrlById;api(fileId).then((res) => {// bug: 这里返回的图片链接必须是永久有效的,否则会出现图片过期无法访问的情况。 暂时没好的解决方案if (res.code === 0) {const val = content?.replace(getUploadingImgName(name), `<img data-id="${fileId}" src="${res?.data}"/>`) ?? '';setValue(editor, val);}});} else {const val = content?.replace(getUploadingImgName(name), `<img data-path="${fileId}" src="${fileId}" alt="上传失败"/>`) ?? '';setValue(editor, val);}}function getUploadingImgName(name: string) {return `[uploading:${name}]`;}function clickSetValue(val) {const editor = unref(editorRef);setValue(editor, val);}function handleSignature(data, base64Data) {editorRef.value?.selection.moveToBookmark(currentBookMark.value); // 把光标位置摆正editorRef.value?.focus(); // 聚焦editorRef.value.execCommand(// 填充!搞定!'mceInsertContent',false,`<img style='height: 80px;width: 250px;' data-id="${data.id}" src="${data}" alt=""/>`,);// editorRef.value?.selection.setContent(`<img style='height: 80px;width: 250px' data-id="${data.id}" src="${data}" alt=""/>`);}// 获取语音转换后的文字function handleGetText(data?) {console.log(data);}function getSign(data: any) {console.log(data);}function getSubmitData() {const editor = unref(editorRef);let controlValue = getControlValue();let content = editor?.getContent() ?? '';let data = {doc: content,data: controlValue,};return data;}function destroyTiny() {tinymce.editors[unref(tinymceId)].destroy();}return {prefixCls,containerWidth,initOptions,signatureModal,tinymceContent,elRef,tinymceId,handleImageUploading,handleDone,editorRef,fullscreen,disabled,props,setValue,clickSetValue,handleSignature,getSign,recorderModal,handleGetText,getSubmitData,setTemplateData,destroyTiny,};},
});
</script><style lang="less" scoped></style><style lang="less">
@prefix-cls: ~'@{namespace}-tinymce-container';.@{prefix-cls} {position: relative;line-height: normal;textarea {z-index: -1;visibility: hidden;}
}
</style>
总结
- 需要优化模版应用的时候,达到的效果,只输入,不能对其他的模版设置进行修改,目前可以对模版进行增删改。
- 模版打印的存在同1的问题
- tinymce的中文文档
- tinymce的英文Api