CZX前端秘籍2

news2024/11/27 18:38:21

vue生命周期( 组件从创建到销毁的过程就是它的生命周期)

创建前 beforeCreat( 在这个阶段属性和方法都不能使用)

创建时 created( 这里时实例创建完成之后, 在这里完成了数据监测, 可以使用数据, 修改数据, 不会触发updated, 也不会更新视图)

挂载前 beforeMount( 完成了模板的编译, 虚拟DOM也完成创建, 即将渲染, 修改数据, 不会触发updated)

挂载时 Mounted( 把编译好的模板挂载到页面, 这里可以发送异步请求也可以访问DOM节点)

更新前 beforeUpdate( 组件数据更新之前使用, 数据是新的, 页面上的数据时旧的, 组件即将更新, 准备渲染, 可以改数据)

更新时 updated( render重新做了渲染, 这时数据和页面都是新的, 避免在此更新数据)

销毁前 beforeDestroy( 实例销毁前, 在这里实例还可以用, 可以清除定时器等等)

销毁时 destroyed( 组件已经被销毁了, 全部都销毁)

keep-alive 组件缓存( 不销毁, 刷新的时候, 保存状态)

多了两个生命周期( activited 组件激活时 deactivited 组件没被激活时)缓存组件, 避免组件内数据重复渲染, 直接可以在页面中调用。

优点: 组件切换过程中, 组件保存在内存中, 防止重复渲染, 减少加载时间, 提高性能。

created和mounted

created: 在渲染前调用, 通常先初始化属性, 然后做渲染

mounted: 在模板渲染完成后, 一般都是初始化页面后, 在对元素节点进行操作在这里请求数据可能会出现闪屏的问题, created里不会

请求的数据对DOM有影响, 那么使用created; 如果请求的数据对DOM无关, 可以放在mounted

vue中的修饰符

1. 事件修饰符

.stop 阻止冒泡 .prevent 组织默认行为 .once 事件只会触发一次

2. 按键修饰符

.keyup 键盘抬起 .keydown 键盘按下

3. 鼠标修饰符

.left 鼠标左键 .right 鼠标右键 .middle 鼠标中键

4. 表单修饰符

.lazy 等输入完之后再显示 .trim 删除内容前后的空格 .number 输入是数字或转为数字

VUE组件

组件通信

父组件通过 props 传值给子组件,子组件通过 $emit 传值给父组件,或触发父组件事件

父组件 $refs 获取子组件 值或方法,子组件 $parent 获取父组件值或方法。 如果是多重嵌套, 也可以使用多层。

祖先组件通过 provide 传值给 孙子组件(通过 inject 接受)。允许一个祖先组件向其所有子孙后代注入一个依赖, 不论组件层次有多深, 并在其上下游关系成立的时间里始终生效。 注意: provide 和 inject 绑定并不是可响应的

attrs 实现孙子组件获取祖先组件的 attribute 绑定的值(class和style除外)listeners 则包含了 祖先组件 中(不含.native 修饰器的) v - on 事件监听器

注意:孙子组件无法直接向祖先组件传值,需要通过父组件或者使用事件总线,本地存储等进行通信

兄弟组件通信(兄传父, 父传弟, 反之亦然。 太繁琐)

安装引用 插件 mitt 或者 插件 $bus( 两者都是基于 事件总线event - bus)

父子组件生命周期钩子函数执行顺序

创建: 父beforeCreate - > 父created - > 父beforeMount - > 子beforeCreate - > 子created - > 子beforeMount - > 子mounted - > 父mounted

销毁: 父beforeDestroy - > 子beforeDestroy - > 子destroyed - > 父destroyed

VUE的单项数据流

Vue 的单向数据流是指数据在 Vue 应用中的流动方向是单向的,数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改。这样会防止从子组件意外改变父组件的状态。

defineProps 父传子(用在子组件,接收父组件的传值,用来声明props)

defineEmits 子传父(用在子组件,将子组件的方法传递给父组件,用来声明emits)

defineExpose 子传父(用在子组件,暴露想传递的值或方法,父组件通过ref属性获取子组件暴露的)

Suspense

<template>
	<div class="app">
		<h3>我是App组件</h3>
		<Suspense>
			<template v-slot:default>
				<Child />
			</template>
			<template v-slot:fallback>
				<h3>稍等,加载中...</h3>
			</template>
		</Suspense>
	</div>
</template>
// import Child from './components/Child'//静态引入
import { defineAsyncComponent } from "vue";
const Child = defineAsyncComponent(() => import("./components/Child")); //异步引入
export default {
	name: "App",
	components: { Child },
};

在上面的例子中,AsyncComponent 被定义为异步组件,它只有在被实际渲染到页面时,才会从 './AsyncComponent.vue'文件中加载和解析,而不是在页面加载时就被引入。通过 defineAsyncComponent,Vue3 提供了一种简单而强大的方式来管理和优化组件的加载行为,使得应用程序可以更高效地使用和分发组件资源

VueX和Pinia

state(数据存储的地方) 、getter(计算属性)、mutations(同步方法)、actions(异步方法)

vue当store中的值发生变化时,怎么去通知使用到的组件?

1 直接在计算属性中访问 store

2 使用 watch 监听 store 中的状态变化

3 使用 actions 和 mutations

Vuex页面刷新数据丢失,解决方法

需要做 vuex 数据持久化 ,一般使 用本地储存的方案 来保存数据,可以自己设计存储方案,也可以使用第三方插件。

推荐使用 vuex-persist(脯肉赛斯特 )插件,它是为 Vuex 持久化储存而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者localStorage 中

vue路由的hash模式和history模式区别

1. hash的路由地址上有 #号, history模式没有

2. hash模式支持低版本浏览器, history不支持, 因为是H5新增的API

3. hash模式利用的是锚点 history借助 history 对象中的 pushState() 函数重写 URL 路径, 都是通过控制页面的display: none 属性

4. hash模式简单,部署容易,不需要服务器端的配置。history需要

5. hash不会重新加载页面,在做回车刷新的时候, hash模式会加载对应页面。history会报错404

vue-router

路由传参方式

router 传值方式(router-link 和 this.$router.push)

1 query 传参(显示参数) this.$router.push({ name: 'Child', query: { id: 123 } })

2 params 传参(显示参数和不显示参数)

显示参数 this.$router.push({path:'/child/${id}',})

不显示参数 this.$router.push({ name: 'Child', params:{ id: 123 } })

三种导航守卫

1. 全局守卫(Global Before Guards) 写在 router/index.js 文件中的全局

用于全局的路由控制逻辑,如登录验证、权限控制等。

2. 路由守卫(Per-Route Guards) 写在 router/index.js 文件中的const routes = [] 中的每一个路由内

用于特定路由或路由群组的控制逻辑,如权限验证、数据预加载等。

3. 组件守卫(In-Component Guards)写在该组件的Vue页面中,类似 生命周期的写法

用于处理单个组件在路由导航过程中的逻辑,如特定路由下的数据加载、页面交互等。

to:目标路由对象;from:即将要离开的路由对象; next:它是最重要的一个参数,调用该方法后,才能进入下一个钩子函数。

next()//直接进to 所指路由

next(false) //中断当前路由

路由拦截

路由拦截, 需要在路由配置中添加一个字段, 它是用于判断路由是否需要拦截,然后再在全局或路由守卫进行拦截

{
    name: "index",
    path: "/index",
    component: Index,
    meta: {
        requirtAuth: true
    }
}
router.beforeEach((to, from, next) => {
    if (to.meta.requirtAuth) {
        next()
    }
})

AXIOS的封装和拦截

import axios from 'axios'
import getBaseUrl from './getBaseUrl'
// 创建axios实例
const request = axios.create({
	baseURL: getBaseUrl(),// 所有的请求地址前缀部分(没有后端请求不用写)
	timeout: 80000, // 请求超时时间(毫秒)
})
// request拦截器
request.interceptors.request.use(
	config => {
		// 如果你要去localStor获取token,(如果你有)
		// let token = localStorage.getItem("x-auth-token");
		// if (token) {
		//添加请求头
		//config.headers["Authorization"]="Bearer "+ token
		// }
		return config
	},
	error => {
		// 对请求错误做些什么
		Promise.reject(error)
	}
)
// response 拦截器
request.interceptors.response.use(
	response => {
		// 对响应数据做点什么
		return response.data
	},
	error => {
	// 对响应错误做点什么
	//响应错误
	let message = "";
	if (error.response && error.response.status) {
		const status = error.response.status;
		switch (status) {
			case 401:
			message = "未授权";
			break;
			case 404:
			message = "请求地址出错";
			break;
			case 500:
			message = "服务器内部错误!";
			break;
			default:
			message = "请求失败";
		}
		return Promise.reject(error);
	}
	return Promise.reject(error);
}
)
export default request

vue 强制刷新

1. localtion.reload() // 跟按F5一样 不会利用缓存,直接重新加载 浪费性能

2. this.$router.go(0) // 跟按F5一样 不会利用缓存,直接重新加载 浪费性能

3. 找个空白页过渡一下 // 地址闪动效果,页面闪动

4. 利用 provider inject, 在孙组件中就可以直接调用祖先组件的方法, 进行刷新页面, 推荐

其本质上是通过控制 app.vue 中 router-view 标签设置 v-if='show' ,控制他先消失再显示

因为是 刷新按钮 一般不在 app.vue 文件,而是在其子孙组件中,所以使用到 provider 和 inject

computed和watch

1. computed是计算属性,data中无定义的数据,computed的值,可以直接使用;watch是监听, 监听的是data中数据的变化

2. computed是支持缓存, 依赖的属性值发生变化, 计算属性才会重新计算, 否则用缓存; watch不支持缓存

3. computed不支持异步, watch是可以异步操作

4. computed是第一次加载就监听, watch不监听( 有 deep: true // 开启深度侦听,immediate: true // 立马监听)

5. computed函数中必须有return watch不用

watch和watchEffect

1 watchEffect是立即执行的,在页面加载时会主动执行一次,来收集依赖;而watch是惰性地执行。

2 watchEffect只需要传递一个回调函数;watch至少要有两个参数(第三个参数是配置项),第一个参数是侦听的数据,第二个参数是回调函数。

3 watchEffect获取不到更改前的值;而watch可以同时获取更改前和更改后的值。

4 watchEffect自动追踪所有使用的响应式数据,当任何相关数据变化时,重新运行整个函数;watch 用于监测一个或多个特定的响应式数据源,并在变化时执行回调。

vue过滤器

vue的特性, 用来对文本进行格式化处理。 使用它的两个地方, 一个是插值表达式, 一个是v-bind

1. 全局过滤器

Vue.filter("add", function(v) {
		return v < 10 ? "0" + v : v
}) 
{{33 | add}}
2. 本地过滤器( 和methods同级)

filter: {
	add: function(v) {
		return v < 10 ? "0" + v : v
	}
}

插槽Slot

1. 占位符 2. slot 名称位置( template) 3. 子组件传值 v-slot = 'slotProps'

Vue 中 delete 和 Vue.delete 删除数组

delete 只是被删除的元素变成了empty/undefined 其他的元素的键值还是不变。Vue.delete 直接删除了数组 改变了数组的键值 。

let arr = [1, 2, 3];
delete arr[1];
console.log(arr); // 输出:[1, undefined, 3]
let arr = [1, 2, 3];
vue.$delete(arr, 1);
console.log(arr); // 输出:[1, 3]

内置指令和自定义指令

v-model的双向绑定原理

v-model 是一个语法糖,结合了 v-bind 和 v-on 两个指令的功能。v-bind:用于将表单控件的值绑定到 Vue 实例的数据属性上。v-on:用于监听表单控件的输入事件,然后将事件的新值更新到 Vue 实例的数据属性上。

v-html、 v-bind、 v-on、 v-model......

// 自定义指令
Vue.directive("focus", {
	inserted: function(el) {
		el.focus()
	}
})

开发过自定义指令: 拖拽,复制,长按,防抖,节流,过滤日期。

vue2 $set 和 $nextTick

只有vue2 有 $set 方法用于向响应式对象添加响应式属性,并确保这个新添加的属性是响应式的。Vue.js在初始化实例时会将data中的属性转换为getter/setter,从而使其变成响应式的。但是,对于新增的属性,Vue无法自动实现响应式,因此需要使用$set方法来手动添加响应式属性。

$nextTick方法主要用于确保在DOM更新完成后执行特定的回调函数。

VueX(Vue2) 和 Pinia(Vue3)

VueX: state 存储变量; getters state的计算属性; mutations 提交更新数据的方法; actions 和 mutations 差不多, 他是提交mutations来修改数据, 可以包括异步操作

Pinia:state 存储变量; getters state的计算属性; actions(包含 mutations)

hooks详解

Vue3 Hooks是一种函数式的API,允许我们在组件之间复用状态逻辑。这些函数包括setup、reactive、ref等,以及一系列生命周期函数如onMounted、onUpdated等。

// 1 获取宽高hooks,可以变成获取不同设备的hooks
// hooks/xxx.js
import{ ref, onMounted, onUnmounted } from 'vue'
export function useWindowResize(){
	const width = ref(window.innerWidth);
	const height = ref(window.innerHeight);
	const handleResize=()=>{
		width.value = window.innerWidth;
		height.value = window.innerHeight;
	}
	onMounted(() => { 
		window.addEventListener('resize',handleResize)
	});
	onUnmounted(() => {
		window.removeEventListener('resize',handleResize)
	});
	return { width, height }
}
// 使用就更简单了,只需要调用这个钩子就可以获得 window 的宽度和高度。
// xxx.vue
import useWindowResize from "../hooks/xxx"
const { width, height } = useWindowResize()
// 2 剪切hooks
// hooks/xxx.js
function copyToClipboard(text){
	// 这个 input 最好变成参数传递进来 id 或者 class 名
	let input = document.createElement('input');
	input.setAttribute('value',text);
	document.body.appendchild(input);
	input.select();
	let result= document.execCommand('copy');
	document.body.removechild(input);
	return result;
}
export const useCopyToclipboard=()=>{
	return(text)=>{
		if(typeof text === "string" || typeof text == "number"){
			return copyToClipboard(text);
		}
		return false;
	}
}
// 使用xxx.vue
const copyToClipboard = useCopyToclipboard()
copyToClipboard('just copy')
// 3 滚动条滚动到底部hooks
import { onMounted,onUnmounted } from 'vue'
export const useScrollToBottom = (callback= () =>{}) => {
	const handleScrolling = () => {
		if((window.innerHeight + window.scrollY) >= document.body.scrollHeight){
			callback()
		}
	}
	onMounted(() => {
		window.addEventListener('scroll',handleScrolling)
	});
	onUnmounted(() => {
		window.removeEventListener('scroll',handleScrolling)
	});
}
// 使用xxx.vue
useScrollToBottom(() => {
	console.log("到底了")
})

hooks 和 units 区别

1. 表现形式不同: hooks 是在 utils 的基础上再包一层组件级别的东西(钩子函数等); utils 一般用于封装相应的逻辑函数, 没有组件的东西;

2. 数据是否具有响应式: hooks 中如果涉及到 ref, reactive, computed 这些 api 的数据, 是具有响应式的; 而 utils 只是单纯提取公共方法就不具备响应式;

3. 作用范围不同: hooks 封装, 可以将组件的状态和生命周期方法提取出来, 并在多个组件之间共享和重用; utils 通常是指一些辅助函数或工具方法, 用于实现一些常见的操作或提供特定功能。

v-if v-show v-for

v-if和v-show的区别(都可以控制元素的显示和隐藏)

1. v-show时控制元素的display值来让元素显示和隐藏; v-if显示隐藏时把DOM元素整个添加和删除

2. v-if有一个局部编译 / 卸载的过程, 切换这个过程中会适当的销毁和重建内部的事件监听和子组件; v-show只是简单的css切换

3. v-if的切换效率比较低 v-show的效率比较高

[Vue] 中为何不要把 v-if 和 v-for 同时⽤在同一个元素上

1 优先级冲突: v-for 指令在 Vue 中的优先级比 v-if 高。这意味着,如果你在一个元素上同时使用 v-if 和 v-for,v-for 将首先运行并渲染列表中的所有项目,然后 v-if 将根据条件决定是否显示整个列表。这可能会导致不必要的渲染和性能问题。

2 性能问题: 当 v-if 的条件不满足时,Vue 仍然会对列表中的每个项目执行 v-for,即使这些项目最终不会被渲染到 DOM 中。这种额外的计算和虚拟 DOM 的更新可能会影响性能,特别是当列表中的项目数量较大时。

在vue2中,v-for的优先级高于v-if; 在vue3中,v-if的优先级高于v-for. 外层使用另外的div或template嵌套

Vue2 的实现原理

vue.is 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体步骤

第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和 getter这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化

第二步:compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

第三步:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁

第四步:MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过Observer来监听自己的 model数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher搭起 Observer和 Compile 之间的通信桥梁,达到数据变化 ->视图更新;视图交互变化(input)-> 数据 model 变更的双向绑定效果。

Model(模型) View(视图) ViewModel(视图模型)

vue3 proxy

一、defineProperty 是对属性进行劫持,proxy是代理整个对象

二、defineProperty 无法监听对象新增属性,Proxy可以(所以vue2用到了 $set)

三、defineProperty 无法监听对象删除属性,Proxy可以

四、defineProperty 无法监听数组下标改变值的变化,proxy 可以且不需要对数组的方法进行重写

vue2和vue3已实现数据响应式来更新DOM了,为什么还有diff算法?

1 性能优化:直接操作真实 DOM 是非常昂贵的,而虚拟DOM 可以在内存中快速进行比较和计算差异。Diff算法帮助减少了更新操作的次数和范围,从而提升了页面渲染的性能。

2 批量更新:Diff算法能够将多次 DOM 更新操作合并为一次,避免了频繁的 DOM 操作,减少了浏览器的重排和重绘。

3 Diff算法可以智能地比较新旧DOM树的变化,只更新必要的部分,从而提高了更新效率。

vue2 和 vue3 的区别

1.双向数据绑定的原理不同

2.生命周期的不同

3.组件、指令和插槽使用方式都不同

4.没有this, 通过hooks的方式,设置选项式API转变组合式API

5.对TS的支持,diff算法的优化,性能提升

Vue3 组合式Api及其作用

reactive 和 ref 是用来创建响应式数据的函数。

ref 用于创建一个包含单一值的响应式引用,可以通过 .value 属性访问其值。

reactive 用于创建一个包含多个属性的响应式对象。

toRefs 用于将一个响应式对象转换为普通对象,对象的每个属性都被包装成 ref,使得对象的属性可以像 ref 一样使用。

computed 用来创建计算属性,依赖于响应式数据,并在依赖数据更新时自动重新计算其值。

watch 用来监视指定的响应式数据或计算属性,并在其变化时执行特定的操作。

watchEffect 会立即执行一个函数,并响应其内部响应式数据的变化。

生命周期钩子,路由等

ref 和 reactive 区别(两种响应式数据绑定方式)

1 ref用于包装JS的基本数据类型,而reactive用于包装JS对象和数组等复杂类型的数据。

2 使用和访问方式不同 .value

3 ref 底层是 Object.defineProperty() 数据劫持,reactive底层是proxy

虚拟 DOM、diffing 算法、key

虚拟 DOM, 比较“轻”,真实 DOM 比较“重”,因为虚拟 DOM 是内部在用,无需真实 DOM 上那么多的属性。本质是object类型的对象(一般对象)。虚拟DOM最终会被转化为真实DOM,呈现在页面上。

diffing 算法,对于页面的更新,内部会调用diffing算法,将旧的虚拟dom和新的虚拟dom进行一层一层的节点比较,如果节点相同就不重新渲染到真实dom,如果不相同就重新渲染。最小的对比单位是一个节点。

key的作用,key作为节点的表示,在节点发生更新的时候起着重要作用。在状态发生改变后,新旧虚拟dom发生比较,先比较新dom中是否有相同key值的节点,如果有则进行比较节点,如果没有,则直接渲染新的节点。 key属性是DOM元素的唯一标识

最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值

禁止使用整个 item,会造成性能问题

简述 Vue 单页面和传统的多页面区别

单页面应用(SPA):通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html,is,css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端

多页面(MPA):指一个应用中有多个页面,页面跳转时是整页刷新

单页面的优点:用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点 spa 对服务器压力较小;前后端分离;页面效果会比较炫酷(比如切换页面内容时的专场动画)。

单页面缺点:不利于 seo;导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理);初次加载时耗时多;页面复杂度提高很多

vue单页面不利于seo,改成多页面为什么也不利于seo?

因为Vue是需要在创建前生命周期调用后才开始渲染页面的

vue项目性能优化

1、v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if

2、防止内部泄露,组件销毁后把全局变量和时间销毁

3、图片、路由、懒加载,第三方插件的按需加载

4、防抖、节流的运用

5、服务端渲染 SSR or 预渲染(由于浏览器在渲染出页面之前,需要先加载和解析相应的 html、css 和 js 文件,为此会有一段白屏的时间,可以添加loading,或者骨架屏幕尽可能的减少白屏对用户的影响体积优化)

注意:SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端。SSR有着更好的SEO、并且首屏加载速度更快等优点。

6、减少不必要的请求

7、使用插件等进行打包优化

vue中的data为什么是一个函数

1 隔离作用域 2 实例化多个组件 

当组件被复用时,如果data是一个对象,那么所有的组件实例将共享同一个数据对象,这意味着一个组件的修改会影响到其他组件。

所以把data设置为工厂函数,数据不会相互影响。

webPage 和 Vite

Loader 和 Plugin

功能不同

Loader: Loader 主要用于处理文件类型的转换和处理,比如将 ES6/ES7 代码转换成ES5 代码,将LESS/SASS/CSS 文件转换成浏览器可识别的CSS 文件等

Plugin: Plugin 主要用于在打包过程中做一些额外的处理工作,比如文件压缩、代码分离、资源优化、生成 HTML 文件等。

作用范围不同

Loader 是针对于每个文件进行处理的,每个文件都会经过 Loader 进行转换处理,因此 Loader 的作用范围比较小。

Plugin 是针对于整个项目进行处理的,它们能够修改打包的结果、优化打包过程、生成文件等。

Vite 快速的冷启动,按需编译,不用等待整个项目编译完成。

Vite打包

vue底层是rollup 和 esbuild在起作用

npm run build 后 打包生成 dist 文件,给后端部署

1 配置打包文件和打包后位置

默认是

vite.config.ts 中的配置 rollupOptions 中 output 设置新的文件位置

export default defineConfig({
    plugins: [vue()],
    build: {
        rollupOptions: {
            output: {
                entryFileNames: 'js/[name].[hash].js',
                chunkFileNames: 'js/[name].[hash].js',
                assetFileNames(assetInfo){
                    if(assetInfo.name.endsWith('.css')){
                        return 'css/[name].[hash].css'
                    }
                    if(['.png', '.jpg', '.jpeg', '.wenp', '.svg', '.git'].some(ext => assetInfo.name.endsWith(ext))){
                        return 'img/[name].[hash].[ext]'
                    }
                    return 'assets/[name].[hash].[ext]'
                }
            }
        }
    }
})

2 配置分包,控制每次build的时候进行包的那些打包和那些不打包

分包策略 就是把不会常规更新的文件,单独打包处理。

vite 在进行打包的时候,会在文件名中添加一个hash值,这个hash值与文件内容有关,当文件内容发生变化时,这个hash值就会发生变化。 之所以使用这个hash值的方式,就是为了让浏览器能够在文件内容更新时及时地去重新请求新的资源。

manualChunks

export default defineConfig({
    plugins: [vue()],
    build: {
        rollupOptions: {
            // manualChunks: {
            //    aaa: ['lodash', 'vue'],
            //}
            manualChunks(id){
                // 把 node_modules 下的不需要重复打包的文件 分包成 vendor
                if(id.includes('node_modules')){
                    return 'vendor'
                }
                console.log(id)
            }
        }
    }
})

配置打包后的文件名,处理浏览器硬性清除缓存问题。打包配置HASH后缀

3 vite 打包压缩配置,需要一个插件 : vite-plugin-compression

/**
 * 文件压缩的配置 
 */

import { defineConfig} from "vite"
import { compression } from 'vite-plugin-compression2'
export default defineConfig({
	plugins:[
		// 就是使用这个插件实现的文件压缩
		compression({
			threshold:2000, // 设置只有超过 2k 的文件才执行压缩
			deleteOriginalAssets:false, // 设置是否删除原文件
			skipIfLargerOrEqual:true, // 如果压缩后的文件大小与原文件大小一致或者更大时,不进行压缩
			// 其他的属性暂不需要配置,使用默认即可
		})
	]
})

使用vue-cli或者vite创建出来的项目是不具备多端兼容的,其中的路由方式、接口请求方式、标签等等。使用taro或uniapp为底层框架,做了多端适配,引入vue,可以做多端系统。

VUE项目每次打包的版本号是可以配置不同编码(哈希值)的。这样的话不用线上强制刷新。

场景题

1 购物车页面的制作逻辑

拿到data,for循环设置状态,async await,根据图片名称异步调用图片,点击勾选时更改状态,利用 Computed 进行计算,删除购物利用状态和过滤器进行操作。

2 Vue首页加载优化

懒加载路由组件 异步组件结合Suspense 减少首页组件数量 图片优化 代码优化 缓存 服务器端渲染

3 一些多端兼容问题

3.1 手机端快速向下滚动时,IOS浏览器的地址栏和任务栏会隐藏;快速向上滚动时,会显示;缓慢滚动不改变;此时点击弹出层时,就会出现各种各样的页面布局影响。

解决方法:在打开弹出层时把滚动条位置设置为0的基础上加上 1. 弹出层不占满手机大小 2. 弹出层的大小随手机的变化而变化

3.2 小程序谷歌地图 web-view 传值时,H5和app 无法使用同一个方法: H5 无法使用 web-view 的 @message 方法,而 app 无使用 web-view 存储区。

解决方法:分开页面跳转,进行不同的操作。H5 直接使用 本地储存进行地址传值。app 则通过 web-view 自身的 @message 进行传值

4 uniapp分包小程序

4.1 配置manifest.json,'optimization':{'subPackages':true}

4.2 在pages.json中新建数组'subPackages',数组中包含两个参数:(root:为子包的根目录,pages:子包由哪些页面组成,参数同pages;)

4.3 注意:主包和分包是不能再同一目录下,在构建uniapp项目时,可以考虑一下目录结构,以便后期进行分包;

  • 小程序分包:是将小程序的代码和资源分割成多个包,主要用于降低主包体积,提升加载速度。适合大型小程序,能按需加载。

  • 小程序独立分包:是指某个分包可以独立于主包运行,不需要主包中的代码和资源。适用于功能相对独立且体积较大的模块,比如复杂的游戏或工具。

  • 小程序分包预加载:允许在用户进入某个页面之前,提前加载该分包的资源,确保用户体验流畅。常用于用户可能频繁访问的功能,以减少等待时间。

5 前后端加密(心理医生信息资料,文章加密)

传递重要参数加密、登录加密

密钥固定加密:前后端无需传递密钥,只需传递加密完的字符就行了

动态密钥加密:前端生成随机密钥,将加密好参数和密钥一起发送给后端

Crypto.JS:是一个流行的JavaScript加密库,支持多种加密算法,包括AES,DES,RC4等加密方式或者md5,SHA等哈希散列。

import CryptoJS from 'crypto-js';
// AES加密 要加密的内容 密钥
const ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123').toString();
console.log(ciphertext);
// AES解密 要解密的内容 密钥
const bytes  = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
const originalText = bytes.toString(CryptoJS.enc.Utf8);
console.log(originalText);

6 阿里巴巴矢量库图标导入

https://blog.csdn.net/weixin_59528719/article/details/136343957

按照官方配置后,类名就能输出对应的图标(借助before)

.icon-test:before{ content: '\e8c5'}

7 前端分页

前端分页:调接口拿到数据,存放所有数据的数组,存放当前页面数据的数组,设置keyWord,page,size等查询参数。

state.showList = state.list.filter(item => item.name.includes(state.keyWord)).slice((state.currentPage - 1) * state.pageSize, state.currentPage * state.pageSize)

state.total = state.list.filter(item => item.name.includes(state.keyWord)).length
8 滚动分页

监听滚动事件:在Vue组件中,可以使用window对象的scroll事件来监听用户滚动。计算滚动位置:确定用户是否滚动到页面底部,通常是通过比较滚动位置和页面高度的差异来实现的。加载更多数据:当用户滚动到底部时,触发加载更多数据的操作,例如获取下一页的内容。

9 多表单处理

通用的数据使用VueX存储,分页展示,组件Suspense异步加载、延迟加载loading,扩展的数据懒加载

10 图片懒加载

现在很多组件库都可以直接加 lazy 设置图片懒加载。

图片懒加载 监听图片是否进入到可视区,正式进入可视区之后将图片url数据交给img标签的src属性。

11 虚拟滚动

VueUse 是一个基于 Vue 3 的插件库。

VueUse 提供了大量的功能性钩子函数和工具函数,包括但不限于:

useLocalStorage:用于在 Vue 组件中方便地使用 localStorage。

useClipboard:用于在 Vue 组件中方便地操作剪贴板。

useMouse:用于获取鼠标事件信息的钩子函数。

useDebounce 和 useThrottle:用于创建防抖和节流的函数。

useIntersectionObserver:用于观察元素是否进入视口的钩子函数。

//图片加载失败所显示的默认图片
import defaltImg from '@/assets/images/200.png'
// 引入监听是否进入视口
import { useIntersectionObserver } from '@vueuse/core'
export default {
  // 需要拿到main.js中由createApp方法产出的app实例对象
  install (app) {
	// app实例身上有我们想要的全局注册指令方法  调用即可
	app.directive('imgLazy', {
	
	  mounted (el, binding) {
		// el:img dom对象
		// binding.value  图片url地址
		// 使用vueuse/core提供的监听api 对图片dom进行监听 正式进入视口才加载
		// img.src = url
	  
		const { stop } = useIntersectionObserver(
		  // 监听目标元素
		  el,
		  ([{ isIntersecting }], observerElement) => {
			if (isIntersecting) {
			  // 当图片url无效加载失败的时候使用默认图片替代
			  el.onerror = function () {
				el.src = defaltImg
			  }
			  el.src = binding.value
			  stop()
			}
		  })
	  }
	})
  }
}

img v-imgLazy="item.picture" alt=""

JS实现虚拟滚动注意点和代码

滚动条其实是嵌套父盒子里面跟显示元素同级的盒子的滚动条。滚动条的高度是所有元素的高度。

每一个列表的高度固定,父元素的高度固定,相除得出显示元素的个数(slice、map函数),再加上Math.ceil的四舍五入进行,设置滚动的时候不会出现空一截的情况,记住开始开始index和结束位置的index

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>原生JS虚拟滚动列表</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .list-wrapper {
            width: 230px;
            height: 100px;
            border: 1px solid pink;
            margin: 10px auto;
            position: relative;
            overflow-y: scroll;
        }

        .scroll-bar {
            width: 100%;
        }

        .data-box {
            position: absolute;
            left: 0;
            top: 0;
        }

        .list-item {
            width: 100px;
            height: 20px;
            background-color: #ccc;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="list-wrapper" id="listWrapper">
            <div class="scroll-bar" id="scrollBar"></div>
            <div class="data-box" id="dataBox">
                <!-- 列表项将在这里动态生成 -->
            </div>
        </div>
    </div>
    <script>
        (function () {
            const listWrapper = document.getElementById('listWrapper');
            const dataBox = document.getElementById('dataBox');
            const scrollBar = document.getElementById('scrollBar');
            const itemHeight = 20;
            const listWrapperHeight = 100;
            const listData = []
            for (let i = 1; i <= 50; i++) {
                listData.push(i)
            }
            console.log(listData)

            function renderList(startIndex, endIndex) {
                const items = listData.slice(startIndex, endIndex).map(item => `<div class="list-item">${item}</div>`).join('');
                dataBox.innerHTML = items;
            }

            function updateScroll() {
                const scrollTop = listWrapper.scrollTop;
                const scrollCount = Math.floor(scrollTop / itemHeight);
                const startIndex = scrollCount;
                const showCount = Math.ceil(listWrapperHeight / itemHeight);
                const endIndex = startIndex + showCount;
                const startOffset = startIndex * itemHeight;
                dataBox.style.transform = `translate3d(0, ${startOffset}px, 0)`;
                renderList(startIndex, endIndex);
            }

            // 设置 scrollBar 的高度
            const totalHeight = listData.length * itemHeight;
            scrollBar.style.height = `${totalHeight}px`;

            // 初始化渲染
            updateScroll();

            // 监听滚动事件
            listWrapper.addEventListener('scroll', updateScroll);
        })();
    </script>
</body>

</html>

12 聚合支付详解

新西兰项目,微信支付、支付宝、VISA、PayPal、cash,选完订单选择支付方式,后端返回地址,进入地址。如果支付成功,后端返回支付页面(在聚合支付后台可以配置)地址加上完成订单id,前端做判断,如果存在完成订单的id则支付成功,跳到订单详情页面,如果不存在订单id,则用户未支付,再次回到餐厅页面。

13 谷歌地图 多端适配问题

要钱的,公司申请的,apiKey 还有秘钥

pubilc 引进 谷歌地图 获取定位传回app

getlocation

小程序谷歌地图 web-view 传值时,H5和app 无法使用同一个方法: H5 无法使用 web-view 的 @message 方法,而 app 无使用 web-view 存储区。

vue谷歌地图  vue-google-maps 插件

14 海康摄像头

进入官网下载对应的web视频插件,script标签引入js文件

div标签类似echart生成图表的做法,结合WebControl,后端获取每个学校不同区域的摄像头设备号和端口号,进行连接画面

15 Excel导入导出 PDF预览打印

excel导入(使用Element UI的文件上传组件获取文件,并使用xlsx库解析Excel文件内容。)

excel导出可以前端操作(xlsxfile-saver,使用xlsx库生成Excel文件,并使用file-saver库将其保存到用户设备。),也可以后端操作(后端下载文件,然后生成地址的方式)

PDF预览、使用vue-pdf插件或者iframe标签

export function postExportData(data) {
  return request({
    method: 'POST',
    url: '/synthetic/exportTodoMatterList',
    responseType: 'blob',
    data
  }).then(res => {
    console.log(`[res]: `, res)
    // const fileName = res.headers['Content-Disposition'].split('filename=')[1]
    const fileName = '123.xlsx'
    const url = URL.createObjectURL(new Blob([res.data]));
    const link = document.createElement('a');
    link.download = fileName
    link.href = url
    link.click()
  })
}

file(文件流)、bolb(本地流)、base64(二进制流)三者可以互相转换

以下的方法无论是图片、excel文件、PDF都可使用,设置responseType,结合URL.createObjectURL生成链接,然后自点击进行下载

16 小程序支付

 小程序注册微信支付商户号,绑定小程序ID

1 前端调用uniapp登录接口,这个code用户登录凭证,有效期5分钟,可以换取 opendId,unionId,session_key等核心信息

 
uni.login({
    provider: 'weixin', // 使用微信登录
	success: (res) => {
        // 这个code用户登录凭证,有效期5分钟,可以换取 opendId,unionId,session_key等核心信息
		res.code
	}
});

2 发送请求调用微信官方接口,用code凭证换取用户openId

uni.request({
    // 微信官方接口
    url: `https://pi.weixin.qq.com/sns/jscode2session`,
    data: {
        appid: '小程序appid',
        secret: '小程序密钥',
        js_code: '上一步获取的code',
        // 固定值
        grant_type: 'authorization_code',
    },
    success: (res => {
        // 获取openId 用户真实唯一ID
        console.log(res.data.openId)
    })
})

3 调用公司后端接口,获取支付核心数据

uni.request({
    url: `公司后端接口`,
    data: {
        金额:
        订单号:
    },
    method:'POST',
    success: (res => {
        // 获取openId 用户真实唯一ID
        console.log(res.data.openId)
    })
})

4 调用微信官方支付接口,弹出支付页面

wx.requestPayment({
    timeStamp: '时间戳',
    noncesTR: '随机字符',
    package: 'prepay_id',
    signType: 'MD5',
    paySign: '后端返回的签名',
    success(res){},
    fail(res){}
})
17 生成画布分享朋友圈

设置是全局分享功能,如果是点击右上角的三个点得分享功能 onShareAppMessage 分享朋友 onShareTimeline 分享朋友圈

    onShareTimeline(res) { //发送到朋友圈
	onShareAppMessage(res) { //发送给朋友
		// 获取加载的页面
		let pages = getCurrentPages();
		// 获取当前页面的对象
		let view = pages[pages.length - 1];
		//分享的页面路径
		let path = `/${view.route}`;
		let imageUrl = '/static/11111.png'
	
		return {
			title: 'czxml',
			path: path,
			imageUrl: imageUrl,
			success(res) {
				console.log('success(res)==', res);
				uni.showToast({
					title: '分享成功'
				})
			},
			fail(res) {
				console.log('fail(res)==', res);
				uni.showToast({
					title: '分享失败',
					icon: 'none'
				})
			}
		}
	},

如果是通过自己设置得分享按钮 button open-type="share"

<button @click="clickShare" style="width: 100rpx; height: 100rpx; border: none; opacity: 0;" open-type="share"></button>

18 餐桌独立点单和JSBridge 生成餐厅桌椅摆放图

Safari浏览器自带的扫一扫,扫描二维码进入餐桌,传来餐桌ID改变餐桌状态,和生成临时用户信息给后端,结束订单付款的时候,工作人员点击结束订单。

和安卓开发一起联调,给我个地址,使用script标签引入 基本的 JSBridge,当进入制定餐桌页面时,就是我的前端页面,调用JSBridge方法判断有没有登录,有没有权限进行设置,sucess返回状态,有则进行页面展示,使用拖拉拽API和画布生成图片(详解),然后保存给后端。如果没有权限则使用JSBridge.openWindow,打开app本身的登录页面进行登录。

            window.JSBridge.openWindow({
              url: `http://yqt-dev.digitalgd.com.cn/default/#/pages/mine/personal/company/index`
            })

通过设置鼠标的按下、移动、松开事件,再根据鼠标的偏移量,设置元素的偏移。

19 流程问题

新建计划、计划分派、检查方案、检查准备,预通知、检查报告、综合评定报告、结果处置。

20 请求的封装包括不同环境

.env 判断不同坏境:

开发(.env.development)、正式(.env.production)、测试(.env.test)

NODE_ENV = development/production/test
VUE_APP_API_URL = 'http://*****'
VUE_APP_SERVER_URL = '/api'
// 创建axios实例
const request = axios.create({
    baseURL: getBaseUrl(),// 所有的请求地址前缀部分(没有后端请求不用写)
    timeout: 80000, // 请求超时时间(毫秒)
    // headers: {
    // 设置后端需要的传参类型
    // 'Content-Type': 'application/json',
    // 'token': x-auth-token',//一开始就要token
    // 'X-Requested-With': 'XMLHttpRequest',
    // },
})

// request拦截器(请求拦截)
request.interceptors.request.use(
    config => {
        // 如果你要去localStor获取token,(如果你有)
        // let token = localStorage.getItem("x-auth-token");
        // if (token) {
            //添加请求头
            //config.headers["Authorization"]="Bearer "+ token
        // }
        return config
    },
    error => {
        // 对请求错误做些什么
        Promise.reject(error)
    }
)

// response 拦截器(响应拦截)
request.interceptors.response.use(
    response => {
        // 对响应数据做点什么
        return response.data
    },
    error => {  
        // 对响应错误做点什么
        //响应错误
		let message = "";
		if (error.response && error.response.status) {
			const status = error.response.status;
			switch (status) {
				case 404:
					message = "请求地址出错";
					break;
				default:
					message = "请求失败";
			}
			return Promise.reject(error);
		}
	  	return Promise.reject(error);
    }
)

 总结:配置.env文件设置不同环境下的请求地址,创建axios实例,设置请求头,请求地址和请求超时时间限制。设置请求拦截器,关于一些接口,判断系统是否存在token做相对应处理,设置响应拦截,关于接口返回的状态码结合当前组件库做出对应的提示。

前端中断请求的方式 AbortController

21  eChart 

需要定义宽高,找到ID,init元素,setOption,当离开页面时,要去销毁。

import * as echarts from 'echarts';

var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;

option = {
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  },
  yAxis: {
    type: 'value'
  },
  series: [
    {
      data: [150, 230, 224, 218, 135, 147, 260],
      type: 'line'
    }
  ]
};

option && myChart.setOption(option);

佛山市地图

获取地图的GeoJSON,DataV.GeoAtlas地理小工具系列,阿里云数据可视化平台,点击你想要生成的地图省市,生成JSON数据,准备容器,放置JSON数据,进行展示和resize跳转。

22 webscoket 流式读取

fetch 使用 async await 。第一个await等待的是响应头,第二个await等待的是响应体,在第二个await进行操作,结合 getReader 可读器,reader.read() 一小部分一小部分读取(done【false还未读完】和value(值)),TextDecoder解码器,二进制加码成文本。使用while循环。

const url = 'http://xxx.com/posts';

async function getResponse() {
    const resp = await fetch(url, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json'
        }
    });

    const reader = resp.body.getReader();
    const decoder = new TextDecoder();

    while (true) {
        const {
            done,
            value
        } = await reader.read();

        if (done) break;

        const txt = decoder.decode(value);

        console.log(done);
        console.log(txt);
    }
}

getResponse();
23 富文本发布文章在小程序和PC端不兼容问题

PC富文本使用的是 wangEditor 5

小程序video sourse无法播放原因,PC的话 sourse 和 src都能设置视频地址,两者需要判断不同环境进行不同字符串拼接

24 动态设计普查心理问卷和答完问卷正确错误及分数展示

左侧点击生成题目,右侧设计题目类型,标题,必填,分数,排序

每一种题型都做成组件,使用components循环引出。

使用JSON转,字符格式大致如下

{ id: '', type: '', title: '', score: '', required: true, answer: '', option: [], sort: '', }

25 前端拖拽排序

v-drag

JS拖拽

1 设置 元素 属性 draggable="true"

2 找到父元素事件委托,监听拖拽开始事件 ondragstart,添加样式

3 设置当元素被拖拽时,改变元素样式,记得使用setTimeout 控制样式,使得被拖拽出来的本体和留在原地的本体出现样式上的区别

4 ondragenter 拖拽进入,如果进入的父元素或者拖拽元素的本身,则返回;否则将进入的元素和拖拽元素进行位置切换,使用 insertBefore 插入元素

5 当松开鼠标时,dragend 事件 移除样式。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
        .list-item {
            background-color: #266fff;
            border-radius: 5px;
            color: #fff;
            width: 250px;
            padding: 5px;
            margin-bottom: 10px;
        }

        .list-item.moving {
            background-color: transparent;
            color: transparent;
            border: 1px solid #ccc;
        }
    </style>
</head>

<body>
    <div class="list">
        <div draggable="true" class="list-item">1</div>
        <div draggable="true" class="list-item">2</div>
        <div draggable="true" class="list-item">3</div>
        <div draggable="true" class="list-item">4</div>
        <div draggable="true" class="list-item">5</div>
    </div>
</body>
<script>
    // 拖拽排序在很多站点里面是非常常见的,使用的API就是一个拖拽API

    // 1、让元素变的可拖拽   找到这些元素,给这些元素加上 draggable 属性,值为true,这样就变得可拖拽了
    // 2、拖拽的时候样式得变   class为moving的样式
    const list = document.querySelector('.list')

    // 用来记录当前拖拽的是哪个元素
    let sourceNode = null
    // 用事件委托的方式 给父元素绑定事件
    list.addEventListener('dragstart', function (e) {
        
        // e.target 拖的是哪个元素

        // 当拖拽开始的时候要找到拖拽的那个元素给他添加类样式

        // 为什么要用setTimeout,不用的话拖拽的那个元素也会变成虚线(就是添加了类样式后的样子),
        // 它的样式取决于拖拽开始时候元素本身的样式,需要在拖拽开始的时候保持原来的样式 把它变成异步的
        setTimeout(() => {
            e.target.classList.add('moving')
        }, 0)
        sourceNode = e.target
    })

    // 3、什么时候产生排序?拖拽的时候把拖拽对象放到了某些元素之上,这里就要监听拖拽进入事件
    list.addEventListener('dragenter', function (e) {

        // e.target 进入的是哪个元素

        e.preventDefault()
        // 排除掉一些情况,比如拖拽的时候进入了父元素 或者 是本身自己
        if (e.target == list || e.target == sourceNode) return
        const children = [...list.children]
        // 通过所处元素的下标来判断是上方还是下方
        const sourceIndex = children.indexOf(sourceNode)
        const targetIndex = children.indexOf(e.target)
        // console.log(sourceIndex, targetIndex)
        // 4、当拖拽的元素进入到别的元素身上的时候,要做的一些事情
        if (sourceIndex < targetIndex) {
            // console.log('下方')
            // 插入到那个元素下一个元素之前
            list.insertBefore(sourceNode, e.target.nextElementSibling)
        } else {
            //  console.log('上方')
            list.insertBefore(sourceNode, e.target)
        }
        // console.log(sourceIndex)
    })

    // 拖拽完毕 松开鼠标
    list.addEventListener('dragend', function (e) {
        // 移除掉类样式即可
        sourceNode.classList.remove('moving')
    })
</script>

</html>
26 前端文章水印及其防止篡改功能

现在很多组件库都有自己的水印组件 waterMark。

1 获取该要加水印的 div

2 使用canner结合文章盒子大小生成背景水印图片

// canvas 生成 水印背景
watermarkBg() {
    const canvas = document.createElement('canvas');
    const devicePixelRatio = window.devicePixelRatio || 1;
    const fontSize = this.config.fontSize * devicePixelRatio;
    const font = fontSize + 'px "Microsoft YaHei", sans-serif';
    const ctx = canvas.getContext('2d');
    if (!ctx) {
      return null
    }
    ctx.font = font;
    const { width } = ctx.measureText(this.config.text);
    const canvasSize = Math.max(100, width) + this.config.gap * devicePixelRatio
    canvas.width = canvasSize;
    canvas.height = canvasSize;
    ctx.translate(canvasSize / 2, canvasSize / 2);
    ctx.rotate((Math.PI / 180) * 45);
    ctx.fillStyle = 'rgba(0,0,0,0.3)';
    ctx.font = font;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(this.config.text, 0, 0);
    return {
      base64: canvas.toDataURL('image/png'),
      size: canvasSize / devicePixelRatio
    }
}

3 把背景水印图片加到 div 上,重置水印,当有人把水印元素删除或者修改时,重新执行该方法

resetWatermark() {
    if (!this.config.wrapperElement) {
      console.log('未获取到父元素');
      return
    }
    // 由于监听元素变化后会重新创建,此处做判断如果有水印元素则要删除重新创建,防止水印元素重复创建
    if (this.watermarkElement) {
      this.watermarkElement.remove();
    }
    const bg = this.watermarkBg()
    if (!bg) {
      return
    }
    const { base64, size } = bg
    this.watermarkElement = document.createElement('div');
    this.watermarkElement.style.position = 'absolute';
    this.watermarkElement.style.backgroundImage = `url(${base64})`;
    this.watermarkElement.style.backgroundSize = `${size}px ${size}px`;
    this.watermarkElement.style.backgroundRepeat = 'repeat';
    this.watermarkElement.style.zIndex = '9999'
    this.watermarkElement.style.pointerEvents = 'none';
    this.watermarkElement.style.inset = '0'
    this.config.wrapperElement.appendChild(this.watermarkElement);
}

4 使用 MutationObserver 监听用户是否进行篡改水印 DOM,如果篡改,进行水印重置

  // 监听操作变化
  const ob = new MutationObserver((entries) => {
    for (const entry of entries) {
      //  删除
      for(const dom of entry.removedNodes) {
        if(dom === div) {
          console.log('水印被删除');
          resetWatermark();
          return;
        }
      }
      // 修改
      if(entry.target === div) {
          console.log('水印被修改');
          resetWatermark();
          return;
      }
    }
  })

完整代码

<template>
  <div class="watermark-container" ref="parentRef">
    <slot></slot>
  </div>
</template>
 
<script setup>
  import useWatermarkBg from './useWatermarkBg';
  import defineProps, { ref, onMounted, onUnmounted } from 'vue'
  const props = defineProps({
    text: {
      type: String,
      required: true,
      default: 'watermark'
    },
    fontSize: {
      type: Number,
      default: 40
    },
    gap: {
      type: Number,
      default: 20
    },
  });
  const bg = useWatermarkBg(props);
 
  const parentRef = ref(null);
  let div;
  // 重置水印
  function resetWatermark() {
    if(!parentRef.value) return;
    if (div) {
      div.remove();
    }
    const { base64, size } = bg.value;
    div = document.createElement('div');
    div.style.position = 'absolute';
    div.style.backgroundImage = `url(${base64})`;
    div.style.backgroundSize = `${size}px ${size}px`;
    div.style.backgroundRepeat = 'repeat';
    div.style.zIndex = 9999;
    div.style.inset = 0;
    parentRef.value.appendChild(div);
  }
  onMounted(resetWatermark);
  
  // 监听操作变化
  const ob = new MutationObserver((entries) => {
    for (const entry of entries) {
      //  删除
      for(const dom of entry.removedNodes) {
        if(dom === div) {
          console.log('水印被删除');
          resetWatermark();
          return;
        }
      }
      // 修改
      if(entry.target === div) {
          console.log('水印被修改');
          resetWatermark();
          return;
      }
    }
  })
 
  onMounted(() => {
    ob.observe(parentRef.value, {
      childList: true,
      subtree: true,
      attributes: true,
    })
  })
  onUnmounted(() => {
    // 取消监听
    ob.disconnect();
  })
</script>
 
<style scoped>
  .watermark-container {
    position: relative;
  }
</style>
27 多套语言系统的引入

设置多套js进行存储不同数据,页面根据用户设置的语言进行选择展示。( i18n 插件

28 为什么要用webSocket?

实时通信(低延迟、双向通信)
减少网络开销(持久连接、更小的传输数据量)

node 包管理工具(npm,cnpm,nvm,pnpm,yarn)

买个服务器作为代码仓库(registry),在里面放所有需要被共享的代码 发邮件通知 jQuery、Bootstrap、Underscore 作者使用 npm publish 把代码提交到 registry 上,分别取名 jquery、bootstrap 和 underscore(注意大小写) 社区里的其他人如果想使用这些代码,就把 jquery、bootstrap 和 underscore 写到 package.json 里,然后运行 npm install ,npm 就会帮他们下载代码 下载完的代码出现在 node_modules 目录里,可以随意使用了

npm 是 Node.js 的默认包管理工具,用于安装、管理和发布 JavaScript 模块。它是一个强大的工具,支持管理项目依赖、执行脚本、发布包等功能。

cnpm(China npm) 是 npm 的镜像,专为中国用户优化,使用 cnpm 可以加速 npm 的包下载和安装。使用的是国内的镜像源。

nvm (Node Version Manager)是 Node.js 的版本管理工具,允许用户在同一台机器上安装和切换多个 Node.js 版本。可以用于在不同项目中使用不同的 Node.js 版本,同时避免全局环境的版本冲突。

pnpm 是一个快速、节省磁盘空间的包管理器,与 npm 和 yarn 不同,它采用硬链接和符号链接的方式共享依赖。可以减少依赖的重复安装,节省磁盘空间,并且速度比传统的 npm 和 yarn 快。

yarn 是 Facebook 开发的包管理工具,旨在解决 npm 的一些性能和安全问题。它使用并行安装和缓存等策略来提高速度,并且支持离线安装和版本锁定等功能。

不要在一个项目上使用多种安装方式,不然会有兼容性报错风险。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2222706.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++进阶】之C++11的简单介绍(二)

&#x1f4c3;博客主页&#xff1a; 小镇敲码人 &#x1f49a;代码仓库&#xff0c;欢迎访问 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f30f; 任尔江湖满血骨&#xff0c;我自踏雪寻梅香。 万千浮云遮碧…

C#使用log4net结合sqlite数据库记录日志

0 前言 为什么要把日志存到数据库里? 因为结构化的数据库存储的日志信息,可以写专门的软件读取历史日志信息,通过各种条件筛选,可操作性极大增强,有这方面需求的开发人员可以考虑。 为什么选择SQLite? 轻量级数据库,免安装,数据库的常用的基本功能都有,可以随程序…

批量修改YOLO格式的标注类别

1.解决的问题 假如你有一个YOLO格式的数据集&#xff0c;标注类别为0&#xff0c;1&#xff0c;2&#xff0c;3四个类别标签。如果你想删除标签1&#xff0c;只保留0&#xff0c;2&#xff0c;3类别的标注信息&#xff0c;或者想将标签0和标签1合并为标签1&#xff0c;只剩下标…

第三届“奇安信杯”网络安全技能竞赛 部分题目WriteUP

第三届“奇安信杯”网络安全技能竞赛WriteUP 文章目录 第三届“奇安信杯”网络安全技能竞赛WriteUPMISCGIFpycseeyouagain CRYPTObase全家桶base6432rsa WEB MISC GIF 下载附件&#xff0c;解压得到test1.jpg。 用010 Editor打开&#xff0c;发现GIF文件头&#xff0c;修改文…

从“Hello World”到“Success” —— 1024程序员节的感悟与成长

目录 1.成为程序员 2.成长之路 3.困难与挑战 4.磨炼与前进 5.总结与收获 6.感悟 1.成为程序员 今天&#xff0c;我们迎来了专属于程序员的节日——1024程序员节。这一天不仅是对所有编程爱好者的致敬&#xff0c;更是回顾过去一年来成长历程的时刻。对于每一位踏上编程之旅…

AI带货主播如何打造真实视觉效果!

AI带货主播作为新兴的数字营销手段&#xff0c;正在逐步改变着电商行业的面貌&#xff0c;AI技术的不断进步使得带货主播能够以更加真实、生动的视觉效果展现在消费者面前&#xff0c;从而大大提升了购物体验和销售转化率。 那么&#xff0c;AI带货主播如何打造真实视觉效果呢…

深入浅出神经网络:从基础原理到高级应用

第5章 神经网络 更加详细内容可以看这篇文章 5.1 神经元模型 神经网络的基本单元是神经元模型。神经元模拟了生物神经元的行为&#xff0c;通过接收输入信号&#xff0c;进行加权求和&#xff0c;然后经过激活函数输出结果。 数学上&#xff0c;一个简单的神经元可以表示为&…

业务开发常见问题-并发工具类

hello&#xff0c;大家好&#xff0c;本讲我们一起聊一下常见的几个并发工具类的使用和坑&#xff01; 在日常工作中&#xff0c;我们经常会遇到多线程并发问题&#xff0c;比如ThreadLocal、锁、ConcurrentHashMap、CopyOnWriteArrayList等。那么如何正常的使用呢&#xff1f;…

P7400 [COCI2020-2021#5] Magenta 题解

#1024程序员节&#xff5c;征文# 人生中的第二道紫题。。。 题目传送门 解题思路 下文中的距离指的是 a , b a,b a,b 之间的边的数量。 Sub 2 即所有边 Paula 与 Marin 都可以行走。 根据题意 Paula 先手。因此&#xff0c;如果一开始 Paula 动不了&#xff0c;那么 M…

浏览器的渲染过程

文章目录 什么是浏览器的渲染&#xff1f;浏览器渲染过程面试问点&#xff1a;为什么操作DOM慢&#xff1f;回流与重绘那么&#xff0c;什么情况下会触发回流&#xff1f; 浏览器的优化 什么是浏览器的渲染&#xff1f; 简单的说就是浏览器将 HTML 代码解析出来&#xff0c;把…

轻松学会!回收站数据恢复的几种妙招

回收站数据恢复方法是一个涉及计算机操作和数据安全的重要话题。在日常使用电脑的过程中&#xff0c;我们经常会遇到误删文件或清空回收站的情况&#xff0c;导致重要数据丢失。幸运的是&#xff0c;有多种方法可以尝试恢复这些丢失的数据。以下将详细介绍几种常见的回收站数据…

C++: C/C++内存管理

前言 本篇博客将详细介绍C的内存管理 &#x1f496; 个人主页&#xff1a;熬夜写代码的小蔡 &#x1f5a5; 文章专栏&#xff1a;C 若有问题 评论区见 &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 ​ 一.C/C内存分布 让我们先来看看下面的代码吧 int globalVar 1; st…

【植物识别系统】Python+人工智能+深度学习+卷积神经网络算法+TensorFlow+算法模型+Django网页界面平台

一、介绍 植物识别系统&#xff0c;使用Python作为主要编程语言开发&#xff0c;通过收集常见的6中植物树叶&#xff08;‘广玉兰’, ‘杜鹃’, ‘梧桐’, ‘樟叶’, ‘芭蕉’, ‘银杏’&#xff09;图片作为数据集&#xff0c;然后使用TensorFlow搭建ResNet50算法网络模型&am…

C++:模板的特化与分离编译

之前我们在介绍模板的时候仅仅是简单的介绍了模板的用法&#xff0c;本篇文章我们来详细的介绍下模板中比较重要的几个点。 一&#xff0c;非类型模板参数 我们之前的c中&#xff0c;会将经常使用的而又确保在我们程序的运行过程中值不会改变的值进行#define&#xff1a; #d…

Unity Apple Vision Pro 保姆级开发教程-环境配置、导入 PolySpatial 案例、程序发布到设备

视频教程 Unity 环境配置、导入 PolySpatial 案例、程序发布到设备 Unity Vision Pro 中文课堂教程地址&#xff1a; Unity3D Vision Pro 开发教程【保姆级】 | Unity 中文课堂 教程说明 这期教程我将介绍使用 Unity 开发 Apple Vision Pro 应用所需要的 Unity 环境配置&…

055_基于python摄影平台交流系统

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

Android compose 重建流程1

前言 本文是笔者学习Compose是如何自动触发UI刷新的笔记,可能缺乏一定可读性和教导性.(建议阅读参考文献更具启发性) 使用以下BOM作为研究环境. composeBom "2024.04.01" androidx-compose-bom { group "androidx.compose", name "compose-bom…

实习冲刺Day2

算法题 反转链表 206. 反转链表 - 力扣&#xff08;LeetCode&#xff09; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}*…

AI大模型应用(3)开源框架Vanna: 利用RAG方法做Text2SQL任务

AI大模型应用(3)开源框架Vanna: 利用RAG方法做Text2SQL任务 RAG&#xff08;Retrieval-Augmented Generation&#xff0c;如下图所示&#xff09;检索增强生成&#xff0c;即大模型LLM在回答问题时&#xff0c;会先从大量的文档中检索出相关信息&#xff0c;然后基于这些检索出…

【LLaMA-Facrory】【模型评估】:代码能力评估——Qwen-Coder-7B 和 deepseek-coder-7b-base-v1.5

目录 序言 1 拉取 Qwen2.5-Coder-7B 模型 2 编写python测试模型 3 启动webui导入模型测试 4 模型评估 4.1 前期准备工作 4.2 Qwen2.5-Coder-7B 模型评估 数据说明 综合分析 4.3 deepseek-coder-7b-base-v1.5 模型评估 数据说明 综合分析 4.4 模型比较 1. 文本生成…