• 收藏|0 基础开源数据可视化平台 FlyFish 大屏开发指南


    作者:Rise Hao,云智慧前端开发工程师。开源项目数据可视化平台 FlyFish Maintainer。主攻可视化大屏方向,专注工程研发的降本增质、增效,在可视化方面具有丰富的开发经验 。

    FlyFish 是云智慧公司自主设计、研发的一款低门槛、高拓展性的低代码应用开发平台, 为数据可视化开发场景提供了高效的一站式解决方案。FlyFish提供丰富的组件和应用模板库, 可通过拖拉拽的形式完成数据可视化开发,零开发背景的用户也可完成数据可视化开发工作。 同时,FlyFish也提供了灵活的拓展能力,支持组件开发、自定义函数与全局事件等配置, 面向复杂需求场景能够保证高效开发与交付。

    相关文档地址

    开始前(准备)

    FlyFish平台在线地址

    创建项目(整体项目名称)查看是否有当前正要做的项目

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

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

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

    开始上手(初级)

    1. 选择要开发的应用(可视化大屏)

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

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

    请求数据的方式

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

    ​开始开发(中级)

    1. 有类似的项目组件(但是仍需要进行定制化的)复制此组件,并起一个新的名字

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

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

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

    1. 代码结构

    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. 是否需要配置模块

    1 是右边的数据请求,2 是右边的模块配置

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

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

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

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

    ​获取默认选项

    ​1. 安装依赖(如果组件开发中需要引用某些插件)

    FlyFish支持通过Echarts等外部平台开发组件,如有需要可通过引用相关插件的方式去实现。

    ​2. 更新上线

    开始进阶(高级)

    1. 设置大屏(官方文档

    2. 事件(官方文档)组件之间传递事件

    • 第一个箭头传递给某个组件事件以及参数

    • 第二个箭头可以直接触发某个组件的数据请求

    ​组件之间接收事件

    • 收到了组件传递过来的事件则会自动执行你的自定义

    • ​配置组件之间的事件(不配置不会生效哟)

    • 有了刚才组件的内部自定义的事件,我们可以设置之间的关联

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

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

    • 选择刚刚定义的trigger事件

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

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

    1. 函数(官方文档

    自定义函数,常见的用法是提供给大屏的事件使用。

    1. 全局数据集(官方文档)全局数据集可以给多个组件使用

    开始进阶(骨灰级)

    1. 默认选项跟随数据进行实时渲染?

      重写load方法,因为他可以更新默认的选项 defaultOptions。

      1. /**
      2. * 加载数据
      3. * @param {Object=} options 临时加载选项
      4. * @param {function(Array.)=} onSuccess 加载完成回调
      5. * @param {function(string)} onError 加载失败回调
      6. * @returns {Component}
      7. */
      8. load(options = {}, onSuccess = null, onError = null) {
      9. if (this.hasDataSource()) {
      10. if (isFunction(options)) {
      11. /* eslint-disable no-param-reassign */
      12. onError = onSuccess;
      13. onSuccess = options;
      14. options = {};
      15. /* eslint-enable no-param-reassign */
      16. }
      17. // 加载数据事件
      18. this.trigger('load');
      19. this.dataSource.load(
      20. options,
      21. (data) => {
      22. call(onSuccess, this, data);
      23. let opt = this.getOptions()
      24. const { lineBackgroundDefault, lineBackground } = opt;
      25. const newLineBackground = data.dataList.map((_,i) => lineBackground[i] || lineBackgroundDefault);
      26. // 数据加载完成事件
      27. console.log(newLineBackground, '<--data')
      28. this.trigger('loaded', data);
      29. this.setOptions({lineBackground: JSON.parse(JSON.stringify(newLineBackground))})
      30. this.draw(data);
      31. },
      32. onError
      33. );
      34. }
      35. return this;
      36. }
      37. 配置面板如何根据数据实现联动变化?

      38. 在options.js文件写上下面的句子就可以拿到更新之后的数据了。

          1. /*
          2. * @Author: Rise.Hao
          3. * @Date: 2022-05-11 22:53:50
          4. * @LastEditors: Rise.Hao
          5. * @LastEditTime: 2022-06-01 21:33:08
          6. * @Description: file content
          7. */
          8. 'use strict';
          9. import React from 'react';
          10. import Base from './panel/index.js'
          11. import { cloneDeep } from "data-vi/helpers";
          12. import { recursionOptions } from '@cloudwise-fe/chart-panel'
          13. import { ComponentOptionsSetting } from 'datavi-editor/templates';
          14. export default class OptionsSetting extends ComponentOptionsSetting {
          15. constructor(props) {
          16. super(props)
          17. }
          18. // 可自定义样式: 若您在设置面板中书写样式会抽离出setting.css.
          19. // 显式的将以下属性设置为true可告知FlyFish来加载您的样式文件
          20. enableLoadCssFile = true;
          21. componentDidMount() {
          22. const { component } = this.props;
          23. component.bind('draw', () => {
          24. this.forceUpdate()
          25. })
          26. }
          27. componentWillUnmount() {
          28. const { component } = this.props;
          29. this.computedSettingStyleAppend(true);
          30. component.unbind('draw');
          31. }
          32. getTabs() {
          33. const options = recursionOptions(this.props.options, true)
          34. const {component, updateOptions} = this.props;
          35. return {
          36. config: {
          37. label: '配置',
          38. content: () => <Base initialValues={options} props={this.props} options={cloneDeep(component.getOptions())} onChange={updateOptions} />,
          39. },
          40. }
          41. }
          42. }
        1. 有些时候更改了某个配置项而他有没有生效?  比如:参数本身是一个数组又或者是一个对象,这个数组本身就存在,而你此次操作只是给数组里面删除了一个对象,最终没有生效。原因是FlyFish默认执行的setOptions是合并数据而不是更新数据把数组进行字符串处理,让他变成一个值,这样就不是合并了。重写setOptions方法,数组里面有的参数都执行更新操作,没有的执行合并操作。

          1. import { defaultsDeep } from "data-vi/helpers";
          2. /**
          3. * 设置选项
          4. *
          5. * @param {Object} options 选项
          6. * @param {boolean} merge 是否合并原来的选项
          7. * @returns {Component}
          8. */
          9. setOptions(options = {}, merge = true) {
          10. const { replaceAll, ...mergeOptions } = options;
          11. const replaceKeys = ['lineBackground'];
          12. // 魔改一下部分结果处理
          13. if (replaceAll) {
          14. this.options = mergeOptions;
          15. } else if (merge) {
          16. let cloneOption = defaultsDeep({}, mergeOptions, this.options);
          17. if (replaceKeys.find((v) => typeof mergeOptions[v] !== 'undefined')) {
          18. cloneOption = {
          19. ...cloneOption,
          20. ...mergeOptions,
          21. };
          22. }
          23. this.options = cloneOption;
          24. } else {
          25. this.options = defaultsDeep({}, mergeOptions, this.getDefaultOptions());
          26. }
        2. 确保在所有组件加载完成后自动执行一个trigger方法?

          1. useEffect(() => {
          2. if (!nowdata) return;//nowdata是请求后端返回来的数据
          3. if (parent && parent.screen) {
          4. const allComponent = parent.screen.getComponents();
          5. const lastComponent = allComponent[allComponent.length - 1];
          6. if (lastComponent.mounted) {
          7. parent.trigger('add', { id: currentItem, value: nowdata })
          8. } else {
          9. lastComponent.bind("mounted", () => {
          10. parent.trigger('add', { id: currentItem, value: nowdata })
          11. lastComponent.unbind("mounted");
          12. })
          13. }
          14. }
          15. }, [nowdata])
        3. 我这个组件怎么去更改别的组件的默认选项?(谨慎操作)

          1. const compontentList = this.props.component.screen.getComponents()
          2. compontentList.forEach((item)=>{
          3. //这里可以做判断对那个组件进行操作
          4. item.setConfig({
          5. visible: true
          6. })
          7. }
        4. 建议不带 get 的 static?

          1. // 默认配置
          2. static defaultConfig = {};
          3. getDefaultConfig() {
          4. return defaultsDeep({}, this.constructor.defaultConfig, {
          5. width: 100,
          6. height: 100,
          7. index: 0,
          8. left: 0,
          9. top: 0,
          10. name: '',
          11. visible: true,
          12. class: ''
          13. });
          14. }
        5. 输入框和FlyFish的事件冲突?

          1. // 禁止冒泡掉
          2. const bubblingFunc= (event)=>{
          3. event.stopPropagation();
          4. }
          5. onKeyUp={bubblingFunc}
          6. onKeyDown={bubblingFunc}
          7. />
        6. 事件可以在组件里面直接写好了!

          1. // 注册事件
          2. registerComponentEvents("id", "DEFAULT_VERSION", {
          3. onChange: "变更",
          4. onValueChange: "值变更",
          5. });
          6. // 注册action
          7. registerComponentAction("id", "DEFAULT_VERSION", "changeValue", ReactCompont);
          8. call(component, "changeValue", ...args);
          9. // ReactCompont;
          10. export default (props) => (
          11. <Form>
          12. <FormItem label="横坐标(X)" cols={[8, 8]}>
          13. <Input
          14. value={toString(props.args[0])}
          15. placeholder="请输入横坐标(X)"
          16. onChange={(event) =>
          17. props.onChange(0, toNumber(event.target.value))
          18. }
          19. />
          20. FormItem>
          21. <FormItem label="纵坐标(Y)" cols={[8, 8]}>
          22. <Input
          23. value={toString(props.args[1])}
          24. placeholder="请输入纵坐标(Y)"
          25. onChange={(event) =>
          26. props.onChange(1, toNumber(event.target.value))
          27. }
          28. />
          29. FormItem>
          30. Form>
          31. );
        7. 静态文件从根目录取绝对路径的该如何设置?

          1. import { DEFAULT_VERSION } from "data-vi/components";
          2. import config from "data-vi/config";
          3. const componentStaticDir = props.parent.getVersion() == null || props.parent.getVersion() === DEFAULT_VERSION ? "components" : "release";
          4. const link = `${config.componentsDir}/${props.parent.getType()}/${props.parent.getVersion() || DEFAULT_VERSION}/${componentStaticDir}/public`;
          5. //webpack.config.production.js文件
          6. const CopyPlugin = require("copy-webpack-plugin");
          7. plugins: [
          8. new CopyPlugin( [
          9. { from: path.resolve(__dirname, '../') + '/src/ModelRotates/public', to: path.resolve(__dirname, '../') + '/components/public/', },
          10. ]),
          11. ]
          12. //安装依赖
          13. "copy-webpack-plugin": "5.1.1"
        8. 组件内需要自己写请求?

          1. import { getHttpData } from 'data-vi/api';
          2. import { componentApiDomain } from 'data-vi/config';
          3. const getMapdata = (name) => {
          4. getHttpData(componentApiDomain + `/atlas/info?location=${encodeURIComponent(name)}`, 'GET', {})
          5. .done((request) => {
          6. console.log('请求成功', request)
          7. setNowdata(request.data)
          8. })
          9. .fail((request, xhr, msg) => {
          10. console.log('失败了')
          11. });
          12. }
        9. 比如跳转大屏如何根据url实现数据变更?

          1. function preDisposeParams(params) {
          2. let sumParams = window.location.search ? window.location.search.split('?')[1] : '';
          3. let eachParams = sumParams.split('&')[1] || '';
          4. let systemCode = eachParams.split('=')[1] || '';
          5. let jsonParams ={
          6. "systemCode":systemCode
          7. }
          8. console.log(sumParams,"-",eachParams,"-",systemCode)
          9. return jsonParams; }
        10. 整张大屏内如何使用字体?【后期可能会更改】  示例组件

          1. /**
          2. * 钩子方法 组件mount挂载时调用
          3. */
          4. _mount() {
          5. const container = this.getContainer();
          6. console.log(this.getType(),this.getVersion(),'123')
          7. const componentStaticDir =
          8. this.getVersion() == null || this.getVersion() === DEFAULT_VERSION ? "components" : "release";
          9. const link = `${config.componentsDir}/${this.getType()}/${
          10. this.getVersion() || DEFAULT_VERSION
          11. }/${componentStaticDir}/assets`;
          12. container.html(`
          13. <style>
          14. @font-face {
          15. font-family: FZZYJW;
          16. src: url('${link}/FZZYJW.TTF');
          17. }
          18. @font-face {
          19. font-family: FZZZHONGJW;
          20. src: url('${link}/FZZZHONGJW.TTF');
          21. }
          22. @font-face {
          23. font-family: HYa9gj;
          24. src: url('${link}/hya9gjm.ttf');
          25. }
          26. @font-face {
          27. font-family: HYk2gj;
          28. src: url('${link}/HYLingXin.ttf');
          29. }
          30. @font-face {
          31. font-family: SourceHanSerifSCHeavy;
          32. src: url('${link}/SourceHanSerifSCHeavy.ttf');
          33. }
          34. style>
          35. `);
          36. }

          开发完成

        11. 点击预览,查看效果是否满足,并简单自测是否有BUG

        1. 导出已完成的可视化大屏

        部署上线

        1. componentApiDomain = 请求后端数据的ip地址

        2. 如果nginx代理没有从根目录配置则需要更改

        3. iplpadImgDir的路径 = ./ (/data/app需要根据nginx代理的具体路径来配置)

        4. components的路径= /data/app/components (/data/app需要根据nginx代理的具体路径来配置)

        标准流程 tengine部署

        1. 修改前端部署包配置文件

        2. 新建前端部署文件夹 web(/data/web/

        3. tengine部署中都以该文件夹为例)

        4. 将前端包文件screen.zip拷贝到该目录下解压命令:unzip screen.zip

        5. 修改/data/app/sxdl_web/config/env.production.js

        6. 修改/data/tengine/conf/vhost/路径 (tengine部署目录为/data/tengine)

        7. 修改/data/app/sxdl_web/index.html (浏览器刷新文本)

        8. 修改/data/app/sxdl_web/config/env.conf.json (浏览器页签文本)

        9. 重启tengine服务 /data/app/tengine/sbin/nginx -s reload 前端访问地址

        nginx部署

        1. 修改前端部署目录xxxx/config/env.production.js配置文件

        2. 配置文件: env.production.js:{ componentApiDomain:后端接口地址 }

        3. 部署环境nginx 注意:大屏前端配置的端口不可以和其他服务的前端的端口冲突

        4. 首先备份/nginx/conf下的nginx.conf

        5. 编辑nginx.conf

        重启ngnix /sbinx下 ./ngnix -t //检查配置文件nginx.conf的正确性 ./ngnix -s reload //重新载入配置文件

        1. 上传screen.zip file.dir: /var/www/html 将解压后screen.zip文件放入该目录(先清空html文件夹)

        2. 备注:如果没有相同的路径,则随便找个路径放文件就行

        3. 解压screen.zip文件

        4. 修改/var/www/html/env.production.js文件 \\修改配置文档: env.production.js

        5. 修改配置后,重启nginx

        6. 项目运行地址: 服务器地址+’/index.html’

        注:前端本次用FlyFish开发页面,直接打出包,git上无仓库)

        下载源码

        1. 前缀:http://10.0.16.230:7001/applications/export-source/(演示样例,真实链接会根据FlyFish本地的地址变化)

        2. 大屏的ID

        1. 最终下载地址(演示样例,非真实地址):http://10.0.16.230:7001/applications/export-source/62134fbaddc0c8314cd3be30

        注:前缀 + 大屏ID = 下载地址(请谨慎频繁调用) Echarts配置及导出:https://www.npmjs.com/package/@cloudwise-fe/chart-panel(如有下载失败,请更换版本号)

        1. 安装依赖: yarn 或 npm install

        2. 启动项目:yarn dev 或 npm run dev

        3. 再次编译:yarn build 或 npm run build

      39. 相关阅读:
        深入理解Nginx线程池【内附原理讲解以及源码分析】
        MySQL索引理解
        SSM框架-SpringMVC(二)
        设计模式探索:适配器模式
        SpringBoot接口 - 怎么处理Controller异常
        LeetCode75——Day9
        图片叠加_图片压缩
        GBase 8c 硬件检查异常修复
        DP28 跳跃游戏(三)
        RocketMq源码分析(三)--Broker启动流程
      40. 原文地址:https://blog.csdn.net/Sharon0408/article/details/126016072