基于vue的前端架构

1、局部样式与全局样式

局部样式:一般都是使用scoped方案:

<style lang="scss" scoped>
  ...
</style>

全局样式:variable.scss 全局变量管理;mixins.scss 全局Mixins管理;global.scss 全局样式

其中variable.scss和mixins.scss会优先于global.scss加载,并且可以不通过import的方式在项目中任何位置使用这些变量和mixins

// vue.config.js
module.exports = {
  css: {
    loaderOptions: {
      sass: {
        prependData: `
        @import '@/styles/variable.scss';
        @import '@/styles/mixins.scss';
        `,
      },
    },
  },
}

2、体验优化

页面载入进度条

使用nprogress对路由跳转时做一个伪进度条,这样做在网络不好的情况下可以让用户知道页面已经在加载了:

import NProgress from 'nprogress';

router.beforeEach(() => {
  NProgress.start();
});

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

美化滚动条

::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

::-webkit-scrollbar-track {
  width: 6px;
  background: rgba(#101F1C, 0.1);
  -webkit-border-radius: 2em;
  -moz-border-radius: 2em;
  border-radius: 2em;
}

::-webkit-scrollbar-thumb {
  background-color: rgba(#101F1C, 0.5);
  background-clip: padding-box;
  min-height: 28px;
  -webkit-border-radius: 2em;
  -moz-border-radius: 2em;
  border-radius: 2em;
}

::-webkit-scrollbar-thumb:hover {
  background-color: rgba(#101F1C, 1);
}

3、移动端100vh问题

在移动端使用100vh时,发现在Chrome、Safari浏览器中,因为浏览器栏和一些导航栏、连接栏导致不一样的呈现:

你以为的100vh===视口高度

实际上100vh===视口高度 + 浏览器工具栏(地址栏等等)的高度

解决方案:

安装 vh-check (npm install vh-check --save)

import vhCheck from 'vh-check';
vhCheck('browser-address-bar');

定义一个css Mixin

@mixin vh($height: 100vh) {
  height: $height;
  height: calc(#{$height} - var(--browser-address-bar, 0px));
}

4、静态资源与图标

静态资源

所有的静态资源文件都会上传到 阿里云 OSS 上,所以在环境变量上加以区分。

.env.development 与 .env.production 的 VUE_APP_STATIC_URL 属性分别配置了本地的静态资源服务器地址和线上 OSS 的地址。

本地的静态资源服务器是通过 pm2 + http-server 创建的,设计师切完直接扔进来就好了。

自动注册Svg图标

直接name等于文件名即可使用

<template>
    <svg name="logo" />
</template>

首先需要对@/assets/icons 文件夹下的svg图标进行自动注册,需要对webpack 和 svg-sprite-loader 进行了相关设置,文件全部打包成 svg-sprite

module.exports = {
  chainWebpack: (config) => {
    config.module
      .rule('svg')
      .exclude.add(resolve('src/assets/icons'))
      .end();

    config.module
      .rule('icons')
      .test(/.svg$/)
      .include.add(resolve('src/assets/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader');
  },
}

写一个全局用的 Vue 组件<m-svg />:

@/components/m-svg/index.js

const requireAll = (requireContext) => requireContext.keys().map(requireContext);
const req = require.context('@/assets/icons', false, /.svg$/);
requireAll(req);

@/components/m-svg/index.vue

<template>
  <svg class="mw-svg" aria-hidden="true">
    <use :xlink:href="iconName"></use>
  </svg>
</template>
<script>
export default {
  name: 'm-svg',
  props: {
    name: { type: String, default: '' },
  },
  computed: {
    iconName() {
      return `#${this.name}`;
    },
  },
};
</script>
<style lang="scss" scoped>
.mw-svg {
  width: 1.4em;
  height: 1.4em;
  fill: currentColor;
  overflow: hidden;
  line-height: 1em;
  display: inline-block;
}
</style>

放置在 @/assets/icons 文件夹下的文件名

5、Axios封装

import axios from 'axios';
import get from 'lodash/get';
import storage from 'store';
// 创建 axios 实例
const request = axios.create({
 // API 请求的默认前缀
 baseURL: process.env.VUE_APP_BASE_URL,
 timeout: 10000, // 请求超时时间
});

// 异常拦截处理器
const errorHandler = (error) => {
 const status = get(error, 'response.status');
 switch (status) {
   /* eslint-disable no-param-reassign */
   case 400: error.message = '请求错误'; break;
   case 401: error.message = '未授权,请登录'; break;
   case 403: error.message = '拒绝访问'; break;
   case 404: error.message = `请求地址出错: ${error.response.config.url}`; break;
   case 408: error.message = '请求超时'; break;
   case 500: error.message = '服务器内部错误'; break;
   case 501: error.message = '服务未实现'; break;
   case 502: error.message = '网关错误'; break;
   case 503: error.message = '服务不可用'; break;
   case 504: error.message = '网关超时'; break;
   case 505: error.message = 'HTTP版本不受支持'; break;
   default: break;
   /* eslint-disabled */
 }
 return Promise.reject(error);
};

// request interceptor
request.interceptors.request.use((config) => {
 // 如果 token 存在
 // 让每个请求携带自定义 token 请根据实际情况自行修改
 // eslint-disable-next-line no-param-reassign
 config.headers.Authorization = `bearer ${storage.get('ACCESS_TOKEN')}`;
 return config;
}, errorHandler);

// response interceptor
request.interceptors.response.use((response) => {
 const dataAxios = response.data;
 // 这个状态码是和后端约定的
 const { code } = dataAxios;
 // 根据 code 进行判断
 if (code === undefined) {
   // 如果没有 code 代表这不是项目后端开发的接口
   return dataAxios;
 // eslint-disable-next-line no-else-return
 } else {
   // 有 code 代表这是一个后端接口 可以进行进一步的判断
   switch (code) {
     case 200:
       // [ 示例 ] code === 200 代表没有错误
       return dataAxios.data;
     case 'xxx':
       // [ 示例 ] 其它和后台约定的 code
       return 'xxx';
     default:
       // 不是正确的 code
       return '不是正确的code';
   }
 }
}, errorHandler);

export default request;

6、跨域问题

可以用devServer提供的proxy代理:

// vue.config.js
devServer: {
  proxy: {
    '/api': {
      target: 'http://47.100.186.132/your-path/api',
      ws: true,
      changeOrigin: true,
      pathRewrite: {
        '^/api': ''
      }
    }
  }
}

7、路由

Layout

布局暂时分为三大类:

frameln:基于BasicLayout,通常需要登陆或权限认证的路由

frameOut:不需要动态判断权限的路由,如登录页或通用页面

errorPage:例如404

权限验证

通过获取当前用户的权限去比对路由表,生成当前用户的权限可访问的路由表,通过router.addRoutes动态挂载到router上

  判断页面是否需要登陆状态,需要则跳转到/user/login

  本地存储中不存在token则跳转到/user/login

  如果存在token,用户信息不存在,自动调用vuex、‘/system/user/getInfo’

在路由中,集成了权限验证的功能,需要为页面增加权限时,在meta下添加相应的key

auth:当auth为true时,此页面需要进行登录权限验证,只针对frameIn路由有效

permissions:permissions每一个key对应权限功能的验证,当key的值为true时,代表具有权限,若key为false,配合v-permission指令,可以隐藏相应的DOM。

import router from '@/router';
import store from '@/store';
import storage from 'store';
import util from '@/libs/utils';

// 进度条
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';

const loginRoutePath = '/user/login';
const defaultRoutePath = '/home';

/**
 * 路由拦截
 * 权限验证
 */
router.beforeEach(async (to, from, next) => {
  // 进度条
  NProgress.start();
  // 验证当前路由所有的匹配中是否需要有登录验证的
  if (to.matched.some((r) => r.meta.auth)) {
    // 是否存有token作为验证是否登录的条件
    const token = storage.get('ACCESS_TOKEN');
    if (token && token !== 'undefined') {
      // 是否处于登录页面
      if (to.path === loginRoutePath) {
        next({ path: defaultRoutePath });
        // 查询是否储存用户信息
      } else if (Object.keys(store.state.system.user.info).length === 0) {
        store.dispatch('system/user/getInfo').then(() => {
          next();
        });
      } else {
        next();
      }
    } else {
      // 没有登录的时候跳转到登录界面
      // 携带上登陆成功之后需要跳转的页面完整路径
      next({
        name: 'Login',
        query: {
          redirect: to.fullPath,
        },
      });
      NProgress.done();
    }
  } else {
    // 不需要身份校验 直接通过
    next();
  }
});

router.afterEach((to) => {
  // 进度条
  NProgress.done();
  util.title(to.meta.title);
});

8、构建优化

包分析工具

const WebpackBundleAnalyzer = require('webpack-bundle-analyzer');

module.exports = {
  chainWebpack: (config) => {
    if (process.env.use_analyzer) {
      config
        .plugin('webpack-bundle-analyzer')
        .use(WebpackBundleAnalyzer.BundleAnalyzerPlugin);
    }
  },
};

开启Gzip

chainWebpack: (config) => {
  config
    .plugin('CompressionPlugin')
    .use(CompressionPlugin, []);
},

路由懒加载

{
  path: 'home',
  name: 'Home',
  component: () => import(
    /* webpackChunkName: "home" */ '@/views/home/index.vue'
  ),
},
原文地址:https://www.cnblogs.com/chao202426/p/14102397.html