Repository: DaMaiCoding/uni-plus Branch: master Commit: 816af7fe5b79 Files: 91 Total size: 188.2 KB Directory structure: gitextract_kd37txss/ ├── .github/ │ └── workflows/ │ └── deploy-demo.yml ├── .gitignore ├── .hbuilderx/ │ └── launch.json ├── .husky/ │ └── pre-commit ├── .npmrc ├── .prettierignore ├── .release-it.json ├── .stylelintignore ├── .vscode/ │ ├── settings.json │ └── vue3.code-snippets ├── CHANGELOG.md ├── LICENSE ├── README.md ├── commitlint.config.cjs ├── components.d.ts ├── eslint.config.mjs ├── index.html ├── package.json ├── pages.config.ts ├── prettier.config.mjs ├── src/ │ ├── App.vue │ ├── api/ │ │ ├── loginApi.ts │ │ └── testApi.ts │ ├── hooks/ │ │ ├── useEcharts.ts │ │ ├── useI18n.ts │ │ ├── useRequest.ts │ │ └── useTheme.ts │ ├── http/ │ │ └── httpClient.ts │ ├── interceptors/ │ │ ├── index.ts │ │ ├── request.ts │ │ └── router.ts │ ├── layouts/ │ │ ├── default.vue │ │ ├── fullPage.vue │ │ └── theme.vue │ ├── locale/ │ │ ├── en.json │ │ └── zh-Hans.json │ ├── main.ts │ ├── manifest.json │ ├── pages/ │ │ ├── customDemo/ │ │ │ └── index.vue │ │ ├── echartsDemo/ │ │ │ ├── echartsData.ts │ │ │ └── index.vue │ │ ├── i18nDemo/ │ │ │ └── index.vue │ │ ├── index/ │ │ │ └── index.vue │ │ ├── layoutDemo/ │ │ │ └── index.vue │ │ ├── piniaDemo/ │ │ │ └── index.vue │ │ ├── queryDemo/ │ │ │ ├── loginDemo.vue │ │ │ ├── queryTestDemo.vue │ │ │ ├── useRequestDemo.vue │ │ │ └── zPagingDemo.vue │ │ ├── routerDemo/ │ │ │ ├── index.vue │ │ │ ├── login.vue │ │ │ ├── page1.vue │ │ │ ├── page2.vue │ │ │ └── page3.vue │ │ ├── unocssDemo/ │ │ │ └── index.vue │ │ └── wotUiDemo/ │ │ └── index.vue │ ├── pages-sub/ │ │ ├── subDemo/ │ │ │ └── index.vue │ │ └── testDemo/ │ │ └── index.vue │ ├── pages.json │ ├── store/ │ │ ├── index.ts │ │ └── user.ts │ ├── theme.json │ ├── types/ │ │ ├── auto-import.d.ts │ │ ├── gloal.d.ts │ │ └── uni-pages.d.ts │ ├── typings.ts │ ├── uni.scss │ ├── uni_modules/ │ │ └── lime-echart/ │ │ ├── changelog.md │ │ ├── components/ │ │ │ ├── l-echart/ │ │ │ │ ├── canvas.js │ │ │ │ ├── l-echart.uvue │ │ │ │ ├── l-echart.vue │ │ │ │ ├── nvue.js │ │ │ │ ├── utils.js │ │ │ │ └── uvue.uts │ │ │ └── lime-echart/ │ │ │ ├── lime-echart.nvue │ │ │ ├── lime-echart.uvue │ │ │ └── lime-echart.vue │ │ ├── package.json │ │ ├── readme.md │ │ └── static/ │ │ ├── uni.webview.1.5.5.js │ │ └── uvue.html │ └── utils/ │ ├── assets.ts │ ├── index.ts │ ├── log.ts │ ├── qs.ts │ └── router.ts ├── stylelint.config.mjs ├── tsconfig.json ├── unocss.config.ts ├── vite-plugin/ │ └── vite-plugin-directives.ts └── vite.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/deploy-demo.yml ================================================ name: Deploy Pages Demo on: push: branches: ['master'] workflow_dispatch: permissions: contents: read pages: write id-token: write jobs: build: runs-on: ubuntu-latest steps: # 设置服务器时区为东八区 - name: Set time zone run: sudo timedatectl set-timezone 'Asia/Shanghai' - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 with: version: 8 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'pnpm' - name: Install dependencies run: pnpm i --no-frozen-lockfile - name: Build run: pnpm build:h5 - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: ./dist/build/h5 deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* **/.eslintrc* **/.eslintcache* node_modules .DS_Store dist *.local # Editor directories and files .idea *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: .hbuilderx/launch.json ================================================ { // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/ // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数 "version": "0.0", "configurations": [ { "app-plus": { "launchtype": "local" }, "default": { "launchtype": "local" }, "mp-weixin": { "launchtype": "local" }, "type": "uniCloud" }, { "playground": "custom", "type": "uni-app:app-android" } ] } ================================================ FILE: .husky/pre-commit ================================================ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" # Run the pre-commit hook npx --no-install -- lint-staged ================================================ FILE: .npmrc ================================================ # registry = https://registry.npmjs.org registry = https://registry.npmmirror.com strict-peer-dependencies=false auto-install-peers=true shamefully-hoist=true ================================================ FILE: .prettierignore ================================================ # Logs logs *.log # Editor directories and files .vscode .idea **/*.svg # projects .husky node_modules src/uni_modules/ src/static/ dist/ # uniapp 插件生成的文件 src/pages.json src/manifest.json ================================================ FILE: .release-it.json ================================================ { "plugins": { "@release-it/conventional-changelog": { "emoji": true, "emojiPosition": "before", "infile": "CHANGELOG.md", "ignoreRecommendedBump": true, "strictSemVer": true } }, "npm": { "publish": false }, "git": { "commitMessage": "🐳 chore: release v${version}" }, "github": { "release": false, "draft": false } } ================================================ FILE: .stylelintignore ================================================ node_modules src/uni_modules/ src/static/ dist/ **/*.svg ================================================ FILE: .vscode/settings.json ================================================ { // 默认格式化工具选择prettier "editor.defaultFormatter": "esbenp.prettier-vscode", "files.encoding": "utf8", "cSpell.words": [ "Attributify", "autofocus", "autoscan", "bianjiliebiao", "commitlint", "conventionalcommits", "darkmode", "easycom", "echart", "Fbase", "Fdemo", "FILESYSTEMS", "Fpages", "Froute", "infile", "itemclick", "Logined", "miniprogram", "mjsx", "mtsx", "nvue", "plusplus", "Prefixs", "splashscreen", "timedatectl", "tseslint", "unplugin", "VITE" ], "GitCommitPlugin.ShowEmoji": true, // 提交是否显示 emoji 符号 "GitCommitPlugin.MaxSubjectCharacters": 20, // 提交 Subject 最大字符数 // 增加自定义类型,可以配置多个 "GitCommitPlugin.CustomCommitType": [ // 自定义提交类型 // { // "key": "wip", // "label": "wip", // "detail": "正在开发中", // "icon":"🔓️ ", // }, // { // "key": "workflow", // "label": "workflow", // "detail": "工作流程改进", // "icon":"⏳️ ", // }, // { // "key": "types", // "label": "types", // "detail": "类型定义文件修改", // "icon":"🚙 ", // }, ], "GitCommitPlugin.Templates": [ // 自定义提交模板 // { // "templateName": "git-uni-plus", // "templateContent": "():", // "default":true // 是否覆盖默认模板 // } ], // 配置stylelint检查的文件类型范围 "stylelint.validate": ["css", "scss", "vue", "html"], // 与package.json的scripts对应 "stylelint.enable": true, // 开启stylelint "css.validate": false, // 关闭 vsocode 的 css校验 "less.validate": false, // 关闭 vsocode 的 less校验 "scss.validate": false, // 关闭 vsocode 的 scss校验 "[shellscript]": { "editor.defaultFormatter": "foxundermoon.shell-format" }, "[dotenv]": { "editor.defaultFormatter": "foxundermoon.shell-format" }, "[vue]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, // 配置语言的文件关联 "files.associations": { "pages.json": "jsonc", // pages.json 可以写注释 "manifest.json": "jsonc" // manifest.json 可以写注释 }, "explorer.fileNesting.enabled": true, // 开启文件嵌套 "explorer.fileNesting.expand": false, // 默认折叠 "explorer.fileNesting.patterns": { "*.ts": "$(capture).test.ts, $(capture).test.tsx", "*.tsx": "$(capture).test.ts, $(capture).test.tsx", // "*.env": "$(capture).env.*", "README.md": "*.md", "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc", "eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,commitlint.*,prettier.*,stylelint.*,.eslintrc-auto-import.*,.release*", ".env": ".env.*", }, "i18n-ally.localesPaths": [ "src/i18n" ] } ================================================ FILE: .vscode/vue3.code-snippets ================================================ { "Uni-Plus Vue3 SFC": { "scope": "vue", "prefix": "v3", "description": "vue3 的 uni-plus sfc 文件模板", "body": [ "\n", "", "{", " layout: 'default',", " style: {", " navigationBarTitleText: '$2',", " },", "}", "\n", "\n", "\n", "\n", ], } } ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## 1.0.0 (2025-02-11) - ✨ feat: 适配 H5 与 微信小程序端 ([c438d98](https://github.com/DaMaiCoding/uni-plus/commit/c438d98)) - ✨ feat: 完善 首页基本功能 ([27773cd](https://github.com/DaMaiCoding/uni-plus/commit/27773cd)) - ✨ feat: 优化 暗黑模式 与 增加各个案例的链接 ([2a9a70b](https://github.com/DaMaiCoding/uni-plus/commit/2a9a70b)) - ✨ feat: 增加 demo 导航页 ([e107631](https://github.com/DaMaiCoding/uni-plus/commit/e107631)) - ✨ feat: 增加 README.md 与 LICENSE 开源协议 ([582cd6d](https://github.com/DaMaiCoding/uni-plus/commit/582cd6d)) - 🎈 perf: 去掉 vue-i18n,使用自定义代替 ([6833bd9](https://github.com/DaMaiCoding/uni-plus/commit/6833bd9)) - 🎉 init: 项目初始化 ([baf10db](https://github.com/DaMaiCoding/uni-plus/commit/baf10db)) - 🐎 ci: 修复 CI build 报错 ([b5fed26](https://github.com/DaMaiCoding/uni-plus/commit/b5fed26)) - 🐎 ci: 修复 CI build 报错 ([23c00d0](https://github.com/DaMaiCoding/uni-plus/commit/23c00d0)) - 🐎 ci: 增加 在线 DEMO ([65c571b](https://github.com/DaMaiCoding/uni-plus/commit/65c571b)) - 🐞 fix: 修复 全局 CSS 变量不生效问题 ([0d35ac9](https://github.com/DaMaiCoding/uni-plus/commit/0d35ac9)) - 🐞 fix: 修复 uni-plus DEMO 不显示问题 ([d4f3a86](https://github.com/DaMaiCoding/uni-plus/commit/d4f3a86)) - 📃 docs: 优化 README.md ([f1a1b59](https://github.com/DaMaiCoding/uni-plus/commit/f1a1b59)) - 📃 docs: 增加 README 兼容性 环境配置 ([22787a1](https://github.com/DaMaiCoding/uni-plus/commit/22787a1)) ## 0.0.12 (2025-01-02) - ✨ feat: git 提交规范化 ([cc31756](https://gitee.com/FOM/uni-plus/commits/cc31756)) - 🎈 perf: 去掉没必要的 eslint 缓存 ([d70797e](https://gitee.com/FOM/uni-plus/commits/d70797e)) - 🎈 perf: 优化 git 提交 ([208478d](https://gitee.com/FOM/uni-plus/commits/208478d)) ## 0.0.1 (2024-12-31) - ✨ feat: 新增 emoji ([7865661](https://gitee.com/FOM/uni-plus/commits/7865661)) - uni-plus 项目初始化 ([be9e6fa](https://gitee.com/FOM/uni-plus/commits/be9e6fa)) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2024-present, 大麦大麦 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

一个 “超超超” 好用的 uniapp 开发模板

`uni-plus` 提供了 `layout布局`、`请求封装`、`请求拦截`、`权限控制`、`原子CSS`、`路由拦截`、`路由自动导入` 等基础功能,并且配备了 `代码提示`、`代码高亮`、`代码格式化`、`commit 优化` 等开发环境配置,让您的开发更加高效、便捷。

📓 文档地址 | 🌰 预览地址

| H5 | IOS | 安卓 | 微信小程序 | 字节小程序 | 快手小程序 | 支付宝小程序 | 钉钉小程序 | 百度小程序 | | --- | --- | ---- | ---------- | ---------- | ---------- | ------------ | ---------- | ---------- | | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ## ☘️ 环境配置 - Node.js 20+ - Pnpm 9+ - Vue3 3.4+ - TypeScript 4.9+ ## 🦈 mock 配置 这个 `mock` 项目主要为 `demo` 分支的案例,提供 `mock` 数据 如果你不下载这个 `mock` 项目来提供数据的话,里面请求部分的案例,将无法正常显示数据 - [`mock` 项目地址(`github`)](https://github.com/DaMaiCoding/uni-plus-mock) - [`mock` 项目地址(`gitee`)](https://gitee.com/DaMaiCoding/uni-plus-mock) ```shell # mock 项目下载下来后,执行下面命令运行即可 pnpm i pnpm start:dev ``` ## 🎯 快速开始 ```bash pnpm create uni-plus my-project # my-project 为项目名称 pnpm i # 安装依赖 pnpm dev:h5 # 启动 h5 开发环境 ``` ## 📦 运行方式 - 推荐使用 `VScode` 进行开发,因为本模板已经配置好了 `VScode` 的开发环境,包括代码格式化、代码提示、代码高亮、插件等 - `Vscode` 开发的话,非 `h5` 的情况下,执行 `pnpm dev:mp-weixin` 然后打开微信开发者工具,导入 `dist/dev/mp-weixin` 文件,其他平台类似 - 如果你使用 `HBuilderX` 开发,直接打开项目,鼠标选中项目文件文件,然后点击 运行 -> 允许到小程序模拟器,然后选择对应的平台即可 ## 😄 维护者 [大麦大麦(DaMaiCoding)](https://github.com/DaMaiCoding) ## 📄 许可证 完全免费开源 [MIT © 2024-present, 大麦大麦(DaMaiCoding)](./LICENSE) ## 🤔 如何贡献 非常欢迎您的加入![提一个 Issue](https://github.com/DaMaiCoding/uni-plus/issues) 或者提交一个 `Pull Request` **Pull Request:** 1. `Fork` 代码到自己的项目下,不要直接在仓库下建分支 2. 请选择 `dev` 分支,进行 `PR` 3. 提交 `PR` 前请 `rebase`,确保 `commit` 记录的整洁 4. 注意 `commit` 信息规范,要以 `emoji type: 描述信息` 的形式填写,注意 `type` 得是下面规范之中的一个 5. 示例 `commit` 信息:`🐞 fix: 修复 无感刷新 重试失败问题` 6. 可以使用项目中的 `pnpm cz` 进行 `commit` 提交,这样就会默认为 `type` 前面添加 `emoji` 7. 等待作者 `review` 通过后,即可合并 ## ⌛ Git 贡献提交规范 参考 [历史提交记录](https://github.com/DaMaiCoding/uni-plus/commits/dev) - `✨ feat` 新增功能 - `🐞 fix` 修复 bug - `📃 docs` 文档变更 - `🌈 style` 代码格式(仅仅修改了空格、缩进、逗号等等,不改变代码逻辑) - `🦄 refactor` 代码重构,没有加新功能或修复 bug - `🎈 perf` 代码优化,比如提升性能、体验 - `🔧 build` 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等) - `🐳 chore` 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例) - `⏳️ workflow` 工作流程改进 ## ⭐ Star 非常感谢留下星星的 `小哥哥 、小姐姐`,感谢您的支持 ❤ [![Stargazers repo roster for @DaMaiCoding/uni-plus](https://bytecrank.com/nastyox/reporoster/php/stargazersSVG.php?user=DaMaiCoding&repo=uni-plus)](https://github.com/DaMaiCoding/uni-plus/stargazers) ================================================ FILE: commitlint.config.cjs ================================================ // const fs = require('fs') // const path = require('path') // const { execSync } = require('child_process') // const scopes = fs // .readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true }) // .filter(dirent => dirent.isDirectory()) // .map(dirent => dirent.name.replace(/s$/, '')) // // precomputed scope // const scopeComplete = execSync('git status --porcelain || true') // .toString() // .trim() // .split('\n') // .find(r => ~r.indexOf('M src')) // ?.replace(/(\/)/g, '%%') // ?.match(/src%%((\w|-)*)/)?.[1] // ?.replace(/s$/, '') module.exports = { userEmoji: true, // ignores: [commit => commit.includes('init')], extends: ['git-commit-emoji'], rules: { // 'header-max-length': [2, 'always', 108], }, prompt: { /** @use `pnpm commit :f` */ // customScopesAlign: !scopeComplete ? 'top' : 'bottom', // 如果 scope 不完整,则将 customScopesAlign 设置为 top,否则设置为 bottom // defaultScope: scopeComplete, // 如果 scope 完整,则将 defaultScope 设置为 true,否则设置为 false // scopes: [...scopes, 'mock'], allowEmptyIssuePrefixs: false, // 不允许空 issue 前缀 allowCustomIssuePrefixs: false, // 不允许自定义 issue 前缀 // emptyScopesAlias: 'empty: 不填写', // customScopesAlias: 'custom: 自定义', skipQuestions: ['scope', 'body', 'breaking', 'footer'], // 跳过 body、breaking、footer 三个问题 userEmoji: true, emojiAlign: 'left', messages: { type: '选择你要提交的类型 :', // scope: '选择一个提交范围 (可选):', // customScope: '请输入自定义的提交范围 :', subject: '填写简短精炼的变更描述 :\n', // body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n', // breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n', // footerPrefixsSelect: '选择关联issue前缀 (可选):', // customFooterPrefixs: '输入自定义issue前缀 :', // footer: '列举关联issue (可选) 例如: #31, #I3244 :\n', confirmCommit: '是否提交或修改commit ?' }, typeEnum: [ 'init', 'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'wip', 'workflow', 'types', 'versions', 'revert' ], types: [ { value: 'init', name: '🎉 init: 初始化项目', emoji: '🎉' }, { value: 'feat', name: '✨ feat: 新增功能', emoji: '✨' }, { value: 'fix', name: '🐞 fix: 修复bug', emoji: '🐞' }, { value: 'docs', name: '📃 docs: 文档变更', emoji: '📃' }, { value: 'style', name: '🌈 style: 代码格式(仅仅修改了空格、缩进、逗号等等,不改变代码逻辑)', emoji: '🌈' }, { value: 'refactor', name: '🦄 refactor: 代码重构,没有加新功能或修复bug', emoji: '🦄' }, { value: 'perf', name: '🎈 perf: 代码优化,比如提升性能、体验', emoji: '🎈' }, { value: 'test', name: '🧪 test: 添加疏漏测试或已有测试改动', emoji: '🧪' }, { value: 'build', name: '🔧 build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)', emoji: '🔧' }, { value: 'ci', name: '🐎 ci: 修改 CI 配置,例如对k8s、docker的配置文件的修改', emoji: '🐎' }, { value: 'chore', name: '🐳 chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)', emoji: '🐳' }, { value: 'wip', name: '🔓️ wip: 正在开发中', emoji: '🔓️' }, { value: 'workflow', name: '⏳️ workflow: 工作流程改进', emoji: '⏳️' }, { value: 'types', name: '🚙 types: 类型定义文件修改', emoji: '🚙' }, { value: 'versions', name: '🔖 versions: 类型定义文件修改', emoji: '🔖' }, { value: 'revert', name: '↩ revert: 回滚 commit', emoji: '↩' } ] } } ================================================ FILE: components.d.ts ================================================ /* eslint-disable */ /* prettier-ignore */ // @ts-nocheck // Generated by vite-plugin-uni-components // Read more: https://github.com/vuejs/core/pull/3399 export {} declare module 'vue' { export interface GlobalComponents { WdBadge: (typeof import('wot-design-uni/components/wd-badge/wd-badge.vue'))['default'] WdButton: (typeof import('wot-design-uni/components/wd-button/wd-button.vue'))['default'] WdCalendar: (typeof import('wot-design-uni/components/wd-calendar/wd-calendar.vue'))['default'] WdCheckbox: (typeof import('wot-design-uni/components/wd-checkbox/wd-checkbox.vue'))['default'] WdConfigProvider: (typeof import('wot-design-uni/components/wd-config-provider/wd-config-provider.vue'))['default'] WdDivider: (typeof import('wot-design-uni/components/wd-divider/wd-divider.vue'))['default'] WdGrid: (typeof import('wot-design-uni/components/wd-grid/wd-grid.vue'))['default'] WdGridItem: (typeof import('wot-design-uni/components/wd-grid-item/wd-grid-item.vue'))['default'] WdIcon: (typeof import('wot-design-uni/components/wd-icon/wd-icon.vue'))['default'] WdSlider: (typeof import('wot-design-uni/components/wd-slider/wd-slider.vue'))['default'] WdSwitch: (typeof import('wot-design-uni/components/wd-switch/wd-switch.vue'))['default'] WdTab: (typeof import('wot-design-uni/components/wd-tab/wd-tab.vue'))['default'] WdTabs: (typeof import('wot-design-uni/components/wd-tabs/wd-tabs.vue'))['default'] WdTag: (typeof import('wot-design-uni/components/wd-tag/wd-tag.vue'))['default'] } } ================================================ FILE: eslint.config.mjs ================================================ // eslint.config.mjs import globals from 'globals' import pluginJs from '@eslint/js' import { configs, parser } from 'typescript-eslint' import pluginVue from 'eslint-plugin-vue' import eslintPluginImportX from 'eslint-plugin-import-x' // import tsParser from '@typescript-eslint/parser' import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' import { readFile } from 'node:fs/promises' const autoImportFile = new URL('./.eslintrc-auto-import.json', import.meta.url) const autoImportGlobals = JSON.parse(await readFile(autoImportFile, 'utf8')) /** @type {import('eslint').Linter.Config[]} */ const config = [ // 全局 files 指定 ESlint 匹配的文件 { files: ['**/*.{js,mjs,jsx,mjsx,ts,tsx,mtsx,vue}'] }, // 基础配置 { languageOptions: { // 指定全局变量 globals: { ...globals.browser, // 浏览器环境 ...globals.node, // node 环境 ...autoImportGlobals.globals, // 自动导入配置 $t: true, uni: true, wx: true, assets: true, UniApp: true, WechatMiniprogram: true, getCurrentPages: true, UniHelper: true, Page: true, App: true, NodeJS: true }, // parser: tsParser, ecmaVersion: 'latest', sourceType: 'module' } }, pluginJs.configs.recommended, ...configs.recommended, ...pluginVue.configs['flat/essential'], // eslint-plugin-import-x 扩展插件 eslintPluginImportX.flatConfigs.recommended, eslintPluginImportX.flatConfigs.typescript, // prettier 扩展插件 eslintPluginPrettierRecommended, // 仅对所有 vue 文件的自定义配置 { files: ['**/*.vue'], languageOptions: { parserOptions: { parser: parser } } }, // 自定义 rules { rules: { // 不允许存在未使用的变量 '@typescript-eslint/no-unused-vars': 'warn', // vue 组件必须多单词驼峰命名,关闭它 'vue/multi-word-component-names': 'off', // 禁止变量重新声明 '@typescript-eslint/no-redeclare': 'error', // 禁止变量重新声明,与 @typescript-eslint 重复提示了,关闭它 'no-redeclare': 'off', // 关闭 import-x 的 no-unresolved 'import-x/no-unresolved': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-unused-expressions': 'off' } }, // ignores 提升为全局忽略项 { ignores: ['src/uni_modules/', 'src/static/', '.vscode', '.husky'] } ] export default config ================================================ FILE: index.html ================================================
================================================ FILE: package.json ================================================ { "name": "uni-plus", "version": "1.0.0", "type": "commonjs", "author": { "name": "DaMaiCoding", "zhName": "大麦大麦", "email": "1351123861@qq.com", "github": "https://github.com/DaMaiCoding", "gitee": "https://gitee.com/DaMaiCoding" }, "license": "MIT", "repository": "https://github.com/DaMaiCoding/uni-plus", "repository-gitee": "https://gitee.com/DaMaiCoding/uni-plus", "bugs": { "url": "https://github.com/DaMaiCoding/uni-plus/issues" }, "homepage": "https://damaicoding.github.io/uni-plus/", "engines": { "node": ">=18", "pnpm": ">=7.30" }, "scripts": { "dev:custom": "uni -p", "dev:h5": "uni", "dev:h5:ssr": "uni --ssr", "dev:mp-alipay": "uni -p mp-alipay", "dev:mp-baidu": "uni -p mp-baidu", "dev:mp-jd": "uni -p mp-jd", "dev:mp-kuaishou": "uni -p mp-kuaishou", "dev:mp-lark": "uni -p mp-lark", "dev:mp-qq": "uni -p mp-qq", "dev:mp-toutiao": "uni -p mp-toutiao", "dev:mp-weixin": "uni -p mp-weixin", "dev:mp-xhs": "uni -p mp-xhs", "dev:quickapp-webview": "uni -p quickapp-webview", "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei", "dev:quickapp-webview-union": "uni -p quickapp-webview-union", "build:custom": "uni build -p", "build:h5": "uni build", "build:h5:ssr": "uni build --ssr", "build:mp-alipay": "uni build -p mp-alipay", "build:mp-baidu": "uni build -p mp-baidu", "build:mp-jd": "uni build -p mp-jd", "build:mp-kuaishou": "uni build -p mp-kuaishou", "build:mp-lark": "uni build -p mp-lark", "build:mp-qq": "uni build -p mp-qq", "build:mp-toutiao": "uni build -p mp-toutiao", "build:mp-weixin": "uni build -p mp-weixin", "build:mp-xhs": "uni build -p mp-xhs", "build:quickapp-webview": "uni build -p quickapp-webview", "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei", "build:quickapp-webview-union": "uni build -p quickapp-webview-union", "prepare": "husky", "release": "release-it", "type-check": "vue-tsc --noEmit", "cz": "czg emoji" }, "lint-staged": { "**/*.{vue,js,ts,jsx,tsx,mjs,cjs,html,json,md}": [ "prettier --write" ], "**/*.{vue,js,ts,jsx,tsx,mjs,cjs}": [ "eslint --fix" ], "**/*.{vue,css,scss,html}": [ "stylelint --fix" ] }, "dependencies": { "@dcloudio/uni-app": "3.0.0-4040520250104002", "@dcloudio/uni-app-harmony": "3.0.0-4040520250104002", "@dcloudio/uni-app-plus": "3.0.0-4040520250104002", "@dcloudio/uni-components": "3.0.0-4040520250104002", "@dcloudio/uni-h5": "3.0.0-4040520250104002", "@dcloudio/uni-mp-alipay": "3.0.0-4040520250104002", "@dcloudio/uni-mp-baidu": "3.0.0-4040520250104002", "@dcloudio/uni-mp-jd": "3.0.0-4040520250104002", "@dcloudio/uni-mp-kuaishou": "3.0.0-4040520250104002", "@dcloudio/uni-mp-lark": "3.0.0-4040520250104002", "@dcloudio/uni-mp-qq": "3.0.0-4040520250104002", "@dcloudio/uni-mp-toutiao": "3.0.0-4040520250104002", "@dcloudio/uni-mp-weixin": "3.0.0-4040520250104002", "@dcloudio/uni-mp-xhs": "3.0.0-4040520250104002", "@dcloudio/uni-quickapp-webview": "3.0.0-4040520250104002", "czg": "^1.11.0", "echarts": "^5.6.0", "husky": "^9.1.7", "lodash-es": "^4.17.21", "pinia": "^2.3.0", "pinia-plugin-persistedstate": "^3.2.3", "vue": "^3.4.21", "wot-design-uni": "^1.6.1", "z-paging": "^2.8.4" }, "devDependencies": { "@commitlint/cli": "^19.6.1", "@dcloudio/types": "^3.4.8", "@dcloudio/uni-automator": "3.0.0-4040520250104002", "@dcloudio/uni-cli-shared": "3.0.0-4040520250104002", "@dcloudio/uni-stacktracey": "3.0.0-4040520250104002", "@dcloudio/vite-plugin-uni": "3.0.0-4040520250104002", "@eslint/js": "^9.17.0", "@iconify-json/uiw": "^1.2.1", "@release-it/conventional-changelog": "^9.0.4", "@types/wechat-miniprogram": "^3.4.8", "@typescript-eslint/parser": "^8.18.2", "@uni-helper/uni-types": "1.0.0-alpha.6", "@uni-helper/vite-plugin-uni-components": "^0.2.0", "@uni-helper/vite-plugin-uni-layouts": "^0.1.10", "@uni-helper/vite-plugin-uni-pages": "^0.2.28", "@unocss/preset-legacy-compat": "^65.4.2", "@vue/runtime-core": "^3.4.21", "@vue/tsconfig": "^0.1.3", "commitlint-config-git-commit-emoji": "^1.0.0", "eslint": "^9.17.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-import-x": "^4.6.1", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-vue": "^9.32.0", "globals": "^15.14.0", "lint-staged": "^15.4.1", "prettier": "3.4.2", "release-it": "^17.11.0", "sass": "^1.78.0", "sass-embedded": "^1.83.0", "stylelint": "^16.12.0", "stylelint-config-recess-order": "^5.1.1", "stylelint-config-recommended": "^14.0.1", "stylelint-config-recommended-scss": "^14.1.0", "stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-standard": "^36.0.1", "typescript": "^4.9.4", "typescript-eslint": "^8.18.2", "unocss": "0.65.2", "unocss-applet": "~0.7.8", "unplugin-auto-import": "^19.0.0", "vite": "5.2.8", "vite-plugin-restart": "^0.4.2", "vue-tsc": "^1.0.24" } } ================================================ FILE: pages.config.ts ================================================ import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages' export default defineUniPages({ // 你也可以定义 pages 字段,它具有最高的优先级。 pages: [], globalStyle: { // 导航栏字体颜色 navigationBarTextStyle: '@navTxtStyle', // 导航栏背景色 navigationBarBackgroundColor: '@navBgColor', // 导航栏背景底色 backgroundColor: '@bgColor' }, easycom: { autoscan: true, custom: { '^wd-(.*)': 'wot-design-uni/components/wd-$1/wd-$1.vue', '^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)': 'z-paging/components/z-paging$1/z-paging$1.vue' } } }) ================================================ FILE: prettier.config.mjs ================================================ // prettier.config.mjs /** * @see https://prettier.io/docs/en/configuration.html * @type {import("prettier").Config} */ const config = { // 不尾随分号 semi: false, // 使用单引号 singleQuote: true, // 多行逗号分割的语法中,最后一行不加逗号 trailingComma: 'none', // 行尾风格,设置为auto endOfLine: 'auto', // 一行最多 100 字符 printWidth: 120, // 使用 2 个空格缩进 tabWidth: 2, // 不使用缩进符,而使用空格 useTabs: false, // 单个参数的箭头函数不加括号 x => x arrowParens: 'avoid', // 对象大括号内两边是否加空格 { a:0 } bracketSpacing: true, // 忽略html中的空格 htmlWhitespaceSensitivity: 'ignore' } export default config ================================================ FILE: src/App.vue ================================================ ================================================ FILE: src/api/loginApi.ts ================================================ import http from '@/http/httpClient' import { useUserStore } from '@/store' /* 登录 获取 accessToken */ export const loginApi: any = async data => { const res: any = await http.post('/login', { data }) const store = useUserStore() store.setUserInfo({ refreshToken: res.data.refreshToken, accessToken: res.data.accessToken }) } /* 拿 refreshToken 换取 accessToken 与 新 refreshToken */ /* 即刷新 accessToken */ export const refreshTokenApi: any = () => { const store = useUserStore() const { refreshToken } = store.userInfo || {} return http .get('/refresh', { data: { refreshToken: refreshToken } }) .then( res => { store.setUserInfo({ refreshToken: res.data.refreshToken, accessToken: res.data.accessToken }) return [res, null] }, e => [null, e] ) } /* 获取用户信息 */ export const getListApi: any = async () => { return http.get('/userInfo') } ================================================ FILE: src/api/testApi.ts ================================================ import http from '@/http/httpClient' /** GET 请求测试 */ export const getTestApi: any = query => { return http.get('/getTest', { query }) } /** POST 请求测试 */ export const postTestApi: any = (data, query) => { return http.post('/postTest', { data, query }) } /* await-to-js 简洁用法 */ export const awaitToJsTestApi: any = query => { return ( http .post('/postTest', { query }) // 返回正常 // .post('/error', { query }) // 返回错误 .then( data => [data, null], e => [null, e] ) ) } /** 分页 请求测试 */ export const getUserListApi: any = query => { return http.get('/testList', { query }) } /** 长时间请求 请求测试 */ export const getLongRequestApi: any = () => { return http.get('/longRequest') } ================================================ FILE: src/hooks/useEcharts.ts ================================================ // 小程序中引入 echarts // #ifndef APP-PLUS || H5 const echarts = require('../uni_modules/lime-echart/static/echarts.min') // #endif // APP 与 H5 引入 echarts // #ifdef APP-PLUS || H5 import * as echartsAppOrH5 from 'echarts' // #endif import { ref } from 'vue' // echarts 图表 Hooks export const useEcharts = (options): any => { // echarts 图实例 const chartRef = ref(null) // echarts 图绘制函数 const draw = () => { setTimeout(async () => { if (!chartRef.value) return // #ifndef APP-PLUS || H5 const chart = await chartRef.value.init(echarts) chart.setOption(options.value) // #endif // #ifdef APP-PLUS || H5 const chartAPP = await chartRef.value.init(echartsAppOrH5) chartAPP.setOption(options.value) // #endif }, 300) } // 这里就封装巧妙之处,如果是对象,那么需要重命名,而是是数组,无需重命名 return [chartRef, options, draw] } ================================================ FILE: src/hooks/useI18n.ts ================================================ import { useUserStore } from '@/store' import { Locale, useCurrentLang } from 'wot-design-uni' import enUS from 'wot-design-uni/locale/lang/en-US' import zhCN from 'wot-design-uni/locale/lang/zh-CN' import en from '@/locale/en.json' import zh from '@/locale/zh-Hans.json' export const useI18n = () => { const store = useUserStore() const pagePath = getCurrentPages() const pagePathKey = pagePath[pagePath.length - 1].route.replace(/\//g, '.') const t = (key, type?: string) => { /* 如果国际化是元素,直接返回,如果是 ref 中的数据就需要加 computed 不然不会动态变化 */ if (type === 'text') { return computed(() => { return (store.getLocale === 'zh-CN' ? zh[pagePathKey][key] : en[pagePathKey][key]) ?? '' }) } return (store.getLocale === 'zh-CN' ? zh[pagePathKey][key] : en[pagePathKey][key]) ?? '' } /* 切换 原生元素 语言 */ const changeNativeLang = () => { // 切换 顶部导航栏 语言 uni.setNavigationBarTitle({ title: (store.getLocale === 'zh-CN' ? zh[pagePathKey]?.title : en[pagePathKey]?.title) ?? '' }) // 切换 tabBar 语言 // ... } /* 切换 UI 库 语言 */ const changeUiLang = () => { /* 设置 WotUI 组件语言 */ const wotUiCurrentLang = useCurrentLang().value if (wotUiCurrentLang != store.getLocale) { Locale.use(store.getLocale, store.getLocale === 'zh-CN' ? zhCN : enUS) } } const setLocale = () => { /* 切换 语言 */ store.setLocale() changeNativeLang() changeUiLang() } /* 页面初始化渲染一次 */ changeNativeLang() changeUiLang() return { t, setLocale } } ================================================ FILE: src/hooks/useRequest.ts ================================================ import { UnwrapRef, Ref } from 'vue' type RequestOptions = { /** 是否立即执行 */ immediate?: boolean } /** * useRequest 是请求的再次封装,类似于 react-query 中的 userQuery * 核心使用场景,就前端而言,分页查询用的比较多,其他嘛用得还是比较少 * @param queryFn 一个执行异步请求的函数,返回一个包含响应数据的 Promise * @param queryKey 请求的唯一标识,用于监听参数变化,重新发起请求 * @param options 包含请求选项的对象 {immediate}。 * @param options.immediate 是否立即执行请求,默认为false。 * @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。 */ export default function useRequest({ queryKey, options = { immediate: false }, queryFn }: { queryKey: Ref[] options: RequestOptions queryFn: () => Promise> }) { const loading = ref(false) const error = ref(false) const data = ref() // 不做类型定义,让 ts 默认推导 const run = async () => { loading.value = true return queryFn() .then(res => { data.value = res.data as UnwrapRef error.value = false return data.value }) .catch(err => { error.value = err throw err }) .finally(() => { loading.value = false }) } // 监听 queryKey 变化,重新发起请求 watch([...queryKey], () => { run() }) options.immediate && run() return { loading, error, data, run } } ================================================ FILE: src/hooks/useTheme.ts ================================================ import type { ConfigProviderThemeVars } from 'wot-design-uni' import { ref } from 'vue' import { useUserStore } from '@/store' import { storeToRefs } from 'pinia' const theme = ref<'light' | 'dark'>('light') const themeVars = ref() export function useTheme(vars?: ConfigProviderThemeVars) { const store = useUserStore() const { userInfo } = storeToRefs(store) /* 替换 Wot 组件的样式 */ vars && (themeVars.value = vars) const setTheme = () => { nextTick(() => { if (userInfo.value.followSystem) { // #ifdef APP-PLUS plus.nativeUI.setUIStyle('auto') // 设置应用主题为跟随系统(android下能够切换,IOS 未测试) theme.value = 'light' // #endif // #ifndef APP-PLUS uni.getSystemInfo({ success: (res: any) => { const osThemeInfo = res.hostTheme ? res.hostTheme : res.osTheme store.setUserInfo({ theme: osThemeInfo }) theme.value = osThemeInfo uni.setNavigationBarColor({ frontColor: theme.value === 'light' ? '#000000' : '#ffffff', backgroundColor: theme.value === 'light' ? '#ffffff' : '#000000', animation: { duration: 400, timingFunc: 'easeIn' } }) } }) // #endif } else { // #ifdef APP-PLUS plus.nativeUI.setUIStyle('light') // 设置应用主题为跟随系统(android下能够切换,IOS 未测试) // #endif uni.setNavigationBarColor({ frontColor: theme.value === 'light' ? '#000000' : '#ffffff', backgroundColor: theme.value === 'light' ? '#ffffff' : '#000000', animation: { duration: 400, timingFunc: 'easeIn' } }) } }) } watch(theme, () => { store.setUserInfo({ theme: theme.value }) setTheme() }) onShow(() => { setTheme() }) return { theme, themeVars, setTheme } } ================================================ FILE: src/http/httpClient.ts ================================================ import { refreshTokenApi } from '@/api/loginApi' import { CustomRequestOptions } from '@/interceptors/request' import { useUserStore } from '@/store' type CustomRequestOptionsOmit = Omit let refreshing = false // 防止重复刷新 token 标示 let taskQueue = [] // 刷新 token 请求队列 export default class ApiClient { private static http(options: Omit) { let requestTask const promise = new Promise>((resolve, reject) => { requestTask = uni.request({ ...options, success: async (res: any) => { /* -------- 请求成功 ----------- */ if ((res.statusCode >= 200 && res.statusCode < 300) || res.data.code === 0) { return resolve(res.data as ResData) } /* -------- 无感刷新 token ----------- */ const store = useUserStore() const { refreshToken } = store.userInfo || {} // token 失效的,且有刷新 token 的,才放到请求队列里 if ((res.data.code == 401 || res.statusCode == 401) && refreshToken != '') { taskQueue.push(() => { resolve(this.http(options)) }) } if ((res.data.code == 401 || res.statusCode == 401) && refreshToken != '' && !refreshing) { refreshing = true // 发起刷新 token 请求 const [refreshTokenRes, refreshTokenErr] = await refreshTokenApi() refreshing = false // 刷新 token 成功,将任务队列的所有任务重新请求 if (refreshTokenRes?.data.code == 200) { nextTick(() => { // 关闭其他弹窗 uni.hideToast() // 刷新 token 失败,跳转到登录页 uni.showToast({ title: 'token 刷新成功,重载中', icon: 'none' }) }) taskQueue.forEach(event => { event() }) } if (refreshTokenErr) { // 刷新 token 失败,跳转到登录页 nextTick(() => { // 关闭其他弹窗 uni.hideToast() // 刷新 token 失败,跳转到登录页 uni.showToast({ title: '登录已过期,请重新登录', icon: 'none' }) }) // setTimeout(() => { // // 清除 用户信息(包括 token) // store.clearUserInfo() // // 跳转到登录页 // uni.navigateTo({ url: '/pages/login/login' }) // }, 2500) } // 不管刷新 token 成功与否,都清空任务队列 taskQueue = [] } /* -------- 剩余情况都默认请求异常 ----------- */ uni.showToast({ title: res.data.msg || res.data.message || '请求异常', icon: 'none' }) reject(res) }, fail: err => { if (err.errMsg === 'request:fail abort') { console.log(`请求 ${options.url} 被取消`) } else { reject(err) } } }) }) return { ...promise, // 补全 Promise 的类型 then: promise.then.bind(promise), catch: promise.catch.bind(promise), finally: promise.finally.bind(promise), [Symbol.iterator]: promise[Symbol.iterator], abort: () => { // 取消请求 requestTask.abort() } } } // GET public static get(url: string, options?: CustomRequestOptionsOmit) { return this.http({ url, method: 'GET', ...options }) } // POST public static post(url: string, options?: CustomRequestOptionsOmit) { return this.http({ url, method: 'POST', ...options }) } // PUT public static put(url: string, options?: CustomRequestOptionsOmit) { return this.http({ url, method: 'PUT', ...options }) } // DELETE public static delete(url: string, options?: CustomRequestOptionsOmit) { return this.http({ url, method: 'DELETE', ...options }) } } ================================================ FILE: src/interceptors/index.ts ================================================ export { requestInterceptor } from './request' export { routerInterceptor } from './router' ================================================ FILE: src/interceptors/request.ts ================================================ import { qs } from '@/utils' import { useUserStore } from '@/store' export type CustomRequestOptions = UniApp.RequestOptions & { query?: Record /** 出错时是否隐藏错误提示 */ hideErrorToast?: boolean } const timeout = 30000 // 请求超时时间 const baseUrl = (import.meta as any).env.VITE_SERVER_BASEURL // 请求基础路径 // 拦截器配置 const httpInterceptor = { // 请求前的拦截 invoke(options: CustomRequestOptions) { // 1. 设置请求路径 if (!options.url.includes('//')) { // 非完整路径,补全基础路径 // 完整路径:http://localhost:8080/api/user/login // 非完整路径:api/user/loginApi options.url = baseUrl + options.url } // 2. query 参数处理 if (options.query) { // qs.stringify() 方法将一个 JavaScript 对象或数组转换为一个查询字符串 // 比如: { name: 'tom', age: 18 } 转换成 name=tom&age=18 const query = qs.stringify(options.query) options.url += options.url.includes('?') ? '&' : '?' + query } // 3. 请求超时时间设置 options.timeout = timeout // 4. 定义请求返回数据的格式(设为 json,会尝试对返回的数据做一次 JSON.parse) options.dataType = 'json' // #ifndef MP-WEIXIN options.responseType = 'json' // #endif // 5. 添加请求头标识,可以告诉后台是小程序端发起的请求 options.header = { ...options.header, 'Content-Type': 'application/json; charset=utf-8' // platform: 'mini-program', 比如 platform 字段可以告诉后台是小程序端发起的请求 } // 6. 添加 token 请求头标识 const store = useUserStore() const { accessToken } = store.userInfo || {} if (accessToken) { options.header.Authorization = `Bearer ${accessToken}` } return options } } export const requestInterceptor = { install() { // 拦截 request 请求 uni.addInterceptor('request', httpInterceptor) } } ================================================ FILE: src/interceptors/router.ts ================================================ /** * 权限拦截 * 主要分两个:路由权限、按钮权限 * 1. 路由权限 * 这里主要实现路由权限,实现了黑名单拦截,权限不够就拦截 * 比如现在用户权限是 ['logined', 'vip'] 那么他能进入就只能是 permissionKeys 在 ['logined', 'vip'] 范围里面的路由 * 如果页面 permissionKeys 是 ['logined', 'vip', 'admin'],那么这个页面就无法被当前用户访问 * 如果页面 permissionKeys 是 ['logined']、['logined', 'vip'],或者没有 permissionKeys 那么这个页面可以被当前用户访问 * * 2. 按钮权限在 utils/router.ts */ import { useUserStore } from '@/store' import { getPermissionKeysByPath } from '@/utils' const loginRoute = '/pages/routerDemo/login' // 获取用户权限标识 const getUserPermissionKeys = () => { const userStore = useUserStore() return userStore.getUserPermissionKeys } /** * 实现原理: * 1. 或者所有需要登录的路由,存到黑名单,需要在 route-block 配置标识 key,根据 isNeedLogin 判断是否需要登录 * 2. 在路由跳转前,判断是否需要登录,如果需要登录,判断是否已经登录,如果没有登录,跳转到登录页 **/ const navigateToInterceptor = { invoke({ url }: { url: string }) { // 'pages.json' 里面的 path 是 'pages/index/index' // url 如果带参数则是 /pages/route-interceptor/index?name=uni-plus,所以需要去掉带的参数、去掉前面的 / const path = url.split('?')[0].slice(1) // pages/index/index // 用户已有权限 const userPermissionKeys = getUserPermissionKeys() // ['logined', 'vip'] // 页面所需权限 const pageRoutePaths = getPermissionKeysByPath(path) // ['vip'] // 两个权限取交集,然后交集再与用户权限取补集 const LackPermissionKeys = pageRoutePaths.filter(item => !userPermissionKeys.includes(item)) // 如果有权限就放行 if (LackPermissionKeys.length === 0) { return true } /* ---------- 无权限,对缺少各种权限做处理 --------- */ // 如果缺少登录权限,跳转到登录页 if (LackPermissionKeys.includes('logined')) { const redirectRoute = `${loginRoute}?redirect=${encodeURIComponent(url)}` uni.navigateTo({ url: redirectRoute }) } // 如果缺少其他权限,提示无权限,需要其他权限特殊判定可以继续添加 else { uni.showToast({ title: '无权限访问', icon: 'none' }) } return false } } export const routerInterceptor = { install() { uni.addInterceptor('navigateTo', navigateToInterceptor) uni.addInterceptor('reLaunch', navigateToInterceptor) uni.addInterceptor('redirectTo', navigateToInterceptor) uni.addInterceptor('switchTab', navigateToInterceptor) } } ================================================ FILE: src/layouts/default.vue ================================================ ================================================ FILE: src/layouts/fullPage.vue ================================================ ================================================ FILE: src/layouts/theme.vue ================================================ ================================================ FILE: src/locale/en.json ================================================ { "pages.index.index": { "layersTitle": "Layout SFC", "attachTitle": "Pinia use", "attachText": "State management", "linkUnlinkTitle": "Single request", "linkUnlinkText": "Separation", "linkTitle": "Request+Status", "linkText": "Combined", "userTitle": "Login example", "userText": "Noninductive refresh", "lockOffTitle": "Right control", "lockOffText": "Routes and buttons", "chartTitle": "Chart module", "transferTitle": "WotUI module", "transferText": "High-frequency", "viewModuleTitle": "Paging case", "viewListTitle": "Multilingual", "menuFoldTitle": "Customize bar", "menuFoldText": "Custom component", "chartBubbleTitle": "Atomic CSS", "nowLocale": "English-US", "nowLocaleText": "Language setting (i18n)", "darkMode": "Dark Mode: ", "darkModeFollowText": "Dark mode follow system", "about": "A 'super super super' easy to use uniapp template", "bgColor1": "Yellow", "bgColor2": "Orange", "bgColor3": "Default", "authorText": "Author: DaMaiCoding" }, "pages.i18nDemo.index": { "title": "i18n Multilingual Demo", "authorText": "Author: DaMaiCoding", "about": "A 'super super super' easy to use uniapp template", "nowLocale": "Click to switch language(English-US)", "calendarLabel": "Calendar" } } ================================================ FILE: src/locale/zh-Hans.json ================================================ { "pages.index.index": { "layersTitle": "Layout布局", "attachTitle": "Pinia使用", "attachText": "状态管理", "linkUnlinkTitle": "单一请求", "linkUnlinkText": "请求(与状态分离)", "linkTitle": "请求+状态", "linkText": "请求(与状态结合)", "userTitle": "登录示例", "userText": "无感刷新", "lockOffTitle": "权限控制", "lockOffText": "路由与按钮", "chartTitle": "图表组件", "transferTitle": "WotUI组件", "transferText": "高频组件", "viewModuleTitle": "分页案例", "viewListTitle": "国际化", "menuFoldTitle": "自定义导航栏", "menuFoldText": "自定义组件实现", "chartBubbleTitle": "原子CSS", "nowLocale": "简体中文", "nowLocaleText": "设置语言(国际化)", "darkMode": "暗黑模式:", "darkModeFollowText": "暗黑模式跟随系统", "about": "一个 “超超超” 好用的 uniapp 模板", "bgColor1": "小黄", "bgColor2": "橙色", "bgColor3": "默认", "authorText": "作者:大麦大麦" }, "pages.i18nDemo.index": { "title": "i18n 多语言 Demo", "authorText": "作者:大麦大麦", "about": "一个 “超超超” 好用的 uniapp 模板", "nowLocale": "点击切换语言(简体中文)", "calendarLabel": "日历" } } ================================================ FILE: src/main.ts ================================================ import { createSSRApp } from 'vue' import { requestInterceptor, routerInterceptor } from './interceptors' import { checkBtnPermission, useAssets } from './utils' import App from './App.vue' import store from './store' import 'uno.css' export function createApp() { const app = createSSRApp(App) app.use(store) app.config.globalProperties.$perms = checkBtnPermission app.config.globalProperties.$assets = useAssets app.use(requestInterceptor) app.use(routerInterceptor) return { app } } ================================================ FILE: src/manifest.json ================================================ { "name" : "uni-plus", "appid" : "__UNI__42E0EC3", "description" : "uni-plus app 打包", "versionName" : "1.0.1", "versionCode" : 101, /* 5+App特有相关 */ "app-plus" : { "usingComponents" : true, "nvueStyleCompiler" : "uni-app", "compilerVersion" : 3, "splashscreen" : { "alwaysShowBeforeRender" : true, "waiting" : true, "autoclose" : true }, /* 模块配置 */ "modules" : {}, "darkmode" : true, // 开启支持深色模式 "themeLocation" : "theme.json", // 深色模式JSON文件,如果 theme.json 在根目录可省略 /* 应用发布信息 */ "distribute" : { /* android打包配置 */ "android" : { "permissions" : [ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" ] }, /* ios打包配置 */ "ios" : { "dSYMs" : false }, /* SDK配置 */ "sdkConfigs" : { "ad" : {} } } }, /* 快应用特有相关 */ "quickapp" : {}, /* 小程序特有相关 */ "mp-weixin" : { "appid" : "", "setting" : { "urlCheck" : false, "es6" : true, "postcss" : true, "minified" : true, "uglifyFileName" : true, "swc" : true }, "usingComponents" : true, "darkmode" : true, // 开启支持深色模式 "themeLocation" : "theme.json", // 深色模式JSON文件,如果 theme.json 在根目录可省略 "libVersion" : "latest", // 处理微信开发者工具报错 /* 该配置,上传时会忽略已配置的静态资源文件 */ "packOptions" : { "ignore" : [ { "type" : "folder", "value" : "static/onLine" } ] } }, "mp-alipay" : { "usingComponents" : true }, "mp-baidu" : { "usingComponents" : true }, "mp-toutiao" : { "usingComponents" : true }, "uniStatistics" : { "enable" : false }, "vueVersion" : "3", "locale" : "zh-Hans", "h5" : { "router" : { "base" : "/uni-plus/" } } } ================================================ FILE: src/pages/customDemo/index.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationStyle: 'custom' } } ================================================ FILE: src/pages/echartsDemo/echartsData.ts ================================================ import { useEcharts } from '@/hooks/useEcharts' import { ref } from 'vue' // 折线图 export const useLineEcharts = (): any => { // 折线图配置项数据 // 每次编写新类型的图表时,只需要复制一份修改这里的配置即可 // 应用 这个类型的图表时,只需在 .vue 文件中通过 set 修复数据即可,或者简单的样式修改,如果样式过于复杂,可以重新建立一个 linkHookB 等等 const option = ref({ xAxis: { data: [12, 13, 10, 13, 9, 23, 21, 32, 12, 15, 13, 10], boundaryGap: false, axisTick: { show: false }, axisLine: { show: true, lineStyle: { color: '#0C4787' } }, axisLabel: { show: true, color: '#000000' } }, grid: { left: 5, right: 16, bottom: 5, top: 20, containLabel: true }, tooltip: { trigger: 'axis', axisPointer: { type: 'cross' }, padding: [5, 10] }, yAxis: { axisTick: { show: false }, splitLine: { show: false }, axisLine: { show: true, lineStyle: { color: '#0C4787' } }, axisLabel: { show: true, color: '#000000' } }, series: { smooth: true, type: 'line', data: [12, 13, 10, 13, 9, 23, 21, 32, 12, 15, 13, 10], animationDuration: 2800, animationEasing: 'cubicInOut', symbol: 'circle', color: '#fed42b', symbolSize: 8, lineStyle: { color: '#fed42b' //改变折线颜色 }, areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: '#fed42b' // 0% 处的颜色 }, { offset: 1, color: 'rgba(254, 212, 43, 0.1)' // 100% 处的颜色 } ], global: false // 缺省为 false } } } }) return useEcharts(option) } // 柱状图 export const useBarEcharts = (): any => { const option = ref({ xAxis: { data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], axisTick: { show: false }, axisLine: { show: true, lineStyle: { color: '#0C4787' } }, axisLabel: { show: true, color: '#000000' } }, grid: { left: 5, right: 16, bottom: 5, top: 20, containLabel: true }, tooltip: { trigger: 'axis', axisPointer: { type: 'cross' }, padding: [5, 10] }, yAxis: { axisTick: { show: false }, splitLine: { show: false }, axisLine: { show: true, lineStyle: { color: '#0C4787' } }, axisLabel: { show: true, color: '#000000' } }, series: { type: 'bar', data: [120, 200, 150, 80, 70, 110, 130], animationDuration: 2800, animationEasing: 'cubicInOut', color: '#fed42b', barWidth: 10, itemStyle: { barBorderRadius: [3, 3, 0, 0] } } }) return useEcharts(option) } ================================================ FILE: src/pages/echartsDemo/index.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: '图表 dome' } } ================================================ FILE: src/pages/i18nDemo/index.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: '%pages.index.index.title%' } } ================================================ FILE: src/pages/index/index.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationStyle: 'custom' } } ================================================ FILE: src/pages/layoutDemo/index.vue ================================================ { layout: 'fullPage', // 使用默认布局,替换成 foot 试试 style: { navigationBarTitleText: '布局 Demo' } } ================================================ FILE: src/pages/piniaDemo/index.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: 'pinia 使用 dome' } } ================================================ FILE: src/pages/queryDemo/loginDemo.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: '登录请求示例' } } ================================================ FILE: src/pages/queryDemo/queryTestDemo.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: '请求测试页面' } } ================================================ FILE: src/pages/queryDemo/useRequestDemo.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: 'useRequest使用示例' } } ================================================ FILE: src/pages/queryDemo/zPagingDemo.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: 'z-paging 使用示例' } } ================================================ FILE: src/pages/routerDemo/index.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: '页面路由权限控制' } } ================================================ FILE: src/pages/routerDemo/login.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: '登录页' } } ================================================ FILE: src/pages/routerDemo/page1.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: '页面1' }, permissionKeys: ['vip'] } ================================================ FILE: src/pages/routerDemo/page2.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: '页面2' }, permissionKeys: ['vip', 'admin'] } ================================================ FILE: src/pages/routerDemo/page3.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: '页面3' }, permissionKeys: ['logined'] } ================================================ FILE: src/pages/unocssDemo/index.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: 'Unocss 使用 dome' } } ================================================ FILE: src/pages/wotUiDemo/index.vue ================================================ { layout: 'theme', // 使用主题 style: { navigationBarTitleText: 'Wot UI Demo' } } ================================================ FILE: src/pages-sub/subDemo/index.vue ================================================ { style: { navigationBarTitleText: 'subDemo 分包页面' } } ================================================ FILE: src/pages-sub/testDemo/index.vue ================================================ { layout: 'default', style: { navigationBarTitleText: '测试页面' } } ================================================ FILE: src/pages.json ================================================ { "pages": [ { "path": "pages/index/index", "type": "home", "layout": "theme", "style": { "navigationStyle": "custom" } }, { "path": "pages/customDemo/index", "type": "page", "layout": "theme", "style": { "navigationStyle": "custom" } }, { "path": "pages/echartsDemo/index", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "图表 dome" } }, { "path": "pages/i18nDemo/index", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "%pages.index.index.title%" } }, { "path": "pages/layoutDemo/index", "type": "page", "layout": "fullPage", "style": { "navigationBarTitleText": "布局 Demo" } }, { "path": "pages/piniaDemo/index", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "pinia 使用 dome" } }, { "path": "pages/queryDemo/loginDemo", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "登录请求示例" } }, { "path": "pages/queryDemo/queryTestDemo", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "请求测试页面" } }, { "path": "pages/queryDemo/useRequestDemo", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "useRequest使用示例" } }, { "path": "pages/queryDemo/zPagingDemo", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "z-paging 使用示例" } }, { "path": "pages/routerDemo/index", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "页面路由权限控制" } }, { "path": "pages/routerDemo/login", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "登录页" } }, { "path": "pages/routerDemo/page1", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "页面1" }, "permissionKeys": [ "vip" ] }, { "path": "pages/routerDemo/page2", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "页面2" }, "permissionKeys": [ "vip", "admin" ] }, { "path": "pages/routerDemo/page3", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "页面3" }, "permissionKeys": [ "logined" ] }, { "path": "pages/unocssDemo/index", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "Unocss 使用 dome" } }, { "path": "pages/wotUiDemo/index", "type": "page", "layout": "theme", "style": { "navigationBarTitleText": "Wot UI Demo" } } ], "globalStyle": { "navigationBarTextStyle": "@navTxtStyle", "navigationBarBackgroundColor": "@navBgColor", "backgroundColor": "@bgColor" }, "easycom": { "autoscan": true, "custom": { "^wd-(.*)": "wot-design-uni/components/wd-$1/wd-$1.vue", "^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue" } }, "subPackages": [ { "root": "pages-sub", "pages": [ { "path": "subDemo/index", "type": "page", "style": { "navigationBarTitleText": "subDemo 分包页面" } }, { "path": "testDemo/index", "type": "page", "layout": "default", "style": { "navigationBarTitleText": "测试页面" } } ] } ] } ================================================ FILE: src/store/index.ts ================================================ import { createPinia } from 'pinia' import { createPersistedState } from 'pinia-plugin-persistedstate' // 数据持久化 // 持久化把数据放在本地存储中 const store = createPinia() store.use( createPersistedState({ storage: { getItem: uni.getStorageSync, setItem: uni.setStorageSync } }) ) export default store // 模块统一导出 export * from './user' ================================================ FILE: src/store/user.ts ================================================ import { defineStore } from 'pinia' import { ref } from 'vue' type UserInfo = { userName?: string userId?: string avatar?: string accessToken?: string refreshToken?: string isVip?: boolean theme?: 'light' | 'dark' locale?: string followSystem?: boolean userBtnPermission?: string[] } // 初始用户数据,可用户初始化 const initState = { userName: 'uni-plus', userId: '', avatar: '', accessToken: '', refreshToken: '', theme: <'light' | 'dark'>'light', followSystem: false, locale: 'zh-CN', isVip: true, userBtnPermission: ['operation:user:create', 'operation:user:update'] // ... } export const useUserStore = defineStore( 'user', // Setup Store 写法,Vue3 推荐用这个种方法写 () => { const userInfo = ref({ ...initState }) // 设置用户信息 可设置部分信息(比如更新 token) const setUserInfo = (val: UserInfo): void => { userInfo.value = { ...userInfo.value, ...val } } // 清除用户信息 const clearUserInfo = (): void => { userInfo.value = { ...initState } } // 获取页面权限信息 const getUserPermissionKeys = computed(() => { const permissionKeys: string[] = [] // 是登录 if (!!userInfo.value.accessToken && !!userInfo.value.refreshToken) { permissionKeys.push('logined') } // 是否为 vip if (userInfo.value.isVip) { permissionKeys.push('vip') } // 可以继续加你需要的权限判定 ... return permissionKeys }) // 获取用户按钮权限 const getUserBtnPermission = computed(() => { return userInfo.value.userBtnPermission }) // 获取主题状态 const getTheme = computed(() => { return userInfo.value.theme }) // 获取 locale const getLocale = computed(() => { return userInfo.value.locale }) // 设置 locale const setLocale = () => { userInfo.value.locale = userInfo.value.locale === 'zh-CN' ? 'en-US' : 'zh-CN' } return { userInfo, getLocale, getTheme, getUserPermissionKeys, getUserBtnPermission, setUserInfo, clearUserInfo, setLocale } }, { persist: true // 是否持久化 } ) ================================================ FILE: src/theme.json ================================================ { // 浅色模式 "light": { "navBgColor": "#fff", // 导航栏背景色 "navTxtStyle": "black", // 导航栏文字颜色 "bgColor": "#f5f5f5", // 页面背景色 "bgTxtStyle": "light", // 下拉 loading 的样式,仅支持 dark/light "bgColorTop": "#eeeeee", // 顶部窗口的背景色(bounce回弹区域) "bgColorBottom": "#efefef", // 底部窗口的背景色(bounce回弹区域) "tabFontColor": "#666666", // tabBar 上的文字默认颜色 "tabSelectedColor": "#8fc97f", // tabBar 上的文字选中颜色 "tabBgColor": "#ffffff", // tabBar 背景色 "tabBorderStyle": "white" // tabBar 上边框的颜色,可选值 black/white,也支持其他颜色值 }, // 深色模式 "dark": { "navBgColor": "#1d1d1d", // 导航栏背景色 "navTxtStyle": "white", // 导航栏文字颜色 "bgColor": "#222222", // 页面背景色 "bgTxtStyle": "dark", // 下拉 loading 的样式,仅支持 dark/light "bgColorTop": "#1e1e1e", // 顶部窗口的背景色(bounce回弹区域) "bgColorBottom": "#1e1e1e", // 底部窗口的背景色(bounce回弹区域) "tabFontColor": "#bbb", // tabBar 上的文字默认颜色 "tabSelectedColor": "#8fc97f", // tabBar 上的文字选中颜色 "tabBgColor": "#212121", // tabBar 背景色 "tabBorderStyle": "black" // tabBar 上边框的颜色,可选值 black/white,也支持其他颜色值 } } ================================================ FILE: src/types/auto-import.d.ts ================================================ /* eslint-disable */ /* prettier-ignore */ // @ts-nocheck // noinspection JSUnusedGlobalSymbols // Generated by unplugin-auto-import // biome-ignore lint: disable export {} declare global { const EffectScope: (typeof import('vue'))['EffectScope'] const computed: (typeof import('vue'))['computed'] const createApp: (typeof import('vue'))['createApp'] const customRef: (typeof import('vue'))['customRef'] const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent'] const defineComponent: (typeof import('vue'))['defineComponent'] const effectScope: (typeof import('vue'))['effectScope'] const getCurrentInstance: (typeof import('vue'))['getCurrentInstance'] const getCurrentScope: (typeof import('vue'))['getCurrentScope'] const h: (typeof import('vue'))['h'] const inject: (typeof import('vue'))['inject'] const isProxy: (typeof import('vue'))['isProxy'] const isReactive: (typeof import('vue'))['isReactive'] const isReadonly: (typeof import('vue'))['isReadonly'] const isRef: (typeof import('vue'))['isRef'] const markRaw: (typeof import('vue'))['markRaw'] const nextTick: (typeof import('vue'))['nextTick'] const onActivated: (typeof import('vue'))['onActivated'] const onAddToFavorites: (typeof import('@dcloudio/uni-app'))['onAddToFavorites'] const onBackPress: (typeof import('@dcloudio/uni-app'))['onBackPress'] const onBeforeMount: (typeof import('vue'))['onBeforeMount'] const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount'] const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate'] const onDeactivated: (typeof import('vue'))['onDeactivated'] const onError: (typeof import('@dcloudio/uni-app'))['onError'] const onErrorCaptured: (typeof import('vue'))['onErrorCaptured'] const onHide: (typeof import('@dcloudio/uni-app'))['onHide'] const onLaunch: (typeof import('@dcloudio/uni-app'))['onLaunch'] const onLoad: (typeof import('@dcloudio/uni-app'))['onLoad'] const onMounted: (typeof import('vue'))['onMounted'] const onNavigationBarButtonTap: (typeof import('@dcloudio/uni-app'))['onNavigationBarButtonTap'] const onNavigationBarSearchInputChanged: (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputChanged'] const onNavigationBarSearchInputClicked: (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputClicked'] const onNavigationBarSearchInputConfirmed: (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputConfirmed'] const onNavigationBarSearchInputFocusChanged: (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputFocusChanged'] const onPageNotFound: (typeof import('@dcloudio/uni-app'))['onPageNotFound'] const onPageScroll: (typeof import('@dcloudio/uni-app'))['onPageScroll'] const onPullDownRefresh: (typeof import('@dcloudio/uni-app'))['onPullDownRefresh'] const onReachBottom: (typeof import('@dcloudio/uni-app'))['onReachBottom'] const onReady: (typeof import('@dcloudio/uni-app'))['onReady'] const onRenderTracked: (typeof import('vue'))['onRenderTracked'] const onRenderTriggered: (typeof import('vue'))['onRenderTriggered'] const onResize: (typeof import('@dcloudio/uni-app'))['onResize'] const onScopeDispose: (typeof import('vue'))['onScopeDispose'] const onServerPrefetch: (typeof import('vue'))['onServerPrefetch'] const onShareAppMessage: (typeof import('@dcloudio/uni-app'))['onShareAppMessage'] const onShareTimeline: (typeof import('@dcloudio/uni-app'))['onShareTimeline'] const onShow: (typeof import('@dcloudio/uni-app'))['onShow'] const onTabItemTap: (typeof import('@dcloudio/uni-app'))['onTabItemTap'] const onThemeChange: (typeof import('@dcloudio/uni-app'))['onThemeChange'] const onUnhandledRejection: (typeof import('@dcloudio/uni-app'))['onUnhandledRejection'] const onUnload: (typeof import('@dcloudio/uni-app'))['onUnload'] const onUnmounted: (typeof import('vue'))['onUnmounted'] const onUpdated: (typeof import('vue'))['onUpdated'] const onWatcherCleanup: (typeof import('vue'))['onWatcherCleanup'] const provide: (typeof import('vue'))['provide'] const reactive: (typeof import('vue'))['reactive'] const readonly: (typeof import('vue'))['readonly'] const ref: (typeof import('vue'))['ref'] const resolveComponent: (typeof import('vue'))['resolveComponent'] const shallowReactive: (typeof import('vue'))['shallowReactive'] const shallowReadonly: (typeof import('vue'))['shallowReadonly'] const shallowRef: (typeof import('vue'))['shallowRef'] const toRaw: (typeof import('vue'))['toRaw'] const toRef: (typeof import('vue'))['toRef'] const toRefs: (typeof import('vue'))['toRefs'] const toValue: (typeof import('vue'))['toValue'] const triggerRef: (typeof import('vue'))['triggerRef'] const unref: (typeof import('vue'))['unref'] const useAttrs: (typeof import('vue'))['useAttrs'] const useCssModule: (typeof import('vue'))['useCssModule'] const useCssVars: (typeof import('vue'))['useCssVars'] const useEcharts: (typeof import('../hooks/useEcharts'))['useEcharts'] const useI18n: (typeof import('../hooks/useI18n'))['useI18n'] const useId: (typeof import('vue'))['useId'] const useModel: (typeof import('vue'))['useModel'] const useRequest: (typeof import('../hooks/useRequest'))['default'] const useSlots: (typeof import('vue'))['useSlots'] const useTemplateRef: (typeof import('vue'))['useTemplateRef'] const useTheme: (typeof import('../hooks/useTheme'))['useTheme'] const watch: (typeof import('vue'))['watch'] const watchEffect: (typeof import('vue'))['watchEffect'] const watchPostEffect: (typeof import('vue'))['watchPostEffect'] const watchSyncEffect: (typeof import('vue'))['watchSyncEffect'] } // for type re-export declare global { // @ts-ignore export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' import('vue') } // for vue template auto import import { UnwrapRef } from 'vue' declare module 'vue' { interface GlobalComponents {} interface ComponentCustomProperties { readonly EffectScope: UnwrapRef<(typeof import('vue'))['EffectScope']> readonly computed: UnwrapRef<(typeof import('vue'))['computed']> readonly createApp: UnwrapRef<(typeof import('vue'))['createApp']> readonly customRef: UnwrapRef<(typeof import('vue'))['customRef']> readonly defineAsyncComponent: UnwrapRef<(typeof import('vue'))['defineAsyncComponent']> readonly defineComponent: UnwrapRef<(typeof import('vue'))['defineComponent']> readonly effectScope: UnwrapRef<(typeof import('vue'))['effectScope']> readonly getCurrentInstance: UnwrapRef<(typeof import('vue'))['getCurrentInstance']> readonly getCurrentScope: UnwrapRef<(typeof import('vue'))['getCurrentScope']> readonly h: UnwrapRef<(typeof import('vue'))['h']> readonly inject: UnwrapRef<(typeof import('vue'))['inject']> readonly isProxy: UnwrapRef<(typeof import('vue'))['isProxy']> readonly isReactive: UnwrapRef<(typeof import('vue'))['isReactive']> readonly isReadonly: UnwrapRef<(typeof import('vue'))['isReadonly']> readonly isRef: UnwrapRef<(typeof import('vue'))['isRef']> readonly markRaw: UnwrapRef<(typeof import('vue'))['markRaw']> readonly nextTick: UnwrapRef<(typeof import('vue'))['nextTick']> readonly onActivated: UnwrapRef<(typeof import('vue'))['onActivated']> readonly onAddToFavorites: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onAddToFavorites']> readonly onBackPress: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onBackPress']> readonly onBeforeMount: UnwrapRef<(typeof import('vue'))['onBeforeMount']> readonly onBeforeUnmount: UnwrapRef<(typeof import('vue'))['onBeforeUnmount']> readonly onBeforeUpdate: UnwrapRef<(typeof import('vue'))['onBeforeUpdate']> readonly onDeactivated: UnwrapRef<(typeof import('vue'))['onDeactivated']> readonly onError: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onError']> readonly onErrorCaptured: UnwrapRef<(typeof import('vue'))['onErrorCaptured']> readonly onHide: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onHide']> readonly onLaunch: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onLaunch']> readonly onLoad: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onLoad']> readonly onMounted: UnwrapRef<(typeof import('vue'))['onMounted']> readonly onNavigationBarButtonTap: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onNavigationBarButtonTap']> readonly onNavigationBarSearchInputChanged: UnwrapRef< (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputChanged'] > readonly onNavigationBarSearchInputClicked: UnwrapRef< (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputClicked'] > readonly onNavigationBarSearchInputConfirmed: UnwrapRef< (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputConfirmed'] > readonly onNavigationBarSearchInputFocusChanged: UnwrapRef< (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputFocusChanged'] > readonly onPageNotFound: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onPageNotFound']> readonly onPageScroll: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onPageScroll']> readonly onPullDownRefresh: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onPullDownRefresh']> readonly onReachBottom: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onReachBottom']> readonly onReady: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onReady']> readonly onRenderTracked: UnwrapRef<(typeof import('vue'))['onRenderTracked']> readonly onRenderTriggered: UnwrapRef<(typeof import('vue'))['onRenderTriggered']> readonly onResize: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onResize']> readonly onScopeDispose: UnwrapRef<(typeof import('vue'))['onScopeDispose']> readonly onServerPrefetch: UnwrapRef<(typeof import('vue'))['onServerPrefetch']> readonly onShareAppMessage: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onShareAppMessage']> readonly onShareTimeline: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onShareTimeline']> readonly onShow: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onShow']> readonly onTabItemTap: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onTabItemTap']> readonly onThemeChange: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onThemeChange']> readonly onUnhandledRejection: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onUnhandledRejection']> readonly onUnload: UnwrapRef<(typeof import('@dcloudio/uni-app'))['onUnload']> readonly onUnmounted: UnwrapRef<(typeof import('vue'))['onUnmounted']> readonly onUpdated: UnwrapRef<(typeof import('vue'))['onUpdated']> readonly onWatcherCleanup: UnwrapRef<(typeof import('vue'))['onWatcherCleanup']> readonly provide: UnwrapRef<(typeof import('vue'))['provide']> readonly reactive: UnwrapRef<(typeof import('vue'))['reactive']> readonly readonly: UnwrapRef<(typeof import('vue'))['readonly']> readonly ref: UnwrapRef<(typeof import('vue'))['ref']> readonly resolveComponent: UnwrapRef<(typeof import('vue'))['resolveComponent']> readonly shallowReactive: UnwrapRef<(typeof import('vue'))['shallowReactive']> readonly shallowReadonly: UnwrapRef<(typeof import('vue'))['shallowReadonly']> readonly shallowRef: UnwrapRef<(typeof import('vue'))['shallowRef']> readonly toRaw: UnwrapRef<(typeof import('vue'))['toRaw']> readonly toRef: UnwrapRef<(typeof import('vue'))['toRef']> readonly toRefs: UnwrapRef<(typeof import('vue'))['toRefs']> readonly toValue: UnwrapRef<(typeof import('vue'))['toValue']> readonly triggerRef: UnwrapRef<(typeof import('vue'))['triggerRef']> readonly unref: UnwrapRef<(typeof import('vue'))['unref']> readonly useAttrs: UnwrapRef<(typeof import('vue'))['useAttrs']> readonly useCssModule: UnwrapRef<(typeof import('vue'))['useCssModule']> readonly useCssVars: UnwrapRef<(typeof import('vue'))['useCssVars']> readonly useEcharts: UnwrapRef<(typeof import('../hooks/useEcharts'))['useEcharts']> readonly useI18n: UnwrapRef<(typeof import('../hooks/useI18n'))['useI18n']> readonly useId: UnwrapRef<(typeof import('vue'))['useId']> readonly useModel: UnwrapRef<(typeof import('vue'))['useModel']> readonly useRequest: UnwrapRef<(typeof import('../hooks/useRequest'))['default']> readonly useSlots: UnwrapRef<(typeof import('vue'))['useSlots']> readonly useTemplateRef: UnwrapRef<(typeof import('vue'))['useTemplateRef']> readonly useTheme: UnwrapRef<(typeof import('../hooks/useTheme'))['useTheme']> readonly watch: UnwrapRef<(typeof import('vue'))['watch']> readonly watchEffect: UnwrapRef<(typeof import('vue'))['watchEffect']> readonly watchPostEffect: UnwrapRef<(typeof import('vue'))['watchPostEffect']> readonly watchSyncEffect: UnwrapRef<(typeof import('vue'))['watchSyncEffect']> } } ================================================ FILE: src/types/gloal.d.ts ================================================ import { ComponentCustomProperties } from 'vue' declare module 'vue' { interface ComponentCustomProperties { $assets: any } } ================================================ FILE: src/types/uni-pages.d.ts ================================================ /* eslint-disable */ /* prettier-ignore */ // @ts-nocheck // Generated by vite-plugin-uni-pages interface NavigateToOptions { url: "/pages/index/index" | "/pages/customDemo/index" | "/pages/echartsDemo/index" | "/pages/i18nDemo/index" | "/pages/layoutDemo/index" | "/pages/piniaDemo/index" | "/pages/queryDemo/loginDemo" | "/pages/queryDemo/queryTestDemo" | "/pages/queryDemo/useRequestDemo" | "/pages/queryDemo/zPagingDemo" | "/pages/routerDemo/index" | "/pages/routerDemo/login" | "/pages/routerDemo/page1" | "/pages/routerDemo/page2" | "/pages/routerDemo/page3" | "/pages/unocssDemo/index" | "/pages/wotUiDemo/index" | "/pages-sub/subDemo/index" | "/pages-sub/testDemo/index"; } interface RedirectToOptions extends NavigateToOptions {} interface SwitchTabOptions {} type ReLaunchOptions = NavigateToOptions | SwitchTabOptions declare interface Uni { navigateTo(options: UniNamespace.NavigateToOptions & NavigateToOptions): void redirectTo(options: UniNamespace.RedirectToOptions & RedirectToOptions): void switchTab(options: UniNamespace.SwitchTabOptions & SwitchTabOptions): void reLaunch(options: UniNamespace.ReLaunchOptions & ReLaunchOptions): void } ================================================ FILE: src/typings.ts ================================================ type ResData = { data: T code: number message: string } ================================================ FILE: src/uni.scss ================================================ /** * 这里是uni-app内置的常用样式变量 * * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App * */ /** * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 * * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 */ /* 颜色变量 */ /* 行为相关颜色 */ $uni-color-primary: #007aff; $uni-color-success: #4cd964; $uni-color-warning: #f0ad4e; $uni-color-error: #dd524d; /* 文字基本颜色 */ $uni-text-color: #333; // 基本色 $uni-text-color-inverse: #fff; // 反色 $uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息 $uni-text-color-placeholder: #808080; $uni-text-color-disable: #c0c0c0; /* 背景颜色 */ $uni-bg-color: #fff; $uni-bg-color-grey: #f8f8f8; $uni-bg-color-hover: #f1f1f1; // 点击状态颜色 $uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色 /* 边框颜色 */ $uni-border-color: #c8c7cc; /* 尺寸变量 */ /* 文字尺寸 */ $uni-font-size-sm: 12px; $uni-font-size-base: 14px; $uni-font-size-lg: 16; /* 图片尺寸 */ $uni-img-size-sm: 20px; $uni-img-size-base: 26px; $uni-img-size-lg: 40px; /* Border Radius */ $uni-border-radius-sm: 2px; $uni-border-radius-base: 3px; $uni-border-radius-lg: 6px; $uni-border-radius-circle: 50%; /* 水平间距 */ $uni-spacing-row-sm: 5px; $uni-spacing-row-base: 10px; $uni-spacing-row-lg: 15px; /* 垂直间距 */ $uni-spacing-col-sm: 4px; $uni-spacing-col-base: 8px; $uni-spacing-col-lg: 12px; /* 透明度 */ $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 /* 文章场景相关 */ $uni-color-title: #2c405a; // 文章标题颜色 $uni-font-size-title: 20px; $uni-color-subtitle: #555; // 二级标题颜色 $uni-font-size-subtitle: 18px; $uni-color-paragraph: #3f536e; // 文章段落颜色 $uni-font-size-paragraph: 15px; ================================================ FILE: src/uni_modules/lime-echart/changelog.md ================================================ ## 0.9.8(2024-12-20) - fix: 修复 APP 无法放大问题 ## 0.9.7(2024-12-02) - feat: uniapp 增加`landscape`,当`landscape`为`true`时旋转90deg达到横屏效果。 - feat: 支持uniapp x 微信小程序 ## 0.9.6(2024-07-23) - fix: 修复 uni is not defined ## 0.9.5(2024-07-19) - chore: 鸿蒙`measureText`为异步,异步字体不正常,使用模拟方式。 ## 0.9.4(2024-07-18) - chore: 更新文档 ## 0.9.3(2024-07-16) - feat: 鸿蒙 canvas 事件缺失,待官方修复,如何在鸿蒙使用请看文档`常见问题 vue3` ## 0.9.2(2024-07-12) - chore: 删除多余文件 ## 0.9.1(2024-07-12) - fix: 修复 安卓5不显示图表问题 ## 0.9.0(2024-06-13) - chore: 合并nvue和uvue ## 0.8.9(2024-05-19) - chore: 更新文档 ## 0.8.8(2024-05-13) - chore: 更新文档和uvue示例 ## 0.8.7(2024-04-26) - fix: uniapp x需要HBX 4.13以上 ## 0.8.6(2024-04-10) - feat: 支持 uniapp x ios ## 0.8.5(2024-04-03) - fix: 修复 nvue `reset`传值不生效问题 - feat: 支持 uniapp x web ## 0.8.4(2024-01-27) - chore: 更新文档 ## 0.8.3(2024-01-21) - chore: 更新文档 ## 0.8.2(2024-01-21) - feat: 支持 `uvue` ## 0.8.1(2023-08-24) - fix: app 的`touch`事件为`object` 导致无法显示 `tooltip` ## 0.8.0(2023-08-22) - fix: 离屏 报错问题 - fix: 微信小程序PC无法使用事件 - chore: 更新文档 ## 0.7.9(2023-07-29) - chore: 更新文档 ## 0.7.8(2023-07-29) - fix: 离屏 报错问题 ## 0.7.7(2023-07-27) - chore: 更新文档 - chore: lime-echart 里的示例使用自定tooltips - feat: 对支持离屏的使用离屏创建(微信、字节、支付宝) ## 0.7.6(2023-06-30) - fix: vue3 报`width`的错 ## 0.7.5(2023-05-25) - chore: 更新文档 和 demo, 使用`lime-echart`这个标签即可查看示例 ## 0.7.4(2023-05-22) - chore: 增加关于钉钉小程序上传时提示安全问题的说明及修改建议 ## 0.7.3(2023-05-16) - chore: 更新 vue3 非微信小程序平台可能缺少`wx`的说明 ## 0.7.2(2023-05-16) - chore: 更新 vue3 非微信小程序平台的可以缺少`wx`的说明 ## 0.7.1(2023-04-26) - chore: 更新demo,使用`lime-echart`这个标签即可查看示例 - chore:微信小程序的`tooltip`文字有阴影,怀疑是微信的锅,临时解决方法是`tooltip.shadowBlur = 0` ## 0.7.0(2023-04-24) - fix: 修复`setAttribute is not a function` ## 0.6.9(2023-04-15) - chore: 更新文档,vue3请使用echarts esm的包 ## 0.6.8(2023-03-22) - feat: mac pc无法使用canvas 2d ## 0.6.7(2023-03-17) - feat: 更新文档 ## 0.6.6(2023-03-17) - feat: 微信小程序PC已经支持canvas 2d,故去掉判断PC ## 0.6.5(2022-11-03) - fix: 某些手机touches为对象,导致无法交互。 ## 0.6.4(2022-10-28) - fix: 优化点击事件的触发条件 ## 0.6.3(2022-10-26) - fix: 修复 dataZoom 拖动问题 ## 0.6.2(2022-10-23) - fix: 修复 飞书小程序 尺寸问题 ## 0.6.1(2022-10-19) - fix: 修复 PC mousewheel 事件 鼠标位置不准确的BUG,不兼容火狐! - feat: showLoading 增加传参 ## 0.6.0(2022-09-16) - feat: 增加PC的mousewheel事件 ## 0.5.4(2022-09-16) - fix: 修复 nvue 动态数据不显示问题 ## 0.5.3(2022-09-16) - feat: 增加enableHover属性, 在PC端时当鼠标进入显示tooltip,不必按下。 - chore: 更新文档 ## 0.5.2(2022-09-16) - feat: 增加enableHover属性, 在PC端时当鼠标进入显示tooltip,不必按下。 ## 0.5.1(2022-09-16) - fix: 修复nvue报错 ## 0.5.0(2022-09-15) - feat: init(echarts, theme?:string, opts?:{}, callback: function(chart)) ## 0.4.8(2022-09-11) - feat: 增加 @finished ## 0.4.7(2022-08-24) - chore: 去掉 stylus ## 0.4.6(2022-08-24) - feat: 增加 beforeDelay ## 0.4.5(2022-08-12) - chore: 更新文档 ## 0.4.4(2022-08-12) - fix: 修复 resize 无参数时报错 ## 0.4.3(2022-08-07) # 评论有说本插件对新手不友好,让我做不好就不要发出来。 还有的说跟官网一样,发出来做什么,给我整无语了。 # 所以在此提醒一下准备要下载的你,如果你从未使用过 echarts 请不要下载 或 谨慎下载。 # 如果你确认要下载,麻烦看完文档。还有请注意插件是让echarts在uniapp能运行,API 配置请自行去官网查阅! # 如果你不会echarts 但又需要图表,市场上有个很优秀的图表插件 uchart 你可以去使用这款插件,uchart的作者人很好,也热情。 # 每个人都有自己的本职工作,如果你能力强可以自行兼容,如果使用了他人的插件也麻烦尊重他人的成果和劳动时间。谢谢。 # 为了心情愉悦,本人已经使用插件屏蔽差评。 - chore: 更新文档 ## 0.4.2(2022-07-20) - feat: 增加 resize ## 0.4.1(2022-06-07) - fix: 修复 canvasToTempFilePath 不生效问题 ## 0.4.0(2022-06-04) - chore 为了词云 增加一个canvas 标签 - 词云下载地址[echart-wordcloud](https://ext.dcloud.net.cn/plugin?id=8430) ## 0.3.9(2022-06-02) - chore: 更新文档 - tips: lines 不支持 `trailLength` ## 0.3.8(2022-05-31) - fix: 修复 因mouse事件冲突tooltip跳动问题 ## 0.3.7(2022-05-26) - chore: 更新文档 - chore: 设置默认宽高300px - fix: 修复 vue3 微信小程序 拖影BUG - chore: 支持PC ## 0.3.5(2022-04-28) - chore: 更新使用方式 - 🔔 必须使用hbuilderx 3.4.8-alpha以上 ## 0.3.4(2021-08-03) - chore: 增加 setOption的参数值 ## 0.3.3(2021-07-22) - fix: 修复 径向渐变报错的问题 ## 0.3.2(2021-07-09) - chore: 统一命名规范,无须主动引入组件 ## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example) ## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example) ## 0.3.1(2021-06-21) - fix: 修复 app-nvue ios is-enable 无效的问题 ## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example) ## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example) ## 0.3.0(2021-06-14) - fix: 修复 头条系小程序 2d 报 JSON.stringify 的问题 - 目前 头条系小程序 2d 无法在开发工具上预览,划动图表页面无法滚动,axisLabel 字体颜色无法更改,建议使用非2d。 ## 0.2.9(2021-06-06) - fix: 修复 头条系小程序 2d 放大的BUG - 头条系小程序 2d 无法在开发工具上预览,也存在划动图表页面无法滚动的问题。 ## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example) ## 0.2.8(2021-05-19) - fix: 修复 微信小程序 PC 显示过大的问题 ## 0.2.7(2021-05-19) - fix: 修复 微信小程序 PC 不显示问题 ## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example) ## 0.2.6(2021-05-14) - feat: 支持 `image` - feat: props 增加 `ec.clear`,更新时是否先删除图表样式 - feat: props 增加 `isDisableScroll` ,触摸图表时是否禁止页面滚动 - feat: props 增加 `webviewStyles` ,webview 的样式, 仅nvue有效 ## 0.2.5(2021-05-13) - docs: 插件用到了css 预编译器 [stylus](https://ext.dcloud.net.cn/plugin?name=compile-stylus) 请安装它 ## 0.2.4(2021-05-12) - fix: 修复 百度平台 多个图表ctx 和 渐变色 bug - ## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example) ## 0.2.3(2021-05-10) - feat: 增加 `canvasToTempFilePath` 方法,用于生成图片 ```js this.$refs.chart.canvasToTempFilePath({success: (res) => { console.log('tempFilePath:', res.tempFilePath) }}) ``` ## 0.2.2(2021-05-10) - feat: 增加 `dispose` 方法,用于销毁实例 - feat: 增加 `isClickable` 是否派发点击 - feat: 实验性的支持 `nvue` 使用要慎重考虑 - ## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example) ## 0.2.1(2021-05-06) - fix:修复 微信小程序 json 报错 - chore: `reset` 更改为 `setChart` - feat: 增加 `isEnable` 开启初始化 启用这个后 无须再使用`init`方法 ```html ``` ```js // 显示加载 this.$refs.chart.showLoading() // 使用实例回调 this.$refs.chart.setChart(chart => ...code) // 直接设置图表配置 this.$refs.chart.setOption(data) ``` ## 0.2.0(2021-05-05) - fix:修复 头条 百度 偏移的问题 - docs: 更新文档 ## [代码示例:http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example) ## 0.1.0(2021-05-02) - chore: 第一次上传,基本全端兼容,使用方法与官网一致。 - 已知BUG:非2d 无法使用背景色,已反馈官方 - 已知BUG:头条 百度 有许些偏移 - 后期计划:兼容nvue ================================================ FILE: src/uni_modules/lime-echart/components/l-echart/canvas.js ================================================ import {getDeviceInfo} from './utils'; const cacheChart = {} const fontSizeReg = /([\d\.]+)px/; class EventEmit { constructor() { this.__events = {}; } on(type, listener) { if (!type || !listener) { return; } const events = this.__events[type] || []; events.push(listener); this.__events[type] = events; } emit(type, e) { if (type.constructor === Object) { e = type; type = e && e.type; } if (!type) { return; } const events = this.__events[type]; if (!events || !events.length) { return; } events.forEach((listener) => { listener.call(this, e); }); } off(type, listener) { const __events = this.__events; const events = __events[type]; if (!events || !events.length) { return; } if (!listener) { delete __events[type]; return; } for (let i = 0, len = events.length; i < len; i++) { if (events[i] === listener) { events.splice(i, 1); i--; } } } } class Image { constructor() { this.currentSrc = null this.naturalHeight = 0 this.naturalWidth = 0 this.width = 0 this.height = 0 this.tagName = 'IMG' } set src(src) { this.currentSrc = src uni.getImageInfo({ src, success: (res) => { this.naturalWidth = this.width = res.width this.naturalHeight = this.height = res.height this.onload() }, fail: () => { this.onerror() } }) } get src() { return this.currentSrc } } class OffscreenCanvas { constructor(ctx, com, canvasId) { this.tagName = 'canvas' this.com = com this.canvasId = canvasId this.ctx = ctx } set width(w) { this.com.offscreenWidth = w } set height(h) { this.com.offscreenHeight = h } get width() { return this.com.offscreenWidth || 0 } get height() { return this.com.offscreenHeight || 0 } getContext(type) { return this.ctx } getImageData() { return new Promise((resolve, reject) => { this.com.$nextTick(() => { uni.canvasGetImageData({ x:0, y:0, width: this.com.offscreenWidth, height: this.com.offscreenHeight, canvasId: this.canvasId, success: (res) => { resolve(res) }, fail: (err) => { reject(err) }, }, this.com) }) }) } } export class Canvas { constructor(ctx, com, isNew, canvasNode={}) { cacheChart[com.canvasId] = {ctx} this.canvasId = com.canvasId; this.chart = null; this.isNew = isNew this.tagName = 'canvas' this.canvasNode = canvasNode; this.com = com; if (!isNew) { this._initStyle(ctx) } this._initEvent(); this._ee = new EventEmit() } getContext(type) { if (type === '2d') { return this.ctx; } } setAttribute(key, value) { if(key === 'aria-label') { this.com['ariaLabel'] = value } } setChart(chart) { this.chart = chart; } createOffscreenCanvas(param){ if(!this.children) { this.com.isOffscreenCanvas = true this.com.offscreenWidth = param.width||300 this.com.offscreenHeight = param.height||300 const com = this.com const canvasId = this.com.offscreenCanvasId const context = uni.createCanvasContext(canvasId, this.com) this._initStyle(context) this.children = new OffscreenCanvas(context, com, canvasId) } return this.children } appendChild(child) { console.log('child', child) } dispatchEvent(type, e) { if(typeof type == 'object') { this._ee.emit(type.type, type); } else { this._ee.emit(type, e); } return true } attachEvent() { } detachEvent() { } addEventListener(type, listener) { this._ee.on(type, listener) } removeEventListener(type, listener) { this._ee.off(type, listener) } _initCanvas(zrender, ctx) { // zrender.util.getContext = function() { // return ctx; // }; // zrender.util.$override('measureText', function(text, font) { // ctx.font = font || '12px sans-serif'; // return ctx.measureText(text, font); // }); } _initStyle(ctx, child) { const styles = [ 'fillStyle', 'strokeStyle', 'fontSize', 'globalAlpha', 'opacity', 'textAlign', 'textBaseline', 'shadow', 'lineWidth', 'lineCap', 'lineJoin', 'lineDash', 'miterLimit', // #ifdef H5 'font', // #endif ]; const colorReg = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b/g; styles.forEach(style => { Object.defineProperty(ctx, style, { set: value => { // #ifdef H5 if (style === 'font' && fontSizeReg.test(value)) { const match = fontSizeReg.exec(value); ctx.setFontSize(match[1]); return; } // #endif if (style === 'opacity') { ctx.setGlobalAlpha(value) return; } if (style !== 'fillStyle' && style !== 'strokeStyle' || value !== 'none' && value !== null) { // #ifdef H5 || APP-PLUS || MP-BAIDU if(typeof value == 'object') { if (value.hasOwnProperty('colorStop') || value.hasOwnProperty('colors')) { ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value); } return } // #endif // #ifdef MP-TOUTIAO if(colorReg.test(value)) { value = value.replace(colorReg, '#$1$1$2$2$3$3') } // #endif ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value); } } }); }); if(!this.isNew && !child) { ctx.uniDrawImage = ctx.drawImage ctx.drawImage = (...a) => { a[0] = a[0].src ctx.uniDrawImage(...a) } } if(!ctx.createRadialGradient) { ctx.createRadialGradient = function() { return ctx.createCircularGradient(...[...arguments].slice(-3)) }; } // 字节不支持 if (!ctx.strokeText) { ctx.strokeText = (...a) => { ctx.fillText(...a) } } // 钉钉不支持 , 鸿蒙是异步 if (!ctx.measureText || getDeviceInfo().osName == 'harmonyos') { ctx._measureText = ctx.measureText const strLen = (str) => { let len = 0; for (let i = 0; i < str.length; i++) { if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) { len++; } else { len += 2; } } return len; } ctx.measureText = (text, font) => { let fontSize = ctx?.state?.fontSize || 12; if (font) { fontSize = parseInt(font.match(/([\d\.]+)px/)[1]) } fontSize /= 2; let isBold = fontSize >= 16; const widthFactor = isBold ? 1.3 : 1; // ctx._measureText(text, (res) => {}) return { width: strLen(text) * fontSize * widthFactor }; } } } _initEvent(e) { this.event = {}; const eventNames = [{ wxName: 'touchStart', ecName: 'mousedown' }, { wxName: 'touchMove', ecName: 'mousemove' }, { wxName: 'touchEnd', ecName: 'mouseup' }, { wxName: 'touchEnd', ecName: 'click' }]; eventNames.forEach(name => { this.event[name.wxName] = e => { const touch = e.touches[0]; this.chart.getZr().handler.dispatch(name.ecName, { zrX: name.wxName === 'tap' ? touch.clientX : touch.x, zrY: name.wxName === 'tap' ? touch.clientY : touch.y }); }; }); } set width(w) { this.canvasNode.width = w } set height(h) { this.canvasNode.height = h } get width() { return this.canvasNode.width || 0 } get height() { return this.canvasNode.height || 0 } get ctx() { return cacheChart[this.canvasId]['ctx'] || null } set chart(chart) { cacheChart[this.canvasId]['chart'] = chart } get chart() { return cacheChart[this.canvasId]['chart'] || null } } export function dispatch(name, {x,y, wheelDelta}) { this.dispatch(name, { zrX: x, zrY: y, zrDelta: wheelDelta, preventDefault: () => {}, stopPropagation: () =>{} }); } export function setCanvasCreator(echarts, {canvas, node}) { // echarts.setCanvasCreator(() => canvas); if(echarts && !echarts.registerPreprocessor) { return console.warn('echarts 版本不对或未传入echarts,vue3请使用esm格式') } echarts.registerPreprocessor(option => { if (option && option.series) { if (option.series.length > 0) { option.series.forEach(series => { series.progressive = 0; }); } else if (typeof option.series === 'object') { option.series.progressive = 0; } } }); function loadImage(src, onload, onerror) { let img = null if(node && node.createImage) { img = node.createImage() img.onload = onload.bind(img); img.onerror = onerror.bind(img); img.src = src; return img } else { img = new Image() img.onload = onload.bind(img) img.onerror = onerror.bind(img); img.src = src return img } } if(echarts.setPlatformAPI) { echarts.setPlatformAPI({ loadImage: canvas.setChart ? loadImage : null, createCanvas(){ const key = 'createOffscreenCanvas' return uni.canIUse(key) && uni[key] ? uni[key]({type: '2d'}) : canvas } }) } } ================================================ FILE: src/uni_modules/lime-echart/components/l-echart/l-echart.uvue ================================================ ================================================ FILE: src/uni_modules/lime-echart/components/l-echart/l-echart.vue ================================================ ================================================ FILE: src/uni_modules/lime-echart/components/l-echart/nvue.js ================================================ export class Echarts { eventMap = new Map() constructor(webview) { this.webview = webview this.options = null } setOption() { this.options = arguments this.webview.evalJs(`setOption(${JSON.stringify(arguments)})`); } getOption() { return this.options } showLoading() { this.webview.evalJs(`showLoading(${JSON.stringify(arguments)})`); } hideLoading() { this.webview.evalJs(`hideLoading()`); } clear() { this.webview.evalJs(`clear()`); } dispose() { this.webview.evalJs(`dispose()`); } resize(size) { if(size) { this.webview.evalJs(`resize(${JSON.stringify(size)})`); } else { this.webview.evalJs(`resize()`); } } on(type, ...args) { const query = args[0] const useQuery = query && typeof query != 'function' const param = useQuery ? [type, query] : [type] const key = `${type}${useQuery ? JSON.stringify(query): '' }` const callback = useQuery ? args[1]: args[0] if(typeof callback == 'function'){ this.eventMap.set(key, callback) } this.webview.evalJs(`on(${JSON.stringify(param)})`); console.warn('nvue 暂不支持事件') } dispatchAction(type, options){ const handler = this.eventMap.get(type) if(handler){ handler(options) } } } ================================================ FILE: src/uni_modules/lime-echart/components/l-echart/utils.js ================================================ // @ts-nocheck /** * 获取设备基础信息 * * @see [uni.getDeviceInfo](https://uniapp.dcloud.net.cn/api/system/getDeviceInfo.html) */ export function getDeviceInfo() { if (uni.getDeviceInfo && uni.canIUse('getDeviceInfo')) { return uni.getDeviceInfo(); } else { return uni.getSystemInfoSync(); } } /** * 获取窗口信息 * * @see [uni.getWindowInfo](https://uniapp.dcloud.net.cn/api/system/getWindowInfo.html) */ export function getWindowInfo() { if (uni.getWindowInfo && uni.canIUse('getWindowInfo')) { return uni.getWindowInfo(); } else { return uni.getSystemInfoSync(); } } /** * 获取APP基础信息 * * @see [uni.getAppBaseInfo](https://uniapp.dcloud.net.cn/api/system/getAppBaseInfo.html) */ export function getAppBaseInfo() { if (uni.getAppBaseInfo && uni.canIUse('getAppBaseInfo')) { return uni.getAppBaseInfo(); } else { return uni.getSystemInfoSync(); } } // #ifndef APP-NVUE // 计算版本 export function compareVersion(v1, v2) { v1 = v1.split('.') v2 = v2.split('.') const len = Math.max(v1.length, v2.length) while (v1.length < len) { v1.push('0') } while (v2.length < len) { v2.push('0') } for (let i = 0; i < len; i++) { const num1 = parseInt(v1[i], 10) const num2 = parseInt(v2[i], 10) if (num1 > num2) { return 1 } else if (num1 < num2) { return -1 } } return 0 } // const systemInfo = uni.getSystemInfoSync(); function gte(version) { // 截止 2023-03-22 mac pc小程序不支持 canvas 2d // let { // SDKVersion, // platform // } = systemInfo; const { platform } = getDeviceInfo(); let { SDKVersion } = getAppBaseInfo(); // #ifdef MP-ALIPAY SDKVersion = my.SDKVersion // #endif // #ifdef MP-WEIXIN return platform !== 'mac' && compareVersion(SDKVersion, version) >= 0; // #endif return compareVersion(SDKVersion, version) >= 0; } export function canIUseCanvas2d() { // #ifdef MP-WEIXIN return gte('2.9.0'); // #endif // #ifdef MP-ALIPAY return gte('2.7.0'); // #endif // #ifdef MP-TOUTIAO return gte('1.78.0'); // #endif return false } export function convertTouchesToArray(touches) { // 如果 touches 是一个数组,则直接返回它 if (Array.isArray(touches)) { return touches; } // 如果touches是一个对象,则转换为数组 if (typeof touches === 'object' && touches !== null) { return Object.values(touches); } // 对于其他类型,直接返回它 return touches; } export function wrapTouch(event) { event.touches = convertTouchesToArray(event.touches) for (let i = 0; i < event.touches.length; ++i) { const touch = event.touches[i]; touch.offsetX = touch.x; touch.offsetY = touch.y; } return event; } // export const devicePixelRatio = uni.getSystemInfoSync().pixelRatio export const devicePixelRatio = getWindowInfo().pixelRatio; // #endif // #ifdef APP-NVUE export function base64ToPath(base64) { return new Promise((resolve, reject) => { const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || []; const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now()) bitmap.loadBase64Data(base64, () => { if (!format) { reject(new Error('ERROR_BASE64SRC_PARSE')) } const time = new Date().getTime(); const filePath = `_doc/uniapp_temp/${time}.${format}` bitmap.save(filePath, {}, () => { bitmap.clear() resolve(filePath) }, (error) => { bitmap.clear() console.error(`${JSON.stringify(error)}`) reject(error) }) }, (error) => { bitmap.clear() console.error(`${JSON.stringify(error)}`) reject(error) }) }) } // #endif export function sleep(time) { return new Promise((resolve) => { setTimeout(() => { resolve(true) }, time) }) } export function getRect(selector, options = {}) { const typeDefault = 'boundingClientRect' const { context, type = typeDefault } = options return new Promise((resolve, reject) => { const dom = uni.createSelectorQuery().in(context).select(selector); const result = (rect) => { if (rect) { resolve(rect) } else { reject() } } if (type == typeDefault) { dom[type](result).exec() } else { dom[type]({ node: true, size: true, rect: true }, result).exec() } }); }; ================================================ FILE: src/uni_modules/lime-echart/components/l-echart/uvue.uts ================================================ // @ts-nocheck // #ifdef APP type EchartsEventHandler = (event: UTSJSONObject)=>void // type EchartsTempResolve = (obj : UTSJSONObject) => void // type EchartsTempOptions = UTSJSONObject export class Echarts { options: UTSJSONObject = {} as UTSJSONObject context: UniWebViewElement eventMap: Map = new Map() private temp: UTSJSONObject[] = [] constructor(context: UniWebViewElement){ this.context = context this.init() } init(){ this.context.evalJS(`init(null, null, ${JSON.stringify({})})`) this.context.addEventListener('message', (e : UniWebViewMessageEvent) => { // event.stopPropagation() // event.preventDefault() const detail = e.detail.data[0] const file = detail.getString('file') const data = detail.get('data') const key = detail.getString('event') const options = typeof data == 'object' ? (data as UTSJSONObject).getJSON('options'): null const event = typeof data == 'object' ? (data as UTSJSONObject).getString('event'): null if (key == 'log' && data != null) { console.log(data) } if (event != null && options != null) { this.dispatchAction(event.replace(/"/g,''), options) } if(file != null){ while (this.temp.length > 0) { const opt = this.temp.pop() const success = opt?.get('success') if(typeof success == 'function'){ success as (res: UTSJSONObject) => void success({tempFilePath: file}) } } } }) } setOption(option: UTSJSONObject){ this.options = option; this.context.evalJS(`setOption(${JSON.stringify([option])})`) } setOption(option: UTSJSONObject, notMerge: boolean = false, lazyUpdate: boolean = false){ this.options = option; this.context.evalJS(`setOption(${JSON.stringify([option, notMerge, lazyUpdate])})`) } setOption(option: UTSJSONObject, notMerge: UTSJSONObject){ this.options = option; this.context.evalJS(`setOption(${JSON.stringify([option, notMerge])})`) } getOption(): UTSJSONObject { return this.options } showLoading(){ this.context.evalJS(`showLoading(${JSON.stringify([] as any[])})`); } showLoading(type: string, opts: UTSJSONObject){ this.context.evalJS(`showLoading(${JSON.stringify([type, opts])})`); } hideLoading(){ this.context.evalJS(`hideLoading()`); } clear(){ this.context.evalJS(`clear()`); } dispose(){ this.context.evalJS(`dispose()`); } resize(size:UTSJSONObject){ setTimeout(()=>{ this.context.evalJS(`resize(${JSON.stringify(size)})`); },0) } resize(){ setTimeout(()=>{ this.context.evalJS(`resize()`); },10) } on(type:string, query: any, callback: EchartsEventHandler) { const key = `${type}${JSON.stringify(query)}` if(typeof callback == 'function'){ this.eventMap.set(key, callback) } this.context.evalJS(`on(${JSON.stringify([type, query])})`); console.warn('uvue 暂不支持事件') } on(type:string, callback: EchartsEventHandler) { const key = `${type}` if(typeof callback == 'function'){ this.eventMap.set(key, callback) } this.context.evalJS(`on(${JSON.stringify([type])})`); console.warn('uvue 暂不支持事件') } dispatchAction(type:string, options: UTSJSONObject){ const handler = this.eventMap.get(type) if(handler!=null){ handler(options) } } canvasToTempFilePath(opt: UTSJSONObject){ // this.context.evalJS(`on(${JSON.stringify(opt)})`); this.context.evalJS(`canvasToTempFilePath(${JSON.stringify(opt)})`); this.temp.push(opt) } } // #endif // #ifndef APP export class Echarts { constructor() {} setOption(option: UTSJSONObject): void isDisposed(): boolean; clear(): void; resize(size:UTSJSONObject): void; resize(): void; canvasToTempFilePath(opt : UTSJSONObject): void; dispose(): void; showLoading(cfg?: UTSJSONObject): void; showLoading(name?: string, cfg?: UTSJSONObject): void; hideLoading(): void; getZr(): any } // #endif ================================================ FILE: src/uni_modules/lime-echart/components/lime-echart/lime-echart.nvue ================================================ ================================================ FILE: src/uni_modules/lime-echart/components/lime-echart/lime-echart.uvue ================================================ ================================================ FILE: src/uni_modules/lime-echart/components/lime-echart/lime-echart.vue ================================================ ================================================ FILE: src/uni_modules/lime-echart/package.json ================================================ { "id": "lime-echart", "displayName": "echarts", "version": "0.9.8", "description": "echarts 全端兼容,一款使echarts图表能跑在uniapp各端中的插件, 支持uniapp/uniappx(web,ios,安卓)", "keywords": [ "echarts", "canvas", "图表", "可视化" ], "repository": "https://gitee.com/liangei/lime-echart", "engines": { "HBuilderX": "^3.6.4" }, "dcloudext": { "sale": { "regular": { "price": "0.00" }, "sourcecode": { "price": "0.00" } }, "contact": { "qq": "" }, "declaration": { "ads": "无", "data": "无", "permissions": "无" }, "npmurl": "", "type": "component-vue" }, "uni_modules": { "dependencies": [], "encrypt": [], "platforms": { "cloud": { "tcb": "y", "aliyun": "y", "alipay": "n" }, "client": { "App": { "app-vue": "y", "app-nvue": "y", "app-uvue": "y", "app-harmony": "u" }, "H5-mobile": { "Safari": "y", "Android Browser": "y", "微信浏览器(Android)": "y", "QQ浏览器(Android)": "y" }, "H5-pc": { "Chrome": "u", "IE": "u", "Edge": "u", "Firefox": "u", "Safari": "u" }, "小程序": { "微信": "y", "阿里": "y", "百度": "y", "字节跳动": "y", "QQ": "y", "钉钉": "u", "快手": "u", "飞书": "u", "京东": "u" }, "快应用": { "华为": "u", "联盟": "u" }, "Vue": { "vue2": "y", "vue3": "y" } } } }, "dependencies": { "echarts": "^5.4.1", "zrender": "^5.4.3" } } ================================================ FILE: src/uni_modules/lime-echart/readme.md ================================================ # echarts 图表 👑👑👑👑👑 全端 > 一个基于 JavaScript 的开源可视化图表库 [查看更多](https://limeui.qcoon.cn/#/echart)
> 基于 echarts 做了兼容处理,更多示例请访问 [uni示例](https://limeui.qcoon.cn/#/echart-example) | [官方示例](https://echarts.apache.org/examples/zh/index.html)
## 平台兼容 | H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App | | --- | ---------- | ------------ | ---------- | ---------- | --------- | ---- | | √ | √ | √ | √ | √ | √ | √ | ## 安装 - 第一步:在市场导入 [百度图表](https://ext.dcloud.net.cn/plugin?id=4899) - 第二步:选择插件依赖:
1、可以选插件内的`echarts`包或自定义包,自定义包[下载地址](https://echarts.apache.org/zh/builder.html)
2、或者使用`npm`安装`echarts` **注意** * 🔔 echarts 5.3.0及以上 * 🔔 如果是 `cli` 项目请下载插件到`src`目录下的`uni_modules`,没有这个目录就创建一个 ## 代码演示 ### Vue2 - 引入依赖,可以是插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html),也可以是`npm`包 ```html ``` ```js // 插件内的 三选一 import * as echarts from '@/uni_modules/lime-echart/static/echarts.min' // 自定义的 三选一 下载后放入项目的路径 import * as echarts from 'xxx/echarts.min' // npm包 三选一 需要在控制台 输入命令:npm install echarts import * as echarts from 'echarts' ``` ```js export default { data() { return { option: { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, confine: true }, legend: { data: ['热度', '正面', '负面'] }, grid: { left: 20, right: 20, bottom: 15, top: 40, containLabel: true }, xAxis: [ { type: 'value', axisLine: { lineStyle: { color: '#999999' } }, axisLabel: { color: '#666666' } } ], yAxis: [ { type: 'category', axisTick: { show: false }, data: ['汽车之家', '今日头条', '百度贴吧', '一点资讯', '微信', '微博', '知乎'], axisLine: { lineStyle: { color: '#999999' } }, axisLabel: { color: '#666666' } } ], series: [ { name: '热度', type: 'bar', label: { normal: { show: true, position: 'inside' } }, data: [300, 270, 340, 344, 300, 320, 310], }, { name: '正面', type: 'bar', stack: '总量', label: { normal: { show: true } }, data: [120, 102, 141, 174, 190, 250, 220] }, { name: '负面', type: 'bar', stack: '总量', label: { normal: { show: true, position: 'left' } }, data: [-20, -32, -21, -34, -90, -130, -110] } ] }, }; }, // 组件能被调用必须是组件的节点已经被渲染到页面上 methods: { async init() { // chart 图表实例不能存在data里 const chart = await this.$refs.chartRef.init(echarts); chart.setOption(this.option) } } } ``` ### Vue3 - 小程序可以使用`require`引入插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html) - `require`仅支持相对路径,不支持路径别名 - 非小程序使用 `npm` 包 ```html ``` ```js // 小程序 二选一 // 插件内的 二选一 const echarts = require('../../uni_modules/lime-echart/static/echarts.min'); // 自定义的 二选一 下载后放入项目的路径 const echarts = require('xxx/xxx/echarts'); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 非小程序 // 需要在控制台 输入命令:npm install echarts import * as echarts from 'echarts' ``` ```js const chartRef = ref(null) const option = { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, confine: true }, legend: { data: ['热度', '正面', '负面'] }, grid: { left: 20, right: 20, bottom: 15, top: 40, containLabel: true }, xAxis: [ { type: 'value', axisLine: { lineStyle: { color: '#999999' } }, axisLabel: { color: '#666666' } } ], yAxis: [ { type: 'category', axisTick: { show: false }, data: ['汽车之家', '今日头条', '百度贴吧', '一点资讯', '微信', '微博', '知乎'], axisLine: { lineStyle: { color: '#999999' } }, axisLabel: { color: '#666666' } } ], series: [ { name: '热度', type: 'bar', label: { normal: { show: true, position: 'inside' } }, data: [300, 270, 340, 344, 300, 320, 310], }, { name: '正面', type: 'bar', stack: '总量', label: { normal: { show: true } }, data: [120, 102, 141, 174, 190, 250, 220] }, { name: '负面', type: 'bar', stack: '总量', label: { normal: { show: true, position: 'left' } }, data: [-20, -32, -21, -34, -90, -130, -110] } ] }; onMounted( ()=>{ // 组件能被调用必须是组件的节点已经被渲染到页面上 setTimeout(async()=>{ if(!chartRef.value) return const myChart = await chartRef.value.init(echarts) myChart.setOption(option) },300) }) ``` ### Uvue - Uvue和Nvue不需要引入`echarts`,因为它们的实现方式是`webview` - uniapp x需要HBX 4.13以上 ```html ``` ```js // @ts-nocheck // #ifdef H5 import * as echarts from 'echarts/dist/echarts.esm.js' // #endif const chartRef = ref(null); const init = async () => { if(chartRef.value== null) return // #ifdef APP const chart = await chartRef.value!.init(null) // #endif // #ifdef H5 const chart = await chartRef.value!.init(echarts, null) // #endif chart.setOption(option) } ``` ## 数据更新 - 1、使用 `ref` 可获取`setOption`设置更新 - 2、也可以拿到图表实例`chart`设置`myChart.setOption(data)` ```js // ref this.$refs.chart.setOption(data) // 图表实例 myChart.setOption(data) ``` ## 图表大小 - 在有些场景下,我们希望当容器大小改变时,图表的大小也相应地改变。 ```js // 默认获取容器尺寸 this.$refs.chart.resize() // 指定尺寸 this.$refs.chart.resize({width: 375, height: 375}) ``` ## 自定义Tooltips - uvue\nvue 不支持 由于除H5之外都不存在dom,但又有tooltips个性化的需求,代码就不贴了,看示例吧 ``` 代码位于/uni_modules/lime-echart/component/lime-echart ``` ## 插件标签 - 默认 l-echart 为 component - 默认 lime-echart 为 demo ```html // 在任意地方使用可查看domo, 代码位于/uni_modules/lime-echart/component/lime-echart ``` ## 常见问题 - 钉钉小程序 由于没有`measureText`,模拟的`measureText`又无法得到当前字体的`fontWeight`,故可能存在估计不精细的问题 - 微信小程序 `2d` 只支持 真机调试2.0 - 微信开发工具会出现 `canvas` 不跟随页面的情况,真机不影响 - 微信开发工具会出现 `canvas` 层级过高的问题,真机一般不受影响,可以先测只有两个元素的页面看是否会有层级问题。 - toolbox 不支持 `saveImage` - echarts 5.3.0 的 lines 不支持 trailLength,故需设置为 `0` - dataZoom H5不要设置 `showDetail` - 如果微信小程序的`tooltip`文字有阴影,可能是微信的锅,临时解决方法是`tooltip.shadowBlur = 0` - 如果钉钉小程序上传时报安全问题`Uint8Clamped`,可以向钉钉反馈是安全代码扫描把Uint8Clamped数组错误识别了,也可以在 echarts 文件修改`Uint8Clamped` ```js // 找到这段代码把代码中`Uint8Clamped`改成`Uint8_Clamped`,再把下划线去掉,不过直接去掉`Uint8Clamped`也是可行的 // ["Int8","Uint8","Uint8Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e+"Array]"] // 改成如下 ["Int8","Uint8","Uint8_Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e.replace('_','')+"Array]"] ``` ### vue3 如果您是使用 **vite + vue3** 非微信小程序可能会遇到`echarts`文件缺少`wx`判断导致无法使用或缺少`tooltip`
方式一:可以在`echarts.min.js`文件开头增加以下内容,参考插件内的echart.min.js的做法 ```js let global = null let wx = uni ``` 方式二:在`vite.config.js`的`define`设置环境 ```js // 或者在`vite.config.js`的`define`设置环境 import { defineConfig } from 'vite'; import uni from '@dcloudio/vite-plugin-uni'; const define = {} if(!["mp-weixin", "h5", "web"].includes(process.env.UNI_PLATFORM)) { define['global'] = null define['wx'] = 'uni' } export default defineConfig({ plugins: [uni()], define }); ``` ## Props | 参数 | 说明 | 类型 | 默认值 | 版本 | | --------------- | -------- | ------- | ------------ | ----- | | custom-style | 自定义样式 | `string` | - | - | | type | 指定 canvas 类型 | `string` | `2d` | | | is-disable-scroll | 触摸图表时是否禁止页面滚动 | `boolean` | `false` | | | beforeDelay | 延迟初始化 (毫秒) | `number` | `30` | | | enableHover | PC端使用鼠标悬浮 | `boolean` | `false` | | | landscape | 是否旋转90deg,模拟横屏效果 | `boolean` | `false` | | ## 事件 | 参数 | 说明 | | --------------- | --------------- | | init(echarts, chart => {}) | 初始化调用函数,第一个参数是传入`echarts`,第二个参数是回调函数,回调函数的参数是 `chart` 实例 | | setChart(chart => {}) | 已经初始化后,请使用这个方法,是个回调函数,参数是 `chart` 实例 | | setOption(data) | [图表配置项](https://echarts.apache.org/zh/option.html#title),用于更新 ,传递是数据 `option` | | clear() | 清空当前实例,会移除实例中所有的组件和图表。 | | dispose() | 销毁实例 | | showLoading() | 显示加载 | | hideLoading() | 隐藏加载 | | [canvasToTempFilePath](https://uniapp.dcloud.io/api/canvas/canvasToTempFilePath.html#canvastotempfilepath)(opt) | 用于生成图片,与官方使用方法一致,但不需要传`canvasId` | ## 打赏 如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。 ![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png) ![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png) ================================================ FILE: src/uni_modules/lime-echart/static/uni.webview.1.5.5.js ================================================ !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).uni=n()}(this,(function(){"use strict";try{var e={};Object.defineProperty(e,"passive",{get:function(){!0}}),window.addEventListener("test-passive",null,e)}catch(e){}var n=Object.prototype.hasOwnProperty;function i(e,i){return n.call(e,i)}var t=[];function o(){return window.__dcloud_weex_postMessage||window.__dcloud_weex_}function a(){return window.__uniapp_x_postMessage||window.__uniapp_x_}var r=function(e,n){var i={options:{timestamp:+new Date},name:e,arg:n};if(a()){if("postMessage"===e){var r={data:n};return window.__uniapp_x_postMessage?window.__uniapp_x_postMessage(r):window.__uniapp_x_.postMessage(JSON.stringify(r))}var d={type:"WEB_INVOKE_APPSERVICE",args:{data:i,webviewIds:t}};window.__uniapp_x_postMessage?window.__uniapp_x_postMessageToService(d):window.__uniapp_x_.postMessageToService(JSON.stringify(d))}else if(o()){if("postMessage"===e){var s={data:[n]};return window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(s):window.__dcloud_weex_.postMessage(JSON.stringify(s))}var w={type:"WEB_INVOKE_APPSERVICE",args:{data:i,webviewIds:t}};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessageToService(w):window.__dcloud_weex_.postMessageToService(JSON.stringify(w))}else{if(!window.plus)return window.parent.postMessage({type:"WEB_INVOKE_APPSERVICE",data:i,pageId:""},"*");if(0===t.length){var u=plus.webview.currentWebview();if(!u)throw new Error("plus.webview.currentWebview() is undefined");var g=u.parent(),v="";v=g?g.id:u.id,t.push(v)}if(plus.webview.getWebviewById("__uniapp__service"))plus.webview.postMessageToUniNView({type:"WEB_INVOKE_APPSERVICE",args:{data:i,webviewIds:t}},"__uniapp__service");else{var c=JSON.stringify(i);plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat("WEB_INVOKE_APPSERVICE",'",').concat(c,",").concat(JSON.stringify(t),");"))}}},d={navigateTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("navigateTo",{url:encodeURI(n)})},navigateBack:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.delta;r("navigateBack",{delta:parseInt(n)||1})},switchTab:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("switchTab",{url:encodeURI(n)})},reLaunch:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("reLaunch",{url:encodeURI(n)})},redirectTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("redirectTo",{url:encodeURI(n)})},getEnv:function(e){a()?e({uvue:!0}):o()?e({nvue:!0}):window.plus?e({plus:!0}):e({h5:!0})},postMessage:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};r("postMessage",e.data||{})}},s=/uni-app/i.test(navigator.userAgent),w=/Html5Plus/i.test(navigator.userAgent),u=/complete|loaded|interactive/;var g=window.my&&navigator.userAgent.indexOf(["t","n","e","i","l","C","y","a","p","i","l","A"].reverse().join(""))>-1;var v=window.swan&&window.swan.webView&&/swan/i.test(navigator.userAgent);var c=window.qq&&window.qq.miniProgram&&/QQ/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var p=window.tt&&window.tt.miniProgram&&/toutiaomicroapp/i.test(navigator.userAgent);var _=window.wx&&window.wx.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var m=window.qa&&/quickapp/i.test(navigator.userAgent);var f=window.ks&&window.ks.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var l=window.tt&&window.tt.miniProgram&&/Lark|Feishu/i.test(navigator.userAgent);var E=window.jd&&window.jd.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var x=window.xhs&&window.xhs.miniProgram&&/xhsminiapp/i.test(navigator.userAgent);for(var S,h=function(){window.UniAppJSBridge=!0,document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady",{bubbles:!0,cancelable:!0}))},y=[function(e){if(s||w)return window.__uniapp_x_postMessage||window.__uniapp_x_||window.__dcloud_weex_postMessage||window.__dcloud_weex_?document.addEventListener("DOMContentLoaded",e):window.plus&&u.test(document.readyState)?setTimeout(e,0):document.addEventListener("plusready",e),d},function(e){if(_)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.wx.miniProgram},function(e){if(c)return window.QQJSBridge&&window.QQJSBridge.invoke?setTimeout(e,0):document.addEventListener("QQJSBridgeReady",e),window.qq.miniProgram},function(e){if(g){document.addEventListener("DOMContentLoaded",e);var n=window.my;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(v)return document.addEventListener("DOMContentLoaded",e),window.swan.webView},function(e){if(p)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(m){window.QaJSBridge&&window.QaJSBridge.invoke?setTimeout(e,0):document.addEventListener("QaJSBridgeReady",e);var n=window.qa;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(f)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.ks.miniProgram},function(e){if(l)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(E)return window.JDJSBridgeReady&&window.JDJSBridgeReady.invoke?setTimeout(e,0):document.addEventListener("JDJSBridgeReady",e),window.jd.miniProgram},function(e){if(x)return window.xhs.miniProgram},function(e){return document.addEventListener("DOMContentLoaded",e),d}],M=0;M
================================================ FILE: src/utils/assets.ts ================================================ const imgBaseUrl = (import.meta as any).env.VITE_IMG_BASEURL // 图片请求路径 function useImgAssets(filePath: string, { noCache = false } = {}) { // 去掉 filePath 前面的 @ 符号 if (filePath.startsWith('@')) { filePath = 'src' + filePath.slice(1) } const fileUrl = `${imgBaseUrl}${filePath}` if (noCache) { return fileUrl + '?t=' + Date.now() } else { return fileUrl } } export const useAssets = useImgAssets export default useImgAssets ================================================ FILE: src/utils/index.ts ================================================ // 模块统一导出 export * from './qs' export * from './router' export * from './log' export * from './assets' ================================================ FILE: src/utils/log.ts ================================================ /* 打印 控制台宣传信息 */ export const logPromotional = () => { console.log( `%c uni-plus %c 一个超超超好用的 uniapp 开发框架 作者:大麦大麦 %c`, `background:#2d8cf0;border:1px solid #2d8cf0; padding: 1px; border-radius: 4px 0 0 4px; color: #fff;`, `border:1px solid #2d8cf0; padding: 1px; border-radius: 0 4px 4px 0; color: #2d8cf0;`, 'background:transparent' ) console.log('github:https://github.com/damai-dm/uni-plus') console.log('掘金:https://juejin.cn/user/2368772393149325') } ================================================ FILE: src/utils/qs.ts ================================================ // 没必要为了 qs 一个简单的功能引入 qs,手写一个 export const qs = { stringify: function (obj) { return Object.keys(obj) .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])) .join('&') } } ================================================ FILE: src/utils/router.ts ================================================ import { pages, subPackages } from '@/pages.json' import { useUserStore } from '@/store' /** * 根据路由路径,获取页面权限 permissionKeys: ['isNeedLogin', 'isVip', 'admin'] **/ export const getPermissionKeysByPath = (path: string): string[] => { let navigateToPage if (path.split('/')[0] === 'pages') { navigateToPage = pages.find(page => page.path === path) } else { navigateToPage = subPackages.find((page: Record) => page.path === path) } return navigateToPage ? navigateToPage.permissionKeys || [] : [] } /** * 获取上一页路由路径 **/ const getLastPage = () => { // getCurrentPages() 至少有1个元素,所以不再额外判断 // const lastPage = getCurrentPages().at(-1) // 上面那个在低版本安卓中打包回报错,所以改用下面这个【虽然我加了src/interceptions/prototype.ts,但依然报错】 const pages = getCurrentPages() return pages[pages.length - 1] } // 确保 decodeURIComponent 递归调用 const ensureDecodeURIComponent = (url: string) => { if (url.startsWith('%')) { return ensureDecodeURIComponent(decodeURIComponent(url)) } return url } /** * 解析 url 得到 path 和 query * 比如输入url: /pages/login/index?redirect=%2Fpages%2Fdemo%2Fbase%2Froute-interceptor * 输出: {path: /pages/login/index, query: {redirect: /pages/demo/base/route-interceptor}} */ const getUrlObj = (url: string) => { const [path, queryStr] = url.split('?') if (!queryStr) { return { path, query: {} } } const query: Record = {} queryStr.split('&').forEach(item => { const [key, value] = item.split('=') query[key] = ensureDecodeURIComponent(value) // 这里需要统一 decodeURIComponent 一下,可以兼容h5和微信y }) return { path, query } } /** * 根据当前路由路径,判定是否有 redirectPath 路由,如果有则跳转,没有则返回 false **/ export const redirectRouteTo = () => { // 获取上一页路由路径 const lastPage = getLastPage() const currRoute = (lastPage as any).$page const { fullPath } = currRoute as { fullPath: string } // 获取上一页路由路径的 query 参数 const { query } = getUrlObj(fullPath) // const { redirect } = query if (redirect) { uni.redirectTo({ url: redirect }) return true } else { return false } } /* 自定义指令 按钮权限控制 */ export const checkBtnPermission = (btnPermissions: string[]) => { const store = useUserStore() // 放函数内是为了保证,每次调用都获取最新的用户权限 const userBtnPermissions = store.getUserBtnPermission // 用户已有按钮权限 const intersectionPermissions = btnPermissions.filter(item => userBtnPermissions.includes(item)) // 用户已有按钮权限与当前按钮权限的交集 return !!(intersectionPermissions.length === btnPermissions.length) } ================================================ FILE: stylelint.config.mjs ================================================ // prettier.config.mjs /** @type {import('stylelint').Config} */ const config = { extends: [ 'stylelint-config-recommended', 'stylelint-config-recommended-scss', 'stylelint-config-recommended-vue', 'stylelint-config-recess-order' ], overrides: [ // 扫描 .vue/html 文件中的