ct
This commit is contained in:
@@ -40,7 +40,8 @@
|
|||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vuedraggable": "^2.24.3",
|
"vuedraggable": "^2.24.3",
|
||||||
"vuex": "^3.2.0",
|
"vuex": "^3.2.0",
|
||||||
"vuex-persistedstate": "^4.0.0-beta.3"
|
"vuex-persistedstate": "^4.0.0-beta.3",
|
||||||
|
"wangeditor": "^4.7.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/runtime": "~7.12.0",
|
"@babel/runtime": "~7.12.0",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<u-input v-model="form.title" placeholder="请输入,最多30字" maxlength="30"/>
|
<u-input v-model="form.title" placeholder="请输入,最多30字" maxlength="30"/>
|
||||||
</u-form-item>
|
</u-form-item>
|
||||||
<u-form-item label="详情描述" prop="content">
|
<u-form-item label="详情描述" prop="content">
|
||||||
<AiTextarea v-model="form.content" placeholder="请输入,最多500字" :maxlength="500"/>
|
<AiEditor v-model="form.content" placeholder="请输入,最多500字" :maxlength="500"/>
|
||||||
</u-form-item>
|
</u-form-item>
|
||||||
<u-form-item label="图片(最多9张)" class="files">
|
<u-form-item label="图片(最多9张)" class="files">
|
||||||
<AiUploader multiple :limit="9" :def.sync="form.fileList" action="/admin/file/add2"/>
|
<AiUploader multiple :limit="9" :def.sync="form.fileList" action="/admin/file/add2"/>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fullArea: [{name: '全国', id: 0}],
|
fullArea: [{name: '全部', id: 0}],
|
||||||
index: '',
|
index: '',
|
||||||
list: [],
|
list: [],
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.fullArea = res.data
|
this.fullArea = res.data
|
||||||
}
|
}
|
||||||
this.fullArea.unshift({name: '全国', id: 0})
|
this.fullArea.unshift({name: '全部', id: 0})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
265
src/components/AiEditor.vue
Normal file
265
src/components/AiEditor.vue
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
<template>
|
||||||
|
<div class="AiEditor" @click="editing=true">
|
||||||
|
<div ref="AiEditorInstance"/>
|
||||||
|
<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}
|
||||||
|
},
|
||||||
|
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))
|
||||||
|
},
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editing(v) {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
!this.ins && this.initEditor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
::v-deep.AiEditor {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
/* 菜单区 */
|
||||||
|
.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;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fontCount {
|
||||||
|
pointer-events: none;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottomPanel {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 24px;
|
||||||
|
z-index: 1999;
|
||||||
|
|
||||||
|
&.fixed {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.invalid {
|
||||||
|
.w-e-text-container, .w-e-toolbar {
|
||||||
|
border-color: red !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fontCount {
|
||||||
|
color: red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user