✔ 能够通过 HBuilderX 和命令行创建 uni-app 项目。
✔ 能够配置和使用 uni-ui 库。
✔ 掌握 http 请求函数的封装。
项目架构

创建 uni-app 项目
uni-app 支持两种方式创建项目,分别如下。
通过 HBuilderX 创建。
通过命令行创建(更推荐)。
通过 HBuilderX 创建
- 下载安装 HbuilderX 编辑器。

- 通过 HbuilderX 创建 uni-app vue3 项目。

- 安装 uni-app vue3 编译器插件。

- 编译成微信小程序端代码。

- 开启服务端口。

😀 小技巧分享:模拟器窗口分离和置顶。

Hbuildex 和 微信开发者工具 关系

温馨提示:Hbuildex 和 uni-app 都属于 DCloud 公司的产品。
pages.json 和 tabBar 案例
目录结构
我们先来认识 uni-app 项目的目录结构。
1 2 3 4 5 6 7 8 9 10 11
| ├─pages 业务页面文件存放的目录 │ └─index │ └─index.vue index页面 ├─static 存放应用引用的本地静态资源的目录(注意:静态资源只能存放于此) ├─unpackage 非工程代码,一般存放运行或发行的编译结果 ├─index.html H5端页面 ├─main.js Vue初始化入口文件 ├─App.vue 配置App全局样式、监听应用生命周期 ├─pages.json 配置页面路由、导航栏、tabBar等页面类信息 ├─manifest.json 配置appid、应用名称、logo、版本等打包信息 └─uni.scss uni-app内置的常用样式变量
|
解读 pages.json
用于配置页面路由、导航栏、tabBar 等页面类信息。

案例练习

pages.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| { "pages": [ { "path": "pages/index/index", "style": { "navigationBarTitleText": "首页" } }, { "path": "pages/my/my", "style": { "navigationBarTitleText": "我的" } } ], "globalStyle": { "navigationBarTextStyle": "white", "navigationBarTitleText": "uni-app", "navigationBarBackgroundColor": "#27BA9B", "backgroundColor": "#F8F8F8" }, "tabBar": { "selectedColor": "#27BA9B", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/tabs/home_default.png", "selectedIconPath": "static/tabs/home_selected.png" }, { "pagePath": "pages/my/my", "text": "我的", "iconPath": "static/tabs/user_default.png", "selectedIconPath": "static/tabs/user_selected.png" } ] } }
|
uni-app 和原生小程序区别

主要区别
uni-app 项目每个页面是一个 .vue
文件,数据绑定及事件处理同 Vue.js
规范。
属性绑定 src="{ { url }}"
升级成 :src="url"
。
事件绑定 bindtap="eventName"
升级成 @tap="eventName"
,支持()传参。
支持 Vue 常用指令 v-for
、v-if
、v-show
、v-model
等。
其他区别补充
调用接口能力,建议前缀 wx
替换为 uni
,养成好习惯,这样支持多端开发。
<style></style>
样式不需要写 scoped
。
案例练习
主要功能:滑动轮播图;点击大图预览。

pages/index/index.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <template> <swiper class="banner" indicator-dots circular :autoplay="false"> <swiper-item v-for="item in pictures" :key="item.id"> <image @tap="onPreviewImage(item.url)" :src="item.url"></image> </swiper-item> </swiper> </template>
<script> export default { data() { return { pictures: [ { id: '1', url: 'https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/goods_preview_1.jpg', }, { id: '2', url: 'https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/goods_preview_2.jpg', }, { id: '3', url: 'https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/goods_preview_3.jpg', }, { id: '4', url: 'https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/goods_preview_4.jpg', }, { id: '5', url: 'https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/goods_preview_5.jpg', }, ], } }, methods: { onPreviewImage(url) { uni.previewImage({ urls: this.pictures.map((v) => v.url), current: url, }) }, }, } </script>
<style> .banner, .banner image { width: 750rpx; height: 750rpx; } </style>
|
用命令行创建 uni-app 项目

优势:通过命令行创建 uni-app 项目,不必依赖 HBuilderX,TypeScript 类型支持友好。
下面是 vue3 + ts
版,创建其他版本可查看:uni-app 官网。
1
| npx degit dcloudio/uni-preset-vue#vite-ts 项目名称
|
安装依赖 pnpm install
。
编译成微信小程序 pnpm dev:mp-weixin
。
导入微信开发者工具。
😚 温馨提示:在 manifest.json
文件添加小程序 appid
方便真机预览。
VSCode 开发 uni-app 项目
为什么选择 VSCode

用 VSCode 开发配置

1 2 3 4 5 6 7 8 9 10 11 12 13
| { "extends": "@vue/tsconfig/tsconfig.json", "compilerOptions": { "sourceMap": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"] }, "lib": ["esnext", "dom"], "types": ["@dcloudio/types", "@types/wechat-miniprogram", "@uni-helper/uni-app-types"] }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] }
|
开发工具回顾
选择自己习惯的编辑器开发 uni-app 项目即可。
VSCode 和 微信开发者工具 关系。

HbuilderX 和 微信开发者工具 关系。

用 VSCode 开发课后练习
使用 VSCode
编辑器写代码,实现 tabBar 案例 + 轮播图案例。
温馨提示:VSCode
可通过快捷键 Ctrl + i
唤起代码提示。
拉取项目模板代码
项目模板包含:目录结构,项目素材,代码风格。
1
| git clone http://git.itcast.cn/heimaqianduan/erabbit-uni-app-vue3-ts.git heima-shop
|
😚 提示:在 manifest.json
中添加微信小程序的 appid
。
引入 uni-ui 组件库

- 安装 uni-ui 组件库。
1 2
| pnpm i @dcloudio/uni-ui pnpm i sass -D
|
- 配置自动导入组件,
pages.json
。
1 2 3 4 5 6 7 8 9 10 11
| { "easycom": { "autoscan": true, "custom": { "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue" } }, }
|
- 测试,
pages/index/index.vue
。
1 2 3
| <uni-card title="基础卡片" sub-title="副标题" extra="额外信息" thumbnail="https://web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png"> <text>这是一个带头像和双标题的基础卡片,此示例展示了一个完整的卡片。</text> </uni-card>
|
- 鼠标悬停到
uni-card
显示 any,安装类型声明文件并配置。
1
| pnpm i -D @uni-helper/uni-ui-types
|
tsconfig.json
,在 types 中添加最后一个配置项。
1 2 3 4 5 6 7
| { "compilerOptions": { "types": ["@dcloudio/types", "@types/wechat-miniprogram", "@uni-helper/uni-app-types", "@uni-helper/uni-ui-types"] }, }
|
小程序 Pinia 持久化
说明:项目中 Pinia 用法平时完全一致,主要解决持久化插件兼容性问题。

- 持久化存储插件: pinia-plugin-persistedstate。
1
| pnpm i pinia-plugin-persistedstate
|
stores/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persist)
export default pinia
export * from './modules/member'
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script setup lang="ts"> import { useMemberStore } from '@/stores' const memberStore = useMemberStore() </script>
<template> <view class="my"> <view class="my"> <view>会员信息:{{ memberStore.profile }}</view> <button @tap="memberStore.setProfile({ nickname: '黑马先锋' })" size="mini" plain type="primary">保存用户信息</button> <button @tap="memberStore.clearProfile()" size="mini" plain type="warn">清理用户信息</button> </view> </view> </template>
<style lang="scss"> // </style>
|
- 插件默认使用
localStorage
实现持久化,小程序端不兼容,需要替换持久化 API,store/modules/member.ts
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export const useMemberStore = defineStore( 'member', () => { }, { persist: { storage: { setItem(key, value) { uni.setStorageSync(key, value) }, getItem(key) { return uni.getStorageSync(key) }, }, }, }, )
|
- 我的
.vscode/settings.json
参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| { "eslint.enable": true, "eslint.run": "onType", "eslint.options": { "extensions": [ ".js", ".vue", ".jsx", ".tsx" ] }, "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "files.associations": { "pages.json": "jsonc", "manifest.json": "jsonc" } }
|
uni.request 请求封装


添加请求和上传文件拦截器
uni-app 拦截器:uni.addInterceptor。
接口说明:接口文档
参考代码如下,src/utils/http.ts
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import { useMemberStore } from '@/stores' const baseURL = 'https://pcapi-xiaotuxian-front-devtest.itheima.net'
const httpInterceptor = { invoke(options: UniApp.RequestOptions) { if (!options.url.startsWith('http')) { options.url = baseURL + options.url } options.timeout = 10000 options.header = { ...options.header, 'source-client': 'miniapp', } const memberStore = useMemberStore() const token = memberStore.profile?.token if (token) { options.header.Authorization = token } }, }
uni.addInterceptor('request', httpInterceptor)
uni.addInterceptor('uploadFile', httpInterceptor)
|
- 测试,
pages/my/my.vue
。
1 2 3 4 5 6 7 8 9 10 11 12
| import { useMemberStore } from '@/stores' import '@/utils/http' const memberStore = useMemberStore()
const getData = async () => { const r = await uni.request({ method: 'GET', url: '/home/banner' }) console.log(r) } getData()
|
封装 Promise 请求函数

- 封装基础的 http 函数,返回 Promise 对象,
utils/http.ts
。
1 2 3 4 5 6 7 8 9 10 11
| export const http = (options: UniApp.RequestOptions) => { return new Promise((resolve, reject) => { uni.request({ ...options, success(res) { resolve(res.data) } }) }) }
|
测试,pages/my/my.vue
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { useMemberStore } from '@/stores'
import { http } from '@/utils/http' const memberStore = useMemberStore()
const getData = async () => { const r = await http({ method: 'GET', url: '/home/banner' }) console.log(r) } getData()
|
- 给 http 函数添加泛型。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| interface Data<T> { code: string msg: string result: T } export const http = <T>(options: UniApp.RequestOptions) => { return new Promise<Data<T>>((resolve, reject) => { uni.request({ ...options, success(res) { resolve(res.data as Data<T>) } }) }) }
|
测试,pages/my/my.vue
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { useMemberStore } from '@/stores' import { http } from '@/utils/http' const memberStore = useMemberStore()
interface IBannerItem { hrefUrl: string id: string imgUrl: string type: string }
const getData = async () => { const r = await http<IBannerItem[]>({ method: 'GET', url: '/home/banner' }) console.log(r.result[0].hrefUrl) } getData()
|
- 处理失败的情况。


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
type Data<T> = { code: string msg: string result: T }
export const http = <T>(options: UniApp.RequestOptions) => { return new Promise<Data<T>>((resolve, reject) => { uni.request({ ...options, success(res) { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data as Data<T>) } else if (res.statusCode === 401) { const memberStore = useMemberStore() memberStore.clearProfile() uni.navigateTo({ url: '/pages/login/login' }) reject(res) } else { uni.showToast({ icon: 'none', title: (res.data as Data<T>).msg || '请求错误', }) reject(res) } }, fail(err) { uni.showToast({ icon: 'none', title: '网络错误,换个网络试试', }) reject(err) }, }) }) }
|
- 测试 401,
pages/my/my.vue
。
1 2 3
| <button @tap="getProfile" size="mini" plain type="primary"> 获取个人信息 </button>
|
1 2 3 4 5 6 7 8
| const getProfile = async () => { const r = await http({ method: 'GET', url: '/member/profile', header: {} }) console.log(r) }
|
其他错误测试。
1 2 3 4 5 6 7 8 9
| const getProfile = async () => { const r = await http({ method: 'GET', url: '', header: {} }) console.log(r) }
|
【拓展】了解代码规范
为什么需要代码规范?
如果没有统一代码风格,团队协作不便于查看代码提交时所做的修改。

统一代码风格
1
| pnpm i -D eslint prettier eslint-plugin-vue @vue/eslint-config-prettier @vue/eslint-config-typescript @rushstack/eslint-patch @vue/tsconfig
|
- 新建
.eslintrc.cjs
文件,添加以下 eslint
配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = { root: true, extends: [ 'plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-typescript', '@vue/eslint-config-prettier', ], globals: { uni: true, wx: true, WechatMiniprogram: true, getCurrentPages: true, UniApp: true, UniHelper: true, }, parserOptions: { ecmaVersion: 'latest', }, rules: { 'prettier/prettier': [ 'warn', { singleQuote: true, semi: false, printWidth: 100, trailingComma: 'all', endOfLine: 'auto', }, ], 'vue/multi-word-component-names': ['off'], 'vue/no-setup-props-destructure': ['off'], 'vue/no-deprecated-html-element-is': ['off'], '@typescript-eslint/no-unused-vars': ['off'], }, }
|
1 2 3 4 5 6
| { "script": { "lint": "eslint . --ext .vue,.js,.ts --fix --ignore-path .gitignore" } }
|
到此,你已完成 eslint
+ prettier
的配置。
Git 工作流规范
1 2 3 4 5 6 7 8
| { "script": { }, "lint-staged": { "*.{vue,ts,js}": ["eslint --fix"] } }
|
1 2
| npm test // [!code --] pnpm lint-staged // [!code ++]
|
到此,你已完成 husky
+ lint-staged
的配置。