《笔记》vue.js 与 tinymce 富文本整合

9 篇文章 0 订阅
5 篇文章 0 订阅

前端代码

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
	<meta charset="utf-8" />
	<title th:text="${msg}">TinyMCE</title>
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- CSS only -->
	<link href="/bootstrap-5.1.3/css/bootstrap.css?v=5.1.3" rel="stylesheet">
	<link href="/tinymce/prism.css" rel="stylesheet" />
	<script src="/common/js/axios.min.js"></script>
	<script src="/entity/entity.js"></script>
	<script src="/sip.gl.page-error.js"></script>
	<style>
		#describe { resize: none }
	</style>

</head>

<body style="display: none;" onload="loda()">
	<div class="container">
		<div class="row">
			<form class=" needs-validation">
				<div id="counter" class="row">
					<div class="col">
						<div class="mt-2">
							<label for="title " class="form-label">标题:</label>
							<input v-model="article.title" class="form-control form-control-lg" type="text" placeholder="输入标题" required>
						</div>
					</div> 
 
					<div class="mb-3 mt-3">
						<label for="describe" class="form-label">文章描述:</label>
						<textarea id="describe" v-model="article.articlesDescribe" class="form-control" rows="3" placeholder="文章描述" required></textarea>
					</div>
				</div>
				<div class="mb-3 mt-3">
					<textarea id="editor" name="textarea" required>Hello, World!</textarea>
				</div>
				<div class="col-12">
					<button id="submit" type="submit" class="btn btn-primary d-none">保存文章</button>
				</div>
			</form>

		</div>
	</div>
	<script src="/bootstrap-5.1.3/js/bootstrap.js?v=5.1.3"></script>
	<script src="/tinymce/tinymce.min.js"></script>
	<script src="/common/js/vue.global.js?v=2.6"></script>
	<script src="/tinymce/zh_CN.js"></script>
	<script src="/tinymce/prism.js"></script>
	<script type="text/javascript" th:inline="javascript">
		//定义富文本ID
		const editorId = "editor";
		//获取富文本DOM对象
		const tinyMceId = document.getElementById(editorId);
		//加载完成显示页面
		loda = () => {document.body.style.display = "";}
		succeed = (res) => { console.log(res) }
		error = (res) => { console.log(res) }
		
		const Counter = {
			data() {
				return {
					article: new Article(),
					errorInfo: new PageErrorInfo(),
					attachmentIconsPath:'tinymce/plugins/attachment/icons',//附近上传回显图标路径
					fileurl:"file",
					userinfo:[],
				}
			},
			created() {},
			mounted() {
				this.$nextTick(function () {
					this.formInit();
					this.tinymceInit();
					this.onkeydown();
				})
			},
			methods: {
				//设置tinymce内容
				setContent() {tinymce.editors[editorId].setContent(data);},
				//表单提交验证
				formInit() {
					//获取第一个表单
					let form = document.getElementsByTagName("form")[0];
					form.addEventListener('submit', (event) => {
						//如果表单验证成功,执行保存
						if (form.checkValidity()) { this.save(); }
						//阻止冒泡排序
						event.preventDefault()
						event.stopPropagation()
						//添加警告样式
						form.classList.add('was-validated')
					}, false)
				},
				//提交保存事件
				submit() {document.getElementById("submit").click()/*委托点击*/},
				//文件/附近提交
				fileSubmit(file,succFun,failFun){
					var formData = new FormData();
					formData.append("file", file);
					axios.post(vm.fileurl, formData).then( res => {succFun(res.data.location)}).catch( err => {failFun(err)});
				},
				//执行富文本数据保存
				save() {
					console.log("TODO==========")
					//获取富文本内容对象
					let textarea = tinymce.editors[editorId].getContent();
					//实例化一个表单对象
					const formData = new FormData();
					//添加textarea参数对象
					formData.append('textarea', textarea);
					//实例化一个文章对象
					let article = new Article();
					article.articlesDescribe = textarea;
					//格式化json字符串
					let tojsonstr = JSON.stringify(article)
					
					console.log(tojsonstr)
					//在转换字符串json对象为json对象
					let tojson = JSON.parse(tojsonstr)
					console.log(tojson)
					//实例化一个Map
					let map = new Map();
					//遍历对象Key
					let obj = Object.keys(tojson);
					console.log(obj)
					for (let k of obj) {
						console.log(tojson[k])
					}
					console.log("=================")
					for (let k in obj) {
						console.log(obj[k])
					}
					this.article.content = textarea;
					console.log("TODO==========")
					//执行保存提交
					axios.post('/save', this.article).then(succeed).catch(error);
				},
				//监听键盘操作
				onkeydown() {
					document.onkeydown = (e) => {
						const keyCode = e.keyCode || e.which || e.charCode;
						const ctrlKey = e.ctrlKey;
						//监听ctrl + s 操作
						if (ctrlKey && keyCode == 83) {
							//执行提交
							this.submit();
							//阻止冒泡事件
							e.preventDefault();
							return false;
						}
					}
				},
				//计算百分比
				progressCallback(e) {  return (e.loaded / e.total * 100 | 0) + '%'; },
				//tinymce 富文本配置
				tinymceInit() {
					//配置tinymce
					tinymce.init({
						target: tinyMceId,//选择器
						deprecation_warnings: false,// 禁用 tiny 弃用警告
						language: 'zh_CN',//语言
						placeholder: '请再次输入内容',
						toolbar_sticky: true,//粘性工具栏(或停靠工具栏),在向下滚动网页直到不再可见编辑器时,将工具栏和菜单停靠在屏幕顶部。
						menubar: false,//菜单工具类
						statusbar: true,//开启底部标签
						auto_focus: true,//自动获得焦点 
						height: 650, //编辑器高度
						min_height: 600,
						//插件
						plugins: `autoresize attachment importword indent2em print  preview searchreplace autolink fullscreen  image  link media code codesample table save   toc paste charmap  hr  nonbreaking anchor advlist  lists textpattern help emoticons wordcount  searchreplace imagetools  insertdatetime axupimgs bdmap`,
						//工具栏
						toolbar: `fullscreen preview code codesample insertdatetime hr wordcount blockquote | image axupimgs imagetools emoticons searchreplace lineheight indent2em importword attachment | formatselect fontselect fontsizeselect | forecolor backcolor bold italic underline strikethrough  removeformat|  alignleft aligncenter alignright |  bullist numlist subscript superscript | table media link charmap print bdmap | save undo redo  toc`,
						custom_ui_selector: 'form',//此选项可指定某些元素成为编辑器的一部分,当焦点移动到此选择器匹配的元素上时,不会触发编辑器的blur事件。
						lineheight_formats: "0.5 1 1.1 1.2 1.3 1.4 1.5 2",//行高
						insertdatetime_element: true,//插入日期/时间时会生成HTML5时间元素
						insertdatetime_timeformat: '%Y-%m-%d%H:%M:%S',//插入时间格式
						insertdatetime_formats: ["%Y年%m月%d日 %H点%M分%S", "%H点%M分", "%Y年%m月%d日"],
						images_upload_url: vm.fileurl,//单图片上传路径
						paste_data_images: true,//图片粘贴
						//右键菜到事件,参考工具类工具,部分工具可能无效
						contextmenu: "bold copy preview ",
						contextmenu_never_use_native: true,//在编辑器中屏蔽浏览器本身的右键菜单
						//附件图标地址,附近上传完成后根据格式返回的图标存放路径
						attachment_icons_path: vm.attachmentIconsPath,//附件文件上传图标的路径地址
						paste_preprocess: (plugin, args) => {console.log(args.content);},//粘贴时触发 TODO
						//多文件上传触发
						images_upload_handler: (blobInfo, succFun, failFun) => vm.fileSubmit(blobInfo.blob(),succFun,failFun),
						toolbar_mode: 'wrap',//工具栏的布局
						autosave_ask_before_unload: false, 阻止有内容时浏览器阻塞行为, 默认 true  需引入插件autosave
						fontsize_formats: '12px 14px 16px 18px 24px 36px 48px 56px 72px',//字体大小
						font_formats: `微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif; 宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif`,//字体
						save_enablewhendirty: false,//当内容无变化时禁用保存按钮,当有变化时,保存按钮变为可点击状态。
						save_onsavecallback: editor => vm.submit(),//富文本内部保存触发 ctrl + s 触发
						//附件上传
						attachment_upload_handler: (file, succFun, failFun, progressCallback) => vm.fileSubmit(file,succFun,failFun,progressCallback(vm.progressCallback)),
						importword_handler: (editor, files, next) => {//导入word文件前检查
							//用户取消文件上传检查,如果不检查,用户取消后会报错误
							if (files.length == 0) {return;} 
							var file_name = files[0].name;
							//验证是否是 docx 格式的word文档
							if (file_name.substr(file_name.lastIndexOf(".") + 1) == 'docx') {
								editor.notificationManager.open({
									text: '正在转换中...',
									type: 'info',
									closeButton: false,
								});
								next(files);
							} else {
								editor.notificationManager.open({
									text: '目前仅支持docx文件格式',
									type: 'warning',
								});
							}
						},
						//导入word操作
						importword_filter: (result, insert, message) => {
							console.log("导入word警告信息===》",message)
							insert(result) //回调函数
						}
					});
				}
			}
		}
		let vm = Vue.createApp(Counter).mount('#counter')

	</script>
</body>

</html>

js 代码 

/entity/entity.js

注:单纯的方便自己维护

class PageErrorInfo {
	constructor(msg, url, line, col) {
		this.msg = typeof (msg) == "undefined" ? null : msg;
		this.url = typeof (url) == "undefined" ? null : url;
		this.line = typeof (line) == "undefined" ? null : line;
		this.col = typeof (col) == "undefined" ? null : col;
	}
}

class Article {
	constructor(tags, title, cover, articlesDescribe, content) {
		//this.tags = typeof (tags) == "undefined" ? [] : tags;
		this.title = typeof (title) == "undefined" ? null : title;
		this.cover = typeof (cover) == "undefined" ? null : cover;
		this.articlesDescribe = typeof (articlesDescribe) == "undefined" ? null : articlesDescribe;
		this.content = typeof (content) == "undefined" ? null : content;
	}
}

 /sip.gl.page-error.js

注:该js无意义,项目中不可使用,只做个人项目记录前端异常提交到后台作捕获记录

/**
 * @param {string} msg   错误的具体信息:
 * @param {string} url   错误所在的url:
 * @param {int} line  错误所在的行
 * @param {int} col   错误所在的列
 * @param {string} error 具体的error对象
 * 
 * @explain:js异常代码全局接口
 */
window.onerror = function(msg, url, line, col, error) {
	if(url === ""){
		return false
	}
	const errInfo = new PageErrorInfo(msg, url, line, col);
	axios.post('/err-msg', errInfo);
	
}

后端文件上传代码

注:保存文件接口是上传成功返回的文件实例,在项目项目中使用,需写自己的业务逻辑,此代码做参考作用(dome)

	@ResponseBody
	@PostMapping("file")
	public Object file(@RequestParam(value = "file") MultipartFile file) throws IllegalStateException, IOException {

		JSONObject object = new JSONObject();

		System.out.println(file.getOriginalFilename());
		System.out.println(file.getContentType().equals("image/jpeg"));

		if (file.getOriginalFilename().lastIndexOf(".") != -1) {
			String suffixs = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."))
					.toLowerCase();
			String fileName = System.currentTimeMillis() + suffixs;
			file.transferTo(new File("F://" + fileName));
			object.put("location", fileName);
		}

		if (ObjectUtils.isEmpty(object.get("location"))) {
			if (file.getContentType().equals("image/jpeg")) {
				String suffixs = ".jpg";
				String fileName = System.currentTimeMillis() + suffixs;
				file.transferTo(new File("F://" + fileName));
				object.put("location", fileName);
			}
		}

		/**
		 * jpg=image/jpeg tiff=image/tiff gif=image/gif jfif=image/jpeg png=image/png
		 * tif=image/tiff ico=image/x-icon jpeg=image/jpeg wbmp=image/vnd.wap.wbmp
		 * fax=image/fax net=image/pnetvue jpe=image/jpeg rp=image/vnd.rn-realpix
		 */
		return object;
	}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值