uniapp 富文本编辑器

1,149 阅读1分钟
<!-- 
基于 uni editor组件 开发富文本组件
组件功能:简易版富文本(文字编辑、图片编辑)
创建时间:2021/11/30
创建作者:刘俊雄
-->
<template>
	<view class="liu-editor">
		
		<!-- 工具栏 -->
		<view class="scroll">
			<view class="toolsBar-view">
				<view class="toolsBar-item" :class="{active:formats[item.name]}" @click="toolsBarItemClick(item)" v-for="(item,index) in toolbarList" :key="index">
					<image style="width: 30rpx;height: 30rpx;" :src="item.icon" mode=""></image>
				</view>
			</view>
		</view>
		
		<!-- 编辑器 -->
		<editor
			id="editor" 
			:show-img-resize="true" 
			:show-img-size="true" 
			:show-img-toolbar="true" 
			:placeholder="placeholder" 
			@ready="onEditorReady"
			@statuschange="onStatusChange"
		></editor>
		
		<!-- 取色器 -->
		<t-color-picker ref="colorPicker" :color="color" @confirm="colorConfirm" @cancel="colorCancel"></t-color-picker>
		
	</view>
</template>

<script>
	export default {
		name: "liu-editor",

		props: {
			// 提示信息
			placeholder: {
				type: String,
				default: '开始输入...'
			},
			// 加载文字提示
			loadingText: {
				type: String,
				default: '上传中...'
			},
			// 上传图片时是否使用加载
			isLoading: {
				type: Boolean,
				default: true
			},
			// 图片上传路径
			uploadImgUrl: {
				type: String,
				default: ''
			},
			// 上传的配置参数
			uploadConfig:{
				type:Object,
				default:()=>({})
			},
			// 初始化文本信息
			html:{
				type:String,
				default:''
			}
		},

		data() {
			return {
				editorCtx: null, // 富文本上下文api
				formats:{}, // 点前选择的样式集
				
				// 工具栏
				toolbarList:[
					{
						name:'image', // 图片上传
						icon:'/static/editor-img/image.png',
						type:'upload', // 上传类型
					},
					{
						name:'previewFun', // 预览
						icon:'/static/editor-img/preview.png',
						type:'click', // 点击类型
					},
					{
						name:'header', // 文字标题(header)
						icon:'/static/editor-img/header.png',
						type:'select', // 选择类型
						value:[
							{
								name:'一级标题',
								value:'H1'
							},
							{
								name:'二级标题',
								value:'H2'
							},
							{
								name:'三级标题',
								value:'H3'
							},
							{
								name:'四级标题',
								value:'H4'
							},
							{
								name:'五级标题',
								value:'H5'
							},
							{
								name:'六级标题',
								value:'H6'
							}
						]
					},
					{
						name:'bold', // 文字加粗(bold)
						icon:'/static/editor-img/bold.png',
						type:'click', // 点击类型
					},
					{
						name:'italic', // 文字斜体(italic)
						icon:'/static/editor-img/italic.png',
						type:'click', // 点击类型
					},
					{
						name:'underline', // 文字下划线(underline)
						icon:'/static/editor-img/underline.png',
						type:'click', // 点击类型
					},
					{
						name:'strike', // 文字删除线(strike)
						icon:'/static/editor-img/strike.png',
						type:'click', // 点击类型
					},
					{
						name:'indent', // 文字缩进(indent)
						icon:'/static/editor-img/indent.png',
						type:'select', // 选择类型
						value:[
							{
								name:'向右缩进',
								value:'+1',
							},
							{
								name:'向左缩进',
								value:'-1'
							}
						]
					},
					{
						name:'list', // 列表(list)
						icon:'/static/editor-img/list.png',
						type:'select', // 选择类型
						value:[
							{
								name:'有序列表',
								value:'ordered',
							},
							{
								name:'无序列表',
								value:'bullet'
							},
							{
								name:'方框列表',
								value:'check'
							}
						], // 数字 、点、方框
					},
					{
						name:'lineHeight', // 文字行高(lineHeight)
						icon:'/static/editor-img/lineHeight.png',
						type:'select', // 选择类型
						value:[
							{
								name:'1',
								value:'1'
							},
							{
								name:'1.5',
								value:'1.5'
							},
							{
								name:'2',
								value:'2'
							},
							{
								name:'2.5',
								value:'2.5'
							},
							{
								name:'3',
								value:'3'
							}
						],
					},
					{
						name:'fontSize', // 文字大小(fontSize)
						icon:'/static/editor-img/fontSize.png',
						type:'select', // 选择类型
						value:[
							{
								name:'10px',
								value:'10px'
							},
							{
								name:'13px',
								value:'13px'
							},
							{
								name:'16px',
								value:'16px'
							},
							{
								name:'18px',
								value:'18px'
							},
							{
								name:'24px',
								value:'24px'
							},
							{
								name:'32px',
								value:'32px'
							},
							{
								name:'48px',
								value:'48px'
							}
						],
					},
					{
						name:'align', // 文字排版(align)
						icon:'/static/editor-img/align.png',
						type:'select', // 选择类型
						value:[
							{
								name:'左对齐',
								value:'left'
							},
							{
								name:'居中对齐',
								value:'center'
							},
							{
								name:'右对齐',
								value:'right'
							},
							{
								name:'整行对齐',
								value:'justify'
							}
						],
					},
					{
						name:'color', // 文字颜色(color)
						icon:'/static/editor-img/color.png',
						type:'popup', // 弹窗类型
					},
					{
						name:'undoFun', // 撤销
						icon:'/static/editor-img/undo.png',
						type:'click', // 点击类型
					},
					{
						name:'redoFun', // 恢复
						icon:'/static/editor-img/redo.png',
						type:'click', // 点击类型
					},
					{
						name:'margin', // 外边距(margin)
						icon:'/static/editor-img/margin.png',
						type:'select', // 选择类型
						value:[
							{
								name:'四周外边距',
								value:'margin',
							},
							{
								name:'上外边距',
								value:'marginTop',
							},
							{
								name:'下外边距',
								value:'marginBottom',
							},
							{
								name:'左外边距',
								value:'marginLeft',
							},
							{
								name:'右外边距',
								value:'marginRight',
							}
						],
					},
					{
						name:'padding', // 内边距(padding)
						icon:'/static/editor-img/padding.png',
						type:'select', // 选择类型
						value:[
							{
								name:'四周内边距',
								value:'padding',
							},
							{
								name:'上内边距',
								value:'paddingTop',
							},
							{
								name:'下内边距',
								value:'paddingBottom',
							},
							{
								name:'左内边距',
								value:'paddingLeft',
							},
							{
								name:'右内边距',
								value:'paddingRight',
							}
						],
					},
					{
						name:'clearFun', // 清空编辑器内容
						icon:'/static/editor-img/clear.png',
						type:'click', // 选择类型
					}
					
				],
				
				// color
				color: {
					r: 255,
					g: 0,
					b: 0,
					a: 0.6
				},
				
			};
		},
		
		watch:{
			html(n,o){
				n && this.onEditorReady();
			}
		},

		methods: {
			
			// 取色器确定事件
			colorConfirm(e) {
				// console.log(e);
				this.format('color',e.hex);
			},
			// 取色器取消事件
			colorCancel() {
				// console.log('取消');
			},
			
			// 初始化
			onEditorReady() {
				if( this.editorCtx ){
					this.html && this.initEditor()
					return;
				}
				uni.createSelectorQuery().select('#editor').context((res) => {
					if( res.context ){
						this.editorCtx = res.context;
						this.html && this.initEditor()
					}
				}).exec()
			},
			
			// 设置样式时触发
			onStatusChange(e){
				this.formats = e.detail;
			},
			
			// 设置样式
			format(name,value){
				this.editorCtx.format(name,value);
			},

			// 插入图片
			insertImage() {

				uni.chooseImage({
					success: async (res) => {
						// console.log(res);
						// res.tempFilePaths
						if (res.tempFilePaths.length) {
							// console.log(res.tempFilePaths);
							// 加载
							this.isLoading && uni.showLoading({title: this.loadingText,mask:true});
							
							for (let i = 0,ilen=res.tempFilePaths.length; i < ilen; i++) {
								try{
									let [err,img_res] = await this.uploadImageSync(res.tempFilePaths[i]);
									let resData = JSON.parse(img_res.data);
									// console.log(resData);
									if( !resData.error_code ){
										uni.hideLoading();
										this.editorCtx.insertImage({
											src:resData.data.uri,
											alt:'图片',
											extClass:'editor--editor-img'
											// width:'100%',
										})
										this.editorCtx.insertText({text:' '});
										
									}else{
										uni.hideLoading();
										this.toast('图片上传失败');
									}
								}catch(e){
									//TODO handle the exception
									uni.hideLoading();
									this.toast('上传图片发生错误');
								}
								
							}
							// console.log(1122);
						}
					},
					fail: (err) => {
						console.log(err);
					}
				})
			},
			
			// 初始化文本信息
			initEditor(){
				this.editorCtx.setContents({html:this.html});
			},

			// 图片上传
			uploadImageSync(filePath) {
				// 没有上传路径 直接返回 filePath
				if( !this.uploadImgUrl ){return filePath};
				let header = this.uploadConfig.header || {};
				let formData = this.uploadConfig.formData || {};
				// 返回 promise对象
				return uni.uploadFile({
					url: this.uploadImgUrl,
					filePath,
					header:{
						// #ifdef APP-PLUS
						'content-type': 'multipart/form-data',
						// #endif
						...header
					},
					// #ifdef APP-PLUS
					name: 'file',
					// #endif
					formData,
				});
			},
			
			// 轻提示
			toast(title,duration=1000,icon='none'){
				uni.showToast({title,duration,icon});
			},
			
			// 工具栏点击事件
			toolsBarItemClick(item){
				let switchFun = {
					// 点击类型
					'click':()=>{
						let keys = {previewFun:1,undoFun:1,redoFun:1,clearFun:1};
						if(  keys[item.name] ){
							return this[item.name] && this[item.name]();
						}
						this.format(item.name,item.value);
					},
					// 弹窗类型
					'popup':()=>{
						if( this.formats[item.name] ){
							return this.format(item.name,'');
						}
						this.$refs.colorPicker.open();
					},
					// 选择类型
					'select':()=>{
						if( this.formats[item.name] ){
							return this.format(item.name,'');
						}
						let keys = item.value.map(item=>item.name);
						if( item.name === 'margin' || item.name === 'padding' ){
							uni.showActionSheet({
							    itemList: keys,
							    success: resOne=>{
									let name = item.value[resOne.tapIndex].value;
									setTimeout(()=>{
										let list = ['0px','10px','15px','20px','25px','30px'];
										uni.showActionSheet({
										    itemList: list,
										    success: resTwo=>{
												this.format(name,list[resTwo.tapIndex]);
												// console.log(name,list[resTwo.tapIndex]);
										    }
										});
									},100);
									
							    },
							    fail: function (res) {
							        console.log(res.errMsg);
							    }
							});
							return;
						}
						uni.showActionSheet({
						    itemList: keys,
						    success: res=>{
								this.format(item.name,item.value[res.tapIndex].value);
						    },
						    fail: function (res) {
						        console.log(res.errMsg);
						    }
						});
					},
					// 上传类型
					'upload':()=>{
						this.insertImage();
					}
				}
				switchFun[item.type] && switchFun[item.type]();
			},
			
			// 预览
			async previewFun(){
				// this.toast('预览');
				let res = await this.editorGetContents();
				if( res.html ){
					uni.setStorageSync('editorPreviewHtml',res.html);
				}
				uni.navigateTo({url:'/pages/editorPreview/editorPreview'});
			},
			// 撤销
			undoFun(){
				this.toast('撤销');
				this.editorCtx.undo();
			},
			// 清空编辑器内容
			clearFun(){
				uni.showModal({
					content:'确定清空编辑器内容?',
					success: res => {
						if( res.confirm ){
							this.editorCtx.clear();
						}
					}
				})
			},
			
			// 获取编辑器内容
			editorGetContents(cb){
				return new Promise((resolve,reject)=>{
					this.editorCtx.getContents({
						success:res=>{
							resolve(res);
						},
						fail:err=>{
							reject(err);
						}
					})
				});
			},
			
			
		}
	}
</script>

<style>
	.liu-editor {
		width: 100%;
		background-color: #fff;
	}
	
	.toolsBar-view{
		display: flex;
		align-items:center;
		background-color: #F2F2F2;
		padding-left: 20rpx;
		border-bottom: 4rpx solid #e1e1e1;
	}
	.toolsBar-item{
		margin-right:25rpx;
		width: 50rpx;
		height: 50rpx;
		background-color: #fff;
		display: flex;
		align-items: center;
		justify-content: center;
	}
	.toolsBar-item.active{
		background-color: rgba(255,85,0,0.5);
	}

	.scroll{
		width: 100%;
		height: 80rpx;
		overflow: hidden;
		overflow-x: auto;
		display: flex;
	}
	
	#editor {
		padding: 20rpx 20rpx 20rpx;
		background-color: #F2F2F2;
		box-sizing: border-box;
		width: 100%;
		height: 100%;
		font-size: 16px;
		line-height: 1.5;
		overflow: auto;
	}

</style>