开发记录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,
},
]
}