复制文本

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 复制文本
* @param text 文本
*/
function copyText(text) {
let textareaEl = document.createElement('textarea');
textareaEl.style.cssText = 'position: absolute;opacity: 0;';
textareaEl.value = text;
document.body.appendChild(textareaEl);
textareaEl.select();
document.execCommand('copy');
textareaEl.remove();
}

悬停激活

1
2
3
4
5
6
7
8
9
10
/**
* 悬停激活
* @param {HTMLElement} el 元素
*/
function hoverActive(el) {
for (const childrenEl of [...el.parentNode.children]) {
childrenEl.classList.remove('active');
}
el.classList.add('active');
}

处理滚动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 处理滚动
* @param {HTMLElement} el 元素
* @param {string} [direction='right'] 滚动方向 上=up 下=down 左=left 右=right
* @param {number} [step=250] 步长,单位为像素
*/
function handleScroll(el, direction = 'right', step = 250) {
let list = el.parentNode.querySelector('.list');
if (direction === 'up')
list.scrollTo({ top: list.scrollTop - step, behavior: "smooth" });
if (direction === 'down')
list.scrollTo({ top: list.scrollTop + step, behavior: "smooth" });
if (direction === 'left')
list.scrollTo({ left: list.scrollLeft - step, behavior: "smooth" });
if (direction === 'right')
list.scrollTo({ left: list.scrollLeft + step, behavior: "smooth" });
}

切换Tab

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 切换Tab
* @param {HTMLElement} el 元素
* @param {number} index 索引
*/
function switchTab(el, index) {
let tabsEl = el.parentNode;
let contentEl = el.parentNode.parentNode.querySelector('.content');
for (const item of [...tabsEl.children]) item.classList.remove('active');
tabsEl.children[index].classList.add('active');
for (const item of [...contentEl.children]) item.classList.remove('active');
contentEl.children[index].classList.add('active');
}

改变页

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
/**
* 改变页
* @param {HTMLElement} el 元素
* @param {string|number} action 动作 上一页=previous 下一页=next 指定页=item索引值(0,1,2...)
*/
function changePage(el, action) {
let parentEl = el.parentNode.parentNode;
let paginationEl = el.parentNode;
let contentItems = [...parentEl.querySelectorAll(':scope > .content > *')];
let paginationItems = [...paginationEl.querySelectorAll('.item')];
let currentIndex = 0;
contentItems.some((item, key) => {
if (item.classList.contains('active')) {
currentIndex = key;
return true;
}
});
for (const item of contentItems) item.classList.remove('active');
for (const item of paginationItems) item.classList.remove('active');
if (action === 'previous') {
contentItems[getIndex(currentIndex - 1)].classList.add('active');
paginationItems[getIndex(currentIndex - 1)].classList.add('active');
} else if (action === 'next') {
contentItems[getIndex(currentIndex + 1)].classList.add('active');
paginationItems[getIndex(currentIndex + 1)].classList.add('active');
} else if (action.constructor === Number) {
contentItems[getIndex(action)].classList.add('active');
paginationItems[getIndex(action)].classList.add('active');
}

function getIndex(index) {
if (index > paginationItems.length - 1) index = 0;
else if (index < 0) index = paginationItems.length - 1;
return index;
}
}

自适应的rem单位,用于适配各种移动设备

1
2
3
4
5
6
7
8
9
10
(function () {
function setRemUnit() {
const baseSize = 1; // 基础大小,1rem等于多少px
const designWidth = 750; // 设计稿最大px宽度
const scale = window.innerWidth / designWidth;
document.documentElement.style.fontSize = baseSize * Math.min(scale, 2) + 'px';
}
setRemUnit();
window.addEventListener('resize', setRemUnit);
})();

检测浏览器设备,并切换至对应域名

1
2
3
4
5
6
7
if (new RegExp('iPhone|iPad|iPod|Android|Mobile', 'i').test(window.navigator.userAgent)) {
if (new RegExp('^https?:\\/\\/(?!m\\.)', 'i').test(window.location.href))
window.location.assign(window.location.href.replace(new RegExp('^(https?:\\/\\/)(.*)', 'i'), '$1m.$2'));
} else {
if (new RegExp('^https?:\\/\\/m\\.', 'i').test(window.location.href))
window.location.assign(window.location.href.replace(new RegExp('^(https?:\\/\\/)m\\.', 'i'), '$1'));
}

判断值是否为 null 或 undefined

1
2
3
4
5
6
7
8
9
/**
* 判断值是否为 null 或 undefined
* @param value
* @returns {boolean}
*/
function isNull(value) {
if (value === null || value === undefined) return true;
return false;
}

空置合并:相同于 ??(空置合并运算符)

适用于无法使用 ?? 的时候,可通过该Util实现相同作用

1
2
3
4
5
6
7
8
9
10
/**
* 空置合并:相同于 ??(空置合并运算符)
* @param value 如果 value 不为 null 或 undefined 则返回该值
* @param replaceNull 如果 value 为 null 或 undefined 则返回该值
* @returns {*}
*/
function nullMerge(value, replaceNull = value) {
if (value === null || value === undefined) return replaceNull;
return value;
}

可选链:相同于 ?.(可选链操作符)

适用于无法使用 ?. 的时候,可通过该Util实现相同作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 可选链:相同于 ?.(可选链操作符)
* 示例:optionalChain(object)['?.a?.b']
* 如果属性键包含?.可用/?.表示,如属性键为?.a:['?./?.a']
* @param object 需支持可选链的对象
* @return {*}
*/
function optionalChain(object) {
if (typeof object !== 'object') return undefined;
return new Proxy(object, {
get: (object, propertyKey) => {
let propertyKeyArray = propertyKey.replace(new RegExp('([^\\/]|^)\\?\\.', 'g'), '$1{delimiter}').split('{delimiter}');
propertyKeyArray.forEach((item, index) => propertyKeyArray[index] = item.replace(/\/\?\./g, '?.'));
return propertyKeyArray.reduce((a, b) => (a || object)?.[b], object);
}
});
}

日期格式化

常用的日期格式化,如果有更复杂的需求可通过 Moment.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
/**
* 日期格式化
* @param {any} [dateParams=当前时间戳] 需要格式化日期的Date()构造函数的参数
* @param {string} [format='yyyy-MM-dd HH:mm:ss'] 日期格式化表达式,如 yyyy-MM-dd HH:mm:ss
* @returns {string} 格式化后的日期字符串
*/
function dateFormat(dateParams = new Date().getTime(), format = 'yyyy-MM-dd HH:mm:ss') {
let date = new Date(dateParams);
let dateObject = {
"y+": date.getFullYear(), // 年
"M+": date.getMonth() + 1, // 月
"d+": date.getDate(), // 日
"H+": date.getHours(), // 时
"m+": date.getMinutes(), // 分
"s+": date.getSeconds(), // 秒
"S+": date.getMilliseconds(), // 毫秒
"q+": Math.floor((date.getMonth() + 3) / 3), // 季度
};
let matchArray = [];
for (let key in dateObject) {
matchArray = format.match(new RegExp(`(${key})`, 'g'));
if (matchArray && matchArray.length) {
let fillZero = '';
for (let i = 0; i < matchArray[0].length; i++) {
fillZero = fillZero + '0';
}
if (/S+/g.test(matchArray[0])) format = format.replace(matchArray[0], dateObject[key]);
format = format.replace(matchArray[0], (fillZero + dateObject[key]).substring(("" + dateObject[key]).length));
}
}
return format;
}

输入数字限制

项目中常常要求输入框的数字限制,咱干脆就一次整个方法,方便处理输入数字时的各种要求限制,已经过不少维护改良。仅需填写对应规则参数就能轻松解决各种限制要求

1
2
3
4
5
6
7
8
<el-input placeholder="请输入" v-model="item.number"
@input="inputNumberLimit({
target: [item, 'number'],
isPositiveNumber: true,
decimalPlaces: 2,
maxValue: 2147483647,
});"
clearable></el-input>
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
/**
* 输入数字限制
* @param {object} rule 限制规则
* @param {array} [rule.target=[{}, '']] 限制目标,如限制object.property指向的数字,则为[object, 'property']
* @param {boolean} [rule.isPositiveNumber=false] 是否正数
* @param {boolean} [rule.isInteger=false] 是否整数
* @param {number} [rule.digit=-1] 位数(-1则不限制位数)
* @param {number} [rule.decimalPlaces=-1] 小数点位数(-1则不限制位数)
* @param {number} rule.maxValue 最大值
* @param {number} rule.minValue 最小值
* @param {boolean} [rule.isNotEmpty=false] 是否不为空,值为空时替换为0
*/
function inputNumberLimit(rule) {
rule = Object.assign({ // 默认值
target: [{}, ''],
isPositiveNumber: false,
isInteger: false,
digit: -1,
decimalPlaces: -1,
isNotEmpty: false,
}, rule);
if (rule.isPositiveNumber) { // 正数
if (rule.isInteger) rule.target[0][rule.target[1]] = String(rule.target[0][rule.target[1]]).replace(/\D/g, ''); // 整型(正数)
else rule.target[0][rule.target[1]] = String(rule.target[0][rule.target[1]]).replace(/[^\d|^\.]/g, ''); // 浮点型(正数)
} else { // 支持负数
if (rule.isInteger) rule.target[0][rule.target[1]] = String(rule.target[0][rule.target[1]]).replace(/[^-|\d]/g, ''); // 整型(支持负数)
else rule.target[0][rule.target[1]] = String(rule.target[0][rule.target[1]]).replace(/[^-|\d|^\.]/g, ''); // 浮点型(支持负数)
}
if (rule.isNotEmpty) rule.target[0][rule.target[1]] = String(rule.target[0][rule.target[1]]) // 不为空
.replace(/^(-|--)$/, '0'); // 为-或--时替换为0
rule.target[0][rule.target[1]] = String(rule.target[0][rule.target[1]])
.replace(/^(0-)$/, '-0') // 为0-时替换为-0
.replace(/(.)-+/g, '$1') // -不能出现在字符后面
.replace(/^(\.)(.*)$/, '0.$2') // 开头不能有.
.replace(/^(-\.)(.*)$/, '-0.$2') // 开头不能有-.
.replace(/^(0)(-|\d+).*$/, '$2') // 数字不能以0开头
.replace(/^(-0)(\d+).*$/, '-$2'); // 数字不能以-0开头
if (rule.digit !== -1) { // 位数
let numberSplitArray = String(rule.target[0][rule.target[1]]).split('.');
if (numberSplitArray.length === 2) {
numberSplitArray[0] = numberSplitArray[0].replace(new RegExp(`^(-|\\+)?(\\d{${rule.digit}}).*$`), '$1$2');
rule.target[0][rule.target[1]] = numberSplitArray[0] + (numberSplitArray[1] ? ('.' + numberSplitArray[1]) : '.');
} else {
rule.target[0][rule.target[1]] = numberSplitArray[0].replace(new RegExp(`^(-|\\+)?(\\d{${rule.digit}}).*$`), '$1$2');
}
}
if (rule.decimalPlaces !== -1) rule.target[0][rule.target[1]] = String(rule.target[0][rule.target[1]]).replace(new RegExp(`^(-|\\+)?(\\d+)\\.(\\d{${rule.decimalPlaces}}).*$`), '$1$2.$3'); // 小数点位数
if (rule.maxValue != null && (rule.target[0][rule.target[1]] > rule.maxValue)) rule.target[0][rule.target[1]] = String(rule.maxValue); // 最大值
if (rule.minValue != null && (rule.target[0][rule.target[1]] < rule.minValue)) rule.target[0][rule.target[1]] = String(rule.minValue); // 最小值
if (String(rule.target[0][rule.target[1]]).match(/\./g) && String(rule.target[0][rule.target[1]]).match(/\./g).length > 1) rule.target[0][rule.target[1]] = '';
if (rule.isNotEmpty && (rule.target[0][rule.target[1]] == null || rule.target[0][rule.target[1]] === '')) rule.target[0][rule.target[1]] = '0'; // 不为空,值为空时替换为0
}

数字四舍五入

四舍五入为什么要封装一个方法?因为常用的 toFixed()Math.round() 并没有达到预期

  1. toFixed() 把数字转换为字符串,结果的小数点后有指定位数的数字(进行四舍六入五考虑,五后非零则进一,五后皆零看奇偶,五前偶舍奇进一)
    经测试发现,并没有完全遵守银行家舍入规则,可能存在兼容性问题
    所以如果需要四舍五入保留两位小数可以这样写:(Math.round(1.555 * 100) / 100).toFixed(2) // 1.56
  2. Math.round() 处理负数的问题:Math.round(-1.5); // -1
    解决方法(如果是负数的话就转成正数四舍五入后再转回来):Math.round(-1.5 * -1) * -1; // -2

所以才封装了这么一个方法,已解决以上问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 数字四舍五入
* @param {string|number} number 数字
* @param {number} [decimalPlaces=0] 小数点位数
* @param {boolean} [isFillZero=true] 是否补零
* @returns {string} 四舍五入后的数字
*/
function numberRound(number, decimalPlaces = 0, isFillZero = true) {
let flag = Number(number) < 0 ? -1 : 1;
let operand = '1';
for (let i = 0; i < decimalPlaces; i++) operand = operand + '0';
let result = Math.round(number * flag * operand) / operand * flag;
if (isFillZero) return result.toFixed(decimalPlaces);
return String(result);
}

所以看到这,是不是觉得自己已经写过不少 toFixed()Math.round() 感到麻了。。
数字的处理还是要精确的好,一定要重视哦,特别是关于统计重要的数额。

小数计算精度问题

小数之间的计算也有精度问题,比如 0.1 + 0.2 = 0.30000000000000004
这个不是 JavaScript 独有的问题,Java C++ C# Python 都有这个问题,都是采用的 IEEE 754 (二进制浮点数算术标准)

原因:因为计算机只认识二进制,所以需要把数字转成二进制计算再转成十进制,这个过程中出现了无限循环,所以造成超出部分被截取,具体计算过程网上有大量说明,咱就不陈述了

推荐食用 decimal.js 能精度计算,或者自行保留几位小数也行。
Java的话 new BigDecimal("0.1").add(BigDecimal.valueOf(0.2)) 这样就行

数位分级格式化

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
/**
* 数位分级格式化
* @param {string|number} digit 需要数位分级格式化的数字(如 1234567.00)
* @param {number} [intervalNum=3] 以逗号分割的位数
* @return {string} 数位分级格式化后的数字(如 1,234,567.00)
*/
function digitGradeFormat(digit, intervalNum = 3) {
let digitSplitArr = String(digit).split('.');
let count = Math.floor(digitSplitArr[0].length / intervalNum);
for (let i = 0; i < count; i++) {
if (digitSplitArr[0].length > intervalNum) {
let commaIndex = digitSplitArr[0].indexOf(','), newCommaIndex;
if (commaIndex !== -1) newCommaIndex = commaIndex - intervalNum;
else newCommaIndex = digitSplitArr[0].length - intervalNum;
if (newCommaIndex !== 0 && digitSplitArr[0][newCommaIndex - 1] !== '-') {
digitSplitArr[0] = digitSplitArr[0].split('');
digitSplitArr[0].splice(newCommaIndex, 0, ',');
digitSplitArr[0] = digitSplitArr[0].join('');
}
if (digitSplitArr.length === 1) digit = digitSplitArr[0];
else digit = digitSplitArr[0] + '.' + digitSplitArr[1];
}
}
return digit;
}

Url参数格式化

不够用的话,推荐一个更好的依赖:https://github.com/ljharb/qs

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Url参数格式化
* @param {object} params 需要格式化的参数对象 如: {a:1,b:true,c:[1,2],d:{a:1,b:false}}
* @returns {string} 格式化后的Url参数字符串 如: a=1&b=true&c=%5B1%2C2%5D&d=%7B%22a%22%3A1%2C%22b%22%3Afalse%7D
*/
function urlParamsFormat(params) {
let urlParams = '';
for (const key of Object.keys(params)) {
urlParams = urlParams + key + '=' + (typeof params[key] === 'string' ? encodeURIComponent(params[key]) : encodeURIComponent(JSON.stringify(params[key]))) + '&';
}
return urlParams.replace(/&$/, '');
}

深拷贝对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 深拷贝对象
* @param {object|array} objectOrArray 需要深拷贝的对象或含对象的数组
* @returns {object|array} 深拷贝后的对象或含对象的数组
*/
function deepCloneObject(objectOrArray) {
let newObjectOrArray = {};
if (objectOrArray === null) newObjectOrArray = null;
else if (objectOrArray === undefined) newObjectOrArray = undefined;
else if (objectOrArray.constructor === Array) newObjectOrArray = [];
else if (objectOrArray.constructor !== Object) return objectOrArray;
for (let key in objectOrArray) {
let value = objectOrArray[key];
newObjectOrArray[key] = typeof value === 'object' ? deepCloneObject(value) : value;
// newObjectOrArray[key] = typeof value === 'object' ? arguments.callee(value) : value; // arguments.callee 属性包含当前正在执行的函数, 一般用在匿名函数中(箭头函数无效)
}
return newObjectOrArray;
}

什么是浅拷贝和深拷贝?

浅拷贝,深度的属性还是会指向原来的对象

1
2
newObj = Object.assign({}, obj);
newObj = {...obj};

深拷贝,完完全全的新对象

newObj = JSON.parse(JSON.stringify(obj)); // 不推荐,有的类型解析不到

  1. 如果json里面有时间对象,则序列化结果:时间对象=>字符串的形式
  2. 如果json里有RegExp、Error对象,则序列化的结果将只得到空对象 RegExp、Error => {}
  3. 如果json里有 function,undefined,则序列化的结果会把 function,undefined 丢失
  4. 如果json里有NaN、Infinity和-Infinity,则序列化的结果会变成null
  5. 如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor
  6. 如果对象中存在循环引用的情况也无法实现深拷贝

以上,如果拷贝的对象不涉及上面的情况,可以使用 JSON.parse(JSON.stringify(obj)); 实现深拷贝。否则就用这个函数吧

获取通用唯一识别码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 获取通用唯一识别码
* @return {string}
*/
function getUUID() {
let d = new Date().getTime();
if (window && window.performance && typeof window.performance.now === "function") {
d += performance.now();
}
let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid;
}

获取中国区域级联选项

依赖:https://github.com/modood/Administrative-divisions-of-China
中华人民共和国行政区划(五级):省级、地级、县级、乡级和村级。
数据来源民政部、国家统计局:https://www.mca.gov.cn/article/sj/xzqh/1980/

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
import pcasCode from 'china-division/dist/pcas-code.json';

/**
* 获取中国区域级联选项
* @param {number} [level=4] 等级
* @param {string} [labelField='name'] 选项标签字段
* @param {string} [valueField='name'] 选项值字段
* @returns {array} 中国区域级联选项
*/
function getChinaAreaCascadeOptions(level = 4, labelField = 'name', valueField = 'name') {
if (level < 1) return [];
return (function recursion(data, currentLevel) {
let options = [];
for (const item of data) {
let option = {
label: item[labelField],
value: item[valueField],
}
if (item?.children?.length && currentLevel < level) {
option.children = recursion(item.children, currentLevel + 1);
}
options.push(option);
}
return options;
})(pcasCode, 1);
}

枚举的用法

1
2
3
4
5
6
7
8
9
10
11
12
let testEnum = {
enum: [
{key: '测试键0', value: '测试值0'},
{key: '测试键1', value: '测试值1'},
{key: '测试键2', value: '测试值2'},
],
get: (key, value) => testEnum.enum.find(i => i[key] === value) ?? {},
}

console.log(
testEnum.get('key', '测试键1').value
);

平铺的对象数组递归为多级结构

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
let data = [
{
"id": "001",
"parentId": null,
},
{
"id": "002",
"parentId": null,
},
{
"id": "003",
"parentId": "001",
},
{
"id": "004",
"parentId": "003",
},
{
"id": "005",
"parentId": "003",
},
];

let newData = [];

data.forEach((item, index, array) => {
if (!item.parentId) {
newData.push((function recursion(item) {
let children = array.filter(i => i.parentId === item.id) || [];
children.forEach(_item => recursion(_item));
item.children = children;
return item;
})(item));
}
});

评估

满足不了?推荐一个依赖:https://github.com/bplok20010/eval5,支持浏览器、node.js、小程序等 JavaScript 运行环境

1
2
3
4
5
6
7
8
9
/**
* 评估
* @param {string} expression 评估表达式 如 `{result:(1+2)*3}`
* @return {*} 评估结果 如 {result:9}
*/
function evaluate(expression) {
if (typeof (expression) === 'string') return Function('"use strict"; return (' + expression + ')')();
return;
}

下载Blob响应

当通过 AJAX 请求带有 Content-Disposition 响应头的 Blob 时,是不会触发浏览器自动下载附件的功能的,需要自行创建可下载链接,然后通过 iconv-lite 对响应中 filename 键的 UTF-8 值进行解码得到文件名后下载。具体实现如下

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
import iconv from "iconv-lite";
import { ElMessage } from 'element-plus';

/**
* 下载Blob响应
* @param res 响应
*/
function downloadBlobResponse(res) {
let link = document.createElement('a');
link.href = URL.createObjectURL(res.data);
link.target = '_self';
link.download = iconv.decode(res.headers['Content-Disposition']?.replace(/.*filename=(.*)/, '$1') || res.headers['content-disposition']?.replace(/.*filename=(.*)/, '$1') || '', 'UTF-8');
if (link.download) {
link.click();
return;
} else if (res.data.type === 'application/json') {
res.data.text().then(res => {
let resObj = JSON.parse(res);
ElMessage.error({message: resObj.desc || resObj.msg || resObj.message || '服务器内部错误', showClose: true});
});
} else {
ElMessage.error({message: '服务器内部错误', showClose: true});
}
throw '';
}

下载重命名文件

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
/**
* 下载重命名文件
* @param {string} url 下载Url
* @param {string} fileRename 文件重命名
* @param {boolean} [isKeepFileType=true] 是否保持原有文件类型 如.txt
* @return {Promise<unknown>}
*/
function downloadRenameFile(url, fileRename, isKeepFileType = true) {
let fileType = '';
if (isKeepFileType) {
let fileNameSplitArr = new URL(url).pathname.split('.');
if (fileNameSplitArr.length > 1) {
fileType = fileNameSplitArr[fileNameSplitArr.length - 1];
}
}
let _url = url.replace('https://xxx-file-123456789.cos.ap-beijing.myqcloud.com', '/os-xxx-file');
return new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('get', _url, true);
xhr.responseType = 'blob';
xhr.onload = e => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject();
}
}
xhr.send();
}).then(res => {
let link = document.createElement('a');
link.href = URL.createObjectURL(res);
link.target = '_self';
link.download = fileRename + (fileType ? ('.' + fileType) : '');
link.click();
resolve();
}).catch(err => {
reject(err);
});
});
}

上传文件到腾讯云对象存储

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
/**
* 上传文件到腾讯云对象存储
* @param {File} file 文件
* @return {Promise<unknown>}
*/
function uploadFileToOs(file) {
return new Promise(async (resolve, reject) => {
try {
await file.arrayBuffer();
} catch (e) {
if (e.message === 'The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.') {
Message.error({message: '文件已被更改,请重新选择文件', showClose: true});
} else {
Message.error({message: '文件读取失败', showClose: true});
}
return reject();
}
let fileMD5 = ''; // 文件md5
await new Promise((resolve, reject) => { // 获取文件md5
bmf.md5(file, (err, md5) => {
if (err) reject();
fileMD5 = md5;
resolve();
});
}).catch(err => {
Message.error('获取文件md5失败');
reject();
});
await getUploadOsAuth({ // 获取上传腾讯云对象存储授权
param: {
fileName: file.name || '', // 文件名
md5: fileMD5, // 文件md5
}
}).then(res => {
if (res && res.data) {
let authorization = res.data; // 授权
let uploadUrl = authorization.url; // 上传Url
if (process.env.NODE_ENV === 'development') uploadUrl = uploadUrl.replace('https://XXX-file-123456789.cos.ap-beijing.myqcloud.com', '/os-XXX-file');
new Promise((resolve, reject) => { // 上传
let xhr = new XMLHttpRequest();
xhr.open('put', uploadUrl, true);
xhr.setRequestHeader('Content-Type', file.type);
xhr.setRequestHeader('Authorization', authorization.additionalHeaders.Authorization);
xhr.onload = e => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject();
}
}
xhr.send(file);
}).then(res => {
resolve(authorization.url);
}).catch(err => {
Message.error('上传失败');
reject(err);
});
} else {
Message.error('上传授权失败');
reject();
}
}).catch(err => {
reject(err);
});
});
}

CookieUtils

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
class CookieUtils {
static get(sKey) {
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
}

static set(sKey, sValue, vEnd, sPath, sDomain, bSecure) {
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) {
return false;
}
let sExpires = "";
if (vEnd) {
switch (vEnd.constructor) {
case Number:
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
break;
case String:
sExpires = "; expires=" + vEnd;
break;
case Date:
sExpires = "; expires=" + vEnd.toUTCString();
break;
}
}
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
return true;
}

static remove(sKey, sPath, sDomain) {
if (!sKey || !this.has(sKey)) {
return false;
}
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
return true;
}

static has(sKey) {
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
}

static keys() {
let aKeys = document.cookie.replace(new RegExp('((?:^|\\s*;)[^\\=]*)(?:;|$)|^\\s*|\\s*(?:\\=[^;]*)?(?:\\1|$)', 'g'), "").split(/\s*(?:\=[^;]*)?;\s*/);
for (let nIdx = 0; nIdx < aKeys.length; nIdx++) {
aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
}
return aKeys;
}
}