import CommonConst from './CommonConst.js'

/**
 * 通用工具类</br>
 * 引用方式：<b>this.commonUtil.&lt;method_name&gt;</b></br>
 * 其中&lt;method_name&gt;包括：
 * 
 * @property {} <b>通用处理</b>
 * @property {method} fillUrlArgments(url,arg1,arg2,arg3,arg4) 将URL地址中的{}参数替换为实际值，最多4个参数<br/>
 *    参数： url = 待处理的URL地址<br/>
 *           &emsp;&emsp;&emsp;&ensp;arg1 = string 实际值参数1<br/>
 *           &emsp;&emsp;&emsp;&ensp;arg2 = string 实际值参数2<br/>
 *           &emsp;&emsp;&emsp;&ensp;arg3 = string 实际值参数3<br/>
 *           &emsp;&emsp;&emsp;&ensp;arg4 = string 实际值参数4<br/>
 *    返回： string = 处理后的url字符串<br/>
 * @property {method} map2List(map,minus1First,keyName,valueName) 将map转换为数组列表<br/>
 *    参数：  map = object 待转换的map对象<br/>
 *           &emsp;&emsp;&emsp;&ensp;minus1First = boolean 是否将key=-1的条目放在第1条，default=true<br/>
 *           &emsp;&emsp;&emsp;&ensp;keyName = string 转换后的key名称，default="key"<br/>
 *           &emsp;&emsp;&emsp;&ensp;valueName = string 转换后的value名称，default="value"<br/>
 * 	  返回： array = 转换生成的数组列表<br/>
 * @property {method} equals(obj1,obj2) 判断两个数据是否相等<br/>
 *    参数： obj1 = object/array/function/string/number 数据1<br/> 
 *           &emsp;&emsp;&emsp;&ensp;obj2 = object/array/function/string/number 数据2<br/>
 * 	  返回： boolean = 比对结果<br/>
 * @property {method} compareTwoObjectsAreEqual(obj1,obj2) 判断两个对象是否相等<br/>
 *    参数： obj1 = object 对象1<br/> 
 *           &emsp;&emsp;&emsp;&ensp;obj2 = object 对象2<br/>
 * 	  返回： boolean = 比对结果<br/>
 * @property {method} compareTwoArraysAreEqual(obj1,obj2) 判断两个数组是否相等<br/>
 *    参数： obj1 = array 数组1<br/> 
 *           &emsp;&emsp;&emsp;&ensp;obj2 = array 数组2<br/>
 * 	  返回： boolean = 比对结果<br/>
 * @property {method} sortObjectArrayByKey(array,key) 将对象数组按指定属性值排序<br/>
 *    参数： array = array 对象数组<br/>
 *           &emsp;&emsp;&emsp;&ensp;key = string 用于排序的属性名<br/> 
 *    返回： array = 排序后的对象数组<br/>
 * @property {method} calcAgeFromIdNo(idNo) 根据身份证号获取年龄<br/>
 *    参数： idNo = string 身份证号码<br/>
 * 	  返回： number = 年龄值<br/>
 * @property {method} file2Url(file) 根据file对象换算其url地址<br/>
 *    参数： file = object file对象<br/>
 *    返回： object = url地址对象<br/>
 * @property {method} decodeBase64FileData 解码Base64格式的文件数据<br/>
 *    参数： data = string base64数据<br/>
 *    返回： array = 两元素数组：<br/>
 *           &emsp;&emsp;&emsp;&emsp;&emsp;[0] = Uint8Array数组 解码的数据<br/>
 *           &emsp;&emsp;&emsp;&emsp;&emsp;[1] = string MIME类型<br/>
 * @property {method} copyTextToBoard 复制内容到粘贴板<br/>
 *    参数： vue = object vue实例对象<br/>
 *            &emsp;&emsp;&emsp;&ensp;content = string 内容<br/>
 *  
 * @property {} <b>字符串过滤</b>
 * @property {method} filterDigitals(value,reservedChars) 过滤字符串，只保留其中的数字和保留字符<br/>
 *    参数： value = string 待过滤的字符串<br/>
 *           &emsp;&emsp;&emsp;&ensp;reservedChars = array 需保留的字符数组，例如['-', '.']; default=[]<br/>
 *    返回： string = 过滤结果<br/>
 * @property {method} filterLetters(value,reservedChars) 过滤字符串，只保留其中的字母和保留字符<br/>
 *    参数： value = string 待过滤的字符串<br/>
 *           &emsp;&emsp;&emsp;&ensp;reservedChars = array 需保留的字符数组，例如['-', '.']; default=[]<br/>
 *    返回： string = 过滤结果<br/>
 * @property {method} filterDigitalsAndLetters(value,reservedChars) 过滤字符串，只保留其中的字母、数字和保留字符<br/>
 *    参数： value = string 待过滤的字符串<br/>
 *           &emsp;&emsp;&emsp;&ensp;reservedChars = array 需保留的字符数组，例如['-', '.']; default=[]<br/>
 *    返回： string = 过滤结果<br/>
 * @property {method} filterChinese(value,reservedChars) 过滤字符串，只保留其中的汉字和保留字符<br/>
 *    参数： value = string 待过滤的字符串<br/>
 *           &emsp;&emsp;&emsp;&ensp;reservedChars = array 需保留的字符数组，例如['-', '.']; default=[]<br/>
 *    返回： string = 过滤结果<br/>
 * @property {method} filterChineseAndLetters(value,reservedChars) 过滤字符串，只保留其中的汉字、字母和保留字符<br/>
 *    参数： value = string 待过滤的字符串<br/>
 *           &emsp;&emsp;&emsp;&ensp;reservedChars = array 需保留的字符数组，例如['-', '.']; default=[]<br/>
 *    返回： string = 过滤结果<br/>
 * 
 * @property {} <b>数据验证</b>
 * @property {method} validateNumber(str) 正则验证数值，只能为正整数或小数<br/>
 *    参数： number = numner/string 待验证字符串<br/>
 *    返回： boolean = 验证后的结果<br/> 
 * @property {method} validateMobile(mobile) 验证手机号格式是否正确<br/>
 *    参数： mobile = 待验证手机号<br/>
 *    返回： boolean = true-正确/false-错误<br/>
 * @property {method} validateIdCardNo(idCardNo) 验证身份证号格式是否正确<br/>
 *    参数： idCardNo = 待验证的身份证号<br/>
 *    返回： boolean = true-正确/false-错误<br/>
 * @property {method} validatePassword(password) 验证密码格式是否正确<br/>
 *    参数： password = 待验证的密码<br/>
 *    返回： string = 验证通过为 ''，验证失败为 '密码长度8-16位, 应包含数字、大小写字母及特殊字符'<br/>
 * 
 * @property {method} handleListFilter(value, listName, srcList, vue) select组件支持拼音查询挂载的自定义搜索方法<br/>
 *    参数： value = 用户输入的值<br/>
 * 	  参数： listName = 目标过滤数组名称<br/>
 *    参数： srcList = 待过滤数组<br/>
 *    参数： vue = vue实例对象<br/>
 * @property {method} handleListFilterVisibleChange(visible, listName, srcList, vue) select组件支持拼音查询下拉框出现/隐藏时挂载的触发事件<br/>
 *    参数： visible = 事件回调参数值 出现则为 true，隐藏则为 false<br/>
 * 	  参数： listName = 目标过滤数组名称<br/>
 *    参数： srcList = 待过滤数组<br/>
 *    参数： vue = vue实例对象<br/>
 * @property {method} getPercentValue(valueList, index, precision) 最大余额法解决图表百分比总和不是100% 的问题<br/>
 *    参数： valueList = 传入要计算数据的数组<br/>
 * 	  参数： index = 要计算数组中值的下标<br/>
 *    参数： precision = 百分比保留几位小数，默认保留2位小数<br/>
 * 
 * @Author: liusheng,wanqian
 * @Date: 2021-02-07 12:28:58
 * @LastEditTime: 2022-04-14 13:59:17
 */
let CommonUtil = {
    /**
     * @Description: 将URL地址中的{}参数替换为实际值，最多4个参数
     * @Author: liusheng
     * @Date: 2021-02-10 11:36:23
     * @param {string} url 待处理的URL地址
     * @param {*} arg1 参数1
     * @param {*} arg2 参数2
     * @param {*} arg3 参数3
     * @param {*} arg4 参数4
     */
    fillUrlArgments(url, arg1, arg2, arg3, arg4) {
        if (arg1 != undefined) {
            url = url.replace(/\{\w*\}/, arg1);
        }

        if (arg2 != undefined) {
            url = url.replace(/\{\w*\}/, arg2);
        }

        if (arg3 != undefined) {
            url = url.replace(/\{\w*\}/, arg3);
        }

        if (arg4 != undefined) {
            url = url.replace(/\{\w*\}/, arg4);
        }
        return url;
    },

    /**
     * @Description: 将map转换为数组列表
     * 调用示例： map2List(map, true, 'value', 'label')
     * @Author: wanqian, liusheng
     * @Date: 2022-2-15 14:08:23
     * @param {object} map 待处理的map对象
     * @param {boolean} minus1First 是否将key=-1的条目放在第1条
     * @param {string} keyName 转换后的key名称
     * @param {string} valueName 转换后的value名称
     * @return {string} 转换生成的数组列表
     */
    map2List(map, minus1First = true, keyName = 'key', valueName = 'value') {
        if (!map) return [];
        let list = [];
        for (let key in map) {
            let entry = {};
            entry[keyName] = key;
            entry[valueName] = map[key];
            if (key == -1 && minus1First) {
                list.unshift(entry);
            } else {
                list.push(entry);
            }
        }
        return list;
    },

    /**
     * @Description: 判断两个数据是否相等
     * @Author: wanqian, liusheng
     * @Date: 2021-02-10 11:36:23
     * @param {object/array/function/string/number} obj1 比对数据1
     * @param {object/array/function/string/number} obj2 比对数据2
     * @return true/false - 相同/不相同
     */
    equals(obj1, obj2) {
        const classNameA = toString.call(obj1);
        const classNameB = toString.call(obj2);
        // 如果数据类型不相等，则返回false
        if (classNameA !== classNameB) {
            return false;
        }

        switch (classNameA) {
            case '[object Object]':
                return this.compareTwoObjectsAreEqual(obj1, obj2);
            case '[object Array]':
                return this.compareTwoArraysAreEqual(obj1, obj2);
            case '[object Function]':
                return obj1.toString() === obj2.toString();
            default:
                return Object.is(obj1, obj2);
        }
    },

    /**
     * @Description: 判断两个对象是否相等
     * @Author: wanqian
     * @Date: 2022-11-10 16:02:23
     * @param {object} obj1 比对对象1
     * @param {object} obj2 比对对象2
     * @return true/false - 相同/不相同
     */
    compareTwoObjectsAreEqual(obj1, obj2) {
        for (let key in obj1) {
            if (!this.equals(obj1[key], obj2[key])) return false;
        }
        for (let key in obj2) {
            if (!this.equals(obj1[key], obj2[key])) return false;
        }
        return true;
    },

    /**
     * @Description: 判断两个数组是否相等
     * @Author: wanqian
     * @Date: 2022-11-10 16:02:23
     * @param {object} obj1 比对数组1
     * @param {object} obj2 比对数组2
     * @return true/false - 相同/不相同
     */
    compareTwoArraysAreEqual(obj1, obj2) {
        if (obj1.length !== obj2.length) {
            return false;
        }
        for (let i = 0; i < obj1.length; i++) {
            if (!this.equals(obj1[i], obj2[i])) return false;
        }
        return true;
    },

    /**
     * @Description: 将对象数组按指定属性值排序
     * @Author: wanqian，liusheng
     * @Date: 2022-08-11 09:50:23
     * @param {array} array 对象数组
     * @param {string} key 用于排序的属性名
     */
    sortObjectArrayByKey(array, key) {
        return array.sort((a, b) => {
            let x = a[key];
            let y = b[key];
            if (x < y) return -1;
            return (x > y) ? 1 : 0;
        })
    },

    /**
     * @Description: 根据身份证号获取年龄
     * @Author: wanqian
     * @Date: 2021-02-10 11:36:23
     * @param {string} idNo 传入身份证号码
     * @return {number} 年龄值
     */
    calcAgeFromIdNo(idNo) {
        if (idNo.length != 18) return 0;

        let now = new Date();
        let month = now.getMonth() + 1;
        let day = now.getDate();
        let age = now.getFullYear() - idNo.substring(6, 10) - 1;
        if (idNo.substring(10, 12) < month ||
            idNo.substring(10, 12) == month && idNo.substring(12, 14) <= day) age++;
        return age;
    },

    /**
     * @Description: 根据file对象换算其url地址
     * @Author: wanqian, liusheng
     * @Date: 2021-02-10 11:36:23
     * @param {object} file file对象
     * @return {object} url地址
     */
    file2Url(file) {
        let url = null;
        if (window.createObjectURL !== undefined) {
            url = window.createObjectURL(file.raw) // basic
        } else if (window.URL !== undefined) {
            url = window.URL.createObjectURL(file.raw) // IE,google,360,Safari,firefox
        } else if (window.webkitURL !== undefined) {
            url = window.webkitURL.createObjectURL(file.raw) // webkit
        }
        return url;
    },

    /**
     * @Description: 解码Base64格式的文件数据
     * @Author: wanqian, liusheng
     * @Date: 2022-08-09 11:36:23
     * 
     * @param {string} data base64数据
     * @return {array} 两元素数组：0-解码的数据，Uint8Array数组 1-MIME类型
     */
    decodeBase64FileData(data) {
        let arr = data.split(',');
        let bytes = (arr[0].indexOf('base64') >= 0) ?
            window.atob(arr[1]) : window.decodeURIComponent(arr[1]);
        let mime = arr[0].split(':')[1].split(';')[0];
        let dataArray = new Uint8Array(bytes.length);
        for (let i = 0; i < bytes.length; i++) {
            dataArray[i] = bytes.charCodeAt(i);
        }
        return [dataArray, mime];
    },

    /**
     * @Description: 复制内容到粘贴板
     * @param {object} vue vue实例对象
     * @param {string} content 内容
     * @Author: wanqian
     * @Date: 2022-09-22
     */
    copyTextToBoard(vue, content) {
        // 这里做兼容，可能会有浏览器禁用了非安全域的 navigator.clipboard 对象
        if (navigator.clipboard && window.isSecureContext) {
            navigator.clipboard.writeText(content).then(() => {
                vue.$message.success('复制成功');
            });
        } else {
            const input = document.createElement('input');
            input.value = content;
            document.body.appendChild(input);
            input.select();
            vue.$message.success('复制成功');
            // 执行复制命令并移除文本框
            document.execCommand('copy');
            input.remove();
        }
    },

    /**
     * @Description: 过滤字符串，只保留其中的数字和保留字符
     * @Author: liusheng
     * @Date: 2022-09-05 11:36:23
     * @param {string} value 待过滤的字符串
     * @param {array} reservedChars 需保留的字符数组，例如['-', '.']
     * @return {string} 过滤结果
     */
    filterDigitals(value, reservedChars = []) {
        return this.filterResult(value, reservedChars, (char, reservedChars2) => {
            return isNaN(char) && reservedChars2.indexOf(char) == -1;
        });
    },

    /**
     * @Description: 过滤字符串，只保留其中的字母和保留字符
     * @Author: liusheng
     * @Date: 2022-09-05 11:36:23
     * @param {string} value 待过滤的字符串
     * @param {array} reservedChars 需保留的字符数组，例如['-', '.']
     * @return {string} 过滤结果
     */
    filterLetters(value, reservedChars = []) {
        return this.filterResult(value, reservedChars, (char, reservedChars2) => {
            return !/^[a-zA-Z]+$/.test(char) && reservedChars2.indexOf(char) == -1;
        });
    },

    /**
     * @Description: 过滤字符串，只保留其中的字母、数字和保留字符
     * @Author: liusheng
     * @Date: 2022-09-05 11:36:23
     * @param {string} value 待过滤的字符串
     * @param {array} reservedChars 需保留的字符数组，例如['-', '.']
     * @return {string} 过滤结果
     */
    filterDigitalsAndLetters(value, reservedChars = []) {
        return this.filterResult(value, reservedChars, (char, reservedChars2) => {
            return isNaN(char) && !/^[a-zA-Z]+$/.test(char) && reservedChars2.indexOf(char) == -1;
        });
    },

    /**
     * @Description: 过滤字符串，只保留其中的汉字和保留字符
     * @Author: liusheng
     * @Date: 2022-09-05 11:36:23
     * @param {string} value 待过滤的字符串
     * @param {array} reservedChars 需保留的字符数组，例如['-', '.']
     * @return {string} 过滤结果
     */
    filterChinese(value, reservedChars = []) {
        return this.filterResult(value, reservedChars, (char, reservedChars2) => {
            return /[^\u4E00-\u9FA5]/.test(char) && reservedChars2.indexOf(char) == -1;
        });
    },

     /**
     * @Description: 过滤字符串，只保留其中的汉字、字母和保留字符
     * @Author: panqb
     * @Date: 2025-4-21 11:12:53
     * @param {string} value 待过滤的字符串
     * @param {array} reservedChars 需保留的字符数组，例如['-', '.']
     * @return {string} 过滤结果
     */
     filterChineseAndLetters(value, reservedChars = []) {
        return this.filterResult(value, reservedChars, (char, reservedChars2) => {
            return /[^\u4e00-\u9fa5a-zA-Z]/.test(char) && reservedChars2.indexOf(char) == -1;
        });
    },

    filterResult(value, reservedChars, conditionFunc) {
        let charArr = Array.from(value);
        let result = '';
        for (let char of charArr) {
            if (conditionFunc(char, reservedChars)) continue;
            result += char;
        }
        return result;
    },

    /**
     * @Description: 正则验证数值，只能为正整数或小数
     * @Author: wanqian
     * @Date: 2021-02-10 11:36:23
     * @param {string} number 传入验证的数字字符串
     */
    validateNumber(number) {
        let reg = /^[0-9]+.?[0-9]*$/;
        return reg.test(number);
    },

    /**
     * @Description: 验证手机号
     * @Author: wanqian, liusheng
     * @Date: 2022-08-11
     * @param {string} mobile 待验证手机号
     * @return {boolean} true/false
     */
    validateMobile(mobile) {
        let reg = CommonConst.REGEXP_MOBILE;
        return reg.test(mobile);
    },

    /**
     * @Description: 验证身份证号
     * @Author: liusheng
     * @Date: 2022-08-11
     * @param {string} idCardNo 待验证的身份证号
     * @return {boolean} true/false
     */
    validateIdCardNo(idCardNo) {
        let reg = CommonConst.REGEXP_IDCARDNO;
        return reg.test(idCardNo);
    },

    /**
     * @Description: 验证密码
     * @Author: fengal
     * @Date: 2024-07-04 11:00:44
     * @param {password} 待验证的密码
     * @return {string} 验证通过为''，否则为'密码长度8-16位, 应包含数字、大小写字母及特殊字符'
     */
    validatePassword(password) {
        let reg = CommonConst.REGEXP_PASSWORD;
        return reg.test(password) ? '' : '密码长度8-16位, 应包含数字、大小写字母及特殊字符';
    },

    /**
     * @Description: 初始化腾讯地图运行环境
     * @Author: gaohc
     * @Date: 2022-08-10 14:36:23
     * @param {*} key 腾讯地图key值
     */
    initQQMap(key) {
        return new Promise(function(resolve, reject) {
            window.init = function() {
                resolve(qq) //注意这里
            }
            let script = document.createElement("script");
            script.type = "text/javascript";
            script.src = CommonConst.QQMAP_INIT + key;
            script.onerror = reject;
            document.head.appendChild(script);
            let geoScript = document.createElement("script");
            geoScript.type = "text/javascript";
            geoScript.src = CommonConst.QQMAP_GEOlOCATION;
            document.head.appendChild(geoScript);
        })
    },

    /**
     * @Description: select组件支持拼音查询挂载的自定义方法
     * 用于过滤待查询数组返回包含用户输入字母的目标数组
     * @Author: gaohc
     * @Date: 2022-09-15 14:36:23
     * @param {*} value    用户输入值
     * @param {*} listName 目标过滤数组名称
     * @param {*} srcList  待过滤数组
     * @param {*} vue      vue实例对象
     */
    handleListFilter(value, listName, srcList, vue) {
        //统一转化为小写
        let lowerCaseValue = value.toLowerCase();
        vue[listName] = srcList.filter(item => (
            item.name.indexOf(lowerCaseValue) != -1 || item.py.indexOf(lowerCaseValue) != -1
        ));
    },

    /**
     * @Description: select组件下拉框出现/隐藏时触发的挂载事件
     * 用于给目标过滤后的数组恢复成初始数组(包含全部下拉选项的数组)
     * @Author: gaohc
     * @Date: 2022-09-15 14:36:23
     * @param {*} visible  事件回调参数值 出现则为 true，隐藏则为 false
     * @param {*} listName 目标过滤数组名称
     * @param {*} srcList  待过滤数组
     * @param {*} vue      vue实例对象
     */
    handleListFilterVisibleChange(visible, listName, srcList, vue) {
        if (visible) {
            vue[listName] = JSON.parse(JSON.stringify(srcList));
        }
    },

    /**
     * @Description: 最大余额法解决图表百分比总和不是100% 的问题
     * @Author: shh
     * @Date: 2022-07-26 11:36:23
     * @param {*} valueList 传入要计算数据的数组
     * @param {*} index 传入要计算数组中值的下标
     * @param {*} precision 传入百分比保留几位小数，默认保留2位小数
     */
    getPercentValue(valueList, index, precision) {
        if (!valueList[index]) {
            return 0;
        }
        let sum = valueList.reduce(function(item, val) {
            return item + (isNaN(val) ? 0 : val);
        }, 0);
        if (sum === 0) {
            return 0;
        }
        // 10的2次幂是100，用于计算精度。
        let digits = Math.pow(10, precision);
        // 扩大比例100，
        let votesPerQuota = valueList.map(function(val) {
            return ((isNaN(val) ? 0 : val) / sum) * digits * 100;
        });
        // 总数，扩大比例意味的总数要扩大
        let targetSeats = digits * 100;
        // 再向下取值，组成数组
        let seats = votesPerQuota.map(function(votes) {
            return Math.floor(votes);
        });
        // 再新计算合计，用于判断与总数量是否相同，相同则占比会100%
        let currentSum = seats.reduce(function(item, val) {
            return item + val;
        }, 0);
        // 余数部分的数组：原先数组减去向下取值的数组，得到余数部分的数组
        let remainder = votesPerQuota.map(function(votes, indexs) {
            return votes - seats[indexs];
        });
        // 给最大最大的余额加1，凑个占比100%；
        while (currentSum < targetSeats) {
            //  找到下一个最大的余额，给其加1
            let max = Number.NEGATIVE_INFINITY;
            let maxId = null;
            for (let i = 0, len = remainder.length; i < len; ++i) {
                if (remainder[i] > max) {
                    max = remainder[i];
                    maxId = i;
                }
            }
            // 对最大项余额加1
            ++seats[maxId];
            // 已经增加最大余数加1，则下次判断就可以不需要再判断这个余额数。
            remainder[maxId] = 0;
            // 总的也要加1，为了判断是否总数是否相同，跳出循环。
            ++currentSum;
        }
        // 这时候的seats就会总数占比会100%
        return seats[index] / digits;
    },
}

export default CommonUtil