zoukankan      html  css  js  c++  java
  • 基于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'
      ),
    },
  • 相关阅读:
    Element-ui 的 slot 关系理解
    关于Delegate委托和Event事件的学习
    JavaScript 中 prototype 与 __proto__
    正向代理与反向代理的个人理解
    MVC和三层架构
    关于SqlDataAdapter的思考
    关于C#连接Oracle数据库
    关于VS配置环境
    富文本的实现
    博客
  • 原文地址:https://www.cnblogs.com/chao202426/p/14102397.html
Copyright © 2011-2022 走看看