B01-基于VITE开发框架

🧑‍🎓 个人主页Silence Lamb
📖 本章内容:【基于VITE项目开发框架


基于Vite前端快速开发框架 v1.0.0

star star star star star


一、基础配置

1.1🌳【基本环境】

💡 Tipsvue-cli + vue3 + yarn + vite + ts

# vue/cli
yarn global add @vue/cli
# typescript parser
yarn add @typescript-eslint/parser --dev
# 如果没有安装yarn,需先安装yarn
npm i yarn -g
# 创建vite项目
yarn create vite vue3-vite-app --template vue-ts
# 进入项目根目录
cd vue3-vite-app
# 安装依赖
yarn install
# 启动项目
yarn dev

image.png

  • 推荐使用新一代 pnpm 包管理工具,性能和速度以及 node_modules依赖管理都很优秀

💡Tips建议配合 .npmrc 配置使用

# 提升一些依赖包至 node_modules
# 解决部分包模块not found的问题
# 用于配合 pnpm
shamefully-hoist = true

# node-sass 下载问题
# sass_binary_site="https://npm.taobao.org/mirrors/node-sass/"

💡Tips模板语法配合jsx语法,使用起来非常方便、灵活~一些必须的插件

yarn add @vitejs/plugin-legacy -S // 低版本浏览器兼容
yarn add @vitejs/plugin-vue -S // vue 支持
yarn add @vitejs/plugin-vue-jsx -S // jsx 支持

1.2🌳【代码风格】

💡Tips开启Eslint的支持

yarn add eslint -D
yarn add eslint-plugin-vue -D
yarn add @typescript-eslint/eslint-plugin -D
yarn add @typescript-eslint/parser -D

💡Tips:**echo {}> .**eslintrc.js

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,
        },
    },
    plugins: ['vue', '@typescript-eslint', 'prettier'],
    rules: {
        '@typescript-eslint/ban-ts-ignore': 'off',
        '@typescript-eslint/no-unused-vars': 'off',
        '@typescript-eslint/explicit-function-return-type': 'off',
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/no-var-requires': 'off',
        '@typescript-eslint/no-empty-function': 'off',
        '@typescript-eslint/no-use-before-define': 'off',
        '@typescript-eslint/ban-ts-comment': 'off',
        '@typescript-eslint/ban-types': 'off',
        '@typescript-eslint/no-non-null-assertion': 'off',
        '@typescript-eslint/explicit-module-boundary-types': 'off',
        'no-var': 'error',
        'prettier/prettier': ['error', { trailingComma: 'es5', singleQuote: true, printWidth: 80, tabWidth: 2 }],
        // 禁止出现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',
        'no-var': '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

1.3🌳【代码格式】

💡 Tips安装 prettier

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

💡Tipsecho {}> .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',
}

💡Tipsecho {}> .prettierignore

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

💡Tipspackage.json

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

1.4🌳【环境变量 】

import.meta.env.MODE: {string} 应用运行的模式
import.meta.env.BASE_URL: {string} 部署应用时的基本 URL。他由base 配置项决定
import.meta.env.PROD: {boolean} 应用是否运行在生产环境
import.meta.env.DEV: {boolean} 应用是否运行在开发环境 (永远与 import.meta.env.PROD相反)
import.meta.env.SSR: {boolean} 应用是否运行在 server 上
  • 🌳 .env 文件
.env                # 所有情况下都会加载
.env.local          # 所有情况下都会加载,但会被 git 忽略
.env.[mode]         # 只在指定模式下加载
.env.[mode].local   # 只在指定模式下加载,但会被 git 忽略
  • 🌳在tsconfig.json中添加"types": [ “vite/client” ]
  • 用来提供import.meta.env 上 Vite 注入的环境变量的类型定义
"compilerOptions": {
	"types": [  "vite/client" ]
}

💡Tips:另外一种配置方式

  • 🌳 开发环境:.env.development
# 页面标题
VITE_APP_TITLE=开发环境
# 开发环境配置
VITE_NODE_ENV='development'
# 开发环境
VITE_APP_BASE_API='/dev-api'
# 路由懒加载
VITE_CLI_BABEL_TRANSPILE_MODULES=true
  • 🌳 线上环境:.env.production
# 页面标题
VITE_APP_TITLE=线上环境
# 生产环境配置
VITE_NODE_ENV='production'
# 生产环境
VITE_APP_BASE_API='/prod-api'
# 路由懒加载
VITE_CLI_BABEL_TRANSPILE_MODULES=true
  • 🌳 测试环境配置:.env.production
# 页面标题
VITE_APP_TITLE=测试环境配置
VITE_NODE_ENV=production
# 测试环境配置
VITE_NODE_ENV='staging'
# 测试环境
VITE_APP_BASE_API='/stage-api'
  • 🌳为环境变量增加智能提示src/env.d.ts
/**
 * @Description  : 为环境变量增加智能提示
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
interface ImportMetaEnv {
    //定义提示信息 数据是只读的无法被修改
    readonly VITE_APP_TITLE: string;
    readonly VITE_NODE_ENV: string;
    readonly VITE_APP_BASE_API: string;
    //多个变量定义多个...
}
interface ImportMeta {
    readonly env: ImportMetaEnv;
}

在这里插入图片描述

💡Tipsvite.config.ts: 不能使用 import.meta.env

    const ViteEnv= loadEnv(mode, process.cwd());
    console.log(ViteEnv.VITE_APP_BASE_API);
    console.log(ViteEnv.VITE_NODE_ENV);

💡Tipspackage.json

"scripts": {
    "dev": "vite --mode development",
    "build": "vue-tsc --noEmit && vite build",
    "build:prod": "vue-tsc --noEmit && vite build --mode production",
  },
  • 🌳为环境变量增加智能提示src/env.d.ts
/*
 * @Description  : Vite相关方法
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
export default {
  /**
   * @Description : 是否是开发环境
   * @param mode 模式
   */
  isDev(mode: string): boolean {
    return mode === 'development';
  },

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

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

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

1.4🌳【别名配置】

  • 例如,如果我们要导入src下的components目录中的组件,则可以这样写
import MyComponent from '@/components/MyComponent';

💡Tips:首先,在tsconfig.json中添加一个"paths"属性,然后以别名为键名,路径为键值

  • 🌳别名配置:tsconfig.json
{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "/@/*": ["src/*"]
        }
    },
    "exclude": ["node_modules", "dist"]
}

💡Tips:在项目根目录下新建config/aliases文件夹,在其中新建一个index文件

  • 🌳别名配置:config/aliases/index.ts
/*
 * @Description  : 配置别名
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { resolve } from 'path';

function pathResolve(dir: string) {
    return resolve(process.cwd(), '.', dir);
}
export default  {
    // 导入时想要省略的扩展名列表
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
    alias: {
        '/@': pathResolve('src'),
        '/#': pathResolve('config'),
    },
};

💡Tips如果path找不到:npm install @types/node --save-dev

  • 🌳 Vite配置:config/index.ts
/*
 * @Description  : Vite配置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import buildConfig from '/#/build';
import createVitePlugins from '/#/plugins';
import resolve from '/#/aliases';
import serverConfig from '/#/server';

export default function viteConfig(isBuild: boolean, VITE_APP_BASE_API: string) {
    const viteConfig = {
        ['resolve']: resolve,
    };
    return viteConfig;
}
  • 🌳 vite.config.ts配置vite.config.ts
import { UserConfig, ConfigEnv, loadEnv } from 'vite';
import viteUtils from '/@/utils/modules/vite';
import viteConfig from './config';
export default ({ command, mode }: ConfigEnv): UserConfig => {
    const env = loadEnv(mode, process.cwd());
    const { VITE_APP_BASE_API } = env;
    const isBuild = viteUtils.isBuild(command);
    //vite配置在config文件下
    console.log(VITE_APP_BASE_API)
    const ViteConfig = viteConfig(isBuild, VITE_APP_BASE_API);
    return {
        //静态资源
        publicDir: 'public',
        //项目基础路径
        base: viteUtils.isDev(mode) ? '/' : '',
        //build时 去除 console和 debugger
        esbuild: {
            drop: ['console', 'debugger'],
        },
        resolve: ViteConfig['resolve'],
    };
};

二、跨域配置

2.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;

2.2🍖【跨域配置】

  • 🍖 代理目标列表:config/server/modules/index.ts
/*
 * @Description  : 代理目标列表
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { ProxyOptions } from 'vite';
import constants from '/#/constant';
type ProxyTargetList = Record<string, ProxyOptions>;
export default function getProxy(VITE_APP_PROD_API) {
    const proxy: ProxyTargetList = {
        [VITE_APP_PROD_API]: {
            target: constants['silence'].adminUrl,
            changeOrigin: true,
            rewrite: (path) => path.replace(new RegExp(`^${VITE_APP_PROD_API}`), ''),
        },
    };
    return proxy;
}
  • 🍖 跨域配置:config/server/index.ts
/*
 * @Description  : 跨域配置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import getProxy from '/#/server/modules';
import constants from '/#/constant';

export default function serverConfig(VITE_APP_PROD_API: string) {
    return {
        hmr: { overlay: false }, // 禁用或配置 HMR 连接 设置 server.hmr.overlay 为 false 可以禁用服务器错误遮罩层
        // 服务配置
        open: true, // 类型: boolean | string在服务器启动时自动在浏览器中打开应用程序;
        cors: false, // 类型: boolean | CorsOptions 为开发服务器配置 CORS。默认启用并允许任何源
        host: '0.0.0.0', // IP配置,支持从IP启动
        port: constants['silence'].vuePort,
        proxy: getProxy(VITE_APP_PROD_API),
    };
}
  • 🍖 Vite配置:config/index.ts
/*
 * @Description  : Vite配置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import resolve from '/#/aliases';
import serverConfig from '/#/server';

export default function viteConfig(isBuild: boolean, VITE_APP_BASE_API: string) {
    return {
        ['resolve']: resolve,
        ['server']: serverConfig(VITE_APP_BASE_API),
    };
}
  • 🍖 vite.config.ts配置vite.config.ts
import { UserConfig, ConfigEnv, loadEnv } from 'vite';
import viteUtils from '/@/utils/modules/vite';
import viteConfig from './config';
export default ({ command, mode }: ConfigEnv): UserConfig => {
    const { VITE_APP_BASE_API } = loadEnv(mode, process.cwd());
    //是否是build命令
    const isBuild = viteUtils.isBuild(command);
    //vite配置在config文件下
    const ViteConfig = viteConfig(isBuild, VITE_APP_BASE_API);
    return {
        //静态资源
        publicDir: 'public',
        //项目基础路径
        base: viteUtils.isDev(mode) ? '/' : '',
        //build时 去除 console和 debugger
        esbuild: {
            drop: ['console', 'debugger'],
        },
        resolve: ViteConfig['resolve'],
        //跨域配置
        server: ViteConfig['server'],
    };
};

三、按需加载

  • 🍁首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件
yarn add  unplugin-vue-components unplugin-auto-import -D

3.1🍁【自动引入依赖】

  • 🍁自动引入依赖:config/plugins/modules/importDeps.ts
/*
 * @Description  : 按需加载,自动引入依赖
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import AutoImport from 'unplugin-auto-import/vite'
components/resolvers'
export default function ImportDeps() {
    return AutoImport({
        imports: ['vue', 'vue-router', 'vue-i18n', '@vueuse/head', '@vueuse/core'],
        dts: 'src/auto/auto-imports.d.ts',
    })
}

3.2🍁【自动引入组件】

  • 🍁自动引入依赖:config/plugins/modules/importComp.ts
/*
 * @Description  : 按需加载,自动引入组件
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';

export default function ImportComp() {
    return Components({
        // 自动导入自定义的组件
        dirs: ['src/components'],
        // 组件的有效文件扩展名
        extensions: ['vue'],
        // 搜索子目录
        deep: true,
        // 组件解释器
        resolvers: [AntDesignVueResolver({ importStyle: 'less' })],
        // 文件生成目录
        dts: 'src/auto/components.d.ts',
        // 允许子目录作为组件的命名空间前缀
        directoryAsNamespace: false,
        // 用于忽略命名空间前缀的子目录路径
        // works when `directoryAsNamespace: true`
        globalNamespaces: [],
        // 用于转换目标的筛选器
        include: [/\.vue$/, /\.vue\?vue/],
        exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
    });
}

3.3🍁【自动引入样式】

  • 当你使用unplugin-vue-components来引入ui库的时候,message, notification,toast 等引入样式不生效

💡Tips:安装vite-plugin-style-import,实现message, notification,toast 等引入样式自动引入

yarn add vite-plugin-style-import -D
yarn add  consola -D
  • 🍁自动引入样式:config/plugins/modules/importStyle.ts
/*
 * @Description  : 自动引入样式
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { createStyleImportPlugin, AndDesignVueResolve } from 'vite-plugin-style-import';
export default function ImportStyle() {
    return createStyleImportPlugin({
        resolves: [AndDesignVueResolve()],
        // 自定义规则
        libs: [
            {
                libraryName: 'ant-design-vue',
                esModule: true,
                resolveStyle: (name) => {
                    return `ant-design-vue/es/${name}/style/index`;
                },
            },
        ],
    });
}
  • 🍁整合插件:config/plugins/index.ts
/*
 * @Description  : 配置插件
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import type { Plugin } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import ImportComp from './modules/importComp';
import ImportDeps from './modules/importDeps';
import ImportStyle from './modules/importStyle';
export default function createVitePlugins(isBuild: boolean) {
    const vitePlugins: (Plugin | Plugin[])[] = [];
    // vue支持
    vitePlugins.push(vue());
    // JSX支持
    vitePlugins.push(vueJsx());
    // 自动按需引入依赖
    vitePlugins.push(ImportDeps());
    //自动按需引入组件
    vitePlugins.push(ImportComp());
    //自动引入样式
    vitePlugins.push(ImportStyle());
    return vitePlugins;
}
  • 🍁vite配置config/index.ts
/*
 * @Description  : Vite配置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import createVitePlugins from '/#/plugins';
import resolve from '/#/aliases';
import serverConfig from '/#/server';
import { UserConfig } from 'vite';
import vite from '../src/utils/modules/vite';

export default function viteConfig(mode: string, command: string, ViteEnv: object): UserConfig {
    const isBuild = vite.isBuild(command);
    return {
        ['resolve']: resolve,
        ['server']: serverConfig(ViteEnv.VITE_APP_BASE_API),
        ['plugins']: createVitePlugins(isBuild),
    };
}
  • 🍁vite.config.ts配置vite.config.ts
import { UserConfig, ConfigEnv, loadEnv } from 'vite';
import viteUtils from '/@/utils/modules/vite';
import viteConfig from './config';
export default ({ command, mode }: ConfigEnv): UserConfig => {
    const ViteEnv = loadEnv(mode, process.cwd());
    //vite配置在config文件下
    const ViteConfig = viteConfig(command, mode, ViteEnv);
    return {
        //静态资源
        publicDir: 'public',
        //项目基础路径
        base: viteUtils.isDev(mode) ? '/' : '',
        //build时 去除 console和 debugger
        esbuild: {
            drop: ['console', 'debugger'],
        },
        resolve: ViteConfig['resolve'],
        //插件配置
        plugins: ViteConfig['plugins'],
        //打包配置
        build: ViteConfig['build'],
        //跨域配置
        server: ViteConfig['server'],
    };
};


四、插件推荐

4.1🥑【自动重启】

💡Tips:通过监听文件修改,自动重启 vite 服务

  • 最常用的场景就是监听 vite.config.js 和 .env.development 文件
yarn add npm i vite-plugin-restart -D
  • 🥑配置插件config/plugins/modules/autoRestartVite.ts
/*
 * @Description  : 自动重启Vite服务
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import ViteRestart from 'vite-plugin-restart'
export default function RestartVite() {
    return ViteRestart({
        restart: ['vite.config.ts'],
    })
}

💡Tips:自定义图标:以下插件都需要进行添加

vitePlugins.push(restartVite())

4.2🥑【引入图标】

yarn add vite-plugin-svg-icons -D
  • 🥑配置插件config/plugins/modules/svgIcon.ts
/*
 * @Description  : 自定义SvgIcon
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { resolve } from 'path';
function pathResolve(dir: string) {
    return resolve(process.cwd(), '.', dir);
}
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';

export default function svgIconPlugin() {
    return createSvgIconsPlugin({
        // 指定要缓存的图标文件夹
        iconDirs: [pathResolve('src/assets/svg')],
        // 指定符号 ID 格式
        symbolId: 'icon-[dir]-[name]',
    });
}
  • 🥑SvgIcon组件src/components/SvgIcon/index.vue
/*
* @Description  :
* @Author       : SilenceLamb
* @Version      : V1.0.0
/*
* @Description  : 自定义图标
* @Author       : SilenceLamb
* @Version      : V1.0.0
*/
<template>
    <svg class="svg-icon" aria-hidden="true">
        <use :href="symbolId" aria-hidden="true" />
    </svg>
</template>

<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps({
    prefix: {
        type: String,
        default: 'icon',
    },
    name: {
        type: String,
        required: true,
    },
});
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
</script>
<style scoped>
.svg-icon {
    width: 1em;
    height: 1em;
    vertical-align: -0.3em;
    /*因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果*/
    fill: currentColor;
    /*定义元素的颜色,currentColor是一个变量,这个变量的值就表示当前元素的color值,如果当前元素未设置color值,则从父元素继承*/
    overflow: hidden;
}
</style>

 <svg-icon name="order" class="icon" />
  • 🥑配置插件config/plugins/index.ts
/*
 * @Description  : 配置插件
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import type { Plugin } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import ImportComp from './modules/importComp';
import ImportDeps from './modules/importDeps';
import ImportStyle from './modules/importStyle';
import RestartVite from './modules/restartVite';
import SvgIcons from './modules/svgIcons';
import GZIPCompression from '/#/plugins/modules/compression';

// @ts-ignore
export default function createVitePlugins(isBuild: boolean) {
    const vitePlugins: (Plugin | Plugin[])[] = [];
    // vue支持
    vitePlugins.push(vue());
    // JSX支持
    vitePlugins.push(vueJsx());
    // 自动按需引入依赖
    vitePlugins.push(ImportDeps());
    //自动按需引入组件
    vitePlugins.push(ImportComp());
    //自动引入样式
    vitePlugins.push(ImportStyle());
    //自动重启Vite服务
    vitePlugins.push(RestartVite());
    //自定义图标
    vitePlugins.push(SvgIcons());
    return vitePlugins;
}

五、打包配置

5.1⏰【Build配置】

  • SvgIcon组件config/build/index.ts
/*
 * @Description  : Build配置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { resolve } from 'path';
import constants from "/#/constant";
function pathResolve(dir: string) {
    return resolve(process.cwd(), '.', dir);
}
export default  {
        //浏览器兼容性  "esnext"|"modules"
        target: 'modules',
        //启用/禁用 CSS 代码拆分
        cssCodeSplit: true,
        sourcemap: false,
        assetsInlineLimit: 10240,
        //生成静态资源的存放路径
        assetsDir: constants['common'].assetsDir,
        //打包文件生成位置
        outDir: constants['common'].outputDir,
        emptyOutDir: true,
        rollupOptions: {
            input: {
                main: pathResolve('index.html'),
            },
            output: {
                entryFileNames: `js/[name]-[hash].js`,
                chunkFileNames: `js/[name]-[hash].js`,
                assetFileNames: `[ext]/[name]-[hash].[ext]`,
            },
        },
};
/*
* build.target: 项目支持的浏览器目标
* build.polyfillDynamicImport: 是否动态的引入polyfill
* build.outDir: 输出的文件夹
* build.assetsDir: 指定生成静态资源的存放路径
* build.assetsInlineLimit: 小于这个大小的资源,以base64形式进行编码
* build.cssCodeSplit: 对css文件进行拆分,如果禁用,这个项目只有一个css文件
* build.sourceMap: 源文件映射,方便调试
* build.rollupOptions: 把options传给rollup
* build.commonjsOptions: 同上
* build.lib: 导出的是库模式,不是应用的模式
* build.manifest: 生成一个manifest文件。
* build.emptyOutDir: 构建的时候,是否主动清空目录
* build.chunkSizeWarningLimit: 如果打包的文件超过500k,就会给一个警告
* build.watch: 文件变化是否重新编译
*/
  • Vite配置config/index.ts
/*
 * @Description  : Vite配置
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import buildConfig from '/#/build';
import createVitePlugins from '/#/plugins';
import resolve from '/#/aliases';
import serverConfig from '/#/server';
import vite from '../src/utils/modules/vite';
import vite from '../src/utils/modules/vite';

export default function viteConfig(mode: string, command: string, ViteEnv: object): UserConfig {
    const isBuild = vite.isBuild(command);
    return {
        ['resolve']: resolve,
        ['build']: buildConfig,
        ['server']: serverConfig(VITE_APP_BASE_API),
        ['plugins']: createVitePlugins(isBuild),
    };
}
  • vite.config.ts配置vite.config.ts
import { UserConfig, ConfigEnv, loadEnv } from 'vite';
import viteUtils from '/@/utils/modules/vite';
import viteConfig from './config';

export default ({ command, mode }: ConfigEnv): UserConfig => {
    const ViteEnv = loadEnv(mode, process.cwd());
    //vite配置在config文件下
    const ViteConfig = viteConfig(mode, command, ViteEnv);
    return {
        //静态资源
        publicDir: 'public',
        //项目基础路径
        base: viteUtils.isDev(mode) ? '/' : '',
        //build时 去除 console和 debugger
        esbuild: {
            drop: ['console', 'debugger'],
        },
        resolve: ViteConfig['resolve'],
        //插件配置
        plugins: ViteConfig['plugins'],
        //打包配置
        build: ViteConfig['build'],
        //跨域配置
        server: ViteConfig['server'],
    };
};

5.2⏰【GZIP压缩】

  • GZIP压缩config/plugins/modules/compression.ts
/*
 * @Description  : GZIP压缩
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import viteCompression from 'vite-plugin-compression';

export default function GZIPCompression(isBuild: boolean) {
        return viteCompression({
            verbose: true, // //是否在控制台输出压缩结果,默认为 true
            disable: !isBuild, //是否禁用压缩,默认为 false
            deleteOriginFile: isBuild, //压缩后是否删除原文件,默认为 false
            threshold: 0.8, //启用压缩的文件大小限制,单位是字节,默认为 0
            algorithm: 'gzip', //采用的压缩算法,默认是 gzip
            ext: 'gz', //生成的压缩包后缀
        });
}
  • 配置插件config/plugins/index.ts
/*
 * @Description  : 配置插件
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import type { Plugin } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import ImportComp from './modules/importComp';
import ImportDeps from './modules/importDeps';
import ImportStyle from './modules/importStyle';
import RestartVite from './modules/restartVite';
import SvgIcons from './modules/svgIcons';
import GZIPCompression from '/#/plugins/modules/compression';

export default function createVitePlugins(isBuild: boolean) {
    const vitePlugins: (Plugin | Plugin[])[] = [];
    // vue支持
    vitePlugins.push(vue());
    // JSX支持
    vitePlugins.push(vueJsx());
    // 自动按需引入依赖
    vitePlugins.push(ImportDeps());
    //自动按需引入组件
    vitePlugins.push(ImportComp());
    //自定义图标
    vitePlugins.push(SvgIcons());
    //自动引入样式
    vitePlugins.push(ImportStyle());
    //自动重启Vite服务
    vitePlugins.push(RestartVite());
    //GIZP压缩
    vitePlugins.push(GZIPCompression(isBuild));
    return vitePlugins;
}

六、样式重置

  • 安装依赖sass sass-loader
npm install sass sass-loader -D
  • 🌍样式整合src/styles/index.scss
/*样式重置*/
@import 'normalize.css';
/*通用混入*/
@import './base';
/*全局过度*/
@import './common';
  • 🌍全局引入src/main.ts
import './styles/index.scss'; //全局样式

6.1🌍【基础样式】

  • 🌍基础样式src/styles/base/index.scss
/*通用混入*/
@import 'mixin';
/*全局过度*/
@import 'transition';
/*全局样式*/
@import 'global';
  • 🌍混入样式src/styles/base/mixin/index.scss
/*清除默认样式*/
@mixin resetStyles {
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        outline: none !important;
    }
}

/*清楚浮动*/
@mixin clearfix {
    &:after {
        content: '';
        display: table;
        clear: both;
    }
}

/*相对大小*/
@mixin relative {
    position: relative;
    width: 100%;
    height: 100%;
}

/*更改按钮颜色*/
@mixin colorBtn($color) {
    background: $color;

    &:hover {
        color: $color;

        &:before,
        &:after {
            background: $color;
        }
    }
}

/*添加背景图片*/
@mixin bg-image($url) {
    background-image: url($url + '@2x.png');
    @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {
        background-image: url($url + '@3x.png');
    }
}

/*文本不换行*/
@mixin no-wrap() {
    text-overflow: ellipsis;
    overflow: hidden;
    white-space: nowrap;
}

/*多行文本溢出*/
@mixin multiEllipsis($line: 2) {
    overflow: hidden;
    word-break: break-all;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: $line;
    -webkit-box-orient: vertical;
}

/*透明度*/
@mixin opacity($opacity) {
    opacity: $opacity;
    $opacity-ie: $opacity * 100;
    filter: alpha(opacity=$opacity-ie); //IE8
}

/*美化文本的选中*/
@mixin beauty-select($color, $bgcolor) {
    &::selection {
        color: $color;
        background-color: $bgcolor;
    }
}

/*毛玻璃效果*/
@mixin blur($blur: 10px) {
    -webkit-filter: blur($blur);
    -moz-filter: blur($blur);
    -o-filter: blur($blur);
    -ms-filter: blur($blur);
    filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius='${blur}');
    filter: blur($blur);
    *zoom: 1;
}

/* 滤镜: 将彩色照片显示为黑白照片、保留图片层次*/
@mixin grayscale() {
    -webkit-filter: grayscale(100%);
    -moz-filter: grayscale(100%);
    -ms-filter: grayscale(100%);
    -o-filter: grayscale(100%);
    filter: grayscale(100%);
}

/*文本居中*/
@mixin center($height: 100%) {
    height: $height;
    line-height: $height;
    text-align: center;
}

/*修改背景色等*/
@mixin background($bg-color: #5f6970, $color: #000000, $font-weight: 400) {
    color: $color;
    font-weight: $font-weight;
    background-color: $bg-color;
}

/*鼠标hover显示下划线*/
@mixin hoverLine($height: 2px, $color: #ffffff) {
    position: relative;
    &:hover::after {
        content: '';
        position: absolute;
        height: $height;
        width: 100%;
        background-color: $color;
        bottom: 0;
        left: 0;
    }
}

/*滚动条*/
@mixin scrollBar($width: 6px, $height: 6px, $trackBg: rgba(255, 255, 255, 0), $thumbBg: #808080) {
    /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
    &::-webkit-scrollbar {
        width: $width;
        height: $height;
        /* 防止遮挡内容 */
        padding-right: 20px;
        margin: 50px auto;
        overflow: hidden;
    }
    /*滚动条的轨道的两端按钮,允许通过点击微调小方块的位置*/
    &::-webkit-scrollbar-button {
        display: none;
    }
    /*边角,即两个滚动条的交汇处*/
    &::-webkit-scrollbar-corner {
        display: none;
    }
    /*定义滚动条轨道 内阴影+圆角*/
    &::-webkit-scrollbar-track {
        border-radius: 30px; /*轨道背景区域的圆角*/
        background-color: $trackBg; /*轨道的背景颜色*/
    }
    /*定义滑块 内阴影+圆角*/
    &::-webkit-scrollbar-thumb {
        border-radius: 12px;
        /*
    padding-box 表示背景裁剪到内边距框,
    这里也可以用 content-box,表示裁剪到内容框,
    默认为 border-box,表示裁剪到边框
    */
        background-clip: padding-box;
        border-color: transparent;
        background: $thumbBg; /*滑块背景颜色*/
    }

    /* 悬浮更换滚动条颜色 */
    &::-webkit-scrollbar-thumb:hover {
        background: #493131;
        border-width: 0;
    }
}

/* 文本可点击 */
@mixin clickable {
    -webkit-user-select: auto;
    -moz-user-select: auto;
    -ms-user-select: auto;
    user-select: auto;
}

/*文本不可点击*/
@mixin noClickable {
    -webkit-user-select: none; /* Safari */
    -moz-user-select: none; /* Firefox */
    -ms-user-select: none; /* IE10+/Edge */
    user-select: none; /* Standard syntax */
}

@mixin pct($pct) {
    width: #{$pct};
    position: relative;
    margin: 0 auto;
}

@mixin triangle($width, $height, $color, $direction) {
    $width: $width/2;
    $color-border-style: $height solid $color;
    $transparent-border-style: $width solid transparent;
    height: 0;
    width: 0;

    @if $direction==up {
        border-bottom: $color-border-style;
        border-left: $transparent-border-style;
        border-right: $transparent-border-style;
    } @else if $direction==right {
        border-left: $color-border-style;
        border-top: $transparent-border-style;
        border-bottom: $transparent-border-style;
    } @else if $direction==down {
        border-top: $color-border-style;
        border-left: $transparent-border-style;
        border-right: $transparent-border-style;
    } @else if $direction==left {
        border-right: $color-border-style;
        border-top: $transparent-border-style;
        border-bottom: $transparent-border-style;
    }
}

6.2🌍【全局过度】

  • 🌍样式重置src/styles/base/transition/index.scss
// 全局过度 css

/* fade */
.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.28s;
}

.fade-enter,
.fade-leave-active {
    opacity: 0;
}

/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
    transition: all 0.5s;
}

.fade-transform-enter {
    opacity: 0;
    transform: translateX(-30px);
}

.fade-transform-leave-to {
    opacity: 0;
    transform: translateX(30px);
}

/* breadcrumb transition */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
    transition: all 0.5s;
}

.breadcrumb-enter,
.breadcrumb-leave-active {
    opacity: 0;
    transform: translateX(20px);
}

.breadcrumb-move {
    transition: all 0.5s;
}

.breadcrumb-leave-active {
    position: absolute;
}

6.3🌍【CSS 框架】

  • 🌍通过 npm 安装 Tailwind
yarn add tailwindcss@latest -D
yarn add  postcss@latest-D
yarn add  autoprefixer@latest -D
  • 🌍创建您的配置文件
npx tailwindcss init -p
  • 🌍 配置文件postcss.config.js
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
  • 🌍 配置文件tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  // 配置 Tailwind 来移除生产环境下没有使用到的样式声明
  purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
  content: [],
  theme: {
    extend: {},
  },
  plugins: [],
}
  • 🌍 在您的 CSS 中引入 Tailwind
@tailwind base;
@tailwind components;
@tailwind utilities;

七、状态管理

💡Tips:pinia-plugin-persistedstate 插件可以使 Pinia 存储的持久性变得更简单和可配置

  • 类似于 vuex-persistedstate 的 API
  • 单个 Store 的配置
  • 自定义存储方式和自定义序列化数据

7.1🤡【基本配置】

💡Tips:安装依赖

yarn add pinia@next
yarn add pinia-plugin-persistedstate

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

  • 🤡 vuex-persistedstatesrc/store/index.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedState from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedState)

export default pinia
  • 🤡 main.tsmain.ts
import { createApp } from 'vue'
import App from './App.vue'
import pinia from "/@/store"; //状态管理
const app = createApp(App)
app.use(pinia)
app.mount('#app')

💡Tips:store文件夹下新建modules文件夹:用于模块化管理

  • 🤡定义一个 Storesrc/store/modules/test.ts - 未持久化
/**
 * @Description  : Pinia示例-未持久化
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)
    function increment() {
        count.value++
    }

    return { count, doubleCount, increment }
})
  • √🤡定义一个 Storesrc/store/modules/test.ts - 持久化
/**
 * @Description  : Pinia示例-持久化
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
    const count = ref(0)
    const doubleCount = computed(() => count.value * 2)
    function increment() {
        count.value++
    }

    return { count, doubleCount, increment }
}, {
    persist: true,
})

💡Tips:当点击 increment 按钮时,响应属性 count 和计算属性 doubleCount 都会改变

  • 刷新页面后可以看到,它们也都显示为刷新之前的数据,说明数据已经被持久化了
<template>
  {{ countStore.count }}
  <hr>
  {{ countStore.doubleCount }}
  <hr>
  <button @click="countStore.increment">increment</button>
</template>
<script setup>
import { useCounterStore } from "../../store/modules/user"
const countStore = useCounterStore()
</script>

💡Tips插件预先配置了以下内容 - 🚀参考链接

  • localStorage 作为存储
  • store.$id 作为存储的默认键,即 defineStore 的第一个参数
  • JSON.stringify/JSON.parse 作为序列化器/反序列化器
  • 所有属性都会被持久化到本地存储中
    在这里插入图片描述

7.2🤡【基本示例】

🤡创建三个文件夹:模块化管理

  • 🤡Pinia - 持久键值src/store/constant/index.ts
/**
 * @Description  : Pinia-持久性Key
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
export const ADMIN_INFO = 'AdminInfo';
  • 🤡Store - 变量类型src/store/interface/index.ts
/**
 * @Description  : store-变量类型
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
export interface AdminInfo {
    userName: string;
    nickName: string;
    avatar: string;
    token: string;
    refreshToken: string;
}
  • 🤡Pinia - 实例管理(Option Store)src/store/modules/adminInfo.ts
/**
 * @Description  : Pinia示例
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { defineStore } from 'pinia';
import { ADMIN_INFO } from '/@/store/constant';
import { AdminInfo } from '/@/store/interface';

export const useAdminInfoState = defineStore({
    id: 'AdminInfo',
    state: (): AdminInfo => {
        return {
            userName: 'userName',
            nickName: 'nickName',
            avatar: 'avatar',
            token: 'tokena',
            refreshToken: 'refreshToken',
        };
    },
    actions: {
        /**
         * @Description :设置state全部值
         * @param state
         */
        setState(state: AdminInfo) {
            this.$state = { ...this.$state, ...state };
        },
        /**
         * @Description : 通过键设置值
         * @param value 值
         * @param key state的键
         */
        setStateByKey(key: any, value: string) {
                this[key] = value;
        },
        /**
         * 设置Token
         * @param value token
         * @param key state的键
         */
        setToken(key: 'token' | 'refreshToken', value: string) {
            this[key] = value;
        },
        /**
         * 获取Token
         * @param key state的键
         */
        getToken(key: 'auth' | 'refresh' = 'auth') {
            return key === 'auth' ? this.token : this.refreshToken;
        },
        /**
         * @Description : 移除Token
         */
        removeToken() {
            this.token = '';
            this.refreshToken = '';
        },
    },
    persist: {
        key: ADMIN_INFO,
    },
});

7.3🤡【基本使用】

🤡直接获取:获取state

<template>
  <div>{{useAdminInfo.userName}}</div>
  <div>{{useAdminInfo.nickName}}</div>
  <div>{{useAdminInfo.avatar}}</div>
  <div>{{useAdminInfo.token}}</div>
</template>
<script setup lang="ts">
import { useAdminInfoState } from '/@/store/modules/adminInfo'
const useAdminInfo = useAdminInfoState()
</script>

🤡computed获取:获取state

<template>
  <div>{{userName}}</div>
</template>
<script setup lang="ts">
import { useAdminInfoState } from '/@/store/modules/adminInfo'
const useAdminInfo = useAdminInfoState()
//响应式的
const userName=computed(()=>useAdminInfo.userName)
</script>

🤡直接使用:直接使用getters

export const useStore = defineStore('main', {
  state: () => ({
    count: 0,
  }),
  getters: {
    // 自动推断出返回类型是一个 number
    doubleCount(state) {
      return state.count * 2
    },
    // 返回类型**必须**明确设置
    doublePlusOne(): number {
      // 整个 store 的 自动补全和类型标注 ✨
      return this.doubleCount + 1
    },
  },
})
<script setup>
const store = useCounterStore()
store.count = 3
store.doubleCount // 6
</script>

🤡直接使用:直接使用getters

<script setup lang="ts">
import { useAdminInfoState } from '/@/store/modules/adminInfo'
const useAdminInfo = useAdminInfoState()
useAdminInfo.getToken()
</script>

7.4🤡【定义插件】

- 为 store 添加新的属性
- 定义 store 时增加新的选项
- 为 store 增加新的方法
- 包装现有的方法
- 改变甚至取消 action
- 实现副作用,如本地存储
- 仅应用插件于特定 store

🤡 Pinia 插件是一个函数,可以选择性地返回要添加到 store 的属性。它接收一个可选参数,即 context。

export function myPiniaPlugin(context) {
  context.pinia // 用 `createPinia()` 创建的 pinia。 
  context.app // 用 `createApp()` 创建的当前应用(仅 Vue 3)。
  context.store // 该插件想扩展的 store
  context.options // 定义传给 `defineStore()` 的 store 的可选对象。
  // ...
}
  • 然后用 pinia.use() 将这个函数传给 pinia:
pinia.use(myPiniaPlugin)

🤡 自定义持久化插件

  • 🤡简洁版持久化插件src/store/plugin/persistence.ts

/**
 * @Description  : 简洁版持久化插件:使用storeId作为Key进行持久化
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import {PiniaPluginContext} from "pinia";

export function persistenceState(context:PiniaPluginContext) {
    /**
     * @Description : 每次启动时都执行
     * 根据stateId从localStorage获取数据
     */
    const  currentState = JSON.parse(localStorage.getItem(context.store.$id)||'{}')
    context.store.$patch(currentState)

    /**
     * @Description  : 每次state发生变化时执行
     * @param {state} : oldState 原来的数据
     * @param {state} : newState 修改的数据
     * 都要把state保存到localStorage里面
     */
    context.store.$subscribe((oldState,newState)=>{
        console.log(newState)
        localStorage.setItem(oldState.storeId, JSON.stringify(newState))
    },{
        detached : true
    })
}
  • 🤡使用持久化插件src/store/index.ts
/**
 * @Description  : Pinia 存储的持久性
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import { createPinia } from 'pinia';
import {persistenceState} from "/@/store/plugin/persistence";

const pinia = createPinia();
pinia.use(persistenceState);

export default pinia;

在这里插入图片描述


八、路由配置

8.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>

8.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;

8.3🍑【组件跳转】

  • 🍑 所以我们不能再直接访问 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,
        },
      })
    }
  },
}

8.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'
         }
     })
 }

8.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>

九、封装工具类

9.1🛞【基本配置】

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

/*
 * @Description  :
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import fileNameUtils from './modules/fileName';
export default {
    install(app) {
        const modules = import.meta.glob('./modules/*.ts', { eager: true });

        for (const path in modules) {
            const method: any = modules[path],
                fileName = fileNameUtils.getFileName(path);

            if (method.default !== undefined) {
                Object.keys(method).forEach(() => {
                    app.config.globalProperties['$' + fileName] = method.default;
                });
            } else {
                // 将该方法注册为 $fileName 的全局方法
                app.config.globalProperties['$' + fileName] = method;
            }
        }
    },
};

💡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')

9.2🛞【基本使用】

🛞 Tips:app.use(utils,true)

  • 🛞 < script setup lang=“ts” >
<template>
    <div class="app-container">
        <a-button @click="message">message</a-button>
    </div>
</template>
<script setup lang="ts">
const model = inject('model');
function message() {
    model.message('消息提示', 'success');
}
</script>
  • 🛞 < script lang=“ts” >
<template >
<div class="app-container">
  <a-button @click="message">message</a-button>
</div>
</template>
<script lang="ts">

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

💡Tips:🛞app.use(utils,false)

<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>

9.3🛞【常用工具】

🛞Tips本地缓存 | 会话缓存src/utils/modules/cache.ts

/*
 * @Description  : 本地缓存 | 会话缓存
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
/**
 * window.localStorage
 * @method set 设置
 * @method get 获取
 * @method remove 移除
 * @method clear 移除全部
 */
export const Local = {
    set(key: string, val: any) {
        window.localStorage.setItem(key, JSON.stringify(val));
    },
    get(key: string) {
        const json: any = window.localStorage.getItem(key);

        return JSON.parse(json);
    },
    remove(key: string) {
        window.localStorage.removeItem(key);
    },
    clear() {
        window.localStorage.clear();
    },
};

/**
 * window.sessionStorage
 * @method set 设置会话缓存
 * @method get 获取会话缓存
 * @method remove 移除会话缓存
 * @method clear 移除全部会话缓存
 */
export const Session = {
    set(key: string, val: any) {
        window.sessionStorage.setItem(key, JSON.stringify(val));
    },
    get(key: string) {
        const json: any = window.sessionStorage.getItem(key);

        return JSON.parse(json);
    },
    remove(key: string) {
        window.sessionStorage.removeItem(key);
    },
    clear() {
        window.sessionStorage.clear();
    },
};

🛞Tips消息提示src/utils/modules/model.ts

/*
 * @Description  : 用于消息提示、确认消息和提交内容
 * @Version      : V1.0.0
 * @Author       : SilenceLamb
 */
import { message, notification, Modal, Spin } from 'ant-design-vue';
let loadingInstance;

export default {
    // 消息提示
    message(content, type: 'error' | 'success' | 'warning' | 'info') {
        message[type](content).then();
    },
    // 弹出提示 -(弹框)
    alert(title: string, content: any, type: 'error' | 'success' | 'warning' | 'info') {
        Modal[type]({
            title: title,
            content: content,
        });
    },
    // 通知提示 -(通知)
    notify(title: any, content: any, type: 'error' | 'success' | 'warning' | 'info') {
        notification[type]({
            message: title,
            description: content,
        });
    },
    // 确认窗体 -(窗体)
    confirm(title: any, content: any) {
        return Modal.confirm({
            title: title,
            content: content,
            okText: '确认',
            cancelText: '取消',
            type: 'warning',
        });
    },
};

🛞Tips参数处理src/utils/modules/params.ts

/*
 * @Description  : 参数处理工具
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
export default {
    /**
     * @Description  :请求参数处理
     * @param params
     * @return {string}
     */
    tansParams(params:any):any {
        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
    },
}

🛞TipsToken工具src/utils/modules/token.ts

/*
 * @Description  : Token管理工具
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export default {
    /**
     * @Description  获取Token
     * @returns  token
     */
    getToken() {
        return Cookies.get(TokenKey)
    },

    /**
     * @Description 设置Token
     * @param {*} token
     */
    setToken(token) {
        return Cookies.set(TokenKey, token)
    },

    /**
     * @Description 移除Token
     */
    removeToken() {
        return Cookies.remove(TokenKey)
    },
}

🛞Tips:获取文件名:src/utils/modules/fileName.ts

/*
 * @Description  : 获取文件名工具
 * @Author       : SilenceLamb
 * @Version      : V1.0.0
 */
export default {
    /**
     * @Description 获取最后一个文件夹
     * @param {string} filePath
     * @returns {string} fileName 子文件夹名
     */
    getLastFileName(filePath) {
        const matches = filePath.substring(0, filePath.lastIndexOf('/')).split('/').pop();

        return matches;
    },

    /**
     * @Description 获取文件名
     * @param {string} filePath
     * @returns {string} fileName 文件名
     */
    getFileName(filePath) {
        // 将路径拆分为数组
        const parts = filePath.split('/'),
            // 获取数组中的最后一个元素
            fileName = parts[parts.length - 1];
        // 获取文件名并返回

        return fileName.replace('.ts', '');
    },
};

十、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>

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') }],
    },
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Silence Lamb

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

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

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

打赏作者

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

抵扣说明:

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

余额充值