diff --git a/application/admin/view/shopro/goods/goods/add.html b/application/admin/view/shopro/goods/goods/add.html index be40ab0..ba428b5 100644 --- a/application/admin/view/shopro/goods/goods/add.html +++ b/application/admin/view/shopro/goods/goods/add.html @@ -436,6 +436,14 @@ color: #333333; padding: 20px 24px !important; } + #goodsContent { + width: 100%; + min-height: 500px; + border: none; + padding: 16px; + box-sizing: border-box; + outline: none; + } /* 底部操作栏与按钮美化 */ .goods-form .sa-footer--submit { diff --git a/application/extra/addons.php b/application/extra/addons.php index 7c27cb1..64ceb8d 100755 --- a/application/extra/addons.php +++ b/application/extra/addons.php @@ -4,6 +4,18 @@ return array ( 'autoload' => false, 'hooks' => array ( + 'epay_config_init' => + array ( + 0 => 'epay', + ), + 'addon_action_begin' => + array ( + 0 => 'epay', + ), + 'action_begin' => + array ( + 0 => 'epay', + ), 'upgrade' => array ( 0 => 'shopro', @@ -13,6 +25,11 @@ return array ( 0 => 'shopro', ), 'config_init' => + array ( + 0 => 'shopro', + 1 => 'summernote', + ), + 'flatten_shopro_menus' => array ( 0 => 'shopro', ), diff --git a/public/assets/js/addons.js b/public/assets/js/addons.js index 9c7c693..16df618 100755 --- a/public/assets/js/addons.js +++ b/public/assets/js/addons.js @@ -1,59 +1,222 @@ define([], function () { - if (Config.modulename == 'admin' && Config.controllername == 'index' && Config.actionname == 'index') { - require.config({ - paths: { - 'vue3': "../addons/shopro/libs/vue", - 'vue': "../addons/shopro/libs/vue.amd", - 'text': "../addons/shopro/libs/require-text", - 'SaChat': '../addons/shopro/chat/index', - 'ElementPlus': '../addons/shopro/libs/element-plus/index', - 'ElementPlusIconsVue3': "../addons/shopro/libs/element-plus/icons-vue", - 'ElementPlusIconsVue': '../addons/shopro/libs/element-plus/icons-vue.amd', - 'io': '../addons/shopro/libs/socket.io', - }, - shim: { - 'ElementPlus': { - deps: ['css!../addons/shopro/libs/element-plus/index.css'] - }, - }, - }); - require(['vue3', 'ElementPlusIconsVue3'], function (Vue3, ElementPlusIconsVue3) { - require(['vue', 'jquery', 'SaChat', 'text!../addons/shopro/chat/index.html', 'ElementPlus', 'ElementPlusIconsVue', 'io'], function (Vue, $, SaChat, SaChatTemplate, ElementPlus, ElementPlusIconsVue, io) { - if (Config.dark_type != 'none') { - SaChatTemplate = SaChatTemplate.replaceAll('__DARK__', ``) - } - - SaChatTemplate = SaChatTemplate.replaceAll('__DARK__', ``) - SaChatTemplate = SaChatTemplate.replaceAll('__CDN__', Config.__CDN__) - - Fast.api.ajax({ - url: 'shopro/chat/index/init', - loading: false, - type: 'GET' - }, function (ret, res) { - $("body").append(`
-
`); - - $("#SaChatTemplateContainer").append(SaChatTemplate); - - const { createApp } = Vue - const app = createApp({}) - - app.use(ElementPlus) - for (const [key, component] of Object.entries(ElementPlusIconsVue)) { - app.component(key, component) - } - - app.component('sa-chat', SaChat) - app.mount(`#SaChatWrap`) - return false; - }, function (ret, res) { - if (res.msg == '') { - return false; - } - }) - }); - - }); + if (Config.modulename == 'admin' && Config.controllername == 'index' && Config.actionname == 'index') { + require.config({ + paths: { + 'vue3': "../addons/shopro/libs/vue", + 'vue': "../addons/shopro/libs/vue.amd", + 'text': "../addons/shopro/libs/require-text", + 'SaChat': '../addons/shopro/chat/index', + 'ElementPlus': '../addons/shopro/libs/element-plus/index', + 'ElementPlusIconsVue3': "../addons/shopro/libs/element-plus/icons-vue", + 'ElementPlusIconsVue': '../addons/shopro/libs/element-plus/icons-vue.amd', + 'io': '../addons/shopro/libs/socket.io', + }, + shim: { + 'ElementPlus': { + deps: ['css!../addons/shopro/libs/element-plus/index.css'] + }, + }, + }); + require(['vue3', 'ElementPlusIconsVue3'], function (Vue3, ElementPlusIconsVue3) { + require(['vue', 'jquery', 'SaChat', 'text!../addons/shopro/chat/index.html', 'ElementPlus', 'ElementPlusIconsVue', 'io'], function (Vue, $, SaChat, SaChatTemplate, ElementPlus, ElementPlusIconsVue, io) { + if (Config.dark_type != 'none') { + SaChatTemplate = SaChatTemplate.replaceAll('__DARK__', ``) + } + + SaChatTemplate = SaChatTemplate.replaceAll('__DARK__', ``) + SaChatTemplate = SaChatTemplate.replaceAll('__CDN__', Config.__CDN__) + + Fast.api.ajax({ + url: 'shopro/chat/index/init', + loading: false, + type: 'GET' + }, function (ret, res) { + $("body").append(`
+
`); + + $("#SaChatTemplateContainer").append(SaChatTemplate); + + const { createApp } = Vue + const app = createApp({}) + + app.use(ElementPlus) + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) + } + + app.component('sa-chat', SaChat) + app.mount(`#SaChatWrap`) + return false; + }, function (ret, res) { + if (res.msg == '') { + return false; + } + }) + }); + + }); } +require.config({ + paths: { + 'summernote-core': '../addons/summernote/js/summernote.min', + 'summernote-lang': '../addons/summernote/lang/summernote-zh-CN.min', + 'summernote': '../addons/summernote/js/summernote.min', + 'purify': '../addons/summernote/js/purify.min' + }, + shim: { + 'summernote-lang': ['summernote-core'], + 'summernote': ['css!../addons/summernote/css/summernote.min.css'], + } +}); +require(['form', 'upload'], function (Form, Upload) { + var _bindevent = Form.events.bindevent; + Form.events.bindevent = function (form) { + _bindevent.apply(this, [form]); + try { + var classname = (Config && Config.summernote && Config.summernote.classname) || '.editor'; + //绑定summernote事件 + if ($(classname, form).length > 0) { + var selectUrl = typeof Config !== 'undefined' && Config.modulename === 'index' ? 'user/attachment' : 'general/attachment/select'; + require(['summernote-core', 'purify', 'css!../addons/summernote/css/summernote.min.css'], function (undefined, DOMPurify) { + require(['summernote-lang'], function () { + var imageButton = function (context) { + var ui = $.summernote.ui; + var button = ui.button({ + contents: '', + tooltip: __('Choose'), + click: function () { + parent.Fast.api.open(selectUrl + "?element_id=&multiple=true&mimetype=image/", __('Choose'), { + callback: function (data) { + var urlArr = data.url.split(/\,/); + $.each(urlArr, function () { + var url = Fast.api.cdnurl(this, true); + context.invoke('editor.insertImage', url); + }); + } + }); + return false; + } + }); + return button.render(); + }; + var attachmentButton = function (context) { + var ui = $.summernote.ui; + var button = ui.button({ + contents: '', + tooltip: __('Choose'), + click: function () { + parent.Fast.api.open(selectUrl + "?element_id=&multiple=true&mimetype=*", __('Choose'), { + callback: function (data) { + var urlArr = data.url.split(/\,/); + $.each(urlArr, function () { + var url = Fast.api.cdnurl(this, true); + var node = $("" + url + ""); + context.invoke('insertNode', node[0]); + }); + } + }); + return false; + } + }); + return button.render(); + }; + var cfg = (Config && Config.summernote) || {}; + if (cfg.isdompurify && typeof DOMPurify !== 'undefined') { + DOMPurify.addHook('uponSanitizeElement', function (node, data, config) { + if (data.tagName === 'iframe') { + var allowedIframePrefixes = cfg.allowiframeprefixs || []; + var src = node.getAttribute('src'); + + var isAllowed = false; + for (var i = 0; i < allowedIframePrefixes.length; i++) { + if (src && src.indexOf(allowedIframePrefixes[i]) === 0) { + isAllowed = true; + break; + } + } + + if (!isAllowed) { + return node.parentNode.removeChild(node); + } + + node.setAttribute('allowfullscreen', ''); + node.setAttribute('allow', 'fullscreen'); + } + }); + + var purifyOptions = { + ADD_TAGS: ['iframe'], + FORCE_REJECT_IFRAME: false + }; + $.extend($.summernote.plugins, { + 'dompurify': function (context) { + const originalPurify = context.options.modules.codeview.prototype.purify; + context.options.modules.codeview.prototype.purify = function (html) { + html = DOMPurify.sanitize(html, purifyOptions); + return originalPurify.call(this, html); + }; + } + }); + } + + $(classname, form).each(function () { + $(this).summernote($.extend(true, {}, { + height: isNaN(cfg.height) ? null : parseInt(cfg.height), + minHeight: parseInt(cfg.minHeight || 250), + toolbar: cfg.toolbar, + followingToolbar: parseInt(cfg.followingToolbar), + placeholder: cfg.placeholder || '', + airMode: parseInt(cfg.airMode) || false, + lang: 'zh-CN', + fontNames: cfg.fontNames || [], + fontNamesIgnoreCheck: ["Open Sans", "Microsoft YaHei", '微软雅黑', '宋体', '黑体', '仿宋', '楷体', '幼圆'], + buttons: { + image: imageButton, + attachment: attachmentButton, + }, + plugins: { + 'dompurify': true + }, + dialogsInBody: true, + callbacks: { + onChange: function (contents) { + if (cfg.isdompurify && typeof DOMPurify !== 'undefined') { + contents = DOMPurify.sanitize(contents, purifyOptions); + } + $(this).val(contents); + $(this).trigger('change'); + }, + onInit: function () { + }, + onImageUpload: function (files) { + var that = this; + for (var i = 0; i < files.length; i++) { + Upload.api.send(files[i], function (data) { + var url = Fast.api.cdnurl(data.url, true); + $(that).summernote("insertImage", url, 'filename'); + }); + } + }, + onPaste: function (e) { + if (cfg.pasteAsPlainText || false) { + var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text'); + e.preventDefault(); + setTimeout(function () { + document.execCommand('insertText', false, bufferText); + }, 10); + } + } + } + }, $(this).data("summernote-options") || {})); + }); + }); + }); + } + } catch (e) { + + } + + }; +}); + }); \ No newline at end of file diff --git a/public/assets/js/backend/shopro/goods/goods.js b/public/assets/js/backend/shopro/goods/goods.js index de12a35..3e33d1e 100644 --- a/public/assets/js/backend/shopro/goods/goods.js +++ b/public/assets/js/backend/shopro/goods/goods.js @@ -456,6 +456,157 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin }, }) + require.config({ + paths: { + 'summernote-core': '../addons/summernote/js/summernote.min', + 'summernote-lang': '../addons/summernote/lang/summernote-zh-CN.min', + 'purify': '../addons/summernote/js/purify.min' + }, + shim: { + 'summernote-lang': ['summernote-core'] + } + }); + + function loadAndInitSummernote($editor) { + if ($editor.next('.note-editor').length > 0) { + if (typeof $editor.summernote === 'function' && form.model.content && !$editor.summernote('code')) { + $editor.summernote('code', form.model.content); + } + $editor.next('.note-editor').find('.note-editable').css({ + 'min-height': '600px', + 'height': '620px' + }); + return; + } + + require(['summernote-core', 'purify', 'upload', 'css!../addons/summernote/css/summernote.min.css'], function (undefined, DOMPurify, Upload) { + require(['summernote-lang'], function () { + if ($editor.next('.note-editor').length > 0) { + return; + } + + var selectUrl = typeof Config !== 'undefined' && Config.modulename === 'index' ? 'user/attachment' : 'general/attachment/select'; + var imageButton = function (context) { + var ui = $.summernote.ui; + var button = ui.button({ + contents: '', + tooltip: __('Choose'), + click: function () { + parent.Fast.api.open(selectUrl + "?element_id=&multiple=true&mimetype=image/", __('Choose'), { + callback: function (data) { + var urlArr = data.url.split(/\,/); + $.each(urlArr, function () { + var url = Fast.api.cdnurl(this, true); + context.invoke('editor.insertImage', url); + }); + } + }); + return false; + } + }); + return button.render(); + }; + + var attachmentButton = function (context) { + var ui = $.summernote.ui; + var button = ui.button({ + contents: '', + tooltip: __('Choose'), + click: function () { + parent.Fast.api.open(selectUrl + "?element_id=&multiple=true&mimetype=*", __('Choose'), { + callback: function (data) { + var urlArr = data.url.split(/\,/); + $.each(urlArr, function () { + var url = Fast.api.cdnurl(this, true); + var node = $("" + url + ""); + context.invoke('insertNode', node[0]); + }); + } + }); + return false; + } + }); + return button.render(); + }; + + var cfg = (window.Config && window.Config.summernote) || {}; + var purifyOptions = { + ADD_TAGS: ['iframe'], + FORCE_REJECT_IFRAME: false + }; + + $editor.summernote($.extend(true, {}, { + height: 620, + minHeight: 550, + toolbar: cfg.toolbar || [ + ['style', ['style', 'undo', 'redo']], + ['font', ['bold', 'underline', 'strikethrough', 'clear']], + ['fontname', ['color', 'fontname', 'fontsize']], + ['para', ['ul', 'ol', 'paragraph', 'height']], + ['table', ['table', 'hr']], + ['insert', ['link', 'picture', 'video']], + ['select', ['image', 'attachment']], + ['view', ['fullscreen', 'codeview', 'help']] + ], + followingToolbar: parseInt(cfg.followingToolbar) || 1, + placeholder: cfg.placeholder || '请输入商品详情内容...', + lang: 'zh-CN', + fontNames: cfg.fontNames || ["Arial", "Microsoft YaHei", "微软雅黑", "宋体", "黑体", "仿宋", "楷体"], + fontNamesIgnoreCheck: ["Open Sans", "Microsoft YaHei", '微软雅黑', '宋体', '黑体', '仿宋', '楷体', '幼圆'], + buttons: { + image: imageButton, + attachment: attachmentButton, + }, + dialogsInBody: true, + callbacks: { + onChange: function (contents) { + if (cfg.isdompurify && typeof DOMPurify !== 'undefined') { + contents = DOMPurify.sanitize(contents, purifyOptions); + } + form.model.content = contents; + $editor.val(contents); + }, + onInit: function () { + if (form.model.content) { + $editor.summernote('code', form.model.content); + } + $editor.next('.note-editor').find('.note-editable').css({ + 'min-height': '600px', + 'height': '620px' + }); + }, + onImageUpload: function (files) { + var that = this; + for (var i = 0; i < files.length; i++) { + Upload.api.send(files[i], function (data) { + var url = Fast.api.cdnurl(data.url, true); + $(that).summernote("insertImage", url, 'filename'); + }); + } + }, + onPaste: function (e) { + if (cfg.pasteAsPlainText) { + var bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text'); + e.preventDefault(); + setTimeout(function () { + document.execCommand('insertText', false, bufferText); + }, 10); + } + } + } + }, $editor.data("summernote-options") || {})); + + if (form.model.content && typeof $editor.summernote === 'function') { + $editor.summernote('code', form.model.content); + } + $editor.next('.note-editor').find('.note-editable').css({ + 'min-height': '600px', + 'height': '620px' + }); + }); + }); + } + function initEditor() { let $editor = $('#goodsContent'); if (!$editor.length) return; @@ -465,46 +616,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin $editor.val(form.model.content); } - // 若已初始化 summernote,则同步 code 并返回 - if ($editor.next('.note-editor').length > 0) { - if (typeof $editor.summernote === 'function') { - if (form.model.content && !$editor.summernote('code')) { - $editor.summernote('code', form.model.content); - } - } - $editor.next('.note-editor').find('.note-editable').css({ - 'min-height': '600px', - 'height': '620px' - }); - return; - } - - // 优先走 FastAdmin 标准 bindevent - Controller.api.bindevent(); - - // 容错兜底:若 150ms 内未自动实例化,则直接初始化 summernote - setTimeout(() => { - if ($editor.next('.note-editor').length === 0 && typeof $.fn.summernote !== 'undefined') { - var options = (window.Config && window.Config.summernote) || {}; - $editor.summernote($.extend(true, { - height: parseInt(options.height || 620), - minHeight: parseInt(options.minHeight || 550), - lang: 'zh-CN', - dialogsInBody: true - }, $editor.data("summernote-options") || {})); - if (form.model.content) { - $editor.summernote('code', form.model.content); - } - } - if ($editor.next('.note-editor').length > 0) { - $editor.next('.note-editor').find('.note-editable').css({ - 'min-height': '600px', - 'height': '620px' - }); - } - }, 150); + loadAndInitSummernote($editor); } + // 预加载富文本依赖 + try { + require(['summernote-core', 'purify', 'upload', 'css!../addons/summernote/css/summernote.min.css'], function () { + require(['summernote-lang'], function () {}); + }); + } catch (e) {} + watch(() => state.activeStep, (newStep) => { if (newStep == 4) { nextTick(() => { @@ -538,7 +659,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin form.model.service_ids = form.model.service_ids ? form.model.service_ids : []; // 富文本 - $('#goodsContent').val(form.model.content || ''); + let $editor = $('#goodsContent'); + $editor.val(form.model.content || ''); + if ($editor.next('.note-editor').length > 0 && typeof $editor.summernote === 'function') { + $editor.summernote('code', form.model.content || ''); + } if (state.activeStep == 4) { nextTick(() => { initEditor(); @@ -1014,7 +1139,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin if ($editor.next('.note-editor').length > 0 && typeof $editor.summernote === 'function') { editorContent = $editor.summernote('code'); } else { - editorContent = $editor.val() || ''; + editorContent = form.model.content || $editor.val() || ''; } submitForm.content = editorContent;