前言
最近项目中需要实现一个功能:在富文本编辑器中创建一个供前台填写内容的单子。
内容就和普通常见的请假申请单一样,类似
请假日期:2019年5月14日 16:59:44
Vinsea的病假申请单(表格标题) 姓名 Vinsea 部门 开发部 请假类型 病假 请假开始时间 2019年5月15日 请假结束时间 2019年5月16日 项目经理意见 通过/不通过 部门经理意见 通过/不通过 最终结果 通过/不通过/打回
难点
- 每种假的申请单格式、流程都不一样,需要分种类做成模版
- 每个模版可以后期维护
- 审批意见是单选框组,用到的是
el-radio-group,通过v-model绑定值
对应解决思路
第一个难点:每种请假模版写一个基础得的html,存到数据库中;
第二个难点:内容可维护,利用Vue的数据双向绑定特性,将模版中的value部分写成的格式,通过<component :is="xxxx"></component>组件或者Vue.component()引入模版;
第三个难点:UEditor中不存在这种工具,只能切换到源码模式添加,但这样运维需要有代码基础,于是解决办法就是在工具栏上添加一个按钮,可以添加el-radio-group之类的Vue组件元素
实现方法见下面的 “下拉列表带按钮” 模块。
UEditor中添加自定义按钮
我将按钮大致分为四种:直接点击使用、下拉菜单、下拉列表带按钮、弹出对话框。
公共步骤
1、在ueditor.config.js中的toolbars属性中添加自定义按钮的名称,接上述例子,这里按钮名称叫suggestion,用|竖线符号代表分组
//工具栏上的所有的功能按钮和下拉框,可以在new编辑器的实例时选择自己需要的重新定义
toolbars: [
[
"fullscreen",
"source",
...
"|",
"suggestion"
]2
3
4
5
6
7
8
9
10
11
12
2、找到ueditor.all.js文件,找到btnCmds属性,添加刚才的按钮名称,这里用于触发命令
//为工具栏添加按钮,以下都是统一的按钮触发命令,所以写在一起
var btnCmds = [
'undo',
'redo',
...
'suggestion'
];2
3
4
5
6
7
3、制作一个心仪的图标,大小推荐:18*18,起名为icon-suggestion.png,放入文件夹:themes/default/images/
找到themes/default/css/buttonicon.css,为刚才添加的按钮增加图标样式
.edui-default .edui-for-suggestion .edui-button-body .edui-icon{
background: url("../images/icon-suggestion.png") no-repeat center !important;
background-size: 100% 100%;
}2
3
4
下面是这四种自定义按钮事件添加的方法,可以对照官网API进行修改:https://ueditor.baidu.com/doc/
直接点击使用
比如:“分隔符”、“日期”、“居中对齐”...
自定义按钮方法,比如要自定义一个按钮叫vinsea,先重复上面的“公共步骤”部分,然后:
在ueditor.all.js中,找到最后一个UE.commands的定义,在它下面添加
UE.commands['vinsea'] = {
execCommand: function (cmd) {
// TODO 在这里写自己的逻辑,也就是点击按钮后执行的方法
}
};2
3
4
5
下拉菜单
比如:“选择字体”、“选择字号”...
自定义按钮方法,比如要自定义一个按钮叫vinsea,先重复上面的“公共步骤”部分,然后:
在ueditor.all.js中,找到最后一个UE.commands的定义,在它下面添加 1、加事件
UE.commands['vinsea'] = {
execCommand: function (command, value) {
// TODO 在这里实现自己的逻辑
// 这里实现的是,在当前光标位置,插入所传入的值 value,用div包裹
this.execCommand('insertHtml', '<div>' + value + '</div>');
return true;
}
};2
3
4
5
6
7
8
2、初始化按钮
editorui.atymenu0 = function (editor) {
var cmd = 'vinsea'; // 上面定义的事件名称
// 取出
var vals = {'1': '哈哈', '2': '嘻嘻', '3': '嘿嘿'},
items = [];
// 定义下拉列表被点击的事件
_onMenuClick = function () {
// this.value就是下面item.push中定义的value
editor.execCommand(cmd, this.value);
},
for (var i in vals) {
items.push({
label: vals[i],
value: i,
theme: editor.options.theme,
onclick: _onMenuClick
});
}
// 实例化一个下拉列表按钮
var ui = new editorui.MenuButton({
editor: editor,
className: 'edui-for-' + cmd,
title: 'Vinsea按钮', // 鼠标悬浮显示的标题
items: items,
// 这个按钮是点击“图标”的事件
onbuttonclick: function () {
// 取最新值
var value = editor.queryCommandValue(cmd) || this.value;
editor.execCommand(cmd, value);
}
});
editorui.buttons[cmd] = ui;
return ui;
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
最终效果是,点击图标右侧下拉按钮,出现 哈哈、嘻嘻、嘿嘿三个选项。
分别点击,会在当前光标处插入对应的值(value)
下拉列表带按钮
比如:“选择字体颜色”、“选择字体背景颜色”、“选择自动排版”...
字体颜色中,每一种颜色就算是一个按钮
这个方法也是本篇开始例子中用到的实现方式。
1、加事件
在ueditor.all.js中,找到最后一个UE.commands的定义,在它下面添加自定义事件
// add by Vinsea@2019年05月15日
UE.commands['suggestion'] = {
lastValue: '', // 当前值
// execCommand的value从哪里来?看后面几个步骤。
execCommand: function (command, value) {
getHtmlContent = function (value) {
var len = 0;
var resultShow = '';
var resultVinsea = '';
if(value && value.length>0){
len = value.length;
for(var i=0;i<len;i++){
resultShow += '<div><input type="radio"> value[i].name</div>' // 默认样式
resultVinsea += '<el-radio :label="'+value[i].name+'"></aty-radio>'
}
}
return `
<div class="radio-wrapper" v-show="false">
<span class="radio-text">审批结论</span>
${resultShow}
</div>
<el-radio-group name="cljl" v-model="cljl">
${resultVinsea}
</el-radio-group>
`
};
this.execCommand('insertHtml', getHtmlContent(value));
// 存储当前值
this.lastValue = value;
return true;
},
// 查询给定命令在当前选区内的值,后面会用到
queryCommandValue: function () {
return this.lastValue;
}
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2、按钮点击展开后的内容
这一步实现点击按钮后展开的内容
// ui/vsuggestionpicker.js
// add by Vinsea@2019年05月15日 审批意见选项值
///import core
///import uicore
(function () {
var utils = baidu.editor.utils,
UIBase = baidu.editor.ui.UIBase;
var VSuggestionPicker = (baidu.editor.ui.VSuggestionPicker = function (
options
) {
// 初始化自定义配置
this.initOptions(options);
// 初始化 VSuggestionPicker 组件(直接写this.initUIBase()也行)
this.initVSuggestionPicker ();
});
VSuggestionPicker.prototype = {
initVSuggestionPicker: function () {
this.initUIBase();
},
getHtmlTpl: function () {
var me = this.editor;
return (
// ## 井号会被ueditor替换成真正的id,%% 百分号会被替换成当前class名
'<div id="##" class="edui-vsuggestionpicker %%">' +
'<div class="edui-vsuggestionpicker-body">' +
'<table>' +
'<tr><td nowrap><input type="checkbox" name="1" value="1" data-text="同意">同意</td></tr>' +
'<tr><td nowrap><input type="checkbox" name="2" value="2" data-text="不同意">不同意</td></tr>' +
'<tr><td nowrap><input type="checkbox" name="3" value="3" data-text="发回">发回</td></tr>' +
'<tr><td nowrap><button style="width:100%">确定</button></td></tr>' +
'</table>' +
'</div>' +
'</div>'
);
},
_UIBase_render: UIBase.prototype.render
};
// 继承 UIBase 的特性
utils.inherits(AtyMenuSpyjPicker, UIBase);
})();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
3、toolbar加点击下拉展开事件
这一步实现“点击展开”这个动作
// ui/suggestionbutton.js
// add by Vinsea@2019年05月15日
///import core
///import uicore
///import ui/popup.js
///import ui/vsuggestionpicker.js
///import ui/splitbutton.js
(function () {
var utils = baidu.editor.utils, // 工具类
Popup = baidu.editor.ui.Popup, // 弹出框组件
VSuggestionPicker = baidu.editor.ui.VSuggestionPicker, // 上一步自定义的内容组件
SplitButton = baidu.editor.ui.SplitButton, // 公用按钮组件
// 本组件
SuggestionButton = (baidu.editor.ui.SuggestionButton = function (options) {
// 初始化自定义配置
this.initOptions(options);
// 初始化本组件
this.initSuggestionButton();
});
// 获取选中的值
function getPara (me) {
var cont = me.getDom(),
editorId = me.editor.uid,
inputType = null,
attrName = '',
result = [],
ipts = domUtils.getElementsByTagName(cont, 'input');
for (var i = ipts.length - 1, ipt; (ipt = ipts[i--]);) {
inputType = ipt.getAttribute('type');
if (inputType == 'checkbox') {
attrName = ipt.dataset.text;
if (ipt.checked) {
result.unshift({name:attrName,value:ipt.value})
}
}
}
return result;
}
SuggestionButton.prototype = {
initSuggestionButton: function () {
var me = this;
// 实例化一个弹出层
this.popup = new Popup({
// 传入上一步配置的内容,参数为当前editor对象
content: new VSuggestionPicker({editor: me.editor}),
editor: me.editor,
// 弹出层关闭方法
hide: function () {
if (!this._hidden && this.getDom()) {
// getPara(this);
this.getDom().style.display = 'none';
this._hidden = true;
this.fireEvent('hide');
}
}
});
// 标志位,用于判断是否已经给按钮添加click事件,防止重复添加造成内存溢出
var flag = 0;
// 弹出层弹出前的钩子
this.popup.addListener('postRenderAfter', function () {
var popupUI = this;
if (flag) return;
var cont = this.getDom(),
// 获取上一步中内容里写的按钮元素
btn = cont.getElementsByTagName('button')[0];
// 添加click事件
btn.onclick = function () {
// 获取已勾选的值
var result = getPara(popupUI);
// 调用 suggestion 命令(也就是上面 “第1步” 定义的)
// 也就是 第1步中 execCommand 的方法
// 第二个参数是勾选的值的数组集合
me.editor.execCommand('suggestion' , result);
// 关闭图层
popupUI.hide();
};
flag = 1;
});
// 初始化
this.initSplitButton();
}
};
// 继承公用按钮的特性
utils.inherits(SuggestionButton, SplitButton);
})();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
4、最后一步,初始化按钮
/* 审批意见 */
editorui.suggestion = function (editor) {
var ui = new editorui.VSuggestionButton({
editor: editor,
title:'审批意见',
className: 'edui-for-suggestion',
// 这个点击事件,不是下拉展开事件
// 而是直接点击图标的事件(类似 “重复上一个动作” 事件)
onbuttonclick:function(){
// 通过 suggestion 的查询命令,获取当前值
var value = editor.queryCommandValue('suggestion');
// 执行(和上一步中点击 “确定” 效果一样)
editor.execCommand('suggestion',value);
}
});
// 添加到 suggestion 实例上
editorui.buttons['suggestion'] = ui;
// 因为是添加button,所以需要返回这个button
return ui;
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
完成效果

看一下其中一个 “意见” 的html部分
<div class="radio-wrapper" v-show="false">
<span class="radio-text">审批结论</span>
<div class="radio"></div>
<span class="radio-text">同意</span>
<div class="radio"></div>
<span class="radio-text">不同意</span>
<div class="radio"></div>
<span class="radio-text">发回</span>
<div class="radio"></div>
<span class="radio-text">部分同意</span>
</div>
<el-radio-group name="cljl" v-model="cljl">
<el-radio :label="同意"></<el-radio>
<<el-radio :label="不同意"></<el-radio>
<<el-radio :label="发回"></<el-radio>
</<el-radio-group>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这个v-show="false"就很有灵性了,因为在html中没有 v-show 这么个东西,所以在ueditor编辑器中会解析显示部分代码,而html中不存在 el-radio-group 这个元素,所以不会显示在ueditor中。
正好实现了:
在ueditor中显示单选框样式,在Vue使用中使用 el-radio-group
弹出对话框
比如:“添加超链接”、“添加图片”、“添加音乐”...
自定义按钮方法: https://github.com/Vinsea/ueditor/blob/dev-1.5.0/_examples/addCustomizeDialog.js