命令行创建 uni-app 项目:

vue3 + ts 版

[github]
1
2
# 通过 npx 从 github 下载
npx degit dcloudio/uni-preset-vue#vite-ts 项目名称
[👉国内 gitee]
1
2
# 通过 git 从 gitee 克隆下载 (👉备用地址)
git clone -b vite-ts https://gitee.com/dcloud/uni-preset-vue.git

安装依赖 pnpm install


用 VS Code 开发配置

  • 👉 前置工作:安装 Vue3 插件,点击查看官方文档

    • 安装 Vue Language Features (Volar) :Vue3 语法提示插件
    • 安装 TypeScript Vue Plugin (Volar) :Vue3+TS 插件
    • 工作区禁用 Vue2 的 Vetur 插件(Vue3 插件和 Vue2 冲突)
    • 工作区禁用 @builtin typescript 插件(禁用后开启 Vue3 的 TS 托管模式)
  • 👉 安装 uni-app 开发插件

    • uni-create-view :快速创建 uni-app 页面

    • uni-helper uni-app :代码提示

    • uniapp 小程序扩展 :鼠标悬停查文档

      —————————————-以上只需安装一次,后续无需重复安装————————————-

  • 👉 TS 类型校验

    • 安装 类型声明文件 pnpm i -D miniprogram-api-typings @uni-helper/uni-app-types
    • 配置 tsconfig.json
  • 👉 JSON 注释问题

    • 设置文件关联,把 manifest.jsonpages.json 设置为 jsonc

tsconfig.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
// tsconfig.json
{
"extends": "@vue/tsconfig/tsconfig.json",
"compilerOptions": {
"ignoreDeprecations": "5.0",
"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"]
}

工作区设置(ctrl+shift+p打开面板)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// .vscode/settings.json
{
// 在保存时格式化文件
"editor.formatOnSave": true,
// 文件格式化配置
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// 配置语言的文件关联
"files.associations": {
"pages.json": "jsonc", // pages.json 可以写注释
"manifest.json": "jsonc" // manifest.json 可以写注释
}
}

引入 uni-ui 组件库

安装 uni-ui 组件库

1
pnpm i @dcloudio/uni-ui

配置自动导入组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// pages.json
{
// 组件自动导入
"easycom": {
"autoscan": true,
"custom": {
// uni-ui 规则如下配置 // [!code ++]
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue" // [!code ++]
}
},
"pages": [
// …省略
]
}

安装类型声明文件

1
pnpm i -D @uni-helper/uni-ui-types

配置类型声明文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// tsconfig.json
{
"compilerOptions": {
// ...
"types": [
"@dcloudio/types", // uni-app API 类型
"miniprogram-api-typings", // 原生微信小程序类型
"@uni-helper/uni-app-types", // uni-app 组件类型
"@uni-helper/uni-ui-types" // uni-ui 组件类型 // [!code ++]
]
},
// vue 编译器类型,校验标签类型
"vueCompilerOptions": {
"nativeTags": ["block", "component", "template", "slot"]
}
}

增加h5端

1
2
3
4
5
6
7
8
9
10
11
12
// manifest.json
/* 网页端特有相关 */
"h5" : {
"router" : {
"base" : "",
"mode" : "hash"
},
"devServer" : {
"port" : 5173
},
"usingComponents" : true
},

安装pinia和持久化插件

安装pinia:

1
pnpm i pinia

安装持久化存储插件: pinia-plugin-persistedstate

1
pnpm i pinia-plugin-persistedstate

插件默认使用 localStorage 实现持久化,小程序端不兼容,需要替换持久化 API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/stores/index.ts
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'

// 创建 pinia 实例
const pinia = createPinia()
// 使用持久化存储插件
pinia.use(persist)

// 默认导出,给 main.ts 使用
export default pinia

// 模块统一导出
export * from './modules/client'
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
// src/stores/modules/client.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'

// 定义 Store
export const useClientStore = defineStore(
'client',
() => {
const token = ref()
const setToken = (val:string)=>{
token.value = val
}
const clearToken = ()=>{
token.value = undefined
}
return {
token,
setToken,
clearToken
}
},
// TODO: 持久化
{
persist: {
// 调整为兼容多端的API
storage: {
setItem(key, value) {
uni.setStorageSync(key, value) // [!code warning]
},
getItem(key) {
return uni.getStorageSync(key) // [!code warning]
},
},
}
},
)
1
2
3
4
5
6
7
8
9
10
11
// src/main.ts
import { createSSRApp } from "vue";
import App from "./App.vue";
import pinia from "./stores"; // [!code ++]
export function createApp() {
const app = createSSRApp(App);
app.use(pinia) // [!code ++]
return {
app,
};
}

http 请求封装

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// src/utils/http.ts
// 请求基地址
const baseURL = 'http://localhost:8080'

// 拦截器配置
const httpInterceptor = {
// 拦截前触发
invoke(options: UniApp.RequestOptions) {
// 1. 非 http 开头需拼接地址
if (!options.url.startsWith('http')) {
options.url = baseURL + options.url
}
// 2. 请求超时
options.timeout = 10000

// 3. 添加 token 请求头标识
const clientStore = useClientStore()
const token = clientStore.token
if(token){
options.header.Authorization = token
}
},
}

// 拦截 request 请求
uni.addInterceptor('request', httpInterceptor)
// 拦截 uploadFile 文件上传
uni.addInterceptor('uploadFile', httpInterceptor)

// 添加类型,支持泛型
interface Data<T> {
code: string
msg: string
data: T
}
export const http = <T>(options: UniApp.RequestOptions) => {
// 1. 返回 Promise 对象
return new Promise<Data<T>>((resolve, reject) => {
uni.request({
...options,
// 响应成功
success(res) {
// 状态码 2xx,参考 axios 的设计
if (res.statusCode >= 200 && res.statusCode < 300) {
// 2.1 提取核心数据 res.data
resolve(res.data as Data<T>)
} else if (res.statusCode == 401) {
// 401错误 -> 清理用户信息,跳转到登录页
const clientStore = useClientStore()
clientStore.clearToken()
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)
},
})
})
}


【拓展】代码规范

统一代码风格

  • 安装 eslint + prettier
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
41
42
43
44
45
/* eslint-env node */
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,
getApp: true,
UniApp: true,
UniHelper: true,
App: true,
Page: true,
Component: true,
AnyObject: 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'],
},
}
  • 配置 package.json
1
2
3
4
5
6
{
"script": {
// ... 省略 ...
"lint": "eslint . --ext .vue,.js,.ts --fix --ignore-path .gitignore"
}
}
  • 运行
1
pnpm lint

Git 工作流规范

  • 安装并初始化 husky

::: code-group

[pnpx]
1
pnpm dlx husky-init
[npx]
1
npx husky-init

:::

  • 安装 lint-staged
1
pnpm i -D lint-staged
  • 配置 package.json
1
2
3
4
5
6
7
8
{
"script": {
// ... 省略 ...
},
"lint-staged": {
"*.{vue,ts,js}": ["eslint --fix"]
}
}
  • 修改 .husky/pre-commit 文件
1
2
npm test   // [!code --]
npm run lint-staged // [!code ++]

完结撒花