├── .commitlintrc.cjs ├── .czrc ├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ ├── deploy-docs.yml │ └── release.yml ├── .gitignore ├── .lintstagedrc.cjs ├── .npmrc ├── .nvmrc ├── .simple-git-hooks.cjs ├── .vitepress ├── components │ └── HomeTeam.vue ├── config.ts └── theme │ ├── index.ts │ └── style.css ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.config.ts ├── docs ├── guide │ ├── index.md │ └── notice.md └── index.md ├── eslint.config.js ├── package.json ├── playground ├── index.html ├── package.json ├── src │ ├── App.vue │ ├── main.ts │ ├── manifest.json │ ├── pages.json │ ├── pages │ │ └── index │ │ │ └── index.vue │ └── static │ │ └── logo.png ├── tsconfig.json └── vite.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── public └── logo.png ├── src ├── exports.ts ├── index.md ├── index.ts ├── tryOnBackPress │ ├── index.md │ └── index.ts ├── tryOnHide │ ├── index.md │ └── index.ts ├── tryOnInit │ ├── index.md │ └── index.ts ├── tryOnLoad │ ├── index.md │ └── index.ts ├── tryOnReady │ ├── index.md │ └── index.ts ├── tryOnScopeDispose │ ├── index.md │ └── index.ts ├── tryOnShow │ ├── index.md │ └── index.ts ├── tryOnUnload │ ├── index.md │ └── index.ts ├── types.ts ├── useActionSheet │ ├── index.md │ └── index.ts ├── useClipboardData │ ├── index.md │ └── index.ts ├── useDownloadFile │ ├── index.md │ └── index.ts ├── useGlobalData │ ├── index.md │ └── index.ts ├── useInterceptor │ ├── index.md │ ├── index.test.ts │ └── index.ts ├── useLoading │ ├── index.md │ └── index.ts ├── useModal │ ├── index.md │ └── index.ts ├── useNetwork │ ├── index.md │ └── index.ts ├── useOnline │ ├── index.md │ └── index.ts ├── usePage │ ├── index.md │ └── index.ts ├── usePageScroll │ ├── index.md │ └── index.ts ├── usePages │ ├── index.md │ └── index.ts ├── usePreferredDark │ ├── index.md │ └── index.ts ├── usePreferredLanguage │ ├── index.md │ └── index.ts ├── usePrevPage │ ├── index.md │ └── index.ts ├── usePrevRoute │ ├── index.md │ └── index.ts ├── useProvider │ ├── index.md │ └── index.ts ├── useRequest │ ├── index.md │ └── index.ts ├── useRoute │ ├── index.md │ └── index.ts ├── useRouter │ ├── index.md │ └── index.ts ├── useScanCode │ ├── index.md │ └── index.ts ├── useScreenBrightness │ ├── index.md │ └── index.ts ├── useSelectorQuery │ ├── index.md │ └── index.ts ├── useSocket │ ├── index.md │ └── index.ts ├── useStorage │ ├── index.md │ ├── index.test.ts │ └── index.ts ├── useStorageAsync │ ├── index.md │ └── index.ts ├── useStorageSync │ ├── index.md │ └── index.ts ├── useToast │ ├── index.md │ └── index.ts ├── useUploadFile │ ├── index.md │ └── index.ts ├── useVisible │ ├── index.md │ └── index.ts ├── utils.test.ts └── utils.ts ├── test ├── index.ts ├── mount.ts └── setup.ts ├── tsconfig.json ├── types └── global.d.ts └── vitest.config.ts /.commitlintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "@commitlint/prompt" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: pnpm/action-setup@v3 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version-file: .nvmrc 20 | cache: pnpm 21 | registry-url: https://registry.npmjs.org 22 | - run: corepack enable 23 | - run: pnpm install 24 | - run: pnpm run build 25 | - run: pnpm run lint 26 | 27 | typecheck: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: pnpm/action-setup@v3 32 | - uses: actions/setup-node@v3 33 | with: 34 | node-version-file: .nvmrc 35 | cache: pnpm 36 | registry-url: https://registry.npmjs.org 37 | - run: corepack enable 38 | - run: pnpm install 39 | - run: pnpm run check:types 40 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a VitePress site to GitHub Pages 2 | # 3 | name: Deploy VitePress site to Pages 4 | 5 | on: 6 | # Runs on pushes targeting the `main` branch. Change this to `master` if you're 7 | # using the `master` branch as the default branch. 8 | push: 9 | branches: [main] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 21 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 22 | concurrency: 23 | group: pages 24 | cancel-in-progress: false 25 | 26 | jobs: 27 | # Build job 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 35 | - uses: pnpm/action-setup@v3 36 | - uses: actions/setup-node@v4 37 | with: 38 | node-version-file: .nvmrc 39 | cache: pnpm 40 | registry-url: https://registry.npmjs.org 41 | - name: Setup Pages 42 | uses: actions/configure-pages@v4 43 | - name: Install dependencies 44 | run: pnpm install # or pnpm install / yarn install / bun install 45 | - name: Build with VitePress 46 | run: pnpm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build 47 | - name: Upload artifact 48 | uses: actions/upload-pages-artifact@v3 49 | with: 50 | path: .vitepress/dist 51 | 52 | # Deployment job 53 | deploy: 54 | environment: 55 | name: github-pages 56 | url: ${{ steps.deployment.outputs.page_url }} 57 | needs: build 58 | runs-on: ubuntu-latest 59 | name: Deploy 60 | steps: 61 | - name: Deploy to GitHub Pages 62 | id: deployment 63 | uses: actions/deploy-pages@v4 64 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | check: 13 | runs-on: ubuntu-latest 14 | outputs: 15 | version: ${{ steps.info.outputs.version }} 16 | exists: ${{ steps.checkTag.outputs.exists }} 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Read package.json INFO 22 | id: info 23 | uses: jaywcjlove/github-action-package@main 24 | 25 | - run: echo "version - ${{ steps.info.outputs.version }}" 26 | 27 | - name: Check if tag exist 28 | id: checkTag 29 | uses: mukunku/tag-exists-action@v1.6.0 30 | if: steps.info.outputs.version != '' 31 | with: 32 | tag: 'v${{steps.info.outputs.version}}' 33 | 34 | - run: echo "exists - ${{ steps.checkTag.outputs.exists }}" 35 | 36 | release: 37 | runs-on: ubuntu-latest 38 | needs: check 39 | if: needs.check.outputs.exists == 'false' 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | - uses: pnpm/action-setup@v3 44 | - uses: actions/setup-node@v3 45 | with: 46 | node-version-file: .nvmrc 47 | cache: pnpm 48 | registry-url: https://registry.npmjs.org 49 | - run: corepack enable 50 | - run: pnpm install 51 | - run: pnpm publish --access public --no-git-checks 52 | env: 53 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 54 | 55 | - name: Release 56 | uses: softprops/action-gh-release@v1 57 | with: 58 | tag_name: 'v${{needs.check.outputs.version}}' 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | pnpm-debug.log* 9 | run 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules 44 | jspm_packages 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules 48 | 49 | # Temp directory 50 | .temp 51 | .tmp 52 | 53 | # TypeScript cache 54 | *.tsbuildinfo 55 | 56 | # Optional npm cache directory 57 | .npm 58 | 59 | # Optional eslint cache 60 | .eslintcache 61 | 62 | # Microbundle cache 63 | .rpt2_cache 64 | .rts2_cache_cjs 65 | .rts2_cache_es 66 | .rts2_cache_umd 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | # dotenv environment variables file 78 | .env 79 | .env.test 80 | .env.*.test 81 | .env.local 82 | .env.*.local 83 | *.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Electron 90 | dist-electron 91 | dist_electron 92 | 93 | # Tauri 94 | dist-tauri 95 | dist_tauri 96 | 97 | # Cordova 98 | dist-cordova 99 | dist_cordova 100 | 101 | # Capacitor 102 | dist-capacitor 103 | dist_capacitor 104 | 105 | # Next.js build output 106 | .next 107 | out 108 | 109 | # Umi.js build output 110 | .umi 111 | .umi-production 112 | .umi-test 113 | 114 | # Nuxt.js build / generate output 115 | .nuxt 116 | dist 117 | 118 | # Rax.js build 119 | .rax 120 | 121 | # Vuepress build output 122 | .vuepress/dist 123 | 124 | # SSR 125 | dist-ssr 126 | dist_ssr 127 | 128 | # SSG 129 | dist-ssg 130 | dist_ssg 131 | 132 | # Serverless 133 | .serverless 134 | .dynamodb 135 | .s3 136 | .buckets 137 | .seeds 138 | 139 | # FuseBox cache 140 | .fusebox 141 | 142 | # TernJS port file 143 | .tern-port 144 | 145 | # Cypress 146 | /cypress/videos/ 147 | /cypress/screenshots/ 148 | 149 | # Editor 150 | .vscode-test 151 | .vscode/** 152 | !.vscode/extensions.json 153 | !.vscode/settings.json 154 | *.vsix 155 | .idea 156 | .hbuilder 157 | .hbuilderx 158 | *.suo 159 | *.ntvs* 160 | *.njsproj 161 | *.sln 162 | *.sw? 163 | 164 | # yarn v2 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # Apple 172 | .DS_Store 173 | *.p12 174 | *.mobileprovision 175 | 176 | # Android 177 | *.keystore 178 | 179 | # Vitepress 180 | cache -------------------------------------------------------------------------------- /.lintstagedrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,cjs,mjs,ts,cts,mts,md}': () => [ 3 | 'pnpm check:types', 4 | 'pnpm lint:eslint', 5 | 'pnpm lint:publint', 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | registry=https://registry.npmjs.org/ 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /.simple-git-hooks.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-template-curly-in-string */ 2 | module.exports = { 3 | 'pre-commit': 'npx lint-staged', 4 | 'commit-msg': 'npx commitlint --edit ${1}', 5 | }; 6 | -------------------------------------------------------------------------------- /.vitepress/components/HomeTeam.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 32 | -------------------------------------------------------------------------------- /.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { dirname, join } from 'node:path'; 2 | import fg from 'fast-glob'; 3 | import { defineConfig } from 'vitepress'; 4 | 5 | // https://vitepress.dev/reference/site-config 6 | export default async () => { 7 | const files = await fg('**/*.md', { 8 | cwd: join(__dirname, '..', 'src'), 9 | }); 10 | 11 | const dirs = [...new Set(files.map(f => dirname(f)).filter(d => d !== '.' && d !== '..'))]; 12 | 13 | return defineConfig({ 14 | title: 'UniUse', 15 | description: 'uni-app (vue3) 组合式工具集。', 16 | lang: 'zh-CN', 17 | base: '/uni-use/', 18 | srcDir: '.', 19 | // srcExclude: ['!(docs|src)/**'], 20 | rewrites: { 21 | 'docs/:name.md': ':name.md', 22 | 'docs/:doc/:name.md': ':doc/:name.md', 23 | 'src/index.md': 'apis.md', 24 | 'src/:pkg/:name.md': ':pkg/:name.md', 25 | }, 26 | 27 | themeConfig: { 28 | logo: '/logo.png', 29 | 30 | nav: [ 31 | { text: '指南', link: '/guide/' }, 32 | { text: 'API', link: '/apis/' }, 33 | { text: 'ChangeLog', link: '/CHANGELOG.md' }, 34 | ], 35 | 36 | sidebar: { 37 | '/guide/': [ 38 | { 39 | text: '指南', 40 | items: [ 41 | { 42 | text: '开始', 43 | link: '/guide/', 44 | }, 45 | { 46 | text: '注意事项', 47 | link: '/guide/notice', 48 | }, 49 | { 50 | text: '更新日志', 51 | link: '/CHANGELOG.md', 52 | }, 53 | { 54 | text: '所有函数', 55 | link: '/apis/', 56 | }, 57 | ], 58 | }, 59 | ], 60 | '/': [ 61 | { 62 | text: 'All Functions', 63 | items: [ 64 | { 65 | text: '函数列表', 66 | link: '/apis/', 67 | }, 68 | ], 69 | }, 70 | { 71 | text: 'API', 72 | items: dirs.map(dir => ({ 73 | text: dir, 74 | link: `/${dir}/`, 75 | })), 76 | }, 77 | ], 78 | }, 79 | 80 | editLink: { 81 | pattern: 'https://github.com/uni-helper/uni-use/tree/main/src/:path', 82 | text: '为这个页面提供建议', 83 | }, 84 | 85 | footer: { 86 | message: 'Released under the MIT License.', 87 | copyright: 'Copyright © 2022-PRESENT uni-helper and uni-helper contributors', 88 | }, 89 | 90 | docFooter: { 91 | prev: '上一页', 92 | next: '下一页', 93 | }, 94 | 95 | outline: { 96 | label: '页面导航', 97 | }, 98 | 99 | search: { 100 | provider: 'local', 101 | options: { 102 | locales: { 103 | 'zh-CN': { 104 | translations: { 105 | button: { 106 | buttonText: '搜索文档', 107 | buttonAriaLabel: '搜索文档', 108 | }, 109 | modal: { 110 | noResultsText: '无法找到相关结果', 111 | resetButtonTitle: '清除查询条件', 112 | footer: { 113 | selectText: '选择', 114 | navigateText: '切换', 115 | closeText: '关闭', 116 | }, 117 | }, 118 | }, 119 | }, 120 | }, 121 | }, 122 | }, 123 | 124 | lastUpdated: { 125 | text: '最后更新于', 126 | formatOptions: { 127 | dateStyle: 'short', 128 | timeStyle: 'medium', 129 | }, 130 | }, 131 | 132 | langMenuLabel: '多语言', 133 | returnToTopLabel: '回到顶部', 134 | sidebarMenuLabel: '菜单', 135 | darkModeSwitchLabel: '主题', 136 | lightModeSwitchTitle: '切换到浅色模式', 137 | darkModeSwitchTitle: '切换到深色模式', 138 | 139 | socialLinks: [ 140 | { icon: 'github', link: 'https://github.com/uni-helper/uni-use' }, 141 | ], 142 | }, 143 | 144 | }); 145 | }; 146 | -------------------------------------------------------------------------------- /.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // https://vitepress.dev/guide/custom-theme 2 | import type { Theme } from 'vitepress'; 3 | import DefaultTheme from 'vitepress/theme'; 4 | import { h } from 'vue'; 5 | import HomeTeam from '../components/HomeTeam.vue'; 6 | import './style.css'; 7 | 8 | export default { 9 | extends: DefaultTheme, 10 | Layout: () => { 11 | return h(DefaultTheme.Layout, null, { 12 | // https://vitepress.dev/guide/extending-default-theme#layout-slots 13 | }); 14 | }, 15 | enhanceApp({ app }) { 16 | app.component('HomeTeam', HomeTeam); 17 | }, 18 | } satisfies Theme; 19 | -------------------------------------------------------------------------------- /.vitepress/theme/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Customize default theme styling by overriding CSS variables: 3 | * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css 4 | */ 5 | 6 | /** 7 | * Colors 8 | * 9 | * Each colors have exact same color scale system with 3 levels of solid 10 | * colors with different brightness, and 1 soft color. 11 | * 12 | * - `XXX-1`: The most solid color used mainly for colored text. It must 13 | * satisfy the contrast ratio against when used on top of `XXX-soft`. 14 | * 15 | * - `XXX-2`: The color used mainly for hover state of the button. 16 | * 17 | * - `XXX-3`: The color for solid background, such as bg color of the button. 18 | * It must satisfy the contrast ratio with pure white (#ffffff) text on 19 | * top of it. 20 | * 21 | * - `XXX-soft`: The color used for subtle background such as custom container 22 | * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors 23 | * on top of it. 24 | * 25 | * The soft color must be semi transparent alpha channel. This is crucial 26 | * because it allows adding multiple "soft" colors on top of each other 27 | * to create a accent, such as when having inline code block inside 28 | * custom containers. 29 | * 30 | * - `default`: The color used purely for subtle indication without any 31 | * special meanings attached to it such as bg color for menu hover state. 32 | * 33 | * - `brand`: Used for primary brand colors, such as link text, button with 34 | * brand theme, etc. 35 | * 36 | * - `tip`: Used to indicate useful information. The default theme uses the 37 | * brand color for this by default. 38 | * 39 | * - `warning`: Used to indicate warning to the users. Used in custom 40 | * container, badges, etc. 41 | * 42 | * - `danger`: Used to show error, or dangerous message to the users. Used 43 | * in custom container, badges, etc. 44 | * -------------------------------------------------------------------------- */ 45 | 46 | :root { 47 | --vp-c-default-1: var(--vp-c-gray-1); 48 | --vp-c-default-2: var(--vp-c-gray-2); 49 | --vp-c-default-3: var(--vp-c-gray-3); 50 | --vp-c-default-soft: var(--vp-c-gray-soft); 51 | 52 | --vp-c-brand-1: var(--vp-c-indigo-1); 53 | --vp-c-brand-2: var(--vp-c-indigo-2); 54 | --vp-c-brand-3: var(--vp-c-indigo-3); 55 | --vp-c-brand-soft: var(--vp-c-indigo-soft); 56 | 57 | --vp-c-tip-1: var(--vp-c-brand-1); 58 | --vp-c-tip-2: var(--vp-c-brand-2); 59 | --vp-c-tip-3: var(--vp-c-brand-3); 60 | --vp-c-tip-soft: var(--vp-c-brand-soft); 61 | 62 | --vp-c-warning-1: var(--vp-c-yellow-1); 63 | --vp-c-warning-2: var(--vp-c-yellow-2); 64 | --vp-c-warning-3: var(--vp-c-yellow-3); 65 | --vp-c-warning-soft: var(--vp-c-yellow-soft); 66 | 67 | --vp-c-danger-1: var(--vp-c-red-1); 68 | --vp-c-danger-2: var(--vp-c-red-2); 69 | --vp-c-danger-3: var(--vp-c-red-3); 70 | --vp-c-danger-soft: var(--vp-c-red-soft); 71 | } 72 | 73 | /** 74 | * Component: Button 75 | * -------------------------------------------------------------------------- */ 76 | 77 | :root { 78 | --vp-button-brand-border: transparent; 79 | --vp-button-brand-text: var(--vp-c-white); 80 | --vp-button-brand-bg: var(--vp-c-brand-3); 81 | --vp-button-brand-hover-border: transparent; 82 | --vp-button-brand-hover-text: var(--vp-c-white); 83 | --vp-button-brand-hover-bg: var(--vp-c-brand-2); 84 | --vp-button-brand-active-border: transparent; 85 | --vp-button-brand-active-text: var(--vp-c-white); 86 | --vp-button-brand-active-bg: var(--vp-c-brand-1); 87 | } 88 | 89 | /** 90 | * Component: Home 91 | * -------------------------------------------------------------------------- */ 92 | 93 | :root { 94 | --vp-home-hero-name-color: transparent; 95 | --vp-home-hero-name-background: -webkit-linear-gradient( 96 | 120deg, 97 | #bd34fe 30%, 98 | #41d1ff 99 | ); 100 | 101 | --vp-home-hero-image-background-image: linear-gradient( 102 | -45deg, 103 | #bd34fe 50%, 104 | #47caff 50% 105 | ); 106 | --vp-home-hero-image-filter: blur(44px); 107 | } 108 | 109 | @media (min-width: 640px) { 110 | :root { 111 | --vp-home-hero-image-filter: blur(56px); 112 | } 113 | } 114 | 115 | @media (min-width: 960px) { 116 | :root { 117 | --vp-home-hero-image-filter: blur(68px); 118 | } 119 | } 120 | 121 | /** 122 | * Component: Custom Block 123 | * -------------------------------------------------------------------------- */ 124 | 125 | :root { 126 | --vp-custom-block-tip-border: transparent; 127 | --vp-custom-block-tip-text: var(--vp-c-text-1); 128 | --vp-custom-block-tip-bg: var(--vp-c-brand-soft); 129 | --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); 130 | } 131 | 132 | /** 133 | * Component: Algolia 134 | * -------------------------------------------------------------------------- */ 135 | 136 | .DocSearch { 137 | --docsearch-primary-color: var(--vp-c-brand-1) !important; 138 | } 139 | 140 | /** 141 | * Component: Home 142 | * -------------------------------------------------------------------------- */ 143 | 144 | :root { 145 | --vp-home-hero-name-color: transparent; 146 | --vp-home-hero-name-background: -webkit-linear-gradient( 147 | 315deg, 148 | #42d392 25%, 149 | #647eff 150 | ); 151 | --vp-home-hero-image-background-image: linear-gradient( 152 | -45deg, 153 | #41b88380 30%, 154 | #35495e80 155 | ); 156 | --vp-home-hero-image-filter: blur(30px); 157 | } 158 | 159 | @media (min-width: 640px) { 160 | :root { 161 | --vp-home-hero-image-filter: blur(56px); 162 | } 163 | } 164 | 165 | @media (min-width: 960px) { 166 | :root { 167 | --vp-home-hero-image-filter: blur(72px); 168 | } 169 | } 170 | 171 | /** 172 | * VitePress: Custom fix 173 | * -------------------------------------------------------------------------- */ 174 | 175 | .VPLocalSearchBox .result { 176 | --vp-c-bg-search-result: var(--vp-c-bg); 177 | background: var(--vp-c-bg-search-result) !important; 178 | padding: 4px !important; 179 | border: 1px solid var(--vp-c-divider) !important; 180 | } 181 | .VPLocalSearchBox .result.selected { 182 | --vp-c-bg-search-result: var(--vp-c-bg-soft) !important; 183 | } 184 | .VPLocalSearchBox .result .excerpt-gradient-top { 185 | background: linear-gradient( 186 | var(--vp-c-bg-search-result), 187 | transparent 188 | ) !important; 189 | } 190 | .VPLocalSearchBox .result .excerpt-gradient-bottom { 191 | background: linear-gradient( 192 | transparent, 193 | var(--vp-c-bg-search-result) 194 | ) !important; 195 | } 196 | .VPLocalSearchBox .title-icon { 197 | display: none; 198 | } 199 | .VPLocalSearchBox .excerpt-wrapper { 200 | margin-top: 4px; 201 | } 202 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "manifest.json": "jsonc", 4 | "pages.json": "jsonc" 5 | }, 6 | 7 | // Disable the default formatter, use eslint instead 8 | "prettier.enable": false, 9 | "editor.formatOnSave": false, 10 | 11 | // Auto fix 12 | "editor.codeActionsOnSave": { 13 | "source.fixAll.eslint": "always" 14 | }, 15 | 16 | // Silent the stylistic rules in you IDE, but still auto fix them 17 | "eslint.rules.customizations": [ 18 | { "rule": "style/*", "severity": "off" }, 19 | { "rule": "format/*", "severity": "off" }, 20 | { "rule": "*-indent", "severity": "off" }, 21 | { "rule": "*-spacing", "severity": "off" }, 22 | { "rule": "*-spaces", "severity": "off" }, 23 | { "rule": "*-order", "severity": "off" }, 24 | { "rule": "*-dangle", "severity": "off" }, 25 | { "rule": "*-newline", "severity": "off" }, 26 | { "rule": "*quotes", "severity": "off" }, 27 | { "rule": "*semi", "severity": "off" } 28 | ], 29 | 30 | // Enable eslint for all supported languages 31 | "eslint.validate": [ 32 | "javascript", 33 | "javascriptreact", 34 | "typescript", 35 | "typescriptreact", 36 | "vue", 37 | "html", 38 | "markdown", 39 | "json", 40 | "jsonc", 41 | "yaml", 42 | "toml" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 改动日志 2 | 3 | ## 0.19.0 (2024-01-17) 4 | 5 | - fix: 重构 `useStorage` / `useStorageSync`,使其更贴合 `uniapp` 的使用习惯 6 | - feat: 强化 `useRouter`,增加功能。 7 | - feat: 增加 `uniUseAutoImports` 函数。 8 | - feat: 增加 `tryOnBackPress` 函数 9 | 10 | ## 0.18.0 (2023-01-15) 11 | 12 | - feat!: 调整 `useLoading`,现在返回是一个对象,其中包含 `showLoading` 和 `hideLoading` 两个方法 13 | - fix: 调整类型实现,支持 `vue` v3.2.47 和 `@vueuse/core` v10,但仍需要用户侧做部分调整,参考 [uni-app#4604](https://github.com/dcloudio/uni-app/issues/4604) 14 | - feat: `useDownloadFile`、`useRequest`、`useSocket`、`useStorageAsync`、`useUploadFile` 对标 `@vueuse/core` v10.7.1 15 | 16 | ## 0.17.1 (2023-11-14) 17 | 18 | - fix: 修复 `useStorage`,关闭 [#28](https://github.com/uni-helper/uni-use/issues/28) 19 | 20 | ## 0.17.0 (2023-11-13) 21 | 22 | - fix: 修复 `useStorage`,关闭 [#28](https://github.com/uni-helper/uni-use/issues/28) 23 | - feat!: 要求 `node>=18` 24 | 25 | ## 0.16.0 (2023-10-17) 26 | 27 | - feat: 增加 `usePageScroll`,感谢 [okxiaoliang4](https://github.com/okxiaoliang4) 在 [#27](https://github.com/uni-helper/uni-use/pull/27) 的贡献! 28 | 29 | ## 0.15.1 (2023-09-22) 30 | 31 | - fix: 修复导出 32 | 33 | ## 0.15.0 (2023-08-31) 34 | 35 | - feat: 增加 `UniUseAutoImports`,方便和 `unplugin-auto-import` 结合使用 36 | 37 | ## 0.14.1 (2023-08-15) 38 | 39 | - fix: 修复 `useStorageAsync` 逻辑判断,关闭 [#25](https://github.com/uni-helper/uni-use/issues/25) 40 | 41 | ## 0.14.0 (2023-07-28) 42 | 43 | - feat: 新增 `useSelectorQuery`,感谢 [edwinhuish](https://github.com/edwinhuish) 在 [#23](https://github.com/uni-helper/uni-use/pull/23) 的贡献! 44 | 45 | ## 0.13.0 (2023-05-05) 46 | 47 | - feat!: 移除 `useImmer` 48 | - fix: 修复 `useSocket` 类型 49 | 50 | ## 0.12.0 (2023-02-16) 51 | 52 | - build: 设置目标为 `es2017` 53 | - fix: 修复 `useDownloadFile` 没有正确中止的问题 54 | - fix: 修复 `useRequest` 没有正确中止的问题 55 | - fix: 修复 `useUploadFile` 没有正确中止的问题 56 | - fix: 修复 `useSocket` 内部实现 57 | - feat: 新增 `useStorageAsync`,和 `vue-use` 的 `useStorageAsync` 类似 58 | - feat!: 移除遗留的组件版本 59 | - feat!: 移除大量无状态逻辑方法,避免过度封装 60 | - feat!: 调整 `useClipboardData` 实现,现在会返回一个 `Ref` 61 | - feat!: 调整 `useGlobalData` 实现,现在会返回一个 `Ref` 62 | - feat!: 调整 `useLoading` 实现,现在会返回可调用的方法,和 `useActionSheet` 类似 63 | - feat!: 调整 `useNetwork` 初始默认值为 `none` 并移除传参 64 | - feat!: 调整 `useRouter` 实现,移除了所有方法 65 | - feat!: 调整 `useScreenBrightness` 实现,现在会返回一个 `Ref` 66 | - feat!: 调整 `useSocket` 实现,现在和 `vue-use` 的 `useWebSocket` 类似 67 | - feat!: 移除 `useStorage` 68 | - feat!: 调整 `useToast` 实现,现在会返回可调用的方法,和 `useActionSheet` 类似 69 | 70 | 请先阅读 [组合式函数](https://cn.vuejs.org/guide/reusability/composables.html) 和 [组合式 API 常见问答](https://cn.vuejs.org/guide/extras/composition-api-faq.html),以了解为什么这个版本移除了大量无状态逻辑方法。核心内容摘录如下。 71 | 72 | > 在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。 73 | > 74 | > 当构建前端应用时,我们常常需要复用公共任务的逻辑。例如为了在不同地方格式化时间,我们可能会抽取一个可复用的日期格式化函数。这个函数封装了无状态的逻辑:它在接收一些输入后立刻返回所期望的输出。复用无状态逻辑的库有很多,比如你可能已经用过的 lodash 或是 date-fns。 75 | > 76 | > 相比之下,有状态逻辑负责管理会随时间而变化的状态。一个简单的例子是跟踪当前鼠标在页面中的位置。在实际应用中,也可能是像触摸手势或与数据库的连接状态这样的更复杂的逻辑。 77 | 78 | ## 0.11.0 (2023-01-10) 79 | 80 | - feat!: 因难以保证稳定性移除所有组件版本 81 | 82 | ## 0.10.4 (2023-01-04) 83 | 84 | - fix: 修复情况判断 85 | - build: 切换到 `rollup` 86 | 87 | ## 0.10.3 (2022-12-28) 88 | 89 | - fix: 修复导出 90 | 91 | ## 0.10.2 (2022-12-26) 92 | 93 | - fix: 修复类型 94 | 95 | ## 0.10.1 (2022-12-23) 96 | 97 | - fix: 修复构建 98 | 99 | ## 0.10.0 (2022-12-15) 100 | 101 | - feat!: 要求 `node >= 14.18`,这是为了对标 `rollup` 和 `vite` 102 | - feat: 默认为 esm 包,但仍支持 cjs 103 | 104 | ## 0.9.1 (2022-11-16) 105 | 106 | - fix: 修复构建 107 | 108 | ## 0.9.0 (2022-11-16) 109 | 110 | - feat!: 迁移到 `@uni-helper/uni-use` 111 | 112 | ## 0.8.0 (2022-10-24) 113 | 114 | - feat: 新增 `UseAccountInfo` 115 | - feat: 新增 `useAddress` 116 | - feat: 新增 `useAppBaseInfo` 和 `UseAppBaseInfo` 117 | - feat: 新增 `useAudio` 118 | - feat: 新增 `useAuthorize` 119 | - feat: 新增 `useBackground` 导出 120 | - feat: 新增 `useCamera` 121 | - feat: 新增 `useDeviceInfo` 和 `UseDeviceInfo` 122 | - feat: 新增 `useFile` 123 | - feat: 新增 `useImage` 导出 124 | - feat: 新增 `useImmer` 125 | - feat: 新增 `useInvoice` 126 | - feat: 新增 `useLocation` 导出 127 | - feat: 新增 `useMap` 128 | - feat: 新增 `useMenuButtonBoundingClientRect` 129 | - feat: 新增 `useRecorder` 130 | - feat: 新增 `useSubscription` 131 | - feat: 新增 `useSystemSetting` 132 | - feat: 新增 `useUpdate` 133 | - feat: 新增 `useUser` 134 | - feat: 新增 `useVideo` 135 | - feat: 增加 `useWindowInfo` 和 `UseWindowInfo` 136 | - fix: 修复组件没有正确导出的问题 137 | 138 | ## 0.7.2 (2022-10-12) 139 | 140 | - perf: 优化 `useRequest`、`useDownloadFile` 和 `useUploadFile` 类型 141 | - fix: 修复构建 142 | 143 | ## 0.7.1 (2022-09-30) 144 | 145 | - fix: 修复了构建不正常的问题 146 | 147 | ## 0.7.0 (2022-09-29) 148 | 149 | - feat!: 现在要求使用 `node >= 14.16` 150 | - feat!: 现在构建目标是 `esnext` 151 | - fix: 修复了构建不正常的问题 152 | - feat: 新增 `useAccountInfo` 153 | - feat: 新增 `useLaunchOptions` 154 | - feat: 新增 `useEnterOptions` 155 | 156 | ## 0.6.1 157 | 158 | - fix: 修复 `useSelectorQuery` 类型 159 | 160 | ## 0.6.0 161 | 162 | - feat!: 放弃 `vue@2` 支持 163 | - perf!: 调整 `useApp` 导出 164 | - perf: 优化 `useActionSheet` 类型 165 | - feat: `useArrayBufferToBase64` 支持传入 `ref` 166 | - feat: `useBase64ToArrayBuffer` 支持传入 `ref` 167 | - perf: 优化 `useBackground` 类型 168 | - perf: 优化 `useClipboardData` 类型 169 | - fix: 修复 `useGlobalData` 错误赋值 170 | - perf: 优化 `useGlobalData` 类型 171 | - fix: 修复 `useGlobalData` 导出 172 | - perf: 优化 `useImage` 类型 173 | - perf: 优化 `useLoading` 类型 174 | - perf: 优化 `useLocation` 类型 175 | - perf: 优化 `useModal` 类型 176 | - perf: 优化 `useNavigationBar` 类型 177 | - fix: 替换 `useRouter` 中的 `at` 178 | - perf: 优化 `useRouter` 类型 179 | - perf: 优化 `useScanCode` 类型 180 | - perf: 优化 `useScreenBrightness` 类型 181 | - perf: 优化 `useStorage` 类型 182 | - perf: 优化 `useSupported` 类型 183 | - perf: 优化 `useTabBar` 类型 184 | - perf: 优化 `useToast` 类型 185 | - perf: 优化 `useVibrate` 类型 186 | 187 | ## 0.5.0 188 | 189 | - feat: 新增 `useActionSheet` 190 | - perf!: 调整 `useArrayBufferToBase64` 实现 191 | - feat: 新增 `useBackground` 192 | - perf!: 调整 `useBase64ToArrayBuffer` 实现 193 | - fix: 修复 `useClipboardData` 监听 194 | - perf: 调整 `useClipboardData` 实现 195 | - feat!: 移除 `useColorMode` 196 | - feat!: 移除 `useDark` 197 | - perf!: 调整 `useGlobalData` 实现 198 | - feat: 新增 `useImage` 199 | - fix: 修复 `useInterceptor` 监听 200 | - feat: 新增 `useLoading` 201 | - feat: 新增 `useLocation` 202 | - feat: 新增 `useModal` 203 | - feat: 新增 `useNavigationBar` 204 | - perf: 调整 `useNetwork` 实现 205 | - perf: 调整 `UsePreferredDark` 实现 206 | - perf: 调整 `UsePreferredLanguage` 实现 207 | - feat: 新增 `usePullDownRefresh` 实现 208 | - feat: 新增 `useScanCode` 209 | - feat: 新增 `useScreenBrightness` 和 `UseScreenBrightness` 210 | - feat: 新增 `useSelectorQuery` 211 | - feat: 新增 `useStorage` 212 | - feat!: 移除 `useStorageAsync` 213 | - feat: 新增 `useSystemInfo` 和 `UseSystemInfo` 214 | - feat: 新增 `useTabBar` 215 | - feat: 新增 `useToast` 216 | - perf: 调整 `useVibrate` 实现 217 | 218 | ## 0.4.2 219 | 220 | - fix: 修复链接 221 | 222 | ## 0.4.1 223 | 224 | - fix: 修复 `setClipboardData` 未正确更新的问题 225 | - fix: 修复 `UseGlobalData` 导出错误 226 | - fix: 修复 `UseUniPlatform` 导出错误 227 | - perf: 调整 `useRouter` 导出 228 | 229 | ## 0.4.0 230 | 231 | - feat: 新增 `usePrevPage` 232 | - feat: 新增 `usePrevRoute` 233 | - feat: 新增 `useRoute` 234 | - feat: 新增 `useVibrate` 235 | - fix: 调整 `useArrayBufferToBase64` 类型 236 | - fix: 调整 `useBase64ToArrayBuffer` 类型 237 | - fix: 调整 `useClipboardData` 参数和内部实现 238 | 239 | ## 0.3.0 240 | 241 | - feat: 新增 `tryOnHide` 242 | - feat: 新增 `tryOnInit` 243 | - feat: 新增 `tryOnLoad` 244 | - feat: 新增 `tryOnReady` 245 | - feat: 新增 `tryOnShow` 246 | - feat: 新增 `tryOnUnload` 247 | - feat: 新增 `useApp` 248 | - feat: 新增 `useArrayBufferToBase64` 249 | - feat: 新增 `useBase64ToArrayBuffer` 250 | - feat: 新增 `useClipboardData` 和 `UseClipboardData` 251 | - feat: 新增 `useGlobalData` 和 `useGlobalData` 252 | - feat: 新增 `usePage` 253 | - feat: 新增 `usePages` 254 | - feat: 新增 `useRouter` 255 | - feat: 新增 `useUniPlatform` 和 `UseUniPlatform` 256 | - feat: 新增 `useSocket` 257 | - feat: 新增 `useSupported` 258 | - feat: 新增 `useVisible` 259 | - fix: 修复 `useStorageAsync` 判断 260 | 261 | ## 0.2.0 262 | 263 | - feat: 新增 `useColorMode` 和 `UseColorMode` 264 | - feat: 新增 `useDark` 和 `UseDark` 265 | - fix: 修复 `UseNetwork` 导入 266 | - fix: 修复 `UseOnline` 导入 267 | - feat: 新增 `usePreferredDark` 和 `UsePreferredDark` 268 | - feat: 新增 `usePreferredLanguage` 和 `UsePreferredLanguage` 269 | 270 | ## 0.1.0 271 | 272 | - feat: 新增 `useDownloadFile` 273 | - feat: 新增 `useInterceptor` 274 | - feat: 新增 `useNetwork` 和 `UseNetwork` 275 | - feat: 新增 `useOnline` 和 `UseOnline` 276 | - feat: 新增 `useRequest` 277 | - feat: 新增 `useStorageAsync` 278 | - feat: 新增 `useUploadFile` 279 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present uni-helper 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @uni-helper/uni-use 2 | 3 | [![License](https://img.shields.io/github/license/uni-helper/uni-use)](https://github.com/uni-helper/uni-use/blob/main/LICENSE) 4 | [![npm](https://img.shields.io/npm/v/@uni-helper/uni-use)](https://www.npmjs.com/package/@uni-helper/uni-use) 5 | 6 | `uni-app (vue3)` 组合式工具集。要求 `node>=18`。 7 | 8 | - [@uni-helper/uni-use](#uni-helperuni-use) 9 | - [安装依赖](#安装依赖) 10 | - [使用](#使用) 11 | - [其它](#其它) 12 | - [限制](#限制) 13 | - [构建](#构建) 14 | - [和 `unplugin-auto-import` 结合使用](#和-unplugin-auto-import-结合使用) 15 | - [EventBus](#eventbus) 16 | - [TypeScript](#typescript) 17 | - [资源](#资源) 18 | - [致谢](#致谢) 19 | 20 | ## 安装依赖 21 | 22 | ```shell 23 | npm install @uni-helper/uni-use @vueuse/core@9 24 | ``` 25 | 26 | 如果你希望使用 `@vueuse/core` v10,请参考 [uni-app#4604](https://github.com/dcloudio/uni-app/issues/4604)。 27 | 28 |
29 | yarn v2 或以上 30 |

请参考 文档 设置 nodeLinkernode_modules

31 |
32 | 33 |
34 | pnpm 35 |

请参考 文档 设置 shamefully-hoisttrue

36 |
37 | 38 | 目前没有支持 `uni_modules` 的计划,但欢迎 PR 贡献。 39 | 40 | ## 使用 41 | 42 | 详情请看 [`uni-use`函数列表](./src/index.md) 43 | 44 | ## 其它 45 | 46 | ### 限制 47 | 48 | 在小程序和移动应用环境下有如下无法避开的限制: 49 | 50 | - 缺失某些全局变量(如 `window`、`navigator` 等) 51 | - 必须使用 `uni-app` 提供的 API 实现功能(如拦截器、存储等),API 不支持的也就无法支持,比如拦截同步 API、监听其它地方引起的剪切板变化等 52 | - 无法使用顶层 `await` 53 | 54 | 在开发网页时,建议直接使用 `vue`,避免过多的环境判断代码,同时也能享受更好的生态,如 `vueuse` 的完整支持。 55 | 56 | ### 构建 57 | 58 | 目前 `@uni-helper/uni-use` 会使用 `unbuild` 将 `uni` API 之外的部分转译到 `ES2017`(即 `ES8`)。`uni` API 需要在项目构建时由 `uni-app` 官方提供的插件处理。 59 | 60 | 对于 `vite + vue3` 项目,请先设置 `build.target` 为 `ES6`。 61 | 62 | ```typescript 63 | import uni from '@dcloudio/vite-plugin-uni'; 64 | import { defineConfig } from 'vite'; 65 | 66 | // https://vitejs.dev/config/ 67 | export default defineConfig({ 68 | build: { 69 | target: 'es6', 70 | cssTarget: 'chrome61', // https://cn.vitejs.dev/config/build-options.html#build-csstarget 71 | }, 72 | optimizeDeps: { 73 | exclude: ['vue-demi'], 74 | }, 75 | plugins: [ 76 | // ..., 77 | uni(), 78 | // ..., 79 | ], 80 | }); 81 | ``` 82 | 83 | 然后在 `src/main.ts` 或 `src/main.js` 处自行添加 polyfill。以下是使用 [core-js](https://github.com/zloirock/core-js) 的示例(需要自行安装 `core-js`),你也可以使用 [es-shims](https://github.com/es-shims)。 84 | 85 | ```typescript 86 | import 'core-js/actual/array/iterator'; 87 | import 'core-js/actual/promise'; 88 | import 'core-js/actual/object/assign'; 89 | import 'core-js/actual/promise/finally'; 90 | // 你可以根据需要自行添加额外的 polyfills 91 | // import 'core-js/actual/object/values' 92 | import { createSSRApp } from 'vue'; 93 | import App from './App.vue'; 94 | 95 | export function createApp() { 96 | const app = createSSRApp(App); 97 | return { 98 | app, 99 | }; 100 | } 101 | ``` 102 | 103 | 微信小程序的 JavaScript 支持度见 [wechat-miniprogram/miniprogram-compat](https://github.com/wechat-miniprogram/miniprogram-compat)。微信小程序要支持 `vue3`,需设置基础库最低版本为 2.11.2 或以上,2.11.2 对应 `chrome>=53,ios>=10`。 104 | 105 | ### 和 `unplugin-auto-import` 结合使用 106 | 107 | ```typescript 108 | // vite.config.ts 109 | import { fileURLToPath } from 'node:url'; 110 | import uni from '@dcloudio/vite-plugin-uni'; 111 | import { uniuseAutoImports } from '@uni-helper/uni-use'; 112 | import autoImport from 'unplugin-auto-import/vite'; 113 | import { defineConfig } from 'vitest/config'; 114 | 115 | // https://vitejs.dev/config/ 116 | export default defineConfig({ 117 | plugins: [ 118 | autoImport({ 119 | imports: [ 120 | uniuseAutoImports(), 121 | ], 122 | }), 123 | uni({ /* ... */ }), 124 | ], 125 | }); 126 | ``` 127 | 128 | ### EventBus 129 | 130 | 如果你想使用 `EventBus`,请考虑使用 [VueUse - useEventBus](https://vueuse.org/core/useeventbus/#useeventbus)、[mitt](https://github.com/developit/mitt) 或 [nanoevents](https://github.com/ai/nanoevents)。这个库不再重复提供类似功能。 131 | 132 | ### TypeScript 133 | 134 | `@uni-helper/uni-use` 本身使用 [TypeScript](https://www.typescriptlang.org/) 开发,拥有类型提示。 135 | 136 | ## 资源 137 | 138 | - [改动日志](/CHANGELOG.md) 139 | 140 | ## 致谢 141 | 142 | - [vueuse](https://vueuse.org/) [#1073](https://github.com/vueuse/vueuse/pull/1073) 143 | - [taro-hooks](https://taro-hooks-innocces.vercel.app/) 144 | - [tob-use](https://tob-use.netlify.app/) 145 | -------------------------------------------------------------------------------- /build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild'; 2 | 3 | export default defineBuildConfig({ 4 | entries: ['./src/index'], 5 | clean: true, 6 | declaration: true, 7 | externals: [ 8 | '@dcloudio/uni-cli-shared', 9 | '@vue/runtime-core', 10 | ], 11 | rollup: { 12 | emitCJS: true, 13 | esbuild: { 14 | target: 'es2017', 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # 开始 2 | 3 | `uni-app (vue3)` 组合式工具集。要求 `node>=18`。 4 | 5 | ## 安装 6 | 7 | ```bash 8 | npm install @uni-helper/uni-use @vueuse/core@9 9 | ``` 10 | 11 | 如果你希望使用 `@vueuse/core` v10+,请参考 [uni-app#4604](https://github.com/dcloudio/uni-app/issues/4604), 自行提供 `polyfill` 或者参考使用 [`vite-plugin-uni-polyfill`](https://github.com/Ares-Chang/vite-plugin-uni-polyfill)。 12 | 13 | ::: details yarn v2 或以上 14 | 请参考 [文档](https://yarnpkg.com/configuration/yarnrc/#nodeLinker) 设置 `nodeLinker` 为 `node_modules`。 15 | ::: 16 | 17 | ::: details pnpm 18 | 请参考 [文档](https://pnpm.io/npmrc#shamefully-hoist) 设置 `shamefully-hoist` 为 `true`。 19 | ::: 20 | 21 | > 目前没有支持 uni_modules 的计划,但欢迎 PR 贡献。 22 | 23 | ## 使用 24 | 25 | ```ts 26 | import { tryOnLoad } from '@uni-helper/uni-use'; 27 | 28 | tryOnLoad(() => { 29 | console.log('onLoad'); 30 | }); 31 | ``` 32 | 33 | 其它详情请查看所有 [API](/apis.md)。 34 | 35 | ### 和 `unplugin-auto-import` 结合使用 36 | 37 | ```typescript 38 | // vite.config.ts 39 | import { fileURLToPath } from 'node:url'; 40 | import uni from '@dcloudio/vite-plugin-uni'; 41 | import { uniuseAutoImports } from '@uni-helper/uni-use'; 42 | import autoImport from 'unplugin-auto-import/vite'; 43 | import { defineConfig } from 'vitest/config'; 44 | 45 | // https://vitejs.dev/config/ 46 | export default defineConfig({ 47 | plugins: [ 48 | autoImport({ 49 | imports: [ 50 | uniuseAutoImports(), 51 | ], 52 | }), 53 | uni({ /* ... */ }), 54 | ], 55 | }); 56 | ``` 57 | 58 | ### TypeScript 59 | 60 | `@uni-helper/uni-use` 本身使用 [TypeScript](https://www.typescriptlang.org/) 开发,天然具有类型提示。 61 | 62 | ## 贡献 63 | 64 | 如果有新想法,热爱开源,欢迎 PR 贡献。 65 | 66 | ## 感谢 67 | 68 | 感谢以下项目提供的灵感及帮助。 69 | 70 | - [vueuse](https://vueuse.org/) and [#1073](https://github.com/vueuse/vueuse/pull/1073) 71 | - [taro-hooks](https://taro-hooks-innocces.vercel.app/) 72 | - [tob-use](https://tob-use.netlify.app/) 73 | -------------------------------------------------------------------------------- /docs/guide/notice.md: -------------------------------------------------------------------------------- 1 | # 注意事项 2 | 3 | ## 限制 4 | 5 | 在小程序和移动应用环境下有如下无法避开的限制: 6 | 7 | - 缺失某些全局变量(如 `window`、`navigator` 等) 8 | - 必须使用 `uni-app` 提供的 API 实现功能(如拦截器、存储等),API 不支持的也就无法支持,比如拦截同步 API、监听其它地方引起的剪切板变化等 9 | - 无法使用顶层 `await` 10 | 11 | 在开发网页时,建议直接使用 `vue`,避免过多的环境判断代码,同时也能享受更好的生态,如 `vueuse` 的完整支持。 12 | 13 | ## 构建 14 | 15 | 目前 `@uni-helper/uni-use` 会使用 `unbuild` 将 `uni` API 之外的部分转译到 `ES2017`(即 `ES8`)。`uni` API 需要在项目构建时由 `uni-app` 官方提供的插件处理。 16 | 17 | 对于 `vite + vue3` 项目,请先设置 `build.target` 为 `ES6`。 18 | 19 | ```typescript 20 | import uni from '@dcloudio/vite-plugin-uni'; 21 | import { defineConfig } from 'vite'; 22 | 23 | // https://vitejs.dev/config/ 24 | export default defineConfig({ 25 | build: { 26 | target: 'es6', 27 | cssTarget: 'chrome61', // https://cn.vitejs.dev/config/build-options.html#build-csstarget 28 | }, 29 | optimizeDeps: { 30 | exclude: ['vue-demi'], 31 | }, 32 | plugins: [ 33 | // ..., 34 | uni(), 35 | // ..., 36 | ], 37 | }); 38 | ``` 39 | 40 | 然后在 `src/main.ts` 或 `src/main.js` 处自行添加 polyfill。以下是使用 [core-js](https://github.com/zloirock/core-js) 的示例(需要自行安装 `core-js`),你也可以使用 [es-shims](https://github.com/es-shims)。 41 | 42 | ```typescript 43 | import 'core-js/actual/array/iterator'; 44 | import 'core-js/actual/promise'; 45 | import 'core-js/actual/object/assign'; 46 | import 'core-js/actual/promise/finally'; 47 | // 你可以根据需要自行添加额外的 polyfills 48 | // import 'core-js/actual/object/values' 49 | import { createSSRApp } from 'vue'; 50 | import App from './App.vue'; 51 | 52 | export function createApp() { 53 | const app = createSSRApp(App); 54 | return { 55 | app, 56 | }; 57 | } 58 | ``` 59 | 60 | 微信小程序的 JavaScript 支持度见 [wechat-miniprogram/miniprogram-compat](https://github.com/wechat-miniprogram/miniprogram-compat)。微信小程序要支持 `vue3`,需设置基础库最低版本为 2.11.2 或以上,2.11.2 对应 `chrome>=53,ios>=10`。 61 | 62 | ## EventBus 63 | 64 | 如果你想使用 `EventBus`,请考虑使用 [VueUse - useEventBus](https://vueuse.org/core/useeventbus/#useeventbus)、[mitt](https://github.com/developit/mitt) 或 [nanoevents](https://github.com/ai/nanoevents)。这个库不再重复提供类似功能。 65 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | hero: 5 | name: UniUse 6 | text: uni-app 组合式工具集 7 | tagline: 为 uni-app 量身打造的组合式工具集 8 | image: 9 | src: /logo.png 10 | alt: UniUse 11 | actions: 12 | - theme: brand 13 | text: 开始 14 | link: /guide/ 15 | - theme: alt 16 | text: API 集合 17 | link: /apis 18 | 19 | features: 20 | - icon: 🎛 21 | title: 功能丰富 22 | details: 提供了丰富的功能,封装了 uni-app 中常用的功能。 23 | - icon: 💚 24 | title: 开箱即用 25 | details: 开箱即用的支持了 uni-app Vue3 的 Composition API。 26 | - icon: 🦾 27 | title: 类型安全 28 | details: 所有函数都支持 TS 类型推导,无需手动标注类型 29 | --- 30 | 31 | 32 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config'; 2 | 3 | export default antfu( 4 | { }, 5 | { 6 | // setting 7 | languageOptions: { 8 | globals: { 9 | UniApp: 'readonly', 10 | uni: 'readonly', 11 | plus: 'readonly', 12 | process: 'readonly', 13 | browser: 'readonly', 14 | }, 15 | }, 16 | // style 17 | rules: { 18 | 'style/quote-props': ['error', 'as-needed'], 19 | 'style/semi': ['error', 'always'], 20 | 'style/max-statements-per-line': ['error', { max: 1 }], 21 | curly: ['warn', 'all'], 22 | 'style/member-delimiter-style': ['warn', { 23 | multiline: { delimiter: 'semi', requireLast: true }, 24 | singleline: { delimiter: 'semi', requireLast: false }, 25 | multilineDetection: 'brackets', 26 | }], 27 | }, 28 | }, 29 | { 30 | rules: { 31 | 'no-console': 'off', 32 | }, 33 | }, 34 | { 35 | files: ['**/manifest.json'], 36 | rules: { 37 | indent: ['error', 4], 38 | 'jsonc/indent': ['error', 4], 39 | }, 40 | }, 41 | ); 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uni-helper/uni-use", 3 | "type": "module", 4 | "version": "0.19.14", 5 | "packageManager": "pnpm@8.14.1", 6 | "description": "uni-app (vue3) 组合式工具集", 7 | "author": { 8 | "name": "ModyQyW", 9 | "email": "wurui-dev@foxmail.com", 10 | "url": "https://modyqyw.github.io" 11 | }, 12 | "license": "MIT", 13 | "homepage": "https://github.com/uni-helper/uni-use#readme", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/uni-helper/uni-use.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/uni-helper/uni-use/issues" 20 | }, 21 | "keywords": [ 22 | "uni-app", 23 | "uniapp", 24 | "uni", 25 | "use", 26 | "composition", 27 | "composable" 28 | ], 29 | "exports": { 30 | ".": { 31 | "import": { 32 | "types": "./dist/index.d.mts", 33 | "default": "./dist/index.mjs" 34 | }, 35 | "require": { 36 | "types": "./dist/index.d.cts", 37 | "default": "./dist/index.cjs" 38 | } 39 | } 40 | }, 41 | "main": "./dist/index.cjs", 42 | "module": "./dist/index.mjs", 43 | "types": "./dist/index.d.ts", 44 | "files": [ 45 | "dist" 46 | ], 47 | "engines": { 48 | "node": ">=18" 49 | }, 50 | "scripts": { 51 | "build": "unbuild", 52 | "check:deps": "taze -f", 53 | "check:types": "tsc --noEmit", 54 | "commit": "git-cz", 55 | "dev": "unbuild --stub", 56 | "lint": "pnpm build && conc \"pnpm:check:types\" \"pnpm:lint:eslint\" \"pnpm:lint:publint\"", 57 | "lint:eslint": "eslint . --fix --cache", 58 | "lint:publint": "publint", 59 | "prepare": "is-ci || simple-git-hooks", 60 | "prepublishOnly": "pnpm run build", 61 | "release": "pnpm install && pnpm run lint && bumpp", 62 | "test": "vitest", 63 | "docs:dev": "vitepress dev", 64 | "docs:build": "vitepress build", 65 | "docs:preview": "vitepress preview" 66 | }, 67 | "peerDependencies": { 68 | "@vueuse/core": "^9.0.0 || ^10.0.0", 69 | "typescript": "^4.5.0 || ^5.0.0", 70 | "vue": "^3.2.47" 71 | }, 72 | "peerDependenciesMeta": { 73 | "typescript": { 74 | "optional": true 75 | } 76 | }, 77 | "dependencies": { 78 | "@dcloudio/types": "^3.4.7", 79 | "@dcloudio/uni-app": "^3.0.0-3090920231225001" 80 | }, 81 | "devDependencies": { 82 | "@antfu/eslint-config": "^3.8.0", 83 | "@commitlint/cli": "^18.4.4", 84 | "@commitlint/config-conventional": "^18.4.4", 85 | "@commitlint/prompt": "^18.4.4", 86 | "@dcloudio/uni-cli-shared": "^3.0.0-3090920231225001", 87 | "@dcloudio/vite-plugin-uni": "3.0.0-3090920231225001", 88 | "@tsconfig/node18": "^18.2.2", 89 | "@types/node": "^20.11.0", 90 | "@typescript-eslint/eslint-plugin": "^8.9.0", 91 | "@typescript-eslint/parser": "^8.9.0", 92 | "@vueuse/core": "^9.13.0", 93 | "bumpp": "^9.2.1", 94 | "commitizen": "^4.3.0", 95 | "concurrently": "^8.2.2", 96 | "eslint": "^9.12.0", 97 | "fast-glob": "^3.3.2", 98 | "is-ci": "^3.0.1", 99 | "lint-staged": "^15.2.0", 100 | "prettier": "^3.2.2", 101 | "publint": "^0.2.7", 102 | "simple-git-hooks": "^2.9.0", 103 | "taze": "^0.13.1", 104 | "ts-node": "^10.9.2", 105 | "tsx": "^4.7.0", 106 | "typescript": "^5.3.3", 107 | "unbuild": "^2.0.0", 108 | "vite": "^4.0.0", 109 | "vitepress": "^1.4.0", 110 | "vitest": "^1.4.0" 111 | }, 112 | "publishConfig": { 113 | "access": "public", 114 | "registry": "https://registry.npmjs.org/" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uni-preset-vue", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "build:app": "uni build -p app", 6 | "build:app-android": "uni build -p app-android", 7 | "build:app-ios": "uni build -p app-ios", 8 | "build:custom": "uni build -p", 9 | "build:h5": "uni build", 10 | "build:h5:ssr": "uni build --ssr", 11 | "build:mp-alipay": "uni build -p mp-alipay", 12 | "build:mp-baidu": "uni build -p mp-baidu", 13 | "build:mp-kuaishou": "uni build -p mp-kuaishou", 14 | "build:mp-lark": "uni build -p mp-lark", 15 | "build:mp-qq": "uni build -p mp-qq", 16 | "build:mp-toutiao": "uni build -p mp-toutiao", 17 | "build:mp-weixin": "uni build -p mp-weixin", 18 | "build:quickapp-webview": "uni build -p quickapp-webview", 19 | "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei", 20 | "build:quickapp-webview-union": "uni build -p quickapp-webview-union", 21 | "dev:app": "uni -p app", 22 | "dev:app-android": "uni -p app-android", 23 | "dev:app-ios": "uni -p app-ios", 24 | "dev:custom": "uni -p", 25 | "dev:h5": "uni", 26 | "dev:h5:ssr": "uni --ssr", 27 | "dev:mp-alipay": "uni -p mp-alipay", 28 | "dev:mp-baidu": "uni -p mp-baidu", 29 | "dev:mp-kuaishou": "uni -p mp-kuaishou", 30 | "dev:mp-lark": "uni -p mp-lark", 31 | "dev:mp-qq": "uni -p mp-qq", 32 | "dev:mp-toutiao": "uni -p mp-toutiao", 33 | "dev:mp-weixin": "uni -p mp-weixin", 34 | "dev:quickapp-webview": "uni -p quickapp-webview", 35 | "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei", 36 | "dev:quickapp-webview-union": "uni -p quickapp-webview-union" 37 | }, 38 | "dependencies": { 39 | "@dcloudio/uni-app": "3.0.0-3090920231225001", 40 | "@dcloudio/uni-app-plus": "3.0.0-3090920231225001", 41 | "@dcloudio/uni-components": "3.0.0-3090920231225001", 42 | "@dcloudio/uni-h5": "3.0.0-3090920231225001", 43 | "@dcloudio/uni-mp-alipay": "3.0.0-3090920231225001", 44 | "@dcloudio/uni-mp-baidu": "3.0.0-3090920231225001", 45 | "@dcloudio/uni-mp-kuaishou": "3.0.0-3090920231225001", 46 | "@dcloudio/uni-mp-lark": "3.0.0-3090920231225001", 47 | "@dcloudio/uni-mp-qq": "3.0.0-3090920231225001", 48 | "@dcloudio/uni-mp-toutiao": "3.0.0-3090920231225001", 49 | "@dcloudio/uni-mp-weixin": "3.0.0-3090920231225001", 50 | "@dcloudio/uni-quickapp-webview": "3.0.0-3090920231225001", 51 | "@uni-helper/uni-use": "workspace:*", 52 | "vue": "3.2.47", 53 | "vue-i18n": "9.9.0" 54 | }, 55 | "devDependencies": { 56 | "@dcloudio/types": "3.4.7", 57 | "@dcloudio/uni-automator": "3.0.0-3090920231225001", 58 | "@dcloudio/uni-cli-shared": "3.0.0-3090920231225001", 59 | "@dcloudio/uni-stacktracey": "3.0.0-3090920231225001", 60 | "@dcloudio/vite-plugin-uni": "3.0.0-3090920231225001", 61 | "typescript": "5.3.3", 62 | "vite": "4.5.1" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /playground/src/App.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createSSRApp } from 'vue'; 2 | import App from './App.vue'; 3 | 4 | export function createApp() { 5 | const app = createSSRApp(App); 6 | return { 7 | app, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /playground/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "appid": "", 4 | "description": "", 5 | "versionName": "1.0.0", 6 | "versionCode": "100", 7 | "transformPx": false, 8 | /* 5+App特有相关 */ 9 | "app-plus": { 10 | "usingComponents": true, 11 | "nvueStyleCompiler": "uni-app", 12 | "compilerVersion": 3, 13 | "splashscreen": { 14 | "alwaysShowBeforeRender": true, 15 | "waiting": true, 16 | "autoclose": true, 17 | "delay": 0 18 | }, 19 | /* 模块配置 */ 20 | "modules": {}, 21 | /* 应用发布信息 */ 22 | "distribute": { 23 | /* android打包配置 */ 24 | "android": { 25 | "permissions": [ 26 | "", 27 | "", 28 | "", 29 | "", 30 | "", 31 | "", 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "", 38 | "", 39 | "", 40 | "" 41 | ] 42 | }, 43 | /* ios打包配置 */ 44 | "ios": {}, 45 | /* SDK配置 */ 46 | "sdkConfigs": {} 47 | } 48 | }, 49 | /* 快应用特有相关 */ 50 | "quickapp": {}, 51 | /* 小程序特有相关 */ 52 | "mp-weixin": { 53 | "appid": "", 54 | "setting": { 55 | "urlCheck": false 56 | }, 57 | "usingComponents": true 58 | }, 59 | "mp-alipay": { 60 | "usingComponents": true 61 | }, 62 | "mp-baidu": { 63 | "usingComponents": true 64 | }, 65 | "mp-toutiao": { 66 | "usingComponents": true 67 | }, 68 | "uniStatistics": { 69 | "enable": false 70 | }, 71 | "vueVersion": "3" 72 | } 73 | -------------------------------------------------------------------------------- /playground/src/pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ // pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages 3 | { 4 | "path": "pages/index/index", 5 | "style": { 6 | "navigationBarTitleText": "uni-app" 7 | } 8 | } 9 | ], 10 | "globalStyle": { 11 | "navigationBarTextStyle": "black", 12 | "navigationBarTitleText": "uni-app", 13 | "navigationBarBackgroundColor": "#F8F8F8", 14 | "backgroundColor": "#F8F8F8" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /playground/src/pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 58 | -------------------------------------------------------------------------------- /playground/src/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uni-helper/uni-use/40b4fccbb18b89fdd661d88c52c6f6b40845815f/playground/src/static/logo.png -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "jsx": "preserve", 5 | "lib": ["esnext", "dom"], 6 | "useDefineForClassFields": true, 7 | "module": "esnext", 8 | "moduleResolution": "node", 9 | "paths": { 10 | "@/*": ["./src/*"] 11 | }, 12 | "resolveJsonModule": true, 13 | "types": ["vite/client", "@dcloudio/types"], 14 | "strict": true, 15 | "sourceMap": true, 16 | "esModuleInterop": true 17 | }, 18 | "vueCompilerOptions": { 19 | "experimentalRuntimeMode": "runtime-uni-app", 20 | "nativeTags": ["block", "component", "template", "slot"] 21 | }, 22 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 23 | } 24 | -------------------------------------------------------------------------------- /playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url'; 2 | import uni from '@dcloudio/vite-plugin-uni'; 3 | import { defineConfig } from 'vite'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [uni()], 8 | resolve: { 9 | alias: { 10 | '@': fileURLToPath(new URL('src', import.meta.url)), 11 | }, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - playground 3 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uni-helper/uni-use/40b4fccbb18b89fdd661d88c52c6f6b40845815f/public/logo.png -------------------------------------------------------------------------------- /src/exports.ts: -------------------------------------------------------------------------------- 1 | export * from './tryOnBackPress'; 2 | export * from './tryOnHide'; 3 | export * from './tryOnInit'; 4 | export * from './tryOnLoad'; 5 | export * from './tryOnReady'; 6 | export * from './tryOnScopeDispose'; 7 | export * from './tryOnShow'; 8 | export * from './tryOnUnload'; 9 | export * from './useActionSheet'; 10 | export * from './useClipboardData'; 11 | export * from './useDownloadFile'; 12 | export * from './useGlobalData'; 13 | export * from './useInterceptor'; 14 | export * from './useLoading'; 15 | export * from './useModal'; 16 | export * from './useNetwork'; 17 | export * from './useOnline'; 18 | export * from './usePage'; 19 | export * from './usePages'; 20 | export * from './usePageScroll'; 21 | export * from './usePreferredDark'; 22 | export * from './usePreferredLanguage'; 23 | export * from './usePrevPage'; 24 | export * from './usePrevRoute'; 25 | export * from './useProvider'; 26 | export * from './useRequest'; 27 | export * from './useRoute'; 28 | export * from './useRouter'; 29 | export * from './useScanCode'; 30 | export * from './useScreenBrightness'; 31 | export * from './useSelectorQuery'; 32 | export * from './useSocket'; 33 | export * from './useStorage'; 34 | export * from './useStorageAsync'; 35 | export * from './useStorageSync'; 36 | export * from './useToast'; 37 | export * from './useUploadFile'; 38 | export * from './useVisible'; 39 | -------------------------------------------------------------------------------- /src/index.md: -------------------------------------------------------------------------------- 1 | # 函数列表 2 | 3 | - [tryOnBackPress](./tryOnBackPress/index.md) 4 | - [tryOnHide](./tryOnHide/index.md) 5 | - [tryOnInit](./tryOnInit/index.md) 6 | - [tryOnLoad](./tryOnLoad/index.md) 7 | - [tryOnReady](./tryOnReady/index.md) 8 | - [tryOnScopeDispose](./tryOnScopeDispose/index.md) 9 | - [tryOnShow](./tryOnShow/index.md) 10 | - [tryOnUnload](./tryOnUnload/index.md) 11 | - [useActionSheet](./useActionSheet/index.md) 12 | - [useClipboardData](./useClipboardData/index.md) 13 | - [useDownloadFile](./useDownloadFile/index.md) 14 | - [useGlobalData](./useGlobalData/index.md) 15 | - [useInterceptor](./useInterceptor/index.md) 16 | - [useLoading](./useLoading/index.md) 17 | - [useModal](./useModal/index.md) 18 | - [useNetwork](./useNetwork/index.md) 19 | - [useOnline](./useOnline/index.md) 20 | - [usePage](./usePage/index.md) 21 | - [usePages](./usePages/index.md) 22 | - [usePageScroll](./usePageScroll/index.md) 23 | - [usePreferredDark](./usePreferredDark/index.md) 24 | - [usePreferredLanguage](./usePreferredLanguage/index.md) 25 | - [usePrevPage](./usePrevPage/index.md) 26 | - [usePrevRoute](./usePrevRoute/index.md) 27 | - [useProvider](./useProvider/index.md) 28 | - [useRequest](./useRequest/index.md) 29 | - [useRoute](./useRoute/index.md) 30 | - [useRouter](./useRouter/index.md) 31 | - [useScanCode](./useScanCode/index.md) 32 | - [useScreenBrightness](./useScreenBrightness/index.md) 33 | - [useSelectorQuery](./useSelectorQuery/index.md) 34 | - [useSocket](./useSocket/index.md) 35 | - [useStorage](./useStorage/index.md) 36 | - [useStorageSync](./useStorageSync/index.md) 37 | - [useToast](./useToast/index.md) 38 | - [useUploadFile](./useUploadFile/index.md) 39 | - [useVisible](./useVisible/index.md) 40 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { name } from '../package.json'; 2 | import * as UniUse from './exports'; 3 | 4 | export * from './exports'; 5 | 6 | /** @deprecated 建议使用 `uniuseAutoImports` 函数 */ 7 | export const UniUseAutoImports: Record> = { 8 | [name]: Object.keys(UniUse), 9 | }; 10 | 11 | export type UniUseFunction = keyof typeof UniUse; 12 | 13 | export interface UniUseAutoImportsOptions { 14 | only?: UniUseFunction[]; 15 | except?: UniUseFunction[]; 16 | } 17 | 18 | /** 自定义配置 unplugin-auto-import */ 19 | export function uniuseAutoImports(options: UniUseAutoImportsOptions = {}) { 20 | let exports = Object.keys(UniUse); 21 | 22 | if (options.only) { 23 | exports = exports.filter(fn => (options.only as string[])!.includes(fn)); 24 | } 25 | 26 | if (options.except) { 27 | exports = exports.filter(fn => !(options.except as string[])!.includes(fn)); 28 | } 29 | 30 | return { 31 | [name]: exports, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/tryOnBackPress/index.md: -------------------------------------------------------------------------------- 1 | # tryOnBackPress 2 | 3 | 尝试执行 onBackPress。如果是在组件生命周期内,就直接调用 `onBackPress`;如果不是,就根据设定间隔重新尝试。 4 | 5 | 前两个参数和 `onBackPress` 完全一致。 6 | 7 | ```typescript 8 | import { tryOnBackPress } from '@uni-helper/uni-use'; 9 | 10 | tryOnBackPress((e) => { 11 | if (e.from === 'navigateBack') { 12 | // do somthing 13 | } 14 | 15 | if (e.from === 'backbutton') { 16 | // do something 17 | } 18 | }, null, { 19 | retry: 5, // optional 20 | interval: 100, // optional 21 | }); 22 | ``` 23 | -------------------------------------------------------------------------------- /src/tryOnBackPress/index.ts: -------------------------------------------------------------------------------- 1 | import { onBackPress } from '@dcloudio/uni-app'; 2 | import { getCurrentInstance } from 'vue'; 3 | import { sleep } from '../utils'; 4 | 5 | export interface TryOnBackPressOptions { 6 | /** 7 | * 最大尝试次数 8 | * 9 | * @default 3 10 | */ 11 | retry?: number; 12 | /** 13 | * 尝试间隔时长,单位 ms 14 | * 15 | * @default 500 16 | */ 17 | interval?: number; 18 | } 19 | 20 | type OnBackPressParameters = Parameters; 21 | 22 | /** 尝试绑定 onBackPress 超出尝试次数将调用 onError */ 23 | export async function tryOnBackPress( 24 | hook: OnBackPressParameters[0], 25 | target?: OnBackPressParameters[1], 26 | options: TryOnBackPressOptions = {}, 27 | ) { 28 | const { 29 | retry = 3, 30 | interval = 500, 31 | } = options; 32 | 33 | function tryBind() { 34 | const instance = (target || getCurrentInstance()) as OnBackPressParameters[1] | undefined; 35 | if (instance) { 36 | onBackPress(hook, instance); 37 | return true; 38 | } 39 | 40 | return false; 41 | } 42 | 43 | for (let circle = 1; circle <= retry; circle++) { 44 | if (tryBind()) { 45 | return; 46 | } 47 | await sleep(interval); 48 | } 49 | 50 | throw new Error('Binding onBackPress failed, maximum number of attempts exceeded.'); 51 | } 52 | -------------------------------------------------------------------------------- /src/tryOnHide/index.md: -------------------------------------------------------------------------------- 1 | # tryOnHide 2 | 3 | 尝试获取组件生命周期,并调用 `onHide` 4 | 5 | 超过重试次数,根据 `runFinally` 直接执行或抛出异常 6 | 7 | ```typescript 8 | import { tryOnHide } from '@uni-helper/uni-use'; 9 | 10 | tryOnHide(() => { 11 | // ... 12 | }); 13 | ``` 14 | -------------------------------------------------------------------------------- /src/tryOnHide/index.ts: -------------------------------------------------------------------------------- 1 | import type { TryOptions } from '../types'; 2 | import { onHide } from '@dcloudio/uni-app'; 3 | import { getCurrentInstance } from 'vue'; 4 | import { sleep } from '../utils'; 5 | 6 | type OnHideParameters = Parameters; 7 | 8 | export type TryOnHideOptions = TryOptions; 9 | 10 | /** 11 | * 尝试获取组件生命周期,并调用 onHide 12 | * 13 | * 超过重试次数,根据 runFinally 直接执行或抛出异常 14 | */ 15 | export async function tryOnHide( 16 | hook: OnHideParameters[0], 17 | target?: OnHideParameters[1], 18 | options: TryOnHideOptions = {}, 19 | ) { 20 | const { 21 | retry = 3, 22 | interval = 500, 23 | runFinally = true, 24 | } = options; 25 | 26 | function tryBind() { 27 | const instance = (target || getCurrentInstance()) as OnHideParameters[1] | undefined; 28 | if (instance) { 29 | onHide(hook, instance); 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | for (let circle = 1; circle <= retry; circle++) { 36 | if (tryBind()) { 37 | return; 38 | } 39 | await sleep(interval); 40 | } 41 | 42 | if (runFinally) { 43 | return onHide(hook); 44 | } 45 | 46 | throw new Error('Binding onHide failed, maximum number of attempts exceeded.'); 47 | } 48 | -------------------------------------------------------------------------------- /src/tryOnInit/index.md: -------------------------------------------------------------------------------- 1 | # tryOnInit 2 | 3 | 尝试获取组件生命周期,并调用 `onInit` 4 | 5 | 超过重试次数,根据 `runFinally` 直接执行或抛出异常 6 | 7 | ```typescript 8 | import { tryOnInit } from '@uni-helper/uni-use'; 9 | 10 | tryOnInit(() => { 11 | // ... 12 | }); 13 | ``` 14 | -------------------------------------------------------------------------------- /src/tryOnInit/index.ts: -------------------------------------------------------------------------------- 1 | import type { TryOptions } from '../types'; 2 | import { onInit } from '@dcloudio/uni-app'; 3 | import { getCurrentInstance } from 'vue'; 4 | import { sleep } from '../utils'; 5 | 6 | type OnInitParameters = Parameters; 7 | 8 | export type TryOnInitOptions = TryOptions; 9 | 10 | /** 11 | * 尝试获取组件生命周期,并调用 onInit 12 | * 13 | * 超过重试次数,根据 runFinally 直接执行或抛出异常 14 | */ 15 | export async function tryOnInit( 16 | hook: OnInitParameters[0], 17 | target?: OnInitParameters[1], 18 | options: TryOnInitOptions = {}, 19 | ) { 20 | const { 21 | retry = 3, 22 | interval = 500, 23 | runFinally = true, 24 | } = options; 25 | 26 | function tryBind() { 27 | const instance = (target || getCurrentInstance()) as OnInitParameters[1] | undefined; 28 | if (instance) { 29 | onInit(hook, instance); 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | for (let circle = 1; circle <= retry; circle++) { 36 | if (tryBind()) { 37 | return; 38 | } 39 | await sleep(interval); 40 | } 41 | 42 | if (runFinally) { 43 | return onInit(hook); 44 | } 45 | 46 | throw new Error('Binding onInit failed, maximum number of attempts exceeded.'); 47 | } 48 | -------------------------------------------------------------------------------- /src/tryOnLoad/index.md: -------------------------------------------------------------------------------- 1 | # tryOnLoad 2 | 3 | 尝试获取组件生命周期,并调用 `onLoad` 4 | 5 | 超过重试次数,根据 `runFinally` 直接执行或抛出异常 6 | 7 | ```typescript 8 | import { tryOnLoad } from '@uni-helper/uni-use'; 9 | 10 | tryOnLoad(() => { 11 | // ... 12 | }); 13 | ``` 14 | -------------------------------------------------------------------------------- /src/tryOnLoad/index.ts: -------------------------------------------------------------------------------- 1 | import type { TryOptions } from '../types'; 2 | import { onLoad } from '@dcloudio/uni-app'; 3 | import { getCurrentInstance } from 'vue'; 4 | import { sleep } from '../utils'; 5 | 6 | type OnLoadParameters = Parameters; 7 | 8 | export type TryOnLoadOptions = TryOptions; 9 | 10 | /** 11 | * 尝试获取组件生命周期,并调用 onLoad 12 | * 13 | * 超过重试次数,根据 runFinally 直接执行或抛出异常 14 | */ 15 | export async function tryOnLoad( 16 | hook: OnLoadParameters[0], 17 | target?: OnLoadParameters[1], 18 | options: TryOnLoadOptions = {}, 19 | ) { 20 | const { 21 | retry = 3, 22 | interval = 500, 23 | runFinally = true, 24 | } = options; 25 | 26 | function tryBind() { 27 | const instance = (target || getCurrentInstance()) as OnLoadParameters[1] | undefined; 28 | if (instance) { 29 | onLoad(hook, instance); 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | for (let circle = 1; circle <= retry; circle++) { 36 | if (tryBind()) { 37 | return; 38 | } 39 | await sleep(interval); 40 | } 41 | 42 | if (runFinally) { 43 | return onLoad(hook); 44 | } 45 | 46 | throw new Error('Binding onLoad failed, maximum number of attempts exceeded.'); 47 | } 48 | -------------------------------------------------------------------------------- /src/tryOnReady/index.md: -------------------------------------------------------------------------------- 1 | # tryOnReady 2 | 3 | 尝试获取组件生命周期,并调用 `onReady` 4 | 5 | 超过重试次数,根据 `runFinally` 直接执行或抛出异常 6 | 7 | ```typescript 8 | import { tryOnReady } from '@uni-helper/uni-use'; 9 | 10 | tryOnReady(() => { 11 | // ... 12 | }); 13 | ``` 14 | -------------------------------------------------------------------------------- /src/tryOnReady/index.ts: -------------------------------------------------------------------------------- 1 | import type { TryOptions } from '../types'; 2 | import { onReady } from '@dcloudio/uni-app'; 3 | import { getCurrentInstance } from 'vue'; 4 | import { sleep } from '../utils'; 5 | 6 | type OnReadyParameters = Parameters; 7 | 8 | export type TryOnReadyOptions = TryOptions; 9 | 10 | /** 11 | * 尝试获取组件生命周期,并调用 onReady 12 | * 13 | * 超过重试次数,根据 runFinally 直接执行或抛出异常 14 | */ 15 | export async function tryOnReady( 16 | hook: OnReadyParameters[0], 17 | target?: OnReadyParameters[1], 18 | options: TryOnReadyOptions = {}, 19 | ) { 20 | const { 21 | retry = 3, 22 | interval = 500, 23 | runFinally = true, 24 | } = options; 25 | 26 | function tryBind() { 27 | const instance = (target || getCurrentInstance()) as OnReadyParameters[1] | undefined; 28 | if (instance) { 29 | onReady(hook, instance); 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | for (let circle = 1; circle <= retry; circle++) { 36 | if (tryBind()) { 37 | return; 38 | } 39 | await sleep(interval); 40 | } 41 | 42 | if (runFinally) { 43 | return onReady(hook); 44 | } 45 | 46 | throw new Error('Binding onReady failed, maximum number of attempts exceeded.'); 47 | } 48 | -------------------------------------------------------------------------------- /src/tryOnScopeDispose/index.md: -------------------------------------------------------------------------------- 1 | # tryOnScopeDispose 2 | 3 | Safe `onScopeDispose`. Call `onScopeDispose()` if it's inside an effect scope lifecycle, if not, do nothing 4 | 5 | ## Usage 6 | 7 | ```js 8 | import { tryOnScopeDispose } from '@uni-helper/uni-use'; 9 | 10 | tryOnScopeDispose(() => { 11 | 12 | }); 13 | ``` 14 | -------------------------------------------------------------------------------- /src/tryOnScopeDispose/index.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentScope, onScopeDispose } from 'vue'; 2 | 3 | /** 4 | * Call onScopeDispose() if it's inside an effect scope lifecycle, if not, do nothing 5 | * 6 | * @param fn 7 | */ 8 | export function tryOnScopeDispose(fn: () => void) { 9 | if (getCurrentScope()) { 10 | onScopeDispose(fn); 11 | return true; 12 | } 13 | return false; 14 | } 15 | -------------------------------------------------------------------------------- /src/tryOnShow/index.md: -------------------------------------------------------------------------------- 1 | # tryOnShow 2 | 3 | 尝试获取组件生命周期,并调用 `onShow` 4 | 5 | 超过重试次数,根据 `runFinally` 直接执行或抛出异常 6 | 7 | ```typescript 8 | import { tryOnShow } from '@uni-helper/uni-use'; 9 | 10 | tryOnShow(() => { 11 | // ... 12 | }); 13 | ``` 14 | -------------------------------------------------------------------------------- /src/tryOnShow/index.ts: -------------------------------------------------------------------------------- 1 | import type { TryOptions } from '../types'; 2 | import { onShow } from '@dcloudio/uni-app'; 3 | import { getCurrentInstance } from 'vue'; 4 | import { sleep } from '../utils'; 5 | 6 | type OnShowParameters = Parameters; 7 | 8 | export type TryOnShowOptions = TryOptions; 9 | 10 | /** 11 | * 尝试获取组件生命周期,并调用 onShow 12 | * 13 | * 超过重试次数,根据 runFinally 直接执行或抛出异常 14 | */ 15 | export async function tryOnShow( 16 | hook: OnShowParameters[0], 17 | target?: OnShowParameters[1], 18 | options: TryOnShowOptions = {}, 19 | ) { 20 | const { 21 | retry = 3, 22 | interval = 500, 23 | runFinally = true, 24 | } = options; 25 | 26 | function tryBind() { 27 | const instance = (target || getCurrentInstance()) as OnShowParameters[1] | undefined; 28 | if (instance) { 29 | onShow(hook, instance); 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | for (let circle = 1; circle <= retry; circle++) { 36 | if (tryBind()) { 37 | return; 38 | } 39 | await sleep(interval); 40 | } 41 | 42 | if (runFinally) { 43 | return onShow(hook); 44 | } 45 | 46 | throw new Error('Binding onShow failed, maximum number of attempts exceeded.'); 47 | } 48 | -------------------------------------------------------------------------------- /src/tryOnUnload/index.md: -------------------------------------------------------------------------------- 1 | # tryOnUnload 2 | 3 | 尝试获取组件生命周期,并调用 `onUnload` 4 | 5 | 超过重试次数,根据 `runFinally` 直接执行或抛出异常 6 | 7 | ```typescript 8 | import { tryOnUnload } from '@uni-helper/uni-use'; 9 | 10 | tryOnUnload(() => { 11 | // ... 12 | }); 13 | ``` 14 | -------------------------------------------------------------------------------- /src/tryOnUnload/index.ts: -------------------------------------------------------------------------------- 1 | import type { TryOptions } from '../types'; 2 | import { onUnload } from '@dcloudio/uni-app'; 3 | import { getCurrentInstance } from 'vue'; 4 | import { sleep } from '../utils'; 5 | 6 | type OnUnloadParameters = Parameters; 7 | 8 | export type TryOnUnloadOptions = TryOptions; 9 | 10 | /** 11 | * 尝试获取组件生命周期,并调用 onUnload 12 | * 13 | * 超过重试次数,根据 runFinally 直接执行或抛出异常 14 | */ 15 | export async function tryOnUnload( 16 | hook: OnUnloadParameters[0], 17 | target?: OnUnloadParameters[1], 18 | options: TryOnUnloadOptions = {}, 19 | ) { 20 | const { 21 | retry = 3, 22 | interval = 500, 23 | runFinally = true, 24 | } = options; 25 | 26 | function tryBind() { 27 | const instance = (target || getCurrentInstance()) as OnUnloadParameters[1] | undefined; 28 | if (instance) { 29 | onUnload(hook, instance); 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | for (let circle = 1; circle <= retry; circle++) { 36 | if (tryBind()) { 37 | return; 38 | } 39 | await sleep(interval); 40 | } 41 | 42 | if (runFinally) { 43 | return onUnload(hook); 44 | } 45 | 46 | throw new Error('Binding onUnload failed, maximum number of attempts exceeded.'); 47 | } 48 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | 3 | export type AnyRecord = Record; 4 | 5 | export type MaybeRef = T | Ref; 6 | 7 | export type MaybeRefOrGetter = MaybeRef | (() => T); 8 | 9 | export type MaybeComputedRef = MaybeRefOrGetter; 10 | 11 | export type Optional = Pick, K> & Omit; 12 | 13 | export type RequiredProperty = T & { [P in K]-?: T[P] }; 14 | 15 | export type RequiredOnly = RequiredProperty, K>; 16 | 17 | export type MaybePromise = T | Promise; 18 | 19 | export interface TryOptions { 20 | /** 21 | * 最大尝试次数 22 | * 23 | * @default 3 24 | */ 25 | retry?: number; 26 | /** 27 | * 尝试间隔时长,单位 ms 28 | * 29 | * @default 500 30 | */ 31 | interval?: number; 32 | 33 | /** 34 | * 当超时时是否立即执行, 值为false时将在最后无法运行时抛出异常 35 | * 36 | * @default true 37 | */ 38 | runFinally?: boolean; 39 | } 40 | -------------------------------------------------------------------------------- /src/useActionSheet/index.md: -------------------------------------------------------------------------------- 1 | # useActionSheet 2 | 3 | 返回一个方法,调用后从底部向上弹出操作菜单。 4 | 5 | ```typescript 6 | import { useActionSheet } from '@uni-helper/uni-use'; 7 | 8 | const showActionSheet = useActionSheet({ 9 | /* 传入配置 */ 10 | }); 11 | showActionSheet(); // 从底部向上弹出操作菜单 12 | ``` 13 | 14 | 调用方法时,可以传入一个对象来更新已有配置,这样会使用 [扩展运算符](https://es6.ruanyifeng.com/#docs/object#%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%89%A9%E5%B1%95%E8%BF%90%E7%AE%97%E7%AC%A6) 来确认最终配置。 15 | 16 | ```typescript 17 | showActionSheet({ 18 | /* 新传入配置 */ 19 | }); 20 | ``` 21 | -------------------------------------------------------------------------------- /src/useActionSheet/index.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeComputedRef } from '../types'; 2 | import { resolveUnref } from '@vueuse/core'; 3 | import { reactive } from 'vue'; 4 | 5 | export interface UniShowActionSheetOptions extends Omit { 6 | /** 文字数组 */ 7 | itemList: string[]; 8 | } 9 | export type ShowActionSheetOptions = MaybeComputedRef; 10 | export type UseActionSheetOptions = ShowActionSheetOptions; 11 | 12 | /** 13 | * 返回一个方法,调用后从底部向上弹出操作菜单 14 | * 15 | * https://uniapp.dcloud.net.cn/api/ui/prompt.html#showactionsheet 16 | */ 17 | export function useActionSheet(options?: UseActionSheetOptions) { 18 | /** 19 | * 从底部向上弹出操作菜单 20 | * 21 | * https://uniapp.dcloud.net.cn/api/ui/prompt.html#showactionsheet 22 | */ 23 | return function showActionSheet(newOptions?: ShowActionSheetOptions) { 24 | return uni.showActionSheet( 25 | reactive({ 26 | itemList: [], 27 | ...resolveUnref(options), 28 | ...resolveUnref(newOptions), 29 | }), 30 | ); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/useClipboardData/index.md: -------------------------------------------------------------------------------- 1 | # useClipboardData 2 | 3 | 获取和设置剪切板数据。你需要将默认值作为第一个参数传入。 4 | 5 | ```typescript 6 | import { useClipboardData } from '@uni-helper/uni-use'; 7 | 8 | const clipboardData = useClipboardData(''); 9 | 10 | // 查看剪切板数据 11 | console.log('clipboardData', clipboardData.value); 12 | // 设置剪切板数据 13 | clipboardData.value = 'abc'; 14 | ``` 15 | 16 | 为了在操作数据后不显示消息提示框,你可以传递第二个参数。 17 | 18 | ```typescript 19 | import { useClipboardData } from '@uni-helper/uni-use'; 20 | 21 | const clipboardData = useClipboardData('', { showToast: false }); 22 | ``` 23 | 24 | 默认使用 `console.error` 输出错误信息,你也可以自定义错误处理。 25 | 26 | ```typescript 27 | import { useClipboardData } from '@uni-helper/uni-use'; 28 | 29 | const clipboardData = useClipboardData('', { 30 | onError: (error) => { 31 | console.log(error); 32 | } 33 | }); 34 | ``` 35 | -------------------------------------------------------------------------------- /src/useClipboardData/index.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigurableEventFilter, ConfigurableFlush } from '@vueuse/core'; 2 | import type { Ref } from 'vue'; 3 | import type { MaybeComputedRef } from '../types'; 4 | import { watchWithFilter } from '@vueuse/core'; 5 | import { ref } from 'vue'; 6 | import { useInterceptor } from '../useInterceptor'; 7 | 8 | function getClipboardData(showToast = true) { 9 | return new Promise((resolve, reject) => { 10 | uni.getClipboardData({ 11 | showToast, 12 | success: ({ data }) => resolve(data), 13 | fail: error => reject(error), 14 | complete: () => { 15 | if (!showToast) { 16 | uni.hideToast(); 17 | } 18 | }, 19 | }); 20 | if (!showToast) { 21 | uni.hideToast(); 22 | } 23 | }); 24 | } 25 | 26 | function setClipboardData(data: string, showToast = true) { 27 | return new Promise((resolve, reject) => { 28 | uni.setClipboardData({ 29 | data, 30 | showToast, 31 | success: ({ data }) => resolve(data), 32 | fail: error => reject(error), 33 | complete: () => { 34 | if (!showToast) { 35 | uni.hideToast(); 36 | } 37 | }, 38 | }); 39 | if (!showToast) { 40 | uni.hideToast(); 41 | } 42 | }); 43 | } 44 | 45 | export interface UseClipboardDataOptions extends ConfigurableEventFilter, ConfigurableFlush { 46 | /** 47 | * 操作剪切板数据后是否显示 toast 48 | * 49 | * @default true 50 | */ 51 | showToast?: boolean; 52 | /** 53 | * 是否监听 setClipboardData 引起的剪切板变化 54 | * 55 | * @default true 56 | */ 57 | listenToClipboardDataChanges?: boolean; 58 | /** 59 | * 错误回调 60 | * 61 | * 默认用 `console.error` 打印错误 62 | */ 63 | onError?: (error: unknown) => void; 64 | } 65 | 66 | /** 67 | * 剪切板 68 | * 69 | * https://uniapp.dcloud.net.cn/api/system/clipboard.html 70 | */ 71 | export function useClipboardData( 72 | initialValue: MaybeComputedRef, 73 | options: UseClipboardDataOptions = {}, 74 | ) { 75 | const { 76 | showToast = true, 77 | listenToClipboardDataChanges = true, 78 | onError = error => console.error(error), 79 | flush = 'pre', 80 | eventFilter, 81 | } = options; 82 | 83 | const data = ref(initialValue) as Ref; 84 | 85 | async function read() { 86 | try { 87 | data.value = await getClipboardData(showToast); 88 | } 89 | catch (error) { 90 | onError(error); 91 | } 92 | } 93 | 94 | read(); 95 | 96 | if (listenToClipboardDataChanges) { 97 | useInterceptor('setClipboardData', { complete: () => setTimeout(() => read(), 0) }); 98 | } 99 | 100 | watchWithFilter( 101 | data, 102 | async () => { 103 | try { 104 | await setClipboardData(data.value); 105 | } 106 | catch (error) { 107 | onError(error); 108 | } 109 | }, 110 | { flush, eventFilter }, 111 | ); 112 | 113 | return data; 114 | } 115 | -------------------------------------------------------------------------------- /src/useDownloadFile/index.md: -------------------------------------------------------------------------------- 1 | # useDownloadFile 2 | 3 | `uni.downloadFile` 的封装,对标 `@vueuse/core` v10.7.1。使用方法参见 。 4 | 5 | **返回值中含有 task,可自行操作。** 6 | -------------------------------------------------------------------------------- /src/useDownloadFile/index.ts: -------------------------------------------------------------------------------- 1 | import { until } from '@vueuse/core'; 2 | import { type Ref, ref, type ShallowRef, shallowRef } from 'vue'; 3 | import { isString, noop } from '../utils'; 4 | 5 | /** 对标 @vueuse/core v10.7.1 useAxios */ 6 | 7 | export interface UseDownloadFileReturn { 8 | task: ShallowRef; 9 | 10 | /** uni.downloadFile 响应 */ 11 | response: ShallowRef; 12 | 13 | /** uni.downloadFile 响应内的数据 */ 14 | data: Ref; 15 | 16 | /** 下载是否完成 */ 17 | isFinished: Ref; 18 | 19 | /** 下载是否进行中 */ 20 | isLoading: Ref; 21 | 22 | /** 下载是否中止 */ 23 | isAborted: Ref; 24 | 25 | /** 下载间发生的错误 */ 26 | error: ShallowRef; 27 | 28 | /** 中止当前下载 */ 29 | abort: (message?: string | undefined) => void; 30 | 31 | /** abort 别名 */ 32 | cancel: (message?: string | undefined) => void; 33 | 34 | /** isAborted 别名 */ 35 | isCanceled: Ref; 36 | } 37 | export interface StrictUseDownloadFileReturn extends UseDownloadFileReturn { 38 | /** 手动开始下载 */ 39 | execute: ( 40 | url?: string | UniApp.DownloadFileOption, 41 | config?: UniApp.DownloadFileOption, 42 | ) => PromiseLike>; 43 | } 44 | export interface EasyUseDownloadFileReturn extends UseDownloadFileReturn { 45 | /** 手动开始下载 */ 46 | execute: ( 47 | url: string, 48 | config?: UniApp.DownloadFileOption, 49 | ) => PromiseLike>; 50 | } 51 | export type OverallUseDownloadFileReturn = 52 | | StrictUseDownloadFileReturn 53 | | EasyUseDownloadFileReturn; 54 | 55 | export interface UseDownloadFileOptions { 56 | /** 是否自动开始下载 */ 57 | immediate?: boolean; 58 | 59 | /** 60 | * 是否使用 shallowRef 61 | * 62 | * @default true 63 | */ 64 | shallow?: boolean; 65 | 66 | /** 下载错误时的回调 */ 67 | onError?: (e: UniApp.GeneralCallbackResult) => void; 68 | 69 | /** 下载成功时的回调 */ 70 | onSuccess?: (data: T) => void; 71 | 72 | /** 要使用的初始化数据 */ 73 | initialData?: T; 74 | 75 | /** 是否在执行承诺之前将状态设置为初始状态 */ 76 | resetOnExecute?: boolean; 77 | 78 | /** 下载结束时的回调 */ 79 | onFinish?: (result?: UniApp.GeneralCallbackResult) => void; 80 | } 81 | 82 | export function useDownloadFile( 83 | url: string, 84 | config?: UniApp.DownloadFileOption, 85 | options?: UseDownloadFileOptions, 86 | ): StrictUseDownloadFileReturn & PromiseLike>; 87 | export function useDownloadFile( 88 | config?: UniApp.DownloadFileOption, 89 | ): EasyUseDownloadFileReturn & PromiseLike>; 90 | 91 | /** uni.downloadFile 的封装 */ 92 | export function useDownloadFile( 93 | ...args: any[] 94 | ): OverallUseDownloadFileReturn & PromiseLike> { 95 | const url: string | undefined = typeof args[0] === 'string' ? args[0] : undefined; 96 | const argsPlaceholder = isString(url) ? 1 : 0; 97 | const defaultOptions: UseDownloadFileOptions = { 98 | immediate: !!argsPlaceholder, 99 | shallow: true, 100 | }; 101 | let defaultConfig: Partial = {}; 102 | let options: UseDownloadFileOptions = defaultOptions; 103 | 104 | if (args.length > 0 + argsPlaceholder) { 105 | defaultConfig = args[0 + argsPlaceholder]; 106 | } 107 | 108 | if (args.length === 3) { 109 | options = args[0 + argsPlaceholder]; 110 | } 111 | 112 | const { 113 | initialData, 114 | shallow, 115 | onSuccess = noop, 116 | onError = noop, 117 | onFinish = noop, 118 | immediate, 119 | resetOnExecute = false, 120 | } = options; 121 | 122 | const task = shallowRef(); 123 | const response = shallowRef(); 124 | const data = shallow ? shallowRef() : ref(); 125 | const isFinished = ref(false); 126 | const isLoading = ref(false); 127 | const isAborted = ref(false); 128 | const error = shallowRef(); 129 | 130 | const abort = (message?: string) => { 131 | if (isFinished.value || !isLoading.value) { 132 | return; 133 | } 134 | // @ts-expect-error no types 135 | task.value?.abort(message); 136 | isAborted.value = true; 137 | isLoading.value = false; 138 | isFinished.value = false; 139 | }; 140 | 141 | const loading = (loading: boolean) => { 142 | isLoading.value = loading; 143 | isFinished.value = !loading; 144 | }; 145 | 146 | const resetData = () => { 147 | if (resetOnExecute) { 148 | data.value = initialData; 149 | } 150 | }; 151 | 152 | const promise = { 153 | then: (...args) => waitUntilFinished().then(...args), 154 | catch: (...args) => waitUntilFinished().catch(...args), 155 | } as Promise>; 156 | 157 | let executeCounter = 0; 158 | const execute: OverallUseDownloadFileReturn['execute'] = ( 159 | executeUrl: string | UniApp.DownloadFileOption | undefined = url, 160 | config: Partial = {}, 161 | ) => { 162 | error.value = undefined; 163 | const _url = typeof executeUrl === 'string' ? executeUrl : url ?? config.url; 164 | 165 | if (_url === undefined) { 166 | error.value = { 167 | errMsg: 'Invalid URL provided for uni.request.', 168 | }; 169 | isFinished.value = true; 170 | return promise; 171 | } 172 | resetData(); 173 | abort(); 174 | loading(true); 175 | 176 | executeCounter += 1; 177 | const currentExecuteCounter = executeCounter; 178 | isAborted.value = false; 179 | 180 | const _config = { 181 | ...defaultConfig, 182 | ...(typeof executeUrl === 'object' ? executeUrl : config), 183 | url: _url, 184 | }; 185 | task.value = uni.downloadFile({ 186 | ..._config, 187 | success: (r) => { 188 | if (isAborted.value) { 189 | return; 190 | } 191 | _config.success?.(r); 192 | response.value = r; 193 | const result 194 | // @ts-expect-error no types 195 | = r?.data 196 | ?? ({ 197 | tempFilePath: r?.tempFilePath, 198 | } as unknown as T); 199 | data.value = result; 200 | onSuccess(result); 201 | }, 202 | fail: (e) => { 203 | _config.fail?.(e); 204 | error.value = e; 205 | onError(e); 206 | }, 207 | complete: (r) => { 208 | _config.complete?.(r); 209 | onFinish(r); 210 | if (currentExecuteCounter === executeCounter) { 211 | loading(false); 212 | } 213 | }, 214 | }); 215 | return promise; 216 | }; 217 | if (immediate && url) { 218 | (execute as StrictUseDownloadFileReturn['execute'])(); 219 | } 220 | 221 | const result = { 222 | task, 223 | response, 224 | data, 225 | error, 226 | isFinished, 227 | isLoading, 228 | cancel: abort, 229 | isAborted, 230 | isCanceled: isAborted, 231 | abort, 232 | execute, 233 | } as OverallUseDownloadFileReturn; 234 | 235 | function waitUntilFinished() { 236 | return new Promise>((resolve, reject) => { 237 | until(isFinished) 238 | .toBe(true) 239 | .then(() => (error.value ? reject(error.value) : resolve(result))); 240 | }); 241 | } 242 | 243 | return { 244 | ...result, 245 | ...promise, 246 | }; 247 | } 248 | -------------------------------------------------------------------------------- /src/useGlobalData/index.md: -------------------------------------------------------------------------------- 1 | # useGlobalData 2 | 3 | 获取和设置当前应用实例的 `globalData`。你需要将默认值作为第一个参数传入。 4 | 5 | ```typescript 6 | import { useGlobalData } from '@uni-helper/uni-use'; 7 | 8 | const globalData = useGlobalData({}); 9 | ``` 10 | 11 | 如果你需要使用 `shallowRef`,需要在第二个参数中指明。如果你需要设置一个很大的数据,`shallowRef` 会很有用。 12 | 13 | ```typescript 14 | useGlobalData({}, { shallow: true }); 15 | ``` 16 | 17 | 我们建议直接使用 [pinia](https://pinia.vuejs.org/zh/) 作为状态管理工具。 18 | -------------------------------------------------------------------------------- /src/useGlobalData/index.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigurableEventFilter, ConfigurableFlush, RemovableRef } from '@vueuse/core'; 2 | import type { MaybeComputedRef } from '../types'; 3 | import { resolveUnref, watchWithFilter } from '@vueuse/core'; 4 | import { ref, shallowRef } from 'vue'; 5 | import { isFunction } from '../utils'; 6 | 7 | export interface UseGlobalDataOptions 8 | extends ConfigurableEventFilter, 9 | ConfigurableFlush { 10 | /** 11 | * 当 globalData 还没有已有值时,是否写入 globalData 12 | * 13 | * @default true 14 | */ 15 | writeDefaults?: boolean; 16 | /** 17 | * 是否合并默认值和已有值 18 | * 19 | * 当设置为 true 时,浅合并对象 20 | * 21 | * 你也可以传一个方法来自定义合并 22 | * 23 | * @default false 24 | */ 25 | mergeDefaults?: boolean | ((nextValue: T, prevValue: T) => T); 26 | /** 27 | * 是否使用 shallowRef 28 | * 29 | * @default false 30 | */ 31 | shallow?: boolean; 32 | /** 33 | * 是否监听深层变化 34 | * 35 | * @default true 36 | */ 37 | deep?: boolean; 38 | } 39 | 40 | /** 41 | * globalData 42 | * 43 | * https://uniapp.dcloud.net.cn/collocation/App.html#globaldata 44 | */ 45 | 46 | export function useGlobalData( 47 | initialValue: MaybeComputedRef, 48 | options: UseGlobalDataOptions = {}, 49 | ) { 50 | const { 51 | writeDefaults = true, 52 | mergeDefaults = false, 53 | shallow = false, 54 | deep = true, 55 | flush = 'pre', 56 | eventFilter, 57 | } = options; 58 | 59 | const app = ref(getApp()); 60 | 61 | const rawInit: T = resolveUnref(initialValue); 62 | 63 | const data = (shallow ? shallowRef : ref)(initialValue) as RemovableRef; 64 | 65 | watchWithFilter(data, () => (app.value.globalData = data.value ?? undefined), { 66 | flush, 67 | deep, 68 | eventFilter, 69 | }); 70 | 71 | function read() { 72 | // 读取已有值 73 | const rawValue = app.value.globalData; 74 | if (rawValue == null) { 75 | if (writeDefaults && rawInit !== null) { 76 | app.value.globalData = rawInit; 77 | } 78 | return rawInit; 79 | } 80 | else if (mergeDefaults) { 81 | const value = rawValue as T; 82 | return isFunction(mergeDefaults) 83 | ? mergeDefaults(value, rawInit) 84 | : { ...(rawInit as any), ...value }; 85 | } 86 | else { 87 | return rawValue; 88 | } 89 | } 90 | 91 | data.value = read(); 92 | 93 | return data; 94 | } 95 | -------------------------------------------------------------------------------- /src/useInterceptor/index.md: -------------------------------------------------------------------------------- 1 | # useInterceptor 2 | 3 | 设置拦截器。 支持拦截 sync 函数,并且 `invoke` 返回 false 将终止拦截器继续执行 4 | 5 | ```typescript 6 | import { useInterceptor } from '@uni-helper/uni-use'; 7 | 8 | const event = 'request'; 9 | 10 | // 设置拦截器 11 | const stop = useInterceptor(event, { 12 | invoke: (args) => { 13 | args[0].url = `https://www.example.com/${args[0].url}`; 14 | }, 15 | success: (response) => { 16 | console.log('interceptor-success', response); 17 | response.data.code = 1; 18 | }, 19 | fail: (error) => { 20 | console.log('interceptor-fail', error); 21 | }, 22 | complete: () => { 23 | console.log('interceptor-complete'); 24 | }, 25 | }); 26 | 27 | // 删除拦截器 28 | stop(); 29 | ``` 30 | -------------------------------------------------------------------------------- /src/useInterceptor/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { useInterceptor } from '.'; 3 | 4 | describe('useInterceptor', () => { 5 | it('export module', () => { 6 | expect(useInterceptor).toBeDefined(); 7 | }); 8 | 9 | it('invoke args', () => { 10 | const key = 'custom-key'; 11 | 12 | const stop = useInterceptor('getStorage', { invoke: (args) => { 13 | expect(args[0].key).toBe(key); 14 | } }); 15 | 16 | uni.getStorage({ key }); 17 | 18 | stop(); 19 | }); 20 | 21 | it('return value ASYNC', () => { 22 | const key = 'custom-key'; 23 | 24 | uni.setStorageSync(key, 'a'); 25 | 26 | const stop = useInterceptor('getStorage', { invoke: (args) => { 27 | expect(args[0].key).toBe(key); 28 | } }); 29 | 30 | const returnVal = uni.getStorage({ key }); 31 | 32 | expect(returnVal.then).toBeDefined(); 33 | 34 | returnVal.then((val: any) => { 35 | expect(val).toStrictEqual({ 36 | data: 'a', 37 | errMsg: 'getStorage:ok', 38 | }); 39 | }); 40 | 41 | stop(); 42 | }); 43 | 44 | it('return value SYNC', () => { 45 | const key = 'custom-key'; 46 | 47 | uni.setStorageSync(key, 'b'); 48 | 49 | const stop = useInterceptor('getStorageSync', { invoke: (args) => { 50 | expect(args[0]).toBe(key); 51 | } }); 52 | 53 | const returnVal = uni.getStorageSync(key); 54 | 55 | expect(returnVal).toBe('b'); 56 | 57 | stop(); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/useInterceptor/index.ts: -------------------------------------------------------------------------------- 1 | import { tryOnScopeDispose } from '../tryOnScopeDispose'; 2 | import { isThenable } from '../utils'; 3 | 4 | type FunctionKeys = { 5 | // eslint-disable-next-line ts/no-unsafe-function-type 6 | [K in keyof T]: T[K] extends Function ? K : never; 7 | }[keyof T]; 8 | 9 | type UniMethod = FunctionKeys; 10 | 11 | export interface InterceptorOptions { 12 | /** 返回 false 则终止执行 */ 13 | invoke?: (args: Parameters) => void | boolean; 14 | 15 | success?: Parameters[0]['success'] | ReturnType; 16 | 17 | fail?: Parameters[0]['fail'] | ((err: any) => void); 18 | 19 | complete?: Parameters[0]['complete'] | (() => void); 20 | } 21 | 22 | const globalInterceptors: Record> = {}; 23 | const originMethods = {} as Record; 24 | 25 | /** 26 | * 包装uni-app中的方法,添加拦截器功能 27 | * 28 | * @param method uni-app中的方法名 29 | * @returns 包装后的方法 30 | */ 31 | function wrappMethod(method: UniMethod) { 32 | // 判断是否已经包装过 33 | if (method in originMethods) { 34 | // 直接返回 35 | return uni[method]; 36 | } 37 | 38 | // 获取原始方法 39 | const origin = uni[method]; 40 | // 记录原始方法 41 | originMethods[method] = origin; 42 | // 原函数的类型定义 43 | type FN = typeof origin; 44 | 45 | // 开始包裹函数 46 | uni[method] = ((...args: Parameters) => { 47 | // 获取拦截器 48 | const interceptors = globalInterceptors[method] || {}; 49 | // 实际起作用的拦截器 50 | const effectInterceptors: InterceptorOptions[] = []; 51 | 52 | // invoke 在函数执行前运行,返回false则终止此拦截器执行后续的 success / fail / complete 回调 53 | for (const [_key, interceptor] of Object.entries(interceptors)) { 54 | if (interceptor.invoke && interceptor.invoke(args) === false) { 55 | continue; 56 | } 57 | 58 | effectInterceptors.push(interceptor); 59 | } 60 | 61 | /** 62 | * 判断函数是否符合异步函数的参数 63 | * 含有 success / fail / complete 的async函数将不会返回promise 64 | * @see https://uniapp.dcloud.net.cn/api/#api-promise-%E5%8C%96 65 | */ 66 | const hasAsyncOption = args.length === 1 && ((args[0] as any).success || (args[0] as any).fail || (args[0] as any).complete); 67 | 68 | if (hasAsyncOption) { 69 | const opt = args[0]; 70 | 71 | const oldSuccess = opt.success; 72 | opt.success = (result: any) => { 73 | for (const interceptor of effectInterceptors) { 74 | interceptor.success && interceptor.success(result); 75 | } 76 | oldSuccess && oldSuccess(result); 77 | }; 78 | 79 | const oldFail = opt.fail; 80 | opt.fail = (err: any) => { 81 | for (const interceptor of effectInterceptors) { 82 | interceptor.fail && interceptor.fail(err); 83 | } 84 | oldFail && oldFail(err); 85 | }; 86 | 87 | const oldComplete = opt.complete; 88 | opt.complete = () => { 89 | for (const interceptor of effectInterceptors) { 90 | interceptor.complete && interceptor.complete(); 91 | } 92 | oldComplete && oldComplete(); 93 | }; 94 | 95 | return (origin as any)(opt); // 保持和官方一致,不返回promise 96 | } 97 | else { 98 | try { 99 | const result = (origin as any)(...args); 100 | 101 | // is promise 102 | if (isThenable(result)) { 103 | // 如果返回值是 Promise,则将回调挂载在 Promise 上,直接返回当前 Promise 104 | return result.then((res: any) => { 105 | for (const interceptor of effectInterceptors) { 106 | interceptor.success && interceptor.success(res); 107 | } 108 | return res; 109 | }).catch((err: any) => { 110 | for (const interceptor of effectInterceptors) { 111 | interceptor.fail && interceptor.fail(err); 112 | } 113 | return err; 114 | }); 115 | } 116 | 117 | // 不是 Promise,且未报错,执行 success 回调 118 | for (const interceptor of effectInterceptors) { 119 | interceptor.success && interceptor.success(result); 120 | } 121 | 122 | return result; 123 | } 124 | catch (err: any) { // only catch for not thenable 125 | // 不是 Promise,且报错,执行 fail 回调 126 | for (const interceptor of effectInterceptors) { 127 | interceptor.fail && interceptor.fail(err); 128 | } 129 | } 130 | finally { // finally for ALL (thenable and normal) 131 | // 无论是否 Promise 都执行的 complete 回调 132 | for (const interceptor of effectInterceptors) { 133 | interceptor.complete && interceptor.complete(); 134 | } 135 | } 136 | } 137 | }) as any; 138 | 139 | return uni[method]; 140 | } 141 | 142 | /** 143 | * 注册拦截器,在活跃的 effect 作用域停止时自动移除 144 | * 145 | * https://cn.vuejs.org/api/reactivity-advanced.htmlSeffectscope 146 | */ 147 | export function useInterceptor(method: F, interceptor: InterceptorOptions) { 148 | // 包裹、封装函数,注入拦截器操作 149 | wrappMethod(method); 150 | 151 | globalInterceptors[method] = globalInterceptors[method] || {}; 152 | const key = Math.random().toString(36).slice(-8); 153 | globalInterceptors[method][key] = interceptor; 154 | 155 | const stop = () => { 156 | delete globalInterceptors[method][key]; 157 | }; 158 | 159 | tryOnScopeDispose(stop); 160 | 161 | return stop; 162 | } 163 | -------------------------------------------------------------------------------- /src/useLoading/index.md: -------------------------------------------------------------------------------- 1 | # useLoading 2 | 3 | 返回一个对象,包含两个方法。其中`showLoading` 调用后显示加载提示框,`hideLoading` 调用后隐藏加载提示框。 4 | 5 | ```typescript 6 | import { useLoading } from '@uni-helper/uni-use'; 7 | 8 | const { showLoading, hideLoading } = useLoading({ 9 | /* 传入配置 */ 10 | }); 11 | showLoading(); // 显示加载提示框 12 | 13 | hideLoading(); // 隐藏加载提示框 14 | ``` 15 | 16 | 你也可以通过调用 `showLoading` 来获取 `hideLoading`。 17 | 18 | ```typescript 19 | import { useLoading } from '@uni-helper/uni-use'; 20 | 21 | const { showLoading, hideLoading } = useLoading({ 22 | /* 传入配置 */ 23 | }); 24 | const hideLoading = showLoading(); 25 | ``` 26 | 27 | 可以传入一个对象来更新已有配置,这样会使用 [扩展运算符](https://es6.ruanyifeng.com/#docs/object#%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%89%A9%E5%B1%95%E8%BF%90%E7%AE%97%E7%AC%A6) 来确认最终配置。 28 | 29 | ```typescript 30 | showLoading({ 31 | /* 新传入配置 */ 32 | }); 33 | ``` 34 | -------------------------------------------------------------------------------- /src/useLoading/index.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeComputedRef } from '../types'; 2 | import { resolveUnref } from '@vueuse/core'; 3 | import { reactive } from 'vue'; 4 | 5 | export interface UniShowLoadingOptions extends UniApp.ShowLoadingOptions {} 6 | export type ShowLoadingOptions = MaybeComputedRef; 7 | export type UseLoadingOptions = ShowLoadingOptions; 8 | 9 | function hideLoading() { 10 | return uni.hideLoading(); 11 | } 12 | 13 | /** 14 | * 返回一个对象,包含两个方法 15 | * 16 | * 其中`showLoading` 调用后显示加载提示框,`hideLoading` 调用后隐藏加载提示框 17 | * 18 | * https://uniapp.dcloud.net.cn/api/ui/prompt.html#showloading 19 | */ 20 | export function useLoading(options?: UseLoadingOptions) { 21 | function showLoading(newOptions?: ShowLoadingOptions) { 22 | uni.showLoading( 23 | reactive({ 24 | ...resolveUnref(options), 25 | ...resolveUnref(newOptions), 26 | }), 27 | ); 28 | return hideLoading; 29 | } 30 | 31 | return { 32 | /** 33 | * 显示加载提示框 34 | * 35 | * https://uniapp.dcloud.net.cn/api/ui/prompt.html#showloading 36 | */ 37 | showLoading, 38 | 39 | /** 40 | * 隐藏加载提示框 41 | * 42 | * https://uniapp.dcloud.net.cn/api/ui/prompt.html#hideloading 43 | */ 44 | hideLoading, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/useModal/index.md: -------------------------------------------------------------------------------- 1 | # useModal 2 | 3 | 返回一个方法,调用后显示模态弹窗。 4 | 5 | ```typescript 6 | import { useModal } from '@uni-helper/uni-use'; 7 | 8 | const showModal = useModal({ 9 | /* 传入配置 */ 10 | }); 11 | showModal(); // 显示模态弹窗 12 | ``` 13 | 14 | 可以传入一个对象来更新已有配置,这样会使用 [扩展运算符](https://es6.ruanyifeng.com/#docs/object#%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%89%A9%E5%B1%95%E8%BF%90%E7%AE%97%E7%AC%A6) 来确认最终配置。 15 | 16 | ```typescript 17 | showModal({ 18 | /* 新传入配置 */ 19 | }); 20 | ``` 21 | -------------------------------------------------------------------------------- /src/useModal/index.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeComputedRef } from '../types'; 2 | import { resolveUnref } from '@vueuse/core'; 3 | import { reactive } from 'vue'; 4 | 5 | export interface UniShowModalOptions extends UniApp.ShowModalOptions {} 6 | export type ShowModalOptions = MaybeComputedRef; 7 | export type UseModalOptions = ShowModalOptions; 8 | 9 | /** 10 | * 返回一个方法,调用后显示模态弹窗 11 | * 12 | * https://uniapp.dcloud.net.cn/api/ui/prompt?id=showmodal 13 | */ 14 | export function useModal(options?: UseModalOptions) { 15 | /** 16 | * 显示模态弹窗 17 | * 18 | * https://uniapp.dcloud.net.cn/api/ui/prompt?id=showmodal 19 | */ 20 | return function showModal(newOptions?: ShowModalOptions) { 21 | return uni.showModal( 22 | reactive({ 23 | ...resolveUnref(options), 24 | ...resolveUnref(newOptions), 25 | }), 26 | ); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/useNetwork/index.md: -------------------------------------------------------------------------------- 1 | # useNetwork 2 | 3 | 获取网络信息。 4 | 5 | ```typescript 6 | import { useNetwork } from '@uni-helper/uni-use'; 7 | 8 | const { type, isWifi, is2g, is3g, is4g, is5g, isEthernet, isUnknown, isOnline, isOffline } 9 | = useNetwork(); 10 | ``` 11 | -------------------------------------------------------------------------------- /src/useNetwork/index.ts: -------------------------------------------------------------------------------- 1 | import { tryOnScopeDispose } from '@vueuse/core'; 2 | import { computed, ref } from 'vue'; 3 | 4 | export type NetworkType = 'ethernet' | 'none' | 'wifi' | 'unknown' | '2g' | '3g' | '4g' | '5g'; 5 | 6 | /** 7 | * 获取网络信息 8 | * 9 | * https://uniapp.dcloud.net.cn/api/system/network.html 10 | */ 11 | export function useNetwork() { 12 | const type = ref('none'); 13 | const isWifi = computed(() => type.value === 'wifi'); 14 | const is2g = computed(() => type.value === '2g'); 15 | const is3g = computed(() => type.value === '3g'); 16 | const is4g = computed(() => type.value === '4g'); 17 | const is5g = computed(() => type.value === '5g'); 18 | const isEthernet = computed(() => type.value === 'ethernet'); 19 | const isUnknown = computed(() => type.value === 'unknown'); 20 | 21 | const isOffline = computed(() => type.value === 'none'); 22 | const isOnline = computed(() => !isOffline.value); 23 | 24 | const updateNetwork = ( 25 | result: UniApp.GetNetworkTypeSuccess | UniApp.OnNetworkStatusChangeSuccess, 26 | ) => { 27 | type.value = (result?.networkType ?? 'unknown') as NetworkType; 28 | }; 29 | 30 | uni.getNetworkType({ 31 | success: result => updateNetwork(result), 32 | }); 33 | 34 | const callback = (result: UniApp.OnNetworkStatusChangeSuccess) => updateNetwork(result); 35 | uni.onNetworkStatusChange(callback); 36 | const stop = () => uni.offNetworkStatusChange(callback); 37 | tryOnScopeDispose(stop); 38 | 39 | return { 40 | type, 41 | isWifi, 42 | is2g, 43 | is3g, 44 | is4g, 45 | is5g, 46 | isEthernet, 47 | isUnknown, 48 | isOnline, 49 | isOffline, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/useOnline/index.md: -------------------------------------------------------------------------------- 1 | # useOnline 2 | 3 | 获取是否在线。基于 `useNetwork`。 4 | 5 | ```typescript 6 | import { useOnline } from '@uni-helper/uni-use'; 7 | 8 | const isOnline = useOnline(); 9 | ``` 10 | -------------------------------------------------------------------------------- /src/useOnline/index.ts: -------------------------------------------------------------------------------- 1 | import { useNetwork } from '../useNetwork'; 2 | 3 | /** 4 | * 获取是否在线 5 | * 6 | * https://uniapp.dcloud.net.cn/api/system/network.html 7 | */ 8 | export function useOnline() { 9 | const { isOnline } = useNetwork(); 10 | return isOnline; 11 | } 12 | -------------------------------------------------------------------------------- /src/usePage/index.md: -------------------------------------------------------------------------------- 1 | # usePage 2 | 3 | 获取当前页信息。 4 | 5 | ```typescript 6 | import { usePage } from '@uni-helper/uni-use'; 7 | 8 | const page = usePage(); 9 | ``` 10 | -------------------------------------------------------------------------------- /src/usePage/index.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from '../useRouter'; 2 | 3 | /** 获取当前页信息 */ 4 | export function usePage() { 5 | const { page } = useRouter(); 6 | return page; 7 | } 8 | -------------------------------------------------------------------------------- /src/usePageScroll/index.md: -------------------------------------------------------------------------------- 1 | # usePageScroll 2 | 3 | 控制与监听页面滚动。 4 | 5 | ```typescript 6 | import { usePageScroll } from '@uni-helper/uni-use'; 7 | 8 | const { scrollTop } = usePageScroll({ 9 | onPageScroll: true, 10 | }); 11 | ``` 12 | -------------------------------------------------------------------------------- /src/usePageScroll/index.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeRef } from '../types'; 2 | import { onPageScroll } from '@dcloudio/uni-app'; 3 | import { watchWithFilter } from '@vueuse/core'; 4 | import { computed, isRef, ref } from 'vue'; 5 | 6 | export interface UsePageScrollOptions { 7 | /** 8 | * 此选项不可缺! 9 | * uniapp 必须在页面内检测到 onPageScroll 关键词才会注册事件。 10 | * @see https://github.com/dcloudio/uni-app/issues/3099 让页面被正则捕获从而开启监听 11 | */ 12 | onPageScroll: any; 13 | /** 14 | * 滚动到指定选择器 15 | * 16 | * @see https://uniapp.dcloud.net.cn/api/ui/scroll?id=pagescrollto 17 | */ 18 | scrollToSelector?: MaybeRef; 19 | /** 20 | * 滚动动画时长 21 | * 22 | * @default 300 23 | * @see https://uniapp.dcloud.net.cn/api/ui/scroll?id=pagescrollto 24 | */ 25 | duration?: number; 26 | } 27 | 28 | /** 29 | * 页面滚动 30 | * 31 | * @param options 配置项 32 | * @see https://uniapp.dcloud.net.cn/tutorial/page.html#onpagescroll 33 | */ 34 | export function usePageScroll(options: UsePageScrollOptions) { 35 | const { duration = 300 } = options; 36 | 37 | const _scrollTop = ref(0); 38 | const scrollTop = computed({ 39 | get() { 40 | return _scrollTop.value; 41 | }, 42 | set(val) { 43 | uni.pageScrollTo({ 44 | scrollTop: val, 45 | duration, 46 | }); 47 | }, 48 | }); 49 | 50 | onPageScroll((e) => { 51 | _scrollTop.value = e.scrollTop; 52 | }); 53 | 54 | const scrollToSelector = isRef(options?.scrollToSelector) 55 | ? options.scrollToSelector 56 | : ref(options?.scrollToSelector || ''); 57 | 58 | watchWithFilter( 59 | () => scrollToSelector.value, 60 | (newValue) => { 61 | uni.pageScrollTo({ 62 | selector: newValue, 63 | duration, 64 | }); 65 | }, 66 | { 67 | eventFilter: e => e !== undefined, 68 | }, 69 | ); 70 | 71 | return { 72 | scrollTop, 73 | scrollToSelector, 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /src/usePages/index.md: -------------------------------------------------------------------------------- 1 | # usePages 2 | 3 | 获取当前页面栈信息。 4 | 5 | ```typescript 6 | import { usePages } from '@uni-helper/uni-use'; 7 | 8 | const pages = usePages(); 9 | ``` 10 | -------------------------------------------------------------------------------- /src/usePages/index.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from '../useRouter'; 2 | 3 | /** 获取当前页面栈信息 */ 4 | export function usePages() { 5 | const { pages } = useRouter(); 6 | return pages; 7 | } 8 | -------------------------------------------------------------------------------- /src/usePreferredDark/index.md: -------------------------------------------------------------------------------- 1 | # usePreferredDark 2 | 3 | 响应式的暗黑主题偏好。 4 | 5 | ```typescript 6 | import { usePreferredDark } from '@uni-helper/uni-use'; 7 | 8 | const prefersDark = usePreferredDark(); 9 | ``` 10 | -------------------------------------------------------------------------------- /src/usePreferredDark/index.ts: -------------------------------------------------------------------------------- 1 | import { tryOnScopeDispose } from '@vueuse/core'; 2 | import { readonly, ref } from 'vue'; 3 | 4 | /** 5 | * 响应式的暗黑主题偏好 6 | * 7 | * https://zh.uniapp.dcloud.io/api/system/theme.html 8 | * 9 | * https://uniapp.dcloud.net.cn/tutorial/darkmode.html 10 | */ 11 | export function usePreferredDark() { 12 | const prefersDark = ref(uni.getSystemInfoSync().osTheme === 'dark'); 13 | 14 | const callback = ({ theme }: UniApp.OnThemeChangeCallbackResult) => { 15 | prefersDark.value = theme === 'dark'; 16 | }; 17 | uni.onThemeChange(callback); 18 | const stop = () => uni.offThemeChange(callback); 19 | tryOnScopeDispose(stop); 20 | 21 | return readonly(prefersDark); 22 | } 23 | -------------------------------------------------------------------------------- /src/usePreferredLanguage/index.md: -------------------------------------------------------------------------------- 1 | # usePreferredLanguage 2 | 3 | 响应式的语言偏好。 4 | 5 | ```typescript 6 | import { usePreferredLanguage } from '@uni-helper/uni-use'; 7 | 8 | const language = usePreferredLanguage(); 9 | ``` 10 | -------------------------------------------------------------------------------- /src/usePreferredLanguage/index.ts: -------------------------------------------------------------------------------- 1 | import { tryOnScopeDispose } from '@vueuse/core'; 2 | import { readonly, ref } from 'vue'; 3 | 4 | /** 5 | * 响应式的语言偏好 6 | * 7 | * https://uniapp.dcloud.net.cn/api/ui/locale.html 8 | * 9 | * https://uniapp.dcloud.net.cn/tutorial/i18n 10 | */ 11 | export function usePreferredLanguage() { 12 | const locale = ref(uni.getLocale()); 13 | 14 | const callback = (result: UniApp.OnLocaleChangeCallbackResult) => { 15 | locale.value = result.locale ?? locale.value; 16 | }; 17 | uni.onLocaleChange(callback); 18 | const stop = () => { 19 | // @ts-expect-error no types 20 | if (uni.offLocaleChange) { 21 | // @ts-expect-error no types 22 | uni.offLocaleChange(callback); 23 | } 24 | }; 25 | tryOnScopeDispose(stop); 26 | 27 | return readonly(locale); 28 | } 29 | -------------------------------------------------------------------------------- /src/usePrevPage/index.md: -------------------------------------------------------------------------------- 1 | # usePrevPage 2 | 3 | 获取前一页信息。 4 | 5 | ```typescript 6 | import { usePrevPage } from '@uni-helper/uni-use'; 7 | 8 | const prevPage = usePrevPage(); 9 | ``` 10 | -------------------------------------------------------------------------------- /src/usePrevPage/index.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from '../useRouter'; 2 | 3 | /** 获取前一页信息 */ 4 | export function usePrevPage() { 5 | const { prevPage } = useRouter(); 6 | return prevPage; 7 | } 8 | -------------------------------------------------------------------------------- /src/usePrevRoute/index.md: -------------------------------------------------------------------------------- 1 | # usePrevRoute 2 | 3 | 获取前一页面路由信息。 4 | 5 | ```typescript 6 | import { usePrevRoute } from '@uni-helper/uni-use'; 7 | 8 | const prevRoute = usePrevRoute(); 9 | ``` 10 | -------------------------------------------------------------------------------- /src/usePrevRoute/index.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from '../useRouter'; 2 | 3 | /** 获取前一页路由信息 */ 4 | export function usePrevRoute() { 5 | const { prevRoute } = useRouter(); 6 | return prevRoute; 7 | } 8 | -------------------------------------------------------------------------------- /src/useProvider/index.md: -------------------------------------------------------------------------------- 1 | # useProvider 2 | 3 | 设置服务供应商参数,调用返回方法获取服务供应商。 4 | 5 | ```typescript 6 | import { useProvider } from '@uni-helper/uni-use'; 7 | 8 | const getProvider = useProvider({ 9 | /* 传入配置 */ 10 | }); 11 | getProvider(); // 获取服务供应商 12 | ``` 13 | 14 | 可以传入一个对象来更新已有配置,这样会使用 [扩展运算符](https://es6.ruanyifeng.com/#docs/object#%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%89%A9%E5%B1%95%E8%BF%90%E7%AE%97%E7%AC%A6) 来确认最终配置。 15 | 16 | ```typescript 17 | getProvider({ 18 | /* 新传入配置 */ 19 | }); 20 | ``` 21 | -------------------------------------------------------------------------------- /src/useProvider/index.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeComputedRef } from '../types'; 2 | import { resolveUnref } from '@vueuse/core'; 3 | import { reactive } from 'vue'; 4 | 5 | export interface UniGetProviderOptions extends UniApp.GetProviderOptions {} 6 | export type GetProviderOptions = MaybeComputedRef; 7 | export type UseProviderOptions = GetProviderOptions; 8 | 9 | /** 10 | * 返回一个方法,调用后获取服务供应商 11 | * 12 | * https://uniapp.dcloud.net.cn/api/plugins/provider?id=getprovider 13 | */ 14 | export function useProvider(options?: UseProviderOptions) { 15 | /** 16 | * 获取服务供应商 17 | * 18 | * https://uniapp.dcloud.net.cn/api/plugins/provider?id=getprovider 19 | */ 20 | return function getProvider(newOptions?: GetProviderOptions) { 21 | return uni.getProvider( 22 | reactive({ 23 | service: 'oauth', 24 | ...resolveUnref(options), 25 | ...resolveUnref(newOptions), 26 | }), 27 | ); 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/useRequest/index.md: -------------------------------------------------------------------------------- 1 | # useRequest 2 | 3 | `uni.request` 的封装,对标 `@vueuse/core` v10.7.1。使用方法参见 。 4 | 5 | **返回值中含有 task,可自行操作。** 6 | -------------------------------------------------------------------------------- /src/useRequest/index.ts: -------------------------------------------------------------------------------- 1 | import { until } from '@vueuse/core'; 2 | import { type Ref, ref, type ShallowRef, shallowRef } from 'vue'; 3 | import { isString, noop } from '../utils'; 4 | 5 | /** 对标 @vueuse/core v10.7.1 useAxios */ 6 | 7 | export interface UseRequestReturn { 8 | task: ShallowRef; 9 | 10 | /** uni.request 响应 */ 11 | response: ShallowRef; 12 | 13 | /** uni.request 响应内的数据 */ 14 | data: Ref; 15 | 16 | /** 请求是否完成 */ 17 | isFinished: Ref; 18 | 19 | /** 请求是否进行中 */ 20 | isLoading: Ref; 21 | 22 | /** 请求是否中止 */ 23 | isAborted: Ref; 24 | 25 | /** 请求间发生的错误 */ 26 | error: ShallowRef; 27 | 28 | /** 中止当前请求 */ 29 | abort: (message?: string | undefined) => void; 30 | 31 | /** abort 别名 */ 32 | cancel: (message?: string | undefined) => void; 33 | 34 | /** isAborted 别名 */ 35 | isCanceled: Ref; 36 | } 37 | export interface StrictUseRequestReturn extends UseRequestReturn { 38 | /** 手动开始请求 */ 39 | execute: ( 40 | url?: string | UniApp.RequestOptions, 41 | config?: UniApp.RequestOptions, 42 | ) => PromiseLike>; 43 | } 44 | export interface EasyUseRequestReturn extends UseRequestReturn { 45 | /** 手动开始下载 */ 46 | execute: (url: string, config?: UniApp.RequestOptions) => PromiseLike>; 47 | } 48 | export type OverallUseRequestReturn = StrictUseRequestReturn | EasyUseRequestReturn; 49 | 50 | export interface UseRequestOptions { 51 | /** 是否自动开始请求 */ 52 | immediate?: boolean; 53 | 54 | /** 55 | * 是否使用 shallowRef 56 | * 57 | * @default true 58 | */ 59 | shallow?: boolean; 60 | 61 | /** 请求错误时的回调 */ 62 | onError?: (e: UniApp.GeneralCallbackResult) => void; 63 | 64 | /** 请求成功时的回调 */ 65 | onSuccess?: (data: T) => void; 66 | 67 | /** 要使用的初始化数据 */ 68 | initialData?: T; 69 | 70 | /** 是否在执行承诺之前将状态设置为初始状态 */ 71 | resetOnExecute?: boolean; 72 | 73 | /** 请求结束时的回调 */ 74 | onFinish?: (result?: UniApp.GeneralCallbackResult) => void; 75 | } 76 | 77 | export function useRequest( 78 | url: string, 79 | config?: UniApp.RequestOptions, 80 | options?: UseRequestOptions, 81 | ): StrictUseRequestReturn & PromiseLike>; 82 | export function useRequest( 83 | config?: UniApp.RequestOptions, 84 | ): EasyUseRequestReturn & PromiseLike>; 85 | 86 | /** uni.request 的封装 */ 87 | export function useRequest( 88 | ...args: any[] 89 | ): OverallUseRequestReturn & PromiseLike> { 90 | const url: string | undefined = typeof args[0] === 'string' ? args[0] : undefined; 91 | const argsPlaceholder = isString(url) ? 1 : 0; 92 | const defaultOptions: UseRequestOptions = { 93 | immediate: !!argsPlaceholder, 94 | shallow: true, 95 | }; 96 | let defaultConfig: Partial = {}; 97 | let options: UseRequestOptions = defaultOptions; 98 | 99 | if (args.length > 0 + argsPlaceholder) { 100 | defaultConfig = args[0 + argsPlaceholder]; 101 | } 102 | 103 | if (args.length === 3) { 104 | options = args[0 + argsPlaceholder]; 105 | } 106 | 107 | const { 108 | initialData, 109 | shallow, 110 | onSuccess = noop, 111 | onError = noop, 112 | onFinish = noop, 113 | immediate, 114 | resetOnExecute = false, 115 | } = options; 116 | 117 | const task = shallowRef(); 118 | const response = shallowRef(); 119 | const data = shallow ? shallowRef() : ref(); 120 | const isFinished = ref(false); 121 | const isLoading = ref(false); 122 | const isAborted = ref(false); 123 | const error = shallowRef(); 124 | 125 | const abort = (message?: string) => { 126 | if (isFinished.value || !isLoading.value) { 127 | return; 128 | } 129 | // @ts-expect-error no types 130 | task.value?.abort(message); 131 | isAborted.value = true; 132 | isLoading.value = false; 133 | isFinished.value = false; 134 | }; 135 | 136 | const loading = (loading: boolean) => { 137 | isLoading.value = loading; 138 | isFinished.value = !loading; 139 | }; 140 | 141 | const resetData = () => { 142 | if (resetOnExecute) { 143 | data.value = initialData; 144 | } 145 | }; 146 | 147 | const promise = { 148 | then: (...args) => waitUntilFinished().then(...args), 149 | catch: (...args) => waitUntilFinished().catch(...args), 150 | } as Promise>; 151 | 152 | let executeCounter = 0; 153 | const execute: OverallUseRequestReturn['execute'] = ( 154 | executeUrl: string | UniApp.RequestOptions | undefined = url, 155 | config: Partial = {}, 156 | ) => { 157 | error.value = undefined; 158 | const _url = typeof executeUrl === 'string' ? executeUrl : url ?? config.url; 159 | 160 | if (_url === undefined) { 161 | error.value = { 162 | errMsg: 'Invalid URL provided for uni.request.', 163 | }; 164 | isFinished.value = true; 165 | return promise; 166 | } 167 | resetData(); 168 | abort(); 169 | loading(true); 170 | 171 | executeCounter += 1; 172 | const currentExecuteCounter = executeCounter; 173 | isAborted.value = false; 174 | 175 | const _config = { 176 | ...defaultConfig, 177 | ...(typeof executeUrl === 'object' ? executeUrl : config), 178 | url: _url, 179 | }; 180 | task.value = uni.request({ 181 | ..._config, 182 | success: (r) => { 183 | if (isAborted.value) { 184 | return; 185 | } 186 | _config.success?.(r); 187 | response.value = r; 188 | const result = r.data as unknown as T; 189 | data.value = result; 190 | onSuccess(result); 191 | }, 192 | fail: (e) => { 193 | _config.fail?.(e); 194 | error.value = e; 195 | onError(e); 196 | }, 197 | complete: (r) => { 198 | _config.complete?.(r); 199 | onFinish(r); 200 | if (currentExecuteCounter === executeCounter) { 201 | loading(false); 202 | } 203 | }, 204 | }); 205 | return promise; 206 | }; 207 | if (immediate && url) { 208 | (execute as StrictUseRequestReturn['execute'])(); 209 | } 210 | 211 | const result = { 212 | task, 213 | response, 214 | data, 215 | error, 216 | isFinished, 217 | isLoading, 218 | cancel: abort, 219 | isAborted, 220 | isCanceled: isAborted, 221 | abort, 222 | execute, 223 | } as OverallUseRequestReturn; 224 | 225 | function waitUntilFinished() { 226 | return new Promise>((resolve, reject) => { 227 | until(isFinished) 228 | .toBe(true) 229 | .then(() => (error.value ? reject(error.value) : resolve(result))); 230 | }); 231 | } 232 | 233 | return { 234 | ...result, 235 | ...promise, 236 | }; 237 | } 238 | -------------------------------------------------------------------------------- /src/useRoute/index.md: -------------------------------------------------------------------------------- 1 | # useRoute 2 | 3 | 获取当前页路由信息。 4 | 5 | ```typescript 6 | import { useRoute } from '@uni-helper/uni-use'; 7 | 8 | const route = useRoute(); 9 | ``` 10 | -------------------------------------------------------------------------------- /src/useRoute/index.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from '../useRouter'; 2 | 3 | /** 4 | * 获取当前页路由信息 5 | * 6 | * @deprecated use `useRouter().currentUrl` instead 7 | */ 8 | export function useRoute() { 9 | const { route } = useRouter(); 10 | return route; 11 | } 12 | -------------------------------------------------------------------------------- /src/useRouter/index.md: -------------------------------------------------------------------------------- 1 | # useRouter 2 | 3 | 路由相关的操作和变量 4 | 5 | ## 使用方式 6 | 7 | ```ts 8 | import { tabBar } from '@/pages.json'; 9 | import { useRouter } from '@uni-helper/uni-use'; 10 | 11 | const router = useRouter({ 12 | /** 13 | * 是否尝试跳转 tabBar 14 | * 开启后,使用 navigate / redirect 将会先尝试 tabBar 15 | * @default true 16 | */ 17 | tryTabBar: true, 18 | /** 19 | * pages.json 里的 tabBar list 配置 20 | * tryTabBar 开启时,会判断跳转页面 21 | * 全局配置,仅需要配置一次 22 | */ 23 | tabBarList: tabBar.list, 24 | }); 25 | 26 | // 如果上面的 tryTabBar 设定为 false,或非常确定是 tabbar 页面,可以直接使用 switchTab 27 | router.switchTab({ url: '/pages/tabbar/tabbar1' }); 28 | 29 | // 路由跳转,参数和 uniapp 的一致 30 | // 当 tryTabBar = true 时,会自动判断 tabBar 页面进行跳转 31 | router.navigate({ url: '/pages/topics/index' }); 32 | 33 | // 路由重定向,参数和 uniapp 的一致 34 | // 当 tryTabBar = true 时,会自动判断 tabBar 页面进行重定向 35 | router.redirect({ url: '/pages/auth/login' }); 36 | 37 | // 路由重定向,并清空当前页面栈 38 | router.reLaunch({ url: '/pages/auth/login' }); 39 | 40 | // 后退 41 | router.back(); 42 | ``` 43 | -------------------------------------------------------------------------------- /src/useRouter/index.ts: -------------------------------------------------------------------------------- 1 | import type { AppJson } from '@dcloudio/uni-cli-shared'; 2 | import type { RequiredOnly } from '../types'; 3 | import { computed, ref } from 'vue'; 4 | import { tryOnBackPress } from '../tryOnBackPress'; 5 | import { pathResolve } from '../utils'; 6 | 7 | /** 获取当前页面栈信息 */ 8 | const pages = ref([]); 9 | const pageLength = computed(() => pages.value.length); // 使用 computed 可触发依赖项更新 10 | 11 | /** 获取当前页信息 */ 12 | // at is not supported 13 | const current = computed(() => pages.value?.[pageLength.value - 1]); 14 | /** 获取前一页信息 */ 15 | const prev = computed(() => 16 | pageLength.value > 1 ? pages.value[pageLength.value - 2] : pages.value?.[pageLength.value - 1], 17 | ); 18 | 19 | /** 获取当前页路由信息 */ 20 | const currentUrl = computed(() => current.value?.route || '/'); 21 | /** 获取前一页路由信息 */ 22 | const prevUrl = computed(() => prev.value?.route); 23 | 24 | let tabBarUrls: string[] = []; 25 | 26 | let isAddInterceptors = false; 27 | let isBindBackPress = false; 28 | 29 | function initIfNotInited() { 30 | // 默认路由的拦截 31 | if (!isAddInterceptors) { 32 | isAddInterceptors = true; 33 | 34 | uni.addInterceptor('navigateTo', { complete: refreshCurrentPages }); 35 | uni.addInterceptor('redirectTo', { complete: refreshCurrentPages }); 36 | uni.addInterceptor('reLaunch', { complete: refreshCurrentPages }); 37 | uni.addInterceptor('switchTab', { complete: refreshCurrentPages }); 38 | uni.addInterceptor('navigateBack', { complete: refreshCurrentPages }); 39 | } 40 | 41 | // 对实体按键 / 顶部导航栏返回按钮进行监听 42 | if (!isBindBackPress) { 43 | isBindBackPress = true; 44 | 45 | tryOnBackPress((e) => { 46 | if (e.from === 'navigateBack') { 47 | return; 48 | } 49 | refreshCurrentPages(); 50 | }).catch(() => { 51 | isBindBackPress = false; 52 | }); 53 | } 54 | 55 | // 每次 init 都更新一次 56 | refreshCurrentPages(); 57 | } 58 | 59 | function refreshCurrentPages() { 60 | pages.value = getCurrentPages(); 61 | } 62 | 63 | function warpPromiseOptions( 64 | opts: T, 65 | resolve: (res: any) => any, 66 | reject: (err: any) => any, 67 | ) { 68 | let { fail, success, complete } = opts as any; 69 | 70 | fail = fail || ((err: any) => err); 71 | success = success || ((res: any) => res); 72 | complete = complete || (() => {}); 73 | 74 | return { 75 | ...opts, 76 | success: (res: any) => resolve(success(res)), 77 | fail: (err: any) => reject(fail(err)), 78 | complete, 79 | }; 80 | } 81 | 82 | /** 切换 tabbar 页面 */ 83 | function switchTab(options: UniNamespace.SwitchTabOptions): Promise { 84 | return new Promise((resolve, reject) => { 85 | uni.switchTab(warpPromiseOptions(options, resolve, reject)); 86 | }); 87 | } 88 | 89 | function navigateTo(options: UniNamespace.NavigateToOptions) { 90 | return new Promise((resolve, reject) => { 91 | uni.navigateTo(warpPromiseOptions(options, resolve, reject)); 92 | }); 93 | } 94 | 95 | function redirectTo(options: UniNamespace.RedirectToOptions) { 96 | return new Promise((resolve, reject) => { 97 | uni.redirectTo(warpPromiseOptions(options, resolve, reject)); 98 | }); 99 | } 100 | 101 | /** 重定向,并清空当前页面栈 */ 102 | function reLaunch(options: UniNamespace.ReLaunchOptions): Promise { 103 | return new Promise((resolve, reject) => { 104 | uni.reLaunch(warpPromiseOptions(options, resolve, reject)); 105 | }); 106 | } 107 | 108 | /** 后退 */ 109 | function back(options?: UniNamespace.NavigateBackOptions): Promise { 110 | return new Promise((resolve, reject) => { 111 | uni.navigateBack(warpPromiseOptions(options || {}, resolve, reject)); 112 | }); 113 | } 114 | 115 | /** 路由跳转 `tryTabBar = true` 时,自动判断是否 tabbar 页面 */ 116 | function trySwitchTab( 117 | tryTabBar: boolean, 118 | forward: FN, 119 | options: Parameters[0], 120 | ): Promise { 121 | // 不尝试 tabbar 页面,直接跳转 122 | if (!tryTabBar) { 123 | return forward(options); 124 | } 125 | 126 | // 未设置 tabBarList,先尝试 switchTab,报错再尝试跳转 127 | if (tabBarUrls.length === 0) { 128 | return switchTab(options).catch(() => forward(options)); 129 | } 130 | 131 | const url = typeof options.url === 'string' ? options.url : options.url.toString(); 132 | 133 | // 如果是 tabBar 页面,则直接 switchTab 134 | if (isTabBarPath(url)) { 135 | return switchTab(options); 136 | } 137 | 138 | // 不是 tabBar,直接跳转 139 | return forward(options); 140 | } 141 | 142 | function isTabBarPath(path: string) { 143 | const target = pathResolve(path); 144 | const tabbar = tabBarUrls.find(url => url === target || `${url}/` === target); 145 | return tabbar !== undefined; 146 | } 147 | 148 | type UniTabBarItem = Exclude['list'][number]; 149 | 150 | type TabBarItem = RequiredOnly; 151 | 152 | export interface UseRouterOptions { 153 | /** 154 | * 是否尝试跳转 tabBar 开启后,使用 navigate / redirect 将会先尝试 tabBar 155 | * 156 | * @default true 157 | */ 158 | tryTabBar?: boolean; 159 | /** 160 | * 全局配置,仅需要配置一次 161 | * 配置 tryTabBar 开启时,会判断跳转页面 162 | * 163 | * 可填入 pages.json 里的 tabBar list 或仅 tabbar 的 url 数组 164 | */ 165 | tabBarList?: Array; 166 | } 167 | 168 | /** 169 | * 路由操作的封装 170 | * 171 | * UNIAPP 官方文档 @see https://uniapp.dcloud.net.cn/api/router.html 172 | */ 173 | export function useRouter(options: UseRouterOptions = {}) { 174 | initIfNotInited(); 175 | 176 | const { tryTabBar = true } = options; 177 | 178 | if (options.tabBarList) { 179 | const urls: string[] = []; 180 | for (const item of options.tabBarList) { 181 | if (typeof item === 'string') { 182 | urls.push(item); 183 | } 184 | else { 185 | urls.push(item.pagePath); 186 | } 187 | } 188 | tabBarUrls = urls.filter(url => !!url); 189 | } 190 | 191 | /** 路由跳转 */ 192 | function navigate(options: UniNamespace.NavigateToOptions): Promise { 193 | return trySwitchTab(tryTabBar, navigateTo, options); 194 | } 195 | 196 | /** 路由重定向 */ 197 | function redirect(options: UniNamespace.RedirectToOptions): Promise { 198 | return trySwitchTab(tryTabBar, redirectTo, options); 199 | } 200 | 201 | return { 202 | /** 获取当前页面栈信息 */ 203 | pages, 204 | /** 获取当前页信息 */ 205 | current, 206 | /** @deprecated 弃用,请使用 current */ 207 | page: current, 208 | /** 获取当前页路由信息 */ 209 | currentUrl, 210 | /** @deprecated 弃用,请使用 currentUrl */ 211 | route: currentUrl, 212 | /** 获取前一页信息 */ 213 | prev, 214 | /** @deprecated 弃用,请使用 prev */ 215 | prevPage: prev, 216 | /** 获取前一页路由信息 */ 217 | prevUrl, 218 | /** @deprecated 弃用,请使用 prevUrl */ 219 | prevRoute: prevUrl, 220 | /** 切换 tabbar 页面。 */ 221 | switchTab, 222 | /** 路由跳转 */ 223 | navigate, 224 | /** 路由重定向 */ 225 | redirect, 226 | /** 重定向,并清空当前页面栈 */ 227 | reLaunch, 228 | /** 后退 */ 229 | back, 230 | }; 231 | } 232 | -------------------------------------------------------------------------------- /src/useScanCode/index.md: -------------------------------------------------------------------------------- 1 | # useScanCode 2 | 3 | 返回一个方法,调用后调起客户端扫码界面。 4 | 5 | ```typescript 6 | import { useScanCode } from '@uni-helper/uni-use'; 7 | 8 | const scan = useScanCode({ 9 | /* 传入配置 */ 10 | }); 11 | scan(); // 调起扫码 12 | ``` 13 | 14 | 可以传入一个对象来更新已有配置,这样会使用 [扩展运算符](https://es6.ruanyifeng.com/#docs/object#%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%89%A9%E5%B1%95%E8%BF%90%E7%AE%97%E7%AC%A6) 来确认最终配置。 15 | 16 | ```typescript 17 | scan({ 18 | /* 新传入配置 */ 19 | }); 20 | ``` 21 | -------------------------------------------------------------------------------- /src/useScanCode/index.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeComputedRef } from '../types'; 2 | import { resolveUnref } from '@vueuse/core'; 3 | import { reactive } from 'vue'; 4 | 5 | export interface UniScanCodeOptions extends UniApp.ScanCodeOptions {} 6 | export type ScanCodeOptions = MaybeComputedRef; 7 | export type UseScanCodeOptions = ScanCodeOptions; 8 | 9 | /** 10 | * 返回一个方法,调用后调起客户端扫码界面 11 | * 12 | * https://uniapp.dcloud.net.cn/api/system/barcode?id=scancode 13 | */ 14 | export function useScanCode(options?: UseScanCodeOptions) { 15 | /** 16 | * 调起客户端扫码界面 17 | * 18 | * https://uniapp.dcloud.net.cn/api/system/barcode?id=scancode 19 | */ 20 | return function scanCode(newOptions?: ScanCodeOptions) { 21 | return uni.scanCode( 22 | reactive({ 23 | ...resolveUnref(options), 24 | ...resolveUnref(newOptions), 25 | }), 26 | ); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/useScreenBrightness/index.md: -------------------------------------------------------------------------------- 1 | # useScreenBrightness 2 | 3 | 获取和设置屏幕亮度。你需要将默认值作为第一个参数传入。 4 | 5 | ```typescript 6 | import { useScreenBrightness } from '@uni-helper/uni-use'; 7 | 8 | const screenBrightness = useScreenBrightness(1); 9 | 10 | // 查看屏幕亮度 11 | console.log('screenBrightness', screenBrightness.value); 12 | // 设置屏幕亮度 13 | screenBrightness.value = 0; 14 | ``` 15 | 16 | 默认使用 `console.error` 输出错误信息,你也可以自定义错误处理。 17 | 18 | ```typescript 19 | import { useScreenBrightness } from '@uni-helper/uni-use'; 20 | 21 | const screenBrightness = useScreenBrightness('', { 22 | onError: (error) => { 23 | console.log(error); 24 | } 25 | }); 26 | ``` 27 | -------------------------------------------------------------------------------- /src/useScreenBrightness/index.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigurableEventFilter, ConfigurableFlush } from '@vueuse/core'; 2 | import type { Ref } from 'vue'; 3 | import type { MaybeComputedRef } from '../types'; 4 | import { watchWithFilter } from '@vueuse/core'; 5 | import { ref } from 'vue'; 6 | import { useInterceptor } from '../useInterceptor'; 7 | 8 | function getScreenBrightness() { 9 | return new Promise((resolve, reject) => { 10 | uni.getScreenBrightness({ 11 | success: ({ value }) => resolve(value), 12 | fail: error => reject(error), 13 | }); 14 | }); 15 | } 16 | 17 | function setScreenBrightness(value: number) { 18 | return new Promise((resolve, reject) => { 19 | uni.setScreenBrightness({ 20 | value, 21 | success: () => resolve(), 22 | fail: error => reject(error), 23 | }); 24 | }); 25 | } 26 | 27 | export interface UseScreenBrightnessOptions extends ConfigurableEventFilter, ConfigurableFlush { 28 | /** 29 | * 是否监听 setScreenBrightness 引起的屏幕亮度变化 30 | * 31 | * @default true 32 | */ 33 | listenToScreenBrightnessChanges?: boolean; 34 | /** 35 | * 错误回调 36 | * 37 | * 默认用 `console.error` 打印错误 38 | */ 39 | onError?: (error: unknown) => void; 40 | } 41 | 42 | /** 43 | * 屏幕亮度 44 | * 45 | * https://uniapp.dcloud.net.cn/api/system/brightness.html 46 | */ 47 | export function useScreenBrightness( 48 | initialValue: MaybeComputedRef, 49 | options: UseScreenBrightnessOptions = {}, 50 | ) { 51 | const { 52 | listenToScreenBrightnessChanges = true, 53 | onError = error => console.error(error), 54 | flush = 'pre', 55 | eventFilter, 56 | } = options; 57 | 58 | const data = ref(initialValue) as Ref; 59 | 60 | async function read() { 61 | try { 62 | data.value = await getScreenBrightness(); 63 | } 64 | catch (error) { 65 | onError(error); 66 | } 67 | } 68 | 69 | read(); 70 | 71 | if (listenToScreenBrightnessChanges) { 72 | useInterceptor('setScreenBrightness', { complete: () => setTimeout(() => read(), 0) }); 73 | } 74 | 75 | watchWithFilter( 76 | data, 77 | async () => { 78 | try { 79 | await setScreenBrightness(data.value); 80 | } 81 | catch (error) { 82 | onError(error); 83 | } 84 | }, 85 | { flush, eventFilter }, 86 | ); 87 | 88 | return data; 89 | } 90 | -------------------------------------------------------------------------------- /src/useSelectorQuery/index.md: -------------------------------------------------------------------------------- 1 | # useSelectorQuery 2 | 3 | `uni.createSelectorQuery` 的封装。 4 | 5 | ```typescript 6 | import { useSelectorQuery } from '@uni-helper/uni-use'; 7 | 8 | const { select, getBoundingClientRect, getFields, getScrollOffset, getContext } 9 | = useSelectorQuery(); 10 | 11 | // 获取 NodeRef 12 | const node = select('#id'); 13 | 14 | // 获取单个 rect 15 | const rect = await getBoundingClientRect('#id'); 16 | 17 | // 获取所有 .selector 的 rect,返回值为 UniApp.NodeInfo[] 18 | const rects = await getBoundingClientRect('.selector', true); 19 | 20 | // getFields,getScrollOffset,getContext 使用方式和 getBoundingClientRect 一致 21 | ``` 22 | -------------------------------------------------------------------------------- /src/useSelectorQuery/index.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, onMounted, ref } from 'vue'; 2 | 3 | export type SelectAll = boolean; 4 | export type QueryResult = M extends true ? UniApp.NodeInfo[] : UniApp.NodeInfo; 5 | 6 | export function useSelectorQuery() { 7 | const query = ref(); 8 | 9 | // 尝试初始化一次,覆盖该方法在 onMounted 之后调用的情况 10 | initQuery(); 11 | onMounted(initQuery); 12 | 13 | function initQuery() { 14 | if (query.value) { 15 | return; 16 | } 17 | const instance = getCurrentInstance(); 18 | if (instance == null) { 19 | return; 20 | } 21 | query.value = uni.createSelectorQuery().in(instance); 22 | } 23 | 24 | function getQuery(): UniApp.SelectorQuery { 25 | initQuery(); 26 | if (query.value == null) { 27 | throw new Error('SelectorQuery initialization failed'); 28 | } 29 | return query.value; 30 | } 31 | 32 | function select(selector: string | UniApp.NodesRef, all?: SelectAll) { 33 | return typeof selector === 'string' 34 | ? all 35 | ? getQuery().selectAll(selector) 36 | : getQuery().select(selector) 37 | : selector; 38 | } 39 | 40 | function getBoundingClientRect>( 41 | selector: string | UniApp.NodesRef, 42 | all?: T, 43 | ) { 44 | return new Promise((resolve) => { 45 | select(selector, all) 46 | .boundingClientRect(res => resolve(res as R)) 47 | .exec(); 48 | }); 49 | } 50 | 51 | function getFields>( 52 | selector: string | UniApp.NodesRef, 53 | fields: UniApp.NodeField, 54 | all?: T, 55 | ) { 56 | return new Promise((resolve) => { 57 | select(selector, all) 58 | .fields(fields, res => resolve(res as R)) 59 | .exec(); 60 | }); 61 | } 62 | 63 | function getScrollOffset>( 64 | selector?: string | UniApp.NodesRef, 65 | ) { 66 | return new Promise((resolve) => { 67 | const node = selector === undefined ? getQuery().selectViewport() : select(selector); 68 | node.scrollOffset(res => resolve(res as R)).exec(); 69 | }); 70 | } 71 | 72 | function getContext>( 73 | selector: string | UniApp.NodesRef, 74 | all?: T, 75 | ) { 76 | return new Promise((resolve) => { 77 | select(selector, all) 78 | .context(res => resolve(res as R)) 79 | .exec(); 80 | }); 81 | } 82 | 83 | return { 84 | query, 85 | getQuery, 86 | select, 87 | getNode: select, 88 | getBoundingClientRect, 89 | getFields, 90 | getScrollOffset, 91 | getContext, 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /src/useSocket/index.md: -------------------------------------------------------------------------------- 1 | # useSocket 2 | 3 | `uni-app` 关于 `socket` 的封装,对标 `@vueuse/core` v10.7.1。使用方法参见 。 4 | 5 | **返回值中含有 task,可自行操作。** 6 | -------------------------------------------------------------------------------- /src/useSocket/index.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Ref } from 'vue'; 2 | import type { MaybeComputedRef } from '../types'; 3 | import { type Fn, resolveRef, tryOnScopeDispose, useIntervalFn } from '@vueuse/core'; 4 | import { computed, ref, watch } from 'vue'; 5 | import { tryOnUnload } from '../tryOnUnload'; 6 | 7 | /** 对标 @vueuse/core v10.7.1 useWebSocket */ 8 | 9 | export type SocketTask = UniApp.SocketTask; 10 | 11 | export type SocketStatus = 'OPEN' | 'CONNECTING' | 'CLOSED'; 12 | 13 | const DEFAULT_PING_MESSAGE = 'ping'; 14 | 15 | export interface UseSocketOptions { 16 | onConnected?: (task: SocketTask, result: UniApp.OnSocketOpenCallbackResult) => void; 17 | onClosed?: (task: SocketTask, result: UniApp.OnSocketCloseOptions) => void; 18 | onError?: (task: SocketTask, error: UniApp.GeneralCallbackResult) => void; 19 | onMessage?: (task: SocketTask, result: UniApp.OnSocketMessageCallbackResult) => void; 20 | 21 | /** 22 | * 是否每过 x 毫秒发送一次心跳信号 23 | * 24 | * @default false 25 | */ 26 | heartbeat?: 27 | | boolean 28 | | { 29 | /** 30 | * 心跳信号信息 31 | * 32 | * @default 'ping' 33 | */ 34 | message?: string | ArrayBuffer; 35 | 36 | /** 37 | * 毫秒级时间间隔 38 | * 39 | * @default 1000 40 | */ 41 | interval?: number; 42 | 43 | /** 44 | * 毫秒级心跳信号响应超时时间 45 | * 46 | * @default 1000 47 | */ 48 | pongTimeout?: number; 49 | }; 50 | 51 | /** 52 | * 是否允许自动重连 53 | * 54 | * @default false 55 | */ 56 | autoReconnect?: 57 | | boolean 58 | | { 59 | /** 60 | * 最大重连次数 61 | * 62 | * 你也可以传一个方法,返回 true 表示需要重连 63 | * 64 | * @default -1 65 | */ 66 | retries?: number | (() => boolean); 67 | 68 | /** 69 | * 毫秒级重连延迟 70 | * 71 | * @default 1000 72 | */ 73 | delay?: number; 74 | 75 | /** 到达最大重连次数时触发 */ 76 | onFailed?: Fn; 77 | }; 78 | 79 | /** 80 | * 是否自动打开连接 81 | * 82 | * @default true 83 | */ 84 | immediate?: boolean; 85 | 86 | /** 87 | * 是否自动关闭连接 88 | * 89 | * @default true 90 | */ 91 | autoClose?: boolean; 92 | 93 | /** 94 | * 是否多实例 95 | * 96 | * @default false 97 | */ 98 | multiple?: boolean; 99 | 100 | /** 头部 */ 101 | headers?: Record; 102 | 103 | /** 104 | * 请求方法 105 | * 106 | * @default 'GET' 107 | */ 108 | method?: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'; 109 | 110 | /** 111 | * 一个或多个子协议字符串的列表 112 | * 113 | * @default [ ] 114 | */ 115 | protocols?: string[]; 116 | } 117 | 118 | export interface UseSocketReturn { 119 | /** websocket 收到的最新数据的引用,可以观察到对传入信息的响应 */ 120 | data: Ref; 121 | 122 | /** 当前 websocket 状态,只能是 OPEN、CONNECTING 和 CLOSED 之一 */ 123 | status: Ref; 124 | 125 | /** 当前 websocket 是否处于 OPEN 状态 */ 126 | isOpen: ComputedRef; 127 | 128 | /** 当前 websocket 是否处于 CONNECTING 状态 */ 129 | isConnecting: ComputedRef; 130 | 131 | /** 当前 websocket 是否处于 CLOSED 状态 */ 132 | isClosed: ComputedRef; 133 | 134 | /** 关闭 websocket 连接 */ 135 | close: SocketTask['close']; 136 | 137 | /** 138 | * 打开 websocket 连接 139 | * 140 | * 如果存在已打开的 websocket 连接,会先关闭它再打开一个新的连接 141 | */ 142 | open: Fn; 143 | 144 | /** 145 | * 发送数据 146 | * 147 | * @param data 148 | * @param useBuffer 当 websocket 连接尚未打开时,是否把数据存储在 buffer 中并在连接打开时发送它们。默认为 true。 149 | */ 150 | send: (data: string | ArrayBuffer, useBuffer?: boolean) => boolean; 151 | 152 | /** SocketTask 实例引用 */ 153 | task: Ref; 154 | } 155 | 156 | function resolveNestedOptions(options: T | true): T { 157 | if (options === true) { 158 | return {} as T; 159 | } 160 | return options; 161 | } 162 | 163 | /** 164 | * 响应式的 Socket 客户端 165 | * 166 | * https://uniapp.dcloud.net.cn/api/request/websocket.html 167 | */ 168 | export function useSocket( 169 | url: MaybeComputedRef, 170 | options: UseSocketOptions = {}, 171 | ): UseSocketReturn { 172 | const { 173 | onConnected, 174 | onClosed, 175 | onError, 176 | onMessage, 177 | heartbeat = false, 178 | autoReconnect = false, 179 | immediate = true, 180 | autoClose = true, 181 | multiple = false, 182 | headers, 183 | method = 'GET', 184 | protocols = [], 185 | } = options; 186 | 187 | const data: Ref = ref(null); 188 | const status = ref('CLOSED'); 189 | const isOpen = computed(() => status.value === 'OPEN'); 190 | const isConnecting = computed(() => status.value === 'CONNECTING'); 191 | const isClosed = computed(() => status.value === 'CLOSED'); 192 | const taskRef = ref(); 193 | const urlRef = resolveRef(url); 194 | 195 | let heartbeatPause: Fn | undefined; 196 | let heartbeatResume: Fn | undefined; 197 | 198 | let explicitlyClosed = false; 199 | let retried = 0; 200 | 201 | let bufferedData: (string | ArrayBuffer)[] = []; 202 | 203 | let pongTimeoutWait: ReturnType | undefined; 204 | 205 | const _sendBuffer = () => { 206 | if (bufferedData.length > 0 && taskRef.value && status.value === 'OPEN') { 207 | for (const buffer of bufferedData) { 208 | taskRef.value.send({ data: buffer }); 209 | } 210 | bufferedData = []; 211 | } 212 | }; 213 | 214 | const resetHeartbeat = () => { 215 | clearTimeout(pongTimeoutWait); 216 | pongTimeoutWait = undefined; 217 | }; 218 | 219 | // 1000 表示正常关闭 220 | // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code 221 | // https://uniapp.dcloud.net.cn/api/request/socket-task.html#sockettask-close 222 | const close: SocketTask['close'] = ({ code = 1000, reason } = {}) => { 223 | if (!taskRef.value) { 224 | return; 225 | } 226 | explicitlyClosed = true; 227 | heartbeatPause?.(); 228 | taskRef.value.close({ code, reason }); 229 | }; 230 | 231 | const send = (data: string | ArrayBuffer, useBuffer = true) => { 232 | if (!taskRef.value || status.value !== 'OPEN') { 233 | if (useBuffer) { 234 | bufferedData.push(data); 235 | } 236 | return false; 237 | } 238 | _sendBuffer(); 239 | taskRef.value.send({ data }); 240 | return true; 241 | }; 242 | 243 | const _init = () => { 244 | if (explicitlyClosed || urlRef.value === undefined) { 245 | return; 246 | } 247 | 248 | const task = uni.connectSocket({ 249 | url: urlRef.value, 250 | multiple, 251 | header: headers, 252 | method, 253 | protocols, 254 | complete: () => { 255 | // just for correct types 256 | }, 257 | } as UniApp.ConnectSocketOption) as unknown as UniApp.SocketTask; 258 | taskRef.value = task; 259 | status.value = 'CONNECTING'; 260 | 261 | task.onOpen((result) => { 262 | status.value = 'OPEN'; 263 | onConnected?.(task, result); 264 | heartbeatResume?.(); 265 | _sendBuffer(); 266 | }); 267 | 268 | task.onClose((result) => { 269 | status.value = 'CLOSED'; 270 | taskRef.value = undefined; 271 | onClosed?.(task, result); 272 | if (!explicitlyClosed && autoReconnect) { 273 | const { retries = -1, delay = 1000, onFailed } = resolveNestedOptions(autoReconnect); 274 | retried += 1; 275 | if (typeof retries === 'number' && (retries < 0 || retried < retries)) { 276 | setTimeout(_init, delay); 277 | } 278 | else if (typeof retries === 'function' && retries()) { 279 | setTimeout(_init, delay); 280 | } 281 | else { onFailed?.(); } 282 | } 283 | }); 284 | 285 | task.onError((error) => { 286 | onError?.(task, error); 287 | }); 288 | 289 | task.onMessage((result) => { 290 | if (heartbeat) { 291 | resetHeartbeat(); 292 | const { message = DEFAULT_PING_MESSAGE } = resolveNestedOptions(heartbeat); 293 | if (result.data === message) { 294 | return; 295 | } 296 | } 297 | data.value = result.data; 298 | onMessage?.(task, result); 299 | }); 300 | }; 301 | 302 | if (heartbeat) { 303 | const { 304 | message = DEFAULT_PING_MESSAGE, 305 | interval = 1000, 306 | pongTimeout = 1000, 307 | } = resolveNestedOptions(heartbeat); 308 | 309 | const { pause, resume } = useIntervalFn( 310 | () => { 311 | send(message, false); 312 | if (pongTimeoutWait != null) { 313 | return; 314 | } 315 | pongTimeoutWait = setTimeout(() => { 316 | // 明确关闭连接以避免重试 317 | close({}); 318 | }, pongTimeout); 319 | }, 320 | interval, 321 | { immediate: false }, 322 | ); 323 | 324 | heartbeatPause = pause; 325 | heartbeatResume = resume; 326 | } 327 | 328 | if (autoClose) { 329 | tryOnUnload(() => close({})); 330 | tryOnScopeDispose(() => close({})); 331 | } 332 | 333 | const open = () => { 334 | close({}); 335 | explicitlyClosed = false; 336 | retried = 0; 337 | _init(); 338 | }; 339 | 340 | if (immediate) { 341 | watch(urlRef, open, { immediate: true }); 342 | } 343 | 344 | return { 345 | data, 346 | status, 347 | isOpen, 348 | isConnecting, 349 | isClosed, 350 | close, 351 | send, 352 | open, 353 | task: taskRef, 354 | }; 355 | } 356 | -------------------------------------------------------------------------------- /src/useStorage/index.md: -------------------------------------------------------------------------------- 1 | # useStorage 2 | 3 | `uni-app` 关于 `异步 storage 操作` 的封装。 4 | 5 | UNIAPP官网文档: 6 | 7 | 具体实现借鉴了 `@vueuse/core` v10.7.1。。 8 | 9 | **注意:** 10 | 11 | - 这是异步操作,赋值后,ref 的值会立即生效,但并不会马上写入storage 12 | - 和 uni 的原生 getStorageSync 混用的情况下,有可能会导致 getStorageSync 读取不到值。 13 | - 如需和 uni 的原生 storage 操作混用,或需要同步操作,请使用[`useStorageSync`](../useStorageSync/index.md) 14 | 15 | ```typescript 16 | import { useStorage } from '@uni-helper/uni-use'; 17 | 18 | const token = useStorage('authorization', ''); 19 | 20 | // 赋值 21 | token.value = 'authorization-token'; 22 | 23 | // 读取 24 | console.log(token.value); // authorization-token 25 | ``` 26 | -------------------------------------------------------------------------------- /src/useStorage/index.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, describe, expect, it, vi } from 'vitest'; 2 | import { useStorage } from '.'; 3 | import { useSetup } from '../../test'; 4 | import { sleep } from '../utils'; 5 | 6 | describe('useStorage', () => { 7 | const getItemSpy = vi.spyOn(uni, 'getStorage'); 8 | const setItemSpy = vi.spyOn(uni, 'setStorage'); 9 | 10 | afterEach(() => { 11 | uni.clearStorage(); // uni 的 interceptor 仅支持异步接口 12 | }); 13 | 14 | it('export module', () => { 15 | expect(useStorage).toBeDefined(); 16 | }); 17 | 18 | it('string', async () => { 19 | const key = 'string'; 20 | 21 | const vm = useSetup(() => { 22 | const ref = useStorage(key, 'a'); 23 | return { ref }; 24 | }); 25 | expect(vm.ref).toBe('a'); 26 | expect(getItemSpy).toHaveBeenLastCalledWith({ key }); 27 | 28 | vm.ref = 'b'; 29 | await sleep(200); 30 | expect(vm.ref).toBe('b'); 31 | expect(setItemSpy).toHaveBeenLastCalledWith({ key, data: 'b' }); 32 | }); 33 | 34 | it('number', async () => { 35 | const key = 'number'; 36 | 37 | await sleep(200); 38 | uni.setStorageSync(key, '0'); // 模拟先有值 39 | 40 | await sleep(200); 41 | const store = useStorage(key, 1); // 再初始化 42 | expect(store.value).toBe(0); 43 | 44 | store.value = 2; 45 | await sleep(200); 46 | expect(setItemSpy).toHaveBeenLastCalledWith({ key, data: '2' }); 47 | 48 | store.value = -1; 49 | await sleep(200); 50 | expect(setItemSpy).toHaveBeenLastCalledWith({ key, data: '-1' }); 51 | }); 52 | 53 | it('boolean', async () => { 54 | const key = 'boolean'; 55 | const store = useStorage(key, true); 56 | await sleep(200); 57 | expect(store.value).toBe(true); 58 | }); 59 | 60 | it('null string', async () => { 61 | const key = 'null_string'; 62 | 63 | uni.setStorage({ key, data: 'null' }); 64 | 65 | const store = useStorage(key, null); 66 | await sleep(200); 67 | const { data: storedValue } = await uni.getStorage({ key }); 68 | 69 | expect(store.value).toBe('null'); 70 | expect(storedValue).toBe('null'); 71 | }); 72 | 73 | it('null value', async () => { 74 | const key = 'null_value'; 75 | uni.removeStorage({ key }); 76 | 77 | const store = useStorage(key, null); 78 | 79 | expect(store.value).toBe(null); 80 | 81 | await sleep(200); 82 | expect(uni.getStorage({ key })).rejects.toThrowError(); // 将storage设为null将自动删除uni storage 83 | }); 84 | 85 | it('undefined value', async () => { 86 | const key = 'undefined_value'; 87 | uni.removeStorage({ key }); 88 | 89 | const store = useStorage(key, undefined); 90 | 91 | expect(store.value).toBe(undefined); 92 | 93 | await sleep(200); 94 | expect(uni.getStorage({ key })).rejects.toThrowError(); // 将storage设为undefined将自动删除uni storage 95 | }); 96 | 97 | it('remove value', async () => { 98 | const key = 'remove_value'; 99 | uni.getStorage({ key, data: 'random' }); 100 | 101 | const store = useStorage(key, null); 102 | 103 | store.value = null; 104 | 105 | await sleep(200); 106 | 107 | expect(store.value).toBe(null); 108 | 109 | expect(uni.getStorage({ key })).rejects.toThrowError(); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /src/useStorage/index.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigurableEventFilter, ConfigurableFlush, RemovableRef, WatchPausableReturn } from '@vueuse/core'; 2 | import type { Ref } from 'vue'; 3 | import type { MaybeComputedRef } from '../types'; 4 | import { pausableWatch, resolveUnref, tryOnMounted, tryOnScopeDispose } from '@vueuse/core'; 5 | import { ref, shallowRef } from 'vue'; 6 | import { useInterceptor } from '../useInterceptor'; 7 | 8 | export type UniStorageLike = Pick; 9 | 10 | export interface Serializer { 11 | read: (raw: string) => T; 12 | write: (value: T) => string; 13 | } 14 | 15 | export type DataType = string | number | boolean | object | null; 16 | 17 | export function guessSerializerType( 18 | raw: T, 19 | ) { 20 | return raw == null 21 | ? 'any' 22 | : raw instanceof Set 23 | ? 'set' 24 | : raw instanceof Map 25 | ? 'map' 26 | : raw instanceof Date 27 | ? 'date' 28 | : typeof raw === 'boolean' 29 | ? 'boolean' 30 | : typeof raw === 'string' 31 | ? 'string' 32 | : typeof raw === 'object' 33 | ? 'object' 34 | : Number.isNaN(raw) 35 | ? 'any' 36 | : 'number'; 37 | } 38 | 39 | const StorageSerializers: Record< 40 | 'boolean' | 'object' | 'number' | 'any' | 'string' | 'map' | 'set' | 'date', 41 | Serializer 42 | > = { 43 | boolean: { 44 | read: (v: any) => v === 'true', 45 | write: String, 46 | }, 47 | object: { 48 | read: (v: any) => JSON.parse(v), 49 | write: (v: any) => JSON.stringify(v), 50 | }, 51 | number: { 52 | read: (v: any) => Number.parseFloat(v), 53 | write: String, 54 | }, 55 | any: { 56 | read: (v: any) => v, 57 | write: String, 58 | }, 59 | string: { 60 | read: (v: any) => v, 61 | write: String, 62 | }, 63 | map: { 64 | read: (v: any) => new Map(JSON.parse(v)), 65 | write: (v: any) => JSON.stringify([...(v as Map).entries()]), 66 | }, 67 | set: { 68 | read: (v: any) => new Set(JSON.parse(v)), 69 | write: (v: any) => JSON.stringify([...(v as Set)]), 70 | }, 71 | date: { 72 | read: (v: any) => new Date(v), 73 | write: (v: any) => v.toISOString(), 74 | }, 75 | }; 76 | 77 | interface Data extends Ref { 78 | key: string; 79 | type: 'boolean' | 'object' | 'number' | 'any' | 'string' | 'map' | 'set' | 'date'; 80 | isUpdating: boolean; 81 | serializer: Serializer; 82 | default: T; 83 | /** 写入 storage 的 timer */ 84 | timer: NodeJS.Timeout; 85 | /** data 修改监听器 */ 86 | watch: WatchPausableReturn; 87 | read: () => T; 88 | write: (val: T) => void; 89 | /** 根据 storage 的值,刷新变量。 */ 90 | refresh: () => Data; 91 | /** 将变量的值写入 storage */ 92 | sync: () => void; 93 | /** 清除写入操作的timer */ 94 | clearTimer: () => void; 95 | /** 使用 raw 值更新 data */ 96 | updateByRaw: (raw: string | null | undefined) => void; 97 | } 98 | 99 | const store: Record> = {}; 100 | 101 | export interface UseStorageOptions extends ConfigurableEventFilter, ConfigurableFlush { 102 | /** 103 | * 是否监听深层变化 104 | * 105 | * @default true 106 | */ 107 | deep?: boolean; 108 | /** 109 | * 是否监听 setStorage、removeStorage 和 clearStorage 引起的本地缓存变化 110 | * 111 | * @default true 112 | */ 113 | listenToStorageChanges?: boolean; 114 | /** 115 | * 当本地缓存不存在时,是否把默认值写入缓存 116 | * 117 | * @deprecated 变量ref和storage是响应式的,当storage没值,返回带默认值的ref必然会写入storage 118 | * @default true 119 | */ 120 | writeDefaults?: boolean; 121 | /** 122 | * 是否合并默认值和本地缓存值 123 | * 124 | * 当设置为 true 时,浅合并对象 125 | * 126 | * 你也可以传一个方法来自定义合并 127 | * 128 | * @default false 129 | */ 130 | mergeDefaults?: boolean | ((storageValue: T, defaults: T) => T); 131 | /** 自定义数据序列化 */ 132 | serializer?: Serializer; 133 | /** 134 | * 错误回调 135 | * 136 | * 默认用 `console.error` 打印错误 137 | */ 138 | onError?: (error: unknown) => void; 139 | /** 140 | * 是否使用 shallowRef 141 | * 142 | * @default false 143 | */ 144 | shallow?: boolean; 145 | /** 146 | * Wait for the component to be mounted before reading the storage. 147 | * 148 | * @default false 149 | */ 150 | initOnMounted?: boolean; 151 | /** 异步 storage */ 152 | storage?: UniStorageLike; 153 | } 154 | 155 | export function useStorage( 156 | key: string, 157 | initialValue?: MaybeComputedRef, 158 | options?: UseStorageOptions, 159 | ): RemovableRef; 160 | 161 | export function useStorage( 162 | key: string, 163 | initialValue: MaybeComputedRef, 164 | options?: UseStorageOptions, 165 | ): RemovableRef; 166 | 167 | /** 168 | * 响应式的本地缓存(异步) 169 | * 170 | * https://uniapp.dcloud.net.cn/api/storage/storage.html 171 | */ 172 | export function useStorage( 173 | key: string, 174 | initialValue?: MaybeComputedRef, 175 | options: UseStorageOptions = {}, 176 | ): RemovableRef { 177 | const { 178 | flush = 'pre', 179 | deep = true, 180 | listenToStorageChanges = true, 181 | mergeDefaults = false, 182 | shallow = false, 183 | eventFilter, 184 | onError = error => console.error(error), 185 | initOnMounted, 186 | storage = uni as UniStorageLike, 187 | } = options; 188 | 189 | const rawInit = resolveUnref(initialValue) as T; 190 | 191 | const type = guessSerializerType(rawInit); 192 | 193 | const hasStore = !!store[key]; 194 | 195 | const data = hasStore ? store[key] : (shallow ? shallowRef : ref)(rawInit) as Ref as Data; 196 | 197 | const serializer = options.serializer ?? StorageSerializers[type]; 198 | 199 | data.key = key; 200 | data.type = type; 201 | data.serializer = serializer; 202 | data.isUpdating = false; 203 | data.default = rawInit; 204 | data.read = readStorage; 205 | data.write = writeStorageImmediately; 206 | data.refresh = () => { 207 | data.read(); 208 | return data; 209 | }; 210 | data.sync = () => data.write(data.value); 211 | data.clearTimer = clearTimer; 212 | data.updateByRaw = updateByRaw; 213 | 214 | store[key] = data; // 重新映射 215 | 216 | if (hasStore) { 217 | // 不重复读数据 218 | return data; 219 | } 220 | 221 | if (initOnMounted) { 222 | tryOnMounted(data.read); 223 | } 224 | else { 225 | data.read(); 226 | } 227 | 228 | data.watch = pausableWatch(data, () => !data.isUpdating && writeStorage(data.value), { flush, deep, eventFilter }); 229 | 230 | if (listenToStorageChanges) { 231 | listenDataChange(data); 232 | } 233 | 234 | tryOnScopeDispose(clearTimer); 235 | 236 | function clearTimer() { 237 | data.timer && clearTimeout(data.timer); 238 | } 239 | 240 | function writeStorage(val: T) { 241 | clearTimer(); 242 | // 如果是同步操作,则直接写 storage 243 | if (flush === 'sync') { 244 | writeStorageImmediately(val); 245 | return; 246 | } 247 | 248 | data.timer = setTimeout(() => writeStorageImmediately(val), 100); 249 | } 250 | 251 | function writeStorageImmediately(val: T) { 252 | clearTimer(); 253 | 254 | if (data.isUpdating) { 255 | return; 256 | } 257 | 258 | try { 259 | data.isUpdating = true; 260 | 261 | if (val == null) { 262 | storage.removeStorage({ 263 | key, 264 | fail: error => onError(error), 265 | }); 266 | clearTimer(); 267 | return; 268 | } 269 | const serialized = data.serializer.write(val); 270 | storage.setStorage({ 271 | key, 272 | data: serialized, 273 | fail: error => onError(error), 274 | }); 275 | } 276 | catch (error) { 277 | onError(error); 278 | } 279 | finally { 280 | data.isUpdating = false; 281 | } 282 | } 283 | 284 | function updateByRaw(raw: string | null | undefined) { 285 | try { 286 | if (raw == null) { 287 | // 没有对应的值,直接使用默认值 288 | data.value = data.default; 289 | return; 290 | } 291 | 292 | // 解析 value 293 | const value: T = data.serializer.read(raw); 294 | 295 | if (mergeDefaults) { 296 | // 如果是方法,调用 297 | if (typeof mergeDefaults === 'function') { 298 | data.value = mergeDefaults(value, data.default); 299 | return; 300 | } 301 | 302 | // 如果是对象,浅合并 303 | if (type === 'object' && !Array.isArray(value)) { 304 | data.value = { ...(data.default as any), ...(value as any) }; 305 | return; 306 | } 307 | } 308 | 309 | // 有对应的值,不需要合并 310 | data.value = value; 311 | } 312 | catch (err: any) { 313 | onError(err); 314 | } 315 | }; 316 | 317 | function readStorage() { 318 | // 读取本地缓存值 319 | storage.getStorage({ 320 | key: data.key, 321 | success: ({ data: raw }) => { 322 | updateByRaw(raw); 323 | }, 324 | fail: () => { 325 | updateByRaw(undefined); 326 | }, 327 | }); 328 | 329 | return data.value; 330 | } 331 | 332 | return data; 333 | } 334 | 335 | function listenDataChange(data: Data) { 336 | useInterceptor('setStorage', { 337 | invoke: (args) => { 338 | if (args[0].key !== data.key) { 339 | return false; 340 | } 341 | // 非主动更新 342 | if (!data.isUpdating) { 343 | data.isUpdating = true; 344 | 345 | const raw = (typeof args[0].data !== 'string' && args[0].data != null) 346 | ? JSON.stringify(args[0].data) 347 | : args[0].data; 348 | 349 | data.updateByRaw(raw); 350 | 351 | data.isUpdating = false; 352 | } 353 | }, 354 | }); 355 | useInterceptor('removeStorage', { 356 | invoke: (args) => { 357 | if (args[0].key !== data.key) { 358 | return false; 359 | } 360 | // 非主动更新 361 | if (!data.isUpdating) { 362 | data.isUpdating = true; 363 | data.value = undefined as unknown as T; 364 | data.isUpdating = false; 365 | } 366 | }, 367 | }); 368 | useInterceptor('clearStorage', { 369 | complete: () => { 370 | data.isUpdating = true; 371 | data.value = undefined as unknown as T; 372 | data.isUpdating = false; 373 | }, 374 | }); 375 | 376 | useInterceptor('setStorageSync', { 377 | invoke: (args) => { 378 | if (args[0] !== data.key) { 379 | return false; 380 | } 381 | // 非主动更新 382 | if (!data.isUpdating) { 383 | data.isUpdating = true; 384 | 385 | const raw = (typeof args[1] !== 'string' && args[1] != null) 386 | ? JSON.stringify(args[1]) 387 | : args[1]; 388 | 389 | data.updateByRaw(raw); 390 | 391 | data.isUpdating = false; 392 | } 393 | }, 394 | }); 395 | useInterceptor('removeStorageSync', { 396 | invoke: (args) => { 397 | if (args[0] !== data.key) { 398 | return false; 399 | } 400 | // 非主动更新 401 | if (!data.isUpdating) { 402 | data.isUpdating = true; 403 | data.value = undefined as unknown as T; 404 | data.isUpdating = false; 405 | } 406 | }, 407 | }); 408 | useInterceptor('clearStorageSync', { 409 | complete: () => { 410 | data.isUpdating = true; 411 | data.value = undefined as unknown as T; 412 | data.isUpdating = false; 413 | }, 414 | }); 415 | } 416 | -------------------------------------------------------------------------------- /src/useStorageAsync/index.md: -------------------------------------------------------------------------------- 1 | # useStorageAsync (废弃,请使用 useStorage) 2 | 3 | `uni-app` 关于 `异步 storage 操作` 的封装。 4 | 5 | UNIAPP官网文档: 6 | 7 | 具体实现借鉴了 `@vueuse/core` v10.7.1。。 8 | 9 | **注意:** 10 | 11 | - 这是异步操作,赋值后,ref 的值会生效,但并不会马上写入storage 12 | - 和 uni 的原生 getStorageSync 混用的情况下,有可能会导致 getStorageSync 读取不到值。 13 | - 如需和 uni 的原生 storage 操作混用,或需要同步操作,请使用[`useStorageSync`](../useStorageSync/index.md) 14 | 15 | ```typescript 16 | import { useStorageAsync } from '@uni-helper/uni-use'; 17 | 18 | const token = useStorageAsync('authorization', ''); 19 | 20 | // 赋值 21 | token.value = 'authorization-token'; 22 | 23 | // 读取 24 | console.log(token.value); // authorization-token 25 | ``` 26 | -------------------------------------------------------------------------------- /src/useStorageAsync/index.ts: -------------------------------------------------------------------------------- 1 | /** @deprecated use `useStorage` instead */ 2 | export { useStorage as useStorageAsync } from '../useStorage'; 3 | -------------------------------------------------------------------------------- /src/useStorageSync/index.md: -------------------------------------------------------------------------------- 1 | # useStorageSync 2 | 3 | `uni-app` 关于 `同步 storage 操作` 的封装。 4 | 5 | UNIAPP官网文档: 6 | 7 | **注意:** 8 | 9 | - 这是同步操作,赋值后会立即写入storage,直到写入结束前会一直阻塞线程 10 | - 如果需要非阻塞,请使用[`useStorage`](../useStorage/index.md) 11 | - 如无须使用 uni 原生的 storage 操作,建议使用异步 [`useStorage`](../useStorage/index.md) 12 | 13 | ```typescript 14 | import { useStorageSync } from '@uni-helper/uni-use'; 15 | 16 | const token = useStorageSync('authorization', ''); 17 | 18 | // 赋值 19 | token.value = 'authorization-token'; 20 | 21 | // 读取 22 | console.log(token.value); // authorization-token 23 | ``` 24 | -------------------------------------------------------------------------------- /src/useStorageSync/index.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigurableFlushSync, RemovableRef } from '@vueuse/core'; 2 | import type { MaybeComputedRef } from '../types'; 3 | import type { UniStorageLike, UseStorageOptions } from '../useStorage'; 4 | import { useStorage } from '../useStorage'; 5 | 6 | export type UniStorageSyncLike = Pick; 7 | 8 | // uni is not defined 9 | let UniStorage: UniStorageLike; 10 | function initUniStorageIfNotInited() { 11 | if (UniStorage) { 12 | return; 13 | } 14 | 15 | UniStorage = parseUniStorageLike({ 16 | getStorageSync: uni.getStorageSync, 17 | setStorageSync: uni.setStorageSync, 18 | removeStorageSync: uni.removeStorageSync, 19 | }); 20 | } 21 | 22 | function parseUniStorageLike(storageSync: UniStorageSyncLike) { 23 | const storage: UniStorageLike = { 24 | getStorage: ({ key, success, fail, complete }: UniNamespace.GetStorageOptions) => { 25 | try { 26 | const data = storageSync.getStorageSync(key); 27 | success && success({ data }); 28 | } 29 | catch (error) { 30 | fail && fail(error); 31 | } 32 | finally { 33 | complete && complete(void 0); 34 | } 35 | }, 36 | setStorage: ({ key, data, success, fail, complete }: UniNamespace.SetStorageOptions) => { 37 | try { 38 | const raw = storageSync.setStorageSync(key, data); 39 | success && success({ data: raw }); 40 | } 41 | catch (error) { 42 | fail && fail(error); 43 | } 44 | finally { 45 | complete && complete(void 0); 46 | } 47 | }, 48 | removeStorage: ({ key, success, fail, complete }: UniNamespace.RemoveStorageOptions) => { 49 | try { 50 | storageSync.removeStorageSync(key); 51 | success && success({ data: void 0 }); 52 | } 53 | catch (error) { 54 | fail && fail(error); 55 | } 56 | finally { 57 | complete && complete(void 0); 58 | } 59 | }, 60 | }; 61 | 62 | return storage; 63 | } 64 | 65 | export interface UseStorageSyncOptions 66 | extends Omit, 'flush' | 'storage'>, 67 | ConfigurableFlushSync { 68 | /** 同步 storage */ 69 | storage?: UniStorageSyncLike; 70 | } 71 | 72 | export function useStorageSync( 73 | key: string, 74 | initialValue: MaybeComputedRef, 75 | options?: UseStorageSyncOptions, 76 | ): RemovableRef; 77 | export function useStorageSync( 78 | key: string, 79 | initialValue: MaybeComputedRef, 80 | options?: UseStorageSyncOptions, 81 | ): RemovableRef; 82 | export function useStorageSync( 83 | key: string, 84 | initialValue: MaybeComputedRef, 85 | options?: UseStorageSyncOptions, 86 | ): RemovableRef; 87 | export function useStorageSync( 88 | key: string, 89 | initialValue: MaybeComputedRef, 90 | options?: UseStorageSyncOptions, 91 | ): RemovableRef; 92 | export function useStorageSync( 93 | key: string, 94 | initialValue: MaybeComputedRef, 95 | options?: UseStorageSyncOptions, 96 | ): RemovableRef; 97 | 98 | /** 99 | * 响应式的本地缓存 (同步) 100 | * 101 | * https://uniapp.dcloud.net.cn/api/storage/storage.html 102 | */ 103 | export function useStorageSync( 104 | key: string, 105 | initialValue: MaybeComputedRef, 106 | options: UseStorageSyncOptions = {}, 107 | ): RemovableRef { 108 | // fix uni is not defined 109 | initUniStorageIfNotInited(); 110 | 111 | const { flush = 'sync', storage, ...others } = options; 112 | 113 | const storageAsync = storage ? parseUniStorageLike(storage) : UniStorage; 114 | 115 | return useStorage(key, initialValue, { flush, storage: storageAsync, ...others }); 116 | } 117 | -------------------------------------------------------------------------------- /src/useToast/index.md: -------------------------------------------------------------------------------- 1 | # useToast 2 | 3 | 返回一个方法,调用后显示消息提示框。 4 | 5 | ```typescript 6 | import { useToast } from '@uni-helper/uni-use'; 7 | 8 | const showToast = useToast({ 9 | /* 传入配置 */ 10 | }); 11 | const hideToast = showToast(); // 显示消息提示框 12 | hideToast(); // 隐藏消息提示框 13 | ``` 14 | 15 | 可以传入一个对象来更新已有配置,这样会使用 [扩展运算符](https://es6.ruanyifeng.com/#docs/object#%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%89%A9%E5%B1%95%E8%BF%90%E7%AE%97%E7%AC%A6) 来确认最终配置。 16 | 17 | ```typescript 18 | showToast({ 19 | /* 新传入配置 */ 20 | }); 21 | ``` 22 | -------------------------------------------------------------------------------- /src/useToast/index.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeComputedRef } from '../types'; 2 | import { resolveUnref } from '@vueuse/core'; 3 | 4 | export interface UniShowToastOptions extends UniApp.ShowToastOptions {} 5 | export type ShowToastOptions = MaybeComputedRef; 6 | export type UseToastOptions = ShowToastOptions; 7 | 8 | /** 9 | * 返回一个方法,调用后显示消息提示框 10 | * 11 | * https://uniapp.dcloud.net.cn/api/ui/prompt.html#showtoast 12 | */ 13 | export function useToast(options?: UseToastOptions) { 14 | /** 15 | * 显示消息提示框 16 | * 17 | * 返回一个方法,调用后隐藏消息提示框 18 | * 19 | * https://uniapp.dcloud.net.cn/api/ui/prompt.html#showtoast 20 | * 21 | * https://uniapp.dcloud.net.cn/api/ui/prompt.html#hidetoast 22 | */ 23 | return function showToast(newOptions?: ShowToastOptions) { 24 | uni.showToast( 25 | { 26 | ...resolveUnref(options), 27 | ...resolveUnref(newOptions), 28 | }, 29 | ); 30 | /** 31 | * 隐藏消息提示框 32 | * 33 | * https://uniapp.dcloud.net.cn/api/ui/prompt.html#hidetoast 34 | */ 35 | return function hideToast() { 36 | return uni.hideToast(); 37 | }; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/useUploadFile/index.md: -------------------------------------------------------------------------------- 1 | # useUploadFile 2 | 3 | `uni.uploadFile` 的封装,对标 `@vueuse/core` v10.7.1。使用方法参见 。 4 | 5 | **返回值中含有 task,可自行操作。** 6 | -------------------------------------------------------------------------------- /src/useUploadFile/index.ts: -------------------------------------------------------------------------------- 1 | import { until } from '@vueuse/core'; 2 | import { type Ref, ref, type ShallowRef, shallowRef } from 'vue'; 3 | import { isString, noop } from '../utils'; 4 | 5 | /** 对标 @vueuse/core v10.7.1 useAxios */ 6 | 7 | export interface UseUploadFileReturn { 8 | task: ShallowRef; 9 | 10 | /** uni.uploadFile 响应 */ 11 | response: ShallowRef; 12 | 13 | /** uni.uploadFile 响应内的数据 */ 14 | data: Ref; 15 | 16 | /** 上传是否完成 */ 17 | isFinished: Ref; 18 | 19 | /** 上传是否进行中 */ 20 | isLoading: Ref; 21 | 22 | /** 上传是否中止 */ 23 | isAborted: Ref; 24 | 25 | /** 上传间发生的错误 */ 26 | error: ShallowRef; 27 | 28 | /** 中止当前上传 */ 29 | abort: (message?: string | undefined) => void; 30 | 31 | /** abort 别名 */ 32 | cancel: (message?: string | undefined) => void; 33 | 34 | /** isAborted 别名 */ 35 | isCanceled: Ref; 36 | } 37 | export interface StrictUseUploadFileReturn extends UseUploadFileReturn { 38 | /** 手动开始下载 */ 39 | execute: ( 40 | url?: string | UniApp.UploadFileOption, 41 | config?: UniApp.UploadFileOption, 42 | ) => PromiseLike>; 43 | } 44 | export interface EasyUseUploadFileReturn extends UseUploadFileReturn { 45 | /** 手动开始下载 */ 46 | execute: ( 47 | url: string, 48 | config?: UniApp.UploadFileOption, 49 | ) => PromiseLike>; 50 | } 51 | export type OverallUseUploadFileReturn = 52 | | StrictUseUploadFileReturn 53 | | EasyUseUploadFileReturn; 54 | 55 | export interface UseUploadFileOptions { 56 | /** 是否自动开始上传 */ 57 | immediate?: boolean; 58 | 59 | /** 60 | * 是否使用 shallowRef 61 | * 62 | * @default true 63 | */ 64 | shallow?: boolean; 65 | 66 | /** 上传错误时的回调 */ 67 | onError?: (e: UniApp.GeneralCallbackResult) => void; 68 | 69 | /** 上传成功时的回调 */ 70 | onSuccess?: (data: T) => void; 71 | 72 | /** 要使用的初始化数据 */ 73 | initialData?: T; 74 | 75 | /** 是否在执行承诺之前将状态设置为初始状态 */ 76 | resetOnExecute?: boolean; 77 | 78 | /** 上传结束时的回调 */ 79 | onFinish?: (result?: UniApp.GeneralCallbackResult) => void; 80 | } 81 | 82 | export function useUploadFile( 83 | url: string, 84 | config?: UniApp.UploadFileOption, 85 | options?: UseUploadFileOptions, 86 | ): StrictUseUploadFileReturn & PromiseLike>; 87 | export function useUploadFile( 88 | config?: UniApp.UploadFileOption, 89 | ): EasyUseUploadFileReturn & PromiseLike>; 90 | 91 | /** uni.uploadFile 的封装 */ 92 | export function useUploadFile( 93 | ...args: any[] 94 | ): OverallUseUploadFileReturn & PromiseLike> { 95 | const url: string | undefined = typeof args[0] === 'string' ? args[0] : undefined; 96 | const argsPlaceholder = isString(url) ? 1 : 0; 97 | const defaultOptions: UseUploadFileOptions = { 98 | immediate: !!argsPlaceholder, 99 | shallow: true, 100 | }; 101 | let defaultConfig: Partial = {}; 102 | let options: UseUploadFileOptions = defaultOptions; 103 | 104 | if (args.length > 0 + argsPlaceholder) { 105 | defaultConfig = args[0 + argsPlaceholder]; 106 | } 107 | 108 | if (args.length === 3) { 109 | options = args[0 + argsPlaceholder]; 110 | } 111 | 112 | const { 113 | initialData, 114 | shallow, 115 | onSuccess = noop, 116 | onError = noop, 117 | onFinish = noop, 118 | immediate, 119 | resetOnExecute = false, 120 | } = options; 121 | 122 | const task = shallowRef(); 123 | const response = shallowRef(); 124 | const data = shallow ? shallowRef() : ref(); 125 | const isFinished = ref(false); 126 | const isLoading = ref(false); 127 | const isAborted = ref(false); 128 | const error = shallowRef(); 129 | 130 | const abort = (message?: string) => { 131 | if (isFinished.value || !isLoading.value) { 132 | return; 133 | } 134 | // @ts-expect-error no types 135 | task.value?.abort(message); 136 | isAborted.value = true; 137 | isLoading.value = false; 138 | isFinished.value = false; 139 | }; 140 | 141 | const loading = (loading: boolean) => { 142 | isLoading.value = loading; 143 | isFinished.value = !loading; 144 | }; 145 | 146 | const resetData = () => { 147 | if (resetOnExecute) { 148 | data.value = initialData; 149 | } 150 | }; 151 | 152 | const promise = { 153 | then: (...args) => waitUntilFinished().then(...args), 154 | catch: (...args) => waitUntilFinished().catch(...args), 155 | } as Promise>; 156 | 157 | let executeCounter = 0; 158 | const execute: OverallUseUploadFileReturn['execute'] = ( 159 | executeUrl: string | UniApp.UploadFileOption | undefined = url, 160 | config: Partial = {}, 161 | ) => { 162 | error.value = undefined; 163 | const _url = typeof executeUrl === 'string' ? executeUrl : url ?? config.url; 164 | 165 | if (_url === undefined) { 166 | error.value = { 167 | errMsg: 'Invalid URL provided for uni.request.', 168 | }; 169 | isFinished.value = true; 170 | return promise; 171 | } 172 | resetData(); 173 | abort(); 174 | loading(true); 175 | 176 | executeCounter += 1; 177 | const currentExecuteCounter = executeCounter; 178 | isAborted.value = false; 179 | 180 | const _config = { 181 | ...defaultConfig, 182 | ...(typeof executeUrl === 'object' ? executeUrl : config), 183 | url: _url, 184 | }; 185 | task.value = uni.uploadFile({ 186 | ..._config, 187 | success: (r) => { 188 | if (isAborted.value) { 189 | return; 190 | } 191 | _config.success?.(r); 192 | response.value = r; 193 | const result = r.data as unknown as T; 194 | data.value = result; 195 | onSuccess(result); 196 | }, 197 | fail: (e) => { 198 | _config.fail?.(e); 199 | error.value = e; 200 | onError(e); 201 | }, 202 | complete: (r) => { 203 | _config.complete?.(r); 204 | onFinish(r); 205 | if (currentExecuteCounter === executeCounter) { 206 | loading(false); 207 | } 208 | }, 209 | }); 210 | return promise; 211 | }; 212 | if (immediate && url) { 213 | (execute as StrictUseUploadFileReturn['execute'])(); 214 | } 215 | 216 | const result = { 217 | task, 218 | response, 219 | data, 220 | error, 221 | isFinished, 222 | isLoading, 223 | cancel: abort, 224 | isAborted, 225 | isCanceled: isAborted, 226 | abort, 227 | execute, 228 | } as OverallUseUploadFileReturn; 229 | 230 | function waitUntilFinished() { 231 | return new Promise>((resolve, reject) => { 232 | until(isFinished) 233 | .toBe(true) 234 | .then(() => (error.value ? reject(error.value) : resolve(result))); 235 | }); 236 | } 237 | 238 | return { 239 | ...result, 240 | ...promise, 241 | }; 242 | } 243 | -------------------------------------------------------------------------------- /src/useVisible/index.md: -------------------------------------------------------------------------------- 1 | # useVisible 2 | 3 | 获取当前页面显隐状态。 4 | 5 | ```typescript 6 | import { useVisible } from '@uni-helper/uni-use'; 7 | 8 | const isVisible = useVisible(); 9 | ``` 10 | -------------------------------------------------------------------------------- /src/useVisible/index.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { tryOnHide } from '../tryOnHide'; 3 | import { tryOnShow } from '../tryOnShow'; 4 | 5 | /** 获取当前页面是否可见 */ 6 | export function useVisible() { 7 | const isVisible = ref(true); 8 | 9 | tryOnShow(() => { 10 | isVisible.value = true; 11 | }); 12 | 13 | tryOnHide(() => { 14 | isVisible.value = false; 15 | }); 16 | 17 | return isVisible; 18 | } 19 | -------------------------------------------------------------------------------- /src/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { pathResolve } from './utils'; 3 | 4 | describe('utils', () => { 5 | it('pathResolve', () => { 6 | // target 为 / 开头的,直接返回 7 | expect(pathResolve('/a/b', '/c/d')).toBe('/a/b'); 8 | // target 为非 / 开头的,拼接当前路径 9 | expect(pathResolve('a/b', '/c/d/page.html')).toBe('/c/d/a/b'); 10 | // target 为空,返回当前路径 11 | expect(pathResolve('', '/c/d/page.html')).toBe('/c/d/page.html'); 12 | // target 含有 .. ,返回解析后的绝对路径 13 | expect(pathResolve('../a/b', '/c/d/page.html')).toBe('/c/a/b'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from './useRouter'; 2 | 3 | /** 4 | * 判断一个值是否为字符串类型 5 | * 6 | * @param val 待判断的值 7 | * @returns 如果值是字符串类型,则返回true;否则返回false 8 | */ 9 | export function isString(val: unknown): val is string { 10 | return typeof val === 'string'; 11 | } 12 | 13 | /** 14 | * 判断一个值是否为函数类型 15 | * 16 | * @template T 函数的类型 17 | * @param val 待判断的值 18 | * @returns 如果值为函数类型,则返回 true,否则返回 false 19 | */ 20 | export function isFunction any>(val: any): val is T { 21 | return typeof val === 'function'; 22 | } 23 | 24 | /** 25 | * 一个空函数,不执行任何操作。 26 | */ 27 | export function noop() { } 28 | 29 | /** 30 | * 解析路径 31 | * 32 | * @param target 目标路径(不含domain) 33 | * @param current 当前路径(不含domain),默认为当前页面路由 34 | * @returns 解析后的路径 35 | * @throws 当当前路径未定义且无法找到时,抛出错误 36 | */ 37 | export function pathResolve(target: string, current?: string) { 38 | // 如果目标路径已经是绝对路径,则直接返回 39 | if (target.startsWith('/')) { 40 | return target; 41 | } 42 | 43 | // 如果当前路径未定义,则从路由中获取当前路径 44 | if (!current) { 45 | const { currentUrl } = useRouter(); 46 | current = currentUrl.value; 47 | } 48 | 49 | // 如果当前路径未定义,则抛出错误 50 | if (!current) { 51 | throw new Error('The current path is undefined and cannot be found.'); 52 | } 53 | 54 | // 如果目标路径为空,则直接返回当前路径 55 | if (!target) { 56 | return current; 57 | } 58 | 59 | // 如果目标路径以 ./ 开头,则递归调用 pathResolve 函数,将当前路径作为新的当前路径 60 | if (target.startsWith('./')) { 61 | return pathResolve(target.slice(2), current); 62 | } 63 | 64 | // 获取当前路径的页面路径 65 | let currentPaths: string[] = []; 66 | if (current.endsWith('/')) { 67 | currentPaths = current.split('/').filter(part => part !== '' && part !== '.'); 68 | } 69 | else { 70 | currentPaths = current.split('/'); 71 | currentPaths.pop(); // 去除最后的页面路径 72 | currentPaths = currentPaths.filter(part => part !== '' && part !== '.'); 73 | } 74 | 75 | // 获取目标路径的页面路径和页面 76 | let targetPaths: string[] = []; 77 | let targetPage = ''; 78 | if (target.endsWith('/')) { 79 | targetPaths = target.split('/').filter(part => part !== '' && part !== '.'); 80 | } 81 | else { 82 | targetPaths = target.split('/'); 83 | targetPage = targetPaths.pop() || ''; // 去除最后的页面路径 84 | targetPaths = targetPaths.filter(part => part !== '' && part !== '.'); 85 | } 86 | 87 | // 合并路径 88 | const paths = [...currentPaths, ...targetPaths]; 89 | const finalPaths: string[] = []; 90 | for (const p of paths) { 91 | if (p === '..') { 92 | finalPaths.pop(); 93 | } 94 | else { 95 | finalPaths.push(p); 96 | } 97 | } 98 | 99 | // 返回最终路径 100 | return `/${finalPaths.join('/')}/${targetPage}`; 101 | } 102 | 103 | /** 104 | * 延迟执行一段时间 105 | * 106 | * @param ms 延迟时间,单位为毫秒,默认为0,即不延迟 107 | * @returns 返回一个Promise对象 108 | */ 109 | export function sleep(ms = 0) { 110 | return new Promise(resolve => setTimeout(resolve, ms)); 111 | } 112 | 113 | /** 114 | * 判断传入的对象是否是具有 .then 方法的 Thenable 类型 115 | * 116 | * @param promise 需要判断的对象 117 | * @returns 如果传入的对象具有 .then 方法,则返回 true;否则返回 false 118 | */ 119 | export function isThenable(promise: any) { 120 | return typeof promise.then === 'function'; 121 | } 122 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mount'; 2 | -------------------------------------------------------------------------------- /test/mount.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey, Ref } from 'vue'; 2 | import { createApp, defineComponent, h, provide, ref } from 'vue'; 3 | 4 | type InstanceType = V extends { new (...arg: any[]): infer X } ? X : never; 5 | type VM = InstanceType & { unmount: () => void }; 6 | 7 | export function mount(Comp: V) { 8 | const el = document.createElement('div'); 9 | const app = createApp(Comp as any); 10 | 11 | const unmount = () => app.unmount(); 12 | const comp = app.mount(el) as any as VM; 13 | comp.unmount = unmount; 14 | return comp; 15 | } 16 | 17 | export function useSetup(setup: () => V) { 18 | const Comp = defineComponent({ 19 | setup, 20 | render() { 21 | return h('div', []); 22 | }, 23 | }); 24 | 25 | return mount(Comp); 26 | } 27 | 28 | export const Key: InjectionKey> = Symbol('num'); 29 | 30 | export function useInjectedSetup(setup: () => V) { 31 | const Comp = defineComponent({ 32 | setup, 33 | render() { 34 | return h('div', []); 35 | }, 36 | }); 37 | 38 | const Provider = defineComponent({ 39 | components: Comp, 40 | setup() { 41 | provide(Key, ref(1)); 42 | }, 43 | render() { 44 | return h('div', []); 45 | }, 46 | }); 47 | 48 | return mount(Provider); 49 | } 50 | -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- 1 | import { uni } from '@dcloudio/uni-h5/dist/uni-h5.es'; 2 | import { beforeEach, vi } from 'vitest'; 3 | 4 | vi.stubEnv('NODE_ENV', 'test'); 5 | vi.stubEnv('UNI_PLATFORM', 'h5'); 6 | 7 | vi.stubGlobal('uni', uni); 8 | 9 | beforeEach(() => { 10 | document.body.innerHTML = ''; 11 | document.head.innerHTML = ''; 12 | }); 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "baseUrl": ".", 7 | "module": "ESNext", 8 | "moduleResolution": "Node", 9 | "resolveJsonModule": true, 10 | "typeRoots": ["types"] 11 | }, 12 | "include": ["**/*.js", "**/*.cjs", "**/*.mjs", "**/*.ts", "**/*.cts", "**/*.mts"], 13 | "exclude": ["**/node_modules", "**/dist", "playground"] 14 | } 15 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | declare module '@dcloudio/uni-h5/dist/uni-h5.es'; 5 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | resolve: { 6 | alias: { 7 | '@uni-helper/uni-use': resolve(__dirname, 'src/index.ts'), 8 | }, 9 | dedupe: [ 10 | '@dcloudio/uni-app', 11 | ], 12 | }, 13 | test: { 14 | environment: 'jsdom', 15 | setupFiles: [resolve(__dirname, 'test/setup.ts')], 16 | reporters: 'dot', 17 | pool: 'forks', 18 | poolOptions: { 19 | forks: { 20 | singleFork: true, 21 | }, 22 | }, 23 | server: { 24 | deps: { 25 | inline: [ 26 | '@dcloudio/uni-app', 27 | ], 28 | }, 29 | }, 30 | }, 31 | ssr: { 32 | noExternal: [ 33 | /@uni-helper\/uni-use/, 34 | ], 35 | }, 36 | }); 37 | --------------------------------------------------------------------------------