Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

迷你模式、自定义排版及设计器插件 #55

Open
xinglie opened this issue Sep 26, 2021 · 2 comments
Open

迷你模式、自定义排版及设计器插件 #55

xinglie opened this issue Sep 26, 2021 · 2 comments
Labels
技术方案 介绍项目中好的技术点

Comments

@xinglie
Copy link
Owner

xinglie commented Sep 26, 2021

迷你模式

该设计器可以嵌入到其他页面中的某个区域内,和现有系统更好的融合

为了保证显示的完整,嵌入的区块请保证至少宽960px

首先在页面中,添加如下的html

<div>
    some other text
</div>
<div style="display:flex;">
    <div style="width:200px">
        left
    </div>
    <div id="app" class="app" style="width:1000px;height:600px;border:solid 1px #ccc;position: relative;overflow: hidden;">
        <div class="outer"><div class="inner"></div></div>
    </div>
    <div>
        right
    </div>
</div>
<div>
    <input placeholder="外部输入框测试" />
     bottom text
</div>

请注意上面htmlidapp的节点,到时候设计器会渲染在该节点里。为了更好的体验,你可以在app这个节点里添加一些loading动画,设计器加载完渲染时,会清除app节点下的内容。

完整示例源码请查看:https://github.com/xinglie/report-designer/blob/master/mini.html
在线demo示例请查看:https://xinglie.github.io/report-designer/mini.html

自定义排版

在设计器中默认的行为中,设计器会提供自身的一些交互方案,比如顶部元素的展示、面板的拖动处理等。

在这些交互的原则中,其中之一就是严格控制设计器显示的内容在指定的区域内,比如面板不能拖动到指定的DOM元素外边来。这是因为宿主页面的界面和交互是未知的,我们不能干扰和影响宿主页面的展示行为。

如果要对迷你模式进行近一步的界面控制,可以使用拆分再重新组织界面的方式。

完整示例源码请查看:https://github.com/xinglie/report-designer/blob/master/split.html
在线demo示例请查看:https://xinglie.github.io/report-designer/split.html

以下讲解下代码和思路

首先组织自己想要展示的HTML结构,如下示例

<div id="toolbar">

</div>
<div id="header">

</div>
<div style="display:flex;">
    <div style="width:150px" id="left">
        left
    </div>
    <div id="app" class="app" style="min-width:1100px;max-width:1100px;height:600px;border:solid 1px #ccc;position: relative;overflow: hidden;">
        <div style="display: flex;align-items: center;justify-content: center; height: 100%;">
            这里可以放一个加载动画...
        </div>
    </div>
    <div id="right">
        right
    </div>
</div>

我们把设计器中工具栏渲染到外部指定的toolbar这个节点上,header同理。

在设计器初始化的代码里,我们可以看到如下的代码

designer.setup({
    rootId:'app',
    mini:true,
    panels:{
        element:{
            to:'left'
        },
        data:{
            to:'right'
        }
    },
    header:{
        to:'header',
        hidden:false
    },
    toolbar:{
        hidden:false,
        to:'toolbar'
    }
});

我们需要指定splittrue,否则会有一些样式上的差异。

同时我们配置了panelsheadertoolbar等选项,选项中to指定设计器中的相应界面展示到哪个外部节点里,hidden指示是否隐藏该界面,比如我想隐藏header又不想把它渲染到外部其它节点中,则可以这样配置

designer.setup({
    rootId:'app',
    header:{
        hidden:true
    },
});

这样设计器自身将不再显示顶部元素栏

后续可以把设计器中的界面拿到外部来展示,进行一定的控制。如果界面改动量比较大,无法通过样式控制完成时,则需要自己二次开发组织相应的界面来供使用。

以顶部元素点击、拖动添加为例,默认是这样展示的。

image

我们如果把它拿到外部其它节点里渲染时,可能不符合交互要求,此时我们可以开发相同功能的面板,即把顶部元素放到一个面板里重新组织展示。

设计器提供了一个元素面板,在面板里展示顶部元素并完成相应的功能,设计器已经自带了该示例,目前界面如下

image

元素面板里面的行为与顶部在功能上一模一样。

后续则可以利用前面提到的,把设计器中的界面拿到外部使用,渲染元素面板到外部节点里。

看过代码你会清楚,像这样功能保持不变,对界面重新组织的需求,只需要继承原来已经实现的View,在html里重新组织下界面就好。

这样我们就可以对设计器进行一定的外部控制,同时可以保护好内部的功能和界面。

setup中,panels配置的面板名称都有哪些?

/**
 * 面板配置项
 */
interface OuterPanelsConfig {
    /**
     * 结构树面板
     */
    tree?: OuterViewConfig
    /**
     * 属性面板
     */
    props?: OuterViewConfig
    /**
     * 概览图面板
     */
    outline?: OuterViewConfig
    /**
     * 历史记录面板
     */
    record?: OuterViewConfig
    /**
     * 数据源面板
     */
    data?: OuterViewConfig
    /**
     * 元素面板
     */
    element?: OuterViewConfig
    /**
     * 调试面板
     */
    debug?: OuterViewConfig,
    /**
     * 动画面板
     */
    animate?: OuterViewConfig
    /**
     * 草稿
     */
    draft?: OuterViewConfig
    /**
     * 资源
     */
    resource?:OuterViewConfig
}

技术点

如何保证js代码与现有页面中的不冲突?

  1. 代码全部采用模块化的方式,设计器本身不向全局挂载和读取变量,只在入口提供designer对象供初始化配置,所以不会冲突
  2. 模块化目前使用seajs的加载器。如果你现有的加载器与seajs的define冲突,可自行修改打包配置,换成如require等其它加载器,目前支持amd cmd iife等加载器规范

如何保证样式代码不冲突?

https://github.com/thx/magix-composer 打包工具会对设计器中使用到的样式做全局编译,且编译时可对生成的选择器做规则定制,比如可以对所有选择器统一添加一个report-desinger的前缀,这样只要你项目中没有以report-desinger开头的样式则不会冲突。

风格统一

目前使用less管理相关的界面皮肤,主题已支持css3 变量,所以界面风格可以定制修改,与你当前系统页面相匹配

界面定制

设计器本身也是采用区块化开发的方式,把一个个界面区块独立开发,然后在入口页面对它们统一布局管理。这样当后期需要对它们重新布局或更新功能时非常方便。

其他像面板,顶部元素面板均可以去掉或定制到其它dom节点内,以和现有的页面更好的整合。

其它技术方案插件

React插件

封装designer.tsx文件

import React from 'react';
let idCounter = 0;
let scriptSource = '//localhost/report-designer/dist/designer.js';

let promisePools = {};
let LoadScript = url => {
    let key = 'script_' + url;
    let p = promisePools[key];
    if (!p) {
        p = promisePools[key] = new Promise(resolve => {
            let script = document.createElement('script');
            script.onload = script.onerror = () => {
                script.parentNode.removeChild(script);
                setTimeout(resolve, 20);
            };
            script.src = url;
            document.body.append(script);
        });
    }
    return p;
};
export default class extends React.Component {
    constructor() {
        super();
        this.state = {
            nodeId: 'designer_' + idCounter++
        };
    }
    async updateView() {
        await LoadScript(scriptSource);
        if (window.designer) {
            window.designer.setup({
                rootId: this.state.nodeId,
                mini:true
            });
        }
    }
    componentDidMount() {
        this.updateView();
    }
    componentWillUnmount() {
        if (window.designer) {
            window.designer.destroy();
        }
    }
    render() {
        return (<div id={this.state.nodeId} {...this.props}>{this.props.children}</div>)
    }
}

使用designer.tsx文件

import Designer from './designer.tsx';
//...

return (
        <>
          <Designer style={{position:'relative',width:900,height:400}}/>
    </>)

vue3插件

vue项目中新建Designer.vue文件

Designer.vue中的内容如下

<template>
    <div :id="designerId"></div>
</template>

<script lang="ts">
    import { defineComponent, onMounted, onBeforeUnmount } from 'vue'
    let idCounter = 0;
    let scriptSource = '//localhost/report-designer/dist/designer.js';

    let promisePools = {};
    let LoadScript = url => {
        let key = 'script_' + url;
        let p = promisePools[key];
        if (!p) {
            p = promisePools[key] = new Promise(resolve => {
                let script = document.createElement('script');
                script.onload = script.onerror = () => {
                    script.parentNode.removeChild(script);
                    setTimeout(resolve, 20);
                };
                script.src = url;
                document.body.append(script);
            });
        }
        return p;
    };
    export default defineComponent({
        setup() {
            let designerId = 'report-desinger-' + (idCounter++);
            onBeforeUnmount(() => {
                if (window.designer) {
                    window.designer.destroy();
                }
            });

            onMounted(async () => {
                await LoadScript(scriptSource);
                if (window.designer) {
                    window.designer.setup({
                        rootId: designerId,
                        mini: true
                    });
                }
            });
            return {
                designerId
            }
        },
    })
</script>

使用

<script setup>
import Designer from './path/to/Designer.vue'
</script>

<template>
    <Designer style="width:1000px;height:80vh"/>
</template>

vue插件2

<!doctype html>
<html>

<head>
  <meta charset="utf-8">
  <title>测试</title>
  <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
  <div id="app"></div>
</body>

</html>

<script>
  let ReportDesigner = () => {
    let idCounter = 0;
    let scriptSource = '//localhost/report-designer/dist/designer.js';
    let promisePools = {};
    let LoadScript = url => {
      let key = 'script_' + url;
      let p = promisePools[key];
      if (!p) {
        p = promisePools[key] = new Promise(resolve => {
          let script = document.createElement('script');
          script.onload = script.onerror = () => {
            script.parentNode.removeChild(script);
            setTimeout(resolve, 20);
          };
          script.src = url;
          document.body.append(script);
        });
      }
      return p;
    };
    return {
      data() {
        return {
          designerId: 'rd-' + idCounter++
        }
      },
      async mounted() {
        await LoadScript(scriptSource);
        if (window.designer) {
          window.designer.setup({
            rootId: this.designerId,
            mini: true
          });
        }
      },
      beforeUnmount() {
        if (window.designer) {
          window.designer.destroy();
        }
      },
      render() {
        return Vue.h('div', { id: this.designerId,style:'width:980px;height:400px' });
      }
    }
  };
  Vue.createApp(ReportDesigner()).mount('#app');
</script>

通用方案

也可以像mini.html那样,在入口页面引入designer.js,如,然后在合适的时候,通过调用designer.setup安装和designer.destroy销毁

<!DOCTYPE html>
<html>
<head>
	<title>report designer</title>
   <!--这里引入打包后的文件-->
	<script src="//localhost/report-designer/dist/designer.js"></script>
</head>
<body>
	<!--可以用样式进行修饰设计器的外轮廓-->
	<div id="rd" style="width:1000px;height:80vh"></div>
</body>
</html>
<script>
    //此时widnow上存在designer对象,可以调用designer.setup进行安装
	designer.setup({
		rootId:'rd',//设计器展示在哪个节点里
		mini:true//迷你模式,精简界面
	});
	//在合适的时候进行销毁,这里模拟10s后销毁
	setTimeout(()=>{
		designer.destroy();
	},10000);
</script>
@xinglie xinglie added the 技术方案 介绍项目中好的技术点 label Sep 26, 2021
@xinglie xinglie changed the title 拆分、组织设计器 拆分、重组设计器 Sep 30, 2021
@a847704969
Copy link

大佬,数据源可以绑定 input 吗

@xinglie
Copy link
Owner Author

xinglie commented Dec 31, 2021

大佬,数据源可以绑定 input 吗

可以,配置一下就好了

@xinglie xinglie changed the title 拆分、重组设计器 迷你模式、自定义排版及设计器插件 Jun 29, 2022
@xinglie xinglie pinned this issue Jun 9, 2023
Repository owner deleted a comment from xuyangshuaige Oct 9, 2023
Repository owner deleted a comment from xuyangshuaige Oct 9, 2023
Repository owner deleted a comment from xuyangshuaige Oct 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
技术方案 介绍项目中好的技术点
Projects
None yet
Development

No branches or pull requests

2 participants