基于 Vue 的前端架构,我做了这 15 点

`,

},

},

},

}

体验优化

页面载入进度条

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

import NProgress from ‘nprogress’;

router.beforeEach(() => {

NProgress.start();

});

router.afterEach(() => {

NProgress.done();

});

美化滚动条

一直用 Mac 做前端,突然发现同事的 Windows 上出现了十分丑陋的滚动条,为了保持一致:

::-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);

}

静态资源加载页面

首次加载页面时,会产生大量的白屏时间,这时做一个 loading 效果看起来会很友好,其实很简单,直接在 public/index.html 里写一些静态的样式即可。

移动端 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));

}

之后就是哪里不会点哪里。

3.组件库


因为 Element UI 长期没更新,并且之前使用过 React 的 Ant Design(重点),所以组件库选择了Ant Design Vue。

覆盖 Ant Design Vue 样式

设计师眼中的 Ant Design === ‘丑’(心酸)。

1.使用 .less 文件

Ant Design Vue 的样式使用了 Less 作为开发语言,并定义了一系列全局/组件的样式变量,所以需要安装了 less、less-loader,在 @/styles/antd-theme.less 可以覆盖默认样式。

优点是:

方便快捷,可以修改 class,覆盖默认变量。

缺点是:

必须引入 @import '~ant-design-vue/dist/antd.less'; ,引入后会将所有的组件样式全部引入,导致打包后的 css 体积达到 500kb 左右。

2.使用 JavaScript 对象

通过 JavaScript 对象的方式可以修改内置变量,需要对 Less 进行配置:

// vue.config.js

const modifyVars = require(‘./src/styles/antdTheme.js’);

module.exports = {

css: {

loaderOptions: {

less: {

lessOptions: {

javascriptEnabled: true,

modifyVars,

},

},

},

},

}

这一步还可以继续优化,通过 babel-plugin-import 使 Ant Design Vue 的组件样式可以按需加载:

// babel.config.js

module.exports = {

presets: [

‘@vue/cli-plugin-babel/preset’,

],

plugins: [

[

‘import’,

{ libraryName: ‘ant-design-vue’, libraryDirectory: ‘es’, style: true },

],

],

};

优点是:

可以按需引入,打包后的 CSS 体积取决于你引用了多少个组件。

缺点是:

不能使用 class 进行样式覆盖。

干掉无用的图标

Ant Design Vue 把所有的 Icon 一次性引入(不管你因用了多少个组件),这使得体积打包后图标所占的体积竟然有几百 kb 之多。这些图标大多数不会被设计师所采纳,所以部分图标都应该被干掉:

创建一个 icons.js 来管理 Ant Design Vue 图标,这里以一个 Loading 图标为例:

// @/src/assets/icons.js

export { default as LoadingOutline } from ‘@ant-design/icons/lib/outline/LoadingOutline’;

如何知道你要加载的图标在什么路径下?

在 @ant-design/icons/lib 目录下有三种风格的图标,分别是 fill、outline、twotone,这里面内部的文件并不是 svg 格式,而是 js 和 ts 格式,这就是为什么我们可以这么引入图标的关键所在了。

下一步是通过配置 vue.config.js 将这个文件引入进来:

// vue.config.js

module.exports = {

configureWebpack: {

resolve: {

alias: {

‘@ant-design/icons/lib/dist$’: path.resolve(__dirname, ‘./src/assets/icons.js’),

},

},

},

}

解决 Moment 多国语

解决到这之后,Ant Design Vue 居然还很大,这是因为 moment 是 Ant Design Vue 中有强依赖该插件,所以使用 webpack 插件减小打包体积,这里我们只保留 zh-cn 语言包:

// vue.config.js

module.exports = {

chainWebpack: (config) => {

config

.plugin(‘ContextReplacementPlugin’)

.use(webpack.ContextReplacementPlugin, [/moment[/]locale$/, /zh-cn/]);

},

}

部分组件需要在页面内引用

Ant Design Vue 中部分体积较大的组件,例如 DatePicker,根据业务需求,应考虑在页面中进行加载,尽量保证首屏加载的速度:

4.静态资源与图标


静态资源

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

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

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

自动注册 Svg 图标

在日常的开发中,总是会有着大量的图标需要使用,这里我们直接选择使用 SVG 图标。但是如果每次使用图标还需要通过路径找到这张图标岂不是很麻烦?

下面这种才是我想要的方案(直接 name 等于 文件名即可):

而且最后打包后需要合并成一张雪碧图。

首先需要对 @/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

参数 name

  • 类型:String

  • 默认值:null

  • 说明:放置在 @/assets/icons 文件夹下的文件名

样式

  • 图标的大小可以通过 width + height 属性改变。

  • 通过改变 font-size 属性改变,宽高 = font-zise * 1.4

5.异步请求


封装 Axios

@/libs/request.js 路径下对 Axios 进行封装,封装了请求参数,请求头,以及错误提示信息、 request 拦截器、response 拦截器、统一的错误处理、baseURL 设置等。

废话不说直接贴代码:

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;

  • 通过 VUE_APP_BASE_URL 区分线上与开发环境的 API 地址。

  • code 起到一个比较关键的作用,例如 token 过期时的验证。

  • 使用了一个叫 sotre 的包作为本地储存的工具用来存储 token。

跨域问题

跨域问题一般情况直接找后端解决了,你要是不好意思打扰他们的话,可以用 devServer 提供的 proxy 代理:

// vue.config.js

devServer: {

proxy: {

‘/api’: {

target: ‘http://47.100.186.132/your-path/api’,

ws: true,

changeOrigin: true,

pathRewrite: {

‘^/api’: ‘’

}

}

}

}

Mock 数据

一个很常见的情况,后端接口没出来,前端在这干瞪眼。

Mock 数据功能是基于 mock.js (opens new window)开发,通过 webpack 进行自动加载 mock 配置文件。

规则
  • 所有的 mock 配置文件均应放置在 @/mock/services 路径内。

  • @/mock/services 内部可以建立业务相关的文件夹分类存放配置文件。

  • 所有的配置文件应按照 ***.mock.js 的命名规范创建。

  • 配置文件使用 ES6 Module 导出 export defaultexport 一个数组。

入口文件

import Mock from ‘mockjs’;

Mock.setup({

timeout: ‘500-800’,

});

const context = require.context(‘./services’, true, /.mock.js$/);

context.keys().forEach((key) => {

Object.keys(context(key)).forEach((paramKey) => {

Mock.mock(…context(key)[paramKey]);

});

});

示例模板

import Mock from ‘mockjs’;

const { Random } = Mock;

export default [

RegExp(‘/example.*’),

‘get’,

{

‘range|50-100’: 50,

‘data|10’: [

{

// 唯一 ID

id

真题解析、进阶学习笔记、最新讲解视频、实战项目源码、学习路线大纲
详情关注公中号【编程进阶路】

: ‘@guid()’,

// 生成一个中文名字

cname: ‘@cname()’,

// 生成一个 url

url: ‘@url()’,

// 生成一个地址

county: Mock.mock(‘@county(true)’),

// 从数组中随机选择一个值

‘array|1’: [‘A’, ‘B’, ‘C’, ‘D’, ‘E’],

// 随机生成一个时间

time: ‘@datetime()’,

// 生成一张图片

image: Random.dataImage(‘200x100’, ‘Mock Image’),

},

],

},

];

6.路由


Layout

布局暂时分为三大类:

  • frameIn:基于 BasicLayout,通常需要登录或权限认证的路由。

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

  • errorPage:例如404。

权限验证

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

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

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

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

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

auth
  • 类型:Boolean

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

permissions
  • 类型:Object

  • 说明: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.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);

});

页面开发

  • 根据业务需要划分,按照路由层级在 views 中创建相对应的页面组件,以文件夹的形式创建,并在文件夹内创建 index.vue 文件作为页面的入口文件。

  • 页面内的组件:在页面文件夹下创建 components 文件夹,在其内部对应创建相应的组件文件,如果是复杂组件,应以文件夹的形式创建组件。

  • 工具模块:能够高度抽象的工具模块,应创建在 @/src/libs 内创建 js 文件。

7、构建优化


包分析工具

构建代码之后,到底是什么占用了这么多空间?靠直觉猜测或者使用 webpack-bundle-analyzer。

const WebpackBundleAnalyzer = require(‘webpack-bundle-analyzer’);

module.exports = {

chainWebpack: (config) => {

if (process.env.use_analyzer) {

config

.plugin(‘webpack-bundle-analyzer’)

.use(WebpackBundleAnalyzer.BundleAnalyzerPlugin);

}

},

};

开启 Gzip

对,这这么一句话,后端就得支持你的 .gz 文件了,而你只需要坐着等老板夸:

chainWebpack: (config) => {

config

.plugin(‘CompressionPlugin’)

.use(CompressionPlugin, []);

},

路由懒加载

这块 @vue/cli 已经帮忙处理好了,但也需要了解一下他的原理和如何配置。

{

path: ‘home’,

name: ‘Home’,

component: () => import(

/* webpackChunkName: “home” */ ‘@/views/home/index.vue’

),

},

webpackChunkName 这条注释还是很有必要加的,至少你打包后知道又是哪个页面变得又臭又大。

Preload & Prefetch

不清楚这两个功能的去 @vue/cli 补课,这两个功能非常有助于你处理加载的性能。

8.测试框架


最后:

总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。

面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。

并在文件夹内创建 index.vue 文件作为页面的入口文件。

  • 页面内的组件:在页面文件夹下创建 components 文件夹,在其内部对应创建相应的组件文件,如果是复杂组件,应以文件夹的形式创建组件。

  • 工具模块:能够高度抽象的工具模块,应创建在 @/src/libs 内创建 js 文件。

7、构建优化


包分析工具

构建代码之后,到底是什么占用了这么多空间?靠直觉猜测或者使用 webpack-bundle-analyzer。

const WebpackBundleAnalyzer = require(‘webpack-bundle-analyzer’);

module.exports = {

chainWebpack: (config) => {

if (process.env.use_analyzer) {

config

.plugin(‘webpack-bundle-analyzer’)

.use(WebpackBundleAnalyzer.BundleAnalyzerPlugin);

}

},

};

开启 Gzip

对,这这么一句话,后端就得支持你的 .gz 文件了,而你只需要坐着等老板夸:

chainWebpack: (config) => {

config

.plugin(‘CompressionPlugin’)

.use(CompressionPlugin, []);

},

路由懒加载

这块 @vue/cli 已经帮忙处理好了,但也需要了解一下他的原理和如何配置。

{

path: ‘home’,

name: ‘Home’,

component: () => import(

/* webpackChunkName: “home” */ ‘@/views/home/index.vue’

),

},

webpackChunkName 这条注释还是很有必要加的,至少你打包后知道又是哪个页面变得又臭又大。

Preload & Prefetch

不清楚这两个功能的去 @vue/cli 补课,这两个功能非常有助于你处理加载的性能。

8.测试框架


最后:

总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。

面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。

  • 25
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值