2023-06-08 21:09:28 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<editor :key="editorKey" v-model="content" :init="initConfig" :id="props.id"></editor>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { reactive, ref, watch, computed } from "vue"
|
|
|
|
|
|
import { useAppStore } from "@/store"
|
|
|
|
|
|
|
|
|
|
|
|
import Editor from "@tinymce/tinymce-vue"
|
|
|
|
|
|
import tinymce from "tinymce/tinymce"
|
|
|
|
|
|
|
|
|
|
|
|
import "tinymce/icons/default"
|
|
|
|
|
|
import "tinymce/models/dom"
|
|
|
|
|
|
import "tinymce/themes/silver"
|
|
|
|
|
|
|
2024-05-30 11:29:44 +08:00
|
|
|
|
// import "tinymce/plugins/advlist" // 高级列表
|
|
|
|
|
|
// import "tinymce/plugins/anchor" // 锚点
|
|
|
|
|
|
// import "tinymce/plugins/autolink" // 自动链接
|
|
|
|
|
|
import "tinymce/plugins/autosave" // 自动存稿
|
|
|
|
|
|
// import "tinymce/plugins/charmap" // 特殊字符
|
|
|
|
|
|
import "tinymce/plugins/code" // 编辑源码
|
|
|
|
|
|
// import "tinymce/plugins/codesample" // 代码示例
|
|
|
|
|
|
// import "tinymce/plugins/directionality" // 文字方向
|
|
|
|
|
|
import "tinymce/plugins/image" // 插入编辑图片
|
|
|
|
|
|
import "tinymce/plugins/importcss" // 引入css
|
|
|
|
|
|
// import "tinymce/plugins/insertdatetime" // 插入日期时间
|
|
|
|
|
|
import "tinymce/plugins/lists" // 列表插件
|
|
|
|
|
|
import "tinymce/plugins/nonbreaking" // 插入不间断空格
|
|
|
|
|
|
// import "tinymce/plugins/pagebreak" // 插入分页符
|
|
|
|
|
|
// import "tinymce/plugins/preview" // 预览
|
|
|
|
|
|
import "tinymce/plugins/quickbars" // 快速工具栏
|
|
|
|
|
|
import "tinymce/plugins/save" // 保存
|
|
|
|
|
|
import "tinymce/plugins/searchreplace" // 查找替换
|
|
|
|
|
|
import "tinymce/plugins/table" // 表格
|
|
|
|
|
|
// import "tinymce/plugins/visualblocks" //显示元素范围
|
|
|
|
|
|
import "tinymce/plugins/visualchars" // 显示不可见字符
|
|
|
|
|
|
// import "tinymce/plugins/wordcount" // 字数统计
|
2023-06-08 21:09:28 +08:00
|
|
|
|
|
|
|
|
|
|
const appStore = useAppStore()
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
modelValue: { type: String },
|
2024-05-15 17:04:11 +08:00
|
|
|
|
height: { type: Number, default: 200 },
|
2023-06-08 21:09:28 +08:00
|
|
|
|
id: { type: String, default: () => "tinymce" + new Date().getTime().toString() },
|
|
|
|
|
|
plugins: {
|
|
|
|
|
|
type: [String, Array],
|
2024-05-30 11:29:44 +08:00
|
|
|
|
default: "searchreplace visualchars code table nonbreaking lists autosave"
|
2023-06-08 21:09:28 +08:00
|
|
|
|
},
|
|
|
|
|
|
toolbar: {
|
|
|
|
|
|
type: [String, Array],
|
2024-05-15 17:04:11 +08:00
|
|
|
|
// 如果要取消粘贴只粘贴文本,需要用户加格式请加上pastetext
|
|
|
|
|
|
default:
|
2024-05-30 11:29:44 +08:00
|
|
|
|
"code undo redo restoredraft | paste | bold | alignleft alignjustify indent formatpainter | \
|
|
|
|
|
|
styleselect formatselect fontselect fontsizeselect | bullist numlist | subscript superscript removeformat"
|
2023-06-08 21:09:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(["update:modelValue", "change"])
|
|
|
|
|
|
|
|
|
|
|
|
let content = computed({
|
|
|
|
|
|
get() {
|
|
|
|
|
|
return props.modelValue
|
|
|
|
|
|
},
|
|
|
|
|
|
set(value) {
|
|
|
|
|
|
emit("update:modelValue", value)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const list = ref([])
|
|
|
|
|
|
|
2024-05-30 11:29:44 +08:00
|
|
|
|
// 辅助函数:遍历元素和子元素的style样式
|
|
|
|
|
|
function cleanStyles(element) {
|
|
|
|
|
|
element.removeAttribute("style") // 移除元素自身的style属性
|
|
|
|
|
|
element.removeAttribute("class") // 移除元素自身的class属性
|
|
|
|
|
|
// 遍历子元素,递归清理样式
|
|
|
|
|
|
for (const child of element.children) {
|
|
|
|
|
|
// 移除子元素的style以及class属性
|
|
|
|
|
|
cleanStyles(child)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 辅助函数:将元素中span变为text节点
|
|
|
|
|
|
function removeUnwantedSpansAndMore(element) {
|
|
|
|
|
|
// 所有span变为字符串
|
|
|
|
|
|
const spans = element.querySelectorAll("span")
|
|
|
|
|
|
for (const span of spans) {
|
|
|
|
|
|
const text = span.textContent
|
|
|
|
|
|
const textNode = document.createTextNode(text)
|
|
|
|
|
|
const parent = span.parentNode
|
|
|
|
|
|
parent.removeChild(span)
|
|
|
|
|
|
parent.appendChild(textNode)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 所有a元素变成为字符串
|
|
|
|
|
|
const as = element.querySelectorAll("a")
|
|
|
|
|
|
for (const a of as) {
|
|
|
|
|
|
const text = a.textContent
|
|
|
|
|
|
const textNode = document.createTextNode(text)
|
|
|
|
|
|
const parent = a.parentNode
|
|
|
|
|
|
parent.removeChild(a)
|
|
|
|
|
|
parent.appendChild(textNode)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 辅助函数:去掉注释节点
|
|
|
|
|
|
function removeCommentNodes(node) {
|
|
|
|
|
|
const childNodes = node.childNodes
|
|
|
|
|
|
// 遍历子节点
|
|
|
|
|
|
for (let i = childNodes.length - 1; i >= 0; i--) {
|
|
|
|
|
|
// 如果是注释节点
|
|
|
|
|
|
if (childNodes[i].nodeType === Node.COMMENT_NODE) {
|
|
|
|
|
|
node.removeChild(childNodes[i]) // 从父节点中移除该注释节点
|
|
|
|
|
|
} else if (childNodes[i].nodeType === Node.ELEMENT_NODE) {
|
|
|
|
|
|
removeCommentNodes(childNodes[i]) // 如果是元素节点,递归检查该元素的子节点
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-08 21:09:28 +08:00
|
|
|
|
const initConfig = reactive({
|
|
|
|
|
|
menubar: false, // 菜单栏显隐
|
|
|
|
|
|
language_url: "/tinymce/i18n/zh_CN.js",
|
|
|
|
|
|
language: "zh_CN",
|
2023-06-15 20:13:46 +08:00
|
|
|
|
skin_url: "/tinymce/skins/ui/tinymce-5",
|
2023-06-08 21:09:28 +08:00
|
|
|
|
height: props.height,
|
|
|
|
|
|
toolbar_mode: "wrap",
|
|
|
|
|
|
plugins: props.plugins,
|
|
|
|
|
|
toolbar: props.toolbar,
|
2023-08-25 13:28:24 +08:00
|
|
|
|
skeletonScreen: true,
|
2023-06-08 21:09:28 +08:00
|
|
|
|
branding: false,
|
|
|
|
|
|
content_css: "/tinymce/skins/content/default/content.css",
|
2024-05-30 11:29:44 +08:00
|
|
|
|
selector: "#textarea1", // 下面自定义样式选中的区域为编辑区
|
|
|
|
|
|
content_style: "body {line-height:1.5;font-size:14px;} p {margin:2px 0px;}", // 这里可以设置自定义样式
|
|
|
|
|
|
// paste_as_text: false, // 粘贴文字只能是纯文本
|
|
|
|
|
|
// 1.自定义粘贴过滤器函数,args.content就是粘贴内容
|
|
|
|
|
|
paste_preprocess: function (plugin, args) {
|
|
|
|
|
|
let content = args.content
|
|
|
|
|
|
const parser = new DOMParser()
|
|
|
|
|
|
const doc = parser.parseFromString(content, "text/html")
|
|
|
|
|
|
// 遍历一级元素
|
|
|
|
|
|
Array.from(doc.body.children).forEach((element) => {
|
|
|
|
|
|
cleanStyles(element)
|
|
|
|
|
|
removeUnwantedSpansAndMore(element)
|
|
|
|
|
|
removeCommentNodes(element)
|
|
|
|
|
|
console.log(element)
|
2023-06-08 21:09:28 +08:00
|
|
|
|
})
|
2024-05-30 11:29:44 +08:00
|
|
|
|
// 将处理后的fragment转换回HTML字符串
|
|
|
|
|
|
args.content = doc.body.innerHTML
|
2023-06-08 21:09:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const editorKey = ref(new Date().getTime())
|
|
|
|
|
|
|
2024-05-30 11:29:44 +08:00
|
|
|
|
// 图片作为img的base64处理
|
2023-06-08 21:09:28 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
() => list.value,
|
|
|
|
|
|
(imgs) => {
|
|
|
|
|
|
let tmp = ""
|
|
|
|
|
|
imgs.map((img) => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
img.indexOf(".jpg") > -1 ||
|
|
|
|
|
|
img.indexOf(".png") > -1 ||
|
|
|
|
|
|
img.indexOf(".bmp") > -1 ||
|
|
|
|
|
|
img.indexOf(".jpeg") > -1 ||
|
|
|
|
|
|
img.indexOf(".svg") > -1 ||
|
|
|
|
|
|
img.indexOf(".gif") > -1
|
|
|
|
|
|
) {
|
|
|
|
|
|
tmp += `<img src=${img} width="100%" />`
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
content.value = content.value ? content.value + tmp : tmp
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => content.value,
|
|
|
|
|
|
(vl) => emit("change", vl)
|
|
|
|
|
|
)
|
|
|
|
|
|
</script>
|
2024-05-15 17:04:11 +08:00
|
|
|
|
<style lang="less"></style>
|