A01-搭建VUE基础项目

🧑‍🎓 个人主页Silence Lamb
📖 本章内容:【搭建VUE基础项目


Silence-Vue v1.0.0

基于VUE前端快速开发框架

在这里插入图片描述

🍎平台简介

star star star star star star


一、创建项目

1.1🥎【创建项目】

  • 🥎 安装脚手架
npm i -g @vue/cli
  • 🥎 初始化图形化页面
vue ui

1.2🥎【目录介绍】

在这里插入图片描述

1.3🥎【文件目录】

  • public文件夹:静态资源
- webpack进行打包的时候会原封不动打包到dist文件夹中
  • pubilc/index.html:是一个模板文件
- 作用是生成项目的入口文件
- webpack打包的js,css也会自动注入到该页面中
- 我们浏览器访问项目的时候就会默认打开生成好的index.html
  • src文件夹(程序员代码文件夹)
- assets: 存放公用的静态资源
- components: 非路由组件(全局组件),其他组件放在views或者pages文件夹中
- App.vue: 唯一的根组件
- main.js: 程序入口文件,最先执行的文件
  • babel.config.js:配置文件(babel相关)
  • package.json:项目的详细信息记录
  • package-lock.json:缓存性文件(各种包的来源)

1.4🥎【代码模板】

  • 首选项/配置用户代码片段
{
  "生成 vue 模板": {
    "prefix": "vue",
    "body": [
      "<template>",
      "<div></div>",
      "</template>",
      "",
      "<script>",
      "//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json文件,图片文件等等)",
      "//例如:import 《组件名称》 from '《组件路径》';",
      "",
      "export default {",
      "//import 引入的组件需要注入到对象中才能使用",
      "components: {},",
      "props: {},",
      "data() {",
      "//这里存放数据",
      "return {",
      "",
      "};",
      "},",
      "//计算属性 类似于 data 概念",
      "computed: {},",
      "//监控 data 中的数据变化",
      "watch: {},",
      "//方法集合",
      "methods: {",
      "",
      "},",
      "//生命周期 - 创建完成(可以访问当前 this 实例)",
      "created() {",
      "",
      "},",
      "//生命周期 - 挂载完成(可以访问 DOM 元素)",
      "mounted() {",
      "",
      "},",
      "beforeCreate() {}, //生命周期 - 创建之前",
      "beforeMount() {}, //生命周期 - 挂载之前",
      "beforeUpdate() {}, //生命周期 - 更新之前",
      "updated() {}, //生命周期 - 更新之后",
      "beforeDestroy() {}, //生命周期 - 销毁之前",
      "destroyed() {}, //生命周期 - 销毁完成",
      "activated() {}, //如果页面有 keep-alive 缓存功能,这个函数会触发",
      "}",
      "</script>",
      "<style lang='scss' scoped>",
      "//@import url($3); 引入公共 css 类",
      "$4",
      "</style>"
    ],
    "description": "生成 vue 模板"
  }
}

二、项目配置

2.1🍁【代码检查】

  • 🍁相关依赖.eslintrc.js
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.10.0",
  • 🍁代码检查.eslintrc.js
/*
 * @Description  : Eslint校验
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
module.exports = {
    root: true,
    env: {
        browser: true,
        node: true,
        es6: true,
    },
    parser: 'vue-eslint-parser',
    parserOptions: {
        parser: '@typescript-eslint/parser',
        ecmaVersion: 2020,
        sourceType: 'module',
        jsxPragma: 'React',
        ecmaFeatures: {
            jsx: true,
        },
    },
    extends: ['prettier', 'plugin:vue/vue3-recommended', 'plugin:prettier/recommended'],
    rules: {
        'no-var': 'error',
        'prettier/prettier': ['error'],
        // 禁止出现console
        'no-console': 'warn',
        // 禁用debugger
        'no-debugger': 'warn',
        // 禁止出现重复的 case 标签
        'no-duplicate-case': 'warn',
        // 禁止出现空语句块
        'no-empty': 'warn',
        // 禁止不必要的括号
        'no-extra-parens': 'off',
        // 禁止对 function 声明重新赋值
        'no-func-assign': 'warn',
        // 禁止在 return、throw、continue 和 break 语句之后出现不可达代码
        'no-unreachable': 'warn',
        // 强制所有控制语句使用一致的括号风格
        curly: 'warn',
        // 要求 switch 语句中有 default 分支
        'default-case': 'warn',
        // 强制尽可能地使用点号
        'dot-notation': 'warn',
        // 要求使用 === 和 !==
        eqeqeq: 'warn',
        // 禁止 if 语句中 return 语句之后有 else 块
        'no-else-return': 'warn',
        // 禁止出现空函数
        'no-empty-function': 'warn',
        // 禁用不必要的嵌套块
        'no-lone-blocks': 'warn',
        // 禁止使用多个空格
        'no-multi-spaces': 'warn',
        // 禁止多次声明同一变量
        'no-redeclare': 'warn',
        // 禁止在 return 语句中使用赋值语句
        'no-return-assign': 'warn',
        // 禁用不必要的 return await
        'no-return-await': 'warn',
        // 禁止自我赋值
        'no-self-assign': 'warn',
        // 禁止自身比较
        'no-self-compare': 'warn',
        // 禁止不必要的 catch 子句
        'no-useless-catch': 'warn',
        // 禁止多余的 return 语句
        'no-useless-return': 'warn',
        // 禁止变量声明与外层作用域的变量同名
        'no-shadow': 'off',
        // 允许delete变量
        'no-delete-var': 'off',
        // 强制数组方括号中使用一致的空格
        'array-bracket-spacing': 'warn',
        // 强制在代码块中使用一致的大括号风格
        'brace-style': 'warn',
        // 强制使用骆驼拼写法命名约定
        camelcase: 'warn',
        // 强制使用一致的缩进
        indent: 'off',
        // 强制在 JSX 属性中一致地使用双引号或单引号
        // 'jsx-quotes': 'warn',
        // 强制可嵌套的块的最大深度4
        'max-depth': 'warn',
        // 强制最大行数 300
        // "max-lines": ["warn", { "max": 1200 }],
        // 强制函数最大代码行数 50
        // 'max-lines-per-function': ['warn', { max: 70 }],
        // 强制函数块最多允许的的语句数量20
        'max-statements': ['warn', 100],
        // 强制回调函数最大嵌套深度
        'max-nested-callbacks': ['warn', 3],
        // 强制函数定义中最多允许的参数数量
        'max-params': ['warn', 3],
        // 强制每一行中所允许的最大语句数量
        'max-statements-per-line': ['warn', { max: 1 }],
        // 要求方法链中每个调用都有一个换行符
        'newline-per-chained-call': ['warn', { ignoreChainWithDepth: 3 }],
        // 禁止 if 作为唯一的语句出现在 else 语句中
        'no-lonely-if': 'warn',
        // 禁止空格和 tab 的混合缩进
        'no-mixed-spaces-and-tabs': 'warn',
        // 禁止出现多行空行
        'no-multiple-empty-lines': 'warn',
        // 禁止出现;
        semi: ['warn', 'never'],
        // 强制在块之前使用一致的空格
        'space-before-blocks': 'warn',
        // 强制在 function的左括号之前使用一致的空格
        // 'space-before-function-paren': ['warn', 'never'],
        // 强制在圆括号内使用一致的空格
        'space-in-parens': 'warn',
        // 要求操作符周围有空格
        'space-infix-ops': 'warn',
        // 强制在一元操作符前后使用一致的空格
        'space-unary-ops': 'warn',
        // 强制在注释中 // 或 /* 使用一致的空格
        // "spaced-comment": "warn",
        // 强制在 switch 的冒号左右有空格
        'switch-colon-spacing': 'warn',
        // 强制箭头函数的箭头前后使用一致的空格
        'arrow-spacing': 'warn',
        'prefer-const': 'warn',
        'prefer-rest-params': 'warn',
        'no-useless-escape': 'warn',
        'no-irregular-whitespace': 'warn',
        'no-prototype-builtins': 'warn',
        'no-fallthrough': 'warn',
        'no-extra-boolean-cast': 'warn',
        'no-case-declarations': 'warn',
        'no-async-promise-executor': 'warn',
    },
}

🍁 Tips:echo {}> .eslintignore

# eslint 忽略检查 (根据项目需要自行添加)
node_modules
dist

2.2🍁【代码格式】

🍁 Tips:安装 prettier

# 安装 prettier 
yarn add prettier -D
# 安装插件 eslint-config-prettier
yarn add eslint-config-prettier -D

🍁 Tips:echo {} > .prettierrc.js

/*
 * @Description  :
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
module.exports = {
    printWidth: 150,
    // 指定每个缩进级别的空格数
    tabWidth: 4,
    // 使用制表符而不是空格缩进行
    useTabs: false,
    // 在语句末尾打印分号
    semi: false,
    // 使用单引号而不是双引号
    singleQuote: true,
    // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
    quoteProps: 'as-needed',
    // 在JSX中使用单引号而不是双引号
    jsxSingleQuote: false,
    // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
    trailingComma: 'es5',
    // 在对象文字中的括号之间打印空格
    bracketSpacing: true,
    // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
    arrowParens: 'always',
    // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
    rangeStart: 0,
    rangeEnd: Infinity,
    // 指定要使用的解析器,不需要写文件开头的 @prettier
    requirePragma: false,
    // 不需要自动在文件开头插入 @prettier
    insertPragma: false,
    // 使用默认的折行标准 always\never\preserve
    proseWrap: 'preserve',
    // 指定HTML文件的全局空格敏感度 css\strict\ignore
    htmlWhitespaceSensitivity: 'css',
    // Vue文件脚本和样式标签缩进
    vueIndentScriptAndStyle: false,
    // 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
    endOfLine: 'lf',
}

🍁 Tips:echo {}> .prettierignore

# 忽略格式化文件 (根据项目需要自行添加)
node_modules
dist

🍁Tips:package.json

"scripts": {
 "lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
 "prettier": "prettier --write ."
},

2.3🍁【环境变量】

  • 🍁开发环境.env.development
# 页面标题
VUE_APP_TITLE=开发环境

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

# 开发环境
VUE_APP_BASE_API='/dev-api'

# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES=true
  • 🍁线上环境.env.production
# 页面标题
VUE_APP_TITLE=线上环境

# 线上环境
VUE_MODE_ENV='production'

# 管理系统/生产环境
VUE_APP_BASE_API='/prod-api'
  • 🍁测试环境.env.staging
# 页面标题
VUE_APP_TITLE=测试环境

# 测试环境
VUE_MODE_ENV='staging'

# 测试环境
VUE_APP_BASE_API='/stage-api'
  • 🍁环境判断相关方法jsconfig.json
/**
 * @Description  : Vue环境工具
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */

export default {
    /**
     * @Description : 是否是开发环境
     * @param mode 模式
     */
    isDev(mode) {
        return mode === 'development'
    },

    /**
     * @Description : 是否是线上环境
     * @param mode 模式
     */
    isProd(mode) {
        return mode === 'production'
    },

    /**
     * @Description : 是否是构建
     * @param common 启动环境
     */
    isBuild(common) {
        return common === 'build'
    },

    /**
     * @Description : 是否是启动服务器
     * @param common 启动环境
     */
    isServer(common) {
        return common === 'server'
    },
}

2.3🍁【别名配置】

  • 🍁别名配置jsconfig.json
{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "#/*": ["config/*"],
            "@api/*": ["src/api/*"],
            "@assets/*": ["src/assets/*"],
            "@components/*": ["src/components/*"],
            "@store/*": ["src/store/*"],
            "@utils/*": ["src/utils/*"],
            "@views/*": ["src/views/*"],
            "@/*": ["src/*"]
        }
    },
    "exclude": ["node_modules", "dist"]
}
  • 🍁别名配置config/resolve/index.js
/*
 * @Description  : 配置别名
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { resolve } from 'path';

function pathResolve(dir) {
    return resolve(process.cwd(), '.', dir);
}
export default {
    // 导入时想要省略的扩展名列表
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
    fallback: { path: require.resolve('path-browserify') },
    alias: {
        '@': pathResolve('src'),
        '@api': pathResolve('src/api'),
        '@assets': pathResolve('src/assets'),
        '@components': pathResolve('src/components'),
        '@store': pathResolve('src/store'),
        '@utils': pathResolve('src/utils'),
        '@views': pathResolve('src/views'),
        '#': pathResolve('config'),
    },
};
  • 🍁Vue配置config/index.js
/**
 * @Description  : Vue相关配置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */

import resolve from './resolve/index'

export default function VueConfig(isBuild, VITE_APP_BASE_API) {
    return {
        ['resolve']: resolve,
    }
}
  • 🍁Vue配置package.json
const VueConfig = require('#/index')
const WebpackName = process.env.VUE_APP_TITLE // 网页标题
module.exports = {
    configureWebpack: {
        resolve: VueConfig['resolve'],
    },
}
  • 修改跨域配置
const path = require('path')

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

const port = 80
const url = "http://localhost:88"
const name = process.env.VUE_APP_TITLE || "Silence-VUE"// 网页标题

module.exports = {
    publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
    // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
    outputDir: 'dist',
    // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
    assetsDir: 'static',
    // 是否开启eslint保存检测,有效值:ture | false | 'error'
    lintOnSave: process.env.NODE_ENV === 'development',
    // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
    productionSourceMap: false,
    // 跨域相关配置
    devServer: {
        host: '127.0.0.1',
        port: port,
        proxy: {
            [process.env.VUE_APP_BASE_API]: {
                target: url,
                open: true,
                changeOrigin: true,
                pathRewrite: {
                    ['^' + process.env.VUE_APP_BASE_API]: ''
                }
            }
        },
    }
}

三、封装工具类

3.1🍚【基本配置】

💡Tips:在src文件下创建utils,文件夹下新建index.ts文件夹:用于扫描ts文件

/**
 * 动态引入方法
 */
export default {
    install(app) {
        const methods = require.context('./modules/', true, /\.js$/)
        // 加载在某个目录中动态注册的所有全局方法
        methods.keys().forEach((filePath) => {
            const method = methods(filePath)
            const fileName = getFileName(filePath)
            //export default{}类型
            if (method.default !== undefined && method.default !== null) {
                // 将该方法注册为 $fileName 的全局方法
                Object.keys(method).forEach(() => {
                    app.config.globalProperties['$' + fileName] = method.default
                })
            } else {
                // 将该方法注册为 $fileName 的全局方法
                app.config.globalProperties['$' + fileName] = method
            }
        })
    },
}
/**
 * @Description 获取最后一个文件夹
 * @param {string} filePath
 * @returns {string} fileName 子文件夹名
 */
export function getLastFileName(filePath) {
    return filePath.substring(0, filePath.lastIndexOf('/')).split('/').pop()
}

Tips:main.ts全局引入

  • 🍚 main.tsmain.ts
import { createApp } from 'vue'
import App from './App.vue'
import utils from '/@/utils';
const app = createApp(App)
app.use(utils,true)
app.use(router)
app.mount('#app')

3.2🍚【创建方法】

  • 🍚 创建Token工具类
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)
}
  • 🍚 消息提示src/utils/modules/model.js
/**
 * @Description  : 用于消息提示、确认消息和提交内容
 * @Version      : V1.0.0
 * @Author       : SilenceLamb
 */
let loadingInstance
export default {
    // 消息提示
    message(content, type) {
        ElMessage[type](content)
    },

    // 弹出提示 -(弹框)
    alert(title, content, type) {
        ElMessageBox.alert(content, '系统提示', { type: type }).then()
    },

    // 通知提示 -(通知)
    notify(content, type) {
        ElNotification[type](content)
    },

    // 确认窗体 -(窗体)
    confirm(title, content, type) {
        return ElMessageBox.confirm(content, content, {
            title: title,
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: type,
        })
    },
    // 提交内容 -(窗体)
    prompt(title, content, type) {
        return ElMessageBox.prompt(content, content, {
            title: title,
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: type,
        })
    },
    // 打开遮罩层 -(遮罩)
    loading(content) {
        loadingInstance = ElLoading.service({
            lock: true,
            text: content,
            spinner: 'el-icon-loading',
            background: 'rgba(0, 0, 0, 0.7)',
        })
    },
    // 关闭遮罩层 -(遮罩)
    closeLoading() {
        loadingInstance.close()
    },
}

  • 🛞 参数处理工具src/utils/modules/params.js
/**
 * @Description  : 参数处理工具
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
export default {
    /**
     * @Description  :请求参数处理
     * @param params
     * @return {string}
     */
    tansParams(params) {
        let result = ''
        for (const propName of Object.keys(params)) {
            const value = params[propName]
            const part = `${encodeURIComponent(propName)}=`
            if (value !== null && value !== '' && typeof value !== 'undefined') {
                if (typeof value === 'object') {
                    for (const key of Object.keys(value)) {
                        // eslint-disable-next-line max-depth
                        if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
                            const params = `${propName}[${key}]`
                            const subPart = `${encodeURIComponent(params)}=`
                            result += `${subPart + encodeURIComponent(value[key])}&`
                        }
                    }
                } else {
                    result += `${part + encodeURIComponent(value)}&`
                }
            }
        }
        return result
    },
}

3.2🍚【基本使用】

  • 🍚 main.tsmain.ts
<template >
<div class="app-container">
  <a-button @click="message">message</a-button>
</div>
</template>
<script lang="ts">

export default{
 methods:{
    message(){
      this.$model.message('消息提示', 'success');
    }
  }
}
</script>

四、跨域配置

4.1🍖【通用常量】

  • 🍖 项目设置:config/constant/silence.ts
/*
 * @Description  : 项目设置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
export default {
    /**
     * 前端端口
     */
    vuePort: 80,

    /**
     * 后端接口
     */
    adminUrl: 'http://localhost:88',
};
  • 🍖 通用设置:config/constant/modules/common.ts
/*
 * @Description  : 通用设置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
export default {
    /**
     * @Description    : 在npm run build 或 yarn build 时 ,生成文件的目录名称
     * @param          {String} param -(默认dist)
     */
    outputDir: 'dist',
    /**
     * @Description    : 用于放置生成的静态资源 (js、css、img、fonts) 的
     * @param          {String} param -(默认static)
     */
    assetsDir: 'static',
};
  • 🍖 整合常量:config/constant/modules/common.ts
/*
 * @Description  : 整合常量
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import silence from './modules/silence';
import common from './modules/common';

const constants = {
    ['silence']: silence,
    ['common']: common,
};
export default constants;

4.2🍖【跨域配置】

  • 🍖 代理目标列表:config/server/modules/index.js
/*
 * @Description  : 代理目标列表
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
const constant = require('../../constant/index')
module.exports = function () {
    return {
        [process.env.VUE_APP_BASE_API]: {
            target: constant['silence'].adminUrl,
            changeOrigin: true,
            pathRewrite: { [`^${process.env.VUE_APP_BASE_API}`]: '' },
        },
    }
}
  • 不配置pathRewrite 请求就被转发到 XX.XX.XX.XX:80 并把相应uri带上

  • 比如:localhost:80/api/xxx 会被转发到XX.XX.XX.XX:80/api/xxx

  • 🍖 跨域配置:config/devServer/index.js

/**
 * @Description  : 跨域相关配置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
const constant = require('../constant/index')
const getProxy = require('./modules')
module.exports = {
    host: '0.0.0.0',
    open: true,
    port: constant['silence'].vuePort,
    proxy: getProxy(),
}

  • 🍖 vue.config.js配置:vue.config.js
const path = require('path')
function resolve(dir) {
    return path.join(__dirname, dir)
}
const { VueConfig } = require('./config/index')
const constant = require('./config/constant/index')
const vueConfig = VueConfig(process.env.VUE_MODE_ENV, process.env.VUE_APP_BASE_API)
module.exports = {
    publicPath: process.env.NODE_ENV === 'production' ? '/' : '/',
    // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
    outputDir: constant['common'].outputDir,
    // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
    assetsDir: constant['common'].assetsDir,
    // 是否开启eslint保存检测,有效值:ture | false | 'error'
    lintOnSave: constant['common'].lintOnSave,
    // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
    productionSourceMap: constant['common'].productionSourceMap,
    devServer: vueConfig['server'],
    css: {
        loaderOptions: { sass: { sassOptions: { outputStyle: 'expanded' } } },
    },
    configureWebpack: {
        resolve: vueConfig['resolve'],
        plugins: vueConfig['plugins'],
    }
}

五、按需加载

5.1🫑【自动引入依赖】

  • 🫑自动引入依赖config/plugins/modules/importDeps.js
/**
 * @Description  : 依赖自动按需导入
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
const ImportDeps = require('unplugin-auto-import/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
exports.AutoImportDeps = function () {
    return ImportDeps({
        resolvers: [ElementPlusResolver()],
        imports: ['vue', 'vue-router', 'vuex', '@vueuse/head'],
        // 可以选择auto-import.d.ts生成的位置,建议设置为 'src/auto-import.d.ts
        dts: 'src/auto/auto-import.d.ts',
    })
}

5.2🫑【自动引入组件】

  • 🫑自动引入组件config/plugins/modules/importComp.js
/**
 * @Description  : 组件按需自动导入
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
const ImportComp = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
exports.ImportComponents = function () {
    /* 组件按需自动导入*/
    return ImportComp({
        //引入自己的组件
        dirs: ['src/modules/components'],
        //引入ElementPlus官方组件
        resolvers: [ElementPlusResolver()],
        dts: 'src/auto/components.d.ts',
        // 组件的有效文件扩展名
        extensions: ['vue'],
        // 允许子目录作为组件的命名空间前缀
        directoryAsNamespace: false,
        // 用于转换目标的筛选器
        include: [/\.vue$/, /\.vue\?vue/],
        exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/],
        // 自定义规则
        libs: [
            {
                libraryName: 'element-plus',
                esModule: true,
                resolveStyle: (name) => {
                    return `element-plus/lib/theme-chalk/${name}.scss`
                },
            },
        ],
    })
}

六、状态管理

6.1🍜【基本配置】

  • 🍜 基本配置 src/store/index.js
import { createStore } from 'vuex'
import getters from './getter'
const modulesFiles = require.context('./modules', true, /\.js$/)

/*
 * 如果你需要 `import app from './modules/app'`
 * 它将自动要求模块文件中的所有 Vuex 模块
 */
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
    // set './index.scss' => 'app'
    const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
    const value = modulesFiles(modulePath)
    modules[moduleName] = value.default
    return modules
}, {})

export default createStore({
    modules,
    getters,
})
  • 🍜 定义一个state src/store/modules/settings.js
import defaultSettings from '@/config/settings'
const { title, logo, fixedHeader, sidebarLogo } = defaultSettings

/**
 * 可能是在一个 Vuex store 中作为状态的初始值进行定义
 * @type {lang} 语言
 * @type {title} 网站标题
 * @type {logo} 网站logo
 * @type {fixedHeader} 头部固定
 * @type {sidebarLogo} 是否显示Logo
 */
const state = {
    lang: 'zh',
    title,
    logo,
    fixedHeader,
    sidebarLogo,
}

/**
 * 用于存储更改应用程序状态的函数
 * 这些函数通常在 actions 中被调用
 */
const mutations = {
    /**
     * 更改设置
     * @param state state对象
     * @param key state属性
     * @param value 给定的值
     * @constructor
     */
    CHANGE_SETTING: (state, { key, value }) => {
        if (Object.hasOwnProperty.call(state, key)) {
            state[key] = value
        }
    },
}

/**
 * 用于存储异步操作的函数
 * 通常用于执行一些异步操作
 */
const actions = {
    /**
     * 更改设置
     * @param commit
     * @param data { key:'', value:''}
     */
    changeSetting({ commit }, data) {
        commit('CHANGE_SETTING', data)
    },
}

export default {
    namespaced: true,
    state,
    mutations,
    actions,
}

6.2🍜【基本使用】

  • 🍜 基本使用:state src/views/index/index.vue
this.$store.state.count

mapState({
    // 箭头函数可使代码更简练
    count: state => state.count
})
  • 🍜 基本使用:getters src/views/index/index.vue
this.$store.getters.doneTodosCount

 ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  • 🍜 基本使用:mutations src/views/index/index.vue
store.commit('increment', {
  amount: 10
})
  • 🍜 基本使用:actions src/views/index/index.vue
// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

6.3🍜【持久化配置】

  • 🍑持久化配置src/store/index.js
/**
 * @Description  : 动态加载模块
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { createStore } from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import Token from '@/utils/modules/token'
const modulesFiles = require.context('./modules', true, /\.js$/)

/*
 * 它将自动要求模块文件中的所有 Vuex 模块
 */
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
    // set './mixin.scss' => 'app'
    const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
    const value = modulesFiles(modulePath)
    modules[moduleName] = value.default
    return modules
}, {})

export default createStore({
    modules,
    // 持久化操作 当修改state的内容自动持久化存储
    plugins: [
        createPersistedState({
            storage: window.localStorage,
            key: 'State',
            setItem: function (key, value) {
            	//将token信息存储到Cookies中
                if (key === 'token') {
                    Token.setToken(value)
                }
            },
        }),
    ],
})

七、路由配置

7.1🍑【路由配置】

  • 🍑 yarn命令安装
yarn add vue-router@4

💡Tips:src文件夹下新建router文件夹,router文件夹下新建static.ts

  • 🍑路由配置src/router/static.ts
/**
 * @Description  : 路由配置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import {  RouteRecordRaw } from 'vue-router';

export const constantRoutes: RouteRecordRaw [] = [
    {
        path: '/',
        name: 'Index',
        component: () => import('/@/views/index/index.vue'),
    },
];

export default {constantRoutes};

💡Tips:在main.ts中,引入router并注册

import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
const app = createApp(App)
app.use(router)
app.mount('#app')

💡Tips:在App.vue中设置路由展现出口

<template>
    <router-view />
</template>

7.2🍑【路由守卫】

💡Tips:src文件夹下新建router文件夹,router文件夹下新建index.ts

  • 🍑简单的路由守卫src/router/index.ts
/**
 * @Description  : 路由守卫
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import {createRouter, createWebHistory} from 'vue-router';
import {constantRoutes} from './static';
import {useAdminInfoState} from '/@/store/modules/adminInfo';
//引入进度条样式
import 'nprogress/nprogress.css';

const router = createRouter({
    history: createWebHistory(),
    routes: constantRoutes,
});
/**
 * @Description 全局前置守卫
 * 当一个导航触发时,全局前置守卫按照创建顺序调用
 * @param to: 即将要进入的目标 用一种标准化的方式
 * @param from: 当前导航正要离开的路由 用一种标准化的方式
 * @return {boolean} false: 取消当前的导航 |true  通过一个路由地址跳转到一个不同的地址
 */
/* 添加白名单*/
const whiteList = ['/login']; // no redirect whitelist
// @ts-ignore
router.beforeEach((to, from, next) => {

    console.log(useAdminInfoState().getToken())
    if (useAdminInfoState().getToken()) {
        /* has token*/
        if (to.path === '/login') {
            next({path: '/'})
        } else if (useAdminInfoState().userName === null) {
            //获取用户信息
        } else {
            next()
        }
    } else {
        // 没有token
        if (whiteList.indexOf(to.path) !== -1) {
            // 在免登录白名单,直接进入
            next()
        } else {
            next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
        }
    }
})

/**
 * @Description 全局后置钩子
 * 钩子不会接受 next 函数也不会改变导航本身
 */
router.afterEach(() => {
});

export default router;

7.3🍑【组件跳转】

  • 🍑 setup我们不能再直接访问 this. r o u t e r 或 t h i s . router 或 this. routerthis.route
  • 🍑作为替代,我们使用 useRouter 和 useRoute 函数
import { useRouter, useRoute } from 'vue-router'

export default {
  setup() {
    const router = useRouter()
    const route = useRoute()

    function pushWithQuery(query) {
      router.push({
        name: 'search',
        query: {
          ...route.query,
        },
      })
    }
  },
}
  • 🍑路由重定向src/views/redirect/index.vue
<script>
export default {
    name: 'RedirectVue',
    created() {
        const { params, query } = this.$route
        const { path } = params
        this.$router.replace({
            path: `/${path}`,
            query,
        })
    },
    render(h) {
        return h() // avoid warning message
    },
}
</script>

7.4🍑【组件守卫】

💡Tips:组件内路由守卫

  • 🍑新增组合式api:可以替代原有的组件内守卫
    • onBeforeRouteLeave(离开当前页面路由时触发)
    • onBeforeRouteUpdate(路由更新时触发)

💡Tips你可以在路由组件内直接定义路由导航守卫(传递给路由配置的)

  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
  • 🍑页面使用
import { useRouter,useRoute } from 'vue-router';
 const go=()=>{
     const Router=useRouter()
     const Route=useRoute()
     Router.push({
         name:'/login',
         query:{
             id:'123456'
         }
     })
 }

7.5🍑【过渡动效】

  • 🍑整个布局过度动效src/router/static.ts
<router-view v-slot="{ Component }">
  <transition name="fade">
    <component :is="Component" />
  </transition>
</router-view>
  • 🍑单个路由的过渡src/router/static.ts
const routes = [
  {
    path: '/custom-transition',
    component: PanelLeft,
    meta: { transition: 'slide-left' },
  },
  {
    path: '/other-transition',
    component: PanelRight,
    meta: { transition: 'slide-right' },
  },
]
  • 🍑 你可以将元信息和动态的 name 结合在一起,放在 < transition > 上
<router-view v-slot="{ Component, route }">
  <!-- 使用任何自定义过渡和回退到 `fade` -->
  <transition :name="route.meta.transition || 'fade'">
    <component :is="Component" />
  </transition>
</router-view>

八、Layout布局

8.1⏰【Layout容器】

Tips:简单Layout布局:src/layout/index.vue

<template>
    <div class="app-wrapper">
        <app-main />
        <back-to-top />
    </div>
</template>

<style lang="scss" scoped>
@import 'src/styles/index';

.app-wrapper {
    @include clearfix;
    position: relative;
    height: 100%;
    width: 100%;
}
</style>

8.2⏰【主要内容】

Tips:主要区域组件:src/layout/components/appMain/index.vue

<template>
    <section class="app-main">
        <router-view v-slot="{ Component }">
            <transition name="fade-transform" mode="out-in">
                <component :is="Component" :key="$route.path" />
            </transition>
        </router-view>
    </section>
</template>

<script>
export default { name: 'AppMain' };
</script>

<style lang="scss" scoped>
.app-main {
    /*50 = navbar  */
    min-height: 100vh;
    width: 100%;
    position: relative;
    overflow: hidden;
}
</style>

<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
    .fixed-header {
        padding-right: 15px;
    }
}
</style>

Tips:路由引入Layout布局:

import Layout from '/@/layout/index.vue';
    {
        path: '/',
        redirect: '/index',
        component: Layout,
        children: [{ name: 'index', path: '/index', component: () => import('/@/views/index/index.vue') }],
    },

九、安装Axios

9.1🍟【安装Axios】

npm install axios --save
  • main.js引入Axios
const app = createApp(App)

app.config.globalProperties.$axios  = axios
  • 就可以使用以下方式发送请求
 getMenu() {
	this.$axios({
      url: "",
      method: "get",
      params: {}
    }).then(res => {
      console.log(res)
    })
 }

9.2🍟【封装axios】

import axios from "axios";
//1、对axios二次封装
const requests = axios.create({
    //基础路径,requests发出的请求在端口号后面会跟改baseURl
    baseURL:'/api',
    timeout: 5000,
})
//2、配置请求拦截器
requests.interceptors.request.use(config => {
    //config内主要是对请求头Header配置
    //比如添加token
    
    return config;
})
//3、配置相应拦截器
requests.interceptors.response.use((res) => {
    //成功的回调函数
    return  res.data;
},(error) => {
    //失败的回调函数
    console.log("响应失败"+error)
    return Promise.reject(new Error('fail'))
})
//4、对外暴露
export default requests;

9.3🍟【请求接口】

//当前模块,API进行统一管理,即对请求接口统一管理
import requests from "@/utils/request";

//获取验证码
export function getCodeImage() {
    return requests({
        url: '/captchaImage',
        method: 'get'
    })
}
  • 当组件想要使用相关请求时,只需要导入相关函数即可
import {reqCateGoryList} from './api'
//发起请求
reqCateGoryList();

十、添加Token验证

10.1🥣安装 js-cookie

  • 🌟 安装 js-cookie,并且main.js引入 js-cookie
import Cookies from 'js-cookie'

const app = createApp(App)
app.use(Cookies)

10.2🥣创建Token工具类

  • 🌟 创建Token工具类
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)
}

10.3🥣配置请求拦截器

//2、配置请求拦截器
requests.interceptors.request.use(config => {

    // 是否需要设置 token
    const isToken = (config.headers || {}).isToken === true
    // 是否需要防止数据重复提交
    const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
    //添加token
    if (isToken) {
        //config内主要是对请求头Header配置
        config.headers['Authorization'] = 'Bearer ' +token.getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
    }

    return config
}, error => {
    Promise.reject(error).then(r => r)
})

在这里插入图片描述

10.4🥣配置相应拦截器

//3、配置相应拦截器
requests.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
    } else if (code === 500) {
        ElMessage({
            message: msg || 'Error',
            type: 'error',
            duration: 5 * 1000
        })
        return Promise.reject(new Error(msg))
    }  else {
        //成功的回调函数
        return res.data;
    }
}, (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: 5 * 1000
    })
    return Promise.reject(error)
})

10.5🥣Api是否需要Token

//当前模块,API进行统一管理,即对请求接口统一管理
import requests from "@/utils/requests"

//获取验证码
export function getCodeImage() {
    return requests({
        url: '/captchaImage',
        method: 'get',
        headers: {
            isToken: false
        },
    })
}

📢🌥️如果文章对你有帮助【关注👍点赞❤️收藏⭐】

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Silence Lamb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值