{"id":20875962,"url":"https://github.com/saqqdy/js-cool","last_synced_at":"2026-03-15T06:19:49.567Z","repository":{"id":57282965,"uuid":"330189979","full_name":"saqqdy/js-cool","owner":"saqqdy","description":"Collection of common JavaScript / TypeScript utilities","archived":false,"fork":false,"pushed_at":"2026-03-14T15:12:23.000Z","size":3998,"stargazers_count":17,"open_issues_count":3,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-15T01:34:12.582Z","etag":null,"topics":["await-to","cache","compareversion","cookies","csv","fingerprint","get-types","js-cool","js-functions","library","localstorage","osversion","random","tool","utilties","uuid"],"latest_commit_sha":null,"homepage":"https://www.saqqdy.com/js-cool","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saqqdy.png","metadata":{"files":{"readme":"README-zh_CN.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["saqqdy"]}},"created_at":"2021-01-16T15:13:16.000Z","updated_at":"2026-03-14T15:12:26.000Z","dependencies_parsed_at":"2024-02-26T13:47:31.267Z","dependency_job_id":"f9841768-fceb-4dd8-b35a-fe4c57ad3bd7","html_url":"https://github.com/saqqdy/js-cool","commit_stats":{"total_commits":161,"total_committers":4,"mean_commits":40.25,"dds":0.2795031055900621,"last_synced_commit":"ccd8e94418231d1250cb1ff4c7eb3e938f2679e4"},"previous_names":[],"tags_count":79,"template":false,"template_full_name":null,"purl":"pkg:github/saqqdy/js-cool","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saqqdy%2Fjs-cool","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saqqdy%2Fjs-cool/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saqqdy%2Fjs-cool/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saqqdy%2Fjs-cool/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saqqdy","download_url":"https://codeload.github.com/saqqdy/js-cool/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saqqdy%2Fjs-cool/sbom","scorecard":{"id":800455,"data":{"date":"2025-08-11","repo":{"name":"github.com/saqqdy/js-cool","commit":"5430bc23dba00cd720f80f0b3b2f5b0daa443e4d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.5,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/22 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/build.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/build.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codacy-analysis.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/codacy-analysis.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/codacy-analysis.yml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/codacy-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codacy-analysis.yml:47: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/codacy-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:42: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:46: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:57: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:71: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/lint.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/lint.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/lint.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/lint.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish-test.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/publish-test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/publish-test.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/publish-test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish-test.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/publish-test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/publish.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/publish.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/publish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/saqqdy/js-cool/publish.yml/master?enable=pin","Info:   0 out of  13 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   5 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql-analysis.yml:28","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql-analysis.yml:29","Warn: jobLevel 'packages' permission set to 'write': .github/workflows/publish-test.yml:11","Info: jobLevel 'contents' permission set to 'read': .github/workflows/publish-test.yml:12","Warn: jobLevel 'packages' permission set to 'write': .github/workflows/publish.yml:11","Info: jobLevel 'contents' permission set to 'read': .github/workflows/publish.yml:12","Warn: no topLevel permission defined: .github/workflows/build.yml:1","Warn: no topLevel permission defined: .github/workflows/codacy-analysis.yml:1","Warn: no topLevel permission defined: .github/workflows/codeql-analysis.yml:1","Warn: no topLevel permission defined: .github/workflows/lint.yml:1","Warn: no topLevel permission defined: .github/workflows/publish-test.yml:1","Warn: no topLevel permission defined: .github/workflows/publish.yml:1"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":8,"reason":"SAST tool detected but not run on all commits","details":["Info: SAST configuration detected: CodeQL","Warn: 4 commits out of 8 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":5,"reason":"5 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-67mh-4wv8-2f99","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T10:19:28.737Z","repository_id":57282965,"created_at":"2025-08-23T10:19:28.737Z","updated_at":"2025-08-23T10:19:28.737Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30529364,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-15T00:42:01.247Z","status":"online","status_checked_at":"2026-03-15T02:00:07.485Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["await-to","cache","compareversion","cookies","csv","fingerprint","get-types","js-cool","js-functions","library","localstorage","osversion","random","tool","utilties","uuid"],"created_at":"2024-11-18T06:49:19.740Z","updated_at":"2026-03-15T06:19:49.561Z","avatar_url":"https://github.com/saqqdy.png","language":"TypeScript","funding_links":["https://github.com/sponsors/saqqdy"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# js-cool\n\n**JavaScript / TypeScript 常用工具函数库**\n\n[![NPM version](https://img.shields.io/npm/v/js-cool.svg?style=flat-square)](https://npmjs.org/package/js-cool)\n[![npm download](https://img.shields.io/npm/dm/js-cool.svg?style=flat-square)](https://npmjs.org/package/js-cool)\n[![Test coverage](https://img.shields.io/codecov/c/github/saqqdy/js-cool.svg?style=flat-square)](https://codecov.io/github/saqqdy/js-cool)\n![typescript](https://badgen.net/badge/icon/typescript?icon=typescript\u0026label)\n[![tree shaking](https://badgen.net/bundlephobia/tree-shaking/js-cool)](https://bundlephobia.com/package/js-cool)\n[![gzip](http://img.badgesize.io/https://unpkg.com/js-cool/dist/index.global.prod.js?compression=gzip\u0026label=gzip%20size:%20JS)](http://img.badgesize.io/https://unpkg.com/js-cool/dist/index.global.prod.js?compression=gzip\u0026label=gzip%20size:%20JS)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n**[文档](https://www.saqqdy.com/js-cool)** • **[更新日志](./CHANGELOG.md)** • **[English](./README.md)**\n\n\u003c/div\u003e\n\n---\n\n## 安装\n\n```bash\n# pnpm\npnpm add js-cool\n\n# npm\nnpm install js-cool\n\n# yarn\nyarn add js-cool\n```\n\n## 使用\n\n```js\n// ES Module\nimport { osVersion, copy, randomString } from 'js-cool'\n\n// Node.js CommonJS\nconst { osVersion, copy, randomString } = require('js-cool')\n\n// CDN (浏览器)\n\u003cscript src=\"https://unpkg.com/js-cool/dist/index.global.prod.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  jsCool.browserVersion()\n\u003c/script\u003e\n\n// 直接导入 (支持 tree-shaking)\nimport copy from 'js-cool/copy'\nimport { randomString } from 'js-cool'\n```\n\n---\n\n## API 参考\n\n### 全局\n\n#### client\n\n浏览器检测工具。\n\n```js\nimport { client } from 'js-cool'\n\n// 获取所有浏览器信息\nclient.get(['device', 'browser', 'engine', 'os'])\n// { device: 'Mobile', browser: 'Chrome', os: 'Android', engine: 'Blink' }\n\n// 获取单个信息\nclient.get('browser') // { browser: 'Chrome' }\nclient.get('device') // { device: 'Mobile' }\nclient.get('os') // { os: 'Android' }\nclient.get('engine') // { engine: 'Blink' }\n\n// 获取多个信息\nclient.get(['browser', 'os'])\n// { browser: 'Chrome', os: 'Android' }\n\n// 获取语言\nclient.getLanguage() // 'zh-CN'\n\n// 获取网络信息\nclient.getNetwork() // { effectiveType: '4g', downlink: 10 }\n\n// 获取屏幕方向\nclient.getOrientationStatus() // 'vertical' | 'horizontal'\n```\n\n#### pattern\n\n常用正则表达式集合。\n\n```js\nimport { pattern } from 'js-cool'\n\n// 邮箱验证\npattern.email.test('test@example.com') // true\npattern.email.test('invalid-email') // false\n\n// 中国手机号\npattern.mobile.test('13800138000') // true\npattern.mobile.test('12345678901') // false\n\n// URL 验证\npattern.url.test('https://example.com') // true\npattern.url.test('ftp://files.server') // true\n\n// 数字验证\npattern.number.test('12345') // true\npattern.number.test('12.34') // true\n\n// 中文字符\npattern.chinese.test('中文测试') // true\npattern.chinese.test('test123') // false\n\n// QQ 号\npattern.qq.test('123456789') // true\n\n// 固定电话\npattern.tel.test('010-12345678') // true\npattern.tel.test('021-87654321') // true\n\n// 邮编\npattern.postcode.test('100000') // true\n\n// 用户名 (字母数字下划线, 4-16位)\npattern.username.test('user_name') // true\n\n// 密码 (至少6位, 包含字母和数字)\npattern.pass.test('abc123') // true\n\n// JSON 字符串\npattern.json.test('{\"a\":1}') // true\n\n// MAC 地址\npattern.mac.test('00:1A:2B:3C:4D:5E') // true\n\n// IPv4 地址\npattern.ip4.test('192.168.1.1') // true\n\n// 私有 IPv4\npattern.ip4_pri.test('192.168.1.1') // true\npattern.ip4_pri.test('10.0.0.1') // true\npattern.ip4_pri.test('172.16.0.1') // true\n```\n\n---\n\n### 字符串\n\n#### trim\n\n去除字符串首尾空格。\n\n```js\nimport { trim } from 'js-cool'\n\ntrim('  hello  ') // 'hello'\ntrim('\\nhello\\n') // 'hello'\ntrim('\\thello\\t') // 'hello'\ntrim('  hello world  ') // 'hello world'\n```\n\n#### clearAttr\n\n去除 HTML 标签所有属性。\n\n```js\nimport { clearAttr } from 'js-cool'\n\nclearAttr('\u003cdiv id=\"test\" class=\"box\"\u003econtent\u003c/div\u003e')\n// '\u003cdiv\u003econtent\u003c/div\u003e'\n\nclearAttr('\u003ca href=\"url\" target=\"_blank\"\u003elink\u003c/a\u003e')\n// '\u003ca\u003elink\u003c/a\u003e'\n\nclearAttr('\u003cinput type=\"text\" name=\"field\" /\u003e')\n// '\u003cinput /\u003e'\n\nclearAttr('\u003cimg src=\"pic.jpg\" alt=\"image\" /\u003e')\n// '\u003cimg /\u003e'\n```\n\n#### clearHtml\n\n去除 HTML 标签。\n\n```js\nimport { clearHtml } from 'js-cool'\n\nclearHtml('\u003cdiv\u003etest\u003cbr/\u003estring\u003c/div\u003e') // 'teststring'\nclearHtml('\u003cp\u003eHello \u003cb\u003eWorld\u003c/b\u003e\u003c/p\u003e') // 'Hello World'\nclearHtml('\u003ca href=\"#\"\u003elink\u003c/a\u003e') // 'link'\nclearHtml('plain text') // 'plain text'\n```\n\n#### escape / unescape\n\n转义/还原 HTML 特殊字符。\n\n```js\nimport { escape, unescape } from 'js-cool'\n\n// 转义\nescape('\u003cdiv\u003etest\u003c/div\u003e') // '\u0026lt;div\u0026gt;test\u0026lt;/div\u0026gt;'\nescape('a \u003c b \u0026 c \u003e d') // 'a \u0026lt; b \u0026amp; c \u0026gt; d'\nescape('\"hello\" \u0026 \\'world\\'') // '\u0026quot;hello\u0026quot; \u0026amp; \u0026#39;world\u0026#39;'\n\n// 还原\nunescape('\u0026lt;div\u0026gt;test\u0026lt;/div\u0026gt;') // '\u003cdiv\u003etest\u003c/div\u003e'\nunescape('\u0026amp;lt;') // '\u0026lt;'\nunescape('\u0026quot;hello\u0026quot;') // '\"hello\"'\n```\n\n#### getNumber\n\n从字符串中提取数字。\n\n```js\nimport { getNumber } from 'js-cool'\n\ngetNumber('Chrome123.45') // '123.45'\ngetNumber('price: $99.99') // '99.99'\ngetNumber('version 2.0.1') // '2.0.1'\ngetNumber('no numbers here') // ''\ngetNumber('123abc456') // '123456'\ngetNumber('-12.34') // '-12.34'\n```\n\n#### camel2Dash / dash2Camel\n\n驼峰与 kebab-case 互转。\n\n```js\nimport { camel2Dash, dash2Camel } from 'js-cool'\n\n// 驼峰转 kebab-case\ncamel2Dash('jsCool') // 'js-cool'\ncamel2Dash('backgroundColor') // 'background-color'\ncamel2Dash('marginTop') // 'margin-top'\ncamel2Dash('XMLHttpRequest') // 'x-m-l-http-request'\n\n// kebab-case 转驼峰\ndash2Camel('js-cool') // 'jsCool'\ndash2Camel('background-color') // 'backgroundColor'\ndash2Camel('margin-top') // 'marginTop'\ndash2Camel('-webkit-transform') // 'WebkitTransform'\n```\n\n#### upperFirst\n\n首字母大写。\n\n```js\nimport { upperFirst } from 'js-cool'\n\nupperFirst('hello') // 'Hello'\nupperFirst('HELLO') // 'HELLO'\nupperFirst('h') // 'H'\nupperFirst('') // ''\n```\n\n#### randomString\n\n生成随机字符串（多种选项）。\n\n```js\nimport { randomString } from 'js-cool'\n\n// 默认：32位，包含大小写字母和数字\nrandomString() // 'aB3dE7fG9hJ2kL5mN8pQ1rS4tU6vW0xY'\n\n// 指定长度\nrandomString(8) // 'xY7mN2pQ'\nrandomString(16) // 'aB3dE7fG9hJ2kL5m'\n\n// 使用选项对象\nrandomString({ length: 16 })\n// 'kL5mN8pQ1rS4tU6v'\n\n// 纯数字\nrandomString({ length: 6, charTypes: 'number' })\n// '847291'\n\n// 纯小写字母\nrandomString({ length: 8, charTypes: 'lowercase' })\n// 'qwertyui'\n\n// 纯大写字母\nrandomString({ length: 8, charTypes: 'uppercase' })\n// 'ASDFGHJK'\n\n// 多种字符类型\nrandomString({ length: 16, charTypes: ['uppercase', 'number'] })\n// 'A3B7C9D2E5F8G1H4'\n\n// 包含特殊字符\nrandomString({ length: 16, charTypes: ['lowercase', 'number', 'special'] })\n// 'a1@b2#c3$d4%e5^f6'\n\n// 所有字符类型\nrandomString({ length: 20, charTypes: ['uppercase', 'lowercase', 'number', 'special'] })\n// 'A1a@B2b#C3c$D4d%E5e^'\n\n// 排除易混淆字符 (o, O, 0, l, 1, I)\nrandomString({ length: 16, noConfuse: true })\n// 'aB3dE7fG9hJ2kL5m' (不含 o, O, 0, l, 1, I)\n\n// 严格模式：每种字符类型至少包含一个\nrandomString({ length: 16, charTypes: ['uppercase', 'lowercase', 'number'], strict: true })\n// 保证至少包含1个大写、1个小写、1个数字\n\n// 旧版 API（仍支持）\nrandomString(16, true) // 16位包含特殊字符\nrandomString(true) // 32位包含特殊字符\n```\n\n#### getCHSLength\n\n获取字符串长度（中文=2字符）。\n\n```js\nimport { getCHSLength } from 'js-cool'\n\ngetCHSLength('hello') // 5\ngetCHSLength('你好') // 4\ngetCHSLength('hello世界') // 9 (5 + 4)\ngetCHSLength('测试Test') // 8 (4 + 4)\ngetCHSLength('') // 0\n```\n\n#### cutCHSString\n\n截取字符串（中文=2字节）。\n\n```js\nimport { cutCHSString } from 'js-cool'\n\ncutCHSString('hello世界', 6) // 'hello世'\ncutCHSString('hello世界', 6, true) // 'hello世...'\ncutCHSString('测试字符串', 4) // '测试'\ncutCHSString('测试字符串', 4, true) // '测试...'\ncutCHSString('abc', 10) // 'abc'\ncutCHSString('abc', 10, true) // 'abc'\n```\n\n---\n\n### 数组\n\n#### shuffle\n\n打乱数组或字符串。\n\n```js\nimport { shuffle } from 'js-cool'\n\n// 打乱数组\nshuffle([1, 2, 3, 4, 5]) // [3, 1, 5, 2, 4]\nshuffle(['a', 'b', 'c']) // ['c', 'a', 'b']\n\n// 打乱字符串\nshuffle('hello') // 'olleh'\nshuffle('abcdefg') // 'gfedcba'\n\n// 限制数量\nshuffle([1, 2, 3, 4, 5], 3) // [4, 1, 5] (随机取3个)\nshuffle('hello', 3) // 'leh' (随机取3个字符)\n```\n\n#### unique\n\n数组去重。\n\n```js\nimport { unique } from 'js-cool'\n\nunique([1, 2, 2, 3, 3, 3]) // [1, 2, 3]\nunique(['a', 'b', 'a', 'c']) // ['a', 'b', 'c']\nunique([1, '1', 1]) // [1, '1']\nunique([true, false, true]) // [true, false]\nunique([null, null, undefined]) // [null, undefined]\n```\n\n#### intersect\n\n多个数组求交集。\n\n```js\nimport { intersect } from 'js-cool'\n\nintersect([1, 2, 3], [2, 3, 4]) // [2, 3]\nintersect([1, 2, 3], [2, 3, 4], [3, 4, 5]) // [3]\nintersect(['a', 'b'], ['b', 'c']) // ['b']\nintersect([1, 2], [3, 4]) // []\n```\n\n#### union\n\n多个数组求并集。\n\n```js\nimport { union } from 'js-cool'\n\nunion([1, 2], [3, 4]) // [1, 2, 3, 4]\nunion([1, 2], [2, 3]) // [1, 2, 3]\nunion([1, 2], [2, 3], [3, 4]) // [1, 2, 3, 4]\nunion(['a'], ['b'], ['c']) // ['a', 'b', 'c']\n```\n\n#### minus\n\n多个数组求差集（第一个数组有，其他数组没有的元素）。\n\n```js\nimport { minus } from 'js-cool'\n\nminus([1, 2, 3], [2, 3, 4]) // [1]\nminus([1, 2, 3, 4], [2, 3]) // [1, 4]\nminus([1, 2, 3], [2], [3]) // [1]\nminus(['a', 'b', 'c'], ['b']) // ['a', 'c']\n```\n\n#### complement\n\n多个数组求补集（不在所有数组交集中的元素）。\n\n```js\nimport { complement } from 'js-cool'\n\ncomplement([1, 2], [2, 3]) // [1, 3]\ncomplement([1, 2], [3, 4]) // [1, 2, 3, 4]\ncomplement(['a', 'b'], ['b', 'c']) // ['a', 'c']\n```\n\n#### contains\n\n判断数组是否包含元素。\n\n```js\nimport { contains } from 'js-cool'\n\ncontains([1, 2, 3], 2) // true\ncontains([1, 2, 3], 4) // false\ncontains(['a', 'b'], 'a') // true\ncontains([null], null) // true\ncontains([NaN], NaN) // true\n```\n\n#### all / any\n\n判断数组元素是否满足条件。\n\n```js\nimport { all, any } from 'js-cool'\n\n// all - 所有元素都满足\nall([1, 2, 3], x =\u003e x \u003e 0) // true\nall([1, 2, 3], x =\u003e x \u003e 1) // false\nall(['a', 'b'], x =\u003e x.length === 1) // true\nall([], x =\u003e x \u003e 0) // true (空数组)\n\n// any - 任一元素满足\nany([1, 2, 3], x =\u003e x \u003e 2) // true\nany([1, 2, 3], x =\u003e x \u003e 10) // false\nany(['hello', 'world'], x =\u003e x.includes('o')) // true\nany([], x =\u003e x \u003e 0) // false (空数组)\n```\n\n---\n\n### 对象\n\n#### extend\n\n深度合并对象。\n\n```js\nimport { extend } from 'js-cool'\n\n// 浅拷贝\nconst result1 = extend({}, { a: 1 }, { b: 2 })\n// { a: 1, b: 2 }\n\n// 深度合并\nconst result2 = extend(true, {}, { a: { b: 1 } }, { a: { c: 2 } })\n// { a: { b: 1, c: 2 } }\n\n// 覆盖值\nconst result3 = extend(true, {}, { a: 1, b: 2 }, { b: 3 })\n// { a: 1, b: 3 }\n\n// 合并数组\nconst result4 = extend(true, {}, { arr: [1, 2] }, { arr: [3] })\n// { arr: [3, 2] }\n\n// 多个源对象\nconst result5 = extend(true, {}, { a: 1 }, { b: 2 }, { c: 3 })\n// { a: 1, b: 2, c: 3 }\n```\n\n#### clone\n\n深拷贝对象。\n\n```js\nimport { clone } from 'js-cool'\n\nconst obj = { a: { b: 1, c: [1, 2, 3] } }\nconst cloned = clone(obj)\n\ncloned.a.b = 2\ncloned.a.c.push(4)\n\nobj.a.b // 仍然是 1\nobj.a.c // 仍然是 [1, 2, 3]\n\n// 克隆数组\nconst arr = [{ a: 1 }, { b: 2 }]\nconst clonedArr = clone(arr)\n\n// 克隆日期\nconst date = new Date()\nconst clonedDate = clone(date)\n\n// 克隆正则\nconst regex = /test/gi\nconst clonedRegex = clone(regex)\n```\n\n#### isEqual\n\n深度比较相等。\n\n```js\nimport { isEqual } from 'js-cool'\n\nisEqual({ a: 1 }, { a: 1 }) // true\nisEqual({ a: 1, b: 2 }, { b: 2, a: 1 }) // true (顺序无关)\nisEqual([1, 2, 3], [1, 2, 3]) // true\nisEqual(NaN, NaN) // true\nisEqual(null, null) // true\nisEqual(undefined, undefined) // true\n\nisEqual({ a: 1 }, { a: 2 }) // false\nisEqual([1, 2], [2, 1]) // false (顺序有关)\nisEqual({ a: 1 }, { a: 1, b: 2 }) // false\n\n// 深度比较\nisEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } }) // true\n```\n\n#### getProperty / setProperty\n\n根据路径获取/设置属性值。\n\n```js\nimport { getProperty, setProperty } from 'js-cool'\n\nconst obj = {\n  a: {\n    b: [{ c: 1 }, { c: 2 }],\n    d: { e: 'hello' }\n  }\n}\n\n// 获取属性\ngetProperty(obj, 'a.b.0.c') // 1\ngetProperty(obj, 'a.b.1.c') // 2\ngetProperty(obj, 'a.d.e') // 'hello'\ngetProperty(obj, 'a.b') // [{ c: 1 }, { c: 2 }]\n\n// 带默认值\ngetProperty(obj, 'a.b.5.c', 'default') // 'default'\ngetProperty(obj, 'x.y.z', null) // null\n\n// 设置属性\nsetProperty(obj, 'a.b.0.c', 100)\n// obj.a.b[0].c === 100\n\nsetProperty(obj, 'a.d.e', 'world')\n// obj.a.d.e === 'world'\n\nsetProperty(obj, 'a.new', 'value')\n// obj.a.new === 'value'\n```\n\n#### searchObject\n\n对象树深度查找。\n\n```js\nimport { searchObject } from 'js-cool'\n\nconst tree = {\n  id: 1,\n  name: 'root',\n  children: [\n    { id: 2, name: 'child1', children: [] },\n    { id: 3, name: 'child2', children: [{ id: 4, name: 'grandchild' }] }\n  ]\n}\n\n// 按条件查找\nsearchObject(tree, item =\u003e item.id === 3, { children: 'children' })\n// [{ id: 3, name: 'child2', ... }]\n\n// 按名称查找\nsearchObject(tree, item =\u003e item.name.includes('child'), { children: 'children' })\n// [{ id: 2, ... }, { id: 3, ... }, { id: 4, ... }]\n\n// 自定义键配置\nsearchObject(tree, item =\u003e item.id \u003e 2, {\n  children: 'children',\n  id: 'id'\n})\n```\n\n---\n\n### 类型判断\n\n```js\nimport {\n  isArray,\n  isDate,\n  isDigitals,\n  isIterable,\n  isObject,\n  isPlainObject,\n  isRegExp,\n  isWindow\n} from 'js-cool'\n\n// isArray\nisArray([1, 2, 3]) // true\nisArray('array') // false\nisArray(null) // false\n\n// isObject\nisObject({}) // true\nisObject([]) // true (数组也是对象)\nisObject(null) // false\nisObject(function () {}) // true\n\n// isPlainObject\nisPlainObject({}) // true\nisPlainObject(Object.create(null)) // true\nisPlainObject([]) // false\nisPlainObject(new Date()) // false\n\n// isDate\nisDate(new Date()) // true\nisDate('2024-01-01') // false\nisDate(1234567890000) // false\n\n// isRegExp\nisRegExp(/test/) // true\nisRegExp(new RegExp('test')) // true\nisRegExp('/test/') // false\n\n// isWindow\nisWindow(window) // true (浏览器中)\nisWindow({}) // false\n\n// isIterable\nisIterable([1, 2, 3]) // true\nisIterable('string') // true\nisIterable(new Set()) // true\nisIterable(new Map()) // true\nisIterable({}) // false\nisIterable(null) // false\n\n// isDigitals\nisDigitals('12345') // true\nisDigitals('12.34') // true\nisDigitals('-123') // true\nisDigitals('12a34') // false\n```\n\n---\n\n### 环境检测\n\n```js\nimport { inBrowser, inNodeJs, isDarkMode, windowSize } from 'js-cool'\n\n// inBrowser - 判断是否在浏览器环境\ninBrowser() // 浏览器中返回 true，Node.js 中返回 false\n\n// inNodeJs - 判断是否在 Node.js 环境\ninNodeJs() // Node.js 中返回 true，浏览器中返回 false\n\n// isDarkMode - 判断是否为暗色模式\nisDarkMode() // 暗色模式返回 true\n\n// windowSize - 获取窗口尺寸\nwindowSize() // { width: 1920, height: 1080 }\nwindowSize() // { width: 375, height: 667 } (移动端)\n```\n\n---\n\n### 浏览器检测\n\n```js\nimport { appVersion, browserVersion, fingerprint, isNumberBrowser, osVersion } from 'js-cool'\n\n// appVersion - 从 UA 获取 APP 版本\nappVersion('Chrome') // '123.0.0.0'\nappVersion('Safari') // '17.0'\nappVersion('Firefox') // '123.0'\nappVersion('MicroMessenger') // '8.0' (微信)\n\n// 使用自定义 UA\nappVersion('Chrome', 'Mozilla/5.0 Chrome/100.0.0.0')\n// '100.0.0.0'\n\n// osVersion - 获取操作系统名称和版本\nosVersion()\n// { name: 'Windows', version: '10.0' }\n// { name: 'Mac OS', version: '10.15.7' }\n// { name: 'Android', version: '13' }\n// { name: 'iOS', version: '17.0' }\n// { name: 'Harmony', version: '4.0' }\n\n// browserVersion - 获取浏览器名称和版本\nbrowserVersion()\n// { name: 'Chrome', version: '123.0.0.0' }\n// { name: 'Safari', version: '17.0' }\n// { name: 'Firefox', version: '123.0' }\n// { name: 'Edge', version: '123.0.0.0' }\n\n// isNumberBrowser - 判断是否为 360 浏览器\nisNumberBrowser() // 360 浏览器返回 true\n\n// fingerprint - 生成浏览器指纹\nfingerprint() // 'wc7sWJJA8'\nfingerprint('example.com') // 'xK9mN2pL5' (自定义域名)\n```\n\n---\n\n### URL \u0026 参数\n\n#### compareVersion\n\n比较版本号。\n\n```js\nimport { compareVersion } from 'js-cool'\n\ncompareVersion('1.2.3', '1.2.2') // 1 (大于)\ncompareVersion('1.2.3', '1.2.3') // 0 (等于)\ncompareVersion('1.2.3', '1.2.4') // -1 (小于)\ncompareVersion('2.0.0', '1.9.9') // 1\ncompareVersion('1.10.0', '1.9.0') // 1\n\n// 预发布标签 (优先级: rc \u003e beta \u003e alpha)\ncompareVersion('1.0.0-rc.1', '1.0.0-beta.1') // 1\ncompareVersion('1.0.0-beta.1', '1.0.0-alpha.1') // 1\ncompareVersion('1.0.0-alpha.1', '1.0.0') // -1\n\n// 实际应用\nconst needsUpdate = compareVersion(currentVersion, minVersion) \u003c 0\n```\n\n#### parseUrlParam\n\n解析 URL 参数字符串。\n\n```js\nimport { parseUrlParam } from 'js-cool'\n\n// 基本解析\nparseUrlParam('a=1\u0026b=2\u0026c=3')\n// { a: '1', b: '2', c: '3' }\n\n// 自动类型转换\nparseUrlParam('a=1\u0026b=2\u0026c=true', true)\n// { a: 1, b: 2, c: true }\n\n// 复杂值\nparseUrlParam('name=hello%20world\u0026list=1,2,3')\n// { name: 'hello world', list: '1,2,3' }\n\n// 空字符串\nparseUrlParam('') // {}\n\n// 特殊值 (即使传 true 也不转换)\nparseUrlParam('hex=0xFF\u0026bin=0b111\u0026oct=0o77\u0026exp=1e3', true)\n// { hex: '0xFF', bin: '0b111', oct: '0o77', exp: '1e3' }\n```\n\n#### spliceUrlParam\n\n构建 URL 参数字符串。\n\n```js\nimport { spliceUrlParam } from 'js-cool'\n\nspliceUrlParam({ a: 1, b: 2 })\n// 'a=1\u0026b=2'\n\nspliceUrlParam({ name: 'hello world' })\n// 'name=hello%20world'\n\nspliceUrlParam({ a: 1, b: null, c: undefined })\n// 'a=1' (null 和 undefined 被跳过)\n\n// 带选项\nspliceUrlParam({ a: 1, b: 2 }, { encode: true })\n// 'a=1\u0026b=2'\n\nspliceUrlParam({ a: 1, b: 2 }, { encode: false })\n// 'a=1\u0026b=2'\n\n// 复杂值\nspliceUrlParam({ arr: [1, 2, 3] })\n// 'arr=1,2,3'\n```\n\n#### getUrlParam / getUrlParams\n\n获取 URL 参数 (location.search, # 之前)。\n\n```js\nimport { getUrlParam, getUrlParams } from 'js-cool'\n\n// 获取单个参数\ngetUrlParam('a', '?a=1\u0026b=2') // '1'\ngetUrlParam('b', '?a=1\u0026b=2') // '2'\ngetUrlParam('c', '?a=1\u0026b=2') // null\n\n// 获取所有参数\ngetUrlParams('?a=1\u0026b=2\u0026c=3')\n// { a: '1', b: '2', c: '3' }\n\n// 自动类型转换\ngetUrlParams('?a=1\u0026b=2', true)\n// { a: 1, b: 2 }\n\n// 从完整 URL 解析\ngetUrlParams('https://example.com?foo=bar')\n// { foo: 'bar' }\n```\n\n#### getQueryParam / getQueryParams\n\n获取 query 参数 (# 之后)。\n\n```js\nimport { getQueryParam, getQueryParams } from 'js-cool'\n\n// 获取单个 query 参数\ngetQueryParam('a', '#/?a=1\u0026b=2') // '1'\ngetQueryParam('b', 'https://example.com#/page?a=1\u0026b=2') // '1'\n\n// 获取所有 query 参数\ngetQueryParams('#/?a=1\u0026b=2')\n// { a: '1', b: '2' }\n\n// 自动类型转换\ngetQueryParams('#/?a=1\u0026b=true', true)\n// { a: 1, b: true }\n```\n\n#### getDirParam\n\n获取目录形式 URL 参数。\n\n```js\nimport { getDirParam } from 'js-cool'\n\ngetDirParam('https://example.com/a/b/c')\n// { 0: 'a', 1: 'b', 2: 'c' }\n\ngetDirParam('/user/123/profile')\n// { 0: 'user', 1: '123', 2: 'profile' }\n```\n\n---\n\n### 存储\n\n#### localStorage (getCache / setCache / delCache)\n\n```js\nimport { delCache, getCache, setCache } from 'js-cool'\n\n// 存储字符串\nsetCache('name', 'value')\n\n// 存储对象 (自动 JSON 序列化)\nsetCache('user', { id: 1, name: 'John' })\ngetCache('user') // { id: 1, name: 'John' }\n\n// 存储数字\nsetCache('count', 100)\ngetCache('count') // 100\n\n// 存储布尔值\nsetCache('flag', true)\ngetCache('flag') // true\n\n// 设置过期时间 (秒)\nsetCache('session', 'data', 3600) // 1小时后过期\nsetCache('token', 'abc123', 86400) // 24小时后过期\n\n// 获取不存在的 key\ngetCache('nonexistent') // null\n\n// 删除\ndelCache('name')\ndelCache('user')\n```\n\n#### sessionStorage (getSession / setSession / delSession)\n\n```js\nimport { delSession, getSession, setSession } from 'js-cool'\n\nsetSession('temp', 'data')\ngetSession('temp') // 'data'\n\nsetSession('list', [1, 2, 3])\ngetSession('list') // [1, 2, 3]\n\nsetSession('config', { theme: 'dark' }, 1800) // 30分钟后过期\ngetSession('config') // { theme: 'dark' }\n\ndelSession('temp')\n```\n\n#### Cookie (getCookie / getCookies / setCookie / delCookie)\n\n```js\nimport { delCookie, getCookie, getCookies, setCookie } from 'js-cool'\n\n// 设置 cookie (默认: 1天)\nsetCookie('name', 'value')\n\n// 带选项设置\nsetCookie('name', 'value', { days: 7 })\nsetCookie('name', 'value', { days: 30, path: '/' })\nsetCookie('name', 'value', {\n  days: 7,\n  path: '/',\n  domain: '.example.com',\n  secure: true,\n  sameSite: 'Strict'\n})\n\n// 获取单个 cookie\ngetCookie('name') // 'value'\ngetCookie('nonexistent') // null\n\n// 获取所有 cookie\ngetCookies() // { name: 'value', other: 'data' }\n\n// 删除 cookie\ndelCookie('name')\ndelCookie('name', { path: '/', domain: '.example.com' })\n```\n\n---\n\n### 编码\n\n#### Base64\n\n```js\nimport { decodeBase64, encodeBase64 } from 'js-cool'\n\n// 编码\nencodeBase64('hello') // 'aGVsbG8='\nencodeBase64('你好') // '5L2g5aW9'\nencodeBase64('{\"a\":1}') // 'eyJhIjoxfQ=='\nencodeBase64(12345) // 'MTIzNDU='\n\n// 解码\ndecodeBase64('aGVsbG8=') // 'hello'\ndecodeBase64('5L2g5aW9') // '你好'\ndecodeBase64('eyJhIjoxfQ==') // '{\"a\":1}'\n```\n\n#### UTF-8\n\n```js\nimport { decodeUtf8, encodeUtf8 } from 'js-cool'\n\nencodeUtf8('hello') // 编码后的字符串\nencodeUtf8('你好') // 编码后的字符串\ndecodeUtf8(encoded) // 原始字符串\n```\n\n#### 安全 JSON\n\n```js\nimport { safeParse, safeStringify } from 'js-cool'\n\n// safeParse - 永不抛出错误\nsafeParse('{\"a\":1}') // { a: 1 }\nsafeParse('invalid json') // null (不报错!)\nsafeParse('{\"a\":BigInt(1)}') // { a: BigInt(1) }\nsafeParse(null) // null\nsafeParse(undefined) // null\n\n// safeStringify - 支持 BigInt、循环引用\nsafeStringify({ a: 1 }) // '{\"a\":1}'\nsafeStringify({ a: BigInt(9007199254740993n) }) // 支持 BigInt\nsafeStringify({ a: () =\u003e {} }) // '{\"a\":null}'\n```\n\n---\n\n### 事件\n\n```js\nimport { addEvent, removeEvent, stopBubble, stopDefault } from 'js-cool'\n\n// 阻止冒泡\ndocument.getElementById('btn').addEventListener('click', e =\u003e {\n  stopBubble(e) // e.stopPropagation()\n})\n\n// 阻止默认行为\ndocument.getElementById('link').addEventListener('click', e =\u003e {\n  stopDefault(e) // e.preventDefault()\n})\n\n// 事件委托\nconst handler = e =\u003e {\n  console.log('clicked:', e.target)\n}\n\n// 添加委托事件\naddEvent(document, 'click', handler)\n\n// 移除委托事件\nremoveEvent(document, 'click', handler)\n\n// 委托到特定容器\naddEvent(document.getElementById('list'), 'click', e =\u003e {\n  if (e.target.tagName === 'LI') {\n    console.log('列表项被点击')\n  }\n})\n```\n\n---\n\n### 工具\n\n#### uuid\n\n```js\nimport { uuid } from 'js-cool'\n\nuuid() // '550e8400-e29b-41d4-a716-446655440000'\nuuid() // '6ba7b810-9dad-11d1-80b4-00c04fd430c8'\nuuid() // 'f47ac10b-58cc-4372-a567-0e02b2c3d479'\n```\n\n#### copy\n\n```js\nimport { copy } from 'js-cool'\n\n// 基本用法\nawait copy('要复制的文本') // true\n\n// 在异步函数中\nasync function handleCopy() {\n  const success = await copy('复制的文本')\n  if (success) {\n    console.log('复制成功!')\n  } else {\n    console.log('复制失败')\n  }\n}\n\n// 带回退处理\nconst result = await copy('回退文本')\nconsole.log(result ? '成功' : '失败')\n```\n\n#### download\n\n```js\nimport { download } from 'js-cool'\n\n// 通过 URL 下载\ndownload('https://example.com/file.pdf')\n\n// 带自定义文件名\ndownload('https://example.com/file.pdf', 'document.pdf')\n\n// 下载 data URL\ndownload('data:text/plain,Hello World', 'hello.txt')\n```\n\n#### openUrl\n\n```js\nimport { openUrl } from 'js-cool'\n\nopenUrl('https://example.com') // 新标签页打开\nopenUrl('https://example.com/file.pdf') // 无法解析时触发下载\n```\n\n#### toThousands\n\n```js\nimport { toThousands } from 'js-cool'\n\ntoThousands(1234567.89) // '1,234,567.89'\ntoThousands(1234567890) // '1,234,567,890'\ntoThousands(1234.567) // '1,234.567'\ntoThousands('1234567') // '1,234,567'\ntoThousands(null) // ''\ntoThousands(undefined) // ''\n```\n\n#### randomNumber / randomNumbers\n\n```js\nimport { randomNumber, randomNumbers } from 'js-cool'\n\n// 随机整数\nrandomNumber() // 1-10 之间的随机整数\nrandomNumber(1, 100) // 1-100 之间的随机整数\nrandomNumber(0, 1) // 0 或 1\nrandomNumber(-10, 10) // -10 到 10 之间的随机整数\n\n// 随机浮点数\nrandomNumber(0.1, 0.9) // 0.1-0.9 之间的随机浮点数\n\n// 固定总和的随机数\nrandomNumbers(4, 100) // [25, 30, 20, 25] (总和 = 100)\nrandomNumbers(3, 10) // [3, 4, 3] (总和 = 10)\nrandomNumbers(5, 1) // [0, 0, 1, 0, 0] (总和 = 1)\n\n// 是否允许零 (默认: true)\nrandomNumbers(4, 100, true) // 不允许零\nrandomNumbers(4, 100, false) // 允许零\n```\n\n#### randomColor\n\n```js\nimport { randomColor } from 'js-cool'\n\n// 随机颜色\nrandomColor() // '#bf444b'\n\n// 较浅颜色 (更高的最小值)\nrandomColor(200) // '#d6e9d7' (所有通道 \u003e= 200)\nrandomColor(128) // '#a1b2c3' (所有通道 \u003e= 128)\n\n// 所有通道范围\nrandomColor(200, 255) // '#d3f9e4' (200-255)\n\n// 单独通道范围\nrandomColor([0, 100, 0], [100, 255, 100])\n// 红: 0-100, 绿: 100-255, 蓝: 0-100\n\n// 暖色系 (更多红/黄)\nrandomColor([200, 100, 0], [255, 200, 100])\n\n// 冷色系 (更多蓝/绿)\nrandomColor([0, 100, 200], [100, 200, 255])\n\n// 深色\nrandomColor(0, 100) // '#3a2b1c'\n\n// 柔和色\nrandomColor(150, 230) // '#c8e6c9'\n```\n\n#### nextIndex\n\n```js\nimport { nextIndex } from 'js-cool'\n\nnextIndex() // 1001\nnextIndex() // 1002\nnextIndex() // 1003\n\n// 用于模态框、提示框\nmodal.style.zIndex = nextIndex()\n```\n\n#### nextVersion\n\n```js\nimport { nextVersion } from 'js-cool'\n\nnextVersion('1.2.3', 'major') // '2.0.0'\nnextVersion('1.2.3', 'minor') // '1.3.0'\nnextVersion('1.2.3', 'patch') // '1.2.4'\nnextVersion('1.2.3', 'premajor') // '2.0.0-0'\nnextVersion('1.2.3', 'preminor') // '1.3.0-0'\nnextVersion('1.2.3', 'prepatch') // '1.2.4-0'\nnextVersion('1.2.3-alpha.1', 'prerelease') // '1.2.3-alpha.2'\n\n// 默认是 patch\nnextVersion('1.2.3') // '1.2.4'\n```\n\n#### getType\n\n```js\nimport { getType } from 'js-cool'\n\ngetType([1, 2, 3]) // 'array'\ngetType({}) // 'object'\ngetType(null) // 'null'\ngetType(undefined) // 'undefined'\ngetType('string') // 'string'\ngetType(123) // 'number'\ngetType(true) // 'boolean'\ngetType(() =\u003e {}) // 'function'\ngetType(new Date()) // 'date'\ngetType(/regex/) // 'regexp'\ngetType(new Error()) // 'error'\ngetType(new Map()) // 'map'\ngetType(new Set()) // 'set'\ngetType(Symbol()) // 'symbol'\ngetType(BigInt(1)) // 'bigint'\n```\n\n#### getFileType\n\n```js\nimport { getFileType } from 'js-cool'\n\ngetFileType('document.pdf') // 'pdf'\ngetFileType('image.png') // 'image'\ngetFileType('video.mp4') // 'video'\ngetFileType('audio.mp3') // 'audio'\ngetFileType('archive.zip') // 'archive'\ngetFileType('code.js') // 'code'\ngetFileType('https://example.com/file.pdf') // 'pdf'\n```\n\n#### fixNumber\n\n```js\nimport { fixNumber } from 'js-cool'\n\nfixNumber(3.14159) // '3.14' (默认2位小数)\nfixNumber(3.14159, 2) // '3.14'\nfixNumber(3.14159, 4) // '3.1416'\nfixNumber(3.1, 4) // '3.1' (不补零)\nfixNumber(100, 2) // '100'\n```\n\n#### delay\n\n```js\nimport { delay } from 'js-cool'\n\nconst d = delay()\n\n// 防抖模式 (第一次触发立即执行)\nd.register('search', () =\u003e console.log('search'), 300, true)\n\n// 节流模式 (延迟执行)\nd.register('scroll', () =\u003e console.log('scroll'), 300, false)\n\n// 销毁特定处理器\nd.destroy('search')\n\n// delay 对象存储所有注册的处理器\n```\n\n#### waiting\n\n```js\nimport { waiting } from 'js-cool'\n\n// 基本等待\nawait waiting(1000) // 等待1秒\n\n// 带超时抛出\nawait waiting(5000, true) // 5秒后抛出错误\n\n// 在异步函数中\nasync function example() {\n  console.log('开始')\n  await waiting(1000)\n  console.log('1秒后')\n}\n\n// 实际应用\nasync function poll() {\n  while (true) {\n    const result = await checkStatus()\n    if (result.done) break\n    await waiting(1000) // 下次轮询前等待\n  }\n}\n```\n\n#### mapTemplate\n\n```js\nimport { mapTemplate } from 'js-cool'\n\n// ${xxx} 风格\nmapTemplate('Hello, ${name}!', { name: 'World' })\n// 'Hello, World!'\n\n// {{xxx}} 风格\nmapTemplate('Hello, {{name}}!', { name: 'World' })\n// 'Hello, World!'\n\n// {xxx} 风格\nmapTemplate('Hello, {name}!', { name: 'World' })\n// 'Hello, World!'\n\n// 多个变量\nmapTemplate('${greeting}, ${name}! You have ${count} messages.', {\n  greeting: 'Hello',\n  name: 'John',\n  count: 5\n})\n// 'Hello, John! You have 5 messages.'\n\n// 嵌套对象\nmapTemplate('User: ${user.name}', { user: { name: 'John' } })\n// 'User: John'\n```\n\n#### sorter\n\n```js\nimport { sorter } from 'js-cool'\n\nconst users = [\n  { name: 'Bob', age: 30 },\n  { name: 'Alice', age: 25 },\n  { name: 'Charlie', age: 35 }\n]\n\n// 按字符串字段排序\nusers.sort(sorter('name', 'asc'))\n// [{ name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }, ...]\n\nusers.sort(sorter('name', 'desc'))\n// [{ name: 'Charlie', age: 35 }, { name: 'Bob', age: 30 }, ...]\n\n// 按数字字段排序\nusers.sort(sorter('age', 'asc'))\n// [{ name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }, ...]\n\n// 带转换函数\nusers.sort(sorter('name', 'asc', name =\u003e name.toLowerCase()))\n```\n\n#### sortPinyin\n\n```js\nimport { sortPinyin } from 'js-cool'\n\nsortPinyin(['张三', '李四', '王五'])\n// ['李四', '王五', '张三']\n\nsortPinyin(['北京', '上海', '广州', '深圳'])\n// ['北京', '广州', '上海', '深圳']\n```\n\n#### punctualTimer\n\n```js\nimport { punctualTimer } from 'js-cool'\n\n// 创建定时器\nconst timer = punctualTimer(() =\u003e {\n  console.log('Tick at:', new Date())\n}, 1000)\n\n// 停止定时器\ntimer.stop()\n\n// 重启定时器\ntimer.start()\n\n// 检查是否运行中\ntimer.isRunning // true/false\n```\n\n#### awaitTo\n\n```js\nimport { awaitTo } from 'js-cool'\n\n// 基本用法\nconst [err, data] = await awaitTo(fetch('/api/data'))\nif (err) {\n  console.error('Error:', err)\n  return\n}\nconsole.log('Data:', data)\n\n// 在异步函数中\nasync function getUser(id) {\n  const [err, user] = await awaitTo(fetch(`/api/users/${id}`))\n  if (err) return null\n  return user.json()\n}\n\n// 多个 Promise\nconst [err, results] = await awaitTo(Promise.all([fetch('/api/users'), fetch('/api/posts')]))\n```\n\n---\n\n### 资源加载\n\n```js\nimport { loadSource, mountCss, mountImg, mountJs, mountStyle, preloader } from 'js-cool'\n\n// 加载 JS 文件\nawait mountJs('https://example.com/script.js')\nawait mountJs('https://example.com/script.js', { async: true })\n\n// 加载 CSS 文件\nawait mountCss('https://example.com/styles.css')\n\n// 注入 CSS 样式\nmountStyle('.modal { display: none; }')\nmountStyle(`\n  .container { max-width: 1200px; }\n  .header { height: 60px; }\n`)\n\n// 加载图片\nawait mountImg('https://example.com/image.png')\nconst img = await mountImg('https://example.com/image.png', { crossOrigin: 'anonymous' })\n\n// 通用资源加载器\nawait loadSource({ type: 'js', url: 'https://example.com/script.js' })\nawait loadSource({ type: 'css', url: 'https://example.com/styles.css' })\nawait loadSource({ type: 'img', url: 'https://example.com/image.png' })\n\n// 预加载图片\nawait preloader(['image1.jpg', 'image2.jpg', 'image3.jpg'])\n```\n\n---\n\n### 二进制转换\n\n```js\nimport {\n  arrayBufferToBase64,\n  arrayBufferToBlob,\n  base64ToArrayBuffer,\n  base64ToBlob,\n  base64ToFile,\n  blobToArrayBuffer,\n  blobToBase64,\n  blobToUrl,\n  fileToBase64,\n  svgToBlob,\n  urlToBlob\n} from 'js-cool'\n\n// ArrayBuffer 转换\nconst buffer = new ArrayBuffer(10)\nconst base64 = arrayBufferToBase64(buffer)\nconst base64WithMime = arrayBufferToBase64(buffer, 'image/png')\nconst blob = arrayBufferToBlob(buffer, 'image/png')\n\n// Base64 转换\nconst buffer = base64ToArrayBuffer('aGVsbG8=')\nconst blob = base64ToBlob('data:image/png;base64,...')\nconst file = base64ToFile('data:image/png;base64,...', 'image.png', 'image/png')\n\n// Blob 转换\nconst buffer = await blobToArrayBuffer(blob)\nconst base64 = await blobToBase64(blob)\nconst url = blobToUrl(blob) // 'blob:origin/uuid'\n\n// File 转换\nconst base64 = await fileToBase64(file) // 'data:image/png;base64,...'\n\n// SVG 转换\nconst blob = svgToBlob('\u003csvg viewBox=\"0 0 100 100\"\u003e...\u003c/svg\u003e')\n// Blob with type 'image/svg+xml'\n\n// URL 转 Blob\nconst blob = await urlToBlob('https://example.com/image.png')\n```\n\n---\n\n### CSV 转换\n\n```js\nimport { CSVToArray, CSVToJSON, JSONToCSV, arrayToCSV } from 'js-cool'\n\nconst csv = 'name,age,city\\nJohn,30,NYC\\nJane,25,LA'\n\n// CSV 转二维数组\nCSVToArray(csv)\n// [['name', 'age', 'city'], ['John', '30', 'NYC'], ['Jane', '25', 'LA']]\n\n// 二维数组转 CSV\narrayToCSV([\n  ['name', 'age'],\n  ['John', 30],\n  ['Jane', 25]\n])\n// 'name,age\\nJohn,30\\nJane,25'\n\n// CSV 转 JSON\nCSVToJSON(csv)\n// [\n//   { name: 'John', age: '30', city: 'NYC' },\n//   { name: 'Jane', age: '25', city: 'LA' }\n// ]\n\n// JSON 转 CSV\nJSONToCSV(\n  [\n    { name: 'John', age: 30 },\n    { name: 'Jane', age: 25 }\n  ],\n  ['name', 'age']\n)\n// 'name,age\\nJohn,30\\nJane,25'\n```\n\n---\n\n### 颜色\n\n#### RGBToHex\n\n```js\nimport { RGBToHex } from 'js-cool'\n\nRGBToHex(255, 0, 0) // '#ff0000'\nRGBToHex(0, 255, 0) // '#00ff00'\nRGBToHex(0, 0, 255) // '#0000ff'\nRGBToHex(255, 255, 255) // '#ffffff'\nRGBToHex(0, 0, 0) // '#000000'\n```\n\n---\n\n## 问题和支持\n\n请提交 issue [这里](https://github.com/saqqdy/js-cool/issues)。\n\n## 许可证\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaqqdy%2Fjs-cool","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaqqdy%2Fjs-cool","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaqqdy%2Fjs-cool/lists"}