Open xinglie opened 3 years ago
该设计器可以嵌入到其他页面中的某个区域内,和现有系统更好的融合
为了保证显示的完整,嵌入的区块请保证至少宽960px
960px
首先在页面中,添加如下的html
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>
请注意上面html中id为app的节点,到时候设计器会渲染在该节点里。为了更好的体验,你可以在app这个节点里添加一些loading动画,设计器加载完渲染时,会清除app节点下的内容。
id
app
loading
完整示例源码请查看:https://github.com/xinglie/report-designer/blob/master/mini.html 在线demo示例请查看:https://xinglie.github.io/report-designer/mini.html
在设计器中默认的行为中,设计器会提供自身的一些交互方案,比如顶部元素的展示、面板的拖动处理等。
在这些交互的原则中,其中之一就是严格控制设计器显示的内容在指定的区域内,比如面板不能拖动到指定的DOM元素外边来。这是因为宿主页面的界面和交互是未知的,我们不能干扰和影响宿主页面的展示行为。
DOM
如果要对迷你模式进行近一步的界面控制,可以使用拆分再重新组织界面的方式。
完整示例源码请查看:https://github.com/xinglie/report-designer/blob/master/split.html 在线demo示例请查看:https://xinglie.github.io/report-designer/split.html
以下讲解下代码和思路
首先组织自己想要展示的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同理。
工具栏
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' } });
我们需要指定split为true,否则会有一些样式上的差异。
split
true
同时我们配置了panels、header、toolbar等选项,选项中to指定设计器中的相应界面展示到哪个外部节点里,hidden指示是否隐藏该界面,比如我想隐藏header又不想把它渲染到外部其它节点中,则可以这样配置
panels
to
hidden
designer.setup({ rootId:'app', header:{ hidden:true }, });
这样设计器自身将不再显示顶部元素栏
后续可以把设计器中的界面拿到外部来展示,进行一定的控制。如果界面改动量比较大,无法通过样式控制完成时,则需要自己二次开发组织相应的界面来供使用。
以顶部元素点击、拖动添加为例,默认是这样展示的。
我们如果把它拿到外部其它节点里渲染时,可能不符合交互要求,此时我们可以开发相同功能的面板,即把顶部元素放到一个面板里重新组织展示。
设计器提供了一个元素面板,在面板里展示顶部元素并完成相应的功能,设计器已经自带了该示例,目前界面如下
元素面板
元素面板里面的行为与顶部在功能上一模一样。
后续则可以利用前面提到的,把设计器中的界面拿到外部使用,渲染元素面板到外部节点里。
看过代码你会清楚,像这样功能保持不变,对界面重新组织的需求,只需要继承原来已经实现的View,在html里重新组织下界面就好。
View
这样我们就可以对设计器进行一定的外部控制,同时可以保护好内部的功能和界面。
/** * 面板配置项 */ interface OuterPanelsConfig { /** * 结构树面板 */ tree?: OuterViewConfig /** * 属性面板 */ props?: OuterViewConfig /** * 概览图面板 */ outline?: OuterViewConfig /** * 历史记录面板 */ record?: OuterViewConfig /** * 数据源面板 */ data?: OuterViewConfig /** * 元素面板 */ element?: OuterViewConfig /** * 调试面板 */ debug?: OuterViewConfig, /** * 动画面板 */ animate?: OuterViewConfig /** * 草稿 */ draft?: OuterViewConfig /** * 资源 */ resource?:OuterViewConfig }
https://github.com/thx/magix-composer 打包工具会对设计器中使用到的样式做全局编译,且编译时可对生成的选择器做规则定制,比如可以对所有选择器统一添加一个report-desinger的前缀,这样只要你项目中没有以report-desinger开头的样式则不会冲突。
report-desinger
目前使用less管理相关的界面皮肤,主题已支持css3 变量,所以界面风格可以定制修改,与你当前系统页面相匹配
设计器本身也是采用区块化开发的方式,把一个个界面区块独立开发,然后在入口页面对它们统一布局管理。这样当后期需要对它们重新布局或更新功能时非常方便。
其他像面板,顶部元素面板均可以去掉或定制到其它dom节点内,以和现有的页面更好的整合。
封装designer.tsx文件 import React from 'react'; let idCounter = 0; let scriptSource = '//localhost/report-designer/dist/designer.js';
封装designer.tsx文件
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}
> 使用`designer.tsx`文件 ```js import Designer from './designer.tsx'; //... return ( <> <Designer style={{position:'relative',width:900,height:400}}/> </>)
在vue项目中新建Designer.vue文件 Designer.vue中的内容如下
在vue项目中新建Designer.vue文件
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>
<!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销毁
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>
大佬,数据源可以绑定 input 吗
可以,配置一下就好了
迷你模式
为了保证显示的完整,嵌入的区块请保证至少宽
960px
首先在页面中,添加如下的
html
请注意上面
html
中id
为app
的节点,到时候设计器会渲染在该节点里。为了更好的体验,你可以在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
结构,如下示例我们把设计器中
工具栏
渲染到外部指定的toolbar
这个节点上,header
同理。在设计器初始化的代码里,我们可以看到如下的代码
我们需要指定
split
为true
,否则会有一些样式上的差异。同时我们配置了
panels
、header
、toolbar
等选项,选项中to
指定设计器中的相应界面展示到哪个外部节点里,hidden
指示是否隐藏该界面,比如我想隐藏header
又不想把它渲染到外部其它节点中,则可以这样配置这样设计器自身将不再显示顶部元素栏
后续可以把设计器中的界面拿到外部来展示,进行一定的控制。如果界面改动量比较大,无法通过样式控制完成时,则需要自己二次开发组织相应的界面来供使用。
以顶部元素点击、拖动添加为例,默认是这样展示的。
我们如果把它拿到外部其它节点里渲染时,可能不符合交互要求,此时我们可以开发相同功能的面板,即把顶部元素放到一个面板里重新组织展示。
设计器提供了一个
元素面板
,在面板里展示顶部元素并完成相应的功能,设计器已经自带了该示例,目前界面如下元素面板
里面的行为与顶部在功能上一模一样。后续则可以利用前面提到的,把设计器中的界面拿到外部使用,渲染
元素面板
到外部节点里。看过代码你会清楚,像这样功能保持不变,对界面重新组织的需求,只需要继承原来已经实现的
View
,在html
里重新组织下界面就好。这样我们就可以对设计器进行一定的外部控制,同时可以保护好内部的功能和界面。
setup中,panels配置的面板名称都有哪些?
技术点
如何保证js代码与现有页面中的不冲突?
如何保证样式代码不冲突?
https://github.com/thx/magix-composer 打包工具会对设计器中使用到的样式做全局编译,且编译时可对生成的选择器做规则定制,比如可以对所有选择器统一添加一个
report-desinger
的前缀,这样只要你项目中没有以report-desinger
开头的样式则不会冲突。风格统一
目前使用less管理相关的界面皮肤,主题已支持css3 变量,所以界面风格可以定制修改,与你当前系统页面相匹配
界面定制
设计器本身也是采用区块化开发的方式,把一个个界面区块独立开发,然后在入口页面对它们统一布局管理。这样当后期需要对它们重新布局或更新功能时非常方便。
其他像面板,顶部元素面板均可以去掉或定制到其它dom节点内,以和现有的页面更好的整合。
其它技术方案插件
React插件
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}
vue3插件
使用
vue插件2
通用方案
也可以像
mini.html
那样,在入口页面引入designer.js
,如,然后在合适的时候,通过调用designer.setup
安装和designer.destroy
销毁