Getting Started: Build a vue3+webpack practical project template from scratch

Build a vue3 + webpack5 + element-plus basic template (vue3 + webpack5 configure the project from scratch).
The structure of this project can be used as the basic structure of a practical project to build and learn. It is more suitable for practice by friends who have just finished learning Vue and have no practical project experience.

project address:

GitHub:https://github.com/with-the-winds/vue3-vuecli-template

gitee: https://gitee.com/with_the_winds/vue3-vuecli-template

Preface

This project is a vue3 project created through vue-cli. The basic creation ideas are all projects based on the ruoyi framework. In fact, the vue-admin-template and ruoyi frameworks are similar, because these two projects only have usage documents and no documentation ideas for construction, so I wrote the construction ideas myself.

So why not just use the project? It’s because I think the template for this project is too huge (although you can delete the ones you don’t need directly, but it’s a bit troublesome), so I copied it and wrote a template myself (writing it myself is also more conducive to my own understanding and use).

environment

node: v16.16.0

npm: v8.11.0

vue3 + webpack5 + vue-router4 + vuex4

Use directly

Download the code through git clone or directly download the zip package.

// gitee 地址
git clone https://gitee.com/with_the_winds/vue3-vuecli-template.git
// github 地址
git clone https://github.com/with-the-winds/vue3-vuecli-template.git

Project setup

cd vue3-vuecli-template
npm install

Compiles and hot-reloads for development

npm run serve

Compiles and minifies for production

npm run build

Customize configuration

See Configuration Reference.

Building process

The building process is mainly to let beginners know how the template is built and to facilitate access to information.

1. Create a project through vue-cli

vue create own-vue3-vuecli-template

Installation options:
Choose to install vuex, vue-router, and sass. These will be installed by default and will not be installed again.

2. Create environment variables

Create two files, .env.development and .env.production, in the root directory (under the own-vue3-vuecli-template file) to store the environment variable values ​​of the development environment and production environment.

.env.development:

# 开发环境配置
NODE_ENV = 'development'

# 页面标题
VUE_APP_TITLE = '页面标题'

# 开发环境/重写路径(公共路径)
VUE_APP_BASE_URL = '/dev-api'

# 百度地图KEY
VUE_APP_BAIDU_MAP_KEY = ''

.env.production:

# 生产环境配置
NODE_ENV = 'production'

# 页面标题
VUE_APP_TITLE = '页面标题'

# 生产环境/重写路径(公共路径)
VUE_APP_BASE_URL = '/prod-api'

# 百度地图KEY
VUE_APP_BAIDU_MAP_KEY = ''

You can modify it yourself wherever you want to adjust it later.

3. Install element-plus

You can also use other component libraries, here element-plus is used.

npm install element-plus --save

After the installation is complete, there will be no global reference here, and the automatic import method will be used. At this time, you need to install unplugin-vue-components and unplugin-auto-import plugin.

npm install -D unplugin-vue-components unplugin-auto-import

Then insert the following code into the Webpack configuration file:
vue.config.js:

const {
    
     defineConfig } = require('@vue/cli-service')

const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const {
    
     ElementPlusResolver } = require('unplugin-vue-components/resolvers')

module.exports = defineConfig({
    
    
  transpileDependencies: true,
  // 和webpapck属性完全一致,最后会进行合并
  configureWebpack:{
    
    
    //配置webpack自动按需引入element-plus
    plugins: [
      AutoImport({
    
    
        resolvers: [ElementPlusResolver()],
      }),
      Components({
    
    
        resolvers: [ElementPlusResolver()],
      }),
    ],
  }
})

Example:
HomeView.vue:

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    <el-button type="success">按钮</el-button>
  </div>
</template>

image.png
You can see that it can be used normally without introducing element-plus.

4. Install axios

1. Install axios to implement network requests

npm install axios --save

2. Encapsulate axios

Create a utils folder in the src directory to store commonly used js tool files. And create a new request.js file under the utils folder.

2.1 First create the basic structure of axios

request.js:

import axios from 'axios'

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
    
    
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 10000
})

// request拦截器
service.interceptors.request.use(config => {
    
    
  // 请求之前要处理的地方
  
  return config
}, error => {
    
    
    console.log(error)
    Promise.reject(error)
})

// 响应拦截器
service.interceptors.response.use(res => {
    
    
  // 响应成功返回要处理的地方

  return res
  },
  error => {
    
    
    // 响应失败要处理的地方
    console.log('err' + error)
    let {
    
     message } = error;

    return Promise.reject(error)
  }
)

export default service
2.2 Install and package js-cookie

Token verification is generally required when logging in, so you need to use a cookie to store the token. Of course, it can also be stored in localStorage or sessionStorage (usually in a cookie).
Installation:

npm install js-cookie --save

Packaging:
Create an auth.js file in the utils folder and copy the following code:
auth.js:

import Cookies from 'js-cookie'

const TokenKey = 'Admin-Token'

export function getToken() {
    
    
  return Cookies.get(TokenKey)
}

export function setToken(token) {
    
    
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
    
    
  return Cookies.remove(TokenKey)
}

2.3 Improve the structure of axios

Improve request.js to make the encapsulated axios function richer. Implemented functions:

1. Set the token option when requesting, because some interfaces do not necessarily require token;

2. Request an error report;

3. The response is returned successfully and the response error is reported (the code returned in the response is connected to the backend based on the actual project).

Create the errorCode.js file in the utils folder to store the message value of the error code returned in response (according to the actual project).
errorCode.js:

export default {
    
    
  '401': '认证失败,无法访问系统资源',
  '403': '当前操作没有权限',
  '404': '访问资源不存在',
  'default': '系统未知错误,请反馈给管理员'
}

Improve the request.js file.
request.js:

import axios from 'axios'
import {
    
     getToken } from './auth'
import errorCode from './errorCode'
import {
    
     ElMessage, ElMessageBox, ElNotification } from 'element-plus';

// 是否显示重新登录
export let isRelogin = {
    
     show: false };

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
    
    
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 3000
})

// request拦截器
service.interceptors.request.use(config => {
    
    
  // 是否需要设置 token (默认是存在的,只有传值 false 的时候是不需要token)
  const isToken = (config.headers || {
    
    }).isToken === false
  if (getToken() && !isToken) {
    
    
    config.headers['Authorization'] = 'Bearer' + getToken() // 让每个请求携带 token 根据实际情况修改
  }
  return config
}, error => {
    
    
    console.log(error)
    Promise.reject(error)
})

// 响应拦截器
service.interceptors.response.use(
  res => {
    
    
    // 未设置状态码则默认成功状态
    const code = res.data.code || 200;
    // 获取错误信息
    const msg = errorCode[code] || res.data.msg || errorCode['default']
    // 二进制数据则直接返回
    if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {
    
    
      return res.data
    }
    if (code === 401) {
    
    
      if (!isRelogin.show) {
    
    
        isRelogin.show = true;
        ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录','系统提示', {
    
    
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
    
    
          isRelogin.show = false;
          // 调用退出登录接口...
        }).catch(() => {
    
    
          isRelogin.show = false
        })
      }
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
    } else if (code === 500) {
    
    
      ElMessage({
    
     message: msg, type: 'error' })
      return Promise.reject(new Error(msg))
    } else if (code !== 200) {
    
    
      ElNotification({
    
     type: 'error', title: msg})
      return Promise.reject('error')
    } else {
    
    
      return res.data
    }
  },
  error => {
    
    
    // 响应失败要处理的地方
    console.log('err' + error)
    let {
    
     message } = error;
    if (message == "Network Error") {
    
    
      message = "后端接口连接异常";
    } else if (message.includes("timeout")) {
    
    
      message = "系统接口请求超时";
    } else if (message.includes("Request failed with status code")) {
    
    
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }
    ElMessage({
    
     message: message, type: 'error', duration: 3 * 1000 })
    return Promise.reject(error)
  }
)

export default service

As you can see from the above, if a 401 is returned during a network request and a re-login prompt is made, the logout interface here will log out through the state management store. Because the store has not actually written yet, it is temporarily left empty. The location will be added later.

5. Create api using encapsulated request.js

The above axios has now been encapsulated, and now you should create an interface to see how to use the encapsulated axios.

Create an api folder under the src folder, and then create a login.js file under the api folder to store login and other related interfaces.

login.js:

import request from '../utils/request.js'

// 登录
export function login(data) {
    
    
  return request({
    
    
    url: '/login',
    headers: {
    
    
      isToken: false
    },
    method: 'post',
    data: data
  })
}

// 获取用户详细信息
export function getInfo() {
    
    
  return request({
    
    
    url: '/getInfo',
    method: 'get'
  })
}

// 退出登录
export function logout() {
    
    
  return request({
    
    
    url: '/logout',
    method: 'post'
  })
}

This way you can use the interface on other pages.

6. Configure vue.config.js

If the created interface is run in a development environment, you need to set up the proxy server devServer. In the production environment, it is usually configured with nginx, so configure the proxy server under the vue.config.js file, and configure other options by the way. .

For the default configuration and related configuration information of webpack in vue, you can read this articlehttps://juejin.cn/post/6886698055685373965#heading-3 It is better to eat it together with official documents! ! !

Install compression-webpack-plugin plug-in for gzip compression and packaging.

npm install compression-webpack-plugin --save-dev

The complete configuration is as follows (including the configuration of the gzip plug-in and the proxy server):

vue.config.js:

'use strict'
const path = require('path')

function resolve(dir) {
    
    
  return path.join(__dirname, dir)
}

const {
    
     defineConfig } = require('@vue/cli-service')

const name = process.env.VUE_APP_TITLE || '网页标题' // 网页标题

const port = process.env.port || process.env.npm_config_port || 8080 // 端口

// element-plus 按需导入自动导入的插件
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const {
    
     ElementPlusResolver } = require('unplugin-vue-components/resolvers')
// 实现 gzip 压缩打包
const CompressionPlugin = require('compression-webpack-plugin')

module.exports = defineConfig({
    
    
  // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
  // 例如 https://www.my-app.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径
  // 例如,如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 publicPath 为 /my-app/
  publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
  // 当运行 vue-cli-service build 时生成的生产环境构建文件的目录。注意目标目录的内容在构建之前会被清除 (构建时传入 --no-clean 可关闭该行为)。
  outputDir: 'dist',
  // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录
  assetsDir: 'static',
  // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
  productionSourceMap: false,
  // 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。你可以启用本选项,以避免构建后的代码中出现未转译的第三方依赖(对所有的依赖都进行转译可能会降低构建速度)
  transpileDependencies: false,
  // webpack-dev-server 相关配置
  devServer: {
    
    
    host: '0.0.0.0',
    port: port,
    open: true,
    proxy: {
    
    
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
    
    
        target: `http://localhost:8088`,
        changeOrigin: true,
        pathRewrite: {
    
    
          ['^' + process.env.VUE_APP_BASE_API]: ''
        }
      }
    }
  },
  // 和webpapck属性完全一致,最后会进行合并
  configureWebpack:{
    
    
    name: name,
    resolve: {
    
    
      alias: {
    
    
        '@': resolve('src')
      }
    },
    //配置webpack自动按需引入element-plus
    plugins: [
      AutoImport({
    
    
        resolvers: [ElementPlusResolver()],
      }),
      Components({
    
    
        resolvers: [ElementPlusResolver()],
      }),
      // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
      new CompressionPlugin({
    
    
        test: /\.(js|css|html)?$/i,     // 压缩文件格式
        filename: '[path][base].gz',   // 压缩后的文件名
        algorithm: 'gzip',              // 使用gzip压缩
        threshold: 10240,               // 最小文件开启压缩
        minRatio: 0.8                   // 压缩率小于1才会压缩
      })
    ],
  }
})

7. Build a store

To modularize status management, generally templates such as vue-admin-template and ruoyi-vue will add mobile device judgment, but I am not very good at it, so I will not add it here.

Create modules folder

Create a modules folder under the store folder to store modules. Then create a user.js file under the modules folder to store user-related information.

Implement user.js module

The idea of ​​​​implementing this file:

1. Store relevant information after the user logs in (generally speaking, store tokens, role information, and button permission information, but the specifics will depend on the actual project);

2. Clear user related information when logging out.

user.js:

import {
    
     login, logout, getInfo } from "@/api/login";
import {
    
     getToken, setToken, removeToken } from "@/utils/auth";

const user = {
    
    
  state: {
    
    
    token: getToken(),
    name: '',
    roles: [],
    permissions: []
  },
  mutations: {
    
    
    SET_TOKEN: (state, token) => {
    
    
      state.token = token
    },
    SET_NAME: (state, name) => {
    
    
      state.name = name
    },
    SET_ROLES: (state, roles) => {
    
    
      state.roles = roles
    },
    SET_PERMISSIONS: (state, permissions) => {
    
    
      state.permissions = permissions
    }
  },
  actions: {
    
    
    // 登录
    Login({
     
      commit }, userInfo) {
    
    
      return new Promise((resolve, reject) => {
    
    
        login(userInfo).then(res => {
    
    
          setToken(res.token)
          commit('SET_TOKEN', res.token)
          resolve()
        }).catch(error => {
    
    
          reject(error)
        })
      })
    },

    // 获取用户信息
    GetInfo({
     
      commit, state }) {
    
    
      return new Promise((resolve, reject) => {
    
    
        getInfo().then(res => {
    
    
          const user = res.user
          if (res.roles && res.roles.length > 0) {
    
     // 验证返回的roles是否是一个非空数组
            commit('SET_ROLES', res.roles)
            commit('SET_PERMISSIONS', res.permissions)
          } else {
    
    
            commit('SET_ROLES', ['ROLE_DEFAULT'])
          }
          commit('SET_NAME', user.userName)
          resolve(res)
        }).catch(error => {
    
    
          reject(error)
        })
      })
    },

    // 退出系统
    LogOut({
     
      commit, state }) {
    
    
      return new Promise((resolve, reject) => {
    
    
        logout().then(() => {
    
    
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          commit('SET_PERMISSIONS', [])
          removeToken()
          resolve()
        }).catch(error => {
    
    
          reject(error)
        })
      })
    }
  }
}

export default user

Introduce user.js module

Introduce the user.js file into the index.js file under the store folder.

index.js:

import {
    
     createStore } from 'vuex'
import user from './modules/user'

export default createStore({
    
    
  state: {
    
    
  },
  getters: {
    
    
  },
  mutations: {
    
    
  },
  actions: {
    
    
  },
  modules: {
    
    
    user
  }
})

Handle the legacy logout function of request.js

In the request.js file, there was a 401 status code that previously called the function of logging out of the login interface but was not implemented. Now it is complete.

Import the store file:

import store from '@/store';

Add logout interface:

if (!isRelogin.show) {
    
    
  isRelogin.show = true;
  ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录','系统提示', {
    
    
    confirmButtonText: '重新登录',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    
    
    isRelogin.show = false;
    store.dispatch('LogOut').then(() => {
    
    
      location.href = '/index';
    })
  }).catch(() => {
    
    
    isRelogin.show = false
  })
}

8. Add permission.js

Create the permission.js file under the src folder:

The main function of this file is to control routing through routing navigation guards:

1. When the token disappears, you cannot jump to other pages and must return to the login page;

2. After clicking login, you cannot return to the login page through the back button (this is not necessary and depends on the specific implementation, the other three are mandatory);

3. After clicking to log out, you will automatically jump to the login page. You cannot also enter other pages through the back button after logging in;

4. Since page refresh will reset the store, it is necessary to implement vuex persistence (of course there is a plug-in for vuex persistence, but I have not used it here).

Install plug-innprogressUsed to control the display of the progress bar:

npm i nprogress -S

permission.js:

import NProgress from "nprogress";
import 'nprogress/nprogress.css'
import router from "./router";
import store from "./store";
import {
    
     ElMessage } from "element-plus";
import {
    
     getToken } from "./utils/auth";
import {
    
     isRelogin } from "./utils/request";

NProgress.configure({
    
     showSpinner: false }) // 关闭加载微调器

const whiteList = ['/login'] // 设置白名单,用于任何人可访问

// 钩子函数
router.beforeEach((to, from, next) => {
    
    
  NProgress.start()
  if (getToken()) {
    
    
    // to.meta.title && store.dispatch('settings/setTitle', to.meta.title) // 用于设置title
    /* has token*/
    if (to.path === '/login') {
    
    
      next({
    
     path: '/' })
      NProgress.done()
    } else {
    
    
      if (store.getters.roles.length === 0) {
    
    
        isRelogin.show = true
        // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(() => {
    
    
          isRelogin.show = false
          // 用于生成路由
          // store.dispatch('GenerateRoutes').then(accessRoutes => {
    
    
          //   // 根据roles权限生成可访问的路由表
          //   router.addRoutes(accessRoutes) // 动态添加可访问路由表
          //   next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          // })
        }).catch(err => {
    
    
            store.dispatch('LogOut').then(() => {
    
    
              ElMessage({
    
     type: 'error', message: err })
              next({
    
     path: '/' })
            })
          })
      } else {
    
    
        next()
      }
    }
  } else {
    
    
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
    
    
      // 在免登录白名单,直接进入
      next()
    } else {
    
    
      next(`/login?redirect=${
      
      to.fullPath}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})

router.afterEach(() => {
    
    
  NProgress.done()
})

Currently, the redirected pages are all login pages, so modify the routing configuration now.

Note: I have commented two places here, one is the part that generates the route, and the other is the part that sets the page title. The part of generating routes is used to dynamically generate routes, instead of placing all page routes statically in the router.js page and passing them through the back-end interface (please write this part according to the actual project interface).

After creation, it is introduced into the main.js file to control the permissions of the page.

main.js:

import {
    
     createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import './permission' // permission control

createApp(App).use(store).use(router).mount('#app')

9. Build routing page

Pages are divided into public pages and dynamically loaded pages. Public pages generally include login pages and 404 pages.

Create login page

Delete the original AboutView.vue file and HomeView.vue file. Pay attention to remove the link in the APP.vue file, leaving only the link. Then create a login folder under the views folder, and create an index.vue file under the login folder.

login/index.vue:

<template>
  <div class="login">
    <el-form ref="loginRef" :model="loginForm" :rules="loginRules" size="small" class="login-form">
      <title>登录表单</title>
      <el-form-item prop="username" label="账号:">
        <el-input 
          v-model="loginForm.username" 
          type="text" 
          placeholder="账号">
        </el-input>
      </el-form-item>
      <el-form-item prop="password" label="密码:">
        <el-input 
          v-model="loginForm.password" 
          type="password" 
          placeholder="密码" 
          @keyup.enter="handleLogin">
        </el-input>
      </el-form-item>
      <el-form-item style="width: 100%;">
        <el-button 
          :loading="loading" 
          type="primary" 
          @click.prevent="handleLogin"
        >
          <span v-if="!loading">登录</span>
          <span v-else>登 录 中 ...</span>
        </el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import store from '@/store'

export default {
    
    
  data() {
    
    
    return {
    
    
      loginForm: {
    
    
        username: '',
        password: ''
      },
      loginRules: {
    
    
        username: [{
    
     required: true, trigger: 'blur', message: '请输入您的账号' }],
        password: [{
    
     required: true, trigger: 'blur', message: '请输入您的密码' }]
      },
      loading: false
    }
  },
  methods: {
    
    
    handleLogin() {
    
    
      this.$refs.loginRef.validate(valid => {
    
    
        if (valid) {
    
    
          this.loading = true
          store.dispatch('user/Login', this.loginForm).then(() => {
    
    
            this.$router.push({
    
     path: '/home' })
          })
        }
      })
    }
  }
}
</script>

<style lang="scss" scoped>

</style>

There is no style written here, just simply build the page so that the functions can run.

Create a 404 page

Create an error folder under the view folder, and create a 404.vue file under the error folder.
There are two 404 images used here, respectively under the assets/images/base folder.

error/404.vue:

<template>
  <div class="wscn-http404-container">
    <div class="wscn-http404">
      <div class="pic-404">
        <img class="pic-404__parent" src="@/assets/images/base/404.png" alt="404">
        <img class="pic-404__child left" src="@/assets/images/base/404_cloud.png" alt="404">
        <img class="pic-404__child mid" src="@/assets/images/base/404_cloud.png" alt="404">
        <img class="pic-404__child right" src="@/assets/images/base/404_cloud.png" alt="404">
      </div>
      <div class="bullshit">
        <div class="bullshit__oops">
          404错误!
        </div>
        <div class="bullshit__headline">
          {
    
    {
    
     message }}
        </div>
        <div class="bullshit__info">
          对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。
        </div>
        <router-link to="/" class="bullshit__return-home">
          返回首页
        </router-link>
      </div>
    </div>
  </div>
</template>

<script>

export default {
    
    
  name: 'Page404',
  computed: {
    
    
    message() {
    
    
      return '找不到网页!'
    }
  }
}
</script>

<style lang="scss" scoped>
.wscn-http404-container{
    
    
  transform: translate(-50%,-50%);
  position: absolute;
  top: 40%;
  left: 50%;
}
.wscn-http404 {
    
    
  position: relative;
  width: 1200px;
  padding: 0 50px;
  overflow: hidden;
  .pic-404 {
    
    
    position: relative;
    float: left;
    width: 600px;
    overflow: hidden;
    &__parent {
    
    
      width: 100%;
    }
    &__child {
    
    
      position: absolute;
      &.left {
    
    
        width: 80px;
        top: 17px;
        left: 220px;
        opacity: 0;
        animation-name: cloudLeft;
        animation-duration: 2s;
        animation-timing-function: linear;
        animation-fill-mode: forwards;
        animation-delay: 1s;
      }
      &.mid {
    
    
        width: 46px;
        top: 10px;
        left: 420px;
        opacity: 0;
        animation-name: cloudMid;
        animation-duration: 2s;
        animation-timing-function: linear;
        animation-fill-mode: forwards;
        animation-delay: 1.2s;
      }
      &.right {
    
    
        width: 62px;
        top: 100px;
        left: 500px;
        opacity: 0;
        animation-name: cloudRight;
        animation-duration: 2s;
        animation-timing-function: linear;
        animation-fill-mode: forwards;
        animation-delay: 1s;
      }
      @keyframes cloudLeft {
    
    
        0% {
    
    
          top: 17px;
          left: 220px;
          opacity: 0;
        }
        20% {
    
    
          top: 33px;
          left: 188px;
          opacity: 1;
        }
        80% {
    
    
          top: 81px;
          left: 92px;
          opacity: 1;
        }
        100% {
    
    
          top: 97px;
          left: 60px;
          opacity: 0;
        }
      }
      @keyframes cloudMid {
    
    
        0% {
    
    
          top: 10px;
          left: 420px;
          opacity: 0;
        }
        20% {
    
    
          top: 40px;
          left: 360px;
          opacity: 1;
        }
        70% {
    
    
          top: 130px;
          left: 180px;
          opacity: 1;
        }
        100% {
    
    
          top: 160px;
          left: 120px;
          opacity: 0;
        }
      }
      @keyframes cloudRight {
    
    
        0% {
    
    
          top: 100px;
          left: 500px;
          opacity: 0;
        }
        20% {
    
    
          top: 120px;
          left: 460px;
          opacity: 1;
        }
        80% {
    
    
          top: 180px;
          left: 340px;
          opacity: 1;
        }
        100% {
    
    
          top: 200px;
          left: 300px;
          opacity: 0;
        }
      }
    }
  }
  .bullshit {
    
    
    position: relative;
    float: left;
    width: 300px;
    padding: 30px 0;
    overflow: hidden;
    &__oops {
    
    
      font-size: 32px;
      font-weight: bold;
      line-height: 40px;
      color: #1482f0;
      opacity: 0;
      margin-bottom: 20px;
      animation-name: slideUp;
      animation-duration: 0.5s;
      animation-fill-mode: forwards;
    }
    &__headline {
    
    
      font-size: 20px;
      line-height: 24px;
      color: #222;
      font-weight: bold;
      opacity: 0;
      margin-bottom: 10px;
      animation-name: slideUp;
      animation-duration: 0.5s;
      animation-delay: 0.1s;
      animation-fill-mode: forwards;
    }
    &__info {
    
    
      font-size: 13px;
      line-height: 21px;
      color: grey;
      opacity: 0;
      margin-bottom: 30px;
      animation-name: slideUp;
      animation-duration: 0.5s;
      animation-delay: 0.2s;
      animation-fill-mode: forwards;
    }
    &__return-home {
    
    
      display: block;
      float: left;
      width: 110px;
      height: 36px;
      background: #1482f0;
      border-radius: 100px;
      text-align: center;
      color: #ffffff;
      opacity: 0;
      font-size: 14px;
      line-height: 36px;
      cursor: pointer;
      animation-name: slideUp;
      animation-duration: 0.5s;
      animation-delay: 0.3s;
      animation-fill-mode: forwards;
    }
    @keyframes slideUp {
    
    
      0% {
    
    
        transform: translateY(60px);
        opacity: 0;
      }
      100% {
    
    
        transform: translateY(0);
        opacity: 1;
      }
    }
  }
}
</style>

Rewrite router file

router/index.vue:

import {
    
     createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    
    
    path: '/login',
    name: 'login',
    component: () => import('../views/login/index.vue')
  },
  {
    
    
    path: '/',
    redirect: '/login'
  },
  {
    
    
    path: '/404',
    component: () => import('../views/error/404.vue')
  },
  {
    
    
    path: '/:catchAll(.*)',
    redirect: '/404'
  }
]

const router = createRouter({
    
    
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

Note: The 404 page must be placed at the end. Since it is a static route, I put it directly here. The correct way should be to add the route through dynamic routing and finally add path: '/:catchAll(.* )' this routing page. This is because Vue’s routing matching rules are traversed one by one. If this path is placed in the middle, then subsequent routing pages will not be matched and will go directly to the 404 page.

Note: At this time, remember to add the ‘/404’ path to the whitelist of the permission.js file, otherwise it will be redirected (you must understand the role of peimission.js).

permission.js:

const whiteList = ['/login','/404'] // 设置白名单,用于任何人可访问

10. Add Layout page

Please write this step according to the actual project, because I write a lot of backend projects here, so after entering the basic login page, there is the left navigation bar or the upper navigation bar, and then the content is displayed on the right or below the navigation bar.
Create a layout folder under the src folder, and then create an index.vue file under the layout folder. This file serves as the entry page after successful login.

layout/index.vue:

<template>
  <div class="app-wrapper">
    <div class="sidebar"></div>
    <div class="app-main">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    

    }
  }
}
</script>

<style lang="scss" scoped>
.app-wrapper {
    
    
  width: 100%;
  height: 100%;
  position: relative;
}
.sidebar {
    
    
  width: 200px;
  height: 100%;
  background-color: red;
  position: absolute;
  top: 0;
  left: 0;
}
.app-main {
    
    
  width: calc(100% - 200px);
  height: 100%;
  background-color: rgb(68, 150, 222);
  position: absolute;
  top: 0;
  left: 200px;
}
</style>

It is divided into left and right layouts. Simply use red to represent the sidebar and blue to represent the content area. Now the basic structure is set up like this. If you need to add other functions, you can continue to add them, such as component encapsulation of the sidebar.

Now create a home folder under the views folder, and then create an index.vue file under the home folder. This file is used to test whether the sidebar and content area just created work.

home/index.vue:

<template>
  <div>
    <h1>这是 Home 页面</h1>
  </div>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    
      
    }
  }
}
</script>

Note: Modify the style of APP.vue, otherwise the current html will have no height:

APP.view:

<style lang="scss">
html {
  width: 100%;
  height: 100%;
}
body {
  margin: 0;
  width: 100%;
  height: 100%;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  width: 100%;
  height: 100%;
}
</style>

Add route:
Configure the newly added home page in the index.js file under the router folder:

router/index.js:

import {
    
     createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    
    
    path: '/login',
    name: 'login',
    component: () => import('../views/login/index.vue')
  },
  {
    
    
    path: '/',
    redirect: '/login'
  },
  {
    
    
    path: '/layout',
    name: 'Layout',
    component: () => import('../layout/index.vue'),
    redirect: '/home',
    children: [{
    
    
      path: '/home',
      name: 'Home',
      component: () => import('../views/home/index.vue')
    }]
  },
  {
    
    
    path: '/404',
    component: () => import('../views/error/404.vue')
  },
  {
    
    
    path: '/:catchAll(.*)',
    redirect: '/404'
  }
]

const router = createRouter({
    
    
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

Note: Don’t forget to add a whitelist in the permission.js file, otherwise the permission will not be able to access the newly added page, because there is no actual login interface, so it will always be redirected.

permission.js:

const whiteList = ['/login','/404','/layout', '/home'] // 设置白名单,用于任何人可访问

Effect:
image.png
In this way, the basic project structure is completed. When developing the actual project, remember to set the whitelist page to the page you want.

In the above steps, the content obtained by dynamic routing is not added. It is usually placed behind the interface for obtaining user information, and then added to the route through the addRoute() method.

end

The project is basically finished here. Some functions should be added later based on the actual project, such as permission acquisition, sidebar encapsulation, prevention of repeated requests, etc., but it should not be updated. The documentation is up (if possible, it will be written in the supplement later), so there may be some discrepancies between the build documentation and the actual pulled projects.

Replenish

Guess you like

Origin blog.csdn.net/qq_43651168/article/details/131301179