作者:Rise Hao,云智慧前端开发工程师。开源项目数据可视化平台 FlyFish Maintainer。主攻可视化大屏方向,专注工程研发的降本增质、增效,在可视化方面具有丰富的开发经验 。
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 文件写上下面的句子就可以拿到更新之后的数据了。
/*
* @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’
注:前端本次用 FlyFish 开发页面,直接打出包,git 上无仓库)
下载源码
前缀:http://10.0.16.230:7001/applications/export-source/(演示样例,真实链接会根据 FlyFish 本地的地址变化)
大屏的 ID
最终下载地址(演示样例,非真实地址):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
评论