utils.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. import { h } from 'vue'
  2. import { NIcon } from 'naive-ui'
  3. import screenfull from 'screenfull'
  4. import throttle from 'lodash/throttle'
  5. import Image_404 from '../assets/images/exception/image-404.png'
  6. import html2canvas from 'html2canvas'
  7. import { downloadByA } from './file'
  8. import { toString } from './type'
  9. import cloneDeep from 'lodash/cloneDeep'
  10. import { WinKeyboard } from '@/enums/editPageEnum'
  11. import { RequestHttpIntervalEnum, RequestParamsObjType } from '@/enums/httpEnum'
  12. import { CreateComponentType, CreateComponentGroupType } from '@/packages/index.d'
  13. import { excludeParseEventKeyList, excludeParseEventValueList } from '@/enums/eventEnum'
  14. /**
  15. * * 判断是否是开发环境
  16. * @return { Boolean }
  17. */
  18. export const isDev = () => {
  19. return import.meta.env.DEV
  20. }
  21. /**
  22. * * 生成一个不重复的ID
  23. * @param { Number } randomLength
  24. */
  25. export const getUUID = (randomLength = 10) => {
  26. return 'id_' + Number(Math.random().toString().substring(2, randomLength) + Date.now()).toString(36)
  27. }
  28. /**
  29. * * render 图标
  30. * @param icon 图标
  31. * @param set 设置项
  32. */
  33. export const renderIcon = (icon: any, set = {}) => {
  34. return () => h(NIcon, set, { default: () => h(icon) })
  35. }
  36. /**
  37. * * render 语言
  38. * @param lang 语言标识
  39. * @param set 设置项
  40. * @param tag 要渲染成的标签
  41. */
  42. export const renderLang = (lang: string, set = {}, tag = 'span') => {
  43. return () => h(tag, set, { default: () => window['$t'](lang) })
  44. }
  45. /**
  46. * * 获取错误处理图片,默认 404 图
  47. * @returns url
  48. */
  49. export const requireErrorImg = () => {
  50. return Image_404
  51. }
  52. /**
  53. * * 全屏操作函数
  54. * @param isFullscreen
  55. * @param isEnabled
  56. * @returns
  57. */
  58. export const screenfullFn = (isFullscreen?: boolean, isEnabled?: boolean) => {
  59. // 是否是全屏
  60. if (isFullscreen) return screenfull.isFullscreen
  61. // 是否支持全屏
  62. if (isEnabled) return screenfull.isEnabled
  63. if (screenfull.isEnabled) {
  64. screenfull.toggle()
  65. return
  66. }
  67. // TODO lang
  68. window['$message'].warning('您的浏览器不支持全屏功能!')
  69. }
  70. /**
  71. * 修改元素位置
  72. * @param target 对象
  73. * @param x X轴
  74. * @param y Y轴
  75. */
  76. export const setComponentPosition = (
  77. target: CreateComponentType | CreateComponentGroupType,
  78. x?: number,
  79. y?: number
  80. ) => {
  81. x && (target.attr.x = x)
  82. y && (target.attr.y = y)
  83. }
  84. /**
  85. * * 设置元素属性
  86. * @param HTMLElement 元素
  87. * @param key 键名
  88. * @param value 键值
  89. */
  90. export const setDomAttribute = <K extends keyof CSSStyleDeclaration, V extends CSSStyleDeclaration[K]>(
  91. HTMLElement: HTMLElement,
  92. key: K,
  93. value: V
  94. ) => {
  95. if (HTMLElement) {
  96. HTMLElement.style[key] = value
  97. }
  98. }
  99. /**
  100. * * 判断是否是 mac
  101. * @returns boolean
  102. */
  103. export const isMac = () => {
  104. return /macintosh|mac os x/i.test(navigator.userAgent)
  105. }
  106. /**
  107. * * file转url
  108. */
  109. // export const fileToUrl = (file: File): string => {
  110. // const Url = URL || window.URL || window.webkitURL
  111. // const ImageUrl = Url.createObjectURL(file)
  112. // return ImageUrl
  113. // }
  114. /**
  115. * * file转base64
  116. */
  117. // export const fileTobase64 = (file: File, callback: Function) => {
  118. // let reader = new FileReader()
  119. // reader.readAsDataURL(file)
  120. // reader.onload = function (e: ProgressEvent<FileReader>) {
  121. // if (e.target) {
  122. // let base64 = e.target.result
  123. // callback(base64)
  124. // }
  125. // }
  126. // }
  127. /**
  128. * * 挂载监听
  129. */
  130. // eslint-disable-next-line no-undef
  131. export const addEventListener = <K extends keyof WindowEventMap>(
  132. target: HTMLElement | Document,
  133. type: K,
  134. listener: any,
  135. delay?: number,
  136. // eslint-disable-next-line no-undef
  137. options?: boolean | AddEventListenerOptions | undefined
  138. ) => {
  139. if (!target) return
  140. target.addEventListener(
  141. type,
  142. throttle(listener, delay || 300, {
  143. leading: true,
  144. trailing: false
  145. }),
  146. options
  147. )
  148. }
  149. /**
  150. * * 卸载监听
  151. */
  152. // eslint-disable-next-line no-undef
  153. export const removeEventListener = <K extends keyof WindowEventMap>(
  154. target: HTMLElement | Document,
  155. type: K,
  156. listener: any
  157. ) => {
  158. if (!target) return
  159. target.removeEventListener(type, listener)
  160. }
  161. /**
  162. * * 截取画面为图片并下载
  163. * @param html 需要截取的 DOM
  164. */
  165. export const canvasCut = (html: HTMLElement | null, callback?: Function) => {
  166. if (!html) {
  167. window['$message'].error('导出失败!')
  168. if (callback) callback()
  169. return
  170. }
  171. html2canvas(html, {
  172. backgroundColor: null,
  173. allowTaint: true,
  174. useCORS: true
  175. }).then((canvas: HTMLCanvasElement) => {
  176. window['$message'].success('导出成功!')
  177. downloadByA(canvas.toDataURL(), undefined, 'png')
  178. if (callback) callback()
  179. })
  180. }
  181. /**
  182. * * 函数过滤器
  183. * @param data 数据值
  184. * @param res 返回顶级对象
  185. * @param funcStr 函数字符串
  186. * @param isToString 是否转为字符串
  187. * @param errorCallBack 错误回调函数
  188. * @param successCallBack 成功回调函数
  189. * @returns
  190. */
  191. export const newFunctionHandle = (
  192. data: any,
  193. res: any,
  194. funcStr?: string,
  195. isToString?: boolean,
  196. errorCallBack?: Function,
  197. successCallBack?: Function
  198. ) => {
  199. try {
  200. if (!funcStr) return data
  201. const fn = new Function('data', 'res', funcStr)
  202. const fnRes = fn(cloneDeep(data), cloneDeep(res))
  203. const resHandle = isToString ? toString(fnRes) : fnRes
  204. // 成功回调
  205. successCallBack && successCallBack(resHandle)
  206. return resHandle
  207. } catch (error) {
  208. // 失败回调
  209. errorCallBack && errorCallBack(error)
  210. return '函数执行错误'
  211. }
  212. }
  213. /**
  214. * * 处理请求事件单位
  215. * @param num 时间间隔
  216. * @param unit RequestHttpIntervalEnum
  217. * @return number 秒数
  218. */
  219. export const intervalUnitHandle = (num: number, unit: RequestHttpIntervalEnum) => {
  220. switch (unit) {
  221. // 秒
  222. case RequestHttpIntervalEnum.SECOND:
  223. return num * 1000
  224. // 分
  225. case RequestHttpIntervalEnum.MINUTE:
  226. return num * 1000 * 60
  227. // 时
  228. case RequestHttpIntervalEnum.HOUR:
  229. return num * 1000 * 60 * 60
  230. // 天
  231. case RequestHttpIntervalEnum.DAY:
  232. return num * 1000 * 60 * 60 * 24
  233. default:
  234. return num * 1000
  235. }
  236. }
  237. /**
  238. * * 对象转换 cookie 格式
  239. * @param obj
  240. * @returns string
  241. */
  242. export const objToCookie = (obj: RequestParamsObjType) => {
  243. if (!obj) return ''
  244. let str = ''
  245. for (const key in obj) {
  246. str += key + '=' + obj[key] + ';'
  247. }
  248. return str.substring(0, str.length - 1)
  249. }
  250. /**
  251. * * 设置按下键盘按键的底部展示
  252. * @param keyCode
  253. * @returns
  254. */
  255. export const setKeyboardDressShow = (keyCode?: number) => {
  256. const code = new Map([
  257. [17, WinKeyboard.CTRL],
  258. [32, WinKeyboard.SPACE]
  259. ])
  260. const dom = document.getElementById('keyboard-dress-show')
  261. if (!dom) return
  262. if (!keyCode) {
  263. window.onKeySpacePressHold?.(false)
  264. dom.innerText = ''
  265. return
  266. }
  267. if (keyCode && code.has(keyCode)) {
  268. if (keyCode == 32) window.onKeySpacePressHold?.(true)
  269. dom.innerText = `按下了「${code.get(keyCode)}」键`
  270. }
  271. }
  272. /**
  273. * * JSON序列化,支持函数和 undefined
  274. * @param data
  275. */
  276. export const JSONStringify = <T>(data: T) => {
  277. return JSON.stringify(
  278. data,
  279. (key, val) => {
  280. // 处理函数丢失问题
  281. if (typeof val === 'function') {
  282. return `${val}`
  283. }
  284. // 处理 undefined 丢失问题
  285. if (typeof val === 'undefined') {
  286. return null
  287. }
  288. return val
  289. },
  290. 2
  291. )
  292. }
  293. export const evalFn = (fn: string) => {
  294. var Fun = Function // 一个变量指向Function,防止前端编译工具报错
  295. return new Fun('return ' + fn)()
  296. }
  297. /**
  298. * * JSON反序列化,支持函数和 undefined
  299. * @param data
  300. */
  301. export const JSONParse = (data: string) => {
  302. if (data.trim() === '') return
  303. return JSON.parse(data, (k, v) => {
  304. // // 过滤函数字符串
  305. // if (excludeParseEventKeyList.includes(k)) return v
  306. // // 过滤函数值表达式
  307. // if (typeof v === 'string') {
  308. // const someValue = excludeParseEventValueList.some(excludeValue => v.indexOf(excludeValue) > -1)
  309. // if (someValue) return v
  310. // }
  311. if (k !== 'formatter') {
  312. return v
  313. }
  314. // 还原函数值
  315. if (typeof v === 'string' && v.indexOf && (v.indexOf('function') > -1 || v.indexOf('=>') > -1)) {
  316. return evalFn(`(function(){return ${v}})()`)
  317. } else if (typeof v === 'string' && v.indexOf && v.indexOf('return ') > -1) {
  318. const baseLeftIndex = v.indexOf('(')
  319. if (baseLeftIndex > -1) {
  320. const newFn = `function ${v.substring(baseLeftIndex)}`
  321. return evalFn(`(function(){return ${newFn}})()`)
  322. }
  323. }
  324. return v
  325. })
  326. }
  327. /**
  328. * * 修改顶部标题
  329. * @param title
  330. */
  331. export const setTitle = (title?: string) => {
  332. title && (document.title = title)
  333. }
  334. /**
  335. * 时间日期转换
  336. * @param date 当前时间,new Date() 格式
  337. * @param format 需要转换的时间格式字符串
  338. * @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
  339. * @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
  340. * @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
  341. * @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
  342. * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
  343. * @returns 返回拼接后的时间字符串
  344. */
  345. export function formatDate(date: Date, format: string): string {
  346. const we = date.getDay() // 星期
  347. const z = getWeek(date) // 周
  348. const qut = Math.floor((date.getMonth() + 3) / 3).toString() // 季度
  349. const opt: { [key: string]: string } = {
  350. 'Y+': date.getFullYear().toString(), // 年
  351. 'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
  352. 'd+': date.getDate().toString(), // 日
  353. 'H+': date.getHours().toString(), // 时
  354. 'M+': date.getMinutes().toString(), // 分
  355. 'S+': date.getSeconds().toString(), // 秒
  356. 'q+': qut // 季度
  357. }
  358. // 中文数字 (星期)
  359. const week: { [key: string]: string } = {
  360. '0': '日',
  361. '1': '一',
  362. '2': '二',
  363. '3': '三',
  364. '4': '四',
  365. '5': '五',
  366. '6': '六'
  367. }
  368. // 中文数字(季度)
  369. const quarter: { [key: string]: string } = {
  370. '1': '一',
  371. '2': '二',
  372. '3': '三',
  373. '4': '四'
  374. }
  375. if (/(W+)/.test(format))
  376. format = format.replace(
  377. RegExp.$1,
  378. RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]
  379. )
  380. if (/(Q+)/.test(format))
  381. format = format.replace(
  382. RegExp.$1,
  383. RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]
  384. )
  385. if (/(Z+)/.test(format))
  386. format = format.replace(RegExp.$1, RegExp.$1.length == 3 ? '第' + z + '周' : z + '')
  387. for (const k in opt) {
  388. const r = new RegExp('(' + k + ')').exec(format)
  389. // 若输入的长度不为1,则前面补零
  390. if (r)
  391. format = format.replace(
  392. r[1],
  393. RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0')
  394. )
  395. }
  396. return format
  397. }
  398. /**
  399. * 获取当前日期是第几周
  400. * @param dateTime 当前传入的日期值
  401. * @returns 返回第几周数字值
  402. */
  403. export function getWeek(dateTime: Date): number {
  404. const temptTime = new Date(dateTime.getTime())
  405. // 周几
  406. const weekday = temptTime.getDay() || 7
  407. // 周1+5天=周六
  408. temptTime.setDate(temptTime.getDate() - weekday + 1 + 5)
  409. let firstDay = new Date(temptTime.getFullYear(), 0, 1)
  410. const dayOfWeek = firstDay.getDay()
  411. let spendDay = 1
  412. if (dayOfWeek != 0) spendDay = 7 - dayOfWeek + 1
  413. firstDay = new Date(temptTime.getFullYear(), 0, 1 + spendDay)
  414. const d = Math.ceil((temptTime.valueOf() - firstDay.valueOf()) / 86400000)
  415. const result = Math.ceil(d / 7)
  416. return result
  417. }