Showing preview only (228K chars total). Download the full file or copy to clipboard to get everything.
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": "<icon><scope><type>(<scope>):<subject>",
// "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": [
"<!--",
"@description: $1",
"@creationTime: ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
"-->\n",
"<route type=\"page\" lang=\"json5\" >",
"{",
" layout: 'default',",
" style: {",
" navigationBarTitleText: '$2',",
" },",
"}",
"</route>\n",
"<template>",
" <view>$3</view>",
"</template>\n",
"<script lang=\"ts\" setup>",
"/* ------------------------ 导入 与 引用 ----------------------------------- */",
"/* ------------------------ 函数 与 方法 ----------------------------------- */",
"/* ------------------------- 生命周期 -------------------------------------- */",
"</script>\n",
"<style lang=\"scss\" scoped></style>\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))
## <small>0.0.12 (2025-01-02)</small>
- ✨ 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))
## <small>0.0.1 (2024-12-31)</small>
- ✨ 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
================================================
<p align="center">
<a href="https://github.com/DaMaiCoding/uni-plus">
<img width="160" src="./src/static/local/logo.png">
</a>
</p>
<h1 align="center">
<b>一个 “超超超” 好用的 uniapp 开发模板</b>
</h1>
`uni-plus` 提供了 `layout布局`、`请求封装`、`请求拦截`、`权限控制`、`原子CSS`、`路由拦截`、`路由自动导入` 等基础功能,并且配备了 `代码提示`、`代码高亮`、`代码格式化`、`commit 优化` 等开发环境配置,让您的开发更加高效、便捷。
<p align="center">
<a href="https://damaicoding.github.io/uni-plus-doc/" target="_blank">📓 文档地址</a>
<span style="margin:0 10px;">|</span>
<a href="https://damaicoding.github.io/uni-plus/" target="_blank">🌰 预览地址</a>
</p>
| 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)
## 🤔 如何贡献
非常欢迎您的加入 或者提交一个 `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
非常感谢留下星星的 `小哥哥 、小姐姐`,感谢您的支持 ❤
[](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
================================================
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<script>
var coverSupport =
'CSS' in window &&
typeof CSS.supports === 'function' &&
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') +
'" />'
)
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script>
</body>
</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
================================================
<script setup lang="ts">
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
import { logPromotional } from '@/utils/log'
onLaunch(() => {
// 控制台打印项目宣传信息
logPromotional()
// #ifdef MP-WEIXIN
// 更新版本提示
if (wx.canIUse('getUpdateManager')) {
const updateManager = wx.getUpdateManager()
updateManager.onCheckForUpdate(function (res) {
if (res.hasUpdate) {
updateManager.onUpdateReady(function () {
wx.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success: function (res) {
if (res.confirm) {
updateManager.applyUpdate()
}
}
})
})
updateManager.onUpdateFailed(function () {
wx.showModal({
title: '已经有新版本了哟~',
content: '新版本已经上线啦~,请您删除当前小程序,重新搜索打开哟~'
})
})
}
})
} else {
wx.showModal({
title: '提示',
content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
})
}
// #endif
})
onShow(() => {
// console.log('App Show')
})
onHide(() => {
// console.log('App Hide')
})
</script>
<style>
/* 默认主题 */
:root page {
color: #000000;
background: #f5f5f5;
--theme-bg-color: #ffffff;
}
</style>
================================================
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<T>({
queryKey,
options = { immediate: false },
queryFn
}: {
queryKey: Ref<T>[]
options: RequestOptions
queryFn: () => Promise<ResData<T>>
}) {
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<T>
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<ConfigProviderThemeVars>()
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<CustomRequestOptions, 'url' | 'method'>
let refreshing = false // 防止重复刷新 token 标示
let taskQueue = [] // 刷新 token 请求队列
export default class ApiClient {
private static http<T>(options: Omit<CustomRequestOptions, 'isBaseUrl'>) {
let requestTask
const promise = new Promise<ResData<T>>((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<T>)
}
/* -------- 无感刷新 token ----------- */
const store = useUserStore()
const { refreshToken } = store.userInfo || {}
// token 失效的,且有刷新 token 的,才放到请求队列里
if ((res.data.code == 401 || res.statusCode == 401) && refreshToken != '') {
taskQueue.push(() => {
resolve(this.http<T>(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<T>(url: string, options?: CustomRequestOptionsOmit) {
return this.http<T>({
url,
method: 'GET',
...options
})
}
// POST
public static post<T>(url: string, options?: CustomRequestOptionsOmit) {
return this.http<T>({
url,
method: 'POST',
...options
})
}
// PUT
public static put<T>(url: string, options?: CustomRequestOptionsOmit) {
return this.http<T>({
url,
method: 'PUT',
...options
})
}
// DELETE
public static delete<T>(url: string, options?: CustomRequestOptionsOmit) {
return this.http<T>({
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<string, any>
/** 出错时是否隐藏错误提示 */
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
================================================
<!-- default.vue -->
<template>
<slot></slot>
</template>
================================================
FILE: src/layouts/fullPage.vue
================================================
<!-- fullPage.vue -->
<template>
<WdConfigProvider :theme="theme" :theme-vars="themeVars">
<div class="flex-col-center min-h-100vh">
<div class="w-100vw h-30vh bg-#f1bbba flex justify-center items-center">我是页头</div>
<slot></slot>
<div class="w-100vw h-30vh bg-#f8ecc9 flex justify-center items-center">我是页尾</div>
</div>
</WdConfigProvider>
</template>
<script setup lang="ts">
import { useTheme } from '@/hooks/useTheme'
const { theme, themeVars } = useTheme()
</script>
<style>
/* 默认主题 */
:root page {
color: #000000;
background: #f5f5f5;
--theme-bg-color: #ffffff;
}
</style>
================================================
FILE: src/layouts/theme.vue
================================================
<!-- theme.vue -->
<template>
<WdConfigProvider :theme="theme" :theme-vars="themeVars">
<div class="min-h-100vh">
<slot></slot>
</div>
</WdConfigProvider>
</template>
<script setup lang="ts">
import { useTheme } from '@/hooks/useTheme'
const { theme, themeVars } = useTheme()
</script>
================================================
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" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* 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
================================================
<!--
@description: 自定义导航栏 demo
-->
<route lang="json5" type="page">
{
layout: 'theme', // 使用主题
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<div :style="{ height: `${statusBarHeight}px` }" class="bg-[var(--theme-bg-color)]"></div>
<div
class="w-[calc(100vw-24px)] flex justify-between items-center bg-[var(--theme-bg-color)] font-size-16px p-x-12px"
:style="{ height: `${barHeight}px` }"
>
<div class="flex items-center w-80rpx h-100%" @click="goBack">
<wd-icon name="thin-arrow-left" size="18px"></wd-icon>
</div>
<div class="w-[calc(100%-160rpx)] pr-80rpx text-center">自定义导航栏 demo</div>
</div>
</template>
<script setup lang="ts">
/* 返回上一页 */
const goBack = () => {
uni.redirectTo({ url: '/pages/index/index' })
}
const statusBarHeight = ref(20)
const barHeight = ref(38)
onLoad(() => {
// 状态栏高度
statusBarHeight.value = (uni.getSystemInfoSync().statusBarHeight as number) || 0
// #ifdef MP-WEIXIN
// 胶囊数据
const { top, height } = uni.getMenuButtonBoundingClientRect()
// 自定义导航栏高度 = 胶囊高度 + 胶囊的 padding * 2, 如果获取不到设置为 38
barHeight.value = height ? height + (top - statusBarHeight.value) * 2 : 38
// #endif
})
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
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
================================================
<!--
@description: Echarts 图表 dome
-->
<route type="page" lang="json5">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: '图表 dome'
}
}
</route>
<template>
<view>折线图1</view>
<view style="width: 300rpx; height: 300rpx">
<l-echart ref="lineChartRef" />
</view>
<view>折线图2</view>
<view style="width: 300rpx; height: 300rpx">
<l-echart ref="lineChartRef2" />
</view>
<view>柱状图</view>
<view style="width: 300rpx; height: 300rpx">
<l-echart ref="barChartRef" />
</view>
<button @click="changeLineChartData">切换折线图1数据</button>
<button @click="changeBarChartData">改变柱状图样式</button>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useBarEcharts, useLineEcharts } from './echartsData'
import { set } from 'lodash-es'
const [lineChartRef, lineOption, lineDraw] = useLineEcharts()
const [lineChartRef2, lineOption2, lineDraw2] = useLineEcharts()
const [barChartRef, barOption, barDraw] = useBarEcharts()
// 初始化 折线图表1
const initLineChart = () => {
set(lineOption.value, 'series.data', [120, 132, 101, 134, 90, 230, 210, 220, 182, 191, 234, 290])
lineDraw()
}
// 初始化 折线图表2
const initLineChart2 = () => {
set(lineOption2.value, 'series.data', [220, 182, 191, 234, 290, 330, 310, 320, 302, 301, 334, 390])
lineDraw2()
}
// 初始化 柱状图表
const initBarChart = () => {
set(barOption.value, 'series.data', [120, 200, 150, 80, 70, 110, 130])
barDraw()
}
// 切换折线图数据
const changeLineChartData = () => {
set(lineOption.value, 'series.data', [324, 332, 301, 334, 390, 330, 320, 302, 301, 334, 390, 330])
lineDraw()
}
// 改变柱状图样式
const changeBarChartData = () => {
set(barOption.value, 'series.color', '#5677fc')
barDraw()
}
onMounted(() => {
initLineChart()
initLineChart2()
initBarChart()
})
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: #222222;
--theme-bg-color: #222222;
}
</style>
================================================
FILE: src/pages/i18nDemo/index.vue
================================================
<route type="page" lang="json5">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: '%pages.index.index.title%'
}
}
</route>
<template>
<button @click="setLocale">{{ t('nowLocale') }}</button>
<!-- 底部作者信息 -->
<div class="w-60% mt-10rpx opacity-60 m-auto">
<wd-divider>{{ t('authorText') }}</wd-divider>
</div>
<div class="w-100vw mt-10rpx opacity-60 flex justify-center font-size-28rpx" v-for="item in list" :key="item.about">
{{ item.about }}
</div>
<div class="w-100vw mt-40rpx">
<wd-calendar v-model="calendarData" :label="t('calendarLabel')" />
</div>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n'
const { t, setLocale } = useI18n()
const calendarData = ref<number>(Date.now())
const list = ref([
{
about: t('about', 'text')
}
])
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: #222222;
--theme-bg-color: #222222;
}
</style>
================================================
FILE: src/pages/index/index.vue
================================================
<!--
@description: DEMO 案例主导航页
-->
<route lang="json5" type="home">
{
layout: 'theme', // 使用主题
style: {
navigationStyle: 'custom'
}
}
</route>
<template>
<div class="min-h-100vh">
<!-- 顶部自定义导航栏 -->
<div class="w-[calc(100vw-60rpx)] p-x-30rpx bg-[var(--wot-color-theme)]">
<!-- 状态栏高度 -->
<div :style="{ height: `${statusBarHeight}px` }"></div>
<div class="flex p-b-20rpx">
<div class="w-160rpx h-100% flex">
<img class="w-160rpx h-160rpx" :src="$assets('@/static/local/logo.png')" />
<!-- <img class="w-160rpx h-160rpx" :src="'@/static/local/logo.png'" /> -->
</div>
<div class="ml-30rpx w-[calc(100%-160rpx)] flex flex-col">
<div class="font-size-44rpx font-bold flex items-center" :style="{ height: `${barHeight}px` }">uni-plus</div>
<div class="font-size-26rpx opacity-60 flex-1 flex flex-col justify-center">{{ t('about') }}</div>
</div>
</div>
</div>
<div class="p-x-30rpx flex flex-col items-center mt-30rpx">
<!-- 切换主题色 -->
<div
class="bg-[var(--theme-bg-color)] rounded-20rpx overflow-hidden flex justify-evenly items-center w-100% h-120rpx font-size-20rpx line-height-28rpx shadow-sm"
>
<div
class="w-94rpx h-50rpx rounded-25rpx bg-#ffc400 flex justify-center items-center"
@click="changeTheme('#ffc400')"
>
{{ t('bgColor1') }}
</div>
<div
class="w-94rpx h-50rpx rounded-25rpx bg-#ff5f2e color-#ffffff flex justify-center items-center"
@click="changeTheme('#ff5f2e', '#ffffff')"
>
{{ t('bgColor2') }}
</div>
<div
class="w-94rpx h-50rpx rounded-25rpx bg-#795548 color-#ffffff flex justify-center items-center"
@click="changeTheme()"
>
{{ t('bgColor3') }}
</div>
<div class="flex justify-center items-center w-260rpx">
<div class="font-size-28rpx mr-10rpx color-#9e9e9e flex-1 text-right">{{ t('darkMode') }}</div>
<wd-switch
v-model="theme"
size="23px"
active-value="dark"
inactive-value="light"
active-color="#272a2f"
:disabled="userInfo.followSystem"
/>
</div>
</div>
<!-- 功能列表 -->
<div class="bg-[var(--theme-bg-color)] w-100% shadow-md rounded-20rpx overflow-hidden mt-40rpx">
<wd-grid border :column="3" clickable>
<wd-grid-item use-slot v-for="item in gridList" :key="item.iconName" link-type="navigateTo" :url="item.url">
<wd-icon :name="item.iconName" size="36px" :color="item.color"></wd-icon>
<div class="font-size-28rpx font-bold">{{ item.title }}</div>
<div class="color-#9e9e9e font-size-22rpx">{{ item.text }}</div>
</wd-grid-item>
</wd-grid>
</div>
<!-- 多语言 -->
<div
class="bg-[var(--theme-bg-color)] rounded-20rpx overflow-hidden flex justify-between items-center w-[calc(100%-60rpx)] h-120rpx mt-40rpx p-x-30rpx shadow-sm"
@click="setLocale"
>
<div class="flex items-center">
<wd-icon name="setting" size="22px" color="#0163ff"></wd-icon>
<div class="font-size-30rpx ml-30rpx">{{ t('nowLocaleText') }}</div>
</div>
<div class="flex items-center">
<div class="font-size-28rpx color-#9e9e9e mr-5rpx h-22px line-height-22px">{{ t('nowLocale') }}</div>
<wd-icon name="chevron-right" size="22px" color="#9e9e9e"></wd-icon>
</div>
</div>
<!-- 暗黑模式 -->
<div
class="bg-[var(--theme-bg-color)] rounded-20rpx overflow-hidden flex justify-between items-center w-[calc(100%-60rpx)] h-120rpx mt-40rpx p-x-30rpx shadow-sm"
>
<div class="flex items-center">
<wd-icon name="windows-filled" size="26px" color="#cf3d35"></wd-icon>
<div class="font-size-30rpx ml-30rpx">{{ t('darkModeFollowText') }}</div>
</div>
<wd-checkbox
custom-shape-class="w-[var(--wot-checkbox-size,22px)!important] h-[var(--wot-checkbox-size,22px)!important]"
v-model="userInfo.followSystem"
checked-color="#cf3d35"
custom-style="margin:0"
@change="setTheme"
/>
</div>
<!-- 底部作者信息 -->
<div class="w-60% mt-10rpx opacity-60">
<wd-divider>{{ t('authorText') }}</wd-divider>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useTheme } from '@/hooks/useTheme'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/store'
import { useI18n } from '@/hooks/useI18n'
const store = useUserStore()
const { theme, themeVars, setTheme } = useTheme()
const { t, setLocale } = useI18n()
const { userInfo } = storeToRefs(store)
const statusBarHeight = ref(20)
const barHeight = ref(38)
/* 宫格列表 */
const gridList = ref([
{
iconName: 'layers',
title: t('layersTitle', 'text'),
text: 'SFC',
color: '#0163ff',
url: '/pages/layoutDemo/index'
},
{
iconName: 'attach',
title: t('attachTitle', 'text'),
text: t('attachText', 'text'),
color: '#ffc400',
url: '/pages/piniaDemo/index'
},
{
iconName: 'link-unlink',
title: t('linkUnlinkTitle', 'text'),
text: t('linkUnlinkText', 'text'),
color: '#ca145d',
url: '/pages/queryDemo/queryTestDemo'
},
{
iconName: 'link',
title: t('linkTitle', 'text'),
text: t('linkText', 'text'),
color: '#11cde8',
url: '/pages/queryDemo/useRequestDemo'
},
{
iconName: 'user',
title: t('userTitle', 'text'),
text: t('userText', 'text'),
color: '#fe1c00',
url: '/pages/queryDemo/loginDemo'
},
{
iconName: 'lock-off',
title: t('lockOffTitle', 'text'),
text: t('lockOffText', 'text'),
color: '#607d8b',
url: '/pages/routerDemo/index'
},
{
iconName: 'chart',
title: t('chartTitle', 'text'),
text: 'Echarts 5.14',
color: '#795548',
url: '/pages/echartsDemo/index'
},
{
iconName: 'transfer',
title: t('transferTitle', 'text'),
text: t('transferText', 'text'),
color: '#a61bc3',
url: '/pages/wotUiDemo/index'
},
{
iconName: 'view-module',
title: t('viewModuleTitle', 'text'),
text: 'z-padding',
color: '#d4ed00',
url: '/pages/queryDemo/zPagingDemo'
},
{
iconName: 'bianjiliebiao',
title: t('viewListTitle', 'text'),
text: 'i18n',
color: '#fea600',
url: '/pages/i18nDemo/index'
},
{
iconName: 'menu-fold',
title: t('menuFoldTitle', 'text'),
text: t('menuFoldText', 'text'),
color: '#652df4',
url: '/pages/customDemo/index'
},
{
iconName: 'chart-bubble',
title: 'Unocss',
text: t('chartBubbleTitle', 'text'),
color: '#ca145d',
url: '/pages/unocssDemo/index'
}
])
/* 切换主题色 */
const changeTheme = (bgColor?: string, fontColor?: string) => {
themeVars.value = {
colorTheme: bgColor ?? 'transparency',
darkColor3: (fontColor ?? theme.value === 'dark') ? '#f5f5f5' : '#272a2f'
}
}
onLoad(() => {
// 状态栏高度
statusBarHeight.value = (uni.getSystemInfoSync().statusBarHeight as number) || 20
// #ifdef MP-WEIXIN
// 胶囊数据
const { top, height } = uni.getMenuButtonBoundingClientRect()
// 自定义导航栏高度 = 胶囊高度 + 胶囊的 padding * 2, 如果获取不到设置为 38
barHeight.value = height ? height + (top - statusBarHeight.value) * 2 : 38
// #endif
})
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/layoutDemo/index.vue
================================================
<route type="page" lang="json5">
{
layout: 'fullPage', // 使用默认布局,替换成 foot 试试
style: {
navigationBarTitleText: '布局 Demo'
}
}
</route>
<template>
<div class="flex w-100vw h-40vh justify-center items-center bg-#f9cdad">我是布局 Demo 页面的内容</div>
</template>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: #222222;
--theme-bg-color: #222222;
}
</style>
================================================
FILE: src/pages/piniaDemo/index.vue
================================================
<!--
@description: pinia 使用 dome
-->
<route type="page" lang="json5">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: 'pinia 使用 dome'
}
}
</route>
<template>
<div>用户名:{{ userInfo.userName }}</div>
<button @click="changeUserName">改变用户名</button>
</template>
<script setup lang="ts">
/* ------------------------ 导入 与 引用 ----------------------------------- */
import { useUserStore } from '@/store'
import { storeToRefs } from 'pinia'
const store = useUserStore()
const { userInfo } = storeToRefs(store)
// 改变用户名
const changeUserName = () => {
store.setUserInfo({ userName: 'uni-plus-pro' })
}
onMounted(() => {
console.log('userInfo', userInfo.value)
})
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: #222222;
--theme-bg-color: #222222;
}
</style>
================================================
FILE: src/pages/queryDemo/loginDemo.vue
================================================
<route type="page" lang="json5">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: '登录请求示例'
}
}
</route>
<template>
<button @click="login">登录请求</button>
<div class="flex w-100vw justify-center items-center h-80rpx font-size-26rpx">
【 accessToken 失效,有 refreshToken 获取列表会无感刷新】
</div>
<button @click="getList">获取列表</button>
<button @click="accessTokenInvalid">accessToken 失效</button>
<button @click="refreshTokenInvalid">refreshToken 失效</button>
<div class="flex flex-col justify-center items-center mt-40rpx">
<div>----- accessToken -----</div>
<div class="w-80vw break-words mt-20rpx">{{ userInfo.accessToken }}</div>
<div class="mt-40rpx">----- refreshToken -----</div>
<div class="w-80vw break-words mt-20rpx">{{ userInfo.refreshToken }}</div>
<div class="m-y-20rpx">----- list 数据 -----</div>
<div>{{ list }}</div>
</div>
</template>
<script setup lang="ts">
import { loginApi, getListApi } from '@/api/loginApi'
import { useUserStore } from '@/store'
import { storeToRefs } from 'pinia'
const store = useUserStore()
const { userInfo } = storeToRefs(store)
// 登录请求
const login = () => {
loginApi({
username: 'uniLin',
password: '111111'
})
}
// 获取 列表数据
const list = ref()
const getList = async () => {
list.value = []
list.value = await getListApi()
}
// token 失效
const accessTokenInvalid = () => {
const str = userInfo.value.accessToken.split('')
store.setUserInfo({
accessToken: [...str].sort(() => Math.random() - 0.5).join('')
})
}
// refreshToken 失效
const refreshTokenInvalid = () => {
const str = userInfo.value.refreshToken.split('')
store.setUserInfo({
refreshToken: [...str].sort(() => Math.random() - 0.5).join('')
})
}
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/queryDemo/queryTestDemo.vue
================================================
<route type="page" lang="json5">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: '请求测试页面'
}
}
</route>
<template>
<div>
<button @click="getTest">GET 请求</button>
<button @click="postTest">POST 请求</button>
<button @click="delayRequest">3S后返回数据, 可点击下面取消请求取消</button>
<button @click="cancelRequest">取消 请求</button>
<button @click="awaitToJs">awaitToJs</button>
<div class="mt-40rpx">
{{ pageData }}
</div>
</div>
</template>
<script setup lang="ts">
import { awaitToJsTestApi, getTestApi, postTestApi, getLongRequestApi } from '@/api/testApi'
const pageData = ref()
// GET 请求
const getTest = async () => {
pageData.value = await getTestApi({ name: 'uni-plus' })
}
// POST 请求
const postTest = async () => {
pageData.value = await postTestApi({ name: 'uni-plus' }, { id: '123456' })
}
// 长时间请求(测试取消请求)
let requestTask = null
const delayRequest = () => {
requestTask = getLongRequestApi({ name: 'uni-plus' }, { id: '123456' })
requestTask
.then(res => {
pageData.value = res
})
.catch(err => {
pageData.value = err
})
}
// 取消 请求
const cancelRequest = () => {
// 取消请求
requestTask.abort()
pageData.value = { msg: '取消请求' }
}
/* await-to-js 代替 try catch 用法,根据实际需求进行使用 */
const awaitToJs = async () => {
const [data, err] = await awaitToJsTestApi({ name: 'uni-plus' })
if (data) {
pageData.value = data
}
if (err) {
pageData.value = err
}
}
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/queryDemo/useRequestDemo.vue
================================================
<route type="page" lang="json5">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: 'useRequest使用示例'
}
}
</route>
<template>
<button @click="prevPage">上一页</button>
<button @click="nextPage">下一页</button>
<button @click="getList">重新获取列表</button>
<div v-if="loading">加载中...</div>
<div v-else>
<div class="w-100vw font-size-36rpx m-y-20rpx">当前页码:{{ page }}</div>
<ul>
<li v-for="item in filterData" :key="item.id" class="mb-10rpx">
{{ item.title }}
</li>
</ul>
</div>
<div v-if="error">请求失败: {{ error }}</div>
</template>
<script setup lang="ts">
import { getUserListApi } from '@/api/testApi'
import useRequest from '@/hooks/useRequest'
// data 数据处理
const filterData = computed(() => {
return data.value?.map((item: any) => {
return {
...item,
title: item.title + '(这是额外添加的内容~)'
}
})
})
// useRequest 的使用
const page = ref(1)
const { loading, error, data, run } = useRequest({
queryKey: [page],
queryFn: () => getUserListApi({ page: page.value, limit: 10 }),
options: { immediate: true }
})
// 上一页
const prevPage = () => {
page.value = Math.max(page.value - 1, 1)
}
// 下一页
const nextPage = () => {
page.value = page.value + 1
}
// 重新获取列表数据
const getList = () => {
run()
}
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/queryDemo/zPagingDemo.vue
================================================
<route type="page" lang="json5">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: 'z-paging 使用示例'
}
}
</route>
<template>
<z-paging ref="paging" v-model="dataList" @query="queryList">
<div>
<ul>
<li v-for="item in dataList" :key="item.id">
{{ item.title }}
</li>
</ul>
</div>
<!-- 页面内容 -->
</z-paging>
</template>
<script setup lang="ts">
import { getUserListApi } from '@/api/testApi'
const paging = ref(null)
const dataList = ref([])
const queryList = (pageNo, pageSize) => {
// 这里的pageNo和pageSize会自动计算好,直接传给服务器即可
// 这里的请求只是演示,请替换成自己的项目的网络请求,并在网络请求回调中通过paging.value.complete(请求回来的数组)将请求结果传给z-paging
getUserListApi({ page: pageNo, limit: pageSize })
.then(res => {
// 请勿在网络请求回调中给dataList赋值!!只需要调用complete就可以了
// 如果你需要处理 res.data 的数据,请在 complete 之前处理
paging.value.complete(res.data)
})
.catch(res => {
console.log(res)
// 如果请求失败写paging.value.complete(false),会自动展示错误页面
// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
paging.value.complete(false)
})
}
onMounted(() => {
queryList(1, 10)
})
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/routerDemo/index.vue
================================================
<route lang="json5" type="page">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: '页面路由权限控制'
}
}
</route>
<template>
<div class="w-100vw flex justify-center items-center p-y-20rpx">--------用户权限----------</div>
<div class="w-100vw flex justify-center items-center p-y-20rpx">{{ getUserPermissionKeys() }}</div>
<div class="w-100vw flex justify-center items-center p-y-20rpx">--------页面权限----------</div>
<button @click="goToPage1">['vip']</button>
<button @click="goToPage2">['vip', 'admin']</button>
<button @click="goToPage3">['logined']</button>
<div class="w-100vw flex justify-center items-center p-y-20rpx">--------按钮权限----------</div>
<button v-perms="['operation:user:create']">有按钮权限: operation:user:create</button>
<button v-perms="['operation:user:delete']">无按钮权限: operation:user:delete</button>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store'
const getUserPermissionKeys = () => {
const userStore = useUserStore()
return userStore.getUserPermissionKeys
}
// 跳转到页面1
const goToPage1 = () => {
uni.navigateTo({
url: '/pages/routerDemo/page1'
})
}
// 跳转到页面2
const goToPage2 = () => {
uni.navigateTo({
url: '/pages/routerDemo/page2'
})
}
// 跳转到页面3
const goToPage3 = () => {
uni.navigateTo({
url: '/pages/routerDemo/page3'
})
}
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/routerDemo/login.vue
================================================
<route lang="json5" type="page">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: '登录页'
}
}
</route>
<template>
<button @click="login">使用假数据模拟登录</button>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store'
import { redirectRouteTo } from '@/utils'
const userStore = useUserStore()
/* 假登录 */
const login = () => {
userStore.setUserInfo({
accessToken: 'fakeAccessToken',
refreshToken: 'fakeRefreshToken'
})
// 如果有重定向路径,跳转才有效
redirectRouteTo()
}
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/routerDemo/page1.vue
================================================
<route lang="json5" type="page">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: '页面1'
},
permissionKeys: ['vip']
}
</route>
<template>
<h1>页面1</h1>
</template>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/routerDemo/page2.vue
================================================
<route lang="json5" type="page">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: '页面2'
},
permissionKeys: ['vip', 'admin']
}
</route>
<template>
<h1>页面2</h1>
</template>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/routerDemo/page3.vue
================================================
<route lang="json5" type="page">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: '页面3'
},
permissionKeys: ['logined']
}
</route>
<template>
<h1>页面3</h1>
</template>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/unocssDemo/index.vue
================================================
<!--
@description: Unocss 使用 dome
-->
<route type="page" lang="json5">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: 'Unocss 使用 dome'
}
}
</route>
<template>
<div class="w-100vw flex justify-center items-center p-y-20rpx">--------unocss 图标----------</div>
<div class="flex justify-center gap-3 text-5xl text-blue-300">
<!-- 类目最前面加 i- -->
<!-- 写法一:图标库名称 + : + 图标名称 -->
<i class="i-uiw-alipay" />
<!-- 写法二:图标库名称 + - + 图标名称 -->
<i class="i-uiw:apple" />
</div>
<div class="w-100vw flex justify-center items-center p-y-20rpx">--------传统图标----------</div>
<div class="flex justify-center items-center gap-3">
<img class="w-100rpx h-100rpx" src="https://api.iconify.design/uiw:github.svg?color=%23faa5a2" />
<img class="w-100rpx h-100rpx" src="https://api.iconify.design/uiw:html5.svg?color=%23faa5a2" />
</div>
</template>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages/wotUiDemo/index.vue
================================================
<route type="page" lang="json5">
{
layout: 'theme', // 使用主题
style: {
navigationBarTitleText: 'Wot UI Demo'
}
}
</route>
<template>
<div class="flex-col-center font-size-14">
<div class="flex flex-col">
<text class="title">{{ title }}</text>
<wd-button type="success">成功按钮</wd-button>
<wd-button type="info">默认按钮</wd-button>
<wd-slider v-model="value" />
</div>
</div>
</template>
<script setup lang="ts">
const title = ref<string>('Hello')
const value = ref<number>(30)
</script>
<style>
/* 暗黑模式 start */
.wot-theme-dark {
color: #f5f5f5;
background: black;
--theme-bg-color: #1b1b1b;
}
</style>
================================================
FILE: src/pages-sub/subDemo/index.vue
================================================
<route lang="json5" type="page">
{
style: { navigationBarTitleText: 'subDemo 分包页面' }
}
</route>
<template>
<view class="text-center">
<view class="text-green-500">subDemo 分包页面</view>
</view>
</template>
================================================
FILE: src/pages-sub/testDemo/index.vue
================================================
<!--
@description: 测试页面
@creationTime: 2025-01-23
-->
<route type="page" lang="json5">
{
layout: 'default',
style: {
navigationBarTitleText: '测试页面'
}
}
</route>
<template>
<view>测试页面</view>
</template>
<script lang="ts" setup>
/* ------------------------ 导入 与 引用 ----------------------------------- */
/* ------------------------ 函数 与 方法 ----------------------------------- */
/* ------------------------- 生命周期 -------------------------------------- */
</script>
<style lang="scss" scoped></style>
================================================
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<UserInfo>({ ...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<T> = {
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
<l-echart ref="chart" is-enable />
```
```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
================================================
<template>
<!-- #ifdef APP -->
<web-view class="lime-echart" ref="chartRef" @load="loaded" :style="[customStyle]"
:webview-styles="[webviewStyles]" src="/uni_modules/lime-echart/static/uvue.html?v=10112">
</web-view>
<!-- #endif -->
<!-- #ifdef H5 -->
<div class="lime-echart" ref="chartRef"></div>
<!-- #endif -->
<!-- #ifndef H5 || APP-->
<canvas class="lime-echart" :id="canvasid" :canvas-id="canvasid"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend">
</canvas>
<!-- #endif -->
</template>
<script lang="uts" setup>
// @ts-nocheck
import { getCurrentInstance, nextTick } from "vue";
import { Echarts } from './uvue';
// #ifdef WEB
import { dispatch } from './canvas';
// #endif
// #ifndef APP || WEB
import {Canvas, setCanvasCreator, dispatch} from './canvas';
import {wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect} from './utils';
// #endif
type EchartsResolve = (value : Echarts) => void
defineOptions({
name: 'l-echart'
})
const emits = defineEmits(['finished'])
const props = defineProps({
// #ifdef APP
webviewStyles: {
type: Object
},
customStyle: {
type: Object
},
// #endif
// #ifndef APP
webviewStyles: {
type: Object
},
customStyle: {
type: [String, Object]
},
// #endif
isDisableScroll: {
type: Boolean,
default: false
},
isClickable: {
type: Boolean,
default: true
},
enableHover: {
type: Boolean,
default: false
},
beforeDelay: {
type: Number,
default: 30
}
})
const instance = getCurrentInstance()!;
const canvasid = `lime-echart-${instance.uid}`
const finished = ref(false)
const map = [] as EchartsResolve[]
const callbackMap = [] as EchartsResolve[]
// let context = null as UniWebViewElement | null
let chart = null as Echarts | null
let chartRef = ref<UniWebViewElement | null>(null)
const trigger = () => {
// #ifdef APP
if (finished.value) {
if (chart == null) {
chart = new Echarts(chartRef.value!)
}
while (map.length > 0) {
const resolve = map.pop() as EchartsResolve
resolve(chart!)
}
}
// #endif
// #ifndef APP
while (map.length > 0) {
if(chart != null){
const resolve = map.pop() as EchartsResolve
resolve(chart!)
}
}
// #endif
if(chart != null){
while(callbackMap.length > 0){
const callback = callbackMap.pop() as EchartsResolve
callback(chart!)
}
}
}
// #ifdef APP
const loaded = (event : UniWebViewLoadEvent) => {
event.stopPropagation()
event.preventDefault()
finished.value = true
trigger()
emits('finished')
}
// #endif
const _next = () : boolean => {
if (chart == null) {
console.warn(`组件还未初始化,请先使用 init`)
return true
}
return false
}
const setOption = (option : UTSJSONObject) => {
if (_next()) return
chart!.setOption(option);
}
const showLoading = () => {
if (_next()) return
chart!.showLoading();
}
const hideLoading = () => {
if (_next()) return
chart!.hideLoading();
}
const clear = () => {
if (_next()) return
chart!.clear();
}
const dispose = () => {
if (_next()) return
chart!.dispose();
}
const resize = (size : UTSJSONObject) => {
if (_next()) return
chart!.resize(size);
}
const canvasToTempFilePath = (opt : UTSJSONObject) => {
if (_next()) return
chart!.canvasToTempFilePath(opt);
}
// #ifdef APP
function init(callback : ((chart : Echarts) => void) | null) : Promise<Echarts> {
if(callback!=null){
callbackMap.push(callback)
}
return new Promise<Echarts>((resolve) => {
map.push(resolve)
trigger()
})
}
// #endif
// #ifndef APP
// #ifndef WEB
let use2dCanvas = canIUseCanvas2d()
const getContext = async () =>{
return getRect(`#${canvasid}`, {context: instance.proxy!, type: use2dCanvas ? 'fields': 'boundingClientRect'}).then(res => {
if(res) {
let dpr = uni.getWindowInfo().pixelRatio
let {width, height, node} = res
let canvas;
if(node) {
const ctx = node.getContext('2d');
canvas = new Canvas(ctx, instance.proxy, true, node);
} else {
const ctx = uni.createCanvasContext(canvasid, instance.proxy);
canvas = new Canvas(ctx, instance.proxy, false);
}
return { canvas, width, height, devicePixelRatio: dpr, node };
} else {
return {}
}
})
}
// #endif
const getTouch = (e) => {
const touches = e.touches[0]
// #ifdef WEB
const rect = chart!.getZr().dom.getBoundingClientRect();
const touch = {
x: touches.clientX - rect.left,
y: touches.clientY - rect.top
}
// #endif
// #ifndef WEB
const touch = {
x: touches.x,
y: touches.y
}
// #endif
return touch
}
const touchstart = (e) => {
if(chart == null) return
const handler = chart.getZr().handler;
const touch = getTouch(e)
dispatch.call(handler, 'mousedown', touch)
dispatch.call(handler, 'click', touch)
}
const touchmove = (e) => {
if(chart == null) return
const handler = chart.getZr().handler;
const touch = getTouch(e)
dispatch.call(handler, 'mousemove', touch)
// const rect = chart.getZr().dom.getBoundingClientRect()
// handler.dispatch('mousemove', {
// zrX: e.touches[0].clientX - rect.left,
// zrY: e.touches[0].clientY - rect.top
// })
}
const touchend = (e) => {
if(chart == null) return
const handler = chart.getZr().handler;
const touch = {
x: 999999999,
y: 999999999
}
dispatch.call(handler, 'mousemove', touch)
dispatch.call(handler, 'touchend', touch)
}
async function init(echarts: any, ...args: any[]): Promise<Echarts>{
if(echarts == null){
console.error('请确保已经引入了 ECharts 库');
return Promise.reject('请确保已经引入了 ECharts 库');
}
let theme:string|null=null
let opts={}
let callback:Function|null=null;
args.forEach(item =>{
if(typeof item === 'function') {
callback = item
} else if(['string'].includes(typeof item)){
theme = item
} else if(typeof item === 'object'){
opts = item
}
})
// #ifdef WEB
chart = echarts.init(chartRef.value, theme, opts)
window.addEventListener('touchstart', touchstart)
window.addEventListener('touchmove', touchmove)
window.addEventListener('touchend', touchend)
// #endif
// #ifndef WEB
let config = await getContext();
setCanvasCreator(echarts, config)
chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
// #endif
console.log('chart', chart)
if(callback!=null && typeof callback == 'function'){
callbackMap.push(callback)
}
return new Promise<Echarts>((resolve) => {
map.push(resolve)
trigger()
})
}
onMounted(()=>{
nextTick(()=>{
finished.value = true
trigger()
emits('finished')
})
})
onUnmounted(()=>{
// #ifdef WEB
window.removeEventListener('touchstart', touchstart)
window.removeEventListener('touchmove', touchmove)
window.removeEventListener('touchend', touchend)
// #endif
})
// #endif
defineExpose({
init,
setOption,
showLoading,
hideLoading,
clear,
dispose,
resize,
canvasToTempFilePath
})
</script>
<style lang="scss">
.lime-echart {
flex: 1;
width: 100%;
}
</style>
================================================
FILE: src/uni_modules/lime-echart/components/l-echart/l-echart.vue
================================================
<template>
<view class="lime-echart" :style="[customStyle]" v-if="canvasId" ref="limeEchart" :aria-label="ariaLabel">
<!-- #ifndef APP-NVUE -->
<canvas
class="lime-echart__canvas"
v-if="use2dCanvas"
type="2d"
:id="canvasId"
:style="canvasStyle"
:disable-scroll="isDisableScroll"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
/>
<canvas
class="lime-echart__canvas"
v-else
:width="nodeWidth"
:height="nodeHeight"
:style="canvasStyle"
:canvas-id="canvasId"
:id="canvasId"
:disable-scroll="isDisableScroll"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
/>
<view class="lime-echart__mask"
v-if="isPC"
@mousedown="touchStart"
@mousemove="touchMove"
@mouseup="touchEnd"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd">
</view>
<canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<web-view
class="lime-echart__canvas"
:id="canvasId"
:style="canvasStyle"
:webview-styles="webviewStyles"
ref="webview"
src="/uni_modules/lime-echart/static/uvue.html?v=1"
@pagefinish="finished = true"
@onPostMessage="onMessage"
></web-view>
<!-- #endif -->
</view>
</template>
<script>
// @ts-nocheck
// #ifndef APP-NVUE
import {Canvas, setCanvasCreator, dispatch} from './canvas';
import {wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect, getDeviceInfo} from './utils';
// #endif
// #ifdef APP-NVUE
import { base64ToPath, sleep } from './utils';
import {Echarts} from './nvue'
// #endif
const charts = {}
const echartsObj = {}
/**
* LimeChart 图表
* @description 全端兼容的eCharts
* @tutorial https://ext.dcloud.net.cn/plugin?id=4899
* @property {String} customStyle 自定义样式
* @property {String} type 指定 canvas 类型
* @value 2d 使用canvas 2d,部分小程序支持
* @value '' 使用原生canvas,会有层级问题
* @value bottom right 不缩放图片,只显示图片的右下边区域
* @property {Boolean} isDisableScroll
* @property {number} beforeDelay = [30] 延迟初始化 (毫秒)
* @property {Boolean} enableHover PC端使用鼠标悬浮
* @event {Function} finished 加载完成触发
*/
export default {
name: 'lime-echart',
props: {
// #ifdef MP-WEIXIN || MP-TOUTIAO
type: {
type: String,
default: '2d'
},
// #endif
// #ifdef APP-NVUE
webviewStyles: Object,
// hybrid: Boolean,
// #endif
customStyle: String,
isDisableScroll: Boolean,
isClickable: {
type: Boolean,
default: true
},
enableHover: Boolean,
beforeDelay: {
type: Number,
default: 30
},
landscape: Boolean
},
data() {
return {
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
use2dCanvas: true,
// #endif
// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
use2dCanvas: false,
// #endif
ariaLabel: '图表',
width: null,
height: null,
nodeWidth: null,
nodeHeight: null,
// canvasNode: null,
config: {},
inited: false,
finished: false,
file: '',
platform: '',
isPC: false,
isDown: false,
isOffscreenCanvas: false,
offscreenWidth: 0,
offscreenHeight: 0,
};
},
computed: {
rootStyle() {
if(this.landscape) {
return `transform: translate(-50%,-50%) rotate(90deg); top:50%; left:50%;`
}
},
canvasId() {
return `lime-echart${this._ && this._.uid || this._uid}`
},
offscreenCanvasId() {
return `${this.canvasId}_offscreen`
},
offscreenStyle() {
return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
},
canvasStyle() {
return this.rootStyle + (this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : '')
}
},
// #ifndef VUE3
beforeDestroy() {
this.clear()
this.dispose()
// #ifdef H5
if(this.isPC) {
document.removeEventListener('mousewheel', this.mousewheel)
}
// #endif
},
// #endif
// #ifdef VUE3
beforeUnmount() {
this.clear()
this.dispose()
// #ifdef H5
if(this.isPC) {
document.removeEventListener('mousewheel', this.mousewheel)
}
// #endif
},
// #endif
created() {
// #ifdef H5
if(!('ontouchstart' in window)) {
this.isPC = true
document.addEventListener('mousewheel', this.mousewheel)
}
// #endif
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
// const { platform } = uni.getSystemInfoSync();
const { platform } = getDeviceInfo();
this.isPC = /windows/i.test(platform)
// #endif
this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
},
mounted() {
this.$nextTick(() => {
this.$emit('finished')
})
},
methods: {
// #ifdef APP-NVUE
onMessage(e) {
const detail = e?.detail?.data[0] || null;
const data = detail?.data
const key = detail?.event
const options = data?.options
const event = data?.event
const file = detail?.file
if (key == 'log' && data) {
console.log(data)
}
if(event) {
this.chart.dispatchAction(event.replace(/"/g,''), options)
}
if(file) {
thie.file = file
}
},
// #endif
setChart(callback) {
if(!this.chart) {
console.warn(`组件还未初始化,请先使用 init`)
return
}
if(typeof callback === 'function' && this.chart) {
callback(this.chart);
}
// #ifdef APP-NVUE
if(typeof callback === 'function') {
this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`);
}
// #endif
},
setOption() {
if (!this.chart || !this.chart.setOption) {
console.warn(`组件还未初始化,请先使用 init`)
return
}
this.chart.setOption(...arguments);
},
showLoading() {
if(this.chart) {
this.chart.showLoading(...arguments)
}
},
hideLoading() {
if(this.chart) {
this.chart.hideLoading()
}
},
clear() {
if(this.chart) {
this.chart.clear()
}
},
dispose() {
if(this.chart) {
this.chart.dispose()
}
},
resize(size) {
if(size && size.width && size.height) {
this.height = size.height
this.width = size.width
if(this.chart) {this.chart.resize(size)}
} else {
this.$nextTick(() => {
uni.createSelectorQuery()
.in(this)
.select(`.lime-echart`)
.boundingClientRect()
.exec(res => {
if (res) {
let { width, height } = res[0];
this.width = width = width || 300;
this.height = height = height || 300;
this.chart.resize({width, height})
}
});
})
}
},
canvasToTempFilePath(args = {}) {
// #ifndef APP-NVUE
const { use2dCanvas, canvasId } = this;
return new Promise((resolve, reject) => {
const copyArgs = Object.assign({
canvasId,
success: resolve,
fail: reject
}, args);
if (use2dCanvas) {
delete copyArgs.canvasId;
copyArgs.canvas = this.canvasNode;
}
uni.canvasToTempFilePath(copyArgs, this);
});
// #endif
// #ifdef APP-NVUE
this.file = ''
this.$refs.webview.evalJs(`canvasToTempFilePath()`);
return new Promise((resolve, reject) => {
this.$watch('file', async (file) => {
if(file) {
const tempFilePath = await base64ToPath(file)
resolve(args.success({tempFilePath}))
} else {
reject(args.fail({error: ``}))
}
})
})
// #endif
},
async init(echarts, ...args) {
// #ifndef APP-NVUE
if(args && args.length == 0 && !echarts) {
console.error('缺少参数:init(echarts, theme?:string, opts?: object, callback?: function)')
return
}
// #endif
let theme=null,opts={},callback;
Array.from(arguments).forEach(item => {
if(typeof item === 'function') {
callback = item
}
if(['string'].includes(typeof item)) {
theme = item
}
if(typeof item === 'object') {
opts = item
}
})
if(this.beforeDelay) {
await sleep(this.beforeDelay)
}
let config = await this.getContext();
// #ifndef APP-NVUE
setCanvasCreator(echarts, config)
try {
this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
if(typeof callback === 'function') {
callback(this.chart)
} else {
return this.chart
}
} catch(e) {
console.error(e.messges)
return null
}
// #endif
// #ifdef APP-NVUE
this.chart = new Echarts(this.$refs.webview)
this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
if(callback) {
callback(this.chart)
} else {
return this.chart
}
// #endif
},
getContext() {
// #ifdef APP-NVUE
if(this.finished) {
return Promise.resolve(this.finished)
}
return new Promise(resolve => {
this.$watch('finished', (val) => {
if(val) {
resolve(this.finished)
}
})
})
// #endif
// #ifndef APP-NVUE
return getRect(`#${this.canvasId}`, {context: this, type: this.use2dCanvas ? 'fields': 'boundingClientRect'}).then(res => {
if(res) {
let dpr = devicePixelRatio
let {width, height, node} = res
let canvas;
this.width = width = width || 300;
this.height = height = height || 300;
if(node) {
const ctx = node.getContext('2d');
canvas = new Canvas(ctx, this, true, node);
this.canvasNode = node
} else {
// #ifdef MP-TOUTIAO
dpr = !this.isPC ? devicePixelRatio : 1// 1.25
// #endif
// #ifndef MP-ALIPAY || MP-TOUTIAO
dpr = this.isPC ? devicePixelRatio : 1
// #endif
// #ifdef MP-ALIPAY || MP-LARK
dpr = devicePixelRatio
// #endif
// #ifdef WEB
dpr = 1
// #endif
this.rect = res
this.nodeWidth = width * dpr;
this.nodeHeight = height * dpr;
const ctx = uni.createCanvasContext(this.canvasId, this);
canvas = new Canvas(ctx, this, false);
}
return { canvas, width, height, devicePixelRatio: dpr, node };
} else {
return {}
}
})
// #endif
},
// #ifndef APP-NVUE
getRelative(e, touches) {
let { clientX, clientY } = e
if(!(clientX && clientY) && touches && touches[0]) {
clientX = touches[0].clientX
clientY = touches[0].clientY
}
return {x: clientX - this.rect.left, y: clientY - this.rect.top, wheelDelta: e.wheelDelta || 0}
},
getTouch(e, touches) {
const {x} = touches && touches[0] || {}
const touch = x ? touches[0] : this.getRelative(e, touches);
if(this.landscape) {
[touch.x, touch.y] = [touch.y, this.height - touch.x]
}
return touch;
},
touchStart(e) {
this.isDown = true
const next = () => {
const touches = convertTouchesToArray(e.touches)
if(this.chart) {
const touch = this.getTouch(e, touches)
this.startX = touch.x
this.startY = touch.y
this.startT = new Date()
const handler = this.chart.getZr().handler;
dispatch.call(handler, 'mousedown', touch)
dispatch.call(handler, 'mousemove', touch)
handler.processGesture(wrapTouch(e), 'start');
clearTimeout(this.endTimer);
}
}
if(this.isPC) {
getRect(`#${this.canvasId}`, {context: this}).then(res => {
this.rect = res
next()
})
return
}
next()
},
touchMove(e) {
if(this.isPC && this.enableHover && !this.isDown) {this.isDown = true}
const touches = convertTouchesToArray(e.touches)
if (this.chart && this.isDown) {
const handler = this.chart.getZr().handler;
dispatch.call(handler, 'mousemove', this.getTouch(e, touches))
handler.processGesture(wrapTouch(e), 'change');
}
},
touchEnd(e) {
this.isDown = false
if (this.chart) {
const touches = convertTouchesToArray(e.changedTouches)
const {x} = touches && touches[0] || {}
const touch = (x ? touches[0] : this.getRelative(e, touches)) || {};
if(this.landscape) {
[touch.x, touch.y] = [touch.y, this.height - touch.x]
}
const handler = this.chart.getZr().handler;
const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
dispatch.call(handler, 'mouseup', touch)
handler.processGesture(wrapTouch(e), 'end');
if(isClick) {
dispatch.call(handler, 'click', touch)
} else {
this.endTimer = setTimeout(() => {
dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
},50)
}
}
},
// #endif
// #ifdef H5
mousewheel(e){
if(this.chart) {
// dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
}
}
// #endif
}
};
</script>
<style>
.lime-echart {
position: relative;
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
.lime-echart__canvas {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
/* #ifndef APP-NVUE */
.lime-echart__mask {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 1;
}
/* #endif */
</style>
================================================
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<string, EchartsEventHandler> = 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
================================================
<template>
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
</template>
<script>
export default {
data() {
return {
showTip: false,
option: {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
}
},
mounted() {
console.log('lime echarts nvue')
},
methods: {
init() {
const chartRef = this.$refs['chartRef']
chartRef.init(chart => {
chart.setOption(this.option);
setTimeout(()=>{
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
chart.setOption(option);
},1000)
})
},
save() {
// this.$refs.chart.canvasToTempFilePath({
// success(res) {
// console.log('res::::', res)
// }
// })
}
}
}
</script>
<style>
</style>
================================================
FILE: src/uni_modules/lime-echart/components/lime-echart/lime-echart.uvue
================================================
<template>
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
</template>
<script lang="uts" setup>
// @ts-nocheck
// #ifndef APP
import * as echarts from 'echarts/dist/echarts.esm.js'
// #endif
const chartRef = ref<LEchartComponentPublicInstance|null>(null)
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
// formatter: async (params: any) => {
// console.log('params', params)
// return 1
// },
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
const init = async () =>{
if(chartRef.value== null) return
// #ifdef APP
const chart = await chartRef.value!.init(null)
// #endif
// #ifndef APP
const chart = await chartRef.value!.init(echarts, null)
// #endif
chart.setOption(option)
chart.on('mouseover', function (params) {
console.log('params', params);
});
// setTimeout(()=> {
// const option1 = {
// tooltip: {
// trigger: 'axis',
// // shadowBlur: 0,
// textStyle: {
// textShadowBlur: 0
// },
// renderMode: 'richText',
// },
// legend: {
// data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
// },
// grid: {
// left: '3%',
// right: '4%',
// bottom: '3%',
// containLabel: true
// },
// xAxis: {
// type: 'category',
// boundaryGap: false,
// data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
// },
// yAxis: {
// type: 'value'
// },
// series: [
// {
// name: '邮件营销',
// type: 'line',
// stack: '总量',
// data: [820, 132, 101, 134, 90, 230, 210]
// },
// {
// name: '联盟广告',
// type: 'line',
// stack: '总量',
// data: [220, 182, 191, 234, 290, 330, 310]
// },
// {
// name: '视频广告',
// type: 'line',
// stack: '总量',
// data: [950, 232, 201, 154, 190, 330, 410]
// },
// {
// name: '直接访问',
// type: 'line',
// stack: '总量',
// data: [320, 332, 301, 334, 390, 330, 320]
// },
// {
// name: '搜索引擎',
// type: 'line',
// stack: '总量',
// data: [820, 932, 901, 934, 1290, 1330, 1320]
// }
// ]
// }
// chart.setOption(option1)
// },1000)
}
</script>
<style>
</style>
================================================
FILE: src/uni_modules/lime-echart/components/lime-echart/lime-echart.vue
================================================
<template>
<view>
<view style="height: 750rpx; position: relative">
<l-echart ref="chart" @finished="init"></l-echart>
<view
class="customTooltips"
:style="{ left: position[0] + 'px', top: position[1] + 'px' }"
v-if="params.length && position.length && showTip"
>
<view>这是个自定的tooltips</view>
<view>{{ params[0]['axisValue'] }}</view>
<view v-for="item in params">
<view>
<text>{{ item.seriesName }}</text>
<text>{{ item.value }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
// nvue 不需要引入
// #ifdef VUE2
import * as echarts from '@/uni_modules/lime-echart/static/echarts.min'
// #endif
// #ifdef VUE3
// #ifdef MP
// 由于vue3 使用vite 不支持umd格式的包,小程序依然可以使用,但需要使用require
const echarts = require('../../static/echarts.min')
// #endif
// #ifndef MP
// 由于 vue3 使用vite 不支持umd格式的包,故引入npm的包
import * as echarts from 'echarts/dist/echarts.esm'
// #endif
// #endif
export default {
data() {
return {
showTip: false,
position: [],
params: [],
option: {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0,
},
renderMode: 'richText',
position: (point, params, dom, rect, size) => {
// 假设自定义的tooltips尺寸
const box = [170, 170]
// 偏移
const offsetX = point[0] < size.viewSize[0] / 2 ? 20 : -box[0] - 20
const offsetY = point[1] < size.viewSize[1] / 2 ? 20 : -box[1] - 20
const x = point[0] + offsetX
const y = point[1] + offsetY
this.position = [x, y]
this.params = params
},
formatter: (params, ticket, callback) => {},
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
},
}
},
methods: {
init() {
// init(echarts, theme?:string, opts?:{}, chart => {})
// echarts 必填, 非nvue必填,nvue不用填
// theme 可选,应用的主题,目前只支持名称,如:'dark'
// opts = { // 可选
// locale?: string // 从 `5.0.0` 开始支持
// }
// chart => {} , callback 返回图表实例
// setTimeout(()=>{
// this.$refs.chart.init(echarts, chart => {
// chart.setOption(this.option);
// });
// },300)
this.$refs.chart.init(echarts, (chart) => {
chart.setOption(this.option)
// 监听tooltip显示事件
chart.on('showTip', (params) => {
this.showTip = true
console.log('showTip::')
})
chart.on('hideTip', (params) => {
setTimeout(() => {
this.showTip = false
}, 300)
})
setTimeout(() => {
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0,
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [1120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 632, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [820, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
}
chart.setOption(option)
}, 1000)
})
},
save() {
this.$refs.chart.canvasToTempFilePath({
success(res) {
console.log('res::::', res)
},
})
},
},
}
</script>
<style>
.customTooltips {
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
padding: 20rpx;
}
</style>
================================================
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 图表 <span style="font-size:16px;">👑👑👑👑👑 <span style="background:#ff9d00;padding:2px 4px;color:#fff;font-size:10px;border-radius: 3px;">全端</span></span>
> 一个基于 JavaScript 的开源可视化图表库 [查看更多](https://limeui.qcoon.cn/#/echart) <br>
> 基于 echarts 做了兼容处理,更多示例请访问 [uni示例](https://limeui.qcoon.cn/#/echart-example) | [官方示例](https://echarts.apache.org/examples/zh/index.html) <br>
## 平台兼容
| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
| --- | ---------- | ------------ | ---------- | ---------- | --------- | ---- |
| √ | √ | √ | √ | √ | √ | √ |
## 安装
- 第一步:在市场导入 [百度图表](https://ext.dcloud.net.cn/plugin?id=4899)
- 第二步:选择插件依赖:<br>
1、可以选插件内的`echarts`包或自定义包,自定义包[下载地址](https://echarts.apache.org/zh/builder.html)<br>
2、或者使用`npm`安装`echarts`
**注意**
* 🔔 echarts 5.3.0及以上
* 🔔 如果是 `cli` 项目请下载插件到`src`目录下的`uni_modules`,没有这个目录就创建一个
## 代码演示
### Vue2
- 引入依赖,可以是插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html),也可以是`npm`包
```html
<view style="width:750rpx; height:750rpx"><l-echart ref="chartRef" @finished="init"></l-echart></view>
```
```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
<view style="width:750rpx; height:750rpx"><l-echart ref="chartRef"></l-echart></view>
```
```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
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
```
```js
// @ts-nocheck
// #ifdef H5
import * as echarts from 'echarts/dist/echarts.esm.js'
// #endif
const chartRef = ref<LEchartComponentPublicInstance|null>(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
<lime-echart></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`<br>
方式一:可以在`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` |
## 打赏
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。


================================================
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<y.length&&!(S=y[M](h));M++);S||(S={});var P="undefined"!=typeof uni?uni:{};if(!P.navigateTo)for(var b in S)i(S,b)&&(P[b]=S[b]);return P.webView=S,P}));
================================================
FILE: src/uni_modules/lime-echart/static/uvue.html
================================================
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style type="text/css">
html,
body,
.canvas {
padding: 0;
margin: 0;
overflow-y: hidden;
background-color: transparent;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="canvas" id="limeChart"></div>
<script type="text/javascript" src="./uni.webview.1.5.5.js"></script>
<script type="text/javascript" src="./echarts.min.js"></script>
<script type="text/javascript" src="./ecStat.min.js"></script>
<!-- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-liquidfill@latest/dist/echarts-liquidfill.min.js"></script> -->
<script>
let chart = null;
let cache = [];
console.log = function() {
emit('log', {
log: arguments,
})
}
function emit(event, data) {
postMessage({
event,
data
})
cache = []
}
function postMessage(data) {
uni.webView.postMessage({
data
})
// window.__uniapp_x_.postMessage(JSON.stringify(data))
};
function stringify(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
return;
}
cache.push(value);
}
return value;
}
function parse(name, callback, options) {
const optionNameReg = /[\w]+\.setOption\(([\w]+\.)?([\w]+)\)/
if (optionNameReg.test(callback)) {
const optionNames = callback.match(optionNameReg)
if (optionNames[1]) {
const _this = optionNames[1].split('.')[0]
window[_this] = {}
window[_this][optionNames[2]] = options
return optionNames[2]
} else {
return null
}
}
return null
}
function init(callback, options, opts, theme) {
if (!chart) {
chart = echarts.init(document.getElementById('limeChart'), theme, opts)
if (options) {
chart.setOption(options)
}
}
}
function on(data) {
if (chart && data.length > 0) {
const [type, query] = data
const key = `${type}${JSON.stringify(query||'')}`
if (query) {
chart.on(type, query, function(options) {
var obj = {};
Object.keys(options).forEach(function(key) {
if (key != 'event') {
obj[key] = options[key];
}
});
emit(key, {
event: key,
options: obj,
});
});
} else {
chart.on(type, function(options) {
var obj = {};
Object.keys(options).forEach(function(key) {
if (key != 'event') {
obj[key] = options[key];
}
});
emit(key, {
event: key,
options: obj,
});
});
}
}
}
function setChart(callback, options) {
if (!callback) return
if (chart && callback && options) {
var r = null
const name = parse('r', callback, options)
if (name) this[name] = options
eval(`r = ${callback};`)
if (r) {
r(chart)
}
}
}
function setOption(data) {
if (chart) chart.setOption(data[0], data[1])
}
function showLoading(data) {
if (chart) chart.showLoading(data[0], data[1])
}
function hideLoading() {
if (chart) chart.hideLoading()
}
function clear() {
if (chart) chart.clear()
}
function dispose() {
if (chart) chart.dispose()
}
function resize(size) {
if (chart) chart.resize(size)
}
function canvasToTempFilePath(opt) {
if (chart) {
delete opt.success
const src = chart.getDataURL(opt)
postMessage({
// event: 'file',
file: src
})
}
}
document.addEventListener('touchmove', () => {
})
</script>
</body>
</html>
================================================
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<string, any>) => 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<string, string> = {}
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 文件中的<style>标签内的样式
{
files: ['**/*.{vue,html}'],
customSyntax: 'postcss-html'
},
{
files: ['**/*.{css,scss}'],
customSyntax: 'postcss-scss'
}
],
rules: {
'no-empty-source': null,
// 处理不认识 rpx 问题
'unit-no-unknown': [
true,
{
ignoreUnits: ['rpx']
}
],
// 处理小程序page标签不认识的问题
'selector-type-no-unknown': [
true,
{
ignoreTypes: ['page']
}
]
}
}
export default config
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"noImplicitThis": true,
"allowSyntheticDefaultImports": true,
"allowJs": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"outDir": "dist",
"lib": ["esnext", "dom"],
"types": ["@dcloudio/types", "@uni-helper/uni-types", "@types/wechat-miniprogram", "wot-design-uni/global"]
},
"vueCompilerOptions": {
"plugins": ["@uni-helper/uni-types/volar-plugin"]
},
"exclude": ["node_modules"],
"include": [
"src/**/*.ts",
"src/**/*.js",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.jsx",
"src/**/*.vue",
"src/**/*.json"
]
}
================================================
FILE: unocss.config.ts
================================================
import {
type Preset,
defineConfig,
presetUno,
presetAttributify,
presetIcons,
transformerDirectives,
transformerVariantGroup,
SourceCodeTransformer
} from 'unocss'
import { presetApplet, presetRemRpx, transformerApplet, transformerAttributify } from 'unocss-applet'
// 判断是否是小程序
const isApplet = process.env?.UNI_PLATFORM?.startsWith('mp-') ?? false
const presets: Preset[] = []
const transformers: SourceCodeTransformer[] = []
// 先判断是不是小程序,如果是小程序,则使用小程序的预设和转换器
if (isApplet) {
// 使用小程序预设
presets.push(
// 小程序用官方预设
presetApplet(),
// 小程序用 rpx 预设
presetRemRpx()
)
transformers.push(
// 小程序用 @apply 功能
transformerApplet(),
// 小程序,解决与第三方框架样式冲突问题
transformerAttributify({
prefixedOnly: true, // 只支持以 `ul-` 开头的类名
prefix: 'ul'
})
)
} else {
presets.push(
// 非小程序用官方预设
presetUno(),
// 解决与第三方框架样式冲突问题
presetAttributify({
prefixedOnly: true, // 只支持以 `ul-` 开头的类名
prefix: 'ul'
})
)
transformers.push(
// 启用 @apply 功能,比如:
// .custom - div { @apply text-center my-0 font-medium } =>
// .custom - div { margin-top: 0rem; margin-bottom: 0rem; text-align: center; font-weight: 500;}
transformerDirectives(),
// 启用 () 分组功能,比如:
// <div class="hover:(bg-gray-400 font-medium) font-(light mono)"/> =>
// <div class="hover:bg-gray-400 hover:font-medium font-light font-mo
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
SYMBOL INDEX (95 symbols across 18 files)
FILE: components.d.ts
type GlobalComponents (line 9) | interface GlobalComponents {
FILE: src/hooks/useRequest.ts
type RequestOptions (line 3) | type RequestOptions = {
function useRequest (line 17) | function useRequest<T>({
FILE: src/hooks/useTheme.ts
function useTheme (line 9) | function useTheme(vars?: ConfigProviderThemeVars) {
FILE: src/http/httpClient.ts
type CustomRequestOptionsOmit (line 5) | type CustomRequestOptionsOmit = Omit<CustomRequestOptions, 'url' | 'meth...
class ApiClient (line 10) | class ApiClient {
method http (line 11) | private static http<T>(options: Omit<CustomRequestOptions, 'isBaseUrl'...
method get (line 103) | public static get<T>(url: string, options?: CustomRequestOptionsOmit) {
method post (line 111) | public static post<T>(url: string, options?: CustomRequestOptionsOmit) {
method put (line 119) | public static put<T>(url: string, options?: CustomRequestOptionsOmit) {
method delete (line 127) | public static delete<T>(url: string, options?: CustomRequestOptionsOmi...
FILE: src/interceptors/request.ts
type CustomRequestOptions (line 4) | type CustomRequestOptions = UniApp.RequestOptions & {
method invoke (line 16) | invoke(options: CustomRequestOptions) {
method install (line 55) | install() {
FILE: src/interceptors/router.ts
method invoke (line 29) | invoke({ url }: { url: string }) {
method install (line 63) | install() {
FILE: src/main.ts
function createApp (line 8) | function createApp() {
FILE: src/store/user.ts
type UserInfo (line 4) | type UserInfo = {
FILE: src/types/auto-import.d.ts
type GlobalComponents (line 119) | interface GlobalComponents {}
type ComponentCustomProperties (line 120) | interface ComponentCustomProperties {
FILE: src/types/gloal.d.ts
type ComponentCustomProperties (line 4) | interface ComponentCustomProperties {
FILE: src/types/uni-pages.d.ts
type NavigateToOptions (line 6) | interface NavigateToOptions {
type RedirectToOptions (line 27) | interface RedirectToOptions extends NavigateToOptions {}
type SwitchTabOptions (line 29) | interface SwitchTabOptions {}
type ReLaunchOptions (line 31) | type ReLaunchOptions = NavigateToOptions | SwitchTabOptions
type Uni (line 33) | interface Uni {
FILE: src/typings.ts
type ResData (line 1) | type ResData<T> = {
FILE: src/uni_modules/lime-echart/components/l-echart/canvas.js
class EventEmit (line 5) | class EventEmit {
method constructor (line 6) | constructor() {
method on (line 9) | on(type, listener) {
method emit (line 17) | emit(type, e) {
method off (line 33) | off(type, listener) {
class Image (line 51) | class Image {
method constructor (line 52) | constructor() {
method src (line 60) | set src(src) {
method src (line 74) | get src() {
class OffscreenCanvas (line 78) | class OffscreenCanvas {
method constructor (line 79) | constructor(ctx, com, canvasId) {
method width (line 85) | set width(w) {
method height (line 88) | set height(h) {
method width (line 91) | get width() {
method height (line 94) | get height() {
method getContext (line 97) | getContext(type) {
method getImageData (line 100) | getImageData() {
class Canvas (line 120) | class Canvas {
method constructor (line 121) | constructor(ctx, com, isNew, canvasNode={}) {
method getContext (line 135) | getContext(type) {
method setAttribute (line 140) | setAttribute(key, value) {
method setChart (line 145) | setChart(chart) {
method createOffscreenCanvas (line 148) | createOffscreenCanvas(param){
method appendChild (line 161) | appendChild(child) {
method dispatchEvent (line 164) | dispatchEvent(type, e) {
method attachEvent (line 172) | attachEvent() {
method detachEvent (line 174) | detachEvent() {
method addEventListener (line 176) | addEventListener(type, listener) {
method removeEventListener (line 179) | removeEventListener(type, listener) {
method _initCanvas (line 182) | _initCanvas(zrender, ctx) {
method _initStyle (line 191) | _initStyle(ctx, child) {
method _initEvent (line 294) | _initEvent(e) {
method width (line 321) | set width(w) {
method height (line 324) | set height(h) {
method width (line 328) | get width() {
method height (line 331) | get height() {
method ctx (line 334) | get ctx() {
method chart (line 337) | set chart(chart) {
method chart (line 340) | get chart() {
function dispatch (line 345) | function dispatch(name, {x,y, wheelDelta}) {
function setCanvasCreator (line 354) | function setCanvasCreator(echarts, {canvas, node}) {
FILE: src/uni_modules/lime-echart/components/l-echart/nvue.js
class Echarts (line 1) | class Echarts {
method constructor (line 3) | constructor(webview) {
method setOption (line 7) | setOption() {
method getOption (line 11) | getOption() {
method showLoading (line 14) | showLoading() {
method hideLoading (line 17) | hideLoading() {
method clear (line 20) | clear() {
method dispose (line 23) | dispose() {
method resize (line 26) | resize(size) {
method on (line 33) | on(type, ...args) {
method dispatchAction (line 45) | dispatchAction(type, options){
FILE: src/uni_modules/lime-echart/components/l-echart/utils.js
function getDeviceInfo (line 7) | function getDeviceInfo() {
function getWindowInfo (line 20) | function getWindowInfo() {
function getAppBaseInfo (line 33) | function getAppBaseInfo() {
function compareVersion (line 44) | function compareVersion(v1, v2) {
function gte (line 68) | function gte(version) {
function canIUseCanvas2d (line 86) | function canIUseCanvas2d() {
function convertTouchesToArray (line 99) | function convertTouchesToArray(touches) {
function wrapTouch (line 112) | function wrapTouch(event) {
function base64ToPath (line 125) | function base64ToPath(base64) {
function sleep (line 156) | function sleep(time) {
function getRect (line 165) | function getRect(selector, options = {}) {
FILE: src/uni_modules/lime-echart/static/uni.webview.1.5.5.js
function i (line 1) | function i(e,i){return n.call(e,i)}
function o (line 1) | function o(){return window.__dcloud_weex_postMessage||window.__dcloud_we...
function a (line 1) | function a(){return window.__uniapp_x_postMessage||window.__uniapp_x_}
FILE: src/utils/assets.ts
function useImgAssets (line 3) | function useImgAssets(filePath: string, { noCache = false } = {}) {
FILE: vite-plugin/vite-plugin-directives.ts
method transform (line 8) | transform(code, path) {
Condensed preview — 91 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (237K chars).
[
{
"path": ".github/workflows/deploy-demo.yml",
"chars": 1019,
"preview": "name: Deploy Pages Demo\n\non:\n push:\n branches: ['master']\n\n workflow_dispatch:\n\npermissions:\n contents: read\n pag"
},
{
"path": ".gitignore",
"chars": 239,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n**/.eslintrc*\n**/.esli"
},
{
"path": ".hbuilderx/launch.json",
"chars": 529,
"preview": "{\n // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/\n"
},
{
"path": ".husky/pre-commit",
"chars": 111,
"preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\n# Run the pre-commit hook\nnpx --no-install -- lint-staged\n"
},
{
"path": ".npmrc",
"chars": 160,
"preview": "# registry = https://registry.npmjs.org\nregistry = https://registry.npmmirror.com\n\nstrict-peer-dependencies=false\nauto-i"
},
{
"path": ".prettierignore",
"chars": 191,
"preview": "# Logs\nlogs\n*.log\n\n# Editor directories and files\n.vscode\n.idea\n**/*.svg\n\n# projects\n.husky\nnode_modules\nsrc/uni_modules"
},
{
"path": ".release-it.json",
"chars": 388,
"preview": "{\n \"plugins\": {\n \"@release-it/conventional-changelog\": {\n \"emoji\": true,\n \"emojiPosition\": \"before\",\n "
},
{
"path": ".stylelintignore",
"chars": 59,
"preview": "node_modules\nsrc/uni_modules/\nsrc/static/\ndist/\n\n**/*.svg\n\n"
},
{
"path": ".vscode/settings.json",
"chars": 2869,
"preview": "{\n // 默认格式化工具选择prettier\n \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n \"files.encoding\": \"utf8\",\n \"cSpell.wo"
},
{
"path": ".vscode/vue3.code-snippets",
"chars": 893,
"preview": "{\n \"Uni-Plus Vue3 SFC\": {\n \"scope\": \"vue\",\n \"prefix\": \"v3\",\n \"description\": \"vue3 的 uni-plus sfc 文件模板\",\n \"b"
},
{
"path": "CHANGELOG.md",
"chars": 1856,
"preview": "# Changelog\r\n\r\n## 1.0.0 (2025-02-11)\r\n\r\n- ✨ feat: 适配 H5 与 微信小程序端 ([c438d98](https://github.com/DaMaiCoding/uni-plus/comm"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2024-present, 大麦大麦\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 2728,
"preview": "<p align=\"center\">\n <a href=\"https://github.com/DaMaiCoding/uni-plus\">\n <img width=\"160\" src=\"./src/static/local/log"
},
{
"path": "commitlint.config.cjs",
"chars": 3243,
"preview": "// const fs = require('fs')\n// const path = require('path')\n// const { execSync } = require('child_process')\n\n// const s"
},
{
"path": "components.d.ts",
"chars": 1559,
"preview": "/* eslint-disable */\n/* prettier-ignore */\n// @ts-nocheck\n// Generated by vite-plugin-uni-components\n// Read more: https"
},
{
"path": "eslint.config.mjs",
"chars": 2369,
"preview": "// eslint.config.mjs\n\nimport globals from 'globals'\nimport pluginJs from '@eslint/js'\nimport { configs, parser } from 't"
},
{
"path": "index.html",
"chars": 701,
"preview": "<!doctype html>\n<html>\n <head>\n <meta charset=\"UTF-8\" />\n <script>\n var coverSupport =\n 'CSS' in wind"
},
{
"path": "package.json",
"chars": 5261,
"preview": "{\n \"name\": \"uni-plus\",\n \"version\": \"1.0.0\",\n \"type\": \"commonjs\",\n \"author\": {\n \"name\": \"DaMaiCoding\",\n \"zhName"
},
{
"path": "pages.config.ts",
"chars": 569,
"preview": "import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'\n\nexport default defineUniPages({\n // 你也可以定义 pages 字段"
},
{
"path": "prettier.config.mjs",
"chars": 571,
"preview": "// prettier.config.mjs\n\n/**\n * @see https://prettier.io/docs/en/configuration.html\n * @type {import(\"prettier\").Config}\n"
},
{
"path": "src/App.vue",
"chars": 1293,
"preview": "<script setup lang=\"ts\">\r\nimport { onLaunch, onShow, onHide } from '@dcloudio/uni-app'\r\nimport { logPromotional } from '"
},
{
"path": "src/api/loginApi.ts",
"chars": 959,
"preview": "import http from '@/http/httpClient'\nimport { useUserStore } from '@/store'\n\n/* 登录 获取 accessToken */\nexport const loginA"
},
{
"path": "src/api/testApi.ts",
"chars": 726,
"preview": "import http from '@/http/httpClient'\n\n/** GET 请求测试 */\nexport const getTestApi: any = query => {\n return http.get('/getT"
},
{
"path": "src/hooks/useEcharts.ts",
"chars": 859,
"preview": "// 小程序中引入 echarts\n// #ifndef APP-PLUS || H5\nconst echarts = require('../uni_modules/lime-echart/static/echarts.min')\n// "
},
{
"path": "src/hooks/useI18n.ts",
"chars": 1532,
"preview": "import { useUserStore } from '@/store'\nimport { Locale, useCurrentLang } from 'wot-design-uni'\nimport enUS from 'wot-des"
},
{
"path": "src/hooks/useRequest.ts",
"chars": 1233,
"preview": "import { UnwrapRef, Ref } from 'vue'\n\ntype RequestOptions = {\n /** 是否立即执行 */\n immediate?: boolean\n}\n\n/**\n * useRequest"
},
{
"path": "src/hooks/useTheme.ts",
"chars": 1944,
"preview": "import type { ConfigProviderThemeVars } from 'wot-design-uni'\nimport { ref } from 'vue'\nimport { useUserStore } from '@/"
},
{
"path": "src/http/httpClient.ts",
"chars": 3991,
"preview": "import { refreshTokenApi } from '@/api/loginApi'\nimport { CustomRequestOptions } from '@/interceptors/request'\nimport { "
},
{
"path": "src/interceptors/index.ts",
"chars": 92,
"preview": "export { requestInterceptor } from './request'\nexport { routerInterceptor } from './router'\n"
},
{
"path": "src/interceptors/request.ts",
"chars": 1665,
"preview": "import { qs } from '@/utils'\nimport { useUserStore } from '@/store'\n\nexport type CustomRequestOptions = UniApp.RequestOp"
},
{
"path": "src/interceptors/router.ts",
"chars": 2115,
"preview": "/**\n * 权限拦截\n * 主要分两个:路由权限、按钮权限\n * 1. 路由权限\n * 这里主要实现路由权限,实现了黑名单拦截,权限不够就拦截\n * 比如现在用户权限是 ['logined', 'vip'] 那么他能进入就只能是 perm"
},
{
"path": "src/layouts/default.vue",
"chars": 60,
"preview": "<!-- default.vue -->\n<template>\n <slot></slot>\n</template>\n"
},
{
"path": "src/layouts/fullPage.vue",
"chars": 616,
"preview": "<!-- fullPage.vue -->\n<template>\n <WdConfigProvider :theme=\"theme\" :theme-vars=\"themeVars\">\n <div class=\"flex-col-ce"
},
{
"path": "src/layouts/theme.vue",
"chars": 305,
"preview": "<!-- theme.vue -->\n<template>\n <WdConfigProvider :theme=\"theme\" :theme-vars=\"themeVars\">\n <div class=\"min-h-100vh\">\n"
},
{
"path": "src/locale/en.json",
"chars": 1344,
"preview": "{\n \"pages.index.index\": {\n \"layersTitle\": \"Layout SFC\",\n \"attachTitle\": \"Pinia use\",\n \"attachText\": \"State man"
},
{
"path": "src/locale/zh-Hans.json",
"chars": 1031,
"preview": "{\n \"pages.index.index\": {\n \"layersTitle\": \"Layout布局\",\n \"attachTitle\": \"Pinia使用\",\n \"attachText\": \"状态管理\",\n \"l"
},
{
"path": "src/main.ts",
"chars": 525,
"preview": "import { createSSRApp } from 'vue'\r\nimport { requestInterceptor, routerInterceptor } from './interceptors'\r\nimport { che"
},
{
"path": "src/manifest.json",
"chars": 3472,
"preview": "{\n \"name\" : \"uni-plus\",\n \"appid\" : \"__UNI__42E0EC3\",\n \"description\" : \"uni-plus app 打包\",\n \"versionName\" : \"1"
},
{
"path": "src/pages/customDemo/index.vue",
"chars": 1322,
"preview": "<!--\n@description: 自定义导航栏 demo\n-->\n\n<route lang=\"json5\" type=\"page\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigat"
},
{
"path": "src/pages/echartsDemo/echartsData.ts",
"chars": 3113,
"preview": "import { useEcharts } from '@/hooks/useEcharts'\nimport { ref } from 'vue'\n\n// 折线图\nexport const useLineEcharts = (): any "
},
{
"path": "src/pages/echartsDemo/index.vue",
"chars": 1913,
"preview": "<!--\n@description: Echarts 图表 dome\n-->\n<route type=\"page\" lang=\"json5\">\n{\n layout: 'theme', // 使用主题\n style: {\n navi"
},
{
"path": "src/pages/i18nDemo/index.vue",
"chars": 960,
"preview": "<route type=\"page\" lang=\"json5\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: '%pages.index.index"
},
{
"path": "src/pages/index/index.vue",
"chars": 7844,
"preview": "<!--\r\n@description: DEMO 案例主导航页\r\n-->\r\n\r\n<route lang=\"json5\" type=\"home\">\r\n{\r\n layout: 'theme', // 使用主题\r\n style: {\r\n "
},
{
"path": "src/pages/layoutDemo/index.vue",
"chars": 387,
"preview": "<route type=\"page\" lang=\"json5\">\n{\n layout: 'fullPage', // 使用默认布局,替换成 foot 试试\n style: {\n navigationBarTitleText: '布"
},
{
"path": "src/pages/piniaDemo/index.vue",
"chars": 821,
"preview": "<!--\n@description: pinia 使用 dome\n-->\n\n<route type=\"page\" lang=\"json5\">\n{\n layout: 'theme', // 使用主题\n style: {\n navig"
},
{
"path": "src/pages/queryDemo/loginDemo.vue",
"chars": 1870,
"preview": "<route type=\"page\" lang=\"json5\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: '登录请求示例'\n }\n}\n</ro"
},
{
"path": "src/pages/queryDemo/queryTestDemo.vue",
"chars": 1588,
"preview": "<route type=\"page\" lang=\"json5\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: '请求测试页面'\n }\n}\n</ro"
},
{
"path": "src/pages/queryDemo/useRequestDemo.vue",
"chars": 1409,
"preview": "<route type=\"page\" lang=\"json5\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: 'useRequest使用示例'\n "
},
{
"path": "src/pages/queryDemo/zPagingDemo.vue",
"chars": 1327,
"preview": "<route type=\"page\" lang=\"json5\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: 'z-paging 使用示例'\n }"
},
{
"path": "src/pages/routerDemo/index.vue",
"chars": 1466,
"preview": "<route lang=\"json5\" type=\"page\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: '页面路由权限控制'\n }\n}\n</"
},
{
"path": "src/pages/routerDemo/login.vue",
"chars": 642,
"preview": "<route lang=\"json5\" type=\"page\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: '登录页'\n }\n}\n</route"
},
{
"path": "src/pages/routerDemo/page1.vue",
"chars": 311,
"preview": "<route lang=\"json5\" type=\"page\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: '页面1'\n },\n permis"
},
{
"path": "src/pages/routerDemo/page2.vue",
"chars": 320,
"preview": "<route lang=\"json5\" type=\"page\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: '页面2'\n },\n permis"
},
{
"path": "src/pages/routerDemo/page3.vue",
"chars": 315,
"preview": "<route lang=\"json5\" type=\"page\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: '页面3'\n },\n permis"
},
{
"path": "src/pages/unocssDemo/index.vue",
"chars": 1008,
"preview": "<!--\n@description: Unocss 使用 dome\n-->\n\n<route type=\"page\" lang=\"json5\">\n{\n layout: 'theme', // 使用主题\n style: {\n navi"
},
{
"path": "src/pages/wotUiDemo/index.vue",
"chars": 650,
"preview": "<route type=\"page\" lang=\"json5\">\n{\n layout: 'theme', // 使用主题\n style: {\n navigationBarTitleText: 'Wot UI Demo'\n }\n}"
},
{
"path": "src/pages-sub/subDemo/index.vue",
"chars": 214,
"preview": "<route lang=\"json5\" type=\"page\">\n{\n style: { navigationBarTitleText: 'subDemo 分包页面' }\n}\n</route>\n\n<template>\n <view cl"
},
{
"path": "src/pages-sub/testDemo/index.vue",
"chars": 514,
"preview": "<!--\n@description: 测试页面\n@creationTime: 2025-01-23\n-->\n\n<route type=\"page\" lang=\"json5\">\n{\n layout: 'default',\n style: "
},
{
"path": "src/pages.json",
"chars": 3395,
"preview": "{\n\t\"pages\": [\n\t\t{\n\t\t\t\"path\": \"pages/index/index\",\n\t\t\t\"type\": \"home\",\n\t\t\t\"layout\": \"theme\",\n\t\t\t\"style\": {\n\t\t\t\t\"navigation"
},
{
"path": "src/store/index.ts",
"chars": 347,
"preview": "import { createPinia } from 'pinia'\nimport { createPersistedState } from 'pinia-plugin-persistedstate' // 数据持久化\n\n// 持久化把"
},
{
"path": "src/store/user.ts",
"chars": 2082,
"preview": "import { defineStore } from 'pinia'\nimport { ref } from 'vue'\n\ntype UserInfo = {\n userName?: string\n userId?: string\n "
},
{
"path": "src/theme.json",
"chars": 1053,
"preview": "{\n // 浅色模式\n \"light\": {\n \"navBgColor\": \"#fff\", // 导航栏背景色\n \"navTxtStyle\": \"black\", // 导航栏文字颜色\n \"bgColor\": \"#f5f"
},
{
"path": "src/types/auto-import.d.ts",
"chars": 13140,
"preview": "/* eslint-disable */\n/* prettier-ignore */\n// @ts-nocheck\n// noinspection JSUnusedGlobalSymbols\n// Generated by unplugin"
},
{
"path": "src/types/gloal.d.ts",
"chars": 135,
"preview": "import { ComponentCustomProperties } from 'vue'\n\ndeclare module 'vue' {\n interface ComponentCustomProperties {\n $ass"
},
{
"path": "src/types/uni-pages.d.ts",
"chars": 1298,
"preview": "/* eslint-disable */\n/* prettier-ignore */\n// @ts-nocheck\n// Generated by vite-plugin-uni-pages\n\ninterface NavigateToOpt"
},
{
"path": "src/typings.ts",
"chars": 65,
"preview": "type ResData<T> = {\n data: T\n code: number\n message: string\n}\n"
},
{
"path": "src/uni.scss",
"chars": 1703,
"preview": "/**\r\n * 这里是uni-app内置的常用样式变量\r\n *\r\n * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量\r\n * 如果你是插件开发者,建议你使用s"
},
{
"path": "src/uni_modules/lime-echart/changelog.md",
"chars": 6058,
"preview": "## 0.9.8(2024-12-20)\n- fix: 修复 APP 无法放大问题\n## 0.9.7(2024-12-02)\n- feat: uniapp 增加`landscape`,当`landscape`为`true`时旋转90deg达"
},
{
"path": "src/uni_modules/lime-echart/components/l-echart/canvas.js",
"chars": 8976,
"preview": "import {getDeviceInfo} from './utils';\r\n\r\nconst cacheChart = {}\r\nconst fontSizeReg = /([\\d\\.]+)px/;\r\nclass EventEmit {\n\t"
},
{
"path": "src/uni_modules/lime-echart/components/l-echart/l-echart.uvue",
"chars": 7451,
"preview": "<template>\r\n\t<!-- #ifdef APP -->\r\n\t<web-view class=\"lime-echart\" ref=\"chartRef\" @load=\"loaded\" :style=\"[customStyle]\" \r\n"
},
{
"path": "src/uni_modules/lime-echart/components/l-echart/l-echart.vue",
"chars": 13488,
"preview": "<template>\r\n\t<view class=\"lime-echart\" :style=\"[customStyle]\" v-if=\"canvasId\" ref=\"limeEchart\" :aria-label=\"ariaLabel\">\r"
},
{
"path": "src/uni_modules/lime-echart/components/l-echart/nvue.js",
"chars": 1253,
"preview": "export class Echarts {\r\n\teventMap = new Map()\r\n\tconstructor(webview) {\r\n\t\tthis.webview = webview\r\n\t\tthis.options = null\r"
},
{
"path": "src/uni_modules/lime-echart/components/l-echart/utils.js",
"chars": 4238,
"preview": "// @ts-nocheck\r\n/**\n * 获取设备基础信息\n *\n * @see [uni.getDeviceInfo](https://uniapp.dcloud.net.cn/api/system/getDeviceInfo.htm"
},
{
"path": "src/uni_modules/lime-echart/components/l-echart/uvue.uts",
"chars": 3983,
"preview": "// @ts-nocheck\r\n// #ifdef APP\r\ntype EchartsEventHandler = (event: UTSJSONObject)=>void\r\n// type EchartsTempResolve = (ob"
},
{
"path": "src/uni_modules/lime-echart/components/lime-echart/lime-echart.nvue",
"chars": 3323,
"preview": "<template>\r\n\t<view style=\"width: 100%; height: 408px;\">\r\n\t\t<l-echart ref=\"chartRef\" @finished=\"init\"></l-echart>\r\n\t</vie"
},
{
"path": "src/uni_modules/lime-echart/components/lime-echart/lime-echart.uvue",
"chars": 3357,
"preview": "<template>\r\n\t<view style=\"width: 100%; height: 408px;\">\r\n\t\t<l-echart ref=\"chartRef\" @finished=\"init\"></l-echart>\r\n\t</vie"
},
{
"path": "src/uni_modules/lime-echart/components/lime-echart/lime-echart.vue",
"chars": 5944,
"preview": "<template>\n <view>\n <view style=\"height: 750rpx; position: relative\">\n <l-echart ref=\"chart\" @finished=\"init\"><"
},
{
"path": "src/uni_modules/lime-echart/package.json",
"chars": 1755,
"preview": "{\n \"id\": \"lime-echart\",\n \"displayName\": \"echarts\",\n \"version\": \"0.9.8\",\n \"description\": \"echarts 全端兼容,一款使echarts图表能跑"
},
{
"path": "src/uni_modules/lime-echart/readme.md",
"chars": 10005,
"preview": "# echarts 图表 <span style=\"font-size:16px;\">👑👑👑👑👑 <span style=\"background:#ff9d00;padding:2px 4px;color:#fff;font-size:10"
},
{
"path": "src/uni_modules/lime-echart/static/uni.webview.1.5.5.js",
"chars": 6147,
"preview": "!function(e,n){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=n():\"function\"==typeof define&&define"
},
{
"path": "src/uni_modules/lime-echart/static/uvue.html",
"chars": 4079,
"preview": "<!DOCTYPE html>\r\n<html lang=\"zh\">\r\n\t<head>\r\n\t\t<meta charset=\"UTF-8\">\r\n\t\t<meta name=\"viewport\"\r\n\t\t\tcontent=\"width=device-"
},
{
"path": "src/utils/assets.ts",
"chars": 453,
"preview": "const imgBaseUrl = (import.meta as any).env.VITE_IMG_BASEURL // 图片请求路径\n\nfunction useImgAssets(filePath: string, { noCach"
},
{
"path": "src/utils/index.ts",
"chars": 103,
"preview": "// 模块统一导出\nexport * from './qs'\nexport * from './router'\nexport * from './log'\nexport * from './assets'\n"
},
{
"path": "src/utils/log.ts",
"chars": 490,
"preview": "/* 打印 控制台宣传信息 */\nexport const logPromotional = () => {\n console.log(\n `%c uni-plus %c 一个超超超好用的 uniapp 开发框架 作者:大麦大麦 %"
},
{
"path": "src/utils/qs.ts",
"chars": 211,
"preview": "// 没必要为了 qs 一个简单的功能引入 qs,手写一个\nexport const qs = {\n stringify: function (obj) {\n return Object.keys(obj)\n .map(k"
},
{
"path": "src/utils/router.ts",
"chars": 2479,
"preview": "import { pages, subPackages } from '@/pages.json'\nimport { useUserStore } from '@/store'\n\n/**\n * 根据路由路径,获取页面权限 permissio"
},
{
"path": "stylelint.config.mjs",
"chars": 776,
"preview": "// prettier.config.mjs\n\n/** @type {import('stylelint').Config} */\nconst config = {\n extends: [\n 'stylelint-config-re"
},
{
"path": "tsconfig.json",
"chars": 787,
"preview": "{\n \"compilerOptions\": {\n \"composite\": true,\n \"skipLibCheck\": true,\n \"module\": \"ESNext\",\n \"moduleResolution\""
},
{
"path": "unocss.config.ts",
"chars": 2936,
"preview": "import {\n type Preset,\n defineConfig,\n presetUno,\n presetAttributify,\n presetIcons,\n transformerDirectives,\n tran"
},
{
"path": "vite-plugin/vite-plugin-directives.ts",
"chars": 1035,
"preview": "import { Plugin } from 'vite'\nimport { parse } from 'vue/compiler-sfc'\n\nconst vitePluginDirectives = ({ directives = 'v-"
},
{
"path": "vite.config.ts",
"chars": 2000,
"preview": "import { defineConfig } from 'vite'\nimport path from 'node:path'\nimport uni from '@dcloudio/vite-plugin-uni'\nimport UniP"
}
]
About this extraction
This page contains the full source code of the DaMaiCoding/uni-plus GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 91 files (188.2 KB), approximately 64.3k tokens, and a symbol index with 95 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.