侧边栏壁纸
博主头像
Dioxide-CN博主等级

茶边话旧,看几许星迢露冕,从淮海南来。

  • 累计撰写 50 篇文章
  • 累计创建 49 个标签
  • 累计收到 21 条评论

目 录CONTENT

文章目录

后端开发日常:国家电网CMS系统

Dioxide-CN
2022-05-03 / 0 评论 / 6 点赞 / 195 阅读 / 10,230 字
温馨提示:
本文最后更新于 2022-06-21,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Vue开发日常:开发国家电网CMS系统

引言

在我上一个 WordPress 博客中写过,后来这个服务器拿去做 Springboot 测试之后数据库就炸了,然后也没备份,文章就全没了。所以这是一期补档!

项目介绍

这是 国家电网 下子公司委派开发的 扬州市充电桩后台管理系统 开发任务。本人主要负责其中的 CMS 模块开发。
你问我什么是 CMS ?那我一定会怀疑你没用过百度。

项目分析

鲁迅说过:要想做好项目必先研究项目结构

  • 后端:使用 SSM Tomcat8.5.73
  • 后台:使用 Vue2 ElementUI Vendor
  • 数据库:MySQL5.7

image-1651836212296

  • 目标:在公告管理系统中重新开发 CMS 富文本系统并能在前端中同步显示。需要考虑 xss 防注入的安全问题。

如何实现

我们在学前班学习 Vue 时,鱿鱼须(不是) 老师就教过我们如何使用 Vue 的 组件通讯 ,这里就不在赘述其原理。
我们将使用 WangEditor v4 进行架构(小广告: WangEditor v4-lite 是我基于 WangEditor v4 重新开发的一款更精致的富文本编辑器,你可以在 👉🏻这里 了解到它)。
防 xss 注入策略,我们这里使用 URI code 进行转码并将所有内容移动至 "" 中来规避不必要的 html 标签的出现。

心动不如行动

  1. 实例化我们的 WangEditor 组件,通过 Vue 继承后我们开始处理其中的逻辑:
// 这个是子组件向父组件传值的组件
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()
    }
})
// 同样的,父组件向子组件传值也用一样的方法构造
  1. 这样我们就构造好了一个 we 富文本编辑器,我们将它注入到 Vue 原型中,并构造他的逻辑:
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(/&lt;/g, '<').replace(/&gt;/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() {...}
    }
});

现在我们来分析其中的核心方法:

replaceM()

该方法在提交表单时用来替换所有的 <> 字符来防范 xss 注入。

updateHandle()

该方法旨在更新公告内容,很简单,不赘述,看代码就行。

insertHandle()

该方法旨在插入行的公告,很简单,不赘述,看代码就行。

通讯传值

这里的 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 的组件中。

6

评论区