ThingJS平台怎样实现实时3D可视化的?

1,772 阅读16分钟

ThingJS 是一个先进的 PaaS 开发平台,开发者可以方便、安全地基于云端的各种工具组件随时随地进行开发。

ThingJS 界面概述 为了便于开发者在ThingJS下进行界面开发,ThingJS 提供的界面体系结构目录如下:

上述ThingJS界面体系中,进行3D场景可视化的区域,我们定义为3D容器,如下图所示:

在3D“容器”内 提供了3D和2D的界面展示能力,如下图所示:

3D 界面

Marker:可以将图标、Canvas绘制的图片,展现在3D场景中或绑定在3D物体上。 WebView:可以将页面嵌入到3D场景中。 2D 界面

原生界面:用户可以使用js代码编写原生的界面,将dom元素插入到相应的节点中。 快捷界面库:内置各种组件模块,供用户进行拼接组装使用。 UIAnchor:可以将普通的2D界面“挂接”到某个3D物体对象上,使之随物体移动。 在3D“容器”外 提供通栏组件(如上通栏、侧通栏)。如果用户想基于 ThingJS 做一套独立的应用系统,可使用通栏组件作为系统级别的菜单。

3D 界面 ThingJS 主要提供 Marker 物体和 WebView 物体以支持 3D 空间界面。

Marker 物体

Marker 物体可以添加一个图片放置到你希望的位置,也可以将这个图片作为孩子添加到对象身上,随着对象一同移动。

例子 1:

app.create({ type: "Marker", offset: [0, 2, 0], size: [4, 4], url: "thingjs.com/static/imag…", parent: app.query("car01")[0] });

参数:

type : 通知系统创建 Marker 物体; offset : 设置自身坐标系下偏移量为[0, 2, 0]; size : 设置 Marker 物体大小,也可以添单独数字如 4,等同于[4,4],大小是以米计算的; url : 图片的 url; parent :指定 Marker 的父物体; 运行结果见下图。Marker 默认是受距离远近影响,呈现近大远小的 3D 效果,也会在 3D 空间中实现前后遮挡。

例子 2:

app.create({ type: "Marker", offset: [0, 8, 0], size: 2, keepSize: true, url: "thingjs.com/static/imag…", parent: app.query(".Building")[1] });

参数:

keepSize: 控制是否受距离远近影响,呈现近大远小的 3D 效果。如果设置 true,表示保持大小,不随距离近大远小,此时 size 的单位是屏幕的像素点; offset : 设置自身坐标系下偏移量为[0, 2, 0]; size : 设置 Marker 物体大小,也可以添单独数字如 4,等同于[4,4],大小是以米计算的; url : 图片的 url; parent :指定 Marker 的父物体; 运行结果见下图:

我们还可以使用 h5 的 canvas 手动创建动态图。

例子 3:

function createTextCanvas(text, canvas) { if (!canvas) { canvas = document.createElement("canvas"); canvas.width = 64; canvas.height = 64; }

const ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(32, 32, 256)";
ctx.beginPath();
ctx.arc(32, 32, 30, 0, Math.PI * 2);
ctx.fill();

ctx.strokeStyle = "rgb(255, 255, 255)";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(32, 32, 30, 0, Math.PI * 2);
ctx.stroke();

ctx.fillStyle = "rgb(255, 255, 255)";
ctx.font = "32px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(text, 32, 32);
return canvas;

}

app.on('load', function (ev) { var marker = app.create({ type: "Marker", offset: [0, 2, 0], size: 3, canvas: createTextCanvas('100'), parent: app.query('car02')[0] }).on('click', function (ev) { var txt = Math.floor(Math.random() * 100); ev.object.canvas = createTextCanvas(txt, ev.object.canvas) }) })

参数:

canvas: 接收 canvas 作为贴图显示 运行结果见下图,在 Marker 上点击时,会改变标记上的数字:

查看示例 WebView 物体 我们可以使用 WebView 物体,将其他网站或者页面的内容嵌到 3D 中。

例子 4:

var webView01 = app.create({ type: 'WebView', url: 'www.thingjs.com', position: [10, 13, -5], width: 1920 * 0.01, // 3D 中实际宽度 单位 米 height: 1080 * 0.01, // 3D 中实际高度 单位 米 domWidth: 1920, // 页面宽度 单位 px domHeight: 1080// 页面高度 单位 px });

查看示例 2D html 界面 JS 编写原生界面 var template = `

Hello World!

Into Level
`;// 插入到 ThingJS 内置的 2D 界面 div 中$('#div2d').append($(template)); 查看示例

ThingJS 为了让大家快速编写界面,我们提供一个“快捷界面库”,可快速创建界面。

UIAnchor 还有一个神奇的功能,即使是 2D html 界面,我们照样可以把它连接到 3D 物体上,跟随 3D 物体移动,我们使用 UIAnchor 物体来实现这个功能。

示例如下:

var uiAnchor = app.create({ type: "UIAnchor", parent: app.query("car02")[0], element: document.getElementById("XXXX"), localPosition: [0, 2, 0], pivot: [0.5, 1] });

参数:

element :要绑定的页面的 element 对象 pivot :指定页面的哪个点放到 localPosition 位置上,0.5 相当于 50% 查看示例

删除UIAnchor方法为:

uiAnchor.destroy();

注意事项: 删除后,其对应的 panel 也会被删除

显示和隐藏UIAnchor方法为:

uiAnchor.visible = true / false;

通过 js 编写的界面:

可以利用 UIAnchor 连接到 3D 物体上。

查看示例 也可以通过快捷界面库,创建 Panel 以 UIAnchor 的方式连接到物体上。

查看示例

快捷界面库 THING.widget 是一个支持动态数据绑定的轻量级界面库。

可通过界面库中的 Panel 组件创建一个面板,并可向该面板中添加文本、数字、单选框、复选框等其他组件。

效果如下:

创建面板 var panel = new THING.widget.Panel({ // 设置面板样式 template: 'default', // 角标样式 cornerType: "none", // 设置面板宽度 width: "300px", // 是否有标题 hasTitle: true, // 设置标题名称 titleText: "我是标题", // 面板是否允许有关闭按钮 closeIcon: true, // 面板是否支持拖拽功能 dragable: true, // 面板是否支持收起功能 retractable: true, // 设置透明度 opacity: 0.9, // 设置层级 zIndex: 99 });

width: 如果写百分比字符串则表示相对宽度(相对于3D容器的宽度) template:目前,模板样式提供两个样式 default 和 default2,如下图:

cornerType: cornerType 是指角标样式,依次是:没有角标 none ,没有线的角标 noline ,折线角标 polyline ;依次见下图:

注意事项: 角标样式都不区分大小写

如果 panel 面板设置了关闭按钮 则点击关闭按钮时 会将面板设置为隐藏,如需再次打开该面板 则调用 panel.visible = true; 显示面板即可。

查看示例 面板属性及方法介绍 // 获取面板标签 panel.domElement;

// 修改面板标题 panel.titleText='修改标题';

// 设置/获取面板相关属性 panel.visible = true / false; panel.position = [10, 10];//设置panel面板的位置 panel.zIndex = 9; panel.opacity = 0.5;

// 删除面板 panel.destroy();

面板事件 // 常用事件类型均支持 panel.on("click", callback); // 'close'事件为面板关闭时触发 panel.on("close", callback);

面板中的数据 可通过各组件实现双向绑定 var dataObj = { pressure: "0.14MPa", temperature: "21°C", checkbox: { 设备1: false, 设备2: false, 设备3: true, 设备4: true }, radio: "摄像头01", open1: true, height: 10, maxSize: 1.0, iframe: "www.3dmomoda.com", progress: 1, img: "www.thingjs.com/guide/image…", button: false };

逐条添加组件 var press = panel.addString(dataObj, 'pressure').caption('水压').isChangeValue(true); var height = panel.addNumber(dataObj, 'height').caption('高度'); var maxSize = panel.addNumberSlider(dataObj, 'maxSize').step(0.25).min(1).max(10); var open1 = panel.addBoolean(dataObj, 'open1').caption('开关01'); var radio = panel.addRadio(dataObj, 'radio', ['摄像头01', '摄像头02']); var check = panel.addCheckbox(dataObj, 'checkbox').caption({ "设备2": "设备2(rename)" }); var iframe = panel.addIframe(dataObj, 'iframe').caption('视屏'); var img = panel.addIframe(dataObj, 'img').caption('图片'); var button = panel.addImageBoolean(dataObj, 'button').caption('仓库编号').url('www.thingjs.com/static/imag…');

界面库可设置 Caption 的字体颜色:如下例所示:

var press = panel.addString(dataObj, 'pressure').caption("水压").isChangeValue(true); var water = panel.addString(dataObj, 'temperature').caption("水温").isChangeValue(true); var check = panel.addCheckbox(dataObj, 'checkbox').caption({ "设备2": "设备2(rename)" }); var open1 = panel.addBoolean(dataObj, 'open1').caption("开关01"); var height = panel.addNumber(dataObj, 'height').caption("高度"); var maxSize = panel.addNumberSlider(dataObj, 'maxSize').caption("maxSize").step(0.25).min(1).max(10); var iframe = panel.addIframe(dataObj, 'iframe').caption('视屏');

效果如下所示:

删除组件 panel.remove(press); panel.remove(radio); ......

组件方法介绍 组件通用方法:

// 设置名称 element.caption("水压"); // 设置值是否允许改变 element.isChangeValue(true); // 设置显隐 element.show(true);

NumberSlider 组件还有如下方法:

// 设置滑动器步幅 element.step(0.25); // 设置滑动器最小值 element.min(-10); // 设置滑动器最大值 element.max(10); // 将绝对数值转为百分比 element.isPercentage(true);

Iframe 组件还有如下方法:

// 设置iframe高度 element.setHeight("300px");

组件事件 change 事件

press.on("change", function(ev) { console.log(ev); }); //特别注意 checkBox 返回的是数组。所以 是check[0], 其他都是对象 check[0].on("change", function(ev) { console.log(ev); });

快捷界面库示例 这里我们将列举几个界面的示例,供大家参考学习。

图标按钮面板 创建图片按钮面板:

var toolbar = new THING.widget.Panel({ width: "163px"});

注意事项: 图片按钮面板可设置两种标题的展示方式,通过 captionPos 进行设置,默认显示在图片下方,也可以设置成鼠标滑入效果。

// 设置为鼠标 hover 效果: var toolbar = new THING.widget.Panel({ width: "163px",captionPos:'hover'});

如下图所示:

面板中的数据:

//绑定物体 var obj = { warehouseCode: false, temperature: false, humidity: false, statistics: false, status: false, insect: false, cerealsReserve: false, video: false, cloud: true, orientation: true };

添加按钮并引入字体图标文件:

var button0 = toolbar.addImageBoolean(dataObj, 'warehouseCode').caption('仓库编号').url('#momoda_lc-icontubiao10'); var button1 = toolbar.addImageBoolean(dataObj, 'temperature').caption('温度检测').url('#momoda_lc-icontubiao2'); var button2 = toolbar.addImageBoolean(dataObj, 'humidity').caption('湿度检测').url('#momoda_lc-icontubiao5') var button3 = toolbar.addImageBoolean(dataObj, 'statistics').caption('能耗统计').url('#momoda_lc-icontubiao20'); var button4 = toolbar.addImageBoolean(dataObj, 'status').caption('保粮状态').url('#momoda_lc-icontubiao'); var button5 = toolbar.addImageBoolean(dataObj, 'insect').caption('虫害').url('#momoda_lc-icontubiao11'); var button6 = toolbar.addImageBoolean(dataObj, 'cerealsReserve').caption('粮食储存').url('#momoda_lc-icontubiao21'); var button7 = toolbar.addImageBoolean(dataObj, 'video').caption('视屏监控').url('#momoda_lc-icontubiao9'); var button8 = toolbar.addImageBoolean(dataObj, 'cloud').caption('温度云图').url('#momoda_lc-icontubiao16'); var button9 = toolbar.addImageBoolean(dataObj, 'orientation').caption('人车定位').url('#momoda_lc-icontubiao10');

按钮添加注册事件:

button0.on('change', function (state) { // boolean 类型 返回按钮是否是点击状态 console.log(state); });

面板效果图:

查看示例 Tab 面板 创建图片按钮面板:

var panel = THING.widget.Panel({ template: "default", hasTitle: true, titleText: "粮仓信息", closeIcon: true, dragable: true, retractable: true, width: "380px" });

面板中的数据:

var obj = { '基本信息信息': { '品种': "小麦", '库存数量': "6100", '保管员': "张三", '入库时间': "19:02", '用电量': "100", '单仓核算': "无" }, '粮情信息': { '仓房温度': "26", '粮食温度': "22" }, '报警信息': { '温度': "22", '火灾': "无", '虫害': "无" }, '添加组件': { } };

数据通过 addTab 添加到面板中:

panel.addTab(obj);

通过 link 添加到 Tab 的某个标签页中:

// 加载复选框组件 var check = panel.addCheckbox(dataObj, 'checkbox').caption({ "设备2": "设备2(rename)" }).link("添加组件"); // 复选框需逐个添加change事件 check[0].on('change', function(ev) { console.log(ev); }); check[1].on('change', function(ev) { console.log(ev); }) // 加载单选框组件 var radio = panel.addRadio(dataObj, 'radio', ['摄像头01', '摄像头02']).link("添加组件"); radio.on('change', function(ev) { console.log(ev); }) // 加载开关组件(适用于Boolean类型数据) var open1 = panel.addBoolean(dataObj, 'open').caption('开关01').link("添加组件"); open1.on('change', function(ev) { console.log(ev); })

// 加载数字型进度条组件 var numberSlider = panel.addNumberSlider(dataObj, 'value').step(0.5).min(1).max(10).isChangeValue(true).link("添加组件"); numberSlider.on('change', function(ev) { console.log(ev); })

// 加载iframe组件 var iframe = panel.addIframe(dataObj, 'iframe').caption('视频').link("添加组件"); // 设置iframe高度 iframe.setHeight("400px");

tab 面板效果图:

查看示例 进度条 进度条这里主要有两种:

数值型进度条 创建Panel:

var panel = new THING.widget.Panel({ titleText: "数值型进度条", width: '400px', hasTitle: true });

添加进度条并绑定数据:

panel.position = [10, 10]; panel.addNumberSlider(dataObj, '海拔').step(1).min(0).max(123); panel.addNumberSlider(dataObj, '气温').step(1).min(-20).max(40).isChangeValue(true).on('change', function (value) { console.log('气温 ' + value); }); panel.addNumberSlider(dataObj, '人口数量').step(1).min(0).max(123).isChangeValue(true); panel.addNumberSlider(dataObj, '人口比例').step(1).min(0).max(123).isChangeValue(true).isPercentage(true);

参数如下:

isChangeValue 可与进度条交互滑动; isPercentage 将绝对数值转为百分比。 数值型进度条效果图:

导航类进度条 创建Panel:

var panel2 = new THING.widget.Panel({ titleText: "导航型进度条", width: '400px', hasTitle: true });

添加进度条并绑定数据:

var progress = bar.addProgress(dataObj, 'progress', [ { name: '2号楼', describe: '教学楼' }, { name: '3号楼', describe: '实验楼' }, { name: '5号楼', describe: '室内篮球场' }, { name: '餐厅', describe: '五星级' }, { name: '大讲堂', describe: '开讲了' } ]);

进度条变化:

progress.on('change', function (id) { console.log(id); });

进度条单位播放时间:

progress.time(3000) // 单位为毫秒,默认为5000ms

更新:

setInterval(function () { if (dataObj.progress >= 4) dataObj.progress = 0; else dataObj.progress++; }, 1000);

导航型进度条效果图:

查看示例 iframe 引用页面 在“快捷界面库”一章中,讲到了创建一个 Panel 后,再创建一个 iframe 组件,可以把外部页面资源引用进来,示例如下:

// 界面组件 var panel = new THING.widget.Panel({ 'width':'400px' });

// 创建数据对象 var dataObj = { 'iframe': 'forum.thingjs.com/' };

var iframe = panel.addIframe(dataObj, 'iframe').caption('iframe'); // 设置 iframe 高度 iframe.setHeight('800px'); // 通过 domElement 获取包裹 iframe 的 div 从而获取真正的 iframe Dom节点 var iframeDom = iframe.domElement.getElementsByTagName('iframe')[0]; // 然后可以用原始的 iframe 相关属性 和 方法 进行开发 // 比如 设置是否有滚动条 //iframeDom.scrolling = "auto";

借助此能力,用户可以把自己开发的页面嵌入到 ThingJS 中,以便更灵活的进行页面开发。

此方式引入的 iframe 不能用于 创建 UIAnchor ,仅作为普通页面元素,嵌入集成到 ThingJS 中。

如果要进行 iframe 与 ThingJS 在线开发环境页面之间的通信,那么也需要分“同域”和“跨域”两种情况进行讨论。

同域 iframe 通信 对于从 ThingJS 网站中上传的页面,与 ThingJS 在线开发环境 属于同域。

对于这种情况,在 ThingJS 开发环境中,得到引用的 iframe Dom 节点后,直接通过 contentWindow 获取并调用相应子页面内的全局函数即可,比如:

// 调用同域的iframe页面内的 changeText 方法 iframeDom.contentWindow.changeText('from ThingJS');

相应的,在 iframe 子页面中也可以调用 ThingJS 在线开发页面中的函数方法,比如:

// iframe页面中通过 window.parent 获取并调用 ThingJS 在线开发环境中的函数方法 window.parent.changeLevel('car01');

查看示例

上述示例的 iframe 页面代码如下:

Document

Hello World!

Into Level 跨域 iframe 通信 对于跨域的 iframe 无法直接相互调用函数。

可以利用 HTML5 提供的 postMessage 接口实现跨域 iframe 页面间的相互函数方法调用。

关于postMessage 的详细 API ,点击 此处 查看

例如,ThingJS 向 iframe 引用的子页面 (https://localhost:3000/pages/index.html) 发送消息

iframeDom.contentWindow.postMessage

postMessage 方法的第一个参数是具体的信息内容;

第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送;

在 iframe 页面中,通过 message 事件,监听 ThingJS 页面发送的消息。

window.addEventListener('message', function(event) { console.log(event.data);

message 事件的 event 事件对象,提供以下三个属性:

event.data: 消息内容 event.origin: 消息发送方窗口的 origin(域名、协议和端口) event.source:消息发送窗口的对象引用 查看示例

上述示例的 iframe 页面代码如下:

Document

Hello World!

Into Level 通栏 如果用户想基于 ThingJS 做一套独立的应用系统,可使用通栏组件作为系统级别的菜单。

创建Banner // 创建侧通栏 var banner = new THING.widget.Banner({column:'left'});

// 创建侧通栏 var banner = new THING.widget.Banner({column:'left'});

参数中 column 代表通栏类型,'top' 为上通栏(默认),' left' 为左侧通栏;

通栏中的按钮绑定数据: var dataObj = { warehouseCode: false, temperature: false, humidity: false, statistics: false, status: false, insect: false, cerealsReserve: false, video: true, cloud: true };

通栏中添加按钮 var img0 = banner.addImageBoolean(dataObj, 'warehouseCode').caption('仓库编号').url('#momoda_lc-icontubiao10'); var img1 = banner.addImageBoolean(dataObj, 'temperature').caption('温度检测').url('#momoda_lc-icontubiao2'); var img2 = banner.addImageBoolean(dataObj, 'humidity').caption('湿度检测').url('#momoda_lc-icontubiao5'); var img3 = banner.addImageBoolean(dataObj, 'statistics').caption('能耗统计').url('#momoda_lc-icontubiao20'); var img4 = banner.addImageBoolean(dataObj, 'status').caption('保粮状态').url('#momoda_lc-icontubiao'); var img5 = banner.addImageBoolean(dataObj, 'insect').caption('虫害').url('#momoda_lc-icontubiao11'); var img6 = banner.addImageBoolean(dataObj, 'cerealsReserve').caption('粮食储存').url('#momoda_lc-icontubiao21'); var img7 = banner.addImageBoolean(dataObj, 'video').caption('视屏监控').url('#momoda_lc-icontubiao9'); var img8 = banner.addImageBoolean(dataObj, 'cloud').caption('温度云图').url('#momoda_lc-icontubiao16');

效果如图所示:

查看示例 面板定位 绝对位置与相对位置 在“快捷界面库”一章中已经提到,对于面板组件 Panel ,可通过设置 position 属性来确定它在3D容器内的位置。

当 position 里的值为数字时,以像素为单位进行绝对定位。

当 position 里的值为百分比字符串时,则以3D容器大小进行相对定位。

// 创建面板配置项 var panel = new THING.widget.Panel({ hasTitle: true, // 是否允许有面板title titleText: "面板", // 设置title标题名称 width: '200px' }); // 创建数据对象用于绑定面板数据 var dataObj = { temperature: '24℃', humidity: '70%', aqi: '良', }; // 添加数据 panel.addString(dataObj, 'temperature').caption('气温'); panel.addString(dataObj, 'humidity').caption('湿度'); panel.addString(dataObj, 'aqi').caption('空气质量');

当 position 里的值为百分比字符串时,则以3D容器大小进行相对定位。

// 距3D容器左侧 100px , 顶部 100px panel.position=[100,100]; // 距3D容器左侧 200px , 相对顶部 60% panel.position=[200,'60%'] // 相对3D容器左侧 30% , 距离顶部 200px panel.position=['30%',200] // 相对3D容器左侧 50% , 相对顶部 50% panel.position=['50%','50%']

面板锚点 ThingJS 快捷界面库中提供的 Panel 面板可根据面板的锚点进行定位。

对于一个 Panel 面板而言,目前提供四个锚点类型进行定位,分别为:

左上角(TopLeft) 左下角 (BottomLeft) 右上角 (TopRight) 右下角 (BottomRight) 默认基于左上角进行定位。

可通过 positionOrigin 属性设置锚点类型

// 基于左上(默认) panel.positionOrigin = 'TL';// top-left // 基于左下 panel.positionOrigin = 'BL';// bottom-left // 基于右上 panel.positionOrigin = 'TR';// top-right // 基于右上 panel.positionOrigin = 'BR';// bottom-right

例如,当设置面板锚点为右下角时,设置位置效果如下:

ThingJS 引用 Echarts

查看示例 引用步骤 加载 echarts.js 加载 echarts.js 的目的是为了引用 Echarts 图表库中的图表。

通过 THING.Utils.dynamicLoad 方法 加载 js ,方法的执行需要两个参数:

第一个参数为 echarts.js 的 URL (链接地址); 第二个参数为加载 echarts.js 之后调用的回调方法 THING.Utils.dynamicLoad(['cdn.bootcss.com/echarts/4.1…'],function() {})

创建需要的 DOM 节点 DOM 节点为需要在场景中显示的节点。

通过 document.createElement 创建新的 DOM 节点。

由于 DEMO 的需求,此处创建 3 个 DOM 节点,这三个 DOM 节点分别是:

背景颜色 div; 标题 div; 底部图表 div; //背景颜色 var bottomBackground = document.createElement('div'); //标题 var bottomFont = document.createElement('div'); //图表 var bottomDom = document.createElement('div'); //背景样式右下角对齐 var backgroundStyle = 'bottom:0px; position: absolute;right:0px;height:400px;width:600px;background: rgba(41,57,75,0.74);'; //字体样式 var fontStyle = 'position: absolute;top:0px;right:0px;color:rgba(113,252,244,1);height:78px;width:600px;line-height: 45px;text-align: center;top: 20px;'; //图表DIV样式 var chartsStyle = 'position: absolute;top:80px;right:0px;width:600px;height:300px;';

//设置样式 bottomBackground.setAttribute('style', backgroundStyle); bottomFont.setAttribute('style', fontStyle); bottomDom.setAttribute('style', chartsStyle);

//底部标题文字 bottomFont.innerHTML = '温度降水量平均变化图';

初始化 Echarts 加载 echarts.js 完成以后,已经将 Echarts 引入到场景中了。通过以下两步可以得到图表实例:

调用 window.echarts 获取 Echarts; 通过 init 方法创建图表实例,传入的参数为需要 Echarts 图表的 DOM 节点,返回的是图表实例; 具体初始化方法可以参照 Echarts 官网五分钟上手 Echarts

let bottomCharts = window.echarts.init(bottomDom)

配置图表的属性 图表的各项属性 options 代表的含义可以点击 Echarts 官网 配置项手册 。

最好在 Echarts 官网或者 ChartBuilder 官网上,将图表的 options 配置完毕,这样可以快速查看配置的效果 调用 setOptions 方法将配置好的 options 传入图表 let echartOptions = { "tooltip": { "trigger": "axis", "axisPointer": { "type": "cross", "crossStyle": { "color": "#999" } } }, "legend": { "textStyle": { "color": "auto" }, "data": [ "蒸发量", "降水量", "平均温度" ] }, "xAxis": [ { "axisLabel": { "textStyle": { "color": "#fff" } }, "type": "category", "data": [ "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月" ], "axisPointer": { "type": "shadow" } } ], "yAxis": [ { "type": "value", "name": "水量", "min": 0, "max": 250, "interval": 50, "splitLine": { "lineStyle": { "type": "dotted" }, "show": true }, "nameTextStyle": { "color": "#fff" }, "axisLabel": { "textStyle": { "color": "#fff" }, "formatter": "{value} ml" } }, { "splitLine": { "lineStyle": { "type": "dotted" }, "show": true }, "type": "value", "name": "温度", "min": 0, "max": 25, "interval": 5, "nameTextStyle": { "color": "#fff" }, "axisLabel": { "textStyle": { "color": "#fff" }, "formatter": "{value} °C" } } ], "series": [ { "name": "蒸发量", "type": "bar", "data": [ 2, 4.9, 7, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20, 6.4, 3.3 ] }, { "name": "降水量", "type": "bar", "data": [ 2.6, 5.9, 9, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6, 2.3 ] }, { "name": "平均温度", "type": "line", "yAxisIndex": 1, "data": [ 2, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23, 16.5, 12, 6.2 ] } ], "color": [ "#2b908f", "#90ee7e", "#f45b5b", "#7798BF", "#aaeeee", "#ff0066", "#eeaaee", "#55BF3B", "#DF5353", "#7798BF", "#aaeeee" ] } bottomCharts.setOption(echartOptions);

将节点放到 app dom 下 bottomBackground.appendChild(bottomFont); bottomBackground.appendChild(bottomDom); app.domElement.appendChild(bottomBackground);