在我上一个 WordPress 博客中写过,后来这个服务器拿去做 Springboot 测试之后数据库就炸了,然后也没备份,文章就全没了。所以这是一期补档!
这是 国家电网 下子公司委派开发的 扬州市充电桩后台管理系统 开发任务。本人主要负责其中的 CMS 模块开发。 你问我什么是 CMS ?那我一定会怀疑你没用过百度。
鲁迅说过:要想做好项目必先研究项目结构
我们在学前班学习 Vue 时,鱿鱼须(不是) 老师就教过我们如何使用 Vue 的 组件通讯 ,这里就不在赘述其原理。
我们将使用 WangEditor v4 进行架构(小广告: WangEditor v4-lite 是我基于 WangEditor v4 重新开发的一款更精致的富文本编辑器,你可以在 👉🏻这里 了解到它)。
防 xss 注入策略,我们这里使用 URI code 进行转码并将所有内容移动至 ""
中来规避不必要的 html 标签的出现。
// 这个是子组件向父组件传值的组件
const Editor = Vue.extend({
template: `
<div id="div_mount" style="width: 80%;">
<p>在此处输入文本...</p>
</div>`,
mounted() {
const E = window.wangEditor
const editor = new E('#div_mount')
editor.config.menus = [
'bold',
'head',
'link',
'italic',
'underline',
'image'
]
editor.config.uploadImgShowBase64 = true
editor.create()
}
})
// 同样的,父组件向子组件传值也用一样的方法构造
var vue = new Vue({
el: "#app",
components: {
Editor,
Update
},
data: function() {
return {
editor_main:'',
// 查询字段
title:'',
// 表格数据
tableData: [],
// 勾选
multipleSelection: [],
// 分页相关
totalSize: 0,
pageIndex: 1,
pageSize: 15,
// 门户信息类型
allInfoType: [],
// 门户信息来源
allInfoSource: [],
// 表单
insertForm: { // 插入
id:'',
title:'',
keyWords:'',
content:'', // 对象内容数组
type:'',
picUrl:'',
attachmentUrl:'',
crtDate:'',
infoSource:'',
externalLinks:''
},
updateForm: { // 更新
id:'',
title:'',
keyWords:'',
content: '', // 更新内容
type:'',
picUrl:'',
attachmentUrl:'',
crtDate:'',
infoSource:'',
externalLinks:''
},
ruleOne: {
title: [{
required: true,
message: '请输入',
trigger: 'blur'
}],
keyWords: [{
required: true,
message: '请输入',
trigger: 'blur'
}],
content: [{
}],
type: [{
required: true,
message: '请输入',
trigger: 'blur'
}],
picUrl: [{
}],
attachmentUrl: [{
}],
infoSource: [{
required: true,
message: '请输入',
trigger: 'blur'
}]
},
formLabelWidth: '120px',
// 对话框
dialogAddVisible: false,
dialogModityVisible: false,
// 上传相关
uploadFileUrl: 'portalInfo/upload',
uploadParamPic: {
subPath: 'portalPic'
},
uploadParamAtta: {
subPath: 'portalAttachment'
}
}
},
mounted() {
this.refresh();
},
methods: {
replaceM:function(input){
return input.replace(/</g, '<').replace(/>/g, '>').replace('alt','src');
},
search: function() {...},
refresh: function() {...},
adjustTableTransDic: function(theTableData) {...},
transInfoType: function(code) {...},
transInfoSource: function(code) {...},
// 列选中
rowClick: function(row, event, column) {...},
toggleSelection: function(rows) {...},
handleSelectionChange: function(val) {...},
// 分页
handleSizeChange: function(val) {...},
handleCurrentChange: function(val) {...},
rowModify: function(rowItem) {...},
addClose: function(done) {...},
modityClose: function(done) {...},
deleteBatch: function() {...},
updateHandle: function(formName) { // 更新方法
var that = this;
this.updateForm.content = document.getElementById('div_mount2').firstChild.nextSibling.firstChild.innerHTML;
this.$refs[formName].validate((valid) => {
if (valid) {
$.post("portalInfo/update", this.updateForm, function(result) {
if (result > 0) {
that.$message({
showClose: true,
message: '修改成功!',
});
} else {
that.$message({
showClose: true,
message: '修改失败!',
});
}
that.closeUpdatePop();
});
}
});
},
insertHandle: function(formName) { // 插入方法
var that = this;
this.insertForm.content = document.getElementById('div_mount').firstChild.nextSibling.firstChild.innerHTML;
this.$refs[formName].validate((valid) => {
if (valid) {
$.post("portalInfo/save", this.insertForm, function(result) {
if(result ==3){
that.$message({
message: '添加失败,机构代码已存在!',
});
}else if (result > 0) {
that.$message({
showClose: true,
message: '添加成功!',
});
}else {
that.$message({
showClose: true,
message: '添加失败!',
});
}
that.closeInsertPop();
});
}
});
},
closeInsertPop: function() {...},
closeUpdatePop: function() {...},
// 上传成功后的回调
uploadSuccessInsertPic: function(response, file, fileList) {...},
uploadSuccessInsertAtta: function(response, file, fileList) {...},
uploadSuccessUpdatePic: function(response, file, fileList) {...},
uploadSuccessUpdateAtta: function(response, file, fileList) {...},
// 上传错误
uploadError: function(response, file, fileList) {...},
// 清除上传
clearUpdatePic: function() {...},
clearUpdateFile: function() {...},
clearInsertPic: function() {...},
clearInsertFile: function() {...}
}
});
现在我们来分析其中的核心方法:
该方法在提交表单时用来替换所有的 <>
字符来防范 xss 注入。
该方法旨在更新公告内容,很简单,不赘述,看代码就行。
该方法旨在插入行的公告,很简单,不赘述,看代码就行。
这里的 insertHandle() 与 updateHandle() 方法使用的是 dom 节点操作法来获取内容 document.get......Child.innerHtml
随后将内容传递给 Vue 的数据 this.inserForm.content
再通过 elmentUI 的携带数据上传 v-model
双向绑定的功能提交至后端处理。
<el-form-item label="内容" prop="content">
<el-row>
<el-col :span="24">
<Editor></Editor>
</el-col>
</el-row>
</el-form-item>
这里,我们使用父子组件传值来处理。先看源码:
<!-- 修改弹窗 -->
<el-dialog v-if="dialogModityVisible" title="修改门户信息" :visible.sync="dialogModityVisible" :before-close="closeUpdatePop" :close-on-click-modal="false" width="50%">
<el-form :model="updateForm" ref="updateForm" :rules="ruleOne" :label-width="formLabelWidth">
<el-form-item label="信息标题" prop="title">
<el-input v-model="updateForm.title" auto-complete="off" class="width-form-input"></el-input>
</el-form-item>
<el-form-item label="关键字" prop="keyWords">
<el-input v-model="updateForm.keyWords" auto-complete="off" class="width-form-input"></el-input>
</el-form-item>
<el-form-item label="内容" prop="content">
<!-- 获取数据源 -->
<Update :demo="updateForm.content" v-html="replaceM(updateForm.content)"></Update>
<!-- <el-input class="width-form-input" type="textarea" :rows="10" v-model="updateForm.content" auto-complete="off"> -->
<!-- </el-input> -->
</el-form-item>
<el-form-item label="信息类型" prop="type">
<el-select v-model="updateForm.type" placeholder="请选择" :filterable="true" class="width-form-input">
<el-option v-for="item in allInfoType" :label="item.NAME" :key="item.CODE"
:value="item.CODE">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="信息来源" prop="infoSource">
<el-select v-model="updateForm.infoSource" placeholder="请选择" :filterable="true" class="width-form-input">
<el-option v-for="item in allInfoSource" :label="item.NAME" :key="item.CODE"
:value="item.CODE">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="外部链接" prop="externalLinks">
<el-input v-model="updateForm.externalLinks" auto-complete="off" class="width-form-input"></el-input>
</el-form-item>
<el-form-item label="图片地址" prop="picUrl">
<el-row>
<el-col :span="11" style="margin-right: 5px">
<el-input v-model="updateForm.picUrl" auto-complete="off" readonly="readonly"></el-input>
</el-col>
<el-col :span="4" style="margin-right: 5px">
<el-upload ref="uploadElPicUpdate" :action="uploadFileUrl" :data="uploadParamPic" :limit="1"
:auto-upload="true" :show-file-list="false" :on-error="uploadError"
:on-success="uploadSuccessUpdatePic">
<el-button size="small" type="primary" class="el-icon-upload"
style="width: 100%; height:40px;">点击上传</el-button>
</el-upload>
</el-col>
<el-col :span="4">
<el-button size="small" type="warning" style="width: 100%; height:40px;"
@click="clearUpdatePic">清除</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="附件" prop="attachmentUrl">
<el-row>
<el-col :span="11" style="margin-right: 5px">
<el-input v-model="updateForm.attachmentUrl" auto-complete="off" readonly="readonly">
</el-input>
</el-col>
<el-col :span="4" style="margin-right: 5px">
<el-upload ref="uploadElAttaUpdate" :action="uploadFileUrl" :data="uploadParamAtta"
:limit="1" :auto-upload="true" :show-file-list="false" :on-error="uploadError"
:on-success="uploadSuccessUpdateAtta">
<el-button size="small" type="primary" class="el-icon-upload"
style="width: 100%; height:40px;">点击上传</el-button>
</el-upload>
</el-col>
<el-col :span="4">
<el-button size="small" type="warning" style="width: 100%; height:40px;"
@click="clearUpdateFile">清除</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="创建时间" prop="crtDate">
<el-input :disabled="true" v-model="updateForm.crtDate" class="width-form-input"></el-input>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="closeUpdatePop">取消</el-button>
<el-button type="primary" @click="updateHandle('updateForm')">确定</el-button>
</div>
</el-dialog>
你一定注意到 <Update :demo="updateForm.content" v-html="replaceM(updateForm.content)"></Update>
这一行了,这里父子组件传递了 demo
这个数据值。
在执行方法时我们直接拿到了原先的公告内容,在提交时通过 updateHandle()
方法进行了处理。
这里很简单,只讲原理。通过 Spring 提供的 数据库数据获取 接口拿到公告内容后传递给 Vue 原型中的数组。 使用 v-for 与 :key 属性罗列出所有的公告内容即可。
这是一个非常简单的项目,主要使用了 Vue 的几个知识点。需要突破的难点为构造一个 CMS 编辑器并注入到 Vue 的组件中。