上一篇文章連結
富文字編輯器 vue-quill-editor入門(使用,顯示,表格支援)
之前由於自己也只是粗淺的使用,以為沒有太多人和我一樣用到它
有一些地方,就沒有展開講
沒想到大家用到的還挺多的,於是再展開講解一下
先看一下效果
NewsAdd.vue 新增頁面,可以新增附件
NewsDetail.vue 展示頁面
NewsUpdate.vue 修改頁面 獲取富文字內容,然後展示,以供修改
專案程式碼放github不太合適, 就直接粘一下了
簡單講解一下邏輯
editor.vue 富文字編輯器元件
1.activated元件的時候,會請求介面,載入富文字內容this.quill.root.innerHTML = this.content2,展示相應的內容。
2.在mounted中使用this.quill.on(‘text-change’, () => {this.$emit(‘contentData’, this.quill.root.innerHTML, this.enclosureIDs)})來監聽修改,將html格式的富文字,傳遞給父元件。
3.使用el-upload實現附件上傳,自定義了它的httpRequest
NewsAdd.vue 新增頁面
1.使用@contentData="change(arguments,userDefined)"獲取子元件傳來的內容
2.請求介面,儲存資料
NewsUpdate 修改頁面
1.使用@contentData=「change($event)」 獲取子元件傳來的,修改後的資料
2.請求介面,修改資料
NewsDetail 展示頁面
1.無需引入editor元件,只需要從介面中取出富文字的html,放在
<el-col :span="20">
<div class="ql-container ql-snow">
<div class="ql-editor" v-html="form.content"></div>
</div>
<!-- <editor v-html="form.content" style="background-color:white"></editor> -->
</el-col>
即可顯示內容。
以下為程式碼,大家可以取出自己需要的部分
editor.vue
<template>
<keep-alive>
<div>
<div class="editor"></div>
<el-upload
class="upload-demo"
action="#"
:http-request="httpRequest"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
multiple
:limit="50"
:on-exceed="handleExceed"
:ref="upload"
:file-list="fileList">
<el-button
size="small"
type="primary"
@click="uploadBtn"
class="my-upload"
v-show="false"
style="height:0px">點選上傳</el-button>
<div slot="tip" class="el-upload__tip" style="margin-top:0px">已選擇附件:</div>
</el-upload>
</div>
</keep-alive>
</template>
<script>
import api from '@/api/index'
import Quill from 'quill'
import 'quill/dist/quill.snow.css'
const titleConfig = {
'ql-bold': '加粗',
'ql-color': '顏色',
'ql-font': '字型',
'ql-code': '插入程式碼',
'ql-italic': '斜體',
'ql-link': '新增連結',
'ql-background': '顏色',
'ql-size': '字型大小',
'ql-strike': '刪除線',
'ql-script': '上標/下標',
'ql-underline': '下劃線',
'ql-blockquote': '參照',
'ql-header': '標題',
'ql-indent': '縮排',
'ql-list': '列表',
'ql-align': '文字對齊',
'ql-direction': '文字方向',
'ql-code-block': '程式碼塊',
'ql-formula': '公式',
'ql-image': '圖片',
'ql-video': '視訊',
'ql-clean': '清除字型樣式',
'ql-upload': '檔案',
'ql-table': '插入表格',
'ql-table-insert-row': '插入行',
'ql-table-insert-column': '插入列',
'ql-table-delete-row': '刪除行',
'ql-table-delete-column': '刪除列'
}
export default {
name: 'Editor',
props: {
},
data () {
return {
upload: '',
enclosureIDs: [],
fileReader: null,
fileList: [],
content2: '',
loadData: (date = this.date) => {
return this.$http
.get(api.NewsDetail, {
params: { nid: this.$route.query.nid }
})
.then(res => {
var resData = res.result.data[0]
if (resData) {
this.content2 = resData.content
this.quill.root.innerHTML = this.content2
} else {
this.content2 = ''
}
})
},
quill: null,
uploadAPI: api.Enclosure,
options: {
theme: 'snow',
modules: {
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'],
[{ header: 1 }, { header: 2 }],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ color: [] }, { background: [] }],
[{ font: [] }],
[{ align: [] }],
['clean'],
[
{ table: 'TD' },
{ 'table-insert-row': 'TIR' },
{ 'table-insert-column': 'TIC' },
{ 'table-delete-row': 'TDR' },
{ 'table-delete-column': 'TDC' }
],
['sourceEditor']
],
handlers: {
table: function (val) {
this.quill.getModule('table').insertTable(2, 3)
},
'table-insert-row': function () {
this.quill.getModule('table').insertRowBelow()
},
'table-insert-column': function () {
this.quill.getModule('table').insertColumnRight()
},
'table-delete-row': function () {
this.quill.getModule('table').deleteRow()
},
'table-delete-column': function () {
this.quill.getModule('table').deleteColumn()
},
sourceEditor: function () { // 新增工具方法
document.getElementsByClassName('my-upload')[0].click()
}
}
},
table: true
},
placeholder: ''
}
}
},
methods: {
httpRequest (options) {
var file = options.file
var filename = file.name
const reader = new FileReader()
reader.readAsDataURL(file)
const that = this
reader.onload = function () {
var base64Str = this.result
if (base64Str === 'data:') {
// 為空txt時下載的時候會顯示網路錯誤,這裡進行處理轉換為有一個空格的txt
base64Str = 'data:text/plain;base64,IA=='
}
that.$http
.post(api.Enclosure, {
params: { base64Str: base64Str, name: filename, nid: that.$route.query.nid }
})
.then(res => {
that.enclosureIDs.push({ 'en_id': res.enclosure_id, 'name': filename })
that.$emit('contentData', that.quill.root.innerHTML, that.enclosureIDs)
})
}
},
uploadBtn () {
},
handleRemove (file, fileList) {
var deleteId = 0
if (file.id) {
for (var i = 0; i < this.fileList.length; i++) {
if (this.fileList[i].name === file.name) {
this.fileList.splice(i, 1)
deleteId = file.id
break
}
}
} else if (file.uid) {
for (var j = 0; j < this.enclosureIDs.length; j++) {
if (this.enclosureIDs[j].name === file.name) {
deleteId = this.enclosureIDs[j]['en_id']
this.enclosureIDs.splice(j, 1)
break
}
}
}
// for (var i = this.fileList.length - 1; i > 0; i--) {
// console.log(this.fileList[i])
// }
// this.$emit('contentData', this.quill.root.innerHTML, this.enclosureIDs)
// // 請求刪除附件介面
this.$http
.delete(api.Enclosure, {
params: { eid: deleteId }
})
.then(res => {
this.$emit('contentData', this.quill.root.innerHTML, this.enclosureIDs)
this.$message({
type: 'success',
message: '刪除成功!'
})
})
},
handlePreview (file) {
},
handleExceed (files, fileList) {
this.$message.warning(`當前限制選擇 3 個檔案,本次選擇了 ${files.length} 個檔案,共選擇了 ${files.length + fileList.length} 個檔案`)
},
beforeRemove (file, fileList) {
return this.$confirm(`確定移除 ${ file.name }?`)
},
addQuillTitle () {
const oToolBar = document.querySelector('.ql-toolbar')
const aButton = oToolBar.querySelectorAll('button')
const aSelect = oToolBar.querySelectorAll('select')
aButton.forEach(function (item) {
if (item.className === 'ql-script') {
item.value === 'sub' ? (item.title = '下標') : (item.title = '上標')
} else if (item.className === 'ql-indent') {
item.value === '+1' ? (item.title = '向右縮排') : (item.title = '向左縮排')
} else {
item.title = titleConfig[item.classList[0]]
}
})
aSelect.forEach(function (item) {
item.parentNode.title = titleConfig[item.classList[0]]
})
},
getContentData () {
return this.quill.getContents()
}
},
mounted () {
this.fileReader = new FileReader()
const dom = this.$el.querySelector('.editor')
this.quill = new Quill(dom, this.options)
this.myEl = this.$el.querySelector('.ql-sourceEditor')
// this.quill.setContents(this.value)
this.quill.on('text-change', () => {
// console.log(this.quill.getContents())
// this.$emit('contentData', this.quill.getContents())
// console.log(this.quill.root.innerHTML)
this.$emit('contentData', this.quill.root.innerHTML, this.enclosureIDs)
})
this.$el.querySelector(
'.ql-table-insert-row'
).innerHTML = `<svg t="1591862376726" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6306" width="18" height="200"><path d="M500.8 604.779L267.307 371.392l-45.227 45.27 278.741 278.613L779.307 416.66l-45.248-45.248z" p-id="6307"></path></svg>`
this.$el.querySelector(
'.ql-table-insert-column'
).innerHTML = `<svg t="1591862238963" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6509" width="18" height="200"><path d="M593.450667 512.128L360.064 278.613333l45.290667-45.226666 278.613333 278.762666L405.333333 790.613333l-45.226666-45.269333z" p-id="6510"></path></svg>`
this.$el.querySelector(
'.ql-table-delete-row'
).innerHTML = `<svg t="1591862253524" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6632" width="18" height="200"><path d="M500.8 461.909333L267.306667 695.296l-45.226667-45.269333 278.741333-278.613334L779.306667 650.026667l-45.248 45.226666z" p-id="6633"></path></svg>`
this.$el.querySelector(
'.ql-table-delete-column'
).innerHTML = `<svg t="1591862261059" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6755" width="18" height="200"><path d="M641.28 278.613333l-45.226667-45.226666-278.634666 278.762666 278.613333 278.485334 45.248-45.269334-233.365333-233.237333z" p-id="6756"></path></svg>`
this.addQuillTitle()
this.$el.querySelector(
'.ql-sourceEditor'
).innerHTML = `<svg t="1592278063482" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6358" width="18" height="200"><path d="M704 341.333333h64a64 64 0 0 1 64 64v362.666667a64 64 0 0 1-64 64H256a64 64 0 0 1-64-64V405.333333a64 64 0 0 1 64-64h64v64h-64v362.666667h512V405.333333h-64v-64zM517.653333 124.629333l150.826667 150.826667-45.226667 45.269333-74.026666-74.005333v304.768h-64V247.616l-73.173334 73.130667-45.248-45.248 150.826667-150.848z" p-id="6359"></path></svg>`
},
activated () {
if (this.$route.query.nid) {
this.loadData()
} else {
this.quill.root.innerHTML = ''
}
}
}
</script>
<style>
.el-upload-list__item {
transition: none !important;
}
</style>
NewsAdd.vue
<template>
<keep-alive>
<div :style="widthStyle">
<el-row :gutter="24">
<el-col :span="24">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label>
<h1 style="font-size:30px">釋出訊息</h1>
</el-form-item>
<el-form-item label="型別">
<el-select v-model="select_value" filterable placeholder="型別" @change="selectChanged">
<el-option
v-for="item in SelectOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="標題">
<el-input placeholder="標題" v-model="title"></el-input>
</el-form-item>
<el-form-item label="內容">
<editor @contentData="change(arguments,userDefined)" ref="son"></editor>
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="isAble" @click="onSubmit">立即建立</el-button>
</el-form-item>
<el-form-item>
</el-form-item>
</el-form>
</el-col>
</el-row>
<!-- <el-row :gutter="24">
<el-col :span="18">
<div class="my-upload">
<el-upload
multiple
ref="upload"
action=""
accept=".pdf"
:before-remove="handleBeforeRemove"
:file-list="fileList"></el-upload>
</div>
</el-col>
</el-row> -->
</div>
</keep-alive>
</template>
<script>
import api from '@/api/index'
import editor from '@/views/news/editor.vue'
export default {
components: {
editor
},
name: 'Editor',
computed: {
widthStyle: function () {
// console.log(document.body.clientWidth)
var result = ''
var screenWidth = document.body.clientWidth
if (screenWidth <= 1280) {
result = '100%'
} else if (screenWidth > 1280 && screenWidth <= 1440) {
result = '90%'
} else {
result = '80%'
}
return {
width: result
}
}
},
data: function () {
return {
screenWidth: document.body.clientWidth,
userDefined: '',
enclosureIDs: [],
loadSelectData: (date = this.date) => {
return this.$http
.get(api.NewsType, {
params: { 'select': true }
})
.then(res => {
// this.SelectOptions = res.result.data
// var result = []currentPage
// res.result.data.forEach((item) => {
// result.push({ 'id': item.tid, 'name': item.news_type })
// }
// )
// console.log(result)
this.SelectOptions = res.result.data
})
},
bg: {},
plan: {},
content: {},
contentHtml: '',
SelectOptions: [],
select_value: '',
value: '<h1>123</h1>',
title: '釋出訊息',
isAble: false,
form: {
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: '',
SelectOptions: []
},
quill: null
}
},
methods: {
change (data) {
this.contentHtml = data[0]
this.enclosureIDs = data[1]
},
onSubmit () {
this.isAble = true
// console.log(this.$refs.childMethod.getContentData())
this.$http
.post(api.NewsDetail, {
params: { type_id: this.select_value, title: this.title, content: this.contentHtml, enclosure_ids: this.enclosureIDs }
})
.then(res => {
this.isAble = false
this.$refs.son.fileList = []
this.$refs.son.enclosureIDs = []
this.SelectOptions = res.result.data
this.$router.push({
path: '/news/'
})
})
},
selectChanged (value) {}
},
created: function () {},
mounted () {
this.loadSelectData()
},
activated () {
this.title = ''
this.content = {}
this.contentHtml = ''
this.loadSelectData()
}
}
</script>
<style>
.ql-editor {
min-height:400px; height:auto!important; height:400px;
}
</style>
NewsUpdate.vue
<template>
<keep-alive>
<el-row :gutter="24">
<el-col :span="18">
<el-form label-width="80px">
<el-form-item label>
<h1 style="font-size:30px">修改</h1>
</el-form-item>
<el-form-item label="標題">
<el-input placeholder="標題" v-model="title"></el-input>
</el-form-item>
<el-form-item label="內容">
<editor @contentData="change($event)" ref="son"></editor>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit" >確認修改</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</keep-alive>
</template>
<script>
import api from '@/api/index'
import editor from '@/views/news/editor.vue'
export default {
components: {
editor
},
name: 'Editor',
data: function () {
return {
aaa: 'aaa',
loadData: (date = this.date) => {
return this.$http
.get(api.NewsDetail, {
params: { nid: this.$route.query.nid }
})
.then(res => {
var resData = res.result.data[0]
if (resData) {
this.title = resData.title
this.content = resData.content
// this.$refs.son.fileList = [{ name: '空.txt', url: '#' }, { name: 'food2.jpeg', url: '#' }]
// this.quill.root.innerHTML = resData.content
} else {
this.title = ''
this.content = ''
}
})
},
loadEnclosureData: () => {
return this.$http
.get(api.Enclosure, {
params: { nid: this.$route.query.nid }
})
.then(res => {
this.$refs.son.fileList = res
})
},
bg: {},
plan: {},
content: '',
contentHtml: '',
SelectOptions: [],
select_value: '',
value: '<h1>123</h1>',
title: '',
quill: null
}
},
methods: {
change (data) {
this.contentHtml = data
},
onSubmit () {
// console.log(this.$refs.childMethod.getContentData())
this.$http
.patch(api.NewsDetail, {
params: { nid: this.$route.query.nid, title: this.title, content: this.contentHtml }
})
.then(res => {
this.$message({
type: 'success',
message: '修改成功!'
})
this.$router.push({
path: '/news/detail/',
query: {
nid: this.$route.query.nid
}
})
})
}
},
created: function () {
// this.loadData()
},
mounted: function () {
// this.loadData()
// this.loadEnclosureData()
},
activated () {
this.loadData()
this.loadEnclosureData()
}
}
</script>
<style>
.ql-editor {
min-height:400px; height:auto!important; height:400px;
}
</style>
NewsDetail.vue
<template>
<keep-alive>
<div>
<el-row :gutter="24" type="flex" justify="center">
<el-col :span="10" >
<div>
<h3 style="font-size:30px">{{ form.news_type }} - {{ form.title }}</h3>
</div>
</el-col>
</el-row>
<el-row :gutter="24" type="flex" justify="space-between">
<el-col :span="6">
<div>
<h3>{{ form.department }} - {{ form.nickname }}</h3>
</div>
</el-col>
<el-col :offset="8" :span="6">
<div>
<h3>{{ form.date }}</h3>
</div>
</el-col>
</el-row>
<el-row v-show="EnclosureList.length">
<el-col :span="10">
<h3>附件:</h3>
<div v-for="en in EnclosureList" :key="en.id">
<a :href="en.content" :download="en.name">{{ en.name }}</a>
<!-- <a :href="en.content">{{ en.name }}</a> -->
<a style="margin-left:20px" @click="preView(en.content)">預覽</a>
</div>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="20">
<div class="ql-container ql-snow">
<div class="ql-editor" v-html="form.content"></div>
</div>
<!-- <editor v-html="form.content" style="background-color:white"></editor> -->
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-button type="primary" plain style="margin-bottom:20px" @click="updateNews" v-show="showUpdateBtn">修改</el-button>
<el-button type="danger" plain style="margin-bottom:20px" @click="deleteNews" v-show="showDeleteBtn">刪除</el-button>
</el-col>
</el-row>
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<span>確定要刪除該訊息麼 ?</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmDelete">確 定</el-button>
</span>
</el-dialog>
</div>
</keep-alive>
</template>
<script>
import api from '@/api/index'
import editor from '@/views/news/editor.vue'
export default {
components: {
editor
},
name: 'Detail',
data: function () {
return {
dialogVisible: false,
showDeleteBtn: false,
showUpdateBtn: false,
EnclosureList: [],
content: {},
loadData: (date = this.date) => {
return this.$http
.get(api.NewsDetail, {
params: { nid: this.$route.query.nid, tid: this.$route.query.tid }
})
.then(res => {
var resData = res.result.data[0]
if (resData) {
this.form.news_id = resData.id
this.form.news_type = resData.news_type
this.form.department = resData.department
this.form.nickname = resData.nickname
this.form.date = resData.date
this.form.title = resData.title
this.form.content = resData.content
if (resData.deleteable) {
this.showDeleteBtn = true
}
if (resData.updateable) {
this.showUpdateBtn = true
}
} else {
this.form.news_id = ''
this.form.news_type = ''
this.form.department = ''
this.form.nickname = ''
this.form.date = ''
this.form.title = ''
this.form.content = ''
}
})
},
loadEnclosureData: () => {
return this.$http
.get(api.Enclosure, {
params: { nid: this.$route.query.nid }
})
.then(res => {
this.EnclosureList = res
})
},
form: {
news_id: '',
news_type: '',
department: '',
nickname: '',
date: '',
title: '',
content: {}
}
}
},
methods: {
preView (content) {
var string = content
var iframe = "<iframe width='100%' height='100%' src='" + string + "'></iframe>"
var x = window.open()
x.document.open()
x.document.write(iframe)
x.document.close()
},
confirmDelete () {
this.$http
.delete(api.NewsDetail, {
params: { nid: this.$route.query.nid }
})
.then(res => {
this.$message({
type: 'success',
message: '刪除成功!'
})
this.$router.push({
path: '/news/',
query: {}
})
})
this.dialogVisible = false
},
handleClose (done) {
done()
},
onSubmit () {},
updateNews () {
this.$router.push({
path: '/news/update/',
query: { nid: this.$route.query.nid }
})
},
deleteNews () {
this.dialogVisible = true
}
},
created: function () {},
mounted: function () {
this.loadData()
this.loadEnclosureData()
},
activated () {
this.showUpdateBtn = false
this.showDeleteBtn = false
this.loadData()
this.loadEnclosureData()
}
}
</script>
<style>
.ql-editor {
min-height:400px; height:auto!important; height:400px;
}
.el-row {
margin-bottom: 20px;
}
.el-col {
border-radius: 4px;
}
.bg-purple-dark {
background: #99a9bf;
}
.bg-purple {
background: #d3dce6;
}
.bg-purple-light {
background: #e5e9f2;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
.row-bg {
padding: 10px 0;
background-color: #f9fafc;
}
</style>