項目開發工作總結(工具類、圖表組件等)

一、前言

       轉眼間,來目前這家公司工作已經近一年了,雖然在這一年中有過很多抱怨(主要是關於產品、設計),但也收穫良多。疫情期間,天災人禍,人要生存,同樣的公司也要,於是理所應當的一系列猛如虎的操作……以前也在想,一個工作到底能給我們本身帶來什麼?當你離職了,你又能得到什麼?最近又不斷的想着這個問題。

      來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);
  }
}

三、最後的總結

    項目封版了,測試還在繼續,嗯!又有新的問題了,先工作以後再寫吧

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章