J'Blog

开发记录2

开发记录2

React实现eventBus

// event.ts
import EventEmitter, { EventHandler } from './event-emitter'
import { useEffect } from 'react'

const events = new EventEmitter()

export default events

export function useEventBus<T = any>(event: string, cb: EventHandler<T>) {
  useEffect(() => {
    events.on(event, cb)
    return () => {
      events.off(event, cb)
    }
  })
}

// event-emitter.ts
import { pull } from 'lodash';
 
export type EventHandler<E = any> = (e: E) => void;
 
/*这个就是事件的原型*/
class EventEmitter<E = any> {
  private _events: Record<string, EventHandler<E>[]>;
 
  constructor() {
    this._events = {};
  }
 
  private _getFns(event: string) {
    return this._events[event] || (this._events[event] = []);
  }
 
  public on<T = E>(event: string, cb: EventHandler<T>) {
    const fns = this._getFns(event);
    fns.push(cb as any);
  }
 
  public off(event: string, cb?: EventHandler<E>) {
    if (cb) {
      const fns = this._getFns(event);
      pull(fns, cb);
    } else {
      delete this._events[event];
    }
  }
 
  public once<T = E>(event: string, cb: EventHandler<T>) {
    const fn2: EventHandler<E> = (e) => {
      this.off(event, fn2);
      cb(e as any);
    };
    this.on(event, fn2);
  }
  
  /* 同步调用 */
  public emit<T = E>(event: string, param?: T) {
    const fns = this._getFns(event);
    for (let i = 0; i < fns.length; i++) {
      const fn = fns[i] as EventHandler<any>;
 
      fn(param);
    }
  }
 
  /* 可以异步调用,返回一个Promise */
  public invoke<T = E>(event: string, param?: T): Promise<any> {
    const fns = this._getFns(event);
 
    flag: for (let i = 0; i < fns.length; i++) {
      const fn = fns[i] as EventHandler<any>;
      return new Promise<any>((resolve) => {
        resolve(fn(param));
      });
      break flag;
    }
    return Promise.reject();
  }
}
 
export default EventEmitter;

导出pdf

React中将div中的内容导出到pdf,首先将div中的内容通过html2canvas生成canvas图片,继而通过jsPDF插件将canvas图片导出到pdf

// html代码
<div ref={pdfRef}>...</div>

js代码
// 整页导出pdf,避免出现分页后图片被隔断的问题
export const imgDownloadPdf = (
  divContent,  // div内容
  pdfName = moment(new Date()).format('YYYY-MM-DD'), // 导出文件名称
  x = 0,  // 图像在PDF中的x坐标。
  y = 30  // 图像在PDF中的y坐标。
) => {
  document.body.classList.add('hide_Pdf_body')
  // 获取需转图片的范围元素
  document.documentElement.scrollTop = 0
  document.body.scrollTop = 0
  // 利用 html2canvas 下载 canvas
  html2canvas(divContent).then(function (canvas) {
    //转化为本地的图片地址
    const filename = pdfName
    const pageWidth = canvas.width
    const pageHeight = canvas.height
    const pageData = canvas.toDataURL('image/jpeg', 1.0)
    const pdf = new jsPDF({
      orientation: pageHeight > pageWidth ? 'portrait' : 'landscape',
      unit: 'px',
      format: [pageHeight, pageWidth]
    })
    pdf.addImage(pageData, x || x == 0 ? x : 0, y || y == 0 ? y : 100, pageWidth, pageHeight - y)
    //转pdf下载
    const nowDate = moment(new Date()).format('YYYY-MM-DD')
    pdf.save(filename || '报告-' + nowDate)
  })
  setTimeout(() => {
    document.body.classList.remove('hide_Pdf_body')
  }, 500)
}

实现chartGPT回答时光标闪动效果

在应答过程中给文本添加typingLoading类,在文本末尾添加光标闪烁的伪元素

    p {
	&.typingLoading {
            &::after {
              content: ' ▌';
              animation: changeOpacity 1s infinite;
            }
          }
    }

    @keyframes changeOpacity {
  	from {
    	    opacity: 1;
  	}

  	to {
    	    opacity: 0;
  	}
    }

根据返回数据类型生成对应的表格/柱状图/饼图/折线图等

Echart使用

柱状图带标准线

    const option = {
        tooltip: {
	    // 鼠标悬浮时展示的提示信息
            trigger: 'axis',
            axisPointer: {
                type: 'shadow'
            },
            className: 'xzhu-tooltip'
        },
        xAxis: {
            type: 'category',
            boundaryGap: false,
            data: ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun', '']
        },  //这里x轴的首尾加两个空字符串用来做占位
        yAxis: {
            type: 'value'
        },
        legend: {
            show: true,
            left: 'center',
            bottom: 10,
            data: [// 如果是多条标准线,就设置多个对应的对象
                {
                    name: 'xxx',
                    icon: 'rect',
                    textStyle: {
                        color: '#666666' //对应文本颜色
                    },
                },
                {
                    name: 'yyy',
                    icon: 'rect',
                    textStyle: {
                        color: '#666666'
                    },
                }
            ]
        },
        series: [
            {
                name: 'uuu',
                data: [0, 120, 200, 150, 80, 70, 110, 130, 0], //这里首尾添加两个0,跟上面加的两个空字符串对应作为占位符
                type: 'bar',
                itemStyle: {
                    color: '#2676ED'
                },
            },
            {
                name: 'xxx', // 这里的名称要和上面的保持一致
                data: [40, 40, 40, 40, 40, 40, 40, 40, 40], //标准线是多少,这里的每一项就是多少,跟上面x轴的个数保持一致
                type: 'line',
                symbol: 'none',
                itemStyle: {
                    color: '#E35454' //对应线条颜色
                }
            },
            {   //这是第二条标准线,name名称要和上面定义的一样
                name: 'yyy', // 标准线多少就是多少,这里是例子80%,这里的名称要和上面的保持一致
                data: [80, 80, 80, 80, 80, 80, 80, 80, 80], //标准线是多少,这里的每一项就是多少,跟上面x轴的个数保持一致
                type: 'line',
                symbol: 'none',
                itemStyle: {
                    color: '#E35454'
                }
            }
        ]
    };

分组柱状图带图例

    const option = {
        tooltip: {
            trigger: 'item',
            axisPointer: {
                type: 'shadow'
            },
            className: 'xzhu-tooltip'
        },
        xAxis: {
            type: 'category',
            data: ['']  //这里就是['']
        },
        yAxis: {
            type: 'value'
        },
        legend: {
            type: 'scroll',
            show: true,
            left: 'center',
            bottom: 10,
            icon: 'circle'
        },
        series: [ //这里就是每个x轴对应的数据
            {
                name: 'Mon', //每个x轴对应的名称
                data: [120], //当前x轴对应的值
                type: 'bar',
                itemStyle: {
                    color: randomColor() //这里是随机颜色值,例如'#ffffff'
                },
                label: {
                    show: true,
                    formatter: '{a}',
                    position: 'bottom',
                },
            },
            {
                name: 'Tue',
                data: [200],
                type: 'bar',
                itemStyle: {
                    color: randomColor()
                },
                label: {
                    show: true,
                    formatter: '{a}',
                    position: 'bottom',
                }
            },
            {
                name: 'Wed',
                data: [150],
                type: 'bar',
                itemStyle: {
                    color: randomColor()
                },
                label: {
                    show: true,
                    formatter: '{a}',
                    position: 'bottom',
                }
            },
        ]
    }

横向分组柱状图

    const option = {
        tooltip: {
            trigger: 'item',
        },
        xAxis: {
            show: false,
        },
        yAxis: {
            type: 'category',
            data: [''] //为空
        },

        series: [ //每个柱状的对象
            {
                name: 'Mon', //label名称
                data: [120], //对应的值
                type: 'bar',
                itemStyle: {
                    color: randomColor() //随机颜色值,例'#ffffff'
                },
                label: {
                    show: true,
                    formatter: '{a}',
                    position: 'left',
                },
                tooltip: {
                    formatter: '{c}',
                    position: 'right',
                }
            },
            {
                name: 'Tue',
                data: [200],
                type: 'bar',
                itemStyle: {
                    color: randomColor()
                },
                label: {
                    show: true,
                    formatter: '{a}',
                    position: 'left',
                },
                tooltip: {
                    formatter: '{c}',
                    position: 'right',
                }
            },
            {
                name: 'Wed',
                data: [150],
                type: 'bar',
                itemStyle: {
                    color: randomColor()
                },
                label: {
                    show: true,
                    formatter: '{a}',
                    position: 'left',
                },
                tooltip: {
                    formatter: '{c}',
                    position: 'right',
                }
            },
        ]
    }

普通折线图

    const option = {
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'shadow'
            },
            className: 'zhexian-tooltip'
        },
        xAxis: {
            type: 'category',
            data: ['Mon', 'Tue', 'Wed'] //x轴数据
        },
        yAxis: {
            type: 'value'
        },
        series: [
            {
                data: [120, 100, 150], //y轴对应数据
                type: 'line',
            },
        ]
    }

分段折线图

    const option = {
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'shadow'
            },
            className: 'zhexian-tooltip'
        },
        xAxis: {
            type: 'category',
            data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sta', 'Sun'] //x轴数据
        },
        yAxis: {
            type: 'value'
        },
        legend: {
            show: true,
            left: 'center',
            bottom: 10,
            icon: 'circle'
        },
        series: [
            {
                name: '过去',
                data: [120, 100, 300, 80, 200], //过去的数据
                type: 'line',
                smooth: 0.6,
                lineStyle: {
                    color: '#2676ED',
                },
                areaStyle: {
                    color: {
                        type: 'linear',
                        x: 0,
                        y: 0,
                        x2: 0,
                        y2: 1,
                        colorStops: [{
                            offset: 0, color: '#C6DBFF' // 0% 处的颜色
                        }, {
                            offset: 1, color: 'rgba(221, 234, 255, 0)' // 100% 处的颜色
                        }],
                        global: false // 缺省为 false
                    },
                    origin: 'auto',
                },
            },
            {
                name: '未来',
                data: ['', '', '', '', 200, 180, 150], //未来的数据,这里前面空着的是过去的数据,空字符串占位
                type: 'line',
                smooth: 0.6,
                lineStyle: {
                    color: '#36CBCB',
                },
            },
        ]
    }

基础饼图数据

const option = {
        tooltip: {
            trigger: 'item'
        },
        series: [
            {
                type: 'pie',
                radius: '50%',
                data: [ //对应的数据,value和name对应显示
                    { value: 90, name: 'xxx', itemStyle: { color: 'rgba(38, 118, 237, 1)' } },
                    { value: 10, name: 'yyy', itemStyle: { color: 'rgba(54, 203, 203, 1)' } },
                ],
                emphasis: {
                    itemStyle: {
                        shadowBlur: 10,
                        shadowOffsetX: 0,
                        shadowColor: 'rgba(0, 0, 0, 0.5)'
                    }
                }
            }
        ]
    };

分组柱状以及分组折线并存

const option = {
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'shadow'
            },
            className: 'xzhu-tooltip'
        },
        xAxis: {
            type: 'category',
            data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sta', 'Sun'] //x轴数据
        },
        yAxis: [
            {
                type: 'value',
                name: '同期值', //左边数据名称
            },
            {
                type: 'value',
                name: '同期增长率', //右边数据名称
                axisLabel: {
                    formatter: '{value} %', //右边数据对应格式,如果返回的数据值是自带%,那这里的formatter就不需要了
                },
            }
        ],
        legend: {
            type: 'scroll',
            show: true,
            left: 'center',
            top: 10,
            icon: 'circle'
        },
        series: [
            {
                name: '同期值', 
                data: [100, 130, 80, 150, 110, 160, 190], //同期值的数据
                type: 'bar',
                itemStyle: {
                    color: 'rgba(38, 118, 237, 1)'
                },
            },
            {
                name: '环期值', //
                data: [110, 132, 150, 180, 120, 80, 150], //环期值的数据值
                type: 'bar',
                itemStyle: {
                    color: 'rgba(20, 201, 201, 1)'
                },
            },
            {
                name: '同期增长率',
                data: [20, 50, 20, 40, 30, 80, 50], //同期增长率的数据值
                type: 'line',
                itemStyle: {
                    color: 'rgba(247, 163, 92, 1)'
                },
                yAxisIndex: 1,
            },
            {
                name: '环期增长率',
                data: [30, 20, 90, 10, 50, 90, 20], //环期增长率的数据值
                type: 'line',
                itemStyle: {
                    color: 'rgba(188, 149, 223, 1)'
                },yAxisIndex: 1,
            },
        ]
    }