一、前言
轉眼間,來目前這家公司工作已經近一年了,雖然在這一年中有過很多抱怨(主要是關於產品、設計),但也收穫良多。疫情期間,天災人禍,人要生存,同樣的公司也要,於是理所應當的一系列猛如虎的操作……以前也在想,一個工作到底能給我們本身帶來什麼?當你離職了,你又能得到什麼?最近又不斷的想着這個問題。
來JZ快一年了,還記得去這個時候正辛辛苦苦的準備着找工作,那個時候的自己,剛從後端轉前端,做過一兩個前端項目,但都是用pro框架來寫的,很少涉及到原生的東西,本來就沒啥基礎的自己,CSS不熟、JS不熟,React還只是照貓畫虎,ant組件也馬馬虎虎,找工作時,就算進了三面,也是滿腦子的不自信。
這一年的時間裏,自己寫CSS樣式,而且還是UI朝令夕改的那種,那個時候想着,反正自己啥都不會,就當學習了。因爲工作的原因,用了大量的bizchart圖表,也用到了echart(之前還停留在聽說的階段),還因爲需求原因,不得不去學習純前端導出PDF的實現,當然最主要的是現在的自己在前端開發上最起碼不再那麼不自信,畏畏縮縮了。
在公司負責的項目上線了,我想我也該總結下,畢竟我自己都擔心哪天自己都忘記了那些文件、那些方法是自己一遍又一遍找同事商量、一遍又一遍測試後的產物。
二、工作中思維的火花
2.1、關於接口環境-動態獲取當前項目運行環境
最開始待的那家公司,到後來,前端正式員工,就剩下唯一的自己了,因爲之前的項目要給客戶演示,但演示之前要有小的改動,需要連接本地環境測試,給客戶演示時連接的是客戶的服務器地址,所以,每次都要修改連接的地址。有的時候忘記修改地址了,關鍵是公司的全部文件都是加密的,每次打包都要申請解密,整個流程超級麻煩。老大找到我,說讓我研究下怎麼樣可以不用修改文件之間打包,就是動態的設置項目環境。當時的自己,對這些完全沒有任何經驗,向已經離職的同事抱怨,他們建議我看看webpack文檔,說不定能有收穫。
終於不負所望,通過修改package.json中的node命令可以實現,激動之餘,把所有可能的環境都設置了一遍。代碼如下:
"start": "cross-env UMI_UI=none umi dev",
"start:prod": "cross-env ENVIRONMENT=production umi dev",
"build": "cross-env ENVIRONMENT=production umi build",
找不到最初的代碼了,只好把現在項目的代碼拿出來了,唉!真的是,明明是付出過的,可還是說忘記就忘記了。廢話說太多了,總結下實現思路,如下:
1、定義變量用來區分運行環境
在運行命令時,通過cross-env 定義一個變量並賦不同的值,本次代碼定義了ENVIRONMENT;
2、取出變量並暴露出去
在配置文件config.ts中,通過process.env.變量名取出變量,並根據不同的變量設置不同的服務器地址
const { ENVIRONMENT } = process.env;
define: {
'process.env.HOST': ENVIRONMENT === 'production' ? HostConfig.PROD : HostConfig.DEV,
},
3、在需要使用到的地方(一般是接口請求地址)引入後使用
const { HOST } = process.env;
2.2統一接口校驗-返回數據並根據不同的狀態做出反應
最開始參與公司項目時,一個空的殼子,除了框架做好的基礎,啥都沒有。頻繁的對請求後的接口做是否成功的處理,關鍵是,每個後端返回數據成功的字段不一樣,字段值也不一樣,有着後端開發封裝強迫症的我,再做了幾次判斷後,忍無可忍,於是,寫了一個方法,每次直接調用就行。剛開始前端就我一個,後來隨着參與的人多了,前端大佬又重新定義了一個工具類,嗯!功能確實很全,包括檢查數據是否正確返回,當後端返回爲401時,頁面跳轉到登錄頁面
/**
* Description:統一的 response 狀態碼檢查
*/
import { message } from 'antd';
const SUCCESS_CODE_LIST = ['200', 200, '0', 0]; // 定義成功的狀態碼
const LOGIN_CODE = '401'; // 需要登錄
/**
* 檢查狀態碼是否爲成功
* @param responseObject response對象
*/
export default function checkStatusCode(responseObject: any): boolean {
if (responseObject) {
if (
SUCCESS_CODE_LIST.includes(responseObject.code) ||
SUCCESS_CODE_LIST.includes(responseObject.status)
) {
return true;
}
if (responseObject.code === LOGIN_CODE || responseObject.status === LOGIN_CODE) {
window.location.href = '/login';
return false;
}
}
// 統一的消息提示
if (responseObject && responseObject.message) {
message.error(responseObject.message, 3);
}
return false;
}
2.3 service請求封裝(pro中的service層)
1 、GET、POST、導出Excel等常用請求封裝
/**
* Description:提供通用請求方法
*/
import request from '@/utils/request';
import appendParams from '@/utils/URLUtils';
/**
* GET 請求
* @param url 請求的 url
* @param params 參數
*/
export async function GET(url: string, params?: {}, options?: {}): Promise<any> {
return request(appendParams(url, params as {}), {
...options,
});
}
/**
* POST 請求
* @param url 請求的 url
* @param params 參數
*/
export async function POST(url: string, params?: {}, options?: {}): Promise<any> {
return request(url, {
method: 'POST',
data: params,
...options,
});
}
/**
* PUT 請求
* @param url 請求的 url
* @param params 參數
*/
export async function PUT(url: string, params?: {}): Promise<any> {
return request(url, {
method: 'PUT',
data: params,
});
}
/**
* DELETE 請求
* @param url 請求的 url
* * @param params 參數
*/
export async function DELETE(url: string, params?: {}): Promise<any> {
return request(url, {
method: 'DELETE',
data: params,
});
}
/**
* POST 請求:僅僅用於導出Excel
* @param url 請求的 url
* * @param body 參數
*/
export async function EXPORTEXCEL(url: string, body?: {}): Promise<any> {
return request(url, {
method: 'POST',
data: body,
responseType: 'blob',
});
}
/**
* GET的導出EXCEl
* @param url
* @param body
* @constructor
*/
export async function EXPORTEXCELGET(url: string, params?: {}): Promise<any> {
return request(appendParams(url, params as {}), { responseType: 'blob' });
}
2、GET請求補充代碼
/**
* 將參數拼接至 api
*/
export default function appendParams(api: string, params: object) {
let finalAPI = api;
if (params) {
Object.keys(params).forEach((key, index) => {
// 解決:當請求參數是中文時,IE不兼容,返回400
const formatParams = encodeURIComponent(params[key]);
if (index === 0) {
finalAPI += `?${key}=${formatParams}`;
} else {
finalAPI += `&${key}=${formatParams}`;
}
});
}
return finalAPI;
}
2.4 導出Excel工具類封裝
export function exportExceil(result: any, title: string) {
// 解決IE瀏覽器兼容問題(注意IE緩存)
if ('msSaveOrOpenBlob' in navigator) {
window.navigator.msSaveOrOpenBlob(new Blob([result]), `${title}.xlsx`);
} else {
const blob = new Blob([result]);
const tempLink = document.createElement('a');
tempLink.href = URL.createObjectURL(blob);
tempLink.setAttribute('download', `${title}.xlsx`);
tempLink.click();
}
}
2.5純前端實現導出PDF工具
如果是要完全按照前端來實現,儘量說服產品,不要設置複雜的樣式,這樣就不會有一大堆亂七八糟的問題了
import html2canvas from 'html2canvas';
const a4Width = 595.28;
const a4Height = 841.89;
export function exportPDF(id: string, fileName?: string) {
if (id) {
const realTarget = document.getElementById(id);
if (realTarget) {
if (realTarget) {
// @ts-ignore
html2canvas(realTarget, {
// 色值背景色
backgroundColor: 'white',
scale: 2,
} as any).then((canvas: any) => {
// 將canvas對象處理成PDF文件並實現下載
changToPdf(canvas, fileName);
});
}
}
}
}
// 將圖片轉爲PDF文件並下載
function changToPdf(canvas: any, fileName?: string) {
const context = canvas.getContext('2d');
// 【重要】關閉抗鋸齒
context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;
// 得到canvas畫布的單位是px 像素單位
const contentWidth = canvas.width;
const contentHeight = canvas.height;
const pageData = saveAsPic(canvas);
// 一頁pdf顯示html頁面生成的canvas高度;
const pdfY = (contentWidth / a4Width) * a4Height;
// 未生成pdf的html頁面高度
let realHeight = contentHeight;
// 頁面縱向偏移
let realPosition = 0;
// a4紙的尺寸[595.28,841.89],html頁面生成的canvas在pdf中圖片的寬高
const imgX = a4Width;
const imgY = (a4Width / contentWidth) * contentHeight;
// @ts-ignore
// eslint-disable-next-line new-cap,no-undef
const PDF = new jsPDF('', 'pt', 'a4');
// 將內容圖片添加到pdf中,因爲內容寬高和pdf寬高一樣,就只需要一頁,位置就是 0,0
// 有兩個高度需要區分,一個是html頁面的實際高度,和生成pdf的頁面高度(841.89)
// 當內容未超過pdf一頁顯示的範圍,無需分頁
if (realHeight < pdfY) {
PDF.addImage(pageData, 'jpeg', 0, realPosition, imgX, imgY);
} else {
while (realHeight > 0) {
PDF.addImage(pageData, 'jpeg', 0, realPosition, imgX, imgY);
realHeight -= pdfY;
realPosition -= a4Height;
// 避免添加空白頁
if (realHeight > 0) {
PDF.addPage();
}
}
}
PDF.save(`${fileName || '個人簡歷'}.pdf`);
}
// 將canvas轉爲圖片
function saveAsPic(canvas: any) {
return canvas.toDataURL('image/jpeg', 1.0);
}
2.6前端組件默認顯示的值,以及設置Select寬度問題
沒有統一規範的開發,簡直超級累。一個大點的項目,往往有好多個前端一起開發,凡是涉及到人的,肯定會有好多的不同點。現在依舊記得公司爲了拿到一個項目,讓前端一個星期實現數據寫死的系統。時間緊急,等完工時才發現,下拉框的默認顯示大家的都不一樣,有的是“全部XX”,有的是“所有”,最後產品說要統一,很不幸我就是那個被統一的。一個頁面好多個模塊,每個模塊又有好多個下拉框,所以,改得超級心累。結果,公司領導看了,又說,還是覺得“xxxx”更好,於是,又要改回去。一個小小的細節問題,在產品看來無所謂,先隨便定個,後面不行再改嘛,可是他們根本不知道這種問題比改需求還讓人絕望:繁瑣而又沒有意義。後來調整了自己寫代碼的心態:產品肯定會變得,所以能讓代碼不做改動或者改得少。
嗯!找出共性,封裝起來,不管你想改成啥,分分鐘搞定。
const KEY = '全部';
export function getDefalut(name: string): string {
return `${KEY}${name}`;
}
export function setSelectMinWidth(type?: string): string {
switch (type) {
case 'term':
return '200px';
case 'college':
return '120px';
case 'major':
return '140px';
case 'multiple':
return '120px';
default:
return '110px';
}
}
當然,還有下拉框的寬度問題,每個UI設置的還不一樣,有的寬、有的窄,有的還需要自適應,嗯,這不算什麼,關鍵是驗收時又來了個UI,說要整個系統相同的Select應該統一。我要是一個個去設置,真的要吐血了,還好機智,又做了封裝。
2.7圖表之雙軸座標圖表(bizchart)
雙軸座標的圖表,狹義理解就是一個圖表上有兩個‘y’軸,嗯,官網有demo(https://bizcharts.net/product/bizcharts/demo/8),照貓畫虎也能實現,但是實際開發中就並不會那麼回事了,大概會遇到如下問題:
1、兩邊的grid對不齊;
2、隱藏一邊的grid卻發現grid和另一邊的label對不齊
另外,某些特殊的時候,產品還希望座標刻度必須爲整數,基於以上需求,封裝整理成方法如下:
/**
* 返回數組中經過處理後的最大值、interval、range
* @param arr
* @param pro
* @param tickCount
*/
export function dealChartAxisLabel(arr: any[], pro: string, tickCount: number): any {
if (!arr || (arr && arr.length === 0)) {
return { yAxisMax: 0, yInterval: 0, yRang: [] };
}
// 防止值傳遞引入改變原來數組的問題
const transArr = JSON.parse(JSON.stringify(arr));
// stpe1 排序 --降序
const temp = transArr.sort((a: any, b: any) => b[pro] - a[pro]);
// step2 取最大值(第一個值)
const max = temp[0][pro];
// step3 計算出符合要求的值
const t = tickCount - 1; // 從0開始,0算是第一條線
const yAxisMax = max % t === 0 && max !== 0 ? max : max + (t - (max % t));
const yInterval = yAxisMax / t;
const result: number[] = [];
if (yAxisMax && tickCount && yInterval) {
for (let i = 0; i < tickCount; i += 1) {
result.push(i * yInterval);
}
}
return { yAxisMax, yInterval, yRang: result };
}
2.8常見操作例如增刪改查後提示語
在進入項目開發之前,同事就提醒我,一定要統一提示語,他們剛剛做完的那個項目演示時,同樣的操作,五花八門的提示語,超級尷尬,後面要改成統一的,因爲這些提示語散佈不同的地方,改起來賊麻煩,但確實需要改,很無語。
想想也是,不說演示時的尷尬,但就產品內部的變動就很不好應付了,何況還有領導的意見,嗯!統一起來挺好的。
/**
* 某個操作後的提示語,
* 1包含基本的增刪改查以及刪除前的警告;
* 2表單字段的提示語(placeholder)以及錯誤提示
*/
const HINTS = {
// 查詢數據事,失敗
getDataFailHint: '服務器異常,請稍後重試',
// 增刪改成功後的提示語
// 刪除前的提示音
beforDelHint: '確認刪除所選數據嗎?',
addSuccessHint: '新增成功',
editSuccessHint: '修改成功',
delSuccessHint: '刪除成功',
// 增刪改失敗後的提示語
addFailHint: '新增失敗,請檢查服務器後重試',
editFailHint: '修改失敗,請檢查服務器後重試',
delFailHint: '刪除失敗,請檢查服務器後重試',
// 列表頁面根據 關鍵字 查詢提示語(placeHolder)
/**
* @param oper :組件的操作類型,一般而言,輸入框對應’輸入‘,select框,對應選擇
* @param name
*/
keywordPlaceHint: (name: string, oper = '輸入'): string => `請${oper}${name}進行搜索`,
// 表單元素提示語(placeHolder)
formItemPlaceHint: (name: string, oper = '輸入'): string => `請${oper}${name}`,
// -------------------表單項校驗提示語------------------------
// 必填校驗提示語
emptyCheckHint: (name: string): string => `${name}不能爲空`,
// 範圍提示語 (一般是數字類型)
rangeCheckHint: (min?: number, max?: number, type = '整數'): string => {
if (!!min && !!max) {
return `請輸入${min}~${max}之間的${type}`;
}
if (min) {
// 有最小範圍限制
return `請輸入大於${min}的${type}`;
}
if (max) {
return `請輸入小於${max}的${type}`;
}
return '';
},
// 長度提示語
lengthCheckHint: (min?: number, max?: number): string => {
if (!!min && !!max) {
return `長度應該在${min}~${max}個字符`;
}
if (min) {
// 有最小範圍限制
return `長度不能小於${min}個字符`;
}
if (max) {
return `長度不能超過${max}個字符`;
}
return '';
},
// 電話校驗提示語
phoneCheckHint: '請輸入正確的電話號碼格式',
// 郵箱校驗提示語
emailCheckHint: '請輸入正確的郵箱格式',
// 唯一性校驗提示語
uniqueCheckHint: (key: string): string => `該${key}已存在,請重新輸入!`,
};
export default HINTS;
2.9、Select下拉框默認選中值
一個項目結束了,你以爲結束了,殊不知這纔是剛剛開始,因爲我們做的是產品,哦!原來產品就是不斷的改!改!改!永無止境!
哈哈哈哈,遇到事情先抱怨幾句,發泄一下心中的不滿,然後靜下心來專心想怎麼實現。
項目結束了,項目組有同事離職了,很不幸,我差不多算是這個項目裏唯一剩下的前端了,猝不及防的離職,理所應當的工作交接,然後,然後,然後,所有的優化工作理所應當的歸我了。
話說上線了,實施把真實數據介入了,才發現頁面中大量的暫無數據,然後產品說這樣太醜了吧。嗯!改,默認選中的應該是有數據的選項!有數據的選項?好吧!這下子前後端都慌了,不,前端更慌,整個項目裏,就輸下拉框最多了,都做完了,這會你告訴我要默認顯示有數據的選項!當然,生氣歸生氣,產品的命令還是不能違的,畢竟,好像也挺有道理的。能怎麼辦?一個一個找、一個一個改唄。
從下拉框選擇中提取有數據的選項,其實是一個通用的實現方法,嗯,沒錯,封裝就好了,封裝一到位,每個地方的處理那就基本一行搞定了,只是因爲需求變更,導致接口也要跟着改,這個就真沒法封裝了,還是得慢慢來。
export function getSelectedOption(data: any[], dataKey: string, judgeKey?: string) {
let result = '';
judgeKey = judgeKey || 'selected';
data.forEach((item: any) => {
if (item[`${judgeKey}`]) {
result = item[`${dataKey}`];
}
});
return result;
}
2.10桑基圖
雖然現在還是無法避免的要給測試、產品以及實施人員(有時候不止一個)解釋:爲啥當數據只在第一次有值時,沒辦法顯示桑基圖,只能顯示暫無數據?爲啥某段數據沒了,這段就會跑到最後……,但是回頭想想,確實也學到了好多。另外,桑基圖竟然是我用echart開發的第一個組件,當時還不知道怎麼引入echart,跟着同事的代碼照貓畫虎。
先做一個簡單的概況,使用桑基圖都實現了什麼、以及遇到了哪些坑。
1、bizchart的桑基圖一引入頁面,所在頁面就會內存溢出,當然這是之前的版本了,不知道現在修復了沒,這也是我不得不轉用echart的唯一原因;
2、桑基圖豎向小的item應該是有序的====》每列的item有序並且一致。這是第一個坑,在線編輯了無數次demo,並沒有發現其排序的依據,甚至中間都有想過會不會是根據數量排序的,改變了數量,發現並不是的!嗯,後來發現默認情況下,無論你怎麼設置,桑基圖都會重新排序。
其實文檔有說:
如果想指定每層節點的順序是按照 data 中的順序排列的。可以設置 layoutIterations 爲 0。
3、桑基圖每個item的顏色應該固定,嗯!還要和順序保持一致,但是桑基圖有幾個item是不固定的。其實最開始UI給的圖時漸變色,不是每個item顏色一致,是每一次顏色漸變。開發時間比較緊急,問同事,大家好像之前沒遇到過,就說你直接給產品說實現不了吧,對!確實是告訴產品實現不了了,結果產品發來某截圖告訴我,人家都可以實現……不過,因爲確實比較麻煩,也沒有逼得很緊,等項目提測了,又完完整整的看了一遍關於桑基圖的文檔,一次又一次測試,實現了
const option = {
animation: false,
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
},
series: {
layoutIterations: 0,
type: 'sankey',
layout: 'none',
focusNodeAdjacency: 'allEdges',
nodeGap: 6,
label: {
color: '#FFF',
},
levels: [
{
depth: 0,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#7D4AFF', // 0% 處的顏色
},
{
offset: 1,
color: '#D74AFF', // 100% 處的顏色
},
],
global: false, // 缺省爲 false
},
},
// lineStyle: {
// color: 'source',
// opacity: 0.6,
// },
},
{
depth: 1,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#4A5FFF', // 0% 處的顏色
},
{
offset: 1,
color: '#4AB4FF', // 100% 處的顏色
},
],
global: false, // 缺省爲 false
},
},
// lineStyle: {
// color: 'source',
// opacity: 0.6,
// },
},
{
depth: 2,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#34B4CB', // 0% 處的顏色
},
{
offset: 1,
color: '#51AD6F', // 100% 處的顏色
},
],
global: false, // 缺省爲 false
},
},
},
{
depth: 3,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#FF5C5C', // 0% 處的顏色
},
{
offset: 1,
color: '#DD763E', // 100% 處的顏色
},
],
global: false, // 缺省爲 false
},
},
},
{
depth: 4,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#D71ED5', // 0% 處的顏色
},
{
offset: 1,
color: '#EB518F', // 100% 處的顏色
},
],
global: false, // 缺省爲 false
},
},
},
],
lineStyle: {
color: 'source',
opacity: 0.3,
curveness: 0.5,
backgroundColor: '#FFF',
},
data,,
links,
},
然而,產品看了卻說太花裏胡哨了,我勒個去,UI出了高保時你咋不說!
後來,實施介入,說需要的不是每一層一種顏色,而是每一個item一種顏色,並在此發截圖說明,這下子真的不知道怎麼辦了,後放繼續後放。不知道哪天產品又找我,說讓我再研究研究,行吧!又看了好久的文檔,得出結論:肯定不能設置死,必須是動態的,item是動態的,顏色先預設幾種,在數據處理時想辦法讓顏色和item對應起來。對!顏色設置在option中的data中。代碼大概如下:
step1:首先將每個item和顏色對應起來
for (let i = 0; i < data.length; i += 1) {
colorData.push({
name: data[i] ? data[i].name: '',
color: colors1[i],
});
}
step2:通過雙層for循環將每條數據和顏色對應起來
data2.forEach((item: any) => {
colorData.forEach((each: { name: string; color: string }) => {
if (item.name.indexOf(each.name) === 0) {
data3.push({
name: item.name,
count: item.count,
itemStyle: {
normal: {
color: each.color,
borderColor: each.color,
},
},
});
}
});
});
4、懸浮提示
官網的demo是沒有懸浮提示的,並且當時的版本好像還不支持,剛對產品說無法實現,熟手意間把代碼複製到官網,發現竟然可以!
看更新文檔才發現原來新版本纔可以,而且新的版本才發佈沒幾天,果斷更新版本,真懸
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
formatter: (params: any) => {
const temp = params.data;
if (temp) {
if (temp.name && temp.count) {
// 柱子
return `${temp.name}:${temp.count}`;
}
if (temp.source && temp.target && temp.value) {
// 區間
return `${temp.source}--${temp.target}:${temp.value}`;
}
}
return '';
},
},
5、要有下鑽:①柱子和區間下鑽到不同的頁面②鼠標懸浮展示成小手形狀
子和區間下鑽到不同的頁面:下鑽通過圖表的click實現,柱子和區間通過數字區分;
鼠標懸浮展示成小手形狀:click事件中修改元素樣式
sankey.on('click', (params: any) => {
if (params.event.event.toElement) {
params.event.event.toElement.style.cursor = 'pointer';
}
const { collegeCode = '', educationCode = '', grade = '', majorCode = '' } = query;
if (params.data.value) {
router.push({
pathname: pn1,
query: {
collegeCode,
educationCode,
grade,
majorCode,
rankSource: params.data.source,
rankTarget: params.data.target,
},
});
// 存在value,點擊的是區間
} else {
// 不存在,這說明點擊的是柱子
router.push({
pathname: pn2,
query: {
collegeCode,
educationCode,
grade,
majorCode,
rankName: params.data.name,
},
});
}
});
6、每個item進入和出去的數量不一樣,到底顯示哪一個?
在自定義tootip時,無論你怎麼選擇都只能選擇其中一個,但仔細思考桑基圖的整個流程,你會發現哪一個都有可能不對,最後決定讓後端再給一個字段顯示應該顯示的值。(tooltip代碼中已經處理過)
此致,整個桑基圖基本完工了,也滿足產品需求,現整理代碼如下:
// 桑基圖
import * as eCharts from 'echarts';
import router from 'umi/router';
export function getSankeyComponent(
param: { data: any[]; link: any[] },
query: {
educationCode: string;
grade: string;
collegeCode: string;
majorCode: string;
},
) {
const element: any = document.getElementById('sankey');
if (element) {
const sankey = eCharts.init(element);
const option = {
animation: false,
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
formatter: (params: any) => {
const temp = params.data;
if (temp) {
if (temp.name && temp.count) {
// 柱子
return `${temp.name}:${temp.count}`;
}
if (temp.source && temp.target && temp.value) {
// 區間
return `${temp.source}--${temp.target}:${temp.value}`;
}
}
return '';
},
},
series: {
layoutIterations: 0,
type: 'sankey',
layout: 'none',
focusNodeAdjacency: 'allEdges',
nodeGap: 6,
label: {
color: '#FFF',
},
lineStyle: {
color: 'source',
opacity: 0.3,
curveness: 0.5,
backgroundColor: '#FFF',
},
data: param.data,
links: param.link,
},
};
sankey.off('click');
sankey.on('mouseover', (params: any) => {
if (params.event.event.toElement) {
params.event.event.toElement.style.cursor = 'pointer';
}
});
sankey.on('click', (params: any) => {
if (params.event.event.toElement) {
params.event.event.toElement.style.cursor = 'pointer';
}
const { collegeCode = '', educationCode = '', grade = '', majorCode = '' } = query;
if (params.data.value) {
router.push({
pathname: '/firstClass/achievementTrendAnalysis/gradeTierChangeStudent',
query: {
collegeCode,
educationCode,
grade,
majorCode,
rankSource: params.data.source,
rankTarget: params.data.target,
},
});
// 存在value,點擊的是區間
} else {
// 不存在,這說明點擊的是柱子
router.push({
pathname: '/firstClass/achievementTrendAnalysis/gradeTierStudent',
query: {
collegeCode,
educationCode,
grade,
majorCode,
rankName: params.data.name,
},
});
}
});
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
sankey.setOption(option);
}
}
三、最後的總結
項目封版了,測試還在繼續,嗯!又有新的問題了,先工作以後再寫吧