作者:Rise Hao,云智慧前端开发工程师。开源我的项目数据可视化平台 FlyFish Maintainer。主攻可视化大屏方向,专一工程研发的降本增质、增效,在可视化方面具备丰盛的开发教训。
FlyFish 是云智慧公司自主设计、研发的一款低门槛、高拓展性的低代码利用开发平台,为数据可视化开发场景提供了高效的一站式解决方案。FlyFish 提供丰盛的组件和利用模板库,可通过利落拽的模式实现数据可视化开发,零开发背景的用户也可实现数据可视化开发工作。同时,FlyFish 也提供了灵便的拓展能力,反对组件开发、自定义函数与全局事件等配置,面向简单需要场景可能保障高效开发与交付。
相干文档地址
- FlyFish 官网开发文档
- YAPI 数据模仿文档
- GitHub 地址
- Gitee 地址
开始前(筹备)
-
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: "值变更",
});
// 注册 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/appli…
演示样例,实在链接会依据 FlyFish 本地的地址变动
- 大屏的 ID
-
最终下载地址(演示样例,非实在地址):http://10.0.16.230:7001/appli…
注:前缀 + 大屏 ID = 下载地址(请审慎频繁调用)
Echarts 配置及导出(如有下载失败,请更换版本号):https://www.npmjs.com/package…
- 装置依赖: yarn 或 npm install
- 启动我的项目:yarn dev 或 npm run dev
- 再次编译:yarn build 或 npm run build