开发记录3

项目记录
项目1(目标库和源库数据比对,校验数据一致性、空值、值域等)
// 技术栈:vue2 + vuex + event-bus + vue-router + axios + element-ui
// vue mixin 类似于hooks
// ********** 重点改造功能:路由 keep-alive 多级路由缓存,修改根据组件名称获取更改为路由名称进行缓存,同一个页面不同路由参数也会进行缓存
// <keep-alive>组件包含的页面都会进行缓存
// codemirror web代码编辑器
// sql-formatter sql语句格式化工具
// mavon-editor 基于vue的markdown编辑器
// 自定义指令:
import Vue from 'vue';
let MyPlugin = {};
export default MyPlugin.install = function (vue, options) {
Vue.directive('lazy', {
bind: function (el, binding) {
const lazyLoadObser = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
if (entry.intersectionRatio > 0) {
binding.value.loadingMore();
}
})
})
lazyLoadObser.observe(el)
}
})
};
// 下载文件
export const download = (res) => {
const content = res.data
const blob = new Blob([content])
// const fileName = '导出信息.xlsx'
const fileName = decodeURIComponent(res.headers['content-disposition'].split('filename=')[1])
if ('download' in document.createElement('a')) { // 非IE下载
const link = document.createElement('a')//创建一个a标签通过a标签的点击事件区下载文件
link.download = fileName
link.style.display = 'none'
link.href = URL.createObjectURL(blob)//使用blob创建一个指向类型数组的URL
document.body.appendChild(link)
link.click()
URL.revokeObjectURL(link.href) // 释放URL 对象
document.body.removeChild(link)
} else { // IE10+下载
navigator.msSaveBlob(blob, fileName)
}
}
/**
* 返回昨天日期
* @returns {string}
*/
export const getYesterday = () => {
let now = new Date()
let yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
let _year = yesterday.getFullYear()
let _month = yesterday.getMonth() + 1 < 10 ? '0' + (yesterday.getMonth() + 1) : (yesterday.getMonth() + 1)
let _day = yesterday.getDate() < 10 ? '0' + yesterday.getDate() : yesterday.getDate()
return _year + '-' + _month + '-' + _day
};
/**
* 获取当前年份
*/
export const getCurrentYear = () => {
let now = new Date()
return now.getFullYear()
}
/**
* 获取当前月份
*/
export const getCurrentMonth = (n = 0) => {
let now = new Date()
return now.getMonth() + 1 + n
}
/**
* 浏览器判断是否全屏
*/
export const fullscreenToggel = () => {
if (fullscreenEnable()) {
exitFullScreen();
} else {
reqFullScreen();
}
};
/**
* esc监听全屏
*/
export const listenfullscreen = (callback) => {
function listen() {
callback();
}
document.addEventListener('fullscreenchange', function () {
listen();
});
document.addEventListener('mozfullscreenchange', function () {
listen();
});
document.addEventListener('webkitfullscreenchange', function () {
listen();
});
document.addEventListener('msfullscreenchange', function () {
listen();
});
};
/**
* 浏览器判断是否全屏
*/
export const fullscreenEnable = () => {
return document.isFullScreen || document.mozIsFullScreen || document.webkitIsFullScreen;
};
/**
* 浏览器全屏
*/
export const reqFullScreen = () => {
if (document.documentElement.requestFullScreen) {
document.documentElement.requestFullScreen();
} else if (document.documentElement.webkitRequestFullScreen) {
document.documentElement.webkitRequestFullScreen();
} else if (document.documentElement.mozRequestFullScreen) {
document.documentElement.mozRequestFullScreen();
}
};
/**
* 浏览器退出全屏
*/
export const exitFullScreen = () => {
if (document.documentElement.requestFullScreen) {
document.exitFullScreen();
} else if (document.documentElement.webkitRequestFullScreen) {
document.webkitCancelFullScreen();
} else if (document.documentElement.mozRequestFullScreen) {
document.mozCancelFullScreen();
}
};
项目2
// 技术栈:vue3 + ts + vite2 + pinia + ant-design-vue + antv/x6(流程图)
// echarts 和 D3.js的区别就是echarts是使用canvas绘制,D3使用svg绘制;echarts是封装好的,灵活性差,D3比较灵活,支持事件处理器操作dom,但是学习成本高,看具体开发需求
// go.js也是绘制力导向图的一个js库
项目3
// 技术栈: react + typescript + vite + cbdesign + react-router-dom + mobx + pnpm
// 根据问题智能推荐不同图表数据进行展示并支持切换图标展示,可导出到pdf
/** 重点改造功能: */
/** 1. 手写eventBus */
/** 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;
/** events.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)
}
})
}
/** 如何使用eventBus */
useEventBus('exportPdf', handleExportPdf) // 监听exportPdf事件,如果触发了exportPdf事件则执行handleExportPdf方法
events.emit('exportPdf') // 触发exportPdf事件
/** 2. jspdf插件集合html2canvas实现导出图片到pdf文件 */
export const exportPdf = (canvas: HTMLCanvasElement) => {
const contentWidth = canvas.width
const contentHeight = canvas.height
// 一页pdf显示html页面生成的canvas高度;
const pageHeight = (contentWidth / 592.28) * 841.89
// 未生成pdf的html页面高度
let leftHeight = contentHeight
// 页面偏移
let position = 0
// a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
const imgWidth = 595.28
const imgHeight = (595.28 / contentWidth) * contentHeight
const pageData = canvas.toDataURL('image/jpeg', 1.0)
const pdf = new JsPDF('p', 'pt', 'a4')
// 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
// 当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < pageHeight) {
pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
} else {
while (leftHeight > 0) {
pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
leftHeight -= pageHeight
// 避免添加空白页
position -= 841.89
if (leftHeight > 0) {
pdf.addPage()
}
}
}
const nowDate = moment(new Date()).format('YYYY-MM-DD')
// if (isPrint) {
// //打印
// pdf.autoPrint();
// pdf.output('dataurlnewwindow');
// } else {
// //下载
pdf.save('test-' + nowDate + '.pdf')
// }
}
// 整页导出pdf
export const imgDownloadPdf = (
img: any,
pdfName: any = moment(new Date()).format('YYYY-MM-DD'),
x: any = 0,
y: any = 30
) => {
document.body.classList.add('hide_Pdf_body')
// 获取需转图片的范围元素
document.documentElement.scrollTop = 0
document.body.scrollTop = 0
// 利用 html2canvas 下载 canvas
html2canvas(img).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)
}
/** 3. 是否在可视范围内hooks */
import React from "react";
export function useObserver(ref: any) {
const [isIntersecting, setIsIntersecting] = React.useState(false);
// OPTIONS
const options = {
// root: target.current,
rootMargin: '0px',
threshold: 0.5, // A threshold of 1.0 means that when 100% of the target is visible within the element specified by the root option, the callback is invoked.
};
// Observer
const observer = new IntersectionObserver(([entry]) => setIsIntersecting(entry.isIntersecting), options);
// Use Effect
React.useEffect(() => {
observer.observe(ref.current)
// DISCONNECT
return () => observer.disconnect()
}, []);
// Return
return isIntersecting
};
/** 4. 是否改变了窗口大小hooks */
import { useEffect, useState } from 'react';
const useResizeObserver = (ref: any) => {
const [dimensions, setDimensions] = useState < { width: number; height: number } | null > (null);
useEffect(() => {
const observer = new ResizeObserver((entries) => {
const { width, height } = entries[0].contentRect;
setDimensions({ width, height });
});
observer.observe(ref.current);
return () => {
if (ref.current) {
observer.unobserve(ref.current);
}
};
}, [ref]);
return dimensions;
};
export default useResizeObserver;
/** 5. 模拟打字效果 */
const search: any = (data: any) => {
const str = data?.title
const strLen = str.length
let index = 0
let timer: any = null
let cur = currentAnswer
let curLoading = currentAnswerLoading
timer = setInterval(() => {
if (index >= strLen) {
setCurrentAnswerLoading(false)
clearInterval(timer)
} else {
if (!curLoading) curLoading = true
const randomLen = Math.ceil(Math.random() * (strLen - index > 10 ? 10 : strLen - index))
const curIndex = index
index += randomLen
cur += str?.slice(curIndex, index)
setCurrentAnswer(cur)
setCurrentAnswerLoading(curLoading)
}
}, Math.random() * 500)
}
// 给文本一个类,设置伪元素光标闪烁
// .typingLoading {
// &::after {
// content: ' ▌';
// animation: changeBg 1s infinite;
// }
// }
// @keyframes changeBg {
// from {
// opacity: 1;
// }
// to {
// opacity: 0;
// }
// }
项目4
// 重点改造功能:
// 1. electron-vite-react
// 1. B端&C端打开弹窗,B端通过调用方法,postMessage传输数据;C端electron约定协议打开桌面应用内嵌web页面,涉及到electron于内嵌web页面之间通信方式
// electron功能点:
// 1. 自定义标题栏, 首先配置隐藏默认标题栏,react自定义标题栏,设置css控制部分不可拖拽
// 2. electron和react通信,react页面通过ipcRenderer 的send或on方法
// 3. 注册自定义协议,实现浏览器或桌面应用点击打开我们的桌面应用,app.setAsDefaultProtocolClient("")
// 4. iframe和内部react页面之间的通信postMessage
// 5. 打开新窗口,涉及到打包后本地hash url,loadUrl, loadFile
// 2. 封装权限hooks
import { useMemo } from 'react'
import { isAuthorized } from '@/assets/js/publicFunc'
export const useAuth = (key: string) => {
const auth = useMemo(() => {
return isAuthorized(key)
}, [key])
return auth
}
// 使用forwardRef, useImperativeHandle调用子组件中的方法
/** 获取文件大小 */
export function formatSizeUnits(bytes) {
if (bytes >= 1073741824) { bytes = (bytes / 1073741824).toFixed(2) + " GB"; }
else if (bytes >= 1048576) { bytes = (bytes / 1048576).toFixed(2) + " MB"; }
else if (bytes >= 1024) { bytes = (bytes / 1024).toFixed(2) + " KB"; }
else if (bytes > 1) { bytes = bytes + " bytes"; }
else if (bytes == 1) { bytes = bytes + " byte"; }
else { bytes = "0 bytes"; }
return bytes;
}
项目5
// 重点改造功能:
// 1. 递归数据处理,表格拆分合并单元格,表格单元格编辑等
// 2. 拆分文本组件封装
import { getSelectedText } from '@/utils/index'
import { PlusOutlined } from '@ant-design/icons'
import { useEffect, useRef, useState } from 'react'
interface Props {
text: string;
addSplitHandle: any
}
const SplitText = ({ text, addSplitHandle }: Props) => {
const [showAddIcon, setShowAddIcon] = useState < any > (false)
const [curSelectionText, setCurSelectionText] = useState < any > ('')
const textRef = useRef < any > (null)
const mouseUpHandle = (e: any) => {
const selectionText = getSelectedText();
if (selectionText) {
setCurSelectionText(selectionText)
if (!showAddIcon) setShowAddIcon(true)
const addIcon = document.querySelector('.split-add-icon');
addIcon?.setAttribute('style', `left: ${e.offsetX + 5}px;top: ${e.offsetY - 7}px`)
} else {
setShowAddIcon(false)
}
}
const mouseDownHandle = () => {
textRef.current?.addEventListener('mouseup', mouseUpHandle)
if (showAddIcon) setShowAddIcon(false)
}
const clickHandle = () => {
addSplitHandle(curSelectionText)
setShowAddIcon(false)
}
useEffect(() => {
textRef.current?.addEventListener('mousedown', mouseDownHandle)
}, [])
return (
<p className='split-text' ref={textRef}>
{text}
<span className='split-add-icon' style={{ opacity: showAddIcon ? 1 : 0 }} onClick={clickHandle}><PlusOutlined key='addIcon' /></span>
</p>
)
}
export default SplitText
/**
* 获取选中文本
*/
export const getSelectedText = () => {
let selectedText = ''
if (window.getSelection) {
selectedText = (window.getSelection() as any).toString()
} else if ((document as any).selection && (document as any).selection.type !== 'Control') {
selectedText = (document as any).selection.createRange().text
}
return selectedText
}