Newer
Older
dxCard-admin / src / utils / common / compUtils.ts
YFJ on 23 Sep 16 KB 项目推送
import { useGlobSetting } from '/@/hooks/setting';
import { merge, random } from 'lodash-es';
import { isArray } from '/@/utils/is';
import { FormSchema } from '/@/components/Form';
import { reactive } from "vue";
import { getTenantId, getToken } from "/@/utils/auth";
import { useUserStoreWithOut } from "/@/store/modules/user";
import dayjs from 'dayjs';
import Big from 'big.js';

import { Modal } from "ant-design-vue";
import { defHttp } from "@/utils/http/axios";
import { useI18n } from "@/hooks/web/useI18n";

const globSetting = useGlobSetting();
const baseApiUrl = globSetting.domainUrl;
/**
 *  获取文件服务访问路径
 * @param fileUrl 文件路径
 * @param prefix(默认http)  文件路径前缀 http/https
 */
export const getFileAccessHttpUrl = (fileUrl, prefix = 'http') => {
  let result = fileUrl;
  try {
    if (fileUrl && fileUrl.length > 0 && !fileUrl.startsWith(prefix)) {
      //判断是否是数组格式
      let isArray = fileUrl.indexOf('[') != -1;
      if (!isArray) {
        let prefix = `${baseApiUrl}/sys/common/static/`;
        // 判断是否已包含前缀
        if (!fileUrl.startsWith(prefix)) {
          result = `${prefix}${fileUrl}`;
        }
      }
    }
  } catch (err) {}
  return result;
};

/**
 * 触发 window.resize
 */
export function triggerWindowResizeEvent() {
  let event: any = document.createEvent('HTMLEvents');
  event.initEvent('resize', true, true);
  event.eventType = 'message';
  window.dispatchEvent(event);
}

/**
 * 获取随机数
 *  @param length 数字位数
 */
export const getRandom = (length: number = 1) => {
  return '-' + parseInt(String(Math.random() * 10000 + 1), length);
};

/**
 * 随机生成字符串
 * @param length 字符串的长度
 * @param chats 可选字符串区间(只会生成传入的字符串中的字符)
 * @return string 生成的字符串
 */
export function randomString(length: number, chats?: string) {
  if (!length) length = 1;
  if (!chats) {
    // noinspection SpellCheckingInspection
    chats = '0123456789qwertyuioplkjhgfdsazxcvbnm';
  }
  let str = '';
  for (let i = 0; i < length; i++) {
    let num = random(0, chats.length - 1);
    str += chats[num];
  }
  return str;
}

/**
 * 将普通列表数据转化为tree结构
 * @param array tree数据
 * @param opt  配置参数
 * @param startPid 父节点
 */
export const listToTree = (array, opt, startPid) => {
  const obj = {
    primaryKey: opt.primaryKey || 'key',
    parentKey: opt.parentKey || 'parentId',
    titleKey: opt.titleKey || 'title',
    startPid: opt.startPid || '',
    currentDept: opt.currentDept || 0,
    maxDept: opt.maxDept || 100,
    childKey: opt.childKey || 'children',
  };
  if (startPid) {
    obj.startPid = startPid;
  }
  return toTree(array, obj.startPid, obj.currentDept, obj);
};
/**
 *  递归构建tree
 * @param list
 * @param startPid
 * @param currentDept
 * @param opt
 * @returns {Array}
 */
export const toTree = (array, startPid, currentDept, opt) => {
  if (opt.maxDept < currentDept) {
    return [];
  }
  let child = [];
  if (array && array.length > 0) {
    child = array
      .map((item) => {
        // 筛查符合条件的数据(主键 = startPid)
        if (typeof item[opt.parentKey] !== 'undefined' && item[opt.parentKey] === startPid) {
          // 满足条件则递归
          const nextChild = toTree(array, item[opt.primaryKey], currentDept + 1, opt);
          // 节点信息保存
          if (nextChild.length > 0) {
            item['isLeaf'] = false;
            item[opt.childKey] = nextChild;
          } else {
            item['isLeaf'] = true;
          }
          item['title'] = item[opt.titleKey];
          item['label'] = item[opt.titleKey];
          item['key'] = item[opt.primaryKey];
          item['value'] = item[opt.primaryKey];
          return item;
        }
      })
      .filter((item) => {
        return item !== undefined;
      });
  }
  return child;
};

/**
 * 表格底部合计工具方法
 * @param tableData 表格数据
 * @param fieldKeys 要计算合计的列字段
 */
export function mapTableTotalSummary(tableData: Recordable[], fieldKeys: string[]) {
  let totals: any = { _row: '合计', _index: '合计' };
  fieldKeys.forEach((key) => {
    totals[key] = tableData.reduce((prev, next) => {
      // update-begin--author:liaozhiyang---date:20240118---for:【QQYUN-7891】PR 合计工具方法,转换为Nuber类型再计算
      const value = Number(next[key]);
      if (!Number.isNaN(value)) {
        // update-begin--author:liaozhiyang---date:20250224---for:【issues/7830】合计小数计算精度
        prev = Big(prev).plus(value).toString();
        // update-end--author:liaozhiyang---date:20250224---for:【issues/7830】合计小数计算精度
      }
      // update-end--author:liaozhiyang---date:20240118---for:【issues/7830】PR 合计工具方法,转换为Nuber类型再计算
      return prev;
    }, 0);
    // update-begin--author:liaozhiyang---date:20250224---for:【issues/7830】合计小数计算精度
    totals[key] = +totals[key];
    // update-end--author:liaozhiyang---date:20250224---for:【issues/7830】合计小数计算精度
  });
  return totals;
}

/**
 * 简单实现防抖方法
 *
 * 防抖(debounce)函数在第一次触发给定的函数时,不立即执行函数,而是给出一个期限值(delay),比如100ms。
 * 如果100ms内再次执行函数,就重新开始计时,直到计时结束后再真正执行函数。
 * 这样做的好处是如果短时间内大量触发同一事件,只会执行一次函数。
 *
 * @param fn 要防抖的函数
 * @param delay 防抖的毫秒数
 * @returns {Function}
 */
export function simpleDebounce(fn, delay = 100) {
  let timer: any | null = null;
  return function () {
    let args = arguments;
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      // @ts-ignore
      fn.apply(this, args);
    }, delay);
  };
}

/**
 * 日期格式化
 * @param date 日期
 * @param block 格式化字符串
 */
export function dateFormat(date, block) {
  if (!date) {
    return '';
  }
  let format = block || 'yyyy-MM-dd';
  date = new Date(date);
  const map = {
    M: date.getMonth() + 1, // 月份
    d: date.getDate(), // 日
    h: date.getHours(), // 小时
    m: date.getMinutes(), // 分
    s: date.getSeconds(), // 秒
    q: Math.floor((date.getMonth() + 3) / 3), // 季度
    S: date.getMilliseconds(), // 毫秒
  };
  format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
    let v = map[t];
    if (v !== undefined) {
      if (all.length > 1) {
        v = `0${v}`;
        v = v.substr(v.length - 2);
      }
      return v;
    } else if (t === 'y') {
      return date
        .getFullYear()
        .toString()
        .substr(4 - all.length);
    }
    return all;
  });
  return format;
}

/**
 * 获取事件冒泡路径,兼容 IE11,Edge,Chrome,Firefox,Safari
 * 目前使用的地方:JVxeTable Span模式
 */
export function getEventPath(event) {
  let target = event.target;
  let path = (event.composedPath && event.composedPath()) || event.path;

  if (path != null) {
    return path.indexOf(window) < 0 ? path.concat(window) : path;
  }

  if (target === window) {
    return [window];
  }

  let getParents = (node, memo) => {
    const parentNode = node.parentNode;

    if (!parentNode) {
      return memo;
    } else {
      return getParents(parentNode, memo.concat(parentNode));
    }
  };
  return [target].concat(getParents(target, []), window);
}

/**
 * 如果值不存在就 push 进数组,反之不处理
 * @param array 要操作的数据
 * @param value 要添加的值
 * @param key 可空,如果比较的是对象,可能存在地址不一样但值实际上是一样的情况,可以传此字段判断对象中唯一的字段,例如 id。不传则直接比较实际值
 * @returns {boolean} 成功 push 返回 true,不处理返回 false
 */
export function pushIfNotExist(array, value, key?) {
  for (let item of array) {
    if (key && item[key] === value[key]) {
      return false;
    } else if (item === value) {
      return false;
    }
  }
  array.push(value);
  return true;
}
/**
 * 过滤对象中为空的属性
 * @param obj
 * @returns {*}
 */
export function filterObj(obj) {
  if (!(typeof obj == 'object')) {
    return;
  }

  for (let key in obj) {
    if (obj.hasOwnProperty(key) && (obj[key] == null || obj[key] == undefined || obj[key] === '')) {
      delete obj[key];
    }
  }
  return obj;
}

/**
 * 下划线转驼峰
 * @param string
 */
export function underLine2CamelCase(string: string) {
  return string.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}

/**
 * 查找树结构
 * @param treeList
 * @param fn 查找方法
 * @param childrenKey
 */
export function findTree(treeList: any[], fn: Fn, childrenKey = 'children') {
  for (let i = 0; i < treeList.length; i++) {
    let item = treeList[i];
    if (fn(item, i, treeList)) {
      return item;
    }
    let children = item[childrenKey];
    if (isArray(children)) {
      let findResult = findTree(children, fn, childrenKey);
      if (findResult) {
        return findResult;
      }
    }
  }
  return null;
}

/** 获取 mapFormSchema 方法 */
export function bindMapFormSchema<T>(spanMap, spanTypeDef: T) {
  return function (s: FormSchema, spanType: T = spanTypeDef) {
    return merge(
      {
        disabledLabelWidth: true,
      } as FormSchema,
      spanMap[spanType],
      s
    );
  };
}

/**
 * 字符串是否为null或null字符串
 * @param str
 * @return {boolean}
 */
export function stringIsNull(str) {
  // 两个 == 可以同时判断 null 和 undefined
  return str == null || str === 'null' || str === 'undefined';
}

/**
 * 【组件多了可能存在性能问题】获取弹窗div,将下拉框、日期等组件挂载到modal上,解决弹窗遮盖问题
 * @param node
 */
export function getAutoScrollContainer(node: HTMLElement) {
  let element: Nullable<HTMLElement> = node
  while (element != null) {
    if (element.classList.contains('scrollbar__view')) {
      // 判断是否有滚动条
      if (element.clientHeight < element.scrollHeight) {
        // 有滚动条时,挂载到父级,解决滚动问题
        return node.parentElement
      } else {
        // 无滚动条时,挂载到body上,解决下拉框遮盖问题
        return document.body
      }
    } else {
      element = element.parentElement
    }
  }
  // 不在弹窗内,走默认逻辑
  return node.parentElement
}

/**
 * 判断子菜单是否全部隐藏
 * @param menuTreeItem
 */
export  function checkChildrenHidden(menuTreeItem){
  //是否是聚合路由
  let alwaysShow=menuTreeItem.alwaysShow;
  if(alwaysShow){
    return false;
  }
  if(!menuTreeItem.children){
    return false
  }
  return menuTreeItem.children?.find((item) => item.hideMenu == false) != null;
}

/**
 * 计算文件大小
 * @param fileSize
 * @param unit
 * @return 返回大小及后缀
 */
export function calculateFileSize(fileSize, unit?) {
  let unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  if (unit && unit.length > 0) {
    unitArr = unit;
  }
  let size = fileSize;
  let unitIndex = 0;
  while (size >= 1024 && unitIndex < unitArr.length - 1) {
    size /= 1024;
    unitIndex++;
  }
  //保留两位小数,四舍五入
  size = Math.round(size * 100) / 100;
  return size + unitArr[unitIndex];
}

/**
 * 获取上传header
 */
export function getHeaders() {
  let tenantId = getTenantId();
  return reactive({
    'X-Access-Token': getToken(),
    'X-Tenant-Id': tenantId ? tenantId : '0',
  });
}

/** 根据表达式获取相应的用户信息 */
export function getUserInfoByExpression(expression) {
  if (!expression) {
    return expression;
  }
  // 当前日期
  if (expression === 'sys_date' || expression === 'sysDate') {
    return dayjs().format('YYYY-MM-DD');
  }
  // 当前时间
  if (expression === 'sys_time' || expression === 'sysTime') {
    return dayjs().format('HH:mm:ss');
  }
  const userStore = useUserStoreWithOut();
  let userInfo = userStore.getUserInfo;
  if (userInfo) {
    switch (expression) {
      case 'sysUserId':
        return userInfo.id;
      // 当前登录用户登录账号
      case 'sysUserCode':
      case 'sys_user_code':
        return userInfo.username;
      // 当前登录用户真实名称
      case 'sysUserName':
        return userInfo.realname;
      // 当前登录用户部门编号
      case 'sysOrgCode':
      case 'sys_org_code':
        return userInfo.orgCode;
    }
  }
  return expression;
}

/**
 * 替换表达式(#{xxx})为用户信息
 * @param expression
 */
export function replaceUserInfoByExpression(expression: string | any[]) {
  if (!expression) {
    return expression;
  }
  const isString = typeof expression === 'string';
  const isArray = Array.isArray(expression)
  if (!isString && !isArray) {
    return expression;
  }
  const reg = /#{(.*?)}/g;
  const replace = (str) => {
    if (typeof str !== 'string') {
      return str;
    }
    let result = str.match(reg);
    if (result && result.length > 0) {
      result.forEach((item) => {
        let userInfo = getUserInfoByExpression(item.substring(2, item.length - 1));
        str = str.replace(item, userInfo);
      });
    }
    return str;
  };
  // @ts-ignore
  return isString ? replace(expression) : expression.map(replace);
}

/**
 * 设置租户缓存,当租户退出的时候
 * 
 * @param tenantId
 */
export async function userExitChangeLoginTenantId(tenantId){
  const userStore = useUserStoreWithOut();
  //step 1 获取用户租户
  const url = '/sys/tenant/getCurrentUserTenant'
  let currentTenantId = null;
  const data = await defHttp.get({ url });
  if(data && data.list){
    let arr = data.list;
    if(arr.length>0){
      //step 2.判断当前id是否存在用户租户中
      let filterTenantId = arr.filter((item) => item.id == tenantId);
      //存在说明不是退出的不是当前租户,还用用来的租户即可
      if(filterTenantId && filterTenantId.length>0){
        currentTenantId = tenantId;
      }else{
        //不存在默认第一个
        currentTenantId = arr[0].id
      }
    }
  }
  let loginTenantId = getTenantId();
  userStore.setTenant(currentTenantId);

  //update-begin---author:wangshuai---date:2023-11-07---for:【QQYUN-7005】退租户,判断退出的租户ID与当前租户ID一致,再刷新---
  //租户为空,说明没有租户了,需要刷新页面。或者当前租户和退出的租户一致则需要刷新浏览器
  if(!currentTenantId || tenantId == loginTenantId){
    window.location.reload();
  }
  //update-end---author:wangshuai---date:2023-11-07---for:【QQYUN-7005】退租户,判断退出的租户ID与当前租户ID一致,再刷新---
}

/**
 * 我的租户模块需要开启多租户提示
 * 
 * @param title 标题
 */
export function tenantSaasMessage(title){
  let tenantId = getTenantId();
  if(!tenantId){
    Modal.confirm({
      title:title,
      content: '此菜单需要在多租户模式下使用,否则数据会出现混乱',
      okText: '确认',
      okType: 'danger',
      // @ts-ignore
      cancelButtonProps: { style: { display: 'none' } },
    })
  }
}

/**
 * 判断日期和当前时间是否为同一天
 * @param dateStr
 */
export function sameDay(dateStr) {
  if (!dateStr) {
    return false;
  }
  // 获取当前日期
  let currentDate = new Date();
  let currentDay = currentDate.getDate();
  let currentMonth = currentDate.getMonth();
  let currentYear = currentDate.getFullYear();

  //创建另一个日期进行比较
  let otherDate = new Date(dateStr);
  let otherDay = otherDate.getDate();
  let otherMonth = otherDate.getMonth();
  let otherYear = otherDate.getFullYear();

  //比较日期
  if (currentDay === otherDay && currentMonth === otherMonth && currentYear === otherYear) {
    return true;
  } else {
    return false;
  }
}


/**
 * 翻译菜单名称
 * 2024-02-28
 * liaozhiyang
 * @param data
 */
export function translateTitle(data) {
  if (data?.length) {
    const { t } = useI18n();
    data.forEach((item) => {
      if (item.slotTitle) {
        if (item.slotTitle.includes("t('") && t) {
          item.slotTitle = new Function('t', `return ${item.slotTitle}`)(t);
        }
      }
      if (item.children?.length) {
        translateTitle(item.children);
      }
    });
  }
  return data;
}

/**
 *
 * 深度冻结对象
 * @param obj Object or Array
 */
export function freezeDeep(obj: Recordable | Recordable[]) {
  if (obj != null) {
    if (Array.isArray(obj)) {
      obj.forEach(item => freezeDeep(item))
    } else if (typeof obj === 'object') {
      Object.values(obj).forEach(value => {
        freezeDeep(value)
      })
    }
    Object.freeze(obj)
  }
  return obj
}