当前位置:网站首页>收藏!0基础开源数据可视化平台FlyFish大屏开发指南
收藏!0基础开源数据可视化平台FlyFish大屏开发指南
2022-07-27 14:46:00 【云智慧AIOps社区】
作者:Rise Hao,云智慧前端开发工程师。开源项目数据可视化平台FlyFishMaintainer。主攻可视化大屏方向,专注工程研发的降本增质、增效,在可视化方面具有丰富的开发经验 。
FlyFish 是云智慧公司自主设计、研发的一款低门槛、高拓展性的低代码应用开发平台, 为数据可视化开发场景提供了高效的一站式解决方案。FlyFish提供丰富的组件和应用模板库, 可通过拖拉拽的形式完成数据可视化开发,零开发背景的用户也可完成数据可视化开发工作。 同时,FlyFish也提供了灵活的拓展能力,支持组件开发、自定义函数与全局事件等配置, 面向复杂需求场景能够保证高效开发与交付。
相关文档地址
开始前(准备)
FlyFish平台在线地址
创建项目(整体项目名称)
- 查看是否有当前正要做的项目

- 添加当前项目(如果没有当前项目,有则忽略)

- 添加应用(可视化大屏)


- 浏览是否有满足UI设计的基础组件(UI:可视化大屏组件样式)

开始上手(初级)
选择要开发的应用(可视化大屏)

选择适合的基础组件
- 拖入可视化大屏内需要摆放的位置,去选择合适的配置满足UI的需求

- 如果仅通过配置项无法满足当前组件与UI的要求,可自定义CSS,添加css名字(会添加到当前组件的最外层,并在全局样式内进行自定义)


- 请求数据的方式


选择当前项目下的组件(如果有的话...)

开始开发(中级)
有类似的项目组件(但是仍需要进行定制化的)
- 复制此组件,并起一个新的名字

- 编辑此组件信息,添加到当前项目

添加定制化项目组件(如果基础组件不具备满足你当前的需求)

项目组件开发,选择刚刚创建好的项目组件、点击开发组件

代码结构

build/webpack.config.dev.js
组件开发阶段保存对组件进行 webpack 编译打包扩展配置文件,具体请参考更改组件编译配置
#build/webpack.config.production.js
组件导出阶段对组件进行 webpack 编译打包扩展配置文件,具体请参考更改组件编译配置
#package.json
组件信息和依赖,具体请参考添加组件依赖
#options.json
组件开发底部的组件预览大屏的预设,具体请参考增加组件开发大屏预设
#src/main.js
组件注册入口,组件开发会自动产生此文件,如务必要不需要更改。具体请参考注册组件
#src/Component.js
组件代码文件,仅支持原生 Javascript 进行开发,请参考开发组件。如使用 react 开发,请参考React 开发组件
#src/setting.js
组件设置区域注册入口,组件开发会自动产生此文件,如组件有需要开发自定义配置和数据绑定,请打开此文件内注释掉的注册内容
#src/setting/options
组件设置区域组件,需使用 react 开发,具体请参考增加组件配置
#src/setting/data
组件设置数据区域组件,需使用 react 开发,具体请参考增加组件数据配置
是否需要配置模块
1 是右边的数据请求,2 是右边的模块配置

数据请求方式(直接在代码中写)
- 仅开发中生效--的模拟数据

- 大屏依然生效--的模拟数据(应用 = 可视化大屏)

- 默认选项,没有数据,但该参数又是必须参数(传递给组件的默认数据)

组件内获取数据
- 获取API请求数据,直接props中获取data

- 获取默认选项

安装依赖(如果组件开发中需要引用某些插件)
FlyFish支持通过Echarts等外部平台开发组件,如有需要可通过引用相关插件的方式去实现。

更新上线

开始进阶(高级)
配置组件之间的事件(不配置不会生效哟)
- 有了刚才组件的内部自定义的事件,我们可以设置之间的关联

- 如果选择红框框内的事件则作用在整个组件身上,如果选择红色箭头的事件则按照你刚才创建的方法开始执行

- 如果选择红色圆圈内的则作用于整个大屏之上,不在于某个组件内,如果选择红色方框则作用于所选的组件内部事件

- 选择刚刚定义的trigger事件

- 接收组件定义的方法(注意不是发送事件的那个哟,当然为了避免容易犯错误,你可以将两个名字设为一致)

- 可以选择修改刚刚定义的事件

全局数据集可以给多个组件使用 
开始进阶(骨灰级)
默认选项跟随数据进行实时渲染?
- 重写load方法,因为他可以更新默认的选项 defaultOptions。
/** * 加载数据 * @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; } ```配置面板如何根据数据实现联动变化?
- 在options.js文件写上下面的句子就可以拿到更新之后的数据了。todo(data)
/* * @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} />, }, } } } ```
配置面板应该怎么写?
/* * @Author: Rise.Hao * @Date: 2022-05-29 13:33:05 * @LastEditors: Rise.Hao * @LastEditTime: 2022-06-01 22:00:47 * @Description: file content */ import React from 'react' import { Input, Select, ConfigProvider, InputNumber } from 'antd'; import { ChartProvider, FormItem, FormItemGroup, CollapsePanel, Collapse, ColorPickerInput } from '@cloudwise-fe/chart-panel' export default function Index(props) { const { options, initialValues, onChange } = props; const { lineBackground = [] } = options || {}; const lineFunc = (e, index, key) => { const newLineBackground = lineBackground.map((item, i) => { return i === index ? { ...item, [key]: e } : item }) onChange({ lineBackground: newLineBackground }) } console.log(lineBackground,'<--lineBackground') return <ChartProvider> <ConfigProvider prefixCls="ant4"> <Collapse> <CollapsePanel title="面积颜色" key="1"> <FormItemGroup layout="vertical"> { lineBackground.map((item, index) => { return <FormItem key={`background${index}`} label={`第${index + 1}条线面积颜色`}> <ColorPickerInput onChange={(e) => lineFunc(e, index, 'background')} value={item.background} gradientMode="gradient" /> </FormItem> }) } </FormItemGroup> </CollapsePanel> <CollapsePanel title="字体颜色" key="1"> <FormItemGroup layout="vertical" initialValues={initialValues} onValuesChange={onChange}> <FormItem label="X轴字体" name="xAxisFontColor"> <ColorPickerInput gradientMode="gradient" /> </FormItem> <FormItem label="Y轴字体" name="yAxisFontColor"> <ColorPickerInput gradientMode="gradient" /> </FormItem> </FormItemGroup> </CollapsePanel> </Collapse> </ConfigProvider> </ChartProvider> }
有些时候更改了某个配置项而他有没有生效?
比如:参数本身是一个数组又或者是一个对象,这个数组本身就存在,而你此次操作只是给数组里面删除了一个对象,最终没有生效。原因是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: "值变更",});// 注册actionregisterComponentAction("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’
(注:前端本次用FlyFish开发页面,直接打出包,git上无仓库)
下载源码

最终下载地址(演示样例,非真实地址):http://10.0.16.230:7001/applications/export-source/62134fbaddc0c8314cd3be30
注:前缀 + 大屏ID = 下载地址(请谨慎频繁调用)
Echarts配置及导出(如有下载失败,请更换版本号):https://www.npmjs.com/package/@cloudwise-fe/chart-panel
安装依赖: yarn 或 npm install
启动项目:yarn dev 或 npm run dev
再次编译:yarn build 或 npm run build
边栏推荐
- Mapreduce实例(二):求平均值
- 企业运维安全就用行云管家堡垒机!
- How PHP changes a two-dimensional array into a one-dimensional array
- __ The difference between typeof and typeof
- Draw circuit diagram according to Verilog code
- C language programming (Third Edition)
- Flask connects to existing tables in MySQL database
- Delete node quickly and efficiently_ modules
- ARIMA model selection and residuals
- Mapreduce实例(一):WordCount
猜你喜欢

Flask connects to existing tables in MySQL database

webRTC中的coturn服务安装

Leetcode 226 flip binary tree (recursive)

solidwork装配体导入到Adams中出现多个Part重名和Part丢失的情况处理

Content ambiguity occurs when using transform:translate()

Web test learning notes 01

Boolean value

centos上mysql5.7主从热备设置

Servlet基础知识点

Solve the problem that Flink cannot be closed normally after startup
随机推荐
profileapi. h header
Sudden! 28 Chinese entities including Hikvision / Dahua / Shangtang / Kuangshi / ITU / iFLYTEK have been blacklisted by the United States
DeFi安全之DEX与AMMs
JMeter5.3 及以后的版本jmeter函数助手生成的字符在置灰无法复制
The method of inserting degree in word
ARIMA模型选择与残差
DRF learning notes (V): viewset
Enable shallow and deep copies+
Firefox old version
云管平台中租户以及多租户概念简单说明
EXE程序加密锁
Solve the compilation warning of multiple folders with duplicate names under the openwrt package directory (call subdir function)
Delete node quickly and efficiently_ modules
Crmeb Pro v1.4 makes the user experience more brilliant!
solidwork装配体导入到Adams中出现多个Part重名和Part丢失的情况处理
The new JMeter function assistant is not under the options menu - in the toolbar
Penetration test - dry goods | 80 + network security interview experience post (interview)
SolidWorks simulation curve attribute setting
第31回---第52回
Scratch crawler framework


