npm package: @highmark/crm-utils
Description: ```shell npm i @highmark/crm-utils ```
Latest version: 0.0.36

## 安装

```shell
npm i @highmark/crm-utils
```

## 引入

```js
import utils from '@highmark/crm-utils'
```

## 工具类 - tool

> tool这块引用了 **lodash** 和 **vueuse**, 如果当前项目需要 **lodash** 和 **vueuse** 别的方法则单独安装这两个包, 否则会出现**幽灵依赖**的问题

- cloneDeep (lodash)

- debounce (lodash)

- throttle (lodash)

- isEqual (lodash)

- pick (lodash)

- pickBy (lodash)

- isNumber (lodash)

- chunk (lodash)

- useDebounceFn ( @vueuse/core )

- useThrottleFn ( @vueuse/core )

- delay(time=30) // 延迟

- findNodeByName // 根据 name 获取对应树的节点 (适用于菜单树)

- calculateStringLength(input:string): number // 计算当前字符串长度, 如果汉字则为2, 如果是英文则为1, 如果没有值则为0

## vue element

- isNonEmptySlot(slots, slotKey) // 查看指定插槽内是否有元素

- isCommentType // 判断当前目标是否是注释类型 (用于判断虚拟节点)

- getPaddingByElement. // 获取元素的padding值

  ```typescript
  interface IGetPaddingByElement {
    (element: HTMLElement): GetPaddingByElementResultType
  }
  type GetPaddingByElementResultType = {
    top: number
    right: number
    bottom: number
    left: number
  }
  ```

- findParentsWithClass // 找到当前dom节点父级包含`className`的节点

  ```typescript
  interface IFindParentsWithClass {
    // className: 类名不用.开头
    (el: HTMLElement, className: string): HTMLElement[]
  }
  ```

## 处理 url 类. - url

- replaceCurrentUrl // 替换当前 url

- paramObjectToQueryString // 将对象转换为参数字符串的工具方法, 返回结果 '?name=aa&age=19'

- parseQueryString(urlStr) // 解析 url 参数

- parseUrl // 根据 url 获取参数对象, 返回 `{ hash: '#...', hostname: 'https://...' , params: { k: v, ... }}`

- mergeFormDataByCurrentUrl( {formData} ) // 融合当前 url 的 formData 参数 (注意是放在当前模块 formData 的默认赋值的上面还是下面, 这个循序会影响相同属性是否会覆盖)

- toPageCarryFormData({ name, formData }) // 跳转页面并携带 formdata 参数, 会对 formData 进行编码

## 时间 - time

> dayjs改造

- subDate // 获取时间 a-b 的值, 参数(Date1, Date2)

- forMatterDate(date, format = 'YYYY-MM-DD HH:mm:ss') // 转换时间格式

- get30PreDay // 获取 30 天前的时间

- getCurrentDate(format = 'YYYY-MM-DD HH:mm:ss') // 获取当前时间

- get30PreDay // 获取三十天前的时间

- getPreFirstYearAndCurrentDate(format = 'YYYY-MM-DD HH:mm:ss') // 获取当前时间的前一年时间和当前时间, 返回 [ 前一年时间, 当前时间 ]

- getFirstDayOfCurrentMonth(format = 'YYYY-MM-DD HH:mm:ss') // 获取当前月的第一天

- getCurrentTimestamp // 获取当前时间戳（以毫秒为单位）

## 处理业务类 - yewu

- 😭refreshClientId // 刷新 clientId, 防止重复请求

- formatUserAndOrg // 格式化对象为 用户带部门的对象(大多用于反显), 参数: { userName, userId, orgId, orgName }

- 😭openDetailPopup // 打开详情页 ( 传参 { componentName, componentTitle } )

- 😭refreshAllTabCount // 通过事件总线的方式, 刷新详情中 tabs 组件的 count

- formatUserAndOrgAndNodeId({ userName, userId, orgId, orgName }) // 格式化对象为 用户带部门的对象, 返回 `{ name, id, orgs, _node_id }`

- formatNmyUserAndOrgAndNodeId_V2({ orgId, orgName, userId, userName }) // (nmy组织架构组件专用) 格式化对象为 用户带部门的对象, 返回 `{ userName, userId, orgId, orgName, id: '用户id!部门id' }`

- parseUserAndOrg // 将员工对象格式化为 { userName, userId, orgId, orgName }

## 数学类 - math

- addNums // 计算: 加

- subNums // 计算: 减

- mulNums // 计算: 乘

- divNums // 计算: 除

- round // 四舍五入

- isNumberStrict // 判断是否是数字 (必须数字类型, 严格模式)

- isNumberLoose // 判断是否能转换为数字 (任何类型, 宽松模式)

- getFixed // 解析数字, 补充后两位

- getFixedNumber // 解析为 最多保留两位小数的数字 (常用于验证: 比较金额, 数量等), 返回数字类型

- handleOnlyAllowNumber // 只能输入数字和. (用于 input 组件的监听限制)

- isZero // 判断当前是否是 0 或者'0'

- truncateDecimal( value, 保留几位 ) // 将数字保留小数 ( 向下取整, 不会约等于 )

- truncateDecimalStr( value, 保留几位 ) // 值为字符串

- generateRandomCode 获取随机字符串和数字 (去掉了 0 和o 和 I 和 1, l, 以免歧义)

- isSame 比较两个数字是否相等

## 转换 - tran

- tranId // 转换 id, 如果是正常的 id, 则返回原值, 如果是空或者下面的 id, 则返回 null, 也会判断`00000000-0000-0000-0000-000000000000`

- tranDate // 转换时间, 如果是正常的时间, 则返回原值, 如果是空或者下面的 id, 则返回 null, 也会判断`1900-01-01 00:00:00`

- tranNumber // 转换为数字, Infinity 或者 NaN 或者 null 或者'' 或者 NaN 或者 undefined 都返回 0

- parseValueJudgeEmpty // 判断值是否为空, 如果为空返回 '-'

- tranTipInnerHtml(...args) // 生成tips 的html片段

- replaceInvalidValuesWithNull(value) // 替换无效值为null, 目标为对象或数组 (用于格式化获取请求回来的数据, 避免出现脏数据)(深度克隆)

## 校验 - validate

- isNonEmptyArray // 检查数组是否非空且长度大于 1

- getFormRulesByCurrentEnv // 根据当前环境(移动端/pc)获取验证规则
  - mobile: {
    // 数字: 支持验证负数, 支持字符串格式
    isNumber
    // 金额: 数字类型 && 不等于 0
    isNonEmptyNumber
    // 检查数组是否非空且长度大于 1
    isNonEmptyArray
    // 必须填写
    requiredInput
    // 必须选择
    requiredSelect
    // 必须上传 (目标数组, 报错信息)
    requiredUploadByTarget(附件数组,'报错消息')
    // 自定义规则 (如果有错误直接返回报错信息, 没有错误不用管)
    customValidator({ cb:(value)=>{ return '报错信息' } })
    }
  - pc: {
    // 数字: 支持验证负数, 支持字符串格式
    isNumber
    // 金额: 数字类型 && 不能为 0
    isNonEmptyNumber,
    // 检查数组是否非空且长度大于 1
    isNonEmptyArray,
    // 必须填写
    requiredInput,
    // 必须选择
    requiredSelect,
    // 必须上传
    requiredUpload,
    // 自定义规则 (如果有错误直接返回报错信息, 没有错误不用管)
    customValidator({ cb:(value)=>{ return '报错信息' } })
    }

## file 文件处理

- getMd5ByFile // 文件转换为 MD5

- blobTOBase64 // blob 转 base64

- blobToFile // blob 转文件

- urlTOBlob // url 转 blob

- urlTOFile // url 转 file

- fileTOBase64 // file 转 base64

- fileTOBlob // file 转 blob

- base64TOBlob // base64 转 Blob

- elementTOImage // img 元素转 base64

## permission 权限处理

> import { permission } from '../../config/permission'

- isDisabled // 当前是否是禁用( 响应式 ), 使用: isDisabled(permission.BTN_DETAIL)

  ```typescript
  // 计算属性 ComputedRef<Array<string>> 或者 Ref<Array<string> >都可以
  type permissionListProxyType = {
    value: Array<string>
  }
  interface IIsDisabled {
    (action: string, permissionListProxy: permissionListProxyType): Ref<boolean>
  }
  ```

- isEnabled // 当前是否是启用( 响应式 ), 使用: isEnabled(permission.BTN_DETAIL)

## client 客户端相关 - client

- isMobile
- isSafari
- useIsMobileHook // 返回 { isMobileRef }
- isSafari: isSafariType // 当前是否是Safari浏览器
- isPc: isMobileRefType   // 当前是否是PC端
- isDing: isDingType  // 当前是否是钉钉浏览器

## vite

- openEditDetailComponent

```js
options: {

  viewsModuleName?: string    				// 模块名称

  viewModuleId?: string       				// 模块id

  detailComponentName?: string  			// 详情页组件名称

  destroyPropName?: string   					// 组件销毁的props属性名 (对应props值为函数类型)

  routeMapper?: InstanceType<typeof Map<number, string>>   // 模块id和路由映射

  methodMapper: InstanceType<typeof Map<string, any>>   // Map<触发方法名, 传参>

  metaGlobModules: any                // import.meta.glob

}
```

二次封装

```js
import utils from '@highmark/crm-utils'

/**
 * 创建一个映射，用于存储路由ID和对应的页面路径
 */
const routeMapper = new Map()
// { 模块id, 模块name }
routeMapper.set(1, 'about') // 注意 模块name为views内的模块id, 这里建议与路由的name一致

/**
 * 打开详情页功能。
 * 用于加载并显示指定的详情组件，支持组件名称和组件ID映射的方式。
 * @param {Object} param 一个包含多个配置选项的对象。
 * @param {Map<string, any>} param.methodMapper 组件暴露的方法映射 Map<方法名, 传参|null>
 * @param {string} param.viewsModuleName 视图模块的名称。
 * @param {string} param.viewModuleId 视图模块的ID。
 * @param {string} param.detailComponentName 详情组件的名称，默认为"AddAndEdit.vue"。
 * @param {string} param.destroyPropName 销毁属性的名称，默认为'destroy'。
 */
export function openAddAndDetail({
  exposedMethods,
  viewsModuleName,
  viewModuleId,
  detailComponentName,
  destroyPropName
} = {}) {
  // 判断当前浏览器环境
  const { isMobileRef } = utils.client.useIsMobileHook()

  // 根据当前运行环境动态导入所有视图文件以支持路径的通配符匹配
  let metaGlobModules = ''

  if (isMobileRef.value) {
    // 配置 移动端 详情 路径
    metaGlobModules = import.meta.glob(`@/views/**/mobile/**/**.vue`)
  } else {
    // 配置 pc 详情 路径
    metaGlobModules = import.meta.glob(`@/views/**/pc/**/**.vue`)
  }

  // 调用utils工具库中的方法，以打开编辑详情组件，并传入相关配置
  utils.vite.openEditDetailComponent({
    metaGlobModules,
    methodMapper: methodMapper, // new Map(Object.entries({ open: { dataId: '数据id' } }))
    viewsModuleName: viewsModuleName,
    routeMapper: routeMapper, // 路由映射器，用于解析路由ID到实际路径
    viewModuleId: viewModuleId,
    detailComponentName: detailComponentName || 'AddAndEdit.vue', // 如果未提供详情组件名称，则默认为"AddAndEdit.vue"
    destroyPropName: destroyPropName || 'destroy' // 如果未提供销毁props名称，则默认为'destroy'
  })
}
```

使用

```js
openAddAndDetail({
  // exposedMethods,
  // viewsModuleName,
  viewModuleId, // 如果配置了 routeMapper 映射, 直接传入模块id即可弹出详情
  //  触发组件暴露方法的映射 Map<方法名, 传参>
  methodMapper: new Map(
    Object.entries({
      // 如果携带数据值为 { dataId, ... }
      open: { dataId: '数据详情的id' }
    })
  )
  // detailComponentName,
  // destroyPropName
})
```

注意
( 详情页`props`必须配置 `destroy` 属性,
必须暴露 `open`, `close` 方法,
如弹窗销毁必须触发销毁`props.destroy`的方法
):

```js
const props = defineProps({
  // 销毁
  destroy: {
    type: Function,
    default: null
  }
})

const isShow = ref(false)

/**
 * 触发销毁prop
 **/
const executeDestroyProp = () => {
  if (typeof props.destroy === 'function') {
    props.destroy()
  }
}

const close = () => {
  isShow.value = false
  // 必须执行触发销毁prop
  executeDestroyProp()
}

defineExpose({
  open,
  close
})
```

## 公共常量 constant

### 微服务事件类

`utils.constant.microEventTypes`

```js
import utils from '@highmark/crm-utils'

// 传向基座
utils.constant.microEventTypes.toBase.OPEN_DETAIL_POPUP
// 传向子模块
utils.constant.microEventTypes.toChild.OPEN_DETAIL_POPUP
// 传向其他兄弟模块
utils.constant.microEventTypes.toSibling.OPEN_DETAIL_POPUP
```

##### 简单描述传递参数

> 参数说明 陆续补充

```js
/**
   * 发送到基组件的事件类型
   */
  toBase: {
    /**
     * 打开详细信息弹窗
     * 传参 {
     *  eventType:string,   // 事件类型
     *  moduleId?:string,   // 路由id
     *  moduleName?:string, // 路由Name
     *  appid?:string,      // 微服务Id
     *  detailData?:Record<string, any>  // 详情对象信息
     * }
     */
    OPEN_DETAIL_POPUP,
    /**
     * 刷新待办消息列表数据
     * 传参 {
     *  eventType:string,
     * }
     */
    REFRESH_TODO_MESSAGE_LIST_DATA,
    /**
     * token 过期登出
     * 传参 {
     *  eventType:string,
     * }
     */
    TOKEN_EXPIRES_SIGN_OUT,
    /**
     * 子模块加载路由成功后通知基座的事件
     * 传参 {
     *  eventType:string,
     * }
     */
    LOAD_ROUTES_SUCCESS,
    /**
     * 子模块通知基座已经打开详情;
     * 传参 { state: 'on' | 'off' }
     */
    SWITCH_CHILDREN_DETAIL_POPUP_STATE
  },
  /**
   * 发送到子组件的事件类型
   */
  toChild: {
    /**
     * 打开详细信息弹窗
     *  传参 {
     *  eventType:string,   // 事件类型
     *  moduleId?:string,   // 路由id
     *  moduleName?:string, // 路由Name
     *  appid?:string,      // 微服务Id
     *  detailData?:Record<string, any>  // 详情对象信息
     * }
     */
    OPEN_DETAIL_POPUP,
    /**
     * 用户权限信息
     * 传参 {
     *  eventType:string,   // 事件类型
     *  menus:Array<{ name:string, path:string, children: any[], meta: {  appid:string, title:string, type:string, icon:string, ... } }>,  // 菜单
     *  permission: string[],  // 按钮权限
     *  refreshToken: string,  // 刷新token
     *  token: string,         // token
     *  userInfo: Record<string, any>  // 用户信息
     *  refreshTokenFn: 刷新 token 和 refreshToken的方法
     *                  ( 注意: 该方法为异步函数, 返回值 { token, refreshToken } )
     *  baseRouter,     // 基座router路由对象
     * }
     */
    AUTH_DATA,
    /**
     * 通知 子模块 更新本地 token 和 refreshToken
     * 传参 {
     *  token, refreshToken
     *  }
     */
    UPDATE_TOKEN_AND_REFRESHTOKEN,
    /**
     * 通知 子模块 的 keepAlive 路由列表
     * 传参 {
     *  keepAliveList: string[]
     * }
     */
    UPDATE_KEEP_ALIVE_LIST,
     /**
     * 通知子模块刷新页面
     */
    REFRESH_PAGE
  },
  /**
   * 发送到同级组件的事件类型
   */
  toSibling: {
    /**
     * 打开详细信息弹窗
     *  传参 {
     *  eventType:string,   // 事件类型
     *  moduleId?:string,   // 路由id
     *  moduleName?:string, // 路由Name
     *  appid?:string,      // 微服务Id
     *  detailData?:Record<string, any>  // 详情对象信息
     * }
     */
    OPEN_DETAIL_POPUP
  }
```

##### 子模块获取auth的案例

```js
// 获取用户权限相关信息
const { authData } = initAuthData()

// 可以拿到token, 菜单...等信息
// console.log(authData.token)

// 初始化权限信息
function initAuthData() {
  const data = window.microApp.getData() // 返回主应用下发的data数据

  let authData // 权限信息

  // 判断类型为权限才会赋值
  if (data.eventType === crmUtils.constant.microEventTypes.toChild.AUTH_DATA) {
    authData = data || {}
  }

  return {
    authData
  }
}
```