第一章 Vue3
1. Vue3的基础
1.1 Vue3环境安装
Vue官网地址:
https://cn.vuejs.org/
-
安装nodejs
安装地址
官网地址: https://nodejs.org/en 安装地址: https://nodejs.org/dist/v20.11.1/node-v20.11.1-x64.msi
-
设置npm的安装源(可选)
-
临时指定淘宝镜像源
$ npm --registry https://registry.npm.taobao.org install express
-
永久指定淘宝镜像源
$ npm config set registry https://registry.npm.taobao.org
-
检查vue包信息
npm info vue
C:\Users\004>npm info vue vue@3.4.21 | MIT | deps: 5 | versions: 491 The progressive JavaScript framework for building modern web UI. https://github.com/vuejs/core/tree/main/packages/vue#readme keywords: vue dist .tarball: https://registry.npmjs.org/vue/-/vue-3.4.21.tgz .shasum: 69ec30e267d358ee3a0ce16612ba89e00aaeb731 .integrity: sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA== .unpackedSize: 2.2 MB dependencies: @vue/compiler-dom: 3.4.21 @vue/runtime-dom: 3.4.21 @vue/shared: 3.4.21 @vue/compiler-sfc: 3.4.21 @vue/server-renderer: 3.4.21 maintainers: - soda <npm@haoqun.me> - yyx990803 <yyx990803@gmail.com> - posva <posva13@gmail.com> dist-tags: alpha: 3.4.0-alpha.4 csp: 1.0.28-csp legacy: 2.7.16 v2-latest: 2.7.16 beta: 3.4.0-beta.4 latest: 3.4.21 rc: 3.4.0-rc.3
-
-
用最版本创建Vue3项目:
在控制台,执行npm命令,
$ npm create vue@latest
✔ Project name: … <your-project-name> ✔ Add TypeScript? … No / Yes ✔ Add JSX Support? … No / Yes ✔ Add Vue Router for Single Page Application development? … No / Yes ✔ Add Pinia for state management? … No / Yes ✔ Add Vitest for Unit testing? … No / Yes ✔ Add an End-to-End Testing Solution? … No / Cypress / Playwright ✔ Add ESLint for code quality? … No / Yes ✔ Add Prettier for code formatting? … No / Yes
说明: 1. Vue3拥抱TypeScript, 因此在Add TypeScript 选Yes 2. 其他默认选No.
-
vue2的创建方式
-
安装脚手架
$ npm install -g @vue/cli
-
创建项目
$ vue create my-project
-
运行项目
$ cd my-project $ npm run serve
-
1.2 Vue项目运行
-
进入项目路径,执行npm install ,系统将下载相关依赖到node_modules的依赖
$ cd <project_dir> $ npm install
-
执行 npm run dev, 运行vue3项目
$ npm run dev
说明: 如果要发布项目,则执行npm run build, 则脚手架将吧代码发布到 dist目录, 后续只需将dist目录部署到http服务器即可
1.3 Vue项目目录结构
-
入口文件 index.html
<!DOCTYPE html> <html lang="en"> <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> <script type="module" src="/src/main.ts"></script> </body> </html>
说明: 1. 所有Vue项目,军放置在一个<div>标签里,需要对该div标签指定一个id, 然后需要在脚本中指定该id 2. 入口函数,定义在src/main.ts里 3. 通常开发一个Vue3项目,该index文件不需要更改
-
src目录
该目录是游戏开发的主要目录,几乎所有开发工作均在该目录内部
-
main.ts
// 引入createApp用于创建应用 import {createApp} from 'vue' // 引入 App跟组件 import App from './App.vue' createApp(App).mount('#app');
说明: 1. 该文件定义了整个Vue项目的根组件 2. 该文件需要指定根的id, 并且启动该组件
-
App.vue
<template> <Person/> </template> <script lang="ts"> import Person from './components/Person.vue'; // 脚本, ts或js export default { name:"App", // 组件名 components:{Person} } </script>
说明: 1. 该文件是整个Vue的根组件定义了整个项目的框架, 在模板中,可以自定义多个自定义组件 2. Vue3可以支持相同组件,定义多次,Veu2中不支持 3. 因为脚本使用的ts, 因此在<Script>标签中,需要添加lang="ts"
-
components目录
通常所有的子组件,均放置在 components目录下,
-
1.4 Vue3 Setup用法
-
Vue3中可以兼容Vue2的语法,但是Vue2中不能包含Vue3的语法
-
Vue2的结构:
- data: 存放数据
- methods: 存放用户自定义函数
- beforeCreate: 执行create前的钩子函数
- created: 在实例创建完成立即被调用
- mounted:在挂载之前被调用,此时可以访问当前组件的dom对象
- …
-
Vue3 的核心是,组件开发方式,功能封装在一起,而Vue2在是在data中封装各个功能的数据,对于每个钩子函数,也都放置各个功能的代码,这样功能和功能之间穿插在一起.
-
Vue3的Setup函数是和 data这些平级的, 其内部处理所有的数据,和功能
<script lang="ts"> export default { name: 'Person', setup(){ console.log('@@', this) // setup里的this 是 undefined, vue3中弱化this了 // 数据, 原来 写在data中,且是响应式,写这里,不是响应式的 let name = "张三"; // 此时变量不是响应式 let age = 18; let tel = '13300334556'; // 方法 function changeName(){ name = '李四'; } function changeAge(){ age += 1; } function showTel(){ alert(tel); } //return ()=>"<div><h1>Setup返回值</h1></div>" //return function(){return "haha"} // 返回这个字典,实际就是通过模板进行渲染 // return {name:name, changeName:changeName} // 返回的也可能是渲染函数 return {name, age, changeName, changeAge, showTel} } } </script>
说明: 1. 返回值: 实际返回的是渲染函数, 如果传递的是一个字典, 则将返回的字典作为变量,对模板进行渲染,如果返回的是一个字符串,则直接渲染字符串 2. 在模板中,模板语法,和vue2一致, 使用{{变量名}}进行访问,只是对于默认的setup里的let, 其变量不是响应式变量,如果需要响应式变量,需要用ref或reactive进行修饰. 3. vue3兼容vue2的写法,因此,在有setup函数时,在data中仍然可以有数据,因为setup执行时机是在beforeCreate之前, 所有在setup中可以访问vue2中data定义的属性,但是反过来,不行 4. 建议如果使用vue3语法,中间不要夹杂vue2的语法.
1.5 Setup语法糖的使用
因为每次在setup中,定义了变量后,需要手动在返回值中将需要渲染的变量或者函数进行返回,对于开发者不友好,所以有了Setup语法糖的概念
<!--
npm i vite-plugin-vue-setup-extend -D
<script setup lang="ts" name:"Person">
-->
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script setup lang="ts">
let name = "张三"; // 此时变量不是响应式
let age = 18;
let tel = "13300334556";
function changeName(){
name = '李四';
}
function changeAge(){
age += 1;
}
function showTel(){
alert(tel);
}
</script>
说明:
1. 需要在scrpt中指定语言是ts
2. 在script中,不需要手动指定哪些变量,哪些函数是需要渲染,这个过程由底层帮助实现
3. 如果组件名和组件文件的名称始终一致,可以不需要上面的 export default {name:'Person'}, 但是如果一致,需要保留,或者通过第三方插件vite-plugin-vue-setup-extend来移除该脚本. 此时,名称需要写在 setup的script中的 name属性中, <script setup lang="ts" name:"Person">
2. Vue核心语法
2.1 响应式数据
2.1.1 基本数据响应式实现 Ref的使用
对于基本数据,通过Ref将原来数据进行包裹
<script setup lang="ts">
import {ref} from 'vue'
// ref 返回一个RefImpl对象,这个对象是一个响应式对象, 其value,是实际的对象
// ref是对基本类型的响应数据;
let name = ref("张三");
let age = ref(18);
let tel = "13300334556"; // 此时变量不是响应式
function changeName(){
// 因为name是 RefImpl对象,那么实际访问该对象时,需要用.value进行访问
name.value = '李四';
}
function changeAge(){
age.value += 1;
}
function showTel(){
alert(tel);
}
</script>
说明:
1. 需要从vue脚本中引入ref函数
2. ref返回的是一个RefImpl对象, 在模板中,范围时,直接用对象名称,而在ts脚本中,访问,采用obj.value进行
3. ref值对基础类型有用.
2.1.2 复杂响应类性对象 ref的使用
<script setup lang="ts">
import {ref} from 'vue'
// reactive, 返回 Proxy对象
// 包裹的对象放置.Target对象内部, reactive内,还可以包含数组,等
let car = ref({
brand:'奔驰',
price:100
})
let games = ref([
{id:'abc1001', name:'王者荣耀'},
{id:'abc1002', name:'元神'},
{id:'abc1003', name:'三国志'}
])
console.log(car)
function changePrice(){
car.value.price += 10;
console.log("car price:", car.value.price)
}
function changeGameName() {
games.value[0].name = '魔兽世界';
}
</script>
说明:
1. Ref除了可以修饰基本数据类型,也可以修饰类和列表类型
2. Ref如果是基本类型,其内部实现的value则是基本类型,如果其修饰的是对象类型,其内部value则是6.3节中的reactive类型,即Ref对对象的响应式实现,是借助于reactive函数.
3. Ref实现对象的响应式,其使用,仍然需要 RelImpl.value.xxx进行访问.
2.1.3 复杂响应类型对象 reactive的使用
复杂对象包含 对象(字典), 列表, reactive只能包裹 对象
template:
<template>
<div class="person">
<h2>一辆{{car.brand}}车,价值{{car.price}}w</h2>\
<button @click="changePrice">修改价格</button>
<div>
<ul>
<li v-for="game in games" :key="game.id">
<span>{{game.id}}</span>----<span>{{ game.name }}</span>
</li>
</ul>
<button @click="changeGameName">更改游戏名字</button>
</div>
<div>
<div>
<a> obj.b.c.d val: {{ obj.b.c.d }}</a>
</div>
<button @click="changeVal">更改obj.b.c.d</button>
</div>
</div>
</template>
script:
<script setup lang="ts">
import {reactive} from 'vue'
// reactive, 返回 Proxy对象
// 包裹的对象放置.Target对象内部, reactive内,还可以包含数组,等
let car = reactive({
brand:'奔驰',
price:100
})
let games = reactive([
{id:'abc1001', name:'王者荣耀'},
{id:'abc1002', name:'元神'},
{id:'abc1003', name:'三国志'}
])
let obj = reactive({
b: {
c: {
d:125
}
}
})
console.log(car)
function changePrice(){
car.price += 10;
console.log("car price:", car.price)
}
function changeGameName() {
games[0].name = '魔兽世界';
}
function changeVal() {
obj.b.c.d += 1
}
</script>
说明:
1. 通过reactive函数包裹,返回实际的对象是Proxy对象, 而真实际的数据,在proxy.Target对象内部. 而reactive函数参数,是{}和[]数组对象。
2. 在ts代码中,访问和直接访问完全是一致的。
3. 对于Proxy对象,我们通常称之为响应式对象, 而对于reactive的参数里的字典{}称为原生对象。
2.1.4 Ref和reactive的区别和使用场景
- 各自功能
- ref 用来定义: 基本类型数据、对象类型数据
- reactive 用来定义: 对象类型数据
- 区别:
- ref 创建的变量必须使用.value(可使用volar插件自动添加.value)
- reactive 重新分配一个新对象, 会失去响应式(可以使用Object.assign去整体替换)。
- 使用场景:
- 若需要一个基本类型的响应式数据,必须用ref
- 若需要一个响应式对象,层级不慎,ref, reactive都可以使用
- 若需要一个响应式对象,且层级较深,推荐使用reactive.
2.1.5 toRefs和toRef的使用
当需要对一个对象的字段进行解包时, 如果直接解包,则数据是非响应式的,只有使用toRefs和toRef解包,数据才是响应式的.
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<div class="person">
<h3>{{ person.name }}</h3>
<h3>{{ person.age }}</h3>
<button @click="ChangeName">修改名称</button>
<button @click="ChangeAge">修改年龄</button>
<button @click="ChangeAge2">修改年龄2</button>
</div>
</template>
<script lang="ts">
export default {
name: 'PersonVue'
}
</script>
<script setup lang="ts">
import { reactive, toRefs, toRef} from 'vue'
let person = reactive({
name: "张三",
age: 18
})
// 默认对象解包,其属性,不是响应式的.
// let {name, age} = person;
// 使用 toRefs, 将对具体的内容的字段进行分别解包,其顺序和传入的顺序是一致的. 当使用toRefs解包后, 属性需要用.value进行访问,而用以前的对象person.name, 仍然可以更改和响应.
let {name, age} = toRefs(person);
// 使用 toRef, 对指定的字段进行获取,更改.
let age3 = toRef(person, 'age');
function ChangeName(){
name.value += "~";
}
function ChangeAge() {
age.value += 1;
}
function ChangeAge2() {
person.age += 1;
age3.value += 10;
}
</script>
说明:
1. 使用toRefs, 对对象里具体的数据,按照传入的键值顺序,进行一次解包.
2. 当解包后,其变量,可以直接在模板中使用,如果在ts脚本中使用的话,则需要按照ref的使用规则,添加.value再进行使用
3. 解包后的变量,实际和原来对象里的变量,是指向同一个数据,当更改了解包后的变量后,其原来对象的变量也同时被更改
4. 使用解包后的Ref变量进行更改后,原来对象的属性,仍然可以直接更改.
2.2 Computed计算属性的使用
<template>
<div class="person">
<div>
姓:<input type="text" v-model="firstName"/>
</div>
<div>
名:<input type="text" v-model="lastName"/>
</div>
<h3>{{ fullName }}</h3>
<input type="button" @click="ChangeFullName" value="更改"/>
</div>
</template>
<script lang="ts">
export default {
name: 'PersonVue'
}
</script>
<script setup lang="ts">
import {ref, computed} from 'vue'
let firstName = ref("zhang")
let lastName = ref("san")
let fullName = computed({
get() {
return firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value;
},
set(v){
let [nameF, nameL] = v.split('-');
firstName.value = nameF;
lastName.value = nameL;
}
})
function ChangeFullName() {
fullName.value = 'li-si'
}
</script>
说明:
1. Computed用于在html模板中属性更新后的回调, 当有属性依赖于其他属性时, 需要在该函数内进行属性的更新.
2. Vue设计原则, 在模板中,尽量少的逻辑,或者没有逻辑.
3. 用Computed参数直接放置箭头函数,则该属性是只读的.
4. Computed函数返回的对象是 ComputedRefImpl, 脚本内部访问时,需要写.value才能进行操作.
5. 当把fullname作为数据保存,则逻辑不需要放置在模板中,而可以在脚本中进行实现.
2.3 Watch的使用
- 作用: 监视数据的变化
- Vue3中Watch能监视的四种数据
- ref定义的数据
- reactive定义的数据
- 函数返回的一个值(getter函数)
- 一个包含上述内容的数组
2.3.1 情况1: Ref定义的基本类型
<template>
<div class="person">
<h3>{{sum}}</h3>
<div><button @click="changeSum">自加</button></div>
<div><h4>{{tips}}</h4></div>
</div>
</template>
<script lang="ts">
export default {
name: 'PersonVue'
}
</script>
<script setup lang="ts">
import {ref, watch} from 'vue'
let sum = ref(0);
let tips = ref('');
function changeSum(){
sum.value += 1;
}
let sumStopWatcher = watch(sum, (newVal, oldVal)=>{
if (newVal>10){
sumStopWatcher();
}
tips.value = `sum 变化前:${oldVal} 变化后:${newVal}`;
})
</script>
2.3.2 情况2: Ref定义的对象类型
<template>
<div class="person">
<div>
<h2>情况二: 监视对象数据类型</h2>
<h3>{{ person.name }}</h3>
<h3>{{ person.age }}</h3>
<div>
<button @click="changeName">改变名称</button>
<button @click="changeAge">改变年龄</button>
<button @click="changeObject">改变对象</button>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'PersonVue'
}
</script>
<script setup lang="ts">
import {ref, watch} from 'vue'
let person = ref({
name: "zhangsan",
age:20
})
// 函数
function changeName() {
person.value.name += "~";
}
function changeAge(){
person.value.age += 1;
}
function changeObject() {
person.value = {"name":"李四", "age":999}
}
/* 监视对象,
参数1: 需要监视的对象
参数2: 变化时的回调函数,如果是更改对象的属性,实际对象并没有变化,因此,newVal和oldVal是一致.
参数3: 配置参数(deep, 是否监听属性变化, immediate, 是否已启动就监听)
*/
watch(person, (newVal, oldVal)=>{
console.log(oldVal,oldVal);
}, {deep:true})
</script>
2.3.3 情况3: reactive定义的对象类型
<template>
<div class="person">
<div>
<h2>情况三: 监视对象数据(reactive)类型</h2>
<h3>{{ person.name }}</h3>
<h3>{{ person.age }}</h3>
<div>
<button @click="changeName">改变名称</button>
<button @click="changeAge">改变年龄</button>
<button @click="changeObject">改变对象</button>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'PersonVue'
}
</script>
<script setup lang="ts">
import {reactive, watch} from 'vue'
let person = reactive({
name: "zhangsan",
age:20
})
// 函数
function changeName() {
person.name += "~";
}
function changeAge(){
person.age += 1;
}
function changeObject() {
Object.assign(person, {"name":"李四", "age":999})
}
/* 监视对象,
参数1: 需要监视的reactive对象
参数2: 变化时的回调函数,如果是更改对象的属性,实际对象并没有变化,因此,newVal和oldVal是一致.
参数3: 对于reactive对象,部需要deep:true, 默认就是深度监测,且手动都无法更改成deep:false;
*/
watch(person, (newVal, oldVal)=>{
console.log(oldVal,oldVal);
}, {deep:true})
</script>
2.3.4 情况四: 监视对象中的某个属性
监视ref或reactive定义的【对象类型】数据中的某个属性,注意点如下:
- 若改属性值不是【对象类型】, 需要写出函数形式
- 若该属性值依然是【对象类型】,可以直接便,也可以写成函数,不过建议写出函数
- 监视的是对象,直接写函数式,需要关注对象内部,需要手动开启深度监视
<template>
<div class="person">
<div>
<h2>情况四: 监视对象数据的指定属性</h2>
<h3>{{ person.name }}</h3>
<h3>{{ person.age }}</h3>
<h4>车1:{{person.car.car1}} 车2:{{person.car.car2}}</h4>
<div>
<button @click="changeName">改变名称</button>
<button @click="changeAge">改变年龄</button>
<button @click="changeCar1">改变车1</button>
<button @click="changeCar2">改变车2</button>
<button @click="changeAllCar">改变整个车</button>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'PersonVue'
}
</script>
<script setup lang="ts">
import {reactive, watch} from 'vue'
let person = reactive({
name: "zhangsan",
age:20,
car:{
car1:"奥迪",
car2:"奔驰"
}
})
// 函数
function changeName() {
person.name += "~";
}
function changeAge(){
person.age += 1;
}
function changeCar1() {
person.car.car1 = "宝马"
}
function changeCar2() {
person.car.car2 = "捷豹"
}
function changeAllCar() {
person.car = {car1:"艾玛", car2:"乐迪"}
}
// 直接监视对象的基础属性, v1和v2是基础值
watch(()=>person.name, (v1, v2)=>{
console.log("name:",v1, v2)
})
// 监视对象, 虽然对象可以直接监视,但是当整个对象变化时,无法监视,因此最好直接将对象也写函数式
watch(()=>person.car, (v1, v2)=>{
console.log("car changed, ", v1.car1, v2.car2, v2.car1, v2.car2)
}, {deep:true})
</script>
2.3.5 情况五: 同时监视一个对象的多个属性
<script setup lang="ts">
import {reactive, watch} from 'vue'
let person = reactive({
name: "zhangsan",
age:20,
car:{
car1:"奥迪",
car2:"奔驰"
}
})
// 函数
function changeName() {
person.name += "~";
}
function changeAge(){
person.age += 1;
}
function changeCar1() {
person.car.car1 = "宝马"
}
function changeCar2() {
person.car.car2 = "捷豹"
}
function changeAllCar() {
person.car = {car1:"艾玛", car2:"乐迪"}
}
// 需要监视对象的多个属性,则需要将多个属性放置到一个数组中,且每个数组都是函数式, 其变化值v1,v2,将也是一个数组,数组的每个元素和前面声明的类型一致
watch([()=>person.name, ()=>person.car.car1, ()=>person.car.car2], (v1, v2)=>{
console.log("name:",v1, v2)
})
</script>
2.4 WatchEffect
- 立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时,重新执行该函数。
- watch和watchEffect
- 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
- watch: 要明确指出监视的数据
- watchEffect: 不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)
<template>
<div class="person">
<div>
<h2>watchEffect使用</h2>
<h3>英语:{{ student.English }}</h3>
<h3>语文:{{ student.Chinese }}</h3>
<h3>数学:{{ student.Math }}</h3>
<div>
<button @click="changeEnglish">改变英语</button>
<button @click="changeChinese">改变语文</button>
<button @click="changeMath">改变数学</button>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'PersonVue'
}
</script>
<script setup lang="ts">
import {reactive, watchEffect} from 'vue'
let student = reactive({
English: 10,
Chinese:10,
Math:10,
})
// 函数
function changeChinese() {
student.Chinese += 10;
}
function changeEnglish() {
student.English += 10;
}
function changeMath() {
student.Math += 10;
}
watchEffect(()=>{
if (student.Chinese + student.English + student.Math>=100) {
console.log(`总分超过100分了! English${student.English} Chinese:${student.Chinese} Math:${student.Math}`)
}
})
</script>
2.5 Ref标签属性
Ref标签的目的,是为了保证每个组件(vue)内部定义的标签,不会和其他的vue进行重名。 如果采用id,进行设计,则如果多个组件重名,则会爆发冲突.
- Ref可以用在普通html标签上,其实际拿到的是html的dom元素
- Ref还可以用在组件对象上, 其拿到的是当前组件对象的实例
- 如果当前组件对象实例中,要访问其子对象,不能直接获取,而要使用defineExpose来进行先把对象暴露出来,然后才可以访问对应的子对象
person.vue
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<div class="person">
<div>
<h2>watchEffect使用</h2>
<h3 ref="title">我是标题</h3>
<div>
<button @click="obtainTitle">获取标题</button>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'PersonVue'
}
</script>
<script setup lang="ts">
import {ref} from 'vue'
// 这里的title必须要和标签中的ref的名字一致
let title = ref();
// 函数
function obtainTitle() {
alert(title.value.innerHTML)
}
</script>
app.vue
<template>
<h3 ref="title">AppVue</h3>
<button @click="getTitle">获取标题</button>
<Person/>
</template>
<script lang="ts">
export default {
name:"AppInstance" // 组件名
}
</script>
<!--这里采用Vue3写法, 当引入Person组件后,系统会自动进行注册和返回,所有只需要导入即可-->
<script lang="ts" setup>
import {ref} from 'vue';
import Person from './components/Person.vue';
let title = ref();
function getTitle() {
//console.log(title.value.innerHTML);
alert(title.value.innerHTML);
}
</script>
说明:
1. 如果需要从App中访问 Person中, 需要引入 import {defineExpose} from 'vue', 在Person.vue中,执行脚本 defineExpose({a,b,c}), 如果a,b,c是对应的Ref对象,那么,在App中则可以访问person.a, person.b, person.c;
2.6 TypeScript的接口, 泛型,以及自定义类型
-
TypeScript 定义接口,目的的为了规范代码,在写代码时,进行规避编译错误。
-
通常定义接口没单独放置一个ts文件,建议放置到types目录下
-
定义接口时,interface 关键字, 定义字段时,需要指定每个字段的类型
-
接口定义后,需要用关键字export进行到出(也可以全部统一导出类型)
export interface IPerson { id: string; name: string; age: number; }
-
使用接口时,需要先要引用该接口, 引用时,为了区分和变量区别,需要增添关键字type
import { type IPerson, type IPersonList } from "@/types"
-
-
泛型, 常用的泛型容器主要是数组
let personList:Array<IPerson> = {[xxx], [xxx]};
-
自定义类型
export type IPersonList = Array<IPerson>; // 使用时,可以用IPersonList代替Array<IPerson>;
完整例子:
types.ts:
// 人的接口定义
export interface IPerson {
id: string;
name: string;
age: number;
}
// 人这个数组的接口定义, 可以直接用泛型Array定义,也可以用[]定义数组.
export type IPersonList = Array<IPerson>;
//export type IPersonList = IPerson[];
Person.vue:
<script setup lang="ts">
// 导入接口, 不管从哪个位置,只要添加@/, 则都会定位到 src目录, 导入的接口和普通变量为了区分,因此增加一个 type 关键字.
import { type IPerson, type IPersonList } from "@/types"
let person:IPerson = {name:"zhangsan", age:20, id:"xab0001"};
let personList : IPersonList = [
{name:"lisi", id:"xab0002", age:21},
{name:"wangwu", id:"xab0003", age:24},
{name:"zhaoliu", id:"xab0004", age:18}
]
</script>
2.7 Prop的使用【父节点向子节点传递数据】
- 普通传值
App.vue(父组件)
<template>
<Person title="四年级二班学生" :studentList="personList"/>
</template>
<script lang="ts">
export default {
name:"AppInstance" // 组件名
}
</script>
<!--这里采用Vue3写法, 当引入Person组件后,系统会自动进行注册和返回,所有只需要导入即可-->
<script lang="ts" setup>
import {reactive} from 'vue';
import Person from './components/Person.vue';
import {type IPersonList} from '@/types'
let personList:IPersonList = reactive([
{name:"zhangsan",age:23,"id":"AP0001"},
{name:"lisi",age:43,"id":"AP0002"},
{name:"wangwu",age:37,"id":"AP0003"}
]);
</script>
Person.vue(子组件)
<script setup lang="ts">
import {withDefaults} from 'vue'
import {type IPersonList} from '@/types'
//defineProps(["title", "studentList"])
/*
let parentData = defineProps(["title", "studentList"])
console.log(parentData.title)
console.log(parentData.studentList)
*/
// 指定参数类型,来约束父组件传递的值.
//defineProps<{studentList:IPersonList, title:string}>()
withDefaults(defineProps<{studentList:IPersonList, title:string}>(), {title:"标题默认值", studentList:[
{name:"unknown",age:99,"id":"AP0001"}]})
</script>
-
子组件指定父组件传递的类型
传递类型时,使用defineProps时, 采用泛型方法,泛型内部,传递一个需要的结构 defineProps<{studentList:IPersonList, title:string}>()
-
子组件在父组件不传递时赋予默认值, 通过withDefaults进行
withDefaults(defineProps<{studentList:IPersonList, title:string}>(), {title:"标题默认值", studentList:[ {name:"unknown",age:99,"id":"AP0001"}]})
2.8 组件的生命周期
又称生命周期函数,生命周期钩子,指组件会经历的关键时间点, 测试可以在函数内增添debugger;
-
Vue2生命周期
-
创建
刚刚分配内存时
- 创建前 beforeCreate
- 创建完毕 created
-
挂载
挂载是当前界面准备好,已经可以访问dom对象了.
- 挂载前 beforeMount
- 挂载完毕 mounted
-
更新
- 更新前 beforeUpdate
- 更新完毕 updated
-
销毁
- 销毁前 beforeDestroy
- 销毁完毕 destroyed
-
-
Vue3 生命周期
- 创建
- setup
- 挂载
- onBeforeMount
- onMounted
- 更新
- onBeforeUpdate
- onUpdated
- 销毁
- onBeforeUnmount
- onUnmount
- 创建
-
说明:
- 对于有父组件,还有子组件,只有子组件挂载完成后,父组件才能完成
App.vue
<template>
<Person v-if="showPerson"/>
<button @click="togglePerson">{{ showTips }}person</button>
</template>
<script lang="ts">
export default {
name:"AppInstance" // 组件名
}
</script>
<!--这里采用Vue3写法, 当引入Person组件后,系统会自动进行注册和返回,所有只需要导入即可-->
<script lang="ts" setup>
import Person from './components/Person.vue';
import {ref} from 'vue'
let showPerson = ref(true);
let showTips = ref("隐藏");
function togglePerson() {
showPerson.value = !showPerson.value
showTips.value = showPerson.value?"隐藏":"显示";
}
</script>
Person.vue
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<div class="person">
<h3>{{ sum }}</h3>
<button @click="addSum">sum+1</button>
</div>
</template>
<script lang="ts">
export default {
name: 'PersonVue'
}
</script>
<script setup lang="ts">
import {ref} from 'vue'
import {onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from 'vue'
let sum = ref(0)
console.log("创建前");
function addSum(){
sum.value += 1;
}
onBeforeMount(()=>{
console.log("挂载前");
});
onMounted(()=>{
console.log("挂载后");
})
onBeforeUpdate(()=>{
console.log("更新前");
})
onUpdated(()=>{
console.log("更新后");
})
onBeforeUnmount(()=>{
console.log("卸载前");
})
onUnmounted(()=>{
console.log("卸载后");
})
console.log("创建后");
</script>
2.9 自定义Hooks
-
准备条件: 安装axios 客户端向服务器发送网络请求
$ npm i axios
狗图片网站:
https://dog.ceo/api/breed/pembroke/images/random
hooks的目的是将setup里的功能,按照功能单独存放至一个脚本文件中,然后在vue中导入脚本文件,并进行暴露到模板中.
src/hooks/usePerson.ts
import type { IPerson } from '@/types';
import {ref, reactive, computed, onMounted} from 'vue'
export default function(){
// 数据
let person:IPerson = reactive({name:"张三", age:25, id:"P0001"})
let tips = computed(()=>{
return person.name + "今年" + person.age + "岁了";
})
// 方法
function modifyPersonAge() {
person.age += 1;
}
// 在hook里面可以使用生命周期函数;
onMounted(()=>{
person.age=10;
})
// 返回需要暴露的数据和方法
return {person, modifyPersonAge, tips};
}
src/hooks/useDog.ts
import axios from 'axios'
import {ref, reactive} from 'vue'
export default function(){
// 数据
let dogList:string[] = reactive(["https://images.dog.ceo//breeds//pembroke//n02113023_2919.jpg"])
// 方法
async function addDog() {
try {
let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
dogList.push(result.data.message)
}
catch(error){
alert(error);
}
}
return {dogList, addDog};
}
Person.vue
<template>
<div class="person">
<h3>玩家信息:</h3>
<h3>姓名:{{ person.name }}</h3>
<h3>年龄:{{ person.age }}</h3>
<button @click="modifyPersonAge">修改年龄</button>{{ tips }}
<br>
<div>
<img v-for="(dog, index) in dogList" :src="dog" :key="index" alt=""/>
</div>
<button @click="addDog">增加一条狗</button>
</div>
</template>
<script lang="ts">
export default {
name: 'PersonVue'
}
</script>
<script setup lang="ts">
import usePerson from '@/hooks/usePerson';
import useDog from '@/hooks/useDog';
const {person, modifyPersonAge, tips} = usePerson();
const {dogList, addDog} = useDog();
</script>
说明:
1. 按照模块划分的脚本文件,约定以use+功能名.ts进行命名.
2. 功能脚本中,定义一个暴露的方法,然后数据和内部方法,均在该函数内部实现,最后暴露出来.
3. 外部导入时,把方法进行导入,然后用结构化分解赋值, 进行使用函数,达到暴露的目的.
4. 每个功能脚本中,可以单独使用生命周期里的回调函数.
3. 路由器
3.1 路由器的基本使用
-
安装路由器:
$ npm install vue-router@next --save;
-
创建路由器
router/index.ts
import {createRouter, createWebHistory } from 'vue-router' import AboutVue from '@/components/AboutVue.vue' import HomeVue from '@/components/HomeVue.vue' import NewsVue from '@/components/NewsVue.vue' const router = createRouter({ history:createWebHistory(), // 指定具体的url路由 routes:[ {path:"/home", component:HomeVue}, {path:"/news", component:NewsVue}, {path:"/about", component:AboutVue} ] }) // 暴露路由器 export default router;
-
使用路由器
main.ts
// 引入createApp用于创建应用 import {createApp} from 'vue' // 引入 App跟组件 import App from './App.vue' import router from './router' const app = createApp(App); app.use(router); app.mount('#app');
-
根组件使用路由器
<template> <div class="App"> <!--标题区域--> <div ><h2 class="title">{{ title }}</h2></div> <!--导航区域--> <div class="navigate"> <RouterLink to="/home" class="item">首页</RouterLink> <RouterLink to="/news" class="item">新闻</RouterLink> <RouterLink to="/about" class="item">关于</RouterLink> </div> <!--正文区域--> <div class="content"> <RouterView></RouterView> </div> </div> </template> <script lang="ts"> export default { name:"AppInstance" // 组件名 } </script> <script lang="ts" setup> import {ref} from 'vue' import {RouterLink, RouterView} from 'vue-router' let title = ref("学生管理系统") </script>
-
工程化组织
- 路由器组件(不直接写标签,而是通过路由器指定规则对应的组件),通常放置在views/pages目录,而一般的组件,则放置在components目录下面
- 通过导航,视觉效果上消失的组件,默认是被卸载掉的,需要的时候,再去挂载.
-
to的两种写法
<!--第一种 to的字符串写法, to的内容无法改变--> <router-link active-class="active" to="/home">主页</router-link> <!--第二种, to的对象写法,可以动态更改路由的匹配路径--> <router-link active-class="active" :to="{path:'/home'}">主页</router-link> <!--第三种 to的对象写法, 路由名称,需要在 routes = {name:"zhuye", path:"/home", component:xx} 中指定--> <router-link active-class="active" :to="{name:'zhuye'}">主页</router-link>
3.2 路由器的工作模式
-
history模式
-
优点:
URL更加美观,不带有#, 更接近传统的网站URL。
-
缺点:
后期项目上线,需要服务器配合处理路径问题,否则刷新会有404错误
-
使用方式:
const router = createRouter({ history:createWebHistory(), routes:[...] });
-
使用场景:
前台网站,因为需要考虑到优化,以及美观, 同时注重性能
-
-
hash模式
-
优点:
兼容性更好, 因为不需要服务器端处理路径。
-
缺点:
URL带有#不太美观,且SEO优化方面相对较差. 网站的SEO优化是指通过一系列技术和策略手段,提高网站在搜索引擎中的排名,从而增加网站的曝光度和访问量。SEO,即搜索引擎优化(Search Engine Optimization),是一种通过了解搜索引擎的排名机制,对网站进行内部及外部的调整优化,以改进网站在搜索引擎中的关键词自然排名,获得更多流量,从而达成网站销售及品牌建设的目标。
-
使用方式
const router = createRouter({ history:createWebHashHistroy(), // hash模式 routes:[...] })
-
使用场景
通常的后台项目,因为不需要考虑性能,以及seo,而是项目稳定,所以通常hash模式
-
3.3 嵌套路由
app.vue(父组件)
import {createRouter, createWebHistory } from 'vue-router'
import AboutVue from '@/pages/AboutVue.vue'
import HomeVue from '@/pages/HomeVue.vue'
import NewsVue from '@/pages/NewsVue.vue'
import DetailVue from '@/pages/DetailVue.vue'
const router = createRouter({
history:createWebHistory(),
// 指定具体的url路由
routes:[
{path:"/", component:HomeVue},
{path:"/home", component:HomeVue},
{name:"news", path:"/news", component:NewsVue,
children:[{
name:"detail",
path:"detail",
component:DetailVue
}]},
{path:"/about", component:AboutVue}
]
})
// 暴露路由器
export default router;
news.vue
<template>
<div container style="float:left">
<div class="navigate">
<ul>
<li v-for="news in newsList" :key="news.id">
<RouterLink
:to="{
name:'detail',
query:{
id:news.id,
title:news.title,
content:news.content
}
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
</div>
<div class="content">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts">
export default {
name:"NewsVue" // 组件名
}
</script>
<script lang="ts" setup>
import {reactive} from 'vue';
import { RouterLink } from 'vue-router';
let newsList = reactive([
{id:"news001", title:"巴以战争正愈演愈烈!", content:"巴伊战争不但没有结束,且愈演愈烈,以色列的经济也越来越糟糕"},
{id:"news002", title:"懂王能战胜老拜?", content:"目前的名义,特朗普的支持率明显高于老拜,但是由于特烂狗的管事"},
{id:"news003", title:"俄乌战争即将结束?", content:"巴伊战争不但没有2323434355越来越糟糕"},
{id:"news004", title:"菲律宾继续挑战我国底线!", content:"巴伊战争不655656来越糟糕"}
])
</script>
说明:
1. 对于嵌套路由,因为子组件的内容,需要父组件进行传递,因此,在传递参数是,采用to的对象写法,对于参数传递,有两种模式,一种是query, 一种是params.
2. 在子路由时,to中如果写入的是路径,需要写绝对路径,即/news/detail, 而写名称更方便,这里只需要写入子路由的名称detail即可.
子路由参数的接收
detail.vue
<template>
<h2>{{ route.query.title }}</h2>
<a> {{ route.query.content }}</a>
</template>
<script lang="ts">
export default {
name:"HomeVue" // 组件名
}
</script>
<script lang="ts" setup>
//import {toRefs} from 'vue';
import {useRoute} from 'vue-router';
let route = useRoute();
//let query = toRefs(useRoute())
</script>
<style scoped>
h2 {
text-align: center;
}
a {
color: blue;
}
</style>
-
params参数传递
-
传递前,需要在路由配置中,路径需要使用占位符, 最后一个如果可能为空,需要添加?
children:[{ name:"detail", path:"detail/:id/:title/:content?", component:DetailVue }]},
-
在跳转中指定子路由,和传递参数时,不能使用path路径,而只能使用name.
-
params不能传递数组和对象
-
3.4 路由的props配置
作用: 让路由组件更方便的收到参数(可以将路由参数作为props传给组件)
-
方法一:
在路由配置中其中将props:true, 可以将params参数传递到 子组件上去
-
方法二:
在props:配置一个字典,直接传递到子组件上去,很少用
-
方法三
props配置函数式方法, 函数内部可以把query参数和params参数都传递到子组件上去
const router = createRouter({ history:createWebHistory(), // 指定具体的url路由 routes:[ {path:"/", component:HomeVue}, {path:"/home", component:HomeVue}, {name:"news", path:"/news", component:NewsVue, children:[{ name:"detail", path:"detail/:id/:title/:content", component:DetailVue, /*第一种写法,直接将params的参数传递到props */ //props:true /*第二种写法,传递一个对象 */ //props:{id:'xx11',title:'xx11',content:"content22"} /*第三种写法 =>route.query*/ props:(route)=>route.params }]}, {path:"/about", component:AboutVue} ] })
3.5 路由的push和replace模式
-
push模式
- 浏览器访问某个网页后,希望点击路由器进行返回,或者再前进,这就是push模式,即当前有个堆栈保留了历史浏览网页的记录,可以回退和前进
- vue默认是push模式
-
replace模式
-
浏览器访问某个网页,不让返回,则是replace模式
-
更改replace模式方法:
<RouterLink replace to="/about" class="item">关于</RouterLink>
-
3.6 编程式路由导航
脱离<RouterLink> 通过脚本实现导航
使用步骤:
1. 获取router.
2. 通过router.push或replace进行路由到目标页面
3. push的参数,和routerLink中的to的写法完全一致.
使用场景:
所有需要进行条件判断的才能跳转的,都需要使用编程式路由导航。
主页隔三秒主动跳转到新闻标签
<script lang="ts" setup>
import {onMounted} from 'vue'
import {useRouter} from 'vue-router';
const router = useRouter();
onMounted(()=>{
setTimeout(() => {
router.push({
name:"news"
});
}, 3000);
})
</script>
新闻中心,点击按钮跳转详细新闻
<template>
<div container style="float:left">
<div class="navigate">
<ul>
<li v-for="news in newsList" :key="news.id">
<Button class="go-btn" @click="goto(news)" >Go</Button>
<RouterLink
:to="{
name:'detail',
params:{
id:news.id,
title:news.title,
content:news.content
}
}">
{{ news.title }}
</RouterLink>
</li>
</ul>
</div>
<div class="content">
<RouterView></RouterView>
</div>
</div>
</template>
<script lang="ts">
export default {
name:"NewsVue" // 组件名
}
</script>
<script lang="ts" setup>
import {reactive} from 'vue';
import { RouterLink , useRouter} from 'vue-router';
import {INews} from '@/types'
let newsList = reactive([
{id:"news001", title:"巴以战争正愈演愈烈!", content:"巴伊战争不但没有结束,且愈演愈烈,以色列的经济也越来越糟糕"},
{id:"news002", title:"懂王能战胜老拜?", content:"目前的名义,特朗普的支持率明显高于老拜,但是由于特烂狗的管事"},
{id:"news003", title:"俄乌战争即将结束?", content:"巴伊战争不但没有2323434355越来越糟糕"},
{id:"news004", title:"菲律宾继续挑战我国底线!", content:"巴伊战争不655656来越糟糕"}
])
let router = useRouter();
function goto(news:INews){
router.push({
name:"detail",
params: {
id:news.id,
title:news.title,
content:news.content
}
})
}
</script>
3.6 路由重定向
const router = createRouter({
history:createWebHistory(),
// 指定具体的url路由
routes:[
{name:"home",path:"/home", component:HomeVue},
{name:"news", path:"/news", component:NewsVue,
children:[{
name:"detail",
path:"detail/:id/:title/:content",
component:DetailVue,
/*第一种写法,直接将params的参数传递到props */
//props:true
/*第二种写法,传递一个对象 */
//props:{id:'xx11',title:'xx11',content:"content22"}
/*第三种写法 =>route.query*/
props:(route)=>route.params
}]},
{name:"about", path:"/about", component:AboutVue},
// 默认重定向到home组件
{path:"/", redirect:"/home"},
]
})
4. Pinia 状态管理库
官网地址:https://pinia.vuejs.org/zh/
Pinia符合直觉的Vue.js 状态管理库, 和vuex功能类似,但是Pinia提供原生的typescript支持, 而vuex需要额外插件才能使用typescript支持
vuex采用集中式的状态管理模式, 整个状态被集中存储在一个store中,而pinia采用了去中心化的架构,在模块化和代码组织更灵活。
4.1 准备环境
-
使用场景:
-
当数据需要供其他组件进行共享时,建议把数据存储在Pinia中,如果仅仅自己组件使用,那么仅仅存在自己的组件内部即可。
-
准备环境
-
获取土味情话地址:
https://api.uomg.com/api/rand.qinghua?format=json
-
生成唯一id第三方库:
$ npm i uuid
$ npm i nanoid
import {nanoid} from 'nanoid' let id = nanoid();
-
安装pinia库
$ npm i pinia
-
引入pinia
main.ts
import {createApp} from 'vue' // 1. 引入pinia import { createPinia } from 'pinia'; import App from './App.vue' const app = createApp(App); // 2. 创建pinia; const pinia = createPinia(); // 3. 使用pinia app.use(pinia); app.mount('#app');
-
4.2 存储读取数据
1. 需要在src创建store目录.
2. 创建和需要存储数据的Vue一样的脚本,命名采用useXXX.ts.
3. 引入库中pinia中defineStore函数.
4. 实现一个defineStore,并且进行导出.
useLoveStore.ts:
import {defineStore } from 'pinia'
import {nanoid} from 'nanoid';
export const useLoveStore = defineStore("love", {
state(){
return {
talkList:[
{
id:nanoid(),
content:"我觉得你好像一款游戏。什么游戏?我的世界。"
}
]
}
}
});
LoveVue.vue
<template>
<div class="LoveArea">
<button @click="GenerateTalk">产生一组土味情话</button>
<ul>
<li v-for="talk in loveStore.talkList" :key="talk.id">
{{ talk.content }}
</li>
</ul>
</div>
</template>
<script lang="ts">
export default {
name: 'LoveVue'
}
</script>
<script lang="ts" setup>
import {reactive} from 'vue';
import {nanoid} from 'nanoid';
import axios from 'axios';
import { useLoveStore } from '@/store/useLoveVue';
const loveStore = useLoveStore();
</script>
4.3 修改数据的三种方式
- 直接修改
- 使用$patch进行碎片修改
- 使用action修改, 更改数据的位置放置在store内部;
<template>
<div class="CountArea">
<h4>当前和是{{countStore.sum}}</h4>
<h5>姓名:{{countStore.name}} 年龄:{{countStore.age}}</h5>
<div>
<select v-model.number="count">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<button @click="Add">Add</button>
<button @click="Sub">Sub</button>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'CountVue'
}
</script>
<script lang="ts" setup>
import {ref} from 'vue';
import {useCountStore} from '@/store/useCountVue';
const countStore = useCountStore();
let count = ref(1);
function Add() {
//sum.value += count.value;
// 第一种数据修改方式: 直接获取字段修改, 或 countStore.$state.sum += count.value;
// countStore.sum += count.value;
// // 第二中数据修改方式:
// countStore.$patch({
// sum:222,
// name:"lisi",
// age:27
// })
// 第三种数据修改方式:
countStore.addVal(count.value);
}
function Sub() {
//sum.value -= count.value;
countStore.sum -= count.value;
}
</script>
import {defineStore } from 'pinia'
export const useCountStore = defineStore("count", {
state(){
return {
sum:1,
name:"zhangsan",
age:20
}
},
actions:{
addVal(val:number){
if(this.sum +val <= 10)
this.sum += val;
}
}
});
-
storeToRefs使用
当要对store进行解构赋值时(需要响应式数据), 使用toRefs可以达到要求,但是效率太低,因此更推荐使用storeToRefs
<script lang="ts" setup> import {ref} from 'vue'; import {storeToRefs } from 'pinia' import {useCountStore} from '@/store/useCountVue'; const countStore = useCountStore(); let {sum, age, name} = storeToRefs(countStore); </script>
4.4 getter的使用
当对默认data中的数据不能满足需求,需要进行加工时,可以通过getter来实现,而在上层访问时,可以直接按照state中的字段进行访问.
useCountVue.ts
export const useCountStore = defineStore("count", {
state(){
return {
sum:1,
name:"zhangsan",
age:20
}
},
actions:{
addVal(val:number){
if(this.sum +val <= 10)
this.sum += val;
},
subVal(val:number) {
if(this.sum-val<0){
this.sum = 0;
}
else {
this.sum -= val;
}
}
},
getters:{
bigSum1:state=>state.sum*10,
bigSum2(state) {
return state.sum*10;
},
bigSum3(state) {
return this.sum * 10;
},
introduce(state) {
return `我的名字叫:${state.name} 我今年:${state.age}`;
}
}
});
<script lang="ts" setup>
import {ref} from 'vue';
import {storeToRefs } from 'pinia'
import {useCountStore} from '@/store/useCountVue';
const countStore = useCountStore();
let {sum, age, name, bigSum1, bigSum2, bigSum3, introduce} = storeToRefs(countStore);
</script>
4.5 $subscribe使用
subscribe如同组件中的watch, 只是这里监听的是pinia的store
-
监听用法:
<script lang="ts" setup> import axios from 'axios'; import { useLoveStore } from '@/store/useLoveVue'; const loveStore = useLoveStore(); loveStore.$subscribe((mutate, state:any)=>{ localStorage.setItem("LoveTalks", JSON.stringify(state.talkList)); }); // 方法 function GenerateTalk() { loveStore.GenerateTalk(); } </script>
-
适用场景
当保存的数据发生变化时,进行本地化的时候
当数据变化,需要向服务器发送消息的时候
lovevue.vue
<script lang="ts" setup> import axios from 'axios'; import { useLoveStore } from '@/store/useLoveVue'; const loveStore = useLoveStore(); loveStore.$subscribe((mutate, state:any)=>{ localStorage.setItem("LoveTalks", JSON.stringify(state.talkList)); }); // 方法 function GenerateTalk() { loveStore.GenerateTalk(); } </script>
-
localStorage本地化
通常将数据本地化,都需要进行JSON序列号,而在读取的时候进行反序列化.
在数据本地化后,项目启动时,初始化数据,应该从localStorage中获取.
4.6 Pinia的组合式写法
useLoveVue.vue
import {defineStore } from 'pinia'
import {nanoid} from 'nanoid';
import axios from 'axios';
import {reactive} from 'vue';
export const useLoveStore = defineStore("love", ()=>{
let talkList = reactive(JSON.parse(localStorage.getItem("LoveTalks") as string) || []);
async function GenerateTalk() {
const {data:{content}} = await axios.get("https://api.uomg.com/api/rand.qinghua?format=json");
console.log(content);
talkList.unshift({id:nanoid(), content});
}
return {talkList, GenerateTalk}
})
说明:
1. 使用defineStore的第二个参数,不再是一个字典,而是一个函数
2. 在函数中,需要将需要使用的变量和函数均要返回出来.
3. 这个函数相当于组件中的Setup函数
5. 组件之间的通讯
5.1 Props传递
father.vue
<template>
<div class="father">
<h2> Prop父组件 </h2>
<h3>汽车:{{car}}</h3>
<h3 v-show="toy">来自子的玩家:{{toy}}</h3>
<Child :car="car" :sendToy="getToy"/>
</div>
</template>
<script lang="ts">
export default {
name:"PropFather" // 组件名
}
</script>
<script lang="ts" setup>
import Child from './Child.vue'
import {ref} from 'vue';
let car = ref('奔驰')
let toy = ref('');
function getToy(val:string) {
toy.value = val;
}
</script>
child.vue
<template>
<div class="child">
<h2> 子界面 </h2>
<h4> 玩具:{{toy}}</h4>
<h4> 来自父的汽车:{{car}}</h4>
<button @click="sendToy(toy)" >玩具传父</button>
<!-- <button @click="ManualSendToy" >玩具传父</button> -->
</div>
</template>
<script lang="ts">
export default {
name:"PropFather" // 组件名
}
</script>
<script lang="ts" setup>
import {ref} from 'vue';
let toy = ref("奥特曼");
defineProps(['car', 'sendToy'])
/* 方式1: 直接通过返回值props,调用父节点的函数,将值传递到父组件.
let props = defineProps(['car', 'sendToy'])
function ManualSendToy() {
props.sendToy(toy);
}
*/
</script>
说明:
1. 父传递给子数据,直接通过props的值进行传递
2. 子传递给父数据,需要先在父定义一个函数,带有参数,而后在子进行调用参数,来将子数据传递到父.
5.2 自定义事件(CustomEvent)
自定义事件,注意是由父组件定义事件,而由子组件进行调用
Father.vue
<template>
<div class="father">
<h2> Event 父组件 </h2>
<h4 v-show="toy"> 来自子组件的Toy:{{toy}}</h4>
<Child @send-toy="GetToy" />
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
import Child from './Child.vue'
let toy = ref("");
function GetToy(v:string) {
toy.value = v;
}
</script>
Child.vue
<template>
<div class="child">
<h2> 子界面 </h2>
<h3>玩家:{{toy}}</h3>
<button @click="emit('send-toy', toy)">发送父组件</button>
<GrandSon @send-toy="GetToy"/>
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
import GrandSon from './GrandSon.vue'
let toy = ref("奥特曼");
const emit = defineEmits(['send-toy'])
function GetToy(v:string) {
emit("send-toy", v)
}
</script>
说明:
1. 自定义事件, 是在模板标签中,子组件上定义标签,标签用@开头, 事件的名称,官方建议用烤串的方式进行命名, 如send-key, 而后的函数名字,采用驼峰命名.
2. 需要自定义事件中可以自定义多个参数.
3. 在子组件中, 需要使用父组件的自定义消息,需要用defineEmits进行声明可以使用的自定义事件, 在调用自定义事件时,擦用emit('事件名', 附加参数...)进行调用.
5.3 mitt通讯
和mitt类似的功能由 1. pubsub, 2. $bus, 3. mitt.
mitt,是组件需要监听事件,在mitt中提前监听事件, 而需要发送事件的,找到mitt,发送事件,则会触发mitt中监听的所有组件,并回调之前监听事件的回调。
环境安装
$npm i mitt;
-
定义一个全局的emitter对象
utils/emitter.ts
// 引入mitt包 import mitt from "mitt"; // 生成一个emitter对象 const emitter = mitt(); // 暴露出去 export default emitter;
-
需要监听事件的vue组件,注册事件
Father.vue
<template> <div class="father"> <h2> Event 父组件 </h2> <h4 v-show="toy"> 来自子组件的Toy:{{toy}}</h4> <Child /> </div> </template> <script lang="ts" setup> import {ref, onMounted, onUnmounted} from 'vue' import Child from './Child.vue' import emitter from '@/utils/emitter'; let toy = ref(""); onMounted(()=>{ emitter.on("send-toy", (val:any)=>{ toy.value = val; }) }); onUnmounted(()=>{ emitter.off("send-toy"); }); </script>
-
需要触发事件的vue组件
grandson.vue
<template> <div class="grandson"> <h2> 孙子界面 </h2> <h3>孙子玩具:{{toy}}</h3> <button @click="emitter.emit('send-toy', toy)">发送父组件</button> </div> </template> <script lang="ts" setup> import {ref} from 'vue' import emitter from '@/utils/emitter'; let toy = ref("超级飞侠"); </script>
说明:
1. mitt通讯,可以适用于所有组件之间的通讯
2. mitt通讯的参数,和自定义事件类似.
3. 当接收者,在组件卸载后,应该要移除事件, 即要监听onUnmount事件,并调用emitter.off进行移除
5.4 v-model通讯
说明:
1. v-model 大量用关于ui库,
2. v-model 本质是由 :value 和@input进行实现
<template>
<div class="father">
<h2> v-Model 父组件 </h2>
<!-- <input type="text" v-model="username"> -->
<input type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value">
</div>
</template>
$event到底是啥?
- 对于原生事件, $event就是事件对象, ===>能.target
- 对于自定义事件, $event就是触发事件时, 所传递的数据.
v-model, 对于html标签和对于组件标签,底层实现原理不一样
组件的v-model实现原理:
<CustomInput v-model="username"></CustomInput>
<CustomInput :modelValue="username" @update:modelValue="username=$event"/>
CustomInput.vue:
<template>
<div >
<input type="text" :value="modelValue" @input="emit('update:modelValue', $event.target.value)">
</div>
</template>
<script lang="ts">
export default {
name:"CustomInput" // 组件名
}
</script>
<script lang="ts" setup>
defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue'])
</script>
Parent.vue
<template>
<div class="father">
<h2> v-Model 父组件 </h2>
<!-- <input type="text" v-model="username"> -->
<input type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value">
<!-- <input type="text" :value="username" @input="onInputChanged"> -->
<CustomInput v-model="username"></CustomInput>
<!--上面等效于下面语句-->
<CustomInput :modelValue="username" @update:modelValue="username=$event"/>
</div>
</template>
<script lang="ts">
export default {
name:"PropFather" // 组件名
}
</script>
<script lang="ts" setup>
import {ref} from 'vue';
import CustomInput from './CustomInput.vue'
let username = ref("zhangsan");
</script>
说明:
v-model, 即可以父传子,又可以子传父.
- modelValue这个字段可以更改,但是在绑定v-model时需要进行相应的调整.
<CustomInput v-model:acc="username" v-model:pwd="password"></CustomInput>
father.vue
<template>
<div class="father">
<h2> v-Model 父组件 </h2>
<CustomInput v-model:acc="username" v-model:pwd="password"></CustomInput>
<h4>你输入的账号为:{{username}}</h4>
<h4>你输入的密码为:{{password}}</h4>
</div>
</template>
<script lang="ts">
export default {
name:"PropFather" // 组件名
}
</script>
<script lang="ts" setup>
import {ref} from 'vue';
import CustomInput from './CustomInput.vue'
let username = ref("zhangsan");
let password = ref("");
</script>
<style scoped>
.father {
background-color: aliceblue;
padding: 20px;
border-radius: 10px;
}
</style>
CustomInput.vue
<template>
<div >
<input type="text" :value="acc" @input="emit('update:acc', (<HTMLInputElement>$event.target).value)">
<br/>
<input type="password" :value="pwd" @input="emit('update:pwd', (<HTMLInputElement>$event.target).value)">
</div>
</template>
<script lang="ts">
export default {
name:"CustomInput" // 组件名
}
</script>
<script lang="ts" setup>
defineProps(["acc", "pwd"]);
const emit = defineEmits(['update:acc', 'update:pwd'])
</script>
5.5 $attr通讯
-
概述: $attrs用于实现当前组件的父组件,向当前组件的子组件通讯(祖->孙)
-
具体说明:
$attrs是一个对象,包含所有父组件传入的标签属性.
$attrs会自动排除props中声明的属性
v-bind="{x:100,y:100}"
等效于
:x="100" :y="100"
Father.vue:
<template>
<div class="father">
<h2> Event 父组件 </h2>
<h3> 父亲名字:{{ parentName }} </h3>
<h4 v-show="toy"> 来自孙子组件的Toy:{{toy}}</h4>
<Child :parentName="parentName" v-bind="{parentAge:66}" :updateToy="GetToy"/>
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
import Child from './Child.vue'
let parentName = ref("我是父亲");
let toy = ref("");
function GetToy(v:string) {
toy.value = v;
}
</script>
Child.vue
<template>
<div class="child">
<h2> 子界面 </h2>
<GrandSon v-bind="$attrs"/>
</div>
</template>
<script lang="ts" setup>
import GrandSon from './GrandSon.vue'
</script>
GrandSon.vue
<template>
<div class="grandson">
<h2> 孙子界面 </h2>
<h3>孙子玩具:{{toy}}</h3>
<h4>父亲名字:{{ parentName }}</h4>
<h4>父亲年龄:{{ parentAge }}</h4>
<button @click="updateToy(toy)">发送父组件</button>
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
let toy = ref("超级飞侠");
defineProps(["parentName", "parentAge", "updateToy"])
</script>
5.6 $refs && $parent
$refs 父传子
$parent 子传父
说明:
1. 父访问子对象中的成员,需要子组件中,调用 defineExposed({})进行指定哪些需要暴露
2. $refs存放的是当前组件的子组件的所有列表,存放到一个{(key:string):object}类型
parent.vue
<template>
<div class="father">
<h2> refChild 父组件 </h2>
<h4> 拥有房产:{{ house }} 套</h4>
<div>
<button @click="ChangeChild1Toy()"> 更改c1玩具</button>
<button @click="ChangeChild2Computer()" > 更改c2电脑</button>
<button @click="IncrementBooks($refs)" > 增加书籍</button>
</div>
<ChildX ref="c1"/>
<ChildY ref="c2"/>
</div>
</template>
<script lang="ts">
export default {
name:"PropFather" // 组件名
}
</script>
<script lang="ts" setup>
import {ref} from 'vue'
import ChildX from './Child1.vue'
import ChildY from './Child2.vue'
let c1 = ref();
let c2 = ref();
let house = ref(4)
function ChangeChild1Toy() {
c1.value.toy = "超级飞侠"
}
function ChangeChild2Computer() {
c2.value.computer = "华为"
}
function IncrementBooks(refs) {
for(let key in refs) {
refs[key].book += 2
}
}
defineExpose({house})
</script>
child1.vue
<template>
<div class="child">
<h2> 子界面1 </h2>
<h4>玩具:{{toy}}</h4>
<h4>书:{{book}}</h4>
<button @click="addParentHouse($parent)"> 增加父亲房产</button>
</div>
</template>
<script lang="ts">
export default {
name:"PropFather" // 组件名
}
</script>
<script lang="ts" setup>
import {ref} from 'vue'
let toy = ref("奥特曼")
let book = ref(5)
function addParentHouse(parent:any) {
parent.house += 1;
}
defineExpose({toy, book})
</script>
5.7 provide和inject通讯
说明:
1. provide提供数据, 在其子以及孙力,都可以通过inject来获取对应的数据
2. 如果需要子传递给父数据,则在父传递一个回调函数
3. 传递数据时,需要传递响应式数据(ref或reactive)
parent.vue
<template>
<div class="father">
<h2>inject 父组件 </h2>
<h3>存款: {{ money }} 万元 </h3>
<h4>我有一辆{{car.brand}}车,价值{{car.price}}万元</h4>
<Child/>
</div>
</template>
<script lang="ts">
export default {
name:"PropFather" // 组件名
}
</script>
<script lang="ts" setup>
import {ref, reactive, provide} from 'vue'
import Child from './Child.vue'
let money = ref(100)
let car = reactive({
brand:"奥迪",
price:100
})
function changeMoney(val:number) {
money.value -= val;
}
provide('moneyContext', {money, changeMoney})
provide('car', car)
</script>
Grand.vue
<template>
<div class="child">
<h2> 孙界面 </h2>
<h4> 父亲有一辆{{ car.brand }}, 价值:{{ car.price }}万元 </h4>
<button @click="changeMoney(6)">更改父亲价格</button>
</div>
</template>
<script lang="ts">
export default {
name:"PropFather" // 组件名
}
</script>
<script lang="ts" setup>
import {ref, inject} from 'vue'
let {money, changeMoney} = inject('moneyContext', {money:0, changeMoney:(v:number)=>{}})
let car = inject("car",{brand:'', price:0})
</script>
5.8 Slot通讯
5.8.1 默认插槽
slot是为了让一个组件进行通用, 通常需要在父组件中,自定义子组件中展现的不同的html标签.
img: https://z1.ax1x.com/2023/11/19/piNxLo4.jpg
mp4: http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4
Father.vue
<template>
<div class="father">
<h2>slot 父组件 </h2>
<div class="content">
<Category title="热门游戏">
<ul>
<li v-for="game in gameList" :key="game.id">
{{ game.name }}
</li>
</ul>
</Category>
<Category title="热门城市">
<img :src="hotCityImg" alt=""/>
</Category>
<Category title="热门电影"/>
<video :src="hotMovieUrl"></video>
</div>
</div>
</template>
<script lang="ts">
export default {
name:"PropFather" // 组件名
}
</script>
<script lang="ts" setup>
import {ref, reactive} from 'vue'
import Category from './Category.vue'
let gameList = reactive([
{id:"gm001", name:"王者荣耀"},
{id:"gm002", name:"绝地求生"},
{id:"gm003", name:"星际争霸"},
{id:"gm004", name:"梦幻西游"}
])
let hotCityImg = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
let hotMovieUrl = ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4')
</script>
Category.vue
<template>
<div class="category">
<h3 class="title"> {{ title}} </h3>
<slot></slot>
</div>
</template>
<script lang="ts">
export default {
name:"CategoryVue" // 组件名
}
</script>
<script lang="ts" setup>
import {ref} from 'vue';
defineProps(['title'])
</script>
5.8.2 具名插槽
-
:v-slot标签 可以写在子组件上,也可以写在template标签上,但是如果写在子组件标签上,则也只能填充一个插槽,因此推荐写到tempalte插槽上, 插入的内容用template进行包裹
<template v-slot:title> <h3 class="title">热门游戏</h3> </template> <!--简写--> <template #title> <h3 class="title">热门游戏</h3> </template>
-
插槽需要定义名字:, 默认不写名字的默认插槽,其名字默认是default.
<template> <div class="category"> <slot name="title"></slot> <slot name="content"></slot> </div> </template>
-
语法糖, :v-slot 可以直接用 #s1替代, 即<template #s1>
Father.vue
<template>
<div class="father">
<h2>slot 父组件 </h2>
<div class="content">
<Category title="热门游戏">
<template v-slot:title>
<h3 class="title">热门游戏</h3>
</template>
<template #content>
<ul>
<li v-for="game in gameList" :key="game.id">
{{ game.name }}
</li>
</ul>
</template>
</Category>
<Category title="热门城市">
<template #content>
<img :src="hotCityImg" alt=""/>
</template>
<template v-slot:title>
<h3 class="title">热门城市</h3>
</template>
</Category>
<Category title="热门电影">
<template #content>
<img :src="hotCityImg" alt=""/>
</template>
<template v-slot:title>
<h3 class="title">热门电影</h3>
</template>
</Category>
</div>
</div>
</template>
<script lang="ts">
export default {
name:"PropFather" // 组件名
}
</script>
<script lang="ts" setup>
import {ref, reactive} from 'vue'
import Category from './Category.vue'
let gameList = reactive([
{id:"gm001", name:"王者荣耀"},
{id:"gm002", name:"绝地求生"},
{id:"gm003", name:"星际争霸"},
{id:"gm004", name:"梦幻西游"}
])
let hotCityImg = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
let hotMovieUrl = ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4')
</script>
<style scoped>
.father {
background-color: aliceblue;
padding: 20px;
border-radius: 10px;
}
.content {
display: flex;
justify-content: space-evenly;
}
img, video {
width:100%;
}
.title {
background-color: goldenrod;
text-align: center;
font-size: 20;
}
</style>
Category.vue
<template>
<div class="category">
<slot name="title"></slot>
<slot name="content"></slot>
</div>
</template>
<script lang="ts">
export default {
name:"CategoryVue" // 组件名
}
</script>
5.8.3 作用域插槽
作用域插槽,是指从子组件中,存放数据,通过slot的prop存入到slot中,再由调用插槽的父类进行获取插槽里存放的props数据
parent.vue
<template>
<div class="father">
<h2>slot 父组件 </h2>
<div class="content">
<Category title="热门游戏">
<template v-slot:title>
<h3 class="title">热门游戏</h3>
</template>
<template #content>
<ul>
<li v-for="game in gameList" :key="game.id">
{{ game.name }}
</li>
</ul>
</template>
<template #movieContent="{moveData}">
<ol>
<li v-for="movie in moveData" :key="movie.id">
{{ movie.name }}
</li>
</ol>
</template>
</Category>
Category.vue
<template>
<div class="category">
<slot name="title"></slot>
<slot name="content"></slot>
<slot name="movieContent" :moveData="movieList"></slot>
</div>
</template>
<script lang="ts">
export default {
name:"CategoryVue" // 组件名
}
</script>
<script lang="ts" setup>
import {reactive} from 'vue'
let movieList = reactive([
{id:"m001", name:"终结者4"},
{id:"m002", name:"侏罗纪公园"},
{id:"m003", name:"中国医生"},
{id:"m004", name:"孤注一掷"},
])
</script>
说明:
1. 可以插槽接受数据,同时指定名字, <template #movieContent="{moveData}"> 等效于
<template v-slot:movieContent="data">
<li v-for="item in data.movieData">
6. 其他api
6.1 shallowRef
处理一级层次的数据,改成响应式的,对于深层次的,则不是响应式的。
但是第一次展开则是正常的。
-
使用场景
1. 有些时候对象很大,比如一个对象有一百多个属性,每个属性,有可能多层次,如果这个大对象全部用Ref,则树层次很大,而我们只关心部分数据,或者整个数据,所以用shallowref, 关心整个数据
6.2 shallowReactive
使用同shallowRef。
6.3 readonly与shallowReadonly
readonly
1. 作用: 用于创建一个对象的深只读副本
2. 用法:
const original = reactive({...})
const original2 = ref(...)
const readOnlyCopy = readonly(original)
const readOnlyCopy2 = readonly(original2)
3. 特点:
对象的所有嵌套都将变为只读,对于radOnlyCopy
但是当original更改后,对应的readOnlyCopy也会跟着改变.
4. 应用场景
a. 创建不可变的状态快照.
b. 保护全局状态或配置不被修改.
c. 模块之间分享当前模块数据给其他模块,只能当前模块数据更改,而其他模块不能修改数据,
shallowReadonly
作用: 与readonly类似,但只作用于对象的顶层属性.
特点:
只将顶层属性设置为只读, 对象内部的嵌套属性仍然是可变的。
6.4 toRaw与markRow
-
toRaw
-
作用: 用于获取一个响应式对象的原始对象, toRaw返回的对象不再是响应式的,不会触发视图更新。
- 这时一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不粗发更改的特殊方法,不建议保存对原始对象的持久引用,请谨慎使用。
-
何时使用?
-
需要将响应式对象传递给非Vue的库或外部系统时, 使用toRaw可以确保他们收到的是普通对象;.
-
具体编码
import {reactive, toRaw, markRaw, isReactive} from 'vue' let person = reactive({name:'tony', age:18}) let rawPerson = toRaw(person)
-
-
markRaw
-
作用: 标记一个对象,使其永远不会变成响应式的。
- 例如使用mockjs时,为了防止误把mockjs变成响应式对象,可以使用markRaw去标记mockjs
-
编码:
<hr> <!--分割线--> let car = markRaw({brand:'Benz', price:100}) <!--尽管car2是reactive包裹的,但是其源头标记不是响应式,所以car2也不会是响应式--> let car2 = reactive(car)
-
使用场景
-
mockjs库
$ npm i mockjs
import mockjs from 'mockjs' let mockjs = markRaw(mockjs) // 以后使用mockjs用于不会是响应式对象了.
-
-
6.5 customRef
作用: 创建一个自定义的ref, 并对其依赖项跟踪和更新触发进行逻辑控制.
<template>
<div class="person">
<h2>{{ name }}</h2>
<input type="text" v-model = "name">
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<script lang="ts" setup>
import {customRef} from 'vue'
let rawValue = "666";
let name = customRef((track, trigger)=>{
return {
get() {
track();
return rawValue
},
set(val) {
rawValue = val;
trigger();
}
}
})
</script>
说明:
1. 应用场景, 可以在调用set时, 不立即进行赋值,而是增加一个timeout,这样可以达到延时刷新的目的.
第二章 Elementplus
1.1 Elementplus的简介
Element Plus,一套为开发者、设计师和产品经理准备的基于 Vue 3.0 的桌面端组件库
1.2 Elementplus的环境搭建
-
npm安装element plus
$ npm install element-plus --save
-
整体导入
-
浏览器直接引入
- index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="icon" href="/favicon.ico"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="/css/bootstrap.css"/> <link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" /> <!-- 引入 Vue --> <script src="//unpkg.com/vue@3"></script> <!-- 引入组件库 --> <script src="//unpkg.com/element-plus"></script> <title>Vite App</title> </head> <body> <div id="app"></div> <script type="module" src="/src/main.ts"></script> </body> </html>
-
修改引入的main.ts
// 引入createApp用于创建应用 import {createApp} from 'vue' // 引入 App跟组件 import App from './App.vue' // 整体导入elementplus; import ElementPlus from 'element-plus' // 导入ElementPlus组件库的所有模块和功能 import 'element-plus/dist/index.css' // 导入ElementPlus组件库所需的全局css样式 const app = createApp(App) // 将ElementPlus插件注册到Vue应用中. app.use(ElementPlus) app.mount('#app')
- 在组件中使用element plus的控件
person.vue
<template> <div class="person"> <h2>{{ name }}</h2> <input type="text" v-model = "name"> <div> <el-row> <el-button>默认按钮</el-button> <el-button type="primary">主要按钮</el-button> <el-button type="success">成功按钮</el-button> <el-button type="info">信息按钮</el-button> <el-button type="warning">警告按钮</el-button> <el-button type="danger">危险按钮</el-button> </el-row> </div> </div> </template>
-
-
按需导入
-
安装插件 unplugin-auto-import\unplugin-vue-components\unplugin-icons
$ npm install -D unplugin-vue-components unplugin-auto-import, unplugin-icons
-
修改vite.config.js
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: [ vue(), //VueSetupExtend() AutoImport({ imports: ['vue'], resolvers:[ ElementPlusResolver(), IconsResolver() ], }), Components([ resolvers:[ ElementPlusResolver(), IconsResolver({ enabledCollections:['ep'], }) ] ]), Icons({ autoInstall:true, }) ] })
说明: 1. 使用自动导入图标时,需要使用前缀, i-e-p-, IEp, iEp 如: <el-icon><i-ep-Plus/></el-icon> <el-icon><IEpPlus/></el-icon> <el-icon><iEpPlus/></el-icon>
-
1.3 Elementplus基础
1.3.1 Layout布局
通过基础的 24 分栏,迅速简便地创建布局。
-
通过 :span=“x” 来标记当前分割大小, x=24代表100%, x=12代表50%.
<el-row> <el-col :span="12"> <h2>{{ name }}</h2> </el-col> <el-col :span="12"> <input type="text" v-model = "name"> </el-col> </el-row>
-
在el-row中,使用属性 :gutter=“20”, 来设定分割栏之间的距离
<el-row :gutter="20"> <!--每列间隔20个像素--> <el-col :span="6"><div class="grid-content bg-purple"></div></el-col> <el-col :span="6"><div class="grid-content bg-purple"></div></el-col> <el-col :span="6"><div class="grid-content bg-purple"></div></el-col> <el-col :span="6"><div class="grid-content bg-purple"></div></el-col> </el-row>
-
在el-row中,可以混合进行布局
<el-row :gutter="20"> <el-col :span="16"><div class="grid-content bg-purple"></div></el-col> <el-col :span="8"><div class="grid-content bg-purple"></div></el-col> </el-row> <el-row :gutter="20"> <el-col :span="8"><div class="grid-content bg-purple"></div></el-col> <el-col :span="8"><div class="grid-content bg-purple"></div></el-col> <el-col :span="4"><div class="grid-content bg-purple"></div></el-col> <el-col :span="4"><div class="grid-content bg-purple"></div></el-col> </el-row> <el-row :gutter="20"> <el-col :span="4"><div class="grid-content bg-purple"></div></el-col> <el-col :span="16"><div class="grid-content bg-purple"></div></el-col> <el-col :span="4"><div class="grid-content bg-purple"></div></el-col> </el-row>
-
在el-row中,可以使用justify属性定义子标签的对齐方式
- start: 居左显示;
- center: 居中显示;
- end: 居右显示;
- space-between: 第一个子布局靠左显示,第二个子布局靠右显示,剩下的均匀分布
- space-around: 所有子布局均匀分布.
<!--下面两个layout进行局中显示--> <el-row :gutter="20" justify="center"> <el-col :span="5"><div class="grid-content bg-purple"></div></el-col> <el-col :span="5"><div class="grid-content bg-purple"></div></el-col> </el-row>
-
响应式布局
-
五个响应尺寸(参考BootsTrap)
-
xs
width < 768px
-
sm
768px <= width < 992px
-
md
992px <= width < 1200px
-
lg
1200px <= width < 1920px
-
xl
1920 <= width
-
-
在响应式布局中, 需要将列,通常设计成,不同响应尺寸模式,不同的尺寸比例
<el-row :gutter="10"> <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="1" ><div class="grid-content bg-purple">a</div ></el-col> <el-col :xs="4" :sm="6" :md="8" :lg="9" :xl="11" ><div class="grid-content bg-purple-light">b</div ></el-col> <el-col :xs="4" :sm="6" :md="8" :lg="9" :xl="11" ><div class="grid-content bg-purple">c</div ></el-col> <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="1" ><div class="grid-content bg-purple-light">d</div ></el-col> </el-row>
-
-
el-row的属性
- glutter: 栅格间隔, 类型:number
- justify: flex 布局下的水平排列方式 start/end/center/space-around/space-between, 默认: start
- align: flex 布局下的垂直排列方式, top/middle/bottom, 默认: top
- tag: 自定义标签
-
el-col的属性
- span:栅格占的格数
- offset: 栅格左侧间隔格数
- push: 栅格向右移动格数
- pull: 栅格向左移动格数
- xs/sm/md/lg/xl: 不同分辨率下对应的栅格个数
1.3.2 Container 布局容器
用于布局的容器组件,方便快速搭建页面的基本结构:
-
: 外层容器。当子元素包含 或 时, 全部子元素会垂直上下排列,否则会水平左右排列
-
: 顶栏容器
-
: 侧边栏容器
-
: 主要区域容器
-
: 底栏容器
说明: 1. el-container的直接子元素必须是后四个组件中的一个或多个. 2. el-header, el-aside, el-main, el-footer的父元素必须是el-container
常用的组合
-
常用的组合:
-
Header + Main
<template> <div class="common-layout"> <el-container> <el-header>Header</el-header> <el-main>Main</el-main> </el-container> </div> </template>
-
Header + Main + Footer
<template> <div class="common-layout"> <el-container> <el-header>Header</el-header> <el-main>Main</el-main> <el-footer>Footer</el-footer> </el-container> </div> </template>
-
Aside + Main
<template> <div class="common-layout"> <el-container> <el-aside width="200px">Aside</el-aside> <el-main>Main</el-main> </el-container> </div> </template>
-
Header + (Aside + Main)
<template> <div class="common-layout"> <el-container> <el-header>Header</el-header> <el-container> <el-aside width="200px">Aside</el-aside> <el-main>Main</el-main> </el-container> </el-container> </div> </template>
-
Header + (Aside + (Main + Footer))
<template> <div class="common-layout"> <el-container> <el-header>Header</el-header> <el-container> <el-aside width="200px">Aside</el-aside> <el-container> <el-main>Main</el-main> <el-footer>Footer</el-footer> </el-container> </el-container> </el-container> </div> </template>
-
Aside+ (Header+ Main)
<template> <div class="common-layout"> <el-container> <el-header>Header</el-header> <el-container> <el-aside width="200px">Aside</el-aside> <el-container> <el-main>Main</el-main> <el-footer>Footer</el-footer> </el-container> </el-container> </el-container> </div> </template>
-
Aside + (Header + Main + Footer)
<template> <div class="common-layout"> <el-container> <el-aside width="200px">Aside</el-aside> <el-container> <el-header>Header</el-header> <el-main>Main</el-main> <el-footer>Footer</el-footer> </el-container> </el-container> </div> </template>
-
1.3.3 图标
-
安装:
npm install @element-plus/icons-vue
-
注册所有图标
main.ts
// main.ts // 如果您正在使用CDN引入,请删除下面一行。 import * as ElementPlusIconsVue from '@element-plus/icons-vue' const app = createApp(App) for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) }
-
图标属性
- size 图标的尺寸
- color 图标的颜色
-
例子
<el-row> <el-icon><Plus/> </el-icon> <el-icon><Edit/> </el-icon> <el-icon><Delete/> </el-icon> <el-icon color="red" size="40"><ArrowLeft/> </el-icon> </el-row> <!--搜索图标和按钮的组合--> <el-row> <el-button type="primary"> <el-icon><Search/> </el-icon><span> 搜索</span></el-button> </el-row> <!--按钮组--> <el-button-group> <el-button type="primary"> <el-icon><Plus/> </el-icon></el-button> <el-button type="primary"> <el-icon><Edit/> </el-icon></el-button> <el-button type="primary"> <el-icon><Delete/> </el-icon></el-button> </el-button-group>
1.3.4 按钮
-
按钮的种类(type)
<el-button>Default</el-button> <el-button type="primary">Primary</el-button> <el-button type="success">Success</el-button> <el-button type="info">Info</el-button> <el-button type="warning">Warning</el-button> <el-button type="danger">Danger</el-button>
-
按钮的扁平模式(plain)
<el-button type="primary" plain>Primary</el-button>
-
圆角按钮(round)
<el-button type="primary" round>Primary</el-button>
-
圆形图标按钮(circle)
<el-button type="primary" :icon="Edit" circle />
-
按钮的禁用状态(disabled)
<el-button type="success" disabled>Success</el-button> <el-button type="primary" :disabled="isDisabled" > <el-icon><Edit/> </el-icon></el-button> <!--点击Delete按钮, 将isDisabled改为true, 让按钮Edit按钮变成可用状态.--> <el-button type="primary" @click="isDisabled=false" > <el-icon><Delete/> </el-icon></el-button>
<script lang="ts" setup> import {ref} from 'vue' let name = ref("Hello, world") let isDisabled = ref(true) // Edit按钮,默认禁用状态. </script>
-
链接按钮(link)
<el-button type="primary" link> <el-icon><Search/> </el-icon><span> 搜索</span></el-button>
-
文字按钮(text)
<el-button type="primary" link> 搜索</el-button> <!--文字按钮和连接按钮主要区别,文字按钮鼠标上面,有背景和边框,链接按钮没有-->
-
图标按钮
<!--图标按钮,是按钮和图标el-icon搭配使用--> <el-button type="primary" > <el-icon><Edit/> </el-icon></el-button>
-
按钮组
<el-button-group> <el-button type="primary" :icon="Edit" /> <el-button type="primary" :icon="Share" /> <el-button type="primary" :icon="Delete" /> </el-button-group> <!--按钮组,会处理两边按钮的边框,使其融为一体-->
-
加载按钮
<el-button type="primary" loading>Loading</el-button> <el-button type="primary" :loading="isLoading">Loading</el-button> <!--loadingIcon 可以自定义加载图标 --> <el-button type="primary" :loading-icon="Eleme" loading>Loading</el-button>
-
调整尺寸(size)
<el-button type="primary" size="large"> 搜索</el-button> <el-button type="primary"> 搜索</el-button> <el-button type="primary" size="small"> 搜索</el-button> <!--size进仅三个尺寸,默认,大,小-->
-
自定义颜色(color)
<el-button color="#626aef" :dark="isDark">Default</el-button> <!--dark=true, 则hover时颜色会更深,否则颜色会更亮-->
1.3.5 提示框
-
ElMessage
import {ElMessage} from 'element-plus' const openMsg = ()=>{ ElMessage({ type:"success", // success, warning, info, error message:'恭喜你成功获取屠龙刀', showClose:true }) }
-
ElMessageBox
import {ElMessageBox} from 'element-plus' const openConfirm = ()=>{ ElMessageBox.confirm("确认删除", '移除物品', { type:'warning', confirmButtonText: '确认', cancelButtonText: '取消' }).then(()=>{ console.log("confirm") }).catch(()=>{ console.log('cancel') }) }
- ElNotification
import {ElNotification} from 'element-plus'
const openNotify1 = ()=>{
ElNotification({
title:"标题",
message:'通知内容',
duration: 1500 // 展示事件,单位毫秒
})
}
const openNotify2 = () => {
ElNotification ({
type:'success', // success | warning | info | error
title: '标题',
message: "通知内容",
duration : 1500,
position: 'bottom-right'
})
}
1.3.5 导航
导航通过el-menu、el-sub-menu, el-menu-item组合进行
-
垂直导航
<template> <div class="content"> <div class="common-layout"> <el-container> <el-menu :default-active="selectedIndex" @select="selected"> <el-menu-item index="1"> 主页 </el-menu-item> <el-menu-item index="2"> 新闻 </el-menu-item> <el-sub-menu index="3"> <template #title>产品</template> <el-menu-item index="3-1">家电</el-menu-item> <el-menu-item index="3-2">厨具</el-menu-item> </el-sub-menu> <el-menu-item index="4">联系我们</el-menu-item> <el-menu-item index="5">关于</el-menu-item> </el-menu> </el-container> </div> </div> </template> <script lang="ts" setup> import {ref} from 'vue' let selectedIndex = ref("1") const selected = (index, indexPath) =>{ console.log("index", index, "indexPath", indexPath) } </script>
-
水平导航
<template> <div class="content"> <div class="common-layout"> <el-container> <el-menu mode="horizontal" :default-active="selectedIndex" @select="selected"> <el-menu-item index="1"> 主页 </el-menu-item> <el-menu-item index="2"> 新闻 </el-menu-item> <el-sub-menu index="3"> <template #title>产品</template> <el-menu-item index="3-1">家电</el-menu-item> <el-menu-item index="3-2">厨具</el-menu-item> </el-sub-menu> <el-menu-item index="4">联系我们</el-menu-item> <el-menu-item index="5">关于</el-menu-item> </el-menu> </el-container> </div> </div> </template> <script lang="ts" setup> import {ref} from 'vue' let selectedIndex = ref("1") const selected = (index, indexPath) =>{ console.log("index", index, "indexPath", indexPath) } </script>
-
el-menu 属性
- background-color: 菜单背景颜色
- text-color: 文本颜色
- active-text-color: 选中文本颜色
- style: 样式,可以设为宽度和高度
<el-menu mode="horizontal" :default-active="selectedIndex" @select="selected" background-color="#0064" text-color="#0ff" active-text-color="#00ff00" style="height:40px;width:600px"> <el-menu-item index="1"> 主页 </el-menu-item> <el-menu-item index="2"> 新闻 </el-menu-item> <el-sub-menu index="3"> <template #title>产品</template> <el-menu-item index="3-1">家电</el-menu-item> <el-menu-item index="3-2">厨具</el-menu-item> </el-sub-menu> <el-menu-item index="4">联系我们</el-menu-item> <el-menu-item index="5">关于</el-menu-item> </el-menu>
-
面包屑
面包屑用于描述当前页面的路径,可在路径上点击返回到上一级路径
<h3> 面包屑 </h3> <el-breadcrumb separator=">"> <!--separator用于指定分隔符--> <el-breadcrumb-item><a href="#">首页</a></el-breadcrumb-item> <el-breadcrumb-item><a href="#">产品</a></el-breadcrumb-item> <el-breadcrumb-item><a href="#">关于我们</a></el-breadcrumb-item> </el-breadcrumb>
展示效果: 首页>产品>关于我们
-
下拉菜单
<h3>下拉菜单</h3> <el-dropdown @command="userCommand"> <span> 个人中心<el-icon><User/></el-icon> </span> <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="order">订单</el-dropdown-item> <el-dropdown-item command="logout">退出</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown>
<script lang="ts" setup> <!--当用户点击具体的el-dropdown-item, 则会将对应的command传递到该函数--> const userCommand = (command:String) => { console.log("command:", command) } </script>
1.3.6 标签页
<h3> 标签页 </h3>
<el-tabs v-model="selectedName" @tab-click="tabClick">
<el-tab-pane label="排行榜" name="1">这里是排行榜</el-tab-pane>
<el-tab-pane label="好友" name="2">这里是好友</el-tab-pane>
<el-tab-pane label="背包" name="3">这里是背包</el-tab-pane>
<el-tab-pane label="上帝" name="4">这里是商店</el-tab-pane>
</el-tabs>
<script lang="ts" setup>
import {ref} from 'vue'
// 默认选中的标签名称
let selectedName = ref("2")
// 选中标签触发的回调
const tabClick = (tab:any, event:any) =>{
console.log("tab", tab.props, 'event', event, tab.props.name)
}
</script>
说明:
1. 每个标签定义一个名字name属性, el-tabs的v-model和对应的name对应
2. tabClick的tab.props中可以获取每个item的属性. 获取name,需要tab.props.name
如果要将标签也设置成卡片风格,只需要在el-tabs增加type=“card”, 有标签的卡片风格: border-card
<el-tabs v-model="selectedName" @tab-click="tabClick" type="card">
-
动态标签页
<h3>动态添加</h3> <el-button @click="tabAdd">添加</el-button> <el-tabs v-model="selectedName2" @tab-remove="tabRemove" closable type="card"> <el-tab-pane v-for="value in tab.arr" :key="value.name" :label="value.title" :name="value.name"> {{ value.content }} </el-tab-pane> </el-tabs>
<script lang="ts" setup> import {ref, reactive} from 'vue' let selectedName2 = ref("1") const tabAdd = () =>{ let index = tab.arr.length index++ tab.arr.push({ name:index.toString(), title:"新建选项"+index, content:"这是新建内容"+index }) } const tabRemove = (name:string)=>{ console.log("tabremove", name) const index = tab.arr.findIndex((v)=>v.name === name); tab.arr.splice(index, 1) // 移除 } const tab = reactive({ arr:[ {name:"1", title:"语文", content:"语文分数"}, {name:"2", title:"数学", content:"数学分数"}, {name:"3", title:"英语", content:"英语分数"}, ] }) </script>
1.3.7 输入框
-
文本输入框
<el-input v-model="name" maxlength="10" show-word-limit clearable :placeholder="nameHolder"/>
-
密码框
<el-input v-model="password" show-password :placeholder="passwordHolder"/>
-
文本域
<el-input v-model="introduce" maxlength="50" show-word-limit type="textarea" rows="3" placeholder="这家伙太懒了"/>
-
输入框属性
-
size
- large
- small
-
输入框前置
<el-row> <el-col :span="6"> 个人主页1: </el-col> <el-col :span="18"> <el-input v-model="webUrl"> <template #prepend>https://</template> </el-input> </el-col> </el-row>
-
输入框后置
<el-row> <el-col :span="6"> 联系QQ邮箱: </el-col> <el-col :span="18"> <el-input v-model="qqEmail"> <template #append>qq.com</template> </el-input> </el-col> </el-row>
-
输入框中置
<el-row> <el-col :span="6"> 个人主页2: </el-col> <el-col :span="18"> <el-input v-model="webUrl2"> <template #prepend>https://</template> <template #append>.com</template> </el-input> </el-col> </el-row>
-
完整例子
<template>
<div class="content">
<div class="common-layout">
<h3 style="text-align:center margin-bottom:100px"> 用户注册 </h3>
<el-row>
<el-col :span="6">
账号:
</el-col>
<el-col :span="18">
<el-input v-model="name" maxlength="10" show-word-limit clearable :placeholder="nameHolder"/>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
密码:
</el-col>
<el-col :span="18">
<el-input v-model="password" maxlength="10" show-password :placeholder="passwordHolder"/>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
自我介绍:
</el-col>
<el-col :span="18">
<el-input v-model="introduce" maxlength="50" show-word-limit type="textarea" rows="3" placeholder="这家伙太懒了"/>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
个人主页1:
</el-col>
<el-col :span="18">
<el-input v-model="webUrl">
<template #prepend>https://</template>
</el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
个人主页2:
</el-col>
<el-col :span="18">
<el-input v-model="webUrl2">
<template #prepend>https://</template>
<template #append>.com</template>
</el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
联系QQ邮箱:
</el-col>
<el-col :span="18">
<el-input v-model="qqEmail">
<template #append>qq.com</template>
</el-input>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
请输入擅长学科:
</el-col>
<el-col :span="18">
<el-input placeholder="请输入课程名称">
<template #prepend>
<el-select v-model="selectedClass" place="请选择" style="width:100px" >
<el-option label="语文" value="1"/>
<el-option label="数学" value="2"/>
<el-option label="英语" value="3"/>
</el-select>
</template>
<template #append>
<el-button>
<el-icon><Search/></el-icon>
</el-button>
</template>
</el-input>
</el-col>
</el-row>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
let name = ref("")
let nameHolder = ref("请输入账号")
let password = ref("")
let passwordHolder = ref("请输入密码")
let introduce = ref("")
let webUrl = ref("")
let qqEmail = ref("")
let webUrl2 = ref("")
let selectedClass = ref("1")
</script>
1.3.8 单选框复选框
-
单选框
<el-row> <el-col :span="6"> 语言: </el-col> <el-col :span="18"> <el-radio v-model="lang" @change="langChange" value="1">英语</el-radio> <el-radio v-model="lang" @change="langChange" value="2">日语</el-radio> <el-radio v-model="lang" @change="langChange" value="3">汉语</el-radio> </el-col> </el-row> <script lang="ts" setup> import {ref} from 'vue' let lang = ref("1") const langChange = (v:string)=>{ console.log("langChanged", v) } </script>
说明: 1. 将每个el-radio单独进行绑定一个career变量 2. 绑定的事件的参数是实际切换的选项的value值
-
单选框组
<el-row> <el-col :span="6"> 籍贯: </el-col> <el-radio-group v-model="city" @change="cityChange"> <el-radio value="cd">成都</el-radio> <el-radio value="sh">上海</el-radio> <el-radio value="bj">北京</el-radio> </el-radio-group> </el-row>
说明: 1. 单选框组,不需要每个选项绑定一个变量,而只需在组中绑定就可以
-
复选框
<el-row> <el-col :span="6"> 学科: </el-col> <el-checkbox-group v-model="subjects" @change="subjectChanged"> <el-checkbox value="1">高等数学</el-checkbox> <el-checkbox value="2">大学英语</el-checkbox> <el-checkbox value="3">马列主义</el-checkbox> <el-checkbox value="4">电子电路</el-checkbox> </el-checkbox-group> </el-row>
<script lang="ts" setup> import {ref} from 'vue' let subjects = ref(["1", "2"]) const subjectChanged = (v:any)=>{ console.log(v) } </script>
说明: 1. 数组,这里实验了下,必须使用ref定义响应式数组,用reactive无效. 2. 回调函数的v,是当前选择的所有的数组.
1.3.9 下拉框
-
静态下拉框
<el-row> <el-col :span="6"> 城市: </el-col> <el-col :span="18"> <el-select v-model="city" placeholder="请选择" @change="selectCity"> <el-option value="cd" label="成都"/> <el-option value="cd" label="上海"/> <el-option value="cd" label="北京"/> </el-select> </el-col> </el-row>
-
动态下拉框
<el-row> <el-col :span="6"> 年龄: </el-col> <el-col :span="18"> <el-select v-model="ageIndex" placeholder="请选择" @change="selectAges"> <el-option v-for="item in ageRanges" :value="item.value" :key="item.value" :label="item.label"/> </el-select> </el-col> </el-row>
-
动态多选下拉框
<el-row>
<el-col :span="6">
爱好:
</el-col>
<el-col :span="18">
<el-select v-model="selectHobbies" multiple placeholder="请选择" @change="hobbiesChange">
<el-option v-for="item in hobbies" :value="item.value" :key="item.value" :label="item.label"/>
</el-select>
</el-col>
</el-row>
说明:
1. 多选下拉框,只需添加 multiple即可.
完整例子:
<template>
<div class="content">
<div class="common-layout">
<h3 style="text-align:center"> </h3>
<el-row>
<el-col :span="6">
城市:
</el-col>
<el-col :span="18">
<el-select v-model="city" placeholder="请选择" @change="selectCity">
<el-option value="cd" label="成都"/>
<el-option value="cd" label="上海"/>
<el-option value="cd" label="北京"/>
</el-select>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
年龄:
</el-col>
<el-col :span="18">
<el-select v-model="ageIndex" placeholder="请选择" @change="selectAges">
<el-option v-for="item in ageRanges" :value="item.value" :key="item.value" :label="item.label"/>
</el-select>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
爱好:
</el-col>
<el-col :span="18">
<el-select v-model="selectHobbies" multiple placeholder="请选择" @change="hobbiesChange">
<el-option v-for="item in hobbies" :value="item.value" :key="item.value" :label="item.label"/>
</el-select>
</el-col>
</el-row>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'Content'
}
</script>
<script lang="ts" setup>
import {ref} from 'vue'
let city = ref("cd")
const selectCity = (v:any)=>{
console.log("cityChanged", v)
}
let ageRanges = ref([
{value:1,label:"6岁以下"},
{value:2,label:"6岁-12岁"},
{value:3,label:"13岁-18岁"},
{value:4,label:"18岁-60岁"},
{value:5,label:"60岁以上"},
])
let hobbies = ref([
{value:1,label:"篮球"},
{value:2,label:"足球"},
{value:3,label:"健美"},
{value:4,label:"体操"},
{value:5,label:"游泳"},
])
let ageIndex = ref(2)
const selectAges = (v:string)=>{
console.log("selectAges", v)
}
let selectHobbies = ref([1,3])
const hobbiesChange = (v:any)=>{
console.log("hobbiesChange", v)
}
</script>
1.3.10 日期选择器
-
导入中文语言包
main.ts
// 引入createApp用于创建应用 import {createApp} from 'vue' // 引入 App跟组件 import App from './App.vue' import ElementPlus from 'element-plus' import zhCn from 'element-plus/dist/locale/zh-cn.mjs' // ElementPlus中文语言包 import 'element-plus/dist/index.css' // 使用图标 import * as ElementPlusIconsVue from '@element-plus/icons-vue' // 导入ElementPlus组件库中所有图标. const app = createApp(App) // 注册 ElementPlus组件库中所有图标到全局Vue应用中. for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(ElementPlus, { locale: zhCn }) app.mount('#app')
-
日期选择器
<el-row> <el-col :span="6"> 出生日期: </el-col> <el-col :span="18"> <el-date-picker v-model="bornDate" type="date" place="请选择"/> </el-col> </el-row>
-
日期时间选择器
<el-row> <el-col :span="6"> 注册时间: </el-col> <el-col :span="18"> <el-date-picker v-model="loginTime" type="datetime" place="请选择"/> </el-col> </el-row>
-
日期时间选择器+响应时间
<el-row> <el-col :span="6"> 完成时间: </el-col> <el-col :span="18"> <el-date-picker v-model="finishTime" value-format="YYYY-MM-DD HH:mm:ss" @change="finishTimeChange" type="datetime" place="请选择"/> </el-col> </el-row> <script> let finishTime = ref("") const finishTimeChange = (val:any)=>{ console.log("finishTimeChanged:", val) } </script>
说明: 1. 默认的时间,格式是通用的格式时间,不是我们常见的格式,需要设定value-format转换
1.3.11 表单
表单:el-form, 每个提交项:el-form-item, 提交项可以添加 label="用户名" 作为标签
用户登录例子:
<template>
<div class="content">
<div class="common-layout">
<h3>用户注册</h3>
<el-form label-width="80" style="width:100%;">
<el-row>
<el-col :span="6">
账号名称:
</el-col>
<el-col :span="18">
<el-form-item >
<el-input v-model="user.account" placeholder="请填写账户名称"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
性别:
</el-col>
<el-col :span="18">
<el-form-item>
<el-radio-group v-model="user.gender">
<el-radio v-for="item in genders" :key="item.index" :value="item.index">{{item.label}}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
城市:
</el-col>
<el-col :span="18">
<el-form-item>
<el-radio-group v-model="user.city">
<el-radio v-for="item in citys" :key="item.index" :value="item.index">{{item.label}}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
出生日期:
</el-col>
<el-col :span="18">
<el-date-picker v-model="user.bornDate" type="date" place="请选择"/>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
注册时间:
</el-col>
<el-col :span="18">
<el-date-picker v-model="user.loginTime" type="datetime" place="请选择"/>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
完成时间:
</el-col>
<el-col :span="18">
<el-date-picker v-model="user.finishTime" value-format="YYYY-MM-DD HHmmss" @change="finishTimeChange" type="datetime" place="请选择"/>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
学科个数:
</el-col>
<el-col :span="18">
<el-input-number v-model="user.subjectNum" :min="1" :max="10" @change="handleChange" />
</el-col>
</el-row>
<el-row>
<el-col :span="12" :offset="12">
<el-button-group>
<el-button type="primary" @click="submitUser">提交</el-button>
<el-button type="primary" @click="resetUser">重置</el-button>
</el-button-group>
</el-col>
</el-row>
</el-form>
<h3 style="text-align:center"> </h3>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref, reactive} from 'vue'
const citys = ref([
{index:1, label:"成都"},
{index:2, label:"北京"},
{index:3, label:"上海"},
])
const genders = ref ([
{index:1, label:"男"},
{index:2, label:"女"},
])
let user = reactive({
account: "",
gender: 1,
city: 1,
bornDate:"",
loginTime:"",
finishTime:"",
subjectNum:2
})
const finishTimeChange = (val:any)=>{
console.log("finishTimeChanged:", val)
}
let num = ref(1)
const handleChange = (v:number)=>{
num.value = v
}
const submitUser = ()=>{
console.log(user)
}
const resetUser = ()=>{
Object.assign(user, {
account: "",
gender: 1,
city: 1,
bornDate:"",
loginTime:"",
finishTime:"",
subjectNum:1
})
}
</script>
1.3.12 对话框
对话框使用标签el-dialog, v-model绑定一个布尔值变量,代表是否显示,
<el-dialog v-model="showDialog" width="800" title="用户登录" draggable @close="showDialog=false">
<el-form label-width="100">
<el-form-item label="用户名:">
<el-input v-model="user.account" placeholder="请输入账号"></el-input>
</el-form-item>
<el-form-item label="密码:">
<el-input v-model="user.password" placeholder="请输入密码"></el-input>
</el-form-item>
<el-row>
<el-col :offset="10" :span="8">
<el-button-group>
<el-button type="primary" @click="showDialog=false">提交</el-button>
<el-button type="primary" @click="showDialog=false">重置</el-button>
</el-button-group>
</el-col>
</el-row>
</el-form>
</el-dialog>
<script lang="ts" setup>
import {ref, reactive} from 'vue'
let showDialog = ref(false)
let user = reactive({
account:"",
password:""
})
</script>
1.3.13 分页
- 分页使用的标签是el-pagination
- 属性:
- page-size
- total
- layout
- prev 显示上一页
- pager // 页码
- next // 显示下一页
- jumper // 显示跳转
- total // 显示总条数
- 属性:
<template>
<el-row>
<el-col :span="12">
<h5> page-size:每页显示记录数 total: 总记录数</h5>
</el-col>
<el-col :span="12">
<el-pagination layout="prev, pager, next" :page-size="10" :total="500"/>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<h5> 显示背景</h5>
</el-col>
<el-col :span="12">
<el-pagination layout="prev, pager, next" :page-size="10" :total="500" background/>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<h5> 显示总数</h5>
</el-col>
<el-col :span="12">
<el-pagination layout="prev, pager, next, total" :page-size="10" :total="500" background/>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<h5> 跳转</h5>
</el-col>
<el-col :span="12">
<el-pagination layout="prev, pager, next, total, jumper" :page-size="10" :total="500" background/>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<h5> 事件绑定</h5>
</el-col>
<el-col :span="12">
<el-pagination layout="prev, pager, next, total, jumper" @current-change="currentPage" :page-size="10" :total="500" background/>
</el-col>
</el-row>
</template>
1.3.12 表格
-
普通表格
<h3>学生表1</h3> <el-table :data="data.students" height="150" border style="width:800px"> <el-table-column prop="id" label="编号" width="80"/> <el-table-column prop="name" label="编号"/> <el-table-column prop="age" label="年龄"/> <el-table-column prop="gender" label="性别"/> </el-table>
-
可多选的表格
<h3>学生表2</h3> <el-table :data="data.students" height="150" border style="width:800px"> <el-table-column type="selection" width="80"/> <el-table-column prop="id" label="编号" width="80"/> <el-table-column prop="name" label="姓名"/> <el-table-column prop="age" label="年龄"/> <el-table-column prop="gender" label="性别"/> </el-table>
-
动态表格+动态分页+操作多选和单选
<template> <h3> 学生表3</h3> <el-table :data="data.students.slice(currentPage*pageSize, (currentPage+1)*pageSize)" @selection-change="selected" border style="width:100%"> <el-table-column type="selection" width="80"/> <el-table-column v-for="(column, index) in tableColumns" :key="index" :prop="column.prop" :label="column.label"> </el-table-column> <el-table-column label="操作" width="200"> <template #default="scope"> <el-button size="small" type="primary" @click="edit(scope.$index, scope.row)">编辑</el-button> <el-button size="small" type="danger">删除</el-button> </template> </el-table-column> </el-table> <el-pagination layout="prev, pager, next, jumper, total" @current-change="handleCurrentChange" :page-size="pageSize" :total="data.students.length" :current-page="currentPage + 1" /> <el-button type="danger" @click="del">删除选中项</el-button> </template>
<script lang="ts" setup> import {ref, reactive} from 'vue' // 动态定义表格需要显示的字段 const tableColumns = ref([ {prop:"id", label:"编号"}, {prop:"name", label:"姓名"}, {prop:"age", label:"年龄"}, {prop:"gender", label:"性别"}, {prop:"city", label:"城市"}, ]) // 显示的动态数据. const data = ref({ students : [ {id:1, name:"张三", age:24, gender:1, city:'cd'}, {id:2, name:"李四", age:25, gender:2, city:'sh'}, {id:3, name:"王五", age:27, gender:1, city:'tj'}, {id:4, name:"赵六", age:22, gender:2, city:'bj'}, {id:5, name:"李七", age:23, gender:2, city:'bj'}, {id:6, name:"张三2", age:24, gender:1, city:'cd'}, {id:7, name:"李四3", age:25, gender:2, city:'sh'}, {id:8, name:"王五4", age:27, gender:1, city:'tj'}, {id:9, name:"赵六5", age:22, gender:2, city:'bj'}, {id:10, name:"李七6", age:23, gender:2, city:'bj'}, {id:11, name:"张三7", age:24, gender:1, city:'cd'}, {id:12, name:"李四8", age:25, gender:2, city:'sh'}, {id:13, name:"王五9", age:27, gender:1, city:'tj'}, {id:14, name:"赵六10", age:22, gender:2, city:'bj'}, {id:15, name:"李七11", age:23, gender:2, city:'bj'}, {id:16, name:"张三12", age:24, gender:1, city:'cd'}, {id:17, name:"李四13", age:25, gender:2, city:'sh'}, {id:18, name:"王五14", age:27, gender:1, city:'tj'}, {id:19, name:"赵六15", age:22, gender:2, city:'bj'}, {id:20, name:"李七16", age:23, gender:2, city:'bj'}, ] }) // 当前显示的页 let currentPage = ref(0) // 每一页显示的数据条数 const pageSize = ref(5) // 当前页发生变化,需要动态刷新数据 const handleCurrentChange = (val:number) =>{ // Element 的分页是从 1 开始的,但数组索引是从 0 开始的,所以需要减 1 currentPage.value = val - 1 } // 保存当前选中的列,以帮助点击删除是,进行删除. let selectedIds:Array<number> = [] const selected = (data:any) =>{ selectedIds = [] data.forEach(val => { selectedIds.push(val.id) }); console.log("selectedIds", selectedIds) } const edit = (index:number,row:number) =>{ console.log("index", index, "row", row) } const del = () =>{ console.log("del:", selectedIds) } </script>