• 构建工具使用 vue3 推荐的 vite;
  • 状态管理使用 pinia,该库的作者也是 vuex 的核心成员;
  • UI 组件库使用尤大推荐的 naiveui。 欢迎各位大神指导。话不多说,直接开撸。

# 一、初始化 vite 模板

国内使用最好将 yarn 切换到淘宝源:

yarn config set registry https://registry.npmmirror.com/
yarn create @vitejs/app

之后会让你输入项目名称,选择框架等。这里我们输入名称为 jianshu,框架选择 vue,回车
然后进入项目中,输入 yarn 回车,安装依赖

cd jianshu && yarn

安装完成之后,使用 yarn dev 命令启动开发服务。
打开 localhost:3000 地址,可以看到 vite 默认的欢迎页面。

# 二、安装插件

  • axios:网络请求;
  • sass:css 预处理;
  • js-md5:登录密码使用 md5 加密,如果不需要则可以不用安装;
  • pinia:状态管理;
  • moment:时间格式化;
  • naive-ui:UI 组件库;
  • vfonts:naiveui 的字体库;
  • @vicons/antd:图标库,可自行选择;
  • vue-router:路由;
  • unplugin-auto-import:自动导入 composition api;
  • unplugin-vue-components:自动注册组件;
yarn add axios sass js-md5 pinia moment naive-ui vfonts @vicons/antd vue-router unplugin-auto-import unplugin-vue-components

全部安装完成后,删除 components 下默认的页面和 assets 下的 logo.png,然后进行项目的配置

# 三、项目配置

# 1. unplugin-auto-import,unplugin-vue-components,naive-ui:

打开 vite.config.js 文件,引入组件

import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';

然后在 plugins 内添加配置

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports: ['vue']
    }),
    Components({
      resolvers: [NaiveUiResolver()]
    })
  ]
});

这里 naiveui 使用的是按需自动引入,具体可参考官方文档:按需引入(Tree Shaking) - Naive UI 添加了一些打包的配置,不需要可以忽略。配置完成后的样子:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
// 获取当前时间戳
const timeStamp = new Date().getTime();
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      imports: ['vue']
    }),
    Components({
      resolvers: [NaiveUiResolver()]
    })
  ],
  // 自定义端口号,默认为 3000
  server: {
    port: 3001
  },
  // 打包配置
  build: {
    // 文件夹名称
    outDir: 'dist',
    // 去掉 console 语句
    terserOptions: {
      compress: {
        drop_console: true
      }
    },
    compress: {
      drop_console: true,
      drop_debugger: true
    },
    // 文件名
    rollupOptions: {
      output: {
        entryFileNames: `assets/[name].${timeStamp}.js`,
        chunkFileNames: `assets/[name].${timeStamp}.js`,
        assetFileNames: `assets/[name].${timeStamp}.[ext]`
      }
    }
  }
});

# 2. 配置 pinia:

在 src 目录下新建 plugins 文件夹,然后新建 pinia.js:

import { createPinia } from 'pinia';
const pinia = createPinia();
export default pinia;

到 src 目录下新建 store 目录,然后新建 user.js:

import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: JSON.parse(localStorage.getItem('userInfo')),
    token: localStorage.getItem('token')
  }),
  actions: {
    LoginIn(data) {
      this.token = data.token;
      this.userInfo = data;
      localStorage.setItem('userInfo', JSON.stringify(data));
      localStorage.setItem('token', data.token);
      localStorage.setItem('expire', data.expire);
    },
    LoginOut() {
      localStorage.removeItem('userInfo');
      localStorage.removeItem('token');
      localStorage.removeItem('expire');
      location.href = '/';
    }
  }
});

user.js 里面添加了一些基本的用户信息和登录操作
pinia 和 vuex 的用法不一样,具体可以参考官方文档:Home | Pinia

# 3. 配置 axios:

在 plugins 下新建 axios.js 文件:

import axios from 'axios';
import pinia from './pinia';
import { useUserStore } from '../store/user';
const user = useUserStore(pinia);
// api
const apiHost = ''; // 接口地址
let http = axios.create({
  // 当前环境为线上时,自动使用 production 环境变量里的 VITE_API_BASEURL 线上接口地址
  baseURL: import.meta.env.MODE === 'production' ? import.meta.env.VITE_API_BASEURL : apiHost,
  timeout: 60000,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8;'
  }
});
// 参数配置
let needLoading = true, // 是否需要 loading
  needErrorNotify = true, // 是否需要错误提示
  requestCount = 0; // 请求数量,用于 loading 控制,避免多个接口并发时重复 loading
//loading 操作
const ShowLoading = () => {
  if (requestCount === 0) {
    $uiMsg.loading('加载中');
  }
  requestCount++;
};
const HideLoading = () => {
  requestCount--;
  if (requestCount <= 0) {
    requestCount = 0;
    $uiMsg.removeMessage();
  }
};
http.interceptors.request.use(config => {
  // 切换组件时,取消还未完成的请求
  config.cancelToken = new axios.CancelToken(cancel => {
    window.__axiosPromiseArr.push({
      url: config.url,
      cancel
    });
  });
  needLoading && ShowLoading();
  if (user.token) {
    config.headers.Authorization = 'Bearer ' + user.token;
  }
  return config;
});
http.interceptors.response.use(
  response => {
    if (needLoading) {
      HideLoading();
    }
    // 换 token 时报错,退出登录
    if (response.config.url.indexOf('/token') !== -1 && response.data.code !== 200) {
      HideLoading();
      $uiMsg.error('登录已过期,请重新登录', () => {
        user.LoginOut();
      });
      return;
    }
    return response.data;
  },
  error => {
    HideLoading();
    if (error.response && error.response.status === 401) {
      //token 过期访问刷新 token 的接口
      return http
        .post('/token', JSON.stringify(user.token))
        .then(res => {
          if (res.code !== 200) {
            $uiMsg.error(res.msg);
            return;
          }
          // 更新 pinia 里的数据
          user.LoginIn(res.body);
          // 刷新 token 后重新请求之前的接口
          const config = error.response.config;
              config.headers.Authorization = 'Bearer ' + res.body.token;
          return http(config);
        })
        .catch(error => {
          $uiMsg.error(error.msg);
        });
    } else {
      $uiMsg.error('网络错误,请稍后再试');
      return Promise.reject(error);
    }
  }
);
const get = (url, params, _needLoading = true, _needErrorNotify = true) => {
  needLoading = _needLoading;
  needErrorNotify = _needErrorNotify;
  return new Promise((resolve, reject) => {
    let _timestamp = new Date().getTime();
    http
      .get(url, { params })
      .then(res => {
        if (new Date().getTime() - _timestamp < 200) {
          setTimeout(() => {
            if (res?.code === 200) {
              resolve(res);
            } else {
              needErrorNotify && res && res.msg && $uiMsg.error(res.msg);
              reject(res);
            }
          }, 200);
        } else {
          if (res?.code === 200) {
            resolve(res);
          } else {
            needErrorNotify && res && res.msg && $uiMsg.error(res.msg);
            reject(res);
          }
        }
      })
      .catch(error => {
        reject(error);
      });
  });
};
const post = (url, params, _needLoading = true, _needErrorNotify = true) => {
  needLoading = _needLoading;
  needErrorNotify = _needErrorNotify;
  return new Promise((resolve, reject) => {
    let _timestamp = new Date().getTime();
    http
      .post(url, params)
      .then(res => {
        if (new Date().getTime() - _timestamp < 200) {
          setTimeout(() => {
            if (res?.code === 200) {
              resolve(res);
            } else {
              needErrorNotify && res && res.msg && $uiMsg.error(res.msg);
              reject(res);
            }
          }, 200);
        } else {
          if (res?.code === 200) {
            resolve(res);
          } else {
            needErrorNotify && res && res.msg && $uiMsg.error(res.msg);
            reject(res);
          }
        }
      })
      .catch(error => {
        reject(error);
      });
  });
};
export { get, post };

这里使用了环境变量来自动选择 api 接口地址,在 src 同级目录下新建.env.production 文件:

NODE_ENV = production
# 线上接口请求地址
VITE_API_BASEURL = ''

项目打包后会自动使用 production 环境变量里的 VITE_API_BASEURL

# 3. 配置 vue-router:

在 plugins 下新建 router.js:

import { createRouter, createWebHistory } from 'vue-router';
import pinia from './pinia';
import { useUserStore } from '../store/user';
const user = useUserStore(pinia);
// 不需要权限的页面
const constantRoutes = [
  {
    // 登录
    path: '/login',
    name: 'login',
    component: () => import('../views/login/index.vue')
  },
  {
    // 404
    path: '/:pathMatch(.*)',
    name: 'notFound',
    component: () => import('../views/error/notFound.vue')
  },
  {
    // 无权限
    path: '/noPermission',
    name: 'noPermission',
    component: () => import('../views/error/noPermission.vue')
  }
];
const asyncRoutes = {
  path: '/',
  name: 'main',
  component: () => import('../views/mainPage.vue'),
  children: [
    {
      // 首页
      path: '/',
      name: 'home',
      component: () => import('../views/home/index.vue')
    },
    {
      // 用户管理
      path: '/settingUser',
      name: 'settingUser',
      component: () => import('../views/setting/user.vue')
    }
  ]
};
const router = createRouter({
  history: createWebHistory('/'),
  routes: constantRoutes
});
router.addRoute(asyncRoutes);
router.beforeEach((to, from, next) => {
  // 切换 router 时,取消 pending 中的请求
  if (window.__axiosPromiseArr) {
    window.__axiosPromiseArr.forEach((ele, ind) => {
      ele.cancel();
      delete window.__axiosPromiseArr[ind];
    });
  }
  //token 过期
  if (localStorage.getItem('expires') && (new Date().getTime() - localStorage.getItem('expires')) / 1000 > 1) {
    $uiMsg.error('登录失效,请重新登录', () => {
      localStorage.removeItem('userInfon');
      localStorage.removeItem('token');
      localStorage.removeItem('expires');
      location.href = '/login';
    });
    return;
  }
  // 登录判断
  if (user.token) {
    if (to.path === '/login') {
      next({ path: '/' });
    } else {
      // 权限判断
      next();
    }
  } else {
    if (to.path === '/login') {
      next();
    } else {
      next({ name: 'login' });
    }
  }
});
// 跳转完成后,将滚动条位置重置
router.afterEach(to => {
  window.scrollTo(0, 0);
});
export default router;

router 中引入了一些默认页面,我们来创建一些简单的页面组件:

/src/views/error/notFound.vue

<div class="not-found">
    <svg width="500px" viewBox="0 0 770 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>6</title>
    <desc>Created with Sketch.</desc>
    <defs>
        <path d="M451.167458,89.4511247 C403.062267,29.8170416 338.891681,0 258.655699,0 C138.301726,0 69.263862,60.1782766 27.9579265,152.101254 C-13.3480089,244.024231 -12.6661889,369.858107 55.6494632,409.696073 C123.965115,449.534039 210.08756,459.743134 340.957927,438.489218 C471.828293,417.235303 508.472089,464.890133 589.496232,451.689675 C670.520376,438.489218 748.359885,414.0324 766.111966,329.133852 C783.864046,244.235303 714.426288,177.226358 677.67078,152.101254 C640.915272,126.97615 569.728461,175.208649 519.030321,160.235303 C485.231561,150.253072 462.610607,126.658346 451.167458,89.4511247 Z" id="path-1"></path>
        <path d="M0.816264722,0 L370.714266,0 L370.714266,180.257104 L402.92544,180.257104 C424.638356,218.017298 440.878062,240.166012 451.644559,246.703245 L119.609274,243.521057 C112.14379,243.449507 105.100966,240.045172 100.407325,234.239285 C89.3772632,220.595444 81.4909058,210.013897 76.7482527,202.494643 C68.1135311,188.804698 66.7639588,180.257104 51.9095874,180.257104 C37.055216,180.257104 30.8879728,215.663472 26.2206784,229.536211 C21.5533841,243.408951 4.54747351e-13,240.685607 4.54747351e-13,229.536211 C4.54747351e-13,222.103281 0.272088241,145.59121 0.816264722,0 Z" id="path-3"></path>
        <polygon id="path-5" points="0 25.9764499 26.0411111 2.29150032e-13 52.9088048 25.9764499"></polygon>
        <polygon id="path-7" points="-2.27373675e-13 28.2395915 28.1433883 3.41060513e-13 54.0330976 28.2395915"></polygon>
        <path d="M3.53184776,0 L61.4681522,0 C63.1250065,4.9985658e-15 64.4681522,1.34314575 64.4681522,3 C64.4681522,3.16257855 64.4549364,3.32488807 64.4286352,3.48532508 L55.4122418,58.4853251 C55.1745077,59.9355031 53.921294,61 52.4517588,61 L12.5482412,61 C11.078706,61 9.82549232,59.9355031 9.58775821,58.4853251 L0.571364767,3.48532508 C0.303327126,1.85029547 1.41149307,0.307554646 3.04652268,0.0395170047 C3.20695969,0.0132158559 3.36926922,-1.30240244e-15 3.53184776,0 Z" id="path-9"></path>
        <path d="M-1.42108547e-14,115.48446 C1.32743544,94.0102656 2.89289856,78.9508436 4.69638937,70.3061937 C8.43003277,52.4097675 15.5176097,37.8448008 19.4787027,30.195863 C29.7253967,10.409323 39.7215535,5.31301339 44.6820442,2.63347577 C49.6425348,-0.0460618448 60.3007481,-1.62222357 66.327433,2.63347577 C72.3541179,6.88917511 74.5668372,13.0533931 73.7454921,23.1564165 C72.924147,33.2594398 65.469448,39.1497458 58.0193289,42.7343523 C50.5692098,46.3189588 31.0128594,60.1734323 19.4787027,74.1118722 C11.7892649,83.4041655 5.29636401,97.195028 -1.42108547e-14,115.48446 Z" id="path-11"></path>
        <path d="M0,61.382873 C12.627563,35.4721831 22.8842273,18.9178104 30.7699929,11.7197549 C42.5986412,0.922671591 57.9238693,-1.5327187 66.3547392,0.814866828 C74.7856091,3.16245236 78.9526569,14.6315037 74.3469666,21.3628973 C69.7412762,28.0942909 65.4378728,28.0568843 50.8423324,30.6914365 C36.246792,33.3259886 29.5659376,36.8930178 23.8425136,39.4010039 C21.5824174,40.3913708 15.331987,43.4769377 10.1725242,48.4356558 C7.80517763,50.7108935 4.41433624,55.0266325 0,61.382873 Z" id="path-13"></path>
        <path d="M-2.08995462,65.6474954 C12.5975781,38.2270573 23.8842273,20.9178104 31.7699929,13.7197549 C43.5986412,2.92267159 58.9238693,0.467281299 67.3547392,2.81486683 C75.7856091,5.16245236 79.9526569,16.6315037 75.3469666,23.3628973 C70.7412762,30.0942909 66.4378728,30.0568843 51.8423324,32.6914365 C37.246792,35.3259886 30.5659376,38.8930178 24.8425136,41.4010039 C22.5824174,42.3913708 13.2420323,47.7415601 8.08256956,52.7002782 C5.71522301,54.9755159 2.32438162,59.2912549 -2.08995462,65.6474954 Z" id="path-15"></path>
        <path d="M70.3618111,117.305105 C65.1514723,93.5149533 59.5592828,76.7727476 53.5852425,67.0784883 C44.6241821,52.5370993 33.2521675,43.1631445 21.9273327,38.7089848 C10.6024978,34.2548251 1.37005489,28.3143707 0.166250333,19.5991494 C-1.03755422,10.8839281 4.30184276,1.89650161 15.9982131,0.359853321 C27.6945835,-1.17679496 39.680528,1.89650161 50.3232751,15.6556441 C60.9660221,29.4147866 71.7898492,71.0503233 71.7898492,87.5111312 C71.7898492,98.4850031 71.3138365,108.416328 70.3618111,117.305105 Z" id="path-17"></path>
        <path d="M40.4361627,109.727577 C42.2080966,71.0333394 41.2052946,44.753324 37.4277569,30.8875312 C31.7614504,10.088842 22.8541813,-1.27827958 11.3728741,0.114578571 C-0.108432993,1.50743672 -2.5866861,11.539269 2.54272088,19.2423116 C7.67212787,26.9453541 22.1964111,48.5363293 27.3543068,61.4631547 C30.7929039,70.0810384 35.1535225,86.1691793 40.4361627,109.727577 Z" id="path-19"></path>
        <path d="M86.8630745,43.7959111 C72.5806324,23.5140129 56.8667378,10.125403 39.7213908,3.6300812 C14.0033702,-6.11290144 -7.10542736e-15,5.90110838 -7.10542736e-15,14.52167 C-7.10542736e-15,23.1422316 6.80949202,28.0268155 17.0489556,28.0268155 C27.2884192,28.0268155 43.7234658,26.0070237 58.8280258,34.5737997 C68.8977326,40.2849837 79.1842128,49.927944 89.6874666,63.5026805 L86.8630745,43.7959111 Z" id="path-21"></path>
        <circle id="path-23" cx="42" cy="42" r="42"></circle>
    </defs>
    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="画板" transform="translate(-2046.000000, -1809.000000)">
            <g id="6" transform="translate(2046.223123, 1809.764697)">
                <g id="编组-89" transform="translate(0.109175, 0.235303)">
                    <mask id="mask-2" fill="white">
                        <use xlink:href="#path-1"></use>
                    </mask>
                    <use id="路径-307" fill="#F3F7FF" xlink:href="#path-1"></use>
                    <rect id="矩形" fill="#D0DEFE" mask="url(#mask-2)" x="0" y="362" width="791" height="112"></rect>
                    <rect id="矩形" fill="#C4D6FF" mask="url(#mask-2)" transform="translate(395.500000, 353.500000) scale(1, -1) translate(-395.500000, -353.500000) " x="0" y="345" width="791" height="17"></rect>
                </g>
                <rect id="矩形" stroke="#979797" fill="#D8D8D8" x="632.609175" y="381.735303" width="39" height="10"></rect>
                <rect id="矩形" stroke="#979797" fill="#D8D8D8" x="632.609175" y="402.735303" width="39" height="10"></rect>
                <rect id="矩形" stroke="#979797" fill="#D8D8D8" x="628.609175" y="392.735303" width="39" height="10"></rect>
                <g id="编组-88" transform="translate(547.109175, 141.235303)">
                    <rect id="矩形" fill="#BCD4FF" x="0" y="0" width="144" height="281"></rect>
                    <rect id="矩形" fill="#EDF4FF" x="5" y="10.5" width="131" height="262"></rect>
                    <rect id="矩形" fill="#9EBEF8" x="106" y="10.5" width="30" height="262"></rect>
                    <rect id="矩形" fill="#BCD4FF" x="56" y="136" width="80" height="8"></rect>
                    <rect id="矩形" fill="#BCD4FF" x="56" y="203" width="80" height="8"></rect>
                    <g id="编组-87" transform="translate(63.000000, 153.000000)">
                        <rect id="矩形" fill="#FFECC8" x="29" y="0" width="40" height="50"></rect>
                        <rect id="矩形" fill="#FFE2AC" x="58" y="0" width="11" height="50"></rect>
                        <rect id="矩形" fill="#D3E1FF" x="14" y="0" width="40" height="50"></rect>
                        <rect id="矩形" fill="#BDD2FF" x="43" y="0" width="11" height="50"></rect>
                        <rect id="矩形" fill="#FFECC8" x="0" y="8" width="40" height="42"></rect>
                        <rect id="矩形" fill="#FFE2AC" x="29" y="8" width="11" height="42"></rect>
                    </g>
                    <g id="编组-87" transform="translate(63.000000, 222.000000)">
                        <rect id="矩形" fill="#FFECC8" x="29" y="0" width="40" height="50"></rect>
                        <rect id="矩形" fill="#FFE2AC" x="58" y="0" width="11" height="50"></rect>
                        <rect id="矩形" fill="#D3E1FF" x="14" y="0" width="40" height="50"></rect>
                        <rect id="矩形" fill="#BDD2FF" x="43" y="0" width="11" height="50"></rect>
                        <rect id="矩形" fill="#FFECC8" x="0" y="8" width="40" height="42"></rect>
                        <rect id="矩形" fill="#FFE2AC" x="29" y="8" width="11" height="42"></rect>
                    </g>
                    <g id="编组-87" transform="translate(63.000000, 86.000000)">
                        <rect id="矩形" fill="#FFECC8" x="29" y="0" width="40" height="50"></rect>
                        <rect id="矩形" fill="#FFE2AC" x="58" y="0" width="11" height="50"></rect>
                        <rect id="矩形" fill="#D3E1FF" x="14" y="0" width="40" height="50"></rect>
                        <rect id="矩形" fill="#BDD2FF" x="43" y="0" width="11" height="50"></rect>
                        <rect id="矩形" fill="#FFECC8" x="0" y="8" width="40" height="42"></rect>
                        <rect id="矩形" fill="#FFE2AC" x="29" y="8" width="11" height="42"></rect>
                    </g>
                </g>
                <path d="M206.109175,130.235303 L586.109175,130.235303 C596.0503,130.235303 604.109175,138.294177 604.109175,148.235303 L604.109175,417.235303 L604.109175,417.235303 L188.109175,417.235303 L188.109175,148.235303 C188.109175,138.294177 196.168049,130.235303 206.109175,130.235303 Z" id="矩形" fill="#DDE9FF"></path>
                <path d="M206.109175,130.235303 L586.109175,130.235303 C596.0503,130.235303 604.109175,138.294177 604.109175,148.235303 L604.109175,163.235303 L604.109175,163.235303 L188.109175,163.235303 L188.109175,148.235303 C188.109175,138.294177 196.168049,130.235303 206.109175,130.235303 Z" id="矩形" fill="#FFECC8"></path>
                <path d="M206.109175,130.235303 L586.109175,130.235303 C596.0503,130.235303 604.109175,138.294177 604.109175,148.235303 L604.109175,160.235303 L604.109175,160.235303 L188.109175,160.235303 L188.109175,148.235303 C188.109175,138.294177 196.168049,130.235303 206.109175,130.235303 Z" id="矩形" fill="#A4C3FC"></path>
                <circle id="椭圆形" fill="#FFBB3C" cx="210" cy="146" r="4"></circle>
                <circle id="椭圆形" fill="#ECF2FF" cx="223.109175" cy="145.235303" r="4"></circle>
                <g id="编组-86" transform="translate(210.109175, 178.235303)">
                    <mask id="mask-4" fill="white">
                        <use xlink:href="#path-3"></use>
                    </mask>
                    <use id="路径-289" fill="#FFFFFF" xlink:href="#path-3"></use>
                    <rect id="矩形" fill="#ECF2FF" mask="url(#mask-4)" x="50.8162647" y="180.401344" width="412" height="87"></rect>
                    <polygon id="路径" fill="#FFEAC2" fill-rule="nonzero" mask="url(#mask-4)" points="361.449861 8.85304449 361.449861 180.761462 360.449861 180.761462 360.449 9.853 14.449 9.853 14.449 223.853 27.9199219 223.853044 27.9199219 224.853044 13.4498606 224.853044 13.4498606 8.85304449"></polygon>
                </g>
                <path d="M333.259175,333.235303 L333.259175,308.935303 L350.659175,308.935303 L350.659175,298.885303 L333.259175,298.885303 L333.259175,226.135303 L321.709175,226.135303 L267.709175,297.235303 L267.709175,308.935303 L321.559175,308.935303 L321.559175,333.235303 L333.259175,333.235303 Z M321.559175,298.885303 L278.059175,298.885303 L321.109175,242.185303 L321.559175,242.185303 L321.559175,298.885303 Z M399.109175,335.335303 C411.859175,335.335303 421.459175,329.635303 428.059175,318.385303 C433.759175,308.785303 436.609175,295.885303 436.609175,279.685303 C436.609175,263.485303 433.759175,250.585303 428.059175,240.985303 C421.459175,229.585303 411.859175,224.035303 399.109175,224.035303 C386.209175,224.035303 376.609175,229.585303 370.159175,240.985303 C364.459175,250.585303 361.609175,263.485303 361.609175,279.685303 C361.609175,295.885303 364.459175,308.785303 370.159175,318.385303 C376.609175,329.635303 386.209175,335.335303 399.109175,335.335303 Z M399.109175,324.835303 C389.509175,324.835303 382.609175,319.585303 378.409175,309.385303 C375.409175,302.035303 373.909175,292.135303 373.909175,279.685303 C373.909175,267.085303 375.409175,257.185303 378.409175,249.985303 C382.609175,239.635303 389.509175,234.535303 399.109175,234.535303 C408.709175,234.535303 415.609175,239.635303 419.809175,249.985303 C422.809175,257.185303 424.459175,267.085303 424.459175,279.685303 C424.459175,292.135303 422.809175,302.035303 419.809175,309.385303 C415.609175,319.585303 408.709175,324.835303 399.109175,324.835303 Z M513.259175,333.235303 L513.259175,308.935303 L530.659175,308.935303 L530.659175,298.885303 L513.259175,298.885303 L513.259175,226.135303 L501.709175,226.135303 L447.709175,297.235303 L447.709175,308.935303 L501.559175,308.935303 L501.559175,333.235303 L513.259175,333.235303 Z M501.559175,298.885303 L458.059175,298.885303 L501.109175,242.185303 L501.559175,242.185303 L501.559175,298.885303 Z" id="404" fill="#FFEAC2" fill-rule="nonzero"></path>
                <path d="M330.259175,330.235303 L330.259175,305.935303 L347.659175,305.935303 L347.659175,295.885303 L330.259175,295.885303 L330.259175,223.135303 L318.709175,223.135303 L264.709175,294.235303 L264.709175,305.935303 L318.559175,305.935303 L318.559175,330.235303 L330.259175,330.235303 Z M318.559175,295.885303 L275.059175,295.885303 L318.109175,239.185303 L318.559175,239.185303 L318.559175,295.885303 Z M396.109175,332.335303 C408.859175,332.335303 418.459175,326.635303 425.059175,315.385303 C430.759175,305.785303 433.609175,292.885303 433.609175,276.685303 C433.609175,260.485303 430.759175,247.585303 425.059175,237.985303 C418.459175,226.585303 408.859175,221.035303 396.109175,221.035303 C383.209175,221.035303 373.609175,226.585303 367.159175,237.985303 C361.459175,247.585303 358.609175,260.485303 358.609175,276.685303 C358.609175,292.885303 361.459175,305.785303 367.159175,315.385303 C373.609175,326.635303 383.209175,332.335303 396.109175,332.335303 Z M396.109175,321.835303 C386.509175,321.835303 379.609175,316.585303 375.409175,306.385303 C372.409175,299.035303 370.909175,289.135303 370.909175,276.685303 C370.909175,264.085303 372.409175,254.185303 375.409175,246.985303 C379.609175,236.635303 386.509175,231.535303 396.109175,231.535303 C405.709175,231.535303 412.609175,236.635303 416.809175,246.985303 C419.809175,254.185303 421.459175,264.085303 421.459175,276.685303 C421.459175,289.135303 419.809175,299.035303 416.809175,306.385303 C412.609175,316.585303 405.709175,321.835303 396.109175,321.835303 Z M510.259175,330.235303 L510.259175,305.935303 L527.659175,305.935303 L527.659175,295.885303 L510.259175,295.885303 L510.259175,223.135303 L498.709175,223.135303 L444.709175,294.235303 L444.709175,305.935303 L498.559175,305.935303 L498.559175,330.235303 L510.259175,330.235303 Z M498.559175,295.885303 L455.059175,295.885303 L498.109175,239.185303 L498.559175,239.185303 L498.559175,295.885303 Z" id="404" fill="#ACC9FF" fill-rule="nonzero"></path>
                <polygon id="路径-298" fill="#6EA1FF" fill-rule="nonzero" points="369.741481 26.3549544 369.741481 145.784171 368.741481 145.784171 368.741481 26.3549544"></polygon>
                <g id="编组-113" transform="translate(343.200370, 145.784171)">
                    <mask id="mask-6" fill="white">
                        <use xlink:href="#path-5"></use>
                    </mask>
                    <use id="路径-299" fill="#FFD078" xlink:href="#path-5"></use>
                    <polygon id="路径-299" fill="#FFBB3C" mask="url(#mask-6)" points="-3 25.9764499 23.0411111 1.77635684e-15 49.9088048 25.9764499"></polygon>
                </g>
                <polygon id="路径-300" fill="#6EA1FF" fill-rule="nonzero" points="254.30695 -0.00143864693 255.306945 0.00143864694 255.109173 68.7367415 254.109177 68.7338642"></polygon>
                <g id="编组-112" transform="translate(226.663559, 65.717848)">
                    <mask id="mask-8" fill="white">
                        <use xlink:href="#path-7"></use>
                    </mask>
                    <use id="路径-301" fill="#D2E2FF" xlink:href="#path-7"></use>
                    <polygon id="路径-301" fill="#A4C3FC" mask="url(#mask-8)" points="-3 28.2395915 25.1433883 -1.13686838e-13 51.0330976 28.2395915"></polygon>
                </g>
                <path d="M464.109175,72.2353029 L574.109175,72.2353029 C578.527453,72.2353029 582.109175,75.8170249 582.109175,80.2353029 L582.109175,152.269143 L582.109175,152.269143 L602.747625,174.760621 L464.163722,175.18059 C454.222643,175.210716 446.139383,167.1763 446.109258,157.23522 C446.109203,157.217038 446.109175,157.198855 446.109175,157.180672 L446.109175,90.2353029 C446.109175,80.2941774 454.168049,72.2353029 464.109175,72.2353029 Z" id="矩形" fill="#FFECC8"></path>
                <path d="M460.109175,69.2353029 L570.109175,69.2353029 C574.527453,69.2353029 578.109175,72.8170249 578.109175,77.2353029 L578.109175,149.269143 L578.109175,149.269143 L598.747625,171.760621 L460.163722,172.18059 C450.222643,172.210716 442.139383,164.1763 442.109258,154.23522 C442.109203,154.217038 442.109175,154.198855 442.109175,154.180672 L442.109175,87.2353029 C442.109175,77.2941774 450.168049,69.2353029 460.109175,69.2353029 Z" id="矩形" fill="#EBF2FF"></path>
                <rect id="矩形" fill="#FFFFFF" x="480" y="95" width="7" height="64"></rect>
                <rect id="矩形" fill="#FFFFFF" x="497.109175" y="95" width="7" height="64"></rect>
                <rect id="矩形" fill="#FFFFFF" x="514.109175" y="95" width="7" height="64"></rect>
                <rect id="矩形" fill="#FFFFFF" x="530.109175" y="95" width="7" height="64"></rect>
                <rect id="矩形" fill="#FFFFFF" x="546.109175" y="95" width="7" height="64"></rect>
                <polygon id="路径-302" fill="#A4C3FC" fill-rule="nonzero" points="466.970801 86.0695627 466.97 158.272 566.971883 158.272396 566.971883 159.272396 465.970801 159.272396 465.970801 86.0695627"></polygon>
                <polygon id="路径-304" fill="#979797" fill-rule="nonzero" points="559.240013 152.472555 559.909745 151.729952 567.687435 158.744424 559.937708 166.917681 559.21205 166.229626 566.256 158.8"></polygon>
                <path d="M547.776877,151.235303 L657.776877,151.235303 C662.195155,151.235303 665.776877,154.817025 665.776877,159.235303 L665.776877,231.269143 L665.776877,231.269143 L686.415326,253.760621 L547.831424,254.18059 C537.890344,254.210716 529.807085,246.1763 529.776959,236.23522 C529.776904,236.217038 529.776877,236.198855 529.776877,236.180672 L529.776877,169.235303 C529.776877,159.294177 537.835751,151.235303 547.776877,151.235303 Z" id="矩形" fill="#A4C3FC" transform="translate(608.096101, 202.735303) scale(-1, 1) translate(-608.096101, -202.735303) "></path>
                <path d="M542.776877,150.235303 L652.776877,150.235303 C657.195155,150.235303 660.776877,153.817025 660.776877,158.235303 L660.776877,230.269143 L660.776877,230.269143 L681.415326,252.760621 L542.831424,253.18059 C532.890344,253.210716 524.807085,245.1763 524.776959,235.23522 C524.776904,235.217038 524.776877,235.198855 524.776877,235.180672 L524.776877,168.235303 C524.776877,158.294177 532.835751,150.235303 542.776877,150.235303 Z" id="矩形" fill="#FFCA67" transform="translate(603.096101, 201.735303) scale(-1, 1) translate(-603.096101, -201.735303) "></path>
                <path d="M551.888365,105.031459 C555.290806,103.139777 558.513795,102.897668 562.237517,104.467631 L562.588104,104.620129 L562.17909,105.532657 C558.599379,103.928154 555.612548,104.105059 552.37429,105.905459 C550.368282,107.020755 548.58771,108.45472 545.16394,111.609463 L541.614214,114.898486 C538.015181,118.209826 536.087942,119.845252 533.11225,122.086913 C532.782184,122.335559 532.450805,122.581445 532.117803,122.824718 C528.104792,125.756407 523.934988,126.987135 519.313532,126.876779 C516.035171,126.798495 513.270144,126.221396 508.17289,124.737029 L505.737532,124.022849 C497.810115,121.733418 494.471662,121.366012 490.348408,122.889971 C482.286296,125.869735 475.026188,137.650266 468.664891,158.243664 L468.457777,158.918311 L467.501306,158.626481 C473.997113,137.336567 481.463921,125.107569 490.001728,121.951987 C494.55996,120.267261 498.129316,120.741123 506.945337,123.333314 L508.921,123.912647 C513.638819,125.272395 516.27064,125.803833 519.337404,125.877064 C523.744193,125.982294 527.697642,124.815424 531.527904,122.017241 L532.020296,121.654721 L532.510552,121.288189 C535.634194,118.935074 537.593599,117.254212 541.615416,113.537015 L544.497926,110.863327 C547.974881,107.659837 549.794043,106.195856 551.888365,105.031459 Z" id="路径-303" fill="#FFBB3C" fill-rule="nonzero"></path>
                <polygon id="路径-305" fill="#A4C3FC" fill-rule="nonzero" points="458.750713 92.4640098 466.468831 85.3932668 474.275626 92.4620486 473.604426 93.2033249 466.472 86.745 459.42622 93.2013637"></polygon>
                <g id="编组-81" transform="translate(50.109175, 134.235303)">
                    <g id="编组-63" transform="translate(63.914093, 222.107327)">
                        <mask id="mask-10" fill="white">
                            <use xlink:href="#path-9"></use>
                        </mask>
                        <use id="矩形" fill="#DDE9FF" xlink:href="#path-9"></use>
                        <path d="M1.20882698,0 L63.4052217,0 C64.4023405,3.36954592e-15 65.3342971,0.495421402 65.8920292,1.32196893 L67.6990928,4 C68.491521,5.17436234 68.1819023,6.76876112 67.0075399,7.56118935 C66.5836904,7.84719165 66.0840393,8 65.5727217,8 L2.06042429,8 C0.819095645,8 -0.294028853,7.23549708 -0.7396257,6.076903 L-1.5384054,4 C-2.12194354,2.48274545 -1.36501684,0.779716485 0.152237704,0.196178338 C0.489411271,0.0665009295 0.84757604,9.54539142e-16 1.20882698,0 Z" id="矩形" fill="#FFBB3C" mask="url(#mask-10)"></path>
                    </g>
                    <g id="编组-103" transform="translate(90.000000, 84.000000)">
                        <mask id="mask-12" fill="white">
                            <use xlink:href="#path-11"></use>
                        </mask>
                        <use id="路径-246" fill="#EAFFF3" xlink:href="#path-11"></use>
                        <path d="M-1.42108547e-14,119.48446 C1.32743544,98.0102656 2.89289856,82.9508436 4.69638937,74.3061937 C8.43003277,56.4097675 15.5176097,41.8448008 19.4787027,34.195863 C29.7253967,14.409323 39.7215535,9.31301339 44.6820442,6.63347577 C49.6425348,3.95393816 60.3007481,2.37777643 66.327433,6.63347577 C72.3541179,10.8891751 74.5668372,17.0533931 73.7454921,27.1564165 C72.924147,37.2594398 65.469448,43.1497458 58.0193289,46.7343523 C50.5692098,50.3189588 31.0128594,64.1734323 19.4787027,78.1118722 C11.7892649,87.4041655 5.29636401,101.195028 -1.42108547e-14,119.48446 Z" id="路径-246" fill="#D3F0E0" mask="url(#mask-12)"></path>
                        <path d="M61.0623172,22.0501917 C61.3287364,21.9775593 61.6035919,22.1346545 61.6762243,22.4010736 C61.7488567,22.6674927 61.5917615,22.9423483 61.3253424,23.0149807 C30.6460939,31.3788982 10.4195539,62.2160822 0.685726462,115.62224 C0.636212326,115.893907 0.375843567,116.073997 0.104176562,116.024483 C-0.167490443,115.974969 -0.347580926,115.7146 -0.29806679,115.442933 C9.49743654,61.6983811 29.9375632,30.535565 61.0623172,22.0501917 Z" id="路径-251" fill="#9FC8B1" fill-rule="nonzero" mask="url(#mask-12)"></path>
                        <path d="M53.2988281,45.3242187 C23.2203776,62.4189453 5.8733724,84.8946126 1.2578125,112.751221 C31.0439453,72.423808 48.9638672,50.9959922 55.0175781,48.4677734 C61.0712891,45.9395547 60.4983724,44.8917031 53.2988281,45.3242187 Z" id="路径-358" fill="#C4E0D1" mask="url(#mask-12)"></path>
                    </g>
                    <g id="编组-102" transform="translate(112.698267, 46.543175)">
                        <mask id="mask-14" fill="white">
                            <use xlink:href="#path-13"></use>
                        </mask>
                        <use id="路径-247" fill="#EAFFF3" xlink:href="#path-13"></use>
                        <mask id="mask-16" fill="white">
                            <use xlink:href="#path-15"></use>
                        </mask>
                        <use id="路径-247" fill="#D3F0E0" xlink:href="#path-15"></use>
                        <path d="M60.5426357,11.3128799 C60.8171154,11.2826219 61.064154,11.4806027 61.094412,11.7550823 C61.12467,12.0295619 60.9266892,12.2766006 60.6522096,12.3068585 C39.729997,14.6132741 18.9462607,31.6462845 -1.67037213,63.4563407 C-1.8205596,63.6880697 -2.13016408,63.7541722 -2.36189309,63.6039847 C-2.5936221,63.4537972 -2.65972458,63.1441927 -2.50953711,62.9124637 C18.2541689,30.8754836 39.2620175,13.6588053 60.5426357,11.3128799 Z" id="路径-253" fill="#9FC8B1" fill-rule="nonzero" mask="url(#mask-16)"></path>
                        <path d="M77,14.004188 C76.1582967,19.0507483 74.4123575,22.6778642 71.7621824,24.8855357 C67.7869198,28.1970428 61.7621824,29.629188 56.5004637,30.6914365 C51.2387449,31.753685 28.3095457,36.4578462 17.2245848,45.7839732 L39.1176512,38.3640513 L70.2081638,30.6914365 L79.9838621,20.4621958 L77,14.004188 Z" id="路径-357" fill="#C4E0D1" mask="url(#mask-16)"></path>
                    </g>
                    <g id="编组-105" transform="translate(17.048956, 30.887531)">
                        <mask id="mask-18" fill="white">
                            <use xlink:href="#path-17"></use>
                        </mask>
                        <use id="路径-248" fill="#EAFFF3" xlink:href="#path-17"></use>
                        <path d="M69.3618111,119.305105 C64.1514723,95.5149533 58.5592828,78.7727476 52.5852425,69.0784883 C43.6241821,54.5370993 32.2521675,45.1631445 20.9273327,40.7089848 C9.60249781,36.2548251 0.370054887,30.3143707 -0.833749667,21.5991494 C-2.03755422,12.8839281 3.30184276,3.89650161 14.9982131,2.35985332 C26.6945835,0.823205037 38.680528,3.89650161 49.3232751,17.6556441 C59.9660221,31.4147866 70.7898492,73.0503233 70.7898492,89.5111312 C70.7898492,100.485003 70.3138365,110.416328 69.3618111,119.305105 Z" id="路径-248" fill="#D3F0E0" mask="url(#mask-18)"></path>
                        <path d="M23.8031025,13.6524169 C23.9507563,13.4197035 24.2596222,13.3495935 24.4929738,13.4972473 C53.619825,31.9273315 69.2261583,67.7464847 71.3453163,120.899853 C71.3559107,121.175776 71.1411468,121.408372 70.8652236,121.419775 C70.5893003,121.43037 70.3567042,121.215606 70.3457056,120.939683 C68.238827,68.0842278 52.7653698,32.5700476 23.9582721,14.3422882 C23.7249205,14.1946344 23.6554487,13.8857685 23.8031025,13.6524169 Z" id="路径-254" fill="#9FC8B1" fill-rule="nonzero" mask="url(#mask-18)"></path>
                        <path d="M2.10955328,9.39371881 C-0.468124531,21.2459323 4.83890245,29.8251967 18.0306342,35.1315118 C37.8182319,43.0909844 45.0262397,50.6209933 54.8953803,65.9239922 C61.4748074,76.1259915 66.5415392,93.4846608 70.0955756,118 L-6.37610406,29.466961 L2.10955328,9.39371881 Z" id="路径-354" fill="#C4E0D1" mask="url(#mask-18)"></path>
                    </g>
                    <g id="编组-104" transform="translate(48.402642, -0.000000)">
                        <mask id="mask-20" fill="white">
                            <use xlink:href="#path-19"></use>
                        </mask>
                        <use id="路径-249" fill="#EAFFF3" xlink:href="#path-19"></use>
                        <path d="M38.4361627,109.727577 C40.2080966,71.0333394 39.2052946,44.753324 35.4277569,30.8875312 C29.7614504,10.088842 20.8541813,-1.27827958 9.37287413,0.114578571 C-2.10843299,1.50743672 -4.5866861,11.539269 0.542720885,19.2423116 C5.67212787,26.9453541 20.1964111,48.5363293 25.3543068,61.4631547 C28.7929039,70.0810384 33.1535225,86.1691793 38.4361627,109.727577 Z" id="路径-249" fill="#D3F0E0" mask="url(#mask-20)" transform="translate(18.642412, 54.863789) rotate(-2.000000) translate(-18.642412, -54.863789) "></path>
                        <path d="M1.96015082,4.87988281 C0.585845275,10.6699219 1.44620769,15.6948242 4.54123807,19.9545898 C9.18378363,26.3442383 28.9997016,54.6122295 32.8727485,77.5431753 L3.69550238,25.6259766 L-2.5784234,12.7954102 L1.96015082,4.87988281 Z" id="路径-356" fill="#C4E0D1" mask="url(#mask-20)"></path>
                    </g>
                    <g id="编组-106" transform="translate(-0.000000, 140.484501)">
                        <mask id="mask-22" fill="white">
                            <use xlink:href="#path-21"></use>
                        </mask>
                        <use id="路径-250" fill="#EAFFF3" xlink:href="#path-21"></use>
                        <path d="M86.8630745,45.7959111 C72.5806324,25.5140129 56.8667378,12.125403 39.7213908,5.6300812 C14.0033702,-4.11290144 -7.10542736e-15,7.90110838 -7.10542736e-15,16.52167 C-7.10542736e-15,25.1422316 6.80949202,30.0268155 17.0489556,30.0268155 C27.2884192,30.0268155 43.7234658,28.0070237 58.8280258,36.5737997 C68.8977326,42.2849837 79.1842128,51.927944 89.6874666,65.5026805 L86.8630745,45.7959111 Z" id="路径-250" fill="#D3F0E0" mask="url(#mask-22)"></path>
                        <path d="M1.1501319,20.1112023 C3.60224814,22.8815414 6.77648803,24.6116846 10.6728516,25.301632 C16.5173969,26.3365529 23.2104492,26.0726281 29.4458008,26.0726281 C47.5162559,26.0726281 66.1025391,30.4051476 89.6874666,63.5026805 L73.4389648,56.501339 L2.37988281,27.6380577 L1.1501319,20.1112023 Z" id="路径-355" fill="#C4E0D1" mask="url(#mask-22)"></path>
                    </g>
                    <path d="M63.5963011,11.6405216 C63.8094921,11.4650103 64.1245976,11.4955556 64.3001089,11.7087466 C74.8415791,24.513311 82.3205992,48.9543952 86.3331527,79.7677903 L86.4536838,80.7034746 C89.7765293,106.781999 90.2171879,135.285754 87.8968469,153.563607 L87.8038207,154.279761 C84.7801018,177.032488 87.8036828,199.576221 96.8773487,221.919195 C96.9812512,222.175044 96.858074,222.466681 96.6022247,222.570583 C96.3463753,222.674486 96.0547388,222.551308 95.9508363,222.295459 C86.945111,200.119782 83.8552445,177.733575 86.6839023,155.144723 L86.812536,154.148024 C89.2247883,135.99643 88.8179579,107.170593 85.4617038,80.8298697 L85.2198735,78.9727728 C81.2011719,48.7675619 73.814123,24.8386348 63.5280761,12.3443293 C63.3525648,12.1311384 63.3831102,11.8160329 63.5963011,11.6405216 Z" id="路径-245" fill="#9FC8B1" fill-rule="nonzero"></path>
                    <path d="M19.210295,152.513695 C50.2079907,155.741553 73.2591803,169.485692 88.3189956,193.730479 C88.4647019,193.965052 88.3926616,194.273329 88.1580891,194.419035 C87.9235165,194.564741 87.6152396,194.492701 87.4695333,194.258128 C72.5772256,170.283012 49.8045783,156.704952 19.1067228,153.508317 C18.8320656,153.479717 18.6325973,153.233878 18.6611979,152.95922 C18.6866207,152.715081 18.8836888,152.530349 19.1200684,152.512399 L19.210295,152.513695 Z" id="路径-252" fill="#9FC8B1" fill-rule="nonzero"></path>
                </g>
                <g id="编组-76" transform="translate(570.109175, 160.235303)">
                    <mask id="mask-24" fill="white">
                        <use xlink:href="#path-23"></use>
                    </mask>
                    <use id="椭圆形" fill="#FFFFFF" xlink:href="#path-23"></use>
                    <polygon id="路径-306" fill="#CCDEFF" mask="url(#mask-24)" points="42 44.0199101 77.9801299 18.2088648 90.719119 40.5 84 59.9151825"></polygon>
                </g>
            </g>
        </g>
    </g>
    </svg>
    <p class="title">很抱歉,您访问的页面不存在</p>
    <el-button @click="GoHome" class="gutter-20" type="primary">返回首页</el-button>
  </div>
</template>
<script>
export default {
  name: 'noPermisson',
  methods: {
    GoHome() {
      this.$router.replace('/');
    }
  }
}
</script>
<style lang="scss" scoped>
.gutter-20{
  margin-top: 20px;
}
.not-found{
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  padding: 100px 0 0 0;
}
.no-permisson-img{
  width: 500px;
}
.title{
  text-align: center;
  width: 100%;
  padding: 20px 0 0 0;
}
</style>

/src/views/error/noPermission.vue

<template>
  <div class="no-permisson">
    <svg class="no-permisson-img" viewBox="0 0 244 208" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
        <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
            <g id="亮背景时使用" transform="translate(-40.000000, -688.000000)">
                <g id="暂无权限、访问受限、锁屏" transform="translate(40.000000, 688.000000)">
                    <g id="背景/彩">
                        <g id="-">
                            <g id="编组" transform="translate(28.400000, 18.400000)">
                                <polyline id="路径" stroke="#DEF5FF" stroke-width="40" stroke-linecap="round" stroke-linejoin="round" points="121.6 27.4476562 49.6 71.0238281 121.6 66.6 129.6 66.6 72.167827 114.6"></polyline>
                                <path d="M1.6,146.2 C0.7163444,146.2 -5.68434189e-14,145.483656 -5.68434189e-14,144.6 C-5.68434189e-14,143.716344 0.7163444,143 1.6,143 L141.6,143 C142.483656,143 143.2,143.716344 143.2,144.6 C143.2,145.483656 142.483656,146.2 141.6,146.2 L1.6,146.2 Z M149.6,146.2 C148.716344,146.2 148,145.483656 148,144.6 C148,143.716344 148.716344,143 149.6,143 L154.6,143 C155.483656,143 156.2,143.716344 156.2,144.6 C156.2,145.483656 155.483656,146.2 154.6,146.2 L149.6,146.2 Z M167.6,146.2 C166.716344,146.2 166,145.483656 166,144.6 C166,143.716344 166.716344,143 167.6,143 L185.6,143 C186.483656,143 187.2,143.716344 187.2,144.6 C187.2,145.483656 186.483656,146.2 185.6,146.2 L167.6,146.2 Z M47.6,161.2 C46.7163444,161.2 46,160.483656 46,159.6 C46,158.716344 46.7163444,158 47.6,158 L61.6,158 C62.4836556,158 63.2,158.716344 63.2,159.6 C63.2,160.483656 62.4836556,161.2 61.6,161.2 L47.6,161.2 Z M70.6,161.2 C69.7163444,161.2 69,160.483656 69,159.6 C69,158.716344 69.7163444,158 70.6,158 L131.6,158 C132.483656,158 133.2,158.716344 133.2,159.6 C133.2,160.483656 132.483656,161.2 131.6,161.2 L70.6,161.2 Z M160,105.6 C160,103.466667 163.2,103.466667 163.2,105.6 L163.2,111.6 C163.2,112.483656 162.483656,113.2 161.6,113.2 L155.6,113.2 C153.466667,113.2 153.466667,110 155.6,110 L160,110 L160,105.6 Z M163.2,117.6 C163.2,119.733333 160,119.733333 160,117.6 L160,111.6 C160,110.716344 160.716344,110 161.6,110 L167.6,110 C169.733333,110 169.733333,113.2 167.6,113.2 L163.2,113.2 L163.2,117.6 Z M8,57.6 C8,55.4666667 11.2,55.4666667 11.2,57.6 L11.2,63.6 C11.2,64.4836556 10.4836556,65.2 9.6,65.2 L3.6,65.2 C1.46666667,65.2 1.46666667,62 3.6,62 L8,62 L8,57.6 Z M156.2,7 L161.6,7 C163.733333,7 163.733333,10.2 161.6,10.2 L156.2,10.2 L156.2,15.6 C156.2,17.7333333 153,17.7333333 153,15.6 L153,10.2 L147.6,10.2 C145.466667,10.2 145.466667,7 147.6,7 L153,7 L153,1.6 C153,-0.533333333 156.2,-0.533333333 156.2,1.6 L156.2,7 Z M11.2,69.6 C11.2,71.7333333 8,71.7333333 8,69.6 L8,63.6 C8,62.7163444 8.7163444,62 9.6,62 L15.6,62 C17.7333333,62 17.7333333,65.2 15.6,65.2 L11.2,65.2 L11.2,69.6 Z" id="Path-2" fill="#333745" fill-rule="nonzero"></path>
                                <path d="M28.1857864,122.286292 C29.1285955,121.343482 30.542809,122.757696 29.6,123.700505 L27.4786797,125.821825 C27.0881554,126.21235 26.4549904,126.21235 26.0644661,125.821825 L23.9431458,123.700505 C23.0003367,122.757696 24.4145503,121.343482 25.3573593,122.286292 L26.7715729,123.700505 L28.1857864,122.286292 Z M25.3573593,127.943146 C24.4145503,128.885955 23.0003367,127.471741 23.9431458,126.528932 L26.0644661,124.407612 C26.4549904,124.017088 27.0881554,124.017088 27.4786797,124.407612 L29.6,126.528932 C30.542809,127.471741 29.1285955,128.885955 28.1857864,127.943146 L26.7715729,126.528932 L25.3573593,127.943146 Z M45.8426407,17.6 C46.7854497,16.657191 48.1996633,18.0714045 47.2568542,19.0142136 L45.1355339,21.1355339 C44.7450096,21.5260582 44.1118446,21.5260582 43.7213203,21.1355339 L41.6,19.0142136 C40.657191,18.0714045 42.0714045,16.657191 43.0142136,17.6 L44.4284271,19.0142136 L45.8426407,17.6 Z M43.0142136,23.2568542 C42.0714045,24.1996633 40.657191,22.7854497 41.6,21.8426407 L43.7213203,19.7213203 C44.1118446,19.3307961 44.7450096,19.3307961 45.1355339,19.7213203 L47.2568542,21.8426407 C48.1996633,22.7854497 46.7854497,24.1996633 45.8426407,23.2568542 L44.4284271,21.8426407 L43.0142136,23.2568542 Z" id="Path复制" fill="#657180" fill-rule="nonzero"></path>
                            </g>
                        </g>
                    </g>
                    <g id="分组" transform="translate(70.000000, 66.000000)">
                        <path d="M4,0 L100,0 C102.209139,-4.05812251e-16 104,1.790861 104,4 L104,72 C104,74.209139 102.209139,76 100,76 L4,76 C1.790861,76 2.705415e-16,74.209139 0,72 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z" id="矩形" stroke="#333745" stroke-width="3.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"></path>
                        <g id="Group-7" transform="translate(38.000000, 32.000000)">
                            <path d="M22.6153846,14 L22.6153846,4.30769231 C22.6153846,1.92876923 20.6866154,0 18.3076923,0 L9.69230769,0 C7.31338462,0 5.38461538,1.92876923 5.38461538,4.30769231 L5.38461538,14" id="Stroke-3" stroke="#333745" stroke-width="3.26666667" stroke-linecap="round" stroke-linejoin="round"></path>
                            <path d="M25.8461538,14 L2.15384615,14 C0.964923077,14 0,14.9649231 0,16.1538462 L0,25.8461538 C0,27.0350769 0.964923077,28 2.15384615,28 L25.8461538,28 C27.0350769,28 28,27.0350769 28,25.8461538 L28,16.1538462 C28,14.9649231 27.0350769,14 25.8461538,14 Z" id="Stroke-1" stroke="#333745" stroke-width="2.33333333" fill="#C3CAD7" stroke-linecap="round" stroke-linejoin="round"></path>
                            <path d="M13.6941963,20.3 L14.3058037,20.3 C14.6373767,20.3 14.9234643,20.5326285 14.99109,20.857232 L15.5744233,23.657232 C15.6532719,24.0357052 15.4103782,24.4064377 15.031905,24.4852863 C14.9849455,24.4950695 14.9371048,24.5 14.889137,24.5 L13.110863,24.5 C12.7242636,24.5 12.410863,24.1865993 12.410863,23.8 C12.410863,23.7520322 12.4157934,23.7041915 12.4255767,23.657232 L13.00891,20.857232 C13.0765357,20.5326285 13.3626233,20.3 13.6941963,20.3 Z" id="Path" fill="#333745"></path>
                        </g>
                        <path d="M4,0 L100,0 C102.209139,-4.05812251e-16 104,1.790861 104,4 L104,14 L0,14 L0,4 C-2.705415e-16,1.790861 1.790861,4.05812251e-16 4,0 Z" id="矩形-copy" stroke="#333745" stroke-width="3.2" fill="#FFC107" stroke-linecap="round" stroke-linejoin="round"></path>
                        <circle id="椭圆形" fill="#333745" cx="21" cy="7" r="3"></circle>
                        <path d="M8.99682617,6.08666992 L8.99682617,4.08666992 C8.99682617,3.53438517 9.44454142,3.08666992 9.99682617,3.08666992 C10.5491109,3.08666992 10.9968262,3.53438517 10.9968262,4.08666992 L10.9968262,6.08666992 L12.9968262,6.08666992 C13.5491109,6.08666992 13.9968262,6.53438517 13.9968262,7.08666992 C13.9968262,7.63895467 13.5491109,8.08666992 12.9968262,8.08666992 L10.9968262,8.08666992 L10.9968262,10.0866699 C10.9968262,10.6389547 10.5491109,11.0866699 9.99682617,11.0866699 C9.44454142,11.0866699 8.99682617,10.6389547 8.99682617,10.0866699 L8.99682617,8.08666992 L6.99682617,8.08666992 C6.44454142,8.08666992 5.99682617,7.63895467 5.99682617,7.08666992 C5.99682617,6.53438517 6.44454142,6.08666992 6.99682617,6.08666992 L8.99682617,6.08666992 Z" id="路径" fill="#333745" fill-rule="nonzero" transform="translate(9.996826, 7.086670) rotate(-45.000000) translate(-9.996826, -7.086670) "></path>
                    </g>
                </g>
            </g>
        </g>
    </svg>
    <p class="title">暂无访问权限,请联系管理员配置</p>
    <el-button @click="GoHome" class="gutter-20" type="primary">返回首页</el-button>
  </div>
</template>
<script>
export default {
  name: 'noPermisson',
  methods: {
    GoHome() {
      this.$router.replace('/');
    }
  }
}
</script>
<style lang="scss" scoped>
.gutter-20{
  margin-top: 20px;
}
.no-permisson{
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  padding: 100px 0 0 0;
}
.no-permisson-img{
  width: 500px;
}
.title{
  text-align: center;
  width: 100%;
}
</style>

/src/views/mainPage.vue

<template>
  <n-layout has-sider>
    <n-layout-sider class="ui-menu" width="200">
      <Menu />
    </n-layout-sider>
    <n-layout>
      <n-layout-header class="ui-header">
        <Header />
      </n-layout-header>
      <n-layout-content class="ui-main">
        <router-view></router-view>
      </n-layout-content>
    </n-layout>
  </n-layout>
</template>
<script setup>
import Header from '../components/layout/Header.vue';
import Menu from '../components/layout/Menu.vue';
</script>
<style lang="scss" scoped>
.ui-menu {
  min-height: 100vh;
  background-color: #001428;
  color: #fff;
}
.ui-header {
  height: 50px;
  border-bottom: 1px solid #ddd;
}
.main-page-box {
  height: calc(100vh - 50px);
  overflow: auto;
}
.ui-main{
  padding: 10px;
}
</style>

mainPage 中引入了两个组件,下面公共组件的时候会讲到。
/src/views/home/index.vue

<template> 我是首页 </template>
<script setup></script>
<style lang="scss" scoped></style>
/src/views/login/index.vue
<template>
  <n-button @click="Login">我要登录</n-button>
</template>
<script setup>
import { useUserStore } from '../../store/user';
const user = useUserStore();
const Login = () => {
  user.LoginIn({
    expire: 3600,
    loginName: 'admin',
    staffId: 1,
    token: 'test',
    trueName: '管理员'
  });
  $uiMsg.success('登录成功', async () => {
    $router.push('/');
  });
};
</script>

可以看到 axios 和 router 中都使用了 pinia,需要注意的是,这里不能像组件中直接引入,需要将 pinia 单独配置成一个 js,然后使用的时候引入。

组件中的写法:

import { useUserStore } from '../store/user';
const user = useUserStore();

axios 和 router 中的写法:

import pinia from './pinia';
import { useUserStore } from '../store/user';
const user = useUserStore(pinia);

# 4. main.js 配置

引入我们刚刚配置好的插件,挂载全局变量。打开 main.js

import { createApp } from 'vue';
import App from './App.vue';
import './style/index.css';
import { uiCopy, uiDownloadImg } from './utils';
import { get, post } from './plugins/axios';
import moment from 'moment';
import pinia from './plugins/pinia';
import router from './plugins/router';
const app = createApp(App);
app.use(pinia);
app.use(router);
if (import.meta.env.MODE === 'production') {
  app.config.devtools = false;
} else {
  app.config.devtools = true;
}
// 请求队列
window.__axiosPromiseArr = [];
// 挂载全局变量
app.config.globalProperties.$get = get;
app.config.globalProperties.$post = post;
app.config.globalProperties.$moment = moment;
app.config.globalProperties.$uiCopy = uiCopy;
app.config.globalProperties.$uiDownloadImg = uiDownloadImg;
app.mount('#app');
./style/index.css`是使用了reset.css重置了浏览器的默认样式 `index.css
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
    display: block;
}
body {
    line-height: 1;
}
ol, ul {
    list-style: none;
}
blockquote, q {
    quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
    content: '';
    content: none;
}
table {
    border-collapse: collapse;
    border-spacing: 0;
}

utils 内是常用的一些工具函数,具体可以看下面。不需要则可以忽略

# 5. 其他配置

# Utils

src 目录下新建 utils 文件夹,创建 index.js

// 公共复制剪贴板
const uiCopy = str => {
  let copyDom = document.createElement('div');
  copyDom.innerText = str;
  copyDom.style.position = 'absolute';
  copyDom.style.top = '0px';
  copyDom.style.right = '-9999px';
  document.body.appendChild(copyDom);
  // 创建选中范围
  let range = document.createRange();
  range.selectNode(copyDom);
  // 移除剪切板中内容
  window.getSelection().removeAllRanges();
  // 添加新的内容到剪切板
  window.getSelection().addRange(range);
  // 复制
  let successful = document.execCommand('copy');
  copyDom.parentNode.removeChild(copyDom);
  try {
    uiMsg(successful ? '复制成功!' : '复制失败,请手动复制内容', null, successful ? 'success' : 'error');
  } catch (err) {}
};
// 下载 base64 格式的图片
const uiDownloadImg = (data, fileName) => {
  var link = document.createElement('a');
  link.href = data;
  link.download = fileName;
  link.click();
};
export { uiCopy, uiDownloadImg };

# 全局的消息提示和 naiveui 的主题配置

naiveui 的 message 组件需要在 n-message-provider 内部并且使用 useMessage 去获取 API。 在 components 中新建 provider 文件夹,然后新建 index.vue:

<template>
  <n-config-provider :locale="zhCN" :date-locale="dateZhCN" :theme-overrides="themeOverrides">
    <n-message-provider>
      <Message />
      <slot></slot>
    </n-message-provider>
  </n-config-provider>
</template>
<script setup>
import { NMessageProvider, NConfigProvider, NGlobalStyle } from 'naive-ui';
import { zhCN, dateZhCN } from 'naive-ui';
import Message from './Message.vue';
const themeOverrides = reactive({
  common: {
    primaryColor: '#009688'
  },
  Menu: {
    itemTextColor: '#aaa',
    itemTextColorHover: '#fff',
    itemTextColorActive: '#fff',
    itemTextColorActiveHover: '#fff',
    itemTextColorChildActive: '#fff',
    itemTextColorChildActiveHover: '#fff',
    itemIconColor: '#aaa',
    itemIconColorHover: '#fff',
    itemIconColorActive: '#fff',
    itemIconColorChildActive: '#fff',
    itemIconColorActiveHover: '#fff',
    itemIconColorChildActiveHover: '#fff',
    itemColorHover: '#009688',
    itemColorActive: '#009688',
    itemColorActiveHover: '#009688',
    arrowColor: '#aaa',
    arrowColorHover: '#fff',
    arrowColorActive: '#fff',
    arrowColorChildActive: '#fff',
    arrowColorChildActiveHover: '#fff'
  }
});
</script>

这里笔者配置了菜单的颜色和默认语言为中文。如果不需要颜色配置则可以忽略 Menu。
下面来配置全局的消息提示: 在 index.vue 同级新建 Message.vue 文件:

<template></template>
<script setup>
import { useMessage } from 'naive-ui';
const NMessage = useMessage();
let loadingMessage = null;
class Message {
  removeMessage() {
    if (loadingMessage) {
      loadingMessage.destroy();
      loadingMessage = null;
    }
  }
  showMessage(type, content, option = {}) {
    if (loadingMessage) {
      loadingMessage.destroy();
      loadingMessage = null;
    }
    loadingMessage = NMessage[type](content, { ...option, duration: option.duration !== undefined ? option.duration : 1500 });
  }
  loading(content) {
    this.showMessage('loading', content, { duration: 0 });
  }
  success(content, callback = () => {}) {
    this.showMessage('success', content, {
      onLeave: () => {
        callback();
      }
    });
  }
  error(content, callback = () => {}) {
    this.showMessage('error', content, {
      onLeave: () => {
        callback();
      }
    });
  }
  info(content, callback) {
    this.showMessage('info', content, {
      onAfterLeave: () => {
        callback && callback();
      }
    });
  }
  warning(content, callback) {
    this.showMessage('warning', content, {
      onAfterLeave: () => {
        callback && callback();
      }
    });
  }
}
window['$uiMsg'] = new Message();
</script>

配置完成后需要到 App.vue 组件中引入 Provider:

<template>
  <Provider>
    <router-view></router-view>
  </Provider>
</template>
<script setup>
import Provider from './components/provider/index.vue';
</script>
<style lang="scss">
.ui-flex {
  display: flex;
  .flex-1 {
    flex: 1;
  }
  &.align-start {
    align-items: flex-start;
  }
  &.align-center {
    align-items: center;
  }
  &.align-end {
    align-items: flex-end;
  }
  &.justify-center {
    justify-content: center;
  }
  &.justify-between {
    justify-content: space-between;
  }
  &.justify-around {
    justify-content: space-around;
  }
  &.justify-start {
    justify-content: flex-start;
  }
  &.justify-end {
    justify-content: flex-end;
  }
  &.flex-wrap {
    flex-wrap: wrap;
  }
}
</style>

这里配置好后,就可以使用 $uiMsg.success('成功') 来调用消息提示的组件了;
类型有 loading,success,error,info,warning ,可以根据需要增减。
笔者在这里还加入了全局 css 样式,不需要可以去掉。

# 6. 头部和左侧菜单组件

做完以上的配置之后项目大体结构已经搭建完毕,下面我们来编写公共的头部和左侧菜单组件。
在 components 文件夹下新建 layout 文件夹,新建 Header.vue 和 Menu.vue。

# 公共头部:

<template>
  <div class="ui-flex align-center justify-end">
    <n-dropdown trigger="hover" :options="options" @select="Select">
      <n-button text type="info">
        <n-icon> <DownOutlined /></n-icon>
      </n-button>
    </n-dropdown>
  </div>
</template>
<script setup>
import { DownOutlined } from '@vicons/antd';
import { useUserStore } from '../../store/user';
const user = useUserStore();
const { trueName, loginName } = user.userInfo;
const options = ref([
  {
    label: '退出登录',
    key: 'loginout'
  }
]);
const Select = key => {
  if (key === 'loginout') {
    user.LoginOut();
  }
};
</script>
<style lang="scss" scoped>
.ui-flex {
  height: 100%;
  padding-right: 10px;
}
</style>

# 公共左侧菜单:

<template>
  <a href="./" class="logo"></a>
  <n-menu ref="menuRef" :options="menuOptions" v-model:value="selectedKey" />
</template>
<script setup>
import { NIcon } from 'naive-ui';
import { FileTextOutlined, SettingOutlined } from '@vicons/antd';
import { RouterLink } from 'vue-router';
const { $router } = getCurrentInstance().appContext.config.globalProperties;
const menuOptions = [
  {
    label: () =>
      h(
        RouterLink,
        {
          to: {
            name: 'noPermission'
          }
        },
        { default: () => '无权限' }
      ),
    key: 'noPermission',
    icon: () => h(NIcon, null, { default: () => h(FileTextOutlined) })
  }
];
const menuRef = ref(null),
  selectedKey = ref('');
onMounted(() => {
  let currentPath = $router.currentRoute.value.name;
  menuRef.value?.showOption(currentPath);
  selectedKey.value = currentPath;
});
</script>
<style lang="scss" scoped>
.logo {
  display: block;
  width: 200px;
  height: 50px;
  background: url('https://fakeimg.pl/200x50') no-repeat center center/200px 50px;
}
</style>