|
@@ -0,0 +1,377 @@
|
|
|
+/**
|
|
|
+ 下载方法 通过blob和文件名
|
|
|
+*/
|
|
|
+export const downloadFile = (blob: Blob, fileName: string) => {
|
|
|
+ const link = document.createElement("a")
|
|
|
+ link.style.display = "none"
|
|
|
+ link.href = window.URL.createObjectURL(blob)
|
|
|
+ link.download = fileName
|
|
|
+ // 触发点击
|
|
|
+ document.body.appendChild(link)
|
|
|
+ link.click()
|
|
|
+ // 然后移除
|
|
|
+ document.body.removeChild(link)
|
|
|
+ window.URL.revokeObjectURL(link.href)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ 下载方法 通过文件路径
|
|
|
+ */
|
|
|
+export const downloadFileUrl = (url: string) => {
|
|
|
+ const link = document.createElement("a")
|
|
|
+ link.style.display = "none"
|
|
|
+ link.href = url
|
|
|
+ // 触发点击
|
|
|
+ document.body.appendChild(link)
|
|
|
+ link.click()
|
|
|
+ // 然后移除
|
|
|
+ document.body.removeChild(link)
|
|
|
+ window.URL.revokeObjectURL(link.href)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ 批量下载方法 通过文件路径 尽量打成压缩包下载 这个方法很多浏览器禁用
|
|
|
+ */
|
|
|
+export const batchDownloadFileUrl = (url: string) => {
|
|
|
+ const iframe = document.createElement("iframe")
|
|
|
+ iframe.style.display = "none"
|
|
|
+ iframe.src = url
|
|
|
+ document.body.appendChild(iframe)
|
|
|
+ iframe.onload = () => {
|
|
|
+ iframe.remove()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 去掉字符串中的html标签
|
|
|
+ */
|
|
|
+export const removeHtmlTag = (str: string) => {
|
|
|
+ return new DOMParser().parseFromString(str, "text/html").body.textContent || ""
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ 复制文本到剪贴板
|
|
|
+*/
|
|
|
+export const copyText = (text: string) => {
|
|
|
+ return navigator.clipboard.writeText(text)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 时间转换
|
|
|
+ * @param fmt yyyy-mm-dd hh:ii:ss
|
|
|
+ */
|
|
|
+export const format = (dateData?: Date | number | string, fmt?: string) => {
|
|
|
+ // 兼容ie replace(/-/g, "/")
|
|
|
+ const date = dateData ? new Date(typeof dateData === "string" ? dateData.replace(/-/g, "/") : dateData) : new Date()
|
|
|
+ fmt = fmt || "yyyy-mm-dd"
|
|
|
+ const opt = {
|
|
|
+ "y+": date.getFullYear().toString(), // 年
|
|
|
+ "m+": (date.getMonth() + 1).toString(), // 月
|
|
|
+ "d+": date.getDate().toString(), // 日
|
|
|
+ "h+": date.getHours().toString(), // 时
|
|
|
+ "i+": date.getMinutes().toString(), // 分
|
|
|
+ "s+": date.getSeconds().toString() // 秒
|
|
|
+ }
|
|
|
+ let k: keyof typeof opt
|
|
|
+ for (k in opt) {
|
|
|
+ const ret = new RegExp(`(${k})`).exec(fmt)
|
|
|
+ if (ret) {
|
|
|
+ fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0"))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return fmt
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ 获取两日期之间日期列表函数 返回数组
|
|
|
+*/
|
|
|
+export const getdiffdate = (stime: string, etime: string) => {
|
|
|
+ // 初始化日期列表,数组
|
|
|
+ const diffdate: string[] = []
|
|
|
+ let i = 0
|
|
|
+ // 开始日期小于等于结束日期,并循环
|
|
|
+ while (stime <= etime) {
|
|
|
+ diffdate[i] = stime
|
|
|
+ // 获取开始日期时间戳
|
|
|
+ const stime_ts = new Date(stime.replace(/-/g, "/")).getTime()
|
|
|
+ // 增加一天时间戳后的日期
|
|
|
+ const next_date = stime_ts + 24 * 60 * 60 * 1000
|
|
|
+ // 拼接年月日,这里的月份会返回(0-11),所以要+1
|
|
|
+ const next_dates_y = new Date(next_date).getFullYear() + "-"
|
|
|
+ const next_dates_m =
|
|
|
+ new Date(next_date).getMonth() + 1 < 10 ? "0" + (new Date(next_date).getMonth() + 1) + "-" : new Date(next_date).getMonth() + 1 + "-"
|
|
|
+ const next_dates_d = new Date(next_date).getDate() < 10 ? "0" + new Date(next_date).getDate() : new Date(next_date).getDate()
|
|
|
+ stime = next_dates_y + next_dates_m + next_dates_d
|
|
|
+ // 增加数组key
|
|
|
+ i++
|
|
|
+ }
|
|
|
+ return diffdate
|
|
|
+}
|
|
|
+
|
|
|
+/** 生成 uuid */
|
|
|
+export const UUID = () => {
|
|
|
+ let d = new Date().getTime()
|
|
|
+ if (window.performance && typeof window.performance.now === "function") {
|
|
|
+ d += performance.now() // use high-precision timer if available
|
|
|
+ }
|
|
|
+ const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
|
|
+ const r = (d + Math.random() * 16) % 16 | 0
|
|
|
+ d = Math.floor(d / 16)
|
|
|
+ return (c == "x" ? r : (r & 0x3) | 0x8).toString(16)
|
|
|
+ })
|
|
|
+ return uuid
|
|
|
+}
|
|
|
+
|
|
|
+/** 深拷贝 */
|
|
|
+export const deepCopy = <T extends Record<string, any>>(source: T): T => {
|
|
|
+ const target = (Array.isArray(source) ? [] : {}) as T
|
|
|
+ for (const k in source) {
|
|
|
+ if (Object.hasOwnProperty.call(source, k)) {
|
|
|
+ // __proto__上面的属性不拷贝
|
|
|
+ const sourceVal = source[k]
|
|
|
+ const typeSource = Object.prototype.toString.call(sourceVal).slice(8, -1)
|
|
|
+ if (typeSource === "Object" || typeSource === "Array") {
|
|
|
+ target[k] = deepCopy(sourceVal)
|
|
|
+ } else {
|
|
|
+ target[k] = sourceVal
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return target
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 判断是不是空
|
|
|
+ *
|
|
|
+ * 能判断 "",[],null,undefined
|
|
|
+ */
|
|
|
+export const isEmpty = (val: string | Array<any> | number | null | undefined) => {
|
|
|
+ if (typeof val === "number") {
|
|
|
+ return val ? false : true
|
|
|
+ }
|
|
|
+ return val === null || val === undefined || val.length === 0 ? true : false
|
|
|
+}
|
|
|
+
|
|
|
+/** 根据身份证号获取出生年月日 */
|
|
|
+export const getBirthByIdCard = (idCard: string) => {
|
|
|
+ let birthday = ""
|
|
|
+ if (idCard.length == 15) {
|
|
|
+ birthday = "19" + idCard.slice(6, 12)
|
|
|
+ } else if (idCard.length == 18) {
|
|
|
+ birthday = idCard.slice(6, 14)
|
|
|
+ }
|
|
|
+ birthday = birthday.replace(/(.{4})(.{2})/, "$1-$2-")
|
|
|
+ // 通过正则表达式来指定输出格式为:1990-01-01
|
|
|
+ return birthday
|
|
|
+}
|
|
|
+
|
|
|
+/** 根据省份证获取性别 */
|
|
|
+export const getSexByIdCard = (idCard: string) => {
|
|
|
+ return parseInt(idCard.slice(-2, -1)) % 2 == 1 ? "1" : "0"
|
|
|
+}
|
|
|
+
|
|
|
+interface ThrottleFunc<T extends (...args: any[]) => any> {
|
|
|
+ (...args: Parameters<T>): ReturnType<T>
|
|
|
+ cancel(): void
|
|
|
+}
|
|
|
+/**
|
|
|
+ 创建并返回一个像节流阀一样的函数,当重复调用函数的时候,至少每隔 wait毫秒调用一次该函数。对于想控制一些触发频率较高的事件有帮助。
|
|
|
+ 默认情况下,throttle将在你调用的第一时间尽快执行这个function,并且,如果你在wait周期内调用任意次数的函数,都将尽快的被覆盖。
|
|
|
+ 如果你想禁用第一次首先执行的话,传递{leading: false},还有如果你想禁用最后一次执行的话,传递{trailing: false}。
|
|
|
+ 如果需要取消预定的 throttle ,可以在 throttle 函数上调用 .cancel()。
|
|
|
+*/
|
|
|
+export const throttle = <T extends (...args: any[]) => any>(
|
|
|
+ func: T,
|
|
|
+ wait = 600,
|
|
|
+ options: {
|
|
|
+ trailing?: boolean
|
|
|
+ leading?: boolean
|
|
|
+ } = {}
|
|
|
+): ThrottleFunc<T> => {
|
|
|
+ let timeout: number | null, context: any, args: any[], result: ReturnType<T>
|
|
|
+ let previous = 0
|
|
|
+ const later = function () {
|
|
|
+ previous = options.leading === false ? 0 : new Date().getTime()
|
|
|
+ timeout = null
|
|
|
+ result = func.apply(context, args)
|
|
|
+ if (!timeout) (args = []), (context = null)
|
|
|
+ }
|
|
|
+ const throttled: ThrottleFunc<T> = function (this: any, ..._args) {
|
|
|
+ const _now = new Date().getTime()
|
|
|
+ if (!previous && options.leading === false) previous = _now
|
|
|
+ const remaining = wait - (_now - previous)
|
|
|
+ context = this
|
|
|
+ args = _args
|
|
|
+ if (remaining <= 0 || remaining > wait) {
|
|
|
+ if (timeout) {
|
|
|
+ clearTimeout(timeout)
|
|
|
+ timeout = null
|
|
|
+ }
|
|
|
+ previous = _now
|
|
|
+ result = func.apply(context, args)
|
|
|
+ if (!timeout) (args = []), (context = null)
|
|
|
+ } else if (!timeout && options.trailing !== false) {
|
|
|
+ timeout = window.setTimeout(later, remaining)
|
|
|
+ }
|
|
|
+ return result
|
|
|
+ }
|
|
|
+ throttled.cancel = function () {
|
|
|
+ timeout && clearTimeout(timeout)
|
|
|
+ previous = 0
|
|
|
+ args = []
|
|
|
+ timeout = context = null
|
|
|
+ }
|
|
|
+ return throttled
|
|
|
+}
|
|
|
+
|
|
|
+interface DebouncedFunc<T extends (...args: any[]) => any> {
|
|
|
+ (...args: Parameters<T>): ReturnType<T>
|
|
|
+ cancel(): void
|
|
|
+}
|
|
|
+/**
|
|
|
+ 返回 function 函数的防反跳版本, 将延迟函数的执行(真正的执行)在函数最后一次调用时刻的 wait 毫秒之后. 对于必须在一些输入(多是一些用户操作)停止到达之后执行的行为有帮助。
|
|
|
+ 在 wait 间隔结束时,将使用最近传递给 debounced(去抖动)函数的参数调用该函数。
|
|
|
+ 传参 immediate 为 true, debounce会在 wait 时间间隔的开始调用这个函数 。(愚人码头注:并且在 waite 的时间之内,不会再次调用。)在类似不小心点了提交按钮两下而提交了两次的情况下很有用。
|
|
|
+ 如果需要取消预定的 debounce ,可以在 debounce 函数上调用 .cancel()。
|
|
|
+*/
|
|
|
+export const debounce = <T extends (...args: any[]) => any>(func: T, wait = 600, immediate = false): DebouncedFunc<T> => {
|
|
|
+ let timeout: number | null, previous: number, args: any[], result: ReturnType<T>, context: any
|
|
|
+ const later = function () {
|
|
|
+ const passed = new Date().getTime() - previous
|
|
|
+ if (wait > passed) {
|
|
|
+ timeout = window.setTimeout(later, wait - passed)
|
|
|
+ } else {
|
|
|
+ timeout = null
|
|
|
+ if (!immediate) result = func.apply(context, args)
|
|
|
+ // This check is needed because `func` can recursively invoke `debounced`.
|
|
|
+ if (!timeout) (args = []), (context = null)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const debounced: DebouncedFunc<T> = function (this: any, ..._args) {
|
|
|
+ context = this
|
|
|
+ args = _args
|
|
|
+ previous = new Date().getTime()
|
|
|
+ if (!timeout) {
|
|
|
+ timeout = window.setTimeout(later, wait)
|
|
|
+ if (immediate) result = func.apply(context, args)
|
|
|
+ }
|
|
|
+ return result
|
|
|
+ }
|
|
|
+ debounced.cancel = function () {
|
|
|
+ timeout && clearTimeout(timeout)
|
|
|
+ args = []
|
|
|
+ timeout = context = null
|
|
|
+ }
|
|
|
+ return debounced
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * html字符串关键词高亮
|
|
|
+ * @param html html格式的字符串
|
|
|
+ * @param keyword 需要查找的关键字
|
|
|
+ */
|
|
|
+export function htmlMarkHitClass(html: string, keyword: string) {
|
|
|
+ const repKey = "♽⚁" // 如果查询的关键词与这个替换的冲突,查询就会出bug 排除以下情况
|
|
|
+ if (["♽⚁", "♽", "⚁"].includes(keyword)) {
|
|
|
+ return {
|
|
|
+ html,
|
|
|
+ indexLen: 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!keyword) {
|
|
|
+ return {
|
|
|
+ html,
|
|
|
+ indexLen: 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let indexLen = 0
|
|
|
+ const regTag = /<.*?>/gi
|
|
|
+ const htmlTags = html.match(regTag) || []
|
|
|
+ html = html.replace(regTag, repKey)
|
|
|
+ html = html.replace(new RegExp(keyword, "g"), () => {
|
|
|
+ indexLen++
|
|
|
+ return `<span id="cus_${indexLen}" class="hitClass" >${keyword}</span>`
|
|
|
+ })
|
|
|
+ let regTagIndex = -1
|
|
|
+ html = html.replace(new RegExp(repKey, "g"), () => {
|
|
|
+ regTagIndex++
|
|
|
+ return htmlTags[regTagIndex]
|
|
|
+ })
|
|
|
+ return {
|
|
|
+ html,
|
|
|
+ indexLen
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const classNameToArray = (cls = "") => cls.split(" ").filter(item => !!item.trim())
|
|
|
+/** Boolean 判断有没有这个class */
|
|
|
+export const hasClass = (el: HTMLElement | null | undefined, cls: string) => {
|
|
|
+ if (!el) return false
|
|
|
+ if (cls.includes(" ")) throw new Error("className should not contain space.")
|
|
|
+ return el.classList.contains(cls)
|
|
|
+}
|
|
|
+/**
|
|
|
+ 添加class cls:"a b c" or "a" or "a b"
|
|
|
+*/
|
|
|
+export const addClass = (el: HTMLElement | null | undefined, cls: string) => {
|
|
|
+ if (!el) return
|
|
|
+ el.classList.add(...classNameToArray(cls))
|
|
|
+}
|
|
|
+
|
|
|
+/** 删除class cls:"a b c" or "a" or "a b" */
|
|
|
+export const removeClass = (el: HTMLElement | null | undefined, cls: string) => {
|
|
|
+ if (!el) return
|
|
|
+ el.classList.remove(...classNameToArray(cls))
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 获取el的style值
|
|
|
+ *
|
|
|
+ * 例:getStyle(document.querySelector("body"),"position") relative
|
|
|
+ */
|
|
|
+export const getStyle = (el: HTMLElement | null | undefined, styleName: keyof CSSStyleDeclaration): string => {
|
|
|
+ if (!el) return ""
|
|
|
+ if (styleName === "float") {
|
|
|
+ styleName = "cssFloat"
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const style = el.style[styleName]
|
|
|
+ if (style) return style as string
|
|
|
+ const computed = document.defaultView?.getComputedStyle(el, "")
|
|
|
+ return computed ? (computed[styleName] as string) : ""
|
|
|
+ } catch {
|
|
|
+ return el.style[styleName] as string
|
|
|
+ }
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 给el修改 style
|
|
|
+ *
|
|
|
+ setStyle(document.querySelector("body"),{left:0,position:"relative"})
|
|
|
+ setStyle(document.querySelector("body"),"left","0"})
|
|
|
+*/
|
|
|
+export const setStyle = (el: HTMLElement | null | undefined, styleName: CSSStyleDeclaration | keyof CSSStyleDeclaration, value?: string) => {
|
|
|
+ if (!el) return
|
|
|
+ if (typeof styleName === "object") {
|
|
|
+ let prop: keyof CSSStyleDeclaration
|
|
|
+ for (prop in styleName) {
|
|
|
+ setStyle(el, prop, styleName[prop])
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ typeof value === "string" && (el.style[styleName as any] = value)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 删除style
|
|
|
+ *removeStyle(document.querySelector("body"),{left:"",position:""})
|
|
|
+ * removeStyle(document.querySelector("body"),"left")
|
|
|
+ */
|
|
|
+export const removeStyle = (el: HTMLElement | null | undefined, style: CSSStyleDeclaration | keyof CSSStyleDeclaration) => {
|
|
|
+ if (!el) return
|
|
|
+ if (typeof style === "object") {
|
|
|
+ let prop: keyof CSSStyleDeclaration
|
|
|
+ for (prop in style) {
|
|
|
+ setStyle(el, prop, "")
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ setStyle(el, style, "")
|
|
|
+ }
|
|
|
+}
|