需求背景
这里为什么会用到在线编辑功能呢?有这样的一个文件管理系统,实时上传js、css、html、shtml、txt等格式文件及文件夹,但是有时候发现上传的文件内容上有不对的地方,如果按传统的下载文件到本地,打开文件编辑保存,再次上传文件到对应路径这样一套操作下来的话会比较麻烦,耽误时间,而如果可以在线编辑有内容问题的文件的话,就可以省去很多步骤,直接在线修改并保存文件就可以了,是不是很方便,于是就产生了在线编辑的需求。
在线编辑
对于文件在线编辑,如果自行通过普通的html元素来加载并编辑的话,操作难度和代码识别度都很差劲,与其说是编辑代码,不如说是编译一堆字母,完全没有任何代码格式可言,这时就考虑到引用在线编辑插件来实现这一功能,通过网上搜索找到了应用的比较广泛比较稳定的CodeMirror在线编辑插件,CodeMirror插件官网地址:https://codemirror.net/
这里我准备了我已经下载好的codemirror的插件包:https://download.csdn.net/download/csdn565973850/87410467,如果你没有积分又不愿意去官网下载的话可以私聊我,我这里给你提供codemirror插件包。
项目引入
这里首先说在线编辑页面,然后再说后台在线编辑处理的逻辑代码,所有代码仅供提供思路,勿要全盘拿过去复用,毕竟不同的代码架构跳转方式会有不同。
列表页面
在线编辑按钮对应的跳转代码
//在线编辑functioncodeonline(fileName){ vartitle=$("#title").val(); varplatform=$("#platform").val(); varurl=prefix+"/codeOnline?path="+title+"&name="+fileName+"&platform="+platform; $.modal.openTab('在线编辑',url); }
加载页面内容
codeOnline方法下载腾讯云文件并读取文件内容返回到在线编辑页面
/*** 获取待编辑文件内容*/"/codeOnline") ("project:publishCosFiles:codeOnline") (publicStringcodeOnline(HttpServletRequestrequest,ModelMapmmap){ //获取存储路径Stringpath=request.getParameter("path"); mmap.put("path",path); //获取文件名称Stringname=request.getParameter("name"); mmap.put("name",name); //获取平台Stringplatform=request.getParameter("platform"); mmap.put("platform",platform); Stringtext1path=null; try { //临时文件存放目录Stringprofile=ConfigConstant.cosTempPath; //版本库没有 原始文件地址Stringversionkey=path+name; if (Constants.Platform.COS.getValue().equals(Integer.valueOf(platform))) { CosClientUtilcosClientUtil=newCosClientUtil(); try { //下载到服务器临时目录的文件路径及名称text1path=profile+name; //开始下载cosClientUtil.download(versionkey, text1path); } catch (InterruptedExceptione) { e.printStackTrace(); } }else { text1path=path+"/"+name; } //逐行读取文件内容BufferedReaderreader; StringBuffersb=newStringBuffer(); mmap.put("filearea",""); try { reader=newBufferedReader(newInputStreamReader( newFileInputStream(text1path), "utf-8")); while (reader.ready()) { sb.append(reader.readLine()+"\r\n"); } reader.close(); mmap.put("filearea",sb.toString()); } catch (IOExceptione) { e.printStackTrace(); } }catch (Exceptione) { e.printStackTrace(); }finally { if (Constants.Platform.COS.getValue().equals(Integer.valueOf(platform))) { //删除下载的临时文件if (StringUtils.isNotEmpty(text1path)) { booleandelete=newFile(text1path).delete(); } } } returnprefix+"/codeOnline"; }
在线编辑页面
在线编辑页面样式及js代码如下
<htmllang="zh"xmlns:th="http://www.thymeleaf.org"><head><th:blockth:include="include :: header('在线编辑')"/><th:blockth:include="include :: select2-css"/><th:blockth:include="include :: bootstrap-select-css"/><linkrel="stylesheet"th:href="@{/codemirror-5.65.11/theme/darcula.css}"><linkth:href="@{/codemirror-5.65.11/lib/codemirror.css}"rel="stylesheet"/><!--引入js,绑定Vim--><linkrel="stylesheet"th:href="@{/codemirror-5.65.11/addon/dialog/dialog.css}"><!--支持代码折叠--><linkrel="stylesheet"th:href="@{/codemirror-5.65.11/addon/fold/foldgutter.css}"/><!--全屏模式--><linkrel="stylesheet"th:href="@{/codemirror-5.65.11/addon/display/fullscreen.css}"><!--自动补全--><linkrel="stylesheet"th:href="@{/codemirror-5.65.11/addon/hint/show-hint.css}"><style>/*设置编辑框大小*//*.CodeMirror{border:1px solid black;font-size:15px;width:100px;height:100px;}*/</style></head><bodyclass="white-bg"><!--<div class="ibox-content"><div class="form-group"><label class="font-noraml">选择主题:</label><select id="theme" class="form-control" onclick="changeval();"><option value="">--请选择--</option><option value="3024-day">3024-day</option><option value="3024-night">3024-night</option><option value="abbott">abbott</option><option value="abcdef">abcdef</option><option value="ambiance">ambiance</option><option value="ambiance-mobile">ambiance-mobile</option><option value="ayu-dark">ayu-dark</option><option value="ayu-mirage">ayu-mirage</option><option value="base16-dark">base16-dark</option><option value="base16-light">base16-light</option><option value="bespin">bespin</option><option value="blackboard">blackboard</option><option value="cobalt">cobalt</option><option value="colorforth">colorforth</option><option value="darcula">darcula</option><option value="dracula">dracula</option><option value="duotone-dark">duotone-dark</option><option value="duotone-light">duotone-light</option><option value="eclipse">eclipse</option><option value="elegant">elegant</option><option value="erlang-dark">erlang-dark</option><option value="gruvbox-dark">gruvbox-dark</option><option value="hopscotch">hopscotch</option><option value="icecoder">icecoder</option><option value="idea">idea</option><option value="isotope">isotope</option><option value="juejin">juejin</option><option value="lesser-dark">lesser-dark</option><option value="liquibyte">liquibyte</option><option value="lucario">lucario</option><option value="material">material</option><option value="material-darker">material-darker</option><option value="material-ocean">material-ocean</option><option value="material-palenight">material-palenight</option><option value="mbo">mbo</option><option value="mdn-like">mdn-like</option><option value="midnight">midnight</option><option value="monokai">monokai</option><option value="moxer">moxer</option><option value="neat">neat</option><option value="neo">neo</option><option value="night">night</option><option value="nord">nord</option><option value="oceanic-next">oceanic-next</option><option value="panda-syntax">panda-syntax</option><option value="paraiso-dark">paraiso-dark</option><option value="paraiso-light">paraiso-light</option><option value="pastel-on-dark">pastel-on-dark</option><option value="railscasts">railscasts</option><option value="rubyblue">rubyblue</option><option value="seti">seti</option><option value="shadowfox">shadowfox</option><option value="solarized">solarized</option><option value="ssms">ssms</option><option value="the-matrix">the-matrix</option><option value="tomorrow-night-bright">tomorrow-night-bright</option><option value="tomorrow-night-eighties">tomorrow-night-eighties</option><option value="ttcn">ttcn</option><option value="twilight">twilight</option><option value="vibrant-ink">vibrant-ink</option><option value="xq-dark">xq-dark</option><option value="xq-light">xq-light</option><option value="yeti">yeti</option><option value="yonce">yonce</option><option value="zenburn">zenburn</option></select></div></div>--><divclass="container-div ui-layout-center"><divclass="row"><divclass="col-sm-12 my-search-collapse"><divclass="select-list"><divstyle="color:red;"th:if="${platform} == 1">当前文件:[[${path}]][[${name}]]</div><divstyle="color:red;"th:if="${platform} != 1">当前文件:[[${path}]]/[[${name}]]</div></div></div><divclass="col-sm-12 search-collapse"><divclass="ibox-content"><divclass="form-group"><labelstyle="float: left;padding-top: 10px;"><fontcolor="red"style="padding-right: 5px;">*</font></fon>备注: </label><divclass="col-sm-8"><inputid="remark"name="remark"placeholder="请在保存之前填写"class="form-control"/></div><divclass="col-sm-3"style="padding-top: 7px;float: right;"><ahref="#"title="Ctrl+A: selectAll(全选)Ctrl+D: deleteLine(删除整行)Alt+G: jumpToLine(跳转指定行)Ctrl+Z: undo(撤销)Ctrl+Home: goDocStart(光标定位到文档开始)Ctrl+End: goDocEnd(光标定位到文档结束)Ctrl+Up: goLineUp(光标上移一行)Ctrl+Down: goLineDown(光标下移一行)Ctrl+Left: goGroupLeft(光标左移)Ctrl+Right: goGroupRight(光标右移)Alt+Left: goLineStart(光标左移)Alt+Right: goLineEnd(光标右移)Ctrl+Backspace: delGroupBefore(删除光标前一个词)Ctrl+Delete: delGroupAfter(删除光标后一个词)Ctrl+F: find(查找)Ctrl+G: findNext(查找下一个)Ctrl+U: undoSelection(撤销)Shift+Ctrl+U: redoSelection(重做)Alt+U: redoSelection(重做)">快捷键操作<iclass="fa fa-question"></i></a></div></div></div><divstyle="padding-top:10px;"><divclass="col-sm-offset-5 col-sm-10"><buttontype="button"class="btn btn-sm btn-primary"onclick="submitHandler()"><iclass="fa fa-check"></i>保 存</button> <buttontype="button"class="btn btn-sm btn-danger"onclick="closeItem()"><iclass="fa fa-reply-all"></i>关 闭 </button></div></div></div><divid="tableDiv"class="col-sm-12 select-table table-striped"style="height: calc(100% - 140px);overflow: auto;padding-right:15px;"><divstyle="padding-top:10px;padding-right:15px;"><formstyle="margin-top: 20px;margin-left: 20px;"id="form-code-edit"><inputtype="hidden"name="fileName"id="fileName"th:value="${name}"/><inputtype="hidden"name="versionId"id="versionId"th:value="${versionId}"/><inputtype="hidden"name="platform"id="platform"th:value="${platform}"/><inputtype="hidden"name="path"id="path"th:value="${path}"/><textareaid="filearea"name="filearea">[[${filearea}]]</textarea></form></div></div></div></div><divth:include="include::footer"></div><scriptth:src="@{/codemirror-5.65.11/lib/codemirror.js}"></script><!--引入js,sublime--><scriptth:src="@{/codemirror-5.65.11/keymap/sublime.js}"></script><!--光标定位--><scriptth:src="@{/codemirror-5.65.11/addon/search/searchcursor.js}"></script><scriptth:src="@{/codemirror-5.65.11/addon/search/jump-to-line.js}"></script><!--支持代码折叠--><scriptth:src="@{/codemirror-5.65.11/addon/fold/foldcode.js}"></script><scriptth:src="@{/codemirror-5.65.11/addon/fold/foldgutter.js}"></script><scriptth:src="@{/codemirror-5.65.11/addon/fold/brace-fold.js}"></script><scriptth:src="@{/codemirror-5.65.11/addon/fold/comment-fold.js}"></script><!--全屏模式--><scriptth:src="@{/codemirror-5.65.11/addon/display/fullscreen.js}"></script><!--括号匹配--><scriptth:src="@{/codemirror-5.65.11/addon/edit/matchbrackets.js}"></script><!--自动补全--><scriptth:src="@{/codemirror-5.65.11/addon/hint/show-hint.js}"></script><scriptth:src="@{/codemirror-5.65.11/addon/hint/javascript-hint.js}"></script><scriptth:src="@{/codemirror-5.65.11/addon/hint/html-hint.js}"></script><scriptth:src="@{/codemirror-5.65.11/addon/hint/css-hint.js}"></script><!--js高亮--><scriptth:src="@{/codemirror-5.65.11/mode/javascript/javascript.js}"></script><!--选中词高亮--><scriptth:src="@{/codemirror-5.65.11/addon/search/match-highlighter.js}"></script><scripttype="text/javascript">varprefix=ctx+"project/publishCosFiles"; //根据DOM元素的id构造出一个编辑器vareditor=CodeMirror.fromTextArea(document.getElementById("filearea"),{ mode:"text/javascript", //行号lineNumbers:true, //设置主题theme:"darcula", //绑定sublimekeyMap:"sublime", //代码折叠lineWrapping:true, foldGutter: true, gutters:["CodeMirror-linenumbers", "CodeMirror-foldgutter"], //全屏模式fullScreen:true, //括号匹配matchBrackets:true, //智能提示 //ctrl-space唤起智能提示extraKeys:{"Tab":"autocomplete","Ctrl-D":"deleteLine", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown"}, highlightSelectionMatches:true }); functionsubmitHandler() { //该方法得到的结果是经过转义的数据vartoTextArea=editor.getValue(); if (toTextArea!="") { $("#filearea").text(toTextArea); }else { $.modal.alertWarning("文件内容已被人工删除,请注意!"); returnfalse; } varremark=$("#remark").val(); if (remark==""||remark==undefined) { $.modal.alertWarning("请添加备注!"); returnfalse; } //console.log($("#filearea").val());vardata=$('#form-code-edit').serializeArray(); data.push({"name": "remark", "value": remark}); $.operate.saveTab(prefix+"/saveCode", data); } //设置主题functionchangeval() { varval=$("#theme").val(); editor.setOption("theme",val); } /*function myAlert() { $.modal.msg("Ctrl+A: selectAll(全选)\n" + "Ctrl+D: deleteLine(删除整行)\n" + "Ctrl+Z: undo(撤销)\n" + "Ctrl+Home: goDocStart(光标定位到文档开始)\n" + "Ctrl+End: goDocEnd(光标定位到文档结束)\n" + "Ctrl+Up: goLineUp(光标上移一行)\n" + "Ctrl+Down: goLineDown(光标下移一行)\n" + "Ctrl+Left: goGroupLeft(光标左移)\n" + "Ctrl+Right: goGroupRight(光标右移)\n" + "Alt+Left: goLineStart(光标左移)\n" + "Alt+Right: goLineEnd(光标右移)\n" + "Ctrl+Backspace: delGroupBefore(删除光标前一个词)\n" + "Ctrl+Delete: delGroupAfter(删除光标后一个词)\n" + "Ctrl+F: find(查找)\n" + "Ctrl+G: findNext(查找下一个)\n" + "Ctrl+U: undoSelection(撤销)\n" + "Shift+Ctrl+U: redoSelection(重做)\n" + "Alt+U: redoSelection(重做)"); }*///获取Codemirror的值//该方法得到的结果是经过转义的数据//editor.getValue();//该方法得到的结果是未经过转义的数据//editor.toTextArea();//editor.getTextArea().value;//如果是通过 JS 进行表单提交,可以在提交的 JS 代码中这样使用://将 Codemirror 的内容赋值给 Textarea//$("#content").text(editor.getValue());</script></body></html>
页面展示效果图如图可以看到
当前页面支持的快捷键主要有以下功能
在线编辑内容保存
最后在说一下在线编辑内容的保存,保存操作这里主要分为两步,首先获取页面提交的字符串内容,然后写入本地临时文件,再将临时文件上传到腾讯云即可,在线编辑上传内容方法如下,但是这里的方法主要是在线编辑相关业务代码,涉及到业务相关的代码已经删除,方便大家理解单纯的在线编辑内容上传
/*** 保存在线编辑内容** @param request* @return*/"project:publishCosFiles:codeOnline") ("/saveCode") (publicAjaxResultsaveCode(HttpServletRequestrequest) { Stringfilearea=request.getParameter("filearea"); StringfileName=request.getParameter("fileName"); Stringpath=request.getParameter("path"); try { //保存文件编辑内容并上传streamService.editUpload(filearea,fileName,path); returnAjaxResult.success("上传成功!"); }catch (Exceptione) { e.printStackTrace(); returnAjaxResult.error("上传失败!"); } }
在线编辑实际保存操作方法
/*** 编辑后执行腾讯云COS上传* @param filearea 文件内容字符串* @param fileName 文件名称*/publicvoideditUpload(Stringfilearea, StringfileName, Stringpath){ Stringkey=null; try { //临时文件存放目录 写入改写后的文件Stringprofile=ConfigConstant.cosTempPath; //临时文件全路径key=profile+fileName; FileWriterwriter=newFileWriter(key); writer.write(filearea); writer.flush(); writer.close(); if("1".equals(platform)){ //腾讯云文件名需要追加具体路径StringuploadFileName=path+fileName; //腾讯云平台CosClientUtilcosClientUtil=newCosClientUtil(); //临时文件写入成功之后进入后续上传文件到腾讯云操作try { //上传本地临时文件到腾讯云cosClientUtil.uploadFile(uploadFileName, key); } catch (Exceptionse) { se.printStackTrace(); } }else { //移动token为现有文件Filefile=newFile(key); FilecurrentFile=newFile( path+"/"+fileName); //删除现有文件IoUtil.getFile(fileName,path).delete(); Files.copy(file.toPath(), currentFile.toPath()); } } catch (Exceptione) { e.printStackTrace(); thrownewBusinessException("编辑保存过程异常",e); }finally { //由于此处文件内容写在临时文件中,所以使用完之后需要删除下载的临时文件if (com.ruoyi.common.utils.StringUtils.isNotEmpty(key)) { booleandelete=newFile(key).delete(); } } }
到此整个在线编辑的前后端数据交互及在线编辑后文件上传已经完成了,欢迎有问题的小伙伴咨询哈。