├── docs ├── guide │ ├── Media.md │ ├── Plugins.md │ ├── Polyfill.md │ ├── start.md │ ├── Tools.md │ ├── Event.md │ ├── Canvas.md │ ├── Cookie.md │ ├── Random.md │ ├── index.md │ ├── EditMd.md │ ├── Number.md │ ├── Device.md │ ├── Storage.md │ ├── URL.md │ ├── String.md │ ├── Browser.md │ ├── Time.md │ ├── Regex.md │ └── Fn.md ├── .vitepress │ ├── theme │ │ ├── index.js │ │ └── custom.css │ └── config.ts ├── index.md └── images │ ├── gradient-left-dark.svg │ └── gradient-right-dark.svg ├── index.ts ├── src ├── events │ ├── index.ts │ └── mitt.ts ├── tools │ ├── index.ts │ └── print.ts ├── objects │ ├── index.ts │ └── isObject.ts ├── dom │ ├── index.ts │ ├── resize.ts │ ├── html.ts │ └── file.ts ├── polyfill │ ├── index.ts │ ├── array.ts │ └── string.ts ├── function │ ├── index.ts │ ├── mergeObj.ts │ ├── observe.ts │ └── tool.ts ├── storage │ ├── index.ts │ ├── localStorage.ts │ ├── sessionStorage.ts │ └── cookie.ts ├── string │ ├── money.ts │ ├── nanoid.ts │ └── index.ts ├── plugins │ ├── index.ts │ ├── dayjs.ts │ ├── copy.ts │ ├── keycode.ts │ └── importPlugin.ts ├── browser │ ├── isBrowser.ts │ ├── isSupportWebP.ts │ ├── getClientWidth.ts │ ├── index.ts │ ├── scrollToTop.ts │ ├── scrollToBottom.ts │ ├── toFullScreen.ts │ ├── exitFullscreen.ts │ ├── openNewTag.ts │ ├── smoothScroll.ts │ ├── getClientHeight.ts │ └── getBrowserInfo.ts ├── index.ts ├── log.ts ├── canvas │ └── index.ts ├── random │ └── index.ts ├── number │ └── index.ts ├── device │ └── index.ts ├── url │ └── index.ts ├── regex │ └── index.ts └── time │ └── index.ts ├── .env ├── .babelrc ├── public ├── pay.jpg └── fastool.jpg ├── .gitignore ├── .prettierrc ├── .npmignore ├── tsconfig.json ├── examples ├── demo.html ├── index.html └── logo.svg ├── .github └── workflows │ └── deploy.yml ├── README.md ├── package.json ├── rollup.config.js ├── LICENSE └── lib └── esm.min.js /docs/guide/Media.md: -------------------------------------------------------------------------------- 1 | # Media 2 | 3 | -------------------------------------------------------------------------------- /docs/guide/Plugins.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/index' -------------------------------------------------------------------------------- /docs/guide/Polyfill.md: -------------------------------------------------------------------------------- 1 | # Polyfill 2 | 3 | -------------------------------------------------------------------------------- /src/events/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mitt'; -------------------------------------------------------------------------------- /src/tools/index.ts: -------------------------------------------------------------------------------- 1 | export * from './print' -------------------------------------------------------------------------------- /src/objects/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './isObject'; -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ROLLUP_VERSION=2.79.1 2 | TYPESCRIPT_VERSION=4.9.5 -------------------------------------------------------------------------------- /src/dom/index.ts: -------------------------------------------------------------------------------- 1 | export * from './html' 2 | export * from './file' -------------------------------------------------------------------------------- /docs/guide/start.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 自动添加版本 3 | ```bash 4 | npm version patch 5 | ``` -------------------------------------------------------------------------------- /src/polyfill/index.ts: -------------------------------------------------------------------------------- 1 | export * from './array' 2 | export * from './string' 3 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-typescript"] 3 | } -------------------------------------------------------------------------------- /public/pay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobe-fe-dalao/fastool/HEAD/public/pay.jpg -------------------------------------------------------------------------------- /public/fastool.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tobe-fe-dalao/fastool/HEAD/public/fastool.jpg -------------------------------------------------------------------------------- /src/function/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tool'; 2 | export * from './observe' 3 | export * from './mergeObj' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules 3 | WORKFLOW.md 4 | coverage/ 5 | demo/ 6 | .vscode 7 | .DS_Store 8 | .history/ -------------------------------------------------------------------------------- /src/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './localStorage'; 2 | export * from './sessionStorage'; 3 | export * from './cookie'; -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 120, 4 | "semi": true, 5 | "singleQuote": true, 6 | "arrowParens": "avoid" 7 | } -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import DefaultTheme from "vitepress/theme"; 2 | import "./custom.css"; 3 | 4 | export default DefaultTheme; 5 | -------------------------------------------------------------------------------- /src/string/money.ts: -------------------------------------------------------------------------------- 1 | 2 | // 不要重复造轮子,要学会使用 3 | // 特点:130 bytes,它比 UUID 快 60% 4 | // https://github.com/ai/nanoid/blob/main/README.zh-CN.md 5 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export { default as keyCode } from './keycode' 2 | export { default as dayJs } from './dayjs' 3 | export { default as copyJs } from './copy' 4 | export { default as importPlugin } from './importPlugin' -------------------------------------------------------------------------------- /src/objects/isObject.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为对象 3 | * @param {any} data 要判断的值 4 | * @returns {boolean} 是否为对象 5 | */ 6 | export function isObject(data: any): boolean { 7 | return Object.prototype.toString.call(data) === '[object Object]'; 8 | } -------------------------------------------------------------------------------- /src/browser/isBrowser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func isBrowser 3 | * @returns {boolean} 4 | * @desc 检测代码是否运行在浏览器环境 5 | * @example if (isBrowser()) {...} 6 | */ 7 | 8 | export const isBrowser: boolean = typeof window === 'object' && typeof document === 'object'; 9 | -------------------------------------------------------------------------------- /docs/guide/Tools.md: -------------------------------------------------------------------------------- 1 | # Tools 2 | ## Print 3 | 打印 4 | ```typescript 5 | /** 6 | * @Class Print 7 | * @desc 打印 8 | * @param { string } selector 选择器 9 | * @param { any } options 参数 10 | * @return { Object } { d, h, m, s } 天 时 分 秒 11 | * @example Print('#print', {}) 12 | */ 13 | ``` -------------------------------------------------------------------------------- /src/browser/isSupportWebP.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func isSupportWebP 3 | * @desc 判断浏览器是否支持webP格式图片 4 | * @returns {boolean} 5 | * @example if (isSupportWebP()) {...} 6 | */ 7 | export const isSupportWebP = (): boolean => { 8 | return !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0; 9 | } -------------------------------------------------------------------------------- /src/browser/getClientWidth.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @func getClientWidth 4 | * @returns {number} 5 | * @desc 📝 获取可视窗口的高度 6 | * @example const clientW = getClientWidth(); 7 | */ 8 | export const getClientWidth = (): number => { 9 | return (document.compatMode == "BackCompat" ? document.body : document.documentElement).clientWidth; 10 | } -------------------------------------------------------------------------------- /src/browser/index.ts: -------------------------------------------------------------------------------- 1 | export * from './isBrowser'; 2 | export * from './getBrowserInfo'; 3 | export * from './isSupportWebP'; 4 | export * from './scrollToTop'; 5 | export * from './scrollToBottom'; 6 | export * from './smoothScroll'; 7 | export * from './getClientHeight'; 8 | export * from './toFullScreen'; 9 | export * from './exitFullscreen'; 10 | -------------------------------------------------------------------------------- /src/browser/scrollToTop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func scrollToTop 3 | * @returns {void} 4 | * @desc 📝 平滑滚动到顶部 5 | * @example scrollToTop(); 6 | */ 7 | export const scrollToTop = (): void => { 8 | const c = document?.documentElement?.scrollTop ?? document?.body?.scrollTop; 9 | if (c > 0) { 10 | window.requestAnimationFrame(scrollToTop); 11 | window.scrollTo(0, c - c / 8); 12 | } 13 | } -------------------------------------------------------------------------------- /src/string/nanoid.ts: -------------------------------------------------------------------------------- 1 | // import { nanoid } from 'nanoid' 2 | // 不要重复造轮子,要学会使用 3 | // 特点:130 bytes,它比 UUID 快 60% 4 | // https://github.com/ai/nanoid/blob/main/README.zh-CN.md 5 | // const uuid = nanoid() 6 | const nanoid = (t = 21) => crypto.getRandomValues(new Uint8Array(t)).reduce(((t, e) => t += (e &= 63) < 36 ? e.toString(36) : e < 62 ? (e - 26).toString(36).toUpperCase() : e > 62 ? "-" : "_"), ""); 7 | export { nanoid }; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './browser'; 3 | export * from './string'; 4 | export * from './regex'; 5 | export * from './device'; 6 | export * from './time'; 7 | export * from './url'; 8 | export * from './string'; 9 | export * from './number'; 10 | export * from './function'; 11 | export * from './random'; 12 | export * from './tools'; 13 | export * from './plugins'; 14 | export * from './dom'; 15 | export * from './log'; 16 | 17 | -------------------------------------------------------------------------------- /src/plugins/dayjs.ts: -------------------------------------------------------------------------------- 1 | import importPlugin from './importPlugin' 2 | /** 3 | * @func dayJs 4 | * @desc dayjs日期格式化 5 | * @github https://github.com/iamkun/dayjs/blob/dev/docs/zh-cn/README.zh-CN.md 6 | * @example dayJs().format('YYYY-MM-DD') 7 | */ 8 | const dayJs = async () => { 9 | return await importPlugin( 10 | 'https://unpkg.com/dayjs@1.11.3/dayjs.min.js', 11 | 'dayjs' 12 | ) 13 | } 14 | export default dayJs 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/guide/Event.md: -------------------------------------------------------------------------------- 1 | # Event 2 | 3 | ## getDeviceId 4 | 事件订阅发布及调度中心 5 | ```typescript 6 | /** 7 | * @func mitt 8 | * @desc 事件订阅发布及调度中心 9 | * @return { Object } 事件订阅发布及调度中心 10 | * @github 三方库地址:https://github.com/developit/mitt 11 | * @example 12 | * const emitter = mitt(); 13 | * emitter.on('foo', e => console.log('foo', e) )// 事件监听 14 | * function onFoo() {} 15 | * emitter.on('foo', onFoo) // listen 16 | * emitter.off('foo', onFoo) // unlisten 17 | */ 18 | ``` -------------------------------------------------------------------------------- /src/browser/scrollToBottom.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func scrollToBottom 3 | * @returns {void} 4 | * @desc 📝 平滑滚动到底部 5 | * @example scrollToBottom(); 6 | */ 7 | export const scrollToBottom = (): void => { 8 | const c = document?.documentElement?.scrollTop ?? document?.body?.scrollTop; 9 | const d = document?.documentElement?.scrollHeight ?? document?.body?.scrollHeight; 10 | if (c < d) { 11 | window.requestAnimationFrame(scrollToBottom); 12 | window.scrollTo(0, c + c / 8); 13 | } 14 | } -------------------------------------------------------------------------------- /src/plugins/copy.ts: -------------------------------------------------------------------------------- 1 | import importPlugin from './importPlugin' 2 | /** 3 | * @func copyJs 4 | * @desc 复制文本到剪贴板 5 | * @example const copy = new copyJs() 6 | * @returns {promise} 7 | * @github https://github.com/zenorocha/clipboard.js 8 | * @size 3kb 9 | * 10 | */ 11 | 12 | const copyJs = async (): Promise => { 13 | return await importPlugin( 14 | 'https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.10/clipboard.min.js', 15 | 'ClipboardJS', 16 | ) 17 | } 18 | export default copyJs 19 | -------------------------------------------------------------------------------- /src/browser/toFullScreen.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func toFullScreen 3 | * @returns {void} 4 | * @desc 📝 全屏 5 | * @example toFullScreen(); 6 | */ 7 | export const toFullScreen = (): void => { 8 | let element: any = document.body; 9 | if (element.requestFullscreen) { 10 | element.requestFullscreen() 11 | } else if (element.mozRequestFullScreen) { 12 | element.mozRequestFullScreen() 13 | } else if (element.msRequestFullscreen) { 14 | element.msRequestFullscreen() 15 | } else if (element.webkitRequestFullscreen) { 16 | element.webkitRequestFullScreen() 17 | } 18 | } -------------------------------------------------------------------------------- /src/browser/exitFullscreen.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func exitFullScreen 3 | * @returns {void} 4 | * @desc 📝 退出全屏 5 | * @example exitFullScreen(); 6 | */ 7 | export const exitFullScreen = (): void => { 8 | if (document.exitFullscreen) { 9 | document.exitFullscreen() 10 | } else if ((document as any).msExitFullscreen) { 11 | (document as any).msExitFullscreen() 12 | } else if ((document as any).mozCancelFullScreen) { 13 | (document as any).mozCancelFullScreen() 14 | } else if ((document as any).webkitExitFullscreen) { 15 | (document as any).webkitExitFullscreen() 16 | } 17 | } -------------------------------------------------------------------------------- /docs/guide/Canvas.md: -------------------------------------------------------------------------------- 1 | # Canvas 2 | ## getImageBase64Url 3 | 获取图片base64Url 4 | ```typescript 5 | /** 6 | * @func getImageBase64Url 7 | * @desc 获取图片base64Url 8 | * @param { HTMLImageElement } image img标签 9 | * @returns { string } 图片base64Url 10 | * @example const base64Url = getImageBase64Url(imgdom) 11 | */ 12 | ``` 13 | ## changeColorToRGBA 14 | 将颜色色值转rgba 15 | ```typescript 16 | /** 17 | * @func changeColorToRGBA 18 | * @desc 将颜色色值转rgba 19 | * @param { string } color 16进制色值 20 | * @return { object } rgba对象 21 | * @example const rgba = changeColorToRGBA("#FFFFFF") 22 | */ 23 | ``` 24 | -------------------------------------------------------------------------------- /src/browser/openNewTag.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func openNewTag 3 | * @param {String} url 4 | * @param {Number} tagType? 5 | * @returns {void} 6 | * @desc 打开新标签页 7 | */ 8 | export function openNewTag(url: string, tagType: number = 1) { 9 | // 新建标签页打开 10 | if (tagType == 1) { 11 | window.open(url, '_blank'); 12 | } else if (tagType == 2) { //新建无状态小窗口打开 13 | window.open(url, "_blank", "width=800,height=600,top=150,left=300,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,modal=false,alwaysRaised=yes"); 14 | } else { // UUOA内嵌页面打开 15 | // todo 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # 忽略目录 2 | .DS_Store 3 | public/ 4 | examples/ 5 | packages/ 6 | basic/ 7 | test/ 8 | coverage/ 9 | 10 | # 忽略指定文件 11 | .gitignore 12 | babel.config.js 13 | jest.config.ts 14 | *.map 15 | .prettierrc 16 | rollup.config.js 17 | pnpm-lock.yaml 18 | WORKFLOW.md 19 | index.ts 20 | .npmignore 21 | .babelrc 22 | tsconfig.json 23 | test.js 24 | demo/ 25 | 26 | 27 | # local env files 28 | .env.local 29 | .env.*.local 30 | 31 | # Log files 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Editor directories and files 37 | .idea 38 | .vscode 39 | *.suo 40 | *.ntvs* 41 | *.njsproj 42 | *.sln 43 | *.sw? -------------------------------------------------------------------------------- /src/dom/resize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @desc H5软键盘缩回、弹起回调 4 | * 当软件键盘弹起会改变当前 window.innerHeight,监听这个值变化 5 | * @param {Function} downCb 当软键盘弹起后,缩回的回调 6 | * @param {Function} upCb 当软键盘弹起的回调 7 | */ 8 | 9 | export const windowResize = (downCb: any, upCb: any) => { 10 | let clientHeight = window.innerHeight; 11 | downCb = typeof downCb === 'function' ? downCb : function () { } 12 | upCb = typeof upCb === 'function' ? upCb : function () { } 13 | window.addEventListener('resize', () => { 14 | let height = window.innerHeight; 15 | if (height === clientHeight) { 16 | downCb(); 17 | } 18 | if (height < clientHeight) { 19 | upCb(); 20 | } 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/log.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func log 3 | * @desc 📝 Logs a message to the console. 4 | * @example log.info('Hello world'); log.error('Oh no!'); 5 | */ 6 | 7 | export const log = { 8 | info: (...args: any[]) => console.log(`%c%s`, 'color: #9E9E9E', ...args), 9 | error: (...args: any[]) => console.log(`%c%s`, 'color: #d81e06', ...args), 10 | warn: (...args: any[]) => console.log(`%c%s`, 'color: #ffc107', ...args), 11 | debug: (...args: any[]) => console.log(`%c%s`, 'color: #2196f3', ...args), 12 | success: (...args: any[]) => console.log(`%c%s`, 'color: #4caf50', ...args), 13 | color: (color: string) => (...args: any[]) => console.log(`%c%s`, `color: ${color}`, ...args) as any, 14 | }; -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | title: FasTool 5 | titleTemplate: 一个短小而精悍的现代JavaScript使用工具库 6 | 7 | hero: 8 | name: FasTool 9 | text: 一个短小而精悍的现代JavaScript使用工具库 10 | actions: 11 | - theme: brand 12 | text: Get Started 13 | link: /guide/ 14 | - theme: alt 15 | text: View on GitHub 16 | link: https://github.com/tobe-fe-dalao/fastool 17 | 18 | features: 19 | - title: TypeScript 20 | details: 基于 TypeScript 编写,Rollup打包的一款现代 JS 函数类库 21 | - title: ES6+ 22 | details: 适用于现代浏览器,基于ES6+的JavaScript规范,聚焦业务而非fn 23 | - title: Convenient 24 | details: 包含动态插件库,可以随时调用,做随手可得且最全的工具库 25 | - title: More 26 | details: 加入我们,更多能力等你挖掘.... 27 | 28 | --- 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", // 编译后的es版本 4 | "module": "esnext", // 前端模块化规范 5 | "allowJs": true, // 允许引入js文件 6 | "strict": true, // 开启严格模式 7 | "baseUrl": "./", 8 | "importHelpers": true, 9 | "moduleResolution": "node", 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "resolveJsonModule": true, 15 | "sourceMap": true, 16 | "declaration": true, 17 | "lib": ["ES2015", "DOM", "es5", "es2015.promise", "es2015", "es2017", "esnext"] 18 | }, 19 | "exclude": ["node_modules/**"], 20 | "include": ["src/**/*", "index.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /src/plugins/keycode.ts: -------------------------------------------------------------------------------- 1 | import importPlugin from './importPlugin' 2 | /** 3 | * @func keyCode 4 | * @desc 监听键盘按键类库 5 | * @github https://github.com/jamiebuilds/tinykeys 6 | * @example keyCode(window, { 7 | * "Shift+D": () => { 8 | * alert("The 'Shift' and 'd' keys were pressed at the same time") 9 | * }, 10 | */ 11 | // 注入模块的声明定义 需要自己定义修改 12 | interface ITinyKey { 13 | default: any 14 | createKeybindingsHandler: (options: any) => Function 15 | parseKeybinding: (keyName: string) => Array 16 | } 17 | const keyCode = async () => { 18 | return await importPlugin( 19 | 'https://unpkg.com/tinykeys@latest/dist/tinykeys.module.js', 20 | 'tinykeys', 21 | ) 22 | } 23 | export default keyCode 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/guide/Cookie.md: -------------------------------------------------------------------------------- 1 | # Cookie 2 | ## setCookie 3 | 🧿设置cookie 4 | ```typescript 5 | /** 6 | * @func setCookie 7 | * @desc 设置cookie 8 | * @param { string } key 键名 9 | * @param { string } value 键值 10 | * @param { number } expire 过期时间记录(expires) 11 | * @example setCookie('a', 'b', 500) 12 | */ 13 | ``` 14 | ## getCookie 15 | 🧿获取cookie 16 | ```typescript 17 | /** 18 | * @func getCookie 19 | * @desc 获取cookie 20 | * @param { string } key 键名 21 | * @return { Array | string | undefined } cookie值 22 | * @example const val = getCookie('a') 23 | */ 24 | ``` 25 | ## clearCookie 26 | 🧿清除cookie 27 | ```typescript 28 | /** 29 | * @func clearCookie 30 | * @desc 清除cookie 31 | * @param { string } key 需要清除的键名 32 | * @example clearCookie('a') 33 | */ 34 | ``` 35 | -------------------------------------------------------------------------------- /src/browser/smoothScroll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func smoothScroll 3 | * @param {number} top 滚动到的位置 4 | * @param {number} duration 滚动的时间 5 | * @returns {void} 6 | * @desc 📝 平滑滚动到指定位置 7 | * @example smoothScroll(0, 1000); 8 | */ 9 | export const smoothScroll = (to: number, duration: number = 300): void => { 10 | const start = document?.documentElement?.scrollTop ?? document?.body?.scrollTop; 11 | const change = to - start; 12 | const startDate = +new Date(); 13 | const tick = (): void => { 14 | const now = +new Date(); 15 | const val = Math.min(1, (now - startDate) / duration); 16 | window.scrollTo(0, start + change * val); 17 | if (val < 1) { 18 | window.requestAnimationFrame(tick); 19 | } 20 | } 21 | window.requestAnimationFrame(tick); 22 | } -------------------------------------------------------------------------------- /docs/guide/Random.md: -------------------------------------------------------------------------------- 1 | # Random 2 | ## randomColor 3 | 随机生成颜色 4 | ```typescript 5 | /** 6 | * @func randomColor 7 | * @desc 随机生成颜色 8 | * @return { string } 随机色 9 | * @example const color = randomColor() 10 | */ 11 | ``` 12 | ## randomInt 13 | 生成指定范围[min, max]的随机数 14 | ```typescript 15 | /** 16 | * @func randomInt 17 | * @desc 生成指定范围[min, max]的随机数 18 | * @param { number } min 最小值 19 | * @param { number } max 最大值 20 | * @return { number } 随机数 21 | * @example const num = randomInt(0, 10) 22 | */ 23 | ``` 24 | ## randomIP 25 | 生成一个随机的IP地址,可以是IPv4或者IPv6 26 | ```typescript 27 | /** 28 | * @func randomIP 29 | * @desc 生成一个随机的IP地址,可以是IPv4或者IPv6 30 | * @param { number } type 0: ipv4; 1: ipv6 31 | * @return { string } ip地址 32 | * @example const ip = randomIP(1) 33 | */ 34 | ``` -------------------------------------------------------------------------------- /src/browser/getClientHeight.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func getClientHeight 3 | * @returns {number} 4 | * @desc 📝 获取可视窗口的高度 5 | * @example getClientHeight(); 6 | */ 7 | export const getClientHeight = (): number => { 8 | // return document?.documentElement?.clientHeight ?? document?.body?.clientHeight ?? 0; 9 | let clientHeight = 0; 10 | if (document.body.clientHeight && document.documentElement.clientHeight) { 11 | clientHeight = (document.body.clientHeight < document.documentElement.clientHeight) ? document.body.clientHeight : document.documentElement.clientHeight; 12 | } 13 | else { 14 | clientHeight = (document.body.clientHeight > document.documentElement.clientHeight) ? document.body.clientHeight : document.documentElement.clientHeight; 15 | } 16 | return clientHeight; 17 | } 18 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # 什么是Fastool? 2 | [FasTool](https://tobe-fe-dalao.github.io/fastool/),是一个短小而精悍的现代JavaScript使用工具库 3 | ::: warning 4 | FasTool 当前仍在 `开发中` 状态. 它已经构建开箱即用的api使用,但文档和API可能仍会在一些版本之间发生变化。 5 | ::: 6 | ## 动机 7 | 我们在开发业务中总是不得不使用一些方法来提高我们的效率,随着生态越来越完善,高效的工具层出不穷。我们很难记住每一个工具的存在从而降低我们的开发效率。 8 | 9 | 另外随着ESM规范被越来越多的现代浏览器所支持,我们迫切希望拥有一个轻量级,适合现代浏览器,能够像查字典一样根据具体的业务调用函数或工具。 10 | 11 | 所以,我们发起了FasTool,系统能够为前端开发者们提供基础且全面的工具库 12 | 13 | ## 特点 14 | 我们做了一些事情,让其更加先进... 15 | 16 | ### 通俗易懂的文档 17 | - 基于最新VitePress构建,更快,更便捷 18 | - 更快的热更新 19 | ### 更轻量级,更易扩展 20 | - 使用Rollup打包 21 | - 动态插件,你可以一分钟内挂载你喜欢插件 22 | - 开启服务端压缩,在200k以内 23 | ### 基于TypeScript 24 | - 适用于更多的场景 25 | - 包含基础类库,删除冗余函数和废弃的API 26 | - 附带详细的注释(func/desc/returns/example...) 27 | 28 | # 它是所谓高级前端者的又一个轮子吗? 29 | 它或许有一些Fn涉及了底层的方法,它或许是整理了一些常用的工具,但它也应该是高级开发者不屑重写的Code 30 | 31 | 关于这个话题的讨论[正在进行中](https://github.com/tobe-fe-dalao/fastool/discussions/19)。如果你有兴趣喷,请加入我们。 -------------------------------------------------------------------------------- /docs/guide/EditMd.md: -------------------------------------------------------------------------------- 1 | # 一起写文档 2 | 欢迎大家一起写文档,我们总在寻找一种高效的协作方式,尽可能将维护成本降到最低。 3 | ## 第一步:注册路由 4 | 按照我们约定的类目在`docs/.vitepress/theme/config.ts`文件中`sidebarGuide`下创建一级菜单(如果已存在则直接跳过) 5 | 然后关联你所负责的文档文件,即可开始编写文档 6 | ```javascript 7 | { 8 | text: '本地存储', // 一级目录路由 9 | items: [ 10 | { text: 'Storage操作', link: '/guide/Storage' }, //二级目录路由 11 | { text: 'Cookie操作', link: '/guide/Cookie' }, 12 | ] 13 | } 14 | 15 | ``` 16 | ## 第二步:编写规范 17 | 我们为了减少维护成本,约束代码规范,标题为:`Function`命名,内容为中文解释 18 | 代码块紧跟其后,代码块内容必须为`Function`的注释,便于后期直接替换 19 | ```typescript 20 | // 这是一个完整规范 21 | ## observeProxy //标题 22 | 设计一个对象的观测者-proxy方案 //注释 23 | /** 24 | * @func observeProxy 25 | * @desc 设计一个对象的观测者-proxy方案 26 | * @param { Object } obj 对象 27 | * @return { cal } 观测对象回调方法 28 | * @github git链接 29 | * @example let obj = observeProxy({name:'alex',age:18},callback) 30 | * @size 插件大小 31 | */ 32 | ``` 33 | 34 | ## 第三步:运行检查 35 | 运行文档,检查错别字等问题 36 | ``` 37 | yarn docs:dev 38 | ``` -------------------------------------------------------------------------------- /examples/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example 6 | 7 | 25 | 26 | 27 | 28 |

Hello World!

29 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /docs/guide/Number.md: -------------------------------------------------------------------------------- 1 | # Number 2 | ## digitUppercase 3 | 现金额转大写 4 | ```typescript 5 | /** 6 | * @func digitUppercase 7 | * @desc 现金额转大写 8 | * @param { number } n 金额 9 | * @return { string } 大写金额 10 | * @example digitUppercase(123456789.123) 11 | */ 12 | ``` 13 | ## intToChinese 14 | 数字转中文 15 | ```typescript 16 | /** 17 | * @func intToChinese 18 | * @desc 数字转中文 19 | * @param { number } value 数字 20 | * @return { string } 中文 21 | * @example intToChinese(123456789) 22 | */ 23 | ``` 24 | ## sumAverage 25 | 计算数组平均值 26 | ```typescript 27 | /** 28 | * @func sumAverage 29 | * @desc 计算数组平均值 30 | * @param { number[] } numberArr 数字列表 31 | * @return { number } 计算出的平均值 32 | * @example sumAverage(123456789) 33 | */ 34 | ``` 35 | ## getDistance 36 | 计算两坐标点之间的距离 37 | ```typescript 38 | /** 39 | * @func getDistance 40 | * @desc 计算两坐标点之间的距离 41 | * @param { object } point1 坐标1 42 | * @param { object } point2 坐标2 43 | * @return { number } 距离 44 | * @example const dis = getDistance({x:1,y:2},{x:3,y:4}) // 2.8284271247461903 45 | */ 46 | ``` 47 | -------------------------------------------------------------------------------- /src/polyfill/array.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @func polyfillArrayEvery 4 | * @dec 允许在那些没有原生支持 every 的实现环境中使用它。 5 | * @returns {Boolean} 6 | */ 7 | export function polyfillArrayEvery() { 8 | if (!Array.prototype.every) { 9 | Array.prototype.every = function (callbackfn: Function, thisArg: any) { 10 | 'use strict'; 11 | let T, k = 0; 12 | if (this == null) { 13 | throw new TypeError('this is null or not defined'); 14 | } 15 | let O = Object(this); 16 | let len = O.length >>> 0; 17 | if (typeof callbackfn !== 'function') { 18 | throw new TypeError(); 19 | } 20 | if (arguments.length > 1) { 21 | T = thisArg; 22 | } 23 | while (k < len) { 24 | 25 | var kValue; 26 | if (k in O) { 27 | kValue = O[k]; 28 | var testResult = callbackfn.call(T, kValue, k, O); 29 | if (!testResult) { 30 | return false; 31 | } 32 | } 33 | k++; 34 | } 35 | return true; 36 | }; 37 | } 38 | } -------------------------------------------------------------------------------- /src/storage/localStorage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func setLocalStorage 3 | * @param {string} key 4 | * @param {string} value 5 | * @returns {void} 6 | * @desc 设置localStorage 7 | * @example setLocalStorage('key', 'value'); 8 | */ 9 | export const setLocalStorage = (key: string, value: string): void => { 10 | if (!key) return; 11 | if (typeof value !== 'string') { 12 | value = JSON.stringify(value); 13 | } 14 | window.localStorage.setItem(key, value); 15 | } 16 | 17 | /** 18 | * @func getLocalStorage 19 | * @param {string} key 20 | * @returns {string} 21 | * @desc 获取localStorage 22 | * @example getLocalStorage('key'); 23 | */ 24 | export const getLocalStorage = (key: string): any => { 25 | if (!key) return; 26 | return window.localStorage.getItem(key); 27 | } 28 | 29 | /** 30 | * @func delLocalStorage 31 | * @param {string} key 32 | * @returns {any} 33 | * @desc 获取localStorage 34 | * @example delLocalStorage('key'); 35 | */ 36 | export const delLocalStorage = (key: string): any => { 37 | if (!key) return; 38 | window.localStorage.removeItem(key); 39 | }; 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/guide/Device.md: -------------------------------------------------------------------------------- 1 | # Device 2 | ## getOS 3 | 🧿获取操作系统类型 4 | ```typescript 5 | /** 6 | * @func getOS 7 | * @desc 获取操作系统类型 8 | * @return { string } 操作系统 9 | * @example const device = getOS() 10 | */ 11 | ``` 12 | ## isWeiXin 13 | 🧿是否是微信 14 | ```typescript 15 | /** 16 | * @func isWeiXin 17 | * @desc 是否是微信 18 | * @return { boolean } 是否微信 19 | * @example const iswx = isWeiXin() 20 | */ 21 | ``` 22 | ## isMobile 23 | 🧿是否是移动端 24 | ```typescript 25 | /** 26 | * @func isMobile 27 | * @desc 是否是移动端 28 | * @return { boolean } 是否移动端 29 | * @example const ismobile = isMobile() 30 | */ 31 | ``` 32 | ## isSupportCamera 33 | 🧿是否支持摄像头 34 | ```typescript 35 | /** 36 | * @func isSupportCamera 37 | * @desc 是否支持摄像头 38 | * @return { boolean } 是否支持摄像头 39 | * @example const iscamera = isSupportCamera() 40 | */ 41 | ``` 42 | ## getDeviceId 43 | 🧿生成设备唯一ID 44 | ```typescript 45 | /** 46 | * @func getDeviceId 47 | * @desc 生成设备唯一ID 48 | * @return { Promise } 设备唯一ID 49 | * @github 三方库地址:https://github.com/fingerprintjs/fingerprintjs 50 | * @example const iscamera = getDeviceId() 51 | */ 52 | ``` 53 | -------------------------------------------------------------------------------- /src/storage/sessionStorage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func setSessionStorage 3 | * @param {string} key 4 | * @param {string} value 5 | * @returns {any} 6 | * @desc 设置sessionStorage 7 | * @example setSessionStorage('key', 'value'); 8 | */ 9 | export const setSessionStorage = (key: string, value: string) => { 10 | if (!key) return; 11 | if (typeof value !== 'string') { 12 | value = JSON.stringify(value); 13 | } 14 | window.sessionStorage.setItem(key, value) 15 | }; 16 | 17 | /** 18 | * @func getSessionStorage 19 | * @param {string} key 20 | * @returns {any} 21 | * @desc 获取sessionStorage 22 | * @example getSessionStorage('key'); 23 | */ 24 | export const getSessionStorage = (key: string): any => { 25 | if (!key) return; 26 | return window.sessionStorage.getItem(key) 27 | }; 28 | 29 | 30 | /** 31 | * @func delSessionStorage 32 | * @param {string} key 33 | * @returns {any} 34 | * @desc 删除sessionStorage 35 | * @example delSessionStorage('key'); 36 | */ 37 | export const delSessionStorage = (key: string) => { 38 | if (!key) return; 39 | window.sessionStorage.removeItem(key) 40 | }; 41 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deployment 2 | 3 | on: 4 | push: 5 | branches: [main] # master 分支有 push 时触发 6 | paths-ignore: # 下列文件的变更不触发部署,可以自行添加 7 | - README.md 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # 下载源码 14 | # 这一步就是检出你的仓库并下载里面的代码到runner中,actions/checkout@v2是官方自己造的轮子,直接拿来用就行 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | # 打包构建 19 | - name: Build 20 | uses: actions/setup-node@master 21 | with: 22 | node-version: "16.x" 23 | - run: yarn install # 安装依赖 24 | - run: yarn docs:build # 打包 25 | 26 | # 部署到 GitHub pages 27 | - name: Deploy 28 | uses: peaceiris/actions-gh-pages@v3 # 使用部署到 GitHub pages 的 action 29 | with: 30 | publish_dir: ./docs/.vitepress/dist/ # 部署打包后的 dist 目录 31 | github_token: ${{ secrets.DESIGN_SECRET }} # secret 名 32 | user_name: ${{ secrets.MY_USER_NAME }} 33 | user_email: ${{ secrets.MY_USER_EMAIL }} 34 | commit_message: 自动部署流水线:https://tobe-fe-dalao.github.io/fastool # 部署时的 git 提交信息,自由填写 35 | -------------------------------------------------------------------------------- /src/browser/getBrowserInfo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func getBrowserInfo 3 | * @returns {String} 浏览器类型和版本 4 | * @desc 📝 获取浏览器类型和版本 5 | * @example const browserInfo = getBrowserInfo(); 6 | */ 7 | export const getBrowserInfo = (): object => { 8 | let t = navigator.userAgent.toLowerCase() as any; 9 | return 0 <= t.indexOf("msie") ? { //ie < 11 10 | type: "IE", 11 | version: Number(t.match(/msie ([\d]+)/)[1]) 12 | } : !!t.match(/trident\/.+?rv:(([\d.]+))/) ? { // ie 11 13 | type: "IE", 14 | version: 11 15 | } : 0 <= t.indexOf("edge") ? { 16 | type: "Edge", 17 | version: Number(t!.match(/edge\/([\d]+)/)[1]) 18 | } : 0 <= t.indexOf("firefox") ? { 19 | type: "Firefox", 20 | version: Number(t.match(/firefox\/([\d]+)/)[1]) 21 | } : 0 <= t.indexOf("chrome") ? { 22 | type: "Chrome", 23 | version: Number(t.match(/chrome\/([\d]+)/)[1]) 24 | } : 0 <= t.indexOf("opera") ? { 25 | type: "Opera", 26 | version: Number(t.match(/opera.([\d]+)/)[1]) 27 | } : 0 <= t.indexOf("Safari") ? { 28 | type: "Safari", 29 | version: Number(t.match(/version\/([\d]+)/)[1]) 30 | } : { 31 | type: t, 32 | version: -1 33 | } 34 | } -------------------------------------------------------------------------------- /src/storage/cookie.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取 cookie 值 3 | * @param {string} key cookie 名称 4 | * @returns {string} cookie 值 5 | */ 6 | export const getCookie = (key: string): string => { 7 | const cookies = document.cookie.split(';'); 8 | for (let i = 0; i < cookies.length; i++) { 9 | const cookie = cookies[i].trim(); 10 | if (cookie.startsWith(`${key}=`)) { 11 | return cookie.substring(key.length + 1); 12 | } 13 | } 14 | return ''; 15 | }; 16 | 17 | /** 18 | * 设置 cookie 19 | * @param {string} key cookie 名称 20 | * @param {string} value cookie 值 21 | * @param {number} expire 过期时间(单位:毫秒,默认 1 天) 22 | */ 23 | export const setCookie = (key = '', value = '', expire = 86400000): void => { 24 | if (typeof key !== 'string' || typeof value !== 'string' || typeof expire !== 'number') { 25 | throw new TypeError('Invalid arguments'); 26 | } 27 | const d = new Date(); 28 | d.setTime(d.getTime() + expire); 29 | document.cookie = `${key}=${value};expires=${d.toUTCString()}`; 30 | }; 31 | 32 | /** 33 | * 删除 cookie 34 | * @param {string} key cookie 名称 35 | */ 36 | export const deleteCookie = (key: string): void => { 37 | setCookie(key, '', -1); 38 | }; -------------------------------------------------------------------------------- /src/function/mergeObj.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否为空对象 3 | * @param obj 参数 4 | * @returns {boolean} 5 | */ 6 | 7 | const isObj = (obj: any): boolean => { 8 | return obj !== null && typeof obj === 'object' && !Array.isArray(obj) 9 | } 10 | /** 11 | * @func mergeObject 12 | * @param {object} obj1 外部的参数对象 13 | * @param {object} obj2 默认参数对象 14 | * @desc 深拷贝合并对象 15 | * @returns {Object} 16 | * @example mergeObject(obj1, obj2) 17 | */ 18 | export const mergeObject = (obj1: any, obj2: Object): Object => { 19 | // 如果没有传参,返回默认值 20 | if (!isObj(obj1)) { 21 | return mergeObject({}, obj2); 22 | } 23 | // 如果没有默认,返回传参 24 | if (!isObj(obj2)) { 25 | return mergeObject({}, obj1); 26 | } 27 | // 定义一个以默认值为基础的新对象 28 | let newObj = Object.assign({}, obj2) 29 | // 遍历传参对象 30 | Object.keys(obj1).forEach(function (key) { 31 | let val = obj1[key] 32 | if (key === '__proto__' || key === 'constructor') { 33 | return 34 | } 35 | if (val === null) { 36 | return 37 | } 38 | // 如果传参对象中的值为对象,则递归调用 39 | if (isObj(val) && isObj(newObj[key])) { 40 | newObj[key] = mergeObject(val, newObj[key]) 41 | } else { 42 | newObj[key] = val 43 | } 44 | }) 45 | return newObj 46 | } 47 | -------------------------------------------------------------------------------- /src/polyfill/string.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func polyfillStringIncludes 3 | * @dec 判断一个字符串是否包含在另一个字符串中 4 | * @returns {Boolean} 5 | */ 6 | export function polyfillStringIncludes() { 7 | if (!String.prototype.includes) { 8 | String.prototype.includes = function (search, start) { 9 | 'use strict'; 10 | if (typeof start !== 'number') { 11 | start = 0; 12 | } 13 | 14 | if (start + search.length > this.length) { 15 | return false; 16 | } else { 17 | return this.indexOf(search, start) !== -1; 18 | } 19 | }; 20 | } 21 | } 22 | 23 | /** 24 | * @func polyfillStringEndsWith 25 | * @dec 判断当前字符串是否是以另外一个给定的子字符串“结尾”的 26 | * @returns {Boolean} 27 | */ 28 | export function polyfillStringEndsWith() { 29 | if (!String.prototype.endsWith) { 30 | String.prototype.endsWith = function (search, this_len) { 31 | if (this_len === undefined || this_len > this.length) { 32 | this_len = this.length; 33 | } 34 | return this.substring(this_len - search.length, this_len) === search; 35 | }; 36 | } 37 | } 38 | 39 | /** 40 | * @func polyfillStringTrim 41 | * @dec 从一个字符串的两端删除空白字符 42 | * @returns {void} 43 | */ 44 | export function polyfillStringTrim() { 45 | if (!String.prototype.trim) { 46 | String.prototype.trim = function () { 47 | return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 48 | }; 49 | } 50 | } -------------------------------------------------------------------------------- /src/canvas/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func getImageBase64Url 3 | * @param {HTMLImageElement} image 4 | * @returns {string} 5 | * @desc 获取图片base64Url 6 | * @example getImageBase64Url(image) 7 | */ 8 | export function getImageBase64Url(image: HTMLImageElement): string { 9 | const canvas = document.createElement('canvas'); 10 | canvas.width = image.width; 11 | canvas.height = image.height; 12 | const ctx = canvas.getContext('2d'); 13 | ctx?.drawImage(image, 0, 0, image.width, image.height); 14 | const suffix = image.src.substring(image.src.lastIndexOf('.') + 1).toLowerCase(); 15 | return canvas.toDataURL(`image/${suffix || 'png'}`, 1); 16 | } 17 | 18 | /** 19 | * @func changeColorToRGBA 20 | * @param {string} color 21 | * @returns {object} 22 | * @desc 将颜色色值转rgba 23 | * @example changeColorToRGBA('#fff') => {r: 255, g: 255, b: 255, a: 1} 24 | */ 25 | export function changeColorToRGBA(color: string) { 26 | let canvas = (window as any).canvas; 27 | if (!canvas) { 28 | canvas = document.createElement('canvas'); 29 | canvas.width = 1; 30 | canvas.height = 1; 31 | (window as any).canvas = canvas; 32 | } 33 | const ctx = canvas.getContext('2d'); 34 | ctx.fillStyle = color; 35 | ctx.fillRect(0, 0, 1, 1); 36 | const colorData = ctx.getImageData(0, 0, 1, 1).data; 37 | return { 38 | r: colorData[0], 39 | g: colorData[1], 40 | b: colorData[2], 41 | a: colorData[3], 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/random/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func randomColor 3 | * @desc 随机生成颜色 4 | * @return {String} 5 | * @example randomColor() // '#000000' 6 | */ 7 | export function randomColor(): string { 8 | return '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).slice(-6); 9 | } 10 | 11 | /** 12 | * @func randomInt 13 | * @desc 生成指定范围[min, max]的随机数 14 | * @param {Number} min 15 | * @param {Number} max 16 | * @return {Number} 17 | */ 18 | export function randomInt(min: number, max: number): number { 19 | min = Math.ceil(min) 20 | max = Math.floor(max) 21 | return Math.floor(Math.random() * (max - min + 1)) + min; 22 | } 23 | 24 | /** 25 | * @func randomIP 26 | * @param {number} type - 0: ipv4, 1: ipv6 27 | * @returns {string} - random ip address 28 | * @desc 生成一个随机的IP地址,可以是IPv4或者IPv6 29 | * @example randomIP(1) 30 | */ 31 | 32 | export const randomIP = (type: number = 0): string => { 33 | const ipv4 = randomInt(0, 255) + '.' + randomInt(0, 255) + '.' + randomInt(0, 255) + '.' + randomInt(0, 255); 34 | const ipv6 = 35 | randomInt(0, 65535) + 36 | ':' + 37 | randomInt(0, 65535) + 38 | ':' + 39 | randomInt(0, 65535) + 40 | ':' + 41 | randomInt(0, 65535) + 42 | ':' + 43 | randomInt(0, 65535) + 44 | ':' + 45 | randomInt(0, 65535) + 46 | ':' + 47 | randomInt(0, 65535) + 48 | ':' + 49 | randomInt(0, 65535); 50 | return type ? ipv6 : ipv4; 51 | }; 52 | -------------------------------------------------------------------------------- /docs/images/gradient-left-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/guide/Storage.md: -------------------------------------------------------------------------------- 1 | # LocalStorage 2 | ## setLocalStorage 3 | 🧿设置localStorage 4 | ```typescript 5 | /** 6 | * @func setLocalStorage 7 | * @param {string} key 8 | * @param {string} value 9 | * @returns {void} 10 | * @desc 设置localStorage 11 | * @example setLocalStorage('key', 'value'); 12 | */ 13 | ``` 14 | ## getLocalStorage 15 | 🧿获取localStorage 16 | ```typescript 17 | /** 18 | * @func getLocalStorage 19 | * @param {string} key 20 | * @returns {string} 21 | * @desc 获取localStorage 22 | * @example getLocalStorage('key'); 23 | */ 24 | ``` 25 | 26 | ## delLocalStorage 27 | 🧿删除localStorage 28 | ```typescript 29 | /** 30 | * @func delLocalStorage 31 | * @param {string} key 32 | * @returns {any} 33 | * @desc 获取localStorage 34 | * @example delLocalStorage('key'); 35 | */ 36 | ``` 37 | # SessionStorage 38 | ## setSessionStorage 39 | 🧿设置sessionStorage 40 | ```typescript 41 | /** 42 | * @func setSessionStorage 43 | * @param {string} key 44 | * @param {string} value 45 | * @returns {any} 46 | * @desc 设置sessionStorage 47 | * @example setSessionStorage('key', 'value'); 48 | */ 49 | ``` 50 | ## getSessionStorage 51 | 🧿获取sessionStorage 52 | ```typescript 53 | /** 54 | * @func getSessionStorage 55 | * @param {string} key 56 | * @returns {any} 57 | * @desc 获取sessionStorage 58 | * @example getSessionStorage('key'); 59 | */ 60 | ``` 61 | ## delSessionStorage 62 | 🧿删除sessionStorage 63 | ```typescript 64 | /** 65 | * @func delSessionStorage 66 | * @param {string} key 67 | * @returns {any} 68 | * @desc 删除sessionStorage 69 | * @example delSessionStorage('key'); 70 | */ 71 | ``` 72 | 73 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-c-brand : #7828C8; 3 | --vp-c-green : #7828C8; 4 | --vp-c-green-light : #571D91; 5 | --vp-c-green-lighter: #571D91; 6 | --vp-c-green-dark : #571D91; 7 | --vp-c-green-darker : #571D91; 8 | } 9 | 10 | .dark { 11 | --vp-c-bg : #000; 12 | --vp-code-block-bg: #111; 13 | --vp-c-bg-alt : rgba(0, 0, 0, 0.5) 14 | } 15 | 16 | 17 | @media (min-width: 960px) { 18 | .dark .VPNavBar.has-sidebar .content[data-v-d84f2262] { 19 | background: rgba(36, 36, 36, 0.1); 20 | } 21 | } 22 | 23 | .Layout::before { 24 | content : ""; 25 | background-image : url(../../images/gradient-right-dark.svg); 26 | background-size : 100% 100%; 27 | background-position: right; 28 | background-repeat : no-repeat; 29 | opacity : 1; 30 | max-width : 100%; 31 | height : 100%; 32 | width : 50%; 33 | position : fixed; 34 | top : -20%; 35 | right : -10%; 36 | display : block; 37 | } 38 | 39 | .Layout::after { 40 | content : ""; 41 | background-image : url(../../images/gradient-left-dark.svg); 42 | background-size : 100% 100%; 43 | background-position: right; 44 | background-repeat : no-repeat; 45 | opacity : 1; 46 | max-width : 100%; 47 | height : 100%; 48 | width : 50%; 49 | position : fixed; 50 | bottom : -20%; 51 | left : -10%; 52 | display : block; 53 | z-index : -1; 54 | } 55 | 56 | @media (min-width: 960px) { 57 | .title { 58 | font-size: 24px !important; 59 | } 60 | } -------------------------------------------------------------------------------- /src/function/observe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 使一个对象转化成可观测对象 3 | * @param { Object } obj 对象 4 | * @param { String } key 对象的key 5 | * @param { Any } val 对象的某个key的值 6 | */ 7 | const defineReactive = (obj: Object, key: string, val: any): void => { 8 | Object.defineProperty(obj, key, { 9 | enumerable: true, 10 | configurable: true, 11 | get() { 12 | // console.log(`${key}属性被读取了`); 13 | return val 14 | }, 15 | set(newVal) { 16 | //console.log(`${key}属性被修改了`); 17 | val = newVal 18 | } 19 | }) 20 | } 21 | /** 22 | * 设计一个对象的观测者 23 | * @param { Object } obj 对象 24 | * @return { Object } 返回一个可观测对象 25 | * @example let obj = observerDef({name:'alex',age:18}) 26 | */ 27 | export const observeDef = (obj: Object): Object | undefined => { 28 | if (!obj || typeof obj !== 'object') { 29 | return 30 | } 31 | let keys = Object.keys(obj) 32 | keys.forEach(key => { 33 | defineReactive(obj, key, obj[key]) 34 | }) 35 | return obj 36 | } 37 | 38 | /** 39 | * 设计一个对象的观测者-proxy方案 40 | * @param { Object } obj 对象 41 | * @return { cal } 观测对象回调方法 42 | * @example let obj = observeProxy({name:'alex',age:18},callback) 43 | */ 44 | export const observeProxy = (obj: Object, cal: (val: any) => void) => { 45 | return new Proxy(obj, { 46 | get: function (target, prop) { 47 | //console.log('get调用了') 48 | return Reflect.get(target, prop) 49 | }, 50 | set: function (target, prop, val) { 51 | cal(val) 52 | //console.log('set调用了') 53 | return Reflect.set(target, prop, val) 54 | }, 55 | deleteProperty: function (target, prop) { 56 | //console.log('deleteProperty调用了') 57 | return Reflect.deleteProperty(target, prop) 58 | } 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /docs/guide/URL.md: -------------------------------------------------------------------------------- 1 | # URL 2 | ## getUrlParams 3 | 解析url 获取 url 中所有的参数,以对象的形式返回,如果参数名重复,则以数组的形式返回 4 | ```typescript 5 | /** 6 | * @func getUrlParams 7 | * @desc 📝 解析url 获取 url 中所有的参数,以对象的形式返回,如果参数名重复,则以数组的形式返回 8 | * @param { string } url 9 | * @return { object } url参数 { id: 123, name: menwei } 10 | * @example const param = getUrlParams('www.baidu.com?id=123&name=menwei'); // { id: 123, name: menwei } 11 | */ 12 | ``` 13 | ## setUrlParams 14 | 修改URL参数 15 | ```typescript 16 | /** 17 | * @func setUrlParams 18 | * @desc 📝 修改URL参数 19 | * @param { string } urlKey 参数名 20 | * @param { string } urlValue 参数值 21 | * @param { string } url 原始URL 22 | * @return { string } url 修改后的URL 23 | * @example const link = setUrlParams('id', '123', 'www.baidu.com') // 'www.baidu.com?id=123 24 | */ 25 | ``` 26 | ## delUrlParams 27 | 删除 url 中的参数 28 | ```typescript 29 | /** 30 | * @func delUrlParams 31 | * @desc 📝 删除 url 中的参数 32 | * @param { string } 参数 33 | * @return { string } url 修改后的URL 34 | * @example delUrlParams('id') 35 | */ 36 | ``` 37 | ## paramsJoinUrl 38 | 将参数对象转为 url 字符串 39 | ```typescript 40 | /** 41 | * @func paramsJoinUrl 42 | * @desc 📝 将参数对象转为 url 字符串 43 | * @param { object } 参数对象 44 | * @return { string } url 修改后的URL 45 | * @example const param = paramsJoinUrl({ id: 123, name: 'menwei'}) // '?id=123&name=menwei' 46 | */ 47 | ``` 48 | ## getBaseUrl 49 | 获取 url 中?之前的部分 50 | ```typescript 51 | /** 52 | * @func getBaseUrl 53 | * @desc 📝 获取 url 中?之前的部分 54 | * @param { string } url 原始URL 55 | * @return { string } url 修改后的URL 56 | * @example const link = getBaseUrl('www.baidu.com?id=123&name=menwei') // 'www.baidu.com' 57 | */ 58 | ``` 59 | ## getUrlDomain 60 | 获取 url 中的域名 61 | ```typescript 62 | /** 63 | * @func getUrlDomain 64 | * @desc 📝 获取 url 中的域名 65 | * @param { string } url 原始URL 66 | * @returns { string } url 修改后的URL 67 | * @example const link = getUrlDomain('http://www.baidu.com?id=123&name=menwei') 68 | */ 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/guide/String.md: -------------------------------------------------------------------------------- 1 | # String 2 | ## randomString 3 | 生成随机字符串 4 | ```typescript 5 | /** 6 | * @func randomString 7 | * @desc 生成随机字符串 8 | * @param { number } len 长度 9 | * @return { string } 随机字符串 10 | * @example const str = randomString(15) 11 | */ 12 | ``` 13 | ## firstUpperCase 14 | 首字母大写 15 | ```typescript 16 | /** 17 | * @func firstUpperCase 18 | * @desc 首字母大写 19 | * @param { string } str 需要处理的字符 20 | * @return { string } 处理后的字符 21 | * @example const str = firstUpperCase('abc') 22 | */ 23 | ``` 24 | ## telEncrypt 25 | 手机号中间加密 26 | ```typescript 27 | /** 28 | * @func telEncrypt 29 | * @desc 手机号中间加密 30 | * @param { number } tel 需要处理的手机号 31 | * @return { string } 处理后的手机号 32 | * @example const str = telEncrypt(12345678912) 33 | */ 34 | ``` 35 | ## getKebabCase 36 | 转换成短横线命名 37 | ```typescript 38 | /** 39 | * @func getKebabCase 40 | * @desc 转换成短横线命名 41 | * @param { string } str 需要处理的字符 42 | * @return { string } 处理后的字符 43 | * @example const str = getKebabCase('aBc') 44 | */ 45 | ``` 46 | ## getCamelCase 47 | 转换成驼峰命名 48 | ```typescript 49 | /** 50 | * @func getCamelCase 51 | * @desc 转换成驼峰命名 52 | * @param { string } str 需要处理的字符 53 | * @return { string } 处理后的字符 54 | * @example const str = getCamelCase('a-bc') 55 | */ 56 | ``` 57 | ## getEscapeString 58 | 字符串的转义,将`&`, `<`, `>`, `"`, `'`分别转义为`&`, `<`, `>`, `"`, `'` 59 | ```typescript 60 | /** 61 | * @func getEscapeString 62 | * @desc 字符串的转义,将`&`, `<`, `>`, `"`, `'`分别转义为`&`, `<`, `>`, `"`, `'` 63 | * @param { string } str 需要处理的字符 64 | * @return { string } 处理后的字符 65 | * @example const str = getEscapeString('<') 66 | */ 67 | ``` 68 | ## getUnEscapeString 69 | 字符串的反转义,将`&`, `<`, `>`, `"`, `'`替换为转义前的符号 70 | ```typescript 71 | /** 72 | * @func getUnEscapeString 73 | * @desc 字符串的反转义,将`&`, `<`, `>`, `"`, `'`替换为转义前的符号 74 | * @param { string } str 需要处理的字符 75 | * @return { string } 处理后的字符 76 | * @example const str = getUnEscapeString('&') 77 | */ 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/guide/Browser.md: -------------------------------------------------------------------------------- 1 | # Browser 2 | ## isBrowser 3 | 🧿检测代码是否运行在浏览器环境 4 | ```typescript 5 | /** 6 | * @func isBrowser 7 | * @return {boolean} 8 | * @desc 检测代码是否运行在浏览器环境 9 | * @example if (isBrowser()) {...} 10 | */ 11 | ``` 12 | ## getBrowserInfo 13 | 🧿获取浏览器类型和版本 14 | ```typescript 15 | /** 16 | * @func getBrowserInfo 17 | * @return {String} 浏览器类型和版本 18 | * @desc 📝 获取浏览器类型和版本 19 | * @example const browserInfo = getBrowserInfo(); 20 | */ 21 | ``` 22 | ## isSupportWebP 23 | 🧿判断浏览器是否支持webP格式图片 24 | ```typescript 25 | /** 26 | * @func isSupportWebP 27 | * @desc 判断浏览器是否支持webP格式图片 28 | * @return {boolean} 29 | * @example if (isSupportWebP()) {...} 30 | */ 31 | ``` 32 | ## scrollToTop 33 | 🧿平滑滚动到顶部 34 | ```typescript 35 | /** 36 | * @func scrollToTop 37 | * @return {void} 38 | * @desc 📝 平滑滚动到顶部 39 | * @example scrollToTop(); 40 | */ 41 | ``` 42 | ## scrollToBottom 43 | 🧿平滑滚动到底部 44 | ```typescript 45 | /** 46 | * @func scrollToBottom 47 | * @return {void} 48 | * @desc 📝 平滑滚动到底部 49 | * @example scrollToBottom(); 50 | */ 51 | ``` 52 | ## smoothScroll 53 | 🧿平滑滚动到指定位置 54 | ```typescript 55 | /** 56 | * @func smoothScroll 57 | * @param {number} top 滚动到的位置 58 | * @param {number} duration 滚动的时间 59 | * @return {void} 60 | * @desc 📝 平滑滚动到指定位置 61 | * @example smoothScroll(0, 1000); 62 | */ 63 | ``` 64 | ## getClientHeight 65 | 获取可视窗口的高度 66 | ```typescript 67 | /** 68 | * @func getClientHeight 69 | * @return {number} 70 | * @desc 📝 获取可视窗口的高度 71 | * @example getClientHeight(); 72 | */ 73 | ``` 74 | ## getClientWidth 75 | 🧿获取可视窗口的宽度 76 | ```typescript 77 | /** 78 | * @func getClientWidth 79 | * @return {number} 80 | * @desc 📝 获取可视窗口的高度 81 | * @example const clientW = getClientWidth(); 82 | */ 83 | ``` 84 | ## toFullScreen 85 | 🧿打开全屏 86 | ```typescript 87 | /** 88 | * @func toFullScreen 89 | * @return {void} 90 | * @desc 📝 全屏 91 | * @example toFullScreen(); 92 | */ 93 | ``` 94 | ## exitFullScreen 95 | 🧿退出全屏 96 | ```typescript 97 | /** 98 | * @func exitFullScreen 99 | * @return {void} 100 | * @desc 📝 退出全屏 101 | * @example exitFullScreen(); 102 | */ 103 | ``` -------------------------------------------------------------------------------- /docs/guide/Time.md: -------------------------------------------------------------------------------- 1 | # Time 2 | ## diffDays 3 | 比较两个日期相差的天数 4 | ```typescript 5 | /** 6 | * @func diffDays 7 | * @desc 比较两个日期相差的天数 8 | * @param { Date } date1 日期1 9 | * @param { Date } date2 日期2 10 | * @return { number } 差值(毫秒) 11 | * @example const leftTime = diffDays(date1, date2) 12 | */ 13 | ``` 14 | ## getNowTime 15 | 获取当前时间 16 | ```typescript 17 | /** 18 | * @func getNowTime 19 | * @desc 获取当前时间 20 | * @return { string } 时间字符串(年 月 日 HH:mm:ss) 21 | * @example const time = getNowTime() 22 | */ 23 | ``` 24 | ## formatDate 25 | 格式化时间 26 | ```typescript 27 | /** 28 | * @func formatDate 29 | * @desc 格式化时间 30 | * @param { string } format 格式化的参数 31 | * @param { Date } time 需要格式化的时间 32 | * @return { string } 格式化后的时间字符串 33 | * @example const time = formatDate(yyyy-MM-dd HH:mm:ss) //'2022-07-09 18:01:03' 34 | */ 35 | ``` 36 | ## formatPassTime 37 | 格式化${startTime}距现在的已过时间 38 | ```typescript 39 | /** 40 | * @func formatPassTime 41 | * @desc 格式化${startTime}距现在的已过时间 42 | * @param { Date } startTime 参考时间 43 | * @return { string } 时间差 44 | * @example const formatPassTime = formatPassTime(1657361005773) // "刚刚" 45 | */ 46 | ``` 47 | ## formatRemainTime 48 | 格式化现在距${endTime}的剩余时间 49 | ```typescript 50 | /** 51 | * @func formatRemainTime 52 | * @desc 格式化现在距${endTime}的剩余时间 53 | * @param { Date } endTime 参考时间 54 | * @return { string } 时间差 55 | * @example const formatRemainTime = formatRemainTime(1657361005773) // "1天3小时2分钟16秒" 56 | */ 57 | ``` 58 | ## isLeapYear 59 | 是否为闰年 60 | ```typescript 61 | /** 62 | * @func isLeapYear 63 | * @desc 是否为闰年 64 | * @param { number } year 年份 65 | * @return { boolean } 是否为闰年 66 | * @example if (isLeapYear) { doSomething } 67 | */ 68 | ``` 69 | ## getDaysByMonth 70 | 获取指定日期月份的总天数 71 | ```typescript 72 | /** 73 | * @func getDaysByMonth 74 | * @desc 获取指定日期月份的总天数 75 | * @param { Date } time 日期 76 | * @return { number } 天数 77 | * @example const days = getDaysByMonth(1657361005773) 78 | */ 79 | ``` 80 | ## timeLeft 81 | 倒计时 ${startTime - endTime}的剩余时间,startTime大于endTime时,均返回0 82 | ```typescript 83 | /** 84 | * @func timeLeft 85 | * @desc 倒计时 ${startTime - endTime}的剩余时间,startTime大于endTime时,均返回0 86 | * @param { Date | String } startTime 开始时间 87 | * @param { Date | String } endTime 结束时间 88 | * @return { Object } { d, h, m, s } 天 时 分 秒 89 | * @example const leftTime = timeLeft(startTime, endTime) // { d, h, m, s } 天 时 分 秒 90 | */ 91 | ``` -------------------------------------------------------------------------------- /src/dom/html.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func getFromClipboard 3 | * @desc 📝 获取剪贴板内容 4 | * @returns {Promise} 5 | * @example getFromClipboard().then(res => {...} 6 | */ 7 | export const getFromClipboard = (): Promise => { 8 | return new Promise((resolve, reject) => { 9 | navigator.clipboard.readText() 10 | .then(text => { 11 | resolve(text); 12 | }) 13 | .catch(err => { 14 | reject(err); 15 | }); 16 | }); 17 | }; 18 | /** 19 | * @func removeHTMLTag 20 | * @param {string} str 21 | * @return {string} 22 | * @desc 📝 去掉文本中所有标签,只保留文本 23 | * @example removeHTMLTag('

hello

') // 'hello' 24 | */ 25 | export const removeHTMLTag = (str: string): string => str.replace(/<[^>]+>/g, ''); 26 | 27 | 28 | type objectKeyOnlyCss = { 29 | [k in keyof CSSStyleDeclaration]?: any 30 | } 31 | /** 32 | * @func setElementStyle 33 | * @desc 📝 设置元素的样式 34 | * @param {HTMLElement} el 35 | * @param {objectKeyOnlyCss} styleObj 36 | * @returns {void} 37 | * @example setStyle(el, {border:'',position:''}) 38 | */ 39 | export const setElementStyle = (el: HTMLElement | null, 40 | styleObj: objectKeyOnlyCss) => { 41 | for (const key in styleObj) { 42 | el!.style[key] = styleObj[key] 43 | } 44 | } 45 | 46 | /** 47 | * @func setProperties 48 | * @desc 📝 设置元素的属性 49 | * @param {(HTMLElement | null)} el 50 | * @param {object} properties 51 | * @returns {void} 52 | * @example setProperties(el, {--rotate:'360deg'}) 53 | */ 54 | export const setProperties = ( 55 | el: HTMLElement | null, 56 | properties: object 57 | ) => { 58 | for (const key in properties) { 59 | el!.style.setProperty(key, properties[key]) 60 | } 61 | } 62 | // Check if an element has a class 63 | export const hasClass = (ele: HTMLElement, className: string) => { 64 | return !!ele.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)')); 65 | }; 66 | 67 | // Add class to element 68 | export const addClass = (ele: HTMLElement, className: string) => { 69 | if (!hasClass(ele, className)) { 70 | ele.className += ' ' + className; 71 | } 72 | }; 73 | 74 | // Remove class from element 75 | export const removeClass = (ele: HTMLElement, className: string) => { 76 | if (hasClass(ele, className)) { 77 | const reg = new RegExp('(\\s|^)' + className + '(\\s|$)'); 78 | ele.className = ele.className.replace(reg, ' '); 79 | } 80 | }; 81 | 82 | -------------------------------------------------------------------------------- /docs/guide/Regex.md: -------------------------------------------------------------------------------- 1 | # Regex 2 | 3 | 4 | ## isChines 5 | 是否是中文 6 | ```typescript 7 | /** 8 | * @func isChines 9 | * @desc 是否是中文 10 | * @param { string } str 需要判断的数据 11 | * @return { boolean } 是否是中文 12 | * @example if (isChines(str)) { doSomething } 13 | */ 14 | ``` 15 | ## isCard 16 | 是否为身份证号: 支持(1/2)代,15位或18位 17 | ```typescript 18 | /** 19 | * @func isCard 20 | * @desc 是否为身份证号: 支持(1/2)代,15位或18位 21 | * @param { string } str 需要判断的数据 22 | * @param { number } type 1: 15位;2: 18位;默认0:同事匹配15位和18位 23 | * @return { boolean } 是否为身份证号 24 | * @example if (isCard(str)) { doSomething } 25 | */ 26 | ``` 27 | ## isPostCode 28 | 校验是否是大陆邮政编码 29 | ```typescript 30 | /** 31 | * @func isPostCode 32 | * @desc 校验是否是大陆邮政编码 33 | * @param { number } value 需要判断的数据 34 | * @return { boolean } 是否是大陆邮政编码 35 | * @example if (isPostCode(str)) { doSomething } 36 | */ 37 | ``` 38 | ## isIPv6 39 | 校验是否是IPv6地址 40 | ```typescript 41 | /** 42 | * @func isIPv6 43 | * @desc 校验是否是IPv6地址 44 | * @param { string } str 需要判断的数据 45 | * @return { boolean } 是否是IPv6地址 46 | * @example if (isIPv6(str)) { doSomething } 47 | */ 48 | ``` 49 | ## isEmail 50 | 是否是邮箱 51 | ```typescript 52 | /** 53 | * @func isEmail 54 | * @desc 是否是邮箱 55 | * @param { string } value 需要判断的数据 56 | * @return { boolean } 是否是邮箱 57 | * @example if (isEmail(str)) { doSomething } 58 | */ 59 | ``` 60 | ## isTelNumber 61 | 是否是手机号 62 | ```typescript 63 | /** 64 | * @func isTelNumber 65 | * @desc 是否是手机号 66 | * @param { string } str 需要判断的数据 67 | * @return { boolean } 是否是手机号 68 | * @example if (isTelNumber(str)) { doSomething } 69 | */ 70 | ``` 71 | ## isHasEmoji 72 | 是否包含emoji表情 73 | ```typescript 74 | /** 75 | * @func isHasEmoji 76 | * @desc 是否包含emoji表情 77 | * @param { string } value 需要判断的数据 78 | * @return { boolean } 是否包含emoji表情 79 | * @example if (isHasEmoji(str)) { doSomething } 80 | */ 81 | ``` 82 | ## isUrl 83 | 校验是否是URL 84 | ```typescript 85 | /** 86 | * @func isUrl 87 | * @desc 校验是否是URL 88 | * @param { string } value 需要判断的数据 89 | * @return { boolean } 校验是否是URL 90 | * @example if (isUrl(str)) { doSomething } 91 | */ 92 | ``` 93 | ## isColor 94 | 判断字符串是否是十六进制的颜色值 95 | ```typescript 96 | /** 97 | * @func isColor 98 | * @desc 判断字符串是否是十六进制的颜色值 99 | * @param {string} value 需要判断的数据 100 | * @returns {boolean} 校验是否是十六进制的颜色值 101 | * @example if (isColor(str)) { doSomething } 102 | */ 103 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | Prettier 13 | Apache 14 |

15 | 16 | 17 | # Fastool 18 | > 我计划在接下来的两周内,使用ChatGPT对该插件进行调整和优化,以解决其性能问题。 19 | 20 | 这是一个简洁而功能强大的现代 JavaScript 工具库,使用该库可以有效提高你的开发效率,快速完成项目。 21 | 22 | [使用文档介绍](https://tobe-fe-dalao.github.io/fastool/guide/) | [博文介绍]() 23 |

24 | 25 |

26 | 27 | # 特性 28 | 29 | - ✅短小精悍,代码轻量 30 | - ✅基于 TypeScript 开发,类型安全 31 | - ✅适用于现代ES6规范 32 | - ✅包含动态插件库,可以随时调用,做随手可得且最全的工具库 33 | - ✅具备更多能力,等你去挖掘.... 34 | 35 | # 如何使用 36 | 37 | ## import 38 | 39 | ```bash 40 | # npm 41 | npm install fastool 42 | 43 | # yarn 44 | yarn add fastool 45 | 46 | # pnpm 🔥 47 | pnpm add fastool 48 | ``` 49 | 50 | > 如果你不想在项目中引入太多依赖,而又想使用某一个方法 51 | > 52 | > 那么可以复制文档中的源码,在你的项目中引入 53 | 54 | ### CDN源 55 | ```bash 56 | // CDN源:jsdelivr 57 | 58 | https://cdn.jsdelivr.net/npm/fastool@latest 59 | 60 | CDN源:unpkg 61 | 62 | https://unpkg.com/fastool@latest 63 | ``` 64 | 65 | ## **Authors** 66 | 67 | 这个项目需要感谢的参与者 68 |
69 | 70 | 71 | 72 |
73 | 74 | # About Me 75 | 76 | 如果您认为这个项目不错,可以在[Github](https://github.com/MaleWeb/fastool) 上为我点个赞,支持一下作者 ☜(゚ヮ゚☜) 77 | 78 | 微信打赏一杯卡布奇诺 79 | 80 | 81 | 82 | 83 | 84 | # RoadMap 85 | [Milestones](https://github.com/tobe-fe-dalao/femate/projects) 86 | 87 | # License 88 | [Apache-2.0](./LICENSE) © [MaleWeb](https://github.com/MaleWeb) 89 | 90 | -------------------------------------------------------------------------------- /src/string/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func randomString 3 | * @param {number} len 4 | * @returns {string} 5 | * @desc 生成随机字符串 6 | */ 7 | export const randomString = (len: number) => { 8 | let chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz123456789'; 9 | let strLen = chars.length; 10 | let randomStr = ''; 11 | for (let i = 0; i < len; i++) { 12 | randomStr += chars.charAt(Math.floor(Math.random() * strLen)); 13 | } 14 | return randomStr; 15 | }; 16 | /** 17 | * @func firstUpperCase 18 | * @param {string} str 19 | * @returns {string} 20 | * @desc 首字母大写 21 | */ 22 | export const firstUpperCase = (str: string) => { 23 | return str.charAt(0).toUpperCase() + str.slice(1); 24 | }; 25 | 26 | /** 27 | * @func telEncrypt 28 | * @param {number} tel 29 | * @returns {string} 30 | * @desc 手机号中间加密 31 | */ 32 | export const telEncrypt = (tel: number) => { 33 | return tel.toString().replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); 34 | }; 35 | 36 | /** 37 | * @func getKebabCase 38 | * @param {string} str 39 | * @returns {string} 40 | * @desc 转换成短横线命名 41 | */ 42 | export const getKebabCase = (str: string) => { 43 | return str.replace(/[A-Z]/g, item => '-' + item.toLowerCase()); 44 | }; 45 | /** 46 | * @func getCamelCase 47 | * @param {string} str 48 | * @returns {string} 49 | * @desc 转换成驼峰命名 50 | */ 51 | export const getCamelCase = (str: string) => { 52 | return str.replace(/-([a-z])/g, (i, item) => item.toUpperCase()); 53 | }; 54 | 55 | /** 56 | * @func getEscapeString 57 | * @param {string} str 58 | * @returns {string} 59 | * @desc 字符串的转义,将`&`, `<`, `>`, `"`, `'`分别转义为`&`, `<`, `>`, `"`, `'` 60 | */ 61 | export const getEscapeString = (str: string): string => { 62 | const ESCAPE = { 63 | '&': '&', 64 | '<': '<', 65 | '>': '>', 66 | '"': '"', 67 | "'": ''', 68 | }; 69 | const ESCAPE_EXPR = /(&|<|>|"|')/g; 70 | return ('' + str).replace(ESCAPE_EXPR, match => { 71 | return ESCAPE[match]; 72 | }); 73 | }; 74 | 75 | /** 76 | * @func getUnEscapeString 77 | * @param {string} str 78 | * @returns {string} 79 | * @desc 字符串的反转义,将`&`, `<`, `>`, `"`, `'`替换为转义前的符号 80 | */ 81 | export const getUnEscapeString = (str: string): string => { 82 | const UN_ESCAPE = { 83 | '&': '&', 84 | '<': '<', 85 | '>': '>', 86 | '"': '"', 87 | ''': "'", 88 | }; 89 | const UN_ESCAPE_EXPR = /(&|<|>|"|')/g; 90 | return ('' + str).replace(UN_ESCAPE_EXPR, match => { 91 | return UN_ESCAPE[match]; 92 | }); 93 | }; 94 | -------------------------------------------------------------------------------- /src/plugins/importPlugin.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func importPlugin 3 | * @desc 根据url引入插件模块 4 | * @param {string} url 5 | * @param {string} moduleName 该插件对应的名称 6 | * @returns {Promise} 7 | * @example await importPlugin(cdnUrl, pluginName) 8 | */ 9 | // export default async function importPlugin(url: string, moduleName?: string): Promise { 10 | // // Check if ES6 modules are supported 11 | // const supportsES6Modules = (() => { 12 | // try { 13 | // new Function('import("")'); 14 | // return true; 15 | // } catch { 16 | // return false; 17 | // } 18 | // })(); 19 | // console.log(supportsES6Modules, '检测是否支持ESM') 20 | // if (supportsES6Modules) { 21 | // // Use import() to load the module 22 | 23 | // const module = await import(url); 24 | // if (moduleName) { 25 | // if (module[moduleName]) { 26 | // return module[moduleName]; 27 | // } else { 28 | // throw new Error(`Module "${moduleName}" not found in ${url}`); 29 | // } 30 | // } else { 31 | // return module; 32 | // } 33 | // } else { 34 | // // Use script tag to load the script 35 | // return new Promise((resolve, reject) => { 36 | // const script = document.createElement('script'); 37 | // script.src = url; 38 | 39 | // script.onload = () => { 40 | // if (moduleName) { 41 | // if (typeof window[moduleName] === 'undefined') { 42 | // reject(new Error(`Global variable "${moduleName}" not found`)); 43 | // } else { 44 | // resolve(window[moduleName]); 45 | // } 46 | // } else { 47 | // resolve(moduleName); 48 | // } 49 | // }; 50 | 51 | // script.onerror = () => { 52 | // reject(new Error(`Failed to load script from ${url}`)); 53 | // }; 54 | 55 | // document.head.appendChild(script); 56 | // }); 57 | // } 58 | // } 59 | export default function importPluginBycdn(moduleName: string, cdnUrl: string): Promise { 60 | if (typeof window !== 'undefined') { 61 | // 浏览器支持ES Modules 62 | return import(cdnUrl) 63 | } else { 64 | // 浏览器不支持ES Modules 65 | return new Promise((resolve, reject) => { 66 | const script = document.createElement("script") 67 | script.src = cdnUrl 68 | script.async = true 69 | script.onload = () => { 70 | resolve((window as any)[moduleName]) 71 | } 72 | script.onerror = () => { 73 | reject(new Error(`Failed to load ${cdnUrl}`)) 74 | } 75 | document.head.appendChild(script) 76 | }) 77 | } 78 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fastool", 3 | "version": "0.1.8", 4 | "description": "一个短小而精悍的现代JavaScript使用工具库", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.esm.js", 7 | "unpkg": "dist/index.umd.js", 8 | "jsdelivr": "dist/index.umd.js", 9 | "types": "dist/index.d.ts", 10 | "jsname": "fastool", 11 | "scripts": { 12 | "dev": " cross-env NODE_ENV=dev rollup -c -w", 13 | "build": "rimraf dist && cross-env NODE_ENV=production rollup -c", 14 | "release": "pnpx semantic-release", 15 | "docs:dev": "vitepress dev docs", 16 | "docs:build": "vitepress build docs", 17 | "docs:serve": "vitepress serve docs" 18 | }, 19 | "homepage": "", 20 | "bugs": "https://github.com/tobe-fe-dalao/fastool/issues", 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/tobe-fe-dalao/fastool.git" 24 | }, 25 | "author": "BlindMonk", 26 | "license": "Apache-2.0", 27 | "files": [ 28 | "dist", 29 | "types", 30 | "index.js" 31 | ], 32 | "keywords": [ 33 | "" 34 | ], 35 | "devDependencies": { 36 | "@babel/core": "^7.21.0", 37 | "@babel/plugin-transform-runtime": "^7.21.0", 38 | "@babel/preset-env": "^7.20.2", 39 | "@babel/runtime": "^7.21.0", 40 | "@rollup/plugin-commonjs": "^16.0.0", 41 | "@rollup/plugin-eslint": "^8.0.5", 42 | "@rollup/plugin-json": "^4.1.0", 43 | "@rollup/plugin-node-resolve": "^10.0.0", 44 | "@rollup/plugin-typescript": "^6.1.0", 45 | "@typescript-eslint/parser": "^4.33.0", 46 | "babel-plugin-external-helpers": "^6.22.0", 47 | "babel-preset-latest": "^6.24.1", 48 | "eslint": "^7.32.0", 49 | "eslint-plugin-prettier": "^3.4.1", 50 | "prettier": "^2.8.4", 51 | "rollup": "^2.79.1", 52 | "rollup-plugin-babel": "^4.4.0", 53 | "rollup-plugin-delete": "^2.0.0", 54 | "rollup-plugin-dts": "^3.0.2", 55 | "rollup-plugin-livereload": "^2.0.5", 56 | "rollup-plugin-serve": "^1.1.0", 57 | "rollup-plugin-styles": "^4.0.0", 58 | "rollup-plugin-terser": "^7.0.2", 59 | "rollup-plugin-typescript2": "^0.30.0", 60 | "tslib": "^2.5.0", 61 | "typescript": "^4.9.5", 62 | "vitepress": "1.0.0-alpha.47" 63 | }, 64 | "dependencies": { 65 | "@rollup/plugin-replace": "^5.0.2", 66 | "@types/node": "^17.0.45", 67 | "cross-env": "^7.0.3", 68 | "rimraf": "^4.1.2", 69 | "rollup-plugin-gzip": "^3.1.0", 70 | "rollup-plugin-uglify": "^6.0.4" 71 | } 72 | } -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import ts from 'rollup-plugin-typescript2'; 3 | // 将json 文件转换为ES6 模块 4 | import json from '@rollup/plugin-json'; 5 | // 在node_模块中查找并绑定第三方依赖项 6 | import resolve from '@rollup/plugin-node-resolve'; 7 | // 将CommonJS模块转换为ES6 8 | import commonjs from '@rollup/plugin-commonjs'; 9 | // rollup babel插件 10 | import babel from 'rollup-plugin-babel'; 11 | // 优化代码 12 | import { terser } from 'rollup-plugin-terser'; 13 | // 加载样式文件 14 | import styles from 'rollup-plugin-styles'; 15 | import dts from 'rollup-plugin-dts'; 16 | // 替换环境变量 17 | // import replace from '@rollup/plugin-replace'; 18 | // 热更新服务 19 | import livereload from 'rollup-plugin-livereload'; 20 | // 开发服务器 21 | import serve from 'rollup-plugin-serve'; 22 | // 删除工具 23 | import del from 'rollup-plugin-delete'; 24 | // import eslint from '@rollup/plugin-eslint' 25 | import pkg from './package.json'; 26 | import gzip from 'rollup-plugin-gzip'; 27 | // 判断是是否为生产环境 28 | // 开发环境or生产环境 29 | const isPro = function () { 30 | return process.env.NODE_ENV === 'production'; 31 | }; 32 | const extensions = ['.jsx', '.ts', '.tsx', '.less']; 33 | export default [ 34 | { 35 | input: path.resolve('./index.ts'), 36 | output: [ 37 | { 38 | file: pkg.unpkg, 39 | format: 'umd', 40 | name: pkg.jsname, 41 | sourcemap: true, 42 | }, 43 | { 44 | file: pkg.module, 45 | format: 'esm', 46 | sourcemap: true, 47 | }, 48 | { 49 | file: pkg.main, 50 | format: 'cjs', 51 | sourcemap: true, 52 | }, 53 | ], 54 | plugins: [ 55 | resolve(), //快速查找外部模块 56 | commonjs(), //将CommonJS转换为ES6模块 57 | json(), //将json转换为ES6模块 58 | styles(), 59 | //ts编译插件 60 | ts({ 61 | tsconfig: path.resolve(__dirname, './tsconfig.json'), 62 | extensions: extensions, 63 | }), 64 | babel({ 65 | runtimeHelpers: true, 66 | exclude: ['node_modules/**', 'src/plugins/**.js'], 67 | }), 68 | !isPro() && 69 | livereload({ 70 | watch: ['dist', 'examples', 'src/**/*'], 71 | verbose: false, 72 | }), 73 | isPro() && terser(), 74 | isPro() && gzip(), 75 | !isPro() && 76 | serve({ 77 | open: false, 78 | port: 9529, 79 | contentBase: './', 80 | openPage: '/examples/index.html', 81 | }), 82 | ], 83 | }, 84 | { 85 | // 生成 .d.ts 类型声明文件 86 | input: path.resolve('./index.ts'), 87 | output: { 88 | file: pkg.types, 89 | format: 'es', 90 | }, 91 | plugins: [ 92 | dts(), 93 | del({ 94 | targets: ['./dist/src'], 95 | hook: 'buildEnd', 96 | }), 97 | ], 98 | }, 99 | ]; 100 | -------------------------------------------------------------------------------- /docs/images/gradient-right-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/guide/Fn.md: -------------------------------------------------------------------------------- 1 | # Fn 2 | ## mergeObject 3 | 深拷贝合并对象 4 | ```typescript 5 | /** 6 | * @func mergeObject 7 | * @desc 深拷贝合并对象 8 | * @param { Object } obj1 外部的参数对象 9 | * @param { Object } obj2 默认参数对象 10 | * @return { Object } 合并后的对象 11 | * @example const obj = mergeObject(obj1, obj2) 12 | */ 13 | ``` 14 | ## observeDef 15 | 设计一个对象的观测者 16 | ```typescript 17 | /** 18 | * @func observeDef 19 | * @desc 设计一个对象的观测者 20 | * @param { Object } obj obj对象 21 | * @return { Object } 返回一个可观测对象 22 | * @example const obj = observeDef({name:'alex',age:18}) 23 | */ 24 | ``` 25 | ## observeProxy 26 | 设计一个对象的观测者-proxy方案 27 | ```typescript 28 | /** 29 | * @func observeProxy 30 | * @desc 设计一个对象的观测者-proxy方案 31 | * @param { Object } obj obj对象 32 | * @param { Function } cal 观测对象回调方法 33 | * @return { Promise } 返回一个可观测对象 34 | * @example const obj = observeProxy({name:'alex',age:18}, callback) 35 | */ 36 | ``` 37 | ## throttle 38 | 函数节流,每隔一段时间执行一次,防止函数过于频繁调用,导致性能问题 39 | ```typescript 40 | /** 41 | * @func throttle 42 | * @desc 函数节流,每隔一段时间执行一次,防止函数过于频繁调用,导致性能问题 43 | * @param { Function } fn 将要处理的函数 44 | * @param { number } wait 时间, 单位为毫秒 45 | * @return { Function } 节流函数 46 | * @example throttle(fn, wait) 47 | */ 48 | ``` 49 | ## debounce 50 | 防抖函数 51 | 与throttle不同的是,debounce保证一个函数在多少毫秒内不再被触发,只会执行一次,要么在第一次调用return的防抖函数时执行,要么在延迟指定毫秒后调用。 52 | ```typescript 53 | /** 54 | * @func debounce 55 | * @desc 防抖函数 56 | 与throttle不同的是,debounce保证一个函数在多少毫秒内不再被触发,只会执行一次,要么在第一次调用return的防抖函数时执行,要么在延迟指定毫秒后调用。 57 | * @param { Function } fn 将要处理的函数 58 | * @param { number } wait 时间, 单位为毫秒 59 | * @param { boolean } immediate 是否在触发事件后 在时间段n开始,立即执行,否则是时间段n结束,才执行 60 | * @return { Function } 包装好的节流函数 61 | * @example debounce(fn, wait) 62 | */ 63 | ``` 64 | ## deepClone 65 | 深度复制对象 66 | ```typescript 67 | /** 68 | * @func deepClone 69 | * @desc 深度复制对象 70 | * @param { Object } obj 将要复制的对象 71 | * @param { string } hash 哈希值 72 | * @return { Object } 包装好的节流函数 73 | * @example const objCopy = deepClone(obj) 74 | */ 75 | ``` 76 | ## fetchUtil 77 | 封装fetch函数,用Promise做回调 78 | ```typescript 79 | /** 80 | * @func fetchUtil 81 | * @desc 封装fetch函数,用Promise做回调 82 | * @return { Promise } 83 | * @type {{get: (function(*=)), post: (function(*=, *=))}} 84 | * @example fetchUtil.post(apiUrl) 85 | */ 86 | ``` 87 | ## getTypeOf 88 | 获取参数类型 89 | ```typescript 90 | /** 91 | * @func getTypeOf 92 | * @desc 获取参数类型 93 | * @param { unknown } param 参数 94 | * @return { string } 参数类型 95 | * @example getTypeOf(...) 96 | */ 97 | ``` 98 | ## sleep 99 | 睡眠函数 100 | ```typescript 101 | /** 102 | * @func sleep 103 | * @desc 睡眠函数 104 | * @param { number } wait 睡眠时间 毫秒 105 | * @return { Promise } 106 | * @example sleep(300) 107 | */ 108 | ``` 109 | ## importPluginByUrl 110 | 根据url引入插件模块 111 | ```typescript 112 | /** 113 | * @func importPluginByUrl 114 | * @desc 根据url引入插件模块 115 | * @param { string } cdnUrl 引入路径 116 | * @param { string } pluginName 该插件对应的名称 117 | * @param { string } newName 重新定义的名称 118 | * @param { boolean } isEsm 是否是esm模块 119 | * @return { Promise } 120 | * @example importPluginByUrl(cdnUrl, pluginName) 121 | */ 122 | ``` -------------------------------------------------------------------------------- /src/number/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func digitUppercase 3 | * @param {number} n 4 | * @return {string} 5 | * @desc 现金额转大写 6 | * @example digitUppercase(123456789.123) // 壹佰贰拾叁万肆仟伍佰陆拾柒元叁角肆分 7 | */ 8 | 9 | export const digitUppercase = (n: number): String => { 10 | var fraction = ['角', '分']; 11 | var digit = [ 12 | '零', '壹', '贰', '叁', '肆', 13 | '伍', '陆', '柒', '捌', '玖' 14 | ]; 15 | var unit = [ 16 | ['元', '万', '亿'], 17 | ['', '拾', '佰', '仟'] 18 | ]; 19 | var head = n < 0 ? '欠' : ''; 20 | n = Math.abs(n); 21 | var s = ''; 22 | for (var i = 0; i < fraction.length; i++) { 23 | s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, ''); 24 | } 25 | s = s || '整'; 26 | n = Math.floor(n); 27 | for (var i = 0; i < unit[0].length && n > 0; i++) { 28 | var p = ''; 29 | for (var j = 0; j < unit[1].length && n > 0; j++) { 30 | p = digit[n % 10] + unit[1][j] + p; 31 | n = Math.floor(n / 10); 32 | } 33 | s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s; 34 | } 35 | return head + s.replace(/(零.)*零元/, '元') 36 | .replace(/(零.)+/g, '零') 37 | .replace(/^整$/, '零元整'); 38 | }; 39 | /** 40 | * @func intToChinese 41 | * @param {number} value 42 | * @return {string} 43 | * @example intToChinese(123456789) // 一亿二千三百四十五万六千七百八十九 44 | * @desc 数字转中文 45 | */ 46 | export const intToChinese = (value: string) => { 47 | const str = String(value); 48 | const len = str.length - 1; 49 | const idxs = ['', '十', '百', '千', '万', '十', '百', '千', '亿', '十', '百', '千', '万', '十', '百', '千', '亿']; 50 | const num = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; 51 | return str.replace(/([1-9]|0+)/g, ($, $1, idx, full) => { 52 | let pos = 0; 53 | if ($1[0] !== '0') { 54 | pos = len - idx; 55 | if (idx == 0 && $1[0] == 1 && idxs[len - idx] == '十') { 56 | return idxs[len - idx]; 57 | } 58 | return num[$1[0]] + idxs[len - idx]; 59 | } else { 60 | let left = len - idx; 61 | let right = len - idx + $1.length; 62 | if (Math.floor(right / 4) - Math.floor(left / 4) > 0) { 63 | pos = left - left % 4; 64 | } 65 | if (pos) { 66 | return idxs[pos] + num[$1[0]]; 67 | } else if (idx + $1.length >= len) { 68 | return ''; 69 | } else { 70 | return num[$1[0]] 71 | } 72 | } 73 | }); 74 | } 75 | /** 76 | * @func sumAverage 77 | * @param {number[]} numberArr 78 | * @return {number} 79 | * @desc 计算数组平均值 80 | * @example sumAverage([1,2,3,4,5]) // 3 81 | */ 82 | export const sumAverage = (numberArr: number[]): number => { 83 | return numberArr.reduce((acc, curr) => acc + curr, 0) / numberArr.length; 84 | } 85 | 86 | /** 87 | * @func getDistance 88 | * @param {object} point1 89 | * @param {object} point2 90 | * @returns {number} 距离 91 | * @desc 计算两坐标点之间的距离 92 | * @example getDistance({x:1,y:2},{x:3,y:4}) // 2.8284271247461903 93 | */ 94 | interface Point { 95 | x: number, 96 | y: number 97 | } 98 | export const getDistance = (point1: Point, point2: Point): number => { 99 | const { x: x1, y: y1 } = point1; 100 | const { x: x2, y: y2 } = point2; 101 | return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); 102 | } 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/device/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func: getOS 3 | * @returns {String} 操作系统类型 4 | * @desc: 获取操作系统类型 5 | * @example: getOS() 6 | */ 7 | export const getOS = (): string => { 8 | let userAgent = navigator.userAgent, 9 | isWindowsPhone = /(?:Windows Phone)/.test(userAgent), 10 | isSymbian = /(?:SymbianOS)/.test(userAgent), 11 | isAndroid = /(?:Android)/.test(userAgent), 12 | isFireFox = /(?:Firefox)/.test(userAgent), 13 | isChrome = /(?:Chrome|CriOS)/.test(userAgent), 14 | isTablet = /(?:iPad|PlayBook)/.test(userAgent) || (isAndroid && !/(?:Mobile)/.test(userAgent)) || (isFireFox && /(?:Tablet)/.test(userAgent)), 15 | isPhone = /(?:iPhone)/.test(userAgent) && !isTablet, 16 | isPc = !isPhone && !isAndroid && !isSymbian; 17 | if (isWindowsPhone) return 'Windows Phone' 18 | if (isSymbian) return 'Symbian' 19 | if (isTablet) return 'Tablet' 20 | if (isPhone) return 'Ios' 21 | if (isAndroid) return 'Android' 22 | if (isFireFox) return 'FireFox' 23 | if (isChrome) return 'Chrome' 24 | if (isPc) return 'Pc' 25 | return 'Unkonwn' 26 | } 27 | /** 28 | * @func isWeiXin 29 | * @returns {Boolean} 是否是微信 30 | * @desc 📝 是否是微信浏览器 31 | * @example: isWeiXin() 32 | */ 33 | export const isWeiXin = (): boolean => { 34 | const ua = window.navigator.userAgent.toLowerCase(); 35 | const match = ua.match(/MicroMessenger/i); 36 | if (match === null) { 37 | return false; 38 | } 39 | if (match.includes('micromessenger')) { 40 | return true; 41 | } 42 | return false; 43 | } 44 | /** 45 | * @func isMobile 46 | * @return {Boolean} 是否是移动端 47 | * @desc 📝 是否是移动端 48 | * @example: isMobile() 49 | */ 50 | export const isMobile = (): boolean => { 51 | if ((navigator.userAgent.match(/(iPhone|iPod|Android|ios|iOS|iPad|Backerry|WebOS|Symbian|Windows Phone|Phone)/i))) { 52 | return true; 53 | } 54 | return false; 55 | } 56 | 57 | /** 58 | * @func isSupportCamera 59 | * @return {Boolean} 是否支持摄像头 60 | * @desc 📝 是否支持摄像头 61 | * @example: isSupportCamera() 62 | */ 63 | export const isSupportCamera = (): boolean => { 64 | if (navigator.mediaDevices && (navigator as any).mediaDevices.getUserMedia) { 65 | let deviceList: any = []; 66 | navigator.mediaDevices 67 | .enumerateDevices() 68 | .then(devices => { 69 | devices.forEach(device => { 70 | deviceList.push(device.kind); 71 | }); 72 | if (deviceList.indexOf("videoinput") == "-1") { 73 | return false; 74 | } else { 75 | return true; 76 | } 77 | }) 78 | .catch(function (err) { 79 | console.log(err.name + ": " + err.message); 80 | }); 81 | } 82 | return false; 83 | } 84 | 85 | 86 | /** 87 | * @func getDeviceId 88 | * @desc 通过三方库生成设备唯一ID 三方库地址:https://github.com/fingerprintjs/fingerprintjs 89 | * @returns {Promise} 设备唯一ID 90 | * @example: getDeviceId() 91 | */ 92 | export const getDeviceId = () => { 93 | //远程加载方式 import(https://openfpcdn.io/fingerprintjs/v3) 94 | return new Promise((resolve, reject) => { 95 | const fpPromise = import('https://openfpcdn.io/fingerprintjs/v3' as any) 96 | .then(FingerprintJS => FingerprintJS.load()) 97 | // Get the visitor identifier when you need it. 98 | fpPromise 99 | .then(fp => fp.get()) 100 | .then(result => { 101 | // This is the visitor identifier: 102 | resolve(result.visitorId) 103 | }) 104 | }) 105 | } -------------------------------------------------------------------------------- /src/url/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func getUrlParams 3 | * @param {string} url 4 | * @returns {object} 5 | * @desc 📝 获取 url 中所有的参数,以对象的形式返回,如果参数名重复,则以数组的形式返回 6 | */ 7 | export const getUrlParams = (url: string = location.href.toString()): object => { 8 | const params: { [key: string]: any } = {}; 9 | const paramsStr = /.+\?(.+)$/.exec(url)![1]; // 将 ? 后面的字符串取出来 10 | const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中 11 | // 将 params 存到对象中 12 | paramsArr.forEach(param => { 13 | if (/=/.test(param)) { // 处理有 value 的参数 14 | let [key, val] = param.split('='); // 分割 key 和 value 15 | val = decodeURIComponent(val); // 解码 16 | (val as any) = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字 17 | if (params.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值 18 | params[key] = [].concat(params[key], val as any); 19 | } else { // 如果对象没有这个 key,创建 key 并设置值 20 | params[key] = val; 21 | } 22 | } else { // 处理没有 value 的参数 23 | params[param] = true; 24 | } 25 | }) 26 | return params; 27 | } 28 | /** 29 | * @func setUrlParams 30 | * @param {string} url 原始URL 31 | * @param {string} urlKey 参数名 32 | * @param {string} urlValue 参数值 33 | * @returns {string} url 修改后的URL 34 | * @desc 📝 修改URL参数 35 | */ 36 | export const setUrlParams = (urlKey: any, urlValue: any, url?: string): string => { 37 | // 兼容eval污染全局问题 https://rollupjs.org/guide/en/#avoiding-eval 38 | let eval2 = eval; 39 | url ?? (url = location.href.toString()); 40 | const re = eval2('/(' + urlKey + '=)([^&]*)/gi'); 41 | url = url.replace(re, urlKey + '=' + urlValue); 42 | return url; 43 | } 44 | //解决eval污染全局方案二 45 | export function eval2(fn: Function) { 46 | var Fn = Function; 47 | return new Fn('return ' + fn)(); 48 | } 49 | /** 50 | * @func delUrlParams 51 | * @param {string} 参数 52 | * @returns {string} url 修改后的URL 53 | * @desc 📝 删除 url 中的参数 54 | */ 55 | export const delUrlParams = (name: string) => { 56 | const baseUrl = location.origin + location.pathname + "?"; 57 | const query = location.search.substr(1); 58 | if (query.indexOf(name) > -1) { 59 | let obj = {}; 60 | let arr: any = query.split("&"); 61 | for (let i = 0; i < arr.length; i++) { 62 | arr[i] = arr[i].split("="); 63 | obj[arr[i][0]] = arr[i][1]; 64 | } 65 | delete obj[name]; 66 | return baseUrl + JSON.stringify(obj).replace(/[\"\{\}]/g, "").replace(/\:/g, "=").replace(/\,/g, "&"); 67 | } 68 | } 69 | /** 70 | * @func paramsJoinUrl 71 | * @param {object} 参数对象 72 | * @returns {string} url 修改后的URL 73 | * @desc 📝 将参数对象转为 url 字符串 74 | */ 75 | export const paramsJoinUrl = (params: object): string => { 76 | let param = [] 77 | for (let key in params) { 78 | param.push(`${key}=${params[key]}`); 79 | } 80 | return encodeURIComponent(param.join('&')) 81 | } 82 | /** 83 | * @func getBaseUrl 84 | * @param {string} url 原始URL 85 | * @returns {string} url 修改后的URL 86 | * @desc 📝 获取 url 中?之前的部分 87 | */ 88 | export const getBaseUrl = (url: string = location.href.toString()): string => { 89 | return url.includes('?') ? url.split('?')[0] : url; 90 | } 91 | /** 92 | * @func getUrlDomain 93 | * @param {string} url 原始URL 94 | * @returns {string} url 修改后的URL 95 | * @desc 📝 获取 url 中的域名 96 | */ 97 | export const getUrlDomain = (url: string = location.href.toString()): string => { 98 | const baseUrl = /^(http|https):\/\/[^\/]+/.exec(url)![0]; 99 | return baseUrl 100 | } 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/dom/file.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func dataURLtoFile 3 | * @param {string} dataUrl 4 | * @param {string} fileName 5 | * @desc 将dataUrl转换为文件 6 | * ie无法使用new File构造函数,建议使用dataURLtoBlob 7 | * @return {any} 文件对象 8 | * @example let file = dataURLtoFile(dataUrl, fileName) 9 | */ 10 | export const dataURLtoFile = (dataUrl: string, fileName: string = getFileName()): any => { 11 | let arr: Array = dataUrl.split(','); 12 | let mime = arr[0].match(/:(.*?);/)[1]; 13 | let bstr = atob(arr[1]); 14 | let n = bstr.length; 15 | let u8arr = new Uint8Array(n); 16 | while (n--) { 17 | u8arr[n] = bstr.charCodeAt(n); 18 | } 19 | return new File([u8arr], fileName, { type: mime }); 20 | } 21 | /** 22 | * @func base64toFile 23 | * @param {string} base64 24 | * @param {string} fileName 25 | * @desc 将base64转换为文件 26 | * @returns {any} 文件对象 27 | * @example let file = base64toFile(base64, fileName) 28 | */ 29 | export const base64toFile = (base64: string, fileName: string = getFileName()): any => { 30 | return dataURLtoFile(base64, fileName) 31 | } 32 | /** 33 | * @func getFileName 34 | * @desc 设置文件名字 35 | * @return { string } fileName 文件名 36 | * @example let fileName = getFileName(); 37 | */ 38 | function getFileName() { 39 | let date = new Date(); 40 | let fileName = '' + date.getFullYear() + (date.getMonth() < 9 ? +'0' : '') 41 | + (date.getMonth() + 1) + (date.getDate() < 10 ? +'0' : '') + date.getDate() 42 | + date.getHours() + date.getMinutes() + (date.getSeconds() < 10 ? '0' : '') + date.getSeconds(); 43 | return fileName; 44 | } 45 | /** 46 | * @func dataURLtoBlob 47 | * @param { string } dataurl 48 | * @desc 将dataUrl/base64转换为blob 49 | * @return { Blob } Blob 50 | * @example let blob = dataURLtoBlob(dataurl) 51 | */ 52 | export const dataURLtoBlob = (dataurl: string): Blob | null => { 53 | let mime; 54 | let u8arr; 55 | if (dataurl) { 56 | const arr = dataurl.split(','); 57 | if (arr && arr.length) { 58 | const mimem = arr[0].match(/:(.*?);/); 59 | if (mimem) { 60 | mime = mimem[1] || 'image/png'; 61 | } 62 | const bstr = atob(arr[1]); 63 | let n = bstr.length; 64 | u8arr = new Uint8Array(n); 65 | while (n--) { 66 | u8arr[n] = bstr.charCodeAt(n); 67 | } 68 | return new Blob([u8arr], { type: mime }); 69 | } 70 | } 71 | return null; 72 | }; 73 | /** 74 | * @func base64toBlob 75 | * @param { string } base64 76 | * @desc 将base64转换为blob 77 | * @return { Blob } Blob 78 | * @example let blob = base64toBlob(dataurl) 79 | */ 80 | export const base64toBlob = (base64: string): Blob | null => { 81 | return dataURLtoBlob(base64) 82 | }; 83 | /** 84 | * @func blobtoFile 85 | * @param { Blob } blob 86 | * @param { string } fileName 87 | * @desc 将blob转换为文件 88 | * @return { any } 文件对象 89 | * @example let file = blobtoFile(blob, filename) 90 | */ 91 | export const blobtoFile = (blob: Blob, fileName: string = getFileName()): any => { 92 | return new File([blob], fileName, { type: blob.type }) 93 | }; 94 | /** 95 | * @func filetoBase64 96 | * @param file 文件对象 97 | * @desc 文件对象转base64 98 | * @return { Promise } Promise对象,异步处理 99 | */ 100 | export const filetoBase64 = (file: any): Promise => { 101 | // return window.URL.createObjectURL(file) 102 | return new Promise((resolve, reject) => { 103 | const reader = new FileReader() 104 | reader.readAsDataURL(file) 105 | reader.onload = (event) => { 106 | if (event.target) { 107 | resolve(event.target.result) 108 | } else { 109 | reject( 110 | console.error(event) 111 | ) 112 | } 113 | } 114 | }) 115 | } -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 这是一个Ts+Rollup的打包模板-盲僧幼儿园出品 9 | 10 | 11 | 12 | 13 |
14 |

Hello 我是扫地盲僧!

15 |

为了快速开发TS插件,我封装了一个简洁版的TS打包脚手架

16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 32 |
33 | 34 | 111 | 117 | 118 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | lang: 'zh-CN', 3 | title: 'FasTool', 4 | base: '/fastool/', 5 | description: '一个短小而精悍的现代JavaScript使用工具库', 6 | lastUpdated: true, 7 | head: [ 8 | // 添加图标 9 | ['link', { rel: 'icon', href: '/favicon.ico' }], 10 | ], 11 | themeConfig: { 12 | nav: nav(), 13 | sidebar: { 14 | '/guide/': sidebarGuide(), 15 | }, 16 | socialLinks: [{ icon: 'github', link: 'https://github.com/tobe-fe-dalao/fastool' }], 17 | editLink: { 18 | pattern: 'https://github.com/tobe-fe-dalao/fastool/tree/doc/docs/:path', 19 | text: '在GitHub编辑此页', 20 | }, 21 | footer: { 22 | message: 'Released under the MIT License.', 23 | copyright: 'Copyright © 2022-present BlindMonk', 24 | }, 25 | algolia: { 26 | appId: 'ZZNDXJ2XIU', 27 | apiKey: 'a6e9af943b8057a410b4059a1bb3306f', 28 | indexName: 'FasTool', 29 | }, 30 | }, 31 | } 32 | 33 | function nav() { 34 | return [ 35 | { text: '介绍', link: '/guide/' }, 36 | { text: '掘金', link: 'https://juejin.cn/user/3016715636842622' }, 37 | { text: '加入我们', link: 'https://github.com/tobe-fe-dalao/fastool' }, 38 | ] 39 | } 40 | 41 | function sidebarGuide() { 42 | return [ 43 | { 44 | text: '介绍', 45 | collapsible: true, 46 | items: [ 47 | { text: '介绍', link: '/guide/' }, 48 | { text: '快速上手', link: '/guide/start' }, 49 | { text: '参与编辑', link: '/guide/EditMd' }, 50 | ], 51 | }, 52 | { 53 | text: 'Browser浏览器', 54 | items: [ 55 | { text: 'Browser', link: '/guide/Browser' }, 56 | { text: 'Device设备', link: '/guide/Device' } 57 | ] 58 | }, 59 | { 60 | text: '本地储存', 61 | collapsible: true, 62 | items: [ 63 | { text: 'Cookie操作', link: '/guide/Cookie' }, 64 | { text: 'Storage操作', link: '/guide/Storage' }, 65 | ], 66 | }, 67 | { 68 | text: '实用工具函数', 69 | collapsible: false, 70 | items: [ 71 | { text: 'Regex校验', link: '/guide/Regex' }, 72 | { text: 'Time操作', link: '/guide/Time' } 73 | ] 74 | }, 75 | { 76 | text: 'Random操作', 77 | collapsible: false, 78 | items: [ 79 | { text: 'Random操作', link: '/guide/Random' } 80 | ] 81 | }, 82 | { 83 | text: 'Media操作', 84 | collapsible: false, 85 | items: [ 86 | { text: 'Media操作', link: '/guide/Media' } 87 | ] 88 | }, 89 | { 90 | text: 'Polyfill兼容', 91 | collapsible: false, 92 | items: [ 93 | { text: 'Polyfill兼容', link: '/guide/Polyfill' } 94 | ] 95 | }, 96 | { 97 | text: 'Canvas操作', 98 | collapsible: false, 99 | items: [ 100 | { text: 'Canvas操作', link: '/guide/Canvas' } 101 | ] 102 | }, 103 | { 104 | text: 'Event事件', 105 | collapsible: false, 106 | items: [ 107 | { text: 'Event事件', link: '/guide/Event' } 108 | ] 109 | }, 110 | { 111 | text: 'Plugins插件库', 112 | collapsible: false, 113 | items: [ 114 | { text: 'Plugins插件库', link: '/guide/Plugins' } 115 | ] 116 | }, 117 | { 118 | text: 'Fn函数', 119 | collapsible: false, 120 | items: [ 121 | { text: 'Fn函数', link: '/guide/Fn' } 122 | ] 123 | }, 124 | { 125 | text: 'Tools工具库', 126 | collapsible: false, 127 | items: [ 128 | { text: 'Tools工具库', link: '/guide/Tools' } 129 | ] 130 | }, 131 | { 132 | text: 'Regex校验', 133 | collapsible: false, 134 | items: [ 135 | { text: 'Regex校验', link: '/guide/Regex' } 136 | ] 137 | }, 138 | { 139 | text: 'URL链接操作', 140 | collapsible: false, 141 | items: [ 142 | { text: 'URL链接操作', link: '/guide/URL' } 143 | ] 144 | }, 145 | { 146 | text: 'Number操作', 147 | collapsible: false, 148 | items: [ 149 | { text: 'Number操作', link: '/guide/Number' } 150 | ] 151 | }, 152 | { 153 | text: 'String操作', 154 | collapsible: false, 155 | items: [ 156 | { text: 'String操作', link: '/guide/String' } 157 | ] 158 | }, 159 | ] 160 | } 161 | -------------------------------------------------------------------------------- /src/events/mitt.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func mitt 3 | * @desc mitt 事件订阅发布及调度中心 4 | * @github https://github.com/developit/mitt 5 | * @example 6 | * const emitter = mitt(); 7 | * emitter.on('foo', e => console.log('foo', e) )// 事件监听 8 | * function onFoo() {} 9 | * emitter.on('foo', onFoo) // listen 10 | * emitter.off('foo', onFoo) // unlisten 11 | */ 12 | export type EventType = string | symbol; 13 | // An event handler can take an optional event argument 14 | // and should not return a value 15 | export type Handler = (event: T) => void; 16 | export type WildcardHandler> = ( 17 | type: keyof T, 18 | event: T[keyof T] 19 | ) => void; 20 | 21 | // An array of all currently registered event handlers for a type 22 | export type EventHandlerList = Array>; 23 | export type WildCardEventHandlerList> = Array>; 24 | 25 | // A map of event types and their corresponding event handlers. 26 | export type EventHandlerMap> = Map< 27 | keyof Events | '*', 28 | EventHandlerList | WildCardEventHandlerList 29 | >; 30 | 31 | export interface Emitter> { 32 | all: EventHandlerMap; 33 | 34 | on(type: Key, handler: Handler): void; 35 | on(type: '*', handler: WildcardHandler): void; 36 | 37 | off(type: Key, handler?: Handler): void; 38 | off(type: '*', handler: WildcardHandler): void; 39 | 40 | emit(type: Key, event: Events[Key]): void; 41 | emit(type: undefined extends Events[Key] ? Key : never): void; 42 | } 43 | 44 | /** 45 | * Mitt: Tiny (~200b) functional event emitter / pubsub. 46 | * @name mitt 47 | * @returns {Mitt} 48 | */ 49 | export default function mitt>( 50 | all?: EventHandlerMap 51 | ): Emitter { 52 | type GenericEventHandler = 53 | | Handler 54 | | WildcardHandler; 55 | all = all || new Map(); 56 | 57 | return { 58 | 59 | /** 60 | * A Map of event names to registered handler functions. 61 | */ 62 | all, 63 | 64 | /** 65 | * Register an event handler for the given type. 66 | * @param {string|symbol} type Type of event to listen for, or `'*'` for all events 67 | * @param {Function} handler Function to call in response to given event 68 | * @memberOf mitt 69 | */ 70 | on(type: Key, handler: GenericEventHandler) { 71 | const handlers: Array | undefined = all!.get(type); 72 | if (handlers) { 73 | handlers.push(handler); 74 | } 75 | else { 76 | all!.set(type, [handler] as EventHandlerList); 77 | } 78 | }, 79 | 80 | /** 81 | * Remove an event handler for the given type. 82 | * If `handler` is omitted, all handlers of the given type are removed. 83 | * @param {string|symbol} type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler) 84 | * @param {Function} [handler] Handler function to remove 85 | * @memberOf mitt 86 | */ 87 | off(type: Key, handler?: GenericEventHandler) { 88 | const handlers: Array | undefined = all!.get(type); 89 | if (handlers) { 90 | if (handler) { 91 | handlers.splice(handlers.indexOf(handler) >>> 0, 1); 92 | } 93 | else { 94 | all!.set(type, []); 95 | } 96 | } 97 | }, 98 | 99 | /** 100 | * Invoke all handlers for the given type. 101 | * If present, `'*'` handlers are invoked after type-matched handlers. 102 | * 103 | * Note: Manually firing '*' handlers is not supported. 104 | * 105 | * @param {string|symbol} type The event type to invoke 106 | * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler 107 | * @memberOf mitt 108 | */ 109 | emit(type: Key, evt?: Events[Key]) { 110 | let handlers = all!.get(type); 111 | if (handlers) { 112 | (handlers as EventHandlerList) 113 | .slice() 114 | .map((handler) => { 115 | handler(evt!); 116 | }); 117 | } 118 | 119 | handlers = all!.get('*'); 120 | if (handlers) { 121 | (handlers as WildCardEventHandlerList) 122 | .slice() 123 | .map((handler) => { 124 | handler(type, evt!); 125 | }); 126 | } 127 | } 128 | }; 129 | } -------------------------------------------------------------------------------- /src/regex/index.ts: -------------------------------------------------------------------------------- 1 | // 整理常用的校验规则-扫地盲僧含泪整理转载署名-授权许可证 2 | // TODO:干到这里了 3 | /** 4 | * @func isChines 5 | * @param {string} str 6 | * @returns {boolean} 7 | * @desc 是否是中文 8 | */ 9 | export const isChines = (str: string): boolean => { 10 | const reg = /^[\u4E00-\u9FA5]+$/; 11 | return reg.test(str); 12 | } 13 | 14 | /** 15 | * @func isCard 16 | * @param {string} str 身份证号 17 | * @param {number} type 1:15位,2:18位,默认0 同时匹配15位和18位 18 | * @returns {boolean} 19 | * @desc 是否为身份证号: 支持(1/2)代,15位或18位 20 | */ 21 | export const isIdCard = (str: string, type: number = 0): boolean => { 22 | // 1代身份证 23 | const reg1 = /^[1-9]\d{7}(?:0\d|10|11|12)(?:0[1-9]|[1-2][\d]|30|31)\d{3}$/; 24 | // 2代身份证 25 | const reg2 = /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/; 26 | const reg = 27 | /^\d{6}((((((19|20)\d{2})(0[13-9]|1[012])(0[1-9]|[12]\d|30))|(((19|20)\d{2})(0[13578]|1[02])31)|((19|20)\d{2})02(0[1-9]|1\d|2[0-8])|((((19|20)([13579][26]|[2468][048]|0[48]))|(2000))0229))\d{3})|((((\d{2})(0[13-9]|1[012])(0[1-9]|[12]\d|30))|((\d{2})(0[13578]|1[02])31)|((\d{2})02(0[1-9]|1\d|2[0-8]))|(([13579][26]|[2468][048]|0[048])0229))\d{2}))(\d|X|x)$/; 28 | 29 | switch (type) { 30 | case 1: 31 | return reg1.test(str); 32 | case 2: 33 | return reg2.test(str); 34 | default: 35 | return reg.test(str); 36 | } 37 | }; 38 | 39 | /** 40 | * @func isPostCode 41 | * @param {number} value 42 | * @returns {boolean} 43 | * @desc 校验是否是大陆邮政编码 44 | */ 45 | export const isPostCode = (value: number): boolean => { 46 | return /^[1-9][0-9]{5}$/.test(value.toString()); 47 | } 48 | 49 | /** 50 | * @func isIPv6 51 | * @param {string} str 52 | * @returns {boolean} 53 | * @desc 校验是否是IPv6地址 54 | */ 55 | export const isIPv6 = (str: string): boolean => { 56 | return Boolean(str.match(/:/g) ? str.match(/:/g)!.length <= 7 : false && /::/.test(str) ? /^([\da-f]{1,4}(:|::)){1,6}[\da-f]{1,4}$/i.test(str) : /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i.test(str)); 57 | } 58 | /** 59 | * @func isEmail 60 | * @param {string} value 61 | * @returns {boolean} 62 | * @desc 是否是邮箱 63 | */ 64 | export const isEmail = (value: string): boolean => { 65 | const reg = 66 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 67 | return reg.test(value); 68 | } 69 | 70 | /** 71 | * @func isTelNumber 72 | * @param {string} str 73 | * @returns {boolean} 74 | * @description 是否是手机号 75 | */ 76 | export const isTelNumber = (str: string): boolean => { 77 | const reg = /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/; 78 | return reg.test(str); 79 | } 80 | 81 | /** 82 | * @func isHasEmoji 83 | * @param {string} value 84 | * @returns {boolean} 85 | * @description 是否包含emoji表情 86 | */ 87 | export const isHasEmoji = (value: string): boolean => { 88 | value = String(value); 89 | for (let i = 0; i < value.length; i++) { 90 | const hs = value.charCodeAt(i); 91 | if (0xd800 <= hs && hs <= 0xdbff) { 92 | if (value.length > 1) { 93 | const ls = value.charCodeAt(i + 1); 94 | const uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000; 95 | if (0x1d000 <= uc && uc <= 0x1f77f) { 96 | return true; 97 | } 98 | } 99 | } else if (value.length > 1) { 100 | const ls = value.charCodeAt(i + 1); 101 | if (ls == 0x20e3) { 102 | return true; 103 | } 104 | } else { 105 | if (0x2100 <= hs && hs <= 0x27ff) { 106 | return true; 107 | } else if (0x2B05 <= hs && hs <= 0x2b07) { 108 | return true; 109 | } else if (0x2934 <= hs && hs <= 0x2935) { 110 | return true; 111 | } else if (0x3297 <= hs && hs <= 0x3299) { 112 | return true; 113 | } else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030 114 | || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b 115 | || hs == 0x2b50) { 116 | return true; 117 | } 118 | } 119 | } 120 | return false; 121 | } 122 | 123 | /** 124 | * @func isUrl 125 | * @param {string} str 126 | * @returns {boolean} 127 | * @desc 校验是否是URL 128 | */ 129 | export const isUrl = (str: string): boolean => { 130 | const reg = /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/; 131 | return reg.test(str); 132 | } 133 | 134 | /** 135 | * @func isColor 136 | * @desc 判断字符串是否是十六进制的颜色值 137 | * @param {string} value 需要判断的数据 138 | * @returns {boolean} 校验是否是十六进制的颜色值 139 | * @example if (isColor(str)) { doSomething } 140 | */ 141 | export const isColor = function(value: string): boolean { 142 | return /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/.test(value); 143 | } 144 | -------------------------------------------------------------------------------- /src/time/index.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @func diffDays 4 | * @desc 📝比较两个日期相差的天数 5 | * @param {Date} date1 6 | * @param {Date} date2 7 | * @returns {number} 8 | */ 9 | export function diffDays(date1: Date, date2: Date): number { 10 | const time1 = date1.getTime(); 11 | const time2 = date2.getTime(); 12 | const diff = Math.abs(time1 >= time2 ? time1 - time2 : time2 - time1); 13 | return Math.floor(diff / (1000 * 60 * 60 * 24)); 14 | } 15 | 16 | /** 17 | * @func getNowTime 18 | * @desc 📝获取当前时间 19 | * @returns {Date} 20 | * @example getNowTime() 21 | */ 22 | export const getNowTime = () => { 23 | const now = new Date(); 24 | const year = now.getFullYear(); 25 | const month = now.getMonth(); 26 | const date = now.getDate() >= 10 ? now.getDate() : ('0' + now.getDate()); 27 | const hour = now.getHours() >= 10 ? now.getHours() : ('0' + now.getHours()); 28 | const miu = now.getMinutes() >= 10 ? now.getMinutes() : ('0' + now.getMinutes()); 29 | const sec = now.getSeconds() >= 10 ? now.getSeconds() : ('0' + now.getSeconds()); 30 | return +year + "年" + (month + 1) + "月" + date + "日 " + hour + ":" + miu + ":" + sec; 31 | } 32 | 33 | 34 | export const formatDate = (format: string, time?: Date) => { 35 | let date = time ? new Date(time) : new Date(), 36 | Y = date.getFullYear() + '', 37 | M = date.getMonth() + 1, 38 | D = date.getDate(), 39 | H = date.getHours(), 40 | m = date.getMinutes(), 41 | s = date.getSeconds(); 42 | return format.replace(/YYYY|yyyy/g, Y) 43 | .replace(/YY|yy/g, Y.substr(2, 2)) 44 | .replace(/MM/g, (M < 10 ? '0' : '') + M) 45 | .replace(/DD/g, (D < 10 ? '0' : '') + D) 46 | .replace(/HH|hh/g, (H < 10 ? '0' : '') + H) 47 | .replace(/mm/g, (m < 10 ? '0' : '') + m) 48 | .replace(/ss/g, (s < 10 ? '0' : '') + s) 49 | } 50 | /** 51 | * @func formatPassTime 52 | * @desc 格式化${startTime}距现在的已过时间 53 | * @param {Date} startTime 时间戳 54 | * @return {String} 55 | */ 56 | export const formatPassTime = (startTime?: Date) => { 57 | let currentTime = Date.parse(new Date().toString()), 58 | starT = startTime ? Date.parse(new Date(startTime).toString()) : currentTime, 59 | time = currentTime - starT, 60 | day = parseInt(String(time / (1000 * 60 * 60 * 24))), 61 | hour = parseInt(String(time / (1000 * 60 * 60))), 62 | min = parseInt(String(time / (1000 * 60))), 63 | sec = parseInt(String(time / 1000)), 64 | month = parseInt(String(day / 30)), 65 | year = parseInt(String(month / 12)); 66 | if (year) return year + "年前" 67 | if (month) return month + "个月前" 68 | if (day) return day + "天前" 69 | if (hour) return hour + "小时前" 70 | if (min) return min + "分钟前" 71 | if (sec) return sec + "秒前" 72 | else return '刚刚' 73 | } 74 | 75 | /** 76 | * @func formatRemainTime 77 | * @desc 格式化现在距${endTime}的剩余时间 78 | * @param {Date} endTime 79 | * @return {String} 80 | */ 81 | export const formatRemainTime = (endTime: Date) => { 82 | let startDate = new Date(); //开始时间 83 | let endDate = new Date(endTime); //结束时间 84 | let t = endDate.getTime() - startDate.getTime(); //时间差 85 | let d = 0, 86 | h = 0, 87 | m = 0, 88 | s = 0; 89 | if (t >= 0) { 90 | d = Math.floor(t / 1000 / 3600 / 24); 91 | h = Math.floor(t / 1000 / 60 / 60 % 24); 92 | m = Math.floor(t / 1000 / 60 % 60); 93 | s = Math.floor(t / 1000 % 60); 94 | } 95 | return d + "天 " + h + "小时 " + m + "分钟 " + s + "秒"; 96 | } 97 | /** 98 | * @func isLeapYear 99 | * @desc 是否为闰年 100 | * @param {Number} year 101 | * @returns {Boolean} 102 | * @example isLeapYear(2020) 103 | */ 104 | 105 | export const isLeapYear = (year: number) => { 106 | if (0 === year % 4 && (year % 100 !== 0 || year % 400 === 0)) { 107 | return true 108 | } 109 | return false; 110 | } 111 | /** 112 | * @func getDaysByMonth 113 | * @desc 获取指定日期月份的总天数 114 | * @param {Date} time 115 | * @return {Number} 116 | */ 117 | export const getDaysByMonth = (time: Date) => { 118 | time = new Date(time); 119 | let year = time.getFullYear(); 120 | let month = time.getMonth() + 1; 121 | return new Date(year, month, 0).getDate(); 122 | } 123 | 124 | /** 125 | * @func timeLeft 126 | * @desc ${startTime - endTime}的剩余时间,startTime大于endTime时,均返回0 127 | * @param { Date | String } startTime 128 | * @param { Date | String } endTime 129 | * @returns { Object } { d, h, m, s } 天 时 分 秒 130 | */ 131 | export const timeLeft = (startTime: Date | string, endTime: Date | String) => { 132 | if (!startTime || !endTime) { 133 | return 134 | } 135 | let startDate, endDate; 136 | if (startTime instanceof Date) { 137 | startDate = startTime; 138 | } else { 139 | startDate = new Date(startTime.replace(/-/g, '/')) //开始时间 140 | } 141 | if (endTime instanceof Date) { 142 | endDate = endTime; 143 | } else { 144 | endDate = new Date(endTime.replace(/-/g, '/')) //结束时间 145 | } 146 | let t = endDate.getTime() - startDate.getTime() 147 | let d = 0, 148 | h = 0, 149 | m = 0, 150 | s = 0 151 | if (t >= 0) { 152 | d = Math.floor(t / 1000 / 3600 / 24) 153 | h = Math.floor(t / 1000 / 60 / 60 % 24) 154 | m = Math.floor(t / 1000 / 60 % 60) 155 | s = Math.floor(t / 1000 % 60) 156 | } 157 | return { d, h, m, s } 158 | } -------------------------------------------------------------------------------- /src/tools/print.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func Print 3 | * @param {string} selector 4 | * @param {any} options 5 | * @desc 打印 6 | * @example Print('#print', {}) 7 | */ 8 | class Print { 9 | protected selector: string; 10 | protected options: any; 11 | protected rootEl: HTMLElement | null = null; 12 | constructor(selector: string, options: any) { 13 | this.selector = selector; 14 | this.options = this.extend( 15 | { 16 | noPrint: ".no-print", 17 | }, 18 | options 19 | ); 20 | this.rootEl = document.querySelector(selector); 21 | 22 | // if (!(this instanceof Print)) return new Print(selector, options); 23 | this.init(); 24 | } 25 | init() { 26 | if (!this.rootEl) { 27 | return; 28 | } 29 | let content = this.getStyle() + this.getHtml(); 30 | this.writeIframe(content); 31 | } 32 | extend(obj: object, obj2: object) { 33 | for (let k in obj2) { 34 | obj[k] = obj2[k]; 35 | } 36 | return obj; 37 | } 38 | 39 | getStyle() { 40 | let str = "", 41 | styles = document.querySelectorAll("style,link"); 42 | for (let i = 0; i < styles.length; i++) { 43 | str += styles[i].outerHTML; 44 | } 45 | str += 46 | ""; 49 | 50 | return str; 51 | } 52 | 53 | getHtml() { 54 | let inputs = document.querySelectorAll("input"); 55 | let textareas = document.querySelectorAll("textarea"); 56 | let selects = document.querySelectorAll("select"); 57 | 58 | for (let k = 0; k < inputs.length; k++) { 59 | if (inputs[k].type == "checkbox" || inputs[k].type == "radio") { 60 | if (inputs[k].checked == true) { 61 | inputs[k].setAttribute("checked", "checked"); 62 | } else { 63 | inputs[k].removeAttribute("checked"); 64 | } 65 | } else if (inputs[k].type == "text") { 66 | inputs[k].setAttribute("value", inputs[k].value); 67 | } else { 68 | inputs[k].setAttribute("value", inputs[k].value); 69 | } 70 | } 71 | 72 | for (let k2 = 0; k2 < textareas.length; k2++) { 73 | if (textareas[k2].type == "textarea") { 74 | textareas[k2].innerHTML = textareas[k2].value; 75 | } 76 | } 77 | 78 | for (let k3 = 0; k3 < selects.length; k3++) { 79 | if (selects[k3].type == "select-one") { 80 | let child = selects[k3].children; 81 | for (let i in child) { 82 | if (child[i].tagName == "OPTION") { 83 | if ((child[i] as any).selected == true) { 84 | child[i].setAttribute("selected", "selected"); 85 | } else { 86 | child[i].removeAttribute("selected"); 87 | } 88 | } 89 | } 90 | } 91 | } 92 | // 包裹要打印的元素 93 | // fix: https://github.com/xyl66/vuePlugs_printjs/issues/36 94 | let outerHTML = this.wrapperRefDom(this.rootEl).outerHTML; 95 | return outerHTML; 96 | } 97 | // 向父级元素循环,包裹当前需要打印的元素 98 | // 防止根级别开头的 css 选择器不生效 99 | wrapperRefDom(refDom: any) { 100 | let prevDom = null; 101 | let currDom = refDom; 102 | // 判断当前元素是否在 body 中,不在文档中则直接返回该节点 103 | if (!this.isInBody(currDom)) return currDom; 104 | 105 | while (currDom) { 106 | if (prevDom) { 107 | let element = currDom.cloneNode(false); 108 | element.appendChild(prevDom); 109 | prevDom = element; 110 | } else { 111 | prevDom = currDom.cloneNode(true); 112 | } 113 | 114 | currDom = currDom.parentElement; 115 | } 116 | return prevDom; 117 | } 118 | 119 | writeIframe(content: any) { 120 | let w: any, 121 | doc, 122 | iframe = document.createElement("iframe"), 123 | f: any = document.body.appendChild(iframe); 124 | iframe.id = "myIframe"; 125 | //iframe.style = "position:absolute;width:0;height:0;top:-10px;left:-10px;"; 126 | iframe.setAttribute( 127 | "style", 128 | "position:absolute;width:0;height:0;top:-10px;left:-10px;" 129 | ); 130 | w = f.contentWindow || f.contentDocument; 131 | doc = f.contentDocument || f.contentWindow.document; 132 | doc.open(); 133 | doc.write(content); 134 | doc.close(); 135 | let _this = this; 136 | iframe.onload = function () { 137 | _this.toPrint(w); 138 | setTimeout(function () { 139 | document.body.removeChild(iframe); 140 | }, 100); 141 | }; 142 | } 143 | 144 | toPrint(frameWindow: any) { 145 | try { 146 | setTimeout(function () { 147 | frameWindow.focus(); 148 | try { 149 | if (!frameWindow.document.execCommand("print", false, null)) { 150 | frameWindow.print(); 151 | } 152 | } catch (e) { 153 | frameWindow.print(); 154 | } 155 | frameWindow.close(); 156 | }, 10); 157 | } catch (err) { 158 | console.log("err", err); 159 | } 160 | } 161 | // 检查一个元素是否是 body 元素的后代元素且非 body 元素本身 162 | isInBody(node: any) { 163 | return node === document.body ? false : document.body.contains(node); 164 | } 165 | }; 166 | // const MyPlugin = {}; 167 | // MyPlugin.install = function (Vue, options) { 168 | // // 4. 添加实例方法 169 | // Vue.prototype.$print = Print; 170 | // }; 171 | export default Print; 172 | -------------------------------------------------------------------------------- /src/function/tool.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @func throttle 3 | * @desc 函数节流,每隔一段时间执行一次,防止函数过于频繁调用,导致性能问题 4 | * @param {Function} fn 将要处理的函数 5 | * @param {number} wait 时间, 单位为毫秒 6 | * @returns 节流函数 7 | * @example throttle(fn, wait) 8 | */ 9 | export const throttle = (fn: Function, wait: number = 300): Function => { 10 | let isRunning = false; 11 | return (...args: any[]) => { 12 | if (isRunning) return; 13 | isRunning = true; 14 | setTimeout(() => { 15 | fn(...args); 16 | isRunning = false; 17 | }, wait); 18 | } 19 | } 20 | 21 | /** 22 | * @func debounce 23 | * @desc 防抖函数 24 | * 与throttle不同的是,debounce保证一个函数在多少毫秒内不再被触发,只会执行一次, 25 | * 要么在第一次调用return的防抖函数时执行,要么在延迟指定毫秒后调用。 26 | * @param {function} fn 将要处理的函数 27 | * @param {number} wait 时间, 单位为毫秒 28 | * @param immediate 是否在触发事件后 在时间段n开始,立即执行,否则是时间段n结束,才执行 29 | * @returns 包装好的节流函数 30 | * @example debounce(fn, wait) 31 | * 32 | */ 33 | export function debounce(fn: Function, wait: number, immediate: boolean = false) { 34 | let timer: any = null; 35 | return function (this: unknown, ...args: any) { 36 | if (timer) { 37 | clearTimeout(timer); 38 | timer = null; 39 | } 40 | if (immediate) { 41 | if (!timer) fn.apply(this, args); 42 | timer = setTimeout(function () {//n 秒内 多次触发事件,重新计算.timeer 43 | timer = null;//n 秒内没有触发事件 timeer 设置为null,保证了n 秒后能重新触发事件 flag = true = !timmer 44 | }, wait) 45 | } else { 46 | timer = setTimeout(() => { fn.apply(this, args) }, wait); 47 | } 48 | }; 49 | } 50 | 51 | /** 52 | * @func deepClone 53 | * @param {object} obj 将要复制的对象 54 | * @param {string} hash 哈希值 55 | * @returns {object} 复制后的对象 56 | * @desc 深度复制对象 57 | */ 58 | 59 | export const deepClone = (obj: object, hash: any = new WeakMap()): object => { 60 | // 日期对象直接返回一个新的日期对象 61 | if (obj instanceof Date) { 62 | return new Date(obj); 63 | } 64 | //正则对象直接返回一个新的正则对象 65 | if (obj instanceof RegExp) { 66 | return new RegExp(obj); 67 | } 68 | //如果循环引用,就用 weakMap 来解决 69 | if (hash.has(obj)) { 70 | return hash.get(obj); 71 | } 72 | // 获取对象所有自身属性的描述 73 | let allDesc = Object.getOwnPropertyDescriptors(obj); 74 | // 遍历传入参数所有键的特性 75 | let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc) 76 | hash.set(obj, cloneObj) 77 | for (let key of Reflect.ownKeys(obj)) { 78 | if (typeof obj[key] === 'object' && obj[key] !== null) { 79 | cloneObj[key] = deepClone(obj[key], hash); 80 | } else { 81 | cloneObj[key] = obj[key]; 82 | } 83 | } 84 | return cloneObj 85 | } 86 | 87 | /** 88 | * @func fetchUtil 89 | * @desc 封装fetch函数,用Promise做回调 90 | * @returns {Promise} 91 | * @type {{get: (function(*=)), post: (function(*=, *=))}} 92 | * @example fetchUtil.get(url) 93 | */ 94 | export const fetchUtil = { 95 | get: (url: string) => { 96 | return new Promise((resolve, reject) => { 97 | fetch(url, { 98 | method: 'GET', 99 | headers: { 100 | 'Content-Type': 'application/x-www-form-urlencoded', 101 | } 102 | }).then((response) => response.json()).then(response => { 103 | resolve(response); 104 | }).catch(err => { 105 | reject(new Error(err)); 106 | }); 107 | }); 108 | }, 109 | post: (url: string, params: any) => { 110 | return new Promise((resolve, reject) => { 111 | fetch(url, { 112 | method: 'POST', 113 | headers: { 114 | 'Content-Type': 'application/x-www-form-urlencoded', 115 | }, 116 | body: params 117 | }).then((response) => response.json()).then(response => { 118 | resolve(response); 119 | }).catch(err => { 120 | reject(new Error(err)); 121 | }); 122 | }); 123 | } 124 | }; 125 | 126 | /** 127 | * @func getTypeOf 128 | * @param {unknown} param 129 | * @returns {string} 130 | * @desc 获取参数类型 131 | * @example getTypeOf(...) 132 | * String, Number, Boolean, Symbol, Null, Undefined, Object 133 | * Array, RegExp, Date, Error, Function, AsyncFunction, HTMLDocument 134 | */ 135 | export const getTypeOf = (param: unknown): string => { 136 | const type = Object.prototype.toString.call(param).slice(8, -1); 137 | return type.toLowerCase(); 138 | }; 139 | 140 | /** 141 | * @func sleep 142 | * @param {number} wait 143 | * @returns {Promise} 144 | * @desc 睡眠函数 145 | */ 146 | export const sleep = async (wait: number) => new Promise(resolve => setTimeout(resolve, wait)); 147 | 148 | /** 149 | * @func importPluginByUrl 150 | * @desc 根据url引入插件模块 151 | * @param {string} cdnUrl 152 | * @param {string} pluginName 该插件对应的名称 153 | * @param {string} newName 重新定义的名称 154 | * @param {boolean} isEsm 是否是esm模块 155 | * @returns {Promise} 156 | * @example importPluginByUrl(cdnUrl, pluginName) 157 | */ 158 | export function importPluginByUrl(cdnUrl: string, pluginName: string, newName: string, isEsm: boolean = true): Promise | void { 159 | if (isEsm) { 160 | // TODO: 若不支持 ESM 或者需要 polyfill 可以使用 SystemJS 等库 161 | return import(cdnUrl as any) 162 | .then((module) => { 163 | window[newName] = module; 164 | console.log(module); 165 | return module; 166 | }); 167 | } else { 168 | return new Promise((resolve, reject) => { 169 | const scriptList = Array.from(document.getElementsByTagName('script')); 170 | const hasInject = window[pluginName] && scriptList.some(script => script.getAttribute('src') === cdnUrl) 171 | if (hasInject) { 172 | window[newName] = window[pluginName]; 173 | resolve(window[pluginName]?.default ?? window[pluginName]); 174 | } else { 175 | const script = document.createElement('script') 176 | script.setAttribute('src', cdnUrl) 177 | document.head.appendChild(script) 178 | script.onload = () => { 179 | window[newName] = window[pluginName]; 180 | resolve(window[pluginName]?.default ?? window[pluginName]); 181 | } 182 | script.onerror = () => { 183 | reject(`加载${pluginName}失败`) 184 | } 185 | } 186 | }); 187 | } 188 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015-2025 MaleWeb Group Holding Ltd 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /examples/logo.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 8 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 73 | 74 | 75 | 76 | 77 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /lib/esm.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FingerprintJS v3.3.3 - Copyright (c) FingerprintJS, Inc, 2022 (https://fingerprintjs.com) 3 | * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. 4 | * 5 | * This software contains code from open-source projects: 6 | * MurmurHash3 by Karan Lyons (https://github.com/karanlyons/murmurHash3.js) 7 | */ 8 | var e=function(){return e=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&a[a.length-1])||6!==o[0]&&2!==o[0])){i=0;continue}if(3===o[0]&&(!a||o[1]>a[0]&&o[1]=t+o?(t=c,[4,a(0)]):[3,3]):[3,4];case 2:n.sent(),n.label=3;case 3:return++i,[3,1];case 4:return[2]}}))}))}function c(e,t){e=[e[0]>>>16,65535&e[0],e[1]>>>16,65535&e[1]],t=[t[0]>>>16,65535&t[0],t[1]>>>16,65535&t[1]];var n=[0,0,0,0];return n[3]+=e[3]+t[3],n[2]+=n[3]>>>16,n[3]&=65535,n[2]+=e[2]+t[2],n[1]+=n[2]>>>16,n[2]&=65535,n[1]+=e[1]+t[1],n[0]+=n[1]>>>16,n[1]&=65535,n[0]+=e[0]+t[0],n[0]&=65535,[n[0]<<16|n[1],n[2]<<16|n[3]]}function u(e,t){e=[e[0]>>>16,65535&e[0],e[1]>>>16,65535&e[1]],t=[t[0]>>>16,65535&t[0],t[1]>>>16,65535&t[1]];var n=[0,0,0,0];return n[3]+=e[3]*t[3],n[2]+=n[3]>>>16,n[3]&=65535,n[2]+=e[2]*t[3],n[1]+=n[2]>>>16,n[2]&=65535,n[2]+=e[3]*t[2],n[1]+=n[2]>>>16,n[2]&=65535,n[1]+=e[1]*t[3],n[0]+=n[1]>>>16,n[1]&=65535,n[1]+=e[2]*t[2],n[0]+=n[1]>>>16,n[1]&=65535,n[1]+=e[3]*t[1],n[0]+=n[1]>>>16,n[1]&=65535,n[0]+=e[0]*t[3]+e[1]*t[2]+e[2]*t[1]+e[3]*t[0],n[0]&=65535,[n[0]<<16|n[1],n[2]<<16|n[3]]}function s(e,t){return 32===(t%=64)?[e[1],e[0]]:t<32?[e[0]<>>32-t,e[1]<>>32-t]:(t-=32,[e[1]<>>32-t,e[0]<>>32-t])}function l(e,t){return 0===(t%=64)?e:t<32?[e[0]<>>32-t,e[1]<>>1]),e=d(e=u(e,[4283543511,3981806797]),[0,e[0]>>>1]),e=d(e=u(e,[3301882366,444984403]),[0,e[0]>>>1])}function h(e,t){t=t||0;var n,r=(e=e||"").length%16,a=e.length-r,o=[0,t],i=[0,t],h=[0,0],v=[0,0],p=[2277735313,289559509],m=[1291169091,658871167];for(n=0;n>>0).toString(16)).slice(-8)+("00000000"+(o[1]>>>0).toString(16)).slice(-8)+("00000000"+(i[0]>>>0).toString(16)).slice(-8)+("00000000"+(i[1]>>>0).toString(16)).slice(-8)}function v(e){return parseInt(e)}function p(e){return parseFloat(e)}function m(e,t){return"number"==typeof e&&isNaN(e)?t:e}function g(e){return e.reduce((function(e,t){return e+(t?1:0)}),0)}function b(e,t){if(void 0===t&&(t=1),Math.abs(t)>=1)return Math.round(e/t)*t;var n=1/t;return Math.round(e*n)/n}function w(e){return e&&"object"==typeof e&&"message"in e?e:{message:e}}function y(e,r,c){var u=Object.keys(e).filter((function(e){return!function(e,t){for(var n=0,r=e.length;n=4}function C(){var e=window,t=navigator;return g(["msWriteProfilerMark"in e,"MSStream"in e,"msLaunchUri"in t,"msSaveBlob"in t])>=3&&!k()}function A(){var e=window,t=navigator;return g(["webkitPersistentStorage"in t,"webkitTemporaryStorage"in t,0===t.vendor.indexOf("Google"),"webkitResolveLocalFileSystemURL"in e,"BatteryManager"in e,"webkitMediaStream"in e,"webkitSpeechGrammar"in e])>=5}function x(){var e=window,t=navigator;return g(["ApplePayError"in e,"CSSPrimitiveValue"in e,"Counter"in e,0===t.vendor.indexOf("Apple"),"getStorageUpdates"in t,"WebKitMediaKeys"in e])>=4}function S(){var e=window;return g(["safari"in e,!("DeviceMotionEvent"in e),!("ongestureend"in e),!("standalone"in navigator)])>=3}function M(){var e,t,n=window;return g(["buildID"in navigator,"MozAppearance"in(null!==(t=null===(e=document.documentElement)||void 0===e?void 0:e.style)&&void 0!==t?t:{}),"onmozfullscreenchange"in n,"mozInnerScreenX"in n,"CSSMozDocumentRule"in n,"CanvasCaptureMediaStream"in n])>=4}function P(){var e=document;return e.fullscreenElement||e.msFullscreenElement||e.mozFullScreenElement||e.webkitFullscreenElement||null}function _(){var e=A(),t=M();if(!e&&!t)return!1;var n=window;return g(["onorientationchange"in n,"orientation"in n,e&&!("SharedWorker"in n),t&&/android/i.test(navigator.appVersion)])>=2}function T(e){var t=new Error(e);return t.name=e,t}function E(e,r,o){var i,c,u;return void 0===o&&(o=50),t(this,void 0,void 0,(function(){var t,s;return n(this,(function(n){switch(n.label){case 0:t=document,n.label=1;case 1:return t.body?[3,3]:[4,a(o)];case 2:return n.sent(),[3,1];case 3:s=t.createElement("iframe"),n.label=4;case 4:return n.trys.push([4,,10,11]),[4,new Promise((function(e,n){var a=!1,o=function(){a=!0,e()};s.onload=o,s.onerror=function(e){a=!0,n(e)};var i=s.style;i.setProperty("display","block","important"),i.position="absolute",i.top="0",i.left="0",i.visibility="hidden",r&&"srcdoc"in s?s.srcdoc=r:s.src="about:blank",t.body.appendChild(s);var c=function(){var e,t;a||("complete"===(null===(t=null===(e=s.contentWindow)||void 0===e?void 0:e.document)||void 0===t?void 0:t.readyState)?o():setTimeout(c,10))};c()}))];case 5:n.sent(),n.label=6;case 6:return(null===(c=null===(i=s.contentWindow)||void 0===i?void 0:i.document)||void 0===c?void 0:c.body)?[3,8]:[4,a(o)];case 7:return n.sent(),[3,6];case 8:return[4,e(s,s.contentWindow)];case 9:return[2,n.sent()];case 10:return null===(u=s.parentNode)||void 0===u||u.removeChild(s),[7];case 11:return[2]}}))}))}function z(e){for(var t=function(e){for(var t,n,r="Unexpected syntax '"+e+"'",a=/^\s*([a-z-]*)(.*)$/i.exec(e),o=a[1]||void 0,i={},c=/([.:#][\w-]+|\[.+?\])/gi,u=function(e,t){i[e]=i[e]||[],i[e].push(t)};;){var s=c.exec(a[2]);if(!s)break;var l=s[0];switch(l[0]){case".":u("class",l.slice(1));break;case"#":u("id",l.slice(1));break;case"[":var d=/^\[([\w-]+)([~|^$*]?=("(.*?)"|([\w-]+)))?(\s+[is])?\]$/.exec(l);if(!d)throw new Error(r);u(d[1],null!==(n=null!==(t=d[4])&&void 0!==t?t:d[5])&&void 0!==n?n:"");break;default:throw new Error(r)}}return[o,i]}(e),n=t[0],r=t[1],a=document.createElement(null!=n?n:"div"),o=0,i=Object.keys(r);o.6*n.length}))).sort(),[2,a]}}))}))},fontPreferences:function(){return function(e,t){void 0===t&&(t=4e3);return E((function(n,a){var o=a.document,i=o.body,c=i.style;c.width=t+"px",c.webkitTextSizeAdjust=c.textSizeAdjust="none",A()?i.style.zoom=""+1/a.devicePixelRatio:x()&&(i.style.zoom="reset");var u=o.createElement("div");return u.textContent=r(Array(t/20<<0)).map((function(){return"word"})).join(" "),i.appendChild(u),e(o,i)}),'')}((function(e,t){for(var n={},r={},a=0,o=Object.keys(ee);a=3}())return-1;var n=new t(1,5e3,44100),r=n.createOscillator();r.type="triangle",r.frequency.value=1e4;var a=n.createDynamicsCompressor();a.threshold.value=-50,a.knee.value=40,a.ratio.value=12,a.attack.value=0,a.release.value=.25,r.connect(a),a.connect(n.destination),r.start(0);var o=function(e){var t=3,n=500,r=500,a=5e3,o=function(){};return[new Promise((function(i,c){var u=!1,s=0,l=0;e.oncomplete=function(e){return i(e.renderedBuffer)};var d=function(){setTimeout((function(){return c(T("timeout"))}),Math.min(r,l+a-Date.now()))},f=function(){try{switch(e.startRendering(),e.state){case"running":l=Date.now(),u&&d();break;case"suspended":document.hidden||s++,u&&s>=t?c(T("suspended")):setTimeout(f,n)}}catch(r){c(r)}};f(),o=function(){u||(u=!0,l>0&&d())}})),o]}(n),i=o[0],c=o[1],u=i.then((function(e){return function(e){for(var t=0,n=0;n=3||n.push(t.languages);else if("string"==typeof t.languages){var a=t.languages;a&&n.push(a.split(","))}return n},colorDepth:function(){return window.screen.colorDepth},deviceMemory:function(){return m(p(navigator.deviceMemory),void 0)},screenResolution:function(){var e=screen,t=function(e){return m(v(e),null)},n=[t(e.width),t(e.height)];return n.sort().reverse(),n},hardwareConcurrency:function(){return m(v(navigator.hardwareConcurrency),void 0)},timezone:function(){var e,t=null===(e=window.Intl)||void 0===e?void 0:e.DateTimeFormat;if(t){var n=(new t).resolvedOptions().timeZone;if(n)return n}var r,a=(r=(new Date).getFullYear(),-Math.max(p(new Date(r,0,1).getTimezoneOffset()),p(new Date(r,6,1).getTimezoneOffset())));return"UTC"+(a>=0?"+":"")+Math.abs(a)},sessionStorage:function(){try{return!!window.sessionStorage}catch(e){return!0}},localStorage:function(){try{return!!window.localStorage}catch(e){return!0}},indexedDB:function(){if(!k()&&!C())try{return!!window.indexedDB}catch(e){return!0}},openDatabase:function(){return!!window.openDatabase},cpuClass:function(){return navigator.cpuClass},platform:function(){var e=navigator.platform;return"MacIntel"===e&&x()&&!S()?function(){if("iPad"===navigator.platform)return!0;var e=screen,t=e.width/e.height;return g(["MediaSource"in window,!!Element.prototype.webkitRequestFullscreen,t>.65&&t<1.53])>=2}()?"iPad":"iPhone":e},plugins:function(){var e=navigator.plugins;if(e){for(var t=[],n=0;n