yarn add wangeditor -S
npm i wangeditor
import E from ‘wangeditor’;
<editor ref="editor"
v-model="submitData.content"
:img-sep="editorImgSep"
:req-file-name="reqFileName"
:upload-params="editorUploadParams"
:custom-upload-img="customUploadImg"
v-bind="ruleConfig.content.fieldProps" />
import Editor from '@bus_comp/business/editor/index.vue';
src/components/business/editor/
/**
* @description: 全屏按钮
*/
import E from 'wangeditor';
let { BtnMenu } = E;
export class FullScreen extends BtnMenu {
constructor (editor) {
let $elem = E.$(
`<div class="w-e-menu">
<a class="_editor_btn_fullscreen" data-fullsreen-status="false">
<i class="w-e-icon-fullscreen"></i>
</a>
</div>`
);
super($elem, editor);
}
clickHandler () {
let sirEditor = document.querySelector('#SirEditor');
let classes = sirEditor?.className;
if (!classes) {
sirEditor?.className = 'fullscreen-editor';
} else {
let classArr = classes.split(' ');
if (!classArr.includes('fullscreen-editor')) {
classArr.push('fullscreen-editor');
} else {
let findIndex = classArr.findIndex(item => item === 'fullscreen-editor');
classArr.splice(findIndex, 1);
}
sirEditor?.className = classArr.join(' ');
}
let iel = document.querySelector('#SirEditor' + ' ._editor_btn_fullscreen i');
let ielClasses = iel?.className;
let ielClassesArr = ielClasses?.split(' ');
if (ielClassesArr.includes('w-e-icon-fullscreen')) {
iel?.classList.remove('w-e-icon-fullscreen');
iel?.classList.add('w-e-icon-fullscreen_exit');
} else {
iel?.classList.remove('w-e-icon-fullscreen_exit');
iel?.classList.add('w-e-icon-fullscreen');
}
}
tryChangeActive () {
this.active();
}
}
/**
* @description: 富文本编辑器配置
*/
type MENU_TYPE =
| 'head'
| 'bold'
| 'fontSize'
| 'fontName'
| 'italic'
| 'underline'
| 'strikeThrough'
| 'foreColor'
| 'backColor'
| 'link'
| 'list'
| 'justify'
| 'quote'
| 'emoticon'
| 'image'
| 'table'
| 'video'
| 'code'
| 'undo'
| 'fullscreen'
| 'lineHeight'
| 'indent'
| 'splitLine'
| 'todo'
| 'redo';
interface MenuTipItem {
menuItem: MENU_TYPE; // 菜单的menu
class?: string; // 实际对应的className 样式命名不一样按照menuItem...
tip: string; // title悬浮提示
}
// 这里只为那些悬浮上去没有选择菜单的按钮加提示
export const MENU_TIPS: MenuTipItem[] = [
{
menuItem: 'bold',
tip: _('粗体')
},
{
menuItem: 'italic',
tip: _('斜体')
},
{
menuItem: 'underline',
tip: _('下划线')
},
{
menuItem: 'strikeThrough',
tip: _('删除线')
},
{
menuItem: 'link',
tip: _('插入链接')
},
{
menuItem: 'justify',
tip: _('对齐方式')
},
{
menuItem: 'quote',
class: 'quotes-left',
tip: _('引用')
},
{
menuItem: 'code',
tip: _('插入代码')
},
{
menuItem: 'image',
tip: _('插入图片')
},
{
menuItem: 'table',
class: 'table2',
tip: _('插入表格')
},
{
menuItem: 'undo',
tip: _('撤销')
},
{
menuItem: 'redo',
tip: _('重复')
},
{
menuItem: 'fullscreen',
tip: _('全屏')
},
{
menuItem: 'indent',
tip: _('缩进')
},
{
menuItem: 'splitLine',
tip: _('分割线')
},
{
menuItem: 'todo',
tip: _('待办事项')
},
{
menuItem: 'lineHeight',
tip: _('行高')
}
];
const FULLSCREEN_TIP = {
isFullscreen: _('退出全屏'),
notFullscreen: _('全屏')
};
export const VIEWER_TIP = {
FULLSCREEN_TIP
};
/**
* 文件大小单位
*/
const BYTE = 1024;
export const CN = {
trillion: BYTE * BYTE // 1M
};
export const DEFAULT_FILE_SIZE = 200;
<template>
<div id="SirEditor"
class="editor-box">
<div :id="editorBar"
ref="toolbar"
class="toolbar"></div>
<div ref="textArea"
class="text-area"
@mousewheel="_onDomMouseWheel">
<div :id="editorElem"
class="editor-content"></div>
<!--用来模拟placeholder-->
<textarea
v-show="showTip"
ref="input"
v-model.trim="tip"
class="placeholder-tip form-control"
:placeholder="placeholder"
@blur="_onDomBlur"
@focus="_onDomFocus"></textarea>
</div>
</div>
</template>
<script>
/**
* @description: 富文本编辑器-组件
*/
import { removeErrTip, showErrTip } from '../utils/index';
import E from 'wangeditor';
import { uuid } from '../utils/uuid';
import lodashIsFunction from 'lodash/isFunction';
import { MENU_TIPS, CN } from './const';
import { FullScreen } from './custom_btns/full_screen';
// import xss from 'xss';
/* eslint-disable */
export default {
props: {
richContent: {
// 文本内容
type: String,
default: ''
},
placeholder: {
type: String,
default: _('请输入正文')
},
menus: {
// 菜单配置,默认全部显示
type: Array,
default() {
return [];
}
},
isBase64: {
// 是否以Base64形式存储
type: Boolean,
default: false
},
customUploadImg: {
// 是否完全自定义上传
type: Function,
default: null
},
callback: Function, // 自定义上传回调
serverUrl: {
// 上传情况的上传地址
type: String,
default: ''
},
beforeUpload: Function, // 插件方式上传图片之前的校验
reqFileName: {
// 上传图片请求图片名
type: String,
defaule: 'file'
},
acceptImgs: {
// 富文本支持上传图片格式
type: Array,
default: () => ['png', 'jpg', 'jpeg', 'bmp']
},
uploadImgParams: {
// 配置上传参数,默认会被添加到formdata中。
type: Object,
default() {
return {};
}
},
imgSep: {
type: String,
default: 'file:'
},
uploadHeaders: {
type: Object,
default() {
return {};
}
},
menuTooltipPosition: {
type: String,
default: 'down'
},
paramsWithUrl: {
// 将参数拼接到 url 中
type: Boolean,
default: false
},
maxSize: {
// 不传就是不限制大小
type: Number,
default: 0
},
timeout: {
// 上传超时时间
type: Number,
default: 5000
},
maxNum: {
// 最多上传几张
type: Number,
default: 0
},
showLinkImg: {
// 是否显示“网络图片”tab,默认显示
type: Boolean,
default: false
},
blankText: {
type: String,
default: _('该输入项不允许为空')
},
clickFocus: {
// 点击立即聚焦富文本,粘贴图片会需要
type: Boolean,
default: false
},
checkBase64: Function,
showMenusTip: {
type: Boolean,
default: false
},
menusTips: {
// 给工具栏的按钮增加title悬浮提示
type: Array,
default() {
return MENU_TIPS;
}
},
preventScroll: {
type: Boolean,
default: false
}
},
data() {
return {
editorBar: uuid(),
editorElem: uuid(),
tipTime: 2000, // 提示显示的时间
editorContent: '', /
<template>
<div class="sir-editor-wrapper"
:class="prefixCls">
<editor
ref="sirEditor"
class="sir-editor"
:rich-content.sync="editorContent"
:show-link-img="showLinkImg"
:server-url="uploadUrl"
:before-upload="beforeUpload"
:upload-img-params="uploadParams"
:custom-upload-img="customUploadImg"
:req-file-name="reqFileName"
show-menus-tip
:menus="menus"
:max-num="fileNumber"
:img-sep="imgSep"
:click-focus="true"
:placeholder="placeholder"
@update:richContent="handleChange" />
</div>
</template>
<script lang="ts">
/**
* @description: 富文本编辑器
*/
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import { API_PREFIX } from '@common/const/const';
import { DEFAULT_FILE_SIZE, CN } from './const';
import Editor from './editor.vue';
@Component({
name: 'SirEditor',
components: {
Editor
},
model: {
prop: 'value',
event: 'change'
}
})
export default class SirEditor extends Vue {
@Prop(String) private readonly value?: string;
@Prop(String) private readonly placeholder?: string;
@Prop(String) private readonly imgSep?: string;
@Prop({ default: false }) private readonly showLinkImg?: boolean;
@Prop({ default: () => ({}) }) private readonly uploadParams?: object;
@Prop({ default: '' }) private readonly prefixCls?: string;
@Prop(Number) private readonly fileSize?: number;
@Prop(Number) private readonly fileNumber?: number;
@Prop({
type: String,
default: 'file'
})
private readonly reqFileName?: number;
@Prop({
type: Function,
default: null
})
private readonly customUploadImg?;
/** 内容 */
private editorContent?: string | null = null;
/** 文件上传路径 */
private uploadUrl?: string = API_PREFIX + '/attachments';
private menus = [
'head', // 标题
'bold', // 粗体
'fontSize', // 字号
'fontName', // 字体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
'backColor', // 背景颜色
'link', // 插入链接
'list', // 列表
'justify', // 对齐方式
'image', // 插入图片
'quote', // 引用
// 'emoticon', // 表情
'table', // 表格
// 'video', // 插入视频
// 'code', // 插入代码
'undo', // 撤销
'redo', // 重复
'lineHeight', // 行高
'indent', // 增加缩进/减少缩进
'fullscreen', // 全屏
'splitLine', // 分割线
'todo' // 待办事项
]; // 富文本toolbar配置
@Watch('value', { immediate: true })
private handleValueChange (value: string) {
// 防止在编辑时,触发watch
if (value !== this.editorContent) {
let sirEditor: any = this.$refs.sirEditor;
if (sirEditor) {
sirEditor.setJsonValue(value);
}
this.editorContent = value || null;
}
}
beforeUpload (xhr, editor, files) {
const FILE_SIZE = this.fileSize || DEFAULT_FILE_SIZE;
const IMG_MAX_SIZE = FILE_SIZE * CN.trillion; // 单张不超过5M
let vm = this as any;
let currentImgs = vm.getContentImgs();
let curImgLength = currentImgs.length;
if (this.fileNumber) {
let canUploadCount = this.fileNumber - curImgLength;
if (canUploadCount <= 0) {
// 为0 不能再上传了
vm.$warn(_(`最多上传{0}个文件`, this.fileNumber), { autoHide: true });
return true;
}
// 截取可上传的总数
if (files.length > canUploadCount) {
files = files.slice(0, canUploadCount);
vm.$warn(_(`最多上传{0}个文件`, this.fileNumber), { autoHide: true });
}
}
if (files.some(fi
<!-- eslint-disable vue/no-v-html -->
<template>
<div class="sir-editor-viewer w-e-text">
<div v-html="content"></div>
</div>
</template>
<script>
/**
* @description: 富文本编辑器-viewer查看页面
*/
import { VIEWER_TIP } from './const';
export default {
name: 'SirEditorViewer',
props: {
allowFullscreen: {
// 是否允许全屏
type: Boolean,
default: true
},
toolbarDirection: {
// 工具栏的位置,left or right
type: String,
default: 'right'
},
maxHeight: {
// 最大高度
type: String,
default: '300px'
},
content: {
// 文本内容
type: String,
default: ''
}
},
data () {
return {
isFullscreen: false,
VIEWER_TIP
};
},
computed: {
gettoolbarDirection () {
return this.toolbarDirection === 'left' ? 'left' : 'right';
},
fullscreenText () {
return this.isFullscreen ? VIEWER_TIP.FULLSCREEN_TIP.isFullscreen : VIEWER_TIP.FULLSCREEN_TIP.notFullscreen;
}
},
methods: {
toggleFullscreen () {
this.isFullscreen = !this.isFullscreen;
},
onCopy () {
this.$ok(_('复制成功'));
},
onError () {
this.$fail(_('复制失败'));
}
}
};
</script>
<style lang="less" scoped>
/* stylelint-disable */
@toolbar-height: 40px;
.sir-editor-viewer {
position: relative;
display: inline-block;
width: 100%;
min-height: calc(@toolbar-height + 2px);
padding: 0;
cursor: pointer;
&.fullscreen {
position: fixed !important;
top: 0 !important;
left: 0 !important;
z-index: 9999;
display: flex;
flex-direction: column;
width: 100% !important;
height: 100% !important;
background-color: white;
.content-box {
max-height: 100% !important;
}
.toolbar-wrapper {
position: static;
display: block;
border-bottom: 1px solid rgb(211, 211, 211);
.toolbar-item {
background-color: transparent;
}
}
}
&:hover {
&:not(.fullscreen) {
.toolbar-wrapper {
display: block;
}
}
}
.toolbar-wrapper {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 100;
display: none;
height: @toolbar-height;
line-height: @toolbar-height;
&.left {
.toolbar-item {
float: left;
}
}
&.right {
.toolbar-item {
float: right;
}
}
.toolbar-item {
width: @toolbar-height;
height: 100%;
color: rgb(129, 129, 129);
text-align: center;
background-color: #f6f6f6a8;
transition: all 0.2s;
&:hover {
color: #333;
background-color: #e4e4e4;
}
}
}
.content-box {
padding: 6px 10px;
overflow: auto;
}
/deep/ li {
list-style: inherit;
}
}
</style>