JavaScript 《公司开发功能》 99+ 大合集

 ​前期回顾    ​  

Vue项目实战 —— 后台管理系统( pc端 ) 第三篇_0.活在风浪里的博客-CSDN博客mock模拟数据、折线图、柱状图、饼图,一遍就懂!!~https://blog.csdn.net/m0_57904695/article/details/124861409?spm=1001.2014.3001.5501

目录

一:创建对象

1、通过内置的构造函数创建对象

Object.create() 

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>

Object.create() 

Object.create() 是 JavaScript 中用于创建一个新对象,并将新对象的原型设置为指定的对象的方法。它的语法如下:

Object.create(proto, [propertiesObject])

1:其中,proto 是一个对象,作为新创建对象的原型。

2:propertiesObject 是一个可选参数,用于定义新对象的属性。

以下是一个示例,演示了如何使用 Object.create() 创建对象:

const parent = { a: 1 };
const child = Object.create(parent);
child.b = 2;

parent ,child 输出?

parent 是一个普通的对象,它有一个属性 a,值为 1。而 child 是通过 Object.create() 方法创建的一个对象,它继承自 parent 对象,并且有一个自身的属性 b,值为 2

因此,对于 parent 和 child 对象的输出如下:

console.log(parent);
// 输出:{ a: 1 }

console.log(child);
// 输出:{ b: 2 }

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 5
        this.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===2

    function 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实现虚拟列表滚动效果 (vue3+element-plus+Ts)

<template>
	<div style="height: 400px" @mouseenter="stopAnimate" @mouseleave="startAnimate">
		<el-auto-resizer>
			<template #default="{ height, width }">
				<el-table-v2
					:columns="columns"
					:data="data"
					:width="width"
					:height="height"
					fixed
				/>
			</template>
		</el-auto-resizer>
	</div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue';

const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
	Array.from({ length }).map((_, columnIndex) => ({
		...props,
		key: `${prefix}${columnIndex}`,
		dataKey: `${prefix}${columnIndex}`,
		title: `Column ${columnIndex}`,
		width: 150,
	}));

const generateData = (columns: ReturnType<typeof generateColumns>, length = 20, prefix = 'row-') =>
	Array.from({ length }).map((_, rowIndex) => {
		return columns.reduce(
			(rowData, column, columnIndex) => {
				rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`;
				return rowData;
			},
			{
				id: `${prefix}${rowIndex}`,
				parentId: null,
			}
		);
	});

const columns = generateColumns(10);
const data = ref(generateData(columns, 10));

let lastTime = Date.now();
const scrollDataDown = () => {
	const now = Date.now();
	const delta = now - lastTime;
	if (delta > 200) {
		const firstItem = data.value.shift();
		if (firstItem) data.value.push(firstItem);
		lastTime = now;
	}
};

let animationFrameId: number | undefined;

const animate = () => {
	scrollDataDown();
	animationFrameId = requestAnimationFrame(animate);
};

const startAnimate = () => {
	if (!animationFrameId) {
		animate();
	}
};

const stopAnimate = () => {
	if (animationFrameId) {
		cancelAnimationFrame(animationFrameId);
		animationFrameId = undefined;
	}
};

onMounted(() => {
	startAnimate();
});

onUnmounted(() => {
	stopAnimate();
});
</script>

<style scoped>
:deep(.el-table-v2__row:nth-child(odd)) {
	background-color: #fafafa;
}
:deep(.el-table-v2__row:nth-child(even)) {
	background-color: #fff;
}
</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 / once
      let countOfRender = 0
      let 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

import { AxiosResponse } from 'axios';
import { ElMessage } from 'element-plus';
interface Navigator {
	msSaveBlob: (blob: Blob, defaultName?: string) => boolean;
}
declare const navigator: Navigator;
/**
 * 用于下载脱敏接口清单模版文件文件的工具函数
 * @name downloadFile
 * @param { AxiosResponse<any> } res - 请求返回的文件数据
 * @param { string } fileName - 文件名称
 * @param { string } fileType - 文件类型
 * @returns { void } - 无返回值
 * @description
 * @fileType word:application/msword
 * @fileType pdf:application/pdf;chartset=UTF-8;
 * @fileType excel:application/vnd.ms-excel
 * @fileType ppt:application/vnd.ms-powerpoint
 * @fileType gif:image/gif
 * @fileType jpg:image/jpeg
 * @fileType png:image/png
 * @fileType txt:text/plain
 * @example
 * > import { downloadFile } from '/@/utils/download'
 * > downloadFile({ fileId }) Tips:失败状态在拦截器中处理
 * @author zk
 * @createDate 2023/08/23 11:29:40
 * @lastFixDate 2023/08/23 11:29:40
 */
export function downloadFile(
	res: AxiosResponse<any>,
	fileName: string = 'download',
	fileType: string = 'application/msword'
): void {
	try {
		const blob = new Blob([res.data], { type: fileType });

		if ('download' in document.createElement('a')) {
			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);
			document.body.removeChild(elink);
			ElMessage.success('下载成功');
		} else {
			// Internet Explorer浏览器(版本10及以上)的情况下使用
			navigator.msSaveBlob(blob, fileName);
		}
	} catch (error) {
		throw new Error(error as string);
	}
}

七:事件修饰符,组织冒泡

<!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-switch
              v-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-upload
      v-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-button
            style="background-color: #ffea00; color: white"
            plain
            size="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>
          如查看合规检测报告,请
          <a
            style="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 request
  cancel("你手动把请求取消了");
}
</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.0

        3、npm uninstall node-sass

        4、 cnpm install node-sass@6.0.1

        5、再试一次 npm run serve

十四:Vue3 reactive响应式赋值页面不渲染问题 data数据更新,页面没有渲染 

问题描述:

//声明变量 -- 错误定义会导致异步请求不及时渲染视图
let data = reactive([])
http().then(res=>{
  data = res.data
  console.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 >
        <input
        v-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 >
        <input
        v-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项目引入字体类型

  1. 在assets目录下新建foots(名字语义化可自定义),将后缀为ttf的字体包放进去 
  2. 然后新建一个css文件(我这里新建的是index.css)
  3. 在新建的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  top
					domset.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-input
            v-model="inputValue"
            placeholder="Please input"
            clearable
            v-focus
            v-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:1
        app.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-input
            v-model="inputValue"
            placeholder="Please input"
            clearable
            v-focus
            v-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,disY
        el.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;
          // 得到鼠标移动后盒子的offset
          let 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>
    <li
      v-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赋值给itemClick
  itemClick.value = item;
  // 重要
  commonFn();
}

// 点击上一项将上一项设置为当前选中的item
function handlerClickPrev() {
  itemClick.value = prevItem.value;
  commonFn();
}
// 点击下一项将下一项设置为当前选中的item
function handlerClickNext() {
  itemClick.value = nextItem.value;
  commonFn();
}

// 公共方法 上下项赋值(上下篇)
function commonFn() {
  // 检测当前项在数组中是否存在,存在则获取索引   indexOf---查询某个元素在数组中第一次出现的位置 存在该元素,返回下标,不存在 返回 -1
  itemIndex.value = dataArr.indexOf(itemClick.value);
  //  获取上一项item
  prevItem.value = dataArr[itemIndex.value - 1];
  //  获取下一项item
  nextItem.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 jspdf

import 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 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 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 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 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 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 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 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 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 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 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 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 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 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 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 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 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
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 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 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 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 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 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 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 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 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 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 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 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 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 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 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 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 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 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 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 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 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
      123 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的边距,显示区域190x277
          imgHeight = 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 ,这里要减掉 40
        var 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 ,这里要减掉 40
        var 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 &&
          currentMain.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;  // 返回0

    a = '字符串' && 0; // 返回0

    a = '' && 0; //空字符串转换布尔值为false 所以短路 返回空字符串

    a = undefined && 0; //undefined为false所以短路 返回undefined

    a = function () { } && 0; //函数转换布尔值为true 所以返回0

    a = null && 0; //null为false 所以短路 返回null

    a = NaN && 0; //NaN为false 所以短路 返回NaN

    a = 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,子组件已经抛出去了,父组件在挂载前取值,所以拿不到
/**
   beforeCreate
      vue组件实例对象已经创建完毕但是 data methods里面的内容还没有准备好 不可用 
  created
      ata 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.2

console.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谁先下载好,谁执行

小结

  1. defer和async都是异步加载外部JS文件
  2. 它俩的差别在于脚本下载完之后何时执行,defer属性会在html解析之后执行 ,async 则会在下载完就开始执行。
  3. 多个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>0
      console.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 引用数据直接比较为false
      console.log({}==={});//false 引用数据直接比较为false
    console.log(1 == 1);// true
    console.log(1 == "1");// true  隐式转换与数值类型比较
    console.log(1 == "1px");//false  
    console.log(1 == true);//true  js中true为1 flase为0
    console.log([]==false)// true  []表示false fasle为0
    console.log([]==0)// true   []表示false 0表示fasle
    console.log(100 == false);// false
    console.log(188 == "188");// true 不比较类型不是===严格等于
    console.log(false == " ");// true  " "空字符串为fasle
    console.log({}==[])// false //空数组不等于空对象
    console.log({}=={})// false //引用数据类型地址不相等
    console.log([]=="")// true false==false
    console.log([]==0)// true  false==0

   console.log([]==[])// false 引用数据类型比较的空间地址
    console.log(![] == [])// true  先比较在取反 对象先转字符串再转数字,布尔转数字; 数字比数字
    console.log([]==false) //true
    console.log([]===false) //false
    console.log(![]);//fasle
    console.log(![]==![])//true
    console.log(![]===![]);//true
    console.log(![]==false);//true  ![]typeof检测是boolean boolean==false true
    console.log({a:1}=="[object Object]")// true
    console.log("12px" == 12);//false

   //绝对比较; 只要数据类型不一样(隐式转换之前),那么返回false;
   console.log(1 === 1);// true
   console.log(1 === true);// false
   console.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>  
  <iframe
    src="/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 随机持续时间 实现有的快有的红包持续时间慢-->
    <div
      class="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为true
	canvas.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为false
	canvas.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"
      border
      style="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-column
        v-if="columnList.length > 0"
        type="index"
        :label="'No.'"
      />
      <el-table-column
        v-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-button
        type="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-button
        size="mini"
        type="primary"
        @click="addRow()"
        v-show="!curTarget.isHead"
      >
        上方插入一行
      </el-button>
      <el-button
        size="mini"
        type="primary"
        @click="addRow(true)"
        v-show="!curTarget.isHead"
      >
        下方插入一行
      </el-button>
      <el-button
        size="mini"
        type="primary"
        @click="addRowHead(true)"
        v-show="curTarget.isHead"
      >
        下方插入一行
      </el-button>
      <el-button
        type="primary"
        size="mini"
        @click="openColumnOrRowSpringFrame('行')"
        v-show="!curTarget.isHead"
      >
        删除当前行
      </el-button>
    </div>

    <!-- 单元格/表头内容编辑框 -->
    <div v-show="showEditInput" id="editInput">
      <el-input
        v-focus
        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>
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 = false
      this.showMenu = false;
      if (column.index == null) return;
      this.locateMenuOrEditInput("contextmenu", 140, event); // 菜单定位
      this.showMenu = true;
      // 当前目标
      this.curTarget = {
        rowIdx: row ? row.row_index : null, // 目标行下标,表头无 row_index
        colIdx: 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>
	<!-- 可编辑表格 -->
	<div id="table-wrap">
		<p class="tips">右键单击打开菜单,左键编辑</p>
		<el-table
			:data="tableData"
			max-height="calc(100vh - 170px)"
			stripe
			border
			style="width: 100%; margin-top: 10px"
			@cell-click="cellClick"
			@header-contextmenu="(column: any, event: MouseEvent) => rightClick(null, column, event)"
			@row-contextmenu="rightClick"
			:row-class-name="tableRowClassName"
		>
			<el-table-column width="50" v-if="columnList.length > 0" type="index" :label="'No.'" />
			<el-table-column
				v-for="(col, idx) in columnList"
				:key="col.prop"
				:prop="col.prop"
				:label="col.label"
				:index="idx"
			/>
		</el-table>

		<!-- 右键菜单框 -->
		<div v-show="showMenu" id="contextmenu" @mouseleave="showMenu = false">
			<p style="margin-bottom: 10px">列:</p>
			<el-button :icon="CaretTop" @click="addColumn(false)"> 前方插入一列 </el-button>
			<el-button :icon="CaretBottom" @click="addColumn(true)"> 后方插入一列 </el-button>
			<el-button :icon="DeleteFilled" @click="openColumnOrRowSpringFrame('列')">
				删除当前列
			</el-button>
			<el-button @click="renameCol" :icon="EditPen"> 更改列名 </el-button>

			<div style="color: #ccc">-----------------------</div>

			<p style="margin-bottom: 12px">行:</p>
			<el-button :icon="CaretTop" @click="addRow(false)" v-show="!curTarget.isHead">
				上方插入一行
			</el-button>
			<el-button :icon="CaretBottom" @click="addRow(true)" v-show="!curTarget.isHead">
				下方插入一行
			</el-button>
			<el-button :icon="DeleteFilled" @click="addRowHead" v-show="curTarget.isHead">
				下方插入一行
			</el-button>
			<el-button
				:icon="DeleteFilled"
				@click="openColumnOrRowSpringFrame('行')"
				v-show="!curTarget.isHead"
			>
				删除当前行
			</el-button>
		</div>

		<!-- 输入框 -->
		<div v-show="showEditInput" id="editInput">
			<el-input
				ref="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 lang="ts">
import { ref, reactive, computed, toRefs, nextTick } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { DeleteFilled, EditPen, CaretBottom, CaretTop } from '@element-plus/icons-vue';
interface Column {
	prop: string;
	label: string;
}

interface Data {
	choiceCode: string;
	choiceContent: string;
	riskIds: string;
	itemScore: number;
	[key: string]: string | number;
}

interface Target {
	rowIdx: number | null;
	colIdx: number | null;
	val: string | null;
	isHead: boolean | undefined;
}

const state = reactive({
	columnList: [
		{ prop: 'choiceCode', label: '选项编码' },
		{ prop: 'choiceContent', label: '选项内容' },
		{ prop: 'riskIds', label: '风险点' },
		{ prop: 'itemScore', label: '选项分值' },
	] as Column[],
	tableData: [{ choiceCode: 'A', choiceContent: '是', riskIds: '广州', itemScore: 1 }] as Data[],
	showMenu: false, // 显示右键菜单
	showEditInput: false, // 显示单元格/表头内容编辑输入框
	curTarget: {
		// 当前目标信息
		rowIdx: null, // 行下标
		colIdx: null, // 列下标
		val: null, // 单元格内容/列名
		isHead: undefined, // 当前目标是表头?
	} as Target,
	countCol: 0, // 新建列计数
});
const iptRef = ref();
const { columnList, tableData, showMenu, showEditInput, curTarget, countCol } = toRefs(state);
const curColumn = computed(() => {
	return curTarget.value.colIdx !== null
		? columnList.value[curTarget.value.colIdx]
		: { prop: '', label: '' };
});

// 删除当前列或当前行
const openColumnOrRowSpringFrame = (type: string) => {
	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: KeyboardEvent) => {
	if (e.key === 'Enter') {
		showEditInput.value = false;
	}
};

// 单元格事件 - 更改单元格数值
const cellClick = (
	row: { [x: string]: any; row_index: any },
	column: { index: null; property: string | number },
	cell: any,
	event: MouseEvent
) => {
	showEditInput.value = true;
	iptRef.value.focus();
	showEditInput.value = false;
	if (column.index == null) return;
	locateMenuOrEditInput('editInput', -400, 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: any, column: any, event: MouseEvent) => {
	if (column.index == null) return;
	event.preventDefault();

	showMenu.value = false;
	locateMenuOrEditInput('contextmenu', -500, event);
	showMenu.value = true;
	curTarget.value = {
		rowIdx: row ? row.row_index : null,
		colIdx: column.index,
		val: row ? row[column.property] : column.label,
		isHead: !row,
	};
};

// 更改列名
const renameCol = () => {
	showEditInput.value = false;
	if (curTarget.value.colIdx === null) return;
	showEditInput.value = true;
	nextTick(() => {
		iptRef.value.focus();
	});
};

// 更改单元格内容/列名
const updTbCellOrHeader = (val: string) => {
	if (!curTarget.value.isHead) {
		if (curTarget.value.rowIdx !== null) {
			(tableData.value[curTarget.value.rowIdx] as Data)[curColumn.value.prop] = val;
		}
	} else {
		if (!val) return;
		if (curTarget.value.colIdx !== null) {
			columnList.value[curTarget.value.colIdx].label = val;
		}
	}
};
// 新增行
const addRow = (later: boolean) => {
	showMenu.value = false;
	const idx = later ? curTarget.value.rowIdx! + 1 : curTarget.value.rowIdx!;
	let obj: any = {};
	columnList.value.forEach((p) => (obj[p.prop] = ''));
	tableData.value.splice(idx, 0, obj);
};

// 表头下新增行
const addRowHead = () => {
	showMenu.value = false;
	let obj: any = {};
	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: boolean) => {
	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: any; rowIndex: number }) => {
	row.row_index = rowIndex;
};

// 定位菜单/编辑框
const locateMenuOrEditInput = (eleId: string, distance: number, event: MouseEvent) => {
	const ele = document.getElementById(eleId) as HTMLElement;
	const x = event.pageX;
	const y = event.pageY;
	let left = x + distance - 100;
	let top;
	if (eleId == 'editInput') {
		top = y + distance + 100;
	} else top = y + distance - 130;

	ele.style.left = `${left}px`;
	ele.style.top = `${top}px`;
};
</script>

<style lang="scss" scoped>
#table-wrap {
	position: relative;
	width: 100%;

	.tips {
		color: #999;
		font-style: italic;
		font-size: 13px;
	}
	.el-button {
		background-color: rgba(0, 0, 0, 0.1);
	}
	#contextmenu {
		position: absolute;
		z-index: 999;
		top: 0;
		left: 0;
		height: auto;
		width: 167px;
		border-radius: 3px;
		border: #e2e2e2 1px solid;
		box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
		background-color: #fff;
		border-radius: 6px;
		padding: 15px 10px 14px 12px;

		button {
			display: block;
			margin: 0 0 5px;
		}
	}

	#editInput {
		position: absolute;
		top: 0;
		left: 0;
		z-index: 999;
		// top: 25%;
		// left: 50%;
		// transform: translateX(-30%);
	}
}
</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和symbol
			state.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.highlights
            if (!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 输入值为空,清空container
      if (!name)
      {
        container.innerHTML = '';
        return;
      }
      if (cache.get(name))
      {//如果map对象中有输入的valu 直接插入container
        container.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-ignore
	document.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 成立

       // 让代码成立 1
        let 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?')
        }

      // 让代码成立2
        let a = [1, 2, 3];
        a.toString = a.shift
        // 修改a的this,指向对象原型toString方法
        console.log('!这里输出 🚀 ==>:', Object.prototype.toString.call(...a));//[object Number]
        console.log('!这里输出 🚀 ==>:', a === 1);//false
        console.log('!这里输出 🚀 ==>:', a == 1);//true

        if (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">
			<button
				v-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++) {
		// 全选赋值所有ck
		tab.options[i].checked = tab.selectAllVm;
	}
	// 如果不同的 Tab 中有相同的选项,则将它们联动
	for (let i = 0; i < tabsData.length; i++) {
		// 非当前tab
		if (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.活在风浪里!

  • 8
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
没有最全,只有更全!!! world版全面开放 0、ANDROID常用类库说明 6 1、ANDROID文件系统与应用程序架构 7 1.1、ANDROID 文件系统 7 1.2、ANDROID应用程序架构 9 2、ANDROID应用程序结构 11 2.1、ACTIVITY 12 2.1.1、概述 12 2.1.2、Activity的生命周期 15 2.1.3、Activity 的创建 16 2.1.4、Activity 的跳转(含Bundle传值) 17 2.1.5.Actvity 堆栈 18 2.1.6、Intent对象调用Activity实例 19 2.1.7、Activity透明 21 2.1.8、一次性关闭所有的Activity 22 2.1.9、PreferenceActivity 用法 22 2.1.10、Dialog风格的Activity 23 2.1.11、横竖屏切换不销毁当前Activity 23 2.2、INTENT RECEIVER 25 2.3、SERVICE 26 2.3.1、什么是Service 26 2.3.2、如何使用Service 27 2.3.3、Service的生命周期 32 2.3.4、判断服务开启状态 33 2.3.5、获取启动的服务 34 2.4、CONTENT PROVIDER 35 3、ANDROID UI LAYOUT 35 3.1、概述 35 3.2、线性布局(LINEAR LAYOUT) 36 3.3、相对布局(RELATIVE LAYOUT) 39 3.4、TABLELAYOUT 40 3.5、ABSOLUTELAYOUT 47 4、ANDROID UI 控件 48 4.1、IMAGEBUTTON 48 4.1.1、图案填充问题 48 4.2、TEXTVIEW 49 4.2.1、动态滚动 49 4.3、EDITTEXT 49 4.3.1、光标选择 49 4.4、TITLEBAR 50 4.4.1、非全屏状态下不显示title标题栏 50 4.4.2、标题栏进度指示器 50 4.4.3、titleBar 高级实现方法(更美观) 51 4.4.4、获取标题栏和状态栏高度 57 4.4.5、标题栏显示简单的进度框 57 4.5、MENU 58 4.5.1、简单的代码 58 4.5.2、menu实现的两种方法 58 4.5.3、自定义MENU背景 62 4.5.4、触发menu 64 4.5.5、Context Menu和Options Menu菜单的区别 64 4.5.6、Context menus for expandable lists 64 4.6、LISTVIEW 66 4.6.1、ListView自定义分割线 66 4.6.2、LIST例一 66 4.6.3、LIST例二 76 4.6.4、LIST例三 80 4.6.5、ListView 被选中item的背景颜色 82 4.6.6、ListView自定义背景颜色 83 4.6.7、List长按与短按消息映射 84 4.6.8、点击ListView改变背景色 87 4.6.9、自动滚动ListView 88 4.6.10、BaseExpandableListAdapter例 88 4.6.11、列表视图(List View) 96 4.6.12、NoteList 99 4.7、TAB与TABHOST 106 4.8、RATINGBAR 110 4.8.1、例一 110 4.8.2、例二 112 4.9、DATE/TIME SET 115 4.9.1、DatePicker/TimePicker 115 4.9.2、DatePickerDialog/TimePickerDialog 119 4.10、WEBVIEW 120 4.10.1、WebView的使用 120 4.11、SCROLLVIEW 121 4.11.1、ScrollView的使用 121 4.12、GRIDVIEW 124 4.12.1、GridView的使用 124 4.13、GAMEVIEW 127 4.13.1、显示到一个布局中 127 4.14、TOASTE 128 4.14.1、短时间显示 128 4.14.2、长时间显示 128 4.15、对话框 128 4.15.1、简单的对话框: 128 4.15.2、包含两个按钮的对话框 128 4.15.3、三个按钮的提示框 129 4.15.4、包含输入的dlg 131 4.15.5、圆形进度框 133 4.15.6、AlertDialog.Builder 133 4.15.7、模式对话框 134 4.16、拖动BUTTON获得位置 135 5、ANDROID UI 美化 137 5.1、简单美化BUTTON、IMAGEBUTTON、TEXTVIEW等控件 137 5.2、BUTTON美化案例☆ 139 5.3、IMAGEBUTTON 按下时的动画效果 142 5.4、滚动条显示与隐藏 143 5.5、LISTVIEW 与 SCROLLVIEW 解决办法 144 方法一:(重写ListView) 144 方法二: 150 5.6、3D魔方 151 6、ANDROID UI 动画 160 6.1、四种2D动画 160 6.1.1、透明度控制动画效果 alpha 160 6.1.2、旋转动画效果 rotate 161 6.1.3、尺寸伸缩动画效果 scale 162 6.1.4、位置转移动画效果 translate 163 6.1.5、四种动画效果的调用 164 7、异步调用 167 开辟一个线程: 167 THREAD: 168 HANDER 170 TIMER 173 ANDROID 界面刷新 174 MESSAGE HANDER 175 用法: 175 线程与子线程调用MessageHander 177 Messagehandler实例 177 8、数据存储与读取 179 1. PREFERENCES 179 2. FILES 180 3. DATABASES 180 4. NETWORK 183 5、CONTENTPROVIDER 183 6、执行SQL语句进行查询 188 用法1 188 其它: 188 详解: 189 查看SQLITE表格内容 192 9、常用功能的实现 193 9.1、获取手机型号以及系统版本号 193 9.2、更改应用程序图标 194 9.3、迎合不同的手机分辨率 194 9.4.ANDROID屏幕适应的四个原则 195 9.5、ANDROID常用单位 196 9.6、取得屏幕信息 197 9.7、横竖屏 197 9.8、程序完全全屏 200 9.8.1锁屏锁键盘 200 9.9、程序的开机启动 201 9.10、动态START页面 208 9.11、彻底退出当前程序 212 9.12、获取应用程序的名称,包名,版本号和图标 212 9.13、调用ANDROID INSTALLER 安装和卸载程序 215 9.14、后台监控应用程序包的安装&卸载 216 9.15、显示应用详细列表 224 9.16、寻找应用 224 9.17、注册一个BROADCASTRECEIVER 225 9.18、打开另一程序 225 9.19、播放默认铃声 225 9.20、设置默认来电铃声 226 9.21、位图旋转 227 9.22、手机震动控制 228 9.23、SENSOR2D感应实例 228 9.24、运用JAVA MAIL包实现发GMAIL邮件 230 9.26、ANDROID键盘响应 236 9.27、后台监听某个按键 238 9.28、VECTOR用法 239 9.29、CURSOR 242 9.30、把一个字符串写进文件 244 9.31、把文件内容读出到一个字符串 245 9.32、扫描WIFI热点演示实例教程 246 9.33、调用GOOGLE搜索 249 9.34、调用浏览器 载入某网址 249 9.35、获取 IP地址 249 9.36、从输入流中获取数据并以字节数组返回 250 9.37、通过ANDROID 客户端上传数据到服务器 251 9.38、文件下载类 255 9.39、下载文件的进度条提示 263 9.40、通过HTTPCLIENT从指定SERVER获取数据 265 9.41、通过FTP传输文件,关闭UI获得返回码 266 9.42、激活JAVASCRIPT打开内部链接 266 9.43、清空手机COOKIES 267 9.44、检查SD卡是否存在并且可以写入 267 9.45、获取SD卡的路径和存储空间 268 9.46、将程序安装到SD卡 268 9.47、创建一个SD映像 269 9.48、查看手机内存存储 269 9.49、在模拟器上调试GOOGLE MAPS 271 9.50、建立GPRS连接 273 9.51、获取手机位置 274 9.5* 获得经纬度,地名标注在地图上 274 9.52、获得两个GPS坐标之间的距离 276 9.53、通过经纬度显示地图 277 9.54、路径规划 277 9.55、将坐标传递到GOOGLE MAP并显示 277 9.56、获取本机电话号码 280 9.57、获得手机联系人 280 9.58、2.0以上版本查询联系人详细信息 282 9.59、2.0以上版本添加联系人 285 9.60、拨打电话 287 9.61、发送SMS、MMS 287 9.62、监听电话被呼叫状态 288 9.63、监听要拨打的电话(可以后台进行修改号码) 290 9.64、后台监听短信内容 291 9.65、删除最近收到的一条短信 292 9.66、调用发短信的程序 293 9.67、后台发送短信 293 9.68、调用发送彩信程序 294 9.69、发送EMAIL 294 9.70、播放多媒体 295 9.71、控制音量 296 9.72、定义CONTENTOBSERVER,监听某个数据表 302 9.73、打开照相机 303 9.74、从GALLERY选取图片 303 9.75、打开录音机 303 9.76、语音朗读 303 9.77、手机获取视频流显示在电脑上 305 9.78、蓝牙的使用 313 9.79、一个很好的加密解密字符串 316 9.80、DRAWABLE、BITMAP、BYTE[]之间的转换 318 9.81、高循环效率的代码 320 9.82、给模拟器打电话发短信 321 9.83、加快模拟器速度 321 9.83.1、模拟器 “尚未注册网络” 322 9.84、EMULATOR命令行参数 322 9.85、如何进行单元测试 323 9.86、ANDROID自动化测试初探 324 9.86.1、捕获Activity上的Element 324 9.86.2、Hierarchyviewer 捕获Element的 328 9.86.3、架构实现 330 9.86.4、模拟键盘鼠标事件(Socket+Instrumentation实现) 332 9.86.5、再述模拟键盘鼠标事件(adb shell 实现) 334 9.87、反编译APK 344 9.88、更换APK图标(签名打包) 348 9.89、利用ANDROID MARKET赚钱 363 9.90、ANDROID-MARKET 使用 365 9.91、传感器 369 9.91.1、获取手机上的传感器 369 9.91.2、 371 9.92、时间类 372 * 获得日期或时间字符串 372 * num天前的日期 373 * num天后的日期 373 * 判断 thingdate 的 dotime 天后是否在今天之后 374 * 判断testDate+testTime是否在两个时间之内 375 附录: 378 附录1、XML布局中的常用属性 378 1.通用属性 378 2.Edit Text部分属性 381 3.layout_alignParentRight android:paddingRight 384 附录2、INTENT ACTION 385 附录3、ANDROID的动作、广播、类别等标志 387 ★★★附带工具包说明 393 1.APK反编译工具.rar 393 2.APK安装工具.rar 393

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彩色之外

你的打赏是我创作的氮气加速动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值