在后台管理中,当有图文混排等各种文章(比如新闻、小说类)需要使用富文本编辑器。这次我使用了一下tinyMCE编辑器。这款编辑器是免费开源的。
提交成功效果
Download everything you need for production usage (including a jQuery integration plugin) for free. TinyMCE is open source and licensed under LGPL 2.1.
LGPL2.1可以让我们放心的使用了。
首先在https://www.tiny.cloud/download/self-hosted/ 下载tinyMCE
下载哪个都可以
安装很简单,只要在页面里引用就可以了。
<script language="JavaScript" type="text/javascript" src="../RichText/tinymce/tinymce.js"></script> <script language="JavaScript" type="text/javascript" src="../RichText/tinymce/jquery.tinymce.min.js"></script>
当然,还需要init,这里给出一个设置的代码例子
<script language="JavaScript" type="text/javascript"> var tinymceEditor; tinymce.init({ selector: '#NContent', auto_focus: "Content", height: 220, language: "zh_CN", theme: "modern", add_unload_trigger: false, image_advtab: true, automatic_uploads: false, plugins: [ "advlist autolink lists link image imagetools charmap preview", "searchreplace visualblocks fullscreen", "insertdatetime media table contextmenu paste", "emoticons textcolor" ], toolbar1: "undo redo | styleselect | fontselect | fontsizeselect | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent", toolbar2: "forecolor backcolor table emoticons link image imagetools media | fullscreen preview | ", //TinyMCE 会将所有的 font 元素转换成 span 元素 convert_fonts_to_spans: true, //换行符会被转换成 br 元素 convert_newlines_to_brs: false, //在换行处 TinyMCE 会用 BR 元素而不是插入段落 force_br_newlines: false, //当返回或进入 Mozilla/Firefox 时,这个选项可以打开/关闭段落的建立 force_p_newlines: false, //这个选项控制是否将换行符从输出的 HTML 中去除。选项默认打开,因为许多服务端系统将换行转换成 <br />,因为文本是在无格式的 textarea 中输入的。使用这个选项可以让所有内容在同一行。 remove_linebreaks: false, //不能把这个设置去掉,不然图片路径会出错 relative_urls: false, //不允许拖动大小 resize: true, font_formats: "宋体=宋体;黑体=黑体;仿宋=仿宋;楷体=楷体;隶书=隶书;幼圆=幼圆;Arial=arial,helvetica,sans-serif;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Tahoma=tahoma,arial,helvetica,sans-serif;Times New Roman=times new roman,times;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats", fontsize_formats: "8pt 10pt 12pt 14pt 18pt 24pt 36pt", //////////////自己控制图片上传begin///////////////////////////////////////// images_upload_handler: function(blobInfo, success, failure){ var xhr, formData; // var stuId = ${user.userId}; // var maxLogId = ${maxLogId}; var myId = "joel" xhr = new XMLHttpRequest(); xhr.withCredentials = false; xhr.open("POST", "../upload2/?myid="+myId); formData = new FormData(); formData.append("uploadfile", blobInfo.blob()); xhr.onload = function(e){ var json; if (xhr.status != 200) { failure('HTTP Error: ' + xhr.status); return; } json = JSON.parse(this.responseText); if (!json || typeof json.location != 'string') { failure('Invalid JSON: ' + xhr.responseText); return; } success(json.location); }; xhr.send(formData); } //////////////自己控制图片上传end///////////////////////////////////////// }); tinymce.mceImage </script>
这个上传的前端代码,实现了一个文件的上传,同时还上传了一个字符串参数myid。(项目中你可以用这个参数做一些上传用户的判断之类的。)
代码中需要注意的部分
tinymce.init({
selector: '#NContent',
auto_focus: "NContent",
这里的NContent是对应界面模板中的 textarea 组件id
xhr.open("POST", "../upload2/?myid="+myId);
formData = new FormData();
formData.append("uploadfile", blobInfo.blob());
这里面的 upload2 是接收数据的路径,uploadfile是传递文件的组件名称name,都是需要在服务端对应的。
if (!json || typeof json.location != 'string') {
failure('Invalid JSON: ' + xhr.responseText);
return;
}
这段代码中 json.location 中的 location 是服务端接收到文件后,返回给客户端的json串中的一个键。
html模板完整代码
{{define "news"}}<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Joel News</title> <script language="JavaScript" type="text/javascript" src="../RichText/tinymce/tinymce.js"></script> <script language="JavaScript" type="text/javascript" src="../RichText/tinymce/jquery.tinymce.min.js"></script> <script language="JavaScript" type="text/javascript"> var tinymceEditor; tinymce.init({ selector: '#NContent', auto_focus: "NContent", height: 220, language: "zh_CN", theme: "modern", add_unload_trigger: false, image_advtab: true, automatic_uploads: false, plugins: [ "advlist autolink lists link image imagetools charmap preview", "searchreplace visualblocks fullscreen", "insertdatetime media table contextmenu paste", "emoticons textcolor" ], toolbar1: "undo redo | styleselect | fontselect | fontsizeselect | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent", toolbar2: "forecolor backcolor table emoticons link image imagetools media | fullscreen preview | ", //TinyMCE 会将所有的 font 元素转换成 span 元素 convert_fonts_to_spans: true, //换行符会被转换成 br 元素 convert_newlines_to_brs: false, //在换行处 TinyMCE 会用 BR 元素而不是插入段落 force_br_newlines: false, //当返回或进入 Mozilla/Firefox 时,这个选项可以打开/关闭段落的建立 force_p_newlines: false, //这个选项控制是否将换行符从输出的 HTML 中去除。选项默认打开,因为许多服务端系统将换行转换成 <br />,因为文本是在无格式的 textarea 中输入的。使用这个选项可以让所有内容在同一行。 remove_linebreaks: false, //不能把这个设置去掉,不然图片路径会出错 relative_urls: false, //不允许拖动大小 resize: true, font_formats: "宋体=宋体;黑体=黑体;仿宋=仿宋;楷体=楷体;隶书=隶书;幼圆=幼圆;Arial=arial,helvetica,sans-serif;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Tahoma=tahoma,arial,helvetica,sans-serif;Times New Roman=times new roman,times;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats", fontsize_formats: "8pt 10pt 12pt 14pt 18pt 24pt 36pt", //////////////自己控制图片上传begin///////////////////////////////////////// images_upload_handler: function(blobInfo, success, failure){ var xhr, formData; // var stuId = ${user.userId}; // var maxLogId = ${maxLogId}; var myId = "joel" xhr = new XMLHttpRequest(); xhr.withCredentials = false; xhr.open("POST", "../upload2/?myid="+myId); formData = new FormData(); formData.append("uploadfile", blobInfo.blob()); xhr.onload = function(e){ var json; if (xhr.status != 200) { failure('HTTP Error: ' + xhr.status); return; } json = JSON.parse(this.responseText); if (!json || typeof json.location != 'string') { failure('Invalid JSON: ' + xhr.responseText); return; } success(json.location); }; xhr.send(formData); } //////////////自己控制图片上传end///////////////////////////////////////// }); tinymce.mceImage </script></head><body>News<hr color="#e8e8e8">标题:{{.NTitle}} 作者:{{.NAuthor}} 发布时间:{{.NPublish}}<br>内容:{{.NContent }}<br>附件:{{.NAttachment}} 关键字:{{.NKeyword}} 标签:{{.NTab}} 分类:{{.NClass}}<hr color="#e3e3e3"><form method="post" action="" enctype="multipart/form-data"> <table> <tr> <td>标题:</td> <td><input id="NTitle" name="NTitle" value={{.NTitle}}></td> <td>作者:</td> <td><input id="NAuthor" name="NAuthor" value={{.NAuthor}}></td> <td>发布时间:</td> <td><input id="NPublish" name="NPublish" value=""></td> </tr> <tr> <td>内容:</td> <td colspan="5"><textarea id="NContent" name="NContent" rows="5" cols="80" class="editor">{{.NContent }}</textarea></td> </tr> <tr> <td>附件:</td> <td><input id="NAttachment" name="NAttachment" value=""></td> <td>关键字:</td> <td><input id="NKeyword" name="NKeyword" value=""></td> <td>标签:</td> <td><input id="NTab" name="NTab" value=""></td> <td>分类:</td> <td><input id="NClass" name="NClass" value=""></td> </tr> <tr> <td></td> <td colspan="5"><input id="NSubmit" type="submit" value="提交"/></td> </tr> </table></form></body></html>{{end}}
这个模板命名为 news
下面来看golang代码。
此代码借用前面章节使用过的代码示例,稍作修改。
代码大体分两个部分,一个新闻的模板绑定,一个文件的服务端接收。
新闻代码
type News struct { NTitle string //标题 NAuthor string //作者 NPublish string //发布时间 NContent template.HTML //内容 NAttachment string //附件 NKeyword string //关键字 NTab string //标签 NClass string //分类}func processNewsHandler(writer http.ResponseWriter, request *http.Request) { var RNTitle = request.FormValue("NTitle") var RNAuthor = request.FormValue("NAuthor") var RNPublish = request.FormValue("NPublish") var RNContent = request.FormValue("NContent") var RNAttachment = request.FormValue("NAttachment") var RNKeyword = request.FormValue("NKeyword") var RNTab = request.FormValue("NTab") var RNClass = request.FormValue("NClass") myNews := News{} myNews.NTitle = RNTitle myNews.NAuthor = RNAuthor myNews.NPublish = RNPublish myNews.NContent = template.HTML(RNContent) myNews.NAttachment = RNAttachment myNews.NKeyword = RNKeyword myNews.NTab = RNTab myNews.NClass = RNClass t, _ := template.ParseFiles("./JoelTempWeb/tmplNews2.html") t.ExecuteTemplate(writer, "news", myNews) }
文件接收代码
//单页上传,无模板文件,只有模板代码func index(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte(tpl)) }const tpl = `<html> <head> <title>上传文件</title> </head> <body> <form enctype="multipart/form-data" action="/upload2/" method="post"> <input type="file" name="uploadfile"> <input type="hidden" name="token" value="{...{.}...}"> <input type="submit" value="upload"> </form> </body> </html> ` func upload(writer http.ResponseWriter, request *http.Request) { request.ParseMultipartForm(32<<20) //接收客户端传来的文件 uploadfile 与客户端保持一致 file, handler, err := request.FormFile("uploadfile") myid := request.FormValue("myid") fmt.Println(myid) if err != nil{ fmt.Println(err) return } defer file.Close() //上传的文件保存在ppp路径下 ext := path.Ext(handler.Filename) //获取文件后缀 fileNewName := string(time.Now().Format("20060102150405"))+strconv.Itoa(time.Now().Nanosecond())+ext f, err := os.OpenFile("./ppp/"+fileNewName, os.O_WRONLY|os.O_CREATE, 0666) if err != nil{ fmt.Println(err) return } defer f.Close() io.Copy(f, file) back := "{\"error\":false,\"location\":\"../cdn/image/"+fileNewName+"\"}" fmt.Fprintln(writer, string(back)) //location33 := "../cdn/image/"+fileNewName //back := make(map[string]interface{}) //back["error"] = false //back["location"] = location33 //result, _:= json.Marshal(back) //fmt.Fprintln(writer, string(result))}
接收代码里,包含了一段内嵌的页面提交代码。用来测试上传功能。就是这部分
//单页上传,无模板文件,只有模板代码 func index(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte(tpl)) } const tpl = `<html><head><title>上传文件</title></head><body><form enctype="multipart/form-data" action="/upload2/" method="post"><input type="file" name="uploadfile"><input type="hidden" name="token" value="{...{.}...}"><input type="submit" value="upload"></form></body></html>`
如果不必要,可是删除。
main里的代码部分
http.HandleFunc("/news/", processNewsHandler) http.HandleFunc("/upload2/", upload) http.HandleFunc("/index/", index) http.Handle("/cdn/image/", http.StripPrefix("/cdn/image/", http.FileServer(http.Dir("ppp")))) http.Handle("/RichText/", http.StripPrefix("/RichText/", http.FileServer(http.Dir("JoelTempWeb/JoelRichText")))) //富文本编辑器server := http.Server{ Addr: ":8090", } server.ListenAndServe()
在接收文件的代码中,返回给客户端值给出了2种写法
back := "{\"error\":false,\"location\":\"../cdn/image/"+fileNewName+"\"}" fmt.Fprintln(writer, string(back)) //location33 := "../cdn/image/"+fileNewName //back := make(map[string]interface{}) //back["error"] = false //back["location"] = location33 //result, _:= json.Marshal(back) //fmt.Fprintln(writer, string(result))
看你喜欢那种。
准备上传图片
选择图片
自动插入图片服务器路径
插入编辑器
提交成功
作者:厚土火烟
链接:https://www.jianshu.com/p/808cc139a99e