Browse Source

可视化编辑

moshaorui 3 months ago
parent
commit
0608d0c7f6

+ 14 - 0
app/Distributor/Controllers/VisualEditorController.php

@@ -3,6 +3,8 @@
 namespace App\Distributor\Controllers;
 
 use App\Distributor\Metrics\DistMessage;
+use App\Distributor\Repositories\DistAdminDistributor;
+use App\Distributor\Repositories\DistAppearancePublishList;
 use App\Distributor\Repositories\DistAppearanceTemplate;
 use App\Distributor\Repositories\SiteMenu;
 use App\Http\Controllers\Controller;
@@ -53,6 +55,18 @@ class VisualEditorController extends Controller
         return response()->json(['status' => 1]);
     }
 
+    /*
+     * 发布
+     */
+    public function publish()
+    {
+        $info = DistAdminDistributor::getInfo();
+        $distId = $info->id;
+        $appearanceId = $info->appearance_id;
+        //DistAppearancePublishList::publish($appearanceId, $distId);
+        return ['status'=>1,'message'=>admin_trans('admin.update_succeeded')];
+    }
+
     /*
      * 上传图片
      */

+ 1 - 0
app/Distributor/Repositories/DistAppearance.php

@@ -23,6 +23,7 @@ class DistAppearance extends EloquentRepository
      * (如果原本就有模板与变量,不会重复生成)
      */
     public static function switchTheme($appearanceId,$distId) {
+        //把原始模板复制给分销商
         DistAppearanceTemplate::copyTemplateToDist($appearanceId, $distId);
         DistAppearanceVariable::copyAppearanceVariable($appearanceId, $distId);
         //发报到正式环境

+ 7 - 7
app/Distributor/Repositories/DistAppearancePublishList.php

@@ -19,11 +19,11 @@ class DistAppearancePublishList extends EloquentRepository
     /*
      * 更新发布版本
      */
-    public static function publishVersion()
+    public static function publishVersion($appearanceId,$distId)
     {
-        $info = DistAdminDistributor::getInfo();
-        $distId = $info->id;
-        $appearanceId = $info->appearance_id;
+//        $info = DistAdminDistributor::getInfo();
+//        $distId = $info->id;
+//        $appearanceId = $info->appearance_id;
 
         $model = new Model();
         $row = $model->where('appearance_id',$appearanceId)->where('dist_id',$distId)->first();
@@ -44,12 +44,12 @@ class DistAppearancePublishList extends EloquentRepository
      * 发布模版与变量
      */
     public static function publish($appearanceId,$distId) {
-        //同步模版
+        //同步模版到正式环境表
         DistAppearanceTemplate::syncAppearanceTemplates($appearanceId,$distId);
-        //同步变量
+        //同步变量到正式环境表
         DistAppearanceVariable::syncAppearanceVariables($appearanceId, $distId);
         //发布版本号
-        DistAppearancePublishList::publishVersion();
+        DistAppearancePublishList::publishVersion($appearanceId,$distId);
         return true;
     }
 }

+ 1 - 0
app/Distributor/routes.php

@@ -72,6 +72,7 @@ Route::group([
     $router->get('visual-editor', 'VisualEditorController@index');
     $router->post('visual-editor/upload', 'VisualEditorController@upload');
     $router->post('visual-editor/preview-save', 'VisualEditorController@previewSave');
+    $router->post('visual-editor/publish', 'VisualEditorController@publish');
 });
 
 /*

+ 151 - 65
public/vendor/grapes/grapes.init.js

@@ -1,3 +1,12 @@
+var faCogActive =  true;
+var faThLargeActive = false;
+var activeIcon = getVariable('active_icon');
+if (activeIcon) {
+    faCogActive = activeIcon === 'fa-cog'? true : false;
+    faThLargeActive = activeIcon === 'fa-th-large'? true : false;
+}
+
+
 const panelsConfig = {
     stylePrefix: 'pn-', // 面板的样式前缀
     defaults: [
@@ -53,7 +62,7 @@ const panelsConfig = {
                     id: 'open-tm',
                     className: 'fa fa-cog',
                     command: 'open-tm',
-                    active: true,
+                    active: faCogActive,
                     togglable: true,
                     attributes: { title: 'Settings' },
                 },
@@ -64,13 +73,14 @@ const panelsConfig = {
                 //     togglable: false,
                 //     attributes: { title: 'Open Layer Manager' },
                 // },
-                // {
-                //     id: 'open-blocks',
-                //     className: 'fa fa-th-large',
-                //     command: 'open-blocks',
-                //     togglable: false,
-                //     attributes: { title: 'Open Blocks' },
-                // },
+                {
+                    id: 'open-blocks',
+                    className: 'fa fa-th-large',
+                    command: 'open-blocks',
+                    active: faThLargeActive,
+                    togglable: true,
+                    attributes: { title: 'Open Blocks' },
+                },
             ],
         },
     ],
@@ -139,29 +149,45 @@ const editor = grapesjs.init({
         },
     },
 });
+//定义block块
+const bm = editor.Blocks; // `Blocks` is an alias of `BlockManager`
+bm.add('block_p', {
+    // Your block properties...
+    label: 'Paragraph',
+    media: `<svg style="width:20px;height:20px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M192 32l64 0 160 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0 0 352c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-352-32 0 0 352c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-96-32 0c-88.4 0-160-71.6-160-160s71.6-160 160-160z"/></svg>`,
+    content: '<p>insert your text here</p>',
+});
+bm.add('block_span', {
+    // Your block properties...
+    label: 'Text',
+    media: `<svg style="width:20px;height:20px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M32 32C14.3 32 0 46.3 0 64S14.3 96 32 96l128 0 0 352c0 17.7 14.3 32 32 32s32-14.3 32-32l0-352 128 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L192 32 32 32z"/></svg>`,
+    content: '<span>insert your text here</span>',
+});
+
+
+//图片管理器
 const am = editor.AssetManager;
 // 锁定整个画布
 editor.getWrapper().set({ locked: true });
-
 // 遍历所有组件并解锁具有edit属性的组件
-const edit_classes = ['mtb_edit']; // 定义需要解锁的组件类名
+// const edit_classes = ['mtb_edit']; // 定义需要解锁的组件类名
 editor.getComponents().each(component => {
     component.onAll(component => {
         //解销CLASS
-        let html = component.toHTML();
-        const classes = component.getClasses();
-        // 判断是否有交集
-        const hasIntersection = classes.some(item => {
-            // 如果是数组,则递归检查子数组
-            if (Array.isArray(item)) {
-                return item.some(subItem => edit_classes.includes(subItem));
-            }
-            // 如果是字符串,直接检查是否在 edit_classes 中
-            return edit_classes.includes(item);
-        });
+        // let html = component.toHTML();
+        // const classes = component.getClasses();
+        // // 判断是否有交集
+        // const hasIntersection = classes.some(item => {
+        //     // 如果是数组,则递归检查子数组
+        //     if (Array.isArray(item)) {
+        //         return item.some(subItem => edit_classes.includes(subItem));
+        //     }
+        //     // 如果是字符串,直接检查是否在 edit_classes 中
+        //     return edit_classes.includes(item);
+        // });
 
         // 如果有交集,解锁组件
-        if (hasIntersection || component.get('attributes').mtb_edit == 'true') {
+        if (component.get('attributes').mtb_edit == 'true') {
             component.set({ locked: false });
             setBorder(component);
         }
@@ -180,15 +206,13 @@ editor.getComponents().each(component => {
         if (component.get('attributes').mtb_edit_this == 'false') {
             component.set({ locked: true });
         }
-
     })
-
 });
 
 function setBorder(component) {
     if (component.attributes.tagName == 'a') {
         component.setStyle({
-            border: '3px dotted #f08300'
+            border: '2px dotted #f08300'
         });
     } else {
         component.setStyle({
@@ -197,24 +221,32 @@ function setBorder(component) {
     }
 }
 
-
 // 监听组件选择事件
 editor.on('component:selected', (comp) => {
     //去掉选中组件后的按钮
     const selectedComponent = editor.getSelected();
-    const defaultToolbar = selectedComponent.get('toolbar');
-    selectedComponent.set({
-        //  toolbar: [ ...defaultToolbar, {  attributes: {class: commandIcon}, command: commandToAdd}]
-        toolbar: [ ]
-    });
+    //const defaultToolbar = selectedComponent.get('toolbar');
+    //最外层不显示按钮
+    if (selectedComponent.get('attributes').mtb_edit == 'true' || selectedComponent.get('attributes').mtb_edit_this == 'true') {
+        selectedComponent.set({
+            toolbar: [
+            ]
+        });
+    } else {
+        selectedComponent.set({
+            //  toolbar: [ ...defaultToolbar, {  attributes: {class: commandIcon}, command: commandToAdd}]
+            toolbar: [{
+                attributes: { class: 'fa fa-clone' },
+                command: 'tlb-clone',
+                title: 'Clone',
+            }, {
+                    attributes: { class: 'fa fa-trash' },
+                    command: 'tlb-delete',
+                    title: 'Delete',
+            }]
+        });
+    }
 
-    //如果是连接与图片,显示右边的属性面板
-    // let tags = ['a', 'img'];
-    // const hasScheduleAttribute = selectedComponent.attributes;
-    // if (tags.includes(selectedComponent.attributes.tagName)) {
-    //     //editor.runCommand('open-tm');
-    // } else {
-    // }
 
     // 双击图片时弹出图片管理器
     if (selectedComponent.attributes.tagName == 'img') {
@@ -224,13 +256,10 @@ editor.on('component:selected', (comp) => {
         el.addEventListener('dblclick', () => {
             am.open({
                 types: ['image'], // This is the default option
-                // Without select, nothing will happen on asset selection
                 select(asset, complete) {
                     const selected = editor.getSelected();
                     if (selected) {
                         selected.addAttributes({ src: asset.getSrc() });
-                        // The default AssetManager UI will trigger `select(asset, false)`
-                        // on asset click and `select(asset, true)` on double-click
                         complete && am.close();
                     }
                 },
@@ -241,24 +270,11 @@ editor.on('component:selected', (comp) => {
 
 });
 
-var open_tm_show = true;
-var cogIcons = document.querySelectorAll('.fa-cog');
-cogIcons.forEach(function(icon) {
-    icon.addEventListener('click', function() {
-        // 在这里执行你想要的操作
-        if (open_tm_show) {
-            open_tm_show = false;
-        } else {
-            open_tm_show = true;
-        }
-        leftPanelDisplay(open_tm_show);
-    });
-});
 
+
+// 隐藏或显示左侧面板
 function leftPanelDisplay(isShow = true) {
     if (isShow) {
-        // canvasBg = document.querySelector('.gjs-cv-canvas');
-        // canvasBg.classList.remove('gjs-cv-canvas-bg');
         let elements1 = document.querySelectorAll('.gjs-pn-views-container');
         elements1.forEach(function(element) {
             element.style.display = 'block'; // 或者 'flex',取决于你希望如何显示这些元素
@@ -268,23 +284,23 @@ function leftPanelDisplay(isShow = true) {
             element.style = 'border_bottom: 2px solid var(--gjs-main-dark-color)';
         });
     } else {
-        // canvasBg = document.querySelector('.gjs-cv-canvas');
-        // canvasBg.classList.add('gjs-cv-canvas-bg');
         let elements1 = document.querySelectorAll('.gjs-pn-views-container');
         elements1.forEach(function(element) {
             element.style.display = 'none'; // 或者 'flex',取决于你希望如何显示这些元素
         });
         let elements2 = document.querySelectorAll('.gjs-pn-views');
         elements2.forEach(function(element) {
-            element.style = 'border:none';
+            element.style = 'border-bottom:none';
         });
     }
 }
 
 
-// 监听 load 事件
+// 监听编辑器加载完成事件
 editor.on('load', () => {
-    // 在这里执行你的初始化逻辑
+    //----------
+    // 下接菜单
+    //----------
     // 获取目标元素
     const targetElement = document.querySelector('.fa-arrows-alt');
     // 创建下拉选择框
@@ -321,8 +337,9 @@ editor.on('load', () => {
             window.location.href = redirectUrl;
         }
     });
-
-    //发布按钮
+    //----------
+    //发布
+    //----------
     customSelect = document.querySelector('.custom-select');
     // 创建 sendButton 按钮
     const sendButton = document.createElement('button');
@@ -333,12 +350,69 @@ editor.on('load', () => {
     sendButton.classList.add('layui-btn-radius');
     sendButton.style = 'margin-left:10px;height:25px;line-height:25px;padding:0 10px;vertical-align:unset;'; // 添加样式(可选)
     customSelect.insertAdjacentElement('afterend', sendButton);
+    //发布按钮点击事件
+    sendButton.addEventListener('click', function () {
+        // 显示loading
+        loadIndex = layer.load(0, {shade: [0.5, '#000']});
+        // 发送数据
+        $.ajax({
+            url: '/dist/visual-editor/publish',
+            type: 'POST',
+            headers: {
+                'X-CSRF-TOKEN': csrfToken,
+            },
+            data: {},
+            success: function (res) {
+                layer.close(loadIndex);
+                layer.msg(res.message,{shade: [0.5, '#000']});
+            },
+            error: function (err) {
+                layer.close(loadIndex);
+                layer.msg(err,{shade: [0.5, '#000']});
+            }
+        });
+    });
 
+    //----------
+    // 右侧面板显示隐藏
+    //----------
+    // 监听属性按钮点击事件
+    var gjsPnBtn = document.querySelectorAll('.gjs-pn-btn');
+    var headRightBtn = ['fa-cog', 'fa-th-large'];
+    gjsPnBtn.forEach(function(icon1) {
+        let hasIntersection = headRightBtn.some(function(btnClass) {
+            return icon1.classList.contains(btnClass);
+        });
+        if (hasIntersection) {
+            icon1.addEventListener('click', function() {
+                hasActive = false;
+                gjsPnBtn.forEach(function(icon) {
+                    if (icon.classList.contains('gjs-pn-active')) {
+                        hasActive = true;
+                        saveVariable('active_icon',icon.classList.item(2));
+                    }
+                });
+                if (hasActive) {
+                    leftPanelDisplay(true);
+                } else {
+                    leftPanelDisplay(false);
+                    saveVariable('active_icon','none');
+                }
+            });
+        }
+    });
+    //右侧面板显示隐藏
+    if (faCogActive === false && faThLargeActive === false) {
+        leftPanelDisplay(false)
+    }
+    //----------
     //关闭loading
+    //----------
     layer.close(loadIndex);
+
 });
 
-//更新时调用接口保存数据
+//保存模版内容
 editor.on('update', () => {
     const html = editor.getHtml();
     // 你可以在这里添加自定义逻辑
@@ -354,7 +428,7 @@ editor.on('update', () => {
         },
         success: function (res) {
             if (res.status != 1) {
-                alert('save error');
+                layer.msg('save data error, please try again later!',{shade: [0.5, '#000']});
             }
         },
         error: function (err) {
@@ -363,6 +437,7 @@ editor.on('update', () => {
     });
 });
 
+
 // 上传图片时触发loading效果
 editor.on('asset:upload:start', () => {
     loadIndex = layer.load(0, {shade: [0.5, '#000']});
@@ -378,3 +453,14 @@ layui.use('flow', function(){
     //当你执行这样一个方法时,即对页面中的全部带有 lay-src 的 img 元素开启了懒加载(当然你也可以指定相关 img)
     flow.lazyimg();
 });
+
+
+// 保存变量到LocalStorage
+function saveVariable(name, value) {
+    localStorage.setItem(name, JSON.stringify(value));
+}
+// 从LocalStorage读取变量
+function getVariable(name) {
+    const value = localStorage.getItem(name);
+    return value ? JSON.parse(value) : null;
+}