# Mobile-Template **Repository Path**: immortal_sun/mobile-template ## Basic Information - **Project Name**: Mobile-Template - **Description**: 移动端H5应用模板,使用vue+vant进行搭建,包含权限验证、Mock数据以及各路由跳转等基础功能。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-04-12 - **Last Updated**: 2023-02-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: Vue, Axios, Vant, Less ## README # vue-vant-mobile-template ## 简介 这是基于 vue-cli4 实现的移动端 H5 开发模板,其中包含项目常用的配置及组件封装,引入 mockjs 进行数据接口模拟,可供快速开发使用。默认引入部分 eslint 进行代码书写限制,初次使用如报错可通过一次性修复。 > 技术栈:vue2.x + webpack4 + vant2 + axios + mockjs + less + postcss-px2rem/postcss-px-to-viewport ```js // 安装依赖 npm install / yarn // 本地启动 npm run dev / yarn dev // 生产打包 npm run build / yarn build // 自动修复eslint npm run lint --fix ``` ## 配置 vant vant 是一套轻量、可靠的移动端 Vue 组件库,非常适合基于 vue 技术栈的移动端开发。在过去很长的一段时间内,本人用的移动端 UI 框架都是 vux。后来由于 vux 不支持 vue-cli3,就转用了 vant,不得不说,无论是在交互体验上,还是代码逻辑上,vant 都比 vux 好很多,而且 vant 的坑比较少。 对于第三方 UI 组件,如果是全部引入的话,比如会造成打包体积过大,加载首页白屏时间过长的问题,所以按需加载非常必要。vant 也提供了按需加载的方法。babel-plugin-import 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。 1、安装依赖 ``` npm i babel-plugin-import -D ``` 2、配置 .babelrc 或者 babel.config.js 文件 ```js // 在.babelrc 中添加配置 { "plugins": [ ["import", { "libraryName": "vant", "libraryDirectory": "es", "style": true }] ] } // 对于使用 babel7 的用户,可以在 babel.config.js 中配置 module.exports = { plugins: [ ['import', { libraryName: 'vant', libraryDirectory: 'es', style: true }, 'vant'] ] }; ``` 3、按需引入 你可以在代码中直接引入 Vant 组件,插件会自动将代码转化为方式二中的按需引入形式 ```js import Vue from "vue"; import { Button, Field } from "vant"; Vue.use(Button).use(Field); ``` ## 移动端适配 移动端适配是开发过程中不得不面对的事情。在此,我们使用 postcss 中的 px2rem-loader,将我们项目中的 px 按一定比例转化 rem;由于当前所有主流浏览器均能支持 viewport 属性,或者使用 postcss-px-to-viewport ,将 px 转化成 vw/vh。 ### 1. rem 适配 我们将 html 字跟字体设置为 100px,很多人选择设置为 375px,但是我觉得这样换算出来的 rem 不够精确,而且我们在控制台上调试代码的时候无法很快地口算得出它本来的 px 值。如果设置 1rem=100px,这样我们看到的 0.16rem,0.3rem 就很快得算出原来是 16px,30px 了。 > 具体步骤如下: 1、安装依赖 ``` npm install px2rem-loader --save-dev ``` 2、在 vue.config.js 进行如下配置 ```js css: { // css预设器配置项 loaderOptions: { postcss: { plugins: [ // 采用 px ===> rem npm postcss-px2rem require("postcss-px2rem")({ // 1rem === 100px 比例 remUnit: 100 }) ] } } }, ``` ### 2. viewport 适配 viewport 适配是以一屏宽度 === 100vw ,一屏高度 === 100vh 进行对应比例换算 px ===> vw/vh 进行适配展示,通过在 config 中进行宽度配置后即可方便使用。 > 具体步骤如下: 1、安装依赖 ``` npm install px2rem-loader --save-dev ``` 2、在 vue.config.js 进行如下配置 ```js loaderOptions: { postcss: { plugins: [ // 采用 px ===> viewport npm postcss-px-to-viewport require("postcss-px-to-viewport")({ // 需要转换的单位,默认为"px" unitToConvert: "px", // 视窗的宽度,对应的是我们设计稿的宽度,一般是750 viewportWidth: 750, // 单位转换后保留的精度 unitPrecision: 3, // 能转化为vw的属性列表 propList: ["*"], // 希望使用的视口单位 viewportUnit: "vw", // 字体使用的视口单位 fontViewportUnit: "vw", // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。 selectorBlackList: [], // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换 minPixelValue: 1, // 媒体查询里的单位是否需要转换单位 mediaQuery: false, // 是否直接更换属性值,而不添加备用属性 replace: true, // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件 exclude: /(\/|\\)(node_modules)(\/|\\)/ }) ]; } } ``` ## axios 请求封装 1、设置请求拦截和响应拦截 ```js const PRODUCT_URL = "https://xxxx.com"; const MOCK_URL = "http://xxxx.com"; let service = axios.create({ baseURL: process.env.NODE_ENV === "production" ? PRODUCT_URL : MOCK_URL }); // 请求拦截器 service.interceptors.request.use( config => { // 设置token,Content-Type var token = sessionStorage.getItem("token"); config.headers["token"] = token; config.headers["Content-Type"] = "application/json;charset=UTF-8"; // 请求显示loading效果 if (config.loading === true) { vm.$loading.show(); } return config; }, error => { vm.$loading.hide(); return Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( res => { vm.$loading.hide(); // token失效,重新登录 if (res.data.code === 401) { // 重新登录 } return res; }, error => { vm.$loading.hide(); return Promise.reject(error); } ); ``` 2、封装 get 和 post 请求方法 ```js function get(url, data, lodaing) { return new Promise((resolve, reject) => { service .get(url) .then( response => { resolve(response); }, err => { reject(err); } ) .catch(error => { reject(error); }); }); } function post(url, data, loading) { return new Promise((resolve, reject) => { service .post(url, data, { loading: loading }) .then( response => { resolve(response); }, err => { reject(err); } ) .catch(error => { reject(error); }); }); } export { get, post }; ``` ## 工具类函数封装 1、添加方法到 vue 实例的原型链上 ```js export default { install (Vue, options) { Vue.prototype.util = { method1(val) { ... }, method2 (val) { ... }, } } ``` 2、在 main.js 通过 vue.use()注册 ```js import utils from "./js/utils"; Vue.use(utils); ``` 本文提供以下函数封装 - 获取 url 参数值 - 判断浏览器类型 - 判断 IOS/android - 校验手机号码 - 检验车牌号 - 校验车架号 - 检验身份证号码 - 日期格式化 - 时间格式化 - 城市格式化 - 压缩图片 - 图片转成 base64 ## vue-router 配置 平时很多人对 vue-router 的配置可配置了 path 和 component,实现了路由跳转即可。其实 vue-router 可做的事情还有很多,比如 - 路由懒加载配置 - 改变单页面应用的 title - 登录权限校验 - 页面缓存配置 #### 路由懒加载配置 Vue 项目中实现路由按需加载(路由懒加载)的 3 中方式: ```js // 1、Vue异步组件技术: { path: '/home', name: 'Home', component: resolve => reqire(['../views/Home.vue'], resolve) } // 2、es6提案的import() { path: '/', name: 'home', component: () => import('../views/Home.vue') } // 3、webpack提供的require.ensure() { path: '/home', name: 'Home', component: r => require.ensure([],() => r(require('../views/Home.vue')), 'home') } ``` 本项目采用的是第二种方式,为了后续 webpack 打包优化。 #### 改变单页面应用的 title 由于单页面应用只有一个 html,所有页面的 title 默认是不会改变的,但是我们可以才路由配置中加入相关属性,再在路由守卫中通过 js 改变页面的 title ```js router.beforeEach((to, from, next) => { document.title = to.meta.title; }); ``` #### 登录权限校验 在应用中,通常会有以下的场景,比如商城:有些页面是不需要登录即可访问的,如首页,商品详情页等,都是用户在任何情况都能看到的;但是也有是需要登录后才能访问的,如个人中心,购物车等。此时就需要对页面访问进行控制了。 此外,像一些需要记录用户信息和登录状态的项目,也是需要做登录权限校验的,以防别有用心的人通过直接访问页面的 url 打开页面。 此时。路由守卫可以帮助我们做登录校验。具体如下: 1、配置路由的 meta 对象的 auth 属性 ```js const routes = [ { path: "/", name: "home", component: () => import("../views/Home.vue"), meta: { title: "首页", keepAlive: false, auth: false } }, { path: "/mine", name: "mine", component: () => import("../views/mine.vue"), meta: { title: "我的", keepAlive: false, auth: true } } ]; ``` 2、在路由首页进行判断。当`to.meta.auth`为`true`(需要登录),且不存在登录信息缓存时,需要重定向去登录页面 ```js router.beforeEach((to, from, next) => { document.title = to.meta.title; const userInfo = sessionStorage.getItem("userInfo") || null; if (!userInfo && to.meta.auth) { next("/login"); } else { next(); } }); ``` #### 页面缓存配置 项目中,总有一些页面我们是希望加载一次就缓存下来的,此时就用到 keep-alive 了。keep-alive 是 Vue 提供的一个抽象组件,用来对组件进行缓存,从而节省性能,由于是一个抽象组件,所以在 v 页面渲染完毕后不会被渲染成一个 DOM 元素。 1、通过配置路由的 meta 对象的 keepAlive 属性值来区分页面是否需要缓存 ```js const routes = [ { path: "/", name: "home", component: () => import("../views/Home.vue"), meta: { title: "首页", keepAlive: false, auth: false } }, { path: "/list", name: "list", component: () => import("../views/list.vue"), meta: { title: "列表页", keepAlive: true, auth: false } } ]; ``` 2、在 app.vue 做缓存判断 ```html
从以上的界面中,我们可以得到以下信息:
- 打包出的文件中都包含了什么,以及模块之间的依赖关系
- 每个文件的大小在总体中的占比,找出较大的文件,思考是否有替换方案,是否使用了它包含了不必要的依赖?
- 是否有重复的依赖项,对此可以如何优化?
- 每个文件的压缩后的大小。
## CDN 资源优化
CDN 的全称是 `Content Delivery Network`,即内容分发网络。CDN 是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术。
随着项目越做越大,依赖的第三方 npm 包越来越多,构建之后的文件也会越来越大。再加上又是单页应用,这就会导致在网速较慢或者服务器带宽有限的情况出现长时间的白屏。此时我们可以使用 CDN 的方法,优化网络加载速度。
1、将 `vue、vue-router、vuex、axios` 这些 vue 全家桶的资源,全部改为通过 CDN 链接获取,在 `index.html` 里插入 相应链接。
```html
```
2、在 `vue.config.js` 配置 externals 属性
```javascript
module.exports = {
···
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios':'axios'
}
}
```
3、卸载相关依赖的 npm 包
```
npm uninstall vue vue-router vuex axios
```
此时启动项目运行就可以了。我们在控制台就能发现项目加载了以上四个 CDN 资源。
不过现在有不少声音说,vue 全家桶加载 CDN 资源其实作用并不大,而且公共的 CDN 资源也没有 npm 包那么稳定,这个就见仁见智了。所以我在源码时新建的分支做这个优化。当项目较小的就不考虑 CDN 优化了。
当然,当引入其他较大第三方资源,比如 echarts,AMAP(高德地图),采用 CDN 资源还是很有必要的。
## gZip 加速优化
所有现代浏览器都支持 gzip 压缩,启用 gzip 压缩可大幅缩减传输资源大小,从而缩短资源下载时间,减少首次白屏时间,提升用户体验。
gzip 对基于文本格式文件的压缩效果最好(如:CSS、JavaScript 和 HTML),在压缩较大文件时往往可实现高达 70-90% 的压缩率,对已经压缩过的资源(如:图片)进行 gzip 压缩处理,效果很不好。
```js
const CompressionPlugin = require("compression-webpack-plugin");
configureWebpack: config => {
if (process.env.NODE_ENV === "production") {
config.plugins.push(
new CompressionPlugin({
// gzip压缩配置
test: /\.js$|\.html$|\.css/, // 匹配文件名
threshold: 10240, // 对超过10kb的数据进行压缩
deleteOriginalAssets: false // 是否删除原文件
})
);
}
};
```
## 使用 SvgIcon 组件
svg 优点:
- 图标易于实时修改,可以带动画
- 可以使用标砖的 prop 和默认值来将图标保持在一个典型的尺寸并随时按需改变他们
- 图标是内联的,所以不需要额外的 HTTP 请求
- 可以动态地使得图标可访问
通常我们项目都是使用 iconfont 阿里巴巴图标矢量库,但是操作比较麻烦,每次更新都要重新下载链接。另外我们可以使 svg-sprite-loader 实现。
1、新增 SvgIcon 组件
```vue
```
2、全局注册组件并导入 svg
```js
import SvgIcon from "./SvgIcon.vue";
import Vue from "vue";
// 注册到全局
Vue.component("svg-icon", SvgIcon);
const requireAll = requireContext => requireContext.keys().map(requireContext);
const req = require.context("./svg", false, /\.svg$/);
requireAll(req);
```
3、在 main.js 中引入
```js
import "./components/icon";
```
4、配置 vue.config.js
```js
module.exports = {
chainWebpack: config => {
const svgRule = config.module.rule("svg");
svgRule.uses.clear();
svgRule.exclude.add(/node_modules/);
svgRule
.test(/\.svg$/)
.use("svg-sprite-loader")
.loader("svg-sprite-loader")
.options({
symbolId: "icon-[name]"
});
const imagesRule = config.module.rule("images");
imagesRule.exclude.add(resolve("src/icons"));
config.module.rule("images").test(/\.(png|jpe?g|gif|svg)(\?.*)?$/);
}
};
```