Vue3-项目创建及初始化

1,025 阅读7分钟

项目技术栈(习惯用yarn)

  • vue3 + 组合式Api+pinia

  • axios

    yarn add axios

  • 持久化状态(token)插件pinia-plugin-persistedstate

    yarn add pinia-plugin-persistedstate

  • 加载进度条

    yarn add nprogress

  • ant-design-vue组件库

    yarn add ant-design-vue

  • less

    yarn add less

采用vite进行vue3项目的创建。

1. 创建项目

 yarn create vue  
 # yarn create vue 是固定的 hrsaas是项目名称
  • 选择工具

image.png

  • 安装依赖、启动
 cd hrsaas # 切换到对应目录
 yarn # 安装依赖
 yarn dev #启动服务
  • 初始化git仓库
 git init 
 git add .
 git commit -m "初始化项目"
 git remote add origin <远程仓库地址>
 git push -u origin master 

2. 配置eslint相关配置和vscode的配置

创建项目时,我们选择了esliint, 但是有一些配置阻碍我们愉快的创建组件和开发
  • 如下的eslint配置写入到项目根目录下的 .eslinttrc.cjs
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')

module.exports = {
  root: true,
  extends: [
    'plugin:vue/vue3-essential',
    'eslint:recommended',
    '@vue/eslint-config-prettier'
    // 当前项目不支持 ts
    // '@vue/eslint-config-typescript/recommended'
  ],
  env: {
    'vue/setup-compiler-macros': true
  },
  rules: {
    'vue/multi-word-component-names': [
      'error',
      {
        ignores: ['index']
      }
    ],
    'vue/no-setup-props-destructure': ['off'],
    // 支持对 defineProps 解构
    'vue/no-mutating-props': ['off']
  }
}
  • settings.json配置,按需配置
{
  // ts文件格式化
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  // vue文件格式化
  "[vue]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  // 保存时格式化
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll": true,
    "source.fixAll.eslint": true
  },
  // 开启 eslint 格式化
  "eslint.enable": true,
  "eslint.run": "onType",
  "eslint.options": {
    "extensions": [
      ".js",
      ".ts",
      ".vue",
      ".jsx",
      ".tsx"
    ]
  },
  // 操作时作为单词分隔符的字符
  "editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?",
  // 一个制表符等于的空格数
  "editor.tabSize": 2,
  // 文件行尾符号
  "files.eol": "\n",
  // 是否以紧凑形式展示文件夹
  "explorer.compactFolders": false,
  //vscode文件图标
  "workbench.iconTheme": "material-icon-theme",
  "window.zoomLevel": 1,
  "liveServer.settings.donotShowInfoMsg": true,
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[jsonc]": {
    "editor.defaultFormatter": "vscode.json-language-features"
  },
  //有时候我们的路径提示会失败,识别不出来@代表的是src,所以要加这个
  "path-intellisense.mappings": {
    "@": "${workspaceFolder}/src",
  },
  // 自动保存
  // "files.autoSave": "afterDelay",
  // 鼠标滚动放大缩小字体
  "editor.mouseWheelZoom": true,
}

3.调整项目目录

image.png

  • 调整完目录后、调整如下文件:
    • 调整App.vue
    • 调整router/index.js
    • 删除views/AboutView.vue views/HomeView.vue
    • 新建views/login/index.vue views/layout/index.vue
    • 删除assets下的内容, 删除components下所有的组件

4.ant-design-vue主题定制

  ant-desigin-vue是蚂蚁金服提供的一套基于Vue的pc组件库,支持最新版的Vue3.x是主流的应用UI框架
  1. 在main.js中进行全局的导入和样式的引入、使用app实例对组件库进行注册
import AntD from 'ant-design-vue'
import 'ant-design-vue/dist/antd.less'
app.use(AntD) // 全局注册antD
  1. 在vite.config.js中配置主题
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    proxy: {
      '/api': {
        changeOrigin: true,
        target: 'https://xxxxxx/api',
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    },
    port: 5173
  },
  // 配置主题
  css: {
    preprocessorOptions: {
      less: {
        modifyVars: {
          'primary-color': '#7094ff' // 配置主题的主色调
        },
        javascriptEnabled: true
      }
    }
  }
})
  • 修改完毕之后,自动生效

5. 完成登录表单的设计- ant-design-vue表单

  • 基于组件库的规范要求,我们可以设计如下的表单结构
     <a-form :model="loginForm" autocomplete="off">
          <a-form-item name="mobile" :rules="[{
            required: true, message: '手机号不能为空',
            trigger: ['change', 'blur']
          }, { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', , trigger: ['change', 'blur'] }
          ]">
            <!-- 手机号 -->
            <!-- v-model原理实现 v2-v3的变化  v-model :value-> modelValue -->
            <!-- v-model实现原理 :value  @input -->
            <a-input size="large" v-model:value="loginForm.mobile"></a-input>
          </a-form-item>
          <a-form-item name="password" :rules="[{
            required: true, message: '密码不能为空', trigger: ['change', 'blur']
          }]">
            <a-input-password size="large" v-model:value="loginForm.password"></a-input-password>
          </a-form-item>
          <a-form-item name="isAgree">
            <a-checkbox v-model:checked="loginForm.isAgree">用户平台使用协议</a-checkbox>
          </a-form-item>
          <a-form-item>
            <a-button size="large" type="primary" block>登录</a-button>
          </a-form-item>
        </a-form>
  • autocomplete属性为自动填充表单数据,我们设置为off
  • rules规则如下 image.png

6.安装请求工具并封装request

import axios from "axios"

// 使用axios创建实例  new Vue()  createApp()

 const serive = axios.create({
  // 初始化参数
 })  // service和axios的功能一摸一样
 // 请求拦截器
serive.interceptors.request.use()
// 响应拦截器
serive.interceptors.response.use()
 export default serive // 导出工具
  • 在这一步的操作中,我们后续可以在这里处理若干的问题,比如token的统一植入,数据的统一处理,异常的统一捕获

7.通过vite配置代理,解决跨域问题

  • 在前后分离模式下,我们是无法在项目中正常访问其他服务的接口的,因为这里形成了跨域,跨域的解决方案在项目中,最简单的方式就是代理
  • image.png
  • 原理:也就是我们通过自己的前端-向自己的后端发出一个请求,让我们自己的后端代替我们发出请求,从而间接拿到我们想要的拿到的数据,因为后端是不受浏览器同源策略的影响的
  1. 配置vite.config.js如下配置:
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  // 在这里配置server 解决跨域
  server: {
    proxy: {
      '/api': {
        changeOrigin: true,
        target: 'https://xxxxxxxxx/api',
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  css: {
    preprocessorOptions: {
      less: {
        modifyVars: {
          'primary-color': '#7094ff' // 配置主题的主色调
        },
        javascriptEnabled: true
      }
    }
  }
})
  1. src/utils/request.js配置:
import axios from "axios"

// 使用axios创建实例  new Vue()  createApp()

 const serive = axios.create({
  // 初始化参数
  // /sys/login => /api/sys/login
  baseURL: '/api' 
 })  // service和axios的功能一摸一样
 // 请求拦截器
serive.interceptors.request.use()
// 响应拦截器
serive.interceptors.response.use()
 export default serive // 导出工具

8.token的处理

  1. pinia- 管理状态的工具
    • 在src/stores 下新建一个token.js
// 专门来管理状态- 状态只有一份
import { defineStore } from "pinia" // 引入定义状态仓库的工具
import { ref } from 'vue'
// 1.仓库的标识 2. 仓库的需要管理的状态
const useToken = defineStore("token", () => {
 // 回调函数  进行返回的状态就是要管理的状态
const token = ref(null) // 需要在token变化的时候 通知组件
// 修改token的方法
const updateToken = (val) => token.value = val
// 删除token的方法
const removeToken = () => token.value = null

return  { token, updateToken, removeToken }
})
// 导出这个方法

export default useToken
  • 通过pinia提供的defineStore的处理,我们就可以实现公共仓库的建立
  • 前端浏览器刷新之后,所有的数据都会重新渲染,token也会随之消失,所以我们需要将公共状态进行可持久化
  1. 安装pinia可持久化插件(pinia-plugin-persistedstate)
    • 在main.js中应用该插件
import { createApp } from 'vue'
import { createPinia } from 'pinia'

import AntDesign from 'ant-design-vue'  // 引入全局包
import 'ant-design-vue/dist/antd.less'  // less - css的预处理器 可以写嵌套语法 可以写变量
import PluginState from 'pinia-plugin-persistedstate'
import App from './App.vue'
import router from './router'

const app = createApp(App)
const piniaApp = createPinia()
piniaApp.use(PluginState) //注册持久化插件
app.use(piniaApp) // 注册pinia
app.use(router)
app.use(AntDesign) // 注册全局组件

app.mount('#app')
  1. 在stores/token.js中设置可持久化标记
// 专门来管理状态- 状态只有一份
import { defineStore } from "pinia" // 引入定义状态仓库的工具
import { ref } from 'vue'
// 1.仓库的标识 2. 仓库的需要管理的状态
const useToken = defineStore("token", () => {
  // 回调函数  进行返回的状态就是要管理的状态
 const token = ref(null) // 需要在token变化的时候 通知组件
 // 修改token的方法
 const updateToken = (val) => token.value = val
 // 删除token的方法
 const removeToken = () => token.value = null

 return  { token, updateToken, removeToken }
}, {
  persist: true // 可持久化的标记- 将我们的数据 持久化到前端缓存中
})
// 导出这个方法

export default useToken

9. 封装请求模块的api-处理响应拦截器

  • 在axios这个工具中,它会将我们的数据默认包裹一层data再返回给我们,所以我们需要处理

10. 登录-存储token-跳转主页

11.基于token权限的导航守卫

  • 我们已经完成了登录的过程,并且存储了token,但是此时主页并没有因为token的有无而被控制访问权限.
  • image.png
  1. 在src下新建permission.js
  • 权限实际上是针对路由控制,操作的实际上是路由
// 做权限控制
import router from '@/router' // 可以不写index.js
import useToken from '@/stores/token'
import nprogress from 'nprogress'
import 'nprogress/nprogress.css' // 引入进度条样式
// import { useRouter } from 'vue-router' 这个方法只能用在组件中

// 前置导航守卫
// 只要是路由发生跳转-就会执行- 跳转之前执行
// to 到哪里去
// from 从哪里来
// next 必须执行的一个函数

const whiteList = ['/login', '/404'] // 登录页 404白名单
router.beforeEach((to, from, next) => {
 nprogress.start() // 开启进度条
 const { token } = useToken()
 if (token) {
   // 有token的情况下
   if (to.path === '/login') {
     // 就是登录页
     next('/') // 跳转到主页
   } else {
     next() // 放行的意思
   }
 } else {
   // 没有token的情况下
   if (whiteList.includes(to.path)) {
     // 在白名单中 直接放行
     next()
   } else {
     next('/login')
   }
 }
})

// 后置导航守卫

router.afterEach(() => nprogress.done()) // 关闭进度条
  • permission.js建好之后,为了让其生效,需要在main.js中引入
  • 为了体现页面进度的效果,我们可以安装一个进度条的修饰器(nprogress)
    • 在前置守卫-开启
    • 在后置后卫-关闭

12. 菜单组件的封装

  • 菜单是根据当前路由中所包含的页面循环出来的,所以我们需要循环当前路由的信息,循环生成若干的菜单
<script setup>
import { computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()  // 获取路由实例
// 计算属性-测算需要展示的路由的信息
const routes = computed(() => {
return router.options.routes.filter((item) => !item.hidden) // 找出所有的hidden为false的路由
})
const getMeta = (obj) => {
// 判断当前有没有子节点  
if (obj.children && obj.children.length) {
  // 如果有子节点 就读取 子节点中第一个的meta属性
  // meta- 路由的元信息-存储信息的地方
  // 有节点
  return obj.children.find((item) => !item.hidden).meta
}
return obj.meta
}
</script>
  • 路由的格式
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/views/layout/index.vue'

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
  {
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/index.vue'),
    hidden: true
  },
  {
    path: '/',
    redirect: '/dashboard',
    component: Layout,
    // 子节点
    children: [
      {
        path: 'dashboard', // 二级路由的地址
        component: () => import('@/views/dashboard/index.vue'),
        // 路由元信息
        meta: {
          title: '数据看板',
          icon: 'HomeOutlined'
        }
      }
    ]
  }
]
})

export default router
  • 我们不想展示某个路由时,就可以设置路由信息下的hidden为true

13. 图标自封装注册

  • ant-design-vue的图标需要单独安装和引入

yarn add @ant-design/icons-vue

  • 在src/components/Icons/index.js中实现所有图标的统一注册
    • app.use(对象), 会调用对象中的install方法
import {
 HomeOutlined,
 PartitionOutlined,
 SettingOutlined,
 TeamOutlined,
 MenuUnfoldOutlined,
 MenuFoldOutlined,
 PoweroffOutlined,
 LockOutlined
} from '@ant-design/icons-vue'

// 以上图标都需要全局注册
const icons = [
 HomeOutlined,
 PartitionOutlined,
 SettingOutlined,
 TeamOutlined,
 MenuUnfoldOutlined,
 MenuFoldOutlined,
 PoweroffOutlined,
 LockOutlined
]
export default {
 install: (app) => icons.forEach((item) => app.component(item.displayName, item))
 // 全局注册引入的所有图标
}
  • 在main.js中统一注册
import Icons from '@/components/Icons'
app.use(Icons)

14.token超时处理

  • token是有有效期的,一般为1个半小时-2个小时,所以当时间到了,我们请求接口的时,后端会返回401状态码,此时要根据状态码进行登出操作
import router from '@/router'

serive.interceptors.response.use(
  (response) => {
    const { success, message, data } = response.data // axios默认加了一层data
    if (success) {
      // 表示执行成功
      return data // 返回需要的业务数据
    }
    // 提示消息
    Msg.error(message)
    // 报错
    return Promise.reject(new Error(message))
  },
  (error) => {
    if (error.response.status === 401) {
      // 此时说明token超时了- 超时和没有token是没有任何区别的
      const { removeToken } = useToken()
      removeToken() // 删除token
      // 回到登录
      router.push('/login')
    }
    return Promise.reject(error)
  }
)