0. 前言

  • 最近在把一个uni-app项目从原来的vue2重构到vue3上,趁这次机会记录一下这个项目打搭建方便自己后面再次用到就可以直接拉代码了。
  • 这篇文章很多实现和思路都是参考其他大佬的,如果有哪里不对欢迎大家指出。

1. 项目初始化

1.1 通过vue-cli命令创建

  • 全局安装vue-cli
1
npm i -g @vue/cli
  • 使用Vue3/Vite版
  • 创建以 typescript 开发的工程(如命令行创建失败,请直接访问 gitee 下载模板)
1
npx degit dcloudio/uni-preset-vue#vite uniapp-vue3-vite

tips:

  • Vue3/Vite版要求 node 版本^14.18.0 || >=16.0.0

1.2 ESLint

1
2
# 根据提示和项目情况选择y/n
npx eslint --init

配置参考文章

vs-code安装和配置ESLint

1.3 prettier

1
npm i prettier eslint-config-prettier eslint-plugin-prettier -D
  • 在根目录创建.prettierrc.js文件
1
2
3
4
5
6
7
8
9
10
11
12
// .prettierrc.js
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false, // 是否使用tab进行缩进,默认为false
singleQuote: true, // 是否使用单引号代替双引号,默认为false
semi: true, // 行尾是否使用分号,默认为true
arrowParens: 'always',
endOfLine: 'auto',
vueIndentScriptAndStyle: true,
htmlWhitespaceSensitivity: 'strict',
};
  • 配置.eslintrc.js
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
// .eslintrc.js

module.exports = {
root: true, // 停止向上查找父级目录中的配置文件
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:prettier/recommended',
'prettier', // eslint-config-prettier 的缩写
],
parser: 'vue-eslint-parser', // 指定要使用的解析器
// 给解析器传入一些其他的配置参数
parserOptions: {
ecmaVersion: 'latest', // 支持的es版本
parser: '@typescript-eslint/parser',
sourceType: 'module', // 模块类型,默认为script,我们设置为module
},
plugins: ['vue', 'prettier'], // eslint-plugin- 可以省略
rules: {
'vue/multi-word-component-names': 'off'
},
};
  • 根目录package.json添加lint命令
1
2
3
4
# package.json

# 可以运行'npm run lint'检查代码
"lint": "eslint --ext .js,.vue,.ts src --fix"

1.4 保存文件自动格式化

  • 在vscode中项目文件里面的.vscode设置
1
2
3
4
5
6
7
8
9
10
//.vscode/settings.json

{
// 保存时eslint自动修复错误
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
// 保存自动格式化
"editor.formatOnSave": true
}

2. 环境变量

vite官方文档:

环境变量和模式

  1. 创建环境变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 根目录创建.env.[mode]文件

# .env.development

# 开发环境
NODE_ENV = development
VITE_APP_API_BASE_URL = 'http://10.204.xx.xx:9091'
# 是否在打包时生成 sourcemap
VITE_BUILD_SOURCEMAP = true
# 是否在打包时删除 console 代码
VITE_BUILD_DROP_CONSOLE = false

# .env.test
# .env.production

.env.[mode]文件中的mode可自定义,如.env.development对应package.json脚本中的--mode development
只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码

1
2
3
4
5
//下面三条命令,分别表示开发环境、测试环境、生产环境的运行和打包命令

"dev:h5": "uni -p h5 --mode development",
"build:test": "uni build --mode test",
"build:pro": "uni build -p h5 --mode production"
  1. 使用环境变量
  • js,vue 文件中可使用import.meta.env获取环境变量,比如:
1
2
3
let baseUrl = import.meta.env.VITE_APP_API_BASE_URL;

let isProd = import.meta.env.MODE === 'production';
  • vite.config.js使用环境变量
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
// vite.config.js

import { defineConfig, loadEnv } from 'vite';

export default ({ command, mode }) => {
const env = loadEnv(mode, process.cwd());
return defineConfig({
plugins: [
uni(),
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@img': path.resolve(__dirname, 'src/static/images'),
},
},
build: {
sourcemap: env.VITE_BUILD_SOURCEMAP === 'true',
minify: 'terser',
terserOptions: {
compress: {
drop_console: env.VITE_BUILD_DROP_CONSOLE === 'true', // 去除 console
},
},
chunkSizeWarningLimit: 1500, // chunk 大小警告的限制(以 kbs 为单位)
},
});
};

3. Css预处理器

1
2
3
4
5
// 1、 安装sass
npm i sass -D 或 yarn add sass -D

//安装 sass-loader
npm i sass-loader@10.1.1 -D 或 yarn add sass-loader@10.1.1 -D
  1. 全局使用自定义变量
    • 根目录新建样式文件夹styles
    • index.scss - 自定义变量
1
2
3
4
5
6
7
8
// vite.config.js
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/vars.scss";`,
},
},
}

vue文件使用

1
2
3
.title {
color: $font-color
}

4. uni-ui

uni-ui官方文档

  1. 安装uni-ui
1
2
//安装 uni-ui
npm i @dcloudio/uni-ui 或 yarn add @dcloudio/uni-ui
  1. 配置easycom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
配置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:[
// ...
]
}

5. 自动导入API

unplugin-auto-import

1
npm i unplugin-auto-import -D
  • Vite配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// vite.config.js
import AutoImport from 'unplugin-auto-import/vite'

plugins: [
AutoImport({
imports: ['vue', 'uni-app'],
// 可以选择auto-import.d.ts生成的位置,使用ts建议设置为'src/auto-import.d.ts'
// dts: 'src/auto-import.d.ts'
// 自动生成'eslintrc-auto-import.json'文件,在'.eslintrc.cjs''extends'中引入解决报错
eslintrc: {
enabled: true,
},
})
]
  • 原理: 安装的时候会自动生成auto-imports.d文件(默认是在根目录)
  • 其他插件 vue-router, vue-i18n, @vueuse/head, @vueuse/core等自动引入的自动引入请查看文档
  • .eslintrc.js配置
1
2
3
4
5
// .eslintrc.js
extends: [
// 解决使用自动导入api报错
'./.eslintrc-auto-import.json',
],

接下来就可以全局使用 vue 相关 api,不用一个个手动导入了。哪些 api 可用请参考生成的 src/auto-import.d.ts 类型声明文件。

6. Pinia

pinia官方文档

  1. 安装
1
npm i pinia
  1. 创建store
1
2
3
4
5
6
7
8
// src/store/index.js

import { createPinia } from 'pinia';

const pinia = createPinia();

export default pinia;
export * from './modules/user';
  1. 挂载store
1
2
3
4
5
6
7
8
9
10
11
12
// src/main.js
import { createSSRApp } from 'vue';
import store from './store';
import App from './App.vue';

export function createApp() {
const app = createSSRApp(App);
app.use(store);
return {
app,
};
}
  1. 创建useUserStore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/store/modules/user/index.js

import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
// id: 'user', // id必填,且需要唯一
state: () => {
return {
name: '张三',
};
},
getters: {
nameLength: (state) => state.name.length,
},
actions: {
updateName(name) {
this.name = name;
},
},
});
  1. 使用useUserStore
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
<template>
<div class="pinia">
<div class="name">用户名:{{ userStore.name }}</div>
<div class="length">长度:{{ userStore.nameLength }}</div>
<van-button type="primary" @click="updateName(true)">action修改store中的name</van-button>
<van-button @click="updateName(false)">patch修改store中的name</van-button>
</div>
</template>

<script setup>
import { useUserStore } from '@/store';

const userStore = useUserStore();

const updateName = (isAction) => {
if (isAction) {
// action 修改 store 中的数据
userStore.updateName('userStore.updateName方式');
} else {
// 未定义 action 时可以用 $patch 方法直接更改状态属性
// $patch 修改 store 中的数据
userStore.$patch({
name: 'userStore.$patch方式',
});
}
};
</script>
  1. ==注意点==

在使用 pinia 中的变量时如果使用解构赋值,需要使用 storeToRefs 这个方法包裹一下,否则全局变量会失去响应式,变量更新时并不会重新渲染组件。

1
2
3
4
5
6
7
import { useUserStore } from '@/store';

// bad
const {name} = useUserStore()

// good
const (user) = storeToRefs(useUserStore())

7. 请求封装

封装请求的方式多种多样,根据自己喜欢的方式实现就好,还可以根据需求增加重试或者取消请求等方法。

  1. request请求统一封装
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
74
75
76
77
78
79
80
81
82
83
84
85
86
// src/utils/http/request.js

import { getToken } from '@/utils/auth';
import { useUserStore } from '@/store';

let baseUrl = import.meta.env.VITE_APP_API_BASE_URL;
const request = ({
url = '',
data = {},
method = 'POST',
header = { token: getToken() },
hideLoading=false,
hideMessage,
}) => {
const userStore = useUserStore();

return new Promise((resolve, reject) => {
// if (!hideLoading) {
// uni.showLoading({});
// }
uni.request({
timeout: 60000,
method,
url: baseUrl + url,
data,
header,
success(response) {
// if (!hideLoading) {
// uni.hideLoading();
// }
let res = response.data;
// 请求成功,状态码不等于0,报错处理
if (res.resultCode !== 0) {
if (hideMessage) {
reject(res || 'Error');
} else {
if (res.resultCode === 3 || res.resultCode === -5) {
// hideMessage 是否隐藏错误提示
uni.showToast({
title: res.resultMessage,
icon: 'none',
duration: 3000,
});
} else if (res.resultCode === -4) {
//
} else {
if (res.resultCode === -1) {
// to re-login
uni.showModal({
title: '提示',
content: '登录失效,请重新登录!',
confirmColor: '#0087FF',
cancelColor: '#0087FF',
success: function (res) {
if (res.confirm) {
//*清空缓存重新登录
userStore.resetToken().then(() => {
uni.navigateTo({
url: '/subPackagesA/personal/chooseLoginType',
});
});
}
},
});
} else {
uni.showToast({
title: `操作异常,请联系管理员(${res.resultCode})!`,
icon: 'none',
duration: 3000,
});
}
}
reject(res || 'Error');
}
} else {
// 成功直接返回promise
resolve(res);
}
},
fail(err) {
reject(err);
},
});
});
};
export default request;
  1. 接口api管理
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
// src/api/UserService.js

import request from '@/utils/request';
import { requestPort } from '@/utils/requestPort';

export default {
login(data) {
return request({
url: `${requestPort.users}/user/login`,
method: 'post',
data,
});
},
logout() {
return request({
url: `${requestPort.users}/user/logout/3`,
method: 'post',
});
},
logoff(data) {
return request({
url: `${requestPort.users}/user/logoff`,
method: 'post',
data,
});
}
};
  1. 使用接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// vue文件内
<template>
<view>
<button type="default" class="logout" @click="logout">注销</button>
</view>
</template>>
<script setup>
import UserService from '@/api/UserService';

const logout = async () => {
try {
const { resultData } = await UserService.logout();
console.log(resultData, 'resultData');
} catch (e) {
console.log(e, 'error');
}
};
<script>

8. 配置开发环境代理

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
// vite.config.js

import { defineConfig, loadEnv } from 'vite';

const path = require('path')

export default ({ command, mode }) => {
const env = loadEnv(mode, process.cwd())
return defineConfig({
plugins: [uni()],
base: './',
server: {
"port": 8080,
proxy: {
'/apis': {
target: 'https://www.xxx.com',
changeOrigin: true,
secure: false,
rewrite: path => {
return path.replace(/^\/apis/, '/')
}
// vue3中更换了写法
// pathRewrite: ( path ) => path.replace( /^\/api/, '' ),
}
}
}
})
}
  • 以上就是环境搭建的大致流程了,缺少的部分后面想到了再更新。