vue相关
vue路由主动态引入
1.将子路由分模块封装
例如 home.router.js
export default { path: '/', name: 'Home', component: () => import('../../views/Home.vue') }
2. 主路由配置
mport Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) //路由主动态引入 const routerList = []; function importALL(r) { r.keys().forEach((key) => routerList.push(r(key).default)); } //webpack 查找文件(代替所有import) //1.文件路径 //2.是否子文件夹 //3.匹配规则 importALL(require.context('./', true, /\.router\.js/)); const routes = [ ...routerList, ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default router
vue配置跨域
1.当前项目的根路径下新建一个文件,文件名是固定的 vue.config.js
2.vue.config.js配置
module.exports = { devServer: { compress: false, //配置是否启用 gzip 压缩。所有来自 dist/ 目录的文件都做 gzip 压缩和提供为服务 // open: true, // host: 'localhost', // port: 8080, // https: false, proxy: { //配置跨域 '/api': { target: 'https://api-xxxxxxx', //后台接口 ws: true, changOrigin: true, //允许跨域 pathRewrite: { '^/api': '' //请求的时候使用这个api就可以 } } } } }
axios,主文件设置
const service = axios.create({ baseURL: '/api', })
请求示例:
//gettRequest为封装的请求方法 , 与解决跨域问题无关
gettRequest("/goods/detail", { goods_id: 55345 }).then((res) => { console.log(res); });
vue路由权限控制
1.配置路由元对象 , 是否需要权限才能进入
{ path: '/goods', name: 'goods', component: () => import('../../views/Goods.vue'), meta: { requierAuth: true //需要权限才能进入 } }
2.main文件配置全局路由守卫
main.js代码
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' Vue.config.productionTip = false; //以token为例 router.beforeEach((to, from, next) => { let token = sessionStorage.getItem('token'); //需要验证之后才能进入 if (to.meta.requierAuth) { if (token) { next(); } else { console.log(to.fullPath) next({ path: './login', query: { redirect: to.fullPath // 将跳转的路由path作为参数,登录成功后跳转到该路由 } }) } } else { next() } }) new Vue({ router, store, render: h => h(App) }).$mount('#app')
2.登录页 ,登录后获取token , 跳回上一路由
<template> <div class="page"> <h1>login</h1> <button @click="loginClick">点我登录</button> </div> </template> <script> export default { name: "", components: {}, data() { return {}; }, methods: { loginClick() { sessionStorage.setItem("token", 123); let redirect = this.$route.query.redirect; this.$router.push(redirect); //.catch(() => {}); }, }, }; </script>
axios api层架构封装
1.目录结构
2. service.js文件 统一处理接口 , 拦截 , 状态处理
import axios from 'axios'; //从本地获取token //token=>令牌 64 128 256 随机字符串 + 用户名 + 用户密码 + 当前时间 + ...... function getTokenByLocal() { let token = sessionStorage.getItem('token') || ''; return token; } //创建axios实例 const service = axios.create({ baseURL: '/api', timeout: 5000, // headers: {'X-Custom-Header': 'foobar'} }) // 在实例已创建后修改默认值 // service.defaults.headers.common['Authorization'] = 'AUTH_TOKEN'; service.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; //请求拦截 service.interceptors.request.use( config => { if (getTokenByLocal()) { config.headers['Authorization'] = getTokenByLocal() } return config; }, error => { return Promise.reject(error); } ) //响应拦截 service.interceptors.response.use( response => { let res = response.data; if (res.code == "401") { //根据返回值 状态码进行不同操作 location.href = "/login" } return Promise.resolve(res) }, error => { return Promise.reject(error) } ) export default service;
3.common.js 传参处理
import service from './service'; //post请求 80% 耦合度极低=> 复用性极高 //细分解耦 export function requestOfPost(url, data) { return service.post(url, data) } export function requestOfGet(url, data) { return service.get(url, { params: data }) }
4.api.js 返回值处理
import { requestOfPost, requestOfGet } from './common' export function postRequest(url, data) { return new Promise((resolve, reject) => { requestOfPost(url, data).then(res => { resolve(res) }).catch(error => reject(error)) }) } export function gettRequest(url, data) { return new Promise((resolve, reject) => { requestOfGet(url, data).then(res => { resolve(res) }).catch(error => reject(error)) }) }
引入使用
import { gettRequest } from "../request/api"; export default { name: "", components: { Login }, created() { gettRequest("/goods/detail", { goods_id: 55345 }).then((res) => { console.log(res); }); }, data() { return {}; }, };
vue引入Fastclick 插件解决 移动端300ms延迟
1.安装
yarn add fastclick --save
2. 在main.js中 导入
import Fastclick from "fastclick"
3.在main.js中 调用
Fastclick.attach(document.body);
4.可能出现的问题 : 多词点击报错
解决方案
* { /*避免点击过快 fastclick 报错*/ touch-action: pan-y; }
vue懒加载
1.安装vue-lazyload
yarn add vue-lazyload --save
2.在main.js中 导入
import VueLazyLoad from "vue-lazyload"
3.安装配置懒加载插件
Vue.use(VueLazyLoad, { loading: require("./assets/img/common/placeholder.png") });
4.使用
:src换为 v-lazy
vue 单位转换
转换vw
1.安装postcss-px-to-viewport
yarn add postcss-px-to-viewport --save--dev
2.package.json 同级文件下 ,新建postcss.config.js文件
module.exports = { plugins: { autoprefixer: {}, "postcss-px-to-viewport": { viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度. viewportHeight: 667, // 视窗的高度,对应的是我们设计稿的高度.(也可以不配置) unitPrecision: 5, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除) viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw selectorBlackList: ['ignore', 'tab-bar'], // 指定不需要转换的类,后面再讲. minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位. mediaQuery: false, // 允许在媒体查询中转换`px` exclude: [/^TabBar/] }, } }
转换rem
module.exports = { css: { loaderOptions: { less: { modifyVars: { 'primary-color': '#3b53f1', ...obj }, javascriptEnabled: true }, postcss: { plugins: [ require('postcss-pxtorem')({ rootValue: 41.25, //换算基数, unitPrecision: 3, //允许REM单位增长到的十进制数字,小数点后保留的位数。 propList: ['*'], exclude: function(file) { //指定文件不转为rem return !(file.indexOf('layout\\header.vue') >= 0 || file.indexOf('layout/header.vue') >= 0) }, mediaQuery: false, //(布尔值)允许在媒体查询中转换px。 minPixelValue: 0 //设置要替换的最小像素值 }) ] } } }, }
.postcssrc.js
// https://github.com/michael-ciniawsky/postcss-load-config module.exports = { plugins: { autoprefixer: { overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8'] }, 'postcss-pxtorem': { rootValue: 41.25, propList: ['*'] } } }
plugins / flexible.js main.js引入
!(function(n) { var e = n.document, t = e.documentElement, i = 1920, d = i / 50, o = 'orientationchange' in n ? 'orientationchange' : 'resize', a = function() { var n = t.clientWidth || 1920 n > 1920 && (n = 1920) t.style.fontSize = n / d + 'px' } e.addEventListener && (n.addEventListener(o, a, !1), e.addEventListener('DOMContentLoaded', a, !1)) })(window)
方案二
-
安装依赖 在项目根目录下执行以下命令安装所需依赖:
npm install postcss postcss-loader postcss-pxtorem --save-dev
-
添加postcss-loader 在webpack.config.js文件中添加postcss-loader,作为css-loader之前的一个loader,如下所示:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},
// 其他规则
]
}
}
-
配置postcss-pxtorem 在项目根目录下新建postcss.config.js文件,并添加如下配置:
module.exports = {
plugins: [
require('postcss-pxtorem')({
rootValue: 75, // 根据设计稿设置,例如设计稿宽度为750px,这里设置为75
propList: ['*']
})
]
}
其中,rootValue是根元素字体大小的值,可以根据设计稿宽度进行设置,propList设置需要进行转换的属性。
-
编写CSS样式 在CSS样式中直接使用px单位即可,插件会自动转换为rem或者vw/vh单位。
自定义vue 插件
举例 :
<template> <div class="toast" v-show="isShow"> {{ message }} </div> </template> <script> export default { name: "Toast", components: {}, data() { return { isShow: false, message: "", }; }, methods: { show(message = "空内容", duration = 2000) { this.isShow = true; this.message = message; console.log(123); if (timer) { clearTimeout(timer); } let timer = setTimeout(() => { this.isShow = false; this.message = ""; clearTimeout(timer); }, duration); }, }, }; </script> <style scoped> .toast { padding: 8px 10px; background: rgba(0, 0, 0, 0.75); position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); color: #fff; z-index: 999; font-size: 24px; } </style>
toast/index.js
toast/index.js方案一:
import Toast from "./Toast"; const obj = {}; obj.install = function (Vue) { // 1.构建组件构造器 const ToastCompon = Vue.extend(Toast); //2.new的方式 创建组件对象 const toast = new ToastCompon(); //3.将组件对象挂载到某一个对象上 toast.$mount(document.createElement('div')); //4.将toast.$el添加到body中 document.body.appendChild(toast.$el) Vue.prototype.$toast = toast; } export default obj;
toast/index.js方案二:
import Toast from './Toast';
let obj = {}; obj.install = function (Vue) { Vue.component(Toast.name,Toast);
} export default obj;
main.js
import Toast from "components/common/toast/index"
...
Vue.use(Toast);
使用
this.$toast.show(res, 1500);
vue 封装滚动插件 better-scroll
- wrapper里必须只有一个子元素;
- 子元素的高度要比wrapper要高;
- 使用的时候,要确定DOM元素是否已经生成,必须要等到DOM渲染完成后,再new BScroll();
- 滚动区域的DOM元素结构有变化后,需要执行刷新 refresh() ;
- 上拉或者下拉,结束后,需要执行finishPullUp()或者finishPullDown(),否则将不会执行下次操作;
- better-scroll,默认会阻止浏览器的原生click事件,如果滚动内容区要添加点击事件,需要在实例化属性里设置click:true;
版本1.15.2
封装 better-scroll
Scroll.vue
<template> <div class="wrapper" ref="wrapper"> <div class="content"> <slot></slot> </div> </div> </template> <script> //scroll组件必须要设定高度 import BScroll from "better-scroll"; export default { name: "Scroll", props: { probeType: { type: Number, default() { return 0; }, }, pullUpLoad: { type: Boolean, default() { return false; }, }, }, components: {}, data() { return { scroll: null, }; }, mounted() { this.scroll = new BScroll(this.$refs.wrapper, { click: true, probeType: this.probeType, pullUpLoad: this.pullUpLoad, // pullUpLoad: { // threshold: -30 // 当上拉距离超过30px时触发 pullingUp 事件 //stop: 20 // 回弹停留在距离顶部20px的位置 // }, // observeDOM: true, // observeImage: true, }); //监听滚动 this.scroll.on("scroll", (position) => { this.$emit("scroll", position); }); //监听上拉加载 this.scroll.on("pullingUp", () => { this.$emit("loadMore", "上啦加载更多"); }); }, methods: { scrollTo(x, y, timer = 300) { //滚动到指定位置 this.scroll && this.scroll.scrollTo(x, y, timer); }, finishPullUp() { //事情做完,需要调用此方法告诉 better-scroll 数据已加载,否则上拉事件只会执行一次 this.scroll && this.scroll.finishPullUp(); }, refresh() { //刷新 this.scroll && this.scroll.refresh(); }, getScrollY() { //获取滚动Y轴位置 return this.scroll ? this.scroll.y : 0; }, }, }; </script> <style scoped></style>
如何使用及注意事项
以下举例都是通过 this.$refs.scroll 来调用组件中的方法
1.引入scroll 组件将需要滚动的内容 包裹 , scroll组件必须要有高度 , 并设置overflow:hidden
2.每次下拉加载更多 , 需要 调用 this.$refs.scroll.finishPullUp();
3.滚动到指定元素位置 this.$refs.scroll.scroll.scrollToElement(this.$refs.tabControl2.$el, 200);
4.滚动到指定位置 this.$refs.scroll.scrollTo(x, y);
5.被包裹的内容的宽高发生改变 , 则需要重新调用 this.$refs.scroll.refresh();
例如图片加载完成后 ,高度发生变化 ,执行this.$refs.scroll.refresh()
item.vue
//item.vue
<img v-lazy="goodsItem.goods_small_logo" alt="" @load="imageLoad" />
home.vue
data() { return { itemImgListener: null, refresh: null } }, mounted() { this.refresh = debounce(this.$refs.scroll.refresh, 200); //debounce为防抖函数
this.itemImgListener = () => { this.refresh(); };
this.$bus.$on("ItemImageLoad", this.itemImgListener); }
}
6.跳转路由 , 保留页面位置
因为使用了 keep-alive 进行页面缓存 , 所以调用以下两个函数
activated() { //创建时跳到获取保留的位置,并刷新下页面 this.$refs.scroll.scrollTo(0, this.saveY, 1); this.$refs.scroll.refresh(); }, deactivated() { //离开时保留滚动位置 this.saveY = this.$refs.scroll.getScrollY(); this.$bus.$off("itemImageLoad", this.itemImageListener); //解绑itemImageLoad },
简单举例:
<template> <div id="home"> <scroll class="content" ref="scroll" @scroll="contentScrollTop" :probeType="3" :pullUpLoad="true" @loadMore="loadMore" > ...需要滚动的内容 </scroll>
</div> </template> <script>
export default { name: "Home", components: { Scroll, }, props: {}, data() { return { }; }, created() { }, methods: { getHomeGoods(type, numpage) { const page = this.goods[type + ""].page + 1; getHomeGoods(type, page, 10).then((res) => { //滚动加载更多 this.goods[type + ""].list.push(...res.message.goods); this.goods[type + ""].page += 1; //下拉加载更多 this.$refs.scroll.finishPullUp(); }); }, tabClick(index) {//滚动到元素位置 this.$refs.scroll.scroll.scrollToElement(this.$refs.tabControl2.$el, 200); }, backClick() { //返回顶部 this.$refs.scroll.scrollTo(0, 0); },
}, computed: { }, mounted() {
}, activated() { //创建时跳到获取保留的位置,并刷新下页面 this.$refs.scroll.scrollTo(0, this.saveY, 1); this.$refs.scroll.refresh(); }, deactivated() { //离开时保留滚动位置 this.saveY = this.$refs.scroll.getScrollY(); this.$bus.$off("itemImageLoad", this.itemImageListener); }, }; </script> <style scoped> .content { /**scroll组件必须要设定高度 */ /* height: calc(100vh - 93px); */ overflow: hidden; position: fixed; top: 44px; bottom: 49px; z-index: 10; left: 0; right: 0; overflow: hidden; } </style>
Vue防止按钮重复提交
src/directive/preventRepeatClick.js
import Vue from 'vue' const preventRepeatClick = Vue.directive('preventRepeatClick', { inserted(el, binding) { el.addEventListener('click', () => { // 为了解决撤销恢复按钮禁用时间不同问题 let timer, ele ele = el.children[0].innerHTML !== '撤 销' && el.children[0].innerHTML !== '恢 复' timer = ele ? 800 : 100 if (!el.disabled) { el.disabled = true setTimeout(() => { el.disabled = false }, binding.value || timer) } }) } }) export {preventRepeatClick}
示例
<a-button v-prevent-repeat-click type="link" :class="item.type == 'danger' ? '' : ''" :size="item.size || 'default'" :disabled="item.disabled || false" > {{ item.title }} </a-button>
...