/**
* @title query转json
* @param {string} key - json.key
* @param {string} url - /aaa?a=1&b=2&c=3 如果不传默认读取location.href
* @param {string[]} joinSymbol - 连接符[s1,s2,s3]
* s1:path与query的连接符,默认?
* s2:多个query的连接符,默认&
* s3:query的kv连接符,默认=
* @return {json}
* @example "a=1&b=2&c=3" ===> QueryToJson() ===> {a:1,b:2,c:3}
* @example QueryToJson("a") ===> 1
*/
export const QueryToJson = (key:string="",url:string="",joinSymbol=["?","&","="]) => {
const [s1,s2,s3] = joinSymbol;
if(!url) url = location.href;
const queryArr = url?.split(s1)[1]?.split(s2) || [];
if (!queryArr.length) return key ? '' : {};
const json = queryArr.reduce((acc, curr, index) => {
const [key,value] = curr.split(s3);
acc[key] = value;
return acc;
}, {})
if(!key) return json;
return json[key] ? json[key] : "";
}
/**
* @title json转query
* @param {object} json
* @param {Array<string>>} excludeKeys - 排除的字段
* @param {boolean} includeEmpty - 是否包含空字符串
* @return query
* @example JsonToQuery({a:1,b:2,c:''}) ===> a=1&b=2
* @example JsonToQuery({a:1,b:2,c:''},true) ===> a=1&b=2&c=
* @example JsonToQuery({a:1,b:2,c:''},true,["b"]) ===> a=1&c=
*/
export const JsonToQuery = (json:object,excludeKeys=[],includeEmpty=false) => {
const queryArr = [];
for(const key in json){
if(!(excludeKeys.includes(key))){
if(!includeEmpty) {
if(json[key]){
queryArr.push(`${key}=${json[key]}`)
}
}else{
queryArr.push(`${key}=${json[key]}`)
}
}
}
return queryArr.join("&");
}
/**
* 根据级别返回文字
* @param {number} level - 级别,从1开始
* @param {Array<string>>} textArr - 级别对应文字
* @return {string} "省"
* @example LevelToLabel(1,["省","市","区县","街道"]) => 省
* */
export const LevelToLabel = (level:number,textArr:Array<string>=["省","市","区县","街道"]):string => {
const label = textArr[level - 1];
return label ? label : "";
}
/**
* 数字转千分位
* @param {number} num - 数值
* @return {string|number}
* @example NumberToLocal(260000) => 260,000
* */
export const NumberToLocal = (num: number) => {
return num ? num.toLocaleString() : 0;
};
/**
* 补位函数,小于9前面统一补个0
* @param {string|number} count - 数值
* @return {string|number} - 小于9补零返回,否则原样返回
* */
export const RepairZero = (count:string|number) => {
return Number(count) > 9 ? count : '0' + count;
}
/**
* 数组转对象
* @param {Array<string>>} arr - 原数组
* @param {(prev,curr,index)=>[string,string]} keyValCb - 回调函数,接受三个参数,(上一项,当前项,当前下标) => [key字符串,value字符串]
* @return {JSON}
* @example ["a=1","b=2","c=3"] ===> {a:1,b:2,c:3}
* [1,2,3] ===> {tagId0:1,tagId1:2,tagId2:3}
* */
export const ArrayToJson = (arr:Array<string>,keyValCb:(prev,curr,index)=>[string,string]) => {
return arr.reduce((acc, curr, index) => {
const [key,value] = keyValCb(acc,curr,index);
acc[key] = value;
return acc;
}, {});
}
/**
* 将扁平化数组进行归纳整理
* @param {Array<any>>} data - 原数组
* @param {string} key - 根据指定key进行分类归纳
* @return {Array<any[]>} 返回二维数组
* @example FixFlatArr([{a:1,b:1},{a:1,b:2},{a:2,b:1}],"a") => [[{a:1,b:1},{a:1,b:2}],[{a:2,b:1}]]
* */
// 方法1
export const FixFlatArr = (data,key) => {
const result = [];
data.forEach(item => {
const value = item[key];
if(!(data.find(d => d.find(e => e[key] === value)))){
result.push(data.filter(e => e[key] === value));
}
})
return result;
}
// 方法2
export const FixFlatArr = (data = [], key = "id") => {
const arrFields = [...new Set(data.map(e => e[key]))];
return arrFields.map(v => data.filter(e => e[key] == v));
}
const arrs = ['adwafda','Addddw','swdA','adawf','sDWW','Addfawf','Afffff'];
/**
* param arr:Array 原数组
* param isRel:Boolean 是否区分大小写
*/
function filterArrByStartStrMax(arr=[],isRel=true){
// 先拿到数组中所有首字母,并全部转为小写
const firstStrArr = arr.map(str => {
return isRel ? str.charAt(0) : str.charAt(0).toLowerCase();
});
// 定义一个json存储每个首字母出现的次数
const obj = {};
for(let i = 0;i<firstStrArr.length;i++){
obj[firstStrArr[i]] = obj[firstStrArr[i]] ? obj[firstStrArr[i]] + 1 : 1;
};
// 获取出现次数最多的字母的次数
const maxCount = Math.max(...(Object.values(obj)));
// 定义变量存储出现次数最多的字母
const maxS = Object.entries(obj).find(e => e[1] === maxCount)[0];
// 过滤原数组
return arr.filter(item => {
if(!isRel) item = item.toLowerCase();
return item.startsWith(maxS);
})
}
filterArrByStartStrMax(arrs);// ['Addddw', 'Addfawf', 'Afffff']
/**
* 返回扁平化数组
* @param data:Array
* @param childField:string
*/
function flatArr(data,childField=""){
const result = [];
const deepFn = (arr) => {
arr.forEach(e => {
if(e[childField] && e[childField].length > 0 ){
deepFn(e[childField])
}else{
result.push(e)
}
})
}
deepFn(data);
return result;
}
/**
* @param {object} obj
* @returns {Array<object>}
* @example AssignArr({ a:[1,2,3],b:[4,5,6],c:[7,8,9] }) => [{a:1,b:4,c:7},{a:2,b:5,c:8},{a:3,b:6,c:9}]
*/
export const AssignArr = (obj) => {
const keys = Object.keys(obj);
const values = Object.values(obj);
return values[0].map((value,index) => {
return keys.reduce((acc,key) => {
acc[key] = obj[key][index];
return acc;
},{})
})
}
/**
* 对象数组排序,根据已知字段的顺序,对数组进行排序
* @param {any[]} dataSource - 原数组
* @param {string[]} order - 已知排序
* @param {string} field - 已知排序的字段名
* @returns {any[]}
* @example SortArr([{id:"aaa"},{id:"ccc"},{id:"bbb"}],["aaa","bbb","ccc"],"id") => [{id:"aaa"},{id:"bbb"},{id:"ccc"}]
*/
export const SortArr = (dataSource:any[] = [],order:string[] = [], field:string="id") => {
return dataSource.sort((a,b) => {
return order.indexOf(a[field]) - order.indexOf(b[field]);
})
}
/**
* 对象数组去重,根据指定字段去重对象数组
* @param {{[key:string]:any}[]} dataSource - 原数组
* @param {string} key- 字段名
* @returns {any[]} 如果指定字段存在多次,去重后只返回第一次出现的结果
* @example ReRepeat([{id:1,name:"张三"},{id:2,name:"李四"},{id:1,name:"王五"}],"id") => [{id:1,name:"张三"},{id:2,name:"李四"}]
*/
export const ReRepeat = (dataSource:{[key:string]:any}[],key:string) => {
const map = new Map();
return dataSource.filter(item => !map.has(item[key]) && map.set(item[key],item));
};
/**
* 生成树结构
* @param {object} data - 原数组
* @param {[string,string]} relation - 父子关系字段
* @param {boolean=false} moreSon - 允许多个不同父存在相同子
(是否相同子出现在不同父中,如果为true,则parentCode值应为逗号拼接的多个父dictCode)
* @return {object} tree
* */
export const MakeTree = (data: Array<any>, relation = ["dictCode","parentCode"],moreSon=false) => {
const [sonField, parentField] = relation;
// 删除 所有 children,以防止多次调用
data.forEach((item) => {
delete item.children;
});
// 将数据存储为 以 dictCode 为 KEY 的 map 索引数据列
const map = {};
data.forEach((item) => {
map[item[sonField]] = item;
});
const val = [];
const setVal = (parent,item) => {
// 如果找到索引,那么说明此项不在顶级当中,那么需要把此项添加到,他对应的父级中
if (parent) {
parent.children || (parent.children = []);
parent.children.push(item);
} else {
//如果没有在map中找到对应的索引dictCode,那么直接把 当前的item添加到 val结果集中,作为顶级
val.push(item);
}
}
data.forEach((item) => {
if(moreSon) {
// 以当前遍历项的parentCode,去map对象中找到索引的dictCode
for(const parentCode of item[parentField].split(",")){
const parent = map[parentCode ];
setVal(parent,item)
}
} else {
// 以当前遍历项的parentCode,去map对象中找到索引的dictCode
const parent = map[item[parentField]];
setVal(parent,item)
}
});
return val;
};
/**
* 获取树节点指定级别数据集合
* @param {any[]} treeData - 树形数据
* @param {number} level - 指定级别
* @param {string} childField - 子节点字段名
* @return {any[]}
* */
export const GetDatasByTreeLevel = (treeData=[],level=0,childField="children") => {
const result = [];
// 递归获取指定级别数据
// dataSource:需要递归处理的数据
// currentLevel:从哪个级别开始
const deepGetMenus = (dataSource,currentLevel) => {
if(currentLevel === level) {
result.push(...dataSource);
return;
}
dataSource.forEach(data => {
deepGetMenus(data[childField] || [],currentLevel + 1);
})
};
deepGetMenus(treeData,0);
return result;
}
/**
* 生成一组从start到end的数值数组
* @param {number} start - 开始数值
* @param {number} end - 结束数值
* @param {boolean=true} includeEnd - 是否包含结束数值
* @return {number[]}
* */
makeArrByNum(start,end,includeEnd=true) {
const result = [];
for(let i = start; i < end; i++){
result.push(i);
}
if(includeEnd) result.push(end);
return result;
}
/**
* @title 时间转换
* @description 传入时间如果如果在1天内,则返回n小时前,如果大于1天小于10天,则返回n天前,如果大于时间,则返回指定格式
* @param {string} date 时间格式
* @param {object} options 具体参数 { m:60,h:24,d:10,format:YYYY-MM-DD }
* justnow指的是分钟之内返回刚刚 刚刚
* minute指的是分钟之内返回分钟 56分钟前
* hour指的是小时之内返回小时 5小时前
* day天数之内返回天数 3天前
* month月份之内返回月份 2月前
* format代表大于d返回的格式 YYYY/MM/DD
* @return {string}
* */
export interface TransformDateOptions {
justnow:number;
minute:number;
hour:number;
day:number;
month:number;
format:string;
}
export const TransformDate:(date:string,options?:TransformDateOptions) => string = (date,options) => {
const defaultOptions = {
justnow: 60,
minute: 60,
hour: 24,
day: 30,
month: 12,
format: "YYYY/MM/DD"
}
options = Object.assign({},defaultOptions,options);
const specifiedDate = dayjs(date);
const diffInSecond = Math.abs(specifiedDate.diff(dayjs(),"second"));
const diffInMinute = Math.abs(specifiedDate.diff(dayjs(),"minute"));
const diffInHours = Math.abs(specifiedDate.diff(dayjs(),"hours"));
const diffInDay = Math.abs(specifiedDate.diff(dayjs(),"day"));
const diffInMonth = Math.abs(specifiedDate.diff(dayjs(),"month"));
if(diffInSecond < options.justnow) return `${diffInSecond} 秒前`;
if(diffInMinute <= options.minute) return `${diffInMinute} 分钟前`;
if(diffInHours <= options.hour) return `${diffInHours} 小时前`;
if(diffInDay <= options.day) return `${diffInDay} 天前`;
if(diffInMonth <= options.month) return `${diffInMonth} 个月前`;
return specifiedDate.format(options.format);
}
/**
* 文件下载
* @param {string} href - 文件地址
* @param {string} title - 文件名称
* */
export const DownloadFile = (href:string,title:string) => {
const xhr = new window.XMLHttpRequest();
xhr.open("GET",href,true);
xhr.responseType = "blob";
xhr.onload = () => {
const url = window.URL.createObjectURL(xhr.response);
const link = document.createElement("a");
link.href = url;
link.id = `download-${Date.now()}`;
link.setAttribute("download", title);
link.setAttribute("target", "blank");
document.body.appendChild(link);
link.click();
document.getElementById(link.id).remove();
}
xhr.send();
}
/**
* 跨域获取Get请求页面内容
* @param {string} url - 请求地址
* @param {Function} callback - 请求成功回调
* */
export const MakeCorsRequest = (url, callback) => {
let xmlHttp = new XMLHttpRequest();
// 设置跨域访问请求对象
if ("withCredentials" in xmlHttp) {
// "withCredentials"属性是XMLHTTPRequest2中独有的
xmlHttp.open("GET", url, true);
} else if (typeof window.XDomainRequest != "undefined") {
// 检测是否XDomainRequest可用
xmlHttp = new window.XDomainRequest();
xmlHttp.open("GET", url);
} else {
console.log("抱歉,不支持CORS");
return;
}
// 设置计时器,防止超时
let timedout = false;
const timer = setTimeout(function () {
timedout = true;
xmlHttp.abort();
}, 5000);
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState != 4) return;
if (timedout) return;
clearTimeout(timer);
if (xmlHttp.status === 200) {
callback(xmlHttp.responseText);
}
if (xmlHttp.status === 404) {
console.log("路径请求错误");
}
};
xmlHttp.onerror = function () {
console.log("抱歉,请求错误");
};
xmlHttp.send();
}
/**
* 递归创建目录
* @params {string} path - /a/b/c/1.png
* @example MakeDir("/a/b/c/1.png") ==> 会在当前目录生成 ./a/b/c
*/
export const MakeDir = (path:string) => {
// 将path以/分割转为数组,并且读取非文件地址
const dirArr = path.split("/").filter(dir => !!dir && !(/.+\..+$/g).test(dir));
// 递归创建目录方法
const deepFn = (dirs) => {
for(let i = 0; i <dirs.length; i ++) {
try {
fs.readdirSync(path.join("./",dirs[i]));
}catch (err) {
fs.mkdirSync(path.join("./",dirs[i]))
}
if(dirs[i + 1]){
dirs.splice(0,2,`${dirs[i]}/${dirs[i+1]}`);
deepFn(dirs)
}
}
}
}
export enum DETECTION {
// 流量
LIUL = 10,
// 甲烷浓度
JWND = 20,
// 管道压力
GDYL = 30,
// 液位
YEWE = 40
}
export const DETECTION_TYPE = {
[DETECTION.LIUL]:{ LABEL:"流量" },
[DETECTION.JWND]:{ LABEL:"甲烷浓度" },
[DETECTION.GDYL]:{ LABEL:"管道压力" },
[DETECTION.YEWE]:{ LABEL:"液位" }
}
/**
* 根据嵌套对象的key和value返回嵌套的对象
* @param {object} data - js对象
* @param {string} key - 嵌套对象的key
* @param {string} value - 嵌套对象的key对应的value
* @return {array} - [key,data]
* @example GET_DATA(DETECTION_TYPE,"LABEL","流量") => ["10",{ LABEL:"流量" }]
* */
export const GET_DATA = (data={},key:string,value:string) => {
const target = Object.entries(data).find(arr => arr[1][key] === value);
if(!target || !target.length) return [];
return target;
}
/**
* 返回指定嵌套对象的key集合
* @param {object} data - js对象
* @param {string} key - 需要的字段集合
* @param {Array<string>} excludes - 排除掉的字符串
* @return {Array<string>>}
* @example GET_DATA_KEYS(DETECTION_TYPE,"LABEL",["流量"]) => ["甲烷浓度","管道压力","液位"]
* */
export const GET_DATA_KEYS = (data={},key:string,excludes:Array<string>=[]) => {
return Object.values(data).map(e => e[key]).filter(value => !(excludes.includes(value)));
}
/**
* 获取图片
* @param {string} src - assets下级图片地址
* @return {string} - 构建新的图片地址
* @example "images/arrow.png" => "http://192.168.6.192:8080/src/assets/images/arrow.png"
* */
export const GetImage = (src:string) => {
return new URL(`../assets/${src}`,import.meta.url).href;
}
/**
* @param h:number 距离时,< 24
* @param m:number 距离分,< 60
* @param s:number 距离秒,< 60
*/
function getTimeLong(h,m,s){
setInterval(() => {
const D = new Date();
const now = `${D.getFullYear()}/${(D.getMonth() + 1) > 9 ? (D.getMonth() + 1) : '0' + (D.getMonth() + 1)}/${(D.getDate()) > 9 ? (D.getDate()) : '0' + (D.getDate())} ${(D.getHours()) > 9 ? (D.getHours() ) : '0' + (D.getHours())}:${(D.getMinutes()) > 9 ? (D.getMinutes() ) : '0' + (D.getMinutes())}:${(D.getSeconds()) > 9 ? (D.getSeconds() ) : '0' + (D.getSeconds())}`;
const finish = `${D.getFullYear()}/${(D.getMonth() + 1) > 9 ? (D.getMonth() + 1) : '0' + (D.getMonth() + 1)}/${(D.getDate()) > 9 ? (D.getDate()) : '0' + (D.getDate())} ${h}:${m}:${s}`;
const nowTime = new Date(now).getTime();
const finishTime = new Date(finish).getTime();
const ggTime = finishTime - nowTime;
const result = parseInt((ggTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))+'时'+
parseInt((ggTime % (1000 * 60 * 60)) / (1000 * 60)) + '分' +
(ggTime % (1000 * 60)) / 1000 + '秒';
console.log(decodeURIComponent('%E8%B7%9D%E7%A6%BB%E4%B8%8B%E7%8F%AD%E8%BF%98%E6%9C%89%EF%BC%9A')+result)
},1000)
}
/**
* 设置页面标题
* @param {string} title - 设置页面标题title
* */
export const SetTitle = (title="webxue") => {
document.title = title;
}
export const Fullscreen:{ open:Function,close:Function } = {
// 打开全屏
open:(element:HTMLElement = document.body):void => {
element.requestFullscreen().then();
},
// 关闭
close:():void => {
document.exitFullscreen().then();
}
}
// 监听屏幕状态
window.addEventListener("resize",function (){
// 开发环境由于需要打开f12,全屏状态取决于(document的高度等于屏幕高度) || (document的宽度等于屏幕宽度)
// 生产环境忽略打开f12,全屏状态取决于(document的高度等于屏幕高度) && (document的宽度等于屏幕宽度)
// isFullScreen = ref(false) 全局状态
if(import.meta.env.DEV) {
isFullScreen.value = document.body.clientHeight === window.screen.height || document.body.clientWidth === window.screen.width;
}else{
isFullScreen.value = document.body.clientHeight === window.screen.height && document.body.clientWidth === window.screen.width;
}
})
/**
* 选择文件
* @param {string} accept
* */
export const GetFile = (accept:string="") => {
const fileSelectDom = document.createElement("input");
fileSelectDom.type = "file";
fileSelectDom.accept = accept;
fileSelectDom.click();
return new Promise((resolve) => {
fileSelectDom.onchange = function () {
resolve(fileSelectDom.files[0]);
fileSelectDom.onchange = undefined;
}
})
}