尚硅谷Vue3入门到实战,最新版vue3+TypeScript前端开发教程

1.创建Vue3工程

npm create vue@latest

或者

npm init vue@latest

输入项目名和需要的工具后进入项目

如果项目报错 使用命令安装Node.js的项目依赖包

npm i

启动vue项目,查看项目是否创建完成

npm run dev

直接删掉src

然后创建src文件夹,在该文件夹中创建main.ts和App.vue文件

在src中有两个文件必不可少,分别是

main.ts

App.vue

App.vue基本内容:

<style>

</style>
<template>
    
</template>

<script lang="ts">

</script>

那么main.ts的基本内容:

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

2.写一个简单的效果

使用插件

创建components文件夹,然后再创建Person.vue文件

components/Person.vue:

<style>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2>姓名:{{name}}</h2>
        <h2>年龄:{{age}}</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="showTel">显示联系方式</button>
    </div>
</template>

<script lang="ts">
   export default{
    name :'Person',
    data(){
        return {
            name:'张三',
            age:18,
            tel:'13888888888'
        }
    },
    methods:{
        showTel(){
            alert(this.tel)
        },
        changeName(){
            this.name = 'zhang-san'
        },
        changeAge(){
            this.age += 1
        }
    }
   }
</script>

App.vue:

<style scoped>
    .app{
        background-color: skyblue;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
</style>
<template>
    <div class="app">
        <h1>App你好</h1>
        <PersonVue/>
    </div>
</template>

<script lang="ts">
import PersonVue from "./components/Person.vue";

export default{
    name:'App',
    components:{PersonVue}
}
</script>

这说明了Vue3可以写Vue2的代码

上面是选项式API,我们将其改为组合式API,如下:

在setup函数中的this是undefined

如果遇到组件名和文件名不一致的情况,如:

为了使用一个script标签标识setup的同时命名文件的组件名,则需要安装插件

npm i vite-plugin-vue-setup-extend -D

然后再vite.config.ts中加入

import VueSetupExtend from 'vite-plugin-vue-setup-extend'

然后在plugins中追加VueSetupExtend()

如下:

所修改后的组合式API:

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2>姓名:{{name}}</h2>
        <h2>年龄:{{age}}</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="showTel">显示联系方式</button>
    </div>
</template>

<script lang="ts" setup name="Person234">
    let name = '张三'
    let age = 18
    let tel = '13888888888'

    function changeName(){
        name = 'zhang-san'
    }

    function changeAge(){
        age += 1
    }

    function showTel(){
        alert(tel)
    }
</script>

这还不是完整的修改,因为变量name和age,无法通过调用函数的方式显示改变后变量的值,同时还没使用到ref,下面的修改会解决这些问题

3.ref创建,基本类型的响应式数据

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2>姓名:{{name}}</h2>
        <h2>年龄:{{age}}</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="showTel">显示联系方式</button>
    </div>
</template>

<script lang="ts" setup name="Person">
import { ref } from "vue"
    let name = ref('张三')
    let age = ref(18)
    let tel = '13888888888'

    function changeName(){
        name.value = 'zhang-san'
    }

    function changeAge(){
        age.value += 1
    }

    function showTel(){
        alert(tel)
    }
</script>

4.reative创建,对象类型的响应式数据

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
       <h2>一辆{{car.brand}}车,价值{{car.price}}万</h2>
       <button @click="changePrice">修改汽车的价格</button>
    </div>
    <br>
    <h2>游戏列表:</h2>
    <ul>
        <li v-for="game in games" :key="game.id">{{game.name}}</li>
    </ul>
    <button @click="changeFirstGame">修改第一个游戏名称</button>
</template>

<script lang="ts" setup name="Person">
    import { reactive } from "vue"
    let car = reactive({brand:'奔驰',price:100})
    let games = reactive([
        {id:'game01',name:'王者'},
        {id:'game02',name:'原神'},
        {id:'game03',name:'三国'}
    ])

    function changePrice(){
        car.price += 10
    }

    function changeFirstGame(){
        games[0].name = "荣耀"
    }

</script>

5.将reactive修改为ref的方式

修改的结果如下:

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
       <h2>一辆{{car.brand}}车,价值{{car.price}}万</h2>
       <button @click="changePrice">修改汽车的价格</button>
    </div>
    <br>
    <h2>游戏列表:</h2>
    <ul>
        <li v-for="game in games" :key="game.id">{{game.name}}</li>
    </ul>
    <button @click="changeFirstGame">修改第一个游戏名称</button>
</template>

<script lang="ts" setup name="Person">
    import {ref } from "vue"
    let car = ref({brand:'奔驰',price:100})
    let games = ref([
        {id:'game01',name:'王者'},
        {id:'game02',name:'原神'},
        {id:'game03',name:'三国'}
    ])

    function changePrice(){
        car.value.price += 10
    }

    function changeFirstGame(){
        games.value[0].name = "荣耀"
    }

</script>

6.volar工具

ref要定义的对象要加上.value改变值

可以使用volar工具,这样不用刻意去记哪个是ref,那个是reactive定义的对象

下载插件后,勾选扩展功能

7.处理数据返回

对于返回来reactive定义对象的数据,如果要更新原本的数据,可以使用Object.assign这么处理

8.toRefs和toRef

toRefs和toRef都是拿响应式数据解构出来,使其具有响应式能力,都是ref对象,所以需要.value

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
       <h2>姓名:{{name}}</h2>
       <h2>年龄:{{age}},n1:{{ n1 }}</h2>
       <button @click="changeName">修改名字</button>
       <button @click="changeAge">修改年龄</button>
    </div>
   
</template>

<script lang="ts" setup name="Person">
    import {reactive, ref, toRef, toRefs } from "vue"
    
    let person = reactive({
        name:'张三',
        age:18
    })

    let {name,age} = toRefs(person)
    let n1 = toRef(person,'age')

    function changeName(){
        name.value += '~'
    }
    function changeAge(){
        age.value += 1
    }

</script>

9.computed计算属性

定义computed 想可读可写的方式如下所示

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
      姓:<input type="text" v-model="firstName"/>
      <br>
      名:<input type="text" v-model="lastName"/>
      <br>
      姓名:<span>{{fullName}}</span>
      <button @click="changeFullName">修改名字</button>
    </div>
   
</template>

<script lang="ts" setup name="Person">
    import { computed, ref } from "vue"
    let firstName = ref('张')
    let lastName = ref('三')

    //这么定义的fullName是一个计算属性,只可读
/*     let fullName = computed(()=>{
        return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value
    }) */

    //这么定义的fullName是一个计算属性,可读可写
    let fullName = computed({
        get(){
            return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + lastName.value
        },
        set(val){
            const [str1,str2] = val.split('-')
            firstName.value = str1
            lastName.value = str2
        }
    })

    function changeFullName(){
        fullName.value = 'li-si'
    }
</script>

10.watch监视

10.1watch监视ref定义基本类型的数据

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2>sum:{{sum}}</h2>
        <button @click="changeSum">sum++</button>
    </div>
   
</template>

<script lang="ts" setup name="Person">
   import {ref,watch} from 'vue'

   let sum = ref(0)

   function changeSum(){
        sum.value += 1
   }

   const stopWatch = watch(sum,(newValue,oldValue)=>{
        console.log('sum变化了',newValue,oldValue)
        //定义停止监听条件
        if(newValue >= 10){
            stopWatch()
        }
   })
</script>

10.2watch监视ref定义的对象类型数据

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2>姓名:{{person.name}}</h2>
        <h2>年龄:{{person.age}}</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changePerson">修改人</button>
    </div>
   
</template>

<script lang="ts" setup name="Person">
    import {ref,watch} from 'vue'
    let person = ref({
        name:'张三',
        age:18
    })

    function changeName(){
        person.value.name = '李四'
    }

    function changeAge(){
        person.value.age += 1
    }

    function changePerson(){
        person.value = {name:'小刘',age:20}
    }

    watch(person,(newValue,oldValue)=>{
        console.log(newValue,oldValue)
    },{deep:true})
</script>

10.3watch监视reactive定义对象类型数据,且默认开启了深度监听

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2>姓名:{{person.name}}</h2>
        <h2>年龄:{{person.age}}</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changePerson">修改人</button>
    </div>
   
</template>

<script lang="ts" setup name="Person">
    import {reactive,watch} from 'vue'
    let person = reactive({
        name:'张三',
        age:18
    })

    function changeName(){
        person.name = '李四'
    }

    function changeAge(){
        person.age += 1
    }

    function changePerson(){
        Object.assign(person,{name:'小刘',age:20})
    }

    watch(person,(newValue,oldValue)=>{
        console.log(newValue,oldValue)
    })
</script>

10.4watch监视ref或reactive定义对象类型数据中某个属性

如果监听响应式对象中的某个属性,若该属性是基本类型属性,要写成函数式

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2>姓名:{{person.name}}</h2>
        <h2>年龄:{{person.age}}</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changePerson">修改人</button>
    </div>
   
</template>

<script lang="ts" setup name="Person">
    import {reactive,watch} from 'vue'
    let person = reactive({
        name:'张三',
        age:18
    })

    function changeName(){
        person.name += '~'
    }

    function changeAge(){
        person.age += 1
    }

    function changePerson(){
        Object.assign(person,{name:'小刘',age:20})
    }

    watch(()=>person.name,(newValue,oldValue)=>{
        console.log(newValue,oldValue)
    })
</script>

10.5watch监听多个属性

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2>姓名:{{person.name}}</h2>
        <h2>年龄:{{person.age}}</h2>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changePerson">修改人</button>
    </div>
   
</template>

<script lang="ts" setup name="Person">
    import {reactive,watch} from 'vue'
    let person = reactive({
        name:'张三',
        age:18
    })

    function changeName(){
        person.name += '~'
    }

    function changeAge(){
        person.age += 1
    }

    function changePerson(){
        Object.assign(person,{name:'小刘',age:20})
    }

    watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
        console.log(newValue,oldValue)
    })
</script>

10.6watchEffect

watchEffect不用明确指出监听的数据(函数中用到哪些属性,那就监听哪些属性)

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2>水温:{{temp}}</h2>
        <h2>水位:{{height}}</h2>
        <button @click="changeTemp">提高水温+10</button>
        <button @click="changeHeight">提高水位+10</button>
    </div>
   
</template>

<script lang="ts" setup name="Person">
    import {reactive,ref,watch, watchEffect} from 'vue'
   
    let temp = ref(10)
    let height = ref(0)

    function changeTemp(){
        temp.value += 10
    }
    
    function changeHeight(){
        height.value += 10
    }

    //监听 -- watch
    // watch([temp,height],(value)=>{
    //     let [newTemp,newHeight] = value
    //     if(newTemp>=30 || newHeight>=50){
    //         console.log('服务器发起请求')
    //     }
    // })

    //监听 -- watchEffect
    watchEffect(()=>{
        if(temp.value >= 30 || height.value >=50){
            console.log('服务器发起请求')
        }
    })

</script>

11.标签的ref属性

创建一个title2,用于存储ref标记的内容

Person.vue:

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2 ref="title2">北京</h2>
        <button @click="showLog">点击我输出h2元素</button>
    </div>
   
</template>

<script lang="ts" setup name="Person">
import { ref } from 'vue';

let title2 = ref()

function showLog(){
    console.log(title2.value)
}


</script>

App.vue:

<style scoped>
    .app{
        background-color: skyblue;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
</style>
<template>
    <h2 ref="title2">你好</h2>
    <button @click="showLog">点击我输出h2</button>
    <PersonVue/>
</template>

<script lang="ts" setup name="App">
import PersonVue from "./components/Person.vue";
import { ref }from 'vue'
let title2 = ref()

function showLog(){
    console.log(title2.value)
}

</script>

如果父组件想获取子组件中的元素,则可以使用defineExpose

Person.vue:

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
    <div class="person">
        <h2 ref="title2">北京</h2>
        <button @click="showLog">点击我输出h2元素</button>
    </div>
   
</template>

<script lang="ts" setup name="Person">
import { ref,defineExpose } from 'vue';

let title2 = ref()
let a = ref(0)
let b = ref(1)

function showLog(){
    console.log(title2.value)
}

defineExpose({a,b})

</script>

App.vue:

<style scoped>
    .app{
        background-color: skyblue;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
</style>
<template>
    <h2 ref="title2">你好</h2>
    <button @click="showLog">点击我输出</button>
    <PersonVue ref="ren" />
</template>

<script lang="ts" setup name="App">
import PersonVue from "./components/Person.vue";
import { ref }from 'vue'
let title2 = ref()
let ren = ref()

function showLog(){
    console.log(ren.value)
    
}



</script>

12.TS中的接口、泛型、自定义类型

如果引入的是数据类型,而不是值的话,则需要使用type来标识

创建types/index.ts

//定义一个接口,用于限制person对象的具体属性
export interface PersonInter {
    id:string,
    name:string,
    age:number
}

//一个自定义类型
// export type Persons = Array<PersonInter>
export type Persons = PersonInter[]

Person.vue:

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
  <div class="person">

  </div>
   
</template>

<script lang="ts" setup name="Person">
import  {type PersonInter,type Persons } from '@/types'


// let person:PersonInter = {id:'dasads01',name:'zhangsan',age:20}

let person:Persons = [
    {id:'dasads01',name:'zhangsan',age:20},
    {id:'dasads02',name:'lisi',age:22},
    {id:'dasads03',name:'wangwu',age:30}
]
</script>

13.props的使用

如果子组件想获取父组件中的元素,则可以使用defineProps

Person.vue:

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}
</style>
<template>
  <div class="person">
    <ul>
        <li v-for="personObj in list" :key="personObj.id">
            {{ personObj.name }} -- {{ personObj.age }}
        </li>
    </ul>
  </div>
   
</template>

<script lang="ts" setup name="Person">
import  { Persons } from '@/types'

//只接收list
// defineProps(['list'])

//接收list + 限制类型
// defineProps<{list:Persons}>()


//接收list + 限制类型 + 限制必要性 + 指定默认值
//在list后打上?,表示可以不传入数据
withDefaults(defineProps<{list?:Persons}>(),{
    list:()=>[{id:'asdasas01',name:'康之福',age:23}]
})

</script>

App.vue:

<style scoped>
    .app{
        background-color: skyblue;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }
</style>
<template>

    <PersonVue a="哈哈" :list="personList"/>
</template>

<script lang="ts" setup name="App">
import PersonVue from "./components/Person.vue";
import {reactive} from 'vue';
import { type Persons } from "./types";

let personList = reactive<Persons>([
    {id:'dasads01',name:'zhangsan',age:20},
    {id:'dasads02',name:'lisi',age:22},
    {id:'dasads03',name:'wangwu',age:30}
])


</script>

14.生命周期

14.1 Vue2的生命周期

分为四个阶段:创建、挂载、更新、销毁

创建前:beforeCreate()

创建完毕:created()

挂载前:beforeMount()

挂载完毕:mounted()

更新前:beforeUpdate()

更新完毕:updated()

销毁前:beforeDestroy()

销毁完毕:destroyed()

14.2 Vue3的生命周期

创建阶段:setup

挂载阶段:onBeforeMount、onMounted

更新阶段:onBeforeUpdate、onUpdated

卸载阶段:onBeforeUnmount、onUnmounted

常用的钩子:onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)

15.自定义Hooks

安装axios

npm i axios

以两个函数为示例,这是没有使用hooks的方式

Person.vue:

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}

img{
    height: 100px;
    margin: 10px;
}
</style>
<template>
  <div class="person">
    <h2>当前求和:{{ sum }}</h2>
    <button @click="add">点我+1</button>
    <br>
    <button @click="getDog">换一只狗</button>
    <img v-for="(dog,index) in dogList" :src="dog" :key="index">
  </div>
   
</template>

<script lang="ts" setup name="Person">

import { reactive, ref, } from 'vue';
import axios from 'axios';


let sum = ref(0)

let dogList = reactive([
    'https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg'
])

async function getDog(){
    let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
    dogList.push(result.data.message)
}

function add(){
    sum.value += 1
}

</script>

使用hooks后的方式,先创建hooks文件夹,然后创建useDog.ts和useSum.ts文件,分别存放,Sum和Dog相关的代码块,使用方式:

useSum.ts:

import {computed, onMounted, ref } from 'vue';


export default function(){
    let sum = ref(0)

    let bigsum = computed(()=>{
        return sum.value * 10
    })


    function add(){
        sum.value += 1
    }

    onMounted(()=>{
        sum.value += 1
    })

    return {sum,add,bigsum}
}

useDog.ts:

import { reactive} from 'vue';
import axios from 'axios';


export default function(){
    let dogList = reactive([
        'https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg'
    ])
    
    async function getDog(){
        let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
        dogList.push(result.data.message)
    }

    return {dogList,getDog}
}

Person.vue:

<style scoped>
.person{
    background-color: skyblue;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
}
button{
    margin: 0 10px;
}

img{
    height: 100px;
    margin: 10px;
}
</style>
<template>
  <div class="person">
    <h2>当前求和:{{ sum }},放大十倍后:{{bigsum}}</h2>
    <button @click="add">点我+1</button>
    <br>
    <button @click="getDog">换一只狗</button>
    <img v-for="(dog,index) in dogList" :src="dog" :key="index">
  </div>
   
</template>

<script lang="ts" setup name="Person">
import useSum from '@/hooks/useSum'
import useDog from '@/hooks/useDog'
const {sum,add,bigsum} = useSum()
const {dogList,getDog} = useDog()

</script>

16.路由

16.1路由的基本切换效果

安装路由器

npm i vue-router

在components中创建Home.vue、News.vue、About.vue文件

创建文件夹router,然后在router中创建index.ts文件

path应该以斜杠 / 开头,以确保它是一个完整的路径

router/index.ts:

//引入createRouter
import { createRouter, createWebHistory } from "vue-router";

//引入组件
import Home from "@/components/Home.vue";
import About from "@/components/About.vue";
import News from "@/components/News.vue";

//创建路由器
const router = createRouter({
    history:createWebHistory(), //路由器编译规则
    routes:[{
        path:'/home',
        component:Home
    },{
        path:'/about',
        component:About
    },{
        path:'/news',
        component:News
    }]
})

export default router

main.ts:

import { createApp } from "vue";

import App from './App.vue'
import router from "./router";

//创建一个应用
const app = createApp(App)
//使用路由器
app.use(router)
//挂载整个应用到app容器中
app.mount('#app')

App.vue:

<style scoped>
    .app{
        background-color: skyblue;
        box-shadow: 0 0 10px;
        border-radius: 10px;
        padding: 20px;
    }

    
</style>
<template>
    <div class="navigate">
        <h1>navigate</h1>
       <RouterLink to="/home">首页</RouterLink>
       <RouterLink to="/news">新闻</RouterLink>
       <RouterLink to="/about">关于</RouterLink>
    </div>
    <br>
    内容:
    <div>
        <RouterView></RouterView>
    </div>

</template>

<script lang="ts" setup name="App">

import {RouterView,RouterLink} from 'vue-router'

</script>

16.2路由两个注意点

注意:

(1)一般组件存放在components文件夹中,路由组件存放在pages或views文件夹中

(2)通过点击导航,视觉效果上"消失"了的路由组件,默认是被销毁的,需要的时候再去挂载

16.3 to的两种写法

16.4路由命名

16.5嵌套路由

注意:children下的path的开头是不需要加上斜杠 / 的

在pages中创建文件Detail.vue

pages/Detail.vue:

<template>
    <div class="detail">
      <h2>Detail</h2>
    </div>
</template>
  
<script lang="ts" setup name="Detail">

</script>
<style scoped>

</style>

pages/News.vue:

<template>
    <div class="news">
        <h2>News</h2>
        <RouterView></RouterView>  
    </div>    
</template>

<script lang="ts" setup name="News">

</script>
<style scoped> 

</style>

router/index.ts:

//引入createRouter
import { createRouter, createWebHistory } from "vue-router";

//引入组件
import Home from "@/pages/Home.vue";
import About from "@/pages/About.vue";
import News from "@/pages/News.vue";
import Detail from "@/pages/Detail.vue";

//创建路由器
const router = createRouter({
    history:createWebHistory(), //路由器编译规则
    routes:[{
        name:'zhuye',
        path:'/home',
        component:Home
    },{
        name:'guanyu',
        path:'/about',
        component:About
    },{
        name:'xinwen',
        path:'/news',
        component:News,
        children:[{
            path:'detail',
            component:Detail
        }]
    }]
})

export default router

main.ts:

import { createApp } from "vue";

import App from './App.vue'
import router from "./router";

//创建一个应用
const app = createApp(App)
//使用路由器
app.use(router)
//挂载整个应用到app容器中
app.mount('#app')

16.6路由query参数(传参)

App.vue:

<template>  
    <div class="app">  
        <h1>navigate</h1>  
        <ul>
            <li v-for="news in newsList" :key="news.id">
                <!-- 第一种方法 -->
                <!-- <RouterLink :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`" >{{news.title}}</RouterLink>   -->
                
                <!-- 第二种写法 -->
                <RouterLink :to="{
                    path:'/news/detail',
                    query:{
                        id:news.id,
                        title:news.title,
                        content:news.content
                    }
                }">{{news.title}}</RouterLink>
                
            </li>
        </ul>
        <RouterLink to="/home">首页</RouterLink>  
       
        <RouterLink to="/about">关于</RouterLink>  
        <br>  
        内容:  
        <div>  
            <RouterView></RouterView>  
        </div>  
    </div>  
</template>  
  
<script lang="ts" setup name="App">  
import { reactive } from 'vue';
import { RouterView, RouterLink } from 'vue-router';  

const newsList = reactive([  
  { id: 'asdad01', title: '标题1', content: '内容1' },  
  { id: 'asdad02', title: '标题2', content: '内容2' },  
  { id: 'asdad03', title: '标题3', content: '内容3' }  
]);

</script>  
  
<style scoped>  
.app {  
    background-color: skyblue;  
    box-shadow: 0 0 10px;  
    border-radius: 10px;  
    padding: 20px;  
}  
</style>

pages/Detail.vue:

<template>
    <div class="detail">
      <h2>Detail</h2>
      <ul>
        <li>编号:{{route.query.id}}</li>
        <li>标题:{{route.query.title}}</li>
        <li>内容:{{route.query.content}}</li>
      </ul>
    </div>
</template>
  
<script lang="ts" setup name="Detail">
import { useRoute } from 'vue-router';
let route = useRoute()
</script>
<style scoped>

</style>

可以将pages/Detail.vue这么改:

<template>
    <div class="detail">
      <h2>Detail</h2>
      <ul>
        <li>编号:{{query.id}}</li>
        <li>标题:{{query.title}}</li>
        <li>内容:{{query.content}}</li>
      </ul>
    </div>
</template>
  
<script lang="ts" setup name="Detail">
import { toRefs } from 'vue';
import { useRoute } from 'vue-router';
let route = useRoute()
let {query} = toRefs(route)
</script>
<style scoped>

</style>

16.7路由params参数(传参)

注意:

(1)传递params参数时,使用to的对象写法,必须使用name配置项,不能用path

(2)传递params参数时,需要提前在规则中占位

App.vue:

<template>  
    <div class="app">  
        <h1>navigate</h1>  
        <ul>
            <li v-for="news in newsList" :key="news.id">
                <!-- 第一种方法 -->
                <!-- <RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`" >{{news.title}}</RouterLink>   -->
                
                <!-- 第二种写法 -->
                <RouterLink :to="{
                    name:'xiang',
                    params:{
                        id:news.id,
                        title:news.title,
                        content:news.content
                    }
                }">{{news.title}}</RouterLink>

            </li>
        </ul>
        <RouterLink to="/home">首页</RouterLink>  
       
        <RouterLink to="/about">关于</RouterLink>  
        <br>  
        内容:  
        <div>  
            <RouterView></RouterView>  
        </div>  
    </div>  
</template>  
  
<script lang="ts" setup name="App">  
import { reactive } from 'vue';
import { RouterView, RouterLink } from 'vue-router';  

const newsList = reactive([  
  { id: 'asdad01', title: '标题1', content: '内容1' },  
  { id: 'asdad02', title: '标题2', content: '内容2' },  
  { id: 'asdad03', title: '标题3', content: '内容3' }  
]);

</script>  
  
<style scoped>  
.app {  
    background-color: skyblue;  
    box-shadow: 0 0 10px;  
    border-radius: 10px;  
    padding: 20px;  
}  
</style>

router/index.ts:

注意:params传参需要对路径名前加上:,表示所传递的参数,不然会当作子路径

//引入createRouter
import { createRouter, createWebHistory } from "vue-router";

//引入组件
import Home from "@/pages/Home.vue";
import About from "@/pages/About.vue";
import News from "@/pages/News.vue";
import Detail from "@/pages/Detail.vue";

//创建路由器
const router = createRouter({
    history:createWebHistory(), //路由器编译规则
    routes:[{
        name:'zhuye',
        path:'/home',
        component:Home
    },{
        name:'guanyu',
        path:'/about',
        component:About
    },{
        name:'xinwen',
        path:'/news',
        component:News,
        children:[{
            name:'xiang',
            path:'detail/:id/:title/:content',
            component:Detail
        }]
    }]
})

export default router

pages/Detail.vue:

<template>
    <div class="detail">
      <h2>Detail</h2>
      <ul>
        <li>编号:{{route.params.id}}</li>
        <li>标题:{{route.params.title}}</li>
        <li>内容:{{route.params.content}}</li>
      </ul>
    </div>
</template>
  
<script lang="ts" setup name="Detail">
import { toRefs } from 'vue';
import { useRoute } from 'vue-router';
let route = useRoute()

</script>
<style scoped>

</style>

16.8路由props配置

router/index.ts:

一般使用第一种方法和第二种方法,第二种方法使用params方法传递参数所以return route.params,如果是使用query方法传递参数,则return route.query

//引入createRouter
import { createRouter, createWebHistory } from "vue-router";

//引入组件
import Home from "@/pages/Home.vue";
import About from "@/pages/About.vue";
import News from "@/pages/News.vue";
import Detail from "@/pages/Detail.vue";

//创建路由器
const router = createRouter({
    history:createWebHistory(), //路由器编译规则
    routes:[{
        name:'zhuye',
        path:'/home',
        component:Home
    },{
        name:'guanyu',
        path:'/about',
        component:About
    },{
        name:'xinwen',
        path:'/news',
        component:News,
        children:[{
            name:'xiang',
            path:'detail/:id/:title/:content',
            component:Detail,
            // 第一种写法:将路由收到的所有params参数作为props传给路由组件
            // props:true

            //第二种写法:函数写法,可以自己决定将什么作为props给路由组件
            props(route){
                return route.params
            }

            //第三种写法:对象写法,可以自己决定将什么作为props给路由组件
            /* props:{
                id:10,
                title:'今天去哪里?',
                content:'当然是北京'
            } */
        }]
    }]
})

export default router

pages/Detail.vue:

<template>
    <div class="detail">
      <h2>Detail</h2>
      <ul>
        <li>编号:{{id}}</li>
        <li>标题:{{title}}</li>
        <li>内容:{{content}}</li>
      </ul>
    </div>
</template>
  
<script lang="ts" setup name="Detail">
defineProps(['id','title','content'])

</script>
<style scoped>

</style>

16.9路由replace属性

给RouterLink加上replace后就无法返回上一页的内容

16.9路由导航,路由跳转

有点类似<RouterLink/>

不带参数跳转:

pages/Home.vue:

<template>
    <div class="home">
        <h2>Home</h2>
    </div>  
</template>

<script lang="ts" setup name="Home">
import { onMounted } from "vue";
import {useRouter} from 'vue-router'

const router = useRouter()

onMounted(()=>{
    setTimeout(()=>{
        //跳转路由
        router.push('/news')
    },3000)
})

</script>

<style scoped>

</style>

带参数跳转:

App.vue:

<template>  
    <div class="app">  
        <h1>navigate</h1>  
        <ul>
            <li v-for="news in newsList" :key="news.id">
                <button @click="showNewsDetail(news)">查看新闻</button>
                <!-- 第一种方法 -->
                <!-- <RouterLink :to="`/news/detail/${news.id}/${news.title}/${news.content}`" >{{news.title}}</RouterLink>   -->
                
                <!-- 第二种写法 -->
                <RouterLink :to="{
                    name:'xiang',
                    params:{
                        id:news.id,
                        title:news.title,
                        content:news.content
                    }
                }">{{news.title}}</RouterLink>

               
            </li>
        </ul>
        <RouterLink to="/home">首页</RouterLink>  
       
        <RouterLink to="/about">关于</RouterLink>  
        <br>  
        内容:  
        <div>  
            <RouterView></RouterView>  
        </div>  
    </div>  
</template>  
  
<script lang="ts" setup name="App">  
import { reactive } from 'vue';
import { RouterView, RouterLink, useRouter } from 'vue-router';  

const newsList = reactive([  
  { id: 'asdad01', title: '标题1', content: '内容1' },  
  { id: 'asdad02', title: '标题2', content: '内容2' },  
  { id: 'asdad03', title: '标题3', content: '内容3' }  
]);

const router = useRouter()

//用于限制类型
interface NewsInter{
    id:string,
    title:string,
    content:string
}

function showNewsDetail(news:NewsInter){
    router.push({
        name:'xiang',
        params:{
            id:news.id,
            title:news.title,
            content:news.content
        }}
    )
}


</script>  
  
<style scoped>  
.app {  
    background-color: skyblue;  
    box-shadow: 0 0 10px;  
    border-radius: 10px;  
    padding: 20px;  
}  
</style>

如果将router.push改为router.replace,就表示跳转到指定的路由后就无法返回了,将上面的函数的代码改为replace如下所示:

function showNewsDetail(news:NewsInter){
    router.replace({
        name:'xiang',
        params:{
            id:news.id,
            title:news.title,
            content:news.content
        }}
    )
}

16.10路由重定向

重定向(Redirect)是指通过各种方法将各种网络请求重新定向到其它位置的过程

17.Pinia

考虑多个组件共享数据

安装axios

npm i axios

安装nanoid(效果类似uuid)

npm i nanoid

17.1Pinia配置

安装pinia

npm i pinia

配置

mian.ts:

import { createApp } from "vue";

import App from './App.vue'
import { createPinia } from "pinia";

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.mount('#app')

下面是一个效果,

17.2未使用Pinia前的方式

components/Count.vue:

<template>
    <div class="count">
      <h1>当前求和:{{sum}}</h1>
      <select v-model.number="step">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      <button @click="add">加</button>
      <button @click="minus">减</button>
    </div>
</template>

<script lang="ts" setup name="Count">
import { ref } from 'vue';


let sum = ref(0)
let step = ref(1)

function add(){
    sum.value += step.value
}

function minus(){
    sum.value -= step.value
}

</script>

<style scoped>
.count{
    background-color: skyblue;
    padding: 10px;
    border-radius:10px;
    box-shadow:0 0 10px;
}

select,button{
    margin: 0 5px;
    height: 25px;
}

</style>

components/LoveTalk.vue:

<template>
    <div class="talk">
        <button @click="getLoveTalk">获取一句话</button>
        <ul>
            <li v-for="talk in talkList" :key="talk.id">{{talk.title}}</li>
        </ul>
    </div>
</template>

<script lang="ts" setup name="LoveTalk">
import axios from 'axios';
import { reactive } from 'vue';
import {nanoid} from 'nanoid'

let talkList = reactive([
    {id:'sadaohd01',title:'早上好!'},
    {id:'sadaohd02',title:'中午好!'},
    {id:'sadaohd03',title:'晚上好!'}
    ])

async function getLoveTalk(){
    let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
    let obj = {id:nanoid(),title}
    talkList.unshift(obj)
}

</script>

<style scoped>
.talk{
    background-color: skyblue;
    padding: 10px;
    border-radius:10px;
    box-shadow:0 0 10px;
}
</style>

App.vue:

<template>
  <div>
    <h1>App组件</h1>
    <Count/>
    <br>
    <LoveTalk/>
  </div>
</template>

<script lang="ts" setup name="App">
import Count from './components/Count.vue';
import LoveTalk from './components/LoveTalk.vue';
</script>

<style scoped> 

</style>

17.3使用Pinia后的方式

创建文件夹store,然后在该文件夹中创建count.ts

store/count.ts:

import {defineStore} from 'pinia'

export const useCountStore = defineStore('count',{
    actions:{
        increment(value: number){
            console.log('increment被调用了',value)
            if(this.sum<10){
                this.sum += value
            }
        }    
    },
    //真正存储数据的地方
    state(){
        return{
            sum:6,
            school:'guigu',
            address:'beijing'
        }
    }
})

components/Count.vue:

<template>
    <div class="count">
      <h1>当前求和:{{countStore.sum}}</h1>
      <h1>学校:{{countStore.school}};地址:{{countStore.address}}</h1>
      <select v-model.number="step">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      <button @click="add">加</button>
      <button @click="minus">减</button>
    </div>
</template>

<script lang="ts" setup name="Count">
import { ref } from 'vue';
import { useCountStore } from '@/store/count';

const countStore = useCountStore()

//获取state数据的两种方式:
// console.log(countStore.sum)
// console.log(countStore.$state.sum)


let step = ref(1)

function add(){
  //第一种方式修改
 /*  countStore.sum += 1
  countStore.school = '硅谷'
  countStore.address = '北京' */

  //第二种方式批量修改
/*   countStore.$patch({
    sum:888,
    school:'硅谷',
    address:'北京'
  }) */

  //第三种方式,调用store里的函数
  countStore.increment(step.value)

}

function minus(){

}

</script>

<style scoped>
.count{
    background-color: skyblue;
    padding: 10px;
    border-radius:10px;
    box-shadow:0 0 10px;
}

select,button{
    margin: 0 5px;
    height: 25px;
}

</style>

但是上面还没实现方法和调用storeToRefs(),实现后的完整版如下:

components/Count.vue:

<template>
    <div class="count">
      <h1>当前求和:{{sum}}</h1>
      <h1>学校:{{school}};地址:{{address}}</h1>
      <select v-model.number="step">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      <button @click="add">加</button>
      <button @click="minus">减</button>
    </div>
</template>

<script lang="ts" setup name="Count">
import { ref } from 'vue';
import { useCountStore } from '@/store/count';
import { storeToRefs } from 'pinia';

const countStore = useCountStore()
//storeToRefs只会关注store中数据,不会对方法进行ref包裹
const {sum,school,address} = storeToRefs(countStore)


//获取state数据的两种方式:
// console.log(countStore.sum)
// console.log(countStore.$state.sum)


let step = ref(1)

function add(){
  //第一种方式修改
 /*  countStore.sum += 1
  countStore.school = '硅谷'
  countStore.address = '北京' */

  //第二种方式批量修改
/*   countStore.$patch({
    sum:888,
    school:'硅谷',
    address:'北京'
  }) */

  //第三种方式,调用store里的函数
  countStore.increment(step.value)

}

function minus(){
  countStore.sum -= step.value
}

</script>

<style scoped>
.count{
    background-color: skyblue;
    padding: 10px;
    border-radius:10px;
    box-shadow:0 0 10px;
}

select,button{
    margin: 0 5px;
    height: 25px;
}

</style>

store/count.ts:

import {defineStore} from 'pinia'

export const useCountStore = defineStore('count',{
    actions:{
        increment(value: number){
            console.log('increment被调用了',value)
            if(this.sum<10){
                this.sum += value
            }
        }    
    },
    //真正存储数据的地方
    state(){
        return{
            sum:6,
            school:'guigu',
            address:'beijing'
        }
    }
})

components/LoveTalk:

<template>
    <div class="talk">
        <button @click="getLoveTalk">获取一句话</button>
        <ul>
            <li v-for="talk in talkStore.talkList" :key="talk.id">{{talk.title}}</li>
        </ul>
    </div>
</template>

<script lang="ts" setup name="LoveTalk">

import { useTalkStore } from '@/store/loveTalk';
import { storeToRefs } from 'pinia';
const talkStore = useTalkStore()
const {talkList} = storeToRefs(talkStore)

async function getLoveTalk(){
    talkStore.getATalk()
}

</script>

<style scoped>
.talk{
    background-color: skyblue;
    padding: 10px;
    border-radius:10px;
    box-shadow:0 0 10px;
}
</style>

store/loveTalk.ts:

import {defineStore} from 'pinia'
import axios from 'axios';
import { reactive } from 'vue';
import {nanoid} from 'nanoid'

export const useTalkStore = defineStore('talk',{
    actions:{
        async getATalk(){
            let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
            let obj = {id:nanoid(),title}
            this.talkList.unshift(obj)
        }
    },
    //真正存储数据的地方
    state(){
        return{
            talkList:[
                {id:'sadaohd01',title:'早上好!'},
                {id:'sadaohd02',title:'中午好!'},
                {id:'sadaohd03',title:'晚上好!'}
            ]
        }
    }
})

17.4 getters

getters是 Vuex store 中的计算属性。它们类似于 Vue 组件中的计算属性,但是用于从 store 中的状态派生值

store/count.ts:

import {defineStore} from 'pinia'

export const useCountStore = defineStore('count',{
    actions:{
        increment(value: number){
            console.log('increment被调用了',value)
            if(this.sum<10){
                this.sum += value
            }
        }    
    },
    //真正存储数据的地方
    state(){
        return{
            sum:6,
            school:'guigu',
            address:'beijing'
        }
    },
    getters:{
        bigSum:state => state.sum * 10,
        upperSchool():string{
            return this.school.toUpperCase()
        }
    }
})

components/Count.vue:

<template>
    <div class="count">
      <h1>当前求和:{{sum}},放大:{{bigSum}}</h1>
      <h1>学校:{{school}};地址:{{address}},大写:{{upperSchool}}</h1>
      <select v-model.number="step">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      <button @click="add">加</button>
      <button @click="minus">减</button>
    </div>
</template>

<script lang="ts" setup name="Count">
import { ref } from 'vue';
import { useCountStore } from '@/store/count';
import { storeToRefs } from 'pinia';

const countStore = useCountStore()
//storeToRefs只会关注store中数据,不会对方法进行ref包裹
const {sum,school,address,bigSum,upperSchool} = storeToRefs(countStore)


//获取state数据的两种方式:
// console.log(countStore.sum)
// console.log(countStore.$state.sum)


let step = ref(1)

function add(){
  //第一种方式修改
 /*  countStore.sum += 1
  countStore.school = '硅谷'
  countStore.address = '北京' */

  //第二种方式批量修改
/*   countStore.$patch({
    sum:888,
    school:'硅谷',
    address:'北京'
  }) */

  //第三种方式,调用store里的函数
  countStore.increment(step.value)

}

function minus(){
  countStore.sum -= step.value
}

</script>

<style scoped>
.count{
    background-color: skyblue;
    padding: 10px;
    border-radius:10px;
    box-shadow:0 0 10px;
}

select,button{
    margin: 0 5px;
    height: 25px;
}

</style>

17.5 $subscribe的使用

订阅,监视vuex文件里的修改

components/LoveTalk.vue:

<template>
    <div class="talk">
        <button @click="getLoveTalk">获取一句话</button>
        <ul>
            <li v-for="talk in talkStore.talkList" :key="talk.id">{{talk.title}}</li>
        </ul>
    </div>
</template>

<script lang="ts" setup name="LoveTalk">

import { useTalkStore } from '@/store/loveTalk';
import { storeToRefs } from 'pinia';
const talkStore = useTalkStore()
const {talkList} = storeToRefs(talkStore)

//$subscribe订阅
talkStore.$subscribe((mutate,state)=>{
    console.log('talkStore里面保存的数据发生了变化',mutate,state)
    localStorage.setItem('talkList',JSON.stringify(state.talkList))
})



async function getLoveTalk(){
    talkStore.getATalk()
}

</script>

<style scoped>
.talk{
    background-color: skyblue;
    padding: 10px;
    border-radius:10px;
    box-shadow:0 0 10px;
}
</style>

store/loveTalk.ts:

import {defineStore} from 'pinia'
import axios from 'axios';
import { reactive } from 'vue';
import {nanoid} from 'nanoid'

export const useTalkStore = defineStore('talk',{
    actions:{
        async getATalk(){
            let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
            let obj = {id:nanoid(),title}
            this.talkList.unshift(obj)
        }
    },
    //真正存储数据的地方
    state(){
        return{

            talkList:JSON.parse(localStorage.getItem('talkList') as string) || []
        }
    }
})

17.6store里的组合式写法

store/loveTalk.ts:

import {defineStore} from 'pinia'
import axios from 'axios';
import { reactive } from 'vue';
import {nanoid} from 'nanoid'

/* export const useTalkStore = defineStore('talk',{
    actions:{
        async getATalk(){
            let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
            let obj = {id:nanoid(),title}
            this.talkList.unshift(obj)
        }
    },
    //真正存储数据的地方
    state(){
        return{
            talkList:JSON.parse(localStorage.getItem('talkList') as string) || []
        }
    }
}) */


//组合式
export const useTalkStore = defineStore('talk',()=>{

    //talkList就是state
    const talkList = reactive(
        JSON.parse(localStorage.getItem('talkList') as string) || []
    )

    //getATalk函数相当于action
    async function getATalk(){
        let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
        let obj = {id:nanoid(),title}
        talkList.unshift(obj)
    }

    return {talkList,getATalk}
})

18.组件通信

18.1props

可以进行父传子和子传父;

父传子,属性值是非函数

子传父,属性值是函数

18.2自定义事件

18.3mitt

安装mitt

npm i mitt

然后创建utils文件夹,里存放emitter.ts文件

18.4 v-model

18.5 $attrs

实现 祖传孙 和 孙传祖

18.6 refs和parent

$refs:父访问子的属性、方法、计算属性

$parent:子访问父的属性、方法、计算属性

18.7 provide 和 inject

在之前祖传孙需要使用$attrs,且打扰到了子组件

那么使用provide直接就可以传后代,通过inject获取

相关推荐
寒雒44 分钟前
【Python】实战:实现GUI登录界面
开发语言·前端·python
独上归州1 小时前
Vue与React的Suspense组件对比
前端·vue.js·react.js·suspense
战族狼魂1 小时前
html+js实现图片的放大缩小等比缩放翻转,自动播放切换,顺逆时针旋转
javascript·css·html
Komorebi⁼1 小时前
Vue核心特性解析(内含实践项目:设置购物车)
前端·javascript·vue.js·html·html5
明月清风徐徐1 小时前
Vue实训---0-完成Vue开发环境的搭建
前端·javascript·vue.js
SameX1 小时前
HarmonyOS Next 企业数据备份与恢复策略
前端·harmonyos
SameX1 小时前
HarmonyOS Next 企业数据传输安全策略
前端·harmonyos
daopuyun1 小时前
LoadRunner小贴士|开发Web-HTTP/HTML协议HTML5相关视频应用测试脚本的方法
前端·http·html
李先静1 小时前
AWTK-WEB 快速入门(1) - C 语言应用程序
c语言·开发语言·前端
MR·Feng1 小时前
使用Electron将vue2项目打包为桌面exe安装包
前端·javascript·electron