1.前端实现加密签名的方式:
解决的问题:安全方面问题,由于抓包修改传递数据参数,导致侵入网页等问题。
使用sha256+crypto-js 配合实现数据的加密,存储在header中,当每次请求时候携带加密签名带给后端,后端进行解密,如果信息解密一致,则签名通过,反之失败
import * as CryptoJS from 'crypto-js' //加密库
import getSha256 from 'crypto-js/sha256' //ydq-sha256散列
//加密信息
const getSha256Fn = (str, times) => {
const sha256Encrypt = getSha256(str).toString()
let AESText = {
signature: sha256Encrypt,
time: times,
}
AESText = JSON.stringify(AESText)
const key = CryptoJS.enc.Utf8.parse(后台提供) //十六位十六进制数作为密钥
const iv = CryptoJS.enc.Utf8.parse(后台提供) //十六位十六进制数作为密钥偏移量
const encrypted = CryptoJS.AES.encrypt(AESText, key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
})
return encrypted.toString()
}
2.实现自定义添加表头字段思路:
1.自己维护一个表头表:不同业务场景需要的表头不同。 例如:
//可扩展,例如添加每个表头宽度width等
xxx业务1={
fixed:[{prop:xxx1,name:'标签1'},{prop:xxx2,name:'标签1'}],//固定表头
initShow:[{prop:xxx1,name:'标签1'},{prop:xxx2,name:'标签1'}],//初始化需显示的表头
initFalse:[{prop:xxx1,name:'标签1'},{prop:xxx2,name:'标签1'}],//初始化不需要显示的表头
}
2.引入 vuedraggale 可拖拽列表 形成一个拖拽自定义表头组件,用于随时修改表头
3.将我们的表头表定义数据 传递给 自定义表头组件 用于初始化数据展示(如果local内有就优先用)
- 自定义表头组件有三个数组维护数据:fixed:[],initShow:[],initFalse:[],(通过修改数据通过更新数组值即可,同时必须在localStorage中存储,保持持久化)
5.外层父组件table渲染通过获取子组件initShow,和fixed数组来动态渲染table表格
6.注意事项:由于自定义表头组每次保存都需要存一份在localStorage内保持持久化,那么就会存在一个问题:
当后台更改了一个表头字段的时候,我们除了需要在表头表进行手动更新以外,还需要 在自定义表头组件写一个函数捕获local内存储的异常数据(不存在表头表的数据),将他们删除。每次初始化的时候都需要调用此函数。
3.table表格勾选完切换分页会导致前页的勾选状态失效-比较合适的解决方案
由于勾选状态是有两种:已勾选/未勾选
通常的解决方案一般肯定是定义一个数组[],当点击勾选的时候,数据不存在:数据push在数组中;存在:需要删除数组中的对应元素。
还要判断点击全选,将没选中的添加进数组;取消全选,将对应的元素从数组中剔除。
每次切换页数/条数,需要用最新的table数据循环判断是否在存储数组内,存在的需要保持高亮
总结痛点:操作数组地方太多,循环判断太多,维护起来比较难受。
合理的解决方式:new Map() -->使用map结构存储数据,优点:具有唯一性 key。和更加方便的API: set-插入,has-判断,delete-删除
当我们点击勾选/不勾选,只需要通过has判断,有则delete-删除,代表取消勾选;无则set-插入,代表勾选。 这样无需通过普通数组循环判断元素是否存在
在点击全选和取消全选只需要判断表格api传递的arr的length是否大于0/等于0,直接拿数据进行set或者delete即可。也不需要判断是否存在问题。(因为map的唯一性,key重复会覆盖)
总结:减少了数组的操作,代码简洁,不臃肿。api方便强大。
4.大文件分片上传实现方式:
1.结合upload组件 首先获取到文件file,这里可以对文件的类型,大小进行限制
2.通过new FormData() -创建一个formData对象-用于给后端到时候传递
3.先进行文件切片,将大文件切片成小文件
//切割大文件
checkFiles(file,size=10*1024*1024){
let arr=[]
let sum=0
while(sum<file.size){
let checkFile=file.slice(sum,size)
arr.push({file:checkFile})
sum+=size
}
return arr
}
4.将切割文件后的数组转化为需要的指定格式。我这里是转成formData格式-根据需求来定
示例:
checkFiles=checkFiles.map((item,index)=>{
let formData=new FormData()
formData.append('index',index+1)//文件位置
formData.append('file',item)//对应文件
return {
formData,
index:index+1,
fileName:'xxxx'
}
})
5.将整合后的数组进行分片上传:
//分片数据上传-后台接受后整合返回-用于后面分片合并用
checkListUpload(fileList){
let initCount=0//上传成功次数
const total=fileList.length //数组长度
const errorList=[] //上传失败元素次数集合
const MAX_COUNT=3 //最大重试次数
let resList=[] //上传成功后,后台返回的整合数据集合
let errorTag=false//状态,默认状态为成功的
return new Promise((resolve,reject)=>{
const uploadFiles=()=>{
if(fileList.length){
let item=fileList.shift()//拿第一个元素并修改了原数组
let index=item.index //记录元素下标-用于捕获异常文件位置
//将数组中元素拿出来传递给后台
xxxUpload(item.formData).then(res=>{
//代表上传成功,push返回数据到resList
resList.push(res)
initCount++
//成功状态则继续递归上传
if(!errorTag){
uploadFiles()
}
}).catch(e=>{
//代表传递过程出现异常,1.先记录传递异常文件位置和次数
if(errorList[index]===undefined){
//初始化
errorList[index]=0
}
errorList[index]+=1 //代表当前失败次数
//超过最大次数则停止上传
if(errorList[index]>=MAX_COUNT){
errorTag=true //状态更新为失败
return reject('重试失败', retryArr)
}
//重传
fileList.push(item)
uploadFiles()
})
}
//全部上传完成则promise结束,向外抛出resList
if(initCount>=total){
resolve(resList)
}
}
//初始上传并发
for(let i=0;i<3;i++){
uploadFiles()//先来三个,后续的都会在then后递归
}
})
}
6.拿到后台返回的数据resList集合后剩下要做的就是合并分片数据为一个,返回url资源了
//合并文件碎片,获取url资源
getFileUrl(list){
let url=mergeFileList(list)//调用接口就完事
return url
}
5.前端常用插件需知道:
1.富文本编辑器:Wangeditor ,ckeditor
2.视频播放器:videoPlayer
3.图片裁剪器:VueCropper
4.图片预览器:v-viewer
5.简单水印:watermark-dom
6.图片优化方案
需求点:出于对我们的网页弱网环境可能会很卡顿,或者图片资源加载太慢等场景,对图片进行优化的一些方案
1.通过压缩网站进行图片资源压缩,减少图片size
2.通过阿里云OSS图片资源处理,也是通过改变图片宽高模糊度,图片缩放等生成新的预览图
示例:原图::<img src="https://oss-console-img-demo-cn-hangzhou.oss-cn-hangzhou.aliyuncs.com/example.jpg" alt="" />
oss处理后:
<img
src="https://oss-console-img-demo-cn-hangzhou.oss-cn-hangzhou.aliyuncs.com/example.jpg?x-oss-process=image/resize,w_300,h_500,m_lfit"
alt=""
/>
3.使用图片懒加载:vue-lazyload (vue插件好用)
4.图片预加载:创建image标签和配合src属性
5.媒体查询:不同分辨率电脑,显示图片的size不同
6.css代替,弱网环境添加css画一个loading之类的,在图片@load时候取消css load样式
7.canvas学习
1.canvas实现电子签名效果
//定义坐标
let client={
x:0,
y:0,
}
//初始化创建画布,并绑定各种事件
mounted(){
this.canvas = document.querySelector('canvas')
this.canvas.width = 500
this.canvas.height = 200
// 设置一个边框,方便我们查看及使用
this.canvas.style.border = '1px solid #000'
// 创建上下文
this.ctx = this.canvas.getContext('2d')
// 设置填充背景色
this.ctx.fillStyle = 'transparent'
// 绘制填充矩形
this.ctx.fillRect(
0, // x 轴起始绘制位置
0, // y 轴起始绘制位置
500, // 宽度
200, // 高度
)
//添加一些事件
window.addEventListener('mousedown', this.init)
window.addEventListener('mouseup', this.closeDraw)
}
//鼠标按下,获取坐标并且绑定鼠标移动事件
init(event){
//注意事项Canvas 的坐标是相对于画布本身而言的,而不是整个页面。所以在确定图形的坐标时,要根据需要进行相应的计算和调整!!!!!(如果画布周围有东西需要手动计算去除x,y距离,才能得到相对于画布坐标)
const { pageX, pageY } = event
this.client.x=pageX
this.client.y=pageY
// 清除以上一次 beginPath 之后的所有路径,进行绘制
this.ctx.beginPath()
this.ctx.lineWidth=5//线宽
this.ctx.strokeStyle='red'//线颜色
this.ctx.lineCap='round'//线的圆角
//开始画线
this.ctx.moveTo(this.client.x,(this.client.y)
window.addEventListener('mousemove', this.draw)
}
//鼠标移动绘制
draw(event){
const { pageX, pageY } = event
this.client.x=pageX
this.client.y=pageY
this.ctx.lineTo(pageX, pageY)
// 绘制
this.ctx.stroke()
}
//鼠标抬起-结束绘制
cloaseDraw() {
// 结束绘制
this.ctx.closePath()
// 移除鼠标移动或手势移动监听器
window.removeEventListener('mousemove', this.draw)
},
//清空画布
clearCanvas(){
this.ctx.clearRect(0, 0, 500,200)
}
//将画布内容转换为图片下载
downloadCanvas(){
this.canvas.toBlob(blob=>{
const a = document.createElement('a')
a.download = `路径图.png`
a.href = URL.createObjectURL(blob)
a.click()
a.remove()
})
}
8.Websoket实践-聊天GPT对话 (原本应该用SSE更合适,但是对接后台Python原因改用websoket)
需求是:与后端进行数据通信,实现聊天gpt对话功能
这里主要阐述几个难点部分如何解决
1.如何建立通信?
this.ws = new WebSocket('xxxxxx')
this.ws.addEventListener('message',event=>{
//后台返回给前端的信息-Ai数据
...
})
//前端传递数据给后端的方式
this.ws.send(params)
2.后台返回数据流-前端需要实现打字机效果展示
由于是通过message流的形式一直给前端实时推送字符:
let bol=true //默认可以使用打字机延迟效果
let index=0 //默认当前arr数组下标
this.ws.addEventListener('message',event=>{
//后台返回给前端的信息-Ai数据
let data=event.data
this.arr.push(data)//同步存储推送的字符
//通过开关来判断是否需要走打字机效果
if(bol){
this.delayFn()
}
...
})
//打字机效果-异步渲染
delayFn(){
bol=false //关闭,后续的不能使用打字机效果,防止打字机效果覆盖,因为流是一直同步推送的
setTimeout(()=>{
//数组内的元素还没执行完就一直轮询执行
if(index < this.arr.length){
...这里就是把数组中的数据渲染在页面即可
index++
this.delayFn()//进行递归执行,持续渲染打字机效果-直到数组元素渲染完毕
}else{
//这里代表数组中的元素都异步渲染完毕了,可以做清除光标闪烁打开输入框等业务操作
tag=true //打字机使用开关恢复,可以继续使用
}
})
}
3.场景2:用户切换页面了,回来的时候需要看到最新内容,不想傻傻的一直在那里等打字机效果结束,他可以在打字机效果渲染的时候,浏览其他网页,回来直接看到最新内容
解决方案:监视页面变化,页面失活的时候让index永远保持arr的最新长度
created(){
//添加页面监视
document.addEventListener('visibilitychange', this.closeBrowser)
}
this.ws.addEventListener('message',event=>{
//后台返回给前端的信息-Ai数据
...
//需要实时触发页面监视函数,因为当切换页面的时候,websoket还是执行的
this.closeBrowser()
})
//页面失活-始终渲染返回的全部数据,不使用打字机效果
closeBrowser(){
if (document.visibilityState === 'hidden'){
index=this.arr.length//index始终保持最新就不会走打字机异步渲染效果了
let str=this.arr.join('')//需要渲染到页面的数据
}
}
4.场景3:由于对话记录的不断增多,我们的页面如果出现滚动条需要自动将滚动条定位到最底部。同时
如果用户向上滑动滚动条那么自动定位效果就要去除,并且用户如果滑动滚动条到最底部,
自动定位效果就要恢复
解决方案:1.监听滚动条,如果用户滑动滚动条到底部则开启自动定位功能,反之关闭定位功能
2.同时需要监听dom的高度变化,实时更新滚动条位置,第一次出现滚动条需要将滚动条
定位到底部。
示例:<div ref='xx' @scroll='onScroll'>
mounted(){
const config = {
attributes: true,
childList: true,
characterData: true,
subtree: true,
} //观察者 的配置
const observer = new MutationObserver(this.handleMutation) //创建观察者对象
observer.observe(this.$refs['xx'], config) //监视对应dom
}
onScroll(){
const { clientHeight, scrollTop, scrollHeight } = this.$refs['xx']
//代表用户滑动到底部了,可以开启自动定位
if (clientHeight + scrollTop >= scrollHeight) {
this.scrollTag = true
} else {
this.scrollTag = false //关闭自动定位
}
}
//自动定位函数
handleMutation(){
//第一次出现滚动条需要开启自动定位功能,oneTag参数只是一次性用的
if(this.$refs['xx'].scrollHeight > this.$refs['xx'].clientHeight && this.oneTag){
this.scrollTag = true
this.oneTag = false
}
//scrollTop设置最底部
if(this.scrollTag){
this.$refs['xx'].scrollTop=this.$refs['xx'].scrollHeight -this.$refs['xx'].clientHeight
}
}
9.hash和history的区别
1.hash路由带有#号;history没有;
2.在进行回车操作的时候hash会重定向到当前页面;history一般会报404;
3.原理:hash路由的变化是hash值的变化(hash值变化不会导致浏览器向服务器发送http请求);
window.location.hash可以读取 #/xxx
它触发的是 hashchange事件(hashchange只改变#后面的url);由于不会发生请求所以不会重新加载页面;
history是HTML5新增的API;它运用了浏览器的历史记录栈。有back,forward,go,pushState,replaceState
等方法;history这种模式需要后台配置支持的,当我们在浏览器输入url后加载时候会发送http请求,
如果后台没有配置兜底方案,或者请求目标不存在则会进入404
4.后台如何配置兜底方案(404错误请求方案):如果请求目标不存在应该重新指向当前的index.html
5.兼容性:hash可以兼容到IE8;history兼容到IE10
10.移动端适配的方案
1.使用rem适配 + postcss-pxtorem -->让开发者更好编写。px -> rem
实现逻辑:由于rem是CSS3新增的一个相对单位---相对于页面字体根元素
所以我们只需要通过改变不同型号屏幕页面的字体比例来进行适配即可
function setRem(w) {
const scale = document.documentElement.clientWidth / w //计算屏幕比例
document.documentElement.style.fontSize = baseSize * Math.min(scale, 2) + 'px'
}
setRem(360) //设计稿宽度360
window.onresize = setRem
vue.config.js (@vue-cli @5)配置
css: {
loaderOptions: {
postcss: {
plugins: [ require('postcss-pxtorem')({
rootValue : 16, //(数字,函数) 根元素字体大小 unitPrecision: 5, //(数字)允许REM单位增长的十进制数字
replace: true, // (布尔值)替换包含rems的规则,而不添加后备
mediaQuery: false, // (布尔值)允许在媒体查询中转换px
minPixelValue: 0, // (数字)设置要替换的最小像素值
selectorBlackList : [], // 忽略转换正则匹配项
propList : ['*'], // 可以从px转换为rem的属性,匹配正则 exclude: /node_modules/i // (字符串,正则表达式,函数)要忽略并保留为px的文件路径 }),
]}
}
}
2.使用vw/vh + postcss-px-to-viewport
//只需要在vue.config.js配置即可
css: {
loaderOptions: {
postcss: {
postcssOptions: {
plugins: [
require('postcss-px-to-viewport')({
unitToConvert: 'px', // 要转化的单位
viewportWidth: 360, // UI设计稿的宽度
unitPrecision: 6, // 转换后的精度,即小数点位数
propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
selectorBlackList: ['wrap'], // 指定不转换为视窗单位的类名,
minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
replace: true, // 是否转换后直接更换属性值
exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
landscape: false, // 是否处理横屏情况
}),
],
},
},
},
},
11.axios封装的基本模板
https://blog.csdn.net/m0_58481462/article/details/126382678?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169035857616800225536632%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169035857616800225536632&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-1-126382678-null-null.142^v91^control_2,239^v3^insert_chatgpt&utm_term=axios%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%B0%81%E8%A3%85&spm=1018.2226.3001.4187