├── README.md ├── babel.config.js ├── build.sh ├── dist ├── .DS_Store ├── class │ ├── cache.js │ ├── lru-cache.js │ └── queue.js ├── index.cjs.js ├── index.d.ts ├── index.esm.js ├── index.js ├── js │ ├── array.js │ ├── bom.js │ ├── dom.js │ ├── format.js │ ├── index.js │ ├── random.js │ ├── reg.js │ ├── url.js │ └── utils.js ├── package.json.js └── utils │ └── index.js ├── package.json ├── pnpm-workspace.yaml ├── rollup.config.ts ├── scripts ├── build.ts ├── publish.ts ├── release.ts └── utils.ts ├── src ├── billd-utils.d.ts ├── class │ ├── cache.ts │ ├── lru-cache.ts │ └── queue.ts ├── demo.html ├── index.ts ├── js │ ├── array.ts │ ├── bom.ts │ ├── dom.ts │ ├── format.ts │ ├── index.ts │ ├── random.ts │ ├── reg.ts │ ├── url.ts │ └── utils.ts └── utils │ └── index.ts ├── test ├── computeBox.html └── test.js ├── tsconfig.json └── typedoc.config.json /README.md: -------------------------------------------------------------------------------- 1 |

2 | Bi-Utils 3 |

4 | 5 |

6 | 基于rollup + pnpm + esbuild搭建的Bi-Utils 7 |

8 | 9 | # 简介 10 | 11 | 积累常用的 js 方法 12 | 13 | # 安装 14 | 15 | ```sh 16 | npm install bi-utils 17 | ``` 18 | 19 | # 使用 20 | 21 | ```ts 22 | import { isBrowser } from 'bi-utils'; 23 | 24 | console.log(isBrowser()); 25 | ``` 26 | 27 | # 本地调试 28 | 29 | > 本地调试不会构建 umd 30 | 31 | ```sh 32 | pnpm run dev 33 | ``` 34 | 35 | # 本地构建 36 | 37 | ```sh 38 | pnpm run build 39 | ``` 40 | 41 | # 生成文档 42 | 43 | > 使用 [typedoc](https://typedoc.org/) 生成,文档会生成在项目根目录的 doc 目录 44 | 45 | ```sh 46 | pnpm run doc 47 | ``` 48 | 49 | # 发版 50 | 51 | ## 0.确保 git 工作区干净 52 | 53 | 即确保本地的修改已全部提交(git status 的时候会显示:`nothing to commit, working tree clean` ),否则会导致执行 `release:local` 脚本失败 54 | 55 | ## 1.执行本地发版脚本 56 | 57 | ```sh 58 | pnpm run release:local 59 | ``` 60 | 61 | > 该脚本内部会做以下事情: 62 | 63 | 1. 根据用户选择的版本,更新 package.json 的 version 64 | 2. 开始构建 65 | 3. 对比当前版本与上个版本的差异,生成 changelog日志 66 | 4. 提交暂存区到本地仓库:git commit -m 'chore(release): v 当前版本' 67 | 5. 生成当前版本 tag:git tag v 当前版本 68 | 69 | ## 2.执行线上发版脚本 70 | 71 | ```sh 72 | pnpm run release:online 73 | ``` 74 | 75 | > 该脚本内部会做以下事情: 76 | 77 | 1. 提交当前版本:git push 78 | 2. 提交当前版本 tag:git push origin v 当前版本 79 | 3. 发布到 npm 80 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | ], 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 生成头部文件快捷键:ctrl+cmd+i 4 | 5 | # 静态部署的项目,一般流程是在jenkins里面执行build.sh进行构建, 6 | # 构建完成后会连接ssh,执行/node/sh/frontend.sh,frontend.sh会将构建的完成资源复制到/node/xxx。 7 | # 复制完成后,frontend.sh会执行清除buff/cache操作 8 | 9 | # node项目,一般流程是在jenkins里面执行build.sh进行构建, 10 | # 构建完成后会连接ssh,执行/node/sh/node.sh,node.sh会将构建的完成资源复制到/node/xxx,并且执行/node/xxx/pm2.sh。 11 | # 最后,node.sh会执行清除buff/cache操作 12 | 13 | # 注意:JOBNAME=$1,这个等号左右不能有空格! 14 | JOBNAME=$1 #约定$1为任务名 15 | ENV=$2 #约定$2为环境 16 | WORKSPACE=$3 #约定$3为Jenkins工作区 17 | PORT=$4 #约定$4为端口号 18 | TAG=$5 #约定$5为git标签 19 | PUBLICDIR=/node #约定公共目录为/node 20 | 21 | echo 删除node_modules: 22 | rm -rf node_modules 23 | 24 | echo 查看node版本: 25 | node -v 26 | 27 | echo 查看npm版本: 28 | npm -v 29 | 30 | echo 设置npm淘宝镜像: 31 | npm config set registry https://registry.npm.taobao.org/ 32 | 33 | echo 查看当前npm镜像: 34 | npm get registry 35 | 36 | if ! type pnpm >/dev/null 2>&1; then 37 | echo 'pnpm未安装,先全局安装pnpm' 38 | npm i pnpm -g 39 | else 40 | echo 'pnpm已安装' 41 | fi 42 | 43 | echo 查看pnpm版本: 44 | pnpm -v 45 | 46 | echo 设置pnpm淘宝镜像: 47 | pnpm config set registry https://registry.npm.taobao.org/ 48 | #TODO registry 49 | pnpm config set @billd:registry http://registry.../ 50 | 51 | echo 查看当前pnpm镜像: 52 | pnpm config get registry 53 | pnpm config get @billd:registry 54 | 55 | echo 开始安装依赖: 56 | pnpm install 57 | 58 | if [ $ENV = 'beta' ]; then 59 | echo 开始构建测试环境: 60 | elif [ $ENV = 'preview' ]; then 61 | echo 开始构建预发布环境: 62 | elif [ $ENV = 'prod' ]; then 63 | echo 开始构建正式环境: 64 | else 65 | echo 开始构建$ENV环境: 66 | fi 67 | 68 | pnpm run doc 69 | 70 | echo 将doc移动到项目根目录: 71 | mv doc dist 72 | -------------------------------------------------------------------------------- /dist/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-crypto/bit-utils/849a8749385c3fbcfb0f19270025d493633f663e/dist/.DS_Store -------------------------------------------------------------------------------- /dist/class/cache.js: -------------------------------------------------------------------------------- 1 | import{debugLog as e}from"../utils/index.js";class t{prefix="";constructor(e){this.prefix=e}handlePrefix=e=>""===this.prefix?e:`${this.prefix}${e}`;getItem=e=>localStorage.getItem(this.handlePrefix(e));setItem=(e,t)=>localStorage.setItem(this.handlePrefix(e),t);removeItem=e=>localStorage.removeItem(this.handlePrefix(e));getStorage=t=>{try{const e=this.getItem(t);if(e){const r=JSON.parse(e);return r.createTime?r.value:(this.clearStorage(t),null)}return null}catch(r){return e("error",r),this.clearStorage(t),null}};setStorage=(t,r)=>{try{const e=+new Date;this.setItem(t,JSON.stringify({value:r,createTime:e}))}catch(r){e("error",r),this.clearStorage(t)}};clearStorage=t=>{try{this.removeItem(t)}catch(t){e("error",t)}};getStorageExp=t=>{try{const e=this.getItem(t);if(e){const r=JSON.parse(e),a=r.expireTime,i=a<+new Date;return!a||i?(this.clearStorage(t),null):r.value}return null}catch(r){return e("error",r),this.clearStorage(t),null}};setStorageExp=(t,r,a)=>{try{if([t,r,a].includes(void 0))return void e("error","请检查传入的参数!");const i=+new Date,o=i+60*a*60*1e3;this.setItem(t,JSON.stringify({value:r,createTime:i,expireTime:o}))}catch(r){e("error",r),this.clearStorage(t)}}}export{t as CacheModel}; 2 | -------------------------------------------------------------------------------- /dist/class/lru-cache.js: -------------------------------------------------------------------------------- 1 | class t{capacity;data=new Map;constructor(t){if(t<1)throw new Error("capacity必须大于1!");this.capacity=t}get(t){const e=this.data,a=e.get(t);return e.has(t)?(e.delete(t),e.set(t,a),a):null}put(t,e){const a=this.data;a.has(t)&&a.delete(t),a.set(t,e),a.size>this.capacity&&a.delete(a.keys().next().value)}}export{t as LRUCache}; 2 | -------------------------------------------------------------------------------- /dist/class/queue.js: -------------------------------------------------------------------------------- 1 | import{debugLog as t}from"../utils/index.js";class s{tasks=[];max=0;total=0;delay=0;done;constructor(t){let{max:s=5,done:a,delay:i=0}=t;this.tasks=[],this.total=0,this.max=s,this.done=a,this.delay=i,setTimeout((()=>{this.run()}),0)}addTask(t){this.tasks.push(t),this.total+=1}run(){if(0===this.tasks.length)return Promise.resolve("");const s=Math.min(this.tasks.length,this.max);for(let a=0;a{})).catch((s=>{t("error",s)})).finally((()=>{setTimeout((()=>{this.max+=1,this.total-=1,this.run(),0===this.total&&this.done?.()}),this.delay)}))}}export{s as ConcurrentPoll}; 2 | -------------------------------------------------------------------------------- /dist/index.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | var version$1 = "0.0.01"; 6 | 7 | const arrayUnique = arr => { 8 | return [...new Set(arr)]; 9 | }; 10 | 11 | const isIPad = () => { 12 | const ua = navigator.userAgent.toLowerCase(); 13 | const res = ua.match(/iPad/i); 14 | if (res?.length) { 15 | return true; 16 | } 17 | if (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1) { 18 | return true; 19 | } 20 | return false; 21 | }; 22 | 23 | const copyToClipBoard = text => { 24 | const oInput = document.createElement("input"); 25 | oInput.value = text; 26 | document.body.appendChild(oInput); 27 | oInput.select(); 28 | document.execCommand("Copy"); 29 | oInput.parentElement?.removeChild(oInput); 30 | }; 31 | 32 | const formatDate = timetamp => { 33 | function addDateZero(num) { 34 | return num < 10 ? `0${num}` : num; 35 | } 36 | const date = new Date(timetamp); 37 | return { 38 | year: date.getFullYear(), 39 | month: addDateZero(date.getMonth() + 1), 40 | day: addDateZero(date.getDate()), 41 | hour: addDateZero(date.getHours()), 42 | minutes: addDateZero(date.getMinutes()), 43 | seconds: addDateZero(date.getSeconds()) 44 | }; 45 | }; 46 | 47 | const getRangeRandom = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; 48 | const getRandomOne = arr => arr[Math.floor(Math.random() * arr.length)]; 49 | const getRandomString = length => { 50 | const str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 51 | let res = ""; 52 | for (let i = 0; i < length; i += 1) { 53 | res += str.charAt(getRangeRandom(0, str.length - 1)); 54 | } 55 | return res; 56 | }; 57 | const getRandomInt = length => { 58 | if (length > 16 || length < 1) throw new Error("length\u7684\u8303\u56F4:[1,16]"); 59 | let num = +`${Math.random()}`.slice(2, 2 + length); 60 | if (String(num).length !== length) { 61 | num = getRandomInt(length); 62 | } 63 | return num; 64 | }; 65 | 66 | const debugLog = function (type) { 67 | for (var _len = arguments.length, data = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 68 | data[_key - 1] = arguments[_key]; 69 | } 70 | console[type]("bi-utils", ...data); 71 | }; 72 | 73 | function isPureNumber(str) { 74 | const regex = /^\d+$/; 75 | return regex.test(str); 76 | } 77 | const regVerify = (str, type) => { 78 | try { 79 | switch (type) { 80 | case "email": 81 | return /[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+/.test(str); 82 | case "phone": 83 | return /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(str); 84 | } 85 | } catch (error) { 86 | debugLog("error", error); 87 | return false; 88 | } 89 | }; 90 | const judgeStringSpace = value => { 91 | const reg1 = /^\s+/g; 92 | const reg2 = /\s+$/g; 93 | if (reg1.test(value) || reg2.test(value)) { 94 | return true; 95 | } 96 | return false; 97 | }; 98 | 99 | const getUrlParams = key => { 100 | const url = decodeURIComponent(window.location.href); 101 | const str = url.split("?")[1]; 102 | const obj = {}; 103 | if (str) { 104 | const keys = str.split("&"); 105 | keys.forEach(item => { 106 | const arr = item.split("="); 107 | obj[arr[0]] = arr[1]; 108 | }); 109 | } 110 | return key ? obj[key] : obj; 111 | }; 112 | 113 | function strBtoa(str) { 114 | return window.btoa(window.encodeURIComponent(str)); 115 | } 116 | 117 | class CacheModel { 118 | prefix = ""; 119 | constructor(prefix) { 120 | this.prefix = prefix; 121 | } 122 | handlePrefix = key => { 123 | if (this.prefix === "") { 124 | return key; 125 | } else { 126 | return `${this.prefix}${key}`; 127 | } 128 | }; 129 | getItem = key => { 130 | return localStorage.getItem(this.handlePrefix(key)); 131 | }; 132 | setItem = (key, value) => { 133 | return localStorage.setItem(this.handlePrefix(key), value); 134 | }; 135 | removeItem = key => { 136 | return localStorage.removeItem(this.handlePrefix(key)); 137 | }; 138 | getStorage = key => { 139 | try { 140 | const res = this.getItem(key); 141 | if (res) { 142 | const data = JSON.parse(res); 143 | if (!data.createTime) { 144 | this.clearStorage(key); 145 | return null; 146 | } else { 147 | return data.value; 148 | } 149 | } 150 | return null; 151 | } catch (error) { 152 | debugLog("error", error); 153 | this.clearStorage(key); 154 | return null; 155 | } 156 | }; 157 | setStorage = (key, value) => { 158 | try { 159 | const createTime = +new Date(); 160 | this.setItem(key, JSON.stringify({ 161 | value, 162 | createTime 163 | })); 164 | } catch (error) { 165 | debugLog("error", error); 166 | this.clearStorage(key); 167 | } 168 | }; 169 | clearStorage = key => { 170 | try { 171 | this.removeItem(key); 172 | } catch (error) { 173 | debugLog("error", error); 174 | } 175 | }; 176 | getStorageExp = key => { 177 | try { 178 | const res = this.getItem(key); 179 | if (res) { 180 | const data = JSON.parse(res); 181 | const expireTime = data.expireTime; 182 | const isExpired = expireTime < +new Date(); 183 | if (!expireTime || isExpired) { 184 | this.clearStorage(key); 185 | return null; 186 | } else { 187 | return data.value; 188 | } 189 | } 190 | return null; 191 | } catch (error) { 192 | debugLog("error", error); 193 | this.clearStorage(key); 194 | return null; 195 | } 196 | }; 197 | setStorageExp = (key, value, expires) => { 198 | try { 199 | if ([key, value, expires].includes(void 0)) { 200 | debugLog("error", "\u8BF7\u68C0\u67E5\u4F20\u5165\u7684\u53C2\u6570\uFF01"); 201 | return; 202 | } 203 | const createTime = +new Date(); 204 | const expireTime = createTime + expires * 60 * 60 * 1e3; 205 | this.setItem(key, JSON.stringify({ 206 | value, 207 | createTime, 208 | expireTime 209 | })); 210 | } catch (error) { 211 | debugLog("error", error); 212 | this.clearStorage(key); 213 | } 214 | }; 215 | } 216 | 217 | class LRUCache { 218 | capacity; 219 | data = /* @__PURE__ */new Map(); 220 | constructor(capacity) { 221 | if (capacity < 1) throw new Error("capacity\u5FC5\u987B\u5927\u4E8E1\uFF01"); 222 | this.capacity = capacity; 223 | } 224 | get(key) { 225 | const data = this.data; 226 | const value = data.get(key); 227 | if (!data.has(key)) return null; 228 | data.delete(key); 229 | data.set(key, value); 230 | return value; 231 | } 232 | put(key, value) { 233 | const data = this.data; 234 | if (data.has(key)) { 235 | data.delete(key); 236 | } 237 | data.set(key, value); 238 | if (data.size > this.capacity) { 239 | data.delete(data.keys().next().value); 240 | } 241 | } 242 | } 243 | 244 | class ConcurrentPoll { 245 | tasks = []; 246 | max = 0; 247 | total = 0; 248 | delay = 0; 249 | done; 250 | constructor(_ref) { 251 | let { 252 | max = 5, 253 | done, 254 | delay = 0 255 | } = _ref; 256 | this.tasks = []; 257 | this.total = 0; 258 | this.max = max; 259 | this.done = done; 260 | this.delay = delay; 261 | setTimeout(() => { 262 | this.run(); 263 | }, 0); 264 | } 265 | addTask(task) { 266 | this.tasks.push(task); 267 | this.total += 1; 268 | } 269 | run() { 270 | if (this.tasks.length === 0) { 271 | return Promise.resolve(""); 272 | } 273 | const min = Math.min(this.tasks.length, this.max); 274 | for (let i = 0; i < min; i += 1) { 275 | this.max -= 1; 276 | const task = this.tasks.shift(); 277 | task().then(() => {}).catch(error => { 278 | debugLog("error", error); 279 | }).finally(() => { 280 | setTimeout(() => { 281 | this.max += 1; 282 | this.total -= 1; 283 | this.run(); 284 | if (this.total === 0) { 285 | this.done?.(); 286 | } 287 | }, this.delay); 288 | }); 289 | } 290 | } 291 | } 292 | 293 | var utils = /*#__PURE__*/Object.freeze({ 294 | __proto__: null, 295 | arrayUnique: arrayUnique, 296 | isIPad: isIPad, 297 | copyToClipBoard: copyToClipBoard, 298 | formatDate: formatDate, 299 | getRangeRandom: getRangeRandom, 300 | getRandomOne: getRandomOne, 301 | getRandomString: getRandomString, 302 | getRandomInt: getRandomInt, 303 | isPureNumber: isPureNumber, 304 | regVerify: regVerify, 305 | judgeStringSpace: judgeStringSpace, 306 | getUrlParams: getUrlParams, 307 | strBtoa: strBtoa, 308 | CacheModel: CacheModel, 309 | LRUCache: LRUCache, 310 | ConcurrentPoll: ConcurrentPoll 311 | }); 312 | 313 | const version = version$1; 314 | 315 | exports.CacheModel = CacheModel; 316 | exports.ConcurrentPoll = ConcurrentPoll; 317 | exports.LRUCache = LRUCache; 318 | exports.arrayUnique = arrayUnique; 319 | exports.copyToClipBoard = copyToClipBoard; 320 | exports["default"] = utils; 321 | exports.formatDate = formatDate; 322 | exports.getRandomInt = getRandomInt; 323 | exports.getRandomOne = getRandomOne; 324 | exports.getRandomString = getRandomString; 325 | exports.getRangeRandom = getRangeRandom; 326 | exports.getUrlParams = getUrlParams; 327 | exports.isIPad = isIPad; 328 | exports.isPureNumber = isPureNumber; 329 | exports.judgeStringSpace = judgeStringSpace; 330 | exports.regVerify = regVerify; 331 | exports.strBtoa = strBtoa; 332 | exports.version = version; 333 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 数组去重,缺点不能去除{} 3 | * @param {Array} arr 4 | * @return {*} 不修改原数组,返回新数组 5 | */ 6 | declare const arrayUnique: (arr: Array) => any[]; 7 | 8 | /** 判断是否是ipad */ 9 | declare const isIPad: () => boolean; 10 | 11 | /** 12 | * @description 将内容复制到剪切板 13 | * @param {string} text 14 | * @return {*} 15 | */ 16 | declare const copyToClipBoard: (text: string) => void; 17 | 18 | /** 19 | * @description 格式化时间 20 | * @param {number} timetamp 21 | */ 22 | declare const formatDate: (timetamp: number) => { 23 | year: number; 24 | month: string | number; 25 | day: string | number; 26 | hour: string | number; 27 | minutes: string | number; 28 | seconds: string | number; 29 | }; 30 | 31 | /** 32 | * @description 获取[min,max]之间的随机整数。 33 | * @example: getRangeRandom(-10,100) ===> -8 34 | * @param {number} min 35 | * @param {number} max 36 | * @return {*} 37 | */ 38 | declare const getRangeRandom: (min: number, max: number) => number; 39 | /** 40 | * @description: 随机数组的一个元素 41 | * @example: getRandomOne([10,2,4,6]) ===> 6 42 | * @param {any} arr 43 | * @return {*} 44 | */ 45 | declare const getRandomOne: (arr: any[]) => any; 46 | /** 47 | * @description 获取随机字符串(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789) 48 | * @example: getRandomString(4) ===> abd3 49 | * @param {number} length 50 | * @return {*} 51 | */ 52 | declare const getRandomString: (length: number) => string; 53 | /** 54 | * @description: 获取随机整数 55 | * @example: getRandomInt(4) ===> 3251 56 | * @param {*} length 57 | * @return {*} 58 | */ 59 | declare const getRandomInt: (length: any) => number; 60 | 61 | /** 62 | * @description 正则是否是纯数字(纯整数) 63 | * @example: isPureNumber(abc) ===> false;isPureNumber(1.23) ===> false;isPureNumber(123) ===> true; 64 | * @param {string} str 65 | * @return {*} 66 | */ 67 | declare function isPureNumber(str: string): boolean; 68 | /** 69 | * @description 正则验证手机号、邮箱是否合法 70 | * @param {string} str 71 | * @param {*} type 72 | * @return {*} 73 | */ 74 | declare const regVerify: (str: string, type: 'phone' | 'email') => boolean; 75 | /** 76 | * @description 判断字符串的开头和结尾是否有空格,有空格就返回true,否则返回false 77 | * @param {string} value 78 | * @return {*} 79 | */ 80 | declare const judgeStringSpace: (value: string) => boolean; 81 | 82 | /** 83 | * @description 获取地址栏参数(注意:请确保url是http://aaa.com/ds/?aa=1&bb=323这样子的) 84 | * @return {*} 85 | */ 86 | declare const getUrlParams: (key?: string) => any; 87 | 88 | /** 89 | * @description: 字符串编码 90 | * @param {string} str 91 | * @return {*} 92 | */ 93 | declare function strBtoa(str: string): string; 94 | 95 | declare class CacheModel { 96 | prefix: string; 97 | constructor(prefix: string); 98 | handlePrefix: (key: string) => string; 99 | getItem: (key: string) => string | null; 100 | setItem: (key: string, value: any) => void; 101 | removeItem: (key: string) => void; 102 | /** 103 | * @description 获取缓存 104 | * @param {string} key 105 | * @return {*} 106 | */ 107 | getStorage: (key: string) => T | null; 108 | /** 109 | * @description 设置缓存 110 | * @param {*} key 111 | * @param {*} value 112 | */ 113 | setStorage: (key: string, value: any) => void; 114 | /** 115 | * @description 清除缓存 116 | * @param {*} key 117 | */ 118 | clearStorage: (key: string) => void; 119 | /** 120 | * @description 获取缓存,如果缓存已过期,会清除该缓存,并返回null 121 | * @param {*} key 122 | */ 123 | getStorageExp: (key: string) => T | null; 124 | /** 125 | * @description 设置缓存以及缓存时长 126 | * @param {*} key 127 | * @param {*} value 128 | * @param {*} expires 缓存时长,单位:小时 129 | */ 130 | setStorageExp: (key: string, value: any, expires: number) => void; 131 | } 132 | 133 | declare class LRUCache { 134 | capacity: number; 135 | data: Map; 136 | constructor(capacity: number); 137 | get(key: any): any; 138 | put(key: any, value: any): void; 139 | } 140 | 141 | declare class ConcurrentPoll { 142 | /** 任务队列 */ 143 | tasks: any[]; 144 | /** 最大并发数 */ 145 | max: number; 146 | total: number; 147 | delay: number; 148 | done: () => void; 149 | constructor({ max, done, delay }: { 150 | max?: number | undefined; 151 | done: any; 152 | delay?: number | undefined; 153 | }); 154 | addTask(task: any): void; 155 | run(): Promise | undefined; 156 | } 157 | 158 | declare const utils_arrayUnique: typeof arrayUnique; 159 | declare const utils_isIPad: typeof isIPad; 160 | declare const utils_copyToClipBoard: typeof copyToClipBoard; 161 | declare const utils_formatDate: typeof formatDate; 162 | declare const utils_getRangeRandom: typeof getRangeRandom; 163 | declare const utils_getRandomOne: typeof getRandomOne; 164 | declare const utils_getRandomString: typeof getRandomString; 165 | declare const utils_getRandomInt: typeof getRandomInt; 166 | declare const utils_isPureNumber: typeof isPureNumber; 167 | declare const utils_regVerify: typeof regVerify; 168 | declare const utils_judgeStringSpace: typeof judgeStringSpace; 169 | declare const utils_getUrlParams: typeof getUrlParams; 170 | declare const utils_strBtoa: typeof strBtoa; 171 | type utils_CacheModel = CacheModel; 172 | declare const utils_CacheModel: typeof CacheModel; 173 | type utils_LRUCache = LRUCache; 174 | declare const utils_LRUCache: typeof LRUCache; 175 | type utils_ConcurrentPoll = ConcurrentPoll; 176 | declare const utils_ConcurrentPoll: typeof ConcurrentPoll; 177 | declare namespace utils { 178 | export { 179 | utils_arrayUnique as arrayUnique, 180 | utils_isIPad as isIPad, 181 | utils_copyToClipBoard as copyToClipBoard, 182 | utils_formatDate as formatDate, 183 | utils_getRangeRandom as getRangeRandom, 184 | utils_getRandomOne as getRandomOne, 185 | utils_getRandomString as getRandomString, 186 | utils_getRandomInt as getRandomInt, 187 | utils_isPureNumber as isPureNumber, 188 | utils_regVerify as regVerify, 189 | utils_judgeStringSpace as judgeStringSpace, 190 | utils_getUrlParams as getUrlParams, 191 | utils_strBtoa as strBtoa, 192 | utils_CacheModel as CacheModel, 193 | utils_LRUCache as LRUCache, 194 | utils_ConcurrentPoll as ConcurrentPoll, 195 | }; 196 | } 197 | 198 | declare const version: string; 199 | 200 | export { CacheModel, ConcurrentPoll, LRUCache, arrayUnique, copyToClipBoard, utils as default, formatDate, getRandomInt, getRandomOne, getRandomString, getRangeRandom, getUrlParams, isIPad, isPureNumber, judgeStringSpace, regVerify, strBtoa, version }; 201 | -------------------------------------------------------------------------------- /dist/index.esm.js: -------------------------------------------------------------------------------- 1 | var version$1 = "0.0.01"; 2 | 3 | const arrayUnique = arr => { 4 | return [...new Set(arr)]; 5 | }; 6 | 7 | const isIPad = () => { 8 | const ua = navigator.userAgent.toLowerCase(); 9 | const res = ua.match(/iPad/i); 10 | if (res?.length) { 11 | return true; 12 | } 13 | if (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1) { 14 | return true; 15 | } 16 | return false; 17 | }; 18 | 19 | const copyToClipBoard = text => { 20 | const oInput = document.createElement("input"); 21 | oInput.value = text; 22 | document.body.appendChild(oInput); 23 | oInput.select(); 24 | document.execCommand("Copy"); 25 | oInput.parentElement?.removeChild(oInput); 26 | }; 27 | 28 | const formatDate = timetamp => { 29 | function addDateZero(num) { 30 | return num < 10 ? `0${num}` : num; 31 | } 32 | const date = new Date(timetamp); 33 | return { 34 | year: date.getFullYear(), 35 | month: addDateZero(date.getMonth() + 1), 36 | day: addDateZero(date.getDate()), 37 | hour: addDateZero(date.getHours()), 38 | minutes: addDateZero(date.getMinutes()), 39 | seconds: addDateZero(date.getSeconds()) 40 | }; 41 | }; 42 | 43 | const getRangeRandom = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; 44 | const getRandomOne = arr => arr[Math.floor(Math.random() * arr.length)]; 45 | const getRandomString = length => { 46 | const str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 47 | let res = ""; 48 | for (let i = 0; i < length; i += 1) { 49 | res += str.charAt(getRangeRandom(0, str.length - 1)); 50 | } 51 | return res; 52 | }; 53 | const getRandomInt = length => { 54 | if (length > 16 || length < 1) throw new Error("length\u7684\u8303\u56F4:[1,16]"); 55 | let num = +`${Math.random()}`.slice(2, 2 + length); 56 | if (String(num).length !== length) { 57 | num = getRandomInt(length); 58 | } 59 | return num; 60 | }; 61 | 62 | const debugLog = function (type) { 63 | for (var _len = arguments.length, data = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 64 | data[_key - 1] = arguments[_key]; 65 | } 66 | console[type]("bi-utils", ...data); 67 | }; 68 | 69 | function isPureNumber(str) { 70 | const regex = /^\d+$/; 71 | return regex.test(str); 72 | } 73 | const regVerify = (str, type) => { 74 | try { 75 | switch (type) { 76 | case "email": 77 | return /[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+/.test(str); 78 | case "phone": 79 | return /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(str); 80 | } 81 | } catch (error) { 82 | debugLog("error", error); 83 | return false; 84 | } 85 | }; 86 | const judgeStringSpace = value => { 87 | const reg1 = /^\s+/g; 88 | const reg2 = /\s+$/g; 89 | if (reg1.test(value) || reg2.test(value)) { 90 | return true; 91 | } 92 | return false; 93 | }; 94 | 95 | const getUrlParams = key => { 96 | const url = decodeURIComponent(window.location.href); 97 | const str = url.split("?")[1]; 98 | const obj = {}; 99 | if (str) { 100 | const keys = str.split("&"); 101 | keys.forEach(item => { 102 | const arr = item.split("="); 103 | obj[arr[0]] = arr[1]; 104 | }); 105 | } 106 | return key ? obj[key] : obj; 107 | }; 108 | 109 | function strBtoa(str) { 110 | return window.btoa(window.encodeURIComponent(str)); 111 | } 112 | 113 | class CacheModel { 114 | prefix = ""; 115 | constructor(prefix) { 116 | this.prefix = prefix; 117 | } 118 | handlePrefix = key => { 119 | if (this.prefix === "") { 120 | return key; 121 | } else { 122 | return `${this.prefix}${key}`; 123 | } 124 | }; 125 | getItem = key => { 126 | return localStorage.getItem(this.handlePrefix(key)); 127 | }; 128 | setItem = (key, value) => { 129 | return localStorage.setItem(this.handlePrefix(key), value); 130 | }; 131 | removeItem = key => { 132 | return localStorage.removeItem(this.handlePrefix(key)); 133 | }; 134 | getStorage = key => { 135 | try { 136 | const res = this.getItem(key); 137 | if (res) { 138 | const data = JSON.parse(res); 139 | if (!data.createTime) { 140 | this.clearStorage(key); 141 | return null; 142 | } else { 143 | return data.value; 144 | } 145 | } 146 | return null; 147 | } catch (error) { 148 | debugLog("error", error); 149 | this.clearStorage(key); 150 | return null; 151 | } 152 | }; 153 | setStorage = (key, value) => { 154 | try { 155 | const createTime = +new Date(); 156 | this.setItem(key, JSON.stringify({ 157 | value, 158 | createTime 159 | })); 160 | } catch (error) { 161 | debugLog("error", error); 162 | this.clearStorage(key); 163 | } 164 | }; 165 | clearStorage = key => { 166 | try { 167 | this.removeItem(key); 168 | } catch (error) { 169 | debugLog("error", error); 170 | } 171 | }; 172 | getStorageExp = key => { 173 | try { 174 | const res = this.getItem(key); 175 | if (res) { 176 | const data = JSON.parse(res); 177 | const expireTime = data.expireTime; 178 | const isExpired = expireTime < +new Date(); 179 | if (!expireTime || isExpired) { 180 | this.clearStorage(key); 181 | return null; 182 | } else { 183 | return data.value; 184 | } 185 | } 186 | return null; 187 | } catch (error) { 188 | debugLog("error", error); 189 | this.clearStorage(key); 190 | return null; 191 | } 192 | }; 193 | setStorageExp = (key, value, expires) => { 194 | try { 195 | if ([key, value, expires].includes(void 0)) { 196 | debugLog("error", "\u8BF7\u68C0\u67E5\u4F20\u5165\u7684\u53C2\u6570\uFF01"); 197 | return; 198 | } 199 | const createTime = +new Date(); 200 | const expireTime = createTime + expires * 60 * 60 * 1e3; 201 | this.setItem(key, JSON.stringify({ 202 | value, 203 | createTime, 204 | expireTime 205 | })); 206 | } catch (error) { 207 | debugLog("error", error); 208 | this.clearStorage(key); 209 | } 210 | }; 211 | } 212 | 213 | class LRUCache { 214 | capacity; 215 | data = /* @__PURE__ */new Map(); 216 | constructor(capacity) { 217 | if (capacity < 1) throw new Error("capacity\u5FC5\u987B\u5927\u4E8E1\uFF01"); 218 | this.capacity = capacity; 219 | } 220 | get(key) { 221 | const data = this.data; 222 | const value = data.get(key); 223 | if (!data.has(key)) return null; 224 | data.delete(key); 225 | data.set(key, value); 226 | return value; 227 | } 228 | put(key, value) { 229 | const data = this.data; 230 | if (data.has(key)) { 231 | data.delete(key); 232 | } 233 | data.set(key, value); 234 | if (data.size > this.capacity) { 235 | data.delete(data.keys().next().value); 236 | } 237 | } 238 | } 239 | 240 | class ConcurrentPoll { 241 | tasks = []; 242 | max = 0; 243 | total = 0; 244 | delay = 0; 245 | done; 246 | constructor(_ref) { 247 | let { 248 | max = 5, 249 | done, 250 | delay = 0 251 | } = _ref; 252 | this.tasks = []; 253 | this.total = 0; 254 | this.max = max; 255 | this.done = done; 256 | this.delay = delay; 257 | setTimeout(() => { 258 | this.run(); 259 | }, 0); 260 | } 261 | addTask(task) { 262 | this.tasks.push(task); 263 | this.total += 1; 264 | } 265 | run() { 266 | if (this.tasks.length === 0) { 267 | return Promise.resolve(""); 268 | } 269 | const min = Math.min(this.tasks.length, this.max); 270 | for (let i = 0; i < min; i += 1) { 271 | this.max -= 1; 272 | const task = this.tasks.shift(); 273 | task().then(() => {}).catch(error => { 274 | debugLog("error", error); 275 | }).finally(() => { 276 | setTimeout(() => { 277 | this.max += 1; 278 | this.total -= 1; 279 | this.run(); 280 | if (this.total === 0) { 281 | this.done?.(); 282 | } 283 | }, this.delay); 284 | }); 285 | } 286 | } 287 | } 288 | 289 | var utils = /*#__PURE__*/Object.freeze({ 290 | __proto__: null, 291 | arrayUnique: arrayUnique, 292 | isIPad: isIPad, 293 | copyToClipBoard: copyToClipBoard, 294 | formatDate: formatDate, 295 | getRangeRandom: getRangeRandom, 296 | getRandomOne: getRandomOne, 297 | getRandomString: getRandomString, 298 | getRandomInt: getRandomInt, 299 | isPureNumber: isPureNumber, 300 | regVerify: regVerify, 301 | judgeStringSpace: judgeStringSpace, 302 | getUrlParams: getUrlParams, 303 | strBtoa: strBtoa, 304 | CacheModel: CacheModel, 305 | LRUCache: LRUCache, 306 | ConcurrentPoll: ConcurrentPoll 307 | }); 308 | 309 | const version = version$1; 310 | 311 | export { CacheModel, ConcurrentPoll, LRUCache, arrayUnique, copyToClipBoard, utils as default, formatDate, getRandomInt, getRandomOne, getRandomString, getRangeRandom, getUrlParams, isIPad, isPureNumber, judgeStringSpace, regVerify, strBtoa, version }; 312 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | import{version as a}from"./package.json.js";import*as s from"./js/index.js";import{arrayUnique as r}from"./js/array.js";import{isIPad as o}from"./js/bom.js";import{copyToClipBoard as e}from"./js/dom.js";import{formatDate as m}from"./js/format.js";import{getRandomInt as t,getRandomOne as i,getRandomString as n,getRangeRandom as j}from"./js/random.js";import{isPureNumber as g,judgeStringSpace as p,regVerify as d}from"./js/reg.js";import{getUrlParams as c}from"./js/url.js";import{strBtoa as f}from"./js/utils.js";import{CacheModel as l}from"./class/cache.js";import{LRUCache as u}from"./class/lru-cache.js";import{ConcurrentPoll as R}from"./class/queue.js";const C=a;export{l as CacheModel,R as ConcurrentPoll,u as LRUCache,r as arrayUnique,e as copyToClipBoard,s as default,m as formatDate,t as getRandomInt,i as getRandomOne,n as getRandomString,j as getRangeRandom,c as getUrlParams,o as isIPad,g as isPureNumber,p as judgeStringSpace,d as regVerify,f as strBtoa,C as version}; 2 | -------------------------------------------------------------------------------- /dist/js/array.js: -------------------------------------------------------------------------------- 1 | const e=e=>[...new Set(e)];export{e as arrayUnique}; 2 | -------------------------------------------------------------------------------- /dist/js/bom.js: -------------------------------------------------------------------------------- 1 | const a=()=>!!(navigator.userAgent.toLowerCase().match(/iPad/i)?.length||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1);export{a as isIPad}; 2 | -------------------------------------------------------------------------------- /dist/js/dom.js: -------------------------------------------------------------------------------- 1 | const e=e=>{const o=document.createElement("input");o.value=e,document.body.appendChild(o),o.select(),document.execCommand("Copy"),o.parentElement?.removeChild(o)};export{e as copyToClipBoard}; 2 | -------------------------------------------------------------------------------- /dist/js/format.js: -------------------------------------------------------------------------------- 1 | const t=t=>{function e(t){return t<10?`0${t}`:t}const n=new Date(t);return{year:n.getFullYear(),month:e(n.getMonth()+1),day:e(n.getDate()),hour:e(n.getHours()),minutes:e(n.getMinutes()),seconds:e(n.getSeconds())}};export{t as formatDate}; 2 | -------------------------------------------------------------------------------- /dist/js/index.js: -------------------------------------------------------------------------------- 1 | import{arrayUnique as a}from"./array.js";import{isIPad as r}from"./bom.js";import{copyToClipBoard as s}from"./dom.js";import{formatDate as o}from"./format.js";import{getRandomInt as e,getRandomOne as m,getRandomString as t,getRangeRandom as i}from"./random.js";import{isPureNumber as n,judgeStringSpace as g,regVerify as d}from"./reg.js";import{getUrlParams as p}from"./url.js";import{strBtoa as c}from"./utils.js";import{CacheModel as f}from"../class/cache.js";import{LRUCache as l}from"../class/lru-cache.js";import{ConcurrentPoll as u}from"../class/queue.js";export{f as CacheModel,u as ConcurrentPoll,l as LRUCache,a as arrayUnique,s as copyToClipBoard,o as formatDate,e as getRandomInt,m as getRandomOne,t as getRandomString,i as getRangeRandom,p as getUrlParams,r as isIPad,n as isPureNumber,g as judgeStringSpace,d as regVerify,c as strBtoa}; 2 | -------------------------------------------------------------------------------- /dist/js/random.js: -------------------------------------------------------------------------------- 1 | const t=(t,n)=>Math.floor(Math.random()*(n-t+1))+t,n=t=>t[Math.floor(Math.random()*t.length)],a=n=>{const a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";let r="";for(let e=0;e{if(t>16||t<1)throw new Error("length的范围:[1,16]");let n=+`${Math.random()}`.slice(2,2+t);return String(n).length!==t&&(n=r(t)),n};export{r as getRandomInt,n as getRandomOne,a as getRandomString,t as getRangeRandom}; 2 | -------------------------------------------------------------------------------- /dist/js/reg.js: -------------------------------------------------------------------------------- 1 | import{debugLog as t}from"../utils/index.js";function e(t){return/^\d+$/.test(t)}const r=(e,r)=>{try{switch(r){case"email":return/[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+/.test(e);case"phone":return/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test(e)}}catch(e){return t("error",e),!1}},s=t=>!(!/^\s+/g.test(t)&&!/\s+$/g.test(t));export{e as isPureNumber,s as judgeStringSpace,r as regVerify}; 2 | -------------------------------------------------------------------------------- /dist/js/url.js: -------------------------------------------------------------------------------- 1 | const o=o=>{const t=decodeURIComponent(window.location.href).split("?")[1],n={};return t&&t.split("&").forEach((o=>{const t=o.split("=");n[t[0]]=t[1]})),o?n[o]:n};export{o as getUrlParams}; 2 | -------------------------------------------------------------------------------- /dist/js/utils.js: -------------------------------------------------------------------------------- 1 | function o(o){return window.btoa(window.encodeURIComponent(o))}export{o as strBtoa}; 2 | -------------------------------------------------------------------------------- /dist/package.json.js: -------------------------------------------------------------------------------- 1 | var r="0.0.01";export{r as version}; 2 | -------------------------------------------------------------------------------- /dist/utils/index.js: -------------------------------------------------------------------------------- 1 | const o=function(o){for(var n=arguments.length,e=new Array(n>1?n-1:0),r=1;r RegExp(`^${name}($|/)`)); // 在打包esm和cjs的时候,排除所有依赖,让上层应用去处理(如果不排除就会有重复的polyfill) 27 | 28 | const esbuildPlugin = (options?: ESBuildOptions) => 29 | esbuild({ 30 | minify: false, 31 | // sourceMap: true, // 默认就是true 32 | ...options, 33 | }); 34 | 35 | // my-name转化为MyName 36 | export const toPascalCase = (input: string) => { 37 | const res = input.replace(input[0], input[0].toUpperCase()); 38 | return res.replace(/-(\w)/g, function (all, letter) { 39 | return letter.toUpperCase(); 40 | }); 41 | }; 42 | 43 | const umdConfig = (prod = false): RollupOptions => ({ 44 | input: './src/index.ts', 45 | output: { 46 | format: 'umd', 47 | file: prod ? 'dist/index.min.js' : 'dist/index.js', 48 | name: toPascalCase(pkg.name), 49 | /** 50 | * exports默认值是auto,可选:default、none。https://rollupjs.org/guide/zh/#exports 51 | * 如果我们源代码默认导出和具名导出一起使用,编译的时候会报警告(!) Mixing named and default exports 52 | * 设置exports: 'named'就不会报警告了(实际上只是不会报警告了,设不设置named对实际打包的结果都没影响) 53 | * 如果我们源代码没有默认导出和具名导出一起使用,但是设置了exports: 'named',会生成:exports["default"] = BilldUtils; 54 | * 别人通过cjs导入的话,就得const BilldUtils = require("billd-utils").default;才能拿到默认导出;如果不使用exports: 'named', 55 | * 默认会生成:module.exports = BilldUtils;别人通过cjs导入的话,就正常的const BilldUtils = require("billd-utils");即可 56 | */ 57 | exports: 'named', // babel-utils默认导出和具名导出混用了,因此需要设置exports: 'named' 58 | }, 59 | plugins: [ 60 | commonjs(), 61 | nodeResolve(), 62 | esbuildPlugin({ minify: prod ? true : false }), 63 | babel({ 64 | exclude: 'node_modules/**', // 只编译我们的源代码,最好加上它,否则打包umd可能会报错 65 | extensions: [...DEFAULT_EXTENSIONS, '.ts'], 66 | // 这里面的plugins如果和babel.config.js里的plugins冲突, 67 | // 会执行这里的plugins,不会执行babel.config.js里的plugins 68 | plugins: [ 69 | [ 70 | /** 71 | * @babel/plugin-transform-runtime 72 | * useBuiltIns和polyfill选项在 v7 中被删除,只是将其设为默认值。 73 | */ 74 | '@babel/plugin-transform-runtime', 75 | { 76 | // absoluteRuntime: false, // boolean或者string,默认为false。 77 | /** 78 | * corejs:false, 2,3或{ version: 2 | 3, proposals: boolean }, 默认为false 79 | * 设置对应值需要安装对应的包: 80 | * false npm install --save @babel/runtime 81 | * 2 npm install --save @babel/runtime-corejs2 82 | * 3 npm install --save @babel/runtime-corejs3 83 | */ 84 | corejs: 3, 85 | /** 86 | * helpers: boolean, 默认true。在纯babel的情况下: 87 | * 如果是true,就会把需要他runtime包给引进来,如:import _defineProperty from "@babel/runtime/helpers/defineProperty" 88 | * 如果是false,就会把需要的runtime包里面的代码给嵌进bundle里,如function _defineProperty(){} 89 | * 设置false的话,会导致同一个runtime包里面的代码被很多文件设置,产生冗余的代码。而且因为虽然是同一 90 | * 份runtime包里面的代码,但是他们在不同的文件(模块)里面,都有自己的作用域,因此在使用类似webpack之类的 91 | * 打包工具打包的时候,不会做优化。因此推荐设置true,这样可以通过静态分析的手段进行打包,减少打包后的代码体积。 92 | */ 93 | // helpers: true, // 当helpers设置true的时候,babelHelpers需要设置为runtime 94 | helpers: false, // 当helpers设置false的时候,babelHelpers需要设置为bundled 95 | // regenerator: true, // 切换生成器函数是否转换为使用不污染全局范围的再生器运行时。默认为true 96 | version: babelRuntimeVersion, 97 | }, 98 | ], 99 | ], 100 | /** 101 | * babelHelpers,建议显式配置此选项(即使使用其默认值) 102 | * runtime: 您应该使用此功能,尤其是在使用汇总构建库时,它结合external使用 103 | * bundled: 如果您希望生成的捆绑包包含这些帮助程序(每个最多一份),您应该使用它。特别是在捆绑应用程序代码时很有用 104 | * 如果babelHelpers设置成bundled,@babel/plugin-transform-runtime的helpers得设置false! 105 | * 如果babelHelpers设置成runtime,@babel/plugin-transform-runtime的helpers得设置true! 106 | * 在打包esm和cjs时,使用runtime,并且配合external;在打包umd时,使用bundled,并且不要用external,如果打包umd时使 107 | * 用了runtime但是没有配置external,会导致打包重复的polyfill,虽然打包的时候不报错,但是引入包使用的时候会报错 108 | */ 109 | babelHelpers: 'bundled', // 默认bundled,可选:"bundled" | "runtime" | "inline" | "external" | undefined 110 | // babelHelpers: 'runtime', // 默认bundled,可选:"bundled" | "runtime" | "inline" | "external" | undefined 111 | }), 112 | json(), 113 | prod && terser(), 114 | ].filter(Boolean), 115 | }); 116 | 117 | export default defineConfig([ 118 | // esm 119 | { 120 | input: './src/index.ts', 121 | output: { 122 | format: 'esm', 123 | file: 'dist/index.esm.js', 124 | }, 125 | external: allDep, 126 | plugins: [ 127 | /** 128 | * @rollup/plugin-commonjs插件主要是将commonjs转换为esm 129 | * @rollup/plugin-commonjs一般和@rollup/plugin-node-resolve一起使用 130 | * @babel/plugin-transform-runtime使用corejs:3(即@babel/runtime-corejs3), 131 | * 而@babel/runtime-corejs3源码是使用commonjs规范写的,因此需要添加@rollup/plugin-commonjs插件 132 | * 让rollup支持commonjs 规范,识别 commonjs 规范的依赖。 133 | * 打包esm和cjs时,当前的billd-monorepo里面的源码基本都没有依赖第三方的包,并且排除了所有外部依赖,因此 134 | * 这里其实不需要用@rollup/plugin-commonjs插件 135 | * @rollup/plugin-commonjs和@rollup/plugin-babel一起用的使用,先使用@rollup/plugin-commonjs,再用@rollup/plugin-babel 136 | * https://github.com/rollup/plugins/blob/master/packages/babel/README.md#using-with-rollupplugin-commonjs 137 | */ 138 | commonjs(), 139 | /** 140 | * @rollup/plugin-node-resolve只对引入node_modules包的代码起作用 141 | * 不使用@rollup/plugin-node-resolve插件的话,import { ref } from 'vue';就不会把node_modules包 142 | * 里的vue的ref的代码引进来,而是会原封不动的把import { ref } from 'vue';放到打包的代码里面; 143 | * 使用@rollup/plugin-node-resolve插件的话,会将node_modules包里的vue的ref的代码都引进来 144 | * 打包esm和cjs时,当前的billd-monorepo里面的源码基本都没有依赖第三方的包,并且排除了所有外部依赖,因此 145 | * 这里其实不需要用@rollup/plugin-node-resolve插件 146 | */ 147 | nodeResolve(), 148 | esbuildPlugin(), 149 | json(), 150 | babel({ 151 | exclude: 'node_modules/**', // 只编译我们的源代码,最好加上它,否则打包umd可能会报错 152 | extensions: [...DEFAULT_EXTENSIONS, '.ts'], 153 | // 这里面的plugins如果和babel.config.js里的plugins冲突, 154 | // 会执行这里的plugins,不会执行babel.config.js里的plugins 155 | plugins: [ 156 | [ 157 | /** 158 | * @babel/plugin-transform-runtime 159 | * useBuiltIns和polyfill选项在 v7 中被删除,只是将其设为默认值。 160 | */ 161 | '@babel/plugin-transform-runtime', 162 | { 163 | // absoluteRuntime: false, // boolean或者string,默认为false。 164 | /** 165 | * corejs:false, 2,3或{ version: 2 | 3, proposals: boolean }, 默认为false 166 | * 设置对应值需要安装对应的包: 167 | * false npm install --save @babel/runtime 168 | * 2 npm install --save @babel/runtime-corejs2 169 | * 3 npm install --save @babel/runtime-corejs3 170 | */ 171 | corejs: 3, 172 | /** 173 | * helpers: boolean, 默认true。在纯babel的情况下: 174 | * 如果是true,就会把需要他runtime包给引进来,如:import _defineProperty from "@babel/runtime/helpers/defineProperty" 175 | * 如果是false,就会把需要的runtime包里面的代码给嵌进bundle里,如function _defineProperty(){} 176 | * 设置false的话,会导致同一个runtime包里面的代码被很多文件设置,产生冗余的代码。而且因为虽然是同一 177 | * 份runtime包里面的代码,但是他们在不同的文件(模块)里面,都有自己的作用域,因此在使用类似webpack之类的 178 | * 打包工具打包的时候,不会做优化。因此推荐设置true,这样可以通过静态分析的手段进行打包,减少打包后的代码体积。 179 | */ 180 | helpers: true, // 当helpers设置true的时候,babelHelpers需要设置为runtime 181 | // helpers: false, // 当helpers设置false的时候,babelHelpers需要设置为bundled 182 | // regenerator: true, // 切换生成器函数是否转换为使用不污染全局范围的再生器运行时。默认为true 183 | version: babelRuntimeVersion, 184 | }, 185 | ], 186 | ], 187 | /** 188 | * babelHelpers,建议显式配置此选项(即使使用其默认值) 189 | * runtime: 您应该使用此功能,尤其是在使用汇总构建库时,它结合external使用 190 | * bundled: 如果您希望生成的捆绑包包含这些帮助程序(每个最多一份),您应该使用它。特别是在捆绑应用程序代码时很有用 191 | * 如果babelHelpers设置成bundled,@babel/plugin-transform-runtime的helpers得设置false! 192 | * 如果babelHelpers设置成runtime,@babel/plugin-transform-runtime的helpers得设置true! 193 | * 在打包esm和cjs时,使用runtime,并且配合external;在打包umd时,使用bundled,并且不要用external,如果打包umd时使 194 | * 用了runtime但是没有配置external,会导致打包重复的polyfill,虽然打包的时候不报错,但是引入包使用的时候会报错 195 | */ 196 | // babelHelpers: 'bundled', // 默认bundled,可选:"bundled" | "runtime" | "inline" | "external" | undefined 197 | babelHelpers: 'runtime', // 默认bundled,可选:"bundled" | "runtime" | "inline" | "external" | undefined 198 | }), 199 | ], 200 | }, 201 | // cjs 202 | { 203 | input: './src/index.ts', 204 | output: { 205 | format: 'cjs', 206 | file: 'dist/index.cjs.js', 207 | /** 208 | * exports默认值是auto,可选:default、none。https://rollupjs.org/guide/zh/#exports 209 | * 如果我们源代码默认导出和具名导出一起使用,编译的时候会报警告(!) Mixing named and default exports 210 | * 设置exports: 'named'就不会报警告了(实际上只是不会报警告了,设不设置named对实际打包的结果都没影响) 211 | * 如果我们源代码没有默认导出和具名导出一起使用,但是设置了exports: 'named',会生成:exports["default"] = BilldUtils; 212 | * 别人通过cjs导入的话,就得const BilldUtils = require("billd-utils").default;才能拿到默认导出;如果不使用exports: 'named', 213 | * 默认会生成:module.exports = BilldUtils;别人通过cjs导入的话,就正常的const BilldUtils = require("billd-utils");即可 214 | */ 215 | exports: 'named', // babel-utils默认导出和具名导出混用了,因此需要设置exports: 'named' 216 | }, 217 | external: allDep, 218 | plugins: [ 219 | /** 220 | * @rollup/plugin-commonjs插件主要是将commonjs转换为esm 221 | * @rollup/plugin-commonjs一般和@rollup/plugin-node-resolve一起使用 222 | * @babel/plugin-transform-runtime使用corejs:3(即@babel/runtime-corejs3), 223 | * 而@babel/runtime-corejs3源码是使用commonjs规范写的,因此需要添加@rollup/plugin-commonjs插件 224 | * 让rollup支持commonjs 规范,识别 commonjs 规范的依赖。 225 | * 打包esm和cjs时,当前的billd-monorepo里面的源码基本都没有依赖第三方的包,并且排除了所有外部依赖,因此 226 | * 这里其实不需要用@rollup/plugin-commonjs插件 227 | * @rollup/plugin-commonjs和@rollup/plugin-babel一起用的使用,先使用@rollup/plugin-commonjs,再用@rollup/plugin-babel 228 | * https://github.com/rollup/plugins/blob/master/packages/babel/README.md#using-with-rollupplugin-commonjs 229 | */ 230 | commonjs(), 231 | /** 232 | * @rollup/plugin-node-resolve只对引入node_modules包的代码起作用 233 | * 不使用@rollup/plugin-node-resolve插件的话,import { ref } from 'vue';就不会把node_modules包 234 | * 里的vue的ref的代码引进来,而是会原封不动的把import { ref } from 'vue';放到打包的代码里面; 235 | * 使用@rollup/plugin-node-resolve插件的话,会将node_modules包里的vue的ref的代码都引进来 236 | * 打包esm和cjs时,当前的billd-monorepo里面的源码基本都没有依赖第三方的包,并且排除了所有外部依赖,因此 237 | * 这里其实不需要用@rollup/plugin-node-resolve插件 238 | */ 239 | nodeResolve(), 240 | esbuildPlugin(), 241 | json(), 242 | babel({ 243 | exclude: 'node_modules/**', // 只编译我们的源代码,最好加上它,否则打包umd可能会报错 244 | extensions: [...DEFAULT_EXTENSIONS, '.ts'], 245 | // 这里面的plugins如果和babel.config.js里的plugins冲突, 246 | // 会执行这里的plugins,不会执行babel.config.js里的plugins 247 | plugins: [ 248 | [ 249 | /** 250 | * @babel/plugin-transform-runtime 251 | * useBuiltIns和polyfill选项在 v7 中被删除,只是将其设为默认值。 252 | */ 253 | '@babel/plugin-transform-runtime', 254 | { 255 | // absoluteRuntime: false, // boolean或者string,默认为false。 256 | /** 257 | * corejs:false, 2,3或{ version: 2 | 3, proposals: boolean }, 默认为false 258 | * 设置对应值需要安装对应的包: 259 | * false npm install --save @babel/runtime 260 | * 2 npm install --save @babel/runtime-corejs2 261 | * 3 npm install --save @babel/runtime-corejs3 262 | */ 263 | corejs: 3, 264 | /** 265 | * helpers: boolean, 默认true。在纯babel的情况下: 266 | * 如果是true,就会把需要他runtime包给引进来,如:import _defineProperty from "@babel/runtime/helpers/defineProperty" 267 | * 如果是false,就会把需要的runtime包里面的代码给嵌进bundle里,如function _defineProperty(){} 268 | * 设置false的话,会导致同一个runtime包里面的代码被很多文件设置,产生冗余的代码。而且因为虽然是同一 269 | * 份runtime包里面的代码,但是他们在不同的文件(模块)里面,都有自己的作用域,因此在使用类似webpack之类的 270 | * 打包工具打包的时候,不会做优化。因此推荐设置true,这样可以通过静态分析的手段进行打包,减少打包后的代码体积。 271 | */ 272 | helpers: true, // 当helpers设置true的时候,babelHelpers需要设置为runtime 273 | // helpers: false, // 当helpers设置false的时候,babelHelpers需要设置为bundled 274 | // regenerator: true, // 切换生成器函数是否转换为使用不污染全局范围的再生器运行时。默认为true 275 | version: babelRuntimeVersion, 276 | }, 277 | ], 278 | ], 279 | /** 280 | * babelHelpers,建议显式配置此选项(即使使用其默认值) 281 | * runtime: 您应该使用此功能,尤其是在使用汇总构建库时,它结合external使用 282 | * bundled: 如果您希望生成的捆绑包包含这些帮助程序(每个最多一份),您应该使用它。特别是在捆绑应用程序代码时很有用 283 | * 如果babelHelpers设置成bundled,@babel/plugin-transform-runtime的helpers得设置false! 284 | * 如果babelHelpers设置成runtime,@babel/plugin-transform-runtime的helpers得设置true! 285 | * 在打包esm和cjs时,使用runtime,并且配合external;在打包umd时,使用bundled,并且不要用external,如果打包umd时使 286 | * 用了runtime但是没有配置external,会导致打包重复的polyfill,虽然打包的时候不报错,但是引入包使用的时候会报错 287 | */ 288 | // babelHelpers: 'bundled', // 默认bundled,可选:"bundled" | "runtime" | "inline" | "external" | undefined 289 | babelHelpers: 'runtime', // 默认bundled,可选:"bundled" | "runtime" | "inline" | "external" | undefined 290 | }), 291 | ], 292 | }, 293 | // .d.ts 294 | { 295 | input: './src/index.ts', 296 | output: { 297 | file: './dist/index.d.ts', 298 | name: pkg.name, 299 | }, 300 | plugins: [dtsPlugin()], 301 | }, 302 | // umd 303 | umdConfig(), 304 | // umd prod 305 | umdConfig(true), 306 | ]); 307 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | 3 | import { chalkERROR } from './utils'; 4 | 5 | const watch = process.argv.includes('--watch'); 6 | 7 | // rollup打包 8 | const rollupBuild = () => { 9 | execSync(`pnpm run build:rollup${watch ? ' --watch' : ''}`, { 10 | stdio: 'inherit', 11 | }); 12 | }; 13 | 14 | (() => { 15 | try { 16 | rollupBuild(); 17 | // npm publish默认会带上根目录的LICENSE、README.md、package.json 18 | // copyFile(); 19 | } catch (error) { 20 | console.log(chalkERROR(`!!!本地构建失败!!!`)); 21 | console.log(error); 22 | console.log(chalkERROR(`!!!本地构建失败!!!`)); 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /scripts/publish.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | 3 | import pkg from '../package.json'; 4 | import { chalkERROR, chalkSUCCESS } from './utils'; 5 | 6 | const command = 'npm publish'; 7 | 8 | // git push 9 | execSync(`git push origin v${pkg.version}`, { stdio: 'inherit' }); 10 | execSync(`git push`, { stdio: 'inherit' }); 11 | 12 | try { 13 | execSync(command, { 14 | stdio: 'inherit', 15 | }); 16 | console.log(chalkSUCCESS(`发布${pkg.name}@${pkg.version}成功!`)); 17 | } catch (error) { 18 | console.log(error); 19 | console.log(chalkERROR(`发布${pkg.name}@${pkg.version}失败!`)); 20 | } 21 | -------------------------------------------------------------------------------- /scripts/release.ts: -------------------------------------------------------------------------------- 1 | import { exec, execSync } from 'child_process'; 2 | import path from 'path'; 3 | 4 | import { readJSONSync, writeJSONSync } from 'fs-extra'; 5 | import inquirer from 'inquirer'; 6 | import semver from 'semver'; 7 | 8 | import { chalkERROR, chalkINFO, chalkSUCCESS } from './utils'; 9 | 10 | const { version: currentVersion } = readJSONSync('package.json'); // 项目根目录的package.json 11 | 12 | export const DIR_ROOT = path.resolve(__dirname, '..'); 13 | export const DIR_PACKAGES = path.resolve(__dirname, '../packages'); 14 | 15 | const preId = 16 | semver.prerelease(currentVersion) && semver.prerelease(currentVersion)[0]; 17 | 18 | const versionChoices = [ 19 | 'patch', 20 | 'minor', 21 | 'major', 22 | ...(preId ? ['prepatch', 'preminor', 'premajor', 'prerelease'] : []), 23 | ]; 24 | 25 | const inc = (i: string): string => semver.inc(currentVersion, i, preId); 26 | let targetVersion: string; 27 | 28 | const selectReleaseVersion = async () => { 29 | const { release } = await inquirer.prompt([ 30 | { 31 | type: 'list', 32 | name: 'release', 33 | message: 'Select release type', 34 | choices: versionChoices.map((i) => `${i} (${inc(i)})`), 35 | }, 36 | ]); 37 | const pkg = readJSONSync(path.resolve(__dirname, '../package.json')); // 项目根目录的package.json 38 | targetVersion = release.match(/\((.*)\)/)[1]; 39 | 40 | const { confirmRelease } = await inquirer.prompt([ 41 | { 42 | type: 'confirm', 43 | name: 'confirmRelease', 44 | default: false, 45 | message: `Confirm release v${targetVersion}?`, 46 | }, 47 | ]); 48 | 49 | if (confirmRelease) { 50 | console.log(chalkINFO(`开始本地发布v${targetVersion}...`)); 51 | 52 | // 更新根目录的package.json版本号 53 | writeJSONSync( 54 | 'package.json', 55 | { ...pkg, version: targetVersion }, 56 | { spaces: 2 } 57 | ); 58 | 59 | // pnpm run build 60 | execSync(`pnpm run build`, { stdio: 'inherit' }); 61 | 62 | // 生成changelog 63 | execSync(`pnpm run changelog`, { stdio: 'inherit' }); 64 | 65 | // git commit 66 | execSync(`git add .`, { stdio: 'inherit' }); 67 | execSync(`git commit -m 'chore(release): v${targetVersion}'`, { 68 | stdio: 'inherit', 69 | }); 70 | 71 | // git tag 72 | execSync(`git tag v${targetVersion}`, { stdio: 'inherit' }); 73 | } else { 74 | console.log(chalkERROR(`取消本地发布!`)); 75 | } 76 | }; 77 | 78 | function gitIsClean() { 79 | return new Promise((resolve, reject) => { 80 | exec('git status -s', (error, stdout, stderr) => { 81 | if (error || stderr) { 82 | reject(error || stderr); 83 | } 84 | if (stdout.length) { 85 | reject('请确保本地代码已经提交git'); 86 | } else { 87 | resolve('ok'); 88 | } 89 | }); 90 | }); 91 | } 92 | 93 | (async () => { 94 | await gitIsClean(); 95 | await selectReleaseVersion(); 96 | })().then( 97 | () => { 98 | console.log(chalkSUCCESS(`本地发布v${targetVersion}成功!`)); 99 | }, 100 | (rej) => { 101 | console.log(rej); 102 | console.log(chalkERROR(`!!!本地发布v${targetVersion}失败!!!`)); 103 | } 104 | ); 105 | -------------------------------------------------------------------------------- /scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import nodeChalk from 'chalk'; 2 | 3 | export const chalk = nodeChalk; 4 | export const chalkINFO = (v) => 5 | `${chalk.bgBlueBright.black(' INFO ')} ${chalk.blueBright(v)}`; 6 | export const chalkSUCCESS = (v) => 7 | `${chalk.bgGreenBright.black(' SUCCESS ')} ${chalk.greenBright(v)}`; 8 | export const chalkERROR = (v) => 9 | `${chalk.bgRedBright.black(' ERROR ')} ${chalk.redBright(v)}`; 10 | export const chalkWARN = (v) => 11 | `${chalk.bgHex('#FFA500').black(' WARN ')} ${chalk.hex('#FFA500')(v)}`; 12 | -------------------------------------------------------------------------------- /src/billd-utils.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | import utils from 'bi-utils'; 3 | } 4 | -------------------------------------------------------------------------------- /src/class/cache.ts: -------------------------------------------------------------------------------- 1 | // TIP: ctrl+cmd+t,生成函数注释 2 | import { debugLog } from '../utils/index'; 3 | 4 | export class CacheModel { 5 | prefix = ''; 6 | 7 | constructor(prefix: string) { 8 | this.prefix = prefix; 9 | } 10 | 11 | handlePrefix = (key: string) => { 12 | if (this.prefix === '') { 13 | return key; 14 | } else { 15 | return `${this.prefix}${key}`; 16 | } 17 | }; 18 | 19 | getItem = (key: string) => { 20 | return localStorage.getItem(this.handlePrefix(key)); 21 | }; 22 | 23 | setItem = (key: string, value: any) => { 24 | return localStorage.setItem(this.handlePrefix(key), value); 25 | }; 26 | 27 | removeItem = (key: string) => { 28 | return localStorage.removeItem(this.handlePrefix(key)); 29 | }; 30 | 31 | /** 32 | * @description 获取缓存 33 | * @param {string} key 34 | * @return {*} 35 | */ 36 | getStorage = (key: string): T | null => { 37 | try { 38 | const res = this.getItem(key); 39 | if (res) { 40 | const data = JSON.parse(res); 41 | // 如果createTime没有值,则判断该缓存不合法;清除 42 | if (!data.createTime) { 43 | this.clearStorage(key); 44 | return null; 45 | } else { 46 | return data.value; 47 | } 48 | } 49 | return null; 50 | } catch (error) { 51 | debugLog('error', error); 52 | this.clearStorage(key); 53 | return null; 54 | } 55 | }; 56 | 57 | /** 58 | * @description 设置缓存 59 | * @param {*} key 60 | * @param {*} value 61 | */ 62 | setStorage = (key: string, value: any) => { 63 | try { 64 | const createTime = +new Date(); 65 | this.setItem(key, JSON.stringify({ value, createTime })); 66 | } catch (error) { 67 | debugLog('error', error); 68 | this.clearStorage(key); 69 | } 70 | }; 71 | 72 | /** 73 | * @description 清除缓存 74 | * @param {*} key 75 | */ 76 | clearStorage = (key: string) => { 77 | try { 78 | this.removeItem(key); 79 | } catch (error) { 80 | debugLog('error', error); 81 | } 82 | }; 83 | 84 | /** 85 | * @description 获取缓存,如果缓存已过期,会清除该缓存,并返回null 86 | * @param {*} key 87 | */ 88 | getStorageExp = (key: string): T | null => { 89 | try { 90 | const res = this.getItem(key); 91 | if (res) { 92 | const data = JSON.parse(res); 93 | const expireTime = data.expireTime; 94 | const isExpired = expireTime < +new Date(); 95 | // 如果expireTime没有值,则判断该缓存不合法;清除 96 | // 如果expireTime有值,但小于当前时间,则代表已过期;清除 97 | if (!expireTime || isExpired) { 98 | this.clearStorage(key); 99 | return null; 100 | } else { 101 | return data.value; 102 | } 103 | } 104 | return null; 105 | } catch (error) { 106 | debugLog('error', error); 107 | this.clearStorage(key); 108 | return null; 109 | } 110 | }; 111 | 112 | /** 113 | * @description 设置缓存以及缓存时长 114 | * @param {*} key 115 | * @param {*} value 116 | * @param {*} expires 缓存时长,单位:小时 117 | */ 118 | setStorageExp = (key: string, value: any, expires: number) => { 119 | try { 120 | if ([key, value, expires].includes(undefined)) { 121 | debugLog('error', '请检查传入的参数!'); 122 | return; 123 | } 124 | const createTime = +new Date(); 125 | const expireTime = createTime + expires * 60 * 60 * 1000; 126 | this.setItem(key, JSON.stringify({ value, createTime, expireTime })); 127 | } catch (error) { 128 | debugLog('error', error); 129 | this.clearStorage(key); 130 | } 131 | }; 132 | } 133 | -------------------------------------------------------------------------------- /src/class/lru-cache.ts: -------------------------------------------------------------------------------- 1 | // TIP: ctrl+cmd+t,生成函数注释 2 | 3 | export class LRUCache { 4 | capacity: number; 5 | data = new Map(); 6 | 7 | constructor(capacity: number) { 8 | if (capacity < 1) throw new Error('capacity必须大于1!'); 9 | this.capacity = capacity; 10 | } 11 | 12 | get(key) { 13 | const data = this.data; 14 | const value = data.get(key); 15 | 16 | // 如果缓存里没有这个key,则返回null 17 | if (!data.has(key)) return null; 18 | 19 | // 如果缓存里有这个key,则删了旧的缓存,再设置新缓存(目的是让读取的这个key移到最后面) 20 | data.delete(key); 21 | data.set(key, value); 22 | 23 | return value; 24 | } 25 | 26 | put(key, value) { 27 | const data = this.data; 28 | 29 | if (data.has(key)) { 30 | // 如果缓存里有,则删了旧的缓存 31 | data.delete(key); 32 | } 33 | 34 | // 不管缓存里有没有,put操作都要设置缓存 35 | data.set(key, value); 36 | 37 | // 最后判断缓存是否超过capacity,如果超过则删掉最久没使用的缓存(也就是第一个) 38 | if (data.size > this.capacity) { 39 | data.delete(data.keys().next().value); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/class/queue.ts: -------------------------------------------------------------------------------- 1 | // TIP: ctrl+cmd+t,生成函数注释 2 | import { debugLog } from '../utils/index'; 3 | 4 | export class ConcurrentPoll { 5 | /** 任务队列 */ 6 | tasks: any[] = []; 7 | /** 最大并发数 */ 8 | max = 0; 9 | total = 0; 10 | delay = 0; 11 | done: () => void; 12 | 13 | constructor({ max = 5, done, delay = 0 }) { 14 | this.tasks = []; 15 | this.total = 0; 16 | this.max = max; 17 | this.done = done; 18 | this.delay = delay; 19 | setTimeout(() => { 20 | /** 函数主体执行完后立即执行 */ 21 | this.run(); 22 | }, 0); 23 | } 24 | 25 | addTask(task) { 26 | this.tasks.push(task); 27 | this.total += 1; 28 | } 29 | 30 | run() { 31 | /** 原型任务运行方法 */ 32 | if (this.tasks.length === 0) { 33 | /** 判断是否还有任务 */ 34 | return Promise.resolve(''); 35 | } 36 | 37 | /** 取任务个数与最大并发数最小值 */ 38 | const min = Math.min(this.tasks.length, this.max); 39 | 40 | for (let i = 0; i < min; i += 1) { 41 | /** 执行最大并发递减 */ 42 | this.max -= 1; 43 | /** 从数组头部取任务 */ 44 | const task = this.tasks.shift(); 45 | task() 46 | .then(() => { 47 | // 重:此时可理解为,当for循环执行完毕后异步请求执行回调,此时max变为0 48 | }) 49 | .catch((error) => { 50 | debugLog('error', error); 51 | }) 52 | .finally(() => { 53 | /** 重:当所有请求完成并返回结果后,执行finally回调,此回调将按照for循环依次执行,此时max为0. */ 54 | setTimeout(() => { 55 | /** 超过最大并发10以后的任务将按照任务顺序依次执行。此处可理解为递归操作。 */ 56 | this.max += 1; 57 | this.total -= 1; 58 | this.run(); 59 | if (this.total === 0) { 60 | this.done?.(); 61 | } 62 | }, this.delay); 63 | }); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { version as __VERSION__ } from '../package.json'; 2 | import * as utils from './js/index'; 3 | 4 | export * from './js/index'; 5 | 6 | export const version = __VERSION__; 7 | 8 | export default utils; 9 | -------------------------------------------------------------------------------- /src/js/array.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 数组去重,缺点不能去除{} 3 | * @param {Array} arr 4 | * @return {*} 不修改原数组,返回新数组 5 | */ 6 | export const arrayUnique = (arr: Array) => { 7 | return [...new Set(arr)]; 8 | }; 9 | 10 | /** 11 | * @description 洗牌算法 12 | * @param {Array} arr 13 | * @return {*} 不修改原数组,返回新数组 14 | */ 15 | export const arrayShuffle = (arr: Array) => { 16 | const result = [...arr]; 17 | for (let i = result.length - 1; i >= 0; i -= 1) { 18 | const randomIndex = Math.floor(Math.random() * (i + 1)); // 随机下标 19 | const randomVal = result[randomIndex]; // 随机下标的值 20 | // 互换位置 21 | result[randomIndex] = result[i]; 22 | result[i] = randomVal; 23 | } 24 | return result; 25 | }; 26 | 27 | /** 28 | * @description 获取数组交集 29 | * @param {any} a 30 | * @param {any} b 31 | * @return {*} 32 | */ 33 | export const getArrayIntersection = (a: any[], b: any[]) => { 34 | return a.filter((v) => { 35 | return b.indexOf(v) > -1; 36 | }); 37 | }; 38 | 39 | /** 40 | * @description 获取数组的对称差集(不修改原数组) 41 | * @example 42 | * a[1,2,3,4,5],b[3,4,5,6,7],a和b的对称差集:getArraySymmetricDifference(a,b) ===> [1,2,6,7] 43 | * @param {any} a 44 | * @param {any} b 45 | * @return {*} 46 | */ 47 | export function getArraySymmetricDifference(arr1: any[], arr2: any[]) { 48 | const result: any[] = []; 49 | const diff = arr1.filter((x) => !arr2.includes(x)); 50 | const diff2 = arr2.filter((x) => !arr1.includes(x)); 51 | result.push(...diff); 52 | result.push(...diff2); 53 | return result; 54 | } 55 | 56 | /** 57 | * @description 获取数组差集(不修改原数组) 58 | * @example 59 | * a[1,2,3,4,5],b[2,4,6,8,10],a和b的差集:getArrayDifference(a,b) ===> [1,3,5] 60 | * a[1,2,3,4,5],b[2,4,6,8,10],b和a的差集:getArrayDifference(b,a) ===> [6,8,10] 61 | * @param {any} a 62 | * @param {any} b 63 | * @return {*} 64 | */ 65 | export const getArrayDifference = (a: any[], b: any[]) => { 66 | return a.filter((v) => { 67 | return b.indexOf(v) === -1; 68 | }); 69 | }; 70 | 71 | -------------------------------------------------------------------------------- /src/js/bom.ts: -------------------------------------------------------------------------------- 1 | /** 判断是否是ipad */ 2 | export const isIPad = () => { 3 | const ua = navigator.userAgent.toLowerCase(); 4 | // iOS13以前navigator.platform返回"iPhone"或"iPad";iOS13以后的iPad,navigator.platform返回"MacIntel" 5 | const res = ua.match(/iPad/i); 6 | if (res?.length) { 7 | return true; 8 | } 9 | if (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) { 10 | return true; 11 | } 12 | return false; 13 | }; 14 | 15 | /** 16 | * @description 判断是否是移动端(判断比较粗糙) 17 | * @return {*} 18 | */ 19 | export const isMobile = () => { 20 | // iOS13以前navigator.platform返回"iPhone"或"iPad";iOS13以后的iPad,navigator.platform返回"MacIntel" 21 | if (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) { 22 | return true; 23 | } 24 | return /android|ios|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test( 25 | navigator.userAgent 26 | ); 27 | }; 28 | 29 | /** 30 | * @description 判断是否是微信浏览器 31 | * @return {*} 32 | */ 33 | export const isWechat = () => { 34 | return /MicroMessenger/i.test(window.navigator.userAgent); 35 | }; 36 | 37 | /** 38 | * @description 判断设备类型 39 | * @return {*} 40 | */ 41 | export const judgeDevice = () => { 42 | const ua = navigator.userAgent; 43 | const isAndroid = /(Android)/i.test(ua); 44 | const isIphone = /(iPhone|iPad|iPod|iOS)/i.test(ua); 45 | const isIPadRes = isIPad(); 46 | 47 | return { isAndroid, isIphone, isIPad: isIPadRes }; 48 | }; 49 | 50 | /** 51 | * @description 判断是否是浏览器环境 52 | * @param {*} boolean 53 | * @return {*} 54 | */ 55 | export const isBrowser = () => 56 | typeof window !== 'undefined' && 57 | typeof window.document !== 'undefined' && 58 | typeof window.document.createElement !== 'undefined'; 59 | 60 | /** 61 | * @description 判断是否是Safari浏览器 62 | * @return {*} 63 | */ 64 | export const isSafari = () => { 65 | // mac下的Chrome浏览器的navigator.userAgent既有Safari也有Chrome,因此得排除mac下的Chrome浏览器 66 | return ( 67 | /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent) 68 | ); 69 | }; 70 | 71 | /** 72 | * @description 判断是否是ie浏览器 73 | * @return {*} 74 | */ 75 | export const isIe = () => { 76 | return ( 77 | navigator.userAgent.indexOf('MSIE') !== -1 || 78 | navigator.userAgent.indexOf('Trident') !== -1 79 | ); 80 | }; 81 | /** 82 | * @description 判断是否是Firefox浏览器 83 | * @return {*} 84 | */ 85 | export const isFirefox = () => { 86 | return navigator.userAgent.indexOf('Firefox') !== -1; 87 | }; 88 | -------------------------------------------------------------------------------- /src/js/dom.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 计算两个dom是否有交集 3 | * @param {Element} element1 4 | * @param {Element} element2 5 | * @return {*} 6 | */ 7 | export function elementsIsIntersect(element1: Element, element2: Element) { 8 | const rect1 = element1.getBoundingClientRect(); 9 | const rect2 = element2.getBoundingClientRect(); 10 | 11 | return ( 12 | rect1.left <= rect2.right && 13 | rect1.right >= rect2.left && 14 | rect1.top <= rect2.bottom && 15 | rect1.bottom >= rect2.top 16 | ); 17 | } 18 | 19 | /** 20 | * @description 获取dom元素的样式值,注意:如果获取的样式值没有显示的声明, 21 | * 会获取到它的默认值,比如position没有设置值,获取它的position就会返回static 22 | * @param {Element} ele 23 | * @param {*} styleName 24 | * @return {*} 25 | */ 26 | export const getStyle = (ele: Element, styleName: string) => { 27 | if (window.getComputedStyle) { 28 | return window.getComputedStyle(ele, null)[styleName]; 29 | } else { 30 | // 兼容ie 31 | // @ts-ignore 32 | return ele.currentStyle[styleName]; 33 | } 34 | }; 35 | 36 | /** 37 | * @description 将内容复制到剪切板 38 | * @param {string} text 39 | * @return {*} 40 | */ 41 | export const copyToClipBoard = (text: string): void => { 42 | const oInput = document.createElement('input'); 43 | oInput.value = text; 44 | document.body.appendChild(oInput); 45 | oInput.select(); // 选择对象 46 | document.execCommand('Copy'); // 执行浏览器复制命令 47 | oInput.parentElement?.removeChild(oInput); 48 | }; 49 | 50 | /** 51 | * @description 获取滚动条宽度 52 | * @copy https://github.com/iview/iview/blob/2.0/src/utils/assist.js#L19 53 | * @return {*} 54 | */ 55 | export const getScrollBarSize = () => { 56 | const inner = document.createElement('div'); 57 | inner.style.width = '100%'; 58 | inner.style.height = '200px'; 59 | 60 | const outer = document.createElement('div'); 61 | const outerStyle = outer.style; 62 | 63 | outerStyle.position = 'absolute'; 64 | outerStyle.top = '0px'; 65 | outerStyle.left = '0px'; 66 | outerStyle.pointerEvents = 'none'; 67 | outerStyle.visibility = 'hidden'; 68 | outerStyle.width = '200px'; 69 | outerStyle.height = '150px'; 70 | outerStyle.overflow = 'hidden'; 71 | 72 | outer.appendChild(inner); 73 | 74 | document.body.appendChild(outer); 75 | 76 | const widthContained = inner.offsetWidth; 77 | outer.style.overflow = 'scroll'; 78 | let widthScroll = inner.offsetWidth; 79 | 80 | if (widthContained === widthScroll) { 81 | widthScroll = outer.clientWidth; 82 | } 83 | 84 | document.body.removeChild(outer); 85 | 86 | return widthContained - widthScroll; 87 | }; 88 | -------------------------------------------------------------------------------- /src/js/format.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 格式化内存大小(要求传入的数字以byte为单位) 3 | * @param {number} val 4 | * @param {*} num 显示几位小数,默认2 5 | * @return {*} 6 | */ 7 | export const formatMemorySize = (val: number, num = 2) => { 8 | const oneByte = 1; 9 | const oneKb = oneByte * 1024; 10 | const oneMb = oneKb * 1024; 11 | const oneGb = oneMb * 1024; 12 | const oneTb = oneGb * 1024; 13 | const format = (v: number) => v.toFixed(num); 14 | 15 | if (val < oneKb) { 16 | return `${format(val / oneByte)}byte`; 17 | } 18 | if (val < oneMb) { 19 | return `${format(val / oneKb)}kb`; 20 | } 21 | if (val < oneGb) { 22 | return `${format(val / oneMb)}mb`; 23 | } 24 | if (val < oneTb) { 25 | return `${format(val / oneGb)}gb`; 26 | } 27 | return `${format(val / oneTb)}tb`; 28 | }; 29 | 30 | /** 31 | * @description 格式化时间 32 | * @param {number} timetamp 33 | */ 34 | export const formatDate = (timetamp: number) => { 35 | function addDateZero(num: number) { 36 | return num < 10 ? `0${num}` : num; 37 | } 38 | const date = new Date(timetamp); 39 | return { 40 | year: date.getFullYear(), 41 | month: addDateZero(date.getMonth() + 1), 42 | day: addDateZero(date.getDate()), 43 | hour: addDateZero(date.getHours()), 44 | minutes: addDateZero(date.getMinutes()), 45 | seconds: addDateZero(date.getSeconds()), 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /src/js/index.ts: -------------------------------------------------------------------------------- 1 | export * from './array'; 2 | export * from './bom'; 3 | export * from './dom'; 4 | export * from './format'; 5 | export * from './random'; 6 | export * from './reg'; 7 | export * from './url'; 8 | export * from './utils'; 9 | export * from '../class/cache'; 10 | export * from '../class/lru-cache'; 11 | export * from '../class/queue'; 12 | -------------------------------------------------------------------------------- /src/js/random.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取[min,max]之间的随机整数。 3 | * @example: getRangeRandom(-10,100) ===> -8 4 | * @param {number} min 5 | * @param {number} max 6 | * @return {*} 7 | */ 8 | export const getRangeRandom = (min: number, max: number) => 9 | Math.floor(Math.random() * (max - min + 1)) + min; 10 | 11 | /** 12 | * @description: 随机数组的一个元素 13 | * @example: getRandomOne([10,2,4,6]) ===> 6 14 | * @param {any} arr 15 | * @return {*} 16 | */ 17 | export const getRandomOne = (arr: any[]) => 18 | arr[Math.floor(Math.random() * arr.length)]; 19 | 20 | /** 21 | * @description 获取随机字符串(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789) 22 | * @example: getRandomString(4) ===> abd3 23 | * @param {number} length 24 | * @return {*} 25 | */ 26 | export const getRandomString = (length: number): string => { 27 | const str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 28 | let res = ''; 29 | for (let i = 0; i < length; i += 1) { 30 | res += str.charAt(getRangeRandom(0, str.length - 1)); 31 | } 32 | return res; 33 | }; 34 | 35 | /** 36 | * @description: 获取随机整数 37 | * @example: getRandomInt(4) ===> 3251 38 | * @param {*} length 39 | * @return {*} 40 | */ 41 | export const getRandomInt = (length) => { 42 | if (length > 16 || length < 1) throw new Error('length的范围:[1,16]'); 43 | let num = +`${Math.random()}`.slice(2, 2 + length); 44 | if (String(num).length !== length) { 45 | num = getRandomInt(length); 46 | } 47 | return num; 48 | }; 49 | -------------------------------------------------------------------------------- /src/js/reg.ts: -------------------------------------------------------------------------------- 1 | import { debugLog } from '../utils/index'; 2 | 3 | /** 4 | * @description 正则是否是纯数字(纯整数) 5 | * @example: isPureNumber(abc) ===> false;isPureNumber(1.23) ===> false;isPureNumber(123) ===> true; 6 | * @param {string} str 7 | * @return {*} 8 | */ 9 | export function isPureNumber(str: string) { 10 | const regex = /^\d+$/; 11 | return regex.test(str); 12 | } 13 | 14 | /** 15 | * @description 正则验证手机号、邮箱是否合法 16 | * @param {string} str 17 | * @param {*} type 18 | * @return {*} 19 | */ 20 | export const regVerify = (str: string, type: 'phone' | 'email') => { 21 | try { 22 | switch (type) { 23 | case 'email': 24 | // https://ihateregex.io/expr/email 25 | return /[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+/.test(str); 26 | case 'phone': 27 | return /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/.test( 28 | str 29 | ); 30 | } 31 | } catch (error: any) { 32 | debugLog('error', error); 33 | return false; 34 | } 35 | }; 36 | 37 | /** 38 | * @description 判断字符串的开头和结尾是否有空格,有空格就返回true,否则返回false 39 | * @param {string} value 40 | * @return {*} 41 | */ 42 | export const judgeStringSpace = (value: string) => { 43 | const reg1 = /^\s+/g; // 匹配开头空格 44 | const reg2 = /\s+$/g; // 匹配结尾空格 45 | if (reg1.test(value) || reg2.test(value)) { 46 | return true; 47 | } 48 | return false; 49 | }; 50 | -------------------------------------------------------------------------------- /src/js/url.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取地址栏参数(注意:请确保url是http://aaa.com/ds/?aa=1&bb=323这样子的) 3 | * @return {*} 4 | */ 5 | export const getUrlParams = (key?: string) => { 6 | const url = decodeURIComponent(window.location.href); 7 | const str = url.split('?')[1]; 8 | const obj = {}; 9 | if (str) { 10 | const keys = str.split('&'); 11 | keys.forEach((item) => { 12 | const arr = item.split('='); 13 | obj[arr[0]] = arr[1]; 14 | }); 15 | } 16 | return key ? obj[key] : obj; 17 | }; 18 | -------------------------------------------------------------------------------- /src/js/utils.ts: -------------------------------------------------------------------------------- 1 | import { debugLog } from '../utils/index'; 2 | 3 | /** 4 | * @description: 字符串编码 5 | * @param {string} str 6 | * @return {*} 7 | */ 8 | export function strBtoa(str: string) { 9 | return window.btoa(window.encodeURIComponent(str)); 10 | } 11 | 12 | /** 13 | * @description: 字符串解码 14 | * @param {string} str 15 | * @return {*} 16 | */ 17 | export function strAtob(str: string) { 18 | return window.decodeURIComponent(window.atob(str)); 19 | } 20 | 21 | /** 22 | * @description: 异步更新 23 | * @param {*} fn 24 | * @return {*} 25 | */ 26 | export const asyncUpdate = (fn, delay?) => { 27 | return new Promise((resolve) => { 28 | setTimeout(() => { 29 | const res = fn(); 30 | resolve(res); 31 | }, delay || 0); 32 | }); 33 | }; 34 | 35 | /** 36 | * @description: 模拟ajax请求 37 | * @param {*} param1 38 | * @return {*} 39 | */ 40 | export const mockAjax = ({ flag = true, delay = 500 }) => { 41 | return new Promise<{ code: number; data: { id: number }; msg: string }>( 42 | (resolve, reject) => { 43 | setTimeout(() => { 44 | if (flag) { 45 | resolve({ 46 | code: 200, 47 | data: { 48 | id: 1, 49 | }, 50 | msg: '请求成功', 51 | }); 52 | } else { 53 | reject({ 54 | code: 400, 55 | msg: '请求失败', 56 | }); 57 | } 58 | }, delay); 59 | } 60 | ); 61 | }; 62 | 63 | /** 64 | * @description: 将里面盒子等比例适配外层盒子 65 | * 如果里层盒子的宽或高有一边大于外层盒子的宽或高,可不设置minWidth和minHeight, 66 | * 如果里层盒子的宽和高都小于外层盒子的宽和高需要设置maxWidth和minWidth一致,maxHeight和minHeight一致 67 | * @param width 里面的盒子宽度 68 | * @param height 里面的盒子高度 69 | * @param maxWidth 外面的盒子最大宽度 70 | * @param maxHeight 外面的盒子最大高度 71 | * @param minWidth 外面的盒子最小宽度 72 | * @param minHeight 外面的盒子最小高度 73 | * @returns {{width: number, height: number}} 返回适配好的盒子宽高 74 | */ 75 | export function computeBox({ 76 | width, 77 | height, 78 | maxWidth, 79 | maxHeight, 80 | minWidth, 81 | minHeight, 82 | }) { 83 | // w = h / ratio, h = w * ratio 84 | const ratio = height / width; 85 | // eslint-disable-next-line 86 | const _minWidth = minWidth ? minWidth : 0; 87 | // eslint-disable-next-line 88 | const _minHeight = minHeight ? minHeight : 0; 89 | // eslint-disable-next-line 90 | let _width = width; 91 | // eslint-disable-next-line 92 | let _height = height; 93 | 94 | if (_width < _minWidth) { 95 | _width = _minWidth; 96 | _height = _minWidth * ratio; 97 | } 98 | if (_height < _minHeight) { 99 | _width = _minHeight / ratio; 100 | _height = _minHeight; 101 | } 102 | 103 | if (_width > maxWidth) { 104 | _width = maxWidth; 105 | _height = maxWidth * ratio; 106 | } 107 | if (_height > maxHeight) { 108 | _width = maxHeight / ratio; 109 | _height = maxHeight; 110 | } 111 | 112 | return { 113 | width: _width, 114 | height: _height, 115 | }; 116 | } 117 | 118 | /** 119 | * @description: 下载图片 120 | * @param {string} src 121 | * @param {string} name 122 | * @return {*} 123 | */ 124 | export const downloadImg = (src: string, name: string) => { 125 | const imgEl = new Image(); 126 | imgEl.src = src; 127 | imgEl.setAttribute('crossOrigin', 'anonymous'); // 跨域 128 | imgEl.onload = function () { 129 | const canvas = document.createElement('canvas'); 130 | canvas.width = imgEl.width; 131 | canvas.height = imgEl.height; 132 | 133 | const context = canvas.getContext('2d'); 134 | context!.drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); 135 | const url = canvas.toDataURL('image/png'); 136 | 137 | // 生成一个a元素 138 | const a = document.createElement('a'); 139 | // 创建一个单击事件 140 | const event = new MouseEvent('click'); 141 | 142 | a.download = name || '下载图片名称'; 143 | // 将生成的URL设置为a.href属性 144 | a.href = url; 145 | // 触发a的单击事件 146 | a.dispatchEvent(event); 147 | }; 148 | imgEl.onerror = function (e) { 149 | debugLog('error', '下载图片出错', e); 150 | }; 151 | }; 152 | 153 | /** 154 | * @description: 跳转(window.location.href) 155 | * @param {array} arg 156 | * @return {*} 157 | */ 158 | export const hrefToTarget = (url: string) => { 159 | window.location.href = url; 160 | }; 161 | 162 | /** 163 | * @description: 跳转(window.open) 164 | * @param {array} arg 165 | * @return {*} 166 | */ 167 | export const openToTarget = (...arg) => { 168 | window.open(...arg); 169 | }; 170 | 171 | /** 172 | * @description: 刷新页面(window.location.reload) 173 | */ 174 | export const windowReload = () => { 175 | window.location.reload(); 176 | }; 177 | 178 | /** 179 | * @description: 获取文件后缀 180 | * @param {string} filename 181 | * @return {*} 182 | */ 183 | export const getFileExt = (filename: string) => { 184 | const arr = filename.split('.'); 185 | const ext = arr[arr.length - 1]; 186 | return ext; 187 | }; 188 | 189 | /** 190 | * @description: 生成style标签,并挂载到head 191 | * @example: generateStyle({ '.a': { color: 'red' }, '#b': { color: 'blue' } }); 192 | * 最终会将挂载到head里 193 | * @param {number} styleObj 194 | * @return {*} 195 | */ 196 | export const generateStyle = (styleObj: Record) => { 197 | const styleEle = document.createElement('style'); 198 | styleEle.type = 'text/css'; 199 | let textContent = ''; 200 | function getStyleVal(obj: string) { 201 | let str = ''; 202 | Object.keys(obj).forEach((key: string) => { 203 | // eslint-disable-next-line 204 | str += `${key}:${obj[key]};`; 205 | }); 206 | return str; 207 | } 208 | Object.keys(styleObj).forEach((key) => { 209 | textContent += `${key}{`; 210 | textContent += getStyleVal(styleObj[key]); 211 | textContent += '}'; 212 | }); 213 | styleEle.textContent = textContent; 214 | document.head.appendChild(styleEle); 215 | }; 216 | 217 | /** 218 | * @description: 图片预加载 219 | * @example: imgPrereload(['aaa.com/a.webp', 'aaa.com/b.webp']); 220 | * @param {string} imgList 221 | * @return {*} 222 | */ 223 | export const imgPrereload = (imgList: string[]) => { 224 | return imgList.map((url) => { 225 | return new Promise((resolve, reject) => { 226 | const img = new Image(); 227 | img.src = url; 228 | img.onload = () => resolve({ url }); 229 | img.onerror = (error) => reject({ url, error }); 230 | }); 231 | }); 232 | }; 233 | 234 | /** 235 | * @description: 是否支持0.5px 236 | * @return {*} 237 | */ 238 | export const supportHairlines = () => { 239 | const fakeBody = document.createElement('body'); 240 | const testElement = document.createElement('div'); 241 | testElement.style.border = '.5px solid transparent'; 242 | fakeBody.appendChild(testElement); 243 | document.documentElement.appendChild(fakeBody); 244 | if (testElement.offsetHeight === 1) { 245 | return true; 246 | } else { 247 | return false; 248 | } 249 | }; 250 | 251 | /** 252 | * @description: 让系统卡死一段时间 253 | * @param {*} duration 254 | * @return {*} 255 | */ 256 | export const sleep = (duration = 1000) => { 257 | const oldTime = +new Date(); 258 | // eslint-disable-next-line 259 | for (; +new Date() - oldTime < duration;) { } 260 | }; 261 | 262 | /** 263 | * @description: 按屏幕375为基准,生成对应的px值,默认返回单位(px) 264 | * @param {number} val 265 | * @param {*} flag 266 | * @return {*} 267 | */ 268 | export const pxToDesignPx = (val: number, flag = true) => { 269 | // window.screen.availWidth,值是固定的,怎么跳转浏览器大小,值都是屏幕的大小 270 | // window.document.documentElement.clientWidth,值是不定的,根据文档宽度决定 271 | // window.screen和window.document兼容性一致,兼容ie6及以上,不兼容安卓4.3及以下,其余基本没有兼容性问题。 272 | const px = window.document.documentElement.clientWidth * (val / 375); 273 | return flag ? `${px}px` : px; 274 | }; 275 | 276 | /** 277 | * @description: 按屏幕375为基准,生成对应的vw值,默认返回单位(vw) 278 | * @param {number} val 279 | * @param {*} flag 280 | * @return {*} 281 | */ 282 | export const pxToDesignVw = (val: number, flag = true) => { 283 | const vw = ((val / 375) * 100).toFixed(5); 284 | return flag ? `${vw}vw` : vw; 285 | }; 286 | 287 | /** 288 | * @description: 删除对象中值为: null, undefined, NaN, ''的属性 289 | * @param {any} obj 290 | * @return {*} 291 | */ 292 | export const deleteUseLessObjectKey = (obj: any) => { 293 | Object.keys(obj).forEach((key) => { 294 | if ([null, undefined, NaN, ''].includes(obj[key])) { 295 | delete obj[key]; 296 | } 297 | }); 298 | return obj; 299 | }; 300 | 301 | /** 302 | * @description: 替换占位符 303 | * @example: replaceKeyFromValue('Hello {name}',{name:'Word'}) => Hello Word 304 | * @param {string} str 305 | * @param {object} obj 306 | * @return {*} 307 | */ 308 | export const replaceKeyFromValue = (str: string, obj: Record) => { 309 | let res = str; 310 | Object.keys(obj).forEach((v) => { 311 | res = res.replace(new RegExp(`{${v}}`, 'ig'), obj[v]); 312 | }); 313 | return res; 314 | }; 315 | 316 | /** 317 | * @description: 判断数据类型 318 | * @return {*} 319 | */ 320 | export const judgeType = ( 321 | obj: any 322 | ): 323 | | 'boolean' 324 | | 'number' 325 | | 'string' 326 | | 'function' 327 | | 'array' 328 | | 'date' 329 | | 'regExp' 330 | | 'undefined' 331 | | 'null' 332 | | 'object' => { 333 | const map = { 334 | '[object Boolean]': 'boolean', 335 | '[object Number]': 'number', 336 | '[object String]': 'string', 337 | '[object Function]': 'function', 338 | '[object Array]': 'array', 339 | '[object Date]': 'date', 340 | '[object RegExp]': 'regExp', 341 | '[object Undefined]': 'undefined', 342 | '[object Null]': 'null', 343 | '[object Object]': 'object', 344 | }; 345 | return map[Object.prototype.toString.call(obj)]; 346 | }; 347 | 348 | /** 349 | * @description: myName或者MyName转化为my-name 350 | * @copy https://github.com/vueComponent/ant-design-vue/blob/HEAD/antd-tools/generator-types/src/utils.ts 351 | * @param {string} input 352 | * @return {*} 353 | */ 354 | export const toKebabCase = (input: string): string => 355 | input.replace( 356 | /[A-Z]/g, 357 | (val, index) => (index === 0 ? '' : '-') + val.toLowerCase() 358 | ); 359 | 360 | /** 361 | * @description: myName或者MyName转化为my_name 362 | * @param {string} input 363 | * @return {*} 364 | */ 365 | export const toKebabCase2 = (input: string) => 366 | input.replace( 367 | /[A-Z]/g, 368 | (val, index) => (index === 0 ? '' : '_') + val.toLowerCase() 369 | ); 370 | 371 | /** 372 | * @description: my-name转化为myName 373 | * @param {string} input 374 | * @return {*} 375 | */ 376 | export const toCamelCased = (input: string) => 377 | input.replace(/-(\w)/g, function (all, letter) { 378 | return letter.toUpperCase(); 379 | }); 380 | 381 | /** 382 | * @description: my_name转化为myName 383 | * @param {string} input 384 | * @return {*} 385 | */ 386 | export const toCamelCased2 = (input: string) => 387 | input.replace(/_(\w)/g, function (all, letter) { 388 | return letter.toUpperCase(); 389 | }); 390 | 391 | /** 392 | * @description: my-name转化为MyName 393 | * @param {string} input 394 | * @return {*} 395 | */ 396 | export const toPascalCase = (input: string) => { 397 | const res = input.replace(input[0], input[0].toUpperCase()); 398 | return res.replace(/-(\w)/g, function (all, letter) { 399 | return letter.toUpperCase(); 400 | }); 401 | }; 402 | 403 | /** 404 | * @description: my_name转化为MyName 405 | * @param {string} input 406 | * @return {*} 407 | */ 408 | export const toPascalCase2 = (input: string) => { 409 | const res = input.replace(input[0], input[0].toUpperCase()); 410 | return res.replace(/_(\w)/g, function (all, letter) { 411 | return letter.toUpperCase(); 412 | }); 413 | }; 414 | 415 | /** 416 | * @description: 使用json进行深克隆 417 | * @param {*} obj 418 | * @return {*} 419 | */ 420 | export const deepCloneByJson = (obj: T): T => 421 | JSON.parse(JSON.stringify(obj)); 422 | 423 | /** 424 | * @description: 手写深拷贝,解决循环引用 425 | * @param {*} object 426 | * @return {*} 427 | */ 428 | export const deepClone = (object: T): T => { 429 | function clone(obj: any, hash: any) { 430 | const newobj: any = Array.isArray(obj) ? [] : {}; 431 | // eslint-disable-next-line 432 | hash = hash || new WeakMap(); 433 | if (hash.has(obj)) { 434 | return hash.get(obj); 435 | } 436 | hash.set(obj, newobj); 437 | 438 | Object.keys(obj).forEach((i) => { 439 | if (obj[i] instanceof Object) { 440 | newobj[i] = clone(obj[i], hash); 441 | } else { 442 | newobj[i] = obj[i]; 443 | } 444 | }); 445 | return newobj; 446 | } 447 | return clone(object, undefined); 448 | }; 449 | 450 | /** 451 | * @description: 防抖函数(Promise) 452 | * @param {Function} fn 函数 453 | * @param {number} delay 延迟时间 454 | * @param {boolean} leading 首次立即执行 455 | * @return {Promise} 456 | */ 457 | export const debounce = (fn: any, delay: number, leading = false) => { 458 | let timer; 459 | const debounceFn = function (...args) { 460 | if (timer) { 461 | clearTimeout(timer); 462 | } 463 | return new Promise((resolve) => { 464 | if (leading) { 465 | let isFirst = false; 466 | if (!timer) { 467 | // @ts-ignore 468 | resolve(fn.apply(this, args)); 469 | isFirst = true; 470 | } 471 | timer = setTimeout(() => { 472 | timer = null; 473 | if (!isFirst) { 474 | // @ts-ignore 475 | resolve(fn.apply(this, args)); 476 | } 477 | }, delay); 478 | } else { 479 | timer = setTimeout(() => { 480 | // @ts-ignore 481 | resolve(fn.apply(this, args)); 482 | }, delay); 483 | } 484 | }); 485 | }; 486 | 487 | debounceFn.cancel = function () { 488 | clearTimeout(timer); 489 | timer = null; 490 | }; 491 | return debounceFn; 492 | }; 493 | 494 | /** 495 | * @description: 节流函数(Promise) 496 | * @param {Function} fn 函数 497 | * @param {number} interval 间隔 498 | * @param {boolean} trailing 最后一次执行 499 | * @return {Promise} 500 | */ 501 | export const throttle = (fn: any, interval: number, trailing = false) => { 502 | let lastTime = 0; 503 | let timer; 504 | return function (...args) { 505 | const newTime = new Date().getTime(); 506 | if (timer) { 507 | clearTimeout(timer); 508 | } 509 | 510 | let result; 511 | return new Promise((resolve) => { 512 | if (newTime - lastTime > interval) { 513 | // @ts-ignore 514 | result = fn.apply(this, args); 515 | resolve(result); 516 | lastTime = newTime; 517 | } else if (trailing) { 518 | timer = setTimeout(() => { 519 | // @ts-ignore 520 | result = fn.apply(this, args); 521 | resolve(result); 522 | }, interval); 523 | } 524 | }); 525 | }; 526 | }; 527 | 528 | /** 529 | * @description: 生成uuid(16位) 530 | * @example: generate() ===> 9d24f135-3e33-46b7-b51f-dc5b8121d60a 531 | * @return {*} 532 | */ 533 | export const generateUuid = () => { 534 | const uuid = URL.createObjectURL(new Blob()); // blob:null/9d24f135-3e33-46b7-b51f-dc5b8121d60a 535 | URL.revokeObjectURL(uuid); // 在使用完对象 URL 后调用此方法,让浏览器知道不再保留对该文件的引用。 536 | return uuid.split('/')[1].length; 537 | }; 538 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const debugLog = (type: 'log' | 'warn' | 'error', ...data) => { 2 | console[type]('bi-utils', ...data); 3 | }; 4 | -------------------------------------------------------------------------------- /test/computeBox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 21 | 22 | 23 | 24 |
25 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 获取随机整数 3 | * @example: getRandomInt(4) ===> 3251 4 | */ 5 | const getRandomInt = (length) => { 6 | if (length > 16 || length < 1) throw new Error('length的范围:[1,16]'); 7 | let num = +`${Math.random()}`.slice(2, 2 + length); 8 | if (String(num).length !== length) { 9 | num = getRandomInt(length); 10 | } 11 | console.log(num); 12 | return num; 13 | }; 14 | 15 | const getRandomOne = (arr) => arr[Math.floor(Math.random() * arr.length)]; 16 | 17 | const getRangeRandom = (min, max) => 18 | Math.floor(Math.random() * (max - min + 1)) + min; 19 | 20 | const getFileExt = (filename) => { 21 | const arr = filename.split('.'); 22 | const ext = arr[arr.length - 1]; 23 | return ext; 24 | }; 25 | 26 | const generateStyle = (styleObj) => { 27 | // const styleEle = document.createElement('style'); 28 | // styleEle.type = 'text/css'; 29 | let textContent = ''; 30 | function getStyleVal(obj) { 31 | let str = ''; 32 | Object.keys(obj).forEach((key) => { 33 | // eslint-disable-next-line 34 | str += `${key}:${obj[key]};`; 35 | }); 36 | return str; 37 | } 38 | Object.keys(styleObj).forEach((key) => { 39 | console.log(key, 333, getStyleVal(styleObj[key])); 40 | textContent += `${key}{`; 41 | textContent += getStyleVal(styleObj[key]); 42 | textContent += '}'; 43 | }); 44 | console.log(textContent, 222); 45 | // styleEle.textContent = textContent; 46 | // document.head.appendChild(styleEle); 47 | }; 48 | 49 | const generateUuid = () => { 50 | const tempUrl = URL.createObjectURL(new Blob()); 51 | const uuid = tempUrl.toString(); // blob:null/9d24f135-3e33-46b7-b51f-dc5b8121d60a 52 | URL.revokeObjectURL(tempUrl); 53 | console.log(uuid.split('/')[1]); 54 | return uuid.split('/')[1]; 55 | }; 56 | 57 | // my-name转化为MyName 58 | const toPascalCase = (input) => { 59 | const res = input.replace(input[0], input[0].toUpperCase()); 60 | return res.replace(/-(\w)/g, function (all, letter) { 61 | return letter.toUpperCase(); 62 | }); 63 | }; 64 | 65 | console.log(toPascalCase('bi-utils')); 66 | console.log(generateUuid()); 67 | 68 | // generateStyle({ backgroundImage: `url('sss')` }); 69 | generateStyle({ '.a': { color: 'red' }, '#b': { color: 'blue' } }); 70 | // console.log(getFileExt('aaa/dsaads.as.jpg')); 71 | 72 | // for (let i = 0; i < 100; i++) { 73 | // console.log(getRangeRandom(1, 100)); 74 | // } 75 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", // ESNext,CommonJS 5 | "strict": true, 6 | "noImplicitAny": false, 7 | "lib": ["DOM", "ESNext"], 8 | "jsx": "preserve", 9 | "moduleResolution": "Node", 10 | "esModuleInterop": true, // ES 模块互操作,import React from 'react';react是module.exports导出的,因此需要设置该属性 11 | "forceConsistentCasingInFileNames": true, //在文件名中强制使用一致的大小写 12 | "skipLibCheck": true, // 跳过d.ts声明文件的类型检查。 13 | "baseUrl": "./", 14 | // "sourceMap": true, // 是否生成sourceMap 15 | // "noEmitOnError": false, 16 | // "declaration": true, // 生成.d.ts文件 17 | // "declarationDir": "./types", //生成.d.ts文件的目录 18 | "resolveJsonModule": true, // 解析json模块 19 | "paths": { 20 | "@/*": ["src/*"] 21 | } 22 | // "paths": { 23 | // "@/*": ["./src/*"] // 这样写的话,@/不会提示路径,得使用baseUrl:'./'+paths:{"@/*": ["src/*"]}这样才的话@/才会提示路径 24 | // } 25 | }, 26 | // 命令行执行ts-node的时候的配置 27 | "ts-node": { 28 | "compilerOptions": { 29 | "module": "CommonJS" 30 | } 31 | }, 32 | "exclude": ["doc/**/*", "node_modules/**/*"], // 排除include里的文件 33 | "include": [ 34 | "./**/*.ts", 35 | "./**/*.js", 36 | ".eslintrc.js", 37 | "./rollup.config.ts" //https://rollupjs.org/guide/en/#--configplugin-plugin 38 | ] // 仅仅匹配这些文件 39 | } 40 | -------------------------------------------------------------------------------- /typedoc.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["./src/index.ts"], 3 | "out": "./doc" 4 | } 5 | --------------------------------------------------------------------------------