0、Vite搭建项目
B站学习视频
使用 NPM安装Vite: npm create vite@latest
vue渐进式
渐进式框架是指,我们可以根据自己的项目的需求,选择不同部件来构建一个完整的框架。 例如状态管理我可以使用pinia,也可以使用vuex。这种分层可选的灵活方式就是“渐进式”
简述MVC, MVVM的区别
MVVM是一种架构模式,实现了数据的双向绑定。无论用户更新View还是Model,另一个都能跟着自动更新。他是由Model
, View
, 和ViewModel
三部分构成。
Model
数据模型:用来修改数据【JSON、Array、String】;
View
视图界面:就是用户看到的浏览器界面;
ViewModel
绑定器:用来同步view和model的一个对象。
MVC模式,中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验
1、Vue指令(setup语法糖)
当使用 <script setup>
构建组件的时候,我们不再需要把外面定义的方法return出去。任何在 <script setup>
声明的顶层绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用:
<template>
<Breadcrumb :breadcrumbState="breadcrumbState"/>
</template>
<script setup>
import { reactive, ref, watch, onMounted } from 'vue'
//这里我们引入了子组件 面包屑导航[子组件]
import Breadcrumb from '../../components/Breadcrumb/index.vue'
const breadcrumbState = reactive({ tit1: "需求申请", tit2: "新建工单", })
onMounted(() => {
// 组件挂载完成后执行
})
</script>
<style lang="less" scoped>
<!-- 只作用于当前组件中的元素 -->
</style>
Vue3中ref和reactive区别
在vue3中对数据进行响应式的声明,有ref
和reactive/riˈæktɪv/
俩种方式。ref
可以对String、Array、Number类型进行声明,并用.value
赋予新的值,除此之外ref
还可以获取dom元素。而reactive
只能对一个对象进行响应式代理。
ref
声明数据类型:数字、字符串、数组reactive
只能声明数据类型:对象
const count = ref(10) // ref 声明数据类型:数字、字符串、数组
const web = reactive({ // reactive 声明数据类型:对象
title: '上证指数',
year: 3018
})
const onClick = () => {
count.value++ // ref 修改属性值,加个 .value
}
const onYear = () => {
web.year = 3019 // reactive 修改属性值,
}
ref获取dom元素
// vue2 写法
<script>
export default {
mounted(){
console.info("获取dom元素", this.$refs['bar']);
},
}
</script>
<template>
<h3 ref="bar">《帝国崛起:华夏觉醒》</h3>
</template>
// vue3 写法
<script setup lang="ts">
import { ref } from 'vue'
const msg = ref(null)
function handleDom() {
console.log('获取dom元素', msg.value)
}
</script>
<template>
<h2 ref="msg">《帝国崛起:民族战线》</h2>
<button @click="handleDom">获取dom元素</button>
</template>
id获取dom元素
// vue3 写法
<script setup lang="ts">
function handleDom() {
console.log('获取dom元素', document.getElementById('msg'))
}
</script>
<template>
<h2 id="msg">《帝国崛起:民族战线》</h2>
<button @click="handleDom">获取dom元素</button>
</template>
v-if, v-show 的区别
在我们页面渲染的时候,如果这个组件的显示和隐藏是一次性决定,后面不会再修改的话用v-if
。如果说我们的这个元素,需要经常的切换显示和隐藏的话v-show
,因为v-show
的显示隐藏是操作了css的属性display: none
1.v-if能在<template>/ˈtempleɪt/特恩噗累特
上用,而v-show不行。
watch 侦听器
作用: 用于侦听一个或者多个数据
的变化,一旦数据变化时执行回调函数进行其他操作
有三个额外参数:
-
immediate
/ɪˈmiːdiət/ 依眯跌特
(立即执行)。当值设置为 true 时,那么被监控的对象在初始化时就会触发 -
deep (深度侦听)。当侦听对象的时候,明明数据修改了,却没有侦听的提示,这个时候,需要我们开启深度侦听。因为watch默认可以浅层侦听
const state = ref(0)
,若是侦听的是对象属性const state = ref({ count: 0 })
就不会触发回调执行。但deep底层是递归,影响性能不建议使用 -
once (单次侦听)。vue3.4版本新增功能,once:true
computed计算属性,watch侦听器 的区别
-
computed/kəmˈpjuːtɪd/肯piu忒d
是计算用的,当一个数据的值,需要通过某些逻辑,或多个数据计算而来。那么就需要用到computed。最典型的就是购物车结算时的总金额。 -
watch是监听用的,如果是一条数据更改,影响多条数据时,就需要用watch。watch监听有两个可选属性,分别是
immediate/ɪˈmiːdiət/衣咪跌特
和deep/diːp/帝泼
,当immediate
为true时是组件加载立即触发回调函数,deep
是深度监听,当监听对象的时候,明明内部数据发生了修改,却没有监听提示时,我们需要开启深度监听。使用场景搜索框。
watch和watchEffect异同?
-
watch 和 watchEffect 都能监听响应式数据的变化,不同的是它们监听数据变化的方式不同。
-
watch 会明确监听某一个响应数据,而 watchEffect 则是隐式的监听回调函数中响应数据。
-
watch 在数据初始化时,不会执行回调函数的,watchEffect 则会立即执行回调函数。
vue3写法(watch)
监听单个属性
<script setup lang="ts">
// 1.导入watch
import { ref, watch } from 'vue'
const count = ref(0)
const setCount = () => {
count.value++
}
// 2.调用watch 侦听变化
watch( count, (newVal, oldVal) => {
console.log(`count发生变化,新值${newVal},老值${oldVal}`)
}, {
immediate: true // 立即执行
})
</script>
<template>
<div class="card">
<button type="button" @click="setCount">count is {{ count }}</button>
</div>
</template>
监听多个属性
说明:同时侦听多个响应式数据的变化,不管哪个数据变化都需要执行回调
<script setup lang="ts">
// 1.导入watch
import { ref, watch } from 'vue'
const count = ref(0)
const name = ref("柴西")
const setCount = () => {
count.value++
}
// 2.调用watch 侦听变化
watch( [count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`count发生变化,新值${[newCount, newName]},老值${[oldCount, oldName]}`)
})
</script>
<template>
<div class="card">
<button type="button" @click="setCount">count is {{ count }}</button>
</div>
</template>
deep (深度侦听)
默认机制:通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep选项
<script setup lang="ts">
// 1.导入watch
import { ref, watch } from 'vue'
const state = ref({count:0})
const setCount = () => {
state.value.count++
}
// 2.调用watch 侦听变化
watch( state, () => {
console.log(`默认浅层侦听,打印不到回调函数,需要deep`)
}, {
deep: true
})
</script>
<template>
<div class="card">
<button type="button" @click="setCount">count is {{ state.count }}</button>
</div>
</template>
精准侦听
<script setup lang="ts">
// 1.导入watch
import { ref, watch } from 'vue'
const state = ref({
name: '超carry的柴西',
age: 18 // 只在`age`变化时,执行回调函数
})
const setCount = () => {
state.value.age++
}
const setAge = () => { state.value.name = '小柴西' }
// 2.调用watch 精准侦听变化
watch( () => state.value.age, () => {
console.log(`精准侦听,只有点击age,才能打印回调函数`)
})
</script>
<template>
<div class="card">
<button type="button" @click="setCount">count is {{ state.age }}</button>
<button type="button" @click="setAge">count is {{ state.name }}</button>
</div>
</template>
vue2写法(watch)
watch: {
// 监听器:当城市、日期、子导航、发生变化时,重新调用接口
Map_cityName(newVal, oldVal) {
console.log('JJJ1', newVal, oldVal)
},
},
nextTick的使用
概念
/neks'tɪk/ 耐克斯'忒克nextTick
主要用于当数据动态变化后,dom还未及时更新的问题
在vue里面,我们的dom更新是异步执行的。它就相当于我们在修改数据的时候,视图层并不是立即更新,而是会监听数据的一个变化,并且缓存在同一个事件循环当中。只有等同一数据循环中的所有数据变化完成之后,才会进行统一的视图更新。我们经常会在dom元素还没更新的时候,就使用了某个元素,这样是拿不到变更后的dom的。为了确保我们能获取到数据循环之后的dom。所以我们设置了nextTick
方法。这个api的用处是:在修改了数据以后,立即使用这个方法获取更新后的dom。
nextTick使用的场景
只有在这种document.getElementById('counter')
的旧写法才会出现数据动态变化后,dom还未及时更新的问题
- created组件创建后的生命周期,想要获取dom时
- 获取列表更新后的高度
- 添加input框,并获取焦点
代码示例
// vue官网,vue2写法
<script>
import { nextTick } from 'vue'
export default {
data() {
return {
count: 0
}
},
methods: {
async increment() {
this.count++
// DOM 还未更新
console.log(document.getElementById('counter').textContent) // 0
await nextTick()
// DOM 此时已经更新
console.log(document.getElementById('counter').textContent) // 1
}
}
}
</script>
<template>
<button id="counter" @click="increment">{{ count }}</button>
</template>
// vue3 写法
// 添加input框,并获取焦点
<script setup lang='ts'>
import { nextTick, ref } from 'vue'
const isShow = ref(false)
async function addInput() {
isShow.value = true
await nextTick(() => {
document.getElementById('test')?.focus()
})
}
</script>
<template>
<input v-if="isShow" id='test' />
<button @click="addInput">添加input,获取焦点</button>
</template>
// vue3 写法二
// 获取列表更新后的高度
<script setup lang='ts'>
import { nextTick, ref } from 'vue'
const arr = ref(['AA', 'BB', 'CC'])
async function addEE() {
arr.value.push('EE')
console.log('len数_arr.value.length', arr.value.length) // 4
console.log("len数_nextTick()前", document.getElementById('test')?.children.length) // 3
await nextTick()
console.log("len数_nextTick()后", document.getElementById('test')?.children.length) // 4
}
</script>
<template>
<ul id="test">
<li v-for="(item, index) in arr" :key="index">{{ item }}</li>
</ul>
<button @click="addEE">添加EE</button>
</template>
watch 代码示例
watch
: 监听器用于监听数据是否被修改,一旦修改就可以执行一些其他的操作
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
import { ref, reactive, watch } from 'vue'
watch(data, (newVal, oldVal) => {
// ...
}, {immediate: true, deep: true})
注意:第一个参数是一个箭头函数
watch 的第三个参数:
- deep: 深度监听。当监听对象的时候,明明数据修改了,却没有监听的提示,这个时候,需要我们开启深度监控, { deep: true }
- immediate:作用就是设置是否立即执行监控,当值设置为 true 时,那么被监控的对象在初始化时就会触发一次 watch 方法。
这里改成俩个,
一、监听ref声明の变量,数组
const name = ref('握奇')
const age = ref(21)
// 监听单个普通类型
watch(name, (newVal, oldVal) => {
console.log(newVal)
})
// 监听多个普通类型,返回数组
watch([name, age], (newVal, oldVal) => {
console.log(newVal)
})
二、监听reactive声明の整个对象
但是当监听对象的时候,明明数据修改了,却没有监听的提示,这个时候,需要我们开启深度监控,{ deep: true }
const person = reactive({
name: '握奇',
infos: {
age: 21,
address: '上海'
}
})
// 监听对象person时,vue3将强制开启deep深度监听
watch(person, (newVal, oldVal) => {
console.log(newVal)
})
watch(() => person, (newVal, oldVal) => {
console.log(newVal)
}, { deep: true })
两种实现方式效果相同,只要对象中有任何变化就会触发watch方法
三、监听对象中的属性
const person = reactive({
name: '握奇',
infos: {
age: 21,
address: '上海'
}
})
// 只有当person对象中的name属性发生变化才会触发watch方法
watch(() => person.name, (newVal, oldVal) => {
console.log(newVal)
})
// 注意:监听对象的属性为复杂数据类型时,需要开启deep深度监听
watch(() => person.infos, (newVal, oldVal) => {
console.log(newVal)
}, { deep: true })
2、生命周期
概念
生命周期
: 在人生这条单行线中,每一个年龄阶段需要做的一些事情,例如3-6上幼儿园、20-30的时候结婚、60岁的时候退休。每一个时间节点都要做固定的事情,把这些事情组合在一起就是生命周期。
vue3变化
Vue2の生命周期共分为8个阶段,创建前后,载入前后,更新前后,销毁前后
ue3の生命周期没有created
和beforeCreate
。并且setup
在最前执行
vue2 | vue3 | 作用 | |
---|---|---|---|
最最最最最最最最前面执行 | -- | setup | -- |
组件创建前 | beforeCreate | -- | 此时 data数据 和 methods方法 还未完成初始化,无法调用。也不能获得DOM节点。无任何软用 |
组件创建后 | created | -- | 此时 data数据 和 methods方法 已经完成初始化,可以调用。但是模板还没有编译,还不能获取到 DOM节点 |
组件加载前 | beforeMount | onBeforeMount | 此时 数据已渲染出来,模板进行编译,会调用 render 函数生成虚拟DOM,但还是无法获取真实 DOM节点。可以访问接口数据 |
组件加载后 | mounted | onMounted | 此时 数据和DOM都已被渲染出来,也就是我们页面可以显示了。一般我们的异步请求都写在这里。 |
组件更新前 | beforeUpdate | onBeforeUpdate | 此时 data数据已更新,DOM节点已获取,但变化后的数据还未渲染到页面之上。view层没更新 |
组件更新后 | updated | onUpdated | 此时 data数据已更新,DOM节点已获取,但变化后的数据还未渲染到页面之上。view层已更新 |
组件卸载前 | beforeUnmount | onBeforeUnmount | 组件还没有被销毁,还可以正常使用。可用于解除一些定时器或订阅的取消 |
组件卸载后 | unmounted | onUnmount | 实例销毁之后执行,且 dom 完全销毁 |
写法区别
beforeMount(){
// vue2 写法
}
onBeforeMount(() => {
// vue3 写法,并且可以写多个
})
详细用法
实例代码:
// vue2 写法
<script>
import { useRouter } from "vue-router";
export default {
data(){
return { msg: '《帝国崛起:民族战线》' }
},
beforeCreate(){
console.info("-----beforeCreate-----"); // [vue3 已删除] 此时 data数据 和 methods方法 还未完成初始化,无法调用。也不能获得DOM节点。无任何软用
console.info("A1.组件创建前_data", this.msg);
console.info("A1.组件创建前_dom", this.$refs['bar']);
},
created() {
console.info("-----created-----"); // [vue3 已删除] 此时 data数据 和 methods方法 已经完成初始化,可以调用。但是模板还没有编译,还不能获取到 DOM节点
console.info("A2.组件创建后_data", this.msg);
console.info("A2.组件创建后_dom", this.$refs['bar']);
},
beforeMount(){
console.info("-----beforeMount-----"); // 此时 数据已渲染出来,模板进行编译,会调用 render 函数生成虚拟DOM,但还是无法获取真实 DOM节点。
console.info("B1.组件加载前_data", this.msg);
console.info("B1.组件加载前_dom", this.$refs['bar']); // 可以访问各种数据、获取接口数据
},
mounted(){
console.info("-----mounted-----"); // 此时 数据和DOM都已被渲染出来,也就是我们页面可以显示了。
console.info("B2.组件加载后_data", this.msg);
console.info("B2.组件加载后_dom", this.$refs['bar']); // 一般我们的异步请求都写在这里。
},
beforeUpdate() {
console.info("-----beforeUpdate-----"); // 此时 data数据已更新,DOM节点已获取,但变化后的数据还未渲染到页面之上。view层没更新
console.info("C1.组件更新前_data", this.msg);
console.info("C1.组件更新前_dom", this.$refs['bar']);
debugger
},
updated() {
console.info("-----updated-----"); // 此时 data数据已更新,DOM节点已获取,此时变化后的数据已经渲染到页面之上。view层已更新
console.info("C2.组件更新后_data", this.msg);
console.info("C2.组件更新前_dom", this.$refs['bar']);
},
beforeUnmount() {
console.info("-----beforeUnmount-----"); // 此时 组件还没有被销毁,还可以正常使用。可用于解除一些定时器或订阅的取消
console.info("D1.组件卸载前_data", this.msg);
console.info("D1.组件卸载前_dom", this.$refs['bar']);
debugger
},
unmounted() {
console.info("-----unmounted-----"); // 组件 实例销毁之后执行,且 dom 完全销毁。
console.info("D2.组件卸载后_data", this.msg);
console.info("D2.组件卸载后_dom", this.$refs['bar']);
},
methods: {
changeMsg() {
// 点击按钮,组件更新
this.msg = "同志们,向西太平洋,出发!";
},
toRouter() {
// 跳转页面,组件销毁
this.$router.push("/Lifecycle");
}
}
}
</script>
<template>
<h3 ref="bar" @click="changeMsg">{{ msg }}</h3>
<button @click="toRouter">跳转页面,执行组件卸载</button>
</template>
输出结果:
- beforeCreate[组件创建前]
- created[组件创建后]
- beforeMount[组件加载前]
- mounted[组件加载后]
- beforeUpdate ----> [组件更新前]
- updated ----> [组件更新后]
- beforeUnmount ----> [组件卸载前]
- unmounted ----> [组件卸载后]
父子组件生命周期执行顺序
3、组件通信
vue2组件通信
父组件
<div id="demo">
<input type="text" v-model="name">
<!--子组件传递过来的参数由自定义事件绑定的方法接收-->
<demo-child :nameFather="name" @newData="newName"></demo-child>
</div>
import DemoChild from '@/components/DemoChild'
export default {
data(){
return {
name:'nihao'
}
},
components:{
DemoChild
},
methods:{
//因此参数value是 '这是子组件传递过来的数据'
newName(value){
this.name=value
}
}
}
子组件
<template>
<div id="demo_child">
<p @click="change">DemoChild组件:{{nameFather}}</p>
</div>
</template>
export default {
//接收属性nameFather
props:{
nameFather: String
},
methods:{
change(){
//触发父组件中的自定义事件newData , this.$emit('需要触发的自定义事件' [,'需要传递的参数'])
this.$emit('newData','这是子组件传递过来的数据')
}
}
}
vue3组件通信
defineProps() 和 defineEmits()
父传子【defineProps】 ----> [子组件通过props,接收父组件的传参]
子传父【defineEmit】 ----> [子组件自定义事件,可以通过emit传递传参到父组件]
defineExpose()
暴露子组件属性【defineExpose】 ----> [父组件通过Expose,获取子组件定义的一些属性或函数事件]
顶底组件 provide 和 inject
顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信
provide/prəˈvaɪd/ 破肉ˈ外得
,inject/ɪnˈdʒekt/ 因ˈ在克特
,
传递数据
传递事件
WebStorage存储
localStorage
和 sessionStorage
都是浏览器提供的用于在客户端存储数据的机制,它们的主要区别如下:
(1)localStorage:数据是持久化存储的,除非被手动清除,否则数据会一直存在,即使关闭浏览器窗口、重启电脑等操作后数据依然保留。
(2)sessionStorage:数据仅在当前会话期间有效。当浏览器窗口或标签页关闭时,存储的数据将被清除。
//保存数据有3种方法:
sessionStorage.setItem("key","value");
//或
sessionStorage.key="value";
//或
sessionStorage["key"]="value";
//读取数据的3种方法,将数据赋值给变量v
var v=sessionStorage.getItem("key");
//或
var v=sessionStorage.key;
//或
var v=sessionStorage["key"];
//移除
sessionStorage.removeItem("key");
//删除所有数据
sessionStorage.clear()
4、状态管理
Json 假数据渲染
Pinia 安装 & 配置
Pinia 是 Vue 的专属的最新状态管理库 ,是 Vuex 状态管理工具的替代品
-
1.提供更加简单的API (去掉了 mutation )
-
2.提供符合组合式风格的API (和 Vue3 新语法统一
-
3.去掉了 modules 的概念,每一个 store 都是一个独立的模块
-
4.搭配 TypeScript 一起使用提供可靠的类型推断
Pinia 是 Vue 的存储库(官方插件),它允许您跨组件/页面共享状态,不用再通过父组件向子组件传值。
npm install pinia
安装完成后我们需要将pinia挂载到Vue应用中。修改main.js,引入pinia提供的createPinia方法,创建根存储。
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia"; // 复制这里
const pinia = createPinia(); // 复制这里
const app = createApp(App);
app.use(pinia); // 复制这里
app.mount("#app");
定义store
store共有3种内容:{ 变量:state 计算属性:getter 函数:action }
// 文件路径:/src/store/user.ts
import { defineStore } from 'pinia'
// 组件名 xxxStore 在父组件引入时会用到. xxx 没啥用 必须写并且不能重名
export const xxxStore = defineStore( 'xxx', {
// 变量 state
state: () => {
return {
age: 25,
fruitList: ['菠萝', '草莓', '樱桃']
}
},
// 计算属性 getters
getters: {
},
// 函数 actions
actions: {
ageClick() {
this.age++
}
}
})
这个例子后期要更加详细
定义store - vue2
pinia-vue2示例代码
<template>
<h3>年龄: {{ age }}</h3>
<h3>年龄: {{ gettersAge }}</h3>
<button @click="addAge">加一岁</button>
</template>
<script setup>
import { storeToRefs } from 'pinia';
import { useXxxStore } from '../pinia/index.js';
// 变量、计算属性需要写在 storeToRefs() , 函数则不需要
const { age, gettersAge } = storeToRefs(useXxxStore())
const { addAge } = useXxxStore()
</script>
import { defineStore } from 'pinia'
/**
* defineStore() 该函数接收两个参数:
* name:一个字符串,必传项,该store的唯一id
* options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。
*/
// vue2 の写法
export const useXxxStore = defineStore( 'Xxx', {
// 变量 state
state: () => {
// return 防止数据污染
return {
age: 25,
}
},
// 计算属性 getters
getters: {
gettersAge(state) {
return state.age + 5
}
},
// 函数 actions
actions: {
addAge() {
this.age++
}
}
})
定义store - vue3 setup
pinia-vue3示例代码
<template>
<h3>年龄: {{ xxxStore.age }}</h3>
<h3>年龄: {{ xxxStore.gettersAge }}</h3>
<button @click="xxxStore.addAge">加一岁</button>
</template>
<script setup>
import { useXxxStore } from '../pinia/index.js';
const xxxStore = useXxxStore()
</script>
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// vue3 setupの写法
/**
* ref() 就是 state 属性
* computed() 就是 getters
* function() 就是 actions
*/
export const useXxxStore = defineStore( 'Xxx', () => {
const age = ref(25)
const gettersAge = computed(() => {
return age.value + 5
})
function addAge(params) {
age.value++
}
return { age, gettersAge, addAge }
})
State基本使用
State批量修改数据。示例代码
Getters基本使用
getter中调用其它getter
getter传参
Actions基本使用
async \ await 访问接口
import { defineStore } from 'pinia'
import request from '@/utils/request'
// Axios. API商店
export const apiStore = defineStore('apiStore', {
state: () => {
return {
dataSelectAll: [], // API商店 列表数据
dataSelectAllByUser: [], // 我的订阅 列表数据
dataRuDetail: [], // 查看服务示例 列表数据
WaySorting: [
{ value: '1', label: '订阅次数升序' },
{ value: '2', label: '订阅次数降序' },
{ value: '3', label: '浏览次数升序' },
{ value: '4', label: '浏览次数降序' },
], // 排序方式
ApiType: [
{ value: '', label: '全部' },
{ value: '1', label: '使用中' },
{ value: '0', label: '待审核' },
{ value: '-1', label: '已过期' },
], // API状态
saveInfoSuccess: false,
ruDetailSuccess: false,
sqlContent: '', // json编辑器里の数据
}
},
actions: {
/**
* Axios. API商店
* @param {number} page // 当前页
* @param {number} limit // 每页几条
* @param {string} apiName // API名称
* @param {string} orderType // 排序方式
* @param {string} startTime // 服务有效期-开始
* @param {string} endTime // 服务有效期-结束
*
*/
async runSelectAll(params) {
const { data } = await request.post(`/api/apirubaseinfo/selectAll`, params )
console.log('API商店', data)
this.dataSelectAll = data
},
/**
* Axios. 我的订阅
* @param {number} page // 当前页
* @param {number} limit // 每页几条
* @param {string} apiName // API名称
* @param {string} apiStatus // API状态
* @param {string} startTime // 服务有效期-开始
* @param {string} endTime // 服务有效期-结束
*/
async runSelectAllByUser(params) {
const { data } = await request.post(`/api/apirubaseinfo/selectAllByUser`, params )
this.dataSelectAllByUser = data
},
/**
* Axios. API商店 订阅
* @param {string} apiId // api Id
* @param {string} apiName // API名称
* @param {string} apiValidPeriod // 有效期
* @param {string} assigneeId // 第一级审批人 Id [暂时还没有,传空]
* @param {string} remark // 描述
* @param {string} validPeriod // 密钥有效期
*/
async runSaveInfo(params) {
const { data, message } = await request.post(`/api/apisubscriptionrecord/saveInfo`, {
"apiId": params.apiId,
"apiName": params.apiName,
"apiValidPeriod": params.validPeriod,
"assigneeId": '',
"remark": params.serviceDesc,
"validPeriod": params.keyValidPeriod,
})
// this.runSelectAll()
this.saveInfoSuccess = message
// console.log('API商店 订阅', data, message)
},
/**
* 查看服务示例
* @param {string} apiId // api Id
*/
async runRuDetail(params) {
const { data, success } = await request.post(`/api/apihibaseinfo/ruDetail`, {
apiId: params.apiId
})
this.dataRuDetail = data
this.sqlContent = JSON.stringify(data.returnExample, null, 2)
console.log('查看服务示例', data, data.returnExample )
},
}
})
多个store的使用
vueX 安装 & 配置
2.将所有的状态都写在 store文件里,main.js全局引用这个store文件
pina 和 vuex的比较
4、vue3-Router4路由
因为vue是单页应用不会有那么多html, 所有要使用路由做页面的跳转
router4 安装 & 配置
使用Vue3 安装对应的router4版本
使用Vue2 安装对应的router3版本
// 安装路由 vue-router
yarn add vue-router
// # 或
npm install vue-router
// 可以在package.json文件中查看版本
1.在main.ts中导入
代码示例
// main.ts 挂载
import { createApp } from 'vue'
import App from './App.vue'
import vueRouter from './router'
// 注意use要在mount之前
createApp(App).use(vueRouter).mount('#app')
2.在app.vue 添加路由占位符router-view
3.新建router文件夹,创建index.js 该截图是 history路由模式
代码示例
import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router';
const routes:Array<RouteRecordRaw> = [ // RouteRecordRaw 类型, 接收参数:path 和 component。此二为必传项
{
path: '/',
name: '生命周期',
component: () => import('../components/HelloWorld.vue')
},
{
path: '/NextTick',
component: () => import('../components/NextTick.vue')
},
]
const router = createRouter({
history: createWebHistory(), // 路由的模式啊
routes // 存放 router 信息的
})
export default router
hash & history 概念
vue2 | vue3 |
---|---|
history | createWebHistory |
hash | createWebHashHistory |
abstact | createMemoryHistory |
hash
—— 在hash模式下 url带了一个很丑的 # ,前端路由修改的是#中的信息,而浏览器请求时不会将 # 后面的数据发送到后台,对后端完全没有影响,因此改变 hash 不会重新加载页面。
history
—— 丢掉了丑陋的#,但是它也有个问题:不怕前进,不怕后退,就怕刷新,f5,(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的。如果服务器中没有相应的响应或者资源,则会刷新出来404页面。
路由跳转 & 传参
使用push路由跳转,使用query路由传参
// 路由跳转,传递动态参数
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const toPage = () => {
/**
* push 路由跳转时,携带历史记录
* replace 路由跳转时,没有历史记录
*/
router.push({
path: '/details',
query: {
msg: '《帝国崛起:民族战线》'
}
})
}
</script>
<template>
<button @click="toPage">详情页</button>
</template>
接受参数,使用 useRoute 的 query
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute()
</script>
<template>
<div>{{ route.query?.msg }}</div>
</template>
路由嵌套
路由-前置导航守卫
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('../components/Login.vue')
},
{
path: '/Home',
component: () => import('../components/Home.vue'),
children: [
{
path: '/Aaa',
component: () => import('../components/Aaa.vue')
},
{
path: '/Bbb',
component: () => import('../components/Bbb.vue')
},
]
},
]
const router = createRouter({
history: createWebHistory(), // 路由的模式啊
routes // 存放 router 信息的
})
// 白名单
const whileList = ['/', '/Ccc']
// 路由-前置导航
router.beforeEach(( to, form, next) => {
/**
* 如果你跳转的地址,在我的白名单里,或者你有token,我允许你跳
* 否则返回 '/' 登录页
*/
if (whileList.includes(to.path) || localStorage.getItem('token')) {
next()
} else {
next('/')
}
})
export default router
路由重定向
{
name: '路由重定向',
path: '/',
component: Home,
redirect: '/Home', // redirect 就是路由重定向
},
5、Axios二次封装
axios安装 && 配置
npm i axios
// package.json查看版本 "axios": "^1.3.3",
// 无需在main文件中全局配置,按需引用即可 import axios from 'axios'
requset.js文件 封装axios,写一些拦截器,请求头,超时时间
基础请求get、post
<script setup>
import axios from 'axios'
function getHandle() {
/**
* params 传递到服务器端的数据,拼接在url地址的后面
* headers 表示请求头 里面可以写 Authorization:token
*/
// get请求
axios.get(`https://api.vvhan.com/api/hotlist?type=${'bili'}`)
axios.get('https://api.vvhan.com/api/hotlist', {
params: {
type: 'bili'
},
headers: {}
})
// post请求
axios.post('https://api.vvhan.com/api/hotlist', {
type: 'bili' // post 请求体写法
}, {
params: {}, // post请求 也可以将入参拼接在url地址的后面
headers: {}
})
}
</script>
<template>
<button @click="getHandle">get请求</button>
</template>
// 测试post接口
axios.post('http://8.130.106.81:58084/bonchqdec/radarImport/selectRadarKpiList')
.then(function (response) {
console.log(response);
})
axios请求封装
src下创建utils/request.js文件
示例代码
import axios from 'axios';
const service = axios.create({
baseURL: "https://api.vvhan.com",
timeout: 5000,
// headers: headers
})
export default service
全局拦截
import axios from 'axios';
const service = axios.create({
baseURL: "https://api.vvhan.com",
timeout: 5000,
// headers: headers
})
//请求拦截器: 在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
service.interceptors.request.use(config => {
// 在发送请求之前做一些事情
if (localStorage.getItem('token')) {
config.headers['Authorization'] = localStorage.getItem('token')
}
return config;
},error => {
// 处理请求错误
return Promise.reject(error);
});
//响应拦截器:包含两个函数(一个是成功返回的函数,一个是失败的返回的函数)
service.interceptors.response.use(response => {
// 在发送响应之前做一些事情
if (response.status !== 200) {
console.log('你接口又报错倆')
}
return response;
},error => {
// 做一些响应错误的事情
return Promise.reject(error);
});
export default service
token & cookie
token
token的意思是令牌,是用户第一次登录时,服务器生成的一段加密字符串,然后返回给客户端(浏览器),后面客户端每次向服务端请求资源的时候,只需要带上token,不用再带着用户名和密码去请求。为什么要带token呢,是因为用户登录成功后,后续需要反复到服务端去获取数据,服务器对每一次请求,都要去验证是哪位用户发送的请求,这样返回请求数据库,会对数据库造成压力。当后续请求都带上token后,服务器直接解密token,就可以知道用户的相关信息,省去了查询数据库的操作,减轻数据库的压力,这就是token的作用。一般成功登录第一次后,会把token存在sessionStorage中, 并在请求拦截器统一封装,让每一个请求都带上token。
cookie
withCredentials/krəˈdenʃ(ə)lz/位置ˈ克抡笨搜滋
: true, // 允许携带cookie
const service = axios.create({
baseURL: 'http://172.16.82.15:8081/approveService',
withCredentials: true, // 允许携带cookie
timeout: 50000 // 请求超时时间
})
6、api文档工具
Apifox
7、[JS]数组方法
map 和 forEach区别?
共同点
- 只能循环数组,并遍历数组中的每一项。
- 支持3个参数,
- 数组中的当前项 item,
- 当前项的下标索引 index,
- 原始数组 array
区别
- forEach() 没有返回值,会改变原数组
- map()方法返回一个新数组,原数组不变
map
forEach
for..in 和 for..of 区别?
日常工作中主要是处理,循环数组、循环对象、循环数组对象
区别 | ||
---|---|---|
for..in | 遍历得到数组中的下标索引 index | 可枚举的数据:数组、字符串、对象 |
for..of | 遍历得到数组中的每一项 item | 可迭代的数据:数组、字符串、Set、Map |
示例代码
/**
* 循环数组
* for..in循环出来的是数组中的下标索引 index。
* for..of循环出来的是数组中的每一项 item。
*/
const arr = ['a', 'b', 'c']
for (const key in arr) {
// for..in循环出来的是数组中的下标索引 index
console.log('数组[for..in]', key) // 0,1,2
}
for (const val of arr) {
// for..of循环出来的是数组中的每一项 item。
console.log('数组[for..of]', val) // a,b,c
}
/**
* 循环对象
* for..in循环用于遍历对象的属性名、和属性值
* for..of报错,不能循环对象
*/
const obj = {
name: '张三',
age: 18,
}
for (const key in obj) {
// for..in 循环用于遍历对象的属性名、和属性值
console.log('对象[for..in]', key) // name, age
console.log('对象[for..in]', obj[key]) // '张三', 18
}
for (const val of obj) {
// for..of 循环对象会报错
console.log('对象[for..of]', val) // 报错了!
}
replace()替换
replace() 替换字符串的某元素,并返回替换后的字符串
var str = "《帝国崛起:民族战线》"
str.replace(/民族战线/, '12') // '《帝国崛起:12》'
split()截取
把字符串分割为字符串数组
var time = "2023-02-28T11:25:00.075Z"
time.split('T')[0] // '2023-02-28'
time.split('T')[1] // '11:25:00.075Z'
8、[JS]八股文面试题
数据类型
JS的8个数据类型
【前端面试JS】JS的8个数据类型重点讲解es11新增的bigint类型
基本数据类型(7个):number、string、boolean、null、undefined、symbol、bigInt
引用数据类型(1个):object,里面包含有Math对象,数组对象、正则对象、函数(Function)
-
基本数据类型(7个)
- 数字 number
- 字符串 string
- 布尔 boolean
- 对象为空不存在的,转为数值时为0 null
- 变量已经声明,但是没有赋值 undefined
- ES6新增的 symbol
- ES11新增的 bigInt
-
引用数据类型(1个)
-
对象数据类型 object,object里面又包含如下对象等
- 普通对象 {}
- 数组对象 []
- 正则对象 /^$/
- 日期对象 new Date
- 数学函数对象 Math
- 函数对象 Function
-
JS数据类型4种判断方法
【前端面试JS】判断JS数据类型4种方法每种方法优缺点总结和检测原理
- 使用
typeof
操作符:typeof操作符可以返回某个值的数据类型
typeof 42; // 返回 "number"
typeof 'hello'; // 返回 "string"
typeof true; // 返回 "boolean"
typeof {}; // 返回 "object"
typeof function(){}; // 返回 "function"
typeof undefined; // 返回 "undefined"
- 使用
Object.prototype.toString
方法:通过调用Object.prototype.toString方法,可以得到一个对象的具体类型。
Object.prototype.toString.call(42); // 返回 "[object Number]"
Object.prototype.toString.call('hello'); // 返回 "[object String]"
- 使用
instanceof
操作符:instanceof操作符可以检测一个对象是否属于某个特定的类型
42 instanceof Number; // 返回 false
'hello' instanceof String; // 返回 false
- 使用
constructor
/kənˈstrʌktər/肯死抓克特儿
属性:constructor属性指向创建该对象的构造函数。通过判断对象的constructor属性,可以得到对象的具体类型
(42).constructor; // 返回 Number
'hello'.constructor; // 返回 String
判断一个对象为空的方法
- JSON.stringify(),这个方法可以将json对象,转换为json字符串。我们只需要判断序列化后的对象,是否等于字符号花括号即可 '{}'【推荐】
- Object.keys(),这个方法会把对象中的属性名取出来,以数组形式返回,我们只需要判断这个数组的长度是否为0,如果为0,说明该对象中一个属性都没有,也就是说他是个空对象。
- Object.getOwnPropertyNames(),判断方法同上
- Reflect.ownKeys(),
/rɪˈflekt//əʊn/ 瑞福来克特,欧恩Key
,判断方法同上【推荐】
- for..in遍历对象
变量、作用域、函数提升
# 纯干货 js变量和函数提升动画详解 作用域以及var和let区别
JS作用域
全局作用域 任何地方都可以访问的变量
// 全局作用域:任何地方都可以访问的变量
var a = "全局变量"
function Mona () {
console.log(a)
}
函数作用域 只在当前函数体内可以访问
// 函数作用域:只在当前函数体内可以访问
function Mona () {
var a = "函数体内变量"
console.log(a)
}
块级作用域 通常是指一对大括号包裹的代码片段if、for、while
{
let a = 22
const b = 33
}
JS的三种声明方式,let、const、var的区别
(1)用于块级作用域: let和const具有块级作用域,var声明的变量有于函数作用域或全局作用域。
(2)重复定义变量: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(3)先声明后赋值: 在变量声明时,var 和 let 可以先声明后赋值,默认初始值是undefined。const声明变量必须设置初始值,赋值后不可更改。
区别 | var | let | const |
---|---|---|---|
能否用于块级作用域 | × | ✔️ | ✔️ |
能否重复定义变量 | ✔️ | × | × |
能否先声明后赋值 | ✔️ | ✔️ | × |
变量提升
console.log(a) // undefined
var a = 1
// 在执行代码时,Js会将变量的声明,提升到当前作用域顶部,并且是为赋值状态,所以会输出undefined
// 按照如下代码执行顺序
var a
console.log(a) // undefined
a = 1
函数提升
console.log(Mona)
var Mona = 1
console.log(Mona)
function Mona () {
return '666'
}
console.log(Mona)
// 在同一作用域,如果函数名、变量名相同。变量声明会被函数声名覆盖
// 按照如下代码执行顺序
function Mona () {
return '666'
}
console.log(Mona) // 输出 function Mona()
Mona = 1
console.log(Mona) // 输出 1
console.log(Mona) // 输出 1
for 循环机制,含setTimeout
for 循环机制
for (let i = 0; i < 5; i++) {
// 循环体
// console.log(i) // 1、2、3、4
}
/**
* 执行的顺序如下:
* 第一步 : i=0 初始化值
* 第二步 : i<5 进行条件判断,如果为真,则继续执行
* 第三步 : 执行循环体的内容
* 第四步 : i++ 变量i自增
* 第五步 : 回到第二步,条件判断为真,则执行循环体内容,再到i++一直循环,
* 直到第二步的判断条件为假,则退出该循环
*/
for(条件1;条件2;条件3){
循环体4
}
**执行的循环: 1243 243 243 ....直到条件2为假则退出循环**
面试题
var a = 10
function A() {
console.log('1', a) // undefined
var a = 20
console.log('2', a) // 20
for (var a = 1; a < 5; a++) {
setTimeout(() => {
console.log('3', a) // 4个 5。setTimeout为异步最后执行
}, 10);
}
}
A()
console.log('4', a) // 10
答案解析
for (var i = 0; i < 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)
}
setTimeout是异步执行,10ms后往任务队列里面添加一个任务,只有主线上的全部执行完,才会执行任务队列里的任务,当主线执行完成后,i是10,所以此时再去执行任务队列里的任务时,i全部是10了。 对于打印10次是:每一次for循环的时候,settimeout都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里面,等待执行,for循环了4次,就放了4次,当主线程执行完成后,才进入任务队列里面执行。
for (let i = 0; i < 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)
}
因为for循环头部的let不仅将i绑定到for循环快中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过 var 定义的变量是无法传入到这个函数执行域中的,通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 function就能使用 i 这个变量了;这个匿名函数的参数作用域 和 for参数的作用域 不一样,是利用了这一点来完成的。这个匿名函数的作用域有点类似类的属性,是可以被内层方法使用的。
JS中构造函数、回调函数、箭头函数
回调函数
回调函数就是普通函数,当函数作为参数传递给另一个函数,作为参数传递的函数就改名叫回调函数了。
汉堡制作函数 v3.0 -- 多种汉堡[回调函数示例]
// 汉堡制作函数 v1.0 -- 一种汉堡
function Hamburger_v1 () {
console.log('1.烤面包')
console.log('准备牛肉饼 + 酱料配菜')
console.log('食材叠加,组装汉堡🍔')
}
Hamburger_v1()
// 汉堡制作函数 v2.0 -- 多种汉堡
function Hamburger_v2 (type) {
console.log('2.烤面包')
if(type === '牛肉堡') {
console.log('准备牛肉饼🥩 + 酱料配菜')
} else if (type === '鸡腿堡') {
console.log('准备鸡腿肉🍗 + 酱料配菜')
} else if (type === '龙虾堡') {
console.log('加入6只小龙虾🦐 + 酱料配菜')
}
console.log('食材叠加,组装汉堡🍔')
}
Hamburger_v2('牛肉堡')
Hamburger_v2('鸡腿堡')
Hamburger_v2('龙虾堡')
// 汉堡制作函数 v3.0 -- 多种汉堡[回调函数]
function beef() {
console.log('准备牛肉饼🥩 + 酱料配菜')
}
function chicken() {
console.log('准备鸡腿肉🍗 + 酱料配菜')
}
function prawn() {
console.log('加入6只小龙虾🦐 + 酱料配菜')
}
function Hamburger_v3 (type) {
console.log('3.烤面包')
type()
console.log('食材叠加,组装汉堡🍔')
}
Hamburger_v3(beef)
Hamburger_v3(chicken)
Hamburger_v3(prawn)
JS中this相关问题
js的this指向
熟练掌握JS函数相关体系,包括但不限于构造函数、回调函数、箭头函数等等
箭头函数与普通函数的区别
语法简洁性:
- 箭头函数的语法更为简洁,可以在一些情况下省略大括号、return 关键字和参数括号
- 普通函数的语法相对繁琐,需要使用 function 关键字、大括号和参数括号
this 绑定:
- 箭头函数的 this 绑定是词法作用域的,它会捕获当前上下文中的 this 值,无法通过 call、apply 或bind 改变。
- 普通函数的 this 绑定是动态的,取决于函数的调用方式和上下文。
arguments 对象:
- 箭头函数没有自己的 arguments 对象,它会继承外层作用域的 arguments 对象(如果有的话)
- 普通函数具有自己的 arguments 对象,其中包含了函数调用时传递的参数。
构造函数:
- 无法通过 new 关键字实例化对象箭头函数不能用作构造函数
- 普通函数可以用作构造函数,可以通过 new 关键字实例化对象
- 写法不同,箭头函数使用箭头定义,写法简洁。 普通函数使用function定义。
- 箭头函数都是匿名函数,而普通函数既可以是匿名函数,也可以是具名函数。
- 箭头函数不能作为构造函数来使用,普通函数可以用作构造函数,以此来创建一个对象的实例。
- this指向不同,箭头函数没有this,在声明的时候,捕获上下文的this供自己使用,一旦确定不会再变化。在普通函数中,this指向调用自己的对象,如果用在构造函数,this指向创建的对象实例。普通函数可以使用call,apply
- 箭头函数没有arguments(实参列表,类数组对象),每一个普通函数在调用后都有一个arguments对象,用来存储实际传递的参数。
- 箭头函数没有原型,而普通函数有。
继承
原型&原型链
在JS中,每一个函数都自带一个 prototype 属性,我们就把 函数名.prototype 称为原型
。也叫“显式原型”。当我们打印 函数名.prototype
时,打印出来的这个对象,称作原型对象
/**
* 什么是原型?
* 在JS中,每一个函数都自带一个 prototype 属性,我们就把 函数名.prototype 称为原型。也叫“显式原型”
* 当我们打印 函数名.prototype 时,打印出来的这个对象,称作原型对象
*
* 原型对象 默认又有俩个属性:
* 1.constructor(构造器): 指向构造函数本身
* 2.__Proto__(隐式原型):指向其上一级的原型
*
* [[prototype]]其实就是__proto__ 因为各大浏览器厂家取名不同
*/
// 1.构造函数
function MM(name) {
this.name = name
}
// 2.实例化 - 创建对象
const mm = new MM('哈妮克孜')
// 3.打印
console.log(mm) // 实例化对象mm
console.log(MM.prototype) // 原型对象
console.log(MM.prototype.constructor === MM) // true => 指向函数本身
console.log(MM.prototype === mm.__proto__) // true =>
// __proto__和prototype不太一样,
// __proto__是对象拥有的隐式原型,prototype是函数拥有的显式原型
JS中闭包、防抖截流
闭包【概念题】
3W1H,这个东西是什么。在哪里用,为什么这么用,以及怎么去用
闭包是指能够访问另一个函数作用域中变量的一个函数,简单来讲,在我们JS当中只有函数内部的子函数才能访问局部变量,所以闭包可以理解为,定义在一个函数内部中的函数,就是闭包。
使用闭包的主要目的,实现JS中的封装,因为js没有强类型语言的三大基本特征,封装继承多态。所以想要在js中封装就可以使用闭包,设置一些私有的方法跟变量。
优点:避免全局变量的污染,因为在全局作用域去定义一个变量,就会出现变量污染的问题,使用闭包就不会了、
缺点:闭包会常驻在我们的内存中,会增大内存使用量,使用不当就会出现内存泄漏
闭包的特征:1.函数嵌套函数。2.函数内部可以引用外部函数中的这个参数跟变量。3.参数和变量不会被js的垃圾回收机制回收。
闭包主要用于防抖截流
内存泄漏
函数在形成闭包的这个过程中,就会产生内存泄漏。只需要在使用完这个函数,把函数内的变量销毁掉,就会避免内存泄漏
防抖和节流
是性能优化
防抖[回城]: 用户在短时间内多次触发事件时,只取最后一次触发结果。60秒短信
节流[金身]: 用户在短时间内多次触发事件时,将次数压缩一次,取决于定时器设定的时间
区别 | 共同点 | 区别 | 应用场景 |
---|---|---|---|
防抖 debounce | 在事件频繁被触发时 | 只执行最后一次 | input搜索框、scroll滚动条,减少http请求 |
节流 throttle | 减少事件执行次数 | 有规律的执行 | 60秒短信 |
防抖
// 输入框内容变化时的回调
function inputSearch_onChange(params) {
// 获取输入框 value
debounce(function () {
console.log('打印', params.target.value)
// ...执行 Axios 调取数据
})
}
// 防抖
let timer = null
function debounce(fn) {
if (timer) clearTimeout(timer) // 规定时间内若定时器存在则清除
timer = setTimeout(() => {
fn()
}, 1000);
}
节流
function inputSearch_onSearch(params) {
throttle(function () {
console.log('点击搜索按钮', params)
})
}
// 节流
let flag = true
function throttle(fn) {
if(flag) {
setTimeout(() => {
console.log('触发点击')
fn() // 调用接口
flag = true // 在定时器执行后 移除if阻断
}, 1000);
}
flag = false // 在执行一次后 if阻断定时器继续执行
}
图片预加载和懒加载
当页面中有很多图片的时候,图片加载就需要一定的时间,不仅会影响渲染速度,还浪费服务器性能和带宽。而懒加载就是优先加载可视区的内容,其他内容等进入了可视区域再进行加载。我们要实现懒加载需要做到2个步骤,1.如何加载图片,2.如何判断进入可视区域。先说加载图片,我们知道图片都是根据图片标签上的src属性进行加载的,所以在图片进入可视区域前,我们先不给src属性赋值,或者给一个很小的loading图地址,等到图片进入可视区域后再给src附上真正的地址。再来看图片是否进入了可视区域。
方法一.IntersectionObserver
有个方法叫做交叉观察器,intersectionObserver。/ˌɪntəˈsekʃn//əbˈzɜːvə(r)/.音特塞克伸,额波蜇窝
,这个api可以自动观察一个元素是否可见或者两个元素是否相交,一般用于实现图片懒加载、内容无限滚动等功能
代码示例
// 自定义指令 v-lazy
export default {
mounted(el) {
console.log('el', el)
const imgSrc = el.src // 复制图片src地址
el.src = '' // 默认将src清空
// 交叉观察者 API
const lazyLoad = new IntersectionObserver((entries) => {
// 当元素出现在可视区域, 和离开可视区域被触发
console.log('entries', entries[0].isIntersecting)
if (entries[0].isIntersecting) {
// isIntersecting 为 true 说明进入可视区域
el.src = imgSrc // 进入可视区域 还原sec地址,实现懒加载
// 停止观察
lazyLoad.unobserve()
}
});
lazyLoad.observe(el)
}
}
方法二. vue-lazyload
安装vue-lazyload插件,在main.js 中全局引入,然后在图片元素src属性名称写成v-lazy即可
简述vue2, vue3的区别
1.双向数据绑定的原理【核心】
vue2 首先,Vue通过Object.defineProperty()/ˈprɑːpərti/ 普绕ˈ破忒
方法对数据进行劫持,监听数据的变化,并通过getter和setter方法对数据进行读写。当数据发生变化时,Vue会通知所有订阅者进行更新,使得数据和视图完成双向数据绑定。并且Object.defineProperty()方法会遍历所有对象属性,无论是否用到,它都会运行一遍,徒增内存消耗。
补充:订阅者是Vue中的一个概念,每一个挂载到视图上的组件,都可以被看成一个订阅者。当数据发生变化时,Vue会通知所有的订阅者进行更新。
vue3 通过ES6的Proxy代理的方式/ˈprɒːksi/ 普绕ˈ克西依
,拦截对象中的属性变化,通过Reflect/rɪˈflekt/ 瑞ˈ福来克特
反射函数进行读写、添加、删除等操作。vue3可以用ES module按需引入/ˈmɑːdʒuːl/猫灸
,减少内存消耗,优化用户体验。
2.根标签
vue2 vue2中的template/ˈtempleɪt/ 泰母ˈ泼累特
模板,在最外层必须要有一个根标签,根节点
vue3 但在vue3有个fragement的/ˈfræɡmənt/ 福ˈ赖哥闷特
虚拟标签。支持放置多个根标签,根节点
3.API类型不一样
vue2 选项式API,数据放在data里、方法放在method里/ˈmeθədz/咩色滋
。watch和computed也要单独存放。需要多个对象来描述组件的逻辑。
vue3 组合式API,有一个setup函数,定义变量用ref和reactive,而方法可以直接写到setup函数中。可以将一个功能的所有代码放到一块,代码简洁,便于维护。
补充:ref
声明数据类型:数字、字符串、数组。reactive
只能声明数据类型:对象
4.生命周期
vue3的生命周期相对于vue2的少了beforeCreate和created,而多了setup函数来替代。 Vue3的生命周期前面多了一个ˈonˈ关键字。 并且Vue3中可以重复写同一个生命周期函数,例如写上好几份onMounted(()=>{})
补充:beforeCreate组件创建前、created组件创建后
5.组件通信
vue2
- 父传子[props]: 子组件通过props,接收父组件的传参
- 子传父[$emit]: 子组件自定义事件,可以通过emit传递传参到父组件,父组件监听事件来接收参数
vue3
Vue3的组件通信前面多了一个ˈdefineˈ关键字。分别是defineProps() 和 defineEmits()。
还新增了跨层组件通信,顶层组件向任意的底层组件传递数据和方法。顶层组件通过provide/prəˈvaɪd/ 破肉ˈ外得
函数提供数据,底层组件通过inject/ɪnˈdʒekt/ 因ˈ在克特
函数获取数据。
6.vuex 和pinia的区别
vue2 vuex是一个公共的状态管理工具,通常用于管理一些公共的状态。还可以用于给俩个毫不相关组件,进行值的传递。vuex有五个核心
- state 储存公共状态,或者说是公共的数据
- getters,处理state数据后的一些结果,类似于computed
- mutations 用于更改state数据
- actions 处理一些异步任务,就是接口请求
- modules 项目过大时,要分模块去开发,每个模块有自己独立的状态管理,解释:可能你A模块要用到state里的name数据,B模块也要用到name数据,我们要把他用在不同的模块中。
使用方法:组件中使用Vuex的state数据用this.store.$state
、使用mutations的一个方法,用this.store.$commit
,用actions的一个方法,用this.store.$dispatch
。开发过程中还会使用辅助函数,比方说mapState、mapMutations、mapActions。来快速操作state、getters、mutations以及actions中的方法和属性
vue3 pinia最重要的是,搭配 TypeScript 一起使用
pinia 没有 mutations,而actions的使用不同,在actions中可以处理同步也可以处理异步,getters的使用是一致的。
pinia 没有总出口全是模块化,需要定义模块名称,当多个模块需要协作的时候需要引入多个模块,vuex是有总入口的,在使用模块化的时候不需要引入多个模块。
pinia 在修改状态的时候不需要通过其他api,vuex需要通过commit,dispatch去修改所以在语法上比vuex更容易理解和使用,灵活
7.构建工具vite、webpack
vue2 webpack
- 构建方式:webpack需要分析整个项目依赖,进行多次文件扫描和转译。尤其在大型项目中,构建会慢一点。
- 开发模式:webpack的是HMR热模块替换,来实现开发,配置复杂。
- 插件系统:webpack时间久远,拥有庞大的生态插件系统。
- 编译方式:webpack使用了有多种加载器来处理多种不同类型的资源。
- 总结:webpack灵活强大,适用于大型复杂需要自定义配置的项目,构建速度慢,配置复杂,插件生态庞大
vue3 Vite
- 构建方式:Vite特点是轻量级,速度快,极速构建。它是利用ES模块中的一些特性去构建一些正在编译的文件,而不是整个项目,这样使得在开发环境下vite的构建是你写到哪里,它就构建到哪里。
- 开发模式:Vite也支持HMR热模块替换,无需任何配置,默认支持。
- 插件系统:vite插件少。
- 编译方式:vite使用原生浏览器导入来处理模块不需要大规模编译打包。
- 总结:Vite适合快速搭建小应用场景,速度快,0配置启动,原声ES模块支持,插件少
8.diff算法
vue2 vue2中的diff算法是遍历每一个虚拟节点,进行虚拟节点的对比。并返回一个patch对象,用来存储俩个节点不同的地方。用patch去记录一个消息去更新dom。缺点是对于不更新的元素,有一定的性能消耗。
vue3 vue3给每一个虚拟节点添加一个动态标识patchflag/派吃ˈ福赖哥/
,去比较标识发生变化的节点,进行一个视图更新,标识没有变化的元素,在渲染的时候就直接复用
深浅拷贝
深浅拷贝只适用于对象。
-
浅拷贝: 假设B复制了A,当修改A时,B也跟着变。这说明只拷贝了指针,AB实际上还是共用的一份数据。
-
深拷贝: 假设B复制了A,当修改A时,A变,B没有变。复制对象不受原对象的影响,因为不仅拷贝了指针,还拷贝了内存。各自内容互相独立。
浅拷贝实现
把变量A直接赋值给变量B就是浅拷贝
<script type="text/javascript">
const obj1 = {
name: '张三',
age: 18,
address: { city: '北京' },
nobby: [ '台球', '篮球']
}
const obj2 = obj1 // 浅拷贝
obj2.address.city = '上海'
console.log('obj1', obj1)
console.log('obj2', obj2)
</script>
深拷贝实现
4种方法
(1) JSON.parse(JSON.stringify(obj))
js内置的JSON序列化和反序列化方法 缺点: 不能存放函数,不支持循环引用、不支持时间对象和正则
<script type="text/javascript">
const obj1 = {
name: '张三',
age: 18,
address: { city: '北京' },
fn: function name(params) {
},
nobby: [ '台球', '篮球'],
}
// const obj2 = obj1 // 浅拷贝
const obj2 = deepClone(obj1) // 深拷贝
obj2.address.city = '上海'
console.log('obj1', obj1)
console.log('obj2', obj2)
// 方法一:JSON序列化
function deepClone(params) {
return JSON.parse(JSON.stringify(params))
}
</script>
(2) structuredClone()推荐
B站视频.# ES11深拷贝有新方法了-structuredClone
structuredClone
/ˈstrʌktʃərd//kloʊn/ 斯抓克特儿得,克隆恩
优点:支持循环引用、支持时间对象和正则
缺点: 不能存放函数
(3) 递归
优点:对于任何类型的对象都有效,包括循环引用。
缺点:对于大型对象可能会消耗大量内存,并可能导致堆栈溢出。
<script type="text/javascript">
const obj1 = {
name: '张三',
age: 18,
address: { city: '北京' },
fn: function name(obj) {
},
nobby: [ '台球', '篮球'],
}
// const obj2 = obj1 // 浅拷贝
const obj2 = deepClone(obj1) // 深拷贝
obj2.age = 20
obj2.address.city = '上海'
console.log('obj1', obj1)
console.log('obj2', obj2)
// 方法二:递归
function deepClone(obj) {
if (typeof(obj) !== 'object' || typeof(obj) === null) {
// 如果他不是一个对象,或者压根儿就是一个null。我们也没有必要处理它了
return obj
}
// instanceof 判断基本数据类型的方法
let res = obj instanceof Array ? [] : {}
// for in
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
res[key] = deepClone(obj[key])
}
}
return res
}
</script>
(4) lodash.cloneDeep
优点:支持更多类型的对象和库,例如,支持 Proxy 对象。
缺点:会引入依赖导致项目体积增大。
const _ = require('lodash');
function deepClone(obj) {
return _.cloneDeep(obj);
}
跨域、同源策略、HTTP协议
什么是跨域、同源策略、HTTP协议
-
HTTP协议:包括浏览器(客户端)、服务器(服务端)两个实体。当我们想访问某篇博文时。客户端会发送请求
(例如 https://www.bilibili.com)
给服务器,服务器收到API
会解析他们,并返回浏览器相应的数据资源。 -
同源策略:是为了防止恶意脚本窃取数据。浏览器制定了
同源策略(即协议、域名和端口号)
https 协议、www.bilibili.com域名 、 8080 端口号 -
跨域:协议、域名和端口号,三者之间任意一个不同就是跨域。
http和https到底有什么区别?
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
解决跨域
(1) 本地代理
本地代理,它是在浏览器和服务器中间,添加了一个代理的中转站。每次发起请求和返回数据时都要通过这个中转站。
浏览器 -》代理 -》中转站
vue.config.js文件里面设置过这样的一段代理代码,他不仅将我们的服务启动起来,还做了一个代理转发的功能。他就是请求通过这个server转到了后端地址,跳过了浏览器的同源策略,这样本地运行就不会跨域了。
(2) JSONP
(前端+后端)
不受同源策略影响的标签: 、
<script src="http://1ocalhost:4000/getJSONP?fn=setJSONP"></script>
JSONP方法,只支持get请求,不支持post请求
(3) CORS(跨域资源分享)
后端设置响应头中的Access-Control-Alow-Origin字段,该属性表示哪些域名可以访问资源,如果设置为星*
表示所有网站都可以访问资源
res.header('Access-Control-Allow-Origin',"*")
// res.header("Access-Control-Allow-Origin',"http://localhost:3000')
(4) Nginx反向代理
需要下载软件,然后配置server里的响应头Access-Control-Alow-Origin/ˈækses/哎克赛斯,/kənˈtrəʊl/ 肯臭
cookie、session、jwt、token`记录管理状态
异步处理进化史【Promise】
B站视频这个讲的更好 # ES6 Promise的用法,# ES7 async/await异步处理同步化
B站视频# 同步与异步 promise,async/await精讲,极速入门
同步与异步小概念
JS从一出生就是一门单线程语言,所有程序都需要排队执行。起初我认为这是极好的,直到有一天,当遇到定时器或者网络请求的时候经常被卡住。我还需要暂停下手头的工作,等待这些卡住的任务完成,以至于体验极差。为了解决这个问题,我将任务分成了同步和异步俩个类型。
所有的编程语言都有同步和异步的概念,他们是两种不同的编程模型。
- 同步:所有的代码,一行一行的排队执行。不需要等待的就是同步任务。例如:let a = 1
- 异步:当碰到setTimeout、ajax网络请求,这种需要等待的任务时。跳过等待继续执行后续的代码
宏任务和微任务
异步操作执行的机制,涉及到事件循环的微任务和宏任务
JS的执行机制是自上而下,先去执行同步代码,然后是微任务,然后去渲染dom,最后去执行宏任务
微任务
:需要连贯执行:Promise、async、await
宏任务
:不需要连贯执行:setTimeout、setInterval、Ajax、DOM事件
1.0版本 ajax
2.0版本 Promice
通过同步的方式执行异步任务
但凡是出现.then()
就是内置封装了Promice。我们常用的axios就是封装了的
promise是解决异步的方法,本质上是一个构造函数,可以用它实例化一个对象。对象身上有 resolve、reject、all,原型上有then、catch方法。
promise对象有三种状态:
- pending (初识状态/进行中) 、
- resolved或fulfilled (成功)
- rejected (失败)
3.0版本 awite 和synce 异步处理同步化
ES7的新规范,这两个命令是成对出现的,如果使用await没有在函数中使用async命令,那就会报错,如果直接使用async没有使用await不会报错,只是返回的函数是个promise,可以,但是没有意义,所以这两个一起使用才会发挥出它们本身重要的作用。
在vue中,await是等待的意思,await关键字只能放在async函数里,await配合async一起使用,相当于把异步函数变成了同步,await会等待后面表达式的返回结果之后才执行下一步。
nextTick的使用
概念
/neks'tɪk/ 耐克斯'忒克 “nextTick”
是 Vue.js 框架中的一个方法。主要用于当数据动态变化后,DOM 还未及时更新的问题。
在Vue里面,我们的 DOM 更新是异步执行的。所以我们在工作当中,经常遇到数据动态变化后,视图层并未立即更新。这意味着你的代码可能在 DOM 更新之前执行了。这时你可以使用 nextTick 方法。它允许你在 DOM 更新之后执行一个回调函数。在回调函数里写一些修改数据的内容。
nextTick使用的场景
我知道nextTick,也知道created。但上次面试官问我怎么在created获取dom?我怎么就想不到呢
- 生命周期
“created”,组件创建后
,此时 data数据 和 methods方法 已经完成初始化,还不能获取到 DOM节点,可以使用 nextTick 方法获取DOM - Element UI库,里面有一个弹框组件,在设置弹框的visible为true(弹框显示)的时候,获取弹框里的某个dom元素是拿不到的,用了
nextTick
后就能获取到了
代码示例
// vue官网,vue2写法
<script>
import { nextTick } from 'vue'
export default {
data() {
return {
count: 0
}
},
methods: {
async increment() {
this.count++
// DOM 还未更新
console.log(document.getElementById('counter').textContent) // 0
await nextTick()
// DOM 此时已经更新
console.log(document.getElementById('counter').textContent) // 1
}
}
}
</script>
<template>
<button id="counter" @click="increment">{{ count }}</button>
</template>
// vue3 写法
// 添加input框,并获取焦点
<script setup lang='ts'>
import { nextTick, ref } from 'vue'
const isShow = ref(false)
async function addInput() {
isShow.value = true
await nextTick(() => {
document.getElementById('test')?.focus()
})
}
</script>
<template>
<input v-if="isShow" id='test' />
<button @click="addInput">添加input,获取焦点</button>
</template>
// vue3 写法二
// 获取列表更新后的高度
<script setup lang='ts'>
import { nextTick, ref } from 'vue'
const arr = ref(['AA', 'BB', 'CC'])
async function addEE() {
arr.value.push('EE')
console.log('len数_arr.value.length', arr.value.length) // 4
console.log("len数_nextTick()前", document.getElementById('test')?.children.length) // 3
await nextTick()
console.log("len数_nextTick()后", document.getElementById('test')?.children.length) // 4
}
</script>
<template>
<ul id="test">
<li v-for="(item, index) in arr" :key="index">{{ item }}</li>
</ul>
<button @click="addEE">添加EE</button>
</template>
虚拟DOM && diff算法
虚拟DOM
在没有vue之前,我们需要用document.getElementById('')
方法,获取dom元素并对其操作来更新视图。而vue时代之所以能通过,更改数据来更新视图。就是用js模拟了我们的dom结构,并计算出我们的一个变更,最后再把虚拟DOM节点更新为真正的DOM节点。相比于真实的元素节点,虚拟dom可以减少操作dom次数,从而提高性能
diff算法
当页面的内容发生了修改,会生成出新旧两个虚拟dom树,用diff算法,遍历新旧虚拟树,同一层级 比较 标签名、key值,如果两个值都一样,则认为是同一个节点。以此类推比较下一层级。发现有不同,则进行更新属性。找出差异最小化更新视图。diff算法本质上就是两个JS对象的差异
9.搭建 vue3项目遇到的问题
// 升级新安装最新版本的 `@vue/cli`
yarn global add @vue/cli
// # 或 npm install -g @vue/cli
// 创建项目 hello-vue3
vue create hello-vue3
1.新建项目时,报错如下:
error eslint-plugin-vue@8.5.0: The engine "node" is incompatible with this module. Expected version "^12.22.0 || ^14.17.0 || >=16.0.0". Got "12.19.0"
解决方案:
yarn config set ignore-engines true
2.git提交代码时,不提交node_modules文件
// 新建文件 .gitignore
.DS_Store
node_modules/
4.安装 Ant Design Vue 和less
// 安装 less 要安装俩个插件 less、less-loader
yarn add less
yarn add less-loader@5.0.0
// 安装 ant-design-vue
yarn add ant-design-vue
1.在main中导入
import { createApp } from 'vue'
import App from './App.vue'
import vueRouter from './router'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
// 注意use要在mount之前
createApp(App).use(vueRouter).use(Antd).mount('#app')
5.安装axios和vue-request
0.安装
yarn add axios
yarn add vue-request
# or
npm install vue-request
1.最基础用法
axios工作中使用
axios不需要在main中导入,创建api文件包,
import Axios from 'axios'
const baseURL = 'http://172.16.15.94:9898/goSchool/'
const biubiu = Axios.create({
baseURL: baseURL, // 请求接口
timeout: 150000, // 请求超时时间 15秒
// headers: headers,
})
// request 请求拦截器[前台在向后台发送请求时,要做的事]
biubiu.interceptors.request.use(
)
// response 响应拦截器[后台返回数据时,要做的事]
biubiu.interceptors.response.use(
// 1.根据返回的状态码,404、跳转页面
)
export default biubiu
vue.config.js 解决跨域
module.exports = {
devServer: {
proxy: {
'/api': { // /api 表示拦截以 /api开头的请求路径
target: 'http://172.16.15.94:9898/goSchool', // 跨域的域名
changeOrigin: true, // 是否开去跨域
pathRewrite: {
'^/api': '' // 重写路由
}
}
}
}
}
6.通过Cookie控制登录状态
通过Cookie或Session控制Vue页面的登录状态
Cookie存在浏览器端,Session存在服务器
document.cookie
查看是否有cookie
项目中写代码遇到的问题
0.css动态样式
:class='[homeif ? "header cur" : "header"]'
1.全局化配置,插件显示英文的问题
<template>
<a-locale-provider :locale="locale">
<router-view></router-view>
</a-locale-provider>
</template>
<script>
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN';
export default {
name: 'App',
components: {
},
data() {
return {
locale: zhCN,
}
}
}
2.表单提交后,清空
不忘记,:model=“formState”
。也要写
<template>
<a-form ref="formRef" :model="formState" >
<a-form-item label="Activity name" name="name2">
<a-input v-model:value="formState.name2" />
</a-form-item>
<a-form-item>
<a-button @click="resetForm">Reset</a-button>
</a-form-item>
</a-form>
</template>
<script>
import { defineComponent, reactive, ref, toRaw } from 'vue';
export default defineComponent({
setup() {
const formRef = ref();
const formState = reactive({
name2: '',
});
const resetForm = () => {
formRef.value.resetFields();
};
return {
formRef,
formState,
resetForm,
};
},
});
</script>
3.复制邀请码
<!-- 复制邀请码 -->
<template #inviteCode="{ record }">
<a-checkable-tag @change="clickInviteCode(record.inviteCode)">{{record.inviteCode}}</a-checkable-tag>
<input id="copy_content" type="text" value="" style="position: absolute;top: 0;left: 0;opacity: 0;z-index: -10;"/>
</template>
// 复制邀请码
function clickInviteCode(params) {
//获取点击的值
var clickContent = params;
//获取要赋值的input的元素
var inputElement = document.getElementById("copy_content");
//给input框赋值
inputElement.value = clickContent;
//选中input框的内容
inputElement.select();
// 执行浏览器复制命令
document.execCommand('Copy')
//提示已复制
notification["success"]({
message: '提示',
description: "邀请码已复制",
});
}
4.css媒体查询 @media
<style lang="less" scoped>
@media screen and ( max-width: 1680px) {
.formWrap {
padding: 0 80px !important;
}
}
@media screen and ( max-width: 1440px) {
.formWrap {
padding: 0 0px !important;
}
}
</style>
5.夏清の遍历
addtreeIcon = (tree) => {
if (tree === null || tree.length === 0) {
return
}
for (let index = 0; index < tree.length; index++) {
const ele = tree[index];
ele.value = ele.key
if (ele.isLeaf === true) {
if (ele.use === true) {
ele.icon = <CheckCircleTwoTone twoToneColor="#52c41a" />
} else {
ele.icon = <CloseCircleTwoTone twoToneColor="#eb2f96" />
}
}
this.addtreeIcon(ele.children);
}
}
6.获取上传文件的本地路径blob并展示
<html>
<head>
<meta charset="UTF-8">
<title>Video Demo</title>
</head>
<body>
<input type="file" name="file" id="upload" />
<video id="videoA" controls="true" width="100" height="200" src=""></video>
<script>
const upload = document.querySelector('#upload');
const videoA = document.querySelector('#videoA');
upload.onchange = function(){
const file = upload.files[0];
// 生成一个 blob:null/e539e787-f27a-4f66-940a-0f9ee4d14f42 的指向本地的路径格式
const src = URL.createObjectURL(file);
videoA.src = src;
}
</script>
</body>
</html>
js获取本地服务器路径
该路径可以在浏览器中播放
它大概长这样:blob:null/e539e787-f27a-4f66-940a-0f9ee4d14f42
// 本地服务器路径
console.log(URL.createObjectURL(file.raw));
// 本地电脑路径
console.log(document.getElementsByClassName("el-upload__input")[0].value);
7.JS截取-后面或前面的字符串
var str = "我是中国人,你是外国人"
console.log(str.split(',')[1]) // 后面 你是外国人
console.log(str.split(',')[0]) // 前面 我是中国人
8.js 去重总结
- 利用
Set
结构 以上去方式对 NaN 和 undefined 类型去重也是有效的,是因为 NaN 和 undefined 都可以被存储在 Set 中, NaN 之间被视为相同的值(尽管在 js 中:NaN !== NaN)。
const arr = [
1,
2,
2,
"abc",
"abc",
true,
true,
false,
false,
undefined,
undefined,
NaN,
NaN,
];
const set = new Set(arr);
const list = Array.from(set);
console.log(list); // [1, 2, 'abc', true, false, undefined, NaN]
2.通过遍历
遍历是最常用的,网上说的多种去重,很多仅仅是使用不同的数组 api 而已,如 includes
, indexOf
等
const arr = [
1,
2,
2,
"abc",
"abc",
true,
true,
false,
false,
undefined,
undefined,
NaN,
NaN,
];
let list = [];
arr.forEach((item) => {
const flag = list.some((sub) => sub === item);
if (!flag) list.push(item);
});
console.log(list); // [1, 2, 'abc', true, false, undefined, NaN, NaN]
9.AntD Vue —下拉选择器(红玉写法)
<a-select
style="width: 240px"
v-model:value="formStateQQ.resplanTypeitem"
placeholder="请选择套餐资源类型"
:options="resplanTypeitemoptions"
@change="resplanTypeitemChange"
>
-----
const getOption = () => {
API.getoptionsList().then((res) => {
if (res.data.code === 1) {
const options = res.data.data;
for (let i = 0; i < options.length; i++) {
let ele = options[i];
state.resplanTypeitemoptions.push({label:ele.name,value:ele.code});
}
}
});
};
8.# Ant Design Vue - 手动清空表格复选框勾选状态(复选框打勾残留)
例如,当勾选数据后,点击【设置办案人】操作完成后,一般业务需求情况下需要清空复选框勾选(打勾)的残留,不能用户设置完相关操作后,你的复选框依然处于选中状态,
你必须执行完操作后,手动清空复选框勾选状态,如下图所示:
<!--
你只需要关注 rowSelection / rowKey 配置
selectedRowKeys: 复选框勾选后在你本地data存储的值(后续使用)
onSelectChange: 每次复选框发生变化时调用该方法(用于更新本地data存储的值)
-->
<template>
<div>
<button @click="resetSelect()">清空复选框(业务完成后)</button>
<a-table
:columns="XXX"
:data-source="XXX"
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
:rowKey="(record,index) => {return record.id}"
/>
</div>
</template>
export default {
data () {
return {
selectedRowKeys: [],//复选框数据
}
},
methods: {
/**
* 复选框变化时触发
* @description 表格复选框逻辑(赋值本地data数据)
* @param {Object} selectedRowKeys - 选中行的id
* @param {Object} selectedRows - 整row行数据
* @return void
*/
onSelectChange: function(selectedRowKeys, selectedRows) {
// 用哪个根据实际业务需求
// console.log(selectedRowKeys, selectedRows)
// 赋值同步本地data接受数据(我这里只取id即可)
this.selectedRowKeys = selectedRowKeys
},
/**
* 清空表格复选框
* @description 业务完成后调用此方法!
* @return void
*/
resetSelect: function() {
this.selectedRowKeys = []
},
}
}
————————————————
版权声明:本文为CSDN博主「王佳斌」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44198965/article/details/122130106
10.vue3 富文本编辑器 wangEditor
// 安装 editor
yarn add @wangeditor/editor
// # 或者 npm install @wangeditor/editor --save
// 安装 Vue3 组件
yarn add @wangeditor/editor-for-vue@next
// # 或者 npm install @wangeditor/editor-for-vue@next --save