工具问题
JSON中key不能用number类型
- JSON中使用数字作为Key带来的问题,浏览器解析JSON会报错,JSON中key不能用number类型
- json格式如何出现number作为key的话,解析会出现问题
JSON序列化的时候,使用JSON.parse解析会报错
echart
echart 折线图倒叙问题
1.后端返回数据,且对数据不好倒序时(比如时间反序)。
2.根据页面的按钮,变换X或Y的坐标轴数据。
- 翻转X坐标数据,
inverse:true就加在xAxis中,- 相反,翻转Y坐标,就加在
yAxis中。 - 同时加,就是都翻转。
- 相反,翻转Y坐标,就加在
折线图markLine标线 值接近时候出现重合问题
- 使用position的值为’insideStartTop’, ‘insideStartBottom’, ‘insideMiddleTop’, ‘insideMiddleBottom’, ‘insideEndTop’, 'insideEndBottom’时, 可以进行上下位置的调整。
如:
markLine: {
symbol: 'none',
label: {
position: 'end',
formatter: '{b}:{c}(ug/m³)'
},
data: [
{
name: '名称a',
yAxis: 400,
lineStyle: {
color: 'red',
type: 'dashed', // 线的类型
width: 3
},
label: {
position: 'insideEndTop',
distance: [-125, 20],
color: 'red',
formatter: '{b}:{c}(ug/m³)'
}
},
{
name: '名称c',
yAxis: 410,
lineStyle: {
color: 'red',
type: 'dashed', // 线的类型
width: 3
},
label: {
position: 'insideEndBottom',
distance: [0, 30],
color: 'red',
formatter: '{b}:{c}(ug/m³)'
}
}
},
折线图显示百分比
- 在折线图 Y轴显示百分比
option.yAxis.axisLabel = {
show: true,
interval: 'auto',
formatter:'{value}%',
}
- 自定义
TOOLTIP文本携带百分比
let isPercent = true;
const createTopPercent = data => {
return `${data.name}</br> ${data.marker} ${data.seriesName} :${data.data}${isPercent ? `%` :``}`
}
//将覆盖默认显示文本携带百分比
option.tooltip = {
trigger: 'axis',
formatter(data){
return data.map((data) => createTopPercent(data))
}
}
显示标注 markPoint
如:
markPoint: {
symbol: 'pin', //标记(气泡)的图形
symbolSize: 50, //标记(气泡)的大小
itemStyle: {
// color: '#4587E7', //图形的颜色。
borderColor: '#000', //图形的描边颜色。支持的颜色格式同 color,不支持回调函数。
borderWidth: 0, //描边线宽。为 0 时无描边。
borderType: 'solid', //柱条的描边类型,默认为实线,支持 ‘solid’, ‘dashed’, ‘dotted’。
},
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' },
],
},
demo
{
name: '库存金额',
type: 'line',
symbolSize: 12,
symbol: 'circle',
itemStyle: {
color: '#339dff',
borderColor: '#fff',
borderWidth: 1
},
showSymbol: true,
markPoint: {
symbol: 'circle', //标记(气泡)的图形
symbolSize: 10, //标记(气泡)的大小
lable: {
borderWidth: 0
},
itemStyle: {
color: '#dd1e36', //图形的颜色。
borderColor: '#dd1e36', //图形的描边颜色。支持的颜色格式同 color,不支持回调函数。
borderWidth: 3 //描边线宽。为 0 时无描边。
// borderType: 'solid' //柱条的描边类型,默认为实线,支持 ‘solid’, ‘dashed’, ‘dotted’。
},
data: map(markData, (item) => ({ coord: [item.date, item.value] }))
},
data: map(skuStockAmtChart, (item) => [item.date, item.value])
}
echarts-for-react/echart更新数据出现闪烁解决办法
问题:在组件中配置事件后,变更option状态,图标竟然闪烁更新,而且更新同组件内部的其他状态时,图标也闪烁
原因:这是在绑定onEvents后出现的问题
解决:可以通过onChartReady回调,拿到实例去绑定事件
- onChartReady当图表渲染完成触发,可以拿到图表实例
例子
//绑定onEvents
onChartEvents = ()=>{
return {
click:(data)=>{console.log(data)}
}
}
//onChartReady
onChartReady = (instance)=>{
if(instance){
instance.on('click',data=>{
console.log(data)
})
}
}
echart 柱状图 纵坐标上下对称 [双向柱状图]
参考:https://juejin.cn/post/6844904018129469447
-
使用拼接图,上面是一个图表,下面是另一个图表
-
这样设置x轴上下两个部分就是两个不同的echart,会分别以不同的纬度渲染,不会相互影响。
注意
- 会有两个x轴,y轴,要调整只显示一个x轴和y轴
- grid的设置一定要注意,上面图表的bottom和下面图表的top相加要为100%
- 以上option可以直接拿到echart官网实例中直接展示
option = {
title: {
text: '示例',
left:"center",
subtext: '',
textStyle:{
color:"#000"
}
},
color:['#60c4c4','#fca268', '#d48265', '#91c7ae','#749f83', '#ca8622', '#bda29a','#6e7074', '#546570', '#c4ccd3'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
},
legend: {
left:"right",
data: ['示例1','示例2'],
textStyle:{fontSize:16}
},
toolbox: {
show: false
},
Calculable: true,
grid: [{bottom:"50%"},{top:'50%'}],
xAxis: [
{
type: 'category',
axisLine: {show: true, onZero: false},
axisTick: {show: false},
axisLabel: {show: false,color:"green",fontSize:20},
splitArea: {show: false},
splitNumber: 20,
splitLine: {show: false},
data:[],
position:"bottom",
zlevel:10,
show:true,
min:0,
nameTextStyle:{fontSize:16},
},
{
type: 'category',
show:true,
axisLine: {show: true, onZero: false},
axisTick: {show: false},
axisLabel: {show: true,color:"green",fontSize:16},
splitArea: {show: false},
splitNumber: 20,
splitLine: {show: false},
data:[],
gridIndex:1,
position:"bottom",
zlevel:10,
min:0,
nameTextStyle:{fontSize:16},
}
],
yAxis: [
{
type: 'value',
name:"示例1",
axisLine: {show: true, onZero: false,lineStyle:{color:"#000"}},
axisLabel: {formatter: '{value}',fontSize:16},
minInterval:1,
splitLine:false,
nameTextStyle:{fontSize:16},
min:0
},
{
type: 'value',
name:"示例2",
axisLine: {show: true, onZero: false,lineStyle:{color:"#000"}},
axisLabel: {formatter: '{value}',fontSize:16},
minInterval:1,
position:"left",
splitLine:false,
gridIndex:1,
nameGap:30,
inverse:true,
nameTextStyle:{fontSize:16},
min:0
}
],
series: [
{type:"bar",name:"示例1",barMaxWidth:40,data:[20, 40, 60, 80, 100]},
{type:"bar",name:"示例2",barMaxWidth:40,data:[100000, 1, 5000, 50, 40000],xAxisIndex: 1,yAxisIndex: 1}
]
};
echart y轴显示太长,鼠标显示悬浮
- 限制显示3位,超过以...显示
chartEventMouseover = (params) => {
const { event, componentType, value } = params
const x = event.event.layerX + 10
const y = event.event.layerY + 20
if (componentType == 'yAxis') {
//定义额外的容器来显示悬浮
const extension_tooltip = document.querySelector('#extension_tooltip')
//设置样式
extension_tooltip.setAttribute('class', styles.extension_tooltip)
//设置定位
extension_tooltip.setAttribute('style', 'display:block;' + `left:${x}px;` + `top:${y}px`)
// 插入值
extension_tooltip.innerHTML = value
}
}
样式:
.extension_tooltip {
position: absolute;
background: #565656;
font-family: Arial;
font-size: 12px;
padding: 6px 12px;
display: inline;
color: #fff;
}
地图放大
参考:https://echarts.apache.org/zh/option.html#geo.roam
https://segmentfault.com/a/1190000018483921
geo配置 【地理坐标系组件】
- geo.roam
- 是否开启鼠标缩放和平移漫游。默认不开启。如果只想要开启缩放或者平移,可以设置成
'scale'或者'move'。设置成true为都开启
- 是否开启鼠标缩放和平移漫游。默认不开启。如果只想要开启缩放或者平移,可以设置成
1、引入
import ReactECharts from 'echarts-for-react'
import * as echarts from 'echarts'
import chinaJson from '../../../assets/map/json/china.json'
// 导入中国地图
echarts.registerMap('china', chinaJson)
2、解决更新数据,图表重新渲染闪烁
可以通过onChartReady回调,拿到实例去绑定事件
onChartReadyCallback = (ecahrt) => {
ecahrt.on('georoam', (params) => this.chartMapGeoroam(params, ecahrt))
}
3、根据地图缩放,拿到当前位置数据
1、缩放展示市对应的所有城市级别,无法实现,需要使用百度地图/高德地图api,需要收费
2、或者使用ecahrt,【交互会比较麻烦】
- 缩放之后,需要点击展示对应的所有城市级别,但是如果返回上一级的话,会比较麻烦
- 有个下拉输入框,选择对应的市,请求展示的所有城市,json替换
echarts.registerMap('china', chinaJson)
自定义label格式 【series.label】
参考:https://echarts.apache.org/zh/option.html#series-line.label.rich
-
使用rich字段 【series.label.rich】
-
通过formatter去引用rich富文本的样式
label: {
show: true,
formatter: (v)=>{
return `{up|上升}`
},
rich:{
up:{
color:'red'
}
}
柱状图宽度自适应
- 不要设置barWidth即可,可设置barMaxWidth
dom多次执行setoption tooltip无法清除【会不断叠加】
https://echarts.apache.org/zh/api.html#echartsInstance.setOption
- echart会自动合并新的参数和数据,然后刷新图表
- 通过notMerge属性去设置,可选。是否不跟之前设置的
option进行合并。默认为false。即表示合并。合并的规则,详见 组件合并模式。如果为true,表示所有组件都会被删除,然后根据新option创建所有新组件。
- 通过notMerge属性去设置,可选。是否不跟之前设置的
chart.setOption(option, {
notMerge: ...,
lazyUpdate: ...,
silent: ...
});
x轴缩放滚动 dataZoom
- x轴数量量大时候,一般需要缩放滚动
- 利用dataZoom属性
dataZoom: [
{
type: 'slider',
show: size(data) > 12, // 数量大于12条,才显示缩放
xAxisIndex: [0],
left: '5%',
right: '5%',
bottom: 10,
start: 0
}
],
修改dataZoom 区域缩放组件样式
基于echart-for-react封装图表
- ExtendEchart.jsx
import React, { createRef } from 'react'
import {
chain,
get,
keys,
keyBy,
map,
reduce,
castArray,
pickBy,
includes,
set,
head,
last,
isPlainObject,
filter
} from 'lodash'
import ReactECharts from 'echarts-for-react'
import SpinWrapper from 'component/SpinWrapper'
import classNames from 'classnames'
import styles from './index.less'
// 默认参数
const defaultConfig = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
confine: true, // 将tooltip框限制在图表的区域内,如tooltip 超出外界被截断时,此配置比较有用
axisPointer: {
type: 'shadow'
}
},
legend: {
bottom: 20,
icon: 'react',
itemWidth: 14,
itemHeight: 14
},
grid: {
top: 50,
left: 50,
right: 50,
bottom: 50,
containLabel: true // 防止标签溢出
},
xAxis: {
type: 'category',
axisLine: {
lineStyle: {
width: 1,
type: 'solid'
}
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
width: 1,
type: 'solid'
}
}
}
}
class ExtendEcharts extends React.Component {
constructor(props, context) {
super(props, context)
this.tooltipRef = createRef()
}
// 处理y轴 yConfig的配置会添加到yAxis数组每项配置中
// y轴配置(悬浮显示、限制显示个数) yConfig => tooltip, labelLength
renderYAxis = (configParma) => {
const { yConfig, echartsParam = {} } = this.props
// formatter存在时,labelLength和after、before会不生效
const { tooltip, labelLength, formatter: yFormatter, after = '', before = '', ...restConfig } = yConfig || {}
set(configParma, 'triggerEvent', tooltip)
const { axisLabel = {}, ...commonConfig } = { ...restConfig, ...configParma }
const { formatter } = axisLabel
// 处理formatter, axisLabel的formatter优先级比yFormatter高
let renderFormatter = formatter ? formatter : yFormatter ? yFormatter : null
if (!renderFormatter) {
renderFormatter = (value) => {
if (labelLength && value.length > labelLength) {
return value.substring(0, labelLength) + '...'
}
return `${before}${value}${after}`
}
}
return {
...commonConfig,
axisLabel: {
...axisLabel,
formatter: renderFormatter
}
}
}
// 处理x轴
// x轴配置 ,labelLength限制显示个数 after添加到文本后面,before添加到文本前面
renderXAxis = (configParma) => {
const { xConfig, echartsParam = {} } = this.props
// formatter存在时,labelLength和after、before会不生效
const { after = '', before = '', labelLength, xFormatter, ...restConfig } = xConfig || {}
const { axisLabel = {}, ...commonConfig } = { ...restConfig, ...configParma }
const { formatter } = axisLabel
// 处理formatter, axisLabel的formatter优先级比yFormatter高
let renderFormatter = formatter ? formatter : xFormatter ? xFormatter : null
if (!renderFormatter) {
renderFormatter = (value) => {
if (labelLength && value.length > labelLength) {
return value.substring(0, labelLength) + '...'
}
if (after || before) {
return `${before}${value}${after}`
}
return value
}
}
return {
...commonConfig,
axisLabel: {
...axisLabel,
formatter: renderFormatter
}
}
}
//处理series, seriesConfig的配置会添加到series数组每项配置中 【seriesConfig配置用于series每项共同的属性】
renderSeries = (configParma) => {
const { seriesConfig } = this.props
return {
...seriesConfig,
...configParma
}
}
// 处理Tooltip
// 1、数值加百分比 (比较多的场景)
// 2、让用户自定义格式
renderTooltip = (configParma) => {
const { tooltipConfig } = this.props
const { formatter: formatterConfig, percent = false } = tooltipConfig || {}
const { formatter } = configParma
// 处理formatter, tooltip的formatter优先级比formatterConfig高
let renderFormatter = formatter ? formatter : formatterConfig ? formatterConfig : null
const createItem = (data, percent) => {
return `${data.marker} ${data.seriesName} : ${last(data.data)}${percent ? `%` : ''}</br>`
}
if (!renderFormatter) {
renderFormatter = (data) => {
const str = `${get(data, [0, 'name'])}</br>`
return `${str}${data
.map((data) => createItem(data, percent))
.join()
.replaceAll(',', '')}`
}
}
return {
...tooltipConfig, // 共同属性参数
...configParma,
formatter: renderFormatter
}
}
//处理Legend
//1、data:处理图例的数据数组
renderLegend = (configParma) => {
const { legendConfig, echartsParam } = this.props
const { series } = echartsParam || {}
const data = keys(keyBy(series, 'name'))
return {
data,
...configParma
}
}
// 设置悬浮
chartEventMouseover = (params, echart) => {
const { yConfig, onEvents } = this.props
const { mouseover } = onEvents || {}
const { tooltip } = yConfig || {}
if (tooltip) {
const { event, componentType, value } = params
const x = event.event.layerX + 10
const y = event.event.layerY + 20
if (componentType == 'yAxis' && value) {
const { current } = this.tooltipRef
current.setAttribute('class', styles.extension_tooltip)
current.setAttribute('style', 'display:block;' + `left:${x}px;` + `top:${y}px`)
current.innerHTML = value
}
}
mouseover && mouseover(params, echart)
}
chartEventMouseout = (params, echart) => {
const { yConfig, onEvents } = this.props
const { mouseout } = onEvents || {}
const { tooltip } = yConfig || {}
if (tooltip) {
const { current } = this.tooltipRef
if (current) {
current.setAttribute('style', 'display:none')
}
}
mouseout && mouseout(params, echart)
}
// legend切换时候,根据item来动态匹配对应选中项x轴 ,legendConfig配置
// 注意:series的data项数据用一个二维数组表示才有效果。每一列被称为一个『维度』如:使用[[x1,y1],[x2,y2]]数据格式才有效果 (series使用了二维数组,就不需要使用xAxis,yAxis)
chartEventLegendselect = (params, echart) => {
const { legendConfig, onEvents } = this.props
const { legendselectchanged } = onEvents || {}
const { changeX, customChange } = legendConfig || {}
const { series } = echart.getOption()
const { name, selected } = params
// 获取选中的项
const selectItem = keys(pickBy(selected, (item) => item))
const filterSelectItem = filter(series, (item) => includes(selectItem, item.name))
// 获取所有选中项的x轴数据
const selectX = chain(filterSelectItem)
.map((item) => item.data.map((value) => head(value)))
.flatten()
.uniq()
.orderBy()
.value()
//设置x轴,格式
if (changeX && !customChange) {
echart.setOption({ xAxis: { data: selectX } })
}
// 可以让用户自定义改变设置x轴格式数据
if (customChange) {
customChange && customChange({ params, echart, selectX })
}
legendselectchanged && legendselectchanged(params, echart, selectX)
}
// 渲染图表配置
renderOptionConfig = () => {
const { xConfig, yConfig, tooltipConfig, echartsParam = {} } = this.props
const { tooltip, legend, grid, xAxis, yAxis, series = [], ...options } = echartsParam
const genOption = reduce(
{ tooltip, legend, grid, xAxis, yAxis, series, ...options },
(acc, cur, key) => {
if (typeof cur === 'string') {
return { ...acc, [key]: cur }
}
if (isPlainObject(cur) && !includes(['xAxis', 'yAxis', 'series', 'tooltip', 'legend'], key)) {
return { ...acc, [key]: { ...defaultConfig[key], ...cur } }
}
const mergeConfig = reduce(
castArray(cur),
(a, b) => {
const configParma = { ...defaultConfig[key], ...b }
const configAction = {
legend: () => this.renderLegend(configParma),
tooltip: () => this.renderTooltip(configParma),
xAxis: () => this.renderXAxis(configParma),
yAxis: () => this.renderYAxis(configParma),
series: () => this.renderSeries(configParma)
}
if (includes(keys(configAction), key)) {
const config = configAction[key]()
return [...a, config]
}
return [...a, configParma]
},
[]
)
return { ...acc, [key]: mergeConfig }
},
{}
)
return genOption
}
render() {
const {
className,
echartsParam,
onEvents,
loading,
wrapperStyle,
spinWrapperParam,
style = { height: '200px' },
...props
} = this.props
const onEchartEvents = {
...onEvents,
mouseover: this.chartEventMouseover,
mouseout: this.chartEventMouseout,
legendselectchanged: this.chartEventLegendselect
}
const { series } = echartsParam || {}
const isEmptyData = map(series, (item) => item.data).flat()
return (
<div style={wrapperStyle} className={classNames(styles.extend_echarts, className)}>
<SpinWrapper data={isEmptyData} loading={loading} {...spinWrapperParam}>
<ReactECharts {...props} option={this.renderOptionConfig()} onEvents={onEchartEvents} style={style} />
{/* 显示图表y轴悬浮 */}
<div id="extension_tooltip" ref={this.tooltipRef} />
</SpinWrapper>
</div>
)
}
ExtendEcharts.defaultProps = {
notMerge: true // 不合并option
}
export default ExtendEcharts
- index.less
.extend_echarts {
position: relative;
height: 100%;
.extension_tooltip {
position: absolute;
background: #565656;
font-family: Arial;
font-size: 12px;
padding: 6px 12px;
display: none;
color: #fff;
}
}
echarts 建议采用自定义构建的方式
官方自定义构建:https://echarts.apache.org/zh/builder.html
参考:https://www.iquanku.com/read/ECharts3Tutorial/19.md
- echarts 建议采用自定义构建的方式,只包含自己需要的图表,这样可以极大减少echarts体积
echarts 提供了下面这几种定制:
- 完全版:
echarts/dist/echarts.js,体积最大,包含所有的图表和组件,所包含内容参见:echarts/echarts.all.js。 - 常用版:
echarts/dist/echarts.common.js,体积适中,包含常见的图表和组件,所包含内容参见:echarts/echarts.common.js。 - 精简版:
echarts/dist/echarts.simple.js,体积较小,仅包含最常用的图表和组件,所包含内容参见:echarts/echarts.simple.js。 - 如果对文件体积有更严苛的要求,可以自己构建 echarts,能够仅仅包括自己所需要的图表和组件。自定义构建有几种方式:
echarts报错echarts is not defined的问题
- 在taro使用echarts中,如果在小程序,需要使用tooltip的情况,因为echarts官方还未支持小程序的tooltip,需要使用echarts-for-weixin库里的echarts版本,才可以使用
- 而直接使用echarts-for-weixin库里的echarts的版本,会出现echarts is not defined问题
import echarts from 'echarts' // 报错echarts is not defined的问题
import * as echarts from 'echarts' // 即可解决
echart 堆叠柱状图问题
echarts的X轴显示特定个数
需求: 必须显示起始日期和终止日期 echarts默认属性会导致如果重叠,最大值和最小值会被隐藏,首先要实现不让最大值和最小值隐藏,这很简单
xAxis: {
axisLabel: {
showMinLabel: true,
showMaxLabel: true
}
}
需求: 由于画布限制,只让显示起始和终止日期,或者再加上中间日期 此时如果只设置上面的两个属性,中间的刻度仍然会根据echarts的算法去绘制,依然是按照剩余空间来显示,不一定会符合要求,即便只显示了一个值,可能也不是我们预期的那一个,那么还有一个属性
interval 属性表示间隔多少项展示一个刻度,在这里我们直接取一半,这样其他的刻度就会被隐藏掉
xAxis: {
axisLabel: {
showMinLabel: true,
showMaxLabel: true,
interval: Math.ceil(data.length / 2)
}
}
echarts如何优化
-
大数据、后台分页
-
渲染优化
- 纹理缓存
- 对于频繁更新的图表元素,可以使用纹理缓存来减少绘制开销。将不变的图形部分预先渲染成纹理,并在需要时直接使用
- 纹理缓存
-
二进制协议、不要用JSON
- 使用二进制协议代替 JSON 可以减少数据传输的字节大小,提高数据加载速度
-
多点取余,多点合一
- 缩小的时候,多点合一
- 在图表中,当缩小比例较小时,可以将多个点的数据合并为一个点进行渲染,减少渲染数量,提高性能
- 缩小的时候,多点合一
echarts地图
- 类型type为
map - 准备经纬度的地理数据,通常是JSON格式
- 后端需要提供一个接口,通过该接口获取地理数据(经度和纬度)
echarts按需引入
echarts支持按需引入
- echarts从v5.5.0 版本开始使用 ESM 作为默认的模块规范
- 文档:https://echarts.apache.org/handbook/zh/basics/import/
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core';
// 引入柱状图图表,图表后缀都为 Chart
import { BarChart } from 'echarts/charts';
// 引入标题,提示框,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent
} from 'echarts/components';
// 标签自动布局、全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers';
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
BarChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
// 接下来的使用就跟之前一样,初始化图表,设置配置项
var myChart = echarts.init(document.getElementById('main'));
myChart.setOption({
// ...
});
form
- 不能再表单渲染之前给表单项赋值,会报错警告
- 解决:需要等到表单渲染完之后,才进行赋值
- 可使用定时器
- 在vue可使用nextTick
Warning: You cannot set a form field before rendering a field associated with the value.
moment/dayjs
常用日期快速选择时间范围
'1分钟之前': [moment().subtract(1, 'm'), moment().subtract(1, 'm')],
'3分钟之前': [moment().subtract(3, 'm'), moment()],
'15分钟之前': [moment().subtract(15, 'm'), moment()],
'1小时之前': [moment().subtract(1, 'h'), moment()],
'1天': [moment().startOf('day'), moment().endOf('day')],
'今天': [moment(), moment()],
'最近3天': [moment().subtract(3, 'days'), moment()],
'本周': [moment().startOf('week'), moment().endOf('week')],
'最近7天': [moment().subtract(6, 'days'), moment()],
'本月': [moment().startOf('month'), moment().endOf('month')],
'最近30天': [moment().subtract(29, 'days'), moment()],
'最近3个月': [moment().subtract(3, 'month'), moment()],
'最近半年': [moment().subtract(6, 'month'), moment()
'当前月份的第一天': moment().startOf('month').format("YYYY-MM-DD")
'当前月份最后一天':moment().endOf('month').format("YYYY-MM-DD"),
明天: moment().add(1,'days');
昨天 = moment().add(-1, 'days');
计算2个日期的天数差
- diff方法
diff()函数的第三个参数可以是一个布尔值或字符串。如果是布尔值true,则表示返回浮点数,表示两个日期之间的精确差异(以毫秒为单位)
moment('2021-09-01').diff('2021-09-02','days')
const currentYear = moment().year(); // 获取当前年份
const startDate = moment(`${currentYear}-01-01`); // 当年的1月1日
const endDate = moment(); // 当前日期
if (endDate.diff(startDate, 'years', true) >= 1) { // 判断是否超过1年
console.log("今年已经超过一年了");
} else {
console.log("今年还未超过一年");
}
比较日期
- isBefore方法 : 检查一个 moment 是否在另一个 moment 之前。 第一个参数会被解析为 moment(如果尚未解析)。
- isAfter方法:检查一个 moment 是否在另一个 moment 之后。 第一个参数会被解析为 moment(如果尚未解析)。
moment('2010-10-20').isBefore('2010-10-21'); // true
获取某年的最后一天
moment(日期).endOf('year').format("YYYY-MM-DD")
本周
// 本周起始时间
const Monday = dayjs().startOf('week').add(1, 'days')
// 本周末尾时间
const Sunday = dayjs().endOf('week').add(1, 'days')
上周
// 上周起始时间
const lastMonday = dayjs().subtract(1, 'weeks').startOf('week').add(1, 'days')
// 上周末尾时间。
const lastSunday = dayjs().subtract(1, 'weeks').endOf('week').add(1, 'days')
本月
// 本月起始时间
const firstDayOfMouth = dayjs().startOf('month')
// 本月末尾时间
const lastDayOfMouth = dayjs().endOf('month')
上月
// 上月起始时间
const firstDayOfLastMouth = dayjs().subtract(1, 'months').startOf('month')
// 上月末尾时间
const lastDayOfLastMouth = dayjs().subtract(1, 'months').endOf('month')
常见判断日期
- 时间范围最大可以选择当前自然日的前一天开始起算的近3个月时间 【只能选择昨日到近三个月】
disabledDate(current) {
const yesterday = moment().subtract(1, 'day');
const rangeStart = moment(yesterday).subtract(3, "months").startOf("day");
return (current.isBefore(rangeStart) || current.isAfter(yesterday))
}
常用的预设范围日期
// 日期预设范围
const presetsDate = [
{ label: '今天', value: [dayjs(zeroTime()), dayjs()] },
{ label: '昨天', value: [dayjs(dayjs(zeroTime())).add(-1, 'd'), dayjs().add(-1, 'd')] },
{ label: '最近3天', value: [dayjs(dayjs(zeroTime())).add(-2, 'd'), dayjs()] },
{ label: '最近7天', value: [dayjs(dayjs(zeroTime())).add(-6, 'd'), dayjs()] },
{ label: '最近14天', value: [dayjs(dayjs(zeroTime())).add(-13, 'd'), dayjs()] },
{ label: '最近30天', value: [dayjs(dayjs(zeroTime())).add(-29, 'd'), dayjs()] },
{ label: '最近90天', value: [dayjs(dayjs(zeroTime())).add(-89, 'd'), dayjs()] },
{
label: '本周',
// 周一到周日
value: [dayjs().startOf('week'), dayjs().endOf('week')],
},
{
label: '上周',
// 上周一到上周日
value: [dayjs().subtract(1, 'week').startOf('week'), dayjs().subtract(1, 'week').endOf('week')],
},
{ label: '本月', value: [dayjs().startOf('month'), dayjs().endOf('month')] },
{
label: '上月',
value: [
dayjs().subtract(1, 'month').startOf('month'),
dayjs().subtract(1, 'month').endOf('month'),
],
},
];
日历/日期工具库
- internationalized/date
- 提供CalendarDate 对象。这个对象提供了丰富的日期操作方法
import {parseDate, CalendarDate, parseZonedDateTime} from '@internationalized/date';
// 1. 从字符串解析 (ISO 8601)
const date = parseDate('2023-10-25'); // 返回 CalendarDate
// 2. 直接构造
const specificDate = new CalendarDate(2023, 10, 25);
// 3. 获取“今天” (必须指定时区,因为不同时区的“今天”可能不同)
import {getLocalTimeZone, today} from '@internationalized/date';
const now = today(getLocalTimeZone());
- 日期计算与操作
// 加减日期
let date = parseDate('2023-10-25');
// 加 1 周
let nextWeek = date.add({ weeks: 1 });
// 减 2 个月
let prevMonth = date.subtract({ months: 2 });
// 循环字段 (Cycle): 常用于调整特定字段而不改变更大的单位
// 例如:在不改变年份和月份的情况下,将日期设为 5 号
// 注意:如果当前月份没有 31 号,cycle 可能会有不同行为,但在日历 UI 交互中很有用
let newDate = date.set({ day: 5 });
确保精度不缺失
-
可使用mathjs、number-precision来解决
- number-precision更轻量
-
或者
(0.07/100).toFixed(String(0.07).length) //确保精度不丢失
精确保留小数点位数,解决toFixed保留小数四舍五入问题
- 解决toFixed保留小数由于四舍五入导致不准确问题
function toFixed(num, len = 2){
num = String(num)
len = parseInt(len)
const pad = new Array(len + 1).join('0')
//避免出现科学技术法 浏览器中,小于1e-7的数字都会自动转成科学记数法
if (num.indexOf('e-') > 0) {
num = '0' + String(Number(num) + 1).substr(1)
}
const arr = num.split('.')
if (len < 1) {
return arr[0]
}
arr[1] = (arr.length < 2 ? pad : arr[1] + pad).substr(0, len)
return arr.join('.')
}
react函数中防抖失效解决
原因:每次render 都会导致debounce第一个参数(如异步请求函数)更新,进而导致debounce的闭包失效
解决:
- 在class中,可在构造函数绑定防抖函数,确保render渲染不会重新创建函数,只在组件实例化执行一次绑定防抖函数
- hook中,可使用useCallback,防止重复渲染
取消请求
-
axios.cancelToken
-
fetch中断请求,可以使用原生的AbortController接口
-
umi-request中断请求:umi-request 基于 fetch 封装, 兼具 fetch 与 axios 的特点, 中止请求与 fetch 和 axios 一致,既可以使用cancelToken也可以使用AbortController
xlsx库
导出excel
- 自适应宽度 (数据里最长的那行的宽度,全角字符2个宽、半角字符1个宽)
核心代码
const getCellWidth = (value) => {
let width = 0;
for (let i = 0; i < value.length; i += 1) {
const charCode = value.charCodeAt(i);
if (charCode >= 0x00 && charCode <= 0xff) {
width += 1; // 半角字符
} else {
width += 2; // 全角字符
}
}
return width;
};
const downloadExcel = ({ columns, list, fileName }) => {
const wsData = [
columns.map((col) => col.title), // 表头
...list.map((item) => columns.map((col) => item[col.dataIndex])), // 数据行
];
const ws = utils.aoa_to_sheet(wsData);
// 初始化列宽度
ws['!cols'] = [];
columns.forEach((col, index) => {
ws['!cols'][index] = { wch: col.title.length + 2 }; // 默认列宽度为列标题长度加2
});
// 自适应列宽
const range = utils.decode_range(ws['!ref']);
for (let C = range.s.c; C <= range.e.c; C += 1) {
let maxLength = 0;
for (let R = range.s.r; R <= range.e.r; R += 1) {
const cell = ws[utils.encode_cell({ r: R, c: C })];
if (cell && cell.v) {
const cellLength = getCellWidth(cell.v.toString());
if (cellLength > maxLength) {
maxLength = cellLength;
}
}
}
ws['!cols'][C] = { wch: maxLength };
}
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, 'Sheet1');
writeFile(wb, `${fileName}.xlsx`);
};
- 测试数据
const columns = [{ title: 'test1',dataIndex: 'aa',},{title: 'test2',dataIndex: 'bb'}];
const list = [{aa:111,bb:222}]