一、前言

转眼间,来目前这家公司工作已经近一年了,虽然在这一年中有过很多抱怨(主要是关于产品、设计),但也收获良多。疫情期间,天灾人祸,人要生存,同样的公司也要,于是理所应当的一系列猛如虎的操作……以前也在想,一个工作到底能给我们本身带来什么?当你离职了,你又能得到什么?最近又不断的想着这个问题。

来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不兼容,返回400const 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-ignorehtml2canvas(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-undefconst 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;
}

----------------------------更新-------------------------------------------

如果,后端返回数据的逻辑突然发生变化了呢?嗯!后来后端返回的逻辑是:有数据的标识为true====》数据中有多个符合要求===》有多个符合要求时,取第一个

export function getSelectedOptionCode(data: any[], dataKey: string, judgeKey?: string) {let result = '';if (data && data.length > 0) {judgeKey = judgeKey || 'selected';// judgeKey对应值为true的optionconst trueOptions = data.find((item: any) => item[`${judgeKey}`]);if (!!trueOptions) {result = trueOptions[`${dataKey}`];} else {// 全部为false,取第一个result = data[0][`${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-ignoresankey.setOption(option);}
}

三、最后的总结

项目封版了,测试还在继续,嗯!又有新的问题了,先工作以后再写吧

项目开发工作总结(工具类、图表组件等)相关推荐

  1. Android项目中常用的工具类集(史上最全整理)

    如果你是一名有经验的Android开发者,那么你一定积累了不少的工具类,这些工具类是帮助我们快速开发的基础.如果你是新手,那么有了这些辅助类,可以让你的项目做起来更加的简单. 下面介绍一个在GitHu ...

  2. uni-app项目使用uCharts高性能跨全端图表组件

    前言: uCharts全新图表组件,全端全平台支持,开箱即用,可选择uCharts引擎全端渲染,也可指定PC端或APP端单独使用ECharts引擎渲染图表.支持极简单的调用方式,只需指定图表类型及传入 ...

  3. Android开发实现HttpClient工具类

    在Android开发中我们经常会用到网络连接功能与服务器进行数据的交互,为此Android的SDK提供了Apache的HttpClient来方便我们使用各种Http服务.你可以把HttpClient想 ...

  4. 物联网项目开发工作笔记0001---物联网项目的开发周期,项目管理,厂家合作

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 这里以我现在做的这个物联网项目(公网对讲产品)为例,因为第一次作为项目经理,带着做物联网项目,所以 ...

  5. Java开发中的工具类——基于JedisPool的Redis工具类

    目录 一.Maven依赖 二.Redis配置类 三.使用@Cacheable注解进行数据缓存 四.自定义Redis工具类及使用 4.1 序列化工具类 4.2 redis客户端工具类 4.3 redis ...

  6. 直播平台开发,验证码工具类

    直播平台开发,合理使用验证码工具类 package com.ywfcake.demo.util;import lombok.extern.slf4j.Slf4j;import java.awt.*; ...

  7. java 项目中常用的工具类总结

    1.文件 1.根据图片的链接,下载图片 package com.lingxu.module.BigDataJoinMessage.util;import java.io.FileOutputStrea ...

  8. 顺丰丰桥接口开发-java(工具类)

    上一篇讲到我们用的是xml与bean之间的转换的方式来处理数据,那么就需要用到一些类库来解决,网上搜一下JAXBContext,就有很多相关的文章介绍(感谢大咖们的贡献),主要涉及两个方法如下: /* ...

  9. java练习案例_Java项目案例之---常用工具类练习

    常用工具类练习 1. 请根据控制台输入的特定日期格式拆分日期,如:请输入一个日期(格式如:**月**日****年),经过处理得到:****年**月**日 importjava.util.Scanner ...

最新文章

  1. 【一天一个shell命令】文本操作系列-touch
  2. 关于CE端口线路整改的建议
  3. js获取时间(yyyy-MM-dd HH:mm:ss)
  4. c 服务器文件是存在,客户端服务器在较大的文件的c文件传输问题
  5. 实验4 贪心法(作业调度问题)
  6. Android开发自学笔记(基于Android Studio1.3.1)—1.环境搭建(转)
  7. camel java_与Java EE和Camel的轻量级集成
  8. 【ES6(2015)】Function函数
  9. 【POI 2007】Ridges and Valleys山峰和山谷(GRZ)
  10. ViewState - 页面状态保留
  11. 通过keil hex2bin,bin2hex的方法
  12. 中国AI专利数稳居第一!世界各国AI专利深度盘点
  13. 【ASE学习】-测量石墨烯结构的碳碳键平均键长
  14. 【python爬虫】反反爬之破解js加密--入门篇:谷歌学术镜像搜索(scmor.com)
  15. Tableau 中国最美八条骑行线路(三)天数与预算
  16. 软件开发工程师工作总结(转)
  17. vagrant 强制关机 Warning: Authentication failure. Retrying...
  18. 计算机图形学--扫描线填充算法
  19. html div图片拉伸,使图像完全填充div而无需拉伸
  20. 计算机网络05局域网

热门文章

  1. 摔跤吧爸爸-影评感悟(匍匐泥泞,不忘星空-目标)
  2. 装配体机械制图注意事项
  3. 基于灰狼算法优化支持向量机的数据分类算法及其MATLAB实现-附代码
  4. STM32系统学习——RCC(使用HSE/HSI配置时钟)
  5. 易基因项目集锦|易基因科技6篇SCI成果大公开,篇篇IF>5
  6. 没有可用软件包httpd_CentOS 7源码安装httpd服务
  7. .xls+.xlsx转.xml
  8. qq斗图之电脑斗图---如何立于不败之地
  9. 基于51单片机的光照强度检测系统
  10. 看PS自学教程 提高设计效率