从0到1搭建Vue3+Vant移动端项目
基于Vue3和Vant组件库搭建一个完整的移动端项目,需要考虑项目结构、代码规范、基础功能模块等多个方面。下面我将详细介绍如何从零开始构建一个企业级移动端项目。
1. 初始化项目
首先,需要确保Node.js版本 >= 12.0.0,然后使用Vite初始化项目:
bash
# 使用npm
npm init vite@latest my-mobile-app -- --template vue
# 或使用yarn
yarn create vite my-mobile-app --template vue
# 或使用pnpm
pnpm dlx create-vite my-mobile-app --template vue
进入项目目录并安装依赖:
bash
cd my-mobile-app
npm install
2. 添加代码规范工具
2.1 ESLint配置
安装ESLint及相关插件:
bash
npm install eslint eslint-plugin-vue babel-eslint -D
创建.eslintrc.js
配置文件:
javascript
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'plugin:prettier/recommended'
],
rules: {
'prettier/prettier': 'error',
eqeqeq: 2, // 强制使用 === 和 !==
'no-var': 2, // 禁止使用 var 声明变量
'prefer-const': 2, // 使用 const 声明那些声明后不再被修改的变量
// 更多规则...
}
}
2.2 Prettier配置
安装Prettier及相关插件:
bash
npm install prettier eslint-config-prettier eslint-plugin-prettier -D
创建.prettierrc.js
配置文件:
javascript
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
singleQuote: true,
semi: false,
trailingComma: "none",
bracketSpacing: true,
eslintIntegration: true
}
2.3 Git Hook配置
使用husky管理Git Hook:
bash
npm install husky -D
在package.json
中添加配置:
json
{
"scripts": {
"prepare": "husky install",
"lint": "eslint --ext .vue,.js ./",
"lint:fix": "eslint --fix --ext .vue,.js ./"
}
}
执行命令并创建pre-commit钩子:
bash
npm run prepare
创建.husky/pre-commit
文件:
bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint:fix
配置commit-msg钩子:
bash
npm install @commitlint/cli @commitlint/config-conventional -D
创建commitlint.config.js
文件:
javascript
module.exports = {
extends: [
'@commitlint/config-conventional'
]
}
创建.husky/commit-msg
文件:
bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit $1
3. 添加路由管理
安装Vue Router:
bash
npm install vue-router -S
创建src/router/index.js
文件:
javascript
import { createRouter, createWebHashHistory } from 'vue-router'
import Layout from '@/layout/index.vue'
const routes = [
{
path: '/',
component: Layout,
redirect: '/home',
children: [
{
path: 'home',
name: 'Home',
component: () => import('@/views/home/index.vue'),
meta: { title: '首页', keepAlive: false }
},
{
path: 'user',
name: 'User',
component: () => import('@/views/user/index.vue'),
meta: { title: '我的', keepAlive: true }
}
]
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/login/index.vue'),
meta: { title: '登录' }
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/error/404.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
document.title = to.meta.title || '移动端应用'
const token = localStorage.getItem('token')
if (to.path === '/login') {
next()
} else {
if (token) {
next()
} else {
next('/login')
}
}
})
export default router
在main.js
中引入路由:
javascript
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
4. 状态管理
安装Vuex:
bash
npm install vuex -S
创建src/store/index.js
:
javascript
import { createStore } from 'vuex'
import user from './modules/user'
import app from './modules/app'
const store = createStore({
modules: {
user,
app
}
})
export default store
创建用户模块src/store/modules/user.js
:
javascript
import { login, logout, getUserInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
const state = {
token: getToken(),
userInfo: {}
}
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_USER_INFO: (state, info) => {
state.userInfo = info
}
}
const actions = {
// 登录
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password })
.then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
})
.catch(error => {
reject(error)
})
})
},
// 获取用户信息
getUserInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getUserInfo(state.token)
.then(response => {
const { data } = response
commit('SET_USER_INFO', data)
resolve(data)
})
.catch(error => {
reject(error)
})
})
},
// 登出
logout({ commit }) {
return new Promise((resolve, reject) => {
logout()
.then(() => {
commit('SET_TOKEN', '')
commit('SET_USER_INFO', {})
removeToken()
resolve()
})
.catch(error => {
reject(error)
})
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
在main.js
中引入Vuex:
javascript
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
5. 网络请求封装
安装Axios:
bash
npm install axios -S
创建src/utils/request.js
:
javascript
import axios from 'axios'
import { Toast } from 'vant'
import store from '@/store'
import { getToken } from '@/utils/auth'
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 15000
})
// 请求拦截器
service.interceptors.request.use(
config => {
// 如果存在token,请求携带token
if (store.state.user.token) {
config.headers['Authorization'] = `Bearer ${getToken()}`
}
return config
},
error => {
console.log(error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
// 根据自定义错误码处理错误
if (res.code !== 200) {
Toast.fail(res.message || '请求失败')
// 401: 未登录或token过期
if (res.code === 401) {
store.dispatch('user/logout').then(() => {
location.reload()
})
}
return Promise.reject(new Error(res.message || '请求失败'))
} else {
return res
}
},
error => {
console.log('请求错误', error)
// 处理网络错误
let message = '请求失败'
if (error.response) {
switch (error.response.status) {
case 400:
message = '请求错误'
break
case 401:
message = '未授权,请登录'
store.dispatch('user/logout').then(() => {
location.reload()
})
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求地址不存在'
break
case 500:
message = '服务器内部错误'
break
default:
message = `连接错误${error.response.status}`
}
} else {
if (error.message.includes('timeout')) {
message = '请求超时'
}
}
Toast.fail(message)
return Promise.reject(error)
}
)
export default service
6. API模块封装
创建src/api/user.js
:
javascript
import request from '@/utils/request'
// 用户登录
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}
// 获取用户信息
export function getUserInfo() {
return request({
url: '/user/info',
method: 'get'
})
}
// 用户登出
export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
}
创建src/api/common.js
:
javascript
import request from '@/utils/request'
// 获取首页数据
export function getHomeData() {
return request({
url: '/home/data',
method: 'get'
})
}
// 上传文件
export function uploadFile(data) {
return request({
url: '/upload',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data
})
}
7. 工具函数模块
创建src/utils/auth.js
:
javascript
import Cookies from 'js-cookie'
const TokenKey = 'mobile_app_token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
创建src/utils/validate.js
:
javascript
// 验证手机号
export function validatePhone(phone) {
const reg = /^1[3-9]\d{9}$/
return reg.test(phone)
}
// 验证邮箱
export function validateEmail(email) {
const reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
return reg.test(email)
}
// 验证身份证
export function validateIdCard(idCard) {
const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
return reg.test(idCard)
}
创建src/utils/storage.js
:
javascript
// localStorage封装
export const localStorage = {
set(key, value) {
if (typeof value === 'object') {
value = JSON.stringify(value)
}
window.localStorage.setItem(key, value)
},
get(key) {
const value = window.localStorage.getItem(key)
try {
return JSON.parse(value)
} catch (e) {
return value
}
},
remove(key) {
window.localStorage.removeItem(key)
},
clear() {
window.localStorage.clear()
}
}
// sessionStorage封装
export const sessionStorage = {
set(key, value) {
if (typeof value === 'object') {
value = JSON.stringify(value)
}
window.sessionStorage.setItem(key, value)
},
get(key) {
const value = window.sessionStorage.getItem(key)
try {
return JSON.parse(value)
} catch (e) {
return value
}
},
remove(key) {
window.sessionStorage.removeItem(key)
},
clear() {
window.sessionStorage.clear()
}
}