list <---> tree
const sources = [
{ id: "1", name: '部门1', pid: "0" },
{ id: "1-1", name: '部门1的子部门1-1', pid: "1" },
{ id: "1-1-1", name: '部门1-1的子部门1-1-1', pid: "1-1" },
{ id: "1-1-2", name: '部门1-1的子部门1-1-2', pid: "1-1" },
{ id: "1-2", name: '部门1的子部门1-2', pid: "1" },
{ id: "2", name: '部门2', pid: "0" },
{ id: "2-1", name: '部门2的子部门2-1', pid: "2" },
{ id: "2-1-1", name: '部门2-1的子部门2-1-1', pid: "2-1" },
{ id: "3", name: '部门3', pid: "0" }
]
listToTree(sources,{id:"id", parentId:"pid"})
export default function listToTree (list = [], { id, parentId } = { id: "id", parentId: "parentId" }) {
const sourceMap = new Map();
const trees = [];
for (const item of list) {
item.children = [];
sourceMap.set(item[id], item)
}
for (const key of sourceMap.keys()) {
let item = sourceMap.get(key);
if (!sourceMap.has(item[parentId])) {
trees.push(item)
} else {
const parentItem = sourceMap.get(item[parentId]);
parentItem.children.push(item)
}
}
return trees
}
export default function treeToList(trees = [], list = [], childrenKeyName = 'children') {
trees.forEach(item => {
let { [childrenKeyName]: children, ...other } = item
list.push(other)
if (Array.isArray(children) && children.length >= 1) {
treeToList(children, list)
} else {
other.leaf = true
}
})
return list
}
手动导出excel
const suffix = ".xlsx"
function downloadFileByBlob(text, type, fileName) {
let url = window.URL.createObjectURL(new Blob([text], { type }));
let aDom = document.createElement("a");
aDom.setAttribute("href", url);
fileName = decodeURIComponent(fileName);
aDom.setAttribute("download", fileName);
document.body.appendChild(aDom)
aDom.click();
document.body.removeChild(aDom)
window.URL.revokeObjectURL(url);
}
function exportExcel({ datas = [], header = {}, fileName = "文件" } = {}) {
let columnHtml = "";
columnHtml += "<tr style=\"text-align: center;\">\n";
for (let key in header) {
columnHtml += "<td style=\"background-color:#bad5fd\">" + header[key] + "</td>\n";
}
columnHtml += "</tr>\n";
let dataHtml = "";
for (let data of datas) {
dataHtml += "<tr style=\"text-align: center;\">\n";
for (let key in header) {
dataHtml += "<td>" + (data[key] ?? "") + "</td>\n";
}
dataHtml += "</tr>\n";
}
let excelHtml = "<html xmlns:o=\"urn:schemas-microsoft-com:office:office\"\n" +
" xmlns:x=\"urn:schemas-microsoft-com:office:excel\"\n" +
"<head>\n" +
" <xml>\n" +
" <x:ExcelWorkbook>\n" +
" <x:ExcelWorksheets>\n" +
" <x:ExcelWorksheet>\n" +
" <x:Name></x:Name>\n" +
" <x:WorksheetOptions>\n" +
" <x:DisplayGridlines/>\n" +
" </x:WorksheetOptions>\n" +
" </x:ExcelWorksheet>\n" +
" </x:ExcelWorksheets>\n" +
" </x:ExcelWorkbook>\n" +
" </xml>\n" +
" <style>td{font-family: \"宋体\";}</style>\n" +
"</head>\n" +
"<body>\n" +
"<table border=\"1\">\n" +
" <thead>\n" +
columnHtml +
" </thead>\n" +
" <tbody>\n" +
dataHtml +
" </tbody>\n" +
"</table>\n" +
"</body>\n" +
"</html>";
downloadFileByBlob(excelHtml, "application/octet-stream", fileName + suffix);
}
对象拷贝
const sourceObj = { name: "bwf", age: 18, sex: "男", info: "哈哈" }
const targetObj = { age: "", name: "xx", id: 12 }
function propertyCopy(sourceObj = {}, targetObj = {}) {
const sourceKeys = Object.keys(sourceObj)
if (sourceKeys.length < 1) return targetObj
for (const key in targetObj) {
if (!sourceKeys.includes(key)) continue
targetObj[key] = sourceObj[key]
}
return targetObj
}
console.log('propertyCopy', propertyCopy(sourceObj, targetObj))
const sourceObj2 = { name: "bwf", age: 18, sex: "男", info: "哈哈" }
const targetObj2 = { age: "", female: "女", id: 123, userName: "", }
const map = new Map([
['userName', 'name'],
['female', 'sex'],
])
function convertProperty(sourceObj = {}, targetObj = {}, map = new Map()) {
const sourceKeys = Object.keys(sourceObj)
if (sourceKeys.length < 1) return targetObj
for (const key in targetObj) {
if (map.has(key)) {
targetObj[key] = sourceObj[map.get(key)]
} else {
if (!sourceKeys.includes(key)) continue
targetObj[key] = sourceObj[key]
}
}
return targetObj
}
console.log('convertProperty', convertProperty(sourceObj2, targetObj2, map))
源集合 和 目标集合 是否有交集
function includeList(sourceList = [], targetList = []) {
if (sourceList.length < 1 || targetList.length < 1) return false
for (const v of sourceList) {
if (targetList.includes(v)) return true
}
return false
}
const sourceList = ['H5', 'APP']
const targetList = ['PC', 'PC_BK', 'APP', 'WEB']
console.log('includeList', includeList(sourceList, targetList))
日期格式化
import dayjs from 'dayjs'
export default function debounce (val, format = 'YYYY-MM-DD HH:mm:ss') {
if (!isNaN(val)) {
val = parseInt(val)
}
return dayjs(val).format(format)
}
防抖
window.Debounce = function (timeout = 500) {
const instanceMap = new Map();
return function (target, key, descriptor) {
let original = descriptor.value;
descriptor.value = function (...args) {
clearTimeout(instanceMap.get(this));
instanceMap.set(this, setTimeout(() => {
original.apply(this, args);
instanceMap.set(this, null);
}, timeout));
}
return descriptor
}
}
export default function debounce(func, delay) {
let timer = null;
return function () {
let context = this;
let args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
func.apply(context, args);
}, delay);
}
}
深拷贝
export default function deepCopy (obj){
if (typeof obj !== "object" || obj === null) {
return obj;
}
let copy = Array.isArray(obj) ? [] : {};
Object.keys(obj).forEach((key) => {
copy[key] = deepCopy(obj[key]);
});
return copy;
}
脱敏
export default function desensitize (val, format = 'name'){
let result = '';
switch (format) {
case 'name':
const nameLen = val.length
result = val[0] + '*'.repeat(nameLen - 1);
break;
case 'phoneNumber':
result = val.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
break;
case 'idNumber':
result = val.replace(/(\d{4})\d+(\d{4})/, '$1*********$2');
break;
case 'address':
const addressLen = val.length;
result = addressLen > 5 ? val.substring(0, 3) + '*****' + val.substring(addressLen - 2) : val;
break;
default:
break;
}
return result;
}
处理数字成千分位 百分比
export default function numberFormat (num, format, toFixedNum = 2, ){
const reg = /\d{1,3}(?=(\d{3})+$)/g;
const arr = num.toFixed(toFixedNum).split('.');
const int = arr[0];
const decimal = arr[1];
let result = '';
switch (format) {
case 'dollar':
result = '$' + int.replace(reg, '$&,') + '.' + decimal;
break;
case 'percentage':
result = int.replace(reg, '$&,') + '.' + decimal + '%';
break;
default:
result = int.replace(reg, '$&,') + '.' + decimal;
break;
}
return result;
}
校验器
const patterns = {
nameCn: /^[\u4e00-\u9fa5]{2,4}$/,
nameEn: /^[A-Za-z]+([-']?[A-Za-z]+)?\s[A-Za-z]+([-']?[A-Za-z]+)?$/,
mobile: /^(13[0-9]|14[5-9]|15[0-3,5-9]|16[5,6]|17[0-8]|18[0-9]|19[1,8,9])\d{8}$/,
email: /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,
chinese: /[\u4e00-\u9fa5]/,
geZeroInt: /^\d+$/,
int: /^-?\d+$/,
geZero: /^\d+(\.\d+)?$/,
idNo: /^\d{17}(\d|x)$/,
qqNo: /^[1-9]\d{4,10}$/,
weNo: /^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/,
carNo: /^[\u4e00-\u9fa5]{1}[A-Z]{1}[A-Z_0-9]{5}$/,
creditCode: /^[0-9A-Z]{18}$/,
}
const getValidatorsByPatterns = () => {
const validators = {}
for (const key of Object.keys(patterns)) {
const PascalCaseKey = key.slice(0, 1).toUpperCase() + key.slice(1)
validators[`is${PascalCaseKey}`] = (val) => patterns[key].test(val)
}
return validators
}
export default getValidatorsByPatterns()
dateHelper.js
import dayjs from 'dayjs';
import isBetween from 'dayjs/plugin/isBetween';
dayjs.extend(isBetween);
export const formatDateStr = (dateStr, format = 'YYYY-MM-DD') => {
return dayjs(dateStr).format(format);
}
export default class DateHelper {
constructor(dateString) {
this.date = dayjs(dateString);
}
format(formatString) {
return this.date.format(formatString);
}
add(num, unit = 'day', formatString = 'YYYY-MM-DD') {
return this.date.add(num, unit).format(formatString);
}
subtract(num, unit = 'day', formatString = 'YYYY-MM-DD') {
return this.date.subtract(num, unit).format(formatString);
}
getFirstDayByUnit(unit = 'month', formatString = 'YYYY-MM-DD') {
return this.date.startOf(unit).format(formatString);
}
getLastDayByUnit(unit = 'month', formatString = 'YYYY-MM-DD') {
return this.date.endOf(unit).format(formatString);
}
getDiff(otherDate, unit = 'day') {
return this.date.diff(otherDate, unit);
}
isAfter(otherDate) {
return this.date.isAfter(otherDate);
}
isBefore(otherDate) {
return this.date.isBefore(otherDate);
}
isBetween(a, b) {
return this.date.isBetween(a, b);
}
getDayOfWeek() {
const daysOfWeekChinese = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const dayOfWeek = this.date.day();
return daysOfWeekChinese[dayOfWeek];
}
}
desensitizerHelper.js
export default class DesensitizerHelper {
static desensitizeName(name, defaultVal = '--') {
if (!name) return defaultVal;
if (name.length === 1) {
return name;
}
return `${name[0]}${'*'.repeat(name.length - 1)}`;
}
static desensitizePhoneNumber(phoneNumber, defaultVal = '--') {
if (!phoneNumber) return defaultVal;
return phoneNumber.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
static desensitizeEmail(email) {
const [username, domain] = email.split('@');
const usernameLen = username.length;
const maskedUsername = usernameLen <= 2 ? '*'.repeat(usernameLen) : `${username[0]}*${username[usernameLen - 1]}`;
return `${maskedUsername}@${domain}`;
}
static desensitizeBankCardNumber(cardNumber) {
const visibleDigits = 4;
const maskedDigits = cardNumber.length - visibleDigits;
const maskedPart = '*'.repeat(maskedDigits);
const visiblePart = cardNumber.slice(maskedDigits);
return maskedPart + visiblePart;
}
static desensitizeIDNumber(idNumber) {
return idNumber.replace(/(\d{4})\d+(\d{4})/, '$1****$2');
}
static desensitizePassportNumber(passportNumber) {
return passportNumber.replace(/(.{2}).+(.{2})/, '$1****$2');
}
static desensitizeLandlinePhoneNumber(landline) {
return landline.replace(/(\d{3}-\d{4})(\d+)/, '$1****$2');
}
static desensitizeAddress(address) {
const visiblePart = address.substring(0, 6);
const maskedPart = '*'.repeat(address.length - visiblePart.length);
return visiblePart + maskedPart;
}
}
qsHelper
import qs from 'qs';
export function getUrlParamsByUrlStr(urlStr = location.href) {
const index = urlStr.indexOf('?');
if (index < 0) return {};
return qs.parse(urlStr.slice(index + 1));
}
export function getUrlValByKey(key, urlStr = location.href) {
const val = getUrlParamsByUrlStr(urlStr)[key];
return val ? decodeURI(val) : val;
}
utils
export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function downloadFileByBlob(blob, type, fileName) {
const url = window.URL.createObjectURL(new Blob([blob], { type }));
const aEle = document.createElement('a');
aEle.setAttribute('href', url);
aEle.setAttribute('download', fileName);
document.body.appendChild(aEle);
aEle.click();
document.body.removeChild(aEle);
window.URL.revokeObjectURL(url);
}
export function debounce(func, wait = 300, immediate = true) {
let timeout;
return function () {
const context = this;
const args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
};
}
export function chunkArray(array, size) {
const result = [];
for (let i = 0; i < array.length; i += size) {
result.push(array.slice(i, i + size));
}
return result;
}
export function getOs() {
const u = navigator.userAgent;
return {
trident: u.indexOf('Trident') > -1,
presto: u.indexOf('Presto') > -1,
webKit: u.indexOf('AppleWebKit') > -1,
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,
mobile: !!u.match(/AppleWebKit.*Mobile.*/),
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),
android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1,
iPhone: u.indexOf('iPhone') > -1,
iPad: u.indexOf('iPad') > -1,
webApp: u.indexOf('Safari') == -1,
weixin: u.indexOf('MicroMessenger') > -1,
qq: u.match(/\sQQ/i) == ' qq',
};
}
export function formatMoney(number) {
if (Number(number).toString() !== 'NaN') {
const numStr = Number(number).toLocaleString();
return numStr.includes('.') ? numStr.substring(0, numStr.indexOf('.') + 3) : numStr;
}
const num = number.toString().match(/^\d+(\.\d+)?/g);
const suffix = number.toString().replace(/^\d+(\.\d+)?/g, '') || '';
if (num[0]) {
const numStr = Number(num[0]).toLocaleString();
return (numStr.includes('.') ? numStr.substring(0, numStr.indexOf('.') + 3) : numStr) + suffix;
}
return number || '--';
}
export function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'deepClone');
}
const targetObj = source.constructor === Array ? [] : {};
Object.keys(source).forEach(keys => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys]);
} else {
targetObj[keys] = source[keys];
}
});
return targetObj;
}