邹景立 568fb5d274 refactor(editor): 统一富文本编辑器实现
将后台富文本模块统一切换为 wangEditor 加载入口,移除 CKEditor4/CKEditor5 的动态选择与运行时编辑器驱动配置,降低前端资源体积和配置复杂度。

重写 createEditor 封装,支持初始化复用、内容同步、销毁恢复、上传配置透传和图片/视频自定义上传,并在表单提交前通过 data-editor-source 统一回写编辑器内容。

同步更新系统配置页面、接口脚本、初始化数据、语言包、测试用例和业务表单引用,将原 ckeditor 模块调用改为 editor 模块,避免遗留编辑器入口失效。
2026-05-20 23:17:43 +08:00

211 lines
8.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function () {
const WANG_EDITOR_VERSION = '5.7.3';
const DEFAULT_HEIGHT = 500;
const DEFAULT_TOOLBAR_KEYS = [
'headerSelect',
'|',
'bold',
'underline',
'italic',
'|',
'bulletedList',
'numberedList',
'|',
'justifyLeft',
'justifyCenter',
'justifyRight',
'|',
'insertLink',
'uploadImage',
'uploadVideo',
'|',
'undo',
'redo'
];
const DEFAULT_IMAGE_HOVERBAR_KEYS = [
'imageWidth30',
'imageWidth50',
'imageWidth100',
'editorImageSizeMenu',
'editImage',
'deleteImage'
];
const DEFAULT_VIDEO_HOVERBAR_KEYS = [
'enter',
'editVideoSize',
'editVideoSrc'
];
function tip(message, type) {
message = message || '文件上传失败';
if (window.$ && $.msg && typeof $.msg.tips === 'function') return $.msg.tips(message);
if (window.layer && typeof window.layer.msg === 'function') return window.layer.msg(message);
return (type === 'error' ? console.error : console.log)(message);
}
function firstTextarea(ele) {
const $ele = $(ele).eq(0);
if ($ele.length < 1) throw new Error('Editor target not found: ' + ele);
return $ele;
}
function toNumber(value, fallback) {
value = parseFloat(value);
return Number.isFinite(value) ? value : fallback;
}
function toInteger(value, fallback) {
value = parseInt(value, 10);
return Number.isFinite(value) ? value : fallback;
}
function getConfig($ele, option) {
option = option || {};
const data = $ele.data() || {};
const upload = option.upload || {};
const path = String(upload.path || option.uploadPath || data.path || 'editor').replace(/\W/g, '');
return {
mode: option.mode || data.editorMode || 'default',
height: toInteger(option.height || data.editorHeight || data.height, DEFAULT_HEIGHT),
placeholder: option.placeholder || data.placeholder || '请输入内容...',
uploadPath: path || 'editor',
uploadType: upload.uptype || upload.type || option.uptype || data.uptype || '',
uploadSafe: (upload.safe || option.safe || data.safe) ? 1 : 0,
uploadHide: (upload.hide || option.hide || data.hload) ? 1 : 0,
uploadSize: toInteger(upload.size || option.size || data.size, 0),
uploadQuality: toNumber(upload.quality || option.quality || data.quality, 1),
imageMaxWidth: toInteger(upload.maxWidth || option.maxWidth || data.maxWidth, 0),
imageMaxHeight: toInteger(upload.maxHeight || option.maxHeight || data.maxHeight, 0),
imageCutWidth: toInteger(upload.cutWidth || option.cutWidth || data.cutWidth, 0),
imageCutHeight: toInteger(upload.cutHeight || option.cutHeight || data.cutHeight, 0),
toolbarKeys: option.toolbarKeys || data.toolbarKeys || DEFAULT_TOOLBAR_KEYS,
hoverbarKeys: option.hoverbarKeys || {
image: {menuKeys: option.imageHoverbarKeys || DEFAULT_IMAGE_HOVERBAR_KEYS},
video: {menuKeys: option.videoHoverbarKeys || DEFAULT_VIDEO_HOVERBAR_KEYS}
},
excludeKeys: option.excludeKeys || ['fullScreen']
};
}
function uploadBySystem(file, config, type, insertFn) {
if (!window.SystemUploadAdapter) {
const message = '后台上传模块尚未加载,请刷新页面后重试';
tip(message, 'error');
return Promise.reject(new Error(message));
}
file.path = config.uploadPath;
file.quality = config.uploadQuality;
file.maxWidth = type === 'image' ? config.imageMaxWidth : 0;
file.maxHeight = type === 'image' ? config.imageMaxHeight : 0;
file.cutWidth = type === 'image' ? config.imageCutWidth : 0;
file.cutHeight = type === 'image' ? config.imageCutHeight : 0;
return new Promise(function (resolve, reject) {
const $scope = $('<span></span>');
const fail = function (event, data) {
const message = (data && data.file && data.file.xstats) || '文件上传失败';
$scope.off('upload.error', fail);
tip(message, 'error');
reject(new Error(message));
};
$scope.one('upload.error', fail);
new window.SystemUploadAdapter({
elem: $scope,
hide: config.uploadHide,
safe: config.uploadSafe,
type: config.uploadType,
size: config.uploadSize
}).upload([file], function (url, uploadedFile) {
$scope.off('upload.error', fail);
uploadedFile = uploadedFile || file;
if (type === 'video') {
insertFn(url, '');
} else {
// 第三个参数是图片链接 href后台编辑场景不自动加链接避免点击图片跳转导致编辑丢失。
insertFn(url, uploadedFile.name || file.name || '');
}
resolve(url);
});
});
}
function buildMenuConfig(config) {
return {
uploadImage: {
async customUpload(file, insertFn) {
return uploadBySystem(file, config, 'image', insertFn);
}
},
uploadVideo: {
async customUpload(file, insertFn) {
return uploadBySystem(file, config, 'video', insertFn);
}
}
};
}
window.createEditor = function (ele, option) {
const $ele = firstTextarea(ele);
const exists = $ele.data('editorInstance');
if (exists) return exists;
if (!window.wangEditor || typeof window.wangEditor.createEditor !== 'function') {
throw new Error('wangEditor is not loaded.');
}
const config = getConfig($ele, option);
const $layout = $('<div class="ta-editor-layout"><div class="ta-editor-toolbar"></div><div class="ta-editor-body"></div></div>');
$layout.css({border: '1px solid #ccc', zIndex: 1001});
$layout.find('.ta-editor-toolbar').css({borderBottom: '1px solid #ccc'});
$layout.find('.ta-editor-body').css({height: config.height + 'px'});
$ele.hide().attr('data-editor-source', 'wangEditor').data('editorLayout', $layout).after($layout);
const editor = window.wangEditor.createEditor({
selector: $layout.find('.ta-editor-body').get(0),
html: $ele.val() || '<p><br></p>',
config: {
placeholder: config.placeholder,
hoverbarKeys: config.hoverbarKeys,
MENU_CONF: buildMenuConfig(config),
onCreated(editor) {
editor.setHtml($ele.val() || '<p><br></p>');
},
onChange(editor) {
$ele.val(editor.getHtml()).triggerHandler('editor.change', editor);
}
},
mode: config.mode
});
window.wangEditor.createToolbar({
editor: editor,
selector: $layout.find('.ta-editor-toolbar').get(0),
config: {toolbarKeys: config.toolbarKeys, excludeKeys: config.excludeKeys},
mode: config.mode
});
const destroy = editor.destroy.bind(editor);
editor.getData = function () {
return editor.getHtml();
};
editor.setData = function (html) {
return editor.setHtml(html || '<p><br></p>');
};
editor.updateElement = function () {
$ele.val(editor.getHtml());
return editor;
};
editor.destroy = function () {
$ele.removeData('editorInstance').removeData('editorLayout').removeAttr('data-editor-source').show();
$layout.remove();
return destroy();
};
$ele.data('editorInstance', editor).triggerHandler('editor.init', editor);
return editor;
};
window.createEditor.version = WANG_EDITOR_VERSION;
})();