当前位置:网站首页>收藏!0 基础开源数据可视化平台 FlyFish 大屏开发指南
收藏!0 基础开源数据可视化平台 FlyFish 大屏开发指南
2022-07-27 15:56:00 【InfoQ】
相关文档地址
- FlyFish官方开发文档
- YAPI数据模拟文档
- GitHub地址
- Gitee地址
开始前(准备)
FlyFish平台在线地址
创建项目(整体项目名称)查看是否有当前正要做的项目
- 添加当前项目(如果没有当前项目,有则忽略)
- 添加应用(可视化大屏)
- 浏览是否有满足UI设计的基础组件(UI:可视化大屏组件样式)
开始上手(初级)
- 选择要开发的应用(可视化大屏)
- 选择适合的基础组件拖入可视化大屏内需要摆放的位置,去选择合适的配置满足UI的需求,
- 如果仅通过配置项无法满足当前组件与UI的要求,可自定义CSS,添加css名字(会添加到当前组件的最外层,并在全局样式内进行自定义)
- 请求数据的方式
- 选择当前项目下的组件(如果有的话...)
开始开发(中级)
- 有类似的项目组件(但是仍需要进行定制化的)复制此组件,并起一个新的名字
- 编辑此组件信息,添加到当前项目
- 添加定制化项目组件(如果基础组件不具备满足你当前的需求)
- 项目组件开发,选择刚刚创建好的项目组件、点击开发组件
- 代码结构
- 是否需要配置模块
- 数据请求方式(直接在代码中写)仅开发中生效--的模拟数据
- 大屏依然生效--的模拟数据(应用 = 可视化大屏)
- 默认选项,没有数据,但该参数又是必须参数(传递给组件的默认数据)
- 组件内获取数据获取API请求数据,直接props中获取data
- 获取默认选项
- 安装依赖(如果组件开发中需要引用某些插件)
- 更新上线
开始进阶(高级)
- 设置大屏(官方文档)
- 事件(官方文档)组件之间传递事件 第一个箭头传递给某个组件事件以及参数 第二个箭头可以直接触发某个组件的数据请求
- 组件之间接收事件
- 收到了组件传递过来的事件则会自动执行你的自定义
- 配置组件之间的事件(不配置不会生效哟)
- 有了刚才组件的内部自定义的事件,我们可以设置之间的关联
- 如果选择红框框内的事件则作用在整个组件身上,如果选择红色箭头的事件则按照你刚才创建的方法开始执行
- 如果选择红色圆圈内的则作用于整个大屏之上,不在于某个组件内,如果选择红色方框则作用于所选的组件内部事件
- 选择刚刚定义的trigger事件
- 接收组件定义的方法(注意不是发送事件的那个哟,当然为了避免容易犯错误,你可以将两个名字设为一致)
- 可以选择修改刚刚定义的事件
- 函数(官方文档)
- 全局数据集(官方文档)全局数据集可以给多个组件使用
开始进阶(骨灰级)
/**
* 加载数据
* @param {Object=} options 临时加载选项
* @param {function(Array.<Object>)=} onSuccess 加载完成回调
* @param {function(string)} onError 加载失败回调
* @returns {Component}
*/
load(options = {}, onSuccess = null, onError = null) {
if (this.hasDataSource()) {
if (isFunction(options)) {
/* eslint-disable no-param-reassign */
onError = onSuccess;
onSuccess = options;
options = {};
/* eslint-enable no-param-reassign */
}
// 加载数据事件
this.trigger('load');
this.dataSource.load(
options,
(data) => {
call(onSuccess, this, data);
let opt = this.getOptions()
const { lineBackgroundDefault, lineBackground } = opt;
const newLineBackground = data.dataList.map((_,i) => lineBackground[i] || lineBackgroundDefault);
// 数据加载完成事件
console.log(newLineBackground, '<--data')
this.trigger('loaded', data);
this.setOptions({lineBackground: JSON.parse(JSON.stringify(newLineBackground))})
this.draw(data);
},
onError
);
}
return this;
}- 配置面板如何根据数据实现联动变化?
/*
* @Author: Rise.Hao
* @Date: 2022-05-11 22:53:50
* @LastEditors: Rise.Hao
* @LastEditTime: 2022-06-01 21:33:08
* @Description: file content
*/
'use strict';
import React from 'react';
import Base from './panel/index.js'
import { cloneDeep } from "data-vi/helpers";
import { recursionOptions } from '@cloudwise-fe/chart-panel'
import { ComponentOptionsSetting } from 'datavi-editor/templates';
export default class OptionsSetting extends ComponentOptionsSetting {
constructor(props) {
super(props)
}
// 可自定义样式: 若您在设置面板中书写样式会抽离出setting.css.
// 显式的将以下属性设置为true可告知FlyFish来加载您的样式文件
enableLoadCssFile = true;
componentDidMount() {
const { component } = this.props;
component.bind('draw', () => {
this.forceUpdate()
})
}
componentWillUnmount() {
const { component } = this.props;
this.computedSettingStyleAppend(true);
component.unbind('draw');
}
getTabs() {
const options = recursionOptions(this.props.options, true)
const {component, updateOptions} = this.props;
return {
config: {
label: '配置',
content: () => <Base initialValues={options} props={this.props} options={cloneDeep(component.getOptions())} onChange={updateOptions} />,
},
}
}
}- 有些时候更改了某个配置项而他有没有生效? 比如:参数本身是一个数组又或者是一个对象,这个数组本身就存在,而你此次操作只是给数组里面删除了一个对象,最终没有生效。原因是FlyFish默认执行的setOptions是合并数据而不是更新数据把数组进行字符串处理,让他变成一个值,这样就不是合并了。重写setOptions方法,数组里面有的参数都执行更新操作,没有的执行合并操作。
import { defaultsDeep } from "data-vi/helpers";
/**
* 设置选项
*
* @param {Object} options 选项
* @param {boolean} merge 是否合并原来的选项
* @returns {Component}
*/
setOptions(options = {}, merge = true) {
const { replaceAll, ...mergeOptions } = options;
const replaceKeys = ['lineBackground'];
// 魔改一下部分结果处理
if (replaceAll) {
this.options = mergeOptions;
} else if (merge) {
let cloneOption = defaultsDeep({}, mergeOptions, this.options);
if (replaceKeys.find((v) => typeof mergeOptions[v] !== 'undefined')) {
cloneOption = {
...cloneOption,
...mergeOptions,
};
}
this.options = cloneOption;
} else {
this.options = defaultsDeep({}, mergeOptions, this.getDefaultOptions());
}- 确保在所有组件加载完成后自动执行一个trigger方法?
useEffect(() => {
if (!nowdata) return;//nowdata是请求后端返回来的数据
if (parent && parent.screen) {
const allComponent = parent.screen.getComponents();
const lastComponent = allComponent[allComponent.length - 1];
if (lastComponent.mounted) {
parent.trigger('add', { id: currentItem, value: nowdata })
} else {
lastComponent.bind("mounted", () => {
parent.trigger('add', { id: currentItem, value: nowdata })
lastComponent.unbind("mounted");
})
}
}
}, [nowdata])- 我这个组件怎么去更改别的组件的默认选项?(谨慎操作)
const compontentList = this.props.component.screen.getComponents()
compontentList.forEach((item)=>{
//这里可以做判断对那个组件进行操作
item.setConfig({
visible: true
})
}- 建议不带 get 的 static?
// 默认配置
static defaultConfig = {};
getDefaultConfig() {
return defaultsDeep({}, this.constructor.defaultConfig, {
width: 100,
height: 100,
index: 0,
left: 0,
top: 0,
name: '',
visible: true,
class: ''
});
}- 输入框和FlyFish的事件冲突?
// 禁止冒泡掉
const bubblingFunc= (event)=>{
event.stopPropagation();
}
<input
onKeyUp={bubblingFunc}
onKeyDown={bubblingFunc}
/>- 事件可以在组件里面直接写好了!
// 注册事件
registerComponentEvents("id", "DEFAULT_VERSION", {
onChange: "变更",
onValueChange: "值变更",
});
// 注册action
registerComponentAction("id", "DEFAULT_VERSION", "changeValue", ReactCompont);
call(component, "changeValue", ...args);
// ReactCompont;
export default (props) => (
<Form>
<FormItem label="横坐标(X)" cols={[8, 8]}>
<Input
value={toString(props.args[0])}
placeholder="请输入横坐标(X)"
onChange={(event) =>
props.onChange(0, toNumber(event.target.value))
}
/>
</FormItem>
<FormItem label="纵坐标(Y)" cols={[8, 8]}>
<Input
value={toString(props.args[1])}
placeholder="请输入纵坐标(Y)"
onChange={(event) =>
props.onChange(1, toNumber(event.target.value))
}
/>
</FormItem>
</Form>
);- 静态文件从根目录取绝对路径的该如何设置?
import { DEFAULT_VERSION } from "data-vi/components";
import config from "data-vi/config";
const componentStaticDir = props.parent.getVersion() == null || props.parent.getVersion() === DEFAULT_VERSION ? "components" : "release";
const link = `${config.componentsDir}/${props.parent.getType()}/${props.parent.getVersion() || DEFAULT_VERSION}/${componentStaticDir}/public`;
//webpack.config.production.js文件
const CopyPlugin = require("copy-webpack-plugin");
plugins: [
new CopyPlugin( [
{ from: path.resolve(__dirname, '../') + '/src/ModelRotates/public', to: path.resolve(__dirname, '../') + '/components/public/', },
]),
]
//安装依赖
"copy-webpack-plugin": "5.1.1"- 组件内需要自己写请求?
import { getHttpData } from 'data-vi/api';
import { componentApiDomain } from 'data-vi/config';
const getMapdata = (name) => {
getHttpData(componentApiDomain + `/atlas/info?location=${encodeURIComponent(name)}`, 'GET', {})
.done((request) => {
console.log('请求成功', request)
setNowdata(request.data)
})
.fail((request, xhr, msg) => {
console.log('失败了')
});
}- 比如跳转大屏如何根据url实现数据变更?
function preDisposeParams(params) {
let sumParams = window.location.search ? window.location.search.split('?')[1] : '';
let eachParams = sumParams.split('&')[1] || '';
let systemCode = eachParams.split('=')[1] || '';
let jsonParams ={
"systemCode":systemCode
}
console.log(sumParams,"-",eachParams,"-",systemCode)
return jsonParams; }- 整张大屏内如何使用字体?【后期可能会更改】 示例组件
/**
* 钩子方法 组件mount挂载时调用
*/
_mount() {
const container = this.getContainer();
console.log(this.getType(),this.getVersion(),'123')
const componentStaticDir =
this.getVersion() == null || this.getVersion() === DEFAULT_VERSION ? "components" : "release";
const link = `${config.componentsDir}/${this.getType()}/${
this.getVersion() || DEFAULT_VERSION
}/${componentStaticDir}/assets`;
container.html(`
<style>
@font-face {
font-family: FZZYJW;
src: url('${link}/FZZYJW.TTF');
}
@font-face {
font-family: FZZZHONGJW;
src: url('${link}/FZZZHONGJW.TTF');
}
@font-face {
font-family: HYa9gj;
src: url('${link}/hya9gjm.ttf');
}
@font-face {
font-family: HYk2gj;
src: url('${link}/HYLingXin.ttf');
}
@font-face {
font-family: SourceHanSerifSCHeavy;
src: url('${link}/SourceHanSerifSCHeavy.ttf');
}
</style>
`);
}开发完成
- 点击预览,查看效果是否满足,并简单自测是否有BUG
- 导出已完成的可视化大屏
部署上线
- componentApiDomain = 请求后端数据的ip地址
- 如果nginx代理没有从根目录配置则需要更改
- iplpadImgDir的路径 = ./ (/data/app需要根据nginx代理的具体路径来配置)
- components的路径= /data/app/components (/data/app需要根据nginx代理的具体路径来配置)
标准流程 tengine部署
- 修改前端部署包配置文件
- 新建前端部署文件夹 web(/data/web/
- tengine部署中都以该文件夹为例)
- 将前端包文件screen.zip拷贝到该目录下解压命令:unzip screen.zip
- 修改/data/app/sxdl_web/config/env.production.js
- 修改/data/tengine/conf/vhost/路径 (tengine部署目录为/data/tengine)
- 修改/data/app/sxdl_web/index.html (浏览器刷新文本)
- 修改/data/app/sxdl_web/config/env.conf.json (浏览器页签文本)
- 重启tengine服务 /data/app/tengine/sbin/nginx -s reload 前端访问地址
nginx部署
- 修改前端部署目录xxxx/config/env.production.js配置文件
- 配置文件: env.production.js:{ componentApiDomain:后端接口地址 }
- 部署环境nginx 注意:大屏前端配置的端口不可以和其他服务的前端的端口冲突
- 首先备份/nginx/conf下的nginx.conf
- 编辑nginx.conf
重启ngnix /sbinx下 ./ngnix -t //检查配置文件nginx.conf的正确性
./ngnix -s reload //重新载入配置文件 - 上传screen.zip file.dir: /var/www/html 将解压后screen.zip文件放入该目录(先清空html文件夹)
- 备注:如果没有相同的路径,则随便找个路径放文件就行
- 解压screen.zip文件
- 修改/var/www/html/env.production.js文件 \\修改配置文档: env.production.js
- 修改配置后,重启nginx
- 项目运行地址: 服务器地址+’/index.html’
下载源码
- 前缀:http://10.0.16.230:7001/applications/export-source/(演示样例,真实链接会根据FlyFish本地的地址变化)
- 大屏的ID
- 最终下载地址(演示样例,非真实地址):http://10.0.16.230:7001/applications/export-source/62134fbaddc0c8314cd3be30
- 安装依赖: yarn 或 npm install
- 启动项目:yarn dev 或 npm run dev
- 再次编译:yarn build 或 npm run build
边栏推荐
- SQL Server连接到服务器无效的解决办法
- How to develop an online Excel spreadsheet system (Part 1)
- JS to realize the right-click menu bar function
- ts学习笔记-class
- 面试好难啊!蚂蚁金服的六轮面试我是强撑过来!差点OUT(面试复盘)
- Personal understanding of convolution calculation process of convolution neural network
- wallys/DR882-Qualcomm-Atheros-QCA9882-2T2R-MIMO-802.11ac-Mini-PCIe-Wi-Fi-Module-5G-high-power.
- mysql解决唯一索引重复导致的插入失败问题
- With the right tools, CI achieves twice the result with half the effort
- Wechat applet realizes location map display and introduces map map without navigation
猜你喜欢

知物由学 | 关联图分析在反作弊业务中的应用

Profiles vs Permission Sets

I got the P8 "top-level" distributed architecture manual crazy spread on Alibaba intranet

Introduction to ef framework
How to improve the security of Android applications?

Knowing things by learning | app slimming down, the way of safety reinforcement under the new generation AAB framework

Convolutional neural network -- Translation of yolov2 (yolo9000) papers
What are the safety risks of small games?

Learn from what you know | Yidun self-developed text real-time clustering technology, and wipe out the same kind of harmful content in social networks

Oracle 11g数据库安装教程
随机推荐
Convolutional neural network -- Translation of yolov2 (yolo9000) papers
Common shell commands (1) -- variable case conversion
WPF做登陆界面
防止sql注入
Notes on standardized management of "ancestral warehouse" of meituan meal
How difficult the interview is! I was forced to survive the six rounds of interview of ant financial! Almost out (interview resumption)
How to resolve the public domain name to the intranet IP server -- quickly resolve the domain name and map the Internet access
知物由学 | 小游戏的安全风险在哪里?
hutool- 数组工具
org.gradle.api.UncheckedIOException: Could not load properties for module ‘gradle-kotlin-dsl‘ from C
知物由学 | SO加固如何提升Android应用的安全性?
ts学习笔记-interface
年终总结模板
Numpy array matrix operation
机器学习——概念理解之IoU
hutool- 数字计算
[introduction to database system (Wang Shan)] Chapter 1 - Introduction
JDBC connection database reading foreground cannot display data
How to restrict root remote login so that ordinary users have root privileges
Behind every piece of information you collect, you can't live without TA