diff --git a/.changeset/README.md b/.changeset/README.md deleted file mode 100644 index 5654e898baa..00000000000 --- a/.changeset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Changesets - -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) - -We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json deleted file mode 100644 index f954fb4ba97..00000000000 --- a/.changeset/config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", - "changelog": [ - "@changesets/changelog-github", - { "repo": "vbenjs/vue-vben-admin" } - ], - "commit": false, - "fixed": [["@vben-core/*", "@vben/*"]], - "snapshot": { - "prereleaseTemplate": "{tag}-{datetime}" - }, - "privatePackages": { "version": true, "tag": true }, - "linked": [], - "access": "public", - "baseBranch": "main", - "updateInternalDependencies": "patch", - "ignore": [] -} diff --git a/.commitlintrc.cjs b/.commitlintrc.cjs new file mode 100644 index 00000000000..9bb2aef744a --- /dev/null +++ b/.commitlintrc.cjs @@ -0,0 +1,107 @@ +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$/, ''); + +/** @type {import('cz-git').UserConfig} */ +module.exports = { + ignores: [(commit) => commit.includes('init')], + extends: ['@commitlint/config-conventional'], + rules: { + 'body-leading-blank': [2, 'always'], + 'footer-leading-blank': [1, 'always'], + 'header-max-length': [2, 'always', 108], + 'subject-empty': [2, 'never'], + 'type-empty': [2, 'never'], + 'subject-case': [0], + 'type-enum': [ + 2, + 'always', + [ + 'feat', + 'fix', + 'perf', + 'style', + 'docs', + 'test', + 'refactor', + 'build', + 'ci', + 'chore', + 'revert', + 'wip', + 'workflow', + 'types', + 'release', + ], + ], + }, + prompt: { + /** @use `pnpm commit :f` */ + alias: { + f: 'docs: fix typos', + r: 'docs: update README', + s: 'style: update code format', + b: 'build: bump dependencies', + c: 'chore: update config', + }, + customScopesAlign: !scopeComplete ? 'top' : 'bottom', + defaultScope: scopeComplete, + scopes: [...scopes, 'mock'], + allowEmptyIssuePrefixs: false, + allowCustomIssuePrefixs: false, + + // English + typesAppend: [ + { value: 'wip', name: 'wip: work in process' }, + { value: 'workflow', name: 'workflow: workflow improvements' }, + { value: 'types', name: 'types: type definition file changes' }, + ], + + // 中英文对照版 + // messages: { + // type: '选择你要提交的类型 :', + // scope: '选择一个提交范围 (可选):', + // customScope: '请输入自定义的提交范围 :', + // subject: '填写简短精炼的变更描述 :\n', + // body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n', + // breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n', + // footerPrefixsSelect: '选择关联issue前缀 (可选):', + // customFooterPrefixs: '输入自定义issue前缀 :', + // footer: '列举关联issue (可选) 例如: #31, #I3244 :\n', + // confirmCommit: '是否提交或修改commit ?', + // }, + // types: [ + // { value: 'feat', name: 'feat: 新增功能' }, + // { value: 'fix', name: 'fix: 修复缺陷' }, + // { value: 'docs', name: 'docs: 文档变更' }, + // { value: 'style', name: 'style: 代码格式' }, + // { value: 'refactor', name: 'refactor: 代码重构' }, + // { value: 'perf', name: 'perf: 性能优化' }, + // { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' }, + // { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' }, + // { value: 'ci', name: 'ci: 修改 CI 配置、脚本' }, + // { value: 'revert', name: 'revert: 回滚 commit' }, + // { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' }, + // { value: 'wip', name: 'wip: 正在开发中' }, + // { value: 'workflow', name: 'workflow: 工作流程改进' }, + // { value: 'types', name: 'types: 类型定义文件修改' }, + // ], + // emptyScopesAlias: 'empty: 不填写', + // customScopesAlias: 'custom: 自定义', + }, +}; diff --git a/.commitlintrc.js b/.commitlintrc.js deleted file mode 100644 index 02e33fa6244..00000000000 --- a/.commitlintrc.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/commitlint-config'; diff --git a/.dockerignore b/.dockerignore index 52b833a96cc..8617652761f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,3 @@ -node_modules -.git -.gitignore -*.md -dist -.turbo -dist.zip +node_modules/ +dist/ +.vscode/ diff --git a/.editorconfig b/.editorconfig index 179aec6f135..dccf841d49b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,8 +7,6 @@ insert_final_newline=true indent_style=space indent_size=2 max_line_length = 100 -trim_trailing_whitespace = true -quote_type = single [*.{yml,yaml,json}] indent_style = space @@ -16,3 +14,6 @@ indent_size = 2 [*.md] trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.env b/.env new file mode 100644 index 00000000000..361dcb23e58 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +# spa-title +VITE_GLOB_APP_TITLE = Vben Admin diff --git a/.env.analyze b/.env.analyze new file mode 100644 index 00000000000..165728f1015 --- /dev/null +++ b/.env.analyze @@ -0,0 +1,23 @@ +# Whether to open mock +VITE_USE_MOCK = true + +# public path +VITE_PUBLIC_PATH = / + +# Whether to enable gzip or brotli compression +# Optional: gzip | brotli | none +# If you need multiple forms, you can use `,` to separate +VITE_BUILD_COMPRESS = 'none' + + +# Basic interface address SPA +VITE_GLOB_API_URL = /basic-api + +# File upload address, optional +# It can be forwarded by nginx or write the actual address directly +VITE_GLOB_UPLOAD_URL = /upload + +# Interface prefix +VITE_GLOB_API_URL_PREFIX = + +VITE_ENABLE_ANALYZE = true diff --git a/.env.development b/.env.development new file mode 100644 index 00000000000..0daf23b2662 --- /dev/null +++ b/.env.development @@ -0,0 +1,14 @@ +# Whether to open mock +VITE_USE_MOCK = true + +# public path +VITE_PUBLIC_PATH = / + +# Basic interface address SPA +VITE_GLOB_API_URL = /basic-api + +# File upload address, optional +VITE_GLOB_UPLOAD_URL = /upload + +# Interface prefix +VITE_GLOB_API_URL_PREFIX = diff --git a/.env.docker b/.env.docker new file mode 100644 index 00000000000..f7676e70535 --- /dev/null +++ b/.env.docker @@ -0,0 +1,22 @@ +# Whether to open mock +VITE_USE_MOCK = false + +# public path +VITE_PUBLIC_PATH = / + +# timeout(seconds) +VITE_TIMEOUT = 15 +# Delete console +VITE_DROP_CONSOLE = true + +# Whether to enable gzip or brotli compression +# Optional: gzip | brotli | none +# If you need multiple forms, you can use `,` to separate +VITE_BUILD_COMPRESS = 'none' +VITE_GLOB_API_URL = "__vg_base_url" + +# File upload address, optional +# It can be forwarded by nginx or write the actual address directly +VITE_GLOB_UPLOAD_URL = /files/upload +# Interface prefix +VITE_GLOB_API_URL_PREFIX = diff --git a/.env.production b/.env.production new file mode 100644 index 00000000000..c95d6ef9bfb --- /dev/null +++ b/.env.production @@ -0,0 +1,21 @@ +# Whether to open mock +VITE_USE_MOCK = true + +# public path +VITE_PUBLIC_PATH = / + +# Whether to enable gzip or brotli compression +# Optional: gzip | brotli | none +# If you need multiple forms, you can use `,` to separate +VITE_BUILD_COMPRESS = 'none' + + +# Basic interface address SPA +VITE_GLOB_API_URL = /basic-api + +# File upload address, optional +# It can be forwarded by nginx or write the actual address directly +VITE_GLOB_UPLOAD_URL = /upload + +# Interface prefix +VITE_GLOB_API_URL_PREFIX = diff --git a/.env.test b/.env.test new file mode 100644 index 00000000000..f18e16ce9df --- /dev/null +++ b/.env.test @@ -0,0 +1,21 @@ +NODE_ENV = production +# Whether to open mock +VITE_USE_MOCK = true + +# public path +VITE_PUBLIC_PATH = / + +# Whether to enable gzip or brotli compression +# Optional: gzip | brotli | none +# If you need multiple forms, you can use `,` to separate +VITE_BUILD_COMPRESS = 'none' + +# Basic interface address SPA +VITE_GLOB_API_URL = /basic-api + +# File upload address, optional +# It can be forwarded by nginx or write the actual address directly +VITE_GLOB_UPLOAD_URL = /upload + +# Interface prefix +VITE_GLOB_API_URL_PREFIX = diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000000..67ac532fd16 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,16 @@ + +*.sh +node_modules +*.md +*.woff +*.ttf +.vscode +.idea +dist +/public +/docs +.husky +.local +/bin +Dockerfile +package.json diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 00000000000..98fc3ef4706 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + root: true, + extends: ['@vben'], + rules: { + 'no-undef': 'off', + }, +}; diff --git a/.gitconfig b/.gitconfig deleted file mode 100644 index 4b28a69cdb5..00000000000 --- a/.gitconfig +++ /dev/null @@ -1,2 +0,0 @@ -[core] - ignorecase = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index b95ff9460bf..00000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,14 +0,0 @@ -# default onwer -* anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com - -# vben core onwer -/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com -/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com -/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com -/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com -/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com -/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com - -# vben team onwer -apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com -docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com diff --git a/.github/ISSUE_TEMPLATE/1-bug.md b/.github/ISSUE_TEMPLATE/1-bug.md new file mode 100644 index 00000000000..f446a2b559a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug.md @@ -0,0 +1,39 @@ +--- +name: 🐛 Bug report +about: Create a report to help us improve +title: '' +labels: 'bug: pending triage' +assignees: '' +--- + + + +**⚠️ IMPORTANT ⚠️ Please check the following list before proceeding. If you ignore this issue template, your issue will be directly closed.** + +- [ ] Read [the docs](https://anncwb.github.io/vue-vben-admin-doc/). +- [ ] Make sure the code is up to date. (Some bugs have been fixed in the latest code) +- [ ] This is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/anncwb/vue-vben-admin/discussions) or join our [Discord](https://discord.gg/8GuAdwDhj6) Chat Server. + +### Describe the bug + +A clear and concise description of what the bug is.. + +### Reproduction + +Please describe the steps of the problem in detail to ensure that we can restore the correct problem + +## System Info + +- Operating System: +- Node version: +- pnpm version: diff --git a/.github/ISSUE_TEMPLATE/2-feature.md b/.github/ISSUE_TEMPLATE/2-feature.md new file mode 100644 index 00000000000..cbe4164561a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-feature.md @@ -0,0 +1,32 @@ +--- +name: 🚀 Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' +--- + + + +### Subject of the feature + +Describe your issue here. + +### Problem + +If the feature requests relates to a problem, please describe the problem you are trying to solve here. + +### Expected behaviour + +What should happen? Please describe the desired behaviour. + +### Alternatives + +What are the alternative solutions? Please describe what else you have considered? diff --git a/.github/ISSUE_TEMPLATE/3-bug-cn.md b/.github/ISSUE_TEMPLATE/3-bug-cn.md new file mode 100644 index 00000000000..90572314e63 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3-bug-cn.md @@ -0,0 +1,28 @@ +--- +name: 🐛 Bug 报告 +about: 向我们报告一个Bug以帮助我们改进 +title: '' +labels: 'bug: pending triage' +assignees: '' +--- + +**⚠️ 重要 ⚠️ 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭** + +- [ ] 已阅读 [文档](https://anncwb.github.io/vue-vben-admin-doc/). +- [ ] 确保您的代码已是最新或者所报告的 Bug 在最新版本中可以重现. (部分 Bug 可能已经在最近的代码中修复) +- [ ] 已在 Issues 中搜索了相关的关键词 +- [ ] 不是 ant design vue 组件库的 Bug + +### 描述 Bug + +请清晰地描述此 Bug 的具体表现。 + +### 复现 Bug + +请描述在演示页面中复现 Bug 的详细步骤,以确保我们可以理解并定位问题。部分 Bug 如果未在 Demo 中涉及,请务必提供关键代码 + +## 系统信息 + +- 操作系统: +- Node 版本: +- pnpm 版本: diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml deleted file mode 100644 index ae92780319d..00000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: 🐞 Bug Report -description: Report an issue with Vben Admin to help us make it better. -title: 'Bug: ' -labels: ['bug: pending triage'] - -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! - - type: dropdown - id: version - attributes: - label: Version - description: What version of our software are you running? - options: - - Vben Admin V5 - - Vben Admin V2 - default: 0 - validations: - required: true - - - type: textarea - id: bug-desc - attributes: - label: Describe the bug? - description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! - placeholder: Bug Description - validations: - required: true - - - type: textarea - id: reproduction - attributes: - label: Reproduction - description: Please provide a link to [StackBlitz](https://stackblitz.com/fork/github/vitest-dev/vitest/tree/main/examples/basic?initialPath=__vitest__/) (you can also use [examples](https://github.com/vitest-dev/vitest/tree/main/examples)) or a github repo that can reproduce the problem you ran into. A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "needs reproduction" label. If no reproduction is provided after 3 days, it will be auto-closed. - placeholder: Reproduction - validations: - required: true - - - type: textarea - id: system-info - attributes: - label: System Info - description: Output of `npx envinfo --system --npmPackages '{vue}' --binaries --browsers` - render: shell - placeholder: System, Binaries, Browsers - validations: - required: true - - - type: textarea - id: logs - attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. - render: shell - - - type: checkboxes - id: terms - attributes: - label: Validations - description: Before submitting the issue, please make sure you do the following - # description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com). - options: - - label: Read the [docs](https://doc.vben.pro/) - required: true - - label: Ensure the code is up to date. (Some issues have been fixed in the latest version) - required: true - - label: I have searched the [existing issues](https://github.com/vbenjs/vue-vben-admin/issues) and checked that my issue does not duplicate any existing issues. - required: true - - label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/vbenjs/vue-vben-admin/discussions) or join our [Discord Chat Server](https://discord.gg/8GuAdwDhj6). - required: true - - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug. - required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..b83ccbf24d3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Discord Chat + url: https://discord.gg/8GuAdwDhj6 + about: Ask questions and discuss with other Vben users in real time. + - name: Questions & Discussions + url: https://github.com/anncwb/vue-vben-admin/discussions + about: Use GitHub discussions for message-board style questions and discussions. diff --git a/.github/ISSUE_TEMPLATE/docs.yml b/.github/ISSUE_TEMPLATE/docs.yml deleted file mode 100644 index d2bf16eff34..00000000000 --- a/.github/ISSUE_TEMPLATE/docs.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: 📚 Documentation -description: Report an issue with Vben Admin Website to help us make it better. -title: 'Docs: ' -labels: [documentation] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this issue! - - type: checkboxes - id: documentation_is - attributes: - label: Documentation is - options: - - label: Missing - - label: Outdated - - label: Confusing - - label: Not sure? - - type: textarea - id: description - attributes: - label: Explain in Detail - description: A clear and concise description of your suggestion. If you intend to submit a PR for this issue, tell us in the description. Thanks! - placeholder: The description of ... page is not clear. I thought it meant ... but it wasn't. - validations: - required: true - - type: textarea - id: suggestion - attributes: - label: Your Suggestion for Changes - validations: - required: true - - type: textarea - id: reproduction-steps - attributes: - label: Steps to reproduce - description: Please provide any reproduction steps that may need to be described. E.g. if it happens only when running the dev or build script make sure it's clear which one to use. - placeholder: Run `pnpm install` followed by `pnpm run docs:dev` diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index 393334e8dc1..00000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: ✨ New Feature Proposal -description: Propose a new feature to be added to Vben Admin -title: 'FEATURE: ' -labels: ['enhancement: pending triage'] -body: - - type: markdown - attributes: - value: | - Thank you for suggesting a feature for our project! Please fill out the information below to help us understand and implement your request! - - type: dropdown - id: version - attributes: - label: Version - description: What version of our software are you running? - options: - - Vben Admin V5 - - Vben Admin V2 - default: 0 - validations: - required: true - - - type: textarea - id: description - attributes: - label: Description - description: A detailed description of the feature request. - placeholder: Please describe the feature you would like to see, and why it would be useful. - validations: - required: true - - - type: textarea - id: proposed-solution - attributes: - label: Proposed Solution - description: A clear and concise description of what you want to happen. - placeholder: Describe the solution you'd like to see - validations: - required: true - - - type: textarea - id: alternatives - attributes: - label: Alternatives Considered - description: | - A clear and concise description of any alternative solutions or features you've considered. - placeholder: Describe any alternative solutions or features you've considered - validations: - required: false - - - type: input - id: additional-context - attributes: - label: Additional Context - description: Add any other context or screenshots about the feature request here. - placeholder: Any additional information - validations: - required: false - - - type: checkboxes - id: checkboxes - attributes: - label: Validations - description: Before submitting the issue, please make sure you do the following - options: - - label: Read the [docs](https://doc.vben.pro/) - required: true - - label: Ensure the code is up to date. (Some issues have been fixed in the latest version) - required: true - - label: I have searched the [existing issues](https://github.com/vbenjs/vue-vben-admin/issues) and checked that my issue does not duplicate any existing issues. - required: true diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml deleted file mode 100644 index 35fa41c8abd..00000000000 --- a/.github/actions/setup-node/action.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: 'Setup Node' - -description: 'Setup node and pnpm' - -runs: - using: 'composite' - steps: - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version-file: .node-version - cache: 'pnpm' - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache@v4 - name: Setup pnpm cache - if: ${{ github.ref_name == 'main' }} - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - uses: actions/cache/restore@v4 - if: ${{ github.ref_name != 'main' }} - with: - path: ${{ env.STORE_PATH }} - key: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - shell: bash - run: pnpm install --frozen-lockfile diff --git a/.github/config.yml b/.github/config.yml deleted file mode 100644 index 52454840a7e..00000000000 --- a/.github/config.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Prevent issues being created without using the template -blank_issues_enabled: false -checkIssueTemplate: true -checkPullRequestTemplate: true - -contact_links: - - name: 💬 Discord Chat - url: https://discord.gg/8GuAdwDhj6 - about: Ask questions and discuss with other Vben users in real time. - - - name: ❓ Questions & Discussions - url: https://github.com/@vbenjs/vue-vben-admin/discussions - about: Use GitHub discussions for message-board style questions and discussions. - -# Comment to be posted to on PRs from first time contributors in your repository -newPRWelcomeComment: | - 💖 Thanks for opening this pull request! 💖 - Please be patient and we will get back to you as soon as we can. - -# Comment to be posted to on pull requests merged by a first time user -firstPRMergeComment: > - Thanks for your contribution! 🎉🎉🎉 - - -# Comment to be posted to on first time issues -newIssueWelcomeComment: > - Thanks for opening your first issue! Be sure to follow the issue template and provide every bit of information to help the developers! - - -# *OPTIONAL* default titles to check against for lack of descriptiveness -# MUST BE ALL LOWERCASE -requestInfoDefaultTitles: - - update readme.md - - updates - -# *Required* Comment to reply with -requestInfoReplyComment: > - Thanks for filing this issue/PR! It would be much appreciated if you could provide us with more information so we can effectively analyze the situation in context. - diff --git a/.github/contributing.md b/.github/contributing.md index 304c519266d..9a5211b2a5a 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -1,40 +1,5 @@ -# Vben Admin Contributing Guide +# Contributing Guide -Hi! We're really excited that you are interested in contributing to Vben Admin. Before submitting your contribution, please make sure to take a moment and read through the following guidelines: - -- [Pull Request Guidelines](#pull-request-guidelines) - -## Contributor Code of Conduct - -As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. - -We are committed to making participation in this project a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. - -Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. - -## Pull Request Guidelines - -- Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch. - -- If adding a new feature: - - Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it. - -- If fixing bug: - - Provide a detailed description of the bug in the PR. Live demo preferred. - -- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging. - -## Development Setup - -You will need [pnpm](https://pnpm.io/) - -After cloning the repo, run: - -```bash -# install the dependencies of the project -$ pnpm install -# start the project -$ pnpm run dev -``` +1. Make sure you put things in the right category! +2. Always add your items to the end of a list. To be fair, the order is first-come-first-serve. +3. If you think something belongs in the wrong category, or think there needs to be a new category, feel free to edit things too. diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index d1b6d3b44fe..00000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: 2 -updates: - - package-ecosystem: npm - directory: '/' - schedule: - interval: daily - groups: - non-breaking-changes: - update-types: [minor, patch] - - - package-ecosystem: github-actions - directory: '/' - schedule: - interval: weekly - groups: - non-breaking-changes: - update-types: [minor, patch] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8d7da679ea6..82692117dfa 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,28 +1,29 @@ -## Description +### `General` - +- [ ] Pull request template structure not broken - +### `Type` -## Type of change +> ℹ️ What types of changes does your code introduce? -Please delete options that are not relevant. +> 👉 _Put an `x` in the boxes that apply_ - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update -- [ ] Please, don't make changes to `pnpm-lock.yaml` unless you introduce a new test example. -## Checklist +### `Checklist` > ℹ️ Check all checkboxes - this will indicate that you have done everything in accordance with the rules in [CONTRIBUTING](contributing.md). -- [ ] If you introduce new functionality, document it. You can run documentation with `pnpm run docs:dev` command. -- [ ] Run the tests with `pnpm test`. -- [ ] Changes in changelog are generated from PR name. Please, make sure that it explains your changes in an understandable manner. Please, prefix changeset messages with `feat:`, `fix:`, `perf:`, `docs:`, or `chore:`. +> 👉 _Put an `x` in the boxes that apply._ + +- [ ] My code follows the style guidelines of this project +- [ ] Is the code format correct +- [ ] Is the git submission information standard? - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index 886190914c5..00000000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,61 +0,0 @@ -name-template: 'v$RESOLVED_VERSION' -tag-template: 'v$RESOLVED_VERSION' -version-template: $MAJOR.$MINOR.$PATCH -change-template: '* $TITLE (#$NUMBER) @$AUTHOR' -template: | - # What's Changed - - $CHANGES - - **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION - -categories: - - title: '🚀 Features' - labels: - - 'feature' - - title: '🐞 Bug Fixes' - labels: - - 'bug' - - title: '📈 Performance & Enhancement' - labels: - - 'perf' - - 'enhancement' - - title: 📝 Documentation - labels: - - 'documentation' - - title: 👻 Maintenance - labels: - - 'chore' - - 'dependencies' - # collapse-after: 12 - - title: 🚦 Tests - labels: - - 'tests' - - title: 'Breaking' - label: 'breaking' - -version-resolver: - major: - labels: - - 'major' - - 'breaking' - minor: - labels: - - 'minor' - patch: - labels: - - 'feature' - - 'patch' - - 'bug' - - 'maintenance' - - 'docs' - - 'dependencies' - - 'security' - -exclude-labels: - - 'skip-changelog' - - 'no-changelog' - - 'changelog' - - 'bump versions' - - 'reverted' - - 'invalid' diff --git a/.github/semantic.yml b/.github/semantic.yml deleted file mode 100644 index 8d8ffd957f6..00000000000 --- a/.github/semantic.yml +++ /dev/null @@ -1,13 +0,0 @@ -titleAndCommits: true -types: - - feat - - fix - - docs - - chore - - style - - refactor - - perf - - test - - build - - ci - - revert diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index d9f48e4a953..00000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,48 +0,0 @@ -# name: Dependabot post-update -name: Build detection -on: - pull_request_target: - types: [opened, synchronize, reopened] - branches: - - main - -env: - HUSKY: '0' - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} - cancel-in-progress: true - -permissions: - contents: read - pull-requests: write - -jobs: - post-update: - if: github.repository == 'vbenjs/vue-vben-admin' - # if: ${{ github.actor == 'dependabot[bot]' }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - ubuntu-latest - # - macos-latest - - windows-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Checkout out pull request - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh pr checkout ${{ github.event.pull_request.number }} - - - name: Setup Node - uses: ./.github/actions/setup-node - - - name: Build - run: | - pnpm run build diff --git a/.github/workflows/changeset-version.yml b/.github/workflows/changeset-version.yml deleted file mode 100644 index 373d9af27b1..00000000000 --- a/.github/workflows/changeset-version.yml +++ /dev/null @@ -1,42 +0,0 @@ -# https://github.com/changesets/action -name: Changeset version - -on: - workflow_dispatch: - pull_request: - types: - - closed - branches: - - main - -permissions: - pull-requests: write - contents: write - -env: - CI: true - -jobs: - version: - if: (github.event.pull_request.merged || github.event_name == 'workflow_dispatch') && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin' - # if: github.repository == 'vbenjs/vue-vben-admin' - timeout-minutes: 15 - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node - uses: ./.github/actions/setup-node - - - name: Create Release Pull Request - uses: changesets/action@v1 - with: - version: pnpm run version - commit: 'chore: bump versions' - title: 'chore: bump versions' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 00750949337..00000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: CI - -on: - pull_request: - push: - branches: - - main - - 'releases/*' - -permissions: - contents: read - -env: - CI: true - TZ: Asia/Shanghai - -jobs: - test: - name: Test - if: github.repository == 'vbenjs/vue-vben-admin' - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - ubuntu-latest - # - macos-latest - - windows-latest - timeout-minutes: 20 - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - run_install: false - - - name: Setup Node - uses: ./.github/actions/setup-node - - # - name: Check Git version - # run: git --version - - # - name: Setup mock Git user - # run: git config --global user.email "you@example.com" && git config --global user.name "Your Name" - - - name: Vitest tests - run: pnpm run test:unit - - # - name: Upload coverage - # uses: codecov/codecov-action@v4 - # with: - # token: ${{ secrets.CODECOV_TOKEN }} - - lint: - name: Lint - if: github.repository == 'vbenjs/vue-vben-admin' - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - ubuntu-latest - # - macos-latest - - windows-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node - uses: ./.github/actions/setup-node - - - name: Lint - run: pnpm run lint - - check: - name: Check - if: github.repository == 'vbenjs/vue-vben-admin' - runs-on: ${{ matrix.os }} - timeout-minutes: 20 - strategy: - matrix: - os: - - ubuntu-latest - # - macos-latest - - windows-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node - uses: ./.github/actions/setup-node - - - name: Typecheck - run: pnpm check:type - - # From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions - - name: Check workflow files - if: runner.os == 'Linux' - run: | - bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) - ./actionlint -color -shellcheck="" - - ci-ok: - name: CI OK - if: github.repository == 'vbenjs/vue-vben-admin' - runs-on: ubuntu-latest - needs: [test, check, lint] - env: - FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }} - steps: - - name: Check for failure - run: | - echo $FAILURE - if [ "$FAILURE" = "false" ]; then - exit 0 - else - exit 1 - fi diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 69a990f0543..00000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,94 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: 'CodeQL' - -on: - push: - branches: ['main'] - pull_request: - branches: ['main'] - schedule: - - cron: '35 0 * * 0' - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - if: github.repository == 'vbenjs/vue-vben-admin' - # Runner size impacts CodeQL analysis time. To learn more, please see: - # - https://gh.io/recommended-hardware-resources-for-running-codeql - # - https://gh.io/supported-runners-and-hardware-resources - # - https://gh.io/using-larger-runners (GitHub.com only) - # Consider using larger runners or machines with greater resources for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} - permissions: - # required for all workflows - security-events: write - - # required to fetch internal or private CodeQL packs - packages: read - - # only required for workflows in private repositories - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - include: - - language: javascript-typescript - build-mode: none - # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' - # Use `c-cpp` to analyze code written in C, C++ or both - # Use 'java-kotlin' to analyze code written in Java, Kotlin or both - # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both - # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, - # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. - # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how - # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - # If the analyze step fails for one of the languages you are analyzing with - # "We were unable to automatically build your code", modify the matrix above - # to set the build mode to "manual" for that language. Then modify this step - # to build your code. - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: '/language:${{matrix.language}}' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b4afdd93966..21c34c2f7d7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,4 +1,4 @@ -name: Deploy Website on push +name: deploy on: push: @@ -6,167 +6,121 @@ on: - main jobs: - deploy-playground-ftp: - name: Deploy Push Playground Ftp - if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin' + # push-to-ftp: + # if: "contains(github.event.head_commit.message, '[deploy]')" + # runs-on: ubuntu-latest + # steps: + # - name: Checkout + # uses: actions/checkout@v2 + + # - name: Sed Config Base + # shell: bash + # run: | + # sed -i 's#VITE_PUBLIC_PATH\s*=.*#VITE_PUBLIC_PATH = /next/#g' ./.env.production + # sed -i "s#VITE_BUILD_COMPRESS\s*=.*#VITE_BUILD_COMPRESS = 'gzip'#g" ./.env.production + # cat ./.env.production + + # - name: use Node.js 14 + # uses: actions/setup-node@v2.1.2 + # with: + # node-version: '14.x' + + # - name: Get yarn cache + # id: yarn-cache + # run: echo "::set-output name=dir::$(yarn cache dir)" + + # - name: Cache dependencies + # uses: actions/cache@v2 + # with: + # path: ${{ steps.yarn-cache.outputs.dir }} + # key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + # restore-keys: | + # ${{ runner.os }}-yarn- + + # - name: Build + # run: | + # yarn install + # yarn run build + + # - name: Deploy + # uses: SamKirkland/FTP-Deploy-Action@2.0.0 + # env: + # FTP_SERVER: ${{ secrets.FTP_SERVER }} + # FTP_USERNAME: ${{ secrets.FTP_USERNAME }} + # FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} + # METHOD: sftp + # PORT: ${{ secrets.FTP_PORT }} + # LOCAL_DIR: dist + # REMOTE_DIR: /srv/www/vben-admin + # ARGS: --delete --verbose --parallel=80 + + push-to-gh-pages: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 + - name: Checkout + uses: actions/checkout@v3 + + # - uses: NullVoxPopuli/action-setup-pnpm@v2 - name: Sed Config Base shell: bash run: | - sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./playground/.env.production - sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./playground/.env.production - cat ./playground/.env.production - - - name: Setup Node - uses: ./.github/actions/setup-node + sed -i "s#VITE_BUILD_COMPRESS\s*=.*#VITE_BUILD_COMPRESS = 'gzip'#g" ./.env.production + sed -i "s#VITE_DROP_CONSOLE\s*=.*#VITE_DROP_CONSOLE = true#g" ./.env.production + cat ./.env.production - - name: Build - run: pnpm build:play - - - name: Sync Playground files - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + - name: Install pnpm + uses: pnpm/action-setup@v2 with: - server: ${{ secrets.PRO_FTP_HOST }} - username: ${{ secrets.WEB_PLAYGROUND_FTP_ACCOUNT }} - password: ${{ secrets.WEB_PLAYGROUND_FTP_PWSSWORD }} - local-dir: ./playground/dist/ - - deploy-docs-ftp: - name: Deploy Push Docs Ftp - if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin' - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node - uses: ./.github/actions/setup-node - - - name: Build - run: pnpm build:docs + version: 9 + run_install: false - - name: Sync Docs files - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + - name: use Node.js 20 + uses: actions/setup-node@v3 with: - server: ${{ secrets.PRO_FTP_HOST }} - username: ${{ secrets.WEBSITE_FTP_ACCOUNT }} - password: ${{ secrets.WEBSITE_FTP_PASSWORD }} - local-dir: ./docs/.vitepress/dist/ - - deploy-antd-ftp: - name: Deploy Push Antd Ftp - if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin' - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Sed Config Base - shell: bash + node-version: '20.x' + + # - name: Get yarn cache directory path + # id: yarn-cache-dir-path + # run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + # + # - name: Cache dependencies + # uses: actions/cache@v3 + # with: + # path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + # key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + # restore-keys: | + # ${{ runner.os }}-yarn- + + - name: Set SSH Environment + env: + DOCS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} run: | - sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-antd/.env.production - sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-antd/.env.production - cat ./apps/web-antd/.env.production - - - name: Setup Node - uses: ./.github/actions/setup-node + mkdir -p ~/.ssh/ + echo "$ACTIONS_DEPLOY_KEY" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan github.com > ~/.ssh/known_hosts + chmod 700 ~/.ssh && chmod 600 ~/.ssh/* + git config --global user.email "vbenadmin@163.com" + git config --global user.name "vbenAdmin" - name: Build - run: pnpm run build:antd - - - name: Sync files - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 - with: - server: ${{ secrets.PRO_FTP_HOST }} - username: ${{ secrets.WEB_ANTD_FTP_ACCOUNT }} - password: ${{ secrets.WEB_ANTD_FTP_PASSWORD }} - local-dir: ./apps/web-antd/dist/ - - deploy-ele-ftp: - name: Deploy Push Element Ftp - if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin' - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Sed Config Base - shell: bash + env: + NODE_OPTIONS: '--max_old_space_size=4096' run: | - sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-ele/.env.production - sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-ele/.env.production - cat ./apps/web-ele/.env.production - - - name: Setup Node - uses: ./.github/actions/setup-node - - - name: Build - run: pnpm run build:ele - - - name: Sync files - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 - with: - server: ${{ secrets.PRO_FTP_HOST }} - username: ${{ secrets.WEB_ELE_FTP_ACCOUNT }} - password: ${{ secrets.WEB_ELE_FTP_PASSWORD }} - local-dir: ./apps/web-ele/dist/ - - deploy-naive-ftp: - name: Deploy Push Naive Ftp - if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin' - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 + pnpm install --no-frozen-lockfile + pnpm build + touch dist/.nojekyll + cp dist/index.html dist/404.html - - name: Sed Config Base - shell: bash + - name: Delete gh-pages branch run: | - sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-naive/.env.production - sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-naive/.env.production - cat ./apps/web-naive/.env.production - - - name: Setup Node - uses: ./.github/actions/setup-node + git push origin --delete gh-pages - - name: Build - run: pnpm run build:naive - - - name: Sync files - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + - name: Deploy + uses: peaceiris/actions-gh-pages@v3.9.0 with: - server: ${{ secrets.PRO_FTP_HOST }} - username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }} - password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }} - local-dir: ./apps/web-naive/dist/ - - rerun-on-failure: - name: Rerun on failure - needs: - - deploy-playground-ftp - - deploy-docs-ftp - - deploy-antd-ftp - - deploy-ele-ftp - - deploy-naive-ftp - if: failure() && fromJSON(github.run_attempt) < 10 - runs-on: ubuntu-latest - steps: - - name: Retry ${{ fromJSON(github.run_attempt) }} of 10 - env: - GH_REPO: ${{ github.repository }} - GH_TOKEN: ${{ github.token }} - run: gh workflow run rerun.yml -F run_id=${{ github.run_id }} + deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} + PUBLISH_BRANCH: gh-pages + PUBLISH_DIR: ./dist + CNAME: vben.vvbin.cn diff --git a/.github/workflows/draft.yml b/.github/workflows/draft.yml deleted file mode 100644 index 20b4148a7be..00000000000 --- a/.github/workflows/draft.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Release Drafter - -on: - push: - branches: - - main - -permissions: - contents: read - pull-requests: write - -jobs: - update_release_draft: - permissions: - # write permission is required to create a github release - contents: write - # write permission is required for autolabeler - # otherwise, read permission is required at least - pull-requests: write - if: github.repository == 'vbenjs/vue-vben-admin' - runs-on: ubuntu-latest - steps: - - uses: release-drafter/release-drafter@v6 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/issue-close-require.yml b/.github/workflows/issue-close-require.yml index 18c007bfded..a5fb0a40023 100644 --- a/.github/workflows/issue-close-require.yml +++ b/.github/workflows/issue-close-require.yml @@ -1,31 +1,17 @@ -# 每天零点运行一次,它会检查所有带有 "need reproduction" 标签的 Issues。如果这些 Issues 在过去的 3 天内没有任何活动,它们将会被自动关闭。这有助于保持 Issue 列表的整洁,并且提醒用户在必要时提供更多的信息。 name: Issue Close Require -# 触发条件:每天零点 on: - workflow_dispatch: schedule: - cron: '0 0 * * *' -permissions: - pull-requests: write - contents: write - issues: write - jobs: close-issues: - if: github.repository == 'vbenjs/vue-vben-admin' runs-on: ubuntu-latest steps: - # 关闭未活动的 Issues - - name: Close Inactive Issues - uses: actions/stale@v9 + - name: need reproduction + uses: actions-cool/issues-helper@v2.1.1 with: - days-before-stale: -1 # Issues and PR will never be flagged stale automatically. - stale-issue-label: needs-reproduction # Label that flags an issue as stale. - only-labels: needs-reproduction # Only process these issues - days-before-issue-close: 3 - ignore-updates: true - remove-stale-when-updated: false - close-issue-message: This issue was closed because it was open for 3 days without a valid reproduction. - close-issue-label: closed-by-action + actions: 'close-issues' + token: ${{ secrets.OPER_TOKEN }} + labels: 'need reproduction' + inactive-day: 3 diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index 2feda4ef77a..43a7a624a40 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -1,46 +1,29 @@ -name: Label Based Actions +name: Issue Labeled on: issues: types: [labeled] - # pull_request: - # types: [labeled] - -permissions: - issues: write - pull-requests: write - contents: write jobs: reply-labeled: - if: github.repository == 'vbenjs/vue-vben-admin' runs-on: ubuntu-latest steps: - - name: remove enhancement pending - if: github.event.label.name == 'enhancement' - uses: actions-cool/issues-helper@v3 - with: - actions: 'remove-labels' - token: ${{ secrets.GITHUB_TOKEN }} - issue-number: ${{ github.event.issue.number }} - labels: 'enhancement: pending triage' - - - name: remove bug pending - if: github.event.label.name == 'bug' - uses: actions-cool/issues-helper@v3 + - name: remove pending + if: github.event.label.name == 'enhancement' || github.event.label.name == 'bug' + uses: actions-cool/issues-helper@v2.1.1 with: actions: 'remove-labels' - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.OPER_TOKEN }} issue-number: ${{ github.event.issue.number }} labels: 'bug: pending triage' - - name: needs reproduction - if: github.event.label.name == 'needs reproduction' - uses: actions-cool/issues-helper@v3 + - name: need reproduction + if: github.event.label.name == 'need reproduction' + uses: actions-cool/issues-helper@v2.1.1 with: actions: 'create-comment, remove-labels' - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.OPER_TOKEN }} issue-number: ${{ github.event.issue.number }} body: | - Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `needs reproduction` will be closed if no activities in 3 days. + Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `need reproduction` will be closed if no activities in 3 days. labels: 'bug: pending triage' diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 00000000000..1ef4e97272d --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,39 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: Node.js CI + +on: + push: + branches: [main, thin] + pull_request: + branches: [main, thin] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Install pnpm + run: npm install -g pnpm + + - name: Install dependencies + run: pnpm install + + - name: Run type:check + run: pnpm run type:check + + - name: Build + run: pnpm build diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml deleted file mode 100644 index bd73d5331de..00000000000 --- a/.github/workflows/lock.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Lock Threads - -on: - schedule: - - cron: '0 0 * * *' - workflow_dispatch: - -permissions: - issues: write - pull-requests: write - -jobs: - action: - if: github.repository == 'vbenjs/vue-vben-admin' - runs-on: ubuntu-latest - steps: - - uses: dessant/lock-threads@v5 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - issue-inactive-days: '14' - issue-lock-reason: '' - pr-inactive-days: '30' - pr-lock-reason: '' - process-only: 'issues, prs' diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 7f524f908f4..51379aab178 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -3,78 +3,20 @@ name: Create Release Tag on: push: tags: - - 'v*.*.*' # Push events to matching v*, i.e. v1.0, v20.15.10 - -env: - HUSKY: '0' - -permissions: - pull-requests: write - contents: write + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 jobs: build: name: Create Release - if: github.repository == 'vbenjs/vue-vben-admin' runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20] steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - - # - name: Checkout code - # uses: actions/checkout@v4 - # with: - # fetch-depth: 0 - - # - name: Install pnpm - # uses: pnpm/action-setup@v4 - - # - name: Use Node.js ${{ matrix.node-version }} - # uses: actions/setup-node@v4 - # with: - # node-version: ${{ matrix.node-version }} - # cache: "pnpm" - # - name: Install dependencies - # run: pnpm install --frozen-lockfile - - # - name: Test and Build - # run: | - # pnpm run test - # pnpm run build - - - name: version - id: version - run: | - tag=${GITHUB_REF/refs\/tags\//} - version=${tag#v} - major=${version%%.*} - echo "tag=${tag}" >> $GITHUB_OUTPUT - echo "version=${version}" >> $GITHUB_OUTPUT - echo "major=${major}" >> $GITHUB_OUTPUT - - - uses: release-drafter/release-drafter@v6 + - name: Create Release for Tag + id: release_tag + uses: ncipollo/release-action@v1 with: - version: ${{ steps.version.outputs.version }} - publish: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # - name: force update major tag - # run: | - # git tag v${{ steps.version.outputs.major }} ${{ steps.version.outputs.tag }} -f - # git push origin refs/tags/v${{ steps.version.outputs.major }} -f - - # - name: Create Release for Tag - # id: release_tag - # uses: ncipollo/release-action@v1 - # with: - # token: ${{ secrets.GITHUB_TOKEN }} - # generateReleaseNotes: "true" - # body: | - # > Please refer to [CHANGELOG.md](https://github.com/vbenjs/vue-vben-admin/blob/main/CHANGELOG.md) for details. + generateReleaseNotes: 'true' + body: | + > Please refer to [CHANGELOG.md](https://github.com/anncwb/vue-vben-admin/blob/main/CHANGELOG.md) for details. diff --git a/.github/workflows/rerun.yml b/.github/workflows/rerun.yml deleted file mode 100644 index 2b46255141c..00000000000 --- a/.github/workflows/rerun.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Rerun workflow - -on: - workflow_dispatch: - inputs: - run_id: - description: The workflow id to relanch - required: true -jobs: - rerun: - runs-on: ubuntu-latest - steps: - - name: rerun ${{ inputs.run_id }} - env: - GH_REPO: ${{ github.repository }} - GH_TOKEN: ${{ github.token }} - run: | - gh run watch ${{ inputs.run_id }} > /dev/null 2>&1 - gh run rerun ${{ inputs.run_id }} --failed diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml deleted file mode 100644 index db8e9e091ea..00000000000 --- a/.github/workflows/semantic-pull-request.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Semantic Pull Request - -on: - pull_request_target: - types: - - opened - - edited - - synchronize - -jobs: - main: - name: Semantic Pull Request - if: github.repository == 'vbenjs/vue-vben-admin' - runs-on: ubuntu-latest - steps: - - name: Validate PR title - uses: amannn/action-semantic-pull-request@v5 - with: - wip: true - subjectPattern: ^(?![A-Z]).+$ - subjectPatternError: | - The subject "{subject}" found in the pull request title "{title}" - didn't match the configured pattern. Please ensure that the subject - doesn't start with an uppercase character. - requireScope: false - types: | - fix - feat - docs - style - refactor - perf - test - build - ci - chore - revert - types - release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 991e1331dfa..9c2318c8da0 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,16 +2,15 @@ name: 'Close stale issues' on: schedule: - - cron: '0 1 * * *' + - cron: '30 1 * * *' jobs: stale: - if: github.repository == 'vbenjs/vue-vben-admin' runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v3 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} + repo-token: ${{ secrets.OPER_TOKEN }} stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days' stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days' exempt-issue-labels: 'bug,enhancement' diff --git a/.gitignore b/.gitignore index 3399f39c0aa..aa15905c794 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,23 @@ node_modules .DS_Store dist -dist-ssr -dist.zip -dist.tar -dist.war -.nitro -.output -*-dist.zip -*-dist.tar -*-dist.war -coverage -*.local -**/.vitepress/cache .cache .turbo -.temp -dev-dist -.stylelintcache -yarn.lock -package-lock.json -.VSCodeCounter -**/backend-mock/data +tests/server/static +tests/server/static/upload + +.local # local env files .env.local .env.*.local .eslintcache -logs -*.log +# Log files npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* -lerna-debug.log* -vite.config.mts.* -vite.config.mjs.* -vite.config.js.* -vite.config.ts.* # Editor directories and files .idea @@ -48,5 +27,8 @@ vite.config.ts.* *.njsproj *.sln *.sw? + +package-lock.json +pnpm-lock.yaml + .history -.cursor diff --git a/.gitpod.yml b/.gitpod.yml index 5fda2cf70ae..866381fccdc 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,6 +1,6 @@ ports: - - port: 5555 + - port: 3344 onOpen: open-preview tasks: - - init: npm i -g corepack && pnpm install - command: pnpm run dev:play + - init: pnpm install + command: pnpm run dev diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 00000000000..274d2d8ba91 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,8 @@ +#!/bin/sh + +# shellcheck source=./_/husky.sh +. "$(dirname "$0")/_/husky.sh" + +PATH="/usr/local/bin:$PATH" + +npx --no-install commitlint --edit "$1" diff --git a/.husky/common.sh b/.husky/common.sh new file mode 100644 index 00000000000..9d5129bd726 --- /dev/null +++ b/.husky/common.sh @@ -0,0 +1,9 @@ +#!/bin/sh +command_exists () { + command -v "$1" >/dev/null 2>&1 +} + +# Workaround for Windows 10, Git Bash and Yarn +if command_exists winpty && test -t 1; then + exec < /dev/tty +fi diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000000..53685f44582 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,10 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" +. "$(dirname "$0")/common.sh" + +[ -n "$CI" ] && exit 0 + +PATH="/usr/local/bin:$PATH" + +# Format and submit code according to lintstagedrc.js configuration +pnpm exec lint-staged diff --git a/.node-version b/.node-version deleted file mode 100644 index ee5c2446981..00000000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ -22.1.0 diff --git a/.npmrc b/.npmrc index 21147aff25f..6ef3860d3ce 100644 --- a/.npmrc +++ b/.npmrc @@ -1,13 +1,8 @@ -registry = "https://registry.npmmirror.com/" -public-hoist-pattern[]=lefthook -public-hoist-pattern[]=eslint -public-hoist-pattern[]=prettier -public-hoist-pattern[]=prettier-plugin-tailwindcss -public-hoist-pattern[]=stylelint -public-hoist-pattern[]=*postcss* -public-hoist-pattern[]=@commitlint/* -public-hoist-pattern[]=czg - -strict-peer-dependencies=false -auto-install-peers=true -dedupe-peer-dependents=true +public-hoist-pattern[]=husky +public-hoist-pattern[]=*eslint* +public-hoist-pattern[]=*prettier* +public-hoist-pattern[]=lint-staged +public-hoist-pattern[]=*stylelint* +public-hoist-pattern[]=@commitlint/cli +public-hoist-pattern[]=@vben/eslint-config +package-manager-strict=false diff --git a/.prettierignore b/.prettierignore index d0b0ca13348..24531e69ffc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,18 +1,12 @@ dist -dev-dist .local .output.js node_modules -.nvmrc -coverage -CODEOWNERS -.nitro -.output - **/*.svg **/*.sh public .npmrc + *-lock.yaml diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 00000000000..4a24e88c0fb --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,19 @@ +module.exports = { + printWidth: 100, + semi: true, + vueIndentScriptAndStyle: true, + singleQuote: true, + trailingComma: 'all', + proseWrap: 'never', + htmlWhitespaceSensitivity: 'strict', + endOfLine: 'auto', + plugins: ['prettier-plugin-packagejson'], + overrides: [ + { + files: '.*rc', + options: { + parser: 'json', + }, + }, + ], +}; diff --git a/.prettierrc.mjs b/.prettierrc.mjs deleted file mode 100644 index 3e25d2cfa97..00000000000 --- a/.prettierrc.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/prettier-config'; diff --git a/.stylelintignore b/.stylelintignore index f4b2db2c16f..6cd69e0bf9b 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,4 +1,2 @@ dist public -__tests__ -coverage diff --git a/.stylelintrc.cjs b/.stylelintrc.cjs new file mode 100644 index 00000000000..65320e774e9 --- /dev/null +++ b/.stylelintrc.cjs @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ['@vben/stylelint-config'], +}; diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e8dc9ed9bd6..12dc9f2f79f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,30 +1,14 @@ { "recommendations": [ - // Vue 3 的语言支持 - "Vue.volar", - // 将 ESLint JavaScript 集成到 VS Code 中。 + "vue.volar", "dbaeumer.vscode-eslint", - // Visual Studio Code 的官方 Stylelint 扩展 "stylelint.vscode-stylelint", - // 使用 Prettier 的代码格式化程序 "esbenp.prettier-vscode", - // 支持 dotenv 文件语法 - "mikestead.dotenv", - // 源代码的拼写检查器 - "streetsidesoftware.code-spell-checker", - // Tailwind CSS 的官方 VS Code 插件 - "bradlc.vscode-tailwindcss", - // iconify 图标插件 + "mrmlnc.vscode-less", + "lokalise.i18n-ally", "antfu.iconify", - // i18n 插件 - "Lokalise.i18n-ally", - // CSS 变量提示 - "vunguyentuan.vscode-css-variables", - // 在 package.json 中显示 PNPM catalog 的版本 - "antfu.pnpm-catalog-lens" - ], - "unwantedRecommendations": [ - // 和 volar 冲突 - "octref.vetur" + "antfu.unocss", + "mikestead.dotenv", + "warmthsea.vscode-custom-code-color" ] } diff --git a/.vscode/global.code-snippets b/.vscode/global.code-snippets deleted file mode 100644 index 7604b014887..00000000000 --- a/.vscode/global.code-snippets +++ /dev/null @@ -1,37 +0,0 @@ -{ - "import": { - "scope": "javascript,typescript", - "prefix": "im", - "body": ["import { $2 } from '$1';"], - "description": "Import a module", - }, - "export-all": { - "scope": "javascript,typescript", - "prefix": "ex", - "body": ["export * from '$1';"], - "description": "Export a module", - }, - "vue-script-setup": { - "scope": "vue", - "prefix": "", - "const props = defineProps<{", - " modelValue?: boolean,", - "}>()", - "$1", - "", - "", - "", - ], - }, - "vue-computed": { - "scope": "javascript,typescript,vue", - "prefix": "com", - "body": ["computed(() => { $1 })"], - }, -} diff --git a/.vscode/launch.json b/.vscode/launch.json index e9673304486..72e95d0d6a8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,42 +1,13 @@ { - "$schema": "https://json.schemastore.org/launchsettings.json", "version": "0.2.0", "configurations": [ { "type": "chrome", - "name": "vben admin playground dev", "request": "launch", - "url": "http://localhost:5555/", - "env": { "NODE_ENV": "development" }, - "sourceMaps": true, - "webRoot": "${workspaceFolder}/playground" - }, - { - "type": "chrome", - "name": "vben admin antd dev", - "request": "launch", - "url": "http://localhost:5666/", - "env": { "NODE_ENV": "development" }, - "sourceMaps": true, - "webRoot": "${workspaceFolder}/apps/web-antd" - }, - { - "type": "chrome", - "name": "vben admin ele dev", - "request": "launch", - "url": "http://localhost:5777/", - "env": { "NODE_ENV": "development" }, - "sourceMaps": true, - "webRoot": "${workspaceFolder}/apps/web-ele" - }, - { - "type": "chrome", - "name": "vben admin naive dev", - "request": "launch", - "url": "http://localhost:5888/", - "env": { "NODE_ENV": "development" }, - "sourceMaps": true, - "webRoot": "${workspaceFolder}/apps/web-naive" + "name": "Launch Chrome", + "url": "http://localhost:3100/", + "webRoot": "${workspaceFolder}/src", + "sourceMaps": true } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index f38c4278142..70adb5a165c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,106 +1,48 @@ { - "tailwindCSS.experimental.configFile": "internal/tailwind-config/src/index.ts", - // workbench - "workbench.list.smoothScrolling": true, - "workbench.startupEditor": "newUntitledFile", - "workbench.tree.indent": 10, - "workbench.editor.highlightModifiedTabs": true, - "workbench.editor.closeOnFileDelete": true, - "workbench.editor.limit.enabled": true, - "workbench.editor.limit.perEditorGroup": true, - "workbench.editor.limit.value": 5, - - // editor + "typescript.tsdk": "./node_modules/typescript/lib", + "volar.tsPlugin": true, + "volar.tsPluginStatus": false, + "npm.packageManager": "pnpm", "editor.tabSize": 2, - "editor.detectIndentation": false, - "editor.cursorBlinking": "expand", - "editor.largeFileOptimizations": true, - "editor.accessibilitySupport": "off", - "editor.cursorSmoothCaretAnimation": "on", - "editor.guides.bracketPairs": "active", - "editor.inlineSuggest.enabled": true, - "editor.suggestSelection": "recentlyUsedByPrefix", - "editor.acceptSuggestionOnEnter": "smart", - "editor.suggest.snippetsPreventQuickSuggestions": false, - "editor.stickyScroll.enabled": true, - "editor.hover.sticky": true, - "editor.suggest.insertMode": "replace", - "editor.bracketPairColorization.enabled": true, - "editor.autoClosingBrackets": "beforeWhitespace", - "editor.autoClosingDelete": "always", - "editor.autoClosingOvertype": "always", - "editor.autoClosingQuotes": "beforeWhitespace", - "editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?", - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit", - "source.fixAll.stylelint": "explicit", - "source.organizeImports": "never" - }, "editor.defaultFormatter": "esbenp.prettier-vscode", - "[html]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[css]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[scss]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[json]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[markdown]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[jsonc]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[vue]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - // extensions - "extensions.ignoreRecommendations": true, - - // terminal - "terminal.integrated.cursorBlinking": true, - "terminal.integrated.persistentSessionReviveProcess": "never", - "terminal.integrated.tabs.enabled": true, - "terminal.integrated.scrollback": 10000, - "terminal.integrated.stickyScroll.enabled": true, - - // files "files.eol": "\n", - "files.insertFinalNewline": true, - "files.simpleDialog.enable": true, - "files.associations": { - "*.ejs": "html", - "*.art": "html", - "**/tsconfig.json": "jsonc", - "*.json": "jsonc", - "package.json": "json" + "search.exclude": { + "**/node_modules": true, + "**/*.log": true, + "**/*.log*": true, + "**/bower_components": true, + "**/dist": true, + "**/elehukouben": true, + "**/.git": true, + "**/.gitignore": true, + "**/.svn": true, + "**/.DS_Store": true, + "**/.idea": true, + "**/.vscode": false, + "**/yarn.lock": true, + "**/tmp": true, + "out": true, + "dist": true, + "node_modules": true, + "CHANGELOG.md": true, + "examples": true, + "res": true, + "screenshots": true, + "yarn-error.log": true, + "**/.yarn": true }, - "files.exclude": { + "**/.cache": true, + "**/.editorconfig": true, "**/.eslintcache": true, "**/bower_components": true, - "**/.turbo": true, "**/.idea": true, - "**/.vitepress": true, "**/tmp": true, "**/.git": true, "**/.svn": true, "**/.hg": true, "**/CVS": true, - "**/.stylelintcache": true, - "**/.DS_Store": true, - "**/vite.config.mts.*": true, - "**/tea.yaml": true + "**/.DS_Store": true }, "files.watcherExclude": { "**/.git/objects/**": true, @@ -112,130 +54,123 @@ "**/dist/**": true, "**/yarn.lock": true }, - - "typescript.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"], - - // search - "search.searchEditor.singleClickBehaviour": "peekDefinition", - "search.followSymlinks": false, - // 在使用搜索功能时,将这些文件夹/文件排除在外 - "search.exclude": { - "**/node_modules": true, - "**/*.log": true, - "**/*.log*": true, - "**/bower_components": true, - "**/dist": true, - "**/elehukouben": true, - "**/.git": true, - "**/.github": true, - "**/.gitignore": true, - "**/.svn": true, - "**/.DS_Store": true, - "**/.vitepress/cache": true, - "**/.idea": true, - "**/.vscode": false, - "**/.yarn": true, - "**/tmp": true, - "*.xml": true, - "out": true, - "dist": true, - "node_modules": true, - "CHANGELOG.md": true, - "**/pnpm-lock.yaml": true, - "**/yarn.lock": true - }, - - "debug.onTaskErrors": "debugAnyway", - "diffEditor.ignoreTrimWhitespace": false, - "npm.packageManager": "pnpm", - - "css.validate": false, - "less.validate": false, - "scss.validate": false, - - // extension - "emmet.showSuggestionsAsSnippets": true, - "emmet.triggerExpansionOnTab": false, - - "errorLens.enabledDiagnosticLevels": ["warning", "error"], - "errorLens.excludeBySource": ["cSpell", "Grammarly", "eslint"], - "stylelint.enable": true, - "stylelint.packageManager": "pnpm", - "stylelint.validate": ["css", "less", "postcss", "scss", "vue"], - "stylelint.customSyntax": "postcss-html", - "stylelint.snippet": ["css", "less", "postcss", "scss", "vue"], - - "typescript.inlayHints.enumMemberValues.enabled": true, - "typescript.preferences.preferTypeOnlyAutoImports": true, - "typescript.preferences.includePackageJsonAutoImports": "on", - - "eslint.validate": [ - "javascript", - "typescript", - "javascriptreact", - "typescriptreact", - "vue", - "html", - "markdown", - "json", - "jsonc", - "json5" - ], - - "tailwindCSS.experimental.classRegex": [ - ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] - ], - - "github.copilot.enable": { - "*": true, - "markdown": true, - "plaintext": false, - "yaml": false + "stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"], + "path-intellisense.mappings": { + "@/": "${workspaceRoot}/src" }, - - "cssVariables.lookupFiles": ["packages/core/base/design/src/**/*.css"], - - "i18n-ally.localesPaths": [ - "packages/locales/src/langs", - "playground/src/locales/langs", - "apps/*/src/locales/langs" - ], - "i18n-ally.pathMatcher": "{locale}/{namespace}.{ext}", - "i18n-ally.enabledParsers": ["json"], - "i18n-ally.sourceLanguage": "en", - "i18n-ally.displayLanguage": "zh-CN", - "i18n-ally.enabledFrameworks": ["vue", "react"], + "[javascriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[html]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[less]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[scss]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.fixAll.stylelint": "explicit" + }, + "[vue]": { + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.fixAll.stylelint": "explicit" + }, + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "i18n-ally.localesPaths": ["src/locales/lang"], "i18n-ally.keystyle": "nested", "i18n-ally.sortKeys": true, "i18n-ally.namespace": true, - + "i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}", + "i18n-ally.enabledParsers": ["json", "ts", "js"], + "i18n-ally.sourceLanguage": "en", + "i18n-ally.displayLanguage": "zh-CN", + "i18n-ally.enabledFrameworks": ["vue", "react"], + "cSpell.words": [ + "antd", + "antv", + "brotli", + "browserslist", + "codemirror", + "commitlint", + "cropperjs", + "echarts", + "esnext", + "esno", + "iconify", + "INTLIFY", + "lintstagedrc", + "logicflow", + "mockjs", + "nprogress", + "pinia", + "pnpm", + "qrcode", + "sider", + "sortablejs", + "stylelint", + "tailwindcss", + "tinymce", + "unocss", + "unref", + "vben", + "vditor", + "Vite", + "vitejs", + "vueuse", + "zxcvbn" + ], + "vetur.format.scriptInitialIndent": true, + "vetur.format.styleInitialIndent": true, + "vetur.validation.script": false, + "MicroPython.executeButton": [ + { + "text": "▶", + "tooltip": "运行", + "alignment": "left", + "command": "extension.executeFile", + "priority": 3.5 + } + ], + "MicroPython.syncButton": [ + { + "text": "$(sync)", + "tooltip": "同步", + "alignment": "left", + "command": "extension.execute", + "priority": 4 + } + ], // 控制相关文件嵌套展示 "explorer.fileNesting.enabled": true, "explorer.fileNesting.expand": false, "explorer.fileNesting.patterns": { - "*.ts": "$(capture).test.ts, $(capture).test.tsx, $(capture).spec.ts, $(capture).spec.tsx, $(capture).d.ts", - "*.tsx": "$(capture).test.ts, $(capture).test.tsx, $(capture).spec.ts, $(capture).spec.tsx,$(capture).d.ts", + "*.ts": "$(capture).test.ts, $(capture).test.tsx", + "*.tsx": "$(capture).test.ts, $(capture).test.tsx", "*.env": "$(capture).env.*", - "README.md": "README*,CHANGELOG*,LICENSE,CNAME", - "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json", - "eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml", - "tailwind.config.mjs": "postcss.*" + "CHANGELOG.md": "CHANGELOG*", + "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,README*,.npmrc,.browserslistrc", + ".eslintrc.cjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,.stylelintrc.*" }, - "commentTranslate.hover.enabled": false, - "commentTranslate.multiLineMerge": true, - "vue.server.hybridMode": true, - "typescript.tsdk": "node_modules/typescript/lib", - "oxc.enable": false, - "cSpell.words": [ - "archiver", - "axios", - "dotenv", - "isequal", - "jspm", - "napi", - "nolebase", - "rollup", - "vitest" - ] + "terminal.integrated.scrollback": 10000, + "nuxt.isNuxtApp": false, + "vscodeCustomCodeColor.highlightValue": "v-auth", + "vscodeCustomCodeColor.highlightValueColor": "#6366f1" } diff --git a/CHANGELOG.en_US.md b/CHANGELOG.en_US.md new file mode 100644 index 00000000000..0e0d903fb3a --- /dev/null +++ b/CHANGELOG.en_US.md @@ -0,0 +1,1262 @@ +## 2.8.0(2021-11.03) + +### Upgrade Instructions + +- Package manager changed from `yarn` to `pnpm` +- Delete `node_modules` and `yarn.lock`, install `pnpm` globally +- Execute `pnpm install` + +### ✨ Features + +- **Others** + - The `VITE_PROXY` configuration in the `.env` file supports single quotes + - Remove warnings during build + +### 🐛 Bug Fixes + +- **BasicTable** + - Fix the issue that editable cells cannot be submitted in some cases + - Fix the problem that the `inset` attribute does not work + - Fix the problem that the performance of `useTable` and `reload` method `await` of `BasicTable` instance are inconsistent + - Fix the issue that `clickToRowSelect` would ignore the disabled state of the row selection box + - Fix the problem that the page of `BasicTable` will be reset in some cases + - Modify the `deleteTableDataRecord` method +- **BasicModal** + - Fixed the problem that `Modal` could not be closed even when clicking on the mask and pressing the `Esc` key + - Fixed the issue that clicking the close button and the blank area next to the maximize button would also cause `Modal` to close +- **BasicTree** Fix the problem that the node slot does not work +- **CodeEditor** Fix the problem that may cause `Build` failure +- **BasicForm** Fix the problem that the content width of the custom FormItem component may be out of range +- **ApiTreeSelect** Fix the problem that the change of `params` failed to trigger the re-request of api data +- **Others** -Fixed an issue where multiple tabs would not jump to routing when closing tabs in some cases + - Fix the issue that some components may cause abnormal hot update + - Fix the problem that some sub-components of `antdv` will be reported in the build process when directly `import` part of the `antdv`, such as: TabPane, RadioGroup + +## 2.7.2(2021-09-14) + +### ✨ Features + +- **BasicForm** New `Divider` in the form component for dividing the area of longer forms +- **BasicTable** + - Cell editor adds submit callback, which will decide whether to submit data to the form based on the result returned by the callback function + - Add check method for row editing, allowing only check but not submit value, so asynchronously save data successfully before submit to table + - Fix the problem that the `rowClassName` property cannot be used at the same time as `striped`. +- New component **MarkdownViewer** for displaying rich text in Markdown format + +### 🐛 Bug Fixes + +- **CodeEditor** Fix JSON editor throwing exception when formatting invalid JSON text +- **Tinymce** fixes an issue where inline mode throws an exception in some scenarios +- **BasicTable** + - Repair the problem that the editing icon is not displayed when the content of editable cell is empty + - Repair the problem that the total row at the end of the table sometimes fails to align with the columns in the main part of the table. +- **MarkDown** Repair the problem that the value of initial value property does not work. +- **BasicUpload** Repair the problem that `accept` property does not support `MIME` and suffix name starting with dot. +- **ApiSelect** Fix the problem of type definition of `value` property. +- **Other** + - Repair the problem that some wrapper components give error when using slots. + - Repair the problem that `theme` parameter of `useECharts` does not work. + - Repair the problem that when `Token` is invalid, pressing F5 to refresh the page may cause abnormal page loading. + - Repair the problem that the improper call of `useRedo` may lead to `path` redirection abnormality. + - Repair the problem that `vite` custom mode name does not support underscore. + +## 2.7.1(2021-08-16) + +- Upgrade vue 3.2, if the operation fails, delete node_modules and reinstall it + +### ✨ Features + +- **BasicTree** Add search function related properties and methods +- **BasicForm** added `alwaysShowLines` to set the number of lines kept displayed when folding + +### 🐛 Bug Fixes + +- **Cropper** Fix the problem of failure to destroy in time +- **BasicTable** + - Fix the problem that `CellFormat` cannot use `Map` type data + - Fixed an issue where the editable cell failed to display the `0` value correctly + - Fixed the issue that selection-change event failed to trigger correctly when unchecked + - Fix the problem that the background color of the full screen state under the light theme is incorrect + - Fix the problem of obtaining complete data when `getSelectRows` does not support remote data cross-page selection + - Fix the issue that the `size` property provided for editing components in `editComponentProps` is invalid +- **Qrcode** Fixed the problem that the QR code component could not be drawn in time when it was created +- **BasicModal** Fix the problem that the `helpMessage` property does not work +- **BasicButton** Fix the problem that the button style performance is inconsistent with the official antd +- **Others** Fix the problem that `useRedo` (reload the current route) will lose route `params` data + +## 2.7.0(2021-08-03) + +## (Breaking changes) Breaking changes + +- Restore the project `tailwindcss` back to `windicss`, tried `tailwindcss`, there may be a lot of problems, first switch back to `windicss` to improve development efficiency and lower switching costs. + - There are currently incompatible areas of the project + - The wording of `xl:!m-4` needs to be changed to `!xl:m-4`, note that only `!` is incompatible. If you don’t use it, you don’t need to change it. + - The memory overflow problem may still exist (low frequency, just restart, restart vite faster) + +### ✨ Features + +- **Preview** Add new properties and events +- **Dark Theme** added support for tailwindcss night mode +- **Others** add setTip method for useLoading + +### 🐛 Bug Fixes + +- **ApiTreeSelect** Fixed the problem of failing to monitor `params` changes correctly +- **ImgRotateDragVerify** Fix the problem that the component `resume` method cannot be called +- **TableAction** Fix the problem that the stopButtonPropagation property does not work in some cases +- **PageWrapper** Fix the problem of invalid `class` attribute +- **BasicTree** Fix the problem that the `checkAll` method will affect the `disabled` state node +- **BasicTable** + - Fix the issue that editable cells do not support `ellipsis` configuration + - Fixed the problem that the pop-up layer of sub-components (popconfirm and edit components such as select and treeSelect) cannot be seen in full-screen mode + - Fixed an issue where when `expandRowByClick` is enabled, clicking non-expandable rows may cause style errors + - Fix the problem that the dynamic change of `pagination` property does not take effect + - Fix the problem that `getSelectRows` does not support the child data of the tree table -**Dark Theme** Fix the color matching problem under the dark theme + - Fix the background color of the selected node of the `Tree` component + - Fix the color configuration of the `Alert` component + - Fix the problem of the button color of `link` type in the disabled state + - Fix the style problem of checked checkboxes in `Tree` -**Others** Fix the problem that useScript failed to automatically remove the script node + +## 2.6.1(2021-07-19) + +### ✨ Features + +- **NoticeList** Add pagination, auto omit for overlength, title click event, title strikethrough, etc. +- **MixSider** Optimize the style of the bottom collapse button in the Mix menu layout to be consistent with the style of other menu layouts +- **ApiTreeSelect** Extend `TreeSelect` component of `antdv` to support remote data source, similar to `ApiSelect`. +- **BasicTable** New `ApiTreeSelect` editing component +- Different backend home pages can be specified for different users. + - Add `homePath` field (optional) to the user information returned by the `getUserInfo` interface to customize the home page path for the current user + +### 🐛 Bug Fixes + +- **BasicTable** + - Fix scrollbar style issue (removed scroll style patch) + - Fix the alignment problem of cells with expanded icons in tree tables + - Add `headerTop` slot. + - Fix the color display of the operation column button in disabled state. + - Repair the problem that the values of editable cells cannot be updated by modifying `dataSource` directly. + - Repair the problem of data replay when using `ApiSelect` to edit components. + - Repair the problem that editing components may report `onXXX` type error in some scenarios. +- **TableAction** + - Create Tooltip component only if `action.tooltip` exists. + - Fix the problem that the content of the round button inside the component is not centered +- **AppSearch** Fix the problem that the hidden menu may be searched. +- **BasicUpload** Repair the problem of error when handling non-`array` values. +- **Form** Repair the `suffix` slot style problem of `FormItem`. +- **Menu** + - Repair the hovering trigger logic of the left mixed menu + - Repair the problem that the top bar menu is wrong when displaying menu items that need to be hidden. + - Fix the left mixed menu in hover trigger mode will jump to route directly when there is no submenu and it is activated +- **Breadcrumb** Repair the problem that the menu with redirection cannot be jumped when clicked +- **Markdown** fixes an initialization exception and an issue where value was not set dynamically correctly +- **Modal** Make sure props are passed correctly +- **MultipleTab** fixes an issue that could accidentally create login route tabs +- **BasicTree** Fix the problem that the search function may cause `checkedKeys` to be lost +- **CodeEditor** Fix the problem that value does not support v-model usage. +- **CountdownInput** Fix the problem that `input` slot is not supported. +- **ApiSelect** Fix the problem that the `options-change` event parameter is not the standard `options` data used by `select +- **Other** + - Fix the problem that the configuration of default menu collapse does not work + - Repair the problem that `safari` browser reports an error and the website cannot be opened. + - Repair the problem that eslint keeps error due to endOfLine after pulling the code on window. + - Fix `Vue Router warn` caused by dynamic routing + +### 🎫 Chores + +- Add test environment test command + +## 2.6.0(2021-07-04) + +### ✨ Features + +- **Axios** New `withToken` configuration to control whether the request carries a token or not +- **BasicUpload** + - New `preview-delete` event triggered when deleting a file in preview `Modal`. + - `value` supports `v-model` usage +- **Route configuration** + - Add `ignoreRoute` to generate menu only in `ROUTE_MAPPING` or `BACK` permission mode + - Add `hidePathForChildren` configuration to ignore this level `path` when generating menus for child items +- **TableAction** Add `tooltip` configuration to add tooltip hint for button +- **CropperAvatar** + - Added `value` to set the current avatar + - Added `onChange` to accept avatar cropping and upload success event + - New `btnText`, `btnProps` for customizing the text and properties of the upload button + - Add tooltips to the action buttons in `Modal` for cropping +- **Modal** Add tooltip for action button in top right corner + +### 🐛 Bug Fixes + +- **Modal** + - Fix the problem that the mask cannot be closed by clicking on it. + - Fix `setModalProps` does not support setting `defaultFullscreen`. +- **Table** + - Fix the problem that `editComponentProps` doesn't support `onChange`. + - Fix the problem that `selection-change` event is not triggered when `clickToRowSelect` is enabled. + - Fix the problem that global configuration `fetchSetting` may be accidentally modified by local configuration. + - Fix the problem that the parameter of `handleSearchInfoFn` contains redundant blank keys. + - Repair the problem that when rowSelection.onChange is provided for table, the selected items of table cannot be changed manually. + - Fix the problem that the scrollbar continues to be displayed even when it is not needed to be displayed. +- **Icon** Repair the problem that SvgIcon is missing some styles. +- **Menu** + - Repair the problem that single-level menu refreshing will not be activated in route mapping mode. + - Repair the problem that the collapse customization at the bottom of the side menu is invalid. +- **Form** Repair the type definition of `submitButtonOptions` and `resetButtonOptions`. +- **PopConfirmButton** Remove the redundant `title` on `Button`. +- **Axios** Fix the problem that `params` and `data` data cannot be submitted at the same time when non-`GET` requests are made +- **Other** + - Repair the problem that the lock screen function can skip the lock state by refreshing the page or copying the URL to open a new browser tab + - Repair the problem that `Token` won't be synchronized when multiple windows open pages at the same time. + - Repair the problem that `hasPermission` does not work in `ROLE` permission mode. +- **Table** Repair the problem that the parameter of `handleSearchInfoFn` contains extra blank keys. +- **Tailwindcss** Remove console warning + +## 2.5.2(2021-06-27) + +### ⚡ Performance Improvements + +- **Icon** Remove the global registration of Icon components to prevent hot update issues under certain circumstances + +### ✨ Features + +- **Menu** Added `permissionMode=PermissionModeEnum.ROUTE_MAPPING` mode + - The project is changed to this mode by default, and the original menu file is deleted + - If you have written the menu before, you can change to `PermissionModeEnum.ROLE` mode + +## 2.5.1(2021-06-26) + +### ⚡ Performance Improvements + +- Upgrade `vue` and `ant-design-vue` versions to solve compatibility issues +- **Tree** Performance optimization + +### 🐛 Bug Fixes + +- **Table** Fix page jitter problem +- **Upload** Make sure to carry custom parameters +- **Dropdown** Fix the icon display problem of popConfirm +- **Table** Fix the problem that the editing event of the tree table is abnormal +- **Table** Fix the problem that when the table data is empty, the value returned by getDataSource is not the data source used by the table + +## 2.5.0(2021-06-20) + +## (Breaking changes) Breaking changes + +- Change the project `windicss` to `tailwindcss` to solve the memory overflow problem + - There are currently incompatible areas of the project + - The wording of `!xl:m-4` needs to be changed to `xl:!m-4`, note that only `!` is incompatible. If you don’t use it, you don’t need to change it. + - The new features of `windicss` itself need to be adjusted, for example, `Attribute` mode is not compatible + +### ✨ Refactor + +- Remove `useExpose` and use `expose` provided by the component itself instead + +### ⚡ Performance Improvements + +- **Locale** merge multi-language files to reduce the number of files +- **Utils** Mitt default export is changed from `Class` to `Function` +- **Axios** `isTransformRequestResult` is renamed to `isTransformResponse` + +### ✨ Features + +- **CropperImage** `Cropper` Avatar cropping adds circular cropping function +- **CropperAvatar** Added avatar upload component +- **Drawer** `useDrawer` added `closeDrawer` function +- **Preview** Added `createImgPreview` picture preview function +- **Setup** New guide page example +- **Tests** Add jest test suite, Vue component single test is not currently supported +- **Axios** Added `authenticationScheme` configuration to specify the authentication scheme +- **Setting** Added `sessionTimeoutProcessing` project configuration item, used to configure how to deal with session timeout + +### 🐛 Bug Fixes + +- **Modal** fix full screen height calculation error +- **Modal** Fix the problem that the shutdown event is triggered multiple times +- **PageWrapper** fix the height calculation problem +- **FlowChart** Repair drag and drop menu missing +- Fixed Iframe routing error in background mode +- **PageWrapper** Fix the height calculation problem when footer and global footer are opened at the same time +- **Menu** Fix the jitter problem of menu folding animation +- **Store** fixed type error after pinia version upgrade + +## 2.4.2(2021-06-10) + +### ✨ Refactor + +- `CountTo` component refactoring + +### ✨ Features + +- `radioButtonGroup` supports `boolean` value +- `useModalInner` added `redoModalHeight` to reset the height of `Modal` inside Modal +- `useECharts` added `getInstance` to obtain instances of `echart` +- `TableAction` added `stopButtonPropagation` to prevent the action button click event from bubbling +- `BasicTable` in the row edit mode, you can get or set the value of other editing components in the column +- The `ApiSelect` component will automatically re-fetch the data after the `params` is changed +- `TableImg` component improvement +- `BasicTable` added `columns-change` event to monitor the user to change the sorting, display, and fixed status of columns +- `Tinymce` supports dynamic modification readonly +- `BasicTable` added `updateTableDataRecord` method to update the specified row data +- `useModal` added `closeModal` method to close `Modal` + +### 🐛 Bug Fixes + +- Fix the problem that `redoModalHeight` cannot reduce the height +- Fix the problem that the schema data of `BasicForm` does not take effect +- Fix the problem that multiple tags may cause `KeepAlive` to fail +- Fix the problem that the default `axios` interceptor cannot handle custom code +- Fix the height issue of the lock screen pop-up window +- Fixed the problem that the half-selected state of the `Column Display` checkbox of `BaiscTable` was incorrectly displayed +- Fixed the problem that the preview list of the `BasicUpload` component could not be displayed in some cases +- Fix the problem that the `options` setting of `RadioButtonGroup``disabled` does not take effect +- Fix the problem that the button for uploading pictures in the read-only mode of the `Tinymce` component is still available +- Fix the stuttering problem of `BasicForm` under certain circumstances +- Fix the problem that "directory" routing does not work + +## 2.4.1(2021-06-01) + +### ✨ Features + +- Add `DatePicker` and `TimePicker` components to editable tables +- Added `defaultExpandLevel` configuration to `Tree` component + +### ⚡ Performance Improvements + +-Menu search default focus + +### 🐛 Bug Fixes + +- Fix known issues of `CodeEditor` +- Fix the issue of `i18n` console warning +- Fix the problem that the editable table `align` configuration does not take effect +- Ensure that `axios` only processes `Object` parameters +- Fix the failure of the `defaultExpandAll` configuration of the `Tree` component +- Fix the problem of missing dividing line in `TableAction` +- Fix the known issues of the table +- Fix that the lang attribute of HTML will not be set when reloading due to the first loading or changing the language + +## 2.4.0 (2021-05-25) + +### ✨ Features + +-New graphical editor example -New code editor (including Json editor) -Added `JsonPreview`Json data viewing component -The fields of the data column and actionColumn of the table can be controlled according to the authority and business. -Added an example of a permission control table (AuthColumn.vue) -Added user login expiration example + +### ⚡ Performance Improvements + +-Consolidate some language files to reduce the number of files + +### 🐛 Bug Fixes + +-Fix the flashing white screen when the dark theme refreshes -Fix the problem that other functions are invalid when the tab is closed -Fix known issues in the form -Fix the automatic lock screen failure + +## 2.3.0 (2021-04-10) + +## (Breaking changes) Breaking changes + +- Use `pinia` to replace `vuex`, `vuex-module-decorators`. + + -Impact, if you used vuex-module-decorators yourself before, you need to transform it to pinia. + + - the reason: -pinia is basically similar to vuex5api and is easy to understand. -Subsequent switching to vuex5 has a very low cost and can also be used as a third-party state management library + +- Remove `useKeyPress` and use `vueuse`-`onKeyStroke` instead +- Remove `useDebounceFn` and use `vueuse`-`useDebounceFn` instead +- Remove `useThrottle` and use `vueuse`-`useThrottleFn` instead + +### ✨ Features + +- Tabs support persistent storage + +### ✨ Refactor + +- Remove `useElResize` + +### 🐛 Bug Fixes + +- Login page style fix +- Fix the known problems of the menu +- Fix the problem of theme style switching + +## 2.2.0 (2021-04-06) + +### ✨ Features + +- Added `headerTitle` slot +- New printing example +- Added about interface + +### ✨ Refactor + +- Remove useFullScreen function +- tinymce changed from Cdn to npm (the package size is too large) +- Dashboard refactoring +- Remove ApexCharts and examples + +### 🐛 Bug Fixes + +- Make sure the breadcrumbs are displayed correctly +- Fixed the issue of tinymce upload button disappearing in full screen mode +- Make sure that the title changes normally after logging in again +- Ensure that the background mode login is normal +- Fix TableAction click event issue + +## 2.1.1 (2021-03-26) + +### ✨ Features + +- Added hideChildrenInMenu configuration for routing. Used to hide submenu +- Built-in expand/collapse all functions in the tree form + +### ✨ Refactor + +- Refactor the routing multi-layer mode to solve the problem of multiple implementations of nested keepalives + +### 🐛 Bug Fixes + +- Ensure that the CountDownInput component is reset to the empty value +- Fix the display problem on the small screen in split mode +- Fix table height calculation problem +- Fix the problem that components cannot be obtained by background routing +- Fix Modal component loadingTip configuration does not take effect +- Fix the background permission command does not take effect +- Make sure the progress bar is closed properly +- Fix the problem of invalid table check column configuration +- Ensure that the first level menu can be hidden +- Ensure that the hidden fields of the form are verified properly + +### 🎫 Chores + +- Remove ls-lint + +### 🎫 Chores + +- 移除 ls-lint + +## 2.1.0 (2021-03-15) + +### ✨ Features + +- Added svg mode to icon selector +- Added time component +- Added AutoNavi/Baidu/Google Map example + +### ✨ Refactor + +- Refactor the project to solve the hot update problem caused by circular dependencies +- Remove vueHelper/useClickoutside, use @vueuse/core instead + +### 🐛 Bug Fixes + +- Ensure that the value of `table action` is updated correctly +- Fix the animation of page switching cannot be closed +- Fix `PageWrapper`title not showing +- Fix the known issues of the table +- Fix the problem that the BasicTree component can't customize the title +- Fix the button style problem after theme switching + +## 2.0.3 (2021-03-07) + +### ✨ Features + +- `BasicTree` added `clickRowToExpand`, used to click tree node to expand +- Added SvgIcon plugin and examples +- Add the department tree on the left side of the account management interface· + +### ⚡ Performance Improvements + +- Pagination parameters are no longer carried when the table is closed +- The login page monitors the carriage return event to log in +- When the adaptive size of the table is set, the height is filled according to the screen. +- Tree scroll bar optimization +- Optimize the loading speed of local development + +### 🐛 Bug Fixes + +- Fix known issues with `Description` +- Fix known issues with `BasicForm` +- Fix the logic problem of show attribute of ActionItem under `BasicTree` +- Fix the style error of the tree component demo example +- Repair account management to add new but not cleared old data +- The form component should allow the setFieldsValue method to be null or undefined +- Ensure that the single-level breadcrumbs jump correctly +- Ensure that the Form component does not verify hidden form items + +## 2.0.2 (2021-03-04) + +### ✨ Refactor + +- Refactored multi-language modules to support lazy loading and remote loading + +### ✨ Features + +- axios supports form-data format request +- Added icon selector component (support local and online methods) +- Added WebSocket examples and service scripts +- Added the `renderIcon` property to the Tree component to control the display of level icons +- Tree->actionItem added show attribute, used to dynamically control button display +- New toolbar/title/search function for Tree +- Added department management/password modification/account management/role management/menu management sample interface + +### ⚡ Performance Improvements + +- Optimized login interface animation +- Fix the problem of excessively large github warehouse. +- Hide table full screen button by default +- `crypto-es` is changed to `crypto-js` to reduce the package size +- `types` directory moved to the root directory, compatible with other directory global types + +### 🐛 Bug Fixes + +- Fix the warning problem of verification code component +- Fix the table cannot get the selected row correctly +- Fixed modal height calculation error in full screen state +- Fix some table style issues +- Fix the invalidation of the tree form `indentSize` setting + +## 2.0.1 (2021-02-21) + +### ✨ Refactor + +- Refactored login page, new registration page/reset password page/mobile phone login/QR code login + +### ✨ Features + +- Added the `settingButtonPosition` configuration item for configuring the position of the `settings` button +- `modal` can switch the full screen by double-clicking the head +- Added `CountDownInput` component + +### ⚡ Performance Improvements + +- Optimize the editable center style and the width of the drop-down box is too short +- The `edit-change` event listener when the table is added and edited + +### 🐛 Bug Fixes + +- Fix image preview style error +- Fix icon style problem +- Fix the drop-down echo problem of editable table + +## 2.0.0 (2021-02-18) + +## Breaking changes + +- `echarts` is upgraded to 5.0 and introduced on demand (just use `useECharts`). + +### ✨ Refactor + +- Removed `global.less`, `mixin.less`, `design/helper`, replaced by `windicss`, and need to modify the corresponding styles if they are useful + +### ✨ Features + +- useModal adds the return value function `redoModalHeight`, which is used to refresh the modal height when the modal is dynamic content +- Upgrade husky to 5.0 +- Added `brotli`|`gzip` compression and related test commands +- Re-introduction of `windicss` (same as `tailwind`). Faster in speed + +### ⚡ Performance Improvements + +- Adjust the return value of the interface to obtain user information in array format +- Fix the error-log list as the system route + +### 🐛 Bug Fixes + +- Fix the issue of upload component maxNumber invalid +- Fix package sourcemap error report +- Fix code debugger location display error +- Fix the issue of mock plugin post request error +- Fix some themes color value error +- Fix the table in editable row status and press Enter to confirm + +### 🎫 Chores + +- Documentation update +- Upgrade ant-design-vue to `2.0.0` +- Upgrade vite to `2.0.0` + +## 2.0.0-rc.18 (2021-02-05) + +### ✨ Features + +- `ApiSelect` adds `numberToString` property, which is used to convert all the value of `number` into `string` +- Added theme color switch +- Packed image compression + +### ⚡ Performance Improvements + +When mock is not used, move `mock.js` out of the package file + +### 🐛 Bug Fixes + +- Fix modal height calculation error +- Fix the pop-up menu when the menu is clicked on the tab when the menu is collapsed +- Fix the problem that the initial value of form is 0 +- Fix table wrapping problem +- Fix the menu outside link does not jump +- Fix the display problem at the top of the menu +- Fix the issue of `modifyVars` configuration failure + +## 2.0.0-rc.17 (2020-01-18) + +### ✨ Refactor + +- Added `SimpleMenu` component to replace the left menu component (the top menu is not replaced, the function should be as simple as possible without stuck). Solve the menu stuck problem. +- The `ant-design-vue` component is no longer registered globally. In order to better coordinate with the introduction of css on demand. If you need to register globally, you need to add it yourself + +### ✨ Features + +- `css` import on demand + +### 🐛 Bug Fixes + +- Fix `TableAction` icon problem +- Fix the problem of missing menu folding buttons +- Fix menu related issues +- Fix moment multilingual issue + +## 2.0.0-rc.16 (2020-01-12) + +### ✨ Refactor + +- Independent component configuration to `/@/settings/componentsSetting` +- `colorSetting` and `designSetting` are now merged into `designSetting` +- `ant-design-vue` component registration moved to `components/registerComponent` +- Remove the `setup` folder +- Upgrade to `vite2` +- Image preview is changed to `Image` component implementation, temporarily removing functional usage + +### ✨ Features + +- Added `mixSideTrigger` configuration. Used to configure how to open the mixed mode menu on the left. Optional `hover`, default `click` +- Added `mixSideFixed` configuration. Used to fix the left mixed mode menu +- Added `height` and `min-height` properties to the modal component +- Added `PageWrapper` component. And applied to the sample page +- Added tab folding function +- Compatible with older browsers +- tinymce new image upload + +### 🐛 Bug Fixes + +- Fix known issues with table column configuration +- Restore the `isTreeTable` property of the table +- Fix table memory overflow problem +- Fix the function of `layout` shrinking and expanding in split mode +- Fix modal height calculation error +- Fix file upload error + +## 2.0.0-rc.15 (2020-12-31) + +### ✨ Table destructive update + +- Refactored editable cells and editable rows. See examples for details. The writing has changed. For editable tables. + +- Form editing supports form validation + +- Added the following configuration in the table column configuration + +```bash +{ + + # Whether to display columns by default. Those that are not displayed can be opened in the column configuration + defaultHidden?: boolean; + # Help text on the right side of the column header + helpMessage?: string | string[]; + # Custom formatting Cell content. Support time/enumeration automatic conversion + format?: CellFormat; + + # Editable + # Is it an editable cell + edit?: boolean; + # Is it an editable line + editRow?: boolean; + # Edit status. + editable?: boolean; + # Edit component + editComponent?: ComponentType; + # The parameters of the corresponding component + editComponentProps?: Recordable; + # Check + editRule?: boolean | ((text: string, record: Recordable) => Promise); + # Value enumeration conversion + editValueMap?: (value: any) => string; + # Trigger editing Zhenghang + record.onEditRow?: () => void; +} + +``` + +### ✨ Table reconstruction + +- Added `clickToRowSelect` attribute. Used to control whether the clicked row is checked or not +- Monitor row click event +- Add column drag and drop and column fix function for the table column configuration button. +- Added `defaultHidden` attribute to table column configuration. Used to hide by default. You can configure the tick display in the table column +- More powerful column configuration +- useTable: Support for dynamically changing parameters. You can pass in `Ref` type and `Computed` type for dynamic changes +- useTable: Added return function `getForm`. Can be used to manipulate forms in the form Fix known issues in the table + +### ✨ Features + +- Added `v-ripple` water ripple command +- Added the left menu mixed mode +- Add an example of markdown embedded in the form +- Add an example of a page outside the main frame +- `route.meta` added `currentActiveMenu`, `hideTab`, and `hideMenu` parameters to control the display and hide of the crumb-level menu on the detail page. +- Added breadcrumb navigation example +- form: Added `suffix` attribute to configure suffix content +- form: Added remote drop-down `ApiSelect` and examples +- form: Add `autoFocusFirstItem` configuration. Used to configure whether to focus on the first input box of the form +- useForm: Support for dynamically changing parameters. You can pass in `Ref` type and `Computed` type for dynamic changes + +### ⚡ Performance Improvements + +- Optimize the scroll bar components of `modal` and `drawer` +- table: remove the `isTreeTable` attribute +- Import `less` files globally. No need to manually re-introduce the component + +### 🎫 Chores + +- Upgrade `ant-design-vue` to `2.0.0-rc.7` +- Upgrade `vue` to `3.0.5` + +### 🐛 Bug Fixes + +- Fixed the issue of missing scroll bars in mixed mode +- Fix the invalid configuration of environment variables and the logo address problem in history mode +- Fix the calculation error of width and height caused by switching page of chart library +- Fixed the issue of multi-language configuration `Locale.show` causing the configuration not to take effect +- Fix routing type error +- Fix the problem of invalid permissions when the menu is split +- Iframe loads early when closing multi-tab pages +- Fix known issues with `modal` and `drawer` +- Fix the problem of mixing mode adaptation in the left menu + +## 2.0.0-rc.14 (2020-12-15) + +### ✨ Features + +-Remove the left menu search, add the top menu search function -Layout mobile terminal adaptation. Business page is not adapted -axios join the joinTime configuration. Control whether the response includes a timestamp + +### ⚡ Performance Improvements + +-Import components asynchronously -Optimize the overall structure -Replace the default scroll bar of the menu as a scroll component -Menu performance optimization + +### 🎫 Chores + +-Return to the top to adjust the style to avoid covering other elements -Upgrade `ant-design-vue` to `2.0.0-rc.5` -Refresh button layout adjustment -`route.meta` removes the `externalLink` attribute + +### ✨ Refactor + +-`openModal` and `openDrawer` third parameter `openOnSet` is set to true by default + +### 🐛 Bug Fixes + +-Fixed an issue where multi-level routing cache caused components to render multiple times -Fixed the problem of disappearing after switching the map chart -Fix the issue of successful login and notify disappearing -Modify the names of `VirtualScroll` and `ImportExcel` components as `VScroll` and `ImpExcel` to temporarily solve the memory overflow of components containing keywords in the vue template -Fix axios case problem -Fix button style problem -Fix the problem of menu split mode -Fix the issue of invalid data transmission when using emits in `Modal` and `Drawer` components -Fix the known problems of the menu -Fix the issue of upload component api failure -Fix the problem of invalid menu permission filtering + +## 2.0.0-rc.13 (2020-12-10) + +## (Breaking changes) Breaking changes + +-Route reconstruction, the previous format is no longer supported. Change to support the original default structure of vue-router, the specific format can be changed by referring to the example. Realize multi-level route caching, and no longer convert routes to level 2. -Refactor breadcrumbs and use antd's breadcrumbs component. The previous component has been deleted + +### ✨ Features + +-Restore the default loading of antdv, refactor the `Loading` component, and add `useLoading` and `v-loading` instructions. And add examples -i18n supports vscode `i18n-ally` plugin -New examples of increased routing cache -Packaged code split (experimental) -Extract upload address to global variable, package can be dynamically configured + +### ✨ Refactor + +-Tree component ref function call to delete `$` -Reconstruction and beautification of the lock screen interface, delete unnecessary background pictures + +### ⚡ Performance Improvements + +-Page switching loading logic modification. Regardless of whether the loaded page is closed or not, loading will not be displayed when opened again (pages that have been opened are opened again faster, and loading is not required, and the logic of the top progress bar is the same), and it will be restored after refreshing. + +### 🎫 Chores + +-First screen loading modification -Upgrade `vue` to `3.0.4` -Upgrade `ant-design-vue` to `2.0.0-rc.3` -Re-introduction of `vueuse` -Remove the `afterCloseLoading` attribute in route meta -Documentation update + +### 🐛 Bug Fixes + +-Fix form i18n error -Fix the inconsistent size of menu icons -Fix the calculation of the top menu width -Fix table tabSetting problem -Repair file upload and delete invalidation -Fix the problem of editing and saving table rows + +## 2.0.0-rc.12 (2020-11-30) + +## (破坏性更新) Breaking changes + +- The ClickOutSide component import method is changed from `import ClickOutSide from'/@/components/ClickOutSide/index.vue'` to `import {ClickOutSide} from'/@/components/ClickOutSide'` +- Button component import method changed from `import Button from'/@/components/Button/index.vue'` to `import {Button} from'/@/components/Button'` +- StrengthMeter component import method is changed from `import StrengthMeter from'/@/components/StrengthMeter'` to `import {StrengthMeter} from'/@/components/StrengthMeter'` +- In addition to the examples, the global internationalization function is added, supporting Chinese and English + +### ✨ Refactor + +- Refactor the overall layout. Change the code implementation method. Code is more streamlined +- Configuration item reconstruction +- Remove messageSetting configuration +- BasicTitle component `showSpan`=> `span` + +### ✨ Features + +- The cache can be configured to encrypt or not, and Aes encryption is enabled in the production environment by default +- Add tab drag and drop sort +- Added LayoutFooter. The default display, can be closed in the configuration + +### ⚡ Performance Improvements + +- Optimized the problem that the full screen animation of `Modal` component is not smooth + +### 🐛 Bug Fixes + +- tree: Fix the problem that the text exceeds the operation button +- useRedo: Fix the problem of missing parameters when refreshing the page through useRedo +- form: Fix the problem that the form verification is first set in the verification and the console error message +- `modal`&`drawer` fix the problem of component passing array parameters +- form: fix `updateSchema` does not take effect when the value contains `[]` +- table: Fix the display problem of the table `TableAction` icon +- table: fix table column settings not displayed by `setColumns` setting + +### 🎫 Chores + +- Update antdv to `2.0.0-rc.2` +- Update vue to `3.0.3` +- Update vite to `1.0.0.rc13` +- Temporarily delete `@vueuse/core`. After it is stable, it will be integrated. It is currently not stable. + +## 2.0.0-rc.11 (2020-11-18) + +### ✨ Features + +- Added base64 file stream download +- Optimize upload components and examples +- New editable row example +- Add a personal page +- New form page +- Add details page +- Integrate upload components into form by default + +### 🎫 Chores + +- Update antdv to `2.0.0-rc.1` (temporarily restore to beta15, rc1 menu freezes too seriously.) +- Add some notes + +### ✨ Refactor + +- Removed `receiveDrawerDataRef` and `transferDrawerData` properties of `useModal` and `useDrawer` +- `openModal` and `openDrawer` corresponding to `useModal` and `useDrawer` extend the third parameter. Used to open the trigger callback again + +### 🐛 Bug Fixes + +- Repair form inputNumber verification error +- Fix the error of setting the default value of the form +- Fix the problem of occupying position when the menu collapse button is hidden +- Fix the form baseColProps does not take effect + +## 2.0.0-rc.10 (2020-11-13) + +### ✨ Refactor + +- Refactor hook, introduce `@vueuse`, delete existing `hook`, optimize existing hook +- ʻUseEvent` renamed ->ʻuseEventListener` +- Delete the four types `SelectOptGroup`, `SelectOption`, `Transfer`, and `Radio` from the form `ComponentType`. Modify the `RadioButtonGroup` component + +### ✨ Features + +- `componentsProps` support function type of form item +- Added tag display to the menu, supporting 4 types of colors and dot display +- New menu and top bar color selection color matching +- Add sample result page +- New file download example + +### ⚡ Wip + +- Upload components (not completed, testing...) + +### ⚡ Performance Improvements + +- Optimize settingDrawer code +- Optimize the switching speed of multiple tabs +- Add form customization and dynamic capabilities + +### 🐛 Bug Fixes + +- Fixed multiple rich text editors showing only one +- Fixed the problem of not redirecting to the original page after logging in again after expiration +- Fix window system dynamic introduction error +- Fix page type error +- Fixed an error when the form switch and checkBox were used separately + +## 2.0.0-rc.9 (2020-11-9) + +### ✨ Features + +- Menu trigger can select location +- Add an example of rich text embedded form +- Added `required` attribute to form component schema. Simplified configuration +- The second parameter of openModal and openDrawer can be passed internally instead of `transferModalData` +- Routes with parameters can be cached + +### ✨ Refactor + +- Refactored the logic of the menu generated by the background +- Route Module structural transformation + +### ⚡ Performance Improvements + +- Menu performance continues to be optimized and smoother +- Optimize lazy loading components and examples +- layout style fine-tuning + +### 🎫 Chores + +- Delete menu background image +- Update the version of ʻant-design-vue`to`beta15` +- Update `vite` version to `rc.9` +- Exception page adjustment +- `BasicTitle` Color blocks are not displayed by default + +### 🐛 Bug Fixes + +- Fix table type problem after upgrade +- Fix the problem that the last submenu continues to be displayed when the menu is divided and there is no data in the left menu +- Fix the issue of ʻuseMessage` type +- Fix the problem that the form item setting `disabled` does not take effect +- Fix that ʻuseECharts`can't adapt when`resize`, and an error is reported +- Fix that `resize` is not deleted after ʻuseWatermark` is cleared +- Fix form verification problem +- Fixed the problem that the multi-level header configuration does not take effect + +## 2.0.0-rc.8 (2020-11-2) + +### ✨ Features + +- Global loading add text +- Right-click menu supports multiple levels + +### 🎫 Chores + +- Login cache changed from sessionStorage to LocalStorage + +### ⚡ Performance Improvements + +- Update ʻant-design-vue`to`beta.12` +- Layout interface layout style adjustment +- Optimize lazy loading components +- Optimize table rendering performance +- Add animation to form folding search icon +- routeModule can ignore the layout configuration. Convenient to configure the first-level menu + +### 🐛 Bug Fixes + +- Fix table type error +- Fix bug in mock paging tool +- Fix the folding problem of the search form when the table is opened +- Fix the problem of fixed column style when the table size is samll +- Fixed the error report when closing multiple tabs +- Fix message type error + +## 2.0.0-rc.7 (2020-10-31) + +### ✨ Features + +- The form component now supports directly passing in the model to directly perform the set operation, please refer to **Component -> Popup Extension -> Open Popup and Pass Data** + +- The useModalInner of modal now supports the incoming callback function to receive the value passed in from the external `transferModalData` + + - Used to handle the setting values ​​of components such as forms when the pop-up window is opened. Refer to **Component -> Popup Extension -> Open Popup and Pass Data** + - The value of `receiveModalDataRef` is temporarily reserved. Use as little as possible. It may be deleted later. + +- The drawer’s useDrawerInner now supports the incoming callback function to receive the value passed in from the external `transferModalData`,, + - Used to handle the setting values ​​of components such as forms for opening the drawer Refer to **Component->Drawer Extension->Open the drawer and transfer data** + - The value of `receiveModalDataRef` is temporarily reserved. Use as little as possible. It may be deleted later. + +### ✨ Refactor + +- Form code optimization and reconstruction + +### ⚡ Performance Improvements + +- Modal slot can be overwritten +- Optimize table embedding height calculation problem + +### 🎫 Chores + +- Add some notes +- pwa icon supplement +- Type adjustment +- Upgrade ʻant-design-vue`to`beta.11`, and modify the known issues brought about, and some issues will be resolved after discovery + +### 🐛 Bug Fixes + +- Fix the timeout error of local proxy post interface to https address +- Fix modal full screen height calculation problem when footer is not displayed +- Fix the error that the verification information is not deleted when the form is reset +- Fix the style problem of the split mode of the top menu +- Fix the invalidation of table expansion icon animation + +## 2.0.0-rc.6 (2020-10-28) + +### ✨ Features + +- Added `pwa` function, which can be turned on in `.env.production` +- Button component extends `preIcon` and `postIcon` attributes to add icons before and after the text +- Restore the breadcrumb display icon function + +### 🎫 Chores + +- Upgrade vite version to `v1.0.0.rc8` +- vite.config.ts internal plugins extraction +- Build directory structure adjustment +- Dependency update +- Documentation update +- Modify the default route switching animation + +### ⚡ Performance Improvements + +- `setTitle` logic adjustment +- The sessionStorage and LocalStorage cache settings used by the system expire in `7` days by default + +### ✨ Refactor + +- Separate `vite-plugin-html` and modify the logic of inserting html + +### 🐛 Bug Fixes + +- Fix the warning problem of multiple registration components during hot update +- Fix the login tab page appears after login +- Fix the problem of routing switch parameter disappearance +- Fix the useMessage icon style problem + +## # 2.0.0-rc.5 (2020-10-26) + +### ✨ Features + +- Update component documentation +- Breadcrumbs support display icon +- Added tinymce rich text component +- Add submitOnReset to the form to control whether to re-initiate the request when reset +- Added `sortFn` to the table to support custom sorting +- Added animation components and examples +- Added lazy loading/delay loading components and examples + +### ✨ Refactor + +- The detailType of the Drawer component is changed to isDetail + +### 🎫 Chores + +- Remove the optional chain syntax in the code +- Form reset logic modification +- Turn off multi-tab page tabs animation +- Upgrade vite version to `v1.0.0.rc6` +- Delete Chinese path warning. rc6 has been fixed + +### 🐛 Bug Fixes + +- Fix the automatic height and display footer display problems of drawer components +- Reset to default value after repairing form query +- Fix the problem of displaying the collapsed menu when there are no child nodes +- Fix the problem of breadcrumb display style +- Fixed the problem of multiple open drag and drop failure when destroyOnClose=true in modal +- Fixed multiple action columns in the table + +# 2.0.0-rc.4 (2020-10-21) + +### ✨ Features + +- New configuration toolbar for tables +- New message notification module + +### 🎫 Chores + +- The table does not show borders by default +- Dependency update +- Update vue to `v3.0.2` +- Interface style fine-tuning + +### ⚡ Performance Improvements + +- Optimize the size of the first screen +- Optimize the TableAction component +- Reduce the folding width of the menu + +### 🐛 Bug Fixes + +- Fix the problem of the menu name when the first level menu is folded +- Fix the problem that the preview command is not packaged +- Fix the problem that the form actionColOptions parameter does not take effect +- Fix the problem that the loading does not take effect when refreshing the form + +# 2.0.0-rc.3 (2020-10-19) + +### ✨ Features + +- Added excel component and excel/xml/csv/html export example +- Added excel import example +- Added global error handling +- Added markdown components and examples +- The menu name can be displayed when adding a new folding menu + +### Docs + +- add project doc + +### 🎫 Chores + +- update deps + +### 🐛 Bug Fixes + +- Fix the adaptive problem of the top menu +- Fix window system packaging error + +# 2.0.0-rc.2 (2020-10-17) + +### ✨ Features + +- Package can be configured to output `gizp` +- Package can be configured to delete `console` +- Routes and menus do not need to be imported manually, they are imported automatically + +### 🎫 Chores + +- Upgrade vue to `3.0.1` +- Change `vite` version to daily build version + +### 🐛 Bug Fixes + +- Fix menu error +- Fix the problem of table adaptive height +- Fix the issue of error reporting when executing script in `window system` +- Fix the problem of folding components + +### ⚡ Performance Improvements + +- Remove menu to minimize background +- Prevent page refresh and re-render menu +- Some other details are optimized + +# 2.0.0-rc.1 (2020-10-14) + +### ✨ Features + +- Add a tab with parameters + +### ⚡ Performance Improvements + +- Optimized menu folding +- Page details optimization +- Compress html after packaging +- Functional reconstruction of preview components and right-click menu +- The preview component operation column is centered + +### 🎫 Chores + +- update deps +- Added `README.en-US.md` +- Added `CHANGELOG.en-US.md` + +### 🐛 Bug Fixes + +- Fix page refresh and jump to landing page + +# 2.0.0-beta.7 (2020-10-12) + +### ⚡ Performance Improvements + +- The existing tab switching no longer displays animation and progress bar + +### ✨ Features + +- Added `CountTo` component and sample demo +- Added `closeMessageOnSwitch` and `removeAllHttpPending` to the project configuration file +- The production environment has a separate configuration file for dynamic configuration project configuration +- Added ʻuseEcharts` and ʻuseApexChart` to facilitate the use of charts, and added related demos +- New workbench interface +- New analysis page interface + +### 🎫 Chores + +- Update dependencies + +### 🐛 Bug Fixes + +- Fix routing switch, tab inactive problem + +# 2.0.0-beta.6 (2020-10-11) + +### 💄 Styles + +- Menu style adjustment + +### 🐛 Bug Fixes + +- Fix the problem that editable forms cannot be entered +- Repair packaging errors, no proxy is required in the production environment + +### ⚡ Performance Improvements + +- Optimize the switching speed of multi-tab pages +- First screen loading animation + +# 2.0.0-beta.5 (2020-10-10) + +### ♻ Code Refactoring + +- Delete `tailwind css` + +### ⚡ Performance Improvements + +- Optimize page switching speed + +### 🎫 Chores + +- Add `.vscode` and `.github` configuration +- Change menu icon +- Added `.env` configuration file +- Update readme.md + +### 🐛 Bug Fixes + +- Fix the failure of `Tree` component check event + +# 2.0.0-beta.4 (2020-10-08) + +### 🎫 Chores + +- Remove redundant dependencies + +### 🐛 Bug Fixes + +- Fix page refresh blank +- Fix the invalid table style in the production environment + +# 2.0.0-beta.3 (2020-10-07) + +### ✨ Features + +- Added ʻopenNProgress` to the project configuration file to control whether to open the top control bar +- Add `Table` component and demo + +### 🎫 Chores + +- Add ` github workflows` + +# 2.0.0-beta.2 (2020-10-07) + +### ✨ Features + +- Added image preview component + +### 🔧 Continuous Integration + +- Add githubAction script + +# 2.0.0-beta.1(2020-09-30) + +### 🎫 Chores + +- Migrate some code from 1.0 +- Add README.md description file + +### 🐛 Bug Fixes + +- Fix the problem of form, animation and packaging failure diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..d361b960fa3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1863 @@ +## [2.11.4](https://github.com/vbenjs/vue-vben-admin/compare/v2.11.3...v2.11.4) (2024-05-06) + +### Bug Fixes + +- **BasicForm:** 修复 SetFieldsValue 设置值时,会将 Number 类型的值转为 string ([#3802](https://github.com/vbenjs/vue-vben-admin/issues/3802)) ([08a1f7b](https://github.com/vbenjs/vue-vben-admin/commit/08a1f7b682114bdf758ea9dcd4b84daea0d5f196)) +- **breadcrumb:** if hideChildrenInmenu is true & hidden dropdown-menu ([#3807](https://github.com/vbenjs/vue-vben-admin/issues/3807)) ([2af93c9](https://github.com/vbenjs/vue-vben-admin/commit/2af93c9f5307ffdbb29653303177b2c8631e789a)) +- **docker:** update node version of dockerfile ([#3788](https://github.com/vbenjs/vue-vben-admin/issues/3788)) ([338d077](https://github.com/vbenjs/vue-vben-admin/commit/338d077ab3669ef116e7406c586fe2cb59952022)) +- **imgupload:** resultField causing with error display && setField uncertain ([#3798](https://github.com/vbenjs/vue-vben-admin/issues/3798)) ([06018ad](https://github.com/vbenjs/vue-vben-admin/commit/06018add798a6c23ebbfa91a3f4d625c2a57e458)) + +### Features + +- **upload->previewColumns:** Adapt functions && chore upload demo ([#3799](https://github.com/vbenjs/vue-vben-admin/issues/3799)) ([29ef0d3](https://github.com/vbenjs/vue-vben-admin/commit/29ef0d39157957146015e1b914c26d2b6d1bf25e)) + +### Performance Improvements + +- **util:** remove handleInputNumberValue ([#3806](https://github.com/vbenjs/vue-vben-admin/issues/3806)) ([ba5b8f8](https://github.com/vbenjs/vue-vben-admin/commit/ba5b8f8506bb9d052ce2705653f255f0401963e8)) + +## [2.11.3](https://github.com/vbenjs/vue-vben-admin/compare/v2.11.2...v2.11.3) (2024-04-24) + +### Bug Fixes + +- **deps:** lock vue version to 3.4.23 ([#3785](https://github.com/vbenjs/vue-vben-admin/issues/3785)) ([2f655c2](https://github.com/vbenjs/vue-vben-admin/commit/2f655c2127753c0cde1cb29834314e961ce0930e)), closes [#3783](https://github.com/vbenjs/vue-vben-admin/issues/3783) +- **upload:** disabled prop not effect to upload in the form ([#3780](https://github.com/vbenjs/vue-vben-admin/issues/3780)) ([69a6e90](https://github.com/vbenjs/vue-vben-admin/commit/69a6e9023ef80a5504178d0887119f8e7bbd5113)) + +### Features + +- **BasicForm->Components:** add beforeFetch & afterFetch to apicomp && perf the code ([#3786](https://github.com/vbenjs/vue-vben-admin/issues/3786)) ([7ae2ec0](https://github.com/vbenjs/vue-vben-admin/commit/7ae2ec03a773c2223feabbd1e341e90f012f8b7e)) +- **demo:** use Tour component replace dirverjs ([#3777](https://github.com/vbenjs/vue-vben-admin/issues/3777)) ([49c4dc6](https://github.com/vbenjs/vue-vben-admin/commit/49c4dc646a9d123527577f02ee0d92865da9988e)) + +## [2.11.2](https://github.com/vbenjs/vue-vben-admin/compare/v2.11.1...v2.11.2) (2024-04-23) + +### Bug Fixes + +- **BasicForm:** solve the error about setFieldValue array ([#3775](https://github.com/vbenjs/vue-vben-admin/issues/3775)) ([f5cd3ad](https://github.com/vbenjs/vue-vben-admin/commit/f5cd3ad593ded10a9702cf342d788c4b1540944a)) +- **ci:** use for package-manager-strict ([d53a5b2](https://github.com/vbenjs/vue-vben-admin/commit/d53a5b22ccadc28f99fc5e9751e3177d349ba8b9)) + +## [2.11.1](https://github.com/vbenjs/vue-vben-admin/compare/v2.11.0...v2.11.1) (2024-04-20) + +### Bug Fixes + +- the form not working when setFieldsValue through form-groups and add a demo with form groups ([#3765](https://github.com/vbenjs/vue-vben-admin/issues/3765)) ([65e5e71](https://github.com/vbenjs/vue-vben-admin/commit/65e5e71f5ee44eac221721de2c8c1d03e622e34a)) + +# [2.11.0](https://github.com/vbenjs/vue-vben-admin/compare/2.10.1...2.11.0) (2024-04-20) + +### Bug Fixes + +- (demo->page>form>high) expose getDataSource close [#3529](https://github.com/vbenjs/vue-vben-admin/issues/3529) ([#3530](https://github.com/vbenjs/vue-vben-admin/issues/3530)) ([0c1235e](https://github.com/vbenjs/vue-vben-admin/commit/0c1235e75aa6a855d774435ef08d3ffae19d1272)) +- [#2744](https://github.com/vbenjs/vue-vben-admin/issues/2744)tabs选项卡渲染问题,以及完善路由中affix=true时处理逻辑。 ([#3127](https://github.com/vbenjs/vue-vben-admin/issues/3127)) ([b43d306](https://github.com/vbenjs/vue-vben-admin/commit/b43d3069e1ec731e339ed28c17325620f1fe9a6e)) +- [#3077](https://github.com/vbenjs/vue-vben-admin/issues/3077) 最新代码 ApiTransfer编辑后无法正常显示数据 ([#3083](https://github.com/vbenjs/vue-vben-admin/issues/3083)) ([c0c3116](https://github.com/vbenjs/vue-vben-admin/commit/c0c31161939027f64fa44a57084acafa0c6c2a8b)) +- [#3144](https://github.com/vbenjs/vue-vben-admin/issues/3144) Drawer的footer样式错位问题 ([#3148](https://github.com/vbenjs/vue-vben-admin/issues/3148)) ([8e9d4f0](https://github.com/vbenjs/vue-vben-admin/commit/8e9d4f0a5758bf414b2885f02563a3b24f5cf6f1)) +- 1.修正ImageUpload直接使用时无法正常回传value 2.修正ImageUpload无法正常初始化第一次传值 ([#3704](https://github.com/vbenjs/vue-vben-admin/issues/3704)) ([b5c87cf](https://github.com/vbenjs/vue-vben-admin/commit/b5c87cf6abc46ccd9b9bb8795b235b738e8bb376)) +- 菜单搜索功能修复 ([#3688](https://github.com/vbenjs/vue-vben-admin/issues/3688)) ([c1809cd](https://github.com/vbenjs/vue-vben-admin/commit/c1809cd6c59228d7932f24a2b1f8e4933654238d)) +- 当TableAction的actions属性中的ActionItem传递了color属性时,PopConfirm的指示箭头颜色异常问题 ([#3597](https://github.com/vbenjs/vue-vben-admin/issues/3597)) ([e6a7384](https://github.com/vbenjs/vue-vben-admin/commit/e6a73840ab7c7cbd5a5a534bd248f5ed5df11e5c)) +- 多选框必填选中校验异常,close [#3097](https://github.com/vbenjs/vue-vben-admin/issues/3097) ([#3103](https://github.com/vbenjs/vue-vben-admin/issues/3103)) ([18f5583](https://github.com/vbenjs/vue-vben-admin/commit/18f55833e282206c1ca650a7c62654d45e819759)) +- 解决 'Cannot find module uncss' 的问题 ([#3334](https://github.com/vbenjs/vue-vben-admin/issues/3334)) ([3a5f406](https://github.com/vbenjs/vue-vben-admin/commit/3a5f4062602f8394523a82cd807a27580e96a42a)) +- 解决table复选框点击无法勾选状态问题 ([#3370](https://github.com/vbenjs/vue-vben-admin/issues/3370)) ([dde3652](https://github.com/vbenjs/vue-vben-admin/commit/dde3652b7d8b68b7f8ac669bd96a55c7b9b1b1fa)) +- 设置 baseurl 后不生效的问题 ([#3318](https://github.com/vbenjs/vue-vben-admin/issues/3318)) ([4305f58](https://github.com/vbenjs/vue-vben-admin/commit/4305f58d201382c71f41fcd2625bc45ae09a2ae0)) +- 使用suffix时,label没有垂直居中 ([#3384](https://github.com/vbenjs/vue-vben-admin/issues/3384)) ([6b6b790](https://github.com/vbenjs/vue-vben-admin/commit/6b6b790f87edab31cb8e0dff730a1490903a3048)) +- 修复表单按钮的类型定义外部无法修改重置或者提交按钮的文字的问题 ([141f3bd](https://github.com/vbenjs/vue-vben-admin/commit/141f3bdbd06f4d32e3d0e871072f876c29a8d68b)) +- 修复表单设计的右侧属性配置面板中部分表单错乱的问题 ([#3300](https://github.com/vbenjs/vue-vben-admin/issues/3300)). close [#3268](https://github.com/vbenjs/vue-vben-admin/issues/3268) ([cb13986](https://github.com/vbenjs/vue-vben-admin/commit/cb13986a170815c5a21c86033057a8a56d608388)) +- 修复菜单在MIX_SIDEBAR模式下title显示为name的问题 ([#3682](https://github.com/vbenjs/vue-vben-admin/issues/3682)) ([264f34e](https://github.com/vbenjs/vue-vben-admin/commit/264f34e49d413783d6d23715ebd9ab721b03d01c)) +- 修复黑暗模式下一些样式问题 ([#3201](https://github.com/vbenjs/vue-vben-admin/issues/3201)) ([054a476](https://github.com/vbenjs/vue-vben-admin/commit/054a476d25b2d8322b238a6da6028051bcfdab84)) +- 修复确认弹出框样式错乱的问题 ([#3742](https://github.com/vbenjs/vue-vben-admin/issues/3742)) ([a00725b](https://github.com/vbenjs/vue-vben-admin/commit/a00725be571e90fa5d807ec2bc1e23b160c824ff)) +- 修复设置抽屉弹框滚动条样式异常 ([#3193](https://github.com/vbenjs/vue-vben-admin/issues/3193)) ([4755017](https://github.com/vbenjs/vue-vben-admin/commit/4755017bcc2dc0aee2f98b46d929a060a5b1bb62)) +- 修复BasicForm使用componentProps函数返回valueFormat时DatePicker无法正确格式化的问题 ([#3357](https://github.com/vbenjs/vue-vben-admin/issues/3357)) ([beee351](https://github.com/vbenjs/vue-vben-admin/commit/beee35173b84dfb4c27bcd403df689e102379303)) +- 修复index.html加载文字偏移的问题 ([#3306](https://github.com/vbenjs/vue-vben-admin/issues/3306)) ([c715d35](https://github.com/vbenjs/vue-vben-admin/commit/c715d35ad5754dd78135073ddbea86e43e17be91)) +- 修复Modal.confirm的返回类型问题,需要有destroy,update的方法 ([#3161](https://github.com/vbenjs/vue-vben-admin/issues/3161)) ([a0e43ab](https://github.com/vbenjs/vue-vben-admin/commit/a0e43abeab2930097209a0cf6c21f3de687435ca)) +- 修复notice样式绑定路径错误 ([#3218](https://github.com/vbenjs/vue-vben-admin/issues/3218)) ([ee8ec9e](https://github.com/vbenjs/vue-vben-admin/commit/ee8ec9eacfbfe1c5dc3a842da126dbaf5d54b126)) +- 修复rule validator类型默认为string,导致 radio 等组件在 setFormValues 时,如果值不是string类型,提示校验错误 ([a63a10c](https://github.com/vbenjs/vue-vben-admin/commit/a63a10c047cda32d92f35780163772fb20a6fe7a)) +- 英文版本时提示中的单词没有分开 ([eb26650](https://github.com/vbenjs/vue-vben-admin/commit/eb2665059eb5fdcb8299032dc1a49c73f1675156)) +- **ApiCascader:** apiParamKey not working ([c42ba1c](https://github.com/vbenjs/vue-vben-admin/commit/c42ba1cc1b2fba7179701cb1f918443523a9fc70)) +- **ApiCascader:** wrong api reload ([#3536](https://github.com/vbenjs/vue-vben-admin/issues/3536)) resolve [#3534](https://github.com/vbenjs/vue-vben-admin/issues/3534) ([83f16da](https://github.com/vbenjs/vue-vben-admin/commit/83f16da2d35716f94f1837ab9bee41c5878ab3b0)) +- **ApiSelect:** 移除watchEffect引发的重复请求 ([#3107](https://github.com/vbenjs/vue-vben-admin/issues/3107)) ([1519f47](https://github.com/vbenjs/vue-vben-admin/commit/1519f47f7d7785a93be51ce9da0f9ef2e78705c9)) +- **ApiSelect:** 修复监听不到params的变动 ([ccf4027](https://github.com/vbenjs/vue-vben-admin/commit/ccf4027533d2adabd21bade025ca7bc7d34d75f6)) +- **ApiSelect:** ApiSelect的isFirstLoaded赋值逻辑,只有当加载报错时才能恢复未初次加载状态 ([#3671](https://github.com/vbenjs/vue-vben-admin/issues/3671)) ([3d733de](https://github.com/vbenjs/vue-vben-admin/commit/3d733de3995a667782fa3219a0e0b171327c5b6f)) +- **ApiSelect:** BasicForm emit ield-value-change twice ([0f2c2ea](https://github.com/vbenjs/vue-vben-admin/commit/0f2c2eabd671af457febbb16fa3996c845bae145)) +- **ApiSelect:** Incorrect value type definition. close [#3168](https://github.com/vbenjs/vue-vben-admin/issues/3168) ([0cf79d4](https://github.com/vbenjs/vue-vben-admin/commit/0cf79d4ce2786e71444d4d9483ed77a99f57169c)) +- **ApiSelect:** type warning ([8f6153f](https://github.com/vbenjs/vue-vben-admin/commit/8f6153fd2a73b9537e7f9ba0f2326388745bc232)) +- ApiTransfer 不支持disabled ([#3149](https://github.com/vbenjs/vue-vben-admin/issues/3149)) ([95ca2c3](https://github.com/vbenjs/vue-vben-admin/commit/95ca2c3ae6085d97f24546e24117ee2518f33e2d)) +- **ApiTree:** 多触发一次onchange ([882270d](https://github.com/vbenjs/vue-vben-admin/commit/882270d5baff92eb8f51ccb80758c68ef8babe51)) +- **ApiTree:** Modify Trigger Selection Event Name ([094a33c](https://github.com/vbenjs/vue-vben-admin/commit/094a33c0c2511816079c935eb83e60ad93c9289c)) +- async validator ([#3194](https://github.com/vbenjs/vue-vben-admin/issues/3194)) ([405ef9e](https://github.com/vbenjs/vue-vben-admin/commit/405ef9e2b3e61bd6195b58996504b9cb3939ef6f)) +- **BackTop:** repair BackTup comp ([#3581](https://github.com/vbenjs/vue-vben-admin/issues/3581)) ([6f4bdae](https://github.com/vbenjs/vue-vben-admin/commit/6f4bdae5c2a5455cb924e1903612f9fe96cf4481)) +- basemodal 无法透传 attributes 至 Modal.tsx ([#3637](https://github.com/vbenjs/vue-vben-admin/issues/3637)) ([89830ec](https://github.com/vbenjs/vue-vben-admin/commit/89830ec7e69a7cab55e6ccf90b74377bcbadf44c)) +- **BasicDrawer:** remove toRaw props ([#3399](https://github.com/vbenjs/vue-vben-admin/issues/3399)) ([57e6e4f](https://github.com/vbenjs/vue-vben-admin/commit/57e6e4f00435637c545c58de3cd84c102003032a)) +- **BasicForm->ApiRadioGroup:** when options click, duplicate requests. resolve [#3387](https://github.com/vbenjs/vue-vben-admin/issues/3387) ([fdde6f0](https://github.com/vbenjs/vue-vben-admin/commit/fdde6f06b2d388bbdcc7f0de2b5419593cd686c3)) +- **BasicForm->FormItem:** model should update before event call ([#3573](https://github.com/vbenjs/vue-vben-admin/issues/3573)). resolve [#3570](https://github.com/vbenjs/vue-vben-admin/issues/3570) ([43aa743](https://github.com/vbenjs/vue-vben-admin/commit/43aa7430324a7f390c31ea9e8a2f1e00fad8a1d0)) +- **BasicForm:** 修复 useComponentRegister 方法无法添加自定义的组件,添加时爆类型错误的问题 ([#3483](https://github.com/vbenjs/vue-vben-admin/issues/3483)) ([98e2e4c](https://github.com/vbenjs/vue-vben-admin/commit/98e2e4c89a859e67c911134652d4b005be51e2d1)) +- **BasicForm:** script setup defineExpose ([#3316](https://github.com/vbenjs/vue-vben-admin/issues/3316)) ([f58ef67](https://github.com/vbenjs/vue-vben-admin/commit/f58ef6777c4dd8ac905919637410ea5373eb770b)) +- **BasicForm:** type instantiation is excessively deep and possibly infinite. ([#3128](https://github.com/vbenjs/vue-vben-admin/issues/3128)) ([5a388be](https://github.com/vbenjs/vue-vben-admin/commit/5a388be15e44d86c87d76dc8829a5286ac1818e0)) +- **BasicForm:** useForm 中 scheme 选项 slot 与 component冲突 ([#3133](https://github.com/vbenjs/vue-vben-admin/issues/3133)) ([0bb76a8](https://github.com/vbenjs/vue-vben-admin/commit/0bb76a86d25cbd1de1c672f5cc5e63d0ae478b68)) +- **BasicForm:** validate Form tip height ([#3286](https://github.com/vbenjs/vue-vben-admin/issues/3286)). close [#3281](https://github.com/vbenjs/vue-vben-admin/issues/3281) ([100f3cf](https://github.com/vbenjs/vue-vben-admin/commit/100f3cf26c2b124bf94f3bb4913dd3d6d15aed3e)) +- **BasicModal:** 修复BasicModal添加wrapClassName样式异常问题 ([#3726](https://github.com/vbenjs/vue-vben-admin/issues/3726)) ([13b031e](https://github.com/vbenjs/vue-vben-admin/commit/13b031eef9b8d4e8333c9397ff26e4875bf9816a)) +- **BasicTable->rowKey&scroll:** declear and usage about rowKey and scroll ([#3541](https://github.com/vbenjs/vue-vben-admin/issues/3541)) ([e23e338](https://github.com/vbenjs/vue-vben-admin/commit/e23e3383dd73b20a479977d29bab999c51334a1a)) +- **BasicTable->useColumns:** handle deep colunm hidden ([#3561](https://github.com/vbenjs/vue-vben-admin/issues/3561)) resolve [#3559](https://github.com/vbenjs/vue-vben-admin/issues/3559) ([54af5bb](https://github.com/vbenjs/vue-vben-admin/commit/54af5bb42ddd04a56a7da3becf325dd9cfbccc48)) +- **BasicTable:** 滑动表格内容合计行不跟随滑动bug ([#3438](https://github.com/vbenjs/vue-vben-admin/issues/3438)) resolve [#2166](https://github.com/vbenjs/vue-vben-admin/issues/2166) ([7ba83e7](https://github.com/vbenjs/vue-vben-admin/commit/7ba83e71bf718b6c896eac892f3b66cd266747af)) +- **BasicTable:** 修复BasicTable数据为空时,重置后高度不能自适应问题 ([#3724](https://github.com/vbenjs/vue-vben-admin/issues/3724)) ([6054fa2](https://github.com/vbenjs/vue-vben-admin/commit/6054fa2ffac7ff0206a4e0138da16a548d2d25a2)) +- **BasicTable:** avoid select when edit cell ([#3484](https://github.com/vbenjs/vue-vben-admin/issues/3484)) ([2f921cf](https://github.com/vbenjs/vue-vben-admin/commit/2f921cfb88b969fb8bd361c46ddf2f41a9e363b0)) +- **BasicTable:** BasicTable resize wrong in modal ([#3549](https://github.com/vbenjs/vue-vben-admin/issues/3549)) ([a121b32](https://github.com/vbenjs/vue-vben-admin/commit/a121b32252cf4b0570c937a1ab86d8b924b229ce)) +- **BasicTable:** column setting about action fixed and default not cache ([#3441](https://github.com/vbenjs/vue-vben-admin/issues/3441)) ([86ecb27](https://github.com/vbenjs/vue-vben-admin/commit/86ecb2729ef15bb0bb3fc7347a84ffa83487eec8)) +- **BasicTable:** ColumnSetting about selectedRowKeys override ([#3446](https://github.com/vbenjs/vue-vben-admin/issues/3446)) ([65122ea](https://github.com/vbenjs/vue-vben-admin/commit/65122ea1a52e2f06463dc15f7ee06aba4e29d104)) +- **BasicTable:** ColumnSetting mistake when use setColumns ([#3408](https://github.com/vbenjs/vue-vben-admin/issues/3408)) ([fec67b4](https://github.com/vbenjs/vue-vben-admin/commit/fec67b4d53ce82d156f4683f2c436f31dd3b4f7a)) +- **BasicTable:** getSelectRows return duplicate records ([#3545](https://github.com/vbenjs/vue-vben-admin/issues/3545)) ([974c1fa](https://github.com/vbenjs/vue-vben-admin/commit/974c1fad7fb2767429eeb50680cbae8e17b80f1f)) +- **BasicTable:** headerCell slot title not exist ([8df2590](https://github.com/vbenjs/vue-vben-admin/commit/8df2590aae8d646880e8c064e6b4ba48cb54086d)) +- **BasicTable:** index still show when set showIndexColumn false ([#3455](https://github.com/vbenjs/vue-vben-admin/issues/3455)) ([75f5b7a](https://github.com/vbenjs/vue-vben-admin/commit/75f5b7ac4dda840ce0098ed528e0b161d99d9b09)) +- **BasicTable:** keep rowSelection onChange call outside ([#3461](https://github.com/vbenjs/vue-vben-admin/issues/3461)). resolve [#3453](https://github.com/vbenjs/vue-vben-admin/issues/3453) ([a7b2f14](https://github.com/vbenjs/vue-vben-admin/commit/a7b2f14b900771186ee126cf60e8841ecc0cb8c1)) +- **BasicTable:** pagination exceeds page height. close [#3185](https://github.com/vbenjs/vue-vben-admin/issues/3185) ([41451f4](https://github.com/vbenjs/vue-vben-admin/commit/41451f4fa3495be1d1ea4088cad77322dcde57de)) +- **BasicTable:** ref table ([#3327](https://github.com/vbenjs/vue-vben-admin/issues/3327)) ([c8744a0](https://github.com/vbenjs/vue-vben-admin/commit/c8744a057e0da407ab929c91b46e638d196d82cc)) +- **BasicTable:** selection wrong by click input when cross pages ([#3540](https://github.com/vbenjs/vue-vben-admin/issues/3540)). resolve [#3539](https://github.com/vbenjs/vue-vben-admin/issues/3539) ([69c3602](https://github.com/vbenjs/vue-vben-admin/commit/69c36021fa5558097c4fc7b9f63e29816e478cb9)) +- **BasicTable:** showIndexColumn/showRowSelection cache should by route name ([#3489](https://github.com/vbenjs/vue-vben-admin/issues/3489)) ([d88f455](https://github.com/vbenjs/vue-vben-admin/commit/d88f455cd3530cd7cc7450e8f0fe7744ca0cb313)) +- **BasicTable:** table表格宽度自适应,隐藏的列导致宽度增加 ([#3388](https://github.com/vbenjs/vue-vben-admin/issues/3388)) ([bca5154](https://github.com/vbenjs/vue-vben-admin/commit/bca5154a9d3d020a70cd332ac19c853ea94405e2)) +- **BasicTree:** not inherit slot and not show icon slot. close [#1902](https://github.com/vbenjs/vue-vben-admin/issues/1902) ([a0b2a9e](https://github.com/vbenjs/vue-vben-admin/commit/a0b2a9e949dbf1e149da8be757f78fa6b1cebec0)) +- breadcrumb is displayed despite the menu being hidden ([#3733](https://github.com/vbenjs/vue-vben-admin/issues/3733)) ([e8a86ec](https://github.com/vbenjs/vue-vben-admin/commit/e8a86ec8b996387c9c2d167344eb1e7a010d94fb)), closes [#3690](https://github.com/vbenjs/vue-vben-admin/issues/3690) +- **breadcrumb:** 修复面包屑跳转外链时,导致当前页面404问题 ([#3337](https://github.com/vbenjs/vue-vben-admin/issues/3337)). close [#3336](https://github.com/vbenjs/vue-vben-admin/issues/3336) ([895352a](https://github.com/vbenjs/vue-vben-admin/commit/895352ad221f91bdb57fdfd0396925fb7901c2df)) +- breakpoint ([#3242](https://github.com/vbenjs/vue-vben-admin/issues/3242)) ([b6f8379](https://github.com/vbenjs/vue-vben-admin/commit/b6f8379e936df3743c5b9514e88a02148b08a5d1)) +- bug RangePicker with componentProps valueFormat ('YYYY-MM-DD') does not return the formatted value when using form validate() method [#3690](https://github.com/vbenjs/vue-vben-admin/issues/3690) ([#3691](https://github.com/vbenjs/vue-vben-admin/issues/3691)) ([09f795e](https://github.com/vbenjs/vue-vben-admin/commit/09f795e00ed8d56d6b1e028fc40974d3292eed5f)) +- change salesProductPie.vue's data name to '成交占比'. ([#3524](https://github.com/vbenjs/vue-vben-admin/issues/3524)) ([0589458](https://github.com/vbenjs/vue-vben-admin/commit/0589458b2d120b5c6ee0ab10cd8b2d3f13318911)) +- checkedKeys use unref bug ([#3198](https://github.com/vbenjs/vue-vben-admin/issues/3198)) ([ae61fa1](https://github.com/vbenjs/vue-vben-admin/commit/ae61fa11865b2d7b0337b1fd2edcb20bf559a71f)) +- **ci:** update node version for linter ([a5565bf](https://github.com/vbenjs/vue-vben-admin/commit/a5565bf9cf361e38057d8ca34fe2542ea1c39873)) +- **ci:** update use pnpm version for deploy ([83455a0](https://github.com/vbenjs/vue-vben-admin/commit/83455a07a0821142864b4668d89a51057d74330b)) +- column setting index column sort ([#3463](https://github.com/vbenjs/vue-vben-admin/issues/3463)) ([fc002d3](https://github.com/vbenjs/vue-vben-admin/commit/fc002d3db327a432259eb02e2a72b9b81381eb6e)) +- **component->markdown:** 浏览器媒体获取兼容 ([#3470](https://github.com/vbenjs/vue-vben-admin/issues/3470)) ([f0ca8d5](https://github.com/vbenjs/vue-vben-admin/commit/f0ca8d5a03e994c9948b055dc5f69dad40d96922)) +- **component:** insertNextAt is not a function ([#3656](https://github.com/vbenjs/vue-vben-admin/issues/3656)) ([#3657](https://github.com/vbenjs/vue-vben-admin/issues/3657)) ([c827ffb](https://github.com/vbenjs/vue-vben-admin/commit/c827ffb8e680df13dae0c75b19634b6474aa5897)) +- **component:** resolve the defaultValue error in setting the date type ([#3652](https://github.com/vbenjs/vue-vben-admin/issues/3652)) ([d42acb4](https://github.com/vbenjs/vue-vben-admin/commit/d42acb477c577c0f855403bff371e0780e630cdc)), closes [#3651](https://github.com/vbenjs/vue-vben-admin/issues/3651) +- **component:** resolve the error when clicking on a row when clickRowToExpand is true ([#3714](https://github.com/vbenjs/vue-vben-admin/issues/3714)) ([38d58ab](https://github.com/vbenjs/vue-vben-admin/commit/38d58ab47af3611cebade960dbcbd60f165f3a72)) +- **component:** resolve the issue of "vxe-table" export function not being able to be used ([#3646](https://github.com/vbenjs/vue-vben-admin/issues/3646)) ([bc5e5fa](https://github.com/vbenjs/vue-vben-admin/commit/bc5e5fa015f8c5f1d69f3f8572a1957d83cfe44e)), closes [#3614](https://github.com/vbenjs/vue-vben-admin/issues/3614) +- **component:** resovle fullscreen content issues with "fixedHeight" and "contentFullHeight" combined ([#3721](https://github.com/vbenjs/vue-vben-admin/issues/3721)) ([212e78f](https://github.com/vbenjs/vue-vben-admin/commit/212e78fa76132b4239e81d3432f7f3c15d7e5254)) +- **components->Upload:** 修复文件上传过程中删除文件终止上传时,上传状态未改变不能关闭Modal的bug ([#3761](https://github.com/vbenjs/vue-vben-admin/issues/3761)) ([04d4c5c](https://github.com/vbenjs/vue-vben-admin/commit/04d4c5cd665f52cb287b5059b301e71aed75b3df)) +- **config:** vite:html warning ([#3518](https://github.com/vbenjs/vue-vben-admin/issues/3518)) ([6978517](https://github.com/vbenjs/vue-vben-admin/commit/6978517a3a0b522a466fc248d0ed4384cdcfecb7)) +- content fixed mode with blank page ([#3523](https://github.com/vbenjs/vue-vben-admin/issues/3523)) ([49fdb6c](https://github.com/vbenjs/vue-vben-admin/commit/49fdb6c986f36e45fc9f9e206af371f8eb3581f6)) +- contextmenu location not right when body with scroll ([#3516](https://github.com/vbenjs/vue-vben-admin/issues/3516)) ([c2c9f4f](https://github.com/vbenjs/vue-vben-admin/commit/c2c9f4f556d65eb768a5202a398dc9684c08d577)) +- Correct spelling error in comments ([#3678](https://github.com/vbenjs/vue-vben-admin/issues/3678)) ([54f8584](https://github.com/vbenjs/vue-vben-admin/commit/54f85844436d95ce290a2f3b5db5b7c4fd14cc0d)) +- **CropperAvatar:** wrong type about the prop size ([#3635](https://github.com/vbenjs/vue-vben-admin/issues/3635)) ([aef90aa](https://github.com/vbenjs/vue-vben-admin/commit/aef90aa2a0b43c3fc72596d9aecf408a594cb6fd)) +- **CropperModal:** beforeUpload should return false ([#3601](https://github.com/vbenjs/vue-vben-admin/issues/3601)) ([b6bcf8d](https://github.com/vbenjs/vue-vben-admin/commit/b6bcf8d36dc348af7b51782e43c09c5e1cfeed68)) +- **customExport:** Failure to export ([#3137](https://github.com/vbenjs/vue-vben-admin/issues/3137)) ([4d02205](https://github.com/vbenjs/vue-vben-admin/commit/4d02205839aad840bbb247b26b7e1dcdbc8b3a67)) +- **dark:** fix --text-color light color not work ([#3228](https://github.com/vbenjs/vue-vben-admin/issues/3228)) ([2e632e4](https://github.com/vbenjs/vue-vben-admin/commit/2e632e4d4d8af4b8766396b340f9996832310853)) +- **DatePicker:** date show is wrong and setup script defineExpose ([#3324](https://github.com/vbenjs/vue-vben-admin/issues/3324)) ([f62043b](https://github.com/vbenjs/vue-vben-admin/commit/f62043b1fca82234e04aa04629ba4bbcc624b0ee)) +- **DatePicker:** zh-CN is not work in DatePicker ([#3273](https://github.com/vbenjs/vue-vben-admin/issues/3273)) ([6d047fb](https://github.com/vbenjs/vue-vben-admin/commit/6d047fb53fb113a4c6fc5c46c5b4195e50744eb0)) +- defaultValue类型为number时的bug ([#3288](https://github.com/vbenjs/vue-vben-admin/issues/3288)) ([3b2760c](https://github.com/vbenjs/vue-vben-admin/commit/3b2760ca3ae5b51989ea7e97713f629a567fe53a)) +- **demo->customerForm:** FormItem下有多个受控组件控制台显示错误提示的bug ([#3238](https://github.com/vbenjs/vue-vben-admin/issues/3238)) ([ec646c5](https://github.com/vbenjs/vue-vben-admin/commit/ec646c57b8c2d365f5ce298c496e1efaad8456b7)) +- **demo:** 修复引导页文件名问题 ([#3352](https://github.com/vbenjs/vue-vben-admin/issues/3352)) ([be935eb](https://github.com/vbenjs/vue-vben-admin/commit/be935eb44e363665b99288f78f03cb3053274200)) +- **demo:** 修复form demo的远程搜索不会触发的bug ([#3770](https://github.com/vbenjs/vue-vben-admin/issues/3770)) ([44b1877](https://github.com/vbenjs/vue-vben-admin/commit/44b1877eaedca1716aa40b8d819d5fe7cbd26ba0)) +- **demo:** account page table without dept ([#3164](https://github.com/vbenjs/vue-vben-admin/issues/3164)) ([40aac65](https://github.com/vbenjs/vue-vben-admin/commit/40aac6544cb9b22e2362f2a2d99c0a9cc2f7fb57)) +- **demo:** useForm中DatePicker,RangePicker 日期控件位置不对 ([ae58ada](https://github.com/vbenjs/vue-vben-admin/commit/ae58ada82e2f62cea8e89a18c9f2ab18d6dba52a)) +- **dept:** no parentDept can edit parentDept ([#3255](https://github.com/vbenjs/vue-vben-admin/issues/3255)) ([2142506](https://github.com/vbenjs/vue-vben-admin/commit/2142506ce5225dbe19118fcedb2cb48612c58fa7)) +- Docker 打包逻辑改进,彻底解决缓存问题 ([#3473](https://github.com/vbenjs/vue-vben-admin/issues/3473)) ([dcbe551](https://github.com/vbenjs/vue-vben-admin/commit/dcbe5510d42c3dc5e93b68704a5fa24c88d4de69)) +- EditableCell about checked/unChecked Value, getDisable, rowKey missing for updating ([#3418](https://github.com/vbenjs/vue-vben-admin/issues/3418)). resolve [#3419](https://github.com/vbenjs/vue-vben-admin/issues/3419) ([404a472](https://github.com/vbenjs/vue-vben-admin/commit/404a4720b016abb6da93cb0b03271ebea6cca976)) +- **EditCellTable:** 表格编辑行在使用Switch,checkedValue为数字时无法切换开关.close [#2560](https://github.com/vbenjs/vue-vben-admin/issues/2560) ([71c4394](https://github.com/vbenjs/vue-vben-admin/commit/71c43945a5e95fda6daddbb90af51f6d149547f5)) +- **Editor:** ts类型错误 ([8b13f62](https://github.com/vbenjs/vue-vben-admin/commit/8b13f62995cb7f080eabf2e22d530959d4b79e0b)) +- Failed to resolve component EllipsisText ([#3330](https://github.com/vbenjs/vue-vben-admin/issues/3330)) ([42e9de5](https://github.com/vbenjs/vue-vben-admin/commit/42e9de50a2bb150556a0eb8098acb2d2d3662c3c)) +- FormItem won't render when a component is not provided ([9e055ad](https://github.com/vbenjs/vue-vben-admin/commit/9e055ad2734819c2778c0db1990e69b67c95022f)) +- **FormItem:** use getPopupContainer default value ([#3215](https://github.com/vbenjs/vue-vben-admin/issues/3215)) ([ed267d9](https://github.com/vbenjs/vue-vben-admin/commit/ed267d9c016e50b4d9a7886209bad2432a88ad9b)) +- **FormTable:** Invert select bug ([#3412](https://github.com/vbenjs/vue-vben-admin/issues/3412)) ([595b1ce](https://github.com/vbenjs/vue-vben-admin/commit/595b1ce680d8b315589d98036a70333055123b18)) +- **full-screen:** dom fullscreen status text ([#3130](https://github.com/vbenjs/vue-vben-admin/issues/3130)) ([e161c14](https://github.com/vbenjs/vue-vben-admin/commit/e161c1449ac3d097cd6eab9441b25de25d3aa27e)) +- fullscreen-modal width wrong ([#3321](https://github.com/vbenjs/vue-vben-admin/issues/3321)) ([617b013](https://github.com/vbenjs/vue-vben-admin/commit/617b01338cf4a8d88da529dae7da878b52b30c3b)) +- handleFormValues 不再将所有空字符串转换为undefined ([#3496](https://github.com/vbenjs/vue-vben-admin/issues/3496)) ([6fbb576](https://github.com/vbenjs/vue-vben-admin/commit/6fbb57621e6ff79f93830969ab388549cbec5d32)) +- **koa->upload:** fix the error that occurs when uploading files in the `test server` of Koa. ([#3698](https://github.com/vbenjs/vue-vben-admin/issues/3698)) ([954f04f](https://github.com/vbenjs/vue-vben-admin/commit/954f04f1c8e941e45c2bd4ab55df331ba13cb89c)) +- **layout->menu:** can`t hover when menu is colappsed ([#3499](https://github.com/vbenjs/vue-vben-admin/issues/3499)) resolve [#3492](https://github.com/vbenjs/vue-vben-admin/issues/3492) ([7ffe172](https://github.com/vbenjs/vue-vben-admin/commit/7ffe1726b9ac0ac1f90c20b53996636f31af5c31)) +- **layout->user-dropdown:** resolve warning "Invalid prop name: key is a reserved property" ([#3640](https://github.com/vbenjs/vue-vben-admin/issues/3640)) ([eae68bb](https://github.com/vbenjs/vue-vben-admin/commit/eae68bb029864d5f1af4e0c6b57038d0c96e4faf)), closes [#3639](https://github.com/vbenjs/vue-vben-admin/issues/3639) +- **layout:** 修复切换导航栏模式,分割菜单的状态不同步,导致页面内容区域存在被遮挡的问题 ([#3519](https://github.com/vbenjs/vue-vben-admin/issues/3519)) ([50276cb](https://github.com/vbenjs/vue-vben-admin/commit/50276cb60275d15c2d370c5a1be2705067c7b275)) +- **LayoutSidre:** resolve the breakpoint conflict. resolve [#3605](https://github.com/vbenjs/vue-vben-admin/issues/3605) ([1a7ae0e](https://github.com/vbenjs/vue-vben-admin/commit/1a7ae0e81071876fe6a5cf2ef00bb61cbca70736)) +- **LockModal:** Cannot unlock ([#3143](https://github.com/vbenjs/vue-vben-admin/issues/3143)) ([cdac147](https://github.com/vbenjs/vue-vben-admin/commit/cdac147bc8d09fba6ac14ff1ed31ab4d5b5cb28b)) +- **Login:** avoid infinite loop when query redirect to next route redirect ([#3630](https://github.com/vbenjs/vue-vben-admin/issues/3630)). resolve [#3620](https://github.com/vbenjs/vue-vben-admin/issues/3620) [#3627](https://github.com/vbenjs/vue-vben-admin/issues/3627) ([ab55cbf](https://github.com/vbenjs/vue-vben-admin/commit/ab55cbf99bd9891f10546176e203eb4ea8cafaa4)) +- **Menu:** tab标签切换选中状态焦点重复. fix [#1681](https://github.com/vbenjs/vue-vben-admin/issues/1681) ([2ec5f63](https://github.com/vbenjs/vue-vben-admin/commit/2ec5f6322d036ea5d6968c16961d2c253e1cef06)) +- **menu:** top menu and breadcrumb show wrong ([#3703](https://github.com/vbenjs/vue-vben-admin/issues/3703)) ([573fd53](https://github.com/vbenjs/vue-vben-admin/commit/573fd53b4e287524ffa6ec205b68812b657dde71)) +- modal open logic missing ([#3462](https://github.com/vbenjs/vue-vben-admin/issues/3462)) ([cc97f06](https://github.com/vbenjs/vue-vben-admin/commit/cc97f0635438c102b97102a1e0a9d5550961d6fa)) +- **Modal:** 修复BasicModal跟原生Modal样式冲突问题 ([#3720](https://github.com/vbenjs/vue-vben-admin/issues/3720)) ([ade6d4c](https://github.com/vbenjs/vue-vben-admin/commit/ade6d4c22dd62aa666fe934a747e2a3764feb7cd)) +- modalElIterator可能为空,导致报错 ([#3738](https://github.com/vbenjs/vue-vben-admin/issues/3738)) ([162a0d0](https://github.com/vbenjs/vue-vben-admin/commit/162a0d025252ff954f081d126c381e5fefd06e83)) +- navigator.clipboard 兼容问题 [#3372](https://github.com/vbenjs/vue-vben-admin/issues/3372) ([#3403](https://github.com/vbenjs/vue-vben-admin/issues/3403)) ([d3600da](https://github.com/vbenjs/vue-vben-admin/commit/d3600daf5c55cc884f4a0311ee7335bdad529a1a)) +- **PageWrapper:** 修复headerSticky样式 ([#3569](https://github.com/vbenjs/vue-vben-admin/issues/3569)) ([778ebe1](https://github.com/vbenjs/vue-vben-admin/commit/778ebe1f44bc4929f34fad41b5fec13e9270b517)) +- **PopConfirmButton:** avoid type lint error ([#3600](https://github.com/vbenjs/vue-vben-admin/issues/3600)) ([5ec4446](https://github.com/vbenjs/vue-vben-admin/commit/5ec444644384c6a1b8d559e0e21dfa814e5af635)) +- remove duplicate code ([#3674](https://github.com/vbenjs/vue-vben-admin/issues/3674)) ([c33ee66](https://github.com/vbenjs/vue-vben-admin/commit/c33ee66473159a0322b7c4489538dd5309587e45)) +- repair login about redirect query ([#3592](https://github.com/vbenjs/vue-vben-admin/issues/3592)) ([236ddf3](https://github.com/vbenjs/vue-vben-admin/commit/236ddf3471a7851ff6541f5709e9cbb6105b58f7)) +- resolve conflicts between eslint and prettier and bump prettier-plugin-packagejson version to 2.4.6([#3328](https://github.com/vbenjs/vue-vben-admin/issues/3328)) ([8a00070](https://github.com/vbenjs/vue-vben-admin/commit/8a000705d1c89194647a11d01a620c1a893d2643)) +- **router:** resolve menu loading failure when permission is in "role mode" ([#3660](https://github.com/vbenjs/vue-vben-admin/issues/3660)) ([c7631fe](https://github.com/vbenjs/vue-vben-admin/commit/c7631fed681da0dd51583cf3ecba60ebfd76ec4d)), closes [#3655](https://github.com/vbenjs/vue-vben-admin/issues/3655) +- **router:** resolve the next function being called twice ([#3643](https://github.com/vbenjs/vue-vben-admin/issues/3643)) ([7ec9344](https://github.com/vbenjs/vue-vben-admin/commit/7ec9344be8e80899749f41d173c1a11db267354e)), closes [#3642](https://github.com/vbenjs/vue-vben-admin/issues/3642) +- **router:** the issue of blank page navigation during repair of non-LAYOUT first-level route component ([#3764](https://github.com/vbenjs/vue-vben-admin/issues/3764)) ([c58c192](https://github.com/vbenjs/vue-vben-admin/commit/c58c1929c1e8a41c15ff2f4f4398eba0fd375b69)) +- scroll back to top when tab switch ([#3498](https://github.com/vbenjs/vue-vben-admin/issues/3498)). resolve [#3490](https://github.com/vbenjs/vue-vben-admin/issues/3490) ([d709dd6](https://github.com/vbenjs/vue-vben-admin/commit/d709dd67b50a2e2b88cbcce48f19085ce528f971)) +- scrollbar is obscured ([#3331](https://github.com/vbenjs/vue-vben-admin/issues/3331)) ([3f65baf](https://github.com/vbenjs/vue-vben-admin/commit/3f65baf503bee677bde70535525ae514d0585d69)) +- ScrollContainer的一个问题 [#3046](https://github.com/vbenjs/vue-vben-admin/issues/3046) ([#3119](https://github.com/vbenjs/vue-vben-admin/issues/3119)) ([aa03c87](https://github.com/vbenjs/vue-vben-admin/commit/aa03c87383c703ddce7759bd7ba114709f2f5241)) +- **ScrollContainer:** enable x scroll ([#3564](https://github.com/vbenjs/vue-vben-admin/issues/3564)) ([a1e862b](https://github.com/vbenjs/vue-vben-admin/commit/a1e862bde7a29420fd1de6083c5bde02bef8e9fd)) +- **SimpleMenuTag:** SimpleMenuTag的引用都改为动态组件引用,以消除打包警告.close [#3121](https://github.com/vbenjs/vue-vben-admin/issues/3121) ([6e33c26](https://github.com/vbenjs/vue-vben-admin/commit/6e33c268930ad2a99311f0f2b6eb4b90f3cf61ce)) +- **StrengthMeter:** change事件应随handleChange一起抛出。close [#3118](https://github.com/vbenjs/vue-vben-admin/issues/3118) ([f5ce480](https://github.com/vbenjs/vue-vben-admin/commit/f5ce480f0fb2f97de57e26a01945b715938f6e18)) +- **style:** 修复黑暗模式下弹框、demo目录下、按钮样式问题 ([#3208](https://github.com/vbenjs/vue-vben-admin/issues/3208)) ([06a6c94](https://github.com/vbenjs/vue-vben-admin/commit/06a6c947a980530be6654f05357bc3c721c1140b)) +- tabel取消编辑单元格后会回到初始值. close [#2739](https://github.com/vbenjs/vue-vben-admin/issues/2739) ([#3108](https://github.com/vbenjs/vue-vben-admin/issues/3108)) ([9864305](https://github.com/vbenjs/vue-vben-admin/commit/986430513bc2da9a2ad88d40c026b0373bf22d07)) +- table height calc when fullcontent and footer visible change ([#3392](https://github.com/vbenjs/vue-vben-admin/issues/3392)) ([20698c0](https://github.com/vbenjs/vue-vben-admin/commit/20698c052c2b696587ac77ba7c85d74abc974ed5)) +- table index column width is not enough in english ([#3342](https://github.com/vbenjs/vue-vben-admin/issues/3342)) ([0f13758](https://github.com/vbenjs/vue-vben-admin/commit/0f137585542ee2cf3b542a4ca4d94e76aadfb3d0)) +- TableAction设置icon显示iconify关键字 ([#3608](https://github.com/vbenjs/vue-vben-admin/issues/3608)) ([b233973](https://github.com/vbenjs/vue-vben-admin/commit/b2339739746740eaaf4adee3ef16757f3b05ec86)) +- **test-server:** test-server can not lanuch ([#3554](https://github.com/vbenjs/vue-vben-admin/issues/3554)) ([e679704](https://github.com/vbenjs/vue-vben-admin/commit/e6797043c53abb8280ff9c86e32663450a650076)) +- **tree:** remove expandedKeys prop default value ([#3184](https://github.com/vbenjs/vue-vben-admin/issues/3184)) ([92875cb](https://github.com/vbenjs/vue-vben-admin/commit/92875cbeccf6213bcad9acd4e322cdee59e35266)) +- turbo run lint ([#3332](https://github.com/vbenjs/vue-vben-admin/issues/3332)) ([064922d](https://github.com/vbenjs/vue-vben-admin/commit/064922dd4c3ae674a463e4b9453a4ca8bf2a52a0)), closes [#3277](https://github.com/vbenjs/vue-vben-admin/issues/3277) +- **type:** type:check error ([#3309](https://github.com/vbenjs/vue-vben-admin/issues/3309)) ([2cd5a40](https://github.com/vbenjs/vue-vben-admin/commit/2cd5a40322e9b7946e789d7c5023fda0e712af4d)) +- typo in locale ([#3659](https://github.com/vbenjs/vue-vben-admin/issues/3659)) ([a4cc1d5](https://github.com/vbenjs/vue-vben-admin/commit/a4cc1d53169bc26e01862ca4ff88e28d5329ab8f)) +- typo substract -> subtract ([#3551](https://github.com/vbenjs/vue-vben-admin/issues/3551)) ([f3fbb57](https://github.com/vbenjs/vue-vben-admin/commit/f3fbb57dc944a11a06be2eff376d77f9cef29813)) +- **typo:** fileservice class name ([#3625](https://github.com/vbenjs/vue-vben-admin/issues/3625)) ([b794469](https://github.com/vbenjs/vue-vben-admin/commit/b7944690d118377a32e694fa30081b1e43c71719)) +- Update TableAction.vue ([#3619](https://github.com/vbenjs/vue-vben-admin/issues/3619)) ([76ffd8f](https://github.com/vbenjs/vue-vben-admin/commit/76ffd8fdf1ecc37a30f33c901ff199ba21f88ad4)) +- **Upload:** The file name is too long bug ([#3182](https://github.com/vbenjs/vue-vben-admin/issues/3182)) ([e7fbd74](https://github.com/vbenjs/vue-vben-admin/commit/e7fbd742287928112b318bf966e57c74f6a8ee72)) +- **useFormEvent:** 修复表单项存在defaultValue时,updateSchema方法会将setFieldsValue设置的值覆盖问题 ([#3287](https://github.com/vbenjs/vue-vben-admin/issues/3287)) ([72ef3df](https://github.com/vbenjs/vue-vben-admin/commit/72ef3df57fe978c0b8e185f0d632a59e128a390f)) +- **useFormEvents:** 修复setFieldsValue 方法设置完值后,函数 componentProps丢失formActionType 的bug ([#3301](https://github.com/vbenjs/vue-vben-admin/issues/3301)) ([82671d0](https://github.com/vbenjs/vue-vben-admin/commit/82671d07502adbaa06decbe0caa51ca2ba2e5149)) +- **util:** resolve executing retry even when HTTP status code is 401 ([#3756](https://github.com/vbenjs/vue-vben-admin/issues/3756)) ([3627402](https://github.com/vbenjs/vue-vben-admin/commit/36274025d6a19de978bcaaae3f0ddb42f31ecd2f)) +- validateFields await missing ([#3254](https://github.com/vbenjs/vue-vben-admin/issues/3254)) ([71c3fea](https://github.com/vbenjs/vue-vben-admin/commit/71c3fea88afa9209f080458bbd7429b1d37baa2c)) +- **VFormDesign:** findIndex === -1 ([#3305](https://github.com/vbenjs/vue-vben-admin/issues/3305)) ([d7472b8](https://github.com/vbenjs/vue-vben-admin/commit/d7472b8a2e480299888ffeac6ac95466b27afa0f)) +- **vxe-table:** theme dark is not work ([#3239](https://github.com/vbenjs/vue-vben-admin/issues/3239)) ([031d613](https://github.com/vbenjs/vue-vben-admin/commit/031d613b18125c877ebaa4a3241c3b4bf9c7624a)) +- watch open logic lost after build ([#3421](https://github.com/vbenjs/vue-vben-admin/issues/3421)) ([089a989](https://github.com/vbenjs/vue-vben-admin/commit/089a98953e01d6b7cb0003f33d321035e19311c5)) + +### Features + +- 解决Form updateSchema后执行setProps导致schemaRef被重置的问题 ([#3354](https://github.com/vbenjs/vue-vben-admin/issues/3354)) ([b0e8154](https://github.com/vbenjs/vue-vben-admin/commit/b0e8154f9f23db71dba7302395cc7d3db4b4a339)) +- 新增表单只读功能 ([#3335](https://github.com/vbenjs/vue-vben-admin/issues/3335)) ([342328c](https://github.com/vbenjs/vue-vben-admin/commit/342328ce5f06b33dabdbd4a3bdd2c846f011eb03)) +- 修复 vxetable 实例中缺少的 getRefMaps 和 getComputeMaps 方法 ([#3361](https://github.com/vbenjs/vue-vben-admin/issues/3361)) ([2376e8f](https://github.com/vbenjs/vue-vben-admin/commit/2376e8f67d154fe902452c6c1af19248402602b7)) +- 增加文本省略组件 ([#3180](https://github.com/vbenjs/vue-vben-admin/issues/3180)) ([8722471](https://github.com/vbenjs/vue-vben-admin/commit/87224715c3983227866f148d858276c1234f77e0)) +- 支持设置多重水印,增加清除所有水印方法. close [#2610](https://github.com/vbenjs/vue-vben-admin/issues/2610) ([#3084](https://github.com/vbenjs/vue-vben-admin/issues/3084)) ([64b8128](https://github.com/vbenjs/vue-vben-admin/commit/64b812802f5ca053c8f0ae9c9f159cbfedb32d5d)) +- add pinia persist plugin ([#3173](https://github.com/vbenjs/vue-vben-admin/issues/3173)) ([2152b3f](https://github.com/vbenjs/vue-vben-admin/commit/2152b3f779b47f70e4442ff4c850db10048733d3)) +- **ApiTree:** 完善ApiTree组件的重置回显功能. close [#2307](https://github.com/vbenjs/vue-vben-admin/issues/2307) ([a0d4b10](https://github.com/vbenjs/vue-vben-admin/commit/a0d4b10a1f0f858925ce7fff3f9b686308fd3ada)) +- **BasicButton:** BasicButton组件支持icon插槽. close [#1377](https://github.com/vbenjs/vue-vben-admin/issues/1377) ([5aac032](https://github.com/vbenjs/vue-vben-admin/commit/5aac032acc294e087e45d5787cbae843d5a86f28)) +- **BasicForm:** 新增监听表单收缩方法传值进行判断 ([#3745](https://github.com/vbenjs/vue-vben-admin/issues/3745)) ([e9c6dd8](https://github.com/vbenjs/vue-vben-admin/commit/e9c6dd83b1b21bd46fbb7b16f159733761b49fd2)) +- **BasicForm:** Improve ts types for BasicForm ([#3426](https://github.com/vbenjs/vue-vben-admin/issues/3426)) ([6bb7918](https://github.com/vbenjs/vue-vben-admin/commit/6bb79180fc798b1a0b1a6c22f7c13ddb3a45a3b5)) +- **BasicTable:** 新增表格搜索获取参数的方法 ([#3715](https://github.com/vbenjs/vue-vben-admin/issues/3715)) ([de5f9e3](https://github.com/vbenjs/vue-vben-admin/commit/de5f9e304791ce131a589683282b42f77d10238c)) +- **BasicTable:** table enable accordion expand ([#3533](https://github.com/vbenjs/vue-vben-admin/issues/3533)). resolve [#3525](https://github.com/vbenjs/vue-vben-admin/issues/3525) ([abae7f3](https://github.com/vbenjs/vue-vben-admin/commit/abae7f3295846f10b69c591739bbec22d176b6fe)) +- **BasicTree:** BasicTree组件暴露treeData数据 ([caf1783](https://github.com/vbenjs/vue-vben-admin/commit/caf178352537650f603aca441386b377f7cf8821)) +- ColumnSetting and SizeSetting persist ([#3398](https://github.com/vbenjs/vue-vben-admin/issues/3398)) ([f4df2d5](https://github.com/vbenjs/vue-vben-admin/commit/f4df2d5a4bd23f346af10580e5ab7de64933bb4c)) +- **components->Upload:** 修正图片上传组件允许自定义上传格式限制 ([#3755](https://github.com/vbenjs/vue-vben-admin/issues/3755)) ([302e212](https://github.com/vbenjs/vue-vben-admin/commit/302e2125ba1c7e63e7db47ef6e1ee256da9f81d4)) +- **demo->BasicTable:** add TableSelectionBar and enable checkbox rowSelection demo ([#3477](https://github.com/vbenjs/vue-vben-admin/issues/3477)) ([816553b](https://github.com/vbenjs/vue-vben-admin/commit/816553bfcd5a88988c6e7b93cc3378f4a5b17fee)) +- **demo->useRequest:** 更新错误重试示例 ([#3456](https://github.com/vbenjs/vue-vben-admin/issues/3456)) ([7fa2578](https://github.com/vbenjs/vue-vben-admin/commit/7fa2578e6d578e7ab44793fcb2b41189a7ba9af3)) +- **demo->useRequest:** 更新useRequest 依赖 Effect 函数案例 ([#3460](https://github.com/vbenjs/vue-vben-admin/issues/3460)) ([b57d9fc](https://github.com/vbenjs/vue-vben-admin/commit/b57d9fc60dd113607cd381dff1f88c47671fc434)) +- **demo:** hooks useRequest 异步数据管理 ([#3447](https://github.com/vbenjs/vue-vben-admin/issues/3447)) ([d6d1120](https://github.com/vbenjs/vue-vben-admin/commit/d6d1120d00a24b7a97bd37f6a0786c1265fa870d)) +- **deps:** update vite version to 5.x ([#3508](https://github.com/vbenjs/vue-vben-admin/issues/3508)) ([e6c7b5f](https://github.com/vbenjs/vue-vben-admin/commit/e6c7b5f9282adbdd9429495d5918c1eafbffde7d)) +- fix ellipsis bug ([#3644](https://github.com/vbenjs/vue-vben-admin/issues/3644)) ([9372f1d](https://github.com/vbenjs/vue-vben-admin/commit/9372f1d159bb3236810e469defe6986b924d2264)) +- **Form:** 新增Transfer、CropperAvatar、BasicTitle 组件至Form中,并添加至演示页面 ([#3362](https://github.com/vbenjs/vue-vben-admin/issues/3362)) ([f6147fa](https://github.com/vbenjs/vue-vben-admin/commit/f6147fa44985d149692b5c6053a49b76d24767cc)) +- **Form:** 在Form将BasicTitle识做为Divider一样的处理 ([#3371](https://github.com/vbenjs/vue-vben-admin/issues/3371)) ([cd71e60](https://github.com/vbenjs/vue-vben-admin/commit/cd71e60dd16e87706deb2d06310bc507c55d6a99)) +- Form增加ImageUpload组件 ([#3172](https://github.com/vbenjs/vue-vben-admin/issues/3172)) ([b776ac4](https://github.com/vbenjs/vue-vben-admin/commit/b776ac4cd8a4895804b554949f39c82c03382006)) +- **hooks:** useWatermark添加水印防篡改功能([#3395](https://github.com/vbenjs/vue-vben-admin/issues/3395)) ([#3397](https://github.com/vbenjs/vue-vben-admin/issues/3397)) ([0a1a5ff](https://github.com/vbenjs/vue-vben-admin/commit/0a1a5ffedc58190529617c9267c6510dc7e17ca9)) +- **IconPicker:** IconPicker could allowClear and readonly for form ([#3414](https://github.com/vbenjs/vue-vben-admin/issues/3414)) ([e23f294](https://github.com/vbenjs/vue-vben-admin/commit/e23f29464bd609d6bc7228a5940032d693b006aa)) +- iframe expose postmessage function ([#3368](https://github.com/vbenjs/vue-vben-admin/issues/3368)) ([05bc4ac](https://github.com/vbenjs/vue-vben-admin/commit/05bc4acb9b2107bfad8200c0f1942a76367d7a00)) +- **input:** add auto-trimming for vxe-table input components ([#3684](https://github.com/vbenjs/vue-vben-admin/issues/3684)) ([9882e8d](https://github.com/vbenjs/vue-vben-admin/commit/9882e8df86cc68373cf0f1fa4f4aa04a9773825e)) +- **layout->tabs:** support insert new tab after current tab ([#3471](https://github.com/vbenjs/vue-vben-admin/issues/3471)) ([1e34d3e](https://github.com/vbenjs/vue-vben-admin/commit/1e34d3e9e4c79a62a16c6209639f5394dea61148)) +- **layout:** move setting button to tabs when fold ([#3264](https://github.com/vbenjs/vue-vben-admin/issues/3264)) ([83426b5](https://github.com/vbenjs/vue-vben-admin/commit/83426b5c96a88c3a6aa399eb6100ea5fb494fd0e)) +- **Menu:** Add custom images to menu ([#3158](https://github.com/vbenjs/vue-vben-admin/issues/3158)) ([b3a6ef6](https://github.com/vbenjs/vue-vben-admin/commit/b3a6ef63bb500d8103bde17d588b3c0c4c77efd6)) +- **menu:** Restore side bar settings and added menu mouse move in mode light style ([#3295](https://github.com/vbenjs/vue-vben-admin/issues/3295)) ([003a951](https://github.com/vbenjs/vue-vben-admin/commit/003a951befb9ef2878a35fbe3054252d0bc8e77e)) +- **MultipleTab:** add tabs auto collapse interaction in fold mode and setting ([#3256](https://github.com/vbenjs/vue-vben-admin/issues/3256)) ([191e809](https://github.com/vbenjs/vue-vben-admin/commit/191e809b6d696d6e0b72c67ba1c7e89c721f2642)) +- pinia persist plugin custom serializer ([#3244](https://github.com/vbenjs/vue-vben-admin/issues/3244)) ([ea51c49](https://github.com/vbenjs/vue-vben-admin/commit/ea51c492c2ba56aa6693217e24f60dc143f124f1)) +- RefForm页面新增只读功能按钮 ([#3346](https://github.com/vbenjs/vue-vben-admin/issues/3346)) ([97b76ea](https://github.com/vbenjs/vue-vben-admin/commit/97b76ea6bc902d00c87c5cbd1cb2d67aa9a88347)) +- **search:** adjust the menu search function to recognize lowercase input ([#3736](https://github.com/vbenjs/vue-vben-admin/issues/3736)) ([96ac362](https://github.com/vbenjs/vue-vben-admin/commit/96ac362fa6a89af256cf8a84e4b667c195c6ea2e)) +- **Table-> CustomerCell:** helpMessage支持传递 tsx 和 h函数的数据 ([c373ffd](https://github.com/vbenjs/vue-vben-admin/commit/c373ffd3bfb7db454e0501b2f7b138ed1a9ec657)) +- table搜索表单值发生改变可以触发reload ([#3378](https://github.com/vbenjs/vue-vben-admin/issues/3378)) ([1ca3f7c](https://github.com/vbenjs/vue-vben-admin/commit/1ca3f7c2c0c0c00e8395e26c59796c875d34e12c)) +- **treeTable:** add function collapseRows and demo ([#3375](https://github.com/vbenjs/vue-vben-admin/issues/3375)) ([e656b5d](https://github.com/vbenjs/vue-vben-admin/commit/e656b5d8dcde38deeedc73d713d2367e54d2893b)) +- **type->api:** resultField推断api的返回值应该包含recordbale类型 ([#3699](https://github.com/vbenjs/vue-vben-admin/issues/3699)) ([c7ab4a5](https://github.com/vbenjs/vue-vben-admin/commit/c7ab4a52989256ccae996191cc249d9cbb36e6d6)) +- **Upload:** file list add drag func ([#3227](https://github.com/vbenjs/vue-vben-admin/issues/3227)). resolve [#3179](https://github.com/vbenjs/vue-vben-admin/issues/3179) ([beed7f2](https://github.com/vbenjs/vue-vben-admin/commit/beed7f2e1172531fe691384a50c8b0457f3a80d8)) +- **VirtualScroll:** 虚拟滚动增加滚动到顶部, 底部, 指定项方法 ([#3687](https://github.com/vbenjs/vue-vben-admin/issues/3687)) ([7c52f08](https://github.com/vbenjs/vue-vben-admin/commit/7c52f083db30f7e68c3e10a88cbc47d41cf9de20)) +- vxeTable searchInfo demo ([#3223](https://github.com/vbenjs/vue-vben-admin/issues/3223)) close [#3011](https://github.com/vbenjs/vue-vben-admin/issues/3011) ([59145ad](https://github.com/vbenjs/vue-vben-admin/commit/59145ade255ee752a7c8d8995634ec172a7f60c8)) +- **vxetable:** 新增 clearEdit 方法 ([#3369](https://github.com/vbenjs/vue-vben-admin/issues/3369)) ([522e892](https://github.com/vbenjs/vue-vben-admin/commit/522e892d7947807f7d500a2bace23880729df204)) +- **watermark:** support custom style ([#3634](https://github.com/vbenjs/vue-vben-admin/issues/3634)) ([ca3ddd1](https://github.com/vbenjs/vue-vben-admin/commit/ca3ddd19f7811cbf32fd87b6c1cfd6681c08fc69)) + +### Performance Improvements + +- 解决ts文件通过alias引入vue文件后, vscode调转不到正确vue文件路径 ([#3099](https://github.com/vbenjs/vue-vben-admin/issues/3099)) ([033d882](https://github.com/vbenjs/vue-vben-admin/commit/033d8828a9545770e98909291dcb6fd8db90ee41)) +- 优化水印在控制台可以hide的问题 ([#3732](https://github.com/vbenjs/vue-vben-admin/issues/3732)) ([9784cdc](https://github.com/vbenjs/vue-vben-admin/commit/9784cdc840336d5c2bb006fa3ababfa7fa4056af)) +- **BasicTree:** 获取treeData改写成函数 ([748b99b](https://github.com/vbenjs/vue-vben-admin/commit/748b99b18f5fc4e313add38ad457f0a5f064db70)) +- **BasicTree:** 外部获取treeData不必通过treeDataRef.value ([#3353](https://github.com/vbenjs/vue-vben-admin/issues/3353)) ([943f500](https://github.com/vbenjs/vue-vben-admin/commit/943f50051ca80b6646ed44a2117b7c9ef632ed6d)) +- **breakpointEnum:** 修改enum与breakpoint.less内一致 ([#3276](https://github.com/vbenjs/vue-vben-admin/issues/3276)) ([8932318](https://github.com/vbenjs/vue-vben-admin/commit/89323186b56b06b148664ae7483c490cf7eadefa)) +- **component:** formItem: label支持函数渲染 ([#3504](https://github.com/vbenjs/vue-vben-admin/issues/3504)) ([e6a7e4c](https://github.com/vbenjs/vue-vben-admin/commit/e6a7e4c4fcdc5ad94f55c4f19000d6767773691e)) +- **ConfigProvider:** 配置antdv主题色, 使其与modifyVars配置一致 ([#3219](https://github.com/vbenjs/vue-vben-admin/issues/3219)) ([bb3d5b8](https://github.com/vbenjs/vue-vben-admin/commit/bb3d5b8ae8e8ae0e91acb3022f51572daf3210c6)) +- **darkMode:** 深色模式颜色定义以及切换方式优化 ([#3436](https://github.com/vbenjs/vue-vben-admin/issues/3436)) ([b008c44](https://github.com/vbenjs/vue-vben-admin/commit/b008c44246bec32def74c6ae1dd3522bf8c1ca2e)) +- **darkMode:** 优化深色模式颜色切换相关方法; 增加根据主题更新自定义颜色方法和示例 ([#3216](https://github.com/vbenjs/vue-vben-admin/issues/3216)) ([e497721](https://github.com/vbenjs/vue-vben-admin/commit/e497721e9b3d03de07a61808012d0d6e04fd9663)) +- **IconPicker:** input trigger popover by click ([#3278](https://github.com/vbenjs/vue-vben-admin/issues/3278)) ([2bbc2d2](https://github.com/vbenjs/vue-vben-admin/commit/2bbc2d28119c7f7cadbf5411b9ea886fb8965de0)) +- **ImageUpload:** 根据官方示例设置图片回显格式 ([#3252](https://github.com/vbenjs/vue-vben-admin/issues/3252)) ([2991bb1](https://github.com/vbenjs/vue-vben-admin/commit/2991bb1670d44633d1f571374553d027e0dad8a2)) +- Modify i18 file format to JSON ([#3171](https://github.com/vbenjs/vue-vben-admin/issues/3171)) ([c24e0ef](https://github.com/vbenjs/vue-vben-admin/commit/c24e0efd1d2c7148a3df9c8b12a2a500910a3590)) +- **useForm:** If the args of the setFieldsValue is empty, it will not be executed. close [#3209](https://github.com/vbenjs/vue-vben-admin/issues/3209) ([8f90087](https://github.com/vbenjs/vue-vben-admin/commit/8f900871ace30895d9fee84c4eb9018c0ad0450e)) + +### Reverts + +- Revert "chore(deps): update ant-design-vue version to 4.1.0. resolve #3495" ([f9fc369](https://github.com/vbenjs/vue-vben-admin/commit/f9fc369637d1588aa5eebf5fee1f3526de14e0cb)), closes [#3495](https://github.com/vbenjs/vue-vben-admin/issues/3495) +- Revert "feat: table搜索表单值发生改变可以触发reload (#3378)" (#3407) ([2828ed3](https://github.com/vbenjs/vue-vben-admin/commit/2828ed304a778bb16b52223607428dac926d73bf)), closes [#3378](https://github.com/vbenjs/vue-vben-admin/issues/3378) [#3407](https://github.com/vbenjs/vue-vben-admin/issues/3407) +- Revert "chore: update `unplugin-config` (#3116)" (#3117) ([f0550f2](https://github.com/vbenjs/vue-vben-admin/commit/f0550f20438a4c11f51fcf3e4a4286692bcf7d9f)), closes [#3116](https://github.com/vbenjs/vue-vben-admin/issues/3116) [#3117](https://github.com/vbenjs/vue-vben-admin/issues/3117) +- Revert "chore: Update Dependencies" ([ae09d3c](https://github.com/vbenjs/vue-vben-admin/commit/ae09d3cfd6b11689620565b57b506826626d09e9)) +- Revert "refactor: use `unplugin-config` (#3106)" ([694dead](https://github.com/vbenjs/vue-vben-admin/commit/694dead3115a535782b7dd3a3c2dc4eaa9f32d76)), closes [#3106](https://github.com/vbenjs/vue-vben-admin/issues/3106) + +## [2.8.0](https://github.com/anncwb/vue-vben-admin/compare/v2.7.2...v2.8.0) (2021-11-03) + +### Bug Fixes + +- fixed token clear error ([9640484](https://github.com/anncwb/vue-vben-admin/commit/96404848955f84d57b88dd240ab3a57b7017103c)) +- improve type introduction, fix [#1196](https://github.com/anncwb/vue-vben-admin/issues/1196) ([2820d5a](https://github.com/anncwb/vue-vben-admin/commit/2820d5a627260bb8eddfcd25df1cd7d1196932e8)) +- **api-select:** fixed `value` prop define ([f87b0f2](https://github.com/anncwb/vue-vben-admin/commit/f87b0f2f5efe4e9977c4cc0742dbcaefbad2ca02)), closes [#1175](https://github.com/anncwb/vue-vben-admin/issues/1175) +- **card-list:** fixed build error ([628e820](https://github.com/anncwb/vue-vben-admin/commit/628e820684ce5d81f130548505efe83e8d516131)) +- **code-editor:** fixed formatting error ([e7c9636](https://github.com/anncwb/vue-vben-admin/commit/e7c96363a1963b7733a9ee498403eb6a062160e6)) +- **echarts:** theme setting supported ([93812f7](https://github.com/anncwb/vue-vben-admin/commit/93812f734ec85529aa27fc3100a2eaef8c7a6df5)), closes [#1095](https://github.com/anncwb/vue-vben-admin/issues/1095) +- **markdown:** the hierarchy of markDown components after full screen ([c8017b1](https://github.com/anncwb/vue-vben-admin/commit/c8017b1365ea49f95a26148a539f8c30d8a8631f)) +- **markdown:** `value` not worked on init ([0bb9c03](https://github.com/anncwb/vue-vben-admin/commit/0bb9c035f77588c58d36b3fd45d89b9730cd70d7)) +- **modal:** avoid style pollution to the whole world ([#1128](https://github.com/anncwb/vue-vben-admin/issues/1128)) ([6e7f6f8](https://github.com/anncwb/vue-vben-admin/commit/6e7f6f82ed2819e02e2b3114884e665d0762d7e9)) +- **table:** `rowClassName` not worked with `striped` ([044e2e4](https://github.com/anncwb/vue-vben-admin/commit/044e2e4e866dd5b120daab03c47aba1ca1f9140a)), closes [#1167](https://github.com/anncwb/vue-vben-admin/issues/1167) +- **table:** 修复表格背景颜色再深色模式下会被穿透问题 ([#1133](https://github.com/anncwb/vue-vben-admin/issues/1133)) ([30fa4cf](https://github.com/anncwb/vue-vben-admin/commit/30fa4cfa2ab6229efc67224fd082e32da0a95d49)) +- **table:** editable icon not show with empty cell ([edc3096](https://github.com/anncwb/vue-vben-admin/commit/edc30965653831b4572c5d5e067f556f4757ce75)), closes [#1103](https://github.com/anncwb/vue-vben-admin/issues/1103) +- **table:** fix table footer style ([a426b90](https://github.com/anncwb/vue-vben-admin/commit/a426b9027ef524f9033d510d0c74cd17b2ad5bcf)), closes [#1112](https://github.com/anncwb/vue-vben-admin/issues/1112) +- **table:** Solve the bug of setting ifshow to false in table column ([#1166](https://github.com/anncwb/vue-vben-admin/issues/1166)) ([5fa730c](https://github.com/anncwb/vue-vben-admin/commit/5fa730c49ae46fa448d49d597dc7b2b6a019b268)) +- **table-action:** `divider` not work as expected ([7593ef6](https://github.com/anncwb/vue-vben-admin/commit/7593ef6a4f081ed800658b70316ab2f1e3ee631d)) +- **tinymce:** fixed `inline` mode ([8e01377](https://github.com/anncwb/vue-vben-admin/commit/8e01377481a34cda221de6bbb01fc7d5b2824c82)), closes [#1092](https://github.com/anncwb/vue-vben-admin/issues/1092) +- **upload:** `accept` not work as expected ([656ee4e](https://github.com/anncwb/vue-vben-admin/commit/656ee4e5c9b363b6ab59aa071915414e5ee95de4)) +- `getUserinfo` is compatible with empty roles data ([1ddfc31](https://github.com/anncwb/vue-vben-admin/commit/1ddfc31c3c4c792c5f741f6d0f0754ffc9a6613c)) +- `slots` worked in `basicTable` and `basicModal` ([5138e44](https://github.com/anncwb/vue-vben-admin/commit/5138e447e74ef01309457d22f44129c8b1b2f815)) +- `useRedo` called duplicate may cause exception ([1235978](https://github.com/anncwb/vue-vben-admin/commit/1235978ab23740dfb11e3de7ac26a7d10a4899dc)), closes [#1121](https://github.com/anncwb/vue-vben-admin/issues/1121) +- 修复 `apiSelect` 绑定值 `attrs` 的问题 ([#1172](https://github.com/anncwb/vue-vben-admin/issues/1172)) ([c753d94](https://github.com/anncwb/vue-vben-admin/commit/c753d945e08f72cab5bc8a585601cab6a0523fca)) +- 修复弹窗全屏按钮异常关闭的问题([#1177](https://github.com/anncwb/vue-vben-admin/issues/1177)) ([#1182](https://github.com/anncwb/vue-vben-admin/issues/1182)) ([9e9ea3f](https://github.com/anncwb/vue-vben-admin/commit/9e9ea3f43d8c4b88649c1998bf89186b5f7ee6a2)) +- 修改 axios 中 urlPrefix 字段不生效问题 ([#1170](https://github.com/anncwb/vue-vben-admin/issues/1170)) ([7df9b51](https://github.com/anncwb/vue-vben-admin/commit/7df9b513447d8deab2fd8e86fa23c807adb6d440)) +- add loss action for userStore ([a36825a](https://github.com/anncwb/vue-vben-admin/commit/a36825a6d423aae9aaf1936ce55947ba8c2104b0)) +- fix all types of errors, compatible with volar plugin ([e15b4f1](https://github.com/anncwb/vue-vben-admin/commit/e15b4f14db51812effd55670b3d2da7b082e00a7)) +- fixed build warning for style of `intro.js` ([d27633f](https://github.com/anncwb/vue-vben-admin/commit/d27633fb31824e92cbeb24f8d626d8e33ce7179e)), closes [#1130](https://github.com/anncwb/vue-vben-admin/issues/1130) +- Improve content height calculation ([#1136](https://github.com/anncwb/vue-vben-admin/issues/1136)) ([6717fe6](https://github.com/anncwb/vue-vben-admin/commit/6717fe654e88e6a939a16c523832870388ec1886)) +- name of vite `mode` support more characters ([9f68229](https://github.com/anncwb/vue-vben-admin/commit/9f6822991c4b2da78e0a5d0c7d6e0288f0d9d1cb)), closes [#1115](https://github.com/anncwb/vue-vben-admin/issues/1115) +- refresh failed while token invalid ([3a5d1a5](https://github.com/anncwb/vue-vben-admin/commit/3a5d1a5757c0a2be17e6dd370cbb023ddbb30d5e)), closes [#1101](https://github.com/anncwb/vue-vben-admin/issues/1101) +- warning in logout action ([b3307fe](https://github.com/anncwb/vue-vben-admin/commit/b3307fe2836fb6f9806d602d5bdb7e540c49f1b0)) +- **tinymce:** fixed `tinymce` destory method ([fb43fad](https://github.com/anncwb/vue-vben-admin/commit/fb43fad555b093af23194bdb3670bc1347c0010f)) + +### Features + +- **demo:** add `JsonPreview` demo ([83c1683](https://github.com/anncwb/vue-vben-admin/commit/83c1683bfdcf4ea33de771895b46e41f276969e8)), closes [#1146](https://github.com/anncwb/vue-vben-admin/issues/1146) +- **form:** add `Divider` for schema component type ([47a448b](https://github.com/anncwb/vue-vben-admin/commit/47a448b8aea572e54dac97dc4f9fb6c1c005685a)) +- **form:** component `Divider` support `helpMessage` ([a5ff592](https://github.com/anncwb/vue-vben-admin/commit/a5ff59237f2eb6ea4c1770acc594c75bf1f6e95f)) +- **markdown-viewer:** add new component ([73dc492](https://github.com/anncwb/vue-vben-admin/commit/73dc492b2a49793d945ccdae7f5c429c874f298c)), closes [#1181](https://github.com/anncwb/vue-vben-admin/issues/1181) +- **table:** 添加和支持动态删除和插入数据 ([#1152](https://github.com/anncwb/vue-vben-admin/issues/1152)) ([59a9087](https://github.com/anncwb/vue-vben-admin/commit/59a90877287a289f746eec97d12c2d3a1d5476b0)) +- **table:** add `beforeEditSubmit` for editable cell ([2c867b3](https://github.com/anncwb/vue-vben-admin/commit/2c867b3d636d57cdc526a4ca600af7d747b7d833)) +- **table:** add `onValid` for editRow ([ee7c31d](https://github.com/anncwb/vue-vben-admin/commit/ee7c31db44fd8f99f0d26da368e1d82b5630f309)) +- **tree:** 1. 添加自定义数据过滤判断方法 2. 添加搜索完成自动展开结果选项 3. 添加搜索完成自动选中结果选项 4. 树节点数据变化时强制搜索(同步 searchData 避免展示错误) ([#1132](https://github.com/anncwb/vue-vben-admin/issues/1132)) ([e00578c](https://github.com/anncwb/vue-vben-admin/commit/e00578c40a585a4a35f235c0228aebaf62cea1ba)) +- add CardList component ([0f5ddbf](https://github.com/anncwb/vue-vben-admin/commit/0f5ddbf1ec777fc238a94bd037d37ea787316757)) + +### Performance Improvements + +- **tree:** 优化 Tree 搜索功能,添加搜索高亮功能,优化样式表现 ([#1153](https://github.com/anncwb/vue-vben-admin/issues/1153)) ([3b6b4f7](https://github.com/anncwb/vue-vben-admin/commit/3b6b4f73033e8757fd3a032f0910dfcc30dee151)) +- not waiting for router.isReady ([2884e86](https://github.com/anncwb/vue-vben-admin/commit/2884e863ce826cd92cd782f40cdee31588bc6d32)) +- optimize css volume ([466d4ed](https://github.com/anncwb/vue-vben-admin/commit/466d4edcd02fc91e2b4cdbbc3c501bfd2fde7a3d)) + +## [2.7.1](https://github.com/anncwb/vue-vben-admin/compare/v2.6.1...v2.7.1) (2021-08-16) + +### Bug Fixes + +- `slots` working in components ([b1f3176](https://github.com/anncwb/vue-vben-admin/commit/b1f31762e3c86a432a8d559ab957444eaf5525ad)) +- add axios error info from response ([#1083](https://github.com/anncwb/vue-vben-admin/issues/1083)) ([72634ff](https://github.com/anncwb/vue-vben-admin/commit/72634ffe6e6649d36ee41f7633c8ee2ab80cf25e)) +- auto remove script dom in `useScript` ([a544dd3](https://github.com/anncwb/vue-vben-admin/commit/a544dd3e589329339177dad3d5c1f75dd6e6f0ca)) +- fixed `useRedo` may loss route params ([2dd3d85](https://github.com/anncwb/vue-vben-admin/commit/2dd3d8544866231895d23dba63785b683ae0062e)), closes [#1079](https://github.com/anncwb/vue-vben-admin/issues/1079) +- fixed basicButton ghost style ([3ba8a67](https://github.com/anncwb/vue-vben-admin/commit/3ba8a67647d35fb9639a5af66f33d43eff493d15)) +- fixed basicButton primary style ([1b57792](https://github.com/anncwb/vue-vben-admin/commit/1b577922e752c02fe7c033c53be37ef81e4e9b8e)) +- fixed basicButton style ([beb4ae9](https://github.com/anncwb/vue-vben-admin/commit/beb4ae92c190780bbd3bc6bc7547d52e2ccf8cf1)) +- **cropper:** cropper not destroy in time ([3819430](https://github.com/anncwb/vue-vben-admin/commit/381943078fd55123fde3d5555e04f279d7f1c407)), closes [#1027](https://github.com/anncwb/vue-vben-admin/issues/1027) +- **demo:** fix form style in modal ([30c5fc6](https://github.com/anncwb/vue-vben-admin/commit/30c5fc63c8600cfb03f917d79e56c0a7e7ff64e0)), closes [#1076](https://github.com/anncwb/vue-vben-admin/issues/1076) +- **i18n:** add i18n translate data ([1f55c41](https://github.com/anncwb/vue-vben-admin/commit/1f55c4180f9c0cf48e3796a77d6f0bfd46107272)) +- **locales:** fix that vscode extension i18n-Ally detect zh-CN as zh ([#1044](https://github.com/anncwb/vue-vben-admin/issues/1044)) ([b2d49cb](https://github.com/anncwb/vue-vben-admin/commit/b2d49cbbf81cb15e75905deb95bdf7ac4af4e599)) +- **modal:** `helpMessage` doesn't work ([953bfc6](https://github.com/anncwb/vue-vben-admin/commit/953bfc6f1a559309ea2b1114b8ede911a3751cc7)) +- **page-wrapper:** fix `class` not working ([8879ae8](https://github.com/anncwb/vue-vben-admin/commit/8879ae8d773e8dc4c252c4234eefeab9bc135a30)) +- **qrcode:** qrcode not displayed properly ([26f251e](https://github.com/anncwb/vue-vben-admin/commit/26f251e1ed5bfd79c8615fb552ca302f917cc588)), closes [#1026](https://github.com/anncwb/vue-vben-admin/issues/1026) +- **route:** the whitelist should include basicRoutes ([#1048](https://github.com/anncwb/vue-vben-admin/issues/1048)) ([1bb5156](https://github.com/anncwb/vue-vben-admin/commit/1bb51569236fd9bcc55dd9f237f51f218956b258)) +- **table:** `0` is not shown in editable cell ([33a335a](https://github.com/anncwb/vue-vben-admin/commit/33a335a3f52aead522b3fbee0d558e2e797580ff)), closes [#1039](https://github.com/anncwb/vue-vben-admin/issues/1039) +- **table:** `cellFormat` support `Map` ([1214b7c](https://github.com/anncwb/vue-vben-admin/commit/1214b7c32c425750a4d0202a9b235eb9e45a6f47)), closes [#1031](https://github.com/anncwb/vue-vben-admin/issues/1031) +- **table:** `getSelectRows` support multi-page ([4b6025c](https://github.com/anncwb/vue-vben-admin/commit/4b6025cb9a3ef067680201ec3052bc651e0a0c1b)), closes [#914](https://github.com/anncwb/vue-vben-admin/issues/914) +- **table:** `selection-change` not triggered in unchecking ([019555b](https://github.com/anncwb/vue-vben-admin/commit/019555be0c88edc673cae382023d647e78959b30)), closes [#1053](https://github.com/anncwb/vue-vben-admin/issues/1053) +- **table:** `size` not worked in `editComponentProps` ([7971896](https://github.com/anncwb/vue-vben-admin/commit/7971896383296c155b7ab16b5beb3544f34ee525)), closes [#1074](https://github.com/anncwb/vue-vben-admin/issues/1074) +- **table:** fix `getSelectRows` for treeTable ([f2b8bb4](https://github.com/anncwb/vue-vben-admin/commit/f2b8bb43a0b9172b9ef9ced8e83bf91143a091d9)), closes [#1003](https://github.com/anncwb/vue-vben-admin/issues/1003) +- **table:** fix `injection not found` warning ([53e79a2](https://github.com/anncwb/vue-vben-admin/commit/53e79a2d94df19c0e1aa7399d5ce4c27834e0350)) +- **types:** fix some type errors ([9035fd1](https://github.com/anncwb/vue-vben-admin/commit/9035fd191e4e8d954f42b3a4cd1e80ec70b7cbb6)) +- The Style of tableTitle slot ([#1023](https://github.com/anncwb/vue-vben-admin/issues/1023)) ([02e7756](https://github.com/anncwb/vue-vben-admin/commit/02e77560624cc4a95a5a20ffd5e0601f1f88c6f4)) +- fix build handler & misc ([#1060](https://github.com/anncwb/vue-vben-admin/issues/1060)) ([66feb77](https://github.com/anncwb/vue-vben-admin/commit/66feb779a8645a93760c784c510512118c4c6efa)) +- **table:** recursive updateTableDataRecord ([#1024](https://github.com/anncwb/vue-vben-admin/issues/1024)) ([72f953c](https://github.com/anncwb/vue-vben-admin/commit/72f953c8d3413a7f5482793258503017a81cc759)) +- **table:** wrong bg-color in fullscreen mode ([2052eb5](https://github.com/anncwb/vue-vben-admin/commit/2052eb5a65be38c44165efecdb15266de4638667)) +- **type:** fix ant-design-vue -> ([#1043](https://github.com/anncwb/vue-vben-admin/issues/1043)) ([6d5388a](https://github.com/anncwb/vue-vben-admin/commit/6d5388aaf143ac76bac0b68d56a3ab6b5993e807)) +- fix iframe heigth error ([#1012](https://github.com/anncwb/vue-vben-admin/issues/1012)) ([d76cfd7](https://github.com/anncwb/vue-vben-admin/commit/d76cfd7f809ba48880c950a64cb43a5c9c44176c)) +- Fix the invalid hot update of BasicButton when changing style outside ([#1016](https://github.com/anncwb/vue-vben-admin/issues/1016)) ([be2d11d](https://github.com/anncwb/vue-vben-admin/commit/be2d11d5d344a508e94abe3534726c80e1f1f271)) +- the position of tinymce upload image is wrong ([#1015](https://github.com/anncwb/vue-vben-admin/issues/1015)) ([2fd0fd2](https://github.com/anncwb/vue-vben-admin/commit/2fd0fd281e65d6d2551478c5f19250347dc14062)) +- **tree:** fix `checkAll` effects `disabled` node ([ddd1893](https://github.com/anncwb/vue-vben-admin/commit/ddd1893b113e13786037522341abb2e75f8f9d5b)) +- style property of actionColOpt is invalid ([#997](https://github.com/anncwb/vue-vben-admin/issues/997)) ([225bd4c](https://github.com/anncwb/vue-vben-admin/commit/225bd4c39de377d93c605f33bfdf3d8fd565f12b)) +- typo ([#980](https://github.com/anncwb/vue-vben-admin/issues/980)) ([7e6a89f](https://github.com/anncwb/vue-vben-admin/commit/7e6a89ffeb8c63467908d5807d3d7c4761620ee3)) +- **api-tree-select:** auto reload while `params` changed ([c734f68](https://github.com/anncwb/vue-vben-admin/commit/c734f6858daea6d11cd517463b06fcce58744947)) +- **dark-theme:** alert color in dark-theme ([9b7ede0](https://github.com/anncwb/vue-vben-admin/commit/9b7ede09b9efe4d5a15ab0cdeadac480a29c0f62)) +- **dark-theme:** bgcolor of `selected tree node` in dark theme ([8cf004a](https://github.com/anncwb/vue-vben-admin/commit/8cf004a5f59895e2487c3a350c83000e585b897e)), closes [#949](https://github.com/anncwb/vue-vben-admin/issues/949) +- **dark-theme:** disabled link `button` color ([4281216](https://github.com/anncwb/vue-vben-admin/commit/42812162c46832ce4d3e332bd579c042309115bc)) +- **dark-theme:** fixed `TreeSelect` & `DatePicker` theme ([d1e0e8b](https://github.com/anncwb/vue-vben-admin/commit/d1e0e8bcea1c168631222989969e14f7d0d1b6a4)), closes [#955](https://github.com/anncwb/vue-vben-admin/issues/955) +- **dark-theme:** style for checked tree nodes ([662b576](https://github.com/anncwb/vue-vben-admin/commit/662b576ac2088247cb58e295378f228462508a37)) +- **demo:** account page form validation ([8702965](https://github.com/anncwb/vue-vben-admin/commit/87029650570e470431fb94d35a273c5d07a73135)) +- **demo:** fix roles mock data ([c375e32](https://github.com/anncwb/vue-vben-admin/commit/c375e32305eae5128e09ad1bda39ce0cc6afd790)) +- **form:** remove console error for `setFieldsValue` ([8d185bb](https://github.com/anncwb/vue-vben-admin/commit/8d185bb5841c83eb49c78e8342e65067aa6f9b80)), closes [#952](https://github.com/anncwb/vue-vben-admin/issues/952) +- **table:** fix `pagination` props working ([e327893](https://github.com/anncwb/vue-vben-admin/commit/e32789373eb5b1b531572b59692bf552dac365dc)) +- expandIcon slot of BasicTable component is invalid ([#975](https://github.com/anncwb/vue-vben-admin/issues/975)) ([98c206d](https://github.com/anncwb/vue-vben-admin/commit/98c206d9c9661a18dde4ec7782cbe1eb6e62ebeb)) +- **demo:** menu `error-log` link to 404 page ([341bd63](https://github.com/anncwb/vue-vben-admin/commit/341bd633d8ed38a5a357db8f97166c2eba2895d3)) +- **demo:** multi-modal used with dynamic component ([e1c4723](https://github.com/anncwb/vue-vben-admin/commit/e1c47233edf7675aede6d5f023726945a510ddf7)) +- **echarts:** fix graphic config cannot be used in echarts options ([#959](https://github.com/anncwb/vue-vben-admin/issues/959)) ([525484e](https://github.com/anncwb/vue-vben-admin/commit/525484e7a409b032d22231f90a92e700ef4290ae)) +- **form:** fix `validate` promise catch ([571f281](https://github.com/anncwb/vue-vben-admin/commit/571f28138f782553eb39cda2d632e5ac1aa1e145)) +- **img-rotate-drag-verify:** fix `resume` method support ([32d64db](https://github.com/anncwb/vue-vben-admin/commit/32d64dbe816a0afda6ee9e91863199afb3e7b48e)), closes [#946](https://github.com/anncwb/vue-vben-admin/issues/946) +- **login:** fix `auto fill` style in dark-theme ([cebc6a5](https://github.com/anncwb/vue-vben-admin/commit/cebc6a590e1a19af7380a55aed43b23af274df0a)) +- **perm-guard:** Fix the problem that the routing query is lost after refreshing the page ([#941](https://github.com/anncwb/vue-vben-admin/issues/941)) ([9c4889f](https://github.com/anncwb/vue-vben-admin/commit/9c4889f0859bc60decf0ef40c383c1946de1d68a)) +- **qrcode:** Fix the problem that the QR code cannot be dynamically generated ([#974](https://github.com/anncwb/vue-vben-admin/issues/974)) ([fe4eae3](https://github.com/anncwb/vue-vben-admin/commit/fe4eae37146068f01ba08f033e0c2e8bd03e48b5)) +- **style:** fix checkbox-checked css in dark mode ([d3f08e3](https://github.com/anncwb/vue-vben-admin/commit/d3f08e37c5b6e46f33d525e9ef4b4c235d77a192)) +- **table:** component shown in `fullscreen` mode ([a07ab6d](https://github.com/anncwb/vue-vben-admin/commit/a07ab6d7aa1060f856649a9bdbec9dfa50b14f26)) +- **table:** editable cell display with validation ([202aa42](https://github.com/anncwb/vue-vben-admin/commit/202aa42b8d5a94e84ad386bcf7feab96926c70dd)), closes [#953](https://github.com/anncwb/vue-vben-admin/issues/953) +- **table:** fix `dataPicker` show in `fullscreen` mode ([a5a9b3f](https://github.com/anncwb/vue-vben-admin/commit/a5a9b3fb34c64b6ea9c9ab3d58045f6e5963952b)) +- **table:** fix expand style ([14fb21d](https://github.com/anncwb/vue-vben-admin/commit/14fb21d0b7b9ac69c7b3c463de6d709bd5713d14)), closes [#969](https://github.com/anncwb/vue-vben-admin/issues/969) +- **table:** fix tableSettings popup in fullscreen mode ([dce3fb0](https://github.com/anncwb/vue-vben-admin/commit/dce3fb0f20516aaf4817281f5d08109e53a73ecb)) +- **table-action:** stopButtonPropagation not working ([9b8f165](https://github.com/anncwb/vue-vben-admin/commit/9b8f165a365758d001e6d86ae7afe4ae3316d485)) +- Fix vite profile hot update error reporting ([#968](https://github.com/anncwb/vue-vben-admin/issues/968)) ([956ed2e](https://github.com/anncwb/vue-vben-admin/commit/956ed2e3f770cc9cf822ce80f71b1e7f179792fb)) +- **utils:** The date function gets a non-date when the parameter is null ([#954](https://github.com/anncwb/vue-vben-admin/issues/954)) ([350c85a](https://github.com/anncwb/vue-vben-admin/commit/350c85accf5033cc5a21b71bc36d5b7a74eb2573)) +- fixed moment locale config ([27207a7](https://github.com/anncwb/vue-vben-admin/commit/27207a78caccb04372e0275c5cee526ec460de0e)) +- typo for utils/env ([#1004](https://github.com/anncwb/vue-vben-admin/issues/1004)) ([e8eefd1](https://github.com/anncwb/vue-vben-admin/commit/e8eefd1bca41c181ec6395bf1d087d2018e2b1f1)) +- **table:** fix editable cell not support `ellipsis` ([4bb506f](https://github.com/anncwb/vue-vben-admin/commit/4bb506fb1f6ac7d246f8792d29f337ec003ff426)), closes [#944](https://github.com/anncwb/vue-vben-admin/issues/944) + +### Features + +- add `updatePath` for `useTabs` ([bcfa338](https://github.com/anncwb/vue-vben-admin/commit/bcfa33822736b761757a2673d977f752cb5c4f7c)), closes [#1068](https://github.com/anncwb/vue-vben-admin/issues/1068) +- always refresh userinfo when page reload ([cc46935](https://github.com/anncwb/vue-vben-admin/commit/cc46935a8296dae62ecfc753a956338ba433927e)) +- **demo:** add `async-validator` demo ([8b4b767](https://github.com/anncwb/vue-vben-admin/commit/8b4b767f4ca78f7c6f7586d8ba662552c2b7bb51)) +- **form:** add `alwaysShowLines` prop ([93f9a19](https://github.com/anncwb/vue-vben-admin/commit/93f9a19aa16a3e9cb95338417c52d9a398e3f70b)), closes [#1051](https://github.com/anncwb/vue-vben-admin/issues/1051) +- **preview:** add more features ([e23bd26](https://github.com/anncwb/vue-vben-admin/commit/e23bd2696da945291a9b652f1af39ad1936f376b)) +- **table:** add getRawDataSource() function ([#1029](https://github.com/anncwb/vue-vben-admin/issues/1029)) ([f3cf162](https://github.com/anncwb/vue-vben-admin/commit/f3cf162af1fa5634d4e562fa5239939af6f26093)) +- **tree:** add searchable function ([60577d6](https://github.com/anncwb/vue-vben-admin/commit/60577d6720fd3f8d4d1a88b20ab902d6161a0eec)), closes [#1057](https://github.com/anncwb/vue-vben-admin/issues/1057) +- **use-loading:** add `setTip` method ([26d9476](https://github.com/anncwb/vue-vben-admin/commit/26d9476caff41cc355190604af42e0bd2ef0a353)) +- Added support for tailwindcss night mode mechanism ([#998](https://github.com/anncwb/vue-vben-admin/issues/998)) ([189bc6f](https://github.com/anncwb/vue-vben-admin/commit/189bc6feb3f2860be8c531dd1ca996f3a2cff018)) + +### Performance Improvements + +- **table:** fixed code style ([da12da9](https://github.com/anncwb/vue-vben-admin/commit/da12da9d8caeba0e7732551cfbad9b0da3baaac4)), closes [#1070](https://github.com/anncwb/vue-vben-admin/issues/1070) +- improve legacy compatibility ([e2664f6](https://github.com/anncwb/vue-vben-admin/commit/e2664f60029f03642f8b1a6afa9b1998705fce37)) + +# [2.7.0](https://github.com/anncwb/vue-vben-admin/compare/v2.5.9...v2.7.0) (2021-08-03) + +### Bug Fixes + +- The Style of tableTitle slot ([#1023](https://github.com/anncwb/vue-vben-admin/issues/1023)) ([02e7756](https://github.com/anncwb/vue-vben-admin/commit/02e77560624cc4a95a5a20ffd5e0601f1f88c6f4)) +- **api-select:** ensure that the onchange function parameters are correct ([fa64fc8](https://github.com/anncwb/vue-vben-admin/commit/fa64fc8a622832b87fdf672965d55d543b5929a2)) +- **app-search:** exclude hidden items ([faf5c9f](https://github.com/anncwb/vue-vben-admin/commit/faf5c9fd7ea40c407419a5a5c473f9b0c32c2a53)) +- **app-search:** exclude items by `hideChildrenInMenu` ([02d3dca](https://github.com/anncwb/vue-vben-admin/commit/02d3dca57efedc1322ae38e3f432cf1f6c2cf839)) +- **axios:** option `withToken` not work ([d509e89](https://github.com/anncwb/vue-vben-admin/commit/d509e897be5753c852e912112e70dac6247ba467)) +- **breadcrumb:** `redirect` not worked ([f5e31fe](https://github.com/anncwb/vue-vben-admin/commit/f5e31febbd18372a34166cac390b1d9b914fe80e)) +- **comp-tree:** support comp-tree-foreach stop,add insertNodesByKey ([#818](https://github.com/anncwb/vue-vben-admin/issues/818)) ([d97aa92](https://github.com/anncwb/vue-vben-admin/commit/d97aa927417bf45a7c127ecfa9b8e835b6b68855)) +- **CountTo:** Fix displaying empty string when the value is 0 ([#864](https://github.com/anncwb/vue-vben-admin/issues/864)) ([82eb72b](https://github.com/anncwb/vue-vben-admin/commit/82eb72bbced931ba7f50069211f9511035ad09f4)) +- **demo:** `setup` page route config ([d5d5c4b](https://github.com/anncwb/vue-vben-admin/commit/d5d5c4b4bfb3e3a5e54f9993966adc46a09a8b90)) +- **demo:** account list fetch loss param ([424b171](https://github.com/anncwb/vue-vben-admin/commit/424b171e0db727f5e0157cbcfd5460f15f8ea609)), closes [#830](https://github.com/anncwb/vue-vben-admin/issues/830) +- **demo:** add mock data `account detail` route ([993e19d](https://github.com/anncwb/vue-vben-admin/commit/993e19dcc319e2b4c68df2ab76174b7b4d7b0428)), closes [#858](https://github.com/anncwb/vue-vben-admin/issues/858) +- **demo:** fix async tree demo, fixed: [#823](https://github.com/anncwb/vue-vben-admin/issues/823) ([5637588](https://github.com/anncwb/vue-vben-admin/commit/5637588fce880b01137191cc82c73e0fce621e8c)) +- **demo:** form pages support `keepAlive` ([9228282](https://github.com/anncwb/vue-vben-admin/commit/9228282ae27daaa246f42e441e27b1b05eb30464)) +- **demo:** resolve `key not exist` warnings ([45a94e4](https://github.com/anncwb/vue-vben-admin/commit/45a94e41c1397b84d08373f84f766204d2488714)) +- **demo:** style error,fix [#806](https://github.com/anncwb/vue-vben-admin/issues/806) ([a2d8be3](https://github.com/anncwb/vue-vben-admin/commit/a2d8be3ab29da88126f3ba971f6893cb12327759)) +- **demo-form:** add fieldMapToTime example,fix [#807](https://github.com/anncwb/vue-vben-admin/issues/807) ([a2a75a0](https://github.com/anncwb/vue-vben-admin/commit/a2a75a097ff6c9df12471eff0d62d44d2b88cfff)) +- **design:** correct tailwind configuration,fix [#800](https://github.com/anncwb/vue-vben-admin/issues/800) ([aec230c](https://github.com/anncwb/vue-vben-admin/commit/aec230ca19d541079b64c54ba00596ef9cd92ca0)) +- **drawer:** openDrawer is not normal in some cases ([941ad59](https://github.com/anncwb/vue-vben-admin/commit/941ad59759cbd5a5e39bcdf29783d8eea85caf72)) +- **dropdown:** icon and trigger work unexpected ([60b80c9](https://github.com/anncwb/vue-vben-admin/commit/60b80c96e82da9101d56b2e195e9e7571de11f0a)), closes [#796](https://github.com/anncwb/vue-vben-admin/issues/796) [#787](https://github.com/anncwb/vue-vben-admin/issues/787) +- **form:** fix `suffix` slot style ([a9bbed1](https://github.com/anncwb/vue-vben-admin/commit/a9bbed19739376ab2bf67a14b04e872f14ca84cc)) +- **form:** fix some prop declaration ([b5046f0](https://github.com/anncwb/vue-vben-admin/commit/b5046f07a27e8ca7fc8b961b74fa5e1b0d715149)) +- **lock-screen:** ensure lock info is saved ([d38ff66](https://github.com/anncwb/vue-vben-admin/commit/d38ff6670a37478b31447f8058e786c4b044e218)) +- **lock-screen:** fix lock-screen can skip on new window ([d7b84c7](https://github.com/anncwb/vue-vben-admin/commit/d7b84c78744f7d0077a779b232e1358040b50383)) +- **markdown:** resolving markdown exceptions ([d95815b](https://github.com/anncwb/vue-vben-admin/commit/d95815b5031984e224140eb1b1d46e2dbf80abc1)) +- **markdown:** set `value` error ([35e1347](https://github.com/anncwb/vue-vben-admin/commit/35e1347029e29a83a9648b6b398e6863cc40fca9)) +- **menu:** display error when contains hidden items ([5ceeefd](https://github.com/anncwb/vue-vben-admin/commit/5ceeefd17d9ddc0e8844b900069b100f24d9c00e)) +- **menu:** fix mix-menu incorrect jumping in `hover` mode ([cad021c](https://github.com/anncwb/vue-vben-admin/commit/cad021c34b71fa109640af75a0c2b72179e9e257)) +- **menu:** make sure the menu is activated correctly ([cdb10cc](https://github.com/anncwb/vue-vben-admin/commit/cdb10cc4ac5e5e8f9cce3ff18d8fbd29ef10c86f)) +- **mix-sider:** fix mix-sider hover logic ([0595a72](https://github.com/anncwb/vue-vben-admin/commit/0595a72da9c666af81a0916663e8e6a014e6fa69)) +- **modal:** `setModalProps` support `defaultFullscreen` ([c7de65e](https://github.com/anncwb/vue-vben-admin/commit/c7de65ebba53941771153f18b184d3d4d31c0dbf)) +- **modal:** maskClosable not work ([f750ff4](https://github.com/anncwb/vue-vben-admin/commit/f750ff435fee06acee78d6b9633e6e18d91685f8)) +- **modal:** remove console log ([3dbbde2](https://github.com/anncwb/vue-vben-admin/commit/3dbbde2662352780377a9b216598d9348522f6ba)) +- **pop-confirm:** fix event working unexpected ([a6ef771](https://github.com/anncwb/vue-vben-admin/commit/a6ef771fcce14c3644c965afaa69b3a17d0a7087)) +- **popconfirm-button:** remove button excess `title` ([73654b7](https://github.com/anncwb/vue-vben-admin/commit/73654b7862c59d623d6d5dc7dcf6ff2704564d9a)) +- **sider:** bottom trigger not work ([1bde404](https://github.com/anncwb/vue-vben-admin/commit/1bde4041211229d5d9d01ce0ca806fa99356b6de)), closes [#820](https://github.com/anncwb/vue-vben-admin/issues/820) +- **sider:** custom trigger does not take effect ([5005e6e](https://github.com/anncwb/vue-vben-admin/commit/5005e6e56b1cc7763a1cc23e1162dfb49452013b)) +- **svg-icon:** fix SvgIcon style ([99829c7](https://github.com/anncwb/vue-vben-admin/commit/99829c79ab41a2319f40c5595a7d82d9e406ba18)) +- **table:** fix index column style ([c7c0a7e](https://github.com/anncwb/vue-vben-admin/commit/c7c0a7e4c88a895000b1621981e4d4b2020c64b1)) +- **table:** auto hide unnecessary scrollbar ([735028c](https://github.com/anncwb/vue-vben-admin/commit/735028c43055e8e80ebc7344af0cd0f51c744f98)) +- **table:** editComponentProps support onChange ([829b366](https://github.com/anncwb/vue-vben-admin/commit/829b366cb2abf27e69d9665e5be022b3d3f15655)) +- **table:** event editCancel loss params ([8d22231](https://github.com/anncwb/vue-vben-admin/commit/8d22231a5fa4afed19201a4a4e5c29d674498516)) +- **table:** fix rowSelection.onChange not work ([df0f000](https://github.com/anncwb/vue-vben-admin/commit/df0f00085c1113eddd7a15954818ccece3538068)), closes [#825](https://github.com/anncwb/vue-vben-admin/issues/825) +- **table:** fix table jitter problem ([8eba7fb](https://github.com/anncwb/vue-vben-admin/commit/8eba7fb52786d1977e4cb7b67673d74c91c5c827)) +- **table:** fix tree node align ([1e61da6](https://github.com/anncwb/vue-vben-admin/commit/1e61da644f65a79ce10fde98ee017aba7d36be10)), closes [#829](https://github.com/anncwb/vue-vben-admin/issues/829) +- **table:** getDataSource not worked on empty data ([e78af6f](https://github.com/anncwb/vue-vben-admin/commit/e78af6f228e25f052dc4c5a1859a6db50e0b112e)), closes [#752](https://github.com/anncwb/vue-vben-admin/issues/752) +- **table:** global configuration accidentally modified ([b4a3f93](https://github.com/anncwb/vue-vben-admin/commit/b4a3f936cd19bf1fff3a331bacad60e79d2d6c22)) +- **table:** param of `handleSearchInfoFn` ([791b323](https://github.com/anncwb/vue-vben-admin/commit/791b323dbd30acd7fabfe9c3fb6e528916311ffd)) +- **table:** recursive updateTableDataRecord ([#1024](https://github.com/anncwb/vue-vben-admin/issues/1024)) ([72f953c](https://github.com/anncwb/vue-vben-admin/commit/72f953c8d3413a7f5482793258503017a81cc759)) +- auto remove script dom in `useScript` ([a544dd3](https://github.com/anncwb/vue-vben-admin/commit/a544dd3e589329339177dad3d5c1f75dd6e6f0ca)) +- fix iframe heigth error ([#1012](https://github.com/anncwb/vue-vben-admin/issues/1012)) ([d76cfd7](https://github.com/anncwb/vue-vben-admin/commit/d76cfd7f809ba48880c950a64cb43a5c9c44176c)) +- Fix the invalid hot update of BasicButton when changing style outside ([#1016](https://github.com/anncwb/vue-vben-admin/issues/1016)) ([be2d11d](https://github.com/anncwb/vue-vben-admin/commit/be2d11d5d344a508e94abe3534726c80e1f1f271)) +- style property of actionColOpt is invalid ([#997](https://github.com/anncwb/vue-vben-admin/issues/997)) ([225bd4c](https://github.com/anncwb/vue-vben-admin/commit/225bd4c39de377d93c605f33bfdf3d8fd565f12b)) +- the position of tinymce upload image is wrong ([#1015](https://github.com/anncwb/vue-vben-admin/issues/1015)) ([2fd0fd2](https://github.com/anncwb/vue-vben-admin/commit/2fd0fd281e65d6d2551478c5f19250347dc14062)) +- **api-select:** fix `options-change` event data ([897bed9](https://github.com/anncwb/vue-vben-admin/commit/897bed97295a0b9101d33102340749689a4368de)) +- **api-tree-select:** auto load data if necessary ([1b3058f](https://github.com/anncwb/vue-vben-admin/commit/1b3058f8253effe974feaf08a12250a111ab58c0)) +- **api-tree-select:** auto reload while `params` changed ([c734f68](https://github.com/anncwb/vue-vben-admin/commit/c734f6858daea6d11cd517463b06fcce58744947)) +- **api-tree-select:** fix `event` checked in form ([d9d0071](https://github.com/anncwb/vue-vben-admin/commit/d9d00714011fa7914c61f990ce1159351ee21a1a)) +- **basic-tree:** `checkedKeys` not worked with `search` ([b06a7ab](https://github.com/anncwb/vue-vben-admin/commit/b06a7ab77b99abee63dd55770ffd55b594ee42f9)), closes [#915](https://github.com/anncwb/vue-vben-admin/issues/915) +- **code-editor:** `value` not support use as `v-model` ([8832a07](https://github.com/anncwb/vue-vben-admin/commit/8832a074dceb44f057c87289d3a99feef58c08fd)), closes [#933](https://github.com/anncwb/vue-vben-admin/issues/933) +- **countdown-input:** add `slots` support ([a764a95](https://github.com/anncwb/vue-vben-admin/commit/a764a95ae9a6cff831f75aa97b00724cadc48e92)) +- **dark-theme:** alert color in dark-theme ([9b7ede0](https://github.com/anncwb/vue-vben-admin/commit/9b7ede09b9efe4d5a15ab0cdeadac480a29c0f62)) +- **dark-theme:** bgcolor of `selected tree node` in dark theme ([8cf004a](https://github.com/anncwb/vue-vben-admin/commit/8cf004a5f59895e2487c3a350c83000e585b897e)), closes [#949](https://github.com/anncwb/vue-vben-admin/issues/949) +- **dark-theme:** disabled link `button` color ([4281216](https://github.com/anncwb/vue-vben-admin/commit/42812162c46832ce4d3e332bd579c042309115bc)) +- **dark-theme:** fixed `TreeSelect` & `DatePicker` theme ([d1e0e8b](https://github.com/anncwb/vue-vben-admin/commit/d1e0e8bcea1c168631222989969e14f7d0d1b6a4)), closes [#955](https://github.com/anncwb/vue-vben-admin/issues/955) +- **dark-theme:** style for checked tree nodes ([662b576](https://github.com/anncwb/vue-vben-admin/commit/662b576ac2088247cb58e295378f228462508a37)) +- **demo:** account page form validation ([8702965](https://github.com/anncwb/vue-vben-admin/commit/87029650570e470431fb94d35a273c5d07a73135)) +- **demo:** fix display problem of editable table with `apiSelect` ([535bddd](https://github.com/anncwb/vue-vben-admin/commit/535bdddf91785e20295c18cf80c8a22cc2172681)) +- **demo:** fix roles mock data ([c375e32](https://github.com/anncwb/vue-vben-admin/commit/c375e32305eae5128e09ad1bda39ce0cc6afd790)) +- **demo:** menu `error-log` link to 404 page ([341bd63](https://github.com/anncwb/vue-vben-admin/commit/341bd633d8ed38a5a357db8f97166c2eba2895d3)) +- **demo:** multi-modal used with dynamic component ([e1c4723](https://github.com/anncwb/vue-vben-admin/commit/e1c47233edf7675aede6d5f023726945a510ddf7)) +- **echarts:** fix graphic config cannot be used in echarts options ([#959](https://github.com/anncwb/vue-vben-admin/issues/959)) ([525484e](https://github.com/anncwb/vue-vben-admin/commit/525484e7a409b032d22231f90a92e700ef4290ae)) +- **form:** fix `validate` promise catch ([571f281](https://github.com/anncwb/vue-vben-admin/commit/571f28138f782553eb39cda2d632e5ac1aa1e145)) +- **form:** remove console error for `setFieldsValue` ([8d185bb](https://github.com/anncwb/vue-vben-admin/commit/8d185bb5841c83eb49c78e8342e65067aa6f9b80)), closes [#952](https://github.com/anncwb/vue-vben-admin/issues/952) +- **formItem:** Fix labelcol type mismatch ([#903](https://github.com/anncwb/vue-vben-admin/issues/903)) ([03b17a8](https://github.com/anncwb/vue-vben-admin/commit/03b17a8f8bdb50322aa10e3b614bcc40b9e9dcc8)) +- **img-rotate-drag-verify:** fix `resume` method support ([32d64db](https://github.com/anncwb/vue-vben-admin/commit/32d64dbe816a0afda6ee9e91863199afb3e7b48e)), closes [#946](https://github.com/anncwb/vue-vben-admin/issues/946) +- **login:** fix `auto fill` style in dark-theme ([cebc6a5](https://github.com/anncwb/vue-vben-admin/commit/cebc6a590e1a19af7380a55aed43b23af274df0a)) +- **modal:** ensure that props are passed correctly,fix [#897](https://github.com/anncwb/vue-vben-admin/issues/897) ([ae7821e](https://github.com/anncwb/vue-vben-admin/commit/ae7821e29690bea8934ea724bfd0ae4e2cf30c77)) +- **modal:** fixed `fullscreen` not worked ([5baaa58](https://github.com/anncwb/vue-vben-admin/commit/5baaa58581f22a915cda9fa39e4cb9f094254d3b)), closes [#918](https://github.com/anncwb/vue-vben-admin/issues/918) +- **model:** auto validate on value change ([f844017](https://github.com/anncwb/vue-vben-admin/commit/f8440175f35076073c9f53483cf6c0164d427ff4)), closes [#920](https://github.com/anncwb/vue-vben-admin/issues/920) +- **multiple-tab:** ignore login page ([1e63379](https://github.com/anncwb/vue-vben-admin/commit/1e63379088e1d7c823f29f607ab49d62ca22cb25)) +- **page-wrapper:** fix `class` not working ([8879ae8](https://github.com/anncwb/vue-vben-admin/commit/8879ae8d773e8dc4c252c4234eefeab9bc135a30)) +- **perm-guard:** Fix the problem that the routing query is lost after refreshing the page ([#941](https://github.com/anncwb/vue-vben-admin/issues/941)) ([9c4889f](https://github.com/anncwb/vue-vben-admin/commit/9c4889f0859bc60decf0ef40c383c1946de1d68a)) +- **qrcode:** Fix the problem that the QR code cannot be dynamically generated ([#974](https://github.com/anncwb/vue-vben-admin/issues/974)) ([fe4eae3](https://github.com/anncwb/vue-vben-admin/commit/fe4eae37146068f01ba08f033e0c2e8bd03e48b5)) +- **style:** fix checkbox-checked css in dark mode ([d3f08e3](https://github.com/anncwb/vue-vben-admin/commit/d3f08e37c5b6e46f33d525e9ef4b4c235d77a192)) +- **table:** `value` show problem in editable cell ([61ce25b](https://github.com/anncwb/vue-vben-admin/commit/61ce25be1b40d7a0e26205ca6a6757c6c43fc21e)), closes [#922](https://github.com/anncwb/vue-vben-admin/issues/922) +- **table:** component shown in `fullscreen` mode ([a07ab6d](https://github.com/anncwb/vue-vben-admin/commit/a07ab6d7aa1060f856649a9bdbec9dfa50b14f26)) +- **table:** editable cell display with validation ([202aa42](https://github.com/anncwb/vue-vben-admin/commit/202aa42b8d5a94e84ad386bcf7feab96926c70dd)), closes [#953](https://github.com/anncwb/vue-vben-admin/issues/953) +- **table:** fix `dataPicker` show in `fullscreen` mode ([a5a9b3f](https://github.com/anncwb/vue-vben-admin/commit/a5a9b3fb34c64b6ea9c9ab3d58045f6e5963952b)) +- **table:** fix `getSelectRows` for treeTable ([f2b8bb4](https://github.com/anncwb/vue-vben-admin/commit/f2b8bb43a0b9172b9ef9ced8e83bf91143a091d9)), closes [#1003](https://github.com/anncwb/vue-vben-admin/issues/1003) +- **table:** fix `pagination` props working ([e327893](https://github.com/anncwb/vue-vben-admin/commit/e32789373eb5b1b531572b59692bf552dac365dc)) +- **table:** fix editable cell not support `ellipsis` ([4bb506f](https://github.com/anncwb/vue-vben-admin/commit/4bb506fb1f6ac7d246f8792d29f337ec003ff426)), closes [#944](https://github.com/anncwb/vue-vben-admin/issues/944) +- **table:** fix expand style ([14fb21d](https://github.com/anncwb/vue-vben-admin/commit/14fb21d0b7b9ac69c7b3c463de6d709bd5713d14)), closes [#969](https://github.com/anncwb/vue-vben-admin/issues/969) +- **table:** fix tableSettings popup in fullscreen mode ([dce3fb0](https://github.com/anncwb/vue-vben-admin/commit/dce3fb0f20516aaf4817281f5d08109e53a73ecb)) +- **tree:** fix `checkAll` effects `disabled` node ([ddd1893](https://github.com/anncwb/vue-vben-admin/commit/ddd1893b113e13786037522341abb2e75f8f9d5b)) +- ensure PAGE_NOT_FOUND_ROUTE exist ([87583c8](https://github.com/anncwb/vue-vben-admin/commit/87583c8b54d335ddf1c416859ef62bbde189c809)) +- expandIcon slot of BasicTable component is invalid ([#975](https://github.com/anncwb/vue-vben-admin/issues/975)) ([98c206d](https://github.com/anncwb/vue-vben-admin/commit/98c206d9c9661a18dde4ec7782cbe1eb6e62ebeb)) +- fix homePage affix error ([c117802](https://github.com/anncwb/vue-vben-admin/commit/c1178027f0fab2791d02efcd7c52beff5fc7dc25)) +- Fix vite profile hot update error reporting ([#968](https://github.com/anncwb/vue-vben-admin/issues/968)) ([956ed2e](https://github.com/anncwb/vue-vben-admin/commit/956ed2e3f770cc9cf822ce80f71b1e7f179792fb)) +- fixed moment locale config ([27207a7](https://github.com/anncwb/vue-vben-admin/commit/27207a78caccb04372e0275c5cee526ec460de0e)) +- infinite redirect in `BACK` mode ([4b46a84](https://github.com/anncwb/vue-vben-admin/commit/4b46a84c2b85e8da799426c54b3381ae93183db4)) +- resolving `Vue Router warn` ([237e65e](https://github.com/anncwb/vue-vben-admin/commit/237e65eac909368c4b4857da6c8deb1dc18e7684)) +- typo ([#980](https://github.com/anncwb/vue-vben-admin/issues/980)) ([7e6a89f](https://github.com/anncwb/vue-vben-admin/commit/7e6a89ffeb8c63467908d5807d3d7c4761620ee3)) +- typo for utils/env ([#1004](https://github.com/anncwb/vue-vben-admin/issues/1004)) ([e8eefd1](https://github.com/anncwb/vue-vben-admin/commit/e8eefd1bca41c181ec6395bf1d087d2018e2b1f1)) +- **table:** selection-change not triggered on row click ([6f845b5](https://github.com/anncwb/vue-vben-admin/commit/6f845b53bdc4c33fbca3e65f10f64c63166bed0e)) +- **table:** treeTable editable error ([4ae39c5](https://github.com/anncwb/vue-vben-admin/commit/4ae39c53b49532fc6c31086a31e30429d2e236ed)), closes [#811](https://github.com/anncwb/vue-vben-admin/issues/811) +- **table-action:** fix `circle` button style ([db7254a](https://github.com/anncwb/vue-vben-admin/commit/db7254a5e0ac6d10a7ea37334ad523b150facb19)) +- **table-action:** fixed icon `margin` without label ([dc51e6a](https://github.com/anncwb/vue-vben-admin/commit/dc51e6a8d4e4f2c97b387b37959944c9bb49d779)) +- **table-action:** incorrect button color of `disabled` state ([0f28e80](https://github.com/anncwb/vue-vben-admin/commit/0f28e803d0b65537216cd9f40ad5cad63c20db9b)), closes [#891](https://github.com/anncwb/vue-vben-admin/issues/891) +- **table-action:** stopButtonPropagation not working ([9b8f165](https://github.com/anncwb/vue-vben-admin/commit/9b8f165a365758d001e6d86ae7afe4ae3316d485)) +- **tree:** fixed `checkedKeys` with `search` mode ([f707541](https://github.com/anncwb/vue-vben-admin/commit/f707541dda78146bda89814ddccbb259d9f5d8a2)) +- **upload:** ensure the value type is correct ([05329ce](https://github.com/anncwb/vue-vben-admin/commit/05329ce9501eb899a0bbb45320e5807c83372317)) +- **useWatermark:** fix `func` call `createWatermark` call `clear` to resizeEvent removed ([#901](https://github.com/anncwb/vue-vben-admin/issues/901)) ([a1d956d](https://github.com/anncwb/vue-vben-admin/commit/a1d956d3697cd07e0ba8910768f2a73e55f18491)) +- `menuSetting` can not set collapsed to false as default ([808291b](https://github.com/anncwb/vue-vben-admin/commit/808291b503d59e3026f5f0b5e7a38b9c69bcc451)) +- ensure that safari is running properly, fix [#875](https://github.com/anncwb/vue-vben-admin/issues/875) ([dafcdd8](https://github.com/anncwb/vue-vben-admin/commit/dafcdd898caae57104f1155b0ec660ea333e7b19)) +- **table:** scrollbar style ([d8c3820](https://github.com/anncwb/vue-vben-admin/commit/d8c38207c08510d805a8dc66ffbba210e0cf4215)) +- **tailwindcss:** remove console warnings ([acacb32](https://github.com/anncwb/vue-vben-admin/commit/acacb32bb592345cd0a90b4bbeb60a9b6ab1ac3c)) +- `hasPermission` not work in `ROLE` Mode ([76a5f87](https://github.com/anncwb/vue-vben-admin/commit/76a5f87c0ce871cca48b9e4c32331353a796e7d2)) +- fix antdv console warning ([480cfb9](https://github.com/anncwb/vue-vben-admin/commit/480cfb914e78c06eb7784e33465ed91b7d4c3eee)) +- fix defHttp baseUrl work ([d5f9919](https://github.com/anncwb/vue-vben-admin/commit/d5f9919b60fdd7d5c435129e8db519c0bbd37529)) +- multi windows token sharing ([e5f3788](https://github.com/anncwb/vue-vben-admin/commit/e5f37885ffb32d04d244f0ef39ac660dda6b71e1)), closes [#761](https://github.com/anncwb/vue-vben-admin/issues/761) +- routes filter can't effective when permission mode set to ROUTE_MAPPING ([#836](https://github.com/anncwb/vue-vben-admin/issues/836)) ([3871204](https://github.com/anncwb/vue-vben-admin/commit/3871204d08d481b8984440cd60bbf2bacb58d063)) +- support various vite modes of build, not just production ([#832](https://github.com/anncwb/vue-vben-admin/issues/832)) ([95c16a5](https://github.com/anncwb/vue-vben-admin/commit/95c16a5d26f9fd9a1d11894afe1146ca495eee93)) +- user drop-down event key loss ([20d7a25](https://github.com/anncwb/vue-vben-admin/commit/20d7a25eb898a5c28351ff269b93bf104b8ac10e)) +- user dropdown event response failure ([c73694a](https://github.com/anncwb/vue-vben-admin/commit/c73694ab8b0b6242c4d5e0f30bc7ebe3d69b4e33)) +- **upload:** make sure to carry custom parameters, fix [#802](https://github.com/anncwb/vue-vben-admin/issues/802) ([c4b22a2](https://github.com/anncwb/vue-vben-admin/commit/c4b22a225d0088d87be0c0068f543366312521db)) +- **utils:** The date function gets a non-date when the parameter is null ([#954](https://github.com/anncwb/vue-vben-admin/issues/954)) ([350c85a](https://github.com/anncwb/vue-vben-admin/commit/350c85accf5033cc5a21b71bc36d5b7a74eb2573)) + +### Features + +- **use-loading:** add `setTip` method ([26d9476](https://github.com/anncwb/vue-vben-admin/commit/26d9476caff41cc355190604af42e0bd2ef0a353)) +- Added support for tailwindcss night mode mechanism ([#998](https://github.com/anncwb/vue-vben-admin/issues/998)) ([189bc6f](https://github.com/anncwb/vue-vben-admin/commit/189bc6feb3f2860be8c531dd1ca996f3a2cff018)) +- **api-select:** clear options before fetch ([9cf070d](https://github.com/anncwb/vue-vben-admin/commit/9cf070dd6305bb69a67ab6be85ef00bddc86fda0)) +- **api-tree-select:** add `api` options to tree-select ([d81db89](https://github.com/anncwb/vue-vben-admin/commit/d81db890dfeb533d60f378ddb86f8ac50a31252b)) +- **avatar-cropper:** add action tooltip ([6cbac4b](https://github.com/anncwb/vue-vben-admin/commit/6cbac4b7ece60a1a7c1fda931cfffce42dfe3e51)) +- **avatar-cropper:** more props added ([b96ea07](https://github.com/anncwb/vue-vben-admin/commit/b96ea0753bfd769693a368cf1e3d8316688c0dcb)) +- **axios:** add `withToken` option ([c99cf5e](https://github.com/anncwb/vue-vben-admin/commit/c99cf5e53f057cdc332ab6c0635adf9c2d27de29)) +- **axios:** use `defHttp` like `axios` ([49f39de](https://github.com/anncwb/vue-vben-admin/commit/49f39de7b40e3ec8343bdeaf3eb00fd79d395746)), closes [#850](https://github.com/anncwb/vue-vben-admin/issues/850) +- **basic-table:** add `ApiTreeSelect` edit component ([52af1dd](https://github.com/anncwb/vue-vben-admin/commit/52af1dd0d494e66c0af20f886dcc2b983cbb096f)) +- **basic-upload:** `value` support v-model ([16c5d32](https://github.com/anncwb/vue-vben-admin/commit/16c5d327f1209f7c7437acde2ab0fa031da6a641)) +- **basic-upload:** add preview-delete event ([49e72a8](https://github.com/anncwb/vue-vben-admin/commit/49e72a8e76b78fe54e19de9e23d7c72a19427f01)), closes [#835](https://github.com/anncwb/vue-vben-admin/issues/835) +- **demo:** add `async-validator` demo ([8b4b767](https://github.com/anncwb/vue-vben-admin/commit/8b4b767f4ca78f7c6f7586d8ba662552c2b7bb51)) +- **demo:** add basicTree with async data expand all ([5421211](https://github.com/anncwb/vue-vben-admin/commit/542121129eb5bf65f61e7b484835591756c80f04)) +- **demo:** add route multi tabs show ([0e414ba](https://github.com/anncwb/vue-vben-admin/commit/0e414ba3c10b4e47a85feb1a38cae66c815719d8)), closes [#817](https://github.com/anncwb/vue-vben-admin/issues/817) +- **demo:** add search demo for apiSelect ([41e6d94](https://github.com/anncwb/vue-vben-admin/commit/41e6d94b3b64dc0d40b7ec57ecfaa4d966f202ae)) +- **demo:** demo default expanded tree table ([5f1a6cd](https://github.com/anncwb/vue-vben-admin/commit/5f1a6cdc599d5840df2dfebdaad029aac093cd81)) +- **demo:** multi-modal in one page usage ([7a7dab0](https://github.com/anncwb/vue-vben-admin/commit/7a7dab0c4b3602b7bd3e9381408e4168d7494c52)) +- **menu:** the route is automatically mapped to the menu ([913c22c](https://github.com/anncwb/vue-vben-admin/commit/913c22c84fc9a4221fdfff6bae0e79a68fd09b17)) +- **modal:** add `tooltip` for action buttons ([c3b9076](https://github.com/anncwb/vue-vben-admin/commit/c3b907656a5fad7a9b241562179f7a0f6fe0e6f0)) +- **notice-list:** add `pagination` support ([c16be2c](https://github.com/anncwb/vue-vben-admin/commit/c16be2c499d90126dfa35d699da9294c21a4ab48)), closes [#894](https://github.com/anncwb/vue-vben-admin/issues/894) +- **preview:** add more features ([e23bd26](https://github.com/anncwb/vue-vben-admin/commit/e23bd2696da945291a9b652f1af39ad1936f376b)) +- customized user home page ([0a3683a](https://github.com/anncwb/vue-vben-admin/commit/0a3683a186ab55d34a12a5a3c6d794dfa1094ad4)) +- **param-menu:** feature: menu with params ([#845](https://github.com/anncwb/vue-vben-admin/issues/845)) ([48fcd76](https://github.com/anncwb/vue-vben-admin/commit/48fcd7684cabff66e8648b71527c6cb4ce7d03be)) +- **route:** add `hidePathForChildren` in `meta` ([d52b0de](https://github.com/anncwb/vue-vben-admin/commit/d52b0de83e69f7505c28e6f59ec84bbe526ecd0d)) +- **table:** add `headerTop` slot ([540423e](https://github.com/anncwb/vue-vben-admin/commit/540423ecf741a815d28d7a6baa1541ac884efe95)), closes [#881](https://github.com/anncwb/vue-vben-admin/issues/881) +- **table:** support asynchrony in beforeFetch and afterFetch ([#827](https://github.com/anncwb/vue-vben-admin/issues/827)) ([749ba5c](https://github.com/anncwb/vue-vben-admin/commit/749ba5c1daf459625518937c239787b756c0a780)) +- **table-action:** support `tooltip` option ([5fab267](https://github.com/anncwb/vue-vben-admin/commit/5fab267a69600fdf5d7a7f9e4d9fff859d09dede)), closes [#848](https://github.com/anncwb/vue-vben-admin/issues/848) +- **tree:** add `insertNodesByKey` method ([5a20df4](https://github.com/anncwb/vue-vben-admin/commit/5a20df45ad36b523d48bf7fe11bdb10a6d03df64)) +- add Tree LoadData demo ([9298b3c](https://github.com/anncwb/vue-vben-admin/commit/9298b3c988c10b81d83430ca31b9ce1d98a3fad9)) +- routers support `ignoreRoute` option ([72ac240](https://github.com/anncwb/vue-vben-admin/commit/72ac240f2858cd74cb62b7647ca89d63bb71d247)) + +### Performance Improvements + +- improve legacy compatibility ([e2664f6](https://github.com/anncwb/vue-vben-admin/commit/e2664f60029f03642f8b1a6afa9b1998705fce37)) +- **menu:** Optimize the style of the bottom collapse button in the Mix menu layout ([#896](https://github.com/anncwb/vue-vben-admin/issues/896)) ([6f83070](https://github.com/anncwb/vue-vben-admin/commit/6f830703a2607c33e5d25d6d17d0e453fc2fac2e)) +- image compression configuration optimization ([cf840e3](https://github.com/anncwb/vue-vben-admin/commit/cf840e3e73b9572de0ba7bf7b32d83f6a353a8ad)) +- **icon:** remove Icon component global registration ([59d3e8c](https://github.com/anncwb/vue-vben-admin/commit/59d3e8c80f72f029f2b90432b31901ad54ed1ee4)) +- **pagewrapper:** 优化 PageWrapper 的高度自适应表现使用 getViewportOffset 替代 useContentViewHeight ([#792](https://github.com/anncwb/vue-vben-admin/issues/792)) ([4d8e398](https://github.com/anncwb/vue-vben-admin/commit/4d8e39857ea59fff99e69832b4a8cabf3a424c24)) +- **router:** reduce the number of guard files ([327d71b](https://github.com/anncwb/vue-vben-admin/commit/327d71b8fb4907ae971d040f6b84bbecb0a6d897)) +- **scrollbar:** scrollbar update when slot changed ([e9e51b2](https://github.com/anncwb/vue-vben-admin/commit/e9e51b2fdc879a66d8df08504a0955c9c21e3e27)) + +### Reverts + +- **axios:** remove baseUrl config ([61d4efd](https://github.com/anncwb/vue-vben-admin/commit/61d4efd55a8b4f09990b5f1888e23ead43958164)) + +## [2.6.1](https://github.com/anncwb/vue-vben-admin/compare/v2.6.0...v2.6.1) (2021-07-19) + +### Bug Fixes + +- **api-select:** fix `options-change` event data ([897bed9](https://github.com/anncwb/vue-vben-admin/commit/897bed97295a0b9101d33102340749689a4368de)) +- **api-tree-select:** auto load data if necessary ([1b3058f](https://github.com/anncwb/vue-vben-admin/commit/1b3058f8253effe974feaf08a12250a111ab58c0)) +- **api-tree-select:** fix `event` checked in form ([d9d0071](https://github.com/anncwb/vue-vben-admin/commit/d9d00714011fa7914c61f990ce1159351ee21a1a)) +- **app-search:** exclude hidden items ([faf5c9f](https://github.com/anncwb/vue-vben-admin/commit/faf5c9fd7ea40c407419a5a5c473f9b0c32c2a53)) +- **app-search:** exclude items by `hideChildrenInMenu` ([02d3dca](https://github.com/anncwb/vue-vben-admin/commit/02d3dca57efedc1322ae38e3f432cf1f6c2cf839)) +- **basic-tree:** `checkedKeys` not worked with `search` ([b06a7ab](https://github.com/anncwb/vue-vben-admin/commit/b06a7ab77b99abee63dd55770ffd55b594ee42f9)), closes [#915](https://github.com/anncwb/vue-vben-admin/issues/915) +- **breadcrumb:** `redirect` not worked ([f5e31fe](https://github.com/anncwb/vue-vben-admin/commit/f5e31febbd18372a34166cac390b1d9b914fe80e)) +- **code-editor:** `value` not support use as `v-model` ([8832a07](https://github.com/anncwb/vue-vben-admin/commit/8832a074dceb44f057c87289d3a99feef58c08fd)), closes [#933](https://github.com/anncwb/vue-vben-admin/issues/933) +- **countdown-input:** add `slots` support ([a764a95](https://github.com/anncwb/vue-vben-admin/commit/a764a95ae9a6cff831f75aa97b00724cadc48e92)) +- **CountTo:** Fix displaying empty string when the value is 0 ([#864](https://github.com/anncwb/vue-vben-admin/issues/864)) ([82eb72b](https://github.com/anncwb/vue-vben-admin/commit/82eb72bbced931ba7f50069211f9511035ad09f4)) +- **demo:** `setup` page route config ([d5d5c4b](https://github.com/anncwb/vue-vben-admin/commit/d5d5c4b4bfb3e3a5e54f9993966adc46a09a8b90)) +- **demo:** add mock data `account detail` route ([993e19d](https://github.com/anncwb/vue-vben-admin/commit/993e19dcc319e2b4c68df2ab76174b7b4d7b0428)), closes [#858](https://github.com/anncwb/vue-vben-admin/issues/858) +- **demo:** fix display problem of editable table with `apiSelect` ([535bddd](https://github.com/anncwb/vue-vben-admin/commit/535bdddf91785e20295c18cf80c8a22cc2172681)) +- **demo:** form pages support `keepAlive` ([9228282](https://github.com/anncwb/vue-vben-admin/commit/9228282ae27daaa246f42e441e27b1b05eb30464)) +- **demo:** resolve `key not exist` warnings ([45a94e4](https://github.com/anncwb/vue-vben-admin/commit/45a94e41c1397b84d08373f84f766204d2488714)) +- **form:** fix `suffix` slot style ([a9bbed1](https://github.com/anncwb/vue-vben-admin/commit/a9bbed19739376ab2bf67a14b04e872f14ca84cc)) +- **formItem:** Fix labelcol type mismatch ([#903](https://github.com/anncwb/vue-vben-admin/issues/903)) ([03b17a8](https://github.com/anncwb/vue-vben-admin/commit/03b17a8f8bdb50322aa10e3b614bcc40b9e9dcc8)) +- **markdown:** resolving markdown exceptions ([d95815b](https://github.com/anncwb/vue-vben-admin/commit/d95815b5031984e224140eb1b1d46e2dbf80abc1)) +- **markdown:** set `value` error ([35e1347](https://github.com/anncwb/vue-vben-admin/commit/35e1347029e29a83a9648b6b398e6863cc40fca9)) +- **menu:** display error when contains hidden items ([5ceeefd](https://github.com/anncwb/vue-vben-admin/commit/5ceeefd17d9ddc0e8844b900069b100f24d9c00e)) +- **menu:** fix mix-menu incorrect jumping in `hover` mode ([cad021c](https://github.com/anncwb/vue-vben-admin/commit/cad021c34b71fa109640af75a0c2b72179e9e257)) +- **mix-sider:** fix mix-sider hover logic ([0595a72](https://github.com/anncwb/vue-vben-admin/commit/0595a72da9c666af81a0916663e8e6a014e6fa69)) +- **modal:** ensure that props are passed correctly,fix [#897](https://github.com/anncwb/vue-vben-admin/issues/897) ([ae7821e](https://github.com/anncwb/vue-vben-admin/commit/ae7821e29690bea8934ea724bfd0ae4e2cf30c77)) +- **modal:** fixed `fullscreen` not worked ([5baaa58](https://github.com/anncwb/vue-vben-admin/commit/5baaa58581f22a915cda9fa39e4cb9f094254d3b)), closes [#918](https://github.com/anncwb/vue-vben-admin/issues/918) +- **model:** auto validate on value change ([f844017](https://github.com/anncwb/vue-vben-admin/commit/f8440175f35076073c9f53483cf6c0164d427ff4)), closes [#920](https://github.com/anncwb/vue-vben-admin/issues/920) +- **table:** fix index column style ([c7c0a7e](https://github.com/anncwb/vue-vben-admin/commit/c7c0a7e4c88a895000b1621981e4d4b2020c64b1)) +- **table:** `value` show problem in editable cell ([61ce25b](https://github.com/anncwb/vue-vben-admin/commit/61ce25be1b40d7a0e26205ca6a6757c6c43fc21e)), closes [#922](https://github.com/anncwb/vue-vben-admin/issues/922) +- **table-action:** fixed icon `margin` without label ([dc51e6a](https://github.com/anncwb/vue-vben-admin/commit/dc51e6a8d4e4f2c97b387b37959944c9bb49d779)) +- **tree:** fixed `checkedKeys` with `search` mode ([f707541](https://github.com/anncwb/vue-vben-admin/commit/f707541dda78146bda89814ddccbb259d9f5d8a2)) +- fix homePage affix error ([c117802](https://github.com/anncwb/vue-vben-admin/commit/c1178027f0fab2791d02efcd7c52beff5fc7dc25)) +- **table-action:** fix `circle` button style ([db7254a](https://github.com/anncwb/vue-vben-admin/commit/db7254a5e0ac6d10a7ea37334ad523b150facb19)) +- `menuSetting` can not set collapsed to false as default ([808291b](https://github.com/anncwb/vue-vben-admin/commit/808291b503d59e3026f5f0b5e7a38b9c69bcc451)) +- ensure PAGE_NOT_FOUND_ROUTE exist ([87583c8](https://github.com/anncwb/vue-vben-admin/commit/87583c8b54d335ddf1c416859ef62bbde189c809)) +- ensure that safari is running properly, fix [#875](https://github.com/anncwb/vue-vben-admin/issues/875) ([dafcdd8](https://github.com/anncwb/vue-vben-admin/commit/dafcdd898caae57104f1155b0ec660ea333e7b19)) +- infinite redirect in `BACK` mode ([4b46a84](https://github.com/anncwb/vue-vben-admin/commit/4b46a84c2b85e8da799426c54b3381ae93183db4)) +- **multiple-tab:** ignore login page ([1e63379](https://github.com/anncwb/vue-vben-admin/commit/1e63379088e1d7c823f29f607ab49d62ca22cb25)) +- resolving `Vue Router warn` ([237e65e](https://github.com/anncwb/vue-vben-admin/commit/237e65eac909368c4b4857da6c8deb1dc18e7684)) +- **table:** fix tree node align ([1e61da6](https://github.com/anncwb/vue-vben-admin/commit/1e61da644f65a79ce10fde98ee017aba7d36be10)), closes [#829](https://github.com/anncwb/vue-vben-admin/issues/829) +- **table:** scrollbar style ([d8c3820](https://github.com/anncwb/vue-vben-admin/commit/d8c38207c08510d805a8dc66ffbba210e0cf4215)) +- **table-action:** incorrect button color of `disabled` state ([0f28e80](https://github.com/anncwb/vue-vben-admin/commit/0f28e803d0b65537216cd9f40ad5cad63c20db9b)), closes [#891](https://github.com/anncwb/vue-vben-admin/issues/891) +- **upload:** ensure the value type is correct ([05329ce](https://github.com/anncwb/vue-vben-admin/commit/05329ce9501eb899a0bbb45320e5807c83372317)) +- **useWatermark:** fix `func` call `createWatermark` call `clear` to resizeEvent removed ([#901](https://github.com/anncwb/vue-vben-admin/issues/901)) ([a1d956d](https://github.com/anncwb/vue-vben-admin/commit/a1d956d3697cd07e0ba8910768f2a73e55f18491)) + +### Features + +- **api-tree-select:** add `api` options to tree-select ([d81db89](https://github.com/anncwb/vue-vben-admin/commit/d81db890dfeb533d60f378ddb86f8ac50a31252b)) +- **basic-table:** add `ApiTreeSelect` edit component ([52af1dd](https://github.com/anncwb/vue-vben-admin/commit/52af1dd0d494e66c0af20f886dcc2b983cbb096f)) +- **demo:** multi-modal in one page usage ([7a7dab0](https://github.com/anncwb/vue-vben-admin/commit/7a7dab0c4b3602b7bd3e9381408e4168d7494c52)) +- customized user home page ([0a3683a](https://github.com/anncwb/vue-vben-admin/commit/0a3683a186ab55d34a12a5a3c6d794dfa1094ad4)) +- **api-select:** clear options before fetch ([9cf070d](https://github.com/anncwb/vue-vben-admin/commit/9cf070dd6305bb69a67ab6be85ef00bddc86fda0)) +- **demo:** add basicTree with async data expand all ([5421211](https://github.com/anncwb/vue-vben-admin/commit/542121129eb5bf65f61e7b484835591756c80f04)) +- **demo:** add search demo for apiSelect ([41e6d94](https://github.com/anncwb/vue-vben-admin/commit/41e6d94b3b64dc0d40b7ec57ecfaa4d966f202ae)) +- **demo:** demo default expanded tree table ([5f1a6cd](https://github.com/anncwb/vue-vben-admin/commit/5f1a6cdc599d5840df2dfebdaad029aac093cd81)) +- **notice-list:** add `pagination` support ([c16be2c](https://github.com/anncwb/vue-vben-admin/commit/c16be2c499d90126dfa35d699da9294c21a4ab48)), closes [#894](https://github.com/anncwb/vue-vben-admin/issues/894) +- **table:** add `headerTop` slot ([540423e](https://github.com/anncwb/vue-vben-admin/commit/540423ecf741a815d28d7a6baa1541ac884efe95)), closes [#881](https://github.com/anncwb/vue-vben-admin/issues/881) + +### Performance Improvements + +- **menu:** Optimize the style of the bottom collapse button in the Mix menu layout ([#896](https://github.com/anncwb/vue-vben-admin/issues/896)) ([6f83070](https://github.com/anncwb/vue-vben-admin/commit/6f830703a2607c33e5d25d6d17d0e453fc2fac2e)) +- image compression configuration optimization ([cf840e3](https://github.com/anncwb/vue-vben-admin/commit/cf840e3e73b9572de0ba7bf7b32d83f6a353a8ad)) + +# [2.6.0](https://github.com/anncwb/vue-vben-admin/compare/v2.5.2...v2.6.0) (2021-07-04) + +### Bug Fixes + +- **axios:** option `withToken` not work ([d509e89](https://github.com/anncwb/vue-vben-admin/commit/d509e897be5753c852e912112e70dac6247ba467)) +- **demo:** account list fetch loss param ([424b171](https://github.com/anncwb/vue-vben-admin/commit/424b171e0db727f5e0157cbcfd5460f15f8ea609)), closes [#830](https://github.com/anncwb/vue-vben-admin/issues/830) +- **demo:** fix async tree demo, fixed: [#823](https://github.com/anncwb/vue-vben-admin/issues/823) ([5637588](https://github.com/anncwb/vue-vben-admin/commit/5637588fce880b01137191cc82c73e0fce621e8c)) +- **form:** fix some prop declaration ([b5046f0](https://github.com/anncwb/vue-vben-admin/commit/b5046f07a27e8ca7fc8b961b74fa5e1b0d715149)) +- **lock-screen:** ensure lock info is saved ([d38ff66](https://github.com/anncwb/vue-vben-admin/commit/d38ff6670a37478b31447f8058e786c4b044e218)) +- **lock-screen:** fix lock-screen can skip on new window ([d7b84c7](https://github.com/anncwb/vue-vben-admin/commit/d7b84c78744f7d0077a779b232e1358040b50383)) +- **menu:** make sure the menu is activated correctly ([cdb10cc](https://github.com/anncwb/vue-vben-admin/commit/cdb10cc4ac5e5e8f9cce3ff18d8fbd29ef10c86f)) +- **modal:** `setModalProps` support `defaultFullscreen` ([c7de65e](https://github.com/anncwb/vue-vben-admin/commit/c7de65ebba53941771153f18b184d3d4d31c0dbf)) +- **modal:** maskClosable not work ([f750ff4](https://github.com/anncwb/vue-vben-admin/commit/f750ff435fee06acee78d6b9633e6e18d91685f8)) +- **modal:** remove console log ([3dbbde2](https://github.com/anncwb/vue-vben-admin/commit/3dbbde2662352780377a9b216598d9348522f6ba)) +- **popconfirm-button:** remove button excess `title` ([73654b7](https://github.com/anncwb/vue-vben-admin/commit/73654b7862c59d623d6d5dc7dcf6ff2704564d9a)) +- **sider:** bottom trigger not work ([1bde404](https://github.com/anncwb/vue-vben-admin/commit/1bde4041211229d5d9d01ce0ca806fa99356b6de)), closes [#820](https://github.com/anncwb/vue-vben-admin/issues/820) +- **sider:** custom trigger does not take effect ([5005e6e](https://github.com/anncwb/vue-vben-admin/commit/5005e6e56b1cc7763a1cc23e1162dfb49452013b)) +- **svg-icon:** fix SvgIcon style ([99829c7](https://github.com/anncwb/vue-vben-admin/commit/99829c79ab41a2319f40c5595a7d82d9e406ba18)) +- **table:** auto hide unnecessary scrollbar ([735028c](https://github.com/anncwb/vue-vben-admin/commit/735028c43055e8e80ebc7344af0cd0f51c744f98)) +- **table:** global configuration accidentally modified ([b4a3f93](https://github.com/anncwb/vue-vben-admin/commit/b4a3f936cd19bf1fff3a331bacad60e79d2d6c22)) +- **table:** param of `handleSearchInfoFn` ([791b323](https://github.com/anncwb/vue-vben-admin/commit/791b323dbd30acd7fabfe9c3fb6e528916311ffd)) +- **tailwindcss:** remove console warnings ([acacb32](https://github.com/anncwb/vue-vben-admin/commit/acacb32bb592345cd0a90b4bbeb60a9b6ab1ac3c)) +- `hasPermission` not work in `ROLE` Mode ([76a5f87](https://github.com/anncwb/vue-vben-admin/commit/76a5f87c0ce871cca48b9e4c32331353a796e7d2)) +- routes filter can't effective when permission mode set to ROUTE_MAPPING ([#836](https://github.com/anncwb/vue-vben-admin/issues/836)) ([3871204](https://github.com/anncwb/vue-vben-admin/commit/3871204d08d481b8984440cd60bbf2bacb58d063)) +- **table:** selection-change not triggered on row click ([6f845b5](https://github.com/anncwb/vue-vben-admin/commit/6f845b53bdc4c33fbca3e65f10f64c63166bed0e)) +- multi windows token sharing ([e5f3788](https://github.com/anncwb/vue-vben-admin/commit/e5f37885ffb32d04d244f0ef39ac660dda6b71e1)), closes [#761](https://github.com/anncwb/vue-vben-admin/issues/761) +- support various vite modes of build, not just production ([#832](https://github.com/anncwb/vue-vben-admin/issues/832)) ([95c16a5](https://github.com/anncwb/vue-vben-admin/commit/95c16a5d26f9fd9a1d11894afe1146ca495eee93)) +- **table:** editComponentProps support onChange ([829b366](https://github.com/anncwb/vue-vben-admin/commit/829b366cb2abf27e69d9665e5be022b3d3f15655)) +- **table:** fix rowSelection.onChange not work ([df0f000](https://github.com/anncwb/vue-vben-admin/commit/df0f00085c1113eddd7a15954818ccece3538068)), closes [#825](https://github.com/anncwb/vue-vben-admin/issues/825) + +### Features + +- **avatar-cropper:** add action tooltip ([6cbac4b](https://github.com/anncwb/vue-vben-admin/commit/6cbac4b7ece60a1a7c1fda931cfffce42dfe3e51)) +- **avatar-cropper:** more props added ([b96ea07](https://github.com/anncwb/vue-vben-admin/commit/b96ea0753bfd769693a368cf1e3d8316688c0dcb)) +- **axios:** add `withToken` option ([c99cf5e](https://github.com/anncwb/vue-vben-admin/commit/c99cf5e53f057cdc332ab6c0635adf9c2d27de29)) +- **axios:** use `defHttp` like `axios` ([49f39de](https://github.com/anncwb/vue-vben-admin/commit/49f39de7b40e3ec8343bdeaf3eb00fd79d395746)), closes [#850](https://github.com/anncwb/vue-vben-admin/issues/850) +- **basic-upload:** `value` support v-model ([16c5d32](https://github.com/anncwb/vue-vben-admin/commit/16c5d327f1209f7c7437acde2ab0fa031da6a641)) +- **basic-upload:** add preview-delete event ([49e72a8](https://github.com/anncwb/vue-vben-admin/commit/49e72a8e76b78fe54e19de9e23d7c72a19427f01)), closes [#835](https://github.com/anncwb/vue-vben-admin/issues/835) +- **modal:** add `tooltip` for action buttons ([c3b9076](https://github.com/anncwb/vue-vben-admin/commit/c3b907656a5fad7a9b241562179f7a0f6fe0e6f0)) +- **param-menu:** feature: menu with params ([#845](https://github.com/anncwb/vue-vben-admin/issues/845)) ([48fcd76](https://github.com/anncwb/vue-vben-admin/commit/48fcd7684cabff66e8648b71527c6cb4ce7d03be)) +- **route:** add `hidePathForChildren` in `meta` ([d52b0de](https://github.com/anncwb/vue-vben-admin/commit/d52b0de83e69f7505c28e6f59ec84bbe526ecd0d)) +- **table:** support asynchrony in beforeFetch and afterFetch ([#827](https://github.com/anncwb/vue-vben-admin/issues/827)) ([749ba5c](https://github.com/anncwb/vue-vben-admin/commit/749ba5c1daf459625518937c239787b756c0a780)) +- **table-action:** support `tooltip` option ([5fab267](https://github.com/anncwb/vue-vben-admin/commit/5fab267a69600fdf5d7a7f9e4d9fff859d09dede)), closes [#848](https://github.com/anncwb/vue-vben-admin/issues/848) +- **tree:** add `insertNodesByKey` method ([5a20df4](https://github.com/anncwb/vue-vben-admin/commit/5a20df45ad36b523d48bf7fe11bdb10a6d03df64)) +- routers support `ignoreRoute` option ([72ac240](https://github.com/anncwb/vue-vben-admin/commit/72ac240f2858cd74cb62b7647ca89d63bb71d247)) + +### Performance Improvements + +- **scrollbar:** scrollbar update when slot changed ([e9e51b2](https://github.com/anncwb/vue-vben-admin/commit/e9e51b2fdc879a66d8df08504a0955c9c21e3e27)) + +## [2.5.1](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.5.1) (2021-06-26) + +### Bug Fixes + +- **comp-tree:** support comp-tree-foreach stop,add insertNodesByKey ([#818](https://github.com/anncwb/vue-vben-admin/issues/818)) ([d97aa92](https://github.com/anncwb/vue-vben-admin/commit/d97aa927417bf45a7c127ecfa9b8e835b6b68855)) +- fix antdv console warning ([480cfb9](https://github.com/anncwb/vue-vben-admin/commit/480cfb914e78c06eb7784e33465ed91b7d4c3eee)) +- fix defHttp baseUrl work ([d5f9919](https://github.com/anncwb/vue-vben-admin/commit/d5f9919b60fdd7d5c435129e8db519c0bbd37529)) +- **api:** select api type error ([b387681](https://github.com/anncwb/vue-vben-admin/commit/b387681c00ac018f5bc6a9251009ddffe37acae6)) +- **api-select:** ensure that the onchange function parameters are correct ([fa64fc8](https://github.com/anncwb/vue-vben-admin/commit/fa64fc8a622832b87fdf672965d55d543b5929a2)) +- **api-select:** loss option data on event callback ([c5f2577](https://github.com/anncwb/vue-vben-admin/commit/c5f2577f515e7ae96b27b509e5dd4b3317fcb7b4)), closes [#733](https://github.com/anncwb/vue-vben-admin/issues/733) +- **ApiSelect demo:** add demo about ApiSelect's use ([#757](https://github.com/anncwb/vue-vben-admin/issues/757)) ([a03d3cc](https://github.com/anncwb/vue-vben-admin/commit/a03d3cc60c770eba644c1f3837850a2c1c015029)) +- **demo:** `breadcrumb` route invalid redirect ([84d9300](https://github.com/anncwb/vue-vben-admin/commit/84d9300e52fa73da575591aa4b71858a7e459c8c)) +- **demo:** account list page validate and save ([21f7a85](https://github.com/anncwb/vue-vben-admin/commit/21f7a854fe2455315287d04e895661ff739bce17)) +- **demo:** make sure the map https resource is correct ([7b9cd09](https://github.com/anncwb/vue-vben-admin/commit/7b9cd09ad8a50c45b2e661e07953d786d82f367d)) +- **demo:** style error,fix [#806](https://github.com/anncwb/vue-vben-admin/issues/806) ([a2d8be3](https://github.com/anncwb/vue-vben-admin/commit/a2d8be3ab29da88126f3ba971f6893cb12327759)) +- **demo-form:** add fieldMapToTime example,fix [#807](https://github.com/anncwb/vue-vben-admin/issues/807) ([a2a75a0](https://github.com/anncwb/vue-vben-admin/commit/a2a75a097ff6c9df12471eff0d62d44d2b88cfff)) +- **design:** correct tailwind configuration,fix [#800](https://github.com/anncwb/vue-vben-admin/issues/800) ([aec230c](https://github.com/anncwb/vue-vben-admin/commit/aec230ca19d541079b64c54ba00596ef9cd92ca0)) +- **dropdown:** icon and trigger work unexpected ([60b80c9](https://github.com/anncwb/vue-vben-admin/commit/60b80c96e82da9101d56b2e195e9e7571de11f0a)), closes [#796](https://github.com/anncwb/vue-vben-admin/issues/796) [#787](https://github.com/anncwb/vue-vben-admin/issues/787) +- **flow-chart:** fix drag and drop menu loss ([fa828fd](https://github.com/anncwb/vue-vben-admin/commit/fa828fd972efeea87f364be76a1139ae53ec20d8)) +- **form:** loss args on component change event ([513823b](https://github.com/anncwb/vue-vben-admin/commit/513823bfbd3e8acc68098e0708c34bff2dd8dba6)) +- **layout:** props warn ([#756](https://github.com/anncwb/vue-vben-admin/issues/756)) ([bbce002](https://github.com/anncwb/vue-vben-admin/commit/bbce002be170c52db984647c931db88d7724cb52)) +- **menu:** fix the jitter problem of menu folding animation,fix [#732](https://github.com/anncwb/vue-vben-admin/issues/732) ([4c89ea7](https://github.com/anncwb/vue-vben-admin/commit/4c89ea7474f4315870df1790f99f3e431f343b90)) +- **mock:** make sure ignore matches the file correctly, fix [#745](https://github.com/anncwb/vue-vben-admin/issues/745) ([a222ec8](https://github.com/anncwb/vue-vben-admin/commit/a222ec8553f9b4477a43a8f7d113b5646fbfc373)) +- **mock:** type error ([7c1ffa3](https://github.com/anncwb/vue-vben-admin/commit/7c1ffa3d23de508a8d1590985806cb7a484b24e5)) +- **modal:** add v-model support for visible ([de12bab](https://github.com/anncwb/vue-vben-admin/commit/de12babd314ac831d3cb645f42dbf8a476075623)) +- **modal:** ensure that the full screen height is calculated correctly ([1c1755c](https://github.com/anncwb/vue-vben-admin/commit/1c1755cf5b4ada7263c05ddf4105abb52a2abb2f)) +- **modal:** ensure that the shutdown event is not triggered multiple times ([655b743](https://github.com/anncwb/vue-vben-admin/commit/655b74323653147943cbde2352208cb765c82b8a)) +- **pop-confirm:** fix event working unexpected ([a6ef771](https://github.com/anncwb/vue-vben-admin/commit/a6ef771fcce14c3644c965afaa69b3a17d0a7087)) +- **route:** dynamically introduce components error ([c6b766d](https://github.com/anncwb/vue-vben-admin/commit/c6b766d8ea902294ab1f7e4a06781f2bcfdd1f0b)) +- **router:** loss `directory` route ([df8cd86](https://github.com/anncwb/vue-vben-admin/commit/df8cd860514f32f44847dcf724f0737ed4d8b9e0)), closes [#722](https://github.com/anncwb/vue-vben-admin/issues/722) +- **store:** fix type error after pinia version upgrade ([e8d6f88](https://github.com/anncwb/vue-vben-admin/commit/e8d6f8851efd7076946486864936f1797280d3ba)) +- **table:** event editCancel loss params ([8d22231](https://github.com/anncwb/vue-vben-admin/commit/8d22231a5fa4afed19201a4a4e5c29d674498516)) +- **table:** fix table jitter problem ([8eba7fb](https://github.com/anncwb/vue-vben-admin/commit/8eba7fb52786d1977e4cb7b67673d74c91c5c827)) +- **table:** getDataSource not worked on empty data ([e78af6f](https://github.com/anncwb/vue-vben-admin/commit/e78af6f228e25f052dc4c5a1859a6db50e0b112e)), closes [#752](https://github.com/anncwb/vue-vben-admin/issues/752) +- **table:** treeTable editable error ([4ae39c5](https://github.com/anncwb/vue-vben-admin/commit/4ae39c53b49532fc6c31086a31e30429d2e236ed)), closes [#811](https://github.com/anncwb/vue-vben-admin/issues/811) +- **upload:** make sure to carry custom parameters, fix [#802](https://github.com/anncwb/vue-vben-admin/issues/802) ([c4b22a2](https://github.com/anncwb/vue-vben-admin/commit/c4b22a225d0088d87be0c0068f543366312521db)) +- **use-message:** `content` not support vNode ([154ebc3](https://github.com/anncwb/vue-vben-admin/commit/154ebc3d96f73bb3ceab99ea0229a3619d585aba)) +- build error ([5212ea7](https://github.com/anncwb/vue-vben-admin/commit/5212ea79b43c832a5136354b549de8f89b6e2156)) +- **avatar:** mock data and Account center style ([2066f66](https://github.com/anncwb/vue-vben-admin/commit/2066f669715491f3e91ac6d0e905cd2b3e80b58d)) +- **axios:** make sure that the parameter is an object before processing, fix [#660](https://github.com/anncwb/vue-vben-admin/issues/660) ([834fa7e](https://github.com/anncwb/vue-vben-admin/commit/834fa7eb9c8aff252e083d38fdab4f6f53b4d43a)) +- **axios:** transformRequestHook logic error ([b69dcd7](https://github.com/anncwb/vue-vben-admin/commit/b69dcd79d742fd171302ce0f48c7750d60da217f)) +- **code-editor:** fix CodeEditor style problem, fix [#655](https://github.com/anncwb/vue-vben-admin/issues/655) ([5662804](https://github.com/anncwb/vue-vben-admin/commit/566280422de0537c4e31496eaaa95a9d51fe9458)) +- **codeeditor:** empty value set failed.fixed:[#659](https://github.com/anncwb/vue-vben-admin/issues/659) ([ba2bebb](https://github.com/anncwb/vue-vben-admin/commit/ba2bebb4069085817a90d065ed5877fdb50a8039)) +- **codeMirror:** fix the JsonEditor embedded in the bullet frame causing the style to be disordered ([#668](https://github.com/anncwb/vue-vben-admin/issues/668)) ([e1123a2](https://github.com/anncwb/vue-vben-admin/commit/e1123a2ccb5d5450a5072c19e5508a5dc0f14423)) +- **demo:** fix basic form page style ([8b6e07b](https://github.com/anncwb/vue-vben-admin/commit/8b6e07b768f110f13b4f2efa6c46e03266667a8c)) +- **form:** fix form update problem ([bcad95d](https://github.com/anncwb/vue-vben-admin/commit/bcad95d32a08a73f84ecbabab409cd64159f4077)), closes [#720](https://github.com/anncwb/vue-vben-admin/issues/720) +- **form:** radioButtonGroup value support boolean ([9e2aa20](https://github.com/anncwb/vue-vben-admin/commit/9e2aa20daa08d2902cb5d56c1560306947e44939)) +- **form:** radioButtonGroup value support number ([bbddf30](https://github.com/anncwb/vue-vben-admin/commit/bbddf30e96feb1ab048323d93d3b8c1b18857acd)) +- **form:** schemas update problem ([808328d](https://github.com/anncwb/vue-vben-admin/commit/808328dc7e56b1cc07b678d501d9589290173443)), closes [#688](https://github.com/anncwb/vue-vben-admin/issues/688) +- **keep-alive:** tablist cache updating effect ([d62d0ca](https://github.com/anncwb/vue-vben-admin/commit/d62d0ca08cff442c23eb9265851b066a2f24afa8)), closes [#695](https://github.com/anncwb/vue-vben-admin/issues/695) +- **layout:** fix class loss ([d018363](https://github.com/anncwb/vue-vben-admin/commit/d018363ddcd68189a18829a2b2560f3b98da58a6)) +- **layout:** fix style compatibility issues ([905e5b7](https://github.com/anncwb/vue-vben-admin/commit/905e5b714b582548f32feca723012124343686a6)) +- **lock:** fix lock modal height ([40e3cb0](https://github.com/anncwb/vue-vben-admin/commit/40e3cb043c90a8343fa44a32acad2cb77de732da)), closes [#701](https://github.com/anncwb/vue-vben-admin/issues/701) +- **log:** fix Wrong version number ([#653](https://github.com/anncwb/vue-vben-admin/issues/653)) ([4f0d45f](https://github.com/anncwb/vue-vben-admin/commit/4f0d45f1df48755eadc0b09fa19762ee68f9abd1)) +- **login:** login page modal style fixed: [#662](https://github.com/anncwb/vue-vben-admin/issues/662) ([#666](https://github.com/anncwb/vue-vben-admin/issues/666)) ([b218f10](https://github.com/anncwb/vue-vben-admin/commit/b218f10e25a9364c399a5fe42eedb549f57c01ea)) +- **mock:** menu list api loss `type` field ([4185412](https://github.com/anncwb/vue-vben-admin/commit/41854121f3713dbde236afd3a416e9f27bd0c673)) +- **modal:** redoModalHeight not work as expected ([5d554f1](https://github.com/anncwb/vue-vben-admin/commit/5d554f184f7b61774d1a1b2e61451677b38505de)) +- **page:** `basic form` action btns should be in line ([6c4f947](https://github.com/anncwb/vue-vben-admin/commit/6c4f947386c181f45253c94e4ef735d29a253053)) +- **radio-button:** fix RadioButton `disabled` support ([ee384b1](https://github.com/anncwb/vue-vben-admin/commit/ee384b1fa7e387b3680e9d54cbe4a1e2f15ec750)), closes [#710](https://github.com/anncwb/vue-vben-admin/issues/710) +- **table:** wrong indeterminate state ([495b1da](https://github.com/anncwb/vue-vben-admin/commit/495b1da385e9b6428d2b994669d2065722445923)) +- **table:** make sure the table width is correct, fix [#593](https://github.com/anncwb/vue-vben-admin/issues/593) ([d73d43e](https://github.com/anncwb/vue-vben-admin/commit/d73d43ed91f30957cfd202c51552ca40a19cef08)) +- **table:** settings indeterminate state effect ([4fd2051](https://github.com/anncwb/vue-vben-admin/commit/4fd2051bc0403bfc5345ed6a5fc283a372ef7a92)) +- **table:** support change event ([9f4d171](https://github.com/anncwb/vue-vben-admin/commit/9f4d1719caa76de94e6362c16e4df3ac28df253c)), closes [#677](https://github.com/anncwb/vue-vben-admin/issues/677) +- **table:** try to get close to the form stuck ([d81481c](https://github.com/anncwb/vue-vben-admin/commit/d81481c52186145dac130aaa1594f0ba8db4d392)) +- **table:** useTable support onChange ([9f5085c](https://github.com/anncwb/vue-vben-admin/commit/9f5085c9f9f46b09391156b17091c1771bc13026)) +- **table-action:** fix the split line style is missing,fix [#674](https://github.com/anncwb/vue-vben-admin/issues/674) ([b1cb863](https://github.com/anncwb/vue-vben-admin/commit/b1cb86350253dc5be095466966d9469775f4395d)) +- **Tinymce:** Read only status upload button can also be used ([#718](https://github.com/anncwb/vue-vben-admin/issues/718)) ([966571b](https://github.com/anncwb/vue-vben-admin/commit/966571bdcb11c2729ab9ce212bd3e195f7bf3a59)) +- **upload:** ensure preview items valid ([4376928](https://github.com/anncwb/vue-vben-admin/commit/437692869a232ee65c300c65ee473557ae0913c7)) +- ensure that roleList is not empty ([aebad61](https://github.com/anncwb/vue-vben-admin/commit/aebad61b3d3e11aaf720b37e762e53e2e6999d3c)) +- fix darkModeSwitch switch failure ([34a8054](https://github.com/anncwb/vue-vben-admin/commit/34a80542de670f0385dffaf5bf64bb9c3f6b90da)) +- fix if getDropdownList.length==0 show Dropdown component ([21c771b](https://github.com/anncwb/vue-vben-admin/commit/21c771b59cb45defbff37de21c5c1950370b8f92)) +- fix Login Page LocalePicker showLocale condition ([d683b0f](https://github.com/anncwb/vue-vben-admin/commit/d683b0f1e85b85b07090feba4ac7f741bd3bd482)) +- fix node12 version data mock error ([644dbe3](https://github.com/anncwb/vue-vben-admin/commit/644dbe315bb03ea1641a682359873237208a5303)) +- Fix the problem that the `lang` attribute of `HTML` will not be set when it is first loaded ([#682](https://github.com/anncwb/vue-vben-admin/issues/682)) ([eca8907](https://github.com/anncwb/vue-vben-admin/commit/eca8907a11c28d816c3da5a0667f45a38a499012)) +- login failed ([035f55a](https://github.com/anncwb/vue-vben-admin/commit/035f55af9778819d72adc1700d9de56a6569b58f)) +- session timeout login logic error ([#678](https://github.com/anncwb/vue-vben-admin/issues/678)) ([132c7fb](https://github.com/anncwb/vue-vben-admin/commit/132c7fb944df255c4d76a25d6d924439f91f9c54)), closes [#673](https://github.com/anncwb/vue-vben-admin/issues/673) +- **tree:** support defaultExpandAll prop ([3ed2339](https://github.com/anncwb/vue-vben-admin/commit/3ed2339a6d75abbd6ccf723b6eaa762f9921409e)) +- **useViewHeight:** Fix the problem that useContentViewHeight does not calculate the footer ([#747](https://github.com/anncwb/vue-vben-admin/issues/747)) ([33cd8fe](https://github.com/anncwb/vue-vben-admin/commit/33cd8fe6533830176ab63ddfc4d74f75a384366c)) +- theme switching fails ([7e2ca79](https://github.com/anncwb/vue-vben-admin/commit/7e2ca79ece2f5209cb7ce4b0f5ee15012f9f51de)) + +### Features + +- **demo:** add route multi tabs show ([0e414ba](https://github.com/anncwb/vue-vben-admin/commit/0e414ba3c10b4e47a85feb1a38cae66c815719d8)), closes [#817](https://github.com/anncwb/vue-vben-admin/issues/817) +- add Tree LoadData demo ([9298b3c](https://github.com/anncwb/vue-vben-admin/commit/9298b3c988c10b81d83430ca31b9ce1d98a3fad9)) +- optimize error message for api failure ([ea6834a](https://github.com/anncwb/vue-vben-admin/commit/ea6834aeec3ef56d411b2c10a474f75d3d7bfdfc)) +- **api-select:** auto refetch after params changed ([50207ad](https://github.com/anncwb/vue-vben-admin/commit/50207ad702ef3faca1e27c873c89132ab92fae8e)) +- **app-search:** auto focus on show ([1ae6362](https://github.com/anncwb/vue-vben-admin/commit/1ae636296df2cf99e8a777f053c539c50e6ad49a)) +- **axios:** added authenticationScheme configuration,fix [#774](https://github.com/anncwb/vue-vben-admin/issues/774) ([b6d5b07](https://github.com/anncwb/vue-vben-admin/commit/b6d5b0796de4d0b66c0f33c335ec991d44f64ef2)) +- **demo:** `switch` use in table ([46899aa](https://github.com/anncwb/vue-vben-admin/commit/46899aa3cd6b1616c42ac263a28af75be839f6a0)) +- **demo:** added guide page example ([d196340](https://github.com/anncwb/vue-vben-admin/commit/d196340d270d2becbf2cc81b7d4f09273381bd09)) +- **echarts:** add getInstance for useECharts ([fb6c76d](https://github.com/anncwb/vue-vben-admin/commit/fb6c76db535bd0c6305d03c0cff876a1f079100b)) +- **modal:** add closeModal for useModal ([6d5f9aa](https://github.com/anncwb/vue-vben-admin/commit/6d5f9aa699c5da8af6bf5841baddc4a8bd603917)) +- **modal:** add redoModalHeight for useModalInner ([f732b56](https://github.com/anncwb/vue-vben-admin/commit/f732b569042f7fe77c85cb295538ddd85561f7e9)) +- **preview:** added createImgPreview picture preview function ([305630e](https://github.com/anncwb/vue-vben-admin/commit/305630e3fd886b3f690f890a934a8a6ba224fba1)) +- **project-setting:** added sessionTimeoutProcessing project configuration item,fix [#772](https://github.com/anncwb/vue-vben-admin/issues/772) ([0d07084](https://github.com/anncwb/vue-vben-admin/commit/0d0708409c4adbe7a0c5e33abf5307031147eaeb)) +- **table:** add editable DatePicker & TimePicker ([#654](https://github.com/anncwb/vue-vben-admin/issues/654)) ([93006c7](https://github.com/anncwb/vue-vben-admin/commit/93006c7dc7b5243b26637f444c8057c95935e622)) +- **table:** add updateTableDataRecord method ([8e4f486](https://github.com/anncwb/vue-vben-admin/commit/8e4f486fcf835f0b6f2a95676dba268ffdd0566e)) +- **table:** editable component text align ([8eaf575](https://github.com/anncwb/vue-vben-admin/commit/8eaf57562610a833c8083ae9957f458319d1cc93)) +- **table:** support columns-change event ([125a7d1](https://github.com/anncwb/vue-vben-admin/commit/125a7d14831642c9cbb2e4b3e75953c3b2e2cdef)) +- **table:** support custom update on row editing ([fe2bcfc](https://github.com/anncwb/vue-vben-admin/commit/fe2bcfc6f74159c355f3be153a316869fdb8b644)), closes [#646](https://github.com/anncwb/vue-vben-admin/issues/646) +- **table:** updateTableDataRecord support functional rowKey ([448a4c2](https://github.com/anncwb/vue-vben-admin/commit/448a4c2809672480f8f635d7cc4661554112598a)) +- **table-action:** add stopButtonPropagation prop ([808012b](https://github.com/anncwb/vue-vben-admin/commit/808012b544b8c6f3cf467f42653c2783dbe8be6b)), closes [#699](https://github.com/anncwb/vue-vben-admin/issues/699) +- **table-img:** support simple show mode and more props ([19d8e01](https://github.com/anncwb/vue-vben-admin/commit/19d8e01e11644c66222f137abd05940cbdec0bb6)) +- **test:** add jest test suite ([f6fe1dd](https://github.com/anncwb/vue-vben-admin/commit/f6fe1dd62df231ccbd063db0d32359b48aa5c76b)) +- **use-drawer:** add closeDrawer function ([639520a](https://github.com/anncwb/vue-vben-admin/commit/639520ad5ddf829875ab517067abf2b45ebc04c2)) +- add CropperAvatar component ([8e410fc](https://github.com/anncwb/vue-vben-admin/commit/8e410fc6401847d8e5545468b5ce6fd7ce9fc5cc)) +- **tabs:** add setTabTitle method ([#680](https://github.com/anncwb/vue-vben-admin/issues/680)) ([5ddccf6](https://github.com/anncwb/vue-vben-admin/commit/5ddccf6ba28453b9a35355d53d0db65f1a8876bc)) +- **tinymce:** support dark theme and I18n ([83c9cd7](https://github.com/anncwb/vue-vben-admin/commit/83c9cd77421e9c0888a41e2d8dcbca816da67488)) +- **Tinymce:** add dynamics to the read-only state of the rich text editor ([#725](https://github.com/anncwb/vue-vben-admin/issues/725)) ([efce482](https://github.com/anncwb/vue-vben-admin/commit/efce482b3215ddf9ed588f63a218d5f76939e947)) +- **tree:** add defaultExpandLevel prop ([6edca1c](https://github.com/anncwb/vue-vben-admin/commit/6edca1c19c3b0772f9ab82a7b09251a74fff2173)), closes [#672](https://github.com/anncwb/vue-vben-admin/issues/672) + +### Performance Improvements + +- **component:** optimize tree and upload components ([3f6920f](https://github.com/anncwb/vue-vben-admin/commit/3f6920f7a9775fc06a34dead90b1724b23b7759c)) +- **cropper-avatar:** code optimization ([6dbbdba](https://github.com/anncwb/vue-vben-admin/commit/6dbbdbac76c2c3795e12dd346f6310d1b70f6a7d)) +- **i18n:** improve circular dependencies ([d677729](https://github.com/anncwb/vue-vben-admin/commit/d677729acbe2c024ab13cf490b205528507c4823)) +- **i18n:** improve warning prompt ([6ef62ba](https://github.com/anncwb/vue-vben-admin/commit/6ef62ba6ea7f5613a1fec982b30fe6b0f478bf59)) +- **locale:** reduce the number of multilingual files ([0acc4ab](https://github.com/anncwb/vue-vben-admin/commit/0acc4ab2dd70a239bd13929edede02b283feb7c2)) +- **pagewrapper:** 优化 PageWrapper 的高度自适应表现使用 getViewportOffset 替代 useContentViewHeight ([#792](https://github.com/anncwb/vue-vben-admin/issues/792)) ([4d8e398](https://github.com/anncwb/vue-vben-admin/commit/4d8e39857ea59fff99e69832b4a8cabf3a424c24)) +- **PageWrapper:** fix the height calculation problem when footer and global footer are opened at the same time ([#760](https://github.com/anncwb/vue-vben-admin/issues/760)) ([ab2c7ef](https://github.com/anncwb/vue-vben-admin/commit/ab2c7efe6994dacfe0ff407783f2c3b246427bfc)) +- **utils:** mitt default export is changed from Class to Function ([d3d620f](https://github.com/anncwb/vue-vben-admin/commit/d3d620f4fc75dd69270e4d090a71d426701272ef)) +- add createImgPreview func ([#713](https://github.com/anncwb/vue-vben-admin/issues/713)) ([b7c7c46](https://github.com/anncwb/vue-vben-admin/commit/b7c7c46853d332641d116d818e657447884784f3)) +- optimize components and add comments ([55e9d9f](https://github.com/anncwb/vue-vben-admin/commit/55e9d9fc2953643cec95c74b6ed34b0e68641fb6)) + +### Reverts + +- **axios:** remove baseUrl config ([61d4efd](https://github.com/anncwb/vue-vben-admin/commit/61d4efd55a8b4f09990b5f1888e23ead43958164)) + +## [2.5.1](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.5.1) (2021-06-26) + +### Bug Fixes + +- fix antdv console warning ([480cfb9](https://github.com/anncwb/vue-vben-admin/commit/480cfb914e78c06eb7784e33465ed91b7d4c3eee)) +- fix defHttp baseUrl work ([d5f9919](https://github.com/anncwb/vue-vben-admin/commit/d5f9919b60fdd7d5c435129e8db519c0bbd37529)) +- **api:** select api type error ([b387681](https://github.com/anncwb/vue-vben-admin/commit/b387681c00ac018f5bc6a9251009ddffe37acae6)) +- **api-select:** ensure that the onchange function parameters are correct ([fa64fc8](https://github.com/anncwb/vue-vben-admin/commit/fa64fc8a622832b87fdf672965d55d543b5929a2)) +- **api-select:** loss option data on event callback ([c5f2577](https://github.com/anncwb/vue-vben-admin/commit/c5f2577f515e7ae96b27b509e5dd4b3317fcb7b4)), closes [#733](https://github.com/anncwb/vue-vben-admin/issues/733) +- **ApiSelect demo:** add demo about ApiSelect's use ([#757](https://github.com/anncwb/vue-vben-admin/issues/757)) ([a03d3cc](https://github.com/anncwb/vue-vben-admin/commit/a03d3cc60c770eba644c1f3837850a2c1c015029)) +- **avatar:** mock data and Account center style ([2066f66](https://github.com/anncwb/vue-vben-admin/commit/2066f669715491f3e91ac6d0e905cd2b3e80b58d)) +- **axios:** make sure that the parameter is an object before processing, fix [#660](https://github.com/anncwb/vue-vben-admin/issues/660) ([834fa7e](https://github.com/anncwb/vue-vben-admin/commit/834fa7eb9c8aff252e083d38fdab4f6f53b4d43a)) +- **axios:** transformRequestHook logic error ([b69dcd7](https://github.com/anncwb/vue-vben-admin/commit/b69dcd79d742fd171302ce0f48c7750d60da217f)) +- **code-editor:** fix CodeEditor style problem, fix [#655](https://github.com/anncwb/vue-vben-admin/issues/655) ([5662804](https://github.com/anncwb/vue-vben-admin/commit/566280422de0537c4e31496eaaa95a9d51fe9458)) +- **codeeditor:** empty value set failed.fixed:[#659](https://github.com/anncwb/vue-vben-admin/issues/659) ([ba2bebb](https://github.com/anncwb/vue-vben-admin/commit/ba2bebb4069085817a90d065ed5877fdb50a8039)) +- **codeMirror:** fix the JsonEditor embedded in the bullet frame causing the style to be disordered ([#668](https://github.com/anncwb/vue-vben-admin/issues/668)) ([e1123a2](https://github.com/anncwb/vue-vben-admin/commit/e1123a2ccb5d5450a5072c19e5508a5dc0f14423)) +- **demo:** `breadcrumb` route invalid redirect ([84d9300](https://github.com/anncwb/vue-vben-admin/commit/84d9300e52fa73da575591aa4b71858a7e459c8c)) +- **demo:** account list page validate and save ([21f7a85](https://github.com/anncwb/vue-vben-admin/commit/21f7a854fe2455315287d04e895661ff739bce17)) +- **demo:** fix basic form page style ([8b6e07b](https://github.com/anncwb/vue-vben-admin/commit/8b6e07b768f110f13b4f2efa6c46e03266667a8c)) +- **demo:** make sure the map https resource is correct ([7b9cd09](https://github.com/anncwb/vue-vben-admin/commit/7b9cd09ad8a50c45b2e661e07953d786d82f367d)) +- **demo:** style error,fix [#806](https://github.com/anncwb/vue-vben-admin/issues/806) ([a2d8be3](https://github.com/anncwb/vue-vben-admin/commit/a2d8be3ab29da88126f3ba971f6893cb12327759)) +- **demo-form:** add fieldMapToTime example,fix [#807](https://github.com/anncwb/vue-vben-admin/issues/807) ([a2a75a0](https://github.com/anncwb/vue-vben-admin/commit/a2a75a097ff6c9df12471eff0d62d44d2b88cfff)) +- **design:** correct tailwind configuration,fix [#800](https://github.com/anncwb/vue-vben-admin/issues/800) ([aec230c](https://github.com/anncwb/vue-vben-admin/commit/aec230ca19d541079b64c54ba00596ef9cd92ca0)) +- **dropdown:** icon and trigger work unexpected ([60b80c9](https://github.com/anncwb/vue-vben-admin/commit/60b80c96e82da9101d56b2e195e9e7571de11f0a)), closes [#796](https://github.com/anncwb/vue-vben-admin/issues/796) [#787](https://github.com/anncwb/vue-vben-admin/issues/787) +- **flow-chart:** fix drag and drop menu loss ([fa828fd](https://github.com/anncwb/vue-vben-admin/commit/fa828fd972efeea87f364be76a1139ae53ec20d8)) +- **form:** fix form update problem ([bcad95d](https://github.com/anncwb/vue-vben-admin/commit/bcad95d32a08a73f84ecbabab409cd64159f4077)), closes [#720](https://github.com/anncwb/vue-vben-admin/issues/720) +- **form:** loss args on component change event ([513823b](https://github.com/anncwb/vue-vben-admin/commit/513823bfbd3e8acc68098e0708c34bff2dd8dba6)) +- **form:** radioButtonGroup value support boolean ([9e2aa20](https://github.com/anncwb/vue-vben-admin/commit/9e2aa20daa08d2902cb5d56c1560306947e44939)) +- **form:** radioButtonGroup value support number ([bbddf30](https://github.com/anncwb/vue-vben-admin/commit/bbddf30e96feb1ab048323d93d3b8c1b18857acd)) +- **form:** schemas update problem ([808328d](https://github.com/anncwb/vue-vben-admin/commit/808328dc7e56b1cc07b678d501d9589290173443)), closes [#688](https://github.com/anncwb/vue-vben-admin/issues/688) +- **keep-alive:** tablist cache updating effect ([d62d0ca](https://github.com/anncwb/vue-vben-admin/commit/d62d0ca08cff442c23eb9265851b066a2f24afa8)), closes [#695](https://github.com/anncwb/vue-vben-admin/issues/695) +- **layout:** fix class loss ([d018363](https://github.com/anncwb/vue-vben-admin/commit/d018363ddcd68189a18829a2b2560f3b98da58a6)) +- **layout:** fix style compatibility issues ([905e5b7](https://github.com/anncwb/vue-vben-admin/commit/905e5b714b582548f32feca723012124343686a6)) +- **layout:** props warn ([#756](https://github.com/anncwb/vue-vben-admin/issues/756)) ([bbce002](https://github.com/anncwb/vue-vben-admin/commit/bbce002be170c52db984647c931db88d7724cb52)) +- **lock:** fix lock modal height ([40e3cb0](https://github.com/anncwb/vue-vben-admin/commit/40e3cb043c90a8343fa44a32acad2cb77de732da)), closes [#701](https://github.com/anncwb/vue-vben-admin/issues/701) +- **log:** fix Wrong version number ([#653](https://github.com/anncwb/vue-vben-admin/issues/653)) ([4f0d45f](https://github.com/anncwb/vue-vben-admin/commit/4f0d45f1df48755eadc0b09fa19762ee68f9abd1)) +- **login:** login page modal style fixed: [#662](https://github.com/anncwb/vue-vben-admin/issues/662) ([#666](https://github.com/anncwb/vue-vben-admin/issues/666)) ([b218f10](https://github.com/anncwb/vue-vben-admin/commit/b218f10e25a9364c399a5fe42eedb549f57c01ea)) +- **menu:** fix the jitter problem of menu folding animation,fix [#732](https://github.com/anncwb/vue-vben-admin/issues/732) ([4c89ea7](https://github.com/anncwb/vue-vben-admin/commit/4c89ea7474f4315870df1790f99f3e431f343b90)) +- **mock:** make sure ignore matches the file correctly, fix [#745](https://github.com/anncwb/vue-vben-admin/issues/745) ([a222ec8](https://github.com/anncwb/vue-vben-admin/commit/a222ec8553f9b4477a43a8f7d113b5646fbfc373)) +- **mock:** menu list api loss `type` field ([4185412](https://github.com/anncwb/vue-vben-admin/commit/41854121f3713dbde236afd3a416e9f27bd0c673)) +- **mock:** type error ([7c1ffa3](https://github.com/anncwb/vue-vben-admin/commit/7c1ffa3d23de508a8d1590985806cb7a484b24e5)) +- **modal:** add v-model support for visible ([de12bab](https://github.com/anncwb/vue-vben-admin/commit/de12babd314ac831d3cb645f42dbf8a476075623)) +- **modal:** ensure that the full screen height is calculated correctly ([1c1755c](https://github.com/anncwb/vue-vben-admin/commit/1c1755cf5b4ada7263c05ddf4105abb52a2abb2f)) +- **modal:** ensure that the shutdown event is not triggered multiple times ([655b743](https://github.com/anncwb/vue-vben-admin/commit/655b74323653147943cbde2352208cb765c82b8a)) +- **modal:** redoModalHeight not work as expected ([5d554f1](https://github.com/anncwb/vue-vben-admin/commit/5d554f184f7b61774d1a1b2e61451677b38505de)) +- **page:** `basic form` action btns should be in line ([6c4f947](https://github.com/anncwb/vue-vben-admin/commit/6c4f947386c181f45253c94e4ef735d29a253053)) +- **pop-confirm:** fix event working unexpected ([a6ef771](https://github.com/anncwb/vue-vben-admin/commit/a6ef771fcce14c3644c965afaa69b3a17d0a7087)) +- **radio-button:** fix RadioButton `disabled` support ([ee384b1](https://github.com/anncwb/vue-vben-admin/commit/ee384b1fa7e387b3680e9d54cbe4a1e2f15ec750)), closes [#710](https://github.com/anncwb/vue-vben-admin/issues/710) +- **route:** dynamically introduce components error ([c6b766d](https://github.com/anncwb/vue-vben-admin/commit/c6b766d8ea902294ab1f7e4a06781f2bcfdd1f0b)) +- **router:** loss `directory` route ([df8cd86](https://github.com/anncwb/vue-vben-admin/commit/df8cd860514f32f44847dcf724f0737ed4d8b9e0)), closes [#722](https://github.com/anncwb/vue-vben-admin/issues/722) +- **store:** fix type error after pinia version upgrade ([e8d6f88](https://github.com/anncwb/vue-vben-admin/commit/e8d6f8851efd7076946486864936f1797280d3ba)) +- **table:** wrong indeterminate state ([495b1da](https://github.com/anncwb/vue-vben-admin/commit/495b1da385e9b6428d2b994669d2065722445923)) +- **table:** event editCancel loss params ([8d22231](https://github.com/anncwb/vue-vben-admin/commit/8d22231a5fa4afed19201a4a4e5c29d674498516)) +- **table:** fix table jitter problem ([8eba7fb](https://github.com/anncwb/vue-vben-admin/commit/8eba7fb52786d1977e4cb7b67673d74c91c5c827)) +- **table:** getDataSource not worked on empty data ([e78af6f](https://github.com/anncwb/vue-vben-admin/commit/e78af6f228e25f052dc4c5a1859a6db50e0b112e)), closes [#752](https://github.com/anncwb/vue-vben-admin/issues/752) +- **table:** make sure the table width is correct, fix [#593](https://github.com/anncwb/vue-vben-admin/issues/593) ([d73d43e](https://github.com/anncwb/vue-vben-admin/commit/d73d43ed91f30957cfd202c51552ca40a19cef08)) +- **table:** settings indeterminate state effect ([4fd2051](https://github.com/anncwb/vue-vben-admin/commit/4fd2051bc0403bfc5345ed6a5fc283a372ef7a92)) +- **table:** support change event ([9f4d171](https://github.com/anncwb/vue-vben-admin/commit/9f4d1719caa76de94e6362c16e4df3ac28df253c)), closes [#677](https://github.com/anncwb/vue-vben-admin/issues/677) +- **table:** treeTable editable error ([4ae39c5](https://github.com/anncwb/vue-vben-admin/commit/4ae39c53b49532fc6c31086a31e30429d2e236ed)), closes [#811](https://github.com/anncwb/vue-vben-admin/issues/811) +- **table:** try to get close to the form stuck ([d81481c](https://github.com/anncwb/vue-vben-admin/commit/d81481c52186145dac130aaa1594f0ba8db4d392)) +- **table:** useTable support onChange ([9f5085c](https://github.com/anncwb/vue-vben-admin/commit/9f5085c9f9f46b09391156b17091c1771bc13026)) +- **table-action:** fix the split line style is missing,fix [#674](https://github.com/anncwb/vue-vben-admin/issues/674) ([b1cb863](https://github.com/anncwb/vue-vben-admin/commit/b1cb86350253dc5be095466966d9469775f4395d)) +- **Tinymce:** Read only status upload button can also be used ([#718](https://github.com/anncwb/vue-vben-admin/issues/718)) ([966571b](https://github.com/anncwb/vue-vben-admin/commit/966571bdcb11c2729ab9ce212bd3e195f7bf3a59)) +- **upload:** ensure preview items valid ([4376928](https://github.com/anncwb/vue-vben-admin/commit/437692869a232ee65c300c65ee473557ae0913c7)) +- **upload:** make sure to carry custom parameters, fix [#802](https://github.com/anncwb/vue-vben-admin/issues/802) ([c4b22a2](https://github.com/anncwb/vue-vben-admin/commit/c4b22a225d0088d87be0c0068f543366312521db)) +- **use-message:** `content` not support vNode ([154ebc3](https://github.com/anncwb/vue-vben-admin/commit/154ebc3d96f73bb3ceab99ea0229a3619d585aba)) +- build error ([5212ea7](https://github.com/anncwb/vue-vben-admin/commit/5212ea79b43c832a5136354b549de8f89b6e2156)) +- ensure that roleList is not empty ([aebad61](https://github.com/anncwb/vue-vben-admin/commit/aebad61b3d3e11aaf720b37e762e53e2e6999d3c)) +- fix darkModeSwitch switch failure ([34a8054](https://github.com/anncwb/vue-vben-admin/commit/34a80542de670f0385dffaf5bf64bb9c3f6b90da)) +- fix if getDropdownList.length==0 show Dropdown component ([21c771b](https://github.com/anncwb/vue-vben-admin/commit/21c771b59cb45defbff37de21c5c1950370b8f92)) +- fix Login Page LocalePicker showLocale condition ([d683b0f](https://github.com/anncwb/vue-vben-admin/commit/d683b0f1e85b85b07090feba4ac7f741bd3bd482)) +- fix node12 version data mock error ([644dbe3](https://github.com/anncwb/vue-vben-admin/commit/644dbe315bb03ea1641a682359873237208a5303)) +- Fix the problem that the `lang` attribute of `HTML` will not be set when it is first loaded ([#682](https://github.com/anncwb/vue-vben-admin/issues/682)) ([eca8907](https://github.com/anncwb/vue-vben-admin/commit/eca8907a11c28d816c3da5a0667f45a38a499012)) +- login failed ([035f55a](https://github.com/anncwb/vue-vben-admin/commit/035f55af9778819d72adc1700d9de56a6569b58f)) +- session timeout login logic error ([#678](https://github.com/anncwb/vue-vben-admin/issues/678)) ([132c7fb](https://github.com/anncwb/vue-vben-admin/commit/132c7fb944df255c4d76a25d6d924439f91f9c54)), closes [#673](https://github.com/anncwb/vue-vben-admin/issues/673) +- **tree:** support defaultExpandAll prop ([3ed2339](https://github.com/anncwb/vue-vben-admin/commit/3ed2339a6d75abbd6ccf723b6eaa762f9921409e)) +- **useViewHeight:** Fix the problem that useContentViewHeight does not calculate the footer ([#747](https://github.com/anncwb/vue-vben-admin/issues/747)) ([33cd8fe](https://github.com/anncwb/vue-vben-admin/commit/33cd8fe6533830176ab63ddfc4d74f75a384366c)) +- theme switching fails ([7e2ca79](https://github.com/anncwb/vue-vben-admin/commit/7e2ca79ece2f5209cb7ce4b0f5ee15012f9f51de)) + +### Features + +- **demo:** add route multi tabs show ([0e414ba](https://github.com/anncwb/vue-vben-admin/commit/0e414ba3c10b4e47a85feb1a38cae66c815719d8)), closes [#817](https://github.com/anncwb/vue-vben-admin/issues/817) +- add Tree LoadData demo ([9298b3c](https://github.com/anncwb/vue-vben-admin/commit/9298b3c988c10b81d83430ca31b9ce1d98a3fad9)) +- optimize error message for api failure ([ea6834a](https://github.com/anncwb/vue-vben-admin/commit/ea6834aeec3ef56d411b2c10a474f75d3d7bfdfc)) +- **api-select:** auto refetch after params changed ([50207ad](https://github.com/anncwb/vue-vben-admin/commit/50207ad702ef3faca1e27c873c89132ab92fae8e)) +- **app-search:** auto focus on show ([1ae6362](https://github.com/anncwb/vue-vben-admin/commit/1ae636296df2cf99e8a777f053c539c50e6ad49a)) +- **axios:** added authenticationScheme configuration,fix [#774](https://github.com/anncwb/vue-vben-admin/issues/774) ([b6d5b07](https://github.com/anncwb/vue-vben-admin/commit/b6d5b0796de4d0b66c0f33c335ec991d44f64ef2)) +- **demo:** `switch` use in table ([46899aa](https://github.com/anncwb/vue-vben-admin/commit/46899aa3cd6b1616c42ac263a28af75be839f6a0)) +- **demo:** added guide page example ([d196340](https://github.com/anncwb/vue-vben-admin/commit/d196340d270d2becbf2cc81b7d4f09273381bd09)) +- **echarts:** add getInstance for useECharts ([fb6c76d](https://github.com/anncwb/vue-vben-admin/commit/fb6c76db535bd0c6305d03c0cff876a1f079100b)) +- **modal:** add closeModal for useModal ([6d5f9aa](https://github.com/anncwb/vue-vben-admin/commit/6d5f9aa699c5da8af6bf5841baddc4a8bd603917)) +- **modal:** add redoModalHeight for useModalInner ([f732b56](https://github.com/anncwb/vue-vben-admin/commit/f732b569042f7fe77c85cb295538ddd85561f7e9)) +- **preview:** added createImgPreview picture preview function ([305630e](https://github.com/anncwb/vue-vben-admin/commit/305630e3fd886b3f690f890a934a8a6ba224fba1)) +- **project-setting:** added sessionTimeoutProcessing project configuration item,fix [#772](https://github.com/anncwb/vue-vben-admin/issues/772) ([0d07084](https://github.com/anncwb/vue-vben-admin/commit/0d0708409c4adbe7a0c5e33abf5307031147eaeb)) +- **table:** add editable DatePicker & TimePicker ([#654](https://github.com/anncwb/vue-vben-admin/issues/654)) ([93006c7](https://github.com/anncwb/vue-vben-admin/commit/93006c7dc7b5243b26637f444c8057c95935e622)) +- **table:** add updateTableDataRecord method ([8e4f486](https://github.com/anncwb/vue-vben-admin/commit/8e4f486fcf835f0b6f2a95676dba268ffdd0566e)) +- **table:** editable component text align ([8eaf575](https://github.com/anncwb/vue-vben-admin/commit/8eaf57562610a833c8083ae9957f458319d1cc93)) +- **table:** support columns-change event ([125a7d1](https://github.com/anncwb/vue-vben-admin/commit/125a7d14831642c9cbb2e4b3e75953c3b2e2cdef)) +- **table:** support custom update on row editing ([fe2bcfc](https://github.com/anncwb/vue-vben-admin/commit/fe2bcfc6f74159c355f3be153a316869fdb8b644)), closes [#646](https://github.com/anncwb/vue-vben-admin/issues/646) +- **table:** updateTableDataRecord support functional rowKey ([448a4c2](https://github.com/anncwb/vue-vben-admin/commit/448a4c2809672480f8f635d7cc4661554112598a)) +- **table-action:** add stopButtonPropagation prop ([808012b](https://github.com/anncwb/vue-vben-admin/commit/808012b544b8c6f3cf467f42653c2783dbe8be6b)), closes [#699](https://github.com/anncwb/vue-vben-admin/issues/699) +- **table-img:** support simple show mode and more props ([19d8e01](https://github.com/anncwb/vue-vben-admin/commit/19d8e01e11644c66222f137abd05940cbdec0bb6)) +- **test:** add jest test suite ([f6fe1dd](https://github.com/anncwb/vue-vben-admin/commit/f6fe1dd62df231ccbd063db0d32359b48aa5c76b)) +- **use-drawer:** add closeDrawer function ([639520a](https://github.com/anncwb/vue-vben-admin/commit/639520ad5ddf829875ab517067abf2b45ebc04c2)) +- add CropperAvatar component ([8e410fc](https://github.com/anncwb/vue-vben-admin/commit/8e410fc6401847d8e5545468b5ce6fd7ce9fc5cc)) +- **tabs:** add setTabTitle method ([#680](https://github.com/anncwb/vue-vben-admin/issues/680)) ([5ddccf6](https://github.com/anncwb/vue-vben-admin/commit/5ddccf6ba28453b9a35355d53d0db65f1a8876bc)) +- **tinymce:** support dark theme and I18n ([83c9cd7](https://github.com/anncwb/vue-vben-admin/commit/83c9cd77421e9c0888a41e2d8dcbca816da67488)) +- **Tinymce:** add dynamics to the read-only state of the rich text editor ([#725](https://github.com/anncwb/vue-vben-admin/issues/725)) ([efce482](https://github.com/anncwb/vue-vben-admin/commit/efce482b3215ddf9ed588f63a218d5f76939e947)) +- **tree:** add defaultExpandLevel prop ([6edca1c](https://github.com/anncwb/vue-vben-admin/commit/6edca1c19c3b0772f9ab82a7b09251a74fff2173)), closes [#672](https://github.com/anncwb/vue-vben-admin/issues/672) + +### Performance Improvements + +- **component:** optimize tree and upload components ([3f6920f](https://github.com/anncwb/vue-vben-admin/commit/3f6920f7a9775fc06a34dead90b1724b23b7759c)) +- **cropper-avatar:** code optimization ([6dbbdba](https://github.com/anncwb/vue-vben-admin/commit/6dbbdbac76c2c3795e12dd346f6310d1b70f6a7d)) +- **i18n:** improve circular dependencies ([d677729](https://github.com/anncwb/vue-vben-admin/commit/d677729acbe2c024ab13cf490b205528507c4823)) +- **i18n:** improve warning prompt ([6ef62ba](https://github.com/anncwb/vue-vben-admin/commit/6ef62ba6ea7f5613a1fec982b30fe6b0f478bf59)) +- **locale:** reduce the number of multilingual files ([0acc4ab](https://github.com/anncwb/vue-vben-admin/commit/0acc4ab2dd70a239bd13929edede02b283feb7c2)) +- **pagewrapper:** 优化 PageWrapper 的高度自适应表现使用 getViewportOffset 替代 useContentViewHeight ([#792](https://github.com/anncwb/vue-vben-admin/issues/792)) ([4d8e398](https://github.com/anncwb/vue-vben-admin/commit/4d8e39857ea59fff99e69832b4a8cabf3a424c24)) +- **PageWrapper:** fix the height calculation problem when footer and global footer are opened at the same time ([#760](https://github.com/anncwb/vue-vben-admin/issues/760)) ([ab2c7ef](https://github.com/anncwb/vue-vben-admin/commit/ab2c7efe6994dacfe0ff407783f2c3b246427bfc)) +- **utils:** mitt default export is changed from Class to Function ([d3d620f](https://github.com/anncwb/vue-vben-admin/commit/d3d620f4fc75dd69270e4d090a71d426701272ef)) +- add createImgPreview func ([#713](https://github.com/anncwb/vue-vben-admin/issues/713)) ([b7c7c46](https://github.com/anncwb/vue-vben-admin/commit/b7c7c46853d332641d116d818e657447884784f3)) +- optimize components and add comments ([55e9d9f](https://github.com/anncwb/vue-vben-admin/commit/55e9d9fc2953643cec95c74b6ed34b0e68641fb6)) + +### Reverts + +- **axios:** remove baseUrl config ([61d4efd](https://github.com/anncwb/vue-vben-admin/commit/61d4efd55a8b4f09990b5f1888e23ead43958164)) + +# [2.5.0](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.5.0) (2021-06-20) + +### Bug Fixes + +- **api:** select api type error ([b387681](https://github.com/anncwb/vue-vben-admin/commit/b387681c00ac018f5bc6a9251009ddffe37acae6)) +- **api-select:** loss option data on event callback ([c5f2577](https://github.com/anncwb/vue-vben-admin/commit/c5f2577f515e7ae96b27b509e5dd4b3317fcb7b4)), closes [#733](https://github.com/anncwb/vue-vben-admin/issues/733) +- **ApiSelect demo:** add demo about ApiSelect's use ([#757](https://github.com/anncwb/vue-vben-admin/issues/757)) ([a03d3cc](https://github.com/anncwb/vue-vben-admin/commit/a03d3cc60c770eba644c1f3837850a2c1c015029)) +- **avatar:** mock data and Account center style ([2066f66](https://github.com/anncwb/vue-vben-admin/commit/2066f669715491f3e91ac6d0e905cd2b3e80b58d)) +- **axios:** make sure that the parameter is an object before processing, fix [#660](https://github.com/anncwb/vue-vben-admin/issues/660) ([834fa7e](https://github.com/anncwb/vue-vben-admin/commit/834fa7eb9c8aff252e083d38fdab4f6f53b4d43a)) +- **axios:** transformRequestHook logic error ([b69dcd7](https://github.com/anncwb/vue-vben-admin/commit/b69dcd79d742fd171302ce0f48c7750d60da217f)) +- **code-editor:** fix CodeEditor style problem, fix [#655](https://github.com/anncwb/vue-vben-admin/issues/655) ([5662804](https://github.com/anncwb/vue-vben-admin/commit/566280422de0537c4e31496eaaa95a9d51fe9458)) +- **codeeditor:** empty value set failed.fixed:[#659](https://github.com/anncwb/vue-vben-admin/issues/659) ([ba2bebb](https://github.com/anncwb/vue-vben-admin/commit/ba2bebb4069085817a90d065ed5877fdb50a8039)) +- **codeMirror:** fix the JsonEditor embedded in the bullet frame causing the style to be disordered ([#668](https://github.com/anncwb/vue-vben-admin/issues/668)) ([e1123a2](https://github.com/anncwb/vue-vben-admin/commit/e1123a2ccb5d5450a5072c19e5508a5dc0f14423)) +- **demo:** `breadcrumb` route invalid redirect ([84d9300](https://github.com/anncwb/vue-vben-admin/commit/84d9300e52fa73da575591aa4b71858a7e459c8c)) +- **demo:** account list page validate and save ([21f7a85](https://github.com/anncwb/vue-vben-admin/commit/21f7a854fe2455315287d04e895661ff739bce17)) +- **demo:** fix basic form page style ([8b6e07b](https://github.com/anncwb/vue-vben-admin/commit/8b6e07b768f110f13b4f2efa6c46e03266667a8c)) +- **demo:** make sure the map https resource is correct ([7b9cd09](https://github.com/anncwb/vue-vben-admin/commit/7b9cd09ad8a50c45b2e661e07953d786d82f367d)) +- **flow-chart:** fix drag and drop menu loss ([fa828fd](https://github.com/anncwb/vue-vben-admin/commit/fa828fd972efeea87f364be76a1139ae53ec20d8)) +- **form:** fix form update problem ([bcad95d](https://github.com/anncwb/vue-vben-admin/commit/bcad95d32a08a73f84ecbabab409cd64159f4077)), closes [#720](https://github.com/anncwb/vue-vben-admin/issues/720) +- **form:** loss args on component change event ([513823b](https://github.com/anncwb/vue-vben-admin/commit/513823bfbd3e8acc68098e0708c34bff2dd8dba6)) +- **form:** radioButtonGroup value support boolean ([9e2aa20](https://github.com/anncwb/vue-vben-admin/commit/9e2aa20daa08d2902cb5d56c1560306947e44939)) +- **form:** radioButtonGroup value support number ([bbddf30](https://github.com/anncwb/vue-vben-admin/commit/bbddf30e96feb1ab048323d93d3b8c1b18857acd)) +- **form:** schemas update problem ([808328d](https://github.com/anncwb/vue-vben-admin/commit/808328dc7e56b1cc07b678d501d9589290173443)), closes [#688](https://github.com/anncwb/vue-vben-admin/issues/688) +- **keep-alive:** tablist cache updating effect ([d62d0ca](https://github.com/anncwb/vue-vben-admin/commit/d62d0ca08cff442c23eb9265851b066a2f24afa8)), closes [#695](https://github.com/anncwb/vue-vben-admin/issues/695) +- **layout:** fix class loss ([d018363](https://github.com/anncwb/vue-vben-admin/commit/d018363ddcd68189a18829a2b2560f3b98da58a6)) +- **layout:** fix style compatibility issues ([905e5b7](https://github.com/anncwb/vue-vben-admin/commit/905e5b714b582548f32feca723012124343686a6)) +- **layout:** props warn ([#756](https://github.com/anncwb/vue-vben-admin/issues/756)) ([bbce002](https://github.com/anncwb/vue-vben-admin/commit/bbce002be170c52db984647c931db88d7724cb52)) +- **lock:** fix lock modal height ([40e3cb0](https://github.com/anncwb/vue-vben-admin/commit/40e3cb043c90a8343fa44a32acad2cb77de732da)), closes [#701](https://github.com/anncwb/vue-vben-admin/issues/701) +- **log:** fix Wrong version number ([#653](https://github.com/anncwb/vue-vben-admin/issues/653)) ([4f0d45f](https://github.com/anncwb/vue-vben-admin/commit/4f0d45f1df48755eadc0b09fa19762ee68f9abd1)) +- **login:** login page modal style fixed: [#662](https://github.com/anncwb/vue-vben-admin/issues/662) ([#666](https://github.com/anncwb/vue-vben-admin/issues/666)) ([b218f10](https://github.com/anncwb/vue-vben-admin/commit/b218f10e25a9364c399a5fe42eedb549f57c01ea)) +- **menu:** fix the jitter problem of menu folding animation,fix [#732](https://github.com/anncwb/vue-vben-admin/issues/732) ([4c89ea7](https://github.com/anncwb/vue-vben-admin/commit/4c89ea7474f4315870df1790f99f3e431f343b90)) +- **mock:** make sure ignore matches the file correctly, fix [#745](https://github.com/anncwb/vue-vben-admin/issues/745) ([a222ec8](https://github.com/anncwb/vue-vben-admin/commit/a222ec8553f9b4477a43a8f7d113b5646fbfc373)) +- **mock:** menu list api loss `type` field ([4185412](https://github.com/anncwb/vue-vben-admin/commit/41854121f3713dbde236afd3a416e9f27bd0c673)) +- **mock:** type error ([7c1ffa3](https://github.com/anncwb/vue-vben-admin/commit/7c1ffa3d23de508a8d1590985806cb7a484b24e5)) +- **modal:** add v-model support for visible ([de12bab](https://github.com/anncwb/vue-vben-admin/commit/de12babd314ac831d3cb645f42dbf8a476075623)) +- **modal:** ensure that the full screen height is calculated correctly ([1c1755c](https://github.com/anncwb/vue-vben-admin/commit/1c1755cf5b4ada7263c05ddf4105abb52a2abb2f)) +- **modal:** ensure that the shutdown event is not triggered multiple times ([655b743](https://github.com/anncwb/vue-vben-admin/commit/655b74323653147943cbde2352208cb765c82b8a)) +- **store:** fix type error after pinia version upgrade ([e8d6f88](https://github.com/anncwb/vue-vben-admin/commit/e8d6f8851efd7076946486864936f1797280d3ba)) +- **use-message:** `content` not support vNode ([154ebc3](https://github.com/anncwb/vue-vben-admin/commit/154ebc3d96f73bb3ceab99ea0229a3619d585aba)) +- build error ([5212ea7](https://github.com/anncwb/vue-vben-admin/commit/5212ea79b43c832a5136354b549de8f89b6e2156)) +- fix darkModeSwitch switch failure ([34a8054](https://github.com/anncwb/vue-vben-admin/commit/34a80542de670f0385dffaf5bf64bb9c3f6b90da)) +- fix if getDropdownList.length==0 show Dropdown component ([21c771b](https://github.com/anncwb/vue-vben-admin/commit/21c771b59cb45defbff37de21c5c1950370b8f92)) +- fix Login Page LocalePicker showLocale condition ([d683b0f](https://github.com/anncwb/vue-vben-admin/commit/d683b0f1e85b85b07090feba4ac7f741bd3bd482)) +- **modal:** redoModalHeight not work as expected ([5d554f1](https://github.com/anncwb/vue-vben-admin/commit/5d554f184f7b61774d1a1b2e61451677b38505de)) +- **page:** `basic form` action btns should be in line ([6c4f947](https://github.com/anncwb/vue-vben-admin/commit/6c4f947386c181f45253c94e4ef735d29a253053)) +- **radio-button:** fix RadioButton `disabled` support ([ee384b1](https://github.com/anncwb/vue-vben-admin/commit/ee384b1fa7e387b3680e9d54cbe4a1e2f15ec750)), closes [#710](https://github.com/anncwb/vue-vben-admin/issues/710) +- **route:** dynamically introduce components error ([c6b766d](https://github.com/anncwb/vue-vben-admin/commit/c6b766d8ea902294ab1f7e4a06781f2bcfdd1f0b)) +- **router:** loss `directory` route ([df8cd86](https://github.com/anncwb/vue-vben-admin/commit/df8cd860514f32f44847dcf724f0737ed4d8b9e0)), closes [#722](https://github.com/anncwb/vue-vben-admin/issues/722) +- **table:** wrong indeterminate state ([495b1da](https://github.com/anncwb/vue-vben-admin/commit/495b1da385e9b6428d2b994669d2065722445923)) +- **table:** make sure the table width is correct, fix [#593](https://github.com/anncwb/vue-vben-admin/issues/593) ([d73d43e](https://github.com/anncwb/vue-vben-admin/commit/d73d43ed91f30957cfd202c51552ca40a19cef08)) +- **table:** settings indeterminate state effect ([4fd2051](https://github.com/anncwb/vue-vben-admin/commit/4fd2051bc0403bfc5345ed6a5fc283a372ef7a92)) +- **table:** support change event ([9f4d171](https://github.com/anncwb/vue-vben-admin/commit/9f4d1719caa76de94e6362c16e4df3ac28df253c)), closes [#677](https://github.com/anncwb/vue-vben-admin/issues/677) +- **table:** try to get close to the form stuck ([d81481c](https://github.com/anncwb/vue-vben-admin/commit/d81481c52186145dac130aaa1594f0ba8db4d392)) +- **table:** useTable support onChange ([9f5085c](https://github.com/anncwb/vue-vben-admin/commit/9f5085c9f9f46b09391156b17091c1771bc13026)) +- **table-action:** fix the split line style is missing,fix [#674](https://github.com/anncwb/vue-vben-admin/issues/674) ([b1cb863](https://github.com/anncwb/vue-vben-admin/commit/b1cb86350253dc5be095466966d9469775f4395d)) +- **Tinymce:** Read only status upload button can also be used ([#718](https://github.com/anncwb/vue-vben-admin/issues/718)) ([966571b](https://github.com/anncwb/vue-vben-admin/commit/966571bdcb11c2729ab9ce212bd3e195f7bf3a59)) +- **tree:** support defaultExpandAll prop ([3ed2339](https://github.com/anncwb/vue-vben-admin/commit/3ed2339a6d75abbd6ccf723b6eaa762f9921409e)) +- **upload:** ensure preview items valid ([4376928](https://github.com/anncwb/vue-vben-admin/commit/437692869a232ee65c300c65ee473557ae0913c7)) +- **useViewHeight:** Fix the problem that useContentViewHeight does not calculate the footer ([#747](https://github.com/anncwb/vue-vben-admin/issues/747)) ([33cd8fe](https://github.com/anncwb/vue-vben-admin/commit/33cd8fe6533830176ab63ddfc4d74f75a384366c)) +- ensure that roleList is not empty ([aebad61](https://github.com/anncwb/vue-vben-admin/commit/aebad61b3d3e11aaf720b37e762e53e2e6999d3c)) +- fix node12 version data mock error ([644dbe3](https://github.com/anncwb/vue-vben-admin/commit/644dbe315bb03ea1641a682359873237208a5303)) +- Fix the problem that the `lang` attribute of `HTML` will not be set when it is first loaded ([#682](https://github.com/anncwb/vue-vben-admin/issues/682)) ([eca8907](https://github.com/anncwb/vue-vben-admin/commit/eca8907a11c28d816c3da5a0667f45a38a499012)) +- login failed ([035f55a](https://github.com/anncwb/vue-vben-admin/commit/035f55af9778819d72adc1700d9de56a6569b58f)) +- session timeout login logic error ([#678](https://github.com/anncwb/vue-vben-admin/issues/678)) ([132c7fb](https://github.com/anncwb/vue-vben-admin/commit/132c7fb944df255c4d76a25d6d924439f91f9c54)), closes [#673](https://github.com/anncwb/vue-vben-admin/issues/673) +- theme switching fails ([7e2ca79](https://github.com/anncwb/vue-vben-admin/commit/7e2ca79ece2f5209cb7ce4b0f5ee15012f9f51de)) + +### Features + +- optimize error message for api failure ([ea6834a](https://github.com/anncwb/vue-vben-admin/commit/ea6834aeec3ef56d411b2c10a474f75d3d7bfdfc)) +- **api-select:** auto refetch after params changed ([50207ad](https://github.com/anncwb/vue-vben-admin/commit/50207ad702ef3faca1e27c873c89132ab92fae8e)) +- **app-search:** auto focus on show ([1ae6362](https://github.com/anncwb/vue-vben-admin/commit/1ae636296df2cf99e8a777f053c539c50e6ad49a)) +- **axios:** added authenticationScheme configuration,fix [#774](https://github.com/anncwb/vue-vben-admin/issues/774) ([b6d5b07](https://github.com/anncwb/vue-vben-admin/commit/b6d5b0796de4d0b66c0f33c335ec991d44f64ef2)) +- **demo:** `switch` use in table ([46899aa](https://github.com/anncwb/vue-vben-admin/commit/46899aa3cd6b1616c42ac263a28af75be839f6a0)) +- **demo:** added guide page example ([d196340](https://github.com/anncwb/vue-vben-admin/commit/d196340d270d2becbf2cc81b7d4f09273381bd09)) +- **echarts:** add getInstance for useECharts ([fb6c76d](https://github.com/anncwb/vue-vben-admin/commit/fb6c76db535bd0c6305d03c0cff876a1f079100b)) +- **modal:** add closeModal for useModal ([6d5f9aa](https://github.com/anncwb/vue-vben-admin/commit/6d5f9aa699c5da8af6bf5841baddc4a8bd603917)) +- **modal:** add redoModalHeight for useModalInner ([f732b56](https://github.com/anncwb/vue-vben-admin/commit/f732b569042f7fe77c85cb295538ddd85561f7e9)) +- **preview:** added createImgPreview picture preview function ([305630e](https://github.com/anncwb/vue-vben-admin/commit/305630e3fd886b3f690f890a934a8a6ba224fba1)) +- **project-setting:** added sessionTimeoutProcessing project configuration item,fix [#772](https://github.com/anncwb/vue-vben-admin/issues/772) ([0d07084](https://github.com/anncwb/vue-vben-admin/commit/0d0708409c4adbe7a0c5e33abf5307031147eaeb)) +- **table:** add editable DatePicker & TimePicker ([#654](https://github.com/anncwb/vue-vben-admin/issues/654)) ([93006c7](https://github.com/anncwb/vue-vben-admin/commit/93006c7dc7b5243b26637f444c8057c95935e622)) +- **table:** add updateTableDataRecord method ([8e4f486](https://github.com/anncwb/vue-vben-admin/commit/8e4f486fcf835f0b6f2a95676dba268ffdd0566e)) +- **table:** editable component text align ([8eaf575](https://github.com/anncwb/vue-vben-admin/commit/8eaf57562610a833c8083ae9957f458319d1cc93)) +- **table:** support columns-change event ([125a7d1](https://github.com/anncwb/vue-vben-admin/commit/125a7d14831642c9cbb2e4b3e75953c3b2e2cdef)) +- **table:** support custom update on row editing ([fe2bcfc](https://github.com/anncwb/vue-vben-admin/commit/fe2bcfc6f74159c355f3be153a316869fdb8b644)), closes [#646](https://github.com/anncwb/vue-vben-admin/issues/646) +- **table:** updateTableDataRecord support functional rowKey ([448a4c2](https://github.com/anncwb/vue-vben-admin/commit/448a4c2809672480f8f635d7cc4661554112598a)) +- **table-action:** add stopButtonPropagation prop ([808012b](https://github.com/anncwb/vue-vben-admin/commit/808012b544b8c6f3cf467f42653c2783dbe8be6b)), closes [#699](https://github.com/anncwb/vue-vben-admin/issues/699) +- **table-img:** support simple show mode and more props ([19d8e01](https://github.com/anncwb/vue-vben-admin/commit/19d8e01e11644c66222f137abd05940cbdec0bb6)) +- **test:** add jest test suite ([f6fe1dd](https://github.com/anncwb/vue-vben-admin/commit/f6fe1dd62df231ccbd063db0d32359b48aa5c76b)) +- **use-drawer:** add closeDrawer function ([639520a](https://github.com/anncwb/vue-vben-admin/commit/639520ad5ddf829875ab517067abf2b45ebc04c2)) +- add CropperAvatar component ([8e410fc](https://github.com/anncwb/vue-vben-admin/commit/8e410fc6401847d8e5545468b5ce6fd7ce9fc5cc)) +- **tabs:** add setTabTitle method ([#680](https://github.com/anncwb/vue-vben-admin/issues/680)) ([5ddccf6](https://github.com/anncwb/vue-vben-admin/commit/5ddccf6ba28453b9a35355d53d0db65f1a8876bc)) +- **tinymce:** support dark theme and I18n ([83c9cd7](https://github.com/anncwb/vue-vben-admin/commit/83c9cd77421e9c0888a41e2d8dcbca816da67488)) +- **Tinymce:** add dynamics to the read-only state of the rich text editor ([#725](https://github.com/anncwb/vue-vben-admin/issues/725)) ([efce482](https://github.com/anncwb/vue-vben-admin/commit/efce482b3215ddf9ed588f63a218d5f76939e947)) +- **tree:** add defaultExpandLevel prop ([6edca1c](https://github.com/anncwb/vue-vben-admin/commit/6edca1c19c3b0772f9ab82a7b09251a74fff2173)), closes [#672](https://github.com/anncwb/vue-vben-admin/issues/672) + +### Performance Improvements + +- **component:** optimize tree and upload components ([3f6920f](https://github.com/anncwb/vue-vben-admin/commit/3f6920f7a9775fc06a34dead90b1724b23b7759c)) +- **cropper-avatar:** code optimization ([6dbbdba](https://github.com/anncwb/vue-vben-admin/commit/6dbbdbac76c2c3795e12dd346f6310d1b70f6a7d)) +- **i18n:** improve circular dependencies ([d677729](https://github.com/anncwb/vue-vben-admin/commit/d677729acbe2c024ab13cf490b205528507c4823)) +- **i18n:** improve warning prompt ([6ef62ba](https://github.com/anncwb/vue-vben-admin/commit/6ef62ba6ea7f5613a1fec982b30fe6b0f478bf59)) +- **locale:** reduce the number of multilingual files ([0acc4ab](https://github.com/anncwb/vue-vben-admin/commit/0acc4ab2dd70a239bd13929edede02b283feb7c2)) +- **PageWrapper:** fix the height calculation problem when footer and global footer are opened at the same time ([#760](https://github.com/anncwb/vue-vben-admin/issues/760)) ([ab2c7ef](https://github.com/anncwb/vue-vben-admin/commit/ab2c7efe6994dacfe0ff407783f2c3b246427bfc)) +- **utils:** mitt default export is changed from Class to Function ([d3d620f](https://github.com/anncwb/vue-vben-admin/commit/d3d620f4fc75dd69270e4d090a71d426701272ef)) +- add createImgPreview func ([#713](https://github.com/anncwb/vue-vben-admin/issues/713)) ([b7c7c46](https://github.com/anncwb/vue-vben-admin/commit/b7c7c46853d332641d116d818e657447884784f3)) +- optimize components and add comments ([55e9d9f](https://github.com/anncwb/vue-vben-admin/commit/55e9d9fc2953643cec95c74b6ed34b0e68641fb6)) + +## [2.4.2](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.4.2) (2021-06-09) + +### Bug Fixes + +- fix darkModeSwitch switch failure ([34a8054](https://github.com/anncwb/vue-vben-admin/commit/34a80542de670f0385dffaf5bf64bb9c3f6b90da)) +- **api-select:** loss option data on event callback ([c5f2577](https://github.com/anncwb/vue-vben-admin/commit/c5f2577f515e7ae96b27b509e5dd4b3317fcb7b4)), closes [#733](https://github.com/anncwb/vue-vben-admin/issues/733) +- **avatar:** mock data and Account center style ([2066f66](https://github.com/anncwb/vue-vben-admin/commit/2066f669715491f3e91ac6d0e905cd2b3e80b58d)) +- **axios:** transformRequestHook logic error ([b69dcd7](https://github.com/anncwb/vue-vben-admin/commit/b69dcd79d742fd171302ce0f48c7750d60da217f)) +- **codeMirror:** fix the JsonEditor embedded in the bullet frame causing the style to be disordered ([#668](https://github.com/anncwb/vue-vben-admin/issues/668)) ([e1123a2](https://github.com/anncwb/vue-vben-admin/commit/e1123a2ccb5d5450a5072c19e5508a5dc0f14423)) +- **demo:** `breadcrumb` route invalid redirect ([84d9300](https://github.com/anncwb/vue-vben-admin/commit/84d9300e52fa73da575591aa4b71858a7e459c8c)) +- **demo:** account list page validate and save ([21f7a85](https://github.com/anncwb/vue-vben-admin/commit/21f7a854fe2455315287d04e895661ff739bce17)) +- **demo:** fix basic form page style ([8b6e07b](https://github.com/anncwb/vue-vben-admin/commit/8b6e07b768f110f13b4f2efa6c46e03266667a8c)) +- **demo:** make sure the map https resource is correct ([7b9cd09](https://github.com/anncwb/vue-vben-admin/commit/7b9cd09ad8a50c45b2e661e07953d786d82f367d)) +- **form:** fix form update problem ([bcad95d](https://github.com/anncwb/vue-vben-admin/commit/bcad95d32a08a73f84ecbabab409cd64159f4077)), closes [#720](https://github.com/anncwb/vue-vben-admin/issues/720) +- **form:** radioButtonGroup value support boolean ([9e2aa20](https://github.com/anncwb/vue-vben-admin/commit/9e2aa20daa08d2902cb5d56c1560306947e44939)) +- **form:** radioButtonGroup value support number ([bbddf30](https://github.com/anncwb/vue-vben-admin/commit/bbddf30e96feb1ab048323d93d3b8c1b18857acd)) +- **form:** schemas update problem ([808328d](https://github.com/anncwb/vue-vben-admin/commit/808328dc7e56b1cc07b678d501d9589290173443)), closes [#688](https://github.com/anncwb/vue-vben-admin/issues/688) +- **keep-alive:** tablist cache updating effect ([d62d0ca](https://github.com/anncwb/vue-vben-admin/commit/d62d0ca08cff442c23eb9265851b066a2f24afa8)), closes [#695](https://github.com/anncwb/vue-vben-admin/issues/695) +- **lock:** fix lock modal height ([40e3cb0](https://github.com/anncwb/vue-vben-admin/commit/40e3cb043c90a8343fa44a32acad2cb77de732da)), closes [#701](https://github.com/anncwb/vue-vben-admin/issues/701) +- **login:** login page modal style fixed: [#662](https://github.com/anncwb/vue-vben-admin/issues/662) ([#666](https://github.com/anncwb/vue-vben-admin/issues/666)) ([b218f10](https://github.com/anncwb/vue-vben-admin/commit/b218f10e25a9364c399a5fe42eedb549f57c01ea)) +- **mock:** menu list api loss `type` field ([4185412](https://github.com/anncwb/vue-vben-admin/commit/41854121f3713dbde236afd3a416e9f27bd0c673)) +- **mock:** type error ([7c1ffa3](https://github.com/anncwb/vue-vben-admin/commit/7c1ffa3d23de508a8d1590985806cb7a484b24e5)) +- **router:** loss `directory` route ([df8cd86](https://github.com/anncwb/vue-vben-admin/commit/df8cd860514f32f44847dcf724f0737ed4d8b9e0)), closes [#722](https://github.com/anncwb/vue-vben-admin/issues/722) +- build error ([5212ea7](https://github.com/anncwb/vue-vben-admin/commit/5212ea79b43c832a5136354b549de8f89b6e2156)) +- **axios:** make sure that the parameter is an object before processing, fix [#660](https://github.com/anncwb/vue-vben-admin/issues/660) ([834fa7e](https://github.com/anncwb/vue-vben-admin/commit/834fa7eb9c8aff252e083d38fdab4f6f53b4d43a)) +- **code-editor:** fix CodeEditor style problem, fix [#655](https://github.com/anncwb/vue-vben-admin/issues/655) ([5662804](https://github.com/anncwb/vue-vben-admin/commit/566280422de0537c4e31496eaaa95a9d51fe9458)) +- **codeeditor:** empty value set failed.fixed:[#659](https://github.com/anncwb/vue-vben-admin/issues/659) ([ba2bebb](https://github.com/anncwb/vue-vben-admin/commit/ba2bebb4069085817a90d065ed5877fdb50a8039)) +- **layout:** fix class loss ([d018363](https://github.com/anncwb/vue-vben-admin/commit/d018363ddcd68189a18829a2b2560f3b98da58a6)) +- **layout:** fix style compatibility issues ([905e5b7](https://github.com/anncwb/vue-vben-admin/commit/905e5b714b582548f32feca723012124343686a6)) +- **log:** fix Wrong version number ([#653](https://github.com/anncwb/vue-vben-admin/issues/653)) ([4f0d45f](https://github.com/anncwb/vue-vben-admin/commit/4f0d45f1df48755eadc0b09fa19762ee68f9abd1)) +- **modal:** redoModalHeight not work as expected ([5d554f1](https://github.com/anncwb/vue-vben-admin/commit/5d554f184f7b61774d1a1b2e61451677b38505de)) +- **page:** `basic form` action btns should be in line ([6c4f947](https://github.com/anncwb/vue-vben-admin/commit/6c4f947386c181f45253c94e4ef735d29a253053)) +- **radio-button:** fix RadioButton `disabled` support ([ee384b1](https://github.com/anncwb/vue-vben-admin/commit/ee384b1fa7e387b3680e9d54cbe4a1e2f15ec750)), closes [#710](https://github.com/anncwb/vue-vben-admin/issues/710) +- **table:** wrong indeterminate state ([495b1da](https://github.com/anncwb/vue-vben-admin/commit/495b1da385e9b6428d2b994669d2065722445923)) +- **table:** make sure the table width is correct, fix [#593](https://github.com/anncwb/vue-vben-admin/issues/593) ([d73d43e](https://github.com/anncwb/vue-vben-admin/commit/d73d43ed91f30957cfd202c51552ca40a19cef08)) +- **table:** settings indeterminate state effect ([4fd2051](https://github.com/anncwb/vue-vben-admin/commit/4fd2051bc0403bfc5345ed6a5fc283a372ef7a92)) +- **table:** support change event ([9f4d171](https://github.com/anncwb/vue-vben-admin/commit/9f4d1719caa76de94e6362c16e4df3ac28df253c)), closes [#677](https://github.com/anncwb/vue-vben-admin/issues/677) +- **table:** try to get close to the form stuck ([d81481c](https://github.com/anncwb/vue-vben-admin/commit/d81481c52186145dac130aaa1594f0ba8db4d392)) +- **Tinymce:** Read only status upload button can also be used ([#718](https://github.com/anncwb/vue-vben-admin/issues/718)) ([966571b](https://github.com/anncwb/vue-vben-admin/commit/966571bdcb11c2729ab9ce212bd3e195f7bf3a59)) +- **upload:** ensure preview items valid ([4376928](https://github.com/anncwb/vue-vben-admin/commit/437692869a232ee65c300c65ee473557ae0913c7)) +- ensure that roleList is not empty ([aebad61](https://github.com/anncwb/vue-vben-admin/commit/aebad61b3d3e11aaf720b37e762e53e2e6999d3c)) +- fix node12 version data mock error ([644dbe3](https://github.com/anncwb/vue-vben-admin/commit/644dbe315bb03ea1641a682359873237208a5303)) +- Fix the problem that the `lang` attribute of `HTML` will not be set when it is first loaded ([#682](https://github.com/anncwb/vue-vben-admin/issues/682)) ([eca8907](https://github.com/anncwb/vue-vben-admin/commit/eca8907a11c28d816c3da5a0667f45a38a499012)) +- **table:** useTable support onChange ([9f5085c](https://github.com/anncwb/vue-vben-admin/commit/9f5085c9f9f46b09391156b17091c1771bc13026)) +- **table-action:** fix the split line style is missing,fix [#674](https://github.com/anncwb/vue-vben-admin/issues/674) ([b1cb863](https://github.com/anncwb/vue-vben-admin/commit/b1cb86350253dc5be095466966d9469775f4395d)) +- login failed ([035f55a](https://github.com/anncwb/vue-vben-admin/commit/035f55af9778819d72adc1700d9de56a6569b58f)) +- session timeout login logic error ([#678](https://github.com/anncwb/vue-vben-admin/issues/678)) ([132c7fb](https://github.com/anncwb/vue-vben-admin/commit/132c7fb944df255c4d76a25d6d924439f91f9c54)), closes [#673](https://github.com/anncwb/vue-vben-admin/issues/673) +- **tree:** support defaultExpandAll prop ([3ed2339](https://github.com/anncwb/vue-vben-admin/commit/3ed2339a6d75abbd6ccf723b6eaa762f9921409e)) +- theme switching fails ([7e2ca79](https://github.com/anncwb/vue-vben-admin/commit/7e2ca79ece2f5209cb7ce4b0f5ee15012f9f51de)) + +### Features + +- **api-select:** auto refetch after params changed ([50207ad](https://github.com/anncwb/vue-vben-admin/commit/50207ad702ef3faca1e27c873c89132ab92fae8e)) +- **app-search:** auto focus on show ([1ae6362](https://github.com/anncwb/vue-vben-admin/commit/1ae636296df2cf99e8a777f053c539c50e6ad49a)) +- **demo:** `switch` use in table ([46899aa](https://github.com/anncwb/vue-vben-admin/commit/46899aa3cd6b1616c42ac263a28af75be839f6a0)) +- **echarts:** add getInstance for useECharts ([fb6c76d](https://github.com/anncwb/vue-vben-admin/commit/fb6c76db535bd0c6305d03c0cff876a1f079100b)) +- **modal:** add closeModal for useModal ([6d5f9aa](https://github.com/anncwb/vue-vben-admin/commit/6d5f9aa699c5da8af6bf5841baddc4a8bd603917)) +- **modal:** add redoModalHeight for useModalInner ([f732b56](https://github.com/anncwb/vue-vben-admin/commit/f732b569042f7fe77c85cb295538ddd85561f7e9)) +- **table:** add editable DatePicker & TimePicker ([#654](https://github.com/anncwb/vue-vben-admin/issues/654)) ([93006c7](https://github.com/anncwb/vue-vben-admin/commit/93006c7dc7b5243b26637f444c8057c95935e622)) +- **table:** add updateTableDataRecord method ([8e4f486](https://github.com/anncwb/vue-vben-admin/commit/8e4f486fcf835f0b6f2a95676dba268ffdd0566e)) +- **table:** editable component text align ([8eaf575](https://github.com/anncwb/vue-vben-admin/commit/8eaf57562610a833c8083ae9957f458319d1cc93)) +- **table:** support columns-change event ([125a7d1](https://github.com/anncwb/vue-vben-admin/commit/125a7d14831642c9cbb2e4b3e75953c3b2e2cdef)) +- **table:** support custom update on row editing ([fe2bcfc](https://github.com/anncwb/vue-vben-admin/commit/fe2bcfc6f74159c355f3be153a316869fdb8b644)), closes [#646](https://github.com/anncwb/vue-vben-admin/issues/646) +- **table:** updateTableDataRecord support functional rowKey ([448a4c2](https://github.com/anncwb/vue-vben-admin/commit/448a4c2809672480f8f635d7cc4661554112598a)) +- **table-action:** add stopButtonPropagation prop ([808012b](https://github.com/anncwb/vue-vben-admin/commit/808012b544b8c6f3cf467f42653c2783dbe8be6b)), closes [#699](https://github.com/anncwb/vue-vben-admin/issues/699) +- **table-img:** support simple show mode and more props ([19d8e01](https://github.com/anncwb/vue-vben-admin/commit/19d8e01e11644c66222f137abd05940cbdec0bb6)) +- **tabs:** add setTabTitle method ([#680](https://github.com/anncwb/vue-vben-admin/issues/680)) ([5ddccf6](https://github.com/anncwb/vue-vben-admin/commit/5ddccf6ba28453b9a35355d53d0db65f1a8876bc)) +- **tinymce:** support dark theme and I18n ([83c9cd7](https://github.com/anncwb/vue-vben-admin/commit/83c9cd77421e9c0888a41e2d8dcbca816da67488)) +- **Tinymce:** add dynamics to the read-only state of the rich text editor ([#725](https://github.com/anncwb/vue-vben-admin/issues/725)) ([efce482](https://github.com/anncwb/vue-vben-admin/commit/efce482b3215ddf9ed588f63a218d5f76939e947)) +- **tree:** add defaultExpandLevel prop ([6edca1c](https://github.com/anncwb/vue-vben-admin/commit/6edca1c19c3b0772f9ab82a7b09251a74fff2173)), closes [#672](https://github.com/anncwb/vue-vben-admin/issues/672) + +### Performance Improvements + +- optimize components and add comments ([55e9d9f](https://github.com/anncwb/vue-vben-admin/commit/55e9d9fc2953643cec95c74b6ed34b0e68641fb6)) +- **i18n:** improve circular dependencies ([d677729](https://github.com/anncwb/vue-vben-admin/commit/d677729acbe2c024ab13cf490b205528507c4823)) +- **i18n:** improve warning prompt ([6ef62ba](https://github.com/anncwb/vue-vben-admin/commit/6ef62ba6ea7f5613a1fec982b30fe6b0f478bf59)) + +## [2.4.1](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.4.1) (2021-06-01) + +### Bug Fixes + +- **table:** make sure the table width is correct, fix [#593](https://github.com/anncwb/vue-vben-admin/issues/593) ([d73d43e](https://github.com/anncwb/vue-vben-admin/commit/d73d43ed91f30957cfd202c51552ca40a19cef08)) +- Fix the problem that the `lang` attribute of `HTML` will not be set when it is first loaded ([#682](https://github.com/anncwb/vue-vben-admin/issues/682)) ([eca8907](https://github.com/anncwb/vue-vben-admin/commit/eca8907a11c28d816c3da5a0667f45a38a499012)) +- **avatar:** mock data and Account center style ([2066f66](https://github.com/anncwb/vue-vben-admin/commit/2066f669715491f3e91ac6d0e905cd2b3e80b58d)) +- **axios:** make sure that the parameter is an object before processing, fix [#660](https://github.com/anncwb/vue-vben-admin/issues/660) ([834fa7e](https://github.com/anncwb/vue-vben-admin/commit/834fa7eb9c8aff252e083d38fdab4f6f53b4d43a)) +- **code-editor:** fix CodeEditor style problem, fix [#655](https://github.com/anncwb/vue-vben-admin/issues/655) ([5662804](https://github.com/anncwb/vue-vben-admin/commit/566280422de0537c4e31496eaaa95a9d51fe9458)) +- **codeMirror:** fix the JsonEditor embedded in the bullet frame causing the style to be disordered ([#668](https://github.com/anncwb/vue-vben-admin/issues/668)) ([e1123a2](https://github.com/anncwb/vue-vben-admin/commit/e1123a2ccb5d5450a5072c19e5508a5dc0f14423)) +- **form:** radioButtonGroup value support number ([bbddf30](https://github.com/anncwb/vue-vben-admin/commit/bbddf30e96feb1ab048323d93d3b8c1b18857acd)) +- ensure that roleList is not empty ([aebad61](https://github.com/anncwb/vue-vben-admin/commit/aebad61b3d3e11aaf720b37e762e53e2e6999d3c)) +- fix node12 version data mock error ([644dbe3](https://github.com/anncwb/vue-vben-admin/commit/644dbe315bb03ea1641a682359873237208a5303)) +- **codeeditor:** empty value set failed.fixed:[#659](https://github.com/anncwb/vue-vben-admin/issues/659) ([ba2bebb](https://github.com/anncwb/vue-vben-admin/commit/ba2bebb4069085817a90d065ed5877fdb50a8039)) +- **layout:** fix style compatibility issues ([905e5b7](https://github.com/anncwb/vue-vben-admin/commit/905e5b714b582548f32feca723012124343686a6)) +- **login:** login page modal style fixed: [#662](https://github.com/anncwb/vue-vben-admin/issues/662) ([#666](https://github.com/anncwb/vue-vben-admin/issues/666)) ([b218f10](https://github.com/anncwb/vue-vben-admin/commit/b218f10e25a9364c399a5fe42eedb549f57c01ea)) +- **table:** support change event ([9f4d171](https://github.com/anncwb/vue-vben-admin/commit/9f4d1719caa76de94e6362c16e4df3ac28df253c)), closes [#677](https://github.com/anncwb/vue-vben-admin/issues/677) +- **table:** useTable support onChange ([9f5085c](https://github.com/anncwb/vue-vben-admin/commit/9f5085c9f9f46b09391156b17091c1771bc13026)) +- **table-action:** fix the split line style is missing,fix [#674](https://github.com/anncwb/vue-vben-admin/issues/674) ([b1cb863](https://github.com/anncwb/vue-vben-admin/commit/b1cb86350253dc5be095466966d9469775f4395d)) +- login failed ([035f55a](https://github.com/anncwb/vue-vben-admin/commit/035f55af9778819d72adc1700d9de56a6569b58f)) +- session timeout login logic error ([#678](https://github.com/anncwb/vue-vben-admin/issues/678)) ([132c7fb](https://github.com/anncwb/vue-vben-admin/commit/132c7fb944df255c4d76a25d6d924439f91f9c54)), closes [#673](https://github.com/anncwb/vue-vben-admin/issues/673) +- **layout:** fix class loss ([d018363](https://github.com/anncwb/vue-vben-admin/commit/d018363ddcd68189a18829a2b2560f3b98da58a6)) +- **log:** fix Wrong version number ([#653](https://github.com/anncwb/vue-vben-admin/issues/653)) ([4f0d45f](https://github.com/anncwb/vue-vben-admin/commit/4f0d45f1df48755eadc0b09fa19762ee68f9abd1)) +- **tree:** support defaultExpandAll prop ([3ed2339](https://github.com/anncwb/vue-vben-admin/commit/3ed2339a6d75abbd6ccf723b6eaa762f9921409e)) +- theme switching fails ([7e2ca79](https://github.com/anncwb/vue-vben-admin/commit/7e2ca79ece2f5209cb7ce4b0f5ee15012f9f51de)) + +### Features + +- **app-search:** auto focus on show ([1ae6362](https://github.com/anncwb/vue-vben-admin/commit/1ae636296df2cf99e8a777f053c539c50e6ad49a)) +- **table:** add editable DatePicker & TimePicker ([#654](https://github.com/anncwb/vue-vben-admin/issues/654)) ([93006c7](https://github.com/anncwb/vue-vben-admin/commit/93006c7dc7b5243b26637f444c8057c95935e622)) +- **table:** editable component text align ([8eaf575](https://github.com/anncwb/vue-vben-admin/commit/8eaf57562610a833c8083ae9957f458319d1cc93)) +- **tabs:** add setTabTitle method ([#680](https://github.com/anncwb/vue-vben-admin/issues/680)) ([5ddccf6](https://github.com/anncwb/vue-vben-admin/commit/5ddccf6ba28453b9a35355d53d0db65f1a8876bc)) +- **tinymce:** support dark theme and I18n ([83c9cd7](https://github.com/anncwb/vue-vben-admin/commit/83c9cd77421e9c0888a41e2d8dcbca816da67488)) +- **tree:** add defaultExpandLevel prop ([6edca1c](https://github.com/anncwb/vue-vben-admin/commit/6edca1c19c3b0772f9ab82a7b09251a74fff2173)), closes [#672](https://github.com/anncwb/vue-vben-admin/issues/672) + +### Performance Improvements + +- **i18n:** improve warning prompt ([6ef62ba](https://github.com/anncwb/vue-vben-admin/commit/6ef62ba6ea7f5613a1fec982b30fe6b0f478bf59)) + +# [2.4.0](https://github.com/anncwb/vue-vben-admin/compare/v2.2.0...v2.4.0) (2021-05-25) + +### Bug Fixes + +- **api-select:** make sure the type is correct, fix [#468](https://github.com/anncwb/vue-vben-admin/issues/468) ([37c5741](https://github.com/anncwb/vue-vben-admin/commit/37c5741601951349f622801a48a7bf9e45d723a4)) +- **avatar:** show current user's avatar ([#640](https://github.com/anncwb/vue-vben-admin/issues/640)) ([7519a00](https://github.com/anncwb/vue-vben-admin/commit/7519a00ada89966f9caf93d315830dd628253d73)) +- **button:** ghost style ([f4af231](https://github.com/anncwb/vue-vben-admin/commit/f4af231172874eeffa9097e2624c4a7d0654f7d7)) +- **cipher:** fix [#587](https://github.com/anncwb/vue-vben-admin/issues/587) ([#588](https://github.com/anncwb/vue-vben-admin/issues/588)) ([d34467d](https://github.com/anncwb/vue-vben-admin/commit/d34467d3f4d0f709a99194e36c0e0b6f242d9b40)) +- **CodeEditor:** add readonly prop ([#572](https://github.com/anncwb/vue-vben-admin/issues/572)) ([9cd293c](https://github.com/anncwb/vue-vben-admin/commit/9cd293c283ede7391ccd36e2208ae68cbad66453)) +- **flow-chart:** dark style not work ([4a03547](https://github.com/anncwb/vue-vben-admin/commit/4a035478ca0e08098a4575a5b22c06580ffeecbe)) +- **form:** ensure that the DateTime component checked properly,fix [#511](https://github.com/anncwb/vue-vben-admin/issues/511) ([cb35341](https://github.com/anncwb/vue-vben-admin/commit/cb35341b8fd44eb649a79c3a2ae799c7bab8c4f6)) +- **form:** expose formModel,fix [#533](https://github.com/anncwb/vue-vben-admin/issues/533) ([7c41c86](https://github.com/anncwb/vue-vben-admin/commit/7c41c8673c2fd5f2cf946a3ae84d8688578f9754)) +- **form:** Improve form error handling ([9a21b8b](https://github.com/anncwb/vue-vben-admin/commit/9a21b8b6a4a33d69c4e1b439fc01c4038c150ff9)) +- **form:** improve form props acquisition,fix [#527](https://github.com/anncwb/vue-vben-admin/issues/527) ([b7ea68e](https://github.com/anncwb/vue-vben-admin/commit/b7ea68e6f8944b154edf1fccd3faf8744883cbd4)) +- **form:** improve warning prompt, fix [#538](https://github.com/anncwb/vue-vben-admin/issues/538) ([3ff70bb](https://github.com/anncwb/vue-vben-admin/commit/3ff70bb56f998cfc92a773676d75c06372d90658)) +- **form:** placeholder setting in componentProps ([#634](https://github.com/anncwb/vue-vben-admin/issues/634)) ([2d3d04f](https://github.com/anncwb/vue-vben-admin/commit/2d3d04f547046c23cdfc319a7483261b47c08e83)) +- **form:** remove field binding when deleting schema [#471](https://github.com/anncwb/vue-vben-admin/issues/471) ([38f5072](https://github.com/anncwb/vue-vben-admin/commit/38f5072695f63b30c6ce6b2741b003db605abd82)) +- **layout:** fix useLockPage not work, fix [#611](https://github.com/anncwb/vue-vben-admin/issues/611) ([3bb6d11](https://github.com/anncwb/vue-vben-admin/commit/3bb6d11ed1b33adbfd6c76a0e06442cd62356ab7)) +- **lock:** automatic screen lock does not work ([d5b7689](https://github.com/anncwb/vue-vben-admin/commit/d5b768929e02ac4c6a04f3fd17a904e894c50e36)) +- **login:** incorrect enter event bind ([#625](https://github.com/anncwb/vue-vben-admin/issues/625)) ([bb0d2e1](https://github.com/anncwb/vue-vben-admin/commit/bb0d2e1c71899937f3c3d467803b18013e91782a)) +- **menu:** ensure that the external link jumps correctly, fix [#516](https://github.com/anncwb/vue-vben-admin/issues/516) ([6b7f688](https://github.com/anncwb/vue-vben-admin/commit/6b7f688eaf08184272fc625ca7e7665384641714)) +- **menu:** improve menu logic, fix [#461](https://github.com/anncwb/vue-vben-admin/issues/461) ([ee1c349](https://github.com/anncwb/vue-vben-admin/commit/ee1c3498587951a6a4cc0b49edb9dacf3f2af5c3)) +- **modal:** proptype conflict with ant design modal(fixed: [#545](https://github.com/anncwb/vue-vben-admin/issues/545)) ([#575](https://github.com/anncwb/vue-vben-admin/issues/575)) ([a579b84](https://github.com/anncwb/vue-vben-admin/commit/a579b8456ac73ac48c6af1510317acca20ed9b52)) +- **store:** addTab fx ([#607](https://github.com/anncwb/vue-vben-admin/issues/607)) ([336be68](https://github.com/anncwb/vue-vben-admin/commit/336be680d307acf8a1710194eba5505f8532d0bb)) +- **store:** fix pinia typo ([bbf178f](https://github.com/anncwb/vue-vben-admin/commit/bbf178f64b29d4576ba7de8afdce37d677f748e8)) +- **style:** add table title min-height ([#547](https://github.com/anncwb/vue-vben-admin/issues/547)) ([bf365e2](https://github.com/anncwb/vue-vben-admin/commit/bf365e26e5d457ca1924def3e50097e1d211aa43)) +- **style:** fix icon style, fix [#496](https://github.com/anncwb/vue-vben-admin/issues/496) ([ccae5cd](https://github.com/anncwb/vue-vben-admin/commit/ccae5cd9246888709a319f92357d89c6ab9d9c0b)) +- **style:** fix layout style, fix [#633](https://github.com/anncwb/vue-vben-admin/issues/633) ([8e3f84c](https://github.com/anncwb/vue-vben-admin/commit/8e3f84c3b76fbca11222cbede2441e83154127b6)) +- **theme:** make sure the menu style is correct, fix [#382](https://github.com/anncwb/vue-vben-admin/issues/382) ([c77f7e6](https://github.com/anncwb/vue-vben-admin/commit/c77f7e62aba51072325dffdb01d3c0cc87c578b0)) +- **theme:** make sure the steps style is correct, fix [#414](https://github.com/anncwb/vue-vben-admin/issues/414) ([640a2c1](https://github.com/anncwb/vue-vben-admin/commit/640a2c17986e2b59be57125e91051ec879f31eeb)) +- **types:** fix store types ([cd4b5e1](https://github.com/anncwb/vue-vben-admin/commit/cd4b5e14c2afe8841871cf79490a02a30bed0ebe)) +- typo, ifx [#637](https://github.com/anncwb/vue-vben-admin/issues/637) ([e3569b8](https://github.com/anncwb/vue-vben-admin/commit/e3569b81b10e887ed7144349181904ea6fdef14d)) +- **style:** fix build style errors,fix [#528](https://github.com/anncwb/vue-vben-admin/issues/528) ([7f6f8ee](https://github.com/anncwb/vue-vben-admin/commit/7f6f8eefe9b1214d5c6dabc526d966dfcaea76e6)) +- **style:** fix layout header style, basic arrow style and table search form style ([#525](https://github.com/anncwb/vue-vben-admin/issues/525)) ([e2ddf43](https://github.com/anncwb/vue-vben-admin/commit/e2ddf43699df900dacab7d7d384d7caa53879ad9)) +- **table:** columns ref fixed([#564](https://github.com/anncwb/vue-vben-admin/issues/564)) ([#573](https://github.com/anncwb/vue-vben-admin/issues/573)) ([43e4c21](https://github.com/anncwb/vue-vben-admin/commit/43e4c21950ea3659c538ecc29b04b0377a6de874)) +- **table:** submitButtonOptions not work,fix [#531](https://github.com/anncwb/vue-vben-admin/issues/531) ([16ecf71](https://github.com/anncwb/vue-vben-admin/commit/16ecf71850675be0031f41c8cb91371cf07cbea0)) +- **tabs:** fix the problem that other functions are invalid when the tab is closed, close [#376](https://github.com/anncwb/vue-vben-admin/issues/376) ([b92b8a3](https://github.com/anncwb/vue-vben-admin/commit/b92b8a3c6af1d936d48b5f58674f419407eeb600)) +- **theme:** wrong color when RadioButtonGroup checked ([#626](https://github.com/anncwb/vue-vben-admin/issues/626)) ([5eee0ce](https://github.com/anncwb/vue-vben-admin/commit/5eee0ceb6e1e949e63d51cd0d9647cf8094f378c)) +- **theme generate:** Fix [#604](https://github.com/anncwb/vue-vben-admin/issues/604) ([#605](https://github.com/anncwb/vue-vben-admin/issues/605)) ([c26dd03](https://github.com/anncwb/vue-vben-admin/commit/c26dd034165b02d107977fdfe13471ea80e991cc)) +- **tinymce:** ensure that the public resource path is correct,fix [#487](https://github.com/anncwb/vue-vben-admin/issues/487) ([a863ad4](https://github.com/anncwb/vue-vben-admin/commit/a863ad46b4e2837cbbda8bb51b8c9a6e8bb3f442)) +- **tree:** basicTree 设置 blockNode=false 后,显示异常 ([#567](https://github.com/anncwb/vue-vben-admin/issues/567)) ([2f8b218](https://github.com/anncwb/vue-vben-admin/commit/2f8b2183ec25f7c2a11bb5dc0a0a2578d7568ec3)) +- **tree:** onCheck event lose origin param ([#636](https://github.com/anncwb/vue-vben-admin/issues/636)) ([d8ff30d](https://github.com/anncwb/vue-vben-admin/commit/d8ff30d9ece53e006e5e58894461adeeb3b273e0)) +- **tree:** typo([#615](https://github.com/anncwb/vue-vben-admin/issues/615)) ([bc82d1a](https://github.com/anncwb/vue-vben-admin/commit/bc82d1a397beff68ba86365d7d54bb70b3520f9f)) +- **tree:** value prop type ([#613](https://github.com/anncwb/vue-vben-admin/issues/613)) ([0112d6b](https://github.com/anncwb/vue-vben-admin/commit/0112d6b313e66f624cd91e9ef933af57b0d280f9)) +- echart import path ([7e43d88](https://github.com/anncwb/vue-vben-admin/commit/7e43d88f9c37d88d7bf1b2d29e8ffbdc7ca155a5)) +- ensure that the 401 jumps to the login page correctly, fix [#512](https://github.com/anncwb/vue-vben-admin/issues/512) ([6a88205](https://github.com/anncwb/vue-vben-admin/commit/6a8820597fb58ef7cda7ead59f5cbb4c72c0f882)) +- fix AppendFormDemo ([#505](https://github.com/anncwb/vue-vben-admin/issues/505)) ([8c2491f](https://github.com/anncwb/vue-vben-admin/commit/8c2491fcb6853bfe06df265eb6daa5aa7d979b74)) +- fix case errors ([663d13a](https://github.com/anncwb/vue-vben-admin/commit/663d13a67f84fb02a6b9ee44a6e8b53c32cc6044)) +- fix dark theme refreshing flashing white screen ([26adbc9](https://github.com/anncwb/vue-vben-admin/commit/26adbc92be1c8ce5ce6f93302fb806058ef087cf)) +- fix the default value of props ([8b2e0f6](https://github.com/anncwb/vue-vben-admin/commit/8b2e0f665f15edd211f558bc0526465e07e7bab0)) +- improve login page style ([780a8a6](https://github.com/anncwb/vue-vben-admin/commit/780a8a67b874ca1c8d05c2561f88081cc4ec4b28)) +- Improve the picture cropping component ([#463](https://github.com/anncwb/vue-vben-admin/issues/463)) ([700306b](https://github.com/anncwb/vue-vben-admin/commit/700306bb45d5f2b975c20bd2581fb87a210e589c)) +- login page overflow show problem ([#455](https://github.com/anncwb/vue-vben-admin/issues/455)) ([af6d58e](https://github.com/anncwb/vue-vben-admin/commit/af6d58eb26875f02afb419d9d4d5ee2454292863)) +- password icon dislocation ([#501](https://github.com/anncwb/vue-vben-admin/issues/501)) ([bd83ecc](https://github.com/anncwb/vue-vben-admin/commit/bd83eccdc55c697d0db83bc3a7cf2829cafe96e7)) +- trigger resize in full screen to ensure that the height of other components is normal,fix [#508](https://github.com/anncwb/vue-vben-admin/issues/508) ([ca71760](https://github.com/anncwb/vue-vben-admin/commit/ca717602a602ae90e5c175cdfda0bbcc200b72ad)) +- update Axios.ts ([#492](https://github.com/anncwb/vue-vben-admin/issues/492)) ([e1b30a5](https://github.com/anncwb/vue-vben-admin/commit/e1b30a5075a2a2f9e2c538350950e6e09b6decd1)) + +### Features + +- **axios:** Do you want to return the original response header? For example, use this property when you need to get the response header ([56d8af1](https://github.com/anncwb/vue-vben-admin/commit/56d8af147ec88bb98a37fa3ddf47c2aa16a4110e)) +- **demo:** add permission table demo ([9e20841](https://github.com/anncwb/vue-vben-admin/commit/9e208411a24d4ccc9306555cc45aa7135d0df78f)) +- **form:** add 'layout', 'labelAlign', 'rowProps' option ([#651](https://github.com/anncwb/vue-vben-admin/issues/651)) ([785732f](https://github.com/anncwb/vue-vben-admin/commit/785732f438916d7767ad44789c16216a6f6505a8)) +- **form:** add form field nested support ([#591](https://github.com/anncwb/vue-vben-admin/issues/591)) ([ec3d51d](https://github.com/anncwb/vue-vben-admin/commit/ec3d51d69b66500f4f604151255920460d1906ce)) +- **form:** add prop autoSubmitOnEnter ([#620](https://github.com/anncwb/vue-vben-admin/issues/620)) ([9b2d41e](https://github.com/anncwb/vue-vben-admin/commit/9b2d41ea44ed0da4dde22856bf23b52748244642)) +- **form:** add Slider demo ([#555](https://github.com/anncwb/vue-vben-admin/issues/555)) ([e80280f](https://github.com/anncwb/vue-vben-admin/commit/e80280fb81b0bcdd74066c08fd4403e36b00b026)) +- **form:** adding resetSchema method ([c639e49](https://github.com/anncwb/vue-vben-admin/commit/c639e493a5a32789e397990953189541170169c8)) +- **form:** helpMessage Increase function type value ([#616](https://github.com/anncwb/vue-vben-admin/issues/616)) ([f455fb9](https://github.com/anncwb/vue-vben-admin/commit/f455fb97f9b70ca4979561a82ae0f25825527013)) +- **form:** requires Increase function type value ([#649](https://github.com/anncwb/vue-vben-admin/issues/649)) ([765064a](https://github.com/anncwb/vue-vben-admin/commit/765064a190b1a24dfb9ae808e99807ddae2ed212)) +- **qrcode:** custom drawing support ([#580](https://github.com/anncwb/vue-vben-admin/issues/580)) ([2b76b88](https://github.com/anncwb/vue-vben-admin/commit/2b76b88481dab2c580e684987a80028710d4698d)) +- **table:** 表格的数据列和操作列的字段可以根据权限和业务来控制是否显示 ([5a3861b](https://github.com/anncwb/vue-vben-admin/commit/5a3861b9cfc79da3297f8ddd045b88f0daca0ada)) +- **table:** Table operation columns support permission codes ([6afee41](https://github.com/anncwb/vue-vben-admin/commit/6afee415a3a8007f13af57892d62759ffbcde5a5)) +- **user:** add user login expiration example ([5465f05](https://github.com/anncwb/vue-vben-admin/commit/5465f058ceb7b130e456feaebb17c3beedb092a5)) +- add codeEditor component ([a812685](https://github.com/anncwb/vue-vben-admin/commit/a812685084b45ce3c6b6675bb1569e324f742416)) +- add flowChart Component ([#488](https://github.com/anncwb/vue-vben-admin/issues/488)) ([2576735](https://github.com/anncwb/vue-vben-admin/commit/2576735adeb42ddd39bbaae6f4f5662df781b83a)) +- add JsonPreview component ([0649011](https://github.com/anncwb/vue-vben-admin/commit/0649011eba9b86b543223aca99721da754dcea14)) +- add spin prop for Icon ([#477](https://github.com/anncwb/vue-vben-admin/issues/477)) ([6dd7d0f](https://github.com/anncwb/vue-vben-admin/commit/6dd7d0f928ebb4c6d7be66f4cd134fb291fc7dc2)) +- persistent save tab, fix [#359](https://github.com/anncwb/vue-vben-admin/issues/359) ([967b28c](https://github.com/anncwb/vue-vben-admin/commit/967b28c4c06cf92e9ab90cff51f59a0d6ced5d7b)) + +### Performance Improvements + +- let svg-icon support ssr ([94a826d](https://github.com/anncwb/vue-vben-admin/commit/94a826d02858e115adf8c1db4c0d0d7d795d7281)) +- **tree:** improve the beforeRightClick callback to support more configuration of the menu ([#608](https://github.com/anncwb/vue-vben-admin/issues/608)) ([adff788](https://github.com/anncwb/vue-vben-admin/commit/adff788de54a46fd035b569892135be377dd4f92)) +- add AppendFormDemo ([#503](https://github.com/anncwb/vue-vben-admin/issues/503)) ([85b92a9](https://github.com/anncwb/vue-vben-admin/commit/85b92a9add2b560559b4ef60ecf93e22f5941edb)) +- add Coordinating the selection of provinces and cities ([#534](https://github.com/anncwb/vue-vben-admin/issues/534)) ([5fae2b0](https://github.com/anncwb/vue-vben-admin/commit/5fae2b02eae7dc91baef774ca9dfdf0da91b8040)) +- improve countTo ([#499](https://github.com/anncwb/vue-vben-admin/issues/499)) ([94b2222](https://github.com/anncwb/vue-vben-admin/commit/94b2222c085e30cbc4a7a49dfac13af15aec98b9)) +- improve cropper example ([#491](https://github.com/anncwb/vue-vben-admin/issues/491)) ([5e36a8b](https://github.com/anncwb/vue-vben-admin/commit/5e36a8b5754afe916236f1c58a159aa7df69cf83)) +- improve flowChart logic ([e1bc33f](https://github.com/anncwb/vue-vben-admin/commit/e1bc33f5c5660f62591997c1949c887ac7387871)) +- merge locale file ([c04e894](https://github.com/anncwb/vue-vben-admin/commit/c04e8943bcdcdee612044a534d6c1281c956c3c1)) +- optimize i18n to add the initial locale to the locale pool during initialization ([#577](https://github.com/anncwb/vue-vben-admin/issues/577)) ([ae3f832](https://github.com/anncwb/vue-vben-admin/commit/ae3f8329c25ef24c44c54690116fd7d3dc35ae85)) +- set header can use For Qs ([#562](https://github.com/anncwb/vue-vben-admin/issues/562)) ([5724bc5](https://github.com/anncwb/vue-vben-admin/commit/5724bc5b3b960f7c0686c8e60c2b682b16841e6f)) + +# [2.3.0](https://github.com/anncwb/vue-vben-admin/compare/v2.2.0...v2.3.0) (2021-04-10) + +### Bug Fixes + +- **api-select:** make sure the type is correct, fix [#468](https://github.com/anncwb/vue-vben-admin/issues/468) ([37c5741](https://github.com/anncwb/vue-vben-admin/commit/37c5741601951349f622801a48a7bf9e45d723a4)) +- **menu:** improve menu logic, fix [#461](https://github.com/anncwb/vue-vben-admin/issues/461) ([ee1c349](https://github.com/anncwb/vue-vben-admin/commit/ee1c3498587951a6a4cc0b49edb9dacf3f2af5c3)) +- **theme:** make sure the menu style is correct, fix [#382](https://github.com/anncwb/vue-vben-admin/issues/382) ([c77f7e6](https://github.com/anncwb/vue-vben-admin/commit/c77f7e62aba51072325dffdb01d3c0cc87c578b0)) +- **theme:** make sure the steps style is correct, fix [#414](https://github.com/anncwb/vue-vben-admin/issues/414) ([640a2c1](https://github.com/anncwb/vue-vben-admin/commit/640a2c17986e2b59be57125e91051ec879f31eeb)) +- improve login page style ([780a8a6](https://github.com/anncwb/vue-vben-admin/commit/780a8a67b874ca1c8d05c2561f88081cc4ec4b28)) +- Improve the picture cropping component ([#463](https://github.com/anncwb/vue-vben-admin/issues/463)) ([700306b](https://github.com/anncwb/vue-vben-admin/commit/700306bb45d5f2b975c20bd2581fb87a210e589c)) +- login page overflow show problem ([#455](https://github.com/anncwb/vue-vben-admin/issues/455)) ([af6d58e](https://github.com/anncwb/vue-vben-admin/commit/af6d58eb26875f02afb419d9d4d5ee2454292863)) + +### Features + +- persistent save tab, fix [#359](https://github.com/anncwb/vue-vben-admin/issues/359) ([967b28c](https://github.com/anncwb/vue-vben-admin/commit/967b28c4c06cf92e9ab90cff51f59a0d6ced5d7b)) + +# [2.2.0](https://github.com/anncwb/vue-vben-admin/compare/v2.1.1...v2.2.0) (2021-04-07) + +### Bug Fixes + +- **abakysis:** fix tooltip style,fix [#436](https://github.com/anncwb/vue-vben-admin/issues/436) ([1e4a250](https://github.com/anncwb/vue-vben-admin/commit/1e4a250da10b01bfd4e667d533f6cae9b8c58fe9)) +- **breadcrumb:** ensure the breadcrumbs display the icon correctly, fix [#433](https://github.com/anncwb/vue-vben-admin/issues/433) ([0b66360](https://github.com/anncwb/vue-vben-admin/commit/0b66360cc9f60c5064be4c3cae39091541f3be8c)) +- **build:** fix build error ([6d6e0a1](https://github.com/anncwb/vue-vben-admin/commit/6d6e0a1bfef3a152d31776520e1445203d2ba3f4)) +- **drawer:** ensure the slot is working ([b9b470f](https://github.com/anncwb/vue-vben-admin/commit/b9b470f4df1cd57ca501666b6b3270a4d4d4f873)) +- **echart:** legend not work ([b25ceb4](https://github.com/anncwb/vue-vben-admin/commit/b25ceb4201bce806dc129f24c2d98fd2ff0392d1)) +- **menu:** ensure the menu is activated correctly,fix [#432](https://github.com/anncwb/vue-vben-admin/issues/432) ([bb67692](https://github.com/anncwb/vue-vben-admin/commit/bb67692cfdd5089f0f1d60d4a36b52592db22dde)) +- **mock:** make sure the background mode login is normal, fix [#452](https://github.com/anncwb/vue-vben-admin/issues/452) ([1e66987](https://github.com/anncwb/vue-vben-admin/commit/1e669870cc15384bf76f32ee95008f0c998b477b)) +- **server:** grammatical errors ([ee4829c](https://github.com/anncwb/vue-vben-admin/commit/ee4829c15d7c8e978eb616edb7f1e61c258d469b)) +- **table:** ensure data responsiveness, fix [#447](https://github.com/anncwb/vue-vben-admin/issues/447) ([64b6313](https://github.com/anncwb/vue-vben-admin/commit/64b6313b4e43fdc2e9b292f554889b845e26182f)) +- **table:** make sure the editing line is working, fix [#439](https://github.com/anncwb/vue-vben-admin/issues/439) ([b54b794](https://github.com/anncwb/vue-vben-admin/commit/b54b794264ecb513567b841c5a12856965d02754)) +- **table-action:** ensure that the click event is not triggered, fix [#441](https://github.com/anncwb/vue-vben-admin/issues/441) ([67a7a76](https://github.com/anncwb/vue-vben-admin/commit/67a7a76b735aafe2e1a8258c75c4a3c5dd657de6)) +- **use-loading:** rendering fails when used with onMounted, fix [#438](https://github.com/anncwb/vue-vben-admin/issues/438) ([6b99622](https://github.com/anncwb/vue-vben-admin/commit/6b996229e1449b1721ce6797ba6a964850e2e215)) +- **useColumn:** fixed table column changes with hidden columns disappearing after dropping ([#453](https://github.com/anncwb/vue-vben-admin/issues/453)) ([f05cc6d](https://github.com/anncwb/vue-vben-admin/commit/f05cc6d34e935c342e1f7ada6692ea0178b7c984)) + +### Features + +- dark mode ([5b8eb4a](https://github.com/anncwb/vue-vben-admin/commit/5b8eb4a49a097a47caf491c44df427522ab58daa)) +- **api-select:** add immediate option,close [#430](https://github.com/anncwb/vue-vben-admin/issues/430) ([5b4a41c](https://github.com/anncwb/vue-vben-admin/commit/5b4a41ced412fe3623618791ffa3123a3a2cfcdc)) +- **print:** add print example ([2f99892](https://github.com/anncwb/vue-vben-admin/commit/2f99892d96770d550e1cf58e052c40b85efb53c2)) +- **tree:** add headerTitle slot ([6bb19fb](https://github.com/anncwb/vue-vben-admin/commit/6bb19fb2d4fa57d8006281d52acd80baaa054b3e)) + +### Performance Improvements + +- code optimization ([37f6660](https://github.com/anncwb/vue-vben-admin/commit/37f6660c574f0cf8b432f66b67062c3bb0314d5c)) +- delete tinymce useless style files ([edc7525](https://github.com/anncwb/vue-vben-admin/commit/edc7525103f2e0fd90562b2e30839c11ed62556d)) +- refoctor useTitle ([979058a](https://github.com/anncwb/vue-vben-admin/commit/979058ad95d9669cb113033f76b5dafb932aad0f)) + +## [2.1.1](https://github.com/anncwb/vue-vben-admin/compare/v2.1.0...v2.1.1) (2021-03-25) + +### Bug Fixes + +- **form:** ensure that the hidden fields of the form are verified properly, fix [#413](https://github.com/anncwb/vue-vben-admin/issues/413) ([237f41d](https://github.com/anncwb/vue-vben-admin/commit/237f41da68592ede236b722157c91f9d7b45db1b)) +- **icon:** ensure the menu icon style is correct, fix [#425](https://github.com/anncwb/vue-vben-admin/issues/425) ([5c57a1d](https://github.com/anncwb/vue-vben-admin/commit/5c57a1dda13975c13e65511a39e7483e4a5d3999)) +- add route base close [#404](https://github.com/anncwb/vue-vben-admin/issues/404) ([8ad127c](https://github.com/anncwb/vue-vben-admin/commit/8ad127c293872aa10db03044bbc68715dc1b804a)) +- ensure permissionMode exists close [#409](https://github.com/anncwb/vue-vben-admin/issues/409) ([8fb0396](https://github.com/anncwb/vue-vben-admin/commit/8fb03961f50051695983f8cb415d6009b9d6b643)) +- refresh error ([5bf90ee](https://github.com/anncwb/vue-vben-admin/commit/5bf90eea627638517e3ced024289696a6ece8e74)) +- **input-count:** make sure the reset function works close [#381](https://github.com/anncwb/vue-vben-admin/issues/381) ([3c4de9b](https://github.com/anncwb/vue-vben-admin/commit/3c4de9b0be06350f0d9ad97bfb5f7f773c38be38)) +- **menu:** ensure the menu has meta attributes close [#397](https://github.com/anncwb/vue-vben-admin/issues/397) ([b2a1951](https://github.com/anncwb/vue-vben-admin/commit/b2a1951fd00433cb5e1c9dce982c53a9c9edd874)) +- **menu:** fix the menu disappeared in background mode ([50915c9](https://github.com/anncwb/vue-vben-admin/commit/50915c9754473ba9096b3b1cceedf0d7e7212ad9)) +- **menu:** make sure the menu is displayed properly on the small screen close [#336](https://github.com/anncwb/vue-vben-admin/issues/336) ([82c3186](https://github.com/anncwb/vue-vben-admin/commit/82c3186309971517183fc44bfcac159612e48a7b)) +- **progress:** fix progress sometimes cannot done ([#388](https://github.com/anncwb/vue-vben-admin/issues/388)) ([8360b1d](https://github.com/anncwb/vue-vben-admin/commit/8360b1d6886b5639cf43da5ab866156d140a0f01)) +- **route:** ensure that the first level menu can be hidden ([e2cc5af](https://github.com/anncwb/vue-vben-admin/commit/e2cc5af9375f59d2891be769010ef5d3ccfe9755)) +- **table:** ensure that the height calculation is correct close [#395](https://github.com/anncwb/vue-vben-admin/issues/395) ([1d7608e](https://github.com/anncwb/vue-vben-admin/commit/1d7608ee40c27ce81e031947ed6c679cc8b04c77)) +- **table:** fix table check column configuration failure close [#391](https://github.com/anncwb/vue-vben-admin/issues/391) ([c3096e2](https://github.com/anncwb/vue-vben-admin/commit/c3096e26ff24c8afd9555e676c898030664846d7)) +- **tree:** ensure that the check event is emitted close [#400](https://github.com/anncwb/vue-vben-admin/issues/400) ([16ef134](https://github.com/anncwb/vue-vben-admin/commit/16ef13477c8f06c13ff3611b9e67e430fac433e7)) +- ensure the breadcrumb level is correct ([e49072c](https://github.com/anncwb/vue-vben-admin/commit/e49072c31339ba58473ffa883308cc3c2c4c43e9)) +- LayoutMap cannot get correctly ([#398](https://github.com/anncwb/vue-vben-admin/issues/398)) ([7c16c2f](https://github.com/anncwb/vue-vben-admin/commit/7c16c2fa9e6cb2e87894666d6687eed3fc744b64)) +- welcome page not cached in back-end mode ([#389](https://github.com/anncwb/vue-vben-admin/issues/389)) ([f0b93b5](https://github.com/anncwb/vue-vben-admin/commit/f0b93b50e7b6b9c444f8422f91be73085be8c5fe)) +- **v-auth:** ensure the background mode is correct close [#330](https://github.com/anncwb/vue-vben-admin/issues/330) ([67962f1](https://github.com/anncwb/vue-vben-admin/commit/67962f1deea31d695d20ae0ea7fc39b39c1eea47)) + +### Features + +- **route:** add hideChildrenInMenu option close [#346](https://github.com/anncwb/vue-vben-admin/issues/346) ([b67cf22](https://github.com/anncwb/vue-vben-admin/commit/b67cf22dfc8d27428b045f47fcd9e2797b81a81d)) +- **table:** add expandAll/collapseAll function close [#333](https://github.com/anncwb/vue-vben-admin/issues/333) ([391da9e](https://github.com/anncwb/vue-vben-admin/commit/391da9ec2884885f9dfe86ddb869ccc0d193491e)) + +# [2.1.0](https://github.com/anncwb/vue-vben-admin/compare/v2.0.3...v2.1.0) (2021-03-15) + +### Bug Fixes + +- **button:** fix button style error close [#312](https://github.com/anncwb/vue-vben-admin/issues/312) ([7a6c87f](https://github.com/anncwb/vue-vben-admin/commit/7a6c87f8c1aa34a7a00506fb89fb231e3a176f6f)) +- **menu:** fix hideMenu not working close [#338](https://github.com/anncwb/vue-vben-admin/issues/338) ([5b2fbfb](https://github.com/anncwb/vue-vben-admin/commit/5b2fbfb6ce4054ece60c851c45baf60f3a07a4db)) +- **page-wraper:** fix PageWrapper the scroll bar on the right side of the content area when the user clicks on the tab page to reload the page ([#341](https://github.com/anncwb/vue-vben-admin/issues/341)) ([fcff2cb](https://github.com/anncwb/vue-vben-admin/commit/fcff2cb1911f1e18017f25b3509d1c67f7e86e81)) +- **page-wrapper:** fix PageWrapper title not showing ([9e3adaa](https://github.com/anncwb/vue-vben-admin/commit/9e3adaa30c7cdaf23855922100e16717856ba1d9)) +- **table:** ensure that editable cell values are echoed correctly close [#335](https://github.com/anncwb/vue-vben-admin/issues/335) ([fab7a6c](https://github.com/anncwb/vue-vben-admin/commit/fab7a6c58d586300d58e1b6837927e1569b57aa5)) +- **table:** ensure that the height calculation is normal close [#349](https://github.com/anncwb/vue-vben-admin/issues/349) ([6095cb5](https://github.com/anncwb/vue-vben-admin/commit/6095cb54afe3f4fcabbfff26ac6704ecfbbddae5)) +- **table:** ensure that the table height is correct when the data is empty ([53867a8](https://github.com/anncwb/vue-vben-admin/commit/53867a846154d9a3529f50d20d92ce5fdb41986f)) +- **table:** ensure that the value of the table action is updated correctly close [#301](https://github.com/anncwb/vue-vben-admin/issues/301) [#313](https://github.com/anncwb/vue-vben-admin/issues/313) ([7156e47](https://github.com/anncwb/vue-vben-admin/commit/7156e47c1813ec01594d9dff4a1e7d593f3c17db)) +- **table:** fix table height calculation problem ([0fe42a0](https://github.com/anncwb/vue-vben-admin/commit/0fe42a06c1f2ef69805dbfeecbcac919ff0aedd0)), closes [#348](https://github.com/anncwb/vue-vben-admin/issues/348) +- **table:** fix table row misalignment close [#353](https://github.com/anncwb/vue-vben-admin/issues/353) ([e15737b](https://github.com/anncwb/vue-vben-admin/commit/e15737b9d17d8ebea4f4e9897aeae9b250910a15)) +- **table:** fix TableAction row height error close [#350](https://github.com/anncwb/vue-vben-admin/issues/350) ([a759e44](https://github.com/anncwb/vue-vben-admin/commit/a759e44c6e5c223d2fef52c5a9698e571eed2d52)) +- **transition:** fix transition not work close [#334](https://github.com/anncwb/vue-vben-admin/issues/334) ([7d8b8db](https://github.com/anncwb/vue-vben-admin/commit/7d8b8db256f78b228b2b4629a472834a4cce9bd4)) +- **tree:** ebsure the expansion is functioning properly close [#362](https://github.com/anncwb/vue-vben-admin/issues/362) ([a405de8](https://github.com/anncwb/vue-vben-admin/commit/a405de8d202710264e802edb270bbd5cd4a1ab80)) +- **tree:** tree can customize title close [#344](https://github.com/anncwb/vue-vben-admin/issues/344) ([ed422b7](https://github.com/anncwb/vue-vben-admin/commit/ed422b7c56bf9d44be001b8a54358d69c100ff35)) +- **useTableScroll:** query paginationel every time to get the correct height ([#355](https://github.com/anncwb/vue-vben-admin/issues/355)) ([f818bb9](https://github.com/anncwb/vue-vben-admin/commit/f818bb9a107e43adfb8ef2a095635f5fffb5800b)) + +### Features + +- **icon:** added svg icon picker ([1418dc6](https://github.com/anncwb/vue-vben-admin/commit/1418dc6a597a8410711359f07ae66f0fea858977)) +- **map:** added AMap/Baidu/Google Map example close [#81](https://github.com/anncwb/vue-vben-admin/issues/81) ([a9462f0](https://github.com/anncwb/vue-vben-admin/commit/a9462f0d4dacb8db9300c416b2d3f094be624220)) +- **time:** added time compoennt close [#285](https://github.com/anncwb/vue-vben-admin/issues/285) ([a89eeef](https://github.com/anncwb/vue-vben-admin/commit/a89eeef6f3a0b9863d28cf516b126a938eed7361)) + +### Performance Improvements + +- **icon:** icon and SvgIcon integration ([e8fe6a9](https://github.com/anncwb/vue-vben-admin/commit/e8fe6a929be025a889ddec624ff9c2729313c818)) + +## [2.0.3](https://github.com/anncwb/vue-vben-admin/compare/v2.0.2...v2.0.3) (2021-03-07) + +### Bug Fixes + +- **breadcrumb:** ensure that the single-level breadcrumbs jump correctly close [#321](https://github.com/anncwb/vue-vben-admin/issues/321) ([e0dc5cf](https://github.com/anncwb/vue-vben-admin/commit/e0dc5cf2f299fd4c1efdf4f00b9f0f72f07d5937)) +- **description:** ensure that props respond ([ce93e46](https://github.com/anncwb/vue-vben-admin/commit/ce93e46faf1d7250dd3acd3fd97ccd6382b2f822)) +- **form:** allow the setFieldsValue method to be null or undefined close [#320](https://github.com/anncwb/vue-vben-admin/issues/320) ([8f76ef4](https://github.com/anncwb/vue-vben-admin/commit/8f76ef4e70de58ba5c4497d8b10a036a54a9ac87)) +- **form:** ensure that the Form component does not verify hidden form items ([43a45b7](https://github.com/anncwb/vue-vben-admin/commit/43a45b7c996c84f19d00cb9754277b943daf9a10)) +- **form:** fix the problem of form props monitoring close [#322](https://github.com/anncwb/vue-vben-admin/issues/322) ([83a3460](https://github.com/anncwb/vue-vben-admin/commit/83a34603562e6358203b834b8feb59b0b44dbbcd)) +- **menu:** fix menu icon missing close [#328](https://github.com/anncwb/vue-vben-admin/issues/328) ([d5d4c4b](https://github.com/anncwb/vue-vben-admin/commit/d5d4c4b4136158e061e4a3b6b306af6d4e8cd621)) +- **table:** fix pagination error ([745fcfc](https://github.com/anncwb/vue-vben-admin/commit/745fcfc014e3e9e13d6a415a8f094cfef68be908)) +- **tree:** fix the logic problem of show attribute of ActionItem under BasicTree ([80b47c8](https://github.com/anncwb/vue-vben-admin/commit/80b47c84cd490388c6db659921f1103c443d7b9d)) + +### Features + +- add SvgIcon component ([9c2a2a0](https://github.com/anncwb/vue-vben-admin/commit/9c2a2a0c00dae6f334c99acc9ab2f571fd8905c0)) +- **tree:** add clickRowToExpand option close [#318](https://github.com/anncwb/vue-vben-admin/issues/318) ([e696089](https://github.com/anncwb/vue-vben-admin/commit/e696089660131786ea24632ed75adc57b6ea58f4)) + +### Performance Improvements + +- optimize local loading speed close [#329](https://github.com/anncwb/vue-vben-admin/issues/329) ([491f1fc](https://github.com/anncwb/vue-vben-admin/commit/491f1fcfff17f2297e3fee00e1542778aed08e56)) +- **login:** enter to log in ([b93f20f](https://github.com/anncwb/vue-vben-admin/commit/b93f20f0df91689191b8414657171e9f17ba5d68)) +- **table:** the table fills the height according to the screen close [#310](https://github.com/anncwb/vue-vben-admin/issues/310) ([551fe50](https://github.com/anncwb/vue-vben-admin/commit/551fe50a44d0b6358cf3861f772ca223ea56f0e2)) + +## [2.0.2](https://github.com/anncwb/vue-vben-admin/compare/v2.0.0...v2.0.2) (2021-03-03) + +### Bug Fixes + +- change transition-duration to make animate smoothly ([#294](https://github.com/anncwb/vue-vben-admin/issues/294)) ([5eac9b2](https://github.com/anncwb/vue-vben-admin/commit/5eac9b23d6d8ad91e110169519bfd3ab50f985a9)) +- ensure that storage is deleted correctly close [#292](https://github.com/anncwb/vue-vben-admin/issues/292) ([ec7bef7](https://github.com/anncwb/vue-vben-admin/commit/ec7bef792b2a780736c2b1713af3638fa0b69eed)) +- ensure that the correct components are dynamically imported ([b476e1c](https://github.com/anncwb/vue-vben-admin/commit/b476e1c84c52dab7030fd19b34ecd33e65fadcb2)) +- ensure to request the interface correctly ([11d3f39](https://github.com/anncwb/vue-vben-admin/commit/11d3f395caf7e2268630090eb34f4e5c114a96b7)) +- expose tree information in the event close [#315](https://github.com/anncwb/vue-vben-admin/issues/315) ([b6bb816](https://github.com/anncwb/vue-vben-admin/commit/b6bb81630de728c146bf0e559bef88b69d4b8a21)) +- fix login page style ([7b4fcd2](https://github.com/anncwb/vue-vben-admin/commit/7b4fcd2ecac8107f7d052dee08cb8007dc5e5dd9)) +- improve persistent cache logic ([15567e4](https://github.com/anncwb/vue-vben-admin/commit/15567e478c0f274b0f8f0a7410ea5cb636bacc3d)) +- **dashboard:** fix workbench page style ([#280](https://github.com/anncwb/vue-vben-admin/issues/280)) ([7d9b521](https://github.com/anncwb/vue-vben-admin/commit/7d9b521c693b59da5fa28130b5753afa0914e598)) +- **image:** fix preview style close [#276](https://github.com/anncwb/vue-vben-admin/issues/276) ([f675fff](https://github.com/anncwb/vue-vben-admin/commit/f675fff2e66054b4157b2a330dbf151822b0befd)) +- **login:** fix login style close [#306](https://github.com/anncwb/vue-vben-admin/issues/306) ([a84586e](https://github.com/anncwb/vue-vben-admin/commit/a84586e2f49a2966ac5cb02d945e62e107b247d1)) +- **modal:** ensure that the height is correct in the modal full screen state close [#308](https://github.com/anncwb/vue-vben-admin/issues/308) ([37508ca](https://github.com/anncwb/vue-vben-admin/commit/37508ca4113701458cae84fff64062427ba43898)) +- **style:** fix anticon style ([e250ad5](https://github.com/anncwb/vue-vben-admin/commit/e250ad567f3169d4ef7baec8954be2e18c6932e6)) +- **table:** ensure the table setting button dividing line is hidden ([7c2f851](https://github.com/anncwb/vue-vben-admin/commit/7c2f85169248b369f95c5866ef7e90d4fb1739ef)) +- **table:** fix known errors in editable tables close [#267](https://github.com/anncwb/vue-vben-admin/issues/267) ([4f8e1c1](https://github.com/anncwb/vue-vben-admin/commit/4f8e1c1b5ffc78242b300e85be22b1fa07c7d902)) +- **table:** get the selected rows of the table correctly ([6013689](https://github.com/anncwb/vue-vben-admin/commit/601368921f075aa1870d1c3ce8f4a8330260206a)) +- **watermark:** watermark causes a blank bar ([#297](https://github.com/anncwb/vue-vben-admin/issues/297)) ([66fc1b7](https://github.com/anncwb/vue-vben-admin/commit/66fc1b78450fa7846b0d58e6da5f2135e6456238)) + +### Features + +- added system management sample page ([4628d94](https://github.com/anncwb/vue-vben-admin/commit/4628d94415c1787da8b04499e295967f15c4eef5)) +- **icon-picker:** add icon-picker component ([b6cea4a](https://github.com/anncwb/vue-vben-admin/commit/b6cea4a950e92a0f91e06bcc60b4653e1d2709ef)) +- **moda;:** can switch full screen by double-clicking on the head close [#277](https://github.com/anncwb/vue-vben-admin/issues/277) ([e3851dc](https://github.com/anncwb/vue-vben-admin/commit/e3851dc5ea290ef6eb4d12ce2469311b1bee53cd)) +- **tree:** actionItem added show attribute close [#314](https://github.com/anncwb/vue-vben-admin/issues/314) ([8b62fa0](https://github.com/anncwb/vue-vben-admin/commit/8b62fa0cb0559ec3ea8a1b82a2d44165b2337522)) +- **tree:** add renderIcon props close [#309](https://github.com/anncwb/vue-vben-admin/issues/309) ([72b42d7](https://github.com/anncwb/vue-vben-admin/commit/72b42d7b3539919a9baa4f1a7316842f85991c1e)) +- **ws:** added WebSocket examples and service scripts ([c625462](https://github.com/anncwb/vue-vben-admin/commit/c625462e98eec006aaeeef14280775cafeb72364)) +- add dept management page ([3b8ca42](https://github.com/anncwb/vue-vben-admin/commit/3b8ca420c763fe0e386a8dbc023f4f8eb8742252)) +- added settingButtonPosition configuration close [#275](https://github.com/anncwb/vue-vben-admin/issues/275) ([da04913](https://github.com/anncwb/vue-vben-admin/commit/da04913ef324fff122732b445c1b1d1d662b87a3)) +- axios supports form-data format requests ([c41fa75](https://github.com/anncwb/vue-vben-admin/commit/c41fa75265beb00f629dcda808957cb58b905bc2)) + +### Performance Improvements + +- **tree:** strengthen BasicTree function ([cd8e924](https://github.com/anncwb/vue-vben-admin/commit/cd8e924d4641fc46cacd4a934478d8861e8c3c04)) +- hide table full screen button by default ([500900a](https://github.com/anncwb/vue-vben-admin/commit/500900abe16d3e27e1c9e0446a13386c6129d449)) +- imporve axios logic ([a821d9a](https://github.com/anncwb/vue-vben-admin/commit/a821d9a3a279f0e6a5b7dbb316725d603ce30f74)) +- improve login logic ([a09a0ee](https://github.com/anncwb/vue-vben-admin/commit/a09a0eedd29fdc9a9bd5414bd12c08e37c72982a)) +- improve persistent logic ([f57eb94](https://github.com/anncwb/vue-vben-admin/commit/f57eb944edfd967f5f45566ec5bedbf12f147492)) +- move src/types to root ([fcee7d4](https://github.com/anncwb/vue-vben-admin/commit/fcee7d4eb71471dd40567c8d7c97302eeee80697)) +- remove useless code ([be3a3ed](https://github.com/anncwb/vue-vben-admin/commit/be3a3ed699f73d352d49623ef07288093a3332c4)) +- replace crypto-es with crypto-js ([bba7768](https://github.com/anncwb/vue-vben-admin/commit/bba7768759c5d4dedd6599417154c4cb8ab64920)) + +## [2.0.1](https://github.com/anncwb/vue-vben-admin/compare/v2.0.0...v2.0.1) (2021-02-21) + +### Bug Fixes + +- **dashboard:** fix workbench page style ([#280](https://github.com/anncwb/vue-vben-admin/issues/280)) ([7d9b521](https://github.com/anncwb/vue-vben-admin/commit/7d9b521c693b59da5fa28130b5753afa0914e598)) +- **image:** fix preview style close [#276](https://github.com/anncwb/vue-vben-admin/issues/276) ([f675fff](https://github.com/anncwb/vue-vben-admin/commit/f675fff2e66054b4157b2a330dbf151822b0befd)) +- **style:** fix anticon style ([e250ad5](https://github.com/anncwb/vue-vben-admin/commit/e250ad567f3169d4ef7baec8954be2e18c6932e6)) +- **table:** fix known errors in editable tables close [#267](https://github.com/anncwb/vue-vben-admin/issues/267) ([4f8e1c1](https://github.com/anncwb/vue-vben-admin/commit/4f8e1c1b5ffc78242b300e85be22b1fa07c7d902)) + +### Features + +- **moda;:** can switch full screen by double-clicking on the head close [#277](https://github.com/anncwb/vue-vben-admin/issues/277) ([e3851dc](https://github.com/anncwb/vue-vben-admin/commit/e3851dc5ea290ef6eb4d12ce2469311b1bee53cd)) +- added settingButtonPosition configuration close [#275](https://github.com/anncwb/vue-vben-admin/issues/275) ([da04913](https://github.com/anncwb/vue-vben-admin/commit/da04913ef324fff122732b445c1b1d1d662b87a3)) + +# [2.0.0](https://github.com/anncwb/vue-vben-admin/compare/v2.0.0-rc.18...v2.0.0) (2021-02-17) + +### Bug Fixes + +- **i18n:** fix useMessage i18n type [#262](https://github.com/anncwb/vue-vben-admin/issues/262) ([d753155](https://github.com/anncwb/vue-vben-admin/commit/d7531554a274ad9d793ea621739dfffdc7f73db8)) +- **table:** fix the table in the editable row status and press Enter to confirm [#258](https://github.com/anncwb/vue-vben-admin/issues/258) ([64533f6](https://github.com/anncwb/vue-vben-admin/commit/64533f6204f96f79c6006d9911e9417cd9800d0d)) +- correct debugger code ([759e532](https://github.com/anncwb/vue-vben-admin/commit/759e5320790504f0d274289001543c1397e8b617)) +- some color error ([33b2365](https://github.com/anncwb/vue-vben-admin/commit/33b2365f6e645edf2a6c1cf38596aaec52b35df6)) +- **description:** not rendering while show method return false ([#253](https://github.com/anncwb/vue-vben-admin/issues/253)) ([23eba27](https://github.com/anncwb/vue-vben-admin/commit/23eba274560a294f50e4b7c529ae8a63a266fb87)), closes [#252](https://github.com/anncwb/vue-vben-admin/issues/252) +- fix collapse header title not rendering ([#256](https://github.com/anncwb/vue-vben-admin/issues/256)) ([c81d48e](https://github.com/anncwb/vue-vben-admin/commit/c81d48e734b09217fa42df2358e616a970006eab)) +- **pop-confirm-button:** fix responsive failure [#246](https://github.com/anncwb/vue-vben-admin/issues/246) ([c57dea0](https://github.com/anncwb/vue-vben-admin/commit/c57dea0438fc5ba0fbf1716b9e76e2fba1f33f84)) +- fix the problem of mock error under post [#247](https://github.com/anncwb/vue-vben-admin/issues/247) ([9b6f37c](https://github.com/anncwb/vue-vben-admin/commit/9b6f37caef75f8752ea8bd07a78377dcaa59922b)) +- suppoer build sourcemap ([3ba8285](https://github.com/anncwb/vue-vben-admin/commit/3ba828558646a7fa233ebbbda27f71c3121dd7c7)) +- **type:** fix .vue file type error ([22088e8](https://github.com/anncwb/vue-vben-admin/commit/22088e820c79a9832179c8fb7c5cffe30b9b57e9)) +- **upload:** fix maxNumber not work [#240](https://github.com/anncwb/vue-vben-admin/issues/240) ([91e004e](https://github.com/anncwb/vue-vben-admin/commit/91e004e21148c38e572cfbb6b75f0a6f353c15b6)) + +### Features + +- added brotli|gzip compression and related test commands ([993538d](https://github.com/anncwb/vue-vben-admin/commit/993538de21dbb9e54e308afb40ff096ba0ab0e19)) +- support echarts 5.0 ([370b12f](https://github.com/anncwb/vue-vben-admin/commit/370b12f5154f4a531c3a27c3ccc2601845872344)) +- **modal:** exporse redoModalHeight ([a3a903b](https://github.com/anncwb/vue-vben-admin/commit/a3a903bc86e7248424f94f734d21c86c5327ed20)) + +### Performance Improvements + +- adjust the return value of the interface to obtain user information in array format [#259](https://github.com/anncwb/vue-vben-admin/issues/259) ([5894093](https://github.com/anncwb/vue-vben-admin/commit/589409305f58ebc2f6b110bd7b31f924ecd06c16)) +- remove unless code ([2365754](https://github.com/anncwb/vue-vben-admin/commit/23657547ab28fa65c2369ded8e73929dee76c750)) +- update style ([aaae668](https://github.com/anncwb/vue-vben-admin/commit/aaae66835a9f1bdfa316e187c01557e5b54959ab)) + +# [2.0.0-rc.18](https://github.com/anncwb/vue-vben-admin/compare/v2.0.0-rc.17...v2.0.0-rc.18) (2021-02-04) + +### Bug Fixes + +- **build:** fix rollup compact not work ([783e658](https://github.com/anncwb/vue-vben-admin/commit/783e65813d41ad9a3030412edede6f25f8f8cb49)) +- **descriotion:** fix type [#228](https://github.com/anncwb/vue-vben-admin/issues/228) ([4909a4c](https://github.com/anncwb/vue-vben-admin/commit/4909a4cb25ee62661e38cac38a8c3a388fdabbdf)) +- **form:** format destructuring assignment error ([#238](https://github.com/anncwb/vue-vben-admin/issues/238)) ([612995a](https://github.com/anncwb/vue-vben-admin/commit/612995a5326ef183d9f454059da6a2914ce5dd2f)) +- **menu:** fix the menu outside link does not jump ([55d4b77](https://github.com/anncwb/vue-vben-admin/commit/55d4b77b04d7a87b416a37019fbf047df1eeec41)) +- **menu:** top submenu disappeared problem [#214](https://github.com/anncwb/vue-vben-admin/issues/214) ([0ec1a62](https://github.com/anncwb/vue-vben-admin/commit/0ec1a62e596c363f3f017d6ac3b374a1b5caa7c5)) +- **modal:** fullscreen height calculation error [#203](https://github.com/anncwb/vue-vben-admin/issues/203) ([b45f8c5](https://github.com/anncwb/vue-vben-admin/commit/b45f8c5021a4225026ed698c083a1af42a08faff)) +- **moment:** fix moment error [#217](https://github.com/anncwb/vue-vben-admin/issues/217) ([61cf0f7](https://github.com/anncwb/vue-vben-admin/commit/61cf0f791e8ee05676fe7fa382b6a2c2b1bea92d)) +- **ripple:** fix ripple style [#211](https://github.com/anncwb/vue-vben-admin/issues/211) ([2201629](https://github.com/anncwb/vue-vben-admin/commit/22016291e4df206dbca351d00ae033c952276ebe)) +- **table:** fix the table: cancel editing and not restore the initial value [#235](https://github.com/anncwb/vue-vben-admin/issues/235) ([1d0ec36](https://github.com/anncwb/vue-vben-admin/commit/1d0ec3629f9cdd40c16b62ce61f9230dcd56a82f)) +- modifyVars not work ([b107b52](https://github.com/anncwb/vue-vben-admin/commit/b107b5288695130592a82951012b743fc825880f)) +- **optimize-deps:** fix resize-observer-polyfill error ([1fac4b4](https://github.com/anncwb/vue-vben-admin/commit/1fac4b4ba76d432b9a56e142a8d56571e825950f)) +- **simple-menu:** collapse openmenus error [#204](https://github.com/anncwb/vue-vben-admin/issues/204) ([ca4f1a8](https://github.com/anncwb/vue-vben-admin/commit/ca4f1a8faf7d588c0d57d0dc81f4dc04cd757380)) +- **table:** cell content does not wrap [#210](https://github.com/anncwb/vue-vben-admin/issues/210) ([ea93553](https://github.com/anncwb/vue-vben-admin/commit/ea9355398fe89235bf2e657c291541bd79a41d98)) +- **table:** fix the initial data display of editable cells ([#218](https://github.com/anncwb/vue-vben-admin/issues/218)) ([9ea257e](https://github.com/anncwb/vue-vben-admin/commit/9ea257e1fbd9e50369b0065eb4db37d4f9c24970)) +- **use-table:** fix types ([c889fb1](https://github.com/anncwb/vue-vben-admin/commit/c889fb174bbd8301479cd67ed99cb5f3552f9988)) +- error action style ([da64c1d](https://github.com/anncwb/vue-vben-admin/commit/da64c1dac95b96984283e496070ab9dc086dca4d)) + +### Features + +- production mode compressed image ([de332ae](https://github.com/anncwb/vue-vben-admin/commit/de332ae3f55afa611e86322753d5a713ea00307d)) +- theme color switch ([3d1681e](https://github.com/anncwb/vue-vben-admin/commit/3d1681ee9ae2b8e8a8f9d7afeaef3b059aa20b48)) +- vite preview ([c1a4600](https://github.com/anncwb/vue-vben-admin/commit/c1a4600b8a0f42c37d90c05198627062eb5742e2)) +- **api-select:** added numberToString prop [#200](https://github.com/anncwb/vue-vben-admin/issues/200) ([5d51d48](https://github.com/anncwb/vue-vben-admin/commit/5d51d48787f7b96637bc6abe5175578e0263092a)) + +### Performance Improvements + +- **form:** perf form in modal ([2882d6e](https://github.com/anncwb/vue-vben-admin/commit/2882d6e937a7d4996ae42ff62148d9a2f893fe47)) +- **mock:** when mock is not used, move mock.js out of the package file ([43503d5](https://github.com/anncwb/vue-vben-admin/commit/43503d597028926c93e4624d999cad4bbccc75fb)) +- **nprocess:** remove nprocess css ([733afdd](https://github.com/anncwb/vue-vben-admin/commit/733afddd19523550d8c7df5c523a0b0929afc608)) + +### Reverts + +- vite previre ([2eb2d2a](https://github.com/anncwb/vue-vben-admin/commit/2eb2d2a07529f7a33d2fbbf1e5fc2e1aac706b0f)) + +# [2.0.0-rc.17](https://github.com/anncwb/vue-vben-admin/compare/v2.0.0-rc.16...v2.0.0-rc.17) (2021-01-18) + +### Bug Fixes + +- **menu:** currentActiveMenu fails after refresh [#188](https://github.com/anncwb/vue-vben-admin/issues/188) ([6d5c49f](https://github.com/anncwb/vue-vben-admin/commit/6d5c49f0a208de5b745c36d2320dd4c2cffe7d75)) +- **menu-trigger:** menu-trigger lost ([b803c41](https://github.com/anncwb/vue-vben-admin/commit/b803c4100d5b40c04ae4c3b7153f7f8f32d7da81)) +- **mitt:** logout and clear the mitt ([0aeec5e](https://github.com/anncwb/vue-vben-admin/commit/0aeec5e9d727fc6291fa2d6edaedb4c3e1ef0dad)) +- **table:** index column value error [#187](https://github.com/anncwb/vue-vben-admin/issues/187) ([056fc13](https://github.com/anncwb/vue-vben-admin/commit/056fc131168c4e900e9257c3e03257a390c3d3ba)) +- **table:** tableAction icon [#182](https://github.com/anncwb/vue-vben-admin/issues/182) ([b9d53a7](https://github.com/anncwb/vue-vben-admin/commit/b9d53a7133de70922d6f2a0e16e5b623ffab84fb)) + +### Features + +- css import on demand ([c2f6542](https://github.com/anncwb/vue-vben-admin/commit/c2f6542b48abb85b2c80d13a36882899b11c140b)) + +### Performance Improvements + +- auto import mock file ([df6b5e9](https://github.com/anncwb/vue-vben-admin/commit/df6b5e926f3384a1c56e6607a39efcc4638e8dbc)) + +# [2.0.0-rc.16](https://github.com/anncwb/vue-vben-admin/compare/v2.0.0-rc.15...v2.0.0-rc.16) (2021-01-12) + +### Bug Fixes + +- **table:** table setting error [#174](https://github.com/anncwb/vue-vben-admin/issues/174) [#165](https://github.com/anncwb/vue-vben-admin/issues/165) ([c960020](https://github.com/anncwb/vue-vben-admin/commit/c9600208c52e3575fe8741e350833f7952bae3b7)) +- mock plugin error [#171](https://github.com/anncwb/vue-vben-admin/issues/171) ([3509ebe](https://github.com/anncwb/vue-vben-admin/commit/3509ebec165d26651cc02dc233bd9433c544bed5)) +- upload component not work [#169](https://github.com/anncwb/vue-vben-admin/issues/169) ([18ad1bc](https://github.com/anncwb/vue-vben-admin/commit/18ad1bcc6e927f70dc16bf7e3c1627c1f7f376f3)) +- useI18n type ([c22de5c](https://github.com/anncwb/vue-vben-admin/commit/c22de5c35b4781322c9ee17ad375ec0af2fe60a7)) +- **form:** formAction slot not work ([de5bf75](https://github.com/anncwb/vue-vben-admin/commit/de5bf757f241a097d62d61adf4d7346b73a09f92)) +- **layout:** fix layout scale error ([da76f3c](https://github.com/anncwb/vue-vben-admin/commit/da76f3c77bd044caaf65e2c7a5c1c9dd72b4ca44)) +- **modal:** height calc error [#161](https://github.com/anncwb/vue-vben-admin/issues/161) ([144ab57](https://github.com/anncwb/vue-vben-admin/commit/144ab577da06ff0bd1f258d1901b87864f232e45)) +- **table:** fix edit-table not work ([c031163](https://github.com/anncwb/vue-vben-admin/commit/c031163f34d7ec16aa5a7a406d5467a18e527c79)) +- **table:** fix table setting error [#162](https://github.com/anncwb/vue-vben-admin/issues/162) ([a2c89d2](https://github.com/anncwb/vue-vben-admin/commit/a2c89d2e842beb9f15f3fc00d651c42954a57ff7)) +- **table:** restore the property of the table ([5c27353](https://github.com/anncwb/vue-vben-admin/commit/5c2735346745cf91aa9812a0afbf62e4625faf40)) +- **table:** table columns setting error ([af55511](https://github.com/anncwb/vue-vben-admin/commit/af55511bd6e533ab68356aa9038f80f50f53cf26)) +- **table:** table columns setting will uncheck all render columns [#154](https://github.com/anncwb/vue-vben-admin/issues/154) ([aa596af](https://github.com/anncwb/vue-vben-admin/commit/aa596af608a313a5494db8e3ddbf0ef84c7f0c55)) +- **table:** table memory overflow ([7a07b70](https://github.com/anncwb/vue-vben-admin/commit/7a07b703d11afb832daa4bd2b87bf5cab3c61e04)) +- **transition:** fix transition not work ([a7a8b89](https://github.com/anncwb/vue-vben-admin/commit/a7a8b894c1062d8eb05a094fdbb7887044d0d973)) +- invalid error-log page path ([#158](https://github.com/anncwb/vue-vben-admin/issues/158)) ([17ecaea](https://github.com/anncwb/vue-vben-admin/commit/17ecaea97d1d4c61ddb79a23616a49598c9a10aa)) + +### Features + +- **tinymce:** add image upload [#170](https://github.com/anncwb/vue-vben-admin/issues/170) ([3ad1a4f](https://github.com/anncwb/vue-vben-admin/commit/3ad1a4f5a69b4242d55e6bc17aceab7279241e14)) +- added mixSide trigger ([1e5fcd2](https://github.com/anncwb/vue-vben-admin/commit/1e5fcd2cd2981b29f06cff08e588077b2dd02f45)) +- support vite2 ([eba5576](https://github.com/anncwb/vue-vben-admin/commit/eba55769ec765cd4fbf1faefdd4f3df5e38f11d9)) +- **layout:** added setting. Used to fix the left mixed mode menu ([97180e8](https://github.com/anncwb/vue-vben-admin/commit/97180e83f5055ebd138acc2a82c981d8a7399371)) +- **menu:** add mixSideTrigger setting ([0419a07](https://github.com/anncwb/vue-vben-admin/commit/0419a070413be34ea5455ed955fa51d8c522da86)) +- **modal:** add minHeight and height prop [#156](https://github.com/anncwb/vue-vben-admin/issues/156) ([5091a87](https://github.com/anncwb/vue-vben-admin/commit/5091a875ab520c51aec4c57cdd200d68016958ab)) +- **page-wrapper:** added pageWrapper component ([31ff055](https://github.com/anncwb/vue-vben-admin/commit/31ff0559fe3b635fc2091aac0e2f5e340629134c)) +- **table:** add summaryData prop [#163](https://github.com/anncwb/vue-vben-admin/issues/163) ([8d7d083](https://github.com/anncwb/vue-vben-admin/commit/8d7d0835adf4a7d1b8afc5e8bd911a60833006a4)) +- **tabs:** added tab folding ([0e7c57b](https://github.com/anncwb/vue-vben-admin/commit/0e7c57bd5ecafd8283bcc950b24bb63b59b70e5a)) + +### Performance Improvements + +- perf table ([cdf0a60](https://github.com/anncwb/vue-vben-admin/commit/cdf0a600e505daf429446b8a7968269e1034de04)) +- **i18n:** merge common lang ([efbde0d](https://github.com/anncwb/vue-vben-admin/commit/efbde0d57e20d07373d78d1226e2e83f396a74f3)) +- add @ant-design/icons-vue to optimizeDeps ([fb57cf7](https://github.com/anncwb/vue-vben-admin/commit/fb57cf734da31af94f3072c685b778a64fc740a5)) +- **menu:** mixSideTrigger setting ([#155](https://github.com/anncwb/vue-vben-admin/issues/155)) ([e821f4c](https://github.com/anncwb/vue-vben-admin/commit/e821f4c706c4108a4309a0589223e05e718f15cf)) + +# [2.0.0-rc.15](https://github.com/anncwb/vue-vben-admin/compare/v2.0.0-rc.14...v2.0.0-rc.15) (2020-12-31) + +### Bug Fixes + +- **build:** fix environment variable configuration file failure ([bd7b53f](https://github.com/anncwb/vue-vben-admin/commit/bd7b53f14adc05fd3d4af5027b5fb85015021ac9)) +- **charts:** fix echarts does not display after refresh [#140](https://github.com/anncwb/vue-vben-admin/issues/140) ([5cbfb2a](https://github.com/anncwb/vue-vben-admin/commit/5cbfb2a1f9ace8b991ac67c5b7d37b64eb2dbac8)) +- **demo:** fix demo error ([a0681cc](https://github.com/anncwb/vue-vben-admin/commit/a0681cca8f9de2e3686001fa715f53f6fc3cf1a1)) +- **form:** fix appendSchemaByField not work ([405d746](https://github.com/anncwb/vue-vben-admin/commit/405d7466dd935a845e91f4c6ece76b1475507eb7)) +- **form:** form validate error ([a305e59](https://github.com/anncwb/vue-vben-admin/commit/a305e59124f4cc88aaf6ec85a13fc998a18b9471)) +- **form:** form-item style error ([08df198](https://github.com/anncwb/vue-vben-admin/commit/08df198710ff597af2cbffa2afbb3a6ca13a1d63)) +- **iframe:** iframe loads early when closing multi-tabs ([73cee06](https://github.com/anncwb/vue-vben-admin/commit/73cee06daa26c056131fb5ec78afd912dd9832f7)) +- **locale:** fix locale.show not work ([10cd4fc](https://github.com/anncwb/vue-vben-admin/commit/10cd4fcdff2fa3961e095285ae7a26b38be52c2a)) +- **menu:** fix scrillbar not work ([de25557](https://github.com/anncwb/vue-vben-admin/commit/de25557f86945a96b89294043796ccf4ab476ad5)) +- **modal:** do not hide the scroll bar when opening the pop-up window [#151](https://github.com/anncwb/vue-vben-admin/issues/151) ([8f332e3](https://github.com/anncwb/vue-vben-admin/commit/8f332e3cd45814a181a24c884edf050936928755)) +- **sider:** mix mode adaptation in the left menu ([ed213d8](https://github.com/anncwb/vue-vben-admin/commit/ed213d878b78697f0bdb69cb474dfab45972b2cb)) +- **table:** Update useDataSource.ts ([#131](https://github.com/anncwb/vue-vben-admin/issues/131)) ([877311f](https://github.com/anncwb/vue-vben-admin/commit/877311f9df70b7d76f8a0f8b5082f061de439ec8)) +- **table:** wrong tag label style [#134](https://github.com/anncwb/vue-vben-admin/issues/134) ([e09e0a1](https://github.com/anncwb/vue-vben-admin/commit/e09e0a12531977d679ab0f4574f4016d4c5b2ad0)) +- **tinymce:** the editor reports an error under keep-alive [#152](https://github.com/anncwb/vue-vben-admin/issues/152) ([09c9f8a](https://github.com/anncwb/vue-vben-admin/commit/09c9f8a881d1f2c76b11fdeff08f3ca2893e0886)) +- **types:** fix routing type error [#145](https://github.com/anncwb/vue-vben-admin/issues/145) ([b6e5c3f](https://github.com/anncwb/vue-vben-admin/commit/b6e5c3f625f3e30b1fa7433e57b1294a8ce8d04b)) +- add an example of markdown embedded in the form [#138](https://github.com/anncwb/vue-vben-admin/issues/138) ([7db0c5c](https://github.com/anncwb/vue-vben-admin/commit/7db0c5c49f23a4ab4958b3f73d47516deafa6166)) + +### Features + +- **breadcrumb:** add breadcrumb demo [#143](https://github.com/anncwb/vue-vben-admin/issues/143) ([819bcbe](https://github.com/anncwb/vue-vben-admin/commit/819bcbe5263c721f1f77cb277d670a9868b229f7)) +- **hook:** add useKeyPress ([3c3e640](https://github.com/anncwb/vue-vben-admin/commit/3c3e640d69b48d8e9382acd25b60d906af038a9d)) +- add mainout page demo ([930383f](https://github.com/anncwb/vue-vben-admin/commit/930383f9ae17b18d697a35ef9c73ad17dbca1e13)) +- **layout:** add mix sidebar mode ([e6db0d3](https://github.com/anncwb/vue-vben-admin/commit/e6db0d39b9ba98f6396866715ed3b6d56994697a)) +- add ripple directive ([2e79c9f](https://github.com/anncwb/vue-vben-admin/commit/2e79c9f37adda4003e6b054561b26da69a762673)) + +### Performance Improvements + +- **form:** improve the form function ([ac1a369](https://github.com/anncwb/vue-vben-admin/commit/ac1a36950259844822c6300a00710b040dfc2640)) +- **import:** perf components import ([2ee01fa](https://github.com/anncwb/vue-vben-admin/commit/2ee01fa6ea3200ec964d4e1b4765e48dfa7aeb3a)) +- **modal-drawer:** replace the scrollbar assembly ([ebf7c8a](https://github.com/anncwb/vue-vben-admin/commit/ebf7c8aa53b7ed11c72734646d558a559e818473)) +- **route:** refactor guard ([3b126e0](https://github.com/anncwb/vue-vben-admin/commit/3b126e011c7ca7ac1b008c37aa2cf617242a2e9c)) +- Update useApexCharts.ts ([#139](https://github.com/anncwb/vue-vben-admin/issues/139)) ([5eecec0](https://github.com/anncwb/vue-vben-admin/commit/5eecec03126d131bd1210d4fcac3acfe3d5aeb40)) + +# [2.0.0-rc.14](https://github.com/anncwb/vue-vben-admin/compare/2.0.0-beta.3...v2.0.0-rc.14) (2020-12-15) + +### Bug Fixes + +- **form:** fix the form item setting not taking effect ([6936adb](https://github.com/anncwb/vue-vben-admin/commit/6936adb2c2af3c0bfbd238be1d61933601ff2b88)) +- **router:** reserving `Redirect` after reset ([#126](https://github.com/anncwb/vue-vben-admin/issues/126)) ([ec7efcf](https://github.com/anncwb/vue-vben-admin/commit/ec7efcf0f0161c8e14168bf69ba27ba36e2a1ac8)) +- fix modal and drawer component missing uid ([1293a73](https://github.com/anncwb/vue-vben-admin/commit/1293a7389ea797b1c1dad62e06657c846b1dcb3c)) +- **comp:** fix the memory overflow problem of component containing keywords ([6b3195b](https://github.com/anncwb/vue-vben-admin/commit/6b3195b4ca88a33044bcd014e8c5d090710e7fbb)) +- **form:** fix baseColProps not work ([c8ef82b](https://github.com/anncwb/vue-vben-admin/commit/c8ef82b2c11c9938f0f7a7f6a1a10010b82979dc)) +- **form:** fix form inputNumber verification error ([4ddee05](https://github.com/anncwb/vue-vben-admin/commit/4ddee05dee87c944ba95dca54a754e048b8cfc84)) +- **form:** fix form verification and console error message issues ([bb1b267](https://github.com/anncwb/vue-vben-admin/commit/bb1b267e2fc306608300ec09084b1f3d0cab7e59)) +- **icon:** fix g-icon not work ([f7ec3c9](https://github.com/anncwb/vue-vben-admin/commit/f7ec3c931e780b2b5d35bf65ea5b4ace26f7c356)) +- **keep-alive:** fix the problem that the multi-level routing cache page is rendered multiple times [#123](https://github.com/anncwb/vue-vben-admin/issues/123) ([0daca28](https://github.com/anncwb/vue-vben-admin/commit/0daca28362419911d642e4b3a5111e213eef49d9)) +- **login:** fix the problem of successful login and notify disappearing ([0434030](https://github.com/anncwb/vue-vben-admin/commit/0434030f2777ee65a4255287e1842fcb0b772f87)) +- **menu:** calc 0 不能省略单位 ([#124](https://github.com/anncwb/vue-vben-admin/issues/124)) ([d023fb1](https://github.com/anncwb/vue-vben-admin/commit/d023fb13742cc1f5cc1585b82f1a7b3c576ee66c)) +- **menu:** fix externalLink not work ([7bae4c3](https://github.com/anncwb/vue-vben-admin/commit/7bae4c37525c6534ec0b0c3ea8c1b2257af74a33)) +- **menu:** fix menu icon style ([1bc237d](https://github.com/anncwb/vue-vben-admin/commit/1bc237d77a068e99b0e803ab4f16d8bbcf54ff6b)) +- **menu:** fix menu split mode problem ([1ef49e5](https://github.com/anncwb/vue-vben-admin/commit/1ef49e542d23ca44696ec5dd2f6498a4ea8135aa)) +- **theme:** css filter breaking fixed position ([#125](https://github.com/anncwb/vue-vben-admin/issues/125)) ([c911af4](https://github.com/anncwb/vue-vben-admin/commit/c911af4aca49e6f9fe099e74a4d454286554e181)) +- 整体图标调整 ([5dc8226](https://github.com/anncwb/vue-vben-admin/commit/5dc8226ce14559f48f8b979809f8a054ce7935e5)) +- file upload key loss [#120](https://github.com/anncwb/vue-vben-admin/issues/120) ([29461a8](https://github.com/anncwb/vue-vben-admin/commit/29461a856826fbb7726848982387ea78f8573754)) +- **menu:** fix the calculation error of the top menu width ([de1f006](https://github.com/anncwb/vue-vben-admin/commit/de1f00628479c4d31e6ed904d4b0fd7e312cc030)) +- **table:** fix table setting error ([59ad824](https://github.com/anncwb/vue-vben-admin/commit/59ad82442bf213bac547940086ff4e14d0cd342a)) +- **table:** fix unsuccessful saving of row edit table ([#117](https://github.com/anncwb/vue-vben-admin/issues/117)) ([404db2f](https://github.com/anncwb/vue-vben-admin/commit/404db2fb4975c69851dbf73a9ea8f981fb0ddb56)) +- **upload:** fix file upload key loss [#120](https://github.com/anncwb/vue-vben-admin/issues/120) ([fb5395b](https://github.com/anncwb/vue-vben-admin/commit/fb5395b5401b4b1f9e605d2721784482a76d49cc)) +- **upload:** repair file upload and delete invalidation ([bd6b203](https://github.com/anncwb/vue-vben-admin/commit/bd6b203fa969d173574657940a50b649c778b0b4)) +- fix cssVar hmr error ([2b95be8](https://github.com/anncwb/vue-vben-admin/commit/2b95be8013e70e1b891601cecb6d9e03a56d1ac2)) +- fix descriotions title not work ([819127e](https://github.com/anncwb/vue-vben-admin/commit/819127e807123cccc7ae50f0fdffb43a662465d4)) +- fix form submit error ([94bf854](https://github.com/anncwb/vue-vben-admin/commit/94bf854dd98f37ffb39e9086c565a0610c250205)) +- fix form validate error ([1db72c8](https://github.com/anncwb/vue-vben-admin/commit/1db72c8fe13384f24e9cc1bdc839d5e4176ea9b4)) +- fix keepAlive not work ([b884654](https://github.com/anncwb/vue-vben-admin/commit/b884654761f93455014fd1dcb0e40c030d8fb360)) +- fix menu style not work ([bda3e5d](https://github.com/anncwb/vue-vben-admin/commit/bda3e5da30b434dd3a5879695261422fdd365455)) +- fix mock data error [#109](https://github.com/anncwb/vue-vben-admin/issues/109) ([41a4b82](https://github.com/anncwb/vue-vben-admin/commit/41a4b827a22e785453238da6b9b8b5b1c604b91a)) +- fix notify type error ([cb1ae34](https://github.com/anncwb/vue-vben-admin/commit/cb1ae34f1120d2555ff039fc945235c3f45e13a8)) +- fix spelling errors of i18n words ([68a96b7](https://github.com/anncwb/vue-vben-admin/commit/68a96b7f81a1ad72c93a53c2ebfde046c66c215f)) +- fix spin style ([fca0bb1](https://github.com/anncwb/vue-vben-admin/commit/fca0bb164a0f2e03acb5090bf59634225f5c06ee)) +- fix table column settings not displayed by setting ([54d1405](https://github.com/anncwb/vue-vben-admin/commit/54d14056462566521f2528480c13fb24279156ae)) +- fix the display problem of table icon ([de499a1](https://github.com/anncwb/vue-vben-admin/commit/de499a145556427304abe075b62e6869f44dc640)) +- fix the original page after login expired ([6676c95](https://github.com/anncwb/vue-vben-admin/commit/6676c9506be7b3095c466c83432d40b2a36565fb)) +- fix win system dynamicImport error ([a90d93f](https://github.com/anncwb/vue-vben-admin/commit/a90d93fc4d8dd8491702183f3db700c33dbcc5a8)) +- page switching did not return to the top ([fef3644](https://github.com/anncwb/vue-vben-admin/commit/fef3644067b7ccac96ec9ae122e3f1c8b8fc58ef)) +- pageLoading not working ([3f78b5a](https://github.com/anncwb/vue-vben-admin/commit/3f78b5aa0cd3e7a6f17d58512ca93ee2905d5e2f)) +- style error ([7bfe5f7](https://github.com/anncwb/vue-vben-admin/commit/7bfe5f753d77620027248a6238bccd8a23f7ad7c)) +- **charts:** fix useCharts resize not work ([6d9585b](https://github.com/anncwb/vue-vben-admin/commit/6d9585b46f849ea4cf3dc93d46f15c2c09d04891)) +- **form:** fix updateSchema error [#100](https://github.com/anncwb/vue-vben-admin/issues/100) ([4982786](https://github.com/anncwb/vue-vben-admin/commit/498278660112a52b7c6e608159d20920d6047e04)) +- 修复链接 ([#49](https://github.com/anncwb/vue-vben-admin/issues/49)) ([28392c3](https://github.com/anncwb/vue-vben-admin/commit/28392c3d6efc2fb3298255bc2c466167e8a4e91c)) +- fix editable cells cannot be entered ([4500214](https://github.com/anncwb/vue-vben-admin/commit/4500214b2a158965281e43e673622e4492e8ca26)) +- fix expandTransition ([3355066](https://github.com/anncwb/vue-vben-admin/commit/335506628e15e29e08df55d4b7e7cf6333fe25be)) +- fix fullscreen bg color not work ([#75](https://github.com/anncwb/vue-vben-admin/issues/75)) ([0c28ffa](https://github.com/anncwb/vue-vben-admin/commit/0c28ffa8e6a93e8923b7d3a32292db8ae786242c)) +- **table:** fix table typo ([69af37e](https://github.com/anncwb/vue-vben-admin/commit/69af37ec88e21acf926fdf5969c2189dc7450822)) +- fix menu permission failure ([b8353fe](https://github.com/anncwb/vue-vben-admin/commit/b8353fe1f262b87cc20af56aaf380ae1a5599724)) +- fix message type error ([35d2bfc](https://github.com/anncwb/vue-vben-admin/commit/35d2bfc5623fcf3a608ae12e9781b2e23ff4130d)) +- fix the problem of closing multiple tabs ([275ad9f](https://github.com/anncwb/vue-vben-admin/commit/275ad9f14e8fa75620ff35c906c06c616fb2104f)) +- **mock:** fix mock paging tool error ([b36d948](https://github.com/anncwb/vue-vben-admin/commit/b36d9486a544dd3badea23d86088af98aadad8f4)) +- **table:** fix table search criteria collapse failure ([84b8302](https://github.com/anncwb/vue-vben-admin/commit/84b8302c0921ea7fbcd1c42fa057b94660129857)) +- fix missing cache of refresh page ([02d6a39](https://github.com/anncwb/vue-vben-admin/commit/02d6a3940277a5939d25d16fda58e09346821e0e)) +- fix npm build error ([a3b7a65](https://github.com/anncwb/vue-vben-admin/commit/a3b7a6537ae25af076fdcccb50dd6967f0def40b)) +- fix table small style ([#67](https://github.com/anncwb/vue-vben-admin/issues/67)) ([da4aea1](https://github.com/anncwb/vue-vben-admin/commit/da4aea1399f67759b06266aa410036f69fde9521)) +- **table:** fix table type error ([05980a8](https://github.com/anncwb/vue-vben-admin/commit/05980a817e68c2a57eed2db7cf23bd7eb4ec10ba)) +- build error ([7bd0b8e](https://github.com/anncwb/vue-vben-admin/commit/7bd0b8eb6ffb143b4f341efeeb60b4e90f0e4ddf)) +- fix abnormal breadcrumb status ([144fde8](https://github.com/anncwb/vue-vben-admin/commit/144fde8a688217440071c7b0ac70e46f6832635a)) +- fix base-help style not work ([1fb759e](https://github.com/anncwb/vue-vben-admin/commit/1fb759ec7cf2c6104670025073920ca352413b10)) +- fix drawer autoHeight ([88de82c](https://github.com/anncwb/vue-vben-admin/commit/88de82c493b068b6d9bb5e29475350ed092fe482)) +- fix missing page refresh parameters ([349d197](https://github.com/anncwb/vue-vben-admin/commit/349d1978b154f6e9e74e36de7cc56a2ca261d0b0)) +- fix modal dragging failure when destroyOnClose=true ([#51](https://github.com/anncwb/vue-vben-admin/issues/51)) ([9c02d8e](https://github.com/anncwb/vue-vben-admin/commit/9c02d8ec08b309e7f982f417a4c907f33ccc96f0)) +- fix npm script ([b84de1a](https://github.com/anncwb/vue-vben-admin/commit/b84de1a515600d2ead1c2b5f6db949e7bf6ab923)) +- fix require error ([06e1d38](https://github.com/anncwb/vue-vben-admin/commit/06e1d3879be187f99f5142e054884e1c09ac8dfa)) +- fix routing switch, tab is not activated ([beb4c3a](https://github.com/anncwb/vue-vben-admin/commit/beb4c3a37f314b97657a1d85e7db2abf40dbe6c3)) +- fix script preview no build ([c2333f5](https://github.com/anncwb/vue-vben-admin/commit/c2333f5d044c74c9df82c6c3134681ba21e0d0cd)) +- fix table auto height ([ddc3786](https://github.com/anncwb/vue-vben-admin/commit/ddc3786b168a2931200ef61cc68dd80a18d714cc)) +- fix the failure of table expansion icon animation ([8e885d6](https://github.com/anncwb/vue-vben-admin/commit/8e885d6967747f3204e61ca85bde25ac2b8ba2a4)) +- fix the failure of table expansion icon animation ([db06289](https://github.com/anncwb/vue-vben-admin/commit/db06289481965524f42ed36a056bd54ba1a46dfe)) +- fix the problem of folding display name of the first level menu ([e3cbc93](https://github.com/anncwb/vue-vben-admin/commit/e3cbc9326ecedf386919f344df5dbdef8eb3d78c)) +- fix the problem of page blank caused by page refresh ([7653610](https://github.com/anncwb/vue-vben-admin/commit/7653610c7bc45e97cb744994835cf7fb5074ff7b)) +- fix the style problem of the table border in the production environment ([f2c7638](https://github.com/anncwb/vue-vben-admin/commit/f2c7638bd7789bddacd56ea2ab809f4a0b3b86cb)) +- fix the top menu adaptive failure ([2f12556](https://github.com/anncwb/vue-vben-admin/commit/2f12556d26ba386d9dca2ecf8a88e3764abab870)) +- fix window npm script ([a0b09e7](https://github.com/anncwb/vue-vben-admin/commit/a0b09e74baf1f4e514da85ed9b1859ca2820fb37)) +- form col style ([840332a](https://github.com/anncwb/vue-vben-admin/commit/840332abf733dd1dc404523d38c5377114f4b6c2)) +- some error ([2407b33](https://github.com/anncwb/vue-vben-admin/commit/2407b3368c3fc5128bbfced98a1d2c70fa3e02e0)) +- **modal:** fix modal not showing footer ([fb0c776](https://github.com/anncwb/vue-vben-admin/commit/fb0c7763eddde38d3746cb424ebe9662ac576c86)) +- **tree:** fix tree style ([#99](https://github.com/anncwb/vue-vben-admin/issues/99)) ([e8ccdc7](https://github.com/anncwb/vue-vben-admin/commit/e8ccdc7f34891ea31768aea9ebcfc33227d37eb7)) +- **use-redo:** refresh the page to keep the parameters([#104](https://github.com/anncwb/vue-vben-admin/issues/104)) ([e04aaa0](https://github.com/anncwb/vue-vben-admin/commit/e04aaa06459c6613e59aa6ae5906b998b0685bdb)) +- fix the disappearance of tab switching parameters ([#56](https://github.com/anncwb/vue-vben-admin/issues/56)) ([6bffdb5](https://github.com/anncwb/vue-vben-admin/commit/6bffdb5c64aa139cf6119b50aeed42629a65f07b)) +- fix the occupancy problem of the folding button ([#90](https://github.com/anncwb/vue-vben-admin/issues/90)) ([cd35d3e](https://github.com/anncwb/vue-vben-admin/commit/cd35d3e0d16cb57cb15c2ca20c8a663f21e4bfbf)) +- fix the problem of collapsed display when the menu has no child nodes ([5cff73b](https://github.com/anncwb/vue-vben-admin/commit/5cff73bcafc27a36f111949d33f463dd2bb52571)) +- fix topMenu align not work ([25d43a5](https://github.com/anncwb/vue-vben-admin/commit/25d43a5f7c9182f2ca620f1daf0d5f47d2e4fb2d)) +- fix useTimeoutFn not work ([b49950a](https://github.com/anncwb/vue-vben-admin/commit/b49950a3906de6626eedb973590d02e4d95b98b9)) +- hmr multiple registered components ([7a6181e](https://github.com/anncwb/vue-vben-admin/commit/7a6181e8c72cd110cdfc09f624f8be43e76ef74c)) +- repair local development post request proxy to https error problem ([#63](https://github.com/anncwb/vue-vben-admin/issues/63)) ([34c09fc](https://github.com/anncwb/vue-vben-admin/commit/34c09fcea82e3529519a5acc563a22adcd5faae1)) +- repair packaging error ([526e6ce](https://github.com/anncwb/vue-vben-admin/commit/526e6ce22bf15cd04a09faf53a08ac43da491534)) +- Repair tree component click to select ([#33](https://github.com/anncwb/vue-vben-admin/issues/33)) ([67df9b8](https://github.com/anncwb/vue-vben-admin/commit/67df9b8c93a26b0edb4f3d5d5c589d355803cea0)) +- replace taskfile module ([e828baa](https://github.com/anncwb/vue-vben-admin/commit/e828baa67b5f8e6fa28354d85563d127b6b70d6b)) +- reset back to default value after fixing form query ([1c075a7](https://github.com/anncwb/vue-vben-admin/commit/1c075a7a32dd05454bc45d4eb686e2234c3c6175)) +- the action column appears repeatedly in the table ([#53](https://github.com/anncwb/vue-vben-admin/issues/53)) ([74d4742](https://github.com/anncwb/vue-vben-admin/commit/74d47424069c4dca71579637916431aa80014fd8)) +- the login tab page in tabs ([#60](https://github.com/anncwb/vue-vben-admin/issues/60)) ([bfac425](https://github.com/anncwb/vue-vben-admin/commit/bfac425d1e12943b55e9afb732a36d84f6a02404)) +- the useMessage icon style problem ([a2c413a](https://github.com/anncwb/vue-vben-admin/commit/a2c413a838bb3f737e28e95302ccf0a0171c91b6)) +- type error ([ecfb702](https://github.com/anncwb/vue-vben-admin/commit/ecfb702b09e296efd5bf095d65840147d47b7923)) +- typo ([7658f4d](https://github.com/anncwb/vue-vben-admin/commit/7658f4d6e82dc532b378ec13157756f0e1cd78de)) +- update account page demo ([#92](https://github.com/anncwb/vue-vben-admin/issues/92)) ([9f8796e](https://github.com/anncwb/vue-vben-admin/commit/9f8796ee586a5f33e20713f53d2aa447b6aa312e)) +- update upload component ([815250e](https://github.com/anncwb/vue-vben-admin/commit/815250ed341ccaec23e7ea34db6cc478a47ad065)) +- **excel:** update excel demo ([a207caf](https://github.com/anncwb/vue-vben-admin/commit/a207cafec98461b39882f352f2bf5c7d3c21716a)) +- **table:** fix table actionColOptions not work ([5a6db8c](https://github.com/anncwb/vue-vben-admin/commit/5a6db8c640376ca67b451a9647b9958946e5c3ab)) +- **table:** fix table type error ([db0bfc8](https://github.com/anncwb/vue-vben-admin/commit/db0bfc886314b193e7cb86a80b6c13b2743aa652)) +- **table:** fix the problem that multi-level header configuration does not take effect ([cdf2c59](https://github.com/anncwb/vue-vben-admin/commit/cdf2c59e5c3b070d039c04fb746b53147f5e0ced)) +- **tinymce:** fixed multiple editors showing only one ([#83](https://github.com/anncwb/vue-vben-admin/issues/83)) ([1093ec3](https://github.com/anncwb/vue-vben-admin/commit/1093ec3e6e4fe1f49b7458c29e518744fe56532f)) + +### Features + +- add account center page ([#86](https://github.com/anncwb/vue-vben-admin/issues/86)) ([78d4d41](https://github.com/anncwb/vue-vben-admin/commit/78d4d41c85f5341bb5dfd2a1cbb6e60d6858b084)) +- add accountSetting page ([#85](https://github.com/anncwb/vue-vben-admin/issues/85)) ([7ad4cee](https://github.com/anncwb/vue-vben-admin/commit/7ad4cee79ade617a13358f7417ce3e1182c1027f)) +- add basic-list page ([2f75a94](https://github.com/anncwb/vue-vben-admin/commit/2f75a948899713e10b200e0f39a48d4b62ef231e)) +- add card-list page ([3a132f3](https://github.com/anncwb/vue-vben-admin/commit/3a132f3f4f4e08b4675c157548aa093b3a1c3c94)) +- add collapsedShowTitle setting ([5737e47](https://github.com/anncwb/vue-vben-admin/commit/5737e478f671e7f1c60f7db08a0007f154b6f4b8)) +- add count-to component and demo ([afc7263](https://github.com/anncwb/vue-vben-admin/commit/afc7263efb90c0410041358a9dd5f10ec685ac2f)) +- add design setting ([bae53f3](https://github.com/anncwb/vue-vben-admin/commit/bae53f3e2c62b3fca246432307f45a6363c4c176)) +- add error handle ([7101587](https://github.com/anncwb/vue-vben-admin/commit/7101587b9676c91e9079044a096df08848f1f602)) +- add file download demo ([db3092d](https://github.com/anncwb/vue-vben-admin/commit/db3092db2eb7d5346778847757adb2b9c4041ed5)) +- add lazyContainer comp and demo ([fdeaa00](https://github.com/anncwb/vue-vben-admin/commit/fdeaa00bf24b0710ca341fafba8327c786ab9879)) +- add markdown component ([5fb069f](https://github.com/anncwb/vue-vben-admin/commit/5fb069f432799e0d17a7102fae70757e320dc0c5)) +- add notice ([#47](https://github.com/anncwb/vue-vben-admin/issues/47)) ([7a1e94c](https://github.com/anncwb/vue-vben-admin/commit/7a1e94c49d546e155d8c17b492ff6b1e5fb55121)) +- add permissionCacheType setting ([26b6109](https://github.com/anncwb/vue-vben-admin/commit/26b6109ca08a28c37355474bf8593f2e2b741ef6)) +- add pwa ([a1b9902](https://github.com/anncwb/vue-vben-admin/commit/a1b9902b97da03d0ee1e99a021fc6497b8f51fa6)) +- add README.en-US.md ([#37](https://github.com/anncwb/vue-vben-admin/issues/37)) ([7437896](https://github.com/anncwb/vue-vben-admin/commit/74378960345e706b45fab1f39fba045a1e95a547)) +- add result page demo ([21e0548](https://github.com/anncwb/vue-vben-admin/commit/21e0548e34cf70ebf97967089f458e759ca326d9)) +- add search page ([dddda5b](https://github.com/anncwb/vue-vben-admin/commit/dddda5b296025d1d6b37ec15930a02722b8e1b0c)) +- add search-list page ([4cb3784](https://github.com/anncwb/vue-vben-admin/commit/4cb3784f13fc516c6343798e8bf8a435e14d774c)) +- add tab drag and drop sort ([cedba37](https://github.com/anncwb/vue-vben-admin/commit/cedba37e4cf63456c97f7e391761f176137e0165)) +- add table setting ([8b3a4d3](https://github.com/anncwb/vue-vben-admin/commit/8b3a4d37a8addd151b918cf64bce6361376dec9e)) +- add tag display to the menu ([a3887f8](https://github.com/anncwb/vue-vben-admin/commit/a3887f8cd99546cde8882d77271cc430eb7a83f5)) +- add the parameter sortFn to the table ([491ba9a](https://github.com/anncwb/vue-vben-admin/commit/491ba9a3cc19ceb97dd9a6448831b64c86e1e475)) +- add the parameter submitOnReset to the form ([#54](https://github.com/anncwb/vue-vben-admin/issues/54)) ([d09406e](https://github.com/anncwb/vue-vben-admin/commit/d09406e3cb8cfc069ce79b5f4194f7d959f63daf)) +- add tinymce embedded form example ([58f988a](https://github.com/anncwb/vue-vben-admin/commit/58f988a7184dd7bdec415627e16b56b80f36b661)) +- add useDesign ([74e62cb](https://github.com/anncwb/vue-vben-admin/commit/74e62cbc712bdd4d4826e5fe80f537d87e44ffce)) +- added base64 file stream download ([a161bfa](https://github.com/anncwb/vue-vben-admin/commit/a161bfa818cb63d9cc0b00ae062eb16b1efaf74f)) +- auto import route ([8a1bfdf](https://github.com/anncwb/vue-vben-admin/commit/8a1bfdf13de966acc5eb41718ccb085d3efc4581)) +- axios add joinTime field ([f646e37](https://github.com/anncwb/vue-vben-admin/commit/f646e37754d21ba7c89437176bd9e375924dee03)) +- first screen loading waiting animation ([4811cce](https://github.com/anncwb/vue-vben-admin/commit/4811cce809453df78dc2c25cd9805eae483297fc)) +- global loading add text ([4f98978](https://github.com/anncwb/vue-vben-admin/commit/4f98978edacbe72610a226267628ab20b57cfc4e)) +- integrate upload components into form by default ([be2b8a7](https://github.com/anncwb/vue-vben-admin/commit/be2b8a7e175033dace7a521ab26cd319c5cfdea6)) +- multi-language component ([dc09de1](https://github.com/anncwb/vue-vben-admin/commit/dc09de1e052d6b104c5af3a426af6b0e7bb147c7)) +- multi-language layout ([e5f8ce3](https://github.com/anncwb/vue-vben-admin/commit/e5f8ce3fd8ec25c6fdb122867cd33e4e84a6f43f)) +- multi-language support ([1901129](https://github.com/anncwb/vue-vben-admin/commit/19011296ed61f820356f6b201cbb274d57dcb7d3)) +- new menu and top bar color selection color matching ([7692ffb](https://github.com/anncwb/vue-vben-admin/commit/7692ffb95b94672b6fbc8c25fd43d9dd1a1da81e)) +- projectSetting add closeMessageOnSwitch and removeAllHttpPending ([e83cb06](https://github.com/anncwb/vue-vben-admin/commit/e83cb06bb93544369c8934d1065bf46835e3f003)) +- restore the breadcrumb display icon function ([f65bed7](https://github.com/anncwb/vue-vben-admin/commit/f65bed72ac8c63aaed640d59703f73e83de80da5)) +- right-click menu supports multiple levels ([f645680](https://github.com/anncwb/vue-vben-admin/commit/f645680a3b9a1f75395329970551d9e5d6bd845b)) +- routes with parameters can be cached ([90b3fab](https://github.com/anncwb/vue-vben-admin/commit/90b3fab28ef53135f3cab1f69a4675f98a130857)) +- support mobile layout adaptation ([c774a6d](https://github.com/anncwb/vue-vben-admin/commit/c774a6d3a03d9507a9023d600aa9dd9592f52fb3)) +- support vscode i18n-ally plugin ([962f90d](https://github.com/anncwb/vue-vben-admin/commit/962f90de445d7935ad76ea7b74a98f12ce9a7498)) +- the cache can be configured to be encrypted ([234c1d1](https://github.com/anncwb/vue-vben-admin/commit/234c1d1fae6a7f2c78e456f992f91622ca599060)) +- **analysis:** add analysis page ([52ee35c](https://github.com/anncwb/vue-vben-admin/commit/52ee35c4beca8fc07737aa28328663e86ba797d4)) +- **breadcrumb:** support showIcon ([#48](https://github.com/anncwb/vue-vben-admin/issues/48)) ([d8b25b4](https://github.com/anncwb/vue-vben-admin/commit/d8b25b488ba4c6626d3b94ed84270e96f403d859)) +- **chart:** add useEcharts and useApexChart demo ([21d0ed9](https://github.com/anncwb/vue-vben-admin/commit/21d0ed92dffd28f45c98afee547d25d9b40dde7f)) +- **desc-page:** add desc page demo ([7a00036](https://github.com/anncwb/vue-vben-admin/commit/7a000366b92b942727dd2cd7c0aec193f8c1a7b0)) +- **excel:** import/export ([#40](https://github.com/anncwb/vue-vben-admin/issues/40)) ([c0692b0](https://github.com/anncwb/vue-vben-admin/commit/c0692b0f43b50be56e399c4aa07c0c4244080e9f)) +- **form:** support function type of form item ([5832ee6](https://github.com/anncwb/vue-vben-admin/commit/5832ee6697e23afefc25ba2aa4df9476b5034bf4)) +- **form-page:** add form page demo ([0b6110a](https://github.com/anncwb/vue-vben-admin/commit/0b6110a8fc92a11df6501346e093246a5abe2b0e)) +- **from:** add required in schema ([2859067](https://github.com/anncwb/vue-vben-admin/commit/28590676214b1c5fdbf6878e40da45a7bc0c5874)) +- **tinymce:** add line height ([#58](https://github.com/anncwb/vue-vben-admin/issues/58)) ([adffefd](https://github.com/anncwb/vue-vben-admin/commit/adffefd702688ba5fa8c5df616b8f3685a0fb778)) +- **tinymce:** add rich editor ([c0e4c9e](https://github.com/anncwb/vue-vben-admin/commit/c0e4c9e5a55524840e9598d24d84dcada8b57102)) +- **transition:** add transition comp and demo ([3713487](https://github.com/anncwb/vue-vben-admin/commit/3713487c85f4b512ab3e13fcb4c89a14b9ee8d50)) +- **trigger:** add trigger config ([4f6b65b](https://github.com/anncwb/vue-vben-admin/commit/4f6b65b8a1b7e694718b4aa42aced1e59e90ec9e)) +- the Button component extends the and attributes ([8f5016e](https://github.com/anncwb/vue-vben-admin/commit/8f5016e3f3476539a763162ea235cf2aac230eea)) +- the production environment can be dynamically configured ([bb3b8f8](https://github.com/anncwb/vue-vben-admin/commit/bb3b8f817de15d336968354515649f7142cd5683)) +- **workbench:** add workbench page ([1cd75fc](https://github.com/anncwb/vue-vben-admin/commit/1cd75fcf5ba7a3114399db8f22cf8eb6f2e4d783)) + +### Performance Improvements + +- **setting-drawer:** perf setting-drawer ([ed41e50](https://github.com/anncwb/vue-vben-admin/commit/ed41e5082fd2e6109c2ad3ff77199d15ac14342a)) +- **tabs:** perf multiple-tabs ([f81c401](https://github.com/anncwb/vue-vben-admin/commit/f81c401959dda4b8d568c00786b691c21abbb59c)) +- **tabs:** perf multiple-tabs ([27e50b4](https://github.com/anncwb/vue-vben-admin/commit/27e50b47479af8eaeb4be020aeb0fcbdb4308295)) +- Add the style injection of the top row to the form. ([#102](https://github.com/anncwb/vue-vben-admin/issues/102)) ([b9d3d60](https://github.com/anncwb/vue-vben-admin/commit/b9d3d60e0f8fe1166a0addcc8295365cbe65a7bf)) +- adjust the logic of ([b350098](https://github.com/anncwb/vue-vben-admin/commit/b350098f442be1b8143b44e09e735179676f755c)) +- code style ([f96d6b2](https://github.com/anncwb/vue-vben-admin/commit/f96d6b221c7ad97e0ed80250acb192b6be92c4a6)) +- enhance openModal and openDrawer ([b6d5e5c](https://github.com/anncwb/vue-vben-admin/commit/b6d5e5c96f89c31d4df11e71f2d4cb5ecf8f0b92)) +- layout code adjustment ([4392917](https://github.com/anncwb/vue-vben-admin/commit/439291746fe237410140575be2a634a74e8ef382)) +- layout style optimization ([7702832](https://github.com/anncwb/vue-vben-admin/commit/77028321816f00799cc3f70d3f0d6bde27c34522)) +- mobile style adjustment ([1899146](https://github.com/anncwb/vue-vben-admin/commit/1899146f71ab2020dc01bd84b282e6b614ad3d57)) +- optimize lazy loading components ([87fcd0d](https://github.com/anncwb/vue-vben-admin/commit/87fcd0d21ea78ce916a4f2b9cdcceda5e7866eee)) +- optimize multiple-tab switching effect ([f2bdf0b](https://github.com/anncwb/vue-vben-admin/commit/f2bdf0b86dd818f3cc59fdb0c55eb1b53b222f7f)) +- optimize preview and ContextMenu functions ([bbfb06f](https://github.com/anncwb/vue-vben-admin/commit/bbfb06f0ad1e345b0e716da730acaf7c0a778e4b)) +- optimize settingDrawer code ([4ff6b73](https://github.com/anncwb/vue-vben-admin/commit/4ff6b73c2bb57764db2bcd8212d82f028e25e36d)) +- optimize tab switching speed ([4baf90a](https://github.com/anncwb/vue-vben-admin/commit/4baf90a5c87493939830129efaa146624faabbcc)) +- optimize the size of the first screen ([968f791](https://github.com/anncwb/vue-vben-admin/commit/968f791f4b7112730813c8c990379051c3f8340d)) +- optimized page switching effect ([5f2a927](https://github.com/anncwb/vue-vben-admin/commit/5f2a927cd50a5efe4c9576528d944553c5243277)) +- perf component ([73c8e0c](https://github.com/anncwb/vue-vben-admin/commit/73c8e0c1583afa83353ff36d1d9ec847776d3016)) +- perf context menu ([6e03e05](https://github.com/anncwb/vue-vben-admin/commit/6e03e05032474c858151b3835eb5318486a56729)) +- perf excel comp code ([eecde4c](https://github.com/anncwb/vue-vben-admin/commit/eecde4c7e947cf392dbd8eace2db8ed9aea417b1)) +- perf loading logic ([f4621cc](https://github.com/anncwb/vue-vben-admin/commit/f4621cc66411d8ff4ca852b548a79cd3da9be1ce)) +- perf menu ([88f4a3f](https://github.com/anncwb/vue-vben-admin/commit/88f4a3f02a0c0f35953c93427fe700d414b6ec50)) +- perf menu mini style ([66acb21](https://github.com/anncwb/vue-vben-admin/commit/66acb21edda3fcac61849c7c03c6b396992d8d06)) +- perf modal and drawer ([81baf1d](https://github.com/anncwb/vue-vben-admin/commit/81baf1d5c4606aab83c0e65397ce4b090c2e4e08)) +- tsx use useExpose ([9bb7514](https://github.com/anncwb/vue-vben-admin/commit/9bb751475dc212d4e2829468cf1a11502137071e)) +- **button:** delete the button component useless code ([bdce845](https://github.com/anncwb/vue-vben-admin/commit/bdce84537aa58b9507744a3a14c8d598e88e95fc)) +- **drawer:** perf drawer ([28f7f7b](https://github.com/anncwb/vue-vben-admin/commit/28f7f7bf7f3ae49759b44395f6b06c2c61359d04)) +- **lazy-container:** optimize lazyContainer code ([0f4b847](https://github.com/anncwb/vue-vben-admin/commit/0f4b847d69e90e5bbb4fb0883fb5ea1dd1daf1e7)) +- **logo:** optimize logo code ([e79e540](https://github.com/anncwb/vue-vben-admin/commit/e79e540b48be80fb08b67a99e64bede3816b2c9e)) +- **menu:** optimize layout menu ([96c10d6](https://github.com/anncwb/vue-vben-admin/commit/96c10d6c0fb46b56b0e74e09a8e20bcfc9f54cde)) +- **modal:** optimize table embedding height calculation problem ([9abf176](https://github.com/anncwb/vue-vben-admin/commit/9abf1763c78ead7de21ece6d328337a6a1da5f05)) +- **strength-meter:** modify name word ([#38](https://github.com/anncwb/vue-vben-admin/issues/38)) ([19477cd](https://github.com/anncwb/vue-vben-admin/commit/19477cd980661ace337ec6e3295f76c44d05763c)) +- **table:** optimize effect performance ([a1ffb61](https://github.com/anncwb/vue-vben-admin/commit/a1ffb61804940f1ebaea741b0df41485ad95d5f2)) +- **upload:** improve upload component ([661db0c](https://github.com/anncwb/vue-vben-admin/commit/661db0c767772bb7a30da9d3eeaf2b47858ccf0b)) +- **use-message:** fix typo ([bcab4b7](https://github.com/anncwb/vue-vben-admin/commit/bcab4b774d384a5de9b87a0c700a9937c79eb5cd)) +- perf TableAction ([4b384b1](https://github.com/anncwb/vue-vben-admin/commit/4b384b137c58428f0cf28621e183250da4576479)) +- performance optimization ([70fba7e](https://github.com/anncwb/vue-vben-admin/commit/70fba7ecac80a1cd8ec08052e8265641f2b56204)) +- pwa icon ([404c73d](https://github.com/anncwb/vue-vben-admin/commit/404c73de450c165ffe623ca2969322bae1786a73)) +- remove optional chain ([e034d1b](https://github.com/anncwb/vue-vben-admin/commit/e034d1bacc5501a83188d20129951422bc127e3b)) +- review tinymce code ([f75425d](https://github.com/anncwb/vue-vben-admin/commit/f75425d13bc9f6003021fd4b5d6451ae096c09b7)) +- set cache default time ([c620f82](https://github.com/anncwb/vue-vben-admin/commit/c620f8279f1056ddab84b3907fb50b3af4fe9247)) +- tabs optimization ([6e40051](https://github.com/anncwb/vue-vben-admin/commit/6e4005111db58ca10f10e9aa4bca4aec57363736)) +- the existing tab switching no longer displays animation and processbar ([e9536b5](https://github.com/anncwb/vue-vben-admin/commit/e9536b5b7ccc5f667496c4ec7ab838738f804a71)) +- the routeModule can ignore the layou configuration without writing ([4c658f4](https://github.com/anncwb/vue-vben-admin/commit/4c658f4868c7df6e0b8f18728c5d5ae53b04448a)) +- update form types ([a0c3197](https://github.com/anncwb/vue-vben-admin/commit/a0c3197454b59a231cf6d27048b2e9c0bd7bf77f)) + +### Reverts + +- **table:** revert form type annotation ([261936b](https://github.com/anncwb/vue-vben-admin/commit/261936b117d1d261ecb8fafc0f6c839cb2913918)) + +# [2.0.0-beta.3](https://github.com/anncwb/vue-vben-admin/compare/2.0.0-beta.2...2.0.0-beta.3) (2020-10-07) + +### Features + +- **setting:** add openNProgress setting ([67d0ff0](https://github.com/anncwb/vue-vben-admin/commit/67d0ff0e251f584883d50fd71b2413b6ca94729d)) +- **table:** add table component ([faf3f46](https://github.com/anncwb/vue-vben-admin/commit/faf3f4602ecf4b16ff57994668edc8433a43945d)) + +# [2.0.0-beta.2](https://github.com/anncwb/vue-vben-admin/compare/2.0.0-beta.1...2.0.0-beta.2) (2020-10-07) + +### Features + +- **img-preview:** add imgPreview componnt ([e6093aa](https://github.com/anncwb/vue-vben-admin/commit/e6093aa4f48f3b3c16b1640c56512e6e3cf84c4b)) + +# [2.0.0-beta.1](https://github.com/anncwb/vue-vben-admin/compare/2f268ca8f43d98687ffd809e2c1d140d29033bd6...2.0.0-beta.1) (2020-09-30) + +### Bug Fixes + +- fix form,transition,build bug ([2f268ca](https://github.com/anncwb/vue-vben-admin/commit/2f268ca8f43d98687ffd809e2c1d140d29033bd6)) diff --git a/CHANGELOG.zh_CN.md b/CHANGELOG.zh_CN.md new file mode 100644 index 00000000000..acf8957ea2e --- /dev/null +++ b/CHANGELOG.zh_CN.md @@ -0,0 +1,1317 @@ +## 2.8.0(2021-11.03) + +### 升级说明 + +- 包管理器由`yarn`改为 `pnpm` +- 删除`node_modules`和`yarn.lock`,全局安装`pnpm` +- 执行`pnpm install` + +### ✨ Features + +- **其它** + - `.env`文件中的`VITE_PROXY`配置支持单引号 + - 移除 build 过程中的警告 + +### 🐛 Bug Fixes + +- **BasicTable** + - 修复可编辑单元格某些情况下无法提交的问题 + - 修复`inset`属性不起作用的问题 + - 修复`useTable`与`BasicTable`实例的`reload`方法`await`表现不一致的问题 + - 修复`clickToRowSelect`会无视行选择框 disabled 状态的问题 + - 修复`BasicTable`在某些情况下,分页会被重置的问题 + - 修改 `deleteTableDataRecord` 方法 +- **BasicModal** + - 修复点击遮罩、按下`Esc`键都不能关闭`Modal`的问题 + - 修复点击关闭按钮、最大化按钮旁边的空白区域也会导致`Modal`关闭的问题 +- **BasicTree** 修复节点插槽不起作用的问题 +- **CodeEditor** 修复可能会造成的`Build`失败的问题 +- **BasicForm** 修复自定义 FormItem 组件的内容宽度可能超出范围的问题 +- **ApiTreeSelect** 修复`params`变化未能触发重新请求 api 数据的问题 +- **其它** + - 修复多标签在某些情况下关闭页签不会跳转路由的问题 + - 修复部分组件可能会造成热更新异常的问题 + - 修复直接`import`部分`antdv`子组件时会在 build 过程中报错的问题,如:TabPane、RadioGroup + +## 2.7.2(2021-09-14) + +### ✨ Features + +- **BasicForm** 表单组件新增`Divider`,用于较长表单的区域分割 +- **BasicTable** + - 单元格编辑新增提交回调,将根据回调函数返回的结果来决定是否将数据提交到表格 + - 行编辑添加校验方法,允许只校验而不提交值,以便异步保存数据成功后才提交倒表格 + - 修复`rowClassName`属性无法和`striped`同时使用的问题 +- 新增组件 **MarkdownViewer** 用于显示 Markdown 格式的富文本 + +### 🐛 Bug Fixes + +- **CodeEditor** 修复 JSON 编辑器在格式化无效 JSON 文本时会抛出异常的问题 +- **Tinymce** 修复 inline 模式在一些场景下会出现异常的问题 +- **BasicTable** + - 修复可编辑单元格的内容为空时,不会显示编辑图标的问题 + - 修复表尾合计行与表格主体部分的列有时候未能对齐的问题 +- **MarkDown** 修复初始 value 属性的值不起作用的问题 +- **BasicUpload** 修复`accept`属性不支持`MIME`及点开头的后缀名的问题 +- **ApiSelect** 修复`value`属性的类型定义问题 +- **其它** + - 修复部分封装组件在使用插槽时报错的问题 + - 修复`useECharts`的`theme`参数不起作用的问题 + - 修复`Token`失效时,按 F5 刷新页面可能会出现页面加载异常的问题 + - 修复`useRedo`的不当调用可能会导致重定向`path`异常的问题 + - 修复`vite`自定义模式名称不支持下划线的问题 + +## 2.7.1(2021-08-16) + +- 升级 vue 3.2,如果运行失败,删除 node_modules 后重装即可 + +### ✨ Features + +- **BasicTree** 添加搜索功能相关属性和方法 +- **BasicForm** 新增`alwaysShowLines`用于设置折叠时保留显示的行数 + +### 🐛 Bug Fixes + +- **Cropper** 修复未能及时销毁的问题 +- **BasicTable** + - 修复`CellFormat`无法使用`Map`类型数据的问题 + - 修复可编辑单元格未能正确显示`0`值的问题 + - 修复 selection-change 事件在取消勾选时未能正确触发的问题 + - 修复浅色主题下的全屏状态背景颜色不正确的问题 + - 修复`getSelectRows`不支持远程数据跨页选择时获取完整数据的问题 + - 修复在`editComponentProps`中为编辑组件提供的`size`属性无效的问题 +- **Qrcode** 修复二维码组件在创建时未能及时绘制的问题 +- **BasicModal** 修复`helpMessage`属性不起作用的问题 +- **BasicButton** 修复按钮样式表现与 antd 官方不一致的问题 +- **其它** 修复`useRedo`(重新加载当前路由)会丢失路由`params`数据的问题 + +## 2.7.0(2021-08-03) + +## (破坏性更新) Breaking changes + +- 将项目`tailwindcss`还原回`windicss`,尝试了`tailwindcss`,问题可能还挺多,先切换回`windicss`提高开发效率,切换成本较低。 + - 目前项目不兼容地方有 + - `xl:!m-4` 之类的写法需要改为`!xl:m-4`,注意只有`!`这个不兼容,没用到则不用改 + - 内存溢出问题可能还在(频率低,重启下即可,重启 vite 较快) + +### ✨ Features + +- **Preview** 添加新的属性及事件 +- **Dark Theme** 新增对 tailwindcss 夜间模式的支持 +- **其它** 为 useLoading 添加 setTip 方法 + +### 🐛 Bug Fixes + +- **ApiTreeSelect** 修复未能正确监听`params`变化的问题 +- **ImgRotateDragVerify** 修复组件`resume`方法无法调用的问题 +- **TableAction** 修复 stopButtonPropagation 属性某些情况下不起作用的问题 +- **PageWrapper** 修复`class`属性无效的问题 +- **BasicTree** 修复`checkAll`方法会影响到`disabled`状态节点的问题 +- **BasicTable** + - 修复可编辑单元格不支持`ellipsis`配置的问题 + - 修复全屏模式下看不到子组件弹出层(popconfirm 以及 select、treeSelect 等编辑组件)的问题 + - 修复启用`expandRowByClick`时,点击不可展开的行可能会导致样式错误的问题 + - 修复`pagination`属性动态改变不生效的问题 + - 修复`getSelectRows`不支持树形表格子级数据的问题 +- **Dark Theme** 黑暗主题下的配色问题修正 + - 修复`Tree`组件被选中节点的背景颜色 + - 修复`Alert`组件的颜色配置 + - 修复禁用状态下的`link`类型的按钮颜色问题 + - 修复`Tree`已勾选的复选框的样式问题 +- **其它** 修复 useScript 未能自动移除 script 节点的问题 + +## 2.6.1(2021-07-19) + +### ✨ Features + +- **NoticeList** 添加分页、超长自动省略、标题点击事件、标题删除线等功能 +- **MixSider** 优化 Mix 菜单布局时 底部折叠按钮 的样式,与其它菜单布局时的风格保持一致 +- **ApiTreeSelect** 扩展`antdv`的`TreeSelect`组件,支持远程数据源,用法类似`ApiSelect` +- **BasicTable** + - 新增`ApiTreeSelect`编辑组件 + - 新增`headerTop`插槽 +- **其它** 可以为不同的用户指定不同的后台首页: + - 在`getUserInfo`接口返回的用户信息中增加`homePath`字段(可选)即可为当前用户定制首页路径 + +### 🐛 Bug Fixes + +- **BasicTable** + - 修复滚动条样式问题(移除了滚动样式补丁) + - 修复树形表格的带有展开图标的单元格的内容对齐问题 + - 修复操作列的按钮在 disabled 状态下的颜色显示 + - 修复可编辑单元格的值不能直接通过修改`dataSource`来更新显示的问题 + - 修复使用`ApiSelect`编辑组件时的数据回显问题 + - 修复在部分场景下编辑组件可能会报`onXXX`类型错误的问题 +- **TableAction** + - 仅在 `action.tooltip`存在的情况下 才创建 Tooltip 组件 + - 修复组件内的圆形按钮内容没有居中的问题 +- **AppSearch** 修复可能会搜索隐藏菜单的问题 +- **BasicUpload** 修复处理非`array`值时报错的问题 +- **Form** 修复`FormItem`的`suffix`插槽样式问题 +- **Menu** + - 修复左侧混合菜单的悬停触发逻辑 + - 修复顶栏菜单在显示包含需要隐藏的菜单项目时出错的问题 + - 修复悬停触发模式下左侧混合菜单会在没有子菜单且被激活时直接跳转路由 +- **Breadcrumb** 修复带有重定向的菜单点击无法跳转的问题 +- **Markdown** 修复初始化异常以及不能正确地动态设置 value 的问题 +- **Modal** 确保 props 正确被传递 +- **MultipleTab** 修复可能会意外创建登录路由标签的问题 +- **BasicTree** 修复搜索功能可能导致`checkedKeys`丢失的问题 +- **CodeEditor** 修复 value 不支持 v-model 用法的问题 +- **CountdownInput** 修复不支持`input`插槽的问题 +- **ApiSelect** 修复`options-change`事件参数不是`select`所使用的标准`options`数据的问题 +- **其它** + - 修复菜单默认折叠的配置不起作用的问题 + - 修复`safari`浏览器报错导致网站打不开 + - 修复在 window 上,拉取代码后 eslint 因 endOfLine 而报错问题 + - 修复因动态路由而产生的 `Vue Router warn` + +### 🎫 Chores + +- 添加 test 环境测试命令 + +## 2.6.0(2021-07-04) + +### ✨ Features + +- **Axios** 新增`withToken`配置,用于控制请求是否携带 token +- **BasicUpload** + - 新增在预览 `Modal` 中删除文件时触发`preview-delete` 事件 + - `value` 支持 `v-model` 用法 +- **Route 配置** + - 增加`ignoreRoute`用于在`ROUTE_MAPPING`或`BACK`权限模式下仅生成菜单 + - 增加`hidePathForChildren`配置,标识为子项目生成菜单时忽略本级`path` +- **TableAction** 新增`tooltip`配置,可以为按钮增加 tooltip 提示 +- **CropperAvatar** + - 新增`value`用于设置当前头像 + - 新增`onChange`用于接受头像剪裁并上传成功事件 + - 新增`btnText`、`btnProps` 用于自定义上传按钮文案和属性 + - 为剪裁`Modal`内的操作按钮添加工具提示 +- **Modal** 为右上角的操作按钮添加工具提示 + +### 🐛 Bug Fixes + +- **Modal** + - 修复点击遮罩不能关闭的问题 + - 修复 `setModalProps` 不支持设置 `defaultFullscreen` 的问题 +- **Table** + - 修复 `editComponentProps` 不支持 `onChange`的问题 + - 修复启用`clickToRowSelect`时,点击行不会触发`selection-change`事件的问题 + - 修复全局配置`fetchSetting`可能会被局部配置意外修改的问题 + - 修复`handleSearchInfoFn`的参数包含多余空白键的问题 + - 修复为 table 提供 rowSelection.onChange 时,无法手动变更 table 的选中项的问题 + - 修复滚动条在无需显示的时候仍然持续显示的问题 +- **Icon** 修复 SvgIcon 缺少部分样式的问题 +- **Menu** + - 修复路由映射模式下,单级菜单刷新不会激活 + - 修复侧边菜单底部的折叠自定义失效的问题 +- **Form** 修复`submitButtonOptions`和`resetButtonOptions`的类型定义 +- **PopConfirmButton** 移除`Button`上多余的`title` +- **Axios** 修复非`GET`请求时,无法同时提交`params`和`data`数据的问题 +- **其它** + - 修复锁屏功能可以通过刷新页面或复制 URL 打开新的浏览器标签来跳过锁定状态的问题 + - 修复多个窗口同时打开页面时,`Token` 不会同步的问题 + - 修复`ROLE`权限模式下`hasPermission`不工作的问题 +- **Table** 修复`handleSearchInfoFn`的参数包含多余空白键的问题 +- **Tailwindcss** 移除控制台警告 + +## 2.5.2(2021-06-27) + +### ⚡ Performance Improvements + +- **Icon** 移除 Icon 组件全局注册,防止特定情况下热更新问题 + +### ✨ Features + +- **Menu** 新增 `permissionMode=PermissionModeEnum.ROUTE_MAPPING`模式 + - 项目默认改为该模式,删除原有菜单文件 + - 如果之前已经写好了菜单,可以更改为`PermissionModeEnum.ROLE`模式即可 + +### 🐛 Bug Fixes + +- **Drawer** 修复`visible`状态异常 + +## 2.5.1(2021-06-26) + +### ⚡ Performance Improvements + +- 升级`vue`与`ant-design-vue`版本,解决兼容问题 +- **Tree** 性能优化 + +### 🐛 Bug Fixes + +- **Table** 修复分页抖动问题 +- **Upload** 确保携带自定义参数 +- **Dropdown** 修复 popConfirm 的图标显示问题 +- **Table** 修复树形表格的编辑事件不正常的问题 +- **Table** 修复当表格数据为空时,getDataSource 返回的值不是表格所使用的数据源的问题 + +## 2.5.0(2021-06-20) + +## (破坏性更新) Breaking changes + +- 将项目`windicss`改为`tailwindcss`,解决内存溢出问题 + - 目前项目不兼容地方有 + - `!xl:m-4` 之类的写法需要改为`xl:!m-4`,注意只有`!`这个不兼容,没用到则不用改 + - `windicss`自身新增的特性需要调整,比如`Attribute`模式不兼容 + +### ✨ Refactor + +- 移除`useExpose`,使用组件自身提供的`expose`代替 + +### ⚡ Performance Improvements + +- **Locale** 合并多语言文件,减少文件数量 +- **Utils** Mitt 默认导出由 `Class` 改为 `Function` +- **Axios** `isTransformRequestResult`更名为`isTransformResponse` + +### ✨ Features + +- **CropperImage** `Cropper` 头像裁剪新增圆形裁剪功能 +- **CropperAvatar** 新增头像上传组件 +- **Drawer** `useDrawer`新增`closeDrawer`函数 +- **Preview** 新增`createImgPreview`图片预览函数 +- **Setup** 新增引导页示例 +- **Tests** 添加 jest 测试套件,暂不支持 Vue 组件单测 +- **Axios** 新增`authenticationScheme`配置,用于指定认证方案 +- **Setting** 新增 `sessionTimeoutProcessing` 项目配置项,用于配置会话超时如何处理 + +### 🐛 Bug Fixes + +- **Modal** 修复全屏高度计算错误 +- **Modal** 修复关闭事件触发多次问题 +- **PageWrapper** 修复高度计算问题 +- **FlowChart** 修复拖放菜单丢失 +- 修复后台模式下,Iframe 路由错误 +- **PageWrapper** 修复 footer 与全局页脚同时开启时的高度计算问题 +- **Menu** 修复菜单折叠动画抖动问题 +- **Store**修复 pinia 版本升级之后类型错误 + +## 2.4.2(2021-06-10) + +### ✨ Refactor + +- `CountTo`组件重构 + +### ✨ Features + +- `radioButtonGroup` 支持`boolean`值 +- `useModalInner` 新增 `redoModalHeight`用于在 Modal 内部重设`Modal`高度 +- `useECharts` 新增`getInstance`用于获取`echart`实例 +- `TableAction` 新增 `stopButtonPropagation` 阻止操作按钮点击事件冒泡 +- `BasicTable` 在行编辑模式下,可以获取或设置其它处于列的编辑组件的值 +- `ApiSelect` 组件在`params`改变后会自动重新`fetch`数据 +- `TableImg` 组件改进 +- `BasicTable` 新增 `columns-change` 事件用于监听用户改变列排序、展示、固定状态 +- `Tinymce`支持动态修改 readonly +- `BasicTable`新增`updateTableDataRecord`方法用于更新指定行数据 +- `useModal`新增`closeModal`方法用于关闭`Modal` + +### 🐛 Bug Fixes + +- 修复`redoModalHeight`不能减小高度的问题 +- 修复 `BasicForm`设置 schemas 数据不生效的问题 +- 修复多标签可能导致`KeepAlive`失效的问题 +- 修复默认的`axios`拦截器不能处理自定义 code 的问题 +- 修复锁屏弹窗的高度问题 +- 修复`BaiscTable`的`列展示`复选框的半选状态显示不正确的问题 +- 修复`BasicUpload`组件的预览列表某些情况下不能显示的问题 +- 修复`RadioButtonGroup`的`options`设置`disabled`不生效的问题 +- 修复`Tinymce`组件在只读模式下上传图片的按钮仍然可用的问题 +- 修复`BasicForm`特定情况下的卡顿问题 +- 修复"目录"路由不起作用的问题 + +## 2.4.1(2021-06-01) + +### ✨ Features + +- 可编辑表格新增`DatePicker`和`TimePicker`组件 +- `Tree` 组件新增`defaultExpandLevel`配置 + +### ⚡ Performance Improvements + +- 菜单搜索默认聚焦 + +### 🐛 Bug Fixes + +- 修复`CodeEditor`已知问题 +- 修复`i18n`控制台警告问题 +- 修复可编辑表格`align`配置不生效问题 +- 确保`axios`只对`Object`参数进行处理 +- 修复`Tree`组件 `defaultExpandAll` 配置失效 +- 修复`TableAction` 分割线丢失问题 +- 修复表格已知问题 +- 修复首次加载或改变语言导致重载时,不会设置 HTML 的 lang 属性 + +## 2.4.0 (2021-05-25) + +### ✨ Features + +- 新增图形编辑器示例 +- 新增代码编辑器(包含 Json 编辑器) +- 新增 `JsonPreview`Json 数据查看组件 +- 表格的数据列(column)和操作列(actionColumn)的字段可以根据权限和业务来控制是否显示 +- 新增权限控制表格示例(AuthColumn.vue) +- 新增用户登录过期示例 + +### ⚡ Performance Improvements + +- 合并部分语言文件,减少文件数量 + +### 🐛 Bug Fixes + +- 修复黑暗主题刷新闪烁的白屏 +- 修复标签页关闭其他功能失效问题 +- 修复表单已知问题 +- 修复自动锁屏失效 + +## 2.3.0 (2021-04-10) + +## (破坏性更新) Breaking changes + +- 使用 `pinia` 替换 `vuex`,`vuex-module-decorators`。 + + - 影响,之前如果有自己使用 vuex-module-decorators,需要改造为 pinia。 + - 原因: + - pinia 于 vuex5api 基本类似,且简单易懂。 + - 后续切换 vuex5 成本非常低,也可以当作第三方状态管理库使用 + +- 移除 `useKeyPress` 使用`vueuse`-`onKeyStroke`代替 +- 移除 `useDebounceFn` 使用`vueuse`-`useDebounceFn`代替 +- 移除 `useThrottle` 使用`vueuse`-`useThrottleFn`代替 + +### ✨ Features + +- 标签页支持持久化保存 + +### ✨ Refactor + +- 移除 `useElResize` + +### 🐛 Bug Fixes + +- 登录页样式修复 +- 修复菜单已知问题 +- 修复主题样式切换问题 + +## 2.2.0 (2021-04-06) + +### ✨ Features + +- 新增`headerTitle` slot +- 新增打印示例 +- 新增关于界面 + +### ✨ Refactor + +- 移除 useFullScreen 函数 +- tinymce 由 Cdn 改为 npm(打包体积偏大) +- Dashboard 重构 +- 移除 ApexCharts 及示例 + +### 🐛 Bug Fixes + +- 确保面包屑正确的显示图标 +- 修复 tinymce 上传按钮全屏模式下消失问题 +- 确保 title 在重新登录后正常改变 +- 确保后台模式登录正常 +- 修复 TableAction 点击事件问题 + +## 2.1.1 (2021-03-26) + +### ✨ Features + +- 路由新增 hideChildrenInMenu 配置。用于隐藏子菜单 +- 树形表格内置展开/折叠全部函数 + +### ✨ Refactor + +- 重构路由多层模式,解决嵌套 keepalive 执行多次问题 + +### 🐛 Bug Fixes + +- 确保 CountDownInput 组件重置清空值 +- 修复分割模式下在小屏幕中显示问题 +- 修复表格高度计算问题 +- 修复后台路由获取不到组件问题 +- 修复 Modal 组件 loadingTip 配置不生效 +- 修复后台权限指令不生效 +- 确保 progress 进度条正确关闭 +- 修复表格勾选列配置失效问题 +- 确保一级菜单可以被隐藏 +- 确保表单隐藏字段校验正常 + +### 🎫 Chores + +- 移除 ls-lint + +## 2.1.0 (2021-03-15) + +### ✨ Features + +- 图标选择器新增 svg 模式 +- 新增时间组件 +- 新增高德/百度/谷歌地图示例 + +### ✨ Refactor + +- 重构项目以解决循环依赖项导致的热更新问题 +- 移除 vueHelper/useClickoutside,使用@vueuse/core 代替 + +### 🐛 Bug Fixes + +- 确保 `table action` 的值被正确更新 +- 修复页面切换的动画无法关闭 +- 修复`PageWrapper`title 不显示 +- 修复表格已知问题 +- 修复 BasicTree 组件不能自定义 title 问题 +- 修复主题切换后按钮样式问题 + +## 2.0.3 (2021-03-07) + +### ✨ Features + +- `BasicTree` 新增`clickRowToExpand`,用于单击树节点展开 +- 新增 SvgIcon 插件及示例 +- 账号管理界面增加左侧部门树· + +### ⚡ Performance Improvements + +- 表格关闭分页时不再携带分页参数 +- 登录页监听回车事件进行登录 +- 当表格设置自适应大小时,根据屏幕来铺满了高度. +- Tree 滚动条优化 +- 优化本地开发加载速度 + +### 🐛 Bug Fixes + +- 修复`Description`已知问题 +- 修复`BasicForm`已知问题 +- 修复`BasicTree`下 ActionItem 的 show 属性逻辑问题 +- 修复树组件 demo 示例样式错误 +- 修复账号管理新增未清空旧数据 +- form 组件应允许 setFieldsValue 方法值为 null 或者 undefined +- 确保单级面包屑正确跳转 +- 确保 Form 组件不校验隐藏的表单项 + +## 2.0.2 (2021-03-04) + +### ✨ Refactor + +- 重构多语言模块,支持懒加载及远程加载 + +### ✨ Features + +- axios 支持 form-data 格式请求 +- 新增图标选择器组件(支持本地和在线方式) +- 新增 WebSocket 示例和服务脚本 +- Tree 组件新增 `renderIcon` 属性用于控制层级图标显示 +- Tree->actionItem 新增 show 属性,用于动态控制按钮显示 +- Tree 新增工具栏/title/搜索功能 +- 新增部门管理/修改密码/账号管理/角色管理/菜单管理示例界面 + +### ⚡ Performance Improvements + +- 登录界面动画优化 +- 修复 github 仓库体积过大问题. +- 默认隐藏表格全屏按钮 +- `crypto-es`改为`crypto-js`,减小打包体积 +- `types`目录移动到根目录,兼容其他目录全局类型 + +### 🐛 Bug Fixes + +- 修复验证码组件警告问题 +- 修复表格不能正确的获取选中行 +- 修复全屏状态下 modal 高度计算错误 +- 修复部分表格样式问题 +- 修复树形表格 `indentSize`设置失效 + +## 2.0.1 (2021-02-21) + +### ✨ Refactor + +- 登录页重构,新增注册页面/重置密码页面/手机登录/二维码登录 + +### ✨ Features + +- 新增 `settingButtonPosition`配置项,用于配置`设置`按钮位置 +- `modal`可以通过双击头部切换全屏 +- 新增`CountDownInput`组件 + +### ⚡ Performance Improvements + +- 优化可编辑居中样式及下拉框宽度过短 +- 表格新增编辑时`edit-change`事件监听 + +### 🐛 Bug Fixes + +- 修复图片预览样式错误 +- 修复图标样式问题 +- 修复可编辑表格下拉回显问题 + +## 2.0.0 (2021-02-18) + +## (破坏性更新) Breaking changes + +- `echarts` 升级到 5.0,并且进行按需引入(只需使用 `useECharts` 即可). + +### ✨ Refactor + +- 移除`global.less`,`mixin.less`,`design/helper`,由`windicss`代替,有用到的需要修改对应的样式 + +### ✨ Features + +- useModal 新增返回值函数 `redoModalHeight`,用于在 modal 内为动态内容时刷新 modal 高度 +- 升级 husky 到 5.0 +- 新增 `brotli`|`gzip`压缩及相关测试命令 +- 重新引入 `windicss` (与`tailwind`一样).在速度上更快 + +### ⚡ Performance Improvements + +- 调整获取用户信息接口返回值为数组格式 +- 将 error-log 列表固定为系统路由 + +### 🐛 Bug Fixes + +- 修复 Upload 组件 maxNumber 失效问题 +- 修复打包 sourcemap 报错 +- 修复代码 debugger 位置显示错误 +- 修复 mock 插件 post 请求错误问题 +- 修复部分主题颜色值错误 +- 修复表格在可编辑行状态回车确认 + +### 🎫 Chores + +- 文档更新 +- 升级 ant-design-vue 到 `2.0.0` +- 升级 vite 到 `2.0.0` + +## 2.0.0-rc.18 (2021-02-05) + +### ✨ Features + +- `ApiSelect`新增 `numberToString`属性,用于将 value 为`number`的值全部转化为`string` +- 新增主题色切换 +- 打包图片压缩 + +### ⚡ Performance Improvements + +当不使用 mock 时,将 `mock.js` 移出打包文件 + +### 🐛 Bug Fixes + +- 修复 modal 高度计算错误 +- 修复菜单折叠状态下点击标签页弹出菜单 +- 修复 form 表单初始化值为 0 问题 +- 修复表格换行问题 +- 修复菜单外链不跳转 +- 修复菜单顶部显示问题 +- 修复`modifyVars`配置失效问题 + +## 2.0.0-rc.17 (2021-01-18) + +### ✨ Refactor + +- 新增 `SimpleMenu`组件替代左侧菜单组件(顶部菜单没有替换,功能尽量做到简单不卡)。解决菜单卡顿问题。 +- `ant-design-vue`组件不再全局注册。以便于更好配合 css 按需引入。如果需要全局注册,需要自己加 + +### ✨ Features + +- `css` 按需引入 + +### 🐛 Bug Fixes + +- 修复 `TableAction`图标问题 +- 修复菜单折叠按钮丢失问题 +- 修复菜单相关问题 +- 修复 moment 多语言问题 + +## 2.0.0-rc.16 (2021-01-12) + +### ✨ Refactor + +- 独立组件配置到 `/@/settings/componentsSetting` +- `colorSetting`和`designSetting`现在合并为`designSetting` +- `ant-design-vue`组件注册移动到`components/registerComponent` +- 移除 `setup` 文件夹 +- 升级到`vite2` +- 图片预览改为`Image`组件实现,暂时移除函数式使用方式 + +### ✨ Features + +- 新增`mixSideTrigger`配置。用于配置左侧混合模式菜单打开方式。可选`hover`,默认`click` +- 新增`mixSideFixed`配置。用于固定左侧混合模式菜单 +- modal 组件新增`height`和`min-height`属性 +- 新增`PageWrapper`组件。并应用于示例页面 +- 新增标签页折叠功能 +- 兼容旧版浏览器 +- tinymce 新增图片上传 + +### 🐛 Bug Fixes + +- 修复表格列配置已知问题 +- 恢复 table 的`isTreeTable`属性 +- 修复表格内存溢出问题 +- 修复`layout` 收缩展开功能在分割模式下失效 +- 修复 modal 高度计算错误 +- 修复文件上传错误 +- 修复表格已知问题 + +### 🎫 Chores + +- 文档更新 + +## 2.0.0-rc.15 (2020-12-31) + +### ✨ 表格破坏性更新 + +- 重构了可编辑单元格及可编辑行。具体看示例。写法已改变。针对可编辑表格。 + +- 表格编辑支持表单校验 + +- 在表格列配置增加了以下配置 + +```bash +{ + + # 默认是否显示列。不显示的可以在列配置打开 + defaultHidden?: boolean; + # 列头右侧帮助文本 + helpMessage?: string | string[]; + # 自定义格式化 单元格内容。 支持时间/枚举自动转化 + format?: CellFormat; + + # Editable + # 是否是可编辑单元格 + edit?: boolean; + # 是否是可编辑行 + editRow?: boolean; + # 编辑状态。 + editable?: boolean; + # 编辑组件 + editComponent?: ComponentType; + # 所对应组件的参数 + editComponentProps?: Recordable; + # 校验 + editRule?: boolean | ((text: string, record: Recordable) => Promise); + # 值枚举转化 + editValueMap?: (value: any) => string; + # 触发编辑正航 + record.onEditRow?: () => void; +} + +``` + +### ✨ 表格重构 + +- 新增`clickToRowSelect`属性。用于控制点击行是否选中勾选框 +- 监听行点击事件 +- 表格列配置按钮增加 列拖拽,列固定功能。 +- 表格列配置新增`defaultHidden` 属性。用于默认隐藏。可在表格列配置勾选显示 +- 更强大的列配置 +- useTable:支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改 +- useTable:新增返回 `getForm`函数。可以用于操作表格内的表单 +- 修复表格已知的问题 + +### ✨ Features + +- 新增 `v-ripple`水波纹指令 +- 新增左侧菜单混合模式 +- 新增 markdown 嵌入表单内示例 +- 新增主框架外页面示例 +- `route.meta` 新增`currentActiveMenu`,`hideTab`,`hideMenu`参数 用于控制详情页面包屑级菜单显示隐藏。 +- 新增面包屑导航示例 +- form: 新增`suffix`属性,用于配置后缀内容 +- form: 新增远程下拉`ApiSelect`及示例 +- form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框 +- useForm: 支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改 + +### ⚡ Performance Improvements + +- 优化`modal`与`drawer`滚动条组件 +- table: 移除 `isTreeTable`属性 +- 全局引入`less`文件。无需手动在组件再次引入 + +### 🎫 Chores + +- 升级`ant-design-vue`到`2.0.0-rc.7` +- 升级`vue`到`3.0.5` + +### 🐛 Bug Fixes + +- 修复混合模式下滚动条丢失问题 +- 修复环境变量配置失效以及 history 模式下 logo 地址问题 +- 修复图表库切换页面导致宽高计算错误 +- 修复多语言配置 `Locale.show`导致配置不生效 +- 修复路由类型错误 +- 修复菜单分割时权限失效问题 +- 关闭多标签页时 iframe 提前加载 +- 修复`modal`与`drawer`已知问题 +- 修复左侧菜单混合模式适配问题 + +## 2.0.0-rc.14 (2020-12-15) + +### ✨ Features + +- 移除左侧菜单搜索,新增顶部菜单搜索功能 +- layout 移动端适配。业务页面未适配 +- axios 加入 joinTime 配置。控制响应是否加入时间戳 + +### ⚡ Performance Improvements + +- 异步引入组件 +- 优化整体结构 +- 替换菜单默认滚动条为滚动组件 +- 菜单性能优化 + +### 🎫 Chores + +- 返回顶部样式调整,避免遮住其他元素 +- 升级`ant-design-vue`到`2.0.0-rc.5` +- 刷新按钮布局调整 +- `route.meta` 移除 `externalLink` 属性 + +### ✨ Refactor + +- `openModal`与`openDrawer`第三个参数`openOnSet`默认设置为 true + +### 🐛 Bug Fixes + +- 修复多级路由缓存导致组件渲染多次的问题 +- 修复地图图表切换后消失问题 +- 修复登录成功 notify 消失问题 +- 修改 `VirtualScroll`和`ImportExcel`组件名为`VScroll`与`ImpExcel`,暂时解决含有关键字的组件在 vue 模版内使用内存溢出 +- 修复 axios 大小写问题 +- 修复按钮样式问题 +- 修复菜单分割模式问题 +- 修复 `Modal`与`Drawer`组件在使用 emits 数据传递失效问题 +- 修复菜单已知问题 +- 修复上传组件 api 失效问题 +- 修复菜单权限过滤失效问题 + +## 2.0.0-rc.13 (2020-12-10) + +## (破坏性更新) Breaking changes + +- 路由重构, 不再支持以前的格式。改为支持 vue-router 最初的默认结构,具体格式可以参考示例更改。实现多级路由缓存,不再将路由转化为 2 级。 +- 重构面包屑,使用 antd 的面包屑组件。之前的组件已删除 + +### ✨ Features + +- 还原 antdv 默认 loading,重构 `Loading` 组件,增加`useLoading`和`v-loading`指令。并增加示例 +- i18n 支持 vscode `i18n-ally`插件 +- 新增多级路由缓存示例 +- 打包代码拆分(试验) +- 提取上传地址到全局变量,打包可以动态配置 + +### ✨ Refactor + +- tree 组件 ref 函数调用删除 `$` +- 锁屏界面重构美化,删除不必要的背景图片 + +### ⚡ Performance Improvements + +- 页面切换 loading 逻辑修改。对于已经加载过的页面不管有没有关闭,再次打开不会在显示 loading(已经打开过的页面再次打开速度比较快,可以不需要 loading,同理顶部进度条逻辑也一样),刷新后恢复。 + +### 🎫 Chores + +- 首屏 loading 修改 +- 升级`vue`到`3.0.4` +- 升级`ant-design-vue`到`2.0.0-rc.3` +- 重新引入`vueuse` +- 移除 route meta 内的`afterCloseLoading`属性 +- 文档更新 + +### 🐛 Bug Fixes + +- 修复表格 i18n 错误 +- 修复菜单图标大小不一致 +- 修复顶部菜单宽度计算问题 +- 修复表格 tabSetting 问题 +- 修复文件上传删除失效 +- 修复表格行编辑保存错误问题 + +## 2.0.0-rc.12 (2020-11-30) + +## (破坏性更新) Breaking changes + +- ClickOutSide 组件引入方式由 `import ClickOutSide from '/@/components/ClickOutSide/index.vue'`变更为`import { ClickOutSide } from '/@/components/ClickOutSide'` +- Button 组件引入方式由 `import Button from '/@/components/Button/index.vue'`变更为`import { Button } from '/@/components/Button'` +- StrengthMeter 组件引入方式由 `import StrengthMeter from '/@/components/StrengthMeter'`变更为`import { StrengthMeter } from '/@/components/StrengthMeter'` +- 除示例外加入全局国际化功能,支持中文与英文 + +### ✨ Refactor + +- 重构整体 layout。更改代码实现方式。代码更精简 +- 配置项重构 +- 移除 messageSetting 配置 +- BasicTitle 组件 `showSpan`=> `span` + +### ✨ Features + +- 缓存可以配置是否加密,默认生产环境开启 Aes 加密 +- 新增标签页拖拽排序 +- 新增 LayoutFooter.默认显示,可以在配置内关闭 + +### ⚡ Performance Improvements + +- 优化`Modal`组件全屏动画不流畅问题 + +### 🐛 Bug Fixes + +- tree: 修复文本超出挡住操作按钮问题 +- useRedo: 修复通过 useRedo 刷新页面参数丢失问题 +- form: 修复表单校验先设置在校验及控制台错误信息问题 +- `modal`&`drawer` 修复组件传递数组参数问题 +- form: 修复`updateSchema`赋值含有`[]`时不生效 +- table: 修复表格 `TableAction` 图标显示问题 +- table: 修复表格列设置通过`setColumns`设置不显示 + +### 🎫 Chores + +- 更新 antdv 到`2.0.0-rc.2` +- 更新 vue 到`3.0.3` +- 更新 vite 到`1.0.0.rc13` +- 暂时删除 `@vueuse/core`.等稳定后在集成。目前不太稳定。 + +## 2.0.0-rc.11 (2020-11-18) + +### ✨ Features + +- 新增 base64 文件流下载 +- 优化上传组件及示例 +- 新增可编辑行示例 +- 新增个人页 +- 新增表单页 +- 新增详情页 +- 将上传组件默认集成到 form + +### 🎫 Chores + +- 更新 antdv 到`2.0.0-rc.1`(暂时还原到 beta15,rc1 菜单卡顿太严重.) +- 添加部分注释 + +### ✨ Refactor + +- 移除`useModal`与`useDrawer`的`receiveDrawerDataRef`和`transferDrawerData`属性 +- `useModal`与`useDrawer`对应的`openModal`与`openDrawer`扩展第三个参数。用于再次打开触发回调 + +### 🐛 Bug Fixes + +- 修复表单 inputNumber 校验错误 +- 修复表单默认值设置错误 +- 修复菜单折叠按钮隐藏时占位问题 +- 修复表单 baseColProps 不生效 + +## 2.0.0-rc.10 (2020-11-13) + +### ✨ Refactor + +- 重构 hook,引入 `@vueuse`,删除其中已有的`hook`,优化现有的 hook +- `useEvent` 更名->`useEventListener` +- 表单`ComponentType`删除 `SelectOptGroup`,`SelectOption`,`Transfer`,`Radio`,四个类型。修改`RadioButtonGroup`组件 + +### ✨ Features + +- 表单项的`componentsProps`支持函数类型 +- 菜单新增 tag 显示,支持 4 中类型颜色及 dot 圆点显示 +- 新增菜单及顶栏颜色选择配色 +- 增加示例结果页 +- 新增文件下载示例 + +### ⚡ Wip + +- 上传组件(未完成,测试中...) + +### ⚡ Performance Improvements + +- 优化 settingDrawer 代码 +- 优化多标签页切换速度 +- 增加表单自定义及动态能力 + +### 🐛 Bug Fixes + +- 修复多个富文本编辑器只显示一个 +- 修复登录过期后重新登录未跳转原来页面的 +- 修复 window 系统动态引入错误 +- 修复页面类型错误 +- 修复表单 switch 和 checkBox 单独使用报错 + +## 2.0.0-rc.9 (2020-11-9) + +### ✨ Features + +- 菜单 trigger 可以选择位置 +- 增加富文本嵌入表单的示例 +- 表单组件 schema 增加 `required`属性。简化配置 +- openModal 和 openDrawer 第二个参数可以代替`transferModalData`传参到内部 +- 带参路由可以被缓存 + +### ✨ Refactor + +- 重构由后台生成菜单的逻辑 +- Route Module 结构改造 + +### ⚡ Performance Improvements + +- 菜单性能继续优化,更流畅 +- 优化懒加载组件及示例 +- layout 样式微调 + +### 🎫 Chores + +- 删除菜单背景图 +- 更新`ant-design-vue`版本为`beta15` +- 更新`vite`版本为`rc.9` +- 异常页调整 +- `BasicTitle` 色块默认不显示 + +### 🐛 Bug Fixes + +- 修复升级之后 table 类型问题 +- 修复分割菜单且左侧菜单没有数据时候,继续展示上一次子菜单的问题 +- 修复`useMessage`类型问题 +- 修复表单项设置`disabled`不生效问题 +- 修复`useECharts`在`resize`时不能自适应,报错 +- 修复`useWatermark`在清空后`resize`未删除 +- 修复表单校验问题 +- 修复多级表头配置不生效问题 + +## 2.0.0-rc.8 (2020-11-2) + +### ✨ Features + +- 全局 loading 添加文本 +- 右键菜单支持多级 + +### 🎫 Chores + +- 登录缓存从 sessionStorage 改为 LocalStorage + +### ⚡ Performance Improvements + +- 更新`ant-design-vue`到`beta.12` +- Layout 界面布局样式调整 +- 优化懒加载组件 +- 优化表格渲染性能 +- 表单折叠搜索添图标添加动画 +- routeModule 可以忽略 layout 配置不写。方便配置一级菜单 + +### 🐛 Bug Fixes + +- 修复表格类型错误 +- 修复 mock 分页工具错误 +- 修复表格开启搜索表单折叠问题 +- 修复表格 size 为 samll 时候,fixed 列样式问题 +- 修复多标签页关闭报错问题 +- 修复 message 类型错误 + +## 2.0.0-rc.7 (2020-10-31) + +### ✨ Features + +- 表单组件现在支持直接传入 model 直接进行 set 操作,参考**组件->弹窗扩展->打开弹窗并传递数据** + +- modal 的 useModalInner 现在支持传入回调函数,用于接收外部`transferModalData`传进来的值, + + - 用于处理打开弹窗对表单等组件的设置值。参考**组件->弹窗扩展->打开弹窗并传递数据** + - `receiveModalDataRef`这个值暂时保留。尽量少用。后续可能会删除。 + +- drawer 的 useDrawerInner 现在支持传入回调函数,用于接收外部`transferModalData`传进来的值, + - 用于处理打开抽屉对表单等组件的设置值。参考**组件->抽屉扩展->打开抽屉并传递数据** + - `receiveModalDataRef`这个值暂时保留。尽量少用。后续可能会删除。 + +### ✨ Refactor + +- 表单代码优化重构 + +### ⚡ Performance Improvements + +- Modal slot 可以覆盖 +- 优化表格嵌入高度计算问题 + +### 🎫 Chores + +- 添加部分注释 +- pwa 图标补充 +- types 类型调整 +- 升级`ant-design-vue`到`beta.11`,并修改带来的已知问题,部分问题发现后在解决 + +### 🐛 Bug Fixes + +- 修复本地代理 post 接口到 https 地址超时错误 +- 修复 modal 在不显示 footer 的时候全屏高度计算问题 +- 修复表单重置未删除校验信息错误 +- 修复顶部菜单分割模式样式问题 +- 修复表格展开图标动画失效 + +## 2.0.0-rc.6 (2020-10-28) + +### ✨ Features + +- 新增`pwa`功能,可在`.env.production`开启 +- Button 组件扩展 `preIcon`和`postIcon`属性用于在文本前后添加图标 +- 恢复面包屑显示图标功能 + +### 🎫 Chores + +- 升级 vite 版本为`v1.0.0.rc8` +- vite.config.ts 内部 plugins 抽取 +- build 目录结构调整 +- 依赖更新 +- 文档更新 +- 修改默认路由切换动画 + +### ⚡ Performance Improvements + +- `setTitle`逻辑调整 +- 将系统用到的 sessionStorage 及 LocalStorage 缓存设置默认 `7` 天过期 + +### ✨ Refactor + +- 独立出`vite-plugin-html`,并修改相关插入 html 的逻辑 + +### 🐛 Bug Fixes + +- 修复热更新时多次注册组件警告问题 +- 修复登录后出现登录标签页 +- 修复路由切换参数消失问题 +- 修复 useMessage 图标样式问题 + +## 2.0.0-rc.5 (2020-10-26) + +### ✨ Features + +- 更新组件文档 +- 面包屑支持显示图标 +- 新增 tinymce 富文本组件 +- 表单新增 submitOnReset 控制是否在重置时重新发起请求 +- 表格新增`sortFn`支持自定义排序 +- 新增动画组件及示例 +- 新增懒加载/延时加载组件及示例 + +### ✨ Refactor + +- Drawer 组件的 detailType 修改为 isDetail + +### 🎫 Chores + +- 删除代码内的可选链语法 +- 表单重置逻辑修改 +- 关闭多标签页 tabs 动画 +- 升级 vite 版本为`v1.0.0.rc6` +- 删除中文路径警告。rc6 已修复 + +### 🐛 Bug Fixes + +- 修复抽屉组件自动高度及显示 footer 显示问题 +- 修复表单查询后重置回默认值 +- 修复菜单没有子节点时显示折叠的问题 +- 修复面包屑显示样式问题 +- 修复 modal 在 destroyOnClose=true 时多次打开拖拽失效 +- 修复表格出现多个 action 列 + +# 2.0.0-rc.4 (2020-10-21) + +### ✨ Features + +- 表格新增配置工具栏 +- 新增消息通知模块 + +### 🎫 Chores + +- 表格默认不显示边框 +- 依赖更新 +- 更新 vue 为`v3.0.2` +- 界面样式微调 + +### ⚡ Performance Improvements + +- 优化首屏体积大小 +- 优化 TableAction 组件 +- 减小菜单折叠宽度 + +### 🐛 Bug Fixes + +- 修复一级菜单折叠显示菜单名问题 +- 修复预览命令不打包问题 +- 修复表格 actionColOptions 参数不生效问题 +- 修复表格刷新表单 loading 不生效问题 +- 修复带参界面刷新参数丢失问题 + +# 2.0.0-rc.3 (2020-10-19) + +### ✨ Features + +- 新增 excel 组件及 excel/xml/csv/html 导出示例 +- 新增 excel 导入示例 +- 新增全局错误处理 +- 新增 markdown 组件及示例 +- 新增折叠菜单时可显示菜单名 + +### Docs + +- 添加项目文档 + +### 🎫 Chores + +- 升级依赖 +- 其他细节优化 + +### 🐛 Bug Fixes + +- 修复顶部菜单自适应问题 +- 修复 window 系统打包报错问题 + +# 2.0.0-rc.2 (2020-10-17) + +### ✨ Features + +- 打包可以配置输出`gizp` +- 打包可以配置删除`console` +- 路由及菜单不需要在手动引入,改为自动引入 + +### 🎫 Chores + +- 升级 vue 到`3.0.1` +- 将`vite`版本改为每日构建版本 + +### 🐛 Bug Fixes + +- 修复菜单报错 +- 修复表格自适应高度问题 +- 修复`window系统`执行 script 报错问题 +- 修复折叠组件问题 + +### ⚡ Performance Improvements + +- 删除菜单最小化背景 +- 阻止页面刷新重新渲染菜单 +- 其他一些细节优化 + +# 2.0.0-rc.1 (2020-10-14) + +### ✨ Features + +- 添加带参 tab + +### ⚡ Performance Improvements + +- 菜单折叠优化 +- 页面细节优化 +- 打包后压缩 html +- 预览组件及右键菜单函数化重构 +- 预览组件操作列居中 + +### 🎫 Chores + +- 更新依赖 +- 添加`README.en-US.md` +- 添加`CHANGELOG.en-US.md` + +### 🐛 Bug Fixes + +- 修复页面刷新跳转到登陆页 + +# 2.0.0-beta.7 (2020-10-12) + +### ⚡ Performance Improvements + +- 现有的选项卡切换不再显示动画和和进度条 + +### ✨ Features + +- 新增 `CountTo`组件及示例 demo +- 项目配置文件新增 `closeMessageOnSwitch`和`removeAllHttpPending` +- 生产环境独立出配置文件,用于动态配置项目配置 +- 新增 `useEcharts`和`useApexChart`来方便图表使用,同时新增相关 demo +- 新增工作台界面 +- 新增分析页界面 + +### 🎫 Chores + +- 更新依赖 + +### 🐛 Bug Fixes + +- 修复路由切换,tab 未激活问题 + +# 2.0.0-beta.56 (2020-10-11) + +### 💄 Styles + +- 菜单样式调整 + +### 🐛 Bug Fixes + +- 修复可编辑表格不能输入问题 +- 修复打包报错,生产环境不需要设计 proxy + +### ⚡ Performance Improvements + +- 优化多标签页切换速度 +- 首屏加载动画 + +# 2.0.0-beta.5 (2020-10-10) + +### ♻ Code Refactoring + +- 删除`tailwind css` + +### ⚡ Performance Improvements + +- 优化页面切换速度 + +### 🎫 Chores + +- 添加 `.vscode`和`.github`配置 +- 更改菜单图标 +- 新增`.env`配置文件 +- 更新 readme.md + +### 🐛 Bug Fixes + +- 修复`Tree`组件勾选事件失效问题 + +# 2.0.0-beta.4 (2020-10-08) + +### 🎫 Chores + +- 删除多余依赖 + +### 🐛 Bug Fixes + +- 修复页面刷新空白 +- 修复表格在生产环境样式失效 + +# 2.0.0-beta.3 (2020-10-07) + +### ✨ Features + +- 项目配置文件新增`openNProgress`用于控制是否开启顶部控制条 +- 添加`table`组件及 demo + +### 🎫 Chores + +- 添加` github workflows` + +# 2.0.0-beta.2 (2020-10-07) + +### ✨ Features + +- 新增图片预览组件 + +### 🔧 Continuous Integration + +- 增加 githubAction 脚本 + +# 2.0.0-beta.1(2020-09-30) + +### 🎫 Chores + +- 从 1.0 迁移部分代码 +- 添加 README.md 描述文件 + +### 🐛 Bug Fixes + +- 修复表单,动画及打包失败问题 diff --git a/CNAME b/CNAME new file mode 100644 index 00000000000..3436928ab70 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +vben.vvbin.cn diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..c208facbeec --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +# node 构建 +FROM node:18-alpine as build-stage +# 署名 +MAINTAINER Adoin 'adoin@qq.com' +WORKDIR /app +COPY . ./ +# 设置 node 阿里镜像 +RUN npm config set registry https://registry.npmmirror.com +# 设置--max-old-space-size +ENV NODE_OPTIONS=--max-old-space-size=16384 +# 设置阿里镜像、pnpm、依赖、编译 +RUN npm install pnpm -g && \ + pnpm install --frozen-lockfile && \ + pnpm build:docker +# node部分结束 +RUN echo "🎉 编 🎉 译 🎉 成 🎉 功 🎉" +# nginx 部署 +FROM nginx:1.23.3-alpine as production-stage +COPY --from=build-stage /app/dist /usr/share/nginx/html/dist +COPY --from=build-stage /app/nginx.conf /etc/nginx/nginx.conf +EXPOSE 80 +## 将/usr/share/nginx/html/dist/assets/index.js 和/usr/share/nginx/html/dist/_app.config.js中的"$vg_base_url"替换为环境变量中的VG_BASE_URL,$vg_sub_domain 替换成VG_SUB_DOMAIN,$vg_default_user替换成VG_DEFAULT_USER,$vg_default_password替换成VG_DEFAULT_PASSWORD 而后启动nginx +CMD sed -i "s|__vg_base_url|$VG_BASE_URL|g" /usr/share/nginx/html/dist/assets/entry/index-*.js /usr/share/nginx/html/dist/_app.config.js && \ + nginx -g 'daemon off;' +RUN echo "🎉 架 🎉 设 🎉 成 🎉 功 🎉" diff --git a/LICENSE b/LICENSE index cec5b42741c..ba2fe3b14c1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,21 @@ MIT License -Copyright (c) 2024-present, Vben +Copyright (c) 2020-present, Vben -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: +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 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. +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. diff --git a/README.ja-JP.md b/README.ja-JP.md deleted file mode 100644 index 4ce285a741b..00000000000 --- a/README.ja-JP.md +++ /dev/null @@ -1,157 +0,0 @@ -
- - VbenAdmin Logo - -
-
- -[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) - -

Vue Vben Admin

-
- -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg) - -**日本語** | [English](./README.md) | [中文](./README.zh-CN.md) - -## 紹介 - -Vue Vben Adminは、最新の`vue3`、`vite`、`TypeScript`などの主流技術を使用して開発された、無料でオープンソースの中・後端テンプレートです。すぐに使える中・後端のフロントエンドソリューションとして、学習の参考にもなります。 - -## アップグレード通知 - -これは最新バージョン `5.0` であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。 - -## 特徴 - -- **最新技術スタック**:Vue 3やViteなどの最先端フロントエンド技術で開発 -- **TypeScript**:アプリケーション規模のJavaScriptのための言語 -- **テーマ**:複数のテーマカラーが利用可能で、カスタマイズオプションも豊富 -- **国際化**:完全な内蔵国際化サポート -- **権限管理**:動的ルートベースの権限生成ソリューションを内蔵 - -## プレビュー - -- [Vben Admin](https://vben.pro/) - フルバージョンの中国語サイト - -テストアカウント:vben/123456 - -
- VbenAdmin Logo - VbenAdmin Logo - VbenAdmin Logo -
- -### Gitpodを使用 - -Gitpod(GitHub用の無料オンライン開発環境)でプロジェクトを開き、すぐにコーディングを開始します。 - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin) - -## ドキュメント - -[ドキュメント](https://doc.vben.pro/) - -## インストールと使用 - -1. プロジェクトコードを取得 - -```bash -git clone https://github.com/vbenjs/vue-vben-admin.git -``` - -2. 依存関係のインストール - -```bash -cd vue-vben-admin -npm i -g corepack -pnpm install -``` - -3. 実行 - -```bash -pnpm dev -``` - -4. ビルド - -```bash -pnpm build -``` - -## 変更ログ - -[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases) - -## 貢献方法 - -ご参加をお待ちしております![Issueを提出](https://github.com/anncwb/vue-vben-admin/issues/new/choose)するか、Pull Requestを送信してください。 - -**Pull Request プロセス:** - -1. コードをフォーク -2. 自分のブランチを作成:`git checkout -b feat/xxxx` -3. 変更をコミット:`git commit -am 'feat(function): add xxxxx'` -4. ブランチをプッシュ:`git push origin feat/xxxx` -5. `pull request`を送信 - -## Git貢献提出規則 - -参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 規則 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) - -- `feat` 新機能の追加 -- `fix` 問題/バグの修正 -- `style` コードスタイルに関連し、実行結果に影響しない -- `perf` 最適化/パフォーマンス向上 -- `refactor` リファクタリング -- `revert` 変更の取り消し -- `test` テスト関連 -- `docs` ドキュメント/注釈 -- `chore` 依存関係の更新/スキャフォールディング設定の変更など -- `ci` 継続的インテグレーション -- `types` 型定義ファイルの変更 - -## ブラウザサポート - -ローカル開発には `Chrome 80+` ブラウザを推奨します - -モダンブラウザをサポートし、IEはサポートしません - -| [Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | -| :-: | :-: | :-: | :-: | -| 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン | - -## メンテナー - -[@Vben](https://github.com/anncwb) - -## スター歴史 - -[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date) - -## 寄付 - -このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます! - -![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png) - -Paypal Me - -## 貢献者 - - - Contribution Leaderboard - - - - Contributors - - -## Discord - -- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) - -## ライセンス - -[MIT © Vben-2020](./LICENSE) diff --git a/README.md b/README.md index b9dd73eb98a..25cf7151258 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,178 @@ -
- - VbenAdmin Logo - -
-
+
VbenAdmin Logo

[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) -

Vue Vben Admin

+

Vue vben admin

-[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg) - -**English** | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md) +**English** | [中文](./README.zh-CN.md) ## Introduction -Vue Vben Admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite`, `TypeScript` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference. - -## Upgrade Notice - -This is the latest version, 5.0, and it is not compatible with previous versions. If you are starting a new project, it is recommended to use the latest version. If you wish to view the old version, please use the [v2 branch](https://github.com/vbenjs/vue-vben-admin/tree/v2). +Vue Vben Admin is a free and open source middle platform/back-end template. Using the latest `vue3`, `vite4`, `TypeScript` and other mainstream technology, Vben is the out-of-the-box front-end solution for both production and learning purpose. ## Features -- **Latest Technology Stack**: Developed with cutting-edge front-end technologies like Vue 3 and Vite -- **TypeScript**: A language for application-scale JavaScript -- **Themes**: Multiple theme colors available with customizable options -- **Internationalization**: Comprehensive built-in internationalization support -- **Permissions**: Built-in solution for dynamic route-based permission generation +- **State-of-art Techinical Stack**:Using the latest and popular front-end technology such as Vue3/vite2 +- **TypeScript**: Application-level JavaScript language +- **Theming**: Configurable themes +- **International**:Built-in i18n support +- **Response Mock**: Built-in response mock ability +- **Authority**: Built-in permission system based on dynamic routes. +- **Component**: Extracted and encapsulated components for various scenarios. ## Preview -- [Vben Admin](https://vben.pro/) - Full version Chinese site +- [vue-vben-admin](https://vben.vvbin.cn/) - Full version (Chinese) +- [vue-vben-admin-gh-pages](https://anncwb.github.io/vue-vben-admin/) - Full version (github hosted) +- [vben-admin-thin-next](https://vben.vvbin.cn/thin/next/) - Simplified Version (Chinese) +- [vben-admin-thin-gh-pages](https://anncwb.github.io/vben-admin-thin-next/) -Simplified Version (github hosted) -Test Account: vben/123456 +Test account: vben/123456 -
- VbenAdmin Logo - VbenAdmin Logo - VbenAdmin Logo -
+

+ VbenAdmin Logo + VbenAdmin Logo + VbenAdmin Logo +

### Use Gitpod Open the project in Gitpod (free online dev environment for GitHub) and start coding immediately. -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin) +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/anncwb/vue-vben-admin) ## Documentation -[Document](https://doc.vben.pro/) +[Document](https://doc.vvbin.cn/) -## Install and Use +## Preparation -1. Get the project code +- [node](http://nodejs.org/) and [git](https://git-scm.com/) - Project development environment +- [Vite](https://vitejs.dev/) - Familiar with vite features +- [Vue3](https://v3.vuejs.org/) - Familiar with Vue basic syntax +- [TypeScript](https://www.typescriptlang.org/) - Familiar with the basic syntax of `TypeScript` +- [Es6+](http://es6.ruanyifeng.com/) - Familiar with es6 basic syntax +- [Vue-Router-Next](https://next.router.vuejs.org/) - Familiar with the basic use of vue-router +- [Ant-Design-Vue](https://antdv.com/docs/vue/introduce-cn/) - ui basic use +- [Mock.js](https://github.com/nuysoft/Mock) - mockjs basic syntax + +## Install and use + +- Get the project code ```bash git clone https://github.com/vbenjs/vue-vben-admin.git ``` -2. Install dependencies +- Install dependencies ```bash cd vue-vben-admin -npm i -g corepack + pnpm install + ``` -3. Run +- run ```bash -pnpm dev +pnpm serve ``` -4. Build +- build ```bash pnpm build ``` -## Change Log +- docker -[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases) +### The dockerFile is located in the project root directory and supports differential deployment -## How to Contribute +#### build image -You are very welcome to join! [Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) or submit a Pull Request. +```bash +docker build -t vue-vben-admin . +``` -**Pull Request Process:** +#### Use environment variables to achieve differentiated container deployment. Specify service endpoint by assigning `VG_BASE_URL`. In the following example, `http://localhost:3333` is used as the back-end service address and the container is mapped to port `6666`: -1. Fork the code -2. Create your branch: `git checkout -b feat/xxxx` -3. Submit your changes: `git commit -am 'feat(function): add xxxxx'` -4. Push your branch: `git push origin feat/xxxx` -5. Submit `pull request` +```bash +docker run --name vue-vben-admin -d -p 6666:80 -e VG_BASE_URL=http://localhost:3333 vue-vben-admin +``` -## Git Contribution Submission Specification +Then you can navigate to `http://localhost:6666` -Reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) +## Change Log -- `feat` Add new features -- `fix` Fix the problem/BUG -- `style` The code style is related and does not affect the running result -- `perf` Optimization/performance improvement -- `refactor` Refactor -- `revert` Undo edit -- `test` Test related -- `docs` Documentation/notes -- `chore` Dependency update/scaffolding configuration modification etc. -- `ci` Continuous integration -- `types` Type definition file changes +[CHANGELOG](./CHANGELOG.zh_CN.md) -## Browser Support +## Project -The `Chrome 80+` browser is recommended for local development +- [vue-vben-admin](https://github.com/anncwb/vue-vben-admin) - full version +- [vue-vben-admin-thin-next](https://github.com/anncwb/vben-admin-thin-next) - Simplified version -Support modern browsers, not IE +## How to contribute -| [Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | -| :-: | :-: | :-: | :-: | -| last 2 versions | last 2 versions | last 2 versions | last 2 versions | +You are very welcome to join![Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) or submit a Pull Request。 -## Maintainer +**Pull Request:** -[@Vben](https://github.com/anncwb) +1. Fork code! +2. Create your own branch: `git checkout -b feat/xxxx` +3. Submit your changes: `git commit -am 'feat(function): add xxxxx'` +4. Push your branch: `git push origin feat/xxxx` +5. submit`pull request` -## Star History +## Git Contribution submission specification -[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date) +- reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) + + - `feat` Add new features + - `fix` Fix the problem/BUG + - `style` Modify the code style/format that does not affect the feature + - `perf` Optimization/performance improvement + - `refactor` Refactor + - `revert` Undo edit + - `test` Test related + - `docs` Documentation/notes + - `chore` Dependency update/scaffolding configuration modification etc. + - `workflow` Workflow improvements + - `ci` Continuous integration + - `types` Type definition file changes + - `wip` In development -## Donate +## Related warehouse -If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support! +If these plugins are helpful to you, you can show support by leaving a star! -![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png) +- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - Used for local and development environment data mock +- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - Used for html template conversion and compression +- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - Used to pack input .gz|.brotil files +- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - Used to quickly generate svg sprite -Paypal Me +## Browser support -## Contributors +The `Chrome 80+` browser is recommended for local development + +Support modern browsers, doesn't include IE - - Contribution Leaderboard - +| [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| :-: | :-: | :-: | :-: | :-: | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | + +## Maintainer - - Contributors - +[@Vben](https://github.com/anncwb) [@Jinmao](https://github.com/jinmao88) -## Discord +## Thanks -- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) +JetBrains Logo (Main) logo. + +## Star History Chart + +[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date) ## License diff --git a/README.zh-CN.md b/README.zh-CN.md index d3193ef65be..077e82f4314 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,157 +1,190 @@ -
- - VbenAdmin Logo - -
-
+
VbenAdmin Logo

[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) -

Vue Vben Admin

+

Vue vben admin

-[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg) - -**中文** | [English](./README.md) | [日本語](./README.ja-JP.md) +**中文** | [English](./README.md) ## 简介 -Vue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的中后台模板,它采用了最新的 Vue 3、Vite、TypeScript 等主流技术开发,开箱即用,可用于中后台前端开发,也适合学习参考。 - -## 升级提示 - -该版本为最新版本 `5.0`,与其他版本不兼容,如果你是新项目,建议使用最新版本。如果你想查看旧版本,请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2) +Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3`,`vite5`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。 ## 特性 -- **最新技术栈**:使用 Vue3/vite 等前端前沿技术开发 -- **TypeScript**:应用程序级 JavaScript 的语言 -- **主题**:提供多套主题色彩,可配置自定义主题 +- **最新技术栈**:使用 Vue3/vite5 等前端前沿技术开发 +- **TypeScript**: 应用程序级 JavaScript 的语言 +- **主题**:可配置的主题 - **国际化**:内置完善的国际化方案 -- **权限**:内置完善的动态路由权限生成方案 +- **Mock 数据** 内置 Mock 数据方案 +- **权限** 内置完善的动态路由权限生成方案 +- **组件** 二次封装了多个常用的组件 ## 预览 -- [Vben Admin](https://vben.pro/) - 完整版中文站点 +- [vue-vben-admin](https://vben.vvbin.cn/) - 完整版中文站点 +- [vue-vben-admin-gh-pages](https://anncwb.github.io/vue-vben-admin/) - 完整版 github 站点 +- [vben-admin-thin-next](https://vben.vvbin.cn/thin/next/) - 简化版中文站点 +- [vben-admin-thin-gh-pages](https://anncwb.github.io/vben-admin-thin-next/) - 简化版 github 站点 -测试账号:vben/123456 +测试账号: vben/123456 -
- VbenAdmin Logo - VbenAdmin Logo - VbenAdmin Logo -
+

+ VbenAdmin Logo + VbenAdmin Logo + VbenAdmin Logo +

### 使用 Gitpod -在 Gitpod(适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码。 +在 Gitpod(适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码. -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin) +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/anncwb/vue-vben-admin) ## 文档 -[文档地址](https://doc.vben.pro/) +[文档地址](https://doc.vvbin.cn/) + +## 准备 + +- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) -项目开发环境 +- [Vite](https://vitejs.dev/) - 熟悉 vite 特性 +- [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法 +- [TypeScript](https://www.typescriptlang.org/) - 熟悉`TypeScript`基本语法 +- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法 +- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用 +- [Ant-Design-Vue](https://antdv.com/docs/vue/introduce-cn/) - ui 基本使用 +- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法 ## 安装使用 -1. 获取项目代码 +- 获取项目代码 ```bash git clone https://github.com/vbenjs/vue-vben-admin.git ``` -2. 安装依赖 +- 安装依赖 ```bash cd vue-vben-admin -npm i -g corepack + pnpm install + ``` -3. 运行 +- 运行 ```bash -pnpm dev +pnpm serve ``` -4. 打包 +- 打包 ```bash pnpm build ``` +- docker + +### dockerFile 位于项目根目录下 并且支持差异化部署 + +#### 构建镜像 + +```bash +docker build -t vue-vben-admin . +``` + +#### 动态使用环境变量实现容器差异化部署,通过不同的 VG_BASE_URL 环境变量,指向不同的后端服务地址,下面例子使用 http://localhost:3333 作为后端服务地址,并且将容器映射到 6666 端口 + +```bash +docker run --name vue-vben-admin -d -p 6666:80 -e VG_BASE_URL=http://localhost:3333 vue-vben-admin +``` + +而后可以打开 http://localhost:6666 访问 + ## 更新日志 -[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases) +[CHANGELOG](./CHANGELOG.zh_CN.md) + +## 项目地址 + +- [vue-vben-admin](https://github.com/anncwb/vue-vben-admin) - 完整版 +- [vue-vben-admin-thin-next](https://github.com/anncwb/vben-admin-thin-next) - 简化版 ## 如何贡献 非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。 -**Pull Request 流程:** +**Pull Request:** -1. Fork 代码 -2. 创建自己的分支:`git checkout -b feature/xxxx` -3. 提交你的修改:`git commit -am 'feat(function): add xxxxx'` -4. 推送您的分支:`git push origin feature/xxxx` -5. 提交 `pull request` +1. Fork 代码! +2. 创建自己的分支: `git checkout -b feat/xxxx` +3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'` +4. 推送您的分支: `git push origin feat/xxxx` +5. 提交`pull request` ## Git 贡献提交规范 -参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) - -- `feat` 增加新功能 -- `fix` 修复问题/BUG -- `style` 代码风格相关无影响运行结果的 -- `perf` 优化/性能提升 -- `refactor` 重构 -- `revert` 撤销修改 -- `test` 测试相关 -- `docs` 文档/注释 -- `chore` 依赖更新/脚手架配置修改等 -- `ci` 持续集成 -- `types` 类型定义文件更改 +- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) + + - `feat` 增加新功能 + - `fix` 修复问题/BUG + - `style` 代码风格相关无影响运行结果的 + - `perf` 优化/性能提升 + - `refactor` 重构 + - `revert` 撤销修改 + - `test` 测试相关 + - `docs` 文档/注释 + - `chore` 依赖更新/脚手架配置修改等 + - `workflow` 工作流改进 + - `ci` 持续集成 + - `types` 类型定义文件更改 + - `wip` 开发中 ## 浏览器支持 -本地开发推荐使用 `Chrome 80+` 浏览器 +本地开发推荐使用`Chrome 80+` 浏览器 -支持现代浏览器,不支持 IE +支持现代浏览器, 不支持 IE -| [Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | -| :-: | :-: | :-: | :-: | -| last 2 versions | last 2 versions | last 2 versions | last 2 versions | +| [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| :-: | :-: | :-: | :-: | :-: | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | -## 维护者 +## 相关仓库 -[@Vben](https://github.com/anncwb) +如果这些插件对你有帮助,可以给一个 star 支持下 -## Star 历史 +- [vite-plugin-mock](https://github.com/anncwb/vite-plugin-mock) - 用于本地及开发环境数据 mock +- [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) - 用于 html 模版转换及压缩 +- [vite-plugin-compression](https://github.com/anncwb/vite-plugin-compression) - 用于打包输出.gz|.brotil 文件 +- [vite-plugin-svg-icons](https://github.com/anncwb/vite-plugin-svg-icons) - 用于快速生成 svg 雪碧图 -[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date) +## 后台整合示例 -## 捐赠 +- [lamp-cloud](https://github.com/zuihou/lamp-cloud) - 基于 SpringCloud Alibaba 的微服务中后台快速开发平台 +- [matecloud](https://github.com/matevip/matecloud) - MateCloud 微服务脚手架,基于 Spring Cloud 2020.0.3、SpringBoot 2.5.3 的全开源平台 -如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持! +## 维护者 -![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png) +[@Vben](https://github.com/anncwb) [@Jinmao](https://github.com/jinmao88) -Paypal Me +## 感谢 -## 贡献者 +JetBrains Logo (Main) logo. - - Contribution Leaderboard - +## 交流 - - Contributors - +`Vue-vben-Admin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供 QQ 交流群使用问题欢迎在群内提问。 -## Discord +- QQ 群 `569291866` -- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) +## Star 历史 + +[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date) -## 许可证 +## License [MIT © Vben-2020](./LICENSE) diff --git a/apps/backend-mock/.env b/apps/backend-mock/.env deleted file mode 100644 index b20c4a65ff6..00000000000 --- a/apps/backend-mock/.env +++ /dev/null @@ -1,3 +0,0 @@ -PORT=5320 -ACCESS_TOKEN_SECRET=access_token_secret -REFRESH_TOKEN_SECRET=refresh_token_secret diff --git a/apps/backend-mock/README.md b/apps/backend-mock/README.md deleted file mode 100644 index 401bda76f5e..00000000000 --- a/apps/backend-mock/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# @vben/backend-mock - -## Description - -Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。 - -## Running the app - -```bash -# development -$ pnpm run start - -# production mode -$ pnpm run build -``` diff --git a/apps/backend-mock/api/auth/codes.ts b/apps/backend-mock/api/auth/codes.ts deleted file mode 100644 index e610b338112..00000000000 --- a/apps/backend-mock/api/auth/codes.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { eventHandler } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { MOCK_CODES } from '~/utils/mock-data'; -import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; - -export default eventHandler((event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - - const codes = - MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? []; - - return useResponseSuccess(codes); -}); diff --git a/apps/backend-mock/api/auth/login.post.ts b/apps/backend-mock/api/auth/login.post.ts deleted file mode 100644 index e23942c46b8..00000000000 --- a/apps/backend-mock/api/auth/login.post.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { defineEventHandler, readBody, setResponseStatus } from 'h3'; -import { - clearRefreshTokenCookie, - setRefreshTokenCookie, -} from '~/utils/cookie-utils'; -import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils'; -import { MOCK_USERS } from '~/utils/mock-data'; -import { - forbiddenResponse, - useResponseError, - useResponseSuccess, -} from '~/utils/response'; - -export default defineEventHandler(async (event) => { - const { password, username } = await readBody(event); - if (!password || !username) { - setResponseStatus(event, 400); - return useResponseError( - 'BadRequestException', - 'Username and password are required', - ); - } - - const findUser = MOCK_USERS.find( - (item) => item.username === username && item.password === password, - ); - - if (!findUser) { - clearRefreshTokenCookie(event); - return forbiddenResponse(event, 'Username or password is incorrect.'); - } - - const accessToken = generateAccessToken(findUser); - const refreshToken = generateRefreshToken(findUser); - - setRefreshTokenCookie(event, refreshToken); - - return useResponseSuccess({ - ...findUser, - accessToken, - }); -}); diff --git a/apps/backend-mock/api/auth/logout.post.ts b/apps/backend-mock/api/auth/logout.post.ts deleted file mode 100644 index 74c8d315199..00000000000 --- a/apps/backend-mock/api/auth/logout.post.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { defineEventHandler } from 'h3'; -import { - clearRefreshTokenCookie, - getRefreshTokenFromCookie, -} from '~/utils/cookie-utils'; -import { useResponseSuccess } from '~/utils/response'; - -export default defineEventHandler(async (event) => { - const refreshToken = getRefreshTokenFromCookie(event); - if (!refreshToken) { - return useResponseSuccess(''); - } - - clearRefreshTokenCookie(event); - - return useResponseSuccess(''); -}); diff --git a/apps/backend-mock/api/auth/refresh.post.ts b/apps/backend-mock/api/auth/refresh.post.ts deleted file mode 100644 index 7d8d3a51e36..00000000000 --- a/apps/backend-mock/api/auth/refresh.post.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { defineEventHandler } from 'h3'; -import { - clearRefreshTokenCookie, - getRefreshTokenFromCookie, - setRefreshTokenCookie, -} from '~/utils/cookie-utils'; -import { generateAccessToken, verifyRefreshToken } from '~/utils/jwt-utils'; -import { MOCK_USERS } from '~/utils/mock-data'; -import { forbiddenResponse } from '~/utils/response'; - -export default defineEventHandler(async (event) => { - const refreshToken = getRefreshTokenFromCookie(event); - if (!refreshToken) { - return forbiddenResponse(event); - } - - clearRefreshTokenCookie(event); - - const userinfo = verifyRefreshToken(refreshToken); - if (!userinfo) { - return forbiddenResponse(event); - } - - const findUser = MOCK_USERS.find( - (item) => item.username === userinfo.username, - ); - if (!findUser) { - return forbiddenResponse(event); - } - const accessToken = generateAccessToken(findUser); - - setRefreshTokenCookie(event, refreshToken); - - return accessToken; -}); diff --git a/apps/backend-mock/api/demo/bigint.ts b/apps/backend-mock/api/demo/bigint.ts deleted file mode 100644 index 00d6c28c530..00000000000 --- a/apps/backend-mock/api/demo/bigint.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { eventHandler, setHeader } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { unAuthorizedResponse } from '~/utils/response'; - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - const data = ` - { - "code": 0, - "message": "success", - "data": [ - { - "id": 123456789012345678901234567890123456789012345678901234567890, - "name": "John Doe", - "age": 30, - "email": "john-doe@demo.com" - }, - { - "id": 987654321098765432109876543210987654321098765432109876543210, - "name": "Jane Smith", - "age": 25, - "email": "jane@demo.com" - } - ] - } - `; - setHeader(event, 'Content-Type', 'application/json'); - return data; -}); diff --git a/apps/backend-mock/api/menu/all.ts b/apps/backend-mock/api/menu/all.ts deleted file mode 100644 index 7923f7ca5b2..00000000000 --- a/apps/backend-mock/api/menu/all.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { eventHandler } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { MOCK_MENUS } from '~/utils/mock-data'; -import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - - const menus = - MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? []; - return useResponseSuccess(menus); -}); diff --git a/apps/backend-mock/api/status.ts b/apps/backend-mock/api/status.ts deleted file mode 100644 index 43782095d73..00000000000 --- a/apps/backend-mock/api/status.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { eventHandler, getQuery, setResponseStatus } from 'h3'; -import { useResponseError } from '~/utils/response'; - -export default eventHandler((event) => { - const { status } = getQuery(event); - setResponseStatus(event, Number(status)); - return useResponseError(`${status}`); -}); diff --git a/apps/backend-mock/api/system/dept/.post.ts b/apps/backend-mock/api/system/dept/.post.ts deleted file mode 100644 index 9a4896afaf2..00000000000 --- a/apps/backend-mock/api/system/dept/.post.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { eventHandler } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { - sleep, - unAuthorizedResponse, - useResponseSuccess, -} from '~/utils/response'; - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - await sleep(600); - return useResponseSuccess(null); -}); diff --git a/apps/backend-mock/api/system/dept/[id].delete.ts b/apps/backend-mock/api/system/dept/[id].delete.ts deleted file mode 100644 index eac0f584692..00000000000 --- a/apps/backend-mock/api/system/dept/[id].delete.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { eventHandler } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { - sleep, - unAuthorizedResponse, - useResponseSuccess, -} from '~/utils/response'; - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - await sleep(1000); - return useResponseSuccess(null); -}); diff --git a/apps/backend-mock/api/system/dept/[id].put.ts b/apps/backend-mock/api/system/dept/[id].put.ts deleted file mode 100644 index 6805e1395a1..00000000000 --- a/apps/backend-mock/api/system/dept/[id].put.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { eventHandler } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { - sleep, - unAuthorizedResponse, - useResponseSuccess, -} from '~/utils/response'; - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - await sleep(2000); - return useResponseSuccess(null); -}); diff --git a/apps/backend-mock/api/system/dept/list.ts b/apps/backend-mock/api/system/dept/list.ts deleted file mode 100644 index a649a0d2f92..00000000000 --- a/apps/backend-mock/api/system/dept/list.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { faker } from '@faker-js/faker'; -import { eventHandler } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; - -const formatterCN = new Intl.DateTimeFormat('zh-CN', { - timeZone: 'Asia/Shanghai', - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', -}); - -function generateMockDataList(count: number) { - const dataList = []; - - for (let i = 0; i < count; i++) { - const dataItem: Record = { - id: faker.string.uuid(), - pid: 0, - name: faker.commerce.department(), - status: faker.helpers.arrayElement([0, 1]), - createTime: formatterCN.format( - faker.date.between({ from: '2021-01-01', to: '2022-12-31' }), - ), - remark: faker.lorem.sentence(), - }; - if (faker.datatype.boolean()) { - dataItem.children = Array.from( - { length: faker.number.int({ min: 1, max: 5 }) }, - () => ({ - id: faker.string.uuid(), - pid: dataItem.id, - name: faker.commerce.department(), - status: faker.helpers.arrayElement([0, 1]), - createTime: formatterCN.format( - faker.date.between({ from: '2023-01-01', to: '2023-12-31' }), - ), - remark: faker.lorem.sentence(), - }), - ); - } - dataList.push(dataItem); - } - - return dataList; -} - -const mockData = generateMockDataList(10); - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - - const listData = structuredClone(mockData); - - return useResponseSuccess(listData); -}); diff --git a/apps/backend-mock/api/system/menu/list.ts b/apps/backend-mock/api/system/menu/list.ts deleted file mode 100644 index ce96bb14e7d..00000000000 --- a/apps/backend-mock/api/system/menu/list.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { eventHandler } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { MOCK_MENU_LIST } from '~/utils/mock-data'; -import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - - return useResponseSuccess(MOCK_MENU_LIST); -}); diff --git a/apps/backend-mock/api/system/menu/name-exists.ts b/apps/backend-mock/api/system/menu/name-exists.ts deleted file mode 100644 index 7d5551b3b40..00000000000 --- a/apps/backend-mock/api/system/menu/name-exists.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { eventHandler, getQuery } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { MOCK_MENU_LIST } from '~/utils/mock-data'; -import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; - -const namesMap: Record = {}; - -function getNames(menus: any[]) { - menus.forEach((menu) => { - namesMap[menu.name] = String(menu.id); - if (menu.children) { - getNames(menu.children); - } - }); -} -getNames(MOCK_MENU_LIST); - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - const { id, name } = getQuery(event); - - return (name as string) in namesMap && - (!id || namesMap[name as string] !== String(id)) - ? useResponseSuccess(true) - : useResponseSuccess(false); -}); diff --git a/apps/backend-mock/api/system/menu/path-exists.ts b/apps/backend-mock/api/system/menu/path-exists.ts deleted file mode 100644 index f3c3be997cc..00000000000 --- a/apps/backend-mock/api/system/menu/path-exists.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { eventHandler, getQuery } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { MOCK_MENU_LIST } from '~/utils/mock-data'; -import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; - -const pathMap: Record = { '/': 0 }; - -function getPaths(menus: any[]) { - menus.forEach((menu) => { - pathMap[menu.path] = String(menu.id); - if (menu.children) { - getPaths(menu.children); - } - }); -} -getPaths(MOCK_MENU_LIST); - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - const { id, path } = getQuery(event); - - return (path as string) in pathMap && - (!id || pathMap[path as string] !== String(id)) - ? useResponseSuccess(true) - : useResponseSuccess(false); -}); diff --git a/apps/backend-mock/api/system/role/list.ts b/apps/backend-mock/api/system/role/list.ts deleted file mode 100644 index bad29a513e3..00000000000 --- a/apps/backend-mock/api/system/role/list.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { faker } from '@faker-js/faker'; -import { eventHandler, getQuery } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data'; -import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; - -const formatterCN = new Intl.DateTimeFormat('zh-CN', { - timeZone: 'Asia/Shanghai', - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', -}); - -const menuIds = getMenuIds(MOCK_MENU_LIST); - -function generateMockDataList(count: number) { - const dataList = []; - - for (let i = 0; i < count; i++) { - const dataItem: Record = { - id: faker.string.uuid(), - name: faker.commerce.product(), - status: faker.helpers.arrayElement([0, 1]), - createTime: formatterCN.format( - faker.date.between({ from: '2022-01-01', to: '2025-01-01' }), - ), - permissions: faker.helpers.arrayElements(menuIds), - remark: faker.lorem.sentence(), - }; - - dataList.push(dataItem); - } - - return dataList; -} - -const mockData = generateMockDataList(100); - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - - const { - page = 1, - pageSize = 20, - name, - id, - remark, - startTime, - endTime, - status, - } = getQuery(event); - let listData = structuredClone(mockData); - if (name) { - listData = listData.filter((item) => - item.name.toLowerCase().includes(String(name).toLowerCase()), - ); - } - if (id) { - listData = listData.filter((item) => - item.id.toLowerCase().includes(String(id).toLowerCase()), - ); - } - if (remark) { - listData = listData.filter((item) => - item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()), - ); - } - if (startTime) { - listData = listData.filter((item) => item.createTime >= startTime); - } - if (endTime) { - listData = listData.filter((item) => item.createTime <= endTime); - } - if (['0', '1'].includes(status as string)) { - listData = listData.filter((item) => item.status === Number(status)); - } - return usePageResponseSuccess(page as string, pageSize as string, listData); -}); diff --git a/apps/backend-mock/api/table/list.ts b/apps/backend-mock/api/table/list.ts deleted file mode 100644 index 6664b583e4e..00000000000 --- a/apps/backend-mock/api/table/list.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { faker } from '@faker-js/faker'; -import { eventHandler, getQuery } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { - sleep, - unAuthorizedResponse, - usePageResponseSuccess, -} from '~/utils/response'; - -function generateMockDataList(count: number) { - const dataList = []; - - for (let i = 0; i < count; i++) { - const dataItem = { - id: faker.string.uuid(), - imageUrl: faker.image.avatar(), - imageUrl2: faker.image.avatar(), - open: faker.datatype.boolean(), - status: faker.helpers.arrayElement(['success', 'error', 'warning']), - productName: faker.commerce.productName(), - price: faker.commerce.price(), - currency: faker.finance.currencyCode(), - quantity: faker.number.int({ min: 1, max: 100 }), - available: faker.datatype.boolean(), - category: faker.commerce.department(), - releaseDate: faker.date.past(), - rating: faker.number.float({ min: 1, max: 5 }), - description: faker.commerce.productDescription(), - weight: faker.number.float({ min: 0.1, max: 10 }), - color: faker.color.human(), - inProduction: faker.datatype.boolean(), - tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()), - }; - - dataList.push(dataItem); - } - - return dataList; -} - -const mockData = generateMockDataList(100); - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - - await sleep(600); - - const { page, pageSize, sortBy, sortOrder } = getQuery(event); - // 规范化分页参数,处理 string[] - const pageRaw = Array.isArray(page) ? page[0] : page; - const pageSizeRaw = Array.isArray(pageSize) ? pageSize[0] : pageSize; - const pageNumber = Math.max( - 1, - Number.parseInt(String(pageRaw ?? '1'), 10) || 1, - ); - const pageSizeNumber = Math.min( - 100, - Math.max(1, Number.parseInt(String(pageSizeRaw ?? '10'), 10) || 10), - ); - const listData = structuredClone(mockData); - - // 规范化 query 入参,兼容 string[] - const sortKeyRaw = Array.isArray(sortBy) ? sortBy[0] : sortBy; - const sortOrderRaw = Array.isArray(sortOrder) ? sortOrder[0] : sortOrder; - // 检查 sortBy 是否是 listData 元素的合法属性键 - if ( - typeof sortKeyRaw === 'string' && - listData[0] && - Object.prototype.hasOwnProperty.call(listData[0], sortKeyRaw) - ) { - // 定义数组元素的类型 - type ItemType = (typeof listData)[0]; - const sortKey = sortKeyRaw as keyof ItemType; // 将 sortBy 断言为合法键 - const isDesc = sortOrderRaw === 'desc'; - listData.sort((a, b) => { - const aValue = a[sortKey] as unknown; - const bValue = b[sortKey] as unknown; - - let result = 0; - - if (typeof aValue === 'number' && typeof bValue === 'number') { - result = aValue - bValue; - } else if (aValue instanceof Date && bValue instanceof Date) { - result = aValue.getTime() - bValue.getTime(); - } else if (typeof aValue === 'boolean' && typeof bValue === 'boolean') { - if (aValue === bValue) { - result = 0; - } else { - result = aValue ? 1 : -1; - } - } else { - const aStr = String(aValue); - const bStr = String(bValue); - const aNum = Number(aStr); - const bNum = Number(bStr); - result = - Number.isFinite(aNum) && Number.isFinite(bNum) - ? aNum - bNum - : aStr.localeCompare(bStr, undefined, { - numeric: true, - sensitivity: 'base', - }); - } - - return isDesc ? -result : result; - }); - } - - return usePageResponseSuccess( - String(pageNumber), - String(pageSizeNumber), - listData, - ); -}); diff --git a/apps/backend-mock/api/test.get.ts b/apps/backend-mock/api/test.get.ts deleted file mode 100644 index dc2ceef799f..00000000000 --- a/apps/backend-mock/api/test.get.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { defineEventHandler } from 'h3'; - -export default defineEventHandler(() => 'Test get handler'); diff --git a/apps/backend-mock/api/test.post.ts b/apps/backend-mock/api/test.post.ts deleted file mode 100644 index 0e9e337a8b3..00000000000 --- a/apps/backend-mock/api/test.post.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { defineEventHandler } from 'h3'; - -export default defineEventHandler(() => 'Test post handler'); diff --git a/apps/backend-mock/api/upload.ts b/apps/backend-mock/api/upload.ts deleted file mode 100644 index 436b63cbfdd..00000000000 --- a/apps/backend-mock/api/upload.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { eventHandler } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; - -export default eventHandler((event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - return useResponseSuccess({ - url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp', - }); - // return useResponseError("test") -}); diff --git a/apps/backend-mock/api/user/info.ts b/apps/backend-mock/api/user/info.ts deleted file mode 100644 index 138cb433157..00000000000 --- a/apps/backend-mock/api/user/info.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { eventHandler } from 'h3'; -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; - -export default eventHandler((event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - return useResponseSuccess(userinfo); -}); diff --git a/apps/backend-mock/error.ts b/apps/backend-mock/error.ts deleted file mode 100644 index e20beac4e5d..00000000000 --- a/apps/backend-mock/error.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { NitroErrorHandler } from 'nitropack'; - -const errorHandler: NitroErrorHandler = function (error, event) { - event.node.res.end(`[Error Handler] ${error.stack}`); -}; - -export default errorHandler; diff --git a/apps/backend-mock/middleware/1.api.ts b/apps/backend-mock/middleware/1.api.ts deleted file mode 100644 index 339cda4db97..00000000000 --- a/apps/backend-mock/middleware/1.api.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { defineEventHandler } from 'h3'; -import { forbiddenResponse, sleep } from '~/utils/response'; - -export default defineEventHandler(async (event) => { - event.node.res.setHeader( - 'Access-Control-Allow-Origin', - event.headers.get('Origin') ?? '*', - ); - if (event.method === 'OPTIONS') { - event.node.res.statusCode = 204; - event.node.res.statusMessage = 'No Content.'; - return 'OK'; - } else if ( - ['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) && - event.path.startsWith('/api/system/') - ) { - await sleep(Math.floor(Math.random() * 2000)); - return forbiddenResponse(event, '演示环境,禁止修改'); - } -}); diff --git a/apps/backend-mock/nitro.config.ts b/apps/backend-mock/nitro.config.ts deleted file mode 100644 index c0fc13e2ef3..00000000000 --- a/apps/backend-mock/nitro.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import errorHandler from './error'; - -process.env.COMPATIBILITY_DATE = new Date().toISOString(); -export default defineNitroConfig({ - devErrorHandler: errorHandler, - errorHandler: '~/error', - routeRules: { - '/api/**': { - cors: true, - headers: { - 'Access-Control-Allow-Credentials': 'true', - 'Access-Control-Allow-Headers': - 'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With', - 'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Expose-Headers': '*', - }, - }, - }, -}); diff --git a/apps/backend-mock/package.json b/apps/backend-mock/package.json deleted file mode 100644 index cc0b8d5334a..00000000000 --- a/apps/backend-mock/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "@vben/backend-mock", - "version": "0.0.1", - "description": "", - "private": true, - "license": "MIT", - "author": "", - "scripts": { - "build": "nitro build", - "start": "nitro dev" - }, - "dependencies": { - "@faker-js/faker": "catalog:", - "jsonwebtoken": "catalog:", - "nitropack": "catalog:" - }, - "devDependencies": { - "@types/jsonwebtoken": "catalog:", - "h3": "catalog:" - } -} diff --git a/apps/backend-mock/routes/[...].ts b/apps/backend-mock/routes/[...].ts deleted file mode 100644 index 5a22563dc1a..00000000000 --- a/apps/backend-mock/routes/[...].ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineEventHandler } from 'h3'; - -export default defineEventHandler(() => { - return ` -

Hello Vben Admin

-

Mock service is starting

- -`; -}); diff --git a/apps/backend-mock/tsconfig.build.json b/apps/backend-mock/tsconfig.build.json deleted file mode 100644 index 64f86c6bd2b..00000000000 --- a/apps/backend-mock/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] -} diff --git a/apps/backend-mock/tsconfig.json b/apps/backend-mock/tsconfig.json deleted file mode 100644 index 43008af1c71..00000000000 --- a/apps/backend-mock/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./.nitro/types/tsconfig.json" -} diff --git a/apps/backend-mock/utils/cookie-utils.ts b/apps/backend-mock/utils/cookie-utils.ts deleted file mode 100644 index 187ce2f0018..00000000000 --- a/apps/backend-mock/utils/cookie-utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { EventHandlerRequest, H3Event } from 'h3'; - -import { deleteCookie, getCookie, setCookie } from 'h3'; - -export function clearRefreshTokenCookie(event: H3Event) { - deleteCookie(event, 'jwt', { - httpOnly: true, - sameSite: 'none', - secure: true, - }); -} - -export function setRefreshTokenCookie( - event: H3Event, - refreshToken: string, -) { - setCookie(event, 'jwt', refreshToken, { - httpOnly: true, - maxAge: 24 * 60 * 60, // unit: seconds - sameSite: 'none', - secure: true, - }); -} - -export function getRefreshTokenFromCookie(event: H3Event) { - const refreshToken = getCookie(event, 'jwt'); - return refreshToken; -} diff --git a/apps/backend-mock/utils/jwt-utils.ts b/apps/backend-mock/utils/jwt-utils.ts deleted file mode 100644 index 71858307087..00000000000 --- a/apps/backend-mock/utils/jwt-utils.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { EventHandlerRequest, H3Event } from 'h3'; - -import type { UserInfo } from './mock-data'; - -import { getHeader } from 'h3'; -import jwt from 'jsonwebtoken'; - -import { MOCK_USERS } from './mock-data'; - -// TODO: Replace with your own secret key -const ACCESS_TOKEN_SECRET = 'access_token_secret'; -const REFRESH_TOKEN_SECRET = 'refresh_token_secret'; - -export interface UserPayload extends UserInfo { - iat: number; - exp: number; -} - -export function generateAccessToken(user: UserInfo) { - return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' }); -} - -export function generateRefreshToken(user: UserInfo) { - return jwt.sign(user, REFRESH_TOKEN_SECRET, { - expiresIn: '30d', - }); -} - -export function verifyAccessToken( - event: H3Event, -): null | Omit { - const authHeader = getHeader(event, 'Authorization'); - if (!authHeader?.startsWith('Bearer')) { - return null; - } - - const tokenParts = authHeader.split(' '); - if (tokenParts.length !== 2) { - return null; - } - const token = tokenParts[1] as string; - try { - const decoded = jwt.verify( - token, - ACCESS_TOKEN_SECRET, - ) as unknown as UserPayload; - - const username = decoded.username; - const user = MOCK_USERS.find((item) => item.username === username); - if (!user) { - return null; - } - const { password: _pwd, ...userinfo } = user; - return userinfo; - } catch { - return null; - } -} - -export function verifyRefreshToken( - token: string, -): null | Omit { - try { - const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload; - const username = decoded.username; - const user = MOCK_USERS.find( - (item) => item.username === username, - ) as UserInfo; - if (!user) { - return null; - } - const { password: _pwd, ...userinfo } = user; - return userinfo; - } catch { - return null; - } -} diff --git a/apps/backend-mock/utils/mock-data.ts b/apps/backend-mock/utils/mock-data.ts deleted file mode 100644 index 192f30a0068..00000000000 --- a/apps/backend-mock/utils/mock-data.ts +++ /dev/null @@ -1,390 +0,0 @@ -export interface UserInfo { - id: number; - password: string; - realName: string; - roles: string[]; - username: string; - homePath?: string; -} - -export const MOCK_USERS: UserInfo[] = [ - { - id: 0, - password: '123456', - realName: 'Vben', - roles: ['super'], - username: 'vben', - }, - { - id: 1, - password: '123456', - realName: 'Admin', - roles: ['admin'], - username: 'admin', - homePath: '/workspace', - }, - { - id: 2, - password: '123456', - realName: 'Jack', - roles: ['user'], - username: 'jack', - homePath: '/analytics', - }, -]; - -export const MOCK_CODES = [ - // super - { - codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'], - username: 'vben', - }, - { - // admin - codes: ['AC_100010', 'AC_100020', 'AC_100030'], - username: 'admin', - }, - { - // user - codes: ['AC_1000001', 'AC_1000002'], - username: 'jack', - }, -]; - -const dashboardMenus = [ - { - meta: { - order: -1, - title: 'page.dashboard.title', - }, - name: 'Dashboard', - path: '/dashboard', - redirect: '/analytics', - children: [ - { - name: 'Analytics', - path: '/analytics', - component: '/dashboard/analytics/index', - meta: { - affixTab: true, - title: 'page.dashboard.analytics', - }, - }, - { - name: 'Workspace', - path: '/workspace', - component: '/dashboard/workspace/index', - meta: { - title: 'page.dashboard.workspace', - }, - }, - ], - }, -]; - -const createDemosMenus = (role: 'admin' | 'super' | 'user') => { - const roleWithMenus = { - admin: { - component: '/demos/access/admin-visible', - meta: { - icon: 'mdi:button-cursor', - title: 'demos.access.adminVisible', - }, - name: 'AccessAdminVisibleDemo', - path: '/demos/access/admin-visible', - }, - super: { - component: '/demos/access/super-visible', - meta: { - icon: 'mdi:button-cursor', - title: 'demos.access.superVisible', - }, - name: 'AccessSuperVisibleDemo', - path: '/demos/access/super-visible', - }, - user: { - component: '/demos/access/user-visible', - meta: { - icon: 'mdi:button-cursor', - title: 'demos.access.userVisible', - }, - name: 'AccessUserVisibleDemo', - path: '/demos/access/user-visible', - }, - }; - - return [ - { - meta: { - icon: 'ic:baseline-view-in-ar', - keepAlive: true, - order: 1000, - title: 'demos.title', - }, - name: 'Demos', - path: '/demos', - redirect: '/demos/access', - children: [ - { - name: 'AccessDemos', - path: '/demosaccess', - meta: { - icon: 'mdi:cloud-key-outline', - title: 'demos.access.backendPermissions', - }, - redirect: '/demos/access/page-control', - children: [ - { - name: 'AccessPageControlDemo', - path: '/demos/access/page-control', - component: '/demos/access/index', - meta: { - icon: 'mdi:page-previous-outline', - title: 'demos.access.pageAccess', - }, - }, - { - name: 'AccessButtonControlDemo', - path: '/demos/access/button-control', - component: '/demos/access/button-control', - meta: { - icon: 'mdi:button-cursor', - title: 'demos.access.buttonControl', - }, - }, - { - name: 'AccessMenuVisible403Demo', - path: '/demos/access/menu-visible-403', - component: '/demos/access/menu-visible-403', - meta: { - authority: ['no-body'], - icon: 'mdi:button-cursor', - menuVisibleWithForbidden: true, - title: 'demos.access.menuVisible403', - }, - }, - roleWithMenus[role], - ], - }, - ], - }, - ]; -}; - -export const MOCK_MENUS = [ - { - menus: [...dashboardMenus, ...createDemosMenus('super')], - username: 'vben', - }, - { - menus: [...dashboardMenus, ...createDemosMenus('admin')], - username: 'admin', - }, - { - menus: [...dashboardMenus, ...createDemosMenus('user')], - username: 'jack', - }, -]; - -export const MOCK_MENU_LIST = [ - { - id: 1, - name: 'Workspace', - status: 1, - type: 'menu', - icon: 'mdi:dashboard', - path: '/workspace', - component: '/dashboard/workspace/index', - meta: { - icon: 'carbon:workspace', - title: 'page.dashboard.workspace', - affixTab: true, - order: 0, - }, - }, - { - id: 2, - meta: { - icon: 'carbon:settings', - order: 9997, - title: 'system.title', - badge: 'new', - badgeType: 'normal', - badgeVariants: 'primary', - }, - status: 1, - type: 'catalog', - name: 'System', - path: '/system', - children: [ - { - id: 201, - pid: 2, - path: '/system/menu', - name: 'SystemMenu', - authCode: 'System:Menu:List', - status: 1, - type: 'menu', - meta: { - icon: 'carbon:menu', - title: 'system.menu.title', - }, - component: '/system/menu/list', - children: [ - { - id: 20_101, - pid: 201, - name: 'SystemMenuCreate', - status: 1, - type: 'button', - authCode: 'System:Menu:Create', - meta: { title: 'common.create' }, - }, - { - id: 20_102, - pid: 201, - name: 'SystemMenuEdit', - status: 1, - type: 'button', - authCode: 'System:Menu:Edit', - meta: { title: 'common.edit' }, - }, - { - id: 20_103, - pid: 201, - name: 'SystemMenuDelete', - status: 1, - type: 'button', - authCode: 'System:Menu:Delete', - meta: { title: 'common.delete' }, - }, - ], - }, - { - id: 202, - pid: 2, - path: '/system/dept', - name: 'SystemDept', - status: 1, - type: 'menu', - authCode: 'System:Dept:List', - meta: { - icon: 'carbon:container-services', - title: 'system.dept.title', - }, - component: '/system/dept/list', - children: [ - { - id: 20_401, - pid: 201, - name: 'SystemDeptCreate', - status: 1, - type: 'button', - authCode: 'System:Dept:Create', - meta: { title: 'common.create' }, - }, - { - id: 20_402, - pid: 201, - name: 'SystemDeptEdit', - status: 1, - type: 'button', - authCode: 'System:Dept:Edit', - meta: { title: 'common.edit' }, - }, - { - id: 20_403, - pid: 201, - name: 'SystemDeptDelete', - status: 1, - type: 'button', - authCode: 'System:Dept:Delete', - meta: { title: 'common.delete' }, - }, - ], - }, - ], - }, - { - id: 9, - meta: { - badgeType: 'dot', - order: 9998, - title: 'demos.vben.title', - icon: 'carbon:data-center', - }, - name: 'Project', - path: '/vben-admin', - type: 'catalog', - status: 1, - children: [ - { - id: 901, - pid: 9, - name: 'VbenDocument', - path: '/vben-admin/document', - component: 'IFrameView', - type: 'embedded', - status: 1, - meta: { - icon: 'carbon:book', - iframeSrc: 'https://doc.vben.pro/', - title: 'demos.vben.document', - }, - }, - { - id: 902, - pid: 9, - name: 'VbenGithub', - path: '/vben-admin/github', - component: 'IFrameView', - type: 'link', - status: 1, - meta: { - icon: 'carbon:logo-github', - link: 'https://github.com/vbenjs/vue-vben-admin', - title: 'Github', - }, - }, - { - id: 903, - pid: 9, - name: 'VbenAntdv', - path: '/vben-admin/antdv', - component: 'IFrameView', - type: 'link', - status: 0, - meta: { - icon: 'carbon:hexagon-vertical-solid', - badgeType: 'dot', - link: 'https://ant.vben.pro/', - title: 'demos.vben.antdv', - }, - }, - ], - }, - { - id: 10, - component: '_core/about/index', - type: 'menu', - status: 1, - meta: { - icon: 'lucide:copyright', - order: 9999, - title: 'demos.vben.about', - }, - name: 'About', - path: '/about', - }, -]; - -export function getMenuIds(menus: any[]) { - const ids: number[] = []; - menus.forEach((item) => { - ids.push(item.id); - if (item.children && item.children.length > 0) { - ids.push(...getMenuIds(item.children)); - } - }); - return ids; -} diff --git a/apps/backend-mock/utils/response.ts b/apps/backend-mock/utils/response.ts deleted file mode 100644 index 2d4242e9884..00000000000 --- a/apps/backend-mock/utils/response.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { EventHandlerRequest, H3Event } from 'h3'; - -import { setResponseStatus } from 'h3'; - -export function useResponseSuccess(data: T) { - return { - code: 0, - data, - error: null, - message: 'ok', - }; -} - -export function usePageResponseSuccess( - page: number | string, - pageSize: number | string, - list: T[], - { message = 'ok' } = {}, -) { - const pageData = pagination( - Number.parseInt(`${page}`), - Number.parseInt(`${pageSize}`), - list, - ); - - return { - ...useResponseSuccess({ - items: pageData, - total: list.length, - }), - message, - }; -} - -export function useResponseError(message: string, error: any = null) { - return { - code: -1, - data: null, - error, - message, - }; -} - -export function forbiddenResponse( - event: H3Event, - message = 'Forbidden Exception', -) { - setResponseStatus(event, 403); - return useResponseError(message, message); -} - -export function unAuthorizedResponse(event: H3Event) { - setResponseStatus(event, 401); - return useResponseError('Unauthorized Exception', 'Unauthorized Exception'); -} - -export function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -export function pagination( - pageNo: number, - pageSize: number, - array: T[], -): T[] { - const offset = (pageNo - 1) * Number(pageSize); - return offset + Number(pageSize) >= array.length - ? array.slice(offset) - : array.slice(offset, offset + Number(pageSize)); -} diff --git a/apps/portal-view/.gitkeep b/apps/portal-view/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/test-server/README.md b/apps/test-server/README.md new file mode 100644 index 00000000000..14298dfc4b0 --- /dev/null +++ b/apps/test-server/README.md @@ -0,0 +1,15 @@ +# Test Server + +It is used to start the test interface service, which can test the upload, websocket, login and other interfaces. + +## Usage + +```bash + +cd ./test/server + +pnpm install + +pnpm run start + +``` diff --git a/apps/test-server/controller/FileController.ts b/apps/test-server/controller/FileController.ts new file mode 100644 index 00000000000..cf6d90d5bd4 --- /dev/null +++ b/apps/test-server/controller/FileController.ts @@ -0,0 +1,18 @@ +import FileService from '../service/FileService'; + +class FileController { + private service: FileService = new FileService(); + + upload = async (ctx) => { + const files = ctx.request.files.file; + console.log(files); + + if (files.length === undefined) { + this.service.upload(ctx, files, false); + } else { + this.service.upload(ctx, files, true); + } + }; +} + +export default new FileController(); diff --git a/apps/test-server/controller/UserController.ts b/apps/test-server/controller/UserController.ts new file mode 100644 index 00000000000..db815c2f25d --- /dev/null +++ b/apps/test-server/controller/UserController.ts @@ -0,0 +1,15 @@ +import UserService from '../service/UserService'; + +class UserController { + private service: UserService = new UserService(); + + login = async (ctx) => { + ctx.body = await this.service.login(); + }; + + getUserInfoById = async (ctx) => { + ctx.body = await this.service.getUserInfoById(); + }; +} + +export default new UserController(); diff --git a/apps/test-server/ecosystem.config.cjs b/apps/test-server/ecosystem.config.cjs new file mode 100644 index 00000000000..a31e457e652 --- /dev/null +++ b/apps/test-server/ecosystem.config.cjs @@ -0,0 +1,18 @@ +const { name } = require('./package.json'); +const path = require('path'); + +module.exports = { + apps: [ + { + name, + script: path.resolve(__dirname, './dist/index.js'), + instances: require('os').cpus().length, + autorestart: true, + watch: true, + env_production: { + NODE_ENV: 'production', + PORT: 8080, + }, + }, + ], +}; diff --git a/apps/test-server/index.ts b/apps/test-server/index.ts new file mode 100644 index 00000000000..6c92d76667e --- /dev/null +++ b/apps/test-server/index.ts @@ -0,0 +1,63 @@ +import Koa from 'koa'; +import path from 'path'; +import Router from 'koa-router'; +import body from 'koa-body'; +import cors from 'koa2-cors'; +import koaStatic from 'koa-static'; +import websockify from 'koa-websocket'; +import route from 'koa-route'; + +import AppRoutes from './routes'; + +const PORT = 3300; + +const app = websockify(new Koa()); + +app.ws.use(function (ctx, next) { + ctx.websocket.send('connection succeeded!'); + return next(ctx); +}); + +app.ws.use( + route.all('/test', function (ctx) { + // ctx.websocket.send('Hello World'); + ctx.websocket.on('message', function (message) { + // do something with the message from client + + if (message !== 'ping') { + const data = JSON.stringify({ + id: Math.ceil(Math.random() * 1000), + time: new Date().getTime(), + res: `${message}`, + }); + ctx.websocket.send(data); + } + console.log(message); + }); + }), +); + +const router = new Router(); + +// router +AppRoutes.forEach((route) => router[route.method](route.path, route.action)); + +app.use(cors()); +app.use( + body({ + encoding: 'gzip', + multipart: true, + formidable: { + // uploadDir: path.join(__dirname, '/upload/'), // 设置文件上传目录 + keepExtensions: true, + maxFieldsSize: 20 * 1024 * 1024, + }, + }), +); +app.use(router.routes()); +app.use(router.allowedMethods()); +app.use(koaStatic(path.join(__dirname))); + +app.listen(PORT, () => { + console.log(`Application started successfully: http://localhost:${PORT}`); +}); diff --git a/apps/test-server/nodemon.json b/apps/test-server/nodemon.json new file mode 100644 index 00000000000..59fa5afb02c --- /dev/null +++ b/apps/test-server/nodemon.json @@ -0,0 +1,8 @@ +{ + "watch": ["src"], + "ext": "ts", + "exec": "ts-node -r tsconfig-paths/register index.ts", + "events": { + "restart": "clear" + } +} diff --git a/apps/test-server/package.json b/apps/test-server/package.json new file mode 100644 index 00000000000..d2f3177c01a --- /dev/null +++ b/apps/test-server/package.json @@ -0,0 +1,36 @@ +{ + "name": "server", + "version": "1.0.0", + "license": "MIT", + "scripts": { + "compile": "rimraf ./dist && tsup ./index.ts --dts --format cjs,esm ", + "prod": "npx pm2 start ecosystem.config.cjs --env production", + "restart": "pm2 restart ecosystem.config.cjs --env production", + "start": "nodemon", + "stop": "npx pm2 stop ecosystem.config.cjs" + }, + "dependencies": { + "fs-extra": "^11.1.1", + "koa": "^2.14.2", + "koa-body": "^6.0.1", + "koa-bodyparser": "^4.4.1", + "koa-route": "^3.2.0", + "koa-router": "^12.0.0", + "koa-static": "^5.0.0", + "koa-websocket": "^7.0.0", + "koa2-cors": "^2.0.6" + }, + "devDependencies": { + "@types/koa": "^2.13.6", + "@types/koa-bodyparser": "^5.0.2", + "@types/koa-router": "^7.4.4", + "@types/node": "^20.4.0", + "nodemon": "^2.0.22", + "pm2": "^5.3.0", + "rimraf": "^5.0.1", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "tsup": "^7.1.0", + "typescript": "^5.1.6" + } +} diff --git a/apps/test-server/routes.ts b/apps/test-server/routes.ts new file mode 100644 index 00000000000..7fe64759832 --- /dev/null +++ b/apps/test-server/routes.ts @@ -0,0 +1,23 @@ +import UserController from './controller/UserController'; +import FileController from './controller/FileController'; + +export default [ + // user + { + path: '/login', + method: 'post', + action: UserController.login, + }, + { + path: '/getUserInfoById', + method: 'get', + action: UserController.getUserInfoById, + }, + + // file + { + path: '/upload', + method: 'post', + action: FileController.upload, + }, +]; diff --git a/apps/test-server/service/FileService.ts b/apps/test-server/service/FileService.ts new file mode 100644 index 00000000000..6ad4918562f --- /dev/null +++ b/apps/test-server/service/FileService.ts @@ -0,0 +1,54 @@ +import path from 'path'; +import fs from 'fs-extra'; + +const uploadUrl = 'http://localhost:3300/static/upload'; +const filePath = path.join(__dirname, '../static/upload/'); + +fs.ensureDir(filePath); +export default class FileService { + async upload(ctx, files, isMultiple) { + let fileReader, fileResource, writeStream; + + const fileFunc = function (file) { + fileReader = fs.createReadStream(file.filepath); + fileResource = filePath + `/${file.originalFilename}`; + console.log(fileResource); + + writeStream = fs.createWriteStream(fileResource); + fileReader.pipe(writeStream); + }; + + const returnFunc = function (flag) { + if (flag) { + let url = ''; + for (let i = 0; i < files.length; i++) { + url += uploadUrl + `/${files[i].originalFilename},`; + } + url = url.replace(/,$/gi, ''); + ctx.body = { + url: url, + code: 0, + message: 'upload Success!', + }; + } else { + ctx.body = { + url: uploadUrl + `/${files.originalFilename}`, + code: 0, + message: 'upload Success!', + }; + } + }; + console.log(isMultiple, files.length); + + if (isMultiple) { + for (let i = 0; i < files.length; i++) { + const f1 = files[i]; + fileFunc(f1); + } + } else { + fileFunc(files); + } + fs.ensureDir(filePath); + returnFunc(isMultiple); + } +} diff --git a/apps/test-server/service/UserService.ts b/apps/test-server/service/UserService.ts new file mode 100644 index 00000000000..0c395e52e7e --- /dev/null +++ b/apps/test-server/service/UserService.ts @@ -0,0 +1,25 @@ +import { Result } from '../utils'; + +const fakeUserInfo = { + userId: '1', + username: 'vben', + realName: 'Vben Admin', + desc: 'manager', + password: '123456', + token: 'fakeToken1', + roles: [ + { + roleName: 'Super Admin', + value: 'super', + }, + ], +}; +export default class UserService { + async login() { + return Result.success(fakeUserInfo); + } + + async getUserInfoById() { + return Result.success(fakeUserInfo); + } +} diff --git a/apps/test-server/tsconfig.json b/apps/test-server/tsconfig.json new file mode 100644 index 00000000000..3560460eba5 --- /dev/null +++ b/apps/test-server/tsconfig.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/ts-config/node-server.json", + "compilerOptions": { + "noImplicitAny": false + } +} diff --git a/apps/test-server/utils.ts b/apps/test-server/utils.ts new file mode 100644 index 00000000000..7fd0b3fadb9 --- /dev/null +++ b/apps/test-server/utils.ts @@ -0,0 +1,9 @@ +export class Result { + static success(data: any) { + return { + code: 0, + success: true, + result: data, + }; + } +} diff --git a/apps/web-antd/.env b/apps/web-antd/.env deleted file mode 100644 index 19735f36fda..00000000000 --- a/apps/web-antd/.env +++ /dev/null @@ -1,8 +0,0 @@ -# 应用标题 -VITE_APP_TITLE=Vben Admin Antd - -# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 -VITE_APP_NAMESPACE=vben-web-antd - -# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密 -VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key diff --git a/apps/web-antd/.env.analyze b/apps/web-antd/.env.analyze deleted file mode 100644 index ffafa8dd529..00000000000 --- a/apps/web-antd/.env.analyze +++ /dev/null @@ -1,7 +0,0 @@ -# public path -VITE_BASE=/ - -# Basic interface address SPA -VITE_GLOB_API_URL=/api - -VITE_VISUALIZER=true diff --git a/apps/web-antd/.env.development b/apps/web-antd/.env.development deleted file mode 100644 index c138f482918..00000000000 --- a/apps/web-antd/.env.development +++ /dev/null @@ -1,16 +0,0 @@ -# 端口号 -VITE_PORT=5666 - -VITE_BASE=/ - -# 接口地址 -VITE_GLOB_API_URL=/api - -# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 -VITE_NITRO_MOCK=true - -# 是否打开 devtools,true 为打开,false 为关闭 -VITE_DEVTOOLS=false - -# 是否注入全局loading -VITE_INJECT_APP_LOADING=true diff --git a/apps/web-antd/.env.production b/apps/web-antd/.env.production deleted file mode 100644 index 5375847a6ca..00000000000 --- a/apps/web-antd/.env.production +++ /dev/null @@ -1,19 +0,0 @@ -VITE_BASE=/ - -# 接口地址 -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api - -# 是否开启压缩,可以设置为 none, brotli, gzip -VITE_COMPRESS=none - -# 是否开启 PWA -VITE_PWA=false - -# vue-router 的模式 -VITE_ROUTER_HISTORY=hash - -# 是否注入全局loading -VITE_INJECT_APP_LOADING=true - -# 打包后是否生成dist.zip -VITE_ARCHIVER=true diff --git a/apps/web-antd/index.html b/apps/web-antd/index.html deleted file mode 100644 index 480eb84de6a..00000000000 --- a/apps/web-antd/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - <%= VITE_APP_TITLE %> - - - - -
- - - diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json deleted file mode 100644 index db7d0070ec1..00000000000 --- a/apps/web-antd/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@vben/web-antd", - "version": "5.5.9", - "homepage": "https://vben.pro/", - "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", - "repository": { - "type": "git", - "url": "git+https://github.com/vbenjs/vue-vben-admin.git", - "directory": "apps/web-antd" - }, - "license": "MIT", - "author": { - "name": "vben", - "email": "ann.vben@gmail.com", - "url": "https://github.com/anncwb" - }, - "type": "module", - "scripts": { - "build": "pnpm vite build --mode production", - "build:analyze": "pnpm vite build --mode analyze", - "dev": "pnpm vite --mode development", - "preview": "vite preview", - "typecheck": "vue-tsc --noEmit --skipLibCheck" - }, - "imports": { - "#/*": "./src/*" - }, - "dependencies": { - "@vben/access": "workspace:*", - "@vben/common-ui": "workspace:*", - "@vben/constants": "workspace:*", - "@vben/hooks": "workspace:*", - "@vben/icons": "workspace:*", - "@vben/layouts": "workspace:*", - "@vben/locales": "workspace:*", - "@vben/plugins": "workspace:*", - "@vben/preferences": "workspace:*", - "@vben/request": "workspace:*", - "@vben/stores": "workspace:*", - "@vben/styles": "workspace:*", - "@vben/types": "workspace:*", - "@vben/utils": "workspace:*", - "@vueuse/core": "catalog:", - "ant-design-vue": "catalog:", - "dayjs": "catalog:", - "pinia": "catalog:", - "vue": "catalog:", - "vue-router": "catalog:" - } -} diff --git a/apps/web-antd/postcss.config.mjs b/apps/web-antd/postcss.config.mjs deleted file mode 100644 index 3d807045561..00000000000 --- a/apps/web-antd/postcss.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config/postcss'; diff --git a/apps/web-antd/public/favicon.ico b/apps/web-antd/public/favicon.ico deleted file mode 100644 index fcf9818e2cf..00000000000 Binary files a/apps/web-antd/public/favicon.ico and /dev/null differ diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts deleted file mode 100644 index 786a93dae7f..00000000000 --- a/apps/web-antd/src/adapter/component/index.ts +++ /dev/null @@ -1,211 +0,0 @@ -/** - * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 - * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, - */ - -import type { Component } from 'vue'; - -import type { BaseFormComponentType } from '@vben/common-ui'; -import type { Recordable } from '@vben/types'; - -import { defineAsyncComponent, defineComponent, h, ref } from 'vue'; - -import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; -import { $t } from '@vben/locales'; - -import { notification } from 'ant-design-vue'; - -const AutoComplete = defineAsyncComponent( - () => import('ant-design-vue/es/auto-complete'), -); -const Button = defineAsyncComponent(() => import('ant-design-vue/es/button')); -const Checkbox = defineAsyncComponent( - () => import('ant-design-vue/es/checkbox'), -); -const CheckboxGroup = defineAsyncComponent(() => - import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup), -); -const DatePicker = defineAsyncComponent( - () => import('ant-design-vue/es/date-picker'), -); -const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider')); -const Input = defineAsyncComponent(() => import('ant-design-vue/es/input')); -const InputNumber = defineAsyncComponent( - () => import('ant-design-vue/es/input-number'), -); -const InputPassword = defineAsyncComponent(() => - import('ant-design-vue/es/input').then((res) => res.InputPassword), -); -const Mentions = defineAsyncComponent( - () => import('ant-design-vue/es/mentions'), -); -const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio')); -const RadioGroup = defineAsyncComponent(() => - import('ant-design-vue/es/radio').then((res) => res.RadioGroup), -); -const RangePicker = defineAsyncComponent(() => - import('ant-design-vue/es/date-picker').then((res) => res.RangePicker), -); -const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate')); -const Select = defineAsyncComponent(() => import('ant-design-vue/es/select')); -const Space = defineAsyncComponent(() => import('ant-design-vue/es/space')); -const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch')); -const Textarea = defineAsyncComponent(() => - import('ant-design-vue/es/input').then((res) => res.Textarea), -); -const TimePicker = defineAsyncComponent( - () => import('ant-design-vue/es/time-picker'), -); -const TreeSelect = defineAsyncComponent( - () => import('ant-design-vue/es/tree-select'), -); -const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload')); - -const withDefaultPlaceholder = ( - component: T, - type: 'input' | 'select', - componentProps: Recordable = {}, -) => { - return defineComponent({ - name: component.name, - inheritAttrs: false, - setup: (props: any, { attrs, expose, slots }) => { - const placeholder = - props?.placeholder || - attrs?.placeholder || - $t(`ui.placeholder.${type}`); - // 透传组件暴露的方法 - const innerRef = ref(); - expose( - new Proxy( - {}, - { - get: (_target, key) => innerRef.value?.[key], - has: (_target, key) => key in (innerRef.value || {}), - }, - ), - ); - return () => - h( - component, - { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef }, - slots, - ); - }, - }); -}; - -// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 -export type ComponentType = - | 'ApiSelect' - | 'ApiTreeSelect' - | 'AutoComplete' - | 'Checkbox' - | 'CheckboxGroup' - | 'DatePicker' - | 'DefaultButton' - | 'Divider' - | 'IconPicker' - | 'Input' - | 'InputNumber' - | 'InputPassword' - | 'Mentions' - | 'PrimaryButton' - | 'Radio' - | 'RadioGroup' - | 'RangePicker' - | 'Rate' - | 'Select' - | 'Space' - | 'Switch' - | 'Textarea' - | 'TimePicker' - | 'TreeSelect' - | 'Upload' - | BaseFormComponentType; - -async function initComponentAdapter() { - const components: Partial> = { - // 如果你的组件体积比较大,可以使用异步加载 - // Button: () => - // import('xxx').then((res) => res.Button), - ApiSelect: withDefaultPlaceholder( - { - ...ApiComponent, - name: 'ApiSelect', - }, - 'select', - { - component: Select, - loadingSlot: 'suffixIcon', - visibleEvent: 'onDropdownVisibleChange', - modelPropName: 'value', - }, - ), - ApiTreeSelect: withDefaultPlaceholder( - { - ...ApiComponent, - name: 'ApiTreeSelect', - }, - 'select', - { - component: TreeSelect, - fieldNames: { label: 'label', value: 'value', children: 'children' }, - loadingSlot: 'suffixIcon', - modelPropName: 'value', - optionsPropName: 'treeData', - visibleEvent: 'onVisibleChange', - }, - ), - AutoComplete, - Checkbox, - CheckboxGroup, - DatePicker, - // 自定义默认按钮 - DefaultButton: (props, { attrs, slots }) => { - return h(Button, { ...props, attrs, type: 'default' }, slots); - }, - Divider, - IconPicker: withDefaultPlaceholder(IconPicker, 'select', { - iconSlot: 'addonAfter', - inputComponent: Input, - modelValueProp: 'value', - }), - Input: withDefaultPlaceholder(Input, 'input'), - InputNumber: withDefaultPlaceholder(InputNumber, 'input'), - InputPassword: withDefaultPlaceholder(InputPassword, 'input'), - Mentions: withDefaultPlaceholder(Mentions, 'input'), - // 自定义主要按钮 - PrimaryButton: (props, { attrs, slots }) => { - return h(Button, { ...props, attrs, type: 'primary' }, slots); - }, - Radio, - RadioGroup, - RangePicker, - Rate, - Select: withDefaultPlaceholder(Select, 'select'), - Space, - Switch, - Textarea: withDefaultPlaceholder(Textarea, 'input'), - TimePicker, - TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), - Upload, - }; - - // 将组件注册到全局共享状态中 - globalShareState.setComponents(components); - - // 定义全局共享状态中的消息提示 - globalShareState.defineMessage({ - // 复制成功消息提示 - copyPreferencesSuccess: (title, content) => { - notification.success({ - description: content, - message: title, - placement: 'bottomRight', - }); - }, - }); -} - -export { initComponentAdapter }; diff --git a/apps/web-antd/src/adapter/form.ts b/apps/web-antd/src/adapter/form.ts deleted file mode 100644 index 983a7f51698..00000000000 --- a/apps/web-antd/src/adapter/form.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { - VbenFormSchema as FormSchema, - VbenFormProps, -} from '@vben/common-ui'; - -import type { ComponentType } from './component'; - -import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; -import { $t } from '@vben/locales'; - -async function initSetupVbenForm() { - setupVbenForm({ - config: { - // ant design vue组件库默认都是 v-model:value - baseModelPropName: 'value', - - // 一些组件是 v-model:checked 或者 v-model:fileList - modelPropNameMap: { - Checkbox: 'checked', - Radio: 'checked', - Switch: 'checked', - Upload: 'fileList', - }, - }, - defineRules: { - // 输入项目必填国际化适配 - required: (value, _params, ctx) => { - if (value === undefined || value === null || value.length === 0) { - return $t('ui.formRules.required', [ctx.label]); - } - return true; - }, - // 选择项目必填国际化适配 - selectRequired: (value, _params, ctx) => { - if (value === undefined || value === null) { - return $t('ui.formRules.selectRequired', [ctx.label]); - } - return true; - }, - }, - }); -} - -const useVbenForm = useForm; - -export { initSetupVbenForm, useVbenForm, z }; - -export type VbenFormSchema = FormSchema; -export type { VbenFormProps }; diff --git a/apps/web-antd/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts deleted file mode 100644 index 7de2859de8b..00000000000 --- a/apps/web-antd/src/adapter/vxe-table.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; - -import { h } from 'vue'; - -import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; - -import { Button, Image } from 'ant-design-vue'; - -import { useVbenForm } from './form'; - -setupVbenVxeTable({ - configVxeTable: (vxeUI) => { - vxeUI.setConfig({ - grid: { - align: 'center', - border: false, - columnConfig: { - resizable: true, - }, - minHeight: 180, - formConfig: { - // 全局禁用vxe-table的表单配置,使用formOptions - enabled: false, - }, - proxyConfig: { - autoLoad: true, - response: { - result: 'items', - total: 'total', - list: 'items', - }, - showActiveMsg: true, - showResponseMsg: false, - }, - round: true, - showOverflow: true, - size: 'small', - } as VxeTableGridOptions, - }); - - // 表格配置项可以用 cellRender: { name: 'CellImage' }, - vxeUI.renderer.add('CellImage', { - renderTableDefault(_renderOpts, params) { - const { column, row } = params; - return h(Image, { src: row[column.field] }); - }, - }); - - // 表格配置项可以用 cellRender: { name: 'CellLink' }, - vxeUI.renderer.add('CellLink', { - renderTableDefault(renderOpts) { - const { props } = renderOpts; - return h( - Button, - { size: 'small', type: 'link' }, - { default: () => props?.text }, - ); - }, - }); - - // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 - // vxeUI.formats.add - }, - useVbenForm, -}); - -export { useVbenVxeGrid }; - -export type * from '@vben/plugins/vxe-table'; diff --git a/apps/web-antd/src/api/core/auth.ts b/apps/web-antd/src/api/core/auth.ts deleted file mode 100644 index 71d9f994396..00000000000 --- a/apps/web-antd/src/api/core/auth.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { baseRequestClient, requestClient } from '#/api/request'; - -export namespace AuthApi { - /** 登录接口参数 */ - export interface LoginParams { - password?: string; - username?: string; - } - - /** 登录接口返回值 */ - export interface LoginResult { - accessToken: string; - } - - export interface RefreshTokenResult { - data: string; - status: number; - } -} - -/** - * 登录 - */ -export async function loginApi(data: AuthApi.LoginParams) { - return requestClient.post('/auth/login', data); -} - -/** - * 刷新accessToken - */ -export async function refreshTokenApi() { - return baseRequestClient.post('/auth/refresh', { - withCredentials: true, - }); -} - -/** - * 退出登录 - */ -export async function logoutApi() { - return baseRequestClient.post('/auth/logout', { - withCredentials: true, - }); -} - -/** - * 获取用户权限码 - */ -export async function getAccessCodesApi() { - return requestClient.get('/auth/codes'); -} diff --git a/apps/web-antd/src/api/core/index.ts b/apps/web-antd/src/api/core/index.ts deleted file mode 100644 index 28a5aef47ef..00000000000 --- a/apps/web-antd/src/api/core/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './auth'; -export * from './menu'; -export * from './user'; diff --git a/apps/web-antd/src/api/core/menu.ts b/apps/web-antd/src/api/core/menu.ts deleted file mode 100644 index 9ef60b11cd2..00000000000 --- a/apps/web-antd/src/api/core/menu.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { RouteRecordStringComponent } from '@vben/types'; - -import { requestClient } from '#/api/request'; - -/** - * 获取用户所有菜单 - */ -export async function getAllMenusApi() { - return requestClient.get('/menu/all'); -} diff --git a/apps/web-antd/src/api/core/user.ts b/apps/web-antd/src/api/core/user.ts deleted file mode 100644 index 7e28ea8489a..00000000000 --- a/apps/web-antd/src/api/core/user.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { UserInfo } from '@vben/types'; - -import { requestClient } from '#/api/request'; - -/** - * 获取用户信息 - */ -export async function getUserInfoApi() { - return requestClient.get('/user/info'); -} diff --git a/apps/web-antd/src/api/index.ts b/apps/web-antd/src/api/index.ts deleted file mode 100644 index 4b0e0413762..00000000000 --- a/apps/web-antd/src/api/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './core'; diff --git a/apps/web-antd/src/api/request.ts b/apps/web-antd/src/api/request.ts deleted file mode 100644 index 288dddd09db..00000000000 --- a/apps/web-antd/src/api/request.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * 该文件可自行根据业务逻辑进行调整 - */ -import type { RequestClientOptions } from '@vben/request'; - -import { useAppConfig } from '@vben/hooks'; -import { preferences } from '@vben/preferences'; -import { - authenticateResponseInterceptor, - defaultResponseInterceptor, - errorMessageResponseInterceptor, - RequestClient, -} from '@vben/request'; -import { useAccessStore } from '@vben/stores'; - -import { message } from 'ant-design-vue'; - -import { useAuthStore } from '#/store'; - -import { refreshTokenApi } from './core'; - -const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); - -function createRequestClient(baseURL: string, options?: RequestClientOptions) { - const client = new RequestClient({ - ...options, - baseURL, - }); - - /** - * 重新认证逻辑 - */ - async function doReAuthenticate() { - console.warn('Access token or refresh token is invalid or expired. '); - const accessStore = useAccessStore(); - const authStore = useAuthStore(); - accessStore.setAccessToken(null); - if ( - preferences.app.loginExpiredMode === 'modal' && - accessStore.isAccessChecked - ) { - accessStore.setLoginExpired(true); - } else { - await authStore.logout(); - } - } - - /** - * 刷新token逻辑 - */ - async function doRefreshToken() { - const accessStore = useAccessStore(); - const resp = await refreshTokenApi(); - const newToken = resp.data; - accessStore.setAccessToken(newToken); - return newToken; - } - - function formatToken(token: null | string) { - return token ? `Bearer ${token}` : null; - } - - // 请求头处理 - client.addRequestInterceptor({ - fulfilled: async (config) => { - const accessStore = useAccessStore(); - - config.headers.Authorization = formatToken(accessStore.accessToken); - config.headers['Accept-Language'] = preferences.app.locale; - return config; - }, - }); - - // 处理返回的响应数据格式 - client.addResponseInterceptor( - defaultResponseInterceptor({ - codeField: 'code', - dataField: 'data', - successCode: 0, - }), - ); - - // token过期的处理 - client.addResponseInterceptor( - authenticateResponseInterceptor({ - client, - doReAuthenticate, - doRefreshToken, - enableRefreshToken: preferences.app.enableRefreshToken, - formatToken, - }), - ); - - // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里 - client.addResponseInterceptor( - errorMessageResponseInterceptor((msg: string, error) => { - // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg - // 当前mock接口返回的错误字段是 error 或者 message - const responseData = error?.response?.data ?? {}; - const errorMessage = responseData?.error ?? responseData?.message ?? ''; - // 如果没有错误信息,则会根据状态码进行提示 - message.error(errorMessage || msg); - }), - ); - - return client; -} - -export const requestClient = createRequestClient(apiURL, { - responseReturn: 'data', -}); - -export const baseRequestClient = new RequestClient({ baseURL: apiURL }); diff --git a/apps/web-antd/src/app.vue b/apps/web-antd/src/app.vue deleted file mode 100644 index bbaccce13bc..00000000000 --- a/apps/web-antd/src/app.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - diff --git a/apps/web-antd/src/bootstrap.ts b/apps/web-antd/src/bootstrap.ts deleted file mode 100644 index ec721125434..00000000000 --- a/apps/web-antd/src/bootstrap.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { createApp, watchEffect } from 'vue'; - -import { registerAccessDirective } from '@vben/access'; -import { registerLoadingDirective } from '@vben/common-ui/es/loading'; -import { preferences } from '@vben/preferences'; -import { initStores } from '@vben/stores'; -import '@vben/styles'; -import '@vben/styles/antd'; - -import { useTitle } from '@vueuse/core'; - -import { $t, setupI18n } from '#/locales'; - -import { initComponentAdapter } from './adapter/component'; -import { initSetupVbenForm } from './adapter/form'; -import App from './app.vue'; -import { router } from './router'; - -async function bootstrap(namespace: string) { - // 初始化组件适配器 - await initComponentAdapter(); - - // 初始化表单组件 - await initSetupVbenForm(); - - // // 设置弹窗的默认配置 - // setDefaultModalProps({ - // fullscreenButton: false, - // }); - // // 设置抽屉的默认配置 - // setDefaultDrawerProps({ - // zIndex: 1020, - // }); - - const app = createApp(App); - - // 注册v-loading指令 - registerLoadingDirective(app, { - loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 - spinning: 'spinning', - }); - - // 国际化 i18n 配置 - await setupI18n(app); - - // 配置 pinia-tore - await initStores(app, { namespace }); - - // 安装权限指令 - registerAccessDirective(app); - - // 初始化 tippy - const { initTippy } = await import('@vben/common-ui/es/tippy'); - initTippy(app); - - // 配置路由及路由守卫 - app.use(router); - - // 配置Motion插件 - const { MotionPlugin } = await import('@vben/plugins/motion'); - app.use(MotionPlugin); - - // 动态更新标题 - watchEffect(() => { - if (preferences.app.dynamicTitle) { - const routeTitle = router.currentRoute.value.meta?.title; - const pageTitle = - (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; - useTitle(pageTitle); - } - }); - - app.mount('#app'); -} - -export { bootstrap }; diff --git a/apps/web-antd/src/layouts/auth.vue b/apps/web-antd/src/layouts/auth.vue deleted file mode 100644 index 18d415bc7fb..00000000000 --- a/apps/web-antd/src/layouts/auth.vue +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/apps/web-antd/src/layouts/basic.vue b/apps/web-antd/src/layouts/basic.vue deleted file mode 100644 index 805b8a73d2e..00000000000 --- a/apps/web-antd/src/layouts/basic.vue +++ /dev/null @@ -1,162 +0,0 @@ - - - diff --git a/apps/web-antd/src/layouts/index.ts b/apps/web-antd/src/layouts/index.ts deleted file mode 100644 index a4320780540..00000000000 --- a/apps/web-antd/src/layouts/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -const BasicLayout = () => import('./basic.vue'); -const AuthPageLayout = () => import('./auth.vue'); - -const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView); - -export { AuthPageLayout, BasicLayout, IFrameView }; diff --git a/apps/web-antd/src/locales/README.md b/apps/web-antd/src/locales/README.md deleted file mode 100644 index 7b451032e6c..00000000000 --- a/apps/web-antd/src/locales/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# locale - -每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。 diff --git a/apps/web-antd/src/locales/index.ts b/apps/web-antd/src/locales/index.ts deleted file mode 100644 index 7f32bd18ef3..00000000000 --- a/apps/web-antd/src/locales/index.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { Locale } from 'ant-design-vue/es/locale'; - -import type { App } from 'vue'; - -import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales'; - -import { ref } from 'vue'; - -import { - $t, - setupI18n as coreSetup, - loadLocalesMapFromDir, -} from '@vben/locales'; -import { preferences } from '@vben/preferences'; - -import antdEnLocale from 'ant-design-vue/es/locale/en_US'; -import antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN'; -import dayjs from 'dayjs'; - -const antdLocale = ref(antdDefaultLocale); - -const modules = import.meta.glob('./langs/**/*.json'); - -const localesMap = loadLocalesMapFromDir( - /\.\/langs\/([^/]+)\/(.*)\.json$/, - modules, -); -/** - * 加载应用特有的语言包 - * 这里也可以改造为从服务端获取翻译数据 - * @param lang - */ -async function loadMessages(lang: SupportedLanguagesType) { - const [appLocaleMessages] = await Promise.all([ - localesMap[lang]?.(), - loadThirdPartyMessage(lang), - ]); - return appLocaleMessages?.default; -} - -/** - * 加载第三方组件库的语言包 - * @param lang - */ -async function loadThirdPartyMessage(lang: SupportedLanguagesType) { - await Promise.all([loadAntdLocale(lang), loadDayjsLocale(lang)]); -} - -/** - * 加载dayjs的语言包 - * @param lang - */ -async function loadDayjsLocale(lang: SupportedLanguagesType) { - let locale; - switch (lang) { - case 'en-US': { - locale = await import('dayjs/locale/en'); - break; - } - case 'zh-CN': { - locale = await import('dayjs/locale/zh-cn'); - break; - } - // 默认使用英语 - default: { - locale = await import('dayjs/locale/en'); - } - } - if (locale) { - dayjs.locale(locale); - } else { - console.error(`Failed to load dayjs locale for ${lang}`); - } -} - -/** - * 加载antd的语言包 - * @param lang - */ -async function loadAntdLocale(lang: SupportedLanguagesType) { - switch (lang) { - case 'en-US': { - antdLocale.value = antdEnLocale; - break; - } - case 'zh-CN': { - antdLocale.value = antdDefaultLocale; - break; - } - } -} - -async function setupI18n(app: App, options: LocaleSetupOptions = {}) { - await coreSetup(app, { - defaultLocale: preferences.app.locale, - loadMessages, - missingWarn: !import.meta.env.PROD, - ...options, - }); -} - -export { $t, antdLocale, setupI18n }; diff --git a/apps/web-antd/src/locales/langs/en-US/demos.json b/apps/web-antd/src/locales/langs/en-US/demos.json deleted file mode 100644 index 071564349b7..00000000000 --- a/apps/web-antd/src/locales/langs/en-US/demos.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "Demos", - "antd": "Ant Design Vue", - "vben": { - "title": "Project", - "about": "About", - "document": "Document", - "antdv": "Ant Design Vue Version", - "naive-ui": "Naive UI Version", - "element-plus": "Element Plus Version" - } -} diff --git a/apps/web-antd/src/locales/langs/en-US/page.json b/apps/web-antd/src/locales/langs/en-US/page.json deleted file mode 100644 index 618a258c0a6..00000000000 --- a/apps/web-antd/src/locales/langs/en-US/page.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "auth": { - "login": "Login", - "register": "Register", - "codeLogin": "Code Login", - "qrcodeLogin": "Qr Code Login", - "forgetPassword": "Forget Password" - }, - "dashboard": { - "title": "Dashboard", - "analytics": "Analytics", - "workspace": "Workspace" - } -} diff --git a/apps/web-antd/src/locales/langs/zh-CN/demos.json b/apps/web-antd/src/locales/langs/zh-CN/demos.json deleted file mode 100644 index 93ee722f59f..00000000000 --- a/apps/web-antd/src/locales/langs/zh-CN/demos.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "演示", - "antd": "Ant Design Vue", - "vben": { - "title": "项目", - "about": "关于", - "document": "文档", - "antdv": "Ant Design Vue 版本", - "naive-ui": "Naive UI 版本", - "element-plus": "Element Plus 版本" - } -} diff --git a/apps/web-antd/src/locales/langs/zh-CN/page.json b/apps/web-antd/src/locales/langs/zh-CN/page.json deleted file mode 100644 index 4cb67081cbf..00000000000 --- a/apps/web-antd/src/locales/langs/zh-CN/page.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "auth": { - "login": "登录", - "register": "注册", - "codeLogin": "验证码登录", - "qrcodeLogin": "二维码登录", - "forgetPassword": "忘记密码" - }, - "dashboard": { - "title": "概览", - "analytics": "分析页", - "workspace": "工作台" - } -} diff --git a/apps/web-antd/src/main.ts b/apps/web-antd/src/main.ts deleted file mode 100644 index 5d728a02acb..00000000000 --- a/apps/web-antd/src/main.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { initPreferences } from '@vben/preferences'; -import { unmountGlobalLoading } from '@vben/utils'; - -import { overridesPreferences } from './preferences'; - -/** - * 应用初始化完成之后再进行页面加载渲染 - */ -async function initApplication() { - // name用于指定项目唯一标识 - // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据 - const env = import.meta.env.PROD ? 'prod' : 'dev'; - const appVersion = import.meta.env.VITE_APP_VERSION; - const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`; - - // app偏好设置初始化 - await initPreferences({ - namespace, - overrides: overridesPreferences, - }); - - // 启动应用并挂载 - // vue应用主要逻辑及视图 - const { bootstrap } = await import('./bootstrap'); - await bootstrap(namespace); - - // 移除并销毁loading - unmountGlobalLoading(); -} - -initApplication(); diff --git a/apps/web-antd/src/preferences.ts b/apps/web-antd/src/preferences.ts deleted file mode 100644 index b2e9ace43c8..00000000000 --- a/apps/web-antd/src/preferences.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineOverridesPreferences } from '@vben/preferences'; - -/** - * @description 项目配置文件 - * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置 - * !!! 更改配置后请清空缓存,否则可能不生效 - */ -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - name: import.meta.env.VITE_APP_TITLE, - }, -}); diff --git a/apps/web-antd/src/router/access.ts b/apps/web-antd/src/router/access.ts deleted file mode 100644 index 3a48be2378c..00000000000 --- a/apps/web-antd/src/router/access.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { - ComponentRecordType, - GenerateMenuAndRoutesOptions, -} from '@vben/types'; - -import { generateAccessible } from '@vben/access'; -import { preferences } from '@vben/preferences'; - -import { message } from 'ant-design-vue'; - -import { getAllMenusApi } from '#/api'; -import { BasicLayout, IFrameView } from '#/layouts'; -import { $t } from '#/locales'; - -const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); - -async function generateAccess(options: GenerateMenuAndRoutesOptions) { - const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); - - const layoutMap: ComponentRecordType = { - BasicLayout, - IFrameView, - }; - - return await generateAccessible(preferences.app.accessMode, { - ...options, - fetchMenuListAsync: async () => { - message.loading({ - content: `${$t('common.loadingMenu')}...`, - duration: 1.5, - }); - return await getAllMenusApi(); - }, - // 可以指定没有权限跳转403页面 - forbiddenComponent, - // 如果 route.meta.menuVisibleWithForbidden = true - layoutMap, - pageMap, - }); -} - -export { generateAccess }; diff --git a/apps/web-antd/src/router/guard.ts b/apps/web-antd/src/router/guard.ts deleted file mode 100644 index a1ad6d88cff..00000000000 --- a/apps/web-antd/src/router/guard.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type { Router } from 'vue-router'; - -import { LOGIN_PATH } from '@vben/constants'; -import { preferences } from '@vben/preferences'; -import { useAccessStore, useUserStore } from '@vben/stores'; -import { startProgress, stopProgress } from '@vben/utils'; - -import { accessRoutes, coreRouteNames } from '#/router/routes'; -import { useAuthStore } from '#/store'; - -import { generateAccess } from './access'; - -/** - * 通用守卫配置 - * @param router - */ -function setupCommonGuard(router: Router) { - // 记录已经加载的页面 - const loadedPaths = new Set(); - - router.beforeEach((to) => { - to.meta.loaded = loadedPaths.has(to.path); - - // 页面加载进度条 - if (!to.meta.loaded && preferences.transition.progress) { - startProgress(); - } - return true; - }); - - router.afterEach((to) => { - // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 - - loadedPaths.add(to.path); - - // 关闭页面加载进度条 - if (preferences.transition.progress) { - stopProgress(); - } - }); -} - -/** - * 权限访问守卫配置 - * @param router - */ -function setupAccessGuard(router: Router) { - router.beforeEach(async (to, from) => { - const accessStore = useAccessStore(); - const userStore = useUserStore(); - const authStore = useAuthStore(); - - // 基本路由,这些路由不需要进入权限拦截 - if (coreRouteNames.includes(to.name as string)) { - if (to.path === LOGIN_PATH && accessStore.accessToken) { - return decodeURIComponent( - (to.query?.redirect as string) || - userStore.userInfo?.homePath || - preferences.app.defaultHomePath, - ); - } - return true; - } - - // accessToken 检查 - if (!accessStore.accessToken) { - // 明确声明忽略权限访问权限,则可以访问 - if (to.meta.ignoreAccess) { - return true; - } - - // 没有访问权限,跳转登录页面 - if (to.fullPath !== LOGIN_PATH) { - return { - path: LOGIN_PATH, - // 如不需要,直接删除 query - query: - to.fullPath === preferences.app.defaultHomePath - ? {} - : { redirect: encodeURIComponent(to.fullPath) }, - // 携带当前跳转的页面,登录后重新跳转该页面 - replace: true, - }; - } - return to; - } - - // 是否已经生成过动态路由 - if (accessStore.isAccessChecked) { - return true; - } - - // 生成路由表 - // 当前登录用户拥有的角色标识列表 - const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); - const userRoles = userInfo.roles ?? []; - - // 生成菜单和路由 - const { accessibleMenus, accessibleRoutes } = await generateAccess({ - roles: userRoles, - router, - // 则会在菜单中显示,但是访问会被重定向到403 - routes: accessRoutes, - }); - - // 保存菜单信息和路由信息 - accessStore.setAccessMenus(accessibleMenus); - accessStore.setAccessRoutes(accessibleRoutes); - accessStore.setIsAccessChecked(true); - const redirectPath = (from.query.redirect ?? - (to.path === preferences.app.defaultHomePath - ? userInfo.homePath || preferences.app.defaultHomePath - : to.fullPath)) as string; - - return { - ...router.resolve(decodeURIComponent(redirectPath)), - replace: true, - }; - }); -} - -/** - * 项目守卫配置 - * @param router - */ -function createRouterGuard(router: Router) { - /** 通用 */ - setupCommonGuard(router); - /** 权限访问 */ - setupAccessGuard(router); -} - -export { createRouterGuard }; diff --git a/apps/web-antd/src/router/index.ts b/apps/web-antd/src/router/index.ts deleted file mode 100644 index 48402303425..00000000000 --- a/apps/web-antd/src/router/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - createRouter, - createWebHashHistory, - createWebHistory, -} from 'vue-router'; - -import { resetStaticRoutes } from '@vben/utils'; - -import { createRouterGuard } from './guard'; -import { routes } from './routes'; - -/** - * @zh_CN 创建vue-router实例 - */ -const router = createRouter({ - history: - import.meta.env.VITE_ROUTER_HISTORY === 'hash' - ? createWebHashHistory(import.meta.env.VITE_BASE) - : createWebHistory(import.meta.env.VITE_BASE), - // 应该添加到路由的初始路由列表。 - routes, - scrollBehavior: (to, _from, savedPosition) => { - if (savedPosition) { - return savedPosition; - } - return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 }; - }, - // 是否应该禁止尾部斜杠。 - // strict: true, -}); - -const resetRoutes = () => resetStaticRoutes(router, routes); - -// 创建路由守卫 -createRouterGuard(router); - -export { resetRoutes, router }; diff --git a/apps/web-antd/src/router/routes/core.ts b/apps/web-antd/src/router/routes/core.ts deleted file mode 100644 index 949b0b65acf..00000000000 --- a/apps/web-antd/src/router/routes/core.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { LOGIN_PATH } from '@vben/constants'; -import { preferences } from '@vben/preferences'; - -import { $t } from '#/locales'; - -const BasicLayout = () => import('#/layouts/basic.vue'); -const AuthPageLayout = () => import('#/layouts/auth.vue'); -/** 全局404页面 */ -const fallbackNotFoundRoute: RouteRecordRaw = { - component: () => import('#/views/_core/fallback/not-found.vue'), - meta: { - hideInBreadcrumb: true, - hideInMenu: true, - hideInTab: true, - title: '404', - }, - name: 'FallbackNotFound', - path: '/:path(.*)*', -}; - -/** 基本路由,这些路由是必须存在的 */ -const coreRoutes: RouteRecordRaw[] = [ - /** - * 根路由 - * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。 - * 此路由必须存在,且不应修改 - */ - { - component: BasicLayout, - meta: { - hideInBreadcrumb: true, - title: 'Root', - }, - name: 'Root', - path: '/', - redirect: preferences.app.defaultHomePath, - children: [], - }, - { - component: AuthPageLayout, - meta: { - hideInTab: true, - title: 'Authentication', - }, - name: 'Authentication', - path: '/auth', - redirect: LOGIN_PATH, - children: [ - { - name: 'Login', - path: 'login', - component: () => import('#/views/_core/authentication/login.vue'), - meta: { - title: $t('page.auth.login'), - }, - }, - { - name: 'CodeLogin', - path: 'code-login', - component: () => import('#/views/_core/authentication/code-login.vue'), - meta: { - title: $t('page.auth.codeLogin'), - }, - }, - { - name: 'QrCodeLogin', - path: 'qrcode-login', - component: () => - import('#/views/_core/authentication/qrcode-login.vue'), - meta: { - title: $t('page.auth.qrcodeLogin'), - }, - }, - { - name: 'ForgetPassword', - path: 'forget-password', - component: () => - import('#/views/_core/authentication/forget-password.vue'), - meta: { - title: $t('page.auth.forgetPassword'), - }, - }, - { - name: 'Register', - path: 'register', - component: () => import('#/views/_core/authentication/register.vue'), - meta: { - title: $t('page.auth.register'), - }, - }, - ], - }, -]; - -export { coreRoutes, fallbackNotFoundRoute }; diff --git a/apps/web-antd/src/router/routes/index.ts b/apps/web-antd/src/router/routes/index.ts deleted file mode 100644 index e6fb1440204..00000000000 --- a/apps/web-antd/src/router/routes/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { mergeRouteModules, traverseTreeValues } from '@vben/utils'; - -import { coreRoutes, fallbackNotFoundRoute } from './core'; - -const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { - eager: true, -}); - -// 有需要可以自行打开注释,并创建文件夹 -// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); -// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); - -/** 动态路由 */ -const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); - -/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ -// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); -// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles); -const staticRoutes: RouteRecordRaw[] = []; -const externalRoutes: RouteRecordRaw[] = []; - -/** 路由列表,由基本路由、外部路由和404兜底路由组成 - * 无需走权限验证(会一直显示在菜单中) */ -const routes: RouteRecordRaw[] = [ - ...coreRoutes, - ...externalRoutes, - fallbackNotFoundRoute, -]; - -/** 基本路由列表,这些路由不需要进入权限拦截 */ -const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name); - -/** 有权限校验的路由列表,包含动态路由和静态路由 */ -const accessRoutes = [...dynamicRoutes, ...staticRoutes]; -export { accessRoutes, coreRouteNames, routes }; diff --git a/apps/web-antd/src/router/routes/modules/dashboard.ts b/apps/web-antd/src/router/routes/modules/dashboard.ts deleted file mode 100644 index 5254dc65de4..00000000000 --- a/apps/web-antd/src/router/routes/modules/dashboard.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'lucide:layout-dashboard', - order: -1, - title: $t('page.dashboard.title'), - }, - name: 'Dashboard', - path: '/dashboard', - children: [ - { - name: 'Analytics', - path: '/analytics', - component: () => import('#/views/dashboard/analytics/index.vue'), - meta: { - affixTab: true, - icon: 'lucide:area-chart', - title: $t('page.dashboard.analytics'), - }, - }, - { - name: 'Workspace', - path: '/workspace', - component: () => import('#/views/dashboard/workspace/index.vue'), - meta: { - icon: 'carbon:workspace', - title: $t('page.dashboard.workspace'), - }, - }, - ], - }, -]; - -export default routes; diff --git a/apps/web-antd/src/router/routes/modules/demos.ts b/apps/web-antd/src/router/routes/modules/demos.ts deleted file mode 100644 index 55ade09c954..00000000000 --- a/apps/web-antd/src/router/routes/modules/demos.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'ic:baseline-view-in-ar', - keepAlive: true, - order: 1000, - title: $t('demos.title'), - }, - name: 'Demos', - path: '/demos', - children: [ - { - meta: { - title: $t('demos.antd'), - }, - name: 'AntDesignDemos', - path: '/demos/ant-design', - component: () => import('#/views/demos/antd/index.vue'), - }, - ], - }, -]; - -export default routes; diff --git a/apps/web-antd/src/router/routes/modules/vben.ts b/apps/web-antd/src/router/routes/modules/vben.ts deleted file mode 100644 index 98acf582118..00000000000 --- a/apps/web-antd/src/router/routes/modules/vben.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { - VBEN_DOC_URL, - VBEN_ELE_PREVIEW_URL, - VBEN_GITHUB_URL, - VBEN_LOGO_URL, - VBEN_NAIVE_PREVIEW_URL, -} from '@vben/constants'; - -import { IFrameView } from '#/layouts'; -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - badgeType: 'dot', - icon: VBEN_LOGO_URL, - order: 9998, - title: $t('demos.vben.title'), - }, - name: 'VbenProject', - path: '/vben-admin', - children: [ - { - name: 'VbenDocument', - path: '/vben-admin/document', - component: IFrameView, - meta: { - icon: 'lucide:book-open-text', - link: VBEN_DOC_URL, - title: $t('demos.vben.document'), - }, - }, - { - name: 'VbenGithub', - path: '/vben-admin/github', - component: IFrameView, - meta: { - icon: 'mdi:github', - link: VBEN_GITHUB_URL, - title: 'Github', - }, - }, - { - name: 'VbenNaive', - path: '/vben-admin/naive', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: 'logos:naiveui', - link: VBEN_NAIVE_PREVIEW_URL, - title: $t('demos.vben.naive-ui'), - }, - }, - { - name: 'VbenElementPlus', - path: '/vben-admin/ele', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: 'logos:element', - link: VBEN_ELE_PREVIEW_URL, - title: $t('demos.vben.element-plus'), - }, - }, - ], - }, - { - name: 'VbenAbout', - path: '/vben-admin/about', - component: () => import('#/views/_core/about/index.vue'), - meta: { - icon: 'lucide:copyright', - title: $t('demos.vben.about'), - order: 9999, - }, - }, -]; - -export default routes; diff --git a/apps/web-antd/src/store/auth.ts b/apps/web-antd/src/store/auth.ts deleted file mode 100644 index bd496d1ee41..00000000000 --- a/apps/web-antd/src/store/auth.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { Recordable, UserInfo } from '@vben/types'; - -import { ref } from 'vue'; -import { useRouter } from 'vue-router'; - -import { LOGIN_PATH } from '@vben/constants'; -import { preferences } from '@vben/preferences'; -import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; - -import { notification } from 'ant-design-vue'; -import { defineStore } from 'pinia'; - -import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; -import { $t } from '#/locales'; - -export const useAuthStore = defineStore('auth', () => { - const accessStore = useAccessStore(); - const userStore = useUserStore(); - const router = useRouter(); - - const loginLoading = ref(false); - - /** - * 异步处理登录操作 - * Asynchronously handle the login process - * @param params 登录表单数据 - */ - async function authLogin( - params: Recordable, - onSuccess?: () => Promise | void, - ) { - // 异步处理用户登录操作并获取 accessToken - let userInfo: null | UserInfo = null; - try { - loginLoading.value = true; - const { accessToken } = await loginApi(params); - - // 如果成功获取到 accessToken - if (accessToken) { - accessStore.setAccessToken(accessToken); - - // 获取用户信息并存储到 accessStore 中 - const [fetchUserInfoResult, accessCodes] = await Promise.all([ - fetchUserInfo(), - getAccessCodesApi(), - ]); - - userInfo = fetchUserInfoResult; - - userStore.setUserInfo(userInfo); - accessStore.setAccessCodes(accessCodes); - - if (accessStore.loginExpired) { - accessStore.setLoginExpired(false); - } else { - onSuccess - ? await onSuccess?.() - : await router.push( - userInfo.homePath || preferences.app.defaultHomePath, - ); - } - - if (userInfo?.realName) { - notification.success({ - description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, - duration: 3, - message: $t('authentication.loginSuccess'), - }); - } - } - } finally { - loginLoading.value = false; - } - - return { - userInfo, - }; - } - - async function logout(redirect: boolean = true) { - try { - await logoutApi(); - } catch { - // 不做任何处理 - } - resetAllStores(); - accessStore.setLoginExpired(false); - - // 回登录页带上当前路由地址 - await router.replace({ - path: LOGIN_PATH, - query: redirect - ? { - redirect: encodeURIComponent(router.currentRoute.value.fullPath), - } - : {}, - }); - } - - async function fetchUserInfo() { - let userInfo: null | UserInfo = null; - userInfo = await getUserInfoApi(); - userStore.setUserInfo(userInfo); - return userInfo; - } - - function $reset() { - loginLoading.value = false; - } - - return { - $reset, - authLogin, - fetchUserInfo, - loginLoading, - logout, - }; -}); diff --git a/apps/web-antd/src/store/index.ts b/apps/web-antd/src/store/index.ts deleted file mode 100644 index 269586ee8b8..00000000000 --- a/apps/web-antd/src/store/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './auth'; diff --git a/apps/web-antd/src/views/_core/README.md b/apps/web-antd/src/views/_core/README.md deleted file mode 100644 index 8248afe6c10..00000000000 --- a/apps/web-antd/src/views/_core/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# \_core - -此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。 diff --git a/apps/web-antd/src/views/_core/about/index.vue b/apps/web-antd/src/views/_core/about/index.vue deleted file mode 100644 index 0ee524335c2..00000000000 --- a/apps/web-antd/src/views/_core/about/index.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/authentication/code-login.vue b/apps/web-antd/src/views/_core/authentication/code-login.vue deleted file mode 100644 index acfd1fd7881..00000000000 --- a/apps/web-antd/src/views/_core/authentication/code-login.vue +++ /dev/null @@ -1,69 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/authentication/forget-password.vue b/apps/web-antd/src/views/_core/authentication/forget-password.vue deleted file mode 100644 index fef0d427945..00000000000 --- a/apps/web-antd/src/views/_core/authentication/forget-password.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/authentication/login.vue b/apps/web-antd/src/views/_core/authentication/login.vue deleted file mode 100644 index 099e4c8c026..00000000000 --- a/apps/web-antd/src/views/_core/authentication/login.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/authentication/qrcode-login.vue b/apps/web-antd/src/views/_core/authentication/qrcode-login.vue deleted file mode 100644 index 23f5f2dad58..00000000000 --- a/apps/web-antd/src/views/_core/authentication/qrcode-login.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/authentication/register.vue b/apps/web-antd/src/views/_core/authentication/register.vue deleted file mode 100644 index b1a5de726d2..00000000000 --- a/apps/web-antd/src/views/_core/authentication/register.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/fallback/coming-soon.vue b/apps/web-antd/src/views/_core/fallback/coming-soon.vue deleted file mode 100644 index f394930f21a..00000000000 --- a/apps/web-antd/src/views/_core/fallback/coming-soon.vue +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/fallback/forbidden.vue b/apps/web-antd/src/views/_core/fallback/forbidden.vue deleted file mode 100644 index 8ea65fedb52..00000000000 --- a/apps/web-antd/src/views/_core/fallback/forbidden.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/fallback/internal-error.vue b/apps/web-antd/src/views/_core/fallback/internal-error.vue deleted file mode 100644 index 819a47d5ea8..00000000000 --- a/apps/web-antd/src/views/_core/fallback/internal-error.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/fallback/not-found.vue b/apps/web-antd/src/views/_core/fallback/not-found.vue deleted file mode 100644 index 4d178e9cbd4..00000000000 --- a/apps/web-antd/src/views/_core/fallback/not-found.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/fallback/offline.vue b/apps/web-antd/src/views/_core/fallback/offline.vue deleted file mode 100644 index 5de4a88de4e..00000000000 --- a/apps/web-antd/src/views/_core/fallback/offline.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue deleted file mode 100644 index f1f0b232a62..00000000000 --- a/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue deleted file mode 100644 index 190fb41f47d..00000000000 --- a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue deleted file mode 100644 index 02f5091231b..00000000000 --- a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue deleted file mode 100644 index 0915c7af7dd..00000000000 --- a/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue b/apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue deleted file mode 100644 index 7e0f10133d8..00000000000 --- a/apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/dashboard/analytics/index.vue b/apps/web-antd/src/views/dashboard/analytics/index.vue deleted file mode 100644 index 5e3d6d285ae..00000000000 --- a/apps/web-antd/src/views/dashboard/analytics/index.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/dashboard/workspace/index.vue b/apps/web-antd/src/views/dashboard/workspace/index.vue deleted file mode 100644 index b95d6138166..00000000000 --- a/apps/web-antd/src/views/dashboard/workspace/index.vue +++ /dev/null @@ -1,266 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/demos/antd/index.vue b/apps/web-antd/src/views/demos/antd/index.vue deleted file mode 100644 index b3b05cc153f..00000000000 --- a/apps/web-antd/src/views/demos/antd/index.vue +++ /dev/null @@ -1,66 +0,0 @@ - - - diff --git a/apps/web-antd/tailwind.config.mjs b/apps/web-antd/tailwind.config.mjs deleted file mode 100644 index f17f556fa9f..00000000000 --- a/apps/web-antd/tailwind.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config'; diff --git a/apps/web-antd/tsconfig.json b/apps/web-antd/tsconfig.json deleted file mode 100644 index 02c287fe642..00000000000 --- a/apps/web-antd/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@vben/tsconfig/web-app.json", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "#/*": ["./src/*"] - } - }, - "references": [{ "path": "./tsconfig.node.json" }], - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] -} diff --git a/apps/web-antd/tsconfig.node.json b/apps/web-antd/tsconfig.node.json deleted file mode 100644 index c2f0d86cc78..00000000000 --- a/apps/web-antd/tsconfig.node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@vben/tsconfig/node.json", - "compilerOptions": { - "composite": true, - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "noEmit": false - }, - "include": ["vite.config.mts"] -} diff --git a/apps/web-antd/vite.config.mts b/apps/web-antd/vite.config.mts deleted file mode 100644 index b6360f1d4ac..00000000000 --- a/apps/web-antd/vite.config.mts +++ /dev/null @@ -1,20 +0,0 @@ -import { defineConfig } from '@vben/vite-config'; - -export default defineConfig(async () => { - return { - application: {}, - vite: { - server: { - proxy: { - '/api': { - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, ''), - // mock代理目标地址 - target: 'http://localhost:5320/api', - ws: true, - }, - }, - }, - }, - }; -}); diff --git a/apps/web-ele/.env b/apps/web-ele/.env deleted file mode 100644 index bb57c865130..00000000000 --- a/apps/web-ele/.env +++ /dev/null @@ -1,8 +0,0 @@ -# 应用标题 -VITE_APP_TITLE=Vben Admin Ele - -# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 -VITE_APP_NAMESPACE=vben-web-ele - -# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密 -VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key diff --git a/apps/web-ele/.env.analyze b/apps/web-ele/.env.analyze deleted file mode 100644 index ffafa8dd529..00000000000 --- a/apps/web-ele/.env.analyze +++ /dev/null @@ -1,7 +0,0 @@ -# public path -VITE_BASE=/ - -# Basic interface address SPA -VITE_GLOB_API_URL=/api - -VITE_VISUALIZER=true diff --git a/apps/web-ele/.env.development b/apps/web-ele/.env.development deleted file mode 100644 index 8bcb432e6d0..00000000000 --- a/apps/web-ele/.env.development +++ /dev/null @@ -1,16 +0,0 @@ -# 端口号 -VITE_PORT=5777 - -VITE_BASE=/ - -# 接口地址 -VITE_GLOB_API_URL=/api - -# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 -VITE_NITRO_MOCK=true - -# 是否打开 devtools,true 为打开,false 为关闭 -VITE_DEVTOOLS=false - -# 是否注入全局loading -VITE_INJECT_APP_LOADING=true diff --git a/apps/web-ele/.env.production b/apps/web-ele/.env.production deleted file mode 100644 index 5375847a6ca..00000000000 --- a/apps/web-ele/.env.production +++ /dev/null @@ -1,19 +0,0 @@ -VITE_BASE=/ - -# 接口地址 -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api - -# 是否开启压缩,可以设置为 none, brotli, gzip -VITE_COMPRESS=none - -# 是否开启 PWA -VITE_PWA=false - -# vue-router 的模式 -VITE_ROUTER_HISTORY=hash - -# 是否注入全局loading -VITE_INJECT_APP_LOADING=true - -# 打包后是否生成dist.zip -VITE_ARCHIVER=true diff --git a/apps/web-ele/index.html b/apps/web-ele/index.html deleted file mode 100644 index 2b59b8d75fd..00000000000 --- a/apps/web-ele/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - <%= VITE_APP_TITLE %> - - - - -
- - - diff --git a/apps/web-ele/package.json b/apps/web-ele/package.json deleted file mode 100644 index abbedb63d5e..00000000000 --- a/apps/web-ele/package.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "@vben/web-ele", - "version": "5.5.9", - "homepage": "https://vben.pro/", - "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", - "repository": { - "type": "git", - "url": "git+https://github.com/vbenjs/vue-vben-admin.git", - "directory": "apps/web-ele" - }, - "license": "MIT", - "author": { - "name": "vben", - "email": "ann.vben@gmail.com", - "url": "https://github.com/anncwb" - }, - "type": "module", - "scripts": { - "build": "pnpm vite build --mode production", - "build:analyze": "pnpm vite build --mode analyze", - "dev": "pnpm vite --mode development", - "preview": "vite preview", - "typecheck": "vue-tsc --noEmit --skipLibCheck" - }, - "imports": { - "#/*": "./src/*" - }, - "dependencies": { - "@vben/access": "workspace:*", - "@vben/common-ui": "workspace:*", - "@vben/constants": "workspace:*", - "@vben/hooks": "workspace:*", - "@vben/icons": "workspace:*", - "@vben/layouts": "workspace:*", - "@vben/locales": "workspace:*", - "@vben/plugins": "workspace:*", - "@vben/preferences": "workspace:*", - "@vben/request": "workspace:*", - "@vben/stores": "workspace:*", - "@vben/styles": "workspace:*", - "@vben/types": "workspace:*", - "@vben/utils": "workspace:*", - "@vueuse/core": "catalog:", - "dayjs": "catalog:", - "element-plus": "catalog:", - "pinia": "catalog:", - "vue": "catalog:", - "vue-router": "catalog:" - }, - "devDependencies": { - "unplugin-element-plus": "catalog:" - } -} diff --git a/apps/web-ele/postcss.config.mjs b/apps/web-ele/postcss.config.mjs deleted file mode 100644 index 3d807045561..00000000000 --- a/apps/web-ele/postcss.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config/postcss'; diff --git a/apps/web-ele/public/favicon.ico b/apps/web-ele/public/favicon.ico deleted file mode 100644 index fcf9818e2cf..00000000000 Binary files a/apps/web-ele/public/favicon.ico and /dev/null differ diff --git a/apps/web-ele/src/adapter/component/index.ts b/apps/web-ele/src/adapter/component/index.ts deleted file mode 100644 index 79a46360299..00000000000 --- a/apps/web-ele/src/adapter/component/index.ts +++ /dev/null @@ -1,331 +0,0 @@ -/** - * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 - * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, - */ - -import type { Component } from 'vue'; - -import type { BaseFormComponentType } from '@vben/common-ui'; -import type { Recordable } from '@vben/types'; - -import { defineAsyncComponent, defineComponent, h, ref } from 'vue'; - -import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; -import { $t } from '@vben/locales'; - -import { ElNotification } from 'element-plus'; - -const ElButton = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/button/index'), - import('element-plus/es/components/button/style/css'), - ]).then(([res]) => res.ElButton), -); -const ElCheckbox = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/checkbox/index'), - import('element-plus/es/components/checkbox/style/css'), - ]).then(([res]) => res.ElCheckbox), -); -const ElCheckboxButton = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/checkbox/index'), - import('element-plus/es/components/checkbox-button/style/css'), - ]).then(([res]) => res.ElCheckboxButton), -); -const ElCheckboxGroup = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/checkbox/index'), - import('element-plus/es/components/checkbox-group/style/css'), - ]).then(([res]) => res.ElCheckboxGroup), -); -const ElDatePicker = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/date-picker/index'), - import('element-plus/es/components/date-picker/style/css'), - ]).then(([res]) => res.ElDatePicker), -); -const ElDivider = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/divider/index'), - import('element-plus/es/components/divider/style/css'), - ]).then(([res]) => res.ElDivider), -); -const ElInput = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/input/index'), - import('element-plus/es/components/input/style/css'), - ]).then(([res]) => res.ElInput), -); -const ElInputNumber = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/input-number/index'), - import('element-plus/es/components/input-number/style/css'), - ]).then(([res]) => res.ElInputNumber), -); -const ElRadio = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/radio/index'), - import('element-plus/es/components/radio/style/css'), - ]).then(([res]) => res.ElRadio), -); -const ElRadioButton = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/radio/index'), - import('element-plus/es/components/radio-button/style/css'), - ]).then(([res]) => res.ElRadioButton), -); -const ElRadioGroup = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/radio/index'), - import('element-plus/es/components/radio-group/style/css'), - ]).then(([res]) => res.ElRadioGroup), -); -const ElSelectV2 = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/select-v2/index'), - import('element-plus/es/components/select-v2/style/css'), - ]).then(([res]) => res.ElSelectV2), -); -const ElSpace = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/space/index'), - import('element-plus/es/components/space/style/css'), - ]).then(([res]) => res.ElSpace), -); -const ElSwitch = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/switch/index'), - import('element-plus/es/components/switch/style/css'), - ]).then(([res]) => res.ElSwitch), -); -const ElTimePicker = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/time-picker/index'), - import('element-plus/es/components/time-picker/style/css'), - ]).then(([res]) => res.ElTimePicker), -); -const ElTreeSelect = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/tree-select/index'), - import('element-plus/es/components/tree-select/style/css'), - ]).then(([res]) => res.ElTreeSelect), -); -const ElUpload = defineAsyncComponent(() => - Promise.all([ - import('element-plus/es/components/upload/index'), - import('element-plus/es/components/upload/style/css'), - ]).then(([res]) => res.ElUpload), -); - -const withDefaultPlaceholder = ( - component: T, - type: 'input' | 'select', - componentProps: Recordable = {}, -) => { - return defineComponent({ - name: component.name, - inheritAttrs: false, - setup: (props: any, { attrs, expose, slots }) => { - const placeholder = - props?.placeholder || - attrs?.placeholder || - $t(`ui.placeholder.${type}`); - // 透传组件暴露的方法 - const innerRef = ref(); - expose( - new Proxy( - {}, - { - get: (_target, key) => innerRef.value?.[key], - has: (_target, key) => key in (innerRef.value || {}), - }, - ), - ); - return () => - h( - component, - { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef }, - slots, - ); - }, - }); -}; - -// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 -export type ComponentType = - | 'ApiSelect' - | 'ApiTreeSelect' - | 'Checkbox' - | 'CheckboxGroup' - | 'DatePicker' - | 'Divider' - | 'IconPicker' - | 'Input' - | 'InputNumber' - | 'RadioGroup' - | 'Select' - | 'Space' - | 'Switch' - | 'TimePicker' - | 'TreeSelect' - | 'Upload' - | BaseFormComponentType; - -async function initComponentAdapter() { - const components: Partial> = { - // 如果你的组件体积比较大,可以使用异步加载 - // Button: () => - // import('xxx').then((res) => res.Button), - ApiSelect: withDefaultPlaceholder( - { - ...ApiComponent, - name: 'ApiSelect', - }, - 'select', - { - component: ElSelectV2, - loadingSlot: 'loading', - visibleEvent: 'onVisibleChange', - }, - ), - ApiTreeSelect: withDefaultPlaceholder( - { - ...ApiComponent, - name: 'ApiTreeSelect', - }, - 'select', - { - component: ElTreeSelect, - props: { label: 'label', children: 'children' }, - nodeKey: 'value', - loadingSlot: 'loading', - optionsPropName: 'data', - visibleEvent: 'onVisibleChange', - }, - ), - Checkbox: ElCheckbox, - CheckboxGroup: (props, { attrs, slots }) => { - let defaultSlot; - if (Reflect.has(slots, 'default')) { - defaultSlot = slots.default; - } else { - const { options, isButton } = attrs; - if (Array.isArray(options)) { - defaultSlot = () => - options.map((option) => - h(isButton ? ElCheckboxButton : ElCheckbox, option), - ); - } - } - return h( - ElCheckboxGroup, - { ...props, ...attrs }, - { ...slots, default: defaultSlot }, - ); - }, - // 自定义默认按钮 - DefaultButton: (props, { attrs, slots }) => { - return h(ElButton, { ...props, attrs, type: 'info' }, slots); - }, - // 自定义主要按钮 - PrimaryButton: (props, { attrs, slots }) => { - return h(ElButton, { ...props, attrs, type: 'primary' }, slots); - }, - Divider: ElDivider, - IconPicker: withDefaultPlaceholder(IconPicker, 'select', { - iconSlot: 'append', - modelValueProp: 'model-value', - inputComponent: ElInput, - }), - Input: withDefaultPlaceholder(ElInput, 'input'), - InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'), - RadioGroup: (props, { attrs, slots }) => { - let defaultSlot; - if (Reflect.has(slots, 'default')) { - defaultSlot = slots.default; - } else { - const { options } = attrs; - if (Array.isArray(options)) { - defaultSlot = () => - options.map((option) => - h(attrs.isButton ? ElRadioButton : ElRadio, option), - ); - } - } - return h( - ElRadioGroup, - { ...props, ...attrs }, - { ...slots, default: defaultSlot }, - ); - }, - Select: (props, { attrs, slots }) => { - return h(ElSelectV2, { ...props, attrs }, slots); - }, - Space: ElSpace, - Switch: ElSwitch, - TimePicker: (props, { attrs, slots }) => { - const { name, id, isRange } = props; - const extraProps: Recordable = {}; - if (isRange) { - if (name && !Array.isArray(name)) { - extraProps.name = [name, `${name}_end`]; - } - if (id && !Array.isArray(id)) { - extraProps.id = [id, `${id}_end`]; - } - } - return h( - ElTimePicker, - { - ...props, - ...attrs, - ...extraProps, - }, - slots, - ); - }, - DatePicker: (props, { attrs, slots }) => { - const { name, id, type } = props; - const extraProps: Recordable = {}; - if (type && type.includes('range')) { - if (name && !Array.isArray(name)) { - extraProps.name = [name, `${name}_end`]; - } - if (id && !Array.isArray(id)) { - extraProps.id = [id, `${id}_end`]; - } - } - return h( - ElDatePicker, - { - ...props, - ...attrs, - ...extraProps, - }, - slots, - ); - }, - TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'), - Upload: ElUpload, - }; - - // 将组件注册到全局共享状态中 - globalShareState.setComponents(components); - - // 定义全局共享状态中的消息提示 - globalShareState.defineMessage({ - // 复制成功消息提示 - copyPreferencesSuccess: (title, content) => { - ElNotification({ - title, - message: content, - position: 'bottom-right', - duration: 0, - type: 'success', - }); - }, - }); -} - -export { initComponentAdapter }; diff --git a/apps/web-ele/src/adapter/form.ts b/apps/web-ele/src/adapter/form.ts deleted file mode 100644 index 936c3fe4c3a..00000000000 --- a/apps/web-ele/src/adapter/form.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { - VbenFormSchema as FormSchema, - VbenFormProps, -} from '@vben/common-ui'; - -import type { ComponentType } from './component'; - -import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; -import { $t } from '@vben/locales'; - -async function initSetupVbenForm() { - setupVbenForm({ - config: { - modelPropNameMap: { - Upload: 'fileList', - CheckboxGroup: 'model-value', - }, - }, - defineRules: { - required: (value, _params, ctx) => { - if (value === undefined || value === null || value.length === 0) { - return $t('ui.formRules.required', [ctx.label]); - } - return true; - }, - selectRequired: (value, _params, ctx) => { - if (value === undefined || value === null) { - return $t('ui.formRules.selectRequired', [ctx.label]); - } - return true; - }, - }, - }); -} - -const useVbenForm = useForm; - -export { initSetupVbenForm, useVbenForm, z }; - -export type VbenFormSchema = FormSchema; -export type { VbenFormProps }; diff --git a/apps/web-ele/src/adapter/vxe-table.ts b/apps/web-ele/src/adapter/vxe-table.ts deleted file mode 100644 index 40b8179d399..00000000000 --- a/apps/web-ele/src/adapter/vxe-table.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; - -import { h } from 'vue'; - -import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; - -import { ElButton, ElImage } from 'element-plus'; - -import { useVbenForm } from './form'; - -setupVbenVxeTable({ - configVxeTable: (vxeUI) => { - vxeUI.setConfig({ - grid: { - align: 'center', - border: false, - columnConfig: { - resizable: true, - }, - minHeight: 180, - formConfig: { - // 全局禁用vxe-table的表单配置,使用formOptions - enabled: false, - }, - proxyConfig: { - autoLoad: true, - response: { - result: 'items', - total: 'total', - list: 'items', - }, - showActiveMsg: true, - showResponseMsg: false, - }, - round: true, - showOverflow: true, - size: 'small', - } as VxeTableGridOptions, - }); - - // 表格配置项可以用 cellRender: { name: 'CellImage' }, - vxeUI.renderer.add('CellImage', { - renderTableDefault(_renderOpts, params) { - const { column, row } = params; - const src = row[column.field]; - return h(ElImage, { src, previewSrcList: [src] }); - }, - }); - - // 表格配置项可以用 cellRender: { name: 'CellLink' }, - vxeUI.renderer.add('CellLink', { - renderTableDefault(renderOpts) { - const { props } = renderOpts; - return h( - ElButton, - { size: 'small', link: true }, - { default: () => props?.text }, - ); - }, - }); - - // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 - // vxeUI.formats.add - }, - useVbenForm, -}); - -export { useVbenVxeGrid }; - -export type * from '@vben/plugins/vxe-table'; diff --git a/apps/web-ele/src/api/core/auth.ts b/apps/web-ele/src/api/core/auth.ts deleted file mode 100644 index 71d9f994396..00000000000 --- a/apps/web-ele/src/api/core/auth.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { baseRequestClient, requestClient } from '#/api/request'; - -export namespace AuthApi { - /** 登录接口参数 */ - export interface LoginParams { - password?: string; - username?: string; - } - - /** 登录接口返回值 */ - export interface LoginResult { - accessToken: string; - } - - export interface RefreshTokenResult { - data: string; - status: number; - } -} - -/** - * 登录 - */ -export async function loginApi(data: AuthApi.LoginParams) { - return requestClient.post('/auth/login', data); -} - -/** - * 刷新accessToken - */ -export async function refreshTokenApi() { - return baseRequestClient.post('/auth/refresh', { - withCredentials: true, - }); -} - -/** - * 退出登录 - */ -export async function logoutApi() { - return baseRequestClient.post('/auth/logout', { - withCredentials: true, - }); -} - -/** - * 获取用户权限码 - */ -export async function getAccessCodesApi() { - return requestClient.get('/auth/codes'); -} diff --git a/apps/web-ele/src/api/core/index.ts b/apps/web-ele/src/api/core/index.ts deleted file mode 100644 index 28a5aef47ef..00000000000 --- a/apps/web-ele/src/api/core/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './auth'; -export * from './menu'; -export * from './user'; diff --git a/apps/web-ele/src/api/core/menu.ts b/apps/web-ele/src/api/core/menu.ts deleted file mode 100644 index 9ef60b11cd2..00000000000 --- a/apps/web-ele/src/api/core/menu.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { RouteRecordStringComponent } from '@vben/types'; - -import { requestClient } from '#/api/request'; - -/** - * 获取用户所有菜单 - */ -export async function getAllMenusApi() { - return requestClient.get('/menu/all'); -} diff --git a/apps/web-ele/src/api/core/user.ts b/apps/web-ele/src/api/core/user.ts deleted file mode 100644 index 7e28ea8489a..00000000000 --- a/apps/web-ele/src/api/core/user.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { UserInfo } from '@vben/types'; - -import { requestClient } from '#/api/request'; - -/** - * 获取用户信息 - */ -export async function getUserInfoApi() { - return requestClient.get('/user/info'); -} diff --git a/apps/web-ele/src/api/index.ts b/apps/web-ele/src/api/index.ts deleted file mode 100644 index 4b0e0413762..00000000000 --- a/apps/web-ele/src/api/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './core'; diff --git a/apps/web-ele/src/api/request.ts b/apps/web-ele/src/api/request.ts deleted file mode 100644 index 203b35bf704..00000000000 --- a/apps/web-ele/src/api/request.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * 该文件可自行根据业务逻辑进行调整 - */ -import type { RequestClientOptions } from '@vben/request'; - -import { useAppConfig } from '@vben/hooks'; -import { preferences } from '@vben/preferences'; -import { - authenticateResponseInterceptor, - defaultResponseInterceptor, - errorMessageResponseInterceptor, - RequestClient, -} from '@vben/request'; -import { useAccessStore } from '@vben/stores'; - -import { ElMessage } from 'element-plus'; - -import { useAuthStore } from '#/store'; - -import { refreshTokenApi } from './core'; - -const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); - -function createRequestClient(baseURL: string, options?: RequestClientOptions) { - const client = new RequestClient({ - ...options, - baseURL, - }); - - /** - * 重新认证逻辑 - */ - async function doReAuthenticate() { - console.warn('Access token or refresh token is invalid or expired. '); - const accessStore = useAccessStore(); - const authStore = useAuthStore(); - accessStore.setAccessToken(null); - if ( - preferences.app.loginExpiredMode === 'modal' && - accessStore.isAccessChecked - ) { - accessStore.setLoginExpired(true); - } else { - await authStore.logout(); - } - } - - /** - * 刷新token逻辑 - */ - async function doRefreshToken() { - const accessStore = useAccessStore(); - const resp = await refreshTokenApi(); - const newToken = resp.data; - accessStore.setAccessToken(newToken); - return newToken; - } - - function formatToken(token: null | string) { - return token ? `Bearer ${token}` : null; - } - - // 请求头处理 - client.addRequestInterceptor({ - fulfilled: async (config) => { - const accessStore = useAccessStore(); - - config.headers.Authorization = formatToken(accessStore.accessToken); - config.headers['Accept-Language'] = preferences.app.locale; - return config; - }, - }); - - // 处理返回的响应数据格式 - client.addResponseInterceptor( - defaultResponseInterceptor({ - codeField: 'code', - dataField: 'data', - successCode: 0, - }), - ); - - // token过期的处理 - client.addResponseInterceptor( - authenticateResponseInterceptor({ - client, - doReAuthenticate, - doRefreshToken, - enableRefreshToken: preferences.app.enableRefreshToken, - formatToken, - }), - ); - - // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里 - client.addResponseInterceptor( - errorMessageResponseInterceptor((msg: string, error) => { - // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg - // 当前mock接口返回的错误字段是 error 或者 message - const responseData = error?.response?.data ?? {}; - const errorMessage = responseData?.error ?? responseData?.message ?? ''; - // 如果没有错误信息,则会根据状态码进行提示 - ElMessage.error(errorMessage || msg); - }), - ); - - return client; -} - -export const requestClient = createRequestClient(apiURL, { - responseReturn: 'data', -}); - -export const baseRequestClient = new RequestClient({ baseURL: apiURL }); diff --git a/apps/web-ele/src/app.vue b/apps/web-ele/src/app.vue deleted file mode 100644 index 1217658da55..00000000000 --- a/apps/web-ele/src/app.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/apps/web-ele/src/bootstrap.ts b/apps/web-ele/src/bootstrap.ts deleted file mode 100644 index e5befb5a790..00000000000 --- a/apps/web-ele/src/bootstrap.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { createApp, watchEffect } from 'vue'; - -import { registerAccessDirective } from '@vben/access'; -import { registerLoadingDirective } from '@vben/common-ui'; -import { preferences } from '@vben/preferences'; -import { initStores } from '@vben/stores'; -import '@vben/styles'; -import '@vben/styles/ele'; - -import { useTitle } from '@vueuse/core'; -import { ElLoading } from 'element-plus'; - -import { $t, setupI18n } from '#/locales'; - -import { initComponentAdapter } from './adapter/component'; -import { initSetupVbenForm } from './adapter/form'; -import App from './app.vue'; -import { router } from './router'; - -async function bootstrap(namespace: string) { - // 初始化组件适配器 - await initComponentAdapter(); - - // 初始化表单组件 - await initSetupVbenForm(); - - // // 设置弹窗的默认配置 - // setDefaultModalProps({ - // fullscreenButton: false, - // }); - // // 设置抽屉的默认配置 - // setDefaultDrawerProps({ - // zIndex: 2000, - // }); - const app = createApp(App); - - // 注册Element Plus提供的v-loading指令 - app.directive('loading', ElLoading.directive); - - // 注册Vben提供的v-loading和v-spinning指令 - registerLoadingDirective(app, { - loading: false, // Vben提供的v-loading指令和Element Plus提供的v-loading指令二选一即可,此处false表示不注册Vben提供的v-loading指令 - spinning: 'spinning', - }); - - // 国际化 i18n 配置 - await setupI18n(app); - - // 配置 pinia-tore - await initStores(app, { namespace }); - - // 安装权限指令 - registerAccessDirective(app); - - // 初始化 tippy - const { initTippy } = await import('@vben/common-ui/es/tippy'); - initTippy(app); - - // 配置路由及路由守卫 - app.use(router); - - // 配置Motion插件 - const { MotionPlugin } = await import('@vben/plugins/motion'); - app.use(MotionPlugin); - - // 动态更新标题 - watchEffect(() => { - if (preferences.app.dynamicTitle) { - const routeTitle = router.currentRoute.value.meta?.title; - const pageTitle = - (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; - useTitle(pageTitle); - } - }); - - app.mount('#app'); -} - -export { bootstrap }; diff --git a/apps/web-ele/src/layouts/auth.vue b/apps/web-ele/src/layouts/auth.vue deleted file mode 100644 index 18d415bc7fb..00000000000 --- a/apps/web-ele/src/layouts/auth.vue +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/apps/web-ele/src/layouts/basic.vue b/apps/web-ele/src/layouts/basic.vue deleted file mode 100644 index 805b8a73d2e..00000000000 --- a/apps/web-ele/src/layouts/basic.vue +++ /dev/null @@ -1,162 +0,0 @@ - - - diff --git a/apps/web-ele/src/layouts/index.ts b/apps/web-ele/src/layouts/index.ts deleted file mode 100644 index a4320780540..00000000000 --- a/apps/web-ele/src/layouts/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -const BasicLayout = () => import('./basic.vue'); -const AuthPageLayout = () => import('./auth.vue'); - -const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView); - -export { AuthPageLayout, BasicLayout, IFrameView }; diff --git a/apps/web-ele/src/locales/README.md b/apps/web-ele/src/locales/README.md deleted file mode 100644 index 7b451032e6c..00000000000 --- a/apps/web-ele/src/locales/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# locale - -每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。 diff --git a/apps/web-ele/src/locales/index.ts b/apps/web-ele/src/locales/index.ts deleted file mode 100644 index 57b87dfdf7d..00000000000 --- a/apps/web-ele/src/locales/index.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { Language } from 'element-plus/es/locale'; - -import type { App } from 'vue'; - -import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales'; - -import { ref } from 'vue'; - -import { - $t, - setupI18n as coreSetup, - loadLocalesMapFromDir, -} from '@vben/locales'; -import { preferences } from '@vben/preferences'; - -import dayjs from 'dayjs'; -import enLocale from 'element-plus/es/locale/lang/en'; -import defaultLocale from 'element-plus/es/locale/lang/zh-cn'; - -const elementLocale = ref(defaultLocale); - -const modules = import.meta.glob('./langs/**/*.json'); - -const localesMap = loadLocalesMapFromDir( - /\.\/langs\/([^/]+)\/(.*)\.json$/, - modules, -); -/** - * 加载应用特有的语言包 - * 这里也可以改造为从服务端获取翻译数据 - * @param lang - */ -async function loadMessages(lang: SupportedLanguagesType) { - const [appLocaleMessages] = await Promise.all([ - localesMap[lang]?.(), - loadThirdPartyMessage(lang), - ]); - return appLocaleMessages?.default; -} - -/** - * 加载第三方组件库的语言包 - * @param lang - */ -async function loadThirdPartyMessage(lang: SupportedLanguagesType) { - await Promise.all([loadElementLocale(lang), loadDayjsLocale(lang)]); -} - -/** - * 加载dayjs的语言包 - * @param lang - */ -async function loadDayjsLocale(lang: SupportedLanguagesType) { - let locale; - switch (lang) { - case 'en-US': { - locale = await import('dayjs/locale/en'); - break; - } - case 'zh-CN': { - locale = await import('dayjs/locale/zh-cn'); - break; - } - // 默认使用英语 - default: { - locale = await import('dayjs/locale/en'); - } - } - if (locale) { - dayjs.locale(locale); - } else { - console.error(`Failed to load dayjs locale for ${lang}`); - } -} - -/** - * 加载element-plus的语言包 - * @param lang - */ -async function loadElementLocale(lang: SupportedLanguagesType) { - switch (lang) { - case 'en-US': { - elementLocale.value = enLocale; - break; - } - case 'zh-CN': { - elementLocale.value = defaultLocale; - break; - } - } -} - -async function setupI18n(app: App, options: LocaleSetupOptions = {}) { - await coreSetup(app, { - defaultLocale: preferences.app.locale, - loadMessages, - missingWarn: !import.meta.env.PROD, - ...options, - }); -} - -export { $t, elementLocale, setupI18n }; diff --git a/apps/web-ele/src/locales/langs/en-US/demos.json b/apps/web-ele/src/locales/langs/en-US/demos.json deleted file mode 100644 index 6eddebb531d..00000000000 --- a/apps/web-ele/src/locales/langs/en-US/demos.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title": "Demos", - "elementPlus": "Element Plus", - "form": "Form", - "vben": { - "title": "Project", - "about": "About", - "document": "Document", - "antdv": "Ant Design Vue Version", - "naive-ui": "Naive UI Version", - "element-plus": "Element Plus Version" - } -} diff --git a/apps/web-ele/src/locales/langs/en-US/page.json b/apps/web-ele/src/locales/langs/en-US/page.json deleted file mode 100644 index 618a258c0a6..00000000000 --- a/apps/web-ele/src/locales/langs/en-US/page.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "auth": { - "login": "Login", - "register": "Register", - "codeLogin": "Code Login", - "qrcodeLogin": "Qr Code Login", - "forgetPassword": "Forget Password" - }, - "dashboard": { - "title": "Dashboard", - "analytics": "Analytics", - "workspace": "Workspace" - } -} diff --git a/apps/web-ele/src/locales/langs/zh-CN/demos.json b/apps/web-ele/src/locales/langs/zh-CN/demos.json deleted file mode 100644 index ba6d6ccd0ef..00000000000 --- a/apps/web-ele/src/locales/langs/zh-CN/demos.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title": "演示", - "elementPlus": "Element Plus", - "form": "表单演示", - "vben": { - "title": "项目", - "about": "关于", - "document": "文档", - "antdv": "Ant Design Vue 版本", - "naive-ui": "Naive UI 版本", - "element-plus": "Element Plus 版本" - } -} diff --git a/apps/web-ele/src/locales/langs/zh-CN/page.json b/apps/web-ele/src/locales/langs/zh-CN/page.json deleted file mode 100644 index 4cb67081cbf..00000000000 --- a/apps/web-ele/src/locales/langs/zh-CN/page.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "auth": { - "login": "登录", - "register": "注册", - "codeLogin": "验证码登录", - "qrcodeLogin": "二维码登录", - "forgetPassword": "忘记密码" - }, - "dashboard": { - "title": "概览", - "analytics": "分析页", - "workspace": "工作台" - } -} diff --git a/apps/web-ele/src/main.ts b/apps/web-ele/src/main.ts deleted file mode 100644 index 5d728a02acb..00000000000 --- a/apps/web-ele/src/main.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { initPreferences } from '@vben/preferences'; -import { unmountGlobalLoading } from '@vben/utils'; - -import { overridesPreferences } from './preferences'; - -/** - * 应用初始化完成之后再进行页面加载渲染 - */ -async function initApplication() { - // name用于指定项目唯一标识 - // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据 - const env = import.meta.env.PROD ? 'prod' : 'dev'; - const appVersion = import.meta.env.VITE_APP_VERSION; - const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`; - - // app偏好设置初始化 - await initPreferences({ - namespace, - overrides: overridesPreferences, - }); - - // 启动应用并挂载 - // vue应用主要逻辑及视图 - const { bootstrap } = await import('./bootstrap'); - await bootstrap(namespace); - - // 移除并销毁loading - unmountGlobalLoading(); -} - -initApplication(); diff --git a/apps/web-ele/src/preferences.ts b/apps/web-ele/src/preferences.ts deleted file mode 100644 index b2e9ace43c8..00000000000 --- a/apps/web-ele/src/preferences.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineOverridesPreferences } from '@vben/preferences'; - -/** - * @description 项目配置文件 - * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置 - * !!! 更改配置后请清空缓存,否则可能不生效 - */ -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - name: import.meta.env.VITE_APP_TITLE, - }, -}); diff --git a/apps/web-ele/src/router/access.ts b/apps/web-ele/src/router/access.ts deleted file mode 100644 index 2d07c892b23..00000000000 --- a/apps/web-ele/src/router/access.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { - ComponentRecordType, - GenerateMenuAndRoutesOptions, -} from '@vben/types'; - -import { generateAccessible } from '@vben/access'; -import { preferences } from '@vben/preferences'; - -import { ElMessage } from 'element-plus'; - -import { getAllMenusApi } from '#/api'; -import { BasicLayout, IFrameView } from '#/layouts'; -import { $t } from '#/locales'; - -const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); - -async function generateAccess(options: GenerateMenuAndRoutesOptions) { - const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); - - const layoutMap: ComponentRecordType = { - BasicLayout, - IFrameView, - }; - - return await generateAccessible(preferences.app.accessMode, { - ...options, - fetchMenuListAsync: async () => { - ElMessage({ - duration: 1500, - message: `${$t('common.loadingMenu')}...`, - }); - return await getAllMenusApi(); - }, - // 可以指定没有权限跳转403页面 - forbiddenComponent, - // 如果 route.meta.menuVisibleWithForbidden = true - layoutMap, - pageMap, - }); -} - -export { generateAccess }; diff --git a/apps/web-ele/src/router/guard.ts b/apps/web-ele/src/router/guard.ts deleted file mode 100644 index a1ad6d88cff..00000000000 --- a/apps/web-ele/src/router/guard.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type { Router } from 'vue-router'; - -import { LOGIN_PATH } from '@vben/constants'; -import { preferences } from '@vben/preferences'; -import { useAccessStore, useUserStore } from '@vben/stores'; -import { startProgress, stopProgress } from '@vben/utils'; - -import { accessRoutes, coreRouteNames } from '#/router/routes'; -import { useAuthStore } from '#/store'; - -import { generateAccess } from './access'; - -/** - * 通用守卫配置 - * @param router - */ -function setupCommonGuard(router: Router) { - // 记录已经加载的页面 - const loadedPaths = new Set(); - - router.beforeEach((to) => { - to.meta.loaded = loadedPaths.has(to.path); - - // 页面加载进度条 - if (!to.meta.loaded && preferences.transition.progress) { - startProgress(); - } - return true; - }); - - router.afterEach((to) => { - // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 - - loadedPaths.add(to.path); - - // 关闭页面加载进度条 - if (preferences.transition.progress) { - stopProgress(); - } - }); -} - -/** - * 权限访问守卫配置 - * @param router - */ -function setupAccessGuard(router: Router) { - router.beforeEach(async (to, from) => { - const accessStore = useAccessStore(); - const userStore = useUserStore(); - const authStore = useAuthStore(); - - // 基本路由,这些路由不需要进入权限拦截 - if (coreRouteNames.includes(to.name as string)) { - if (to.path === LOGIN_PATH && accessStore.accessToken) { - return decodeURIComponent( - (to.query?.redirect as string) || - userStore.userInfo?.homePath || - preferences.app.defaultHomePath, - ); - } - return true; - } - - // accessToken 检查 - if (!accessStore.accessToken) { - // 明确声明忽略权限访问权限,则可以访问 - if (to.meta.ignoreAccess) { - return true; - } - - // 没有访问权限,跳转登录页面 - if (to.fullPath !== LOGIN_PATH) { - return { - path: LOGIN_PATH, - // 如不需要,直接删除 query - query: - to.fullPath === preferences.app.defaultHomePath - ? {} - : { redirect: encodeURIComponent(to.fullPath) }, - // 携带当前跳转的页面,登录后重新跳转该页面 - replace: true, - }; - } - return to; - } - - // 是否已经生成过动态路由 - if (accessStore.isAccessChecked) { - return true; - } - - // 生成路由表 - // 当前登录用户拥有的角色标识列表 - const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); - const userRoles = userInfo.roles ?? []; - - // 生成菜单和路由 - const { accessibleMenus, accessibleRoutes } = await generateAccess({ - roles: userRoles, - router, - // 则会在菜单中显示,但是访问会被重定向到403 - routes: accessRoutes, - }); - - // 保存菜单信息和路由信息 - accessStore.setAccessMenus(accessibleMenus); - accessStore.setAccessRoutes(accessibleRoutes); - accessStore.setIsAccessChecked(true); - const redirectPath = (from.query.redirect ?? - (to.path === preferences.app.defaultHomePath - ? userInfo.homePath || preferences.app.defaultHomePath - : to.fullPath)) as string; - - return { - ...router.resolve(decodeURIComponent(redirectPath)), - replace: true, - }; - }); -} - -/** - * 项目守卫配置 - * @param router - */ -function createRouterGuard(router: Router) { - /** 通用 */ - setupCommonGuard(router); - /** 权限访问 */ - setupAccessGuard(router); -} - -export { createRouterGuard }; diff --git a/apps/web-ele/src/router/index.ts b/apps/web-ele/src/router/index.ts deleted file mode 100644 index 48402303425..00000000000 --- a/apps/web-ele/src/router/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - createRouter, - createWebHashHistory, - createWebHistory, -} from 'vue-router'; - -import { resetStaticRoutes } from '@vben/utils'; - -import { createRouterGuard } from './guard'; -import { routes } from './routes'; - -/** - * @zh_CN 创建vue-router实例 - */ -const router = createRouter({ - history: - import.meta.env.VITE_ROUTER_HISTORY === 'hash' - ? createWebHashHistory(import.meta.env.VITE_BASE) - : createWebHistory(import.meta.env.VITE_BASE), - // 应该添加到路由的初始路由列表。 - routes, - scrollBehavior: (to, _from, savedPosition) => { - if (savedPosition) { - return savedPosition; - } - return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 }; - }, - // 是否应该禁止尾部斜杠。 - // strict: true, -}); - -const resetRoutes = () => resetStaticRoutes(router, routes); - -// 创建路由守卫 -createRouterGuard(router); - -export { resetRoutes, router }; diff --git a/apps/web-ele/src/router/routes/core.ts b/apps/web-ele/src/router/routes/core.ts deleted file mode 100644 index 949b0b65acf..00000000000 --- a/apps/web-ele/src/router/routes/core.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { LOGIN_PATH } from '@vben/constants'; -import { preferences } from '@vben/preferences'; - -import { $t } from '#/locales'; - -const BasicLayout = () => import('#/layouts/basic.vue'); -const AuthPageLayout = () => import('#/layouts/auth.vue'); -/** 全局404页面 */ -const fallbackNotFoundRoute: RouteRecordRaw = { - component: () => import('#/views/_core/fallback/not-found.vue'), - meta: { - hideInBreadcrumb: true, - hideInMenu: true, - hideInTab: true, - title: '404', - }, - name: 'FallbackNotFound', - path: '/:path(.*)*', -}; - -/** 基本路由,这些路由是必须存在的 */ -const coreRoutes: RouteRecordRaw[] = [ - /** - * 根路由 - * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。 - * 此路由必须存在,且不应修改 - */ - { - component: BasicLayout, - meta: { - hideInBreadcrumb: true, - title: 'Root', - }, - name: 'Root', - path: '/', - redirect: preferences.app.defaultHomePath, - children: [], - }, - { - component: AuthPageLayout, - meta: { - hideInTab: true, - title: 'Authentication', - }, - name: 'Authentication', - path: '/auth', - redirect: LOGIN_PATH, - children: [ - { - name: 'Login', - path: 'login', - component: () => import('#/views/_core/authentication/login.vue'), - meta: { - title: $t('page.auth.login'), - }, - }, - { - name: 'CodeLogin', - path: 'code-login', - component: () => import('#/views/_core/authentication/code-login.vue'), - meta: { - title: $t('page.auth.codeLogin'), - }, - }, - { - name: 'QrCodeLogin', - path: 'qrcode-login', - component: () => - import('#/views/_core/authentication/qrcode-login.vue'), - meta: { - title: $t('page.auth.qrcodeLogin'), - }, - }, - { - name: 'ForgetPassword', - path: 'forget-password', - component: () => - import('#/views/_core/authentication/forget-password.vue'), - meta: { - title: $t('page.auth.forgetPassword'), - }, - }, - { - name: 'Register', - path: 'register', - component: () => import('#/views/_core/authentication/register.vue'), - meta: { - title: $t('page.auth.register'), - }, - }, - ], - }, -]; - -export { coreRoutes, fallbackNotFoundRoute }; diff --git a/apps/web-ele/src/router/routes/index.ts b/apps/web-ele/src/router/routes/index.ts deleted file mode 100644 index e6fb1440204..00000000000 --- a/apps/web-ele/src/router/routes/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { mergeRouteModules, traverseTreeValues } from '@vben/utils'; - -import { coreRoutes, fallbackNotFoundRoute } from './core'; - -const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { - eager: true, -}); - -// 有需要可以自行打开注释,并创建文件夹 -// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); -// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); - -/** 动态路由 */ -const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); - -/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ -// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); -// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles); -const staticRoutes: RouteRecordRaw[] = []; -const externalRoutes: RouteRecordRaw[] = []; - -/** 路由列表,由基本路由、外部路由和404兜底路由组成 - * 无需走权限验证(会一直显示在菜单中) */ -const routes: RouteRecordRaw[] = [ - ...coreRoutes, - ...externalRoutes, - fallbackNotFoundRoute, -]; - -/** 基本路由列表,这些路由不需要进入权限拦截 */ -const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name); - -/** 有权限校验的路由列表,包含动态路由和静态路由 */ -const accessRoutes = [...dynamicRoutes, ...staticRoutes]; -export { accessRoutes, coreRouteNames, routes }; diff --git a/apps/web-ele/src/router/routes/modules/dashboard.ts b/apps/web-ele/src/router/routes/modules/dashboard.ts deleted file mode 100644 index 5254dc65de4..00000000000 --- a/apps/web-ele/src/router/routes/modules/dashboard.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'lucide:layout-dashboard', - order: -1, - title: $t('page.dashboard.title'), - }, - name: 'Dashboard', - path: '/dashboard', - children: [ - { - name: 'Analytics', - path: '/analytics', - component: () => import('#/views/dashboard/analytics/index.vue'), - meta: { - affixTab: true, - icon: 'lucide:area-chart', - title: $t('page.dashboard.analytics'), - }, - }, - { - name: 'Workspace', - path: '/workspace', - component: () => import('#/views/dashboard/workspace/index.vue'), - meta: { - icon: 'carbon:workspace', - title: $t('page.dashboard.workspace'), - }, - }, - ], - }, -]; - -export default routes; diff --git a/apps/web-ele/src/router/routes/modules/demos.ts b/apps/web-ele/src/router/routes/modules/demos.ts deleted file mode 100644 index 907ea3f4589..00000000000 --- a/apps/web-ele/src/router/routes/modules/demos.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'ic:baseline-view-in-ar', - keepAlive: true, - order: 1000, - title: $t('demos.title'), - }, - name: 'Demos', - path: '/demos', - children: [ - { - meta: { - title: $t('demos.elementPlus'), - }, - name: 'NaiveDemos', - path: '/demos/element', - component: () => import('#/views/demos/element/index.vue'), - }, - { - meta: { - title: $t('demos.form'), - }, - name: 'BasicForm', - path: '/demos/form', - component: () => import('#/views/demos/form/basic.vue'), - }, - ], - }, -]; - -export default routes; diff --git a/apps/web-ele/src/router/routes/modules/vben.ts b/apps/web-ele/src/router/routes/modules/vben.ts deleted file mode 100644 index 20fbc96c0c3..00000000000 --- a/apps/web-ele/src/router/routes/modules/vben.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { - VBEN_ANT_PREVIEW_URL, - VBEN_DOC_URL, - VBEN_GITHUB_URL, - VBEN_LOGO_URL, - VBEN_NAIVE_PREVIEW_URL, -} from '@vben/constants'; -import { SvgAntdvLogoIcon } from '@vben/icons'; - -import { IFrameView } from '#/layouts'; -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - badgeType: 'dot', - icon: VBEN_LOGO_URL, - order: 9998, - title: $t('demos.vben.title'), - }, - name: 'VbenProject', - path: '/vben-admin', - children: [ - { - name: 'VbenDocument', - path: '/vben-admin/document', - component: IFrameView, - meta: { - icon: 'lucide:book-open-text', - link: VBEN_DOC_URL, - title: $t('demos.vben.document'), - }, - }, - { - name: 'VbenGithub', - path: '/vben-admin/github', - component: IFrameView, - meta: { - icon: 'mdi:github', - link: VBEN_GITHUB_URL, - title: 'Github', - }, - }, - { - name: 'VbenNaive', - path: '/vben-admin/naive', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: 'logos:naiveui', - link: VBEN_NAIVE_PREVIEW_URL, - title: $t('demos.vben.naive-ui'), - }, - }, - { - name: 'VbenAntd', - path: '/vben-admin/antd', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: SvgAntdvLogoIcon, - link: VBEN_ANT_PREVIEW_URL, - title: $t('demos.vben.antdv'), - }, - }, - ], - }, - { - name: 'VbenAbout', - path: '/vben-admin/about', - component: () => import('#/views/_core/about/index.vue'), - meta: { - icon: 'lucide:copyright', - title: $t('demos.vben.about'), - order: 9999, - }, - }, -]; - -export default routes; diff --git a/apps/web-ele/src/store/auth.ts b/apps/web-ele/src/store/auth.ts deleted file mode 100644 index 74fadfe2479..00000000000 --- a/apps/web-ele/src/store/auth.ts +++ /dev/null @@ -1,119 +0,0 @@ -import type { Recordable, UserInfo } from '@vben/types'; - -import { ref } from 'vue'; -import { useRouter } from 'vue-router'; - -import { LOGIN_PATH } from '@vben/constants'; -import { preferences } from '@vben/preferences'; -import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; - -import { ElNotification } from 'element-plus'; -import { defineStore } from 'pinia'; - -import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; -import { $t } from '#/locales'; - -export const useAuthStore = defineStore('auth', () => { - const accessStore = useAccessStore(); - const userStore = useUserStore(); - const router = useRouter(); - - const loginLoading = ref(false); - - /** - * 异步处理登录操作 - * Asynchronously handle the login process - * @param params 登录表单数据 - */ - async function authLogin( - params: Recordable, - onSuccess?: () => Promise | void, - ) { - // 异步处理用户登录操作并获取 accessToken - let userInfo: null | UserInfo = null; - try { - loginLoading.value = true; - const { accessToken } = await loginApi(params); - - // 如果成功获取到 accessToken - if (accessToken) { - // 将 accessToken 存储到 accessStore 中 - accessStore.setAccessToken(accessToken); - - // 获取用户信息并存储到 accessStore 中 - const [fetchUserInfoResult, accessCodes] = await Promise.all([ - fetchUserInfo(), - getAccessCodesApi(), - ]); - - userInfo = fetchUserInfoResult; - - userStore.setUserInfo(userInfo); - accessStore.setAccessCodes(accessCodes); - - if (accessStore.loginExpired) { - accessStore.setLoginExpired(false); - } else { - onSuccess - ? await onSuccess?.() - : await router.push( - userInfo.homePath || preferences.app.defaultHomePath, - ); - } - - if (userInfo?.realName) { - ElNotification({ - message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, - title: $t('authentication.loginSuccess'), - type: 'success', - }); - } - } - } finally { - loginLoading.value = false; - } - - return { - userInfo, - }; - } - - async function logout(redirect: boolean = true) { - try { - await logoutApi(); - } catch { - // 不做任何处理 - } - resetAllStores(); - accessStore.setLoginExpired(false); - - // 回登录页带上当前路由地址 - await router.replace({ - path: LOGIN_PATH, - query: redirect - ? { - redirect: encodeURIComponent(router.currentRoute.value.fullPath), - } - : {}, - }); - } - - async function fetchUserInfo() { - let userInfo: null | UserInfo = null; - userInfo = await getUserInfoApi(); - userStore.setUserInfo(userInfo); - return userInfo; - } - - function $reset() { - loginLoading.value = false; - } - - return { - $reset, - authLogin, - fetchUserInfo, - loginLoading, - logout, - }; -}); diff --git a/apps/web-ele/src/store/index.ts b/apps/web-ele/src/store/index.ts deleted file mode 100644 index 269586ee8b8..00000000000 --- a/apps/web-ele/src/store/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './auth'; diff --git a/apps/web-ele/src/views/_core/README.md b/apps/web-ele/src/views/_core/README.md deleted file mode 100644 index 8248afe6c10..00000000000 --- a/apps/web-ele/src/views/_core/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# \_core - -此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。 diff --git a/apps/web-ele/src/views/_core/about/index.vue b/apps/web-ele/src/views/_core/about/index.vue deleted file mode 100644 index 0ee524335c2..00000000000 --- a/apps/web-ele/src/views/_core/about/index.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/_core/authentication/code-login.vue b/apps/web-ele/src/views/_core/authentication/code-login.vue deleted file mode 100644 index acfd1fd7881..00000000000 --- a/apps/web-ele/src/views/_core/authentication/code-login.vue +++ /dev/null @@ -1,69 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/_core/authentication/forget-password.vue b/apps/web-ele/src/views/_core/authentication/forget-password.vue deleted file mode 100644 index fef0d427945..00000000000 --- a/apps/web-ele/src/views/_core/authentication/forget-password.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/_core/authentication/login.vue b/apps/web-ele/src/views/_core/authentication/login.vue deleted file mode 100644 index 099e4c8c026..00000000000 --- a/apps/web-ele/src/views/_core/authentication/login.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/_core/authentication/qrcode-login.vue b/apps/web-ele/src/views/_core/authentication/qrcode-login.vue deleted file mode 100644 index 23f5f2dad58..00000000000 --- a/apps/web-ele/src/views/_core/authentication/qrcode-login.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/_core/authentication/register.vue b/apps/web-ele/src/views/_core/authentication/register.vue deleted file mode 100644 index b1a5de726d2..00000000000 --- a/apps/web-ele/src/views/_core/authentication/register.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/_core/fallback/coming-soon.vue b/apps/web-ele/src/views/_core/fallback/coming-soon.vue deleted file mode 100644 index f394930f21a..00000000000 --- a/apps/web-ele/src/views/_core/fallback/coming-soon.vue +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/_core/fallback/forbidden.vue b/apps/web-ele/src/views/_core/fallback/forbidden.vue deleted file mode 100644 index 8ea65fedb52..00000000000 --- a/apps/web-ele/src/views/_core/fallback/forbidden.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/_core/fallback/internal-error.vue b/apps/web-ele/src/views/_core/fallback/internal-error.vue deleted file mode 100644 index 819a47d5ea8..00000000000 --- a/apps/web-ele/src/views/_core/fallback/internal-error.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/_core/fallback/not-found.vue b/apps/web-ele/src/views/_core/fallback/not-found.vue deleted file mode 100644 index 4d178e9cbd4..00000000000 --- a/apps/web-ele/src/views/_core/fallback/not-found.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/_core/fallback/offline.vue b/apps/web-ele/src/views/_core/fallback/offline.vue deleted file mode 100644 index 5de4a88de4e..00000000000 --- a/apps/web-ele/src/views/_core/fallback/offline.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue deleted file mode 100644 index f1f0b232a62..00000000000 --- a/apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue deleted file mode 100644 index 190fb41f47d..00000000000 --- a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue deleted file mode 100644 index 02f5091231b..00000000000 --- a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue deleted file mode 100644 index 0915c7af7dd..00000000000 --- a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue deleted file mode 100644 index 7e0f10133d8..00000000000 --- a/apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/analytics/index.vue b/apps/web-ele/src/views/dashboard/analytics/index.vue deleted file mode 100644 index 5e3d6d285ae..00000000000 --- a/apps/web-ele/src/views/dashboard/analytics/index.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/dashboard/workspace/index.vue b/apps/web-ele/src/views/dashboard/workspace/index.vue deleted file mode 100644 index b95d6138166..00000000000 --- a/apps/web-ele/src/views/dashboard/workspace/index.vue +++ /dev/null @@ -1,266 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/demos/element/index.vue b/apps/web-ele/src/views/demos/element/index.vue deleted file mode 100644 index 0a7012d63ee..00000000000 --- a/apps/web-ele/src/views/demos/element/index.vue +++ /dev/null @@ -1,117 +0,0 @@ - - - diff --git a/apps/web-ele/src/views/demos/form/basic.vue b/apps/web-ele/src/views/demos/form/basic.vue deleted file mode 100644 index 0ecab58643e..00000000000 --- a/apps/web-ele/src/views/demos/form/basic.vue +++ /dev/null @@ -1,191 +0,0 @@ - - diff --git a/apps/web-ele/tailwind.config.mjs b/apps/web-ele/tailwind.config.mjs deleted file mode 100644 index f17f556fa9f..00000000000 --- a/apps/web-ele/tailwind.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config'; diff --git a/apps/web-ele/tsconfig.json b/apps/web-ele/tsconfig.json deleted file mode 100644 index 02c287fe642..00000000000 --- a/apps/web-ele/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@vben/tsconfig/web-app.json", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "#/*": ["./src/*"] - } - }, - "references": [{ "path": "./tsconfig.node.json" }], - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] -} diff --git a/apps/web-ele/tsconfig.node.json b/apps/web-ele/tsconfig.node.json deleted file mode 100644 index c2f0d86cc78..00000000000 --- a/apps/web-ele/tsconfig.node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@vben/tsconfig/node.json", - "compilerOptions": { - "composite": true, - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "noEmit": false - }, - "include": ["vite.config.mts"] -} diff --git a/apps/web-ele/vite.config.mts b/apps/web-ele/vite.config.mts deleted file mode 100644 index 9f1e7235379..00000000000 --- a/apps/web-ele/vite.config.mts +++ /dev/null @@ -1,27 +0,0 @@ -import { defineConfig } from '@vben/vite-config'; - -import ElementPlus from 'unplugin-element-plus/vite'; - -export default defineConfig(async () => { - return { - application: {}, - vite: { - plugins: [ - ElementPlus({ - format: 'esm', - }), - ], - server: { - proxy: { - '/api': { - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, ''), - // mock代理目标地址 - target: 'http://localhost:5320/api', - ws: true, - }, - }, - }, - }, - }; -}); diff --git a/apps/web-naive/.env b/apps/web-naive/.env deleted file mode 100644 index 213b52ce9a7..00000000000 --- a/apps/web-naive/.env +++ /dev/null @@ -1,8 +0,0 @@ -# 应用标题 -VITE_APP_TITLE=Vben Admin Naive - -# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 -VITE_APP_NAMESPACE=vben-web-naive - -# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密 -VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key diff --git a/apps/web-naive/.env.analyze b/apps/web-naive/.env.analyze deleted file mode 100644 index ffafa8dd529..00000000000 --- a/apps/web-naive/.env.analyze +++ /dev/null @@ -1,7 +0,0 @@ -# public path -VITE_BASE=/ - -# Basic interface address SPA -VITE_GLOB_API_URL=/api - -VITE_VISUALIZER=true diff --git a/apps/web-naive/.env.development b/apps/web-naive/.env.development deleted file mode 100644 index 11c5254ae44..00000000000 --- a/apps/web-naive/.env.development +++ /dev/null @@ -1,16 +0,0 @@ -# 端口号 -VITE_PORT=5888 - -VITE_BASE=/ - -# 接口地址 -VITE_GLOB_API_URL=/api - -# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 -VITE_NITRO_MOCK=true - -# 是否打开 devtools,true 为打开,false 为关闭 -VITE_DEVTOOLS=false - -# 是否注入全局loading -VITE_INJECT_APP_LOADING=true diff --git a/apps/web-naive/.env.production b/apps/web-naive/.env.production deleted file mode 100644 index 5375847a6ca..00000000000 --- a/apps/web-naive/.env.production +++ /dev/null @@ -1,19 +0,0 @@ -VITE_BASE=/ - -# 接口地址 -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api - -# 是否开启压缩,可以设置为 none, brotli, gzip -VITE_COMPRESS=none - -# 是否开启 PWA -VITE_PWA=false - -# vue-router 的模式 -VITE_ROUTER_HISTORY=hash - -# 是否注入全局loading -VITE_INJECT_APP_LOADING=true - -# 打包后是否生成dist.zip -VITE_ARCHIVER=true diff --git a/apps/web-naive/index.html b/apps/web-naive/index.html deleted file mode 100644 index 7ea63841879..00000000000 --- a/apps/web-naive/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - <%= VITE_APP_TITLE %> - - - - -
- - - diff --git a/apps/web-naive/package.json b/apps/web-naive/package.json deleted file mode 100644 index ff69374ffca..00000000000 --- a/apps/web-naive/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@vben/web-naive", - "version": "5.5.9", - "homepage": "https://vben.pro/", - "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", - "repository": { - "type": "git", - "url": "git+https://github.com/vbenjs/vue-vben-admin.git", - "directory": "apps/web-naive" - }, - "license": "MIT", - "author": { - "name": "vben", - "email": "ann.vben@gmail.com", - "url": "https://github.com/anncwb" - }, - "type": "module", - "scripts": { - "build": "pnpm vite build --mode production", - "build:analyze": "pnpm vite build --mode analyze", - "dev": "pnpm vite --mode development", - "preview": "vite preview", - "typecheck": "vue-tsc --noEmit --skipLibCheck" - }, - "imports": { - "#/*": "./src/*" - }, - "dependencies": { - "@vben/access": "workspace:*", - "@vben/common-ui": "workspace:*", - "@vben/constants": "workspace:*", - "@vben/hooks": "workspace:*", - "@vben/icons": "workspace:*", - "@vben/layouts": "workspace:*", - "@vben/locales": "workspace:*", - "@vben/plugins": "workspace:*", - "@vben/preferences": "workspace:*", - "@vben/request": "workspace:*", - "@vben/stores": "workspace:*", - "@vben/styles": "workspace:*", - "@vben/types": "workspace:*", - "@vben/utils": "workspace:*", - "@vueuse/core": "catalog:", - "naive-ui": "catalog:", - "pinia": "catalog:", - "vue": "catalog:", - "vue-router": "catalog:" - } -} diff --git a/apps/web-naive/postcss.config.mjs b/apps/web-naive/postcss.config.mjs deleted file mode 100644 index 3d807045561..00000000000 --- a/apps/web-naive/postcss.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config/postcss'; diff --git a/apps/web-naive/public/favicon.ico b/apps/web-naive/public/favicon.ico deleted file mode 100644 index fcf9818e2cf..00000000000 Binary files a/apps/web-naive/public/favicon.ico and /dev/null differ diff --git a/apps/web-naive/src/adapter/component/index.ts b/apps/web-naive/src/adapter/component/index.ts deleted file mode 100644 index f9df20273d6..00000000000 --- a/apps/web-naive/src/adapter/component/index.ts +++ /dev/null @@ -1,231 +0,0 @@ -/** - * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 - * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, - */ - -import type { Component } from 'vue'; - -import type { BaseFormComponentType } from '@vben/common-ui'; -import type { Recordable } from '@vben/types'; - -import { defineAsyncComponent, defineComponent, h, ref } from 'vue'; - -import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; -import { $t } from '@vben/locales'; - -import { message } from '#/adapter/naive'; - -const NButton = defineAsyncComponent(() => - import('naive-ui/es/button').then((res) => res.NButton), -); -const NCheckbox = defineAsyncComponent(() => - import('naive-ui/es/checkbox').then((res) => res.NCheckbox), -); -const NCheckboxGroup = defineAsyncComponent(() => - import('naive-ui/es/checkbox').then((res) => res.NCheckboxGroup), -); -const NDatePicker = defineAsyncComponent(() => - import('naive-ui/es/date-picker').then((res) => res.NDatePicker), -); -const NDivider = defineAsyncComponent(() => - import('naive-ui/es/divider').then((res) => res.NDivider), -); -const NInput = defineAsyncComponent(() => - import('naive-ui/es/input').then((res) => res.NInput), -); -const NInputNumber = defineAsyncComponent(() => - import('naive-ui/es/input-number').then((res) => res.NInputNumber), -); -const NRadio = defineAsyncComponent(() => - import('naive-ui/es/radio').then((res) => res.NRadio), -); -const NRadioButton = defineAsyncComponent(() => - import('naive-ui/es/radio').then((res) => res.NRadioButton), -); -const NRadioGroup = defineAsyncComponent(() => - import('naive-ui/es/radio').then((res) => res.NRadioGroup), -); -const NSelect = defineAsyncComponent(() => - import('naive-ui/es/select').then((res) => res.NSelect), -); -const NSpace = defineAsyncComponent(() => - import('naive-ui/es/space').then((res) => res.NSpace), -); -const NSwitch = defineAsyncComponent(() => - import('naive-ui/es/switch').then((res) => res.NSwitch), -); -const NTimePicker = defineAsyncComponent(() => - import('naive-ui/es/time-picker').then((res) => res.NTimePicker), -); -const NTreeSelect = defineAsyncComponent(() => - import('naive-ui/es/tree-select').then((res) => res.NTreeSelect), -); -const NUpload = defineAsyncComponent(() => - import('naive-ui/es/upload').then((res) => res.NUpload), -); - -const withDefaultPlaceholder = ( - component: T, - type: 'input' | 'select', - componentProps: Recordable = {}, -) => { - return defineComponent({ - name: component.name, - inheritAttrs: false, - setup: (props: any, { attrs, expose, slots }) => { - const placeholder = - props?.placeholder || - attrs?.placeholder || - $t(`ui.placeholder.${type}`); - // 透传组件暴露的方法 - const innerRef = ref(); - expose( - new Proxy( - {}, - { - get: (_target, key) => innerRef.value?.[key], - has: (_target, key) => key in (innerRef.value || {}), - }, - ), - ); - return () => - h( - component, - { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef }, - slots, - ); - }, - }); -}; - -// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 -export type ComponentType = - | 'ApiSelect' - | 'ApiTreeSelect' - | 'Checkbox' - | 'CheckboxGroup' - | 'DatePicker' - | 'Divider' - | 'IconPicker' - | 'Input' - | 'InputNumber' - | 'RadioGroup' - | 'Select' - | 'Space' - | 'Switch' - | 'TimePicker' - | 'TreeSelect' - | 'Upload' - | BaseFormComponentType; - -async function initComponentAdapter() { - const components: Partial> = { - // 如果你的组件体积比较大,可以使用异步加载 - // Button: () => - // import('xxx').then((res) => res.Button), - - ApiSelect: withDefaultPlaceholder( - { - ...ApiComponent, - name: 'ApiSelect', - }, - 'select', - { - component: NSelect, - modelPropName: 'value', - }, - ), - ApiTreeSelect: withDefaultPlaceholder( - { - ...ApiComponent, - name: 'ApiTreeSelect', - }, - 'select', - { - component: NTreeSelect, - nodeKey: 'value', - loadingSlot: 'arrow', - keyField: 'value', - modelPropName: 'value', - optionsPropName: 'options', - visibleEvent: 'onVisibleChange', - }, - ), - Checkbox: NCheckbox, - CheckboxGroup: (props, { attrs, slots }) => { - let defaultSlot; - if (Reflect.has(slots, 'default')) { - defaultSlot = slots.default; - } else { - const { options } = attrs; - if (Array.isArray(options)) { - defaultSlot = () => options.map((option) => h(NCheckbox, option)); - } - } - return h( - NCheckboxGroup, - { ...props, ...attrs }, - { default: defaultSlot }, - ); - }, - DatePicker: NDatePicker, - // 自定义默认按钮 - DefaultButton: (props, { attrs, slots }) => { - return h(NButton, { ...props, attrs, type: 'default' }, slots); - }, - // 自定义主要按钮 - PrimaryButton: (props, { attrs, slots }) => { - return h(NButton, { ...props, attrs, type: 'primary' }, slots); - }, - Divider: NDivider, - IconPicker: withDefaultPlaceholder(IconPicker, 'select', { - iconSlot: 'suffix', - inputComponent: NInput, - }), - Input: withDefaultPlaceholder(NInput, 'input'), - InputNumber: withDefaultPlaceholder(NInputNumber, 'input'), - RadioGroup: (props, { attrs, slots }) => { - let defaultSlot; - if (Reflect.has(slots, 'default')) { - defaultSlot = slots.default; - } else { - const { options } = attrs; - if (Array.isArray(options)) { - defaultSlot = () => - options.map((option) => - h(attrs.isButton ? NRadioButton : NRadio, option), - ); - } - } - const groupRender = h( - NRadioGroup, - { ...props, ...attrs }, - { default: defaultSlot }, - ); - return attrs.isButton - ? h(NSpace, { vertical: true }, () => groupRender) - : groupRender; - }, - Select: withDefaultPlaceholder(NSelect, 'select'), - Space: NSpace, - Switch: NSwitch, - TimePicker: NTimePicker, - TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'), - Upload: NUpload, - }; - - // 将组件注册到全局共享状态中 - globalShareState.setComponents(components); - - // 定义全局共享状态中的消息提示 - globalShareState.defineMessage({ - // 复制成功消息提示 - copyPreferencesSuccess: (title, content) => { - message.success(content || title, { - duration: 0, - }); - }, - }); -} - -export { initComponentAdapter }; diff --git a/apps/web-naive/src/adapter/form.ts b/apps/web-naive/src/adapter/form.ts deleted file mode 100644 index 9de44a01d2b..00000000000 --- a/apps/web-naive/src/adapter/form.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { - VbenFormSchema as FormSchema, - VbenFormProps, -} from '@vben/common-ui'; - -import type { ComponentType } from './component'; - -import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; -import { $t } from '@vben/locales'; - -async function initSetupVbenForm() { - setupVbenForm({ - config: { - // naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效 - emptyStateValue: null, - baseModelPropName: 'value', - modelPropNameMap: { - Checkbox: 'checked', - Radio: 'checked', - Upload: 'fileList', - }, - }, - defineRules: { - required: (value, _params, ctx) => { - if (value === undefined || value === null || value.length === 0) { - return $t('ui.formRules.required', [ctx.label]); - } - return true; - }, - selectRequired: (value, _params, ctx) => { - if (value === undefined || value === null) { - return $t('ui.formRules.selectRequired', [ctx.label]); - } - return true; - }, - }, - }); -} - -const useVbenForm = useForm; - -export { initSetupVbenForm, useVbenForm, z }; - -export type VbenFormSchema = FormSchema; -export type { VbenFormProps }; diff --git a/apps/web-naive/src/adapter/naive.ts b/apps/web-naive/src/adapter/naive.ts deleted file mode 100644 index 1eb7b7b6e8d..00000000000 --- a/apps/web-naive/src/adapter/naive.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { computed } from 'vue'; - -import { preferences } from '@vben/preferences'; -import '@vben/styles'; - -import { createDiscreteApi, darkTheme, lightTheme } from 'naive-ui'; - -const themeOverridesProviderProps = computed(() => ({ - themeOverrides: preferences.theme.mode === 'light' ? lightTheme : darkTheme, -})); - -const themeProviderProps = computed(() => ({ - theme: preferences.theme.mode === 'light' ? lightTheme : darkTheme, -})); - -export const { dialog, loadingBar, message, modal, notification } = - createDiscreteApi( - ['message', 'dialog', 'notification', 'loadingBar', 'modal'], - { - configProviderProps: themeProviderProps, - loadingBarProviderProps: themeOverridesProviderProps, - messageProviderProps: themeOverridesProviderProps, - notificationProviderProps: themeOverridesProviderProps, - }, - ); diff --git a/apps/web-naive/src/adapter/vxe-table.ts b/apps/web-naive/src/adapter/vxe-table.ts deleted file mode 100644 index 3bad067cd16..00000000000 --- a/apps/web-naive/src/adapter/vxe-table.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; - -import { h } from 'vue'; - -import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; - -import { NButton, NImage } from 'naive-ui'; - -import { useVbenForm } from './form'; - -setupVbenVxeTable({ - configVxeTable: (vxeUI) => { - vxeUI.setConfig({ - grid: { - align: 'center', - border: false, - columnConfig: { - resizable: true, - }, - minHeight: 180, - formConfig: { - // 全局禁用vxe-table的表单配置,使用formOptions - enabled: false, - }, - proxyConfig: { - autoLoad: true, - response: { - result: 'items', - total: 'total', - list: 'items', - }, - showActiveMsg: true, - showResponseMsg: false, - }, - round: true, - showOverflow: true, - size: 'small', - } as VxeTableGridOptions, - }); - - // 表格配置项可以用 cellRender: { name: 'CellImage' }, - vxeUI.renderer.add('CellImage', { - renderTableDefault(_renderOpts, params) { - const { column, row } = params; - return h(NImage, { src: row[column.field] }); - }, - }); - - // 表格配置项可以用 cellRender: { name: 'CellLink' }, - vxeUI.renderer.add('CellLink', { - renderTableDefault(renderOpts) { - const { props } = renderOpts; - return h( - NButton, - { size: 'small', type: 'primary', quaternary: true }, - { default: () => props?.text }, - ); - }, - }); - - // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 - // vxeUI.formats.add - }, - useVbenForm, -}); - -export { useVbenVxeGrid }; - -export type * from '@vben/plugins/vxe-table'; diff --git a/apps/web-naive/src/api/core/auth.ts b/apps/web-naive/src/api/core/auth.ts deleted file mode 100644 index 71d9f994396..00000000000 --- a/apps/web-naive/src/api/core/auth.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { baseRequestClient, requestClient } from '#/api/request'; - -export namespace AuthApi { - /** 登录接口参数 */ - export interface LoginParams { - password?: string; - username?: string; - } - - /** 登录接口返回值 */ - export interface LoginResult { - accessToken: string; - } - - export interface RefreshTokenResult { - data: string; - status: number; - } -} - -/** - * 登录 - */ -export async function loginApi(data: AuthApi.LoginParams) { - return requestClient.post('/auth/login', data); -} - -/** - * 刷新accessToken - */ -export async function refreshTokenApi() { - return baseRequestClient.post('/auth/refresh', { - withCredentials: true, - }); -} - -/** - * 退出登录 - */ -export async function logoutApi() { - return baseRequestClient.post('/auth/logout', { - withCredentials: true, - }); -} - -/** - * 获取用户权限码 - */ -export async function getAccessCodesApi() { - return requestClient.get('/auth/codes'); -} diff --git a/apps/web-naive/src/api/core/index.ts b/apps/web-naive/src/api/core/index.ts deleted file mode 100644 index 28a5aef47ef..00000000000 --- a/apps/web-naive/src/api/core/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './auth'; -export * from './menu'; -export * from './user'; diff --git a/apps/web-naive/src/api/core/menu.ts b/apps/web-naive/src/api/core/menu.ts deleted file mode 100644 index 9ef60b11cd2..00000000000 --- a/apps/web-naive/src/api/core/menu.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { RouteRecordStringComponent } from '@vben/types'; - -import { requestClient } from '#/api/request'; - -/** - * 获取用户所有菜单 - */ -export async function getAllMenusApi() { - return requestClient.get('/menu/all'); -} diff --git a/apps/web-naive/src/api/core/user.ts b/apps/web-naive/src/api/core/user.ts deleted file mode 100644 index 7e28ea8489a..00000000000 --- a/apps/web-naive/src/api/core/user.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { UserInfo } from '@vben/types'; - -import { requestClient } from '#/api/request'; - -/** - * 获取用户信息 - */ -export async function getUserInfoApi() { - return requestClient.get('/user/info'); -} diff --git a/apps/web-naive/src/api/index.ts b/apps/web-naive/src/api/index.ts deleted file mode 100644 index 4b0e0413762..00000000000 --- a/apps/web-naive/src/api/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './core'; diff --git a/apps/web-naive/src/api/request.ts b/apps/web-naive/src/api/request.ts deleted file mode 100644 index f8fbacc0bc5..00000000000 --- a/apps/web-naive/src/api/request.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * 该文件可自行根据业务逻辑进行调整 - */ -import type { RequestClientOptions } from '@vben/request'; - -import { useAppConfig } from '@vben/hooks'; -import { preferences } from '@vben/preferences'; -import { - authenticateResponseInterceptor, - defaultResponseInterceptor, - errorMessageResponseInterceptor, - RequestClient, -} from '@vben/request'; -import { useAccessStore } from '@vben/stores'; - -import { message } from '#/adapter/naive'; -import { useAuthStore } from '#/store'; - -import { refreshTokenApi } from './core'; - -const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); - -function createRequestClient(baseURL: string, options?: RequestClientOptions) { - const client = new RequestClient({ - ...options, - baseURL, - }); - - /** - * 重新认证逻辑 - */ - async function doReAuthenticate() { - console.warn('Access token or refresh token is invalid or expired. '); - const accessStore = useAccessStore(); - const authStore = useAuthStore(); - accessStore.setAccessToken(null); - if ( - preferences.app.loginExpiredMode === 'modal' && - accessStore.isAccessChecked - ) { - accessStore.setLoginExpired(true); - } else { - await authStore.logout(); - } - } - - /** - * 刷新token逻辑 - */ - async function doRefreshToken() { - const accessStore = useAccessStore(); - const resp = await refreshTokenApi(); - const newToken = resp.data; - accessStore.setAccessToken(newToken); - return newToken; - } - - function formatToken(token: null | string) { - return token ? `Bearer ${token}` : null; - } - - // 请求头处理 - client.addRequestInterceptor({ - fulfilled: async (config) => { - const accessStore = useAccessStore(); - - config.headers.Authorization = formatToken(accessStore.accessToken); - config.headers['Accept-Language'] = preferences.app.locale; - return config; - }, - }); - - // 处理返回的响应数据格式 - client.addResponseInterceptor( - defaultResponseInterceptor({ - codeField: 'code', - dataField: 'data', - successCode: 0, - }), - ); - - // token过期的处理 - client.addResponseInterceptor( - authenticateResponseInterceptor({ - client, - doReAuthenticate, - doRefreshToken, - enableRefreshToken: preferences.app.enableRefreshToken, - formatToken, - }), - ); - - // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里 - client.addResponseInterceptor( - errorMessageResponseInterceptor((msg: string, error) => { - // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg - // 当前mock接口返回的错误字段是 error 或者 message - const responseData = error?.response?.data ?? {}; - const errorMessage = responseData?.error ?? responseData?.message ?? ''; - // 如果没有错误信息,则会根据状态码进行提示 - message.error(errorMessage || msg); - }), - ); - - return client; -} - -export const requestClient = createRequestClient(apiURL, { - responseReturn: 'data', -}); - -export const baseRequestClient = new RequestClient({ baseURL: apiURL }); diff --git a/apps/web-naive/src/app.vue b/apps/web-naive/src/app.vue deleted file mode 100644 index 23983c55c98..00000000000 --- a/apps/web-naive/src/app.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - diff --git a/apps/web-naive/src/bootstrap.ts b/apps/web-naive/src/bootstrap.ts deleted file mode 100644 index df0b2cbb815..00000000000 --- a/apps/web-naive/src/bootstrap.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { createApp, watchEffect } from 'vue'; - -import { registerAccessDirective } from '@vben/access'; -import { registerLoadingDirective } from '@vben/common-ui'; -import { preferences } from '@vben/preferences'; -import { initStores } from '@vben/stores'; -import '@vben/styles'; -import '@vben/styles/naive'; - -import { useTitle } from '@vueuse/core'; - -import { $t, setupI18n } from '#/locales'; - -import { initComponentAdapter } from './adapter/component'; -import { initSetupVbenForm } from './adapter/form'; -import App from './app.vue'; -import { router } from './router'; - -async function bootstrap(namespace: string) { - // 初始化组件适配器 - await initComponentAdapter(); - - // 初始化表单组件 - await initSetupVbenForm(); - - // // 设置弹窗的默认配置 - // setDefaultModalProps({ - // fullscreenButton: false, - // }); - // // 设置抽屉的默认配置 - // setDefaultDrawerProps({ - // // zIndex: 2000, - // }); - - const app = createApp(App); - - // 注册v-loading指令 - registerLoadingDirective(app, { - loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 - spinning: 'spinning', - }); - - // 国际化 i18n 配置 - await setupI18n(app); - - // 配置 pinia-tore - await initStores(app, { namespace }); - - // 安装权限指令 - registerAccessDirective(app); - - // 初始化 tippy - const { initTippy } = await import('@vben/common-ui/es/tippy'); - initTippy(app); - - // 配置路由及路由守卫 - app.use(router); - - // 配置Motion插件 - const { MotionPlugin } = await import('@vben/plugins/motion'); - app.use(MotionPlugin); - - // 动态更新标题 - watchEffect(() => { - if (preferences.app.dynamicTitle) { - const routeTitle = router.currentRoute.value.meta?.title; - const pageTitle = - (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; - useTitle(pageTitle); - } - }); - - app.mount('#app'); -} - -export { bootstrap }; diff --git a/apps/web-naive/src/layouts/auth.vue b/apps/web-naive/src/layouts/auth.vue deleted file mode 100644 index 18d415bc7fb..00000000000 --- a/apps/web-naive/src/layouts/auth.vue +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/apps/web-naive/src/layouts/basic.vue b/apps/web-naive/src/layouts/basic.vue deleted file mode 100644 index 0e9747befa6..00000000000 --- a/apps/web-naive/src/layouts/basic.vue +++ /dev/null @@ -1,163 +0,0 @@ - - - diff --git a/apps/web-naive/src/layouts/index.ts b/apps/web-naive/src/layouts/index.ts deleted file mode 100644 index a4320780540..00000000000 --- a/apps/web-naive/src/layouts/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -const BasicLayout = () => import('./basic.vue'); -const AuthPageLayout = () => import('./auth.vue'); - -const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView); - -export { AuthPageLayout, BasicLayout, IFrameView }; diff --git a/apps/web-naive/src/locales/README.md b/apps/web-naive/src/locales/README.md deleted file mode 100644 index 7b451032e6c..00000000000 --- a/apps/web-naive/src/locales/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# locale - -每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。 diff --git a/apps/web-naive/src/locales/index.ts b/apps/web-naive/src/locales/index.ts deleted file mode 100644 index 58f63c10d35..00000000000 --- a/apps/web-naive/src/locales/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { App } from 'vue'; - -import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales'; - -import { - $t, - setupI18n as coreSetup, - loadLocalesMapFromDir, -} from '@vben/locales'; -import { preferences } from '@vben/preferences'; - -const modules = import.meta.glob('./langs/**/*.json'); - -const localesMap = loadLocalesMapFromDir( - /\.\/langs\/([^/]+)\/(.*)\.json$/, - modules, -); - -/** - * 加载应用特有的语言包 - * 这里也可以改造为从服务端获取翻译数据 - * @param lang - */ -async function loadMessages(lang: SupportedLanguagesType) { - const appLocaleMessages = await localesMap[lang]?.(); - return appLocaleMessages?.default; -} - -async function setupI18n(app: App, options: LocaleSetupOptions = {}) { - await coreSetup(app, { - defaultLocale: preferences.app.locale, - loadMessages, - missingWarn: !import.meta.env.PROD, - ...options, - }); -} - -export { $t, setupI18n }; diff --git a/apps/web-naive/src/locales/langs/en-US/demos.json b/apps/web-naive/src/locales/langs/en-US/demos.json deleted file mode 100644 index 839fc2e68d7..00000000000 --- a/apps/web-naive/src/locales/langs/en-US/demos.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "title": "Demos", - "naive": "Naive UI", - "table": "Table", - "form": "Form", - "vben": { - "title": "Project", - "about": "About", - "document": "Document", - "antdv": "Ant Design Vue Version", - "naive-ui": "Naive UI Version", - "element-plus": "Element Plus Version" - } -} diff --git a/apps/web-naive/src/locales/langs/en-US/page.json b/apps/web-naive/src/locales/langs/en-US/page.json deleted file mode 100644 index 618a258c0a6..00000000000 --- a/apps/web-naive/src/locales/langs/en-US/page.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "auth": { - "login": "Login", - "register": "Register", - "codeLogin": "Code Login", - "qrcodeLogin": "Qr Code Login", - "forgetPassword": "Forget Password" - }, - "dashboard": { - "title": "Dashboard", - "analytics": "Analytics", - "workspace": "Workspace" - } -} diff --git a/apps/web-naive/src/locales/langs/zh-CN/demos.json b/apps/web-naive/src/locales/langs/zh-CN/demos.json deleted file mode 100644 index e0d7e616d2b..00000000000 --- a/apps/web-naive/src/locales/langs/zh-CN/demos.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "title": "演示", - "naive": "Naive UI", - "table": "Table", - "form": "表单", - "vben": { - "title": "项目", - "about": "关于", - "document": "文档", - "antdv": "Ant Design Vue 版本", - "naive-ui": "Naive UI 版本", - "element-plus": "Element Plus 版本" - } -} diff --git a/apps/web-naive/src/locales/langs/zh-CN/page.json b/apps/web-naive/src/locales/langs/zh-CN/page.json deleted file mode 100644 index 4cb67081cbf..00000000000 --- a/apps/web-naive/src/locales/langs/zh-CN/page.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "auth": { - "login": "登录", - "register": "注册", - "codeLogin": "验证码登录", - "qrcodeLogin": "二维码登录", - "forgetPassword": "忘记密码" - }, - "dashboard": { - "title": "概览", - "analytics": "分析页", - "workspace": "工作台" - } -} diff --git a/apps/web-naive/src/main.ts b/apps/web-naive/src/main.ts deleted file mode 100644 index 5d728a02acb..00000000000 --- a/apps/web-naive/src/main.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { initPreferences } from '@vben/preferences'; -import { unmountGlobalLoading } from '@vben/utils'; - -import { overridesPreferences } from './preferences'; - -/** - * 应用初始化完成之后再进行页面加载渲染 - */ -async function initApplication() { - // name用于指定项目唯一标识 - // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据 - const env = import.meta.env.PROD ? 'prod' : 'dev'; - const appVersion = import.meta.env.VITE_APP_VERSION; - const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`; - - // app偏好设置初始化 - await initPreferences({ - namespace, - overrides: overridesPreferences, - }); - - // 启动应用并挂载 - // vue应用主要逻辑及视图 - const { bootstrap } = await import('./bootstrap'); - await bootstrap(namespace); - - // 移除并销毁loading - unmountGlobalLoading(); -} - -initApplication(); diff --git a/apps/web-naive/src/preferences.ts b/apps/web-naive/src/preferences.ts deleted file mode 100644 index b2e9ace43c8..00000000000 --- a/apps/web-naive/src/preferences.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineOverridesPreferences } from '@vben/preferences'; - -/** - * @description 项目配置文件 - * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置 - * !!! 更改配置后请清空缓存,否则可能不生效 - */ -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - name: import.meta.env.VITE_APP_TITLE, - }, -}); diff --git a/apps/web-naive/src/router/access.ts b/apps/web-naive/src/router/access.ts deleted file mode 100644 index 7a80bac0979..00000000000 --- a/apps/web-naive/src/router/access.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { - ComponentRecordType, - GenerateMenuAndRoutesOptions, -} from '@vben/types'; - -import { generateAccessible } from '@vben/access'; -import { preferences } from '@vben/preferences'; - -import { message } from '#/adapter/naive'; -import { getAllMenusApi } from '#/api'; -import { BasicLayout, IFrameView } from '#/layouts'; -import { $t } from '#/locales'; - -const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); - -async function generateAccess(options: GenerateMenuAndRoutesOptions) { - const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); - - const layoutMap: ComponentRecordType = { - BasicLayout, - IFrameView, - }; - - return await generateAccessible(preferences.app.accessMode, { - ...options, - fetchMenuListAsync: async () => { - message.loading(`${$t('common.loadingMenu')}...`, { - duration: 1.5, - }); - return await getAllMenusApi(); - }, - // 可以指定没有权限跳转403页面 - forbiddenComponent, - // 如果 route.meta.menuVisibleWithForbidden = true - layoutMap, - pageMap, - }); -} - -export { generateAccess }; diff --git a/apps/web-naive/src/router/guard.ts b/apps/web-naive/src/router/guard.ts deleted file mode 100644 index 28d1cea7431..00000000000 --- a/apps/web-naive/src/router/guard.ts +++ /dev/null @@ -1,132 +0,0 @@ -import type { Router } from 'vue-router'; - -import { LOGIN_PATH } from '@vben/constants'; -import { preferences } from '@vben/preferences'; -import { useAccessStore, useUserStore } from '@vben/stores'; -import { startProgress, stopProgress } from '@vben/utils'; - -import { accessRoutes, coreRouteNames } from '#/router/routes'; -import { useAuthStore } from '#/store'; - -import { generateAccess } from './access'; - -/** - * 通用守卫配置 - * @param router - */ -function setupCommonGuard(router: Router) { - // 记录已经加载的页面 - const loadedPaths = new Set(); - - router.beforeEach((to) => { - to.meta.loaded = loadedPaths.has(to.path); - - // 页面加载进度条 - if (!to.meta.loaded && preferences.transition.progress) { - startProgress(); - } - return true; - }); - - router.afterEach((to) => { - // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 - - loadedPaths.add(to.path); - - // 关闭页面加载进度条 - if (preferences.transition.progress) { - stopProgress(); - } - }); -} - -/** - * 权限访问守卫配置 - * @param router - */ -function setupAccessGuard(router: Router) { - router.beforeEach(async (to, from) => { - const accessStore = useAccessStore(); - const userStore = useUserStore(); - const authStore = useAuthStore(); - - // 基本路由,这些路由不需要进入权限拦截 - if (coreRouteNames.includes(to.name as string)) { - if (to.path === LOGIN_PATH && accessStore.accessToken) { - return decodeURIComponent( - (to.query?.redirect as string) || - userStore.userInfo?.homePath || - preferences.app.defaultHomePath, - ); - } - return true; - } - - // accessToken 检查 - if (!accessStore.accessToken) { - // 明确声明忽略权限访问权限,则可以访问 - if (to.meta.ignoreAccess) { - return true; - } - - // 没有访问权限,跳转登录页面 - if (to.fullPath !== LOGIN_PATH) { - return { - path: LOGIN_PATH, - // 如不需要,直接删除 query - query: - to.fullPath === preferences.app.defaultHomePath - ? {} - : { redirect: encodeURIComponent(to.fullPath) }, - // 携带当前跳转的页面,登录后重新跳转该页面 - replace: true, - }; - } - return to; - } - - // 是否已经生成过动态路由 - if (accessStore.isAccessChecked) { - return true; - } - // 生成路由表 - // 当前登录用户拥有的角色标识列表 - const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); - const userRoles = userInfo.roles ?? []; - - // 生成菜单和路由 - const { accessibleMenus, accessibleRoutes } = await generateAccess({ - roles: userRoles, - router, - // 则会在菜单中显示,但是访问会被重定向到403 - routes: accessRoutes, - }); - - // 保存菜单信息和路由信息 - accessStore.setAccessMenus(accessibleMenus); - accessStore.setAccessRoutes(accessibleRoutes); - accessStore.setIsAccessChecked(true); - const redirectPath = (from.query.redirect ?? - (to.path === preferences.app.defaultHomePath - ? userInfo.homePath || preferences.app.defaultHomePath - : to.fullPath)) as string; - - return { - ...router.resolve(decodeURIComponent(redirectPath)), - replace: true, - }; - }); -} - -/** - * 项目守卫配置 - * @param router - */ -function createRouterGuard(router: Router) { - /** 通用 */ - setupCommonGuard(router); - /** 权限访问 */ - setupAccessGuard(router); -} - -export { createRouterGuard }; diff --git a/apps/web-naive/src/router/index.ts b/apps/web-naive/src/router/index.ts deleted file mode 100644 index 48402303425..00000000000 --- a/apps/web-naive/src/router/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - createRouter, - createWebHashHistory, - createWebHistory, -} from 'vue-router'; - -import { resetStaticRoutes } from '@vben/utils'; - -import { createRouterGuard } from './guard'; -import { routes } from './routes'; - -/** - * @zh_CN 创建vue-router实例 - */ -const router = createRouter({ - history: - import.meta.env.VITE_ROUTER_HISTORY === 'hash' - ? createWebHashHistory(import.meta.env.VITE_BASE) - : createWebHistory(import.meta.env.VITE_BASE), - // 应该添加到路由的初始路由列表。 - routes, - scrollBehavior: (to, _from, savedPosition) => { - if (savedPosition) { - return savedPosition; - } - return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 }; - }, - // 是否应该禁止尾部斜杠。 - // strict: true, -}); - -const resetRoutes = () => resetStaticRoutes(router, routes); - -// 创建路由守卫 -createRouterGuard(router); - -export { resetRoutes, router }; diff --git a/apps/web-naive/src/router/routes/core.ts b/apps/web-naive/src/router/routes/core.ts deleted file mode 100644 index 949b0b65acf..00000000000 --- a/apps/web-naive/src/router/routes/core.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { LOGIN_PATH } from '@vben/constants'; -import { preferences } from '@vben/preferences'; - -import { $t } from '#/locales'; - -const BasicLayout = () => import('#/layouts/basic.vue'); -const AuthPageLayout = () => import('#/layouts/auth.vue'); -/** 全局404页面 */ -const fallbackNotFoundRoute: RouteRecordRaw = { - component: () => import('#/views/_core/fallback/not-found.vue'), - meta: { - hideInBreadcrumb: true, - hideInMenu: true, - hideInTab: true, - title: '404', - }, - name: 'FallbackNotFound', - path: '/:path(.*)*', -}; - -/** 基本路由,这些路由是必须存在的 */ -const coreRoutes: RouteRecordRaw[] = [ - /** - * 根路由 - * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。 - * 此路由必须存在,且不应修改 - */ - { - component: BasicLayout, - meta: { - hideInBreadcrumb: true, - title: 'Root', - }, - name: 'Root', - path: '/', - redirect: preferences.app.defaultHomePath, - children: [], - }, - { - component: AuthPageLayout, - meta: { - hideInTab: true, - title: 'Authentication', - }, - name: 'Authentication', - path: '/auth', - redirect: LOGIN_PATH, - children: [ - { - name: 'Login', - path: 'login', - component: () => import('#/views/_core/authentication/login.vue'), - meta: { - title: $t('page.auth.login'), - }, - }, - { - name: 'CodeLogin', - path: 'code-login', - component: () => import('#/views/_core/authentication/code-login.vue'), - meta: { - title: $t('page.auth.codeLogin'), - }, - }, - { - name: 'QrCodeLogin', - path: 'qrcode-login', - component: () => - import('#/views/_core/authentication/qrcode-login.vue'), - meta: { - title: $t('page.auth.qrcodeLogin'), - }, - }, - { - name: 'ForgetPassword', - path: 'forget-password', - component: () => - import('#/views/_core/authentication/forget-password.vue'), - meta: { - title: $t('page.auth.forgetPassword'), - }, - }, - { - name: 'Register', - path: 'register', - component: () => import('#/views/_core/authentication/register.vue'), - meta: { - title: $t('page.auth.register'), - }, - }, - ], - }, -]; - -export { coreRoutes, fallbackNotFoundRoute }; diff --git a/apps/web-naive/src/router/routes/index.ts b/apps/web-naive/src/router/routes/index.ts deleted file mode 100644 index e6fb1440204..00000000000 --- a/apps/web-naive/src/router/routes/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { mergeRouteModules, traverseTreeValues } from '@vben/utils'; - -import { coreRoutes, fallbackNotFoundRoute } from './core'; - -const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { - eager: true, -}); - -// 有需要可以自行打开注释,并创建文件夹 -// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); -// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); - -/** 动态路由 */ -const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); - -/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ -// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); -// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles); -const staticRoutes: RouteRecordRaw[] = []; -const externalRoutes: RouteRecordRaw[] = []; - -/** 路由列表,由基本路由、外部路由和404兜底路由组成 - * 无需走权限验证(会一直显示在菜单中) */ -const routes: RouteRecordRaw[] = [ - ...coreRoutes, - ...externalRoutes, - fallbackNotFoundRoute, -]; - -/** 基本路由列表,这些路由不需要进入权限拦截 */ -const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name); - -/** 有权限校验的路由列表,包含动态路由和静态路由 */ -const accessRoutes = [...dynamicRoutes, ...staticRoutes]; -export { accessRoutes, coreRouteNames, routes }; diff --git a/apps/web-naive/src/router/routes/modules/dashboard.ts b/apps/web-naive/src/router/routes/modules/dashboard.ts deleted file mode 100644 index 5254dc65de4..00000000000 --- a/apps/web-naive/src/router/routes/modules/dashboard.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'lucide:layout-dashboard', - order: -1, - title: $t('page.dashboard.title'), - }, - name: 'Dashboard', - path: '/dashboard', - children: [ - { - name: 'Analytics', - path: '/analytics', - component: () => import('#/views/dashboard/analytics/index.vue'), - meta: { - affixTab: true, - icon: 'lucide:area-chart', - title: $t('page.dashboard.analytics'), - }, - }, - { - name: 'Workspace', - path: '/workspace', - component: () => import('#/views/dashboard/workspace/index.vue'), - meta: { - icon: 'carbon:workspace', - title: $t('page.dashboard.workspace'), - }, - }, - ], - }, -]; - -export default routes; diff --git a/apps/web-naive/src/router/routes/modules/demos.ts b/apps/web-naive/src/router/routes/modules/demos.ts deleted file mode 100644 index 5e49ffa0166..00000000000 --- a/apps/web-naive/src/router/routes/modules/demos.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'ic:baseline-view-in-ar', - keepAlive: true, - order: 1000, - title: $t('demos.title'), - }, - name: 'Demos', - path: '/demos', - children: [ - { - meta: { - title: $t('demos.naive'), - }, - name: 'NaiveDemos', - path: '/demos/naive', - component: () => import('#/views/demos/naive/index.vue'), - }, - { - meta: { - title: $t('demos.table'), - }, - name: 'Table', - path: '/demos/table', - component: () => import('#/views/demos/table/index.vue'), - }, - { - meta: { - title: $t('demos.form'), - }, - name: 'Form', - path: '/demos/form', - component: () => import('#/views/demos/form/basic.vue'), - }, - ], - }, -]; - -export default routes; diff --git a/apps/web-naive/src/router/routes/modules/vben.ts b/apps/web-naive/src/router/routes/modules/vben.ts deleted file mode 100644 index 169de855b93..00000000000 --- a/apps/web-naive/src/router/routes/modules/vben.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { - VBEN_ANT_PREVIEW_URL, - VBEN_DOC_URL, - VBEN_ELE_PREVIEW_URL, - VBEN_GITHUB_URL, - VBEN_LOGO_URL, -} from '@vben/constants'; -import { SvgAntdvLogoIcon } from '@vben/icons'; - -import { IFrameView } from '#/layouts'; -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - badgeType: 'dot', - icon: VBEN_LOGO_URL, - order: 9998, - title: $t('demos.vben.title'), - }, - name: 'VbenProject', - path: '/vben-admin', - children: [ - { - name: 'VbenDocument', - path: '/vben-admin/document', - component: IFrameView, - meta: { - icon: 'lucide:book-open-text', - link: VBEN_DOC_URL, - title: $t('demos.vben.document'), - }, - }, - { - name: 'VbenGithub', - path: '/vben-admin/github', - component: IFrameView, - meta: { - icon: 'mdi:github', - link: VBEN_GITHUB_URL, - title: 'Github', - }, - }, - { - name: 'VbenAntd', - path: '/vben-admin/antd', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: SvgAntdvLogoIcon, - link: VBEN_ANT_PREVIEW_URL, - title: $t('demos.vben.antdv'), - }, - }, - { - name: 'VbenElementPlus', - path: '/vben-admin/ele', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: 'logos:element', - link: VBEN_ELE_PREVIEW_URL, - title: $t('demos.vben.element-plus'), - }, - }, - ], - }, - { - name: 'VbenAbout', - path: '/vben-admin/about', - component: () => import('#/views/_core/about/index.vue'), - meta: { - icon: 'lucide:copyright', - title: $t('demos.vben.about'), - order: 9999, - }, - }, -]; - -export default routes; diff --git a/apps/web-naive/src/store/auth.ts b/apps/web-naive/src/store/auth.ts deleted file mode 100644 index 0ff050b3bc8..00000000000 --- a/apps/web-naive/src/store/auth.ts +++ /dev/null @@ -1,119 +0,0 @@ -import type { Recordable, UserInfo } from '@vben/types'; - -import { ref } from 'vue'; -import { useRouter } from 'vue-router'; - -import { LOGIN_PATH } from '@vben/constants'; -import { preferences } from '@vben/preferences'; -import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; - -import { defineStore } from 'pinia'; - -import { notification } from '#/adapter/naive'; -import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; -import { $t } from '#/locales'; - -export const useAuthStore = defineStore('auth', () => { - const accessStore = useAccessStore(); - const userStore = useUserStore(); - const router = useRouter(); - - const loginLoading = ref(false); - - /** - * 异步处理登录操作 - * Asynchronously handle the login process - * @param params 登录表单数据 - */ - async function authLogin( - params: Recordable, - onSuccess?: () => Promise | void, - ) { - // 异步处理用户登录操作并获取 accessToken - let userInfo: null | UserInfo = null; - try { - loginLoading.value = true; - const { accessToken } = await loginApi(params); - - // 如果成功获取到 accessToken - if (accessToken) { - // 将 accessToken 存储到 accessStore 中 - accessStore.setAccessToken(accessToken); - - // 获取用户信息并存储到 accessStore 中 - const [fetchUserInfoResult, accessCodes] = await Promise.all([ - fetchUserInfo(), - getAccessCodesApi(), - ]); - - userInfo = fetchUserInfoResult; - - userStore.setUserInfo(userInfo); - accessStore.setAccessCodes(accessCodes); - - if (accessStore.loginExpired) { - accessStore.setLoginExpired(false); - } else { - onSuccess - ? await onSuccess?.() - : await router.push( - userInfo.homePath || preferences.app.defaultHomePath, - ); - } - - if (userInfo?.realName) { - notification.success({ - content: $t('authentication.loginSuccess'), - description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, - duration: 3000, - }); - } - } - } finally { - loginLoading.value = false; - } - - return { - userInfo, - }; - } - - async function logout(redirect: boolean = true) { - try { - await logoutApi(); - } catch { - // 不做任何处理 - } - resetAllStores(); - accessStore.setLoginExpired(false); - - // 回登录页带上当前路由地址 - await router.replace({ - path: LOGIN_PATH, - query: redirect - ? { - redirect: encodeURIComponent(router.currentRoute.value.fullPath), - } - : {}, - }); - } - - async function fetchUserInfo() { - let userInfo: null | UserInfo = null; - userInfo = await getUserInfoApi(); - userStore.setUserInfo(userInfo); - return userInfo; - } - - function $reset() { - loginLoading.value = false; - } - - return { - $reset, - authLogin, - fetchUserInfo, - loginLoading, - logout, - }; -}); diff --git a/apps/web-naive/src/store/index.ts b/apps/web-naive/src/store/index.ts deleted file mode 100644 index 269586ee8b8..00000000000 --- a/apps/web-naive/src/store/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './auth'; diff --git a/apps/web-naive/src/views/_core/README.md b/apps/web-naive/src/views/_core/README.md deleted file mode 100644 index 8248afe6c10..00000000000 --- a/apps/web-naive/src/views/_core/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# \_core - -此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。 diff --git a/apps/web-naive/src/views/_core/about/index.vue b/apps/web-naive/src/views/_core/about/index.vue deleted file mode 100644 index 0ee524335c2..00000000000 --- a/apps/web-naive/src/views/_core/about/index.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/_core/authentication/code-login.vue b/apps/web-naive/src/views/_core/authentication/code-login.vue deleted file mode 100644 index acfd1fd7881..00000000000 --- a/apps/web-naive/src/views/_core/authentication/code-login.vue +++ /dev/null @@ -1,69 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/_core/authentication/forget-password.vue b/apps/web-naive/src/views/_core/authentication/forget-password.vue deleted file mode 100644 index fef0d427945..00000000000 --- a/apps/web-naive/src/views/_core/authentication/forget-password.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/_core/authentication/login.vue b/apps/web-naive/src/views/_core/authentication/login.vue deleted file mode 100644 index 099e4c8c026..00000000000 --- a/apps/web-naive/src/views/_core/authentication/login.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/_core/authentication/qrcode-login.vue b/apps/web-naive/src/views/_core/authentication/qrcode-login.vue deleted file mode 100644 index 23f5f2dad58..00000000000 --- a/apps/web-naive/src/views/_core/authentication/qrcode-login.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/_core/authentication/register.vue b/apps/web-naive/src/views/_core/authentication/register.vue deleted file mode 100644 index daf89c447a3..00000000000 --- a/apps/web-naive/src/views/_core/authentication/register.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/_core/fallback/coming-soon.vue b/apps/web-naive/src/views/_core/fallback/coming-soon.vue deleted file mode 100644 index f394930f21a..00000000000 --- a/apps/web-naive/src/views/_core/fallback/coming-soon.vue +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/_core/fallback/forbidden.vue b/apps/web-naive/src/views/_core/fallback/forbidden.vue deleted file mode 100644 index 8ea65fedb52..00000000000 --- a/apps/web-naive/src/views/_core/fallback/forbidden.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/_core/fallback/internal-error.vue b/apps/web-naive/src/views/_core/fallback/internal-error.vue deleted file mode 100644 index 819a47d5ea8..00000000000 --- a/apps/web-naive/src/views/_core/fallback/internal-error.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/_core/fallback/not-found.vue b/apps/web-naive/src/views/_core/fallback/not-found.vue deleted file mode 100644 index 4d178e9cbd4..00000000000 --- a/apps/web-naive/src/views/_core/fallback/not-found.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/_core/fallback/offline.vue b/apps/web-naive/src/views/_core/fallback/offline.vue deleted file mode 100644 index 5de4a88de4e..00000000000 --- a/apps/web-naive/src/views/_core/fallback/offline.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/dashboard/analytics/analytics-trends.vue b/apps/web-naive/src/views/dashboard/analytics/analytics-trends.vue deleted file mode 100644 index f1f0b232a62..00000000000 --- a/apps/web-naive/src/views/dashboard/analytics/analytics-trends.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/dashboard/analytics/analytics-visits-data.vue b/apps/web-naive/src/views/dashboard/analytics/analytics-visits-data.vue deleted file mode 100644 index 190fb41f47d..00000000000 --- a/apps/web-naive/src/views/dashboard/analytics/analytics-visits-data.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/dashboard/analytics/analytics-visits-sales.vue b/apps/web-naive/src/views/dashboard/analytics/analytics-visits-sales.vue deleted file mode 100644 index 02f5091231b..00000000000 --- a/apps/web-naive/src/views/dashboard/analytics/analytics-visits-sales.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/dashboard/analytics/analytics-visits-source.vue b/apps/web-naive/src/views/dashboard/analytics/analytics-visits-source.vue deleted file mode 100644 index 0915c7af7dd..00000000000 --- a/apps/web-naive/src/views/dashboard/analytics/analytics-visits-source.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/dashboard/analytics/analytics-visits.vue b/apps/web-naive/src/views/dashboard/analytics/analytics-visits.vue deleted file mode 100644 index 7e0f10133d8..00000000000 --- a/apps/web-naive/src/views/dashboard/analytics/analytics-visits.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/dashboard/analytics/index.vue b/apps/web-naive/src/views/dashboard/analytics/index.vue deleted file mode 100644 index 5e3d6d285ae..00000000000 --- a/apps/web-naive/src/views/dashboard/analytics/index.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/dashboard/workspace/index.vue b/apps/web-naive/src/views/dashboard/workspace/index.vue deleted file mode 100644 index b95d6138166..00000000000 --- a/apps/web-naive/src/views/dashboard/workspace/index.vue +++ /dev/null @@ -1,266 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/demos/form/basic.vue b/apps/web-naive/src/views/demos/form/basic.vue deleted file mode 100644 index 60702a11ef8..00000000000 --- a/apps/web-naive/src/views/demos/form/basic.vue +++ /dev/null @@ -1,169 +0,0 @@ - - diff --git a/apps/web-naive/src/views/demos/form/modal.vue b/apps/web-naive/src/views/demos/form/modal.vue deleted file mode 100644 index 52e23542d58..00000000000 --- a/apps/web-naive/src/views/demos/form/modal.vue +++ /dev/null @@ -1,71 +0,0 @@ - - diff --git a/apps/web-naive/src/views/demos/naive/index.vue b/apps/web-naive/src/views/demos/naive/index.vue deleted file mode 100644 index f72cdb20f5a..00000000000 --- a/apps/web-naive/src/views/demos/naive/index.vue +++ /dev/null @@ -1,69 +0,0 @@ - - - diff --git a/apps/web-naive/src/views/demos/table/index.vue b/apps/web-naive/src/views/demos/table/index.vue deleted file mode 100644 index ddc958bc37b..00000000000 --- a/apps/web-naive/src/views/demos/table/index.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - - - diff --git a/apps/web-naive/tailwind.config.mjs b/apps/web-naive/tailwind.config.mjs deleted file mode 100644 index f17f556fa9f..00000000000 --- a/apps/web-naive/tailwind.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config'; diff --git a/apps/web-naive/tsconfig.json b/apps/web-naive/tsconfig.json deleted file mode 100644 index 02c287fe642..00000000000 --- a/apps/web-naive/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@vben/tsconfig/web-app.json", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "#/*": ["./src/*"] - } - }, - "references": [{ "path": "./tsconfig.node.json" }], - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] -} diff --git a/apps/web-naive/tsconfig.node.json b/apps/web-naive/tsconfig.node.json deleted file mode 100644 index c2f0d86cc78..00000000000 --- a/apps/web-naive/tsconfig.node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "@vben/tsconfig/node.json", - "compilerOptions": { - "composite": true, - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "noEmit": false - }, - "include": ["vite.config.mts"] -} diff --git a/apps/web-naive/vite.config.mts b/apps/web-naive/vite.config.mts deleted file mode 100644 index b6360f1d4ac..00000000000 --- a/apps/web-naive/vite.config.mts +++ /dev/null @@ -1,20 +0,0 @@ -import { defineConfig } from '@vben/vite-config'; - -export default defineConfig(async () => { - return { - application: {}, - vite: { - server: { - proxy: { - '/api': { - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, ''), - // mock代理目标地址 - target: 'http://localhost:5320/api', - ws: true, - }, - }, - }, - }, - }; -}); diff --git a/cspell.json b/cspell.json deleted file mode 100644 index 89545b43216..00000000000 --- a/cspell.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", - "version": "0.2", - "language": "en,en-US", - "allowCompoundWords": true, - "words": [ - "acmr", - "antd", - "antdv", - "astro", - "brotli", - "clsx", - "defu", - "demi", - "echarts", - "ependencies", - "esno", - "etag", - "execa", - "iconify", - "iconoir", - "intlify", - "lockb", - "lucide", - "minh", - "minw", - "mkdist", - "mockjs", - "naiveui", - "nocheck", - "noopener", - "noreferrer", - "nprogress", - "nuxt", - "pinia", - "prefixs", - "publint", - "qrcode", - "shadcn", - "sonner", - "sortablejs", - "styl", - "taze", - "ui-kit", - "uicons", - "unplugin", - "unref", - "vben", - "vbenjs", - "vite", - "vitejs", - "vitepress", - "vnode", - "vueuse", - "yxxx" - ], - "ignorePaths": [ - "**/node_modules/**", - "**/dist/**", - "**/*-dist/**", - "**/icons/**", - "pnpm-lock.yaml", - "**/*.log", - "**/*.test.ts", - "**/*.spec.ts", - "**/__tests__/**" - ] -} diff --git a/docs/.vitepress/components/demo-preview.vue b/docs/.vitepress/components/demo-preview.vue deleted file mode 100644 index 4c8829f8875..00000000000 --- a/docs/.vitepress/components/demo-preview.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/docs/.vitepress/components/index.ts b/docs/.vitepress/components/index.ts deleted file mode 100644 index 9430871e6d0..00000000000 --- a/docs/.vitepress/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as DemoPreview } from './demo-preview.vue'; diff --git a/docs/.vitepress/components/preview-group.vue b/docs/.vitepress/components/preview-group.vue deleted file mode 100644 index e712157c27f..00000000000 --- a/docs/.vitepress/components/preview-group.vue +++ /dev/null @@ -1,110 +0,0 @@ - - - diff --git a/docs/.vitepress/config/en.mts b/docs/.vitepress/config/en.mts deleted file mode 100644 index a74a7e5fb55..00000000000 --- a/docs/.vitepress/config/en.mts +++ /dev/null @@ -1,231 +0,0 @@ -import type { DefaultTheme } from 'vitepress'; - -import { defineConfig } from 'vitepress'; - -import { version } from '../../../package.json'; - -export const en = defineConfig({ - description: 'Vben Admin & Enterprise level management system framework', - lang: 'en-US', - themeConfig: { - darkModeSwitchLabel: 'Theme', - darkModeSwitchTitle: 'Switch to Dark Mode', - docFooter: { - next: 'Next Page', - prev: 'Previous Page', - }, - editLink: { - pattern: - 'https://github.com/vbenjs/vue-vben-admin/edit/main/docs/src/:path', - text: 'Edit this page on GitHub', - }, - footer: { - copyright: `Copyright © 2020-${new Date().getFullYear()} Vben`, - message: 'Released under the MIT License.', - }, - langMenuLabel: 'Language', - lastUpdated: { - formatOptions: { - dateStyle: 'short', - timeStyle: 'medium', - }, - text: 'Last updated on', - }, - lightModeSwitchTitle: 'Switch to Light Mode', - nav: nav(), - outline: { - label: 'Navigate', - }, - returnToTopLabel: 'Back to top', - sidebar: { - '/en/commercial/': { - base: '/en/commercial/', - items: sidebarCommercial(), - }, - '/en/guide/': { base: '/en/guide/', items: sidebarGuide() }, - }, - }, -}); - -function sidebarGuide(): DefaultTheme.SidebarItem[] { - return [ - { - collapsed: false, - text: 'Introduction', - items: [ - { - link: 'introduction/vben', - text: 'About Vben Admin', - }, - { - link: 'introduction/why', - text: 'Why Choose Us?', - }, - { link: 'introduction/quick-start', text: 'Quick Start' }, - { link: 'introduction/thin', text: 'Lite Version' }, - ], - }, - { - text: 'Basics', - items: [ - { link: 'essentials/concept', text: 'Basic Concepts' }, - { link: 'essentials/development', text: 'Local Development' }, - { link: 'essentials/route', text: 'Routing and Menu' }, - { link: 'essentials/settings', text: 'Configuration' }, - { link: 'essentials/icons', text: 'Icons' }, - { link: 'essentials/styles', text: 'Styles' }, - { link: 'essentials/external-module', text: 'External Modules' }, - { link: 'essentials/build', text: 'Build and Deployment' }, - { link: 'essentials/server', text: 'Server Interaction and Data Mock' }, - ], - }, - { - text: 'Advanced', - items: [ - { link: 'in-depth/login', text: 'Login' }, - { link: 'in-depth/theme', text: 'Theme' }, - { link: 'in-depth/access', text: 'Access Control' }, - { link: 'in-depth/locale', text: 'Internationalization' }, - { link: 'in-depth/features', text: 'Common Features' }, - { link: 'in-depth/check-updates', text: 'Check Updates' }, - { link: 'in-depth/loading', text: 'Global Loading' }, - { link: 'in-depth/ui-framework', text: 'UI Framework Switching' }, - ], - }, - { - text: 'Engineering', - items: [ - { link: 'project/standard', text: 'Standards' }, - { link: 'project/cli', text: 'CLI' }, - { link: 'project/dir', text: 'Directory Explanation' }, - { link: 'project/test', text: 'Unit Testing' }, - { link: 'project/tailwindcss', text: 'Tailwind CSS' }, - { link: 'project/changeset', text: 'Changeset' }, - { link: 'project/vite', text: 'Vite Config' }, - ], - }, - { - text: 'Others', - items: [ - { link: 'other/project-update', text: 'Project Update' }, - { link: 'other/remove-code', text: 'Remove Code' }, - { link: 'other/faq', text: 'FAQ' }, - ], - }, - ]; -} - -function sidebarCommercial(): DefaultTheme.SidebarItem[] { - return [ - { - link: 'community', - text: 'Community', - }, - { - link: 'technical-support', - text: 'Technical-support', - }, - { - link: 'customized', - text: 'Customized', - }, - ]; -} - -function nav(): DefaultTheme.NavItem[] { - return [ - { - activeMatch: '^/en/(guide|components)/', - text: 'Doc', - items: [ - { - activeMatch: '^/en/guide/', - link: '/en/guide/introduction/vben', - text: 'Guide', - }, - // { - // activeMatch: '^/en/components/', - // link: '/en/components/introduction', - // text: 'Components', - // }, - { - text: 'Historical Versions', - items: [ - { - link: 'https://doc.vvbin.cn/', - text: '2.x Version Documentation', - }, - ], - }, - ], - }, - { - text: 'Demo', - items: [ - { - text: 'Vben Admin', - items: [ - { - link: 'https://www.vben.pro/', - text: 'Demo Version', - }, - { - link: 'https://ant.vben.pro/', - text: 'Ant Design Vue Version', - }, - { - link: 'https://naive.vben.pro/', - text: 'Naive Version', - }, - { - link: 'https://ele.vben.pro/', - text: 'Element Plus Version', - }, - ], - }, - { - text: 'Others', - items: [ - { - link: 'https://vben.vvbin.cn/', - text: 'Vben Admin 2.x', - }, - ], - }, - ], - }, - { - text: version, - items: [ - { - link: 'https://github.com/vbenjs/vue-vben-admin/releases', - text: 'Changelog', - }, - { - link: 'https://github.com/orgs/vbenjs/projects/5', - text: 'Roadmap', - }, - { - link: 'https://github.com/vbenjs/vue-vben-admin/blob/main/.github/contributing.md', - text: 'Contribution', - }, - ], - }, - { - link: '/commercial/technical-support', - text: '🦄 Tech Support', - }, - { - link: '/sponsor/personal', - text: '✨ Sponsor', - }, - { - link: '/commercial/community', - text: '👨‍👦‍👦 Community', - }, - // { - // link: '/friend-links/', - // text: '🤝 Friend Links', - // }, - ]; -} diff --git a/docs/.vitepress/config/index.mts b/docs/.vitepress/config/index.mts deleted file mode 100644 index 6b8cb81d852..00000000000 --- a/docs/.vitepress/config/index.mts +++ /dev/null @@ -1,25 +0,0 @@ -import { withPwa } from '@vite-pwa/vitepress'; -import { defineConfigWithTheme } from 'vitepress'; - -import { en } from './en.mts'; -import { shared } from './shared.mts'; -import { zh } from './zh.mts'; - -export default withPwa( - defineConfigWithTheme({ - ...shared, - locales: { - en: { - label: 'English', - lang: 'en', - link: '/en/', - ...en, - }, - root: { - label: '简体中文', - lang: 'zh-CN', - ...zh, - }, - }, - }), -); diff --git a/docs/.vitepress/config/plugins/demo-preview.ts b/docs/.vitepress/config/plugins/demo-preview.ts deleted file mode 100644 index 03b1698ca77..00000000000 --- a/docs/.vitepress/config/plugins/demo-preview.ts +++ /dev/null @@ -1,143 +0,0 @@ -import type { MarkdownEnv, MarkdownRenderer } from 'vitepress'; - -import crypto from 'node:crypto'; -import { readdirSync } from 'node:fs'; -import { join } from 'node:path'; - -export const rawPathRegexp = - // eslint-disable-next-line regexp/no-super-linear-backtracking, regexp/strict - /^(.+?(?:\.([\da-z]+))?)(#[\w-]+)?(?: ?{(\d+(?:[,-]\d+)*)? ?(\S+)?})? ?(?:\[(.+)])?$/; - -function rawPathToToken(rawPath: string) { - const [ - filepath = '', - extension = '', - region = '', - lines = '', - lang = '', - rawTitle = '', - ] = (rawPathRegexp.exec(rawPath) || []).slice(1); - - const title = rawTitle || filepath.split('/').pop() || ''; - - return { extension, filepath, lang, lines, region, title }; -} - -export const demoPreviewPlugin = (md: MarkdownRenderer) => { - md.core.ruler.after('inline', 'demo-preview', (state) => { - const insertComponentImport = (importString: string) => { - const index = state.tokens.findIndex( - (i) => i.type === 'html_block' && i.content.match(/\n`; - state.tokens.splice(0, 0, importComponent); - } else { - if (state.tokens[index]) { - const content = state.tokens[index].content; - state.tokens[index].content = content.replace( - '', - `${importString}\n`, - ); - } - } - }; - // Define the regular expression to match the desired pattern - const regex = /]*\sdir="([^"]*)"/g; - // Iterate through the Markdown content and replace the pattern - state.src = state.src.replaceAll(regex, (_match, dir) => { - const componentDir = join(process.cwd(), 'src', dir).replaceAll( - '\\', - '/', - ); - - let childFiles: string[] = []; - let dirExists = true; - - try { - childFiles = - readdirSync(componentDir, { - encoding: 'utf8', - recursive: false, - withFileTypes: false, - }) || []; - } catch { - dirExists = false; - } - - if (!dirExists) { - return ''; - } - - const uniqueWord = generateContentHash(componentDir); - - const ComponentName = `DemoComponent_${uniqueWord}`; - insertComponentImport( - `import ${ComponentName} from '${componentDir}/index.vue'`, - ); - const { path: _path } = state.env as MarkdownEnv; - - const index = state.tokens.findIndex((i) => i.content.match(regex)); - - if (!state.tokens[index]) { - return ''; - } - const firstString = 'index.vue'; - childFiles = childFiles.sort((a, b) => { - if (a === firstString) return -1; - if (b === firstString) return 1; - return a.localeCompare(b, 'en', { sensitivity: 'base' }); - }); - state.tokens[index].content = - `<${ComponentName}/> - `; - - const _dummyToken = new state.Token('', '', 0); - const tokenArray: Array = []; - childFiles.forEach((filename) => { - // const slotName = filename.replace(extname(filename), ''); - - const templateStart = new state.Token('html_inline', '', 0); - templateStart.content = `'; - tokenArray.push(templateEnd); - }); - const endTag = new state.Token('html_inline', '', 0); - endTag.content = ''; - tokenArray.push(endTag); - - state.tokens.splice(index + 1, 0, ...tokenArray); - - // console.log( - // state.md.renderer.render(state.tokens, state?.options ?? [], state.env), - // ); - return ''; - }); - }); -}; - -function generateContentHash(input: string, length: number = 10): string { - // 使用 SHA-256 生成哈希值 - const hash = crypto.createHash('sha256').update(input).digest('hex'); - - // 将哈希值转换为 Base36 编码,并取指定长度的字符作为结果 - return Number.parseInt(hash, 16).toString(36).slice(0, length); -} diff --git a/docs/.vitepress/config/shared.mts b/docs/.vitepress/config/shared.mts deleted file mode 100644 index c48cc6097d7..00000000000 --- a/docs/.vitepress/config/shared.mts +++ /dev/null @@ -1,172 +0,0 @@ -import type { PwaOptions } from '@vite-pwa/vitepress'; -import type { HeadConfig } from 'vitepress'; - -import { resolve } from 'node:path'; - -import { - viteArchiverPlugin, - viteVxeTableImportsPlugin, -} from '@vben/vite-config'; - -import { - GitChangelog, - GitChangelogMarkdownSection, -} from '@nolebase/vitepress-plugin-git-changelog/vite'; -import tailwind from 'tailwindcss'; -import { defineConfig, postcssIsolateStyles } from 'vitepress'; -import { - groupIconMdPlugin, - groupIconVitePlugin, -} from 'vitepress-plugin-group-icons'; - -import { demoPreviewPlugin } from './plugins/demo-preview'; -import { search as zhSearch } from './zh.mts'; - -export const shared = defineConfig({ - appearance: 'dark', - head: head(), - markdown: { - preConfig(md) { - md.use(demoPreviewPlugin); - md.use(groupIconMdPlugin); - }, - }, - pwa: pwa(), - srcDir: 'src', - themeConfig: { - i18nRouting: true, - logo: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp', - search: { - options: { - locales: { - ...zhSearch, - }, - }, - provider: 'local', - }, - siteTitle: 'Vben Admin', - socialLinks: [ - { icon: 'github', link: 'https://github.com/vbenjs/vue-vben-admin' }, - ], - }, - title: 'Vben Admin', - vite: { - build: { - chunkSizeWarningLimit: Infinity, - minify: 'terser', - }, - css: { - postcss: { - plugins: [ - tailwind(), - postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] }), - ], - }, - preprocessorOptions: { - scss: { - api: 'modern', - }, - }, - }, - json: { - stringify: true, - }, - plugins: [ - GitChangelog({ - mapAuthors: [ - { - mapByNameAliases: ['Vben'], - name: 'vben', - username: 'anncwb', - }, - { - name: 'vince', - username: 'vince292007', - }, - { - name: 'Li Kui', - username: 'likui628', - }, - ], - repoURL: () => 'https://github.com/vbenjs/vue-vben-admin', - }), - GitChangelogMarkdownSection(), - viteArchiverPlugin({ outputDir: '.vitepress' }), - groupIconVitePlugin(), - await viteVxeTableImportsPlugin(), - ], - server: { - fs: { - allow: ['../..'], - }, - host: true, - port: 6173, - }, - - ssr: { - external: ['@vue/repl'], - }, - }, -}); - -function head(): HeadConfig[] { - return [ - ['meta', { content: 'Vbenjs Team', name: 'author' }], - [ - 'meta', - { - content: 'vben, vitejs, vite, shacdn-ui, vue', - name: 'keywords', - }, - ], - ['link', { href: '/favicon.ico', rel: 'icon', type: 'image/svg+xml' }], - [ - 'meta', - { - content: - 'width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no', - name: 'viewport', - }, - ], - ['meta', { content: 'vben admin docs', name: 'keywords' }], - ['link', { href: '/favicon.ico', rel: 'icon' }], - // [ - // 'script', - // { - // src: 'https://cdn.tailwindcss.com/', - // }, - // ], - ]; -} - -function pwa(): PwaOptions { - return { - includeManifestIcons: false, - manifest: { - description: - 'Vben Admin is a modern admin dashboard template based on Vue 3. ', - icons: [ - { - sizes: '192x192', - src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-192.png', - type: 'image/png', - }, - { - sizes: '512x512', - src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-512.png', - type: 'image/png', - }, - ], - id: '/', - name: 'Vben Admin Doc', - short_name: 'vben_admin_doc', - theme_color: '#ffffff', - }, - outDir: resolve(process.cwd(), '.vitepress/dist'), - registerType: 'autoUpdate', - workbox: { - globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'], - maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, - }, - }; -} diff --git a/docs/.vitepress/config/zh.mts b/docs/.vitepress/config/zh.mts deleted file mode 100644 index 2c3753debe0..00000000000 --- a/docs/.vitepress/config/zh.mts +++ /dev/null @@ -1,358 +0,0 @@ -import type { DefaultTheme } from 'vitepress'; - -import { defineConfig } from 'vitepress'; - -import { version } from '../../../package.json'; - -export const zh = defineConfig({ - description: 'Vben Admin & 企业级管理系统框架', - lang: 'zh-Hans', - themeConfig: { - darkModeSwitchLabel: '主题', - darkModeSwitchTitle: '切换到深色模式', - docFooter: { - next: '下一页', - prev: '上一页', - }, - editLink: { - pattern: - 'https://github.com/vbenjs/vue-vben-admin/edit/main/docs/src/:path', - text: '在 GitHub 上编辑此页面', - }, - footer: { - copyright: `Copyright © 2020-${new Date().getFullYear()} Vben`, - message: '基于 MIT 许可发布.', - }, - langMenuLabel: '多语言', - lastUpdated: { - formatOptions: { - dateStyle: 'short', - timeStyle: 'medium', - }, - text: '最后更新于', - }, - lightModeSwitchTitle: '切换到浅色模式', - nav: nav(), - - outline: { - label: '页面导航', - }, - returnToTopLabel: '回到顶部', - - sidebar: { - '/commercial/': { base: '/commercial/', items: sidebarCommercial() }, - '/components/': { base: '/components/', items: sidebarComponents() }, - '/guide/': { base: '/guide/', items: sidebarGuide() }, - }, - sidebarMenuLabel: '菜单', - }, -}); - -function sidebarGuide(): DefaultTheme.SidebarItem[] { - return [ - { - collapsed: false, - text: '简介', - items: [ - { - link: 'introduction/vben', - text: '关于 Vben Admin', - }, - { - link: 'introduction/why', - text: '为什么选择我们?', - }, - { link: 'introduction/quick-start', text: '快速开始' }, - { link: 'introduction/thin', text: '精简版本' }, - { - base: '/', - link: 'components/introduction', - text: '组件文档', - }, - ], - }, - { - text: '基础', - items: [ - { link: 'essentials/concept', text: '基础概念' }, - { link: 'essentials/development', text: '本地开发' }, - { link: 'essentials/route', text: '路由和菜单' }, - { link: 'essentials/settings', text: '配置' }, - { link: 'essentials/icons', text: '图标' }, - { link: 'essentials/styles', text: '样式' }, - { link: 'essentials/external-module', text: '外部模块' }, - { link: 'essentials/build', text: '构建与部署' }, - { link: 'essentials/server', text: '服务端交互与数据Mock' }, - ], - }, - { - text: '深入', - items: [ - { link: 'in-depth/login', text: '登录' }, - // { link: 'in-depth/layout', text: '布局' }, - { link: 'in-depth/theme', text: '主题' }, - { link: 'in-depth/access', text: '权限' }, - { link: 'in-depth/locale', text: '国际化' }, - { link: 'in-depth/features', text: '常用功能' }, - { link: 'in-depth/check-updates', text: '检查更新' }, - { link: 'in-depth/loading', text: '全局loading' }, - { link: 'in-depth/ui-framework', text: '组件库切换' }, - ], - }, - { - text: '工程', - items: [ - { link: 'project/standard', text: '规范' }, - { link: 'project/cli', text: 'CLI' }, - { link: 'project/dir', text: '目录说明' }, - { link: 'project/test', text: '单元测试' }, - { link: 'project/tailwindcss', text: 'Tailwind CSS' }, - { link: 'project/changeset', text: 'Changeset' }, - { link: 'project/vite', text: 'Vite Config' }, - ], - }, - { - text: '其他', - items: [ - { link: 'other/project-update', text: '项目更新' }, - { link: 'other/remove-code', text: '移除代码' }, - { link: 'other/faq', text: '常见问题' }, - ], - }, - ]; -} - -function sidebarCommercial(): DefaultTheme.SidebarItem[] { - return [ - { - link: 'community', - text: '交流群', - }, - { - link: 'technical-support', - text: '技术支持', - }, - { - link: 'customized', - text: '定制开发', - }, - ]; -} - -function sidebarComponents(): DefaultTheme.SidebarItem[] { - return [ - { - text: '组件', - items: [ - { - link: 'introduction', - text: '介绍', - }, - ], - }, - { - collapsed: false, - text: '布局组件', - items: [ - { - link: 'layout-ui/page', - text: 'Page 页面', - }, - ], - }, - { - collapsed: false, - text: '通用组件', - items: [ - { - link: 'common-ui/vben-api-component', - text: 'ApiComponent Api组件包装器', - }, - { - link: 'common-ui/vben-alert', - text: 'Alert 轻量提示框', - }, - { - link: 'common-ui/vben-modal', - text: 'Modal 模态框', - }, - { - link: 'common-ui/vben-drawer', - text: 'Drawer 抽屉', - }, - { - link: 'common-ui/vben-form', - text: 'Form 表单', - }, - { - link: 'common-ui/vben-vxe-table', - text: 'Vxe Table 表格', - }, - { - link: 'common-ui/vben-count-to-animator', - text: 'CountToAnimator 数字动画', - }, - { - link: 'common-ui/vben-ellipsis-text', - text: 'EllipsisText 省略文本', - }, - ], - }, - ]; -} - -function nav(): DefaultTheme.NavItem[] { - return [ - { - activeMatch: '^/(guide|components)/', - text: '文档', - items: [ - { - activeMatch: '^/guide/', - link: '/guide/introduction/vben', - text: '指南', - }, - { - activeMatch: '^/components/', - link: '/components/introduction', - text: '组件', - }, - { - text: '历史版本', - items: [ - { - link: 'https://doc.vvbin.cn/', - text: '2.x版本文档', - }, - ], - }, - ], - }, - { - text: '演示', - items: [ - { - text: 'Vben Admin', - items: [ - { - link: 'https://www.vben.pro/', - text: '演示版本', - }, - { - link: 'https://ant.vben.pro/', - text: 'Ant Design Vue 版本', - }, - { - link: 'https://naive.vben.pro/', - text: 'Naive 版本', - }, - { - link: 'https://ele.vben.pro/', - text: 'Element Plus版本', - }, - ], - }, - { - text: '其他', - items: [ - { - link: 'https://vben.vvbin.cn/', - text: 'Vben Admin 2.x', - }, - ], - }, - ], - }, - { - text: version, - items: [ - { - link: 'https://github.com/vbenjs/vue-vben-admin/releases', - text: '更新日志', - }, - { - link: 'https://github.com/orgs/vbenjs/projects/5', - text: '路线图', - }, - { - link: 'https://github.com/vbenjs/vue-vben-admin/blob/main/.github/contributing.md', - text: '贡献', - }, - ], - }, - { - link: '/commercial/technical-support', - text: '🦄 技术支持', - }, - { - link: '/sponsor/personal', - text: '✨ 赞助', - }, - { - link: '/commercial/community', - text: '👨‍👦‍👦 交流群', - // items: [ - // { - // link: 'https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=22ySzj7pKiw&businessType=9&from=246610&biz=ka&mainSourceId=share&subSourceId=others&jumpsource=shorturl#/pc', - // text: 'QQ频道', - // }, - // { - // link: 'https://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=mjZmlhgVzzUxvdxllB6C1vHpX8O8QRL0&authKey=DBdFbBwERmfaKY95JvRWqLCJIRGJAmKyZbrpzZ41EKDMZ5SR6MfbjOBaaNRN73fr&noverify=0&group_code=4286109', - // text: 'QQ群', - // }, - // { - // link: 'https://discord.gg/VU62jTecad', - // text: 'Discord', - // }, - // ], - }, - // { - // link: '/friend-links/', - // text: '🤝 友情链接', - // }, - ]; -} - -export const search: DefaultTheme.AlgoliaSearchOptions['locales'] = { - root: { - placeholder: '搜索文档', - translations: { - button: { - buttonAriaLabel: '搜索文档', - buttonText: '搜索文档', - }, - modal: { - errorScreen: { - helpText: '你可能需要检查你的网络连接', - titleText: '无法获取结果', - }, - footer: { - closeText: '关闭', - navigateText: '切换', - searchByText: '搜索提供者', - selectText: '选择', - }, - noResultsScreen: { - noResultsText: '无法找到相关结果', - reportMissingResultsLinkText: '点击反馈', - reportMissingResultsText: '你认为该查询应该有结果?', - suggestedQueryText: '你可以尝试查询', - }, - searchBox: { - cancelButtonAriaLabel: '取消', - cancelButtonText: '取消', - resetButtonAriaLabel: '清除查询条件', - resetButtonTitle: '清除查询条件', - }, - startScreen: { - favoriteSearchesTitle: '收藏', - noRecentSearchesText: '没有搜索历史', - recentSearchesTitle: '搜索历史', - removeFavoriteSearchButtonTitle: '从收藏中移除', - removeRecentSearchButtonTitle: '从搜索历史中移除', - saveRecentSearchButtonTitle: '保存至搜索历史', - }, - }, - }, - }, -}; diff --git a/docs/.vitepress/theme/components/site-layout.vue b/docs/.vitepress/theme/components/site-layout.vue deleted file mode 100644 index 96427d05503..00000000000 --- a/docs/.vitepress/theme/components/site-layout.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/vben-contributors.vue b/docs/.vitepress/theme/components/vben-contributors.vue deleted file mode 100644 index 9b887d925ec..00000000000 --- a/docs/.vitepress/theme/components/vben-contributors.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts deleted file mode 100644 index 7d4d3dc2d2a..00000000000 --- a/docs/.vitepress/theme/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -// https://vitepress.dev/guide/custom-theme -import type { EnhanceAppContext, Theme } from 'vitepress'; - -import { NolebaseGitChangelogPlugin } from '@nolebase/vitepress-plugin-git-changelog/client'; -import DefaultTheme from 'vitepress/theme'; - -import { DemoPreview } from '../components'; -import SiteLayout from './components/site-layout.vue'; -import VbenContributors from './components/vben-contributors.vue'; -import { initHmPlugin } from './plugins/hm'; - -import './styles'; - -import 'virtual:group-icons.css'; -import '@nolebase/vitepress-plugin-git-changelog/client/style.css'; - -export default { - async enhanceApp(ctx: EnhanceAppContext) { - const { app } = ctx; - app.component('VbenContributors', VbenContributors); - app.component('DemoPreview', DemoPreview); - app.use(NolebaseGitChangelogPlugin); - - // 百度统计 - initHmPlugin(); - }, - extends: DefaultTheme, - Layout: SiteLayout, -} satisfies Theme; diff --git a/docs/.vitepress/theme/plugins/hm.ts b/docs/.vitepress/theme/plugins/hm.ts deleted file mode 100644 index 5e0a93182a1..00000000000 --- a/docs/.vitepress/theme/plugins/hm.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { inBrowser } from 'vitepress'; - -const SITE_ID = '2e443a834727c065877c01d89921545e'; - -declare global { - interface Window { - _hmt: any; - } -} - -function registerAnalytics() { - window._hmt = window._hmt || []; - const script = document.createElement('script'); - script.innerHTML = `var _hmt = _hmt || []; - (function() { - var hm = document.createElement("script"); - hm.src = "https://hm.baidu.com/hm.js?${SITE_ID}"; - var s = document.getElementsByTagName("script")[0]; - s.parentNode.insertBefore(hm, s); - })()`; - document.querySelector('head')?.append(script); -} - -export function initHmPlugin() { - if (inBrowser && import.meta.env.PROD) { - registerAnalytics(); - } -} diff --git a/docs/.vitepress/theme/styles/base.css b/docs/.vitepress/theme/styles/base.css deleted file mode 100644 index 8eb423afae6..00000000000 --- a/docs/.vitepress/theme/styles/base.css +++ /dev/null @@ -1,22 +0,0 @@ -html.dark { - color-scheme: dark; -} - -.dark .VPContent { - /* background-color: #14161a; */ -} - -.form-valid-error p { - margin: 0; -} - -/* 顶部导航栏选中项样式 */ -.VPNavBarMenuLink, -.VPNavBarMenuGroup { - border-bottom: 1px solid transparent; -} - -.VPNavBarMenuLink.active, -.VPNavBarMenuGroup.active { - border-bottom-color: var(--vp-c-brand-1); -} diff --git a/docs/.vitepress/theme/styles/index.ts b/docs/.vitepress/theme/styles/index.ts deleted file mode 100644 index 566e63f8cd0..00000000000 --- a/docs/.vitepress/theme/styles/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import '@vben/styles'; - -import './variables.css'; -import './base.css'; diff --git a/docs/.vitepress/theme/styles/variables.css b/docs/.vitepress/theme/styles/variables.css deleted file mode 100644 index 124bb0c4532..00000000000 --- a/docs/.vitepress/theme/styles/variables.css +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Customize default theme styling by overriding CSS variables: - * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css - */ - -/** - * Colors - * - * Each colors have exact same color scale system with 3 levels of solid - * colors with different brightness, and 1 soft color. - * - * - `XXX-1`: The most solid color used mainly for colored text. It must - * satisfy the contrast ratio against when used on top of `XXX-soft`. - * - * - `XXX-2`: The color used mainly for hover state of the button. - * - * - `XXX-3`: The color for solid background, such as bg color of the button. - * It must satisfy the contrast ratio with pure white (#ffffff) text on - * top of it. - * - * - `XXX-soft`: The color used for subtle background such as custom container - * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors - * on top of it. - * - * The soft color must be semi transparent alpha channel. This is crucial - * because it allows adding multiple "soft" colors on top of each other - * to create a accent, such as when having inline code block inside - * custom containers. - * - * - `default`: The color used purely for subtle indication without any - * special meanings attched to it such as bg color for menu hover state. - * - * - `brand`: Used for primary brand colors, such as link text, button with - * brand theme, etc. - * - * - `tip`: Used to indicate useful information. The default theme uses the - * brand color for this by default. - * - * - `warning`: Used to indicate warning to the users. Used in custom - * container, badges, etc. - * - * - `danger`: Used to show error, or dangerous message to the users. Used - * in custom container, badges, etc. - * -------------------------------------------------------------------------- */ - -:root { - /* --vp-c-indigo-1: #4f69fd; */ - --vp-c-default-1: var(--vp-c-gray-1); - --vp-c-default-2: var(--vp-c-gray-2); - --vp-c-default-3: var(--vp-c-gray-3); - --vp-c-default-soft: var(--vp-c-gray-soft); - --vp-c-brand-1: var(--vp-c-indigo-1); - --vp-c-brand-2: var(--vp-c-indigo-2); - --vp-c-brand-3: var(--vp-c-indigo-3); - --vp-c-brand-soft: var(--vp-c-indigo-soft); - --vp-c-tip-1: var(--vp-c-brand-1); - --vp-c-tip-2: var(--vp-c-brand-2); - --vp-c-tip-3: var(--vp-c-brand-3); - --vp-c-tip-soft: var(--vp-c-brand-soft); - --vp-c-warning-1: var(--vp-c-yellow-1); - --vp-c-warning-2: var(--vp-c-yellow-2); - --vp-c-warning-3: var(--vp-c-yellow-3); - --vp-c-warning-soft: var(--vp-c-yellow-soft); - --vp-c-danger-1: var(--vp-c-red-1); - --vp-c-danger-2: var(--vp-c-red-2); - --vp-c-danger-3: var(--vp-c-red-3); - --vp-c-danger-soft: var(--vp-c-red-soft); - - /** - * Component: Button - * -------------------------------------------------------------------------- */ - - --vp-button-brand-border: transparent; - --vp-button-brand-text: var(--vp-c-white); - --vp-button-brand-bg: var(--vp-c-brand-3); - --vp-button-brand-hover-border: transparent; - --vp-button-brand-hover-text: var(--vp-c-white); - --vp-button-brand-hover-bg: var(--vp-c-brand-2); - --vp-button-brand-active-border: transparent; - --vp-button-brand-active-text: var(--vp-c-white); - --vp-button-brand-active-bg: var(--vp-c-brand-1); - - /** - * Component: Home - * -------------------------------------------------------------------------- */ - - --vp-home-hero-name-color: transparent; - --vp-home-hero-name-background: linear-gradient( - 120deg, - var(--vp-c-indigo-1) 30%, - #18cefe - ); - --vp-home-hero-image-background-image: linear-gradient( - -45deg, - #18cefe 50%, - #c279ed 50% - ); - --vp-home-hero-image-filter: blur(44px); - - /** - * Component: Custom Block - * -------------------------------------------------------------------------- */ - --vp-custom-block-tip-border: transparent; - --vp-custom-block-tip-text: var(--vp-c-text-1); - --vp-custom-block-tip-bg: var(--vp-c-brand-soft); - --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); - - /** - * modal zIndex - */ - --popup-z-index: 1000; -} - -@media (min-width: 640px) { - :root { - --vp-home-hero-image-filter: blur(56px); - } -} - -@media (min-width: 960px) { - :root { - --vp-home-hero-image-filter: blur(68px); - } -} - -/** - * Component: Algolia - * -------------------------------------------------------------------------- */ - -.DocSearch { - --docsearch-primary-color: var(--vp-c-brand-1) !important; -} diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index 864e450aa47..00000000000 --- a/docs/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@vben/docs", - "version": "5.5.9", - "private": true, - "scripts": { - "build": "vitepress build", - "dev": "vitepress dev", - "docs:preview": "vitepress preview" - }, - "imports": { - "#/*": { - "node": "./src/_env/node/*", - "default": "./src/_env/*" - } - }, - "dependencies": { - "@vben-core/shadcn-ui": "workspace:*", - "@vben/common-ui": "workspace:*", - "@vben/locales": "workspace:*", - "@vben/plugins": "workspace:*", - "@vben/styles": "workspace:*", - "ant-design-vue": "catalog:", - "lucide-vue-next": "catalog:", - "medium-zoom": "catalog:", - "radix-vue": "catalog:", - "vitepress-plugin-group-icons": "catalog:" - }, - "devDependencies": { - "@nolebase/vitepress-plugin-git-changelog": "catalog:", - "@vben/vite-config": "workspace:*", - "@vite-pwa/vitepress": "catalog:", - "vitepress": "catalog:", - "vue": "catalog:" - } -} diff --git a/docs/src/_env/adapter/component.ts b/docs/src/_env/adapter/component.ts deleted file mode 100644 index ec3d0196cf8..00000000000 --- a/docs/src/_env/adapter/component.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 - * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, - */ - -import type { Component, SetupContext } from 'vue'; - -import type { BaseFormComponentType } from '@vben/common-ui'; - -import { h } from 'vue'; - -import { globalShareState } from '@vben/common-ui'; -import { $t } from '@vben/locales'; - -import { - AutoComplete, - Button, - Checkbox, - CheckboxGroup, - DatePicker, - Divider, - Input, - InputNumber, - InputPassword, - Mentions, - notification, - Radio, - RadioGroup, - RangePicker, - Rate, - Select, - Space, - Switch, - Textarea, - TimePicker, - TreeSelect, - Upload, -} from 'ant-design-vue'; - -const withDefaultPlaceholder = ( - component: T, - type: 'input' | 'select', -) => { - return (props: any, { attrs, slots }: Omit) => { - const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`); - return h(component, { ...props, ...attrs, placeholder }, slots); - }; -}; - -// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 -export type ComponentType = - | 'AutoComplete' - | 'Checkbox' - | 'CheckboxGroup' - | 'DatePicker' - | 'DefaultButton' - | 'Divider' - | 'Input' - | 'InputNumber' - | 'InputPassword' - | 'Mentions' - | 'PrimaryButton' - | 'Radio' - | 'RadioGroup' - | 'RangePicker' - | 'Rate' - | 'Select' - | 'Space' - | 'Switch' - | 'Textarea' - | 'TimePicker' - | 'TreeSelect' - | 'Upload' - | BaseFormComponentType; - -async function initComponentAdapter() { - const components: Partial> = { - // 如果你的组件体积比较大,可以使用异步加载 - // Button: () => - // import('xxx').then((res) => res.Button), - - AutoComplete, - Checkbox, - CheckboxGroup, - DatePicker, - // 自定义默认按钮 - DefaultButton: (props, { attrs, slots }) => { - return h(Button, { ...props, attrs, type: 'default' }, slots); - }, - Divider, - Input: withDefaultPlaceholder(Input, 'input'), - InputNumber: withDefaultPlaceholder(InputNumber, 'input'), - InputPassword: withDefaultPlaceholder(InputPassword, 'input'), - Mentions: withDefaultPlaceholder(Mentions, 'input'), - // 自定义主要按钮 - PrimaryButton: (props, { attrs, slots }) => { - return h(Button, { ...props, attrs, type: 'primary' }, slots); - }, - Radio, - RadioGroup, - RangePicker, - Rate, - Select: withDefaultPlaceholder(Select, 'select'), - Space, - Switch, - Textarea: withDefaultPlaceholder(Textarea, 'input'), - TimePicker, - TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), - Upload, - }; - - // 将组件注册到全局共享状态中 - globalShareState.setComponents(components); - - // 定义全局共享状态中的消息提示 - globalShareState.defineMessage({ - // 复制成功消息提示 - copyPreferencesSuccess: (title, content) => { - notification.success({ - description: content, - message: title, - placement: 'bottomRight', - }); - }, - }); -} - -export { initComponentAdapter }; diff --git a/docs/src/_env/adapter/form.ts b/docs/src/_env/adapter/form.ts deleted file mode 100644 index d8b51c254d1..00000000000 --- a/docs/src/_env/adapter/form.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { - VbenFormSchema as FormSchema, - VbenFormProps, -} from '@vben/common-ui'; - -import type { ComponentType } from './component'; - -import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; -import { $t } from '@vben/locales'; - -import { initComponentAdapter } from './component'; - -initComponentAdapter(); -setupVbenForm({ - config: { - baseModelPropName: 'value', - // naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效 - emptyStateValue: null, - modelPropNameMap: { - Checkbox: 'checked', - Radio: 'checked', - Switch: 'checked', - Upload: 'fileList', - }, - }, - defineRules: { - required: (value, _params, ctx) => { - if (value === undefined || value === null || value.length === 0) { - return $t('ui.formRules.required', [ctx.label]); - } - return true; - }, - selectRequired: (value, _params, ctx) => { - if (value === undefined || value === null) { - return $t('ui.formRules.selectRequired', [ctx.label]); - } - return true; - }, - }, -}); - -const useVbenForm = useForm; - -export { useVbenForm, z }; - -export type VbenFormSchema = FormSchema; -export type { VbenFormProps }; diff --git a/docs/src/_env/adapter/vxe-table.ts b/docs/src/_env/adapter/vxe-table.ts deleted file mode 100644 index bab7f3d386e..00000000000 --- a/docs/src/_env/adapter/vxe-table.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { h } from 'vue'; - -import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; - -import { Button, Image } from 'ant-design-vue'; - -import { useVbenForm } from './form'; - -if (!import.meta.env.SSR) { - setupVbenVxeTable({ - configVxeTable: (vxeUI) => { - vxeUI.setConfig({ - grid: { - align: 'center', - border: false, - columnConfig: { - resizable: true, - }, - - formConfig: { - // 全局禁用vxe-table的表单配置,使用formOptions - enabled: false, - }, - minHeight: 180, - proxyConfig: { - autoLoad: true, - response: { - result: 'items', - total: 'total', - list: 'items', - }, - showActiveMsg: true, - showResponseMsg: false, - }, - round: true, - showOverflow: true, - size: 'small', - }, - }); - - // 表格配置项可以用 cellRender: { name: 'CellImage' }, - vxeUI.renderer.add('CellImage', { - renderTableDefault(_renderOpts, params) { - const { column, row } = params; - return h(Image, { src: row[column.field] }); - }, - }); - - // 表格配置项可以用 cellRender: { name: 'CellLink' }, - vxeUI.renderer.add('CellLink', { - renderTableDefault(renderOpts) { - const { props } = renderOpts; - return h( - Button, - { size: 'small', type: 'link' }, - { default: () => props?.text }, - ); - }, - }); - - // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 - // vxeUI.formats.add - }, - useVbenForm, - }); -} - -export { useVbenVxeGrid }; - -export type * from '@vben/plugins/vxe-table'; diff --git a/docs/src/_env/node/adapter/form.ts b/docs/src/_env/node/adapter/form.ts deleted file mode 100644 index a206c0d8b67..00000000000 --- a/docs/src/_env/node/adapter/form.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const useVbenForm = () => {}; -export const z = {}; -export type VbenFormSchema = any; -export type VbenFormProps = any; diff --git a/docs/src/_env/node/adapter/vxe-table.ts b/docs/src/_env/node/adapter/vxe-table.ts deleted file mode 100644 index 5ec409fb2ba..00000000000 --- a/docs/src/_env/node/adapter/vxe-table.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type * from '@vben/plugins/vxe-table'; - -export const useVbenVxeGrid = () => {}; diff --git a/docs/src/commercial/community.md b/docs/src/commercial/community.md deleted file mode 100644 index 60d9f2543d9..00000000000 --- a/docs/src/commercial/community.md +++ /dev/null @@ -1,30 +0,0 @@ -# 社区交流 - -社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群: - -- [QQ频道](https://pd.qq.com/s/16p8lvvob):推荐!!!主要提供问题解答,分享经验等。 -- QQ群:[大群](https://qm.qq.com/q/MEmHoCLbG0),[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E),[4群](https://qm.qq.com/q/sCzSlm3504),[5群](https://qm.qq.com/q/ya9XrtbS6s),主要的使用者交流群。 -- [Discord](https://discord.com/invite/VU62jTecad): 主要提供问题解答,分享经验等。 - -::: tip - -免费QQ群人数上限200,将会不定期清理。推荐加入QQ频道进行交流 - -::: - -## 微信群 - -作者主要通过微信群提供帮助,如果你有问题,可以通过以下方式加入微信群。 - -通过微信联系作者,注明加群来意: - -::: tip - -因为微信群人数有限制,加微信群要求: - -- 通过[赞助](../sponsor/personal.md)任意金额。 -- 发送赞助`截图`,备注`加入微信群`即可。 - -::: - - diff --git a/docs/src/commercial/customized.md b/docs/src/commercial/customized.md deleted file mode 100644 index 1f0bcecaf47..00000000000 --- a/docs/src/commercial/customized.md +++ /dev/null @@ -1,12 +0,0 @@ -# 定制开发 - -我们提供基于 Vben Admin 的技术支持服务及定制开发,基本需求我们都可以满足。 - -详细需求可添加作者了解,并注明来意: - -- 通过邮箱联系开发者: [ann.vben@gmail.com](mailto:ann.vben@gmail.com) -- 通过微信联系开发者: - - - -我们会在第一时间回复您,定制费用根据需求而定。 diff --git a/docs/src/commercial/technical-support.md b/docs/src/commercial/technical-support.md deleted file mode 100644 index ded9bf2dbdd..00000000000 --- a/docs/src/commercial/technical-support.md +++ /dev/null @@ -1,8 +0,0 @@ -# 技术支持 - -## 问题反馈 - -在使用项目的过程中,如果遇到问题,你可以先详细阅读本文档,未找到解决方案时,可以通过以下方式获取技术支持: - -- 通过 [GitHub Issues](https://github.com/vbenjs/vue-vben-admin/issues) -- 通过 [GitHub Discussions](https://github.com/vbenjs/vue-vben-admin/discussions) diff --git a/docs/src/components/common-ui/vben-alert.md b/docs/src/components/common-ui/vben-alert.md deleted file mode 100644 index 6541b665d34..00000000000 --- a/docs/src/components/common-ui/vben-alert.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -outline: deep ---- - -# Vben Alert 轻量提示框 - -框架提供的一些用于轻量提示的弹窗,仅使用js代码即可快速动态创建提示而不需要在template写任何代码。 - -::: info 应用场景 - -Alert提供的功能与Modal类似,但只适用于简单应用场景。例如临时性、动态地弹出模态确认框、输入框等。如果对弹窗有更复杂的需求,请使用VbenModal - -::: - -::: tip 注意 - -Alert提供的快捷方法alert、confirm、prompt动态创建的弹窗在已打开的情况下,不支持HMR(热更新),代码变更后需要关闭这些弹窗后重新打开。 - -::: - -::: tip README - -下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。 - -::: - -## 基础用法 - -使用 `alert` 创建只有一个确认按钮的提示框。 - - - -使用 `confirm` 创建有确认和取消按钮的提示框。 - - - -使用 `prompt` 创建有确认和取消按钮、接受用户输入的提示框。 - - - -## useAlertContext - -当弹窗的content、footer、icon使用自定义组件时,在这些组件中可以使用 `useAlertContext` 获取当前弹窗的上下文对象,用来主动控制弹窗。 - -::: tip 注意 - -`useAlertContext`只能用在setup或者函数式组件中。 - -::: - -### Methods - -| 方法 | 描述 | 类型 | 版本要求 | -| --------- | ------------------ | -------- | -------- | -| doConfirm | 调用弹窗的确认操作 | ()=>void | >5.5.4 | -| doCancel | 调用弹窗的取消操作 | ()=>void | >5.5.4 | - -## 类型说明 - -```ts -/** 预置的图标类型 */ -export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning'; - -export type BeforeCloseScope = { - /** 是否为点击确认按钮触发的关闭 */ - isConfirm: boolean; -}; - -/** - * alert 属性 - */ -export type AlertProps = { - /** 关闭前的回调,如果返回false,则终止关闭 */ - beforeClose?: ( - scope: BeforeCloseScope, - ) => boolean | Promise | undefined; - /** 边框 */ - bordered?: boolean; - /** 按钮对齐方式 */ - buttonAlign?: 'center' | 'end' | 'start'; - /** 取消按钮的标题 */ - cancelText?: string; - /** 是否居中显示 */ - centered?: boolean; - /** 确认按钮的标题 */ - confirmText?: string; - /** 弹窗容器的额外样式 */ - containerClass?: string; - /** 弹窗提示内容 */ - content: Component | string; - /** 弹窗内容的额外样式 */ - contentClass?: string; - /** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/ - contentMasking?: boolean; - /** 弹窗底部内容(与按钮在同一个容器中) */ - footer?: Component | string; - /** 弹窗的图标(在标题的前面) */ - icon?: Component | IconType; - /** - * 弹窗遮罩模糊效果 - */ - overlayBlur?: number; - /** 是否显示取消按钮 */ - showCancel?: boolean; - /** 弹窗标题 */ - title?: string; -}; - -/** prompt 属性 */ -export type PromptProps = { - /** 关闭前的回调,如果返回false,则终止关闭 */ - beforeClose?: (scope: { - isConfirm: boolean; - value: T | undefined; - }) => boolean | Promise | undefined; - /** 用于接受用户输入的组件 */ - component?: Component; - /** 输入组件的属性 */ - componentProps?: Recordable; - /** 输入组件的插槽 */ - componentSlots?: Recordable; - /** 默认值 */ - defaultValue?: T; - /** 输入组件的值属性名 */ - modelPropName?: string; -} & Omit; - -/** - * 函数签名 - * alert和confirm的函数签名相同。 - * confirm默认会显示取消按钮,而alert默认只有一个按钮 - * */ -export function alert(options: AlertProps): Promise; -export function alert( - message: string, - options?: Partial, -): Promise; -export function alert( - message: string, - title?: string, - options?: Partial, -): Promise; - -/** - * 弹出输入框的函数签名。 - * beforeClose的参数会传入用户当前输入的值 - * component指定接受用户输入的组件,默认为Input - * componentProps 为输入组件设置的属性数据 - * defaultValue 默认的值 - * modelPropName 输入组件的值属性名称。默认为modelValue - */ -export async function prompt( - options: Omit & { - beforeClose?: ( - scope: BeforeCloseScope & { - /** 输入组件的当前值 */ - value: T; - }, - ) => boolean | Promise | undefined; - component?: Component; - componentProps?: Recordable; - defaultValue?: T; - modelPropName?: string; - }, -): Promise; -``` diff --git a/docs/src/components/common-ui/vben-api-component.md b/docs/src/components/common-ui/vben-api-component.md deleted file mode 100644 index 2c84e56b507..00000000000 --- a/docs/src/components/common-ui/vben-api-component.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -outline: deep ---- - -# Vben ApiComponent Api组件包装器 - -框架提供的API“包装器”,它一般不独立使用,主要用于包装其它组件,为目标组件提供自动获取远程数据的能力,但仍然保持了目标组件的原始用法。 - -::: info 写在前面 - -我们在各个应用的组件适配器中,使用ApiComponent包装了Select、TreeSelect组件,使得这些组件可以自动获取远程数据并生成选项。其它类似的组件(比如Cascader)如有需要也可以参考示例代码自行进行包装。 - -::: - -## 基础用法 - -通过 `component` 传入其它组件的定义,并配置相关的其它属性(主要是一些名称映射)。包装组件将通过`api`获取数据(`beforerFetch`、`afterFetch`将分别在`api`运行前、运行后被调用),使用`resultField`从中提取数组,使用`valueField`、`labelField`等来从数据中提取value和label(如果提供了`childrenField`,会将其作为树形结构递归处理每一级数据),之后将处理好的数据通过`optionsPropName`指定的属性传递给目标组件。 - -::: details 包装级联选择器,点击下拉时开始加载远程数据 - -```vue - - -``` - -::: - -## 并发和缓存 - -有些场景下可能需要使用多个ApiComponent,它们使用了相同的远程数据源(例如用在可编辑的表格中)。如果直接将请求后端接口的函数传递给api属性,则每一个实例都会访问一次接口,这会造成资源浪费,是完全没有必要的。Tanstack Query提供了并发控制、缓存、重试等诸多特性,我们可以将接口请求函数用useQuery包装一下再传递给ApiComponent,这样的话无论页面有多少个使用相同数据源的ApiComponent实例,都只会发起一次远程请求。演示效果请参考 [Playground vue-query](https://www.vben.pro/#/demos/features/vue-query),具体代码请查看项目文件[concurrency-caching](https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/views/demos/features/vue-query/concurrency-caching.vue) - -## API - -### Props - -| 属性名 | 描述 | 类型 | 默认值 | 版本要求 | -| --- | --- | --- | --- | --- | -| modelValue(v-model) | 当前值 | `any` | - | - | -| component | 欲包装的组件(以下称为目标组件) | `Component` | - | - | -| numberToString | 是否将value从数字转为string | `boolean` | `false` | - | -| api | 获取数据的函数 | `(arg?: any) => Promise>` | - | - | -| params | 传递给api的参数 | `Record` | - | - | -| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - | - | -| labelField | label字段名 | `string` | `label` | - | -| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` | - | -| valueField | value字段名 | `string` | `value` | - | -| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` | - | -| modelPropName | 目标组件的双向绑定属性名,默认为modelValue。部分组件可能为value | `string` | `modelValue` | - | -| immediate | 是否立即调用api | `boolean` | `true` | - | -| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` | - | -| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction` | - | - | -| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction` | - | - | -| options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - | - | -| visibleEvent | 触发重新请求数据的事件名 | `string` | - | - | -| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - | - | -| autoSelect | 自动设置选项 | `'first' \| 'last' \| 'one'\| ((item: OptionsItem[]) => OptionsItem) \| false` | `false` | >5.5.4 | - -#### autoSelect 自动设置选项 - -如果当前值为undefined,在选项数据成功加载之后,自动从备选项中选择一个作为当前值。默认值为`false`,即不自动选择选项。注意:该属性不应用于多选组件。可选值有: - -- `"first"`:自动选择第一个选项 -- `"last"`:自动选择最后一个选项 -- `"one"`:有且仅有一个选项时,自动选择它 -- `自定义函数`:自定义选择逻辑,函数的参数为options,返回值为选择的选项 -- `false`:不自动选择选项 - -### Methods - -| 方法 | 描述 | 类型 | 版本要求 | -| --- | --- | --- | --- | -| getComponentRef | 获取被包装的组件的实例 | ()=>T | >5.5.4 | -| updateParam | 设置接口请求参数(将与params属性合并) | (newParams: Record)=>void | >5.5.4 | -| getOptions | 获取已加载的选项数据 | ()=>OptionsItem[] | >5.5.4 | -| getValue | 获取当前值 | ()=>any | >5.5.4 | diff --git a/docs/src/components/common-ui/vben-count-to-animator.md b/docs/src/components/common-ui/vben-count-to-animator.md deleted file mode 100644 index 5f3ec188168..00000000000 --- a/docs/src/components/common-ui/vben-count-to-animator.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -outline: deep ---- - -# Vben CountToAnimator 数字动画 - -框架提供的数字动画组件,支持数字动画效果。 - -> 如果文档内没有参数说明,可以尝试在在线示例内寻找 - -::: info 写在前面 - -如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。 - -::: - -## 基础用法 - -通过 `start-val` 和 `end-val`设置数字动画的开始值和结束值, 持续时间`3000`ms。 - - - -## 自定义前缀及分隔符 - -通过 `prefix` 和 `separator` 设置数字动画的前缀和分隔符。 - - - -### Props - -| 属性名 | 描述 | 类型 | 默认值 | -| ---------- | -------------- | --------- | -------- | -| startVal | 起始值 | `number` | `0` | -| endVal | 结束值 | `number` | `2021` | -| duration | 动画持续时间 | `number` | `1500` | -| autoplay | 自动执行 | `boolean` | `true` | -| prefix | 前缀 | `string` | - | -| suffix | 后缀 | `string` | - | -| separator | 分隔符 | `string` | `,` | -| color | 字体颜色 | `string` | - | -| useEasing | 是否开启动画 | `boolean` | `true` | -| transition | 动画效果 | `string` | `linear` | -| decimals | 保留小数点位数 | `number` | `0` | - -### Events - -| 事件名 | 描述 | 类型 | -| -------------- | -------------- | -------------- | -| started | 动画已开始 | `()=>void` | -| finished | 动画已结束 | `()=>void` | -| ~~onStarted~~ | ~~动画已开始~~ | ~~`()=>void`~~ | -| ~~onFinished~~ | ~~动画已结束~~ | ~~`()=>void`~~ | - -### Methods - -| 方法名 | 描述 | 类型 | -| ------ | ------------ | ---------- | -| start | 开始执行动画 | `()=>void` | -| reset | 重置 | `()=>void` | diff --git a/docs/src/components/common-ui/vben-drawer.md b/docs/src/components/common-ui/vben-drawer.md deleted file mode 100644 index 3a28cce7925..00000000000 --- a/docs/src/components/common-ui/vben-drawer.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -outline: deep ---- - -# Vben Drawer 抽屉 - -框架提供的抽屉组件,支持`自动高度`、`loading`等功能。 - -> 如果文档内没有参数说明,可以尝试在在线示例内寻找 - -::: info 写在前面 - -如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。 - -::: - -::: tip README - -下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。 - -::: - -## 基础用法 - -使用 `useVbenDrawer` 创建最基础的抽屉。 - - - -## 组件抽离 - -Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 drawer 内的内容抽离出来,也方便复用。通过 `connectedComponent` 参数,可以将内外组件进行连接,而不用其他任何操作。 - - - -## 自动计算高度 - -弹窗会自动计算内容高度,超过一定高度会出现滚动条,同时结合 `loading` 效果以及使用 `prepend-footer` 插槽。 - - - -## 使用 Api - -通过 `drawerApi` 可以调用 drawer 的方法以及使用 `setState` 更新 drawer 的状态。 - - - -## 数据共享 - -如果你使用了 `connectedComponent` 参数,那么内外组件会共享数据,比如一些表单回填等操作。可以用 `drawerApi` 来获取数据和设置数据,配合 `onOpenChange`,可以满足大部分的需求。 - - - -::: info 注意 - -- `VbenDrawer` 组件对于参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 -- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。 -- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。 -- 如果抽屉的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultDrawerProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。 - -::: - -## API - -```ts -// Drawer 为弹窗组件 -// drawerApi 为弹窗的方法 -const [Drawer, drawerApi] = useVbenDrawer({ - // 属性 - // 事件 -}); -``` - -### Props - -所有属性都可以传入 `useVbenDrawer` 的第一个参数中。 - -| 属性名 | 描述 | 类型 | 默认值 | -| --- | --- | --- | --- | -| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` | -| connectedComponent | 连接另一个Drawer组件 | `Component` | - | -| destroyOnClose | 关闭时销毁 | `boolean` | `false` | -| title | 标题 | `string\|slot` | - | -| titleTooltip | 标题提示信息 | `string\|slot` | - | -| description | 描述信息 | `string\|slot` | - | -| isOpen | 弹窗打开状态 | `boolean` | `false` | -| loading | 弹窗加载状态 | `boolean` | `false` | -| closable | 显示关闭按钮 | `boolean` | `true` | -| closeIconPlacement | 关闭按钮位置 | `'left'\|'right'` | `right` | -| modal | 显示遮罩 | `boolean` | `true` | -| header | 显示header | `boolean` | `true` | -| footer | 显示footer | `boolean\|slot` | `true` | -| confirmLoading | 确认按钮loading状态 | `boolean` | `false` | -| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` | -| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` | -| confirmText | 确认按钮文本 | `string\|slot` | `确认` | -| cancelText | 取消按钮文本 | `string\|slot` | `取消` | -| placement | 抽屉弹出位置 | `'left'\|'right'\|'top'\|'bottom'` | `right` | -| showCancelButton | 显示取消按钮 | `boolean` | `true` | -| showConfirmButton | 显示确认按钮 | `boolean` | `true` | -| class | modal的class,宽度通过这个配置 | `string` | - | -| contentClass | modal内容区域的class | `string` | - | -| footerClass | modal底部区域的class | `string` | - | -| headerClass | modal顶部区域的class | `string` | - | -| zIndex | 抽屉的ZIndex层级 | `number` | `1000` | -| overlayBlur | 遮罩模糊度 | `number` | - | - -::: info appendToMain - -`appendToMain`可以指定将抽屉挂载到内容区域,打开抽屉时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,抽屉会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便抽屉能够正确计算高度。 - -::: - -### Event - -以下事件,只有在 `useVbenDrawer({onCancel:()=>{}})` 中传入才会生效。 - -| 事件名 | 描述 | 类型 | 版本限制 | -| --- | --- | --- | --- | -| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` | --- | -| onCancel | 点击取消按钮触发 | `()=>void` | --- | -| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.5.2 | -| onConfirm | 点击确认按钮触发 | `()=>void` | --- | -| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` | --- | -| onOpened | 打开动画播放完毕时触发 | `()=>void` | >5.5.2 | - -### Slots - -除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。 - -| 插槽名 | 描述 | -| -------------- | -------------------------------------------------- | -| default | 默认插槽 - 弹窗内容 | -| prepend-footer | 取消按钮左侧 | -| center-footer | 取消按钮和确认按钮中间(不使用 footer 插槽时有效) | -| append-footer | 确认按钮右侧 | -| close-icon | 关闭按钮图标 | -| extra | 额外内容(标题右侧) | - -### drawerApi - -| 方法 | 描述 | 类型 | 版本限制 | -| --- | --- | --- | --- | -| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial)\| Partial)=>drawerApi` | -| open | 打开弹窗 | `()=>void` | --- | -| close | 关闭弹窗 | `()=>void` | --- | -| setData | 设置共享数据 | `(data:T)=>drawerApi` | --- | -| getData | 获取共享数据 | `()=>T` | --- | -| useStore | 获取可响应式状态 | - | --- | -| lock | 将抽屉标记为提交中,锁定当前状态 | `(isLock:boolean)=>drawerApi` | >5.5.3 | -| unlock | lock方法的反操作,解除抽屉的锁定状态,也是lock(false)的别名 | `()=>drawerApi` | >5.5.3 | - -::: info lock - -`lock`方法用于锁定抽屉的状态,一般用于提交数据的过程中防止用户重复提交或者抽屉被意外关闭、表单数据被改变等等。当处于锁定状态时,抽屉的确认按钮会变为loading状态,同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭抽屉、开启抽屉的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的抽屉时,会自动解锁。要主动解除这种状态,可以调用`unlock`方法或者再次调用lock方法并传入false参数。 - -::: diff --git a/docs/src/components/common-ui/vben-ellipsis-text.md b/docs/src/components/common-ui/vben-ellipsis-text.md deleted file mode 100644 index ce6c0334ac5..00000000000 --- a/docs/src/components/common-ui/vben-ellipsis-text.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -outline: deep ---- - -# Vben EllipsisText 省略文本 - -框架提供的文本展示组件,可配置超长省略、tooltip提示、展开收起等功能。 - -> 如果文档内没有参数说明,可以尝试在在线示例内寻找 - -## 基础用法 - -通过默认插槽设置文本内容,`maxWidth`属性设置最大宽度。 - - - -## 可折叠的文本块 - -通过`line`设置折叠后的行数,`expand`属性设置是否支持展开收起。 - - - -## 自定义提示浮层 - -通过名为`tooltip`的插槽定制提示信息。 - - - -## 自动显示 tooltip - -通过`tooltip-when-ellipsis`设置,仅在文本长度超出导致省略号出现时才触发 tooltip。 - - - -## API - -### Props - -| 属性名 | 描述 | 类型 | 默认值 | -| --- | --- | --- | --- | -| expand | 支持点击展开或收起 | `boolean` | `false` | -| line | 文本最大行数 | `number` | `1` | -| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` | -| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` | -| tooltip | 启用文本提示 | `boolean` | `true` | -| tooltipWhenEllipsis | 内容超出,自动启用文本提示 | `boolean` | `false` | -| ellipsisThreshold | 设置 tooltipWhenEllipsis 后才生效,文本截断检测的像素差异阈值,越大则判断越严格,如果碰见异常情况可以自己设置阈值 | `number` | `3` | -| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - | -| tooltipColor | 提示文本的颜色 | `string` | - | -| tooltipFontSize | 提示文本的大小 | `string` | - | -| tooltipMaxWidth | 提示浮层的最大宽度。如不设置则保持与文本宽度一致 | `number` | - | -| tooltipOverlayStyle | 提示框内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` | - -### Events - -| 事件名 | 描述 | 类型 | -| ------------ | ------------ | -------------------------- | -| expandChange | 展开状态改变 | `(isExpand:boolean)=>void` | - -### Slots - -| 插槽名 | 描述 | -| ------- | -------------------------------- | -| tooltip | 启用文本提示时,用来定制提示内容 | diff --git a/docs/src/components/common-ui/vben-form.md b/docs/src/components/common-ui/vben-form.md deleted file mode 100644 index 48772bfacc0..00000000000 --- a/docs/src/components/common-ui/vben-form.md +++ /dev/null @@ -1,587 +0,0 @@ ---- -outline: deep ---- - -# Vben Form 表单 - -框架提供的表单组件,可适配 `Element Plus`、`Ant Design Vue`、`Naive UI` 等框架。 - -> 如果文档内没有参数说明,可以尝试在在线示例内寻找 - -::: info 写在前面 - -如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。 - -::: - -## 适配器 - -表单底层使用 [vee-validate](https://vee-validate.logaretm.com/v4/) 进行表单验证,所以你可以使用 `vee-validate` 的所有功能。对于不同的 UI 框架,我们提供了适配器,以便更好的适配不同的 UI 框架。 - -### 适配器说明 - -每个应用都有不同的 UI 框架,所以在应用的 `src/adapter/form` 和 `src/adapter/component` 内部,你可以根据自己的需求,进行组件适配。下面是 `Ant Design Vue` 的适配器示例代码,可根据注释查看说明: - -::: details ant design vue 表单适配器 - -```ts -import type { - VbenFormSchema as FormSchema, - VbenFormProps, -} from '@vben/common-ui'; - -import type { ComponentType } from './component'; - -import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; -import { $t } from '@vben/locales'; - -setupVbenForm({ - config: { - // ant design vue组件库默认都是 v-model:value - baseModelPropName: 'value', - // 一些组件是 v-model:checked 或者 v-model:fileList - modelPropNameMap: { - Checkbox: 'checked', - Radio: 'checked', - Switch: 'checked', - Upload: 'fileList', - }, - }, - defineRules: { - // 输入项目必填国际化适配 - required: (value, _params, ctx) => { - if (value === undefined || value === null || value.length === 0) { - return $t('ui.formRules.required', [ctx.label]); - } - return true; - }, - // 选择项目必填国际化适配 - selectRequired: (value, _params, ctx) => { - if (value === undefined || value === null) { - return $t('ui.formRules.selectRequired', [ctx.label]); - } - return true; - }, - }, -}); - -const useVbenForm = useForm; - -export { useVbenForm, z }; -export type VbenFormSchema = FormSchema; -export type { VbenFormProps }; -``` - -::: - -::: details ant design vue 组件适配器 - -```ts -/** - * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 - * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, - */ - -import type { BaseFormComponentType } from '@vben/common-ui'; - -import type { Component, SetupContext } from 'vue'; -import { h } from 'vue'; - -import { globalShareState, IconPicker } from '@vben/common-ui'; -import { $t } from '@vben/locales'; - -const AutoComplete = defineAsyncComponent( - () => import('ant-design-vue/es/auto-complete'), -); -const Button = defineAsyncComponent(() => import('ant-design-vue/es/button')); -const Checkbox = defineAsyncComponent( - () => import('ant-design-vue/es/checkbox'), -); -const CheckboxGroup = defineAsyncComponent(() => - import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup), -); -const DatePicker = defineAsyncComponent( - () => import('ant-design-vue/es/date-picker'), -); -const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider')); -const Input = defineAsyncComponent(() => import('ant-design-vue/es/input')); -const InputNumber = defineAsyncComponent( - () => import('ant-design-vue/es/input-number'), -); -const InputPassword = defineAsyncComponent(() => - import('ant-design-vue/es/input').then((res) => res.InputPassword), -); -const Mentions = defineAsyncComponent( - () => import('ant-design-vue/es/mentions'), -); -const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio')); -const RadioGroup = defineAsyncComponent(() => - import('ant-design-vue/es/radio').then((res) => res.RadioGroup), -); -const RangePicker = defineAsyncComponent(() => - import('ant-design-vue/es/date-picker').then((res) => res.RangePicker), -); -const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate')); -const Select = defineAsyncComponent(() => import('ant-design-vue/es/select')); -const Space = defineAsyncComponent(() => import('ant-design-vue/es/space')); -const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch')); -const Textarea = defineAsyncComponent(() => - import('ant-design-vue/es/input').then((res) => res.Textarea), -); -const TimePicker = defineAsyncComponent( - () => import('ant-design-vue/es/time-picker'), -); -const TreeSelect = defineAsyncComponent( - () => import('ant-design-vue/es/tree-select'), -); -const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload')); - - -const withDefaultPlaceholder = ( - component: T, - type: 'input' | 'select', -) => { - return (props: any, { attrs, slots }: Omit) => { - const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`); - return h(component, { ...props, ...attrs, placeholder }, slots); - }; -}; - -// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 -export type ComponentType = - | 'AutoComplete' - | 'Checkbox' - | 'CheckboxGroup' - | 'DatePicker' - | 'DefaultButton' - | 'Divider' - | 'Input' - | 'InputNumber' - | 'InputPassword' - | 'Mentions' - | 'PrimaryButton' - | 'Radio' - | 'RadioGroup' - | 'RangePicker' - | 'Rate' - | 'Select' - | 'Space' - | 'Switch' - | 'Textarea' - | 'TimePicker' - | 'TreeSelect' - | 'Upload' - | 'IconPicker'; - | BaseFormComponentType; - -async function initComponentAdapter() { - const components: Partial> = { - // 如果你的组件体积比较大,可以使用异步加载 - // Button: () => - // import('xxx').then((res) => res.Button), - - AutoComplete, - Checkbox, - CheckboxGroup, - DatePicker, - // 自定义默认按钮 - DefaultButton: (props, { attrs, slots }) => { - return h(Button, { ...props, attrs, type: 'default' }, slots); - }, - Divider, - IconPicker, - Input: withDefaultPlaceholder(Input, 'input'), - InputNumber: withDefaultPlaceholder(InputNumber, 'input'), - InputPassword: withDefaultPlaceholder(InputPassword, 'input'), - Mentions: withDefaultPlaceholder(Mentions, 'input'), - // 自定义主要按钮 - PrimaryButton: (props, { attrs, slots }) => { - return h(Button, { ...props, attrs, type: 'primary' }, slots); - }, - Radio, - RadioGroup, - RangePicker, - Rate, - Select: withDefaultPlaceholder(Select, 'select'), - Space, - Switch, - Textarea: withDefaultPlaceholder(Textarea, 'input'), - TimePicker, - TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), - Upload, - }; - - // 将组件注册到全局共享状态中 - globalShareState.setComponents(components); - - // 定义全局共享状态中的消息提示 - globalShareState.defineMessage({ - // 复制成功消息提示 - copyPreferencesSuccess: (title, content) => { - notification.success({ - description: content, - message: title, - placement: 'bottomRight', - }); - }, - }); -} - -export { initComponentAdapter }; -``` - -::: - -## 基础用法 - -::: tip README - -下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。 - -::: - -使用 `useVbenForm` 创建最基础的表单。 - - - -## 查询表单 - -查询表单是一种特殊的表单,用于查询数据。查询表单不会触发表单验证,只会触发查询事件。 - - - -## 表单校验 - -表单校验是一个非常重要的功能,可以通过 `rules` 属性进行校验。 - - - -## 表单联动 - -表单联动是一个非常常见的功能,可以通过 `dependencies` 属性进行联动。 - -_注意_ 需要指定 `dependencies` 的 `triggerFields` 属性,设置由谁的改动来触发,以便表单组件能够正确的联动。 - - - -## 自定义组件 - -如果你的业务组件库没有提供某个组件,你可以自行封装一个组件,然后加到表单内部。 - - - -## 操作 - -一些常见的表单操作。 - - - -## API - -`useVbenForm` 返回一个数组,第一个元素是表单组件,第二个元素是表单的方法。 - -```vue - - - -``` - -### FormApi - -useVbenForm 返回的第二个参数,是一个对象,包含了一些表单的方法。 - -| 方法名 | 描述 | 类型 | 版本号 | -| --- | --- | --- | --- | -| submitForm | 提交表单 | `(e:Event)=>Promise>` | - | -| validateAndSubmitForm | 提交并校验表单 | `(e:Event)=>Promise>` | - | -| resetForm | 重置表单 | `()=>Promise` | - | -| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record, filterFields?: boolean, shouldValidate?: boolean) => Promise` | - | -| getValues | 获取表单值 | `(fields:Record,shouldValidate: boolean = false)=>Promise` | - | -| validate | 表单校验 | `()=>Promise` | - | -| validateField | 校验指定字段 | `(fieldName: string)=>Promise>` | - | -| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise` | - | -| resetValidate | 重置表单校验 | `()=>Promise` | - | -| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` | - | -| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise` | - | -| setState | 设置组件状态(props) | `(stateOrFn:\| ((prev: VbenFormProps) => Partial)\| Partial)=>Promise` | - | -| getState | 获取组件状态(props) | `()=>Promise` | - | -| form | 表单对象实例,可以操作表单,见 [useForm](https://vee-validate.logaretm.com/v4/api/use-form/) | - | - | -| getFieldComponentRef | 获取指定字段的组件实例 | `(fieldName: string)=>T` | >5.5.3 | -| getFocusedField | 获取当前已获得焦点的字段 | `()=>string\|undefined` | >5.5.3 | - -## Props - -所有属性都可以传入 `useVbenForm` 的第一个参数中。 - -| 属性名 | 描述 | 类型 | 默认值 | -| --- | --- | --- | --- | -| layout | 表单项布局 | `'horizontal' \| 'vertical'\| 'inline'` | `horizontal` | -| showCollapseButton | 是否显示折叠按钮 | `boolean` | `false` | -| wrapperClass | 表单的布局,基于tailwindcss | `any` | - | -| actionWrapperClass | 表单操作区域class | `any` | - | -| actionLayout | 表单操作按钮位置 | `'newLine' \| 'rowEnd' \| 'inline'` | `rowEnd` | -| actionPosition | 表单操作按钮对齐方式 | `'left' \| 'center' \| 'right'` | `right` | -| handleReset | 表单重置回调 | `(values: Record,) => Promise \| void` | - | -| handleSubmit | 表单提交回调 | `(values: Record,) => Promise \| void` | - | -| handleValuesChange | 表单值变化回调 | `(values: Record, fieldsChanged: string[]) => void` | - | -| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` | -| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - | -| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - | -| showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` | -| collapsed | 是否折叠,在`showCollapseButton`为`true`时生效 | `boolean` | `false` | -| collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` | -| collapsedRows | 折叠时保持的行数 | `number` | `1` | -| fieldMappingTime | 用于将表单内的数组值映射成 2 个字段 | `[string, [string, string],Nullable\|[string,string]\|((any,string)=>any)?][]` | - | -| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - | -| schema | 表单项的每一项配置 | `FormSchema[]` | - | -| submitOnEnter | 按下回车健时提交表单 | `boolean` | false | -| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false | -| compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false | -| scrollToFirstError | 表单验证失败时是否自动滚动到第一个错误字段 | `boolean` | false | - -::: tip handleValuesChange - -`handleValuesChange` 回调函数的第一个参数`values`装载了表单改变后的当前值对象,第二个参数`fieldsChanged`是一个数组,包含了所有被改变的字段名。注意:第二个参数仅在v5.5.4(不含)以上版本可用,并且传递的是已在schema中定义的字段名。如果你使用了字段映射并且需要检查是哪些字段发生了变化的话,请注意该参数并不会包含映射后的字段名。 - -::: - -::: tip fieldMappingTime - -此属性用于将表单内的数组值映射成 2 个字段,它应当传入一个数组,数组的每一项是一个映射规则,规则的第一个成员是一个字符串,表示需要映射的字段名,第二个成员是一个数组,表示映射后的字段名,第三个成员是一个可选的格式掩码,用于格式化日期时间字段;也可以提供一个格式化函数(参数分别为当前值和当前字段名,返回格式化后的值)。如果明确地将格式掩码设为null,则原值映射而不进行格式化(适用于非日期时间字段)。例如:`[['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD']]`,`timeRange`应当是一个至少具有2个成员的数组类型的值。Form会将`timeRange`的值前两个值分别按照格式掩码`YYYY-MM-DD`格式化后映射到`startTime`和`endTime`字段上。每一项的第三个参数是一个可选的格式掩码, - -::: - -### TS 类型说明 - -::: details ActionButtonOptions - -```ts -export interface ActionButtonOptions { - /** 样式 */ - class?: ClassType; - /** 是否禁用 */ - disabled?: boolean; - /** 是否加载中 */ - loading?: boolean; - /** 按钮大小 */ - size?: ButtonVariantSize; - /** 按钮类型 */ - variant?: ButtonVariants; - /** 是否显示 */ - show?: boolean; - /** 按钮文本 */ - content?: string; - /** 任意属性 */ - [key: string]: any; -} -``` - -::: - -::: details FormCommonConfig - -```ts -export interface FormCommonConfig { - /** - * 所有表单项的props - */ - componentProps?: ComponentProps; - /** - * 所有表单项的控件样式 - */ - controlClass?: string; - /** - * 在表单项的Label后显示一个冒号 - */ - colon?: boolean; - /** - * 所有表单项的禁用状态 - * @default false - */ - disabled?: boolean; - /** - * 所有表单项的控件样式 - * @default {} - */ - formFieldProps?: Partial; - /** - * 所有表单项的栅格布局 - * @default "" - */ - formItemClass?: (() => string) | string; - /** - * 隐藏所有表单项label - * @default false - */ - hideLabel?: boolean; - /** - * 是否隐藏必填标记 - * @default false - */ - hideRequiredMark?: boolean; - /** - * 所有表单项的label样式 - * @default "" - */ - labelClass?: string; - /** - * 所有表单项的label宽度 - */ - labelWidth?: number; - /** - * 所有表单项的model属性名。使用自定义组件时可通过此配置指定组件的model属性名。已经在modelPropNameMap中注册的组件不受此配置影响 - * @default "modelValue" - */ - modelPropName?: string; - /** - * 所有表单项的wrapper样式 - */ - wrapperClass?: string; -} -``` - -::: - -::: details FormSchema - -```ts -export interface FormSchema< - T extends BaseFormComponentType = BaseFormComponentType, -> extends FormCommonConfig { - /** 组件 */ - component: Component | T; - /** 组件参数 */ - componentProps?: ComponentProps; - /** 默认值 */ - defaultValue?: any; - /** 依赖 */ - dependencies?: FormItemDependencies; - /** 描述 */ - description?: string; - /** 字段名,也作为自定义插槽的名称 */ - fieldName: string; - /** 帮助信息 */ - help?: CustomRenderType; - /** 是否隐藏表单项 */ - hide?: boolean; - /** 表单的标签(如果是一个string,会用于默认必选规则的消息提示) */ - label?: CustomRenderType; - /** 自定义组件内部渲染 */ - renderComponentContent?: RenderComponentContentType; - /** 字段规则 */ - rules?: FormSchemaRuleType; - /** 后缀 */ - suffix?: CustomRenderType; -} -``` - -::: - -### 表单联动 - -表单联动需要通过 schema 内的 `dependencies` 属性进行联动,允许您添加字段之间的依赖项,以根据其他字段的值控制字段。 - -```ts -dependencies: { - // 触发字段。只有这些字段值变动时,联动才会触发 - triggerFields: ['name'], - // 动态判断当前字段是否需要显示,不显示则直接销毁 - if(values,formApi){}, - // 动态判断当前字段是否需要显示,不显示用css隐藏 - show(values,formApi){}, - // 动态判断当前字段是否需要禁用 - disabled(values,formApi){}, - // 字段变更时,都会触发该函数 - trigger(values,formApi){}, - // 动态rules - rules(values,formApi){}, - // 动态必填 - required(values,formApi){}, - // 动态组件参数 - componentProps(values,formApi){}, -} -``` - -### 表单校验 - -表单校验需要通过 schema 内的 `rules` 属性进行配置。 - -rules的值可以是字符串(预定义的校验规则名称),也可以是一个zod的schema。 - -#### 预定义的校验规则 - -```ts -// 表示字段必填,默认会根据适配器的required进行国际化 -{ - rules: 'required'; -} - -// 表示字段必填,默认会根据适配器的required进行国际化,用于下拉选择之类 -{ - rules: 'selectRequired'; -} -``` - -#### zod - -rules也支持 zod 的 schema,可以进行更复杂的校验,zod 的使用请查看 [zod文档](https://zod.dev/)。 - -```ts -import { z } from '#/adapter/form'; - -// 基础类型 -{ - rules: z.string().min(1, { message: '请输入字符串' }); -} - -// 可选(可以是undefined),并且携带默认值。注意zod的optional不包括空字符串'' -{ - rules: z.string().default('默认值').optional(); -} - -// 可以是空字符串、undefined或者一个邮箱地址(两种不同的用法) -{ - rules: z.union([z.string().email().optional(), z.literal('')]); -} - -{ - rules: z.string().email().or(z.literal('')).optional(); -} - -// 复杂校验 -{ - z.string() - .min(1, { message: '请输入' }) - .refine((value) => value === '123', { - message: '值必须为123', - }); -} -``` - -## Slots - -可以使用以下插槽在表单中插入自定义的内容 - -| 插槽名 | 描述 | -| ------------- | ------------------ | -| reset-before | 重置按钮之前的位置 | -| submit-before | 提交按钮之前的位置 | -| expand-before | 展开按钮之前的位置 | -| expand-after | 展开按钮之后的位置 | - -::: tip 字段插槽 - -除了以上内置插槽之外,`schema`属性中每个字段的`fieldName`都可以作为插槽名称,这些字段插槽的优先级高于`component`定义的组件。也就是说,当提供了与`fieldName`同名的插槽时,这些插槽的内容将会作为这些字段的组件,此时`component`的值将会被忽略。 - -::: diff --git a/docs/src/components/common-ui/vben-modal.md b/docs/src/components/common-ui/vben-modal.md deleted file mode 100644 index fc714e27915..00000000000 --- a/docs/src/components/common-ui/vben-modal.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -outline: deep ---- - -# Vben Modal 模态框 - -框架提供的模态框组件,支持`拖拽`、`全屏`、`自动高度`、`loading`等功能。 - -> 如果文档内没有参数说明,可以尝试在在线示例内寻找 - -::: info 写在前面 - -如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。 - -::: - -::: tip README - -下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。 - -::: - -## 基础用法 - -使用 `useVbenModal` 创建最基础的模态框。 - - - -## 组件抽离 - -Modal 内的内容一般业务中,会比较复杂,所以我们可以将 modal 内的内容抽离出来,也方便复用。通过 `connectedComponent` 参数,可以将内外组件进行连接,而不用其他任何操作。 - - - -## 开启拖拽 - -通过 `draggable` 参数,可开启拖拽功能。 - - - -## 自动计算高度 - -弹窗会自动计算内容高度,超过一定高度会出现滚动条,同时结合 `loading` 效果以及使用 `prepend-footer` 插槽。 - - - -## 使用 Api - -通过 `modalApi` 可以调用 modal 的方法以及使用 `setState` 更新 modal 的状态。 - - - -## 数据共享 - -如果你使用了 `connectedComponent` 参数,那么内外组件会共享数据,比如一些表单回填等操作。可以用 `modalApi` 来获取数据和设置数据,配合 `onOpenChange`,可以满足大部分的需求。 - - - -## 动画类型 - -通过 `animationType` 属性可以控制弹窗的动画效果: - -- `slide`(默认):从顶部向下滑动进入/退出 -- `scale`:缩放淡入/淡出效果 - - - -::: info 注意 - -- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 -- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。另外,如果设置了`destroyOnClose`,内部Modal及其子组件会在被关闭后完全销毁。 -- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。 - -::: - -## API - -```ts -// Modal 为弹窗组件 -// modalApi 为弹窗的方法 -const [Modal, modalApi] = useVbenModal({ - // 属性 - // 事件 -}); -``` - -### Props - -所有属性都可以传入 `useVbenModal` 的第一个参数中。 - -| 属性名 | 描述 | 类型 | 默认值 | -| --- | --- | --- | --- | -| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` | -| connectedComponent | 连接另一个Modal组件 | `Component` | - | -| destroyOnClose | 关闭时销毁 | `boolean` | `false` | -| title | 标题 | `string\|slot` | - | -| titleTooltip | 标题提示信息 | `string\|slot` | - | -| description | 描述信息 | `string\|slot` | - | -| isOpen | 弹窗打开状态 | `boolean` | `false` | -| loading | 弹窗加载状态 | `boolean` | `false` | -| fullscreen | 全屏显示 | `boolean` | `false` | -| fullscreenButton | 显示全屏按钮 | `boolean` | `true` | -| draggable | 可拖拽 | `boolean` | `false` | -| closable | 显示关闭按钮 | `boolean` | `true` | -| centered | 居中显示 | `boolean` | `false` | -| modal | 显示遮罩 | `boolean` | `true` | -| header | 显示header | `boolean` | `true` | -| footer | 显示footer | `boolean\|slot` | `true` | -| confirmDisabled | 禁用确认按钮 | `boolean` | `false` | -| confirmLoading | 确认按钮loading状态 | `boolean` | `false` | -| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` | -| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` | -| confirmText | 确认按钮文本 | `string\|slot` | `确认` | -| cancelText | 取消按钮文本 | `string\|slot` | `取消` | -| showCancelButton | 显示取消按钮 | `boolean` | `true` | -| showConfirmButton | 显示确认按钮 | `boolean` | `true` | -| class | modal的class,宽度通过这个配置 | `string` | - | -| contentClass | modal内容区域的class | `string` | - | -| footerClass | modal底部区域的class | `string` | - | -| headerClass | modal顶部区域的class | `string` | - | -| bordered | 是否显示border | `boolean` | `false` | -| zIndex | 弹窗的ZIndex层级 | `number` | `1000` | -| overlayBlur | 遮罩模糊度 | `number` | - | -| animationType | 动画类型 | `'slide' \| 'scale'` | `'slide'` | -| submitting | 标记为提交中,锁定弹窗当前状态 | `boolean` | `false` | - -::: info appendToMain - -`appendToMain`可以指定将弹窗挂载到内容区域,打开这种弹窗时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,弹窗会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便弹窗能够正确计算高度。 - -::: - -### Event - -以下事件,只有在 `useVbenModal({onCancel:()=>{}})` 中传入才会生效。 - -| 事件名 | 描述 | 类型 | 版本号 | -| --- | --- | --- | --- | -| onBeforeClose | 关闭前触发,返回 `false`或者被`reject`则禁止关闭 | `()=>Promise\|boolean` | >5.5.2支持Promise | -| onCancel | 点击取消按钮触发 | `()=>void` | | -| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.4.3 | -| onConfirm | 点击确认按钮触发 | `()=>void` | | -| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` | | -| onOpened | 打开动画播放完毕时触发 | `()=>void` | >5.4.3 | - -### Slots - -除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。 - -| 插槽名 | 描述 | -| -------------- | -------------------------------------------------- | -| default | 默认插槽 - 弹窗内容 | -| prepend-footer | 取消按钮左侧 | -| center-footer | 取消按钮和确认按钮中间(不使用 footer 插槽时有效) | -| append-footer | 确认按钮右侧 | - -### modalApi - -| 方法 | 描述 | 类型 | 版本 | -| --- | --- | --- | --- | -| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial)\| Partial)=>modalApi` | - | -| open | 打开弹窗 | `()=>void` | - | -| close | 关闭弹窗 | `()=>void` | - | -| setData | 设置共享数据 | `(data:T)=>modalApi` | - | -| getData | 获取共享数据 | `()=>T` | - | -| useStore | 获取可响应式状态 | - | - | -| lock | 将弹窗标记为提交中,锁定当前状态 | `(isLock:boolean)=>modalApi` | >5.5.2 | -| unlock | lock方法的反操作,解除弹窗的锁定状态,也是lock(false)的别名 | `()=>modalApi` | >5.5.3 | - -::: info lock - -`lock`方法用于锁定当前弹窗的状态,一般用于提交数据的过程中防止用户重复提交或者弹窗被意外关闭、表单数据被改变等等。当处于锁定状态时,弹窗的确认按钮会变为loading状态,同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭弹窗、开启弹窗的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的弹窗时,会自动解锁。要主动解除这种状态,可以调用`unlock`方法或者再次调用lock方法并传入false参数。 - -::: diff --git a/docs/src/components/common-ui/vben-vxe-table.md b/docs/src/components/common-ui/vben-vxe-table.md deleted file mode 100644 index 344ecae11e6..00000000000 --- a/docs/src/components/common-ui/vben-vxe-table.md +++ /dev/null @@ -1,276 +0,0 @@ ---- -outline: deep ---- - -# Vben Vxe Table 表格 - -框架提供的Table 列表组件基于 [vxe-table](https://vxetable.cn/v4/#/grid/api?apiKey=grid),结合`Vben Form 表单`进行了二次封装。 - -其中,表头的 **表单搜索** 部分采用了`Vben Form表单`,表格主体部分使用了`vxe-grid`组件,支持表格的分页、排序、筛选等功能。 - -> 如果文档内没有参数说明,可以尝试在在线示例或者在 [vxe-grid 官方API 文档](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 内寻找 - -::: info 写在前面 - -如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。 - -::: - -## 适配器 - -表格底层使用 [vxe-table](https://vxetable.cn/#/start/install) 进行实现,所以你可以使用 `vxe-table` 的所有功能。对于不同的 UI 框架,我们提供了适配器,以便更好的适配不同的 UI 框架。 - -### 适配器说明 - -每个应用都可以自己配置`vxe-table`的适配器,你可以根据自己的需求。下面是一个简单的配置示例: - -::: details vxe-table 表格适配器 - -```ts -import { h } from 'vue'; - -import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; - -import { Button, Image } from 'ant-design-vue'; - -import { useVbenForm } from './form'; - -setupVbenVxeTable({ - configVxeTable: (vxeUI) => { - vxeUI.setConfig({ - grid: { - align: 'center', - border: false, - columnConfig: { - resizable: true, - }, - minHeight: 180, - formConfig: { - // 全局禁用vxe-table的表单配置,使用formOptions - enabled: false, - }, - proxyConfig: { - autoLoad: true, - response: { - result: 'items', - total: 'total', - list: 'items', - }, - showActiveMsg: true, - showResponseMsg: false, - }, - round: true, - showOverflow: true, - size: 'small', - }, - }); - - // 表格配置项可以用 cellRender: { name: 'CellImage' }, - vxeUI.renderer.add('CellImage', { - renderTableDefault(_renderOpts, params) { - const { column, row } = params; - return h(Image, { src: row[column.field] }); - }, - }); - - // 表格配置项可以用 cellRender: { name: 'CellLink' }, - vxeUI.renderer.add('CellLink', { - renderTableDefault(renderOpts) { - const { props } = renderOpts; - return h( - Button, - { size: 'small', type: 'link' }, - { default: () => props?.text }, - ); - }, - }); - - // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 - // vxeUI.formats.add - }, - useVbenForm, -}); - -export { useVbenVxeGrid }; - -export type * from '@vben/plugins/vxe-table'; -``` - -::: - -## 基础表格 - -使用 `useVbenVxeGrid` 创建最基础的表格。 - - - -## 远程加载 - -通过指定 `proxyConfig.ajax` 的 `query` 方法,可以实现远程加载数据。 - - - -## 树形表格 - -树形表格的数据源为扁平结构,可以指定`treeConfig`配置项,实现树形表格。 - -```typescript -treeConfig: { - transform: true, // 指定表格为树形表格 - parentField: 'parentId', // 父节点字段名 - rowField: 'id', // 行数据字段名 -}, -``` - - - -## 固定表头/列 - -列固定可选参数: `'left' | 'right' | '' | null` - - - -## 自定义单元格 - -自定义单元格有两种实现方式 - -- 通过 `slots` 插槽 -- 通过 `customCell` 自定义单元格,但是要先添加渲染器 - -```typescript -// 表格配置项可以用 cellRender: { name: 'CellImage' }, -vxeUI.renderer.add('CellImage', { - renderDefault(_renderOpts, params) { - const { column, row } = params; - return h(Image, { src: row[column.field] } as any); // 注意此处的Image 组件,来源于Antd,需要自行引入,否则会使用js的Image类 - }, -}); - -// 表格配置项可以用 cellRender: { name: 'CellLink' }, -vxeUI.renderer.add('CellLink', { - renderDefault(renderOpts) { - const { props } = renderOpts; - return h( - Button, - { size: 'small', type: 'link' }, - { default: () => props?.text }, - ); - }, -}); -``` - - - -## 搜索表单 - -**表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)。 - -当启用了表单搜索时,可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。表格的所有以`form-`开头的命名插槽都会被传递给搜索表单。 - -### 定制分隔条 - -当你启用表单搜索时,在表单和表格之间会显示一个分隔条。这个分隔条使用了默认的组件背景色,并且横向贯穿整个Vben Vxe Table在视觉上融入了页面的默认背景中。如果你在Vben Vxe Table的外层包裹了一个不同背景色的容器(如将其放在一个Card内),默认的表单和表格之间的分隔条可能就显得格格不入了,下面的代码演示了如何定制这个分隔条。 - -```ts -const [Grid] = useVbenVxeGrid({ - formOptions: {}, - gridOptions: {}, - // 完全移除分隔条 - separator: false, - // 你也可以使用下面的代码来移除分隔条 - // separator: { show: false }, - // 或者使用下面的代码来改变分隔条的颜色 - // separator: { backgroundColor: 'rgba(100,100,0,0.5)' }, -}); -``` - - - -## 单元格编辑 - -通过指定`editConfig.mode`为`cell`,可以实现单元格编辑。 - - - -## 行编辑 - -通过指定`editConfig.mode`为`row`,可以实现行编辑。 - - - -## 虚拟滚动 - -通过 scroll-y.enabled 与 scroll-y.gt 组合开启,其中 enabled 为总开关,gt 是指当总行数大于指定行数时自动开启。 - -> 参考 [vxe-table 官方文档 - 虚拟滚动](https://vxetable.cn/v4/#/component/grid/scroll/vertical)。 - - - -## API - -`useVbenVxeGrid` 返回一个数组,第一个元素是表格组件,第二个元素是表格的方法。 - -```vue - - - -``` - -### GridApi - -useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表单的方法。 - -| 方法名 | 描述 | 类型 | 说明 | -| --- | --- | --- | --- | -| setLoading | 设置loading状态 | `(loading)=>void` | - | -| setGridOptions | 设置vxe-table grid组件参数 | `(options: Partialvoid` | - | -| reload | 重载表格,会进行初始化 | `(params:any)=>void` | - | -| query | 重载表格,会保留当前分页 | `(params:any)=>void` | - | -| grid | vxe-table grid实例 | `VxeGridInstance` | - | -| formApi | vbenForm api实例 | `FormApi` | - | -| toggleSearchForm | 设置搜索表单显示状态 | `(show?: boolean)=>boolean` | 当省略参数时,则将表单在显示和隐藏两种状态之间切换 | - -## Props - -所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。 - -| 属性名 | 描述 | 类型 | 版本要求 | -| --- | --- | --- | --- | -| tableTitle | 表格标题 | `string` | - | -| tableTitleHelp | 表格标题帮助信息 | `string` | - | -| gridClass | grid组件的class | `string` | - | -| gridOptions | grid组件的参数 | `VxeTableGridProps` | - | -| gridEvents | grid组件的触发的事件 | `VxeGridListeners` | - | -| formOptions | 表单参数 | `VbenFormProps` | - | -| showSearchForm | 是否显示搜索表单 | `boolean` | - | -| separator | 搜索表单与表格主体之间的分隔条 | `boolean\|SeparatorOptions` | >5.5.4 | - -## Slots - -大部分插槽的说明请参考 [vxe-table 官方文档](https://vxetable.cn/v4/#/grid/api),但工具栏部分由于做了一些定制封装,需使用以下插槽定制表格的工具栏: - -| 插槽名 | 描述 | -| --------------- | -------------------------------------------- | -| toolbar-actions | 工具栏左侧部分(表格标题附近) | -| toolbar-tools | 工具栏右侧部分(vxeTable原生工具按钮的左侧) | -| table-title | 表格标题插槽 | - -::: info 搜索表单的插槽 - -对于使用了搜索表单的表格来说,所有以`form-`开头的命名插槽都会传递给表单。 - -::: diff --git a/docs/src/components/introduction.md b/docs/src/components/introduction.md deleted file mode 100644 index 438470e9a36..00000000000 --- a/docs/src/components/introduction.md +++ /dev/null @@ -1,15 +0,0 @@ -# 介绍 - -::: info README - -该文档介绍的是框架组件的使用方法、属性、事件等。如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。 - -::: - -## 布局组件 - -布局组件一般在页面内容区域用作顶层容器组件,提供一些统一的布局样式和基本功能。 - -## 通用组件 - -通用组件是一些常用的组件,比如弹窗、抽屉、表单等。大部分基于 `Tailwind CSS` 实现,可适用于不同 UI 组件库的应用。 diff --git a/docs/src/components/layout-ui/page.md b/docs/src/components/layout-ui/page.md deleted file mode 100644 index 29fbdd40f0e..00000000000 --- a/docs/src/components/layout-ui/page.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -outline: deep ---- - -# Page 常规页面组件 - -提供一个常规页面布局的组件,包括头部、内容区域、底部三个部分。 - -::: info 写在前面 - -本组件是一个基本布局组件。如果有更多的通用页面布局需求(比如双列布局等),可以根据实际需求自行封装。 - -::: - -## 基础用法 - -将`Page`作为你的业务页面的根组件即可。 - -### Props - -| 属性名 | 描述 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | --- | -| title | 页面标题 | `string\|slot` | - | - | -| description | 页面描述(标题下的内容) | `string\|slot` | - | - | -| contentClass | 内容区域的class | `string` | - | - | -| headerClass | 头部区域的class | `string` | - | - | -| footerClass | 底部区域的class | `string` | - | - | -| autoContentHeight | 自动调整内容区域的高度 | `boolean` | `false` | - | - -::: tip 注意 - -如果`title`、`description`、`extra`三者均未提供有效内容(通过`props`或者`slots`均可),则页面头部区域不会渲染。 - -::: - -### Slots - -| 插槽名称 | 描述 | -| ----------- | ------------ | -| default | 页面内容 | -| title | 页面标题 | -| description | 页面描述 | -| extra | 页面头部右侧 | -| footer | 页面底部 | diff --git a/docs/src/demos/vben-alert/alert/index.vue b/docs/src/demos/vben-alert/alert/index.vue deleted file mode 100644 index 9ba18a4da85..00000000000 --- a/docs/src/demos/vben-alert/alert/index.vue +++ /dev/null @@ -1,36 +0,0 @@ - - diff --git a/docs/src/demos/vben-alert/confirm/index.vue b/docs/src/demos/vben-alert/confirm/index.vue deleted file mode 100644 index 723445d9665..00000000000 --- a/docs/src/demos/vben-alert/confirm/index.vue +++ /dev/null @@ -1,75 +0,0 @@ - - diff --git a/docs/src/demos/vben-alert/prompt/index.vue b/docs/src/demos/vben-alert/prompt/index.vue deleted file mode 100644 index a192213478b..00000000000 --- a/docs/src/demos/vben-alert/prompt/index.vue +++ /dev/null @@ -1,118 +0,0 @@ - - diff --git a/docs/src/demos/vben-api-component/cascader/index.vue b/docs/src/demos/vben-api-component/cascader/index.vue deleted file mode 100644 index 957964cd3df..00000000000 --- a/docs/src/demos/vben-api-component/cascader/index.vue +++ /dev/null @@ -1,100 +0,0 @@ - - diff --git a/docs/src/demos/vben-count-to-animator/basic/index.vue b/docs/src/demos/vben-count-to-animator/basic/index.vue deleted file mode 100644 index acc76edaa94..00000000000 --- a/docs/src/demos/vben-count-to-animator/basic/index.vue +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/docs/src/demos/vben-count-to-animator/custom/index.vue b/docs/src/demos/vben-count-to-animator/custom/index.vue deleted file mode 100644 index 5a2434058a6..00000000000 --- a/docs/src/demos/vben-count-to-animator/custom/index.vue +++ /dev/null @@ -1,12 +0,0 @@ - - diff --git a/docs/src/demos/vben-drawer/auto-height/drawer.vue b/docs/src/demos/vben-drawer/auto-height/drawer.vue deleted file mode 100644 index 9ab433ccb1e..00000000000 --- a/docs/src/demos/vben-drawer/auto-height/drawer.vue +++ /dev/null @@ -1,45 +0,0 @@ - - diff --git a/docs/src/demos/vben-drawer/auto-height/index.vue b/docs/src/demos/vben-drawer/auto-height/index.vue deleted file mode 100644 index 59294e53892..00000000000 --- a/docs/src/demos/vben-drawer/auto-height/index.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/docs/src/demos/vben-drawer/basic/index.vue b/docs/src/demos/vben-drawer/basic/index.vue deleted file mode 100644 index bd7d9275041..00000000000 --- a/docs/src/demos/vben-drawer/basic/index.vue +++ /dev/null @@ -1,11 +0,0 @@ - - diff --git a/docs/src/demos/vben-drawer/dynamic/drawer.vue b/docs/src/demos/vben-drawer/dynamic/drawer.vue deleted file mode 100644 index 50f628316d2..00000000000 --- a/docs/src/demos/vben-drawer/dynamic/drawer.vue +++ /dev/null @@ -1,26 +0,0 @@ - - diff --git a/docs/src/demos/vben-drawer/dynamic/index.vue b/docs/src/demos/vben-drawer/dynamic/index.vue deleted file mode 100644 index 95464023a41..00000000000 --- a/docs/src/demos/vben-drawer/dynamic/index.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/docs/src/demos/vben-drawer/extra/drawer.vue b/docs/src/demos/vben-drawer/extra/drawer.vue deleted file mode 100644 index e84c1939f6c..00000000000 --- a/docs/src/demos/vben-drawer/extra/drawer.vue +++ /dev/null @@ -1,8 +0,0 @@ - - diff --git a/docs/src/demos/vben-drawer/extra/index.vue b/docs/src/demos/vben-drawer/extra/index.vue deleted file mode 100644 index 59294e53892..00000000000 --- a/docs/src/demos/vben-drawer/extra/index.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/docs/src/demos/vben-drawer/shared-data/drawer.vue b/docs/src/demos/vben-drawer/shared-data/drawer.vue deleted file mode 100644 index 629199b67e5..00000000000 --- a/docs/src/demos/vben-drawer/shared-data/drawer.vue +++ /dev/null @@ -1,26 +0,0 @@ - - diff --git a/docs/src/demos/vben-drawer/shared-data/index.vue b/docs/src/demos/vben-drawer/shared-data/index.vue deleted file mode 100644 index fef6cb05868..00000000000 --- a/docs/src/demos/vben-drawer/shared-data/index.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/docs/src/demos/vben-ellipsis-text/auto-display/index.vue b/docs/src/demos/vben-ellipsis-text/auto-display/index.vue deleted file mode 100644 index fb5a32a50fe..00000000000 --- a/docs/src/demos/vben-ellipsis-text/auto-display/index.vue +++ /dev/null @@ -1,16 +0,0 @@ - - diff --git a/docs/src/demos/vben-ellipsis-text/expand/index.vue b/docs/src/demos/vben-ellipsis-text/expand/index.vue deleted file mode 100644 index 842f6b32e84..00000000000 --- a/docs/src/demos/vben-ellipsis-text/expand/index.vue +++ /dev/null @@ -1,10 +0,0 @@ - - diff --git a/docs/src/demos/vben-ellipsis-text/line/index.vue b/docs/src/demos/vben-ellipsis-text/line/index.vue deleted file mode 100644 index dfbf20effc7..00000000000 --- a/docs/src/demos/vben-ellipsis-text/line/index.vue +++ /dev/null @@ -1,10 +0,0 @@ - - diff --git a/docs/src/demos/vben-ellipsis-text/tooltip/index.vue b/docs/src/demos/vben-ellipsis-text/tooltip/index.vue deleted file mode 100644 index e6287a12f51..00000000000 --- a/docs/src/demos/vben-ellipsis-text/tooltip/index.vue +++ /dev/null @@ -1,14 +0,0 @@ - - diff --git a/docs/src/demos/vben-form/api/index.vue b/docs/src/demos/vben-form/api/index.vue deleted file mode 100644 index 786dc89e53a..00000000000 --- a/docs/src/demos/vben-form/api/index.vue +++ /dev/null @@ -1,236 +0,0 @@ - - - diff --git a/docs/src/demos/vben-form/basic/index.vue b/docs/src/demos/vben-form/basic/index.vue deleted file mode 100644 index 88d0142566b..00000000000 --- a/docs/src/demos/vben-form/basic/index.vue +++ /dev/null @@ -1,231 +0,0 @@ - - - diff --git a/docs/src/demos/vben-form/custom/index.vue b/docs/src/demos/vben-form/custom/index.vue deleted file mode 100644 index e4da38dcc88..00000000000 --- a/docs/src/demos/vben-form/custom/index.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - diff --git a/docs/src/demos/vben-form/dynamic/index.vue b/docs/src/demos/vben-form/dynamic/index.vue deleted file mode 100644 index 83d1a7110c5..00000000000 --- a/docs/src/demos/vben-form/dynamic/index.vue +++ /dev/null @@ -1,168 +0,0 @@ - - - diff --git a/docs/src/demos/vben-form/query/index.vue b/docs/src/demos/vben-form/query/index.vue deleted file mode 100644 index a0b64f5ddfe..00000000000 --- a/docs/src/demos/vben-form/query/index.vue +++ /dev/null @@ -1,94 +0,0 @@ - - - diff --git a/docs/src/demos/vben-form/rules/index.vue b/docs/src/demos/vben-form/rules/index.vue deleted file mode 100644 index 78e5981334b..00000000000 --- a/docs/src/demos/vben-form/rules/index.vue +++ /dev/null @@ -1,190 +0,0 @@ - - - diff --git a/docs/src/demos/vben-modal/animation-type/index.vue b/docs/src/demos/vben-modal/animation-type/index.vue deleted file mode 100644 index 79ba9d33a27..00000000000 --- a/docs/src/demos/vben-modal/animation-type/index.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/docs/src/demos/vben-modal/auto-height/index.vue b/docs/src/demos/vben-modal/auto-height/index.vue deleted file mode 100644 index 2addf2e9050..00000000000 --- a/docs/src/demos/vben-modal/auto-height/index.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/docs/src/demos/vben-modal/auto-height/modal.vue b/docs/src/demos/vben-modal/auto-height/modal.vue deleted file mode 100644 index 8757d5ef0ad..00000000000 --- a/docs/src/demos/vben-modal/auto-height/modal.vue +++ /dev/null @@ -1,45 +0,0 @@ - - diff --git a/docs/src/demos/vben-modal/basic/index.vue b/docs/src/demos/vben-modal/basic/index.vue deleted file mode 100644 index 9f8997085af..00000000000 --- a/docs/src/demos/vben-modal/basic/index.vue +++ /dev/null @@ -1,11 +0,0 @@ - - diff --git a/docs/src/demos/vben-modal/draggable/index.vue b/docs/src/demos/vben-modal/draggable/index.vue deleted file mode 100644 index 2addf2e9050..00000000000 --- a/docs/src/demos/vben-modal/draggable/index.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/docs/src/demos/vben-modal/draggable/modal.vue b/docs/src/demos/vben-modal/draggable/modal.vue deleted file mode 100644 index ecca497b19e..00000000000 --- a/docs/src/demos/vben-modal/draggable/modal.vue +++ /dev/null @@ -1,10 +0,0 @@ - - diff --git a/docs/src/demos/vben-modal/dynamic/index.vue b/docs/src/demos/vben-modal/dynamic/index.vue deleted file mode 100644 index 1b02554592d..00000000000 --- a/docs/src/demos/vben-modal/dynamic/index.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/docs/src/demos/vben-modal/dynamic/modal.vue b/docs/src/demos/vben-modal/dynamic/modal.vue deleted file mode 100644 index d4612896527..00000000000 --- a/docs/src/demos/vben-modal/dynamic/modal.vue +++ /dev/null @@ -1,38 +0,0 @@ - - diff --git a/docs/src/demos/vben-modal/extra/index.vue b/docs/src/demos/vben-modal/extra/index.vue deleted file mode 100644 index 2addf2e9050..00000000000 --- a/docs/src/demos/vben-modal/extra/index.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/docs/src/demos/vben-modal/extra/modal.vue b/docs/src/demos/vben-modal/extra/modal.vue deleted file mode 100644 index 488fd4a0dda..00000000000 --- a/docs/src/demos/vben-modal/extra/modal.vue +++ /dev/null @@ -1,8 +0,0 @@ - - diff --git a/docs/src/demos/vben-modal/shared-data/index.vue b/docs/src/demos/vben-modal/shared-data/index.vue deleted file mode 100644 index 91afeb70d91..00000000000 --- a/docs/src/demos/vben-modal/shared-data/index.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/docs/src/demos/vben-modal/shared-data/modal.vue b/docs/src/demos/vben-modal/shared-data/modal.vue deleted file mode 100644 index 806585d99f8..00000000000 --- a/docs/src/demos/vben-modal/shared-data/modal.vue +++ /dev/null @@ -1,26 +0,0 @@ - - diff --git a/docs/src/demos/vben-vxe-table/basic/index.vue b/docs/src/demos/vben-vxe-table/basic/index.vue deleted file mode 100644 index 4b6b5a632fd..00000000000 --- a/docs/src/demos/vben-vxe-table/basic/index.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - diff --git a/docs/src/demos/vben-vxe-table/custom-cell/index.vue b/docs/src/demos/vben-vxe-table/custom-cell/index.vue deleted file mode 100644 index 517e73f3870..00000000000 --- a/docs/src/demos/vben-vxe-table/custom-cell/index.vue +++ /dev/null @@ -1,105 +0,0 @@ - - - diff --git a/docs/src/demos/vben-vxe-table/edit-cell/index.vue b/docs/src/demos/vben-vxe-table/edit-cell/index.vue deleted file mode 100644 index 711941de313..00000000000 --- a/docs/src/demos/vben-vxe-table/edit-cell/index.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - diff --git a/docs/src/demos/vben-vxe-table/edit-row/index.vue b/docs/src/demos/vben-vxe-table/edit-row/index.vue deleted file mode 100644 index f317f69d160..00000000000 --- a/docs/src/demos/vben-vxe-table/edit-row/index.vue +++ /dev/null @@ -1,92 +0,0 @@ - - - diff --git a/docs/src/demos/vben-vxe-table/fixed/index.vue b/docs/src/demos/vben-vxe-table/fixed/index.vue deleted file mode 100644 index 6067a5ebe7d..00000000000 --- a/docs/src/demos/vben-vxe-table/fixed/index.vue +++ /dev/null @@ -1,67 +0,0 @@ - - - diff --git a/docs/src/demos/vben-vxe-table/form/index.vue b/docs/src/demos/vben-vxe-table/form/index.vue deleted file mode 100644 index bcf3f5a5d19..00000000000 --- a/docs/src/demos/vben-vxe-table/form/index.vue +++ /dev/null @@ -1,127 +0,0 @@ - - - diff --git a/docs/src/demos/vben-vxe-table/mock-api.ts b/docs/src/demos/vben-vxe-table/mock-api.ts deleted file mode 100644 index e5c40b61eec..00000000000 --- a/docs/src/demos/vben-vxe-table/mock-api.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { MOCK_API_DATA } from './table-data'; - -export namespace DemoTableApi { - export interface PageFetchParams { - [key: string]: any; - page: number; - pageSize: number; - } -} - -export function sleep(time = 1000) { - return new Promise((resolve) => { - setTimeout(() => { - resolve(true); - }, time); - }); -} - -/** - * 获取示例表格数据 - */ -async function getExampleTableApi(params: DemoTableApi.PageFetchParams) { - return new Promise<{ items: any; total: number }>((resolve) => { - const { page, pageSize } = params; - const items = MOCK_API_DATA.slice((page - 1) * pageSize, page * pageSize); - - sleep(1000).then(() => { - resolve({ - total: items.length, - items, - }); - }); - }); -} - -export { getExampleTableApi }; diff --git a/docs/src/demos/vben-vxe-table/remote/index.vue b/docs/src/demos/vben-vxe-table/remote/index.vue deleted file mode 100644 index bc93d29a997..00000000000 --- a/docs/src/demos/vben-vxe-table/remote/index.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - diff --git a/docs/src/demos/vben-vxe-table/table-data.ts b/docs/src/demos/vben-vxe-table/table-data.ts deleted file mode 100644 index c37b88ade68..00000000000 --- a/docs/src/demos/vben-vxe-table/table-data.ts +++ /dev/null @@ -1,384 +0,0 @@ -interface TableRowData { - address: string; - age: number; - id: number; - name: string; - nickname: string; - role: string; -} - -const roles = ['User', 'Admin', 'Manager', 'Guest']; - -export const MOCK_TABLE_DATA: TableRowData[] = (() => { - const data: TableRowData[] = []; - for (let i = 0; i < 10; i++) { - data.push({ - address: `New York${i}`, - age: i + 1, - id: i, - name: `Test${i}`, - nickname: `Test${i}`, - role: roles[Math.floor(Math.random() * roles.length)] as string, - }); - } - return data; -})(); - -export const MOCK_TREE_TABLE_DATA = [ - { - date: '2020-08-01', - id: 10_000, - name: 'Test1', - parentId: null, - size: 1024, - type: 'mp3', - }, - { - date: '2021-04-01', - id: 10_050, - name: 'Test2', - parentId: null, - size: 0, - type: 'mp4', - }, - { - date: '2020-03-01', - id: 24_300, - name: 'Test3', - parentId: 10_050, - size: 1024, - type: 'avi', - }, - { - date: '2021-04-01', - id: 20_045, - name: 'Test4', - parentId: 24_300, - size: 600, - type: 'html', - }, - { - date: '2021-04-01', - id: 10_053, - name: 'Test5', - parentId: 24_300, - size: 0, - type: 'avi', - }, - { - date: '2021-10-01', - id: 24_330, - name: 'Test6', - parentId: 10_053, - size: 25, - type: 'txt', - }, - { - date: '2020-01-01', - id: 21_011, - name: 'Test7', - parentId: 10_053, - size: 512, - type: 'pdf', - }, - { - date: '2021-06-01', - id: 22_200, - name: 'Test8', - parentId: 10_053, - size: 1024, - type: 'js', - }, - { - date: '2020-11-01', - id: 23_666, - name: 'Test9', - parentId: null, - size: 2048, - type: 'xlsx', - }, - { - date: '2021-06-01', - id: 23_677, - name: 'Test10', - parentId: 23_666, - size: 1024, - type: 'js', - }, - { - date: '2021-06-01', - id: 23_671, - name: 'Test11', - parentId: 23_677, - size: 1024, - type: 'js', - }, - { - date: '2021-06-01', - id: 23_672, - name: 'Test12', - parentId: 23_677, - size: 1024, - type: 'js', - }, - { - date: '2021-06-01', - id: 23_688, - name: 'Test13', - parentId: 23_666, - size: 1024, - type: 'js', - }, - { - date: '2021-06-01', - id: 23_681, - name: 'Test14', - parentId: 23_688, - size: 1024, - type: 'js', - }, - { - date: '2021-06-01', - id: 23_682, - name: 'Test15', - parentId: 23_688, - size: 1024, - type: 'js', - }, - { - date: '2020-10-01', - id: 24_555, - name: 'Test16', - parentId: null, - size: 224, - type: 'avi', - }, - { - date: '2021-06-01', - id: 24_566, - name: 'Test17', - parentId: 24_555, - size: 1024, - type: 'js', - }, - { - date: '2021-06-01', - id: 24_577, - name: 'Test18', - parentId: 24_555, - size: 1024, - type: 'js', - }, -]; - -export const MOCK_API_DATA = [ - { - available: true, - category: 'Computers', - color: 'purple', - currency: 'NAD', - description: - 'Ergonomic executive chair upholstered in bonded black leather and PVC padded seat and back for all-day comfort and support', - id: '45a613df-227a-4907-a89f-4a7f1252ca0c', - imageUrl: 'https://avatars.githubusercontent.com/u/62715097', - imageUrl2: 'https://avatars.githubusercontent.com/u/75395683', - inProduction: false, - open: true, - price: '48.89', - productName: 'Handcrafted Steel Salad', - quantity: 70, - rating: 3.780_582_329_574_367, - releaseDate: '2024-09-09T04:06:57.793Z', - status: 'error', - tags: ['Bespoke', 'Handmade', 'Luxurious'], - weight: 1.031_015_671_912_002_5, - }, - { - available: true, - category: 'Toys', - color: 'green', - currency: 'CZK', - description: - 'The Nagasaki Lander is the trademarked name of several series of Nagasaki sport bikes, that started with the 1984 ABC800J', - id: 'd02e5ee9-bc98-4de2-98fa-25a6567ecc19', - imageUrl: 'https://avatars.githubusercontent.com/u/51512330', - imageUrl2: 'https://avatars.githubusercontent.com/u/58698113', - inProduction: false, - open: false, - price: '68.15', - productName: 'Generic Cotton Gloves', - quantity: 3, - rating: 1.681_749_367_682_703_3, - releaseDate: '2024-06-16T09:00:36.806Z', - status: 'warning', - tags: ['Rustic', 'Handcrafted', 'Recycled'], - weight: 9.601_076_149_300_575, - }, - { - available: true, - category: 'Beauty', - color: 'teal', - currency: 'OMR', - description: - 'The Apollotech B340 is an affordable wireless mouse with reliable connectivity, 12 months battery life and modern design', - id: '2b72521c-225c-4e64-8030-611b76b10b37', - imageUrl: 'https://avatars.githubusercontent.com/u/50300075', - imageUrl2: 'https://avatars.githubusercontent.com/u/36541691', - inProduction: true, - open: true, - price: '696.94', - productName: 'Gorgeous Soft Ball', - quantity: 50, - rating: 2.361_581_777_372_057_5, - releaseDate: '2024-06-03T13:24:19.809Z', - status: 'warning', - tags: ['Gorgeous', 'Ergonomic', 'Licensed'], - weight: 8.882_340_049_286_19, - }, - { - available: true, - category: 'Games', - color: 'silver', - currency: 'SOS', - description: - 'Carbonite web goalkeeper gloves are ergonomically designed to give easy fit', - id: 'bafab694-3801-452c-b102-9eb519bd1143', - imageUrl: 'https://avatars.githubusercontent.com/u/89827115', - imageUrl2: 'https://avatars.githubusercontent.com/u/55952747', - inProduction: false, - open: false, - price: '553.84', - productName: 'Bespoke Soft Computer', - quantity: 29, - rating: 2.176_412_873_760_271_7, - releaseDate: '2024-09-17T12:16:27.034Z', - status: 'error', - tags: ['Elegant', 'Rustic', 'Recycled'], - weight: 9.653_285_869_978_038, - }, - { - available: true, - category: 'Toys', - color: 'indigo', - currency: 'BIF', - description: - 'Andy shoes are designed to keeping in mind durability as well as trends, the most stylish range of shoes & sandals', - id: 'bf6dea6b-2a55-441d-8773-937e03d99389', - imageUrl: 'https://avatars.githubusercontent.com/u/21431092', - imageUrl2: 'https://avatars.githubusercontent.com/u/3771350', - inProduction: true, - open: true, - price: '237.39', - productName: 'Handcrafted Cotton Mouse', - quantity: 54, - rating: 4.363_265_388_265_461, - releaseDate: '2023-10-23T13:42:34.947Z', - status: 'error', - tags: ['Unbranded', 'Handmade', 'Generic'], - weight: 9.513_203_612_535_571, - }, - { - available: false, - category: 'Tools', - color: 'violet', - currency: 'TZS', - description: - 'New ABC 13 9370, 13.3, 5th Gen CoreA5-8250U, 8GB RAM, 256GB SSD, power UHD Graphics, OS 10 Home, OS Office A & J 2016', - id: '135ba6ab-32ee-4989-8189-5cfa658ef970', - imageUrl: 'https://avatars.githubusercontent.com/u/29946092', - imageUrl2: 'https://avatars.githubusercontent.com/u/23842994', - inProduction: false, - open: false, - price: '825.25', - productName: 'Awesome Bronze Ball', - quantity: 94, - rating: 4.251_159_804_726_753, - releaseDate: '2023-12-30T07:31:43.464Z', - status: 'warning', - tags: ['Handmade', 'Elegant', 'Unbranded'], - weight: 2.247_473_385_732_636_8, - }, - { - available: true, - category: 'Automotive', - color: 'teal', - currency: 'BOB', - description: 'The Football Is Good For Training And Recreational Purposes', - id: '652ef256-7d4e-48b7-976c-7afaa781ea92', - imageUrl: 'https://avatars.githubusercontent.com/u/2531904', - imageUrl2: 'https://avatars.githubusercontent.com/u/15215990', - inProduction: false, - open: false, - price: '780.49', - productName: 'Oriental Rubber Pants', - quantity: 70, - rating: 2.636_323_417_377_916, - releaseDate: '2024-02-23T23:30:49.628Z', - status: 'success', - tags: ['Unbranded', 'Elegant', 'Unbranded'], - weight: 4.812_965_858_018_838, - }, - { - available: false, - category: 'Garden', - color: 'plum', - currency: 'LRD', - description: - 'The slim & simple Maple Gaming Keyboard from Dev Byte comes with a sleek body and 7- Color RGB LED Back-lighting for smart functionality', - id: '3ea24798-6589-40cc-85f0-ab78752244a0', - imageUrl: 'https://avatars.githubusercontent.com/u/23165285', - imageUrl2: 'https://avatars.githubusercontent.com/u/14595665', - inProduction: false, - open: true, - price: '583.85', - productName: 'Handcrafted Concrete Hat', - quantity: 15, - rating: 1.371_600_527_752_802_7, - releaseDate: '2024-03-02T19:40:50.255Z', - status: 'error', - tags: ['Rustic', 'Sleek', 'Ergonomic'], - weight: 4.926_949_366_405_728_4, - }, - { - available: false, - category: 'Industrial', - color: 'salmon', - currency: 'AUD', - description: - 'The Apollotech B340 is an affordable wireless mouse with reliable connectivity, 12 months battery life and modern design', - id: '997113dd-f6e4-4acc-9790-ef554c7498d1', - imageUrl: 'https://avatars.githubusercontent.com/u/49021914', - imageUrl2: 'https://avatars.githubusercontent.com/u/4690621', - inProduction: true, - open: false, - price: '67.99', - productName: 'Generic Rubber Bacon', - quantity: 68, - rating: 4.129_840_682_128_08, - releaseDate: '2023-12-17T01:40:25.415Z', - status: 'error', - tags: ['Oriental', 'Small', 'Handcrafted'], - weight: 1.080_114_331_801_906_4, - }, - { - available: false, - category: 'Tools', - color: 'sky blue', - currency: 'NOK', - description: - 'The Nagasaki Lander is the trademarked name of several series of Nagasaki sport bikes, that started with the 1984 ABC800J', - id: 'f697a250-6cb2-46c8-b0f7-871ab1f2fa8d', - imageUrl: 'https://avatars.githubusercontent.com/u/95928385', - imageUrl2: 'https://avatars.githubusercontent.com/u/47588244', - inProduction: false, - open: false, - price: '613.89', - productName: 'Gorgeous Frozen Ball', - quantity: 55, - rating: 1.646_947_205_998_534_6, - releaseDate: '2024-10-13T12:31:04.929Z', - status: 'warning', - tags: ['Handmade', 'Unbranded', 'Unbranded'], - weight: 9.430_690_557_758_114, - }, -]; diff --git a/docs/src/demos/vben-vxe-table/tree/index.vue b/docs/src/demos/vben-vxe-table/tree/index.vue deleted file mode 100644 index 0024765a8ca..00000000000 --- a/docs/src/demos/vben-vxe-table/tree/index.vue +++ /dev/null @@ -1,80 +0,0 @@ - - - diff --git a/docs/src/demos/vben-vxe-table/virtual/index.vue b/docs/src/demos/vben-vxe-table/virtual/index.vue deleted file mode 100644 index 81fa00af260..00000000000 --- a/docs/src/demos/vben-vxe-table/virtual/index.vue +++ /dev/null @@ -1,64 +0,0 @@ - - - diff --git a/docs/src/en/guide/essentials/build.md b/docs/src/en/guide/essentials/build.md deleted file mode 100644 index 7f5c95321de..00000000000 --- a/docs/src/en/guide/essentials/build.md +++ /dev/null @@ -1,243 +0,0 @@ -# Build and Deployment - -::: tip Preface - -Since this is a demonstration project, the package size after building is relatively large. If there are plugins in the project that are not used, you can delete the corresponding files or routes. If they are not referenced, they will not be packaged. - -::: - -## Building - -After the project development is completed, execute the following command to build: - -**Note:** Please execute the following command in the project root directory. - -```bash -pnpm build -``` - -After the build is successful, a `dist` folder for the corresponding application will be generated in the root directory, which contains the built and packaged files, for example: `apps/web-antd/dist/` - -## Preview - -Before publishing, you can preview it locally in several ways, here are two: - -- Using the project's custom command for preview (recommended) - -**Note:** Please execute the following command in the project root directory. - -```bash -pnpm preview -``` - -After waiting for the build to succeed, visit `http://localhost:4173` to view the effect. - -- Local server preview - -You can globally install a `serve` service on your computer, such as `live-server`, - -```bash -npm i -g live-server -``` - -Then execute the `live-server` command in the `dist` directory to view the effect locally. - -```bash -cd apps/web-antd/dist -# Local preview, default port 8080 -live-server -# Specify port -live-server --port 9000 -``` - -## Compression - -### Enable `gzip` Compression - -To enable during the build process, change the `.env.production` configuration: - -```bash -VITE_COMPRESS=gzip -``` - -### Enable `brotli` Compression - -To enable during the build process, change the `.env.production` configuration: - -```bash -VITE_COMPRESS=brotli -``` - -### Enable Both `gzip` and `brotli` Compression - -To enable during the build process, change the `.env.production` configuration: - -```bash -VITE_COMPRESS=gzip,brotli -``` - -::: tip Note - -Both `gzip` and `brotli` require specific modules to be installed for use. - -::: - -::: details gzip 与 brotli 在 nginx 内的配置 - -```bash -http { - # Enable gzip - gzip on; - # Enable gzip_static - # After enabling gzip_static, there might be errors, requiring the installation of specific modules. The installation method can be researched independently. - # Only with this enabled, the .gz files packaged by vue files will be effective; otherwise, there is no need to enable gzip for packaging. - gzip_static on; - gzip_proxied any; - gzip_min_length 1k; - gzip_buffers 4 16k; - # If nginx uses multiple layers of proxy, this must be set to enable gzip. - gzip_http_version 1.0; - gzip_comp_level 2; - gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; - gzip_vary off; - gzip_disable "MSIE [1-6]\."; - - # Enable brotli compression - # Requires the installation of the corresponding nginx module, which can be researched independently. - # Can coexist with gzip without conflict. - brotli on; - brotli_comp_level 6; - brotli_buffers 16 8k; - brotli_min_length 20; - brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml; -} -``` - -::: - -## Build Analysis - -If your build files are large, you can optimize your code by analyzing the code size with the built-in [rollup-plugin-analyzer](https://github.com/doesdev/rollup-plugin-analyzer) plugin. Just execute the following command in the `root directory`: - -```bash -pnpm run build:analyze -``` - -After running, you can see the specific distribution of sizes on the automatically opened page to analyze which dependencies are problematic. - -![Build analysis report](/guide/report.png) - -## Deployment - -A simple deployment only requires publishing the final static files, the static files in the dist folder, to your CDN or static server. It's important to note that the index.html is usually the entry page for your backend service. After determining the static js and css, you may need to change the page's import path. - -For example, to upload to an nginx server, you can upload the files under the dist folder to the server's `/srv/www/project/index.html` directory, and then access the configured domain name. - -```bash -# nginx configuration -location / { - # Do not cache html to prevent cache from continuing to be effective after program updates - if ($request_filename ~* .*\.(?:htm|html)$) { - add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate"; - access_log on; - } - # This is the storage path for the files inside the vue packaged dist folder - root /srv/www/project/; - index index.html index.htm; -} -``` - -If you find the resource path is incorrect during deployment, you just need to modify the `.env.production` file. - -```bash -# Configure the change according to your own path -# Note that it needs to start and end with / -VITE_BASE=/ -VITE_BASE=/xxx/ -``` - -### Integration of Frontend Routing and Server - -The project uses vue-router for frontend routing, so you can choose between two modes: history and hash. - -- `hash` mode will append `#` to the URL by default. -- `history` mode will not, but `history` mode requires server-side support. - -You can modify the mode in `.env.production`: - -```bash -VITE_ROUTER_HISTORY=hash -``` - -### Server Configuration for History Mode Routing - -Enabling `history` mode requires server configuration. For more details on server configuration, see [history-mode](https://router.vuejs.org/guide/essentials/history-mode.html#html5-mode) - -Here is an example of `nginx` configuration: - -#### Deployment at the Root Directory - -```bash {5} -server { - listen 80; - location / { - # For use with History mode - try_files $uri $uri/ /index.html; - } -} -``` - -#### Deployment to a Non-root Directory - -- First, you need to change the `.env.production` configuration during packaging: - -```bash -VITE_BASE = /sub/ -``` - -- Then configure in the nginx configuration file - -```bash {8} -server { - listen 80; - server_name localhost; - location /sub/ { - # This is the path where the vue packaged dist files are stored - alias /srv/www/project/; - index index.html index.htm; - try_files $uri $uri/ /sub/index.html; - } -} -``` - -## Cross-Domain Handling - -Using nginx to handle cross-domain issues after project deployment - -1. Configure the frontend project API address in the `.env.production` file in the project directory: - -```bash -VITE_GLOB_API_URL=/api -``` - -2. Configure nginx to forward requests to the backend - -```bash {10-11} -server { - listen 8080; - server_name localhost; - # API proxy for solving cross-domain issues - location /api { - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # Backend API address - proxy_pass http://110.110.1.1:8080/api; - rewrite "^/api/(.*)$" /$1 break; - proxy_redirect default; - add_header Access-Control-Allow-Origin *; - add_header Access-Control-Allow-Headers X-Requested-With; - add_header Access-Control-Allow-Methods GET,POST,OPTIONS; - } -} -``` diff --git a/docs/src/en/guide/essentials/concept.md b/docs/src/en/guide/essentials/concept.md deleted file mode 100644 index 8c940a943ba..00000000000 --- a/docs/src/en/guide/essentials/concept.md +++ /dev/null @@ -1,70 +0,0 @@ -# Basic Concepts - -In the new version, the entire project has been restructured. Now, we will introduce some basic concepts to help you better understand the entire document. Please make sure to read this section first. - -## Monorepo - -Monorepo refers to the repository of the entire project, which includes all code, packages, applications, standards, documentation, configurations, etc., that is, the entire content of a `Monorepo` directory. - -## Applications - -Applications refer to a complete project; a project can contain multiple applications, which can reuse the code, packages, standards, etc., within the monorepo. Applications are placed in the `apps` directory. Each application is independent and can be run, built, tested, and deployed separately; it can also include different component libraries, etc. - -::: tip - -Applications are not limited to front-end applications; they can also be back-end applications, mobile applications, etc. For example, `apps/backend-mock` is a back-end service. - -::: - -## Packages - -A package refers to an independent module, which can be a component, a tool, a library, etc. Packages can be referenced by multiple applications or other packages. Packages are placed in the `packages` directory. - -You can consider these packages as independent `npm` packages, and they are used in the same way as `npm` packages. - -### Package Import - -Importing a package in `package.json`: - -```json {3} -{ - "dependencies": { - "@vben/utils": "workspace:*" - } -} -``` - -### Package Usage - -Importing a package in the code: - -```ts -import { isString } from '@vben/utils'; -``` - -## Aliases - -In the project, you can see some paths starting with `#`, such as `#/api`, `#/views`. These paths are aliases, used for quickly locating a certain directory. They are not implemented through `vite`'s `alias`, but through the principle of [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) in `Node.js` itself. You only need to configure the `imports` field in `package.json`. - -```json {3} -{ - "imports": { - "#/*": "./src/*" - } -} -``` - -To make these aliases recognizable by the IDE, we also need to configure them in `tsconfig.json`: - -```json {5} -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "#/*": ["src/*"] - } - } -} -``` - -This way, you can use aliases in your code. diff --git a/docs/src/en/guide/essentials/development.md b/docs/src/en/guide/essentials/development.md deleted file mode 100644 index 9ff832b7b78..00000000000 --- a/docs/src/en/guide/essentials/development.md +++ /dev/null @@ -1,255 +0,0 @@ -# Local Development {#development} - -::: tip Code Acquisition - -If you haven't acquired the code yet, you can start by reading the documentation from [Quick Start](../introduction/quick-start.md). - -::: - -## Prerequisites - -For a better development experience, we provide some tool configurations and project descriptions to facilitate your development. - -### Required Basic Knowledge - -This project requires some basic frontend knowledge. Please ensure you are familiar with the basics of Vue to handle common issues. It is recommended to learn the following topics before development. Understanding these will be very helpful for the project: - -- [Vue3](https://vuejs.org/) -- [Tailwind CSS](https://tailwindcss.com/) -- [TypeScript](https://www.typescriptlang.org/) -- [Vue Router](https://router.vuejs.org/) -- [Vitejs](https://vitejs.dev/) -- [Pnpm](https://pnpm.io/) -- [Turbo](https://turbo.build/) - -### Tool Configuration - -If you are using [vscode](https://code.visualstudio.com/) (recommended) as your IDE, you can install the following tools to improve development efficiency and code formatting: - -- [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Official Vue plugin (essential). -- [Tailwind CSS](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) - Tailwind CSS autocomplete plugin. -- [CSS Variable Autocomplete](https://marketplace.visualstudio.com/items?itemName=vunguyentuan.vscode-css-variables) - CSS variable autocomplete plugin. -- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Iconify icon plugin. -- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) - i18n plugin. -- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - Script code linting. -- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - Code formatting. -- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS formatting. -- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - Spelling checker. -- [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - .env file highlighting. - -## Npm Scripts - -Npm scripts are common configurations used in the project to perform common tasks such as starting the project, building the project, etc. The following scripts can be found in the `package.json` file at the root of the project. - -The execution command is: `pnpm run [script]` or `npm run [script]`. - -```json -{ - "scripts": { - // Build the project - "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build", - // Build the project with analysis - "build:analyze": "turbo build:analyze", - // Build a local Docker image - "build:docker": "./build-local-docker-image.sh", - // Build the web-antd application separately - "build:antd": "pnpm run build --filter=@vben/web-antd", - // Build the documentation separately - "build:docs": "pnpm run build --filter=@vben/docs", - // Build the web-ele application separately - "build:ele": "pnpm run build --filter=@vben/web-ele", - // Build the web-naive application separately - "build:naive": "pnpm run build --filter=@vben/naive", - // Build the playground application separately - "build:play": "pnpm run build --filter=@vben/playground", - // Changeset version management - "changeset": "pnpm exec changeset", - // Check for various issues in the project - "check": "pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell", - // Check for circular dependencies - "check:circular": "vsh check-circular", - // Check spelling - "check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress" - // Check dependencies - "check:dep": "vsh check-dep", - // Check types - "check:type": "turbo run typecheck", - // Clean the project (delete node_modules, dist, .turbo, etc.) - "clean": "node ./scripts/clean.mjs", - // Commit code - "commit": "czg", - // Start the project (by default, the dev scripts of all packages in the entire repository will run) - "dev": "turbo-run dev", - // Start the web-antd application - "dev:antd": "pnpm -F @vben/web-antd run dev", - // Start the documentation - "dev:docs": "pnpm -F @vben/docs run dev", - // Start the web-ele application - "dev:ele": "pnpm -F @vben/web-ele run dev", - // Start the web-naive application - "dev:naive": "pnpm -F @vben/web-naive run dev", - // Start the playground application - "dev:play": "pnpm -F @vben/playground run dev", - // Format code - "format": "vsh lint --format", - // Lint code - "lint": "vsh lint", - // After installing dependencies, execute the stub script for all packages - "postinstall": "pnpm -r run stub --if-present", - // Only allow using pnpm - "preinstall": "npx only-allow pnpm", - // Install lefthook - "prepare": "is-ci || lefthook install", - // Preview the application - "preview": "turbo-run preview", - // Package specification check - "publint": "vsh publint", - // Delete all node_modules, yarn.lock, package.lock.json, and reinstall dependencies - "reinstall": "pnpm clean --del-lock && pnpm install", - // Run vitest unit tests - "test:unit": "vitest run --dom", - // Update project dependencies - "update:deps": " pnpm update --latest --recursive", - // Changeset generation and versioning - "version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile" - } -} -``` - -## Running the Project Locally - -To run the documentation locally and make adjustments, you can execute the following command. This command allows you to select the application you want to develop: - -```bash -pnpm dev -``` - -If you want to run a specific application directly, you can execute the following commands: - -To run the `web-antd` application: - -```bash -pnpm dev:antd -``` - -To run the `web-naive` application: - -```bash -pnpm dev:naive -``` - -To run the `web-ele` application: - -```bash -pnpm dev:ele -``` - -To run the `docs` application: - -```bash -pnpm dev:docs -``` - -### Distinguishing Build Environments - -In actual business development, multiple environments are usually distinguished during the build process, such as the test environment `test` and the production environment `build`. - -At this point, you can modify three files and add corresponding script configurations to distinguish between production environments. - -Take the addition of the test environment `test` to `@vben/web-antd` as an example: - -- `apps\web-antd\package.json` - -```json -"scripts": { - "build:prod": "pnpm vite build --mode production", - "build:test": "pnpm vite build --mode test", - "build:analyze": "pnpm vite build --mode analyze", - "dev": "pnpm vite --mode development", - "preview": "vite preview", - "typecheck": "vue-tsc --noEmit --skipLibCheck" -} -``` - -Add the command `"build:test"` and change the original `"build"` to `"build:prod"` to avoid building packages for two environments simultaneously. - -- `package.json` - -```json -"scripts": { - "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build", - "build:analyze": "turbo build:analyze", - "build:antd": "pnpm run build --filter=@vben/web-antd", - "build-test:antd": "pnpm run build --filter=@vben/web-antd build:test", - - ······ -} -``` - -Add the command to build the test environment in the root directory `package.json`. - -- `turbo.json` - -```json -"tasks": { - "build": { - "dependsOn": ["^build"], - "outputs": [ - "dist/**", - "dist.zip", - ".vitepress/dist.zip", - ".vitepress/dist/**" - ] - }, - - "build-test:antd": { - "dependsOn": ["@vben/web-antd#build:test"], - "outputs": ["dist/**"] - }, - - "@vben/web-antd#build:test": { - "dependsOn": ["^build"], - "outputs": ["dist/**"] - }, - - ······ -``` - -Add the relevant dependent commands in `turbo.json`. - -## Public Static Resources - -If you need to use public static resources in the project, such as images, static HTML, etc., and you want to directly import them in the development process through `src="https://github.com/xxx.png"`. - -You need to put the resource in the corresponding project's `public/static` directory. The import path for the resource should be `src="https://github.com/static/xxx.png"`. - -## DevTools - -The project has a built-in [Vue DevTools](https://github.com/vuejs/devtools-next) plugin, which can be used during development. It is disabled by default, but can be enabled in the `.env.development` file. After enabling it, restart the project: - -```bash -VITE_DEVTOOLS=true -``` - -Once enabled, a Vue DevTools icon will appear at the bottom of the page during project runtime. Click it to open the DevTools. - -![Vue DevTools](/guide/devtools.png) - -## Running Documentation Locally - -To run the documentation locally and make adjustments, you can execute the following command: - -```bash -pnpm dev:docs -``` - -## Troubleshooting - -If you encounter dependency-related issues, you can try reinstalling the dependencies: - -```bash -# Execute this command at the root of the project. -# This command will delete all node_modules, yarn.lock, and package.lock.json files -# and then reinstall dependencies (this process will be noticeably slower). -pnpm reinstall -``` diff --git a/docs/src/en/guide/essentials/external-module.md b/docs/src/en/guide/essentials/external-module.md deleted file mode 100644 index f0a6d6e3fb2..00000000000 --- a/docs/src/en/guide/essentials/external-module.md +++ /dev/null @@ -1,58 +0,0 @@ -# External Modules - -In addition to the external modules that are included by default in the project, sometimes we need to import other external modules. Let's take [ant-design-vue](https://antdv.com/components/overview) as an example: - -## Installing Dependencies - -::: tip Install dependencies into a specific package - -- Since the project uses [pnpm](https://pnpm.io/) as the package management tool, we need to use the `pnpm` command to install dependencies. -- As the project is managed using a Monorepo module, we need to install dependencies under a specific package. Please make sure you have entered the specific package directory before installing dependencies. - -::: - -```bash -# cd /path/to/your/package -pnpm add ant-design-vue -``` - -## Usage - -### Global Import - -```ts -import { createApp } from 'vue'; -import Antd from 'ant-design-vue'; -import App from './App'; -import 'ant-design-vue/dist/reset.css'; - -const app = createApp(App); - -app.use(Antd).mount('#app'); -``` - -#### Usage - -```vue - -``` - -### Partial Import - -```vue - - - -``` - -::: warning Note - -- If the component depends on styles, you also need to import the style file. - -::: diff --git a/docs/src/en/guide/essentials/icons.md b/docs/src/en/guide/essentials/icons.md deleted file mode 100644 index 0c1631fe84c..00000000000 --- a/docs/src/en/guide/essentials/icons.md +++ /dev/null @@ -1,78 +0,0 @@ -# Icons - -::: tip About Icon Management - -- The icons in the project are mainly provided by the `@vben/icons` package. It is recommended to manage them within this package for unified management and maintenance. -- If you are using `Vscode`, it is recommended to install the [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) plugin, which makes it easy to find and use icons. - -::: - -There are several ways to use icons in the project, you can choose according to the actual situation: - -## Iconify Icons - -Integrated with the [iconify](https://github.com/iconify/iconify) icon library - -### Adding New Icons - -You can add new icons in the `packages/icons/src/iconify` directory: - -```ts -// packages/icons/src/iconify/index.ts -import { createIconifyIcon } from '@vben-core/icons'; - -export const MdiKeyboardEsc = createIconifyIcon('mdi:keyboard-esc'); -``` - -### Usage - -```vue - - - -``` - -## SVG Icons - -Instead of using Svg Sprite, SVG icons are directly imported, - -### Adding New Icons - -You can add new icon files `test.svg` in the `packages/icons/src/svg/icons` directory, and then import it in `packages/icons/src/svg/index.ts`: - -```ts -// packages/icons/src/svg/index.ts -import { createIconifyIcon } from '@vben-core/icons'; - -const SvgTestIcon = createIconifyIcon('svg:test'); - -export { SvgTestIcon }; -``` - -### Usage - -```vue - - - -``` - -## Tailwind CSS Icons - -### Usage - -You can use the icons by directly adding the Tailwind CSS icon class names, which can be found on [iconify](https://github.com/iconify/iconify) : - -```vue - -``` diff --git a/docs/src/en/guide/essentials/route.md b/docs/src/en/guide/essentials/route.md deleted file mode 100644 index 8fb0a6d1c82..00000000000 --- a/docs/src/en/guide/essentials/route.md +++ /dev/null @@ -1,603 +0,0 @@ ---- -outline: deep ---- - -# Routes and Menus - -::: info - -This page is translated by machine translation and may not be very accurate. - -::: - -In the project, the framework provides a basic routing system and **automatically generates the corresponding menu structure based on the routing files**. - -## Types of Routes - -Routes are divided into core routes, static routes, and dynamic routes. Core routes are built-in routes of the framework, including root routes, login routes, 404 routes, etc.; static routes are routes that are determined when the project starts; dynamic routes are generally generated dynamically based on the user's permissions after the user logs in. - -Both static and dynamic routes go through permission control, which can be controlled by configuring the `authority` field in the `meta` property of the route. - -### Core Routes - -Core routes are built-in routes of the framework, including root routes, login routes, 404 routes, etc. The configuration of core routes is in the `src/router/routes/core` directory under the application. - -::: tip - -Core routes are mainly used for the basic functions of the framework, so it is not recommended to put business-related routes in core routes. It is recommended to put business-related routes in static or dynamic routes. - -::: - -### Static Routes - -The configuration of static routes is in the `src/router/routes/index` directory under the application. Open the commented file content: - -::: tip - -Permission control is controlled by the `authority` field in the `meta` property of the route. If your page project does not require permission control, you can omit the `authority` field. - -::: - -```ts -// Uncomment if needed and create the folder -// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); // [!code --] -const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); // [!code ++] -/** Dynamic routes */ -const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); - -/** External route list, these pages can be accessed without Layout, possibly used for embedding in other systems */ -// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles) // [!code --] -const externalRoutes: RouteRecordRaw[] = []; // [!code --] -const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); // [!code ++] -``` - -### Dynamic Routes - -The configuration of dynamic routes is in the `src/router/routes/modules` directory under the corresponding application. This directory contains all the route files. The content format of each file is consistent with the Vue Router route configuration format. Below is the configuration of secondary and multi-level routes. - -## Route Definition - -The configuration method of static routes and dynamic routes is the same. Below is the configuration of secondary and multi-level routes: - -### Secondary Routes - -::: details Secondary Route Example Code - -```ts -import type { RouteRecordRaw } from 'vue-router'; - -import { VBEN_LOGO_URL } from '@vben/constants'; - -import { BasicLayout } from '#/layouts'; -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - badgeType: 'dot', - badgeVariants: 'destructive', - icon: VBEN_LOGO_URL, - order: 9999, - title: $t('page.vben.title'), - }, - name: 'VbenProject', - path: '/vben-admin', - redirect: '/vben-admin/about', - children: [ - { - name: 'VbenAbout', - path: '/vben-admin/about', - component: () => import('#/views/_core/about/index.vue'), - meta: { - badgeType: 'dot', - badgeVariants: 'destructive', - icon: 'lucide:copyright', - title: $t('page.vben.about'), - }, - }, - ], - }, -]; - -export default routes; -``` - -::: - -### Multi-level Routes - -::: tip - -- The parent route of multi-level routes does not need to set the `component` property, just set the `children` property. Unless you really need to display content nested under the parent route. -- In most cases, the `redirect` property of the parent route does not need to be specified, it will default to the first child route. - -::: - -::: details Multi-level Route Example Code - -```ts -import type { RouteRecordRaw } from 'vue-router'; - -import { BasicLayout } from '#/layouts'; -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'ic:baseline-view-in-ar', - keepAlive: true, - order: 1000, - title: $t('demos.title'), - }, - name: 'Demos', - path: '/demos', - redirect: '/demos/access', - children: [ - // Nested menu - { - meta: { - icon: 'ic:round-menu', - title: $t('demos.nested.title'), - }, - name: 'NestedDemos', - path: '/demos/nested', - redirect: '/demos/nested/menu1', - children: [ - { - name: 'Menu1Demo', - path: '/demos/nested/menu1', - component: () => import('#/views/demos/nested/menu-1.vue'), - meta: { - icon: 'ic:round-menu', - keepAlive: true, - title: $t('demos.nested.menu1'), - }, - }, - { - name: 'Menu2Demo', - path: '/demos/nested/menu2', - meta: { - icon: 'ic:round-menu', - keepAlive: true, - title: $t('demos.nested.menu2'), - }, - redirect: '/demos/nested/menu2/menu2-1', - children: [ - { - name: 'Menu21Demo', - path: '/demos/nested/menu2/menu2-1', - component: () => import('#/views/demos/nested/menu-2-1.vue'), - meta: { - icon: 'ic:round-menu', - keepAlive: true, - title: $t('demos.nested.menu2_1'), - }, - }, - ], - }, - { - name: 'Menu3Demo', - path: '/demos/nested/menu3', - meta: { - icon: 'ic:round-menu', - title: $t('demos.nested.menu3'), - }, - redirect: '/demos/nested/menu3/menu3-1', - children: [ - { - name: 'Menu31Demo', - path: 'menu3-1', - component: () => import('#/views/demos/nested/menu-3-1.vue'), - meta: { - icon: 'ic:round-menu', - keepAlive: true, - title: $t('demos.nested.menu3_1'), - }, - }, - { - name: 'Menu32Demo', - path: 'menu3-2', - meta: { - icon: 'ic:round-menu', - title: $t('demos.nested.menu3_2'), - }, - redirect: '/demos/nested/menu3/menu3-2/menu3-2-1', - children: [ - { - name: 'Menu321Demo', - path: '/demos/nested/menu3/menu3-2/menu3-2-1', - component: () => - import('#/views/demos/nested/menu-3-2-1.vue'), - meta: { - icon: 'ic:round-menu', - keepAlive: true, - title: $t('demos.nested.menu3_2_1'), - }, - }, - ], - }, - ], - }, - ], - }, - ], - }, -]; - -export default routes; -``` - -::: - -## Adding a New Page - -To add a new page, you only need to add a route and the corresponding page component. - -### Adding a Route - -Add a route object in the corresponding route file, as follows: - -```ts -import type { RouteRecordRaw } from 'vue-router'; - -import { VBEN_LOGO_URL } from '@vben/constants'; - -import { BasicLayout } from '#/layouts'; -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'mdi:home', - title: $t('page.home.title'), - }, - name: 'Home', - path: '/home', - redirect: '/home/index', - children: [ - { - name: 'HomeIndex', - path: '/home/index', - component: () => import('#/views/home/index.vue'), - meta: { - icon: 'mdi:home', - title: $t('page.home.index'), - }, - }, - ], - }, -]; - -export default routes; -``` - -### Adding a Page Component - -In `#/views/home/`, add a new `index.vue` file, as follows: - -```vue - -``` - -### Verification - -At this point, the page has been added. Visit `http://localhost:5555/home/index` to see the corresponding page. - -## Route Configuration - -The route configuration items are mainly in the `meta` property of the route object. The following are common configuration items: - -```ts {5-8} -const routes = [ - { - name: 'HomeIndex', - path: '/home/index', - meta: { - icon: 'mdi:home', - title: $t('page.home.index'), - }, - }, -]; -``` - -::: details Route Meta Configuration Type Definition - -```ts -interface RouteMeta { - /** - * Active icon (menu) - */ - activeIcon?: string; - /** - * The currently active menu, sometimes you don't want to activate the existing menu, use this to activate the parent menu - */ - activePath?: string; - /** - * Whether to fix the tab - * @default false - */ - affixTab?: boolean; - /** - * The order of fixed tabs - * @default 0 - */ - affixTabOrder?: number; - /** - * Specific roles required to access - * @default [] - */ - authority?: string[]; - /** - * Badge - */ - badge?: string; - /** - * Badge type - */ - badgeType?: 'dot' | 'normal'; - /** - * Badge color - */ - badgeVariants?: - | 'default' - | 'destructive' - | 'primary' - | 'success' - | 'warning' - | string; - /** - * The children of the current route are not displayed in the menu - * @default false - */ - hideChildrenInMenu?: boolean; - /** - * The current route is not displayed in the breadcrumb - * @default false - */ - hideInBreadcrumb?: boolean; - /** - * The current route is not displayed in the menu - * @default false - */ - hideInMenu?: boolean; - /** - * The current route is not displayed in the tab - * @default false - */ - hideInTab?: boolean; - /** - * Icon (menu/tab) - */ - icon?: string; - /** - * iframe address - */ - iframeSrc?: string; - /** - * Ignore permissions, can be accessed directly - * @default false - */ - ignoreAccess?: boolean; - /** - * Enable KeepAlive cache - */ - keepAlive?: boolean; - /** - * External link - jump path - */ - link?: string; - /** - * Whether the route has been loaded - */ - loaded?: boolean; - /** - * Maximum number of open tabs - * @default false - */ - maxNumOfOpenTab?: number; - /** - * The menu can be seen, but access will be redirected to 403 - */ - menuVisibleWithForbidden?: boolean; - /** - * Open in a new window - */ - openInNewWindow?: boolean; - /** - * Used for route -> menu sorting - */ - order?: number; - /** - * Parameters carried by the menu - */ - query?: Recordable; - /** - * Title name - */ - title: string; -} -``` - -::: - -### title - -- Type: `string` -- Default: `''` - -Used to configure the title of the page, which will be displayed in the menu and tab. Generally used with internationalization. - -### icon - -- Type: `string` -- Default: `''` - -Used to configure the icon of the page, which will be displayed in the menu and tab. Generally used with an icon library, if it is an `http` link, the image will be loaded automatically. - -### activeIcon - -- Type: `string` -- Default: `''` - -Used to configure the active icon of the page, which will be displayed in the menu. Generally used with an icon library, if it is an `http` link, the image will be loaded automatically. - -### keepAlive - -- Type: `boolean` -- Default: `false` - -Used to configure whether the page cache is enabled. When enabled, the page will be cached and will not reload, only effective when the tab is enabled. - -### hideInMenu - -- Type: `boolean` -- Default: `false` - -Used to configure whether the page is hidden in the menu. When hidden, the page will not be displayed in the menu. - -### hideInTab - -- Type: `boolean` -- Default: `false` - -Used to configure whether the page is hidden in the tab. When hidden, the page will not be displayed in the tab. - -### hideInBreadcrumb - -- Type: `boolean` -- Default: `false` - -Used to configure whether the page is hidden in the breadcrumb. When hidden, the page will not be displayed in the breadcrumb. - -### hideChildrenInMenu - -- Type: `boolean` -- Default: `false` - -Used to configure whether the subpages of the page are hidden in the menu. When hidden, the subpages will not be displayed in the menu. - -### authority - -- Type: `string[]` -- Default: `[]` - -Used to configure the permissions of the page. Only users with the corresponding permissions can access the page. If not configured, no permissions are required. - -### badge - -- Type: `string` -- Default: `''` - -Used to configure the badge of the page, which will be displayed in the menu. - -### badgeType - -- Type: `'dot' | 'normal'` -- Default: `'normal'` - -Used to configure the badge type of the page. `dot` is a small red dot, `normal` is text. - -### badgeVariants - -- Type: `'default' | 'destructive' | 'primary' | 'success' | 'warning' | string` -- Default: `'success'` - -Used to configure the badge color of the page. - -### activePath - -- Type: `string` -- Default: `''` - -Used to configure the currently active menu. Sometimes the page is not displayed in the menu, and this is used to activate the parent menu. - -### affixTab - -- Type: `boolean` -- Default: `false` - -Used to configure whether the page is fixed in the tab. When fixed, the page cannot be closed. - -### affixTabOrder - -- Type: `number` -- Default: `0` - -Used to configure the order of fixed tabs, sorted in ascending order. - -### iframeSrc - -- Type: `string` -- Default: `''` - -Used to configure the `iframe` address of the embedded page. When set, the corresponding page will be embedded in the current page. - -### ignoreAccess - -- Type: `boolean` -- Default: `false` - -Used to configure whether the page ignores permissions and can be accessed directly. - -### link - -- Type: `string` -- Default: `''` - -Used to configure the external link jump path, which will open in a new window. - -### maxNumOfOpenTab - -- Type: `number` -- Default: `-1` - -Used to configure the maximum number of open tabs. When set, the earliest opened tab will be automatically closed when opening a new tab (only effective when opening tabs with the same name). - -### menuVisibleWithForbidden - -- Type: `boolean` -- Default: `false` - -Used to configure whether the page can be seen in the menu, but access will be redirected to 403. - -### openInNewWindow - -- Type: `boolean` -- Default: `false` - -When set to `true`, the page will open in a new window. - -### order - -- Type: `number` -- Default: `0` - -Used to configure the sorting of the page, used for route to menu sorting. - -_Note:_ Sorting is only effective for first-level menus. The sorting of second-level menus needs to be set in the corresponding first-level menu in code order. - -### query - -- Type: `Recordable` -- Default: `{}` - -Used to configure the menu parameters of the page, which will be passed to the page in the menu. - -## Route Refresh - -The route refresh method is as follows: - -```vue - -``` diff --git a/docs/src/en/guide/essentials/server.md b/docs/src/en/guide/essentials/server.md deleted file mode 100644 index 67e0b1fd0b2..00000000000 --- a/docs/src/en/guide/essentials/server.md +++ /dev/null @@ -1,356 +0,0 @@ -# Server Interaction and Data Mocking - -::: tip Note - -This document explains how to use Mock data and interact with the server in a development environment, involving technologies such as: - -- [Nitro](https://nitro.unjs.io/) A lightweight backend server that can be deployed anywhere, used as a Mock server in the project. -- [axios](https://axios-http.com/docs/intro) Used to send HTTP requests to interact with the server. - -::: - -## Interaction in Development Environment - -If the frontend application and the backend API server are not running on the same host, you need to proxy the API requests to the API server in the development environment. If they are on the same host, you can directly request the specific API endpoint. - -### Local Development CORS Configuration - -::: tip Hint - -The CORS configuration for local development has already been set up. If you have other requirements, you can add or adjust the configuration as needed. - -::: - -#### Configuring Local Development API Endpoint - -Configure the API endpoint in the `.env.development` file at the project root directory, here it is set to `/api`: - -```bash -VITE_GLOB_API_URL=/api -``` - -#### Configuring Development Server Proxy - -In the development environment, if you need to handle CORS, configure the API endpoint in the `vite.config.mts` file under the corresponding application directory: - -```ts{8-16} -// apps/web-antd/vite.config.mts -import { defineConfig } from '@vben/vite-config'; - -export default defineConfig(async () => { - return { - vite: { - server: { - proxy: {// [!code focus:11] - '/api': { - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, ''), - // mock proxy - target: 'http://localhost:5320/api', - ws: true, - }, - }, - }, - }, - }; -}); -``` - -#### API Requests - -Based on the above configuration, we can use `/api` as the prefix for API requests in our frontend project, for example: - -```ts -import axios from 'axios'; - -axios.get('/api/user').then((res) => { - console.log(res); -}); -``` - -At this point, the request will be proxied to `http://localhost:5320/api/user`. - -::: warning Note - -From the browser's console Network tab, the request appears as `http://localhost:5555/api/user`. This is because the proxy configuration does not change the local request's URL. - -::: - -### Configuration Without CORS - -If there is no CORS issue, you can directly ignore the [Configure Development Server Proxy](./server.md#configure-development-server-proxy) settings and set the API endpoint directly in `VITE_GLOB_API_URL`. - -Configure the API endpoint in the `.env.development` file at the project root directory: - -```bash -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api -``` - -## Production Environment Interaction - -### API Endpoint Configuration - -Configure the API endpoint in the `.env.production` file at the project root directory: - -```bash -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api -``` - -::: tip How to Dynamically Modify API Endpoint in Production - -Variables starting with `VITE_GLOB_*` in the `.env` file are injected into the `_app.config.js` file during packaging. After packaging, you can modify the corresponding API addresses in `dist/_app.config.js` and refresh the page to apply the changes. This eliminates the need to package multiple times for different environments, allowing a single package to be deployed across multiple API environments. - -::: - -### Cross-Origin Resource Sharing (CORS) Handling - -In the production environment, if CORS issues arise, you can use `nginx` to proxy the API address or enable `cors` on the backend to handle it (refer to the mock service for examples). - -## API Request Configuration - -The project comes with a default basic request configuration based on `axios`, provided by the `@vben/request` package. The project does not overly complicate things but simply wraps some common configurations. If there are other requirements, you can add or adjust the configurations as needed. Depending on the app, different component libraries and `store` might be used, so under the `src/api/request.ts` folder in the application directory, there are corresponding request configuration files. For example, in the `web-antd` project, there's a `src/api/request.ts` file where you can configure according to your needs. - -### Request Examples - -#### GET Request - -```ts -import { requestClient } from '#/api/request'; - -export async function getUserInfoApi() { - return requestClient.get('/user/info'); -} -``` - -#### POST/PUT Request - -```ts -import { requestClient } from '#/api/request'; - -export async function saveUserApi(user: UserInfo) { - return requestClient.post('/user', user); -} - -export async function saveUserApi(user: UserInfo) { - return requestClient.put('/user', user); -} - -export async function saveUserApi(user: UserInfo) { - const url = user.id ? `/user/${user.id}` : '/user/'; - return requestClient.request(url, { - data: user, - // OR PUT - method: user.id ? 'PUT' : 'POST', - }); -} -``` - -#### DELETE Request - -```ts -import { requestClient } from '#/api/request'; - -export async function deleteUserApi(userId: number) { - return requestClient.delete(`/user/${userId}`); -} -``` - -### Request Configuration - -The `src/api/request.ts` within the application can be configured according to the needs of your application: - -```ts -/** - * This file can be adjusted according to business logic - */ -import type { HttpResponse } from '@vben/request'; - -import { useAppConfig } from '@vben/hooks'; -import { preferences } from '@vben/preferences'; -import { - authenticateResponseInterceptor, - errorMessageResponseInterceptor, - RequestClient, -} from '@vben/request'; -import { useAccessStore } from '@vben/stores'; - -import { message } from 'ant-design-vue'; - -import { useAuthStore } from '#/store'; - -import { refreshTokenApi } from './core'; - -const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); - -function createRequestClient(baseURL: string) { - const client = new RequestClient({ - baseURL, - }); - - /** - * Re-authentication Logic - */ - async function doReAuthenticate() { - console.warn('Access token or refresh token is invalid or expired. '); - const accessStore = useAccessStore(); - const authStore = useAuthStore(); - accessStore.setAccessToken(null); - if (preferences.app.loginExpiredMode === 'modal') { - accessStore.setLoginExpired(true); - } else { - await authStore.logout(); - } - } - - /** - * Refresh token Logic - */ - async function doRefreshToken() { - const accessStore = useAccessStore(); - const resp = await refreshTokenApi(); - const newToken = resp.data; - accessStore.setAccessToken(newToken); - return newToken; - } - - function formatToken(token: null | string) { - return token ? `Bearer ${token}` : null; - } - - // Request Header Processing - client.addRequestInterceptor({ - fulfilled: async (config) => { - const accessStore = useAccessStore(); - - config.headers.Authorization = formatToken(accessStore.accessToken); - config.headers['Accept-Language'] = preferences.app.locale; - return config; - }, - }); - - // Deal Response Data - client.addResponseInterceptor({ - fulfilled: (response) => { - const { data: responseData, status } = response; - - const { code, data } = responseData; - - if (status >= 200 && status < 400 && code === 0) { - return data; - } - throw Object.assign({}, response, { response }); - }, - }); - - // Handling Token Expiration - client.addResponseInterceptor( - authenticateResponseInterceptor({ - client, - doReAuthenticate, - doRefreshToken, - enableRefreshToken: preferences.app.enableRefreshToken, - formatToken, - }), - ); - - // Generic error handling; if none of the above error handling logic is triggered, it will fall back to this. - client.addResponseInterceptor( - errorMessageResponseInterceptor((msg: string, error) => { - // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg - // 当前mock接口返回的错误字段是 error 或者 message - const responseData = error?.response?.data ?? {}; - const errorMessage = responseData?.error ?? responseData?.message ?? ''; - // 如果没有错误信息,则会根据状态码进行提示 - message.error(errorMessage || msg); - }), - ); - - return client; -} - -export const requestClient = createRequestClient(apiURL); - -export const baseRequestClient = new RequestClient({ baseURL: apiURL }); -``` - -### Multiple API Endpoints - -To handle multiple API endpoints, simply create multiple `requestClient` instances, as follows: - -```ts -const { apiURL, otherApiURL } = useAppConfig( - import.meta.env, - import.meta.env.PROD, -); - -export const requestClient = createRequestClient(apiURL); - -export const otherRequestClient = createRequestClient(otherApiURL); -``` - -## Refresh Token - -The project provides a default logic for refreshing tokens. To enable it, follow the configuration below: - -- Ensure the refresh token feature is enabled - -Adjust the `preferences.ts` in the corresponding application directory to ensure `enableRefreshToken='true'`. - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - enableRefreshToken: true, - }, -}); -``` - -Configure the `doRefreshToken` method in `src/api/request.ts` as follows: - -```ts -// Adjust this to your token format -function formatToken(token: null | string) { - return token ? `Bearer ${token}` : null; -} - -/** - * Refresh token logic - */ -async function doRefreshToken() { - const accessStore = useAccessStore(); - // Adjust this to your refresh token API - const resp = await refreshTokenApi(); - const newToken = resp.data; - accessStore.setAccessToken(newToken); - return newToken; -} -``` - -## Data Mocking - -::: tip Production Environment Mock - -The new version no longer supports mock in the production environment. Please use real interfaces. - -::: - -Mock data is an indispensable part of frontend development, serving as a key link in separating frontend and backend development. By agreeing on interfaces with the server side in advance and simulating request data and even logic, frontend development can proceed independently, without being blocked by the backend development process. - -The project uses [Nitro](https://nitro.unjs.io/) for local mock data processing. The principle is to start an additional backend service locally, which is a real backend service that can handle requests and return data. - -### Using Nitro - -The mock service code is located in the `apps/backend-mock` directory. It does not need to be started manually and is already integrated into the project. You only need to run `pnpm dev` in the project root directory. After running successfully, the console will print `http://localhost:5320/api`, and you can access this address to view the mock service. - -[Nitro](https://nitro.unjs.io/) syntax is simple, and you can configure and develop according to your needs. For specific configurations, you can refer to the [Nitro documentation](https://nitro.unjs.io/). - -## Disabling Mock Service - -Since mock is essentially a real backend service, if you do not need the mock service, you can configure `VITE_NITRO_MOCK=false` in the `.env.development` file in the project root directory to disable the mock service. - -```bash -# .env.development -VITE_NITRO_MOCK=false -``` diff --git a/docs/src/en/guide/essentials/settings.md b/docs/src/en/guide/essentials/settings.md deleted file mode 100644 index 59a6c5c01ba..00000000000 --- a/docs/src/en/guide/essentials/settings.md +++ /dev/null @@ -1,626 +0,0 @@ -# Configuration - -## Environment Variable Configuration - -The project's environment variable configuration is located in the application directory under `.env`, `.env.development`, `.env.production`. - -The rules are consistent with [Vite Env Variables and Modes](https://vitejs.dev/guide/env-and-mode.html). The format is as follows: - -```bash -.env # Loaded in all environments -.env.local # Loaded in all environments, but ignored by git -.env.[mode] # Only loaded in the specified mode -.env.[mode].local # Only loaded in the specified mode, but ignored by git -``` - -::: tip - -- Only variables starting with `VITE_` will be embedded into the client-side package. You can access them in the project code like this: - - ```ts - console.log(import.meta.env.VITE_PROT); - ``` - -- Variables starting with `VITE_GLOB_*` will be added to the `_app.config.js` configuration file during packaging. - -::: - -## Environment Configuration Description - -::: code-group - -```bash [.env] -# Application title -VITE_APP_TITLE=Vben Admin - -# Application namespace, used as a prefix for caching, store, etc., to ensure isolation -VITE_APP_NAMESPACE=vben-web-antd -``` - -```bash [.env.development] -# Port Number -VITE_PORT=5555 - -# Public Path for Resources, must start and end with / -VITE_BASE=/ - -# API URL -VITE_GLOB_API_URL=/api - -# Whether to enable Nitro Mock service, true to enable, false to disable -VITE_NITRO_MOCK=true - -# Whether to open devtools, true to open, false to close -VITE_DEVTOOLS=true - -# Whether to inject global loading -VITE_INJECT_APP_LOADING=true - -# Whether to generate after packaging dist.zip -VITE_ARCHIVER=true -``` - -```bash [.env.production] -# Public Path for Resources, must start and end with / -VITE_BASE=/ - -# API URL -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api - -# Whether to enable compression, can be set to none, brotli, gzip -VITE_COMPRESS=gzip - -# Whether to enable PWA -VITE_PWA=false - -# vue-router mode -VITE_ROUTER_HISTORY=hash - -# Whether to inject global loading -VITE_INJECT_APP_LOADING=true - -# Whether to generate dist.zip after packaging -VITE_ARCHIVER=true -``` - -::: - -## Dynamic Configuration in Production Environment - -When executing `pnpm build` in the root directory of the monorepo, a `dist/_app.config.js` file will be automatically generated in the corresponding application and inserted into `index.html`. - -`_app.config.js` is a dynamic configuration file that allows for modifications to the configuration dynamically based on different environments after the project has been built. The content is as follows: - -```ts -window._VBEN_ADMIN_PRO_APP_CONF_ = { - VITE_GLOB_API_URL: 'https://mock-napi.vben.pro/api', -}; -Object.freeze(window._VBEN_ADMIN_PRO_APP_CONF_); -Object.defineProperty(window, '_VBEN_ADMIN_PRO_APP_CONF_', { - configurable: false, - writable: false, -}); -``` - -### Purpose - -`_app.config.js` is used for projects that need to dynamically modify configurations after packaging, such as API endpoints. There's no need to repackage; you can simply modify the variables in `/dist/_app.config.js` after packaging, and refresh to update the variables in the code. A `js` file is used to ensure that the configuration file is loaded early in the order. - -### Usage - -To access the variables inside `_app.config.js`, you need to use the `useAppConfig` method provided by `@vben/hooks`. - -```ts -const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); -``` - -### Adding New - -To add a new dynamically modifiable configuration item, simply follow the steps below: - -- First, add the variable that needs to be dynamically configurable in the `.env` file or the corresponding development environment configuration file. The variable must start with `VITE_GLOB_*`, for example: - - ```bash - VITE_GLOB_OTHER_API_URL=https://mock-napi.vben.pro/other-api - ``` - -- In `packages/types/global.d.ts`, add the corresponding type definition, such as: - - ```ts - export interface VbenAdminProAppConfigRaw { - VITE_GLOB_API_URL: string; - VITE_GLOB_OTHER_API_URL: string; // [!code ++] - } - - export interface ApplicationConfig { - apiURL: string; - otherApiURL: string; // [!code ++] - } - ``` - -- In `packages/effects/hooks/src/use-app-config.ts`, add the corresponding configuration item, such as: - - ```ts - export function useAppConfig( - env: Record, - isProduction: boolean, - ): ApplicationConfig { - // In production environment, directly use the window._VBEN_ADMIN_PRO_APP_CONF_ global variable - const config = isProduction - ? window._VBEN_ADMIN_PRO_APP_CONF_ - : (env as VbenAdminProAppConfigRaw); - - const { VITE_GLOB_API_URL, VITE_GLOB_OTHER_API_URL } = config; // [!code ++] - - return { - apiURL: VITE_GLOB_API_URL, - otherApiURL: VITE_GLOB_OTHER_API_URL, // [!code ++] - }; - } - ``` - -At this point, you can use the `useAppConfig` method within the project to access the newly added configuration item. - -```ts -const { otherApiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); -``` - -::: warning Warning - -The `useAppConfig` method should only be used within the application and not be coupled with the internals of a package. The reason for passing `import.meta.env` and `import.meta.env.PROD` is to avoid such coupling. A pure package should avoid using variables specific to a particular build tool. - -::: - -## Preferences - -The project offers a wide range of preference settings for dynamically configuring various features of the project: - -![](/guide/preferences.png) - -If you cannot find documentation for a setting, you can try configuring it yourself and then click `Copy Preferences` to override the project defaults. The configuration file is located in the application directory under `preferences.ts`, where you can override the framework's default configurations to achieve custom settings. - -```ts -import { useAppConfig } from '@vben/hooks'; -import { defineOverridesPreferences } from '@vben/preferences'; - -/** - * @description Project configuration file - * Only a part of the configuration in the project needs to be covered, and unnecessary configurations do not need to be covered. The default configuration will be automatically used - * !!! Please clear the cache after changing the configuration, otherwise it may not take effect - */ -export const overridesPreferences = defineOverridesPreferences({ - // overrides -}); -``` - -### Framework default configuration - -::: details View the default configuration of the framework - -```ts -const defaultPreferences: Preferences = { - app: { - accessMode: 'frontend', - authPageLayout: 'panel-right', - checkUpdatesInterval: 1, - colorGrayMode: false, - colorWeakMode: false, - compact: false, - contentCompact: 'wide', - contentCompactWidth: 1200, - contentPadding: 0, - contentPaddingBottom: 0, - contentPaddingLeft: 0, - contentPaddingRight: 0, - contentPaddingTop: 0, - defaultAvatar: - 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp', - defaultHomePath: '/analytics', - dynamicTitle: true, - enableCheckUpdates: true, - enablePreferences: true, - enableRefreshToken: false, - isMobile: false, - layout: 'sidebar-nav', - locale: 'zh-CN', - loginExpiredMode: 'page', - name: 'Vben Admin', - preferencesButtonPosition: 'auto', - watermark: false, - zIndex: 200, - }, - breadcrumb: { - enable: true, - hideOnlyOne: false, - showHome: false, - showIcon: true, - styleType: 'normal', - }, - copyright: { - companyName: 'Vben', - companySiteLink: 'https://www.vben.pro/', - date: '2024', - enable: true, - icp: '', - icpLink: '', - settingShow: true, - }, - footer: { - enable: false, - fixed: false, - height: 32, - }, - header: { - enable: true, - height: 50, - hidden: false, - menuAlign: 'start', - mode: 'fixed', - }, - logo: { - enable: true, - fit: 'contain', - source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp', - }, - navigation: { - accordion: true, - split: true, - styleType: 'rounded', - }, - shortcutKeys: { - enable: true, - globalLockScreen: true, - globalLogout: true, - globalPreferences: true, - globalSearch: true, - }, - sidebar: { - autoActivateChild: false, - collapsed: false, - collapsedButton: true, - collapsedShowTitle: false, - collapseWidth: 60, - enable: true, - expandOnHover: true, - extraCollapse: false, - extraCollapsedWidth: 60, - fixedButton: true, - hidden: false, - mixedWidth: 80, - width: 224, - }, - tabbar: { - draggable: true, - enable: true, - height: 38, - keepAlive: true, - maxCount: 0, - middleClickToClose: false, - persist: true, - showIcon: true, - showMaximize: true, - showMore: true, - styleType: 'chrome', - wheelable: true, - }, - theme: { - builtinType: 'default', - colorDestructive: 'hsl(348 100% 61%)', - colorPrimary: 'hsl(212 100% 45%)', - colorSuccess: 'hsl(144 57% 58%)', - colorWarning: 'hsl(42 84% 61%)', - mode: 'dark', - radius: '0.5', - semiDarkHeader: false, - semiDarkSidebar: false, - }, - transition: { - enable: true, - loading: true, - name: 'fade-slide', - progress: true, - }, - widget: { - fullscreen: true, - globalSearch: true, - languageToggle: true, - lockScreen: true, - notification: true, - refresh: true, - sidebarToggle: true, - themeToggle: true, - }, -}; -``` - -::: - -::: details View the default configuration type of the framework - -```ts -interface AppPreferences { - /** Permission mode */ - accessMode: AccessModeType; - /** Layout of the login/registration page */ - authPageLayout: AuthPageLayoutType; - /** Interval for checking updates */ - checkUpdatesInterval: number; - /** Whether to enable gray mode */ - colorGrayMode: boolean; - /** Whether to enable color weakness mode */ - colorWeakMode: boolean; - /** Whether to enable compact mode */ - compact: boolean; - /** Whether to enable content compact mode */ - contentCompact: ContentCompactType; - /** Content compact width */ - contentCompactWidth: number; - /** Content padding */ - contentPadding: number; - /** Content bottom padding */ - contentPaddingBottom: number; - /** Content left padding */ - contentPaddingLeft: number; - /** Content right padding */ - contentPaddingRight: number; - /** Content top padding */ - contentPaddingTop: number; - // /** Default application avatar */ - defaultAvatar: string; - /** Default homepage path */ - defaultHomePath: string; - // /** Enable dynamic title */ - dynamicTitle: boolean; - /** Whether to enable update checks */ - enableCheckUpdates: boolean; - /** Whether to display preferences */ - enablePreferences: boolean; - /** - * @zh_CN Whether to enable refreshToken - */ - enableRefreshToken: boolean; - /** Whether it's mobile */ - isMobile: boolean; - /** Layout method */ - layout: LayoutType; - /** Supported languages */ - locale: SupportedLanguagesType; - /** Login expiration mode */ - loginExpiredMode: LoginExpiredModeType; - /** Application name */ - name: string; - /** Position of the preferences button */ - preferencesButtonPosition: PreferencesButtonPositionType; - /** - * @zh_CN Whether to enable watermark - */ - watermark: boolean; - /** z-index */ - zIndex: number; -} -interface BreadcrumbPreferences { - /** Whether breadcrumbs are enabled */ - enable: boolean; - /** Whether to hide breadcrumbs when there is only one */ - hideOnlyOne: boolean; - /** Whether the home icon in breadcrumbs is visible */ - showHome: boolean; - /** Whether the icon in breadcrumbs is visible */ - showIcon: boolean; - /** Breadcrumb style */ - styleType: BreadcrumbStyleType; -} - -interface CopyrightPreferences { - /** Copyright company name */ - companyName: string; - /** Link to the copyright company's site */ - companySiteLink: string; - /** Copyright date */ - date: string; - /** Whether copyright is visible */ - enable: boolean; - /** ICP number */ - icp: string; - /** Link to the ICP */ - icpLink: string; - /** Whether to show in settings panel */ - settingShow?: boolean; -} - -interface FooterPreferences { - /** Whether the footer is visible */ - enable: boolean; - /** Whether the footer is fixed */ - fixed: boolean; - /** Footer height */ - height: number; -} - -interface HeaderPreferences { - /** Whether the header is enabled */ - enable: boolean; - /** Header height */ - height: number; - /** Whether the header is hidden, css-hidden */ - hidden: boolean; - /** Header menu alignment */ - menuAlign: LayoutHeaderMenuAlignType; - /** Header display mode */ - mode: LayoutHeaderModeType; -} - -interface LogoPreferences { - /** Whether the logo is visible */ - enable: boolean; - /** Logo image fitting method */ - fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down'; - /** Logo URL */ - source: string; -} - -interface NavigationPreferences { - /** Navigation menu accordion mode */ - accordion: boolean; - /** Whether the navigation menu is split, only effective in layout=mixed-nav */ - split: boolean; - /** Navigation menu style */ - styleType: NavigationStyleType; -} -interface SidebarPreferences { - /** Automatically activate child menu when clicking on directory */ - autoActivateChild: boolean; - /** Whether the sidebar is collapsed */ - collapsed: boolean; - /** Whether the sidebar collapse button is visible */ - collapsedButton: boolean; - /** Whether to show title when sidebar is collapsed */ - collapsedShowTitle: boolean; - /** Sidebar collapse width */ - collapseWidth: number; - /** Whether the sidebar is visible */ - enable: boolean; - /** Menu auto-expand state */ - expandOnHover: boolean; - /** Whether the sidebar extension area is collapsed */ - extraCollapse: boolean; - /** Sidebar extension area collapse width */ - extraCollapsedWidth: number; - /** Whether the sidebar fixed button is visible */ - fixedButton: boolean; - /** Whether the sidebar is hidden - css */ - hidden: boolean; - /** Mixed sidebar width */ - mixedWidth: number; - /** Sidebar width */ - width: number; -} - -interface ShortcutKeyPreferences { - /** Whether shortcut keys are enabled globally */ - enable: boolean; - /** Whether the global lock screen shortcut is enabled */ - globalLockScreen: boolean; - /** Whether the global logout shortcut is enabled */ - globalLogout: boolean; - /** Whether the global preferences shortcut is enabled */ - globalPreferences: boolean; - /** Whether the global search shortcut is enabled */ - globalSearch: boolean; -} - -interface TabbarPreferences { - /** Whether dragging of multiple tabs is enabled */ - draggable: boolean; - /** Whether multiple tabs are enabled */ - enable: boolean; - /** Tab height */ - height: number; - /** Whether tab caching is enabled */ - keepAlive: boolean; - /** Maximum number of tabs */ - maxCount: number; - /** Whether to close tab when middle-clicked */ - middleClickToClose: boolean; - /** Whether tabs are persistent */ - persist: boolean; - /** Whether icons in multiple tabs are enabled */ - showIcon: boolean; - /** Whether to show the maximize button */ - showMaximize: boolean; - /** Whether to show the more button */ - showMore: boolean; - /** Tab style */ - styleType: TabsStyleType; - /** Whether mouse wheel response is enabled */ - wheelable: boolean; -} -interface ThemePreferences { - /** Built-in theme name */ - builtinType: BuiltinThemeType; - /** Destructive color */ - colorDestructive: string; - /** Primary color */ - colorPrimary: string; - /** Success color */ - colorSuccess: string; - /** Warning color */ - colorWarning: string; - /** Current theme */ - mode: ThemeModeType; - /** Radius */ - radius: string; - /** Whether to enable semi-dark header (only effective when theme='light') */ - semiDarkHeader: boolean; - /** Whether to enable semi-dark sidebar (only effective when theme='light') */ - semiDarkSidebar: boolean; -} - -interface TransitionPreferences { - /** Whether page transition animations are enabled */ - enable: boolean; - // /** Whether page loading loading is enabled */ - loading: boolean; - /** Page transition animation */ - name: PageTransitionType | string; - /** Whether page loading progress animation is enabled */ - progress: boolean; -} - -interface WidgetPreferences { - /** Whether fullscreen widgets are enabled */ - fullscreen: boolean; - /** Whether global search widget is enabled */ - globalSearch: boolean; - /** Whether language switch widget is enabled */ - languageToggle: boolean; - /** Whether lock screen functionality is enabled */ - lockScreen: boolean; - /** Whether notification widget is displayed */ - notification: boolean; - /** Whether to show the refresh button */ - refresh: boolean; - /** Whether sidebar show/hide widget is displayed */ - sidebarToggle: boolean; - /** Whether theme switch widget is displayed */ - themeToggle: boolean; -} -interface Preferences { - /** Global configuration */ - app: AppPreferences; - /** Header configuration */ - breadcrumb: BreadcrumbPreferences; - /** Copyright configuration */ - copyright: CopyrightPreferences; - /** Footer configuration */ - footer: FooterPreferences; - /** Breadcrumb configuration */ - header: HeaderPreferences; - /** Logo configuration */ - logo: LogoPreferences; - /** Navigation configuration */ - navigation: NavigationPreferences; - /** Shortcut key configuration */ - shortcutKeys: ShortcutKeyPreferences; - /** Sidebar configuration */ - sidebar: SidebarPreferences; - /** Tab bar configuration */ - tabbar: TabbarPreferences; - /** Theme configuration */ - theme: ThemePreferences; - /** Animation configuration */ - transition: TransitionPreferences; - /** Widget configuration */ - widget: WidgetPreferences; -} -``` - -::: - -::: warning Warning - -- The `overridesPreferences` method only needs to override a part of the configurations in the project. There's no need to override configurations that are not needed; they will automatically use the default settings. -- Any configuration item can be overridden. You just need to override it within the `overridesPreferences` method. Do not modify the default configuration file. -- Please clear the cache after changing the configuration, otherwise it may not take effect. - -::: diff --git a/docs/src/en/guide/essentials/styles.md b/docs/src/en/guide/essentials/styles.md deleted file mode 100644 index 16f6681d28d..00000000000 --- a/docs/src/en/guide/essentials/styles.md +++ /dev/null @@ -1,106 +0,0 @@ -# Styles - -::: tip Preface - -For Vue projects, the [official documentation](https://vuejs.org/api/sfc-css-features.html#deep-selectors) already provides a detailed introduction to the syntax. Here, we mainly introduce the structure and usage of style files in the project. - -::: - -## Project Structure - -The style files in the project are stored in `@vben/styles`, which includes some global styles, such as reset styles, global variables, etc. It inherits the styles and capabilities of `@vben-core/design` and can be overridden according to project needs. - -## Scss - -The project uses `scss` as the style preprocessor, allowing the use of `scss` features such as variables, functions, mixins, etc., within the project. - -```vue - -``` - -## Postcss - -If you're not accustomed to using `scss`, you can also use `postcss`, which is a more powerful style processor that supports a wider range of plugins. The project includes the [postcss-nested](https://github.com/postcss/postcss-nested) plugin and is configured with `Css Variables`, making it a complete substitute for `scss`. - -```vue - -``` - -## Tailwind CSS - -The project integrates [Tailwind CSS](https://tailwindcss.com/), allowing the use of `tailwindcss` class names to quickly build pages. - -```vue - -``` - -## BEM Standard - -Another option to avoid style conflicts is to use the `BEM` standard. If you choose `scss`, it is recommended to use the `BEM` naming convention for better style management. The project provides a default `useNamespace` function to easily generate namespaces. - -```vue - - - -``` - -## CSS Modules - -Another solution to address style conflicts is to use the `CSS Modules` modular approach. The usage method is as follows. - -```vue - - - -``` - -For more usage, see the [CSS Modules official documentation](https://vuejs.org/api/sfc-css-features.html#css-modules). diff --git a/docs/src/en/guide/in-depth/access.md b/docs/src/en/guide/in-depth/access.md deleted file mode 100644 index 545dddabd97..00000000000 --- a/docs/src/en/guide/in-depth/access.md +++ /dev/null @@ -1,356 +0,0 @@ ---- -outline: deep ---- - -# Access Control - -The framework has built-in three types of access control methods: - -- Determining whether a menu or button can be accessed based on user roles -- Determining whether a menu or button can be accessed through an API -- Mixed mode: Using both frontend and backend access control simultaneously - -## Frontend Access Control - -**Implementation Principle**: The permissions for routes are hardcoded on the frontend, specifying which permissions are required to view certain routes. Only general routes are initialized, and routes that require permissions are not added to the route table. After logging in or obtaining user roles through other means, the roles are used to traverse the route table to generate a route table that the role can access. This table is then added to the router instance using `router.addRoute`, achieving permission filtering. - -**Disadvantage**: The permissions are relatively inflexible; if the backend changes roles, the frontend needs to be adjusted accordingly. This is suitable for systems with relatively fixed roles. - -### Steps - -- Ensure the current mode is set to frontend access control - -Adjust `preferences.ts` in the corresponding application directory to ensure `accessMode='frontend'`. - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - // Default value, optional - accessMode: 'frontend', - }, -}); -``` - -- Configure route permissions - -#### If not configured, it is visible by default - -```ts {3} - { - meta: { - authority: ['super'], - }, -}, -``` - -- Ensure the roles returned by the interface match the permissions in the route table - -You can look under `src/store/auth` in the application to find the following code: - -```ts -// Set the login user information, ensuring that userInfo.roles is an array and contains permissions from the route table -// For example: userInfo.roles=['super', 'admin'] -authStore.setUserInfo(userInfo); -``` - -At this point, the configuration is complete. You need to ensure that the roles returned by the interface after login match the permissions in the route table; otherwise, access will not be possible. - -### Menu Visible but Access Forbidden - -Sometimes, we need the menu to be visible but access to it forbidden. This can be achieved by setting `menuVisibleWithForbidden` to `true`. In this case, the menu will be visible, but access will be forbidden, redirecting to a 403 page. - -```ts -{ - meta: { - menuVisibleWithForbidden: true, - }, -}, -``` - -## Backend Access Control - -**Implementation Principle**: It is achieved by dynamically generating a routing table through an API, which returns data following a certain structure. The frontend processes this data into a recognizable structure, then adds it to the routing instance using `router.addRoute`, realizing the dynamic generation of permissions. - -**Disadvantage**: The backend needs to provide a data structure that meets the standards, and the frontend needs to process this structure. This is suitable for systems with more complex permissions. - -### Steps - -- Ensure the current mode is set to backend access control - -Adjust `preferences.ts` in the corresponding application directory to ensure `accessMode='backend'`. - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - accessMode: 'backend', - }, -}); -``` - -- Ensure the structure of the menu data returned by the interface is correct - -You can look under `src/router/access.ts` in the application to find the following code: - -```ts -async function generateAccess(options: GenerateMenuAndRoutesOptions) { - return await generateAccessible(preferences.app.accessMode, { - fetchMenuListAsync: async () => { - // This interface is for the menu data returned by the backend - return await getAllMenus(); - }, - }); -} -``` - -- Interface returns menu data, see comments for explanation - -::: details Example of Interface Returning Menu Data - -```ts -const dashboardMenus = [ - { - // Here, 'BasicLayout' is hardcoded and cannot be changed - component: 'BasicLayout', - meta: { - order: -1, - title: 'page.dashboard.title', - }, - name: 'Dashboard', - path: '/', - redirect: '/analytics', - children: [ - { - name: 'Analytics', - path: '/analytics', - // Here is the path of the page, need to remove 'views/' and '.vue' - component: '/dashboard/analytics/index', - meta: { - affixTab: true, - title: 'page.dashboard.analytics', - }, - }, - { - name: 'Workspace', - path: '/workspace', - component: '/dashboard/workspace/index', - meta: { - title: 'page.dashboard.workspace', - }, - }, - ], - }, -]; -``` - -::: - -At this point, the configuration is complete. You need to ensure that after logging in, the format of the menu returned by the interface is correct; otherwise, access will not be possible. - -## Mixed Access Control - -**Implementation Principle**: Mixed mode combines both frontend access control and backend access control methods. The system processes frontend fixed route permissions and backend dynamic menu data in parallel, ultimately merging both parts of routes to provide a more flexible access control solution. - -**Advantages**: Combines the performance advantages of frontend control with the flexibility of backend control, suitable for complex business scenarios requiring permission management. - -### Steps - -- Ensure the current mode is set to mixed access control - -Adjust `preferences.ts` in the corresponding application directory to ensure `accessMode='mixed'`. - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - accessMode: 'mixed', - }, -}); -``` - -- Configure frontend route permissions - -Same as the route permission configuration method in [Frontend Access Control](#frontend-access-control) mode. - -- Configure backend menu interface - -Same as the interface configuration method in [Backend Access Control](#backend-access-control) mode. - -- Ensure roles and permissions match - -Must satisfy both frontend route permission configuration and backend menu data return requirements, ensuring user roles match the permission configurations of both modes. - -At this point, the configuration is complete. Mixed mode will automatically merge frontend and backend routes, providing complete access control functionality. - -## Fine-grained Control of Buttons - -In some cases, we need to control the display of buttons with fine granularity. We can control the display of buttons through interfaces or roles. - -### Permission Code - -The permission code is the code returned by the interface. The logic to determine whether a button is displayed is located under `src/store/auth`: - -```ts -const [fetchUserInfoResult, accessCodes] = await Promise.all([ - fetchUserInfo(), - getAccessCodes(), -]); - -userInfo = fetchUserInfoResult; -authStore.setUserInfo(userInfo); -accessStore.setAccessCodes(accessCodes); -``` - -Locate the `getAccessCodes` corresponding interface, which can be adjusted according to business logic. - -The data structure returned by the permission code is an array of strings, for example: `['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010']` - -With the permission codes, you can use the `AccessControl` component and API provided by `@vben/access` to show and hide buttons. - -#### Component Method - -```vue - - - -``` - -#### API Method - -```vue - - - -``` - -#### Directive Method - -> The directive supports binding single or multiple permission codes. For a single one, you can pass a string or an array containing one permission code, and for multiple permission codes, you can pass an array. - -```vue - -``` - -### Roles - -The method of determining roles does not require permission codes returned by the interface; it directly determines whether buttons are displayed based on roles. - -#### Component Method - -```vue - - - -``` - -#### API Method - -```vue - - - -``` - -#### Directive Method - -> The directive supports binding single or multiple permission codes. For a single one, you can pass a string or an array containing one permission code, and for multiple permission codes, you can pass an array. - -```vue - -``` diff --git a/docs/src/en/guide/in-depth/check-updates.md b/docs/src/en/guide/in-depth/check-updates.md deleted file mode 100644 index 1e5b679dc46..00000000000 --- a/docs/src/en/guide/in-depth/check-updates.md +++ /dev/null @@ -1,48 +0,0 @@ -# Check Updates - -## Introduction - -When there are updates to the website, you might need to check for updates. The framework provides this functionality. By periodically checking for updates, you can configure the `checkUpdatesInterval` and `enableCheckUpdates` fields in your application's preferences.ts file to enable and set the interval for checking updates (in minutes). - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - // Whether to enable check for updates - enableCheckUpdates: true, - // The interval for checking updates, in minutes - checkUpdatesInterval: 1, - }, -}); -``` - -## Effect - -When an update is detected, a prompt will pop up asking the user whether to refresh the page: - -![check-updates](/guide/update-notice.png) - -## Replacing with Other Update Checking Methods - -If you need to check for updates in other ways, such as through an API to more flexibly control the update logic (such as force refresh, display update content, etc.), you can do so by modifying the `src/widgets/check-updates/check-updates.vue` file under `@vben/layouts`. - -```ts -// Replace this with your update checking logic -async function getVersionTag() { - try { - const response = await fetch('https://github.com/', { - cache: 'no-cache', - method: 'HEAD', - }); - - return ( - response.headers.get('etag') || response.headers.get('last-modified') - ); - } catch { - console.error('Failed to fetch version tag'); - return null; - } -} -``` diff --git a/docs/src/en/guide/in-depth/features.md b/docs/src/en/guide/in-depth/features.md deleted file mode 100644 index 24fecea96d2..00000000000 --- a/docs/src/en/guide/in-depth/features.md +++ /dev/null @@ -1,84 +0,0 @@ -# Common Features - -A collection of some commonly used features. - -## Login Authentication Expiry - -When the interface returns a `401` status code, the framework will consider the login authentication to have expired. Upon login timeout, it will redirect to the login page or open a login popup. This can be configured in `preferences.ts` in the application directory: - -### Redirect to Login Page - -Upon login timeout, it will redirect to the login page. - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - loginExpiredMode: 'page', - }, -}); -``` - -### Open Login Popup - -When login times out, a login popup will open. - -![login-expired](/guide/login-expired.png) - -Configuration: - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - loginExpiredMode: 'modal', - }, -}); -``` - -## Dynamic Title - -- Default value: `true` - -When enabled, the webpage title changes according to the route's `title`. You can enable or disable this in the `preferences.ts` file in your application directory. - -```ts -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - dynamicTitle: true, - }, -}); -``` - -## Page Watermark - -- Default value: `false` - -When enabled, the webpage will display a watermark. You can enable or disable this in the `preferences.ts` file in your application directory. - -```ts -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - watermark: true, - }, -}); -``` - -If you want to update the content of the watermark, you can do so. The parameters can be referred to [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/): - -```ts -import { useWatermark } from '@vben/hooks'; - -const { destroyWatermark, updateWatermark } = useWatermark(); - -await updateWatermark({ - // watermark content - content: 'hello my watermark', -}); -``` diff --git a/docs/src/en/guide/in-depth/layout.md b/docs/src/en/guide/in-depth/layout.md deleted file mode 100644 index 412c1cf07a3..00000000000 --- a/docs/src/en/guide/in-depth/layout.md +++ /dev/null @@ -1 +0,0 @@ -# Layout diff --git a/docs/src/en/guide/in-depth/loading.md b/docs/src/en/guide/in-depth/loading.md deleted file mode 100644 index 0f1cff6eea3..00000000000 --- a/docs/src/en/guide/in-depth/loading.md +++ /dev/null @@ -1,44 +0,0 @@ -# Global Loading - -Global loading refers to the loading effect that appears when the page is refreshed, usually a spinning icon: - -![Global loading spinner](/guide/loading.png) - -## Principle - -Implemented by the `vite-plugin-inject-app-loading` plugin, the plugin injects a global `loading html` into each application. - -## Disable - -If you do not need global loading, you can disable it in the `.env` file: - -```bash -VITE_INJECT_APP_LOADING=false -``` - -## Customization - -If you want to customize the global loading, you can create a `loading.html` file in the application directory, at the same level as `index.html`. The plugin will automatically read and inject this HTML. You can define the style and animation of this HTML as you wish. - -::: tip - -- You can use the same syntax as in `index.html`, such as the `VITE_APP_TITLE` variable, to get the application's title. -- You must ensure there is an element with `id="__app-loading__"`. -- Add a `hidden` class to the element with `id="__app-loading__"`. -- You must ensure there is a `style[data-app-loading="inject-css"]` element. - -```html{1,4} - -
- -
<%= VITE_APP_TITLE %>
-
-``` diff --git a/docs/src/en/guide/in-depth/locale.md b/docs/src/en/guide/in-depth/locale.md deleted file mode 100644 index 549bc83f512..00000000000 --- a/docs/src/en/guide/in-depth/locale.md +++ /dev/null @@ -1,227 +0,0 @@ -# Internationalization - -The project has integrated [Vue i18n](https://kazupon.github.io/vue-i18n/), and Chinese and English language packs have been configured. - -## IDE Plugin - -If you are using vscode as your development tool, it is recommended to install the [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) plugin. It can help you manage internationalization copy more conveniently. After installing this plugin, you can see the corresponding language content in your code in real-time: - -![](/public/guide/locale.png) - -## Configure Default Language - -You just need to override the default preferences. In the corresponding application, find the `src/preferences.ts` file and modify the value of `locale`: - -```ts {3} -export const overridesPreferences = defineOverridesPreferences({ - app: { - locale: 'en-US', - }, -}); -``` - -## Dynamic Language Switching - -Switching languages consists of two parts: - -- Updating preferences -- Loading the corresponding language pack - -```ts -import type { SupportedLanguagesType } from '@vben/locales'; -import { loadLocaleMessages } from '@vben/locales'; -import { updatePreferences } from '@vben/preferences'; - -async function updateLocale(value: string) { - // 1. Update preferences - const locale = value as SupportedLanguagesType; - updatePreferences({ - app: { - locale, - }, - }); - // 2. Load the corresponding language pack - await loadLocaleMessages(locale); -} - -updateLocale('en-US'); -``` - -## Adding Translation Texts - -::: warning Attention - -- Do not place business translation texts inside `@vben/locales` to better manage business and general translation texts. -- When adding new translation texts and multiple language packs are available, ensure to add the corresponding texts in all language packs. - -::: - -To add new translation texts, simply find `src/locales/langs/` in the corresponding application and add the texts accordingly, for example: - -**src/locales/langs/zh-CN/\*.json** - -````ts -```json -{ - "about": { - "desc": "Vben Admin 是一个现代的管理模版。" - } -} -```` - -**src/locales/langs/en-US.ts** - -````ts -```json -{ - "about": { - "desc": "Vben Admin is a modern management template." - } -} -```` - -## Using Translation Texts - -With `@vben/locales`, you can easily use translation texts: - -### In Code - -```vue - - -``` - -## Adding a New Language Pack - -If you need to add a new language pack, follow these steps: - -- Add the corresponding language pack file in the `packages/locales/langs` directory, for example, `zh-TW.json`, and translate the respective texts. -- In the corresponding application, locate the `src/locales/langs` file and add the new language pack `zh-TW.json`. -- Add the corresponding language in `packages/constants/src/core.ts`: - - ```ts - export interface LanguageOption { - label: string; - value: 'en-US' | 'zh-CN'; // [!code --] - value: 'en-US' | 'zh-CN' | 'zh-TW'; // [!code ++] - } - export const SUPPORT_LANGUAGES: LanguageOption[] = [ - { - label: '简体中文', - value: 'zh-CN', - }, - { - label: 'English', - value: 'en-US', - }, - { - label: '繁体中文', // [!code ++] - value: 'zh-TW', // [!code ++] - }, - ]; - ``` - -- In `packages/locales/typing.ts`, add a new TypeScript type: - - ```ts - export type SupportedLanguagesType = 'en-US' | 'zh-CN'; // [!code --] - export type SupportedLanguagesType = 'en-US' | 'zh-CN' | 'zh-TW'; // [!code ++] - ``` - -At this point, you can use the newly added language pack in the project. - -## Interface Language Switching Function - -If you want to disable the language switching display button on the interface, in the corresponding application, find the `src/preferences.ts` file and modify the value of `locale` accordingly: - -```ts {3} -export const overridesPreferences = defineOverridesPreferences({ - widget: { - languageToggle: false, - }, -}); -``` - -## Remote Loading of Language Packs - -::: tip Tip - -When making interface requests through the project's built-in `request` tool, the default request header will include [Accept-Language](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language), allowing the server to dynamically internationalize data based on the request header. - -::: - -Each application has an independent language pack that can override the general language configuration. You can remotely load the corresponding language pack by finding the `src/locales/index.ts` file in the corresponding application and modifying the `loadMessages` method accordingly: - -```ts {3-4} -async function loadMessages(lang: SupportedLanguagesType) { - const [appLocaleMessages] = await Promise.all([ - // Modify here to load data via a remote interface - localesMap[lang](), - loadThirdPartyMessage(lang), - ]); - return appLocaleMessages.default; -} -``` - -## Third-Party Language Packs - -Different applications may use third-party component libraries or plugins with varying internationalization methods, so they need to be handled differently. If you need to introduce a third-party language pack, you can find the `src/locales/index.ts` file in the corresponding application and modify the `loadThirdPartyMessage` method accordingly: - -```ts -/** - * Load the dayjs language pack - * @param lang - */ -async function loadDayjsLocale(lang: SupportedLanguagesType) { - let locale; - switch (lang) { - case 'zh-CN': { - locale = await import('dayjs/locale/zh-cn'); - break; - } - case 'en-US': { - locale = await import('dayjs/locale/en'); - break; - } - // Default to using English - default: { - locale = await import('dayjs/locale/en'); - } - } - if (locale) { - dayjs.locale(locale); - } else { - console.error(`Failed to load dayjs locale for ${lang}`); - } -} -``` - -## Removing Internationalization - -Firstly, it is not recommended to remove internationalization, as it is a good development practice. However, if you really need to remove it, you can directly use Chinese copy and then retain the project's built-in language pack, which will not affect the overall development experience. The steps to remove internationalization are as follows: - -- Hide the language switching button on the interface, see: [Interface Language Switching Function](#interface-language-switching-function) -- Modify the default language, see: [Configure Default Language](#configure-default-language) -- Disable `vue-i18n` warning prompts, in the `src/locales/index.ts` file, modify `missingWarn` to `false`: - - ```ts - async function setupI18n(app: App, options: LocaleSetupOptions = {}) { - await coreSetup(app, { - defaultLocale: preferences.app.locale, - loadMessages, - missingWarn: !import.meta.env.PROD, // [!code --] - missingWarn: false, // [!code ++] - ...options, - }); - } - ``` diff --git a/docs/src/en/guide/in-depth/login.md b/docs/src/en/guide/in-depth/login.md deleted file mode 100644 index 7fdac2c6f4d..00000000000 --- a/docs/src/en/guide/in-depth/login.md +++ /dev/null @@ -1,119 +0,0 @@ -# Login - -This document explains how to customize the login page of your application. - -## Login Page Adjustment - -If you want to adjust the title, description, icon, and toolbar of the login page, you can do so by configuring the `props` parameter of the `AuthPageLayout` component. - -![login](/guide/login.png) - -You just need to configure the `props` parameter of `AuthPageLayout` in `src/router/routes/core.ts` within your application: - -```ts {4-8} - { - component: AuthPageLayout, - props: { - sloganImage: "xxx/xxx.png", - pageTitle: "开箱即用的大型中后台管理系统", - pageDescription: "工程化、高性能、跨组件库的前端模版", - toolbar: true, - toolbarList: ['color', 'language', 'layout', 'theme'], - } - // ... - }, -``` - -::: tip - -If these configurations do not meet your needs, you can implement your own login page. Simply implement your own `AuthPageLayout`. - -::: - -## Login Form Adjustment - -If you want to adjust the content of the login form, you can configure the `AuthenticationLogin` component parameters in `src/views/_core/authentication/login.vue` within your application: - -```vue - -``` - -::: details AuthenticationLogin Component Props - -```ts -{ - /** - * @en Verification code login path - */ - codeLoginPath?: string; - /** - * @en Forget password path - */ - forgetPasswordPath?: string; - - /** - * @en Whether it is in loading state - */ - loading?: boolean; - - /** - * @en QR code login path - */ - qrCodeLoginPath?: string; - - /** - * @en Registration path - */ - registerPath?: string; - - /** - * @en Whether to show verification code login - */ - showCodeLogin?: boolean; - /** - * @en Whether to show forget password - */ - showForgetPassword?: boolean; - - /** - * @en Whether to show QR code login - */ - showQrcodeLogin?: boolean; - - /** - * @en Whether to show registration button - */ - showRegister?: boolean; - - /** - * @en Whether to show remember account - */ - showRememberMe?: boolean; - - /** - * @en Whether to show third-party login - */ - showThirdPartyLogin?: boolean; - - /** - * @en Login box subtitle - */ - subTitle?: string; - - /** - * @en Login box title - */ - title?: string; -} -``` - -::: - -::: tip - -If these configurations do not meet your needs, you can implement your own login form and related login logic. - -::: diff --git a/docs/src/en/guide/in-depth/theme.md b/docs/src/en/guide/in-depth/theme.md deleted file mode 100644 index 164ac1797c3..00000000000 --- a/docs/src/en/guide/in-depth/theme.md +++ /dev/null @@ -1,1295 +0,0 @@ -# Theme - -The framework is built on [shadcn-vue](https://www.shadcn-vue.com/themes.html) and [tailwindcss](https://tailwindcss.com/), offering a rich theme configuration. You can easily switch between various themes through simple configuration to meet personalized needs. You can choose to use CSS variables or Tailwind CSS utility classes for theme settings. - -## CSS Variables - -The project follows the theme configuration of [shadcn-vue](https://www.shadcn-vue.com/themes.html), for example: - -```html -
-``` - -We use a simple convention for colors. The `background` variable is used for the background color of components, and the `foreground` variable is used for text color. - -For the following components, `background` will be `hsl(var(--primary))`, and `foreground` will be `hsl(var(--primary-foreground))`. - -## Detailed List of CSS Variables - -::: warning Note - -The colors inside CSS variables must use the `hsl` format, such as `0 0% 100%`, without adding `hsl()` and `,`. - -::: - -You can check the list below to understand all the available variables. - -::: details Default theme CSS variables - -```css -:root { - --font-family: - -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, 'Helvetica Neue', - arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', - 'Segoe UI Symbol', 'Noto Color Emoji'; - - /* Default background color of ...etc */ - --background: 0 0% 100%; - - /* Main area background color */ - --background-deep: 216 20.11% 95.47%; - --foreground: 210 6% 21%; - - /* Background color for */ - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - /* Background color for popovers such as , , */ - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - /* Muted backgrounds such as , and */ - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - /* Theme Colors */ - - --primary: 212 100% 45%; - --primary-foreground: 0 0% 98%; - - /* Used for destructive actions such as
; + } else { + return content; + } +} + +/** + * @description: Create confirmation box + */ +function createConfirm(options: ModalOptionsEx) { + const iconType = options.iconType || 'warning'; + Reflect.deleteProperty(options, 'iconType'); + const opt: ModalFuncProps = { + centered: true, + icon: getIcon(iconType), + ...options, + content: renderContent(options), + }; + return Modal.confirm(opt); +} + +const getBaseOptions = () => { + const { t } = useI18n(); + return { + okText: t('common.okText'), + centered: true, + }; +}; + +function createModalOptions(options: ModalOptionsPartial, icon: string): ModalOptionsPartial { + return { + ...getBaseOptions(), + ...options, + content: renderContent(options), + icon: getIcon(icon), + }; +} + +function createSuccessModal(options: ModalOptionsPartial) { + return Modal.success(createModalOptions(options, 'success')); +} + +function createErrorModal(options: ModalOptionsPartial) { + return Modal.error(createModalOptions(options, 'error')); +} + +function createInfoModal(options: ModalOptionsPartial) { + return Modal.info(createModalOptions(options, 'info')); +} + +function createWarningModal(options: ModalOptionsPartial) { + return Modal.warning(createModalOptions(options, 'warning')); +} + +notification.config({ + placement: 'topRight', + duration: 3, +}); + +/** + * @description: message + */ +export function useMessage() { + return { + createMessage: Message, + notification: notification as NotifyApi, + createConfirm, + createSuccessModal, + createErrorModal, + createInfoModal, + createWarningModal, + }; +} diff --git a/src/hooks/web/usePage.ts b/src/hooks/web/usePage.ts new file mode 100644 index 00000000000..0d60d4509c7 --- /dev/null +++ b/src/hooks/web/usePage.ts @@ -0,0 +1,106 @@ +import type { RouteLocationRaw, Router } from 'vue-router'; + +import { PageEnum } from '@/enums/pageEnum'; +import { unref } from 'vue'; + +import { useRouter } from 'vue-router'; +import { REDIRECT_NAME } from '@/router/constant'; +import { isHttpUrl } from '@/utils/is'; +import { openWindow } from '@/utils'; + +import { useMultipleTabStore } from '@/store/modules/multipleTab'; + +export type PathAsPageEnum = T extends { path: string } ? T & { path: PageEnum } : T; +export type RouteLocationRawEx = PathAsPageEnum; + +function handleError(e: Error) { + console.error(e); +} + +export enum GoType { + 'replace', + 'after', +} + +/** + * page switch + */ +export function useGo(_router?: Router) { + const { push, replace, currentRoute } = _router || useRouter(); + + function go(opt?: RouteLocationRawEx): void; + function go(opt: RouteLocationRawEx, isReplace: boolean): void; + function go(opt: RouteLocationRawEx, goType: GoType): void; + function go( + opt: RouteLocationRawEx = PageEnum.BASE_HOME, + goTypeOrIsReplace: boolean | GoType = false, + ) { + if (!opt) { + return; + } + let path = unref(opt) as string; + if (path[0] === '/') { + path = path.slice(1); + } + if (isHttpUrl(path)) { + return openWindow(path); + } + + const isReplace = goTypeOrIsReplace === true || goTypeOrIsReplace === GoType.replace; + const isAfter = goTypeOrIsReplace === GoType.after; + + if (isReplace) { + replace(opt).catch(handleError); + } else if (isAfter) { + const tabStore = useMultipleTabStore(); + const currentName = unref(currentRoute).name; + // 当前 tab + const currentIndex = tabStore.getTabList.findIndex((item) => item.name === currentName); + // 当前 tab 数量 + const currentCount = tabStore.getTabList.length; + push(opt) + .then(() => { + if (tabStore.getTabList.length > currentCount) { + // 产生新 tab + // 新 tab(也是最后一个) + const targetIndex = tabStore.getTabList.length - 1; + // 新 tab 在 当前 tab 的后面 + if (currentIndex > -1 && targetIndex > currentIndex) { + // 移动 tab + tabStore.sortTabs(targetIndex, currentIndex + 1); + } + } + }) + .catch(handleError); + } else { + push(opt).catch(handleError); + } + } + return go; +} + +/** + * @description: redo current page + */ +export const useRedo = (_router?: Router) => { + const { replace, currentRoute } = _router || useRouter(); + const { query, params = {}, name, fullPath } = unref(currentRoute.value); + function redo(): Promise { + return new Promise((resolve) => { + if (name === REDIRECT_NAME) { + resolve(false); + return; + } + if (name && Object.keys(params).length > 0) { + params['_origin_params'] = JSON.stringify(params ?? {}); + params['_redirect_type'] = 'name'; + params['path'] = String(name); + } else { + params['_redirect_type'] = 'path'; + params['path'] = fullPath; + } + replace({ name: REDIRECT_NAME, params, query }).then(() => resolve(true)); + }); + } + return redo; +}; diff --git a/src/hooks/web/usePagination.ts b/src/hooks/web/usePagination.ts new file mode 100644 index 00000000000..1e199139e5a --- /dev/null +++ b/src/hooks/web/usePagination.ts @@ -0,0 +1,34 @@ +import type { Ref } from 'vue'; +import { ref, unref, computed } from 'vue'; + +function pagination(list: T[], pageNo: number, pageSize: number): T[] { + const offset = (pageNo - 1) * Number(pageSize); + const ret = + offset + Number(pageSize) >= list.length + ? list.slice(offset, list.length) + : list.slice(offset, offset + Number(pageSize)); + return ret; +} + +export function usePagination(list: Ref, pageSize: number) { + const currentPage = ref(1); + const pageSizeRef = ref(pageSize); + + const getPaginationList = computed(() => { + return pagination(unref(list), unref(currentPage), unref(pageSizeRef)); + }); + + const getTotal = computed(() => { + return unref(list).length; + }); + + function setCurrentPage(page: number) { + currentPage.value = page; + } + + function setPageSize(pageSize: number) { + pageSizeRef.value = pageSize; + } + + return { setCurrentPage, getTotal, setPageSize, getPaginationList }; +} diff --git a/src/hooks/web/usePermission.ts b/src/hooks/web/usePermission.ts new file mode 100644 index 00000000000..7853cf7abcd --- /dev/null +++ b/src/hooks/web/usePermission.ts @@ -0,0 +1,121 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { useAppStore } from '@/store/modules/app'; +import { usePermissionStore } from '@/store/modules/permission'; +import { useUserStore } from '@/store/modules/user'; + +import { useTabs } from './useTabs'; + +import { router, resetRouter } from '@/router'; +// import { RootRoute } from '@/router/routes'; + +import projectSetting from '@/settings/projectSetting'; +import { PermissionModeEnum } from '@/enums/appEnum'; +import { RoleEnum } from '@/enums/roleEnum'; + +import { intersection } from 'lodash-es'; +import { isArray } from '@/utils/is'; +import { useMultipleTabStore } from '@/store/modules/multipleTab'; + +// User permissions related operations +export function usePermission() { + const userStore = useUserStore(); + const appStore = useAppStore(); + const permissionStore = usePermissionStore(); + const { closeAll } = useTabs(router); + + /** + * Change permission mode + */ + async function togglePermissionMode() { + appStore.setProjectConfig({ + permissionMode: + appStore.projectConfig?.permissionMode === PermissionModeEnum.BACK + ? PermissionModeEnum.ROUTE_MAPPING + : PermissionModeEnum.BACK, + }); + location.reload(); + } + + /** + * Reset and regain authority resource information + * 重置和重新获得权限资源信息 + */ + async function resume() { + const tabStore = useMultipleTabStore(); + tabStore.clearCacheTabs(); + resetRouter(); + + // 动态加载路由(再次) + const routes = await permissionStore.buildRoutesAction(); + routes.forEach((route) => { + router.addRoute(route as unknown as RouteRecordRaw); + }); + + permissionStore.setLastBuildMenuTime(); + closeAll(); + } + + /** + * Determine whether there is permission + */ + function hasPermission(value?: RoleEnum | RoleEnum[] | string | string[], def = true): boolean { + // Visible by default + if (!value) { + return def; + } + + const permMode = appStore.getProjectConfig.permissionMode; + + if ([PermissionModeEnum.ROUTE_MAPPING, PermissionModeEnum.ROLE].includes(permMode)) { + if (!isArray(value)) { + return userStore.getRoleList?.includes(value as RoleEnum); + } + return (intersection(value, userStore.getRoleList) as RoleEnum[]).length > 0; + } + + if (PermissionModeEnum.BACK === permMode) { + const allCodeList = permissionStore.getPermCodeList as string[]; + if (!isArray(value)) { + const splits = ['||', '&&']; + const splitName = splits.find((item) => value.includes(item)); + if (splitName) { + const splitCodes = value.split(splitName); + return splitName === splits[0] + ? intersection(splitCodes, allCodeList).length > 0 + : intersection(splitCodes, allCodeList).length === splitCodes.length; + } + return allCodeList.includes(value); + } + return (intersection(value, allCodeList) as string[]).length > 0; + } + return true; + } + + /** + * Change roles + * @param roles + */ + async function changeRole(roles: RoleEnum | RoleEnum[]): Promise { + if (projectSetting.permissionMode !== PermissionModeEnum.ROUTE_MAPPING) { + throw new Error( + 'Please switch PermissionModeEnum to ROUTE_MAPPING mode in the configuration to operate!', + ); + } + + if (!isArray(roles)) { + roles = [roles]; + } + userStore.setRoleList(roles); + await resume(); + } + + /** + * refresh menu data + */ + async function refreshMenu() { + resume(); + } + + return { changeRole, hasPermission, togglePermissionMode, refreshMenu }; +} diff --git a/src/hooks/web/useScript.ts b/src/hooks/web/useScript.ts new file mode 100644 index 00000000000..9707116a27e --- /dev/null +++ b/src/hooks/web/useScript.ts @@ -0,0 +1,46 @@ +import { onMounted, onUnmounted, ref } from 'vue'; + +interface ScriptOptions { + src: string; +} + +export function useScript(opts: ScriptOptions) { + const isLoading = ref(false); + const error = ref(false); + const success = ref(false); + let script: HTMLScriptElement; + + const promise = new Promise((resolve, reject) => { + onMounted(() => { + script = document.createElement('script'); + script.type = 'text/javascript'; + script.onload = function () { + isLoading.value = false; + success.value = true; + error.value = false; + resolve(''); + }; + + script.onerror = function (err) { + isLoading.value = false; + success.value = false; + error.value = true; + reject(err); + }; + + script.src = opts.src; + document.head.appendChild(script); + }); + }); + + onUnmounted(() => { + script && script.remove(); + }); + + return { + isLoading, + error, + success, + toPromise: () => promise, + }; +} diff --git a/src/hooks/web/useSortable.ts b/src/hooks/web/useSortable.ts new file mode 100644 index 00000000000..ab7da703bf2 --- /dev/null +++ b/src/hooks/web/useSortable.ts @@ -0,0 +1,23 @@ +import { nextTick, unref } from 'vue'; +import type { Ref } from 'vue'; +import type { Options } from 'sortablejs'; + +export function useSortable(el?: HTMLElement | Ref, options?: Options) { + function initSortable() { + nextTick(async () => { + el = unref(el); + + if (!el) return; + + const Sortable = (await import('sortablejs')).default; + Sortable.create(el, { + animation: 100, + delay: 400, + delayOnTouchOnly: true, + ...options, + }); + }); + } + + return { initSortable }; +} diff --git a/src/hooks/web/useTabs.ts b/src/hooks/web/useTabs.ts new file mode 100644 index 00000000000..ab13e459abe --- /dev/null +++ b/src/hooks/web/useTabs.ts @@ -0,0 +1,103 @@ +import type { RouteLocationNormalized, Router } from 'vue-router'; + +import { useRouter } from 'vue-router'; +import { unref } from 'vue'; + +import { useMultipleTabStore } from '@/store/modules/multipleTab'; +import { useAppStore } from '@/store/modules/app'; + +enum TableActionEnum { + REFRESH, + CLOSE_ALL, + CLOSE_LEFT, + CLOSE_RIGHT, + CLOSE_OTHER, + CLOSE_CURRENT, + CLOSE, +} + +export function useTabs(_router?: Router) { + const appStore = useAppStore(); + + function canIUseTabs(): boolean { + const { show } = appStore.getMultiTabsSetting; + if (!show) { + throw new Error('The multi-tab page is currently not open, please open it in the settings!'); + } + return !!show; + } + + const tabStore = useMultipleTabStore(); + const router = _router || useRouter(); + + const { currentRoute } = router; + + function getCurrentTab() { + const route = unref(currentRoute); + return tabStore.getTabList.find((item) => item.fullPath === route.fullPath)!; + } + + async function updateTabTitle(title: string, tab?: RouteLocationNormalized) { + const canIUse = canIUseTabs; + if (!canIUse) { + return; + } + const targetTab = tab || getCurrentTab(); + await tabStore.setTabTitle(title, targetTab); + } + + async function updateTabPath(path: string, tab?: RouteLocationNormalized) { + const canIUse = canIUseTabs; + if (!canIUse) { + return; + } + const targetTab = tab || getCurrentTab(); + await tabStore.updateTabPath(path, targetTab); + } + + async function handleTabAction(action: TableActionEnum, tab?: RouteLocationNormalized) { + const canIUse = canIUseTabs; + if (!canIUse) { + return; + } + const currentTab = getCurrentTab(); + switch (action) { + case TableActionEnum.REFRESH: + await tabStore.refreshPage(router); + break; + + case TableActionEnum.CLOSE_ALL: + await tabStore.closeAllTab(router); + break; + + case TableActionEnum.CLOSE_LEFT: + await tabStore.closeLeftTabs(currentTab, router); + break; + + case TableActionEnum.CLOSE_RIGHT: + await tabStore.closeRightTabs(currentTab, router); + break; + + case TableActionEnum.CLOSE_OTHER: + await tabStore.closeOtherTabs(currentTab, router); + break; + + case TableActionEnum.CLOSE_CURRENT: + case TableActionEnum.CLOSE: + await tabStore.closeTab(tab || currentTab, router); + break; + } + } + + return { + refreshPage: () => handleTabAction(TableActionEnum.REFRESH), + closeAll: () => handleTabAction(TableActionEnum.CLOSE_ALL), + closeLeft: () => handleTabAction(TableActionEnum.CLOSE_LEFT), + closeRight: () => handleTabAction(TableActionEnum.CLOSE_RIGHT), + closeOther: () => handleTabAction(TableActionEnum.CLOSE_OTHER), + closeCurrent: () => handleTabAction(TableActionEnum.CLOSE_CURRENT), + close: (tab?: RouteLocationNormalized) => handleTabAction(TableActionEnum.CLOSE, tab), + setTitle: (title: string, tab?: RouteLocationNormalized) => updateTabTitle(title, tab), + updatePath: (fullPath: string, tab?: RouteLocationNormalized) => updateTabPath(fullPath, tab), + }; +} diff --git a/src/hooks/web/useTitle.ts b/src/hooks/web/useTitle.ts new file mode 100644 index 00000000000..e3a7653bc9a --- /dev/null +++ b/src/hooks/web/useTitle.ts @@ -0,0 +1,34 @@ +import { watch, unref } from 'vue'; +import { useI18n } from '@/hooks/web/useI18n'; +import { useTitle as usePageTitle } from '@vueuse/core'; +import { useGlobSetting } from '@/hooks/setting'; +import { useRouter } from 'vue-router'; +import { useLocaleStore } from '@/store/modules/locale'; +import { REDIRECT_NAME } from '@/router/constant'; + +/** + * Listening to page changes and dynamically changing site titles + */ +export function useTitle() { + const { title } = useGlobSetting(); + const { t } = useI18n(); + const { currentRoute } = useRouter(); + const localeStore = useLocaleStore(); + + const pageTitle = usePageTitle(); + + watch( + [() => currentRoute.value.path, () => localeStore.getLocale], + () => { + const route = unref(currentRoute); + + if (route.name === REDIRECT_NAME) { + return; + } + + const tTitle = t(route?.meta?.title as string); + pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`; + }, + { immediate: true }, + ); +} diff --git a/src/hooks/web/useWatermark.ts b/src/hooks/web/useWatermark.ts new file mode 100644 index 00000000000..671cedfa993 --- /dev/null +++ b/src/hooks/web/useWatermark.ts @@ -0,0 +1,209 @@ +import { getCurrentInstance, onBeforeUnmount, ref, Ref, shallowRef, unref } from 'vue'; +import { useRafThrottle } from '@/utils/domUtils'; +import { addResizeListener, removeResizeListener } from '@/utils/event'; +import { isDef } from '@/utils/is'; + +const watermarkSymbol = 'watermark-dom'; +const updateWatermarkText = ref(null); + +type UseWatermarkRes = { + setWatermark: (str: string) => void; + clear: () => void; + clearAll: () => void; + waterMarkOptions?: waterMarkOptionsType; + obInstance?: MutationObserver; + targetElement?: HTMLElement; + parentElement?: HTMLElement; +}; + +type waterMarkOptionsType = { + // 自定义水印的文字大小 + fontSize?: number; + // 自定义水印的文字颜色 + fontColor?: string; + // 自定义水印的文字字体 + fontFamily?: string; + // 自定义水印的文字对齐方式 + textAlign?: CanvasTextAlign; + // 自定义水印的文字基线 + textBaseline?: CanvasTextBaseline; + // 自定义水印的文字倾斜角度 + rotate?: number; +}; + +const sourceMap = new Map>(); + +function findTargetNode(el) { + return Array.from(sourceMap.values()).find((item) => item.targetElement === el); +} + +function createBase64(str: string, waterMarkOptions: waterMarkOptionsType) { + const can = document.createElement('canvas'); + const width = 300; + const height = 240; + Object.assign(can, { width, height }); + + const cans = can.getContext('2d'); + if (cans) { + const fontFamily = waterMarkOptions?.fontFamily || 'Vedana'; + const fontSize = waterMarkOptions?.fontSize || 15; + const fontColor = waterMarkOptions?.fontColor || 'rgba(0, 0, 0, 0.15)'; + const textAlign = waterMarkOptions?.textAlign || 'left'; + const textBaseline = waterMarkOptions?.textBaseline || 'middle'; + const rotate = waterMarkOptions?.rotate || 20; + cans.rotate((-rotate * Math.PI) / 180); + cans.font = `${fontSize}px ${fontFamily}`; + cans.fillStyle = fontColor; + cans.textAlign = textAlign; + cans.textBaseline = textBaseline; + cans.fillText(str, width / 20, height); + } + return can.toDataURL('image/png'); +} +const resetWatermarkStyle = ( + element: HTMLElement, + watermarkText: string, + waterMarkOptions: waterMarkOptionsType, +) => { + element.className = '__' + watermarkSymbol; + element.style.pointerEvents = 'none'; + element.style.display = 'block'; + element.style.visibility = 'visible'; + element.style.top = '0px'; + element.style.left = '0px'; + element.style.position = 'absolute'; + element.style.zIndex = '100000'; + element.style.height = '100%'; + element.style.width = '100%'; + element.style.background = `url(${createBase64( + unref(updateWatermarkText) || watermarkText, + waterMarkOptions, + )}) left top repeat`; +}; + +const obFn = () => { + const obInstance = new MutationObserver((mutationRecords) => { + for (const mutation of mutationRecords) { + for (const node of Array.from(mutation.removedNodes)) { + const target = findTargetNode(node); + if (!target) return; + const { targetElement, parentElement } = target; + // 父元素的子元素水印如果被删除 重新插入被删除的水印(防篡改,插入通过控制台删除的水印) + if (!parentElement?.contains(targetElement as Node | null)) { + target?.parentElement?.appendChild(node as HTMLElement); + } + } + if (mutation.type === 'attributes' && mutation.target) { + // 修复控制台可以”Hide element” 的问题 + const _target = mutation.target as HTMLElement; + const target = findTargetNode(_target); + if (target) { + // 禁止改属性 包括class 修改以后 mutation.type 也等于 'attributes' + // 先解除监听 再加一下 + clearAll(); + target.setWatermark(target.targetElement?.['data-watermark-text']); + } + } + } + }); + return obInstance; +}; + +export function useWatermark( + appendEl: Ref = ref(document.body) as Ref, + waterMarkOptions: waterMarkOptionsType = {}, +): UseWatermarkRes { + const domSymbol = Symbol(watermarkSymbol); + const appendElRaw = unref(appendEl); + if (appendElRaw && sourceMap.has(domSymbol)) { + const { setWatermark, clear } = sourceMap.get(domSymbol) as UseWatermarkRes; + return { setWatermark, clear, clearAll }; + } + const func = useRafThrottle(function () { + const el = unref(appendEl); + if (!el) return; + const { clientHeight: height, clientWidth: width } = el; + updateWatermark({ height, width }); + }); + const watermarkEl = shallowRef(); + const clear = () => { + const domId = unref(watermarkEl); + watermarkEl.value = undefined; + const el = unref(appendEl); + sourceMap.has(domSymbol) && sourceMap.get(domSymbol)?.obInstance?.disconnect(); + sourceMap.delete(domSymbol); + if (!el) return; + domId && el.removeChild(domId); + removeResizeListener(el, func); + }; + + function updateWatermark( + options: { + width?: number; + height?: number; + str?: string; + } = {}, + ) { + const el = unref(watermarkEl); + if (!el) return; + if (isDef(options.width)) { + el.style.width = `${options.width}px`; + } + if (isDef(options.height)) { + el.style.height = `${options.height}px`; + } + if (isDef(options.str)) { + el.style.background = `url(https://github.com/$%7BcreateBase64(options.str,%20waterMarkOptions)}) left top repeat`; + } + } + + const createWatermark = (str: string) => { + if (unref(watermarkEl) && sourceMap.has(domSymbol)) { + updateWatermarkText.value = str; + updateWatermark({ str }); + return; + } + const div = document.createElement('div'); + div['data-watermark-text'] = str; //自定义属性 用于恢复水印 + updateWatermarkText.value = str; + watermarkEl.value = div; + resetWatermarkStyle(div, str, waterMarkOptions); + const el = unref(appendEl); + if (!el) return; + const { clientHeight: height, clientWidth: width } = el; + updateWatermark({ str, width, height }); + el.appendChild(div); + sourceMap.set(domSymbol, { + setWatermark, + clear, + parentElement: el, + targetElement: div, + obInstance: obFn(), + waterMarkOptions, + }); + sourceMap.get(domSymbol)?.obInstance?.observe(el, { + childList: true, // 子节点的变动(指新增,删除或者更改) + subtree: true, // 该观察器应用于该节点的所有后代节点 + attributes: true, // 属性的变动 + }); + }; + + function setWatermark(str: string) { + createWatermark(str); + addResizeListener(document.documentElement, func); + const instance = getCurrentInstance(); + if (instance) { + onBeforeUnmount(() => { + clear(); + }); + } + } + return { setWatermark, clear, clearAll }; +} + +function clearAll() { + Array.from(sourceMap.values()).forEach((item) => { + item?.obInstance?.disconnect(); + item.clear(); + }); +} diff --git a/src/layouts/default/content/index.vue b/src/layouts/default/content/index.vue new file mode 100644 index 00000000000..68c794f425c --- /dev/null +++ b/src/layouts/default/content/index.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/layouts/default/content/useContentContext.ts b/src/layouts/default/content/useContentContext.ts new file mode 100644 index 00000000000..133c4cf81ef --- /dev/null +++ b/src/layouts/default/content/useContentContext.ts @@ -0,0 +1,17 @@ +import type { InjectionKey, ComputedRef } from 'vue'; +import { createContext, useContext } from '@/hooks/core/useContext'; + +export interface ContentContextProps { + contentHeight: ComputedRef; + setPageHeight: (height: number) => Promise; +} + +const key: InjectionKey = Symbol(); + +export function createContentContext(context: ContentContextProps) { + return createContext(context, key, { native: true }); +} + +export function useContentContext() { + return useContext(key); +} diff --git a/src/layouts/default/content/useContentViewHeight.ts b/src/layouts/default/content/useContentViewHeight.ts new file mode 100644 index 00000000000..0a9ef9e010a --- /dev/null +++ b/src/layouts/default/content/useContentViewHeight.ts @@ -0,0 +1,41 @@ +import { ref, computed, unref } from 'vue'; +import { createPageContext } from '@/hooks/component/usePageContext'; +import { useWindowSizeFn } from '@vben/hooks'; + +const headerHeightRef = ref(0); +const footerHeightRef = ref(0); + +export function useLayoutHeight() { + function setHeaderHeight(val) { + headerHeightRef.value = val; + } + function setFooterHeight(val) { + footerHeightRef.value = val; + } + return { headerHeightRef, footerHeightRef, setHeaderHeight, setFooterHeight }; +} + +export function useContentViewHeight() { + const contentHeight = ref(window.innerHeight); + const pageHeight = ref(window.innerHeight); + const getViewHeight = computed(() => { + return unref(contentHeight) - unref(headerHeightRef) - unref(footerHeightRef) || 0; + }); + + useWindowSizeFn( + () => { + contentHeight.value = window.innerHeight; + }, + { wait: 100, immediate: true }, + ); + + async function setPageHeight(height: number) { + pageHeight.value = height; + } + + createPageContext({ + contentHeight: getViewHeight, + setPageHeight, + pageHeight, + }); +} diff --git a/src/layouts/default/feature/index.vue b/src/layouts/default/feature/index.vue new file mode 100644 index 00000000000..b56d81c5076 --- /dev/null +++ b/src/layouts/default/feature/index.vue @@ -0,0 +1,72 @@ + + + diff --git a/src/layouts/default/footer/index.vue b/src/layouts/default/footer/index.vue new file mode 100644 index 00000000000..689f795d34c --- /dev/null +++ b/src/layouts/default/footer/index.vue @@ -0,0 +1,81 @@ + + + diff --git a/src/layouts/default/header/MultipleHeader.vue b/src/layouts/default/header/MultipleHeader.vue new file mode 100644 index 00000000000..feb2052ad17 --- /dev/null +++ b/src/layouts/default/header/MultipleHeader.vue @@ -0,0 +1,121 @@ + + + diff --git a/src/layouts/default/header/components/Breadcrumb.vue b/src/layouts/default/header/components/Breadcrumb.vue new file mode 100644 index 00000000000..2a0de007785 --- /dev/null +++ b/src/layouts/default/header/components/Breadcrumb.vue @@ -0,0 +1,218 @@ + + + diff --git a/src/layouts/default/header/components/ChangeApi/index.vue b/src/layouts/default/header/components/ChangeApi/index.vue new file mode 100644 index 00000000000..5cbe07dd18d --- /dev/null +++ b/src/layouts/default/header/components/ChangeApi/index.vue @@ -0,0 +1,81 @@ + + diff --git a/src/layouts/default/header/components/ErrorAction.vue b/src/layouts/default/header/components/ErrorAction.vue new file mode 100644 index 00000000000..f865c97621d --- /dev/null +++ b/src/layouts/default/header/components/ErrorAction.vue @@ -0,0 +1,36 @@ + + diff --git a/src/layouts/default/header/components/FullScreen.vue b/src/layouts/default/header/components/FullScreen.vue new file mode 100644 index 00000000000..6fa5d7d4cec --- /dev/null +++ b/src/layouts/default/header/components/FullScreen.vue @@ -0,0 +1,34 @@ + + diff --git a/src/layouts/default/header/components/index.ts b/src/layouts/default/header/components/index.ts new file mode 100644 index 00000000000..6593e58a43e --- /dev/null +++ b/src/layouts/default/header/components/index.ts @@ -0,0 +1,14 @@ +import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'; +import FullScreen from './FullScreen.vue'; + +export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), { + loading: true, +}); + +export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue')); + +export const Notify = createAsyncComponent(() => import('./notify/index.vue')); + +export const ErrorAction = createAsyncComponent(() => import('./ErrorAction.vue')); + +export { FullScreen }; diff --git a/src/layouts/default/header/components/lock/LockModal.vue b/src/layouts/default/header/components/lock/LockModal.vue new file mode 100644 index 00000000000..0920f36cf3d --- /dev/null +++ b/src/layouts/default/header/components/lock/LockModal.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/layouts/default/header/components/notify/NoticeList.vue b/src/layouts/default/header/components/notify/NoticeList.vue new file mode 100644 index 00000000000..51e46336ddb --- /dev/null +++ b/src/layouts/default/header/components/notify/NoticeList.vue @@ -0,0 +1,176 @@ + + + diff --git a/src/layouts/default/header/components/notify/data.ts b/src/layouts/default/header/components/notify/data.ts new file mode 100644 index 00000000000..15d524d7bcc --- /dev/null +++ b/src/layouts/default/header/components/notify/data.ts @@ -0,0 +1,193 @@ +export interface ListItem { + id: string; + avatar: string; + // 通知的标题内容 + title: string; + // 是否在标题上显示删除线 + titleDelete?: boolean; + datetime: string; + type: string; + read?: boolean; + description: string; + clickClose?: boolean; + extra?: string; + color?: string; +} + +export interface TabItem { + key: string; + name: string; + list: ListItem[]; + unreadlist?: ListItem[]; +} + +export const tabListData: TabItem[] = [ + { + key: '1', + name: '通知', + list: [ + { + id: '000000001', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', + title: '你收到了 14 份新周报', + description: '', + datetime: '2017-08-09', + type: '1', + }, + { + id: '000000002', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', + title: '你推荐的 曲妮妮 已通过第三轮面试', + description: '', + datetime: '2017-08-08', + type: '1', + }, + { + id: '000000003', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png', + title: '这种模板可以区分多种通知类型', + description: '', + datetime: '2017-08-07', + // read: true, + type: '1', + }, + { + id: '000000004', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000005', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: + '标题可以设置自动显示省略号,本例中标题行数已设为1行,如果内容超过1行将自动截断并支持tooltip显示完整标题。', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000006', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000007', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000008', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000009', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000010', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + ], + }, + { + key: '2', + name: '消息', + list: [ + { + id: '000000006', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', + title: '曲丽丽 评论了你', + description: '描述信息描述信息描述信息', + datetime: '2017-08-07', + type: '2', + clickClose: true, + }, + { + id: '000000007', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', + title: '朱偏右 回复了你', + description: '这种模板用于提醒谁与你发生了互动', + datetime: '2017-08-07', + type: '2', + clickClose: true, + }, + { + id: '000000008', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', + title: '标题', + description: + '请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容', + datetime: '2017-08-07', + type: '2', + clickClose: true, + }, + ], + }, + { + key: '3', + name: '待办', + list: [ + { + id: '000000009', + avatar: '', + title: '任务名称', + description: '任务需要在 2017-01-12 20:00 前启动', + datetime: '', + extra: '未开始', + color: '', + type: '3', + }, + { + id: '000000010', + avatar: '', + title: '第三方紧急代码变更', + description: '冠霖 需在 2017-01-07 前完成代码变更任务', + datetime: '', + extra: '马上到期', + color: 'red', + type: '3', + }, + { + id: '000000011', + avatar: '', + title: '信息安全考试', + description: '指派竹尔于 2017-01-09 前完成更新并发布', + datetime: '', + extra: '已耗时 8 天', + color: 'gold', + type: '3', + }, + { + id: '000000012', + avatar: '', + title: 'ABCD 版本发布', + description: '指派竹尔于 2017-01-09 前完成更新并发布', + datetime: '', + extra: '进行中', + color: 'blue', + type: '3', + }, + ], + }, +]; diff --git a/src/layouts/default/header/components/notify/index.vue b/src/layouts/default/header/components/notify/index.vue new file mode 100644 index 00000000000..7473ff594c7 --- /dev/null +++ b/src/layouts/default/header/components/notify/index.vue @@ -0,0 +1,81 @@ + + + diff --git a/src/layouts/default/header/components/user-dropdown/DropMenuItem.vue b/src/layouts/default/header/components/user-dropdown/DropMenuItem.vue new file mode 100644 index 00000000000..8d0a02a37c0 --- /dev/null +++ b/src/layouts/default/header/components/user-dropdown/DropMenuItem.vue @@ -0,0 +1,24 @@ + + diff --git a/src/layouts/default/header/components/user-dropdown/index.vue b/src/layouts/default/header/components/user-dropdown/index.vue new file mode 100644 index 00000000000..d133d3618ec --- /dev/null +++ b/src/layouts/default/header/components/user-dropdown/index.vue @@ -0,0 +1,171 @@ + + + diff --git a/src/layouts/default/header/index.less b/src/layouts/default/header/index.less new file mode 100644 index 00000000000..ab734f17425 --- /dev/null +++ b/src/layouts/default/header/index.less @@ -0,0 +1,196 @@ +@header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger'; +@header-prefix-cls: ~'@{namespace}-layout-header'; +@breadcrumb-prefix-cls: ~'@{namespace}-layout-breadcrumb'; +@logo-prefix-cls: ~'@{namespace}-app-logo'; + +.ant-layout .@{header-prefix-cls} { + display: flex; + align-items: center; + justify-content: space-between; + height: @header-height; + margin-left: -1px; + padding: 0; + background-color: @white; + color: @white; + line-height: @header-height; + + &--mobile { + .@{breadcrumb-prefix-cls}, + .error-action, + .notify-item, + .fullscreen-item { + display: none; + } + + .@{logo-prefix-cls} { + min-width: unset; + padding-right: 0; + + &__title { + display: none; + } + } + .@{header-trigger-prefix-cls} { + padding: 0 4px 0 8px !important; + } + .@{header-prefix-cls}-action { + padding-right: 4px; + } + } + + &--fixed { + position: fixed; + z-index: @layout-header-fixed-z-index; + top: 0; + left: 0; + width: 100%; + } + + &-logo { + min-width: 192px; + height: @header-height; + padding: 0 10px; + font-size: 14px; + + img { + width: @logo-width; + height: @logo-width; + margin-right: 2px; + } + } + + &-left { + display: flex; + align-items: center; + height: 100%; + + .@{header-trigger-prefix-cls} { + display: flex; + align-items: center; + height: 100%; + padding: 1px 10px 0; + cursor: pointer; + + .anticon { + font-size: 16px; + } + + &.light { + &:hover { + background-color: @header-light-bg-hover-color; + } + + svg { + fill: #000; + } + } + + &.dark { + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + } + } + + &-menu { + flex: 1; + align-items: center; + min-width: 0; + height: 100%; + } + + &-action { + display: flex; + // padding-right: 12px; + align-items: center; + min-width: 180px; + + &__item { + display: flex !important; + align-items: center; + height: @header-height; + padding: 0 2px; + font-size: 1.2em; + cursor: pointer; + + .ant-badge { + height: @header-height; + line-height: @header-height; + } + + .ant-badge-dot { + top: 14px; + right: 2px; + } + } + + span[role='img'] { + padding: 0 8px; + } + } + + &--light { + border-bottom: 1px solid @header-light-bottom-border-color; + border-left: 1px solid @header-light-bottom-border-color; + background-color: @white !important; + + .@{header-prefix-cls}-logo { + color: @text-color-base; + + &:hover { + background-color: @header-light-bg-hover-color; + } + } + + .@{header-prefix-cls}-action { + &__item { + color: @text-color-base; + + .app-iconify { + padding: 0 10px; + font-size: 16px !important; + } + + &:hover { + background-color: @header-light-bg-hover-color; + } + } + + &-icon, + span[role='img'] { + color: @text-color-base; + } + } + } + + &--dark { + border-bottom: 1px solid @border-color-base; + border-left: 1px solid @border-color-base; + background-color: @header-dark-bg-color !important; + .@{header-prefix-cls}-logo { + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + + .@{header-prefix-cls}-action { + &__item { + .app-iconify { + padding: 0 10px; + font-size: 16px !important; + } + + .ant-badge { + span { + color: @white; + } + } + + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + } + } +} diff --git a/src/layouts/default/header/index.vue b/src/layouts/default/header/index.vue new file mode 100644 index 00000000000..2ffd43c235a --- /dev/null +++ b/src/layouts/default/header/index.vue @@ -0,0 +1,153 @@ + + + diff --git a/src/layouts/default/index.vue b/src/layouts/default/index.vue new file mode 100644 index 00000000000..e0d64f05d0d --- /dev/null +++ b/src/layouts/default/index.vue @@ -0,0 +1,91 @@ + + + + diff --git a/src/layouts/default/menu/index.vue b/src/layouts/default/menu/index.vue new file mode 100644 index 00000000000..57a4a878c7e --- /dev/null +++ b/src/layouts/default/menu/index.vue @@ -0,0 +1,197 @@ + + diff --git a/src/layouts/default/menu/useLayoutMenu.ts b/src/layouts/default/menu/useLayoutMenu.ts new file mode 100644 index 00000000000..e176b6fc38e --- /dev/null +++ b/src/layouts/default/menu/useLayoutMenu.ts @@ -0,0 +1,109 @@ +import type { Menu } from '@/router/types'; +import type { Ref } from 'vue'; +import { watch, unref, ref, computed } from 'vue'; +import { useRouter } from 'vue-router'; +import { MenuSplitTyeEnum } from '@/enums/menuEnum'; +import { useThrottleFn } from '@vueuse/core'; +import { useMenuSetting } from '@/hooks/setting/useMenuSetting'; +import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '@/router/menus'; +import { usePermissionStore } from '@/store/modules/permission'; +import { useAppInject } from '@/hooks/web/useAppInject'; + +export function useSplitMenu(splitType: Ref) { + // Menu array + const menusRef = ref([]); + const { currentRoute } = useRouter(); + const { getIsMobile } = useAppInject(); + const permissionStore = usePermissionStore(); + const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting(); + + const throttleHandleSplitLeftMenu = useThrottleFn(handleSplitLeftMenu, 50); + + const splitNotLeft = computed( + () => unref(splitType) !== MenuSplitTyeEnum.LEFT && !unref(getIsHorizontal), + ); + + const getSplitLeft = computed( + () => !unref(getSplit) || unref(splitType) !== MenuSplitTyeEnum.LEFT, + ); + + const getSpiltTop = computed(() => unref(splitType) === MenuSplitTyeEnum.TOP); + + const normalType = computed(() => { + return unref(splitType) === MenuSplitTyeEnum.NONE || !unref(getSplit); + }); + + watch( + [() => unref(currentRoute).path, () => unref(splitType)], + async ([path]: [string, MenuSplitTyeEnum]) => { + if (unref(splitNotLeft) || unref(getIsMobile)) return; + + const { meta } = unref(currentRoute); + const currentActiveMenu = meta.currentActiveMenu as string; + let parentPath = await getCurrentParentPath(path); + if (!parentPath) { + parentPath = await getCurrentParentPath(currentActiveMenu); + } + parentPath && throttleHandleSplitLeftMenu(parentPath); + }, + { + immediate: true, + }, + ); + + // Menu changes + watch( + [() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList], + () => { + genMenus(); + }, + { + immediate: true, + }, + ); + + // split Menu changes + watch( + () => getSplit.value, + () => { + if (unref(splitNotLeft)) return; + genMenus(); + }, + ); + + // Handle left menu split + async function handleSplitLeftMenu(parentPath: string) { + if (unref(getSplitLeft) || unref(getIsMobile)) return; + + // spilt mode left + const children = await getChildrenMenus(parentPath); + + if (!children || !children.length) { + setMenuSetting({ hidden: true }); + menusRef.value = []; + return; + } + + setMenuSetting({ hidden: false }); + menusRef.value = children; + } + + // get menus + async function genMenus() { + // normal mode + if (unref(normalType) || unref(getIsMobile)) { + menusRef.value = await getMenus(); + return; + } + + // split-top + if (unref(getSpiltTop)) { + const shallowMenus = await getShallowMenus(); + + menusRef.value = shallowMenus; + return; + } + } + + return { menusRef }; +} diff --git a/src/layouts/default/setting/SettingDrawer.tsx b/src/layouts/default/setting/SettingDrawer.tsx new file mode 100644 index 00000000000..8f6ca0ffed4 --- /dev/null +++ b/src/layouts/default/setting/SettingDrawer.tsx @@ -0,0 +1,435 @@ +import { defineComponent, computed, unref } from 'vue'; +import { BasicDrawer } from '@/components/Drawer'; +import { Divider } from 'ant-design-vue'; +import { + TypePicker, + ThemeColorPicker, + SettingFooter, + SwitchItem, + SelectItem, + InputNumberItem, +} from './components'; + +import { AppDarkModeToggle } from '@/components/Application'; + +import { MenuTypeEnum, TriggerEnum } from '@/enums/menuEnum'; + +import { useRootSetting } from '@/hooks/setting/useRootSetting'; +import { useMenuSetting } from '@/hooks/setting/useMenuSetting'; +import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting'; +import { useMultipleTabSetting } from '@/hooks/setting/useMultipleTabSetting'; +import { useTransitionSetting } from '@/hooks/setting/useTransitionSetting'; +import { useI18n } from '@/hooks/web/useI18n'; + +import { baseHandler } from './handler'; + +import { + HandlerEnum, + contentModeOptions, + topMenuAlignOptions, + getMenuTriggerOptions, + routerTransitionOptions, + menuTypeListEnum, + mixSidebarTriggerOptions, +} from './enum'; + +// import { +// HEADER_PRESET_BG_COLOR_LIST, +// SIDE_BAR_BG_COLOR_LIST, +// APP_PRESET_COLOR_LIST, +// } from '@/settings/designSetting'; +import { SIDE_BAR_BG_COLOR_LIST } from '@/settings/designSetting'; + +const { t } = useI18n(); + +export default defineComponent({ + name: 'SettingDrawer', + setup(_, { attrs }) { + const { + getContentMode, + getShowFooter, + getShowBreadCrumb, + getShowBreadCrumbIcon, + getShowLogo, + getFullContent, + getColorWeak, + getGrayMode, + getLockTime, + getShowDarkModeToggle, + // getThemeColor, + } = useRootSetting(); + + const { getOpenPageLoading, getBasicTransition, getEnableTransition, getOpenNProgress } = + useTransitionSetting(); + + const { + getIsHorizontal, + getShowMenu, + getMenuType, + getTrigger, + getCollapsedShowTitle, + getMenuFixed, + getCollapsed, + getCanDrag, + getTopMenuAlign, + getAccordion, + getMenuWidth, + getMenuBgColor, + getIsTopMenu, + getSplit, + getIsMixSidebar, + getCloseMixSidebarOnChange, + getMixSideTrigger, + getMixSideFixed, + } = useMenuSetting(); + + const { + getShowHeader, + getFixed: getHeaderFixed, + // getHeaderBgColor, + getShowSearch, + } = useHeaderSetting(); + + const { getShowMultipleTab, getShowQuick, getShowRedo, getShowFold, getAutoCollapse } = + useMultipleTabSetting(); + + const getShowMenuRef = computed(() => { + return unref(getShowMenu) && !unref(getIsHorizontal); + }); + + function renderSidebar() { + return ( + <> + { + baseHandler(HandlerEnum.CHANGE_LAYOUT, { + mode: item.mode, + type: item.type, + split: unref(getIsHorizontal) ? false : undefined, + }); + }} + def={unref(getMenuType)} + /> + + ); + } + + // function renderHeaderTheme() { + // return ( + // + // ); + // } + + function renderSideBarTheme() { + return ( + + ); + } + + // function renderMainTheme() { + // return ( + // + // ); + // } + + /** + * @description: + */ + function renderFeatures() { + let triggerDef = unref(getTrigger); + + const triggerOptions = getMenuTriggerOptions(unref(getSplit)); + const some = triggerOptions.some((item) => item.value === triggerDef); + if (!some) { + triggerDef = TriggerEnum.FOOTER; + } + + return ( + <> + + + + + + + + + + + + + + + + + + + + { + return parseInt(value) === 0 + ? `0(${t('layout.setting.notAutoScreenLock')})` + : `${value}${t('layout.setting.minute')}`; + }} + /> + `${parseInt(value)}px`} + /> + + ); + } + + function renderContent() { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + ); + } + + function renderTransition() { + return ( + <> + + + + + + + + ); + } + + return () => ( + + {unref(getShowDarkModeToggle) && {() => t('layout.setting.darkMode')}} + {unref(getShowDarkModeToggle) && } + {() => t('layout.setting.navMode')} + {renderSidebar()} + {/* {() => t('layout.setting.sysTheme')} + {renderMainTheme()} + {() => t('layout.setting.headerTheme')} + {renderHeaderTheme()} */} + {() => t('layout.setting.sidebarTheme')} + {renderSideBarTheme()} + {() => t('layout.setting.interfaceFunction')} + {renderFeatures()} + {() => t('layout.setting.interfaceDisplay')} + {renderContent()} + {() => t('layout.setting.animation')} + {renderTransition()} + + + + ); + }, +}); diff --git a/src/layouts/default/setting/components/InputNumberItem.vue b/src/layouts/default/setting/components/InputNumberItem.vue new file mode 100644 index 00000000000..09b9d3b3494 --- /dev/null +++ b/src/layouts/default/setting/components/InputNumberItem.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/layouts/default/setting/components/SelectItem.vue b/src/layouts/default/setting/components/SelectItem.vue new file mode 100644 index 00000000000..ec866ed8ec2 --- /dev/null +++ b/src/layouts/default/setting/components/SelectItem.vue @@ -0,0 +1,67 @@ + + + diff --git a/src/layouts/default/setting/components/SettingFooter.vue b/src/layouts/default/setting/components/SettingFooter.vue new file mode 100644 index 00000000000..b61da43e46b --- /dev/null +++ b/src/layouts/default/setting/components/SettingFooter.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/layouts/default/setting/components/SwitchItem.vue b/src/layouts/default/setting/components/SwitchItem.vue new file mode 100644 index 00000000000..3e57893846c --- /dev/null +++ b/src/layouts/default/setting/components/SwitchItem.vue @@ -0,0 +1,58 @@ + + + diff --git a/src/layouts/default/setting/components/ThemeColorPicker.vue b/src/layouts/default/setting/components/ThemeColorPicker.vue new file mode 100644 index 00000000000..1e971506214 --- /dev/null +++ b/src/layouts/default/setting/components/ThemeColorPicker.vue @@ -0,0 +1,81 @@ + + + diff --git a/src/layouts/default/setting/components/TypePicker.vue b/src/layouts/default/setting/components/TypePicker.vue new file mode 100644 index 00000000000..6449bb2b91a --- /dev/null +++ b/src/layouts/default/setting/components/TypePicker.vue @@ -0,0 +1,172 @@ + + + diff --git a/src/layouts/default/setting/components/index.ts b/src/layouts/default/setting/components/index.ts new file mode 100644 index 00000000000..e4910c65eff --- /dev/null +++ b/src/layouts/default/setting/components/index.ts @@ -0,0 +1,8 @@ +import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'; + +export const TypePicker = createAsyncComponent(() => import('./TypePicker.vue')); +export const ThemeColorPicker = createAsyncComponent(() => import('./ThemeColorPicker.vue')); +export const SettingFooter = createAsyncComponent(() => import('./SettingFooter.vue')); +export const SwitchItem = createAsyncComponent(() => import('./SwitchItem.vue')); +export const SelectItem = createAsyncComponent(() => import('./SelectItem.vue')); +export const InputNumberItem = createAsyncComponent(() => import('./InputNumberItem.vue')); diff --git a/src/layouts/default/setting/enum.ts b/src/layouts/default/setting/enum.ts new file mode 100644 index 00000000000..3bc55f0cbd0 --- /dev/null +++ b/src/layouts/default/setting/enum.ts @@ -0,0 +1,157 @@ +import { ContentEnum, RouterTransitionEnum } from '@/enums/appEnum'; +import { + MenuModeEnum, + MenuTypeEnum, + TopMenuAlignEnum, + TriggerEnum, + MixSidebarTriggerEnum, +} from '@/enums/menuEnum'; + +import { useI18n } from '@/hooks/web/useI18n'; + +const { t } = useI18n(); + +export enum HandlerEnum { + CHANGE_LAYOUT, + CHANGE_THEME_COLOR, + CHANGE_THEME, + // menu + MENU_HAS_DRAG, + MENU_ACCORDION, + MENU_TRIGGER, + MENU_TOP_ALIGN, + MENU_COLLAPSED, + MENU_COLLAPSED_SHOW_TITLE, + MENU_WIDTH, + MENU_SHOW_SIDEBAR, + MENU_THEME, + MENU_SPLIT, + MENU_FIXED, + MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE, + MENU_TRIGGER_MIX_SIDEBAR, + MENU_FIXED_MIX_SIDEBAR, + + // header + HEADER_SHOW, + HEADER_THEME, + HEADER_FIXED, + + HEADER_SEARCH, + + TABS_SHOW_QUICK, + TABS_SHOW_REDO, + TABS_SHOW, + TABS_SHOW_FOLD, + TABS_AUTO_COLLAPSE, + + LOCK_TIME, + FULL_CONTENT, + CONTENT_MODE, + SHOW_BREADCRUMB, + SHOW_BREADCRUMB_ICON, + GRAY_MODE, + COLOR_WEAK, + SHOW_LOGO, + SHOW_FOOTER, + + ROUTER_TRANSITION, + OPEN_PROGRESS, + OPEN_PAGE_LOADING, + OPEN_ROUTE_TRANSITION, +} + +export const contentModeOptions = [ + { + value: ContentEnum.FULL, + label: t('layout.setting.contentModeFull'), + }, + { + value: ContentEnum.FIXED, + label: t('layout.setting.contentModeFixed'), + }, +]; + +export const topMenuAlignOptions = [ + { + value: TopMenuAlignEnum.CENTER, + label: t('layout.setting.topMenuAlignRight'), + }, + { + value: TopMenuAlignEnum.START, + label: t('layout.setting.topMenuAlignLeft'), + }, + { + value: TopMenuAlignEnum.END, + label: t('layout.setting.topMenuAlignCenter'), + }, +]; + +export const getMenuTriggerOptions = (hideTop: boolean) => { + return [ + { + value: TriggerEnum.NONE, + label: t('layout.setting.menuTriggerNone'), + }, + { + value: TriggerEnum.FOOTER, + label: t('layout.setting.menuTriggerBottom'), + }, + ...(hideTop + ? [] + : [ + { + value: TriggerEnum.HEADER, + label: t('layout.setting.menuTriggerTop'), + }, + ]), + ]; +}; + +export const routerTransitionOptions = [ + RouterTransitionEnum.ZOOM_FADE, + RouterTransitionEnum.FADE, + RouterTransitionEnum.ZOOM_OUT, + RouterTransitionEnum.FADE_SIDE, + RouterTransitionEnum.FADE_BOTTOM, + RouterTransitionEnum.FADE_SCALE, +].map((item) => { + return { + label: item, + value: item, + }; +}); + +export const menuTypeListEnum = [ + { + title: t('layout.setting.menuTypeSidebar'), + mode: MenuModeEnum.INLINE, + type: MenuTypeEnum.SIDEBAR, + }, + { + title: t('layout.setting.menuTypeMix'), + mode: MenuModeEnum.INLINE, + type: MenuTypeEnum.MIX, + }, + + { + title: t('layout.setting.menuTypeTopMenu'), + mode: MenuModeEnum.HORIZONTAL, + type: MenuTypeEnum.TOP_MENU, + }, + { + title: t('layout.setting.menuTypeMixSidebar'), + mode: MenuModeEnum.INLINE, + type: MenuTypeEnum.MIX_SIDEBAR, + }, +]; + +export const mixSidebarTriggerOptions = [ + { + value: MixSidebarTriggerEnum.HOVER, + label: t('layout.setting.triggerHover'), + }, + { + value: MixSidebarTriggerEnum.CLICK, + label: t('layout.setting.triggerClick'), + }, +]; diff --git a/src/layouts/default/setting/handler.ts b/src/layouts/default/setting/handler.ts new file mode 100644 index 00000000000..8028cb2c7a0 --- /dev/null +++ b/src/layouts/default/setting/handler.ts @@ -0,0 +1,182 @@ +import { MenuTypeEnum } from '@/enums/menuEnum'; +import { HandlerEnum } from './enum'; +import { updateHeaderBgColor, updateSidebarBgColor } from '@/logics/theme/updateBackground'; +import { updateColorWeak } from '@/logics/theme/updateColorWeak'; +import { updateGrayMode } from '@/logics/theme/updateGrayMode'; + +import { useAppStore } from '@/store/modules/app'; +import { ProjectConfig } from '#/config'; +import { updateDarkTheme } from '@/logics/theme/dark'; +import { useRootSetting } from '@/hooks/setting/useRootSetting'; +import projectSetting from '@/settings/projectSetting'; + +export function baseHandler(event: HandlerEnum, value: any) { + const appStore = useAppStore(); + const config = handler(event, value); + appStore.setProjectConfig(config); + if (event === HandlerEnum.CHANGE_THEME) { + updateHeaderBgColor(); + updateSidebarBgColor(); + } +} + +export function handler(event: HandlerEnum, value: any): DeepPartial { + const appStore = useAppStore(); + + const { getThemeColor, getDarkMode } = useRootSetting(); + const { menuSetting } = projectSetting; + switch (event) { + case HandlerEnum.CHANGE_LAYOUT: + const { mode, type, split } = value; + const isMixSidebar = type === MenuTypeEnum.MIX; + const mixSideSplitOpt = + menuSetting.type === MenuTypeEnum.MIX ? { split: menuSetting.split } : { split }; + const otherSplitOpt = { split: false }; + const splitOpt = isMixSidebar ? mixSideSplitOpt : otherSplitOpt; + + return { + menuSetting: { + mode, + type, + collapsed: false, + show: true, + hidden: false, + ...splitOpt, + }, + }; + + case HandlerEnum.CHANGE_THEME_COLOR: + if (getThemeColor.value === value) { + return {}; + } + + return { themeColor: value }; + + case HandlerEnum.CHANGE_THEME: + if (getDarkMode.value === value) { + return {}; + } + updateDarkTheme(value); + + return {}; + + case HandlerEnum.MENU_HAS_DRAG: + return { menuSetting: { canDrag: value } }; + + case HandlerEnum.MENU_ACCORDION: + return { menuSetting: { accordion: value } }; + + case HandlerEnum.MENU_TRIGGER: + return { menuSetting: { trigger: value } }; + + case HandlerEnum.MENU_TOP_ALIGN: + return { menuSetting: { topMenuAlign: value } }; + + case HandlerEnum.MENU_COLLAPSED: + return { menuSetting: { collapsed: value } }; + + case HandlerEnum.MENU_WIDTH: + return { menuSetting: { menuWidth: value } }; + + case HandlerEnum.MENU_SHOW_SIDEBAR: + return { menuSetting: { show: value } }; + + case HandlerEnum.MENU_COLLAPSED_SHOW_TITLE: + return { menuSetting: { collapsedShowTitle: value } }; + + case HandlerEnum.MENU_THEME: + updateSidebarBgColor(value); + return { menuSetting: { bgColor: value } }; + + case HandlerEnum.MENU_SPLIT: + return { menuSetting: { split: value } }; + + case HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE: + return { menuSetting: { closeMixSidebarOnChange: value } }; + + case HandlerEnum.MENU_FIXED: + return { menuSetting: { fixed: value } }; + + case HandlerEnum.MENU_TRIGGER_MIX_SIDEBAR: + return { menuSetting: { mixSideTrigger: value } }; + + case HandlerEnum.MENU_FIXED_MIX_SIDEBAR: + return { menuSetting: { mixSideFixed: value } }; + + // ============transition================== + case HandlerEnum.OPEN_PAGE_LOADING: + appStore.setPageLoading(false); + return { transitionSetting: { openPageLoading: value } }; + + case HandlerEnum.ROUTER_TRANSITION: + return { transitionSetting: { basicTransition: value } }; + + case HandlerEnum.OPEN_ROUTE_TRANSITION: + return { transitionSetting: { enable: value } }; + + case HandlerEnum.OPEN_PROGRESS: + return { transitionSetting: { openNProgress: value } }; + // ============root================== + + case HandlerEnum.LOCK_TIME: + return { lockTime: value }; + + case HandlerEnum.FULL_CONTENT: + return { fullContent: value }; + + case HandlerEnum.CONTENT_MODE: + return { contentMode: value }; + + case HandlerEnum.SHOW_BREADCRUMB: + return { showBreadCrumb: value }; + + case HandlerEnum.SHOW_BREADCRUMB_ICON: + return { showBreadCrumbIcon: value }; + + case HandlerEnum.GRAY_MODE: + updateGrayMode(value); + return { grayMode: value }; + + case HandlerEnum.SHOW_FOOTER: + return { showFooter: value }; + + case HandlerEnum.COLOR_WEAK: + updateColorWeak(value); + return { colorWeak: value }; + + case HandlerEnum.SHOW_LOGO: + return { showLogo: value }; + + // ============tabs================== + case HandlerEnum.TABS_SHOW_QUICK: + return { multiTabsSetting: { showQuick: value } }; + + case HandlerEnum.TABS_SHOW: + return { multiTabsSetting: { show: value } }; + + case HandlerEnum.TABS_SHOW_REDO: + return { multiTabsSetting: { showRedo: value } }; + + case HandlerEnum.TABS_SHOW_FOLD: + return { multiTabsSetting: { showFold: value } }; + + case HandlerEnum.TABS_AUTO_COLLAPSE: + return { multiTabsSetting: { autoCollapse: value } }; + + // ============header================== + case HandlerEnum.HEADER_THEME: + updateHeaderBgColor(value); + return { headerSetting: { bgColor: value } }; + + case HandlerEnum.HEADER_SEARCH: + return { headerSetting: { showSearch: value } }; + + case HandlerEnum.HEADER_FIXED: + return { headerSetting: { fixed: value } }; + + case HandlerEnum.HEADER_SHOW: + return { headerSetting: { show: value } }; + default: + return {}; + } +} diff --git a/src/layouts/default/setting/index.vue b/src/layouts/default/setting/index.vue new file mode 100644 index 00000000000..26fda4ae465 --- /dev/null +++ b/src/layouts/default/setting/index.vue @@ -0,0 +1,16 @@ + + diff --git a/src/layouts/default/sider/DragBar.vue b/src/layouts/default/sider/DragBar.vue new file mode 100644 index 00000000000..1b988b7af6f --- /dev/null +++ b/src/layouts/default/sider/DragBar.vue @@ -0,0 +1,58 @@ + + + diff --git a/src/layouts/default/sider/LayoutSider.vue b/src/layouts/default/sider/LayoutSider.vue new file mode 100644 index 00000000000..9678746ce0c --- /dev/null +++ b/src/layouts/default/sider/LayoutSider.vue @@ -0,0 +1,168 @@ + + + diff --git a/src/layouts/default/sider/MixSider.vue b/src/layouts/default/sider/MixSider.vue new file mode 100644 index 00000000000..43838907e78 --- /dev/null +++ b/src/layouts/default/sider/MixSider.vue @@ -0,0 +1,567 @@ + + + diff --git a/src/layouts/default/sider/index.vue b/src/layouts/default/sider/index.vue new file mode 100644 index 00000000000..4abbf565241 --- /dev/null +++ b/src/layouts/default/sider/index.vue @@ -0,0 +1,51 @@ + + + + diff --git a/src/layouts/default/sider/useLayoutSider.ts b/src/layouts/default/sider/useLayoutSider.ts new file mode 100644 index 00000000000..8077b651ebc --- /dev/null +++ b/src/layouts/default/sider/useLayoutSider.ts @@ -0,0 +1,143 @@ +import type { Ref } from 'vue'; + +import { computed, unref, onMounted, nextTick } from 'vue'; + +import { TriggerEnum } from '@/enums/menuEnum'; + +import { useMenuSetting } from '@/hooks/setting/useMenuSetting'; +import { useDebounceFn } from '@vueuse/core'; +import { useAppStore } from '@/store/modules/app'; + +/** + * Handle related operations of menu events + */ +export function useSiderEvent() { + const appStore = useAppStore(); + const { getMiniWidthNumber } = useMenuSetting(); + + const getCollapsedWidth = computed(() => { + return unref(getMiniWidthNumber); + }); + + function onBreakpointChange(broken: boolean) { + appStore.setProjectConfig({ + menuSetting: { + siderHidden: broken, + }, + }); + } + + return { getCollapsedWidth, onBreakpointChange }; +} + +/** + * Handle related operations of menu folding + */ +export function useTrigger(getIsMobile: Ref) { + const { getTrigger, getSplit } = useMenuSetting(); + + const getShowTrigger = computed(() => { + const trigger = unref(getTrigger); + + return ( + trigger !== TriggerEnum.NONE && + !unref(getIsMobile) && + (trigger === TriggerEnum.FOOTER || unref(getSplit)) + ); + }); + + const getTriggerAttr = computed(() => { + if (unref(getShowTrigger)) { + return {}; + } + return { + trigger: null, + }; + }); + + return { getTriggerAttr, getShowTrigger }; +} + +/** + * Handle menu drag and drop related operations + * @param siderRef + * @param dragBarRef + */ +export function useDragLine(siderRef: Ref, dragBarRef: Ref, mix = false) { + const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting(); + + onMounted(() => { + nextTick(() => { + const exec = useDebounceFn(changeWrapWidth, 80); + exec(); + }); + }); + + function getEl(elRef: Ref): any { + const el = unref(elRef); + if (!el) return null; + if (Reflect.has(el, '$el')) { + return (unref(elRef) as ComponentRef)?.$el; + } + return unref(elRef); + } + + function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) { + document.onmousemove = function (innerE) { + let iT = (ele as any).left + (innerE.clientX - clientX); + innerE = innerE || window.event; + const maxT = 800; + const minT = unref(getMiniWidthNumber); + iT < 0 && (iT = 0); + iT > maxT && (iT = maxT); + iT < minT && (iT = minT); + ele.style.left = wrap.style.width = iT + 'px'; + return false; + }; + } + + // Drag and drop in the menu area-release the mouse + function removeMouseup(ele: any) { + const wrap = getEl(siderRef); + document.onmouseup = function () { + document.onmousemove = null; + document.onmouseup = null; + wrap.style.transition = 'width 0.2s'; + const width = parseInt(wrap.style.width); + + if (!mix) { + const miniWidth = unref(getMiniWidthNumber); + if (!unref(getCollapsed)) { + width > miniWidth + 20 + ? setMenuSetting({ menuWidth: width }) + : setMenuSetting({ collapsed: true }); + } else { + width > miniWidth && setMenuSetting({ collapsed: false, menuWidth: width }); + } + } else { + setMenuSetting({ menuWidth: width }); + } + + ele.releaseCapture?.(); + }; + } + + function changeWrapWidth() { + const ele = getEl(dragBarRef); + if (!ele) return; + const wrap = getEl(siderRef); + if (!wrap) return; + + ele.onmousedown = (e: any) => { + wrap.style.transition = 'unset'; + const clientX = e?.clientX; + ele.left = ele.offsetLeft; + handleMouseMove(ele, wrap, clientX); + removeMouseup(ele); + ele.setCapture?.(); + return false; + }; + } + + return {}; +} diff --git a/src/layouts/default/tabs/components/FoldButton.vue b/src/layouts/default/tabs/components/FoldButton.vue new file mode 100644 index 00000000000..7030e3ae8fd --- /dev/null +++ b/src/layouts/default/tabs/components/FoldButton.vue @@ -0,0 +1,36 @@ + + diff --git a/src/layouts/default/tabs/components/SettingButton.vue b/src/layouts/default/tabs/components/SettingButton.vue new file mode 100644 index 00000000000..baf566f7fca --- /dev/null +++ b/src/layouts/default/tabs/components/SettingButton.vue @@ -0,0 +1,19 @@ + + diff --git a/src/layouts/default/tabs/components/TabContent.vue b/src/layouts/default/tabs/components/TabContent.vue new file mode 100644 index 00000000000..c7b42b2a9bb --- /dev/null +++ b/src/layouts/default/tabs/components/TabContent.vue @@ -0,0 +1,63 @@ + + diff --git a/src/layouts/default/tabs/components/TabRedo.vue b/src/layouts/default/tabs/components/TabRedo.vue new file mode 100644 index 00000000000..bc392623d0e --- /dev/null +++ b/src/layouts/default/tabs/components/TabRedo.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/layouts/default/tabs/index.less b/src/layouts/default/tabs/index.less new file mode 100644 index 00000000000..9661aa7e791 --- /dev/null +++ b/src/layouts/default/tabs/index.less @@ -0,0 +1,222 @@ +@prefix-cls: ~'@{namespace}-multiple-tabs'; +@prefix-cls-default-layout: ~'@{namespace}-default-layout'; + +html[data-theme='light'] { + .@{prefix-cls} { + .ant-tabs-tab:not(.ant-tabs-tab-active) { + border: 1px solid #d9d9d9 !important; + border-bottom-color: #f0f0f0 !important; + } + } +} + +.@{prefix-cls-default-layout}-out { + &.ant-layout-auto-collapse-tabs { + .@{prefix-cls} { + margin-top: -(@multiple-height + 2); + opacity: 0.1; + + &:hover, + &--hover { + margin-top: 0; + transition-delay: 0s; + opacity: 1; + } + } + } + .@{prefix-cls} { + transition: + margin 0.2s ease-in-out 0.6s, + opacity 0.2s ease-in-out 0.6s; + } +} + +.@{prefix-cls} { + z-index: 10; + height: @multiple-height + 2; + border-bottom: 1px solid @border-color-base; + background-color: @component-background; + line-height: @multiple-height + 2; + + .ant-tabs-small { + height: @multiple-height; + } + + .ant-tabs.ant-tabs-card { + .ant-tabs-nav { + height: @multiple-height + 2; + margin: 0; + padding-top: 2px; + border: 0; + background-color: @component-background; + box-shadow: none; + + .ant-tabs-nav-container { + height: @multiple-height; + padding-top: 2px; + } + + .ant-tabs-tab { + height: calc(@multiple-height); + padding-right: 12px; + transition: none; + background-color: @component-background; + color: @text-color-base; + line-height: calc(@multiple-height); + + &:hover { + .ant-tabs-tab-remove { + opacity: 1; + } + } + + .ant-tabs-tab-remove { + width: 8px; + height: 30px; + margin-right: -4px; + margin-left: 2px; + transition: none; + opacity: 0; + color: inherit; + font-size: 12px; + + &:hover { + svg { + width: 0.8em; + } + } + } + + // > div { + // display: flex; + // justify-content: center; + // align-items: center; + // } + + svg { + fill: @text-color-base; + } + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + &:hover { + color: @primary-color; + } + } + + .ant-tabs-tab-active { + position: relative; + padding-left: 18px; + transition: none; + border: 0; + background: @primary-color; + + span { + color: @white !important; + } + + .ant-tabs-tab-remove { + opacity: 1; + } + + svg { + width: 0.7em; + fill: @white; + } + } + } + + .ant-tabs-nav > div:nth-child(1) { + padding: 0 6px; + + .ant-tabs-tab { + margin-right: 3px !important; + } + } + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + .anticon-close { + font-size: 12px; + + svg { + width: 0.6em; + } + } + } + + .ant-dropdown-trigger { + display: inline-flex; + } + + &--hide-close { + .ant-tabs-tab-remove { + opacity: 0 !important; + } + } + + &-content { + &__extra-quick, + &__extra-redo, + &__extra-fold { + display: inline-block; + width: 36px; + height: @multiple-height; + border-left: 1px solid @border-color-base; + color: @text-color-secondary; + line-height: @multiple-height; + text-align: center; + cursor: pointer; + + &:hover { + color: @text-color-base; + } + + span[role='img'] { + transform: rotate(90deg); + } + } + + &__extra-redo { + span[role='img'] { + transform: rotate(0deg); + } + } + + &__info { + display: inline-block; + width: 100%; + height: @multiple-height - 2; + margin-left: -10px; + padding-left: 0; + font-size: 12px; + cursor: pointer; + user-select: none; + } + } +} + +.ant-tabs-dropdown-menu { + &-title-content { + display: flex; + align-items: center; + + .@{prefix-cls} { + &-content__info { + width: auto; + margin-left: 0; + line-height: 28px; + } + } + } + + &-item-remove { + margin-left: auto; + } +} + +.multiple-tabs__dropdown { + .ant-dropdown-content { + width: 172px; + } +} diff --git a/src/layouts/default/tabs/index.vue b/src/layouts/default/tabs/index.vue new file mode 100644 index 00000000000..189a1b0d17a --- /dev/null +++ b/src/layouts/default/tabs/index.vue @@ -0,0 +1,137 @@ + + + diff --git a/src/layouts/default/tabs/types.ts b/src/layouts/default/tabs/types.ts new file mode 100644 index 00000000000..30539c14c52 --- /dev/null +++ b/src/layouts/default/tabs/types.ts @@ -0,0 +1,25 @@ +import type { DropMenu } from '@/components/Dropdown'; +import type { RouteLocationNormalized } from 'vue-router'; + +export enum TabContentEnum { + TAB_TYPE, + EXTRA_TYPE, +} + +export type { DropMenu }; + +export interface TabContentProps { + tabItem: RouteLocationNormalized; + type?: TabContentEnum; + trigger?: ('click' | 'hover' | 'contextmenu')[]; +} + +export enum MenuEventEnum { + REFRESH_PAGE, + CLOSE_CURRENT, + CLOSE_LEFT, + CLOSE_RIGHT, + CLOSE_OTHER, + CLOSE_ALL, + SCALE, +} diff --git a/src/layouts/default/tabs/useMultipleTabs.ts b/src/layouts/default/tabs/useMultipleTabs.ts new file mode 100644 index 00000000000..70cac1cc6c4 --- /dev/null +++ b/src/layouts/default/tabs/useMultipleTabs.ts @@ -0,0 +1,83 @@ +import { toRaw, ref, nextTick } from 'vue'; +import type { RouteLocationNormalized } from 'vue-router'; +import { useDesign } from '@/hooks/web/useDesign'; +import { useSortable } from '@/hooks/web/useSortable'; +import { useMultipleTabStore } from '@/store/modules/multipleTab'; +import { isNil } from '@/utils/is'; +import projectSetting from '@/settings/projectSetting'; +import { useRouter } from 'vue-router'; +import { useI18n } from '@/hooks/web/useI18n'; + +const { t } = useI18n(); + +export function initAffixTabs(): string[] { + const affixList = ref([]); + + const tabStore = useMultipleTabStore(); + const router = useRouter(); + /** + * @description: Filter all fixed routes + */ + function filterAffixTabs(routes: RouteLocationNormalized[]) { + const tabs: RouteLocationNormalized[] = []; + routes && + routes.forEach((route) => { + if (route.meta && route.meta.affix) { + tabs.push(toRaw(route)); + } + }); + return tabs; + } + + /** + * @description: Set fixed tabs + */ + function addAffixTabs(): void { + const affixTabs = filterAffixTabs(router.getRoutes() as unknown as RouteLocationNormalized[]); + affixList.value = affixTabs; + for (const tab of affixTabs) { + tabStore.addTab({ + meta: tab.meta, + name: tab.name, + path: tab.path, + } as unknown as RouteLocationNormalized); + } + } + + let isAddAffix = false; + + if (!isAddAffix) { + addAffixTabs(); + isAddAffix = true; + } + return affixList.value.map((item) => item.meta?.title).filter(Boolean) as string[]; +} + +export function useTabsDrag(affixTextList: string[]) { + const tabStore = useMultipleTabStore(); + const { multiTabsSetting } = projectSetting; + const { prefixCls } = useDesign('multiple-tabs'); + nextTick(() => { + if (!multiTabsSetting.canDrag) return; + const el = document.querySelectorAll( + `.${prefixCls} .ant-tabs-nav-wrap > div`, + )?.[0] as HTMLElement; + const { initSortable } = useSortable(el, { + filter: (_evt, target: HTMLElement) => { + const text = target.innerText; + if (!text) return false; + return affixTextList.map((res) => t(res)).includes(text); + }, + onEnd: (evt) => { + const { oldIndex, newIndex } = evt; + + if (isNil(oldIndex) || isNil(newIndex) || oldIndex === newIndex) { + return; + } + + tabStore.sortTabs(oldIndex, newIndex); + }, + }); + initSortable(); + }); +} diff --git a/src/layouts/default/tabs/useTabDropdown.ts b/src/layouts/default/tabs/useTabDropdown.ts new file mode 100644 index 00000000000..8c8e8432255 --- /dev/null +++ b/src/layouts/default/tabs/useTabDropdown.ts @@ -0,0 +1,140 @@ +import type { TabContentProps } from './types'; +import type { DropMenu } from '@/components/Dropdown'; +import type { ComputedRef } from 'vue'; + +import { computed, unref, reactive } from 'vue'; +import { MenuEventEnum } from './types'; +import { useMultipleTabStore } from '@/store/modules/multipleTab'; +import { RouteLocationNormalized, useRouter } from 'vue-router'; +import { useTabs } from '@/hooks/web/useTabs'; +import { useI18n } from '@/hooks/web/useI18n'; + +export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: ComputedRef) { + const state = reactive({ + current: null as Nullable, + currentIndex: 0, + }); + + const { t } = useI18n(); + const tabStore = useMultipleTabStore(); + const { currentRoute } = useRouter(); + const { refreshPage, closeAll, close, closeLeft, closeOther, closeRight } = useTabs(); + + const getTargetTab = computed((): RouteLocationNormalized => { + return unref(getIsTabs) ? tabContentProps.tabItem : unref(currentRoute); + }); + + /** + * @description: drop-down list + */ + const getDropMenuList = computed(() => { + if (!unref(getTargetTab)) { + return; + } + const { meta } = unref(getTargetTab); + const { path } = unref(currentRoute); + + const curItem = state.current; + + const isCurItem = curItem ? curItem.path === path : false; + + // Refresh button + const index = state.currentIndex; + const refreshDisabled = !isCurItem; + // Close left + const closeLeftDisabled = index === 0 || !isCurItem; + + const disabled = tabStore.getTabList.length === 1; + + // Close right + const closeRightDisabled = + !isCurItem || (index === tabStore.getTabList.length - 1 && tabStore.getLastDragEndIndex >= 0); + const dropMenuList: DropMenu[] = [ + { + icon: 'ion:reload-sharp', + event: MenuEventEnum.REFRESH_PAGE, + text: t('layout.multipleTab.reload'), + disabled: refreshDisabled, + }, + { + icon: 'clarity:close-line', + event: MenuEventEnum.CLOSE_CURRENT, + text: t('layout.multipleTab.close'), + disabled: !!meta?.affix || disabled, + divider: true, + }, + { + icon: 'line-md:arrow-close-left', + event: MenuEventEnum.CLOSE_LEFT, + text: t('layout.multipleTab.closeLeft'), + disabled: closeLeftDisabled, + divider: false, + }, + { + icon: 'line-md:arrow-close-right', + event: MenuEventEnum.CLOSE_RIGHT, + text: t('layout.multipleTab.closeRight'), + disabled: closeRightDisabled, + divider: true, + }, + { + icon: 'dashicons:align-center', + event: MenuEventEnum.CLOSE_OTHER, + text: t('layout.multipleTab.closeOther'), + disabled: disabled || !isCurItem, + }, + { + icon: 'clarity:minus-line', + event: MenuEventEnum.CLOSE_ALL, + text: t('layout.multipleTab.closeAll'), + disabled: disabled, + }, + ]; + + return dropMenuList; + }); + + function handleContextMenu(tabItem: RouteLocationNormalized) { + return (e: Event) => { + if (!tabItem) { + return; + } + e?.preventDefault(); + const index = tabStore.getTabList.findIndex((tab) => tab.path === tabItem.path); + state.current = tabItem; + state.currentIndex = index; + }; + } + + // Handle right click event + function handleMenuEvent(menu: DropMenu): void { + const { event } = menu; + switch (event) { + case MenuEventEnum.REFRESH_PAGE: + // refresh page + refreshPage(); + break; + // Close current + case MenuEventEnum.CLOSE_CURRENT: + close(tabContentProps.tabItem); + break; + // Close left + case MenuEventEnum.CLOSE_LEFT: + closeLeft(); + break; + // Close right + case MenuEventEnum.CLOSE_RIGHT: + closeRight(); + break; + // Close other + case MenuEventEnum.CLOSE_OTHER: + closeOther(); + break; + // Close all + case MenuEventEnum.CLOSE_ALL: + closeAll(); + break; + } + } + return { getDropMenuList, handleMenuEvent, handleContextMenu }; +} diff --git a/src/layouts/default/trigger/HeaderTrigger.vue b/src/layouts/default/trigger/HeaderTrigger.vue new file mode 100644 index 00000000000..364aa649ca4 --- /dev/null +++ b/src/layouts/default/trigger/HeaderTrigger.vue @@ -0,0 +1,17 @@ + + diff --git a/src/layouts/default/trigger/SiderTrigger.vue b/src/layouts/default/trigger/SiderTrigger.vue new file mode 100644 index 00000000000..0af0872da1d --- /dev/null +++ b/src/layouts/default/trigger/SiderTrigger.vue @@ -0,0 +1,12 @@ + + diff --git a/src/layouts/default/trigger/index.vue b/src/layouts/default/trigger/index.vue new file mode 100644 index 00000000000..2a030d92411 --- /dev/null +++ b/src/layouts/default/trigger/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/layouts/iframe/index.vue b/src/layouts/iframe/index.vue new file mode 100644 index 00000000000..da9657946a2 --- /dev/null +++ b/src/layouts/iframe/index.vue @@ -0,0 +1,23 @@ + + diff --git a/src/layouts/iframe/useFrameKeepAlive.ts b/src/layouts/iframe/useFrameKeepAlive.ts new file mode 100644 index 00000000000..2c295cc0290 --- /dev/null +++ b/src/layouts/iframe/useFrameKeepAlive.ts @@ -0,0 +1,59 @@ +import type { AppRouteRecordRaw } from '@/router/types'; + +import { computed, toRaw, unref } from 'vue'; + +import { useMultipleTabStore } from '@/store/modules/multipleTab'; + +import { uniqBy } from 'lodash-es'; + +import { useMultipleTabSetting } from '@/hooks/setting/useMultipleTabSetting'; + +import { useRouter } from 'vue-router'; + +export function useFrameKeepAlive() { + const router = useRouter(); + const { currentRoute } = router; + const { getShowMultipleTab } = useMultipleTabSetting(); + const tabStore = useMultipleTabStore(); + const getFramePages = computed(() => { + const ret = getAllFramePages(toRaw(router.getRoutes()) as unknown as AppRouteRecordRaw[]) || []; + return ret; + }); + + const getOpenTabList = computed((): string[] => { + return tabStore.getTabList.reduce((prev: string[], next) => { + if (next.meta && Reflect.has(next.meta, 'frameSrc')) { + prev.push(next.name as string); + } + return prev; + }, []); + }); + + function getAllFramePages(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] { + let res: AppRouteRecordRaw[] = []; + for (const route of routes) { + const { meta: { frameSrc } = {}, children } = route; + if (frameSrc) { + res.push(route); + } + if (children && children.length) { + res.push(...getAllFramePages(children)); + } + } + res = uniqBy(res, 'name'); + return res; + } + + function showIframe(item: AppRouteRecordRaw) { + return item.name === unref(currentRoute).name; + } + + function hasRenderFrame(name: string) { + if (!unref(getShowMultipleTab)) { + return router.currentRoute.value.name === name; + } + return unref(getOpenTabList).includes(name); + } + + return { hasRenderFrame, getFramePages, showIframe, getAllFramePages }; +} diff --git a/src/layouts/page/index.vue b/src/layouts/page/index.vue new file mode 100644 index 00000000000..780ef5d8ad1 --- /dev/null +++ b/src/layouts/page/index.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/layouts/page/transition.ts b/src/layouts/page/transition.ts new file mode 100644 index 00000000000..9e93009d244 --- /dev/null +++ b/src/layouts/page/transition.ts @@ -0,0 +1,33 @@ +import type { FunctionalComponent } from 'vue'; +import type { RouteLocation } from 'vue-router'; + +export interface DefaultContext { + Component: FunctionalComponent & { type: Recordable }; + route: RouteLocation; +} + +export function getTransitionName({ + route, + openCache, + cacheTabs, + enableTransition, + def, +}: Pick & { + enableTransition: boolean; + openCache: boolean; + def: string; + cacheTabs: string[]; +}): string | undefined { + if (!enableTransition) { + return undefined; + } + + const isInCache = cacheTabs.includes(route.name as string); + const transitionName = 'fade-slide'; + let name: string | undefined = transitionName; + + if (openCache) { + name = isInCache && route.meta.loaded ? transitionName : undefined; + } + return name || (route.meta.transitionName as string) || def; +} diff --git a/src/locales/helper.ts b/src/locales/helper.ts new file mode 100644 index 00000000000..6c70c4cbc48 --- /dev/null +++ b/src/locales/helper.ts @@ -0,0 +1,37 @@ +import type { LocaleType } from '#/config'; + +import { set } from 'lodash-es'; + +export const loadLocalePool: LocaleType[] = []; + +export function setHtmlPageLang(locale: LocaleType) { + document.querySelector('html')?.setAttribute('lang', locale); +} + +export function setLoadLocalePool(cb: (loadLocalePool: LocaleType[]) => void) { + cb(loadLocalePool); +} + +export function genMessage(langs: Record>, prefix = 'lang') { + const obj: Recordable = {}; + + Object.keys(langs).forEach((key) => { + const langFileModule = langs[key].default; + let fileName = key.replace(`./${prefix}/`, '').replace(/^\.\//, ''); + const lastIndex = fileName.lastIndexOf('.'); + fileName = fileName.substring(0, lastIndex); + const keyList = fileName.split('/'); + const moduleName = keyList.shift(); + const objKey = keyList.join('.'); + + if (moduleName) { + if (objKey) { + set(obj, moduleName, obj[moduleName] || {}); + set(obj[moduleName], objKey, langFileModule); + } else { + set(obj, moduleName, langFileModule || {}); + } + } + }); + return obj; +} diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts new file mode 100644 index 00000000000..cabc8d7de7a --- /dev/null +++ b/src/locales/lang/en.ts @@ -0,0 +1,12 @@ +import { genMessage } from '../helper'; +import antdLocale from 'ant-design-vue/es/locale/en_US'; + +const modules = import.meta.glob('./en/**/*.{json,ts,js}', { eager: true }); +export default { + message: { + ...genMessage(modules as Recordable, 'en'), + antdLocale, + }, + dateLocale: null, + dateLocaleName: 'en', +}; diff --git a/src/locales/lang/en/common.json b/src/locales/lang/en/common.json new file mode 100644 index 00000000000..e0e73f8d7ab --- /dev/null +++ b/src/locales/lang/en/common.json @@ -0,0 +1,17 @@ +{ + "okText": "OK", + "closeText": "Close", + "cancelText": "Cancel", + "loadingText": "Loading...", + "saveText": "Save", + "delText": "Delete", + "resetText": "Reset", + "searchText": "Search", + "queryText": "Search", + "inputText": "Please enter ", + "chooseText": "Please choose ", + "redo": "Refresh", + "back": "Back", + "light": "Light", + "dark": "Dark" +} diff --git a/src/locales/lang/en/component.json b/src/locales/lang/en/component.json new file mode 100644 index 00000000000..f8cf4f600d3 --- /dev/null +++ b/src/locales/lang/en/component.json @@ -0,0 +1,128 @@ +{ + "app": { + "searchNotData": "No search results yet", + "toSearch": "to search", + "toNavigate": "to navigate" + }, + "countdown": { + "normalText": "Get SMS code", + "sendText": "Reacquire in {0}s" + }, + "cropper": { + "selectImage": "Select Image", + "uploadSuccess": "Uploaded success!", + "imageTooBig": "Image too big", + "modalTitle": "Avatar upload", + "okText": "Confirm and upload", + "btn_reset": "Reset", + "btn_rotate_left": "Counterclockwise rotation", + "btn_rotate_right": "Clockwise rotation", + "btn_scale_x": "Flip horizontal", + "btn_scale_y": "Flip vertical", + "btn_zoom_in": "Zoom in", + "btn_zoom_out": "Zoom out", + "preview": "Preview" + }, + "drawer": { + "loadingText": "Loading...", + "cancelText": "Close", + "okText": "Confirm" + }, + "excel": { + "exportModalTitle": "Export data", + "fileType": "File type", + "fileName": "File name" + }, + "form": { + "putAway": "Put away", + "unfold": "Unfold", + "maxTip": "The number of characters should be less than {0}", + "apiSelectNotFound": "Wait for data loading to complete..." + }, + "icon": { + "placeholder": "Click the select icon", + "search": "Search icon", + "copy": "Copy icon successfully!" + }, + "menu": { + "search": "Menu search" + }, + "modal": { + "cancelText": "Close", + "okText": "Confirm", + "close": "Close", + "maximize": "Maximize", + "restore": "Restore" + }, + "table": { + "settingDens": "Density", + "settingDensDefault": "Default", + "settingDensMiddle": "Middle", + "settingDensSmall": "Compact", + "settingColumn": "Column settings", + "settingColumnShow": "Column display", + "settingIndexColumnShow": "Index Column", + "settingSelectColumnShow": "Selection Column", + "settingFixedLeft": "Fixed Left", + "settingFixedRight": "Fixed Right", + "settingFullScreen": "Full Screen", + "index": "Index", + "total": "total of {total}", + "selectionBarTips": "{count} records selected.", + "selectionBarClear": "Clear", + "selectionBarEmpty": "No records selected." + }, + "time": { + "before": " ago", + "after": " after", + "just": "just now", + "seconds": " seconds", + "minutes": " minutes", + "hours": " hours", + "days": " days" + }, + "tree": { + "selectAll": "Select All", + "unSelectAll": "Cancel Select", + "expandAll": "Expand All", + "unExpandAll": "Collapse all", + "checkStrictly": "Hierarchical association", + "checkUnStrictly": "Hierarchical independence" + }, + "upload": { + "save": "Save", + "upload": "Upload", + "imgUpload": "ImageUpload", + "uploaded": "Uploaded", + "operating": "Operating", + "del": "Delete", + "download": "download", + "saveWarn": "Please wait for the file to upload and save!", + "saveError": "There is no file successfully uploaded and cannot be saved!", + "preview": "Preview", + "choose": "Select the file", + "accept": "Support {0} format", + "acceptUpload": "Only upload files in {0} format", + "maxSize": "A single file does not exceed {0}MB ", + "maxSizeMultiple": "Only upload files up to {0}MB!", + "maxNumber": "Only upload up to {0} files", + "legend": "Legend", + "fileName": "File name", + "fileSize": "File size", + "fileStatue": "File status", + "pending": "Pending", + "startUpload": "Start upload", + "uploadSuccess": "Upload successfully", + "uploadError": "Upload failed", + "uploading": "Uploading", + "uploadWait": "Please wait for the file upload to finish", + "reUploadFailed": "Re-upload failed files" + }, + "verify": { + "error": "verification failed!", + "time": "The verification is successful and it takes {time} seconds!", + "redoTip": "Click the picture to refresh", + "dragText": "Hold down the slider and drag", + "successText": "Verified" + } +} diff --git a/src/locales/lang/en/layout.json b/src/locales/lang/en/layout.json new file mode 100644 index 00000000000..74612d3625b --- /dev/null +++ b/src/locales/lang/en/layout.json @@ -0,0 +1,96 @@ +{ + "footer": { + "onlinePreview": "Preview", + "onlineDocument": "Document" + }, + "header": { + "dropdownChangeApi": "Change Api", + "dropdownItemDoc": "Document", + "dropdownItemLoginOut": "Log Out", + "tooltipErrorLog": "Error log", + "tooltipLock": "Lock screen", + "tooltipNotify": "Notification", + "tooltipEntryFull": "Full Screen", + "tooltipExitFull": "Exit Full Screen", + "lockScreenPassword": "Lock screen password", + "lockScreen": "Lock screen", + "lockScreenBtn": "Locking", + "home": "Home" + }, + "multipleTab": { + "reload": "Refresh current", + "close": "Close current", + "closeLeft": "Close Left", + "closeRight": "Close Right", + "closeOther": "Close Other", + "closeAll": "Close All" + }, + "setting": { + "contentModeFull": "Full", + "contentModeFixed": "Fixed width", + "topMenuAlignLeft": "Left", + "topMenuAlignRight": "Center", + "topMenuAlignCenter": "Right", + "menuTriggerNone": "Not Show", + "menuTriggerBottom": "Bottom", + "menuTriggerTop": "Top", + "menuTypeSidebar": "Left menu mode", + "menuTypeMixSidebar": "Left menu mixed mode", + "menuTypeMix": "Top Menu Mix mode", + "menuTypeTopMenu": "Top menu mode", + "on": "On", + "off": "Off", + "minute": "Minute", + "operatingTitle": "Successful!", + "operatingContent": "The copy is successful, please go to src/settings/projectSetting.ts to modify the configuration!", + "resetSuccess": "Successfully reset!", + "copyBtn": "Copy", + "clearBtn": "Clear cache and to the login page", + "drawerTitle": "Configuration", + "darkMode": "Dark mode", + "navMode": "Navigation mode", + "interfaceFunction": "Interface function", + "interfaceDisplay": "Interface display", + "animation": "Animation", + "splitMenu": "Split menu", + "closeMixSidebarOnChange": "Switch page to close menu", + "sysTheme": "System theme", + "headerTheme": "Header theme", + "sidebarTheme": "Menu theme", + "menuDrag": "Drag Sidebar", + "menuSearch": "Menu search", + "menuAccordion": "Sidebar accordion", + "menuCollapse": "Collapse menu", + "collapseMenuDisplayName": "Collapse menu display name", + "topMenuLayout": "Top menu layout", + "menuCollapseButton": "Menu collapse button", + "contentMode": "Content area width", + "expandedMenuWidth": "Expanded menu width", + "breadcrumb": "Breadcrumbs", + "breadcrumbIcon": "Breadcrumbs Icon", + "tabs": "Tabs", + "tabDetail": "Tab Detail", + "tabsQuickBtn": "Tabs quick button", + "tabsRedoBtn": "Tabs redo button", + "tabsFoldBtn": "Tabs fold button", + "sidebar": "Sidebar", + "header": "Header", + "footer": "Footer", + "fullContent": "Full content", + "grayMode": "Gray mode", + "colorWeak": "Color Weak Mode", + "progress": "Progress", + "switchLoading": "Switch Loading", + "switchAnimation": "Switch animation", + "animationType": "Animation type", + "autoScreenLock": "Auto screen lock", + "notAutoScreenLock": "Not auto lock", + "fixedHeader": "Fixed header", + "fixedSideBar": "Fixed Sidebar", + "mixSidebarTrigger": "Mixed menu Trigger", + "triggerHover": "Hover", + "triggerClick": "Click", + "mixSidebarFixed": "Fixed expanded menu", + "autoCollapseTabsInFold": "Auto collapse tabs in fold" + } +} diff --git a/src/locales/lang/en/routes/basic.json b/src/locales/lang/en/routes/basic.json new file mode 100644 index 00000000000..59401719126 --- /dev/null +++ b/src/locales/lang/en/routes/basic.json @@ -0,0 +1,4 @@ +{ + "login": "Login", + "errorLogList": "Error Log" +} diff --git a/src/locales/lang/en/routes/dashboard.json b/src/locales/lang/en/routes/dashboard.json new file mode 100644 index 00000000000..ad3980c09d8 --- /dev/null +++ b/src/locales/lang/en/routes/dashboard.json @@ -0,0 +1,6 @@ +{ + "dashboard": "Dashboard", + "about": "About", + "workbench": "Workbench", + "analysis": "Analysis" +} diff --git a/src/locales/lang/en/routes/demo.json b/src/locales/lang/en/routes/demo.json new file mode 100644 index 00000000000..a290962ed54 --- /dev/null +++ b/src/locales/lang/en/routes/demo.json @@ -0,0 +1,180 @@ +{ + "charts": { + "baiduMap": "Baidu map", + "aMap": "A map", + "googleMap": "Google map", + "charts": "Chart", + "map": "Map", + "line": "Line", + "pie": "Pie" + }, + "comp": { + "comp": "Component", + "basic": "Basic", + "transition": "Animation", + "countTo": "Count To", + "scroll": "Scroll", + "scrollBasic": "Basic", + "scrollAction": "Scroll Function", + "virtualScroll": "Virtual Scroll", + "tree": "Tree", + "treeBasic": "Basic", + "editTree": "Searchable/toolbar", + "actionTree": "Function operation", + "modal": "Modal", + "drawer": "Drawer", + "desc": "Desc", + "verify": "Verify", + "verifyDrag": "Drag ", + "verifyRotate": "Picture Restore", + "qrcode": "QR code", + "strength": "Password strength", + "upload": "Upload", + "loading": "Loading", + "time": "Relative Time", + "cropperImage": "Cropper Image", + "cardList": "Card List" + }, + "editor": { + "editor": "Editor", + "codeEditor": "Code editor", + "markdown": "Markdown editor", + "tinymce": "Rich text", + "tinymceBasic": "Basic", + "tinymceForm": "embedded form" + }, + "excel": { + "excel": "Excel", + "customExport": "Select export format", + "jsonExport": "JSON data export", + "arrayExport": "Array data export", + "importExcel": "Import" + }, + "feat": { + "feat": "Page Function", + "icon": "Icon", + "screenShot": "Screen Shot", + "tabs": "Tabs", + "tabDetail": "Tab Detail", + "sessionTimeout": "Session Timeout", + "print": "Print", + "contextMenu": "Context Menu", + "download": "Download", + "clickOutSide": "ClickOutSide", + "imgPreview": "Picture Preview", + "copy": "Clipboard", + "ellipsis": "EllipsisText", + "msg": "Message prompt", + "watermark": "Watermark", + "ripple": "Ripple", + "fullScreen": "Full Screen", + "errorLog": "Error Log", + "tab": "Tab with parameters", + "tab1": "Tab with parameter 1", + "tab2": "Tab with parameter 2", + "menu": "Menu with parameters", + "menu1": "Menu with parameters 1", + "menu2": "Menu with parameters 2", + "ws": "Websocket test", + "breadcrumb": "Breadcrumbs", + "breadcrumbFlat": "Flat Mode", + "breadcrumbFlatDetail": "Flat mode details", + "requestDemo": "Retry request demo", + "breadcrumbChildren": "Level mode", + "breadcrumbChildrenDetail": "Level mode detail" + }, + "flow": { + "name": "Graphics editor", + "flowChart": "FlowChart" + }, + "form": { + "form": "Form", + "basic": "Basic", + "useForm": "useForm", + "refForm": "RefForm", + "advancedForm": "Shrinkable", + "ruleForm": "Form validation", + "dynamicForm": "Dynamic", + "customerForm": "Custom", + "appendForm": "Append", + "tabsForm": "TabsForm" + }, + "iframe": { + "frame": "External", + "antv": "antVue doc (embedded)", + "doc": "Project doc (embedded)", + "docExternal": "Project doc (external)" + }, + "level": { + "level": "MultiMenu" + }, + "page": { + "page": "Page", + "form": "Form", + "formBasic": "Basic Form", + "formStep": "Step Form", + "formHigh": "Advanced Form", + "desc": "Details", + "descBasic": "Basic Details", + "descHigh": "Advanced Details", + "result": "Result", + "resultSuccess": "Success", + "resultFail": "Failed", + "account": "Personal", + "accountCenter": "Personal Center", + "accountSetting": "Personal Settings", + "exception": "Exception", + "netWorkError": "Network Error", + "notData": "No data", + "list": "List page", + "listCard": "Card list", + "basic": "Basic list", + "listBasic": "Basic list", + "listSearch": "Search list" + }, + "permission": { + "permission": "Permission", + "front": "front-end", + "frontPage": "Page", + "frontBtn": "Button", + "frontTestA": "Test page A", + "frontTestB": "Test page B", + "back": "background", + "backPage": "Page", + "backBtn": "Button" + }, + "steps": { + "page": "Intro page" + }, + "system": { + "moduleName": "System management", + "account": "Account management", + "vxeTableAccount": "Account management(VxeTable)", + "account_detail": "Account detail", + "password": "Change password", + "dept": "Department management", + "menu": "Menu management", + "role": "Role management" + }, + "table": { + "table": "Table", + "basic": "Basic", + "treeTable": "Tree", + "fetchTable": "Remote loading", + "fixedColumn": "Fixed column", + "customerCell": "Custom column", + "formTable": "Open search", + "useTable": "UseTable", + "refTable": "RefTable", + "multipleHeader": "MultiLevel header", + "mergeHeader": "Merge cells", + "expandTable": "Expandable table", + "fixedHeight": "Fixed height", + "footerTable": "Footer", + "editCellTable": "Editable cell", + "editRowTable": "Editable row", + "authColumn": "Auth column", + "resizeParentHeightTable": "resizeParentHeightTable", + "vxeTable": "VxeTable" + } +} \ No newline at end of file diff --git a/src/locales/lang/en/sys.json b/src/locales/lang/en/sys.json new file mode 100644 index 00000000000..8d86220a1ea --- /dev/null +++ b/src/locales/lang/en/sys.json @@ -0,0 +1,92 @@ +{ + "api": { + "operationSuccess": "Operation Success", + "operationFailed": "Operation failed", + "errorTip": "Error Tip", + "successTip": "Success Tip", + "errorMessage": "The operation failed, the system is abnormal!", + "timeoutMessage": "Login timed out, please log in again!", + "apiTimeoutMessage": "The interface request timed out, please refresh the page and try again!", + "apiRequestFailed": "The interface request failed, please try again later!", + "networkException": "network anomaly", + "networkExceptionMsg": "Please check if your network connection is normal! The network is abnormal", + "errMsg401": "The user does not have permission (token, user name, password error)!", + "errMsg403": "The user is authorized, but access is forbidden!", + "errMsg404": "Network request error, the resource was not found!", + "errMsg405": "Network request error, request method not allowed!", + "errMsg408": "Network request timed out!", + "errMsg500": "Server error, please contact the administrator!", + "errMsg501": "The network is not implemented!", + "errMsg502": "Network Error!", + "errMsg503": "The service is unavailable, the server is temporarily overloaded or maintained!", + "errMsg504": "Network timeout!", + "errMsg505": "The http version does not support the request!" + }, + "app": { + "logoutTip": "Reminder", + "logoutMessage": "Confirm to exit the system?", + "menuLoading": "Menu loading..." + }, + "errorLog": { + "tableTitle": "Error log list", + "tableColumnType": "Type", + "tableColumnDate": "Time", + "tableColumnFile": "File", + "tableColumnMsg": "Error message", + "tableColumnStackMsg": "Stack info", + "tableActionDesc": "Details", + "modalTitle": "Error details", + "fireVueError": "Fire vue error", + "fireResourceError": "Fire resource error", + "fireAjaxError": "Fire ajax error", + "enableMessage": "Only effective when useErrorHandle=true in `/src/settings/projectSetting.ts`." + }, + "exception": { + "backLogin": "Back Login", + "backHome": "Back Home", + "subTitle403": "Sorry, you don't have access to this page.", + "subTitle404": "Sorry, the page you visited does not exist.", + "subTitle500": "Sorry, the server is reporting an error.", + "noDataTitle": "No data on the current page.", + "networkErrorTitle": "Network Error", + "networkErrorSubTitle": "Sorry,Your network connection has been disconnected, please check your network!" + }, + "lock": { + "unlock": "Click to unlock", + "alert": "Lock screen password error", + "backToLogin": "Back to login", + "entry": "Enter the system", + "placeholder": "Please enter the lock screen password or user password" + }, + "login": { + "backSignIn": "Back sign in", + "mobileSignInFormTitle": "Mobile sign in", + "qrSignInFormTitle": "Qr code sign in", + "signInFormTitle": "Sign in", + "signUpFormTitle": "Sign up", + "forgetFormTitle": "Reset password", + "signInTitle": "Backstage management system", + "signInDesc": "Enter your personal details and get started!", + "policy": "I agree to the xxx Privacy Policy", + "scanSign": "scanning the code to complete the login", + "loginButton": "Sign in", + "registerButton": "Sign up", + "rememberMe": "Remember me", + "forgetPassword": "Forget Password?", + "otherSignIn": "Sign in with", + "loginSuccessTitle": "Login successful", + "loginSuccessDesc": "Welcome back", + "accountPlaceholder": "Please input username", + "passwordPlaceholder": "Please input password", + "smsPlaceholder": "Please input sms code", + "mobilePlaceholder": "Please input mobile", + "policyPlaceholder": "Register after checking", + "diffPwd": "The two passwords are inconsistent", + "userName": "Username", + "password": "Password", + "confirmPassword": "Confirm Password", + "email": "Email", + "smsCode": "SMS code", + "mobile": "Mobile" + } +} diff --git a/src/locales/lang/zh-CN/antdLocale/DatePicker.json b/src/locales/lang/zh-CN/antdLocale/DatePicker.json new file mode 100644 index 00000000000..d4abbdeaa20 --- /dev/null +++ b/src/locales/lang/zh-CN/antdLocale/DatePicker.json @@ -0,0 +1,19 @@ +{ + "lang": { + "shortWeekDays": ["日", "一", "二", "三", "四", "五", "六"], + "shortMonths": [ + "1月", + "2月", + "3月", + "4月", + "5月", + "6月", + "7月", + "8月", + "9月", + "10月", + "11月", + "12月" + ] + } +} diff --git a/src/locales/lang/zh-CN/common.json b/src/locales/lang/zh-CN/common.json new file mode 100644 index 00000000000..19647c8a8d3 --- /dev/null +++ b/src/locales/lang/zh-CN/common.json @@ -0,0 +1,20 @@ +{ + "okText": "确认", + "closeText": "关闭", + "cancelText": "取消", + "loadingText": "加载中...", + "saveText": "保存", + "delText": "删除", + "resetText": "重置", + "searchText": "搜索", + "queryText": "查询", + + "inputText": "请输入", + "chooseText": "请选择", + + "redo": "刷新", + "back": "返回", + + "light": "亮色主题", + "dark": "黑暗主题" +} diff --git a/src/locales/lang/zh-CN/component.json b/src/locales/lang/zh-CN/component.json new file mode 100644 index 00000000000..952d99ddc25 --- /dev/null +++ b/src/locales/lang/zh-CN/component.json @@ -0,0 +1,128 @@ +{ + "app": { + "searchNotData": "暂无搜索结果", + "toSearch": "确认", + "toNavigate": "切换" + }, + "countdown": { + "normalText": "获取验证码", + "sendText": "{0}秒后重新获取" + }, + "cropper": { + "selectImage": "选择图片", + "uploadSuccess": "上传成功", + "imageTooBig": "图片超限", + "modalTitle": "头像上传", + "okText": "确认并上传", + "btn_reset": "重置", + "btn_rotate_left": "逆时针旋转", + "btn_rotate_right": "顺时针旋转", + "btn_scale_x": "水平翻转", + "btn_scale_y": "垂直翻转", + "btn_zoom_in": "放大", + "btn_zoom_out": "缩小", + "preview": "预览" + }, + "drawer": { + "loadingText": "加载中...", + "cancelText": "关闭", + "okText": "确认" + }, + "excel": { + "exportModalTitle": "导出数据", + "fileType": "文件类型", + "fileName": "文件名" + }, + "form": { + "putAway": "收起", + "unfold": "展开", + "maxTip": "字符数应小于{0}位", + "apiSelectNotFound": "请等待数据加载完成..." + }, + "icon": { + "placeholder": "点击选择图标", + "search": "搜索图标", + "copy": "复制图标成功!" + }, + "menu": { + "search": "菜单搜索" + }, + "modal": { + "cancelText": "关闭", + "okText": "确认", + "close": "关闭", + "maximize": "最大化", + "restore": "还原" + }, + "table": { + "settingDens": "密度", + "settingDensDefault": "默认", + "settingDensMiddle": "中等", + "settingDensSmall": "紧凑", + "settingColumn": "列设置", + "settingColumnShow": "列展示", + "settingIndexColumnShow": "序号列", + "settingSelectColumnShow": "勾选列", + "settingFixedLeft": "固定到左侧", + "settingFixedRight": "固定到右侧", + "settingFullScreen": "全屏", + "index": "序号", + "total": "共 {total} 条数据", + "selectionBarTips": "已选择{count}条记录", + "selectionBarClear": "清空", + "selectionBarEmpty": "未选中任何记录" + }, + "time": { + "before": "前", + "after": "后", + "just": "刚刚", + "seconds": "秒", + "minutes": "分钟", + "hours": "小时", + "days": "天" + }, + "tree": { + "selectAll": "选择全部", + "unSelectAll": "取消选择", + "expandAll": "展开全部", + "unExpandAll": "折叠全部", + "checkStrictly": "层级关联", + "checkUnStrictly": "层级独立" + }, + "upload": { + "save": "保存", + "upload": "上传", + "imgUpload": "图片上传", + "uploaded": "已上传", + "operating": "操作", + "del": "删除", + "download": "下载", + "saveWarn": "请等待文件上传后,保存!", + "saveError": "没有上传成功的文件,无法保存!", + "preview": "预览", + "choose": "选择文件", + "accept": "支持{0}格式", + "acceptUpload": "只能上传{0}格式文件", + "maxSize": "单个文件不超过{0}MB", + "maxSizeMultiple": "只能上传不超过{0}MB的文件!", + "maxNumber": "最多只能上传{0}个文件", + "legend": "略缩图", + "fileName": "文件名", + "fileSize": "文件大小", + "fileStatue": "状态", + "pending": "待上传", + "startUpload": "开始上传", + "uploadSuccess": "上传成功", + "uploadError": "上传失败", + "uploading": "上传中", + "uploadWait": "请等待文件上传结束后操作", + "reUploadFailed": "重新上传失败文件" + }, + "verify": { + "error": "验证失败!", + "time": "验证校验成功,耗时{time}秒!", + "redoTip": "点击图片可刷新", + "dragText": "请按住滑块拖动", + "successText": "验证通过" + } +} diff --git a/src/locales/lang/zh-CN/layout.json b/src/locales/lang/zh-CN/layout.json new file mode 100644 index 00000000000..d0673813874 --- /dev/null +++ b/src/locales/lang/zh-CN/layout.json @@ -0,0 +1,96 @@ +{ + "footer": { + "onlinePreview": "在线预览", + "onlineDocument": "在线文档" + }, + "header": { + "dropdownChangeApi": "切换API", + "dropdownItemDoc": "文档", + "dropdownItemLoginOut": "退出系统", + "tooltipErrorLog": "错误日志", + "tooltipLock": "锁定屏幕", + "tooltipNotify": "消息通知", + "tooltipEntryFull": "全屏", + "tooltipExitFull": "退出全屏", + "lockScreenPassword": "锁屏密码", + "lockScreen": "锁定屏幕", + "lockScreenBtn": "锁定", + "home": "首页" + }, + "multipleTab": { + "reload": "重新加载", + "close": "关闭标签页", + "closeLeft": "关闭左侧标签页", + "closeRight": "关闭右侧标签页", + "closeOther": "关闭其它标签页", + "closeAll": "关闭全部标签页" + }, + "setting": { + "contentModeFull": "流式", + "contentModeFixed": "定宽", + "topMenuAlignLeft": "居左", + "topMenuAlignRight": "居中", + "topMenuAlignCenter": "居右", + "menuTriggerNone": "不显示", + "menuTriggerBottom": "底部", + "menuTriggerTop": "顶部", + "menuTypeSidebar": "左侧菜单模式", + "menuTypeMixSidebar": "左侧菜单混合模式", + "menuTypeMix": "顶部菜单混合模式", + "menuTypeTopMenu": "顶部菜单模式", + "on": "开", + "off": "关", + "minute": "分钟", + "operatingTitle": "操作成功", + "operatingContent": "复制成功,请到 src/settings/projectSetting.ts 中修改配置!", + "resetSuccess": "重置成功!", + "copyBtn": "拷贝", + "clearBtn": "清空缓存并返回登录页", + "drawerTitle": "项目配置", + "darkMode": "主题", + "navMode": "导航栏模式", + "interfaceFunction": "界面功能", + "interfaceDisplay": "界面显示", + "animation": "动画", + "splitMenu": "分割菜单", + "closeMixSidebarOnChange": "切换页面关闭菜单", + "sysTheme": "系统主题", + "headerTheme": "顶栏主题", + "sidebarTheme": "菜单主题", + "menuDrag": "侧边菜单拖拽", + "menuSearch": "菜单搜索", + "menuAccordion": "侧边菜单手风琴模式", + "menuCollapse": "折叠菜单", + "collapseMenuDisplayName": "折叠菜单显示名称", + "topMenuLayout": "顶部菜单布局", + "menuCollapseButton": "菜单折叠按钮", + "contentMode": "内容区域宽度", + "expandedMenuWidth": "菜单展开宽度", + "breadcrumb": "面包屑", + "breadcrumbIcon": "面包屑图标", + "tabs": "标签页", + "tabDetail": "标签详情页", + "tabsQuickBtn": "标签页快捷按钮", + "tabsRedoBtn": "标签页刷新按钮", + "tabsFoldBtn": "标签页折叠按钮", + "sidebar": "左侧菜单", + "header": "顶栏", + "footer": "页脚", + "fullContent": "全屏内容", + "grayMode": "灰色模式", + "colorWeak": "色弱模式", + "progress": "顶部进度条", + "switchLoading": "切换loading", + "switchAnimation": "切换动画", + "animationType": "动画类型", + "autoScreenLock": "自动锁屏", + "notAutoScreenLock": "不自动锁屏", + "fixedHeader": "固定header", + "fixedSideBar": "固定Sidebar", + "mixSidebarTrigger": "混合菜单触发方式", + "triggerHover": "悬停", + "triggerClick": "点击", + "mixSidebarFixed": "固定展开菜单", + "autoCollapseTabsInFold": "fold模式下自动收起标签页" + } +} diff --git a/src/locales/lang/zh-CN/routes/basic.json b/src/locales/lang/zh-CN/routes/basic.json new file mode 100644 index 00000000000..830ff12ff13 --- /dev/null +++ b/src/locales/lang/zh-CN/routes/basic.json @@ -0,0 +1,4 @@ +{ + "login": "登录", + "errorLogList": "错误日志列表" +} diff --git a/src/locales/lang/zh-CN/routes/dashboard.json b/src/locales/lang/zh-CN/routes/dashboard.json new file mode 100644 index 00000000000..f5fba47b287 --- /dev/null +++ b/src/locales/lang/zh-CN/routes/dashboard.json @@ -0,0 +1,6 @@ +{ + "dashboard": "Dashboard", + "about": "关于", + "workbench": "工作台", + "analysis": "分析页" +} diff --git a/src/locales/lang/zh-CN/routes/demo.json b/src/locales/lang/zh-CN/routes/demo.json new file mode 100644 index 00000000000..e517d470c23 --- /dev/null +++ b/src/locales/lang/zh-CN/routes/demo.json @@ -0,0 +1,179 @@ +{ + "charts": { + "baiduMap": "百度地图", + "aMap": "高德地图", + "googleMap": "谷歌地图", + "charts": "图表", + "map": "地图", + "line": "折线图", + "pie": "饼图" + }, + "comp": { + "comp": "组件", + "basic": "基础组件", + "transition": "动画组件", + "countTo": "数字动画", + "scroll": "滚动组件", + "scrollBasic": "基础滚动", + "scrollAction": "滚动函数", + "virtualScroll": "虚拟滚动", + "tree": "Tree", + "treeBasic": "基础树", + "editTree": "可搜索/工具栏", + "actionTree": "函数操作示例", + "modal": "弹窗扩展", + "drawer": "抽屉扩展", + "desc": "详情组件", + "verify": "验证组件", + "verifyDrag": "拖拽校验", + "verifyRotate": "图片还原", + "qrcode": "二维码组件", + "strength": "密码强度组件", + "upload": "上传组件", + "loading": "Loading", + "time": "相对时间", + "cropperImage": "图片裁剪", + "cardList": "卡片列表" + }, + "editor": { + "editor": "编辑器", + "codeEditor": "代码编辑器", + "markdown": "markdown编辑器", + "tinymce": "富文本", + "tinymceBasic": "基础使用", + "tinymceForm": "嵌入form" + }, + "excel": { + "excel": "Excel", + "customExport": "选择导出格式", + "jsonExport": "JSON数据导出", + "arrayExport": "Array数据导出", + "importExcel": "导入" + }, + "feat": { + "feat": "功能", + "icon": "图标", + "screenShot": "截图", + "sessionTimeout": "登录过期", + "tabs": "标签页操作", + "tabDetail": "标签详情页", + "print": "打印", + "contextMenu": "右键菜单", + "download": "文件下载", + "clickOutSide": "ClickOutSide组件", + "imgPreview": "图片预览", + "copy": "剪切板", + "ellipsis": "文本省略", + "msg": "消息提示", + "watermark": "水印", + "ripple": "水波纹", + "fullScreen": "全屏", + "errorLog": "错误日志", + "tab": "Tab带参", + "tab1": "Tab带参1", + "tab2": "Tab带参2", + "menu": "Menu带参", + "menu1": "Menu带参1", + "menu2": "Menu带参2", + "ws": "websocket测试", + "breadcrumb": "面包屑导航", + "breadcrumbFlat": "平级模式", + "requestDemo": "测试请求重试", + "breadcrumbFlatDetail": "平级详情", + "breadcrumbChildren": "层级模式", + "breadcrumbChildrenDetail": "层级详情" + }, + "flow": { + "name": "图形编辑器", + "flowChart": "流程图" + }, + "form": { + "form": "Form", + "basic": "基础表单", + "useForm": "useForm", + "refForm": "RefForm", + "advancedForm": "可收缩表单", + "ruleForm": "表单验证", + "dynamicForm": "动态表单", + "customerForm": "自定义组件", + "appendForm": "表单增删示例", + "tabsForm": "标签页+多级field" + }, + "iframe": { + "frame": "外部页面", + "antv": "antVue文档(内嵌)", + "doc": "项目文档(内嵌)", + "docExternal": "项目文档(外链)" + }, + "level": { + "level": "多级菜单" + }, + "page": { + "page": "页面", + "form": "表单页", + "formBasic": "基础表单", + "formStep": "分步表单", + "formHigh": "高级表单", + "desc": "详情页", + "descBasic": "基础详情页", + "descHigh": "高级详情页", + "result": "结果页", + "resultSuccess": "成功页", + "resultFail": "失败页", + "account": "个人页", + "accountCenter": "个人中心", + "accountSetting": "个人设置", + "exception": "异常页", + "netWorkError": "网络错误", + "notData": "无数据", + "list": "列表页", + "listCard": "卡片列表", + "listBasic": "标准列表", + "listSearch": "搜索列表" + }, + "permission": { + "permission": "权限管理", + "front": "基于前端权限", + "frontPage": "页面权限", + "frontBtn": "按钮权限", + "frontTestA": "权限测试页A", + "frontTestB": "权限测试页B", + "back": "基于后台权限", + "backPage": "页面权限", + "backBtn": "按钮权限" + }, + "steps": { + "page": "引导页" + }, + "system": { + "moduleName": "系统管理", + "account": "账号管理", + "vxeTableAccount": "账号管理(VxeTable)", + "account_detail": "账号详情", + "password": "修改密码", + "dept": "部门管理", + "menu": "菜单管理", + "role": "角色管理" + }, + "table": { + "table": "Table", + "basic": "基础表格", + "treeTable": "树形表格", + "fetchTable": "远程加载示例", + "fixedColumn": "固定列", + "customerCell": "自定义列", + "formTable": "开启搜索区域", + "useTable": "UseTable", + "refTable": "RefTable", + "multipleHeader": "多级表头", + "mergeHeader": "合并单元格", + "expandTable": "可展开表格", + "fixedHeight": "定高/头部自定义", + "footerTable": "表尾行合计", + "editCellTable": "可编辑单元格", + "editRowTable": "可编辑行", + "authColumn": "权限列", + "resizeParentHeightTable": "继承父元素高度", + "vxeTable": "VxeTable" + } +} \ No newline at end of file diff --git a/src/locales/lang/zh-CN/sys.json b/src/locales/lang/zh-CN/sys.json new file mode 100644 index 00000000000..591eb7b688a --- /dev/null +++ b/src/locales/lang/zh-CN/sys.json @@ -0,0 +1,92 @@ +{ + "api": { + "operationSuccess": "操作成功", + "operationFailed": "操作失败", + "errorTip": "错误提示", + "successTip": "成功提示", + "errorMessage": "操作失败,系统异常!", + "timeoutMessage": "登录超时,请重新登录!", + "apiTimeoutMessage": "接口请求超时,请刷新页面重试!", + "apiRequestFailed": "请求出错,请稍候重试", + "networkException": "网络异常", + "networkExceptionMsg": "网络异常,请检查您的网络连接是否正常!", + "errMsg401": "用户没有权限(令牌、用户名、密码错误)!", + "errMsg403": "用户得到授权,但是访问是被禁止的。!", + "errMsg404": "网络请求错误,未找到该资源!", + "errMsg405": "网络请求错误,请求方法未允许!", + "errMsg408": "网络请求超时!", + "errMsg500": "服务器错误,请联系管理员!", + "errMsg501": "网络未实现!", + "errMsg502": "网络错误!", + "errMsg503": "服务不可用,服务器暂时过载或维护!", + "errMsg504": "网络超时!", + "errMsg505": "http版本不支持该请求!" + }, + "app": { + "logoutTip": "温馨提醒", + "logoutMessage": "是否确认退出系统?", + "menuLoading": "菜单加载中..." + }, + "errorLog": { + "tableTitle": "错误日志列表", + "tableColumnType": "类型", + "tableColumnDate": "时间", + "tableColumnFile": "文件", + "tableColumnMsg": "错误信息", + "tableColumnStackMsg": "stack信息", + "tableActionDesc": "详情", + "modalTitle": "错误详情", + "fireVueError": "点击触发vue错误", + "fireResourceError": "点击触发资源加载错误", + "fireAjaxError": "点击触发ajax错误", + "enableMessage": "只在`/src/settings/projectSetting.ts` 内的useErrorHandle=true时生效." + }, + "exception": { + "backLogin": "返回登录", + "backHome": "返回首页", + "subTitle403": "抱歉,您无权访问此页面。", + "subTitle404": "抱歉,您访问的页面不存在。", + "subTitle500": "抱歉,服务器报告错误。", + "noDataTitle": "当前页无数据", + "networkErrorTitle": "网络错误", + "networkErrorSubTitle": "抱歉,您的网络连接已断开,请检查您的网络!" + }, + "lock": { + "unlock": "点击解锁", + "alert": "锁屏密码错误", + "backToLogin": "返回登录", + "entry": "进入系统", + "placeholder": "请输入锁屏密码或者用户密码" + }, + "login": { + "backSignIn": "返回", + "signInFormTitle": "登录", + "mobileSignInFormTitle": "手机登录", + "qrSignInFormTitle": "二维码登录", + "signUpFormTitle": "注册", + "forgetFormTitle": "重置密码", + "signInTitle": "开箱即用的中后台管理系统", + "signInDesc": "输入您的个人详细信息开始使用!", + "policy": "我同意xxx隐私政策", + "scanSign": "扫码后点击\"确认\",即可完成登录", + "loginButton": "登录", + "registerButton": "注册", + "rememberMe": "记住我", + "forgetPassword": "忘记密码?", + "otherSignIn": "其他登录方式", + "loginSuccessTitle": "登录成功", + "loginSuccessDesc": "欢迎回来", + "accountPlaceholder": "请输入账号", + "passwordPlaceholder": "请输入密码", + "smsPlaceholder": "请输入验证码", + "mobilePlaceholder": "请输入手机号码", + "policyPlaceholder": "勾选后才能注册", + "diffPwd": "两次输入密码不一致", + "userName": "账号", + "password": "密码", + "confirmPassword": "确认密码", + "email": "邮箱", + "smsCode": "短信验证码", + "mobile": "手机号码" + } +} diff --git a/src/locales/lang/zh_CN.ts b/src/locales/lang/zh_CN.ts new file mode 100644 index 00000000000..a13594bbe2a --- /dev/null +++ b/src/locales/lang/zh_CN.ts @@ -0,0 +1,18 @@ +import { genMessage } from '../helper'; +import antdLocale from 'ant-design-vue/es/locale/zh_CN'; +import { deepMerge } from '@/utils'; + +const modules = import.meta.glob('./zh-CN/**/*.{json,ts,js}', { eager: true }); + +export default { + message: { + ...genMessage(modules as Recordable, 'zh-CN'), + antdLocale: { + ...antdLocale, + DatePicker: deepMerge( + antdLocale.DatePicker, + genMessage(modules as Recordable, 'zh-CN').antdLocale.DatePicker, + ), + }, + }, +}; diff --git a/src/locales/setupI18n.ts b/src/locales/setupI18n.ts new file mode 100644 index 00000000000..80f9c6c71ef --- /dev/null +++ b/src/locales/setupI18n.ts @@ -0,0 +1,44 @@ +import type { App } from 'vue'; +import type { I18nOptions } from 'vue-i18n'; + +import { createI18n } from 'vue-i18n'; +import { setHtmlPageLang, setLoadLocalePool } from './helper'; +import { localeSetting } from '@/settings/localeSetting'; +import { useLocaleStoreWithOut } from '@/store/modules/locale'; + +const { fallback, availableLocales } = localeSetting; + +export let i18n: ReturnType; + +async function createI18nOptions(): Promise { + const localeStore = useLocaleStoreWithOut(); + const locale = localeStore.getLocale; + const defaultLocal = await import(`./lang/${locale}.ts`); + const message = defaultLocal.default?.message ?? {}; + + setHtmlPageLang(locale); + setLoadLocalePool((loadLocalePool) => { + loadLocalePool.push(locale); + }); + + return { + legacy: false, + locale, + fallbackLocale: fallback, + messages: { + [locale]: message, + }, + availableLocales: availableLocales, + sync: true, //If you don’t want to inherit locale from global scope, you need to set sync of i18n component option to false. + silentTranslationWarn: true, // true - warning off + missingWarn: false, + silentFallbackWarn: true, + }; +} + +// setup i18n instance with glob +export async function setupI18n(app: App) { + const options = await createI18nOptions(); + i18n = createI18n(options); + app.use(i18n); +} diff --git a/src/locales/useLocale.ts b/src/locales/useLocale.ts new file mode 100644 index 00000000000..2f2b677cbd0 --- /dev/null +++ b/src/locales/useLocale.ts @@ -0,0 +1,71 @@ +/** + * Multi-language related operations + */ +import type { LocaleType } from '#/config'; + +import { i18n } from './setupI18n'; +import { useLocaleStoreWithOut } from '@/store/modules/locale'; +import { unref, computed } from 'vue'; +import { loadLocalePool, setHtmlPageLang } from './helper'; +import { Locale } from 'ant-design-vue/es/locale'; + +interface LangModule { + message: Recordable; + dateLocale: Recordable; + dateLocaleName: string; +} + +function setI18nLanguage(locale: LocaleType) { + const localeStore = useLocaleStoreWithOut(); + + if (i18n.mode === 'legacy') { + i18n.global.locale = locale; + } else { + (i18n.global.locale as any).value = locale; + } + localeStore.setLocaleInfo({ locale }); + setHtmlPageLang(locale); +} + +export function useLocale() { + const localeStore = useLocaleStoreWithOut(); + const getLocale = computed(() => localeStore.getLocale); + const getShowLocalePicker = computed(() => localeStore.getShowPicker); + + const getAntdLocale = computed((): any => { + const localeMessage = i18n.global.getLocaleMessage<{ antdLocale: Locale }>(unref(getLocale)); + return localeMessage?.antdLocale ?? {}; + }); + + // Switching the language will change the locale of useI18n + // And submit to configuration modification + async function changeLocale(locale: LocaleType) { + const globalI18n = i18n.global; + const currentLocale = unref(globalI18n.locale); + if (currentLocale === locale) { + return locale; + } + + if (loadLocalePool.includes(locale)) { + setI18nLanguage(locale); + return locale; + } + const langModule = ((await import(`./lang/${locale}.ts`)) as any).default as LangModule; + if (!langModule) return; + + const { message } = langModule; + + globalI18n.setLocaleMessage(locale, message); + loadLocalePool.push(locale); + + setI18nLanguage(locale); + return locale; + } + + return { + getLocale, + getShowLocalePicker, + changeLocale, + getAntdLocale, + }; +} diff --git a/src/logics/error-handle/index.ts b/src/logics/error-handle/index.ts new file mode 100644 index 00000000000..8705528753f --- /dev/null +++ b/src/logics/error-handle/index.ts @@ -0,0 +1,183 @@ +/** + * Used to configure the global error handling function, which can monitor vue errors, script errors, static resource errors and Promise errors + */ + +import type { ErrorLogInfo } from '#/store'; + +import { useErrorLogStoreWithOut } from '@/store/modules/errorLog'; + +import { ErrorTypeEnum } from '@/enums/exceptionEnum'; +import { App } from 'vue'; +import projectSetting from '@/settings/projectSetting'; + +/** + * Handling error stack information + * @param error + */ +function processStackMsg(error: Error) { + if (!error.stack) { + return ''; + } + let stack = error.stack + .replace(/\n/gi, '') // Remove line breaks to save the size of the transmitted content + .replace(/\bat\b/gi, '@') // At in chrome, @ in ff + .split('@') // Split information with @ + .slice(0, 9) // The maximum stack length (Error.stackTraceLimit = 10), so only take the first 10 + .map((v) => v.replace(/^\s*|\s*$/g, '')) // Remove extra spaces + .join('~') // Manually add separators for later display + .replace(/\?[^:]+/gi, ''); // Remove redundant parameters of js file links (?x=1 and the like) + const msg = error.toString(); + if (stack.indexOf(msg) < 0) { + stack = msg + '@' + stack; + } + return stack; +} + +/** + * get comp name + * @param vm + */ +function formatComponentName(vm: any) { + if (vm.$root === vm) { + return { + name: 'root', + path: 'root', + }; + } + + const options = vm.$options as any; + if (!options) { + return { + name: 'anonymous', + path: 'anonymous', + }; + } + const name = options.name || options._componentTag; + return { + name: name, + path: options.__file, + }; +} + +/** + * Configure Vue error handling function + */ +function vueErrorHandler(err: unknown, vm: any, info: string) { + const errorLogStore = useErrorLogStoreWithOut(); + const { name, path } = formatComponentName(vm); + errorLogStore.addErrorLogInfo({ + type: ErrorTypeEnum.VUE, + name, + file: path, + message: (err as Error).message, + stack: processStackMsg(err as Error), + detail: info, + url: window.location.href, + }); +} + +/** + * Configure script error handling function + */ +export function scriptErrorHandler( + event: Event | string, + source?: string, + lineno?: number, + colno?: number, + error?: Error, +) { + if (event === 'Script error.' && !source) { + return false; + } + const errorInfo: Partial = {}; + colno = colno || (window.event && (window.event as any).errorCharacter) || 0; + errorInfo.message = event as string; + if (error?.stack) { + errorInfo.stack = error.stack; + } else { + errorInfo.stack = ''; + } + const name = source ? source.substr(source.lastIndexOf('/') + 1) : 'script'; + const errorLogStore = useErrorLogStoreWithOut(); + errorLogStore.addErrorLogInfo({ + type: ErrorTypeEnum.SCRIPT, + name: name, + file: source as string, + detail: 'lineno' + lineno, + url: window.location.href, + ...(errorInfo as Pick), + }); + return true; +} + +/** + * Configure Promise error handling function + */ +function registerPromiseErrorHandler() { + window.addEventListener( + 'unhandledrejection', + function (event) { + const errorLogStore = useErrorLogStoreWithOut(); + errorLogStore.addErrorLogInfo({ + type: ErrorTypeEnum.PROMISE, + name: 'Promise Error!', + file: 'none', + detail: 'promise error!', + url: window.location.href, + stack: 'promise error!', + message: event.reason, + }); + }, + true, + ); +} + +/** + * Configure monitoring resource loading error handling function + */ +function registerResourceErrorHandler() { + // Monitoring resource loading error(img,script,css,and jsonp) + window.addEventListener( + 'error', + function (e: Event) { + const target = e.target ? e.target : (e.srcElement as any); + const errorLogStore = useErrorLogStoreWithOut(); + errorLogStore.addErrorLogInfo({ + type: ErrorTypeEnum.RESOURCE, + name: 'Resource Error!', + file: (e.target || ({} as any)).currentSrc, + detail: JSON.stringify({ + tagName: target.localName, + html: target.outerHTML, + type: e.type, + }), + url: window.location.href, + stack: 'resource is not found', + message: (e.target || ({} as any)).localName + ' is load error', + }); + }, + true, + ); +} + +/** + * Configure global error handling + * @param app + */ +export function setupErrorHandle(app: App) { + const { useErrorHandle } = projectSetting; + if (!useErrorHandle) { + return; + } + // Vue exception monitoring; + app.config.errorHandler = vueErrorHandler; + + // script error + window.onerror = scriptErrorHandler; + + // promise exception + registerPromiseErrorHandler(); + + // Static resource exception + registerResourceErrorHandler(); +} diff --git a/src/logics/initAppConfig.ts b/src/logics/initAppConfig.ts new file mode 100644 index 00000000000..364a4cb9139 --- /dev/null +++ b/src/logics/initAppConfig.ts @@ -0,0 +1,77 @@ +/** + * Application configuration + */ +import type { ProjectConfig } from '#/config'; + +import { PROJ_CFG_KEY } from '@/enums/cacheEnum'; +import projectSetting from '@/settings/projectSetting'; + +import { updateDarkTheme } from '@/logics/theme/dark'; +import { updateHeaderBgColor, updateSidebarBgColor } from '@/logics/theme/updateBackground'; +import { updateColorWeak } from '@/logics/theme/updateColorWeak'; +import { updateGrayMode } from '@/logics/theme/updateGrayMode'; + +import { useAppStore } from '@/store/modules/app'; +import { useLocaleStore } from '@/store/modules/locale'; + +import { getCommonStoragePrefix, getStorageShortName } from '@/utils/env'; + +import { ThemeEnum } from '@/enums/appEnum'; +import { deepMerge } from '@/utils'; +import { Persistent } from '@/utils/cache/persistent'; + +// Initial project configuration +export function initAppConfigStore() { + const localeStore = useLocaleStore(); + const appStore = useAppStore(); + let projCfg: ProjectConfig = Persistent.getLocal(PROJ_CFG_KEY) as ProjectConfig; + projCfg = deepMerge(projectSetting, projCfg || {}); + const darkMode = appStore.getDarkMode; + const { + colorWeak, + grayMode, + + headerSetting: { bgColor: headerBgColor } = {}, + menuSetting: { bgColor } = {}, + } = projCfg; + try { + grayMode && updateGrayMode(grayMode); + colorWeak && updateColorWeak(colorWeak); + } catch (error) { + console.log(error); + } + appStore.setProjectConfig(projCfg); + + // init dark mode + updateDarkTheme(darkMode); + if (darkMode === ThemeEnum.DARK) { + updateHeaderBgColor(); + updateSidebarBgColor(); + } else { + headerBgColor && updateHeaderBgColor(headerBgColor); + bgColor && updateSidebarBgColor(bgColor); + } + // init store + localeStore.initLocale(); + + setTimeout(() => { + clearObsoleteStorage(); + }, 16); +} + +/** + * As the version continues to iterate, there will be more and more cache keys stored in localStorage. + * This method is used to delete useless keys + */ +export function clearObsoleteStorage() { + const commonPrefix = getCommonStoragePrefix(); + const shortPrefix = getStorageShortName(); + + [localStorage, sessionStorage].forEach((item: Storage) => { + Object.keys(item).forEach((key) => { + if (key && key.startsWith(commonPrefix) && !key.startsWith(shortPrefix)) { + item.removeItem(key); + } + }); + }); +} diff --git a/src/logics/mitt/routeChange.ts b/src/logics/mitt/routeChange.ts new file mode 100644 index 00000000000..e28e309febe --- /dev/null +++ b/src/logics/mitt/routeChange.ts @@ -0,0 +1,33 @@ +/** + * Used to monitor routing changes to change the status of menus and tabs. There is no need to monitor the route, because the route status change is affected by the page rendering time, which will be slow + */ + +import { mitt } from '@/utils/mitt'; +import type { RouteLocationNormalized } from 'vue-router'; +import { getRawRoute } from '@/utils'; + +const key = Symbol(); + +const emitter = mitt<{ + [key]: RouteLocationNormalized; +}>(); + +let lastChangeTab: RouteLocationNormalized; + +export function setRouteChange(lastChangeRoute: RouteLocationNormalized) { + const r = getRawRoute(lastChangeRoute); + emitter.emit(key, r); + lastChangeTab = r; +} + +export function listenerRouteChange( + callback: (route: RouteLocationNormalized) => void, + immediate = true, +) { + emitter.on(key, callback); + immediate && lastChangeTab && callback(lastChangeTab); +} + +export function removeTabChangeListener() { + emitter.clear(); +} diff --git a/src/logics/theme/dark.ts b/src/logics/theme/dark.ts new file mode 100644 index 00000000000..4d751cfd36c --- /dev/null +++ b/src/logics/theme/dark.ts @@ -0,0 +1,26 @@ +import { addClass, hasClass, removeClass } from '@/utils/domUtils'; + +export type CustomColorType = { + name: string; + light: string; + dark: string; +}; + +export async function updateDarkTheme(mode: string | null = 'light') { + const htmlRoot = document.getElementById('htmlRoot'); + if (!htmlRoot) { + return; + } + const hasDarkClass = hasClass(htmlRoot, 'dark'); + if (mode === 'dark') { + htmlRoot.setAttribute('data-theme', 'dark'); + if (!hasDarkClass) { + addClass(htmlRoot, 'dark'); + } + } else { + htmlRoot.setAttribute('data-theme', 'light'); + if (hasDarkClass) { + removeClass(htmlRoot, 'dark'); + } + } +} diff --git a/src/logics/theme/index.ts b/src/logics/theme/index.ts new file mode 100644 index 00000000000..d8e80b774e0 --- /dev/null +++ b/src/logics/theme/index.ts @@ -0,0 +1 @@ +export async function changeTheme(_color: string) {} diff --git a/src/logics/theme/updateBackground.ts b/src/logics/theme/updateBackground.ts new file mode 100644 index 00000000000..3b2d8be1039 --- /dev/null +++ b/src/logics/theme/updateBackground.ts @@ -0,0 +1,76 @@ +import { colorIsDark, lighten, darken } from '@/utils/color'; +import { useAppStore } from '@/store/modules/app'; +import { ThemeEnum } from '@/enums/appEnum'; +import { setCssVar } from './util'; + +const HEADER_BG_COLOR_VAR = '--header-bg-color'; +const HEADER_BG_HOVER_COLOR_VAR = '--header-bg-hover-color'; +const HEADER_MENU_ACTIVE_BG_COLOR_VAR = '--header-active-menu-bg-color'; + +const SIDER_DARK_BG_COLOR = '--sider-dark-bg-color'; +const SIDER_DARK_DARKEN_BG_COLOR = '--sider-dark-darken-bg-color'; +const SIDER_LIGHTEN_BG_COLOR = '--sider-dark-lighten-bg-color'; + +/** + * Change the background color of the top header + * @param color + */ +export function updateHeaderBgColor(color?: string) { + const appStore = useAppStore(); + const darkMode = appStore.getDarkMode === ThemeEnum.DARK; + if (!color) { + if (darkMode) { + color = '#151515'; + } else { + color = appStore.getHeaderSetting.bgColor; + } + } + + // bg color + setCssVar(HEADER_BG_COLOR_VAR, color); + + // hover color + const hoverColor = lighten(color!, 6); + setCssVar(HEADER_BG_HOVER_COLOR_VAR, hoverColor); + setCssVar(HEADER_MENU_ACTIVE_BG_COLOR_VAR, hoverColor); + + // Determine the depth of the color value and automatically switch the theme + const isDark = colorIsDark(color!); + + appStore.setProjectConfig({ + headerSetting: { + theme: isDark || darkMode ? ThemeEnum.DARK : ThemeEnum.LIGHT, + }, + }); +} + +/** + * Change the background color of the left menu + * @param color bg color + */ +export function updateSidebarBgColor(color?: string) { + const appStore = useAppStore(); + + // if (!isHexColor(color)) return; + const darkMode = appStore.getDarkMode === ThemeEnum.DARK; + if (!color) { + if (darkMode) { + color = '#212121'; + } else { + color = appStore.getMenuSetting.bgColor; + } + } + setCssVar(SIDER_DARK_BG_COLOR, color); + setCssVar(SIDER_DARK_DARKEN_BG_COLOR, darken(color!, 6)); + setCssVar(SIDER_LIGHTEN_BG_COLOR, lighten(color!, 5)); + + // only #ffffff is light + // Only when the background color is #fff, the theme of the menu will be changed to light + const isLight = ['#fff', '#ffffff'].includes(color!.toLowerCase()); + + appStore.setProjectConfig({ + menuSetting: { + theme: isLight && !darkMode ? ThemeEnum.LIGHT : ThemeEnum.DARK, + }, + }); +} diff --git a/src/logics/theme/updateColorWeak.ts b/src/logics/theme/updateColorWeak.ts new file mode 100644 index 00000000000..8a0e64a279a --- /dev/null +++ b/src/logics/theme/updateColorWeak.ts @@ -0,0 +1,9 @@ +import { toggleClass } from './util'; + +/** + * Change the status of the project's color weakness mode + * @param colorWeak + */ +export function updateColorWeak(colorWeak: boolean) { + toggleClass(colorWeak, 'color-weak', document.documentElement); +} diff --git a/src/logics/theme/updateGrayMode.ts b/src/logics/theme/updateGrayMode.ts new file mode 100644 index 00000000000..0fd16fe6977 --- /dev/null +++ b/src/logics/theme/updateGrayMode.ts @@ -0,0 +1,9 @@ +import { toggleClass } from './util'; + +/** + * Change project gray mode status + * @param gray + */ +export function updateGrayMode(gray: boolean) { + toggleClass(gray, 'gray-mode', document.documentElement); +} diff --git a/src/logics/theme/util.ts b/src/logics/theme/util.ts new file mode 100644 index 00000000000..30aef370187 --- /dev/null +++ b/src/logics/theme/util.ts @@ -0,0 +1,11 @@ +const docEle = document.documentElement; +export function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) { + const targetEl = target || document.body; + let { className } = targetEl; + className = className.replace(clsName, ''); + targetEl.className = flag ? `${className} ${clsName} ` : className; +} + +export function setCssVar(prop: string, val: any, dom = docEle) { + dom.style.setProperty(prop, val); +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 00000000000..1c4c808b4a3 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,64 @@ +import 'uno.css'; +import '@/design/index.less'; +import '@/components/VxeTable/src/css/index.scss'; +import 'ant-design-vue/dist/reset.css'; +// Register icon sprite +import 'virtual:svg-icons-register'; + +import { createApp } from 'vue'; + +import { registerGlobComp } from '@/components/registerGlobComp'; +import { setupGlobDirectives } from '@/directives'; +import { setupI18n } from '@/locales/setupI18n'; +import { setupErrorHandle } from '@/logics/error-handle'; +import { initAppConfigStore } from '@/logics/initAppConfig'; +import { router, setupRouter } from '@/router'; +import { setupRouterGuard } from '@/router/guard'; +import { setupStore } from '@/store'; + +import App from './App.vue'; + +async function bootstrap() { + const app = createApp(App); + + // Configure store + // 配置 store + setupStore(app); + + // Initialize internal system configuration + // 初始化内部系统配置 + initAppConfigStore(); + + // Register global components + // 注册全局组件 + registerGlobComp(app); + + // Multilingual configuration + // 多语言配置 + // Asynchronous case: language files may be obtained from the server side + // 异步案例:语言文件可能从服务器端获取 + await setupI18n(app); + + // Configure routing + // 配置路由 + setupRouter(app); + + // router-guard + // 路由守卫 + setupRouterGuard(router); + + // Register global directive + // 注册全局指令 + setupGlobDirectives(app); + + // Configure global error handling + // 配置全局错误处理 + setupErrorHandle(app); + + // https://next.router.vuejs.org/api/#isready + // await router.isReady(); + + app.mount('#app'); +} + +bootstrap(); diff --git a/src/router/constant.ts b/src/router/constant.ts new file mode 100644 index 00000000000..e57bc26012f --- /dev/null +++ b/src/router/constant.ts @@ -0,0 +1,24 @@ +export const REDIRECT_NAME = 'Redirect'; + +export const PARENT_LAYOUT_NAME = 'ParentLayout'; + +export const PAGE_NOT_FOUND_NAME = 'PageNotFound'; + +export const EXCEPTION_COMPONENT = () => import('@/views/sys/exception/Exception.vue'); + +/** + * @description: default layout + */ +export const LAYOUT = () => import('@/layouts/default/index.vue'); + +/** + * @description: parent-layout + */ +export const getParentLayout = (_name?: string) => { + return () => + new Promise((resolve) => { + resolve({ + name: _name || PARENT_LAYOUT_NAME, + }); + }); +}; diff --git a/src/router/guard/index.ts b/src/router/guard/index.ts new file mode 100644 index 00000000000..ee141ff1d4f --- /dev/null +++ b/src/router/guard/index.ts @@ -0,0 +1,147 @@ +import type { Router, RouteLocationNormalized } from 'vue-router'; +import { useAppStoreWithOut } from '@/store/modules/app'; +import { useUserStoreWithOut } from '@/store/modules/user'; +import { useTransitionSetting } from '@/hooks/setting/useTransitionSetting'; +import { AxiosCanceler } from '@/utils/http/axios/axiosCancel'; +import { Modal, notification } from 'ant-design-vue'; +import { warn } from '@/utils/log'; +import { unref } from 'vue'; +import { prefixCls } from '@/settings/designSetting'; +import { setRouteChange } from '@/logics/mitt/routeChange'; +import { createPermissionGuard } from './permissionGuard'; +import { createStateGuard } from './stateGuard'; +import nProgress from 'nprogress'; +import projectSetting from '@/settings/projectSetting'; +import { createParamMenuGuard } from './paramMenuGuard'; + +// Don't change the order of creation +export function setupRouterGuard(router: Router) { + createPageGuard(router); + createPageLoadingGuard(router); + createHttpGuard(router); + createScrollGuard(router); + createMessageGuard(router); + createProgressGuard(router); + createPermissionGuard(router); + createParamMenuGuard(router); // must after createPermissionGuard (menu has been built.) + createStateGuard(router); +} + +/** + * Hooks for handling page state + */ +function createPageGuard(router: Router) { + const loadedPageMap = new Map(); + + router.beforeEach(async (to) => { + // The page has already been loaded, it will be faster to open it again, you don’t need to do loading and other processing + to.meta.loaded = !!loadedPageMap.get(to.path); + // Notify routing changes + setRouteChange(to); + + return true; + }); + + router.afterEach((to) => { + loadedPageMap.set(to.path, true); + }); +} + +// Used to handle page loading status +function createPageLoadingGuard(router: Router) { + const userStore = useUserStoreWithOut(); + const appStore = useAppStoreWithOut(); + const { getOpenPageLoading } = useTransitionSetting(); + router.beforeEach(async (to) => { + if (!userStore.getToken) { + return true; + } + if (to.meta.loaded) { + return true; + } + + if (unref(getOpenPageLoading)) { + appStore.setPageLoadingAction(true); + return true; + } + + return true; + }); + router.afterEach(async () => { + if (unref(getOpenPageLoading)) { + // TODO Looking for a better way + // The timer simulates the loading time to prevent flashing too fast, + setTimeout(() => { + appStore.setPageLoading(false); + }, 220); + } + return true; + }); +} + +/** + * The interface used to close the current page to complete the request when the route is switched + * @param router + */ +function createHttpGuard(router: Router) { + const { removeAllHttpPending } = projectSetting; + let axiosCanceler: Nullable; + if (removeAllHttpPending) { + axiosCanceler = new AxiosCanceler(); + } + router.beforeEach(async () => { + // Switching the route will delete the previous request + axiosCanceler?.removeAllPending(); + return true; + }); +} + +// Routing switch back to the top +function createScrollGuard(router: Router) { + const isHash = (href: string) => { + return /^#/.test(href); + }; + + router.afterEach(async (to) => { + // scroll top + isHash((to as RouteLocationNormalized & { href: string })?.href) && + document.querySelector(`.${prefixCls}-layout-content`)?.scrollTo(0, 0); + return true; + }); +} + +/** + * Used to close the message instance when the route is switched + * @param router + */ +export function createMessageGuard(router: Router) { + const { closeMessageOnSwitch } = projectSetting; + + router.beforeEach(async () => { + try { + if (closeMessageOnSwitch) { + Modal.destroyAll(); + notification.destroy(); + } + } catch (error) { + warn('message guard error:' + error); + } + return true; + }); +} + +export function createProgressGuard(router: Router) { + const { getOpenNProgress } = useTransitionSetting(); + router.beforeEach(async (to) => { + if (to.meta.loaded) { + return true; + } + unref(getOpenNProgress) && nProgress.start(); + return true; + }); + + router.afterEach(async () => { + unref(getOpenNProgress) && nProgress.done(); + return true; + }); +} diff --git a/src/router/guard/paramMenuGuard.ts b/src/router/guard/paramMenuGuard.ts new file mode 100644 index 00000000000..886fa210d27 --- /dev/null +++ b/src/router/guard/paramMenuGuard.ts @@ -0,0 +1,47 @@ +import type { Router } from 'vue-router'; +import { configureDynamicParamsMenu } from '../helper/menuHelper'; +import { Menu } from '../types'; +import { PermissionModeEnum } from '@/enums/appEnum'; +import { useAppStoreWithOut } from '@/store/modules/app'; + +import { usePermissionStoreWithOut } from '@/store/modules/permission'; + +export function createParamMenuGuard(router: Router) { + const permissionStore = usePermissionStoreWithOut(); + router.beforeEach(async (to, _, next) => { + // filter no name route + if (!to.name) { + next(); + return; + } + + // menu has been built. + if (!permissionStore.getIsDynamicAddedRoute) { + next(); + return; + } + + let menus: Menu[] = []; + if (isBackMode()) { + menus = permissionStore.getBackMenuList; + } else if (isRouteMappingMode()) { + menus = permissionStore.getFrontMenuList; + } + menus.forEach((item) => configureDynamicParamsMenu(item, to.params)); + + next(); + }); +} + +const getPermissionMode = () => { + const appStore = useAppStoreWithOut(); + return appStore.getProjectConfig.permissionMode; +}; + +const isBackMode = () => { + return getPermissionMode() === PermissionModeEnum.BACK; +}; + +const isRouteMappingMode = () => { + return getPermissionMode() === PermissionModeEnum.ROUTE_MAPPING; +}; diff --git a/src/router/guard/permissionGuard.ts b/src/router/guard/permissionGuard.ts new file mode 100644 index 00000000000..472e80bd082 --- /dev/null +++ b/src/router/guard/permissionGuard.ts @@ -0,0 +1,133 @@ +import type { Router, RouteRecordRaw } from 'vue-router'; + +import { usePermissionStoreWithOut } from '@/store/modules/permission'; + +import { PageEnum } from '@/enums/pageEnum'; +import { useUserStoreWithOut } from '@/store/modules/user'; + +import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'; + +import { RootRoute } from '@/router/routes'; + +const LOGIN_PATH = PageEnum.BASE_LOGIN; + +const ROOT_PATH = RootRoute.path; + +const whitePathList: PageEnum[] = [LOGIN_PATH]; + +export function createPermissionGuard(router: Router) { + const userStore = useUserStoreWithOut(); + const permissionStore = usePermissionStoreWithOut(); + router.beforeEach(async (to, from, next) => { + if ( + from.path === ROOT_PATH && + to.path === PageEnum.BASE_HOME && + userStore.getUserInfo.homePath && + userStore.getUserInfo.homePath !== PageEnum.BASE_HOME + ) { + next(userStore.getUserInfo.homePath); + return; + } + + const token = userStore.getToken; + + // Whitelist can be directly entered + if (whitePathList.includes(to.path as PageEnum)) { + if (to.path === LOGIN_PATH && token) { + const isSessionTimeout = userStore.getSessionTimeout; + try { + await userStore.afterLoginAction(); + if (!isSessionTimeout) { + next(decodeURIComponent((to.query?.redirect as string) || '/')); + return; + } + } catch { + // + } + } + next(); + return; + } + // token or user does not exist + if (!token) { + // You can access without permission. You need to set the routing meta.ignoreAuth to true + if (to.meta.ignoreAuth) { + next(); + return; + } + + // redirect login page + const redirectData: { path: string; replace: boolean; query?: Recordable } = { + path: LOGIN_PATH, + replace: true, + }; + if (to.path) { + redirectData.query = { + ...redirectData.query, + redirect: to.path, + }; + } + next(redirectData); + return; + } + + // get userinfo while last fetch time is empty + if (userStore.getLastUpdateTime === 0) { + try { + await userStore.getUserInfoAction(); + } catch (err) { + next(); + return; + } + } + + // 动态路由加载(首次) + if (!permissionStore.getIsDynamicAddedRoute) { + const routes = await permissionStore.buildRoutesAction(); + [...routes, PAGE_NOT_FOUND_ROUTE].forEach((route) => { + router.addRoute(route as unknown as RouteRecordRaw); + }); + // 记录动态路由加载完成 + permissionStore.setDynamicAddedRoute(true); + + // 现在的to动态路由加载之前的,可能为PAGE_NOT_FOUND_ROUTE(例如,登陆后,刷新的时候) + // 此处应当重定向到fullPath,否则会加载404页面内容 + next({ path: to.fullPath, replace: true, query: to.query }); + return; + } + + if (to.name === PAGE_NOT_FOUND_ROUTE.name) { + // 遇到不存在页面,后续逻辑不再处理redirect(阻止下面else逻辑) + from.query.redirect = ''; + + if ( + from.path === LOGIN_PATH && + to.fullPath !== (userStore.getUserInfo.homePath || PageEnum.BASE_HOME) + ) { + // 登陆重定向不存在路由,转去“首页” + next({ path: userStore.getUserInfo.homePath || PageEnum.BASE_HOME, replace: true }); + } else { + // 正常前往“404”页面 + next(); + } + } else if (from.query.redirect) { + // 存在redirect + const redirect = decodeURIComponent((from.query.redirect as string) || ''); + + // 只处理一次 from.query.redirect + // 也避免某场景(指向路由定义了 redirect)下的死循环 + from.query.redirect = ''; + + if (redirect === to.fullPath) { + // 已经被redirect + next(); + } else { + // 指向redirect + next({ path: redirect, replace: true }); + } + } else { + // 正常访问 + next(); + } + }); +} diff --git a/src/router/guard/stateGuard.ts b/src/router/guard/stateGuard.ts new file mode 100644 index 00000000000..d91b7d29f6f --- /dev/null +++ b/src/router/guard/stateGuard.ts @@ -0,0 +1,24 @@ +import type { Router } from 'vue-router'; +import { useAppStore } from '@/store/modules/app'; +import { useMultipleTabStore } from '@/store/modules/multipleTab'; +import { useUserStore } from '@/store/modules/user'; +import { usePermissionStore } from '@/store/modules/permission'; +import { PageEnum } from '@/enums/pageEnum'; +import { removeTabChangeListener } from '@/logics/mitt/routeChange'; + +export function createStateGuard(router: Router) { + router.afterEach((to) => { + // Just enter the login page and clear the authentication information + if (to.path === PageEnum.BASE_LOGIN) { + const tabStore = useMultipleTabStore(); + const userStore = useUserStore(); + const appStore = useAppStore(); + const permissionStore = usePermissionStore(); + appStore.resetAllState(); + permissionStore.resetState(); + tabStore.resetState(); + userStore.resetState(); + removeTabChangeListener(); + } + }); +} diff --git a/src/router/helper/menuHelper.ts b/src/router/helper/menuHelper.ts new file mode 100644 index 00000000000..c4eb1dcdb7e --- /dev/null +++ b/src/router/helper/menuHelper.ts @@ -0,0 +1,104 @@ +import { AppRouteModule } from '@/router/types'; +import type { MenuModule, Menu, AppRouteRecordRaw } from '@/router/types'; +import { findPath, treeMap } from '@/utils/helper/treeHelper'; +import { cloneDeep } from 'lodash-es'; +import { isHttpUrl } from '@/utils/is'; +import { RouteParams } from 'vue-router'; +import { toRaw } from 'vue'; + +export function getAllParentPath(treeData: T[], path: string) { + const menuList = findPath(treeData, (n) => n.path === path) as Menu[]; + return (menuList || []).map((item) => item.path); +} + +// 路径处理 +function joinParentPath(menus: Menu[], parentPath = '') { + for (let index = 0; index < menus.length; index++) { + const menu = menus[index]; + // https://next.router.vuejs.org/guide/essentials/nested-routes.html + // Note that nested paths that start with / will be treated as a root path. + // 请注意,以 / 开头的嵌套路径将被视为根路径。 + // This allows you to leverage the component nesting without having to use a nested URL. + // 这允许你利用组件嵌套,而无需使用嵌套 URL。 + if (!(menu.path.startsWith('/') || isHttpUrl(menu.path))) { + // path doesn't start with /, nor is it a url, join parent path + // 路径不以 / 开头,也不是 url,加入父路径 + menu.path = `${parentPath}/${menu.path}`; + } + if (menu?.children?.length) { + joinParentPath(menu.children, menu.meta?.hidePathForChildren ? parentPath : menu.path); + } + } +} + +// Parsing the menu module +export function transformMenuModule(menuModule: MenuModule): Menu { + const menuList = [menuModule]; + + joinParentPath(menuList); + return menuList[0]; +} + +// 将路由转换成菜单 +export function transformRouteToMenu(routeModList: AppRouteModule[], routerMapping = false) { + // 借助 lodash 深拷贝 + const cloneRouteModList = cloneDeep(routeModList); + const routeList: AppRouteRecordRaw[] = []; + + // 对路由项进行修改 + cloneRouteModList.forEach((item) => { + if (routerMapping && item.meta.hideChildrenInMenu && typeof item.redirect === 'string') { + item.path = item.redirect; + } + + if (item.meta?.single) { + const realItem = item?.children?.[0]; + realItem && routeList.push(realItem); + } else { + routeList.push(item); + } + }); + // 提取树指定结构 + const list = treeMap(routeList, { + conversion: (node: AppRouteRecordRaw) => { + const { meta: { hideMenu = false } = {}, name } = node; + + return { + ...(node.meta || {}), + meta: node.meta, + name, + hideMenu, + path: node.path, + ...(node.redirect ? { redirect: node.redirect } : {}), + }; + }, + }); + // 路径处理 + joinParentPath(list); + return cloneDeep(list); +} + +/** + * config menu with given params + */ +const menuParamRegex = /(?::)([\s\S]+?)((?=\/)|$)/g; + +export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) { + const { path, paramPath } = toRaw(menu); + let realPath = paramPath ? paramPath : path; + const matchArr = realPath.match(menuParamRegex); + + matchArr?.forEach((it) => { + const realIt = it.substr(1); + if (params[realIt]) { + realPath = realPath.replace(`:${realIt}`, params[realIt] as string); + } + }); + // save original param path. + if (!paramPath && matchArr && matchArr.length > 0) { + menu.paramPath = path; + } + menu.path = realPath; + // children + menu.children?.forEach((item) => configureDynamicParamsMenu(item, params)); +} diff --git a/src/router/helper/routeHelper.ts b/src/router/helper/routeHelper.ts new file mode 100644 index 00000000000..a96b06b4a09 --- /dev/null +++ b/src/router/helper/routeHelper.ts @@ -0,0 +1,185 @@ +import type { AppRouteModule, AppRouteRecordRaw } from '@/router/types'; +import type { Router, RouteRecordNormalized } from 'vue-router'; + +import { getParentLayout, LAYOUT, EXCEPTION_COMPONENT } from '@/router/constant'; +import { cloneDeep, omit } from 'lodash-es'; +import { warn } from '@/utils/log'; +import { createRouter, createWebHashHistory } from 'vue-router'; + +export type LayoutMapKey = 'LAYOUT'; +const IFRAME = () => import('@/views/sys/iframe/FrameBlank.vue'); + +const LayoutMap = new Map Promise>(); + +LayoutMap.set('LAYOUT', LAYOUT); +LayoutMap.set('IFRAME', IFRAME); + +let dynamicViewsModules: Record Promise>; + +// Dynamic introduction +function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { + dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}'); + if (!routes) return; + routes.forEach((item) => { + if (!item.component && item.meta?.frameSrc) { + item.component = 'IFRAME'; + } + const { component, name } = item; + const { children } = item; + if (component) { + const layoutFound = LayoutMap.get(component.toUpperCase()); + if (layoutFound) { + item.component = layoutFound; + } else { + item.component = dynamicImport(dynamicViewsModules, component as string); + } + } else if (name) { + item.component = getParentLayout(); + } + children && asyncImportRoute(children); + }); +} + +function dynamicImport( + dynamicViewsModules: Record Promise>, + component: string, +) { + const keys = Object.keys(dynamicViewsModules); + const matchKeys = keys.filter((key) => { + const k = key.replace('../../views', ''); + const startFlag = component.startsWith('/'); + const endFlag = component.endsWith('.vue') || component.endsWith('.tsx'); + const startIndex = startFlag ? 0 : 1; + const lastIndex = endFlag ? k.length : k.lastIndexOf('.'); + return k.substring(startIndex, lastIndex) === component; + }); + if (matchKeys?.length === 1) { + const matchKey = matchKeys[0]; + return dynamicViewsModules[matchKey]; + } else if (matchKeys?.length > 1) { + warn( + 'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure', + ); + return; + } else { + warn('在src/views/下找不到`' + component + '.vue` 或 `' + component + '.tsx`, 请自行创建!'); + return EXCEPTION_COMPONENT; + } +} + +// Turn background objects into routing objects +// 将背景对象变成路由对象 +export function transformObjToRoute(routeList: AppRouteModule[]): T[] { + routeList.forEach((route) => { + const component = route.component as string; + if (component) { + if (component.toUpperCase() === 'LAYOUT') { + route.component = LayoutMap.get(component.toUpperCase()); + } else { + route.children = [cloneDeep(route)]; + route.component = LAYOUT; + + //某些情况下如果name如果没有值, 多个一级路由菜单会导致页面404 + if (!route.name) { + warn('找不到菜单对应的name, 请检查数据!' + JSON.stringify(route)); + } + route.name = `${route.name}Parent`; + // 重定向到当前路由,以防空白页面 + route.redirect = route.path; + route.path = ''; + const meta = route.meta || {}; + meta.single = true; + meta.affix = false; + route.meta = meta; + } + } else { + warn('请正确配置路由:' + route?.name + '的component属性'); + } + route.children && asyncImportRoute(route.children); + }); + return routeList as unknown as T[]; +} + +/** + * Convert multi-level routing to level 2 routing + * 将多级路由转换为 2 级路由 + */ +export function flatMultiLevelRoutes(routeModules: AppRouteModule[]) { + const modules: AppRouteModule[] = cloneDeep(routeModules); + + for (let index = 0; index < modules.length; index++) { + const routeModule = modules[index]; + // 判断级别是否 多级 路由 + if (!isMultipleRoute(routeModule)) { + // 声明终止当前循环, 即跳过此次循环,进行下一轮 + continue; + } + // 路由等级提升 + promoteRouteLevel(routeModule); + } + return modules; +} + +// Routing level upgrade +// 路由等级提升 +function promoteRouteLevel(routeModule: AppRouteModule) { + // Use vue-router to splice menus + // 使用vue-router拼接菜单 + // createRouter 创建一个可以被 Vue 应用程序使用的路由实例 + let router: Router | null = createRouter({ + routes: [routeModule as unknown as RouteRecordNormalized], + history: createWebHashHistory(), + }); + // getRoutes: 获取所有 路由记录的完整列表。 + const routes = router.getRoutes(); + // 将所有子路由添加到二级路由 + addToChildren(routes, routeModule.children || [], routeModule); + router = null; + + // omit lodash的函数 对传入的item对象的children进行删除 + routeModule.children = routeModule.children?.map((item) => omit(item, 'children')); +} + +// Add all sub-routes to the secondary route +// 将所有子路由添加到二级路由 +function addToChildren( + routes: RouteRecordNormalized[], + children: AppRouteRecordRaw[], + routeModule: AppRouteModule, +) { + for (let index = 0; index < children.length; index++) { + const child = children[index]; + const route = routes.find((item) => item.name === child.name); + if (!route) { + continue; + } + routeModule.children = routeModule.children || []; + if (!routeModule.children.find((item) => item.name === route.name)) { + routeModule.children?.push(route as unknown as AppRouteModule); + } + if (child.children?.length) { + addToChildren(routes, child.children, routeModule); + } + } +} + +// Determine whether the level exceeds 2 levels +// 判断级别是否超过2级 +function isMultipleRoute(routeModule: AppRouteModule) { + // Reflect.has 与 in 操作符 相同, 用于检查一个对象(包括它原型链上)是否拥有某个属性 + if (!routeModule || !Reflect.has(routeModule, 'children') || !routeModule.children?.length) { + return false; + } + + const children = routeModule.children; + + let flag = false; + for (let index = 0; index < children.length; index++) { + const child = children[index]; + if (child.children?.length) { + flag = true; + break; + } + } + return flag; +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 00000000000..bcfc17fb90c --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,42 @@ +import type { RouteRecordRaw } from 'vue-router'; +import type { App } from 'vue'; + +import { createRouter, createWebHashHistory } from 'vue-router'; +import { basicRoutes } from './routes'; + +// 白名单应该包含基本静态路由 +const WHITE_NAME_LIST: string[] = []; +const getRouteNames = (array: any[]) => + array.forEach((item) => { + WHITE_NAME_LIST.push(item.name); + getRouteNames(item.children || []); + }); +getRouteNames(basicRoutes); + +// app router +// 创建一个可以被 Vue 应用程序使用的路由实例 +export const router = createRouter({ + // 创建一个 hash 历史记录。 + history: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH), + // 应该添加到路由的初始路由列表。 + routes: basicRoutes as unknown as RouteRecordRaw[], + // 是否应该禁止尾部斜杠。默认为假 + strict: true, + scrollBehavior: () => ({ left: 0, top: 0 }), +}); + +// reset router +export function resetRouter() { + router.getRoutes().forEach((route) => { + const { name } = route; + if (name && !WHITE_NAME_LIST.includes(name as string)) { + router.hasRoute(name) && router.removeRoute(name); + } + }); +} + +// config router +// 配置路由器 +export function setupRouter(app: App) { + app.use(router); +} diff --git a/src/router/menus/index.ts b/src/router/menus/index.ts new file mode 100644 index 00000000000..f9e6b4f0a23 --- /dev/null +++ b/src/router/menus/index.ts @@ -0,0 +1,136 @@ +import type { Menu, MenuModule } from '@/router/types'; +import type { RouteRecordNormalized } from 'vue-router'; + +import { useAppStoreWithOut } from '@/store/modules/app'; +import { usePermissionStore } from '@/store/modules/permission'; +import { transformMenuModule, getAllParentPath } from '@/router/helper/menuHelper'; +import { filter } from '@/utils/helper/treeHelper'; +import { isHttpUrl } from '@/utils/is'; +import { router } from '@/router'; +import { PermissionModeEnum } from '@/enums/appEnum'; +import { pathToRegexp } from 'path-to-regexp'; + +const modules = import.meta.glob('../routes/modules/**/*.ts', { eager: true }); + +const menuModules: MenuModule[] = []; + +Object.keys(modules).forEach((key) => { + const mod = (modules as Recordable)[key].default || {}; + const modList = Array.isArray(mod) ? [...mod] : [mod]; + menuModules.push(...modList); +}); + +// =========================== +// ==========Helper=========== +// =========================== + +const getPermissionMode = () => { + const appStore = useAppStoreWithOut(); + return appStore.getProjectConfig.permissionMode; +}; +const isBackMode = () => { + return getPermissionMode() === PermissionModeEnum.BACK; +}; + +const isRouteMappingMode = () => { + return getPermissionMode() === PermissionModeEnum.ROUTE_MAPPING; +}; + +const isRoleMode = () => { + return getPermissionMode() === PermissionModeEnum.ROLE; +}; + +const staticMenus: Menu[] = []; +(() => { + menuModules.sort((a, b) => { + return (a.orderNo || 0) - (b.orderNo || 0); + }); + + for (const menu of menuModules) { + staticMenus.push(transformMenuModule(menu)); + } +})(); + +async function getAsyncMenus() { + const permissionStore = usePermissionStore(); + //递归过滤所有隐藏的菜单 + const menuFilter = (items) => { + return items.filter((item) => { + const show = !item.meta?.hideMenu && !item.hideMenu; + if (show && item.children) { + item.children = menuFilter(item.children); + } + return show; + }); + }; + if (isBackMode()) { + return menuFilter(permissionStore.getBackMenuList); + } + if (isRouteMappingMode()) { + return menuFilter(permissionStore.getFrontMenuList); + } + return staticMenus; +} + +export const getMenus = async (): Promise => { + const menus = await getAsyncMenus(); + if (isRoleMode()) { + const routes = router.getRoutes(); + return filter(menus, basicFilter(routes)); + } + return menus; +}; + +export async function getCurrentParentPath(currentPath: string) { + const menus = await getAsyncMenus(); + const allParentPath = await getAllParentPath(menus, currentPath); + return allParentPath?.[0]; +} + +// Get the level 1 menu, delete children +export async function getShallowMenus(): Promise { + const menus = await getAsyncMenus(); + const shallowMenuList = menus.map((item) => ({ ...item, children: undefined })); + if (isRoleMode()) { + const routes = router.getRoutes(); + return shallowMenuList.filter(basicFilter(routes)); + } + return shallowMenuList; +} + +// Get the children of the menu +export async function getChildrenMenus(parentPath: string) { + const menus = await getMenus(); + const parent = menus.find((item) => item.path === parentPath); + if (!parent || !parent.children || !!parent?.meta?.hideChildrenInMenu) { + return [] as Menu[]; + } + if (isRoleMode()) { + const routes = router.getRoutes(); + return filter(parent.children, basicFilter(routes)); + } + return parent.children; +} + +function basicFilter(routes: RouteRecordNormalized[]) { + return (menu: Menu) => { + const matchRoute = routes.find((route) => { + if (isHttpUrl(menu.path)) return true; + + if (route.meta?.carryParam) { + return pathToRegexp(route.path).test(menu.path); + } + const isSame = route.path === menu.path; + if (!isSame) return false; + + if (route.meta?.ignoreAuth) return true; + + return isSame || pathToRegexp(route.path).test(menu.path); + }); + + if (!matchRoute) return false; + menu.icon = (menu.icon || matchRoute.meta.icon) as string; + menu.meta = matchRoute.meta; + return true; + }; +} diff --git a/src/router/routes/basic.ts b/src/router/routes/basic.ts new file mode 100644 index 00000000000..58ae6ea7b65 --- /dev/null +++ b/src/router/routes/basic.ts @@ -0,0 +1,73 @@ +import type { AppRouteRecordRaw } from '@/router/types'; +import { t } from '@/hooks/web/useI18n'; +import { REDIRECT_NAME, LAYOUT, EXCEPTION_COMPONENT, PAGE_NOT_FOUND_NAME } from '@/router/constant'; + +// 404 on a page +export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { + path: '/:path(.*)*', + name: PAGE_NOT_FOUND_NAME, + component: LAYOUT, + meta: { + title: 'ErrorPage', + hideBreadcrumb: true, + hideMenu: true, + }, + children: [ + { + path: '/:path(.*)*', + name: PAGE_NOT_FOUND_NAME, + component: EXCEPTION_COMPONENT, + meta: { + title: 'ErrorPage', + hideBreadcrumb: true, + hideMenu: true, + }, + }, + ], +}; + +export const REDIRECT_ROUTE: AppRouteRecordRaw = { + path: '/redirect', + component: LAYOUT, + name: 'RedirectTo', + meta: { + title: REDIRECT_NAME, + hideBreadcrumb: true, + hideMenu: true, + }, + children: [ + { + path: '/redirect/:path(.*)/:_redirect_type(.*)/:_origin_params(.*)?', + name: REDIRECT_NAME, + component: () => import('@/views/sys/redirect/index.vue'), + meta: { + title: REDIRECT_NAME, + hideBreadcrumb: true, + }, + }, + ], +}; + +export const ERROR_LOG_ROUTE: AppRouteRecordRaw = { + path: '/error-log', + name: 'ErrorLog', + component: LAYOUT, + redirect: '/error-log/list', + meta: { + title: 'ErrorLog', + hideBreadcrumb: true, + hideChildrenInMenu: true, + }, + children: [ + { + path: 'list', + name: 'ErrorLogList', + component: () => import('@/views/sys/error-log/index.vue'), + meta: { + title: t('routes.basic.errorLogList'), + hideBreadcrumb: true, + currentActiveMenu: '/error-log', + }, + }, + ], +}; diff --git a/src/router/routes/index.ts b/src/router/routes/index.ts new file mode 100644 index 00000000000..0bb349a32d8 --- /dev/null +++ b/src/router/routes/index.ts @@ -0,0 +1,49 @@ +import type { AppRouteRecordRaw, AppRouteModule } from '@/router/types'; + +import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '@/router/routes/basic'; + +import { mainOutRoutes } from './mainOut'; +import { PageEnum } from '@/enums/pageEnum'; +import { t } from '@/hooks/web/useI18n'; + +// import.meta.glob() 直接引入所有的模块 Vite 独有的功能 +const modules = import.meta.glob('./modules/**/*.ts', { eager: true }); +const routeModuleList: AppRouteModule[] = []; + +// 加入到路由集合中 +Object.keys(modules).forEach((key) => { + const mod = (modules as Recordable)[key].default || {}; + const modList = Array.isArray(mod) ? [...mod] : [mod]; + routeModuleList.push(...modList); +}); + +export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList]; + +// 根路由 +export const RootRoute: AppRouteRecordRaw = { + path: '/', + name: 'Root', + redirect: PageEnum.BASE_HOME, + meta: { + title: 'Root', + }, +}; + +export const LoginRoute: AppRouteRecordRaw = { + path: '/login', + name: 'Login', + component: () => import('@/views/sys/login/Login.vue'), + meta: { + title: t('routes.basic.login'), + }, +}; + +// Basic routing without permission +// 未经许可的基本路由 +export const basicRoutes = [ + LoginRoute, + RootRoute, + ...mainOutRoutes, + REDIRECT_ROUTE, + PAGE_NOT_FOUND_ROUTE, +]; diff --git a/src/router/routes/mainOut.ts b/src/router/routes/mainOut.ts new file mode 100644 index 00000000000..6e2a4a448d5 --- /dev/null +++ b/src/router/routes/mainOut.ts @@ -0,0 +1,22 @@ +/** +The routing of this file will not show the layout. +It is an independent new page. +the contents of the file still need to log in to access + */ +import type { AppRouteModule } from '@/router/types'; + +// test +// http:ip:port/main-out +export const mainOutRoutes: AppRouteModule[] = [ + { + path: '/main-out', + name: 'MainOut', + component: () => import('@/views/demo/main-out/index.vue'), + meta: { + title: 'MainOut', + ignoreAuth: true, + }, + }, +]; + +export const mainOutRouteNames = mainOutRoutes.map((item) => item.name); diff --git a/src/router/routes/modules/about.ts b/src/router/routes/modules/about.ts new file mode 100644 index 00000000000..a1f0dcbffeb --- /dev/null +++ b/src/router/routes/modules/about.ts @@ -0,0 +1,31 @@ +import type { AppRouteModule } from '@/router/types'; + +import { LAYOUT } from '@/router/constant'; +import { t } from '@/hooks/web/useI18n'; + +const about: AppRouteModule = { + path: '/about', + name: 'About', + component: LAYOUT, + redirect: '/about/index', + meta: { + hideChildrenInMenu: true, + icon: 'simple-icons:aboutdotme', + title: t('routes.dashboard.about'), + orderNo: 100000, + }, + children: [ + { + path: 'index', + name: 'AboutPage', + component: () => import('@/views/sys/about/index.vue'), + meta: { + title: t('routes.dashboard.about'), + icon: 'simple-icons:aboutdotme', + hideMenu: true, + }, + }, + ], +}; + +export default about; diff --git a/src/router/routes/modules/dashboard.ts b/src/router/routes/modules/dashboard.ts new file mode 100644 index 00000000000..9c1590c7f51 --- /dev/null +++ b/src/router/routes/modules/dashboard.ts @@ -0,0 +1,37 @@ +import type { AppRouteModule } from '@/router/types'; + +import { LAYOUT } from '@/router/constant'; +import { t } from '@/hooks/web/useI18n'; + +const dashboard: AppRouteModule = { + path: '/dashboard', + name: 'Dashboard', + component: LAYOUT, + redirect: '/dashboard/analysis', + meta: { + orderNo: 10, + icon: 'ion:grid-outline', + title: t('routes.dashboard.dashboard'), + }, + children: [ + { + path: 'analysis', + name: 'Analysis', + component: () => import('@/views/dashboard/analysis/index.vue'), + meta: { + // affix: true, + title: t('routes.dashboard.analysis'), + }, + }, + { + path: 'workbench', + name: 'Workbench', + component: () => import('@/views/dashboard/workbench/index.vue'), + meta: { + title: t('routes.dashboard.workbench'), + }, + }, + ], +}; + +export default dashboard; diff --git a/src/router/routes/modules/demo/charts.ts b/src/router/routes/modules/demo/charts.ts new file mode 100644 index 00000000000..c5a4996e8eb --- /dev/null +++ b/src/router/routes/modules/demo/charts.ts @@ -0,0 +1,80 @@ +import type { AppRouteModule } from '@/router/types'; + +import { getParentLayout, LAYOUT } from '@/router/constant'; +import { t } from '@/hooks/web/useI18n'; + +const charts: AppRouteModule = { + path: '/charts', + name: 'Charts', + component: LAYOUT, + redirect: '/charts/echarts/map', + meta: { + orderNo: 500, + icon: 'ion:bar-chart-outline', + title: t('routes.demo.charts.charts'), + }, + children: [ + { + path: 'baiduMap', + name: 'BaiduMap', + meta: { + title: t('routes.demo.charts.baiduMap'), + }, + component: () => import('@/views/demo/charts/map/Baidu.vue'), + }, + { + path: 'aMap', + name: 'AMap', + meta: { + title: t('routes.demo.charts.aMap'), + }, + component: () => import('@/views/demo/charts/map/Gaode.vue'), + }, + { + path: 'googleMap', + name: 'GoogleMap', + meta: { + title: t('routes.demo.charts.googleMap'), + }, + component: () => import('@/views/demo/charts/map/Google.vue'), + }, + + { + path: 'echarts', + name: 'Echarts', + component: getParentLayout('Echarts'), + meta: { + title: 'Echarts', + }, + redirect: '/charts/echarts/map', + children: [ + { + path: 'map', + name: 'Map', + component: () => import('@/views/demo/charts/Map.vue'), + meta: { + title: t('routes.demo.charts.map'), + }, + }, + { + path: 'line', + name: 'Line', + component: () => import('@/views/demo/charts/Line.vue'), + meta: { + title: t('routes.demo.charts.line'), + }, + }, + { + path: 'pie', + name: 'Pie', + component: () => import('@/views/demo/charts/Pie.vue'), + meta: { + title: t('routes.demo.charts.pie'), + }, + }, + ], + }, + ], +}; + +export default charts; diff --git a/src/router/routes/modules/demo/comp.ts b/src/router/routes/modules/demo/comp.ts new file mode 100644 index 00000000000..283196601e7 --- /dev/null +++ b/src/router/routes/modules/demo/comp.ts @@ -0,0 +1,563 @@ +import type { AppRouteModule } from '@/router/types'; + +import { getParentLayout, LAYOUT } from '@/router/constant'; +import { t } from '@/hooks/web/useI18n'; + +const comp: AppRouteModule = { + path: '/comp', + name: 'Comp', + component: LAYOUT, + redirect: '/comp/basic', + meta: { + orderNo: 30, + icon: 'ion:layers-outline', + title: t('routes.demo.comp.comp'), + }, + + children: [ + { + path: 'basic', + name: 'BasicDemo', + component: () => import('@/views/demo/comp/button/index.vue'), + meta: { + title: t('routes.demo.comp.basic'), + }, + }, + + { + path: 'form', + name: 'FormDemo', + redirect: '/comp/form/basic', + component: getParentLayout('FormDemo'), + meta: { + // icon: 'mdi:form-select', + title: t('routes.demo.form.form'), + }, + children: [ + { + path: 'basic', + name: 'FormBasicDemo', + component: () => import('@/views/demo/form/index.vue'), + meta: { + title: t('routes.demo.form.basic'), + }, + }, + { + path: 'useForm', + name: 'UseFormDemo', + component: () => import('@/views/demo/form/UseForm.vue'), + meta: { + title: t('routes.demo.form.useForm'), + }, + }, + { + path: 'refForm', + name: 'RefFormDemo', + component: () => import('@/views/demo/form/RefForm.vue'), + meta: { + title: t('routes.demo.form.refForm'), + }, + }, + { + path: 'advancedForm', + name: 'AdvancedFormDemo', + component: () => import('@/views/demo/form/AdvancedForm.vue'), + meta: { + title: t('routes.demo.form.advancedForm'), + }, + }, + { + path: 'ruleForm', + name: 'RuleFormDemo', + component: () => import('@/views/demo/form/RuleForm.vue'), + meta: { + title: t('routes.demo.form.ruleForm'), + }, + }, + { + path: 'dynamicForm', + name: 'DynamicFormDemo', + component: () => import('@/views/demo/form/DynamicForm.vue'), + meta: { + title: t('routes.demo.form.dynamicForm'), + }, + }, + { + path: 'customerForm', + name: 'CustomerFormDemo', + component: () => import('@/views/demo/form/CustomerForm.vue'), + meta: { + title: t('routes.demo.form.customerForm'), + }, + }, + { + path: 'appendForm', + name: 'appendFormDemo', + component: () => import('@/views/demo/form/AppendForm.vue'), + meta: { + title: t('routes.demo.form.appendForm'), + }, + }, + { + path: 'tabsForm', + name: 'tabsFormDemo', + component: () => import('@/views/demo/form/TabsForm.vue'), + meta: { + title: t('routes.demo.form.tabsForm'), + }, + }, + ], + }, + { + path: 'table', + name: 'TableDemo', + redirect: '/comp/table/basic', + component: getParentLayout('TableDemo'), + meta: { + // icon: 'carbon:table-split', + title: t('routes.demo.table.table'), + }, + + children: [ + { + path: 'basic', + name: 'TableBasicDemo', + component: () => import('@/views/demo/table/Basic.vue'), + meta: { + title: t('routes.demo.table.basic'), + }, + }, + { + path: 'treeTable', + name: 'TreeTableDemo', + component: () => import('@/views/demo/table/TreeTable.vue'), + meta: { + title: t('routes.demo.table.treeTable'), + }, + }, + { + path: 'fetchTable', + name: 'FetchTableDemo', + component: () => import('@/views/demo/table/FetchTable.vue'), + meta: { + title: t('routes.demo.table.fetchTable'), + }, + }, + { + path: 'fixedColumn', + name: 'FixedColumnDemo', + component: () => import('@/views/demo/table/FixedColumn.vue'), + meta: { + title: t('routes.demo.table.fixedColumn'), + }, + }, + { + path: 'customerCell', + name: 'CustomerCellDemo', + component: () => import('@/views/demo/table/CustomerCell.vue'), + meta: { + title: t('routes.demo.table.customerCell'), + }, + }, + { + path: 'formTable', + name: 'FormTableDemo', + component: () => import('@/views/demo/table/FormTable.vue'), + meta: { + title: t('routes.demo.table.formTable'), + }, + }, + { + path: 'useTable', + name: 'UseTableDemo', + component: () => import('@/views/demo/table/UseTable.vue'), + meta: { + title: t('routes.demo.table.useTable'), + }, + }, + { + path: 'refTable', + name: 'RefTableDemo', + component: () => import('@/views/demo/table/RefTable.vue'), + meta: { + title: t('routes.demo.table.refTable'), + }, + }, + { + path: 'multipleHeader', + name: 'MultipleHeaderDemo', + component: () => import('@/views/demo/table/MultipleHeader.vue'), + meta: { + title: t('routes.demo.table.multipleHeader'), + }, + }, + { + path: 'mergeHeader', + name: 'MergeHeaderDemo', + component: () => import('@/views/demo/table/MergeHeader.vue'), + meta: { + title: t('routes.demo.table.mergeHeader'), + }, + }, + { + path: 'expandTable', + name: 'ExpandTableDemo', + component: () => import('@/views/demo/table/ExpandTable.vue'), + meta: { + title: t('routes.demo.table.expandTable'), + }, + }, + { + path: 'fixedHeight', + name: 'FixedHeightDemo', + component: () => import('@/views/demo/table/FixedHeight.vue'), + meta: { + title: t('routes.demo.table.fixedHeight'), + }, + }, + { + path: 'footerTable', + name: 'FooterTableDemo', + component: () => import('@/views/demo/table/FooterTable.vue'), + meta: { + title: t('routes.demo.table.footerTable'), + }, + }, + { + path: 'editCellTable', + name: 'EditCellTableDemo', + component: () => import('@/views/demo/table/EditCellTable.vue'), + meta: { + title: t('routes.demo.table.editCellTable'), + }, + }, + { + path: 'editRowTable', + name: 'EditRowTableDemo', + component: () => import('@/views/demo/table/EditRowTable.vue'), + meta: { + title: t('routes.demo.table.editRowTable'), + }, + }, + { + path: 'authColumn', + name: 'AuthColumnDemo', + component: () => import('@/views/demo/table/AuthColumn.vue'), + meta: { + title: t('routes.demo.table.authColumn'), + }, + }, + { + path: 'resizeParentHeightTable', + name: 'ResizeParentHeightTable', + component: () => import('@/views/demo/table/ResizeParentHeightTable.vue'), + meta: { + title: t('routes.demo.table.resizeParentHeightTable'), + }, + }, + { + path: 'vxeTable', + name: 'VxeTableDemo', + component: () => import('@/views/demo/table/VxeTable.vue'), + meta: { + title: t('routes.demo.table.vxeTable'), + }, + }, + ], + }, + { + path: 'transition', + name: 'transitionDemo', + component: () => import('@/views/demo/comp/transition/index.vue'), + meta: { + title: t('routes.demo.comp.transition'), + }, + }, + { + path: 'cropper', + name: 'CropperDemo', + component: () => import('@/views/demo/comp/cropper/index.vue'), + meta: { + title: t('routes.demo.comp.cropperImage'), + }, + }, + + { + path: 'timestamp', + name: 'TimeDemo', + component: () => import('@/views/demo/comp/time/index.vue'), + meta: { + title: t('routes.demo.comp.time'), + }, + }, + { + path: 'countTo', + name: 'CountTo', + component: () => import('@/views/demo/comp/count-to/index.vue'), + meta: { + title: t('routes.demo.comp.countTo'), + }, + }, + { + path: 'tree', + name: 'TreeDemo', + redirect: '/comp/tree/basic', + component: getParentLayout('TreeDemo'), + meta: { + // icon: 'clarity:tree-view-line', + title: t('routes.demo.comp.tree'), + }, + children: [ + { + path: 'basic', + name: 'BasicTreeDemo', + component: () => import('@/views/demo/tree/index.vue'), + meta: { + title: t('routes.demo.comp.treeBasic'), + }, + }, + { + path: 'editTree', + name: 'EditTreeDemo', + component: () => import('@/views/demo/tree/EditTree.vue'), + meta: { + title: t('routes.demo.comp.editTree'), + }, + }, + { + path: 'actionTree', + name: 'ActionTreeDemo', + component: () => import('@/views/demo/tree/ActionTree.vue'), + meta: { + title: t('routes.demo.comp.actionTree'), + }, + }, + ], + }, + { + path: 'editor', + name: 'EditorDemo', + redirect: '/comp/editor/markdown', + component: getParentLayout('EditorDemo'), + meta: { + // icon: 'carbon:table-split', + title: t('routes.demo.editor.editor'), + }, + children: [ + { + path: 'code', + component: () => import('@/views/demo/editor/code/index.vue'), + name: 'codeEditorDemo', + meta: { + title: t('routes.demo.editor.codeEditor'), + }, + children: [ + { + path: 'code', + name: 'codeBasicDemo', + component: () => import('@/views/demo/editor/code/index.vue'), + meta: { + title: t('routes.demo.editor.tinymceBasic'), + }, + }, + { + path: 'editor', + name: 'codeEditorBasicDemo', + component: () => import('@/views/demo/editor/code/Editor.vue'), + meta: { + title: t('routes.demo.editor.tinymceForm'), + }, + }, + ], + }, + { + path: 'markdown', + component: getParentLayout('MarkdownDemo'), + name: 'MarkdownDemo', + meta: { + title: t('routes.demo.editor.markdown'), + }, + redirect: '/comp/editor/markdown/index', + children: [ + { + path: 'index', + name: 'MarkDownBasicDemo', + component: () => import('@/views/demo/editor/markdown/index.vue'), + meta: { + title: t('routes.demo.editor.tinymceBasic'), + }, + }, + { + path: 'editor', + name: 'MarkDownFormDemo', + component: () => import('@/views/demo/editor/markdown/Editor.vue'), + meta: { + title: t('routes.demo.editor.tinymceForm'), + }, + }, + ], + }, + + { + path: 'tinymce', + component: getParentLayout('TinymceDemo'), + name: 'TinymceDemo', + meta: { + title: t('routes.demo.editor.tinymce'), + }, + redirect: '/comp/editor/tinymce/index', + children: [ + { + path: 'index', + name: 'TinymceBasicDemo', + component: () => import('@/views/demo/editor/tinymce/index.vue'), + meta: { + title: t('routes.demo.editor.tinymceBasic'), + }, + }, + { + path: 'editor', + name: 'TinymceFormDemo', + component: () => import('@/views/demo/editor/tinymce/Editor.vue'), + meta: { + title: t('routes.demo.editor.tinymceForm'), + }, + }, + ], + }, + ], + }, + { + path: 'scroll', + name: 'ScrollDemo', + redirect: '/comp/scroll/basic', + component: getParentLayout('ScrollDemo'), + meta: { + title: t('routes.demo.comp.scroll'), + }, + children: [ + { + path: 'basic', + name: 'BasicScrollDemo', + component: () => import('@/views/demo/comp/scroll/index.vue'), + meta: { + title: t('routes.demo.comp.scrollBasic'), + }, + }, + { + path: 'action', + name: 'ActionScrollDemo', + component: () => import('@/views/demo/comp/scroll/Action.vue'), + meta: { + title: t('routes.demo.comp.scrollAction'), + }, + }, + { + path: 'virtualScroll', + name: 'VirtualScrollDemo', + component: () => import('@/views/demo/comp/scroll/VirtualScroll.vue'), + meta: { + title: t('routes.demo.comp.virtualScroll'), + }, + }, + ], + }, + + { + path: 'modal', + name: 'ModalDemo', + component: () => import('@/views/demo/comp/modal/index.vue'), + meta: { + title: t('routes.demo.comp.modal'), + }, + }, + { + path: 'drawer', + name: 'DrawerDemo', + component: () => import('@/views/demo/comp/drawer/index.vue'), + meta: { + title: t('routes.demo.comp.drawer'), + }, + }, + { + path: 'desc', + name: 'DescDemo', + component: () => import('@/views/demo/comp/desc/index.vue'), + meta: { + title: t('routes.demo.comp.desc'), + }, + }, + + { + path: 'verify', + name: 'VerifyDemo', + component: getParentLayout('VerifyDemo'), + redirect: '/comp/verify/drag', + meta: { + title: t('routes.demo.comp.verify'), + }, + children: [ + { + path: 'drag', + name: 'VerifyDragDemo', + component: () => import('@/views/demo/comp/verify/index.vue'), + meta: { + title: t('routes.demo.comp.verifyDrag'), + }, + }, + { + path: 'rotate', + name: 'VerifyRotateDemo', + component: () => import('@/views/demo/comp/verify/Rotate.vue'), + meta: { + title: t('routes.demo.comp.verifyRotate'), + }, + }, + ], + }, + // + + { + path: 'qrcode', + name: 'QrCodeDemo', + component: () => import('@/views/demo/comp/qrcode/index.vue'), + meta: { + title: t('routes.demo.comp.qrcode'), + }, + }, + { + path: 'strength-meter', + name: 'StrengthMeterDemo', + component: () => import('@/views/demo/comp/strength-meter/index.vue'), + meta: { + title: t('routes.demo.comp.strength'), + }, + }, + { + path: 'upload', + name: 'UploadDemo', + component: () => import('@/views/demo/comp/upload/index.vue'), + meta: { + title: t('routes.demo.comp.upload'), + }, + }, + { + path: 'loading', + name: 'LoadingDemo', + component: () => import('@/views/demo/comp/loading/index.vue'), + meta: { + title: t('routes.demo.comp.loading'), + }, + }, + { + path: 'cardList', + name: 'CardListDemo', + component: () => import('@/views/demo/comp/card-list/index.vue'), + meta: { + title: t('routes.demo.comp.cardList'), + }, + }, + ], +}; + +export default comp; diff --git a/src/router/routes/modules/demo/feat.ts b/src/router/routes/modules/demo/feat.ts new file mode 100644 index 00000000000..4855997dfa5 --- /dev/null +++ b/src/router/routes/modules/demo/feat.ts @@ -0,0 +1,340 @@ +import type { AppRouteModule } from '@/router/types'; + +import { getParentLayout, LAYOUT } from '@/router/constant'; +import { t } from '@/hooks/web/useI18n'; + +const feat: AppRouteModule = { + path: '/feat', + name: 'FeatDemo', + component: LAYOUT, + redirect: '/feat/icon', + meta: { + orderNo: 19, + icon: 'ion:git-compare-outline', + title: t('routes.demo.feat.feat'), + }, + + children: [ + { + path: 'icon', + name: 'IconDemo', + component: () => import('@/views/demo/feat/icon/index.vue'), + meta: { + title: t('routes.demo.feat.icon'), + }, + }, + { + path: 'screenshot', + name: 'Screenshot', + component: () => import('@/views/demo/feat/screenshot/index.vue'), + meta: { + title: t('routes.demo.feat.screenShot'), + }, + }, + { + path: 'ws', + name: 'WebSocket', + component: () => import('@/views/demo/feat/ws/index.vue'), + meta: { + title: t('routes.demo.feat.ws'), + }, + }, + { + path: 'request', + name: 'RequestDemo', + // @ts-ignore + component: () => import('@/views/demo/feat/request-demo/index.vue'), + meta: { + title: t('routes.demo.feat.requestDemo'), + }, + }, + { + path: 'session-timeout', + name: 'SessionTimeout', + component: () => import('@/views/demo/feat/session-timeout/index.vue'), + meta: { + title: t('routes.demo.feat.sessionTimeout'), + }, + }, + { + path: 'print', + name: 'Print', + component: () => import('@/views/demo/feat/print/index.vue'), + meta: { + title: t('routes.demo.feat.print'), + }, + }, + { + path: 'tabs', + name: 'TabsDemo', + component: () => import('@/views/demo/feat/tabs/index.vue'), + meta: { + title: t('routes.demo.feat.tabs'), + hideChildrenInMenu: true, + }, + children: [ + { + path: 'detail/:id', + name: 'TabDetail', + component: () => import('@/views/demo/feat/tabs/TabDetail.vue'), + meta: { + currentActiveMenu: '/feat/tabs', + title: t('routes.demo.feat.tabDetail'), + hideMenu: true, + dynamicLevel: 3, + realPath: '/feat/tabs/detail', + }, + }, + ], + }, + { + path: 'breadcrumb', + name: 'BreadcrumbDemo', + redirect: '/feat/breadcrumb/flat', + component: getParentLayout('BreadcrumbDemo'), + meta: { + title: t('routes.demo.feat.breadcrumb'), + }, + + children: [ + { + path: 'flat', + name: 'BreadcrumbFlatDemo', + component: () => import('@/views/demo/feat/breadcrumb/FlatList.vue'), + meta: { + title: t('routes.demo.feat.breadcrumbFlat'), + }, + }, + { + path: 'flatDetail', + name: 'BreadcrumbFlatDetailDemo', + component: () => import('@/views/demo/feat/breadcrumb/FlatListDetail.vue'), + meta: { + title: t('routes.demo.feat.breadcrumbFlatDetail'), + hideMenu: true, + hideTab: true, + currentActiveMenu: '/feat/breadcrumb/flat', + }, + }, + { + path: 'children', + name: 'BreadcrumbChildrenDemo', + component: () => import('@/views/demo/feat/breadcrumb/ChildrenList.vue'), + meta: { + title: t('routes.demo.feat.breadcrumbChildren'), + }, + children: [ + { + path: 'childrenDetail', + name: 'BreadcrumbChildrenDetailDemo', + component: () => import('@/views/demo/feat/breadcrumb/ChildrenListDetail.vue'), + meta: { + currentActiveMenu: '/feat/breadcrumb/children', + title: t('routes.demo.feat.breadcrumbChildrenDetail'), + //hideTab: true, + // hideMenu: true, + }, + }, + ], + }, + ], + }, + + { + path: 'context-menu', + name: 'ContextMenuDemo', + component: () => import('@/views/demo/feat/context-menu/index.vue'), + meta: { + title: t('routes.demo.feat.contextMenu'), + }, + }, + { + path: 'download', + name: 'DownLoadDemo', + component: () => import('@/views/demo/feat/download/index.vue'), + meta: { + title: t('routes.demo.feat.download'), + }, + }, + { + path: 'click-out-side', + name: 'ClickOutSideDemo', + component: () => import('@/views/demo/feat/click-out-side/index.vue'), + meta: { + title: t('routes.demo.feat.clickOutSide'), + }, + }, + { + path: 'img-preview', + name: 'ImgPreview', + component: () => import('@/views/demo/feat/img-preview/index.vue'), + meta: { + title: t('routes.demo.feat.imgPreview'), + }, + }, + { + path: 'copy', + name: 'CopyDemo', + component: () => import('@/views/demo/feat/copy/index.vue'), + meta: { + title: t('routes.demo.feat.copy'), + }, + }, + { + path: 'ellipsis', + name: 'EllipsisDemo', + component: () => import('@/views/demo/feat/ellipsis/index.vue'), + meta: { + title: t('routes.demo.feat.ellipsis'), + }, + }, + { + path: 'msg', + name: 'MsgDemo', + component: () => import('@/views/demo/feat/msg/index.vue'), + meta: { + title: t('routes.demo.feat.msg'), + }, + }, + { + path: 'watermark', + name: 'WatermarkDemo', + component: () => import('@/views/demo/feat/watermark/index.vue'), + meta: { + title: t('routes.demo.feat.watermark'), + }, + }, + { + path: 'ripple', + name: 'RippleDemo', + component: () => import('@/views/demo/feat/ripple/index.vue'), + meta: { + title: t('routes.demo.feat.ripple'), + }, + }, + { + path: 'full-screen', + name: 'FullScreenDemo', + component: () => import('@/views/demo/feat/full-screen/index.vue'), + meta: { + title: t('routes.demo.feat.fullScreen'), + }, + }, + { + path: '/error-log', + name: 'ErrorLog', + component: () => import('@/views/sys/error-log/index.vue'), + meta: { + title: t('routes.demo.feat.errorLog'), + }, + }, + { + path: 'excel', + name: 'Excel', + redirect: '/feat/excel/customExport', + component: getParentLayout('Excel'), + meta: { + // icon: 'mdi:microsoft-excel', + title: t('routes.demo.excel.excel'), + }, + + children: [ + { + path: 'customExport', + name: 'CustomExport', + component: () => import('@/views/demo/excel/CustomExport.vue'), + meta: { + title: t('routes.demo.excel.customExport'), + }, + }, + { + path: 'jsonExport', + name: 'JsonExport', + component: () => import('@/views/demo/excel/JsonExport.vue'), + meta: { + title: t('routes.demo.excel.jsonExport'), + }, + }, + { + path: 'arrayExport', + name: 'ArrayExport', + component: () => import('@/views/demo/excel/ArrayExport.vue'), + meta: { + title: t('routes.demo.excel.arrayExport'), + }, + }, + { + path: 'importExcel', + name: 'ImportExcel', + component: () => import('@/views/demo/excel/ImportExcel.vue'), + meta: { + title: t('routes.demo.excel.importExcel'), + }, + }, + ], + }, + { + path: 'testTab/:id', + name: 'TestTab', + component: () => import('@/views/demo/feat/tab-params/index.vue'), + meta: { + title: t('routes.demo.feat.tab'), + carryParam: true, + hidePathForChildren: true, + }, + children: [ + { + path: 'testTab/id1', + name: 'TestTab1', + component: () => import('@/views/demo/feat/tab-params/index.vue'), + meta: { + title: t('routes.demo.feat.tab1'), + carryParam: true, + ignoreRoute: true, + }, + }, + { + path: 'testTab/id2', + name: 'TestTab2', + component: () => import('@/views/demo/feat/tab-params/index.vue'), + meta: { + title: t('routes.demo.feat.tab2'), + carryParam: true, + ignoreRoute: true, + }, + }, + ], + }, + { + path: 'testParam/:id', + name: 'TestParam', + component: getParentLayout('TestParam'), + meta: { + title: t('routes.demo.feat.menu'), + ignoreKeepAlive: true, + }, + children: [ + { + path: 'sub1', + name: 'TestParam_1', + component: () => import('@/views/demo/feat/menu-params/index.vue'), + meta: { + title: t('routes.demo.feat.menu1'), + ignoreKeepAlive: true, + }, + }, + { + path: 'sub2', + name: 'TestParam_2', + component: () => import('@/views/demo/feat/menu-params/index.vue'), + meta: { + title: t('routes.demo.feat.menu2'), + ignoreKeepAlive: true, + }, + }, + ], + }, + ], +}; + +export default feat; diff --git a/src/router/routes/modules/demo/flow.ts b/src/router/routes/modules/demo/flow.ts new file mode 100644 index 00000000000..9260e0f69aa --- /dev/null +++ b/src/router/routes/modules/demo/flow.ts @@ -0,0 +1,28 @@ +import type { AppRouteModule } from '@/router/types'; + +import { LAYOUT } from '@/router/constant'; +import { t } from '@/hooks/web/useI18n'; + +const flow: AppRouteModule = { + path: '/flow', + name: 'FlowDemo', + component: LAYOUT, + redirect: '/flow/flowChart', + meta: { + orderNo: 5000, + icon: 'tabler:chart-dots', + title: t('routes.demo.flow.name'), + }, + children: [ + { + path: 'flowChart', + name: 'flowChartDemo', + component: () => import('@/views/demo/comp/flow-chart/index.vue'), + meta: { + title: t('routes.demo.flow.flowChart'), + }, + }, + ], +}; + +export default flow; diff --git a/src/router/routes/modules/demo/iframe.ts b/src/router/routes/modules/demo/iframe.ts new file mode 100644 index 00000000000..339aea9c43c --- /dev/null +++ b/src/router/routes/modules/demo/iframe.ts @@ -0,0 +1,49 @@ +import type { AppRouteModule } from '@/router/types'; + +import { LAYOUT } from '@/router/constant'; +import { t } from '@/hooks/web/useI18n'; + +const IFrame = () => import('@/views/sys/iframe/FrameBlank.vue'); + +const iframe: AppRouteModule = { + path: '/frame', + name: 'Frame', + component: LAYOUT, + redirect: '/frame/doc', + meta: { + orderNo: 1000, + icon: 'ion:tv-outline', + title: t('routes.demo.iframe.frame'), + }, + + children: [ + { + path: 'doc', + name: 'Doc', + component: IFrame, + meta: { + frameSrc: 'https://doc.vvbin.cn/', + title: t('routes.demo.iframe.doc'), + }, + }, + { + path: 'antv', + name: 'Antv', + component: IFrame, + meta: { + frameSrc: 'https://www.antdv.com/docs/vue/introduce-cn/', + title: t('routes.demo.iframe.antv'), + }, + }, + { + path: 'https://doc.vvbin.cn/', + name: 'DocExternal', + component: IFrame, + meta: { + title: t('routes.demo.iframe.docExternal'), + }, + }, + ], +}; + +export default iframe; diff --git a/src/router/routes/modules/demo/level.ts b/src/router/routes/modules/demo/level.ts new file mode 100644 index 00000000000..aa69abcef58 --- /dev/null +++ b/src/router/routes/modules/demo/level.ts @@ -0,0 +1,68 @@ +import type { AppRouteModule } from '@/router/types'; + +import { getParentLayout, LAYOUT } from '@/router/constant'; +import { t } from '@/hooks/web/useI18n'; + +const level: AppRouteModule = { + path: '/level', + name: 'Level', + component: LAYOUT, + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + orderNo: 2000, + icon: 'ion:menu-outline', + title: t('routes.demo.level.level'), + }, + + children: [ + { + path: 'menu1', + name: 'Menu1Demo', + component: getParentLayout('Menu1Demo'), + meta: { + title: 'Menu1', + }, + redirect: '/level/menu1/menu1-1/menu1-1-1', + children: [ + { + path: 'menu1-1', + name: 'Menu11Demo', + component: getParentLayout('Menu11Demo'), + meta: { + title: 'Menu1-1', + }, + redirect: '/level/menu1/menu1-1/menu1-1-1', + children: [ + { + path: 'menu1-1-1', + name: 'Menu111Demo', + component: () => import('@/views/demo/level/Menu111.vue'), + meta: { + title: 'Menu111', + }, + }, + ], + }, + { + path: 'menu1-2', + name: 'Menu12Demo', + component: () => import('@/views/demo/level/Menu12.vue'), + meta: { + title: 'Menu1-2', + }, + }, + ], + }, + { + path: 'menu2', + name: 'Menu2Demo', + component: () => import('@/views/demo/level/Menu2.vue'), + meta: { + title: 'Menu2', + // ignoreKeepAlive: true, + }, + }, + ], +}; + +export default level; diff --git a/src/router/routes/modules/demo/page.ts b/src/router/routes/modules/demo/page.ts new file mode 100644 index 00000000000..32956b9cc8b --- /dev/null +++ b/src/router/routes/modules/demo/page.ts @@ -0,0 +1,255 @@ +import type { AppRouteModule } from '@/router/types'; + +import { getParentLayout, LAYOUT } from '@/router/constant'; +import { ExceptionEnum } from '@/enums/exceptionEnum'; +import { t } from '@/hooks/web/useI18n'; + +const ExceptionPage = () => import('@/views/sys/exception/Exception.vue'); + +const page: AppRouteModule = { + path: '/page-demo', + name: 'PageDemo', + component: LAYOUT, + redirect: '/page-demo/form/basic', + meta: { + orderNo: 20, + icon: 'ion:aperture-outline', + title: t('routes.demo.page.page'), + }, + children: [ + // =============================form start============================= + { + path: 'form', + name: 'FormPage', + redirect: '/page-demo/form/basic', + component: getParentLayout('FormPage'), + meta: { + title: t('routes.demo.page.form'), + }, + children: [ + { + path: 'basic', + name: 'FormBasicPage', + component: () => import('@/views/demo/page/form/basic/index.vue'), + meta: { + title: t('routes.demo.page.formBasic'), + }, + }, + { + path: 'step', + name: 'FormStepPage', + component: () => import('@/views/demo/page/form/step/index.vue'), + meta: { + title: t('routes.demo.page.formStep'), + }, + }, + { + path: 'high', + name: 'FormHightPage', + component: () => import('@/views/demo/page/form/high/index.vue'), + meta: { + title: t('routes.demo.page.formHigh'), + }, + }, + ], + }, + // =============================form end============================= + // =============================desc start============================= + { + path: 'desc', + name: 'DescPage', + component: getParentLayout('DescPage'), + redirect: '/page-demo/desc/basic', + meta: { + title: t('routes.demo.page.desc'), + }, + children: [ + { + path: 'basic', + name: 'DescBasicPage', + component: () => import('@/views/demo/page/desc/basic/index.vue'), + meta: { + title: t('routes.demo.page.descBasic'), + }, + }, + { + path: 'high', + name: 'DescHighPage', + component: () => import('@/views/demo/page/desc/high/index.vue'), + meta: { + title: t('routes.demo.page.descHigh'), + }, + }, + ], + }, + // =============================desc end============================= + + // =============================result start============================= + { + path: 'result', + name: 'ResultPage', + redirect: '/page-demo/result/success', + component: getParentLayout('ResultPage'), + + meta: { + title: t('routes.demo.page.result'), + }, + children: [ + { + path: 'success', + name: 'ResultSuccessPage', + component: () => import('@/views/demo/page/result/success/index.vue'), + meta: { + title: t('routes.demo.page.resultSuccess'), + }, + }, + { + path: 'fail', + name: 'ResultFailPage', + component: () => import('@/views/demo/page/result/fail/index.vue'), + meta: { + title: t('routes.demo.page.resultFail'), + }, + }, + ], + }, + // =============================result end============================= + + // =============================account start============================= + { + path: 'account', + name: 'AccountPage', + component: getParentLayout('AccountPage'), + redirect: '/page-demo/account/setting', + meta: { + title: t('routes.demo.page.account'), + }, + children: [ + { + path: 'center', + name: 'AccountCenterPage', + component: () => import('@/views/demo/page/account/center/index.vue'), + meta: { + title: t('routes.demo.page.accountCenter'), + }, + }, + { + path: 'setting', + name: 'AccountSettingPage', + component: () => import('@/views/demo/page/account/setting/index.vue'), + meta: { + title: t('routes.demo.page.accountSetting'), + }, + }, + ], + }, + // =============================account end============================= + // =============================exception start============================= + { + path: 'exception', + name: 'ExceptionPage', + component: getParentLayout('ExceptionPage'), + redirect: '/page-demo/exception/404', + meta: { + title: t('routes.demo.page.exception'), + }, + children: [ + { + path: '403', + name: 'PageNotAccess', + component: ExceptionPage, + props: { + status: ExceptionEnum.PAGE_NOT_ACCESS, + }, + meta: { + title: '403', + }, + }, + { + path: '404', + name: 'PageNotFound', + component: ExceptionPage, + props: { + status: ExceptionEnum.PAGE_NOT_FOUND, + }, + meta: { + title: '404', + }, + }, + { + path: '500', + name: 'ServiceError', + component: ExceptionPage, + props: { + status: ExceptionEnum.ERROR, + }, + meta: { + title: '500', + }, + }, + { + path: 'net-work-error', + name: 'NetWorkError', + component: ExceptionPage, + props: { + status: ExceptionEnum.NET_WORK_ERROR, + }, + meta: { + title: t('routes.demo.page.netWorkError'), + }, + }, + { + path: 'not-data', + name: 'NotData', + component: ExceptionPage, + props: { + status: ExceptionEnum.PAGE_NOT_DATA, + }, + meta: { + title: t('routes.demo.page.notData'), + }, + }, + ], + }, + // =============================exception end============================= + // =============================list start============================= + { + path: 'list', + name: 'ListPage', + component: getParentLayout('ListPage'), + redirect: '/page-demo/list/card', + meta: { + title: t('routes.demo.page.list'), + }, + children: [ + { + path: 'basic', + name: 'ListBasicPage', + component: () => import('@/views/demo/page/list/basic/index.vue'), + meta: { + title: t('routes.demo.page.listBasic'), + }, + }, + { + path: 'card', + name: 'ListCardPage', + component: () => import('@/views/demo/page/list/card/index.vue'), + meta: { + title: t('routes.demo.page.listCard'), + }, + }, + { + path: 'search', + name: 'ListSearchPage', + component: () => import('@/views/demo/page/list/search/index.vue'), + meta: { + title: t('routes.demo.page.listSearch'), + }, + }, + ], + }, + // =============================list end============================= + ], +}; + +export default page; diff --git a/src/router/routes/modules/demo/permission.ts b/src/router/routes/modules/demo/permission.ts new file mode 100644 index 00000000000..82054ffd86c --- /dev/null +++ b/src/router/routes/modules/demo/permission.ts @@ -0,0 +1,92 @@ +import type { AppRouteModule } from '@/router/types'; + +import { getParentLayout, LAYOUT } from '@/router/constant'; +import { RoleEnum } from '@/enums/roleEnum'; +import { t } from '@/hooks/web/useI18n'; + +const permission: AppRouteModule = { + path: '/permission', + name: 'Permission', + component: LAYOUT, + redirect: '/permission/front/page', + meta: { + orderNo: 15, + icon: 'ion:key-outline', + title: t('routes.demo.permission.permission'), + }, + + children: [ + { + path: 'front', + name: 'PermissionFrontDemo', + component: getParentLayout('PermissionFrontDemo'), + meta: { + title: t('routes.demo.permission.front'), + }, + children: [ + { + path: 'page', + name: 'FrontPageAuth', + component: () => import('@/views/demo/permission/front/index.vue'), + meta: { + title: t('routes.demo.permission.frontPage'), + }, + }, + { + path: 'btn', + name: 'FrontBtnAuth', + component: () => import('@/views/demo/permission/front/Btn.vue'), + meta: { + title: t('routes.demo.permission.frontBtn'), + }, + }, + { + path: 'auth-pageA', + name: 'FrontAuthPageA', + component: () => import('@/views/demo/permission/front/AuthPageA.vue'), + meta: { + title: t('routes.demo.permission.frontTestA'), + roles: [RoleEnum.SUPER], + }, + }, + { + path: 'auth-pageB', + name: 'FrontAuthPageB', + component: () => import('@/views/demo/permission/front/AuthPageB.vue'), + meta: { + title: t('routes.demo.permission.frontTestB'), + roles: [RoleEnum.TEST], + }, + }, + ], + }, + { + path: 'back', + name: 'PermissionBackDemo', + component: getParentLayout('PermissionBackDemo'), + meta: { + title: t('routes.demo.permission.back'), + }, + children: [ + { + path: 'page', + name: 'BackAuthPage', + component: () => import('@/views/demo/permission/back/index.vue'), + meta: { + title: t('routes.demo.permission.backPage'), + }, + }, + { + path: 'btn', + name: 'BackAuthBtn', + component: () => import('@/views/demo/permission/back/Btn.vue'), + meta: { + title: t('routes.demo.permission.backBtn'), + }, + }, + ], + }, + ], +}; + +export default permission; diff --git a/src/router/routes/modules/demo/steps.ts b/src/router/routes/modules/demo/steps.ts new file mode 100644 index 00000000000..69287c38772 --- /dev/null +++ b/src/router/routes/modules/demo/steps.ts @@ -0,0 +1,31 @@ +import type { AppRouteModule } from '@/router/types'; + +import { LAYOUT } from '@/router/constant'; +import { t } from '@/hooks/web/useI18n'; + +const steps: AppRouteModule = { + path: '/steps', + name: 'StepsDemo', + component: LAYOUT, + redirect: '/steps/index', + meta: { + orderNo: 90000, + hideChildrenInMenu: true, + icon: 'whh:paintroll', + title: t('routes.demo.steps.page'), + }, + children: [ + { + path: 'index', + name: 'StepsDemoPage', + component: () => import('@/views/demo/steps/index.vue'), + meta: { + title: t('routes.demo.steps.page'), + icon: 'whh:paintroll', + hideMenu: true, + }, + }, + ], +}; + +export default steps; diff --git a/src/router/routes/modules/demo/system.ts b/src/router/routes/modules/demo/system.ts new file mode 100644 index 00000000000..066425dc855 --- /dev/null +++ b/src/router/routes/modules/demo/system.ts @@ -0,0 +1,87 @@ +import type { AppRouteModule } from '@/router/types'; + +import { LAYOUT } from '@/router/constant'; +import { t } from '@/hooks/web/useI18n'; + +const system: AppRouteModule = { + path: '/system', + name: 'System', + component: LAYOUT, + redirect: '/system/account', + meta: { + orderNo: 2000, + icon: 'ion:settings-outline', + title: t('routes.demo.system.moduleName'), + }, + children: [ + { + path: 'account', + name: 'AccountManagement', + meta: { + title: t('routes.demo.system.account'), + ignoreKeepAlive: false, + }, + component: () => import('@/views/demo/system/account/index.vue'), + }, + { + path: 'vxeTableAccount', + name: 'VxeTableAccountManagement', + meta: { + title: t('routes.demo.system.vxeTableAccount'), + ignoreKeepAlive: false, + }, + component: () => import('@/views/demo/system/vxe-account/index.vue'), + }, + { + path: 'account_detail/:id', + name: 'AccountDetail', + meta: { + hideMenu: true, + title: t('routes.demo.system.account_detail'), + ignoreKeepAlive: true, + showMenu: false, + currentActiveMenu: '/system/account', + }, + component: () => import('@/views/demo/system/account/AccountDetail.vue'), + }, + { + path: 'role', + name: 'RoleManagement', + meta: { + title: t('routes.demo.system.role'), + ignoreKeepAlive: true, + }, + component: () => import('@/views/demo/system/role/index.vue'), + }, + + { + path: 'menu', + name: 'MenuManagement', + meta: { + title: t('routes.demo.system.menu'), + ignoreKeepAlive: true, + }, + component: () => import('@/views/demo/system/menu/index.vue'), + }, + { + path: 'dept', + name: 'DeptManagement', + meta: { + title: t('routes.demo.system.dept'), + ignoreKeepAlive: true, + }, + component: () => import('@/views/demo/system/dept/index.vue'), + }, + { + path: 'changePassword', + name: 'ChangePassword', + meta: { + title: t('routes.demo.system.password'), + ignoreKeepAlive: true, + }, + component: () => import('@/views/demo/system/password/index.vue'), + }, + ], +}; + +export default system; diff --git a/src/router/routes/modules/form-design/main.ts b/src/router/routes/modules/form-design/main.ts new file mode 100644 index 00000000000..5f1e40b8430 --- /dev/null +++ b/src/router/routes/modules/form-design/main.ts @@ -0,0 +1,34 @@ +import type { AppRouteModule } from '@/router/types'; + +import { LAYOUT } from '@/router/constant'; + +const permission: AppRouteModule = { + path: '/form-designer', + name: 'Form-designer', + component: LAYOUT, + meta: { + orderNo: 10000, + icon: 'ion:build-outline', + title: '表单设计', + }, + children: [ + { + path: 'design', + name: 'Design', + meta: { + title: '表单设计', + }, + component: () => import('@/views/form-design/index.vue'), + }, + { + path: 'example1', + name: 'Example1', + meta: { + title: '示例', + }, + component: () => import('@/views/form-design/examples/baseForm.vue'), + }, + ], +}; + +export default permission; diff --git a/src/router/routes/modules/hooks/request.ts b/src/router/routes/modules/hooks/request.ts new file mode 100644 index 00000000000..fd75fc71787 --- /dev/null +++ b/src/router/routes/modules/hooks/request.ts @@ -0,0 +1,79 @@ +import type { AppRouteModule } from '@/router/types'; + +import { LAYOUT } from '@/router/constant'; + +const charts: AppRouteModule = { + path: '/useRequest', + name: 'useRequest', + component: LAYOUT, + redirect: '/useRequest/base', + meta: { + orderNo: 900, + icon: 'ant-design:api-outlined', + title: 'useRequest', + }, + children: [ + { + path: 'base', + name: 'useRequest-base', + meta: { title: '基础用法' }, + component: () => import('@/views/hooks/request/base'), + }, + { + path: 'loading-delay', + name: 'useRequest-loading-delay', + meta: { title: 'Loading Delay' }, + component: () => import('@/views/hooks/request/loading-delay'), + }, + { + path: 'polling', + name: 'useRequest-polling', + meta: { title: '轮询' }, + component: () => import('@/views/hooks/request/polling'), + }, + { + path: 'ready', + name: 'useRequest-ready', + meta: { title: 'Ready' }, + component: () => import('@/views/hooks/request/ready'), + }, + { + path: 'refresy-deps', + name: 'useRequest-refresy-deps', + meta: { title: '依赖刷新' }, + component: () => import('@/views/hooks/request/refresy-deps'), + }, + { + path: 'refresh-on-window-focus', + name: 'useRequest-refresh-on-window-focus', + meta: { title: '屏幕聚焦重新请求' }, + component: () => import('@/views/hooks/request/refresh-on-window-focus'), + }, + { + path: 'debounce', + name: 'useRequest-debounce', + meta: { title: '防抖' }, + component: () => import('@/views/hooks/request/debounce'), + }, + { + path: 'throttle', + name: 'useRequest-throttle', + meta: { title: '节流' }, + component: () => import('@/views/hooks/request/throttle'), + }, + { + path: 'cache', + name: 'useRequest-cache', + meta: { title: '缓存&SWR' }, + component: () => import('@/views/hooks/request/cache'), + }, + { + path: 'retry', + name: 'useRequest-retry', + meta: { title: '错误重试' }, + component: () => import('@/views/hooks/request/retry'), + }, + ], +}; + +export default charts; diff --git a/src/router/types.ts b/src/router/types.ts new file mode 100644 index 00000000000..0302212b8fa --- /dev/null +++ b/src/router/types.ts @@ -0,0 +1,57 @@ +import type { RouteRecordRaw, RouteMeta } from 'vue-router'; +import { RoleEnum } from '@/enums/roleEnum'; +import { defineComponent } from 'vue'; + +export type Component = + | ReturnType + | (() => Promise) + | (() => Promise); + +// @ts-ignore +export interface AppRouteRecordRaw extends Omit { + name: string; + meta: RouteMeta; + component?: Component | string; + components?: Component; + children?: AppRouteRecordRaw[]; + props?: Recordable; + fullPath?: string; +} + +export interface MenuTag { + type?: 'primary' | 'error' | 'warn' | 'success'; + content?: string; + dot?: boolean; +} + +export interface Menu { + name: string; + + icon?: string; + + img?: string; + + path: string; + + // path contains param, auto assignment. + paramPath?: string; + + disabled?: boolean; + + children?: Menu[]; + + orderNo?: number; + + roles?: RoleEnum[]; + + meta?: Partial; + + tag?: MenuTag; + + hideMenu?: boolean; +} + +export type MenuModule = Menu; + +// export type AppRouteModule = RouteModule | AppRouteRecordRaw; +export type AppRouteModule = AppRouteRecordRaw; diff --git a/src/settings/componentSetting.ts b/src/settings/componentSetting.ts new file mode 100644 index 00000000000..a30a48e2352 --- /dev/null +++ b/src/settings/componentSetting.ts @@ -0,0 +1,97 @@ +// Used to configure the general configuration of some components without modifying the components + +import type { SorterResult } from '../components/Table'; + +export default { + // basic-table setting + table: { + // Form interface request general configuration + // support xxx.xxx.xxx + fetchSetting: { + // The field name of the current page passed to the background + pageField: 'page', + // The number field name of each page displayed in the background + sizeField: 'pageSize', + // Field name of the form data returned by the interface + listField: 'items', + // Total number of tables returned by the interface field name + totalField: 'total', + }, + // Number of pages that can be selected + pageSizeOptions: ['10', '50', '80', '100'], + // Default display quantity on one page + defaultPageSize: 10, + // Default Size + defaultSize: 'middle', + // Custom general sort function + defaultSortFn: (sortInfo: SorterResult) => { + const { field, order } = sortInfo; + if (field && order) { + return { + // The sort field passed to the backend you + field, + // Sorting method passed to the background asc/desc + order, + }; + } else { + return {}; + } + }, + // Custom general filter function + defaultFilterFn: (data: Partial>) => { + return data; + }, + }, + vxeTable: { + table: { + border: true, + stripe: true, + columnConfig: { + resizable: true, + isCurrent: true, + isHover: true, + }, + rowConfig: { + isCurrent: true, + isHover: true, + }, + emptyRender: { + name: 'AEmpty', + }, + printConfig: {}, + exportConfig: {}, + customConfig: { + storage: true, + }, + }, + grid: { + toolbarConfig: { + enabled: true, + export: true, + zoom: true, + print: true, + refresh: true, + custom: true, + }, + pagerConfig: { + pageSizes: [20, 50, 100, 500], + pageSize: 20, + autoHidden: true, + }, + proxyConfig: { + form: true, + props: { + result: 'items', + total: 'total', + }, + }, + zoomConfig: {}, + }, + }, + // scrollbar setting + scrollbar: { + // Whether to use native scroll bar + // After opening, the menu, modal, drawer will change the pop-up scroll bar to native + native: false, + }, +}; diff --git a/src/settings/designSetting.ts b/src/settings/designSetting.ts new file mode 100644 index 00000000000..ede11d446ee --- /dev/null +++ b/src/settings/designSetting.ts @@ -0,0 +1,57 @@ +import { ThemeEnum } from '../enums/appEnum'; + +export const prefixCls = 'vben'; + +export const multipleTabHeight = 30; + +export const darkMode = ThemeEnum.LIGHT; + +// 页脚固定高度 +export const footerHeight = 75; + +// .@{namespace}-layout-multiple-header__placeholder +// 全屏页头动画时长 +export const layoutMultipleHeadePlaceholderTime = 0.6; + +// app theme preset color +export const APP_PRESET_COLOR_LIST: string[] = [ + '#0960bd', + '#0084f4', + '#009688', + '#536dfe', + '#ff5c93', + '#ee4f12', + '#0096c7', + '#9c27b0', + '#ff9800', +]; + +// header preset color +export const HEADER_PRESET_BG_COLOR_LIST: string[] = [ + '#ffffff', + '#151515', + '#009688', + '#5172DC', + '#018ffb', + '#409eff', + '#e74c3c', + '#24292e', + '#394664', + '#001529', + '#383f45', +]; + +// sider preset color +export const SIDE_BAR_BG_COLOR_LIST: string[] = [ + '#001529', + '#212121', + '#273352', + '#ffffff', + '#191b24', + '#191a23', + '#304156', + '#001628', + '#28333E', + '#344058', + '#383f45', +]; diff --git a/src/settings/encryptionSetting.ts b/src/settings/encryptionSetting.ts new file mode 100644 index 00000000000..e8b177dc345 --- /dev/null +++ b/src/settings/encryptionSetting.ts @@ -0,0 +1,13 @@ +import { isDevMode } from '@/utils/env'; + +// System default cache time, in seconds +export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; + +// aes encryption key +export const cacheCipher = { + key: '_11111000001111@', + iv: '@11111000001111_', +}; + +// Whether the system cache is encrypted using aes +export const SHOULD_ENABLE_STORAGE_ENCRYPTION = !isDevMode(); diff --git a/src/settings/localeSetting.ts b/src/settings/localeSetting.ts new file mode 100644 index 00000000000..c73420a4d1b --- /dev/null +++ b/src/settings/localeSetting.ts @@ -0,0 +1,29 @@ +import type { DropMenu } from '../components/Dropdown'; +import type { LocaleSetting, LocaleType } from '#/config'; + +export const LOCALE: { [key: string]: LocaleType } = { + ZH_CN: 'zh_CN', + EN_US: 'en', +}; + +export const localeSetting: LocaleSetting = { + showPicker: true, + // Locale + locale: LOCALE.ZH_CN, + // Default locale + fallback: LOCALE.ZH_CN, + // available Locales + availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US], +}; + +// locale list +export const localeList: DropMenu[] = [ + { + text: '简体中文', + event: LOCALE.ZH_CN, + }, + { + text: 'English', + event: LOCALE.EN_US, + }, +]; diff --git a/src/settings/projectSetting.ts b/src/settings/projectSetting.ts new file mode 100644 index 00000000000..3d7c46ae393 --- /dev/null +++ b/src/settings/projectSetting.ts @@ -0,0 +1,188 @@ +import type { ProjectConfig } from '#/config'; +import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '@/enums/menuEnum'; +import { CacheTypeEnum } from '@/enums/cacheEnum'; +import { + ContentEnum, + PermissionModeEnum, + ThemeEnum, + RouterTransitionEnum, + SettingButtonPositionEnum, + SessionTimeoutProcessingEnum, +} from '@/enums/appEnum'; +import { + SIDE_BAR_BG_COLOR_LIST, + HEADER_PRESET_BG_COLOR_LIST, + APP_PRESET_COLOR_LIST, +} from './designSetting'; + +// ! You need to clear the browser cache after the change +const setting: ProjectConfig = { + // Whether to show the configuration button + showSettingButton: true, + + // Whether to show the theme switch button + showDarkModeToggle: true, + + // `Settings` button position + settingButtonPosition: SettingButtonPositionEnum.AUTO, + + // Permission mode + permissionMode: PermissionModeEnum.ROUTE_MAPPING, + + // Permission-related cache is stored in sessionStorage or localStorage + permissionCacheType: CacheTypeEnum.LOCAL, + + // Session timeout processing + sessionTimeoutProcessing: SessionTimeoutProcessingEnum.ROUTE_JUMP, + + // color + themeColor: APP_PRESET_COLOR_LIST[0], + + // Website gray mode, open for possible mourning dates + grayMode: false, + + // Color Weakness Mode + colorWeak: false, + + // Whether to cancel the menu, the top, the multi-tab page display, for possible embedded in other systems + fullContent: false, + + // content mode + contentMode: ContentEnum.FULL, + + // Whether to display the logo + showLogo: true, + + // Whether to show footer + showFooter: false, + + // Header configuration + headerSetting: { + // header bg color + bgColor: HEADER_PRESET_BG_COLOR_LIST[0], + // Fixed at the top + fixed: true, + // Whether to show top + show: true, + // theme + theme: ThemeEnum.LIGHT, + // Whether to enable the lock screen function + useLockPage: true, + // Whether to show the full screen button + showFullScreen: true, + // Whether to show the document button + showDoc: true, + // Whether to show the notification button + showNotice: true, + // Whether to display the menu search + showSearch: true, + showApi: true, + }, + + // Menu configuration + menuSetting: { + // sidebar menu bg color + bgColor: SIDE_BAR_BG_COLOR_LIST[0], + // Whether to fix the left menu + fixed: true, + // Menu collapse + collapsed: false, + // When sider hide because of the responsive layout + siderHidden: false, + // Whether to display the menu name when folding the menu + collapsedShowTitle: false, + // Whether it can be dragged + // Only limited to the opening of the left menu, the mouse has a drag bar on the right side of the menu + canDrag: false, + // Whether to show no dom + show: true, + // Whether to show dom + hidden: false, + // Menu width + menuWidth: 210, + // Menu mode + mode: MenuModeEnum.INLINE, + // Menu type + type: MenuTypeEnum.SIDEBAR, + // Menu theme + theme: ThemeEnum.DARK, + // Split menu + split: false, + // Top menu layout + topMenuAlign: 'center', + // Fold trigger position + trigger: TriggerEnum.HEADER, + // Turn on accordion mode, only show a menu + accordion: true, + // Switch page to close menu + closeMixSidebarOnChange: false, + // Module opening method ‘click’ |'hover' + mixSideTrigger: MixSidebarTriggerEnum.CLICK, + // Fixed expanded menu + mixSideFixed: false, + }, + + // Multi-label + multiTabsSetting: { + cache: false, + // Turn on + show: true, + // Is it possible to drag and drop sorting tabs + canDrag: true, + // Turn on quick actions + showQuick: true, + // Whether to show the refresh button + showRedo: true, + // Whether to show the collapse button + showFold: true, + // Auto collapsed + autoCollapse: false, + }, + + // Transition Setting + transitionSetting: { + // Whether to open the page switching animation + // The disabled state will also disable pageLoading + enable: true, + + // Route basic switching animation + basicTransition: RouterTransitionEnum.FADE_SIDE, + + // Whether to open page switching loading + // Only open when enable=true + openPageLoading: true, + + // Whether to open the top progress bar + openNProgress: false, + }, + + // Whether to enable KeepAlive cache is best to close during development, otherwise the cache needs to be cleared every time + openKeepAlive: true, + + // Automatic screen lock time, 0 does not lock the screen. Unit minute default 0 + lockTime: 0, + + // Whether to show breadcrumbs + showBreadCrumb: true, + + // Whether to show the breadcrumb icon + showBreadCrumbIcon: false, + + // Use error-handler-plugin + useErrorHandle: false, + + // Whether to open back to top + useOpenBackTop: true, + + // Is it possible to embed iframe pages + canEmbedIFramePage: true, + + // Whether to delete unclosed messages and notify when switching the interface + closeMessageOnSwitch: true, + + // Whether to cancel the http request that has been sent but not responded when switching the interface. + // If it is enabled, I want to overwrite a single interface. Can be set in a separate interface + removeAllHttpPending: false, +}; + +export default setting; diff --git a/src/settings/siteSetting.ts b/src/settings/siteSetting.ts new file mode 100644 index 00000000000..19e195a9252 --- /dev/null +++ b/src/settings/siteSetting.ts @@ -0,0 +1,8 @@ +// github repo url +export const GITHUB_URL = 'https://github.com/anncwb/vue-vben-admin'; + +// vue-vben-admin-next-doc +export const DOC_URL = 'https://doc.vvbin.cn/'; + +// site url +export const SITE_URL = 'https://vben.vvbin.cn/'; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 00000000000..9ceca5a62b8 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,12 @@ +import type { App } from 'vue'; +import { createPinia } from 'pinia'; +import { registerPiniaPersistPlugin } from '@/store/plugin/persist'; + +const store = createPinia(); +registerPiniaPersistPlugin(store); + +export function setupStore(app: App) { + app.use(store); +} + +export { store }; diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts new file mode 100644 index 00000000000..df55b000107 --- /dev/null +++ b/src/store/modules/app.ts @@ -0,0 +1,118 @@ +import type { + ProjectConfig, + HeaderSetting, + MenuSetting, + TransitionSetting, + MultiTabsSetting, +} from '#/config'; +import type { BeforeMiniState, ApiAddress } from '#/store'; + +import { defineStore } from 'pinia'; +import { store } from '@/store'; + +import { ThemeEnum } from '@/enums/appEnum'; +import { APP_DARK_MODE_KEY, PROJ_CFG_KEY, API_ADDRESS } from '@/enums/cacheEnum'; +import { Persistent } from '@/utils/cache/persistent'; +import { darkMode } from '@/settings/designSetting'; +import { resetRouter } from '@/router'; +import { deepMerge } from '@/utils'; + +interface AppState { + darkMode?: ThemeEnum; + // Page loading status + pageLoading: boolean; + // project config + projectConfig: ProjectConfig | null; + // When the window shrinks, remember some states, and restore these states when the window is restored + beforeMiniInfo: BeforeMiniState; +} +let timeId: TimeoutHandle; +export const useAppStore = defineStore({ + id: 'app', + state: (): AppState => ({ + darkMode: undefined, + pageLoading: false, + projectConfig: Persistent.getLocal(PROJ_CFG_KEY), + beforeMiniInfo: {}, + }), + getters: { + getPageLoading(state): boolean { + return state.pageLoading; + }, + getDarkMode(state): 'light' | 'dark' | string { + return state.darkMode || localStorage.getItem(APP_DARK_MODE_KEY) || darkMode; + }, + + getBeforeMiniInfo(state): BeforeMiniState { + return state.beforeMiniInfo; + }, + + getProjectConfig(state): ProjectConfig { + return state.projectConfig || ({} as ProjectConfig); + }, + + getHeaderSetting(): HeaderSetting { + return this.getProjectConfig.headerSetting; + }, + getMenuSetting(): MenuSetting { + return this.getProjectConfig.menuSetting; + }, + getTransitionSetting(): TransitionSetting { + return this.getProjectConfig.transitionSetting; + }, + getMultiTabsSetting(): MultiTabsSetting { + return this.getProjectConfig.multiTabsSetting; + }, + getApiAddress() { + return JSON.parse(localStorage.getItem(API_ADDRESS) || '{}'); + }, + }, + actions: { + setPageLoading(loading: boolean): void { + this.pageLoading = loading; + }, + + setDarkMode(mode: ThemeEnum): void { + this.darkMode = mode; + localStorage.setItem(APP_DARK_MODE_KEY, mode); + }, + + setBeforeMiniInfo(state: BeforeMiniState): void { + this.beforeMiniInfo = state; + }, + + setProjectConfig(config: DeepPartial): void { + this.projectConfig = deepMerge(this.projectConfig || {}, config) as ProjectConfig; + Persistent.setLocal(PROJ_CFG_KEY, this.projectConfig); + }, + setMenuSetting(setting: Partial): void { + this.projectConfig!.menuSetting = deepMerge(this.projectConfig!.menuSetting, setting); + Persistent.setLocal(PROJ_CFG_KEY, this.projectConfig); + }, + + async resetAllState() { + resetRouter(); + Persistent.clearAll(); + }, + async setPageLoadingAction(loading: boolean): Promise { + if (loading) { + clearTimeout(timeId); + // Prevent flicker + timeId = setTimeout(() => { + this.setPageLoading(loading); + }, 50); + } else { + this.setPageLoading(loading); + clearTimeout(timeId); + } + }, + setApiAddress(config: ApiAddress): void { + localStorage.setItem(API_ADDRESS, JSON.stringify(config)); + }, + }, +}); + +// Need to be used outside the setup +export function useAppStoreWithOut() { + return useAppStore(store); +} diff --git a/src/store/modules/errorLog.ts b/src/store/modules/errorLog.ts new file mode 100644 index 00000000000..b758c8f1a0d --- /dev/null +++ b/src/store/modules/errorLog.ts @@ -0,0 +1,77 @@ +import type { ErrorLogInfo } from '#/store'; + +import { defineStore } from 'pinia'; +import { store } from '@/store'; + +import { formatToDateTime } from '@/utils/dateUtil'; +import projectSetting from '@/settings/projectSetting'; + +import { ErrorTypeEnum } from '@/enums/exceptionEnum'; + +export interface ErrorLogState { + errorLogInfoList: Nullable; + errorLogListCount: number; +} + +export const useErrorLogStore = defineStore({ + id: 'app-error-log', + state: (): ErrorLogState => ({ + errorLogInfoList: null, + errorLogListCount: 0, + }), + getters: { + getErrorLogInfoList(state): ErrorLogInfo[] { + return state.errorLogInfoList || []; + }, + getErrorLogListCount(state): number { + return state.errorLogListCount; + }, + }, + actions: { + addErrorLogInfo(info: ErrorLogInfo) { + const item = { + ...info, + time: formatToDateTime(new Date()), + }; + this.errorLogInfoList = [item, ...(this.errorLogInfoList || [])]; + this.errorLogListCount += 1; + }, + + setErrorLogListCount(count: number): void { + this.errorLogListCount = count; + }, + + /** + * Triggered after ajax request error + * @param error + * @returns + */ + addAjaxErrorInfo(error) { + const { useErrorHandle } = projectSetting; + if (!useErrorHandle) { + return; + } + const errInfo: Partial = { + message: error.message, + type: ErrorTypeEnum.AJAX, + }; + if (error.response) { + const { + config: { url = '', data: params = '', method = 'get', headers = {} } = {}, + data = {}, + } = error.response; + errInfo.url = url; + errInfo.name = 'Ajax Error!'; + errInfo.file = '-'; + errInfo.stack = JSON.stringify(data); + errInfo.detail = JSON.stringify({ params, method, headers }); + } + this.addErrorLogInfo(errInfo as ErrorLogInfo); + }, + }, +}); + +// Need to be used outside the setup +export function useErrorLogStoreWithOut() { + return useErrorLogStore(store); +} diff --git a/src/store/modules/locale.ts b/src/store/modules/locale.ts new file mode 100644 index 00000000000..176c8f41e3a --- /dev/null +++ b/src/store/modules/locale.ts @@ -0,0 +1,55 @@ +import type { LocaleSetting, LocaleType } from '#/config'; + +import { defineStore } from 'pinia'; +import { store } from '@/store'; + +import { LOCALE_KEY } from '@/enums/cacheEnum'; +import { createLocalStorage } from '@/utils/cache'; +import { localeSetting } from '@/settings/localeSetting'; + +const ls = createLocalStorage(); + +const lsLocaleSetting = (ls.get(LOCALE_KEY) || localeSetting) as LocaleSetting; + +interface LocaleState { + localInfo: LocaleSetting; +} + +export const useLocaleStore = defineStore({ + id: 'app-locale', + state: (): LocaleState => ({ + localInfo: lsLocaleSetting, + }), + getters: { + getShowPicker(state): boolean { + return !!state.localInfo?.showPicker; + }, + getLocale(state): LocaleType { + return state.localInfo?.locale ?? 'zh_CN'; + }, + }, + actions: { + /** + * Set up multilingual information and cache + * @param info multilingual info + */ + setLocaleInfo(info: Partial) { + this.localInfo = { ...this.localInfo, ...info }; + ls.set(LOCALE_KEY, this.localInfo); + }, + /** + * Initialize multilingual information and load the existing configuration from the local cache + */ + initLocale() { + this.setLocaleInfo({ + ...localeSetting, + ...this.localInfo, + }); + }, + }, +}); + +// Need to be used outside the setup +export function useLocaleStoreWithOut() { + return useLocaleStore(store); +} diff --git a/src/store/modules/lock.ts b/src/store/modules/lock.ts new file mode 100644 index 00000000000..efb1e2008b1 --- /dev/null +++ b/src/store/modules/lock.ts @@ -0,0 +1,59 @@ +import type { LockInfo } from '#/store'; + +import { defineStore } from 'pinia'; + +import { LOCK_INFO_KEY } from '@/enums/cacheEnum'; +import { Persistent } from '@/utils/cache/persistent'; +import { useUserStore } from './user'; + +interface LockState { + lockInfo: Nullable; +} + +export const useLockStore = defineStore({ + id: 'app-lock', + state: (): LockState => ({ + lockInfo: Persistent.getLocal(LOCK_INFO_KEY), + }), + getters: { + getLockInfo(state): Nullable { + return state.lockInfo; + }, + }, + actions: { + setLockInfo(info: LockInfo) { + this.lockInfo = Object.assign({}, this.lockInfo, info); + Persistent.setLocal(LOCK_INFO_KEY, this.lockInfo, true); + }, + resetLockInfo() { + Persistent.removeLocal(LOCK_INFO_KEY, true); + this.lockInfo = null; + }, + // Unlock + async unLock(password?: string) { + const userStore = useUserStore(); + if (this.lockInfo?.pwd === password) { + this.resetLockInfo(); + return true; + } + const tryLogin = async () => { + try { + const username = userStore.getUserInfo?.username; + const res = await userStore.login({ + username, + password: password!, + goHome: false, + mode: 'none', + }); + if (res) { + this.resetLockInfo(); + } + return res; + } catch (error) { + return false; + } + }; + return await tryLogin(); + }, + }, +}); diff --git a/src/store/modules/multipleTab.ts b/src/store/modules/multipleTab.ts new file mode 100644 index 00000000000..2fd67361652 --- /dev/null +++ b/src/store/modules/multipleTab.ts @@ -0,0 +1,361 @@ +import type { RouteLocationNormalized, RouteLocationRaw, Router } from 'vue-router'; + +import { toRaw, unref } from 'vue'; +import { defineStore } from 'pinia'; +import { store } from '@/store'; + +import { useGo, useRedo } from '@/hooks/web/usePage'; +import { Persistent } from '@/utils/cache/persistent'; + +import { PageEnum } from '@/enums/pageEnum'; +import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '@/router/routes/basic'; +import { getRawRoute } from '@/utils'; +import { MULTIPLE_TABS_KEY } from '@/enums/cacheEnum'; + +import projectSetting from '@/settings/projectSetting'; +import { useUserStore } from '@/store/modules/user'; + +export interface MultipleTabState { + cacheTabList: Set; + tabList: RouteLocationNormalized[]; + lastDragEndIndex: number; +} + +function handleGotoPage(router: Router) { + const go = useGo(router); + go(unref(router.currentRoute).fullPath, true); +} + +const getToTarget = (tabItem: RouteLocationNormalized) => { + const { params, path, query } = tabItem; + return { + params: params || {}, + path, + query: query || {}, + }; +}; + +const cacheTab = projectSetting.multiTabsSetting.cache; + +export const useMultipleTabStore = defineStore({ + id: 'app-multiple-tab', + state: (): MultipleTabState => ({ + // Tabs that need to be cached + cacheTabList: new Set(), + // multiple tab list + tabList: cacheTab ? Persistent.getLocal(MULTIPLE_TABS_KEY) || [] : [], + // Index of the last moved tab + lastDragEndIndex: 0, + }), + getters: { + getTabList(state): RouteLocationNormalized[] { + return state.tabList; + }, + getCachedTabList(state): string[] { + return Array.from(state.cacheTabList); + }, + getLastDragEndIndex(state): number { + return state.lastDragEndIndex; + }, + }, + actions: { + /** + * Update the cache according to the currently opened tabs + */ + async updateCacheTab() { + const cacheMap: Set = new Set(); + + for (const tab of this.tabList) { + const item = getRawRoute(tab); + // Ignore the cache + const needCache = !item.meta?.ignoreKeepAlive; + if (!needCache) { + continue; + } + const name = item.name as string; + cacheMap.add(name); + } + this.cacheTabList = cacheMap; + }, + + /** + * Refresh tabs + */ + async refreshPage(router: Router) { + const { currentRoute } = router; + const route = unref(currentRoute); + const name = route.name; + + const findTab = this.getCachedTabList.find((item) => item === name); + if (findTab) { + this.cacheTabList.delete(findTab); + } + const redo = useRedo(router); + await redo(); + }, + clearCacheTabs(): void { + this.cacheTabList = new Set(); + }, + resetState(): void { + this.tabList = []; + this.clearCacheTabs(); + }, + goToPage(router: Router) { + const go = useGo(router); + const len = this.tabList.length; + const { path } = unref(router.currentRoute); + + let toPath: PageEnum | string = PageEnum.BASE_HOME; + + if (len > 0) { + const page = this.tabList[len - 1]; + const p = page.fullPath || page.path; + if (p) { + toPath = p; + } + } + // Jump to the current page and report an error + path !== toPath && go(toPath as PageEnum, true); + }, + + async addTab(route: RouteLocationNormalized) { + const { path, name, fullPath, params, query, meta } = getRawRoute(route); + // 404 The page does not need to add a tab + if ( + path === PageEnum.ERROR_PAGE || + path === PageEnum.BASE_LOGIN || + !name || + [REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string) + ) { + return; + } + + let updateIndex = -1; + // Existing pages, do not add tabs repeatedly + const tabHasExits = this.tabList.some((tab, index) => { + updateIndex = index; + return decodeURIComponent(tab.fullPath || tab.path) === decodeURIComponent(fullPath || path); + }); + + // If the tab already exists, perform the update operation + if (tabHasExits) { + const curTab = toRaw(this.tabList)[updateIndex]; + if (!curTab) { + return; + } + curTab.params = params || curTab.params; + curTab.query = query || curTab.query; + curTab.fullPath = fullPath || curTab.fullPath; + this.tabList.splice(updateIndex, 1, curTab); + } else { + // Add tab + // 获取动态路由打开数,超过 0 即代表需要控制打开数 + const dynamicLevel = meta?.dynamicLevel ?? -1; + if (dynamicLevel > 0) { + // 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了 + // 首先获取到真实的路由,使用配置方式减少计算开销. + // const realName: string = path.match(/(\S*)\//)![1]; + const realPath = meta?.realPath ?? ''; + // 获取到已经打开的动态路由数, 判断是否大于某一个值 + if ( + this.tabList.filter((e) => e.meta?.realPath ?? '' === realPath).length >= dynamicLevel + ) { + // 关闭第一个 + const index = this.tabList.findIndex((item) => item.meta.realPath === realPath); + index !== -1 && this.tabList.splice(index, 1); + } + } + this.tabList.push(route); + } + this.updateCacheTab(); + cacheTab && Persistent.setLocal(MULTIPLE_TABS_KEY, this.tabList); + }, + + async closeTab(tab: RouteLocationNormalized, router: Router) { + const close = (route: RouteLocationNormalized) => { + const { fullPath, meta: { affix } = {} } = route; + if (affix) { + return; + } + const index = this.tabList.findIndex((item) => item.fullPath === fullPath); + index !== -1 && this.tabList.splice(index, 1); + }; + + const { currentRoute, replace } = router; + + const { path } = unref(currentRoute); + if (path !== tab.path) { + // Closed is not the activation tab + close(tab); + this.updateCacheTab(); + return; + } + + // Closed is activated atb + let toTarget: RouteLocationRaw = {}; + + const index = this.tabList.findIndex((item) => item.path === path); + + // If the current is the leftmost tab + if (index === 0) { + // There is only one tab, then jump to the homepage, otherwise jump to the right tab + if (this.tabList.length === 1) { + const userStore = useUserStore(); + toTarget = userStore.getUserInfo.homePath || PageEnum.BASE_HOME; + } else { + // Jump to the right tab + const page = this.tabList[index + 1]; + toTarget = getToTarget(page); + } + } else { + // Close the current tab + const page = this.tabList[index - 1]; + toTarget = getToTarget(page); + } + close(currentRoute.value); + await replace(toTarget); + }, + + // Close according to key + async closeTabByKey(key: string, router: Router) { + const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === key); + if (index !== -1) { + await this.closeTab(this.tabList[index], router); + const { currentRoute, replace } = router; + // 检查当前路由是否存在于tabList中 + const isActivated = this.tabList.findIndex((item) => { + return item.fullPath === currentRoute.value.fullPath; + }); + // 如果当前路由不存在于TabList中,尝试切换到其它路由 + if (isActivated === -1) { + let pageIndex; + if (index > 0) { + pageIndex = index - 1; + } else if (index < this.tabList.length - 1) { + pageIndex = index + 1; + } else { + pageIndex = -1; + } + if (pageIndex >= 0) { + const page = this.tabList[index - 1]; + const toTarget = getToTarget(page); + await replace(toTarget); + } + } + } + }, + + // Sort the tabs + async sortTabs(oldIndex: number, newIndex: number) { + const currentTab = this.tabList[oldIndex]; + this.tabList.splice(oldIndex, 1); + this.tabList.splice(newIndex, 0, currentTab); + this.lastDragEndIndex = this.lastDragEndIndex + 1; + }, + + // Close the tab on the right and jump + async closeLeftTabs(route: RouteLocationNormalized, router: Router) { + const index = this.tabList.findIndex((item) => item.path === route.path); + + if (index > 0) { + const leftTabs = this.tabList.slice(0, index); + const pathList: string[] = []; + for (const item of leftTabs) { + const affix = item?.meta?.affix ?? false; + if (!affix) { + pathList.push(item.fullPath); + } + } + this.bulkCloseTabs(pathList); + } + this.updateCacheTab(); + handleGotoPage(router); + }, + + // Close the tab on the left and jump + async closeRightTabs(route: RouteLocationNormalized, router: Router) { + const index = this.tabList.findIndex((item) => item.fullPath === route.fullPath); + + if (index >= 0 && index < this.tabList.length - 1) { + const rightTabs = this.tabList.slice(index + 1, this.tabList.length); + + const pathList: string[] = []; + for (const item of rightTabs) { + const affix = item?.meta?.affix ?? false; + if (!affix) { + pathList.push(item.fullPath); + } + } + this.bulkCloseTabs(pathList); + } + this.updateCacheTab(); + handleGotoPage(router); + }, + + async closeAllTab(router: Router) { + this.tabList = this.tabList.filter((item) => item?.meta?.affix ?? false); + this.clearCacheTabs(); + this.goToPage(router); + }, + + /** + * Close other tabs + */ + async closeOtherTabs(route: RouteLocationNormalized, router: Router) { + const closePathList = this.tabList.map((item) => item.fullPath); + + const pathList: string[] = []; + + for (const path of closePathList) { + if (path !== route.fullPath) { + const closeItem = this.tabList.find((item) => item.fullPath === path); + if (!closeItem) { + continue; + } + const affix = closeItem?.meta?.affix ?? false; + if (!affix) { + pathList.push(closeItem.fullPath); + } + } + } + this.bulkCloseTabs(pathList); + this.updateCacheTab(); + Persistent.setLocal(MULTIPLE_TABS_KEY, this.tabList, true); + handleGotoPage(router); + }, + + /** + * Close tabs in bulk + */ + async bulkCloseTabs(pathList: string[]) { + this.tabList = this.tabList.filter((item) => !pathList.includes(item.fullPath)); + }, + + /** + * Set tab's title + */ + async setTabTitle(title: string, route: RouteLocationNormalized) { + const findTab = this.getTabList.find((item) => item === route); + if (findTab) { + findTab.meta.title = title; + await this.updateCacheTab(); + } + }, + /** + * replace tab's path + * **/ + async updateTabPath(fullPath: string, route: RouteLocationNormalized) { + const findTab = this.getTabList.find((item) => item === route); + if (findTab) { + findTab.fullPath = fullPath; + findTab.path = fullPath; + await this.updateCacheTab(); + } + }, + }, +}); + +// Need to be used outside the setup +export function useMultipleTabWithOutStore() { + return useMultipleTabStore(store); +} diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts new file mode 100644 index 00000000000..34d6d94f8a4 --- /dev/null +++ b/src/store/modules/permission.ts @@ -0,0 +1,260 @@ +import type { AppRouteRecordRaw, Menu } from '@/router/types'; + +import { defineStore } from 'pinia'; +import { store } from '@/store'; +import { useI18n } from '@/hooks/web/useI18n'; +import { useUserStore } from './user'; +import { useAppStoreWithOut } from './app'; +import { toRaw } from 'vue'; +import { transformObjToRoute, flatMultiLevelRoutes } from '@/router/helper/routeHelper'; +import { transformRouteToMenu } from '@/router/helper/menuHelper'; + +import projectSetting from '@/settings/projectSetting'; + +import { PermissionModeEnum } from '@/enums/appEnum'; + +import { asyncRoutes } from '@/router/routes'; +import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'; + +import { filter } from '@/utils/helper/treeHelper'; + +import { getMenuList } from '@/api/sys/menu'; +import { getPermCode } from '@/api/sys/user'; + +import { useMessage } from '@/hooks/web/useMessage'; +import { PageEnum } from '@/enums/pageEnum'; + +interface PermissionState { + // Permission code list + // 权限代码列表 + permCodeList: string[] | number[]; + // Whether the route has been dynamically added + // 路由是否动态添加 + isDynamicAddedRoute: boolean; + // To trigger a menu update + // 触发菜单更新 + lastBuildMenuTime: number; + // Backstage menu list + // 后台菜单列表 + backMenuList: Menu[]; + // 菜单列表 + frontMenuList: Menu[]; +} + +export const usePermissionStore = defineStore({ + id: 'app-permission', + state: (): PermissionState => ({ + // 权限代码列表 + permCodeList: [], + // Whether the route has been dynamically added + // 路由是否动态添加 + isDynamicAddedRoute: false, + // To trigger a menu update + // 触发菜单更新 + lastBuildMenuTime: 0, + // Backstage menu list + // 后台菜单列表 + backMenuList: [], + // menu List + // 菜单列表 + frontMenuList: [], + }), + getters: { + getPermCodeList(state): string[] | number[] { + return state.permCodeList; + }, + getBackMenuList(state): Menu[] { + return state.backMenuList; + }, + getFrontMenuList(state): Menu[] { + return state.frontMenuList; + }, + getLastBuildMenuTime(state): number { + return state.lastBuildMenuTime; + }, + getIsDynamicAddedRoute(state): boolean { + return state.isDynamicAddedRoute; + }, + }, + actions: { + setPermCodeList(codeList: string[]) { + this.permCodeList = codeList; + }, + + setBackMenuList(list: Menu[]) { + this.backMenuList = list; + list?.length > 0 && this.setLastBuildMenuTime(); + }, + + setFrontMenuList(list: Menu[]) { + this.frontMenuList = list; + }, + + setLastBuildMenuTime() { + this.lastBuildMenuTime = new Date().getTime(); + }, + + setDynamicAddedRoute(added: boolean) { + this.isDynamicAddedRoute = added; + }, + resetState(): void { + this.isDynamicAddedRoute = false; + this.permCodeList = []; + this.backMenuList = []; + this.lastBuildMenuTime = 0; + }, + async changePermissionCode() { + const codeList = await getPermCode(); + this.setPermCodeList(codeList); + }, + + // 构建路由 + async buildRoutesAction(): Promise { + const { t } = useI18n(); + const userStore = useUserStore(); + const appStore = useAppStoreWithOut(); + + let routes: AppRouteRecordRaw[] = []; + const roleList = toRaw(userStore.getRoleList) || []; + const { permissionMode = projectSetting.permissionMode } = appStore.getProjectConfig; + + // 路由过滤器 在 函数filter 作为回调传入遍历使用 + const routeFilter = (route: AppRouteRecordRaw) => { + const { meta } = route; + // 抽出角色 + const { roles } = meta || {}; + if (!roles) return true; + // 进行角色权限判断 + return roleList.some((role) => roles.includes(role)); + }; + + const routeRemoveIgnoreFilter = (route: AppRouteRecordRaw) => { + const { meta } = route; + // ignoreRoute 为true 则路由仅用于菜单生成,不会在实际的路由表中出现 + const { ignoreRoute } = meta || {}; + // arr.filter 返回 true 表示该元素通过测试 + return !ignoreRoute; + }; + + /** + * @description 根据设置的首页path,修正routes中的affix标记(固定首页) + * */ + const patchHomeAffix = (routes: AppRouteRecordRaw[]) => { + if (!routes || routes.length === 0) return; + let homePath: string = userStore.getUserInfo.homePath || PageEnum.BASE_HOME; + + function patcher(routes: AppRouteRecordRaw[], parentPath = '') { + if (parentPath) parentPath = parentPath + '/'; + routes.forEach((route: AppRouteRecordRaw) => { + const { path, children, redirect } = route; + const currentPath = path.startsWith('/') ? path : parentPath + path; + if (currentPath === homePath) { + if (redirect) { + homePath = route.redirect! as string; + } else { + route.meta = Object.assign({}, route.meta, { affix: true }); + throw new Error('end'); + } + } + children && children.length > 0 && patcher(children, currentPath); + }); + } + + try { + patcher(routes); + } catch (e) { + // 已处理完毕跳出循环 + } + return; + }; + + switch (permissionMode) { + // 角色权限 + case PermissionModeEnum.ROLE: + // 对非一级路由进行过滤 + routes = filter(asyncRoutes, routeFilter); + // 对一级路由根据角色权限过滤 + routes = routes.filter(routeFilter); + // Convert multi-level routing to level 2 routing + // 将多级路由转换为 2 级路由 + routes = flatMultiLevelRoutes(routes); + break; + + // 路由映射, 默认进入该case + case PermissionModeEnum.ROUTE_MAPPING: + // 对非一级路由进行过滤 + routes = filter(asyncRoutes, routeFilter); + // 对一级路由再次根据角色权限过滤 + routes = routes.filter(routeFilter); + // 将路由转换成菜单 + const menuList = transformRouteToMenu(routes, true); + // 移除掉 ignoreRoute: true 的路由 非一级路由 + routes = filter(routes, routeRemoveIgnoreFilter); + // 移除掉 ignoreRoute: true 的路由 一级路由; + routes = routes.filter(routeRemoveIgnoreFilter); + // 对菜单进行排序 + menuList.sort((a, b) => { + return (a.meta?.orderNo || 0) - (b.meta?.orderNo || 0); + }); + + // 设置菜单列表 + this.setFrontMenuList(menuList); + + // Convert multi-level routing to level 2 routing + // 将多级路由转换为 2 级路由 + routes = flatMultiLevelRoutes(routes); + break; + + // If you are sure that you do not need to do background dynamic permissions, please comment the entire judgment below + // 如果确定不需要做后台动态权限,请在下方注释整个判断 + case PermissionModeEnum.BACK: + const { createMessage } = useMessage(); + + createMessage.loading({ + content: t('sys.app.menuLoading'), + duration: 1, + }); + + // !Simulate to obtain permission codes from the background, + // 模拟从后台获取权限码, + // this function may only need to be executed once, and the actual project can be put at the right time by itself + // 这个功能可能只需要执行一次,实际项目可以自己放在合适的时间 + let routeList: AppRouteRecordRaw[] = []; + try { + await this.changePermissionCode(); + routeList = (await getMenuList()) as AppRouteRecordRaw[]; + } catch (error) { + console.error(error); + } + + // Dynamically introduce components + // 动态引入组件 + routeList = transformObjToRoute(routeList); + + // Background routing to menu structure + // 后台路由到菜单结构 + const backMenuList = transformRouteToMenu(routeList); + this.setBackMenuList(backMenuList); + + // remove meta.ignoreRoute item + // 删除 meta.ignoreRoute 项 + routeList = filter(routeList, routeRemoveIgnoreFilter); + routeList = routeList.filter(routeRemoveIgnoreFilter); + + routeList = flatMultiLevelRoutes(routeList); + routes = [PAGE_NOT_FOUND_ROUTE, ...routeList]; + break; + } + + routes.push(ERROR_LOG_ROUTE); + patchHomeAffix(routes); + return routes; + }, + }, +}); + +// Need to be used outside the setup +// 需要在设置之外使用 +export function usePermissionStoreWithOut() { + return usePermissionStore(store); +} diff --git a/src/store/modules/tableSetting.ts b/src/store/modules/tableSetting.ts new file mode 100644 index 00000000000..b6055afd9b8 --- /dev/null +++ b/src/store/modules/tableSetting.ts @@ -0,0 +1,109 @@ +import { defineStore } from 'pinia'; + +import { TABLE_SETTING_KEY } from '@/enums/cacheEnum'; + +import { Persistent } from '@/utils/cache/persistent'; + +import type { TableSetting } from '#/store'; +import type { SizeType, ColumnOptionsType } from '@/components/Table/src/types/table'; + +interface TableSettingState { + setting: Nullable>; +} + +export const useTableSettingStore = defineStore({ + id: 'table-setting', + state: (): TableSettingState => ({ + setting: Persistent.getLocal(TABLE_SETTING_KEY), + }), + getters: { + getTableSetting(state): Nullable> { + return state.setting; + }, + // + getTableSize(state) { + return state.setting?.size || 'middle'; + }, + // + getShowIndexColumn(state) { + return (routerName: string) => { + return state.setting?.showIndexColumn?.[routerName]; + }; + }, + // + getShowRowSelection(state) { + return (routerName: string) => { + return state.setting?.showRowSelection?.[routerName]; + }; + }, + // + getColumns(state) { + return (routerName: string) => { + return state.setting?.columns && state.setting?.columns[routerName] + ? state.setting?.columns[routerName] + : null; + }; + }, + }, + actions: { + setTableSetting(setting: Partial) { + this.setting = Object.assign({}, this.setting, setting); + Persistent.setLocal(TABLE_SETTING_KEY, this.setting, true); + }, + resetTableSetting() { + Persistent.removeLocal(TABLE_SETTING_KEY, true); + this.setting = null; + }, + // + setTableSize(size: SizeType) { + this.setTableSetting( + Object.assign({}, this.setting, { + size, + }), + ); + }, + // + setShowIndexColumn(routerName: string, show: boolean) { + this.setTableSetting( + Object.assign({}, this.setting, { + showIndexColumn: { + ...this.setting?.showIndexColumn, + [routerName]: show, + }, + }), + ); + }, + // + setShowRowSelection(routerName: string, show: boolean) { + this.setTableSetting( + Object.assign({}, this.setting, { + showRowSelection: { + ...this.setting?.showRowSelection, + [routerName]: show, + }, + }), + ); + }, + // + setColumns(routerName: string, columns: Array) { + this.setTableSetting( + Object.assign({}, this.setting, { + columns: { + ...this.setting?.columns, + [routerName]: columns, + }, + }), + ); + }, + clearColumns(routerName: string) { + this.setTableSetting( + Object.assign({}, this.setting, { + columns: { + ...this.setting?.columns, + [routerName]: undefined, + }, + }), + ); + }, + }, +}); diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 00000000000..12bfb9730e3 --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,192 @@ +import type { UserInfo } from '#/store'; +import type { ErrorMessageMode } from '#/axios'; +import { defineStore } from 'pinia'; +import { store } from '@/store'; +import { RoleEnum } from '@/enums/roleEnum'; +import { PageEnum } from '@/enums/pageEnum'; +import { ROLES_KEY, TOKEN_KEY, USER_INFO_KEY } from '@/enums/cacheEnum'; +import { getAuthCache, setAuthCache } from '@/utils/auth'; +import { GetUserInfoModel, LoginParams } from '@/api/sys/model/userModel'; +import { doLogout, getUserInfo, loginApi } from '@/api/sys/user'; +import { useI18n } from '@/hooks/web/useI18n'; +import { useMessage } from '@/hooks/web/useMessage'; +import { router } from '@/router'; +import { usePermissionStore } from '@/store/modules/permission'; +import { RouteRecordRaw } from 'vue-router'; +import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'; +import { isArray } from '@/utils/is'; +import { h } from 'vue'; + +interface UserState { + userInfo: Nullable; + token?: string; + roleList: RoleEnum[]; + sessionTimeout?: boolean; + lastUpdateTime: number; +} + +export const useUserStore = defineStore({ + id: 'app-user', + state: (): UserState => ({ + // user info + userInfo: null, + // token + token: undefined, + // roleList + roleList: [], + // Whether the login expired + sessionTimeout: false, + // Last fetch time + lastUpdateTime: 0, + }), + getters: { + getUserInfo(state): UserInfo { + return state.userInfo || getAuthCache(USER_INFO_KEY) || {}; + }, + getToken(state): string { + return state.token || getAuthCache(TOKEN_KEY); + }, + getRoleList(state): RoleEnum[] { + return state.roleList.length > 0 ? state.roleList : getAuthCache(ROLES_KEY); + }, + getSessionTimeout(state): boolean { + return !!state.sessionTimeout; + }, + getLastUpdateTime(state): number { + return state.lastUpdateTime; + }, + }, + actions: { + setToken(info: string | undefined) { + this.token = info ? info : ''; // for null or undefined value + setAuthCache(TOKEN_KEY, info); + }, + setRoleList(roleList: RoleEnum[]) { + this.roleList = roleList; + setAuthCache(ROLES_KEY, roleList); + }, + setUserInfo(info: UserInfo | null) { + this.userInfo = info; + this.lastUpdateTime = new Date().getTime(); + setAuthCache(USER_INFO_KEY, info); + }, + setSessionTimeout(flag: boolean) { + this.sessionTimeout = flag; + }, + resetState() { + this.userInfo = null; + this.token = ''; + this.roleList = []; + this.sessionTimeout = false; + }, + /** + * @description: login + */ + async login( + params: LoginParams & { + goHome?: boolean; + mode?: ErrorMessageMode; + }, + ): Promise { + try { + const { goHome = true, mode, ...loginParams } = params; + const data = await loginApi(loginParams, mode); + const { token } = data; + + // save token + this.setToken(token); + return this.afterLoginAction(goHome); + } catch (error) { + return Promise.reject(error); + } + }, + async afterLoginAction(goHome?: boolean): Promise { + if (!this.getToken) return null; + // get user info + const userInfo = await this.getUserInfoAction(); + + const sessionTimeout = this.sessionTimeout; + if (sessionTimeout) { + this.setSessionTimeout(false); + } else { + const permissionStore = usePermissionStore(); + + // 动态路由加载(首次) + if (!permissionStore.isDynamicAddedRoute) { + const routes = await permissionStore.buildRoutesAction(); + [...routes, PAGE_NOT_FOUND_ROUTE].forEach((route) => { + router.addRoute(route as unknown as RouteRecordRaw); + }); + // 记录动态路由加载完成 + permissionStore.setDynamicAddedRoute(true); + } + + goHome && (await router.replace(userInfo?.homePath || PageEnum.BASE_HOME)); + } + return userInfo; + }, + async getUserInfoAction(): Promise { + if (!this.getToken) return null; + const userInfo = await getUserInfo(); + const { roles = [] } = userInfo; + if (isArray(roles)) { + const roleList = roles.map((item) => item.value) as RoleEnum[]; + this.setRoleList(roleList); + } else { + userInfo.roles = []; + this.setRoleList([]); + } + this.setUserInfo(userInfo); + return userInfo; + }, + /** + * @description: logout + */ + async logout(goLogin = false) { + if (this.getToken) { + try { + await doLogout(); + } catch { + console.log('注销Token失败'); + } + } + this.setToken(undefined); + this.setSessionTimeout(false); + this.setUserInfo(null); + if (goLogin) { + // 直接回登陆页 + router.replace(PageEnum.BASE_LOGIN); + } else { + // 回登陆页带上当前路由地址 + router.replace({ + path: PageEnum.BASE_LOGIN, + query: { + redirect: encodeURIComponent(router.currentRoute.value.fullPath), + }, + }); + } + }, + + /** + * @description: Confirm before logging out + */ + confirmLoginOut() { + const { createConfirm } = useMessage(); + const { t } = useI18n(); + createConfirm({ + iconType: 'warning', + title: () => h('span', t('sys.app.logoutTip')), + content: () => h('span', t('sys.app.logoutMessage')), + onOk: async () => { + // 主动登出,不带redirect地址 + await this.logout(true); + }, + }); + }, + }, +}); + +// Need to be used outside the setup +export function useUserStoreWithOut() { + return useUserStore(store); +} diff --git a/src/store/plugin/persist.ts b/src/store/plugin/persist.ts new file mode 100644 index 00000000000..8c8939f7b1c --- /dev/null +++ b/src/store/plugin/persist.ts @@ -0,0 +1,75 @@ +/** + * Pinia Persist Plugin + * Pinia 持久化插件 + * @link https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/ + * + */ +import type { Pinia } from 'pinia'; +import { createPersistedState, Serializer } from 'pinia-plugin-persistedstate'; +import type { PersistedStateFactoryOptions } from 'pinia-plugin-persistedstate'; +import { getCommonStoragePrefix } from '@/utils/env'; +import { Encryption, EncryptionFactory } from '@/utils/cipher'; +import { cacheCipher, SHOULD_ENABLE_STORAGE_ENCRYPTION } from '@/settings/encryptionSetting'; + +export const PERSIST_KEY_PREFIX = getCommonStoragePrefix(); + +const persistEncryption: Encryption = EncryptionFactory.createAesEncryption({ + key: cacheCipher.key, + iv: cacheCipher.iv, +}); + +/** + * Custom serializer for serialization and deserialization of storage data + * 自定义序列化器,用于序列化和反序列化存储数据 + * + * @param shouldEnableEncryption whether to enable encryption for storage data 是否启用存储数据加密 + * @returns serializer + */ +function customSerializer(shouldEnableEncryption: boolean): Serializer { + if (shouldEnableEncryption) { + return { + deserialize: (value) => { + const decrypted = persistEncryption.decrypt(value); + return JSON.parse(decrypted); + }, + serialize: (value) => { + const serialized = JSON.stringify(value); + return persistEncryption.encrypt(serialized); + }, + }; + } else { + return { + deserialize: (value) => { + return JSON.parse(value); + }, + serialize: (value) => { + return JSON.stringify(value); + }, + }; + } +} + +/** + * Register Pinia Persist Plugin + * 注册 Pinia 持久化插件 + * + * @param pinia Pinia instance Pinia 实例 + */ +export function registerPiniaPersistPlugin(pinia: Pinia) { + pinia.use(createPersistedState(createPersistedStateOptions(PERSIST_KEY_PREFIX))); +} + +/** + * Create Persisted State Options + * 创建持久化状态选项 + * + * @param keyPrefix prefix for storage key 储存键前缀 + * @returns persisted state factory options + */ +export function createPersistedStateOptions(keyPrefix: string): PersistedStateFactoryOptions { + return { + storage: localStorage, + key: (id) => `${keyPrefix}__${id}`, + serializer: customSerializer(SHOULD_ENABLE_STORAGE_ENCRYPTION), + }; +} diff --git a/src/utils/__test__/index.test.ts b/src/utils/__test__/index.test.ts new file mode 100644 index 00000000000..bc6995e4f66 --- /dev/null +++ b/src/utils/__test__/index.test.ts @@ -0,0 +1,145 @@ +// 暂时未安装依赖,无法测试 +// @ts-ignore +import { describe, expect, test } from 'vitest'; +import { deepMerge } from '@/utils'; + +describe('deepMerge function', () => { + test('should correctly merge basic data types', () => { + const source = { a: 1, b: 2, c: null }; + const target = { + a: 2, + b: undefined, + c: 3, + }; + const expected = { + a: 2, + b: 2, + c: 3, + }; + expect(deepMerge(source, target)).toStrictEqual(expected); + }); + + test('should return the same date if only 1 is passed', () => { + const foo = new Date(); + const merged = deepMerge(foo, null); + const merged2 = deepMerge(undefined, foo); + expect(merged).toStrictEqual(foo); + expect(merged2).toStrictEqual(foo); + expect(merged).toStrictEqual(merged2); + }); + + test('should merge two objects recursively', () => { + const source = { + a: { b: { c: 1 }, d: [1, 2] }, + e: [1, 2], + foo: { bar: 3 }, + array: [ + { + does: 'work', + too: [1, 2, 3], + }, + ], + r: { a: 1 }, + }; + const target = { + a: { b: { d: [3] } }, + e: [3], + foo: { baz: 4 }, + qu: 5, + array: [ + { + does: 'work', + too: [4, 5, 6], + }, + { + really: 'yes', + }, + ], + r: { a: 2 }, + }; + const expected = { + a: { b: { c: 1, d: [3] }, d: [1, 2] }, + e: [3], + foo: { + bar: 3, + baz: 4, + }, + array: [ + { + does: 'work', + too: [4, 5, 6], + }, + { + really: 'yes', + }, + ], + qu: 5, + r: { a: 2 }, + }; + expect(deepMerge(source, target)).toStrictEqual(expected); + }); + + test('should replace arrays by default', () => { + const source = { + a: { b: { d: [1, 2] } }, + e: [1, 2], + }; + const target = { + a: { b: { d: [3] } }, + e: [3], + }; + const expected = { + a: { b: { d: [3] } }, + e: [3], + }; + expect(deepMerge(source, target)).toStrictEqual(expected); + }); + + test("should union arrays using mergeArrays = 'union'", () => { + const source = { + a: { b: { d: [1, 2] } }, + e: [1, 2], + }; + const target = { + a: { b: { d: [2, 3] } }, + e: [1, 3], + }; + const expected = { + a: { b: { d: [1, 2, 3] } }, + e: [1, 2, 3], + }; + expect(deepMerge(source, target, 'union')).toStrictEqual(expected); + }); + + test("should intersect arrays using mergeArrays = 'intersection'", () => { + const source = { + a: { b: { d: [1, 2] } }, + e: [1, 2], + }; + const target = { + a: { b: { d: [2, 3] } }, + e: [3], + }; + const expected = { + a: { b: { d: [2] } }, + e: [], + }; + expect(deepMerge(source, target, 'intersection')).toStrictEqual(expected); + }); + + test("should concatenate arrays using mergeArrays = 'concat'", () => { + const source = { + a: { b: { d: [1, 2] } }, + e: [1, 2], + }; + const target = { + a: { b: { d: [2, 3] } }, + e: [3], + }; + const expected = { + a: { b: { d: [1, 2, 2, 3] } }, + e: [1, 2, 3], + }; + expect(deepMerge(source, target, 'concat')).toStrictEqual(expected); + }); +}); diff --git a/src/utils/auth/index.ts b/src/utils/auth/index.ts new file mode 100644 index 00000000000..2ec0567fa13 --- /dev/null +++ b/src/utils/auth/index.ts @@ -0,0 +1,25 @@ +import { Persistent, BasicKeys } from '@/utils/cache/persistent'; +import { CacheTypeEnum, TOKEN_KEY } from '@/enums/cacheEnum'; +import projectSetting from '@/settings/projectSetting'; + +const { permissionCacheType } = projectSetting; +const isLocal = permissionCacheType === CacheTypeEnum.LOCAL; + +export function getToken() { + return getAuthCache(TOKEN_KEY); +} + +export function getAuthCache(key: BasicKeys) { + const fn = isLocal ? Persistent.getLocal : Persistent.getSession; + return fn(key) as T; +} + +export function setAuthCache(key: BasicKeys, value) { + const fn = isLocal ? Persistent.setLocal : Persistent.setSession; + return fn(key, value, true); +} + +export function clearAuthCache(immediate = true) { + const fn = isLocal ? Persistent.clearLocal : Persistent.clearSession; + return fn(immediate); +} diff --git a/src/utils/bem.ts b/src/utils/bem.ts new file mode 100644 index 00000000000..cc172acda96 --- /dev/null +++ b/src/utils/bem.ts @@ -0,0 +1,53 @@ +import { prefixCls } from '@/settings/designSetting'; + +type Mod = string | { [key: string]: any }; +type Mods = Mod | Mod[]; + +export type BEM = ReturnType; + +function genBem(name: string, mods?: Mods): string { + if (!mods) { + return ''; + } + + if (typeof mods === 'string') { + return ` ${name}--${mods}`; + } + + // ArrayConstructor.isArray(arg: any): arg is any[] + if (Array.isArray(mods)) { + return (mods as Mod[]).reduce((ret, item) => ret + genBem(name, item), ''); + } + + return Object.keys(mods).reduce((ret, key) => ret + (mods[key] ? genBem(name, key) : ''), ''); +} + +/** + * bem helper + * b() // 'button' + * b('text') // 'button__text' + * b({ disabled }) // 'button button--disabled' + * b('text', { disabled }) // 'button__text button__text--disabled' + * b(['disabled', 'primary']) // 'button button--disabled button--primary' + */ +export function buildBEM(name: string) { + return (el?: Mods, mods?: Mods): Mods => { + if (el && typeof el !== 'string') { + mods = el; + el = ''; + } + + el = el ? `${name}__${el}` : name; + + return `${el}${genBem(el, mods)}`; + }; +} + +export function createBEM(name: string) { + return [buildBEM(`${prefixCls}-${name}`)]; +} + +export function createNamespace(name: string) { + const prefixedName = `${prefixCls}-${name}`; + return [prefixedName, buildBEM(prefixedName)] as const; +} diff --git a/src/utils/cache/index.ts b/src/utils/cache/index.ts new file mode 100644 index 00000000000..62df442dc7f --- /dev/null +++ b/src/utils/cache/index.ts @@ -0,0 +1,31 @@ +import { getStorageShortName } from '@/utils/env'; +import { createStorage as create, CreateStorageParams } from './storageCache'; +import { SHOULD_ENABLE_STORAGE_ENCRYPTION, DEFAULT_CACHE_TIME } from '@/settings/encryptionSetting'; + +export type Options = Partial; + +const createOptions = (storage: Storage, options: Options = {}): Options => { + return { + // No encryption in debug mode + hasEncrypt: SHOULD_ENABLE_STORAGE_ENCRYPTION, + storage, + prefixKey: getStorageShortName(), + ...options, + }; +}; + +export const WebStorage = create(createOptions(sessionStorage)); + +export const createStorage = (storage: Storage = sessionStorage, options: Options = {}) => { + return create(createOptions(storage, options)); +}; + +export const createSessionStorage = (options: Options = {}) => { + return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME }); +}; + +export const createLocalStorage = (options: Options = {}) => { + return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME }); +}; + +export default WebStorage; diff --git a/src/utils/cache/memory.ts b/src/utils/cache/memory.ts new file mode 100644 index 00000000000..8d2cfd18117 --- /dev/null +++ b/src/utils/cache/memory.ts @@ -0,0 +1,107 @@ +export interface Cache { + value?: V; + timeoutId?: ReturnType; + time?: number; + alive?: number; +} + +const NOT_ALIVE = 0; + +export class Memory { + private cache: { [key in keyof T]?: Cache } = {}; + private alive: number; + + constructor(alive = NOT_ALIVE) { + // Unit second + this.alive = alive * 1000; + } + + get getCache() { + return this.cache; + } + + setCache(cache) { + this.cache = cache; + } + + // get(key: K) { + // const item = this.getItem(key); + // const time = item?.time; + // if (!isNil(time) && time < new Date().getTime()) { + // this.remove(key); + // } + // return item?.value ?? undefined; + // } + + get(key: K) { + return this.cache[key]; + } + + set(key: K, value: V, expires?: number) { + let item = this.get(key); + + if (!expires || (expires as number) <= 0) { + expires = this.alive; + } + if (item) { + if (item.timeoutId) { + clearTimeout(item.timeoutId); + item.timeoutId = undefined; + } + item.value = value; + } else { + item = { value, alive: expires }; + this.cache[key] = item; + } + + if (!expires) { + return value; + } + const now = new Date().getTime(); + /** + * Prevent overflow of the setTimeout Maximum delay value + * Maximum delay value 2,147,483,647 ms + * https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value + */ + item.time = expires > now ? expires : now + expires; + item.timeoutId = setTimeout( + () => { + this.remove(key); + }, + expires > now ? expires - now : expires, + ); + + return value; + } + + remove(key: K) { + const item = this.get(key); + Reflect.deleteProperty(this.cache, key); + if (item) { + clearTimeout(item.timeoutId!); + return item.value; + } + } + + resetCache(cache: { [K in keyof T]: Cache }) { + Object.keys(cache).forEach((key) => { + const k = key as any as keyof T; + const item = cache[k]; + if (item && item.time) { + const now = new Date().getTime(); + const expire = item.time; + if (expire > now) { + this.set(k, item.value, expire); + } + } + }); + } + + clear() { + Object.keys(this.cache).forEach((key) => { + const item = this.cache[key]; + item.timeoutId && clearTimeout(item.timeoutId); + }); + this.cache = {}; + } +} diff --git a/src/utils/cache/persistent.ts b/src/utils/cache/persistent.ts new file mode 100644 index 00000000000..7e3d8539234 --- /dev/null +++ b/src/utils/cache/persistent.ts @@ -0,0 +1,134 @@ +import type { LockInfo, UserInfo, TableSetting } from '#/store'; +import type { ProjectConfig } from '#/config'; +import type { RouteLocationNormalized } from 'vue-router'; + +import { createLocalStorage, createSessionStorage } from '@/utils/cache'; +import { Memory } from './memory'; +import { + TOKEN_KEY, + USER_INFO_KEY, + ROLES_KEY, + LOCK_INFO_KEY, + PROJ_CFG_KEY, + APP_LOCAL_CACHE_KEY, + APP_SESSION_CACHE_KEY, + MULTIPLE_TABS_KEY, + TABLE_SETTING_KEY, +} from '@/enums/cacheEnum'; +import { DEFAULT_CACHE_TIME } from '@/settings/encryptionSetting'; +import { toRaw } from 'vue'; +import { pick, omit } from 'lodash-es'; + +interface BasicStore { + [TOKEN_KEY]: string | number | null | undefined; + [USER_INFO_KEY]: UserInfo; + [ROLES_KEY]: string[]; + [LOCK_INFO_KEY]: LockInfo; + [PROJ_CFG_KEY]: ProjectConfig; + [MULTIPLE_TABS_KEY]: RouteLocationNormalized[]; + [TABLE_SETTING_KEY]: Partial; +} + +type LocalStore = BasicStore; + +type SessionStore = BasicStore; + +export type BasicKeys = keyof BasicStore; +type LocalKeys = keyof LocalStore; +type SessionKeys = keyof SessionStore; + +const ls = createLocalStorage(); +const ss = createSessionStorage(); + +const localMemory = new Memory(DEFAULT_CACHE_TIME); +const sessionMemory = new Memory(DEFAULT_CACHE_TIME); + +function initPersistentMemory() { + const localCache = ls.get(APP_LOCAL_CACHE_KEY); + const sessionCache = ss.get(APP_SESSION_CACHE_KEY); + localCache && localMemory.resetCache(localCache); + sessionCache && sessionMemory.resetCache(sessionCache); +} + +export class Persistent { + static getLocal(key: LocalKeys) { + return localMemory.get(key)?.value as Nullable; + } + + static setLocal(key: LocalKeys, value: LocalStore[LocalKeys], immediate = false): void { + localMemory.set(key, toRaw(value)); + immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache); + } + + static removeLocal(key: LocalKeys, immediate = false): void { + localMemory.remove(key); + immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache); + } + + static clearLocal(immediate = false): void { + localMemory.clear(); + immediate && ls.clear(); + } + + static getSession(key: SessionKeys) { + return sessionMemory.get(key)?.value as Nullable; + } + + static setSession(key: SessionKeys, value: SessionStore[SessionKeys], immediate = false): void { + sessionMemory.set(key, toRaw(value)); + immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache); + } + + static removeSession(key: SessionKeys, immediate = false): void { + sessionMemory.remove(key); + immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache); + } + static clearSession(immediate = false): void { + sessionMemory.clear(); + immediate && ss.clear(); + } + + static clearAll(immediate = false) { + sessionMemory.clear(); + localMemory.clear(); + if (immediate) { + ls.clear(); + ss.clear(); + } + } +} + +window.addEventListener('beforeunload', function () { + // TOKEN_KEY 在登录或注销时已经写入到storage了,此处为了解决同时打开多个窗口时token不同步的问题 + // LOCK_INFO_KEY 在锁屏和解锁时写入,此处也不应修改 + ls.set(APP_LOCAL_CACHE_KEY, { + ...omit(localMemory.getCache, LOCK_INFO_KEY), + ...pick(ls.get(APP_LOCAL_CACHE_KEY), [TOKEN_KEY, USER_INFO_KEY, LOCK_INFO_KEY]), + }); + ss.set(APP_SESSION_CACHE_KEY, { + ...omit(sessionMemory.getCache, LOCK_INFO_KEY), + ...pick(ss.get(APP_SESSION_CACHE_KEY), [TOKEN_KEY, USER_INFO_KEY, LOCK_INFO_KEY]), + }); +}); + +function storageChange(e: any) { + const { key, newValue, oldValue } = e; + + if (!key) { + Persistent.clearAll(); + return; + } + + if (!!newValue && !!oldValue) { + if (APP_LOCAL_CACHE_KEY === key) { + Persistent.clearLocal(); + } + if (APP_SESSION_CACHE_KEY === key) { + Persistent.clearSession(); + } + } +} + +window.addEventListener('storage', storageChange); + +initPersistentMemory(); diff --git a/src/utils/cache/storageCache.ts b/src/utils/cache/storageCache.ts new file mode 100644 index 00000000000..3250408ab3c --- /dev/null +++ b/src/utils/cache/storageCache.ts @@ -0,0 +1,111 @@ +import { cacheCipher } from '@/settings/encryptionSetting'; +import { isNil } from '@/utils/is'; +import { Encryption, EncryptionFactory, EncryptionParams } from '@/utils/cipher'; + +export interface CreateStorageParams extends EncryptionParams { + prefixKey: string; + storage: Storage; + hasEncrypt: boolean; + timeout?: Nullable; +} +// TODO 移除此文件夹下全部代码 +export const createStorage = ({ + prefixKey = '', + storage = sessionStorage, + key = cacheCipher.key, + iv = cacheCipher.iv, + timeout = null, + hasEncrypt = true, +}: Partial = {}) => { + if (hasEncrypt && [key.length, iv.length].some((item) => item !== 16)) { + throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!'); + } + + const persistEncryption: Encryption = EncryptionFactory.createAesEncryption({ + key: cacheCipher.key, + iv: cacheCipher.iv, + }); + /** + * Cache class + * Construction parameters can be passed into sessionStorage, localStorage, + * @class Cache + * @example + */ + const WebStorage = class WebStorage { + private storage: Storage; + private prefixKey?: string; + private encryption: Encryption; + private hasEncrypt: boolean; + /** + * + * @param {*} storage + */ + constructor() { + this.storage = storage; + this.prefixKey = prefixKey; + this.encryption = persistEncryption; + this.hasEncrypt = hasEncrypt; + } + + private getKey(key: string) { + return `${this.prefixKey}${key}`.toUpperCase(); + } + + /** + * Set cache + * @param {string} key + * @param {*} value + * @param {*} expire Expiration time in seconds + * @memberof Cache + */ + set(key: string, value: any, expire: number | null = timeout) { + const stringData = JSON.stringify({ + value, + time: Date.now(), + expire: !isNil(expire) ? new Date().getTime() + expire * 1000 : null, + }); + const stringifyValue = this.hasEncrypt ? this.encryption.encrypt(stringData) : stringData; + this.storage.setItem(this.getKey(key), stringifyValue); + } + + /** + * Read cache + * @param {string} key + * @param {*} def + * @memberof Cache + */ + get(key: string, def: any = null): any { + const val = this.storage.getItem(this.getKey(key)); + if (!val) return def; + + try { + const decVal = this.hasEncrypt ? this.encryption.decrypt(val) : val; + const data = JSON.parse(decVal); + const { value, expire } = data; + if (isNil(expire) || expire >= new Date().getTime()) { + return value; + } + this.remove(key); + } catch (e) { + return def; + } + } + + /** + * Delete cache based on key + * @param {string} key + * @memberof Cache + */ + remove(key: string) { + this.storage.removeItem(this.getKey(key)); + } + + /** + * Delete all caches of this instance + */ + clear(): void { + this.storage.clear(); + } + }; + return new WebStorage(); +}; diff --git a/src/utils/cipher.ts b/src/utils/cipher.ts new file mode 100644 index 00000000000..cbc22422f56 --- /dev/null +++ b/src/utils/cipher.ts @@ -0,0 +1,159 @@ +import { decrypt as aesDecrypt, encrypt as aesEncrypt } from 'crypto-js/aes'; +import UTF8, { parse } from 'crypto-js/enc-utf8'; +import pkcs7 from 'crypto-js/pad-pkcs7'; +import CTR from 'crypto-js/mode-ctr'; +import Base64 from 'crypto-js/enc-base64'; +import MD5 from 'crypto-js/md5'; +import SHA256 from 'crypto-js/sha256'; +import SHA512 from 'crypto-js/sha512'; + +// Define an interface for encryption +// 定义一个加密器的接口 +export interface Encryption { + encrypt(plainText: string): string; + decrypt(cipherText: string): string; +} +// Define an interface for Hashing +// 定义一个哈希算法的接口 +export interface Hashing { + hash(data: string): string; +} + +export interface EncryptionParams { + key: string; + iv: string; +} + +class AesEncryption implements Encryption { + private readonly key; + private readonly iv; + + constructor({ key, iv }: EncryptionParams) { + this.key = parse(key); + this.iv = parse(iv); + } + + get getOptions() { + return { + mode: CTR, + padding: pkcs7, + iv: this.iv, + }; + } + + encrypt(plainText: string) { + return aesEncrypt(plainText, this.key, this.getOptions).toString(); + } + + decrypt(cipherText: string) { + return aesDecrypt(cipherText, this.key, this.getOptions).toString(UTF8); + } +} + +// Define a singleton class for Base64 encryption +class Base64Encryption implements Encryption { + private static instance: Base64Encryption; + + private constructor() {} + + // Get the singleton instance + // 获取单例实例 + public static getInstance(): Base64Encryption { + if (!Base64Encryption.instance) { + Base64Encryption.instance = new Base64Encryption(); + } + return Base64Encryption.instance; + } + + encrypt(plainText: string) { + return UTF8.parse(plainText).toString(Base64); + } + + decrypt(cipherText: string) { + return Base64.parse(cipherText).toString(UTF8); + } +} + +// Define a singleton class for MD5 Hashing +class MD5Hashing implements Hashing { + private static instance: MD5Hashing; + + private constructor() {} + + // Get the singleton instance + // 获取单例实例 + public static getInstance(): MD5Hashing { + if (!MD5Hashing.instance) { + MD5Hashing.instance = new MD5Hashing(); + } + return MD5Hashing.instance; + } + + hash(plainText: string) { + return MD5(plainText).toString(); + } +} + +// Define a singleton class for SHA256 Hashing +class SHA256Hashing implements Hashing { + private static instance: SHA256Hashing; + + private constructor() {} + + // Get the singleton instance + // 获取单例实例 + public static getInstance(): SHA256Hashing { + if (!SHA256Hashing.instance) { + SHA256Hashing.instance = new SHA256Hashing(); + } + return SHA256Hashing.instance; + } + + hash(plainText: string) { + return SHA256(plainText).toString(); + } +} + +// Define a singleton class for SHA512 Hashing +class SHA512Hashing implements Hashing { + private static instance: SHA512Hashing; + + private constructor() {} + + // Get the singleton instance + // 获取单例实例 + public static getInstance(): SHA256Hashing { + if (!SHA512Hashing.instance) { + SHA512Hashing.instance = new SHA512Hashing(); + } + return SHA512Hashing.instance; + } + + hash(plainText: string) { + return SHA512(plainText).toString(); + } +} + +export class EncryptionFactory { + public static createAesEncryption(params: EncryptionParams): Encryption { + return new AesEncryption(params); + } + + public static createBase64Encryption(): Encryption { + return Base64Encryption.getInstance(); + } +} + +export class HashingFactory { + public static createMD5Hashing(): Hashing { + return MD5Hashing.getInstance(); + } + + public static createSHA256Hashing(): Hashing { + return SHA256Hashing.getInstance(); + } + + public static createSHA512Hashing(): Hashing { + return SHA512Hashing.getInstance(); + } +} diff --git a/src/utils/color.ts b/src/utils/color.ts new file mode 100644 index 00000000000..3c0ca5e635a --- /dev/null +++ b/src/utils/color.ts @@ -0,0 +1,151 @@ +/** + * 判断是否 十六进制颜色值. + * 输入形式可为 #fff000 #f00 + * + * @param String color 十六进制颜色值 + * @return Boolean + */ +export function isHexColor(color: string) { + const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; + return reg.test(color); +} + +/** + * RGB 颜色值转换为 十六进制颜色值. + * r, g, 和 b 需要在 [0, 255] 范围内 + * + * @return String 类似#ff00ff + * @param r + * @param g + * @param b + */ +export function rgbToHex(r: number, g: number, b: number) { + // tslint:disable-next-line:no-bitwise + const hex = ((r << 16) | (g << 8) | b).toString(16); + return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex; +} + +/** + * Transform a HEX color to its RGB representation + * @param {string} hex The color to transform + * @returns The RGB representation of the passed color + */ +export function hexToRGB(hex: string) { + let sHex = hex.toLowerCase(); + if (isHexColor(hex)) { + if (sHex.length === 4) { + let sColorNew = '#'; + for (let i = 1; i < 4; i += 1) { + sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1)); + } + sHex = sColorNew; + } + const sColorChange: number[] = []; + for (let i = 1; i < 7; i += 2) { + sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2))); + } + return 'RGB(' + sColorChange.join(',') + ')'; + } + return sHex; +} + +export function colorIsDark(color: string) { + if (!isHexColor(color)) return; + const [r, g, b] = hexToRGB(color) + .replace(/(?:\(|\)|rgb|RGB)*/g, '') + .split(',') + .map((item) => Number(item)); + return r * 0.299 + g * 0.578 + b * 0.114 < 192; +} + +/** + * Darkens a HEX color given the passed percentage + * @param {string} color The color to process + * @param {number} amount The amount to change the color by + * @returns {string} The HEX representation of the processed color + */ +export function darken(color: string, amount: number) { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color; + amount = Math.trunc((255 * amount) / 100); + return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight( + color.substring(2, 4), + amount, + )}${subtractLight(color.substring(4, 6), amount)}`; +} + +/** + * Lightens a 6 char HEX color according to the passed percentage + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed color represented as HEX + */ +export function lighten(color: string, amount: number) { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color; + amount = Math.trunc((255 * amount) / 100); + return `#${addLight(color.substring(0, 2), amount)}${addLight( + color.substring(2, 4), + amount, + )}${addLight(color.substring(4, 6), amount)}`; +} + +/* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */ +/** + * Sums the passed percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +function addLight(color: string, amount: number) { + const cc = parseInt(color, 16) + amount; + const c = cc > 255 ? 255 : cc; + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`; +} + +/** + * Calculates luminance of an rgb color + * @param {number} r red + * @param {number} g green + * @param {number} b blue + */ +function luminanace(r: number, g: number, b: number) { + const a = [r, g, b].map((v) => { + v /= 255; + return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); + }); + return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; +} + +/** + * Calculates contrast between two rgb colors + * @param {string} rgb1 rgb color 1 + * @param {string} rgb2 rgb color 2 + */ +function contrast(rgb1: string[], rgb2: number[]) { + return ( + (luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / + (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05) + ); +} + +/** + * Determines what the best text color is (black or white) based con the contrast with the background + * @param hexColor - Last selected color by the user + */ +export function calculateBestTextColor(hexColor: string) { + const rgbColor = hexToRGB(hexColor.substring(1)); + const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0]); + + return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF'; +} + +/** + * Subtracts the indicated percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +function subtractLight(color: string, amount: number) { + const cc = parseInt(color, 16) - amount; + const c = cc < 0 ? 0 : cc; + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`; +} diff --git a/src/utils/copyTextToClipboard.ts b/src/utils/copyTextToClipboard.ts new file mode 100644 index 00000000000..236e9147396 --- /dev/null +++ b/src/utils/copyTextToClipboard.ts @@ -0,0 +1,41 @@ +import { message } from 'ant-design-vue'; + +// `navigator.clipboard` 可能因浏览器设置或浏览器兼容而造成兼容问题 +export function copyText(text: string, prompt: string | null = '已成功复制到剪切板!') { + if (navigator.clipboard) { + return navigator.clipboard + .writeText(text) + .then(() => { + prompt && message.success(prompt); + }) + .catch((error) => { + message.error('复制失败!' + error.message); + return error; + }); + } + if (Reflect.has(document, 'execCommand')) { + return new Promise((resolve, reject) => { + try { + const textArea = document.createElement('textarea'); + textArea.value = text; + // 在手机 Safari 浏览器中,点击复制按钮,整个页面会跳动一下 + textArea.style.width = '0'; + textArea.style.position = 'fixed'; + textArea.style.left = '-999px'; + textArea.style.top = '10px'; + textArea.setAttribute('readonly', 'readonly'); + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + + prompt && message.success(prompt); + resolve(); + } catch (error) { + message.error('复制失败!' + error.message); + reject(error); + } + }); + } + return Promise.reject(`"navigator.clipboard" 或 "document.execCommand" 中存在API错误, 拷贝失败!`); +} diff --git a/src/utils/dateUtil.ts b/src/utils/dateUtil.ts new file mode 100644 index 00000000000..e18387dfa02 --- /dev/null +++ b/src/utils/dateUtil.ts @@ -0,0 +1,17 @@ +/** + * Independent time operation tool to facilitate subsequent switch to dayjs + */ +import dayjs from 'dayjs'; + +const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; +const DATE_FORMAT = 'YYYY-MM-DD'; + +export function formatToDateTime(date?: dayjs.ConfigType, format = DATE_TIME_FORMAT): string { + return dayjs(date).format(format); +} + +export function formatToDate(date?: dayjs.ConfigType, format = DATE_FORMAT): string { + return dayjs(date).format(format); +} + +export const dateUtil = dayjs; diff --git a/src/utils/domUtils.ts b/src/utils/domUtils.ts new file mode 100644 index 00000000000..3b3178fe5f3 --- /dev/null +++ b/src/utils/domUtils.ts @@ -0,0 +1,198 @@ +import type { FunctionArgs } from '@vueuse/core'; +import { upperFirst } from 'lodash-es'; + +export interface ViewportOffsetResult { + /** + * 元素左边距离 body 左边的距离(和 getBoundingClientRect 的 left 一样) + */ + left: number; + /** + * 元素顶边距离 body 顶边的距离(和 getBoundingClientRect 的 top 一样) + */ + top: number; + /** + * 元素右边距离 body 右边的距离 + */ + right: number; + /** + * 元素底边距离 body 底边的距离 + */ + bottom: number; + /** + * 内容宽度 + 计算后的 right + */ + rightIncludeBody: number; + /** + * 内容高度 + 计算后的 bottom + */ + bottomIncludeBody: number; +} + +export function getBoundingClientRect(element: Element): DOMRect | number { + if (!element || !element.getBoundingClientRect) { + return 0; + } + return element.getBoundingClientRect(); +} + +function trim(string: string) { + return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); +} + +/* istanbul ignore next */ +export function hasClass(el: Element, cls: string) { + if (!el || !cls) return false; + if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.'); + if (el.classList) { + return el.classList.contains(cls); + } else { + return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1; + } +} + +/* istanbul ignore next */ +export function addClass(el: Element, cls: string) { + if (!el) return; + let curClass = el.className; + const classes = (cls || '').split(' '); + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i]; + if (!clsName) continue; + + if (el.classList) { + el.classList.add(clsName); + } else if (!hasClass(el, clsName)) { + curClass += ' ' + clsName; + } + } + if (!el.classList) { + el.className = curClass; + } +} + +/* istanbul ignore next */ +export function removeClass(el: Element, cls: string) { + if (!el || !cls) return; + const classes = cls.split(' '); + let curClass = ' ' + el.className + ' '; + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i]; + if (!clsName) continue; + + if (el.classList) { + el.classList.remove(clsName); + } else if (hasClass(el, clsName)) { + curClass = curClass.replace(' ' + clsName + ' ', ' '); + } + } + if (!el.classList) { + el.className = trim(curClass); + } +} +/** + * Get the left and top offset of the current element + * left: the distance between the leftmost element and the left side of the document + * top: the distance from the top of the element to the top of the document + * right: the distance from the far right of the element to the right of the document + * bottom: the distance from the bottom of the element to the bottom of the document + * rightIncludeBody: the distance between the leftmost element and the right side of the document + * bottomIncludeBody: the distance from the bottom of the element to the bottom of the document + * + * @description: + */ +export function getViewportOffset(element: Element): ViewportOffsetResult { + const doc = document.documentElement; + + const docScrollLeft = doc.scrollLeft; + const docScrollTop = doc.scrollTop; + const docClientLeft = doc.clientLeft; + const docClientTop = doc.clientTop; + + const pageXOffset = window.pageXOffset; + const pageYOffset = window.pageYOffset; + + const box = getBoundingClientRect(element); + + const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect; + + const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0); + const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0); + const offsetLeft = retLeft + pageXOffset; + const offsetTop = rectTop + pageYOffset; + + const left = offsetLeft - scrollLeft; + const top = offsetTop - scrollTop; + + const clientWidth = window.document.documentElement.clientWidth; + const clientHeight = window.document.documentElement.clientHeight; + return { + left: left, + top: top, + right: clientWidth - rectWidth - left, + bottom: clientHeight - rectHeight - top, + rightIncludeBody: clientWidth - left, + bottomIncludeBody: clientHeight - top, + }; +} + +export function hackCss(attr: string, value: string) { + const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT']; + + const styleObj: any = {}; + prefix.forEach((item) => { + styleObj[`${item}${upperFirst(attr)}`] = value; + }); + return { + ...styleObj, + [attr]: value, + }; +} + +/* istanbul ignore next */ +export function on( + element: Element | HTMLElement | Document | Window, + event: string, + handler: EventListenerOrEventListenerObject, +): void { + if (element && event && handler) { + element.addEventListener(event, handler, false); + } +} + +/* istanbul ignore next */ +export function off( + element: Element | HTMLElement | Document | Window, + event: string, + handler: Fn, +): void { + if (element && event && handler) { + element.removeEventListener(event, handler, false); + } +} + +/* istanbul ignore next */ +export function once(el: HTMLElement, event: string, fn: EventListener): void { + const listener = function (this: any, ...args: unknown[]) { + if (fn) { + fn.apply(this, args as [evt: Event]); + } + off(el, event, listener); + }; + on(el, event, listener); +} + +export function useRafThrottle(fn: T): T { + let locked = false; + // @ts-ignore + return function (...args: any[]) { + if (locked) return; + locked = true; + window.requestAnimationFrame(() => { + // @ts-ignore + fn.apply(this, args); + locked = false; + }); + }; +} diff --git a/src/utils/env.ts b/src/utils/env.ts new file mode 100644 index 00000000000..43c5aa3ed2e --- /dev/null +++ b/src/utils/env.ts @@ -0,0 +1,82 @@ +import type { GlobEnvConfig } from '#/config'; +import pkg from '../../package.json'; +import { API_ADDRESS } from '@/enums/cacheEnum'; + +export function getCommonStoragePrefix() { + const { VITE_GLOB_APP_TITLE } = getAppEnvConfig(); + return `${VITE_GLOB_APP_TITLE.replace(/\s/g, '_')}__${getEnv()}`.toUpperCase(); +} + +// Generate cache key according to version +export function getStorageShortName() { + return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase(); +} + +const getVariableName = (title: string) => { + function strToHex(str: string) { + const result: string[] = []; + for (let i = 0; i < str.length; ++i) { + const hex = str.charCodeAt(i).toString(16); + result.push(('000' + hex).slice(-4)); + } + return result.join('').toUpperCase(); + } + return `__PRODUCTION__${strToHex(title) || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, ''); +}; + +export function getAppEnvConfig() { + const ENV_NAME = getVariableName(import.meta.env.VITE_GLOB_APP_TITLE); + const ENV = import.meta.env.DEV + ? // Get the global configuration (the configuration will be extracted independently when packaging) + (import.meta.env as unknown as GlobEnvConfig) + : (window[ENV_NAME] as unknown as GlobEnvConfig); + const { VITE_GLOB_APP_TITLE, VITE_GLOB_API_URL_PREFIX, VITE_GLOB_UPLOAD_URL } = ENV; + let { VITE_GLOB_API_URL } = ENV; + if (localStorage.getItem(API_ADDRESS)) { + const address = JSON.parse(localStorage.getItem(API_ADDRESS) || '{}'); + if (address?.key) VITE_GLOB_API_URL = address?.val; + } + return { + VITE_GLOB_APP_TITLE, + VITE_GLOB_API_URL, + VITE_GLOB_API_URL_PREFIX, + VITE_GLOB_UPLOAD_URL, + }; +} + +/** + * @description: Development mode + */ +export const devMode = 'development'; + +/** + * @description: Production mode + */ +export const prodMode = 'production'; + +/** + * @description: Get environment variables + * @returns: + * @example: + */ +export function getEnv(): string { + return import.meta.env.MODE; +} + +/** + * @description: Is it a development mode + * @returns: + * @example: + */ +export function isDevMode(): boolean { + return import.meta.env.DEV; +} + +/** + * @description: Is it a production mode + * @returns: + * @example: + */ +export function isProdMode(): boolean { + return import.meta.env.PROD; +} diff --git a/src/utils/event/index.ts b/src/utils/event/index.ts new file mode 100644 index 00000000000..3a60d7cdc08 --- /dev/null +++ b/src/utils/event/index.ts @@ -0,0 +1,42 @@ +import ResizeObserver from 'resize-observer-polyfill'; + +const isServer = typeof window === 'undefined'; + +/* istanbul ignore next */ +function resizeHandler(entries: any[]) { + for (const entry of entries) { + const listeners = entry.target.__resizeListeners__ || []; + if (listeners.length) { + listeners.forEach((fn: () => any) => { + fn(); + }); + } + } +} + +/* istanbul ignore next */ +export function addResizeListener(element: any, fn: () => any) { + if (isServer) return; + if (!element.__resizeListeners__) { + element.__resizeListeners__ = []; + element.__ro__ = new ResizeObserver(resizeHandler); + element.__ro__.observe(element); + } + element.__resizeListeners__.push(fn); +} + +/* istanbul ignore next */ +export function removeResizeListener(element: any, fn: () => any) { + if (!element || !element.__resizeListeners__) return; + element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1); + if (!element.__resizeListeners__.length) { + element.__ro__.disconnect(); + } +} + +export function triggerWindowResize() { + const event = document.createEvent('HTMLEvents'); + event.initEvent('resize', true, true); + (event as any).eventType = 'message'; + window.dispatchEvent(event); +} diff --git a/src/utils/factory/createAsyncComponent.tsx b/src/utils/factory/createAsyncComponent.tsx new file mode 100644 index 00000000000..78d5c61f7ea --- /dev/null +++ b/src/utils/factory/createAsyncComponent.tsx @@ -0,0 +1,70 @@ +import { + AsyncComponentLoader, + Component, + ComponentPublicInstance, + defineAsyncComponent, + // FunctionalComponent, CSSProperties +} from 'vue'; +import { Spin } from 'ant-design-vue'; +import { noop } from '@/utils'; + +// const Loading: FunctionalComponent<{ size: 'small' | 'default' | 'large' }> = (props) => { +// const style: CSSProperties = { +// position: 'absolute', +// display: 'flex', +// justifyContent: 'center', +// alignItems: 'center', +// }; +// return ( +//
+// +//
+// ); +// }; + +interface Options { + size?: 'default' | 'small' | 'large'; + delay?: number; + timeout?: number; + loading?: boolean; + retry?: boolean; +} + +export function createAsyncComponent< + T extends Component = { + new (): ComponentPublicInstance; + }, +>(loader: AsyncComponentLoader, options: Options = {}) { + const { size = 'small', delay = 100, timeout = 30000, loading = false, retry = true } = options; + return defineAsyncComponent({ + loader, + loadingComponent: loading ? : undefined, + // The error component will be displayed if a timeout is + // provided and exceeded. Default: Infinity. + // TODO + timeout, + // errorComponent + // Defining if component is suspensible. Default: true. + // suspensible: false, + delay, + /** + * + * @param {*} error Error message object + * @param {*} retry A function that indicating whether the async component should retry when the loader promise rejects + * @param {*} fail End of failure + * @param {*} attempts Maximum allowed retries number + */ + onError: !retry + ? noop + : (error, retry, fail, attempts) => { + if (error.message.match(/fetch/) && attempts <= 3) { + // retry on fetch errors, 3 max attempts + retry(); + } else { + // Note that retry/fail are like resolve/reject of a promise: + // one of them must be called for the error handling to continue. + fail(); + } + }, + }); +} diff --git a/src/utils/file/base64Conver.ts b/src/utils/file/base64Conver.ts new file mode 100644 index 00000000000..6751d9777c4 --- /dev/null +++ b/src/utils/file/base64Conver.ts @@ -0,0 +1,41 @@ +/** + * @description: base64 to blob + */ +export function dataURLtoBlob(base64Buf: string): Blob { + const arr = base64Buf.split(','); + const typeItem = arr[0]; + const mime = typeItem.match(/:(.*?);/)![1]; + const bstr = window.atob(arr[1]); + let n = bstr.length; + const u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + return new Blob([u8arr], { type: mime }); +} + +/** + * img url to base64 + * @param url + */ +export function urlToBase64(url: string, mineType?: string): Promise { + return new Promise((resolve, reject) => { + let canvas = document.createElement('CANVAS') as Nullable; + const ctx = canvas!.getContext('2d'); + + const img = new Image(); + img.crossOrigin = ''; + img.onload = function () { + if (!canvas || !ctx) { + return reject(); + } + canvas.height = img.height; + canvas.width = img.width; + ctx.drawImage(img, 0, 0); + const dataURL = canvas.toDataURL(mineType || 'image/png'); + canvas = null; + resolve(dataURL); + }; + img.src = url; + }); +} diff --git a/src/utils/file/download.ts b/src/utils/file/download.ts new file mode 100644 index 00000000000..6af9ab461fa --- /dev/null +++ b/src/utils/file/download.ts @@ -0,0 +1,96 @@ +import { openWindow } from '..'; +import { dataURLtoBlob, urlToBase64 } from './base64Conver'; + +/** + * Download online pictures + * @param url + * @param filename + * @param mime + * @param bom + */ +export function downloadByOnlineUrl(url: string, filename: string, mime?: string, bom?: BlobPart) { + urlToBase64(url).then((base64) => { + downloadByBase64(base64, filename, mime, bom); + }); +} + +/** + * Download pictures based on base64 + * @param buf + * @param filename + * @param mime + * @param bom + */ +export function downloadByBase64(buf: string, filename: string, mime?: string, bom?: BlobPart) { + const base64Buf = dataURLtoBlob(buf); + downloadByData(base64Buf, filename, mime, bom); +} + +/** + * Download according to the background interface file stream + * @param {*} data + * @param {*} filename + * @param {*} mime + * @param {*} bom + */ +export function downloadByData(data: BlobPart, filename: string, mime?: string, bom?: BlobPart) { + const blobData = typeof bom !== 'undefined' ? [bom, data] : [data]; + const blob = new Blob(blobData, { type: mime || 'application/octet-stream' }); + + const blobURL = window.URL.createObjectURL(blob); + const tempLink = document.createElement('a'); + tempLink.style.display = 'none'; + tempLink.href = blobURL; + tempLink.setAttribute('download', filename); + if (typeof tempLink.download === 'undefined') { + tempLink.setAttribute('target', '_blank'); + } + document.body.appendChild(tempLink); + tempLink.click(); + document.body.removeChild(tempLink); + window.URL.revokeObjectURL(blobURL); +} + +/** + * Download file according to file address + * @param {*} sUrl + */ +export function downloadByUrl({ + url, + target = '_blank', + fileName, +}: { + url: string; + target?: TargetContext; + fileName?: string; +}): boolean { + const isChrome = window.navigator.userAgent.toLowerCase().indexOf('chrome') > -1; + const isSafari = window.navigator.userAgent.toLowerCase().indexOf('safari') > -1; + + if (/(iP)/g.test(window.navigator.userAgent)) { + console.error('Your browser does not support download!'); + return false; + } + if (isChrome || isSafari) { + const link = document.createElement('a'); + link.href = url; + link.target = target; + + if (link.download !== undefined) { + link.download = fileName || url.substring(url.lastIndexOf('/') + 1, url.length); + } + + if (document.createEvent) { + const e = document.createEvent('MouseEvents'); + e.initEvent('click', true, true); + link.dispatchEvent(e); + return true; + } + } + if (url.indexOf('?') === -1) { + url += '?download'; + } + + openWindow(url, { target }); + return true; +} diff --git a/src/utils/helper/treeHelper.ts b/src/utils/helper/treeHelper.ts new file mode 100644 index 00000000000..922a47310aa --- /dev/null +++ b/src/utils/helper/treeHelper.ts @@ -0,0 +1,216 @@ +interface TreeHelperConfig { + id: string; + children: string; + pid: string; +} + +// 默认配置 +const DEFAULT_CONFIG: TreeHelperConfig = { + id: 'id', + children: 'children', + pid: 'pid', +}; + +// 获取配置。 Object.assign 从一个或多个源对象复制到目标对象 +const getConfig = (config: Partial) => Object.assign({}, DEFAULT_CONFIG, config); + +// tree from list +// 列表中的树 +export function listToTree(list: any[], config: Partial = {}): T[] { + const conf = getConfig(config) as TreeHelperConfig; + const nodeMap = new Map(); + const result: T[] = []; + const { id, children, pid } = conf; + + for (const node of list) { + node[children] = node[children] || []; + nodeMap.set(node[id], node); + } + for (const node of list) { + const parent = nodeMap.get(node[pid]); + (parent ? parent[children] : result).push(node); + } + return result; +} + +export function treeToList(tree: any, config: Partial = {}): T { + config = getConfig(config); + const { children } = config; + const result: any = [...tree]; + for (let i = 0; i < result.length; i++) { + if (!result[i][children!]) continue; + result.splice(i + 1, 0, ...result[i][children!]); + } + return result; +} + +export function findNode( + tree: any, + func: Fn, + config: Partial = {}, +): T | null { + config = getConfig(config); + const { children } = config; + const list = [...tree]; + for (const node of list) { + if (func(node)) return node; + node[children!] && list.push(...node[children!]); + } + return null; +} + +export function findNodeAll( + tree: any, + func: Fn, + config: Partial = {}, +): T[] { + config = getConfig(config); + const { children } = config; + const list = [...tree]; + const result: T[] = []; + for (const node of list) { + func(node) && result.push(node); + node[children!] && list.push(...node[children!]); + } + return result; +} + +export function findPath( + tree: any, + func: Fn, + config: Partial = {}, +): T | T[] | null { + config = getConfig(config); + const path: T[] = []; + const list = [...tree]; + const visitedSet = new Set(); + const { children } = config; + while (list.length) { + const node = list[0]; + if (visitedSet.has(node)) { + path.pop(); + list.shift(); + } else { + visitedSet.add(node); + node[children!] && list.unshift(...node[children!]); + path.push(node); + if (func(node)) { + return path; + } + } + } + return null; +} + +export function findPathAll(tree: any, func: Fn, config: Partial = {}) { + config = getConfig(config); + const path: any[] = []; + const list = [...tree]; + const result: any[] = []; + const visitedSet = new Set(), + { children } = config; + while (list.length) { + const node = list[0]; + if (visitedSet.has(node)) { + path.pop(); + list.shift(); + } else { + visitedSet.add(node); + node[children!] && list.unshift(...node[children!]); + path.push(node); + func(node) && result.push([...path]); + } + } + return result; +} + +export function filter( + tree: T[], + func: (n: T) => boolean, + // Partial 将 T 中的所有属性设为可选 + config: Partial = {}, +): T[] { + // 获取配置 + config = getConfig(config); + const children = config.children as string; + + function listFilter(list: T[]) { + return list + .map((node: any) => ({ ...node })) + .filter((node) => { + // 递归调用 对含有children项 进行再次调用自身函数 listFilter + node[children] = node[children] && listFilter(node[children]); + // 执行传入的回调 func 进行过滤 + return func(node) || (node[children] && node[children].length); + }); + } + + return listFilter(tree); +} + +export function forEach( + tree: T[], + func: (n: T) => any, + config: Partial = {}, +): void { + config = getConfig(config); + const list: any[] = [...tree]; + const { children } = config; + for (let i = 0; i < list.length; i++) { + //func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿 + if (func(list[i])) { + return; + } + children && list[i][children] && list.splice(i + 1, 0, ...list[i][children]); + } +} + +/** + * @description: Extract tree specified structure + * @description: 提取树指定结构 + */ +export function treeMap(treeData: T[], opt: { children?: string; conversion: Fn }): T[] { + return treeData.map((item) => treeMapEach(item, opt)); +} + +/** + * @description: Extract tree specified structure + * @description: 提取树指定结构 + */ +export function treeMapEach( + data: any, + { children = 'children', conversion }: { children?: string; conversion: Fn }, +) { + const haveChildren = Array.isArray(data[children]) && data[children].length > 0; + const conversionData = conversion(data) || {}; + if (haveChildren) { + return { + ...conversionData, + [children]: data[children].map((i: number) => + treeMapEach(i, { + children, + conversion, + }), + ), + }; + } else { + return { + ...conversionData, + }; + } +} + +/** + * 递归遍历树结构 + * @param treeDatas 树 + * @param callBack 回调 + * @param parentNode 父节点 + */ +export function eachTree(treeDatas: any[], callBack: Fn, parentNode = {}) { + treeDatas.forEach((element) => { + const newNode = callBack(element, parentNode) || element; + if (element.children) { + eachTree(element.children, callBack, newNode); + } + }); +} diff --git a/src/utils/helper/tsxHelper.ts b/src/utils/helper/tsxHelper.ts new file mode 100644 index 00000000000..a8bca7947ab --- /dev/null +++ b/src/utils/helper/tsxHelper.ts @@ -0,0 +1,37 @@ +import { Slots } from 'vue'; +import { isFunction } from '@/utils/is'; +import { RenderOpts } from '@/components/Form'; + +/** + * @description: Get slot to prevent empty error + */ +export function getSlot(slots: Slots, slot = 'default', data?: any, opts?: RenderOpts) { + if (!slots || !Reflect.has(slots, slot)) { + return null; + } + if (!isFunction(slots[slot])) { + console.error(`${slot} is not a function!`); + return null; + } + const slotFn = slots[slot]; + if (!slotFn) return null; + const params = { ...data, ...opts }; + return slotFn(params); +} + +/** + * extends slots + * @param slots + * @param excludeKeys + */ +export function extendSlots(slots: Slots, excludeKeys: string[] = []) { + const slotKeys = Object.keys(slots); + const ret: any = {}; + slotKeys.forEach((key) => { + if (excludeKeys.includes(key)) { + return null; + } + ret[key] = (data?: any) => getSlot(slots, key, data); + }); + return ret; +} diff --git a/src/utils/http/axios/Axios.ts b/src/utils/http/axios/Axios.ts new file mode 100644 index 00000000000..fe226a0d516 --- /dev/null +++ b/src/utils/http/axios/Axios.ts @@ -0,0 +1,252 @@ +import type { + AxiosRequestConfig, + AxiosInstance, + AxiosResponse, + AxiosError, + InternalAxiosRequestConfig, +} from 'axios'; +import type { RequestOptions, Result, UploadFileParams } from '#/axios'; +import type { CreateAxiosOptions } from './axiosTransform'; +import axios from 'axios'; +import qs from 'qs'; +import { AxiosCanceler } from './axiosCancel'; +import { isFunction } from '@/utils/is'; +import { cloneDeep } from 'lodash-es'; +import { ContentTypeEnum, RequestEnum } from '@/enums/httpEnum'; + +export * from './axiosTransform'; + +/** + * @description: axios module + */ +export class VAxios { + private axiosInstance: AxiosInstance; + private readonly options: CreateAxiosOptions; + + constructor(options: CreateAxiosOptions) { + this.options = options; + this.axiosInstance = axios.create(options); + this.setupInterceptors(); + } + + /** + * @description: Create axios instance + */ + private createAxios(config: CreateAxiosOptions): void { + this.axiosInstance = axios.create(config); + } + + private getTransform() { + const { transform } = this.options; + return transform; + } + + getAxios(): AxiosInstance { + return this.axiosInstance; + } + + /** + * @description: Reconfigure axios + */ + configAxios(config: CreateAxiosOptions) { + if (!this.axiosInstance) { + return; + } + this.createAxios(config); + } + + /** + * @description: Set general header + */ + setHeader(headers: any): void { + if (!this.axiosInstance) { + return; + } + Object.assign(this.axiosInstance.defaults.headers, headers); + } + + /** + * @description: Interceptor configuration 拦截器配置 + */ + private setupInterceptors() { + // const transform = this.getTransform(); + const { + axiosInstance, + options: { transform }, + } = this; + if (!transform) { + return; + } + const { + requestInterceptors, + requestInterceptorsCatch, + responseInterceptors, + responseInterceptorsCatch, + } = transform; + + const axiosCanceler = new AxiosCanceler(); + + // Request interceptor configuration processing + this.axiosInstance.interceptors.request.use((config: InternalAxiosRequestConfig) => { + // If cancel repeat request is turned on, then cancel repeat request is prohibited + const requestOptions = + (config as unknown as any).requestOptions ?? this.options.requestOptions; + const ignoreCancelToken = requestOptions?.ignoreCancelToken ?? true; + + !ignoreCancelToken && axiosCanceler.addPending(config); + + if (requestInterceptors && isFunction(requestInterceptors)) { + config = requestInterceptors(config, this.options); + } + return config; + }, undefined); + + // Request interceptor error capture + requestInterceptorsCatch && + isFunction(requestInterceptorsCatch) && + this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch); + + // Response result interceptor processing + this.axiosInstance.interceptors.response.use((res: AxiosResponse) => { + res && axiosCanceler.removePending(res.config); + if (responseInterceptors && isFunction(responseInterceptors)) { + res = responseInterceptors(res); + } + return res; + }, undefined); + + // Response result interceptor error capture + responseInterceptorsCatch && + isFunction(responseInterceptorsCatch) && + this.axiosInstance.interceptors.response.use(undefined, (error) => { + return responseInterceptorsCatch(axiosInstance, error); + }); + } + + /** + * @description: File Upload + */ + uploadFile(config: AxiosRequestConfig, params: UploadFileParams) { + const formData = new window.FormData(); + const customFilename = params.name || 'file'; + + if (params.filename) { + formData.append(customFilename, params.file, params.filename); + } else { + formData.append(customFilename, params.file); + } + + if (params.data) { + Object.keys(params.data).forEach((key) => { + const value = params.data![key]; + if (Array.isArray(value)) { + value.forEach((item) => { + formData.append(`${key}[]`, item); + }); + return; + } + + formData.append(key, params.data![key]); + }); + } + + return this.axiosInstance.request({ + ...config, + method: 'POST', + data: formData, + headers: { + 'Content-type': ContentTypeEnum.FORM_DATA, + // @ts-ignore + ignoreCancelToken: true, + }, + }); + } + + // support form-data + supportFormData(config: AxiosRequestConfig) { + const headers = config.headers || this.options.headers; + const contentType = headers?.['Content-Type'] || headers?.['content-type']; + + if ( + contentType !== ContentTypeEnum.FORM_URLENCODED || + !Reflect.has(config, 'data') || + config.method?.toUpperCase() === RequestEnum.GET + ) { + return config; + } + + return { + ...config, + data: qs.stringify(config.data, { arrayFormat: 'brackets' }), + }; + } + + get(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'GET' }, options); + } + + post(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'POST' }, options); + } + + put(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'PUT' }, options); + } + + delete(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'DELETE' }, options); + } + + request(config: AxiosRequestConfig, options?: RequestOptions): Promise { + let conf: CreateAxiosOptions = cloneDeep(config); + // cancelToken 如果被深拷贝,会导致最外层无法使用cancel方法来取消请求 + if (config.cancelToken) { + conf.cancelToken = config.cancelToken; + } + + if (config.signal) { + conf.signal = config.signal; + } + + const transform = this.getTransform(); + + const { requestOptions } = this.options; + + const opt: RequestOptions = Object.assign({}, requestOptions, options); + + const { beforeRequestHook, requestCatchHook, transformResponseHook } = transform || {}; + if (beforeRequestHook && isFunction(beforeRequestHook)) { + conf = beforeRequestHook(conf, opt); + } + conf.requestOptions = opt; + + conf = this.supportFormData(conf); + + return new Promise((resolve, reject) => { + this.axiosInstance + .request>(conf) + .then((res: AxiosResponse) => { + if (transformResponseHook && isFunction(transformResponseHook)) { + try { + const ret = transformResponseHook(res, opt); + resolve(ret); + } catch (err) { + reject(err || new Error('request error!')); + } + return; + } + resolve(res as unknown as Promise); + }) + .catch((e: Error | AxiosError) => { + if (requestCatchHook && isFunction(requestCatchHook)) { + reject(requestCatchHook(e, opt)); + return; + } + if (axios.isAxiosError(e)) { + // rewrite error message from axios in here + } + reject(e); + }); + }); + } +} diff --git a/src/utils/http/axios/axiosCancel.ts b/src/utils/http/axios/axiosCancel.ts new file mode 100644 index 00000000000..f115ccf5aad --- /dev/null +++ b/src/utils/http/axios/axiosCancel.ts @@ -0,0 +1,60 @@ +import type { AxiosRequestConfig } from 'axios'; + +// 用于存储每个请求的标识和取消函数 +const pendingMap = new Map(); + +const getPendingUrl = (config: AxiosRequestConfig): string => { + return [config.method, config.url].join('&'); +}; + +export class AxiosCanceler { + /** + * 添加请求 + * @param config 请求配置 + */ + public addPending(config: AxiosRequestConfig): void { + this.removePending(config); + const url = getPendingUrl(config); + const controller = new AbortController(); + config.signal = config.signal || controller.signal; + if (!pendingMap.has(url)) { + // 如果当前请求不在等待中,将其添加到等待中 + pendingMap.set(url, controller); + } + } + + /** + * 清除所有等待中的请求 + */ + public removeAllPending(): void { + pendingMap.forEach((abortController) => { + if (abortController) { + abortController.abort(); + } + }); + this.reset(); + } + + /** + * 移除请求 + * @param config 请求配置 + */ + public removePending(config: AxiosRequestConfig): void { + const url = getPendingUrl(config); + if (pendingMap.has(url)) { + // 如果当前请求在等待中,取消它并将其从等待中移除 + const abortController = pendingMap.get(url); + if (abortController) { + abortController.abort(url); + } + pendingMap.delete(url); + } + } + + /** + * 重置 + */ + public reset(): void { + pendingMap.clear(); + } +} diff --git a/src/utils/http/axios/axiosRetry.ts b/src/utils/http/axios/axiosRetry.ts new file mode 100644 index 00000000000..bf44cf742a8 --- /dev/null +++ b/src/utils/http/axios/axiosRetry.ts @@ -0,0 +1,30 @@ +import { AxiosError, AxiosInstance } from 'axios'; +/** + * 请求重试机制 + */ + +export class AxiosRetry { + /** + * 重试 + */ + retry(axiosInstance: AxiosInstance, error: AxiosError) { + // @ts-ignore + const { config } = error.response; + const { waitTime, count } = config?.requestOptions?.retryRequest ?? {}; + config.__retryCount = config.__retryCount || 0; + if (config.__retryCount >= count) { + return Promise.reject(error); + } + config.__retryCount += 1; + //请求返回后config的header不正确造成重试请求失败,删除返回headers采用默认headers + delete config.headers; + return this.delay(waitTime).then(() => axiosInstance(config)); + } + + /** + * 延迟 + */ + private delay(waitTime: number) { + return new Promise((resolve) => setTimeout(resolve, waitTime)); + } +} diff --git a/src/utils/http/axios/axiosTransform.ts b/src/utils/http/axios/axiosTransform.ts new file mode 100644 index 00000000000..5570bf0835b --- /dev/null +++ b/src/utils/http/axios/axiosTransform.ts @@ -0,0 +1,57 @@ +/** + * Data processing class, can be configured according to the project + */ +import type { + AxiosInstance, + AxiosRequestConfig, + AxiosResponse, + InternalAxiosRequestConfig, +} from 'axios'; +import type { RequestOptions, Result } from '#/axios'; + +export interface CreateAxiosOptions extends AxiosRequestConfig { + authenticationScheme?: string; + transform?: AxiosTransform; + requestOptions?: RequestOptions; +} + +export abstract class AxiosTransform { + /** + * A function that is called before a request is sent. It can modify the request configuration as needed. + * 在发送请求之前调用的函数。它可以根据需要修改请求配置。 + */ + beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig; + + /** + * @description: 处理响应数据 + */ + transformResponseHook?: (res: AxiosResponse, options: RequestOptions) => any; + + /** + * @description: 请求失败处理 + */ + requestCatchHook?: (e: Error, options: RequestOptions) => Promise; + + /** + * @description: 请求之前的拦截器 + */ + requestInterceptors?: ( + config: InternalAxiosRequestConfig, + options: CreateAxiosOptions, + ) => InternalAxiosRequestConfig; + + /** + * @description: 请求之后的拦截器 + */ + responseInterceptors?: (res: AxiosResponse) => AxiosResponse; + + /** + * @description: 请求之前的拦截器错误处理 + */ + requestInterceptorsCatch?: (error: Error) => void; + + /** + * @description: 请求之后的拦截器错误处理 + */ + responseInterceptorsCatch?: (axiosInstance: AxiosInstance, error: Error) => void; +} diff --git a/src/utils/http/axios/checkStatus.ts b/src/utils/http/axios/checkStatus.ts new file mode 100644 index 00000000000..10d7267ad7a --- /dev/null +++ b/src/utils/http/axios/checkStatus.ts @@ -0,0 +1,81 @@ +import type { ErrorMessageMode } from '#/axios'; +import { useMessage } from '@/hooks/web/useMessage'; +import { useI18n } from '@/hooks/web/useI18n'; +// import router from '@/router'; +// import { PageEnum } from '@/enums/pageEnum'; +import { useUserStoreWithOut } from '@/store/modules/user'; +import projectSetting from '@/settings/projectSetting'; +import { SessionTimeoutProcessingEnum } from '@/enums/appEnum'; + +const { createMessage, createErrorModal } = useMessage(); +const error = createMessage.error!; +const stp = projectSetting.sessionTimeoutProcessing; + +export function checkStatus( + status: number, + msg: string, + errorMessageMode: ErrorMessageMode = 'message', +): void { + const { t } = useI18n(); + const userStore = useUserStoreWithOut(); + let errMessage = ''; + + switch (status) { + case 400: + errMessage = `${msg}`; + break; + // 401: Not logged in + // Jump to the login page if not logged in, and carry the path of the current page + // Return to the current page after successful login. This step needs to be operated on the login page. + case 401: + userStore.setToken(undefined); + errMessage = msg || t('sys.api.errMsg401'); + if (stp === SessionTimeoutProcessingEnum.PAGE_COVERAGE) { + userStore.setSessionTimeout(true); + } else { + // 被动登出,带redirect地址 + userStore.logout(false); + } + break; + case 403: + errMessage = t('sys.api.errMsg403'); + break; + // 404请求不存在 + case 404: + errMessage = t('sys.api.errMsg404'); + break; + case 405: + errMessage = t('sys.api.errMsg405'); + break; + case 408: + errMessage = t('sys.api.errMsg408'); + break; + case 500: + errMessage = t('sys.api.errMsg500'); + break; + case 501: + errMessage = t('sys.api.errMsg501'); + break; + case 502: + errMessage = t('sys.api.errMsg502'); + break; + case 503: + errMessage = t('sys.api.errMsg503'); + break; + case 504: + errMessage = t('sys.api.errMsg504'); + break; + case 505: + errMessage = t('sys.api.errMsg505'); + break; + default: + } + + if (errMessage) { + if (errorMessageMode === 'modal') { + createErrorModal({ title: t('sys.api.errorTip'), content: errMessage }); + } else if (errorMessageMode === 'message') { + error({ content: errMessage, key: `global_error_message_status_${status}` }); + } + } +} diff --git a/src/utils/http/axios/helper.ts b/src/utils/http/axios/helper.ts new file mode 100644 index 00000000000..36d419a92f7 --- /dev/null +++ b/src/utils/http/axios/helper.ts @@ -0,0 +1,48 @@ +import { isObject, isString } from '@/utils/is'; + +const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; + +export function joinTimestamp( + join: boolean, + restful: T, +): T extends true ? string : object; + +export function joinTimestamp(join: boolean, restful = false): string | object { + if (!join) { + return restful ? '' : {}; + } + const now = new Date().getTime(); + if (restful) { + return `?_t=${now}`; + } + return { _t: now }; +} + +/** + * @description: Format request parameter time + */ +export function formatRequestDate(params: Recordable) { + if (Object.prototype.toString.call(params) !== '[object Object]') { + return; + } + + for (const key in params) { + const format = params[key]?.format ?? null; + if (format && typeof format === 'function') { + params[key] = params[key].format(DATE_TIME_FORMAT); + } + if (isString(key)) { + const value = params[key]; + if (value) { + try { + params[key] = isString(value) ? value.trim() : value; + } catch (error: any) { + throw new Error(error); + } + } + } + if (isObject(params[key])) { + formatRequestDate(params[key]); + } + } +} diff --git a/src/utils/http/axios/index.ts b/src/utils/http/axios/index.ts new file mode 100644 index 00000000000..9c3a86b2aef --- /dev/null +++ b/src/utils/http/axios/index.ts @@ -0,0 +1,287 @@ +// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动 +// The axios configuration can be changed according to the project, just change the file, other files can be left unchanged + +import type { AxiosInstance, AxiosResponse } from 'axios'; +import { clone } from 'lodash-es'; +import type { RequestOptions, Result } from '#/axios'; +import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform'; +import { VAxios } from './Axios'; +import { checkStatus } from './checkStatus'; +import { useGlobSetting } from '@/hooks/setting'; +import { useMessage } from '@/hooks/web/useMessage'; +import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum'; +import { isString, isUndefined, isNull, isEmpty } from '@/utils/is'; +import { getToken } from '@/utils/auth'; +import { setObjToUrlParams, deepMerge } from '@/utils'; +import { useErrorLogStoreWithOut } from '@/store/modules/errorLog'; +import { useI18n } from '@/hooks/web/useI18n'; +import { joinTimestamp, formatRequestDate } from './helper'; +import { useUserStoreWithOut } from '@/store/modules/user'; +import { AxiosRetry } from '@/utils/http/axios/axiosRetry'; +import axios from 'axios'; + +const globSetting = useGlobSetting(); +const urlPrefix = globSetting.urlPrefix; +const { createMessage, createErrorModal, createSuccessModal } = useMessage(); + +/** + * @description: 数据处理,方便区分多种处理方式 + */ +const transform: AxiosTransform = { + /** + * @description: 处理响应数据。如果数据不是预期格式,可直接抛出错误 + */ + transformResponseHook: (res: AxiosResponse, options: RequestOptions) => { + const { t } = useI18n(); + const { isTransformResponse, isReturnNativeResponse } = options; + // 是否返回原生响应头 比如:需要获取响应头时使用该属性 + if (isReturnNativeResponse) { + return res; + } + // 不进行任何处理,直接返回 + // 用于页面代码可能需要直接获取code,data,message这些信息时开启 + if (!isTransformResponse) { + return res.data; + } + // 错误的时候返回 + + const { data } = res; + if (!data) { + // return '[HTTP] Request has no return value'; + throw new Error(t('sys.api.apiRequestFailed')); + } + // 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式 + const { code, result, message } = data; + + // 这里逻辑可以根据项目进行修改 + const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS; + if (hasSuccess) { + let successMsg = message; + + if (isNull(successMsg) || isUndefined(successMsg) || isEmpty(successMsg)) { + successMsg = t(`sys.api.operationSuccess`); + } + + if (options.successMessageMode === 'modal') { + createSuccessModal({ title: t('sys.api.successTip'), content: successMsg }); + } else if (options.successMessageMode === 'message') { + createMessage.success(successMsg); + } + return result; + } + + // 在此处根据自己项目的实际情况对不同的code执行不同的操作 + // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可 + let timeoutMsg = ''; + switch (code) { + case ResultEnum.TIMEOUT: + timeoutMsg = t('sys.api.timeoutMessage'); + const userStore = useUserStoreWithOut(); + // 被动登出,带redirect地址 + userStore.logout(false); + break; + default: + if (message) { + timeoutMsg = message; + } + } + + // errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误 + // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示 + if (options.errorMessageMode === 'modal') { + createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg }); + } else if (options.errorMessageMode === 'message') { + createMessage.error(timeoutMsg); + } + + throw new Error(timeoutMsg || t('sys.api.apiRequestFailed')); + }, + + // 请求之前处理config + beforeRequestHook: (config, options) => { + const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options; + + if (joinPrefix) { + config.url = `${urlPrefix}${config.url}`; + } + + if (apiUrl && isString(apiUrl)) { + config.url = `${apiUrl}${config.url}`; + } + const params = config.params || {}; + const data = config.data || false; + formatDate && data && !isString(data) && formatRequestDate(data); + if (config.method?.toUpperCase() === RequestEnum.GET) { + if (!isString(params)) { + // 给 get 请求加上时间戳参数,避免从缓存中拿数据。 + config.params = Object.assign(params || {}, joinTimestamp(joinTime, false)); + } else { + // 兼容restful风格 + config.url = config.url + params + `${joinTimestamp(joinTime, true)}`; + config.params = undefined; + } + } else { + if (!isString(params)) { + formatDate && formatRequestDate(params); + if ( + Reflect.has(config, 'data') && + config.data && + (Object.keys(config.data).length > 0 || config.data instanceof FormData) + ) { + config.data = data; + config.params = params; + } else { + // 非GET请求如果没有提供data,则将params视为data + config.data = params; + config.params = undefined; + } + if (joinParamsToUrl) { + config.url = setObjToUrlParams( + config.url as string, + Object.assign({}, config.params, config.data), + ); + } + } else { + // 兼容restful风格 + config.url = config.url + params; + config.params = undefined; + } + } + return config; + }, + + /** + * @description: 请求拦截器处理 + */ + requestInterceptors: (config, options) => { + // 请求之前处理config + const token = getToken(); + if (token && (config as Recordable)?.requestOptions?.withToken !== false) { + // jwt token + (config as Recordable).headers.Authorization = options.authenticationScheme + ? `${options.authenticationScheme} ${token}` + : token; + } + return config; + }, + + /** + * @description: 响应拦截器处理 + */ + responseInterceptors: (res: AxiosResponse) => { + return res; + }, + + /** + * @description: 响应错误处理 + */ + responseInterceptorsCatch: (axiosInstance: AxiosInstance, error: any) => { + const { t } = useI18n(); + const errorLogStore = useErrorLogStoreWithOut(); + errorLogStore.addAjaxErrorInfo(error); + const { response, code, message, config } = error || {}; + const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none'; + const msg: string = response?.data?.error?.message ?? ''; + const err: string = error?.toString?.() ?? ''; + let errMessage = ''; + + if (axios.isCancel(error)) { + return Promise.reject(error); + } + + try { + if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) { + errMessage = t('sys.api.apiTimeoutMessage'); + } + if (err?.includes('Network Error')) { + errMessage = t('sys.api.networkExceptionMsg'); + } + + if (errMessage) { + if (errorMessageMode === 'modal') { + createErrorModal({ title: t('sys.api.errorTip'), content: errMessage }); + } else if (errorMessageMode === 'message') { + createMessage.error(errMessage); + } + return Promise.reject(error); + } + } catch (error) { + throw new Error(error as unknown as string); + } + + checkStatus(error?.response?.status, msg, errorMessageMode); + + // 添加自动重试机制 保险起见 只针对GET请求 + const retryRequest = new AxiosRetry(); + const { isOpenRetry } = config.requestOptions.retryRequest; + config.method?.toUpperCase() === RequestEnum.GET && + isOpenRetry && + error?.response?.status !== 401 && + // @ts-ignore + retryRequest.retry(axiosInstance, error); + return Promise.reject(error); + }, +}; + +function createAxios(opt?: Partial) { + return new VAxios( + // 深度合并 + deepMerge( + { + // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes + // authentication schemes,e.g: Bearer + // authenticationScheme: 'Bearer', + authenticationScheme: '', + timeout: 10 * 1000, + // 基础接口地址 + // baseURL: globSetting.apiUrl, + + headers: { 'Content-Type': ContentTypeEnum.JSON }, + // 如果是form-data格式 + // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, + // 数据处理方式 + transform: clone(transform), + // 配置项,下面的选项都可以在独立的接口请求中覆盖 + requestOptions: { + // 默认将prefix 添加到url + joinPrefix: true, + // 是否返回原生响应头 比如:需要获取响应头时使用该属性 + isReturnNativeResponse: false, + // 需要对返回数据进行处理 + isTransformResponse: true, + // post请求的时候添加参数到url + joinParamsToUrl: false, + // 格式化提交参数时间 + formatDate: true, + // 消息提示类型 + errorMessageMode: 'message', + // 接口地址 + apiUrl: globSetting.apiUrl, + // 接口拼接地址 + urlPrefix: urlPrefix, + // 是否加入时间戳 + joinTime: true, + // 忽略重复请求 + ignoreCancelToken: true, + // 是否携带token + withToken: true, + retryRequest: { + isOpenRetry: true, + count: 5, + waitTime: 100, + }, + }, + }, + opt || {}, + ), + ); +} +export const defHttp = createAxios(); + +// other api url +// export const otherHttp = createAxios({ +// requestOptions: { +// apiUrl: 'xxx', +// urlPrefix: 'xxx', +// }, +// }); diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000000..2b6c68b42ab --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,147 @@ +import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'; +import type { App, Component } from 'vue'; + +import { intersectionWith, isEqual, mergeWith, unionWith } from 'lodash-es'; +import { unref } from 'vue'; +import { isArray, isObject } from '@/utils/is'; + +export const noop = () => {}; + +/** + * @description: Set ui mount node + */ +export function getPopupContainer(node?: HTMLElement): HTMLElement { + return (node?.parentNode as HTMLElement) ?? document.body; +} + +/** + * Add the object as a parameter to the URL + * @param baseUrl url + * @param obj + * @returns {string} + * eg: + * let obj = {a: '3', b: '4'} + * setObjToUrlParams('www.baidu.com', obj) + * ==>www.baidu.com?a=3&b=4 + */ +export function setObjToUrlParams(baseUrl: string, obj: any): string { + let parameters = ''; + for (const key in obj) { + parameters += key + '=' + encodeURIComponent(obj[key]) + '&'; + } + parameters = parameters.replace(/&$/, ''); + return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters; +} + +/** + * Recursively merge two objects. + * 递归合并两个对象。 + * + * @param source The source object to merge from. 要合并的源对象。 + * @param target The target object to merge into. 目标对象,合并后结果存放于此。 + * @param mergeArrays How to merge arrays. Default is "replace". + * 如何合并数组。默认为replace。 + * - "union": Union the arrays. 对数组执行并集操作。 + * - "intersection": Intersect the arrays. 对数组执行交集操作。 + * - "concat": Concatenate the arrays. 连接数组。 + * - "replace": Replace the source array with the target array. 用目标数组替换源数组。 + * @returns The merged object. 合并后的对象。 + */ +export function deepMerge( + source: T, + target: U, + mergeArrays: 'union' | 'intersection' | 'concat' | 'replace' = 'replace', +): T & U { + if (!target) { + return source as T & U; + } + if (!source) { + return target as T & U; + } + return mergeWith({}, source, target, (sourceValue, targetValue) => { + if (isArray(targetValue) && isArray(sourceValue)) { + switch (mergeArrays) { + case 'union': + return unionWith(sourceValue, targetValue, isEqual); + case 'intersection': + return intersectionWith(sourceValue, targetValue, isEqual); + case 'concat': + return sourceValue.concat(targetValue); + case 'replace': + return targetValue; + default: + throw new Error(`Unknown merge array strategy: ${mergeArrays as string}`); + } + } + if (isObject(targetValue) && isObject(sourceValue)) { + return deepMerge(sourceValue, targetValue, mergeArrays); + } + return undefined; + }); +} + +export function openWindow( + url: string, + opt?: { target?: TargetContext | string; noopener?: boolean; noreferrer?: boolean }, +) { + const { target = '__blank', noopener = true, noreferrer = true } = opt || {}; + const feature: string[] = []; + + noopener && feature.push('noopener=yes'); + noreferrer && feature.push('noreferrer=yes'); + + window.open(url, target, feature.join(',')); +} + +// dynamic use hook props +export function getDynamicProps, U>(props: T): Partial { + const ret: Recordable = {}; + + Object.keys(props).forEach((key) => { + ret[key] = unref((props as Recordable)[key]); + }); + + return ret as Partial; +} + +export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormalized { + if (!route) return route; + const { matched, ...opt } = route; + return { + ...opt, + matched: (matched + ? matched.map((item) => ({ + meta: item.meta, + name: item.name, + path: item.path, + })) + : undefined) as RouteRecordNormalized[], + }; +} + +// https://github.com/vant-ui/vant/issues/8302 +type EventShim = { + new (...args: any[]): { + $props: { + onClick?: (...args: any[]) => void; + }; + }; +}; + +export type WithInstall = T & { + install(app: App): void; +} & EventShim; + +export type CustomComponent = Component & { displayName?: string }; + +export const withInstall = (component: T, alias?: string) => { + (component as Record).install = (app: App) => { + const compName = component.name || component.displayName; + if (!compName) return; + app.component(compName, component); + if (alias) { + app.config.globalProperties[alias] = component; + } + }; + return component as WithInstall; +}; diff --git a/src/utils/is.ts b/src/utils/is.ts new file mode 100644 index 00000000000..97c2ea95c91 --- /dev/null +++ b/src/utils/is.ts @@ -0,0 +1,72 @@ +export { + isArguments, + isArrayBuffer, + isArrayLike, + isArrayLikeObject, + isBuffer, + isBoolean, + isDate, + isElement, + isEmpty, + isEqual, + isEqualWith, + isError, + isFunction, + isFinite, + isLength, + isMap, + isMatch, + isMatchWith, + isNative, + isNil, + isNumber, + isNull, + isObjectLike, + isPlainObject, + isRegExp, + isSafeInteger, + isSet, + isString, + isSymbol, + isTypedArray, + isUndefined, + isWeakMap, + isWeakSet, +} from 'lodash-es'; +const toString = Object.prototype.toString; + +export function is(val: unknown, type: string) { + return toString.call(val) === `[object ${type}]`; +} + +export function isDef(val?: T): val is T { + return typeof val !== 'undefined'; +} + +// TODO 此处 isObject 存在歧义 +export function isObject(val: any): val is Record { + return val !== null && is(val, 'Object'); +} + +// TODO 此处 isArray 存在歧义 +export function isArray(val: any): val is Array { + return val && Array.isArray(val); +} + +export function isWindow(val: any): val is Window { + return typeof window !== 'undefined' && is(val, 'Window'); +} + +export const isServer = typeof window === 'undefined'; + +export const isClient = !isServer; + +export function isHttpUrl(path: string): boolean { + const reg = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- ./?%&=]*)?/; + return reg.test(path); +} + +export function isPascalCase(str: string): boolean { + const regex = /^[A-Z][A-Za-z]*$/; + return regex.test(str); +} diff --git a/src/utils/lib/echarts.ts b/src/utils/lib/echarts.ts new file mode 100644 index 00000000000..e1f95cd383a --- /dev/null +++ b/src/utils/lib/echarts.ts @@ -0,0 +1,57 @@ +import * as echarts from 'echarts/core'; + +import { + BarChart, + LineChart, + PieChart, + MapChart, + PictorialBarChart, + RadarChart, + ScatterChart, +} from 'echarts/charts'; + +import { + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + LegendComponent, + RadarComponent, + ToolboxComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, + CalendarComponent, + GraphicComponent, +} from 'echarts/components'; + +import { SVGRenderer } from 'echarts/renderers'; + +echarts.use([ + LegendComponent, + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + BarChart, + LineChart, + PieChart, + MapChart, + RadarChart, + SVGRenderer, + PictorialBarChart, + RadarComponent, + ToolboxComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, + CalendarComponent, + GraphicComponent, + ScatterChart, +]); + +export default echarts; diff --git a/src/utils/log.ts b/src/utils/log.ts new file mode 100644 index 00000000000..8f7980012a5 --- /dev/null +++ b/src/utils/log.ts @@ -0,0 +1,9 @@ +const projectName = import.meta.env.VITE_GLOB_APP_TITLE; + +export function warn(message: string) { + console.warn(`[${projectName} warn]:${message}`); +} + +export function error(message: string) { + throw new Error(`[${projectName} error]:${message}`); +} diff --git a/src/utils/mitt.ts b/src/utils/mitt.ts new file mode 100644 index 00000000000..cf09fd81055 --- /dev/null +++ b/src/utils/mitt.ts @@ -0,0 +1,122 @@ +/** + * copy to https://github.com/developit/mitt + * Expand clear method + */ +export type EventType = string | symbol; + +// An event handler can take an optional event argument +// and should not return a value +export type Handler = (event: T) => void; +export type WildcardHandler> = ( + type: keyof T, + event: T[keyof T], +) => void; + +// An array of all currently registered event handlers for a type +export type EventHandlerList = Array>; +export type WildCardEventHandlerList> = Array>; + +// A map of event types and their corresponding event handlers. +export type EventHandlerMap> = Map< + keyof Events | '*', + EventHandlerList | WildCardEventHandlerList +>; + +export interface Emitter> { + all: EventHandlerMap; + + on(type: Key, handler: Handler): void; + on(type: '*', handler: WildcardHandler): void; + + off(type: Key, handler?: Handler): void; + off(type: '*', handler: WildcardHandler): void; + + emit(type: Key, event: Events[Key]): void; + emit(type: undefined extends Events[Key] ? Key : never): void; + clear(): void; +} + +/** + * Mitt: Tiny (~200b) functional event emitter / pubsub. + * @name mitt + * @returns {Mitt} + */ +export function mitt>( + all?: EventHandlerMap, +): Emitter { + type GenericEventHandler = Handler | WildcardHandler; + all = all || new Map(); + + return { + /** + * A Map of event names to registered handler functions. + */ + all, + + /** + * Register an event handler for the given type. + * @param {string|symbol} type Type of event to listen for, or `'*'` for all events + * @param {Function} handler Function to call in response to given event + * @memberOf mitt + */ + on(type: Key, handler: GenericEventHandler) { + const handlers: Array | undefined = all!.get(type); + if (handlers) { + handlers.push(handler); + } else { + all!.set(type, [handler] as EventHandlerList); + } + }, + + /** + * Remove an event handler for the given type. + * If `handler` is omitted, all handlers of the given type are removed. + * @param {string|symbol} type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler) + * @param {Function} [handler] Handler function to remove + * @memberOf mitt + */ + off(type: Key, handler?: GenericEventHandler) { + const handlers: Array | undefined = all!.get(type); + if (handlers) { + if (handler) { + handlers.splice(handlers.indexOf(handler) >>> 0, 1); + } else { + all!.set(type, []); + } + } + }, + + /** + * Invoke all handlers for the given type. + * If present, `'*'` handlers are invoked after type-matched handlers. + * + * Note: Manually firing '*' handlers is not supported. + * + * @param {string|symbol} type The event type to invoke + * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler + * @memberOf mitt + */ + emit(type: Key, evt?: Events[Key]) { + let handlers = all!.get(type); + if (handlers) { + (handlers as EventHandlerList).slice().forEach((handler) => { + handler(evt as Events[Key]); + }); + } + + handlers = all!.get('*'); + if (handlers) { + (handlers as WildCardEventHandlerList).slice().forEach((handler) => { + handler(type, evt as Events[Key]); + }); + } + }, + + /** + * Clear all + */ + clear() { + this.all.clear(); + }, + }; +} diff --git a/src/utils/propTypes.ts b/src/utils/propTypes.ts new file mode 100644 index 00000000000..b4f0623e461 --- /dev/null +++ b/src/utils/propTypes.ts @@ -0,0 +1,35 @@ +import { CSSProperties, VNodeChild } from 'vue'; +import { createTypes, VueTypeValidableDef, VueTypesInterface, toValidableType } from 'vue-types'; + +export type VueNode = VNodeChild | JSX.Element; + +type PropTypes = VueTypesInterface & { + readonly style: VueTypeValidableDef; + readonly VNodeChild: VueTypeValidableDef; + // readonly trueBool: VueTypeValidableDef; +}; +const newPropTypes = createTypes({ + func: undefined, + bool: undefined, + string: undefined, + number: undefined, + object: undefined, + integer: undefined, +}) as PropTypes; + +// 从 vue-types v5.0 开始,extend()方法已经废弃,当前已改为官方推荐的ES6+方法 https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method +class propTypes extends newPropTypes { + // a native-like validator that supports the `.validable` method + static override get style() { + return toValidableType('style', { + type: [String, Object], + }); + } + + static override get VNodeChild() { + return toValidableType('VNodeChild', { + type: undefined, + }); + } +} +export { propTypes }; diff --git a/src/utils/props.ts b/src/utils/props.ts new file mode 100644 index 00000000000..79060bfd86d --- /dev/null +++ b/src/utils/props.ts @@ -0,0 +1,184 @@ +// copy from element-plus + +import { warn } from 'vue'; +import { fromPairs, isObject } from 'lodash-es'; +import type { ExtractPropTypes, PropType } from 'vue'; +import type { Mutable } from './types'; + +const wrapperKey = Symbol(); +export type PropWrapper = { [wrapperKey]: T }; + +export const propKey = Symbol(); + +type ResolveProp = ExtractPropTypes<{ + key: { type: T; required: true }; +}>['key']; +type ResolvePropType = ResolveProp extends { type: infer V } ? V : ResolveProp; +type ResolvePropTypeWithReadonly = + Readonly extends Readonly> ? ResolvePropType : ResolvePropType; + +type IfUnknown = [unknown] extends [T] ? V : T; + +export type BuildPropOption, R, V, C> = { + type?: T; + values?: readonly V[]; + required?: R; + default?: R extends true + ? never + : D extends Record | Array + ? () => D + : (() => D) | D; + validator?: ((val: any) => val is C) | ((val: any) => boolean); +}; + +type _BuildPropType = + | (T extends PropWrapper + ? T[typeof wrapperKey] + : [V] extends [never] + ? ResolvePropTypeWithReadonly + : never) + | V + | C; +export type BuildPropType = _BuildPropType< + IfUnknown, + IfUnknown, + IfUnknown +>; + +type _BuildPropDefault = [T] extends [ + // eslint-disable-next-line @typescript-eslint/ban-types + Record | Array | Function, +] + ? D + : D extends () => T + ? ReturnType + : D; + +export type BuildPropDefault = R extends true + ? { readonly default?: undefined } + : { + readonly default: Exclude extends never + ? undefined + : Exclude<_BuildPropDefault, undefined>; + }; +export type BuildPropReturn = { + readonly type: PropType>; + readonly required: IfUnknown; + readonly validator: ((val: unknown) => boolean) | undefined; + [propKey]: true; +} & BuildPropDefault, IfUnknown, IfUnknown>; + +/** + * @description Build prop. It can better optimize prop types + * @description 生成 prop,能更好地优化类型 + * @example + // limited options + // the type will be PropType<'light' | 'dark'> + buildProp({ + type: String, + values: ['light', 'dark'], + } as const) + * @example + // limited options and other types + // the type will be PropType<'small' | 'medium' | number> + buildProp({ + type: [String, Number], + values: ['small', 'medium'], + validator: (val: unknown): val is number => typeof val === 'number', + } as const) + @link see more: https://github.com/element-plus/element-plus/pull/3341 + */ +export function buildProp< + T = never, + D extends BuildPropType = never, + R extends boolean = false, + V = never, + C = never, +>(option: BuildPropOption, key?: string): BuildPropReturn { + // filter native prop type and nested prop, e.g `null`, `undefined` (from `buildProps`) + if (!isObject(option) || !!option[propKey]) return option as any; + + const { values, required, default: defaultValue, type, validator } = option; + + const _validator = + values || validator + ? (val: unknown) => { + let valid = false; + let allowedValues: unknown[] = []; + + if (values) { + allowedValues = [...values, defaultValue]; + valid ||= allowedValues.includes(val); + } + if (validator) valid ||= validator(val); + + if (!valid && allowedValues.length > 0) { + const allowValuesText = [...new Set(allowedValues)] + .map((value) => JSON.stringify(value)) + .join(', '); + warn( + `Invalid prop: validation failed${ + key ? ` for prop "${key}"` : '' + }. Expected one of [${allowValuesText}], got value ${JSON.stringify(val)}.`, + ); + } + return valid; + } + : undefined; + + return { + type: + typeof type === 'object' && Object.getOwnPropertySymbols(type).includes(wrapperKey) && type + ? type[wrapperKey] + : type, + required: !!required, + default: defaultValue, + validator: _validator, + [propKey]: true, + } as unknown as BuildPropReturn; +} + +type NativePropType = [((...args: any) => any) | { new (...args: any): any } | undefined | null]; + +export const buildProps = < + O extends { + [K in keyof O]: O[K] extends BuildPropReturn + ? O[K] + : [O[K]] extends NativePropType + ? O[K] + : O[K] extends BuildPropOption + ? D extends BuildPropType + ? BuildPropOption + : never + : never; + }, +>( + props: O, +) => + fromPairs( + Object.entries(props).map(([key, option]) => [key, buildProp(option as any, key)]), + ) as unknown as { + [K in keyof O]: O[K] extends { [propKey]: boolean } + ? O[K] + : [O[K]] extends NativePropType + ? O[K] + : O[K] extends BuildPropOption< + infer T, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + infer _D, + infer R, + infer V, + infer C + > + ? BuildPropReturn + : never; + }; + +export const definePropType = (val: any) => ({ [wrapperKey]: val }) as PropWrapper; + +export const keyOf = (arr: T) => Object.keys(arr) as Array; + +export const mutable = >(val: T) => + val as Mutable; + +export const componentSize = ['large', 'medium', 'small', 'mini'] as const; diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 00000000000..7c50e7f3a03 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,65 @@ +// copy from element-plus + +import type { CSSProperties, Plugin } from 'vue'; + +type OptionalKeys> = { + [K in keyof T]: T extends Record ? never : K; +}[keyof T]; + +type RequiredKeys> = Exclude>; + +type MonoArgEmitter = (evt: K, arg?: T[K]) => void; + +type BiArgEmitter = (evt: K, arg: T[K]) => void; + +export type EventEmitter> = MonoArgEmitter> & + BiArgEmitter>; + +export type AnyFunction = (...args: any[]) => T; + +export type PartialReturnType unknown> = Partial>; + +export type SFCWithInstall = T & Plugin; + +export type Nullable = T | null; + +export type RefElement = Nullable; + +export type CustomizedHTMLElement = HTMLElement & T; + +export type Indexable = { + [key: string]: T; +}; + +export type Hash = Indexable; + +export type TimeoutHandle = ReturnType; + +export type ComponentSize = 'large' | 'medium' | 'small' | 'mini'; + +export type StyleValue = string | CSSProperties | Array; + +export type Mutable = { -readonly [P in keyof T]: T[P] }; + +export type Merge = { + [K in keyof O | keyof T]: K extends keyof T ? T[K] : K extends keyof O ? O[K] : never; +}; + +/** + * T = [ + * { name: string; age: number; }, + * { sex: 'male' | 'female'; age: string } + * ] + * => + * MergeAll = { + * name: string; + * sex: 'male' | 'female'; + * age: string + * } + */ +export type MergeAll = T extends [ + infer F extends object, + ...infer Rest extends object[], +] + ? MergeAll> + : R; diff --git a/src/utils/uuid.ts b/src/utils/uuid.ts new file mode 100644 index 00000000000..548bcf39807 --- /dev/null +++ b/src/utils/uuid.ts @@ -0,0 +1,28 @@ +const hexList: string[] = []; +for (let i = 0; i <= 15; i++) { + hexList[i] = i.toString(16); +} + +export function buildUUID(): string { + let uuid = ''; + for (let i = 1; i <= 36; i++) { + if (i === 9 || i === 14 || i === 19 || i === 24) { + uuid += '-'; + } else if (i === 15) { + uuid += 4; + } else if (i === 20) { + uuid += hexList[(Math.random() * 4) | 8]; + } else { + uuid += hexList[(Math.random() * 16) | 0]; + } + } + return uuid.replace(/-/g, ''); +} + +let unique = 0; +export function buildShortUUID(prefix = ''): string { + const time = Date.now(); + const random = Math.floor(Math.random() * 1000000000); + unique++; + return prefix + '_' + random + unique + String(time); +} diff --git a/src/views/dashboard/analysis/components/GrowCard.vue b/src/views/dashboard/analysis/components/GrowCard.vue new file mode 100644 index 00000000000..1bd894948ef --- /dev/null +++ b/src/views/dashboard/analysis/components/GrowCard.vue @@ -0,0 +1,39 @@ + + diff --git a/src/views/dashboard/analysis/components/SalesProductPie.vue b/src/views/dashboard/analysis/components/SalesProductPie.vue new file mode 100644 index 00000000000..40bf8c94da5 --- /dev/null +++ b/src/views/dashboard/analysis/components/SalesProductPie.vue @@ -0,0 +1,64 @@ + + diff --git a/src/views/dashboard/analysis/components/SiteAnalysis.vue b/src/views/dashboard/analysis/components/SiteAnalysis.vue new file mode 100644 index 00000000000..17d7d39f923 --- /dev/null +++ b/src/views/dashboard/analysis/components/SiteAnalysis.vue @@ -0,0 +1,38 @@ + + diff --git a/src/views/dashboard/analysis/components/VisitAnalysis.vue b/src/views/dashboard/analysis/components/VisitAnalysis.vue new file mode 100644 index 00000000000..ccdabd5ce0a --- /dev/null +++ b/src/views/dashboard/analysis/components/VisitAnalysis.vue @@ -0,0 +1,87 @@ + + diff --git a/src/views/dashboard/analysis/components/VisitAnalysisBar.vue b/src/views/dashboard/analysis/components/VisitAnalysisBar.vue new file mode 100644 index 00000000000..f4a659aea01 --- /dev/null +++ b/src/views/dashboard/analysis/components/VisitAnalysisBar.vue @@ -0,0 +1,45 @@ + + diff --git a/src/views/dashboard/analysis/components/VisitRadar.vue b/src/views/dashboard/analysis/components/VisitRadar.vue new file mode 100644 index 00000000000..51b7cfa9992 --- /dev/null +++ b/src/views/dashboard/analysis/components/VisitRadar.vue @@ -0,0 +1,94 @@ + + diff --git a/src/views/dashboard/analysis/components/VisitSource.vue b/src/views/dashboard/analysis/components/VisitSource.vue new file mode 100644 index 00000000000..56a2e318175 --- /dev/null +++ b/src/views/dashboard/analysis/components/VisitSource.vue @@ -0,0 +1,82 @@ + + diff --git a/src/views/dashboard/analysis/components/props.ts b/src/views/dashboard/analysis/components/props.ts new file mode 100644 index 00000000000..86436506c1e --- /dev/null +++ b/src/views/dashboard/analysis/components/props.ts @@ -0,0 +1,16 @@ +import { PropType } from 'vue'; + +export interface BasicProps { + width: string; + height: string; +} +export const basicProps = { + width: { + type: String as PropType, + default: '100%', + }, + height: { + type: String as PropType, + default: '280px', + }, +}; diff --git a/src/views/dashboard/analysis/data.ts b/src/views/dashboard/analysis/data.ts new file mode 100644 index 00000000000..c5c28dd5fd2 --- /dev/null +++ b/src/views/dashboard/analysis/data.ts @@ -0,0 +1,43 @@ +export interface GrowCardItem { + icon: string; + title: string; + value: number; + total: number; + color: string; + action: string; +} + +export const growCardList: GrowCardItem[] = [ + { + title: '访问数', + icon: 'visit-count|svg', + value: 2000, + total: 120000, + color: 'green', + action: '月', + }, + { + title: '成交额', + icon: 'total-sales|svg', + value: 20000, + total: 500000, + color: 'blue', + action: '月', + }, + { + title: '下载数', + icon: 'download-count|svg', + value: 8000, + total: 120000, + color: 'orange', + action: '周', + }, + { + title: '成交数', + icon: 'transaction|svg', + value: 5000, + total: 50000, + color: 'purple', + action: '年', + }, +]; diff --git a/src/views/dashboard/analysis/index.vue b/src/views/dashboard/analysis/index.vue new file mode 100644 index 00000000000..c35fa690357 --- /dev/null +++ b/src/views/dashboard/analysis/index.vue @@ -0,0 +1,25 @@ + + diff --git a/src/views/dashboard/workbench/components/DynamicInfo.vue b/src/views/dashboard/workbench/components/DynamicInfo.vue new file mode 100644 index 00000000000..9947817f956 --- /dev/null +++ b/src/views/dashboard/workbench/components/DynamicInfo.vue @@ -0,0 +1,31 @@ + + diff --git a/src/views/dashboard/workbench/components/ProjectCard.vue b/src/views/dashboard/workbench/components/ProjectCard.vue new file mode 100644 index 00000000000..58ec341cd09 --- /dev/null +++ b/src/views/dashboard/workbench/components/ProjectCard.vue @@ -0,0 +1,24 @@ + + diff --git a/src/views/dashboard/workbench/components/QuickNav.vue b/src/views/dashboard/workbench/components/QuickNav.vue new file mode 100644 index 00000000000..a58960140e2 --- /dev/null +++ b/src/views/dashboard/workbench/components/QuickNav.vue @@ -0,0 +1,15 @@ + + diff --git a/src/views/dashboard/workbench/components/SaleRadar.vue b/src/views/dashboard/workbench/components/SaleRadar.vue new file mode 100644 index 00000000000..5065da30b31 --- /dev/null +++ b/src/views/dashboard/workbench/components/SaleRadar.vue @@ -0,0 +1,94 @@ + + diff --git a/src/views/dashboard/workbench/components/WorkbenchHeader.vue b/src/views/dashboard/workbench/components/WorkbenchHeader.vue new file mode 100644 index 00000000000..f08d5b8804c --- /dev/null +++ b/src/views/dashboard/workbench/components/WorkbenchHeader.vue @@ -0,0 +1,33 @@ + + diff --git a/src/views/dashboard/workbench/components/data.ts b/src/views/dashboard/workbench/components/data.ts new file mode 100644 index 00000000000..19d081396af --- /dev/null +++ b/src/views/dashboard/workbench/components/data.ts @@ -0,0 +1,156 @@ +interface GroupItem { + title: string; + icon: string; + color: string; + desc: string; + date: string; + group: string; +} + +interface NavItem { + title: string; + icon: string; + color: string; +} + +interface DynamicInfoItem { + avatar: string; + name: string; + date: string; + desc: string; +} + +export const navItems: NavItem[] = [ + { + title: '首页', + icon: 'ion:home-outline', + color: '#1fdaca', + }, + { + title: '仪表盘', + icon: 'ion:grid-outline', + color: '#bf0c2c', + }, + { + title: '组件', + icon: 'ion:layers-outline', + color: '#e18525', + }, + { + title: '系统管理', + icon: 'ion:settings-outline', + color: '#3fb27f', + }, + { + title: '权限管理', + icon: 'ion:key-outline', + color: '#4daf1bc9', + }, + { + title: '图表', + icon: 'ion:bar-chart-outline', + color: '#00d8ff', + }, +]; + +export const dynamicInfoItems: DynamicInfoItem[] = [ + { + avatar: 'dynamic-avatar-1|svg', + name: '威廉', + date: '刚刚', + desc: `在 开源组 创建了项目 Vue`, + }, + { + avatar: 'dynamic-avatar-2|svg', + name: '艾文', + date: '1个小时前', + desc: `关注了 威廉 `, + }, + { + avatar: 'dynamic-avatar-3|svg', + name: '克里斯', + date: '1天前', + desc: `发布了 个人动态 `, + }, + { + avatar: 'dynamic-avatar-4|svg', + name: 'Vben', + date: '2天前', + desc: `发表文章 如何编写一个Vite插件 `, + }, + { + avatar: 'dynamic-avatar-5|svg', + name: '皮特', + date: '3天前', + desc: `回复了 杰克 的问题 如何进行项目优化?`, + }, + { + avatar: 'dynamic-avatar-6|svg', + name: '杰克', + date: '1周前', + desc: `关闭了问题 如何运行项目 `, + }, + { + avatar: 'dynamic-avatar-1|svg', + name: '威廉', + date: '1周前', + desc: `发布了 个人动态 `, + }, + { + avatar: 'dynamic-avatar-1|svg', + name: '威廉', + date: '2021-04-01 20:00', + desc: `推送了代码到 Github`, + }, +]; + +export const groupItems: GroupItem[] = [ + { + title: 'Github', + icon: 'carbon:logo-github', + color: '', + desc: '不要等待机会,而要创造机会。', + group: '开源组', + date: '2021-04-01', + }, + { + title: 'Vue', + icon: 'ion:logo-vue', + color: '#3fb27f', + desc: '现在的你决定将来的你。', + group: '算法组', + date: '2021-04-01', + }, + { + title: 'Html5', + icon: 'ion:logo-html5', + color: '#e18525', + desc: '没有什么才能比努力更重要。', + group: '上班摸鱼', + date: '2021-04-01', + }, + { + title: 'Angular', + icon: 'ion:logo-angular', + color: '#bf0c2c', + desc: '热情和欲望可以突破一切难关。', + group: 'UI', + date: '2021-04-01', + }, + { + title: 'React', + icon: 'bx:bxl-react', + color: '#00d8ff', + desc: '健康的身体是实现目标的基石。', + group: '技术牛', + date: '2021-04-01', + }, + { + title: 'Js', + icon: 'ion:logo-javascript', + color: '#EBD94E', + desc: '路是走出来的,而不是空想出来的。', + group: '架构组', + date: '2021-04-01', + }, +]; diff --git a/src/views/dashboard/workbench/index.vue b/src/views/dashboard/workbench/index.vue new file mode 100644 index 00000000000..0d7c9c35fb4 --- /dev/null +++ b/src/views/dashboard/workbench/index.vue @@ -0,0 +1,36 @@ + + diff --git a/src/views/demo/charts/Line.vue b/src/views/demo/charts/Line.vue new file mode 100644 index 00000000000..272e2fb5bf3 --- /dev/null +++ b/src/views/demo/charts/Line.vue @@ -0,0 +1,113 @@ + + diff --git a/src/views/demo/charts/Map.vue b/src/views/demo/charts/Map.vue new file mode 100644 index 00000000000..74cfac26ecb --- /dev/null +++ b/src/views/demo/charts/Map.vue @@ -0,0 +1,70 @@ + + diff --git a/src/views/demo/charts/Pie.vue b/src/views/demo/charts/Pie.vue new file mode 100644 index 00000000000..b4e6d75ba4a --- /dev/null +++ b/src/views/demo/charts/Pie.vue @@ -0,0 +1,141 @@ + + diff --git a/src/views/demo/charts/SaleRadar.vue b/src/views/demo/charts/SaleRadar.vue new file mode 100644 index 00000000000..bdc7cd7c836 --- /dev/null +++ b/src/views/demo/charts/SaleRadar.vue @@ -0,0 +1,95 @@ + + diff --git a/src/views/demo/charts/china.json b/src/views/demo/charts/china.json new file mode 100644 index 00000000000..bbc0a8322af --- /dev/null +++ b/src/views/demo/charts/china.json @@ -0,0 +1,856 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": "710000", + "properties": { + "id": "710000", + "cp": [121.509062, 24.044332], + "name": "台湾", + "childNum": 6 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@°Ü¯Û"], + [ + "@@ƛĴÕƊÉɼģºðʀ\\ƎsÆNŌÔĚäœnÜƤɊĂǀĆĴžĤNJŨxĚĮǂƺòƌ‚–âÔ®ĮXŦţƸZûЋƕƑGđ¨ĭMó·ęcëƝɉlÝƯֹÅŃ^Ó·śŃNjƏďíåɛGɉ™¿@ăƑŽ¥ĘWǬÏĶŁâ" + ], + ["@@\\p|WoYG¿¥I†j@¢"], + ["@@…¡‰@ˆV^RqˆBbAŒnTXeRz¤Lž«³I"], + ["@@ÆEE—„kWqë @œ"], + ["@@fced"], + ["@@„¯ɜÄèaì¯ØǓIġĽ"], + ["@@çûĖ롖hòř "] + ], + "encodeOffsets": [ + [[122886, 24033]], + [[123335, 22980]], + [[122375, 24193]], + [[122518, 24117]], + [[124427, 22618]], + [[124862, 26043]], + [[126259, 26318]], + [[127671, 26683]] + ] + } + }, + { + "type": "Feature", + "id": "130000", + "properties": { + "id": "130000", + "cp": [114.502461, 38.045474], + "name": "河北", + "childNum": 3 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@o~†Z]‚ªr‰ºc_ħ²G¼s`jΟnüsœłNX_“M`ǽÓnUK…Ĝēs¤­©yrý§uģŒc†JŠ›e"], + ["@@U`Ts¿m‚"], + [ + "@@oºƋÄd–eVŽDJj£€J|Ådz•Ft~žKŨ¸IÆv|”‡¢r}膎onb˜}`RÎÄn°ÒdÞ²„^®’lnÐèĄlðӜ×]ªÆ}LiĂ±Ö`^°Ç¶p®đDcœŋ`–ZÔ’¶êqvFƚ†N®ĆTH®¦O’¾ŠIbÐã´BĐɢŴÆíȦp–ĐÞXR€·nndOž¤’OÀĈƒ­Qg˜µFo|gȒęSWb©osx|hYh•gŃfmÖĩnº€T̒Sp›¢dYĤ¶UĈjl’ǐpäìë|³kÛfw²Xjz~ÂqbTŠÑ„ěŨ@|oM‡’zv¢ZrÃVw¬ŧˏfŒ°ÐT€ªqŽs{Sž¯r æÝlNd®²Ğ džiGʂJ™¼lr}~K¨ŸƐÌWö€™ÆŠzRš¤lêmĞL΄’@¡|q]SvK€ÑcwpÏρ†ĿćènĪWlĄkT}ˆJ”¤~ƒÈT„d„™pddʾĬŠ”ŽBVt„EÀ¢ôPĎƗè@~‚k–ü\\rÊĔÖæW_§¼F˜†´©òDòj’ˆYÈrbĞāøŀG{ƀ|¦ðrb|ÀH`pʞkv‚GpuARhÞÆǶgƊTǼƹS£¨¡ù³ŘÍ]¿Ây™ôEP xX¶¹܇O¡“gÚ¡IwÃ鑦ÅB‡Ï|ǰ…N«úmH¯‹âŸDùŽyŜžŲIÄuШDž•¸dɂ‡‚FŸƒ•›Oh‡đ©OŸ›iÃ`ww^ƒÌkŸ‘ÑH«ƇǤŗĺtFu…{Z}Ö@U‡´…ʚLg®¯Oı°ÃwŸ ^˜—€VbÉs‡ˆmA…ê]]w„§›RRl£‡ȭµu¯b{ÍDěïÿȧŽuT£ġƒěŗƃĝ“Q¨fV†Ƌ•ƅn­a@‘³@šď„yýIĹÊKšŭfċŰóŒxV@tˆƯŒJ”]eƒR¾fe|rHA˜|h~Ėƍl§ÏŠlTíb ØoˆÅbbx³^zÃ͚¶Sj®A”yÂhðk`š«P€”ˈµEF†Û¬Y¨Ļrõqi¼‰Wi°§’б´°^[ˆÀ|ĠO@ÆxO\\tŽa\\tĕtû{ġŒȧXýĪÓjùÎRb›š^ΛfK[ݏděYfíÙTyŽuUSyŌŏů@Oi½’éŅ­aVcř§ax¹XŻác‡žWU£ôãºQ¨÷Ñws¥qEH‰Ù|‰›šYQoŕÇyáĂ£MðoťÊ‰P¡mšWO¡€v†{ôvîēÜISpÌhp¨ ‘j†deŔQÖj˜X³à™Ĉ[n`Yp@Už–cM`’RKhŒEbœ”pŞlNut®Etq‚nsÁŠgA‹iú‹oH‡qCX‡”hfgu“~ϋWP½¢G^}¯ÅīGCŸÑ^ãziMáļMTÃƘrMc|O_ž¯Ŏ´|‡morDkO\\mĆJfl@c̬¢aĦtRıҙ¾ùƀ^juųœK­ƒUFy™—Ɲ…›īÛ÷ąV×qƥV¿aȉd³B›qPBm›aËđŻģm“Å®Vйd^K‡KoŸnYg“¯Xhqa”Ldu¥•ÍpDž¡KąÅƒkĝęěhq‡}HyÓ]¹ǧ£…Í÷¿qáµ§š™g‘¤o^á¾ZE‡¤i`ij{n•ƒOl»ŸWÝĔįhg›F[¿¡—ßkOüš_‰€ū‹i„DZàUtėGylƒ}ŒÓM}€jpEC~¡FtoQi‘šHkk{Ãmï‚" + ] + ], + "encodeOffsets": [[[119712, 40641]], [[121616, 39981]], [[116462, 37237]]] + } + }, + { + "type": "Feature", + "id": "140000", + "properties": { + "id": "140000", + "cp": [111.849248, 36.857014], + "name": "山西", + "childNum": 1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@Þĩ҃S‰ra}Á€yWix±Üe´lè“ßÓǏok‘ćiµVZģ¡coœ‘TS˹ĪmnÕńe–hZg{gtwªpXaĚThȑp{¶Eh—®RćƑP¿£‘Pmc¸mQÝW•ďȥoÅîɡųAďä³aωJ‘½¥PG­ąSM­™…EÅruµé€‘Yӎ•Ō_d›ĒCo­Èµ]¯_²ÕjāŽK~©ÅØ^ԛkïçămϑk]­±ƒcݯÑÃmQÍ~_a—pm…~ç¡q“ˆu{JÅŧ·Ls}–EyÁÆcI{¤IiCfUc•ƌÃp§]웫vD@¡SÀ‘µM‚ÅwuŽYY‡¡DbÑc¡hƒ×]nkoQdaMç~eD•ÛtT‰©±@¥ù@É¡‰ZcW|WqOJmĩl«ħşvOÓ«IqăV—¥ŸD[mI~Ó¢cehiÍ]Ɠ~ĥqXŠ·eƷœn±“}v•[ěďŽŕ]_‘œ•`‰¹ƒ§ÕōI™o©b­s^}Ét±ū«³p£ÿ·Wµ|¡¥ăFÏs׌¥ŅxŸÊdÒ{ºvĴÎêÌɊ²¶€ü¨|ÞƸµȲ‘LLúÉƎ¤ϊęĔV`„_bª‹S^|ŸdŠzY|dz¥p†ZbÆ£¶ÒK}tĦÔņƠ‚PYzn€ÍvX¶Ěn ĠÔ„zý¦ª˜÷žÑĸَUȌ¸‚dòÜJð´’ìúNM¬ŒXZ´‘¤ŊǸ_tldIš{¦ƀðĠȤ¥NehXnYG‚‡R° ƬDj¬¸|CĞ„Kq‚ºfƐiĺ©ª~ĆOQª ¤@ìǦɌ²æBŒÊ”TœŸ˜ʂōĖ’šĴŞ–ȀœÆÿȄlŤĒö„t”νî¼ĨXhŒ‘˜|ªM¤Ðz" + ], + "encodeOffsets": [[116874, 41716]] + } + }, + { + "type": "Feature", + "id": "150000", + "properties": { + "id": "150000", + "cp": [111.670801, 41.818311], + "name": "内蒙古", + "childNum": 2 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + "@@¯PqƒFB…‰|S•³C|kñ•H‹d‘iÄ¥sˆʼnő…PóÑÑE^‘ÅPpy_YtS™hQ·aHwsOnʼnÚs©iqj›‰€USiº]ïWš‰«gW¡A–Rë¥_ŽsgÁnUI«m‰…„‹]j‡vV¼euhwqA„aW˜ƒ_µj…»çjioQR¹ēÃßt@r³[ÛlćË^ÍÉáG“›OUۗOB±•XŸkŇ¹£k|e]ol™ŸkVͼÕqtaÏõjgÁ£§U^Œ”RLˆËnX°Ç’Bz†^~wfvˆypV ¯„ƫĉ˭ȫƗŷɿÿĿƑ˃ĝÿÃǃßËőó©ǐȍŒĖM×ÍEyx‹þp]Évïè‘vƀnÂĴÖ@‚‰†V~Ĉv¦wĖt—ējyÄDXÄxGQuv_›i¦aBçw‘˛wD™©{ŸtāmQ€{EJ§KPśƘƿ¥@‰sCT•É}ɃwˆƇy±ŸgÑ“}T[÷kÐ禫…SÒ¥¸ëBX½‰HáŵÀğtSÝÂa[ƣ°¯¦P]£ġ“–“Òk®G²„èQ°óMq}EŠóƐÇ\\ƒ‡@áügQ͋u¥Fƒ“T՛¿Jû‡]|mvāÎYua^WoÀa·­ząÒot×¶CLƗi¯¤mƎHNJ¤îìɾŊìTdåwsRÖgĒųúÍġäÕ}Q¶—ˆ¿A•†‹[¡Œ{d×uQAƒ›M•xV‹vMOmăl«ct[wº_šÇʊŽŸjb£ĦS_é“QZ“_lwgOiýe`YYLq§IÁˆdz£ÙË[ÕªuƏ³ÍT—s·bÁĽäė[›b[ˆŗfãcn¥îC¿÷µ[ŏÀQ­ōšĉm¿Á^£mJVm‡—L[{Ï_£›F¥Ö{ŹA}…×Wu©ÅaųijƳhB{·TQqÙIķˑZđ©Yc|M¡…L•eVUóK_QWk’_ĥ‘¿ãZ•»X\\ĴuUƒè‡lG®ěłTĠğDєOrÍd‚ÆÍz]‹±…ŭ©ŸÅ’]ŒÅÐ}UË¥©Tċ™ïxgckfWgi\\ÏĒ¥HkµE˜ë{»ÏetcG±ahUiñiWsɁˆ·c–C‚Õk]wȑ|ća}w…VaĚ᠞ŒG°ùnM¬¯†{ÈˆÐÆA’¥ÄêJxÙ¢”hP¢Ûˆº€µwWOŸóFŽšÁz^ÀŗÎú´§¢T¤ǻƺSė‰ǵhÝÅQgvBHouʝl_o¿Ga{ïq{¥|ſĿHĂ÷aĝÇq‡Z‘ñiñC³ª—…»E`¨åXēÕqÉû[l•}ç@čƘóO¿¡ƒFUsA‰“ʽīccšocƒ‚ƒÇS}„“£‡IS~ălkĩXçmĈ…ŀЂoÐdxÒuL^T{r@¢‘žÍƒĝKén£kQ™‰yšÅõËXŷƏL§~}kqš»IHėDžjĝŸ»ÑÞoŸå°qTt|r©ÏS‹¯·eŨĕx«È[eMˆ¿yuˆ‘pN~¹ÏyN£{©’—g‹ħWí»Í¾s“əšDž_ÃĀɗ±ą™ijĉʍŌŷ—S›É“A‹±åǥɋ@럣R©ąP©}ĹªƏj¹erƒLDĝ·{i«ƫC£µsKCš…GS|úþX”gp›{ÁX¿Ÿć{ƱȏñZáĔyoÁhA™}ŅĆfdʼn„_¹„Y°ėǩÑ¡H¯¶oMQqð¡Ë™|‘Ñ`ƭŁX½·óۓxğįÅcQ‡ˆ“ƒs«tȋDžF“Ÿù^i‘t«Č¯[›hAi©á¥ÇĚ×l|¹y¯YȵƓ‹ñǙµï‚ċ™Ļ|Dœ™üȭ¶¡˜›oŽäÕG\\ďT¿Òõr¯œŸLguÏYęRƩšɷŌO\\İТæ^Ŋ IJȶȆbÜGŽĝ¬¿ĚVĎgª^íu½jÿĕęjık@Ľƒ]ėl¥Ë‡ĭûÁ„ƒėéV©±ćn©­ȇžÍq¯½•YÃÔʼn“ÉNѝÅÝy¹NqáʅDǡËñ­ƁYÅy̱os§ȋµʽǘǏƬɱà‘ưN¢ƔÊuľýľώȪƺɂļžxœZĈ}ÌʼnŪ˜ĺœŽĭFЛĽ̅ȣͽÒŵìƩÇϋÿȮǡŏçƑůĕ~Ǎ›¼ȳÐUf†dIxÿ\\G ˆzâɏÙOº·pqy£†@ŒŠqþ@Ǟ˽IBäƣzsÂZ†ÁàĻdñ°ŕzéØűzșCìDȐĴĺf®ŽÀľưø@ɜÖÞKĊŇƄ§‚͑těï͡VAġÑÑ»d³öǍÝXĉĕÖ{þĉu¸ËʅğU̎éhɹƆ̗̮ȘNJ֥ड़ࡰţાíϲäʮW¬®ҌeרūȠkɬɻ̼ãüfƠSצɩςåȈHϚÎKdzͲOðÏȆƘ¼CϚǚ࢚˼ФԂ¤ƌžĞ̪Qʤ´¼mȠJˀŸƲÀɠmǐnǔĎȆÞǠN~€ʢĜ‚¶ƌĆĘźʆȬ˪ĚǏĞGȖƴƀj`ĢçĶāàŃºē̃ĖćšYŒÀŎüôQÐÂŎŞdžŞêƖš˜oˆDĤÕºÑǘÛˤ³̀gńƘĔÀ^žªƂ`ªt¾äƚêĦĀ¼Ð€Ĕǎ¨Ȕ»͠^ˮÊȦƤøxRrŜH¤¸ÂxDĝŒ|ø˂˜ƮÐ¬ɚwɲFjĔ²Äw°dždÀɞ_ĸdîàŎjʜêTĞªŌ‡ŜWÈ|tqĢUB~´°ÎFC•ŽU¼pĀēƄN¦¾O¶ŠłKĊOj“Ě”j´ĜYp˜{¦„ˆSĚÍ\\Tš×ªV–÷Ší¨ÅDK°ßtŇĔKš¨ǵÂcḷ̌ĚǣȄĽF‡lġUĵœŇ‹ȣFʉɁƒMğįʏƶɷØŭOǽ«ƽū¹Ʊő̝Ȩ§ȞʘĖiɜɶʦ}¨֪ࠜ̀ƇǬ¹ǨE˦ĥªÔêFŽxúQ„Er´W„rh¤Ɛ \\talĈDJ˜Ü|[Pll̚¸ƎGú´Pž¬W¦†^¦–H]prR“n|or¾wLVnÇIujkmon£cX^Bh`¥V”„¦U¤¸}€xRj–[^xN[~ªŠxQ„‚[`ªHÆÂExx^wšN¶Ê˜|¨ì†˜€MrœdYp‚oRzNy˜ÀDs~€bcfÌ`L–¾n‹|¾T‚°c¨È¢a‚r¤–`[|òDŞĔöxElÖdH„ÀI`„Ď\\Àì~ƎR¼tf•¦^¢ķ¶e”ÐÚMŒptgj–„ɡČÅyġLû™ŇV®ŠÄÈƀ†Ď°P|ªVV†ªj–¬ĚÒêp¬–E|ŬÂc|ÀtƐK fˆ{ĘFǜƌXƲąo½Ę‘\\¥–o}›Ûu£ç­kX‘{uĩ«āíÓUŅßŢq€Ť¥lyň[€oi{¦‹L‡ń‡ðFȪȖ”ĒL„¿Ì‹ˆfŒ£K£ʺ™oqNŸƒwğc`ue—tOj×°KJ±qƒÆġm‰Ěŗos¬…qehqsuœƒH{¸kH¡Š…ÊRǪÇƌbȆ¢´ä܍¢NìÉʖ¦â©Ġu¦öČ^â£Ăh–šĖMÈÄw‚\\fŦ°W ¢¾luŸD„wŠ\\̀ʉÌÛM…Ā[bӞEn}¶Vc…ê“sƒ" + ] + ], + "encodeOffsets": [[[129102, 52189]]] + } + }, + { + "type": "Feature", + "id": "210000", + "properties": { + "id": "210000", + "cp": [123.429096, 41.796767], + "name": "辽宁", + "childNum": 16 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@L–Ž@@s™a"], + ["@@MnNm"], + ["@@d‚c"], + ["@@eÀ‚C@b‚“‰"], + ["@@f‡…Xwkbr–Ä`qg"], + ["@@^jtW‘Q"], + ["@@~ Y]c"], + ["@@G`ĔN^_¿Z‚ÃM"], + ["@@iX¶B‹Y"], + ["@@„YƒZ"], + ["@@L_{Epf"], + ["@@^WqCT\\"], + ["@@\\[“‹§t|”¤_"], + ["@@m`n_"], + ["@@Ïxnj{q_×^Giip"], + [ + "@@@œé^B†‡ntˆaÊU—˜Ÿ]x ¯ÄPIJ­°h€ʙK³†VˆÕ@Y~†|EvĹsDŽ¦­L^p²ŸÒG ’Ël]„xxÄ_˜fT¤Ď¤cŽœP„–C¨¸TVjbgH²sdÎdHt`Bˆ—²¬GJję¶[ÐhjeXdlwhšðSȦªVÊπ‹Æ‘Z˜ÆŶ®²†^ŒÎyÅÎcPqń“ĚDMħĜŁH­ˆk„çvV[ij¼W–‚YÀäĦ’‘`XlžR`žôLUVžfK–¢†{NZdĒª’YĸÌÚJRr¸SA|ƴgŴĴÆbvªØX~†źBŽ|¦ÕœEž¤Ð`\\|Kˆ˜UnnI]¤ÀÂĊnŎ™R®Ő¿¶\\ÀøíDm¦ÎbŨab‰œaĘ\\ľã‚¸a˜tÎSƐ´©v\\ÖÚÌǴ¤Â‡¨JKr€Z_Z€fjþhPkx€`Y”’RIŒjJcVf~sCN¤ ˆE‚œhæm‰–sHy¨SðÑÌ\\\\ŸĐRZk°IS§fqŒßýáЍÙÉÖ[^¯ǤŲ„ê´\\¦¬ĆPM¯£Ÿˆ»uïpùzEx€žanµyoluqe¦W^£ÊL}ñrkqWňûP™‰UP¡ôJŠoo·ŒU}£Œ„[·¨@XŒĸŸ“‹‹DXm­Ûݏº‡›GU‹CÁª½{íĂ^cj‡k“¶Ã[q¤“LÉö³cux«zZfƒ²BWÇ®Yß½ve±ÃC•ý£W{Ú^’q^sÑ·¨‹ÍOt“¹·C¥‡GD›rí@wÕKţ݋˜Ÿ«V·i}xËÍ÷‘i©ĝ‡ɝǡ]ƒˆ{c™±OW‹³Ya±Ÿ‰_穂Hžĕoƫ€Ňqƒr³‰Lys[„ñ³¯OS–ďOMisZ†±ÅFC¥Pq{‚Ã[Pg}\\—¿ghćO…•k^ģÁFıĉĥM­oEqqZûěʼn³F‘¦oĵ—hŸÕP{¯~TÍlª‰N‰ßY“Ð{Ps{ÃVU™™eĎwk±ʼnVÓ½ŽJãÇÇ»Jm°dhcÀff‘dF~ˆ€ĀeĖ€d`sx² šƒ®EżĀdQ‹Âd^~ăÔHˆ¦\\›LKpĄVez¤NP ǹӗR™ÆąJSh­a[¦´Âghwm€BÐ¨źhI|žVVŽ—Ž|p] Â¼èNä¶ÜBÖ¼“L`‚¼bØæŒKV”ŸpoœúNZÞÒKxpw|ÊEMnzEQšŽIZ”ŽZ‡NBˆčÚFÜçmĩ‚WĪñt‘ÞĵÇñZ«uD‚±|Əlij¥ãn·±PmÍa‰–da‡ CL‡Ǒkùó¡³Ï«QaċϑOÃ¥ÕđQȥċƭy‹³ÃA" + ] + ], + "encodeOffsets": [ + [[123686, 41445]], + [[126019, 40435]], + [[124393, 40128]], + [[126117, 39963]], + [[125322, 40140]], + [[126686, 40700]], + [[126041, 40374]], + [[125584, 40168]], + [[125453, 40165]], + [[125362, 40214]], + [[125280, 40291]], + [[125774, 39997]], + [[125976, 40496]], + [[125822, 39993]], + [[125509, 40217]], + [[122731, 40949]] + ] + } + }, + { + "type": "Feature", + "id": "220000", + "properties": { "id": "220000", "cp": [125.3245, 43.886841], "name": "吉林", "childNum": 1 }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@‘p䔳PClƒFbbÍzš€wBG’ĭ€Z„Åi“»ƒlY­ċ²SgŽkÇ£—^S‰“qd¯•‹R…©éŽ£¯S†\\cZ¹iűƏCuƍÓX‡oR}“M^o•£…R}oªU­F…uuXHlEŕ‡€Ï©¤ÛmTŽþ¤D–²ÄufàÀ­XXȱAe„yYw¬dvõ´KÊ£”\\rµÄl”iˆdā]|DÂVŒœH¹ˆÞ®ÜWnŒC”Œķ W‹§@\\¸‹ƒ~¤‹Vp¸‰póIO¢ŠVOšŇürXql~òÉK]¤¥Xrfkvzpm¶bwyFoúvð‡¼¤ N°ąO¥«³[ƒéǡű_°Õ\\ÚÊĝŽþâőàerR¨­JYlďQ[ ÏYëЧTGz•tnŠß¡gFkMŸāGÁ¤ia É‰™È¹`\\xs€¬dĆkNnuNUŠ–užP@‚vRY¾•–\\¢…ŒGªóĄ~RãÖÎĢù‚đŴÕhQŽxtcæëSɽʼníëlj£ƍG£nj°KƘµDsØÑpyƸ®¿bXp‚]vbÍZuĂ{nˆ^IüœÀSք”¦EŒvRÎûh@℈[‚Əȉô~FNr¯ôçR±ƒ­HÑl•’Ģ–^¤¢‚OðŸŒævxsŒ]ÞÁTĠs¶¿âƊGW¾ìA¦·TѬ†è¥€ÏÐJ¨¼ÒÖ¼ƒƦɄxÊ~S–tD@ŠĂ¼Ŵ¡jlºWžvЉˆzƦZЎ²CH— „Axiukd‹ŒGgetqmcžÛ£Ozy¥cE}|…¾cZ…k‚‰¿uŐã[oxGikfeäT@…šSUwpiÚFM©’£è^ڟ‚`@v¶eň†f h˜eP¶žt“äOlÔUgƒÞzŸU`lœ}ÔÆUvØ_Ō¬Öi^ĉi§²ÃŠB~¡Ĉ™ÚEgc|DC_Ȧm²rBx¼MÔ¦ŮdĨÃâYx‘ƘDVÇĺĿg¿cwÅ\\¹˜¥Yĭlœ¤žOv†šLjM_a W`zļMž·\\swqÝSA‡š—q‰Śij¯Š‘°kŠRē°wx^Đkǂғ„œž“œŽ„‹\\]˜nrĂ}²ĊŲÒøãh·M{yMzysěnĒġV·°“G³¼XÀ““™¤¹i´o¤ŃšŸÈ`̃DzÄUĞd\\i֚ŒˆmÈBĤÜɲDEh LG¾ƀľ{WaŒYÍȏĢĘÔRîĐj‹}Ǟ“ccj‡oUb½š{“h§Ǿ{K‹ƖµÎ÷žGĀÖŠåưÎs­l›•yiē«‹`姝H¥Ae^§„GK}iã\\c]v©ģZ“mÃ|“[M}ģTɟĵ‘Â`À–çm‰‘FK¥ÚíÁbXš³ÌQґHof{‰]e€pt·GŋĜYünĎųVY^’˜ydõkÅZW„«WUa~U·Sb•wGçǑ‚“iW^q‹F‚“›uNĝ—·Ew„‹UtW·Ýďæ©PuqEzwAV•—XR‰ãQ`­©GŒM‡ehc›c”ďϝd‡©ÑW_ϗYƅŒ»…é\\ƒɹ~ǙG³mØ©BšuT§Ĥ½¢Ã_ý‘L¡‘ýŸqT^rme™\\Pp•ZZbƒyŸ’uybQ—efµ]UhĿDCmûvašÙNSkCwn‰cćfv~…Y‹„ÇG" + ], + "encodeOffsets": [[130196, 42528]] + } + }, + { + "type": "Feature", + "id": "230000", + "properties": { + "id": "230000", + "cp": [128.642464, 46.756967], + "name": "黑龙江", + "childNum": 2 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + "@@UƒµNÿ¥īè灋•HÍøƕ¶LŒǽ|g¨|”™Ža¾pViˆdd”~ÈiŒíďÓQġėǐZ΋ŽXb½|ſÃH½ŸKFgɱCģÛÇA‡n™‹jÕc[VĝDZÃ˄Ç_™ £ń³pŽj£º”š¿”»WH´¯”U¸đĢmžtĜyzzNN|g¸÷äűѱĉā~mq^—Œ[ƒ”››”ƒǁÑďlw]¯xQĔ‰¯l‰’€°řĴrŠ™˜BˆÞTxr[tޏĻN_yŸX`biN™Ku…P›£k‚ZĮ—¦[ºxÆÀdhŽĹŀUÈƗCw’áZħÄŭcÓ¥»NAw±qȥnD`{ChdÙFćš}¢‰A±Äj¨]ĊÕjŋ«×`VuÓś~_kŷVÝyh„“VkÄãPs”Oµ—fŸge‚Ň…µf@u_Ù ÙcŸªNªÙEojVx™T@†ãSefjlwH\\pŏäÀvŠŽlY†½d{†F~¦dyz¤PÜndsrhf‹HcŒvlwjFœ£G˜±DύƥY‡yϊu¹XikĿ¦ÏqƗǀOŜ¨LI|FRĂn sª|Cš˜zxAè¥bœfudTrFWÁ¹Am|˜ĔĕsķÆF‡´Nš‰}ć…UŠÕ@Áijſmužç’uð^ÊýowŒFzØÎĕNőžǏȎôªÌŒDŽàĀÄ˄ĞŀƒʀĀƘŸˮȬƬĊ°ƒUŸzou‡xe]}Ž…AyȑW¯ÌmK‡“Q]‹Īºif¸ÄX|sZt|½ÚUΠlkš^p{f¤lˆºlÆW –€A²˜PVܜPH”Êâ]ÎĈÌÜk´\\@qàsĔÄQºpRij¼èi†`¶—„bXƒrBgxfv»ŽuUiˆŒ^v~”J¬mVp´£Œ´VWrnP½ì¢BX‚¬h™ŠðX¹^TjVœŠriªj™tŊÄm€tPGx¸bgRšŽsT`ZozÆO]’ÒFô҆Oƒ‡ŊŒvŞ”p’cGŒêŠsx´DR–Œ{A†„EOr°Œ•žx|íœbˆ³Wm~DVjºéNN†Ëܲɶ­GƒxŷCStŸ}]ûō•SmtuÇÃĕN•™āg»šíT«u}ç½BĵÞʣ¥ëÊ¡Mێ³ãȅ¡ƋaǩÈÉQ‰†G¢·lG|›„tvgrrf«†ptęŘnŠÅĢr„I²¯LiØsPf˜_vĠd„xM prʹšL¤‹¤‡eˌƒÀđK“žïÙVY§]I‡óáĥ]ķ†Kˆ¥Œj|pŇ\\kzţ¦šnņäÔVĂîά|vW’®l¤èØr‚˜•xm¶ă~lÄƯĄ̈́öȄEÔ¤ØQĄ–Ą»ƢjȦOǺ¨ìSŖÆƬy”Qœv`–cwƒZSÌ®ü±DŽ]ŀç¬B¬©ńzƺŷɄeeOĨS’Œfm Ċ‚ƀP̎ēz©Ċ‚ÄÕÊmgŸÇsJ¥ƔˆŊśæ’΁Ñqv¿íUOµª‰ÂnĦÁ_½ä@ê텣P}Ġ[@gġ}g“ɊדûÏWXá¢užƻÌsNͽƎÁ§č՛AēeL³àydl›¦ĘVçŁpśdžĽĺſʃQíÜçÛġԏsĕ¬—Ǹ¯YßċġHµ ¡eå`ļƒrĉŘóƢFì“ĎWøxÊk†”ƈdƬv|–I|·©NqńRŀƒ¤é”eŊœŀ›ˆàŀU²ŕƀB‚Q£Ď}L¹Îk@©ĈuǰųǨ”Ú§ƈnTËÇéƟÊcfčŤ^Xm‡—HĊĕË«W·ċëx³ǔķÐċJā‚wİ_ĸ˜Ȁ^ôWr­°oú¬Ħ…ŨK~”ȰCĐ´Ƕ£’fNÎèâw¢XnŮeÂÆĶŽ¾¾xäLĴĘlļO¤ÒĨA¢Êɚ¨®‚ØCÔ ŬGƠ”ƦYĜ‡ĘÜƬDJ—g_ͥœ@čŅĻA“¶¯@wÎqC½Ĉ»NŸăëK™ďÍQ“Ùƫ[«Ãí•gßÔÇOÝáW‘ñuZ“¯ĥ€Ÿŕā¡ÑķJu¤E Ÿå¯°WKɱ_d_}}vyŸõu¬ï¹ÓU±½@gÏ¿rýD‰†g…Cd‰µ—°MFYxw¿CG£‹Rƛ½Õ{]L§{qqąš¿BÇƻğëšܭNJË|c²}Fµ}›ÙRsÓpg±ŠQNqǫŋRwŕnéÑÉKŸ†«SeYR…ŋ‹@{¤SJ}šD Ûǖ֍Ÿ]gr¡µŷjqWÛham³~S«“„›Þ]" + ] + ], + "encodeOffsets": [[[134456, 44547]]] + } + }, + { + "type": "Feature", + "id": "320000", + "properties": { + "id": "320000", + "cp": [119.767413, 33.041544], + "name": "江苏", + "childNum": 1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@cþÅPiŠ`ZŸRu¥É\\]~°ŽY`µ†Óƒ^phÁbnÀşúŽòa–ĬºTÖŒb‚˜e¦¦€{¸ZâćNpŒ©žHr|^ˆmjhŠSEb\\afv`sz^lkŽlj‹Ätg‹¤D˜­¾Xš¿À’|ДiZ„ȀåB·î}GL¢õcßjaŸyBFµÏC^ĭ•cÙt¿sğH]j{s©HM¢ƒQnDÀ©DaÜތ·jgàiDbPufjDk`dPOîƒhw¡ĥ‡¥šG˜ŸP²ĐobºrY†„î¶aHŢ´ ]´‚rılw³r_{£DB_Ûdåuk|ˆŨ¯F Cºyr{XFy™e³Þċ‡¿Â™kĭB¿„MvÛpm`rÚã”@ƹhågËÖƿxnlč¶Åì½Ot¾dJlŠVJʜǀœŞqvnOŠ^ŸJ”Z‘ż·Q}ê͎ÅmµÒ]Žƍ¦Dq}¬R^èĂ´ŀĻĊIԒtžIJyQŐĠMNtœR®òLh‰›Ěs©»œ}OӌGZz¶A\\jĨFˆäOĤ˜HYš†JvÞHNiÜaϚɖnFQlšNM¤ˆB´ĄNöɂtp–Ŭdf先‹qm¿QûŠùއÚb¤uŃJŴu»¹Ą•lȖħŴw̌ŵ²ǹǠ͛hĭłƕrçü±Y™xci‡tğ®jű¢KOķ•Coy`å®VTa­_Ā]ŐÝɞï²ʯÊ^]afYǸÃĆēĪȣJđ͍ôƋĝÄ͎ī‰çÛɈǥ£­ÛmY`ó£Z«§°Ó³QafusNıDž_k}¢m[ÝóDµ—¡RLčiXy‡ÅNïă¡¸iĔϑNÌŕoēdōîåŤûHcs}~Ûwbù¹£¦ÓCt‹OPrƒE^ÒoŠg™ĉIµžÛÅʹK…¤½phMŠü`o怆ŀ" + ], + "encodeOffsets": [[121740, 32276]] + } + }, + { + "type": "Feature", + "id": "330000", + "properties": { + "id": "330000", + "cp": [120.153576, 29.287459], + "name": "浙江", + "childNum": 45 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@E^dQ]K"], + ["@@jX^j‡"], + ["@@sfŠbU‡"], + ["@@qP\\xz[ck"], + ["@@‘Rƒ¢‚FX}°[s_"], + ["@@Cbœ\\—}"], + ["@@e|v\\la{u"], + ["@@v~u}"], + ["@@QxÂF¯}"], + ["@@¹nŒvÞs¯o"], + ["@@rSkUEj"], + ["@@bi­ZŒP"], + ["@@p[}INf"], + ["@@À¿€"], + ["@@¹dnbŒ…"], + ["@@rSŸBnR"], + ["@@g~h}"], + ["@@FlEk"], + ["@@OdPc"], + ["@@v[u\\"], + ["@@FjâL~wyoo~›sµL–\\"], + ["@@¬e¹aNˆ"], + ["@@\\nÔ¡q]L³ë\\ÿ®ŒQ֎"], + ["@@ÊA­©[¬"], + ["@@KxŒv­"], + ["@@@hlIk]"], + ["@@pW{o||j"], + ["@@Md|_mC"], + ["@@¢…X£ÏylD¼XˆtH"], + ["@@hlÜ[LykAvyfw^Ež›¤"], + ["@@fp¤Mus“R"], + ["@@®_ma~•LÁ¬šZ"], + ["@@iM„xZ"], + ["@@ZcYd"], + ["@@Z~dOSo|A¿qZv"], + ["@@@`”EN¡v"], + ["@@|–TY{"], + ["@@@n@m"], + ["@@XWkCT\\"], + ["@@ºwšZRkĕWO¢"], + ["@@™X®±Grƪ\\ÔáXq{‹"], + ["@@ůTG°ĄLHm°UC‹"], + [ + "@@¤Ž€aÜx~}dtüGæţŎíĔcŖpMËВj碷ðĄÆMzˆjWKĎ¢Q¶˜À_꒔_Bı€i«pZ€gf€¤Nrq]§ĂN®«H±‡yƳí¾×ŸīàLłčŴǝĂíÀBŖÕªˆŠÁŖHŗʼnåqûõi¨hÜ·ƒñt»¹ýv_[«¸m‰YL¯‰Qª…mĉÅdMˆ•gÇjcº«•ęœ¬­K­´ƒB«Âącoċ\\xKd¡gěŧ«®á’[~ıxu·Å”KsËɏc¢Ù\\ĭƛëbf¹­ģSƒĜkáƉÔ­ĈZB{ŠaM‘µ‰fzʼnfåÂŧįƋǝÊĕġć£g³ne­ą»@­¦S®‚\\ßðCšh™iqªĭiAu‡A­µ”_W¥ƣO\\lċĢttC¨£t`ˆ™PZäuXßBs‡Ļyek€OđġĵHuXBšµ]׌‡­­\\›°®¬F¢¾pµ¼kŘó¬Wät’¸|@ž•L¨¸µr“ºù³Ù~§WI‹ŸZWŽ®’±Ð¨ÒÉx€`‰²pĜ•rOògtÁZ}þÙ]„’¡ŒŸFK‚wsPlU[}¦Rvn`hq¬\\”nQ´ĘRWb”‚_ rtČFI֊kŠŠĦPJ¶ÖÀÖJĈĄTĚòžC ²@Pú…Øzœ©PœCÈÚœĒ±„hŖ‡l¬â~nm¨f©–iļ«m‡nt–u†ÖZÜÄj“ŠLŽ®E̜Fª²iÊxبžIÈhhst" + ], + ["@@o\\V’zRZ}y"], + ["@@†@°¡mۛGĕ¨§Ianá[ýƤjfæ‡ØL–•äGr™"] + ], + "encodeOffsets": [ + [[125592, 31553]], + [[125785, 31436]], + [[125729, 31431]], + [[125513, 31380]], + [[125223, 30438]], + [[125115, 30114]], + [[124815, 29155]], + [[124419, 28746]], + [[124095, 28635]], + [[124005, 28609]], + [[125000, 30713]], + [[125111, 30698]], + [[125078, 30682]], + [[125150, 30684]], + [[124014, 28103]], + [[125008, 31331]], + [[125411, 31468]], + [[125329, 31479]], + [[125626, 30916]], + [[125417, 30956]], + [[125254, 30976]], + [[125199, 30997]], + [[125095, 31058]], + [[125083, 30915]], + [[124885, 31015]], + [[125218, 30798]], + [[124867, 30838]], + [[124755, 30788]], + [[124802, 30809]], + [[125267, 30657]], + [[125218, 30578]], + [[125200, 30562]], + [[124968, 30474]], + [[125167, 30396]], + [[124955, 29879]], + [[124714, 29781]], + [[124762, 29462]], + [[124325, 28754]], + [[123990, 28459]], + [[125366, 31477]], + [[125115, 30363]], + [[125369, 31139]], + [[122495, 31878]], + [[125329, 30690]], + [[125192, 30787]] + ] + } + }, + { + "type": "Feature", + "id": "340000", + "properties": { "id": "340000", "cp": [117.283042, 31.26119], "name": "安徽", "childNum": 3 }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@^iuLX^"], + ["@@‚e©Ehl"], + [ + "@@°ZÆëϵmkǀwÌÕæhºgBĝâqÙĊz›ÖgņtÀÁÊÆá’hEz|WzqD¹€Ÿ°E‡ŧl{ævÜcA`¤C`|´qžxIJkq^³³ŸGšµbƒíZ…¹qpa±ď OH—¦™Ħˆx¢„gPícOl_iCveaOjCh߸i݋bÛªCC¿€m„RV§¢A|t^iĠGÀtÚs–d]ĮÐDE¶zAb àiödK¡~H¸íæAžǿYƒ“j{ď¿‘™À½W—®£ChŒÃsiŒkkly]_teu[bFa‰Tig‡n{]Gqªo‹ĈMYá|·¥f¥—őaSÕė™NµñĞ«ImŒ_m¿Âa]uĜp …Z_§{Cƒäg¤°r[_Yj‰ÆOdý“[ŽI[á·¥“Q_n‡ùgL¾mv™ˊBÜÆ¶ĊJhšp“c¹˜O]iŠ]œ¥ jtsggJǧw×jÉ©±›EFˍ­‰Ki”ÛÃÕYv…s•ˆm¬njĻª•§emná}k«ŕˆƒgđ²Ù›DǤ›í¡ªOy›†×Où±@DŸñSęćăÕIÕ¿IµĥO‰‰jNÕËT¡¿tNæŇàåyķrĕq§ÄĩsWÆßŽF¶žX®¿‰mŒ™w…RIޓfßoG‘³¾©uyH‘į{Ɓħ¯AFnuP…ÍÔzšŒV—dàôº^Ðæd´€‡oG¤{S‰¬ćxã}›ŧ×Kǥĩ«žÕOEзÖdÖsƘѨ[’Û^Xr¢¼˜§xvěƵ`K”§ tÒ´Cvlo¸fzŨð¾NY´ı~ÉĔē…ßúLÃϖ_ÈÏ|]ÂÏFl”g`bšežž€n¾¢pU‚h~ƴ˶_‚r sĄ~cž”ƈ]|r c~`¼{À{ȒiJjz`îÀT¥Û³…]’u}›f…ïQl{skl“oNdŸjŸäËzDvčoQŠďHI¦rb“tHĔ~BmlRš—V_„ħTLnñH±’DžœL‘¼L˜ªl§Ťa¸ŒĚlK²€\\RòvDcÎJbt[¤€D@®hh~kt°ǾzÖ@¾ªdb„YhüóZ ň¶vHrľ\\ʗJuxAT|dmÀO„‹[ÃԋG·ĚąĐlŪÚpSJ¨ĸˆLvÞcPæķŨŽ®mАˆálŸwKhïgA¢ųƩޖ¤OȜm’°ŒK´" + ] + ], + "encodeOffsets": [[[121722, 32278]], [[119475, 30423]], [[119168, 35472]]] + } + }, + { + "type": "Feature", + "id": "350000", + "properties": { + "id": "350000", + "cp": [118.306239, 26.075302], + "name": "福建", + "childNum": 18 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@“zht´‡]"], + ["@@aj^~ĆG—©O"], + ["@@ed¨„C}}i"], + ["@@@vˆPGsQ"], + ["@@‰sBz‚ddW]Q"], + ["@@SލQ“{"], + ["@@NŽVucW"], + ["@@qptBAq"], + ["@@‰’¸[mu"], + ["@@Q\\pD]_"], + ["@@jSwUadpF"], + ["@@eXª~ƒ•"], + ["@@AjvFso"], + ["@@fT–›_Çí\\Ÿ™—v|ba¦jZÆy€°"], + ["@@IjJi"], + ["@@wJI€ˆxš«¼AoNe{M­"], + ["@@K‰±¡Óˆ”ČäeZ"], + [ + "@@k¡¹Eh~c®wBk‹UplÀ¡I•~Māe£bN¨gZý¡a±Öcp©PhžI”Ÿ¢Qq…ÇGj‹|¥U™ g[Ky¬ŏ–v@OpˆtÉEŸF„\\@ åA¬ˆV{Xģ‰ĐBy…cpě…¼³Ăp·¤ƒ¥o“hqqÚ¡ŅLsƒ^ᗞ§qlŸÀhH¨MCe»åÇGD¥zPO£čÙkJA¼ß–ėu›ĕeûҍiÁŧSW¥˜QŠûŗ½ùěcݧSùĩąSWó«íęACµ›eR—åǃRCÒÇZÍ¢‹ź±^dlsŒtjD¸•‚ZpužÔâÒH¾oLUêÃÔjjēò´ĄW‚ƛ…^Ñ¥‹ĦŸ@Çò–ŠmŒƒOw¡õyJ†yD}¢ďÑÈġfŠZd–a©º²z£šN–ƒjD°Ötj¶¬ZSÎ~¾c°¶Ðm˜x‚O¸¢Pl´žSL|¥žA†ȪĖM’ņIJg®áIJČĒü` ŽQF‡¬h|ÓJ@zµ |ê³È ¸UÖŬŬÀEttĸr‚]€˜ðŽM¤ĶIJHtÏ A’†žĬkvsq‡^aÎbvŒd–™fÊòSD€´Z^’xPsÞrv‹ƞŀ˜jJd×ŘÉ ®A–ΦĤd€xĆqAŒ†ZR”ÀMźŒnĊ»ŒİÐZ— YX–æJŠyĊ²ˆ·¶q§·–K@·{s‘Xãô«lŗ¶»o½E¡­«¢±¨Yˆ®Ø‹¶^A™vWĶGĒĢžPlzfˆļŽtàAvWYãšO_‡¤sD§ssČġ[kƤPX¦Ž`¶“ž®ˆBBvĪjv©šjx[L¥àï[F…¼ÍË»ğV`«•Ip™}ccÅĥZE‹ãoP…´B@ŠD—¸m±“z«Ƴ—¿å³BRضˆœWlâþäą`“]Z£Tc— ĹGµ¶H™m@_©—kŒ‰¾xĨ‡ôȉðX«½đCIbćqK³Á‹Äš¬OAwã»aLʼn‡ËĥW[“ÂGI—ÂNxij¤D¢ŽîĎÎB§°_JœGsƒ¥E@…¤uć…P‘å†cuMuw¢BI¿‡]zG¹guĮck\\_" + ] + ], + "encodeOffsets": [ + [[123250, 27563]], + [[122541, 27268]], + [[123020, 27189]], + [[122916, 27125]], + [[122887, 26845]], + [[122808, 26762]], + [[122568, 25912]], + [[122778, 26197]], + [[122515, 26757]], + [[122816, 26587]], + [[123388, 27005]], + [[122450, 26243]], + [[122578, 25962]], + [[121255, 25103]], + [[120987, 24903]], + [[122339, 25802]], + [[121042, 25093]], + [[122439, 26024]] + ] + } + }, + { + "type": "Feature", + "id": "360000", + "properties": { + "id": "360000", + "cp": [115.592151, 27.676493], + "name": "江西", + "childNum": 1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@ĢĨƐgÂMD~ņªe^\\^§„ý©j׍cZ†Ø¨zdÒa¶ˆlҍJŒìõ`oz÷@¤u޸´†ôęöY¼‰HČƶajlÞƩ¥éZ[”|h}^U Œ ¥p„ĄžƦO lt¸Æ €Q\\€ŠaÆ|CnÂOjt­ĚĤd’ÈŒF`’¶„@Ð딠¦ōҞ¨Sêv†HĢûXD®…QgėWiØPÞìºr¤dž€NĠ¢l–•ĄtZoœCƞÔºCxrpĠV®Ê{f_Y`_ƒeq’’®Aot`@o‚DXfkp¨|Šs¬\\D‘ÄSfè©Hn¬…^DhÆyøJh“ØxĢĀLʈ„ƠPżċĄwȠ̦G®ǒĤäTŠÆ~ĦwŠ«|TF¡Šn€c³Ïå¹]ĉđxe{ÎӐ†vOEm°BƂĨİ|G’vz½ª´€H’àp”eJ݆Qšxn‹ÀŠW­žEµàXÅĪt¨ÃĖrÄwÀFÎ|ňÓMå¼ibµ¯»åDT±m[“r«_gŽmQu~¥V\\OkxtL E¢‹ƒ‘Ú^~ýê‹Pó–qo슱_Êw§ÑªåƗ⼋mĉŹ‹¿NQ“…YB‹ąrwģcÍ¥B•Ÿ­ŗÊcØiI—žƝĿuŒqtāwO]‘³YCñTeɕš‹caub͈]trlu€ī…B‘ПGsĵıN£ï—^ķqss¿FūūV՟·´Ç{éĈý‰ÿ›OEˆR_ŸđûIċâJh­ŅıN‘ȩĕB…¦K{Tk³¡OP·wn—µÏd¯}½TÍ«YiµÕsC¯„iM•¤™­•¦¯P|ÿUHv“he¥oFTu‰õ\\ŽOSs‹MòđƇiaºćXŸĊĵà·çhƃ÷ǜ{‘ígu^›đg’m[×zkKN‘¶Õ»lčÓ{XSƉv©_ÈëJbVk„ĔVÀ¤P¾ºÈMÖxlò~ªÚàGĂ¢B„±’ÌŒK˜y’áV‡¼Ã~­…`g›ŸsÙfI›Ƌlę¹e|–~udjˆuTlXµf`¿JdŠ[\\˜„L‚‘²" + ], + "encodeOffsets": [[116689, 26234]] + } + }, + { + "type": "Feature", + "id": "370000", + "properties": { + "id": "370000", + "cp": [118.000923, 36.275807], + "name": "山东", + "childNum": 13 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@Xjd]{K"], + ["@@itbFHy"], + ["@@HlGk"], + ["@@T‚ŒGŸy"], + ["@@K¬˜•‹U"], + ["@@WdXc"], + ["@@PtOs"], + ["@@•LnXhc"], + ["@@ppVƒu]Or"], + ["@@cdzAUa"], + ["@@udRhnCI‡"], + ["@@ˆoIƒpR„"], + [ + "@@Ľč{fzƤî’Kš–ÎMĮ]†—ZFˆ½Y]â£ph’™š¶¨râøÀ†ÎǨ¤^ºÄ”Gzˆ~grĚĜlĞÆ„LĆdž¢Îo¦–cv“Kb€gr°Wh”mZp ˆL]LºcU‰Æ­n”żĤÌǜbAnrOAœ´žȊcÀbƦUØrĆUÜøœĬƞ†š˜Ez„VL®öØBkŖÝĐ˹ŧ̄±ÀbÎɜnb²ĦhņBĖ›žįĦåXćì@L¯´ywƕCéõė ƿ¸‘lµ¾Z|†ZWyFYŸ¨Mf~C¿`€à_RÇzwƌfQnny´INoƬˆèôº|sT„JUš›‚L„îVj„ǎ¾Ē؍‚Dz²XPn±ŴPè¸ŔLƔÜƺ_T‘üÃĤBBċȉöA´fa„˜M¨{«M`‡¶d¡ô‰Ö°šmȰBÔjjŒ´PM|”c^d¤u•ƒ¤Û´Œä«ƢfPk¶Môlˆ]Lb„}su^ke{lC‘…M•rDŠÇ­]NÑFsmoõľH‰yGă{{çrnÓE‰‹ƕZGª¹Fj¢ïW…uøCǷ돡ąuhÛ¡^Kx•C`C\\bÅxì²ĝÝ¿_N‰īCȽĿåB¥¢·IŖÕy\\‡¹kx‡Ã£Č×GDyÕ¤ÁçFQ¡„KtŵƋ]CgÏAùSed‡cÚź—ŠuYfƒyMmhUWpSyGwMPqŀ—›Á¼zK›¶†G•­Y§Ëƒ@–´śÇµƕBmœ@Io‚g——Z¯u‹TMx}C‘‰VK‚ï{éƵP—™_K«™pÛÙqċtkkù]gŽ‹Tğwo•ɁsMõ³ă‡AN£™MRkmEʕč™ÛbMjÝGu…IZ™—GPģ‡ãħE[iµBEuŸDPԛ~ª¼ętŠœ]ŒûG§€¡QMsğNPŏįzs£Ug{đJĿļā³]ç«Qr~¥CƎÑ^n¶ÆéÎR~ݏY’I“] P‰umŝrƿ›‰›Iā‹[x‰edz‹L‘¯v¯s¬ÁY…~}…ťuٌg›ƋpÝĄ_ņī¶ÏSR´ÁP~ž¿Cyžċßdwk´Ss•X|t‰`Ä Èð€AªìÎT°¦Dd–€a^lĎDĶÚY°Ž`ĪŴǒˆ”àŠv\\ebŒZH„ŖR¬ŢƱùęO•ÑM­³FۃWp[ƒ" + ] + ], + "encodeOffsets": [ + [[123806, 39303]], + [[123821, 39266]], + [[123742, 39256]], + [[123702, 39203]], + [[123649, 39066]], + [[123847, 38933]], + [[123580, 38839]], + [[123894, 37288]], + [[123043, 36624]], + [[123344, 38676]], + [[123522, 38857]], + [[123628, 38858]], + [[118260, 36742]] + ] + } + }, + { + "type": "Feature", + "id": "410000", + "properties": { + "id": "410000", + "cp": [113.665412, 33.757975], + "name": "河南", + "childNum": 1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@•ýL™ùµP³swIÓxcŢĞð†´E®žÚPt†ĴXØx¶˜@«ŕŕQGƒ‹Yfa[şu“ßǩ™đš_X³ijÕčC]kbc•¥CS¯ëÍB©÷‹–³­Siˆ_}m˜YTtž³xlàcȂzÀD}ÂOQ³ÐTĨ¯†ƗòËŖ[hœł‹Ŧv~††}ÂZž«¤lPǕ£ªÝŴÅR§ØnhcŒtâk‡nύ­ľŹUÓÝdKuķ‡I§oTũÙďkęĆH¸ÓŒ\\ăŒ¿PcnS{wBIvɘĽ[GqµuŸŇôYgûƒZcaŽ©@½Õǽys¯}lgg@­C\\£as€IdÍuCQñ[L±ęk·‹ţb¨©kK—’»›KC²‘òGKmĨS`ƒ˜UQ™nk}AGē”sqaJ¥ĐGR‰ĎpCuÌy ã iMc”plk|tRk†ðœev~^‘´†¦ÜŽSí¿_iyjI|ȑ|¿_»d}qŸ^{“Ƈdă}Ÿtqµ`Ƴĕg}V¡om½fa™Ço³TTj¥„tĠ—Ry”K{ùÓjuµ{t}uËR‘iŸvGŠçJFjµŠÍyqΘàQÂFewixGw½Yŷpµú³XU›½ġy™łå‰kÚwZXˆ·l„¢Á¢K”zO„Λ΀jc¼htoDHr…|­J“½}JZ_¯iPq{tę½ĕ¦Zpĵø«kQ…Ťƒ]MÛfaQpě±ǽ¾]u­Fu‹÷nƒ™čįADp}AjmcEǒaª³o³ÆÍSƇĈÙDIzˑ赟^ˆKLœ—i—Þñ€[œƒaA²zz‰Ì÷Dœ|[šíijgf‚ÕÞd®|`ƒĆ~„oĠƑô³Ŋ‘D×°¯CsŠøÀ«ì‰UMhTº¨¸ǡîS–Ô„DruÂÇZ•ÖEŽ’vPZ„žW”~؋ÐtĄE¢¦Ðy¸bŠô´oŬ¬Ž²Ês~€€]®tªašpŎJ¨Öº„_ŠŔ–`’Ŗ^Ѝ\\Ĝu–”~m²Ƹ›¸fW‰ĦrƔ}Î^gjdfÔ¡J}\\n C˜¦þWxªJRÔŠu¬ĨĨmF†dM{\\d\\ŠYÊ¢ú@@¦ª²SŠÜsC–}fNècbpRmlØ^g„d¢aÒ¢CZˆZxvÆ¶N¿’¢T@€uCœ¬^ĊðÄn|žlGl’™Rjsp¢ED}€Fio~ÔNŽ‹„~zkĘHVsDzßjƒŬŒŠŢ`Pûàl¢˜\\ÀœEhŽİgÞē X¼Pk–„|m" + ], + "encodeOffsets": [[118256, 37017]] + } + }, + { + "type": "Feature", + "id": "420000", + "properties": { + "id": "420000", + "cp": [113.298572, 30.684355], + "name": "湖北", + "childNum": 3 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@AB‚"], + ["@@lskt"], + [ + "@@¾«}{ra®pîÃ\\™›{øCŠËyyB±„b\\›ò˜Ý˜jK›‡L ]ĎĽÌ’JyÚCƈćÎT´Å´pb©È‘dFin~BCo°BĎĚømvŒ®E^vǾ½Ĝ²Ro‚bÜeNŽ„^ĺ£R†¬lĶ÷YoĖ¥Ě¾|sOr°jY`~I”¾®I†{GqpCgyl{‡£œÍƒÍyPL“¡ƒ¡¸kW‡xYlÙæŠšŁĢzœ¾žV´W¶ùŸo¾ZHxjwfx„GNÁ•³Xéæl¶‰EièIH‰ u’jÌQ~v|sv¶Ôi|ú¢Fh˜Qsğ¦ƒSiŠBg™ÐE^ÁÐ{–čnOÂȞUÎóĔ†ÊēIJ}Z³½Mŧïeyp·uk³DsѨŸL“¶_œÅuèw»—€¡WqÜ]\\‘Ò§tƗcÕ¸ÕFÏǝĉăxŻČƟO‡ƒKÉġÿ×wg”÷IÅzCg†]m«ªGeçÃTC’«[‰t§{loWeC@ps_Bp‘­r‘„f_``Z|ei¡—oċMqow€¹DƝӛDYpûs•–‹Ykıǃ}s¥ç³[§ŸcYЧHK„«Qy‰]¢“wwö€¸ïx¼ņ¾Xv®ÇÀµRĠЋžHMž±cÏd„ƒǍũȅȷ±DSyúĝ£ŤĀàtÖÿï[îb\\}pĭÉI±Ñy…¿³x¯N‰o‰|¹H™ÏÛm‹júË~Tš•u˜ęjCöAwě¬R’đl¯ Ñb­‰ŇT†Ŀ_[Œ‘IčĄʿnM¦ğ\\É[T·™k¹œ©oĕ@A¾w•ya¥Y\\¥Âaz¯ãÁ¡k¥ne£Ûw†E©Êō¶˓uoj_Uƒ¡cF¹­[Wv“P©w—huÕyBF“ƒ`R‹qJUw\\i¡{jŸŸEPïÿ½fć…QÑÀQ{ž‚°‡fLԁ~wXg—ītêݾ–ĺ‘Hdˆ³fJd]‹HJ²…E€ƒoU¥†HhwQsƐ»Xmg±çve›]Dm͂PˆoCc¾‹_h”–høYrŊU¶eD°Č_N~øĹĚ·`z’]Äþp¼…äÌQŒv\\rCŒé¾TnkžŐڀÜa‡“¼ÝƆ̶Ûo…d…ĔňТJq’Pb ¾|JŒ¾fXŠƐîĨ_Z¯À}úƲ‹N_ĒĊ^„‘ĈaŐyp»CÇĕKŠšñL³ŠġMŒ²wrIÒŭxjb[œžn«øœ˜—æˆàƒ ^²­h¯Ú€ŐªÞ¸€Y²ĒVø}Ā^İ™´‚LŠÚm„¥ÀJÞ{JVŒųÞŃx×sxxƈē ģMř–ÚðòIf–Ċ“Œ\\Ʈ±ŒdʧĘD†vČ_Àæ~DŒċ´A®µ†¨ØLV¦êHÒ¤" + ] + ], + "encodeOffsets": [[[113712, 34000]], [[115612, 30507]], [[113649, 34054]]] + } + }, + { + "type": "Feature", + "id": "430000", + "properties": { "id": "430000", "cp": [111.782279, 28.09409], "name": "湖南", "childNum": 3 }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@—n„FTs"], + ["@@ßÅÆá‰½ÔXr—†CO™“…ËR‘ïÿĩ­TooQyšÓ[‹ŅBE¬–ÎÓXa„į§Ã¸G °ITxp‰úxÚij¥Ïš–̾ŠedžÄ©ĸG…œàGh‚€M¤–Â_U}Ċ}¢pczfŠþg¤€”ÇòAV‘‹M"], + [ + "@@©K—ƒA·³CQ±Á«³BUŠƑ¹AŠtćOw™D]ŒJiØSm¯b£‘ylƒ›X…HËѱH•«–‘C^õľA–Å§¤É¥„ïyuǙuA¢^{ÌC´­¦ŷJ£^[†“ª¿‡ĕ~•Ƈ…•N… skóā‡¹¿€ï]ă~÷O§­@—Vm¡‹Qđ¦¢Ĥ{ºjԏŽŒª¥nf´•~ÕoŸž×Ûą‹MąıuZœmZcÒ IJβSÊDŽŶ¨ƚƒ’CÖŎªQؼrŭŽ­«}NÏürʬŒmjr€@ĘrTW ­SsdHzƓ^ÇÂyUi¯DÅYlŹu{hTœ}mĉ–¹¥ě‰Dÿë©ıÓ[Oº£ž“¥ót€ł¹MՄžƪƒ`Pš…Di–ÛUоÅ‌ìˆU’ñB“È£ýhe‰dy¡oċ€`pfmjP~‚kZa…ZsÐd°wj§ƒ@€Ĵ®w~^‚kÀÅKvNmX\\¨a“”сqvíó¿F„¤¡@ũÑVw}S@j}¾«pĂr–ªg àÀ²NJ¶¶Dô…K‚|^ª†Ž°LX¾ŴäPᜣEXd›”^¶›IJÞܓ~‘u¸ǔ˜Ž›MRhsR…e†`ÄofIÔ\\Ø  i”ćymnú¨cj ¢»–GČìƊÿШXeĈ¾Oð Fi ¢|[jVxrIQŒ„_E”zAN¦zLU`œcªx”OTu RLÄ¢dV„i`p˔vŎµªÉžF~ƒØ€d¢ºgİàw¸Áb[¦Zb¦–z½xBĖ@ªpº›šlS¸Ö\\Ĕ[N¥ˀmĎă’J\\‹ŀ`€…ňSڊĖÁĐiO“Ĝ«BxDõĚiv—ž–S™Ì}iùŒžÜnšÐºGŠ{Šp°M´w†ÀÒzJ²ò¨ oTçüöoÛÿñŽőФ‚ùTz²CȆȸǎۃƑÐc°dPÎŸğ˶[Ƚu¯½WM¡­Éž“’B·rížnZŸÒ `‡¨GA¾\\pē˜XhÆRC­üWGġu…T靧Ŏѝ©ò³I±³}_‘‹EÃħg®ęisÁPDmÅ{‰b[Rşs·€kPŸŽƥƒóRo”O‹ŸVŸ~]{g\\“êYƪ¦kÝbiċƵŠGZ»Ěõ…ó·³vŝž£ø@pyö_‹ëŽIkѵ‡bcѧy…×dY؎ªiþž¨ƒ[]f]Ņ©C}ÁN‡»hĻħƏ’ĩ" + ] + ], + "encodeOffsets": [[[115640, 30489]], [[112543, 27312]], [[116690, 26230]]] + } + }, + { + "type": "Feature", + "id": "440000", + "properties": { + "id": "440000", + "cp": [113.280637, 23.125178], + "name": "广东", + "childNum": 24 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@QdˆAua"], + ["@@ƒlxDLo"], + ["@@sbhNLo"], + ["@@Ă āŸ"], + ["@@WltO[["], + ["@@Krœ]S"], + ["@@e„„I]y"], + ["@@I|„Mym"], + ["@@ƒÛ³LSŒž¼Y"], + ["@@nvºB–ëui©`¾"], + ["@@zdšÛ›Jw®"], + ["@@†°…¯"], + ["@@a yAª¸ËJIx،@€ĀHAmßV¡o•fu•o"], + ["@@šs‰ŗÃÔėAƁ›ZšÄ ~°ČP‚‹äh"], + ["@@‹¶Ý’Ì‚vmĞh­ı‡Q"], + ["@@HœŠdSjĒ¢D}war…“u«ZqadYM"], + ["@@elŒ\\LqqU"], + ["@@~rMo\\"], + ["@@f„^ƒC"], + ["@@øPªoj÷ÍÝħXČx”°Q¨ıXNv"], + ["@@gÇƳˆŽˆ”oˆŠˆ[~tly"], + ["@@E–ÆC¿‘"], + ["@@OŽP"], + [ + "@@w‹†đóg‰™ĝ—[³‹¡VÙæÅöM̳¹pÁaËýý©D©Ü“JŹƕģGą¤{Ùū…ǘO²«BƱéA—Ò‰ĥ‡¡«BhlmtÃPµyU¯uc“d·w_bŝcīímGOŽ|KP’ȏ‡ŹãŝIŕŭŕ@Óoo¿ē‹±ß}Ž…ŭ‚ŸIJWÈCőâUâǙI›ğʼn©I›ijEׅÁ”³Aó›wXJþ±ÌŒÜӔĨ£L]ĈÙƺZǾĆĖMĸĤfŒÎĵl•ŨnȈ‘ĐtF”Š–FĤ–‚êk¶œ^k°f¶gŠŽœ}®Fa˜f`vXŲxl˜„¦–ÔÁ²¬ÐŸ¦pqÊ̲ˆi€XŸØRDÎ}†Ä@ZĠ’s„x®AR~®ETtĄZ†–ƈfŠŠHâÒÐA†µ\\S¸„^wĖkRzŠalŽŜ|E¨ÈNĀňZTŒ’pBh£\\ŒĎƀuXĖtKL–¶G|Ž»ĺEļĞ~ÜĢÛĊrˆO˜Ùîvd]nˆ¬VœÊĜ°R֟pM††–‚ƂªFbwžEÀˆ˜©Œž\\…¤]ŸI®¥D³|ˎ]CöAŤ¦…æ’´¥¸Lv¼€•¢ĽBaô–F~—š®²GÌҐEY„„œzk¤’°ahlV՞I^‹šCxĈPŽsB‰ƒºV‰¸@¾ªR²ĨN]´_eavSi‡vc•}p}Đ¼ƌkJœÚe thœ†_¸ ºx±ò_xN›Ë‹²‘@ƒă¡ßH©Ùñ}wkNÕ¹ÇO½¿£ĕ]ly_WìIžÇª`ŠuTÅxYĒÖ¼k֞’µ‚MžjJÚwn\\h‘œĒv]îh|’È›Ƅøègž¸Ķß ĉĈWb¹ƀdéƌNTtP[ŠöSvrCZžžaGuœbo´ŖÒÇА~¡zCI…özx¢„Pn‹•‰Èñ @ŒĥÒ¦†]ƞŠV}³ăĔñiiÄÓVépKG½Ä‘ÓávYo–C·sit‹iaÀy„ŧΡÈYDÑům}‰ý|m[węõĉZÅxUO}÷N¹³ĉo_qtă“qwµŁYلǝŕ¹tïÛUïmRCº…ˆĭ|µ›ÕÊK™½R‘ē ó]‘–GªęAx–»HO£|ām‡¡diď×YïYWªʼnOeÚtĐ«zđ¹T…ā‡úE™á²\\‹ķÍ}jYàÙÆſ¿Çdğ·ùTßÇţʄ¡XgWÀLJğ·¿ÃˆOj YÇ÷Qě‹i" + ] + ], + "encodeOffsets": [ + [[117381, 22988]], + [[116552, 22934]], + [[116790, 22617]], + [[116973, 22545]], + [[116444, 22536]], + [[116931, 22515]], + [[116496, 22490]], + [[116453, 22449]], + [[113301, 21439]], + [[118726, 21604]], + [[118709, 21486]], + [[113210, 20816]], + [[115482, 22082]], + [[113171, 21585]], + [[113199, 21590]], + [[115232, 22102]], + [[115739, 22373]], + [[115134, 22184]], + [[113056, 21175]], + [[119573, 21271]], + [[119957, 24020]], + [[115859, 22356]], + [[116561, 22649]], + [[116285, 22746]] + ] + } + }, + { + "type": "Feature", + "id": "450000", + "properties": { "id": "450000", "cp": [108.320004, 22.82402], "name": "广西", "childNum": 2 }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@H– TQ§•A"], + [ + "@@ĨʪƒLƒƊDÎĹĐCǦė¸zÚGn£¾›rªŀÜt¬@֛ڈSx~øOŒ˜ŶÐÂæȠ\\„ÈÜObĖw^oބLf¬°bI lTØB̈F£Ć¹gñĤaY“t¿¤VSñœK¸¤nM†¼‚JE±„½¸šŠño‹ÜCƆæĪ^ŠĚQÖ¦^‡ˆˆf´Q†üÜʝz¯šlzUĺš@쇀p¶n]sxtx¶@„~ÒĂJb©gk‚{°‚~c°`ԙ¬rV\\“la¼¤ôá`¯¹LC†ÆbŒxEræO‚v[H­˜„[~|aB£ÖsºdAĐzNÂðsŽÞƔ…Ĥªbƒ–ab`ho¡³F«èVloޤ™ÔRzpp®SŽĪº¨ÖƒºN…ij„d`’a”¦¤F³ºDÎńĀìŠCžĜº¦Ċ•~nS›|gźvZkCÆj°zVÈÁƔ]LÊFZg…čP­kini«‹qǀcz͔Y®¬Ů»qR×ō©DՄ‘§ƙǃŵTÉĩ±ŸıdÑnYY›IJvNĆÌØÜ Öp–}e³¦m‹©iÓ|¹Ÿħņ›|ª¦QF¢Â¬ʖovg¿em‡^ucà÷gՎuŒíÙćĝ}FϼĹ{µHK•sLSđƃr‹č¤[Ag‘oS‹ŇYMÿ§Ç{Fśbky‰lQxĕƒ]T·¶[B…ÑÏGáşşƇe€…•ăYSs­FQ}­Bƒw‘tYğÃ@~…C̀Q ×W‡j˱rÉ¥oÏ ±«ÓÂ¥•ƒ€k—ŽwWűŒmcih³K›~‰µh¯e]lµ›él•E쉕E“ďs‡’mǖŧē`ãògK_ÛsUʝ“ćğ¶hŒöŒO¤Ǜn³Žc‘`¡y‹¦C‘ez€YŠwa™–‘[ďĵűMę§]X˜Î_‚훘Û]é’ÛUćİÕBƣ±…dƒy¹T^džûÅÑŦ·‡PĻþÙ`K€¦˜…¢ÍeœĥR¿Œ³£[~Œäu¼dl‰t‚†W¸oRM¢ď\\zœ}Æzdvň–{ÎXF¶°Â_„ÒÂÏL©Ö•TmuŸ¼ãl‰›īkiqéfA„·Êµ\\őDc¥ÝF“y›Ôć˜c€űH_hL܋êĺШc}rn`½„Ì@¸¶ªVLŒŠhŒ‹\\•Ţĺk~ŽĠið°|gŒtTĭĸ^x‘vK˜VGréAé‘bUu›MJ‰VÃO¡…qĂXËS‰ģãlýàŸ_ju‡YÛÒB†œG^˜é֊¶§ŽƒEG”ÅzěƒƯ¤Ek‡N[kdåucé¬dnYpAyČ{`]þ¯T’bÜÈk‚¡Ġ•vŒàh„ÂƄ¢Jî¶²" + ] + ], + "encodeOffsets": [[[111707, 21520]], [[107619, 25527]]] + } + }, + { + "type": "Feature", + "id": "460000", + "properties": { "id": "460000", "cp": [109.83119, 19.031971], "name": "海南", "childNum": 1 }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@š¦Ŝil¢”XƦ‘ƞò–ïè§ŞCêɕrŧůÇąĻõ™·ĉ³œ̅kÇm@ċȧƒŧĥ‰Ľʉ­ƅſ“ȓÒ˦ŝE}ºƑ[ÍĜȋ gÎfǐÏĤ¨êƺ\\Ɔ¸ĠĎvʄȀœÐ¾jNðĀÒRŒšZdž™zÐŘΰH¨Ƣb²_Ġ " + ], + "encodeOffsets": [[112750, 20508]] + } + }, + { + "type": "Feature", + "id": "510000", + "properties": { + "id": "510000", + "cp": [104.065735, 30.659462], + "name": "四川", + "childNum": 2 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@LqKr"], + [ + "@@Š[ĻéV£ž_ţġñpG •réÏ·~ąSfy×͂·ºſƽiÍıƣıĻmHH}siaX@iǰÁÃ×t«ƒ­Tƒ¤J–JJŒyJ•ÈŠ`Ohߦ¡uËhIyCjmÿw…ZG……Ti‹SˆsO‰žB²ŸfNmsPaˆ{M{ŠõE‘^Hj}gYpaeuž¯‘oáwHjÁ½M¡pM“–uå‡mni{fk”\\oƒÎqCw†EZ¼K›ĝŠƒAy{m÷L‡wO×SimRI¯rK™õBS«sFe‡]fµ¢óY_ÆPRcue°Cbo׌bd£ŌIHgtrnyPt¦foaXďx›lBowz‹_{ÊéWiêE„GhܸºuFĈIxf®Ž•Y½ĀǙ]¤EyŸF²ċ’w¸¿@g¢§RGv»–áŸW`ÃĵJwi]t¥wO­½a[׈]`Ãi­üL€¦LabbTÀå’c}Íh™Æhˆ‹®BH€î|Ék­¤S†y£„ia©taį·Ɖ`ō¥Uh“O…ƒĝLk}©Fos‰´›Jm„µlŁu—…ø–nÑJWΪ–YÀïAetTžŅ‚ӍG™Ë«bo‰{ıwodƟ½ƒžOġܑµxàNÖ¾P²§HKv¾–]|•B‡ÆåoZ`¡Ø`ÀmºĠ~ÌЧnDž¿¤]wğ@sƒ‰rğu‰~‘Io”[é±¹ ¿žſđӉ@q‹gˆ¹zƱřaí°KtǤV»Ã[ĩǭƑ^ÇÓ@ỗs›Zϕ‹œÅĭ€Ƌ•ěpwDóÖሯneQˌq·•GCœýS]xŸ·ý‹q³•O՜Œ¶Qzßti{ř‰áÍÇWŝŭñzÇW‹pç¿JŒ™‚Xœĩè½cŒF–ÂLiVjx}\\N†ŇĖ¥Ge–“JA¼ÄHfÈu~¸Æ«dE³ÉMA|b˜Ò…˜ćhG¬CM‚õŠ„ƤąAvƒüV€éŀ‰_V̳ĐwQj´·ZeÈÁ¨X´Æ¡Qu·»Ÿ“˜ÕZ³ġqDo‰y`L¬gdp°şŠp¦ėìÅĮZްIä”h‚‘ˆzŠĵœf²å ›ĚрKp‹IN|‹„Ñz]ń……·FU×é»R³™MƒÉ»GM«€ki€™ér™}Ã`¹ăÞmȝnÁîRǀ³ĜoİzŔwǶVÚ£À]ɜ»ĆlƂ²Ġ…þTº·àUȞÏʦ¶†I’«dĽĢdĬ¿–»Ĕ׊h\\c¬†ä²GêëĤł¥ÀǿżÃÆMº}BÕĢyFVvw–ˆxBèĻĒ©Ĉ“tCĢɽŠȣ¦āæ·HĽî“ôNԓ~^¤Ɗœu„œ^s¼{TA¼ø°¢İªDè¾Ň¶ÝJ‘®Z´ğ~Sn|ªWÚ©òzPOȸ‚bð¢|‹øĞŠŒœŒQìÛÐ@Ğ™ǎRS¤Á§d…i“´ezÝúØã]Hq„kIŸþËQǦÃsǤ[E¬ÉŪÍxXƒ·ÖƁİlƞ¹ª¹|XÊwn‘ÆƄmÀêErĒtD®ċæcQƒ”E®³^ĭ¥©l}äQto˜ŖÜqƎkµ–„ªÔĻĴ¡@Ċ°B²Èw^^RsºT£ڿœQP‘JvÄz„^Đ¹Æ¯fLà´GC²‘dt˜­ĀRt¼¤ĦOðğfÔðDŨŁĞƘïžPȆ®âbMüÀXZ ¸£@Ś›»»QÉ­™]d“sÖ×_͖_ÌêŮPrĔĐÕGĂeZÜîĘqBhtO ¤tE[h|Y‹Ô‚ZśÎs´xº±UŒ’ñˆt|O’ĩĠºNbgþŠJy^dÂY Į„]Řz¦gC‚³€R`Šz’¢AjŒ¸CL„¤RÆ»@­Ŏk\\Ç´£YW}z@Z}‰Ã¶“oû¶]´^N‡Ò}èN‚ª–P˜Íy¹`S°´†ATe€VamdUĐwʄvĮÕ\\ƒu‹Æŗ¨Yp¹àZÂm™Wh{á„}WØǍ•Éüw™ga§áCNęÎ[ĀÕĪgÖɪX˜øx¬½Ů¦¦[€—„NΆL€ÜUÖ´òrÙŠxR^–†J˜k„ijnDX{Uƒ~ET{ļº¦PZc”jF²Ė@Žp˜g€ˆ¨“B{ƒu¨ŦyhoÚD®¯¢˜ WòàFΤ¨GDäz¦kŮPœġq˚¥À]€Ÿ˜eŽâÚ´ªKxī„Pˆ—Ö|æ[xäJÞĥ‚s’NÖ½ž€I†¬nĨY´®Ð—ƐŠ€mD™ŝuäđđEb…e’e_™v¡}ìęNJē}q”É埁T¯µRs¡M@}ůa†a­¯wvƉåZwž\\Z{åû^›" + ] + ], + "encodeOffsets": [[[108815, 30935]], [[110617, 31811]]] + } + }, + { + "type": "Feature", + "id": "520000", + "properties": { + "id": "520000", + "cp": [106.713478, 26.578343], + "name": "贵州", + "childNum": 3 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@†G\\†lY£‘in"], + ["@@q‚|ˆ‚mc¯tχVSÎ"], + [ + "@@hÑ£Is‡NgßH†›HªķÃh_¹ƒ¡ĝħń¦uيùŽgS¯JHŸ|sÝÅtÁïyMDč»eÕtA¤{b\\}—ƒG®u\\åPFq‹wÅaD…žK°ºâ_£ùbµ”mÁ‹ÛœĹM[q|hlaªāI}тƒµ@swtwm^oµˆD鼊yV™ky°ÉžûÛR…³‚‡eˆ‡¥]RՋěħ[ƅåÛDpŒ”J„iV™™‰ÂF²I…»mN·£›LbÒYb—WsÀbŽ™pki™TZĄă¶HŒq`……ĥ_JŸ¯ae«ƒKpÝx]aĕÛPƒÇȟ[ÁåŵÏő—÷Pw}‡TœÙ@Õs«ĿÛq©½œm¤ÙH·yǥĘĉBµĨÕnđ]K„©„œá‹ŸG纍§Õßg‡ǗĦTèƤƺ{¶ÉHÎd¾ŚÊ·OÐjXWrãLyzÉAL¾ę¢bĶėy_qMĔąro¼hĊžw¶øV¤w”²Ĉ]ʚKx|`ź¦ÂÈdr„cȁbe¸›`I¼čTF´¼Óýȃr¹ÍJ©k_șl³´_pН`oÒh޶pa‚^ÓĔ}D»^Xyœ`d˜[Kv…JPhèhCrĂĚÂ^Êƌ wˆZL­Ġ£šÁbrzOIl’MM”ĪŐžËr×ÎeŦŽtw|Œ¢mKjSǘňĂStÎŦEtqFT†¾†E쬬ôxÌO¢Ÿ KгŀºäY†„”PVgŎ¦Ŋm޼VZwVlŒ„z¤…ž£Tl®ctĽÚó{G­A‡ŒÇgeš~Αd¿æaSba¥KKûj®_ć^\\ؾbP®¦x^sxjĶI_Ä X‚⼕Hu¨Qh¡À@Ëô}ޱžGNìĎlT¸ˆ…`V~R°tbÕĊ`¸úÛtπFDu€[ƒMfqGH·¥yA‰ztMFe|R‚_Gk†ChZeÚ°to˜v`x‹b„ŒDnÐ{E}šZ˜è€x—†NEފREn˜[Pv@{~rĆAB§‚EO¿|UZ~ì„Uf¨J²ĂÝÆ€‚sª–B`„s¶œfvö¦ŠÕ~dÔq¨¸º»uù[[§´sb¤¢zþFœ¢Æ…Àhˆ™ÂˆW\\ıŽËI݊o±ĭŠ£þˆÊs}¡R]ŒěƒD‚g´VG¢‚j±®è†ºÃmpU[Á›‘Œëº°r›ÜbNu¸}Žº¼‡`ni”ºÔXĄ¤¼Ôdaµ€Á_À…†ftQQgœR—‘·Ǔ’v”}Ýלĵ]µœ“Wc¤F²›OĩųãW½¯K‚©…]€{†LóµCIµ±Mß¿hŸ•©āq¬o‚½ž~@i~TUxŪÒ¢@ƒ£ÀEîôruń‚”“‚b[§nWuMÆLl¿]x}ij­€½" + ] + ], + "encodeOffsets": [[[112158, 27383]], [[112105, 27474]], [[112095, 27476]]] + } + }, + { + "type": "Feature", + "id": "530000", + "properties": { + "id": "530000", + "cp": [101.512251, 24.740609], + "name": "云南", + "childNum": 1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@[„ùx½}ÑRH‘YīĺûsÍn‘iEoã½Ya²ė{c¬ĝg•ĂsA•ØÅwď‚õzFjw}—«Dx¿}UũlŸê™@•HÅ­F‰¨ÇoJ´Ónũuą¡Ã¢pÒŌ“Ø TF²‚xa²ËX€‚cʋlHîAßËŁkŻƑŷÉ©h™W­æßU‡“Ës¡¦}•teèÆ¶StǀÇ}Fd£j‹ĈZĆÆ‹¤T‚č\\Dƒ}O÷š£Uˆ§~ŃG™‚åŃDĝ¸œTsd¶¶Bªš¤u¢ŌĎo~t¾ÍŶÒtD¦Ú„iôö‰€z›ØX²ghįh½Û±¯€ÿm·zR¦Ɵ`ªŊÃh¢rOԍ´£Ym¼èêf¯ŪĽn„†cÚbŒw\\zlvWžªâˆ ¦g–mĿBş£¢ƹřbĥkǫßeeZkÙIKueT»sVesb‘aĕ  ¶®dNœĄÄpªyސ¼—„³BE˜®l‡ŽGœŭCœǶwêżĔÂe„pÍÀQƞpC„–¼ŲÈ­AÎô¶R„ä’Q^Øu¬°š_Èôc´¹ò¨P΢hlϦ´Ħ“Æ´sâDŽŲPnÊD^¯°’Upv†}®BP̪–jǬx–Söwlfòªv€qĸ|`H€­viļ€ndĜ­Ćhň•‚em·FyށqóžSᝑ³X_ĞçêtryvL¤§z„¦c¦¥jnŞk˜ˆlD¤øz½ĜàžĂŧMÅ|áƆàÊcðÂF܎‚áŢ¥\\\\º™İøÒÐJĴ‡„îD¦zK²ǏÎEh~’CD­hMn^ÌöÄ©ČZÀžaü„fɭyœpį´ěFűk]Ôě¢qlÅĆÙa¶~Äqššê€ljN¬¼H„ÊšNQ´ê¼VظE††^ŃÒyŒƒM{ŒJLoÒœęæŸe±Ķ›y‰’‡gã“¯JYÆĭĘëo¥Š‰o¯hcK«z_pŠrC´ĢÖY”—¼ v¸¢RŽÅW³Â§fǸYi³xR´ďUˊ`êĿU„û€uĆBƒƣö‰N€DH«Ĉg†——Ñ‚aB{ÊNF´¬c·Åv}eÇÃGB»”If•¦HňĕM…~[iwjUÁKE•Ž‹¾dĪçW›šI‹èÀŒoÈXòyŞŮÈXâÎŚŠj|àsRy‹µÖ›–Pr´þŒ ¸^wþTDŔ–Hr¸‹žRÌmf‡żÕâCôox–ĜƌÆĮŒ›Ð–œY˜tâŦÔ@]ÈǮƒ\\μģUsȯLbîƲŚºyh‡rŒŠ@ĒԝƀŸÀ²º\\êp“’JŠ}ĠvŠqt„Ġ@^xÀ£È†¨mËÏğ}n¹_¿¢×Y_æpˆÅ–A^{½•Lu¨GO±Õ½ßM¶w’ÁĢۂP‚›Ƣ¼pcIJxŠ|ap̬HšÐŒŊSfsðBZ¿©“XÏÒK•k†÷Eû¿‰S…rEFsÕūk”óVǥʼniTL‚¡n{‹uxţÏh™ôŝ¬ğōN“‘NJkyPaq™Âğ¤K®‡YŸxÉƋÁ]āęDqçgOg†ILu—\\_gz—]W¼ž~CÔē]bµogpў_oď`´³Țkl`IªºÎȄqÔþž»E³ĎSJ»œ_f·‚adÇqƒÇc¥Á_Źw{™L^ɱćx“U£µ÷xgĉp»ĆqNē`rĘzaĵĚ¡K½ÊBzyäKXqiWPÏɸ½řÍcÊG|µƕƣG˛÷Ÿk°_^ý|_zċBZocmø¯hhcæ\\lˆMFlư£Ĝ„ÆyH“„F¨‰µêÕ]—›HA…àӄ^it `þßäkŠĤÎT~Wlÿ¨„ÔPzUC–NVv [jâôDôď[}ž‰z¿–msSh‹¯{jïğl}šĹ[–őŒ‰gK‹©U·µË@¾ƒm_~q¡f¹…ÅË^»‘f³ø}Q•„¡Ö˳gͱ^ǁ…\\ëÃA_—¿bW›Ï[¶ƛ鏝£F{īZgm@|kHǭƁć¦UĔťƒ×ë}ǝƒeďºȡȘÏíBə£āĘPªij¶“ʼnÿ‡y©n‰ď£G¹¡I›Š±LÉĺÑdĉ܇W¥˜‰}g˜Á†{aqÃ¥aŠıęÏZ—ï`" + ], + "encodeOffsets": [[104636, 22969]] + } + }, + { + "type": "Feature", + "id": "540000", + "properties": { "id": "540000", "cp": [89.132212, 30.860361], "name": "西藏", "childNum": 1 }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@hžľxŽŖ‰xƒÒVކºÅâAĪÝȆµę¯Ňa±r_w~uSÕň‘qOj]ɄQ…£Z……UDûoY’»©M[‹L¼qãË{V͕çWViŽ]ë©Ä÷àyƛh›ÚU°ŒŒa”d„cQƒ~Mx¥™cc¡ÙaSyF—ցk­ŒuRýq¿Ôµ•QĽ³aG{¿FµëªéĜÿª@¬·–K‰·àariĕĀ«V»Ŷ™Ĵū˜gèLǴŇƶaf‹tŒèBŚ£^Šâ†ǐÝ®–šM¦ÁǞÿ¬LhŸŽJ¾óƾƺcxw‹f]Y…´ƒ¦|œQLn°aœdĊ…œ\\¨o’œǀÍŎœ´ĩĀd`tÊQŞŕ|‚¨C^©œĈ¦„¦ÎJĊ{ŽëĎjª²rЉšl`¼Ą[t|¦St辉PŒÜK¸€d˜Ƅı]s¤—î_v¹ÎVòŦj˜£Əsc—¬_Ğ´|٘¦Avަw`ăaÝaa­¢e¤ı²©ªSªšÈMĄwžÉØŔì@T‘¤—Ę™\\õª@”þo´­xA s”ÂtŎKzó´ÇĊµ¢rž^nĊ­Æ¬×üGž¢‚³ {âĊ]š™G‚~bÀgVjzlhǶf€žOšfdЉªB]pj„•TO–tĊ‚n¤}®¦ƒČ¥d¢¼»ddš”Y¼Žt—¢eȤJ¤}Ǿ¡°§¤AГlc@ĝ”sªćļđAç‡wx•UuzEÖġ~AN¹ÄÅȀݦ¿ģŁéì±H…ãd«g[؉¼ēÀ•cīľġ¬cJ‘µ…ÐʥVȝ¸ßS¹†ý±ğkƁ¼ą^ɛ¤Ûÿ‰b[}¬ōõÃ]ËNm®g@•Bg}ÍF±ǐyL¥íCˆƒIij€Ï÷њį[¹¦[⚍EÛïÁÉdƅß{âNÆāŨߝ¾ě÷yC£‡k­´ÓH@¹†TZ¥¢įƒ·ÌAЧ®—Zc…v½ŸZ­¹|ŕWZqgW“|ieZÅYVӁqdq•bc²R@†c‡¥Rã»Ge†ŸeƃīQ•}J[ғK…¬Ə|o’ėjġĠÑN¡ð¯EBčnwôɍėªƒ²•CλŹġǝʅįĭạ̃ūȹ]ΓͧgšsgȽóϧµǛ†ęgſ¶ҍć`ĘąŌJޚä¤rÅň¥ÖÁUětęuůÞiĊÄÀ\\Æs¦ÓRb|Â^řÌkÄŷ¶½÷‡f±iMݑ›‰@ĥ°G¬ÃM¥n£Øą‚ğ¯ß”§aëbéüÑOčœk£{\\‘eµª×M‘šÉfm«Ƒ{Å׃Gŏǩãy³©WÑăû‚··‘Q—òı}¯ã‰I•éÕÂZ¨īès¶ZÈsŽæĔTŘvŽgÌsN@îá¾ó@‰˜ÙwU±ÉT廣TđŸWxq¹Zo‘b‹s[׌¯cĩv‡Œėŧ³BM|¹k‰ªħ—¥TzNYnݍßpęrñĠĉRS~½ŠěVVе‚õ‡«ŒM££µB•ĉ¥áºae~³AuĐh`Ü³ç@BۘïĿa©|z²Ý¼D”£à貋ŸƒIƒû›I ā€óK¥}rÝ_Á´éMaň¨€~ªSĈ½Ž½KÙóĿeƃÆBŽ·¬ën×W|Uº}LJrƳ˜lŒµ`bÔ`QˆˆÐÓ@s¬ñIŒÍ@ûws¡åQÑßÁ`ŋĴ{Ī“T•ÚÅTSij‚‹Yo|Ç[ǾµMW¢ĭiÕØ¿@˜šMh…pÕ]j†éò¿OƇĆƇp€êĉâlØw–ěsˆǩ‚ĵ¸c…bU¹ř¨WavquSMzeo_^gsÏ·¥Ó@~¯¿RiīB™Š\\”qTGªÇĜçPoŠÿfñòą¦óQīÈáP•œābß{ƒZŗĸIæÅ„hnszÁCËìñšÏ·ąĚÝUm®ó­L·ăU›Èíoù´Êj°ŁŤ_uµ^‘°Œìǖ@tĶĒ¡Æ‡M³Ģ«˜İĨÅ®ğ†RŽāð“ggheÆ¢z‚Ê©Ô\\°ÝĎz~ź¤Pn–MĪÖB£Ÿk™n鄧żćŠ˜ĆK„ǰ¼L¶è‰âz¨u¦¥LDĘz¬ýÎmĘd¾ß”Fz“hg²™Fy¦ĝ¤ċņbΛ@y‚Ąæm°NĮZRÖíŽJ²öLĸÒ¨Y®ƌÐV‰à˜tt_ڀÂyĠzž]Ţh€zĎ{†ĢX”ˆc|šÐqŽšfO¢¤ög‚ÌHNŽ„PKŖœŽ˜Uú´xx[xˆvĐCûŠìÖT¬¸^}Ìsòd´_އKgžLĴ…ÀBon|H@–Êx˜—¦BpŰˆŌ¿fµƌA¾zLjRxжF”œkĄźRzŀˆ~¶[”´Hnª–VƞuĒ­È¨ƎcƽÌm¸ÁÈM¦x͊ëÀxdžB’šú^´W†£–d„kɾĬpœw‚˂ØɦļĬIŚœÊ•n›Ŕa¸™~J°î”lɌxĤÊÈðhÌ®‚g˜T´øŽàCˆŽÀ^ªerrƘdž¢İP|Ė ŸWœªĦ^¶´ÂL„aT±üWƜ˜ǀRšŶUńšĖ[QhlLüA†‹Ü\\†qR›Ą©" + ], + "encodeOffsets": [[90849, 37210]] + } + }, + { + "type": "Feature", + "id": "610000", + "properties": { + "id": "610000", + "cp": [108.948024, 34.263161], + "name": "陕西", + "childNum": 1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@˜p¢—ȮµšûG™Ħ}Ħšðǚ¶òƄ€jɂz°{ºØkÈęâ¦jª‚Bg‚\\œċ°s¬Ž’]jžú ‚E”Ȍdž¬s„t‡”RˆÆdĠݎwܔ¸ôW¾ƮłÒ_{’Ìšû¼„jº¹¢GǪÒ¯ĘƒZ`ºŊƒecņąš~BÂgzpâēòYǠȰÌTΨÂWœ|fcŸă§uF—Œ@NŸ¢XLƒŠRMº[ğȣſï|¥J™kc`sʼnǷ’Y¹‹W@µ÷K…ãï³ÛIcñ·VȋڍÒķø©—þ¥ƒy‚ÓŸğęmWµÎumZyOŅƟĥÓ~sÑL¤µaŅY¦ocyZ{‰y c]{ŒTa©ƒ`U_Ěē£ωÊƍKù’K¶ȱÝƷ§{û»ÅÁȹÍéuij|¹cÑd‘ŠìUYƒŽO‘uF–ÕÈYvÁCqӃT•Ǣí§·S¹NgŠV¬ë÷Át‡°Dد’C´ʼnƒópģ}„ċcE˅FŸŸéGU¥×K…§­¶³B‹Č}C¿åċ`wġB·¤őcƭ²ő[Å^axwQO…ÿEËߌ•ĤNĔŸwƇˆÄŠńwĪ­Šo[„_KÓª³“ÙnK‰Çƒěœÿ]ď€ă_d©·©Ýŏ°Ù®g]±„Ÿ‡ß˜å›—¬÷m\\›iaǑkěX{¢|ZKlçhLt€Ňîŵ€œè[€É@ƉĄEœ‡tƇÏ˜³­ħZ«mJ…›×¾‘MtÝĦ£IwÄå\\Õ{‡˜ƒOwĬ©LÙ³ÙgBƕŀr̛ĢŭO¥lãyC§HÍ£ßEñŸX¡—­°ÙCgpťz‘ˆb`wI„vA|§”‡—hoĕ@E±“iYd¥OϹS|}F@¾oAO²{tfžÜ—¢Fǂ҈W²°BĤh^Wx{@„¬‚­F¸¡„ķn£P|ŸªĴ@^ĠĈæb–Ôc¶l˜Yi…–^Mi˜cϰÂ[ä€vï¶gv@À“Ĭ·lJ¸sn|¼u~a]’ÆÈtŌºJp’ƒþ£KKf~ЦUbyäIšĺãn‡Ô¿^­žŵMT–hĠܤko¼Ŏìąǜh`[tŒRd²IJ_œXPrɲ‰l‘‚XžiL§àƒ–¹ŽH˜°Ȧqº®QC—bA†„ŌJ¸ĕÚ³ĺ§ `d¨YjžiZvRĺ±öVKkjGȊĐePОZmļKÀ€‚[ŠŽ`ösìh†ïÎoĬdtKÞ{¬èÒÒBŒÔpIJÇĬJŊ¦±J«ˆY§‹@·pH€µàåVKe›pW†ftsAÅqC·¬ko«pHÆuK@oŸHĆۄķhx“e‘n›S³àǍrqƶRbzy€¸ËАl›¼EºpĤ¼Œx¼½~Ğ’”à@†ÚüdK^ˆmÌSj" + ], + "encodeOffsets": [[110234, 38774]] + } + }, + { + "type": "Feature", + "id": "620000", + "properties": { + "id": "620000", + "cp": [103.823557, 36.058039], + "name": "甘肃", + "childNum": 2 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@VuUv"], + [ + "@@ũ‹EĠtt~nkh`Q‰¦ÅÄÜdw˜Ab×ĠąJˆ¤DüègĺqBqœj°lI¡ĨÒ¤úSHbš‡ŠjΑBаaZˆ¢KJŽ’O[|A£žDx}Nì•HUnrk„ kp€¼Y kMJn[aG‚áÚÏ[½rc†}aQxOgsPMnUs‡nc‹Z…ž–sKúvA›t„Þġ’£®ĀYKdnFwš¢JE°”Latf`¼h¬we|€Æ‡šbj}GA€·~WŽ”—`†¢MC¤tL©IJ°qdf”O‚“bÞĬ¹ttu`^ZúE`Œ[@„Æsîz®¡’C„ƳƜG²“R‘¢R’m”fŽwĸg܃‚ą G@pzJM½mŠhVy¸uÈÔO±¨{LfæU¶ßGĂq\\ª¬‡²I‚¥IʼnÈīoı‹ÓÑAçÑ|«LÝcspīðÍg…të_õ‰\\ĉñLYnĝg’ŸRǡÁiHLlõUĹ²uQjYi§Z_c¨Ÿ´ĹĖÙ·ŋI…ƒaBD˜­R¹ȥr—¯G•ºß„K¨jWk’ɱŠOq›Wij\\a­‹Q\\sg_ĆǛōëp»£lğۀgS•ŶN®À]ˆÓäm™ĹãJaz¥V}‰Le¤L„ýo‘¹IsŋÅÇ^‘Žbz…³tmEÁ´aйcčecÇN•ĊãÁ\\蝗dNj•]j†—ZµkÓda•ćå]ğij@ ©O{¤ĸm¢ƒE·®ƒ«|@Xwg]A챝‡XǁÑdzªc›wQÚŝñsÕ³ÛV_ýƒ˜¥\\ů¥©¾÷w—Ž©WÕÊĩhÿÖÁRo¸V¬âDb¨šhûx–Ê×nj~Zâƒg|šXÁnßYoº§ZÅŘvŒ[„ĭÖʃuďxcVbnUSf…B¯³_Tzº—ΕO©çMÑ~Mˆ³]µ^püµ”ŠÄY~y@X~¤Z³€[Èōl@®Å¼£QKƒ·Di‹¡By‘ÿ‰Q_´D¥hŗyƒ^ŸĭÁZ]cIzý‰ah¹MĪğP‘s{ò‡‹‘²Vw¹t³Ŝˁ[ŽÑ}X\\gsFŸ£sPAgěp×ëfYHāďÖqēŭOÏë“dLü•\\iŒ”t^c®šRʺ¶—¢H°mˆ‘rYŸ£BŸ¹čIoľu¶uI]vģSQ{ƒUŻ”Å}QÂ|̋°ƅ¤ĩŪU ęĄžÌZҞ\\v˜²PĔ»ƢNHƒĂyAmƂwVmž`”]ȏb•”H`‰Ì¢²ILvĜ—H®¤Dlt_„¢JJÄämèÔDëþgºƫ™”aʎÌrêYi~ ÎݤNpÀA¾Ĕ¼b…ð÷’Žˆ‡®‚”üs”zMzÖĖQdȨý†v§Tè|ªH’þa¸|šÐ ƒwKĢx¦ivr^ÿ ¸l öæfƟĴ·PJv}n\\h¹¶v†·À|\\ƁĚN´Ĝ€çèÁz]ġ¤²¨QÒŨTIl‡ªťØ}¼˗ƦvÄùØE‹’«Fï˛Iq”ōŒTvāÜŏ‚íÛߜÛV—j³âwGăÂíNOŠˆŠPìyV³ʼnĖýZso§HіiYw[߆\\X¦¥c]ÔƩÜ·«j‡ÐqvÁ¦m^ċ±R™¦΋ƈťĚgÀ»IïĨʗƮްƝ˜ĻþÍAƉſ±tÍEÕÞāNU͗¡\\ſčåÒʻĘm ƭÌŹöʥ’ëQ¤µ­ÇcƕªoIýˆ‰Iɐ_mkl³ă‰Ɠ¦j—¡Yz•Ňi–}Msßõ–īʋ —}ƒÁVmŸ_[n}eı­Uĥ¼‘ª•I{ΧDӜƻėoj‘qYhĹT©oūĶ£]ďxĩ‹ǑMĝ‰q`B´ƃ˺Ч—ç~™²ņj@”¥@đ´ί}ĥtPńǾV¬ufӃÉC‹tÓ̻‰…¹£G³€]ƖƾŎĪŪĘ̖¨ʈĢƂlɘ۪üºňUðǜȢƢż̌ȦǼ‚ĤŊɲĖ­Kq´ï¦—ºĒDzņɾªǀÞĈĂD†½ĄĎÌŗĞrôñnŽœN¼â¾ʄľԆ|DŽŽ֦ज़ȗlj̘̭ɺƅêgV̍ʆĠ·ÌĊv|ýĖÕWĊǎÞ´õ¼cÒÒBĢ͢UĜð͒s¨ňƃLĉÕÝ@ɛƯ÷¿Ľ­ĹeȏijëCȚDŲyê×Ŗyò¯ļcÂßY…tÁƤyAã˾J@ǝrý‹‰@¤…rz¸oP¹ɐÚyᐇHŸĀ[Jw…cVeȴϜ»ÈŽĖ}ƒŰŐèȭǢόĀƪÈŶë;Ñ̆ȤМľĮEŔ—ĹŊũ~ËUă{ŸĻƹɁύȩþĽvĽƓÉ@ē„ĽɲßǐƫʾǗĒpäWÐxnsÀ^ƆwW©¦cÅ¡Ji§vúF¶Ž¨c~c¼īŒeXǚ‹\\đ¾JŽwÀďksãA‹fÕ¦L}wa‚o”Z’‹D½†Ml«]eÒÅaɲáo½FõÛ]ĻÒ¡wYR£¢rvÓ®y®LF‹LzĈ„ôe]gx}•|KK}xklL]c¦£fRtív¦†PĤoH{tK" + ] + ], + "encodeOffsets": [[[108619, 36299]], [[108589, 36341]]] + } + }, + { + "type": "Feature", + "id": "630000", + "properties": { "id": "630000", "cp": [96.778916, 35.623178], "name": "青海", "childNum": 2 }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@InJm"], + [ + "@@CƒÆ½OŃĦsΰ~dz¦@@“Ņiš±è}ؘƄ˹A³r_ĞŠǒNΌĐw¤^ŬĵªpĺSZg’rpiƼĘԛ¨C|͖J’©Ħ»®VIJ~f\\m `Un„˜~ʌŸ•ĬàöNt•~ňjy–¢Zi˜Ɣ¥ĄŠk´nl`JʇŠJþ©pdƖ®È£¶ìRʦ‘źõƮËnŸʼėæÑƀĎ[‚˜¢VÎĂMÖÝÎF²sƊƀÎBļýƞ—¯ʘƭðħ¼Jh¿ŦęΌƇš¥²Q]Č¥nuÂÏriˆ¸¬ƪÛ^Ó¦d€¥[Wà…x\\ZŽjҕ¨GtpþYŊĕ´€zUO뇉P‰îMĄÁxH´á˜iÜUà›îÜՁĂÛSuŎ‹r“œJð̬EŒ‘FÁú×uÃÎkr“Ē{V}İ«O_ÌËĬ©ŽÓŧSRѱ§Ģ£^ÂyèçěM³Ƃę{[¸¿u…ºµ[gt£¸OƤĿéYŸõ·kŸq]juw¥Dĩƍ€õÇPéĽG‘ž©ã‡¤G…uȧþRcÕĕNy“yût“ˆ­‡ø‘†ï»a½ē¿BMoᣟÍj}éZËqbʍš“Ƭh¹ìÿÓAçãnIáI`ƒks£CG­ě˜Uy×Cy•…’Ÿ@¶ʡÊBnāzG„ơMē¼±O÷õJËĚăVŸĪũƆ£Œ¯{ËL½Ìzż“„VR|ĠTbuvJvµhĻĖH”Aëáa…­OÇðñęNw‡…œľ·L›mI±íĠĩPÉ×®ÿs—’cB³±JKßĊ«`…ađ»·QAmO’‘Vţéÿ¤¹SQt]]Çx€±¯A@ĉij¢Ó祖•ƒl¶ÅÛr—ŕspãRk~¦ª]Į­´“FR„åd­ČsCqđéFn¿Åƃm’Éx{W©ºƝºįkÕƂƑ¸wWūЩÈFž£\\tÈ¥ÄRÈýÌJ ƒlGr^×äùyÞ³fj”c†€¨£ÂZ|ǓMĝšÏ@ëÜőR‹›ĝ‰Œ÷¡{aïȷPu°ËXÙ{©TmĠ}Y³’­ÞIňµç½©C¡į÷¯B»|St»›]vƒųƒs»”}MÓ ÿʪƟǭA¡fs˜»PY¼c¡»¦c„ċ­¥£~msĉP•–Siƒ^o©A‰Šec‚™PeǵŽkg‚yUi¿h}aH™šĉ^|ᴟ¡HØûÅ«ĉ®]m€¡qĉ¶³ÈyôōLÁst“BŸ®wn±ă¥HSò뚣˜S’ë@לÊăxÇN©™©T±ª£IJ¡fb®ÞbŽb_Ą¥xu¥B—ž{łĝ³«`d˜Ɛt—¤ťiñžÍUuºí`£˜^tƃIJc—·ÛLO‹½Šsç¥Ts{ă\\_»™kϊ±q©čiìĉ|ÍIƒ¥ć¥›€]ª§D{ŝŖÉR_sÿc³Īō›ƿΑ›§p›[ĉ†›c¯bKm›R¥{³„Z†e^ŽŒwx¹dƽŽôIg §Mĕ ƹĴ¿—ǣÜ̓]‹Ý–]snåA{‹eŒƭ`ǻŊĿ\\ijŬű”YÂÿ¬jĖqŽßbЏ•L«¸©@ěĀ©ê¶ìÀEH|´bRľž–Ó¶rÀQþ‹vl®Õ‚E˜TzÜdb ˜hw¤{LR„ƒd“c‹b¯‹ÙVgœ‚ƜßzÃô쮍^jUèXΖ|UäÌ»rKŽ\\ŒªN‘¼pZCü†VY††¤ɃRi^rPҒTÖ}|br°qňb̰ªiƶGQ¾²„x¦PœmlŜ‘[Ĥ¡ΞsĦŸÔÏâ\\ªÚŒU\\f…¢N²§x|¤§„xĔsZPòʛ²SÐqF`ª„VƒÞŜĶƨVZŒÌL`ˆ¢dŐIqr\\oäõ–F礻Ŷ×h¹]Clـ\\¦ďÌį¬řtTӺƙgQÇÓHţĒ”´ÃbEÄlbʔC”|CˆŮˆk„Ʈ[ʼ¬ňœ´KŮÈΰÌζƶlð”ļA†TUvdTŠG†º̼ŠÔ€ŒsÊDԄveOg" + ] + ], + "encodeOffsets": [[[105308, 37219]], [[95370, 40081]]] + } + }, + { + "type": "Feature", + "id": "640000", + "properties": { "id": "640000", "cp": [106.278179, 37.26637], "name": "宁夏", "childNum": 2 }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + "@@KëÀęĞ«OęȿȕŸı]ʼn¡åįÕÔ«Ǵõƪ™ĚQÐZhv K°›öqÀѐS[ÃÖHƖčË‡nL]ûc…Ùß@‚“ĝ‘¾}w»»‹oģF¹œ»kÌÏ·{zPƒ§B­¢íyÅt@ƒ@áš]Yv_ssģ¼i߁”ĻL¾ġsKD£¡N_…“˜X¸}B~Haiˆ™Åf{«x»ge_bs“KF¯¡Ix™mELcÿZ¤­Ģ‘ƒÝœsuBLù•t†ŒYdˆmVtNmtOPhRw~bd…¾qÐ\\âÙH\\bImlNZŸ»loƒŸqlVm–Gā§~QCw¤™{A\\‘PKŸNY‡¯bF‡kC¥’sk‹Šs_Ã\\ă«¢ħkJi¯r›rAhĹûç£CU‡ĕĊ_ԗBixÅُĄnªÑaM~ħpOu¥sîeQ¥¤^dkKwlL~{L~–hw^‚ófćƒKyEŒ­K­zuÔ¡qQ¤xZÑ¢^ļöܾEpž±âbÊÑÆ^fk¬…NC¾‘Œ“YpxbK~¥Že֎ŒäBlt¿Đx½I[ĒǙŒWž‹f»Ĭ}d§dµùEuj¨‚IÆ¢¥dXªƅx¿]mtÏwßR͌X¢͎vÆzƂZò®ǢÌʆCrâºMÞzžÆMҔÊÓŊZľ–r°Î®Ȉmª²ĈUªĚøºˆĮ¦ÌĘk„^FłĬhĚiĀ˾iİbjÕ" + ], + ["@@mfwěwMrŢªv@G‰"] + ], + "encodeOffsets": [[[109366, 40242]], [[108600, 36303]]] + } + }, + { + "type": "Feature", + "id": "650000", + "properties": { "id": "650000", "cp": [85.617733, 40.792818], "name": "新疆", "childNum": 1 }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@QØĔ²X¨”~ǘBºjʐߨvK”ƔX¨vĊOžÃƒ·¢i@~c—‡ĝe_«”Eš“}QxgɪëÏÃ@sÅyXoŖ{ô«ŸuX…ê•Îf`œC‚¹ÂÿÐGĮÕĞXŪōŸMźÈƺQèĽôe|¿ƸJR¤ĘEjcUóº¯Ĩ_ŘÁMª÷Ð¥Oéȇ¿ÖğǤǷÂF҇zÉx[]­Ĥĝ‰œ¦EP}ûƥé¿İƷTėƫœŕƅ™ƱB»Đ±’ēO…¦E–•}‘`cȺrĦáŖuҞª«IJ‡πdƺÏØZƴwʄ¤ĖGЙǂZ̓èH¶}ÚZצʥĪï|ÇĦMŔ»İĝLj‹ì¥Βœba­¯¥ǕǚkĆŵĦɑĺƯxūД̵nơʃĽá½M»›òmqóŘĝč˾ăC…ćāƿÝɽ©DZŅ¹đ¥˜³ðLrÁ®ɱĕģʼnǻ̋ȥơŻǛȡVï¹Ň۩ûkɗġƁ§ʇė̕ĩũƽō^ƕŠUv£ƁQï“Ƶkŏ½ΉÃŭdzLқʻ«ƭ\\lƒ‡ŭD‡“{ʓDkaFÃÄa“³ŤđÔGRÈƚhSӹŚsİ«ĐË[¥ÚDkº^Øg¼ŵ¸£EÍö•€ůʼnT¡c_‡ËKY‹ƧUśĵ„݃U_©rETÏʜ±OñtYw獃{£¨uM³x½şL©Ùá[ÓÐĥ Νtģ¢\\‚ś’nkO›w¥±ƒT»ƷFɯàĩÞáB¹Æ…ÑUw„੍žĽw[“mG½Èå~‡Æ÷QyŠěCFmĭZī—ŵVÁ™ƿQƛ—ûXS²‰b½KϽĉS›©ŷXĕŸ{ŽĕK·¥Ɨcqq©f¿]‡ßDõU³h—­gËÇïģÉɋw“k¯í}I·šœbmœÉ–ř›īJɥĻˁ×xo›ɹī‡l•c…¤³Xù]‘™DžA¿w͉ì¥wÇN·ÂËnƾƍdǧđ®Ɲv•Um©³G\\“}µĿ‡QyŹl㓛µEw‰LJQ½yƋBe¶ŋÀů‡ož¥A—˜Éw@•{Gpm¿Aij†ŽKLhˆ³`ñcËtW‚±»ÕS‰ëüÿďD‡u\\wwwù³—V›LŕƒOMËGh£õP¡™er™Ïd{“‡ġWÁ…č|yšg^ğyÁzÙs`—s|ÉåªÇ}m¢Ń¨`x¥’ù^•}ƒÌ¥H«‰Yªƅ”Aйn~Ꝛf¤áÀz„gŠÇDIԝ´AňĀ҄¶ûEYospõD[{ù°]u›Jq•U•|Soċxţ[õÔĥkŋÞŭZ˺óYËüċrw €ÞkrťË¿XGÉbřaDü·Ē÷Aê[Ää€I®BÕИÞ_¢āĠpŠÛÄȉĖġDKwbm‡ÄNô‡ŠfœƫVÉvi†dz—H‘‹QµâFšù­Âœ³¦{YGžƒd¢ĚÜO „€{Ö¦ÞÍÀPŒ^b–ƾŠlŽ[„vt×ĈÍE˨¡Đ~´î¸ùÎh€uè`¸ŸHÕŔVºwĠââWò‡@{œÙNÝ´ə²ȕn{¿¥{l—÷eé^e’ďˆXj©î\\ªÑò˜Üìc\\üqˆÕ[Č¡xoÂċªbØ­Œø|€¶ȴZdÆÂšońéŒGš\\”¼C°ÌƁn´nxšÊOĨ’ہƴĸ¢¸òTxÊǪMīИÖŲÃɎOvˆʦƢ~FއRěò—¿ġ~åŊœú‰Nšžš¸qŽ’Ę[Ĕ¶ÂćnÒPĒÜvúĀÊbÖ{Äî¸~Ŕünp¤ÂH¾œĄYÒ©ÊfºmԈĘcDoĬMŬ’˜S¤„s²‚”ʘچžȂVŦ –ŽèW°ªB|IJXŔþÈJĦÆæFĚêŠYĂªĂ]øªŖNÞüA€’fɨJ€˜¯ÎrDDšĤ€`€mz\\„§~D¬{vJÂ˜«lµĂb–¤p€ŌŰNĄ¨ĊXW|ų ¿¾ɄĦƐMT”‡òP˜÷fØĶK¢ȝ˔Sô¹òEð­”`Ɩ½ǒÂň×äı–§ĤƝ§C~¡‚hlå‚ǺŦŞkâ’~}ŽFøàIJaĞ‚fƠ¥Ž„Ŕdž˜®U¸ˆźXœv¢aƆúŪtŠųƠjd•ƺŠƺÅìnrh\\ĺ¯äɝĦ]èpĄ¦´LƞĬŠ´ƤǬ˼Ēɸ¤rºǼ²¨zÌPðŀbþ¹ļD¢¹œ\\ĜÑŚŸ¶ZƄ³àjĨoâŠȴLʉȮŒĐ­ĚăŽÀêZǚŐ¤qȂ\\L¢ŌİfÆs|zºeªÙæ§΢{Ā´ƐÚ¬¨Ĵà²łhʺKÞºÖTŠiƢ¾ªì°`öøu®Ê¾ãØ" + ], + "encodeOffsets": [[88824, 50096]] + } + }, + { + "type": "Feature", + "id": "110000", + "properties": { + "id": "110000", + "cp": [116.405285, 39.904989], + "name": "北京", + "childNum": 1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@ĽOÁ›ûtŷmiÍt_H»Ĩ±d`й­{bw…Yr“³S]§§o¹€qGtm_Sŧ€“oa›‹FLg‘QN_•dV€@Zom_ć\\ߚc±x¯oœRcfe…£’o§ËgToÛJíĔóu…|wP¤™XnO¢ÉˆŦ¯rNÄā¤zâŖÈRpŢZŠœÚ{GŠrFt¦Òx§ø¹RóäV¤XdˆżâºWbwڍUd®bêņ¾‘jnŎGŃŶŠnzÚSeîĜZczî¾i]͜™QaúÍÔiþĩȨWĢ‹ü|Ėu[qb[swP@ÅğP¿{\\‡¥A¨Ï‘Ѩj¯ŠX\\¯œMK‘pA³[H…īu}}" + ], + "encodeOffsets": [[120023, 41045]] + } + }, + { + "type": "Feature", + "id": "120000", + "properties": { + "id": "120000", + "cp": [117.190182, 39.125596], + "name": "天津", + "childNum": 1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + "@@ŬgX§Ü«E…¶Ḟ“¬O_™ïlÁg“z±AXe™µÄĵ{¶]gitgšIj·›¥îakS€‰¨ÐƎk}ĕ{gB—qGf{¿a†U^fI“ư‹³õ{YƒıëNĿžk©ïËZŏ‘R§òoY×Ógc…ĥs¡bġ«@dekąI[nlPqCnp{ˆō³°`{PNdƗqSÄĻNNâyj]äžÒD ĬH°Æ]~¡HO¾ŒX}ÐxŒgp“gWˆrDGˆŒpù‚Š^L‚ˆrzWxˆZ^¨´T\\|~@I‰zƒ–bĤ‹œjeĊªz£®Ĕvě€L†mV¾Ô_ȔNW~zbĬvG†²ZmDM~”~" + ], + "encodeOffsets": [[120237, 41215]] + } + }, + { + "type": "Feature", + "id": "310000", + "properties": { + "id": "310000", + "cp": [121.472644, 31.231706], + "name": "上海", + "childNum": 6 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@ɧư¬EpƸÁxc‡"], + ["@@©„ªƒ"], + ["@@”MA‹‘š"], + ["@@Qp݁E§ÉC¾"], + ["@@bŝՕÕEȣÚƥêImɇǦèÜĠŒÚžÃƌÃ͎ó"], + ["@@ǜûȬɋŠŭ™×^‰sYŒɍDŋ‘ŽąñCG²«ªč@h–_p¯A{‡oloY€¬j@IJ`•gQڛhr|ǀ^MIJvtbe´R¯Ô¬¨YŽô¤r]ì†Ƭį"] + ], + "encodeOffsets": [ + [[124702, 32062]], + [[124547, 32200]], + [[124808, 31991]], + [[124726, 32110]], + [[124903, 32376]], + [[124438, 32149]] + ] + } + }, + { + "type": "Feature", + "id": "500000", + "properties": { + "id": "500000", + "cp": [107.304962, 29.533155], + "name": "重庆", + "childNum": 2 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + "@@vjG~nGŘŬĶȂƀƾ¹¸ØÎezĆT¸}êЖqHŸðqĖ䒊¥^CƒIj–²p…\\_ æüY|[YxƊæuž°xb®…Űb@~¢NQt°¶‚S栓Ê~rljĔëĚ¢~šuf`‘‚†fa‚ĔJåĊ„nÖ]„jƎćÊ@Š£¾a®£Ű{ŶĕF‹ègLk{Y|¡ĜWƔtƬJÑxq‹±ĢN´‰òK‰™–LÈüD|s`ŋ’ć]ƒÃ‰`đŒMûƱ½~Y°ħ`ƏíW‰½eI‹½{aŸ‘OIrÏ¡ĕŇa†p†µÜƅġ‘œ^ÖÛbÙŽŏml½S‹êqDu[R‹ãË»†ÿw`»y‘¸_ĺę}÷`M¯ċfCVµqʼn÷Z•gg“Œ`d½pDO‡ÎCnœ^uf²ènh¼WtƏxRGg¦…pV„†FI±ŽG^ŒIc´ec‡’G•ĹÞ½sëĬ„h˜xW‚}Kӈe­Xsbk”F¦›L‘ØgTkïƵNï¶}Gy“w\\oñ¡nmĈzjŸ•@™Óc£»Wă¹Ój“_m»ˆ¹·~MvÛaqœ»­‰êœ’\\ÂoVnŽÓØÍ™²«‹bq¿efE „€‹Ĝ^Qž~ Évý‡ş¤²Į‰pEİ}zcĺƒL‹½‡š¿gņ›¡ýE¡ya£³t\\¨\\vú»¼§·Ñr_oÒý¥u‚•_n»_ƒ•At©Þűā§IVeëƒY}{VPÀFA¨ąB}q@|Ou—\\Fm‰QF݅Mw˜å}]•€|FmϋCaƒwŒu_p—¯sfÙgY…DHl`{QEfNysBЦzG¸rHe‚„N\\CvEsÐùÜ_·ÖĉsaQ¯€}_U‡†xÃđŠq›NH¬•Äd^ÝŰR¬ã°wećJEž·vÝ·Hgƒ‚éFXjÉê`|yŒpxkAwœWĐpb¥eOsmzwqChóUQl¥F^laf‹anòsr›EvfQdÁUVf—ÎvÜ^efˆtET¬ôA\\œ¢sJŽnQTjP؈xøK|nBz‰„œĞ»LY‚…FDxӄvr“[ehľš•vN”¢o¾NiÂxGp⬐z›bfZo~hGi’]öF|‰|Nb‡tOMn eA±ŠtPT‡LjpYQ|†SH††YĀxinzDJ€Ìg¢và¥Pg‰_–ÇzII‹€II•„£®S¬„Øs쐣ŒN" + ], + ["@@ifjN@s"] + ], + "encodeOffsets": [[[109628, 30765]], [[111725, 31320]]] + } + }, + { + "type": "Feature", + "id": "810000", + "properties": { + "id": "810000", + "cp": [114.173355, 22.320048], + "name": "香港", + "childNum": 5 + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + ["@@AlBk"], + ["@@mŽn"], + ["@@EpFo"], + ["@@ea¢pl¸Eõ¹‡hj[ƒ]ÔCΖ@lj˜¡uBXŸ…•´‹AI¹…[‹yDUˆ]W`çwZkmc–…M›žp€Åv›}I‹oJlcaƒfёKްä¬XJmРđhI®æÔtSHn€Eˆ„ÒrÈc"], + ["@@rMUw‡AS®€e"] + ], + "encodeOffsets": [ + [[117111, 23002]], + [[117072, 22876]], + [[117045, 22887]], + [[116975, 23082]], + [[116882, 22747]] + ] + } + }, + { + "type": "Feature", + "id": "820000", + "properties": { "id": "820000", "cp": [113.54909, 22.198951], "name": "澳门", "childNum": 1 }, + "geometry": { + "type": "Polygon", + "coordinates": ["@@kÊd°å§s"], + "encodeOffsets": [[116279, 22639]] + } + } + ], + "UTF8Encoding": true +} diff --git a/src/views/demo/charts/data.ts b/src/views/demo/charts/data.ts new file mode 100644 index 00000000000..547c0895caa --- /dev/null +++ b/src/views/demo/charts/data.ts @@ -0,0 +1,189 @@ +export const mapData: any = [ + { + name: '北京', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '天津', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '上海', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '重庆', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '河北', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '河南', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '云南', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '辽宁', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '黑龙江', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '湖南', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '安徽', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '山东', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '新疆', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '江苏', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '浙江', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '江西', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '湖北', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '广西', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '甘肃', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '山西', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '内蒙古', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '陕西', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '吉林', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '福建', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '贵州', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '广东', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '青海', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '西藏', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '四川', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '宁夏', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '海南', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '台湾', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '香港', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, + { + name: '澳门', + value: Math.round(Math.random() * 1000), + tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)], + }, +]; + +export const getLineData = (() => { + const category: any[] = []; + let dottedBase = +new Date(); + const lineData: any[] = []; + const barData: any[] = []; + + for (let i = 0; i < 20; i++) { + const date = new Date((dottedBase += 1000 * 3600 * 24)); + category.push([date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-')); + const b = Math.random() * 200; + const d = Math.random() * 200; + barData.push(b); + lineData.push(d + b); + } + return { barData, category, lineData }; +})(); diff --git a/src/views/demo/charts/map/Baidu.vue b/src/views/demo/charts/map/Baidu.vue new file mode 100644 index 00000000000..04204088a71 --- /dev/null +++ b/src/views/demo/charts/map/Baidu.vue @@ -0,0 +1,42 @@ + + diff --git a/src/views/demo/charts/map/Gaode.vue b/src/views/demo/charts/map/Gaode.vue new file mode 100644 index 00000000000..dfc1ad5a7c9 --- /dev/null +++ b/src/views/demo/charts/map/Gaode.vue @@ -0,0 +1,42 @@ + + diff --git a/src/views/demo/charts/map/Google.vue b/src/views/demo/charts/map/Google.vue new file mode 100644 index 00000000000..0d062428c7b --- /dev/null +++ b/src/views/demo/charts/map/Google.vue @@ -0,0 +1,48 @@ + + diff --git a/src/views/demo/comp/button/index.vue b/src/views/demo/comp/button/index.vue new file mode 100644 index 00000000000..16365b9d7f4 --- /dev/null +++ b/src/views/demo/comp/button/index.vue @@ -0,0 +1,108 @@ + + diff --git a/src/views/demo/comp/card-list/index.vue b/src/views/demo/comp/card-list/index.vue new file mode 100644 index 00000000000..5affd2139c5 --- /dev/null +++ b/src/views/demo/comp/card-list/index.vue @@ -0,0 +1,32 @@ + + diff --git a/src/views/demo/comp/count-to/index.vue b/src/views/demo/comp/count-to/index.vue new file mode 100644 index 00000000000..eb20c73427a --- /dev/null +++ b/src/views/demo/comp/count-to/index.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/views/demo/comp/cropper/index.vue b/src/views/demo/comp/cropper/index.vue new file mode 100644 index 00000000000..b421e8f31a4 --- /dev/null +++ b/src/views/demo/comp/cropper/index.vue @@ -0,0 +1,78 @@ + + + + diff --git a/src/views/demo/comp/desc/index.vue b/src/views/demo/comp/desc/index.vue new file mode 100644 index 00000000000..74125a5c54d --- /dev/null +++ b/src/views/demo/comp/desc/index.vue @@ -0,0 +1,77 @@ + + diff --git a/src/views/demo/comp/drawer/Drawer1.vue b/src/views/demo/comp/drawer/Drawer1.vue new file mode 100644 index 00000000000..765664c6963 --- /dev/null +++ b/src/views/demo/comp/drawer/Drawer1.vue @@ -0,0 +1,6 @@ + + diff --git a/src/views/demo/comp/drawer/Drawer2.vue b/src/views/demo/comp/drawer/Drawer2.vue new file mode 100644 index 00000000000..83afff917f1 --- /dev/null +++ b/src/views/demo/comp/drawer/Drawer2.vue @@ -0,0 +1,11 @@ + + diff --git a/src/views/demo/comp/drawer/Drawer3.vue b/src/views/demo/comp/drawer/Drawer3.vue new file mode 100644 index 00000000000..e2aaf56b2a8 --- /dev/null +++ b/src/views/demo/comp/drawer/Drawer3.vue @@ -0,0 +1,28 @@ + + diff --git a/src/views/demo/comp/drawer/Drawer4.vue b/src/views/demo/comp/drawer/Drawer4.vue new file mode 100644 index 00000000000..f13c08511c1 --- /dev/null +++ b/src/views/demo/comp/drawer/Drawer4.vue @@ -0,0 +1,46 @@ + + diff --git a/src/views/demo/comp/drawer/Drawer5.vue b/src/views/demo/comp/drawer/Drawer5.vue new file mode 100644 index 00000000000..6e393b10e5c --- /dev/null +++ b/src/views/demo/comp/drawer/Drawer5.vue @@ -0,0 +1,9 @@ + + diff --git a/src/views/demo/comp/drawer/index.vue b/src/views/demo/comp/drawer/index.vue new file mode 100644 index 00000000000..923b473836f --- /dev/null +++ b/src/views/demo/comp/drawer/index.vue @@ -0,0 +1,50 @@ + + diff --git a/src/views/demo/comp/flow-chart/dataTurbo.json b/src/views/demo/comp/flow-chart/dataTurbo.json new file mode 100644 index 00000000000..f6432c4e08c --- /dev/null +++ b/src/views/demo/comp/flow-chart/dataTurbo.json @@ -0,0 +1,240 @@ +{ + "flowElementList": [ + { + "incoming": [], + "outgoing": ["Flow_33inf2k"], + "dockers": [], + "type": 2, + "properties": { + "a": "efrwe", + "b": "wewe", + "name": "开始", + "x": 280, + "y": 200, + "text": { + "x": 280, + "y": 200, + "value": "开始" + }, + "logicFlowType": "bpmn:startEvent" + }, + "key": "Event_1d42u4p" + }, + { + "incoming": ["Flow_379e0o9"], + "outgoing": [], + "dockers": [], + "type": 3, + "properties": { + "a": "efrwe", + "b": "wewe", + "name": "结束", + "x": 920, + "y": 200, + "text": { + "x": 920, + "y": 200, + "value": "结束" + }, + "logicFlowType": "bpmn:endEvent" + }, + "key": "Event_08p8i6q" + }, + { + "incoming": ["Flow_0pfouf0"], + "outgoing": ["Flow_3918lhh"], + "dockers": [], + "type": 6, + "properties": { + "a": "efrwe", + "b": "wewe", + "name": "网关", + "x": 580, + "y": 200, + "text": { + "x": 580, + "y": 200, + "value": "网关" + }, + "logicFlowType": "bpmn:exclusiveGateway" + }, + "key": "Gateway_1fngqgj" + }, + { + "incoming": ["Flow_33inf2k"], + "outgoing": ["Flow_0pfouf0"], + "dockers": [], + "type": 4, + "properties": { + "a": "efrwe", + "b": "wewe", + "name": "用户", + "x": 420, + "y": 200, + "text": { + "x": 420, + "y": 200, + "value": "用户" + }, + "logicFlowType": "bpmn:userTask" + }, + "key": "Activity_2mgtaia" + }, + { + "incoming": ["Flow_3918lhh"], + "outgoing": ["Flow_379e0o9"], + "dockers": [], + "type": 5, + "properties": { + "a": "efrwe", + "b": "wewe", + "name": "服务", + "x": 760, + "y": 200, + "text": { + "x": 760, + "y": 200, + "value": "服务" + }, + "logicFlowType": "bpmn:serviceTask" + }, + "key": "Activity_1sp8qc8" + }, + { + "incoming": ["Event_1d42u4p"], + "outgoing": ["Activity_2mgtaia"], + "type": 1, + "dockers": [], + "properties": { + "name": "边", + "text": { + "x": 331, + "y": 200, + "value": "边" + }, + "startPoint": { + "x": 298, + "y": 200 + }, + "endPoint": { + "x": 370, + "y": 200 + }, + "pointsList": [ + { + "x": 298, + "y": 200 + }, + { + "x": 370, + "y": 200 + } + ], + "logicFlowType": "bpmn:sequenceFlow" + }, + "key": "Flow_33inf2k" + }, + { + "incoming": ["Activity_2mgtaia"], + "outgoing": ["Gateway_1fngqgj"], + "type": 1, + "dockers": [], + "properties": { + "name": "边2", + "text": { + "x": 507, + "y": 200, + "value": "边2" + }, + "startPoint": { + "x": 470, + "y": 200 + }, + "endPoint": { + "x": 555, + "y": 200 + }, + "pointsList": [ + { + "x": 470, + "y": 200 + }, + { + "x": 555, + "y": 200 + } + ], + "logicFlowType": "bpmn:sequenceFlow" + }, + "key": "Flow_0pfouf0" + }, + { + "incoming": ["Gateway_1fngqgj"], + "outgoing": ["Activity_1sp8qc8"], + "type": 1, + "dockers": [], + "properties": { + "name": "边3", + "text": { + "x": 664, + "y": 200, + "value": "边3" + }, + "startPoint": { + "x": 605, + "y": 200 + }, + "endPoint": { + "x": 710, + "y": 200 + }, + "pointsList": [ + { + "x": 605, + "y": 200 + }, + { + "x": 710, + "y": 200 + } + ], + "logicFlowType": "bpmn:sequenceFlow" + }, + "key": "Flow_3918lhh" + }, + { + "incoming": ["Activity_1sp8qc8"], + "outgoing": ["Event_08p8i6q"], + "type": 1, + "dockers": [], + "properties": { + "name": "边4", + "text": { + "x": 871, + "y": 200, + "value": "边4" + }, + "startPoint": { + "x": 810, + "y": 200 + }, + "endPoint": { + "x": 902, + "y": 200 + }, + "pointsList": [ + { + "x": 810, + "y": 200 + }, + { + "x": 902, + "y": 200 + } + ], + "logicFlowType": "bpmn:sequenceFlow" + }, + "key": "Flow_379e0o9" + } + ] +} diff --git a/src/views/demo/comp/flow-chart/index.vue b/src/views/demo/comp/flow-chart/index.vue new file mode 100644 index 00000000000..628d479f2e2 --- /dev/null +++ b/src/views/demo/comp/flow-chart/index.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/views/demo/comp/loading/index.vue b/src/views/demo/comp/loading/index.vue new file mode 100644 index 00000000000..efae9a43916 --- /dev/null +++ b/src/views/demo/comp/loading/index.vue @@ -0,0 +1,103 @@ + + diff --git a/src/views/demo/comp/modal/Modal1.vue b/src/views/demo/comp/modal/Modal1.vue new file mode 100644 index 00000000000..4c7a1d6cd8e --- /dev/null +++ b/src/views/demo/comp/modal/Modal1.vue @@ -0,0 +1,53 @@ + + diff --git a/src/views/demo/comp/modal/Modal2.vue b/src/views/demo/comp/modal/Modal2.vue new file mode 100644 index 00000000000..f2eed5053b5 --- /dev/null +++ b/src/views/demo/comp/modal/Modal2.vue @@ -0,0 +1,18 @@ + + diff --git a/src/views/demo/comp/modal/Modal3.vue b/src/views/demo/comp/modal/Modal3.vue new file mode 100644 index 00000000000..1b47a000e04 --- /dev/null +++ b/src/views/demo/comp/modal/Modal3.vue @@ -0,0 +1,8 @@ + + diff --git a/src/views/demo/comp/modal/Modal4.vue b/src/views/demo/comp/modal/Modal4.vue new file mode 100644 index 00000000000..86dd6bb57a0 --- /dev/null +++ b/src/views/demo/comp/modal/Modal4.vue @@ -0,0 +1,80 @@ + + diff --git a/src/views/demo/comp/modal/Modal5.vue b/src/views/demo/comp/modal/Modal5.vue new file mode 100644 index 00000000000..f6cf1f14f8b --- /dev/null +++ b/src/views/demo/comp/modal/Modal5.vue @@ -0,0 +1,46 @@ + + diff --git a/src/views/demo/comp/modal/index.vue b/src/views/demo/comp/modal/index.vue new file mode 100644 index 00000000000..f63635c622b --- /dev/null +++ b/src/views/demo/comp/modal/index.vue @@ -0,0 +1,124 @@ + + diff --git a/src/views/demo/comp/qrcode/index.vue b/src/views/demo/comp/qrcode/index.vue new file mode 100644 index 00000000000..bc5948afb76 --- /dev/null +++ b/src/views/demo/comp/qrcode/index.vue @@ -0,0 +1,114 @@ + + diff --git a/src/views/demo/comp/scroll/Action.vue b/src/views/demo/comp/scroll/Action.vue new file mode 100644 index 00000000000..8bf2a4a1d76 --- /dev/null +++ b/src/views/demo/comp/scroll/Action.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/views/demo/comp/scroll/VirtualScroll.vue b/src/views/demo/comp/scroll/VirtualScroll.vue new file mode 100644 index 00000000000..c3b146fd496 --- /dev/null +++ b/src/views/demo/comp/scroll/VirtualScroll.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/views/demo/comp/scroll/index.vue b/src/views/demo/comp/scroll/index.vue new file mode 100644 index 00000000000..f4eb8fcb13f --- /dev/null +++ b/src/views/demo/comp/scroll/index.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/views/demo/comp/strength-meter/index.vue b/src/views/demo/comp/strength-meter/index.vue new file mode 100644 index 00000000000..8671f35311c --- /dev/null +++ b/src/views/demo/comp/strength-meter/index.vue @@ -0,0 +1,24 @@ + + + + diff --git a/src/views/demo/comp/time/index.vue b/src/views/demo/comp/time/index.vue new file mode 100644 index 00000000000..9c4f9012aec --- /dev/null +++ b/src/views/demo/comp/time/index.vue @@ -0,0 +1,35 @@ + + diff --git a/src/views/demo/comp/transition/index.vue b/src/views/demo/comp/transition/index.vue new file mode 100644 index 00000000000..2964cbc1e9e --- /dev/null +++ b/src/views/demo/comp/transition/index.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/views/demo/comp/upload/Upload1.vue b/src/views/demo/comp/upload/Upload1.vue new file mode 100644 index 00000000000..7bb1ae517bc --- /dev/null +++ b/src/views/demo/comp/upload/Upload1.vue @@ -0,0 +1,23 @@ + + + diff --git a/src/views/demo/comp/upload/Upload2.vue b/src/views/demo/comp/upload/Upload2.vue new file mode 100644 index 00000000000..2ed3f342fdf --- /dev/null +++ b/src/views/demo/comp/upload/Upload2.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/views/demo/comp/upload/Upload3.vue b/src/views/demo/comp/upload/Upload3.vue new file mode 100644 index 00000000000..32cda4a4130 --- /dev/null +++ b/src/views/demo/comp/upload/Upload3.vue @@ -0,0 +1,73 @@ + + + diff --git a/src/views/demo/comp/upload/Upload4.vue b/src/views/demo/comp/upload/Upload4.vue new file mode 100644 index 00000000000..430db4af47c --- /dev/null +++ b/src/views/demo/comp/upload/Upload4.vue @@ -0,0 +1,178 @@ + + + diff --git a/src/views/demo/comp/upload/index.vue b/src/views/demo/comp/upload/index.vue new file mode 100644 index 00000000000..4aedd1fe968 --- /dev/null +++ b/src/views/demo/comp/upload/index.vue @@ -0,0 +1,15 @@ + + diff --git a/src/views/demo/comp/verify/Rotate.vue b/src/views/demo/comp/verify/Rotate.vue new file mode 100644 index 00000000000..9478c18b33b --- /dev/null +++ b/src/views/demo/comp/verify/Rotate.vue @@ -0,0 +1,18 @@ + + diff --git a/src/views/demo/comp/verify/index.vue b/src/views/demo/comp/verify/index.vue new file mode 100644 index 00000000000..ba74903881d --- /dev/null +++ b/src/views/demo/comp/verify/index.vue @@ -0,0 +1,79 @@ + + diff --git a/src/views/demo/editor/code/Editor.vue b/src/views/demo/editor/code/Editor.vue new file mode 100644 index 00000000000..6d2171eed83 --- /dev/null +++ b/src/views/demo/editor/code/Editor.vue @@ -0,0 +1,84 @@ + + diff --git a/src/views/demo/editor/code/index.vue b/src/views/demo/editor/code/index.vue new file mode 100644 index 00000000000..b5c39667e62 --- /dev/null +++ b/src/views/demo/editor/code/index.vue @@ -0,0 +1,89 @@ + + diff --git a/src/views/demo/editor/markdown/Editor.vue b/src/views/demo/editor/markdown/Editor.vue new file mode 100644 index 00000000000..4d0691345a7 --- /dev/null +++ b/src/views/demo/editor/markdown/Editor.vue @@ -0,0 +1,51 @@ + + diff --git a/src/views/demo/editor/markdown/index.vue b/src/views/demo/editor/markdown/index.vue new file mode 100644 index 00000000000..14e4bafc163 --- /dev/null +++ b/src/views/demo/editor/markdown/index.vue @@ -0,0 +1,85 @@ + + diff --git a/src/views/demo/editor/tinymce/Editor.vue b/src/views/demo/editor/tinymce/Editor.vue new file mode 100644 index 00000000000..5eae42a6e94 --- /dev/null +++ b/src/views/demo/editor/tinymce/Editor.vue @@ -0,0 +1,51 @@ + + diff --git a/src/views/demo/editor/tinymce/index.vue b/src/views/demo/editor/tinymce/index.vue new file mode 100644 index 00000000000..51c3760b69a --- /dev/null +++ b/src/views/demo/editor/tinymce/index.vue @@ -0,0 +1,15 @@ + + diff --git a/src/views/demo/excel/ArrayExport.vue b/src/views/demo/excel/ArrayExport.vue new file mode 100644 index 00000000000..c16dc47fa7c --- /dev/null +++ b/src/views/demo/excel/ArrayExport.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/views/demo/excel/CustomExport.vue b/src/views/demo/excel/CustomExport.vue new file mode 100644 index 00000000000..8d0abe6347a --- /dev/null +++ b/src/views/demo/excel/CustomExport.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/views/demo/excel/ImportExcel.vue b/src/views/demo/excel/ImportExcel.vue new file mode 100644 index 00000000000..78af02e4bde --- /dev/null +++ b/src/views/demo/excel/ImportExcel.vue @@ -0,0 +1,46 @@ + + diff --git a/src/views/demo/excel/JsonExport.vue b/src/views/demo/excel/JsonExport.vue new file mode 100644 index 00000000000..af6ee097f27 --- /dev/null +++ b/src/views/demo/excel/JsonExport.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/views/demo/excel/data.ts b/src/views/demo/excel/data.ts new file mode 100644 index 00000000000..086fba54dc9 --- /dev/null +++ b/src/views/demo/excel/data.ts @@ -0,0 +1,59 @@ +import { BasicColumn } from '@/components/Table'; + +export const columns: BasicColumn[] = [ + { + title: 'ID', + dataIndex: 'id', + width: 80, + }, + { + title: '姓名', + dataIndex: 'name', + width: 120, + }, + { + title: '年龄', + dataIndex: 'age', + width: 80, + }, + { + title: '编号', + dataIndex: 'no', + width: 80, + }, + { + title: '地址', + dataIndex: 'address', + }, + { + title: '开始时间', + dataIndex: 'beginTime', + }, + { + title: '结束时间', + dataIndex: 'endTime', + }, +]; + +export const data: any[] = (() => { + const arr: any[] = []; + for (let index = 0; index < 40; index++) { + arr.push({ + id: `${index}`, + name: `${index} John Brown`, + age: `${index + 10}`, + no: `${index}98678`, + address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park', + beginTime: new Date().toLocaleString(), + endTime: new Date().toLocaleString(), + }); + } + return arr; +})(); + +// ["ID", "姓名", "年龄", "编号", "地址", "开始时间", "结束时间"] +export const arrHeader = columns.map((column) => column.title); +// [["ID", "姓名", "年龄", "编号", "地址", "开始时间", "结束时间"],["0", "0 John Brown", "10", "098678"]] +export const arrData = data.map((item) => { + return Object.keys(item).map((key) => item[key]); +}); diff --git a/src/views/demo/feat/breadcrumb/ChildrenList.vue b/src/views/demo/feat/breadcrumb/ChildrenList.vue new file mode 100644 index 00000000000..5569f8fd995 --- /dev/null +++ b/src/views/demo/feat/breadcrumb/ChildrenList.vue @@ -0,0 +1,8 @@ + + diff --git a/src/views/demo/feat/breadcrumb/ChildrenListDetail.vue b/src/views/demo/feat/breadcrumb/ChildrenListDetail.vue new file mode 100644 index 00000000000..84955cfd01e --- /dev/null +++ b/src/views/demo/feat/breadcrumb/ChildrenListDetail.vue @@ -0,0 +1,8 @@ + + diff --git a/src/views/demo/feat/breadcrumb/FlatList.vue b/src/views/demo/feat/breadcrumb/FlatList.vue new file mode 100644 index 00000000000..adcb49fe9a9 --- /dev/null +++ b/src/views/demo/feat/breadcrumb/FlatList.vue @@ -0,0 +1,8 @@ + + diff --git a/src/views/demo/feat/breadcrumb/FlatListDetail.vue b/src/views/demo/feat/breadcrumb/FlatListDetail.vue new file mode 100644 index 00000000000..f427e0b66d4 --- /dev/null +++ b/src/views/demo/feat/breadcrumb/FlatListDetail.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/demo/feat/click-out-side/index.vue b/src/views/demo/feat/click-out-side/index.vue new file mode 100644 index 00000000000..8e87a5fce6c --- /dev/null +++ b/src/views/demo/feat/click-out-side/index.vue @@ -0,0 +1,27 @@ + + diff --git a/src/views/demo/feat/context-menu/index.vue b/src/views/demo/feat/context-menu/index.vue new file mode 100644 index 00000000000..f0601acb9e5 --- /dev/null +++ b/src/views/demo/feat/context-menu/index.vue @@ -0,0 +1,78 @@ + + diff --git a/src/views/demo/feat/copy/index.vue b/src/views/demo/feat/copy/index.vue new file mode 100644 index 00000000000..7ce8b4d4b97 --- /dev/null +++ b/src/views/demo/feat/copy/index.vue @@ -0,0 +1,29 @@ + + diff --git a/src/views/demo/feat/download/imgBase64.ts b/src/views/demo/feat/download/imgBase64.ts new file mode 100644 index 00000000000..306bdd1a871 --- /dev/null +++ b/src/views/demo/feat/download/imgBase64.ts @@ -0,0 +1 @@ +export default `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wAAAAAzJ3zzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAB3RJTUUH5AodAjIGrlVB/QAAABBjYU52AAAAygAAAMAAAAAFAAAAAASpeQ4AAC6ASURBVHja7b133G1Vdaj9jDnXWruXt7+ncDhIEVCKaCiidATsgIjRm1gTu1FjTbl++ZmYqMmNRPOZm5tc400+Y65iwUQFEUSRIkixgNI5cA6nvX33veYc3x97v2IBpZzzzvdw1sNvc/5gH/ZYY80xy5ijQEZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkbG7kZCC7CaeevLu3QSyPXBKBL3RSIHKOE050EtULdqakaXvr/E+EiR9kbLRz6Wvc5HSxRagNVMIw+bnhyz/m6HbTtNvGjsAQENMNZk+FGB1EOjBfu9okLvbvj6N0Nra88kmzIegte+yDEapbRKMPp/EuZe3rcqOm37sjFKqQOKMDSFFUXxRFicVO1mqZi7/QMLS70DahS2QO9guOB92St9NGQrwEPgUBZFKHZVtr28p3ko5PucWko5MZfKmD5oACuKAM5jU6WP5S7N8+W0XLm6NYomHuZMaM3teWQG8BAkKoypknThoAM3c9ed6548lcrrJvpyTFElBUQDyCUKqUK3iMYJaTevhdufZG470LEzl0dSpyHE2qPJ1stf4vfO6eOdYTTpykc+V9BXn+02rvP+NQf25C1jTkaWvxfEADy4BJJJGB+D+aLcdGXefPCGIpf859tpvPUjsF8v5Ue5iP/9nuzVPhKyFeCXaKSGMsIb0qa+71yopsnhY44Xl5SKCpqysk6gn/+ttsHZEm6yhh2PiaI+B6y1+tI7nNz8nI/RqKXIxVWrLlsHHjHZrnHI61/S4/Uv7ZFDeJZxfK1TN7f7/NSY46RJJ4fESuQGXxV50CGz2z8CMjxxCHl8voKvxBgDWvFa3q+vJ2zo6bPmYgovWPSMpDDi4R1/mVnBIyEzgCG5rrJUEwq5vlxUUX5U0uJG6b90BP+CopcIFdUAW0ZhsAqkIloti5muSZxYJPVIrDCd6uRBHf3dExf1hOtKhn+/aompLqzph9bonkFmAMAdP/XMN6HaFSYMVNqKWibXpJw27uQAA6oS5rwkCl7QbpFOroIbSTSyg+MATtCSYg/qc+z+PZ711QrF9adWGffIXAR/8iHlbR/LVoJfR2YAQLEIb7kgIul6+br36lLqlT7PH03NYWUnKGEOvTC48PIWLdehUhq+sKEwDsQAk47ilOPkw1uc8pp5H310Ek0FZhPo5kJrd3WTGQCQ7oC/f5Njcga+9zlHtctR421eVVT2UyHI1gcGP9q3eM3jp8ok4wkJyi9cQHjAojrZ12MP6uorjTf7vmIzTPSQdV1lrB1au6ubvd4AvvLvjn4VvvWdlD/+RsynPpVbW0NOnOyZA4pecKEEU8CD5PDxiPQLOSVGf+X+wQ8/U07tfqkeVVROcnVG3nMe2lKRpoW3fzTbBj0ce70BnPYyKBeRe7SAyL9ptSPPGlF5YcmRN0DIg29LcKaAn6pi8xGSPvzds+SAMafTa1N96VifA//gIjhiUbHAfDmUdlc/e60BvOF8x++90DNzr3DZxxwX/YGamdYr1ud2csaY40hjNHYSaO8ztLo0jy+U0fFY45xg/MN/nZ6gBU9h354+c21fT7m3Rm1tpHLMnZ5SCu/6s2wVeCj22ouwnKRcMJ6w9m+cbF4b6fQDfuTWd7tzp3bIs+siSIR6DTP+vYJa0XodGauptYLob4g8ciAxMOG1tK7NuYd57v2vgrnw+3V6hy8if310sHP8qmavNYA2lnXXwuw+0L3Hs7kvk/v25XkGDogi1AWa/I1Cx+B6eXrrStjRhFiV33gWWQ6TThQ29DiyqZx6TY5vrumxfawPR18PZ/6ZosAHP5CFSSyzV26B3vrbbSIVzjsk5e++bjUp6/iagp6xIWcOq0UiyzkvIXADt44UalAuKMnQ1fmI/u7wz3HVaK3j2HWOM+uqxU9UVU/30JS99IX/GvZKfex3f0JsUk7ZIfLBs1uMOPesMe9fVVKdMhrG7SkMLr16gto8ur6quXpC7PxgS/RI8QOD0RGvh+6X6msnvDzlBT1ho1MZ8craYG6t1cleZQB/9D7HO/5bj1LJUBDkhb+1qD+tMz7iOGayL4cmTsSF2h0Mb9t8gZ6tS7eUg/gxhF0rkAIjXuVJTp9ad3pyLmXqdTs62jHCJgt/+sHsOLDMXmUAqXpmY88t9Y7cU3T6h/cW4jUNe1alL88pOyLDYAZdabmEwfalLfhCCZmsYhID/jGm3OjwQDzqtTLtOXfa8YwPTeb54LVLxEB7xVN5Vi97jQH80XsdiRdG+obygvDZf/oP+mI2TLTlnHpfjgLESxi3z3Ca17SAq5Qx0wm5+Ne4PR8JPUGtEq93+oy1Ts/YHsn0l44smcOcp+DhT/9S+dM/z1aCvcYA2nenfOPGNgbkK2+J9V3v+92xInLmSCqHVYf7nlDDwQ/jGcoj4ksVQUQfd8aZBzGKjnk1E16fs1b1JTcUTeXzIwZyyP0VKHYDPfAqYq8xABk1PP/wMhNtOO0rjmKffcdTObfsZR8TsNDJstuzk9P+aFFlJFHj9NEdfB/yeQEVxCo67fSgjY6zxDC5TwtGVZlogJ4Z4olXF3uFAbz9vC65kkH3gbKK6IiZKC7oaeN9jsoriZNAs7+CejAJ5Gui1Ty2AGZXWeJynFDdI2t7evhYX5+vjrGvedFKpFI8Hj6wZe/eBu0VBjCRxnQ3p3Lx3U3uKXqJ73Sn5nb680upVKxIuHgfgZ6IzxdF14xIVEwwzu9aY/SDrDKtqq5f1+Y1ky2OO/tvPS+51NP8O/jRxEo/+eriCW0Af/CylLe/LKWRE/JdWLNTmO27ymiLZ4515PDIY0IUuAKWR7l2inSlQq+WqI1BdoebPgVKCvumesC4cuJt/2w2HPy1lOQLcPB2+MB3995V4AltAHnnmE0MHdfjmprq+n5UGk/NC6qYE6sqkQx2IEFwHvpAuYKMVjAJu08YD2KBuiee9PrcdX1OfSs5ueZyiArI/DMDKWEV8IQ1gHed5zjurp2M9ZWSE9m/bTBi9htL7e+UHYcLoIGC3YyCM2iaJx0rEU/nyBkZpDnuDoTBKiComXZ66IYl/7zKPPs/8zo1T/lXpdKC9/+x8icf2vtWgiesASjwzSdPoqry8f1RiUytqHL8VN8cXHWCH+T5BsErSILPjZAWSuqjXeD2fCS/iYG6USbu02Mn/1V/e8f1Mv3pUdHkfyNLFZiIw+gjJE/YaFAnUOjDvj3Pq+5T8s4fOdKTV5Q8kxboBywK1ha8yalfVxFbSTDpLj74PhQ/c4sKOrbEuo0NnteM+cZxli0HtT3NimG2GEoj4XjCGcA7X+oxCqjy5Jl76FYOkK7vjU/1/emjqT0mUpJUwvj9ZRjvo3l8riI6kiMqgFmpCibLcUIVA+usHrQt5nl35GTLeTXu3bTFs5B7wm4IHpYn3BP3XYcj5n+Egnxr7QFsikkqXk4vOfOcqpPYarh4Hw/0EV8qi47XRWKLPNZ4n8fK0OWrZdX6Oqe/8yT09Fcfa+Rd/8MwbldaK+F5whmAtxGfnSqS81Dpe3qG8njfnF53cmSkA997qHgfD76V125SVDeWDDK9QkQnpwKxh7WODdOeJ31rEcOVUA7kFAjJE8oA3nxel09cGnNwdyMfeqGqVa3lvD9rxMlxFSfx8s1oCJwHNUi5hqmXMXnBmEBOF8NgImgLSx1YrMVAZeCW3dt4whjAxW9OOeqS7fzes3tUUy9v/KrDqj+4lvpXl7w/wGqYCg8wrO8jqMvhJmsSjRUkVh5/vM9jxSjSNvTvzfGdTTn53ifm8Hrkq1mM9r684SeMAYwlcMqrxpksiXxgCp2LbbHiePpUj8OKTqyXQC93GO+jOVIzIp1CXjU3LDQaQiAD2gNmI3lgZ1k+/aM1fLdxi+dtf/spFgpBNBSUJ4wB9EX5xBbh6S/YobV9YyZa7sRaKudXnNRCzf7L9X3aoFEe1taIStGuj/d5pBggAlkUadwfyXfnErl+dInuTwuG28vQyg7Bex63vc9z27s8zS7cP6dyzsvW8qYf9iZrPZ470ud4A7ELNPvL0OpcDpevwESiuYJgAqblageYtdy21crnupbtf/dGaAj6uxen1HvhBAvFHn8P8JrrPd+50NL9oOGz38grf05hBE4SJ0ePOLFGBiVOQmz+vYKz+GKNtFoTMaKDW+AAslgGq84OI90dVq7sK9/acS+NT/4p0i6jL/98DJ8PIFhg9vgVoBgr3Aqttsq17/GkjolJJ+eVHUfYYaJLiAFnBhUefCvRXrWETOQ1EkFCHHwVsApOJJ2xcvkDVr783G5//t6N0IjRhT1+FDx29uhHf+fLPO8/0PCfn0r55Lcts11GL93sTs935Zi6Ss5JOLenejAR5OtCpYQtgl0+E6w0BrQrsN3QmjdyyQMR1x7RhDNngc6gmNbeyh577PmD8/psfa5l583Q3NaVzlp0c1OOaSz4t1S7cmgJsT5AU4vl3kZdwZuS+MkJMaNFjPFhOkvCoLzKkpH+3bFcsT2Wz6jRu7ZHIg2ErTF88I/3uvuvn7FHrgBvO29wZXP/n30N1/O8++tet47GSaEnT6/1zDMSL0lQt6dCL0ffV7RbTVQKDCo8BOksyaCx35Llztmc/NNckRv+n39rs6lg1NZgbC8vkbJHmv47XtJjcylmfdvJA7Hoho7mvPFn7NMz71rX5dmRH0SDrjTC4OCbKmpHceNT4qbymiSChOjcaIYybRe6dyfmwvsK8t6W5f6RPrLZoh95PwTq/LRq2ONWgA+/wfHDO3qs73gS5ymkjqbxIyM9zq729TgT6IIJBm5PBe0WSEtlmM6Ti02Ywb9MH2jGcnMjz5dqkc41q5CL0dfNe/7mL8LJtVrY4wwA4APvKIF6/urd29R6SYoqx46l9ohaKhGgPmCii7doUqNfrJDaZYsIIIsFnMKckXa/IF9fv1a++vr/2W4eN6dy0hh8qW541168919mjzOAnnhmroNS28kb/3qMqnMH1nv66qLXg4cn+mDxPh3QXqxuskQ8kScGgvn9I4W+0N1i5aodkXz70zfRPFJzRCVRm4f3vj8b/LCHGcCH3+iInfAPNzZ45mhCdSSulVROGE85vuC14AYN7YJgBvV9fFLD1wrYooTzsMnQ7TlnZWnW8qUlw02f2ejkhBdbWg5u2hlKstXHHmUA57/CEMeGr3/3B5z16Vs03+PpZS8vqjkp5zRcfR8UOuCjAn58REw+gdQRxBqFwfX+kpH+1kiubxr51pYWM/8yZ3lz3OO4Frz07dnsv8weEwrx7vM77FsC11R55f95ph6zlUJ6kzup1OfZsZKE2vcP9/jazkmvWkbHE03ygkkDbX0E1KtI08iPdsbyqbboHX85plwwY/TWiYReGkhPq5Q9YgV49/kdDgL+7IKUH06lut83++UdP07PyLX19JqTohCunakfdHKXchUzVsPmLUbCDX4UZKdRv81yc8Py7WYsnX/cIRJbuOy2QEpaxewRK0CnkvJDZyg3kNp9qs1Uxkd65vykJ0fGDKodhAov7gnay9GfKiMTeSIhnNvTMEh3nInl+q2Jfq0iMjsXQdPBnIGPf1z4eBjRVi2rfgV4x3ldnoKQaxv+4tKcakRcUI6c6JunVZ3J+zBbbWAQ7+MjvKnRKxTV50RlOfl9pTGgTmFWZHEuks9vrpv/fP3tvr9P22OKovW9Md/xEbDqDaBr4c6FiKmuyBvP7FJsm6NKqXlNWdkYq6KPoY3QrkCAjqLEsKZKMpIn8j6g2xOkZejfb7l5ZyTfm5jT1hfHlW5sWMzOvA/LqjWAt5/teNfZPQookRNeviWmK1Kq9OXZo105MXHkQsX7yNDqfB5na6SjeeKiwQa88NW2B5/I1skx+ewBY9z6zv8uFNcZPaqqFDvw9j/NrOChWLVngA5QBCQV/nJjn/GOkdE+R1e9nDyamlIEOAlT3NADKWhcwo2MiI+tqneB6owyMMYdIp68XH/CPvJfVy3o9p/8PXLG0aI8A04IkhGxZ7BqV4BTeoZRJxScl9+/Dx7I92vjfc6uOk62qNn91TQfGjPw+mgz0X6hiKzJaRIbTKiD77DekMxbbn4gki9TYNszXyBM1EWxIFNh5NpTWJUG8LazlWuTPnPW88Fvpxq7QiXy5qR6KidUnRQ0dKILQqkmVKtIQbChAvDMMO5p1oifM3LVXMSlpL73hj9Wdrbh3h/Cpr8JpKg9hFVpALMemlFMUY38/kklYmVt1cv5ZSf7J4ORHyzepye4Xo7+WFVktIBd7ucVKM9XUiHdHssNC5F842LH5nNefI9/xgbB5mDjK4UNv5Ntf34dq84A3nOuUgFihX17cE0spuw5ciI1x5eUsg8Y74OHNMH7Gr1KXikN9RdIHnUKLTGzjUj+v5kyV+wowZkffpJctsOT7OWJLo+UVXcI/knU5cmaY7YOjW7EaT3/7Ir6V1Q805EX0kBpjqrQU8jlseMjxJUEE6q9jDCwvCWBbYbbO3DVPovMv21J5WtHbtdrTt7Ki3oxR1z5490vi4Iay32ThtkDxoE68dLd9Kv7h1HOo2RVGcBLL2hw5DU5miDtFnpzmfz+bU4ec5ycUwlW23YY1k8vT69aQSfymhQMkg7jL1ZaIBnYo6TYu2Zj/cyPpxt3/OA9ZUb/eQs/nJ6TSs/iVuCUZHTQZaSXeNpFUVgCGugelGm+agzg5rtT/uKiDjt3wpsnvP53cWa8HT1tIpVjxlJTtqh6CVPiZLmbQKGCq9ZEE1HEhdk/Dp1fsiSmO5PTb85X0/9YKvVmL1n4Fw6/+niFBF0hLYkqNhVKM4LrCOsnLKU4TztKMbqVS2Q6gIYeHavGAHqp8rT9DZft05QjOqlOOzN5kk9e1hF77KwFL4pnZWfb5S4aTsW7mP5oVSQtk2yBQY+lAPIYBa/idsb+zhunG3dc/NSttXbO5Q+/6pgCpD5aqZvx4aroBWmVTTo7aeZ+WosXpLWdSlpkKdkz2tCvGgN4xgER/3xll298qQuxY+msZOONvnfcjsjURQdLfogBJ6r4yPT9SPxAeVQqhTxjzj/YYmalDsCD31KMmo6J3c7u2Lx/YP3MGc2kc5R4ceLFDgtSrJSKVME4QTpFu3WpwmWjre636ozMO9cUuEcv1xYny+ruu7QqDOCYP0yBLt++QeUzX6xqPmHynf/SOvm2NF2/uSDByogDoNqVUjTv18R9VzSDRNtQojiLxL6bjC9tqazZOZ0UWk+13iAefABflHHgLbSqZq5bZd99Ef2pn76ECu2/YZyTKLCP3sN9sjGYzn7jM4QWAODog4SrfmK55eN36stP/H+5d7s72qDniDJlFKyqWFVW7MPwTw82tvNSiuZMZCatMmIZlBm0rNBHf+5PxSX5brs8sTOOi+0ENYhf7jy2oiz/XifNm83NehRRNKcr7rcvN+2Nr2UH/8iCABzL6q65HnQFOOiNHVDBo/zeJxfgzKfK77/wkH0uur57snP6lFIsEg0CPlf27KsPfrQQ4WtJhJE8Ts0vfGel8ACC5PpbctXGjkKuM21UR7w3g/8QAOOhVzTdual4sV21OYHKdtxxf8LsCRNED/yUDfPPZgvrsBT0LtrypBBi/kaCGsD6OMdOB1++2ks1HtOD1pFceFXnNCQ9vRBLPo4CNbMebPCd5sxOLUddzZkxwIbai6kKqPik1G4nY/Me4+vem1wQYZb1o8z38jK/NGrr/ZiSBRbxU1fROhfMbXkal3fpYshzGDluDibsryfYFmif189ze39+cGqLOsz0UyT264s5PSsXc5gIEi7TRUGkTyWa0aJNUa2iGmyyEFFnk3R7UmuYuNRcL9bHBOryvRwK3s/LbLNmG/0cNUUKgBrIWeT4BE7LY0dOo0ANkZuZ4UzdynXaCaXChyXcGcArxkIjXZI4cVrIt8d7rvU8I+5pkSzXWgiFpMTSphTVJGfGGBwLgqDOgNFuMrqwKa4vdhAdU5UwPd0H6W4KtBpjcX5hKprwVnLDtAwZFgYuAqc2cc+5FC3MoVqnJoeRo78KW5AFMYDD3t8gzg3udNPU0e+kONyTU9JzvOoGXXkX+4N4IDENrSVbtGBzKloLXD6zZyK3GNeXSlGxMxj8gWZ/FNTQ65bM5kbdpN2cjKsMyr7//Lc8engKL4qRtaDM0+UWety2CvtQBjGANRtytFse8YYFr6qxmRQvp4jKEUCY2W2AgvY1b9u+FhuNJCJUWWcGe3+TuMW43twaFdsTiK4JNvgBk+I0ktbiuE3bFRPJw5bXlwL4Y2vIadeyT0U5SGdIZQnPvnpPMPkf8plC/Gja7GJFiIp9pkfVaE9PUMcLVLU6/EqYgy+giZ3Vgu0SyQZEaiH0MxBEFC8uKrdahemdXZvvGnWBvdZCo1cw843RaLqTM1PLvRB++VuDLZFuEPT862g/Tfgm13Ab51PmeFbXxdiKavTkD3c49eM97t8M/Y6XO/5uJ6LJOlU9BdXDV1qeX2C5b2klams97mPIDdwvIWQRAG8Stzkqt5dsobMv1lcf7//2sSI6dHuWTH9xMu50iybh1zRXGeyUxDbxT/tnFk85l6eugZ6ewBYET1HvCvUov8KKDrjL3/ttJqZjDjg0L2/56Ki+8otPLoroc1Q5USFHyIOvSE9js11LkZA3dQyhWmwsGwBJrbGUjCx4xI/jJQmmm8HRd7ZTNs3FUVvzlug3eAXEIHTQyo10nnchSyfCgXYTnn+nKeXVcf8KrKABHPK6BX734tPpO+W+29r8+5832XZv+iSf6ouBg4dTbaCDr4KVPpVkh+ZthNdxNFxxW0WdSdxcPLIYR+XmCCjqwwyaYWSd7xXNzmbddvsJEx7JP6K/C9YiR8XIWWVY36JDGWE7Hc5YJW7RFdNqzwg/urjJj77blJFiXuOEybtv6ZzWbesR1krI+RbAayyp1qIyOVNCw0mjXjCxW4xHlu6MSi0jolME7OUmHvVGegsTcWFxNBrxhuiRKkcGa5kBju3iXwDR6CJe15GXkymQrgK36IoZQN5DY6djy1KXOzctsjSbHtZYTM9NnU4PAx3CHHwVSOwM1Xir5k0FQ2XF5fh5vDgTu35udD5v852qOhOvWID/Q4ljafaK9r5m3dhuwgg8qhc1TKbT/VI42yAHQpPNzDCD41jyXK6tUI8GrJABPOkNi6gqncRRz+Wo2miCmJNNJE8XISbk3l9xWrBtrSUpljw+VD6TDNyeuXQ2rjVmo2JrrURuNJheAJvi3MDt2e8WJZFB6MxjMcZY0SNzcOZfsXHqt9moH2VOAF7LjpCPuDIGUCzm6EYixVyBHJK0RU9LRU8HzQXd+4MnlnmKNtKc2YBIIdzWB0VxcbU5l5ucXZA4zauTcLFag6vIhW7ZdBbHzD69REaXm+49SkRAwY8Kes52eid8hlGjPEm/RJNnBY4W3a0K3v/NKT5t0uum9L2Suj7WyLT3cqZXjpCQtzqDg6/TWjyj1SgGnUbDuScEUon8zrjcJiq2N4hoIdzBd5B93a6Y5uJE1OrnzRSPY7JUBrWUUvSQr9E67R623fwF5u8oEvvzKVPSu/AQJGJ0t2vY2Bz9vpPEODXeV7zT01COFTRcNOOAHrGZpxxFmrclQkTVD1FvwKom9caOqNowIn6NasBIXZVUYGezZtOlkajmDfI4B4qYQQ+H+Fa6J3+BpedCXHAIn6YhFuFwwgyH3WYA+78lBf0JzvVJ05ROx+FVD/bOvwT1+y0rJshTeyAybS3HM5qzdZRJJOQlHKlJ0qVkfM7ElUZOFQ0Z7amGtFsyO1o1a/sJU7Br7iBkcEF2YIQ8rwAHdZmhgtDAESFB3KK77aW7Xg/kENCejKzdpkmpPA56AvijlIAHXx38SwvWaT02GpsIH6658MDtmc7F5cYmW2jXET8VNA7Woy6WdG46LjTrtgSYXekTHp4jDkvhHGF0QwN0X2K5kjVB3KK7zQBsUgCUl757VH/wrxegytGq8mKFZc9GoCkOT2K3aSma05wZxfCILnV2CyrgBVvopcnEPDbpl9WbZMUz4H6mGlDLfLdgtjRrptiLqMqunRqW3aITDn2hok9L2Y+7uUbfy1wQt+guN4B9Xtdg3WuXQDyu15V/+It75IDn//2UOn8aym8x2NuGmeOGM72WbINK1MdQRTVgiIGqSdzOqNpsxZXmtESuEHDro8bhennbWhyPer2c5GWwUu8OgQzoQQY965XsPBA28BFuF4CT+faKPvcuN4Buv89E1eJ7HfL5go4kU8V+t3ma9/1nK+SQFa9u8jNE8BLLEsWooAU7CRKF3Pogksb1xgO50YUFMa6uKiEdA4phvl0zLI5H+6SxKe+m7cGwmozmQc9skz5XObagHK2fZlGUM1b0oXe5p2H7nKdcUJx30mg31RpZo/gXq+phBE10UTQybarx/VqKqkCdkG5YlVSMW0qqzUJUbo4BOQL1eh1OSdoqm7mlUUsaMz3IcNx9Pzl80n2vo3Pqi9h69Tb6N1xLO/0v2pT1bgAast/j+IlHxi59yJvvTuEOIYm8bPpMXrs9al79SQi/BYH8XA/SJzZtrcaR5kwBr4YQCfcMZn+JXDepL22OKo2cGL+GUKHgAuLpKcwujVjTrJqyyO4XRhAVhHvoP+MiGudcS38sIeZCmiLAaSv0+LvsOTt9z+EbLaeeG5N6B0c36afuiNS5l6C6XCQyULyPQmwalOyS5swaDOMrLscvyCTO5nuN/ORsLyq2rXoJFnwnHtTQ7ZbMzlYtKvdjmdKVCb4TBm7RNRHy3AI87VAKxtNDgCtWSCG7zABy0WBs33i3k0++oaSvPy83gvBs9XIMoWP9FbRo2zqatIkkWHmT5fhIE/mdcbk7awud9Rg3PtiJhdn+GK/0C8bPrY2TdsUkK119e+gW3ejh7FvpPKVAXtqktFZIjF260n36sh6z/1bX0w6LYq/6LEHPUtX68D+H2m+nxGa7FqKez9lxNRIHM0U/yPONK61ufny2J3FaHbg9A6HgRXZ0i7KjWbf11EpphU1x2S1acugZDn12Hxk3lHErJMAuOwRf+N0+Lzsh4d7/aNuLv58eum3Ov9Aanm4G2gxz+PUKRlTL8TzlKAEdxz/eW/3HjgpOrFuMq0tpVF0cRdSETHQRj+tUpLk0FvX6ESXCFCRYtrkNoKcL/n6le9EwTmy3T1W7zAA+/KWuXPDVvnZ7Pv7K99KnzDf9ofmYvAgapLrbACW2XapRnqItoBqswZJ6gzG+n9SWNkXVZk7R9UK4+j7i1amRhaXRKLcwFo14S2LC7VKHvU/0ADCHgrtopXbMu8wAbt2WaqcnFCKcWt1ajmQxEUFDKdUrxHaBSrRFC3ZEhXqw1pIAHjWJ7yUjS/mo3KyikoTKuRcP3kjarthtrbotphHTITrv/LxIDCzgTsHfqitYeH6Xrb9rppTIwYYy6dS0/2Ec6ZXesyXI+B8U1PXkTE+rsWpkCnjdXbeaj0CeYZpjrbklrjSrErkxAjoFjKOrkSwsTti4VbLFcEfwgXaGn83ApcDV8aCA1oroZ9dtQFPL+vWGbV8tKt7M5ZLo63FkLkMkXS65sxIP9DNis6DFqKd5sw5DJWSFB/WitthdSiZmliTXi9UFjPdR8JZWp2zmm7VovB/LRMDZXxkoomkwlxjMlYLOpOQxe5oX6IP3VnnKVIX15yt3/uud6bFPLf+gXIovE8PWFXU7Dpr2ei3bWa1Fi2oooIHSHIcVFYzVWVvqpFGpvUGsq4UubNup2HRuOpZeQeKwtQh+Nv1vjpAvV8n/qI/3EUJhhdIhdpkBvHPdTpo9pZizTJ25UW66pdXPJfZqQS5RkSVWqqOQSF+smacYWc3bMSRgYdvBQJe4tjSXG5tvi01H8YEOvgOcwgPtklls1OyoN5KErjXn0RkHl3q4sUUvjYjxeGorFKS7y56/ms+zo9kl7Xqq5bz+8IIi5UJ8J8oXBe5ckafxihhxWopmtBDlEKYI2gNBUoncfDyy6OPaYl5EJeTBF8V3KnaxWbfeWWoasA7rsJ8ZJeKb11D6wjTF7U2uY5qSGgw7WZmw6F02OH7y18NqIn+UckhHWf/aBfGqfWvkJhH5jsC+io6wO+8EFNVYer4eowVjf1buMATeINa140rz/qjYroGuUSTMVmwQ75O6RBqLk1GlMWZLGGxIt+cw5W12lPwVz2f6ujx0v8Vz5ARq+rEVPB7t8hXwOGB8tEQUGT2gUgXDNhHzBRW5YTjud9fgh1hmfSnapgVTx1ALub1VDzZONTcxZ6JSu4CXYPV9xIOPpdWu2M3NqpHUUFvBbpK/ohoGv50ClyWYr/89LP0P1rIPZf3bFRZmlxvA1R+K2DHbJooM97aaYiVKxZjrgMuBneyOOXk54K1gu9SSFCtlfMCkewWTuLm41toRVVojEqflgI5GNY5umkhrYSpKugWTD+z2XGYz6FfLRDcD7MP1fJnncTormxe8W85AP/lEiW4vRQwqvRbdfrNpjHzLGK4Q6O36X1RPxKIWI6FoJwgY7zPo52U0KrfnkvH5OROlZZwJVvxGFPGWxU7Vtho1u64fyUhgn78AS4K5TDHX3sSObsSdch+38Sy+wKWysqrabU4AI5a4INh8HvGRJAXzfRvJV0TY+XPKePwM4n1SrcSbtRI1VKiiGuxwJ4oXo4241I7iSmOtGF8MdvBVRRRt12xvfiJK0+SR1/XcrXIhd1rs5xNyt++HIyZWqFMIkBKx235x0/+qECXgOoYksfi+64jwPYUrURrsulXYYU2bSpyjaKug4WZ/bxCjaVJb2hzXF3pi3IQSrrkeKj01bGnVbK9ZNWMq2NCFyRVmgCsM5vuC9rdQlD6GA5jkGys8+8NuTvzpNpTI9okKsR58fhWTjzdF+fhzEpnbHtTH40ABa9qUop3ko5qKTIbK8hrK44jSZjI2l0aVZqyKDZbm6EGN+FY1WmjWrHURoxCw1OJQrAjzvYT4yxaZq1AmJtIyOTbRDSLQbjWAuz9ZhYKlNF3kpxd1JRkpNAuTpSttbK9Vpf24B6uqat50dCRuaSLgQuX4DsqbSOSbUaUzY4vdSbF+3cDrE+zWN01jac9NRbVm1dT5WeuiIKhH8egMRJeNMnZ1k9/pLdJkP9bRoU0vUCPt3b4i3vGJKrkqJMVY1x8DlWl2iuGrgn4PHkd8pqJYmaEQLfmcmVIj5ZVU3C/KoqgKUbHTz0/NtG2xk6iXx1pJ+XEjCi6W+U7Fbm5XTC41VHZxfZ/HIlLbwzdT5Iot3NuBvxKIeQFCV/YNJtiKbAkbW1Jc23PLZ2dIKjhBrxK4BFgYfuXRvZyB2QiluKWVqI+VUcIm3XuJ/FxUbi3GlUZNojQJWd/HOrq9vHQWJm2S5iRvHkw6CYGmKKOYzc+n/OU3Ufux41ucw/76JhL+gW2BxBqwIgZw64di4iTCRjF3fmVG+jt7s8B3EK5HSHm0L0fwRNLUSiSUo8qggXOoRJdBnm9Ubu2I64stET+Jl5CtENVFzLZrxjdqZkNqKYd3e2pzgug7f87oNYdA67mcLRcywRgRszL9uH/k8bBiTgGHY/auHUSVCCmCWLlJjHxeRLb8nLJ+M6pgpaOl6F4tWK/COAFbCKGiYrWX1Bu5pLpUQzQOleZovKLgGyO2tTge91wUNPBuiADmBynmc0eQ2/QmapxCUQG+szuuhB4lK/ambv1QxNHP2x9A1VhRkSURvo3KdSAdHuEqIJ5BXf9qbDVviniNQ9b3MZFrx7Xmpri6ZCRyEyup018VSDpYtjZGItuqmFFWoL7PI2AW5Irt+GtHuD9NuEvexfu5gg4Xy0Ro2VZWP7f9eB4Z/mPEgMr9IBcJ8tPhV379KqBAJEsUo3nydhwjI4H0NsCLSpJ2cuOz7ajYjtSbXOB4n16rFs23K6bojIwScmXkZ76xqy1y8SJufp4ODhSewUkBfP4PxYoawKZ/GkFV8baqznmZ6dsGxl6iwnfhN5wFBpGdqsVoQUfiOY0lRgNdMimggkTaiIrtVlRurZM4HQ9ZaVGUtJuXdHZNVG2XTUGCXogwaHGAzgjyX3ny1/4W9/lRYhyGs3huOMl+iRVfIe/9xzqWFCTS6kiJpXpxm8BlwA0wSAZ9aMRhWNC8xRfsNDZcPy+GxSWiSmsxPzk3Z5J+IWSiy9Dtub1TNltbZVN1QilwsJsADYUrBK529No3sFYc8EymgtYm+GWCzKDqu1TiiF67QTrbo1wqXdV36Vd6Lj3QqY7IL+cMDOJ9oBzPaSUyGJkIOsEJTsQ34nKzF9caFTHeBqvv41Gj9Fp121uYiCMXST7w1l8VlYjonjyF/xsR3THD0dS5Wg2W77M9qN//lwmiKK/CLZt3MFkq0+sL9811HohILuv0uKWfqv+VXbTisNL2tdhpObLDm6cwGvMGBB9X2jviStOIuPWDUt9hEMG5WGYaIyZp1uwGbyQfOs1R0VZMfM0k01dVqDWmuEmexhQm8KXXQxFEV/f9rxLVeomeh4lynqPW1cjH9qe5yHzRGu7Vny8MMzj4NrUYbdGcKSFMEDK4XkEi55LRBaLqUqwQBZz98Vb6jVE736zbvjfkw0d7KgLfE+SLFdjxcmKKlDQFdq5QZuyjIdhkcVC9inrHu8+JVJNFWTdiZv/w7NzXDloT3djto84P+suKV4iNaj32JFLAay7Y9mcw+JfiSmtLVG0UTJLWh9X1g2A8TWeYWRy1xXbR1AyhAq+XtQMgcyCXAVd/FPof5jaZYY57aLOBw8NJ9zAEiw68/oKIf7nU86oXtbjyuhLHH4JCcteNd6eX3HSve6qIHhQZgcgsUYoaWopGiSQfrLKzDsKd42KnnRubW7K5Xh1nisHKmiukiXRadbvYrth1zkgtWPmLoUhAH+Rqgcs7tOZO5UYS6lqlyn1Bm988PEG3i1ffqjDuOf4Qo3LKgohs6ni4BJFLRekO6gZH230tmcdQDrnXBlQs7ajU0bjSmDZxvxwu0QWMV9ctGz+3Ni71ChIHTHCH4ewvyAOCXCjIDSkpeUakx+2E35Y9PEEN4H++1XL1xRVSL5x1SqRwE59+a/Eer3KFwE/ESEuLtqRFO46QC+Q/Ux3G9MfVxvbc+Py8SdK66q7pnfuYBALvrDzQrpiFdsmMeSQfPt6HJnAV6NUgrYhEQDTPoSwwE06630Dwm/LjDrb8eJPja5/pc/jbT5XohUsaW3OdzUWf10p8OyU7imGKUCVFBkVFkMj3kvqijapLNRUSQhSbk2EoiBHXGIv80qgteqFM2PcoAAa5xWI/Z7CbInJYrAI4eizKuoDi/XpCZwgBcOR+ESd9oMNd21VlC9TXR/e5WC/zFbuP5mwBqCH44etfiQpzy78hgBFDx0R+0SQpYtOSYlIUGXa83N08KIsgopp6kVarajutapRTQc2DprFSZZWXFxwF4kGDSXNtQnKtQLNHX8qUdZFF+rL/Cojz2FkVBgBw8x1dqsUcx312kRveN5JOTLvvgbkJiPFIuGh2BUQHQc8IXgLKAsMAN1UZiMIw+Cho/ykApJfiO5OsZ5ZtupPtIE8JJtUjZdUYQKlkKJcs37kQ6rUGUa6S9mKTYuTBdI4Qb3n5Vk4UMQpmOcQ9kKKGxe58BB4h8OGX5RcjQJEKs2zF4yhSXqHiho+PVWMAkYlodlL2u7yO32BJ+w76RvAKNnBkiw7q/SwfhoOPOcD2Fat+0FcltDAMMlR7NNUQ4ehjwh8vMzIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjJWHf8/ftAmPsVSYvIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMTAtMjlUMDI6NTA6MDYrMDA6MDASZ++eAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTEwLTI5VDAyOjUwOjA2KzAwOjAwYzpXIgAAAABJRU5ErkJggg==`; diff --git a/src/views/demo/feat/download/index.vue b/src/views/demo/feat/download/index.vue new file mode 100644 index 00000000000..79aa2d8ff50 --- /dev/null +++ b/src/views/demo/feat/download/index.vue @@ -0,0 +1,54 @@ + + diff --git a/src/views/demo/feat/ellipsis/index.vue b/src/views/demo/feat/ellipsis/index.vue new file mode 100644 index 00000000000..e9daffad7b3 --- /dev/null +++ b/src/views/demo/feat/ellipsis/index.vue @@ -0,0 +1,42 @@ + + diff --git a/src/views/demo/feat/full-screen/index.vue b/src/views/demo/feat/full-screen/index.vue new file mode 100644 index 00000000000..a6d226fa17f --- /dev/null +++ b/src/views/demo/feat/full-screen/index.vue @@ -0,0 +1,41 @@ + + diff --git a/src/views/demo/feat/icon/index.vue b/src/views/demo/feat/icon/index.vue new file mode 100644 index 00000000000..5c41c86d2b6 --- /dev/null +++ b/src/views/demo/feat/icon/index.vue @@ -0,0 +1,71 @@ + + diff --git a/src/views/demo/feat/img-preview/index.vue b/src/views/demo/feat/img-preview/index.vue new file mode 100644 index 00000000000..ba3207fc6e2 --- /dev/null +++ b/src/views/demo/feat/img-preview/index.vue @@ -0,0 +1,24 @@ + + diff --git a/src/views/demo/feat/menu-params/index.vue b/src/views/demo/feat/menu-params/index.vue new file mode 100644 index 00000000000..ffa89c285dd --- /dev/null +++ b/src/views/demo/feat/menu-params/index.vue @@ -0,0 +1,31 @@ + + diff --git a/src/views/demo/feat/msg/index.vue b/src/views/demo/feat/msg/index.vue new file mode 100644 index 00000000000..031b417dc11 --- /dev/null +++ b/src/views/demo/feat/msg/index.vue @@ -0,0 +1,82 @@ + + diff --git a/src/views/demo/feat/print/index.vue b/src/views/demo/feat/print/index.vue new file mode 100644 index 00000000000..7fab279d0c9 --- /dev/null +++ b/src/views/demo/feat/print/index.vue @@ -0,0 +1,40 @@ + + diff --git a/src/views/demo/feat/request-demo/index.vue b/src/views/demo/feat/request-demo/index.vue new file mode 100644 index 00000000000..4bf5647b2d5 --- /dev/null +++ b/src/views/demo/feat/request-demo/index.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/views/demo/feat/ripple/index.vue b/src/views/demo/feat/ripple/index.vue new file mode 100644 index 00000000000..d7ee7429e5f --- /dev/null +++ b/src/views/demo/feat/ripple/index.vue @@ -0,0 +1,16 @@ + + diff --git a/src/views/demo/feat/screenshot/index.vue b/src/views/demo/feat/screenshot/index.vue new file mode 100644 index 00000000000..6f64e3da129 --- /dev/null +++ b/src/views/demo/feat/screenshot/index.vue @@ -0,0 +1,49 @@ + + diff --git a/src/views/demo/feat/session-timeout/index.vue b/src/views/demo/feat/session-timeout/index.vue new file mode 100644 index 00000000000..a189aabef21 --- /dev/null +++ b/src/views/demo/feat/session-timeout/index.vue @@ -0,0 +1,48 @@ + + diff --git a/src/views/demo/feat/tab-params/index.vue b/src/views/demo/feat/tab-params/index.vue new file mode 100644 index 00000000000..84cebf83e1c --- /dev/null +++ b/src/views/demo/feat/tab-params/index.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/views/demo/feat/tabs/TabDetail.vue b/src/views/demo/feat/tabs/TabDetail.vue new file mode 100644 index 00000000000..6d91dcb322d --- /dev/null +++ b/src/views/demo/feat/tabs/TabDetail.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/views/demo/feat/tabs/index.vue b/src/views/demo/feat/tabs/index.vue new file mode 100644 index 00000000000..2d5bd4df0a4 --- /dev/null +++ b/src/views/demo/feat/tabs/index.vue @@ -0,0 +1,56 @@ + + diff --git a/src/views/demo/feat/watermark/index.vue b/src/views/demo/feat/watermark/index.vue new file mode 100644 index 00000000000..e68cbbd4846 --- /dev/null +++ b/src/views/demo/feat/watermark/index.vue @@ -0,0 +1,40 @@ + + diff --git a/src/views/demo/feat/ws/index.vue b/src/views/demo/feat/ws/index.vue new file mode 100644 index 00000000000..8a407de88e1 --- /dev/null +++ b/src/views/demo/feat/ws/index.vue @@ -0,0 +1,107 @@ + + diff --git a/src/views/demo/form/AdvancedForm.vue b/src/views/demo/form/AdvancedForm.vue new file mode 100644 index 00000000000..c5f32cb7277 --- /dev/null +++ b/src/views/demo/form/AdvancedForm.vue @@ -0,0 +1,194 @@ + + diff --git a/src/views/demo/form/AppendForm.vue b/src/views/demo/form/AppendForm.vue new file mode 100644 index 00000000000..80ba3e0fb67 --- /dev/null +++ b/src/views/demo/form/AppendForm.vue @@ -0,0 +1,203 @@ + + diff --git a/src/views/demo/form/CustomerForm.vue b/src/views/demo/form/CustomerForm.vue new file mode 100644 index 00000000000..5217c851219 --- /dev/null +++ b/src/views/demo/form/CustomerForm.vue @@ -0,0 +1,247 @@ + + + + diff --git a/src/views/demo/form/DynamicForm.vue b/src/views/demo/form/DynamicForm.vue new file mode 100644 index 00000000000..1f3dbacca49 --- /dev/null +++ b/src/views/demo/form/DynamicForm.vue @@ -0,0 +1,229 @@ + + diff --git a/src/views/demo/form/RefForm.vue b/src/views/demo/form/RefForm.vue new file mode 100644 index 00000000000..1c65e258764 --- /dev/null +++ b/src/views/demo/form/RefForm.vue @@ -0,0 +1,186 @@ + + diff --git a/src/views/demo/form/RuleForm.vue b/src/views/demo/form/RuleForm.vue new file mode 100644 index 00000000000..aadf36d974b --- /dev/null +++ b/src/views/demo/form/RuleForm.vue @@ -0,0 +1,245 @@ + + diff --git a/src/views/demo/form/TabsForm.vue b/src/views/demo/form/TabsForm.vue new file mode 100644 index 00000000000..878240bc64b --- /dev/null +++ b/src/views/demo/form/TabsForm.vue @@ -0,0 +1,122 @@ + + + diff --git a/src/views/demo/form/UseForm.vue b/src/views/demo/form/UseForm.vue new file mode 100644 index 00000000000..2a57f35c7f2 --- /dev/null +++ b/src/views/demo/form/UseForm.vue @@ -0,0 +1,491 @@ + + + diff --git a/src/views/demo/form/index.vue b/src/views/demo/form/index.vue new file mode 100644 index 00000000000..9300af0f96f --- /dev/null +++ b/src/views/demo/form/index.vue @@ -0,0 +1,838 @@ + + diff --git a/src/views/demo/level/Menu111.vue b/src/views/demo/level/Menu111.vue new file mode 100644 index 00000000000..48503000a58 --- /dev/null +++ b/src/views/demo/level/Menu111.vue @@ -0,0 +1,10 @@ + + diff --git a/src/views/demo/level/Menu12.vue b/src/views/demo/level/Menu12.vue new file mode 100644 index 00000000000..13c0ffb0fc2 --- /dev/null +++ b/src/views/demo/level/Menu12.vue @@ -0,0 +1,10 @@ + + diff --git a/src/views/demo/level/Menu2.vue b/src/views/demo/level/Menu2.vue new file mode 100644 index 00000000000..7b33de7e08e --- /dev/null +++ b/src/views/demo/level/Menu2.vue @@ -0,0 +1,10 @@ + + diff --git a/src/views/demo/main-out/index.vue b/src/views/demo/main-out/index.vue new file mode 100644 index 00000000000..2406632fd36 --- /dev/null +++ b/src/views/demo/main-out/index.vue @@ -0,0 +1,6 @@ + diff --git a/src/views/demo/page/account/center/Application.vue b/src/views/demo/page/account/center/Application.vue new file mode 100644 index 00000000000..a059d2b23de --- /dev/null +++ b/src/views/demo/page/account/center/Application.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/views/demo/page/account/center/Article.vue b/src/views/demo/page/account/center/Article.vue new file mode 100644 index 00000000000..8f4459616de --- /dev/null +++ b/src/views/demo/page/account/center/Article.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/views/demo/page/account/center/Project.vue b/src/views/demo/page/account/center/Project.vue new file mode 100644 index 00000000000..f2172323a8e --- /dev/null +++ b/src/views/demo/page/account/center/Project.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/views/demo/page/account/center/data.tsx b/src/views/demo/page/account/center/data.tsx new file mode 100644 index 00000000000..51a4d2b0871 --- /dev/null +++ b/src/views/demo/page/account/center/data.tsx @@ -0,0 +1,132 @@ +export interface ListItem { + title: string; + icon: string; + color?: string; +} + +export interface TabItem { + key: string; + name: string; + component: string; +} + +export const tags: string[] = [ + '很有想法的', + '专注设计', + '川妹子', + '大长腿', + '海纳百川', + '前端开发', + 'vue3', +]; + +export const teams: ListItem[] = [ + { + icon: 'ri:alipay-fill', + title: '科学搬砖组', + color: '#ff4000', + }, + { + icon: 'emojione-monotone:letter-a', + title: '中二少年团', + color: '#7c51b8', + }, + { + icon: 'ri:alipay-fill', + title: '高逼格设计', + color: '#00adf7', + }, + { + icon: 'jam:codepen-circle', + title: '程序员日常', + color: '#00adf7', + }, + { + icon: 'fa:behance-square', + title: '科学搬砖组', + color: '#7c51b8', + }, + { + icon: 'jam:codepen-circle', + title: '程序员日常', + color: '#ff4000', + }, +]; + +export const details: ListItem[] = [ + { + icon: 'ic:outline-contacts', + title: '交互专家', + }, + { + icon: 'grommet-icons:cluster', + title: '某某某事业群', + }, + { + icon: 'bx:bx-home-circle', + title: '福建省厦门市', + }, +]; + +export const achieveList: TabItem[] = [ + { + key: '1', + name: '文章', + component: 'Article', + }, + { + key: '2', + name: '应用', + component: 'Application', + }, + { + key: '3', + name: '项目', + component: 'Project', + }, +]; + +export const actions: any[] = [ + { icon: 'clarity:star-line', text: '156', color: '#018ffb' }, + { icon: 'bx:bxs-like', text: '156', color: '#459ae8' }, + { icon: 'bx:bxs-message-dots', text: '2', color: '#42d27d' }, +]; + +export const articleList = (() => { + const result: any[] = []; + for (let i = 0; i < 4; i++) { + result.push({ + title: 'Vben Admin', + description: ['Vben', '设计语言', 'Typescript'], + content: '基于Vue Next, TypeScript, Ant Design实现的一套完整的企业级后台管理系统。', + time: '2020-11-14 11:20', + }); + } + return result; +})(); + +export const applicationList = (() => { + const result: any[] = []; + for (let i = 0; i < 8; i++) { + result.push({ + title: 'Vben Admin', + icon: 'emojione-monotone:letter-a', + color: '#1890ff', + active: '100', + new: '1,799', + download: 'bx:bx-download', + }); + } + return result; +})(); + +export const projectList = (() => { + const result: any[] = []; + for (let i = 0; i < 8; i++) { + result.push({ + title: 'Vben Admin', + content: '基于Vue Next, TypeScript, Ant Design实现的一套完整的企业级后台管理系统。', + }); + } + return result; +})(); diff --git a/src/views/demo/page/account/center/index.vue b/src/views/demo/page/account/center/index.vue new file mode 100644 index 00000000000..fb61cee8bc4 --- /dev/null +++ b/src/views/demo/page/account/center/index.vue @@ -0,0 +1,138 @@ + + + + diff --git a/src/views/demo/page/account/setting/AccountBind.vue b/src/views/demo/page/account/setting/AccountBind.vue new file mode 100644 index 00000000000..51acb7b9582 --- /dev/null +++ b/src/views/demo/page/account/setting/AccountBind.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/views/demo/page/account/setting/BaseSetting.vue b/src/views/demo/page/account/setting/BaseSetting.vue new file mode 100644 index 00000000000..e37ce81af04 --- /dev/null +++ b/src/views/demo/page/account/setting/BaseSetting.vue @@ -0,0 +1,79 @@ + + + + diff --git a/src/views/demo/page/account/setting/MsgNotify.vue b/src/views/demo/page/account/setting/MsgNotify.vue new file mode 100644 index 00000000000..b0b9ffb9685 --- /dev/null +++ b/src/views/demo/page/account/setting/MsgNotify.vue @@ -0,0 +1,32 @@ + + diff --git a/src/views/demo/page/account/setting/SecureSetting.vue b/src/views/demo/page/account/setting/SecureSetting.vue new file mode 100644 index 00000000000..7991cfe4e89 --- /dev/null +++ b/src/views/demo/page/account/setting/SecureSetting.vue @@ -0,0 +1,32 @@ + + diff --git a/src/views/demo/page/account/setting/data.ts b/src/views/demo/page/account/setting/data.ts new file mode 100644 index 00000000000..4ab839f5527 --- /dev/null +++ b/src/views/demo/page/account/setting/data.ts @@ -0,0 +1,149 @@ +import { FormSchema } from '@/components/Form'; + +export interface ListItem { + key: string; + title: string; + description: string; + extra?: string; + avatar?: string; + color?: string; +} + +// tab的list +export const settingList = [ + { + key: '1', + name: '基本设置', + component: 'BaseSetting', + }, + { + key: '2', + name: '安全设置', + component: 'SecureSetting', + }, + { + key: '3', + name: '账号绑定', + component: 'AccountBind', + }, + { + key: '4', + name: '新消息通知', + component: 'MsgNotify', + }, +]; + +// 基础设置 form +export const baseSetschemas: FormSchema[] = [ + { + field: 'email', + component: 'Input', + label: '邮箱', + colProps: { span: 18 }, + }, + { + field: 'name', + component: 'Input', + label: '昵称', + colProps: { span: 18 }, + }, + { + field: 'introduction', + component: 'InputTextArea', + label: '个人简介', + colProps: { span: 18 }, + }, + { + field: 'phone', + component: 'Input', + label: '联系电话', + colProps: { span: 18 }, + }, + { + field: 'address', + component: 'Input', + label: '所在地区', + colProps: { span: 18 }, + }, +]; + +// 安全设置 list +export const secureSettingList: ListItem[] = [ + { + key: '1', + title: '账户密码', + description: '当前密码强度::强', + extra: '修改', + }, + { + key: '2', + title: '密保手机', + description: '已绑定手机::138****8293', + extra: '修改', + }, + { + key: '3', + title: '密保问题', + description: '未设置密保问题,密保问题可有效保护账户安全', + extra: '修改', + }, + { + key: '4', + title: '备用邮箱', + description: '已绑定邮箱::ant***sign.com', + extra: '修改', + }, + { + key: '5', + title: 'MFA 设备', + description: '未绑定 MFA 设备,绑定后,可以进行二次确认', + extra: '修改', + }, +]; + +// 账号绑定 list +export const accountBindList: ListItem[] = [ + { + key: '1', + title: '绑定淘宝', + description: '当前未绑定淘宝账号', + extra: '绑定', + avatar: 'ri:taobao-fill', + color: '#ff4000', + }, + { + key: '2', + title: '绑定支付宝', + description: '当前未绑定支付宝账号', + extra: '绑定', + avatar: 'fa-brands:alipay', + color: '#2eabff', + }, + { + key: '3', + title: '绑定钉钉', + description: '当前未绑定钉钉账号', + extra: '绑定', + avatar: 'ri:dingding-fill', + color: '#2eabff', + }, +]; + +// 新消息通知 list +export const msgNotifyList: ListItem[] = [ + { + key: '1', + title: '账户密码', + description: '其他用户的消息将以站内信的形式通知', + }, + { + key: '2', + title: '系统消息', + description: '系统消息将以站内信的形式通知', + }, + { + key: '3', + title: '待办任务', + description: '待办任务将以站内信的形式通知', + }, +]; diff --git a/src/views/demo/page/account/setting/index.vue b/src/views/demo/page/account/setting/index.vue new file mode 100644 index 00000000000..2e5cdd18ed2 --- /dev/null +++ b/src/views/demo/page/account/setting/index.vue @@ -0,0 +1,50 @@ + + + + diff --git a/src/views/demo/page/desc/basic/data.tsx b/src/views/demo/page/desc/basic/data.tsx new file mode 100644 index 00000000000..43d1e6e97e3 --- /dev/null +++ b/src/views/demo/page/desc/basic/data.tsx @@ -0,0 +1,196 @@ +import { DescItem } from '@/components/Description'; +import { BasicColumn } from '@/components/Table/src/types/table'; +import { Button } from '@/components/Button'; + +import { Badge } from 'ant-design-vue'; + +export const refundData = { + a1: '1000000000', + a2: '已取货', + a3: '1234123421', + a4: '3214321432', +}; + +export const personData = { + b1: '付小小', + b2: '18100000000', + b3: '菜鸟仓储', + b4: '浙江省杭州市西湖区万塘路18号', + b5: '无', +}; +export const refundSchema: DescItem[] = [ + { + field: 'a1', + label: '取货单号', + }, + { + field: 'a2', + label: '状态', + }, + { + field: 'a3', + label: '销售单号', + }, + { + field: 'a4', + label: '子订单', + }, +]; +export const personSchema: DescItem[] = [ + { + field: 'b1', + label: '用户姓名', + }, + { + field: 'b2', + label: '联系电话', + }, + { + field: 'b3', + label: '常用快递', + }, + { + field: 'b4', + label: '取货地址', + }, + { + field: 'b5', + label: '备注', + }, +]; + +export const refundTableSchema: BasicColumn[] = [ + { + title: '商品编号', + width: 150, + dataIndex: 't1', + customRender: ({ record }) => { + return ( + + ); + }, + }, + { + title: '商品名称', + width: 150, + dataIndex: 't2', + }, + { + title: '商品条码', + width: 150, + dataIndex: 't3', + }, + { + title: '单价 ', + width: 150, + dataIndex: 't4', + }, + { + title: '数量(件) ', + width: 150, + dataIndex: 't5', + }, + { + title: '金额', + width: 150, + dataIndex: 't6', + }, +]; +export const refundTimeTableSchema: BasicColumn[] = [ + { + title: '时间', + width: 150, + dataIndex: 't1', + }, + { + title: '当前进度', + width: 150, + dataIndex: 't2', + }, + { + title: '状态', + width: 150, + dataIndex: 't3', + customRender: ({ record }) => { + return ; + }, + }, + { + title: '操作员ID ', + width: 150, + dataIndex: 't4', + }, + { + title: '耗时', + width: 150, + dataIndex: 't5', + }, +]; + +export const refundTableData: any[] = [ + { + t1: 1234561, + t2: '矿泉水 550ml', + t3: '12421432143214321', + t4: '2.00', + t5: 1, + t6: 2.0, + }, + { + t1: 1234562, + t2: '矿泉水 550ml', + t3: '12421432143214321', + t4: '2.00', + t5: 2, + t6: 2.0, + }, + { + t1: 1234562, + t2: '矿泉水 550ml', + t3: '12421432143214321', + t4: '2.00', + t5: 2, + t6: 2.0, + }, + { + t1: 1234562, + t2: '矿泉水 550ml', + t3: '12421432143214321', + t4: '2.00', + t5: 2, + t6: 2.0, + }, +]; + +export const refundTimeTableData: any[] = [ + { + t1: '2017-10-01 14:10', + t2: '联系客户', + t3: '进行中', + t4: '取货员 ID1234', + t5: '5mins', + }, + { + t1: '2017-10-01 14:10', + t2: '取货员出发', + t3: '成功', + t4: '取货员 ID1234', + t5: '5mins', + }, + { + t1: '2017-10-01 14:10', + t2: '取货员接单', + t3: '成功', + t4: '系统', + t5: '5mins', + }, + { + t1: '2017-10-01 14:10', + t2: '申请审批通过', + t3: '成功', + t4: '用户', + t5: '1h', + }, +]; diff --git a/src/views/demo/page/desc/basic/index.vue b/src/views/demo/page/desc/basic/index.vue new file mode 100644 index 00000000000..126f6c6ca04 --- /dev/null +++ b/src/views/demo/page/desc/basic/index.vue @@ -0,0 +1,85 @@ + + + diff --git a/src/views/demo/page/desc/high/data.tsx b/src/views/demo/page/desc/high/data.tsx new file mode 100644 index 00000000000..dc650b3e48a --- /dev/null +++ b/src/views/demo/page/desc/high/data.tsx @@ -0,0 +1,65 @@ +import { BasicColumn } from '@/components/Table/src/types/table'; + +import { Badge } from 'ant-design-vue'; + +export const refundTimeTableSchema: BasicColumn[] = [ + { + title: '时间', + width: 150, + dataIndex: 't1', + }, + { + title: '当前进度', + width: 150, + dataIndex: 't2', + }, + { + title: '状态', + width: 150, + dataIndex: 't3', + customRender: ({ record }) => { + return ; + }, + }, + { + title: '操作员ID ', + width: 150, + dataIndex: 't4', + }, + { + title: '耗时', + width: 150, + dataIndex: 't5', + }, +]; + +export const refundTimeTableData: any[] = [ + { + t1: '2017-10-01 14:10', + t2: '联系客户', + t3: '进行中', + t4: '取货员 ID1234', + t5: '5mins', + }, + { + t1: '2017-10-01 14:10', + t2: '取货员出发', + t3: '成功', + t4: '取货员 ID1234', + t5: '5mins', + }, + { + t1: '2017-10-01 14:10', + t2: '取货员接单', + t3: '成功', + t4: '系统', + t5: '5mins', + }, + { + t1: '2017-10-01 14:10', + t2: '申请审批通过', + t3: '成功', + t4: '用户', + t5: '1h', + }, +]; diff --git a/src/views/demo/page/desc/high/index.vue b/src/views/demo/page/desc/high/index.vue new file mode 100644 index 00000000000..bdc395cbbd7 --- /dev/null +++ b/src/views/demo/page/desc/high/index.vue @@ -0,0 +1,110 @@ + + diff --git a/src/views/demo/page/form/basic/data.ts b/src/views/demo/page/form/basic/data.ts new file mode 100644 index 00000000000..98dcd2c7bbe --- /dev/null +++ b/src/views/demo/page/form/basic/data.ts @@ -0,0 +1,139 @@ +import { FormSchema } from '@/components/Form'; + +const colProps = { + span: 8, +}; + +export const schemas: FormSchema[] = [ + { + field: 'title', + component: 'Input', + label: '标题', + colProps, + componentProps: { + placeholder: '给目标起个名字', + }, + required: true, + }, + { + field: 'time', + component: 'RangePicker', + label: '起止日期', + colProps, + required: true, + }, + { + field: 'client', + component: 'Input', + colProps, + label: '客户', + helpMessage: '目标的服务对象', + subLabel: '( 选填 )', + componentProps: { + placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号', + }, + }, + { + field: 'weights', + component: 'InputNumber', + label: '权重', + colProps, + subLabel: '( 选填 )', + componentProps: { + formatter: (value: string | number) => (value ? `${value}%` : ''), + parser: (value: string) => Number(value.replace('%', '')), + placeholder: '请输入', + }, + }, + { + field: 'target', + component: 'InputTextArea', + label: '目标描述', + colProps, + componentProps: { + placeholder: '请输入你的阶段性工作目标', + rows: 4, + }, + required: true, + }, + { + field: 'metrics', + component: 'InputTextArea', + label: '衡量标准', + colProps, + componentProps: { + placeholder: '请输入衡量标准', + rows: 4, + }, + required: true, + }, + + { + field: 'inviteer', + component: 'Input', + label: '邀评人', + colProps: { + span: 8, + }, + subLabel: '( 选填 )', + componentProps: { + placeholder: '请直接 @姓名/工号,最多可邀请 5 人', + }, + }, + { + field: 'disclosure', + component: 'RadioGroup', + label: '目标公开', + colProps: { + span: 16, + }, + itemProps: { + extra: '客户、邀评人默认被分享', + }, + componentProps: { + options: [ + { + label: '公开', + value: '1', + }, + { + label: '部分公开', + value: '2', + }, + { + label: '不公开', + value: '3', + }, + ], + }, + }, + { + field: 'disclosure', + component: 'Select', + label: ' ', + colProps: { + span: 8, + }, + show: ({ model }) => { + return model.disclosure === '2'; + }, + componentProps: { + placeholder: '公开给', + mode: 'multiple', + options: [ + { + label: '同事1', + value: '1', + }, + { + label: '同事2', + value: '2', + }, + { + label: '同事3', + value: '3', + }, + ], + }, + }, +]; diff --git a/src/views/demo/page/form/basic/index.vue b/src/views/demo/page/form/basic/index.vue new file mode 100644 index 00000000000..13f88f346af --- /dev/null +++ b/src/views/demo/page/form/basic/index.vue @@ -0,0 +1,64 @@ + + + diff --git a/src/views/demo/page/form/high/PersonTable.vue b/src/views/demo/page/form/high/PersonTable.vue new file mode 100644 index 00000000000..aa630fd99f8 --- /dev/null +++ b/src/views/demo/page/form/high/PersonTable.vue @@ -0,0 +1,134 @@ + + diff --git a/src/views/demo/page/form/high/data.ts b/src/views/demo/page/form/high/data.ts new file mode 100644 index 00000000000..187f3cb9eee --- /dev/null +++ b/src/views/demo/page/form/high/data.ts @@ -0,0 +1,149 @@ +import { FormSchema } from '@/components/Form'; + +const basicOptions: LabelValueOptions = [ + { + label: '付晓晓', + value: '1', + }, + { + label: '周毛毛', + value: '2', + }, +]; + +const storeTypeOptions: LabelValueOptions = [ + { + label: '私密', + value: '1', + }, + { + label: '公开', + value: '2', + }, +]; + +export const schemas: FormSchema[] = [ + { + field: 'f1', + component: 'Input', + label: '仓库名', + required: true, + }, + { + field: 'f2', + component: 'Input', + label: '仓库域名', + required: true, + componentProps: { + addonBefore: 'http://', + addonAfter: 'com', + }, + colProps: { + offset: 2, + }, + }, + { + field: 'f3', + component: 'Select', + label: '仓库管理员', + componentProps: { + options: basicOptions, + }, + required: true, + colProps: { + offset: 2, + }, + }, + { + field: 'f4', + component: 'Select', + label: '审批人', + componentProps: { + options: basicOptions, + }, + required: true, + }, + { + field: 'f5', + component: 'RangePicker', + label: '生效日期', + required: true, + colProps: { + offset: 2, + }, + }, + { + field: 'f6', + component: 'Select', + label: '仓库类型', + componentProps: { + options: storeTypeOptions, + }, + required: true, + colProps: { + offset: 2, + }, + }, +]; +export const taskSchemas: FormSchema[] = [ + { + field: 't1', + component: 'Input', + label: '任务名', + required: true, + }, + { + field: 't2', + component: 'Input', + label: '任务描述', + required: true, + colProps: { + offset: 2, + }, + }, + { + field: 't3', + component: 'Select', + label: '执行人', + componentProps: { + options: basicOptions, + }, + required: true, + colProps: { + offset: 2, + }, + }, + { + field: 't4', + component: 'Select', + label: '责任人', + componentProps: { + options: basicOptions, + }, + required: true, + }, + { + field: 't5', + component: 'TimePicker', + label: '生效日期', + required: true, + componentProps: { + style: { width: '100%' }, + }, + colProps: { + offset: 2, + }, + }, + { + field: 't6', + component: 'Select', + label: '任务类型', + componentProps: { + options: storeTypeOptions, + }, + required: true, + colProps: { + offset: 2, + }, + }, +]; diff --git a/src/views/demo/page/form/high/index.vue b/src/views/demo/page/form/high/index.vue new file mode 100644 index 00000000000..2f2e5b6d92a --- /dev/null +++ b/src/views/demo/page/form/high/index.vue @@ -0,0 +1,69 @@ + + + diff --git a/src/views/demo/page/form/step/Step1.vue b/src/views/demo/page/form/step/Step1.vue new file mode 100644 index 00000000000..9cc6d768920 --- /dev/null +++ b/src/views/demo/page/form/step/Step1.vue @@ -0,0 +1,92 @@ + + + diff --git a/src/views/demo/page/form/step/Step2.vue b/src/views/demo/page/form/step/Step2.vue new file mode 100644 index 00000000000..193f3fa3458 --- /dev/null +++ b/src/views/demo/page/form/step/Step2.vue @@ -0,0 +1,61 @@ + + diff --git a/src/views/demo/page/form/step/Step3.vue b/src/views/demo/page/form/step/Step3.vue new file mode 100644 index 00000000000..03cb15cdb34 --- /dev/null +++ b/src/views/demo/page/form/step/Step3.vue @@ -0,0 +1,23 @@ + + diff --git a/src/views/demo/page/form/step/data.tsx b/src/views/demo/page/form/step/data.tsx new file mode 100644 index 00000000000..d7fe07d6278 --- /dev/null +++ b/src/views/demo/page/form/step/data.tsx @@ -0,0 +1,77 @@ +import { FormSchema } from '@/components/Form'; + +export const step1Schemas: FormSchema[] = [ + { + field: 'account', + component: 'Select', + label: '付款账户', + required: true, + defaultValue: '1', + componentProps: { + options: [ + { + label: 'anncwb@126.com', + value: '1', + }, + ], + }, + colProps: { + span: 24, + }, + }, + { + field: 'fac', + label: '收款账户', + required: true, + defaultValue: 'test@example.com', + slot: 'fac', + colProps: { + span: 24, + }, + }, + { + field: 'pay', + component: 'Input', + label: '', + defaultValue: 'zfb', + show: false, + }, + { + field: 'payeeName', + component: 'Input', + label: '收款人姓名', + defaultValue: 'Vben', + required: true, + colProps: { + span: 24, + }, + }, + { + field: 'money', + component: 'Input', + label: '转账金额', + defaultValue: '500', + required: true, + renderComponentContent: () => { + return { + prefix: () => '¥', + }; + }, + colProps: { + span: 24, + }, + }, +]; + +export const step2Schemas: FormSchema[] = [ + { + field: 'pwd', + component: 'InputPassword', + label: '支付密码', + required: true, + defaultValue: '123456', + colProps: { + span: 24, + }, + }, +]; diff --git a/src/views/demo/page/form/step/index.vue b/src/views/demo/page/form/step/index.vue new file mode 100644 index 00000000000..06825d59da6 --- /dev/null +++ b/src/views/demo/page/form/step/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/views/demo/page/list/basic/data.tsx b/src/views/demo/page/list/basic/data.tsx new file mode 100644 index 00000000000..f452c905125 --- /dev/null +++ b/src/views/demo/page/list/basic/data.tsx @@ -0,0 +1,17 @@ +export const cardList = (() => { + const result: any[] = []; + for (let i = 0; i < 6; i++) { + result.push({ + id: i, + title: 'Vben Admin', + description: '基于Vue Next, TypeScript, Ant Design Vue实现的一套完整的企业级后台管理系统', + datetime: '2020-11-26 17:39', + extra: '编辑', + icon: 'logos:vue', + color: '#1890ff', + author: 'Vben', + percent: 20 * (i + 1), + }); + } + return result; +})(); diff --git a/src/views/demo/page/list/basic/index.vue b/src/views/demo/page/list/basic/index.vue new file mode 100644 index 00000000000..8a4724e52e8 --- /dev/null +++ b/src/views/demo/page/list/basic/index.vue @@ -0,0 +1,143 @@ + + + diff --git a/src/views/demo/page/list/card/data.tsx b/src/views/demo/page/list/card/data.tsx new file mode 100644 index 00000000000..c4a156aa623 --- /dev/null +++ b/src/views/demo/page/list/card/data.tsx @@ -0,0 +1,14 @@ +export const cardList = (() => { + const result: any[] = []; + for (let i = 0; i < 12; i++) { + result.push({ + title: 'Vben Admin', + icon: 'logos:vue', + color: '#1890ff', + active: '100', + new: '1,799', + download: 'bx:bx-download', + }); + } + return result; +})(); diff --git a/src/views/demo/page/list/card/index.vue b/src/views/demo/page/list/card/index.vue new file mode 100644 index 00000000000..c4b1215111e --- /dev/null +++ b/src/views/demo/page/list/card/index.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/views/demo/page/list/search/data.tsx b/src/views/demo/page/list/search/data.tsx new file mode 100644 index 00000000000..b84205fd64f --- /dev/null +++ b/src/views/demo/page/list/search/data.tsx @@ -0,0 +1,37 @@ +import { FormSchema } from '@/components/Form'; + +export const searchList = (() => { + const result: any[] = []; + for (let i = 0; i < 6; i++) { + result.push({ + id: i, + title: 'Vben Admin', + description: ['Vben', '设计语言', 'Typescript'], + content: '基于Vue Next, TypeScript, Ant Design实现的一套完整的企业级后台管理系统。', + time: '2020-11-14 11:20', + }); + } + return result; +})(); + +export const actions: any[] = [ + { icon: 'clarity:star-line', text: '156', color: '#018ffb' }, + { icon: 'bx:bxs-like', text: '156', color: '#459ae8' }, + { icon: 'bx:bxs-message-dots', text: '2', color: '#42d27d' }, +]; + +export const schemas: FormSchema[] = [ + { + field: 'field1', + component: 'InputSearch', + label: '项目名', + colProps: { + span: 8, + }, + componentProps: { + onChange: (e: any) => { + console.log(e); + }, + }, + }, +]; diff --git a/src/views/demo/page/list/search/index.vue b/src/views/demo/page/list/search/index.vue new file mode 100644 index 00000000000..5cbad2441cf --- /dev/null +++ b/src/views/demo/page/list/search/index.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/views/demo/page/result/fail/index.vue b/src/views/demo/page/result/fail/index.vue new file mode 100644 index 00000000000..3e3e6e234be --- /dev/null +++ b/src/views/demo/page/result/fail/index.vue @@ -0,0 +1,47 @@ + + + diff --git a/src/views/demo/page/result/success/index.vue b/src/views/demo/page/result/success/index.vue new file mode 100644 index 00000000000..ec80e023ddb --- /dev/null +++ b/src/views/demo/page/result/success/index.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/views/demo/permission/CurrentPermissionMode.vue b/src/views/demo/permission/CurrentPermissionMode.vue new file mode 100644 index 00000000000..2209a74b5eb --- /dev/null +++ b/src/views/demo/permission/CurrentPermissionMode.vue @@ -0,0 +1,23 @@ + + diff --git a/src/views/demo/permission/back/Btn.vue b/src/views/demo/permission/back/Btn.vue new file mode 100644 index 00000000000..21b147b99f8 --- /dev/null +++ b/src/views/demo/permission/back/Btn.vue @@ -0,0 +1,95 @@ + + + diff --git a/src/views/demo/permission/back/index.vue b/src/views/demo/permission/back/index.vue new file mode 100644 index 00000000000..89d86deae4f --- /dev/null +++ b/src/views/demo/permission/back/index.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/views/demo/permission/front/AuthPageA.vue b/src/views/demo/permission/front/AuthPageA.vue new file mode 100644 index 00000000000..cdc08a52964 --- /dev/null +++ b/src/views/demo/permission/front/AuthPageA.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/views/demo/permission/front/AuthPageB.vue b/src/views/demo/permission/front/AuthPageB.vue new file mode 100644 index 00000000000..f6c5ddaeb2f --- /dev/null +++ b/src/views/demo/permission/front/AuthPageB.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/views/demo/permission/front/Btn.vue b/src/views/demo/permission/front/Btn.vue new file mode 100644 index 00000000000..0d1b182a07a --- /dev/null +++ b/src/views/demo/permission/front/Btn.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/views/demo/permission/front/index.vue b/src/views/demo/permission/front/index.vue new file mode 100644 index 00000000000..3353797039d --- /dev/null +++ b/src/views/demo/permission/front/index.vue @@ -0,0 +1,47 @@ + + + diff --git a/src/views/demo/steps/index.vue b/src/views/demo/steps/index.vue new file mode 100644 index 00000000000..a001a812e81 --- /dev/null +++ b/src/views/demo/steps/index.vue @@ -0,0 +1,38 @@ + + diff --git a/src/views/demo/system/account/AccountDetail.vue b/src/views/demo/system/account/AccountDetail.vue new file mode 100644 index 00000000000..65b0131e39d --- /dev/null +++ b/src/views/demo/system/account/AccountDetail.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/views/demo/system/account/AccountModal.vue b/src/views/demo/system/account/AccountModal.vue new file mode 100644 index 00000000000..81ccc18ce62 --- /dev/null +++ b/src/views/demo/system/account/AccountModal.vue @@ -0,0 +1,69 @@ + + diff --git a/src/views/demo/system/account/DeptTree.vue b/src/views/demo/system/account/DeptTree.vue new file mode 100644 index 00000000000..15e65941bf4 --- /dev/null +++ b/src/views/demo/system/account/DeptTree.vue @@ -0,0 +1,38 @@ + + diff --git a/src/views/demo/system/account/account.data.ts b/src/views/demo/system/account/account.data.ts new file mode 100644 index 00000000000..9a24fada04a --- /dev/null +++ b/src/views/demo/system/account/account.data.ts @@ -0,0 +1,156 @@ +import { getAllRoleList, isAccountExist } from '@/api/demo/system'; +import { BasicColumn, FormSchema } from '@/components/Table'; + +/** + * transform mock data + * { + * 0: '华东分部', + * '0-0': '华东分部-研发部' + * '0-1': '华东分部-市场部', + * ... + * } + */ +export const deptMap = (() => { + const pDept = ['华东分部', '华南分部', '西北分部']; + const cDept = ['研发部', '市场部', '商务部', '财务部']; + + return pDept.reduce((map, p, pIdx) => { + map[pIdx] = p; + + cDept.forEach((c, cIndex) => (map[`${pIdx}-${cIndex}`] = `${p}-${c}`)); + + return map; + }, {}); +})(); + +export const columns: BasicColumn[] = [ + { + title: '用户名', + dataIndex: 'account', + width: 120, + }, + { + title: '昵称', + dataIndex: 'nickname', + width: 120, + }, + { + title: '邮箱', + dataIndex: 'email', + width: 120, + }, + { + title: '创建时间', + dataIndex: 'createTime', + width: 180, + }, + { + title: '角色', + dataIndex: 'role', + width: 200, + }, + { + title: '所属部门', + dataIndex: 'dept', + customRender: ({ value }) => { + return deptMap[value]; + }, + }, + { + title: '备注', + dataIndex: 'remark', + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'account', + label: '用户名', + component: 'Input', + colProps: { span: 8 }, + }, + { + field: 'nickname', + label: '昵称', + component: 'Input', + colProps: { span: 8 }, + }, +]; + +export const accountFormSchema: FormSchema[] = [ + { + field: 'account', + label: '用户名', + component: 'Input', + helpMessage: ['本字段演示异步验证', '不能输入带有admin的用户名'], + rules: [ + { + required: true, + message: '请输入用户名', + }, + { + trigger: 'blur', + validator(_, value) { + return new Promise((resolve, reject) => { + if (!value) return resolve(); + isAccountExist(value) + .then(resolve) + .catch((err) => { + reject(err.message || '验证失败'); + }); + }); + }, + }, + ], + }, + { + field: 'pwd', + label: '密码', + component: 'InputPassword', + required: true, + ifShow: false, + }, + { + label: '角色', + field: 'role', + component: 'ApiSelect', + componentProps: { + api: getAllRoleList, + labelField: 'roleName', + valueField: 'roleValue', + }, + required: true, + }, + { + field: 'dept', + label: '所属部门', + component: 'TreeSelect', + componentProps: { + fieldNames: { + label: 'deptName', + value: 'id', + }, + getPopupContainer: () => document.body, + }, + required: true, + }, + { + field: 'nickname', + label: '昵称', + component: 'Input', + required: true, + }, + + { + label: '邮箱', + field: 'email', + component: 'Input', + required: true, + }, + + { + label: '备注', + field: 'remark', + component: 'InputTextArea', + }, +]; diff --git a/src/views/demo/system/account/index.vue b/src/views/demo/system/account/index.vue new file mode 100644 index 00000000000..86e0dbc1e86 --- /dev/null +++ b/src/views/demo/system/account/index.vue @@ -0,0 +1,126 @@ + + diff --git a/src/views/demo/system/dept/DeptModal.vue b/src/views/demo/system/dept/DeptModal.vue new file mode 100644 index 00000000000..198b94db5b7 --- /dev/null +++ b/src/views/demo/system/dept/DeptModal.vue @@ -0,0 +1,58 @@ + + diff --git a/src/views/demo/system/dept/dept.data.ts b/src/views/demo/system/dept/dept.data.ts new file mode 100644 index 00000000000..1491215baaa --- /dev/null +++ b/src/views/demo/system/dept/dept.data.ts @@ -0,0 +1,110 @@ +import { BasicColumn, FormSchema } from '@/components/Table'; +import { h } from 'vue'; +import { Tag } from 'ant-design-vue'; + +export const columns: BasicColumn[] = [ + { + title: '部门名称', + dataIndex: 'deptName', + width: 160, + align: 'left', + }, + { + title: '排序', + dataIndex: 'orderNo', + width: 50, + }, + { + title: '状态', + dataIndex: 'status', + width: 80, + customRender: ({ record }) => { + const status = record.status; + const enable = ~~status === 0; + const color = enable ? 'green' : 'red'; + const text = enable ? '启用' : '停用'; + return h(Tag, { color: color }, () => text); + }, + }, + { + title: '创建时间', + dataIndex: 'createTime', + width: 180, + }, + { + title: '备注', + dataIndex: 'remark', + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'deptName', + label: '部门名称', + component: 'Input', + colProps: { span: 8 }, + }, + { + field: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: [ + { label: '启用', value: '0' }, + { label: '停用', value: '1' }, + ], + }, + colProps: { span: 8 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'deptName', + label: '部门名称', + component: 'Input', + required: true, + }, + { + field: 'parentDept', + label: '上级部门', + component: 'TreeSelect', + ifShow({ values }) { + const { deptName, parentDept } = values; + // Hide without a parentDept when editing + return parentDept || (!deptName && !parentDept); + }, + componentProps: { + fieldNames: { + label: 'deptName', + value: 'id', + }, + getPopupContainer: () => document.body, + }, + required: true, + }, + { + field: 'orderNo', + label: '排序', + component: 'InputNumber', + required: true, + }, + { + field: 'status', + label: '状态', + component: 'RadioButtonGroup', + defaultValue: '0', + componentProps: { + options: [ + { label: '启用', value: '0' }, + { label: '停用', value: '1' }, + ], + }, + required: true, + }, + { + label: '备注', + field: 'remark', + component: 'InputTextArea', + }, +]; diff --git a/src/views/demo/system/dept/index.vue b/src/views/demo/system/dept/index.vue new file mode 100644 index 00000000000..0b30ee70028 --- /dev/null +++ b/src/views/demo/system/dept/index.vue @@ -0,0 +1,88 @@ + + diff --git a/src/views/demo/system/menu/MenuDrawer.vue b/src/views/demo/system/menu/MenuDrawer.vue new file mode 100644 index 00000000000..4e9bf4b40bd --- /dev/null +++ b/src/views/demo/system/menu/MenuDrawer.vue @@ -0,0 +1,65 @@ + + diff --git a/src/views/demo/system/menu/index.vue b/src/views/demo/system/menu/index.vue new file mode 100644 index 00000000000..8578b35caa0 --- /dev/null +++ b/src/views/demo/system/menu/index.vue @@ -0,0 +1,96 @@ + + diff --git a/src/views/demo/system/menu/menu.data.ts b/src/views/demo/system/menu/menu.data.ts new file mode 100644 index 00000000000..dff0bbedea4 --- /dev/null +++ b/src/views/demo/system/menu/menu.data.ts @@ -0,0 +1,200 @@ +import { BasicColumn, FormSchema } from '@/components/Table'; +import { h } from 'vue'; +import { Tag } from 'ant-design-vue'; +import Icon from '@/components/Icon/Icon.vue'; + +export const columns: BasicColumn[] = [ + { + title: '菜单名称', + dataIndex: 'menuName', + width: 200, + align: 'left', + }, + { + title: '图标', + dataIndex: 'icon', + width: 50, + customRender: ({ record }) => { + return h(Icon, { icon: record.icon }); + }, + }, + { + title: '权限标识', + dataIndex: 'permission', + width: 180, + }, + { + title: '组件', + dataIndex: 'component', + }, + { + title: '排序', + dataIndex: 'orderNo', + width: 50, + }, + { + title: '状态', + dataIndex: 'status', + width: 80, + customRender: ({ record }) => { + const status = record.status; + const enable = ~~status === 0; + const color = enable ? 'green' : 'red'; + const text = enable ? '启用' : '停用'; + return h(Tag, { color: color }, () => text); + }, + }, + { + title: '创建时间', + dataIndex: 'createTime', + width: 180, + }, +]; + +const isDir = (type: string) => type === '0'; +const isMenu = (type: string) => type === '1'; +const isButton = (type: string) => type === '2'; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'menuName', + label: '菜单名称', + component: 'Input', + colProps: { span: 8 }, + }, + { + field: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: [ + { label: '启用', value: '0' }, + { label: '停用', value: '1' }, + ], + }, + colProps: { span: 8 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'type', + label: '菜单类型', + component: 'RadioButtonGroup', + defaultValue: '0', + componentProps: { + options: [ + { label: '目录', value: '0' }, + { label: '菜单', value: '1' }, + { label: '按钮', value: '2' }, + ], + }, + colProps: { lg: 24, md: 24 }, + }, + { + field: 'menuName', + label: '菜单名称', + component: 'Input', + required: true, + }, + + { + field: 'parentMenu', + label: '上级菜单', + component: 'TreeSelect', + componentProps: { + fieldNames: { + label: 'menuName', + value: 'id', + }, + getPopupContainer: () => document.body, + }, + }, + + { + field: 'orderNo', + label: '排序', + component: 'InputNumber', + required: true, + }, + { + field: 'icon', + label: '图标', + component: 'IconPicker', + required: true, + ifShow: ({ values }) => !isButton(values.type), + }, + + { + field: 'routePath', + label: '路由地址', + component: 'Input', + required: true, + ifShow: ({ values }) => !isButton(values.type), + }, + { + field: 'component', + label: '组件路径', + component: 'Input', + ifShow: ({ values }) => isMenu(values.type), + }, + { + field: 'permission', + label: '权限标识', + component: 'Input', + ifShow: ({ values }) => !isDir(values.type), + }, + { + field: 'status', + label: '状态', + component: 'RadioButtonGroup', + defaultValue: '0', + componentProps: { + options: [ + { label: '启用', value: '0' }, + { label: '禁用', value: '1' }, + ], + }, + }, + { + field: 'isExt', + label: '是否外链', + component: 'RadioButtonGroup', + defaultValue: '0', + componentProps: { + options: [ + { label: '否', value: '0' }, + { label: '是', value: '1' }, + ], + }, + ifShow: ({ values }) => !isButton(values.type), + }, + + { + field: 'keepalive', + label: '是否缓存', + component: 'RadioButtonGroup', + defaultValue: '0', + componentProps: { + options: [ + { label: '否', value: '0' }, + { label: '是', value: '1' }, + ], + }, + ifShow: ({ values }) => isMenu(values.type), + }, + + { + field: 'show', + label: '是否显示', + component: 'RadioButtonGroup', + defaultValue: '0', + componentProps: { + options: [ + { label: '是', value: '0' }, + { label: '否', value: '1' }, + ], + }, + ifShow: ({ values }) => !isButton(values.type), + }, +]; diff --git a/src/views/demo/system/password/index.vue b/src/views/demo/system/password/index.vue new file mode 100644 index 00000000000..1f3ffd4cd55 --- /dev/null +++ b/src/views/demo/system/password/index.vue @@ -0,0 +1,41 @@ + + diff --git a/src/views/demo/system/password/pwd.data.ts b/src/views/demo/system/password/pwd.data.ts new file mode 100644 index 00000000000..668a3d4728e --- /dev/null +++ b/src/views/demo/system/password/pwd.data.ts @@ -0,0 +1,46 @@ +import { FormSchema } from '@/components/Form'; + +export const formSchema: FormSchema[] = [ + { + field: 'passwordOld', + label: '当前密码', + component: 'InputPassword', + required: true, + }, + { + field: 'passwordNew', + label: '新密码', + component: 'StrengthMeter', + componentProps: { + placeholder: '新密码', + }, + rules: [ + { + required: true, + message: '请输入新密码', + }, + ], + }, + { + field: 'confirmPassword', + label: '确认密码', + component: 'InputPassword', + + dynamicRules: ({ values }) => { + return [ + { + required: true, + validator: (_, value) => { + if (!value) { + return Promise.reject('密码不能为空'); + } + if (value !== values.passwordNew) { + return Promise.reject('两次输入的密码不一致!'); + } + return Promise.resolve(); + }, + }, + ]; + }, + }, +]; diff --git a/src/views/demo/system/role/RoleDrawer.vue b/src/views/demo/system/role/RoleDrawer.vue new file mode 100644 index 00000000000..2bd197014d4 --- /dev/null +++ b/src/views/demo/system/role/RoleDrawer.vue @@ -0,0 +1,74 @@ + + diff --git a/src/views/demo/system/role/index.vue b/src/views/demo/system/role/index.vue new file mode 100644 index 00000000000..08a7252775f --- /dev/null +++ b/src/views/demo/system/role/index.vue @@ -0,0 +1,85 @@ + + diff --git a/src/views/demo/system/role/role.data.ts b/src/views/demo/system/role/role.data.ts new file mode 100644 index 00000000000..1db47679fb0 --- /dev/null +++ b/src/views/demo/system/role/role.data.ts @@ -0,0 +1,123 @@ +import { BasicColumn, FormSchema } from '@/components/Table'; +import { h } from 'vue'; +import { Switch } from 'ant-design-vue'; +import { setRoleStatus } from '@/api/demo/system'; +import { useMessage } from '@/hooks/web/useMessage'; + +type CheckedType = boolean | string | number; +export const columns: BasicColumn[] = [ + { + title: '角色名称', + dataIndex: 'roleName', + width: 200, + }, + { + title: '角色值', + dataIndex: 'roleValue', + width: 180, + }, + { + title: '排序', + dataIndex: 'orderNo', + width: 50, + }, + { + title: '状态', + dataIndex: 'status', + width: 120, + customRender: ({ record }) => { + if (!Reflect.has(record, 'pendingStatus')) { + record.pendingStatus = false; + } + return h(Switch, { + checked: record.status === '1', + checkedChildren: '停用', + unCheckedChildren: '启用', + loading: record.pendingStatus, + onChange(checked: CheckedType) { + record.pendingStatus = true; + const newStatus = checked ? '1' : '0'; + const { createMessage } = useMessage(); + setRoleStatus(record.id, newStatus) + .then(() => { + record.status = newStatus; + createMessage.success(`已成功修改角色状态`); + }) + .catch(() => { + createMessage.error('修改角色状态失败'); + }) + .finally(() => { + record.pendingStatus = false; + }); + }, + }); + }, + }, + { + title: '创建时间', + dataIndex: 'createTime', + width: 180, + }, + { + title: '备注', + dataIndex: 'remark', + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'roleNme', + label: '角色名称', + component: 'Input', + colProps: { span: 8 }, + }, + { + field: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: [ + { label: '启用', value: '1' }, + { label: '停用', value: '0' }, + ], + }, + colProps: { span: 8 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'roleName', + label: '角色名称', + required: true, + component: 'Input', + }, + { + field: 'roleValue', + label: '角色值', + required: true, + component: 'Input', + }, + { + field: 'status', + label: '状态', + component: 'RadioButtonGroup', + defaultValue: '0', + componentProps: { + options: [ + { label: '启用', value: '1' }, + { label: '停用', value: '0' }, + ], + }, + }, + { + label: '备注', + field: 'remark', + component: 'InputTextArea', + }, + { + label: ' ', + field: 'menu', + slot: 'menu', + }, +]; diff --git a/src/views/demo/system/vxe-account/index.vue b/src/views/demo/system/vxe-account/index.vue new file mode 100644 index 00000000000..e061dee8701 --- /dev/null +++ b/src/views/demo/system/vxe-account/index.vue @@ -0,0 +1,86 @@ + + + diff --git a/src/views/demo/system/vxe-account/vxeAccount.data.ts b/src/views/demo/system/vxe-account/vxeAccount.data.ts new file mode 100644 index 00000000000..9053be9aa53 --- /dev/null +++ b/src/views/demo/system/vxe-account/vxeAccount.data.ts @@ -0,0 +1,84 @@ +import { VxeFormItemProps, VxeGridPropTypes } from '@/components/VxeTable'; +import { deptMap } from '../account/account.data'; + +export const columns: VxeGridPropTypes.Columns = [ + { + title: '用户名', + field: 'account', + width: 120, + }, + { + title: '昵称', + field: 'nickname', + width: 120, + }, + { + title: '邮箱', + field: 'email', + width: 120, + }, + { + title: '创建时间', + field: 'createTime', + width: 180, + }, + { + title: '角色', + field: 'role', + width: 200, + }, + { + title: '所属部门', + field: 'dept', + slots: { + default: ({ row }) => { + return deptMap[row.dept]; + }, + }, + }, + { + title: '备注', + field: 'remark', + }, + { + width: 160, + title: '操作', + align: 'center', + slots: { default: 'action' }, + fixed: 'right', + }, +]; + +export const searchFormSchema: VxeFormItemProps[] = [ + { + field: 'account', + title: '用户名', + itemRender: { + name: 'AInput', + }, + span: 6, + }, + { + field: 'nickname', + title: '昵称', + itemRender: { + name: 'AInput', + }, + span: 6, + }, + { + span: 12, + align: 'right', + className: '!pr-0', + itemRender: { + name: 'AButtonGroup', + children: [ + { + props: { type: 'primary', content: '查询', htmlType: 'submit' }, + attrs: { class: 'mr-2' }, + }, + { props: { type: 'default', htmlType: 'reset', content: '重置' } }, + ], + }, + }, +]; diff --git a/src/views/demo/table/AuthColumn.vue b/src/views/demo/table/AuthColumn.vue new file mode 100644 index 00000000000..5b51cc3874d --- /dev/null +++ b/src/views/demo/table/AuthColumn.vue @@ -0,0 +1,150 @@ + + diff --git a/src/views/demo/table/Basic.vue b/src/views/demo/table/Basic.vue new file mode 100644 index 00000000000..979162c68b1 --- /dev/null +++ b/src/views/demo/table/Basic.vue @@ -0,0 +1,65 @@ + + diff --git a/src/views/demo/table/CustomerCell.vue b/src/views/demo/table/CustomerCell.vue new file mode 100644 index 00000000000..03b05e90995 --- /dev/null +++ b/src/views/demo/table/CustomerCell.vue @@ -0,0 +1,97 @@ + + diff --git a/src/views/demo/table/EditCellTable.vue b/src/views/demo/table/EditCellTable.vue new file mode 100644 index 00000000000..f4e8de1cbbe --- /dev/null +++ b/src/views/demo/table/EditCellTable.vue @@ -0,0 +1,265 @@ + + diff --git a/src/views/demo/table/EditRowTable.vue b/src/views/demo/table/EditRowTable.vue new file mode 100644 index 00000000000..c98ea94121e --- /dev/null +++ b/src/views/demo/table/EditRowTable.vue @@ -0,0 +1,310 @@ + + diff --git a/src/views/demo/table/ExpandTable.vue b/src/views/demo/table/ExpandTable.vue new file mode 100644 index 00000000000..848aac1aa01 --- /dev/null +++ b/src/views/demo/table/ExpandTable.vue @@ -0,0 +1,65 @@ + + diff --git a/src/views/demo/table/FetchTable.vue b/src/views/demo/table/FetchTable.vue new file mode 100644 index 00000000000..2ec72867b79 --- /dev/null +++ b/src/views/demo/table/FetchTable.vue @@ -0,0 +1,31 @@ + + diff --git a/src/views/demo/table/FixedColumn.vue b/src/views/demo/table/FixedColumn.vue new file mode 100644 index 00000000000..ff97df3e6eb --- /dev/null +++ b/src/views/demo/table/FixedColumn.vue @@ -0,0 +1,85 @@ + + diff --git a/src/views/demo/table/FixedHeight.vue b/src/views/demo/table/FixedHeight.vue new file mode 100644 index 00000000000..bafd47e3fbd --- /dev/null +++ b/src/views/demo/table/FixedHeight.vue @@ -0,0 +1,37 @@ + + diff --git a/src/views/demo/table/FooterTable.vue b/src/views/demo/table/FooterTable.vue new file mode 100644 index 00000000000..853b42fa89f --- /dev/null +++ b/src/views/demo/table/FooterTable.vue @@ -0,0 +1,41 @@ + + diff --git a/src/views/demo/table/FormTable.vue b/src/views/demo/table/FormTable.vue new file mode 100644 index 00000000000..57d1e5deab3 --- /dev/null +++ b/src/views/demo/table/FormTable.vue @@ -0,0 +1,34 @@ + + diff --git a/src/views/demo/table/MergeHeader.vue b/src/views/demo/table/MergeHeader.vue new file mode 100644 index 00000000000..7a1848d883e --- /dev/null +++ b/src/views/demo/table/MergeHeader.vue @@ -0,0 +1,18 @@ + + diff --git a/src/views/demo/table/MultipleHeader.vue b/src/views/demo/table/MultipleHeader.vue new file mode 100644 index 00000000000..67cd4b9daa9 --- /dev/null +++ b/src/views/demo/table/MultipleHeader.vue @@ -0,0 +1,17 @@ + + diff --git a/src/views/demo/table/RefTable.vue b/src/views/demo/table/RefTable.vue new file mode 100644 index 00000000000..da0eb19f671 --- /dev/null +++ b/src/views/demo/table/RefTable.vue @@ -0,0 +1,117 @@ + + diff --git a/src/views/demo/table/ResizeParentHeightTable.vue b/src/views/demo/table/ResizeParentHeightTable.vue new file mode 100644 index 00000000000..52361a95117 --- /dev/null +++ b/src/views/demo/table/ResizeParentHeightTable.vue @@ -0,0 +1,67 @@ + + diff --git a/src/views/demo/table/TreeTable.vue b/src/views/demo/table/TreeTable.vue new file mode 100644 index 00000000000..5d0cef7c3ae --- /dev/null +++ b/src/views/demo/table/TreeTable.vue @@ -0,0 +1,38 @@ + + diff --git a/src/views/demo/table/UseTable.vue b/src/views/demo/table/UseTable.vue new file mode 100644 index 00000000000..c38faac9a9c --- /dev/null +++ b/src/views/demo/table/UseTable.vue @@ -0,0 +1,137 @@ + + diff --git a/src/views/demo/table/VxeTable.vue b/src/views/demo/table/VxeTable.vue new file mode 100644 index 00000000000..df18877f9c9 --- /dev/null +++ b/src/views/demo/table/VxeTable.vue @@ -0,0 +1,115 @@ + + diff --git a/src/views/demo/table/tableData.tsx b/src/views/demo/table/tableData.tsx new file mode 100644 index 00000000000..538053060c2 --- /dev/null +++ b/src/views/demo/table/tableData.tsx @@ -0,0 +1,464 @@ +import { optionsListApi } from '@/api/demo/select'; +import { FormProps, FormSchema, BasicColumn } from '@/components/Table'; +import { VxeFormItemProps, VxeGridPropTypes } from '@/components/VxeTable'; +import { ref } from 'vue'; +import { Input } from 'ant-design-vue'; + +export function getBasicColumns(): BasicColumn[] { + return [ + { + title: 'ID', + dataIndex: 'id', + fixed: 'left', + width: 200, + }, + { + title: '姓名', + dataIndex: 'name', + width: 150, + filters: [ + { text: 'Male', value: 'male' }, + { text: 'Female', value: 'female' }, + ], + }, + { + title: '地址', + dataIndex: 'address', + }, + { + title: '编号', + dataIndex: 'no', + width: 150, + sorter: true, + defaultHidden: true, + }, + { + title: '开始时间', + width: 150, + sorter: true, + dataIndex: 'beginTime', + }, + { + title: '结束时间', + width: 150, + sorter: true, + dataIndex: 'endTime', + }, + ]; +} + +export function getBasicShortColumns(): BasicColumn[] { + return [ + { + title: 'ID', + width: 150, + dataIndex: 'id', + sorter: true, + sortOrder: 'ascend', + }, + { + title: '姓名', + dataIndex: 'name', + width: 120, + }, + { + title: '地址', + dataIndex: 'address', + }, + { + title: '编号', + dataIndex: 'no', + width: 80, + }, + ]; +} + +export function getMultipleHeaderColumns(): BasicColumn[] { + const testRef = ref('姓名:'); + return [ + { + title: 'ID', + dataIndex: 'id', + width: 200, + }, + { + title: '姓名', + customHeaderRender() { + return ( + + ); + }, + dataIndex: 'name', + width: 120, + }, + { + title: '地址', + dataIndex: 'address', + sorter: true, + children: [ + { + title: '编号', + customHeaderRender(column) { + // 【自定义渲染的】 + return ( +
+ _ {testRef.value} _ + {column.customTitle} +
+ ); + }, + dataIndex: 'no', + width: 120, + filters: [ + { text: 'Male', value: 'male', children: [] }, + { text: 'Female', value: 'female', children: [] }, + ], + }, + + { + title: '开始时间', + dataIndex: 'beginTime', + width: 120, + }, + { + title: '结束时间', + dataIndex: 'endTime', + width: 120, + }, + ], + }, + ]; +} + +export function getCustomHeaderColumns(): BasicColumn[] { + return [ + { + title: 'ID', + dataIndex: 'id', + helpMessage: 'headerHelpMessage方式1', + width: 200, + }, + { + // title: '姓名', + dataIndex: 'name', + width: 120, + }, + { + // title: '地址', + dataIndex: 'address', + width: 120, + sorter: true, + }, + + { + title: '编号', + dataIndex: 'no', + width: 120, + filters: [ + { text: 'Male', value: 'male', children: [] }, + { text: 'Female', value: 'female', children: [] }, + ], + }, + { + title: '开始时间', + dataIndex: 'beginTime', + width: 120, + }, + { + title: '结束时间', + dataIndex: 'endTime', + width: 120, + }, + ]; +} + +const cellContent = (_, index) => ({ + colSpan: index === 9 ? 0 : 1, +}); + +export function getMergeHeaderColumns(): BasicColumn[] { + return [ + { + title: 'ID', + dataIndex: 'id', + width: 300, + customCell: (_, index) => ({ + colSpan: index === 9 ? 6 : 1, + }), + }, + { + title: '姓名', + dataIndex: 'name', + width: 300, + customCell: cellContent, + }, + { + title: '地址', + dataIndex: 'address', + colSpan: 2, + width: 120, + sorter: true, + customCell: (_, index) => ({ + rowSpan: index === 2 ? 2 : 1, + colSpan: index === 3 || index === 9 ? 0 : 1, + }), + }, + { + title: '编号', + dataIndex: 'no', + colSpan: 0, + filters: [ + { text: 'Male', value: 'male', children: [] }, + { text: 'Female', value: 'female', children: [] }, + ], + customCell: cellContent, + }, + { + title: '开始时间', + dataIndex: 'beginTime', + width: 200, + customCell: cellContent, + }, + { + title: '结束时间', + dataIndex: 'endTime', + width: 200, + customCell: cellContent, + }, + ]; +} +export const getAdvanceSchema = (itemNumber = 6): FormSchema[] => { + const arr: FormSchema[] = []; + for (let index = 0; index < itemNumber; index++) { + arr.push({ + field: `field${index}`, + label: `字段${index}`, + component: 'Input', + colProps: { + xl: 12, + xxl: 8, + }, + }); + } + return arr; +}; +export function getFormConfig(): Partial { + return { + labelWidth: 100, + schemas: [ + ...getAdvanceSchema(5), + { + field: `field11`, + label: `Slot示例`, + slot: 'custom', + colProps: { + xl: 12, + xxl: 8, + }, + }, + ], + }; +} +export function getBasicData() { + return (() => { + const arr: any = []; + for (let index = 0; index < 40; index++) { + arr.push({ + id: `${index}`, + name: 'John Brown', + age: `1${index}`, + no: `${index + 10}`, + address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park', + beginTime: new Date().toLocaleString(), + endTime: new Date().toLocaleString(), + }); + } + return arr; + })(); +} + +export function getTreeTableData() { + return (() => { + const arr: any = []; + for (let index = 0; index < 40; index++) { + arr.push({ + id: `${index}`, + name: 'John Brown', + age: `1${index}`, + no: `${index + 10}`, + address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park', + beginTime: new Date().toLocaleString(), + endTime: new Date().toLocaleString(), + children: [ + { + id: `l2-${index}-1`, + name: 'John Brown', + age: `1`, + no: `${index + 10}`, + address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park', + beginTime: new Date().toLocaleString(), + endTime: new Date().toLocaleString(), + children: [ + { + id: `l3-${index}-1-1`, + name: 'John Brown', + age: `11`, + no: `11`, + address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park', + beginTime: new Date().toLocaleString(), + endTime: new Date().toLocaleString(), + }, + { + id: `l3-${index}-1-2`, + name: 'John Brown', + age: `12`, + no: `12`, + address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park', + beginTime: new Date().toLocaleString(), + endTime: new Date().toLocaleString(), + }, + ], + }, + { + id: `l2-${index}-2`, + name: 'John Brown', + age: `2`, + no: `${index + 10}`, + address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park', + beginTime: new Date().toLocaleString(), + endTime: new Date().toLocaleString(), + children: [ + { + id: `l3-${index}-2-1`, + name: 'John Brown', + age: `21`, + no: `21`, + address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park', + beginTime: new Date().toLocaleString(), + endTime: new Date().toLocaleString(), + }, + { + id: `l3-${index}-2-2`, + name: 'John Brown', + age: `22`, + no: `22`, + address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park', + beginTime: new Date().toLocaleString(), + endTime: new Date().toLocaleString(), + }, + ], + }, + ], + }); + } + return arr; + })(); +} + +export const vxeTableColumns: VxeGridPropTypes.Columns = [ + { + title: '序号', + type: 'seq', + fixed: 'left', + width: '50', + align: 'center', + }, + { + title: '固定列', + field: 'name', + width: 150, + showOverflow: 'tooltip', + fixed: 'left', + }, + { + title: '自适应列', + field: 'address', + }, + { + title: '自定义列(自定义导出)', + field: 'no', + width: 200, + showOverflow: 'tooltip', + align: 'center', + slots: { + default: ({ row }) => { + const text = `自定义${row.no}`; + return [
{text}
]; + }, + }, + exportMethod: ({ row }) => { + return `自定义${row.no}导出`; + }, + }, + { + title: '自定义编辑', + width: 150, + field: 'name1', + align: 'center', + editRender: { + name: 'AInput', + placeholder: '请点击输入', + }, + }, + { + title: '开始时间', + width: 150, + field: 'beginTime', + showOverflow: 'tooltip', + align: 'center', + }, + { + title: '结束时间', + width: 150, + field: 'endTime', + showOverflow: 'tooltip', + align: 'center', + }, + { + width: 160, + title: '操作', + align: 'center', + slots: { default: 'action' }, + fixed: 'right', + }, +]; + +export const vxeTableFormSchema: VxeFormItemProps[] = [ + { + field: 'field0', + title: 'field0', + itemRender: { + name: 'AInput', + }, + span: 6, + }, + { + field: 'field1', + title: 'field1', + itemRender: { + name: 'AApiSelect', + props: { + api: optionsListApi, + resultField: 'list', + labelField: 'name', + valueField: 'id', + }, + }, + span: 6, + }, + { + span: 12, + align: 'right', + className: '!pr-0', + itemRender: { + name: 'AButtonGroup', + children: [ + { + props: { type: 'primary', content: '查询', htmlType: 'submit' }, + attrs: { class: 'mr-2' }, + }, + { props: { type: 'default', htmlType: 'reset', content: '重置' } }, + ], + }, + }, +]; diff --git a/src/views/demo/tree/ActionTree.vue b/src/views/demo/tree/ActionTree.vue new file mode 100644 index 00000000000..5cf469e746f --- /dev/null +++ b/src/views/demo/tree/ActionTree.vue @@ -0,0 +1,121 @@ + + diff --git a/src/views/demo/tree/EditTree.vue b/src/views/demo/tree/EditTree.vue new file mode 100644 index 00000000000..e0492f7d7fe --- /dev/null +++ b/src/views/demo/tree/EditTree.vue @@ -0,0 +1,119 @@ + + diff --git a/src/views/demo/tree/data.ts b/src/views/demo/tree/data.ts new file mode 100644 index 00000000000..ea78cebc6a0 --- /dev/null +++ b/src/views/demo/tree/data.ts @@ -0,0 +1,119 @@ +import { TreeItem } from '@/components/Tree'; + +export const treeData: TreeItem[] = [ + { + title: 'parent ', + key: '0-0', + icon: 'ion:settings-outline', + children: [ + { title: 'leaf', key: '0-0-0' }, + { + title: 'leaf', + key: '0-0-1', + children: [ + { title: 'leaf', key: '0-0-0-0', children: [{ title: 'leaf', key: '0-0-0-0-1' }] }, + { title: 'leaf', key: '0-0-0-1' }, + ], + }, + ], + }, + { + title: 'parent 2', + key: '1-1', + children: [ + { title: 'leaf', key: '1-1-0' }, + { title: 'leaf', key: '1-1-1' }, + ], + }, + { + title: 'parent 3', + key: '2-2', + children: [ + { title: 'leaf', key: '2-2-0' }, + { title: 'leaf', key: '2-2-1' }, + ], + }, +]; + +export const treeData2: any[] = [ + { + name: 'parent ', + id: '0-0', + + children: [ + { name: 'leaf', id: '0-0-0' }, + { + name: 'leaf', + id: '0-0-1', + + children: [ + { + name: 'leaf', + + id: '0-0-0-0', + children: [{ name: 'leaf', id: '0-0-0-0-1' }], + }, + { name: 'leaf', id: '0-0-0-1' }, + ], + }, + ], + }, + { + name: 'parent 2', + id: '1-1', + + children: [ + { name: 'leaf', id: '1-1-0' }, + { name: 'leaf', id: '1-1-1' }, + ], + }, + { + name: 'parent 3', + id: '2-2', + + children: [ + { name: 'leaf', id: '2-2-0' }, + { name: 'leaf', id: '2-2-1' }, + ], + }, +]; + +export const treeData3: any[] = [ + { + name: 'parent ', + key: '0-0', + children: [ + { name: 'leaf', key: '0-0-0' }, + { + name: 'leaf', + key: '0-0-1', + children: [ + { + name: 'leaf', + key: '0-0-0-0', + children: [{ name: 'leaf', key: '0-0-0-0-1' }], + }, + { name: 'leaf', key: '0-0-0-1' }, + ], + }, + ], + }, + { + name: 'parent 2', + key: '1-1', + + children: [ + { name: 'leaf', key: '1-1-0' }, + { name: 'leaf', key: '1-1-1' }, + ], + }, + { + name: 'parent 3', + key: '2-2', + + children: [ + { name: 'leaf', key: '2-2-0' }, + { name: 'leaf', key: '2-2-1' }, + ], + }, +]; diff --git a/src/views/demo/tree/index.vue b/src/views/demo/tree/index.vue new file mode 100644 index 00000000000..da3f3261da7 --- /dev/null +++ b/src/views/demo/tree/index.vue @@ -0,0 +1,134 @@ + + diff --git a/src/views/form-design/components/VFormCreate/components/FormRender.vue b/src/views/form-design/components/VFormCreate/components/FormRender.vue new file mode 100644 index 00000000000..564cff73748 --- /dev/null +++ b/src/views/form-design/components/VFormCreate/components/FormRender.vue @@ -0,0 +1,79 @@ + + + + diff --git a/src/views/form-design/components/VFormCreate/index.vue b/src/views/form-design/components/VFormCreate/index.vue new file mode 100644 index 00000000000..8091f71f642 --- /dev/null +++ b/src/views/form-design/components/VFormCreate/index.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/src/views/form-design/components/VFormDesign/components/CodeModal.vue b/src/views/form-design/components/VFormDesign/components/CodeModal.vue new file mode 100644 index 00000000000..a20aa0b72c4 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/CodeModal.vue @@ -0,0 +1,83 @@ + + + diff --git a/src/views/form-design/components/VFormDesign/components/ComponentProps.vue b/src/views/form-design/components/VFormDesign/components/ComponentProps.vue new file mode 100644 index 00000000000..e52711351dd --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/ComponentProps.vue @@ -0,0 +1,243 @@ + + + diff --git a/src/views/form-design/components/VFormDesign/components/FormItemColumnProps.vue b/src/views/form-design/components/VFormDesign/components/FormItemColumnProps.vue new file mode 100644 index 00000000000..6324dc13abb --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/FormItemColumnProps.vue @@ -0,0 +1,64 @@ + + + diff --git a/src/views/form-design/components/VFormDesign/components/FormItemProps.vue b/src/views/form-design/components/VFormDesign/components/FormItemProps.vue new file mode 100644 index 00000000000..6252e690d63 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/FormItemProps.vue @@ -0,0 +1,108 @@ + + + diff --git a/src/views/form-design/components/VFormDesign/components/FormNode.vue b/src/views/form-design/components/VFormDesign/components/FormNode.vue new file mode 100644 index 00000000000..867cca6a627 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/FormNode.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/views/form-design/components/VFormDesign/components/FormNodeOperate.vue b/src/views/form-design/components/VFormDesign/components/FormNodeOperate.vue new file mode 100644 index 00000000000..1b1a587d6bb --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/FormNodeOperate.vue @@ -0,0 +1,74 @@ + + + + diff --git a/src/views/form-design/components/VFormDesign/components/FormOptions.vue b/src/views/form-design/components/VFormDesign/components/FormOptions.vue new file mode 100644 index 00000000000..eba99ec2fe0 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/FormOptions.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/views/form-design/components/VFormDesign/components/FormProps.vue b/src/views/form-design/components/VFormDesign/components/FormProps.vue new file mode 100644 index 00000000000..32a0c9800fb --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/FormProps.vue @@ -0,0 +1,112 @@ + + + diff --git a/src/views/form-design/components/VFormDesign/components/ImportJsonModal.vue b/src/views/form-design/components/VFormDesign/components/ImportJsonModal.vue new file mode 100644 index 00000000000..a82b5c8c058 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/ImportJsonModal.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/src/views/form-design/components/VFormDesign/components/JsonModal.vue b/src/views/form-design/components/VFormDesign/components/JsonModal.vue new file mode 100644 index 00000000000..d380754829c --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/JsonModal.vue @@ -0,0 +1,65 @@ + + + diff --git a/src/views/form-design/components/VFormDesign/components/LayoutItem.vue b/src/views/form-design/components/VFormDesign/components/LayoutItem.vue new file mode 100644 index 00000000000..e64b07b2b00 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/LayoutItem.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/views/form-design/components/VFormDesign/components/PreviewCode.vue b/src/views/form-design/components/VFormDesign/components/PreviewCode.vue new file mode 100644 index 00000000000..95f89a1b639 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/PreviewCode.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/views/form-design/components/VFormDesign/components/RuleProps.vue b/src/views/form-design/components/VFormDesign/components/RuleProps.vue new file mode 100644 index 00000000000..90a6a3a49af --- /dev/null +++ b/src/views/form-design/components/VFormDesign/components/RuleProps.vue @@ -0,0 +1,293 @@ + + + + + diff --git a/src/views/form-design/components/VFormDesign/config/componentPropsConfig.ts b/src/views/form-design/components/VFormDesign/config/componentPropsConfig.ts new file mode 100644 index 00000000000..1220a5d17d4 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/config/componentPropsConfig.ts @@ -0,0 +1,1146 @@ +import { IBaseFormAttrs } from './formItemPropsConfig'; + +interface IBaseComponentProps { + [key: string]: IBaseFormAttrs[]; +} + +type BaseFormAttrs = Omit; + +export const baseComponentControlAttrs: Omit[] = [ + { + // 没有disabled属性的控件不能作为form控件 + name: 'disabled', + label: '禁用', + }, + { + // 没有disabled属性的控件不能作为form控件 + name: 'autofocus', + label: '自动获取焦点', + includes: [ + 'Input', + 'Select', + 'InputTextArea', + 'InputNumber', + 'DatePicker', + 'RangePicker', + 'MonthPicker', + 'TimePicker', + 'Cascader', + 'TreeSelect', + 'Switch', + 'AutoComplete', + 'Slider', + ], + }, + { + name: 'allowClear', + label: '可清除', + includes: [ + 'Input', + 'Select', + 'InputTextArea', + 'InputNumber', + 'DatePicker', + 'RangePicker', + 'MonthPicker', + 'TimePicker', + 'Cascader', + 'TreeSelect', + 'AutoComplete', + ], + }, + { name: 'fullscreen', label: '全屏', includes: ['Calendar'] }, + { + name: 'showSearch', + label: '可搜索', + includes: ['Select', 'TreeSelect', 'Cascader', 'Transfer'], + }, + { + name: 'showTime', + label: '显示时间', + includes: ['DatePicker', 'RangePicker', 'MonthPicker'], + }, + { + name: 'range', + label: '双向滑动', + includes: [], + }, + { + name: 'allowHalf', + label: '允许半选', + includes: ['Rate'], + }, + { + name: 'multiple', + label: '多选', + includes: ['Select', 'TreeSelect', 'Upload'], + }, + { + name: 'directory', + label: '文件夹', + includes: ['Upload'], + }, + { + name: 'withCredentials', + label: '携带cookie', + includes: ['Upload'], + }, + { + name: 'bordered', + label: '是否有边框', + includes: ['Select', 'Input'], + }, + { + name: 'defaultActiveFirstOption', + label: '高亮第一个选项', + component: 'Checkbox', + includes: ['Select', 'AutoComplete'], + }, + { + name: 'dropdownMatchSelectWidth', + label: '下拉菜单和选择器同宽', + component: 'Checkbox', + includes: ['Select', 'TreeSelect', 'AutoComplete'], + }, +]; + +//共用属性 +export const baseComponentCommonAttrs: Omit[] = [ + { + name: 'size', + label: '尺寸', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '默认', + value: 'default', + }, + { + label: '大', + value: 'large', + }, + { + label: '小', + value: 'small', + }, + ], + }, + includes: ['InputNumber', 'Input', 'Cascader', 'Button'], + }, + { + name: 'placeholder', + label: '占位符', + component: 'Input', + componentProps: { + placeholder: '请输入占位符', + }, + includes: [ + 'AutoComplete', + 'InputTextArea', + 'InputNumber', + 'Input', + 'InputTextArea', + 'Select', + 'DatePicker', + 'MonthPicker', + 'TimePicker', + 'TreeSelect', + 'Cascader', + ], + }, + { + name: 'style', + label: '样式', + component: 'Input', + componentProps: { + placeholder: '请输入样式', + }, + }, + { + name: 'open', + label: '一直展开下拉菜单', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '默认', + value: undefined, + }, + { + label: '是', + value: true, + }, + { + label: '否', + value: false, + }, + ], + }, + includes: ['Select', 'AutoComplete'], + }, +]; + +const componentAttrs: IBaseComponentProps = { + AutoComplete: [ + { + name: 'backfill', + label: '自动回填', + component: 'Switch', + componentProps: { + span: 8, + }, + }, + { + name: 'defaultOpen', + label: '是否默认展开下拉菜单', + component: 'Checkbox', + }, + ], + IconPicker: [ + { + name: 'mode', + label: '模式', + component: 'RadioGroup', + componentProps: { + options: [ + { label: 'ICONIFY', value: null }, + { label: 'SVG', value: 'svg' }, + // { label: '组合', value: 'combobox' }, + ], + }, + }, + ], + + // https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#%3Cinput%3E_types + Input: [ + { + name: 'type', + label: '类型', + component: 'Select', + componentProps: { + options: [ + { value: 'text', label: '文本' }, + { value: 'search', label: '搜索' }, + { value: 'number', label: '数字' }, + { value: 'range', label: '数字范围' }, + { value: 'password', label: '密码' }, + { value: 'date', label: '日期' }, + { value: 'datetime-local', label: '日期-无时区' }, + { value: 'time', label: '时间' }, + { value: 'month', label: '年月' }, + { value: 'week', label: '星期' }, + { value: 'email', label: '邮箱' }, + { value: 'url', label: 'URL' }, + { value: 'tel', label: '电话号码' }, + { value: 'file', label: '文件' }, + { value: 'button', label: '按钮' }, + { value: 'submit', label: '提交按钮' }, + { value: 'reset', label: '重置按钮' }, + { value: 'radio', label: '单选按钮' }, + { value: 'checkbox', label: '复选框' }, + { value: 'color', label: '颜色' }, + { value: 'image', label: '图像' }, + { value: 'hidden', label: '隐藏' }, + ], + }, + }, + { + name: 'defaultValue', + label: '默认值', + component: 'Input', + componentProps: { + type: 'text', + placeholder: '请输入默认值', + }, + }, + { + name: 'prefix', + label: '前缀', + component: 'Input', + componentProps: { + type: 'text', + placeholder: '请输入前缀', + }, + }, + { + name: 'suffix', + label: '后缀', + component: 'Input', + componentProps: { + type: 'text', + placeholder: '请输入后缀', + }, + }, + { + name: 'addonBefore', + label: '前置标签', + component: 'Input', + componentProps: { + type: 'text', + placeholder: '请输入前置标签', + }, + }, + { + name: 'addonAfter', + label: '后置标签', + component: 'Input', + componentProps: { + type: 'text', + placeholder: '请输入后置标签', + }, + }, + { + name: 'maxLength', + label: '最大长度', + component: 'InputNumber', + componentProps: { + type: 'text', + placeholder: '请输入最大长度', + }, + }, + ], + + InputNumber: [ + { + name: 'defaultValue', + label: '默认值', + component: 'InputNumber', + componentProps: { + placeholder: '请输入默认值', + }, + }, + { + name: 'min', + label: '最小值', + component: 'InputNumber', + componentProps: { + type: 'text', + placeholder: '请输入最小值', + }, + }, + { + name: 'max', + label: '最大值', + component: 'InputNumber', + componentProps: { + type: 'text', + placeholder: '请输入最大值', + }, + }, + { + name: 'precision', + label: '数值精度', + component: 'InputNumber', + componentProps: { + type: 'text', + placeholder: '请输入最大值', + }, + }, + { + name: 'step', + label: '步长', + component: 'InputNumber', + componentProps: { + type: 'text', + placeholder: '请输入步长', + }, + }, + { + name: 'decimalSeparator', + label: '小数点', + component: 'Input', + componentProps: { type: 'text', placeholder: '请输入小数点' }, + }, + { + name: 'addonBefore', + label: '前置标签', + component: 'Input', + componentProps: { + type: 'text', + placeholder: '请输入前置标签', + }, + }, + { + name: 'addonAfter', + label: '后置标签', + component: 'Input', + componentProps: { + type: 'text', + placeholder: '请输入后置标签', + }, + }, + { + name: 'controls', + label: '是否显示增减按钮', + component: 'Checkbox', + }, + { + name: 'keyboard', + label: '是否启用键盘快捷行为', + component: 'Checkbox', + }, + { + name: 'stringMode', + label: '字符值模式', + component: 'Checkbox', + }, + { + name: 'bordered', + label: '是否有边框', + component: 'Checkbox', + }, + ], + InputTextArea: [ + { + name: 'defaultValue', + label: '默认值', + component: 'Input', + componentProps: { + type: 'text', + placeholder: '请输入默认值', + }, + }, + { + name: 'maxlength', + label: '最大长度', + component: 'InputNumber', + componentProps: { + placeholder: '请输入最大长度', + }, + }, + { + name: 'minlength', + label: '最小长度', + component: 'InputNumber', + componentProps: { + placeholder: '请输入最小长度', + }, + }, + { + name: 'cols', + label: '可见列数', + component: 'InputNumber', + componentProps: { + placeholder: '请输入可见列数', + min: 0, + }, + }, + { + name: 'rows', + label: '可见行数', + component: 'InputNumber', + componentProps: { + placeholder: '请输入可见行数', + min: 0, + }, + }, + { + name: 'minlength', + label: '最小长度', + component: 'InputNumber', + componentProps: { + placeholder: '请输入最小长度', + }, + }, + { + name: 'autosize', + label: '自适应内容高度', + component: 'Checkbox', + }, + { + name: 'showCount', + label: '是否展示字数', + component: 'Checkbox', + }, + { + name: 'readonly', + label: '是否只读', + component: 'Checkbox', + }, + { + name: 'spellcheck', + label: '读写检查', + component: 'Checkbox', + }, + { + name: 'autocomplete', + label: '是否自动完成', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '正常', value: null }, + { label: '开', value: 'on' }, + { label: '关', value: 'off' }, + ], + }, + }, + { + name: 'autocorrect', + label: '是否自动纠错', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '正常', value: null }, + { label: '开', value: 'on' }, + { label: '关', value: 'off' }, + ], + }, + }, + ], + Select: [ + { + name: 'mode', + label: '选择模式(默认单选)', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '单选', value: null }, + { label: '多选', value: 'multiple' }, + { label: '标签', value: 'tags' }, + // { label: '组合', value: 'combobox' }, + ], + }, + }, + { + name: 'autoClearSearchValue', + label: '是否在选中项后清空搜索框', + component: 'Checkbox', + }, + { + name: 'labelInValue', + label: '选项的label包装到value中', + component: 'Checkbox', + }, + { + name: 'showArrow', + label: '显示下拉小箭头', + component: 'Checkbox', + }, + { + name: 'defaultOpen', + label: '默认展开下拉菜单', + component: 'Checkbox', + }, + ], + Checkbox: [ + { + name: 'indeterminate', + label: '设置indeterminate状态', + component: 'Checkbox', + }, + ], + CheckboxGroup: [], + RadioGroup: [ + { + name: 'defaultValue', + label: '默认值', + component: 'Input', + componentProps: { + placeholder: '请输入默认值', + }, + }, + { + name: 'buttonStyle', + label: 'RadioButton的风格样式', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: 'outline', + value: 'outline', + }, + { + label: 'solid', + value: 'solid', + }, + ], + }, + }, + { + name: 'optionType', + label: 'options类型', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '默认', + value: 'default', + }, + { + label: '按钮', + value: 'button', + }, + ], + //根据其它选项的值更新自身控件配置值 + //compProp当前组件的属性, + //configProps,当且组件的所有配置选项 + //self,当前配置的componentProps属性 + //返回真值进行更新 + // _propsFunc: (compProp, configProps, self) => { + // console.log("i'm called"); + // console.log(compProp, configProps, self); + // if (compProp['buttonStyle'] && compProp['buttonStyle'] == 'outline') { + // if (!self['disabled']) { + // self['disabled'] = true; + // return 1; + // } + // } else { + // if (self['disabled']) { + // self['disabled'] = false; + // return 1; + // } + // } + + // // return prop.optionType == 'button'; + // }, + }, + }, + { + name: 'size', + label: '尺寸', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '默认', + value: 'default', + }, + { + label: '大', + value: 'large', + }, + { + label: '小', + value: 'small', + }, + ], + }, + }, + ], + DatePicker: [ + { + name: 'format', + label: '展示格式(format)', + component: 'Input', + componentProps: { + placeholder: 'YYYY-MM-DD', + }, + }, + { + name: 'valueFormat', + label: '绑定值格式(valueFormat)', + component: 'Input', + componentProps: { + placeholder: 'YYYY-MM-DD', + }, + }, + ], + RangePicker: [ + { + name: 'placeholder', + label: '占位符', + children: [ + { + name: '', + label: '', + component: 'Input', + }, + { + name: '', + label: '', + component: 'Input', + }, + ], + }, + { + name: 'format', + label: '展示格式(format)', + component: 'Input', + componentProps: { + placeholder: 'YYYY-MM-DD HH:mm:ss', + }, + }, + { + name: 'valueFormat', + label: '绑定值格式(valueFormat)', + component: 'Input', + componentProps: { + placeholder: 'YYYY-MM-DD', + }, + }, + ], + MonthPicker: [ + { + name: 'format', + label: '展示格式(format)', + component: 'Input', + componentProps: { + placeholder: 'YYYY-MM', + }, + }, + { + name: 'valueFormat', + label: '绑定值格式(valueFormat)', + component: 'Input', + componentProps: { + placeholder: 'YYYY-MM', + }, + }, + ], + TimePicker: [ + { + name: 'format', + label: '展示格式(format)', + component: 'Input', + componentProps: { + placeholder: 'YYYY-MM', + }, + }, + { + name: 'valueFormat', + label: '绑定值格式(valueFormat)', + component: 'Input', + componentProps: { + placeholder: 'YYYY-MM', + }, + }, + ], + Slider: [ + { + name: 'defaultValue', + label: '默认值', + component: 'InputNumber', + componentProps: { + placeholder: '请输入默认值', + }, + }, + { + name: 'min', + label: '最小值', + component: 'InputNumber', + componentProps: { + placeholder: '请输入最小值', + }, + }, + { + name: 'max', + label: '最大值', + component: 'InputNumber', + componentProps: { + placeholder: '请输入最大值', + }, + }, + { + name: 'step', + label: '步长', + component: 'InputNumber', + componentProps: { + placeholder: '请输入步长', + }, + }, + { + name: 'tooltipPlacement', + label: 'Tooltip 展示位置', + component: 'Select', + componentProps: { + options: [ + { value: 'top', label: '上' }, + { value: 'left', label: '左' }, + { value: 'right', label: '右' }, + { value: 'bottom', label: '下' }, + { value: 'topLeft', label: '上右' }, + { value: 'topRight', label: '上左' }, + { value: 'bottomLeft', label: '右下' }, + { value: 'bottomRight', label: '左下' }, + { value: 'leftTop', label: '左下' }, + { value: 'leftBottom', label: '左上' }, + { value: 'rightTop', label: '右下' }, + { value: 'rightBottom', label: '右上' }, + ], + }, + }, + { + name: 'tooltipVisible', + label: '始终显示Tooltip', + component: 'Checkbox', + }, + { + name: 'dots', + label: '只能拖拽到刻度上', + component: 'Checkbox', + }, + { + name: 'range', + label: '双滑块模式', + component: 'Checkbox', + }, + { + name: 'reverse', + label: '反向坐标轴', + component: 'Checkbox', + }, + { + name: 'vertical', + label: '垂直方向', + component: 'Checkbox', + }, + { + name: 'included', + label: '值为包含关系', + component: 'Checkbox', + }, + ], + Rate: [ + { + name: 'defaultValue', + label: '默认值', + component: 'InputNumber', + componentProps: { + placeholder: '请输入默认值', + }, + }, + { + name: 'character', + label: '自定义字符', + component: 'Input', + componentProps: { + placeholder: '请输入自定义字符', + }, + }, + { + name: 'count', + label: 'start 总数', + component: 'InputNumber', + componentProps: { + placeholder: '请输入自定义字符', + }, + }, + ], + Switch: [ + { + name: 'checkedChildren', + label: '选中时的内容', + component: 'Input', + componentProps: { + placeholder: '请输入选中时的内容', + }, + }, + { + name: 'checkedValue', + label: '选中时的值', + component: 'Input', + componentProps: { + placeholder: '请输入选中时的值', + }, + }, + { + name: 'unCheckedChildren', + label: '非选中时的内容', + component: 'Input', + componentProps: { + placeholder: '请输入非选中时的内容', + }, + }, + { + name: 'unCheckedValue', + label: '非选中时的值', + component: 'Input', + componentProps: { + placeholder: '请输入非选中时的值', + }, + }, + { + name: 'loading', + label: '加载中的开关', + component: 'Checkbox', + }, + { + name: 'size', + label: '尺寸', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '默认', + value: 'default', + }, + { + label: '小', + value: 'small', + }, + ], + }, + }, + ], + TreeSelect: [ + { + name: 'defaultValue', + label: '默认值', + component: 'Input', + componentProps: { + placeholder: '请输入默认值', + }, + }, + { + name: 'searchPlaceholder', + label: '搜索框默认文字', + component: 'Input', + componentProps: { + placeholder: '请输入搜索框默认文字', + }, + }, + { + name: 'treeNodeFilterProp', + label: '输入项过滤对应的 treeNode 属性', + component: 'Input', + componentProps: { + defaultValue: 'value', + }, + }, + { + name: 'treeNodeLabelProp', + label: '作为显示的 prop 设置', + component: 'Input', + componentProps: { + defaultValue: 'title', + }, + }, + { + name: 'popupClassName', + label: '下拉菜单的 className 属性', + component: 'Input', + componentProps: { + placeholder: '请输入下拉菜单的 className 属性', + }, + }, + + { + name: 'labelInValue', + label: '选项的label包装到value中', + component: 'Checkbox', + }, + { + name: 'treeIcon', + label: '展示TreeNode title前的图标', + component: 'Checkbox', + }, + { + name: 'treeCheckable', + label: '选项可勾选', + component: 'Checkbox', + }, + { + name: 'treeCheckStrictly', + label: '节点选择完全受控', + component: 'Checkbox', + }, + { + name: 'treeDefaultExpandAll', + label: '默认展开所有', + component: 'Checkbox', + }, + { + name: 'treeLine', + label: '是否展示线条样式', + component: 'Checkbox', + }, + { + name: 'maxTagCount', + label: '最多显示多少个 tag', + component: 'InputNumber', + componentProps: { + placeholder: '最多显示多少个 tag', + }, + }, + { + name: 'size', + label: '尺寸', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '默认', + value: 'default', + }, + { + label: '小', + value: 'small', + }, + ], + }, + }, + ], + Cascader: [ + { + name: 'expandTrigger', + label: '次级展开方式(默认click)', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: 'click', + value: 'click', + }, + { + label: 'hover', + value: 'hover', + }, + ], + }, + }, + ], + Button: [ + { + name: 'type', + label: '类型', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: 'default', + value: 'default', + }, + { + label: 'primary', + value: 'primary', + }, + { + label: 'danger', + value: 'danger', + }, + { + label: 'dashed', + value: 'dashed', + }, + ], + }, + }, + { + name: 'handle', + label: '操作', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '提交', + value: 'submit', + }, + { + label: '重置', + value: 'reset', + }, + ], + }, + }, + ], + Upload: [ + { + name: 'action', + label: '上传地址', + component: 'Input', + }, + { + name: 'name', + label: '附件参数名(name)', + component: 'Input', + }, + ], + // ColorPicker: [ + // { + // name: 'defaultValue', + // label: '默认值', + // component: 'AColorPicker', + // }, + // ], + slot: [ + { + name: 'slotName', + label: '插槽名称', + component: 'Input', + }, + ], + Transfer: [ + // { + // name: 'operations', + // label: '操作文案集合,顺序从上至下', + // component: 'Input', + // componentProps: { + // type: 'text', + // // defaultValue: ['>', '<'], + // }, + // }, + // { + // name: 'titles', + // label: '标题集合,顺序从左至右', + // component: 'Input', + // componentProps: { + // type: 'text', + // // defaultValue: ['', ''], + // }, + // }, + { + name: 'oneWay', + label: '展示为单向样式', + component: 'Checkbox', + }, + { + name: 'pagination', + label: '使用分页样式', + component: 'Checkbox', + }, + { + name: 'showSelectAll', + label: '展示全选勾选框', + component: 'Checkbox', + }, + ], +}; + +function deleteProps(list: Omit[], key: string) { + list.forEach((element, index) => { + if (element.name === key) { + list.splice(index, 1); + } + }); +} + +componentAttrs['StrengthMeter'] = componentAttrs['Input']; +componentAttrs['StrengthMeter'].push({ + name: 'visibilityToggle', + label: '是否显示切换按钮', + component: 'Checkbox', +}); + +deleteProps(componentAttrs['StrengthMeter'], 'type'); +deleteProps(componentAttrs['StrengthMeter'], 'prefix'); +deleteProps(componentAttrs['StrengthMeter'], 'defaultValue'); +deleteProps(componentAttrs['StrengthMeter'], 'suffix'); +//组件属性 +// name 控件的属性 +export const baseComponentAttrs: IBaseComponentProps = componentAttrs; + +//在所有的选项中查找需要配置项 +const findCompoentProps = (props, name) => { + const idx = props.findIndex((value: BaseFormAttrs) => { + return value.name === name; + }); + if (props[idx] && props[idx].componentProps) { + return props[idx].componentProps; + } +}; + +// 根据其它选项的值更新自身控件配置值 +export const componentPropsFuncs = { + RadioGroup: (compProp, options: BaseFormAttrs[]) => { + const props = findCompoentProps(options, 'size'); + if (props) { + if (compProp['optionType'] && compProp['optionType'] != 'button') { + props['disabled'] = true; + compProp['size'] = null; + } else { + props['disabled'] = false; + } + } + }, +}; diff --git a/src/views/form-design/components/VFormDesign/config/formItemPropsConfig.ts b/src/views/form-design/components/VFormDesign/config/formItemPropsConfig.ts new file mode 100644 index 00000000000..80751b2713a --- /dev/null +++ b/src/views/form-design/components/VFormDesign/config/formItemPropsConfig.ts @@ -0,0 +1,353 @@ +import { IAnyObject } from '../../../typings/base-type'; +import { baseComponents, customComponents } from '../../../core/formItemConfig'; +import { Input, Select, RadioGroup, Slider } from 'ant-design-vue'; +import { Component } from 'vue'; + +export const globalConfigState: { span: number } = { + span: 24, +}; +export interface IBaseFormAttrs { + name: string; // 字段名 + label: string; // 字段标签 + component?: string | Component; // 属性控件 + componentProps?: IAnyObject; // 传递给控件的属性 + exclude?: string[]; // 需要排除的控件 + includes?: string[]; // 符合条件的组件 + on?: IAnyObject; + children?: IBaseFormAttrs[]; + category?: 'control' | 'input'; +} + +export interface IBaseFormItemControlAttrs extends IBaseFormAttrs { + target?: 'props' | 'options'; // 绑定到对象下的某个目标key中 +} + +export const baseItemColumnProps: IBaseFormAttrs[] = [ + { + name: 'span', + label: '栅格数', + component: 'Slider', + on: { + change(value: number) { + globalConfigState.span = value; + }, + }, + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + + { + name: 'offset', + label: '栅格左侧的间隔格数', + component: 'Slider', + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + { + name: 'order', + label: '栅格顺序,flex 布局模式下有效', + component: 'Slider', + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + { + name: 'pull', + label: '栅格向左移动格数', + component: 'Slider', + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + { + name: 'push', + label: '栅格向右移动格数', + component: 'Slider', + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + { + name: 'xs', + label: '<576px 响应式栅格', + component: 'Slider', + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + { + name: 'sm', + label: '≥576px 响应式栅格', + component: 'Slider', + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + { + name: 'md', + label: '≥768p 响应式栅格', + component: 'Slider', + + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + { + name: 'lg', + label: '≥992px 响应式栅格', + component: 'Slider', + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + { + name: 'xl', + label: '≥1200px 响应式栅格', + component: 'Slider', + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + { + name: 'xxl', + label: '≥1600px 响应式栅格', + component: 'Slider', + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, + { + name: '≥2000px', + label: '≥1600px 响应式栅格', + component: 'Slider', + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + }, +]; + +// 控件属性面板的配置项 +export const advanceFormItemColProps: IBaseFormAttrs[] = [ + { + name: 'labelCol', + label: '标签col', + component: Slider, + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + exclude: ['Grid'], + }, + { + name: 'wrapperCol', + label: '控件-span', + component: Slider, + componentProps: { + max: 24, + min: 0, + marks: { 12: '' }, + }, + exclude: ['Grid'], + }, +]; +// 控件属性面板的配置项 +export const baseFormItemProps: IBaseFormAttrs[] = [ + { + // 动态的切换控件的类型 + name: 'component', + label: '控件-FormItem', + component: Select, + componentProps: { + options: baseComponents + .concat(customComponents) + .map((item) => ({ value: item.component, label: item.label })), + }, + }, + { + name: 'label', + label: '标签', + component: Input, + componentProps: { + type: 'Input', + placeholder: '请输入标签', + }, + exclude: ['Grid'], + }, + { + name: 'field', + label: '字段标识', + component: Input, + componentProps: { + type: 'InputTextArea', + placeholder: '请输入字段标识', + }, + exclude: ['Grid'], + }, + { + name: 'helpMessage', + label: 'helpMessage', + component: Input, + componentProps: { + placeholder: '请输入提示信息', + }, + exclude: ['Grid'], + }, +]; + +// 控件属性面板的配置项 +export const advanceFormItemProps: IBaseFormAttrs[] = [ + { + name: 'labelAlign', + label: '标签对齐', + component: RadioGroup, + componentProps: { + options: [ + { + label: '靠左', + value: 'left', + }, + { + label: '靠右', + value: 'right', + }, + ], + }, + exclude: ['Grid'], + }, + + { + name: 'help', + label: 'help', + component: Input, + componentProps: { + placeholder: '请输入提示信息', + }, + exclude: ['Grid'], + }, + { + name: 'extra', + label: '额外消息', + component: Input, + componentProps: { + type: 'InputTextArea', + placeholder: '请输入额外消息', + }, + exclude: ['Grid'], + }, + { + name: 'validateTrigger', + label: 'validateTrigger', + component: Input, + componentProps: { + type: 'InputTextArea', + placeholder: '请输入validateTrigger', + }, + exclude: ['Grid'], + }, + { + name: 'validateStatus', + label: '校验状态', + component: RadioGroup, + componentProps: { + options: [ + { + label: '默认', + value: '', + }, + { + label: '成功', + value: 'success', + }, + { + label: '警告', + value: 'warning', + }, + { + label: '错误', + value: 'error', + }, + { + label: '校验中', + value: 'validating', + }, + ], + }, + exclude: ['Grid'], + }, +]; + +export const baseFormItemControlAttrs: IBaseFormItemControlAttrs[] = [ + { + name: 'required', + label: '必填项', + component: 'Checkbox', + exclude: ['alert'], + }, + { + name: 'hidden', + label: '隐藏', + component: 'Checkbox', + exclude: ['alert'], + }, + { + name: 'hiddenLabel', + component: 'Checkbox', + exclude: ['Grid'], + label: '隐藏标签', + }, + { + name: 'colon', + label: 'label后面显示冒号', + component: 'Checkbox', + componentProps: {}, + exclude: ['Grid'], + }, + { + name: 'hasFeedback', + label: '输入反馈', + component: 'Checkbox', + componentProps: {}, + includes: ['Input'], + }, + { + name: 'autoLink', + label: '自动关联', + component: 'Checkbox', + componentProps: {}, + includes: ['Input'], + }, + { + name: 'validateFirst', + label: '检验证错误停止', + component: 'Checkbox', + componentProps: {}, + includes: ['Input'], + }, +]; diff --git a/src/views/form-design/components/VFormDesign/index.vue b/src/views/form-design/components/VFormDesign/index.vue new file mode 100644 index 00000000000..5ff6893cba3 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/index.vue @@ -0,0 +1,351 @@ + + + + + diff --git a/src/views/form-design/components/VFormDesign/modules/CollapseItem.vue b/src/views/form-design/components/VFormDesign/modules/CollapseItem.vue new file mode 100644 index 00000000000..ce36311c939 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/modules/CollapseItem.vue @@ -0,0 +1,112 @@ + + + + diff --git a/src/views/form-design/components/VFormDesign/modules/FormComponentPanel.vue b/src/views/form-design/components/VFormDesign/modules/FormComponentPanel.vue new file mode 100644 index 00000000000..6b1b12bd6ff --- /dev/null +++ b/src/views/form-design/components/VFormDesign/modules/FormComponentPanel.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/src/views/form-design/components/VFormDesign/modules/PropsPanel.vue b/src/views/form-design/components/VFormDesign/modules/PropsPanel.vue new file mode 100644 index 00000000000..ce9c2153751 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/modules/PropsPanel.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/views/form-design/components/VFormDesign/modules/Toolbar.vue b/src/views/form-design/components/VFormDesign/modules/Toolbar.vue new file mode 100644 index 00000000000..f7308cb8cf1 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/modules/Toolbar.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/src/views/form-design/components/VFormDesign/styles/drag.less b/src/views/form-design/components/VFormDesign/styles/drag.less new file mode 100644 index 00000000000..0f806340bf5 --- /dev/null +++ b/src/views/form-design/components/VFormDesign/styles/drag.less @@ -0,0 +1,225 @@ +.draggable-box { + height: 100%; + overflow: auto; + + :deep(.list-main) { + position: relative; + padding: 5px; + overflow: hidden; + + .moving { + position: relative; + box-sizing: border-box; + // 拖放移动中; + min-height: 35px; + padding: 0 !important; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: 0; + right: 0; + width: 100%; + height: 5px; + background-color: @primary-color; + } + } + + .drag-move-box { + position: relative; + box-sizing: border-box; + min-height: 60px; + padding: 8px; + overflow: hidden; + transition: all 0.3s; + + &:hover { + background-color: @primary-hover-bg-color; + } + + // 选择时 start + &::before { + content: ''; + position: absolute; + top: 0; + right: -100%; + width: 100%; + height: 5px; + transition: all 0.3s; + background-color: @primary-color; + } + + &.active { + outline-offset: 0; + background-color: @primary-hover-bg-color; + + &::before { + right: 0; + } + } + + // 选择时 end + .form-item-box { + position: relative; + box-sizing: border-box; + word-wrap: break-word; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + .ant-form-item { + // 修改ant form-item的margin为padding + margin: 0; + padding-bottom: 6px; + } + } + + .show-key-box { + // 显示key + position: absolute; + right: 5px; + bottom: 2px; + // z-index: 999; + color: @primary-color; + font-size: 14px; + } + + .copy, + .delete { + position: absolute; + top: 0; + width: 30px; + height: 30px; + // z-index: 989; + transition: all 0.3s; + color: #fff; + line-height: 30px; + text-align: center; + + &.unactivated { + opacity: 0 !important; + pointer-events: none; + } + + &.active { + opacity: 1 !important; + } + } + + .copy { + right: 30px; + border-radius: 0 0 0 8px; + background-color: @primary-color; + } + + .delete { + right: 0; + background-color: @primary-color; + } + } + + .grid-box { + position: relative; + box-sizing: border-box; + width: 100%; + padding: 5px; + overflow: hidden; + transition: all 0.3s; + background-color: @layout-background-color; + + .form-item-box { + position: relative; + box-sizing: border-box; + + .ant-form-item { + // 修改ant form-item的margin为padding + margin: 0; + padding-bottom: 15px; + } + } + + .grid-row { + background-color: @layout-background-color; + + .grid-col { + .draggable-box { + min-width: 50px; + min-height: 80px; + border: 1px #ccc dashed; + // background: #fff; + + .list-main { + position: relative; + min-height: 83px; + border: 1px #ccc dashed; + } + } + } + } + + // 选择时 start + &::before { + content: ''; + position: absolute; + top: 0; + right: -100%; + width: 100%; + height: 5px; + transition: all 0.3s; + background: transparent; + } + + &.active { + outline-offset: 0; + background-color: @layout-hover-bg-color; + + &::before { + right: 0; + background-color: @layout-color; + } + } + // 选择时 end + > .copy-delete-box { + > .copy, + > .delete { + position: absolute; + top: 0; + width: 30px; + height: 30px; + // z-index: 989; + transition: all 0.3s; + color: #fff; + line-height: 30px; + text-align: center; + + &.unactivated { + opacity: 0 !important; + pointer-events: none; + } + + &.active { + opacity: 1 !important; + } + } + + > .copy { + right: 30px; + border-radius: 0 0 0 8px; + background-color: @layout-color; + } + + > .delete { + right: 0; + background-color: @layout-color; + } + } + } + } +} diff --git a/src/views/form-design/components/VFormDesign/styles/variable.less b/src/views/form-design/components/VFormDesign/styles/variable.less new file mode 100644 index 00000000000..8749dce122f --- /dev/null +++ b/src/views/form-design/components/VFormDesign/styles/variable.less @@ -0,0 +1,15 @@ +// 表单设计器样式 +@primary-color: #13c2c2; +@layout-color: #9867f7; + +@primary-background-color: fade(@primary-color, 6%); +@primary-hover-bg-color: fade(@primary-color, 20%); +@layout-background-color: fade(@layout-color, 12%); +@layout-hover-bg-color: fade(@layout-color, 24%); + +@title-text-color: #fff; +@border-color: #ccc; + +@left-right-width: 280px; +@header-height: 56px; +@operating-area-height: 45px; diff --git a/src/views/form-design/components/VFormItem/index.vue b/src/views/form-design/components/VFormItem/index.vue new file mode 100644 index 00000000000..829a3de9928 --- /dev/null +++ b/src/views/form-design/components/VFormItem/index.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/src/views/form-design/components/VFormItem/vFormItem.vue b/src/views/form-design/components/VFormItem/vFormItem.vue new file mode 100644 index 00000000000..c253bf90950 --- /dev/null +++ b/src/views/form-design/components/VFormItem/vFormItem.vue @@ -0,0 +1,68 @@ + + + + + + diff --git a/src/views/form-design/components/VFormPreview/index.vue b/src/views/form-design/components/VFormPreview/index.vue new file mode 100644 index 00000000000..78119eca7fd --- /dev/null +++ b/src/views/form-design/components/VFormPreview/index.vue @@ -0,0 +1,102 @@ + + + diff --git a/src/views/form-design/components/VFormPreview/useForm.vue b/src/views/form-design/components/VFormPreview/useForm.vue new file mode 100644 index 00000000000..84db5a75910 --- /dev/null +++ b/src/views/form-design/components/VFormPreview/useForm.vue @@ -0,0 +1,71 @@ + + + diff --git a/src/views/form-design/components/index.ts b/src/views/form-design/components/index.ts new file mode 100644 index 00000000000..969b9ae7e33 --- /dev/null +++ b/src/views/form-design/components/index.ts @@ -0,0 +1,69 @@ +import type { Component } from 'vue'; +import { ComponentType } from '@/components/Form/src/types'; +import { IconPicker } from '@/components/Icon'; +/** + * Component list, register here to setting it in the form + */ +import { + Input, + Button, + Select, + Radio, + Checkbox, + AutoComplete, + Cascader, + DatePicker, + InputNumber, + Switch, + TimePicker, + // ColorPicker, + TreeSelect, + Slider, + Rate, + Divider, + Calendar, + Transfer, +} from 'ant-design-vue'; + +//ant-desing本身的Form控件库 + +const componentMap = new Map(); +componentMap.set('Radio', Radio); +componentMap.set('Button', Button); +componentMap.set('Calendar', Calendar); +componentMap.set('Input', Input); +componentMap.set('InputGroup', Input.Group); +componentMap.set('InputPassword', Input.Password); +componentMap.set('InputSearch', Input.Search); +componentMap.set('InputTextArea', Input.TextArea); +componentMap.set('InputNumber', InputNumber); +componentMap.set('AutoComplete', AutoComplete); + +componentMap.set('Select', Select); +componentMap.set('TreeSelect', TreeSelect); +componentMap.set('Switch', Switch); +componentMap.set('RadioGroup', Radio.Group); +componentMap.set('Checkbox', Checkbox); +componentMap.set('CheckboxGroup', Checkbox.Group); +componentMap.set('Cascader', Cascader); +componentMap.set('Slider', Slider); +componentMap.set('Rate', Rate); +componentMap.set('Transfer', Transfer); +componentMap.set('DatePicker', DatePicker); +componentMap.set('MonthPicker', DatePicker.MonthPicker); +componentMap.set('RangePicker', DatePicker.RangePicker); +componentMap.set('WeekPicker', DatePicker.WeekPicker); +componentMap.set('TimePicker', TimePicker); + +componentMap.set('IconPicker', IconPicker); +componentMap.set('Divider', Divider); + +export function add(compName: ComponentType, component: Component) { + componentMap.set(compName, component); +} + +export function del(compName: ComponentType) { + componentMap.delete(compName); +} + +export { componentMap }; diff --git a/src/views/form-design/core/formItemConfig.ts b/src/views/form-design/core/formItemConfig.ts new file mode 100644 index 00000000000..34253a55df2 --- /dev/null +++ b/src/views/form-design/core/formItemConfig.ts @@ -0,0 +1,420 @@ +/** + * @description:表单配置 + */ +import { IVFormComponent } from '../typings/v-form-component'; +import { isArray } from 'lodash-es'; +import { componentMap as VbenCmp, add } from '@/components/Form/src/componentMap'; +import { ComponentType } from '@/components/Form/src/types'; + +import { componentMap as Cmp } from '../components'; +import { Component } from 'vue'; + +const componentMap = new Map(); + +//如果有其它控件,可以在这里初始化 + +//注册Ant控件库 +Cmp.forEach((value, key) => { + componentMap.set(key, value); + if (VbenCmp[key] == null) { + add(key as ComponentType, value); + } +}); +//注册vben控件库 +VbenCmp.forEach((value, key) => { + componentMap.set(key, value); +}); + +export { componentMap }; + +/** + * 设置自定义表单控件 + * @param {IVFormComponent | IVFormComponent[]} config + */ +export function setFormDesignComponents(config: IVFormComponent | IVFormComponent[]) { + if (isArray(config)) { + config.forEach((item) => { + const { componentInstance: component, ...rest } = item; + componentMap[item.component] = component; + customComponents.push(Object.assign({ props: {} }, rest)); + }); + } else { + const { componentInstance: component, ...rest } = config; + componentMap[config.component] = component; + customComponents.push(Object.assign({ props: {} }, rest)); + } +} + +//外部设置的自定义控件 +export const customComponents: IVFormComponent[] = []; + +// 左侧控件列表与初始化的控件属性 +// props.slotName,会在formitem级别生成一个slot,并绑定当前record值 +// 属性props,类型为对象,不能为undefined或是null。 +export const baseComponents: IVFormComponent[] = [ + { + component: 'InputCountDown', + label: '倒计时输入', + icon: 'line-md:iconify2', + colProps: { span: 24 }, + field: '', + componentProps: {}, + }, + { + component: 'IconPicker', + label: '图标选择器', + icon: 'line-md:iconify2', + colProps: { span: 24 }, + field: '', + componentProps: {}, + }, + { + component: 'StrengthMeter', + label: '密码强度', + icon: 'wpf:password1', + colProps: { span: 24 }, + field: '', + componentProps: {}, + }, + { + component: 'AutoComplete', + label: '自动完成', + icon: 'wpf:password1', + colProps: { span: 24 }, + field: '', + componentProps: { + placeholder: '请输入正则表达式', + options: [ + { + value: '/^(?:(?:\\+|00)86)?1[3-9]\\d{9}$/', + label: '手机号码', + }, + { + value: '/^((ht|f)tps?:\\/\\/)?[\\w-]+(\\.[\\w-]+)+:\\d{1,5}\\/?$/', + label: '网址带端口号', + }, + ], + }, + }, + { + component: 'Divider', + label: '分割线', + icon: 'radix-icons:divider-horizontal', + colProps: { span: 24 }, + field: '', + componentProps: { + orientation: 'center', + dashed: true, + }, + }, + { + component: 'Checkbox', + label: '复选框', + icon: 'ant-design:check-circle-outlined', + colProps: { span: 24 }, + field: '', + }, + { + component: 'CheckboxGroup', + label: '复选框-组', + icon: 'ant-design:check-circle-filled', + field: '', + colProps: { span: 24 }, + componentProps: { + options: [ + { + label: '选项1', + value: '1', + }, + { + label: '选项2', + value: '2', + }, + ], + }, + }, + { + component: 'Input', + label: '输入框', + icon: 'bi:input-cursor-text', + field: '', + colProps: { span: 24 }, + componentProps: { + type: 'text', + }, + }, + { + component: 'InputNumber', + label: '数字输入框', + icon: 'ant-design:field-number-outlined', + field: '', + colProps: { span: 24 }, + componentProps: { style: 'width:200px' }, + }, + { + component: 'InputTextArea', + label: '文本域', + icon: 'ant-design:file-text-filled', + field: '', + colProps: { span: 24 }, + componentProps: {}, + }, + { + component: 'Select', + label: '下拉选择', + icon: 'gg:select', + field: '', + colProps: { span: 24 }, + componentProps: { + options: [ + { + label: '选项1', + value: '1', + }, + { + label: '选项2', + value: '2', + }, + ], + }, + }, + + { + component: 'Radio', + label: '单选框', + icon: 'ant-design:check-circle-outlined', + field: '', + colProps: { span: 24 }, + componentProps: {}, + }, + { + component: 'RadioGroup', + label: '单选框-组', + icon: 'carbon:radio-button-checked', + field: '', + colProps: { span: 24 }, + componentProps: { + options: [ + { + label: '选项1', + value: '1', + }, + { + label: '选项2', + value: '2', + }, + ], + }, + }, + { + component: 'DatePicker', + label: '日期选择', + icon: 'healthicons:i-schedule-school-date-time-outline', + field: '', + colProps: { span: 24 }, + componentProps: {}, + }, + { + component: 'RangePicker', + label: '日期范围', + icon: 'healthicons:i-schedule-school-date-time-outline', + field: '', + colProps: { span: 24 }, + componentProps: { + placeholder: ['开始日期', '结束日期'], + }, + }, + { + component: 'MonthPicker', + label: '月份选择', + icon: 'healthicons:i-schedule-school-date-time-outline', + field: '', + colProps: { span: 24 }, + componentProps: { + placeholder: '请选择月份', + }, + }, + { + component: 'TimePicker', + label: '时间选择', + icon: 'healthicons:i-schedule-school-date-time', + field: '', + colProps: { span: 24 }, + componentProps: {}, + }, + { + component: 'Slider', + label: '滑动输入条', + icon: 'vaadin:slider', + field: '', + colProps: { span: 24 }, + componentProps: {}, + }, + { + component: 'Rate', + label: '评分', + icon: 'ic:outline-star-rate', + field: '', + colProps: { span: 24 }, + componentProps: {}, + }, + { + component: 'Switch', + label: '开关', + icon: 'entypo:switch', + field: '', + colProps: { span: 24 }, + componentProps: {}, + }, + { + component: 'TreeSelect', + label: '树形选择', + icon: 'clarity:tree-view-line', + field: '', + colProps: { span: 24 }, + componentProps: { + treeData: [ + { + label: '选项1', + value: '1', + children: [ + { + label: '选项三', + value: '1-1', + }, + ], + }, + { + label: '选项2', + value: '2', + }, + ], + }, + }, + { + component: 'Upload', + label: '上传', + icon: 'ant-design:upload-outlined', + field: '', + colProps: { span: 24 }, + componentProps: { + api: () => 1, + }, + }, + { + component: 'Cascader', + label: '级联选择', + icon: 'ant-design:check-outlined', + field: '', + colProps: { span: 24 }, + componentProps: { + options: [ + { + label: '选项1', + value: '1', + children: [ + { + label: '选项三', + value: '1-1', + }, + ], + }, + { + label: '选项2', + value: '2', + }, + ], + }, + }, + // { + // component: 'Button', + // label: '按钮', + // icon: 'dashicons:button', + // field: '', + // colProps: { span: 24 }, + // hiddenLabel: true, + // componentProps: {}, + // }, + // { + // component: 'ColorPicker', + // label: '颜色选择器', + // icon: 'carbon:color-palette', + // field: '', + // colProps: { span: 24 }, + // componentProps: { + // defaultValue: '', + // value: '', + // }, + // }, + + { + component: 'slot', + label: '插槽', + icon: 'vs:timeslot-question', + field: '', + colProps: { span: 24 }, + componentProps: { + slotName: 'slotName', + }, + }, +]; + +// https://next.antdv.com/components/transfer-cn +const transferControl = { + component: 'Transfer', + label: '穿梭框', + icon: 'bx:bx-transfer-alt', + field: '', + colProps: { span: 24 }, + componentProps: { + render: (item) => item.title, + dataSource: [ + { + key: 'key-1', + title: '标题1', + description: '描述', + disabled: false, + chosen: true, + }, + { + key: 'key-2', + title: 'title2', + description: 'description2', + disabled: true, + }, + { + key: 'key-3', + title: '标题3', + description: '描述3', + disabled: false, + chosen: true, + }, + ], + }, +}; + +baseComponents.push(transferControl); + +export const layoutComponents: IVFormComponent[] = [ + { + field: '', + component: 'Grid', + label: '栅格布局', + icon: 'icon-grid', + componentProps: {}, + columns: [ + { + span: 12, + children: [], + }, + { + span: 12, + children: [], + }, + ], + colProps: { span: 24 }, + options: { + gutter: 0, + }, + }, +]; diff --git a/src/views/form-design/core/iconConfig.ts b/src/views/form-design/core/iconConfig.ts new file mode 100644 index 00000000000..2588b4adeb8 --- /dev/null +++ b/src/views/form-design/core/iconConfig.ts @@ -0,0 +1,739 @@ +const iconConfig = { + filled: [ + 'account-book', + 'alert', + 'alipay-circle', + 'alipay-square', + 'aliwangwang', + 'amazon-circle', + 'android', + 'amazon-square', + 'api', + 'appstore', + 'audio', + 'apple', + 'backward', + 'bank', + 'behance-circle', + 'bell', + 'behance-square', + 'book', + 'box-plot', + 'bug', + 'bulb', + 'calculator', + 'build', + 'calendar', + 'camera', + 'car', + 'caret-down', + 'caret-left', + 'caret-right', + 'carry-out', + 'caret-up', + 'check-circle', + 'check-square', + 'chrome', + 'ci-circle', + 'clock-circle', + 'close-circle', + 'cloud', + 'close-square', + 'code-sandbox-square', + 'code-sandbox-circle', + 'code', + 'codepen-circle', + 'compass', + 'codepen-square', + 'contacts', + 'container', + 'control', + 'copy', + 'copyright-circle', + 'credit-card', + 'crown', + 'customer-service', + 'dashboard', + 'delete', + 'diff', + 'dingtalk-circle', + 'database', + 'dingtalk-square', + 'dislike', + 'dollar-circle', + 'down-circle', + 'down-square', + 'dribbble-circle', + 'dribbble-square', + 'dropbox-circle', + 'dropbox-square', + 'environment', + 'edit', + 'exclamation-circle', + 'euro-circle', + 'experiment', + 'eye-invisible', + 'eye', + 'facebook', + 'fast-backward', + 'fast-forward', + 'file-add', + 'file-excel', + 'file-exclamation', + 'file-image', + 'file-markdown', + 'file-pdf', + 'file-ppt', + 'file-text', + 'file-unknown', + 'file-word', + 'file-zip', + 'file', + 'filter', + 'fire', + 'flag', + 'folder-add', + 'folder', + 'folder-open', + 'forward', + 'frown', + 'fund', + 'funnel-plot', + 'gift', + 'github', + 'gitlab', + 'golden', + 'google-circle', + 'google-plus-circle', + 'google-plus-square', + 'google-square', + 'hdd', + 'heart', + 'highlight', + 'home', + 'hourglass', + 'html5', + 'idcard', + 'ie-circle', + 'ie-square', + 'info-circle', + 'instagram', + 'insurance', + 'interaction', + 'interation', + 'layout', + 'left-circle', + 'left-square', + 'like', + 'linkedin', + 'lock', + 'mail', + 'medicine-box', + 'medium-circle', + 'medium-square', + 'meh', + 'message', + 'minus-circle', + 'minus-square', + 'mobile', + 'money-collect', + 'pause-circle', + 'pay-circle', + 'notification', + 'phone', + 'picture', + 'pie-chart', + 'play-circle', + 'play-square', + 'plus-circle', + 'plus-square', + 'pound-circle', + 'printer', + 'profile', + 'project', + 'pushpin', + 'property-safety', + 'qq-circle', + 'qq-square', + 'question-circle', + 'read', + 'reconciliation', + 'red-envelope', + 'reddit-circle', + 'reddit-square', + 'rest', + 'right-circle', + 'rocket', + 'right-square', + 'safety-certificate', + 'save', + 'schedule', + 'security-scan', + 'setting', + 'shop', + 'shopping', + 'sketch-circle', + 'sketch-square', + 'skin', + 'slack-circle', + 'skype', + 'slack-square', + 'sliders', + 'smile', + 'snippets', + 'sound', + 'star', + 'step-backward', + 'step-forward', + 'stop', + 'switcher', + 'tablet', + 'tag', + 'tags', + 'taobao-circle', + 'taobao-square', + 'tool', + 'thunderbolt', + 'trademark-circle', + 'twitter-circle', + 'trophy', + 'twitter-square', + 'unlock', + 'up-circle', + 'up-square', + 'usb', + 'video-camera', + 'wallet', + 'warning', + 'wechat', + 'weibo-circle', + 'windows', + 'yahoo', + 'weibo-square', + 'yuque', + 'youtube', + 'zhihu-circle', + 'zhihu-square', + ], + outlined: [ + 'account-book', + 'alert', + 'alipay-circle', + 'aliwangwang', + 'android', + 'api', + 'appstore', + 'audio', + 'apple', + 'backward', + 'bank', + 'bell', + 'behance-square', + 'book', + 'box-plot', + 'bug', + 'bulb', + 'calculator', + 'build', + 'calendar', + 'camera', + 'car', + 'caret-down', + 'caret-left', + 'caret-right', + 'carry-out', + 'caret-up', + 'check-circle', + 'check-square', + 'chrome', + 'clock-circle', + 'close-circle', + 'cloud', + 'close-square', + 'code', + 'codepen-circle', + 'compass', + 'contacts', + 'container', + 'control', + 'copy', + 'credit-card', + 'crown', + 'customer-service', + 'dashboard', + 'delete', + 'diff', + 'database', + 'dislike', + 'down-circle', + 'down-square', + 'dribbble-square', + 'environment', + 'edit', + 'exclamation-circle', + 'experiment', + 'eye-invisible', + 'eye', + 'facebook', + 'fast-backward', + 'fast-forward', + 'file-add', + 'file-excel', + 'file-exclamation', + 'file-image', + 'file-markdown', + 'file-pdf', + 'file-ppt', + 'file-text', + 'file-unknown', + 'file-word', + 'file-zip', + 'file', + 'filter', + 'fire', + 'flag', + 'folder-add', + 'folder', + 'folder-open', + 'forward', + 'frown', + 'fund', + 'funnel-plot', + 'gift', + 'github', + 'gitlab', + 'hdd', + 'heart', + 'highlight', + 'home', + 'hourglass', + 'html5', + 'idcard', + 'info-circle', + 'instagram', + 'insurance', + 'interaction', + 'interation', + 'layout', + 'left-circle', + 'left-square', + 'like', + 'linkedin', + 'lock', + 'mail', + 'medicine-box', + 'meh', + 'message', + 'minus-circle', + 'minus-square', + 'mobile', + 'money-collect', + 'pause-circle', + 'pay-circle', + 'notification', + 'phone', + 'picture', + 'pie-chart', + 'play-circle', + 'play-square', + 'plus-circle', + 'plus-square', + 'printer', + 'profile', + 'project', + 'pushpin', + 'property-safety', + 'question-circle', + 'read', + 'reconciliation', + 'red-envelope', + 'rest', + 'right-circle', + 'rocket', + 'right-square', + 'safety-certificate', + 'save', + 'schedule', + 'security-scan', + 'setting', + 'shop', + 'shopping', + 'skin', + 'skype', + 'slack-square', + 'sliders', + 'smile', + 'snippets', + 'sound', + 'star', + 'step-backward', + 'step-forward', + 'stop', + 'switcher', + 'tablet', + 'tag', + 'tags', + 'taobao-circle', + 'tool', + 'thunderbolt', + 'trophy', + 'unlock', + 'up-circle', + 'up-square', + 'usb', + 'video-camera', + 'wallet', + 'warning', + 'wechat', + 'weibo-circle', + 'windows', + 'yahoo', + 'weibo-square', + 'yuque', + 'youtube', + 'alibaba', + 'align-center', + 'align-left', + 'align-right', + 'alipay', + 'aliyun', + 'amazon', + 'ant-cloud', + 'apartment', + 'ant-design', + 'area-chart', + 'arrow-left', + 'arrow-down', + 'arrow-up', + 'arrows-alt', + 'arrow-right', + 'audit', + 'bar-chart', + 'barcode', + 'bars', + 'behance', + 'bg-colors', + 'block', + 'bold', + 'border-bottom', + 'border-left', + 'border-outer', + 'border-inner', + 'border-right', + 'border-horizontal', + 'border-top', + 'border-verticle', + 'border', + 'branches', + 'check', + 'ci', + 'close', + 'cloud-download', + 'cloud-server', + 'cloud-sync', + 'cloud-upload', + 'cluster', + 'codepen', + 'code-sandbox', + 'colum-height', + 'column-width', + 'column-height', + 'coffee', + 'copyright', + 'dash', + 'deployment-unit', + 'desktop', + 'dingding', + 'disconnect', + 'dollar', + 'double-left', + 'dot-chart', + 'double-right', + 'down', + 'drag', + 'download', + 'dribbble', + 'dropbox', + 'ellipsis', + 'enter', + 'euro', + 'exception', + 'exclamation', + 'export', + 'fall', + 'file-done', + 'file-jpg', + 'file-protect', + 'file-sync', + 'file-search', + 'font-colors', + 'font-size', + 'fork', + 'form', + 'fullscreen-exit', + 'fullscreen', + 'gateway', + 'global', + 'google-plus', + 'gold', + 'google', + 'heat-map', + 'history', + 'ie', + 'import', + 'inbox', + 'info', + 'italic', + 'key', + 'issues-close', + 'laptop', + 'left', + 'line-chart', + 'link', + 'line-height', + 'line', + 'loading-3-quarters', + 'loading', + 'login', + 'logout', + 'man', + 'medium', + 'medium-workmark', + 'menu-unfold', + 'menu-fold', + 'menu', + 'minus', + 'monitor', + 'more', + 'ordered-list', + 'number', + 'pause', + 'percentage', + 'paper-clip', + 'pic-center', + 'pic-left', + 'pic-right', + 'plus', + 'pound', + 'poweroff', + 'pull-request', + 'qq', + 'question', + 'radar-chart', + 'qrcode', + 'radius-bottomleft', + 'radius-bottomright', + 'radius-upleft', + 'radius-setting', + 'radius-upright', + 'reddit', + 'redo', + 'reload', + 'retweet', + 'right', + 'rise', + 'rollback', + 'safety', + 'robot', + 'scan', + 'search', + 'scissor', + 'select', + 'shake', + 'share-alt', + 'shopping-cart', + 'shrink', + 'sketch', + 'slack', + 'small-dash', + 'solution', + 'sort-descending', + 'sort-ascending', + 'stock', + 'swap-left', + 'swap-right', + 'strikethrough', + 'swap', + 'sync', + 'table', + 'team', + 'taobao', + 'to-top', + 'trademark', + 'transaction', + 'twitter', + 'underline', + 'undo', + 'unordered-list', + 'up', + 'upload', + 'user-add', + 'user-delete', + 'usergroup-add', + 'user', + 'usergroup-delete', + 'vertical-align-bottom', + 'vertical-align-middle', + 'vertical-align-top', + 'vertical-left', + 'vertical-right', + 'weibo', + 'wifi', + 'zhihu', + 'woman', + 'zoom-out', + 'zoom-in', + ], + twoTone: [ + 'account-book', + 'alert', + 'api', + 'appstore', + 'audio', + 'bank', + 'bell', + 'book', + 'box-plot', + 'bug', + 'bulb', + 'calculator', + 'build', + 'calendar', + 'camera', + 'car', + 'carry-out', + 'check-circle', + 'check-square', + 'clock-circle', + 'close-circle', + 'cloud', + 'close-square', + 'code', + 'compass', + 'contacts', + 'container', + 'control', + 'copy', + 'credit-card', + 'crown', + 'customer-service', + 'dashboard', + 'delete', + 'diff', + 'database', + 'dislike', + 'down-circle', + 'down-square', + 'environment', + 'edit', + 'exclamation-circle', + 'experiment', + 'eye-invisible', + 'eye', + 'file-add', + 'file-excel', + 'file-exclamation', + 'file-image', + 'file-markdown', + 'file-pdf', + 'file-ppt', + 'file-text', + 'file-unknown', + 'file-word', + 'file-zip', + 'file', + 'filter', + 'fire', + 'flag', + 'folder-add', + 'folder', + 'folder-open', + 'frown', + 'fund', + 'funnel-plot', + 'gift', + 'hdd', + 'heart', + 'highlight', + 'home', + 'hourglass', + 'html5', + 'idcard', + 'info-circle', + 'insurance', + 'interaction', + 'interation', + 'layout', + 'left-circle', + 'left-square', + 'like', + 'lock', + 'mail', + 'medicine-box', + 'meh', + 'message', + 'minus-circle', + 'minus-square', + 'mobile', + 'money-collect', + 'pause-circle', + 'notification', + 'phone', + 'picture', + 'pie-chart', + 'play-circle', + 'play-square', + 'plus-circle', + 'plus-square', + 'pound-circle', + 'printer', + 'profile', + 'project', + 'pushpin', + 'property-safety', + 'question-circle', + 'reconciliation', + 'red-envelope', + 'rest', + 'right-circle', + 'rocket', + 'right-square', + 'safety-certificate', + 'save', + 'schedule', + 'security-scan', + 'setting', + 'shop', + 'shopping', + 'skin', + 'sliders', + 'smile', + 'snippets', + 'sound', + 'star', + 'stop', + 'switcher', + 'tablet', + 'tag', + 'tags', + 'tool', + 'thunderbolt', + 'trademark-circle', + 'trophy', + 'unlock', + 'up-circle', + 'up-square', + 'usb', + 'video-camera', + 'wallet', + 'warning', + 'ci', + 'copyright', + 'dollar', + 'euro', + 'gold', + 'canlendar', + ], +}; + +export default iconConfig; diff --git a/src/views/form-design/examples/baseForm.vue b/src/views/form-design/examples/baseForm.vue new file mode 100644 index 00000000000..e5ff6aca6f0 --- /dev/null +++ b/src/views/form-design/examples/baseForm.vue @@ -0,0 +1,37 @@ + + diff --git a/src/views/form-design/hooks/useFormDesignState.ts b/src/views/form-design/hooks/useFormDesignState.ts new file mode 100644 index 00000000000..865016e6481 --- /dev/null +++ b/src/views/form-design/hooks/useFormDesignState.ts @@ -0,0 +1,18 @@ +import { inject, Ref } from 'vue'; +import { IFormDesignMethods } from '../typings/form-type'; +import { IFormConfig } from '../typings/v-form-component'; + +/** + * 获取formDesign状态 + */ +export function useFormDesignState() { + const formConfig = inject('formConfig') as Ref; + const formDesignMethods = inject('formDesignMethods') as IFormDesignMethods; + return { formConfig, formDesignMethods }; +} + +export function useFormModelState() { + const formModel = inject('formModel') as Ref<{}>; + const setFormModel = inject('setFormModelMethod') as (key: String, value: any) => void; + return { formModel, setFormModel }; +} diff --git a/src/views/form-design/hooks/useFormInstanceMethods.ts b/src/views/form-design/hooks/useFormInstanceMethods.ts new file mode 100644 index 00000000000..89db328b370 --- /dev/null +++ b/src/views/form-design/hooks/useFormInstanceMethods.ts @@ -0,0 +1,60 @@ +import { IAnyObject } from '../typings/base-type'; +import { Ref, SetupContext, getCurrentInstance, toRaw, type EmitsOptions } from 'vue'; +import { cloneDeep, forOwn, isFunction } from 'lodash-es'; +import { AForm, IVFormComponent } from '../typings/v-form-component'; +import { Form } from 'ant-design-vue'; + +export function useFormInstanceMethods( + props: IAnyObject, + formdata, + context: SetupContext, + _formInstance: Ref, +) { + /** + * 绑定props和on中的上下文为parent + */ + const bindContext = () => { + const instance = getCurrentInstance(); + const vm = instance?.parent; + if (!vm) return; + + (props.formConfig.schemas as IVFormComponent[]).forEach((item) => { + // 绑定 props 中的上下文 + forOwn(item.componentProps, (value: any, key) => { + if (isFunction(value)) { + item.componentProps![key] = value.bind(vm); + } + }); + // 绑定事件监听(v-on)的上下文 + forOwn(item.on, (value: any, key) => { + if (isFunction(value)) { + item.componentProps![key] = value.bind(vm); + } + }); + }); + }; + bindContext(); + + const { emit } = context; + + const useForm = Form.useForm; + + const { resetFields, validate, clearValidate, validateField } = useForm(formdata, []); + + const submit = async () => { + //const _result = await validate(); + + const data = cloneDeep(toRaw(formdata.value)); + emit?.('submit', data); + props.formConfig.submit?.(data); + return data; + }; + + return { + validate, + validateField, + resetFields, + clearValidate, + submit, + }; +} diff --git a/src/views/form-design/hooks/useVFormMethods.ts b/src/views/form-design/hooks/useVFormMethods.ts new file mode 100644 index 00000000000..191ae17ed7e --- /dev/null +++ b/src/views/form-design/hooks/useVFormMethods.ts @@ -0,0 +1,195 @@ +import { Ref, SetupContext, type EmitsOptions } from 'vue'; +import { IVFormComponent, IFormConfig, AForm } from '../typings/v-form-component'; +import { findFormItem, formItemsForEach } from '../utils'; +import { cloneDeep, isFunction } from 'lodash-es'; +import { IAnyObject } from '../typings/base-type'; + +interface IFormInstanceMethods extends AForm { + submit: () => Promise; +} + +export interface IProps { + formConfig: IFormConfig; + formModel: IAnyObject; +} + +type ISet = ( + field: string, + key: T, + value: IVFormComponent[T], +) => void; +// 获取当前field绑定的表单项 +type IGet = (field: string) => IVFormComponent | undefined; +// 获取field在formData中的值 +type IGetValue = (field: string) => any; +// 设置field在formData中的值并且触发校验 +type ISetValue = (field: string | IAnyObject, value?: any) => void; +// 隐藏field对应的表单项 +type IHidden = (field: string) => void; +// 显示field对应的表单项 +type IShow = (field: string) => void; +// 设置field对应的表单项绑定的props属性 +type ISetProps = (field: string, key: string, value: any) => void; +// 获取formData中的值 +type IGetData = () => Promise; +// 禁用表单,如果field为空,则禁用整个表单 +type IDisable = (field?: string | boolean) => void; +// 设置表单配置方法 +type ISetFormConfig = (key: string, value: any) => void; +interface ILinkOn { + [key: string]: Set; +} + +export interface IVFormMethods extends Partial { + set: ISet; + get: IGet; + getValue: IGetValue; + setValue: ISetValue; + hidden: IHidden; + show: IShow; + setProps: ISetProps; + linkOn: ILinkOn; + getData: IGetData; + disable: IDisable; +} +export function useVFormMethods( + props: IProps, + _context: SetupContext, + formInstance: Ref, + formInstanceMethods: Partial, +): IVFormMethods { + /** + * 根据field获取表单项 + * @param {string} field + * @return {IVFormComponent | undefined} + */ + const get: IGet = (field) => + findFormItem(props.formConfig.schemas, (item) => item.field === field); + + /** + * 根据表单field设置表单项字段值 + * @param {string} field + * @param {keyof IVFormComponent} key + * @param {never} value + */ + const set: ISet = (field, key, value) => { + const formItem = get(field); + if (formItem) formItem[key] = value; + }; + + /** + * 设置表单项的props + * @param {string} field 需要设置的表单项field + * @param {string} key 需要设置的key + * @param value 需要设置的值 + */ + const setProps: ISetProps = (field, key, value) => { + const formItem = get(field); + if (formItem?.componentProps) { + ['options', 'treeData'].includes(key) && setValue(field, undefined); + + formItem.componentProps[key] = value; + } + }; + /** + * 设置字段的值,设置后触发校验 + * @param {string} field 需要设置的字段 + * @param value 需要设置的值 + */ + const setValue: ISetValue = (field, value) => { + if (typeof field === 'string') { + // props.formData[field] = value + props.formModel[field] = value; + formInstance.value?.validateField(field, value, []); + } else { + const keys = Object.keys(field); + keys.forEach((key) => { + props.formModel[key] = field[key]; + formInstance.value?.validateField(key, field[key], []); + }); + } + }; + /** + * 设置表单配置方法 + * @param {string} key + * @param value + */ + const setFormConfig: ISetFormConfig = (key, value) => { + props.formConfig[key] = value; + }; + /** + * 根据表单项field获取字段值,如果field为空,则 + * @param {string} field 需要设置的字段 + */ + const getValue: IGetValue = (field) => { + const formData = cloneDeep(props.formModel); + return formData[field]; + }; + + /** + * 获取formData中的值 + * @return {Promise>} + */ + const getData: IGetData = async () => { + return cloneDeep(props.formModel); + }; + /** + * 隐藏指定表单项 + * @param {string} field 需要隐藏的表单项的field + */ + const hidden: IHidden = (field) => { + set(field, 'hidden', true); + }; + + /** + * 禁用表单 + * @param {string | undefined} field + */ + const disable: IDisable = (field) => { + typeof field === 'string' + ? setProps(field, 'disabled', true) + : setFormConfig('disabled', field !== false); + }; + + /** + * 显示表单项 + * @param {string} field 需要显示的表单项的field + */ + const show: IShow = (field) => { + set(field, 'hidden', false); + }; + + /** + * 监听表单字段联动时触发 + * @type {ILinkOn} + */ + const linkOn: ILinkOn = {}; + const initLink = (schemas: IVFormComponent[]) => { + // 首次遍历,查找需要关联字段的表单 + formItemsForEach(schemas, (formItem) => { + // 如果需要关联,则进行第二层遍历,查找表单中关联的字段,存到Set中 + formItemsForEach(schemas, (item) => { + if (!linkOn[item.field!]) linkOn[item.field!] = new Set(); + if (formItem.link?.includes(item.field!) && isFunction(formItem.update)) { + linkOn[item.field!].add(formItem); + } + }); + linkOn[formItem.field!].add(formItem); + }); + }; + initLink(props.formConfig.schemas); + + return { + linkOn, + setValue, + getValue, + hidden, + show, + set, + get, + setProps, + getData, + disable, + ...formInstanceMethods, + }; +} diff --git a/src/views/form-design/index.vue b/src/views/form-design/index.vue new file mode 100644 index 00000000000..90f4bec2969 --- /dev/null +++ b/src/views/form-design/index.vue @@ -0,0 +1,12 @@ + + + + + diff --git a/src/views/form-design/tests/import1.json b/src/views/form-design/tests/import1.json new file mode 100644 index 00000000000..1f3481d8c38 --- /dev/null +++ b/src/views/form-design/tests/import1.json @@ -0,0 +1,54 @@ +{ + "schemas": [ + { + "field": "filename", + "component": "Input", + "label": "component.excel.fileName", + "rules": [ + { + "required": true + } + ] + }, + { + "field": "bookType", + "component": "Select", + "label": "component.excel.fileType", + "defaultValue": "xlsx", + "rules": [ + { + "required": true + } + ], + "componentProps": { + "options": [ + { + "label": "xlsx", + "value": "xlsx", + "key": "xlsx" + }, + { + "label": "html", + "value": "html", + "key": "html" + }, + { + "label": "csv", + "value": "csv", + "key": "csv" + }, + { + "label": "txt", + "value": "txt", + "key": "txt" + } + ] + } + } + ], + "layout": "horizontal", + "labelLayout": "flex", + "labelWidth": 100, + "labelCol": {}, + "wrapperCol": {} +} diff --git a/src/views/form-design/typings/base-type.ts b/src/views/form-design/typings/base-type.ts new file mode 100644 index 00000000000..94f5d8c88bb --- /dev/null +++ b/src/views/form-design/typings/base-type.ts @@ -0,0 +1,10 @@ +export interface IAnyObject { + [key: string]: T; +} + +export interface IInputEvent { + target: { + value: any; + checked: boolean; + }; +} diff --git a/src/views/form-design/typings/form-type.ts b/src/views/form-design/typings/form-type.ts new file mode 100644 index 00000000000..5f45394672b --- /dev/null +++ b/src/views/form-design/typings/form-type.ts @@ -0,0 +1,52 @@ +import { Ref } from 'vue'; +import { IAnyObject } from './base-type'; +import { IFormConfig, IVFormComponent } from './v-form-component'; + +export interface IToolbarMethods { + showModal: (jsonData: IAnyObject) => void; +} + +type ChangeTabKey = 1 | 2; +export interface IPropsPanel { + changeTab: (key: ChangeTabKey) => void; +} +export interface IState { + // 语言 + locale: any; + // 公用组件 + baseComponents: IVFormComponent[]; + // 自定义组件 + customComponents: IVFormComponent[]; + // 布局组件 + layoutComponents: IVFormComponent[]; + // 属性面板实例 + propsPanel: Ref; + // json模态框实例 + jsonModal: Ref; + // 导入json数据模态框 + importJsonModal: Ref; + // 代码预览模态框 + codeModal: Ref; + // 预览模态框 + eFormPreview: Ref; + + eFormPreview2: Ref; +} + +export interface IFormDesignMethods { + // 设置当前选中的控件 + handleSetSelectItem(item: IVFormComponent): void; + // 添加控件到formConfig.formItems中 + handleListPush(item: IVFormComponent): void; + // 复制控件 + handleCopy(item?: IVFormComponent, isCopy?: boolean): void; + // 添加控件属性 + handleAddAttrs(schemas: IVFormComponent[], index: number): void; + setFormConfig(config: IFormConfig): void; + // 添加到表单中之前触发 + handleBeforeColAdd( + event: { newIndex: string }, + schemas: IVFormComponent[], + isCopy?: boolean, + ): void; +} diff --git a/src/views/form-design/typings/v-form-component.ts b/src/views/form-design/typings/v-form-component.ts new file mode 100644 index 00000000000..b5dce99c863 --- /dev/null +++ b/src/views/form-design/typings/v-form-component.ts @@ -0,0 +1,347 @@ +import { IAnyObject } from './base-type'; +// import { ComponentOptions } from 'vue/types/options'; +import { ComponentOptions } from 'vue'; +import { IVFormMethods } from '../hooks/useVFormMethods'; +import { ColEx } from '@/components/Form/src/types'; + +import { SelectValue } from 'ant-design-vue/lib/select'; +import { validateOptions } from 'ant-design-vue/lib/form/useForm'; +import { RuleError } from 'ant-design-vue/lib/form/interface'; +import { FormItem } from '@/components/Form'; +import { FormLayout, FormProps } from 'ant-design-vue/lib/form/Form'; + +type labelLayout = 'flex' | 'Grid'; +export type PropsTabKey = 1 | 2 | 3; +type ColSpanType = number | string; + +declare type Value = [number, number] | number; +/** + * 组件属性 + */ +export interface IVFormComponent { + // extends Omit { + // 对应的字段 + field?: string; + // 组件类型 + component: string; + // 组件label + label?: string; + // 自定义组件控件实例 + componentInstance?: ComponentOptions; + // 组件icon + icon?: string; + // 组件校验规则 + rules?: Partial[]; + // 是否隐藏 + hidden?: boolean; + // 隐藏label + hiddenLabel?: boolean; + // 组件宽度 + width?: string; + // 是否必选 + required?: boolean; + // 必选提示 + message?: string; + // 提示信息 + helpMessage?: string; + // 传给给组件的属性,默认会吧所有的props都传递给控件 + componentProps?: IAnyObject; + // 监听组件事件对象,以v-on方式传递给控件 + on?: IAnyObject<(...any: []) => void>; + // 组件选项 + options?: IAnyObject; + // 唯一标识 + key?: string; + // Reference formModelItem + itemProps?: Partial; + + colProps?: Partial; + // 联动字段 + link?: string[]; + // 联动属性变化的回调 + update?: (value: any, formItem: IVFormComponent, fApi: IVFormMethods) => void; + // 控件栅格数 + // span?: number; + // 标签布局 + labelCol?: IAnyObject; + // 组件布局 + wrapperCol?: IAnyObject; + // 子控件 + columns?: Array<{ span: number; children: any[] }>; +} + +declare type namesType = string | string[]; + +/** + * 表单配置 + */ +export type PickAntFormConfig = Pick< + FormProps, + | 'layout' + | 'size' + | 'colon' + | 'labelAlign' + | 'disabled' + | 'labelCol' + | 'wrapperCol' + | 'hideRequiredMark' +>; + +// 使用extends 而不使用 &联结 是为了避免 type:check指令类型重载错误 +export interface IFormConfig extends PickAntFormConfig { + labelLayout?: labelLayout; + labelWidth?: number; + schemas: IVFormComponent[]; + currentItem?: IVFormComponent; + activeKey?: PropsTabKey; +} + +export interface AForm { + /** + * Hide required mark of all form items + * @default false + * @type boolean + */ + hideRequiredMark: boolean; + + /** + * The layout of label. You can set span offset to something like {span: 3, offset: 12} or sm: {span: 3, offset: 12} same as with + * @type IACol + */ + labelCol: IACol; + + /** + * Define form layout + * @default 'horizontal' + * @type string + */ + layout: FormLayout; + + /** + * The layout for input controls, same as labelCol + * @type IACol + */ + wrapperCol: IACol; + + /** + * change default props colon value of Form.Item (only effective when prop layout is horizontal) + * @type boolean + * @default true + */ + colon: boolean; + + /** + * text align of label of all items + * @type 'left' | 'right' + * @default 'left' + */ + labelAlign: 'left' | 'right'; + + /** + * data of form component + * @type object + */ + model: IAnyObject; + + /** + * validation rules of form + * @type object + */ + rules: IAnyObject; + + /** + * Default validate message. And its format is similar with newMessages's returned value + * @type any + */ + validateMessages?: any; + + /** + * whether to trigger validation when the rules prop is changed + * @type Boolean + * @default true + */ + validateOnRuleChange: boolean; + + /** + * validate the whole form. Takes a callback as a param. After validation, + * the callback will be executed with two params: a boolean indicating if the validation has passed, + * and an object containing all fields that fail the validation. Returns a promise if callback is omitted + * @type Function + */ + validate: (names?: namesType, option?: validateOptions) => Promise; + + /** + * validate one or several form items + * @type Function + */ + validateField: ( + name: string, + value: any, + rules: Record[], + option?: validateOptions, + ) => Promise; + /** + * reset all the fields and remove validation result + */ + resetFields: () => void; + + /** + * clear validation message for certain fields. + * The parameter is prop name or an array of prop names of the form items whose validation messages will be removed. + * When omitted, all fields' validation messages will be cleared + * @type string[] | string + */ + clearValidate: (props: string[] | string) => void; +} + +interface IACol { + /** + * raster number of cells to occupy, 0 corresponds to display: none + * @default none (0) + * @type ColSpanType + */ + span: Value; + + /** + * raster order, used in flex layout mode + * @default 0 + * @type ColSpanType + */ + order: ColSpanType; + + /** + * the layout fill of flex + * @default none + * @type ColSpanType + */ + flex: ColSpanType; + + /** + * the number of cells to offset Col from the left + * @default 0 + * @type ColSpanType + */ + offset: ColSpanType; + + /** + * the number of cells that raster is moved to the right + * @default 0 + * @type ColSpanType + */ + push: ColSpanType; + + /** + * the number of cells that raster is moved to the left + * @default 0 + * @type ColSpanType + */ + pull: ColSpanType; + + /** + * <576px and also default setting, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + xs: { span: ColSpanType; offset: ColSpanType } | ColSpanType; + + /** + * ≥576px, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + sm: { span: ColSpanType; offset: ColSpanType } | ColSpanType; + + /** + * ≥768px, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + md: { span: ColSpanType; offset: ColSpanType } | ColSpanType; + + /** + * ≥992px, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + lg: { span: ColSpanType; offset: ColSpanType } | ColSpanType; + + /** + * ≥1200px, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + xl: { span: ColSpanType; offset: ColSpanType } | ColSpanType; + + /** + * ≥1600px, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + xxl: { span: ColSpanType; offset: ColSpanType } | ColSpanType; +} + +export interface IValidationRule { + trigger?: 'change' | 'blur' | ['change', 'blur']; + /** + * validation error message + * @type string | Function + */ + message?: string | number; + + /** + * built-in validation type, available options: https://github.com/yiminghe/async-validator#type + * @default 'string' + * @type string + */ + type?: string; + + /** + * indicates whether field is required + * @default false + * @type boolean + */ + required?: boolean; + + /** + * treat required fields that only contain whitespace as errors + * @default false + * @type boolean + */ + whitespace?: boolean; + + /** + * validate the exact length of a field + * @type number + */ + len?: number; + + /** + * validate the min length of a field + * @type number + */ + min?: number; + + /** + * validate the max length of a field + * @type number + */ + max?: number; + + /** + * validate the value from a list of possible values + * @type string | string[] + */ + enum?: string | string[]; + + /** + * validate from a regular expression + * @type boolean + */ + pattern?: SelectValue; + + /** + * transform a value before validation + * @type Function + */ + transform?: (value: any) => any; + + /** + * custom validate function (Note: callback must be called) + * @type Function + */ + validator?: (rule: any, value: any, callback: () => void) => any; +} diff --git a/src/views/form-design/utils/index.ts b/src/views/form-design/utils/index.ts new file mode 100644 index 00000000000..94fbd819269 --- /dev/null +++ b/src/views/form-design/utils/index.ts @@ -0,0 +1,200 @@ +// import { VueConstructor } from 'vue'; +import { IVFormComponent, IFormConfig, IValidationRule } from '../typings/v-form-component'; +import { cloneDeep, isArray, isFunction, isNumber, uniqueId } from 'lodash-es'; +// import { del } from '@vue/composition-api'; +// import { withInstall } from '@/utils'; + +/** + * 组件install方法 + * @param comp 需要挂载install方法的组件 + */ +// export function withInstall(comp: T) { +// return Object.assign(comp, { +// install(Vue: VueConstructor) { +// Vue.component(comp.name, comp); +// }, +// }); +// } + +/** + * 生成key + * @param [formItem] 需要生成 key 的控件,可选,如果不传,默认返回一个唯一 key + * @returns {string|boolean} 返回一个唯一 id 或者 false + */ +export function generateKey(formItem?: IVFormComponent): string | boolean { + if (formItem && formItem.component) { + const key = uniqueId(`${toLine(formItem.component)}_`); + formItem.key = key; + formItem.field = key; + + return true; + } + return uniqueId('key_'); +} + +/** + * 移除数组中指定元素,value可以是一个数字下标,也可以是一个函数,删除函数第一个返回true的元素 + * @param array {Array} 需要移除元素的数组 + * @param value {number | ((item: T, index: number, array: Array) => boolean} + * @returns {T} 返回删除的数组项 + */ +export function remove( + array: Array, + value: number | ((item: T, index: number, array: Array) => boolean), +): T | undefined { + let removeVal: Array = []; + if (!isArray(array)) return undefined; + if (isNumber(value)) { + removeVal = array.splice(value, 1); + } else { + const index = array.findIndex(value); + if (index !== -1) { + removeVal = array.splice(index, 1); + } + } + return removeVal.shift(); +} + +/** + * 判断数据类型 + * @param value + */ +export function getType(value: any): string { + return Object.prototype.toString.call(value).slice(8, -1); +} + +/** + * 生成唯一guid + * @returns {String} 唯一id标识符 + */ +export function randomUUID(): string { + function S4() { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + } + return `${S4() + S4()}-${S4()}-${S4()}-${S4()}-${S4() + S4() + S4()}`; +} + +/** + * 驼峰转下划线 + * @param str + */ +export function toLine(str: string) { + return str.replace(/([A-Z])/g, '_$1').toLowerCase(); +} + +/** + * 遍历表单项 + * @param array + * @param cb + */ +export function formItemsForEach(array: IVFormComponent[], cb: (item: IVFormComponent) => void) { + if (!isArray(array)) return; + const traverse = (schemas: IVFormComponent[]) => { + schemas.forEach((formItem: IVFormComponent) => { + if (['Grid'].includes(formItem.component)) { + // 栅格布局 + formItem.columns?.forEach((item) => traverse(item.children)); + } else { + cb(formItem); + } + }); + }; + traverse(array); +} + +/** + * 查找表单项 + */ +export const findFormItem: ( + schemas: IVFormComponent[], + cb: (formItem: IVFormComponent) => boolean, +) => IVFormComponent | undefined = (schemas, cb) => { + let res; + const traverse = (schemas: IVFormComponent[]): boolean => { + return schemas.some((formItem: IVFormComponent) => { + const { component: type } = formItem; + // 处理栅格 + if (['Grid'].includes(type)) { + return formItem.columns?.some((item) => traverse(item.children)); + } + if (cb(formItem)) res = formItem; + return cb(formItem); + }); + }; + traverse(schemas); + return res; +}; + +/** + * 打开json模态框时删除当前项属性 + * @param formConfig {IFormConfig} + * @returns {IFormConfig} + */ +export const removeAttrs = (formConfig: IFormConfig): IFormConfig => { + const copyFormConfig = cloneDeep(formConfig); + delete copyFormConfig.currentItem; + delete copyFormConfig.activeKey; + copyFormConfig.schemas && + formItemsForEach(copyFormConfig.schemas, (item) => { + delete item.icon; + delete item.key; + }); + return copyFormConfig; +}; + +/** + * 处理异步选项属性,如 select treeSelect 等选项属性如果传递为函数并且返回Promise对象,获取异步返回的选项属性 + * @param {(() => Promise) | any[]} options + * @return {Promise} + */ +export const handleAsyncOptions = async ( + options: (() => Promise) | any[], +): Promise => { + try { + if (isFunction(options)) return await options(); + return options; + } catch { + return []; + } +}; + +/** + * 格式化表单项校验规则配置 + * @param {IVFormComponent[]} schemas + */ +export const formatRules = (schemas: IVFormComponent[]) => { + formItemsForEach(schemas, (item) => { + if ('required' in item) { + !isArray(item.rules) && (item.rules = []); + item.rules.push({ required: true, message: item.message }); + delete item['required']; + delete item['message']; + } + }); +}; + +/** + * 将校验规则中的正则字符串转换为正则对象 + * @param {IValidationRule[]} rules + * @return {IValidationRule[]} + */ +export const strToReg = (rules: IValidationRule[]) => { + const newRules = cloneDeep(rules); + return newRules.map((item) => { + if (item.pattern) item.pattern = runCode(item.pattern); + return item; + }); +}; + +/** + * 执行一段字符串代码,并返回执行结果,如果执行出错,则返回该参数 + * @param code + * @return {any} + */ +export const runCode = (code: any): T => { + try { + return new Function(`return ${code}`)(); + } catch { + return code; + } +}; diff --git a/src/views/form-design/utils/message.ts b/src/views/form-design/utils/message.ts new file mode 100644 index 00000000000..1f3d71a322d --- /dev/null +++ b/src/views/form-design/utils/message.ts @@ -0,0 +1,19 @@ +import { useMessage } from '@/hooks/web/useMessage'; + +const { createMessage } = useMessage(); +const message = Object.assign({ + success: (msg: string) => { + createMessage.success(msg); + }, + error: (msg: string) => { + createMessage.error(msg); + }, + warning: (msg: string) => { + createMessage.warning(msg); + }, + info: (msg: string) => { + createMessage.info(msg); + }, +}); + +export default message; diff --git a/src/views/hooks/request/base.tsx b/src/views/hooks/request/base.tsx new file mode 100644 index 00000000000..d685db30363 --- /dev/null +++ b/src/views/hooks/request/base.tsx @@ -0,0 +1,328 @@ +import { defineComponent, onMounted, ref, unref } from 'vue'; +import { Card, Spin, Typography, message, Input, Button, Space } from 'ant-design-vue'; +import { imitateApi } from './mock-api'; +import { useRequest } from '@vben/hooks'; +import { PageWrapper } from '@/components/Page'; + +const Demo1 = defineComponent({ + setup() { + const { data, error, loading } = useRequest(imitateApi); + + return () => ( + + + + useRequest + 的第一个参数是一个异步函数,在组件初次加载时,会自动触发该函数执行。同时自动管理该异步函数的 + loading + data + error + 等状态。 + + + {`const { data, error, loading } = useRequest(imitateApi);`} + + + + {/* 基础案例 */} + +
{error.value ? 'failed to load' : `Username: ${data.value}`}
+
+
+ ); + }, +}); + +const Demo2 = defineComponent({ + setup() { + const search = ref(''); + const setSearch = (value: string) => { + search.value = value; + }; + + const { loading, run } = useRequest(imitateApi, { + manual: true, + onSuccess: (result, params) => { + if (result) { + setSearch(''); + message.success(`The username was changed to "${params[0]}" !`); + } + }, + }); + + return () => ( + + + + 如果设置了 + options.manual = true + ,则 useRequest 不会默认执行,需要通过 + run 来触发执行。 + + + {`const { loading, run } = useRequest(imitateApi, { manual: true });`} + + + + {/* 手动触发 */} + + + + + + ); + }, +}); + +const Demo3 = defineComponent({ + setup() { + const search = ref(''); + const setSearch = (value: string) => { + search.value = value; + }; + + const { loading, run } = useRequest(imitateApi, { + manual: true, + onBefore: (params) => { + message.info(`Start Request: ${params[0]}`); + }, + onSuccess: (result, params) => { + if (result) { + setSearch(''); + message.success(`The username was changed to "${params[0]}" !`); + } + }, + onError: (error) => { + message.error(error.message); + }, + onFinally: () => { + message.info(`Request finish`); + }, + }); + + return () => ( + + + + useRequest + 提供了以下几个生命周期配置项,供你在异步函数的不同阶段做一些处理。 + + + + onBefore + 请求之前触发 + + + + onSuccess + 请求成功触发 + + + + onError + 请求失败触发 + + + + onFinally + 请求完成触发 + + + + {/* 生命周期 */} + + + + + + + ); + }, +}); + +const Demo4 = defineComponent({ + setup() { + const { data, loading, run, refresh } = useRequest(imitateApi, { + manual: true, + }); + + onMounted(() => run('lutz')); + + const changeData = () => { + data.value = `${Date.now()}`; + }; + + return () => ( + + + + useRequest + 提供了 + refresh 和 + refreshAsync + 方法,使我们可以使用上一次的参数,重新发起请求。 + + + + + +
Username: {data.value}
+ + +
+
+
+ ); + }, +}); + +const Demo5 = defineComponent({ + setup() { + const search = ref(''); + const setSearch = (value: string) => { + search.value = value; + }; + + const { loading, run, cancel } = useRequest(imitateApi, { + manual: true, + onSuccess: (result, params) => { + if (result) { + setSearch(''); + message.success(`The username was changed to "${params[0]}" !`); + } + }, + }); + + return () => ( + + + + useRequest 提供了 + cancel 函数,用于忽略当前 promise + 返回的数据和错误 + + + + {/* 取消响应 */} + + + + + + + ); + }, +}); + +const Demo6 = defineComponent({ + setup() { + const search = ref(''); + + const { + data: username, + loading, + run, + params, + } = useRequest(imitateApi, { + defaultParams: ['lutz'], + }); + + const onChange = () => { + run(search.value); + }; + + return () => ( + + + + useRequest 返回的 + params 会记录当次调用 + service 的参数数组。比如你触发了 + run(1, 2, 3),则 + params 等于 + [1, 2, 3] + + + 如果我们设置了 + options.manual = false ,则首次调用 + service + 的参数可以通过 options.defaultParams + 来设置。 + + + + {/* 管理参数 */} + + + + +
+
UserId: {unref(params)?.[0]}
+
Username: {unref(username)}
+
+
+ ); + }, +}); + +export default defineComponent({ + setup() { + return () => ( + ( + + + ahooks{' '} + + useRequest 的 vue 版本,是一个强大的异步数据管理的 Hooks。 + +
    + {[ + '自动请求/手动请求', + '轮询', + '防抖', + '节流', + '屏幕聚焦重新请求', + '错误重试', + 'loading delay', + 'SWR(stale-while-revalidate)', + '缓存', + ].map((item) => ( +
  • + {item} +
  • + ))} +
+
+
+ ), + }} + > + + + + + + +
+ ); + }, +}); diff --git a/src/views/hooks/request/cache.tsx b/src/views/hooks/request/cache.tsx new file mode 100644 index 00000000000..cc9c4a17c1e --- /dev/null +++ b/src/views/hooks/request/cache.tsx @@ -0,0 +1,318 @@ +import { defineComponent, ref, unref } from 'vue'; +import { Card, Typography, Button, Input, Space, message } from 'ant-design-vue'; +import { getArticle } from './mock-api'; +import { useRequest, clearCache } from '@vben/hooks'; +import { PageWrapper } from '@/components/Page'; + +const Article1 = defineComponent({ + props: { + cacheKey: { + type: String, + default: 'cacheKey-demo', + }, + }, + setup(props) { + const { loading, data } = useRequest(getArticle, { + cacheKey: props.cacheKey, + }); + + return () => ( + <> +

Background loading: {loading.value ? 'true' : 'false'}

+

Latest request time: {unref(data)?.time}

+

{unref(data)?.data}

+ + ); + }, +}); + +const Demo1 = defineComponent({ + setup() { + const state = ref(false); + const toggle = (bool?: boolean) => { + state.value = bool ?? !state.value; + }; + + return () => ( + + + + 下面的示例,我们设置了 + cacheKey + ,在组件第二次加载时,会优先返回缓存的内容,然后在背后重新发起请求。你可以通过点击按钮来体验效果。 + + + + {/* SWR */} +
+ + {state.value && } +
+
+ ); + }, +}); + +const Article2 = defineComponent({ + setup() { + const { loading, data } = useRequest(getArticle, { + cacheKey: 'staleTime-demo', + staleTime: 5000, + }); + + return () => ( + <> +

Background loading: {loading.value ? 'true' : 'false'}

+

Latest request time: {unref(data)?.time}

+

{unref(data)?.data}

+ + ); + }, +}); + +const Demo2 = defineComponent({ + setup() { + const state = ref(false); + const toggle = (bool?: boolean) => { + state.value = bool ?? !state.value; + }; + + return () => ( + + + + 通过设置 + staleTime + ,我们可以指定数据新鲜时间,在这个时间内,不会重新发起请求。下面的示例设置了 5s + 的新鲜时间,你可以通过点击按钮来体验效果 + + + + {/* 数据保持新鲜 */} +
+ + {state.value && } +
+
+ ); + }, +}); + +const Article3 = defineComponent({ + setup() { + const { loading, data, refresh } = useRequest(getArticle, { + cacheKey: 'cacheKey-share', + }); + + return () => ( + <> +

Background loading: {loading.value ? 'true' : 'false'}

+ +

Latest request time: {unref(data)?.time}

+

{unref(data)?.data}

+ + ); + }, +}); + +const Demo3 = defineComponent({ + setup() { + return () => ( + + + + 同一个 cacheKey + 的内容,在全局是共享的,这会带来以下几个特性 + + + +
    +
  • + 请求 Promise 共享,相同的 cacheKey + 同时只会有一个在发起请求,后发起的会共用同一个请求 Promise +
  • +
  • + 数据同步,任何时候,当我们改变其中某个 cacheKey 的内容时,其它相同 + cacheKey + 的内容均会同步 +
  • +
+
+
+ + {/* 数据共享 */} +
+

Article 1

+ +

Article 2

+ +
+
+ ); + }, +}); + +const Article4 = defineComponent({ + setup() { + const { loading, data, params, run } = useRequest(getArticle, { + cacheKey: 'cacheKey-share4', + }); + + const keyword = ref(params.value?.[0] || ''); + + return () => ( + <> + + + + +

Background loading: {loading.value ? 'true' : 'false'}

+

Latest request time: {unref(data)?.time}

+

Latest request data: {unref(data)?.data}

+

keyword: {keyword.value}

+ + ); + }, +}); + +const Demo4 = defineComponent({ + setup() { + const state = ref(false); + const toggle = (bool?: boolean) => { + state.value = bool ?? !state.value; + }; + + return () => ( + + + + 缓存的数据包括 data 和 params,通过 params + 缓存机制,我们可以记忆上一次请求的条件,并在下次初始化 + + + + {/* 参数缓存 */} +
+ +
{state.value && }
+
+
+ ); + }, +}); + +const Demo5 = defineComponent({ + setup() { + const state = ref(false); + const toggle = (bool?: boolean) => { + state.value = bool ?? !state.value; + }; + + const clear = (cacheKey?: string | string[]) => { + clearCache(cacheKey); + const tips = Array.isArray(cacheKey) ? cacheKey.join('、') : cacheKey; + message.success(`Clear ${tips ?? 'All'} finished`); + }; + + return () => ( + + + + useRequest 提供了一个 clearCache 方法,可以清除指定 cacheKey 的缓存数据。 + + + + {/* 删除缓存 */} +
+ + + + + + + +

Article 1

+ {state.value && } +

Article 2

+ {state.value && } +

Article 3

+ {state.value && } +
+
+ ); + }, +}); + +const Article6 = defineComponent({ + setup() { + const cacheKey = 'setCache-demo6'; + const { loading, data } = useRequest(getArticle, { + cacheKey, + setCache: (data) => localStorage.setItem(cacheKey, JSON.stringify(data)), + getCache: () => JSON.parse(localStorage.getItem(cacheKey) || '{}'), + }); + + return () => ( + <> +

Background loading: {loading.value ? 'true' : 'false'}

+

Latest request time: {unref(data)?.time}

+

{unref(data)?.data}

+ + ); + }, +}); + +const Demo6 = defineComponent({ + setup() { + const state = ref(false); + const toggle = (bool?: boolean) => { + state.value = bool ?? !state.value; + }; + + return () => ( + + + + 通过配置 setCache 和 getCache,可以自定义数据缓存,比如可以将数据存储到 + localStorage、IndexDB 等。 + + + + {/* 自定义缓存 */} +
+ +
{state.value && }
+
+
+ ); + }, +}); + +export default defineComponent({ + setup() { + return () => ( + + + + + + + + + ); + }, +}); diff --git a/src/views/hooks/request/debounce.tsx b/src/views/hooks/request/debounce.tsx new file mode 100644 index 00000000000..71b3e8db002 --- /dev/null +++ b/src/views/hooks/request/debounce.tsx @@ -0,0 +1,62 @@ +import { defineComponent, ref } from 'vue'; +import { Card, Typography, Input, Spin, Space } from 'ant-design-vue'; +import { imitateApi } from './mock-api'; +import { useRequest } from '@vben/hooks'; +import { PageWrapper } from '@/components/Page'; + +const Demo1 = defineComponent({ + setup() { + const search = ref(''); + + const { data, loading } = useRequest(imitateApi, { + debounceWait: 1000, + refreshDeps: [search], + }); + + return () => ( + + + + 通过设置 options.debounceWait + ,进入防抖模式,此时如果频繁触发 + run + 或者 + runAsync + 则会以防抖策略进行请求。 + + + + + {`const { data, run } = useRequest(imitateApi, { debounceWait: 300, manual: true });`} + + + + + 如上示例代码,频繁触发 + run , 300ms 执行一次。 + + + 你可以在下面 input 框中快速输入文本,体验效果 + + + {/* 防抖 */} + + + +
Username: {data.value}
+
+
+
+ ); + }, +}); + +export default defineComponent({ + setup() { + return () => ( + + + + ); + }, +}); diff --git a/src/views/hooks/request/loading-delay.tsx b/src/views/hooks/request/loading-delay.tsx new file mode 100644 index 00000000000..36f0aed3c77 --- /dev/null +++ b/src/views/hooks/request/loading-delay.tsx @@ -0,0 +1,61 @@ +import { defineComponent, unref } from 'vue'; +import { Card, Typography, Button, Space } from 'ant-design-vue'; +import { useRequest } from '@vben/hooks'; +import { PageWrapper } from '@/components/Page'; +import { imitateApi } from './mock-api'; + +export default defineComponent({ + setup() { + const action = useRequest(imitateApi); + + const withLoadingDelayAction = useRequest(imitateApi, { + loadingDelay: 300, + }); + + const trigger = () => { + action.run('lutz'); + withLoadingDelayAction.run('lutz'); + }; + + return () => ( + + + + + 通过设置 + options.loadingDelay + 可以延迟 loading 变成 + true + 的时间,有效防止闪烁。 + + + + + {`const { loading, data } = useRequest(imitateApi, { loadingDelay: 300 });`} + + + + + 例如上面的场景,假如 imitateApi 在 300ms 内返回,则{' '} + loading 不会变成{' '} + true Loading... 的情况。 + + + + + + +
Username: {unref(action.loading) ? 'Loading...' : unref(action.data)}
+ +
+ Username:{' '} + {unref(withLoadingDelayAction.loading) + ? 'Loading...' + : unref(withLoadingDelayAction.data)} +
+
+
+
+ ); + }, +}); diff --git a/src/views/hooks/request/mock-api.ts b/src/views/hooks/request/mock-api.ts new file mode 100644 index 00000000000..1ef4672b1a4 --- /dev/null +++ b/src/views/hooks/request/mock-api.ts @@ -0,0 +1,27 @@ +import Mock from 'mockjs'; + +export async function imitateApi(username?: string, pass: boolean = true): Promise { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (pass) { + resolve(username ?? Mock.mock('@name')); + } else { + reject(new Error(`Failed to modify username: ${username}`)); + } + }, 1250); + }); +} + +export async function getArticle( + keyword?: string, +): Promise<{ data: string; time: number; keyword?: string }> { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + data: Mock.mock('@paragraph'), + time: new Date().getTime(), + keyword, + }); + }, 1000); + }); +} diff --git a/src/views/hooks/request/polling.tsx b/src/views/hooks/request/polling.tsx new file mode 100644 index 00000000000..0ba033ba26d --- /dev/null +++ b/src/views/hooks/request/polling.tsx @@ -0,0 +1,96 @@ +import { defineComponent } from 'vue'; +import { Card, Typography, Button, Space, message } from 'ant-design-vue'; +import { imitateApi } from './mock-api'; +import { useRequest } from '@vben/hooks'; +import { PageWrapper } from '@/components/Page'; + +const Demo1 = defineComponent({ + setup() { + const { data, loading, run, cancel } = useRequest(imitateApi, { + pollingInterval: 1000, + pollingWhenHidden: false, + // onSuccess() { + // console.log('不可见是否运行呢'); // 测试不可见时,是否还在执行 + // }, + }); + + return () => ( + + + + 通过设置 + options.pollingInterval + ,进入轮询模式,useRequest 会定时触发 service 执行。 + + + + {`const { data, run, cancel } = useRequest(imitateApi, { pollingInterval: 3000 });`} + + + + +
+
Username: {loading.value ? 'Loading' : data.value}
+ + + + +
+
+ ); + }, +}); + +const Demo2 = defineComponent({ + setup() { + const { data, loading, run, cancel } = useRequest(imitateApi, { + manual: true, + pollingInterval: 3000, + pollingErrorRetryCount: 3, + pollingWhenHidden: false, + onError: (error) => { + message.error(error.message); + }, + }); + + return () => ( + + + + 通过 + options.pollingErrorRetryCount + 轮询错误重试次数。 + + + + {`const { data, run, cancel } = useRequest(imitateApi, { pollingInterval: 3000, pollingErrorRetryCount: 3 });`} + + + + +
+
Username: {loading.value ? 'Loading' : data.value}
+ + + + +
+
+ ); + }, +}); + +export default defineComponent({ + setup() { + return () => ( + + + + + ); + }, +}); diff --git a/src/views/hooks/request/ready.tsx b/src/views/hooks/request/ready.tsx new file mode 100644 index 00000000000..8d8f283a405 --- /dev/null +++ b/src/views/hooks/request/ready.tsx @@ -0,0 +1,86 @@ +import { defineComponent, ref, unref } from 'vue'; +import { Card, Typography, Button, Space } from 'ant-design-vue'; +import { imitateApi } from './mock-api'; +import { useRequest } from '@vben/hooks'; +import { PageWrapper } from '@/components/Page'; + +const Demo1 = defineComponent({ + setup() { + const ready = ref(false); + const toggle = (bool?: boolean) => { + ready.value = bool ?? !ready.value; + }; + const { data, loading } = useRequest(imitateApi, { ready }); + + return () => ( + + + + 以下示例演示了自动模式下 + ready 的行为。每次 + ready 从 false 变为 true + 时,都会重新发起请求。 + + + +
+ +
Ready: {JSON.stringify(unref(ready))}
+ +
+
Username: {loading.value ? 'Loading' : unref(data)}
+
+
+ ); + }, +}); + +const Demo2 = defineComponent({ + setup() { + const ready = ref(false); + const toggle = (bool?: boolean) => { + ready.value = bool ?? !ready.value; + }; + const { data, loading, run } = useRequest(imitateApi, { manual: true, ready }); + + return () => ( + + + + 以下示例演示了手动模式下 + ready + 的行为。只有当 + ready + 等于 true 时,run 才会执行。 + + + +
+ +
Ready: {JSON.stringify(unref(ready))}
+ +
+
+ +
Username: {loading.value ? 'Loading' : unref(data)}
+ +
+
+
+
+ ); + }, +}); + +export default defineComponent({ + setup() { + return () => ( + + + + + ); + }, +}); diff --git a/src/views/hooks/request/refresh-on-window-focus.tsx b/src/views/hooks/request/refresh-on-window-focus.tsx new file mode 100644 index 00000000000..4d2b352d698 --- /dev/null +++ b/src/views/hooks/request/refresh-on-window-focus.tsx @@ -0,0 +1,50 @@ +import { defineComponent } from 'vue'; +import { Card, Typography, Spin } from 'ant-design-vue'; +import { imitateApi } from './mock-api'; +import { useRequest } from '@vben/hooks'; +import { PageWrapper } from '@/components/Page'; + +const Demo1 = defineComponent({ + setup() { + const { data, loading } = useRequest(imitateApi, { + refreshOnWindowFocus: true, + }); + + return () => ( + + + + 通过设置 options.refreshOnWindowFocus + ,在浏览器窗口 refocus 和 revisible 时, 会重新发起请求。 + + + + + {`const { data, run } = useRequest(imitateApi, { refreshOnWindowFocus: true });`} + + + + + 你可以点击浏览器外部,再点击当前页面来体验效果(或者隐藏当前页面,重新展示),如果和上一次请求间隔大于 + 5000ms, 则会重新请求一次。 + + + + {/* 屏幕聚焦重新请求 */} + +
Username: {data.value}
+
+
+ ); + }, +}); + +export default defineComponent({ + setup() { + return () => ( + + + + ); + }, +}); diff --git a/src/views/hooks/request/refresy-deps.tsx b/src/views/hooks/request/refresy-deps.tsx new file mode 100644 index 00000000000..3cf27af8792 --- /dev/null +++ b/src/views/hooks/request/refresy-deps.tsx @@ -0,0 +1,168 @@ +import { computed, defineComponent, reactive, ref, unref } from 'vue'; +import { Button, Card, Typography, Select } from 'ant-design-vue'; +import { imitateApi } from './mock-api'; +import { useRequest } from '@vben/hooks'; +import { PageWrapper } from '@/components/Page'; + +const options = [ + { label: 'Jack', value: 'Jack' }, + { label: 'Lucy', value: 'Lucy' }, + { label: 'Lutz', value: 'Lutz' }, +]; + +const Demo1 = defineComponent({ + setup() { + const select = ref('Lutz'); + const { data, loading } = useRequest(() => imitateApi(select.value), { refreshDeps: [select] }); + + return () => ( + + + + useRequest 提供了一个 + options.refreshDeps + 参数,当它的值变化后,会重新触发请求。 + + + +
+                {`const select = ref('Lutz');
+const { data, loading } = useRequest(() => imitateApi(select.value), {
+  refreshDeps: [select]
+});`}
+              
+
+
+
+ + + + +
+ ); + }, +}); + +export default defineComponent({ + setup() { + return () => ( + ( + + + 通过设置 + options.retryCount + ,指定错误重试次数,则 useRequest 在失败后会进行重试。 + + + +
+                    {`// useRequestOption
+retryCount?: number; // -1, 无限次重试
+retryInterval?: number; // 重试时间间隔,单位为毫秒。如果不设置,默认采用简易的指数退避算法`}
+                  
+
+
+
+ ), + }} + > + +
+ ); + }, +}); diff --git a/src/views/hooks/request/throttle.tsx b/src/views/hooks/request/throttle.tsx new file mode 100644 index 00000000000..6be53a6cfd5 --- /dev/null +++ b/src/views/hooks/request/throttle.tsx @@ -0,0 +1,61 @@ +import { defineComponent, ref } from 'vue'; +import { Card, Typography, Input, Spin, Space } from 'ant-design-vue'; +import { imitateApi } from './mock-api'; +import { useRequest } from '@vben/hooks'; +import { PageWrapper } from '@/components/Page'; + +const Demo1 = defineComponent({ + setup() { + const search = ref(''); + + const { data, loading } = useRequest(imitateApi, { + throttleWait: 1000, + refreshDeps: [search], + }); + + return () => ( + + + + 通过设置 + options.throttleWait + ,进入节流模式,此时如果频繁触发 + run 或者 + runAsync , 则会以节流策略进行请求。 + + + + + {`const { data, run } = useRequest(imitateApi, { throttleWait: 300, manual: true });`} + + + + + 如上示例代码,频繁触发 + run , 300ms 执行一次。 + + + 你可以在下面 input 框中快速输入文本,体验效果 + + + {/* 节流 */} + + + +
Username: {data.value}
+
+
+
+ ); + }, +}); + +export default defineComponent({ + setup() { + return () => ( + + + + ); + }, +}); diff --git a/src/views/sys/about/index.vue b/src/views/sys/about/index.vue new file mode 100644 index 00000000000..90b1567e4be --- /dev/null +++ b/src/views/sys/about/index.vue @@ -0,0 +1,98 @@ + + diff --git a/src/views/sys/error-log/DetailModal.vue b/src/views/sys/error-log/DetailModal.vue new file mode 100644 index 00000000000..9390c04d4dd --- /dev/null +++ b/src/views/sys/error-log/DetailModal.vue @@ -0,0 +1,27 @@ + + diff --git a/src/views/sys/error-log/data.tsx b/src/views/sys/error-log/data.tsx new file mode 100644 index 00000000000..fc60d19f4f8 --- /dev/null +++ b/src/views/sys/error-log/data.tsx @@ -0,0 +1,67 @@ +import { Tag } from 'ant-design-vue'; +import { BasicColumn } from '@/components/Table'; +import { ErrorTypeEnum } from '@/enums/exceptionEnum'; +import { useI18n } from '@/hooks/web/useI18n'; + +const { t } = useI18n(); + +export function getColumns(): BasicColumn[] { + return [ + { + dataIndex: 'type', + title: t('sys.errorLog.tableColumnType'), + width: 80, + customRender: ({ text }) => { + const color = + text === ErrorTypeEnum.VUE + ? 'green' + : text === ErrorTypeEnum.RESOURCE + ? 'cyan' + : text === ErrorTypeEnum.PROMISE + ? 'blue' + : ErrorTypeEnum.AJAX + ? 'red' + : 'purple'; + return {() => text}; + }, + }, + { + dataIndex: 'url', + title: 'URL', + width: 200, + }, + { + dataIndex: 'time', + title: t('sys.errorLog.tableColumnDate'), + width: 160, + }, + { + dataIndex: 'file', + title: t('sys.errorLog.tableColumnFile'), + width: 200, + }, + { + dataIndex: 'name', + title: 'Name', + width: 200, + }, + { + dataIndex: 'message', + title: t('sys.errorLog.tableColumnMsg'), + width: 300, + }, + { + dataIndex: 'stack', + title: t('sys.errorLog.tableColumnStackMsg'), + }, + ]; +} + +export function getDescSchema(): any { + return getColumns().map((column) => { + return { + field: column.dataIndex!, + label: column.title, + }; + }); +} diff --git a/src/views/sys/error-log/index.vue b/src/views/sys/error-log/index.vue new file mode 100644 index 00000000000..f49e12e5e71 --- /dev/null +++ b/src/views/sys/error-log/index.vue @@ -0,0 +1,97 @@ + + + diff --git a/src/views/sys/exception/Exception.vue b/src/views/sys/exception/Exception.vue new file mode 100644 index 00000000000..38e0f49d28e --- /dev/null +++ b/src/views/sys/exception/Exception.vue @@ -0,0 +1,154 @@ + + diff --git a/src/views/sys/exception/index.ts b/src/views/sys/exception/index.ts new file mode 100644 index 00000000000..5002c4acbf6 --- /dev/null +++ b/src/views/sys/exception/index.ts @@ -0,0 +1 @@ +export { default as Exception } from './Exception.vue'; diff --git a/src/views/sys/iframe/FrameBlank.vue b/src/views/sys/iframe/FrameBlank.vue new file mode 100644 index 00000000000..99428bb686d --- /dev/null +++ b/src/views/sys/iframe/FrameBlank.vue @@ -0,0 +1,6 @@ + + diff --git a/src/views/sys/iframe/index.vue b/src/views/sys/iframe/index.vue new file mode 100644 index 00000000000..7ada92e8d7e --- /dev/null +++ b/src/views/sys/iframe/index.vue @@ -0,0 +1,120 @@ + + + diff --git a/src/views/sys/lock/LockPage.vue b/src/views/sys/lock/LockPage.vue new file mode 100644 index 00000000000..ea23afecc49 --- /dev/null +++ b/src/views/sys/lock/LockPage.vue @@ -0,0 +1,238 @@ + + + diff --git a/src/views/sys/lock/index.vue b/src/views/sys/lock/index.vue new file mode 100644 index 00000000000..4af9f81f8ae --- /dev/null +++ b/src/views/sys/lock/index.vue @@ -0,0 +1,13 @@ + + diff --git a/src/views/sys/lock/useNow.ts b/src/views/sys/lock/useNow.ts new file mode 100644 index 00000000000..dafc9abd451 --- /dev/null +++ b/src/views/sys/lock/useNow.ts @@ -0,0 +1,60 @@ +import { dateUtil } from '@/utils/dateUtil'; +import { reactive, toRefs } from 'vue'; +import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'; + +export function useNow(immediate = true) { + let timer: IntervalHandle; + + const state = reactive({ + year: 0, + month: 0, + week: '', + day: 0, + hour: '', + minute: '', + second: 0, + meridiem: '', + }); + + const update = () => { + const now = dateUtil(); + + const h = now.format('HH'); + const m = now.format('mm'); + const s = now.get('s'); + + state.year = now.get('y'); + state.month = now.get('M') + 1; + state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]; + state.day = now.get('date'); + state.hour = h; + state.minute = m; + state.second = s; + + state.meridiem = now.format('A'); + }; + + function start() { + update(); + clearInterval(timer); + timer = setInterval(() => update(), 1000); + } + + function stop() { + clearInterval(timer); + } + + tryOnMounted(() => { + immediate && start(); + }); + + tryOnUnmounted(() => { + stop(); + }); + + return { + ...toRefs(state), + start, + stop, + }; +} diff --git a/src/views/sys/login/ForgetPasswordForm.vue b/src/views/sys/login/ForgetPasswordForm.vue new file mode 100644 index 00000000000..2e4548ab439 --- /dev/null +++ b/src/views/sys/login/ForgetPasswordForm.vue @@ -0,0 +1,64 @@ + + diff --git a/src/views/sys/login/Login.vue b/src/views/sys/login/Login.vue new file mode 100644 index 00000000000..06067ff7f67 --- /dev/null +++ b/src/views/sys/login/Login.vue @@ -0,0 +1,212 @@ + + + diff --git a/src/views/sys/login/LoginForm.vue b/src/views/sys/login/LoginForm.vue new file mode 100644 index 00000000000..eddf28431c8 --- /dev/null +++ b/src/views/sys/login/LoginForm.vue @@ -0,0 +1,159 @@ + + diff --git a/src/views/sys/login/LoginFormTitle.vue b/src/views/sys/login/LoginFormTitle.vue new file mode 100644 index 00000000000..5a055643bf2 --- /dev/null +++ b/src/views/sys/login/LoginFormTitle.vue @@ -0,0 +1,25 @@ + + diff --git a/src/views/sys/login/MobileForm.vue b/src/views/sys/login/MobileForm.vue new file mode 100644 index 00000000000..26169f81107 --- /dev/null +++ b/src/views/sys/login/MobileForm.vue @@ -0,0 +1,63 @@ + + diff --git a/src/views/sys/login/QrCodeForm.vue b/src/views/sys/login/QrCodeForm.vue new file mode 100644 index 00000000000..f27a73df20e --- /dev/null +++ b/src/views/sys/login/QrCodeForm.vue @@ -0,0 +1,31 @@ + + diff --git a/src/views/sys/login/RegisterForm.vue b/src/views/sys/login/RegisterForm.vue new file mode 100644 index 00000000000..c94065171c0 --- /dev/null +++ b/src/views/sys/login/RegisterForm.vue @@ -0,0 +1,104 @@ + + diff --git a/src/views/sys/login/SessionTimeoutLogin.vue b/src/views/sys/login/SessionTimeoutLogin.vue new file mode 100644 index 00000000000..692ba650a8b --- /dev/null +++ b/src/views/sys/login/SessionTimeoutLogin.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/views/sys/login/useLogin.ts b/src/views/sys/login/useLogin.ts new file mode 100644 index 00000000000..ccaa2fb6a32 --- /dev/null +++ b/src/views/sys/login/useLogin.ts @@ -0,0 +1,130 @@ +import type { FormInstance } from 'ant-design-vue/lib/form/Form'; +import type { + RuleObject, + NamePath, + Rule as ValidationRule, +} from 'ant-design-vue/lib/form/interface'; +import { ref, computed, unref, Ref } from 'vue'; +import { useI18n } from '@/hooks/web/useI18n'; + +export enum LoginStateEnum { + LOGIN, + REGISTER, + RESET_PASSWORD, + MOBILE, + QR_CODE, +} + +const currentState = ref(LoginStateEnum.LOGIN); + +// 这里也可以优化 +// import { createGlobalState } from '@vueuse/core' + +export function useLoginState() { + function setLoginState(state: LoginStateEnum) { + currentState.value = state; + } + + const getLoginState = computed(() => currentState.value); + + function handleBackLogin() { + setLoginState(LoginStateEnum.LOGIN); + } + + return { setLoginState, getLoginState, handleBackLogin }; +} + +export function useFormValid(formRef: Ref) { + const validate = computed(() => { + const form = unref(formRef); + return form?.validate ?? ((_nameList?: NamePath) => Promise.resolve()); + }); + + async function validForm() { + const form = unref(formRef); + if (!form) return; + const data = await form.validate(); + return data as T; + } + + return { validate, validForm }; +} + +export function useFormRules(formData?: Recordable) { + const { t } = useI18n(); + + const getAccountFormRule = computed(() => createRule(t('sys.login.accountPlaceholder'))); + const getPasswordFormRule = computed(() => createRule(t('sys.login.passwordPlaceholder'))); + const getSmsFormRule = computed(() => createRule(t('sys.login.smsPlaceholder'))); + const getMobileFormRule = computed(() => createRule(t('sys.login.mobilePlaceholder'))); + + const validatePolicy = async (_: RuleObject, value: boolean) => { + return !value ? Promise.reject(t('sys.login.policyPlaceholder')) : Promise.resolve(); + }; + + const validateConfirmPassword = (password: string) => { + return async (_: RuleObject, value: string) => { + if (!value) { + return Promise.reject(t('sys.login.passwordPlaceholder')); + } + if (value !== password) { + return Promise.reject(t('sys.login.diffPwd')); + } + return Promise.resolve(); + }; + }; + + const getFormRules = computed((): { [k: string]: ValidationRule | ValidationRule[] } => { + const accountFormRule = unref(getAccountFormRule); + const passwordFormRule = unref(getPasswordFormRule); + const smsFormRule = unref(getSmsFormRule); + const mobileFormRule = unref(getMobileFormRule); + + const mobileRule = { + sms: smsFormRule, + mobile: mobileFormRule, + }; + switch (unref(currentState)) { + // register form rules + case LoginStateEnum.REGISTER: + return { + account: accountFormRule, + password: passwordFormRule, + confirmPassword: [ + { validator: validateConfirmPassword(formData?.password), trigger: 'change' }, + ], + policy: [{ validator: validatePolicy, trigger: 'change' }], + ...mobileRule, + }; + + // reset password form rules + case LoginStateEnum.RESET_PASSWORD: + return { + account: accountFormRule, + ...mobileRule, + }; + + // mobile form rules + case LoginStateEnum.MOBILE: + return mobileRule; + + // login form rules + default: + return { + account: accountFormRule, + password: passwordFormRule, + }; + } + }); + return { getFormRules }; +} + +function createRule(message: string): ValidationRule[] { + return [ + { + required: true, + message, + trigger: 'change', + }, + ]; +} diff --git a/src/views/sys/redirect/index.vue b/src/views/sys/redirect/index.vue new file mode 100644 index 00000000000..9e6647b22bd --- /dev/null +++ b/src/views/sys/redirect/index.vue @@ -0,0 +1,30 @@ + + diff --git a/stylelint.config.mjs b/stylelint.config.mjs deleted file mode 100644 index e380674fda0..00000000000 --- a/stylelint.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -export default { - extends: ['@vben/stylelint-config'], - root: true, -}; diff --git a/tea.yaml b/tea.yaml deleted file mode 100644 index 6e56d6f6ceb..00000000000 --- a/tea.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# https://tea.xyz/what-is-this-file ---- -version: 1.0.0 -codeOwners: - - '0xB33cc732DFc15Cd39eF50Fb165c876E24417E48f' -quorum: 1 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000000..9c69499d092 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/ts-config/vue-app.json", + "compilerOptions": { + "baseUrl": ".", + "declaration": false, + "types": ["vite/client"], + "paths": { + "@/*": ["src/*"], + "#/*": ["types/*"] + } + }, + "include": [ + "tests/**/*.ts", + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue", + "types/**/*.d.ts", + "types/**/*.ts", + "build/**/*.ts", + "build/**/*.d.ts", + "mock/**/*.ts", + "vite.config.ts" + ], + "exclude": ["node_modules", "tests/server/**/*.ts", "dist", "**/*.js"] +} diff --git a/turbo.json b/turbo.json index 3443e27cfa1..bf5b89bfc90 100644 --- a/turbo.json +++ b/turbo.json @@ -1,49 +1,18 @@ { - "$schema": "https://turbo.build/schema.json", - "globalDependencies": [ - "pnpm-lock.yaml", - "**/.env.*local", - "**/tsconfig*.json", - "internal/node-utils/*.json", - "internal/node-utils/src/**/*.ts", - "internal/tailwind-config/src/**/*.ts", - "internal/vite-config/*.json", - "internal/vite-config/src/**/*.ts", - "scripts/*/src/**/*.ts", - "scripts/*/src/**/*.json" - ], - "globalEnv": ["NODE_ENV"], - "tasks": { + "$schema": "https://turborepo.org/schema.json", + "pipeline": { "build": { - "dependsOn": ["^build"], - "outputs": [ - "dist/**", - "dist.zip", - ".vitepress/dist.zip", - ".vitepress/dist/**" - ] - }, - "preview": { "dependsOn": ["^build"], "outputs": ["dist/**"] }, - "build:analyze": { - "dependsOn": ["^build"], - "outputs": ["dist/**"] + "stub": {}, + "lint": {}, + "clean": { + "cache": false }, - "@vben/backend-mock#build": { - "dependsOn": ["^build"], - "outputs": [".nitro/**", ".output/**"] - }, - "test:e2e": {}, "dev": { - "dependsOn": [], - "outputs": [], "cache": false, "persistent": true - }, - "typecheck": { - "outputs": [] } } } diff --git a/types/axios.d.ts b/types/axios.d.ts new file mode 100644 index 00000000000..0ff2f5858af --- /dev/null +++ b/types/axios.d.ts @@ -0,0 +1,56 @@ +export type ErrorMessageMode = 'none' | 'modal' | 'message' | undefined; +export type SuccessMessageMode = ErrorMessageMode; + +export interface RequestOptions { + // Splicing request parameters to url + joinParamsToUrl?: boolean; + // Format request parameter time + formatDate?: boolean; + // Whether to process the request result + isTransformResponse?: boolean; + // Whether to return native response headers + // For example: use this attribute when you need to get the response headers + isReturnNativeResponse?: boolean; + // Whether to join url + joinPrefix?: boolean; + // Interface address, use the default apiUrl if you leave it blank + apiUrl?: string; + // 请求拼接路径 + urlPrefix?: string; + // Error message prompt type + errorMessageMode?: ErrorMessageMode; + // Success message prompt type + successMessageMode?: SuccessMessageMode; + // Whether to add a timestamp + joinTime?: boolean; + ignoreCancelToken?: boolean; + // Whether to send token in header + withToken?: boolean; + // 请求重试机制 + retryRequest?: RetryRequest; +} + +export interface RetryRequest { + isOpenRetry: boolean; + count: number; + waitTime: number; +} +export interface Result { + code: number; + type: 'success' | 'error' | 'warning'; + message: string; + result: T; +} + +// multipart/form-data: upload file +export interface UploadFileParams { + // Other parameters + data?: Recordable; + // File parameter interface field name + name?: string; + // file name + file: File | Blob; + // file name + filename?: string; + [key: string]: any; +} diff --git a/types/config.d.ts b/types/config.d.ts new file mode 100644 index 00000000000..7a5b31c3577 --- /dev/null +++ b/types/config.d.ts @@ -0,0 +1,162 @@ +import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '@/enums/menuEnum'; +import { + ContentEnum, + PermissionModeEnum, + ThemeEnum, + RouterTransitionEnum, + SettingButtonPositionEnum, + SessionTimeoutProcessingEnum, +} from '@/enums/appEnum'; + +import { CacheTypeEnum } from '@/enums/cacheEnum'; + +export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja' | 'ko'; + +export interface MenuSetting { + bgColor: string; + fixed: boolean; + collapsed: boolean; + siderHidden: boolean; + canDrag: boolean; + show: boolean; + hidden: boolean; + split: boolean; + menuWidth: number; + mode: MenuModeEnum; + type: MenuTypeEnum; + theme: ThemeEnum; + topMenuAlign: 'start' | 'center' | 'end'; + trigger: TriggerEnum; + accordion: boolean; + closeMixSidebarOnChange: boolean; + collapsedShowTitle: boolean; + mixSideTrigger: MixSidebarTriggerEnum; + mixSideFixed: boolean; +} + +export interface MultiTabsSetting { + cache: boolean; + show: boolean; + showQuick: boolean; + canDrag: boolean; + showRedo: boolean; + showFold: boolean; + autoCollapse: boolean; +} + +export interface HeaderSetting { + bgColor: string; + fixed: boolean; + show: boolean; + theme: ThemeEnum; + // Turn on full screen + showFullScreen: boolean; + // Whether to show the lock screen + useLockPage: boolean; + // Show document button + showDoc: boolean; + // Show message center button + showNotice: boolean; + showSearch: boolean; + showApi: boolean; +} + +export interface LocaleSetting { + showPicker: boolean; + // Current language + locale: LocaleType; + // default language + fallback: LocaleType; + // available Locales + availableLocales: LocaleType[]; +} + +export interface TransitionSetting { + // Whether to open the page switching animation + enable: boolean; + // Route basic switching animation + basicTransition: RouterTransitionEnum; + // Whether to open page switching loading + openPageLoading: boolean; + // Whether to open the top progress bar + openNProgress: boolean; +} + +export interface ProjectConfig { + // Storage location of permission related information + permissionCacheType: CacheTypeEnum; + // Whether to show the configuration button + showSettingButton: boolean; + // Whether to show the theme switch button + showDarkModeToggle: boolean; + // Configure where the button is displayed + settingButtonPosition: SettingButtonPositionEnum; + // Permission mode + permissionMode: PermissionModeEnum; + // Session timeout processing + sessionTimeoutProcessing: SessionTimeoutProcessingEnum; + // Website gray mode, open for possible mourning dates + grayMode: boolean; + // Whether to turn on the color weak mode + colorWeak: boolean; + // Theme color + themeColor: string; + + // The main interface is displayed in full screen, the menu is not displayed, and the top + fullContent: boolean; + // content width + contentMode: ContentEnum; + // Whether to display the logo + showLogo: boolean; + // Whether to show the global footer + showFooter: boolean; + // menuType: MenuTypeEnum; + headerSetting: HeaderSetting; + // menuSetting + menuSetting: MenuSetting; + // Multi-tab settings + multiTabsSetting: MultiTabsSetting; + // Animation configuration + transitionSetting: TransitionSetting; + // pageLayout whether to enable keep-alive + openKeepAlive: boolean; + // Lock screen time + lockTime: number; + // Show breadcrumbs + showBreadCrumb: boolean; + // Show breadcrumb icon + showBreadCrumbIcon: boolean; + // Use error-handler-plugin + useErrorHandle: boolean; + // Whether to open back to top + useOpenBackTop: boolean; + // Is it possible to embed iframe pages + canEmbedIFramePage: boolean; + // Whether to delete unclosed messages and notify when switching the interface + closeMessageOnSwitch: boolean; + // Whether to cancel the http request that has been sent but not responded when switching the interface. + removeAllHttpPending: boolean; +} + +export interface GlobConfig { + // Site title + title: string; + // Service interface url + apiUrl: string; + // Upload url + uploadUrl?: string; + // Service interface url prefix + urlPrefix?: string; + // Project abbreviation + shortName: string; +} +export interface GlobEnvConfig { + // Site title + VITE_GLOB_APP_TITLE: string; + // Service interface url + VITE_GLOB_API_URL: string; + // Service interface url prefix + VITE_GLOB_API_URL_PREFIX?: string; + // Upload url + VITE_GLOB_UPLOAD_URL?: string; +} diff --git a/types/directives.d.ts b/types/directives.d.ts new file mode 100644 index 00000000000..4d61b235b69 --- /dev/null +++ b/types/directives.d.ts @@ -0,0 +1,11 @@ +import type { Directive } from 'vue'; +import { RoleEnum } from '@/enums/roleEnum'; + +declare module 'vue' { + export interface ComponentCustomProperties { + vLoading: Directive; + vAuth: Directive; + } +} + +export {}; diff --git a/types/global.d.ts b/types/global.d.ts new file mode 100644 index 00000000000..301c19aab64 --- /dev/null +++ b/types/global.d.ts @@ -0,0 +1,98 @@ +import type { + ComponentRenderProxy, + VNode, + VNodeChild, + ComponentPublicInstance, + FunctionalComponent, + PropType as VuePropType, +} from 'vue'; + +declare global { + const __APP_INFO__: { + pkg: { + name: string; + version: string; + dependencies: Recordable; + devDependencies: Recordable; + }; + lastBuildTime: string; + }; + // declare interface Window { + // // Global vue app instance + // __APP__: App; + // } + + // fix FullScreen type error + interface Document { + mozFullScreenElement?: Element; + msFullscreenElement?: Element; + webkitFullscreenElement?: Element; + } + + // vue + declare type PropType = VuePropType; + declare type VueNode = VNodeChild | JSX.Element; + + export type Writable = { + -readonly [P in keyof T]: T[P]; + }; + + declare type Nullable = T | null; + declare type NonNullable = T extends null | undefined ? never : T; + declare type Recordable = Record; + declare type ReadonlyRecordable = { + readonly [key: string]: T; + }; + declare type Indexable = { + [key: string]: T; + }; + declare type DeepPartial = { + [P in keyof T]?: DeepPartial; + }; + declare type TimeoutHandle = ReturnType; + declare type IntervalHandle = ReturnType; + + declare interface ChangeEvent extends Event { + target: HTMLInputElement; + } + + declare interface WheelEvent { + path?: EventTarget[]; + } + interface ImportMetaEnv extends ViteEnv { + __: unknown; + } + + declare interface ViteEnv { + VITE_USE_MOCK: boolean; + VITE_PUBLIC_PATH: string; + VITE_GLOB_APP_TITLE: string; + VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none'; + } + + declare function parseInt(s: string | number, radix?: number): number; + + declare function parseFloat(string: string | number): number; + + namespace JSX { + // tslint:disable no-empty-interface + type Element = VNode; + // tslint:disable no-empty-interface + type ElementClass = ComponentRenderProxy; + interface ElementAttributesProperty { + $props: any; + } + interface IntrinsicElements { + [elem: string]: any; + } + interface IntrinsicAttributes { + [elem: string]: any; + } + } +} + +declare module 'vue' { + export type JSXComponent = + | { new (): ComponentPublicInstance } + | FunctionalComponent; +} diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000000..7f67f33df65 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,27 @@ +declare interface Fn { + (...arg: T[]): R; +} + +declare interface PromiseFn { + (...arg: T[]): Promise; +} + +declare type RefType = T | null; + +declare type LabelValueOptions = { + label: string; + value: any; + [key: string]: string | number | boolean; +}[]; + +declare type EmitType = ReturnType; + +declare type TargetContext = '_self' | '_blank'; + +declare interface ComponentElRef { + $el: T; +} + +declare type ComponentRef = ComponentElRef | null; + +declare type ElRef = Nullable; diff --git a/types/module.d.ts b/types/module.d.ts new file mode 100644 index 00000000000..61a0c34e5dd --- /dev/null +++ b/types/module.d.ts @@ -0,0 +1,18 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue'; + + const Component: DefineComponent<{}, {}, any>; + export default Component; +} + +declare module 'ant-design-vue/es/locale/*' { + import { Locale } from 'ant-design-vue/types/locale-provider'; + + const locale: Locale & ReadonlyRecordable; + export default locale as Locale & ReadonlyRecordable; +} + +declare module 'virtual:*' { + const result: any; + export default result; +} diff --git a/types/store.d.ts b/types/store.d.ts new file mode 100644 index 00000000000..3bd85830f56 --- /dev/null +++ b/types/store.d.ts @@ -0,0 +1,60 @@ +import { ErrorTypeEnum } from '@/enums/exceptionEnum'; +import { MenuModeEnum, MenuTypeEnum } from '@/enums/menuEnum'; +import { RoleInfo } from '@/api/sys/model/userModel'; + +// Lock screen information +export interface LockInfo { + // Password required + pwd?: string | undefined; + // Is it locked? + isLock?: boolean; +} + +export interface ApiAddress { + key: string; + val: string; +} + +// Error-log information +export interface ErrorLogInfo { + // Type of error + type: ErrorTypeEnum; + // Error file + file: string; + // Error name + name?: string; + // Error message + message: string; + // Error stack + stack?: string; + // Error detail + detail: string; + // Error url + url: string; + // Error time + time?: string; +} + +export interface UserInfo { + userId: string | number; + username: string; + realName: string; + avatar: string; + desc?: string; + homePath?: string; + roles: RoleInfo[]; +} + +export interface BeforeMiniState { + menuCollapsed?: boolean; + menuSplit?: boolean; + menuMode?: MenuModeEnum; + menuType?: MenuTypeEnum; +} + +export interface TableSetting { + size: Nullable; + showIndexColumn: Recordable>; + columns: Recordable>>; + showRowSelection: Recordable>; +} diff --git a/types/utils.d.ts b/types/utils.d.ts new file mode 100644 index 00000000000..6500d44729c --- /dev/null +++ b/types/utils.d.ts @@ -0,0 +1,5 @@ +import type { ComputedRef, Ref } from 'vue'; + +export type DynamicProps = { + [P in keyof T]: Ref | T[P] | ComputedRef; +}; diff --git a/types/vue-router.d.ts b/types/vue-router.d.ts new file mode 100644 index 00000000000..b006c921f44 --- /dev/null +++ b/types/vue-router.d.ts @@ -0,0 +1,49 @@ +import { RoleEnum } from '@/enums/roleEnum'; + +export {}; + +declare module 'vue-router' { + interface RouteMeta extends Record { + orderNo?: number; + // title + title: string; + // dynamic router level. + dynamicLevel?: number; + // dynamic router real route path (For performance). + realPath?: string; + // Whether to ignore permissions + ignoreAuth?: boolean; + // role info + roles?: RoleEnum[]; + // Whether not to cache + ignoreKeepAlive?: boolean; + // Is it fixed on tab + affix?: boolean; + // icon on tab + icon?: string; + // img on tab + img?: string; + frameSrc?: string; + // current page transition + transitionName?: string; + // Whether the route has been dynamically added + hideBreadcrumb?: boolean; + // Hide submenu + hideChildrenInMenu?: boolean; + // Carrying parameters + carryParam?: boolean; + // Used internally to mark single-level menus + single?: boolean; + // Currently active menu + currentActiveMenu?: string; + // Never show in tab + hideTab?: boolean; + // Never show in menu + hideMenu?: boolean; + isLink?: boolean; + // only build for Menu + ignoreRoute?: boolean; + // Hide path for children + hidePathForChildren?: boolean; + } +} diff --git a/uno.config.ts b/uno.config.ts new file mode 100644 index 00000000000..5e3512f7bcc --- /dev/null +++ b/uno.config.ts @@ -0,0 +1,5 @@ +import { defineConfig, presetTypography, presetUno } from 'unocss'; + +export default defineConfig({ + presets: [presetUno(), presetTypography()], +}); diff --git a/vben-admin.code-workspace b/vben-admin.code-workspace deleted file mode 100644 index aa8205bda73..00000000000 --- a/vben-admin.code-workspace +++ /dev/null @@ -1,172 +0,0 @@ -{ - "folders": [ - { - "name": "@vben/backend-mock", - "path": "apps/backend-mock", - }, - { - "name": "@vben/web-antd", - "path": "apps/web-antd", - }, - { - "name": "@vben/web-ele", - "path": "apps/web-ele", - }, - { - "name": "@vben/web-naive", - "path": "apps/web-naive", - }, - { - "name": "@vben/docs", - "path": "docs", - }, - { - "name": "@vben/commitlint-config", - "path": "internal/lint-configs/commitlint-config", - }, - { - "name": "@vben/eslint-config", - "path": "internal/lint-configs/eslint-config", - }, - { - "name": "@vben/prettier-config", - "path": "internal/lint-configs/prettier-config", - }, - { - "name": "@vben/stylelint-config", - "path": "internal/lint-configs/stylelint-config", - }, - { - "name": "@vben/node-utils", - "path": "internal/node-utils", - }, - { - "name": "@vben/tailwind-config", - "path": "internal/tailwind-config", - }, - { - "name": "@vben/tsconfig", - "path": "internal/tsconfig", - }, - { - "name": "@vben/vite-config", - "path": "internal/vite-config", - }, - { - "name": "@vben-core/design", - "path": "packages/@core/base/design", - }, - { - "name": "@vben-core/icons", - "path": "packages/@core/base/icons", - }, - { - "name": "@vben-core/shared", - "path": "packages/@core/base/shared", - }, - { - "name": "@vben-core/typings", - "path": "packages/@core/base/typings", - }, - { - "name": "@vben-core/composables", - "path": "packages/@core/composables", - }, - { - "name": "@vben-core/preferences", - "path": "packages/@core/preferences", - }, - { - "name": "@vben-core/form-ui", - "path": "packages/@core/ui-kit/form-ui", - }, - { - "name": "@vben-core/layout-ui", - "path": "packages/@core/ui-kit/layout-ui", - }, - { - "name": "@vben-core/menu-ui", - "path": "packages/@core/ui-kit/menu-ui", - }, - { - "name": "@vben-core/popup-ui", - "path": "packages/@core/ui-kit/popup-ui", - }, - { - "name": "@vben-core/shadcn-ui", - "path": "packages/@core/ui-kit/shadcn-ui", - }, - { - "name": "@vben-core/tabs-ui", - "path": "packages/@core/ui-kit/tabs-ui", - }, - { - "name": "@vben/constants", - "path": "packages/constants", - }, - { - "name": "@vben/access", - "path": "packages/effects/access", - }, - { - "name": "@vben/common-ui", - "path": "packages/effects/common-ui", - }, - { - "name": "@vben/hooks", - "path": "packages/effects/hooks", - }, - { - "name": "@vben/layouts", - "path": "packages/effects/layouts", - }, - { - "name": "@vben/plugins", - "path": "packages/effects/plugins", - }, - { - "name": "@vben/request", - "path": "packages/effects/request", - }, - { - "name": "@vben/icons", - "path": "packages/icons", - }, - { - "name": "@vben/locales", - "path": "packages/locales", - }, - { - "name": "@vben/preferences", - "path": "packages/preferences", - }, - { - "name": "@vben/stores", - "path": "packages/stores", - }, - { - "name": "@vben/styles", - "path": "packages/styles", - }, - { - "name": "@vben/types", - "path": "packages/types", - }, - { - "name": "@vben/utils", - "path": "packages/utils", - }, - { - "name": "@vben/playground", - "path": "playground", - }, - { - "name": "@vben/turbo-run", - "path": "scripts/turbo-run", - }, - { - "name": "@vben/vsh", - "path": "scripts/vsh", - }, - ], -} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 00000000000..b838a55fd5b --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,46 @@ +import { defineApplicationConfig } from '@vben/vite-config'; +import Inspector from 'vite-plugin-vue-inspector'; + +export default defineApplicationConfig({ + overrides: { + optimizeDeps: { + include: [ + 'echarts/core', + 'echarts/charts', + 'echarts/components', + 'echarts/renderers', + 'qrcode', + '@iconify/iconify', + 'ant-design-vue/es/locale/zh_CN', + 'ant-design-vue/es/locale/en_US', + ], + }, + server: { + proxy: { + '/basic-api': { + target: 'http://localhost:3000/', + changeOrigin: true, + ws: true, + rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''), + // only https + // secure: false + }, + '/upload': { + target: 'http://localhost:3300/upload', + changeOrigin: true, + ws: true, + rewrite: (path) => path.replace(new RegExp(`^/upload`), ''), + }, + }, + open: true, // 项目启动后,自动打开 + warmup: { + clientFiles: ['./index.html', './src/{views,components}/*'], + }, + }, + plugins: [ + Inspector({ + openInEditorHost: 'http://localhost:5173/', + }), + ], + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts deleted file mode 100644 index a10b5fa3a06..00000000000 --- a/vitest.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Vue from '@vitejs/plugin-vue'; -import VueJsx from '@vitejs/plugin-vue-jsx'; -import { configDefaults, defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [Vue(), VueJsx()], - test: { - environment: 'happy-dom', - exclude: [...configDefaults.exclude, '**/e2e/**'], - }, -}); diff --git a/vitest.workspace.ts b/vitest.workspace.ts deleted file mode 100644 index f00d6f68667..00000000000 --- a/vitest.workspace.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { defineWorkspace } from 'vitest/config'; - -export default defineWorkspace(['vitest.config.ts']);