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中的修饰符
- 事件修饰符
.stop 阻止冒泡 .prevent 组织默认行为 .once 事件只会触发一次
- 按键修饰符
.keyup 键盘抬起 .keydown 键盘按下
- 鼠标修饰符
.left 鼠标左键 .right 鼠标右键 .middle 鼠标中键
- 表单修饰符
.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模式区别
-
hash的路由地址上有 #号, history模式没有
-
hash模式支持低版本浏览器, history不支持, 因为是H5新增的API
-
hash模式利用的是锚点 history借助 history 对象中的 pushState() 函数重写 URL 路径, 都是通过控制页面的display: none 属性
-
hash模式简单,部署容易,不需要服务器端的配置。history需要
-
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 } })
三种导航守卫
- 全局守卫(Global Before Guards) 写在 router/index.js 文件中的全局
用于全局的路由控制逻辑,如登录验证、权限控制等。
- 路由守卫(Per-Route Guards) 写在 router/index.js 文件中的const routes = [] 中的每一个路由内
用于特定路由或路由群组的控制逻辑,如权限验证、数据预加载等。
- 组件守卫(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 强制刷新
-
localtion.reload() // 跟按F5一样 不会利用缓存,直接重新加载 浪费性能
-
this.$router.go(0) // 跟按F5一样 不会利用缓存,直接重新加载 浪费性能
-
找个空白页过渡一下 // 地址闪动效果,页面闪动
-
利用 provider inject, 在孙组件中就可以直接调用祖先组件的方法, 进行刷新页面, 推荐
其本质上是通过控制 app.vue 中 router-view 标签设置 v-if='show' ,控制他先消失再显示
因为是 刷新按钮 一般不在 app.vue 文件,而是在其子孙组件中,所以使用到 provider 和 inject
computed和watch
-
computed是计算属性,data中无定义的数据,computed的值,可以直接使用;watch是监听, 监听的是data中数据的变化
-
computed是支持缓存, 依赖的属性值发生变化, 计算属性才会重新计算, 否则用缓存; watch不支持缓存
-
computed不支持异步, watch是可以异步操作
-
computed是第一次加载就监听, watch不监听( 有 deep: true // 开启深度侦听,immediate: true // 立马监听)
-
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
- 占位符 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 区别
-
表现形式不同: hooks 是在 utils 的基础上再包一层组件级别的东西(钩子函数等); utils 一般用于封装相应的逻辑函数, 没有组件的东西;
-
数据是否具有响应式: hooks 中如果涉及到 ref, reactive, computed 这些 api 的数据, 是具有响应式的; 而 utils 只是单纯提取公共方法就不具备响应式;
-
作用范围不同: hooks 封装, 可以将组件的状态和生命周期方法提取出来, 并在多个组件之间共享和重用; utils 通常是指一些辅助函数或工具方法, 用于实现一些常见的操作或提供特定功能。
v-if v-show v-for
v-if和v-show的区别(都可以控制元素的显示和隐藏)
-
v-show时控制元素的display值来让元素显示和隐藏; v-if显示隐藏时把DOM元素整个添加和删除
-
v-if有一个局部编译 / 卸载的过程, 切换这个过程中会适当的销毁和重建内部的事件监听和子组件; v-show只是简单的css切换
-
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导出可以前端操作(xlsx
和file-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 的一些性能和安全问题。它使用并行安装和缓存等策略来提高速度,并且支持离线安装和版本锁定等功能。
不要在一个项目上使用多种安装方式,不然会有兼容性报错风险。