附加说明:
1)基于element-ui的button和upload组件
2)支持文字和图片混合编辑
实现思路:
页面中主要的组件:多行文本编辑框(高度随文本变化)、图片上传
功能点梳理:
新增段落,在文末或者任一段落上可点击添加段落按钮,点击后在文末或段落后新增一个段落;
新增的段落可选择是文本还是图片,选择文本则将该新增段落变为多行文本编辑框,选择图片则触发选择本地文件进行上传,上传完后替换原新增段落;
任一段落可进行上下移动、删除、在其下增加新增端口的操作;
完成编辑,点击完成编辑按钮,用户可指定后续操作,一般是将编辑的内容保存到后端。
代码如下:
<template>
<div class="editor-viewer">
<div class="content-viewer">
<div class="content-list">
<div v-for="(content, index) in contentList" class="editor-item"
:key="index" :tabindex="index" @mouseover="handleMouseIn(index, content)"
@mouseout="handleMouseOut(index, content)">
<div class="textarea" contenteditable="true" v-if="content.type === 'text'" >
{{content.value}}
</div>
<!-- <textarea v-if="content.type === 'text'" v-model="content.value"></textarea> -->
<img v-if="content.type === 'image'" :src="content.value">
<div class="add-module" v-if="content.type === 'empty'">
<el-button @click="change2Text(index)">A</el-button>
<el-upload :data="{index: index}"
class="el-button"
action="https://jsonplaceholder.typicode.com/posts/"
:show-file-list="false"
:on-success="handleUploadSuccess"
:before-upload="beforeImageUpload" titlr="只能上传jpg/png文件,且不超过2M">
<i class="el-icon-picture" ></i>
</el-upload>
<el-button @click="delOne(index)" icon="el-icon-delete"></el-button>
</div>
<div class="editor-item-ops" v-if="content.type !== 'empty' " v-show="content.visible">
<el-button icon="el-icon-plus" circle @click="beginAdd(index)"></el-button>
<el-button icon="el-icon-arrow-down" v-if="contentList.length > 1 && index !== contentList.length - 1"
circle @click="moveDown(index)"></el-button>
<el-button icon="el-icon-arrow-up" circle v-if="contentList.length > 1 && index !== 0"
@click="moveUp(index)"></el-button>
<el-button icon="el-icon-delete" circle @click="delOne(index, true)"></el-button>
</div>
</div>
</div>
</div>
<div class="editor-btn-viewer">
<el-button class="editor-btn" icon="el-icon-circle-check-outline"
@click="finishEdit"> 完成编辑</el-button>
<el-button class="editor-btn" icon="el-icon-circle-plus-outline" @click="addAtLast"> 添加模块</el-button>
</div>
</div>
</template>
<script>
import {Upload} from 'element-ui'
export default {
data () {
return {
contentList: []
}
},
props: ['contents'],
components: {
ElUpload: Upload
},
methods: {
spliceContent (start, count, added) {
if (typeof added !== 'undefined') {
return this.contentList.splice(start, count, added)
} else {
return this.contentList.splice(start, count)
}
},
addAtLast () {
this.contentList.push({type: 'empty', value: '', visible: false})
},
beginAdd (index) {
this.spliceContent(index+1, 0, {type: 'empty', value: '', visible: false})
},
moveDown (index) {
let cur = this.spliceContent(index, 1)
this.spliceContent(index + 1, 0, cur[0])
},
moveUp (index) {
let cur = this.spliceContent(index, 1)
this.spliceContent(index - 1, 0, cur[0])
},
delOne (index, delImage = false) {
this.spliceContent(index, 1)
if (delImage) {
// todo: 需要删除内容服务器上的图片文件
}
},
change2Text (index) {
this.spliceContent(index, 1, {type: 'text', value: '', visible: false})
},
change2Image (index) {
this.spliceContent(index, 1, {type: 'image', value: '', visible: false})
},
handleUploadSuccess(res, file) {
// res 需要返回附加data:index
let imageUrl = URL.createObjectURL(file.raw);
this.spliceContent(res.index, 1, {type: 'image', value: imageUrl, visible: false})
},
beforeImageUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
console.info(file)
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!');
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
return isJPG && isLt2M;
},
handleMouseIn (index) {
let item = this.contentList[index];
item.visible = true;
this.spliceContent(index, 1, item);
},
handleMouseOut (index) {
let item = this.contentList[index];
item.visible = false;
this.spliceContent(index, 1, item);
},
finishEdit () {
}
},
mounted () {
this.contentList = this.contents.map(el => {
el.visible = false;
return el;
});
}
}
</script>
<style scoped>
.editor-viewer {
width: 600px;
height: 600px;
border: 1px solid #ddd;
position: relative;
}
.content-viewer {
width: 100%;
height: 552px;
overflow-y: scroll;
overflow-x: hidden;
}
.editor-btn-viewer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
}
.el-button {
background-color: #eee;
}
.editor-btn{
width: 50%;
padding: 15px;
outline: none;
border: 1px solid #ddd;
border-radius: 0;
margin: 0;
font-size: 16px;
}
.el-button:hover {
color: #409eff;
border-color: #c6e2ff;
background-color: #ecf5ff;
}
.content-list {
padding: 5px;
}
.editor-item +.editor-item{
margin-top: 5px;
}
.editor-item {
position: relative;
}
div.editor-item:hover{
box-shadow: 0 0 10px #ccc;
}
.editor-item>.textarea,
.editor-item>img{
width: 100%;
height: auto;
border: 1px solid #ccc;
min-height: 200px;
text-align: left;
}
.editor-item>img{
width: 571px;
}
.editor-item>textarea{
resize: none;
}
.editor-item-ops {
height: 50px;
position: absolute;
right: 6px;
bottom: 0;
z-index:100;
}
.editor-item-ops > .editor-btn {
background-color: #666;
}
.add-module {
border: 1px dashed #ccc;
min-height: 100px;
width: 100%;
line-height: 100px;
}
</style>