來源| OSCHINA 社區
作者 |雲智慧AIOps社區
原文鏈接:https://my.oschina.net/yunzhihui/blog/5559335
作者:Rise Hao,雲智慧前端開發工程師。開源項目數據可視化平台FlyFishMaintainer。主攻可視化大屏方向,專注工程研發的降本增質、增效,在可視化方面具有豐富的開發經驗 。
FlyFish 是雲智慧公司自主設計、研發的一款低門檻、高拓展性的低代碼應用開發平台, 為數據可視化開發場景提供了高效的一站式解決方案。FlyFish 提供豐富的組件和應用模板庫, 可通過拖拉拽的形式完成數據可視化開發,零開發背景的用戶也可完成數據可視化開發工作。同時,FlyFish 也提供了靈活的拓展能力,支持組件開發、自定義函數與全局事件等配置, 面向複雜需求場景能夠保證高效開發與交付。相關文檔地址
FlyFish 官方開發文檔:http://docs.aiops.cloudwise.com/zh/flyfish/getting-started/
YAPI 數據模擬文檔:https://hellosean1025.github.io/yapi/documents/adv_mock.html
GitHub 地址:https://github.com/CloudWise-OpenSource/FlyFish
Gitee 地址:https://gitee.com/CloudWise/fly-fish
開始前(準備)FlyFish 平台在線地址https://flyfish-demo.opscloudwise.com:23368/#/login創建項目(整體項目名稱)





瀏覽是否有滿足 UI 設計的基礎組件(UI:可視化大屏組件樣式)
添加應用(可視化大屏)
添加當前項目(如果沒有當前項目,有則忽略)
查看是否有當前正要做的項目
開始上手(初級)選擇要開發的應用(可視化大屏)

選擇適合的基礎組件





請求數據的方式
如果僅通過配置項無法滿足當前組件與 UI 的要求,可自定義 CSS,添加 css 名字(會添加到當前組件的最外層,並在全局樣式內進行自定義)
拖入可視化大屏內需要擺放的位置,去選擇合適的配置滿足 UI 的需求
選擇當前項目下的組件(如果有的話...)

開始開發(中級)有類似的項目組件(但是仍需要進行定製化的)


編輯此組件信息,添加到當前項目
複製此組件,並起一個新的名字
添加定製化項目組件(如果基礎組件不具備滿足你當前的需求)

項目組件開發,選擇剛剛創建好的項目組件、點擊開發組件

代碼結構

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 等外部平台開發組件,如有需要可通過引用相關插件的方式去實現。

更新上線

開始進階(高級)設置大屏官方文檔:http://docs.aiops.cloudwise.com/zh/flyfish/advanced/screen.html#%E8%AE%BE%E7%BD%AE%E5%A4%A7%E5%B1%8F事件官方文檔:http://docs.aiops.cloudwise.com/zh/flyfish/advanced/screen.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6
組件之間傳遞事件

組件之間接收事件
收到了組件傳遞過來的事件則會自動執行你的自定義

第一個箭頭傳遞給某個組件事件以及參數
第二個箭頭可以直接觸發某個組件的數據請求
配置組件之間的事件(不配置不會生效喲)






可以選擇修改剛剛定義的事件
接收組件定義的方法(注意不是發送事件的那個喲,當然為了避免容易犯錯誤,你可以將兩個名字設為一致)
選擇剛剛定義的 trigger 事件
如果選擇紅色圓圈內的則作用於整個大屏之上,不在於某個組件內,如果選擇紅色方框則作用於所選的組件內部事件
如果選擇紅框框內的事件則作用在整個組件身上,如果選擇紅色箭頭的事件則按照你剛才創建的方法開始執行
有了剛才組件的內部自定義的事件,我們可以設置之間的關聯
函數官方文檔:http://docs.aiops.cloudwise.com/zh/flyfish/advanced/screen.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%87%BD%E6%95%B0
自定義函數,常見的用法是提供給大屏的事件使用。

全局數據集官方文檔:http://docs.aiops.cloudwise.com/zh/flyfish/advanced/screen.html#%E5%85%A8%E5%B1%80%E6%95%B0%E6%8D%AE%E9%9B%86
全局數據集可以給多個組件使用
開始進階(骨灰級)1、默認選項跟隨數據進行實時渲染? /** * 加載數據 * @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; } ```
重寫 load 方法,因為他可以更新默認的選項 defaultOptions。
2、配置面板如何根據數據實現聯動變化? /* * @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} />, }, } } } ```
在 options.js 文件寫上下面的句子就可以拿到更新之後的數據了。todo (data)
配置面板應該怎麼寫?
/* * @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> }3、有些時候更改了某個配置項而他有沒有生效?
比如:參數本身是一個數組又或者是一個對象,這個數組本身就存在,而你此次操作只是給數組裡面刪除了一個對象,最終沒有生效。原因是 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()); }4、確保在所有組件加載完成後自動執行一個 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])5、我這個組件怎麼去更改別的組件的默認選項?(謹慎操作)const compontentList = this.props.component.screen.getComponents()compontentList.forEach((item)=>{//這裡可以做判斷對那個組件進行操作 item.setConfig({ visible: true })}6、建議不帶 get 的 static? // 默認配置 static defaultConfig = {};getDefaultConfig() { return defaultsDeep({}, this.constructor.defaultConfig, { width: 100, height: 100, index: 0, left: 0, top: 0, name: '', visible: true, class: '' });}7、輸入框和 FlyFish 的事件衝突?// 禁止冒泡掉const bubblingFunc= (event)=>{ event.stopPropagation();}<input onKeyUp={bubblingFunc} onKeyDown={bubblingFunc} />8、事件可以在組件裡面直接寫好了!// 註冊事件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>);9、靜態文件從根目錄取絕對路徑的該如何設置?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"10、組件內需要自己寫請求?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('失敗了') }); }11、比如跳轉大屏如何根據 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; }12、整張大屏內如何使用字體?【後期可能會更改】
示例組件
/** * 鈎子方法 組件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 部署 重啟ngnix /sbinx下 ./ngnix -t //檢查配置文件nginx.conf的正確性 ./ngnix -s reload //重新載入配置文件 ```
修改前端部署目錄 xxxx/config/env.production.js 配置文件
配置文件:env.production.js:{componentApiDomain:後端接口地址}
部署環境 nginx 注意:大屏前端配置的端口不可以和其他服務的前端的端口衝突
首先備份 /nginx/conf 下的 nginx.conf
編輯 nginx.conf
上傳 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