|
|
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
|
|
|
import { Tag } from "antd";
|
|
|
import { CaretUpOutlined, CaretDownOutlined } from "@ant-design/icons";
|
|
|
import moment from "moment";
|
|
|
|
|
|
if (!String.prototype.padStart) {
|
|
|
String.prototype.padStart = function padStart(targetLength, padString) {
|
|
|
targetLength = targetLength >> 0; // floor if number or convert non-number to 0;
|
|
|
padString = String(typeof padString !== "undefined" ? padString : " ");
|
|
|
if (this.length > targetLength) {
|
|
|
return String(this);
|
|
|
} else {
|
|
|
targetLength = targetLength - this.length;
|
|
|
if (targetLength > padString.length) {
|
|
|
padString += padString.repeat(targetLength / padString.length); // append to original to ensure we are longer than needed
|
|
|
}
|
|
|
return padString.slice(0, targetLength) + String(this);
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
|
|
|
if (!Object.fromEntries) {
|
|
|
Object.fromEntries = function(entries) {
|
|
|
const obj = {};
|
|
|
for (let i = 0; i < entries.length; ++i) {
|
|
|
const entry = entries[i];
|
|
|
if (entry && entry.length === 2) {
|
|
|
obj[entry[0]] = entry[1];
|
|
|
}
|
|
|
}
|
|
|
return obj;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
export function copy(obj) {
|
|
|
return JSON.parse(JSON.stringify(obj));
|
|
|
}
|
|
|
|
|
|
export function named(value) {
|
|
|
return function (target) {
|
|
|
target.definedName = value;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
export function formatCurrency(name) {
|
|
|
if (name === "USD") {
|
|
|
return "$";
|
|
|
} else if (name === "RMB") {
|
|
|
return "¥";
|
|
|
} else if (name === "EUR") {
|
|
|
return "€";
|
|
|
} else if (name === "GBP") {
|
|
|
return "£";
|
|
|
} else {
|
|
|
return name + " ";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export function formatPrice(price) {
|
|
|
return Math.ceil(price).toLocaleString();
|
|
|
}
|
|
|
|
|
|
// 千分符的金额转成数字,默认为0
|
|
|
export function price_to_number(price) {
|
|
|
const num_string = (price + "").replace(/,/g, "");
|
|
|
const number = parseFloat(num_string);
|
|
|
return isNaN(number) ? 0 : number;
|
|
|
}
|
|
|
|
|
|
export function formatPercent(number) {
|
|
|
return Math.round(number * 100) + "%";
|
|
|
}
|
|
|
|
|
|
export function formatDate(date) {
|
|
|
if (isEmpty(date)) {
|
|
|
return "NaN";
|
|
|
}
|
|
|
|
|
|
const year = date.getFullYear();
|
|
|
const month = date.getMonth() + 1;
|
|
|
const day = date.getDate();
|
|
|
|
|
|
const monthStr = ("" + month).padStart(2, 0);
|
|
|
const dayStr = ("" + day).padStart(2, 0);
|
|
|
const formatted = year + "-" + monthStr + "-" + dayStr;
|
|
|
|
|
|
return formatted;
|
|
|
}
|
|
|
|
|
|
export function formatTime(date) {
|
|
|
const hours = date.getHours();
|
|
|
const minutes = date.getMinutes();
|
|
|
|
|
|
const hoursStr = ("" + hours).padStart(2, 0);
|
|
|
const minutesStr = ("" + minutes).padStart(2, 0);
|
|
|
const formatted = hoursStr + ":" + minutesStr;
|
|
|
|
|
|
return formatted;
|
|
|
}
|
|
|
|
|
|
export function formatDatetime(date) {
|
|
|
const year = date.getFullYear();
|
|
|
const month = date.getMonth() + 1;
|
|
|
const day = date.getDate();
|
|
|
|
|
|
const monthStr = ("" + month).padStart(2, 0);
|
|
|
const dayStr = ("" + day).padStart(2, 0);
|
|
|
|
|
|
const hours = date.getHours();
|
|
|
const minutes = date.getMinutes();
|
|
|
|
|
|
const hoursStr = ("" + hours).padStart(2, 0);
|
|
|
const minutesStr = ("" + minutes).padStart(2, 0);
|
|
|
|
|
|
const formatted = year + "-" + monthStr + "-" + dayStr + " " + hoursStr + ":" + minutesStr;
|
|
|
|
|
|
return formatted;
|
|
|
}
|
|
|
|
|
|
export function mixins(...list) {
|
|
|
return function (target) {
|
|
|
list.forEach(val => {
|
|
|
const mixinObj = Object.create(val.prototype, {});
|
|
|
const name = Object.getPrototypeOf(mixinObj).constructor.name;
|
|
|
const camelCase = name.substr(0, 1).toLowerCase() + name.substr(1);
|
|
|
Object.assign(target.prototype, { [camelCase]: mixinObj });
|
|
|
});
|
|
|
};
|
|
|
}
|
|
|
|
|
|
export function camelCase(name) {
|
|
|
return name.substr(0, 1).toLowerCase() + name.substr(1);
|
|
|
}
|
|
|
|
|
|
export class UrlBuilder {
|
|
|
constructor(url) {
|
|
|
this.url = url;
|
|
|
this.paramList = [];
|
|
|
}
|
|
|
|
|
|
append(name, value) {
|
|
|
if (isNotEmpty(value)) {
|
|
|
this.paramList.push({ name, value });
|
|
|
}
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
build() {
|
|
|
this.paramList.forEach((e, i, a) => {
|
|
|
if (i === 0) {
|
|
|
this.url += "?";
|
|
|
} else {
|
|
|
this.url += "&";
|
|
|
}
|
|
|
this.url += e.name + "=" + e.value;
|
|
|
});
|
|
|
return this.url;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export function isNotEmpty(val) {
|
|
|
return val !== undefined && val !== null && val !== "";
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* ! 不支持计算 Set 或 Map
|
|
|
* @param {*} val
|
|
|
* @example
|
|
|
* true if: 0, [], {}, null, '', undefined
|
|
|
* false if: 'false', 'undefined'
|
|
|
*/
|
|
|
export function isEmpty(val) {
|
|
|
// return val === undefined || val === null || val === "";
|
|
|
return [Object, Array].includes((val || {}).constructor) && !Object.entries((val || {})).length;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @example
|
|
|
* empty(0) => false
|
|
|
*/
|
|
|
export function empty(a) {
|
|
|
if (a === "") return true; // 检验空字符串
|
|
|
if (a === "null") return true; // 检验字符串类型的null
|
|
|
if (a === "undefined") return true; // 检验字符串类型的 undefined
|
|
|
if (!a && a !== 0 && a !== "") return true; // 检验 undefined 和 null
|
|
|
if (Array.prototype.isPrototypeOf(a) && a.length === 0) return true; // 检验空数组
|
|
|
if (Object.prototype.isPrototypeOf(a) && Object.keys(a).length === 0) return true; // 检验空对象
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
export function prepareUrl(url) {
|
|
|
return new UrlBuilder(url);
|
|
|
}
|
|
|
|
|
|
export function debounce(fn, delay = 500) {
|
|
|
let timer;
|
|
|
return e => {
|
|
|
e.persist();
|
|
|
clearTimeout(timer);
|
|
|
timer = setTimeout(() => {
|
|
|
fn(e);
|
|
|
}, delay);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
export function throttle(fn, delay, atleast) {
|
|
|
let timeout = null;
|
|
|
let startTime = new Date();
|
|
|
return function () {
|
|
|
const curTime = new Date();
|
|
|
clearTimeout(timeout);
|
|
|
if (curTime - startTime >= atleast) {
|
|
|
fn();
|
|
|
startTime = curTime;
|
|
|
} else {
|
|
|
timeout = setTimeout(fn, delay);
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
|
|
|
export function clickUrl(url) {
|
|
|
const httpLink = document.createElement("a");
|
|
|
httpLink.href = url;
|
|
|
httpLink.target = "_blank";
|
|
|
httpLink.click();
|
|
|
}
|
|
|
|
|
|
export function show_vs_tag(vs, vs_diff, data1, data2) {
|
|
|
let tag = "-";
|
|
|
if (parseInt(vs) < 0) {
|
|
|
tag = (
|
|
|
<Tag icon={<CaretDownOutlined />} color="gold">
|
|
|
{vs} {vs_diff}
|
|
|
</Tag>
|
|
|
);
|
|
|
} else if (parseInt(vs) > 0) {
|
|
|
tag = (
|
|
|
<Tag icon={<CaretUpOutlined />} color="lime">
|
|
|
{vs} {vs_diff}
|
|
|
</Tag>
|
|
|
);
|
|
|
}
|
|
|
return (
|
|
|
<span>
|
|
|
<div>
|
|
|
{data1} vs {data2}
|
|
|
</div>
|
|
|
{tag}
|
|
|
</span>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// 数组去掉重复
|
|
|
export function unique(arr) {
|
|
|
const x = new Set(arr);
|
|
|
return [...x];
|
|
|
}
|
|
|
|
|
|
export function getWeek(date) {
|
|
|
// 参数时间戳
|
|
|
const week = moment(date).day();
|
|
|
switch (week) {
|
|
|
case 1:
|
|
|
return "周一";
|
|
|
case 2:
|
|
|
return "周二";
|
|
|
case 3:
|
|
|
return "周三";
|
|
|
case 4:
|
|
|
return "周四";
|
|
|
case 5:
|
|
|
return "周五";
|
|
|
case 6:
|
|
|
return "周六";
|
|
|
case 0:
|
|
|
return "周日";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 把非数字下标的数组设置下标,因为非数字数组的length为0导致读取失败
|
|
|
export function set_array_index(result) {
|
|
|
const result_array = [];
|
|
|
const result_keys = Object.keys(result);
|
|
|
result_keys.sort(); // 必须做一次排序,用for in循环会导致顺序错误
|
|
|
for (const key of result_keys) {
|
|
|
result_array.push(result[key]);
|
|
|
}
|
|
|
return result_array;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 排序
|
|
|
*/
|
|
|
export const sortBy = (key) => {
|
|
|
return (a, b) => (a[key] > b[key]) ? 1 : ((b[key] > a[key]) ? -1 : 0);
|
|
|
};
|
|
|
|
|
|
export const sortKeys = (obj) =>
|
|
|
Object.keys(obj)
|
|
|
.sort()
|
|
|
.reduce((a, k2) => ({...a, [k2]: obj[k2]}), {});
|
|
|
|
|
|
/**
|
|
|
* 合并Object, 递归地
|
|
|
*/
|
|
|
export function merge(...objects) {
|
|
|
const isDeep = objects.some(obj => obj !== null && typeof obj === 'object');
|
|
|
|
|
|
const result = objects[0] || (isDeep ? {} : objects[0]);
|
|
|
|
|
|
for (let i = 1; i < objects.length; i++) {
|
|
|
const obj = objects[i];
|
|
|
|
|
|
if (!obj) continue;
|
|
|
|
|
|
Object.keys(obj).forEach(key => {
|
|
|
const val = obj[key];
|
|
|
|
|
|
if (isDeep) {
|
|
|
if (Array.isArray(val)) {
|
|
|
result[key] = [].concat(Array.isArray(result[key]) ? result[key] : [result[key]], val);
|
|
|
} else if (typeof val === 'object') {
|
|
|
result[key] = merge(result[key], val);
|
|
|
} else {
|
|
|
result[key] = val;
|
|
|
}
|
|
|
} else {
|
|
|
result[key] = typeof val === 'boolean' ? val : result[key];
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 数组分组
|
|
|
* - 相当于 lodash 的 _.groupBy
|
|
|
* @see https://www.lodashjs.com/docs/lodash.groupBy#_groupbycollection-iteratee_identity
|
|
|
*/
|
|
|
export function groupBy(array, callback) {
|
|
|
return array.reduce((groups, item) => {
|
|
|
const key = callback(item);
|
|
|
|
|
|
if (!groups[key]) {
|
|
|
groups[key] = [];
|
|
|
}
|
|
|
|
|
|
groups[key].push(item);
|
|
|
return groups;
|
|
|
}, {});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建一个从 object 中选中的属性的对象。
|
|
|
* @param {*} object
|
|
|
* @param {array} keys
|
|
|
*/
|
|
|
export function pick(object, keys) {
|
|
|
return keys.reduce((obj, key) => {
|
|
|
if (object && Object.prototype.hasOwnProperty.call(object, key)) {
|
|
|
obj[key] = object[key];
|
|
|
}
|
|
|
return obj;
|
|
|
}, {});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 返回对象的副本,经过筛选以省略指定的键。
|
|
|
* @param {*} object
|
|
|
* @param {string[]} keysToOmit
|
|
|
* @returns
|
|
|
*/
|
|
|
export function omit(object, keysToOmit) {
|
|
|
return Object.fromEntries(
|
|
|
Object.entries(object).filter(
|
|
|
([key]) => !keysToOmit.includes(key)
|
|
|
)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 深拷贝
|
|
|
*/
|
|
|
export function cloneDeep(value) {
|
|
|
if (typeof value !== 'object' || value === null) {
|
|
|
return value;
|
|
|
}
|
|
|
|
|
|
const result = Array.isArray(value) ? [] : {};
|
|
|
|
|
|
for (const key in value) {
|
|
|
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
|
result[key] = cloneDeep(value[key]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 向零四舍五入, 固定精度设置
|
|
|
*/
|
|
|
function curriedFix(precision = 0) {
|
|
|
return function(number) {
|
|
|
// Shift number by precision places
|
|
|
const shift = Math.pow(10, precision);
|
|
|
const shiftedNumber = number * shift;
|
|
|
|
|
|
// Round to nearest integer
|
|
|
const roundedNumber = Math.round(shiftedNumber);
|
|
|
|
|
|
// Shift back decimal place
|
|
|
return roundedNumber / shift;
|
|
|
};
|
|
|
}
|
|
|
/**
|
|
|
* 向零四舍五入, 保留2位小数
|
|
|
*/
|
|
|
export const fixTo2Decimals = curriedFix(2);
|
|
|
/**
|
|
|
* 向零四舍五入, 保留4位小数
|
|
|
*/
|
|
|
export const fixTo4Decimals = curriedFix(4);
|
|
|
|
|
|
export const fixTo1Decimals = curriedFix(1);
|
|
|
export const fixToInt = curriedFix(0);
|
|
|
|
|
|
/**
|
|
|
* 映射
|
|
|
* @example
|
|
|
* const keyMap = {
|
|
|
a: [{key: 'a1'}, {key: 'a2', transform: v => v * 2}],
|
|
|
b: {key: 'b1'}
|
|
|
};
|
|
|
const result = objectMapper({a: 1, b: 3}, keyMap);
|
|
|
// result = {a1: 1, a2: 2, b1: 3}
|
|
|
*
|
|
|
*/
|
|
|
export function objectMapper(input, keyMap) {
|
|
|
// Loop through array mapping
|
|
|
if (Array.isArray(input)) {
|
|
|
return input.map((obj) => objectMapper(obj, keyMap));
|
|
|
}
|
|
|
|
|
|
if (typeof input === 'object') {
|
|
|
const mappedObj = {};
|
|
|
|
|
|
Object.keys(input).forEach((key) => {
|
|
|
// Keep original keys not in keyMap
|
|
|
if (!keyMap[key]) {
|
|
|
mappedObj[key] = input[key];
|
|
|
}
|
|
|
// Handle array of maps
|
|
|
if (Array.isArray(keyMap[key])) {
|
|
|
keyMap[key].forEach((map) => {
|
|
|
let value = input[key];
|
|
|
if (map.transform) value = map.transform(value);
|
|
|
mappedObj[map.key] = value;
|
|
|
});
|
|
|
|
|
|
// Handle single map
|
|
|
} else {
|
|
|
const map = keyMap[key];
|
|
|
if (map) {
|
|
|
let value = input[key];
|
|
|
if (map.transform) value = map.transform(value);
|
|
|
mappedObj[map.key || key] = value;
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
return mappedObj;
|
|
|
}
|
|
|
|
|
|
return input;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建一个对应于对象路径的值数组
|
|
|
*/
|
|
|
export function at(obj, path) {
|
|
|
let result;
|
|
|
if (Array.isArray(obj)) {
|
|
|
// array case
|
|
|
const indexes = path.split('.').map((i) => parseInt(i));
|
|
|
result = [];
|
|
|
for (let i = 0; i < indexes.length; i++) {
|
|
|
result.push(obj[indexes[i]]);
|
|
|
}
|
|
|
} else {
|
|
|
// object case
|
|
|
const indexes = path.split('.').map((i) => i);
|
|
|
result = [obj];
|
|
|
for (let i = 0; i < indexes.length; i++) {
|
|
|
result = [result[0][indexes[i]]];
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
/**
|
|
|
* 删除 null/undefined
|
|
|
*/
|
|
|
export function flush(collection) {
|
|
|
let result, len, i;
|
|
|
if (!collection) {
|
|
|
return undefined;
|
|
|
}
|
|
|
if (Array.isArray(collection)) {
|
|
|
result = [];
|
|
|
len = collection.length;
|
|
|
for (i = 0; i < len; i++) {
|
|
|
const elem = collection[i];
|
|
|
if (elem != null) {
|
|
|
result.push(elem);
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
if (typeof collection === 'object') {
|
|
|
result = {};
|
|
|
const keys = Object.keys(collection);
|
|
|
len = keys.length;
|
|
|
for (i = 0; i < len; i++) {
|
|
|
const key = keys[i];
|
|
|
const value = collection[key];
|
|
|
if (value != null) {
|
|
|
result[key] = value;
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
return undefined;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 千分位 格式化数字
|
|
|
*/
|
|
|
export const numberFormatter = (number) => {
|
|
|
return new Intl.NumberFormat().format(number);
|
|
|
};
|
|
|
|
|
|
export const getNestedValue = (obj, keyArr) => {
|
|
|
return keyArr.reduce((acc, curr) => {
|
|
|
return acc && acc[curr];
|
|
|
}, obj);
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 计算笛卡尔积
|
|
|
*/
|
|
|
export const cartesianProductArray = (arr, sep = '_', index = 0, prefix = '') => {
|
|
|
let result = [];
|
|
|
if(index === arr.length){
|
|
|
return [prefix];
|
|
|
}
|
|
|
arr[index].forEach(item => {
|
|
|
result = result.concat(cartesianProductArray(arr, sep, index+1, prefix ? `${prefix}${sep}${item}` : `${item}`));
|
|
|
});
|
|
|
return result;
|
|
|
};
|