Vue基本内容
Vue的安装使用
脚手架方式
创建vue3项目需要先安装nodejs
- 配置阿里云镜像(仅第一次执行):
npm config set registry https://registry.npmmirror.com
- 创建命令:
npm create vue@latest
- 具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript? Yes
## 是否添加JSX支持
√ Add JSX Support? No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development? No
## 是否添加pinia环境
√ Add Pinia for state management? No
## 是否添加单元测试
√ Add Vitest for Unit Testing? No
## 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
## 是否添加ESLint语法检查
√ Add ESLint for code quality? Yes
## 是否添加Prettiert代码格式化
√ Add Prettier for code formatting? No
- 下载项目依赖:
npm i
- 运行项目:
npm run dev
项目基本目录结构
├── .vscode:VS Code中配置的插件,如果没有相关插件会出现安装提示。
├── node_modules:node安装的依赖包(脚手架的依赖)
├── public:公共目录,存放静态不会变的文件,会被webpack原封不动的打包到dist文件夹中。
│ ├── favicon.ico: 页签图标
├── src:源码文件夹
│ ├── assets: 存放静态资源,存放多个组件公用的静态资源,会被webpack当做一个模块打包到js文件中
│ │ └── logo.png
│ │── router: 定义应用程序的路由配置
│ │── components: 存放公共组件,存放非路由组件和全局组件
│ │ └── HelloWorld.vue
│ │── views: 存放页面组件
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git 版本管制忽略的配置。
├── env.d.ts: 使得ts文件能够识别其他类型文件,并且能够正确引入其他类型文件。
├── index.html: 项目入口文件(一个Vue脚手架项目只有一个html网页)
├── package-lock.json:缓存性文件,使得第二次启动速度更快。
├── package.json: 记录项目有哪些依赖、项目如何运行。
├── README.md: 工程的描述文件。
├── tsconfig.app.json:ts编译器的配置文件。
├── tsconfig.json:ts编译器的配置文件。
├── tsconfig.node.json:ts编译器的配置文件。
├── vue.config.js:文件用于自定义 Vue CLI 的默认配置。
index.html文件
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<!-- 定义根容器 -->
<div id="app"></div>
<!-- 引入src/main.ts文件 -->
<script type="module" src="/src/main.ts"></script>
</body>
</html>
/src/main.ts
import './assets/main.css'
// 引入Vue3项目的根组件App组件
import { createApp } from 'vue'
import App from './App.vue'
// 将App组件挂载到index.html文件中的id=app标签上
createApp(App).mount('#app')
/src/App.vue
<template>
<p>Hello,{{ name }}</p>
</template>
<!--setup属于VUE3的语法糖,可以使用组合式API-->
<script lang="ts" setup name='App'>
const name = 'Vue3'
</script>
<style scoped>
</style>
CSN方式
通过script标签引入vue3
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
Vue开发工具
Vue开发工具Vue Devtools:在使用 Vue 时,我们推荐在你的浏览器上安装Vue Devtoo ls。它允许你在一个更友好的界面中审查和调试 Vue 应用。
安装方式:直接在浏览器添加插件 Vue.js devtools
Vue API风格
选项式API:可以用包含多个选项的对象来描述组件的逻辑,例如 data
、methods
和 mounted
。选项所定义的属性都会暴露在函数内部的 this
上,它会指向当前的组件实例。
组合式API:可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 <script setup>
搭配使用。这个 setup
attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup>
中的导入和顶层变量/函数都能够在模板中直接使用。
注意:对于Vue3来说推荐使用组合式API,编写起来更加方便简洁。
Vue的API
数据绑定
单向数据绑定:使用v-bind,数据只能从data流向页面
- v-bind:可以用在任何属性上面进行单向绑定。
- v-model:只能用在表单类的value属性上面进行双向绑定。
v-model的三个修饰符:
- lazy:表单失去焦点再收集数据
- number::输入字符串转为有效的数字
- trim:输入首位空格过滤
<template>
<div id="root">
单向绑定:<input type="text" v-bind:value="name"><br>
双向绑定:<input type="text" v-model:value="name"><br>
双向绑定:<input type="text" v-model="name"><br>
</div>
</template>
<script>
import { ref } from 'vue';
// Vue3写法
export default {
name: "root",
setup() {
const name = ref("");
return {
name
}
}
}
// Vue2写法
</script>
事件处理
事件绑定的两种方式
方式1:v-on:click="function"
方式2:@click="function"
事件对象event会通过实参
$event
占位符进行传递,Vue会自动将当前事件对象传递给对应实参。
点击事件
点击事件修饰符:
- prevent: 阻止标签的默认事件
- stop:阻止事件的冒泡
- once:只触发一次事件
- capture:使用事件的捕获模式
- self:只有event.target是当前操作的元素时才会触发事件
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕
<template>
<div id="root">
<button @click="showInfo($event, "zhangsan")">点我</button>
<!--修饰符的使用-->
<button @click.passive="showInfo($event, "zhangsan")">点我</button>
</div>
</template>
<script>
import { ref } from 'vue';
// Vue3写法
export default{
name: 'root',
setup() {
const showInfo = (event, name) => {
console.log(name + " " + event.target.innerText);
}
return {
showInfo
}
}
}
</script>
滚轮滚动事件
@scroll
:页面元素滚动条滚动触发事件@wheel
:鼠标滚轮滚动触发事件
键盘事件
@keyup
:键盘松开事件@keydown
:键盘按下事件
Vue常用的按键别名:
别名 | 含义 |
---|---|
回车 | enter |
删除和退格 | delete |
退出 | esc |
空格 | space |
对其 | table |
上 | up |
下 | down |
左 | left |
右 | rigth |
按键别名的使用
<template>
<div id="root">
<input type='text' @keyup.enter='showInfo'>
</div>
</template>
<script>
import { ref } from 'vue';
// Vue3写法
export default{
name: 'root',
setup() {
const showInfo = (event) => {
// 获取到input文本框的值
console.log(event.target.value);
// 获取按键的缩写和code
console.log(event.key, event.keyCode);
}
return {
showInfo
}
}
}
</script>
样式绑定
Vue可以直接通过v-bind:class与Vue中维护的变量进行动态绑定样式,而且可以另外定义class属性进行拼接成一个样式。
<!--变量绑定:会根据变量值动态修改class的值-->
<div class='basic' :class='classStyle'></div>
<!--数组绑定:数组中所有元素都会变成class的值-->
<div class='basic' :class='classArr'></div>
<!--对象绑定:对象中的key就是class值,value是一个bool类型决定是否添加进去-->
<div class='basic' :class='classObj'></div>
条件渲染
- v-if : 如果为 true, 当前标签才会输出到页面
- v-else-if:如果为 true, 当前标签才会输出到页面
- v-else: 如果前面的v-if标签和v-else-if标签为false,当前标签才会输出到页面
- v-show : 通过控制 display 样式来控制显示/隐藏
<!--如果为 true, 当前标签才会输出到页面-->
<div v-if="n === 1">wenxuan</div>
<!--如果为 true, 当前标签才会输出到页面-->
<div v-else-if="n === 1">wenxuan</div>
<!--如果前面的v-if标签和v-else-if标签为false,当前标签才会输出到页面-->
<div v-else="n === 1">wenxuan</div>
<!--通过控制 display 样式来控制显示/隐藏-->
<div v-show="n === 1">wenxuan</div>
v-if和v-show的区别:
- v-if如果表达式为false,元素直接会被移除,所以元素可能无法获取;v-show如果表达式为false,元素存在只是不展示。
- v-if适用于切换频率比较低的场景,v-show适用于切换频率比较高的场景。
列表渲染
v-for:用于遍历数组或者对象
:key最好使用一条数据的唯一标识,这样子效率更高而且不会出现问题
<div id="root">
<ul>
<li v-for="(p, index) in persons" :ke y="p.id">
{{index}}----{{p.name}}-{{p.age}}
</li>
</ul>
<ul>
<li v-for="(value, key) in car" :key="key">
{{value}}
</li>
</ul>
</div>
文本处理
- v-text : 更新元素的 textContent,内容当作文本内容。
- v-html : 更新元素的 innerHTML,内容会被解析为标签。
<template>
<div id="root">
<div v-text="text"></div>
<div v-html="html"></div>
</div>
</template>
<script>d
import { ref } from 'vue';
export default{
name: 'root',
setup() {
const text = ref('text');
const html = ref('html');
return {
text,
html
}
}
}
</script>
ref和reactive函数
通过ref函数和reactive函数来定义响应式数据:
- ref可以用来定义基本类型、对象类型的响应式数据,reactive只能用来定义对象类型的响应式数据。
- ref定义的变量必须使用.value来使用,reactive定义的对象变量无需使用.value来使用。
对于基本类型的变量必须使用ref,对于对象类型的变量一般使用reactive。
// 通过ref定义基本类型的响应式变量
const age = ref(18)
const name = ref('wenxuan')
// 通过reactive定义应用类型的响应式变量
const student = reactive({
name: 'wenxuan',
age: 18
})
对于对象类型的响应式变量需要通过Object.assign()函数来修改:
Object.assign(变量名, 新值)
toRef和toRefs函数
通过toRef和toRefs函数可以将对象类型的响应式数据解构出来变成ref定义的响应式变量。
const person = reactive({
name: '张三',
age: 18
})
// 将person中的name结构出来
const name = toRef(person, 'name')
// 将person中所有属性结构出来
const {name, age} = toRef(person)
计算属性computed
当变量需要通过复杂表达式计算出来时,可以通过computed函数定义计算属性。computed在模板第一次解析加载时会调用一次,后面当computed函数中设计到的响应式数据发生改变时也会调用。
const firstName = ref('wen')
const lastName = ref('xuan')
// 定义计算属性-简写形式(但计算属性只需要读取时)
const fullName = computed(() => {
return firstName.value + '-' + lastName.value
})
// 定义计算属性-完整形式(当计算属性需要读写时)
const fullName = computed({
get() {
return firstName.value + '-' + lastName.value
},
set(val) {
const [str1, str2] = val.split('-')
firstName.value = str1
lastName.value = str2
}
})
监视属性watch
当变量变化时需要处理更加复杂的操作时,可以通过watch函数定义监听操作。
情况1:监听基本类型变量
//引入组件的方式:import 组件名称 from '组件路径'
const age = ref(18)
// 定义监视操作
const stopWatchAge = watch(age, (newVal, oldVal) => {
console.log('age变量发生变化', newVal, oldVal);
if(newVal > 100 || newVal <= 0) {
// 停止监视age变量
stopWatchAge()
}
})
情况2:监听对象类型变量的所有属性
- 对于ref定义的对象类型的响应类型数据监听对象属性需要通过deep开启深度监视。
- 对于reactive定义的对象类型的响应类型数据一定会监听对象类型数据。
const person = ref({
name: 'wenxuan',
age: 18
})
// 定义监视操作
watch(person, (newVal, oldVal) => {
console.log('person变量发生变化', newVal, oldVal);
}, {deep: true}) // deep表示是否开启深度监视,对象类型变量的属性变化是否监视
情况3:监听对象类型变量的某个属性
- 监视基本类型属性或者对象类型属性,写成函数形式。
- 监视对象类型属性,如果需要监视属性的属性,需要通过deep开启深度监视。
const person = reactive({
name: 'wenxuan',
age: 18
})
// 定义监视person变量的name属性,第一个参数为函数,函数返回需要监视的变量属性
watch(() => person.name, (newVal, oldVal) => {
console.log('person.name变量发生变化', newVal, oldVal);
})
情况4:监听多个变量
- watch实现监听多个属性需要指出监视的变量。
- watchEffect实现监听多个变量无需指出监听的变量,会监听函数中使用到的变量。
const name = ref('wenxuan')
const age = ref(18)
// watch实现监听多个变量
watch([() => name, age], (newVal, oldVal) => {
console.log('name或者age变量发生变化', newVal, oldVal);
})
// watchEffect实现监听多个变量
watchEffect(() => {
console.log('name或者age变量发生变化', name.value, age.value)
})
Vue的其他API
Vue一些其他常用的API:
- shallowRef:创建一个响应式数据,但只对顶层属性进行响应式处理。
- shallowReactive:创建一个响应式对象,但只对最顶层的属性变成响应式处理。
- readonly:创建一个会跟随另一个响应式数据变化而变化的变量,但是变量本身不能修改。
- shallowReadonly:创建一个顶层属性制度,对象内部的嵌套属性仍然可变。
- toRaw:返回一个响应式变量对应的原始数据。
- markRaw:标记一个对象永远不会变成响应式变量。
- customRef:创建一个自定义的ref,并对其依赖项跟踪和更新和触发进行逻辑控制。
Vue组件
组件的规范
编写组件存在一些规范:
- App组件是Vue工程的顶级组件,其他所有组件都是在App下面的。
- Components 将数据传递给组件进行渲染。Views 控制路由、获取数据和协调组件之间的交互。
组件的定义和使用
定义组件:
<template>
<div>Hello,{{ name }}</div>
</template>
<script lang='ts' setup>
import { ref } from 'vue'
const name = ref('wenxuan')
</script>
在其他组件中使用组件
<template>
<h1>其他组件</h1>
<ShowInfo />
</template>
<script lang='ts' setup>
import ShowInfo from './ShowInfo.vue'
</script>
组件标签的获取
标签的ref属性:通过ref属性给标签定义一个唯一标识,可以通过ref函数获取DOM元素。
获取普通DOM标签
<template>
<p1 ref="ref1">ref1</p1>
<p1 ref="ref2">ref2</p1>
</template>
<script lang='ts' setup>
// 通过变量名称和ref属性中的标识一一对应
const ref1 = ref()
const ref2 = ref()
</script>
获取子组件标签
<!--父组件-->
<template>
<Son ref="son"/>
<button @click="showS">显示</button>
</template>
<script lang="ts" setup name="Father">
import { ref } from 'vue';
import Person from './Son.vue'
const son = ref()
function showS() {
console.log(son.value.name);
}
</script>
获取所有的子组件标签:$refs会将所有的子组件封装到一个数组中,注意$refs只会获取通过ref属性打标签的子组件。
<template>
<div class="father">
<button @click="getAllChild($refs)">获取所有的子组件</button>
<Child ref="child"/>
</div>
</template>
<script lang='ts' setup>
function getAllChild(childs:any) {
for(const key in childs) {
console.log(childs[key]);
}
}
</script>
组件之间的通信
父子组件的通信
父子组件的通信:
- defineProps:可以实现父传子,也可以实现子传父。
- defineEmit:只能实现子传父。
- v-model:实现父子组件的双向绑定。
defineProps:父子之间的通信
- 父 --> 子:直接通过defineProps接收父组件传递过来的参数。
- 子 --> 父:父组件先传递一个函数,子组件接收到函数并且调用函数传递参数过去。
<!--编写父组件-->
<template>
<div class="father">
<Child :name="name" :getCount="getCount"/>
<p>子组件的统计数据:{{ count }}</p>
</div>
</template>
<script lang='ts' setup>
import { ref } from 'vue';
import Child from './Child.vue';
// 数据
const name = ref('wenxuan')
const count = ref()
// 函数
function getCount(value:number) {
count.value = value
}
</script>
<!--编写子组件-->
<script lang='ts' setup>
// 1. 参数:数组中可以接收多个参数,字符串为接收的参数名
defineProps(['name', 'getCount'])
// 2. 数并限定类型:通过泛型指定参数名和参数类型
defineProps<{name:string}>()
// 3. 数并限定类型和默认值:第一个参数指定参数名和参数类型,第二个参数指定默认值
withDefaults(defineProps<{name:string}>(), {name: () => 'wenxuan'})
// 4. 将参数使用变量保存起来:
const params = defineProps(['name'])
</script>
defineEmit:子组件 --> 父组件
在父组件中可以通过
$event
获取调用emit时提供的数据,可以是任意类型。
<!--父组件:定义自定义事件-->
<template>
<div class="father">
<Child @send-count="sendCount"/>
<p>子组件的统计数据:{{ count }}</p>
</div>
</template>
<script lang='ts' setup>
import { ref } from 'vue';
import Child from './Child.vue';
const count = ref()
// 函数
function sendCount(value:number) {
count.value = value
}
</script>
<!--子组件:接收父组件的自定义事件并调用-->
<script lang='ts' setup>
// 获取父组件的自定义事件
const emit = defineEmits(['send-count'])
// 调用父组件的事件,将参数传递过去
emit('send-count', 10)
</script>
祖孙组件的通信
祖孙组件通信的两种方式:
- 方式一:祖孙组件的通信通过
$attrs
实现,子组件的$attrs
中包含着父组件传递过过来的所有参数,可以将这些所有参数传递给孙组件,孙组件可以通过defineProps
接收。 - 方式二:祖先组件通过
provide
将需要传递的变量或者函数传递下去,孙子组件通过inject
获取祖先组件传递下来的变量或者函数。
attrs方式需要操作经历过的组件,需要将父组件传递下来的参数一层一层往下传递。provide方式需要接收参数的组件通过inject获取即可,中间的组件不需要进行任何操作。
通常使用provide方式,只有当传递参数很多时才会使用$attrs方式
。
$attrs方式:适用于需要传递的层数不高,并且需要传递的参数很多时使用。
<!--父组件:将参数传递给子组件-->
<Child :a="a" :b="b" :c="c" :d="d"/>
<!--子组件接收部分参数同时将父组件的参数传递过孙组件-->
<template>
<div class="child">
<p>a的值:{{ a }}</p>
<GrandChild v-bind="$attrs"/>
</div>
</template>
<script lang='ts' setup>
import GrandChild from './GrandChild.vue';
defineProps(['a'])
</script>
<!--孙组件:接收子组件传递过来的参数-->
<script lang='ts' setup>
defineProps(['a', 'b', 'c', 'd'])
</script>
provide:传递的参数较少且不限制传递层数
<!--父组件:传递参数和函数-->
import { inject, provide, ref } from 'vue';
import Child from './Child.vue';
const count = ref(0)
function updateCount(val:number) {
count.value += val
}
provide('countContext', {count, updateCount})
<!--子组件:接收和使用参数和函数-->
const {count, updateCount} = inject('countContext')
父子组件变量的修改
修改父子组件的变量需要通过想获取组件的标签,同时将需要修改的变量暴露出去。
- 父组件修改子组件变量:子组件将需要修改的变量暴露出去,父组件对子组件标签
ref
打标识,父组件通过ref获取标签就能对子组件的值修改。 - 子组件修改父组件变量:父组件将需要修改的变量暴露出去,子组件通过
$parent
获取父组件的标签即可对变量修改。
父子组件变量的修改逻辑:都是
一个组件将变量暴露
出去,然后另一个组件获取需要修改的组件
并修改。
父组件修改子组件的变量
<!--父组件通过ref对子组件的变量修改-->
<template>
<div class="father">
<button @click="addChildCount">修改子组件的值</button>
<Child ref="child"/>
</div>
</template>
<script lang='ts' setup>
import { ref } from 'vue';
import Child from './Child.vue';
// 数据
const child = ref()
// 方法
function addChildCount() {
child.value.count++
}
</script>
<!--子组件通过defineExpose将需要修改的变量暴露出去-->
import { ref } from 'vue';
const count = ref(0)
defineExpose({count})
子组件修改父组件的变量
<!--父组件将需要修改的变量暴露出去-->
const count = ref(0)
defineExpose({count})
<!--子组件通过$parent获取父组件标签-->
<template>
<div class="child">
<button @click="updateFatherCount($parent)">修改父组件的变量</button>
</div>
</template>
<script lang='ts' setup>
function updateFatherCount(parent:any) {
parent.count++
}
</script>
任意组件的通信
任意组件的通信:
- pinia:Pinia 是 Vue 的状态管理库,主要用于管理 Vue 应用中的全局状态,确保不同组件之间可以共享和管理状态。
- mitt:Mitt 是一个简单而强大的事件总线库,主要用于消息的订阅和发布。
Vue3整合pinia和mitt看下面的小结。
组件的生命周期
生命周期图示
生命周期函数
setup()
:开始创建组件之前执行;onBeforeMount()
:组件挂载到节点上之前执行的函数;onMounted()
:组件挂载完成后执行的函数;onBeforeUpdate()
:组件更新之前执行的函数;onUpdated()
:组件更新完成之后执行的函数;onBeforeUnmount()
:组件卸载之前执行的函数;onUnmounted()
:组件卸载完成后执行的函数;
父子组件的挂载顺序:子组件挂载完毕之后,再开始挂载父组件,所以App组件是最后挂载的。
组件生命周期函数的使用
<script lang='ts' setup>
// 组件挂载之前执行的函数
import { onBeforeMount } from "vue";
onBeforeMount(() => {
console.log("组件挂载到页面之前执行");
})
</script>
组件API封装复用
自定义hook:可以将函数中使用的API进行封装,然后将API暴露出去,其他Components可以引入复用API。
在 /src/hooks/useSum.ts
编写复用的数据和函数,通过函数暴露出去
import { ref } from "vue";
export default function() {
const sum = ref(0)
const increment = () => {
sum.value++;
}
const decrement = () => {
sum.value--
}
// 向外部暴露出去
return {sum, increment, decrement}
}
在组件中使用API
import useSum from '@/hooks/useSum';
const {sum, increment, decrement} = useSum();
组件的插槽
组件插槽的作用:子组件大部分片段相同只有部分片段不同,可以让子组件在根据不同的条件渲染部分不同的片段。
默认插槽
默认插槽:在父组件的子标签内容填写需要插入的标签和数据,在子组件中通过<slot>
标签标注需要插入的位置。
<!--父组件:标签中包含需要插入的内容-->
<template>
<div class="father">
<Child title="游戏">
<ul>
<li v-for="game in games" :key="game.id">{{ game.name }}</li>
</ul>
</Child>
<Child title="美食">
<img :src="imgUrl" alt="">
</Child>
<Child title="影视">
<video :src="videoUrl"></video>
</Child>
</div>
</template>
<!--子组件:标注需要插入的位置-->
<slot></slot>
具名插槽
具名插槽:当子组件中存在多个内容需要插入到很多位置时,可以给内容命名,同时子组件中插槽也命名。
<!--父组件:通过template包括片段,并且给片段命名-->
<template>
<div class="father">
<Child title="游戏">
<template #s1><p>内容1</p></template>
<template #s2><p>内容2</p></template>
</Child>
</div>
</template>
<!--子组件:布置插槽,同时给插槽命名-->
<template>
<div class="child">
<slot name="s2"></slot>
<slot name="s1"></slot>
</div>
</template>
作用域插槽
作用域插槽:数据在子组件上,但是根据数据生成的结构由父组件决定。
<!--父组件:接收子组件的参数,并展示到结构中-->
<template>
<div class="father">
<Child>
<template v-slot="params">
<ul>
<li v-for="game in params.games" :key="game.id">{{ game.name }}</li>
</ul>
</template>
</Child>
</div>
</template>
<!--子组件:准备并传递参数-->
<template>
<div class="child">
<slot :games="games"></slot>
</div>
</template>
<script setup lang="ts" name="Child">
import { reactive } from 'vue';
const games = reactive([
{id:'001', name: '英雄联盟'},
{id:'002', name: '王者荣耀'},
{id:'003', name: '斗罗大陆'},
{id:'004', name: '金铲铲'},
])
</script>
Vue实战技术
Vue3整合mitt
mitt:实现任意组件间的通信,和消息的发布订阅功能类似。
mitt准备环境
安装mitt:npm i mitt
配置emitter:新建/src/utils/emitter.ts
// 引入mitt
import mitt from 'mitt'
// 创建emitter
const emitter = mitt()
// 暴露emitter
export default emitter
mitt的使用
接收数据的组件中:绑定事件
import emitter from '@/utils/emitter';
import { onUnmounted, ref } from 'vue';
import Child from './Child.vue';
const data = ref('')
emitter.on('send-data', (val) => {
console.log("接收到的:", data);
// 将接收到的数据放到响应式变量
data.value = val
})
// 组件销毁时结伴事件
onUnmounted(() => {
emitter.off('send-data')
})
发送数据的组件中,触发事件
import emitter from "@/utils/emitter";
emitter.emit('send-data', 'wenxuan')
Vue3项目实战
项目开发配置
配置自动打开浏览器
在package.json文件中对scripts/dev
配置项改成
vite --open
配置src别名
src别名配置:使得项目中填写路径时@可以标识./src/,简化了路径的填写。
// vite.config.ts
import {defineConfig} from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
"@": path.resolve("./src") // 相对路径别名配置,使用 @ 代替 src
}
}
})
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { //路径映射,相对于baseUrl
"@/*": ["src/*"]
}
}
}
配置环境变量
- 创建三个配置文件
- .env.development:开发环境的配置文件
- .env.production:生产环境的配置文件
- .env.test:测试环境的配置文件
- 编写三个配置文件
# .env.development
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development' # 标识生产环境
VITE_APP_TITLE = 'xxx' # APP应用标题
VITE_APP_BASE_API = '/dev-api' # 开发环境的URL
VITE_SERVE = 'http://localhost:8080' # 开发环境后端接口的路径
# .env.production
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'production' # 标识生产环境
VITE_APP_TITLE = 'xxx' # APP应用标题
VITE_APP_BASE_API = '/prop-api' # 生产环境的URL
VITE_SERVE = 'http://localhost:8080' # 生产环境后端接口的路径
# .env.test
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'test' # 标识测试环境
VITE_APP_TITLE = 'xxx' # APP应用标题
VITE_APP_BASE_API = '/test-api' # 测试环境的URL
VITE_SERVE = 'http://localhost:8080' # 测试环境后端接口的路径
3. 添加四条指令
- dev:开发环境的启动命令
- build:test:打包测试环境的版本命令
- build:pro:打包生产环境的版本命令
- preview:生成预览的生产环境代码
// package.json
"scripts": {
"dev": "vite --open",
"build:test": "vue-tsc && vite build --mode test",
"build:pro": "vue-tsc && vite build --mode production",
"preview": "vite preview"
},
配置全局注册组件
通过自定义插件
实现配置全局注册组件无需在组件中引入可以直接使用,当一个组件被很多组件使用到时可以将该组件配置到全局注册中。比如SVG组件。
- 编写注册全局注册的插件
// /src/components/index.ts
//引入项目中全部的全局组件
import SvgIcon from './SvgIcon/index.vue'
//全局对象
const allGlobalComponent:any = { SvgIcon }
//对外暴露插件对象
export default {
//必须叫做install方法
install(app: any) {
// 注册项目全部的全局组件
Object.keys(allGlobalComponent).forEach((key) => {
// 将组件注册为全局组件
app.component(key, allGlobalComponent[key])
})
},
}
- 使用注册全局组件的插件
// main.ts
const app = createApp(App)
import GlobalComponent from './components'
app.use(GlobalComponent)
配置项目相关样式
注意:如果使用了scss则需要参考Vue3整合Sass先下载依赖
- 配置全局样式
<!--/src/styles/index.scss-->
<!--编写全局样式-->
<!--main.ts:引入全局样式-->
import './styles/index.scss'
- 配置清除默认样式
注意:清除默认样式的代码可以去npm官网下载:www.npmjs.com ,搜索reset.scss
<!--/src/styles/reset.scss-->
<!--编写全局样式-->
<!--/src/styles/index.scss:引入清除默认样式-->
@import './reset.scss'
- 配置scss全局变量
scss全局变量的配置:全局变量保存在/src/styles/variable.scss
注意:需要先配置src目录的别名
// vite.config.ts
export default defineConfig({
// 配置scss全局变量的文件路径
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/styles/variable.scss";`
}
}
}
})
全局变量的使用
// /src/styles/variable.scss
$color: red;
注意:使用变量时需要添加
variable
前缀,variable为全局变量的文件名
<!--App.vue-->
<style scoped lang="scss">
div {
color: variable.$color;
}
</style>
配置变量分离
项目变量分离:比如项目的标题,LOGO的路径等等,可以将这些变量信息保存到一个ts文件中,在组件中引入使用,需要修改时无需找组件,只需要找到ts1文件即可。
- 定义变量
// /src/setting.ts
export default {
title: '硅谷甄选运营平台', // 项目标题
logo: '/public/logo.png', // 项目logo路径
loginHidden: false, // 项目logo是否隐藏
}
- 组件中使用变量
<!--/src/layout/logo/index.vue-->
<script lang='ts' setup name='Logo'>
import setting from '@/setting.ts'
</script>
<template>
<div class='logo'>
<img :src="setting.logo" alt="">
<p>{{ setting.title }}</p>
</div>
</template>
配置项目组件规范
项目路由配置规范:
- 一级路由组件放置在
/src/views
目录下面 - 一级主路由的组件放置在
/src/layout
目录下面 - 二级路由放置到对应的一级路由目录下面
- 非路由组件放置到
/src/components
目录下面
配置多环境解决跨域问题
参考
配置环境变量
先配置环境
解决跨域问题
// /vite.config.ts
import { defineConfig, loadEnv } from 'vite'
// https://vite.dev/config/
export default defineConfig(({ command, mode }) => {
// 获取配置文件定义的变量,process.cwd()表示配置文件在根目录下
const env = loadEnv(mode, process.cwd())
return {
// 代理跨域
server: {
proxy: {
// 动态获取后端接口的前缀
[env.VITE_APP_BASE_API]: {
// 动态获取后端接口的地址
target: env.VITE_SERVE,
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
}
})
Vue3整合第三方库
Vue3整合axios
Axios 是一个基于 promise 网络请求库,作用于node.js
和浏览器中。在服务端它使用原生 node.js http
模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。
- axios依赖下载
axios文档:github.com/axios/axios
方式1:npm install axios
方式2:bower install axios
方式3:yarn add axios
- axios前端解决跨域问题
如果后端已经解决跨域问题,则前端无需再次解决,也就是这一步可以省略。
找到vite.config.ts配置文件,在defineConfig对象中添加属性:
- /api:以api开头的请求会进行代理
- target:代理的目标URL
- changeOrigin:是否修改请求头中的 Origin 字段
- rewrite:改写URI
// vite.config.ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
})
- axios网络请求封装
注意:
- baseURL中引入了环境变量,所以需要先配置环境变量。
- 若整合了Element-Plus可以将下面两处的注释打开。
// /src/utils/request.ts
import axios from 'axios'
// 若整合Element-Plus
// import { ElMessage } from "element-plus";
const getErrorMessage = (code:number) => {
switch(code) {
case 400: return "语义有误"
case 401: return "服务器认证失败"
case 403: return "服务器拒绝访问"
case 404: return "URL地址有误"
case 500: return "服务器出现异常"
case 502: return "服务器无响应"
default: return "未知异常"
}
}
// 创建一个axios对象
const requests = axios.create({
// 配置基础路径
baseURL: import.meta.env.VITE_APP_BASE_API,
// 配置请求超时时间
timeout: 5000,
})
// 请求拦截器
requests.interceptors.request.use((config) => {
// config:配置对象,对象中有一个很重要的属性headers
return config
})
// 响应拦截器
requests.interceptors.response.use((res) => {
// 响应成功的拦截回调函数
// 返回响应值中的data值
return res.data;
}, (error) => {
// 响应失败的拦截回调函数
const {response} = error;
const msg = getErrorMessage(response.status)
console.log("code = " + response.status);
console.log("msg = " + msg);
// ElMessage({
// type: 'error',
// message: response.status + "-" + msg
// })
// 返回异常,可以在发送请求的代码中进行捕获异常
return Promise.reject(error);
})
// 对外暴露axios实例requests
export default requests
请求拦截器的config中包含请求的一系列信息:URL、请求方法、请求头、请求数据等内容,通常可以在这里进行如下处理:
添加请求头
:为请求动态添加认证信息、令牌(Token)等。处理请求参数
:发送请求之前,对请求的参数进行格式化或修改。日志记录
:记录请求的相关信息,如请求的 URL、请求时间等,方便调试。错误处理
:处理一些全局的错误逻辑,比如验证 Token 是否过期,如果过期就跳转到登录页面。
- axios的API接口统一管理
将API接口统一管理放置在/src/api
目录下面,并且将不同类型的接口按照文件夹分开,每个文件夹下面存放两个ts文件
- index.ts:存放API的方法和URL
- type.ts:存放API的请求参数类型和响应数据类型
// /src/api/user/type.ts
// 登录请求参数
export interface loginForm {
username:string,
password:string
}
interface dataType {
token: string
}
// 登录接口返回信息
export interface loginResponseData {
code:number,
data:dataType
}
// /src/api/user/index.ts
// 用户相关的接口
import requests from "../../utils/requests";
import type {
loginForm
} from "./type";
// 用户接口相关的URL
enum API {
LOGIN_URL = '/user/login',
USERINFO_URL = '/user/info'
}
// 登录接口的请求方法
export const reqLogin = (data:loginForm) => requests.post(API.LOGIN_URL, data)
注意:axios发送请求时的键值对参数通过params指定,请求时的请求体参数通过data属性指定。
axios的常用配置
{
// url:用于请求的服务器的URL
url: "/test",
// method:创建请求时使用的方法
method: "get",
// baseUrl:设置自动加到url前面的路径,如果url是一个绝对的请求路径则不会加到url前面
baseUrl: "http://localhost:8080/api",
// headers:设置请求头数据
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
// params:设置url上键值对形式的参数
params: {
"name": "zhangsan"
},
// data:设置添加到请求体上的参数
data: {
"name": "zhangsan"
},
// timeout:设置请求超时时间,单位为毫秒
timeout: 1000,
// responseType:设置请求响应参数格式,默认是json
responseType: "json",
}
axios的响应结构
{
// data:由服务器提供的响应数据
data: {},
// status:服务器响应的 HTTP 状态码
status: 200,
// statusText:来自服务器响应的 HTTP 状态信息
statusText: 'OK',
// headers:服务器响应的头
headers: {},
// config:为请求提供的配置信息
config: {},
// request:生成响应的请求
request: {}
}
Vue3整合router
- router依赖的下载
进入vue项目中,执行下面命令即可下载router依赖
npm i vue-router
- 编写路由常量
// /src/router/routers.ts
// 对外暴露路由
export const constantRoute = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
name: 'login'
},
]
- router的配置项
路由工作模式:
- history模式:createWebHistory(),URL更加美观,URL中不带#,但是需要服务端配合处理路径问题,否则刷新会出现404错误。
- hash模式:createWebHashHistory(),URL中会携带#,且在SEO优化仿麦呢相对更差。但是不需要服务端处理路径。
routes中的name属性,给路由命名,使得路由跳转时可以通过name使用。
// /src/router/index.ts
import { createRouter, createWebHashHistory } from "vue-router";
import { constantRoute } from "./routers";
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoute
})
export default router;
- router全局注入
// /src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app');
- router的跳转
声明式跳转:在template中进行路由跳转的配置
<!-- 跳转到指定路由,类似于a标签 -->
<router-link :to="{path: '/about'}">About</router-link>
<!-- 组件展示的指定位置:会根据路由和配置项中去匹配对应的组件,所以一个组件中最多只能有一个router-view标签 -->
<router-view />
编程式跳转:在script中通过ts代码实现路由跳转
路由相关的两个对象:
- useRouter:路由实例,通过该实例可以进行路由操作,例如导航、替换、前进或后退。
- useRoute:当前激活的路由对象。它是只读的,可以访问与当前路由相关的信息(如路由参数、查询参数、路径等)。
import { useRouter } from "vue-router";
const router = useRouter();
router.push({
path: '/index'
});
- router的嵌套
News组件中需要展示Detail组件内容的地方通过
<router-view />
标签展示。
// /src/router/routers.ts
{
path: '/news',
component: News,
name: 'News',
children: [
{
path: 'detail',
component: Detail,
name: 'detail'
}
]
}
- router的传参
6.1 路由传query参数
query参数表示键值对:/home?key=value
<!--News.vue:传递参数-->
<template>
<RouterLink
:to="{
name: 'detail',
query: {
id: id
}
}"
>
{{news.title}}
</RouterLink>
</template>
<!--Detail.vue:接收参数-->
<script setup lang="ts" name="Detail">
import { toRefs } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute()
// 接收成响应式数据
const { query } = toRefs(route);
// 接收成非响应式数据
const { query } = route
</script>
6.2 路由传params参数
在路由配置项中占位,也就是指定参数的key
// 在src/router/routers.ts:配置路由params参数占位
{
path:'/detail/:id',
name: 'detail',
component: News,
}
News.vue:传递参数
<!--声明式路由,传递params参数-->
<router-link
:to="{
// 注意params传参只能使用name,使用path会失效
name:'detail',
params:{
id: '1'
}
}"
>跳转</router-link>
<!-- 编程式导航 -->
<script setup lang='ts' name='News'>
import { useRoute, useRouter } from "vue-router";
const router = useRouter();
router.push({
// 注意params传参只能使用name,使用path会失效
name: '/detail',
params: {
id: "1"
}
})
</script>
Detail.vue:接收参数
import { toRefs } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute()
// 接收成响应式数据
const { params } = toRefs(route);
// 接收成非响应式数据
const { params } = route
6.3 路由的props属性
路由的props属性可以简化接收参数的方式,在路由项的配置中指定路由接收时可以通过defineprops接收。
// /src/route/routers.ts:配置路由项
// 写法一:开启props,把收到了每一组params参数,作为props传给Detail组件
{
name:'detail',
path:'detail/:id/:title/:content',
component:Detail,
props:true
}
// 写法二:将路由的query参数作为props传给Detail组件
{
name:'detail',
path:'detail,
component:Detail,
props(route){
return route.query
}
}
Detail组件接收时可以通过defineProps接收
// Detail.vue:接收参数
defineProps(['id', 'title', 'content'])
- router的重定向
路由重定向:将指定路由重定向到另一个路由,在router的配置项中配置
// 将 / 路由重定向到 /about
{
path:'/',
redirect:'/about'
}
Vue3整合pinia
pinia:用于实现组件共享数据,将数据保存到pinia的state中,每个组件都能获取到里面的值。
- pinia准备环境
执行下面命令,下载pinia依赖
npm i pinia
- pinia全局注入
// /src/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');
- pinia的使用
// //src/store/count.ts
import { defineStore } from "pinia";
import { ref } from "vue";
export const useCountStore = defineStore('count', () => {
const sum = ref(0)
function increment(value:number) {
sum.value += value
}
function decrement(value:number) {
sum.value -= value
}
return {sum, increment, decrement}
})
- pinia在组件中使用
// App.vue
import { useCountStore } from "@/store/count";
import { storeToRefs } from "pinia";
import { ref } from "vue";
// 数据
const countStore = useCountStore()
// 将store中的变量变成响应式
const { sum } = storeToRefs(countStore)
// 方法
function add() {
countStore.increment(1)
}
function minus() {
countStore.increment(1)
}
- pinia的监听
在组件中通过store.$subscribe()
实现监听,当store中的变量发生变化时,会执行subscribe()传入的回调函数。
监听某一个具体的变量
const countStore = useCountStore()
watch(() => countStore.sum, () => {
console.log('sum变量发生变化')
})
监听任意变量变化
const countStore = useCountStore()
countStore.$subscribe((mutate, state) => {
console.log("countStore中发生变化的变量信息", mutate);
console.log("countStore中变量变化后的状态", state);
})
Vue3整合Element-Plus
- 安装Element-Plus
npm i element-plus
pnpm install element-plus @element-plus/icons-vue
- Vue中引入Element-Plus
完整引入:会导致文件比较大,但是代码编写方便
// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
按需自动引入:文件不会那么大,但是需要什么就引入什么代码编写麻烦
需要先安装两个库:
npm i unplugin-auto-import
npm i unplugin-vue-components
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
- 实现国际化
当Element-Plus使用全局引入时
// main.ts
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//@ts-ignore忽略当前文件ts类型的检测否则有红色提示(打包会失败)
import zhCn from 'element-plus/es/locale/lang/zh-cn'
app.use(ElementPlus, {
locale: zhCn
})
当Element-Plus使用局部自动引入时
<!--App.vue-->
<script setup lang="ts">
import zhCn from 'element-plus/es/locale/lang/zh-cn'
</script>
<template>
<el-config-provider :locale="zhCn">
<div class="app">
</div>
</el-config-provider>
</template>
Vue3整合Element-Plus-Icon
- 下载Element-plus-Icon
npm install @element-plus/icons-vue
Vue3整合SVG图标
- 下载SVG依赖
npm install vite-plugin-svg-icons -D
- 配置SVG插件
// vite.config.ts
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export default () => {
return {
plugins: [
createSvgIconsPlugin({
// 指定svg图标的存放路径
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// 指定生成图表元素的id格式
symbolId: 'icon-[dir]-[name]',
}),
],
}
}
- 编写SVG配置代码
// main.ts
import 'virtual:svg-icons-register'
- 添加需要的SVG文件
SVG文件获取:www.iconfont.cn/
将SVG文件放入 /src/assets/icons
5. 封装SVG组件
<!--/src/assets/icons/index.vue-->
<template>
<div>
<svg :style="{ width: width, height: height }">
<use :xlink:href="prefix + name" :fill="color"></use>
</svg>
</div>
</template>
<script setup lang="ts">
defineProps({
//xlink:href属性值的前缀
prefix: {
type: String,
default: '#icon-'
},
//svg矢量图的名字
name: String,
//svg图标的颜色
color: {
type: String,
default: ""
},
//svg宽度
width: {
type: String,
default: '16px'
},
//svg高度
height: {
type: String,
default: '16px'
}
})
</script>
- 使用SVG组件
注意:使用SVG组件的前提是需要在
/src/assets/icons
目录下面放置好了对应的svg文件,同时SvgIcon标签的name属性填写文件名。
<script setup lang="ts">
import SvgIcon from './components/svgIcon/index.vue'
</script>
<template>
<div class="app">
<SvgIcon name="home" color="red"></SvgIcon>
</div>
</template>
Vue3整合Sass
Sass:一种扩展 CSS 的预处理器,它添加了一些功能和特性,使得 CSS 的编写更加高效和模块化。它最终会被编译为标准的 CSS 文件,从而可以在浏览器中运行。
- 下载Sass
npm install sass sass-loader --save-dev
- 设置sass
<style scoped lang="scss">
</style>
Vue3整合mock
mock:通过模拟后端 API 请求,返回假数据,来加速前端开发和调试的一个常用方案。
- 下载mock相关依赖
pnpm install -D vite-plugin-mock mockjs
- 配置mock插件
注意:vite.config.ts默认可能是以对象的形式编写的,需要改成函数式写法。
- command === 'serve':仅在开发环境启用 Mock
- mockPath: 'mock':Mock 数据文件的存放目录
- logger: true:在控制台打印请求日志
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig(({ command })=> {
return {
plugins: [
viteMockServe({
localEnabled: command === 'serve',
mockPath: 'mock',
logger: true,
}),
],
}
})
- 编写假的接口
// /mock/user.ts
//用户信息数据
function createUserList() {
return [
{
userId: 1,
username: 'wenxuan',
password: 'wenxuan',
desc: '平台管理员',
token: 'wenxuan Token',
},
]
}
export default [
// 用户登录接口
{
url: '/api/user/login',//请求地址
method: 'post',//请求方式
response: ({ body }) => {
//获取请求体携带过来的用户名与密码
const { username, password } = body;
//调用获取用户信息函数,用于判断是否有此用户
const checkUser = createUserList().find(
(item) => item.username === username && item.password === password,
)
//没有用户返回失败信息
if (!checkUser) {
return { code: 201, data: { message: '账号或者密码不正确' } }
}
//如果有返回成功信息
const { token } = checkUser
return { code: 200, data: { token } }
},
},
// 获取用户信息
{
url: '/api/user/info',
method: 'get',
response: (request) => {
//获取请求头携带token
const token = request.headers.token;
//查看用户信息是否包含有次token用户
const checkUser = createUserList().find((item) => item.token === token)
//没有返回失败的信息
if (!checkUser) {
return { code: 201, data: { message: '获取用户信息失败' } }
}
//如果有返回成功信息
return { code: 200, data: {checkUser} }
},
},
]
- 测试接口是否可用
注意:测试接口前需要先下载axios的依赖
// /src/main.ts
// 测试用户登录接口
import axios from 'axios'
axios({
url: '/api/user/login',
method: 'post',
data: {
username: 'wenxuan',
password: 'wenxuan'
}
})
Vue3整合progress
progress:可以实现路由进度条功能
- 下载progress依赖
npm i progress
- 添加路由进度条
通过路由全局首位实现路由进度条
// /src/router/index.ts
import { createRouter, createWebHashHistory } from "vue-router";
import { constantRoute } from "./routers";
import Nprogress from 'nprogress';
import 'nprogress/nprogress.css';
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoute
})
// 全局前置路由
router.beforeEach((to:any, from:any, next:any) => {
Nprogress.start()
next()
})
// 全局后置路由
router.afterEach((to:any, from:any) => {
Nprogress.done()
})
export default router;
- 修改进度条的颜色
修改进度条颜色之后,需要重启项目才能生效。
// /node_modules/nprogress/npgress.css
#nprogress .bar {
background: #29d;
}
- 关闭路由条的圆圈加载
// /src/router/index.ts
Nprogress.configure({ showSpinner: false })
项目常用功能开发
路由不存在重定向404
// /src/router/routers.ts
// 对外暴露路由
export const constantRoute = [
// 与上述路由均不匹配时重定向到404
{
path: '/:pathMatch(.*)*',
redirect: '/404',
name: 'Any'
},
]
登录功能TOKEN保存
登录功能TOKEN保存到pinia和本地存储
- 封装本地存储TOKEN的工具方法。
- 在pinia中提供token变量和userLogin登录方法。
- userLogin方法中调用登录接口,并根据返回值决定对应的登录成功和登录失败逻辑。
封装本地存储TOKEN的方法
// /src/utils/token.ts
export const SET_TOKEN = (token:any) => {
localStorage.setItem("TOKEN", token)
}
export const GET_TOKEN = () => {
return localStorage.getItem("TOKEN")
}
pinia实现token的保存
// /src/store/modules/user.ts
import { defineStore } from "pinia";
import { ref } from "vue";
import { reqLogin } from "@/api/user";
import type { loginForm, loginResponseData } from "../../api/user/type";
import { GET_TOKEN, SET_TOKEN } from "../../utils/token";
export const useUserStore = defineStore('User', () => {
const token = ref(GET_TOKEN())
async function userLogin(data:loginForm) {
// 调用登录接口
let result:loginResponseData = await reqLogin(data)
if(result.code == 200) {
token.value = (result.data.token as string)
// 将token本地存储
SET_TOKEN(result.data.token)
return 'ok'
} else {
// 登录失败
return Promise.reject(new Error(result.data.message))
}
}
return {token, userLogin}
})
动态显示组件
<template>
<component :is='componentName'></component>
</template>
<script setup lang='ts'>
import { ref } from 'vue'
const componentName = ref('Home')
</script>
路由添加动画特效
路由添加动画特效:需要将路由封装到一个组件中,然后组件通过router-view
展示出来。
<template>
<div class='main'>
<router-view v-slot="{ Component }">
<transition name="fade">
<!-- 渲染layout一级路由的子路由 -->
<component :is="Component" />
</transition>
</router-view>
</div>
</template>
<style scoped lang="scss">
.fade-enter-from {
opacity: 0;
transform: scale(0);
}
.fade-enter-active {
transition: all 1s;
}
.fade-enter-to {
opacity: 1;
transform: scale(1);
}
</style>
组件刷新功能
- 通过一个变量和
v-if
实现组件的销毁和创建,从而实现组件的刷新。 - 通过nextTick函数变化变量的值,nextTick可以在组件销毁时调用。
nextTick:组件更新循环接收后执行的回调函数,这是异步函数。比如组件销毁完成后会执行nextTick传入的异步函数。
<template>
<component :is="Component" v-if="flag"/>
</template>
<script lang='ts' setup name='Main'>
import useLayoutSettingStore from '@/store/modules/setting';
import { nextTick, ref, watch } from 'vue';
// 变量
const layoutSettingStore = useLayoutSettingStore()
// 控制Main组件是否销毁
const flag = ref(true)
// 函数
// 监听设置仓库中的刷新变量是否变化
watch(() => layoutSettingStore.refsh, () => {
// 刷新Main组件
flag.value = false
nextTick(() => {
flag.value = true
})
})
</script>
全屏切换功能
const fullScreen = () => {
if(document.fullscreenElement) {
document.exitFullscreen()
} else {
document.documentElement.requestFullscreen()
}
}
登录和退出功能
退出登录功能
- 向服务器发送退出登录请求
- pinia仓库中相关数据清空(token、username、role、permission)
- 跳转到登录页面
路由鉴权功能
常见的路由鉴权功能:
- 未登录时(不存在token):能访问/login路由,不能访问其他路由
- 已登录时(存在token):不能访问/login路由,能访问其他路由
- 用户是否登录可以通过判断pinia中是否含有token,
页面标题动态变化
路由变化页面标题动态变化
- 配置路由项中在
meta
中添加title属性 - 配置全局后置路由设置文档的标题
document.title
// /src/router/routers.ts
export const constantRoute = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
name: 'login',
meta: {
title: '登录',
},
}
]
// /src/router/index.ts
import { createRouter, createWebHashHistory } from "vue-router";
import { constantRoute } from "./routers";
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoute
})
// 配置全局后缀路由守卫
router.afterEach((to:any, from) => {
document.title = to.meta.title
})
export default router;
暗黑模式的设置
- 添加Element-dark的样式
// /src/main.ts
import 'element-plus/theme-chalk/dark/css-vars.css'
- 通过在html标签上设置dark样式即可
const changeDark = () => {
// 获取html根节点
const htmlDocuemnt = document.documentElement
darkTitle.value ? htmlDocuemnt.className = 'dark' : htmlDocuemnt.className = ''
}
权限划分功能
权限的分类
- 路由权限:指能不能显示出路由,跳转到对应页面的路由。
- 按钮权限:指有没有指定按钮的权限,比如增删改查按钮的权限。
路由的划分
- 常量路由:constantRoute,任意用户都能访问的路由。
- 异步路由:asnycRoute,不同用户访问的权限不同的路由。
- 任意路由:anyRoute,与常量路由、异步路由都不匹配的路由(404)。
用户路由权限的实现:在pinia小仓库中保存用户拥有的路由权限信息。
用户按钮权限的实现: