从0到1搭建Vue3+Vant移动端项目(一)

从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()
  }
}
相关推荐
打好高远球6 分钟前
mo契官网建设与SEO实践
前端
神仙别闹12 分钟前
基于Java+MySQL实现(Web)可扩展的程序在线评测系统
java·前端·mysql
心.c26 分钟前
react当中的this指向
前端·javascript·react.js
Java水解33 分钟前
Web API基础
前端
闲鱼不闲34 分钟前
实现iframe重定向通知父级页面跳转
前端
咸鱼青菜好好味35 分钟前
node的项目实战相关-2-前台接口
前端
春秋半夏36 分钟前
音乐播放、歌词滚动
前端·css
Sioncovy40 分钟前
Zustand 源码阅读计划(3)- JS 篇 - Middlewares 中间件逻辑
前端·javascript
bo5210041 分钟前
垃圾回收机制详解
前端
多啦C梦a43 分钟前
🪄 这么优雅?`useContext` + 自定义 Hooks:优雅管理全局状态,从主题切换说起
前端·javascript·react.js