根据uni-app官方的文档指示构建工程
根据uni-app官方的文档指示,我们目前可以使用以下方式创建vue3 + vite + ts的uni-app工程项目
使用Vue3/Vite版
# 创建以 typescript 开发的工程
npx degit dcloudio/uni-preset-vue#vite-ts vue3-vite-uniapp
下载好后,进行以下操作
cd ./vue3-vite-uniapp
yarn install
然后运行h5模块
yarn dev:h5
解决报错
发现报以下错误
这个错误是由于Node版本导致的,然后使用nvm管理Node包工具切换高级版本的Node,我是切换到15.6.0,然后再运行就没报错了。
成功运行
解决vscode语法报错
然后打开项目代码,瞄一眼,哎呀发现报语法错误
这是因为vscode的语法检查工具导致的,使用Vue3需要安装Volar,安装了Volar发现还是报这个错,然后查看一眼Volar文档,其中有这么一句话:
You need to disable Vetur to avoid conflicts.
所以需要把Vue2的Vetur语法提示检查工具禁止了,因为我还安装了Vetur-wepy所以也需要把这个也禁止了。
卸载不需要的包
因为默认的项目中带了vue-i18n的包,而我们小程序和微信H5项目一般都不需要多语言所以把vue-i18n删除了
npm uninstall vue-i18n
安装sass或less样式处理器
npm i -D sass
因为我们是使用Vite进行开发,所以只需要安装一下sass就可以了,不需要额外配置,比如像webpack那样安装loader
配置 eslint + prettier 自动格式化代码
安装相关依赖包
yarn add @typescript-eslint/eslint-plugin --dev
yarn add @typescript-eslint/parser --dev
yarn add @vue/eslint-config-prettier --dev
yarn add @vue/eslint-config-typescript --dev
yarn add @vuedx/typescript-plugin-vue --dev
yarn add eslint --dev
yarn add eslint-plugin-prettier --dev
yarn add eslint-plugin-vue --dev
yarn add prettier --dev
这个时候发现报错:
An unexpected error occurred: "EPERM: operation not permitted, unlink 'E:\test\vue3-vite-uniapp-test\node_modules\esbuild-windows-64\esbuild.exe'".
这个问题产生的原因就是在装包的时候,会删除之前的.bin
文件再重新生成,由于文件被占用导致无法删除文件,因此就会报错,只需要关闭相应的占用程序即可。
配置.eslintrc.js文件
安装完成之后配置.eslintrc.js文件
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'plugin:vue/vue3-recommended',
'eslint:recommended',
'@vue/typescript/recommended',
'@vue/prettier',
'@vue/prettier/@typescript-eslint',
// eslint-config-prettier 的缩写
'prettier',
],
parserOptions: {
ecmaVersion: 2021
},
plugins: [],
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
semi: 0,
}
}
prettier配置 .prettierrc.js
module.exports = {
printWidth: 120,
tabWidth: 2,
tabs: false,
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
jsxBracketSameLine: false,
arrowParens: 'always',
endOfLine: 'auto',
}
vscode 配置 .vscode/settings.json
{
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": ["typescript", "vue", "html", "json"],
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"json.format.enable": false
}
配置package包检测命令
"lint": "eslint --ext .ts,tsx,vue src/** --no-error-on-unmatched-pattern --quiet",
"lint:fix": "eslint --ext .ts,tsx,vue src/** --no-error-on-unmatched-pattern --fix"
配置忽略检查的文件.eslintignore
*.css
*.less
*.scss
*.jpg
*.png
*.gif
*.svg
*vue.d.ts
设置别名
vite.config.ts
resolve: {
alias: [
{
find: '@',
replacement: resolve(__dirname, 'src'),
},
],
},
tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [ "src/*" ],
},
}
}
配置vuex
yarn add vuex@next
目录结构
src\store\modules\app\action-types.ts
export enum AppActionTypes {
ACTION_LOGIN = 'ACTION_LOGIN',
ACTION_RESET_TOKEN = 'ACTION_RESET_TOKEN',
}
src\store\modules\app\ations.ts
import { ActionTree, ActionContext } from 'vuex'
import { RootState } from '@/store'
import { AppState } from './state'
import { Mutations } from './mutations'
import { AppActionTypes } from './action-types'
import { AppMutationTypes } from './mutation-types'
type AugmentedActionContext = {
commit<K extends keyof Mutations>(key: K, payload: Parameters<Mutations[K]>[1]): ReturnType<Mutations[K]>
} & Omit<ActionContext<AppState, RootState>, 'commit'>
export interface Actions {
[AppActionTypes.ACTION_RESET_TOKEN]({ commit }: AugmentedActionContext): void
}
export const actions: ActionTree<AppState, RootState> & Actions = {
[AppActionTypes.ACTION_LOGIN]({ commit }: AugmentedActionContext, token: string) {
commit(AppMutationTypes.SET_TOKEN, token)
},
[AppActionTypes.ACTION_RESET_TOKEN]({ commit }: AugmentedActionContext) {
commit(AppMutationTypes.SET_TOKEN, '')
},
}
src\store\modules\app\index.ts
import { Store as VuexStore, CommitOptions, DispatchOptions, Module } from 'vuex'
import { RootState } from '@/store'
import { state } from './state'
import { actions, Actions } from './ations'
import { mutations, Mutations } from './mutations'
import type { AppState } from './state'
export { AppState }
export type AppStore<S = AppState> = Omit<VuexStore<S>, 'getters' | 'commit' | 'dispatch'> & {
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
key: K,
payload: P,
options?: CommitOptions
): ReturnType<Mutations[K]>
} & {
dispatch<K extends keyof Actions>(
key: K,
payload: Parameters<Actions[K]>[1],
options?: DispatchOptions
): ReturnType<Actions[K]>
}
export const store: Module<AppState, RootState> = {
state,
actions,
mutations,
}
src\store\modules\app\mutation-types.ts
export enum AppMutationTypes {
SET_TOKEN = 'SET_TOKEN',
}
src\store\modules\app\mutations.ts
import { MutationTree } from 'vuex'
import { AppState } from './state'
import { AppMutationTypes } from './mutation-types'
export type Mutations<S = AppState> = {
[AppMutationTypes.SET_TOKEN](state: S, token: string): void
}
export const mutations: MutationTree<AppState> & Mutations = {
[AppMutationTypes.SET_TOKEN](state: AppState, token: string) {
state.token = token
},
}
src\store\modules\app\state.ts
export interface AppState {
token: string
}
export const state: AppState = {
token: '',
}
src\store\getters.ts
import { RootState } from '@/store'
export default {
token: (state: RootState) => state.app.token,
}
src\store\index.ts
import { createStore } from 'vuex'
import { store as app, AppState, AppStore } from '@/store/modules/app'
import getters from './getters'
export interface RootState {
app: AppState
}
export type Store = AppStore<Pick<RootState, 'app'>>
export const store = createStore<RootState>({
modules: {
app,
},
getters,
})
export function useStore(): Store {
return store as Store
}
vuex使用示例
<template>
<view class="content">
<image class="logo" src="/static/logo.png" />
<view class="text-area">
<text class="title">{{ title }}</text>
<view @click="setToken">login</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { AppActionTypes } from '@/store/modules/app/action-types'
import { useStore } from 'vuex'
const title = ref('Hello')
const store = useStore()
const setToken = () => {
store.dispatch(AppActionTypes.ACTION_LOGIN, 'token')
title.value = store.state.app.token
}
</script>
配置uni.request实现网络请求
/* eslint-disable @typescript-eslint/ban-types */
import appConfig from '@/config/app'
import { useStore } from 'vuex'
const { HEADER, HEADERPARAMS, TOKENNAME, HTTP_REQUEST_URL } = appConfig
type RequestOptionsMethod = 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'
type RequestOptionsMethodAll = RequestOptionsMethod | Lowercase<RequestOptionsMethod>
/**
* 发送请求
*/
function baseRequest(
url: string,
method: RequestOptionsMethod,
data: any,
{ noAuth = false, noVerify = false }: any,
params: unknown
) {
const store = useStore()
const token = store.state.app.token
const Url = HTTP_REQUEST_URL
let header = JSON.parse(JSON.stringify(HEADER))
if (params != undefined) {
header = HEADERPARAMS
}
if (!noAuth) {
if (!token) {
return Promise.reject({
msg: '未登录',
})
}
if (token && token !== 'null') header[TOKENNAME] = 'Bearer ' + token
}
return new Promise((reslove, reject) => {
uni.showLoading({
title: '加载中',
mask: true,
})
uni.request({
url: Url + url,
method: method || 'GET',
header: header,
data: data || {},
success: (res: any) => {
console.log('res', res)
uni.hideLoading()
res.data.token &&
res.data.token !== 'null' &&
store.commit('LOGIN', {
token: res.data.token,
})
if (noVerify) {
reslove(res)
} else if (res.statusCode === 200) {
reslove(res)
} else {
reject(res.data.message || '系统错误')
}
},
fail: (msg) => {
uni.hideLoading()
reject('请求失败')
},
})
})
}
// const request: Request = {}
const requestOptions: RequestOptionsMethodAll[] = [
'options',
'get',
'post',
'put',
'head',
'delete',
'trace',
'connect',
]
type Methods = typeof requestOptions[number]
const request: { [key in Methods]?: Function } = {}
requestOptions.forEach((method) => {
const m = method.toUpperCase as unknown as RequestOptionsMethod
request[method] = (api, data, opt, params) => baseRequest(api, m, data, opt || {}, params)
})
export default request
利用Koa配置Mock数据服务器
dependencies
"dependencies": {
"koa": "^2.13.0",
"koa-body": "^4.2.0",
"koa-logger": "^3.2.1",
"koa-router": "^10.0.0",
"koa2-cors": "^2.0.6",
"lodash": "^4.17.20",
"log4js": "^6.3.0",
"faker": "^5.1.0",
"reflect-metadata": "^0.1.13"
}
devDependencies
"devDependencies": {
"@types/koa": "^2.11.6",
"@types/koa-logger": "^3.1.1",
"@types/koa-router": "^7.4.1",
"@types/koa2-cors": "^2.0.1",
"@types/faker": "^5.1.5",
}
在根目录下建一个mock目录
具体目录结构如下
package.json里配置mock服务器启动命令
"scripts": {
"mock": "cd mock && ts-node-dev mock.ts"
}
api/user.ts
import request from '@/utils/request.js'
/**
* 获取用户信息
*
*/
export function fetchUserInfo() {
return request?.get?.('/user/userInfo', {}, { noAuth: true })
}
测试访问使用
fetchUserInfo()
.then((r) => {
console.log('r', r)
})
.catch((err) => console.log(err))
测试运行
运行小程序测试环境
npm run dev:mp-weixin
启动正常并打印出了mock的数据
打印出mock数据
同理测试H5端运行情况
npm run dev:h5
运行正常并打印出了mock数据
实践总结
目前成功实践配置了使用Vue3 + Vite + TS 来搭建uniapp项目,并成功运行了H5端和小程序端,目前这两个端应该是需求最多的,至于其他端,本人并不擅长,所以就留给大神们去探索研究吧。
后续将会继续探索实现具体的项目。