前期回顾
Vue项目实战 —— 后台管理系统( pc端 ) 第三篇_0.活在风浪里的博客-CSDN博客mock模拟数据、折线图、柱状图、饼图,一遍就懂!!~https://blog.csdn.net/m0_57904695/article/details/124861409?spm=1001.2014.3001.5501
目录
一:创建对象
1、通过内置的构造函数创建对象
2、通过字面量创建对象
3、通过构造函数
二 :获取后台接口的前五条数据
三: JavaScript 语法之递归
递归事例1
递归事例2
2.1、for循环 求 1-100的和
2.2、 递归 求1-100的和
四:谷歌浏览器滚动条样式
五:element-ui 表格无缝滚动+鼠标悬停
1:删除最后一个接在第一个数据上
2:使用: requestAnimationFrame实现滚动效果
3:十万条数据秒开、火速渲染
六:下载文件流形式+封装api
七:事件修饰符,组织冒泡
八:js 定时器
九:el-switch 开关
十 : vue动画
十一 :上传
十二 :取消axiso请求
vue3.0
vue2.0
十三:安装node-sass与sass-loader最好指定其版本,版本之间不对应可能会产生安装错误
十四:Vue3 reactive响应式赋值页面不渲染问题 data数据更新,页面没有渲染
解决方法:
十五:动态v-model绑定变量
错误绑定变量形式
变量正确写法
十五:?? 解释
十六:vue项目引入字体类型
十七:assets和static和public的区别
十八:vue3上传组件离开页面取消axios请求
十九:element-plus下拉菜单el-dropdown如何更改样式
二十:拖拽 自定义指令
vue3拖拽
vue3 聚焦 整数/小数点限制
vue2 pc + 移动拖拽
原生js拖拽
transform实现拖拽功能 (提高性能)
二十一:element-plus 面包屑
二十二:vue3国际化多语言 (vue 实现中英文切换功能)
二十四:官网文章上一篇下一篇
二十五:vue3的torefs
二十六 :判断是否是首次进入页面
二十七 :获取1-当前月份的数据
二十八:Vue 页面导出pdf 加水印
实现方法:
完整代码
生成base64
二十九:原生js 滚动楼层
三十 :获取伪元素 修改伪元素
修改伪元素 源码:
三十二:&& 操作符
三十三 :Vue3子组件和父组件都用setup,父组件用ref 获取子组件实例 需要使用defineExpose导出
三十四:Number()和parseInt有什么区别
三十五:网站变灰色
三十五:网站变暗主题(反转)
三十六:高斯模糊
三十七:append和appendChild的三个不同点
三十八:defer和async是什么??区别是什么??
defer
async
三十九:关于<Script>标签在html页面放置位置
四十:vue3中h函数的常见使用方式
render
四十一:Js基础 类型判断
四十二:纯前端预览PDF
四十三:纯前端预览PDF、DOC、DOCX、图片、视频
四十四:纯前端 JS脚本 导出excel 可动态添加数据
四十五:动态红包雨
四十六:Vue3 刮刮乐
四十七: 表格动态编辑Vue2、Vue3
四十八:脱敏 正则
四十九:Javascript模块导入导出
五十: 轮询与长轮询
五十一:交叉观察器 懒加载 new IntersectionObserver(callback, options)
模拟真实场景:
元素观察器 对元素大小进行监听:new ResizeObserver
方法:
例子:
五十二:搜索关键字 下拉框高亮展示
五十三:搜索关键字页面高亮显示 (不支持火狐、苹果)
五十三:关键字防抖搜索高亮
五十四:Vue3文字合成语音
五十五:content arrt()
五十六:经典面试题:让 a == 1 && a == 2 && a == 3 成立
五十七:清除所有定时器
五十八:Vue3 使用ref获取动态DOM
五十九:复选框 全选半选 相同value 联动
六十 :移动端上下左右滑动
六十一: css捕获滚动子元素( scroll-snap-type: y mandatory;)
六十二、滚动柱状图
一:创建对象
1、通过内置的构造函数创建对象
<script>const obj = new Object();obj.name = '张三';obj.age = 18;obj.fn = function() {console.log('我是一个方法');}console.log(obj);
</script>
2、通过字面量创建对象
<script>let obj = {name: '我只会心疼哥哥',age: 0,}console.log(obj)
</script>
3、通过构造函数
构造函数必须new实例化对象,才能传参
<script>function Person(name, age) { // 构造函数 this.age = age;this.name = name;this.run = function() {console.log('我叫' + this.name + '今年' + this.age + '我跑步可快了');}}// prototype 原型Person.prototype.say = function() { // 把say 方法 挂载到Person 原型对象console.log('我叫' + this.name + '今年' + this.age); //引引加加 加加里面是变量}Person.prototype.eat = function() {console.log(this.name + '吃了一顿饭');}var obj = new Person('你不会怪我吧', '28'); // 实例化对象 // new 创建对象 传参 console.log(obj);obj.say();obj.eat();obj.run(); //实例过后才可以调方法和属性
</script>
二 :获取后台接口的前五条数据
axios.get("/api/home/xiaoguotu").then((res) => {console.log(res.data) // 将接口的前5条取出来for (let v = 0; v < 5; v++) { //0 1 2 3 4 5this.list.push(res.data[v]);}console.log(this.list);});
三: JavaScript 语法之递归
递归的概念 :
注意: 函数内部调用自己, 必须要有一个退出条件,条件写在开始位置,不要写在结束位置,否则会无限循环 函数的调用,可以放在循环里面, 也可以放在函数里面
为何报错?
如果你在递归中,递归的变量越来越不接近递归结束条件,那么就会报出 StackOverflowError 这个错误,归根到底就是JVM中栈的空间已经不够容纳递归所需的空间, 所以报错。
如果你递归的数量十分庞大, 那么也会报出 StackOverflowError。
递归事例1
<script>let day = 0;fn()function fn() {day++;console.log('你这个老六真的够了,第' + day + '天,还不放假');if (day < 2) {fn()}}
</script>
打个断点 day===2直接退出函数
递归事例2
2.1、for循环 求 1-100的和
var sum = 0;for (var i = 1; i <= 100; i++) {sum += i; // 0+1 0+2 0+3 ... //相当于sum = sum + i}console.log(sum);
2.2、 递归 求1-100的和
猛一看有可能没看懂是不是?往下看一分钟,还看不懂你把我打一顿
先求得1到2的和
console.log(sum(2)); //现在n===2function sum(n) {return n + n//2+2=4 因为求1到2的和现在n是2要到1才行,所以-1变成 1+2=3,对吧,那么代码就得改成}-----------------------------------------------------------------------------------console.log(sum(3)); function sum(n) {return (n-1) + n // 如果求1-3的和还的让这个函数调用2次才行(3-1 2-1),需要让它自调用,代码得改}
-----------------------------------------------------------------------------------console.log(sum(3));function sum(n) {return sum(n - 1) + n; //递归调用 }
这样咋爆栈了?【娇羞】
第一:减到一不需要继续减了,求的就是1到几,
第二: 看最上面说的递归需要退出条件,并在开始位置写,好的,在写
<script>console.log(sum(100))function sum(n) {//递归结束条件if (n === 1) {return 1;}//递归逻辑return sum(n - 1) + n;}
</script>
四:谷歌浏览器滚动条样式
想要兼容其他浏览器只需要加不同的前缀
Chrome(谷歌浏览器) :WebKit内核 -webkit-Safari(苹果浏览器) :WebKit内核 -webkit-Firefox(火狐浏览器) :Gecko内核 -moz-IE(IE浏览器) : Trident内核 -ms-Opera(欧朋浏览器) :Presto内核 -o-
/* // 滚动条整体 */::-webkit-scrollbar {z-index: 50;width: 10px;height: 3px;border: none;outline: none;}/* // 滚动条滑道 */::-webkit-scrollbar-track {background-color: rgba(206, 14, 14, 0);}/* // 滚动滑块样式 */::-webkit-scrollbar-thumb {-webkit-border-radius: 5px;-moz-border-radius: 5px;border-radius: 5px;background-color: #209ff1;transition: all 0.2s;height: 20px;/* border: 1px solid rgba(0, 0, 0, 0.2); */}/* // 滚动条的样式,是可以跟 :hover 伪类叠加使用的// 滚动条鼠标悬浮时的样式 */:hover::-webkit-scrollbar-thumb {transition: all 0.2s;}::-webkit-scrollbar-thumb:hover {background-image: linear-gradient(to top, #f7f0ac, #acf7f0, #f0acf7);}/* // 滚动条上下的箭头按钮 */::-webkit-scrollbar-button {display: none;}::-webkit-scrollbar-corner {display: none;}
五:element-ui 表格无缝滚动+鼠标悬停
1:删除最后一个接在第一个数据上
<template><el-table:data="tableData"style="width: 100%; height: 100%"@mouseenter.native="Stop()"@mouseleave.native="Up()"><!-- 在vue2中绑定原生的事件需要加native --><el-table-column stripe prop="date" label="日期" width="180"> </el-table-column><el-table-column prop="name" label="姓名" width="180"> </el-table-column><el-table-column prop="address" label="地址"> </el-table-column></el-table>
</template><script>
export default {data() {return {timer: null,tableData: [{date: "2016-05-01",name: "王小虎-1",address: "上海市普陀区金沙江路 1 弄",},{date: "2016-05-02",name: "王小虎-2",address: "上海市普陀区金沙江路 2 弄",},{date: "2016-05-03",name: "王小虎-3",address: "上海市普陀区金沙江路 3 弄",},{date: "2016-05-04",name: "王小虎-4",address: "上海市普陀区金沙江路 4 弄",},],};},created() {this.timer = setInterval(this.scroll, 1000);},methods: {scroll() {setTimeout(() => {this.tableData.push(this.tableData[0]); // 将数组的第一个元素添加到数组的最后this.tableData.shift(); //删除数组的第一个元素,不删的话会将第一个一直放到最后第二个不会变成第一个}, 1000);},//鼠标移上去停止Stop() {clearInterval(this.timer);},Up() {this.timer = setInterval(this.scroll, 1000);},},
};
</script><style lang="scss" scoped>
// 去掉thead的边框并且加背景色
::v-deep .el-table__header thead tr > th {border: none;background-color: #eee;
}
// 去掉tbody的边框和背景色
::v-deep .el-table__body tbody tr > td {border: none;background-color: transparent;
}
::v-deep .el-table__body tbody tr:hover td {background-color: rebeccapurple;
}
::v-deep .el-table__body tbody tr > td :hover {color: red;
}
</style>
2:使用: requestAnimationFrame实现滚动效果
<template><el-table :data="tableData" style="width: 100%" height="200"><el-table-column prop="date" label="Date" /><el-table-column prop="name" label="Name" /><el-table-column prop="address" label="Address" /></el-table>
</template><script lang="ts" setup>
import { onMounted } from 'vue';
const tableData = [{date: '2016-05-03',name: 'Tom1',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-02',name: 'Tom2',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom3',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom4',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-03',name: 'Tom5',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-02',name: 'Tom6',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom7',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom8',address: 'No. 189, Grove St, Los Angeles',},
];
onMounted(() => {// 拿到表格挂载后的真实DOMlet divData = document.querySelector('.el-table__body-wrapper');//1: 拿到元素后,对元素进行定时增加距离顶部距离,实现滚动效果(此配置为每100毫秒移动1像素)/* setInterval(() => {divData.scrollTop += 1;if (divData.clientHeight + divData.scrollTop == divData.scrollHeight) {divData.scrollTop = 0;}}, 30); */// -------------------------------------------------------------------- 使用第二种方法/*2: 使用requestAnimationFrame实现滚动效果,使用setInterval会造成卡顿,requestAnimationFrame不会,因为requestAnimationFrame是浏览器自带的动画函数,会根据浏览器的刷新频率来执行,而setInterval是js自带的定时器,不会根据浏览器的刷新频率来执行,所以会造成卡顿*//*** @method requestAnimationFrame(callback)* @param {Function} callback* @return {Number} requestID* @description 该方法会在浏览器下一次重绘之前调用指定的回调函数,用于更新动画,使动画更加流畅。* @example requestAnimationFrame(callback); 传入回调函数* @example cancelAnimationFrame(requestID); 返回一个数字,用于取消动画,你可以理解为一个动画的id*/// 拿到元素后,对元素进行定时增加距离顶部距离,实现滚动效果(此配置为每100毫秒移动1像素)let timer = null;let speed = 1;function scroll() {// 元素自增距离顶部1像素divData.scrollTop += speed;// 判断元素是否滚动到底部(可视高度+距离顶部=整个高度)if (divData.clientHeight + divData.scrollTop == divData.scrollHeight) {// 重置table距离顶部距离divData.scrollTop = 0;}timer = requestAnimationFrame(scroll);// console.log('😂👨🏾❤️👨🏼==>:', timer); //打印出来的是一个数字,这个数字就是requestAnimationFrame的返回值}// 自动执行scroll();// 取消动画鼠标移入移出divData.onmouseenter = () => {cancelAnimationFrame(timer);timer = null;};divData.onmouseleave = () => {scroll();};// 防止内存泄漏window.onbeforeunload = () => {cancelAnimationFrame(timer);timer = null;};
});
</script><style lang="scss" scoped>
:deep(.el-table__body-wrapper::-webkit-scrollbar) {width: 6px;height: 6px;
}:deep(.el-table__body-wrapper::-webkit-scrollbar-thumb) {background-color: #ddd;border-radius: 3px;
}::-webkit-scrollbar {width: 10px;height: 10px;
}::-webkit-scrollbar-thumb {background-color: #a1a3a9;border-radius: 3px;
}/* // 去掉thead的边框并且加背景色
::v-deep .el-table__header thead tr > th {border: none;background-color: #eee;
}
// 去掉tbody的边框和背景色
::v-deep .el-table__body tbody tr > td {border: none;background-color: transparent;
}
::v-deep .el-table__body tbody tr:hover td {background-color: rebeccapurple;
} */
::v-deep .el-table__body tbody tr > td :hover {color: red;
}
</style>
3:十万条数据秒开、火速渲染
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>十万条数据渲染</title>
</head>
<body><ul>控件</ul><script>setTimeout(() => {// 插入十万条数据const total = 100000// 一次插入 20 条,如果觉得性能不好就减少const once = 20// 渲染数据总共需要几次const loopCount = total / oncelet countOfRender = 0let ul = document.querySelector("ul");function add() {// 优化性能,插入不会造成回流const fragment = document.createDocumentFragment();for (let i = 0; i < once; i++) {const li = document.createElement("li");li.innerText = Math.floor(Math.random() * total);fragment.appendChild(li);}ul.appendChild(fragment);countOfRender += 1;loop();}function loop() {if (countOfRender < loopCount) {window.requestAnimationFrame(add);}}loop();}, 0);</script>
</body>
</html>
六:下载文件流形式+封装api
html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><button onclick="downFile('我是文件里面的内容……','txt')">下载按钮</button>
</body>
<script>function downFile(content, type){let link = document.createElement('a');link.download = '文件名.' + type;link.style.display = 'none';let blob = new Blob([content]);link.href = URL.createObjectURL(blob);document.body.appendChild(link);link.click();document.body.removeChild(link);}
</script></html>
vue
<el-button size="mini" type="primary" @click="downLoad">点击下载</el-button>// 下载文件
const downLoad = () => {axios({url: `/ast/dataItemInformationType/downloadDataItemTemplateFile`,method: 'get',responseType: 'blob',headers: header,}).then((res) => {// 文件类型 pdf:application/pdf;chartset=UTF-8; excel:application/vnd.ms-excel word:'application/mswordconst blob = new Blob([res.data], { type: 'application/vnd.ms-excel' });const fileName = '数据项-企业分类关系模板.xlsx';if ('download' in document.createElement('a')) {// 非IE下载const elink = document.createElement('a');elink.download = fileName;elink.style.display = 'none';elink.href = URL.createObjectURL(blob);document.body.appendChild(elink);elink.click();URL.revokeObjectURL(elink.href); // 释放URL 对象document.body.removeChild(elink);} else {// IE10+下载navigator.msSaveBlob(blob, fileName);}});
};
七:事件修饰符,组织冒泡
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="../vue -lib/js/vue.js"></script><style>.i {width: 500px;}</style>
</head><body><!-- 原生的==> // event.stopPropagation();阻止冒泡 点击事件源冒泡到父级元素上,父元素也要有点击事件 不然没效果// event.preventDefault();阻止默认事件 --><!--Vue里==> // 阻止冒泡 .stop <==> 默认事件.prevent //阻止冒泡和默认事件在-----点击事件后加.stop.prevent 例如:@click.stop.prevent --><div id="app"><!--1 如果只要事件对象不需要传参 不加小括号也可以 方法小括号里直接传形象 --><button @click="btn1($event)"> event事件对象传参</button><br><br><!--2 event要加$,必须加$event, 没有$会被解析成变量 --><!-- 传事件对象$event和实参666 --><button @click="btn2($event,666)"> 传参和 传事件对象</button><br><br><!--3 阻止默认事件 跳转 @click.prevent --><a href="https://www.baidu.com" @click.prevent="a">只弹窗不跳转百度 --组织默认事件跳转</a><br><br><!--4 组止冒泡 点击事件源冒泡到父级元素上 父元素也要有点击事件 不然没效果 @click.stop --><div @click="b"><button @click.stop="b">组止冒泡</button></div><br><!--5 阻止冒泡和默认事件在-----点击事件后加.stop.prevent @click.stop.prevent --><div @click="c"><a href="https://www.baidu.com" @click.stop.prevent="c">阻止冒泡和默认事件</a></div><br><!-- 打印编码console.log(event.keyCode); 打印按键名console.log(event.key); --><!-- 键盘事件 直接在事件后.xx编码或者按键名 --><input type="text" @keydown.enter="d" placeholder="按下回车获取输入的内容" ref="ipt1"><!-- 27 是esc的编码 --><input type="text" @keydown.27="d" placeholder="按下esc获取输入的内容"><!-- 当点击回车 或 esc 打印内容 )--><input type="text" @keydown.esc.enter="d" placeholder="当点击回车 或 esc 打印内容"><!-- f5 只能键盘按下才可以先弹框后刷新加上.stop.就可以阻止刷新 keyup不行 --><!-- // event.preventDefault();// 原生阻止默认事件 在@click后面加.prevent是vue的封装阻止默认事件 @click.prevent="a($event,6)" // event.stopPropagation();// 原生阻止冒泡 在@click后面加.stop是vue的封装阻止冒泡事件 @click.stop="a" --><!-- 阻止默认事件 f5 --><input type="text" @keydown.prevent.f5="e" placeholder="按下f5弹框"><!-- 不绑定点那个按键都会弹框 --><input type="text" @keydown="t" placeholder="没绑定键盘事件点击输入框就会f4弹框"></div>
</body>
<script>new Vue({el: "#app",data: {},methods: {btn1(event) {console.log('打印事件对象里的目标文本-----' + event.target.innerText);},btn2(event, number) {console.log('打印传的参数------' + number);console.log(event);},a(event) {//上面不写小括号这里直接写形参可以收到$event 如果要传参还要打印事件对象event,上面点击事件后就得写() 例如: @click="btn2($event,666) 这里也要俩个形参接受例如:a(event,number){}console.log(event);alert('百度')},b(event) {//直接写event也可以收到时间修饰符alert(0)// event.stopPropagation(); //原生组织冒泡 在@click后面加.stop是vue的封装阻止冒泡事件 @click.stop="b"console.log(event);},c(event) {// event.stopPropagation();阻止冒泡// event.preventDefault();阻止默认事件alert('c')console.log(event);},d(event) {// vue.config.keyCodes.f5=123456// if(event.keyCode !=13){// return false// }// if(event.keyCode !=13) return false; //原生简写console.log(event.target.value);// 打印键盘编码// console.log(event.keyCode);//打印按键名// console.log(event.key);},e(event) {alert('f5')},t() {alert(0)}},created() {this.$nextTick(() => {this.$refs.ipt1.focus();})},})
</script></html>
八:js 定时器
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>用js实现图片定时弹出和定时消失</title>
</head><body><img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1114%2F0FR0104212%2F200FQ04212-7-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1657110663&t=ab0ecb882bf2018a942aaa3f9e26d6d7"width="200px" height="200px" style="display:none" id="img1">
</body></html>
<script>//setTimeout 延迟多少秒---只弹一次的时候用//setInterval 间隔多少秒---循环的时候用var timer = null;timer = setTimeout("fn()", 1000); //一秒钟显示function fn() {var img1 = document.getElementById('img1');img1.style.display = "block";timer = setTimeout("fn1()", 1000); //一秒钟消失}function fn1() {var img1 = document.getElementById('img1');img1.style.display = "none";clearTimeout(timer);timer = setTimeout("fn()", 1000); //在开启就循环}
</script>
九:el-switch 开关
<el-table-column prop="mg_state" label="状态"><template #default="scope"><el-switchv-model="scope.row.mg_state"@change="editSwitch(scope.row.id, scope.row.mg_state)"active-text="开"inactive-text="关"active-color="#13ce66"inactive-color="#ff4949"/></template></el-table-column>
.el-switch__label--left {position: relative;left: 45px;color: #fff;z-index: -1111;}.el-switch__core {width: 50px !important;}.el-switch__label--right {position: relative;right: 46px;color: #fff;z-index: -1111;}.el-switch__label--right.is-active {z-index: 1111;color: #fff !important;}.el-switch__label--left.is-active {z-index: 1111;color: #9c9c9c !important;}
十 : vue动画
<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition name="ani"><h2 v-if="isShow">Hello World</h2></transition></div>
</template>
<script>
export default {name: "App",data() {return {isShow: true,};},methods: {},
};
</script><style >
.ani-enter-from,
.ani-leave-to {opacity: 0;
}
.ani-enter-to,
.ani-leave-from {opacity: 1;
}
.ani-enter-active,
.ani-leave-active {transition: opacity 2s ease;
}
</style>
十一 :上传
<template><el-card class="wrap"><p class="represent">入院治疗知情授权书文件检测工具,是为您进行其文件内容合规情况自查的工具,共包含11个检查项,覆盖患者权利与义务部分。其依据《中华人民共和国民法典》、《中华人民共和国个人信息保护法》、《中华人民共和国执业医师法》等法律法规和标准规范,结合行业数据安全保护相关要求与最佳实践,为您实现安全自查、自测、自改提供专业支持。</p><el-uploadv-model:file-list="fileList"class="upload-demo"action="/compliance/filecheck/fileAnalysis":before-remove="beforeRemove":on-remove="handleRemove":limit="1":on-exceed="handleExceed":headers="header":on-success="handleSuccess":on-error="handleError":before-upload="beforeUpload"accept=".docx, .doc, .pdf"ref="upload":data="{ type: 1 }"><div class="btn"><el-button type="primary" @click="addFile">选取文件</el-button><div style="color: orange">请上传.doc/.docx/pdf文件</div></div></el-upload><div class="processing-status-wrap"><div class="title">处理状态</div><ul class="processing-status"><li class="processing-status-list"><span><img src="/antithetichook.png" alt /></span>待上传文件</li><li class="processing-status-list"><span><img class="blankPicture" :src="blankPicture" alt /></span>正在上传文件</li><li class="processing-status-list"><span><img class="blankPicture" :src="blankPicture" alt /></span>正在解析文件</li><li class="processing-status-list"><span><img class="blankPicture" :src="blankPicture" alt /></span>正在匹配智能规则库</li><li class="processing-status-list"><span><img class="blankPicture" :src="blankPicture" alt /></span>正在智能监测</li><li class="processing-status-list"><span><img class="blankPicture" :src="blankPicture" alt /></span>正在生成检测报告</li><li class="processing-status-list"><span><img class="blankPicture" :src="blankPicture" alt /></span>检测完成</li></ul></div><div><div style="margin-top: 20px" v-if="flag">经平台智能合规文件检测引擎分析,该知情授权书文件的合规风险等级为:<div style="margin-top: 20px; margin-left: 150px"><el-button type="danger" size="mini" v-if="reportStatus == 4">高风险</el-button><el-button type="warning" size="mini" v-if="reportStatus == 3">较高风险</el-button><el-buttonstyle="background-color: #ffea00; color: white"plainsize="mini"v-if="reportStatus == 2">中风险</el-button><el-button type="primary" size="mini" v-if="reportStatus == 1">低风险</el-button><el-button type="success" size="mini" v-if="reportStatus == 0">无风险</el-button></div><br /><p>如查看合规检测报告,请<astyle="color: paleturquoise; cursor: pointer"@click="$router.push({path: '/uploadfile/authorizationCertificateFileCheckReport',query: { id: fileDetectionId },})">【点击此处】</a><!-- $router.query.id --></p></div></div></el-card>
</template>
<script lang="ts" setup>
import { ElMessage, ElMessageBox } from "element-plus";
import type { UploadProps, UploadUserFile } from "element-plus";
import { Session } from "/@/utils/storage";
let blankPicture = ref("public/blankPicture.png");
let fileList = ref<UploadUserFile[]>([]);
const header = reactive({ Authorization: `Bearer ${Session.get("token")}` });
let urlDom = reactive([]);
let timer = ref(null);
// 获取ref元素
let upload = ref(null);
// 检测结果是否显示let reportStatus = ref<number | null>(null);
let fileDetectionId = ref<number | null>(null);
let flag = ref(false);// 文件上传前的钩子函数
const beforeUpload = (file) => {if (file.type !=="application/vnd.openxmlformats-officedocument.wordprocessingml.document" &&file.type !== "application/msword" &&file.type !== "application/pdf") {ElMessage.error("上传文件只能是 docx/doc/pdf 格式");return false;}if (file.size === 0) {ElMessage.warning("上传文件不能是空!");return false;}if (file.size / 1024 / 1024 >= 50) {ElMessage({type: "warning",message: "上传文件大小不能超过50M",duration: 5 * 1000,});handleRemove();return false;}let i = -1;timer.value = setInterval(() => {i++;if (i > 5) {clearInterval(timer.value);} else {urlDom[i].src = "./antithetichook.png";}}, 2000);
};// 成功回调
const handleSuccess = (res) => {// console.log(res);if (res.code === 0) {ElMessage.success("上传成功");reportStatus.value = res.data.reportStatus;fileDetectionId.value = res.data.fileDetectionId;flag.value = true;} else {urlDom[0].src = "public/antithetichook.png";urlDom[1].src = "public/antithetichook.png";ElMessage.error(res.msg);clearInterval(timer.value);fileList.value = [];setTimeout(() => {handlerPublicResetBlankPicture();}, 1000);}
};// 超出个数回调
const handleExceed: UploadProps["onExceed"] = (files, uploadFiles) => {ElMessage.warning(`限制为 ${files.length} 您选择了 ${files.length + uploadFiles.length} 个文件`);
};
// 移除文件之前回调
const beforeRemove: UploadProps["beforeRemove"] = (uploadFile) => {return ElMessageBox.confirm(`您确定移除 ${uploadFile.name} 吗?`).then(() => {flag.value = false;handlerPublicResetBlankPicture();clearInterval(timer.value);// 页面离开前关闭上传请求upload.value.abort();});
};
// 文件移除
const handleRemove = () => {clearInterval(timer.value);// 页面离开前关闭上传请求upload.value.abort();urlDom = [];handlerPublicResetBlankPicture();flag.value = false;
};
//公共重置文件列表 空白图片函数
const handlerPublicResetBlankPicture = () => {for (let i = 0; i < urlDom.length; i++) {urlDom[i].src = "public/blankPicture.png";}
};
// 上传文件按钮点击事件
const addFile = () => {urlDom = document.getElementsByClassName("blankPicture");handlerPublicResetBlankPicture();
};
onBeforeUnmount(() => {clearInterval(timer.value);// 页面离开前关闭上传请求upload.value.abort();
});
</script><style lang="scss" scoped>
.wrap {height: 100vh;display: flex;flex-direction: column;align-items: center;background-color: #fff;border-radius: 5px;padding-top: 40px;.represent {text-indent: 2em;width: 700px;padding-bottom: 30px;}.btn {display: flex;align-items: center;justify-content: center;width: 700px;}ul,li {list-style: none;.blankPicture {width: 15px;height: 15px;}}
}
.processing-status-wrap {position: relative;display: flex;flex-direction: column;justify-content: center;align-items: center;.title {width: 100px;padding-top: 30px;font-size: 15px;font-weight: bolder;text-align: center;}.processing-status-list {padding: 10px 0;}
}
span {width: 30px;height: 30px;
}span img {width: 15px;height: 15px;margin-bottom: -3px;
}
</style>
十二 :取消axiso请求
vue3.0
<template><div><el-button @click="requestAjax">发起ajax</el-button><el-button @click="cancelAjax">取消ajax</el-button></div>
</template>
<script setup>
import axios from "axios";
let cancel = null;
function requestAjax() {if (cancel !== null) {cancel("重复点击发送,取消上一次请求");}axios.get("https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata", {cancelToken: new axios.CancelToken(function executor(c) {// executor 接收一个 cancel"(c)"函数作为参数cancel = c;}),});
}
function cancelAjax() {// cancel the requestcancel("你手动把请求取消了");
}
</script>
vue2.0
<template><div><el-button @click="requestAjax">发起ajax</el-button><el-button @click="cancelAjax">取消ajax</el-button></div>
</template><script>
import axios from "axios";
export default {data() {return {cancel: null,};},methods: {requestAjax() {if (this.cancel) {this.cancel("第二次点击取消了第一次请求");}const CancelToken = axios.CancelToken;axios.get("https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata", {cancelToken: new CancelToken((c) => {this.cancel = c;}),}).then((res) => {console.log(res);}).catch((err) => {console.log(err);});},cancelAjax() {this.cancel();},},
};
</script>
十三:安装node-sass与sass-loader最好指定其版本,版本之间不对应可能会产生安装错误
血泪教训
node版本
安装 sass / loader
"sass-loader": "^10.2.0","node-sass": "^6.0.1",
操作步骤: 1、npm uninstall sass-loader 2、 cnpm install sass-loader@10.2.03、npm uninstall node-sass4、 cnpm install node-sass@6.0.15、再试一次 npm run serve
十四:Vue3 reactive响应式赋值页面不渲染问题 data数据更新,页面没有渲染
问题描述:
//声明变量 -- 错误定义会导致异步请求不及时渲染视图
let data = reactive([])
http().then(res=>{data = res.dataconsole.log(data)//得到数据却没有更新视图
})
//data数据更新,页面没有渲染
解决方法:
- 1、依旧是
reactive
,可以在外面包一层 --- 正确
//声明
let state = reactive({data:[]
})
//赋值
state.data= res.data
- 2、改为
ref
赋值
//声明
let data = ref([])
//赋值
data.value = res.data
所以不建议reactive直接放数组或对象,给它一个属性值
十五:动态v-model绑定变量
- obj[xx] obj.xx 数组对象取值的两种方式,但是他们的区别及使用场景呢?往下看
- 需要循环items时,input v-model动态绑定 obj.name 和 obj.password
items:[{type="text", VM: "obj.name", placeholder: "请输入用户名",}, {type="password", VM: "obj.password", placeholder: "请输入密码",},],obj:{name:'zk',password: '123456',}//VM属性名对应的属性值 为需要动态引入obj对象的name属性
错误绑定变量形式
<div ><inputv-for="(item,index) in items":key="index":type="item.type":placeholder="item.placeholder"v-model="item.VM"/>
</div>
- v-model="item.VM"无法作为变量,只是获取到值
变量正确写法
items:[{type="text", VM: "name", placeholder: "请输入用户名",}, {type="password", VM: "password", placeholder: "请输入密码",},],obj:{name:'zk',password: '123456',}//VM属性名对应的属性值 为需要动态引入obj对象的name属性
<div ><inputv-for="(ele,index) in items":key="index":type="ele.type":placeholder="ele.placeholder"v-model="obj[ele.VM]"/>
</div>
v-model="obj[item.VM]" 使用第二种取值方式,可以作为变量,如果你见到一些比较怪异的嵌套层级,建议使用这种方式,如果使用 obj.xxx , 很有可能出现未知异常,如果不是很清楚js对象,绝大多数排查挺久
原理比较简单,就是在JS中对象,获取属性值时使用的方法不同造成的结果,如果是使用对象.属性名
的方式来获取的话,属性名是不能为变量的;但是使用对象[属性名]
这种方式时,属性名就可以为变量。
十五:?? 解释
更多参考这里
考考你JS 基础牢补牢_0.活在风浪里的博客-CSDN博客
十六:vue项目引入字体类型
- 在assets目录下新建foots(名字语义化可自定义),将后缀为ttf的字体包放进去
- 然后新建一个css文件(我这里新建的是index.css)
- 在新建的index.css写 如下图
@font-face {font-family: "Bold";src: url("Alibaba-PuHuiTi-Bold.ttf") format("truetype");font-weight: normal;font-style: normal;
}
多个字体,复制多份不用在新建文件 font-family是别名 src是路径
4.在main.ts引入
import "@/assets/foots/index.css";
5.在页面使用 font-family:字体别名;
在项目中引入icon字体图标参考我的这篇
vue怎么使用icon阿里字体图标_0.活在风浪里的博客-CSDN博客
十七:assets和static和public的区别
1、目录结构不同
2、assets中的资源会被webpack处理,打包后会在dist中合并成一个文件;static中的资源不会被webpack处理,打包后直接复制到dist(默认是dist/static)下
3、推荐assets中存放自己的资源(css、images、utils等),static中放第三方资源(pdf.js、iconfont等)
4、动态绑定中,assets的图片会加载失败,因为webpack使用commonJS规范,需要使用require引入图片(可以通过import的方式引入)
html
<img style="width:100px;" :src="assetsUrl">
js
assetsUrl: '@/assets/images/002.jpg', // 无法显示图片
正确方式
assetsUrl: require('@/assets/images/002.jpg'),
图片在static下使用@不管用,因为static不会经过webpack,所以配置的@不能用
<img src="../../static/图片路径" >
图片在static下
是不能使用
require
进行载入,报错(找不到资源模块);
public
./表示当前目录
../表示父级目录
/表示根目录
图片在public下直接 / 就可以找到,如果你用相对路径../../类似这样找到public下的图片,本地是可以显示,但是打包后是没有public文件夹的,所以本地可以显示,打包后会找不到路径404,我们可以直接用 / 获取public的图片和文件,本地和打包后都会显示
十八:vue3上传组件离开页面取消axios请求
十九:element-plus下拉菜单el-dropdown如何更改样式
二十:拖拽 自定义指令
vue3拖拽
utils下新建文件夹,里面放拖拽文件和其他自定义指令文件
位置: drag.ts
const dialogDrag = (app) => {app.directive('drag', {// 渲染完毕mounted(el) {// 可视窗口的宽度const clientWidth = document.documentElement.clientWidth;// 可视窗口的高度const clientHeight = document.documentElement.clientHeight;// 记录坐标let domset = {x: clientWidth / 10, // 默认width 50%y: (clientHeight * 5) / 100, // 根据 15vh 计算};// 弹窗的容器const domDrag = el.firstElementChild.firstElementChild;// 重新设置上、左距离domDrag.style.marginTop = domset.y + 'px';domDrag.style.marginLeft = domset.x + 'px';// 记录拖拽开始的光标坐标,0 表示没有拖拽let start = { x: 0, y: 0 };// 移动中记录偏移量let move = { x: 0, y: 0 };// 鼠标按下,开始拖拽document.onmousedown = (e) => {// 判断对话框是否重新打开if (domDrag.style.marginTop === '50vh') {// 重新打开,设置 domset.y topdomset.y = (clientHeight * 5) / 100;}start.x = e.clientX;start.y = e.clientY;domDrag.style.cursor = 'move'; // 改变光标形状};// 鼠标移动,实时跟踪document.onmousemove = (e) => {if (start.x === 0) {// 不是拖拽状态return;}move.x = e.clientX - start.x;move.y = e.clientY - start.y;// 初始位置 + 拖拽距离domDrag.style.marginLeft = domset.x + move.x + 'px';domDrag.style.marginTop = domset.y + move.y + 'px';};// 鼠标抬起,结束拖拽document.onmouseup = (e) => {move.x = e.clientX - start.x;move.y = e.clientY - start.y;// 记录新坐标,作为下次拖拽的初始位置domset.x += move.x;domset.y += move.y;domDrag.style.cursor = ''; // 恢复光标形状domDrag.style.marginLeft = domset.x + 'px';domDrag.style.marginTop = domset.y + 'px';// 结束拖拽start.x = 0;};},});
};
export default dialogDrag;
位置:main.ts
import myDrag from './utils/directive/drag.ts';.use(myDrag)
在.vue文件中使用:
!!! 注意 vue3自定义拖拽 在el-dialog标签外包裹一个div标签后,使用v-dialogdrag即可调用该自定义插件, 不要直接在el-dialog上调用,否则将无法生效 ,会提示不能在多个根节点使用,el-dialog正是多个根节点
<div v-drag><el-dialog v-model="dialogVisible" title="修改" width="30%"><div><el-icon size="8" color="red"><StarFilled /></el-icon><div class="popover-title">Risk Score</div><!-- v-focus 自动聚焦。对于非文本框聚焦使用 v-focus:1 实现自动聚焦 自动选中 v-number整数/小数点限制 --><el-inputv-model="inputValue"placeholder="Please input"clearablev-focusv-number/></div><template #footer><span class="dialog-footer"><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="dialogVisible = false">确定</el-button></span></template></el-dialog></div>
vue3 聚焦 整数/小数点限制
位置:myFocus.ts
import { App, nextTick } from 'vue'
// 根据el获取input
const getInput = (el: HTMLElement): HTMLInputElement | null => el instanceof HTMLInputElement ? el : el.querySelector('input')
export default {install (app: App) {// v-focus 自动聚焦。对于非文本框聚焦使用 v-focus:1 实现自动聚焦 自动选中 v-number 整数/小数点限制app.directive('focus', {updated: async (el: HTMLElement, { arg }) => {// 为了防止数据未即使更新。await nextTick()// 对于非文本框聚焦(使用了 contenteditable )的直接聚焦即可if (arg) el.focus?.()else getInput(el)?.focus()}})// v-select 自动选中。对于非文本框请使用 v-select:1app.directive('select', {mounted: async (el: HTMLElement, { arg }) => {// 为了防止数据未即使更新。await nextTick()if (arg) el// elementplus的文本框。是嵌套了一个文本框。。getInput(el)?.select()}})let inputHandler = () => {}// 限制input框,仅能输入数字。默认限制输入整数,可以通过传参设置小数位数// v-number 限制整数,v-number:2 限制两位小数,v-number:2=3 限制两位小数,整数位三位,v-number=2 限制两位整数\app.directive('number', {mounted (el: HTMLElement, { arg, value }) {const input: HTMLInputElement = <HTMLInputElement>getInput(el)if (input) {// 小数正则const decimal: string = arg ? `(\\.\\d{0,${arg}})?` : ''// 整数正则const integer: string = value ? `(0|[1-9]\\d{0, ${value - 1}})` : '\\d*'const regExp: RegExp = new RegExp((integer + decimal), 'g')inputHandler = () => {// 替换所有的非数字项// 如果输入的数字不符合正则表达式,则替换为''input.value = input.value.toString().trim().replace(/[^\d.]/g, '')?.match?.(regExp)?.[0] ?? ''}// 在文本框输入的时候触发input.addEventListener('input', inputHandler, true)}},unmounted (el: HTMLElement) {// 解除绑定的时候去除事件const input: HTMLInputElement = <HTMLInputElement>getInput(el)input.removeEventListener('input', inputHandler, true)}})}
}
位置;main.ts
import myFocus from './utils/directive/myfocus.ts';.use(myFocus)
页面使用:
<!-- v-focus 自动聚焦。对于非文本框聚焦使用 v-focus:1 实现自动聚焦 自动选中v-number 整数/小数点限制 --><el-inputv-model="inputValue"placeholder="Please input"clearablev-focusv-number/>
vue2 pc + 移动拖拽
新建drag.js文件这是pc端的拖拽方案
import Vue from 'vue'Vue.directive('drag',{bind:function (el) {//监听document是因为如果监听元素el的话鼠标太快移出元素后就会失效el.onmousedown = (event) => {//算出鼠标相对元素的位置let pointX = event.clientX - el.offsetLeft;//鼠标位置X减去元素距离左边距离(鼠标到元素左边的距离)let pointY = event.clientY - el.offsetTop;//鼠标位置Y减去距离顶部距离(鼠标到元素顶部的高度)document.onmousemove = (e)=>{//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置let left = e.clientX - pointX;let top = e.clientY - pointY;//移动当前元素el.style.left = left + 'px';el.style.top = top + 'px';};document.onmouseup = (e) => {document.onmousemove = null;document.onmouseup = null;};};}
})
新建dragMove.js文件这是移动端的拖拽方案
import Vue from 'vue'
Vue.directive('dragMove',{bind: function (el, binding) {var touch,disX,disYel.ontouchstart = (e) => {if(e.touches){//有可能对象在e上也有可能对象在e.touches[0]上touch = e.touches[0];}else {touch = e;}disX = touch.clientX - el.offsetLeft;//鼠标位置X减去元素距离左边距离(鼠标到元素左边的距离)disY = touch.clientY - el.offsetTop;//鼠标位置Y减去距离顶部距离(鼠标到元素顶部的高度)}el.ontouchmove = (e)=>{if(e.touches){//有可能对象在e上也有可能对象在e.touches[0]上touch = e.touches[0];}else {touch = e;}//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置let left = touch.clientX - disX;let top = touch.clientY - disY;//移动当前元素el.style.left = left + 'px';el.style.top = top + 'px';e.preventDefault();//阻止页面的滑动默认事件};el.ontouchend = (e) => {// el.ontouchmove = null;// el.ontouchend = null;};}
})
在页面引用:
<script>import drag from '../assets/drag'import dragMove from '../assets/dragMove' </script>
可以在页面引入使用也可以在main.ts全局注册使用
在页面使用:
<div v-dragMove v-drag>我是拖拽,你如果是pc端,就不必引入移动端的</div>
原生js拖拽
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><style>* {margin: 0;padding: 0;}body,html {width: 100%;height: 100%;}.wrap-father {position: relative;width: 100%;height: 100%;}.drag-child {position: absolute;width: 300px;height: 300px;background-color: pink;}</style><body><div class="wrap-father"><div class="drag-child"></div></div></body><script>/***思路 :* 0 白话解释:其实就是用js不断给元素设置left和top值,让元素跟着鼠标移动,既然设置left top 肯定要开启绝对定位 父元素也要开启相对定位* 1 当鼠标按下的时候 记录鼠标在盒子的位置 [重要] 鼠标的位置 - 盒子offset* 2 当鼠标移动的时候 用当前鼠标的位置 - 鼠标在盒子的位置(1) = 移动后盒子offset* 3 当盒子位置小于0的时候 不让盒子移动 赋值位置为0 (控制左边盒子不让溢出)* 4 当盒子位置大于浏览器的宽度 - 盒子的宽度的时候 不让盒子移动 赋值位置为浏览器的宽度 - 盒子的宽度 (控制右边盒子不让溢出)* 5 鼠标抬起的时候 清除鼠标在盒子的位*/window.onload = function () {// console.log(window);let drag = document.querySelector(".drag-child");drag.onmousedown = function (event) {let ev = event || window.event;// 得到鼠标在盒子的位置let diffX = ev.clientX - drag.offsetLeft;let diffY = ev.clientY - drag.offsetTop;document.onmousemove = function (event) {drag.style.cursor = "move";let ev = event || window.event;// 得到鼠标移动后盒子的offsetlet offsetLastMoveX = ev.clientX - diffX;let offsetLastMoveY = ev.clientY - diffY;// 左右不能溢出if (offsetLastMoveX < 0) {offsetLastMoveX = 0;} else if (offsetLastMoveX >= window.innerWidth - drag.offsetWidth) {offsetLastMoveX = window.innerWidth - drag.offsetWidth;}// 上下不能溢出if (offsetLastMoveY < 0) {offsetLastMoveY = 0;} else if (offsetLastMoveY >=window.innerHeight - drag.offsetHeight) {offsetLastMoveY = window.innerHeight - drag.offsetHeight;}// 判断好设置盒子的位置drag.style.left = offsetLastMoveX + "px";drag.style.top = offsetLastMoveY + "px";};document.onmouseup = function () {// 鼠标抬起的时候 清除事件document.onmousemove = null;document.onmousedown = null;drag.style.cursor = "default";};};};</script> </html>
transform实现拖拽功能 (提高性能)
- 上面的原生js拖拽其实是通过定位不断的赋值left和top,缺点是会造成频繁的回流和重绘
-
在 PC 端上,我们使用绝对定位来做移动是完全没问题的,也可以使用 translate.
因为 PC 上使用绝对定位使用 CPU,触发重排和重绘,浏览器依然可以以每秒60帧来运行,我们肉眼看不出来。
但是放到移动端上,触发浏览器重拍和重绘,造成页面的卡顿。
使用 translate 不会触发重排或重绘,但会触发合成。会使得元素创建一个 GPU 层(图层),这样子,位移也只是在你自己的图层上,不会影响整个页面布局。translate 效率更高,绘制时间端,更加流畅。
- 重绘:
DOM树没有元素增加或删除,只是样式的改变,DOM树没有改变,浏览器对某一元素进行单独的渲染,这个过程就叫做重绘
回流:
DOM树中的元素被增加或者删除,导致浏览器需要重新的去渲染整个DOM树,回流比重绘更消耗性能,发生回流必定重绘,重绘不一定会导致回流。 - 如何避免(减少)回流
css
(1)使用visibility替换display:none(前者只会引起重绘,后者则会引发回流)
(2)避免使用table布局,因为可能一个小小的改动会造成整个页面布局的重新改动
(3)将动画效果应用到position 属性为absolute或fixed的元素上,避免影响其他元素的布局
【脱离文档流之后,对其它的元素影响小,从而提升性能】
(4)避免使用CSS的JavaScript表达式,可能会引发回流(例如:calc())。
JavaScript
(1)避免频繁操作样式,最好将样式列表定义为class并一次性更改class属性。
(2)避免频繁操作DOM,创建一个documentFragment(文档片段),在它上面应用所有 DOM操作,最后再把它添加到文档中。例如如下
(3)可以先为元素设置为不可见:display: none,操作结束后再把它显示出来。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>transform实现拖拽功能</title><style>* {padding: 0;margin: 0;box-sizing: border-box;}html,body {height: 100%;}.app {height: 100%;display: grid;place-items: center;}.drop-box {width: 100px;height: 100px;background-color: orange;border-radius: 10px;box-shadow: 0 0 8px 0 #353535;cursor: move;}</style></head><body><div class="app"><div class="drop-box"></div></div><script>// 使用transform实现拖拽功能比使用定位的方式要性能更好,因为transform只会触发合成层的重绘,而定位会触发重排(回流)和重绘// 什么是合成层?合成层是一个独立的图层,它不会影响到其他图层,也不会被其他图层影响,它的渲染不会阻塞主线程,所以性能更好const box = document.querySelector(".drop-box");let isDown = false;let mX = 0;let mY = 0;let currentX = 0;let currentY = 0;box.addEventListener("mousedown", (e) => {isDown = true;mX = e.pageX;mY = e.pageY;document.addEventListener("mousemove", move);document.addEventListener("mouseup", up);});const move = (e) => {if (!isDown) return;let x = e.pageX - mX;let y = e.pageY - mY;if (currentX && currentY) {x += currentX;y += currentY;}let elHeight = box.offsetHeight;let elWidth = box.offsetWidth;let rangeHeight = document.body.offsetHeight / 2 - elHeight / 2;let rangeWidth = document.body.offsetWidth / 2 - elWidth / 2;if (Math.abs(y) > rangeHeight) {if (y < 0) {y = -rangeHeight;} else {y = rangeHeight;}}if (Math.abs(x) > rangeWidth) {if (x < 0) {x = -rangeWidth;} else {x = rangeWidth;}}box.style.cssText = `transform:translate3d(${x}px, ${y}px, 1px)`;};const up = () => {isDown = false;let formatValue = window.getComputedStyle(box, null).transform.replace(/[^0-9\-,]/g, "");let formatArr = formatValue.split(",");currentX = Number(formatArr[formatArr.length - 4]);currentY = Number(formatArr[formatArr.length - 3]);document.removeEventListener("mousemove", move);document.removeEventListener("mouseup", up);};</script></body>
</html>
二十一:element-plus 面包屑
1:在compontens新建文件 如 breadcrumb.vue
<template><div><el-breadcrumb separator="/"><el-breadcrumb-item to="/" v-if="route.path!=='/'">首页</el-breadcrumb-item><el-breadcrumb-item v-for="(item, index) in navArray" :to="{path:item.path}">{{item.meta.title}}</el-breadcrumb-item></el-breadcrumb></div>
</template>
<script lang="ts" setup>import { useRoute, useRouter } from "vue-router";
import { watch, ref, onMounted } from "vue";const navArray = ref([]);
const route = useRoute();
const router = useRouter();watch(// 监听路由记录() => route.matched,(newValue, oldValue) => {navArray.value = newValue;}
);
</script>
2:在App.vue引入
只要是有子路由在自身展示,需要配置router-view,比如所有页面经过App.vue,则在其配置
解释路由:
二十二:vue3国际化多语言 (vue 实现中英文切换功能)
1、安装
cnpm install vue-i18n --save-dev
2、建立对应的页面
zh.js
export default {header: {home: "主页",login: "登录",subLogin: "子级登录",test: "测试",},footer: {copyright: "中文数安信Copyright © 2019-2020",},
};
en.js (其实就是将中文翻译了一下在使用的地方写变量,点击按钮切换)
export default {header: {home: "Home",login: "Login",subLogin: "subLogin",test: "test",},footer: {copyright: "Copyright © 2019-2020",},
};
index.js
import { createI18n } from "vue-i18n"; //引入vue-i18n组件
import zh from "./zh"; //引入中文语言包
import en from "./en"; //引入英文语言包const i18n = createI18n({locale: "zh", //默认显示的语言fallbackLocale: "zh", //默认显示的语言legacy: false, //是否使用旧的语言包方式,默认为false// 去除控制台警告// missingWarn: false,// fallbackWarn: false,// globalInjection: true,messages: {//语言包zh: zh,en: en,},
});
export default i18n; //将i18n暴露出去,在main.js中引入挂载
3、文件建立完成后,在main.ts引入
import i18n from "./lang";app.use(i18n);
4、引入好了,就该在页面使用了
{{$t('header.login')}}
5、默认中文,切换语言
<el-button type="primary" @click="executorLang">切换语言</el-button>
<script setup>
import { useI18n } from "vue-i18n";
const { locale } = useI18n();function executorLang() {// console.log("😂👨🏾❤️👨🏼==>: ", locale.value);if (locale.value === "zh") {locale.value = "en";} else {locale.value = "zh";}}</script>
可能报警告说需要全局使用 在vite.config.ts配置上路径别名
resolve: {//配置根路径别名: import('@/pages/login/login.vue')alias: {"@": path.resolve(__dirname, "src"),"vue-i18n": "vue-i18n/dist/vue-i18n.cjs.js",},
二十四:官网文章上一篇下一篇
<template><ul><liv-for="(item, index) in dataArr"@click="handlerClick(item)":class="{'current':item===itemClick,item}">{{ item }}</li></ul><a href="javascript:;" v-if="prevItem" @click="handlerClickPrev">上一篇:{{prevItem}}</a><h1>{{itemClick}}</h1><a href="javascript:;" v-if="nextItem" @click="handlerClickNext">下一篇:{{nextItem}}</a>
</template>
<script setup>
// 获取vue api
import { ref, reactive } from "vue";
// 定义数据
let dataArr = reactive(["第一", "第二", "第三", "第四", "第五"]);
// 定义当前选中的item
let itemClick = ref(null);
// 获取点击项的索引
let itemIndex = ref(null);
// 获取上一篇项
let prevItem = ref(null);
// 获取下一篇项
let nextItem = ref(null);// 点击的每一项事件
function handlerClick(item) {// 设置当前选中的item赋值给itemClickitemClick.value = item;// 重要commonFn();
}// 点击上一项将上一项设置为当前选中的item
function handlerClickPrev() {itemClick.value = prevItem.value;commonFn();
}
// 点击下一项将下一项设置为当前选中的item
function handlerClickNext() {itemClick.value = nextItem.value;commonFn();
}// 公共方法 上下项赋值(上下篇)
function commonFn() {// 检测当前项在数组中是否存在,存在则获取索引 indexOf---查询某个元素在数组中第一次出现的位置 存在该元素,返回下标,不存在 返回 -1itemIndex.value = dataArr.indexOf(itemClick.value);// 获取上一项itemprevItem.value = dataArr[itemIndex.value - 1];// 获取下一项itemnextItem.value = dataArr[itemIndex.value + 1];
}
</script>
<style scoped>
.item {width: 300px;height: 30px;line-height: 30px;background-color: pink;margin: 10px auto;
}
.item:hover {background-color: tomato;
}
.current {background-color: tomato;
}
</style>
二十五:vue3的torefs
// reactive 是vue3的一个组合api vue3中新增的最核心的功能就是组合api
// reactive vue3建议只用来声明对象 他声明的对象中的每一个属性都是响应式的,但是解构了或直接将reactive的值写在template上,就不具备响应式了
// 他是结合es6的proxy 结合递归给每一个reactive声明的对象数据添加上 setter/getter方法 从而实现数据的响应式
// ref是用reactive封装成的一个方法 这个方法我们通常用来声明基本数据类型和数组数据
// ref声明的数据是一个对象 值是value ref在template的时候不需要写value 但是js的时候必须写value
// toRefs可以解构reactive对象的属性,将属性解构出来依旧具备响应,转换成一个个ref对象,既然是ref对象肯定是需要.value的
import { toRefs } from "vue";
// 将reactive定义的数据 解构出来且不失去响应式,如果直接解构会失去响应,需要.value
// state.chartData === chartData.value 在template不需要加.value
const { chartData, chartData2, chartData3, chartData4, chartData5 } = toRefs(state);// 对象解构var {x,y}={x:1,y:2};console.log(x,y); // 1 2
二十六 :判断是否是首次进入页面
mounted() {if (window.performance.navigation.type == 1) {console.log("页面被刷新");} else {console.log("首次被加载");}},destroyed() {window.performance.navigation.type == 1;}
二十七 :获取1-当前月份的数据
// 获取1-当前月份的数据
const date = new Date();
const month = date.getMonth() + 1;
const arr = [];
for (let i = 1; i <= month; i++) {arr.push(i + "月");
}
二十八:Vue 页面导出pdf 加水印
实现方法:
首先记得先安装包
npm install html2canvas
npm install jspdfimport html2Canvas from "html2canvas";
import JsPDF from "jspdf";
完整代码
<template><div style="width: 100%; height: 100%"><el-button type="primary" @click="exportPdf('pdfDom', '活在风浪里')">导出pdf(添加页边距的)</el-button><el-button type="primary" @click="getPdfFromHtml('pdfDom', '活在风浪里')">导出pdf时+(水印)</el-button><el-button type="primary" @click="getPdf('with-watermark', '活在风浪里')">导出pdf(带水印一起导出)</el-button><ul id="pdfDom"><!-- li{我是内容 - $ }*100 快速敲出结构 --><li>我是内容 - 1</li><li>我是内容 - 2</li><li>我是内容 - 3</li><li>我是内容 - 4</li><li>我是内容 - 5</li><li>我是内容 - 6</li><li>我是内容 - 7</li><li>我是内容 - 8</li><li>我是内容 - 9</li><li>我是内容 - 10</li><li>我是内容 - 11</li><li>我是内容 - 12</li><li>我是内容 - 13</li><li>我是内容 - 14</li><li>我是内容 - 15</li><li>我是内容 - 16</li><li>我是内容 - 17</li><li>我是内容 - 18</li><li>我是内容 - 19</li><li>我是内容 - 20</li><li>我是内容 - 21</li><li>我是内容 - 22</li><li>我是内容 - 23</li><li>我是内容 - 24</li><li>我是内容 - 25</li><li>我是内容 - 26</li><li>我是内容 - 27</li><li>我是内容 - 28</li><li>我是内容 - 29</li><li>我是内容 - 30</li><li>我是内容 - 31</li><li>我是内容 - 32</li><li>我是内容 - 33</li><li>我是内容 - 34</li><li>我是内容 - 35</li><li>我是内容 - 36</li><li>我是内容 - 37</li><li>我是内容 - 38</li><li>我是内容 - 39</li><li>我是内容 - 40</li><li>我是内容 - 41</li><li>我是内容 - 42</li><li>我是内容 - 43</li><li>我是内容 - 44</li><li>我是内容 - 45</li><li>我是内容 - 46</li><li>我是内容 - 47</li><li>我是内容 - 48</li><li>我是内容 - 49</li><li>我是内容 - 50</li><li>我是内容 - 51</li><li>我是内容 - 52</li><li>我是内容 - 53</li><li>我是内容 - 54</li><li>我是内容 - 55</li><li>我是内容 - 56</li><li>我是内容 - 57</li><li>我是内容 - 58</li><li>我是内容 - 59</li><li>我是内容 - 60</li><li>我是内容 - 61</li><li>我是内容 - 62</li><li>我是内容 - 63</li><li>我是内容 - 64</li><li>我是内容 - 65</li><li>我是内容 - 66</li><li>我是内容 - 67</li><li>我是内容 - 68</li><li>我是内容 - 69</li><li>我是内容 - 70</li><li>我是内容 - 71</li><li>我是内容 - 72</li><li>我是内容 - 73</li><li>我是内容 - 74</li><li>我是内容 - 75</li><li>我是内容 - 76</li><li>我是内容 - 77</li><li>我是内容 - 78</li><li>我是内容 - 79</li><li>我是内容 - 80</li><li>我是内容 - 81</li><li>我是内容 - 82</li><li>我是内容 - 83</li><li>我是内容 - 84</li><li>我是内容 - 85</li><li>我是内容 - 86</li><li>我是内容 - 87</li><li>我是内容 - 88</li><li>我是内容 - 89</li><li>我是内容 - 90</li><li>我是内容 - 91</li><li>我是内容 - 92</li><li>我是内容 - 93</li><li>我是内容 - 94</li><li>我是内容 - 95</li><li>我是内容 - 96</li><li>我是内容 - 97</li><li>我是内容 - 98</li><li>我是内容 - 99</li><li>我是内容 - 100</li></ul><div id="with-watermark">123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123123 123 123 123 123</div></div>
</template><script>
import html2Canvas from "html2canvas";
import JsPDF from "jspdf";
export default {methods: {// 间距导出pdf 需要设置元素padding,不能文字显示会有点问题exportPdf(DomName, title) {var element = document.getElementById("pdfDom");window.pageYoffset = 0; // 滚动置顶document.documentElement.scrollTop = 0;document.body.scrollTop = 0;html2Canvas(element, {logging: false,dpi: window.devicePixelRatio * 1, //将分辨率提高到特定的DPI 提高四倍scale: 1, //按比例增加分辨率useCORS: true, // 【重要】开启跨域配置}).then(function (canvas) {var pdf = new JsPDF("p", "mm", "a4"); //A4纸,纵向var ctx = canvas.getContext("2d"),a4w = 190,a4h = 277, //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277imgHeight = Math.floor((a4h * canvas.width) / a4w), //按A4显示比例换算一页图像的像素高度renderedHeight = 0;while (renderedHeight < canvas.height) {var page = document.createElement("canvas");page.width = canvas.width;page.height = Math.min(imgHeight, canvas.height - renderedHeight); //可能内容不足一页//用getImageData剪裁指定区域,并画到前面创建的canvas对象中page.getContext("2d").putImageData(ctx.getImageData(0,renderedHeight,canvas.width,Math.min(imgHeight, canvas.height - renderedHeight)),0,0);pdf.addImage(page.toDataURL("image/jpeg", 1.0),"JPEG",10,10,a4w,Math.min(a4h, (a4w * page.height) / page.width)); //添加图像到页面,保留10mm边距renderedHeight += imgHeight;if (renderedHeight < canvas.height) pdf.addPage(); //如果后面还有内容,添加一个空页// delete page;}pdf.save(`${title}.pdf`);});},// 导出pdf时加水印getPdfFromHtml(ele, pdfFileName) {ele = document.getElementById("pdfDom");pdfFileName = pdfFileName || "pdf";window.pageYoffset = 0; // 滚动置顶document.documentElement.scrollTop = 0;document.body.scrollTop = 0;let scale = window.devicePixelRatio * 1;html2Canvas(ele, {// dpi: 300,dpi: window.devicePixelRatio * 1, //将分辨率提高到特定的DPI 提高四倍scale: 1, //按比例增加分辨率logging: false,scale: scale,useCORS: true, //允许canvas画布内可以跨域请求外部链接图片, 允许跨域请求。allowTaint: false,height: ele.offsetHeight,width: ele.offsetWidth,windowWidth: document.body.scrollWidth,windowHeight: document.body.scrollHeight,backgroundColor: "#fff",}).then((canvas) => {//未生成pdf的html页面高度var leftHeight = canvas.height;var a4Width = 555.28; // 原A4宽 592 因为要设置边距 20 ,这里要减掉 40var a4Height = 801.89; // 原A4高 841 因为要设置边距 20 ,这里要减掉 40//一页pdf显示html页面生成的canvas高度;var a4HeightRef = Math.floor((canvas.width / a4Width) * a4Height);//pdf页面偏移var position = 0;var pageData = canvas.toDataURL("image/jpeg", 1.0);var pdf = new JsPDF("x", "pt", "a4");var index = 1,canvas1 = document.createElement("canvas"),height;pdf.setDisplayMode("fullwidth", "continuous", "FullScreen");function createImpl(canvas) {if (leftHeight > 0) {index++;var checkCount = 0;if (leftHeight > a4HeightRef) {var i = position + a4HeightRef;for (i = position + a4HeightRef; i >= position; i--) {var isWrite = true;for (var j = 0; j < canvas.width; j++) {var c = canvas.getContext("2d").getImageData(j, i, 1, 1).data;if (c[0] != 0xff || c[1] != 0xff || c[2] != 0xff) {isWrite = false;break;}}if (isWrite) {checkCount++;if (checkCount >= 10) {break;}} else {checkCount = 0;}}height = Math.round(i - position) || Math.min(leftHeight, a4HeightRef);if (height <= 0) {height = a4HeightRef;}} else {height = leftHeight;}canvas1.width = canvas.width;canvas1.height = height;// console.log(index, 'height:', height, 'pos', position);var ctx = canvas1.getContext("2d");ctx.drawImage(canvas,0,position,canvas.width,height,0,0,canvas.width,height); // 边距这里设置0,不然又黑边var pageHeight = Math.round((a4Width / canvas.width) * height);// pdf.setPageSize(null, pageHeight)if (position != 0) {pdf.addPage();}// 设置 20px 边距pdf.addImage(canvas1.toDataURL("image/jpeg", 1.0),"JPEG",20,20,a4Width,(a4Width / canvas1.width) * height);leftHeight -= height;position += height;// $('.pdfProgress').text(index + 1);// $('.pdfTotal').text(index + Math.ceil(leftHeight / a4HeightRef));if (leftHeight > 0) {//添加全屏水印let base64 ="data:image/png;base64,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";for (let i = 0; i < 6; i++) {for (let j = 0; j < 5; j++) {const left = j * 120 + 20;pdf.addImage(base64, "JPEG", left, i * 150, 150, 50); // 关掉水印}}setTimeout(createImpl, 500, canvas);} else {pdf.save(pdfFileName + ".pdf");}}}//当内容未超过pdf一页显示的范围,无需分页if (leftHeight < a4HeightRef) {pdf.addImage(pageData,"JPEG",20,20,a4Width,(a4Width / canvas.width) * leftHeight);pdf.save(pdfFileName + ".pdf");} else {try {pdf.deletePage(0);setTimeout(createImpl, 500, canvas);} catch (err) {console.log(err);}}});},// 带水印一起导出getPdf(ele, pdfFileName) {ele = document.getElementById("with-watermark");pdfFileName = pdfFileName || "pdf";window.pageYoffset = 0; // 滚动置顶document.documentElement.scrollTop = 0;document.body.scrollTop = 0;let scale = window.devicePixelRatio * 1;html2Canvas(ele, {// dpi: 300,dpi: window.devicePixelRatio * 1, //将分辨率提高到特定的DPI 提高四倍scale: 1, //按比例增加分辨率logging: false,scale: scale,useCORS: true, //允许canvas画布内可以跨域请求外部链接图片, 允许跨域请求。allowTaint: false,height: ele.offsetHeight,width: ele.offsetWidth,windowWidth: document.body.scrollWidth,windowHeight: document.body.scrollHeight,backgroundColor: "#fff",}).then((canvas) => {//未生成pdf的html页面高度var leftHeight = canvas.height;var a4Width = 555.28; // 原A4宽 592 因为要设置边距 20 ,这里要减掉 40var a4Height = 801.89; // 原A4高 841 因为要设置边距 20 ,这里要减掉 40//一页pdf显示html页面生成的canvas高度;var a4HeightRef = Math.floor((canvas.width / a4Width) * a4Height);//pdf页面偏移var position = 0;var pageData = canvas.toDataURL("image/jpeg", 1.0);var pdf = new JsPDF("x", "pt", "a4");var index = 1,canvas1 = document.createElement("canvas"),height;pdf.setDisplayMode("fullwidth", "continuous", "FullScreen");function createImpl(canvas) {if (leftHeight > 0) {index++;var checkCount = 0;if (leftHeight > a4HeightRef) {var i = position + a4HeightRef;for (i = position + a4HeightRef; i >= position; i--) {var isWrite = true;for (var j = 0; j < canvas.width; j++) {var c = canvas.getContext("2d").getImageData(j, i, 1, 1).data;if (c[0] != 0xff || c[1] != 0xff || c[2] != 0xff) {isWrite = false;break;}}if (isWrite) {checkCount++;if (checkCount >= 10) {break;}} else {checkCount = 0;}}height = Math.round(i - position) || Math.min(leftHeight, a4HeightRef);if (height <= 0) {height = a4HeightRef;}} else {height = leftHeight;}canvas1.width = canvas.width;canvas1.height = height;// console.log(index, 'height:', height, 'pos', position);var ctx = canvas1.getContext("2d");ctx.drawImage(canvas,0,position,canvas.width,height,0,0,canvas.width,height); // 边距这里设置0,不然又黑边var pageHeight = Math.round((a4Width / canvas.width) * height);// pdf.setPageSize(null, pageHeight)if (position != 0) {pdf.addPage();}// 设置 20px 边距pdf.addImage(canvas1.toDataURL("image/jpeg", 1.0),"JPEG",20,20,a4Width,(a4Width / canvas1.width) * height);leftHeight -= height;position += height;if (leftHeight > 0) {//添加全屏水印// let base64 =// "data:image/png;base64,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";// for (let i = 0; i < 6; i++) {// for (let j = 0; j < 5; j++) {// const left = j * 120 + 20;// pdf.addImage(base64, "JPEG", left, i * 150, 150, 50); // 关掉水印// }// }setTimeout(createImpl, 500, canvas);} else {pdf.save(pdfFileName + ".pdf");}}}//当内容未超过pdf一页显示的范围,无需分页if (leftHeight < a4HeightRef) {pdf.addImage(pageData,"JPEG",20,20,a4Width,(a4Width / canvas.width) * leftHeight);pdf.save(pdfFileName + ".pdf");} else {try {pdf.deletePage(0);setTimeout(createImpl, 500, canvas);} catch (err) {console.log(err);}}});},},mounted() {let canvas = document.createElement("canvas");let ctx = canvas.getContext("2d");ctx.font = "16px Microsoft Yahei";ctx.textAlign = "center";ctx.textBaseline = "middle";ctx.fillStyle = "rgba(0, 0, 0, 1)";ctx.translate(100, 100);ctx.rotate((-20 * Math.PI) / 180);ctx.fillText("活在风浪里", 0, 0);let base64Url = canvas.toDataURL();// 图片层级提到最高document.querySelector("#with-watermark").style.position = "relative";document.querySelector("#with-watermark").style.zIndex = "999";// 背景色document.querySelector("#with-watermark").style.background = "transparent";document.querySelector("#with-watermark").style.backgroundImage ="url(" + base64Url + ")";},
};
</script><style scoped lang="scss">
//带水印直接导出样式
#with-watermark {font-size: 25px;width: 100%;height: 100%;
}
// 导出时加水印
ul {// padding: 20px; // padding也可以留出边距,解决文字显示不全,不过方法设置更好li {min-height: 150px;font-size: 25px;}li:nth-child(odd) {background-color: pink;}li:nth-child(even) {background-color: skyblue;}
}
</style>
生成base64
// 水印 canvas base64 生成图片let canvas = document.createElement("canvas");let ctx = canvas.getContext("2d");ctx.font = "16px Microsoft Yahei";ctx.textAlign = "center";ctx.textBaseline = "middle";ctx.fillStyle = "rgba(0, 0, 0, 0.8)";ctx.translate(100, 100);ctx.rotate((-20 * Math.PI) / 180);ctx.fillText("活在风浪里", 0, 0);const base64 = canvas.toDataURL();console.log('\😂👨🏾❤️👨🏼==>: ', base64);
或者使用这种方法 打印: http://t.csdn.cn/ojrnX
二十九:原生js 滚动楼层
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>楼层滚动</title></head><style>* {padding: 0;margin: 0;}body,html {height: auto;width: 100%;background-image: linear-gradient(to right, #f7f0ac, #acf7f0, #f0acf7);}.nav-wrap {position: fixed;z-index: 99;top: 50%;left: 120px;transform: translateY(-50%);width: 100px;height: 300px;display: flex;flex-direction: column;align-items: center;justify-content: space-between;}.stick {position: sticky;top: 10px;height: 15px;padding: 5px;line-height: 15px;background-color: #5f9ea0;color: #ffff;font-weight: 900;}.nav {width: 100px;flex: 1;margin-bottom: 10px;background: url(https://img1.baidu.com/it/u=105002249,3897918256&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=281)no-repeat center center/cover;display: flex;align-items: center;justify-content: center;font-size: 25px;}.main {text-align: center;line-height: 900px;font-size: 30px;color: #ffff;width: 800px;height: 900px;/* background-color: hsla(0, 0%, 100%, 0.2); */background: rgba(255, 255, 255, 0.4);position: relative;left: 50%;transform: translateX(-50%);}.nav-active {color: red;background: url("https://img1.baidu.com/it/u=833204098,2930185410&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=745")no-repeat center center/cover;}</style><body><div class="nav-wrap"><div class="nav">A</div><div class="nav">B</div><div class="nav">C</div><div class="nav">D</div></div><div class="main">A</div><div class="main"><div class="stick">B</div></div><div class="main"><div class="stick">C</div></div><div class="main"><div class="stick">D</div></div></body><script>const navAll = document.querySelector(".nav-wrap");const mainAll = document.querySelectorAll(".main");function reset() {for (let i = 0; i < navAll.children.length; i++) {navAll.children[i].classList.remove("nav-active");}}// 进入页面时,第一个导航栏高亮navAll.children[0].classList.add("nav-active");navAll.addEventListener("click", (event) => {if (event.target.classList.contains("nav")) {const index = Array.from(navAll.children).indexOf(event.target);console.log(index);window.scrollTo({top: Math.floor(mainAll[index].offsetTop),behavior: "smooth",});}});window.addEventListener("scroll", () => {const scrollTop = document.scrollingElement.scrollTop;let left = 0;let right = navAll.children.length - 1;while (left <= right) {const mid = Math.floor((left + right) / 2);const currentMain = mainAll[mid];if (currentMain.offsetTop <= scrollTop &¤tMain.offsetTop + currentMain.offsetHeight > scrollTop) {reset();navAll.children[mid].classList.add("nav-active");break;} else if (currentMain.offsetTop > scrollTop) {right = mid - 1;} else {left = mid + 1;}}});</script>
</html>
三十 :获取伪元素 修改伪元素
window.getComputedStyle(document.querySelector("#line"), "after")
想要修改伪元素,需要新建类名,在改新建类名的伪元素
<body><div class="demo" > </div></body><script type="text/javascript">var div=document.getElementsByTagName('div')[0];div.οnclick=function() {div.className='demo1';}</script>
.demo{width:100px;height:100px;background-color:red;display:inline-block; } .demo::after{content:"";width:10px;height:10px;display:inline-block;background-color:green; } .demo1{width:100px;height:100px;background-color:red;display:inline-block; } .demo1::after{content:"";width:10px;height:10px;display:inline-block;background-color:orange; }
修改伪元素 源码:
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <style>.div {width: 300px;height: 300px;background-color: red;}.div::after {content: '';display: block;width: 30px;height: 30px;background-color: pink;}.dome {width: 300px;height: 300px;background-color: red;}.dome::after {content: '';display: block;width: 30px;height: 30px;background-color: rgb(9, 212, 53);} </style><body><div class="div">111</div> </body> <script>let div = document.querySelector('.div')div.onclick = function (){div.style.backgroundColor = 'blue'// 无法设置CSSStyleDeclaration的背景颜色属性:这些样式是计算出来的,因此背景颜色属性是只读的// console.log('\😂👨🏾❤️👨🏼==>: ', getComputedStyle(div, '::after').backgroundColor = 'rgb(225, 0, 0)');div.className = 'dome'} </script></html>
三十二:&& 操作符
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body></body>
<script>//前面不是false,就返回会面的值 [重点]var a = 10 && 0; // 返回0a = '字符串' && 0; // 返回0a = '' && 0; //空字符串转换布尔值为false 所以短路 返回空字符串a = undefined && 0; //undefined为false所以短路 返回undefineda = function () { } && 0; //函数转换布尔值为true 所以返回0a = null && 0; //null为false 所以短路 返回nulla = NaN && 0; //NaN为false 所以短路 返回NaNa = 0 && 555; //0为false 所以短路 返回0// console.log('\😂👨🏾❤️👨🏼==>: ', a);// console.log('\😂👨🏾❤️👨🏼==>: ', Boolean(''));//false// console.log('\😂👨🏾❤️👨🏼==>: ', Boolean('老六'));//true// console.log('\😂👨🏾❤️👨🏼==>: ', Boolean(undefined));//false// ----------------------------------------------------------------------- 不判断直接调用,有会成功,没有回报错// 应用场景 调用函数时传入另一个函数作为参数,在调用函数内部调用传入的这个函数,如果这个函数不存在,就不会报错,此时可以用短路运算符来避免这个错误// function fn(f)// {// f()// 报错:f is not a function// }// function fn1()// {// console.log('我是fn1函数的值,一会我会传给fn函数');// }//将函数作为参数传递,函数名就是函数的引用,所以fn1就是函数的引用,// 所以fn1()就是调用函数,fn1和fn1()的区别就是fn1是函数的引用,fn1()是调用函数// fn()// ----------------------------------------------------------------------- 常规if判断function fn2(f){if (f){f()}}function fn3(){console.log('我是fn3函数的值,一会我会传给fn2函数');}fn2()//不会报错,因为fn2函数内部有判断,如果f存在,就调用f函数,如果不存在,就不调用// ------------------------------------------------------------------------- 短路运算符妙用function fn4(f){// console.log('\😂👨🏾❤️👨🏼==>: ', f ); //undefined 转化为布尔值为false,短路不执行f()f && f()}function fn5(){console.log('我是fn5函数的值,一会我会传给fn4函数');}fn4()//不会报错,因为fn4函数内部有短路运算符,如果f为false,就不调用f函数
</script></html>
三十三 :Vue3子组件和父组件都用setup,父组件用ref 获取子组件实例 需要使用defineExpose导出
官网有这么一句话:使用 <script setup> 的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。
因此,父组件是不能直接访问子组件的方法。需要子组件手动的抛出才行。
<script setup>import { defineExpose, ref } from 'vue';const data = ref('');function clear(){data.value = ''}defineExpose({ clear })
</script setup>
看尤大大怎么说:
例子:
子组件
<template><div><h1>我是子组件----{{countChild}}</h1><button @click="handlerBtn">按钮</button></div>
</template>
<script setup>
import { ref, defineExpose } from "vue";
// 定义一个变量,通过defineExpose暴露出去
let countChild = ref(99);
// 通过defineExpose暴露出去,不然父组件拿不到
const handlerBtn = () => {countChild.value += 100;
};
defineExpose({countChild,handlerBtn
});
</script>
父组件
<template><div><h1 @click="handlerChildCount">我是父组件----{{count}}</h1><HelloWorld ref="son" /></div>
</template><script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
import { ref, onMounted } from "vue";
let son = ref(null);
let count = ref(0);
// 通过ref拿到子组件实例,必须在onMounted里面取值,否则可以打印出来,却取不到值(undefined,null)
// 排除法思维找bug,既然可以打印到,说明子组件已经抛出去了,父组件拿不到,那就是父组件的问题,
// setup相当于vue2的created beforeCreate,子组件已经抛出去了,父组件在挂载前取值,所以拿不到
/**beforeCreatevue组件实例对象已经创建完毕但是 data methods里面的内容还没有准备好 不可用 createdata methods 可以用, dom对象不可用*/onMounted(() => {console.log("😂👨🏾❤️👨🏼==>: ", son.value);handlerChildCount(); //页面一打开就执行一次 输出 199 子组件定义的是99 点击按钮+=100
});
let handlerChildCount = () => {son.value.handlerBtn();count.value = son.value.countChild
;
};
</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>
三十四:Number()
和parseInt
有什么区别
Number(string)
函数评估整个字符串并将其转换为数字,如果这个字符串不是一个数字,就返回NaN
。而
parseInt(string, [radix])
会尝试在传递的字符串中找到第一个数字,将其转换为传递的基数,默认为10
,只有在找不到任何数字时才会返回NaN
。这意味着如果你传递一个像
5e2
这样的字符串,那么,parseInt
会在看到e
时停止,因此只返回5
,而Number
则评估整个字符串并返回正确的值500
。
你可以发现这两个函数之间有一些不同:
console.log(Number('a')); // NaN
console.log(Number('1')); // 1
console.log(Number('5e2')); // 500
console.log(Number('16px')); // NaN
console.log(Number('3.2')); // 3.2console.log(parseInt('a')); // NaN
console.log(parseInt('1')); // 1
console.log(parseInt('5e2')); // 5
console.log(parseInt('16px')); // 16
console.log(parseInt('3.2')); // 3
性能:
例如,假设有这样一个简单的函数,循环一亿次,并接受回调,现在让我们调用这个函数两次,第一次使用Number
,第二种使用parseInt
。
三十五:网站变灰色
html,
body,
#app {width: 100%;height: 100%;background-color: #fff;/* 灰度 如果加在*标签这里可能又会一些布局混乱问题哦*/-webkit-filter: grayscale(100%);-moz-filter: grayscale(100%);-ms-filter: grayscale(100%);-o-filter: grayscale(100%);filter: grayscale(100%);filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);}
三十五:网站变暗主题(反转)
var a = document.createElement('style');
a.innerHTML='html{background-color:#fff;filter:invert(1);}img{filter:invert(1)}';
document.head.appendChild(a)
三十六:高斯模糊
filter: blur(20px);
三十七:append和appendChild的三个不同点
const parent = document.createElement('div');
const child = document.createElement('p');
parent.append(child);
// 这会将子元素追加到div元素
// 然后div看起来像这样<div> <p> </ p> </ div>
这会将子元素追加到 div
元素,然后 div
看起来像这样
<div> <p> </ p> </ div>
插入DOMString
const parent = document.createElement('div');
parent.append('附加文本');
然后 div
看起来像这样的
<div>附加文本</ div>
不同点
.append
接受Node对象和DOMString,而 .appendChild
只接受Node对象。
const parent = document.createElement('div');
const child = document.createElement('p');
// 追加节点对象
parent.append(child) // 工作正常
parent.appendChild(child) // 工作正常
// 追加DOMStrings
parent.append('Hello world') // 工作正常
parent.appendChild('Hello world') // 抛出错误
// Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'
.append
没有返回值,而 .appendChild
返回附加的Node对象。
const parent = document.createElement('div');
const child = document.createElement('p');
const appendValue = parent.append(child);
console.log(appendValue) // undefined
const appendChildValue = parent.appendChild(child);
console.log(appendChildValue) // <p><p>
.append
允许您添加多个项目,而 .appendChild
仅允许单个项目。
const parent = document.createElement('div');
const child = document.createElement('p');
const childTwo = document.createElement('p');
parent.append(child, childTwo, 'Hello world'); // 工作正常
parent.appendChild(child, childTwo, 'Hello world');
// 工作正常,但添加第一个元素,而忽略其余元素
三十八:defer和async是什么??区别是什么??
defer
脚本文件会在整个页面都解析完之后再被延迟执行,因此,
<script>
标签设置了defer
属性后浏览器会立即下载,但是会被延迟执行。(即解析完html之后)再执行。
async
与defer
不同的是,标记为async
的脚本浏览器会立即下载并执行,那个脚本先下载完就执行谁。多个async
谁先下载好,谁执行
小结
- defer和async都是异步加载外部JS文件
- 它俩的差别在于脚本下载完之后何时执行,
defer
属性会在html解析之后执行 ,async
则会在下载完就开始执行。 - 多个
defer
会按顺序进行执行,多个async
则不一定,谁先下载完谁先执行。
三十九:关于<Script>标签在html页面放置位置
1.<script></script>标签放置在<head></head>标签内部时:
将script放在<head>里,浏览器解析HTML,发现script标签时,会先下载完所有这些script,再往下解析其他的HTML。讨厌的是浏览器在下载JS时,是不能多个JS并发一起下载的。不管JS是不来来自同一个host,浏览器最多只能同时下载两个JS,且浏览器下载JS时,就阻塞掉解析其他HTML的工作。将script放在头部,会让网页内容呈现滞后,导致用户感觉到卡。2.<script></script>标签放置在<body></body>标签内部时:
将script放在尾部的缺点,是浏览器只能先解析完整个HTML页面,再下载JS。而对于一些高度依赖于JS的网页,就会显得慢了。所以将script放在尾部也不是最优解,最优解是一边解析页面,一边下载JS。(async defer)
3.<script></script>标签放置在</body>标签之后时:
首先声明。这在</body>之后插入其他元素,从HTML 2.0起就是不合标准的。按照HTML5标准中的HTML语法规则,如果在</body>后再出现<script>或任何元素的开始标签,都是 解析错误,浏览器会忽略之前的</body>,即视作仍旧在body内。所以实际效果和写在</body>之前是没有区别的。这种写法虽然也能工作,但是并没有带来任何额外好处,实际上出现这样的写法很可能是误解了“将script放在页面最末端”的教条。所以还是不要这样写为好。
四十:vue3中h函数的常见使用方式
一般情况下每个vue组件都用"<template>"写html, 但实际还可以在js代码中通过render函数生成dom. 最主要常见组件库也需要配合"h"使用.
render
render是组件的一个选项, 他的返回值会被作为组件的DOM结构.
插入字符串 :
<script>
import { defineComponent} from "vue";
export default defineComponent({render(){return '123456789'}
});
</script>
插入html需要配合h 函数:
<script>
import { defineComponent, h } from "vue";
export default defineComponent({render() {const props = { style: { color: "red" } };return h("h2", props, "123456789");},
});
</script>
"h"函数的第1个参数是"标签名", 第2个是"属性", 在这个例子里可以理解为html的所有属性, 第3个是"内容". "内容"不仅仅可以是字符串, 还可以是"VNode"或2者混合:
<script>
import { defineComponent, h } from "vue";
export default defineComponent({render() {const props = { style: { color: "red" }, class: "test" };return h("h2", props, ["父内容",h("span", null, ["h2里面span 子内容1", h("i", null, "h2里面span里面的i 孙内容2")]),]);},
});
</script>
vue3 渲染函数官网 渲染函数 & JSX | Vue.js
四十一:Js基础 类型判断
// 在js中true默认是1 false为0 null代表空对象与undefined相同console.log(true > 0);//true 1>0console.log(true > 1);//false 1>1 console.log(null == undefined);//true null代表空对象与undefined相同console.log(null === undefined);//false 全等会比较类型与值console.log("null" == "undefined");//false 字符串不相同console.log(null);//null console.log(typeof null);//object null代表空对象console.log(typeof undefined);//undefined console.log({}=={});//false 引用数据直接比较为falseconsole.log({}==={});//false 引用数据直接比较为false
console.log(1 == 1);// trueconsole.log(1 == "1");// true 隐式转换与数值类型比较console.log(1 == "1px");//false console.log(1 == true);//true js中true为1 flase为0console.log([]==false)// true []表示false fasle为0console.log([]==0)// true []表示false 0表示fasleconsole.log(100 == false);// falseconsole.log(188 == "188");// true 不比较类型不是===严格等于console.log(false == " ");// true " "空字符串为fasleconsole.log({}==[])// false //空数组不等于空对象console.log({}=={})// false //引用数据类型地址不相等console.log([]=="")// true false==falseconsole.log([]==0)// true false==0console.log([]==[])// false 引用数据类型比较的空间地址console.log(![] == [])// true 先比较在取反 对象先转字符串再转数字,布尔转数字; 数字比数字console.log([]==false) //trueconsole.log([]===false) //falseconsole.log(![]);//fasleconsole.log(![]==![])//trueconsole.log(![]===![]);//trueconsole.log(![]==false);//true ![]typeof检测是boolean boolean==false trueconsole.log({a:1}=="[object Object]")// trueconsole.log("12px" == 12);//false//绝对比较; 只要数据类型不一样(隐式转换之前),那么返回false;console.log(1 === 1);// trueconsole.log(1 === true);// falseconsole.log(1 === "1");// false
四十二:纯前端预览PDF
用了element-plus的button,所以你要先引入element,之后直接copy code 即可
<template><!-- http://viewer.flyfish.group/ --><div><el-button type="success" class="btn">上传PDF 预览效果 </el-button><div class="file"><input type="file" @change="previewFile" accept=".pdf" /></div><embed :src="previewUrl" width="100%" height="790px" v-if="previewUrl" /></div></template>
<script setup>
import { ref } from "vue";
const previewUrl = ref("");
const previewFile = (e) => {const file = e.target.files[0];// 获取文件内容previewUrl.value = URL.createObjectURL(file);const reader = new FileReader();reader.readAsDataURL(file);reader.onload = (e) => {previewUrl.value = e.target.result;};
};
</script>
<style lang="scss" scoped>
iframe {margin: 0 auto;display: block;border: none;
}
.btn {position: relative;width: 150px;height: 25px;
}
.file {position: absolute;top: 15px;left: 13px;input {width: 152px;height: 40px;opacity: 0;}
}
</style>
四十三:纯前端预览PDF、DOC、DOCX、图片、视频
第一步:在要使用的页面,cope 以下代码
<template> <iframesrc="/diat-preview/index.html"frameborder="0"scrolling="no"width="100%"height="800"></iframe>
</template><style lang="scss" scoped>
iframe {margin: 0 auto;display: block;border: none;
}
</style>
第二步:需要吧dist放入你的项目根目录(其他位置也可以,不过需要变动iframe的src)
完活!dist-preview 我以上传资源,在我的主页可以找到 或上我gitee仓库
资源:https://download.csdn.net/download/m0_57904695/87378728
四十四:纯前端 JS脚本 导出excel 可动态添加数据
动态添加:https://download.csdn.net/download/m0_57904695/87378742
gitee:vue3预览excel导入导出: vue3 纯前端实现 预览excel 导入 导出
以下Vue3版本的里面有我封装的表格组件,直接复制不可用,可以参考格式
<template><div class="form-adapt-container size-limit"><el-card shadow="hover" v-loading="loading"><el-button type="primary" class="export-button" @click="tableToExcel('资产服务发现')">导出 Excel</el-button><!-- 子组件 Start --><comm-table-pagination:tableData="state.dataAll.data":tableHeader="tableHeader":total="state.tableData.total":tableHeight="tableHeight":tableWidth="tableWidth"@handleSizeChange="onHandleSizeChange"@handleCurrentChange="onHandleCurrentChange"></comm-table-pagination><!-- 子组件 End --></el-card></div>
</template><script setup lang="ts">
// 引入封装好的table和pagination
import commTablePagination from '/@/components/commTable.vue';
import service from '/@/utils/request';
import { ElMessage } from 'element-plus';
// 定义需要向commTablePagination组件中传入的数据
const tableHeader = reactive([{prop: 'serviceName',label: '资产名称',overHidden: true, // 超出显示省略号 比如某一行字数多,缩小屏幕就省略号显示},{prop: 'serviceType',label: '资产类型',overHidden: true, // 超出显示省略号 比如某一行字数多,缩小屏幕就省略号显示},{prop: 'serviceIp',label: 'IP地址',},{prop: 'servicePort',label: '端口',},
]);
const tableHeight = ref('calc(100vh - 260px)'); // 表格高度
const tableWidth = ref('180'); // 表格宽度// 定义导出excel需要的数据与表头
const excelData = ref();
const excelDataHeader = ref();
const state = reactive({form: {occupation: '',},tableData: {total: 0,loading: false,param: {pageNum: 1,pageSize: 10,},paperName: '',},value1: '',value2: '',dataAll: {data: [],},id: '',
});
onMounted(() => {ElMessage.success('正在加载请稍等');getData();
});
// 创建axios停止函数实例
const loading = ref(true);
//分页改变
const onHandleCurrentChange = (val: number) => {state.tableData.param.pageNum = val;getData();
};
//分页改变
const onHandleSizeChange = (val: number) => {state.tableData.param.pageSize = val;getData();
};
// 获取列表数据
const getData = () => {service.get('/ast/astServiceDiscovery/servicesList', {params: {current: state.tableData.param.pageNum,size: state.tableData.param.pageSize,},}).then((res) => {if (res.code == 0) {loading.value = false;}state.dataAll.data = res.data.records;state.tableData.total = res.data.total;state.tableData.param.pageSize = res.data.size;state.tableData.param.pageNum = res.data.current;});
};
const tableToExcel = () => {// 要导出的json数据const jsonData = state.dataAll.data.map(({ serviceName, serviceType, serviceIp, servicePort }) => ({serviceName,serviceType,serviceIp,servicePort,}));// 列标题let str = '<tr><td>资产名称</td><td>资产类型</td><td>IP地址</td><td>端口</td></tr>';// 循环遍历,每行加入tr标签,每个单元格加td标签for (let i = 0; i < jsonData.length; i++) {str += '<tr>';for (const key in jsonData[i]) {// 增加\t为了不让表格显示科学计数法或者其他格式str += `<td>${jsonData[i][key] + '\t'}</td>`;}str += '</tr>';}// Worksheet名const worksheet = 'Sheet1';const uri = 'data:application/vnd.ms-excel;base64,';// 下载的表格模板数据const template = `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>${worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>${str}</table></body></html>`;// 下载模板window.location.href = uri + base64(template);
};// 输出base64编码
const base64 = (s) => window.btoa(unescape(encodeURIComponent(s)));
</script>
<style lang="scss" scoped>
::v-deep(.el-pagination) {padding: 20px 0 0 !important;
}.export-button {z-index: 10;margin-left: 93%;// position: fixed;// right: 20px;// top: 110px;
}
</style>
参考链接:js 实现纯前端将数据导出excel两种方式,亲测有效_qqtang1406722832的博客-CSDN博客
四十五:动态红包雨
<template><div><!-- 0-4s 随机持续时间 实现有的快有的红包持续时间慢--><divclass="envelope-rain":style="{animationDuration: Math.random() * 2 + 3 + 's',left: Math.random() * 95 + 'vw',}"v-for="n in 15":key="n":data-n="n"></div></div>
</template>
<script setup>
document.addEventListener("click", (e) => {const target = e.target;if (target.classList.contains("envelope-rain")) {target.style.background = "url(/open红包.png) no-repeat center center/80% 100%";// 获取红包金额后,红包停止下落target.style.animationPlayState = "paused";// 1:随机红包金额// target.innerHTML = "¥" + Math.floor(Math.random() * 50);// 2:获取自定义属性以循环的值作为红包金额// console.log(target.dataset.n);target.innerHTML = `<span>${target.dataset.n}</span>`;// 给span设置宽高阴影 并添加动画target.querySelector("span").style.cssText = `width: 100%;height: 100%;display: flex;justify-content: center;margin-top: 40px;font-size: 2.5rem;font-weight: bold;font-family: "楷体";font-style: italic;color: yellow;text-shadow: 8px 8px 10px red;animation: flipInY 1s;`;}
});
</script>
<style lang="scss" scoped>
.envelope-rain {position: fixed;top: 0;width: 100px;height: 100px;background: url("/红包.webp") no-repeat center center/100%;// animation: 起的名字,持续时间,运动曲线 ,延迟,播放次数,是否反方向,结束状态;animation: move linear infinite;&:hover {animation-play-state: paused;}// 红包下落动画@keyframes move {0% {transform: translateY(0);}10% {transform: translateY(10vh);}20% {transform: translateY(20vh);}30% {transform: translateY(30vh);}40% {transform: translateY(40vh);}50% {transform: translateY(50vh);}60% {transform: translateY(60vh);}70% {transform: translateY(70vh);}80% {transform: translateY(80vh);}90% {transform: translateY(90vh);}100% {transform: translateY(100vh);}}// 红包点击弹出金额动画@-webkit-keyframes flipInY {from {-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg);transform: perspective(400px) rotate3d(0, 1, 0, 90deg);-webkit-animation-timing-function: ease-in;animation-timing-function: ease-in;opacity: 0;}40% {-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg);transform: perspective(400px) rotate3d(0, 1, 0, -20deg) scale(1.1);-webkit-animation-timing-function: ease-in;animation-timing-function: ease-in;}60% {-webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg);transform: perspective(400px) rotate3d(0, 1, 0, 10deg) scale(1.2);opacity: 1;}80% {-webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg);transform: perspective(400px) rotate3d(0, 1, 0, -5deg) scale(1.5);}to {-webkit-transform: perspective(400px);transform: perspective(400px);}
}
}
</style>
四十六:Vue3 刮刮乐
<template><!-- 刮刮乐 vue3 --><canvas class="canBg" ref="canRef" width="1125" height="640"></canvas></template><script setup>
import { ref, onMounted } from 'vue';
// 获取canvas元素
const canRef = ref(null);
// 是否按下鼠标
let isPress = false;
onMounted(() => {const canvas = canRef.value;// 获取canvas上下文const ctx = canvas.getContext('2d');// 创建图片对象const img = new Image();img.src = 'https://m.360buyimg.com/babel/jfs/t1/223927/25/16690/210358/63c11558F18bf5cd4/df2deecda76f8a2b.jpg.webp';// 图片加载完成后,绘制到canvas上img.onload = () => {ctx.drawImage(img, 0, 0, canvas.width, canvas.height);};//鼠标按下isPress为truecanvas.addEventListener('mousedown',function () {isPress = true;/* 鼠标样式 */canvas.style.cursor = 'move';},false);// 鼠标移动时,如果isPress为true,清除当前位置的矩形区域canvas.addEventListener('mousemove',function (e) {if (isPress) {const size = 50;ctx.clearRect(e.offsetX, e.offsetY, size, size);}},false);// 鼠标抬起时,isPress为falsecanvas.addEventListener('mouseup',function () {isPress = false;canvas.style.cursor = 'default';},false);
});
</script>
<style lang="scss" scoped>
.canBg {background: url(https://img-blog.csdnimg.cn/20210330153605959.png) no-repeat center center/cover;
}
</style>
四十七: 表格动态编辑Vue2、Vue3
在线运行
<template><!-- 可编辑表格V2 --><div id="hello"><!-- 表格 --><p class="tips">单击 右键菜单,单击 左键编辑</p><el-table:data="tableData"height="500px"borderstyle="width: 100%; margin-top: 10px"@cell-click="cellDblclick"@header-contextmenu="(column, event) => rightClick(null, column, event)"@row-contextmenu="rightClick":row-class-name="tableRowClassName"><el-table-columnv-if="columnList.length > 0"type="index":label="'No.'"/><el-table-columnv-for="(col, idx) in columnList":key="col.prop":prop="col.prop":label="col.label":index="idx"/></el-table><div><h3 style="text-align: center">实时数据展示</h3><label>当前目标:</label><p>{{ JSON.stringify(curTarget) }}</p><label>表头:</label><p v-for="col in columnList" :key="col.prop">{{ JSON.stringify(col) }}</p><label>数据:</label><p v-for="(data, idx) in tableData" :key="idx">{{ JSON.stringify(data) }}</p></div><!-- 右键菜单框 --><div v-show="showMenu" id="contextmenu" @mouseleave="showMenu = false"><p style="margin-bottom: 10px">列:</p><el-button size="mini" type="primary" @click="addColumn()">前方插入一列</el-button><el-button size="mini" type="primary" @click="addColumn(true)">后方插入一列</el-button><el-buttontype="primary"size="mini"@click="openColumnOrRowSpringFrame('列')">删除当前列</el-button><el-button size="mini" type="primary" @click="renameCol($event)">更改列名</el-button><div class="line"></div><p style="margin-bottom: 12px">行:</p><el-buttonsize="mini"type="primary"@click="addRow()"v-show="!curTarget.isHead">上方插入一行</el-button><el-buttonsize="mini"type="primary"@click="addRow(true)"v-show="!curTarget.isHead">下方插入一行</el-button><el-buttonsize="mini"type="primary"@click="addRowHead(true)"v-show="curTarget.isHead">下方插入一行</el-button><el-buttontype="primary"size="mini"@click="openColumnOrRowSpringFrame('行')"v-show="!curTarget.isHead">删除当前行</el-button></div><!-- 单元格/表头内容编辑框 --><div v-show="showEditInput" id="editInput"><el-inputv-focusplaceholder="请输入内容"v-model="curTarget.val"clearable@change="updTbCellOrHeader"@blur="showEditInput = false"@keyup="onKeyUp($event)"><template #prepend>{{ curColumn.label || curColumn.prop }}</template></el-input></div></div>
</template><script>
export default {data() {return {columnList: [{ prop: "name", label: "姓名" },{ prop: "age", label: "年龄" },{ prop: "city", label: "城市" },{ prop: "tel", label: "电话" }],tableData: [{ name: "张三", age: 24, city: "广州", tel: "13312345678" },{ name: "李四", age: 25, city: "九江", tel: "18899998888" }],showMenu: false, // 显示右键菜单showEditInput: false, // 显示单元格/表头内容编辑输入框curTarget: {// 当前目标信息rowIdx: null, // 行下标colIdx: null, // 列下标val: null, // 单元格内容/列名isHead: undefined // 当前目标是表头?},countCol: 0 // 新建列计数};},computed: {curColumn() {return this.columnList[this.curTarget.colIdx] || {};}},methods: {// 删除当前列或当前行openColumnOrRowSpringFrame(type) {this.$confirm(`此操作将永久删除该 ${type === "列" ? "列" : "行"}, 是否继续 ?, '提示'`,{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning"}).then(() => {if (type === "列") {this.delColumn();} else if (type === "行") {this.delRow();}this.$message({type: "success",message: "删除成功!"});}).catch(() => {this.$message({type: "info",message: "已取消删除"});});},// 回车键关闭编辑框onKeyUp(e) {if (e.keyCode === 13) {this.showEditInput = false;}},// 单元格双击事件 - 更改单元格数值cellDblclick(row, column, cell, event) {this.showEditInput = false;if (column.index == null) return;this.locateMenuOrEditInput("editInput", 200, event); // 编辑框定位this.showEditInput = true;// 当前目标this.curTarget = {rowIdx: row.row_index,colIdx: column.index,val: row[column.property],isHead: false};},// 单元格/表头右击事件 - 打开菜单rightClick(row, column, event) {// 阻止浏览器自带的右键菜单弹出event.preventDefault(); // window.event.returnValue = falsethis.showMenu = false;if (column.index == null) return;this.locateMenuOrEditInput("contextmenu", 140, event); // 菜单定位this.showMenu = true;// 当前目标this.curTarget = {rowIdx: row ? row.row_index : null, // 目标行下标,表头无 row_indexcolIdx: column.index, // 目标项下标val: row ? row[column.property] : column.label, // 目标值,表头记录名称isHead: !row};},// 去更改列名renameCol($event) {this.showEditInput = false;if (this.curTarget.colIdx === null) return;this.locateMenuOrEditInput("editInput", 200, $event); // 编辑框定位this.showEditInput = true;},// 更改单元格内容/列名updTbCellOrHeader(val) {if (!this.curTarget.isHead)// 更改单元格内容this.tableData[this.curTarget.rowIdx][this.curColumn.prop] = val;else {// 更改列名if (!val) return;this.columnList[this.curTarget.colIdx].label = val;}},// 新增行addRow(later) {this.showMenu = false;const idx = later ? this.curTarget.rowIdx + 1 : this.curTarget.rowIdx;let obj = {};this.columnList.forEach((p) => (obj[p.prop] = ""));this.tableData.splice(idx, 0, obj);},// 表头下新增行addRowHead() {// 关闭菜单this.showMenu = false;// 新增行let obj = {};// 初始化行数据this.columnList.forEach((p) => (obj[p.prop] = ""));// 插入行数据this.tableData.unshift(obj);},// 删除行delRow() {this.showMenu = false;this.curTarget.rowIdx !== null &&this.tableData.splice(this.curTarget.rowIdx, 1);},// 新增列addColumn(later) {this.showMenu = false;const idx = later ? this.curTarget.colIdx + 1 : this.curTarget.colIdx;const colStr = { prop: "col_" + ++this.countCol, label: "" };this.columnList.splice(idx, 0, colStr);this.tableData.forEach((p) => (p[colStr.prop] = ""));},// 删除列delColumn() {this.showMenu = false;this.tableData.forEach((p) => {delete p[this.curColumn.prop];});this.columnList.splice(this.curTarget.colIdx, 1);},// 添加表格行下标tableRowClassName({ row, rowIndex }) {row.row_index = rowIndex;},// 定位菜单/编辑框locateMenuOrEditInput(eleId, eleWidth, event) {let ele = document.getElementById(eleId);ele.style.top = event.clientY - 100 + "px";ele.style.left = event.clientX - 50 + "px";if (window.innerWidth - eleWidth < event.clientX) {ele.style.left = "unset";ele.style.right = 0;}}}
};
</script><style lang="scss" scoped>
#hello {position: relative;height: 100%;width: 100%;
}
.tips {margin-top: 10px;color: #999;
}
#contextmenu {position: absolute;top: 0;left: 0;height: auto;width: 120px;border-radius: 3px;box-shadow: 0 0 10px 10px #e4e7ed;background-color: #fff;border-radius: 6px;padding: 15px 0 10px 15px;button {display: block;margin: 0 0 5px;}
}
.hideContextMenu {position: absolute;top: -4px;right: -5px;
}
#editInput,
#headereditInput {position: absolute;top: 0;left: 0;height: auto;min-width: 200px;max-width: 400px;padding: 0;
}
#editInput .el-input,
#headereditInput .el-input {outline: 0;border: 1px solid #c0f2f9;border-radius: 5px;box-shadow: 0px 0px 10px 0px #c0f2f9;
}
.line {width: 100%;border: 1px solid #e4e7ed;margin: 10px 0;
}
</style>
<template><!-- 可编辑表格V3 --><div id="hello"><!-- 表格 --><p class="tips">单击 右键菜单,单击 左键编辑</p><el-table:data="tableData"height="500px"stripeborderstyle="width: 100%; margin-top: 10px"@cell-click="cellDblclick"@header-contextmenu="(column, event) => rightClick(null, column, event)"@row-contextmenu="rightClick":row-class-name="tableRowClassName"><el-table-columnwidth="50"v-if="columnList.length > 0"type="index":label="'No.'"/><el-table-columnv-for="(col, idx) in columnList":key="col.prop":prop="col.prop":label="col.label":index="idx"/></el-table><div><h3 style="text-align: center">实时数据展示</h3><label>当前目标:</label><p>{{ JSON.stringify(curTarget) }}</p><label>表头:</label><p v-for="col in columnList" :key="col.prop">{{ JSON.stringify(col) }}</p><label>数据:</label><p v-for="(data, idx) in tableData" :key="idx">{{ JSON.stringify(data) }}</p></div><!-- 右键菜单框 --><div v-show="showMenu" id="contextmenu" @mouseleave="showMenu = false"><p style="margin-bottom: 10px">列:</p><el-button:icon="CaretTop"size="small"type="success"@click="addColumn()">前方插入一列</el-button><el-button:icon="CaretBottom"size="small"type="success"@click="addColumn(true)">后方插入一列</el-button><el-button:icon="DeleteFilled"type="success"size="small"@click="openColumnOrRowSpringFrame('列')">删除当前列</el-button><el-buttonsize="small"type="success"@click="renameCol($event)":icon="EditPen">更改列名</el-button><div style="color: #ccc">--------------</div><p style="margin-bottom: 12px">行:</p><el-button:icon="CaretTop"size="small"type="success"@click="addRow()"v-show="!curTarget.isHead">上方插入一行</el-button><el-button:icon="CaretBottom"size="small"type="success"@click="addRow(true)"v-show="!curTarget.isHead">下方插入一行</el-button><el-button:icon="DeleteFilled"size="small"type="success"@click="addRowHead(true)"v-show="curTarget.isHead">下方插入一行</el-button><el-buttontype="success":icon="DeleteFilled"size="small"@click="openColumnOrRowSpringFrame('行')"v-show="!curTarget.isHead">删除当前行</el-button></div><!-- 单元格/表头内容编辑框 --><div v-show="showEditInput" id="editInput"><el-inputref="iptRef"placeholder="请输入内容"v-model="curTarget.val"clearable@change="updTbCellOrHeader"@blur="showEditInput = false"@keyup="onKeyUp($event)"><template #prepend>{{ curColumn.label || curColumn.prop }}</template></el-input></div></div>
</template><script setup>
import { ref, reactive, computed, toRefs } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {DeleteFilled,EditPen,CaretBottom,CaretTop,
} from "@element-plus/icons-vue";const state = reactive({columnList: [{ prop: "name", label: "姓名" },{ prop: "age", label: "年龄" },{ prop: "city", label: "城市" },{ prop: "tel", label: "电话" },],tableData: [{ name: "张三", age: 24, city: "广州", tel: "13312345678" },{ name: "李四", age: 25, city: "九江", tel: "18899998888" },],showMenu: false, // 显示右键菜单showEditInput: false, // 显示单元格/表头内容编辑输入框curTarget: {// 当前目标信息rowIdx: null, // 行下标colIdx: null, // 列下标val: null, // 单元格内容/列名isHead: undefined, // 当前目标是表头?},countCol: 0, // 新建列计数
});
const iptRef = ref(null);
const { columnList, tableData, showMenu, showEditInput, curTarget, countCol } =toRefs(state);
const curColumn = computed(() => {return columnList.value[curTarget.value.colIdx] || {};
});// 删除当前列或当前行
const openColumnOrRowSpringFrame = (type) => {ElMessageBox.confirm(`此操作将永久删除该 ${type === "列" ? "列" : "行"}, 是否继续 ?, '提示'`,{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {if (type === "列") {delColumn();} else if (type === "行") {delRow();}ElMessage({type: "success",message: "删除成功!",});}).catch(() => {ElMessage({type: "info",message: "已取消删除",});});
};// 回车键关闭编辑框
const onKeyUp = (e) => {if (e.key === "Enter") {showEditInput.value = false;}
};// 单元格双击事件 - 更改单元格数值
const cellDblclick = (row, column, cell, event) => {showEditInput.value = false;if (column.index == null) return;locateMenuOrEditInput("editInput", 200, event); // 编辑框定位showEditInput.value = true;iptRef.value.focus();// 当前目标curTarget.value = {rowIdx: row.row_index,colIdx: column.index,val: row[column.property],isHead: false,};
};// 单元格/表头右击事件 - 打开菜单
const rightClick = (row, column, event) => {// 阻止浏览器自带的右键菜单弹出event.preventDefault(); // window.event.returnValue = falseshowMenu.value = false;if (column.index == null) return;locateMenuOrEditInput("contextmenu", 140, event); // 菜单定位showMenu.value = true;// 当前目标curTarget.value = {rowIdx: row ? row.row_index : null, // 目标行下标,表头无 row_indexcolIdx: column.index, // 目标项下标val: row ? row[column.property] : column.label, // 目标值,表头记录名称isHead: !row,};
};// 更改列名
const renameCol = ($event) => {showEditInput.value = false;if (curTarget.value.colIdx === null) return;locateMenuOrEditInput("editInput", 200, $event); // 编辑框定位showEditInput.value = true;iptRef.value.focus();
};// 更改单元格内容/列名
const updTbCellOrHeader = (val) => {if (!curTarget.value.isHead)// 更改单元格内容tableData.value[curTarget.value.rowIdx][curColumn.value.prop] = val;else {// 更改列名if (!val) return;columnList.value[curTarget.value.colIdx].label = val;}
};// 新增行
const addRow = (later) => {showMenu.value = false;const idx = later ? curTarget.value.rowIdx + 1 : curTarget.value.rowIdx;let obj = {};columnList.value.forEach((p) => (obj[p.prop] = ""));tableData.value.splice(idx, 0, obj);
};// 表头下新增行
const addRowHead = () => {// 关闭菜单showMenu.value = false;// 新增行let obj = {};// 初始化行数据columnList.value.forEach((p) => (obj[p.prop] = ""));// 插入行数据tableData.value.unshift(obj);
};// 删除行
const delRow = () => {showMenu.value = false;curTarget.value.rowIdx !== null &&tableData.value.splice(curTarget.value.rowIdx, 1);
};// 新增列
const addColumn = (later) => {showMenu.value = false;const idx = later ? curTarget.value.colIdx + 1 : curTarget.value.colIdx;const colStr = { prop: "col_" + ++countCol.value, label: "" };columnList.value.splice(idx, 0, colStr);tableData.value.forEach((p) => (p[colStr.prop] = ""));
};// 删除列
const delColumn = () => {showMenu.value = false;tableData.value.forEach((p) => {delete p[curColumn.value.prop];});columnList.value.splice(curTarget.value.colIdx, 1);
};// 添加表格行下标
const tableRowClassName = ({ row, rowIndex }) => {row.row_index = rowIndex;
};// 定位菜单/编辑框
const locateMenuOrEditInput = (eleId, eleWidth, event) => {let ele = document.getElementById(eleId);ele.style.top = event.clientY - 30 + "px";ele.style.left = event.clientX - 100 + "px";if (window.innerWidth - eleWidth < event.clientX) {ele.style.left = "unset";ele.style.right = 0;}
};
</script><style lang="scss" scoped>
#hello {position: relative;height: 100%;width: 100%;
}.tips {margin-top: 10px;color: #999;font-style: italic;font-size: 13px;
}#contextmenu {position: absolute;z-index: 999;top: 0;left: 0;height: auto;width: 136px;border-radius: 3px;filter: drop-shadow(-1px 0 20px rgba(0, 0, 0, 0.2));background-color: #fff;border-radius: 6px;padding: 15px 10px 14px 12px;button {display: block;margin: 0 0 5px;}:deep(el-button) {color: red;}
}.hideContextMenu {position: absolute;top: -40px;right: -5px;
}#editInput,
#headereditInput {position: absolute;z-index: 999;top: 0;left: 0;height: auto;min-width: 200px;max-width: 400px;padding: 0;
}
</style>
四十八:脱敏 正则
service.get('/privacy/customerUserInfo/list', {params: {current: state.tableData.param.pageNum,size: state.tableData.param.pageSize,userName: state.tableData.userName,},}).then((res: any) => {// 深拷贝原始数据,这样缺点是会丢失函数和正则和undefined和symbolstate.originalData = JSON.parse(JSON.stringify(res.data.records));// 随机生成****或***const random = (str: string, $1: string, $2: string) => {return $1 + '*'.repeat(str.length - 2) + $2;};// 手机号、邮箱、姓名、住址脱敏并去掉时间的时分秒res.data.records.map((item: any) => {// 手机号脱敏item.phoneNumber = item.phoneNumber?.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');// 住址脱敏item.address = item.address?.replace(/(\d)/g, '*', '$1**$2');// 邮箱脱敏并根据字符串长度生成不同长度的*号item.email = item.email?.replace(/(\w{1})\w{1,}(\w{1})/, random);// 姓名脱敏从第二个字符开始,替换成*号,包头不包尾,也就是只替换第二个字符,不替换第一个字符和最后一个字符item.userName = item.userName?.replace(item.userName.substring(1, 2), '*');//生日时间 substring(0,2)这个包头不包尾,因此截取是截取两个字符,从第一个到第二个字符,不包含第三个。item.birthday = item.birthday?.substring(0, 10);});
四十九:Javascript模块导入导出
/*** @param date 设置获取的日期* @returns days 日期数组* @description 此函数可以动态获取除今天外任何日期,使用方法在调用函数时传入获取的天数* @author zk*/
// 除今天7天前的日期
const myGetDays = (date: number) => {const days = [];const now = new Date();const year = now.getFullYear();const month = now.getMonth() + 1;const day = now.getDate();for (let i = 0; i < date; i++) {const d = new Date(year, month - 1, day - i - 1);days.push(d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate());}return days;
};
/***1 导出单个变量 export var site = "www.helloworld.net"; 使用:import { site } from "/.a.js"2 导出多个变量 var siteUrl="www.helloworld.net"; var siteName="helloworld开发者社区";export { siteUrl ,siteName } ; 使用:import { siteUrl ,siteName } from "/.a.js"3 导出函数 function sum(a, b) { return a + b; };export { sum }; 使用:import { sum } from "/.a.js"4 导出对象 export var obj = { name: "helloworld", url: "www.helloworld.net" }; 使用:import { obj } from "/.a.js"4.1 导出对象 var obj = { name: "helloworld", url: "www.helloworld.net" };export default obj; 使用:import obj from "/.a.js"5 导出类 export class Hello { constructor() { } } 使用:import { Hello } from "/.a.js"6 导出默认值 export default { name: "helloworld", url: "www.helloworld.net" }; 使用:import obj from "/.a.js" //导出的变量可以用任意变量名接收总结:1:export default 只能导出一次,export 可以导出多次,2:export default 可以导出任何类型,export 只能导出变量、函数、类,3:export default 导出的变量可以用任意变量名接收,export 导出的变量只能用 { } 中对应的变量名接收4:export default 导出的变量可以是匿名函数,export 导出的变量必须有函数名
}*/
export { myGetDays };
在页面使用:
<template>{{ myGetDays(7) }}</template><script setup lang="ts">
import {myGetDays} from '/@/utils/myGetDate';
</script>
参考文章:彻底弄懂Javascript模块导入导出_码云笔记的博客-CSDN博客_js 模块化导出
五十: 轮询与长轮询
长轮询与短轮询
说到长轮询,肯定存在和它相对立的,我们暂且叫它短轮询(定时轮询)吧,我们简单介绍一下短轮询
短轮询也是拉模式。是指不管服务端数据有无更新,客户端每隔定长时间请求拉取一次
数据,可能有更新数据返回,也可能什么都没有。如果配置中心使用这样的方式,会存在
以下问题:
1:由于配置数据并不会频繁变更,若是一直发请求,势必会对服务端造成很大压力,还会
造成推送数据的延迟,比如:每10s请求一次配置,如果在第11s时配置更新了,那么推送
将会延迟95,等待下一次请求!2:如果用户少可以临时使用这种方式,如果大量用户同时在次页面,大量的定时轮询会使服务器压力过大
长轮询为了解决短轮询存在的问题,客户端发起长轮询,如果服务端的数据没有发生变
更,会hold住请求,直到服务端的数据发生变化,或者等待一定时间超时才会返回。返
回后,客户瑞再发起下一次长轮询请求监听。
这样设计的好处:
1:相对于低延时,客户端发起长轮询,服务端感知到数据发生变更后,能立刻返回响
应给客户端。2:服务瑞的压力减小,客户瑞发起长轮询,如果数据没有发生变更,服务端会hol住
此次客户端的请求,hold住请求的时间一骰会设置到30s或者605,并且服务端
hold住请求不会消耗太多服务端的资源。注意会占用线程
长轮询例子:
async function subscribe() {let response = await fetch("/subscribe");if (response.status == 502) {// 状态 502 是连接超时错误,// 连接挂起时间过长时可能会发生,// 远程服务器或代理会关闭它// 让我们重新连接await subscribe();} else if (response.status != 200) {// 一个 error —— 让我们显示它showMessage(response.statusText);// 一秒后重新连接await new Promise(resolve => setTimeout(resolve, 1000));await subscribe();} else {// 获取并显示消息let message = await response.text();showMessage(message);// 再次调用 subscribe() 以获取下一条消息await subscribe();}
}subscribe();
五十一:交叉观察器 懒加载 new IntersectionObserver(callback, options)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>交叉观察器懒加载</title><style style="display:block;white-space: pre;background:orange;color:purple" contenteditable>.item {height: 100px;box-sizing: border-box;border-bottom: 1px solid red;background: orange;width: 300px;text-align: center;line-height: 100px;margin: 0 auto;}body {padding: 0;margin: 0;}footer {width: 300px;margin: 0 auto;height: 50px;margin: 0 auto;background: aqua;text-align: center;line-height: 50px;}</style></head><body><div id="container"></div><footer id="footerEl"> 加载中。。。 </footer><script>// 交叉观察器// 语法:// new IntersectionObserver(callback, options)// 参数:// callback:必填,表示当目标元素与视口发生交叉时,触发的回调函数,回调函数接收一个数组作为参数,数组中的每个元素都是一个 IntersectionObserverEntry 对象,表示目标元素与视口的交叉状态。// options:选填,表示配置对象,用于配置交叉观察器的行为,包含以下属性:// root:可选,表示根元素,即用来检测目标元素与视口的交叉状态的元素,如果不指定,则默认为浏览器的视口。// rootMargin:可选,表示根元素的边界,用来扩大或缩小根元素的范围,可以指定上下左右四个方向的边界,单位为像素,可以是负值。// threshold:可选,表示交叉比例,用来指定目标元素的可见比例,当目标元素的可见比例达到指定的值时,就会触发回调函数。该属性可以是一个数值,也可以是一个数组,如果是数组,则表示多个交叉比例。// 交叉观察器的实例对象有一个 observe 方法,用来观察目标元素,该方法接受一个 DOM 元素作为参数,表示要观察的目标元素。// 交叉观察器的实例对象有一个 unobserve 方法,用来停止观察目标元素,该方法接受一个 DOM 元素作为参数,表示要停止观察的目标元素。// 交叉观察器的实例对象有一个 disconnect 方法,用来停止观察所有目标元素。const footerEl = document.getElementById('footerEl');const observer = new IntersectionObserver((val) =>{//包含了目标元素与视口的交叉状态// console.log( '😂👨🏾❤️👨🏼==>:', val);setTimeout(addItem, 300);});// 观察目标元素(footer 先new IntersectionObserver)observer.observe(footerEl);const container = document.getElementById('container');let num = 0;function addItem(){const fragment = document.createDocumentFragment();// 每次添加十条for (let i = num; i < num + 10; i++){let node = document.createElement("div");node.innerText = `第${i + 1}列`node.className = 'item'// container.appendChild(node)// 不直接追加到container,优化性能先追加到文档片段里fragment.appendChild(node)}container.appendChild(fragment)// num每次循环递增10,让数字链接排号num = num + 10;}</script></body></html>
模拟真实场景:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>交叉观察器懒加载</title><style style="display:block;white-space: pre;background:orange;color:purple" contenteditable>.item {height: 100px;box-sizing: border-box;border-bottom: 1px solid red;background: orange;width: 300px;text-align: center;line-height: 100px;margin: 0 auto;}body {padding: 0;margin: 0;}footer {width: 300px;margin: 0 auto;height: 50px;margin: 0 auto;background: aqua;text-align: center;line-height: 50px;}</style></head><body><div id="container"></div><footer id="footerEl"> 疯狂加载中。。。 </footer><script>const footerEl = document.getElementById('footerEl');const container = document.getElementById('container');const observer = new IntersectionObserver(() => setTimeout(addItem, 300));observer.observe(footerEl);// 假数据let data = [{ name: "name1" },{ name: "name2" },{ name: "name3" },{ name: "name4" },{ name: "name5" },{ name: "name6" },{ name: "name7" },{ name: "name8" },{ name: "name9" },{ name: "name10" },{ name: "name11" },{ name: "name12" },{ name: "name13" },{ name: "name14" },{ name: "name15" },{ name: "name16" },{ name: "name17" },{ name: "name18" },{ name: "name19" },{ name: "name20" },{ name: "name21" },{ name: "name22" },{ name: "name23" },{ name: "name24" },{ name: "name25" },{ name: "name26" },{ name: "name27" },{ name: "name28" },{ name: "name29" },{ name: "name30" },{ name: "name31" },{ name: "name32" },{ name: "name33" },{ name: "name34" },{ name: "name35" },{ name: "name36" },{ name: "name37" },{ name: "name38" },{ name: "name39" },{ name: "name40" },{ name: "name41" },{ name: "name42" },{ name: "name43" },{ name: "name44" },{ name: "name45" },{ name: "name46" },{ name: "name47" },{ name: "name48" },{ name: "name49" },{ name: "name50" },{ name: "name51" },{ name: "name52" },{ name: "name53" },{ name: "name54" },{ name: "name55" },{ name: "name56" },{ name: "name57" },{ name: "name58" },{ name: "name59" },{ name: "name60" },{ name: "name61" },{ name: "name62" },{ name: "name63" },{ name: "name64" },{ name: "name65" },{ name: "name66" },{ name: "name67" },{ name: "name68" },{ name: "name69" },{ name: "name70" },{ name: "name71" },{ name: "name72" },{ name: "name73" },{ name: "name74" },{ name: "name75" },{ name: "name76" },{ name: "name77" },{ name: "name78" },{ name: "name79" },{ name: "name80" },{ name: "name81" },{ name: "name82" },];// 递增的数字let num = 0;function addItem(){if (num >= data.length){observer.unobserve(footerEl);footerEl.innerHTML = "没有更多数据了";return;}let Fragment = document.createDocumentFragment();// 每次添加10条数据for (let i = 0; i < 10; i++){const item = document.createElement('div');item.className = 'item';item.innerHTML = data[num].name;Fragment.appendChild(item);num++;}container.appendChild(Fragment);}</script></body></html>
元素观察器 对元素大小进行监听:new ResizeObserver
这是一个能针对某个元素实行大小、位置变化监听的API,是一个类,它提供了一个观察器,该观察器将在每个 resize 事件上调用,目前chrome、safari、fireFox(pc)基本支持。
方法:
ResizeObserver.disconnect()
取消和结束目标对象上所有对 Element 或 SVGElement 观察。
ResizeObserver.observe()
开始观察指定的 Element 或 SVGElement。
ResizeObserver.unobserve(target)
结束观察指定的Element 或 SVGElement。
const resizeOb= new ResizeObserver(entries => {for(const entry of entries) {console.log(entry)}
})
resizeOb.observe(document.getElementById("test"))//打印结果
//{target: div#test, contentRect: DOMRectReadOnly}
例子:
五十二:搜索关键字 下拉框高亮展示
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>搜索关键字下拉框高亮展示</title></head><style type="text/css">.fonthighlight {color: red;font-weight: 600;font-size: 16px;}input {height: 25px;width: 300px;padding: 0;margin: 0;}button {padding: 0;margin: 0;height: 30px;width: 80px;background-color: orange;border: none;}button:hover {color: red;background-color: #00aaff;}ul {padding-left: 5px;margin-top: 5px;width: 300px;background-color: #efefef;border: #F0F0F0 solid 2px;border-radius: 0.2rem;display: none;}ul li {list-style-type: none;text-align: left;padding: 0;margin-top: 5px;}ul li:hover {background-color: #c7c7c7;cursor: pointer}</style><body><div><input placeholder="请输入搜索关键字..." type="text" name="" id="searchResult" value="" /><button type="button" onclick="onSearch()">搜索</button><ul id="associate"></ul></div><script>// 解决动态生成元素无法绑定事件// @param {Object} type 事件类型// @param {Object} fun 回调函数Element.prototype.on = function(type, fun) {window.addEventListener ? this.addEventListener(type, fun) : this.attachEvent('on' + type, fun);}let globalSearchKey = ''let associate = document.querySelectorAll("#associate")[0];function bindEvent(associateChildNodes, event) {for (let i = 0; i < associateChildNodes.length; i++) {associateChildNodes[i].on(event, function() {let matchNods = this.childNodes;if (matchNods && matchNods.length > 0) {for (let i = 0; i < matchNods.length; i++) {globalSearchKey += matchNods[i].innerHTML;}}console.log("选项被点击:", this.childNodes);document.getElementById("searchResult").value = globalSearchKey.trim();globalSearchKey = '';console.log("globalSearchKey", globalSearchKey)// associate.style.display = 'none';associate.style.visibility = 'hidden';});}}// 想法:把包含搜索关键字的位置分四种情况考虑:// 1.没有找到匹配到搜索关键字,直接返回原字符串// 2.搜索关键字在头部// 3.搜索关键字在尾部// 4.搜索关键字在中间// 搜索关键字高亮// @param {Object} source 原字符串[搜索结果]// @param {Object} target 子字符串[搜索关键字]function highlightText(source, target) {if (!source || !target) {return '';} else {let indexPosition = source.indexOf(target)if (indexPosition != -1) {let sourceLength = source.length;let prefix = source.substring(0, indexPosition);let suffixIndex = (prefix ? prefix.length : 0) + (target ? target.length : 0);let suffix = source.substring(suffixIndex, sourceLength);if (indexPosition == 0) {return `<span class="fonthighlight target">${target}</span><span class="suffix">${suffix}</span>`;} else if (indexPosition + target.length == source.length) {return `<span class="prefix">${prefix}</span><span class="fonthighlight target">${target}</span>`;} else {return `<span>${prefix}</span><span class="fonthighlight target">${target}</span><span>${suffix}</span>`;}} else {return `<span>${source}<span/>`;}}}// 联想数据let shading = ['你真好看','你真帅','你太美丽了','你长到姐的审美标准上了','我想你了','可是....我真的好想你啊'];function onSearch() {let currentSearchKey = document.getElementById("searchResult").value;if (!currentSearchKey) {alert("搜索关键字不能为空!")}alert("当前搜索关键字:" + currentSearchKey);// associate.style.display = 'none';associate.style.visibility = 'hidden';}let dom = document.getElementById("searchResult");// 输入框值改变匹配关键字高亮[底纹数据可换成联想数据]dom.oninput = (event) => {if (!event.target.value) {associate.innerHTML = '<li>暂无匹配数据!</li>';return;}let matchHtml = '';shading.forEach((item, index, slef) => {let matchResultText = highlightText(item, event.target.value);matchHtml += (`<li>` + matchResultText + "</li>");});associate.innerHTML = matchHtml;// 重新渲染一定要重新绑定事件let associateChildNodes = associate.childNodes;bindEvent(associateChildNodes, 'click');}// 输入获得焦点[获取底纹数据]dom.onfocus = (event) => {hint();}// 输入失去焦点dom.onblur = (event) => {console.log("失去焦点")}// 获得焦点是提示的底纹function hint() {let associateHtml = '';shading.forEach((item, index, slef) => {associateHtml += `<li ><span >${item} </span></li>`;});associate.innerHTML = associateHtml;associate.style.display = 'block';let associateChildNodes = associate.childNodes;associate.style.visibility = 'visible';// 绑定事件 bindEvent(associateChildNodes, 'click');}</script></body></html>
五十三:搜索关键字页面高亮显示 (不支持火狐、苹果)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>搜索关键字高亮显示</title><style>/* ::highlight(search-results) {background-color: red;color: white;} *//* 波浪线 */::highlight(search-results) {text-decoration: underline wavy #f06;}</style>
</head><body><label>搜索 <input id="query" type="text"></label><article><p>阅文旗下囊括 QQ 阅读、起点中文网、新丽传媒等业界知名品牌,汇聚了强大的创作者阵营、丰富的作品储备,覆盖 200多种内容品类,触达数亿用户,已成功输出《庆余年》《赘婿》《鬼吹灯》《全职高手》《斗罗大陆》《琅琊榜》等大量优秀网文 IP,改编为动漫、影视、游戏等多业态产品。</p><p>《盗墓笔记》最初连载于起点中文网,是南派三叔成名代表作。2015年网剧开播首日点击破亿,开启了盗墓文学 IP 年。电影于2016年上映,由井柏然、鹿晗、马思纯等主演,累计票房10亿元。</p><p>庆余年》是阅文集团白金作家猫腻的作品,自2007年在起点中文网连载,持续保持历史类收藏榜前五位。改编剧集成为2019年现象级作品,播出期间登上微博热搜百余次,腾讯视频、爱奇艺双平台总播放量突破160亿次,并荣获第26届白玉兰奖最佳编剧(改编)、最佳男配角两项大奖。</p><p>《鬼吹灯》是天下霸唱创作的经典悬疑盗墓小说,连载于起点中文网。先后进行过漫画、游戏、电影、网络电视剧的改编,均取得不俗的成绩,是当之无愧的超级IP。</p></article><script>const query = document.getElementById("query");const article = document.querySelector("article");// 创建 createTreeWalker 迭代器,用于遍历文本节点,保存到一个数组const treeWalker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);const allTextNodes = [];let currentNode = treeWalker.nextNode();while (currentNode){allTextNodes.push(currentNode);currentNode = treeWalker.nextNode();}// 监听inpu事件query.addEventListener("input", () =>{// 判断一下是否支持 CSS.highlightsif (!CSS.highlights){article.textContent = "CSS Custom Highlight API not supported.";return;}// 清除上个高亮CSS.highlights.clear();// 为空判断const str = query.value.trim().toLowerCase();if (!str){return;}// 查找所有文本节点是否包含搜索词const ranges = allTextNodes.map((el) =>{return { el, text: el.textContent.toLowerCase() };}).map(({ text, el }) =>{const indices = [];let startPos = 0;while (startPos < text.length){const index = text.indexOf(str, startPos);if (index === -1) break;indices.push(index);startPos = index + str.length;}// 根据搜索词的位置创建选区return indices.map((index) =>{const range = new Range();range.setStart(el, index);range.setEnd(el, index + str.length);return range;});});// 创建高亮对象const searchResultsHighlight = new Highlight(...ranges.flat());// 注册高亮CSS.highlights.set("search-results", searchResultsHighlight);});</script>
</body></html>
五十三:关键字防抖搜索高亮
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>搜索关键字展示到页面</title><style>a {background-color: yellow;}li {list-style: none;margin: 5px 0;}</style>
</head><body><input class="inp" type="text"><section><ul class="container"></ul></section>
</body>
<script>function debounce(fn, timeout = 300){//防抖let t; //设置一个接收标记,接受定时器返回值return (...args) =>{//argsfn的插入参 ,即最下面的输入事件参数e,以数组的形式返回if (t){clearTimeout(t);}t = setTimeout(() =>{fn.apply(fn, args);//执行(e) => {} 改变指针传入参数e}, timeout);}}function memorize(fn){//缓存const cache = new Map();//创建一个Map对象进行缓存return (name) =>{//name为fn插入参 即value 输入值为空,清空containerif (!name){container.innerHTML = '';return;}if (cache.get(name)){//如果map对象中有输入的valu 直接插入containercontainer.innerHTML = cache.get(name);return;}const res = fn.call(fn, name).join('');//以上两种情况都不是的话 fn执行后返回handleInput的Search //并进行渲染 join方法把数组转成了字符串并清除了","cache.set(name, res);console.log(name)console.log(cache)console.log(res)container.innerHTML = res;}}function handleInput(value){const reg = new RegExp(value);//三个标志:全局模式g,不区分大小写模式i,多行模式m 即第二个参数 //这里只匹配第一个结果 let noSearchVal = document.querySelector('.noSearchVal');const search = data.reduce((res, cur) =>{if (reg.test(cur)){res.push(`<li>${cur.replace(reg, '<a scr="www.baidu.com">$&</a>')}</li>`);//亮高关键字显示,如果再Vue项目里 结合v-html}return res;}, []);//第二个参数[] 默认第一开始遍历时res为数组return search;}const data = ["第一段", "第二招", "第三式", "我大抵是病了,想到要三连,头就隐隐作痛,这痛没来由的,我抓起手机一看,这个up还没有关注,300*1400的平板手机像素上显示着点赞,收藏,加关注这几个词,我横竖看不清,仔细看了几分钟,才从博主的《这一手git你玩的冒烟》,这篇博文里看出字来,满眼的星星都……", "详细,好!不用翻阅很多文档就可以在博文读取大半,但是更多的人还是只为需求,无暇他顾仔细阅读,(如果想读详细一点的官方文档是否更合理详细),此博文会以一种几尽透明的方式《分享方式方法》,一句话:“详情中的精炼精简版尽在博文中”!感谢关注!我很喜欢参加各种富有挑战和考验智慧的事情,令人充满激情和创新,也可以更好的锻炼自己,我对自己充满信心,无论是做过的事,还是对于一件新鲜的事情,我都会努力去做,并且尽自己的能力将他做好"]// 真实场景下,这个数据是从后端获取的,数组里面的每一段都会被插入li标签中渲染到页面上// q:注释代码应该写在当前代码上面还是下面const container = document.querySelector('.container');const memorizeInput = memorize(handleInput);document.querySelector('.inp').addEventListener('input', debounce(e =>{memorizeInput(e.target.value);}))
</script></html>
跳转链接高亮
<template><div class="card content-box"><span class="text">防抖指令 🍇🍇🍇🍓🍓🍓</span><!-- <el-button type="primary" v-debounce="debounceClick">防抖按钮 (0.5秒后执行)</el-button> --><br /><el-input v-debounce="debounceInput" v-model.trim="iptVal" placeholder="防抖输入框 (0.5秒后执行)" style="width: 200px" /><section><ul v-if="flag"><a v-for="(item, index) in listArr" :key="index" :href="item.link" class="link" v-cope="666"><li v-html="item.uname"></li></a></ul></section></div>
</template><script lang="ts" setup>
import { onMounted } from 'vue';
// 进入页面时,自动聚焦到搜索框
onMounted(() => {// @ts-ignoredocument.querySelector('.el-input__inner').focus();
});
// import { ElMessage } from 'element-plus';
// const debounceClick = () => {
// ElMessage.success('我是防抖按钮触发的事件 🍍🍓🍌');
// };
// 双向绑定的搜索默认值
let iptVal = ref<string>('');
// 被搜索的列表,真实项目中应该是从后台获取的数据
let listArr: Array<{ uname: string; link: string }> = reactive([{uname: 'Vue项目实战 —— 后台管理系统( pc端 ) —— Pro最终版本',link: 'https://blog.csdn.net/m0_57904695/article/details/129730440?spm=1001.2014.3001.5501',},{uname: '【提高代码可读性】—— 手握多个代码优化技巧、细数哪些惊艳一时的策略',link: 'https://blog.csdn.net/m0_57904695/article/details/128318224?spm=1001.2014.3001.5502',},{uname: '开源项目 —— 原生JS实现斗地主游戏 ——代码极少、功能都有、直接粘贴即用',link: 'https://blog.csdn.net/m0_57904695/article/details/128982118?spm=1001.2014.3001.5501',},{ uname: 'Vue3项目 —— Vite / Webpack 批量注册组件', link: 'https://blog.csdn.net/m0_57904695/article/details/128919255?spm=1001.2014.3001.5501' },{uname: 'Vue3 项目实战 —— 后台管理系统( pc端 ) —— 动态多级导航菜单顶部侧边联动',link: 'https://blog.csdn.net/m0_57904695/article/details/128740216?spm=1001.2014.3001.5501',},
]);
let flag = ref<boolean>(false);
const debounceInput = () => {// 初始化 恢复高亮flag.value = false;// 被搜索的列表,真实项目中应该是从后台获取的数据listArr = reactive([{uname: 'Vue项目实战 —— 后台管理系统( pc端 ) —— Pro最终版本',link: 'https://blog.csdn.net/m0_57904695/article/details/129730440?spm=1001.2014.3001.5501',},{uname: '【提高代码可读性】—— 手握多个代码优化技巧、细数哪些惊艳一时的策略',link: 'https://blog.csdn.net/m0_57904695/article/details/128318224?spm=1001.2014.3001.5502',},{uname: '开源项目 —— 原生JS实现斗地主游戏 ——代码极少、功能都有、直接粘贴即用',link: 'https://blog.csdn.net/m0_57904695/article/details/128982118?spm=1001.2014.3001.5501',},{ uname: 'Vue3项目 —— Vite / Webpack 批量注册组件', link: 'https://blog.csdn.net/m0_57904695/article/details/128919255?spm=1001.2014.3001.5501' },{uname: 'Vue3 项目实战 —— 后台管理系统( pc端 ) —— 动态多级导航菜单顶部侧边联动',link: 'https://blog.csdn.net/m0_57904695/article/details/128740216?spm=1001.2014.3001.5501',},]);// console.log('!这里输出 🚀 ==>:', iptVal.value.split(''));let searchVal = iptVal.value.split('');if (iptVal.value == '') return;// 输入框的值转为数组方便循环,在循环得到搜索框的每一项,与列表中的每一项进行匹配,如果匹配到,就替换标签,高亮展示searchVal.forEach((searchValItem: string) => onReplace(searchValItem));
};
// 高亮替换标签函数
function onReplace(searchValItem: string) {// 循环列表 { @listArrItem } 列表的每一项listArr.forEach((listArrItem) => {// 如果搜索框的值不在列表中,直接终止返回if (listArrItem.uname.indexOf(searchValItem) == -1) return;// 替换的标签样式let hightStr = `<em style='color: #333333;font-weight: bold;font-style: normal;background-image: url(https://t8.baidu.com/it/u=1501552470,2690656309&fm=167&app=3000&f=PNG&fmt=auto&q=100&size=f624_21);background-repeat: repeat-x;background-position-y: bottom;background-size: 100% 8px;'>${searchValItem}</em>`;//错误写法,假如已经有高亮em标签了,在根据输入框的值匹配,会把已有的高亮标签也替换掉,导致乱码页面卡死 【重要】// let reg = new RegExp(searchValItem, 'gi');// 不匹配已有<em></em> 高亮标签的内容 【重要】,如果是let reg = new RegExp(`(?![^<]*>|[^<>]*<\/em>)${searchValItem}`, 'gi');listArrItem.uname = listArrItem.uname.replace(reg, hightStr);flag.value = true;});
}
</script><style lang="scss" scoped>
// a.link 这是一个交集选择器,即同时满足span和.highth的元素
a.link {// 去掉默认色color: #333333;// 去掉下划线text-decoration: none;// 鼠标移入时的样式&:hover {color: #4a8cd6;text-decoration: none;}
}
</style>
五十四:Vue3文字合成语音
utils
/**
* @method speechSynthesis
* @param 'text需要合成语音的文字'
* @description 封装文字转语音
* @author zk
* @createDate 2023/03/05 21:12:24
* @lastFixDate 2023/03/05 21:12:24
* @use : 1. import { speechSynthesis } from '@/utils/speechSynthesis';
* 2. speechSynthesis('你好');
*/
export function speechSynthesis(text: string)
{// 判断浏览器是否支持,不支持直接终止并提示if (!window.speechSynthesis){alert('浏览器不支持语音合成');return;}// 停止当前正在播放的语音在创建新的语音实例window.speechSynthesis.cancel();// 创建一个新的语音实例const speech = new SpeechSynthesisUtterance();// 设置语音内容speech.text = text;// 设置语音的语言speech.lang = 'zh-CN';// 设置语音的音量speech.volume = 1;// 设置语音的速度speech.rate = 1.3;// 设置语音的音调speech.pitch = 0.8;// 开始播放window.speechSynthesis.speak(speech);}
页面调用
<template><div><el-button @click="speechSynthesis('警告, 数据安全告警提醒,Warning, data security alarm reminder')">文字合成语音</el-button><el-button @click="stopPlay">停止播放</el-button><el-button @click="suspendPlay">暂停播放</el-button><el-button @click="continuePlay">继续播放</el-button></div>
</template><script setup lang="ts">
import { speechSynthesis } from '/@/utils/speechSynthesis';
// onMounted(() => {
// speechSynthesis('打开页面就会播放');
// });// 页面卸载时,停止播放
onUnmounted(() => {window.speechSynthesis.cancel();
});
// 停止播放
const stopPlay = () => {window.speechSynthesis.cancel();
};
// 暂停播放
const suspendPlay = () => {window.speechSynthesis.pause();
};
// 继续播放
const continuePlay = () => {window.speechSynthesis.resume();
};
</script><style lang="scss" scoped></style>
五十五:content arrt()
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>content的attr</title><style>div[data-line]:after {content: "-" attr(data-line) "-";display: inline-block;background-color: pink;border-radius: 5px;text-align: center;color: #fff;text-align: center;margin-left: 30px;}</style>
</head><body><div data-line="100">点击加加</div><script>let dv = document.querySelector('div[data-line]');console.log(dv.dataset.line);dv.addEventListener('click', function (){dv.dataset.line++;console.log(dv.dataset.line);})</script>
</body></html>
五十六:经典面试题:让 a == 1 && a == 2 && a == 3 成立
// 让代码成立 1let a = {i: 1,toString(){return a.i++}}console.log('!这里输出 🚀 ==>:', a);//{i: 1, toString: ƒ}if (a == 1 && a == 2 && a == 3)//代码从左执行执行一次a+1{console.log('你怎么让我成立1?')}
// 让代码成立2let a = [1, 2, 3];a.toString = a.shift// 修改a的this,指向对象原型toString方法console.log('!这里输出 🚀 ==>:', Object.prototype.toString.call(...a));//[object Number]console.log('!这里输出 🚀 ==>:', a === 1);//falseconsole.log('!这里输出 🚀 ==>:', a == 1);//trueif (a == 1 && a == 2 && a == 3){console.log('你怎么让我成立2?')}
// 让代码成立3// q:++a和a++的区别// a:++a是先加后赋值,a++是先赋值后加Object.defineProperties(window, {_a: {value: 0,writable: true},a: {get: function (){return ++_a}}})// ===的话是先判断类型,再判断值。这里的toString已经默认把对象转化为字符串了.使用toStirng的话,结果就不成立了.console.log('!这里输出 🚀 ==>:', a);//数字类型if (a === 1 && a === 2 && a === 3){console.log('你怎么让我成立3?')}
五十七:清除所有定时器
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app" onclick="stop()">清除所有定时器</div><script>// 定义第一个let timer = setInterval(function () {console.log("timer");}, 1000);// 定义第二个let timer1 = setInterval(function () {console.log("timer");}, 1000);//定义没有返回值setInterval(function () {console.log("timer");}, 1000);clearInterval(timer1);clearInterval(timer);/* 第一个定时器我们有用一个变量timer来保存,所以可以直接把timer给clearInterval传进去就清除掉了但是第三个定时器我们没有保存它,clearInterval的时候不知道要传什么? */clearInterval(/* ??? */); //没有定时器返回值怎么清除?不知道有多少个定时器怎么清除?// 点击按钮清除所有定时器function stop() {/* setInterval的返回值是一个代表定时器的数值型,而且这个数值还是按照定时器创建的先后顺序从1开始递增的 ,所以当我们需要清除所有定时器的时候,只需要在创建一个定时器,就可以得到定时器的最后的返回值,然后我们使用循环来清除。结束条件为最后创建的定时器返回序列号*/let timers = setInterval(() => {}, 1000);console.log("!这里输出 🚀 ==>:", timers); //页面定时器个数// 清除所有定时器for (let i = 1; i < timers; i++) {clearInterval(i);}}</script></body>
</html>
推荐清除定时器博文:js清除所有定时器(包括未知定时器)_60rzvvbj的博客-CSDN博客
五十八:Vue3 使用ref获取动态DOM
<template><div><div v-for="item in list" :ref="setItemRef" :key="item" :data-item="item">{{ item }}</div><el-button @click="getRefData">获取全部DOM</el-button><el-button @click="getRefData1">获取内容为'第三行数据'的DOM</el-button></div>
</template><script lang="ts" setup>
import { ref, reactive } from 'vue';
//定义存放DOM数组
const refList = ref<HTMLElement[]>([]);
//定义数据
const list = reactive(['第一行数据', '第二行数据', '第三行数据', '第四行数据']);
//赋值ref
const setItemRef: any = (el: HTMLElement) => {if (el) {refList.value.push(el);}
};//获取ref并执行接下来操作
const getRefData = () => {for (let i = 0; i < refList.value.length; i++) {console.log(refList.value[i]);}
};//获取某个ref
const getRefData1 = () => {for (let i = 0; i < refList.value.length; i++) {if (refList.value[i].dataset.item === '第三行数据') {console.log(refList.value[i]);}}
};
</script>
五十九:复选框 全选半选 相同value 联动
<template><div class="tabsData"><!-- Tab 按钮 --><div class="tab-buttons"><buttonv-for="(tab, index) in tabsData":key="index":class="{ active: activeTab === index, 'tab-button': true }"@click="switchTab(index, tab)">{{ tab.title }}</button></div><!-- Tab 内容 --><div class="tab-content"><div v-for="(tab, index) in tabsData" :key="index" :class="{ 'tab-pane': true, active: activeTab === index }"><!-- 全选按钮 --><div><label><input type="checkbox" v-model="tab.selectAllVm" @change="onSelectAll(tab)" />全选</label></div><!-- 复选框列表 --><div v-for="(option, optionIndex) in tab.options" :key="optionIndex"><label><input type="checkbox" v-model="option.checked" @change="onOptionChange(tab, option)" />{{ option.label }}</label></div></div></div></div>
</template><script lang="ts" setup>
import { reactive, ref } from 'vue';
// 定义interface
interface ckOption {label: string;value: string;checked: boolean;
}
interface ckType {title: string;options: ckOption[];selectAllVm: boolean;
}
// 定义 Tabs 数据
const tabsData = reactive<ckType[]>([{// Tab 标题title: 'Tab 1',// Tab 内容 value相同才会联动options: [{ label: 'Option A', value: 'a', checked: false },{ label: 'Option B', value: 'b', checked: false },{ label: 'Option C', value: 'c', checked: false },{ label: 'Option D', value: 'd', checked: false },{ label: 'Option E', value: 'e', checked: false },{ label: 'Option F', value: 'f', checked: false },],// 全选按钮的v-model值selectAllVm: false,},{title: 'Tab 2',options: [{ label: 'Option B', value: 'b', checked: false },{ label: 'Option C', value: 'c', checked: false },{ label: 'Option E', value: 'e', checked: false },{ label: 'Option F', value: 'f', checked: false },],selectAllVm: false,},{title: 'Tab 3',options: [{ label: 'Option A', value: 'a', checked: false },{ label: 'Option B', value: 'b', checked: false },{ label: 'Option C', value: 'c', checked: false },{ label: 'Option D', value: 'd', checked: false },{ label: 'Option E', value: 'e', checked: false },],selectAllVm: false,},
]);
// 定义当前激活的 Tab 索引
const activeTab = ref(0);// 切换 Tab
function switchTab(index: number, tab: any) {activeTab.value = index;onCk(tab);
}
// ck与全选联动
function onCk(tab: ckType) {const checkedOptions = tab.options.filter((o: any) => o.checked);// ck被选中的长度等于tab的长度,全选if (checkedOptions.length === tab.options.length) {// 当前tab的全选按钮选中tab.selectAllVm = true;} else {// 当前tab的全选按钮取消选中tab.selectAllVm = false;}
}// 选中或取消某个选项
function onOptionChange(tab: ckType, option: ckOption) {// console.log('!当前tab option 🚀 ==>:', tab);onCk(tab);// 如果不同的 Tab 中有相同的选项,则将它们联动for (let i = 0; i < tabsData.length; i++) {if (i !== activeTab.value) {const otherTab = tabsData[i];// 如果在其他tab找到则联动const otherOption = otherTab.options.find((o) => o.value === option.value);if (otherOption) {otherOption.checked = option.checked;}}}
}// 全选或取消全选
function onSelectAll(tab: ckType) {for (let i = 0; i < tab.options.length; i++) {// 全选赋值所有cktab.options[i].checked = tab.selectAllVm;}// 如果不同的 Tab 中有相同的选项,则将它们联动for (let i = 0; i < tabsData.length; i++) {// 非当前tabif (i !== activeTab.value) {const otherTab = tabsData[i];for (let j = 0; j < otherTab.options.length; j++) {const otherOption = otherTab.options[j];const option = tab.options.find((o) => o.value === otherOption.value);if (option) otherOption.checked = option.checked;}}}// 如果是第一个tab1的全选,则将tab2和tab3的全选也选中if (activeTab.value === 0) {tabsData[1].selectAllVm = tab.selectAllVm;tabsData[2].selectAllVm = tab.selectAllVm;}
}
</script><style lang="scss" scoped>
.tabsData {display: flex;flex-direction: column;border: 1px solid #ccc;width: 400px;
}
.tab-buttons {display: flex;
}
.tab-button {border: none;background-color: #f1f1f1;padding: 10px;flex: 1;cursor: pointer;
}
.tab-button.active {background-color: #ccc;
}
.tab-content {padding: 10px;
}
.tab-pane {display: none;
}
.tab-pane.active {display: block;
}
input[type='checkbox'] {margin-right: 5px;
}
label {display: inline-block;margin-right: 10px;
}
</style>
六十 :移动端上下左右滑动
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>移动端上下左右滑动</title><style>* {margin: 0;padding: 0;}#myElement {width: 100vw;height: 100vh;background-color: transparent;}</style></head><body><div id="myElement"></div><script>let myElement = document.getElementById("myElement");// 监听 touchstart 事件,获取触摸起点的坐标let startX, startY;myElement.addEventListener("touchstart", function (e) {e.preventDefault(); // 阻止默认行为,避免屏幕滚动startX = e.touches[0].pageX;startY = e.touches[0].pageY;});// 监听 touchend 事件,获取触摸终点的坐标并计算偏移量let endX, endY, calcX, calcY;myElement.addEventListener("touchend", function (e) {// console.log(e)endX = e.changedTouches[0].pageX;endY = e.changedTouches[0].pageY;calcX = endX - startX; // 计算水平方向的滑动距离calcY = endY - startY; // 计算垂直方向的滑动距离let threshold = 50; // 滑动距离的阈值(单位为像素)// 首先判断是否真的在滑动if (calcX === 0 && calcY === 0) {return; // 如果没有滑动,就直接返回,不执行后面的逻辑}// 判断滑动方向并执行相应操作if (Math.abs(calcX) > Math.abs(calcY)) {// 左右滑动if (Math.abs(calcX) > threshold) {// 水平方向的滑动距离超过阈值才执行操作if (calcX > 0) {alert("右滑");} else {alert("左滑");}}} else {// 上下滑动if (Math.abs(calcY) > threshold) {// 垂直方向的滑动距离超过阈值才执行操作if (calcY > 0) {alert("下滑");} else {alert("上滑");}}}});</script></body>
</html>
六十一: css捕获滚动子元素( scroll-snap-type: y mandatory;)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><style>* {margin: 0;padding: 0;}.container {height: 100vh;overflow-y: auto;scroll-snap-type: y mandatory;}.box {height: 100vh;scroll-snap-align: start;}</style></head><body><!-- 这是一个HTML文件,其中包含一个高度固定为100vh的容器div,它包含四个子盒子。容器具有overflow-y属性设置为auto以实现滚动,并且scroll-snap-type设置为y mandatory以在滚动时对每个盒子进行捕捉。每个子盒子的高度也是固定的100vh,并且scroll-snap-align被设置为start以在滚动时进行对齐。--><div class="container"><div class="box">Box 1</div><div class="box">Box 2</div><div class="box">Box 3</div><div class="box">Box 4</div></div></body>
</html>
六十二、滚动柱状图
<template><div id="chat"></div>
</template><script setup lang="ts">
import * as echarts from "echarts";
import { onBeforeMount, onMounted } from "vue";
let timer: NodeJS.Timeout;
let option = {tooltip: {type: "showTip",trigger: "axis",axisPointer: {type: "shadow",axis: "y",},},legend: {top: "5%",left: "center",textStyle: {color: "#fff", //自定义文字字体颜色fontSize: 12,},},grid: {left: "5%",top: "25%",right: "5%",bottom: "0%",containLabel: true,},xAxis: {show: false,},yAxis: {type: "category",data: ["海鸥市民贷","宅抵e贷","银税e贷","商超e贷","天行用呗","海鸥市民贷","宅抵e贷","银税e贷","商超e贷","天行用呗",],axisTick: {//刻度线show: false,},axisLine: {lineStyle: {width: 0,},},axisLabel: {fontSize: 15,color: "#fff",},// verticalAlign: "bottom",},color: ["#ff0000", "#ffff00", "#0070c0", "#98c0fd", "#26c847"],series: [{name: "5级",type: "bar",barWidth: 16,barCategoryGap: "5%",barGap: "10%",stack: "total",emphasis: { focus: "series" },itemStyle: { borderWidth: 0.5, barBorderRadius: [15, 0, 15, 0] },data: [5, 6, 7, 8, 9, 10, 1, 2, 3, 4],},{name: "4级",type: "bar",stack: "total",barWidth: 10,barCategoryGap: "5%",barGap: "10%",emphasis: { focus: "series" },itemStyle: { borderWidth: 0.5, barBorderRadius: [15, 0, 15, 0] },data: [4, 5, 1, 2, 1, 2, 3, 3, 4, 10],},{name: "3级",type: "bar",stack: "total",barWidth: 10,barCategoryGap: "5%",barGap: "10%",emphasis: { focus: "series" },itemStyle: { borderWidth: 0.5, barBorderRadius: [15, 0, 15, 0] },data: [5, 6, 7, 8, 1, 2, 3, 4, 9, 10],},{name: "2级",type: "bar",stack: "total",barWidth: 10,barCategoryGap: "5%",barGap: "10%",emphasis: { focus: "series" },itemStyle: { borderWidth: 0.5, barBorderRadius: [15, 0, 15, 0] },data: [1, 2, 7, 8, 9, 3, 4, 5, 6, 10],},{name: "1级",type: "bar",stack: "total",barWidth: 10,barCategoryGap: "5%",barGap: "10%",emphasis: { focus: "series" },itemStyle: { borderWidth: 0.5, barBorderRadius: [15, 0, 15, 0] },data: [5, 6, 7, 8, 1, 2, 3, 4, 9, 10],},],dataZoom: [{show: false, // 是否显示滑动条,yAxisIndex: 0, // 这里是从y轴的0刻度开始startValue: 0, // 数据窗口范围的起始数值endValue: 4, // 数据窗口范围的结束数值(一次性展示几个,不算滑动的)},],
};
onMounted(() => {let myChart = echarts.init(document.getElementById("chat") as HTMLDivElement);// 每一秒第一个柱子的变为最后一个柱子,实现柱子的滚动,并且柱子滚动的时候,加上渲染的动画timer = setInterval(() => {let data = option.series[0].data;let data1 = option.series[1].data;let data2 = option.series[2].data;let data3 = option.series[3].data;let data4 = option.series[4].data;let data5 = option.yAxis.data;data.push(data.shift() as number);data1.push(data1.shift() as number);data2.push(data2.shift() as number);data3.push(data3.shift() as number);data4.push(data4.shift() as number);data5.push(data5.shift() as string);myChart.setOption(option);}, 4000);// 监听页面的大小window.addEventListener("resize", () => {myChart.resize();});
});onBeforeMount(() => {window.removeEventListener("resize", () => {});clearInterval(timer);
});
</script><style scoped lang="scss">
#chat {width: 100%;height: calc(100% - 47px);
}
</style>
<template><div id="chat"></div>
</template><script setup lang="ts">
import * as echarts from "echarts";
import { onBeforeMount, onMounted } from "vue";
let timer: NodeJS.Timeout;
const seriesList = [120, 200, 150, 80, 70, 110, 130, 120, 200, 150, 120, 200];
const xAxisList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
const dataZoomEndValue = 6; // 数据窗口范围的结束数值(一次性展示几个)const option = {xAxis: {type: "category",data: xAxisList,},yAxis: {type: "value",},dataZoom: [{show: false, // 是否显示滑动条xAxisIndex: 0, // 这里是从X轴的0刻度开始startValue: 0, // 数据窗口范围的起始数值endValue: dataZoomEndValue, // 数据窗口范围的结束数值(一次性展示几个)},],series: [{type: "bar",showBackground: true,backgroundStyle: {color: "rgba(180, 180, 180, 0.2)",},data: seriesList,},],
};onMounted(() => {let myChart = echarts.init(document.getElementById("chat") as HTMLDivElement);if (xAxisList.length > 0 && seriesList.length > 0) {timer = setInterval(function () {// 每次向后滚动一个,最后一个从头开始if (option.dataZoom[0].endValue === xAxisList.length) {option.dataZoom[0].startValue = 0; // 数据窗口范围的起始数值option.dataZoom[0].endValue = dataZoomEndValue; // 数据窗口范围的结束数值} else {option.dataZoom[0].startValue = option.dataZoom[0].startValue + 1; // 数据窗口范围的起始数值option.dataZoom[0].endValue = option.dataZoom[0].endValue + 1; // 数据窗口范围的结束数值}myChart.setOption(option);}, 2000);}option && myChart.setOption(option);// 监听页面的大小window.addEventListener("resize", () => {myChart.resize();});
});onBeforeMount(() => {window.removeEventListener("resize", () => {});clearInterval(timer);
});
</script><style scoped lang="scss">
#chat {width: 100%;height: calc(100% - 47px);
}
</style>
折线图-心电图效果
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Echarts实现心电图效果</title><script src="https://cdn.bootcss.com/echarts/3.7.1/echarts.js"></script><style>* {margin: 0;padding: 0;}#chat {margin: 0 auto;height: 300px;width: 80vw;transition: all 10s ease-in-out;}</style>
</head><body><!-- 添加鼠标移入停止,移出继续事件 --><div id="chat" onmouseover="stopChart()" onmouseout="continueChart()"></div><script type="text/javascript">// 初始化 Echarts 实例let myChatDom = echarts.init(document.getElementById('chat'));// 初始化 X 轴和 Y 轴的数据let xAxisData = [];let yAxisData = [];// 获取当前日期let today = new Date();// 定义计时器let timer;for (let i = 6; i >= 0; i--) {// 获取当前日期前七天的日期 i第一次循环是6 那么 today - (7 - 6), today - 1 今天减一是前一天的日期 // 在循环中 一直减到 今天前七天的日期并将(24时)装换为毫秒let date = new Date(today - (7 - i) * 24 * 3600 * 1000);// console.log(date); // 今天外前七天的日期xAxisData.push(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate());yAxisData.push(Math.round(Math.random() * 500));}// 配置 Echarts 图表let option = {animation: false,title: {text: '总流量(kbps)'},tooltip: {trigger: 'axis',axisPointer: {type: 'cross'}},grid: {left: 50,right: 15},legend: {data: ['当前流量']},xAxis: {boundaryGap: false,// 倾斜 axisLabel: {interval: 0,rotate: 25},data: xAxisData},yAxis: {},series: {symbol: "none",name: '当前流量',type: 'line',itemStyle: {lineStyle: {width: 2,color: "#FF0000"}},data: yAxisData}};// 将配置项设置到 Echarts 实例中myChatDom.setOption(option);// 鼠标移入停止function stopChart() {clearInterval(timer);}// 鼠标移出继续function continueChart() {handlerInterval()}// 滚动函数function handlerInterval() {// 每秒将第一个数据放在最后timer = setInterval(function () {xAxisData.push(xAxisData.shift());// 因为上面的yAxisData就是随机数 所以这里不用随机数了,直接掐头入尾yAxisData.push(yAxisData.shift());myChatDom.setOption({xAxis: {data: xAxisData},// 变化后重新加载数据series: [{data: yAxisData,// animation: true, // 开启动画// animationDuration: 1000 // 2秒钟完成动画}]});}, 1000);}handlerInterval();</script>
</body></html>
分享快乐,留住感动. '2023-6-14 22:00:22' -- 0.活在风浪里!