import { isArray, isNullOrUnDef, isNumber, isString, isDate, isObject } from "@/utils/is"; import { FieldNamesProps } from "@/components/LjVxeTable/interface"; import XEUtils from "xe-utils"; import { cloneDeep, pick, get } from "lodash-es"; import dayjs from "dayjs"; import { useAuthButtons } from "@/hooks/useAuthButtons"; import { useUserStore } from "@/stores/modules/user"; import { GetFormulaCompute } from "@/api/modules/common"; import { ElMessage } from "element-plus"; import type { Menu } from "@/typings/global"; /** * @description 获取localStorage * @param {String} key Storage名称 * @returns {String} */ export function localGet(key: string) { const value = window.localStorage.getItem(key); try { return JSON.parse(window.localStorage.getItem(key) as string); } catch (error) { return value; } } /** * @description 存储localStorage * @param {String} key Storage名称 * @param {*} value Storage值 * @returns {void} */ export function localSet(key: string, value: any) { window.localStorage.setItem(key, JSON.stringify(value)); } /** * @description 清除localStorage * @param {String} key Storage名称 * @returns {void} */ export function localRemove(key: string) { window.localStorage.removeItem(key); } /** * @description 清除所有localStorage * @returns {void} */ export function localClear() { window.localStorage.clear(); } /** * @description 判断数据类型 * @param {*} val 需要判断类型的数据 * @returns {String} */ export function isType(val: any) { if (val === null) return "null"; if (typeof val !== "object") return typeof val; else return Object.prototype.toString.call(val).slice(8, -1).toLocaleLowerCase(); } /** * @description 生成唯一 uuid * @returns {String} */ export function generateUUID() { let uuid = ""; for (let i = 0; i < 32; i++) { let random = (Math.random() * 16) | 0; if (i === 8 || i === 12 || i === 16 || i === 20) uuid += "-"; uuid += (i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16); } return uuid; } /** * 判断两个对象是否相同 * @param {Object} a 要比较的对象一 * @param {Object} b 要比较的对象二 * @returns {Boolean} 相同返回 true,反之 false */ export function isObjectValueEqual(a: { [key: string]: any }, b: { [key: string]: any }) { if (!a || !b) return false; let aProps = Object.getOwnPropertyNames(a); let bProps = Object.getOwnPropertyNames(b); if (aProps.length != bProps.length) return false; for (let i = 0; i < aProps.length; i++) { let propName = aProps[i]; let propA = a[propName]; let propB = b[propName]; if (!b.hasOwnProperty(propName)) return false; if (propA instanceof Object) { if (!isObjectValueEqual(propA, propB)) return false; } else if (propA !== propB) { return false; } } return true; } /** * @description 生成随机数 * @param {Number} min 最小值 * @param {Number} max 最大值 * @returns {Number} */ export function randomNum(min: number, max: number): number { let num = Math.floor(Math.random() * (min - max) + max); return num; } /** * @description 获取当前时间对应的提示语 * @returns {String} */ export function getTimeState(t: any) { // @ts-ignore let timeNow = new Date(); let hours = timeNow.getHours(); if (hours >= 6 && hours <= 10) return t("sys.greetings.morning") + ` ⛅`; if (hours >= 10 && hours <= 13) return t("sys.greetings.noon") + ` 🌞`; if (hours >= 13 && hours <= 18) return t("sys.greetings.afternoon") + ` 🌞`; if (hours >= 18 && hours <= 24) return t("sys.greetings.evening") + ` 🌛`; if (hours >= 0 && hours <= 6) return t("sys.greetings.deadOfNight") + ` 🌑`; } /** * @description 获取浏览器默认语言 * @returns {String} */ export function getBrowserLang() { let browserLang = navigator.language ? navigator.language : navigator.browserLanguage; console.log("getBrowserLang browserLang :>> ", browserLang); let defaultBrowserLang = ""; if (["cn", "zh", "zh-cn"].includes(browserLang.toLowerCase())) { defaultBrowserLang = "zh-cn"; } else { defaultBrowserLang = "en"; } return defaultBrowserLang; } /** * @description 使用递归扁平化菜单,方便添加动态路由 * @param {Array} menuList 菜单列表 * @returns {Array} */ export function getFlatMenuList(menuList: Menu.MenuOptions[]): Menu.MenuOptions[] { let newMenuList: Menu.MenuOptions[] = JSON.parse(JSON.stringify(menuList)); return newMenuList.flatMap(item => [item, ...(item.children ? getFlatMenuList(item.children) : [])]); } /** * @description 使用递归过滤出需要渲染在左侧菜单的列表 (需剔除 isHide == true 的菜单) * @param {Array} menuList 菜单列表 * @returns {Array} * */ export function getShowMenuList(menuList: Menu.MenuOptions[]) { const { CheckPower } = useAuthButtons(undefined); let newMenuList: Menu.MenuOptions[] = cloneDeep(menuList); return newMenuList.filter(item => { if (item.meta.funid && !CheckPower(item.meta.funid)) { return false; } // 父级节点隐藏,跳过,不需要再递归子级节点 !item.meta?.isHide && item.children?.length && (item.children = getShowMenuList(item.children)); // 过滤隐藏节点 return !item.meta?.isHide; }); } /** * @description 使用递归找出所有面包屑存储到 pinia/vuex 中 * @param {Array} menuList 菜单列表 * @param {Array} parent 父级菜单 * @param {Object} result 处理后的结果 * @returns {Object} */ export const getAllBreadcrumbList = (menuList: Menu.MenuOptions[], parent = [], result: { [key: string]: any } = {}) => { for (const item of menuList) { result[item.path] = [...parent, item]; if (item.children) getAllBreadcrumbList(item.children, result[item.path], result); } return result; }; /** * @description 使用递归过滤出需要渲染在首页常用功能树 (需剔除 isHide == true 的菜单) * @param {Array} menuList 菜单列表 * @returns {Array} * */ export function getQuickEnterFuncTree(menuList: Menu.MenuOptions[]): any[] { class TreeNode { key: string; label: string; children: TreeNode[] | null; icon?: string; path?: string; disabled: boolean; constructor(key: string, label: string, hasChild?: boolean) { this.key = key; this.label = label; this.children = hasChild ? [] : null; } public get getIcon(): string | undefined { return this.icon; } public set setIcon(v: string) { this.icon = v; } public get getPath(): string | undefined { return this.path; } public set setPath(v: string) { this.path = v; } } const { userInfo } = useUserStore(); const recursiveFunTree = (children: Menu.MenuOptions[]): TreeNode[] => { let treeArray: TreeNode[] = []; for (const menu of children) { if (menu.meta?.isHide) continue; let treeNode = new TreeNode(menu.name, menu.meta?.title, isNullOrUnDef(menu.children)); if (isNullOrUnDef(menu.component)) treeNode.path = menu.path; if (isNullOrUnDef(menu.meta?.icon)) treeNode.icon = menu.meta?.icon; if (!isNullOrUnDef(menu.meta?.funid)) treeNode.disabled = userInfo?.rsltFunids && !userInfo?.rsltFunids.includes(menu.meta?.funid); if (isNullOrUnDef(menu.children)) continue; treeArray.push(treeNode); // 递归子集 treeNode.children = recursiveFunTree(menu.children); } return treeArray; }; console.log("getQuickEnterFuncTree menuList :>> ", menuList); let functionTree = recursiveFunTree(menuList); return functionTree; } /** * @description 使用递归过滤出需要渲染在首页常用功能树路由 (需剔除 isHide == true 的菜单) * @param {Array} menuList 菜单列表 * @returns {Array} * */ export function getQuickEnterFuncTreeRouter(menuList: Menu.MenuOptions[]): Map { console.log("getQuickEnterFuncTreeRouter menuList :>> ", menuList); class TreeNode { key: string; name: string; icon?: string; path: string; funid: number | string; constructor(key: string, label: string, path: string, icon?: string, funid?: number) { this.key = key; this.name = label; this.path = path; this.icon = icon || ""; this.funid = funid ?? 0; } } const recursiveFunTree = (children: Menu.MenuOptions[]): Map => { let routerMap: Map = new Map(); for (const menu of children) { // 跳过已隐藏的菜单 | 跳过不存在component的 if (!(menu.meta?.isHide || isNullOrUnDef(menu.component))) { // let treeNode = new TreeNode(menu.name, menu.meta?.title, menu.path, menu.meta?.icon, menu.meta?.funid); routerMap.set(menu.name, treeNode); } if (isNullOrUnDef(menu.children)) continue; // 递归子集 routerMap = new Map([...routerMap, ...recursiveFunTree(menu.children)]); } return routerMap; }; let routerMap = recursiveFunTree(menuList); return routerMap; } /** * @description 使用递归处理路由菜单 path,生成一维数组 (第一版本地路由鉴权会用到,该函数暂未使用) * @param {Array} menuList 所有菜单列表 * @param {Array} menuPathArr 菜单地址的一维数组 ['**','**'] * @returns {Array} */ export function getMenuListPath(menuList: Menu.MenuOptions[], menuPathArr: string[] = []): string[] { for (const item of menuList) { if (typeof item === "object" && item.path) menuPathArr.push(item.path); if (item.children?.length) getMenuListPath(item.children, menuPathArr); } return menuPathArr; } /** * @description 递归查询当前 path 所对应的菜单对象 (该函数暂未使用) * @param {Array} menuList 菜单列表 * @param {String} path 当前访问地址 * @returns {Object | null} */ export function findMenuByPath(menuList: Menu.MenuOptions[], path: string): Menu.MenuOptions | null { for (const item of menuList) { if (item.path === path) return item; if (item.children) { const res = findMenuByPath(item.children, path); if (res) return res; } } return null; } /** * @description 使用递归过滤需要缓存的菜单 name (该函数暂未使用) * @param {Array} menuList 所有菜单列表 * @param {Array} keepAliveNameArr 缓存的菜单 name ['**','**'] * @returns {Array} * */ export function getKeepAliveRouterName(menuList: Menu.MenuOptions[], keepAliveNameArr: string[] = []) { menuList.forEach(item => { item.meta.isKeepAlive && item.name && keepAliveNameArr.push(item.name); item.children?.length && getKeepAliveRouterName(item.children, keepAliveNameArr); }); return keepAliveNameArr; } /** * @description 格式化表格单元格默认值 (el-table-column) * @param {Number} row 行 * @param {Number} col 列 * @param {*} callValue 当前单元格值 * @returns {String} * */ export function formatTableColumn(row: number, col: number, callValue: any) { // 如果当前值为数组,使用 / 拼接(根据需求自定义) if (isArray(callValue)) return callValue.length ? callValue.join(" / ") : ""; return callValue ?? ""; } /** * @description 处理值无数据情况 * @param {*} callValue 需要处理的值 * @returns {String} * */ export function formatValue(callValue: any) { // 如果当前值为数组,使用 / 拼接(根据需求自定义) if (isArray(callValue)) return callValue.length ? callValue.join(" / ") : ""; return callValue ?? ""; } /** * @description 处理 prop 为多级嵌套的情况,返回的数据 (列如: prop: user.name) * @param {Object} row 当前行数据 * @param {String} prop 当前 prop * @returns {*} * */ export function handleRowAccordingToProp(row: { [key: string]: any }, prop: string, ...args: any) { if (args[0]) { return row[prop] ? formatFn[args[0]]({ val: row[prop], format: args[0] }) : ""; } if (!prop.includes(".")) return row[prop] ?? ""; prop.split(".").forEach(item => (row = row[item] ?? "")); return row; } /** * @description 处理 prop,当 prop 为多级嵌套时 ==> 返回最后一级 prop * @param {String} prop 当前 prop * @returns {String} * */ export function handleProp(prop: string) { const propArr = prop.split("."); if (propArr.length == 1) return prop; return propArr[propArr.length - 1]; } /** * @description 根据枚举列表查询当需要的数据(如果指定了 label 和 value 的 key值,会自动识别格式化) * @param {String} callValue 当前单元格值 * @param {Array} enumData 字典列表 * @param {Array} fieldNames label && value && children 的 key 值 * @param {String} type 过滤类型(目前只有 tag) * @returns {String} * */ export function filterEnum(callValue: any, enumData?: any, fieldNames?: FieldNamesProps, type?: "tag") { const value = fieldNames?.value ?? "value"; const label = fieldNames?.label ?? "label"; const children = fieldNames?.children ?? "children"; let filterData: { [key: string]: any } = {}; // 判断 enumData 是否为数组 if (Array.isArray(enumData)) filterData = findItemNested(enumData, callValue, value, children); // 判断是否输出的结果为 tag 类型 if (type == "tag") { return filterData?.tagType ? filterData.tagType : ""; } else { return filterData ? filterData[label] : ""; } } /** * @description 递归查找 callValue 对应的 enum 值 * */ export function findItemNested(enumData: any, callValue: any, value: string, children: string) { return enumData.reduce((accumulator: any, current: any) => { if (accumulator) return accumulator; if (current[value] === callValue) return current; if (current[children]) return findItemNested(current[children], callValue, value, children); }, null); } // 日期格式化 function formatDate({ val, format }: any) { return XEUtils.toDateString(val, format || "yyyy-MM-dd HH:mm:ss"); } // 四舍五入金额,每隔3位逗号分隔,默认0位小数 export function formatIntNumber({ val }: any, digits = 0) { if (isNaN(Number(val))) return val; return XEUtils.commafy(XEUtils.toNumber(val), { digits }); } // 四舍五入金额,每隔3位逗号分隔,默认2位数 export function formatAmount3({ val }: any, digits = 2) { if (isNaN(Number(val))) return val; return XEUtils.commafy(XEUtils.toNumber(val), { digits }); } // 向下舍入,默认两位数 export function formatCutNumber({ val }: any, digits = 2) { if (isNaN(Number(val))) return val; if (Number(val) == 0) return 0; return XEUtils.toFixed(XEUtils.floor(val, digits), digits); } // 向下舍入,默认两位数 function formatCutNumber3({ val }: any, digits = 3) { if (isNaN(Number(val))) return val; return XEUtils.toFixed(XEUtils.floor(val, digits), digits); } // 四舍五入,默认两位数 export function formatFixedNumber({ val }: any, digits = 2) { if (isNaN(Number(val))) return val; return XEUtils.toFixed(XEUtils.round(val, digits), digits); } // 百分比:四舍五入金额,每隔3位逗号分隔,默认2位 function formatPercent({ val }: any, digits = 2) { if (isNaN(Number(val))) return val; return XEUtils.commafy(XEUtils.toNumber(val) * 100, { digits }) + "%"; } /** * @description 格式化调用函数对照 */ const formatFn: any = { "yyyy-MM-dd": formatDate, "yyyy-MM-dd HH:mm:ss": formatDate, "yyyy-MM-dd HH:mm": formatDate, "MM/dd/yyyy": formatDate, "MM/dd": formatDate, "dd/MM/yyyy": formatDate, "yyyy/MM/dd": formatDate, intNumber: formatIntNumber, cutNumber: formatCutNumber, amountNumber: formatAmount3, fixedNumber: formatFixedNumber, cutNumber3: formatCutNumber3, percent: formatPercent }; /** * 一维,精简对象储存属性;支持数组型默认数据(关键字:field) * 逻辑:精简出与默认数据不同的属性,保留layoutAttr包含的字段 * @param {Object} column 列数据 * @param {String[]} layoutAttr 需要保留的数据名称 * @param {Object | Array} layoutDefine 数据默认值 {...} Or [{field: xxx, ...}, ...] * @returns {Array} 返回精简后的对象 */ export function streamlineFunc(column: any, layoutAttr: string[], layoutDefine: any) { let streamlinedColumns = []; if (Array.isArray(layoutDefine)) { for (let item of column) { const matchingDefinition = layoutDefine.find(itm => itm.field === item.field); const relevantAttrs = layoutAttr.filter(attr => { const itemValue = get(item, attr); const defineValue = get(matchingDefinition, attr); // 排除某些属性,并检查值是否不同 return !["field", "table", "datatype"].includes(attr) && defineValue !== itemValue; }); // 将 "field" 包含在过滤后的属性中 const attrsToPick = [...relevantAttrs, "field"]; streamlinedColumns.push(pick(item, attrsToPick)); } } else { for (let item of column) { const relevantAttrs = layoutAttr.filter(attr => { // 检查值是否与布局定义不同 return item[attr] !== layoutDefine[attr]; }); streamlinedColumns.push(pick(item, relevantAttrs)); } } return streamlinedColumns; } /** * 一维,精简对象储存属性 * @param {Object} column 列数据 * @param {String[]} layoutAttr 需要保留的数据名称 * @param {Object} layoutDefine 数据默认值 * @param {String} attr 列数据的二级属性 * @returns {Object} 返回精简后的对象 */ export function streamlineAttrFunc(column: any, layoutAttr: string[], layoutDefine: any, attr: string) { let _map = new Map(); for (let item of column) { let saveProp = cloneDeep(layoutAttr); if (item.basicinfo) { for (let key in layoutDefine) { if (item[attr].hasOwnProperty(key) && layoutDefine[key] === item[attr][key]) { saveProp.splice(saveProp.indexOf(key), 1); } } _map.set(item.field, pick(item[attr], saveProp)); } } return _map; } /** * 提取目标数据差异属性 * @param {Object} oriLayout 原始对象,带默认值 * @param {Object} targetLayout 目标对象,需要提取差异的数据 * @param {String[]} attrs 需要保留字符串名称,规则:仅需提供最底层属性名称 * @param {String} arrayAttr 数组数据,关键字段,用于排序 * @returns {Object} */ export function getDifference(oriLayout: any, targetLayout: any, attrs: string[] = [], arrayAttr = "id") { const diff: any = {}; // 检查oriLayout中的属性 for (const key in oriLayout) { if (oriLayout.hasOwnProperty(key)) { // 如果targetLayout中没有这个属性,则将它添加到差异中 if (!targetLayout.hasOwnProperty(key)) { diff[key] = oriLayout[key]; } else { // 如果targetLayout中也有这个属性,则比较它们的值 if (Array.isArray(oriLayout[key])) { // 比较数组 // 调整顺序,根据目标对象,调整原始对象数组顺序 let _oriLayout = oriLayout[key]; if (arrayAttr) { _oriLayout = oriLayout[key].slice().sort((a: any, b: any) => { let indexA = targetLayout[key].findIndex((item: any) => item[arrayAttr] === a[arrayAttr]); let indexB = targetLayout[key].findIndex((item: any) => item[arrayAttr] === b[arrayAttr]); return indexA - indexB; }); } let arrAttr = []; for (const idx in _oriLayout) { console.log("oriLayout[key][idx] :>> ", _oriLayout[idx]); if (targetLayout[key][idx]) { const nestedDiff = getDifference(_oriLayout[idx], targetLayout[key][idx], attrs, arrayAttr); console.log(nestedDiff); if (Object.keys(nestedDiff).length > 0) { arrAttr.push(nestedDiff); } } } arrAttr.length && (diff[key] = arrAttr); } else if (typeof oriLayout[key] === "object" && typeof targetLayout[key] === "object") { // 递归比较对象 console.log("key :>> ", key, oriLayout[key], targetLayout[key]); const nestedDiff = getDifference(oriLayout[key], targetLayout[key], attrs, arrayAttr); console.log("nestedDiff :>> ", key, nestedDiff); if (Object.keys(nestedDiff).length > 0) { console.log("nestedDiff :>> ", nestedDiff); diff[key] = nestedDiff; } } else if ((oriLayout[key] !== targetLayout[key] || key == arrayAttr) && attrs.includes(key)) { console.log("key :>> ", key); diff[key] = targetLayout[key]; } } } } // 补充:检查targetLayout中的属性 for (const key in targetLayout) { if (targetLayout.hasOwnProperty(key)) { // 如果oriLayout中没有这个属性,则将它添加到差异中 if ( !oriLayout.hasOwnProperty(key) || (oriLayout.hasOwnProperty(key) && isObject(oriLayout[key]) && Object.keys(oriLayout[key]).length == 0) ) { diff[key] = targetLayout[key]; } } } return diff; } /** * 读取数据到原始对象 * @param {Object} oriLayout 原始对象,带默认值 * @param {Object} targetLayout 差异的数据 * @param {String} arrayAttr 数组数据,关键字段,用于排序 * @returns {Object} */ export function setDifference(oriLayout: any, loadLayout: any, arrayAttr: string = "id") { let _oriLayout = cloneDeep(oriLayout); for (const key in loadLayout) { if (loadLayout.hasOwnProperty(key)) { if (Array.isArray(loadLayout[key])) { console.log("setDifference _oriLayout :>> ", _oriLayout); let _sortedObj1 = _oriLayout[key].slice().sort((a: any, b: any) => { let indexA = loadLayout[key].findIndex((item: any) => item[arrayAttr] === a[arrayAttr]); let indexB = loadLayout[key].findIndex((item: any) => item[arrayAttr] === b[arrayAttr]); if (indexA == -1 || indexB == -1) { return 0; } else { return indexA - indexB; } }); console.log("setDifference _sortedObj1 :>> ", _sortedObj1); console.log("setDifference loadLayout[key] :>> ", loadLayout[key]); let arr = []; for (const idx in _sortedObj1) { // console.log("idx :>> ", idx); // console.log("loadLayout[key][idx] :>> ", loadLayout[key][idx]); // console.log("_sortedObj1[idx] :>> ", _sortedObj1[idx]); // if (loadLayout[key][idx] && _sortedObj1[idx] && loadLayout[key][idx][arrayAttr] == _sortedObj1[idx][arrayAttr]) { // arr.push(setDifference(_sortedObj1[idx], loadLayout[key][idx], arrayAttr)); // } let itemIdx = loadLayout[key].findIndex((item: any) => item[arrayAttr] === _sortedObj1[idx][arrayAttr]); console.log(itemIdx); let _loadLayout = itemIdx > -1 ? loadLayout[key][itemIdx] : {}; arr.push(setDifference(_sortedObj1[idx], _loadLayout, arrayAttr)); } console.log("arr :>> ", arr); arr.length && (_oriLayout[key] = arr); } else if (typeof loadLayout[key] === "object") { if (!_oriLayout.hasOwnProperty(key)) { _oriLayout[key] = {}; } _oriLayout[key] = setDifference(_oriLayout[key], loadLayout[key], arrayAttr); } else { _oriLayout[key] = loadLayout[key]; } } } return _oriLayout; } /** * 金额缩略显示 * @param s 数值 * @param n 小数保留位数 * @returns {string} */ export function numberFormat(s: number, n: number) { n = n !== undefined && n >= 0 && n <= 20 ? n : 2; let sum = Math.abs(Number(s)); let sumStr = parseFloat((sum + "").replace(/[^\d\.-]/g, "")).toFixed(n) + ""; let r = n ? "." + sumStr.split(".")[1] : ""; let l = sumStr.split(".")[0].split("").reverse(); let t = ""; for (let i = 0; i < l.length; i++) { t += l[i] + ((i + 1) % 3 == 0 && i + 1 != l.length ? "," : ""); } if (s < 0) return "-" + t.split("").reverse().join("") + r; return t.split("").reverse().join("") + r; } export const noop = () => {}; /** * 获取目标属性,转换字符串对象 * @param obj 目标值 * @param str 目标字符串属性 */ export function convertStrToObj(obj: any, str?: string): any { console.log("convertStrToObj obj :>> ", obj); console.log("convertStrToObj str :>> ", str); if (!obj || JSON.stringify(obj) == "{}" || !str) return undefined; const keys = str.split("."); for (let i = 0; i < keys.length; i++) { const key = keys[i]; obj[key] = obj[key] || undefined; obj = obj[key]; } return obj; } /** * 时间格式转义, detaTime to Diff * @param data {value:Object} * @returns {Object} */ export const getDiffToDate = (data: any) => { const today = dayjs(dayjs().format("YYYY-MM-DD")); for (const key in data) { if (key.indexOf("date") > -1) { if (isNumber(data[key])) { // number => string data[key] = today.add(data[key], "day").format("YYYY-MM-DD HH:mm:ss"); } else if (isArray(data[key])) { // [number, number] => [string, string] data[key] = data[key].map((item: any) => { if (isNumber(item)) { return today.add(item, "day").format("YYYY-MM-DD HH:mm:ss"); } else { return item; } }); } } } console.log("getDiffToDate data :>> ", data); return data; }; /** * 时间格式转义, Diff to DetaTime * @param data {value:Object} * @returns */ export const setDateToDiff = (data: any) => { const today = dayjs(dayjs().format("YYYY-MM-DD")); for (const key in data) { if (key.indexOf("date") > -1) { if (isString(data[key]) || isDate(data[key])) { // string => number let newDate = dayjs(dayjs(data[key]).format("YYYY-MM-DD")); data[key] = newDate.diff(today, "day"); } else if (isArray(data[key])) { // [string, string] => [number, number] data[key] = data[key].map((item: any) => { let newDate = dayjs(dayjs(item).format("YYYY-MM-DD")); return newDate.diff(today, "day"); }); } } } console.log("_habit setDateToDiff data :>> ", data); return data; }; /** * 捕捉报错提示 * @param err 报错含有,关键字:会话、已与服务器失联、找不到网络路径 * @returns {Boolean} */ export const ifErrorToLogin = (err: any) => { return err.indexOf("会话") >= 0 || err.indexOf("已与服务器失联") >= 0 || err.indexOf("找不到网络路径") >= 0; }; /** * 遍历树形结构的数据 * @param tree 遍历的树形结构数据 * @param callback 回调函数,用于处理遍历到的每个节点 */ export const traverseNode = (root: any, action: (node: any) => void) => { const queue = [root]; while (queue.length > 0) { const node = queue.shift(); action(node); if (node.children && node.children.length > 0) { for (const child of node.children) { queue.push(child); } } } }; /*用正则表达式实现html转码*/ export const htmlEncodeByRegExp = (str: string) => { let s = ""; if (str.length == 0) return ""; s = str.replace(/&/g, "&"); s = s.replace(//g, ">"); s = s.replace(/ /g, " "); s = s.replace(/\'/g, "'"); s = s.replace(/\"/g, """); return s; }; /*用正则表达式实现html解码*/ export const htmlDecodeByRegExp = (str: string) => { let s = ""; if (str.length == 0) return ""; s = str.replace(/&/g, "&"); s = s.replace(/</g, "<"); s = s.replace(/>/g, ">"); s = s.replace(/ /g, " "); s = s.replace(/'/g, "'"); s = s.replace(/"/g, '"'); return s; }; /** * 小数点加、减、乘、除 * @param {Number} arg1 数1 * @param {Number} arg2 数2 * @returns */ export const floatAdd = (arg1: any, arg2: any) => { let r1, r2, m; try { r1 = arg1.toString().split(".")[1].length; } catch (e) { r1 = 0; } try { r2 = arg2.toString().split(".")[1].length; } catch (e) { r2 = 0; } m = Math.pow(10, Math.max(r1, r2)); return (Math.round(arg1 * m) + Math.round(arg2 * m)) / m; }; export const floatAddMore = (arr: any) => { let r1, r2, m, sum = 0; for (const item of arr) { if (item == undefined || item == null) continue; r1 = sum.toString().split(".")[1]?.length ?? 0; r2 = item.toString().split(".")[1]?.length ?? 0; m = Math.pow(10, Math.max(r1, r2)); sum = (Math.round(sum * m) + Math.round(item * m)) / m; } return sum; }; export const floatSub = (arg1: any, arg2: any) => { let r1, r2, m, n; try { r1 = arg1.toString().split(".")[1].length; } catch (e) { r1 = 0; } try { r2 = arg2.toString().split(".")[1].length; } catch (e) { r2 = 0; } m = Math.pow(10, Math.max(r1, r2)); //动态控制精度长度 n = r1 >= r2 ? r1 : r2; return ((Math.round(arg1 * m) - Math.round(arg2 * m)) / m).toFixed(n); }; export const floatMul = (arg1: any, arg2: any) => { let m = 0, s1 = arg1.toString(), s2 = arg2.toString(); try { m += s1.split(".")[1].length; } catch (e) {} try { m += s2.split(".")[1].length; } catch (e) {} return (Number(s1.replace(".", "")) * Number(s2.replace(".", ""))) / Math.pow(10, m); }; export const floatMulMore = (arr: any) => { let m = 0, sum = 1; for (const item of arr) { if (item) { let s1 = item.toString(); try { m += s1.split(".")[1].length; } catch (e) {} sum = sum * Number(s1.replace(".", "")); } } return Number(sum) / Math.pow(10, m); }; export const floatDiv = (arg1: any, arg2: any) => { let t1 = 0, t2 = 0, r1, r2; try { t1 = arg1.toString().split(".")[1].length; } catch (e) {} try { t2 = arg2.toString().split(".")[1].length; } catch (e) {} r1 = Number(arg1.toString().replace(".", "")); r2 = Number(arg2.toString().replace(".", "")); return (r1 / r2) * Math.pow(10, t2 - t1); }; /** * @description 文件类型转化base64类型 * @param type 文件类型 * @returns base64类型 */ export const getBase64Type = (type: string) => { switch (type.toLowerCase()) { case "txt": return "data:text/plain;base64,"; case "doc": return "data:application/msword;base64,"; case "docx": return "data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,"; case "xls": return "data:application/vnd.ms-excel;base64,"; case "xlsx": return "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,"; case "pdf": return "data:application/pdf;base64,"; case "pptx": return "data:application/vnd.openxmlformats-officedocument.presentationml.presentation;base64,"; case "ppt": return "data:application/vnd.ms-powerpoint;base64,"; case "png": return "data:image/png;base64,"; case "jpg": return "data:image/jpeg;base64,"; case "gif": return "data:image/gif;base64,"; case "svg": return "data:image/svg+xml;base64,"; case "ico": return "data:image/x-icon;base64,"; case "bmp": return "data:image/bmp;base64,"; default: return ""; } }; /** * @description 自定义函数检查className中是否包含需要的类名 */ export const classNameIncludes = (element: any, target: string[]) => { if (!element) return false; let _className: string[] = []; let parent = element.parentElement; // 获取当前元素的类名 if (typeof element.className === "string") { _className = element.className.split(/\s+/g); } // 如果父元素存在,则获取父元素的类名 while (parent) { if (parent.classList) { // 获取当前父元素的所有类名并添加到数组中 Array.from(parent.classList).forEach((className: any) => { if (!_className.includes(className)) { _className.push(className); } }); } // 移动到下一个父元素 parent = parent.parentElement; } return target.some(item => _className.indexOf(item) > -1); }; /** * @description 倒计时 * @param {number} value 剩余秒数 * @returns {string} 倒计时 00:00:30, */ export const countDownTime = (value: number) => { // 处理数字类型的秒数 if (value <= 0) { return ""; } const days = Math.floor(value / 86400); const hours = Math.floor((value % 86400) / 3600) .toString() .padStart(2, "0"); const minutes = Math.floor((value % 3600) / 60) .toString() .padStart(2, "0"); const seconds = (value % 60).toString().padStart(2, "0"); let text = ""; if (days > 365) { text = `${Math.floor(days / 365)}年`; } else if (days > 31) { text = `${Math.floor(days / 31)}月`; } else if (days > 0) { text = `${days}天`; } else { text = `${hours}:${minutes}:${seconds}`; } return text; }; const parseTime = (time: any, cFormat?: string) => { let format = cFormat ?? `{y}-{m}-{d} {h}:{i}:{s}`; let date; if (typeof time === "object") { date = time; } else { if (typeof time === "string") { if (/^[0-9]+$/.test(time)) { time = parseInt(time); } else { time = time.replace(new RegExp(/-/gm), "/"); } } if (typeof time === "number" && time.toString().length === 10) { time = time * 1000; } date = new Date(time); } const formatObj: any = { y: date.getFullYear(), m: date.getMonth() + 1, d: date.getDate(), h: date.getHours(), i: date.getMinutes(), s: date.getSeconds(), a: date.getDay() }; const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { const value = formatObj[key]; // Note: getDay() returns 0 on Sunday if (key === "a") { return ["日", "一", "二", "三", "四", "五", "六"][value]; } return value.toString().padStart(2, "0"); }); return time_str; }; /** * 最近时间-时差 * @param {number} time * @param {string} option * @returns {Number} (秒) */ export const formatTime = function (time: string | number, option: string, ifshort: boolean, nowtime?: string) { const _time = new Date(time); const d = new Date(time); const now = nowtime ? new Date(nowtime) : new Date(); const diff = (now.getTime() - d.getTime()) / 1000; if (ifshort) { if (diff < 0) { // flutter } else if (diff < 30) { return "刚刚"; } else if (diff < 3600) { // less 1 hour return Math.ceil(diff / 60) + "分钟前"; } else if (diff < 3600 * 6) { // 1~6小时 return Math.ceil(diff / 3600) + "小时前"; } let hourMins = parseTime(_time, "{h}:{i}"); let isSameDay = now.setHours(0, 0, 0, 0) - d.setHours(0, 0, 0, 0); switch (isSameDay) { case 0: return `今天 ${hourMins}`; case 86400000: return `昨天 ${hourMins}`; // case 172800000: // return `前天 ${hourMins}` } if (now.getFullYear() == d.getFullYear()) { return parseTime(_time, "{m}-{d} {h}:{i}"); } else { return parseTime(_time, "{y}-{m}-{d} {h}:{i}"); } } if (option) { return parseTime(_time, option); } }; const funcGetFormulaValue = (item, tgList, fieldList, valueList) => { let _paras = item?.formula .replace(/【/g, "[") .replace(/】/g, "]") .match(/\[([^\]]*)\]/g); let resultFormula = item?.formula.replace(/【/g, "[").replace(/】/g, "]"); _paras.forEach(part => { // console.log(part); let fieldName = part.slice(1, -1); // 去掉方括号 // console.log(fieldName); let sumVal = tgList.find(f => { if (f.field === fieldName) { return f; } else if (f.label === fieldName) { fieldName = f.field; return f; } }); !sumVal && (sumVal = fieldList.find(f => { if (f.field === fieldName) { return f; } else if (f.label === fieldName) { fieldName = f.field; return f; } })); if (sumVal) { let res = valueList[fieldName]; let _formula; if (sumVal?.formula) { _formula = sumVal.formula.replace(part, `${valueList[fieldName]}`); } // console.log(fieldName, _formula, sumVal); if (_formula && _formula.indexOf("[") > -1) { let _res = formulaPartsFormula([{ formula: _formula }], fieldList, valueList, tgList); res = `(${_res[0]})`; } if (typeof res === "undefined") { ElMessage.warning("接口未提供数据,请检查:" + fieldName); console.error("接口未提供数据,请检查:" + fieldName); return; } resultFormula = resultFormula.replace(part, res); } else { valueList[fieldName] && (resultFormula = resultFormula.replace(part, `${valueList[fieldName]}`)); } }); return resultFormula; }; export const formulaPartsFormula = (formulaList, fieldList, valueList, tgList = formulaList) => { let result = []; formulaList.map((item, index) => { if (item?.formula) { let res = funcGetFormulaValue(item, tgList, fieldList, valueList); result.push(res); } }); return result; }; /** * @description 公式计算 * @param formulaList 公式列表 [{field: 'a', formula: '[b] + [c]'}] * @param fieldList 字段列表 [{field: 'a', label: '字段A'}, {field: 'a', formula: '[b] + [c]'}] * @param valueList 值列表 {a: 1, b: 2, c: 3} * @returns {number[]} 计算结果 [1] */ export const calculateFormula = async (formulaList, fieldList, valueList) => { let resultFormula: any[] = formulaPartsFormula(formulaList, fieldList, valueList); console.log("resultFormula: ", resultFormula); try { let res = await GetFormulaCompute({ formulas: resultFormula }); if (res.values.length == 0) return []; let result = formulaList.map((item, index) => { let _res = res.values[index] ?? 0; return { ...item, value: _res }; }); return result; } catch (error) { console.error("Formula evaluation error:", error); return []; } }; /** * @description LjVxeTable获取当前表格选中数据;默认第一条记录 * @param targetRef 表格ref * @returns {$table, curRecords} 当前表格对象,当前选中数据 */ export const getCurrentRecords = (targetRef: any) => { const $table = targetRef.element; const _records = $table.getCheckboxRecords() ?? []; const _cRecords = $table.getCurrentRecord() ?? null; let curRecords = []; if ($table) { if (_records.length) { // 获取勾选列表 curRecords = _records; } else if (_cRecords) { // 获取当前选中数据 curRecords = [_cRecords]; } else { // 默认获取第一条记录 let fullData = $table.getTableData().fullData; if (fullData.length) { curRecords = [fullData[0]]; $table.setCurrentRow(fullData[0]); } } } return { $table, curRecords }; };