Vue与Uni-APP教程

本文最后更新于:2025年4月29日 下午

ES6语法

Promise语法

回调函数

举例就以uniapp里面的网络请求uni.request()为例了,如果是微信小程序wx.request()也是一样的,还有jQuery的ajax(),这些都是异步请求,通过success回调函数获取数据的,而axios网络请求已经封装了promise了。

例如我们先要获取文章分类列表的id,再得到数据后,通过id获取该分类下的所有文章,再通过文章的id获取文章下的评论,最终获取该文章的所有评论:

getData() {
//获取分类列表id
uni.request({
    url: "https://ku.qingnian8.com/dataApi/news/navlist.php",
    success: res => {
        let id = res.data[0].id
        // 根据分类id获取该分类下的所有文章
        uni.request({
            url: "https://ku.qingnian8.com/dataApi/news/newslist.php",
            data: {
                cid: id
            },
            success: res2 => {
                //获取到一篇文章的id,根据文章id找到该文章下的评论
                let id = res2.data[0].id;
                uni.request({
                    url: "https://ku.qingnian8.com/dataApi/news/comment.php",
                    data: {
                        aid: id
                    },
                    success: res3 => {
                        //找到该文章下所有的评论
                        console.log(res3)
                    }
                })
            }
        })

    }
})

上面的代码,有多层嵌套,出现多个success回调,按照这样写的话,可维护、可读性很差,下面开始改造:

onLoad() {
    this.getNav(res => {
        console.log(res);
    });
},
methods: {
    getNav(callback) {
        uni.request({
            url: "https://ku.qingnian8.com/dataApi/news/navlist.php",
            success: res => {
                callback(res)
            }
        })
    },
}

我们在getNav()中可以传一个回调函数callback,将请求的结果调用回调callback(res),下面全部改写回调函数:

onLoad() {
	// 调用获取导航列表后,将结果调用其他函数时,额外传入一个回调函数
    this.getNav(res => {
        let id = res.data[0].id;
        this.getList(id, res => {
            let id = res.data[0].id;
            this.getComment(id, res => {
                console.log(res);
            })
        });
    });

    },
    methods: {
    // 获取导航列表
    getNav(callback) {
        uni.request({
            url: "https://ku.qingnian8.com/dataApi/news/navlist.php",
            success: res => {
                callback(res)
            }
        })
    },
    // 获取新闻列表
    getList(id, callback) {
        uni.request({
            url: "https://ku.qingnian8.com/dataApi/news/newslist.php",
            data: {
                cid: 51
            },
            success: res => {
                callback(res);
            }
        })
    },
    // 获取当前新闻的评论
    getComment(id, callback) {
        uni.request({
            url: "https://ku.qingnian8.com/dataApi/news/comment.php",
            data: {
                aid: id
            },
            success: res => {
                callback(res);
            }
        })
    },
}

这样的话仔细看来,并没有解决回调地狱的问题,还是回调里面嵌套回调,只是把函数独立出来了,看着清晰条理了一些而已,但是维护难度还是有的,所以随着ES6的普及,这种方案逐渐边缘化,取而代之的就是promise方案了。

什么是promise

promise是解决异步的方法,本质上是一个构造函数,可以用它实例化一个对象。对象身上有resolve、reject、all,原型上有then、catch方法。promise对象有三种状态:pending(初识状态/进行中)、resolved或fulfilled(成功)、rejected(失败)

  1. pending。它的意思是 “待定的,将发生的”,相当于是一个初始状态。创建Promise对象时,且没有调用resolve或者是reject方法,相当于是初始状态。这个初始状态会随着你调用resolve,或者是reject函数而切换到另一种状态。
  2. resolved。表示解决了,就是说这个承诺实现了。 要实现从pending到resolved的转变,需要在 创建Promise对象时,在函数体中调用了resolve方法
  3. rejected。拒绝,失败。表示这个承诺没有做到,失败了。要实现从pending到rejected的转换,只需要在创建Promise对象时,调用reject函数。

通过代码打印promise的方法:

console.dir(Promise)

image-20231107145707785


使用Promise进行改造:

onLoad() {
    this.getNav().then(res => {
        console.log(res);
    })
},
methods: {
    // 获取导航列表
    getNav() {
        return new Promise((resolve, reject) => { // 返回promise对象
            uni.request({
                url: "https://ku.qingnian8.com/dataApi/news/navlist.php",
                success: res => {
                    resolve(res);
                },
                fail: (err) => {
                    reject(err);
                }
            })
        })
    },
}

最后的调用变为了.then的链式调用:

//promise链式调用
this.getNav().then(res=>{
    let id=res.data[0].id;
    return this.getArticle(id);
}).then(res=>{
    let id=res.data[0].id;
    return this.getComment(id);
}).then(res=>{
    console.log(res);
}).catch(err => {
    console.log(err);
})

如果想要等待所有请求全部加载完成再响应,可以使用:

onLoad() {
    let p1 = this.getNav();
    let p2 = this.getArticle();
    let p3 = this.getComment();
    Promise.all([p1, p2, p3]).then(res => {
    	console.log(res);
    })
}

await/async异步处理同步化

这两个命令是成对出现的,如果使用await没有在函数中使用async命令,那就会报错,如果直接使用async没有使用await不会报错,只是返回的函数是个promise,可以,但是没有意义,所以这两个一起使用才会发挥出它们本身重要的作用。

这两个命令怎么用那,还是通过上面的案例,来该着一下then的链式调用代码。

async onLoad() {
    let id, res;
    res = await this.getNav();
    id = res.data[0].id;
    res = await this.getArticle(id);
    id = res.data[0].id;
    res = await this.getComment(id);
    console.log(res)
},

以上代码就是最终的改造版了,可以看到onload是函数,这个函数必须有async命令,在调用函数的部分,前面都加了一个await,这个命令的意思就是等这一行的异步方法执行成功后,将返回的值赋值给res变量,然后才能再走下一行代码,这就是将原来的异步编程改为了同步编程,这就是标题提到的“异步处理,同步化”。


总结:如果涉及到网络请求没有依赖关系的话,异步请求是效率最高的,但是下一个的方法依赖于上一个网络请求的结果,那么久必须使用await命令,将异步结果等待返回之后再执行后面的代码。

Vue3

项目初始化

搭建脚手架

npm create vue@latest

然后根据自己需要进行脚手架的设置:

D:\我的文件\Desktop>npm create vue@latest

Vue.js - The Progressive JavaScript Framework

√ 请输入项目名称: ... vue3
√ 是否使用 TypeScript 语法? ... 否 / 是
√ 是否启用 JSX 支持? ... 否 / 是
√ 是否引入 Vue Router 进行单页面应用开发? ... 否 / 是
√ 是否引入 Pinia 用于状态管理? ... 否 / 是
√ 是否引入 Vitest 用于单元测试? ... 否 / 是
√ 是否要引入一款端到端(End to End)测试工具? » 不需要
√ 是否引入 ESLint 用于代码质量检测? ... 否 / 是

正在构建项目 D:\我的文件\Desktop\vue3...

项目构建完成,可执行以下命令:

  cd vue3
  npm install
  npm run dev

项目目录

.vscode					--- VScode工具的配置文件
node_modules			--- Vue项目的运行依赖文件夹
public					--- 资源文件夹(浏览器图标)
src						--- 源码文件夹
.gitignore				--- git忽略文件
index.html				--- rHTML文件
package.json			--- 信息描述文件
README.md				--- 注释文件
vite.config.js			--- Vue配置文件

插件

在官方推荐的编译器VSode中,安装Volar插件,用来支持Vue语法

image-20240125183202252

还可以在这个插件中设置,在使用Ref数据使,自动添加**.value**

VScode的设置中,选择:扩展 -> Volar ,勾选==自动输入value==

image-20240125183851286

vue-router

引入vue-router

安装:

npm install vue-router

使用:

src目录下,新建文件夹router,在router文件夹中新建文件index.ts/index.js,写入一下内容

// 创建一个路由器,暴露出去
import { createRouter, createWebHistory } from 'vue-router'
// 测试路由
import Home from '@/pages/Home.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL), // 路由器的工作模式
  routes: [
    {
      path: '/home',
      component: Home
    }
  ]
})

export default router

main.ts/main.js中引入:

import router from './router'
app.use(router)

在组件中可以使用这种方式:RouterLink为路由跳转,RouterView为不同路由的显示

<!-- 导航区 -->
<div class="navigate">
    <RouterLink to="/home" active-class="active">首页</RouterLink>
    <RouterLink to="/news" active-class="active">新闻</RouterLink>
    <RouterLink to="/about" active-class="active">关于</RouterLink>
</div>
<!-- 展示区 -->
<div class="main-content">
    <RouterView />
</div>

路由器工作模式

  1. history模式

    优点:URL更加美观,不带有#,更接近传统的网站URL

    缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404模式。

    const router = createRouter({
      history: createWebHistory(), // history模式
      /******/
    })
  2. hash模式

    优点:兼容性更好,因为不需要服务端处理路径。

    缺点:URL带有#不太美观,且在SEO优化方面相对较差

    const router = createRouter({
      history: createWebHashHistory(), // hash模式
      /******/
    })

路由_to的两种写法

  1. 路径字符串方式

    <RouterLink to="/home" active-class="active">首页</RouterLink>
  2. 路径对象方式

    <RouterLink :to="{path: '/homne'}" active-class="active">首页</RouterLink>

路由传参

路由传参可通过 queryparam

query参数:

直接通过路由传参:

<router-link :to="`/news/detail?id=${news.id}&title=${news.title}&content=${news.content}`">
    {{ news.title }}
</router-link>

在跳转到的路由组件中,使用route获取

<template>
  <ul class="news-list">
    <li>编号:{{ route.query.id }}</li>
    <li>标题:{{ route.query.title }}</li>
    <li>内容:{{ route.query.content }}</li>
  </ul>
</template>

<script setup lang="ts" name="Detail">
import {useRoute} from "vue-router";
const route = useRoute();
</script>

优化写法:

路由传参:将to使用对象写法,query中写参数

<router-link
  :to="{
          path: `/news/detail`,
          query: {
            id: news.id,
            title: news.title,
            content: news.content
          }
        }"
        >
    {{ news.title }}
</router-link>

在跳转到的路由组件中,使用route获取

<template>
  <ul class="news-list">
    <li>编号:{{ query.id }}</li>
    <li>标题:{{ query.title }}</li>
    <li>内容:{{ query.content }}</li>
  </ul>
</template>

<script setup lang="ts" name="Detail">
import { toRefs } from "vue";
import {useRoute} from "vue-router";
const route = useRoute();
const {query} = toRefs(route);
</script>

param参数:

首先在路由配置页中,预留参数位:在子路由的path中写入detail/:id/:title/:content

routes: [
    {
      path: '/news',
      component: News,
      children: [
          {
              path: 'detail/:id/:title/:content',
              component: Detail
          }
      ]
    },
]

路由跳转:

<router-link :to="`/news/detail/${news.id}/${news.title}/${news.content}`">
    {{ news.title }}
</router-link>

路由接收:

<template>
  <ul class="news-list">
    <li>编号:{{ route.params.id }}</li>
    <li>标题:{{ route.params.title }}</li>
    <li>内容:{{ route.params.content }}</li>
  </ul>
</template>

<script setup lang="ts" name="Detail">
import {useRoute} from "vue-router";
const route = useRoute();
</script>

优化写法

在路由配置页中,为路由设置名称

children: [
    {
        name: 'detail',
        path: 'detail/:id/:title/:content',
        component: Detail
    }
]

路由跳转: 且params中不可以传对象数组

<RouterLink :to="{
  name: 'detail',
  params: {
    id: news.id,
    title: news.title,
    content: news.content
  }
}">
  {{ news.title}}
</RouterLink>

如果有些参数不是必要的,可以在路由配置页中,在该参数后面加一个?

children: [
    {
        name: 'detail',
        path: 'detail/:id/:title/:content?',
        component: Detail
    }
]

props配置

为了简化在路由接收时,从route中取值的繁琐步骤,可在路由配置页,添加:props: true

children: [
    {
        name: 'detail',
        path: 'detail/:id/:title/:content',
        component: Detail,
        props: true
    }
]

此时,路由组件<Detail />相当于变为了 <Detail id=? title=? content=? />,将接收到的参数转化为props

在路由接收页面就可以直接通过 defineProps 拿到参数:

<template>
  <ul class="news-list">
    <li>编号:{{ id }}</li>
    <li>标题:{{ title }}</li>
    <li>内容:{{ content }}</li>
  </ul>
</template>

<script setup lang="ts" name="Detail">
defineProps(['id', 'title', 'content'])
</script>

Props有三种写法,选择合适的写法

{	
	// 第一种写法:将路由收到的所有params参数作为props传给路由组件
	props: true,
	// 第二种写法:第二种写法:函数写法,可以自己决定将什么作为props传给路由组件
    props(route){
    	return route.query
    }
	// 第三种写法:对象写法,可以自己决定将什么作为props传给路由组件
    props: {
        id: 1,
        title: '新闻标题',
        content: '新闻内容'
    }
}

replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式。

  2. 浏览器的历史记录有两种写入方式:分别为pushreplace

    • push是追加历史记录(默认值)。可在浏览器点击退后,返回之前路由
    • replace是替换当前记录。无法回退之前路由,可用于登录页跳转
  3. 开启replace模式:加入replace

    <Router-link replace to="/home">首页</Router-link>

编程式路由导航

编程式路由导航,为了脱离<RouterLink>实现路由跳转,我们只需要在组件种引入useRouter

import { useRouter } from "vue-router";
const router = useRouter()
// 此处 push 用法和路由 to 的用法一样,有两种,字符串和对象写法
router.push({
    name: 'detail',
    params: {
      id: news.id,
      title: news.title,
      content: news.content
    }
})

编程式路由可写在函数中,当满足条件进行跳转,例如登录跳转。

重定向

使用redirect可重定向到指定路由

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL), // 路由器的工作模式
    routes: [
        {
            path: '/home',
            component: Home
        },
        {
            path: '/',
            redirect: '/home'
        }
    ]
})

Pinia

Vue的状态管理库

搭建pinia环境

安装pinia

npm i pinia

main.ts/main.js中引入pinia:

import { createApp } from 'vue'
import App from './App.vue'
// 第一步:引入pinia
import { createPinia } from 'pinia'

const app = createApp(App)
// 第二步:创建pinia
const pinia = createPinia()
// 第三步:挂载pinia
app.use(pinia)
app.mount('#app')

存储+读取数据

假如我们有一个组件叫Count.vue,我们需要存储一个数据为:sum,我们可以在项目路径中@/src/store中创建count.ts,写入

defineStore需要一个id对象,id我们使用组件名

import {defineStore} from "pinia";

export const useCountStore = defineStore('count', {
    // 真正存储数据的地方
    state() {
        return {
            sum: 0
        }
    }
})

在组件Count.vue中使用时,我们先引入count.ts,在通过count.ts中暴露出的useCountStore.sum获取数据

import {useCountStore} from "@/store/count"
const countStore = useCountStore()

// 取数据
countStore.sum

更方便的读取数据 storeToRefs,使用storeToRefs,只会关注store中的数据,不会对方法进行ref包裹

import {useCountStore} from "@/store/count"
import {storeToRefs} from "pinia";

const countStore = useCountStore()
const {sum, school, address} = storeToRefs(countStore)

修改数据(3种方式)

第一种修改方式,直接操作数据

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

第二种修改方式,批量变更,使用$patch可以进行批量数据修改,当批量数据变更时使用这种

function add() {
  countStore.$patch({
    sum: countStore.sum += n.value,
    school: 'bjdx'
  })
}

第三种修改方式,在count.ts中编写修改数据的行为,在count.ts中加入actions,在actions中编写修改行为,可复用

import {defineStore} from "pinia";

export const useCountStore = defineStore('count', {
    // actions里面放置的是一个一个方法,用于响应组件的“动作”
    actions: {
        increment(value: number) {
            this.sum += value // 此处无法直接获取sum,需使用this.sum获取
        }
    },
    // 真正存储数据的地方
    state() {
        return {
            sum: 0,
            school: '北京大学',
            address: '北京市朝阳区芍药居'
        }
    }
})

count.ts组件中调用: 直接使用 countStore.方法

function add() {
  countStore.increment(n.value)
}

getters使用

count.ts中,可以加入getters

import {defineStore} from "pinia";

export const useCountStore = defineStore('count', {
    /******/,
    getters: {
        bigSum: state => state.sum * 10,
        upperSchool(): string {
            return this.school.toUpperCase()
        }
    }
})

使用时和state中的数据一样,推荐使用方法:

import {useCountStore} from "@/store/count"
import {storeToRefs} from "pinia";

const countStore = useCountStore()
const {sum, school, address, bigSum, upperSchool} = storeToRefs(countStore)

$subscribe使用

$subscribe可以监视store中的数据变化,例如下面代码

import {useTalkStore} from "@/store/loveTalk";

const talkStore = useTalkStore()
talkStore.$subscribe((mutation, state) => {
  console.log(mutation)
  console.log(state)
})

mutation: 提供关于状态更改操作的详细信息

state: 取到存储的当前最新的完整状态

Store的组合式

store还可以用组合式去书写,例如loveTalk.ts

import {reactive} from "vue";

export const useTalkStore = defineStore('talk', () => {
    // talkList 就是 state
    const talkList = reactive(JSON.parse(localStorage.getItem('talkList') as string) || [])

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

组件通信

【props】

概述:props是使用频率最高的一种通信方式,常用于: <->

  • 父传子:属性值是非函数
  • 子传父:属性值是函数

首先我们创建一个父组件:Father.vue

<template>
  <div class="father">
    <h3>父组件</h3>
		<h4>汽车:{{ car }}</h4>
		<h4 v-show="toy">子给的玩具:{{ toy }}</h4>
		<Child :car="car" :sendToy="getToy"/>
  </div>
</template>

<script setup lang="ts" name="Father">
	import Child from './Child.vue'
	import {ref} from 'vue'
	// 数据
	let car = ref('梅赛德斯奔驰')
	let toy = ref('')
	// 方法
	function getToy(value:string){
		toy.value = value
	}
</script>

一个子组件:Child.vue

<template>
  <div class="child">
    <h3>子组件</h3>
		<h4>玩具:{{ toy }}</h4>
		<h4>父给的车:{{ car }}</h4>
		<button @click="sendToy(toy)">把玩具给父亲</button>
  </div>
</template>

<script setup lang="ts" name="Child">
	import {ref} from 'vue'
	// 数据
	let toy = ref('奥特曼')
	// 声明接收props
	defineProps(['car','sendToy'])
</script>

父组件传给子组件,通过在子组件:car='car',子组件通过defineProps拿到'car'

子组件传给父组件,在子组件的按钮上写上父组件传的sendToy函数,父组件使用getToy方法获取的值

注意:props适合父子组件,如果组件嵌套过深不适合使用

【自定义事件】

自定义事件专门用于:子->父

在父组件中,给子组件添加自定义事件:@自定义事件,并且在子组件使用defineEmits(),使用emit去接收

父组件:Father.vue

<template>
  <div class="father">
    <h3>父组件</h3>
		<h4 v-show="toy">子给的玩具:{{ toy }}</h4>
		<!-- 给子组件Child绑定事件 -->
    <Child @send-toy="saveToy"/>
  </div>
</template>

<script setup lang="ts" name="Father">
  import Child from './Child.vue'
	import { ref } from "vue";
	// 数据
	let toy = ref('')
	// 用于保存传递过来的玩具
	function saveToy(value:string){
		console.log('saveToy',value)
		toy.value = value
	}
</script>

子组件:Child.vue

<template>
  <div class="child">
    <h3>子组件</h3>
		<h4>玩具:{{ toy }}</h4>
		<button @click="emit('send-toy',toy)">测试</button>
  </div>
</template>

<script setup lang="ts" name="Child">
	import { ref } from "vue";
	// 数据
	let toy = ref('奥特曼')
	// 声明事件
	const emit =  defineEmits(['send-toy'])
</script>

【mitt】

Uni-App

编写Uni-App,推荐使用官方的IDE:HBuilderX,编写微信小程序,还需要下载微信开发者工具:微信开发者工具

引入插件

DCloud有活跃的插件市场,https://ext.dcloud.net.cn,并提供了变现、评价等机制。

DCloud插件市场将插件分为前端组件、JS SDK、uni-app前端模板、App原生插件、uniCloud等7大类、20多个子类。

比如安装uni-uiuni-ui,点击下载插件并导入HBuilderX

1699257514785

云存储

云存储的上传方式有3种:

  1. web界面:即在https://unicloud.dcloud.net.cn web控制台,点击云存储,通过web界面进行文件上传。该管理界面同时提供了资源浏览、删除等操作界面。

  2. 客户端API或组件上传:在前端js中编写uniCloud.uploadFile,或者使用uni ui的FilePicker组件,文件选择+上传均封装完毕。

    <uni-file-picker v-model="imageValue" fileMediatype="image" mode="grid" @select="select" @progress="progress"
    @success="success" @fail="fail" ref="files" />
  3. 云函数上传文件到云存储:即在云函数js中编写uniCloud.uploadFile

使用云函数js编写:

自定义上传页面

<template>
	<view class="file">
		<view class="uploadGroup">
			<view class="box" v-for="(item, index) in temFiles" :key="index">
				<image :src="item" mode="aspectFill"></image>
				<view class="close" @click="onClose(index)">×</view>
			</view>
			<view class="box add" @click="addFile" v-show="temFiles.length<maxSize">+</view>
		</view>

	</view>
</template>

<script>
	export default {
		data() {
			return {
				temFiles: [],
				maxSize: 9
			}
		},
		onLoad() {},
		methods: {
			addFile() {
				uni.chooseImage({
					success: res => {
						this.temFiles = [...this.temFiles, ...res.tempFilePaths].slice(0, this.maxSize);
					}
				})
			},
			onClose(e) {
				this.temFiles.splice(e, 1);
			}
		},
	}
</script>

<style lang="scss" scoped>
	.uploadGroup {
		padding: 30rpx;
		display: flex;
		flex-wrap: wrap;

		.box {
			margin-left: 15rpx;
			margin-bottom: 15rpx;
			width: 200rpx;
			height: 200rpx;
			background: #eee;
			position: relative;

			image {
				width: 100%;
				height: 100%;
			}

			.close {
				position: absolute;
				right: 0;
				top: 0;
				width: 50rpx;
				height: 50rpx;
				background: rgba(0, 0, 0, 0.7);
				color: #fff;
				border-radius: 0 0 0 80rpx;
				display: flex;
				align-items: center;
				justify-content: center;
			}
		}

		.add {
			font-size: 80rpx;
			display: flex;
			align-items: center;
			justify-content: center;
			color: #999;
		}
	}
</style>

下面是效果图:

image-20231107102531845

页面间传值

例如我们从A页=>B页

在A页中的跳转时,将参数拼接上:

clickItem(id) {
    uni.navigateTo({
        url: "/pages/B/B?id=" + id,
    })
}

此时点击跳转的链接就变为了(http://localhost:5173/#/pages/B/B?id=1)

当我们在B页面要接收时,直接使用onLOad去接收

onLoad(id) {
    console.log(id);
},

此时,A页面就将id转给了B页面。

各种事件

下拉刷新

当用户下拉刷新时触发,示例:

onPullDownRefresh() {
	this.listArr = [];
	this.getData();
},

下拉刷新时,重新获取数据,但是刷新的动画不会消失,所以可以在数据完后后使用uni.stopPullDownRefresh()结束刷新的动画。

触底事件

当用户滑倒到页面底部时触发,示例:

// 触底
onReachBottom() {
	this.getData();
},

methods: {
    getData() {
        uniCloud.callFunction({
            name: "art_get_all",
            data: {
                skip: this.listArr.length
            }
        }).then(res => {
            let oldList = this.listArr;
            this.listArr = [...oldList, ...res.result.data]
            uni.stopPullDownRefresh()
        })
    },
},

此处在下拉时,请求数据,并且根据已经加载的数据,获取新的数据,所以采用:this.listArr.length,去分页查询新的数据,下面是与函数的查询:

const db = uniCloud.database();
exports.main = async (event, context) => {
	let {
		skip = 0
	} = event;
	return await db.collection("article").limit(8).skip(skip).orderBy("time", "desc").get(); // 每次传递8条,时间倒序
};

Uni-App-TS

创建项目

使用命令行创建uni-app-vue3-ts版:

npx degit dcloudio/uni-preset-vue#vite-ts 项目名

进入项目后,进行安装依赖:npm i,启动可按package.json文件中的scripts启动命令

用VS Code开发

安装uni-app 插件

TS类型校验

pnpm i -D miniprogram-api-typings @uni-helper/uni-app-types

配置tsconfig.json,可参考:

// tsconfig.json
{
  "extends": "@vue/tsconfig/tsconfig.json",
  "compilerOptions": {
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    "lib": ["esnext", "dom"],
    // 类型声明文件
    "types": [
      "@dcloudio/types", // uni-app API 类型
      "miniprogram-api-typings", // 原生微信小程序类型
      "@uni-helper/uni-app-types" // uni-app 组件类型
    ]
  },
  // vue 编译器类型,校验标签类型
  "vueCompilerOptions": {
    // 原配置 `experimentalRuntimeMode` 现调整为 `nativeTags`
    "nativeTags": ["block", "component", "template", "slot"], 
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

JSON 注释问题

在VScode中,严格要求json格式,在json中会爆红,只需要我们设置文件关联,把 manifest.jsonpages.json 设置为 jsonc

设置 =》搜索文件关联 =》Files: Associations =》添加项

image-20240617025717041

安装uni-ui组件库

采用npm方式进行安装,在 vue-cli 项目中可以使用 npm 安装 uni-ui

npm i @dcloudio/uni-ui

配置easycom

使用 npm 安装好 uni-ui 之后,需要配置 easycom 规则,让 npm 安装的组件支持 easycom

打开项目根目录下的 pages.json 并添加 easycom 节点:

// pages.json
{
	"easycom": {
		"autoscan": true,
		"custom": {
			// uni-ui 规则如下配置
			"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
		}
	},
	
	// 其他内容
	pages:[
		// ...
	]
}

当我们使用ts时,需要对uni-ui声明组件类型,安装@uni-helper/uni-ui-types

npm i -D @uni-helper/uni-ui-types

并且在项目根目录的tsconfig.json添加类型, compilerOptions -> types

"types": [
    "@dcloudio/types",
    "miniprogram-api-typings",
    "@uni-helper/uni-app-types",
    "@uni-helper/uni-ui-types"
]

微信小程序登录

微信小程序扫码登录流程-官方时序图

image-202407212253830

流程为:

  1. 前端调用wx.login()获取code值,发给后端
  2. 后端将code,appId,appSecret作为参数调用微信接口 code2Session
  3. 后端通过调用微信接口返回的openId,session_key(用于解析用户信息)
  4. 后端根据微信接口返回的openId查询是否存在用户信息
  5. 存在则返回用户信息+token,不存在则注册用户返回openId用于前端请求用户保存用户信息

第一步:
引入依赖:

<!-- 微信小程序 -->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-miniapp</artifactId>
    <version>4.1.0</version>
</dependency>

application.yml文件中,配置:

wx:
  miniapp:
    appId: xxxxxxxxxxxxxxxxxx
    secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    msgDataFormat: JSON

创建配置文件类WxConfigProperties.java,读取配置内容:

@Component
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxConfigProperties {
	private String appId;
	private String secret;
}

创建微信工具包对象WxConfigOperator.java

@Component
public class WxConfigOperator {
	
	@Resource
	private WxConfigProperties wxConfigProperties;
	
	@Bean
	public WxMaService wxMaService() {
		// 小程序 id和密钥
		WxMaDefaultConfigImpl wxMaConfig = new WxMaDefaultConfigImpl();
		wxMaConfig.setAppid(wxConfigProperties.getAppId());
		wxMaConfig.setSecret(wxConfigProperties.getSecret());
		
		WxMaService service = new WxMaServiceImpl();
		service.setWxMaConfig(wxMaConfig);
		return service;
	}
}

通过wx.login(微信前端–小程序)接口获取code,将code传到后端,后端调用https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code


Vue与Uni-APP教程
https://junyyds.top/2023/10/18/Vue/
作者
Phils
发布于
2023年10月18日
更新于
2025年4月29日
许可协议