273 lines
6.7 KiB
Vue
273 lines
6.7 KiB
Vue
<template>
|
|
<div class="AiEditor" :class="{noBorder}" @click="editing=true">
|
|
<div ref="AiEditorInstance" style="-webkit-user-select:text;"/>
|
|
<div class="bottomPanel" :class="{fixed:isFullScreen}">
|
|
<slot v-if="$slots.bottom" name="bottom"/>
|
|
<div v-else-if="maxlength" class="fontCount">{{ [editorText.length, maxlength].join(" / ") }}</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
/**
|
|
* 原组件wangeditor封装
|
|
* 修改者:Kubbo
|
|
*/
|
|
import E from 'wangeditor'
|
|
|
|
export default {
|
|
name: "AiEditor",
|
|
inject: {
|
|
elFormItem: {default: ""},
|
|
elForm: {default: ''},
|
|
},
|
|
model: {
|
|
prop: "value",
|
|
event: "change"
|
|
},
|
|
props: {
|
|
value: {type: String, required: true, default: ""},
|
|
placeholder: {default: '请输入正文'},
|
|
conf: Object,
|
|
instance: {type: Function},
|
|
maxlength: Number,
|
|
valid: {type: Boolean, default: true},
|
|
noBorder: Boolean
|
|
},
|
|
data() {
|
|
return {
|
|
ins: null,
|
|
isFullScreen: false,
|
|
isPasteStyle: true,//粘贴是否携带格式
|
|
origin: "",
|
|
editorText: "",
|
|
editing: false//自动赋值问题,
|
|
}
|
|
},
|
|
computed: {
|
|
validateState() {
|
|
return ['', 'success'].includes(this.elFormItem?.validateState)
|
|
},
|
|
extra() {
|
|
return {
|
|
fullscreen(editor) {
|
|
if (editor?.ins.$toolbarElem) {
|
|
let btn = E.$(`<div class="w-e-menu" data-title="全屏"><i class="w-e-icon-fullscreen"/></div>`)
|
|
btn.on("click", () => {
|
|
editor.isFullScreen = !editor.isFullScreen
|
|
editor.isFullScreen ? editor.ins.fullScreen() : editor.ins.unFullScreen()
|
|
})
|
|
editor.ins.$toolbarElem.append(btn)
|
|
}
|
|
},
|
|
preview(editor) {
|
|
if (editor?.ins.$toolbarElem) {
|
|
let btn = E.$(`<div class="w-e-menu" data-title="预览"><i class="el-icon-monitor"/></div>`)
|
|
btn.on("click", () => {
|
|
editor.$refs.preview.dialog = true
|
|
})
|
|
editor.ins.$toolbarElem.append(btn)
|
|
}
|
|
},
|
|
pasteWithStyle(editor) {
|
|
if (editor?.ins.$toolbarElem) {
|
|
let btn = E.$(`<div class="w-e-menu w-e-active" data-title="粘贴格式"><i class="iconfont iconCopy"/></div>`)
|
|
editor.origin = JSON.parse(JSON.stringify(editor.value))
|
|
btn.on("click", () => {
|
|
editor.isPasteStyle = !editor.isPasteStyle
|
|
if (editor.isPasteStyle) {
|
|
editor.setContent(editor.origin)
|
|
btn.addClass("w-e-active")
|
|
} else {
|
|
editor.origin = JSON.parse(JSON.stringify(editor.value))
|
|
editor.setContent(editor.editorText)
|
|
btn.removeClass("w-e-active")
|
|
}
|
|
})
|
|
editor.ins.$toolbarElem.append(btn)
|
|
}
|
|
}
|
|
}
|
|
},
|
|
customConfig() {
|
|
let init = ["fullscreen", "preview", "pasteWithStyle"]
|
|
if (this.maxlength > 0) init = init.slice(0, 2)
|
|
return {
|
|
debug: true,
|
|
pasteFilterStyle: !this.isPasteStyle,
|
|
showFullScreen: false,
|
|
zIndexFullScreen: 1000,
|
|
zIndex: 98,
|
|
focus: false,
|
|
menus: [],
|
|
init,
|
|
customUploadImg: (files, insert) => {
|
|
this.uploadFile(files, insert)
|
|
},
|
|
customUploadVideo: (files, insert) => {
|
|
this.uploadFile(files, insert)
|
|
},
|
|
onfocus: () => {
|
|
this.editing = true
|
|
},
|
|
onblur: () => {
|
|
this.editing = false
|
|
},
|
|
onchange: html => {
|
|
if (this.maxlength > 0) {
|
|
this.editorText = html?.replace(/<\/?.+?\/?>/g, '')
|
|
if (this.editorText.length > this.maxlength) {
|
|
this.ins.history.revoke()
|
|
// this.editorText = this.editorText.substring(0, this.maxlength)
|
|
// this.setContent(this.editorText)
|
|
}
|
|
} else {
|
|
this.editorText = html
|
|
}
|
|
this.$emit("change", this.editorText)
|
|
},
|
|
pasteTextHandle: str => this.isPasteStyle ? str : str?.replace(/<\/?.+?\/?>/g, ''),
|
|
...this.conf
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
initEditor() {
|
|
let {placeholder, customConfig} = this
|
|
this.ins = new E(this.$refs.AiEditorInstance)
|
|
this.ins.config = {...this.ins.config, ...customConfig, placeholder}
|
|
this.ins.create()
|
|
customConfig.init.map(e => this.extra?.[e]?.(this))
|
|
this.value && this.setContent(this.value)
|
|
|
|
},
|
|
setContent(data) {
|
|
if (this.ins) {
|
|
this.ins.txt.html(data)
|
|
this.editing = false
|
|
}
|
|
},
|
|
uploadFile(files, insert) {
|
|
files && files.map(e => {
|
|
let formData = new FormData()
|
|
formData.append('file', e)
|
|
this?.instance?.post(`/admin/file/add`, formData).then(res => {
|
|
if (res && res.data) {
|
|
res.data.map(m => {
|
|
let item = m.split(";")
|
|
insert(item[0])
|
|
})
|
|
}
|
|
})
|
|
})
|
|
},
|
|
/**
|
|
* 表单验证
|
|
* @param componentName
|
|
* @param eventName
|
|
* @param params
|
|
*/
|
|
dispatch(componentName, eventName, params) {
|
|
let parent = this.$parent || this.$root;
|
|
let name = parent.$options.componentName;
|
|
|
|
while (parent && (!name || name !== componentName)) {
|
|
parent = parent.$parent;
|
|
|
|
if (parent) {
|
|
name = parent.$options.componentName;
|
|
}
|
|
}
|
|
if (parent) {
|
|
parent.$emit.apply(parent, [eventName].concat(params));
|
|
}
|
|
},
|
|
},
|
|
watch: {
|
|
value(v) {
|
|
this.dispatch('ElFormItem', 'el.form.change', [v]);
|
|
if (v && !this.editing) {
|
|
this.setContent(v)
|
|
}
|
|
},
|
|
placeholder(v) {
|
|
if (this.ins) {
|
|
document.querySelector('.AiEditor .placeholder').innerHTML = v
|
|
}
|
|
},
|
|
},
|
|
mounted() {
|
|
!this.ins && this.initEditor()
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
::v-deep.AiEditor {
|
|
width: 100%;
|
|
position: relative;
|
|
|
|
&.noBorder {
|
|
.w-e-toolbar, .w-e-text-container {
|
|
border-color: transparent !important;
|
|
}
|
|
}
|
|
|
|
/* 菜单区 */
|
|
.w-e-toolbar {
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
-webkit-box-lines: multiple;
|
|
|
|
.w-e-menu {
|
|
line-height: 24px;
|
|
|
|
a {
|
|
text-decoration: none;
|
|
}
|
|
|
|
&:hover {
|
|
z-index: 10002 !important;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 编辑区 */
|
|
.w-e-text {
|
|
overflow: auto;
|
|
padding: 10px;
|
|
word-break: break-all;
|
|
|
|
p {
|
|
margin: 0;
|
|
}
|
|
}
|
|
|
|
.fontCount {
|
|
pointer-events: none;
|
|
color: #999;
|
|
}
|
|
|
|
.bottomPanel {
|
|
position: absolute;
|
|
bottom: 8px;
|
|
right: 24px;
|
|
z-index: 99;
|
|
|
|
&.fixed {
|
|
position: fixed;
|
|
}
|
|
}
|
|
|
|
&.invalid {
|
|
.w-e-text-container, .w-e-toolbar {
|
|
border-color: red !important;
|
|
}
|
|
|
|
.fontCount {
|
|
color: red
|
|
}
|
|
}
|
|
}
|
|
</style>
|