我的微码

我的微码

自己积累的一些常用代码。基于多地存储的原则,线上保存一份。

持续更新……

场景篇

基于策略模式实现的表单验证类

封装了常用验证规则、弹窗逻辑,易扩展、复用性强。

  • 封装表单验证类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// validate 策略类 (设计模式之策略模式)
class Validate {
constructor() {
this.validateFuncs = [];
this._strategies = {
isNotEmpty(value, errorMsg) {
const trimedValue = typeof value === 'string' ? value.trim() : value;
if (!trimedValue) {
return errorMsg;
}
},
isNotLessThan(value, errorMsg) {
const [num1, num2] = value;
if (Number(num1) < Number(num2)) {
return errorMsg;
}
},
isNotMoreThan(value, errorMsg) {
const [num1, num2] = value;
if (Number(num1) > Number(num2)) {
return errorMsg;
}
},
isShouldIdentilyCard(value, errorMsg) {
const idPattern =
/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
if (!idPattern.test(value)) {
return errorMsg;
}
},
isShouldMobileNo(value, errorMsg) {
const mobilePattern = /^1[1-9]\d{9}$/;
if (!mobilePattern.test(value)) {
return errorMsg;
}
},
};
}

add(value, rule, errorMsg) {
this.validateFuncs.push(() => {
return this._strategies[rule](value, errorMsg);
});
}

check() {
const { validateFuncs } = this;
let result = true;
for (let i = 0, len = validateFuncs.length; i < len; i++) {
const errorMsg = (() => validateFuncs[i]())();
if (errorMsg) {
MessageBox({
title: '温馨提示',
message: errorMsg,
});
result = false;
break;
}
}
return result;
}
}
export default Validate;
  • 使用
1
2
3
4
5
6
7
8
9
10
11
12
const validate = new Validate();
const drawMoney = Number(this.drawMoney);
const canDrawMaxAmount = Number(this.darwData.principalAmt);
validate.add(drawMoney, "isNotEmpty", "请输入支取金额");
validate.add(
[drawMoney, canDrawMaxAmount],
"isNotMoreThan",
"支取金额不能大于最大可支取金额"
);
if (!validate.check()) {
return;
}

基于CSS3 var()实现的色值级主题定制

  • 配置项(通过接口获取)说明
1
2
3
4
5
6
7
{
themeColor: '#B48F4B', // 字体、边框、背景
lightBgColor: 'rgba(236, 209, 157, 0.2)', // 半透明背景
gradientStartColor: '#C29B5F', // 渐变色开始
gradientEndColor: '#B48F4B', // 渐变色结束
successImg: 'http://10.102.201.171:32720/uploads/-/system/user/avatar/621/avatar.png'
}
  • 实现。逻辑和样式集中放置在app.vue中,做了默认值、惰性加载、兼容手机银行类名等处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// src/app.vue
<template>
<div id="app" :style="styleVar" :class="{'custom':isNeedCustom}">
<router-view></router-view>
</div>
</template>
<script>
export default {
data () {
return {
isNeedCustom: false,
// 默认值
styleVar: {
'--themeColor': '#176de6', // 字体、边框、背景
'--lightBgColor': '#ecf9ff',
'--gradientStartColor': '#0173e6',
'--gradientEndColor': '#1452cc'
}
}
},
methods: {
// 根据渠道配置,设置自定义样式
setCustomStyle (config = {}) {
let customStyles = {}
const styleVar = this.styleVar
for (const key in config) {
const value = config[key]
const styleName = `--${key}`
// 如果有值,且是约定中的样式名
if (value && styleVar[styleName]) {
customStyles[styleName] = value
}
}
if (JSON.stringify(customStyles) === '{}') {
// 只有配置了自定义样式,才会应用var变量的这套方案
// 否则就是全局定义的默认样式
return
}
this.styleVar = { ...styleVar, ...customStyles }
this.isNeedCustom = true
}
}
}
</script>
<style lang="less">
// 兼容框架原有换肤方案,保持与其一致的类名
#app.custom .dynamic-font-color {
color: var(--themeColor);
}
#app.custom .dynamic-border-color {
border-color: var(--themeColor);
}
#app.custom .dynamic-bg-color {
background-color: var(--themeColor);
}
#app.custom .dynamic-bg-opacitycolor {
background-color: var(--lightBgColor);
}
#app.custom .btn-single {
background: linear-gradient(
to right,
var(--gradientStartColor) 0%,
var(--gradientEndColor) 100%
);
}
#app.custom .btn-half {
background: linear-gradient(
to right,
var(--gradientStartColor) 0%,
var(--gradientEndColor) 100%
);
}
#app.custom .btn-box-half {
border: 1px solid var(--themeColor);
color: var(--themeColor);
}
#app.custom .gradient-block {
background: linear-gradient(
to right,
var(--gradientStartColor) 0%,
var(--gradientEndColor) 100%
);
}
</style>
  • 使用。获取到渠道配置后,执行app实例中的setCustomStyle方法。
1
2
3
4
// main.js
const app = new Vue({...})
// 获取config并设置主题色
app.$root.$children[0].setCustomStyle(config['wx'])

曝光埋点指令

  • 指令封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// plugin.js
import './intersection-observer.min.js';

const intersectionObserver = new IntersectionObserver(
function (entries) {
let uuId = sessionStorage.getItem('uuid');
if (!uuId) {
sessionStorage.setItem('uuid', uuidv4());
uuId = sessionStorage.getItem('uuid');
}
entries.forEach((entry) => {
// 当节点进入视窗
if (entry.isIntersecting) {
const data = entry.target.attributes['exposure-data'].value;
const dataObJ = JSON.parse(data);
const sendsaData = {
page_name: dataObJ.currentPagename || '', // 所在页面
...
};
sensors.track('shared_op_impression', sendsaData);
}
});
},
{
root: null,
rootMargin: '0px',
threshold: 0.5, // 不一定非得全部露出来 这个阈值可以小一点点
}
);

// 指令
const MyPlugin = {
install: (Vue) => {
// 添加全局方法。会直接挂载在构造函数上
Vue.$post = post;

// 添加实例方法。new实例化后通过this.$xxx访问
Vue.prototype.$post = post;

// 全局指令-曝光埋点
Vue.directive('exposure', {
// 当被绑定的元素插入到 DOM 中时……
bind(el) {
// 监听元素
intersectionObserver.observe(el);
},
});
}
}
  • 使用
    1
    2
    3
    4
    5
    <div v-for="(item, index) in productList"
    v-exposure
    :exposure-data="JSON.stringify(item)"
    :key="index"
    ></div>

    获取验证码时的倒计时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<span v-if="countDownNumber" class="main__row__btn main__row__btn--waiting">重新获取({{ countDownNumber }})</span>
<span v-else class="main__row__btn" @click="onSendCode()">{{
countDownNumber === '' ? '获取验证码' : '重新获取'
}}</span>
</template>
<script>
export default {
data() {
return {
countDownNumber: ""
};
},
methods: {
initCountDown() {
clearInterval(this.timer);
this.countDownNumber = 60;
this.timer = setInterval(() => {
let count = this.countDownNumber;
count--;
this.countDownNumber = count;
if (count === 0) {
clearInterval(this.timer);
}
}, 1000);
}
},
destroyed() {
clearInterval(this.timer);
}
};
</script>

Vue二次封装组件时,对双向绑定和属性继承的处理

纯备忘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<van-popup class="warpper" v-model="isShowPopup" position="bottom" v-bind="$attrs">
</van-popup>
</template>
<script>
export default {
props: {
visible: Boolean // 显隐
},
computed: {
isShowPopup: {
get() {
return this.visible;
},
set(newVal) {
this.$emit("update:visible", newVal);
}
}
}
};
</script>

解决VueRouter在3.0以上版本跳转与当前相同路由时报错的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import VueRouter from 'vue-router';
// 解决vue-router在3.0版本以上跳转与当前路由相同路由时报错问题
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject);
return originalPush.call(this, location).catch((err) => {
if (VueRouter.isNavigationFailure(err)) {
// resolve err
return err;
}
// rethrow error
return Promise.reject(err);
});
};
Vue.use(VueRouter);

VueRouter路由切换时自动滚动到顶部

1
2
3
4
5
6
7
8
// router.js
const router = new VueRouter({
routes: [],
scrollBehavior() {
return { x: 0, y: 0 };
}
});
export default router

重载Vue页面

  • 方案1(推荐)

    如果是在js中使用,可以把reload挂载到window;如果是在vue组件内使用,可以使用依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div id="app">
<router-view v-if="isRouterAlive"></router-view>
</div>
</template>
<script>
export default {
data() {
// 加挂全局的vue对象,用于手机银行重新登陆后的页面重载
window.$appVue = this;
return {
isRouterAlive: true
};
},
methods: {
reload() {
this.isRouterAlive = false;
this.$nextTick(() => {
this.isRouterAlive = true;
});
}
}
};
</script>
  • 方案2
1
2
3
4
5
6
7
8
function reloadPage() {
// window.$appVue.$router.go(0) // location.reload方案在APP无效
const path = window.$appVue.$route.fullPath;
window.$appVue.$router.replace('/');
setTimeout(() => {
window.$appVue.$router.replace(path);
}, 0);
}

axios二次封装,保证并行请求仅有1个loading

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// http.js
let requestNum = 0; // 带loading的请求数量,保证并行请求仅有1个loading
let isLoading = false;

function showLoading() {
if (!isLoading) {
sirius.showLoading();
isLoading = true;
}
requestNum++;
}

function hideLoading() {
requestNum--;
if (requestNum <= 0) {
sirius.hideLoading();
isLoading = false;
}
}

const post = (needLoading = true) => {
needLoading && showLoading();
return new Promise((resolve, reject) => {
axios().then(()=>{
resolve()
needLoading && hideLoading();
}).catch(()=>{
reject()
needLoading && hideLoading();
})
});
};

export default post;

判断首次进入页面

1
2
3
4
5
6
7
router.beforeEach((to, from, next) => {
// 初次进入清除tokne的判断:有accesstoken,且不是单页应用内后退,且不是刷新
// window.performance.navigation.type判断页面加载来源 0跳转 1刷新 2后退
if (accesstoken && from.name === null && window.performance.navigation.type === 0) {
window.localStorage.removeItem('sessions');
}
});

监听页面显隐事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
created() {
document.addEventListener("visibilitychange", this.onVisibilityChange);
},
methods: {
onVisibilityChange() {
if (document.visibilityState === "visible") {
console.log("***页面可见***");
}
}
},
destroyed() {
document.removeEventListener("visibilitychange", this.onVisibilityChange);
}
};

解决滚动穿透

1
2
3
4
// 解决滚动穿透
$('.box').on('touchmove', '.box-bg', (e) => {
e.preventDefault();
});

工具篇

扩展toFixed,以解决四舍五入不准确的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
window.Number.prototype.toFixed = function (d) {
let s = `${this}`;
if (!d) d = 0;
if (s.indexOf('.') === -1) s += '.';
s += new Array(d + 1).join('0');
if (new RegExp(`^(-|\\+)?(\\d+(\\.\\d{0,${d + 1}})?)\\d*$`).test(s)) {
let s2 = `0${RegExp.$2}`;
const pm = RegExp.$1;
let a = RegExp.$3.length;
let b = true;
if (a === d + 2) {
a = s2.match(/\d/g);
if (parseInt(a[a.length - 1]) > 4) {
for (let i = a.length - 2; i >= 0; i--) {
a[i] = parseInt(a[i]) + 1;
if (a[i] === 10) {
a[i] = 0;
b = i !== 1;
} else break;
}
}
s2 = a.join('').replace(new RegExp(`(\\d+)(\\d{${d}})\\d$`), '$1.$2');
}
if (b) s2 = s2.substr(1);
return (pm + s2).replace(/\.$/, '');
}
return `${this}`;
};

封装sessionStroage,使支持对json的存取

1
2
3
4
5
6
7
8
9
10
11
12
13
const saveJsonToSession = (key, data) => {
const str = typeof data === 'string' ? data : JSON.stringify(data);
window.sessionStorage.setItem(key, str);
};

const getJsonFromSession = (key) => {
const stringData = window.sessionStorage.getItem(key);
let result = null;
if (stringData) {
result = stringData.startsWith('{') || stringData.startsWith('[') ? JSON.parse(stringData) : stringData;
}
return result;
};

浮点数四则运算

解决js浮点数运算不准确的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 浮点数计算相加兼容
const floatAdd = (num1, num2) => {
let r1, r2;
try {
r1 = num1.toString().split('.')[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = num2.toString().split('.')[1].length;
} catch (e) {
r2 = 0;
}
const m = 10 ** Math.max(r1, r2);
const n = r1 >= r2 ? r1 : r2;
return ((num1 * m + num2 * m) / m).toFixed(n);
};
// 浮点相减bug兼容
const floatSub = (num1, num2) => {
let r1, r2;
try {
r1 = num1.toString().split('.')[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = num2.toString().split('.')[1].length;
} catch (e) {
r2 = 0;
}
const m = 10 ** Math.max(r1, r2);
const n = r1 >= r2 ? r1 : r2;
return ((num1 * m - num2 * m) / m).toFixed(n);
};
// js 浮点数计算 乘
const floatMul = function (a, b) {
let c = 0;
const d = a.toString();
const e = b.toString();
const dDecimals = d.split('.')[1];
const eDecimals = e.split('.')[1];
if (dDecimals) {
c += dDecimals.length;
}
if (eDecimals) {
c += eDecimals.length;
}
return (Number(d.replace('.', '')) * Number(e.replace('.', ''))) / 10 ** c;
};
// 浮点数相除问题
const floatDiv = function (a, b) {
const c = Number(a.toString().replace('.', ''));
const d = Number(b.toString().replace('.', ''));
const aDecimals = a.toString().split('.')[1];
const bDecimals = b.toString().split('.')[1];
const e = aDecimals ? aDecimals.length : 0;
const f = bDecimals ? bDecimals.length : 0;
return floatMul(c / d, 10 ** (f - e));
};

防抖/节流函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 防抖功能函数
function debounce(fn, timestamp = 1000) {
// 1、创建一个标记用来存放定时器的返回值
let timeout = null;
return function (...args) {
// 2、每次当用户点击/输入的时候,把前一个定时器清除
clearTimeout(timeout);
// 3、然后创建一个新的 setTimeout,
// 这样就能保证点击按钮后的 interval 间隔内
// 如果用户还点击了的话,就不会执行 fn 函数
timeout = setTimeout(() => {
fn.call(this, args);
}, timestamp);
};
}

// 节流函数,立即执行版
function throttle(fn, timestamp = 1000) {
// 1、通过闭包保存一个标记
let canRun = true;
return function (...args) {
// 2、在函数开头判断标志是否为 true,不为 true 则中断函数
if (!canRun) {
return;
}
// 3、将 canRun 设置为 false,防止执行之前再被执行
canRun = false;
// 4、定时器
fn.call(this, args);
setTimeout(() => {
// fn.call(this, arguments);
// 5、执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
canRun = true;
}, timestamp);
};
}

金额转汉字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 金额大写
Vue.filter('amount', function (input) {
if (input !== undefined) {
const intPosTwo = input.indexOf(',');
if (intPosTwo) {
input = input.replace(/,/g, '');
}
if (parseFloat(input) < 0 || isNaN(input) || input === '') {
return '零元整';
}
input = String(parseFloat(input));
let strOutput = '';
let strUnit = '仟佰拾万仟佰拾亿仟佰拾万仟佰拾元角分';
input += '00';
const intPos = input.indexOf('.');
if (intPos >= 0) {
input = input.substring(0, intPos) + input.substr(intPos + 1, 2);
}
strUnit = strUnit.substr(strUnit.length - input.length);
for (let i = 0; i < input.length; i++) {
strOutput += '零壹贰叁肆伍陆柒捌玖'.substr(input.substr(i, 1), 1) + strUnit.substr(i, 1);
}
strOutput = strOutput
.replace(/零角零分$/, '整')
.replace(/零[仟佰拾]/g, '零')
.replace(/零{2,}/g, '零')
.replace(/零([亿|万])/g, '$1')
.replace(/零+元/, '元')
.replace(/亿零{0,3}万/, '亿')
.replace(/^元/, '零元')
.replace(/零角/, '零')
.replace(/零分/, '');
if (parseFloat(input) === 0) {
return '零元整';
}
if (parseFloat(input) > 0 && parseFloat(input) < 100) {
return strOutput.replace(/零元/, '').replace(/零/, '');
}
return strOutput;
}
return '零元';
});