[
  {
    "path": ".browserslistrc",
    "content": "> 1%\nlast 2 versions\nnot dead\nnot ie 11\n"
  },
  {
    "path": ".changeset/README.md",
    "content": "# Changesets\n\nHello 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)\n\nWe 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)\n"
  },
  {
    "path": ".changeset/config.json",
    "content": "{\n  \"$schema\": \"https://unpkg.com/@changesets/config@3.0.0/schema.json\",\n  \"changelog\": [\n    \"@changesets/changelog-github\",\n    { \"repo\": \"vbenjs/vue-vben-admin\" }\n  ],\n  \"commit\": false,\n  \"fixed\": [[\"@vben-core/*\", \"@vben/*\"]],\n  \"snapshot\": {\n    \"prereleaseTemplate\": \"{tag}-{datetime}\"\n  },\n  \"privatePackages\": { \"version\": true, \"tag\": true },\n  \"linked\": [],\n  \"access\": \"public\",\n  \"baseBranch\": \"main\",\n  \"updateInternalDependencies\": \"patch\",\n  \"ignore\": []\n}\n"
  },
  {
    "path": ".commitlintrc.js",
    "content": "export { default } from '@vben/commitlint-config';\n"
  },
  {
    "path": ".dockerignore",
    "content": "node_modules\n.git\n.gitignore\n*.md\ndist\n.turbo\ndist.zip\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset=utf-8\nend_of_line=lf\ninsert_final_newline=true\nindent_style=space\nindent_size=2\nmax_line_length = 100\ntrim_trailing_whitespace = true\nquote_type = single\n\n[*.{yml,yaml,json}]\nindent_style = space\nindent_size = 2\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings\n\n# Automatically normalize line endings (to LF) for all text-based files.\n* text=auto eol=lf\n\n# Declare files that will always have CRLF line endings on checkout.\n*.{cmd,[cC][mM][dD]} text eol=crlf\n*.{bat,[bB][aA][tT]} text eol=crlf\n\n# Denote all files that are truly binary and should not be modified.\n*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary"
  },
  {
    "path": ".gitconfig",
    "content": "[core]\n    ignorecase = false\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# default onwer\n* anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com\n\n# vben core onwer\n/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com\n/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com\n/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com\n/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com\n/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com\n/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com\n\n# vben team onwer\napps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com\ndocs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "content": "name: 🐞 Bug Report\ndescription: Report an issue with Vben Admin to help us make it better.\ntitle: 'Bug: '\nlabels: ['bug: pending triage']\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report!\n  - type: dropdown\n    id: version\n    attributes:\n      label: Version\n      description: What version of our software are you running?\n      options:\n        - Vben Admin V5\n        - Vben Admin V2\n      default: 0\n    validations:\n      required: true\n\n  - type: textarea\n    id: bug-desc\n    attributes:\n      label: Describe the bug?\n      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!\n      placeholder: Bug Description\n    validations:\n      required: true\n\n  - type: textarea\n    id: reproduction\n    attributes:\n      label: Reproduction\n      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.\n      placeholder: Reproduction\n    validations:\n      required: true\n\n  - type: textarea\n    id: system-info\n    attributes:\n      label: System Info\n      description: Output of `npx envinfo --system --npmPackages '{vue}' --binaries --browsers`\n      render: shell\n      placeholder: System, Binaries, Browsers\n    validations:\n      required: true\n\n  - type: textarea\n    id: logs\n    attributes:\n      label: Relevant log output\n      description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.\n      render: shell\n\n  - type: checkboxes\n    id: terms\n    attributes:\n      label: Validations\n      description: Before submitting the issue, please make sure you do the following\n      # description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com).\n      options:\n        - label: Read the [docs](https://doc.vben.pro/)\n          required: true\n        - label: Ensure the code is up to date. (Some issues have been fixed in the latest version)\n          required: true\n        - 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.\n          required: true\n        - 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).\n          required: true\n        - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/docs.yml",
    "content": "name: 📚 Documentation\ndescription: Report an issue with Vben Admin Website to help us make it better.\ntitle: 'Docs: '\nlabels: [documentation]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this issue!\n  - type: checkboxes\n    id: documentation_is\n    attributes:\n      label: Documentation is\n      options:\n        - label: Missing\n        - label: Outdated\n        - label: Confusing\n        - label: Not sure?\n  - type: textarea\n    id: description\n    attributes:\n      label: Explain in Detail\n      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!\n      placeholder: The description of ... page is not clear. I thought it meant ... but it wasn't.\n    validations:\n      required: true\n  - type: textarea\n    id: suggestion\n    attributes:\n      label: Your Suggestion for Changes\n    validations:\n      required: true\n  - type: textarea\n    id: reproduction-steps\n    attributes:\n      label: Steps to reproduce\n      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.\n      placeholder: Run `pnpm install` followed by `pnpm run docs:dev`\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yml",
    "content": "name: ✨ New Feature Proposal\ndescription: Propose a new feature to be added to Vben Admin\ntitle: 'FEATURE: '\nlabels: ['enhancement: pending triage']\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thank you for suggesting a feature for our project! Please fill out the information below to help us understand and implement your request!\n  - type: dropdown\n    id: version\n    attributes:\n      label: Version\n      description: What version of our software are you running?\n      options:\n        - Vben Admin V5\n        - Vben Admin V2\n      default: 0\n    validations:\n      required: true\n\n  - type: textarea\n    id: description\n    attributes:\n      label: Description\n      description: A detailed description of the feature request.\n      placeholder: Please describe the feature you would like to see, and why it would be useful.\n    validations:\n      required: true\n\n  - type: textarea\n    id: proposed-solution\n    attributes:\n      label: Proposed Solution\n      description: A clear and concise description of what you want to happen.\n      placeholder: Describe the solution you'd like to see\n    validations:\n      required: true\n\n  - type: textarea\n    id: alternatives\n    attributes:\n      label: Alternatives Considered\n      description: |\n        A clear and concise description of any alternative solutions or features you've considered.\n      placeholder: Describe any alternative solutions or features you've considered\n    validations:\n      required: false\n\n  - type: input\n    id: additional-context\n    attributes:\n      label: Additional Context\n      description: Add any other context or screenshots about the feature request here.\n      placeholder: Any additional information\n    validations:\n      required: false\n\n  - type: checkboxes\n    id: checkboxes\n    attributes:\n      label: Validations\n      description: Before submitting the issue, please make sure you do the following\n      options:\n        - label: Read the [docs](https://doc.vben.pro/)\n          required: true\n        - label: Ensure the code is up to date. (Some issues have been fixed in the latest version)\n          required: true\n        - 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.\n          required: true\n"
  },
  {
    "path": ".github/actions/setup-node/action.yml",
    "content": "name: 'Setup Node'\n\ndescription: 'Setup node and pnpm'\n\nruns:\n  using: 'composite'\n  steps:\n    - name: Install pnpm\n      uses: pnpm/action-setup@v4\n\n    - name: Install Node.js\n      uses: actions/setup-node@v6\n      with:\n        node-version-file: .node-version\n        cache: 'pnpm'\n\n    - name: Get pnpm store directory\n      shell: bash\n      run: |\n        echo \"STORE_PATH=$(pnpm store path --silent)\" >> $GITHUB_ENV\n\n    - uses: actions/cache@v4\n      name: Setup pnpm cache\n      if: ${{ github.ref_name == 'main' }}\n      with:\n        path: ${{ env.STORE_PATH }}\n        key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}\n        restore-keys: |\n          ${{ runner.os }}-pnpm-store-\n\n    - uses: actions/cache/restore@v4\n      if: ${{ github.ref_name != 'main' }}\n      with:\n        path: ${{ env.STORE_PATH }}\n        key: |\n          ${{ runner.os }}-pnpm-store-\n\n    - name: Install dependencies\n      shell: bash\n      run: pnpm install --frozen-lockfile\n"
  },
  {
    "path": ".github/commit-convention.md",
    "content": "## Git Commit Message Convention\n\n> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).\n\n#### TL;DR:\n\nMessages must be matched by the following regex:\n\n```js\n/^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|types|wip): .{1,50}/;\n```\n\n#### Examples\n\nAppears under \"Features\" header, `dev` subheader:\n\n```\nfeat(dev): add 'comments' option\n```\n\nAppears under \"Bug Fixes\" header, `dev` subheader, with a link to issue #28:\n\n```\nfix(dev): fix dev error\n\nclose #28\n```\n\nAppears under \"Performance Improvements\" header, and under \"Breaking Changes\" with the breaking change explanation:\n\n```\nperf(build): remove 'foo' option\n\nBREAKING CHANGE: The 'foo' option has been removed.\n```\n\nThe following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the \"Reverts\" header.\n\n```\nrevert: feat(compiler): add 'comments' option\n\nThis reverts commit 667ecc1654a317a13331b17617d973392f415f02.\n```\n\n### Full Message Format\n\nA commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:\n\n```\n<type>(<scope>): <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\nThe **header** is mandatory and the **scope** of the header is optional.\n\n### Revert\n\nIf the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.\n\n### Type\n\nIf the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However, if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.\n\nOther prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.\n\n### Scope\n\nThe scope could be anything specifying the place of the commit change. For example `dev`, `build`, `workflow`, `cli` etc...\n\n### Subject\n\nThe subject contains a succinct description of the change:\n\n- use the imperative, present tense: \"change\" not \"changed\" nor \"changes\"\n- don't capitalize the first letter\n- no dot (.) at the end\n\n### Body\n\nJust as in the **subject**, use the imperative, present tense: \"change\" not \"changed\" nor \"changes\". The body should include the motivation for the change and contrast this with previous behavior.\n\n### Footer\n\nThe footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.\n\n**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.\n"
  },
  {
    "path": ".github/config.yml",
    "content": "# Prevent issues being created without using the template\nblank_issues_enabled: false\ncheckIssueTemplate: true\ncheckPullRequestTemplate: true\n\ncontact_links:\n  - name: 💬 Discord Chat\n    url: https://discord.gg/8GuAdwDhj6\n    about: Ask questions and discuss with other Vben users in real time.\n\n  - name: ❓ Questions & Discussions\n    url: https://github.com/@vbenjs/vue-vben-admin/discussions\n    about: Use GitHub discussions for message-board style questions and discussions.\n\n# Comment to be posted to on PRs from first time contributors in your repository\nnewPRWelcomeComment: |\n  💖 Thanks for opening this pull request! 💖\n  Please be patient and we will get back to you as soon as we can.\n\n# Comment to be posted to on pull requests merged by a first time user\nfirstPRMergeComment: >\n  Thanks for your contribution!  🎉🎉🎉\n\n\n# Comment to be posted to on first time issues\nnewIssueWelcomeComment: >\n  Thanks for opening your first issue! Be sure to follow the issue template and provide every bit of information to help the developers!\n\n\n# *OPTIONAL* default titles to check against for lack of descriptiveness\n# MUST BE ALL LOWERCASE\nrequestInfoDefaultTitles:\n  - update readme.md\n  - updates\n\n# *Required* Comment to reply with\nrequestInfoReplyComment: >\n  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.\n\n"
  },
  {
    "path": ".github/contributing.md",
    "content": "# Vben Admin Contributing Guide\n\nHi! 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:\n\n- [Pull Request Guidelines](#pull-request-guidelines)\n\n## Contributor Code of Conduct\n\nAs 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.\n\nWe 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.\n\nExamples 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.\n\nProject 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.\n\n## Pull Request Guidelines\n\n- Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch.\n\n- If adding a new feature:\n  - Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.\n\n- If fixing bug:\n  - Provide a detailed description of the bug in the PR. Live demo preferred.\n\n- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.\n\n## Development Setup\n\nYou will need [pnpm](https://pnpm.io/)\n\nAfter cloning the repo, run:\n\n```bash\n# install the dependencies of the project\n$ pnpm install\n# start the project\n$ pnpm run dev\n```\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: npm\n    directory: '/'\n    schedule:\n      interval: daily\n    groups:\n      non-breaking-changes:\n        update-types: [minor, patch]\n\n  - package-ecosystem: github-actions\n    directory: '/'\n    schedule:\n      interval: weekly\n    groups:\n      non-breaking-changes:\n        update-types: [minor, patch]\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Description\n\n<!-- Please describe the change as necessary. If it's a feature or enhancement please be as detailed as possible. If it's a bug fix, please link the issue that it fixes or describe the bug in as much detail.\n\n -->\n\n<!-- You can also add additional context here -->\n\n## Type of change\n\nPlease delete options that are not relevant.\n\n- [ ] Bug fix (non-breaking change which fixes an issue)\n- [ ] New feature (non-breaking change which adds functionality)\n- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)\n- [ ] This change requires a documentation update\n- [ ] Please, don't make changes to `pnpm-lock.yaml` unless you introduce a new test example.\n\n## Checklist\n\n> ℹ️ Check all checkboxes - this will indicate that you have done everything in accordance with the rules in [CONTRIBUTING](contributing.md).\n\n- [ ] If you introduce new functionality, document it. You can run documentation with `pnpm run docs:dev` command.\n- [ ] Run the tests with `pnpm test`.\n- [ ] 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:`.\n- [ ] My code follows the style guidelines of this project\n- [ ] I have performed a self-review of my own code\n- [ ] I have commented my code, particularly in hard-to-understand areas\n- [ ] I have made corresponding changes to the documentation\n- [ ] My changes generate no new warnings\n- [ ] I have added tests that prove my fix is effective or that my feature works\n- [ ] New and existing unit tests pass locally with my changes\n- [ ] Any dependent changes have been merged and published in downstream modules\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "name-template: 'v$RESOLVED_VERSION'\ntag-template: 'v$RESOLVED_VERSION'\nversion-template: $MAJOR.$MINOR.$PATCH\nchange-template: '* $TITLE (#$NUMBER) @$AUTHOR'\ntemplate: |\n  # What's Changed\n\n  $CHANGES\n\n  **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION\n\ncategories:\n  - title: '🚀 Features'\n    labels:\n      - 'feature'\n  - title: '🐞 Bug Fixes'\n    labels:\n      - 'bug'\n  - title: '📈 Performance & Enhancement'\n    labels:\n      - 'perf'\n      - 'enhancement'\n  - title: 📝 Documentation\n    labels:\n      - 'documentation'\n  - title: 👻 Maintenance\n    labels:\n      - 'chore'\n      - 'dependencies'\n    # collapse-after: 12\n  - title: 🚦 Tests\n    labels:\n      - 'tests'\n  - title: 'Breaking'\n    label: 'breaking'\n\nversion-resolver:\n  major:\n    labels:\n      - 'major'\n      - 'breaking'\n  minor:\n    labels:\n      - 'minor'\n  patch:\n    labels:\n      - 'feature'\n      - 'patch'\n      - 'bug'\n      - 'maintenance'\n      - 'docs'\n      - 'dependencies'\n      - 'security'\n\nexclude-labels:\n  - 'skip-changelog'\n  - 'no-changelog'\n  - 'changelog'\n  - 'bump versions'\n  - 'reverted'\n  - 'invalid'\n"
  },
  {
    "path": ".github/semantic.yml",
    "content": "titleAndCommits: true\ntypes:\n  - feat\n  - fix\n  - docs\n  - chore\n  - style\n  - refactor\n  - perf\n  - test\n  - build\n  - ci\n  - revert\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "# name: Dependabot post-update\nname: Build detection\non:\n  pull_request_target:\n    types: [opened, synchronize, reopened]\n    branches:\n      - main\n\nenv:\n  HUSKY: '0'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n  pull-requests: write\n\njobs:\n  post-update:\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    # if: ${{ github.actor == 'dependabot[bot]' }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          # - macos-latest\n          - windows-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Checkout out pull request\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          gh pr checkout ${{ github.event.pull_request.number }}\n\n      - name: Setup Node\n        uses: ./.github/actions/setup-node\n\n      - name: Build\n        run: |\n          pnpm run build\n"
  },
  {
    "path": ".github/workflows/changeset-version.yml",
    "content": "# https://github.com/changesets/action\nname: Changeset version\n\non:\n  workflow_dispatch:\n  pull_request:\n    types:\n      - closed\n    branches:\n      - main\n\npermissions:\n  pull-requests: write\n  contents: write\n\nenv:\n  CI: true\n\njobs:\n  version:\n    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'\n    # if: github.repository == 'vbenjs/vue-vben-admin'\n    timeout-minutes: 15\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node\n        uses: ./.github/actions/setup-node\n\n      - name: Create Release Pull Request\n        uses: changesets/action@v1\n        with:\n          version: pnpm run version\n          commit: 'chore: bump versions'\n          title: 'chore: bump versions'\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n      - 'releases/*'\n\npermissions:\n  contents: read\n\nenv:\n  CI: true\n  TZ: Asia/Shanghai\n\njobs:\n  test:\n    name: Test\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          # - macos-latest\n          - windows-latest\n    timeout-minutes: 20\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n        with:\n          run_install: false\n\n      - name: Setup Node\n        uses: ./.github/actions/setup-node\n\n      # - name: Check Git version\n      #   run: git --version\n\n      # - name: Setup mock Git user\n      #   run: git config --global user.email \"you@example.com\" && git config --global user.name \"Your Name\"\n\n      - name: Vitest tests\n        run: pnpm run test:unit\n\n      # - name: Upload coverage\n      #   uses: codecov/codecov-action@v4\n      #   with:\n      #     token: ${{ secrets.CODECOV_TOKEN }}\n\n  lint:\n    name: Lint\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          # - macos-latest\n          - windows-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node\n        uses: ./.github/actions/setup-node\n\n      - name: Lint\n        run: pnpm run lint\n\n  check:\n    name: Check\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 20\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          # - macos-latest\n          - windows-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node\n        uses: ./.github/actions/setup-node\n\n      - name: Typecheck\n        run: pnpm check:type\n\n      # From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions\n      - name: Check workflow files\n        if: runner.os == 'Linux'\n        run: |\n          bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)\n          ./actionlint -color -shellcheck=\"\"\n\n  ci-ok:\n    name: CI OK\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    needs: [test, check, lint]\n    env:\n      FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}\n    steps:\n      - name: Check for failure\n        run: |\n          echo $FAILURE\n          if [ \"$FAILURE\" = \"false\" ]; then\n            exit 0\n          else\n            exit 1\n          fi\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: 'CodeQL'\n\non:\n  push:\n    branches: ['main']\n  pull_request:\n    branches: ['main']\n  schedule:\n    - cron: '35 0 * * 0'\n\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    # Runner size impacts CodeQL analysis time. To learn more, please see:\n    #   - https://gh.io/recommended-hardware-resources-for-running-codeql\n    #   - https://gh.io/supported-runners-and-hardware-resources\n    #   - https://gh.io/using-larger-runners (GitHub.com only)\n    # Consider using larger runners or machines with greater resources for possible analysis time improvements.\n    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}\n    timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}\n    permissions:\n      # required for all workflows\n      security-events: write\n\n      # required to fetch internal or private CodeQL packs\n      packages: read\n\n      # only required for workflows in private repositories\n      actions: read\n      contents: read\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - language: javascript-typescript\n            build-mode: none\n        # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'\n        # Use `c-cpp` to analyze code written in C, C++ or both\n        # Use 'java-kotlin' to analyze code written in Java, Kotlin or both\n        # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both\n        # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,\n        # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.\n        # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how\n        # 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\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v3\n        with:\n          languages: ${{ matrix.language }}\n          build-mode: ${{ matrix.build-mode }}\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n          # 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\n          # queries: security-extended,security-and-quality\n\n      # If the analyze step fails for one of the languages you are analyzing with\n      # \"We were unable to automatically build your code\", modify the matrix above\n      # to set the build mode to \"manual\" for that language. Then modify this step\n      # to build your code.\n      # ℹ️ Command-line programs to run using the OS shell.\n      # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n      - if: matrix.build-mode == 'manual'\n        shell: bash\n        run: |\n          echo 'If you are using a \"manual\" build mode for one or more of the' \\\n            'languages you are analyzing, replace this with the commands to build' \\\n            'your code, for example:'\n          echo '  make bootstrap'\n          echo '  make release'\n          exit 1\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v3\n        with:\n          category: '/language:${{matrix.language}}'\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy Website on push\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  deploy-playground-ftp:\n    name: Deploy Push Playground Ftp\n    if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Sed Config Base\n        shell: bash\n        run: |\n          sed -i  \"s#VITE_COMPRESS\\s*=.*#VITE_COMPRESS = gzip#g\" ./playground/.env.production\n          sed -i  \"s#VITE_PWA\\s*=.*#VITE_PWA = true#g\" ./playground/.env.production\n          cat ./playground/.env.production\n\n      - name: Setup Node\n        uses: ./.github/actions/setup-node\n\n      - name: Build\n        run: pnpm build:play\n\n      - name: Sync Playground files\n        uses: SamKirkland/FTP-Deploy-Action@v4.3.6\n        with:\n          server: ${{ secrets.PRO_FTP_HOST }}\n          username: ${{ secrets.WEB_PLAYGROUND_FTP_ACCOUNT }}\n          password: ${{ secrets.WEB_PLAYGROUND_FTP_PWSSWORD }}\n          local-dir: ./playground/dist/\n\n  deploy-docs-ftp:\n    name: Deploy Push Docs Ftp\n    if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node\n        uses: ./.github/actions/setup-node\n\n      - name: Build\n        run: pnpm build:docs\n\n      - name: Sync Docs files\n        uses: SamKirkland/FTP-Deploy-Action@v4.3.6\n        with:\n          server: ${{ secrets.PRO_FTP_HOST }}\n          username: ${{ secrets.WEBSITE_FTP_ACCOUNT }}\n          password: ${{ secrets.WEBSITE_FTP_PASSWORD }}\n          local-dir: ./docs/.vitepress/dist/\n\n  deploy-antd-ftp:\n    name: Deploy Push Antd Ftp\n    if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Sed Config Base\n        shell: bash\n        run: |\n          sed -i  \"s#VITE_COMPRESS\\s*=.*#VITE_COMPRESS = gzip#g\" ./apps/web-antd/.env.production\n          sed -i  \"s#VITE_PWA\\s*=.*#VITE_PWA = true#g\" ./apps/web-antd/.env.production\n          cat ./apps/web-antd/.env.production\n\n      - name: Setup Node\n        uses: ./.github/actions/setup-node\n\n      - name: Build\n        run: pnpm run build:antd\n\n      - name: Sync files\n        uses: SamKirkland/FTP-Deploy-Action@v4.3.6\n        with:\n          server: ${{ secrets.PRO_FTP_HOST }}\n          username: ${{ secrets.WEB_ANTD_FTP_ACCOUNT }}\n          password: ${{ secrets.WEB_ANTD_FTP_PASSWORD }}\n          local-dir: ./apps/web-antd/dist/\n\n  deploy-ele-ftp:\n    name: Deploy Push Element Ftp\n    if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Sed Config Base\n        shell: bash\n        run: |\n          sed -i  \"s#VITE_COMPRESS\\s*=.*#VITE_COMPRESS = gzip#g\" ./apps/web-ele/.env.production\n          sed -i  \"s#VITE_PWA\\s*=.*#VITE_PWA = true#g\" ./apps/web-ele/.env.production\n          cat ./apps/web-ele/.env.production\n\n      - name: Setup Node\n        uses: ./.github/actions/setup-node\n\n      - name: Build\n        run: pnpm run build:ele\n\n      - name: Sync files\n        uses: SamKirkland/FTP-Deploy-Action@v4.3.6\n        with:\n          server: ${{ secrets.PRO_FTP_HOST }}\n          username: ${{ secrets.WEB_ELE_FTP_ACCOUNT }}\n          password: ${{ secrets.WEB_ELE_FTP_PASSWORD }}\n          local-dir: ./apps/web-ele/dist/\n\n  deploy-naive-ftp:\n    name: Deploy Push Naive Ftp\n    if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Sed Config Base\n        shell: bash\n        run: |\n          sed -i  \"s#VITE_COMPRESS\\s*=.*#VITE_COMPRESS = gzip#g\" ./apps/web-naive/.env.production\n          sed -i  \"s#VITE_PWA\\s*=.*#VITE_PWA = true#g\" ./apps/web-naive/.env.production\n          cat ./apps/web-naive/.env.production\n\n      - name: Setup Node\n        uses: ./.github/actions/setup-node\n\n      - name: Build\n        run: pnpm run build:naive\n\n      - name: Sync files\n        uses: SamKirkland/FTP-Deploy-Action@v4.3.6\n        with:\n          server: ${{ secrets.PRO_FTP_HOST }}\n          username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}\n          password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}\n          local-dir: ./apps/web-naive/dist/\n\n  rerun-on-failure:\n    name: Rerun on failure\n    needs:\n      - deploy-playground-ftp\n      - deploy-docs-ftp\n      - deploy-antd-ftp\n      - deploy-ele-ftp\n      - deploy-naive-ftp\n    if: failure() && fromJSON(github.run_attempt) < 10\n    runs-on: ubuntu-latest\n    steps:\n      - name: Retry ${{ fromJSON(github.run_attempt) }} of 10\n        env:\n          GH_REPO: ${{ github.repository }}\n          GH_TOKEN: ${{ github.token }}\n        run: gh workflow run rerun.yml -F run_id=${{ github.run_id }}\n"
  },
  {
    "path": ".github/workflows/draft.yml",
    "content": "name: Release Drafter\n\non:\n  push:\n    branches:\n      - main\n\npermissions:\n  contents: read\n  pull-requests: write\n\njobs:\n  update_release_draft:\n    permissions:\n      # write permission is required to create a github release\n      contents: write\n      # write permission is required for autolabeler\n      # otherwise, read permission is required at least\n      pull-requests: write\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: release-drafter/release-drafter@v6\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/issue-close-require.yml",
    "content": "# 每天零点运行一次，它会检查所有带有 \"need reproduction\" 标签的 Issues。如果这些 Issues 在过去的 3 天内没有任何活动，它们将会被自动关闭。这有助于保持 Issue 列表的整洁，并且提醒用户在必要时提供更多的信息。\nname: Issue Close Require\n\n# 触发条件：每天零点\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 0 * * *'\n\npermissions:\n  pull-requests: write\n  contents: write\n  issues: write\n\njobs:\n  close-issues:\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      # 关闭未活动的 Issues\n      - name: Close Inactive Issues\n        uses: actions/stale@v9\n        with:\n          days-before-stale: -1 # Issues and PR will never be flagged stale automatically.\n          stale-issue-label: needs-reproduction # Label that flags an issue as stale.\n          only-labels: needs-reproduction # Only process these issues\n          days-before-issue-close: 3\n          ignore-updates: true\n          remove-stale-when-updated: false\n          close-issue-message: This issue was closed because it was open for 3 days without a valid reproduction.\n          close-issue-label: closed-by-action\n"
  },
  {
    "path": ".github/workflows/issue-labeled.yml",
    "content": "name: Label Based Actions\n\non:\n  issues:\n    types: [labeled]\n  # pull_request:\n  #   types: [labeled]\n\npermissions:\n  issues: write\n  pull-requests: write\n  contents: write\n\njobs:\n  reply-labeled:\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      - name: remove enhancement pending\n        if: github.event.label.name == 'enhancement'\n        uses: actions-cool/issues-helper@v3\n        with:\n          actions: 'remove-labels'\n          token: ${{ secrets.GITHUB_TOKEN }}\n          issue-number: ${{ github.event.issue.number }}\n          labels: 'enhancement: pending triage'\n\n      - name: remove bug pending\n        if: github.event.label.name == 'bug'\n        uses: actions-cool/issues-helper@v3\n        with:\n          actions: 'remove-labels'\n          token: ${{ secrets.GITHUB_TOKEN }}\n          issue-number: ${{ github.event.issue.number }}\n          labels: 'bug: pending triage'\n\n      - name: needs reproduction\n        if: github.event.label.name == 'needs reproduction'\n        uses: actions-cool/issues-helper@v3\n        with:\n          actions: 'create-comment, remove-labels'\n          token: ${{ secrets.GITHUB_TOKEN }}\n          issue-number: ${{ github.event.issue.number }}\n          body: |\n            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.\n          labels: 'bug: pending triage'\n"
  },
  {
    "path": ".github/workflows/lock.yml",
    "content": "name: Lock Threads\n\non:\n  schedule:\n    - cron: '0 0 * * *'\n  workflow_dispatch:\n\npermissions:\n  issues: write\n  pull-requests: write\n\njobs:\n  action:\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: dessant/lock-threads@v5\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          issue-inactive-days: '14'\n          issue-lock-reason: ''\n          pr-inactive-days: '30'\n          pr-lock-reason: ''\n          process-only: 'issues, prs'\n"
  },
  {
    "path": ".github/workflows/release-tag.yml",
    "content": "name: Create Release Tag\n\non:\n  push:\n    tags:\n      - 'v*.*.*' # Push events to matching v*, i.e. v1.0, v20.15.10\n\nenv:\n  HUSKY: '0'\n\npermissions:\n  pull-requests: write\n  contents: write\n\njobs:\n  build:\n    name: Create Release\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [22]\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      # - name: Checkout code\n      #   uses: actions/checkout@v6\n      #   with:\n      #     fetch-depth: 0\n\n      # - name: Install pnpm\n      #   uses: pnpm/action-setup@v4\n\n      # - name: Use Node.js ${{ matrix.node-version }}\n      #   uses: actions/setup-node@v4\n      #   with:\n      #     node-version: ${{ matrix.node-version }}\n      #     cache: \"pnpm\"\n\n      # - name: Install dependencies\n      #   run: pnpm install --frozen-lockfile\n\n      # - name: Test and Build\n      #   run: |\n      #     pnpm run test\n      #     pnpm run build\n\n      - name: version\n        id: version\n        run: |\n          tag=${GITHUB_REF/refs\\/tags\\//}\n          version=${tag#v}\n          major=${version%%.*}\n          echo \"tag=${tag}\" >> $GITHUB_OUTPUT\n          echo \"version=${version}\" >> $GITHUB_OUTPUT\n          echo \"major=${major}\" >> $GITHUB_OUTPUT\n\n      - uses: release-drafter/release-drafter@v6\n        with:\n          version: ${{ steps.version.outputs.version }}\n          publish: true\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      # - name: force update major tag\n      #   run: |\n      #     git tag v${{ steps.version.outputs.major }} ${{ steps.version.outputs.tag }} -f\n      #     git push origin refs/tags/v${{ steps.version.outputs.major }} -f\n\n      # - name: Create Release for Tag\n      #   id: release_tag\n      #   uses: ncipollo/release-action@v1\n      #   with:\n      #     token: ${{ secrets.GITHUB_TOKEN }}\n      #     generateReleaseNotes: \"true\"\n      #     body: |\n      #       > Please refer to [CHANGELOG.md](https://github.com/vbenjs/vue-vben-admin/blob/main/CHANGELOG.md) for details.\n"
  },
  {
    "path": ".github/workflows/rerun.yml",
    "content": "name: Rerun workflow\n\non:\n  workflow_dispatch:\n    inputs:\n      run_id:\n        description: The workflow id to relanch\n        required: true\njobs:\n  rerun:\n    runs-on: ubuntu-latest\n    steps:\n      - name: rerun ${{ inputs.run_id }}\n        env:\n          GH_REPO: ${{ github.repository }}\n          GH_TOKEN: ${{ github.token }}\n        run: |\n          gh run watch ${{ inputs.run_id }} > /dev/null 2>&1\n          gh run rerun ${{ inputs.run_id }} --failed\n"
  },
  {
    "path": ".github/workflows/semantic-pull-request.yml",
    "content": "name: Semantic Pull Request\n\non:\n  pull_request_target:\n    types:\n      - opened\n      - edited\n      - synchronize\n\njobs:\n  main:\n    name: Semantic Pull Request\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Validate PR title\n        uses: amannn/action-semantic-pull-request@v5\n        with:\n          wip: true\n          subjectPattern: ^(?![A-Z]).+$\n          subjectPatternError: |\n            The subject \"{subject}\" found in the pull request title \"{title}\"\n            didn't match the configured pattern. Please ensure that the subject\n            doesn't start with an uppercase character.\n          requireScope: false\n          types: |\n            fix\n            feat\n            docs\n            style\n            refactor\n            perf\n            test\n            build\n            ci\n            chore\n            revert\n            types\n            release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: 'Close stale issues'\n\non:\n  schedule:\n    - cron: '0 1 * * *'\n\njobs:\n  stale:\n    if: github.repository == 'vbenjs/vue-vben-admin'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v9\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          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'\n          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'\n          exempt-issue-labels: 'bug,enhancement'\n          days-before-stale: 60\n          days-before-close: 7\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\ndist.zip\ndist.tar\ndist.war\n.nitro\n.output\n*-dist.zip\n*-dist.tar\n*-dist.war\ncoverage\n*.local\n**/.vitepress/cache\n.cache\n.turbo\n.temp\ndev-dist\n.stylelintcache\nyarn.lock\npackage-lock.json\n.VSCodeCounter\n**/backend-mock/data\n\n# local env files\n.env.local\n.env.*.local\n.eslintcache\n\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\nvite.config.mts.*\nvite.config.mjs.*\nvite.config.js.*\nvite.config.ts.*\n\n# Editor directories and files\n.idea\n# .vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n.history\n.cursor\n\n# AI\n.agent\n.agents\n.claude\n.codex\nskills-lock.json\n"
  },
  {
    "path": ".gitpod.yml",
    "content": "ports:\n  - port: 5555\n    onOpen: open-preview\ntasks:\n  - init: npm i -g corepack && pnpm install\n    command: pnpm run dev:play\n"
  },
  {
    "path": ".node-version",
    "content": "22.22.0\n"
  },
  {
    "path": ".npmrc",
    "content": "registry=https://registry.npmmirror.com\npublic-hoist-pattern[]=lefthook\npublic-hoist-pattern[]=eslint\npublic-hoist-pattern[]=oxfmt\npublic-hoist-pattern[]=oxlint\npublic-hoist-pattern[]=stylelint\npublic-hoist-pattern[]=*postcss*\npublic-hoist-pattern[]=@commitlint/*\npublic-hoist-pattern[]=czg\n\nstrict-peer-dependencies=false\nauto-install-peers=true\ndedupe-peer-dependents=true\n"
  },
  {
    "path": ".stylelintignore",
    "content": "dist\npublic\n__tests__\ncoverage\n.codex\n.claude\n.agent\n.agents\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    // Vue 3 的语言支持\n    \"Vue.volar\",\n    // 将 eslint 集成到 VS Code 中。\n    \"dbaeumer.vscode-eslint\",\n    // 将 oxlint 集成到 VS Code 中。\n    \"oxc.oxc-vscode\",\n    // Visual Studio Code 的官方 Stylelint 扩展\n    \"stylelint.vscode-stylelint\",\n    // 使用 oxfmt 的代码格式化程序\n    \"oxc.oxc-vscode\",\n    // 支持 dotenv 文件语法\n    \"mikestead.dotenv\",\n    // YAML 语言支持，供 ESLint 校验 pnpm-workspace.yaml 等文件\n    \"redhat.vscode-yaml\",\n    // 源代码的拼写检查器\n    \"streetsidesoftware.code-spell-checker\",\n    // Tailwind CSS 的官方 VS Code 插件\n    \"bradlc.vscode-tailwindcss\",\n    // iconify 图标插件\n    \"antfu.iconify\",\n    // i18n 插件\n    \"Lokalise.i18n-ally\",\n    // CSS 变量提示\n    \"vunguyentuan.vscode-css-variables\",\n    // 在 package.json 中显示 PNPM catalog 的版本\n    \"antfu.pnpm-catalog-lens\"\n  ],\n  \"unwantedRecommendations\": [\n    // 和 volar 冲突\n    \"octref.vetur\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/global.code-snippets",
    "content": "{\n  \"import\": {\n    \"scope\": \"javascript,typescript\",\n    \"prefix\": \"im\",\n    \"body\": [\"import { $2 } from '$1';\"],\n    \"description\": \"Import a module\",\n  },\n  \"export-all\": {\n    \"scope\": \"javascript,typescript\",\n    \"prefix\": \"ex\",\n    \"body\": [\"export * from '$1';\"],\n    \"description\": \"Export a module\",\n  },\n  \"vue-script-setup\": {\n    \"scope\": \"vue\",\n    \"prefix\": \"<sc\",\n    \"body\": [\n      \"<script setup lang=\\\"ts\\\">\",\n      \"const props = defineProps<{\",\n      \"  modelValue?: boolean,\",\n      \"}>()\",\n      \"$1\",\n      \"</script>\",\n      \"\",\n      \"<template>\",\n      \"  <div>\",\n      \"    <slot/>\",\n      \"  </div>\",\n      \"</template>\",\n    ],\n  },\n  \"vue-computed\": {\n    \"scope\": \"javascript,typescript,vue\",\n    \"prefix\": \"com\",\n    \"body\": [\"computed(() => { $1 })\"],\n  },\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/launchsettings.json\",\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"chrome\",\n      \"name\": \"vben admin playground dev\",\n      \"request\": \"launch\",\n      \"url\": \"http://localhost:5555\",\n      \"env\": { \"NODE_ENV\": \"development\" },\n      \"sourceMaps\": true,\n      \"webRoot\": \"${workspaceFolder}/playground\"\n    },\n    {\n      \"type\": \"chrome\",\n      \"name\": \"vben admin antd dev\",\n      \"request\": \"launch\",\n      \"url\": \"http://localhost:5666\",\n      \"env\": { \"NODE_ENV\": \"development\" },\n      \"sourceMaps\": true,\n      \"webRoot\": \"${workspaceFolder}/apps/web-antd\"\n    },\n    {\n      \"type\": \"chrome\",\n      \"name\": \"vben admin ele dev\",\n      \"request\": \"launch\",\n      \"url\": \"http://localhost:5777\",\n      \"env\": { \"NODE_ENV\": \"development\" },\n      \"sourceMaps\": true,\n      \"webRoot\": \"${workspaceFolder}/apps/web-ele\"\n    },\n    {\n      \"type\": \"chrome\",\n      \"name\": \"vben admin naive dev\",\n      \"request\": \"launch\",\n      \"url\": \"http://localhost:5888\",\n      \"env\": { \"NODE_ENV\": \"development\" },\n      \"sourceMaps\": true,\n      \"webRoot\": \"${workspaceFolder}/apps/web-naive\"\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"tailwindCSS.experimental.configFile\": \"internal/tailwind-config/src/theme.css\",\n  \"tailwindCSS.lint.suggestCanonicalClasses\": \"ignore\",\n  // workbench\n  \"workbench.list.smoothScrolling\": true,\n  \"workbench.startupEditor\": \"newUntitledFile\",\n  \"workbench.tree.indent\": 10,\n  \"workbench.editor.highlightModifiedTabs\": true,\n  \"workbench.editor.closeOnFileDelete\": true,\n  \"workbench.editor.limit.enabled\": true,\n  \"workbench.editor.limit.perEditorGroup\": true,\n  \"workbench.editor.limit.value\": 5,\n\n  // editor\n  \"editor.tabSize\": 2,\n  \"editor.detectIndentation\": false,\n  \"editor.cursorBlinking\": \"expand\",\n  \"editor.largeFileOptimizations\": true,\n  \"editor.accessibilitySupport\": \"off\",\n  \"editor.cursorSmoothCaretAnimation\": \"on\",\n  \"editor.guides.bracketPairs\": \"active\",\n  \"editor.inlineSuggest.enabled\": true,\n  \"editor.suggestSelection\": \"recentlyUsedByPrefix\",\n  \"editor.acceptSuggestionOnEnter\": \"smart\",\n  \"editor.suggest.snippetsPreventQuickSuggestions\": false,\n  \"editor.stickyScroll.enabled\": true,\n  \"editor.hover.sticky\": true,\n  \"editor.suggest.insertMode\": \"replace\",\n  \"editor.bracketPairColorization.enabled\": true,\n  \"editor.autoClosingBrackets\": \"beforeWhitespace\",\n  \"editor.autoClosingDelete\": \"always\",\n  \"editor.autoClosingOvertype\": \"always\",\n  \"editor.autoClosingQuotes\": \"beforeWhitespace\",\n  \"editor.wordSeparators\": \"`~!@#%^&*()=+[{]}\\\\|;:'\\\",.<>/?\",\n  \"editor.quickSuggestions\": {\n    \"strings\": \"on\"\n  },\n\n  // lint && format\n  \"oxc.enable\": true,\n  \"oxc.typeAware\": true,\n  \"oxc.configPath\": \"oxlint.config.ts\",\n  \"oxc.fmt.configPath\": \"oxfmt.config.ts\",\n  \"eslint.useFlatConfig\": true,\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\",\n    \"source.fixAll.oxc\": \"explicit\",\n    \"source.fixAll.stylelint\": \"explicit\",\n    \"source.organizeImports\": \"never\"\n  },\n  \"editor.defaultFormatter\": \"oxc.oxc-vscode\",\n  \"[html]\": {\n    \"editor.defaultFormatter\": \"oxc.oxc-vscode\"\n  },\n  \"[css]\": {\n    \"editor.defaultFormatter\": \"oxc.oxc-vscode\"\n  },\n  \"[scss]\": {\n    \"editor.defaultFormatter\": \"oxc.oxc-vscode\"\n  },\n  \"[javascript]\": {\n    \"editor.defaultFormatter\": \"oxc.oxc-vscode\"\n  },\n  \"[typescript]\": {\n    \"editor.defaultFormatter\": \"oxc.oxc-vscode\"\n  },\n  \"[json]\": {\n    \"editor.defaultFormatter\": \"oxc.oxc-vscode\"\n  },\n  \"[markdown]\": {\n    \"editor.defaultFormatter\": \"oxc.oxc-vscode\"\n  },\n  \"[jsonc]\": {\n    \"editor.defaultFormatter\": \"oxc.oxc-vscode\"\n  },\n  \"[vue]\": {\n    \"editor.defaultFormatter\": \"oxc.oxc-vscode\"\n  },\n\n  // extensions\n  \"extensions.ignoreRecommendations\": true,\n\n  // terminal\n  \"terminal.integrated.cursorBlinking\": true,\n  \"terminal.integrated.persistentSessionReviveProcess\": \"never\",\n  \"terminal.integrated.tabs.enabled\": true,\n  \"terminal.integrated.scrollback\": 10000,\n  \"terminal.integrated.stickyScroll.enabled\": true,\n\n  // files\n  \"files.eol\": \"\\n\",\n  \"files.insertFinalNewline\": true,\n  \"files.simpleDialog.enable\": true,\n  \"files.associations\": {\n    \"*.css\": \"tailwindcss\",\n    \"*.ejs\": \"html\",\n    \"*.art\": \"html\",\n    \"**/tsconfig.json\": \"jsonc\",\n    \"*.json\": \"jsonc\",\n    \"package.json\": \"json\"\n  },\n\n  \"files.exclude\": {\n    \"**/.eslintcache\": true,\n    \"**/bower_components\": true,\n    \"**/.turbo\": true,\n    \"**/.idea\": true,\n    \"**/.vitepress\": true,\n    \"**/tmp\": true,\n    \"**/.git\": true,\n    \"**/.svn\": true,\n    \"**/.hg\": true,\n    \"**/CVS\": true,\n    \"**/.stylelintcache\": true,\n    \"**/.DS_Store\": true,\n    \"**/vite.config.mts.*\": true,\n    \"**/tea.yaml\": true\n  },\n  \"files.watcherExclude\": {\n    \"**/.git/objects/**\": true,\n    \"**/.git/subtree-cache/**\": true,\n    \"**/.vscode/**\": true,\n    \"**/node_modules/**\": true,\n    \"**/tmp/**\": true,\n    \"**/bower_components/**\": true,\n    \"**/dist/**\": true,\n    \"**/yarn.lock\": true\n  },\n\n  \"typescript.tsserver.exclude\": [\"**/node_modules\", \"**/dist\", \"**/.turbo\"],\n\n  // search\n  \"search.searchEditor.singleClickBehaviour\": \"peekDefinition\",\n  \"search.followSymlinks\": false,\n  // 使用搜索功能时，将这些文件和文件夹排除在外\n  \"search.exclude\": {\n    \"**/node_modules\": true,\n    \"**/*.log\": true,\n    \"**/*.log*\": true,\n    \"**/bower_components\": true,\n    \"**/dist\": true,\n    \"**/elehukouben\": true,\n    \"**/.git\": true,\n    \"**/.github\": true,\n    \"**/.gitignore\": true,\n    \"**/.svn\": true,\n    \"**/.DS_Store\": true,\n    \"**/.vitepress/cache\": true,\n    \"**/.idea\": true,\n    \"**/.vscode\": false,\n    \"**/.yarn\": true,\n    \"**/tmp\": true,\n    \"*.xml\": true,\n    \"out\": true,\n    \"dist\": true,\n    \"node_modules\": true,\n    \"CHANGELOG.md\": true,\n    \"**/pnpm-lock.yaml\": true,\n    \"**/yarn.lock\": true\n  },\n\n  \"debug.onTaskErrors\": \"debugAnyway\",\n  \"diffEditor.ignoreTrimWhitespace\": false,\n  \"npm.packageManager\": \"pnpm\",\n\n  \"css.validate\": false,\n  \"less.validate\": false,\n  \"scss.validate\": false,\n\n  // extension\n  \"emmet.showSuggestionsAsSnippets\": true,\n  \"emmet.triggerExpansionOnTab\": false,\n\n  \"errorLens.enabledDiagnosticLevels\": [\"warning\", \"error\"],\n  \"errorLens.excludeBySource\": [\"cSpell\", \"Grammarly\"],\n\n  \"stylelint.enable\": true,\n  \"stylelint.packageManager\": \"pnpm\",\n  \"stylelint.validate\": [\"css\", \"less\", \"postcss\", \"scss\", \"vue\"],\n  \"stylelint.customSyntax\": \"postcss-html\",\n  \"stylelint.snippet\": [\"css\", \"less\", \"postcss\", \"scss\", \"vue\"],\n\n  \"typescript.inlayHints.enumMemberValues.enabled\": true,\n  \"typescript.preferences.preferTypeOnlyAutoImports\": true,\n  \"typescript.preferences.includePackageJsonAutoImports\": \"on\",\n\n  \"eslint.validate\": [\n    \"javascript\",\n    \"typescript\",\n    \"javascriptreact\",\n    \"typescriptreact\",\n    \"vue\",\n    \"html\",\n    \"markdown\",\n    \"json\",\n    \"jsonc\",\n    \"json5\",\n    \"yaml\"\n  ],\n\n  \"tailwindCSS.experimental.classRegex\": [\n    [\"cva\\\\(([^)]*)\\\\)\", \"[\\\"'`]([^\\\"'`]*).*?[\\\"'`]\"]\n  ],\n\n  \"github.copilot.enable\": {\n    \"*\": true,\n    \"markdown\": true,\n    \"plaintext\": false,\n    \"yaml\": false\n  },\n\n  \"cssVariables.lookupFiles\": [\"packages/@core/base/design/src/**/*.css\"],\n\n  \"i18n-ally.localesPaths\": [\n    \"packages/locales/src/langs\",\n    \"playground/src/locales/langs\",\n    \"apps/*/src/locales/langs\"\n  ],\n  \"i18n-ally.pathMatcher\": \"{locale}/{namespace}.{ext}\",\n  \"i18n-ally.enabledParsers\": [\"json\"],\n  \"i18n-ally.sourceLanguage\": \"en\",\n  \"i18n-ally.displayLanguage\": \"zh-CN\",\n  \"i18n-ally.enabledFrameworks\": [\"vue\", \"react\"],\n  \"i18n-ally.keystyle\": \"nested\",\n  \"i18n-ally.sortKeys\": true,\n  \"i18n-ally.namespace\": true,\n\n  // 控制相关文件嵌套展示\n  \"explorer.fileNesting.enabled\": true,\n  \"explorer.fileNesting.expand\": false,\n  \"explorer.fileNesting.patterns\": {\n    \"*.ts\": \"$(capture).test.ts, $(capture).test.tsx, $(capture).spec.ts, $(capture).spec.tsx, $(capture).d.ts\",\n    \"*.tsx\": \"$(capture).test.ts, $(capture).test.tsx, $(capture).spec.ts, $(capture).spec.tsx,$(capture).d.ts\",\n    \"*.env\": \"$(capture).env.*\",\n    \"README.md\": \"README*,CHANGELOG*,LICENSE,CNAME\",\n    \"package.json\": \"pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json\",\n    \"oxlint.config.ts\": \".eslintignore,.stylelintignore,.commitlintrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml,oxfmt.config.*,eslint.config.*\"\n  },\n  \"commentTranslate.hover.enabled\": false,\n  \"commentTranslate.multiLineMerge\": true,\n  \"vue.server.hybridMode\": true,\n  \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024-present, Vben\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "README.ja-JP.md",
    "content": "<div align=\"center\">\n  <a href=\"https://github.com/anncwb/vue-vben-admin\">\n    <img alt=\"VbenAdmin Logo\" width=\"215\" src=\"https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp\">\n  </a>\n  <br>\n  <br>\n\n[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)\n\n  <h1>Vue Vben Admin</h1>\n</div>\n\n[![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)\n\n**日本語** | [English](./README.md) | [中文](./README.zh-CN.md)\n\n## 紹介\n\nVue Vben Adminは、最新の`vue3`、`vite`、`TypeScript`などの主流技術を使用して開発された、無料でオープンソースの中・後端テンプレートです。すぐに使える中・後端のフロントエンドソリューションとして、学習の参考にもなります。\n\n## アップグレード通知\n\nこれは最新バージョン `5.0` であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。\n\n## 特徴\n\n- **最新技術スタック**：Vue 3やViteなどの最先端フロントエンド技術で開発\n- **TypeScript**：アプリケーション規模のJavaScriptのための言語\n- **テーマ**：複数のテーマカラーが利用可能で、カスタマイズオプションも豊富\n- **国際化**：完全な内蔵国際化サポート\n- **権限管理**：動的ルートベースの権限生成ソリューションを内蔵\n\n## プレビュー\n\n- [Vben Admin](https://vben.pro/) - フルバージョンの中国語サイト\n\nテストアカウント：vben/123456\n\n<div align=\"center\">\n  <img alt=\"VbenAdmin Logo\" width=\"100%\" src=\"https://anncwb.github.io/anncwb/images/preview1.png\">\n  <img alt=\"VbenAdmin Logo\" width=\"100%\" src=\"https://anncwb.github.io/anncwb/images/preview2.png\">\n  <img alt=\"VbenAdmin Logo\" width=\"100%\" src=\"https://anncwb.github.io/anncwb/images/preview3.png\">\n</div>\n\n### Gitpodを使用\n\nGitpod（GitHub用の無料オンライン開発環境）でプロジェクトを開き、すぐにコーディングを開始します。\n\n[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)\n\n## ドキュメント\n\n[ドキュメント](https://doc.vben.pro/)\n\n## インストールと使用\n\n1. プロジェクトコードを取得\n\n```bash\ngit clone https://github.com/vbenjs/vue-vben-admin.git\n```\n\n2. 依存関係のインストール\n\n```bash\ncd vue-vben-admin\nnpm i -g corepack\npnpm install\n```\n\n3. 実行\n\n```bash\npnpm dev\n```\n\n4. ビルド\n\n```bash\npnpm build\n```\n\n## 変更ログ\n\n[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)\n\n## 貢献方法\n\nご参加をお待ちしております！[Issueを提出](https://github.com/anncwb/vue-vben-admin/issues/new/choose)するか、Pull Requestを送信してください。\n\n**Pull Request プロセス：**\n\n1. コードをフォーク\n2. 自分のブランチを作成：`git checkout -b feat/xxxx`\n3. 変更をコミット：`git commit -am 'feat(function): add xxxxx'`\n4. ブランチをプッシュ：`git push origin feat/xxxx`\n5. `pull request`を送信\n\n## Git貢献提出規則\n\n参考 [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))\n\n- `feat` 新機能の追加\n- `fix` 問題/バグの修正\n- `style` コードスタイルに関連し、実行結果に影響しない\n- `perf` 最適化/パフォーマンス向上\n- `refactor` リファクタリング\n- `revert` 変更の取り消し\n- `test` テスト関連\n- `docs` ドキュメント/注釈\n- `chore` 依存関係の更新/スキャフォールディング設定の変更など\n- `ci` 継続的インテグレーション\n- `types` 型定義ファイルの変更\n\n## ブラウザサポート\n\nローカル開発には `Chrome 80+` ブラウザを推奨します\n\nモダンブラウザをサポートし、IEはサポートしません\n\n| [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png\" alt=\"Edge\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png\" alt=\"Firefox\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png\" alt=\"Chrome\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png\" alt=\"Safari\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Safari |\n| :-: | :-: | :-: | :-: |\n| 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン |\n\n## メンテナー\n\n[@Vben](https://github.com/anncwb)\n\n## スター歴史\n\n[![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)\n\n## 寄付\n\nこのプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます！\n\n![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png)\n\n<a style=\"display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;\" href=\"https://www.paypal.com/paypalme/cvvben\">Paypal Me</a>\n\n## 貢献者\n\n<a href=\"https://openomy.app/github/vbenjs/vue-vben-admin\" target=\"_blank\" style=\"display: block; width: 100%;\" align=\"center\">\n  <img src=\"https://openomy.app/svg?repo=vbenjs/vue-vben-admin&chart=bubble&latestMonth=3\" target=\"_blank\" alt=\"Contribution Leaderboard\" style=\"display: block; width: 100%;\" />\n </a>\n\n<a href=\"https://github.com/vbenjs/vue-vben-admin/graphs/contributors\">\n  <img alt=\"Contributors\" src=\"https://contrib.rocks/image?repo=vbenjs/vue-vben-admin\" />\n</a>\n\n## Discord\n\n- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)\n\n## ライセンス\n\n[MIT © Vben-2020](./LICENSE)\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <a href=\"https://github.com/anncwb/vue-vben-admin\">\n    <img alt=\"VbenAdmin Logo\" width=\"215\" src=\"https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp\">\n  </a>\n  <br>\n  <br>\n\n[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)\n\n  <h1>Vue Vben Admin</h1>\n</div>\n\n[![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)](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml) [![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg)](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml) [![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg)](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml) [![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg)](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml)\n\n**English** | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md)\n\n## Introduction\n\nVue 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.\n\n## Upgrade Notice\n\nThis 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).\n\n## Features\n\n- **Latest Technology Stack**: Developed with cutting-edge front-end technologies like Vue 3 and Vite\n- **TypeScript**: A language for application-scale JavaScript\n- **Themes**: Multiple theme colors available with customizable options\n- **Internationalization**: Comprehensive built-in internationalization support\n- **Permissions**: Built-in solution for dynamic route-based permission generation\n\n## Preview\n\n- [Vben Admin](https://vben.pro/) - Full version Chinese site\n\nTest Account: vben/123456\n\n<div align=\"center\">\n  <img alt=\"VbenAdmin Logo\" width=\"100%\" src=\"https://anncwb.github.io/anncwb/images/preview1.png\">\n  <img alt=\"VbenAdmin Logo\" width=\"100%\" src=\"https://anncwb.github.io/anncwb/images/preview2.png\">\n  <img alt=\"VbenAdmin Logo\" width=\"100%\" src=\"https://anncwb.github.io/anncwb/images/preview3.png\">\n</div>\n\n### Use Gitpod\n\nOpen the project in Gitpod (free online dev environment for GitHub) and start coding immediately.\n\n[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)\n\n## Documentation\n\n[Document](https://doc.vben.pro/)\n\n## Install and Use\n\n1. Get the project code\n\n```bash\ngit clone https://github.com/vbenjs/vue-vben-admin.git\n```\n\n2. Install dependencies\n\n```bash\ncd vue-vben-admin\nnpm i -g corepack\npnpm install\n```\n\n3. Run\n\n```bash\npnpm dev\n```\n\n4. Build\n\n```bash\npnpm build\n```\n\n## Change Log\n\n[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)\n\n## How to Contribute\n\nYou are very welcome to join! [Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) or submit a Pull Request.\n\n**Pull Request Process:**\n\n1. Fork the code\n2. Create your branch: `git checkout -b feat/xxxx`\n3. Submit your changes: `git commit -am 'feat(function): add xxxxx'`\n4. Push your branch: `git push origin feat/xxxx`\n5. Submit `pull request`\n\n## Git Contribution Submission Specification\n\nReference [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))\n\n- `feat` Add new features\n- `fix` Fix the problem/BUG\n- `style` The code style is related and does not affect the running result\n- `perf` Optimization/performance improvement\n- `refactor` Refactor\n- `revert` Undo edit\n- `test` Test related\n- `docs` Documentation/notes\n- `chore` Dependency update/scaffolding configuration modification etc.\n- `ci` Continuous integration\n- `types` Type definition file changes\n\n## Browser Support\n\nThe `Chrome 80+` browser is recommended for local development\n\nSupport modern browsers, not IE\n\n| [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png\" alt=\"Edge\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png\" alt=\"Firefox\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png\" alt=\"Chrome\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png\" alt=\"Safari\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Safari |\n| :-: | :-: | :-: | :-: |\n| last 2 versions | last 2 versions | last 2 versions | last 2 versions |\n\n## Maintainer\n\n[@Vben](https://github.com/anncwb)\n\n## Star History\n\n[![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)\n\n## Donate\n\nIf you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!\n\n![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png)\n\n<a style=\"display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aee;border-radius: 4px;\" href=\"https://www.paypal.com/paypalme/cvvben\">Paypal Me</a>\n\n## Contributors\n\n<a href=\"https://openomy.app/github/vbenjs/vue-vben-admin\" target=\"_blank\" style=\"display: block; width: 100%;\" align=\"center\">\n  <img src=\"https://openomy.app/svg?repo=vbenjs/vue-vben-admin&chart=bubble&latestMonth=3\" target=\"_blank\" alt=\"Contribution Leaderboard\" style=\"display: block; width: 100%;\" />\n </a>\n\n<a href=\"https://github.com/vbenjs/vue-vben-admin/graphs/contributors\">\n  <img alt=\"Contributors\" src=\"https://contrib.rocks/image?repo=vbenjs/vue-vben-admin\" />\n</a>\n\n## Discord\n\n- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)\n\n## License\n\n[MIT © Vben-2020](./LICENSE)\n"
  },
  {
    "path": "README.zh-CN.md",
    "content": "<div align=\"center\">\n  <a href=\"https://github.com/anncwb/vue-vben-admin\">\n    <img alt=\"VbenAdmin Logo\" width=\"215\" src=\"https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp\">\n  </a>\n  <br>\n  <br>\n\n[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)\n\n  <h1>Vue Vben Admin</h1>\n</div>\n\n[![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)\n\n**中文** | [English](./README.md) | [日本語](./README.ja-JP.md)\n\n## 简介\n\nVue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的中后台模板，它采用了最新的 Vue 3、Vite、TypeScript 等主流技术开发，开箱即用，可用于中后台前端开发，也适合学习参考。\n\n## 升级提示\n\n该版本为最新版本 `5.0`，与其他版本不兼容，如果你是新项目，建议使用最新版本。如果你想查看旧版本，请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2)\n\n## 特性\n\n- **最新技术栈**：使用 Vue3/vite 等前端前沿技术开发\n- **TypeScript**：应用程序级 JavaScript 的语言\n- **主题**：提供多套主题色彩，可配置自定义主题\n- **国际化**：内置完善的国际化方案\n- **权限**：内置完善的动态路由权限生成方案\n\n## 预览\n\n- [Vben Admin](https://vben.pro/) - 完整版中文站点\n\n测试账号：vben/123456\n\n<div align=\"center\">\n  <img alt=\"VbenAdmin Logo\" width=\"100%\" src=\"https://anncwb.github.io/anncwb/images/preview1.png\">\n  <img alt=\"VbenAdmin Logo\" width=\"100%\" src=\"https://anncwb.github.io/anncwb/images/preview2.png\">\n  <img alt=\"VbenAdmin Logo\" width=\"100%\" src=\"https://anncwb.github.io/anncwb/images/preview3.png\">\n</div>\n\n### 使用 Gitpod\n\n在 Gitpod（适用于 GitHub 的免费在线开发环境）中打开项目，并立即开始编码。\n\n[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)\n\n## 文档\n\n[文档地址](https://doc.vben.pro/)\n\n## 安装使用\n\n1. 获取项目代码\n\n```bash\ngit clone https://github.com/vbenjs/vue-vben-admin.git\n```\n\n2. 安装依赖\n\n```bash\ncd vue-vben-admin\nnpm i -g corepack\npnpm install\n```\n\n3. 运行\n\n```bash\npnpm dev\n```\n\n4. 打包\n\n```bash\npnpm build\n```\n\n## 更新日志\n\n[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)\n\n## 如何贡献\n\n非常欢迎你的加入！[提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。\n\n**Pull Request 流程：**\n\n1. Fork 代码\n2. 创建自己的分支：`git checkout -b feature/xxxx`\n3. 提交你的修改：`git commit -am 'feat(function): add xxxxx'`\n4. 推送您的分支：`git push origin feature/xxxx`\n5. 提交 `pull request`\n\n## Git 贡献提交规范\n\n参考 [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))\n\n- `feat` 增加新功能\n- `fix` 修复问题/BUG\n- `style` 代码风格相关无影响运行结果的\n- `perf` 优化/性能提升\n- `refactor` 重构\n- `revert` 撤销修改\n- `test` 测试相关\n- `docs` 文档/注释\n- `chore` 依赖更新/脚手架配置修改等\n- `ci` 持续集成\n- `types` 类型定义文件更改\n\n## 浏览器支持\n\n本地开发推荐使用 `Chrome 80+` 浏览器\n\n支持现代浏览器，不支持 IE\n\n| [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png\" alt=\"Edge\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png\" alt=\"Firefox\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png\" alt=\"Chrome\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png\" alt=\"Safari\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)</br>Safari |\n| :-: | :-: | :-: | :-: |\n| last 2 versions | last 2 versions | last 2 versions | last 2 versions |\n\n## 维护者\n\n[@Vben](https://github.com/anncwb)\n\n## Star 历史\n\n[![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)\n\n## 捐赠\n\n如果你觉得这个项目对你有帮助，你可以帮作者买一杯咖啡表示支持！\n\n![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png)\n\n<a style=\"display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;\" href=\"https://www.paypal.com/paypalme/cvvben\">Paypal Me</a>\n\n## 贡献者\n\n<a href=\"https://openomy.app/github/vbenjs/vue-vben-admin\" target=\"_blank\" style=\"display: block; width: 100%;\" align=\"center\">\n  <img src=\"https://openomy.app/svg?repo=vbenjs/vue-vben-admin&chart=bubble&latestMonth=3\" target=\"_blank\" alt=\"Contribution Leaderboard\" style=\"display: block; width: 100%;\" />\n </a>\n\n<a href=\"https://github.com/vbenjs/vue-vben-admin/graphs/contributors\">\n  <img alt=\"Contributors\" src=\"https://contrib.rocks/image?repo=vbenjs/vue-vben-admin\" />\n</a>\n\n## Discord\n\n- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)\n\n## 许可证\n\n[MIT © Vben-2020](./LICENSE)\n"
  },
  {
    "path": "apps/backend-mock/README.md",
    "content": "# @vben/backend-mock\n\n## Description\n\nVben Admin 数据 mock 服务，没有对接任何的数据库，所有数据都是模拟的，用于前端开发时提供数据支持。线上环境不再提供 mock 集成，可自行部署服务或者对接真实数据，由于 `mock.js` 等工具有一些限制，比如上传文件不行、无法模拟复杂的逻辑等，所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务，但是这样可以更好的模拟真实环境。该服务不需要手动启动，已经集成在 vite 插件内，随应用一起启用。\n\n## Running the app\n\n```bash\n# development\n$ pnpm run start\n\n# production mode\n$ pnpm run build\n```\n"
  },
  {
    "path": "apps/backend-mock/api/auth/codes.ts",
    "content": "import { eventHandler } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { MOCK_CODES } from '~/utils/mock-data';\nimport { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';\n\nexport default eventHandler((event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n\n  const codes =\n    MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? [];\n\n  return useResponseSuccess(codes);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/auth/login.post.ts",
    "content": "import { defineEventHandler, readBody, setResponseStatus } from 'h3';\nimport {\n  clearRefreshTokenCookie,\n  setRefreshTokenCookie,\n} from '~/utils/cookie-utils';\nimport { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils';\nimport { MOCK_USERS } from '~/utils/mock-data';\nimport {\n  forbiddenResponse,\n  useResponseError,\n  useResponseSuccess,\n} from '~/utils/response';\n\nexport default defineEventHandler(async (event) => {\n  const { password, username } = await readBody(event);\n  if (!password || !username) {\n    setResponseStatus(event, 400);\n    return useResponseError(\n      'BadRequestException',\n      'Username and password are required',\n    );\n  }\n\n  const findUser = MOCK_USERS.find(\n    (item) => item.username === username && item.password === password,\n  );\n\n  if (!findUser) {\n    clearRefreshTokenCookie(event);\n    return forbiddenResponse(event, 'Username or password is incorrect.');\n  }\n\n  const accessToken = generateAccessToken(findUser);\n  const refreshToken = generateRefreshToken(findUser);\n\n  setRefreshTokenCookie(event, refreshToken);\n\n  return useResponseSuccess({\n    ...findUser,\n    accessToken,\n  });\n});\n"
  },
  {
    "path": "apps/backend-mock/api/auth/logout.post.ts",
    "content": "import { defineEventHandler } from 'h3';\nimport {\n  clearRefreshTokenCookie,\n  getRefreshTokenFromCookie,\n} from '~/utils/cookie-utils';\nimport { useResponseSuccess } from '~/utils/response';\n\nexport default defineEventHandler(async (event) => {\n  const refreshToken = getRefreshTokenFromCookie(event);\n  if (!refreshToken) {\n    return useResponseSuccess('');\n  }\n\n  clearRefreshTokenCookie(event);\n\n  return useResponseSuccess('');\n});\n"
  },
  {
    "path": "apps/backend-mock/api/auth/refresh.post.ts",
    "content": "import { defineEventHandler } from 'h3';\nimport {\n  clearRefreshTokenCookie,\n  getRefreshTokenFromCookie,\n  setRefreshTokenCookie,\n} from '~/utils/cookie-utils';\nimport { generateAccessToken, verifyRefreshToken } from '~/utils/jwt-utils';\nimport { MOCK_USERS } from '~/utils/mock-data';\nimport { forbiddenResponse } from '~/utils/response';\n\nexport default defineEventHandler(async (event) => {\n  const refreshToken = getRefreshTokenFromCookie(event);\n  if (!refreshToken) {\n    return forbiddenResponse(event);\n  }\n\n  clearRefreshTokenCookie(event);\n\n  const userinfo = verifyRefreshToken(refreshToken);\n  if (!userinfo) {\n    return forbiddenResponse(event);\n  }\n\n  const findUser = MOCK_USERS.find(\n    (item) => item.username === userinfo.username,\n  );\n  if (!findUser) {\n    return forbiddenResponse(event);\n  }\n  const accessToken = generateAccessToken(findUser);\n\n  setRefreshTokenCookie(event, refreshToken);\n\n  return accessToken;\n});\n"
  },
  {
    "path": "apps/backend-mock/api/demo/bigint.ts",
    "content": "import { eventHandler, setHeader } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { unAuthorizedResponse } from '~/utils/response';\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n  const data = `\n  {\n    \"code\": 0,\n    \"message\": \"success\",\n    \"data\": [\n              {\n                \"id\": 123456789012345678901234567890123456789012345678901234567890,\n                \"name\": \"John Doe\",\n                \"age\": 30,\n                \"email\": \"john-doe@demo.com\"\n                },\n                {\n                \"id\": 987654321098765432109876543210987654321098765432109876543210,\n                \"name\": \"Jane Smith\",\n                \"age\": 25,\n                \"email\": \"jane@demo.com\"\n                }\n            ]\n  }\n  `;\n  setHeader(event, 'Content-Type', 'application/json');\n  return data;\n});\n"
  },
  {
    "path": "apps/backend-mock/api/menu/all.ts",
    "content": "import { eventHandler } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { MOCK_MENUS } from '~/utils/mock-data';\nimport { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n\n  const menus =\n    MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? [];\n  return useResponseSuccess(menus);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/status.ts",
    "content": "import { eventHandler, getQuery, setResponseStatus } from 'h3';\nimport { useResponseError } from '~/utils/response';\n\nexport default eventHandler((event) => {\n  const { status } = getQuery(event);\n  setResponseStatus(event, Number(status));\n  return useResponseError(`${status}`);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/system/dept/.post.ts",
    "content": "import { eventHandler } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport {\n  sleep,\n  unAuthorizedResponse,\n  useResponseSuccess,\n} from '~/utils/response';\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n  await sleep(600);\n  return useResponseSuccess(null);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/system/dept/[id].delete.ts",
    "content": "import { eventHandler } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport {\n  sleep,\n  unAuthorizedResponse,\n  useResponseSuccess,\n} from '~/utils/response';\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n  await sleep(1000);\n  return useResponseSuccess(null);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/system/dept/[id].put.ts",
    "content": "import { eventHandler } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport {\n  sleep,\n  unAuthorizedResponse,\n  useResponseSuccess,\n} from '~/utils/response';\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n  await sleep(2000);\n  return useResponseSuccess(null);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/system/dept/list.ts",
    "content": "import { faker } from '@faker-js/faker';\nimport { eventHandler } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';\n\nconst formatterCN = new Intl.DateTimeFormat('zh-CN', {\n  timeZone: 'Asia/Shanghai',\n  year: 'numeric',\n  month: '2-digit',\n  day: '2-digit',\n  hour: '2-digit',\n  minute: '2-digit',\n  second: '2-digit',\n});\n\nfunction generateMockDataList(count: number) {\n  const dataList = [];\n\n  for (let i = 0; i < count; i++) {\n    const dataItem: Record<string, any> = {\n      id: faker.string.uuid(),\n      pid: 0,\n      name: faker.commerce.department(),\n      status: faker.helpers.arrayElement([0, 1]),\n      createTime: formatterCN.format(\n        faker.date.between({ from: '2021-01-01', to: '2022-12-31' }),\n      ),\n      remark: faker.lorem.sentence(),\n    };\n    if (faker.datatype.boolean()) {\n      dataItem.children = Array.from(\n        { length: faker.number.int({ min: 1, max: 5 }) },\n        () => ({\n          id: faker.string.uuid(),\n          pid: dataItem.id,\n          name: faker.commerce.department(),\n          status: faker.helpers.arrayElement([0, 1]),\n          createTime: formatterCN.format(\n            faker.date.between({ from: '2023-01-01', to: '2023-12-31' }),\n          ),\n          remark: faker.lorem.sentence(),\n        }),\n      );\n    }\n    dataList.push(dataItem);\n  }\n\n  return dataList;\n}\n\nconst mockData = generateMockDataList(10);\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n\n  const listData = structuredClone(mockData);\n\n  return useResponseSuccess(listData);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/system/menu/list.ts",
    "content": "import { eventHandler } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { MOCK_MENU_LIST } from '~/utils/mock-data';\nimport { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n\n  return useResponseSuccess(MOCK_MENU_LIST);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/system/menu/name-exists.ts",
    "content": "import { eventHandler, getQuery } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { MOCK_MENU_LIST } from '~/utils/mock-data';\nimport { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';\n\nconst namesMap: Record<string, any> = {};\n\nfunction getNames(menus: any[]) {\n  menus.forEach((menu) => {\n    namesMap[menu.name] = String(menu.id);\n    if (menu.children) {\n      getNames(menu.children);\n    }\n  });\n}\ngetNames(MOCK_MENU_LIST);\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n  const { id, name } = getQuery(event);\n\n  return (name as string) in namesMap &&\n    (!id || namesMap[name as string] !== String(id))\n    ? useResponseSuccess(true)\n    : useResponseSuccess(false);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/system/menu/path-exists.ts",
    "content": "import { eventHandler, getQuery } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { MOCK_MENU_LIST } from '~/utils/mock-data';\nimport { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';\n\nconst pathMap: Record<string, any> = { '/': 0 };\n\nfunction getPaths(menus: any[]) {\n  menus.forEach((menu) => {\n    pathMap[menu.path] = String(menu.id);\n    if (menu.children) {\n      getPaths(menu.children);\n    }\n  });\n}\ngetPaths(MOCK_MENU_LIST);\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n  const { id, path } = getQuery(event);\n\n  return (path as string) in pathMap &&\n    (!id || pathMap[path as string] !== String(id))\n    ? useResponseSuccess(true)\n    : useResponseSuccess(false);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/system/role/list.ts",
    "content": "import { faker } from '@faker-js/faker';\nimport { eventHandler, getQuery } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data';\nimport { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';\n\nconst formatterCN = new Intl.DateTimeFormat('zh-CN', {\n  timeZone: 'Asia/Shanghai',\n  year: 'numeric',\n  month: '2-digit',\n  day: '2-digit',\n  hour: '2-digit',\n  minute: '2-digit',\n  second: '2-digit',\n});\n\nconst menuIds = getMenuIds(MOCK_MENU_LIST);\n\nfunction generateMockDataList(count: number) {\n  const dataList = [];\n\n  for (let i = 0; i < count; i++) {\n    const dataItem: Record<string, any> = {\n      id: faker.string.uuid(),\n      name: faker.commerce.product(),\n      status: faker.helpers.arrayElement([0, 1]),\n      createTime: formatterCN.format(\n        faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),\n      ),\n      permissions: faker.helpers.arrayElements(menuIds),\n      remark: faker.lorem.sentence(),\n    };\n\n    dataList.push(dataItem);\n  }\n\n  return dataList;\n}\n\nconst mockData = generateMockDataList(100);\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n\n  const {\n    page = 1,\n    pageSize = 20,\n    name,\n    id,\n    remark,\n    startTime,\n    endTime,\n    status,\n  } = getQuery(event);\n  let listData = structuredClone(mockData);\n  if (name) {\n    listData = listData.filter((item) =>\n      item.name.toLowerCase().includes(String(name).toLowerCase()),\n    );\n  }\n  if (id) {\n    listData = listData.filter((item) =>\n      item.id.toLowerCase().includes(String(id).toLowerCase()),\n    );\n  }\n  if (remark) {\n    listData = listData.filter((item) =>\n      item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),\n    );\n  }\n  if (startTime) {\n    listData = listData.filter((item) => item.createTime >= startTime);\n  }\n  if (endTime) {\n    listData = listData.filter((item) => item.createTime <= endTime);\n  }\n  if (['0', '1'].includes(status as string)) {\n    listData = listData.filter((item) => item.status === Number(status));\n  }\n  return usePageResponseSuccess(page as string, pageSize as string, listData);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/table/list.ts",
    "content": "import { faker } from '@faker-js/faker';\nimport { eventHandler, getQuery } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport {\n  sleep,\n  unAuthorizedResponse,\n  usePageResponseSuccess,\n} from '~/utils/response';\n\nfunction generateMockDataList(count: number) {\n  const dataList = [];\n\n  for (let i = 0; i < count; i++) {\n    const dataItem = {\n      id: faker.string.uuid(),\n      imageUrl: faker.image.avatar(),\n      imageUrl2: faker.image.avatar(),\n      open: faker.datatype.boolean(),\n      status: faker.helpers.arrayElement(['success', 'error', 'warning']),\n      productName: faker.commerce.productName(),\n      price: faker.commerce.price(),\n      currency: faker.finance.currencyCode(),\n      quantity: faker.number.int({ min: 1, max: 100 }),\n      available: faker.datatype.boolean(),\n      category: faker.commerce.department(),\n      releaseDate: faker.date.past(),\n      rating: faker.number.float({ min: 1, max: 5 }),\n      description: faker.commerce.productDescription(),\n      weight: faker.number.float({ min: 0.1, max: 10 }),\n      color: faker.color.human(),\n      inProduction: faker.datatype.boolean(),\n      tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()),\n    };\n\n    dataList.push(dataItem);\n  }\n\n  return dataList;\n}\n\nconst mockData = generateMockDataList(100);\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n\n  await sleep(600);\n\n  const { page, pageSize, sortBy, sortOrder } = getQuery(event);\n  // 规范化分页参数，处理 string[]\n  const pageRaw = Array.isArray(page) ? page[0] : page;\n  const pageSizeRaw = Array.isArray(pageSize) ? pageSize[0] : pageSize;\n  const pageNumber = Math.max(\n    1,\n    Number.parseInt(String(pageRaw ?? '1'), 10) || 1,\n  );\n  const pageSizeNumber = Math.min(\n    100,\n    Math.max(1, Number.parseInt(String(pageSizeRaw ?? '10'), 10) || 10),\n  );\n  const listData = structuredClone(mockData);\n\n  // 规范化 query 入参，兼容 string[]\n  const sortKeyRaw = Array.isArray(sortBy) ? sortBy[0] : sortBy;\n  const sortOrderRaw = Array.isArray(sortOrder) ? sortOrder[0] : sortOrder;\n  // 检查 sortBy 是否是 listData 元素的合法属性键\n  if (\n    typeof sortKeyRaw === 'string' &&\n    listData[0] &&\n    Object.prototype.hasOwnProperty.call(listData[0], sortKeyRaw)\n  ) {\n    // 定义数组元素的类型\n    type ItemType = (typeof listData)[0];\n    const sortKey = sortKeyRaw as keyof ItemType; // 将 sortBy 断言为合法键\n    const isDesc = sortOrderRaw === 'desc';\n    listData.sort((a, b) => {\n      const aValue = a[sortKey] as unknown;\n      const bValue = b[sortKey] as unknown;\n\n      let result: number;\n\n      if (typeof aValue === 'number' && typeof bValue === 'number') {\n        result = aValue - bValue;\n      } else if (aValue instanceof Date && bValue instanceof Date) {\n        result = aValue.getTime() - bValue.getTime();\n      } else if (typeof aValue === 'boolean' && typeof bValue === 'boolean') {\n        if (aValue === bValue) {\n          result = 0;\n        } else {\n          result = aValue ? 1 : -1;\n        }\n      } else {\n        const aStr = String(aValue);\n        const bStr = String(bValue);\n        const aNum = Number(aStr);\n        const bNum = Number(bStr);\n        result =\n          Number.isFinite(aNum) && Number.isFinite(bNum)\n            ? aNum - bNum\n            : aStr.localeCompare(bStr, undefined, {\n                numeric: true,\n                sensitivity: 'base',\n              });\n      }\n\n      return isDesc ? -result : result;\n    });\n  }\n\n  return usePageResponseSuccess(\n    String(pageNumber),\n    String(pageSizeNumber),\n    listData,\n  );\n});\n"
  },
  {
    "path": "apps/backend-mock/api/test.get.ts",
    "content": "import { defineEventHandler } from 'h3';\n\nexport default defineEventHandler(() => 'Test get handler');\n"
  },
  {
    "path": "apps/backend-mock/api/test.post.ts",
    "content": "import { defineEventHandler } from 'h3';\n\nexport default defineEventHandler(() => 'Test post handler');\n"
  },
  {
    "path": "apps/backend-mock/api/timezone/getTimezone.ts",
    "content": "import { eventHandler } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';\nimport { getTimezone } from '~/utils/timezone-utils';\n\nexport default eventHandler((event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n  return useResponseSuccess(getTimezone());\n});\n"
  },
  {
    "path": "apps/backend-mock/api/timezone/getTimezoneOptions.ts",
    "content": "import { eventHandler } from 'h3';\nimport { TIME_ZONE_OPTIONS } from '~/utils/mock-data';\nimport { useResponseSuccess } from '~/utils/response';\n\nexport default eventHandler(() => {\n  const data = TIME_ZONE_OPTIONS.map((o) => ({\n    label: `${o.timezone} (GMT${o.offset >= 0 ? `+${o.offset}` : o.offset})`,\n    value: o.timezone,\n  }));\n  return useResponseSuccess(data);\n});\n"
  },
  {
    "path": "apps/backend-mock/api/timezone/setTimezone.ts",
    "content": "import { eventHandler, readBody } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { TIME_ZONE_OPTIONS } from '~/utils/mock-data';\nimport { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';\nimport { setTimezone } from '~/utils/timezone-utils';\n\nexport default eventHandler(async (event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n  const body = await readBody<{ timezone?: unknown }>(event);\n  const timezone =\n    typeof body?.timezone === 'string' ? body.timezone : undefined;\n  const allowed = TIME_ZONE_OPTIONS.some((o) => o.timezone === timezone);\n  if (!timezone || !allowed) {\n    setResponseStatus(event, 400);\n    return useResponseError('Bad Request', 'Invalid timezone');\n  }\n  setTimezone(timezone);\n  return useResponseSuccess({});\n});\n"
  },
  {
    "path": "apps/backend-mock/api/upload.ts",
    "content": "import { eventHandler } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';\n\nexport default eventHandler((event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n  return useResponseSuccess({\n    url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',\n  });\n  // return useResponseError(\"test\")\n});\n"
  },
  {
    "path": "apps/backend-mock/api/user/info.ts",
    "content": "import { eventHandler } from 'h3';\nimport { verifyAccessToken } from '~/utils/jwt-utils';\nimport { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';\n\nexport default eventHandler((event) => {\n  const userinfo = verifyAccessToken(event);\n  if (!userinfo) {\n    return unAuthorizedResponse(event);\n  }\n  return useResponseSuccess(userinfo);\n});\n"
  },
  {
    "path": "apps/backend-mock/error.ts",
    "content": "import type { NitroErrorHandler } from 'nitropack';\n\nconst errorHandler: NitroErrorHandler = function (error, event) {\n  event.node.res.end(`[Error Handler] ${error.stack}`);\n};\n\nexport default errorHandler;\n"
  },
  {
    "path": "apps/backend-mock/middleware/1.api.ts",
    "content": "import { defineEventHandler } from 'h3';\nimport { forbiddenResponse, sleep } from '~/utils/response';\n\nexport default defineEventHandler(async (event) => {\n  event.node.res.setHeader(\n    'Access-Control-Allow-Origin',\n    event.headers.get('Origin') ?? '*',\n  );\n  if (event.method === 'OPTIONS') {\n    event.node.res.statusCode = 204;\n    event.node.res.statusMessage = 'No Content.';\n    return 'OK';\n  } else if (\n    ['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) &&\n    event.path.startsWith('/api/system/')\n  ) {\n    await sleep(Math.floor(Math.random() * 2000));\n    return forbiddenResponse(event, '演示环境，禁止修改');\n  }\n});\n"
  },
  {
    "path": "apps/backend-mock/nitro.config.ts",
    "content": "import errorHandler from './error';\n\nprocess.env.COMPATIBILITY_DATE = new Date().toISOString();\nexport default defineNitroConfig({\n  devErrorHandler: errorHandler,\n  errorHandler: '~/error',\n  routeRules: {\n    '/api/**': {\n      cors: true,\n      headers: {\n        'Access-Control-Allow-Credentials': 'true',\n        'Access-Control-Allow-Headers':\n          'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',\n        'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',\n        'Access-Control-Allow-Origin': '*',\n        'Access-Control-Expose-Headers': '*',\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "apps/backend-mock/package.json",
    "content": "{\n  \"name\": \"@vben/backend-mock\",\n  \"version\": \"5.7.0\",\n  \"description\": \"\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"author\": \"\",\n  \"scripts\": {\n    \"build\": \"nitro build\",\n    \"start\": \"nitro dev\"\n  },\n  \"dependencies\": {\n    \"@faker-js/faker\": \"catalog:\",\n    \"jsonwebtoken\": \"catalog:\",\n    \"nitropack\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/jsonwebtoken\": \"catalog:\",\n    \"h3\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "apps/backend-mock/routes/[...].ts",
    "content": "import { defineEventHandler } from 'h3';\n\nexport default defineEventHandler(() => {\n  return `\n<h1>Hello Vben Admin</h1>\n<h2>Mock service is starting</h2>\n<ul>\n<li><a href=\"/api/user\">/api/user/info</a></li>\n<li><a href=\"/api/menu\">/api/menu/all</a></li>\n<li><a href=\"/api/auth/codes\">/api/auth/codes</a></li>\n<li><a href=\"/api/auth/login\">/api/auth/login</a></li>\n<li><a href=\"/api/upload\">/api/upload</a></li>\n</ul>\n`;\n});\n"
  },
  {
    "path": "apps/backend-mock/tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"]\n}\n"
  },
  {
    "path": "apps/backend-mock/tsconfig.json",
    "content": "{\n  \"extends\": \"./.nitro/types/tsconfig.json\"\n}\n"
  },
  {
    "path": "apps/backend-mock/utils/cookie-utils.ts",
    "content": "import type { EventHandlerRequest, H3Event } from 'h3';\n\nimport { deleteCookie, getCookie, setCookie } from 'h3';\n\nexport function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) {\n  deleteCookie(event, 'jwt', {\n    httpOnly: true,\n    sameSite: 'none',\n    secure: true,\n  });\n}\n\nexport function setRefreshTokenCookie(\n  event: H3Event<EventHandlerRequest>,\n  refreshToken: string,\n) {\n  setCookie(event, 'jwt', refreshToken, {\n    httpOnly: true,\n    maxAge: 24 * 60 * 60, // unit: seconds\n    sameSite: 'none',\n    secure: true,\n  });\n}\n\nexport function getRefreshTokenFromCookie(event: H3Event<EventHandlerRequest>) {\n  const refreshToken = getCookie(event, 'jwt');\n  return refreshToken;\n}\n"
  },
  {
    "path": "apps/backend-mock/utils/jwt-utils.ts",
    "content": "import type { EventHandlerRequest, H3Event } from 'h3';\n\nimport type { UserInfo } from './mock-data';\n\nimport { getHeader } from 'h3';\nimport jwt from 'jsonwebtoken';\n\nimport { MOCK_USERS } from './mock-data';\n\n// TODO: Replace with your own secret key\nconst ACCESS_TOKEN_SECRET = 'access_token_secret';\nconst REFRESH_TOKEN_SECRET = 'refresh_token_secret';\n\nexport interface UserPayload extends UserInfo {\n  iat: number;\n  exp: number;\n}\n\nexport function generateAccessToken(user: UserInfo) {\n  return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' });\n}\n\nexport function generateRefreshToken(user: UserInfo) {\n  return jwt.sign(user, REFRESH_TOKEN_SECRET, {\n    expiresIn: '30d',\n  });\n}\n\nexport function verifyAccessToken(\n  event: H3Event<EventHandlerRequest>,\n): null | Omit<UserInfo, 'password'> {\n  const authHeader = getHeader(event, 'Authorization');\n  if (!authHeader?.startsWith('Bearer')) {\n    return null;\n  }\n\n  const tokenParts = authHeader.split(' ');\n  if (tokenParts.length !== 2) {\n    return null;\n  }\n  const token = tokenParts[1] as string;\n  try {\n    const decoded = jwt.verify(\n      token,\n      ACCESS_TOKEN_SECRET,\n    ) as unknown as UserPayload;\n\n    const username = decoded.username;\n    const user = MOCK_USERS.find((item) => item.username === username);\n    if (!user) {\n      return null;\n    }\n    const { password: _pwd, ...userinfo } = user;\n    return userinfo;\n  } catch {\n    return null;\n  }\n}\n\nexport function verifyRefreshToken(\n  token: string,\n): null | Omit<UserInfo, 'password'> {\n  try {\n    const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload;\n    const username = decoded.username;\n    const user = MOCK_USERS.find(\n      (item) => item.username === username,\n    ) as UserInfo;\n    if (!user) {\n      return null;\n    }\n    const { password: _pwd, ...userinfo } = user;\n    return userinfo;\n  } catch {\n    return null;\n  }\n}\n"
  },
  {
    "path": "apps/backend-mock/utils/mock-data.ts",
    "content": "export interface UserInfo {\n  id: number;\n  password: string;\n  realName: string;\n  roles: string[];\n  username: string;\n  homePath?: string;\n}\n\nexport interface TimezoneOption {\n  offset: number;\n  timezone: string;\n}\n\nexport const MOCK_USERS: UserInfo[] = [\n  {\n    id: 0,\n    password: '123456',\n    realName: 'Vben',\n    roles: ['super'],\n    username: 'vben',\n  },\n  {\n    id: 1,\n    password: '123456',\n    realName: 'Admin',\n    roles: ['admin'],\n    username: 'admin',\n    homePath: '/workspace',\n  },\n  {\n    id: 2,\n    password: '123456',\n    realName: 'Jack',\n    roles: ['user'],\n    username: 'jack',\n    homePath: '/analytics',\n  },\n];\n\nexport const MOCK_CODES = [\n  // super\n  {\n    codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'],\n    username: 'vben',\n  },\n  {\n    // admin\n    codes: ['AC_100010', 'AC_100020', 'AC_100030'],\n    username: 'admin',\n  },\n  {\n    // user\n    codes: ['AC_1000001', 'AC_1000002'],\n    username: 'jack',\n  },\n];\n\nconst dashboardMenus = [\n  {\n    meta: {\n      order: -1,\n      title: 'page.dashboard.title',\n    },\n    name: 'Dashboard',\n    path: '/dashboard',\n    redirect: '/analytics',\n    children: [\n      {\n        name: 'Analytics',\n        path: '/analytics',\n        component: '/dashboard/analytics/index',\n        meta: {\n          affixTab: true,\n          title: 'page.dashboard.analytics',\n        },\n      },\n      {\n        name: 'Workspace',\n        path: '/workspace',\n        component: '/dashboard/workspace/index',\n        meta: {\n          title: 'page.dashboard.workspace',\n        },\n      },\n    ],\n  },\n];\n\nconst createDemosMenus = (role: 'admin' | 'super' | 'user') => {\n  const roleWithMenus = {\n    admin: {\n      component: '/demos/access/admin-visible',\n      meta: {\n        icon: 'mdi:button-cursor',\n        title: 'demos.access.adminVisible',\n      },\n      name: 'AccessAdminVisibleDemo',\n      path: '/demos/access/admin-visible',\n    },\n    super: {\n      component: '/demos/access/super-visible',\n      meta: {\n        icon: 'mdi:button-cursor',\n        title: 'demos.access.superVisible',\n      },\n      name: 'AccessSuperVisibleDemo',\n      path: '/demos/access/super-visible',\n    },\n    user: {\n      component: '/demos/access/user-visible',\n      meta: {\n        icon: 'mdi:button-cursor',\n        title: 'demos.access.userVisible',\n      },\n      name: 'AccessUserVisibleDemo',\n      path: '/demos/access/user-visible',\n    },\n  };\n\n  return [\n    {\n      meta: {\n        icon: 'ic:baseline-view-in-ar',\n        keepAlive: true,\n        order: 1000,\n        title: 'demos.title',\n      },\n      name: 'Demos',\n      path: '/demos',\n      redirect: '/demos/access',\n      children: [\n        {\n          name: 'AccessDemos',\n          path: '/demosaccess',\n          meta: {\n            icon: 'mdi:cloud-key-outline',\n            title: 'demos.access.backendPermissions',\n          },\n          redirect: '/demos/access/page-control',\n          children: [\n            {\n              name: 'AccessPageControlDemo',\n              path: '/demos/access/page-control',\n              component: '/demos/access/index',\n              meta: {\n                icon: 'mdi:page-previous-outline',\n                title: 'demos.access.pageAccess',\n              },\n            },\n            {\n              name: 'AccessButtonControlDemo',\n              path: '/demos/access/button-control',\n              component: '/demos/access/button-control',\n              meta: {\n                icon: 'mdi:button-cursor',\n                title: 'demos.access.buttonControl',\n              },\n            },\n            {\n              name: 'AccessMenuVisible403Demo',\n              path: '/demos/access/menu-visible-403',\n              component: '/demos/access/menu-visible-403',\n              meta: {\n                authority: ['no-body'],\n                icon: 'mdi:button-cursor',\n                menuVisibleWithForbidden: true,\n                title: 'demos.access.menuVisible403',\n              },\n            },\n            roleWithMenus[role],\n          ],\n        },\n      ],\n    },\n  ];\n};\n\nexport const MOCK_MENUS = [\n  {\n    menus: [...dashboardMenus, ...createDemosMenus('super')],\n    username: 'vben',\n  },\n  {\n    menus: [...dashboardMenus, ...createDemosMenus('admin')],\n    username: 'admin',\n  },\n  {\n    menus: [...dashboardMenus, ...createDemosMenus('user')],\n    username: 'jack',\n  },\n];\n\nexport const MOCK_MENU_LIST = [\n  {\n    id: 1,\n    name: 'Workspace',\n    status: 1,\n    type: 'menu',\n    icon: 'mdi:dashboard',\n    path: '/workspace',\n    component: '/dashboard/workspace/index',\n    meta: {\n      icon: 'carbon:workspace',\n      title: 'page.dashboard.workspace',\n      affixTab: true,\n      order: 0,\n    },\n  },\n  {\n    id: 2,\n    meta: {\n      icon: 'carbon:settings',\n      order: 9997,\n      title: 'system.title',\n      badge: 'new',\n      badgeType: 'normal',\n      badgeVariants: 'primary',\n    },\n    status: 1,\n    type: 'catalog',\n    name: 'System',\n    path: '/system',\n    children: [\n      {\n        id: 201,\n        pid: 2,\n        path: '/system/menu',\n        name: 'SystemMenu',\n        authCode: 'System:Menu:List',\n        status: 1,\n        type: 'menu',\n        meta: {\n          icon: 'carbon:menu',\n          title: 'system.menu.title',\n        },\n        component: '/system/menu/list',\n        children: [\n          {\n            id: 20_101,\n            pid: 201,\n            name: 'SystemMenuCreate',\n            status: 1,\n            type: 'button',\n            authCode: 'System:Menu:Create',\n            meta: { title: 'common.create' },\n          },\n          {\n            id: 20_102,\n            pid: 201,\n            name: 'SystemMenuEdit',\n            status: 1,\n            type: 'button',\n            authCode: 'System:Menu:Edit',\n            meta: { title: 'common.edit' },\n          },\n          {\n            id: 20_103,\n            pid: 201,\n            name: 'SystemMenuDelete',\n            status: 1,\n            type: 'button',\n            authCode: 'System:Menu:Delete',\n            meta: { title: 'common.delete' },\n          },\n        ],\n      },\n      {\n        id: 202,\n        pid: 2,\n        path: '/system/dept',\n        name: 'SystemDept',\n        status: 1,\n        type: 'menu',\n        authCode: 'System:Dept:List',\n        meta: {\n          icon: 'carbon:container-services',\n          title: 'system.dept.title',\n        },\n        component: '/system/dept/list',\n        children: [\n          {\n            id: 20_401,\n            pid: 202,\n            name: 'SystemDeptCreate',\n            status: 1,\n            type: 'button',\n            authCode: 'System:Dept:Create',\n            meta: { title: 'common.create' },\n          },\n          {\n            id: 20_402,\n            pid: 202,\n            name: 'SystemDeptEdit',\n            status: 1,\n            type: 'button',\n            authCode: 'System:Dept:Edit',\n            meta: { title: 'common.edit' },\n          },\n          {\n            id: 20_403,\n            pid: 202,\n            name: 'SystemDeptDelete',\n            status: 1,\n            type: 'button',\n            authCode: 'System:Dept:Delete',\n            meta: { title: 'common.delete' },\n          },\n        ],\n      },\n    ],\n  },\n  {\n    id: 9,\n    meta: {\n      badgeType: 'dot',\n      order: 9998,\n      title: 'demos.vben.title',\n      icon: 'carbon:data-center',\n    },\n    name: 'Project',\n    path: '/vben-admin',\n    type: 'catalog',\n    status: 1,\n    children: [\n      {\n        id: 901,\n        pid: 9,\n        name: 'VbenDocument',\n        path: '/vben-admin/document',\n        component: 'IFrameView',\n        type: 'embedded',\n        status: 1,\n        meta: {\n          icon: 'carbon:book',\n          iframeSrc: 'https://doc.vben.pro',\n          title: 'demos.vben.document',\n        },\n      },\n      {\n        id: 902,\n        pid: 9,\n        name: 'VbenGithub',\n        path: '/vben-admin/github',\n        component: 'IFrameView',\n        type: 'link',\n        status: 1,\n        meta: {\n          icon: 'carbon:logo-github',\n          link: 'https://github.com/vbenjs/vue-vben-admin',\n          title: 'Github',\n        },\n      },\n      {\n        id: 903,\n        pid: 9,\n        name: 'VbenAntdv',\n        path: '/vben-admin/antdv',\n        component: 'IFrameView',\n        type: 'link',\n        status: 0,\n        meta: {\n          icon: 'carbon:hexagon-vertical-solid',\n          badgeType: 'dot',\n          link: 'https://ant.vben.pro',\n          title: 'demos.vben.antdv',\n        },\n      },\n    ],\n  },\n  {\n    id: 10,\n    component: '_core/about/index',\n    type: 'menu',\n    status: 1,\n    meta: {\n      icon: 'lucide:copyright',\n      order: 9999,\n      title: 'demos.vben.about',\n    },\n    name: 'About',\n    path: '/about',\n  },\n];\n\nexport function getMenuIds(menus: any[]) {\n  const ids: number[] = [];\n  menus.forEach((item) => {\n    ids.push(item.id);\n    if (item.children && item.children.length > 0) {\n      ids.push(...getMenuIds(item.children));\n    }\n  });\n  return ids;\n}\n\n/**\n * 时区选项\n */\nexport const TIME_ZONE_OPTIONS: TimezoneOption[] = [\n  {\n    offset: -5,\n    timezone: 'America/New_York',\n  },\n  {\n    offset: 0,\n    timezone: 'Europe/London',\n  },\n  {\n    offset: 8,\n    timezone: 'Asia/Shanghai',\n  },\n  {\n    offset: 9,\n    timezone: 'Asia/Tokyo',\n  },\n  {\n    offset: 9,\n    timezone: 'Asia/Seoul',\n  },\n];\n"
  },
  {
    "path": "apps/backend-mock/utils/response.ts",
    "content": "import type { EventHandlerRequest, H3Event } from 'h3';\n\nimport { setResponseStatus } from 'h3';\n\nexport function useResponseSuccess<T = any>(data: T) {\n  return {\n    code: 0,\n    data,\n    error: null,\n    message: 'ok',\n  };\n}\n\nexport function usePageResponseSuccess<T = any>(\n  page: number | string,\n  pageSize: number | string,\n  list: T[],\n  { message = 'ok' } = {},\n) {\n  const pageData = pagination(\n    Number.parseInt(`${page}`),\n    Number.parseInt(`${pageSize}`),\n    list,\n  );\n\n  return {\n    ...useResponseSuccess({\n      items: pageData,\n      total: list.length,\n    }),\n    message,\n  };\n}\n\nexport function useResponseError(message: string, error: any = null) {\n  return {\n    code: -1,\n    data: null,\n    error,\n    message,\n  };\n}\n\nexport function forbiddenResponse(\n  event: H3Event<EventHandlerRequest>,\n  message = 'Forbidden Exception',\n) {\n  setResponseStatus(event, 403);\n  return useResponseError(message, message);\n}\n\nexport function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {\n  setResponseStatus(event, 401);\n  return useResponseError('Unauthorized Exception', 'Unauthorized Exception');\n}\n\nexport function sleep(ms: number) {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function pagination<T = any>(\n  pageNo: number,\n  pageSize: number,\n  array: T[],\n): T[] {\n  const offset = (pageNo - 1) * Number(pageSize);\n  return offset + Number(pageSize) >= array.length\n    ? array.slice(offset)\n    : array.slice(offset, offset + Number(pageSize));\n}\n"
  },
  {
    "path": "apps/backend-mock/utils/timezone-utils.ts",
    "content": "let mockTimeZone: null | string = null;\n\nexport const setTimezone = (timeZone: string) => {\n  mockTimeZone = timeZone;\n};\n\nexport const getTimezone = () => {\n  return mockTimeZone;\n};\n"
  },
  {
    "path": "apps/web-antd/index.html",
    "content": "<!doctype html>\n<html lang=\"zh\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n    <meta name=\"renderer\" content=\"webkit\" />\n    <meta name=\"description\" content=\"A Modern Back-end Management System\" />\n    <meta name=\"keywords\" content=\"Vben Admin Vue3 Vite\" />\n    <meta name=\"author\" content=\"Vben\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0\"\n    />\n    <!-- 由 vite 注入 VITE_APP_TITLE 变量，在 .env 文件内配置 -->\n    <title><%= VITE_APP_TITLE %></title>\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <script>\n      // 生产环境下注入百度统计\n      if (window._VBEN_ADMIN_PRO_APP_CONF_) {\n        var _hmt = _hmt || [];\n        (function () {\n          var hm = document.createElement('script');\n          hm.src =\n            'https://hm.baidu.com/hm.js?b38e689f40558f20a9a686d7f6f33edf';\n          var s = document.getElementsByTagName('script')[0];\n          s.parentNode.insertBefore(hm, s);\n        })();\n      }\n    </script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "apps/web-antd/package.json",
    "content": "{\n  \"name\": \"@vben/web-antd\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://vben.pro\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"apps/web-antd\"\n  },\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"vben\",\n    \"email\": \"ann.vben@gmail.com\",\n    \"url\": \"https://github.com/anncwb\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm vite build --mode production\",\n    \"build:analyze\": \"pnpm vite build --mode analyze\",\n    \"dev\": \"pnpm vite --mode development\",\n    \"preview\": \"vite preview\",\n    \"typecheck\": \"vue-tsc --noEmit --skipLibCheck\"\n  },\n  \"imports\": {\n    \"#/*\": \"./src/*\"\n  },\n  \"dependencies\": {\n    \"@vben/access\": \"workspace:*\",\n    \"@vben/common-ui\": \"workspace:*\",\n    \"@vben/constants\": \"workspace:*\",\n    \"@vben/hooks\": \"workspace:*\",\n    \"@vben/icons\": \"workspace:*\",\n    \"@vben/layouts\": \"workspace:*\",\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/plugins\": \"workspace:*\",\n    \"@vben/preferences\": \"workspace:*\",\n    \"@vben/request\": \"workspace:*\",\n    \"@vben/stores\": \"workspace:*\",\n    \"@vben/styles\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"ant-design-vue\": \"catalog:\",\n    \"dayjs\": \"catalog:\",\n    \"pinia\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "apps/web-antd/src/adapter/component/index.ts",
    "content": "/**\n * 通用组件共同的使用的基础组件，原先放在 adapter/form 内部，限制了使用范围，这里提取出来，方便其他地方使用\n * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,\n */\n\n/* eslint-disable vue/one-component-per-file */\n\nimport type {\n  UploadChangeParam,\n  UploadFile,\n  UploadProps,\n} from 'ant-design-vue';\n\nimport type { Component, Ref } from 'vue';\n\nimport type { BaseFormComponentType } from '@vben/common-ui';\nimport type { Sortable } from '@vben/hooks';\nimport type { Recordable } from '@vben/types';\n\nimport {\n  computed,\n  defineAsyncComponent,\n  defineComponent,\n  h,\n  nextTick,\n  onMounted,\n  onUnmounted,\n  ref,\n  render,\n  unref,\n  watch,\n} from 'vue';\n\nimport {\n  ApiComponent,\n  globalShareState,\n  IconPicker,\n  VCropper,\n} from '@vben/common-ui';\nimport { useSortable } from '@vben/hooks';\nimport { IconifyIcon } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport { isEmpty } from '@vben/utils';\n\nimport { message, Modal, notification } from 'ant-design-vue';\n\nconst AutoComplete = defineAsyncComponent(\n  () => import('ant-design-vue/es/auto-complete'),\n);\nconst Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));\nconst Checkbox = defineAsyncComponent(\n  () => import('ant-design-vue/es/checkbox'),\n);\nconst CheckboxGroup = defineAsyncComponent(() =>\n  import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),\n);\nconst DatePicker = defineAsyncComponent(\n  () => import('ant-design-vue/es/date-picker'),\n);\nconst Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));\nconst Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));\nconst InputNumber = defineAsyncComponent(\n  () => import('ant-design-vue/es/input-number'),\n);\nconst InputPassword = defineAsyncComponent(() =>\n  import('ant-design-vue/es/input').then((res) => res.InputPassword),\n);\nconst Mentions = defineAsyncComponent(\n  () => import('ant-design-vue/es/mentions'),\n);\nconst Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));\nconst RadioGroup = defineAsyncComponent(() =>\n  import('ant-design-vue/es/radio').then((res) => res.RadioGroup),\n);\nconst RangePicker = defineAsyncComponent(() =>\n  import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),\n);\nconst Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));\nconst Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));\nconst Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));\nconst Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));\nconst Textarea = defineAsyncComponent(() =>\n  import('ant-design-vue/es/input').then((res) => res.Textarea),\n);\nconst TimePicker = defineAsyncComponent(\n  () => import('ant-design-vue/es/time-picker'),\n);\nconst TreeSelect = defineAsyncComponent(\n  () => import('ant-design-vue/es/tree-select'),\n);\nconst Cascader = defineAsyncComponent(\n  () => import('ant-design-vue/es/cascader'),\n);\nconst Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));\nconst Image = defineAsyncComponent(() => import('ant-design-vue/es/image'));\nconst PreviewGroup = defineAsyncComponent(() =>\n  import('ant-design-vue/es/image').then((res) => res.ImagePreviewGroup),\n);\n\nconst withDefaultPlaceholder = <T extends Component>(\n  component: T,\n  type: 'input' | 'select',\n  componentProps: Recordable<any> = {},\n) => {\n  return defineComponent({\n    name: component.name,\n    inheritAttrs: false,\n    setup: (props: any, { attrs, expose, slots }) => {\n      const placeholder =\n        props?.placeholder ||\n        attrs?.placeholder ||\n        $t(`ui.placeholder.${type}`);\n      // 透传组件暴露的方法\n      const innerRef = ref();\n      expose(\n        new Proxy(\n          {},\n          {\n            get: (_target, key) => innerRef.value?.[key],\n            has: (_target, key) => key in (innerRef.value || {}),\n          },\n        ),\n      );\n      return () =>\n        h(\n          component,\n          { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },\n          slots,\n        );\n    },\n  });\n};\n\nconst IMAGE_EXTENSIONS = new Set([\n  'bmp',\n  'gif',\n  'jpeg',\n  'jpg',\n  'png',\n  'svg',\n  'webp',\n]);\n\n/**\n * 检查是否为图片文件\n */\nfunction isImageFile(file: UploadFile): boolean {\n  if (file.url) {\n    try {\n      const pathname = new URL(file.url, 'http://localhost').pathname;\n      const ext = pathname.split('.').pop()?.toLowerCase();\n      return ext ? IMAGE_EXTENSIONS.has(ext) : false;\n    } catch {\n      const ext = file.url?.split('.').pop()?.toLowerCase();\n      return ext ? IMAGE_EXTENSIONS.has(ext) : false;\n    }\n  }\n  if (!file.type) {\n    const ext = file.name?.split('.').pop()?.toLowerCase();\n    return ext ? IMAGE_EXTENSIONS.has(ext) : false;\n  }\n  return file.type.startsWith('image/');\n}\n\n/**\n * 创建默认的上传按钮插槽\n */\nfunction createDefaultUploadSlots(listType: string, placeholder: string) {\n  if (listType === 'picture-card') {\n    return { default: () => placeholder };\n  }\n  return {\n    default: () =>\n      h(\n        Button,\n        {\n          icon: h(IconifyIcon, {\n            icon: 'ant-design:upload-outlined',\n            class: 'mb-1 size-4',\n          }),\n        },\n        () => placeholder,\n      ),\n  };\n}\n\n/**\n * 获取文件的 Base64\n */\nfunction getBase64(file: File): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.readAsDataURL(file);\n    reader.addEventListener('load', () => resolve(reader.result as string));\n    reader.addEventListener('error', reject);\n  });\n}\n\n/**\n * 预览图片\n */\nasync function previewImage(\n  file: UploadFile,\n  visible: Ref<boolean>,\n  fileList: Ref<UploadProps['fileList']>,\n) {\n  // 非图片文件直接打开链接\n  if (!isImageFile(file)) {\n    const url = file.url || file.preview;\n    if (url) {\n      window.open(url, '_blank');\n    } else {\n      message.error($t('ui.formRules.previewWarning'));\n    }\n    return;\n  }\n\n  const [ImageComponent, PreviewGroupComponent] = await Promise.all([\n    Image,\n    PreviewGroup,\n  ]);\n\n  // 过滤图片文件并生成预览\n  const imageFiles = (unref(fileList) || []).filter((f) => isImageFile(f));\n\n  for (const imgFile of imageFiles) {\n    if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {\n      imgFile.preview = await getBase64(imgFile.originFileObj);\n    }\n  }\n\n  const container = document.createElement('div');\n  document.body.append(container);\n  let isUnmounted = false;\n\n  const currentIndex = imageFiles.findIndex((f) => f.uid === file.uid);\n\n  const PreviewWrapper = {\n    setup() {\n      return () => {\n        if (isUnmounted) return null;\n        return h(\n          PreviewGroupComponent,\n          {\n            class: 'hidden',\n            preview: {\n              visible: visible.value,\n              current: currentIndex,\n              onVisibleChange: (value: boolean) => {\n                visible.value = value;\n                if (!value) {\n                  setTimeout(() => {\n                    if (!isUnmounted && container) {\n                      isUnmounted = true;\n                      render(null, container);\n                      container.remove();\n                    }\n                  }, 300);\n                }\n              },\n            },\n          },\n          () =>\n            imageFiles.map((imgFile) =>\n              h(ImageComponent, {\n                key: imgFile.uid,\n                src: imgFile.url || imgFile.preview,\n              }),\n            ),\n        );\n      };\n    },\n  };\n\n  render(h(PreviewWrapper), container);\n}\n\n/**\n * 图片裁剪操作\n */\nfunction cropImage(file: File, aspectRatio: string | undefined) {\n  return new Promise<Blob | string | undefined>((resolve, reject) => {\n    const container = document.createElement('div');\n    document.body.append(container);\n\n    let isUnmounted = false;\n    let objectUrl: null | string = null;\n\n    const open = ref<boolean>(true);\n    const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);\n\n    const closeModal = () => {\n      open.value = false;\n      setTimeout(() => {\n        if (!isUnmounted && container) {\n          if (objectUrl) {\n            URL.revokeObjectURL(objectUrl);\n          }\n          isUnmounted = true;\n          render(null, container);\n          container.remove();\n        }\n      }, 300);\n    };\n\n    const CropperWrapper = {\n      setup() {\n        return () => {\n          if (isUnmounted) return null;\n          if (!objectUrl) {\n            objectUrl = URL.createObjectURL(file);\n          }\n          return h(\n            Modal,\n            {\n              open: open.value,\n              title: h('div', {}, [\n                $t('ui.crop.title'),\n                h(\n                  'span',\n                  {\n                    class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,\n                  },\n                  $t('ui.crop.titleTip', [aspectRatio]),\n                ),\n              ]),\n              centered: true,\n              width: 548,\n              keyboard: false,\n              maskClosable: false,\n              closable: false,\n              cancelText: $t('common.cancel'),\n              okText: $t('ui.crop.confirm'),\n              destroyOnClose: true,\n              onOk: async () => {\n                const cropper = cropperRef.value;\n                if (!cropper) {\n                  reject(new Error('Cropper not found'));\n                  closeModal();\n                  return;\n                }\n                try {\n                  const dataUrl = await cropper.getCropImage();\n                  if (dataUrl) {\n                    resolve(dataUrl);\n                  } else {\n                    reject(new Error($t('ui.crop.errorTip')));\n                  }\n                } catch {\n                  reject(new Error($t('ui.crop.errorTip')));\n                } finally {\n                  closeModal();\n                }\n              },\n              onCancel() {\n                resolve('');\n                closeModal();\n              },\n            },\n            () =>\n              h(VCropper, {\n                ref: (ref: any) => (cropperRef.value = ref),\n                img: objectUrl as string,\n                aspectRatio,\n              }),\n          );\n        };\n      },\n    };\n\n    render(h(CropperWrapper), container);\n  });\n}\n\n/**\n * 带预览功能的上传组件\n */\nconst withPreviewUpload = () => {\n  return defineComponent({\n    name: Upload.name,\n    emits: ['update:modelValue'],\n    setup(\n      props: any,\n      { attrs, slots, emit }: { attrs: any; emit: any; slots: any },\n    ) {\n      const previewVisible = ref<boolean>(false);\n      const placeholder = attrs?.placeholder || $t('ui.placeholder.upload');\n      const listType = attrs?.listType || attrs?.['list-type'] || 'text';\n      const fileList = ref<UploadProps['fileList']>(\n        attrs?.fileList || attrs?.['file-list'] || [],\n      );\n\n      const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']);\n      const aspectRatio = computed(\n        () => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],\n      );\n\n      const handleBeforeUpload = async (\n        file: UploadFile,\n        originFileList: Array<File>,\n      ) => {\n        // 文件大小限制\n        if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {\n          message.error($t('ui.formRules.sizeLimit', [maxSize.value]));\n          file.status = 'removed';\n          return false;\n        }\n\n        // 图片裁剪处理\n        if (\n          attrs.crop &&\n          !attrs.multiple &&\n          originFileList[0] &&\n          isImageFile(file)\n        ) {\n          file.status = 'removed';\n          const blob = await cropImage(originFileList[0], aspectRatio.value);\n          if (!blob) {\n            throw new Error($t('ui.crop.errorTip'));\n          }\n          return blob;\n        }\n\n        return attrs.beforeUpload?.(file) ?? true;\n      };\n\n      const handleChange = (event: UploadChangeParam) => {\n        try {\n          attrs.handleChange?.(event);\n          attrs.onHandleChange?.(event);\n        } catch (error) {\n          console.error(error);\n        }\n        fileList.value = event.fileList.filter(\n          (file) => file.status !== 'removed',\n        );\n        emit(\n          'update:modelValue',\n          event.fileList?.length ? fileList.value : undefined,\n        );\n      };\n\n      const handlePreview = async (file: UploadFile) => {\n        previewVisible.value = true;\n        await previewImage(file, previewVisible, fileList);\n      };\n\n      const renderUploadButton = () => {\n        if (attrs.disabled) return null;\n        return isEmpty(slots)\n          ? createDefaultUploadSlots(listType, placeholder)\n          : slots;\n      };\n\n      // 拖拽排序\n      const draggable = computed(\n        () => (attrs.draggable ?? false) && !attrs.disabled,\n      );\n      const uploadId = `upload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n      const sortableInstance = ref<null | Sortable>(null);\n\n      const styleId = `upload-drag-style-${uploadId}`;\n\n      function injectDragStyle() {\n        if (!document.querySelector(`[id=\"${styleId}\"]`)) {\n          const style = document.createElement('style');\n          style.id = styleId;\n          style.textContent = `\n            [data-upload-id=\"${uploadId}\"] .ant-upload-list-item { cursor: move; }\n            [data-upload-id=\"${uploadId}\"] .ant-upload-list-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); }\n          `;\n          document.head.append(style);\n        }\n      }\n\n      function removeDragStyle() {\n        document.querySelector(`[id=\"${styleId}\"]`)?.remove();\n      }\n\n      async function initSortable(retryCount = 0) {\n        if (!draggable.value) return;\n\n        injectDragStyle();\n        await nextTick();\n        await new Promise((resolve) => setTimeout(resolve, 100));\n\n        const container = document.querySelector(\n          `[data-upload-id=\"${uploadId}\"] .ant-upload-list`,\n        ) as HTMLElement;\n\n        if (!container) {\n          if (retryCount < 5) {\n            setTimeout(() => initSortable(retryCount + 1), 200);\n          }\n          return;\n        }\n\n        const { initializeSortable } = useSortable(container, {\n          animation: 300,\n          delay: 400,\n          delayOnTouchOnly: true,\n          filter:\n            '.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading',\n          onEnd: (evt) => {\n            const { oldIndex, newIndex } = evt;\n            if (\n              oldIndex === undefined ||\n              newIndex === undefined ||\n              oldIndex === newIndex\n            ) {\n              return;\n            }\n\n            const list = [...(fileList.value || [])];\n            const [movedItem] = list.splice(oldIndex, 1);\n            if (movedItem) {\n              list.splice(newIndex, 0, movedItem);\n              fileList.value = list;\n            }\n\n            attrs.onDragSort?.(oldIndex, newIndex);\n            emit('update:modelValue', fileList.value);\n          },\n        });\n\n        sortableInstance.value = await initializeSortable();\n      }\n\n      // 监听表单值变化\n      watch(\n        () => attrs.modelValue,\n        (res) => {\n          fileList.value = res;\n        },\n      );\n\n      onMounted(initSortable);\n      onUnmounted(() => {\n        sortableInstance.value?.destroy();\n        removeDragStyle();\n      });\n\n      return () =>\n        h(\n          'div',\n          { 'data-upload-id': uploadId, class: 'w-full' },\n          h(\n            Upload,\n            {\n              ...props,\n              ...attrs,\n              fileList: fileList.value,\n              beforeUpload: handleBeforeUpload,\n              onChange: handleChange,\n              onPreview: handlePreview,\n            },\n            renderUploadButton() as any,\n          ),\n        );\n    },\n  });\n};\n\n// 这里需要自行根据业务组件库进行适配，需要用到的组件都需要在这里类型说明\nexport type ComponentType =\n  | 'ApiCascader'\n  | 'ApiSelect'\n  | 'ApiTreeSelect'\n  | 'AutoComplete'\n  | 'Cascader'\n  | 'Checkbox'\n  | 'CheckboxGroup'\n  | 'DatePicker'\n  | 'DefaultButton'\n  | 'Divider'\n  | 'IconPicker'\n  | 'Input'\n  | 'InputNumber'\n  | 'InputPassword'\n  | 'Mentions'\n  | 'PrimaryButton'\n  | 'Radio'\n  | 'RadioGroup'\n  | 'RangePicker'\n  | 'Rate'\n  | 'Select'\n  | 'Space'\n  | 'Switch'\n  | 'Textarea'\n  | 'TimePicker'\n  | 'TreeSelect'\n  | 'Upload'\n  | BaseFormComponentType;\n\nasync function initComponentAdapter() {\n  const components: Partial<Record<ComponentType, Component>> = {\n    // 如果你的组件体积比较大，可以使用异步加载\n    // Button: () =>\n    // import('xxx').then((res) => res.Button),\n\n    ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {\n      component: Cascader,\n      fieldNames: { label: 'label', value: 'value', children: 'children' },\n      loadingSlot: 'suffixIcon',\n      modelPropName: 'value',\n      visibleEvent: 'onVisibleChange',\n    }),\n    ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {\n      component: Select,\n      loadingSlot: 'suffixIcon',\n      modelPropName: 'value',\n      visibleEvent: 'onVisibleChange',\n    }),\n    ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {\n      component: TreeSelect,\n      fieldNames: { label: 'label', value: 'value', children: 'children' },\n      loadingSlot: 'suffixIcon',\n      modelPropName: 'value',\n      optionsPropName: 'treeData',\n      visibleEvent: 'onVisibleChange',\n    }),\n    AutoComplete,\n    Cascader,\n    Checkbox,\n    CheckboxGroup,\n    DatePicker,\n    // 自定义默认按钮\n    DefaultButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'default' }, slots);\n    },\n    Divider,\n    IconPicker: withDefaultPlaceholder(IconPicker, 'select', {\n      iconSlot: 'addonAfter',\n      inputComponent: Input,\n      modelValueProp: 'value',\n    }),\n    Input: withDefaultPlaceholder(Input, 'input'),\n    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),\n    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),\n    Mentions: withDefaultPlaceholder(Mentions, 'input'),\n    // 自定义主要按钮\n    PrimaryButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'primary' }, slots);\n    },\n    Radio,\n    RadioGroup,\n    RangePicker,\n    Rate,\n    Select: withDefaultPlaceholder(Select, 'select'),\n    Space,\n    Switch,\n    Textarea: withDefaultPlaceholder(Textarea, 'input'),\n    TimePicker,\n    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),\n    Upload: withPreviewUpload(),\n  };\n\n  // 将组件注册到全局共享状态中\n  globalShareState.setComponents(components);\n\n  // 定义全局共享状态中的消息提示\n  globalShareState.defineMessage({\n    // 复制成功消息提示\n    copyPreferencesSuccess: (title, content) => {\n      notification.success({\n        description: content,\n        message: title,\n        placement: 'bottomRight',\n      });\n    },\n  });\n}\n\nexport { initComponentAdapter };\n"
  },
  {
    "path": "apps/web-antd/src/adapter/form.ts",
    "content": "import type {\n  VbenFormSchema as FormSchema,\n  VbenFormProps,\n} from '@vben/common-ui';\n\nimport type { ComponentType } from './component';\n\nimport { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nasync function initSetupVbenForm() {\n  setupVbenForm<ComponentType>({\n    config: {\n      // ant design vue组件库默认都是 v-model:value\n      baseModelPropName: 'value',\n\n      // 一些组件是 v-model:checked 或者 v-model:fileList\n      modelPropNameMap: {\n        Checkbox: 'checked',\n        Radio: 'checked',\n        Switch: 'checked',\n        Upload: 'fileList',\n      },\n    },\n    defineRules: {\n      // 输入项目必填国际化适配\n      required: (value, _params, ctx) => {\n        if (value === undefined || value === null || value.length === 0) {\n          return $t('ui.formRules.required', [ctx.label]);\n        }\n        return true;\n      },\n      // 选择项目必填国际化适配\n      selectRequired: (value, _params, ctx) => {\n        if (value === undefined || value === null) {\n          return $t('ui.formRules.selectRequired', [ctx.label]);\n        }\n        return true;\n      },\n    },\n  });\n}\n\nconst useVbenForm = useForm<ComponentType>;\n\nexport { initSetupVbenForm, useVbenForm, z };\n\nexport type VbenFormSchema = FormSchema<ComponentType>;\nexport type { VbenFormProps };\n"
  },
  {
    "path": "apps/web-antd/src/adapter/vxe-table.ts",
    "content": "import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';\n\nimport { h } from 'vue';\n\nimport { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';\n\nimport { Button, Image } from 'ant-design-vue';\n\nimport { useVbenForm } from './form';\n\nsetupVbenVxeTable({\n  configVxeTable: (vxeUI) => {\n    vxeUI.setConfig({\n      grid: {\n        align: 'center',\n        border: false,\n        columnConfig: {\n          resizable: true,\n        },\n        minHeight: 180,\n        formConfig: {\n          // 全局禁用vxe-table的表单配置，使用formOptions\n          enabled: false,\n        },\n        proxyConfig: {\n          autoLoad: true,\n          response: {\n            result: 'items',\n            total: 'total',\n            list: 'items',\n          },\n          showActiveMsg: true,\n          showResponseMsg: false,\n        },\n        round: true,\n        showOverflow: true,\n        size: 'small',\n      } as VxeTableGridOptions,\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellImage' },\n    vxeUI.renderer.add('CellImage', {\n      renderTableDefault(renderOpts, params) {\n        const { props } = renderOpts;\n        const { column, row } = params;\n        return h(Image, { src: row[column.field], ...props });\n      },\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellLink' },\n    vxeUI.renderer.add('CellLink', {\n      renderTableDefault(renderOpts) {\n        const { props } = renderOpts;\n        return h(\n          Button,\n          { size: 'small', type: 'link' },\n          { default: () => props?.text },\n        );\n      },\n    });\n\n    // 这里可以自行扩展 vxe-table 的全局配置，比如自定义格式化\n    // vxeUI.formats.add\n  },\n  useVbenForm,\n});\n\nexport { useVbenVxeGrid };\n\nexport type * from '@vben/plugins/vxe-table';\n"
  },
  {
    "path": "apps/web-antd/src/api/core/auth.ts",
    "content": "import { baseRequestClient, requestClient } from '#/api/request';\n\nexport namespace AuthApi {\n  /** 登录接口参数 */\n  export interface LoginParams {\n    password?: string;\n    username?: string;\n  }\n\n  /** 登录接口返回值 */\n  export interface LoginResult {\n    accessToken: string;\n  }\n\n  export interface RefreshTokenResult {\n    data: string;\n    status: number;\n  }\n}\n\n/**\n * 登录\n */\nexport async function loginApi(data: AuthApi.LoginParams) {\n  return requestClient.post<AuthApi.LoginResult>('/auth/login', data);\n}\n\n/**\n * 刷新accessToken\n */\nexport async function refreshTokenApi() {\n  return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {\n    withCredentials: true,\n  });\n}\n\n/**\n * 退出登录\n */\nexport async function logoutApi() {\n  return baseRequestClient.post('/auth/logout', {\n    withCredentials: true,\n  });\n}\n\n/**\n * 获取用户权限码\n */\nexport async function getAccessCodesApi() {\n  return requestClient.get<string[]>('/auth/codes');\n}\n"
  },
  {
    "path": "apps/web-antd/src/api/core/index.ts",
    "content": "export * from './auth';\nexport * from './menu';\nexport * from './user';\n"
  },
  {
    "path": "apps/web-antd/src/api/core/menu.ts",
    "content": "import type { RouteRecordStringComponent } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户所有菜单\n */\nexport async function getAllMenusApi() {\n  return requestClient.get<RouteRecordStringComponent[]>('/menu/all');\n}\n"
  },
  {
    "path": "apps/web-antd/src/api/core/user.ts",
    "content": "import type { UserInfo } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户信息\n */\nexport async function getUserInfoApi() {\n  return requestClient.get<UserInfo>('/user/info');\n}\n"
  },
  {
    "path": "apps/web-antd/src/api/index.ts",
    "content": "export * from './core';\n"
  },
  {
    "path": "apps/web-antd/src/api/request.ts",
    "content": "/**\n * 该文件可自行根据业务逻辑进行调整\n */\nimport type { RequestClientOptions } from '@vben/request';\n\nimport { useAppConfig } from '@vben/hooks';\nimport { preferences } from '@vben/preferences';\nimport {\n  authenticateResponseInterceptor,\n  defaultResponseInterceptor,\n  errorMessageResponseInterceptor,\n  RequestClient,\n} from '@vben/request';\nimport { useAccessStore } from '@vben/stores';\n\nimport { message } from 'ant-design-vue';\n\nimport { useAuthStore } from '#/store';\n\nimport { refreshTokenApi } from './core';\n\nconst { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n\nfunction createRequestClient(baseURL: string, options?: RequestClientOptions) {\n  const client = new RequestClient({\n    ...options,\n    baseURL,\n  });\n\n  /**\n   * 重新认证逻辑\n   */\n  async function doReAuthenticate() {\n    console.warn('Access token or refresh token is invalid or expired. ');\n    const accessStore = useAccessStore();\n    const authStore = useAuthStore();\n    accessStore.setAccessToken(null);\n    if (\n      preferences.app.loginExpiredMode === 'modal' &&\n      accessStore.isAccessChecked\n    ) {\n      accessStore.setLoginExpired(true);\n    } else {\n      await authStore.logout();\n    }\n  }\n\n  /**\n   * 刷新token逻辑\n   */\n  async function doRefreshToken() {\n    const accessStore = useAccessStore();\n    const resp = await refreshTokenApi();\n    const newToken = resp.data;\n    accessStore.setAccessToken(newToken);\n    return newToken;\n  }\n\n  function formatToken(token: null | string) {\n    return token ? `Bearer ${token}` : null;\n  }\n\n  // 请求头处理\n  client.addRequestInterceptor({\n    fulfilled: async (config) => {\n      const accessStore = useAccessStore();\n\n      config.headers.Authorization = formatToken(accessStore.accessToken);\n      config.headers['Accept-Language'] = preferences.app.locale;\n      return config;\n    },\n  });\n\n  // 处理返回的响应数据格式\n  client.addResponseInterceptor(\n    defaultResponseInterceptor({\n      codeField: 'code',\n      dataField: 'data',\n      successCode: 0,\n    }),\n  );\n\n  // token过期的处理\n  client.addResponseInterceptor(\n    authenticateResponseInterceptor({\n      client,\n      doReAuthenticate,\n      doRefreshToken,\n      enableRefreshToken: preferences.app.enableRefreshToken,\n      formatToken,\n    }),\n  );\n\n  // 通用的错误处理,如果没有进入上面的错误处理逻辑，就会进入这里\n  client.addResponseInterceptor(\n    errorMessageResponseInterceptor((msg: string, error) => {\n      // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理，根据不同的 code 做不同的提示，而不是直接使用 message.error 提示 msg\n      // 当前mock接口返回的错误字段是 error 或者 message\n      const responseData = error?.response?.data ?? {};\n      const errorMessage = responseData?.error ?? responseData?.message ?? '';\n      // 如果没有错误信息，则会根据状态码进行提示\n      message.error(errorMessage || msg);\n    }),\n  );\n\n  return client;\n}\n\nexport const requestClient = createRequestClient(apiURL, {\n  responseReturn: 'data',\n});\n\nexport const baseRequestClient = new RequestClient({ baseURL: apiURL });\n"
  },
  {
    "path": "apps/web-antd/src/app.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { useAntdDesignTokens } from '@vben/hooks';\nimport { preferences, usePreferences } from '@vben/preferences';\n\nimport { App, ConfigProvider, theme } from 'ant-design-vue';\n\nimport { antdLocale } from '#/locales';\n\ndefineOptions({ name: 'App' });\n\nconst { isDark } = usePreferences();\nconst { tokens } = useAntdDesignTokens();\n\nconst tokenTheme = computed(() => {\n  const algorithm = isDark.value\n    ? [theme.darkAlgorithm]\n    : [theme.defaultAlgorithm];\n\n  // antd 紧凑模式算法\n  if (preferences.app.compact) {\n    algorithm.push(theme.compactAlgorithm);\n  }\n\n  return {\n    algorithm,\n    token: tokens,\n  };\n});\n</script>\n\n<template>\n  <ConfigProvider :locale=\"antdLocale\" :theme=\"tokenTheme\">\n    <App>\n      <RouterView />\n    </App>\n  </ConfigProvider>\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/bootstrap.ts",
    "content": "import { createApp, watchEffect } from 'vue';\n\nimport { registerAccessDirective } from '@vben/access';\nimport { registerLoadingDirective } from '@vben/common-ui/es/loading';\nimport { preferences } from '@vben/preferences';\nimport { initStores } from '@vben/stores';\nimport '@vben/styles';\nimport '@vben/styles/antd';\n\nimport { useTitle } from '@vueuse/core';\n\nimport { $t, setupI18n } from '#/locales';\n\nimport { initComponentAdapter } from './adapter/component';\nimport { initSetupVbenForm } from './adapter/form';\nimport App from './app.vue';\nimport { router } from './router';\n\nasync function bootstrap(namespace: string) {\n  // 初始化组件适配器\n  await initComponentAdapter();\n\n  // 初始化表单组件\n  await initSetupVbenForm();\n\n  // // 设置弹窗的默认配置\n  // setDefaultModalProps({\n  //   fullscreenButton: false,\n  // });\n  // // 设置抽屉的默认配置\n  // setDefaultDrawerProps({\n  //   zIndex: 1020,\n  // });\n\n  const app = createApp(App);\n\n  // 注册v-loading指令\n  registerLoadingDirective(app, {\n    loading: 'loading', // 在这里可以自定义指令名称，也可以明确提供false表示不注册这个指令\n    spinning: 'spinning',\n  });\n\n  // 国际化 i18n 配置\n  await setupI18n(app);\n\n  // 配置 pinia-tore\n  await initStores(app, { namespace });\n\n  // 安装权限指令\n  registerAccessDirective(app);\n\n  // 初始化 tippy\n  const { initTippy } = await import('@vben/common-ui/es/tippy');\n  initTippy(app);\n\n  // 配置路由及路由守卫\n  app.use(router);\n\n  // 配置Motion插件\n  const { MotionPlugin } = await import('@vben/plugins/motion');\n  app.use(MotionPlugin);\n\n  // 动态更新标题\n  watchEffect(() => {\n    if (preferences.app.dynamicTitle) {\n      const routeTitle = router.currentRoute.value.meta?.title;\n      const pageTitle =\n        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;\n      useTitle(pageTitle);\n    }\n  });\n\n  app.mount('#app');\n}\n\nexport { bootstrap };\n"
  },
  {
    "path": "apps/web-antd/src/layouts/auth.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { AuthPageLayout } from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst appName = computed(() => preferences.app.name);\nconst logo = computed(() => preferences.logo.source);\nconst logoDark = computed(() => preferences.logo.sourceDark);\n</script>\n\n<template>\n  <AuthPageLayout\n    :app-name=\"appName\"\n    :logo=\"logo\"\n    :logo-dark=\"logoDark\"\n    :page-description=\"$t('authentication.pageDesc')\"\n    :page-title=\"$t('authentication.pageTitle')\"\n  >\n    <!-- 自定义工具栏 -->\n    <!-- <template #toolbar></template> -->\n  </AuthPageLayout>\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/layouts/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { NotificationItem } from '@vben/layouts';\n\nimport { computed, ref, watch } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { AuthenticationLoginExpiredModal } from '@vben/common-ui';\nimport { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';\nimport { useWatermark } from '@vben/hooks';\nimport { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons';\nimport {\n  BasicLayout,\n  LockScreen,\n  Notification,\n  UserDropdown,\n} from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport { $t } from '#/locales';\nimport { useAuthStore } from '#/store';\nimport LoginForm from '#/views/_core/authentication/login.vue';\n\nconst notifications = ref<NotificationItem[]>([\n  {\n    id: 1,\n    avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',\n    date: '3小时前',\n    isRead: true,\n    message: '描述信息描述信息描述信息',\n    title: '收到了 14 份新周报',\n  },\n  {\n    id: 2,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '刚刚',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '朱偏右 回复了你',\n  },\n  {\n    id: 3,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '2024-01-01',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '曲丽丽 评论了你',\n  },\n  {\n    id: 4,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '代办提醒',\n  },\n  {\n    id: 5,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转Workspace示例',\n    link: '/workspace',\n  },\n  {\n    id: 6,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转外部链接示例',\n    link: 'https://doc.vben.pro',\n  },\n]);\n\nconst router = useRouter();\nconst userStore = useUserStore();\nconst authStore = useAuthStore();\nconst accessStore = useAccessStore();\nconst { destroyWatermark, updateWatermark } = useWatermark();\nconst showDot = computed(() =>\n  notifications.value.some((item) => !item.isRead),\n);\n\nconst menus = computed(() => [\n  {\n    handler: () => {\n      router.push({ name: 'Profile' });\n    },\n    icon: 'lucide:user',\n    text: $t('page.auth.profile'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_DOC_URL, {\n        target: '_blank',\n      });\n    },\n    icon: BookOpenText,\n    text: $t('ui.widgets.document'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_GITHUB_URL, {\n        target: '_blank',\n      });\n    },\n    icon: SvgGithubIcon,\n    text: 'GitHub',\n  },\n  {\n    handler: () => {\n      openWindow(`${VBEN_GITHUB_URL}/issues`, {\n        target: '_blank',\n      });\n    },\n    icon: CircleHelp,\n    text: $t('ui.widgets.qa'),\n  },\n]);\n\nconst avatar = computed(() => {\n  return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;\n});\n\nasync function handleLogout() {\n  await authStore.logout(false);\n}\n\nfunction handleNoticeClear() {\n  notifications.value = [];\n}\n\nfunction markRead(id: number | string) {\n  const item = notifications.value.find((item) => item.id === id);\n  if (item) {\n    item.isRead = true;\n  }\n}\n\nfunction remove(id: number | string) {\n  notifications.value = notifications.value.filter((item) => item.id !== id);\n}\n\nfunction handleMakeAll() {\n  notifications.value.forEach((item) => (item.isRead = true));\n}\nwatch(\n  () => ({\n    enable: preferences.app.watermark,\n    content: preferences.app.watermarkContent,\n  }),\n  async ({ enable, content }) => {\n    if (enable) {\n      await updateWatermark({\n        content:\n          content ||\n          `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,\n      });\n    } else {\n      destroyWatermark();\n    }\n  },\n  {\n    immediate: true,\n  },\n);\n</script>\n\n<template>\n  <BasicLayout @clear-preferences-and-logout=\"handleLogout\">\n    <template #user-dropdown>\n      <UserDropdown\n        :avatar\n        :menus\n        :text=\"userStore.userInfo?.realName\"\n        description=\"ann.vben@gmail.com\"\n        tag-text=\"Pro\"\n        @logout=\"handleLogout\"\n      />\n    </template>\n    <template #notification>\n      <Notification\n        :dot=\"showDot\"\n        :notifications=\"notifications\"\n        @clear=\"handleNoticeClear\"\n        @read=\"(item) => item.id && markRead(item.id)\"\n        @remove=\"(item) => item.id && remove(item.id)\"\n        @make-all=\"handleMakeAll\"\n      />\n    </template>\n    <template #extra>\n      <AuthenticationLoginExpiredModal\n        v-model:open=\"accessStore.loginExpired\"\n        :avatar\n      >\n        <LoginForm />\n      </AuthenticationLoginExpiredModal>\n    </template>\n    <template #lock-screen>\n      <LockScreen :avatar @to-login=\"handleLogout\" />\n    </template>\n  </BasicLayout>\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/layouts/index.ts",
    "content": "const BasicLayout = () => import('./basic.vue');\nconst AuthPageLayout = () => import('./auth.vue');\n\nconst IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);\n\nexport { AuthPageLayout, BasicLayout, IFrameView };\n"
  },
  {
    "path": "apps/web-antd/src/locales/README.md",
    "content": "# locale\n\n每个app使用的国际化可能不同，这里用于扩展国际化的功能，例如扩展 dayjs、antd组件库的多语言切换，以及app本身的国际化文件。\n"
  },
  {
    "path": "apps/web-antd/src/locales/index.ts",
    "content": "import type { Locale } from 'ant-design-vue/es/locale';\n\nimport type { App } from 'vue';\n\nimport type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';\n\nimport { ref } from 'vue';\n\nimport {\n  $t,\n  setupI18n as coreSetup,\n  loadLocalesMapFromDir,\n} from '@vben/locales';\nimport { preferences } from '@vben/preferences';\n\nimport antdEnLocale from 'ant-design-vue/es/locale/en_US';\nimport antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN';\nimport dayjs from 'dayjs';\n\nconst antdLocale = ref<Locale>(antdDefaultLocale);\n\nconst modules = import.meta.glob('./langs/**/*.json');\n\nconst localesMap = loadLocalesMapFromDir(\n  /\\.\\/langs\\/([^/]+)\\/(.*)\\.json$/,\n  modules,\n);\n/**\n * 加载应用特有的语言包\n * 这里也可以改造为从服务端获取翻译数据\n * @param lang\n */\nasync function loadMessages(lang: SupportedLanguagesType) {\n  const [appLocaleMessages] = await Promise.all([\n    localesMap[lang]?.(),\n    loadThirdPartyMessage(lang),\n  ]);\n  return appLocaleMessages?.default;\n}\n\n/**\n * 加载第三方组件库的语言包\n * @param lang\n */\nasync function loadThirdPartyMessage(lang: SupportedLanguagesType) {\n  await Promise.all([loadAntdLocale(lang), loadDayjsLocale(lang)]);\n}\n\n/**\n * 加载dayjs的语言包\n * @param lang\n */\nasync function loadDayjsLocale(lang: SupportedLanguagesType) {\n  let locale;\n  switch (lang) {\n    case 'en-US': {\n      locale = await import('dayjs/locale/en');\n      break;\n    }\n    case 'zh-CN': {\n      locale = await import('dayjs/locale/zh-cn');\n      break;\n    }\n    // 默认使用英语\n    default: {\n      locale = await import('dayjs/locale/en');\n    }\n  }\n  if (locale) {\n    dayjs.locale(locale);\n  } else {\n    console.error(`Failed to load dayjs locale for ${lang}`);\n  }\n}\n\n/**\n * 加载antd的语言包\n * @param lang\n */\nasync function loadAntdLocale(lang: SupportedLanguagesType) {\n  switch (lang) {\n    case 'en-US': {\n      antdLocale.value = antdEnLocale;\n      break;\n    }\n    case 'zh-CN': {\n      antdLocale.value = antdDefaultLocale;\n      break;\n    }\n  }\n}\n\nasync function setupI18n(app: App, options: LocaleSetupOptions = {}) {\n  await coreSetup(app, {\n    defaultLocale: preferences.app.locale,\n    loadMessages,\n    missingWarn: !import.meta.env.PROD,\n    ...options,\n  });\n}\n\nexport { $t, antdLocale, setupI18n };\n"
  },
  {
    "path": "apps/web-antd/src/locales/langs/en-US/demos.json",
    "content": "{\n  \"title\": \"Demos\",\n  \"antd\": \"Ant Design Vue\",\n  \"vben\": {\n    \"title\": \"Project\",\n    \"about\": \"About\",\n    \"document\": \"Document\",\n    \"antdv\": \"Ant Design Vue Version\",\n    \"antdv-next\": \"Antdv Next Version\",\n    \"naive-ui\": \"Naive UI Version\",\n    \"element-plus\": \"Element Plus Version\",\n    \"tdesign\": \"TDesign Vue Version\"\n  }\n}\n"
  },
  {
    "path": "apps/web-antd/src/locales/langs/en-US/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"Login\",\n    \"register\": \"Register\",\n    \"codeLogin\": \"Code Login\",\n    \"qrcodeLogin\": \"Qr Code Login\",\n    \"forgetPassword\": \"Forget Password\",\n    \"profile\": \"Profile\"\n  },\n  \"dashboard\": {\n    \"title\": \"Dashboard\",\n    \"analytics\": \"Analytics\",\n    \"workspace\": \"Workspace\"\n  }\n}\n"
  },
  {
    "path": "apps/web-antd/src/locales/langs/zh-CN/demos.json",
    "content": "{\n  \"title\": \"演示\",\n  \"antd\": \"Ant Design Vue\",\n  \"vben\": {\n    \"title\": \"项目\",\n    \"about\": \"关于\",\n    \"document\": \"文档\",\n    \"antdv\": \"Ant Design Vue 版本\",\n    \"antdv-next\": \"Antdv Next 版本\",\n    \"naive-ui\": \"Naive UI 版本\",\n    \"element-plus\": \"Element Plus 版本\",\n    \"tdesign\": \"TDesign Vue 版本\"\n  }\n}\n"
  },
  {
    "path": "apps/web-antd/src/locales/langs/zh-CN/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"登录\",\n    \"register\": \"注册\",\n    \"codeLogin\": \"验证码登录\",\n    \"qrcodeLogin\": \"二维码登录\",\n    \"forgetPassword\": \"忘记密码\",\n    \"profile\": \"个人中心\"\n  },\n  \"dashboard\": {\n    \"title\": \"概览\",\n    \"analytics\": \"分析页\",\n    \"workspace\": \"工作台\"\n  }\n}\n"
  },
  {
    "path": "apps/web-antd/src/main.ts",
    "content": "import { initPreferences } from '@vben/preferences';\nimport { unmountGlobalLoading } from '@vben/utils';\n\nimport { overridesPreferences } from './preferences';\n\n/**\n * 应用初始化完成之后再进行页面加载渲染\n */\nasync function initApplication() {\n  // name用于指定项目唯一标识\n  // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据\n  const env = import.meta.env.PROD ? 'prod' : 'dev';\n  const appVersion = import.meta.env.VITE_APP_VERSION;\n  const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;\n\n  // app偏好设置初始化\n  await initPreferences({\n    namespace,\n    overrides: overridesPreferences,\n  });\n\n  // 启动应用并挂载\n  // vue应用主要逻辑及视图\n  const { bootstrap } = await import('./bootstrap');\n  await bootstrap(namespace);\n\n  // 移除并销毁loading\n  unmountGlobalLoading();\n}\n\ninitApplication();\n"
  },
  {
    "path": "apps/web-antd/src/preferences.ts",
    "content": "import { defineOverridesPreferences } from '@vben/preferences';\n\n/**\n * @description 项目配置文件\n * 只需要覆盖项目中的一部分配置，不需要的配置不用覆盖，会自动使用默认配置\n * !!! 更改配置后请清空缓存，否则可能不生效\n */\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    name: import.meta.env.VITE_APP_TITLE,\n  },\n});\n"
  },
  {
    "path": "apps/web-antd/src/router/access.ts",
    "content": "import type {\n  ComponentRecordType,\n  GenerateMenuAndRoutesOptions,\n} from '@vben/types';\n\nimport { generateAccessible } from '@vben/access';\nimport { preferences } from '@vben/preferences';\n\nimport { message } from 'ant-design-vue';\n\nimport { getAllMenusApi } from '#/api';\nimport { BasicLayout, IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');\n\nasync function generateAccess(options: GenerateMenuAndRoutesOptions) {\n  const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');\n\n  const layoutMap: ComponentRecordType = {\n    BasicLayout,\n    IFrameView,\n  };\n\n  return await generateAccessible(preferences.app.accessMode, {\n    ...options,\n    fetchMenuListAsync: async () => {\n      message.loading({\n        content: `${$t('common.loadingMenu')}...`,\n        duration: 1.5,\n      });\n      return await getAllMenusApi();\n    },\n    // 可以指定没有权限跳转403页面\n    forbiddenComponent,\n    // 如果 route.meta.menuVisibleWithForbidden = true\n    layoutMap,\n    pageMap,\n  });\n}\n\nexport { generateAccess };\n"
  },
  {
    "path": "apps/web-antd/src/router/guard.ts",
    "content": "import type { Router } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { startProgress, stopProgress } from '@vben/utils';\n\nimport { accessRoutes, coreRouteNames } from '#/router/routes';\nimport { useAuthStore } from '#/store';\n\nimport { generateAccess } from './access';\n\n/**\n * 通用守卫配置\n * @param router\n */\nfunction setupCommonGuard(router: Router) {\n  // 记录已经加载的页面\n  const loadedPaths = new Set<string>();\n\n  router.beforeEach((to) => {\n    to.meta.loaded = loadedPaths.has(to.path);\n\n    // 页面加载进度条\n    if (!to.meta.loaded && preferences.transition.progress) {\n      startProgress();\n    }\n    return true;\n  });\n\n  router.afterEach((to) => {\n    // 记录页面是否加载,如果已经加载，后续的页面切换动画等效果不在重复执行\n\n    loadedPaths.add(to.path);\n\n    // 关闭页面加载进度条\n    if (preferences.transition.progress) {\n      stopProgress();\n    }\n  });\n}\n\n/**\n * 权限访问守卫配置\n * @param router\n */\nfunction setupAccessGuard(router: Router) {\n  router.beforeEach(async (to, from) => {\n    const accessStore = useAccessStore();\n    const userStore = useUserStore();\n    const authStore = useAuthStore();\n\n    // 基本路由，这些路由不需要进入权限拦截\n    if (coreRouteNames.includes(to.name as string)) {\n      if (to.path === LOGIN_PATH && accessStore.accessToken) {\n        return decodeURIComponent(\n          (to.query?.redirect as string) ||\n            userStore.userInfo?.homePath ||\n            preferences.app.defaultHomePath,\n        );\n      }\n      return true;\n    }\n\n    // accessToken 检查\n    if (!accessStore.accessToken) {\n      // 明确声明忽略权限访问权限，则可以访问\n      if (to.meta.ignoreAccess) {\n        return true;\n      }\n\n      // 没有访问权限，跳转登录页面\n      if (to.fullPath !== LOGIN_PATH) {\n        return {\n          path: LOGIN_PATH,\n          // 如不需要，直接删除 query\n          query:\n            to.fullPath === preferences.app.defaultHomePath\n              ? {}\n              : { redirect: encodeURIComponent(to.fullPath) },\n          // 携带当前跳转的页面，登录后重新跳转该页面\n          replace: true,\n        };\n      }\n      return to;\n    }\n\n    // 是否已经生成过动态路由\n    if (accessStore.isAccessChecked) {\n      return true;\n    }\n\n    // 生成路由表\n    // 当前登录用户拥有的角色标识列表\n    const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());\n    const userRoles = userInfo.roles ?? [];\n\n    // 生成菜单和路由\n    const { accessibleMenus, accessibleRoutes } = await generateAccess({\n      roles: userRoles,\n      router,\n      // 则会在菜单中显示，但是访问会被重定向到403\n      routes: accessRoutes,\n    });\n\n    // 保存菜单信息和路由信息\n    accessStore.setAccessMenus(accessibleMenus);\n    accessStore.setAccessRoutes(accessibleRoutes);\n    accessStore.setIsAccessChecked(true);\n    const redirectPath = (from.query.redirect ??\n      (to.path === preferences.app.defaultHomePath\n        ? userInfo.homePath || preferences.app.defaultHomePath\n        : to.fullPath)) as string;\n\n    return {\n      ...router.resolve(decodeURIComponent(redirectPath)),\n      replace: true,\n    };\n  });\n}\n\n/**\n * 项目守卫配置\n * @param router\n */\nfunction createRouterGuard(router: Router) {\n  /** 通用 */\n  setupCommonGuard(router);\n  /** 权限访问 */\n  setupAccessGuard(router);\n}\n\nexport { createRouterGuard };\n"
  },
  {
    "path": "apps/web-antd/src/router/index.ts",
    "content": "import {\n  createRouter,\n  createWebHashHistory,\n  createWebHistory,\n} from 'vue-router';\n\nimport { resetStaticRoutes } from '@vben/utils';\n\nimport { createRouterGuard } from './guard';\nimport { routes } from './routes';\n\n/**\n *  @zh_CN 创建vue-router实例\n */\nconst router = createRouter({\n  history:\n    import.meta.env.VITE_ROUTER_HISTORY === 'hash'\n      ? createWebHashHistory(import.meta.env.VITE_BASE)\n      : createWebHistory(import.meta.env.VITE_BASE),\n  // 应该添加到路由的初始路由列表。\n  routes,\n  scrollBehavior: (to, _from, savedPosition) => {\n    if (savedPosition) {\n      return savedPosition;\n    }\n    return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };\n  },\n  // 是否应该禁止尾部斜杠。\n  // strict: true,\n});\n\nconst resetRoutes = () => resetStaticRoutes(router, routes);\n\n// 创建路由守卫\ncreateRouterGuard(router);\n\nexport { resetRoutes, router };\n"
  },
  {
    "path": "apps/web-antd/src/router/routes/core.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst BasicLayout = () => import('#/layouts/basic.vue');\nconst AuthPageLayout = () => import('#/layouts/auth.vue');\n/** 全局404页面 */\nconst fallbackNotFoundRoute: RouteRecordRaw = {\n  component: () => import('#/views/_core/fallback/not-found.vue'),\n  meta: {\n    hideInBreadcrumb: true,\n    hideInMenu: true,\n    hideInTab: true,\n    title: '404',\n  },\n  name: 'FallbackNotFound',\n  path: '/:path(.*)*',\n};\n\n/** 基本路由，这些路由是必须存在的 */\nconst coreRoutes: RouteRecordRaw[] = [\n  /**\n   * 根路由\n   * 使用基础布局，作为所有页面的父级容器，子级就不必配置BasicLayout。\n   * 此路由必须存在，且不应修改\n   */\n  {\n    component: BasicLayout,\n    meta: {\n      hideInBreadcrumb: true,\n      title: 'Root',\n    },\n    name: 'Root',\n    path: '/',\n    redirect: preferences.app.defaultHomePath,\n    children: [],\n  },\n  {\n    component: AuthPageLayout,\n    meta: {\n      hideInTab: true,\n      title: 'Authentication',\n    },\n    name: 'Authentication',\n    path: '/auth',\n    redirect: LOGIN_PATH,\n    children: [\n      {\n        name: 'Login',\n        path: 'login',\n        component: () => import('#/views/_core/authentication/login.vue'),\n        meta: {\n          title: $t('page.auth.login'),\n        },\n      },\n      {\n        name: 'CodeLogin',\n        path: 'code-login',\n        component: () => import('#/views/_core/authentication/code-login.vue'),\n        meta: {\n          title: $t('page.auth.codeLogin'),\n        },\n      },\n      {\n        name: 'QrCodeLogin',\n        path: 'qrcode-login',\n        component: () =>\n          import('#/views/_core/authentication/qrcode-login.vue'),\n        meta: {\n          title: $t('page.auth.qrcodeLogin'),\n        },\n      },\n      {\n        name: 'ForgetPassword',\n        path: 'forget-password',\n        component: () =>\n          import('#/views/_core/authentication/forget-password.vue'),\n        meta: {\n          title: $t('page.auth.forgetPassword'),\n        },\n      },\n      {\n        name: 'Register',\n        path: 'register',\n        component: () => import('#/views/_core/authentication/register.vue'),\n        meta: {\n          title: $t('page.auth.register'),\n        },\n      },\n    ],\n  },\n];\n\nexport { coreRoutes, fallbackNotFoundRoute };\n"
  },
  {
    "path": "apps/web-antd/src/router/routes/index.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { mergeRouteModules, traverseTreeValues } from '@vben/utils';\n\nimport { coreRoutes, fallbackNotFoundRoute } from './core';\n\nconst dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {\n  eager: true,\n});\n\n// 有需要可以自行打开注释，并创建文件夹\n// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });\n// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });\n\n/** 动态路由 */\nconst dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);\n\n/** 外部路由列表，访问这些页面可以不需要Layout，可能用于内嵌在别的系统(不会显示在菜单中) */\n// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);\n// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);\nconst staticRoutes: RouteRecordRaw[] = [];\nconst externalRoutes: RouteRecordRaw[] = [];\n\n/** 路由列表，由基本路由、外部路由和404兜底路由组成\n *  无需走权限验证（会一直显示在菜单中） */\nconst routes: RouteRecordRaw[] = [\n  ...coreRoutes,\n  ...externalRoutes,\n  fallbackNotFoundRoute,\n];\n\n/** 基本路由列表，这些路由不需要进入权限拦截 */\nconst coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);\n\n/** 有权限校验的路由列表，包含动态路由和静态路由 */\nconst accessRoutes = [...dynamicRoutes, ...staticRoutes];\nexport { accessRoutes, coreRouteNames, routes };\n"
  },
  {
    "path": "apps/web-antd/src/router/routes/modules/dashboard.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'lucide:layout-dashboard',\n      order: -1,\n      title: $t('page.dashboard.title'),\n    },\n    name: 'Dashboard',\n    path: '/dashboard',\n    children: [\n      {\n        name: 'Analytics',\n        path: '/analytics',\n        component: () => import('#/views/dashboard/analytics/index.vue'),\n        meta: {\n          affixTab: true,\n          icon: 'lucide:area-chart',\n          title: $t('page.dashboard.analytics'),\n        },\n      },\n      {\n        name: 'Workspace',\n        path: '/workspace',\n        component: () => import('#/views/dashboard/workspace/index.vue'),\n        meta: {\n          icon: 'carbon:workspace',\n          title: $t('page.dashboard.workspace'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-antd/src/router/routes/modules/demos.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'ic:baseline-view-in-ar',\n      keepAlive: true,\n      order: 1000,\n      title: $t('demos.title'),\n    },\n    name: 'Demos',\n    path: '/demos',\n    children: [\n      {\n        meta: {\n          title: $t('demos.antd'),\n        },\n        name: 'AntDesignDemos',\n        path: '/demos/ant-design',\n        component: () => import('#/views/demos/antd/index.vue'),\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-antd/src/router/routes/modules/vben.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport {\n  VBEN_ANTDV_NEXT_PREVIEW_URL,\n  VBEN_DOC_URL,\n  VBEN_ELE_PREVIEW_URL,\n  VBEN_GITHUB_URL,\n  VBEN_LOGO_URL,\n  VBEN_NAIVE_PREVIEW_URL,\n  VBEN_TD_PREVIEW_URL,\n} from '@vben/constants';\nimport { SvgAntdvNextLogoIcon, SvgTDesignIcon } from '@vben/icons';\n\nimport { IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      badgeType: 'dot',\n      icon: VBEN_LOGO_URL,\n      order: 9998,\n      title: $t('demos.vben.title'),\n    },\n    name: 'VbenProject',\n    path: '/vben-admin',\n    children: [\n      {\n        name: 'VbenDocument',\n        path: '/vben-admin/document',\n        component: IFrameView,\n        meta: {\n          icon: 'lucide:book-open-text',\n          link: VBEN_DOC_URL,\n          title: $t('demos.vben.document'),\n        },\n      },\n      {\n        name: 'VbenGithub',\n        path: '/vben-admin/github',\n        component: IFrameView,\n        meta: {\n          icon: 'mdi:github',\n          link: VBEN_GITHUB_URL,\n          title: 'Github',\n        },\n      },\n      {\n        name: 'VbenAntdVNext',\n        path: '/vben-admin/antdv-next',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgAntdvNextLogoIcon,\n          link: VBEN_ANTDV_NEXT_PREVIEW_URL,\n          title: $t('demos.vben.antdv-next'),\n        },\n      },\n      {\n        name: 'VbenNaive',\n        path: '/vben-admin/naive',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: 'logos:naiveui',\n          link: VBEN_NAIVE_PREVIEW_URL,\n          title: $t('demos.vben.naive-ui'),\n        },\n      },\n      {\n        name: 'VbenTDesign',\n        path: '/vben-admin/tdesign',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgTDesignIcon,\n          link: VBEN_TD_PREVIEW_URL,\n          title: $t('demos.vben.tdesign'),\n        },\n      },\n      {\n        name: 'VbenElementPlus',\n        path: '/vben-admin/ele',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: 'logos:element',\n          link: VBEN_ELE_PREVIEW_URL,\n          title: $t('demos.vben.element-plus'),\n        },\n      },\n    ],\n  },\n  {\n    name: 'VbenAbout',\n    path: '/vben-admin/about',\n    component: () => import('#/views/_core/about/index.vue'),\n    meta: {\n      icon: 'lucide:copyright',\n      title: $t('demos.vben.about'),\n      order: 9999,\n    },\n  },\n  {\n    name: 'Profile',\n    path: '/profile',\n    component: () => import('#/views/_core/profile/index.vue'),\n    meta: {\n      icon: 'lucide:user',\n      hideInMenu: true,\n      title: $t('page.auth.profile'),\n    },\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-antd/src/store/auth.ts",
    "content": "import type { Recordable, UserInfo } from '@vben/types';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';\n\nimport { notification } from 'ant-design-vue';\nimport { defineStore } from 'pinia';\n\nimport { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';\nimport { $t } from '#/locales';\n\nexport const useAuthStore = defineStore('auth', () => {\n  const accessStore = useAccessStore();\n  const userStore = useUserStore();\n  const router = useRouter();\n\n  const loginLoading = ref(false);\n\n  /**\n   * 异步处理登录操作\n   * Asynchronously handle the login process\n   * @param params 登录表单数据\n   */\n  async function authLogin(\n    params: Recordable<any>,\n    onSuccess?: () => Promise<void> | void,\n  ) {\n    // 异步处理用户登录操作并获取 accessToken\n    let userInfo: null | UserInfo = null;\n    try {\n      loginLoading.value = true;\n      const { accessToken } = await loginApi(params);\n\n      // 如果成功获取到 accessToken\n      if (accessToken) {\n        accessStore.setAccessToken(accessToken);\n\n        // 获取用户信息并存储到 accessStore 中\n        const [fetchUserInfoResult, accessCodes] = await Promise.all([\n          fetchUserInfo(),\n          getAccessCodesApi(),\n        ]);\n\n        userInfo = fetchUserInfoResult;\n\n        userStore.setUserInfo(userInfo);\n        accessStore.setAccessCodes(accessCodes);\n\n        if (accessStore.loginExpired) {\n          accessStore.setLoginExpired(false);\n        } else {\n          onSuccess\n            ? await onSuccess?.()\n            : await router.push(\n                userInfo.homePath || preferences.app.defaultHomePath,\n              );\n        }\n\n        if (userInfo?.realName) {\n          notification.success({\n            description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,\n            duration: 3,\n            message: $t('authentication.loginSuccess'),\n          });\n        }\n      }\n    } finally {\n      loginLoading.value = false;\n    }\n\n    return {\n      userInfo,\n    };\n  }\n\n  async function logout(redirect: boolean = true) {\n    try {\n      await logoutApi();\n    } catch {\n      // 不做任何处理\n    }\n    resetAllStores();\n    accessStore.setLoginExpired(false);\n\n    // 回登录页带上当前路由地址\n    await router.replace({\n      path: LOGIN_PATH,\n      query: redirect\n        ? {\n            redirect: encodeURIComponent(router.currentRoute.value.fullPath),\n          }\n        : {},\n    });\n  }\n\n  async function fetchUserInfo() {\n    const userInfo = await getUserInfoApi();\n    userStore.setUserInfo(userInfo);\n    return userInfo;\n  }\n\n  function $reset() {\n    loginLoading.value = false;\n  }\n\n  return {\n    $reset,\n    authLogin,\n    fetchUserInfo,\n    loginLoading,\n    logout,\n  };\n});\n"
  },
  {
    "path": "apps/web-antd/src/store/index.ts",
    "content": "export * from './auth';\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/README.md",
    "content": "# \\_core\n\n此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/about/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { About } from '@vben/common-ui';\n\ndefineOptions({ name: 'About' });\n</script>\n\n<template>\n  <About />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/authentication/code-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationCodeLogin, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'CodeLogin' });\n\nconst loading = ref(false);\nconst CODE_LENGTH = 6;\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.mobile'),\n      },\n      fieldName: 'phoneNumber',\n      label: $t('authentication.mobile'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.mobileTip') })\n        .refine((v) => /^\\d{11}$/.test(v), {\n          message: $t('authentication.mobileErrortip'),\n        }),\n    },\n    {\n      component: 'VbenPinInput',\n      componentProps: {\n        codeLength: CODE_LENGTH,\n        createText: (countdown: number) => {\n          const text =\n            countdown > 0\n              ? $t('authentication.sendText', [countdown])\n              : $t('authentication.sendCode');\n          return text;\n        },\n        placeholder: $t('authentication.code'),\n      },\n      fieldName: 'code',\n      label: $t('authentication.code'),\n      rules: z.string().length(CODE_LENGTH, {\n        message: $t('authentication.codeTip', [CODE_LENGTH]),\n      }),\n    },\n  ];\n});\n/**\n * 异步处理登录操作\n * Asynchronously handle the login process\n * @param values 登录表单数据\n */\nasync function handleLogin(values: Recordable<any>) {\n  void values;\n}\n</script>\n\n<template>\n  <AuthenticationCodeLogin\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleLogin\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/authentication/forget-password.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationForgetPassword, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'ForgetPassword' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: 'example@example.com',\n      },\n      fieldName: 'email',\n      label: $t('authentication.email'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.emailTip') })\n        .email($t('authentication.emailValidErrorTip')),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationForgetPassword\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/authentication/login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { BasicOption } from '@vben/types';\n\nimport { computed, markRaw } from 'vue';\n\nimport { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { useAuthStore } from '#/store';\n\ndefineOptions({ name: 'Login' });\n\nconst authStore = useAuthStore();\n\nconst MOCK_USER_OPTIONS: BasicOption[] = [\n  {\n    label: 'Super',\n    value: 'vben',\n  },\n  {\n    label: 'Admin',\n    value: 'admin',\n  },\n  {\n    label: 'User',\n    value: 'jack',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenSelect',\n      componentProps: {\n        options: MOCK_USER_OPTIONS,\n        placeholder: $t('authentication.selectAccount'),\n      },\n      fieldName: 'selectAccount',\n      label: $t('authentication.selectAccount'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.selectAccount') })\n        .optional()\n        .default('vben'),\n    },\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      dependencies: {\n        trigger(values, form) {\n          if (values.selectAccount) {\n            const findUser = MOCK_USER_OPTIONS.find(\n              (item) => item.value === values.selectAccount,\n            );\n            if (findUser) {\n              form.setValues({\n                password: '123456',\n                username: findUser.value,\n              });\n            }\n          }\n        },\n        triggerFields: ['selectAccount'],\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: markRaw(SliderCaptcha),\n      fieldName: 'captcha',\n      rules: z.boolean().refine((value) => value, {\n        message: $t('authentication.verifyRequiredTip'),\n      }),\n    },\n  ];\n});\n</script>\n\n<template>\n  <AuthenticationLogin\n    :form-schema=\"formSchema\"\n    :loading=\"authStore.loginLoading\"\n    @submit=\"authStore.authLogin\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/authentication/qrcode-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport { AuthenticationQrCodeLogin } from '@vben/common-ui';\nimport { LOGIN_PATH } from '@vben/constants';\n\ndefineOptions({ name: 'QrCodeLogin' });\n</script>\n\n<template>\n  <AuthenticationQrCodeLogin :login-path=\"LOGIN_PATH\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/authentication/register.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, h, ref } from 'vue';\n\nimport { AuthenticationRegister, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'Register' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      renderComponentContent() {\n        return {\n          strengthText: () => $t('authentication.passwordStrength'),\n        };\n      },\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.confirmPassword'),\n      },\n      dependencies: {\n        rules(values) {\n          const { password } = values;\n          return z\n            .string({ required_error: $t('authentication.passwordTip') })\n            .min(1, { message: $t('authentication.passwordTip') })\n            .refine((value) => value === password, {\n              message: $t('authentication.confirmPasswordTip'),\n            });\n        },\n        triggerFields: ['password'],\n      },\n      fieldName: 'confirmPassword',\n      label: $t('authentication.confirmPassword'),\n    },\n    {\n      component: 'VbenCheckbox',\n      fieldName: 'agreePolicy',\n      renderComponentContent: () => ({\n        default: () =>\n          h('span', [\n            $t('authentication.agree'),\n            h(\n              'a',\n              {\n                class: 'vben-link ml-1 ',\n                href: '',\n              },\n              `${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,\n            ),\n          ]),\n      }),\n      rules: z.boolean().refine((value) => !!value, {\n        message: $t('authentication.agreeTip'),\n      }),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationRegister\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/fallback/coming-soon.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback status=\"coming-soon\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/fallback/forbidden.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback403Demo' });\n</script>\n\n<template>\n  <Fallback status=\"403\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/fallback/internal-error.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback500Demo' });\n</script>\n\n<template>\n  <Fallback status=\"500\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/fallback/not-found.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback404Demo' });\n</script>\n\n<template>\n  <Fallback status=\"404\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/fallback/offline.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'FallbackOfflineDemo' });\n</script>\n\n<template>\n  <Fallback status=\"offline\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/profile/base-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { BasicOption } from '@vben/types';\n\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed, onMounted, ref } from 'vue';\n\nimport { ProfileBaseSetting } from '@vben/common-ui';\n\nimport { getUserInfoApi } from '#/api';\n\nconst profileBaseSettingRef = ref();\n\nconst MOCK_ROLES_OPTIONS: BasicOption[] = [\n  {\n    label: '管理员',\n    value: 'super',\n  },\n  {\n    label: '用户',\n    value: 'user',\n  },\n  {\n    label: '测试',\n    value: 'test',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'realName',\n      component: 'Input',\n      label: '姓名',\n    },\n    {\n      fieldName: 'username',\n      component: 'Input',\n      label: '用户名',\n    },\n    {\n      fieldName: 'roles',\n      component: 'Select',\n      componentProps: {\n        mode: 'tags',\n        options: MOCK_ROLES_OPTIONS,\n      },\n      label: '角色',\n    },\n    {\n      fieldName: 'introduction',\n      component: 'Textarea',\n      label: '个人简介',\n    },\n  ];\n});\n\nonMounted(async () => {\n  const data = await getUserInfoApi();\n  profileBaseSettingRef.value.getFormApi().setValues(data);\n});\n</script>\n<template>\n  <ProfileBaseSetting ref=\"profileBaseSettingRef\" :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/profile/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\n\nimport { Profile } from '@vben/common-ui';\nimport { useUserStore } from '@vben/stores';\n\nimport ProfileBase from './base-setting.vue';\nimport ProfileNotificationSetting from './notification-setting.vue';\nimport ProfilePasswordSetting from './password-setting.vue';\nimport ProfileSecuritySetting from './security-setting.vue';\n\nconst userStore = useUserStore();\n\nconst tabsValue = ref<string>('basic');\n\nconst tabs = ref([\n  {\n    label: '基本设置',\n    value: 'basic',\n  },\n  {\n    label: '安全设置',\n    value: 'security',\n  },\n  {\n    label: '修改密码',\n    value: 'password',\n  },\n  {\n    label: '新消息提醒',\n    value: 'notice',\n  },\n]);\n</script>\n<template>\n  <Profile\n    v-model:model-value=\"tabsValue\"\n    title=\"个人中心\"\n    :user-info=\"userStore.userInfo\"\n    :tabs=\"tabs\"\n  >\n    <template #content>\n      <ProfileBase v-if=\"tabsValue === 'basic'\" />\n      <ProfileSecuritySetting v-if=\"tabsValue === 'security'\" />\n      <ProfilePasswordSetting v-if=\"tabsValue === 'password'\" />\n      <ProfileNotificationSetting v-if=\"tabsValue === 'notice'\" />\n    </template>\n  </Profile>\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/profile/notification-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileNotificationSetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '其他用户的消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'systemMessage',\n      label: '系统消息',\n      description: '系统消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'todoTask',\n      label: '待办任务',\n      description: '待办任务将以站内信的形式通知',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileNotificationSetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/profile/password-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed } from 'vue';\n\nimport { ProfilePasswordSetting, z } from '@vben/common-ui';\n\nimport { message } from 'ant-design-vue';\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'oldPassword',\n      label: '旧密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: '请输入旧密码',\n      },\n    },\n    {\n      fieldName: 'newPassword',\n      label: '新密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请输入新密码',\n      },\n    },\n    {\n      fieldName: 'confirmPassword',\n      label: '确认密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请再次输入新密码',\n      },\n      dependencies: {\n        rules(values) {\n          const { newPassword } = values;\n          return z\n            .string({ required_error: '请再次输入新密码' })\n            .min(1, { message: '请再次输入新密码' })\n            .refine((value) => value === newPassword, {\n              message: '两次输入的密码不一致',\n            });\n        },\n        triggerFields: ['newPassword'],\n      },\n    },\n  ];\n});\n\nfunction handleSubmit() {\n  message.success('密码修改成功');\n}\n</script>\n<template>\n  <ProfilePasswordSetting\n    class=\"w-1/3\"\n    :form-schema=\"formSchema\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/_core/profile/security-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileSecuritySetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '当前密码强度：强',\n    },\n    {\n      value: true,\n      fieldName: 'securityPhone',\n      label: '密保手机',\n      description: '已绑定手机：138****8293',\n    },\n    {\n      value: true,\n      fieldName: 'securityQuestion',\n      label: '密保问题',\n      description: '未设置密保问题，密保问题可有效保护账户安全',\n    },\n    {\n      value: true,\n      fieldName: 'securityEmail',\n      label: '备用邮箱',\n      description: '已绑定邮箱：ant***sign.com',\n    },\n    {\n      value: false,\n      fieldName: 'securityMfa',\n      label: 'MFA 设备',\n      description: '未绑定 MFA 设备，绑定后，可以进行二次确认',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileSecuritySetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        areaStyle: {},\n        data: [\n          111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,\n          36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,\n          111,\n        ],\n        itemStyle: {\n          color: '#5ab1ef',\n        },\n        smooth: true,\n        type: 'line',\n      },\n      {\n        areaStyle: {},\n        data: [\n          33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,\n          11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,\n        ],\n        itemStyle: {\n          color: '#019680',\n        },\n        smooth: true,\n        type: 'line',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          color: '#019680',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    // xAxis: {\n    //   axisTick: {\n    //     show: false,\n    //   },\n    //   boundaryGap: false,\n    //   data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n    //   type: 'category',\n    // },\n    xAxis: {\n      axisTick: {\n        show: false,\n      },\n      boundaryGap: false,\n      data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n      splitLine: {\n        lineStyle: {\n          type: 'solid',\n          width: 1,\n        },\n        show: true,\n      },\n      type: 'category',\n    },\n    yAxis: [\n      {\n        axisTick: {\n          show: false,\n        },\n        max: 80_000,\n        splitArea: {\n          show: true,\n        },\n        splitNumber: 4,\n        type: 'value',\n      },\n    ],\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: 0,\n      data: ['访问', '趋势'],\n    },\n    radar: {\n      indicator: [\n        {\n          name: '网页',\n        },\n        {\n          name: '移动端',\n        },\n        {\n          name: 'Ipad',\n        },\n        {\n          name: '客户端',\n        },\n        {\n          name: '第三方',\n        },\n        {\n          name: '其它',\n        },\n      ],\n      radius: '60%',\n      splitNumber: 8,\n    },\n    series: [\n      {\n        areaStyle: {\n          opacity: 1,\n          shadowBlur: 0,\n          shadowColor: 'rgba(0,0,0,.2)',\n          shadowOffsetX: 0,\n          shadowOffsetY: 10,\n        },\n        data: [\n          {\n            itemStyle: {\n              color: '#b6a2de',\n            },\n            name: '访问',\n            value: [90, 50, 86, 40, 50, 20],\n          },\n          {\n            itemStyle: {\n              color: '#5ab1ef',\n            },\n            name: '趋势',\n            value: [70, 75, 70, 76, 20, 85],\n          },\n        ],\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        symbolSize: 0,\n        type: 'radar',\n      },\n    ],\n    tooltip: {},\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 400;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        center: ['50%', '50%'],\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '外包', value: 500 },\n          { name: '定制', value: 310 },\n          { name: '技术支持', value: 274 },\n          { name: '远程', value: 400 },\n        ].toSorted((a, b) => {\n          return a.value - b.value;\n        }),\n        name: '商业占比',\n        radius: '80%',\n        roseType: 'radius',\n        type: 'pie',\n      },\n    ],\n\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: '2%',\n      left: 'center',\n    },\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 100;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        avoidLabelOverlap: false,\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '搜索引擎', value: 1048 },\n          { name: '直接访问', value: 735 },\n          { name: '邮件营销', value: 580 },\n          { name: '联盟广告', value: 484 },\n        ],\n        emphasis: {\n          label: {\n            fontSize: '12',\n            fontWeight: 'bold',\n            show: true,\n          },\n        },\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        label: {\n          position: 'center',\n          show: false,\n        },\n        labelLine: {\n          show: false,\n        },\n        name: '访问来源',\n        radius: ['40%', '65%'],\n        type: 'pie',\n      },\n    ],\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        barMaxWidth: 80,\n        // color: '#4f69fd',\n        data: [\n          3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,\n          3200, 4800,\n        ],\n        type: 'bar',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          // color: '#4f69fd',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    xAxis: {\n      data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),\n      type: 'category',\n    },\n    yAxis: {\n      max: 8000,\n      splitNumber: 4,\n      type: 'value',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/dashboard/analytics/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { AnalysisOverviewItem } from '@vben/common-ui';\nimport type { TabOption } from '@vben/types';\n\nimport {\n  AnalysisChartCard,\n  AnalysisChartsTabs,\n  AnalysisOverview,\n} from '@vben/common-ui';\nimport {\n  SvgBellIcon,\n  SvgCakeIcon,\n  SvgCardIcon,\n  SvgDownloadIcon,\n} from '@vben/icons';\n\nimport AnalyticsTrends from './analytics-trends.vue';\nimport AnalyticsVisitsData from './analytics-visits-data.vue';\nimport AnalyticsVisitsSales from './analytics-visits-sales.vue';\nimport AnalyticsVisitsSource from './analytics-visits-source.vue';\nimport AnalyticsVisits from './analytics-visits.vue';\n\nconst overviewItems: AnalysisOverviewItem[] = [\n  {\n    icon: SvgCardIcon,\n    title: '用户量',\n    totalTitle: '总用户量',\n    totalValue: 120_000,\n    value: 2000,\n  },\n  {\n    icon: SvgCakeIcon,\n    title: '访问量',\n    totalTitle: '总访问量',\n    totalValue: 500_000,\n    value: 20_000,\n  },\n  {\n    icon: SvgDownloadIcon,\n    title: '下载量',\n    totalTitle: '总下载量',\n    totalValue: 120_000,\n    value: 8000,\n  },\n  {\n    icon: SvgBellIcon,\n    title: '使用量',\n    totalTitle: '总使用量',\n    totalValue: 50_000,\n    value: 5000,\n  },\n];\n\nconst chartTabs: TabOption[] = [\n  {\n    label: '流量趋势',\n    value: 'trends',\n  },\n  {\n    label: '月访问量',\n    value: 'visits',\n  },\n];\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <AnalysisOverview :items=\"overviewItems\" />\n    <AnalysisChartsTabs :tabs=\"chartTabs\" class=\"mt-5\">\n      <template #trends>\n        <AnalyticsTrends />\n      </template>\n      <template #visits>\n        <AnalyticsVisits />\n      </template>\n    </AnalysisChartsTabs>\n\n    <div class=\"mt-5 w-full md:flex\">\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问数量\">\n        <AnalyticsVisitsData />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSource />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSales />\n      </AnalysisChartCard>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/dashboard/workspace/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  WorkbenchProjectItem,\n  WorkbenchQuickNavItem,\n  WorkbenchTodoItem,\n  WorkbenchTrendItem,\n} from '@vben/common-ui';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport {\n  AnalysisChartCard,\n  WorkbenchHeader,\n  WorkbenchProject,\n  WorkbenchQuickNav,\n  WorkbenchTodo,\n  WorkbenchTrends,\n} from '@vben/common-ui';\nimport { preferences } from '@vben/preferences';\nimport { useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';\n\nconst userStore = useUserStore();\n\n// 这是一个示例数据，实际项目中需要根据实际情况进行调整\n// url 也可以是内部路由，在 navTo 方法中识别处理，进行内部跳转\n// 例如：url: /dashboard/workspace\nconst projectItems: WorkbenchProjectItem[] = [\n  {\n    color: '',\n    content: '不要等待机会，而要创造机会。',\n    date: '2021-04-01',\n    group: '开源组',\n    icon: 'carbon:logo-github',\n    title: 'Github',\n    url: 'https://github.com',\n  },\n  {\n    color: '#3fb27f',\n    content: '现在的你决定将来的你。',\n    date: '2021-04-01',\n    group: '算法组',\n    icon: 'ion:logo-vue',\n    title: 'Vue',\n    url: 'https://vuejs.org',\n  },\n  {\n    color: '#e18525',\n    content: '没有什么才能比努力更重要。',\n    date: '2021-04-01',\n    group: '上班摸鱼',\n    icon: 'ion:logo-html5',\n    title: 'Html5',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',\n  },\n  {\n    color: '#bf0c2c',\n    content: '热情和欲望可以突破一切难关。',\n    date: '2021-04-01',\n    group: 'UI',\n    icon: 'ion:logo-angular',\n    title: 'Angular',\n    url: 'https://angular.io',\n  },\n  {\n    color: '#00d8ff',\n    content: '健康的身体是实现目标的基石。',\n    date: '2021-04-01',\n    group: '技术牛',\n    icon: 'bx:bxl-react',\n    title: 'React',\n    url: 'https://reactjs.org',\n  },\n  {\n    color: '#EBD94E',\n    content: '路是走出来的，而不是空想出来的。',\n    date: '2021-04-01',\n    group: '架构组',\n    icon: 'ion:logo-javascript',\n    title: 'Js',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',\n  },\n];\n\n// 同样，这里的 url 也可以使用以 http 开头的外部链接\nconst quickNavItems: WorkbenchQuickNavItem[] = [\n  {\n    color: '#1fdaca',\n    icon: 'ion:home-outline',\n    title: '首页',\n    url: '/',\n  },\n  {\n    color: '#bf0c2c',\n    icon: 'ion:grid-outline',\n    title: '仪表盘',\n    url: '/dashboard',\n  },\n  {\n    color: '#e18525',\n    icon: 'ion:layers-outline',\n    title: '组件',\n    url: '/demos/features/icons',\n  },\n  {\n    color: '#3fb27f',\n    icon: 'ion:settings-outline',\n    title: '系统管理',\n    url: '/demos/features/login-expired', // 这里的 URL 是示例，实际项目中需要根据实际情况进行调整\n  },\n  {\n    color: '#4daf1bc9',\n    icon: 'ion:key-outline',\n    title: '权限管理',\n    url: '/demos/access/page-control',\n  },\n  {\n    color: '#00d8ff',\n    icon: 'ion:bar-chart-outline',\n    title: '图表',\n    url: '/analytics',\n  },\n];\n\nconst todoItems = ref<WorkbenchTodoItem[]>([\n  {\n    completed: false,\n    content: `审查最近提交到Git仓库的前端代码，确保代码质量和规范。`,\n    date: '2024-07-30 11:00:00',\n    title: '审查前端代码提交',\n  },\n  {\n    completed: true,\n    content: `检查并优化系统性能，降低CPU使用率。`,\n    date: '2024-07-30 11:00:00',\n    title: '系统性能优化',\n  },\n  {\n    completed: false,\n    content: `进行系统安全检查，确保没有安全漏洞或未授权的访问。 `,\n    date: '2024-07-30 11:00:00',\n    title: '安全检查',\n  },\n  {\n    completed: false,\n    content: `更新项目中的所有npm依赖包，确保使用最新版本。`,\n    date: '2024-07-30 11:00:00',\n    title: '更新项目依赖',\n  },\n  {\n    completed: false,\n    content: `修复用户报告的页面UI显示问题，确保在不同浏览器中显示一致。 `,\n    date: '2024-07-30 11:00:00',\n    title: '修复UI显示问题',\n  },\n]);\nconst trendItems: WorkbenchTrendItem[] = [\n  {\n    avatar: 'svg:avatar-1',\n    content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,\n    date: '刚刚',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关注了 <a>威廉</a> `,\n    date: '1个小时前',\n    title: '艾文',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1天前',\n    title: '克里斯',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写一个Vite插件</a> `,\n    date: '2天前',\n    title: 'Vben',\n  },\n  {\n    avatar: 'svg:avatar-1',\n    content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化？</a>`,\n    date: '3天前',\n    title: '皮特',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关闭了问题 <a>如何运行项目</a> `,\n    date: '1周前',\n    title: '杰克',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1周前',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `推送了代码到 <a>Github</a>`,\n    date: '2021-04-01 20:00',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写使用 Admin Vben</a> `,\n    date: '2021-03-01 20:00',\n    title: 'Vben',\n  },\n];\n\nconst router = useRouter();\n\n// 这是一个示例方法，实际项目中需要根据实际情况进行调整\n// This is a sample method, adjust according to the actual project requirements\nfunction navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {\n  if (nav.url?.startsWith('http')) {\n    openWindow(nav.url);\n    return;\n  }\n  if (nav.url?.startsWith('/')) {\n    router.push(nav.url).catch((error) => {\n      console.error('Navigation failed:', error);\n    });\n  } else {\n    console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);\n  }\n}\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <WorkbenchHeader\n      :avatar=\"userStore.userInfo?.avatar || preferences.app.defaultAvatar\"\n    >\n      <template #title>\n        早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧！\n      </template>\n      <template #description> 今日晴，20℃ - 32℃！ </template>\n    </WorkbenchHeader>\n\n    <div class=\"mt-5 flex flex-col lg:flex-row\">\n      <div class=\"mr-4 w-full lg:w-3/5\">\n        <WorkbenchProject :items=\"projectItems\" title=\"项目\" @click=\"navTo\" />\n        <WorkbenchTrends :items=\"trendItems\" class=\"mt-5\" title=\"最新动态\" />\n      </div>\n      <div class=\"w-full lg:w-2/5\">\n        <WorkbenchQuickNav\n          :items=\"quickNavItems\"\n          class=\"mt-5 lg:mt-0\"\n          title=\"快捷导航\"\n          @click=\"navTo\"\n        />\n        <WorkbenchTodo :items=\"todoItems\" class=\"mt-5\" title=\"待办事项\" />\n        <AnalysisChartCard class=\"mt-5\" title=\"访问来源\">\n          <AnalyticsVisitsSource />\n        </AnalysisChartCard>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "apps/web-antd/src/views/demos/antd/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Page } from '@vben/common-ui';\n\nimport { Button, Card, message, notification, Space } from 'ant-design-vue';\n\ntype NotificationType = 'error' | 'info' | 'success' | 'warning';\n\nfunction info() {\n  message.info('How many roads must a man walk down');\n}\n\nfunction error() {\n  message.error({\n    content: 'Once upon a time you dressed so fine',\n    duration: 2500,\n  });\n}\n\nfunction warning() {\n  message.warning('How many roads must a man walk down');\n}\nfunction success() {\n  message.success('Cause you walked hand in hand With another man in my place');\n}\n\nfunction notify(type: NotificationType) {\n  notification[type]({\n    duration: 2500,\n    message: '说点啥呢',\n    type,\n  });\n}\n</script>\n\n<template>\n  <Page\n    description=\"支持多语言，主题功能集成切换等\"\n    title=\"Ant Design Vue组件使用演示\"\n  >\n    <Card class=\"mb-5\" title=\"按钮\">\n      <Space>\n        <Button>Default</Button>\n        <Button type=\"primary\"> Primary </Button>\n        <Button> Info </Button>\n        <Button danger> Error </Button>\n      </Space>\n    </Card>\n    <Card class=\"mb-5\" title=\"Message\">\n      <Space>\n        <Button @click=\"info\"> 信息 </Button>\n        <Button danger @click=\"error\"> 错误 </Button>\n        <Button @click=\"warning\"> 警告 </Button>\n        <Button @click=\"success\"> 成功 </Button>\n      </Space>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"Notification\">\n      <Space>\n        <Button @click=\"notify('info')\"> 信息 </Button>\n        <Button danger @click=\"notify('error')\"> 错误 </Button>\n        <Button @click=\"notify('warning')\"> 警告 </Button>\n        <Button @click=\"notify('success')\"> 成功 </Button>\n      </Space>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "apps/web-antd/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web-app.json\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"#/*\": [\"./src/*\"]\n    }\n  },\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }],\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\", \"src/**/*.vue\"]\n}\n"
  },
  {
    "path": "apps/web-antd/tsconfig.node.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"noEmit\": false\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "apps/web-antd/vite.config.ts",
    "content": "import { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    application: {},\n    vite: {\n      server: {\n        proxy: {\n          '/api': {\n            changeOrigin: true,\n            rewrite: (path) => path.replace(/^\\/api/, ''),\n            // mock代理目标地址\n            target: 'http://localhost:5320/api',\n            ws: true,\n          },\n        },\n      },\n    },\n  };\n});\n"
  },
  {
    "path": "apps/web-antdv-next/index.html",
    "content": "<!doctype html>\n<html lang=\"zh\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n    <meta name=\"renderer\" content=\"webkit\" />\n    <meta name=\"description\" content=\"A Modern Back-end Management System\" />\n    <meta name=\"keywords\" content=\"Vben Admin Vue3 Vite\" />\n    <meta name=\"author\" content=\"Vben\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0\"\n    />\n    <!-- 由 vite 注入 VITE_APP_TITLE 变量，在 .env 文件内配置 -->\n    <title><%= VITE_APP_TITLE %></title>\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <script>\n      // 生产环境下注入百度统计\n      if (window._VBEN_ADMIN_PRO_APP_CONF_) {\n        var _hmt = _hmt || [];\n        (function () {\n          var hm = document.createElement('script');\n          hm.src =\n            'https://hm.baidu.com/hm.js?b38e689f40558f20a9a686d7f6f33edf';\n          var s = document.getElementsByTagName('script')[0];\n          s.parentNode.insertBefore(hm, s);\n        })();\n      }\n    </script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "apps/web-antdv-next/package.json",
    "content": "{\n  \"name\": \"@vben/web-antdv-next\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://vben.pro\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"apps/web-antdv-next\"\n  },\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"vben\",\n    \"email\": \"ann.vben@gmail.com\",\n    \"url\": \"https://github.com/anncwb\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm vite build --mode production\",\n    \"build:analyze\": \"pnpm vite build --mode analyze\",\n    \"dev\": \"pnpm vite --mode development\",\n    \"preview\": \"vite preview\",\n    \"typecheck\": \"vue-tsc --noEmit --skipLibCheck\"\n  },\n  \"imports\": {\n    \"#/*\": \"./src/*\"\n  },\n  \"dependencies\": {\n    \"@vben/access\": \"workspace:*\",\n    \"@vben/common-ui\": \"workspace:*\",\n    \"@vben/constants\": \"workspace:*\",\n    \"@vben/hooks\": \"workspace:*\",\n    \"@vben/icons\": \"workspace:*\",\n    \"@vben/layouts\": \"workspace:*\",\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/plugins\": \"workspace:*\",\n    \"@vben/preferences\": \"workspace:*\",\n    \"@vben/request\": \"workspace:*\",\n    \"@vben/stores\": \"workspace:*\",\n    \"@vben/styles\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"antdv-next\": \"catalog:\",\n    \"dayjs\": \"catalog:\",\n    \"pinia\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "apps/web-antdv-next/src/adapter/component/index.ts",
    "content": "/**\n * 通用组件共同的使用的基础组件，原先放在 adapter/form 内部，限制了使用范围，这里提取出来，方便其他地方使用\n * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,\n */\n\n/* eslint-disable vue/one-component-per-file */\n\nimport type { UploadChangeParam, UploadFile, UploadProps } from 'antdv-next';\n\nimport type { Component, Ref } from 'vue';\n\nimport type { BaseFormComponentType } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport {\n  computed,\n  defineAsyncComponent,\n  defineComponent,\n  h,\n  ref,\n  render,\n  unref,\n  watch,\n} from 'vue';\n\nimport {\n  ApiComponent,\n  globalShareState,\n  IconPicker,\n  VCropper,\n} from '@vben/common-ui';\nimport { IconifyIcon } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport { isEmpty } from '@vben/utils';\n\nimport { message, Modal, notification } from 'antdv-next';\n\nconst AutoComplete = defineAsyncComponent(\n  () => import('antdv-next/dist/auto-complete/index'),\n);\nconst Button = defineAsyncComponent(\n  () => import('antdv-next/dist/button/index'),\n);\nconst Checkbox = defineAsyncComponent(\n  () => import('antdv-next/dist/checkbox/index'),\n);\nconst CheckboxGroup = defineAsyncComponent(\n  () => import('antdv-next/dist/checkbox/Group'),\n);\nconst DatePicker = defineAsyncComponent(\n  () => import('antdv-next/dist/date-picker/index'),\n);\nconst Divider = defineAsyncComponent(\n  () => import('antdv-next/dist/divider/index'),\n);\nconst Input = defineAsyncComponent(() => import('antdv-next/dist/input/index'));\nconst InputNumber = defineAsyncComponent(\n  () => import('antdv-next/dist/input-number/index'),\n);\nconst InputPassword = defineAsyncComponent(() =>\n  import('antdv-next/dist/input/index').then((res) => res.InputPassword),\n);\nconst Mentions = defineAsyncComponent(\n  () => import('antdv-next/dist/mentions/index'),\n);\nconst Radio = defineAsyncComponent(() => import('antdv-next/dist/radio/index'));\nconst RadioGroup = defineAsyncComponent(() =>\n  import('antdv-next/dist/radio/index').then((res) => res.RadioGroup),\n);\nconst RangePicker = defineAsyncComponent(() =>\n  import('antdv-next/dist/date-picker/index').then(\n    (res) => res.DateRangePicker,\n  ),\n);\nconst Rate = defineAsyncComponent(() => import('antdv-next/dist/rate/index'));\nconst Select = defineAsyncComponent(\n  () => import('antdv-next/dist/select/index'),\n);\nconst Space = defineAsyncComponent(() => import('antdv-next/dist/space/index'));\nconst Switch = defineAsyncComponent(\n  () => import('antdv-next/dist/switch/index'),\n);\nconst Textarea = defineAsyncComponent(\n  () => import('antdv-next/dist/input/TextArea'),\n);\nconst TimePicker = defineAsyncComponent(\n  () => import('antdv-next/dist/time-picker/index'),\n);\nconst TreeSelect = defineAsyncComponent(\n  () => import('antdv-next/dist/tree-select/index'),\n);\nconst Cascader = defineAsyncComponent(\n  () => import('antdv-next/dist/cascader/index'),\n);\nconst Upload = defineAsyncComponent(\n  () => import('antdv-next/dist/upload/index'),\n);\nconst Image = defineAsyncComponent(() => import('antdv-next/dist/image/index'));\nconst PreviewGroup = defineAsyncComponent(() =>\n  import('antdv-next/dist/image/index').then((res) => res.ImagePreviewGroup),\n);\n\nconst withDefaultPlaceholder = <T extends Component>(\n  component: T,\n  type: 'input' | 'select',\n  componentProps: Recordable<any> = {},\n) => {\n  return defineComponent({\n    name: component.name,\n    inheritAttrs: false,\n    setup: (props: any, { attrs, expose, slots }) => {\n      const placeholder =\n        props?.placeholder ||\n        attrs?.placeholder ||\n        $t(`ui.placeholder.${type}`);\n      // 透传组件暴露的方法\n      const innerRef = ref();\n      expose(\n        new Proxy(\n          {},\n          {\n            get: (_target, key) => innerRef.value?.[key],\n            has: (_target, key) => key in (innerRef.value || {}),\n          },\n        ),\n      );\n      return () =>\n        h(\n          component,\n          { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },\n          slots,\n        );\n    },\n  });\n};\n\nconst withPreviewUpload = () => {\n  // 检查是否为图片文件的辅助函数\n  const isImageFile = (file: UploadFile): boolean => {\n    const imageExtensions = new Set([\n      'bmp',\n      'gif',\n      'jpeg',\n      'jpg',\n      'png',\n      'svg',\n      'webp',\n    ]);\n    if (file.url) {\n      try {\n        const pathname = new URL(file.url, 'http://localhost').pathname;\n        const ext = pathname.split('.').pop()?.toLowerCase();\n        return ext ? imageExtensions.has(ext) : false;\n      } catch {\n        const ext = file.url?.split('.').pop()?.toLowerCase();\n        return ext ? imageExtensions.has(ext) : false;\n      }\n    }\n    if (!file.type) {\n      const ext = file.name?.split('.').pop()?.toLowerCase();\n      return ext ? imageExtensions.has(ext) : false;\n    }\n    return file.type.startsWith('image/');\n  };\n  // 创建默认的上传按钮插槽\n  const createDefaultSlotsWithUpload = (\n    listType: string,\n    placeholder: string,\n  ) => {\n    switch (listType) {\n      case 'picture-card': {\n        return {\n          default: () => placeholder,\n        };\n      }\n      default: {\n        return {\n          default: () =>\n            h(\n              Button,\n              {\n                icon: h(IconifyIcon, {\n                  icon: 'ant-design:upload-outlined',\n                  class: 'mb-1 size-4',\n                }),\n              },\n              () => placeholder,\n            ),\n        };\n      }\n    }\n  };\n  // 构建预览图片组\n  const previewImage = async (\n    file: UploadFile,\n    visible: Ref<boolean>,\n    fileList: Ref<UploadProps['fileList']>,\n  ) => {\n    // 如果当前文件不是图片，直接打开\n    if (!isImageFile(file)) {\n      if (file.url) {\n        window.open(file.url, '_blank');\n      } else if (file.preview) {\n        window.open(file.preview, '_blank');\n      } else {\n        message.error($t('ui.formRules.previewWarning'));\n      }\n      return;\n    }\n\n    // 对于图片文件，继续使用预览组\n    const [ImageComponent, PreviewGroupComponent] = await Promise.all([\n      Image,\n      PreviewGroup,\n    ]);\n\n    const getBase64 = (file: File) => {\n      return new Promise((resolve, reject) => {\n        const reader = new FileReader();\n        reader.readAsDataURL(file);\n        reader.addEventListener('load', () => resolve(reader.result));\n        reader.addEventListener('error', (error) => reject(error));\n      });\n    };\n    // 从fileList中过滤出所有图片文件\n    const imageFiles = (unref(fileList) || []).filter((element) =>\n      isImageFile(element),\n    );\n\n    // 为所有没有预览地址的图片生成预览\n    for (const imgFile of imageFiles) {\n      if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {\n        imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;\n      }\n    }\n    const container: HTMLElement | null = document.createElement('div');\n    document.body.append(container);\n\n    // 用于追踪组件是否已卸载\n    let isUnmounted = false;\n\n    const PreviewWrapper = {\n      setup() {\n        return () => {\n          if (isUnmounted) return null;\n          return h(\n            PreviewGroupComponent,\n            {\n              class: 'hidden',\n              preview: {\n                open: visible.value,\n                // 设置初始显示的图片索引\n                current: imageFiles.findIndex((f) => f.uid === file.uid),\n                onOpenChange: (value: boolean) => {\n                  visible.value = value;\n                  if (!value) {\n                    // 延迟清理，确保动画完成\n                    setTimeout(() => {\n                      if (!isUnmounted && container) {\n                        isUnmounted = true;\n                        render(null, container);\n                        container.remove();\n                      }\n                    }, 300);\n                  }\n                },\n              },\n            },\n            () =>\n              // 渲染所有图片文件\n              imageFiles.map((imgFile) =>\n                h(ImageComponent, {\n                  key: imgFile.uid,\n                  src: imgFile.url || imgFile.preview,\n                }),\n              ),\n          );\n        };\n      },\n    };\n\n    render(h(PreviewWrapper), container);\n  };\n\n  // 图片裁剪操作\n  const cropImage = (file: File, aspectRatio: string | undefined) => {\n    return new Promise((resolve, reject) => {\n      const container: HTMLElement | null = document.createElement('div');\n      document.body.append(container);\n\n      // 用于追踪组件是否已卸载\n      let isUnmounted = false;\n      let objectUrl: null | string = null;\n\n      const open = ref<boolean>(true);\n      const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);\n\n      const closeModal = () => {\n        open.value = false;\n        // 延迟清理，确保动画完成\n        setTimeout(() => {\n          if (!isUnmounted && container) {\n            if (objectUrl) {\n              URL.revokeObjectURL(objectUrl);\n            }\n            isUnmounted = true;\n            render(null, container);\n            container.remove();\n          }\n        }, 300);\n      };\n\n      const CropperWrapper = {\n        setup() {\n          return () => {\n            if (isUnmounted) return null;\n            if (!objectUrl) {\n              objectUrl = URL.createObjectURL(file);\n            }\n            return h(\n              Modal,\n              {\n                open: open.value,\n                title: h('div', {}, [\n                  $t('ui.crop.title'),\n                  h(\n                    'span',\n                    {\n                      class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,\n                    },\n                    $t('ui.crop.titleTip', [aspectRatio]),\n                  ),\n                ]),\n                centered: true,\n                width: 548,\n                keyboard: false,\n                maskClosable: false,\n                closable: false,\n                cancelText: $t('common.cancel'),\n                okText: $t('ui.crop.confirm'),\n                destroyOnHidden: true,\n                onOk: async () => {\n                  const cropper = cropperRef.value;\n                  if (!cropper) {\n                    reject(new Error('Cropper not found'));\n                    closeModal();\n                    return;\n                  }\n                  try {\n                    const dataUrl = await cropper.getCropImage();\n                    resolve(dataUrl);\n                  } catch {\n                    reject(new Error($t('ui.crop.errorTip')));\n                  } finally {\n                    closeModal();\n                  }\n                },\n                onCancel() {\n                  resolve('');\n                  closeModal();\n                },\n              },\n              () =>\n                h(VCropper, {\n                  ref: (ref: any) => (cropperRef.value = ref),\n                  img: objectUrl as string,\n                  aspectRatio,\n                }),\n            );\n          };\n        },\n      };\n\n      render(h(CropperWrapper), container);\n    });\n  };\n\n  return defineComponent({\n    name: 'AUpload',\n    emits: ['update:modelValue'],\n    setup: (\n      props: any,\n      { attrs, slots, emit }: { attrs: any; emit: any; slots: any },\n    ) => {\n      const previewVisible = ref<boolean>(false);\n\n      const placeholder = attrs?.placeholder || $t(`ui.placeholder.upload`);\n\n      const listType = attrs?.listType || attrs?.['list-type'] || 'text';\n\n      const fileList = ref<UploadProps['fileList']>(\n        attrs?.fileList || attrs?.['file-list'] || [],\n      );\n\n      const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']);\n      const aspectRatio = computed(\n        () => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],\n      );\n\n      const handleBeforeUpload = async (\n        file: UploadFile,\n        originFileList: Array<File>,\n      ) => {\n        if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {\n          message.error($t('ui.formRules.sizeLimit', [maxSize.value]));\n          file.status = 'removed';\n          return false;\n        }\n        // 多选或者非图片不唤起裁剪框\n        if (\n          attrs.crop &&\n          !attrs.multiple &&\n          originFileList[0] &&\n          isImageFile(file)\n        ) {\n          file.status = 'removed';\n          // antd Upload组件问题 file参数获取的是UploadFile类型对象无法取到File类型 所以通过originFileList[0]获取\n          const blob = await cropImage(originFileList[0], aspectRatio.value);\n          return new Promise((resolve, reject) => {\n            if (!blob) {\n              return reject(new Error($t('ui.crop.errorTip')));\n            }\n            resolve(blob);\n          });\n        }\n\n        return attrs.beforeUpload?.(file) ?? true;\n      };\n\n      const handleChange = (event: UploadChangeParam) => {\n        try {\n          // 行内写法 handleChange: (event) => {}\n          attrs.handleChange?.(event);\n          // template写法 @handle-change=\"(event) => {}\"\n          attrs.onHandleChange?.(event);\n        } catch (error) {\n          // Avoid breaking internal v-model sync on user handler errors\n          console.error(error);\n        }\n        fileList.value = event.fileList.filter(\n          (file) => file.status !== 'removed',\n        );\n        emit(\n          'update:modelValue',\n          event.fileList?.length ? fileList.value : undefined,\n        );\n      };\n\n      const handlePreview = async (file: UploadFile) => {\n        previewVisible.value = true;\n        await previewImage(file, previewVisible, fileList);\n      };\n\n      const renderUploadButton = (): any => {\n        const isDisabled = attrs.disabled;\n\n        // 如果禁用，不渲染上传按钮\n        if (isDisabled) {\n          return null;\n        }\n\n        // 否则渲染默认上传按钮\n        return isEmpty(slots)\n          ? createDefaultSlotsWithUpload(listType, placeholder)\n          : slots;\n      };\n\n      // 可以监听到表单API设置的值\n      watch(\n        () => attrs.modelValue,\n        (res) => {\n          fileList.value = res;\n        },\n      );\n\n      return () =>\n        h(\n          Upload,\n          {\n            ...props,\n            ...attrs,\n            fileList: fileList.value,\n            beforeUpload: handleBeforeUpload,\n            onChange: handleChange,\n            onPreview: handlePreview,\n          },\n          renderUploadButton(),\n        );\n    },\n  });\n};\n\n// 这里需要自行根据业务组件库进行适配，需要用到的组件都需要在这里类型说明\nexport type ComponentType =\n  | 'ApiCascader'\n  | 'ApiSelect'\n  | 'ApiTreeSelect'\n  | 'AutoComplete'\n  | 'Cascader'\n  | 'Checkbox'\n  | 'CheckboxGroup'\n  | 'DatePicker'\n  | 'DefaultButton'\n  | 'Divider'\n  | 'IconPicker'\n  | 'Input'\n  | 'InputNumber'\n  | 'InputPassword'\n  | 'Mentions'\n  | 'PrimaryButton'\n  | 'Radio'\n  | 'RadioGroup'\n  | 'RangePicker'\n  | 'Rate'\n  | 'Select'\n  | 'Space'\n  | 'Switch'\n  | 'Textarea'\n  | 'TimePicker'\n  | 'TreeSelect'\n  | 'Upload'\n  | BaseFormComponentType;\n\nasync function initComponentAdapter() {\n  const components: Partial<Record<ComponentType, Component>> = {\n    // 如果你的组件体积比较大，可以使用异步加载\n    // Button: () =>\n    // import('xxx').then((res) => res.Button),\n\n    ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {\n      component: Cascader,\n      fieldNames: { label: 'label', value: 'value', children: 'children' },\n      loadingSlot: 'suffixIcon',\n      modelPropName: 'value',\n      visibleEvent: 'onVisibleChange',\n    }),\n    ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {\n      component: Select,\n      loadingSlot: 'suffixIcon',\n      modelPropName: 'value',\n      visibleEvent: 'onVisibleChange',\n    }),\n    ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {\n      component: TreeSelect,\n      fieldNames: { label: 'label', value: 'value', children: 'children' },\n      loadingSlot: 'suffixIcon',\n      modelPropName: 'value',\n      optionsPropName: 'treeData',\n      visibleEvent: 'onVisibleChange',\n    }),\n    AutoComplete,\n    Cascader,\n    Checkbox,\n    CheckboxGroup,\n    DatePicker,\n    // 自定义默认按钮\n    DefaultButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'default' }, slots);\n    },\n    Divider,\n    IconPicker: withDefaultPlaceholder(IconPicker, 'select', {\n      iconSlot: 'addonAfter',\n      inputComponent: Input,\n      modelValueProp: 'value',\n    }),\n    Input: withDefaultPlaceholder(Input, 'input'),\n    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),\n    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),\n    Mentions: withDefaultPlaceholder(Mentions, 'input'),\n    // 自定义主要按钮\n    PrimaryButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'primary' }, slots);\n    },\n    Radio,\n    RadioGroup,\n    RangePicker,\n    Rate,\n    Select: withDefaultPlaceholder(Select, 'select'),\n    Space,\n    Switch,\n    Textarea: withDefaultPlaceholder(Textarea, 'input'),\n    TimePicker,\n    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),\n    Upload: withPreviewUpload(),\n  };\n\n  // 将组件注册到全局共享状态中\n  globalShareState.setComponents(components);\n\n  // 定义全局共享状态中的消息提示\n  globalShareState.defineMessage({\n    // 复制成功消息提示\n    copyPreferencesSuccess: (title, content) => {\n      notification.success({\n        description: content,\n        title,\n        placement: 'bottomRight',\n      });\n    },\n  });\n}\n\nexport { initComponentAdapter };\n"
  },
  {
    "path": "apps/web-antdv-next/src/adapter/form.ts",
    "content": "import type {\n  VbenFormSchema as FormSchema,\n  VbenFormProps,\n} from '@vben/common-ui';\n\nimport type { ComponentType } from './component';\n\nimport { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nasync function initSetupVbenForm() {\n  setupVbenForm<ComponentType>({\n    config: {\n      // ant design vue组件库默认都是 v-model:value\n      baseModelPropName: 'value',\n\n      // 一些组件是 v-model:checked 或者 v-model:fileList\n      modelPropNameMap: {\n        Checkbox: 'checked',\n        Radio: 'checked',\n        Switch: 'checked',\n        Upload: 'fileList',\n      },\n    },\n    defineRules: {\n      // 输入项目必填国际化适配\n      required: (value, _params, ctx) => {\n        if (value === undefined || value === null || value.length === 0) {\n          return $t('ui.formRules.required', [ctx.label]);\n        }\n        return true;\n      },\n      // 选择项目必填国际化适配\n      selectRequired: (value, _params, ctx) => {\n        if (value === undefined || value === null) {\n          return $t('ui.formRules.selectRequired', [ctx.label]);\n        }\n        return true;\n      },\n    },\n  });\n}\n\nconst useVbenForm = useForm<ComponentType>;\n\nexport { initSetupVbenForm, useVbenForm, z };\n\nexport type VbenFormSchema = FormSchema<ComponentType>;\nexport type { VbenFormProps };\n"
  },
  {
    "path": "apps/web-antdv-next/src/adapter/vxe-table.ts",
    "content": "import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';\n\nimport { h } from 'vue';\n\nimport { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';\n\nimport { Button, Image } from 'antdv-next';\n\nimport { useVbenForm } from './form';\n\nsetupVbenVxeTable({\n  configVxeTable: (vxeUI) => {\n    vxeUI.setConfig({\n      grid: {\n        align: 'center',\n        border: false,\n        columnConfig: {\n          resizable: true,\n        },\n        minHeight: 180,\n        formConfig: {\n          // 全局禁用vxe-table的表单配置，使用formOptions\n          enabled: false,\n        },\n        proxyConfig: {\n          autoLoad: true,\n          response: {\n            result: 'items',\n            total: 'total',\n            list: 'items',\n          },\n          showActiveMsg: true,\n          showResponseMsg: false,\n        },\n        round: true,\n        showOverflow: true,\n        size: 'small',\n      } as VxeTableGridOptions,\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellImage' },\n    vxeUI.renderer.add('CellImage', {\n      renderTableDefault(renderOpts, params) {\n        const { props } = renderOpts;\n        const { column, row } = params;\n        return h(Image, { src: row[column.field], ...props });\n      },\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellLink' },\n    vxeUI.renderer.add('CellLink', {\n      renderTableDefault(renderOpts) {\n        const { props } = renderOpts;\n        return h(\n          Button,\n          { size: 'small', type: 'link' },\n          { default: () => props?.text },\n        );\n      },\n    });\n\n    // 这里可以自行扩展 vxe-table 的全局配置，比如自定义格式化\n    // vxeUI.formats.add\n  },\n  useVbenForm,\n});\n\nexport { useVbenVxeGrid };\n\nexport type * from '@vben/plugins/vxe-table';\n"
  },
  {
    "path": "apps/web-antdv-next/src/api/core/auth.ts",
    "content": "import { baseRequestClient, requestClient } from '#/api/request';\n\nexport namespace AuthApi {\n  /** 登录接口参数 */\n  export interface LoginParams {\n    password?: string;\n    username?: string;\n  }\n\n  /** 登录接口返回值 */\n  export interface LoginResult {\n    accessToken: string;\n  }\n\n  export interface RefreshTokenResult {\n    data: string;\n    status: number;\n  }\n}\n\n/**\n * 登录\n */\nexport async function loginApi(data: AuthApi.LoginParams) {\n  return requestClient.post<AuthApi.LoginResult>('/auth/login', data);\n}\n\n/**\n * 刷新accessToken\n */\nexport async function refreshTokenApi() {\n  return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {\n    withCredentials: true,\n  });\n}\n\n/**\n * 退出登录\n */\nexport async function logoutApi() {\n  return baseRequestClient.post('/auth/logout', {\n    withCredentials: true,\n  });\n}\n\n/**\n * 获取用户权限码\n */\nexport async function getAccessCodesApi() {\n  return requestClient.get<string[]>('/auth/codes');\n}\n"
  },
  {
    "path": "apps/web-antdv-next/src/api/core/index.ts",
    "content": "export * from './auth';\nexport * from './menu';\nexport * from './user';\n"
  },
  {
    "path": "apps/web-antdv-next/src/api/core/menu.ts",
    "content": "import type { RouteRecordStringComponent } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户所有菜单\n */\nexport async function getAllMenusApi() {\n  return requestClient.get<RouteRecordStringComponent[]>('/menu/all');\n}\n"
  },
  {
    "path": "apps/web-antdv-next/src/api/core/user.ts",
    "content": "import type { UserInfo } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户信息\n */\nexport async function getUserInfoApi() {\n  return requestClient.get<UserInfo>('/user/info');\n}\n"
  },
  {
    "path": "apps/web-antdv-next/src/api/index.ts",
    "content": "export * from './core';\n"
  },
  {
    "path": "apps/web-antdv-next/src/api/request.ts",
    "content": "/**\n * 该文件可自行根据业务逻辑进行调整\n */\nimport type { RequestClientOptions } from '@vben/request';\n\nimport { useAppConfig } from '@vben/hooks';\nimport { preferences } from '@vben/preferences';\nimport {\n  authenticateResponseInterceptor,\n  defaultResponseInterceptor,\n  errorMessageResponseInterceptor,\n  RequestClient,\n} from '@vben/request';\nimport { useAccessStore } from '@vben/stores';\n\nimport { message } from 'antdv-next';\n\nimport { useAuthStore } from '#/store';\n\nimport { refreshTokenApi } from './core';\n\nconst { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n\nfunction createRequestClient(baseURL: string, options?: RequestClientOptions) {\n  const client = new RequestClient({\n    ...options,\n    baseURL,\n  });\n\n  /**\n   * 重新认证逻辑\n   */\n  async function doReAuthenticate() {\n    console.warn('Access token or refresh token is invalid or expired. ');\n    const accessStore = useAccessStore();\n    const authStore = useAuthStore();\n    accessStore.setAccessToken(null);\n    if (\n      preferences.app.loginExpiredMode === 'modal' &&\n      accessStore.isAccessChecked\n    ) {\n      accessStore.setLoginExpired(true);\n    } else {\n      await authStore.logout();\n    }\n  }\n\n  /**\n   * 刷新token逻辑\n   */\n  async function doRefreshToken() {\n    const accessStore = useAccessStore();\n    const resp = await refreshTokenApi();\n    const newToken = resp.data;\n    accessStore.setAccessToken(newToken);\n    return newToken;\n  }\n\n  function formatToken(token: null | string) {\n    return token ? `Bearer ${token}` : null;\n  }\n\n  // 请求头处理\n  client.addRequestInterceptor({\n    fulfilled: async (config) => {\n      const accessStore = useAccessStore();\n\n      config.headers.Authorization = formatToken(accessStore.accessToken);\n      config.headers['Accept-Language'] = preferences.app.locale;\n      return config;\n    },\n  });\n\n  // 处理返回的响应数据格式\n  client.addResponseInterceptor(\n    defaultResponseInterceptor({\n      codeField: 'code',\n      dataField: 'data',\n      successCode: 0,\n    }),\n  );\n\n  // token过期的处理\n  client.addResponseInterceptor(\n    authenticateResponseInterceptor({\n      client,\n      doReAuthenticate,\n      doRefreshToken,\n      enableRefreshToken: preferences.app.enableRefreshToken,\n      formatToken,\n    }),\n  );\n\n  // 通用的错误处理,如果没有进入上面的错误处理逻辑，就会进入这里\n  client.addResponseInterceptor(\n    errorMessageResponseInterceptor((msg: string, error) => {\n      // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理，根据不同的 code 做不同的提示，而不是直接使用 message.error 提示 msg\n      // 当前mock接口返回的错误字段是 error 或者 message\n      const responseData = error?.response?.data ?? {};\n      const errorMessage = responseData?.error ?? responseData?.message ?? '';\n      // 如果没有错误信息，则会根据状态码进行提示\n      message.error(errorMessage || msg);\n    }),\n  );\n\n  return client;\n}\n\nexport const requestClient = createRequestClient(apiURL, {\n  responseReturn: 'data',\n});\n\nexport const baseRequestClient = new RequestClient({ baseURL: apiURL });\n"
  },
  {
    "path": "apps/web-antdv-next/src/app.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { useAntdDesignTokens } from '@vben/hooks';\nimport { preferences, usePreferences } from '@vben/preferences';\n\nimport { App, ConfigProvider, theme } from 'antdv-next';\n\nimport { antdLocale } from '#/locales';\n\ndefineOptions({ name: 'App' });\n\nconst { isDark } = usePreferences();\nconst { tokens } = useAntdDesignTokens();\n\nconst tokenTheme = computed(() => {\n  const algorithm = isDark.value\n    ? [theme.darkAlgorithm]\n    : [theme.defaultAlgorithm];\n\n  // antd 紧凑模式算法\n  if (preferences.app.compact) {\n    algorithm.push(theme.compactAlgorithm);\n  }\n\n  return {\n    algorithm,\n    token: tokens,\n  };\n});\n</script>\n\n<template>\n  <ConfigProvider :locale=\"antdLocale\" :theme=\"tokenTheme\">\n    <App>\n      <RouterView />\n    </App>\n  </ConfigProvider>\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/bootstrap.ts",
    "content": "import { createApp, watchEffect } from 'vue';\n\nimport { registerAccessDirective } from '@vben/access';\nimport { registerLoadingDirective } from '@vben/common-ui/es/loading';\nimport { preferences } from '@vben/preferences';\nimport { initStores } from '@vben/stores';\nimport '@vben/styles';\nimport '@vben/styles/antdv-next';\n\nimport { useTitle } from '@vueuse/core';\n\nimport { $t, setupI18n } from '#/locales';\n\nimport { initComponentAdapter } from './adapter/component';\nimport { initSetupVbenForm } from './adapter/form';\nimport App from './app.vue';\nimport { router } from './router';\n\nasync function bootstrap(namespace: string) {\n  // 初始化组件适配器\n  await initComponentAdapter();\n\n  // 初始化表单组件\n  await initSetupVbenForm();\n\n  // // 设置弹窗的默认配置\n  // setDefaultModalProps({\n  //   fullscreenButton: false,\n  // });\n  // // 设置抽屉的默认配置\n  // setDefaultDrawerProps({\n  //   zIndex: 1020,\n  // });\n\n  const app = createApp(App);\n\n  // 注册v-loading指令\n  registerLoadingDirective(app, {\n    loading: 'loading', // 在这里可以自定义指令名称，也可以明确提供false表示不注册这个指令\n    spinning: 'spinning',\n  });\n\n  // 国际化 i18n 配置\n  await setupI18n(app);\n\n  // 配置 pinia-tore\n  await initStores(app, { namespace });\n\n  // 安装权限指令\n  registerAccessDirective(app);\n\n  // 初始化 tippy\n  const { initTippy } = await import('@vben/common-ui/es/tippy');\n  initTippy(app);\n\n  // 配置路由及路由守卫\n  app.use(router);\n\n  // 配置Motion插件\n  const { MotionPlugin } = await import('@vben/plugins/motion');\n  app.use(MotionPlugin);\n\n  // 动态更新标题\n  watchEffect(() => {\n    if (preferences.app.dynamicTitle) {\n      const routeTitle = router.currentRoute.value.meta?.title;\n      const pageTitle =\n        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;\n      useTitle(pageTitle);\n    }\n  });\n\n  app.mount('#app');\n}\n\nexport { bootstrap };\n"
  },
  {
    "path": "apps/web-antdv-next/src/layouts/auth.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { AuthPageLayout } from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst appName = computed(() => preferences.app.name);\nconst logo = computed(() => preferences.logo.source);\nconst logoDark = computed(() => preferences.logo.sourceDark);\n</script>\n\n<template>\n  <AuthPageLayout\n    :app-name=\"appName\"\n    :logo=\"logo\"\n    :logo-dark=\"logoDark\"\n    :page-description=\"$t('authentication.pageDesc')\"\n    :page-title=\"$t('authentication.pageTitle')\"\n  >\n    <!-- 自定义工具栏 -->\n    <!-- <template #toolbar></template> -->\n  </AuthPageLayout>\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/layouts/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { NotificationItem } from '@vben/layouts';\n\nimport { computed, ref, watch } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { AuthenticationLoginExpiredModal } from '@vben/common-ui';\nimport { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';\nimport { useWatermark } from '@vben/hooks';\nimport { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons';\nimport {\n  BasicLayout,\n  LockScreen,\n  Notification,\n  UserDropdown,\n} from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport { $t } from '#/locales';\nimport { useAuthStore } from '#/store';\nimport LoginForm from '#/views/_core/authentication/login.vue';\n\nconst notifications = ref<NotificationItem[]>([\n  {\n    id: 1,\n    avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',\n    date: '3小时前',\n    isRead: true,\n    message: '描述信息描述信息描述信息',\n    title: '收到了 14 份新周报',\n  },\n  {\n    id: 2,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '刚刚',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '朱偏右 回复了你',\n  },\n  {\n    id: 3,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '2024-01-01',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '曲丽丽 评论了你',\n  },\n  {\n    id: 4,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '代办提醒',\n  },\n  {\n    id: 5,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转Workspace示例',\n    link: '/workspace',\n  },\n  {\n    id: 6,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转外部链接示例',\n    link: 'https://doc.vben.pro',\n  },\n]);\n\nconst router = useRouter();\nconst userStore = useUserStore();\nconst authStore = useAuthStore();\nconst accessStore = useAccessStore();\nconst { destroyWatermark, updateWatermark } = useWatermark();\nconst showDot = computed(() =>\n  notifications.value.some((item) => !item.isRead),\n);\n\nconst menus = computed(() => [\n  {\n    handler: () => {\n      router.push({ name: 'Profile' });\n    },\n    icon: 'lucide:user',\n    text: $t('page.auth.profile'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_DOC_URL, {\n        target: '_blank',\n      });\n    },\n    icon: BookOpenText,\n    text: $t('ui.widgets.document'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_GITHUB_URL, {\n        target: '_blank',\n      });\n    },\n    icon: SvgGithubIcon,\n    text: 'GitHub',\n  },\n  {\n    handler: () => {\n      openWindow(`${VBEN_GITHUB_URL}/issues`, {\n        target: '_blank',\n      });\n    },\n    icon: CircleHelp,\n    text: $t('ui.widgets.qa'),\n  },\n]);\n\nconst avatar = computed(() => {\n  return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;\n});\n\nasync function handleLogout() {\n  await authStore.logout(false);\n}\n\nfunction handleNoticeClear() {\n  notifications.value = [];\n}\n\nfunction markRead(id: number | string) {\n  const item = notifications.value.find((item) => item.id === id);\n  if (item) {\n    item.isRead = true;\n  }\n}\n\nfunction remove(id: number | string) {\n  notifications.value = notifications.value.filter((item) => item.id !== id);\n}\n\nfunction handleMakeAll() {\n  notifications.value.forEach((item) => (item.isRead = true));\n}\nwatch(\n  () => ({\n    enable: preferences.app.watermark,\n    content: preferences.app.watermarkContent,\n  }),\n  async ({ enable, content }) => {\n    if (enable) {\n      await updateWatermark({\n        content:\n          content ||\n          `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,\n      });\n    } else {\n      destroyWatermark();\n    }\n  },\n  {\n    immediate: true,\n  },\n);\n</script>\n\n<template>\n  <BasicLayout @clear-preferences-and-logout=\"handleLogout\">\n    <template #user-dropdown>\n      <UserDropdown\n        :avatar\n        :menus\n        :text=\"userStore.userInfo?.realName\"\n        description=\"ann.vben@gmail.com\"\n        tag-text=\"Pro\"\n        @logout=\"handleLogout\"\n      />\n    </template>\n    <template #notification>\n      <Notification\n        :dot=\"showDot\"\n        :notifications=\"notifications\"\n        @clear=\"handleNoticeClear\"\n        @read=\"(item) => item.id && markRead(item.id)\"\n        @remove=\"(item) => item.id && remove(item.id)\"\n        @make-all=\"handleMakeAll\"\n      />\n    </template>\n    <template #extra>\n      <AuthenticationLoginExpiredModal\n        v-model:open=\"accessStore.loginExpired\"\n        :avatar\n      >\n        <LoginForm />\n      </AuthenticationLoginExpiredModal>\n    </template>\n    <template #lock-screen>\n      <LockScreen :avatar @to-login=\"handleLogout\" />\n    </template>\n  </BasicLayout>\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/layouts/index.ts",
    "content": "const BasicLayout = () => import('./basic.vue');\nconst AuthPageLayout = () => import('./auth.vue');\n\nconst IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);\n\nexport { AuthPageLayout, BasicLayout, IFrameView };\n"
  },
  {
    "path": "apps/web-antdv-next/src/locales/README.md",
    "content": "# locale\n\n每个app使用的国际化可能不同，这里用于扩展国际化的功能，例如扩展 dayjs、antd组件库的多语言切换，以及app本身的国际化文件。\n"
  },
  {
    "path": "apps/web-antdv-next/src/locales/index.ts",
    "content": "import type { Locale } from 'antdv-next/dist/locale/index';\n\nimport type { App } from 'vue';\n\nimport type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';\n\nimport { ref } from 'vue';\n\nimport {\n  $t,\n  setupI18n as coreSetup,\n  loadLocalesMapFromDir,\n} from '@vben/locales';\nimport { preferences } from '@vben/preferences';\n\nimport antdEnLocale from 'antdv-next/dist/locale/en_US';\nimport antdDefaultLocale from 'antdv-next/dist/locale/zh_CN';\nimport dayjs from 'dayjs';\n\nconst antdLocale = ref<Locale>(antdDefaultLocale);\n\nconst modules = import.meta.glob('./langs/**/*.json');\n\nconst localesMap = loadLocalesMapFromDir(\n  /\\.\\/langs\\/([^/]+)\\/(.*)\\.json$/,\n  modules,\n);\n/**\n * 加载应用特有的语言包\n * 这里也可以改造为从服务端获取翻译数据\n * @param lang\n */\nasync function loadMessages(lang: SupportedLanguagesType) {\n  const [appLocaleMessages] = await Promise.all([\n    localesMap[lang]?.(),\n    loadThirdPartyMessage(lang),\n  ]);\n  return appLocaleMessages?.default;\n}\n\n/**\n * 加载第三方组件库的语言包\n * @param lang\n */\nasync function loadThirdPartyMessage(lang: SupportedLanguagesType) {\n  await Promise.all([loadAntdLocale(lang), loadDayjsLocale(lang)]);\n}\n\n/**\n * 加载dayjs的语言包\n * @param lang\n */\nasync function loadDayjsLocale(lang: SupportedLanguagesType) {\n  let locale;\n  switch (lang) {\n    case 'en-US': {\n      locale = await import('dayjs/locale/en');\n      break;\n    }\n    case 'zh-CN': {\n      locale = await import('dayjs/locale/zh-cn');\n      break;\n    }\n    // 默认使用英语\n    default: {\n      locale = await import('dayjs/locale/en');\n    }\n  }\n  if (locale) {\n    dayjs.locale(locale);\n  } else {\n    console.error(`Failed to load dayjs locale for ${lang}`);\n  }\n}\n\n/**\n * 加载antd的语言包\n * @param lang\n */\nasync function loadAntdLocale(lang: SupportedLanguagesType) {\n  switch (lang) {\n    case 'en-US': {\n      antdLocale.value = antdEnLocale;\n      break;\n    }\n    case 'zh-CN': {\n      antdLocale.value = antdDefaultLocale;\n      break;\n    }\n  }\n}\n\nasync function setupI18n(app: App, options: LocaleSetupOptions = {}) {\n  await coreSetup(app, {\n    defaultLocale: preferences.app.locale,\n    loadMessages,\n    missingWarn: !import.meta.env.PROD,\n    ...options,\n  });\n}\n\nexport { $t, antdLocale, setupI18n };\n"
  },
  {
    "path": "apps/web-antdv-next/src/locales/langs/en-US/demos.json",
    "content": "{\n  \"title\": \"Demos\",\n  \"antd\": \"Antdv Next\",\n  \"vben\": {\n    \"title\": \"Project\",\n    \"about\": \"About\",\n    \"document\": \"Document\",\n    \"antdv\": \"Ant Design Vue Version\",\n    \"antdv-next\": \"Antdv Next Version\",\n    \"naive-ui\": \"Naive UI Version\",\n    \"element-plus\": \"Element Plus Version\",\n    \"tdesign\": \"TDesign Vue Version\"\n  }\n}\n"
  },
  {
    "path": "apps/web-antdv-next/src/locales/langs/en-US/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"Login\",\n    \"register\": \"Register\",\n    \"codeLogin\": \"Code Login\",\n    \"qrcodeLogin\": \"Qr Code Login\",\n    \"forgetPassword\": \"Forget Password\",\n    \"profile\": \"Profile\"\n  },\n  \"dashboard\": {\n    \"title\": \"Dashboard\",\n    \"analytics\": \"Analytics\",\n    \"workspace\": \"Workspace\"\n  }\n}\n"
  },
  {
    "path": "apps/web-antdv-next/src/locales/langs/zh-CN/demos.json",
    "content": "{\n  \"title\": \"演示\",\n  \"antd\": \"Antdv Next\",\n  \"vben\": {\n    \"title\": \"项目\",\n    \"about\": \"关于\",\n    \"document\": \"文档\",\n    \"antdv\": \"Ant Design Vue 版本\",\n    \"antdv-next\": \"Antdv Next 版本\",\n    \"naive-ui\": \"Naive UI 版本\",\n    \"element-plus\": \"Element Plus 版本\",\n    \"tdesign\": \"TDesign Vue 版本\"\n  }\n}\n"
  },
  {
    "path": "apps/web-antdv-next/src/locales/langs/zh-CN/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"登录\",\n    \"register\": \"注册\",\n    \"codeLogin\": \"验证码登录\",\n    \"qrcodeLogin\": \"二维码登录\",\n    \"forgetPassword\": \"忘记密码\",\n    \"profile\": \"个人中心\"\n  },\n  \"dashboard\": {\n    \"title\": \"概览\",\n    \"analytics\": \"分析页\",\n    \"workspace\": \"工作台\"\n  }\n}\n"
  },
  {
    "path": "apps/web-antdv-next/src/main.ts",
    "content": "import { initPreferences } from '@vben/preferences';\nimport { unmountGlobalLoading } from '@vben/utils';\n\nimport { overridesPreferences } from './preferences';\n\n/**\n * 应用初始化完成之后再进行页面加载渲染\n */\nasync function initApplication() {\n  // name用于指定项目唯一标识\n  // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据\n  const env = import.meta.env.PROD ? 'prod' : 'dev';\n  const appVersion = import.meta.env.VITE_APP_VERSION;\n  const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;\n\n  // app偏好设置初始化\n  await initPreferences({\n    namespace,\n    overrides: overridesPreferences,\n  });\n\n  // 启动应用并挂载\n  // vue应用主要逻辑及视图\n  const { bootstrap } = await import('./bootstrap');\n  await bootstrap(namespace);\n\n  // 移除并销毁loading\n  unmountGlobalLoading();\n}\n\ninitApplication();\n"
  },
  {
    "path": "apps/web-antdv-next/src/preferences.ts",
    "content": "import { defineOverridesPreferences } from '@vben/preferences';\n\n/**\n * @description 项目配置文件\n * 只需要覆盖项目中的一部分配置，不需要的配置不用覆盖，会自动使用默认配置\n * !!! 更改配置后请清空缓存，否则可能不生效\n */\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    name: import.meta.env.VITE_APP_TITLE,\n  },\n});\n"
  },
  {
    "path": "apps/web-antdv-next/src/router/access.ts",
    "content": "import type {\n  ComponentRecordType,\n  GenerateMenuAndRoutesOptions,\n} from '@vben/types';\n\nimport { generateAccessible } from '@vben/access';\nimport { preferences } from '@vben/preferences';\n\nimport { message } from 'antdv-next';\n\nimport { getAllMenusApi } from '#/api';\nimport { BasicLayout, IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');\n\nasync function generateAccess(options: GenerateMenuAndRoutesOptions) {\n  const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');\n\n  const layoutMap: ComponentRecordType = {\n    BasicLayout,\n    IFrameView,\n  };\n\n  return await generateAccessible(preferences.app.accessMode, {\n    ...options,\n    fetchMenuListAsync: async () => {\n      message.loading({\n        content: `${$t('common.loadingMenu')}...`,\n        duration: 1.5,\n      });\n      return await getAllMenusApi();\n    },\n    // 可以指定没有权限跳转403页面\n    forbiddenComponent,\n    // 如果 route.meta.menuVisibleWithForbidden = true\n    layoutMap,\n    pageMap,\n  });\n}\n\nexport { generateAccess };\n"
  },
  {
    "path": "apps/web-antdv-next/src/router/guard.ts",
    "content": "import type { Router } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { startProgress, stopProgress } from '@vben/utils';\n\nimport { accessRoutes, coreRouteNames } from '#/router/routes';\nimport { useAuthStore } from '#/store';\n\nimport { generateAccess } from './access';\n\n/**\n * 通用守卫配置\n * @param router\n */\nfunction setupCommonGuard(router: Router) {\n  // 记录已经加载的页面\n  const loadedPaths = new Set<string>();\n\n  router.beforeEach((to) => {\n    to.meta.loaded = loadedPaths.has(to.path);\n\n    // 页面加载进度条\n    if (!to.meta.loaded && preferences.transition.progress) {\n      startProgress();\n    }\n    return true;\n  });\n\n  router.afterEach((to) => {\n    // 记录页面是否加载,如果已经加载，后续的页面切换动画等效果不在重复执行\n\n    loadedPaths.add(to.path);\n\n    // 关闭页面加载进度条\n    if (preferences.transition.progress) {\n      stopProgress();\n    }\n  });\n}\n\n/**\n * 权限访问守卫配置\n * @param router\n */\nfunction setupAccessGuard(router: Router) {\n  router.beforeEach(async (to, from) => {\n    const accessStore = useAccessStore();\n    const userStore = useUserStore();\n    const authStore = useAuthStore();\n\n    // 基本路由，这些路由不需要进入权限拦截\n    if (coreRouteNames.includes(to.name as string)) {\n      if (to.path === LOGIN_PATH && accessStore.accessToken) {\n        return decodeURIComponent(\n          (to.query?.redirect as string) ||\n            userStore.userInfo?.homePath ||\n            preferences.app.defaultHomePath,\n        );\n      }\n      return true;\n    }\n\n    // accessToken 检查\n    if (!accessStore.accessToken) {\n      // 明确声明忽略权限访问权限，则可以访问\n      if (to.meta.ignoreAccess) {\n        return true;\n      }\n\n      // 没有访问权限，跳转登录页面\n      if (to.fullPath !== LOGIN_PATH) {\n        return {\n          path: LOGIN_PATH,\n          // 如不需要，直接删除 query\n          query:\n            to.fullPath === preferences.app.defaultHomePath\n              ? {}\n              : { redirect: encodeURIComponent(to.fullPath) },\n          // 携带当前跳转的页面，登录后重新跳转该页面\n          replace: true,\n        };\n      }\n      return to;\n    }\n\n    // 是否已经生成过动态路由\n    if (accessStore.isAccessChecked) {\n      return true;\n    }\n\n    // 生成路由表\n    // 当前登录用户拥有的角色标识列表\n    const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());\n    const userRoles = userInfo.roles ?? [];\n\n    // 生成菜单和路由\n    const { accessibleMenus, accessibleRoutes } = await generateAccess({\n      roles: userRoles,\n      router,\n      // 则会在菜单中显示，但是访问会被重定向到403\n      routes: accessRoutes,\n    });\n\n    // 保存菜单信息和路由信息\n    accessStore.setAccessMenus(accessibleMenus);\n    accessStore.setAccessRoutes(accessibleRoutes);\n    accessStore.setIsAccessChecked(true);\n    const redirectPath = (from.query.redirect ??\n      (to.path === preferences.app.defaultHomePath\n        ? userInfo.homePath || preferences.app.defaultHomePath\n        : to.fullPath)) as string;\n\n    return {\n      ...router.resolve(decodeURIComponent(redirectPath)),\n      replace: true,\n    };\n  });\n}\n\n/**\n * 项目守卫配置\n * @param router\n */\nfunction createRouterGuard(router: Router) {\n  /** 通用 */\n  setupCommonGuard(router);\n  /** 权限访问 */\n  setupAccessGuard(router);\n}\n\nexport { createRouterGuard };\n"
  },
  {
    "path": "apps/web-antdv-next/src/router/index.ts",
    "content": "import {\n  createRouter,\n  createWebHashHistory,\n  createWebHistory,\n} from 'vue-router';\n\nimport { resetStaticRoutes } from '@vben/utils';\n\nimport { createRouterGuard } from './guard';\nimport { routes } from './routes';\n\n/**\n *  @zh_CN 创建vue-router实例\n */\nconst router = createRouter({\n  history:\n    import.meta.env.VITE_ROUTER_HISTORY === 'hash'\n      ? createWebHashHistory(import.meta.env.VITE_BASE)\n      : createWebHistory(import.meta.env.VITE_BASE),\n  // 应该添加到路由的初始路由列表。\n  routes,\n  scrollBehavior: (to, _from, savedPosition) => {\n    if (savedPosition) {\n      return savedPosition;\n    }\n    return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };\n  },\n  // 是否应该禁止尾部斜杠。\n  // strict: true,\n});\n\nconst resetRoutes = () => resetStaticRoutes(router, routes);\n\n// 创建路由守卫\ncreateRouterGuard(router);\n\nexport { resetRoutes, router };\n"
  },
  {
    "path": "apps/web-antdv-next/src/router/routes/core.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst BasicLayout = () => import('#/layouts/basic.vue');\nconst AuthPageLayout = () => import('#/layouts/auth.vue');\n/** 全局404页面 */\nconst fallbackNotFoundRoute: RouteRecordRaw = {\n  component: () => import('#/views/_core/fallback/not-found.vue'),\n  meta: {\n    hideInBreadcrumb: true,\n    hideInMenu: true,\n    hideInTab: true,\n    title: '404',\n  },\n  name: 'FallbackNotFound',\n  path: '/:path(.*)*',\n};\n\n/** 基本路由，这些路由是必须存在的 */\nconst coreRoutes: RouteRecordRaw[] = [\n  /**\n   * 根路由\n   * 使用基础布局，作为所有页面的父级容器，子级就不必配置BasicLayout。\n   * 此路由必须存在，且不应修改\n   */\n  {\n    component: BasicLayout,\n    meta: {\n      hideInBreadcrumb: true,\n      title: 'Root',\n    },\n    name: 'Root',\n    path: '/',\n    redirect: preferences.app.defaultHomePath,\n    children: [],\n  },\n  {\n    component: AuthPageLayout,\n    meta: {\n      hideInTab: true,\n      title: 'Authentication',\n    },\n    name: 'Authentication',\n    path: '/auth',\n    redirect: LOGIN_PATH,\n    children: [\n      {\n        name: 'Login',\n        path: 'login',\n        component: () => import('#/views/_core/authentication/login.vue'),\n        meta: {\n          title: $t('page.auth.login'),\n        },\n      },\n      {\n        name: 'CodeLogin',\n        path: 'code-login',\n        component: () => import('#/views/_core/authentication/code-login.vue'),\n        meta: {\n          title: $t('page.auth.codeLogin'),\n        },\n      },\n      {\n        name: 'QrCodeLogin',\n        path: 'qrcode-login',\n        component: () =>\n          import('#/views/_core/authentication/qrcode-login.vue'),\n        meta: {\n          title: $t('page.auth.qrcodeLogin'),\n        },\n      },\n      {\n        name: 'ForgetPassword',\n        path: 'forget-password',\n        component: () =>\n          import('#/views/_core/authentication/forget-password.vue'),\n        meta: {\n          title: $t('page.auth.forgetPassword'),\n        },\n      },\n      {\n        name: 'Register',\n        path: 'register',\n        component: () => import('#/views/_core/authentication/register.vue'),\n        meta: {\n          title: $t('page.auth.register'),\n        },\n      },\n    ],\n  },\n];\n\nexport { coreRoutes, fallbackNotFoundRoute };\n"
  },
  {
    "path": "apps/web-antdv-next/src/router/routes/index.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { mergeRouteModules, traverseTreeValues } from '@vben/utils';\n\nimport { coreRoutes, fallbackNotFoundRoute } from './core';\n\nconst dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {\n  eager: true,\n});\n\n// 有需要可以自行打开注释，并创建文件夹\n// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });\n// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });\n\n/** 动态路由 */\nconst dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);\n\n/** 外部路由列表，访问这些页面可以不需要Layout，可能用于内嵌在别的系统(不会显示在菜单中) */\n// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);\n// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);\nconst staticRoutes: RouteRecordRaw[] = [];\nconst externalRoutes: RouteRecordRaw[] = [];\n\n/** 路由列表，由基本路由、外部路由和404兜底路由组成\n *  无需走权限验证（会一直显示在菜单中） */\nconst routes: RouteRecordRaw[] = [\n  ...coreRoutes,\n  ...externalRoutes,\n  fallbackNotFoundRoute,\n];\n\n/** 基本路由列表，这些路由不需要进入权限拦截 */\nconst coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);\n\n/** 有权限校验的路由列表，包含动态路由和静态路由 */\nconst accessRoutes = [...dynamicRoutes, ...staticRoutes];\nexport { accessRoutes, coreRouteNames, routes };\n"
  },
  {
    "path": "apps/web-antdv-next/src/router/routes/modules/dashboard.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'lucide:layout-dashboard',\n      order: -1,\n      title: $t('page.dashboard.title'),\n    },\n    name: 'Dashboard',\n    path: '/dashboard',\n    children: [\n      {\n        name: 'Analytics',\n        path: '/analytics',\n        component: () => import('#/views/dashboard/analytics/index.vue'),\n        meta: {\n          affixTab: true,\n          icon: 'lucide:area-chart',\n          title: $t('page.dashboard.analytics'),\n        },\n      },\n      {\n        name: 'Workspace',\n        path: '/workspace',\n        component: () => import('#/views/dashboard/workspace/index.vue'),\n        meta: {\n          icon: 'carbon:workspace',\n          title: $t('page.dashboard.workspace'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-antdv-next/src/router/routes/modules/demos.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'ic:baseline-view-in-ar',\n      keepAlive: true,\n      order: 1000,\n      title: $t('demos.title'),\n    },\n    name: 'Demos',\n    path: '/demos',\n    children: [\n      {\n        meta: {\n          title: $t('demos.antd'),\n        },\n        name: 'AntDesignDemos',\n        path: '/demos/ant-design-next',\n        component: () => import('#/views/demos/antd/index.vue'),\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-antdv-next/src/router/routes/modules/vben.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport {\n  VBEN_ANT_PREVIEW_URL,\n  VBEN_DOC_URL,\n  VBEN_ELE_PREVIEW_URL,\n  VBEN_GITHUB_URL,\n  VBEN_LOGO_URL,\n  VBEN_NAIVE_PREVIEW_URL,\n  VBEN_TD_PREVIEW_URL,\n} from '@vben/constants';\nimport { SvgAntdvLogoIcon, SvgTDesignIcon } from '@vben/icons';\n\nimport { IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      badgeType: 'dot',\n      icon: VBEN_LOGO_URL,\n      order: 9998,\n      title: $t('demos.vben.title'),\n    },\n    name: 'VbenProject',\n    path: '/vben-admin',\n    children: [\n      {\n        name: 'VbenDocument',\n        path: '/vben-admin/document',\n        component: IFrameView,\n        meta: {\n          icon: 'lucide:book-open-text',\n          link: VBEN_DOC_URL,\n          title: $t('demos.vben.document'),\n        },\n      },\n      {\n        name: 'VbenGithub',\n        path: '/vben-admin/github',\n        component: IFrameView,\n        meta: {\n          icon: 'mdi:github',\n          link: VBEN_GITHUB_URL,\n          title: 'Github',\n        },\n      },\n      {\n        name: 'VbenAntd',\n        path: '/vben-admin/antd',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgAntdvLogoIcon,\n          link: VBEN_ANT_PREVIEW_URL,\n          title: $t('demos.vben.antdv'),\n        },\n      },\n      {\n        name: 'VbenNaive',\n        path: '/vben-admin/naive',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: 'logos:naiveui',\n          link: VBEN_NAIVE_PREVIEW_URL,\n          title: $t('demos.vben.naive-ui'),\n        },\n      },\n      {\n        name: 'VbenTDesign',\n        path: '/vben-admin/tdesign',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgTDesignIcon,\n          link: VBEN_TD_PREVIEW_URL,\n          title: $t('demos.vben.tdesign'),\n        },\n      },\n      {\n        name: 'VbenElementPlus',\n        path: '/vben-admin/ele',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: 'logos:element',\n          link: VBEN_ELE_PREVIEW_URL,\n          title: $t('demos.vben.element-plus'),\n        },\n      },\n    ],\n  },\n  {\n    name: 'VbenAbout',\n    path: '/vben-admin/about',\n    component: () => import('#/views/_core/about/index.vue'),\n    meta: {\n      icon: 'lucide:copyright',\n      title: $t('demos.vben.about'),\n      order: 9999,\n    },\n  },\n  {\n    name: 'Profile',\n    path: '/profile',\n    component: () => import('#/views/_core/profile/index.vue'),\n    meta: {\n      icon: 'lucide:user',\n      hideInMenu: true,\n      title: $t('page.auth.profile'),\n    },\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-antdv-next/src/store/auth.ts",
    "content": "import type { Recordable, UserInfo } from '@vben/types';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';\n\nimport { notification } from 'antdv-next';\nimport { defineStore } from 'pinia';\n\nimport { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';\nimport { $t } from '#/locales';\n\nexport const useAuthStore = defineStore('auth', () => {\n  const accessStore = useAccessStore();\n  const userStore = useUserStore();\n  const router = useRouter();\n\n  const loginLoading = ref(false);\n\n  /**\n   * 异步处理登录操作\n   * Asynchronously handle the login process\n   * @param params 登录表单数据\n   */\n  async function authLogin(\n    params: Recordable<any>,\n    onSuccess?: () => Promise<void> | void,\n  ) {\n    // 异步处理用户登录操作并获取 accessToken\n    let userInfo: null | UserInfo = null;\n    try {\n      loginLoading.value = true;\n      const { accessToken } = await loginApi(params);\n\n      // 如果成功获取到 accessToken\n      if (accessToken) {\n        accessStore.setAccessToken(accessToken);\n\n        // 获取用户信息并存储到 accessStore 中\n        const [fetchUserInfoResult, accessCodes] = await Promise.all([\n          fetchUserInfo(),\n          getAccessCodesApi(),\n        ]);\n\n        userInfo = fetchUserInfoResult;\n\n        userStore.setUserInfo(userInfo);\n        accessStore.setAccessCodes(accessCodes);\n\n        if (accessStore.loginExpired) {\n          accessStore.setLoginExpired(false);\n        } else {\n          onSuccess\n            ? await onSuccess?.()\n            : await router.push(\n                userInfo.homePath || preferences.app.defaultHomePath,\n              );\n        }\n\n        if (userInfo?.realName) {\n          notification.success({\n            description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,\n            duration: 3,\n            title: $t('authentication.loginSuccess'),\n          });\n        }\n      }\n    } finally {\n      loginLoading.value = false;\n    }\n\n    return {\n      userInfo,\n    };\n  }\n\n  async function logout(redirect: boolean = true) {\n    try {\n      await logoutApi();\n    } catch {\n      // 不做任何处理\n    }\n    resetAllStores();\n    accessStore.setLoginExpired(false);\n\n    // 回登录页带上当前路由地址\n    await router.replace({\n      path: LOGIN_PATH,\n      query: redirect\n        ? {\n            redirect: encodeURIComponent(router.currentRoute.value.fullPath),\n          }\n        : {},\n    });\n  }\n\n  async function fetchUserInfo() {\n    const userInfo = await getUserInfoApi();\n    userStore.setUserInfo(userInfo);\n    return userInfo;\n  }\n\n  function $reset() {\n    loginLoading.value = false;\n  }\n\n  return {\n    $reset,\n    authLogin,\n    fetchUserInfo,\n    loginLoading,\n    logout,\n  };\n});\n"
  },
  {
    "path": "apps/web-antdv-next/src/store/index.ts",
    "content": "export * from './auth';\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/README.md",
    "content": "# \\_core\n\n此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/about/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { About } from '@vben/common-ui';\n\ndefineOptions({ name: 'About' });\n</script>\n\n<template>\n  <About />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/authentication/code-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationCodeLogin, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'CodeLogin' });\n\nconst loading = ref(false);\nconst CODE_LENGTH = 6;\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.mobile'),\n      },\n      fieldName: 'phoneNumber',\n      label: $t('authentication.mobile'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.mobileTip') })\n        .refine((v) => /^\\d{11}$/.test(v), {\n          message: $t('authentication.mobileErrortip'),\n        }),\n    },\n    {\n      component: 'VbenPinInput',\n      componentProps: {\n        codeLength: CODE_LENGTH,\n        createText: (countdown: number) => {\n          const text =\n            countdown > 0\n              ? $t('authentication.sendText', [countdown])\n              : $t('authentication.sendCode');\n          return text;\n        },\n        placeholder: $t('authentication.code'),\n      },\n      fieldName: 'code',\n      label: $t('authentication.code'),\n      rules: z.string().length(CODE_LENGTH, {\n        message: $t('authentication.codeTip', [CODE_LENGTH]),\n      }),\n    },\n  ];\n});\n/**\n * 异步处理登录操作\n * Asynchronously handle the login process\n * @param values 登录表单数据\n */\nasync function handleLogin(values: Recordable<any>) {\n  void values;\n}\n</script>\n\n<template>\n  <AuthenticationCodeLogin\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleLogin\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/authentication/forget-password.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationForgetPassword, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'ForgetPassword' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: 'example@example.com',\n      },\n      fieldName: 'email',\n      label: $t('authentication.email'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.emailTip') })\n        .email($t('authentication.emailValidErrorTip')),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationForgetPassword\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/authentication/login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { BasicOption } from '@vben/types';\n\nimport { computed, markRaw } from 'vue';\n\nimport { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { useAuthStore } from '#/store';\n\ndefineOptions({ name: 'Login' });\n\nconst authStore = useAuthStore();\n\nconst MOCK_USER_OPTIONS: BasicOption[] = [\n  {\n    label: 'Super',\n    value: 'vben',\n  },\n  {\n    label: 'Admin',\n    value: 'admin',\n  },\n  {\n    label: 'User',\n    value: 'jack',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenSelect',\n      componentProps: {\n        options: MOCK_USER_OPTIONS,\n        placeholder: $t('authentication.selectAccount'),\n      },\n      fieldName: 'selectAccount',\n      label: $t('authentication.selectAccount'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.selectAccount') })\n        .optional()\n        .default('vben'),\n    },\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      dependencies: {\n        trigger(values, form) {\n          if (values.selectAccount) {\n            const findUser = MOCK_USER_OPTIONS.find(\n              (item) => item.value === values.selectAccount,\n            );\n            if (findUser) {\n              form.setValues({\n                password: '123456',\n                username: findUser.value,\n              });\n            }\n          }\n        },\n        triggerFields: ['selectAccount'],\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: markRaw(SliderCaptcha),\n      fieldName: 'captcha',\n      rules: z.boolean().refine((value) => value, {\n        message: $t('authentication.verifyRequiredTip'),\n      }),\n    },\n  ];\n});\n</script>\n\n<template>\n  <AuthenticationLogin\n    :form-schema=\"formSchema\"\n    :loading=\"authStore.loginLoading\"\n    @submit=\"authStore.authLogin\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/authentication/qrcode-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport { AuthenticationQrCodeLogin } from '@vben/common-ui';\nimport { LOGIN_PATH } from '@vben/constants';\n\ndefineOptions({ name: 'QrCodeLogin' });\n</script>\n\n<template>\n  <AuthenticationQrCodeLogin :login-path=\"LOGIN_PATH\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/authentication/register.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, h, ref } from 'vue';\n\nimport { AuthenticationRegister, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'Register' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      renderComponentContent() {\n        return {\n          strengthText: () => $t('authentication.passwordStrength'),\n        };\n      },\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.confirmPassword'),\n      },\n      dependencies: {\n        rules(values) {\n          const { password } = values;\n          return z\n            .string({ required_error: $t('authentication.passwordTip') })\n            .min(1, { message: $t('authentication.passwordTip') })\n            .refine((value) => value === password, {\n              message: $t('authentication.confirmPasswordTip'),\n            });\n        },\n        triggerFields: ['password'],\n      },\n      fieldName: 'confirmPassword',\n      label: $t('authentication.confirmPassword'),\n    },\n    {\n      component: 'VbenCheckbox',\n      fieldName: 'agreePolicy',\n      renderComponentContent: () => ({\n        default: () =>\n          h('span', [\n            $t('authentication.agree'),\n            h(\n              'a',\n              {\n                class: 'vben-link ml-1 ',\n                href: '',\n              },\n              `${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,\n            ),\n          ]),\n      }),\n      rules: z.boolean().refine((value) => !!value, {\n        message: $t('authentication.agreeTip'),\n      }),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationRegister\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/fallback/coming-soon.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback status=\"coming-soon\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/fallback/forbidden.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback403Demo' });\n</script>\n\n<template>\n  <Fallback status=\"403\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/fallback/internal-error.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback500Demo' });\n</script>\n\n<template>\n  <Fallback status=\"500\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/fallback/not-found.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback404Demo' });\n</script>\n\n<template>\n  <Fallback status=\"404\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/fallback/offline.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'FallbackOfflineDemo' });\n</script>\n\n<template>\n  <Fallback status=\"offline\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/profile/base-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { BasicOption } from '@vben/types';\n\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed, onMounted, ref } from 'vue';\n\nimport { ProfileBaseSetting } from '@vben/common-ui';\n\nimport { getUserInfoApi } from '#/api';\n\nconst profileBaseSettingRef = ref();\n\nconst MOCK_ROLES_OPTIONS: BasicOption[] = [\n  {\n    label: '管理员',\n    value: 'super',\n  },\n  {\n    label: '用户',\n    value: 'user',\n  },\n  {\n    label: '测试',\n    value: 'test',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'realName',\n      component: 'Input',\n      label: '姓名',\n    },\n    {\n      fieldName: 'username',\n      component: 'Input',\n      label: '用户名',\n    },\n    {\n      fieldName: 'roles',\n      component: 'Select',\n      componentProps: {\n        mode: 'tags',\n        options: MOCK_ROLES_OPTIONS,\n      },\n      label: '角色',\n    },\n    {\n      fieldName: 'introduction',\n      component: 'Textarea',\n      label: '个人简介',\n    },\n  ];\n});\n\nonMounted(async () => {\n  const data = await getUserInfoApi();\n  profileBaseSettingRef.value.getFormApi().setValues(data);\n});\n</script>\n<template>\n  <ProfileBaseSetting ref=\"profileBaseSettingRef\" :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/profile/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\n\nimport { Profile } from '@vben/common-ui';\nimport { useUserStore } from '@vben/stores';\n\nimport ProfileBase from './base-setting.vue';\nimport ProfileNotificationSetting from './notification-setting.vue';\nimport ProfilePasswordSetting from './password-setting.vue';\nimport ProfileSecuritySetting from './security-setting.vue';\n\nconst userStore = useUserStore();\n\nconst tabsValue = ref<string>('basic');\n\nconst tabs = ref([\n  {\n    label: '基本设置',\n    value: 'basic',\n  },\n  {\n    label: '安全设置',\n    value: 'security',\n  },\n  {\n    label: '修改密码',\n    value: 'password',\n  },\n  {\n    label: '新消息提醒',\n    value: 'notice',\n  },\n]);\n</script>\n<template>\n  <Profile\n    v-model:model-value=\"tabsValue\"\n    title=\"个人中心\"\n    :user-info=\"userStore.userInfo\"\n    :tabs=\"tabs\"\n  >\n    <template #content>\n      <ProfileBase v-if=\"tabsValue === 'basic'\" />\n      <ProfileSecuritySetting v-if=\"tabsValue === 'security'\" />\n      <ProfilePasswordSetting v-if=\"tabsValue === 'password'\" />\n      <ProfileNotificationSetting v-if=\"tabsValue === 'notice'\" />\n    </template>\n  </Profile>\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/profile/notification-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileNotificationSetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '其他用户的消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'systemMessage',\n      label: '系统消息',\n      description: '系统消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'todoTask',\n      label: '待办任务',\n      description: '待办任务将以站内信的形式通知',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileNotificationSetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/profile/password-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed } from 'vue';\n\nimport { ProfilePasswordSetting, z } from '@vben/common-ui';\n\nimport { message } from 'antdv-next';\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'oldPassword',\n      label: '旧密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: '请输入旧密码',\n      },\n    },\n    {\n      fieldName: 'newPassword',\n      label: '新密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请输入新密码',\n      },\n    },\n    {\n      fieldName: 'confirmPassword',\n      label: '确认密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请再次输入新密码',\n      },\n      dependencies: {\n        rules(values) {\n          const { newPassword } = values;\n          return z\n            .string({ required_error: '请再次输入新密码' })\n            .min(1, { message: '请再次输入新密码' })\n            .refine((value) => value === newPassword, {\n              message: '两次输入的密码不一致',\n            });\n        },\n        triggerFields: ['newPassword'],\n      },\n    },\n  ];\n});\n\nfunction handleSubmit() {\n  message.success('密码修改成功');\n}\n</script>\n<template>\n  <ProfilePasswordSetting\n    class=\"w-1/3\"\n    :form-schema=\"formSchema\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/_core/profile/security-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileSecuritySetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '当前密码强度：强',\n    },\n    {\n      value: true,\n      fieldName: 'securityPhone',\n      label: '密保手机',\n      description: '已绑定手机：138****8293',\n    },\n    {\n      value: true,\n      fieldName: 'securityQuestion',\n      label: '密保问题',\n      description: '未设置密保问题，密保问题可有效保护账户安全',\n    },\n    {\n      value: true,\n      fieldName: 'securityEmail',\n      label: '备用邮箱',\n      description: '已绑定邮箱：ant***sign.com',\n    },\n    {\n      value: false,\n      fieldName: 'securityMfa',\n      label: 'MFA 设备',\n      description: '未绑定 MFA 设备，绑定后，可以进行二次确认',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileSecuritySetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/dashboard/analytics/analytics-trends.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        areaStyle: {},\n        data: [\n          111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,\n          36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,\n          111,\n        ],\n        itemStyle: {\n          color: '#5ab1ef',\n        },\n        smooth: true,\n        type: 'line',\n      },\n      {\n        areaStyle: {},\n        data: [\n          33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,\n          11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,\n        ],\n        itemStyle: {\n          color: '#019680',\n        },\n        smooth: true,\n        type: 'line',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          color: '#019680',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    // xAxis: {\n    //   axisTick: {\n    //     show: false,\n    //   },\n    //   boundaryGap: false,\n    //   data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n    //   type: 'category',\n    // },\n    xAxis: {\n      axisTick: {\n        show: false,\n      },\n      boundaryGap: false,\n      data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n      splitLine: {\n        lineStyle: {\n          type: 'solid',\n          width: 1,\n        },\n        show: true,\n      },\n      type: 'category',\n    },\n    yAxis: [\n      {\n        axisTick: {\n          show: false,\n        },\n        max: 80_000,\n        splitArea: {\n          show: true,\n        },\n        splitNumber: 4,\n        type: 'value',\n      },\n    ],\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-data.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: 0,\n      data: ['访问', '趋势'],\n    },\n    radar: {\n      indicator: [\n        {\n          name: '网页',\n        },\n        {\n          name: '移动端',\n        },\n        {\n          name: 'Ipad',\n        },\n        {\n          name: '客户端',\n        },\n        {\n          name: '第三方',\n        },\n        {\n          name: '其它',\n        },\n      ],\n      radius: '60%',\n      splitNumber: 8,\n    },\n    series: [\n      {\n        areaStyle: {\n          opacity: 1,\n          shadowBlur: 0,\n          shadowColor: 'rgba(0,0,0,.2)',\n          shadowOffsetX: 0,\n          shadowOffsetY: 10,\n        },\n        data: [\n          {\n            itemStyle: {\n              color: '#b6a2de',\n            },\n            name: '访问',\n            value: [90, 50, 86, 40, 50, 20],\n          },\n          {\n            itemStyle: {\n              color: '#5ab1ef',\n            },\n            name: '趋势',\n            value: [70, 75, 70, 76, 20, 85],\n          },\n        ],\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        symbolSize: 0,\n        type: 'radar',\n      },\n    ],\n    tooltip: {},\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-sales.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 400;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        center: ['50%', '50%'],\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '外包', value: 500 },\n          { name: '定制', value: 310 },\n          { name: '技术支持', value: 274 },\n          { name: '远程', value: 400 },\n        ].toSorted((a, b) => {\n          return a.value - b.value;\n        }),\n        name: '商业占比',\n        radius: '80%',\n        roseType: 'radius',\n        type: 'pie',\n      },\n    ],\n\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-source.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: '2%',\n      left: 'center',\n    },\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 100;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        avoidLabelOverlap: false,\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '搜索引擎', value: 1048 },\n          { name: '直接访问', value: 735 },\n          { name: '邮件营销', value: 580 },\n          { name: '联盟广告', value: 484 },\n        ],\n        emphasis: {\n          label: {\n            fontSize: '12',\n            fontWeight: 'bold',\n            show: true,\n          },\n        },\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        label: {\n          position: 'center',\n          show: false,\n        },\n        labelLine: {\n          show: false,\n        },\n        name: '访问来源',\n        radius: ['40%', '65%'],\n        type: 'pie',\n      },\n    ],\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        barMaxWidth: 80,\n        // color: '#4f69fd',\n        data: [\n          3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,\n          3200, 4800,\n        ],\n        type: 'bar',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          // color: '#4f69fd',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    xAxis: {\n      data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),\n      type: 'category',\n    },\n    yAxis: {\n      max: 8000,\n      splitNumber: 4,\n      type: 'value',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/dashboard/analytics/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { AnalysisOverviewItem } from '@vben/common-ui';\nimport type { TabOption } from '@vben/types';\n\nimport {\n  AnalysisChartCard,\n  AnalysisChartsTabs,\n  AnalysisOverview,\n} from '@vben/common-ui';\nimport {\n  SvgBellIcon,\n  SvgCakeIcon,\n  SvgCardIcon,\n  SvgDownloadIcon,\n} from '@vben/icons';\n\nimport AnalyticsTrends from './analytics-trends.vue';\nimport AnalyticsVisitsData from './analytics-visits-data.vue';\nimport AnalyticsVisitsSales from './analytics-visits-sales.vue';\nimport AnalyticsVisitsSource from './analytics-visits-source.vue';\nimport AnalyticsVisits from './analytics-visits.vue';\n\nconst overviewItems: AnalysisOverviewItem[] = [\n  {\n    icon: SvgCardIcon,\n    title: '用户量',\n    totalTitle: '总用户量',\n    totalValue: 120_000,\n    value: 2000,\n  },\n  {\n    icon: SvgCakeIcon,\n    title: '访问量',\n    totalTitle: '总访问量',\n    totalValue: 500_000,\n    value: 20_000,\n  },\n  {\n    icon: SvgDownloadIcon,\n    title: '下载量',\n    totalTitle: '总下载量',\n    totalValue: 120_000,\n    value: 8000,\n  },\n  {\n    icon: SvgBellIcon,\n    title: '使用量',\n    totalTitle: '总使用量',\n    totalValue: 50_000,\n    value: 5000,\n  },\n];\n\nconst chartTabs: TabOption[] = [\n  {\n    label: '流量趋势',\n    value: 'trends',\n  },\n  {\n    label: '月访问量',\n    value: 'visits',\n  },\n];\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <AnalysisOverview :items=\"overviewItems\" />\n    <AnalysisChartsTabs :tabs=\"chartTabs\" class=\"mt-5\">\n      <template #trends>\n        <AnalyticsTrends />\n      </template>\n      <template #visits>\n        <AnalyticsVisits />\n      </template>\n    </AnalysisChartsTabs>\n\n    <div class=\"mt-5 w-full md:flex\">\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问数量\">\n        <AnalyticsVisitsData />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSource />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSales />\n      </AnalysisChartCard>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/dashboard/workspace/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  WorkbenchProjectItem,\n  WorkbenchQuickNavItem,\n  WorkbenchTodoItem,\n  WorkbenchTrendItem,\n} from '@vben/common-ui';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport {\n  AnalysisChartCard,\n  WorkbenchHeader,\n  WorkbenchProject,\n  WorkbenchQuickNav,\n  WorkbenchTodo,\n  WorkbenchTrends,\n} from '@vben/common-ui';\nimport { preferences } from '@vben/preferences';\nimport { useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';\n\nconst userStore = useUserStore();\n\n// 这是一个示例数据，实际项目中需要根据实际情况进行调整\n// url 也可以是内部路由，在 navTo 方法中识别处理，进行内部跳转\n// 例如：url: /dashboard/workspace\nconst projectItems: WorkbenchProjectItem[] = [\n  {\n    color: '',\n    content: '不要等待机会，而要创造机会。',\n    date: '2021-04-01',\n    group: '开源组',\n    icon: 'carbon:logo-github',\n    title: 'Github',\n    url: 'https://github.com',\n  },\n  {\n    color: '#3fb27f',\n    content: '现在的你决定将来的你。',\n    date: '2021-04-01',\n    group: '算法组',\n    icon: 'ion:logo-vue',\n    title: 'Vue',\n    url: 'https://vuejs.org',\n  },\n  {\n    color: '#e18525',\n    content: '没有什么才能比努力更重要。',\n    date: '2021-04-01',\n    group: '上班摸鱼',\n    icon: 'ion:logo-html5',\n    title: 'Html5',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',\n  },\n  {\n    color: '#bf0c2c',\n    content: '热情和欲望可以突破一切难关。',\n    date: '2021-04-01',\n    group: 'UI',\n    icon: 'ion:logo-angular',\n    title: 'Angular',\n    url: 'https://angular.io',\n  },\n  {\n    color: '#00d8ff',\n    content: '健康的身体是实现目标的基石。',\n    date: '2021-04-01',\n    group: '技术牛',\n    icon: 'bx:bxl-react',\n    title: 'React',\n    url: 'https://reactjs.org',\n  },\n  {\n    color: '#EBD94E',\n    content: '路是走出来的，而不是空想出来的。',\n    date: '2021-04-01',\n    group: '架构组',\n    icon: 'ion:logo-javascript',\n    title: 'Js',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',\n  },\n];\n\n// 同样，这里的 url 也可以使用以 http 开头的外部链接\nconst quickNavItems: WorkbenchQuickNavItem[] = [\n  {\n    color: '#1fdaca',\n    icon: 'ion:home-outline',\n    title: '首页',\n    url: '/',\n  },\n  {\n    color: '#bf0c2c',\n    icon: 'ion:grid-outline',\n    title: '仪表盘',\n    url: '/dashboard',\n  },\n  {\n    color: '#e18525',\n    icon: 'ion:layers-outline',\n    title: '组件',\n    url: '/demos/features/icons',\n  },\n  {\n    color: '#3fb27f',\n    icon: 'ion:settings-outline',\n    title: '系统管理',\n    url: '/demos/features/login-expired', // 这里的 URL 是示例，实际项目中需要根据实际情况进行调整\n  },\n  {\n    color: '#4daf1bc9',\n    icon: 'ion:key-outline',\n    title: '权限管理',\n    url: '/demos/access/page-control',\n  },\n  {\n    color: '#00d8ff',\n    icon: 'ion:bar-chart-outline',\n    title: '图表',\n    url: '/analytics',\n  },\n];\n\nconst todoItems = ref<WorkbenchTodoItem[]>([\n  {\n    completed: false,\n    content: `审查最近提交到Git仓库的前端代码，确保代码质量和规范。`,\n    date: '2024-07-30 11:00:00',\n    title: '审查前端代码提交',\n  },\n  {\n    completed: true,\n    content: `检查并优化系统性能，降低CPU使用率。`,\n    date: '2024-07-30 11:00:00',\n    title: '系统性能优化',\n  },\n  {\n    completed: false,\n    content: `进行系统安全检查，确保没有安全漏洞或未授权的访问。 `,\n    date: '2024-07-30 11:00:00',\n    title: '安全检查',\n  },\n  {\n    completed: false,\n    content: `更新项目中的所有npm依赖包，确保使用最新版本。`,\n    date: '2024-07-30 11:00:00',\n    title: '更新项目依赖',\n  },\n  {\n    completed: false,\n    content: `修复用户报告的页面UI显示问题，确保在不同浏览器中显示一致。 `,\n    date: '2024-07-30 11:00:00',\n    title: '修复UI显示问题',\n  },\n]);\nconst trendItems: WorkbenchTrendItem[] = [\n  {\n    avatar: 'svg:avatar-1',\n    content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,\n    date: '刚刚',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关注了 <a>威廉</a> `,\n    date: '1个小时前',\n    title: '艾文',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1天前',\n    title: '克里斯',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写一个Vite插件</a> `,\n    date: '2天前',\n    title: 'Vben',\n  },\n  {\n    avatar: 'svg:avatar-1',\n    content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化？</a>`,\n    date: '3天前',\n    title: '皮特',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关闭了问题 <a>如何运行项目</a> `,\n    date: '1周前',\n    title: '杰克',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1周前',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `推送了代码到 <a>Github</a>`,\n    date: '2021-04-01 20:00',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写使用 Admin Vben</a> `,\n    date: '2021-03-01 20:00',\n    title: 'Vben',\n  },\n];\n\nconst router = useRouter();\n\n// 这是一个示例方法，实际项目中需要根据实际情况进行调整\n// This is a sample method, adjust according to the actual project requirements\nfunction navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {\n  if (nav.url?.startsWith('http')) {\n    openWindow(nav.url);\n    return;\n  }\n  if (nav.url?.startsWith('/')) {\n    router.push(nav.url).catch((error) => {\n      console.error('Navigation failed:', error);\n    });\n  } else {\n    console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);\n  }\n}\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <WorkbenchHeader\n      :avatar=\"userStore.userInfo?.avatar || preferences.app.defaultAvatar\"\n    >\n      <template #title>\n        早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧！\n      </template>\n      <template #description> 今日晴，20℃ - 32℃！ </template>\n    </WorkbenchHeader>\n\n    <div class=\"mt-5 flex flex-col lg:flex-row\">\n      <div class=\"mr-4 w-full lg:w-3/5\">\n        <WorkbenchProject :items=\"projectItems\" title=\"项目\" @click=\"navTo\" />\n        <WorkbenchTrends :items=\"trendItems\" class=\"mt-5\" title=\"最新动态\" />\n      </div>\n      <div class=\"w-full lg:w-2/5\">\n        <WorkbenchQuickNav\n          :items=\"quickNavItems\"\n          class=\"mt-5 lg:mt-0\"\n          title=\"快捷导航\"\n          @click=\"navTo\"\n        />\n        <WorkbenchTodo :items=\"todoItems\" class=\"mt-5\" title=\"待办事项\" />\n        <AnalysisChartCard class=\"mt-5\" title=\"访问来源\">\n          <AnalyticsVisitsSource />\n        </AnalysisChartCard>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/src/views/demos/antd/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Page } from '@vben/common-ui';\n\nimport { Button, Card, message, notification, Space } from 'antdv-next';\n\ntype NotificationType = 'error' | 'info' | 'success' | 'warning';\n\nfunction info() {\n  message.info('How many roads must a man walk down');\n}\n\nfunction error() {\n  message.error({\n    content: 'Once upon a time you dressed so fine',\n    duration: 2.5,\n  });\n}\n\nfunction warning() {\n  message.warning('How many roads must a man walk down');\n}\nfunction success() {\n  message.success('Cause you walked hand in hand With another man in my place');\n}\n\nfunction notify(type: NotificationType) {\n  notification[type]({\n    duration: 2.5,\n    title: '说点啥呢',\n    type,\n  });\n}\n</script>\n\n<template>\n  <Page\n    description=\"支持多语言，主题功能集成切换等\"\n    title=\"Ant Design Vue组件使用演示\"\n  >\n    <Card class=\"mb-5\" title=\"按钮\">\n      <Space>\n        <Button>Default</Button>\n        <Button type=\"primary\"> Primary </Button>\n        <Button> Info </Button>\n        <Button danger> Error </Button>\n      </Space>\n    </Card>\n    <Card class=\"mb-5\" title=\"Message\">\n      <Space>\n        <Button @click=\"info\"> 信息 </Button>\n        <Button danger @click=\"error\"> 错误 </Button>\n        <Button @click=\"warning\"> 警告 </Button>\n        <Button @click=\"success\"> 成功 </Button>\n      </Space>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"Notification\">\n      <Space>\n        <Button @click=\"notify('info')\"> 信息 </Button>\n        <Button danger @click=\"notify('error')\"> 错误 </Button>\n        <Button @click=\"notify('warning')\"> 警告 </Button>\n        <Button @click=\"notify('success')\"> 成功 </Button>\n      </Space>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "apps/web-antdv-next/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web-app.json\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"#/*\": [\"./src/*\"]\n    }\n  },\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }],\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\", \"src/**/*.vue\"]\n}\n"
  },
  {
    "path": "apps/web-antdv-next/tsconfig.node.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"noEmit\": false\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "apps/web-antdv-next/vite.config.ts",
    "content": "import { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    application: {},\n    vite: {\n      server: {\n        proxy: {\n          '/api': {\n            changeOrigin: true,\n            rewrite: (path) => path.replace(/^\\/api/, ''),\n            // mock代理目标地址\n            target: 'http://localhost:5320/api',\n            ws: true,\n          },\n        },\n      },\n    },\n  };\n});\n"
  },
  {
    "path": "apps/web-ele/index.html",
    "content": "<!doctype html>\n<html lang=\"zh\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n    <meta name=\"renderer\" content=\"webkit\" />\n    <meta name=\"description\" content=\"A Modern Back-end Management System\" />\n    <meta name=\"keywords\" content=\"Vben Admin Vue3 Vite\" />\n    <meta name=\"author\" content=\"Vben\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0\"\n    />\n    <!-- 由 vite 注入 VITE_APP_TITLE 变量，在 .env 文件内配置 -->\n    <title><%= VITE_APP_TITLE %></title>\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <script>\n      // 生产环境下注入百度统计\n      if (window._VBEN_ADMIN_PRO_APP_CONF_) {\n        var _hmt = _hmt || [];\n        (function () {\n          var hm = document.createElement('script');\n          hm.src =\n            'https://hm.baidu.com/hm.js?97352b16ed2df8c3860cf5a1a65fb4dd';\n          var s = document.getElementsByTagName('script')[0];\n          s.parentNode.insertBefore(hm, s);\n        })();\n      }\n    </script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "apps/web-ele/package.json",
    "content": "{\n  \"name\": \"@vben/web-ele\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://vben.pro\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"apps/web-ele\"\n  },\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"vben\",\n    \"email\": \"ann.vben@gmail.com\",\n    \"url\": \"https://github.com/anncwb\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm vite build --mode production\",\n    \"build:analyze\": \"pnpm vite build --mode analyze\",\n    \"dev\": \"pnpm vite --mode development\",\n    \"preview\": \"vite preview\",\n    \"typecheck\": \"vue-tsc --noEmit --skipLibCheck\"\n  },\n  \"imports\": {\n    \"#/*\": \"./src/*\"\n  },\n  \"dependencies\": {\n    \"@vben/access\": \"workspace:*\",\n    \"@vben/common-ui\": \"workspace:*\",\n    \"@vben/constants\": \"workspace:*\",\n    \"@vben/hooks\": \"workspace:*\",\n    \"@vben/icons\": \"workspace:*\",\n    \"@vben/layouts\": \"workspace:*\",\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/plugins\": \"workspace:*\",\n    \"@vben/preferences\": \"workspace:*\",\n    \"@vben/request\": \"workspace:*\",\n    \"@vben/stores\": \"workspace:*\",\n    \"@vben/styles\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"dayjs\": \"catalog:\",\n    \"element-plus\": \"catalog:\",\n    \"pinia\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"unplugin-element-plus\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "apps/web-ele/src/adapter/component/index.ts",
    "content": "/**\n * 通用组件共同的使用的基础组件，原先放在 adapter/form 内部，限制了使用范围，这里提取出来，方便其他地方使用\n * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,\n */\n\nimport type { Component } from 'vue';\n\nimport type { BaseFormComponentType } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { defineAsyncComponent, defineComponent, h, ref } from 'vue';\n\nimport { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { ElNotification } from 'element-plus';\n\nconst ElButton = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/button/index'),\n    import('element-plus/es/components/button/style/css'),\n  ]).then(([res]) => res.ElButton),\n);\nconst ElCheckbox = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/checkbox/index'),\n    import('element-plus/es/components/checkbox/style/css'),\n  ]).then(([res]) => res.ElCheckbox),\n);\nconst ElCheckboxButton = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/checkbox/index'),\n    import('element-plus/es/components/checkbox-button/style/css'),\n  ]).then(([res]) => res.ElCheckboxButton),\n);\nconst ElCheckboxGroup = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/checkbox/index'),\n    import('element-plus/es/components/checkbox-group/style/css'),\n  ]).then(([res]) => res.ElCheckboxGroup),\n);\nconst ElDatePicker = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/date-picker/index'),\n    import('element-plus/es/components/date-picker/style/css'),\n  ]).then(([res]) => res.ElDatePicker),\n);\nconst ElDivider = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/divider/index'),\n    import('element-plus/es/components/divider/style/css'),\n  ]).then(([res]) => res.ElDivider),\n);\nconst ElInput = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/input/index'),\n    import('element-plus/es/components/input/style/css'),\n  ]).then(([res]) => res.ElInput),\n);\nconst ElInputNumber = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/input-number/index'),\n    import('element-plus/es/components/input-number/style/css'),\n  ]).then(([res]) => res.ElInputNumber),\n);\nconst ElRadio = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/radio/index'),\n    import('element-plus/es/components/radio/style/css'),\n  ]).then(([res]) => res.ElRadio),\n);\nconst ElRadioButton = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/radio/index'),\n    import('element-plus/es/components/radio-button/style/css'),\n  ]).then(([res]) => res.ElRadioButton),\n);\nconst ElRadioGroup = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/radio/index'),\n    import('element-plus/es/components/radio-group/style/css'),\n  ]).then(([res]) => res.ElRadioGroup),\n);\nconst ElSelectV2 = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/select-v2/index'),\n    import('element-plus/es/components/select-v2/style/css'),\n  ]).then(([res]) => res.ElSelectV2),\n);\nconst ElSpace = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/space/index'),\n    import('element-plus/es/components/space/style/css'),\n  ]).then(([res]) => res.ElSpace),\n);\nconst ElSwitch = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/switch/index'),\n    import('element-plus/es/components/switch/style/css'),\n  ]).then(([res]) => res.ElSwitch),\n);\nconst ElTimePicker = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/time-picker/index'),\n    import('element-plus/es/components/time-picker/style/css'),\n  ]).then(([res]) => res.ElTimePicker),\n);\nconst ElTreeSelect = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/tree-select/index'),\n    import('element-plus/es/components/tree-select/style/css'),\n  ]).then(([res]) => res.ElTreeSelect),\n);\nconst ElUpload = defineAsyncComponent(() =>\n  Promise.all([\n    import('element-plus/es/components/upload/index'),\n    import('element-plus/es/components/upload/style/css'),\n  ]).then(([res]) => res.ElUpload),\n);\n\nconst withDefaultPlaceholder = <T extends Component>(\n  component: T,\n  type: 'input' | 'select',\n  componentProps: Recordable<any> = {},\n) => {\n  return defineComponent({\n    name: component.name,\n    inheritAttrs: false,\n    setup: (props: any, { attrs, expose, slots }) => {\n      const placeholder =\n        props?.placeholder ||\n        attrs?.placeholder ||\n        $t(`ui.placeholder.${type}`);\n      // 透传组件暴露的方法\n      const innerRef = ref();\n      expose(\n        new Proxy(\n          {},\n          {\n            get: (_target, key) => innerRef.value?.[key],\n            has: (_target, key) => key in (innerRef.value || {}),\n          },\n        ),\n      );\n      return () =>\n        h(\n          component,\n          { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },\n          slots,\n        );\n    },\n  });\n};\n\n// 这里需要自行根据业务组件库进行适配，需要用到的组件都需要在这里类型说明\nexport type ComponentType =\n  | 'ApiSelect'\n  | 'ApiTreeSelect'\n  | 'Checkbox'\n  | 'CheckboxGroup'\n  | 'DatePicker'\n  | 'Divider'\n  | 'IconPicker'\n  | 'Input'\n  | 'InputNumber'\n  | 'RadioGroup'\n  | 'Select'\n  | 'Space'\n  | 'Switch'\n  | 'TimePicker'\n  | 'TreeSelect'\n  | 'Upload'\n  | BaseFormComponentType;\n\nasync function initComponentAdapter() {\n  const components: Partial<Record<ComponentType, Component>> = {\n    // 如果你的组件体积比较大，可以使用异步加载\n    // Button: () =>\n    // import('xxx').then((res) => res.Button),\n    ApiSelect: withDefaultPlaceholder(\n      {\n        ...ApiComponent,\n        name: 'ApiSelect',\n      },\n      'select',\n      {\n        component: ElSelectV2,\n        loadingSlot: 'loading',\n        visibleEvent: 'onVisibleChange',\n      },\n    ),\n    ApiTreeSelect: withDefaultPlaceholder(\n      {\n        ...ApiComponent,\n        name: 'ApiTreeSelect',\n      },\n      'select',\n      {\n        component: ElTreeSelect,\n        props: { label: 'label', children: 'children' },\n        nodeKey: 'value',\n        loadingSlot: 'loading',\n        optionsPropName: 'data',\n        visibleEvent: 'onVisibleChange',\n      },\n    ),\n    Checkbox: ElCheckbox,\n    CheckboxGroup: (props, { attrs, slots }) => {\n      let defaultSlot;\n      if (Reflect.has(slots, 'default')) {\n        defaultSlot = slots.default;\n      } else {\n        const { options, isButton } = attrs;\n        if (Array.isArray(options)) {\n          defaultSlot = () =>\n            options.map((option) =>\n              h(isButton ? ElCheckboxButton : ElCheckbox, option),\n            );\n        }\n      }\n      return h(\n        ElCheckboxGroup,\n        { ...props, ...attrs },\n        { ...slots, default: defaultSlot },\n      );\n    },\n    // 自定义默认按钮\n    DefaultButton: (props, { attrs, slots }) => {\n      return h(ElButton, { ...props, attrs, type: 'info' }, slots);\n    },\n    // 自定义主要按钮\n    PrimaryButton: (props, { attrs, slots }) => {\n      return h(ElButton, { ...props, attrs, type: 'primary' }, slots);\n    },\n    Divider: ElDivider,\n    IconPicker: withDefaultPlaceholder(IconPicker, 'select', {\n      iconSlot: 'append',\n      modelValueProp: 'model-value',\n      inputComponent: ElInput,\n    }),\n    Input: withDefaultPlaceholder(ElInput, 'input'),\n    InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),\n    RadioGroup: (props, { attrs, slots }) => {\n      let defaultSlot;\n      if (Reflect.has(slots, 'default')) {\n        defaultSlot = slots.default;\n      } else {\n        const { options } = attrs;\n        if (Array.isArray(options)) {\n          defaultSlot = () =>\n            options.map((option) =>\n              h(attrs.isButton ? ElRadioButton : ElRadio, option),\n            );\n        }\n      }\n      return h(\n        ElRadioGroup,\n        { ...props, ...attrs },\n        { ...slots, default: defaultSlot },\n      );\n    },\n    Select: (props, { attrs, slots }) => {\n      return h(ElSelectV2, { ...props, attrs }, slots);\n    },\n    Space: ElSpace,\n    Switch: ElSwitch,\n    TimePicker: (props, { attrs, slots }) => {\n      const { name, id, isRange } = props;\n      const extraProps: Recordable<any> = {};\n      if (isRange) {\n        if (name && !Array.isArray(name)) {\n          extraProps.name = [name, `${name}_end`];\n        }\n        if (id && !Array.isArray(id)) {\n          extraProps.id = [id, `${id}_end`];\n        }\n      }\n      return h(\n        ElTimePicker,\n        {\n          ...props,\n          ...attrs,\n          ...extraProps,\n        },\n        slots,\n      );\n    },\n    DatePicker: (props, { attrs, slots }) => {\n      const { name, id, type } = props;\n      const extraProps: Recordable<any> = {};\n      if (type && type.includes('range')) {\n        if (name && !Array.isArray(name)) {\n          extraProps.name = [name, `${name}_end`];\n        }\n        if (id && !Array.isArray(id)) {\n          extraProps.id = [id, `${id}_end`];\n        }\n      }\n      return h(\n        ElDatePicker,\n        {\n          ...props,\n          ...attrs,\n          ...extraProps,\n        },\n        slots,\n      );\n    },\n    TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),\n    Upload: ElUpload,\n  };\n\n  // 将组件注册到全局共享状态中\n  globalShareState.setComponents(components);\n\n  // 定义全局共享状态中的消息提示\n  globalShareState.defineMessage({\n    // 复制成功消息提示\n    copyPreferencesSuccess: (title, content) => {\n      ElNotification({\n        title,\n        message: content,\n        position: 'bottom-right',\n        duration: 0,\n        type: 'success',\n      });\n    },\n  });\n}\n\nexport { initComponentAdapter };\n"
  },
  {
    "path": "apps/web-ele/src/adapter/form.ts",
    "content": "import type {\n  VbenFormSchema as FormSchema,\n  VbenFormProps,\n} from '@vben/common-ui';\n\nimport type { ComponentType } from './component';\n\nimport { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nasync function initSetupVbenForm() {\n  setupVbenForm<ComponentType>({\n    config: {\n      modelPropNameMap: {\n        Upload: 'fileList',\n        CheckboxGroup: 'model-value',\n      },\n    },\n    defineRules: {\n      required: (value, _params, ctx) => {\n        if (value === undefined || value === null || value.length === 0) {\n          return $t('ui.formRules.required', [ctx.label]);\n        }\n        return true;\n      },\n      selectRequired: (value, _params, ctx) => {\n        if (value === undefined || value === null) {\n          return $t('ui.formRules.selectRequired', [ctx.label]);\n        }\n        return true;\n      },\n    },\n  });\n}\n\nconst useVbenForm = useForm<ComponentType>;\n\nexport { initSetupVbenForm, useVbenForm, z };\n\nexport type VbenFormSchema = FormSchema<ComponentType>;\nexport type { VbenFormProps };\n"
  },
  {
    "path": "apps/web-ele/src/adapter/vxe-table.ts",
    "content": "import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';\n\nimport { h } from 'vue';\n\nimport { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';\n\nimport { ElButton, ElImage } from 'element-plus';\n\nimport { useVbenForm } from './form';\n\nsetupVbenVxeTable({\n  configVxeTable: (vxeUI) => {\n    vxeUI.setConfig({\n      grid: {\n        align: 'center',\n        border: false,\n        columnConfig: {\n          resizable: true,\n        },\n        minHeight: 180,\n        formConfig: {\n          // 全局禁用vxe-table的表单配置，使用formOptions\n          enabled: false,\n        },\n        proxyConfig: {\n          autoLoad: true,\n          response: {\n            result: 'items',\n            total: 'total',\n            list: 'items',\n          },\n          showActiveMsg: true,\n          showResponseMsg: false,\n        },\n        round: true,\n        showOverflow: true,\n        size: 'small',\n      } as VxeTableGridOptions,\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellImage' },\n    vxeUI.renderer.add('CellImage', {\n      renderTableDefault(renderOpts, params) {\n        const { props } = renderOpts;\n        const { column, row } = params;\n        const src = row[column.field];\n        return h(ElImage, { src, previewSrcList: [src], ...props });\n      },\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellLink' },\n    vxeUI.renderer.add('CellLink', {\n      renderTableDefault(renderOpts) {\n        const { props } = renderOpts;\n        return h(\n          ElButton,\n          { size: 'small', link: true },\n          { default: () => props?.text },\n        );\n      },\n    });\n\n    // 这里可以自行扩展 vxe-table 的全局配置，比如自定义格式化\n    // vxeUI.formats.add\n  },\n  useVbenForm,\n});\n\nexport { useVbenVxeGrid };\n\nexport type * from '@vben/plugins/vxe-table';\n"
  },
  {
    "path": "apps/web-ele/src/api/core/auth.ts",
    "content": "import { baseRequestClient, requestClient } from '#/api/request';\n\nexport namespace AuthApi {\n  /** 登录接口参数 */\n  export interface LoginParams {\n    password?: string;\n    username?: string;\n  }\n\n  /** 登录接口返回值 */\n  export interface LoginResult {\n    accessToken: string;\n  }\n\n  export interface RefreshTokenResult {\n    data: string;\n    status: number;\n  }\n}\n\n/**\n * 登录\n */\nexport async function loginApi(data: AuthApi.LoginParams) {\n  return requestClient.post<AuthApi.LoginResult>('/auth/login', data);\n}\n\n/**\n * 刷新accessToken\n */\nexport async function refreshTokenApi() {\n  return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {\n    withCredentials: true,\n  });\n}\n\n/**\n * 退出登录\n */\nexport async function logoutApi() {\n  return baseRequestClient.post('/auth/logout', {\n    withCredentials: true,\n  });\n}\n\n/**\n * 获取用户权限码\n */\nexport async function getAccessCodesApi() {\n  return requestClient.get<string[]>('/auth/codes');\n}\n"
  },
  {
    "path": "apps/web-ele/src/api/core/index.ts",
    "content": "export * from './auth';\nexport * from './menu';\nexport * from './user';\n"
  },
  {
    "path": "apps/web-ele/src/api/core/menu.ts",
    "content": "import type { RouteRecordStringComponent } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户所有菜单\n */\nexport async function getAllMenusApi() {\n  return requestClient.get<RouteRecordStringComponent[]>('/menu/all');\n}\n"
  },
  {
    "path": "apps/web-ele/src/api/core/user.ts",
    "content": "import type { UserInfo } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户信息\n */\nexport async function getUserInfoApi() {\n  return requestClient.get<UserInfo>('/user/info');\n}\n"
  },
  {
    "path": "apps/web-ele/src/api/index.ts",
    "content": "export * from './core';\n"
  },
  {
    "path": "apps/web-ele/src/api/request.ts",
    "content": "/**\n * 该文件可自行根据业务逻辑进行调整\n */\nimport type { RequestClientOptions } from '@vben/request';\n\nimport { useAppConfig } from '@vben/hooks';\nimport { preferences } from '@vben/preferences';\nimport {\n  authenticateResponseInterceptor,\n  defaultResponseInterceptor,\n  errorMessageResponseInterceptor,\n  RequestClient,\n} from '@vben/request';\nimport { useAccessStore } from '@vben/stores';\n\nimport { ElMessage } from 'element-plus';\n\nimport { useAuthStore } from '#/store';\n\nimport { refreshTokenApi } from './core';\n\nconst { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n\nfunction createRequestClient(baseURL: string, options?: RequestClientOptions) {\n  const client = new RequestClient({\n    ...options,\n    baseURL,\n  });\n\n  /**\n   * 重新认证逻辑\n   */\n  async function doReAuthenticate() {\n    console.warn('Access token or refresh token is invalid or expired. ');\n    const accessStore = useAccessStore();\n    const authStore = useAuthStore();\n    accessStore.setAccessToken(null);\n    if (\n      preferences.app.loginExpiredMode === 'modal' &&\n      accessStore.isAccessChecked\n    ) {\n      accessStore.setLoginExpired(true);\n    } else {\n      await authStore.logout();\n    }\n  }\n\n  /**\n   * 刷新token逻辑\n   */\n  async function doRefreshToken() {\n    const accessStore = useAccessStore();\n    const resp = await refreshTokenApi();\n    const newToken = resp.data;\n    accessStore.setAccessToken(newToken);\n    return newToken;\n  }\n\n  function formatToken(token: null | string) {\n    return token ? `Bearer ${token}` : null;\n  }\n\n  // 请求头处理\n  client.addRequestInterceptor({\n    fulfilled: async (config) => {\n      const accessStore = useAccessStore();\n\n      config.headers.Authorization = formatToken(accessStore.accessToken);\n      config.headers['Accept-Language'] = preferences.app.locale;\n      return config;\n    },\n  });\n\n  // 处理返回的响应数据格式\n  client.addResponseInterceptor(\n    defaultResponseInterceptor({\n      codeField: 'code',\n      dataField: 'data',\n      successCode: 0,\n    }),\n  );\n\n  // token过期的处理\n  client.addResponseInterceptor(\n    authenticateResponseInterceptor({\n      client,\n      doReAuthenticate,\n      doRefreshToken,\n      enableRefreshToken: preferences.app.enableRefreshToken,\n      formatToken,\n    }),\n  );\n\n  // 通用的错误处理,如果没有进入上面的错误处理逻辑，就会进入这里\n  client.addResponseInterceptor(\n    errorMessageResponseInterceptor((msg: string, error) => {\n      // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理，根据不同的 code 做不同的提示，而不是直接使用 message.error 提示 msg\n      // 当前mock接口返回的错误字段是 error 或者 message\n      const responseData = error?.response?.data ?? {};\n      const errorMessage = responseData?.error ?? responseData?.message ?? '';\n      // 如果没有错误信息，则会根据状态码进行提示\n      ElMessage.error(errorMessage || msg);\n    }),\n  );\n\n  return client;\n}\n\nexport const requestClient = createRequestClient(apiURL, {\n  responseReturn: 'data',\n});\n\nexport const baseRequestClient = new RequestClient({ baseURL: apiURL });\n"
  },
  {
    "path": "apps/web-ele/src/app.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useElementPlusDesignTokens } from '@vben/hooks';\n\nimport { ElConfigProvider } from 'element-plus';\n\nimport { elementLocale } from '#/locales';\n\ndefineOptions({ name: 'App' });\n\nuseElementPlusDesignTokens();\n</script>\n\n<template>\n  <ElConfigProvider :locale=\"elementLocale\">\n    <RouterView />\n  </ElConfigProvider>\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/bootstrap.ts",
    "content": "import { createApp, watchEffect } from 'vue';\n\nimport { registerAccessDirective } from '@vben/access';\nimport { registerLoadingDirective } from '@vben/common-ui';\nimport { preferences } from '@vben/preferences';\nimport { initStores } from '@vben/stores';\nimport '@vben/styles';\nimport '@vben/styles/ele';\n\nimport { useTitle } from '@vueuse/core';\nimport { ElLoading } from 'element-plus';\n\nimport { $t, setupI18n } from '#/locales';\n\nimport { initComponentAdapter } from './adapter/component';\nimport { initSetupVbenForm } from './adapter/form';\nimport App from './app.vue';\nimport { router } from './router';\n\nasync function bootstrap(namespace: string) {\n  // 初始化组件适配器\n  await initComponentAdapter();\n\n  // 初始化表单组件\n  await initSetupVbenForm();\n\n  // // 设置弹窗的默认配置\n  // setDefaultModalProps({\n  //   fullscreenButton: false,\n  // });\n  // // 设置抽屉的默认配置\n  // setDefaultDrawerProps({\n  //   zIndex: 2000,\n  // });\n  const app = createApp(App);\n\n  // 注册Element Plus提供的v-loading指令\n  app.directive('loading', ElLoading.directive);\n\n  // 注册Vben提供的v-loading和v-spinning指令\n  registerLoadingDirective(app, {\n    loading: false, // Vben提供的v-loading指令和Element Plus提供的v-loading指令二选一即可，此处false表示不注册Vben提供的v-loading指令\n    spinning: 'spinning',\n  });\n\n  // 国际化 i18n 配置\n  await setupI18n(app);\n\n  // 配置 pinia-tore\n  await initStores(app, { namespace });\n\n  // 安装权限指令\n  registerAccessDirective(app);\n\n  // 初始化 tippy\n  const { initTippy } = await import('@vben/common-ui/es/tippy');\n  initTippy(app);\n\n  // 配置路由及路由守卫\n  app.use(router);\n\n  // 配置Motion插件\n  const { MotionPlugin } = await import('@vben/plugins/motion');\n  app.use(MotionPlugin);\n\n  // 动态更新标题\n  watchEffect(() => {\n    if (preferences.app.dynamicTitle) {\n      const routeTitle = router.currentRoute.value.meta?.title;\n      const pageTitle =\n        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;\n      useTitle(pageTitle);\n    }\n  });\n\n  app.mount('#app');\n}\n\nexport { bootstrap };\n"
  },
  {
    "path": "apps/web-ele/src/layouts/auth.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { AuthPageLayout } from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst appName = computed(() => preferences.app.name);\nconst logo = computed(() => preferences.logo.source);\nconst logoDark = computed(() => preferences.logo.sourceDark);\n</script>\n\n<template>\n  <AuthPageLayout\n    :app-name=\"appName\"\n    :logo=\"logo\"\n    :logo-dark=\"logoDark\"\n    :page-description=\"$t('authentication.pageDesc')\"\n    :page-title=\"$t('authentication.pageTitle')\"\n  >\n    <!-- 自定义工具栏 -->\n    <!-- <template #toolbar></template> -->\n  </AuthPageLayout>\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/layouts/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { NotificationItem } from '@vben/layouts';\n\nimport { computed, ref, watch } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { AuthenticationLoginExpiredModal } from '@vben/common-ui';\nimport { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';\nimport { useWatermark } from '@vben/hooks';\nimport { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons';\nimport {\n  BasicLayout,\n  LockScreen,\n  Notification,\n  UserDropdown,\n} from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport { $t } from '#/locales';\nimport { useAuthStore } from '#/store';\nimport LoginForm from '#/views/_core/authentication/login.vue';\n\nconst notifications = ref<NotificationItem[]>([\n  {\n    id: 1,\n    avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',\n    date: '3小时前',\n    isRead: true,\n    message: '描述信息描述信息描述信息',\n    title: '收到了 14 份新周报',\n  },\n  {\n    id: 2,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '刚刚',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '朱偏右 回复了你',\n  },\n  {\n    id: 3,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '2024-01-01',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '曲丽丽 评论了你',\n  },\n  {\n    id: 4,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '代办提醒',\n  },\n  {\n    id: 5,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转Workspace示例',\n    link: '/workspace',\n  },\n  {\n    id: 6,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转外部链接示例',\n    link: 'https://doc.vben.pro',\n  },\n]);\n\nconst router = useRouter();\nconst userStore = useUserStore();\nconst authStore = useAuthStore();\nconst accessStore = useAccessStore();\nconst { destroyWatermark, updateWatermark } = useWatermark();\nconst showDot = computed(() =>\n  notifications.value.some((item) => !item.isRead),\n);\n\nconst menus = computed(() => [\n  {\n    handler: () => {\n      router.push({ name: 'Profile' });\n    },\n    icon: 'lucide:user',\n    text: $t('page.auth.profile'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_DOC_URL, {\n        target: '_blank',\n      });\n    },\n    icon: BookOpenText,\n    text: $t('ui.widgets.document'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_GITHUB_URL, {\n        target: '_blank',\n      });\n    },\n    icon: SvgGithubIcon,\n    text: 'GitHub',\n  },\n  {\n    handler: () => {\n      openWindow(`${VBEN_GITHUB_URL}/issues`, {\n        target: '_blank',\n      });\n    },\n    icon: CircleHelp,\n    text: $t('ui.widgets.qa'),\n  },\n]);\n\nconst avatar = computed(() => {\n  return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;\n});\n\nasync function handleLogout() {\n  await authStore.logout(false);\n}\n\nfunction handleNoticeClear() {\n  notifications.value = [];\n}\n\nfunction markRead(id: number | string) {\n  const item = notifications.value.find((item) => item.id === id);\n  if (item) {\n    item.isRead = true;\n  }\n}\n\nfunction remove(id: number | string) {\n  notifications.value = notifications.value.filter((item) => item.id !== id);\n}\n\nfunction handleMakeAll() {\n  notifications.value.forEach((item) => (item.isRead = true));\n}\nwatch(\n  () => ({\n    enable: preferences.app.watermark,\n    content: preferences.app.watermarkContent,\n  }),\n  async ({ enable, content }) => {\n    if (enable) {\n      await updateWatermark({\n        content:\n          content ||\n          `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,\n      });\n    } else {\n      destroyWatermark();\n    }\n  },\n  {\n    immediate: true,\n  },\n);\n</script>\n\n<template>\n  <BasicLayout @clear-preferences-and-logout=\"handleLogout\">\n    <template #user-dropdown>\n      <UserDropdown\n        :avatar\n        :menus\n        :text=\"userStore.userInfo?.realName\"\n        description=\"ann.vben@gmail.com\"\n        tag-text=\"Pro\"\n        @logout=\"handleLogout\"\n      />\n    </template>\n    <template #notification>\n      <Notification\n        :dot=\"showDot\"\n        :notifications=\"notifications\"\n        @clear=\"handleNoticeClear\"\n        @read=\"(item) => item.id && markRead(item.id)\"\n        @remove=\"(item) => item.id && remove(item.id)\"\n        @make-all=\"handleMakeAll\"\n      />\n    </template>\n    <template #extra>\n      <AuthenticationLoginExpiredModal\n        v-model:open=\"accessStore.loginExpired\"\n        :avatar\n      >\n        <LoginForm />\n      </AuthenticationLoginExpiredModal>\n    </template>\n    <template #lock-screen>\n      <LockScreen :avatar @to-login=\"handleLogout\" />\n    </template>\n  </BasicLayout>\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/layouts/index.ts",
    "content": "const BasicLayout = () => import('./basic.vue');\nconst AuthPageLayout = () => import('./auth.vue');\n\nconst IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);\n\nexport { AuthPageLayout, BasicLayout, IFrameView };\n"
  },
  {
    "path": "apps/web-ele/src/locales/README.md",
    "content": "# locale\n\n每个app使用的国际化可能不同，这里用于扩展国际化的功能，例如扩展 dayjs、antd组件库的多语言切换，以及app本身的国际化文件。\n"
  },
  {
    "path": "apps/web-ele/src/locales/index.ts",
    "content": "import type { Language } from 'element-plus/es/locale';\n\nimport type { App } from 'vue';\n\nimport type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';\n\nimport { ref } from 'vue';\n\nimport {\n  $t,\n  setupI18n as coreSetup,\n  loadLocalesMapFromDir,\n} from '@vben/locales';\nimport { preferences } from '@vben/preferences';\n\nimport dayjs from 'dayjs';\nimport enLocale from 'element-plus/es/locale/lang/en';\nimport defaultLocale from 'element-plus/es/locale/lang/zh-cn';\n\nconst elementLocale = ref<Language>(defaultLocale);\n\nconst modules = import.meta.glob('./langs/**/*.json');\n\nconst localesMap = loadLocalesMapFromDir(\n  /\\.\\/langs\\/([^/]+)\\/(.*)\\.json$/,\n  modules,\n);\n/**\n * 加载应用特有的语言包\n * 这里也可以改造为从服务端获取翻译数据\n * @param lang\n */\nasync function loadMessages(lang: SupportedLanguagesType) {\n  const [appLocaleMessages] = await Promise.all([\n    localesMap[lang]?.(),\n    loadThirdPartyMessage(lang),\n  ]);\n  return appLocaleMessages?.default;\n}\n\n/**\n * 加载第三方组件库的语言包\n * @param lang\n */\nasync function loadThirdPartyMessage(lang: SupportedLanguagesType) {\n  await Promise.all([loadElementLocale(lang), loadDayjsLocale(lang)]);\n}\n\n/**\n * 加载dayjs的语言包\n * @param lang\n */\nasync function loadDayjsLocale(lang: SupportedLanguagesType) {\n  let locale;\n  switch (lang) {\n    case 'en-US': {\n      locale = await import('dayjs/locale/en');\n      break;\n    }\n    case 'zh-CN': {\n      locale = await import('dayjs/locale/zh-cn');\n      break;\n    }\n    // 默认使用英语\n    default: {\n      locale = await import('dayjs/locale/en');\n    }\n  }\n  if (locale) {\n    dayjs.locale(locale);\n  } else {\n    console.error(`Failed to load dayjs locale for ${lang}`);\n  }\n}\n\n/**\n * 加载element-plus的语言包\n * @param lang\n */\nasync function loadElementLocale(lang: SupportedLanguagesType) {\n  switch (lang) {\n    case 'en-US': {\n      elementLocale.value = enLocale;\n      break;\n    }\n    case 'zh-CN': {\n      elementLocale.value = defaultLocale;\n      break;\n    }\n  }\n}\n\nasync function setupI18n(app: App, options: LocaleSetupOptions = {}) {\n  await coreSetup(app, {\n    defaultLocale: preferences.app.locale,\n    loadMessages,\n    missingWarn: !import.meta.env.PROD,\n    ...options,\n  });\n}\n\nexport { $t, elementLocale, setupI18n };\n"
  },
  {
    "path": "apps/web-ele/src/locales/langs/en-US/demos.json",
    "content": "{\n  \"title\": \"Demos\",\n  \"elementPlus\": \"Element Plus\",\n  \"form\": \"Form\",\n  \"vben\": {\n    \"title\": \"Project\",\n    \"about\": \"About\",\n    \"document\": \"Document\",\n    \"antdv\": \"Ant Design Vue Version\",\n    \"antdv-next\": \"Antdv Next Version\",\n    \"naive-ui\": \"Naive UI Version\",\n    \"element-plus\": \"Element Plus Version\",\n    \"tdesign\": \"TDesign Vue Version\"\n  }\n}\n"
  },
  {
    "path": "apps/web-ele/src/locales/langs/en-US/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"Login\",\n    \"register\": \"Register\",\n    \"codeLogin\": \"Code Login\",\n    \"qrcodeLogin\": \"Qr Code Login\",\n    \"forgetPassword\": \"Forget Password\",\n    \"profile\": \"Profile\"\n  },\n  \"dashboard\": {\n    \"title\": \"Dashboard\",\n    \"analytics\": \"Analytics\",\n    \"workspace\": \"Workspace\"\n  }\n}\n"
  },
  {
    "path": "apps/web-ele/src/locales/langs/zh-CN/demos.json",
    "content": "{\n  \"title\": \"演示\",\n  \"elementPlus\": \"Element Plus\",\n  \"form\": \"表单演示\",\n  \"vben\": {\n    \"title\": \"项目\",\n    \"about\": \"关于\",\n    \"document\": \"文档\",\n    \"antdv\": \"Ant Design Vue 版本\",\n    \"antdv-next\": \"Antdv Next 版本\",\n    \"naive-ui\": \"Naive UI 版本\",\n    \"element-plus\": \"Element Plus 版本\",\n    \"tdesign\": \"TDesign Vue 版本\"\n  }\n}\n"
  },
  {
    "path": "apps/web-ele/src/locales/langs/zh-CN/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"登录\",\n    \"register\": \"注册\",\n    \"codeLogin\": \"验证码登录\",\n    \"qrcodeLogin\": \"二维码登录\",\n    \"forgetPassword\": \"忘记密码\",\n    \"profile\": \"个人中心\"\n  },\n  \"dashboard\": {\n    \"title\": \"概览\",\n    \"analytics\": \"分析页\",\n    \"workspace\": \"工作台\"\n  }\n}\n"
  },
  {
    "path": "apps/web-ele/src/main.ts",
    "content": "import { initPreferences } from '@vben/preferences';\nimport { unmountGlobalLoading } from '@vben/utils';\n\nimport { overridesPreferences } from './preferences';\n\n/**\n * 应用初始化完成之后再进行页面加载渲染\n */\nasync function initApplication() {\n  // name用于指定项目唯一标识\n  // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据\n  const env = import.meta.env.PROD ? 'prod' : 'dev';\n  const appVersion = import.meta.env.VITE_APP_VERSION;\n  const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;\n\n  // app偏好设置初始化\n  await initPreferences({\n    namespace,\n    overrides: overridesPreferences,\n  });\n\n  // 启动应用并挂载\n  // vue应用主要逻辑及视图\n  const { bootstrap } = await import('./bootstrap');\n  await bootstrap(namespace);\n\n  // 移除并销毁loading\n  unmountGlobalLoading();\n}\n\ninitApplication();\n"
  },
  {
    "path": "apps/web-ele/src/preferences.ts",
    "content": "import { defineOverridesPreferences } from '@vben/preferences';\n\n/**\n * @description 项目配置文件\n * 只需要覆盖项目中的一部分配置，不需要的配置不用覆盖，会自动使用默认配置\n * !!! 更改配置后请清空缓存，否则可能不生效\n */\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    name: import.meta.env.VITE_APP_TITLE,\n  },\n});\n"
  },
  {
    "path": "apps/web-ele/src/router/access.ts",
    "content": "import type {\n  ComponentRecordType,\n  GenerateMenuAndRoutesOptions,\n} from '@vben/types';\n\nimport { generateAccessible } from '@vben/access';\nimport { preferences } from '@vben/preferences';\n\nimport { ElMessage } from 'element-plus';\n\nimport { getAllMenusApi } from '#/api';\nimport { BasicLayout, IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');\n\nasync function generateAccess(options: GenerateMenuAndRoutesOptions) {\n  const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');\n\n  const layoutMap: ComponentRecordType = {\n    BasicLayout,\n    IFrameView,\n  };\n\n  return await generateAccessible(preferences.app.accessMode, {\n    ...options,\n    fetchMenuListAsync: async () => {\n      ElMessage({\n        duration: 1500,\n        message: `${$t('common.loadingMenu')}...`,\n      });\n      return await getAllMenusApi();\n    },\n    // 可以指定没有权限跳转403页面\n    forbiddenComponent,\n    // 如果 route.meta.menuVisibleWithForbidden = true\n    layoutMap,\n    pageMap,\n  });\n}\n\nexport { generateAccess };\n"
  },
  {
    "path": "apps/web-ele/src/router/guard.ts",
    "content": "import type { Router } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { startProgress, stopProgress } from '@vben/utils';\n\nimport { accessRoutes, coreRouteNames } from '#/router/routes';\nimport { useAuthStore } from '#/store';\n\nimport { generateAccess } from './access';\n\n/**\n * 通用守卫配置\n * @param router\n */\nfunction setupCommonGuard(router: Router) {\n  // 记录已经加载的页面\n  const loadedPaths = new Set<string>();\n\n  router.beforeEach((to) => {\n    to.meta.loaded = loadedPaths.has(to.path);\n\n    // 页面加载进度条\n    if (!to.meta.loaded && preferences.transition.progress) {\n      startProgress();\n    }\n    return true;\n  });\n\n  router.afterEach((to) => {\n    // 记录页面是否加载,如果已经加载，后续的页面切换动画等效果不在重复执行\n\n    loadedPaths.add(to.path);\n\n    // 关闭页面加载进度条\n    if (preferences.transition.progress) {\n      stopProgress();\n    }\n  });\n}\n\n/**\n * 权限访问守卫配置\n * @param router\n */\nfunction setupAccessGuard(router: Router) {\n  router.beforeEach(async (to, from) => {\n    const accessStore = useAccessStore();\n    const userStore = useUserStore();\n    const authStore = useAuthStore();\n\n    // 基本路由，这些路由不需要进入权限拦截\n    if (coreRouteNames.includes(to.name as string)) {\n      if (to.path === LOGIN_PATH && accessStore.accessToken) {\n        return decodeURIComponent(\n          (to.query?.redirect as string) ||\n            userStore.userInfo?.homePath ||\n            preferences.app.defaultHomePath,\n        );\n      }\n      return true;\n    }\n\n    // accessToken 检查\n    if (!accessStore.accessToken) {\n      // 明确声明忽略权限访问权限，则可以访问\n      if (to.meta.ignoreAccess) {\n        return true;\n      }\n\n      // 没有访问权限，跳转登录页面\n      if (to.fullPath !== LOGIN_PATH) {\n        return {\n          path: LOGIN_PATH,\n          // 如不需要，直接删除 query\n          query:\n            to.fullPath === preferences.app.defaultHomePath\n              ? {}\n              : { redirect: encodeURIComponent(to.fullPath) },\n          // 携带当前跳转的页面，登录后重新跳转该页面\n          replace: true,\n        };\n      }\n      return to;\n    }\n\n    // 是否已经生成过动态路由\n    if (accessStore.isAccessChecked) {\n      return true;\n    }\n\n    // 生成路由表\n    // 当前登录用户拥有的角色标识列表\n    const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());\n    const userRoles = userInfo.roles ?? [];\n\n    // 生成菜单和路由\n    const { accessibleMenus, accessibleRoutes } = await generateAccess({\n      roles: userRoles,\n      router,\n      // 则会在菜单中显示，但是访问会被重定向到403\n      routes: accessRoutes,\n    });\n\n    // 保存菜单信息和路由信息\n    accessStore.setAccessMenus(accessibleMenus);\n    accessStore.setAccessRoutes(accessibleRoutes);\n    accessStore.setIsAccessChecked(true);\n    const redirectPath = (from.query.redirect ??\n      (to.path === preferences.app.defaultHomePath\n        ? userInfo.homePath || preferences.app.defaultHomePath\n        : to.fullPath)) as string;\n\n    return {\n      ...router.resolve(decodeURIComponent(redirectPath)),\n      replace: true,\n    };\n  });\n}\n\n/**\n * 项目守卫配置\n * @param router\n */\nfunction createRouterGuard(router: Router) {\n  /** 通用 */\n  setupCommonGuard(router);\n  /** 权限访问 */\n  setupAccessGuard(router);\n}\n\nexport { createRouterGuard };\n"
  },
  {
    "path": "apps/web-ele/src/router/index.ts",
    "content": "import {\n  createRouter,\n  createWebHashHistory,\n  createWebHistory,\n} from 'vue-router';\n\nimport { resetStaticRoutes } from '@vben/utils';\n\nimport { createRouterGuard } from './guard';\nimport { routes } from './routes';\n\n/**\n *  @zh_CN 创建vue-router实例\n */\nconst router = createRouter({\n  history:\n    import.meta.env.VITE_ROUTER_HISTORY === 'hash'\n      ? createWebHashHistory(import.meta.env.VITE_BASE)\n      : createWebHistory(import.meta.env.VITE_BASE),\n  // 应该添加到路由的初始路由列表。\n  routes,\n  scrollBehavior: (to, _from, savedPosition) => {\n    if (savedPosition) {\n      return savedPosition;\n    }\n    return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };\n  },\n  // 是否应该禁止尾部斜杠。\n  // strict: true,\n});\n\nconst resetRoutes = () => resetStaticRoutes(router, routes);\n\n// 创建路由守卫\ncreateRouterGuard(router);\n\nexport { resetRoutes, router };\n"
  },
  {
    "path": "apps/web-ele/src/router/routes/core.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst BasicLayout = () => import('#/layouts/basic.vue');\nconst AuthPageLayout = () => import('#/layouts/auth.vue');\n/** 全局404页面 */\nconst fallbackNotFoundRoute: RouteRecordRaw = {\n  component: () => import('#/views/_core/fallback/not-found.vue'),\n  meta: {\n    hideInBreadcrumb: true,\n    hideInMenu: true,\n    hideInTab: true,\n    title: '404',\n  },\n  name: 'FallbackNotFound',\n  path: '/:path(.*)*',\n};\n\n/** 基本路由，这些路由是必须存在的 */\nconst coreRoutes: RouteRecordRaw[] = [\n  /**\n   * 根路由\n   * 使用基础布局，作为所有页面的父级容器，子级就不必配置BasicLayout。\n   * 此路由必须存在，且不应修改\n   */\n  {\n    component: BasicLayout,\n    meta: {\n      hideInBreadcrumb: true,\n      title: 'Root',\n    },\n    name: 'Root',\n    path: '/',\n    redirect: preferences.app.defaultHomePath,\n    children: [],\n  },\n  {\n    component: AuthPageLayout,\n    meta: {\n      hideInTab: true,\n      title: 'Authentication',\n    },\n    name: 'Authentication',\n    path: '/auth',\n    redirect: LOGIN_PATH,\n    children: [\n      {\n        name: 'Login',\n        path: 'login',\n        component: () => import('#/views/_core/authentication/login.vue'),\n        meta: {\n          title: $t('page.auth.login'),\n        },\n      },\n      {\n        name: 'CodeLogin',\n        path: 'code-login',\n        component: () => import('#/views/_core/authentication/code-login.vue'),\n        meta: {\n          title: $t('page.auth.codeLogin'),\n        },\n      },\n      {\n        name: 'QrCodeLogin',\n        path: 'qrcode-login',\n        component: () =>\n          import('#/views/_core/authentication/qrcode-login.vue'),\n        meta: {\n          title: $t('page.auth.qrcodeLogin'),\n        },\n      },\n      {\n        name: 'ForgetPassword',\n        path: 'forget-password',\n        component: () =>\n          import('#/views/_core/authentication/forget-password.vue'),\n        meta: {\n          title: $t('page.auth.forgetPassword'),\n        },\n      },\n      {\n        name: 'Register',\n        path: 'register',\n        component: () => import('#/views/_core/authentication/register.vue'),\n        meta: {\n          title: $t('page.auth.register'),\n        },\n      },\n    ],\n  },\n];\n\nexport { coreRoutes, fallbackNotFoundRoute };\n"
  },
  {
    "path": "apps/web-ele/src/router/routes/index.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { mergeRouteModules, traverseTreeValues } from '@vben/utils';\n\nimport { coreRoutes, fallbackNotFoundRoute } from './core';\n\nconst dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {\n  eager: true,\n});\n\n// 有需要可以自行打开注释，并创建文件夹\n// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });\n// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });\n\n/** 动态路由 */\nconst dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);\n\n/** 外部路由列表，访问这些页面可以不需要Layout，可能用于内嵌在别的系统(不会显示在菜单中) */\n// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);\n// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);\nconst staticRoutes: RouteRecordRaw[] = [];\nconst externalRoutes: RouteRecordRaw[] = [];\n\n/** 路由列表，由基本路由、外部路由和404兜底路由组成\n *  无需走权限验证（会一直显示在菜单中） */\nconst routes: RouteRecordRaw[] = [\n  ...coreRoutes,\n  ...externalRoutes,\n  fallbackNotFoundRoute,\n];\n\n/** 基本路由列表，这些路由不需要进入权限拦截 */\nconst coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);\n\n/** 有权限校验的路由列表，包含动态路由和静态路由 */\nconst accessRoutes = [...dynamicRoutes, ...staticRoutes];\nexport { accessRoutes, coreRouteNames, routes };\n"
  },
  {
    "path": "apps/web-ele/src/router/routes/modules/dashboard.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'lucide:layout-dashboard',\n      order: -1,\n      title: $t('page.dashboard.title'),\n    },\n    name: 'Dashboard',\n    path: '/dashboard',\n    children: [\n      {\n        name: 'Analytics',\n        path: '/analytics',\n        component: () => import('#/views/dashboard/analytics/index.vue'),\n        meta: {\n          affixTab: true,\n          icon: 'lucide:area-chart',\n          title: $t('page.dashboard.analytics'),\n        },\n      },\n      {\n        name: 'Workspace',\n        path: '/workspace',\n        component: () => import('#/views/dashboard/workspace/index.vue'),\n        meta: {\n          icon: 'carbon:workspace',\n          title: $t('page.dashboard.workspace'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-ele/src/router/routes/modules/demos.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'ic:baseline-view-in-ar',\n      keepAlive: true,\n      order: 1000,\n      title: $t('demos.title'),\n    },\n    name: 'Demos',\n    path: '/demos',\n    children: [\n      {\n        meta: {\n          title: $t('demos.elementPlus'),\n        },\n        name: 'NaiveDemos',\n        path: '/demos/element',\n        component: () => import('#/views/demos/element/index.vue'),\n      },\n      {\n        meta: {\n          title: $t('demos.form'),\n        },\n        name: 'BasicForm',\n        path: '/demos/form',\n        component: () => import('#/views/demos/form/basic.vue'),\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-ele/src/router/routes/modules/vben.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport {\n  VBEN_ANT_PREVIEW_URL,\n  VBEN_ANTDV_NEXT_PREVIEW_URL,\n  VBEN_DOC_URL,\n  VBEN_GITHUB_URL,\n  VBEN_LOGO_URL,\n  VBEN_NAIVE_PREVIEW_URL,\n  VBEN_TD_PREVIEW_URL,\n} from '@vben/constants';\nimport {\n  SvgAntdvLogoIcon,\n  SvgAntdvNextLogoIcon,\n  SvgTDesignIcon,\n} from '@vben/icons';\n\nimport { IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      badgeType: 'dot',\n      icon: VBEN_LOGO_URL,\n      order: 9998,\n      title: $t('demos.vben.title'),\n    },\n    name: 'VbenProject',\n    path: '/vben-admin',\n    children: [\n      {\n        name: 'VbenDocument',\n        path: '/vben-admin/document',\n        component: IFrameView,\n        meta: {\n          icon: 'lucide:book-open-text',\n          link: VBEN_DOC_URL,\n          title: $t('demos.vben.document'),\n        },\n      },\n      {\n        name: 'VbenGithub',\n        path: '/vben-admin/github',\n        component: IFrameView,\n        meta: {\n          icon: 'mdi:github',\n          link: VBEN_GITHUB_URL,\n          title: 'Github',\n        },\n      },\n      {\n        name: 'VbenNaive',\n        path: '/vben-admin/naive',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: 'logos:naiveui',\n          link: VBEN_NAIVE_PREVIEW_URL,\n          title: $t('demos.vben.naive-ui'),\n        },\n      },\n      {\n        name: 'VbenAntd',\n        path: '/vben-admin/antd',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgAntdvLogoIcon,\n          link: VBEN_ANT_PREVIEW_URL,\n          title: $t('demos.vben.antdv'),\n        },\n      },\n      {\n        name: 'VbenAntdVNext',\n        path: '/vben-admin/antdv-next',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgAntdvNextLogoIcon,\n          link: VBEN_ANTDV_NEXT_PREVIEW_URL,\n          title: $t('demos.vben.antdv-next'),\n        },\n      },\n      {\n        name: 'VbenTDesign',\n        path: '/vben-admin/tdesign',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgTDesignIcon,\n          link: VBEN_TD_PREVIEW_URL,\n          title: $t('demos.vben.tdesign'),\n        },\n      },\n    ],\n  },\n  {\n    name: 'VbenAbout',\n    path: '/vben-admin/about',\n    component: () => import('#/views/_core/about/index.vue'),\n    meta: {\n      icon: 'lucide:copyright',\n      title: $t('demos.vben.about'),\n      order: 9999,\n    },\n  },\n  {\n    name: 'Profile',\n    path: '/profile',\n    component: () => import('#/views/_core/profile/index.vue'),\n    meta: {\n      icon: 'lucide:user',\n      hideInMenu: true,\n      title: $t('page.auth.profile'),\n    },\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-ele/src/store/auth.ts",
    "content": "import type { Recordable, UserInfo } from '@vben/types';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';\n\nimport { ElNotification } from 'element-plus';\nimport { defineStore } from 'pinia';\n\nimport { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';\nimport { $t } from '#/locales';\n\nexport const useAuthStore = defineStore('auth', () => {\n  const accessStore = useAccessStore();\n  const userStore = useUserStore();\n  const router = useRouter();\n\n  const loginLoading = ref(false);\n\n  /**\n   * 异步处理登录操作\n   * Asynchronously handle the login process\n   * @param params 登录表单数据\n   */\n  async function authLogin(\n    params: Recordable<any>,\n    onSuccess?: () => Promise<void> | void,\n  ) {\n    // 异步处理用户登录操作并获取 accessToken\n    let userInfo: null | UserInfo = null;\n    try {\n      loginLoading.value = true;\n      const { accessToken } = await loginApi(params);\n\n      // 如果成功获取到 accessToken\n      if (accessToken) {\n        // 将 accessToken 存储到 accessStore 中\n        accessStore.setAccessToken(accessToken);\n\n        // 获取用户信息并存储到 accessStore 中\n        const [fetchUserInfoResult, accessCodes] = await Promise.all([\n          fetchUserInfo(),\n          getAccessCodesApi(),\n        ]);\n\n        userInfo = fetchUserInfoResult;\n\n        userStore.setUserInfo(userInfo);\n        accessStore.setAccessCodes(accessCodes);\n\n        if (accessStore.loginExpired) {\n          accessStore.setLoginExpired(false);\n        } else {\n          onSuccess\n            ? await onSuccess?.()\n            : await router.push(\n                userInfo.homePath || preferences.app.defaultHomePath,\n              );\n        }\n\n        if (userInfo?.realName) {\n          ElNotification({\n            message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,\n            title: $t('authentication.loginSuccess'),\n            type: 'success',\n          });\n        }\n      }\n    } finally {\n      loginLoading.value = false;\n    }\n\n    return {\n      userInfo,\n    };\n  }\n\n  async function logout(redirect: boolean = true) {\n    try {\n      await logoutApi();\n    } catch {\n      // 不做任何处理\n    }\n    resetAllStores();\n    accessStore.setLoginExpired(false);\n\n    // 回登录页带上当前路由地址\n    await router.replace({\n      path: LOGIN_PATH,\n      query: redirect\n        ? {\n            redirect: encodeURIComponent(router.currentRoute.value.fullPath),\n          }\n        : {},\n    });\n  }\n\n  async function fetchUserInfo() {\n    const userInfo = await getUserInfoApi();\n    userStore.setUserInfo(userInfo);\n    return userInfo;\n  }\n\n  function $reset() {\n    loginLoading.value = false;\n  }\n\n  return {\n    $reset,\n    authLogin,\n    fetchUserInfo,\n    loginLoading,\n    logout,\n  };\n});\n"
  },
  {
    "path": "apps/web-ele/src/store/index.ts",
    "content": "export * from './auth';\n"
  },
  {
    "path": "apps/web-ele/src/types/element-plus-style-css.d.ts",
    "content": "declare module 'element-plus/es/components/*/style/css' {\n  const sideEffect: undefined;\n  export default sideEffect;\n}\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/README.md",
    "content": "# \\_core\n\n此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/about/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { About } from '@vben/common-ui';\n\ndefineOptions({ name: 'About' });\n</script>\n\n<template>\n  <About />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/authentication/code-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationCodeLogin, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'CodeLogin' });\n\nconst loading = ref(false);\nconst CODE_LENGTH = 6;\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.mobile'),\n      },\n      fieldName: 'phoneNumber',\n      label: $t('authentication.mobile'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.mobileTip') })\n        .refine((v) => /^\\d{11}$/.test(v), {\n          message: $t('authentication.mobileErrortip'),\n        }),\n    },\n    {\n      component: 'VbenPinInput',\n      componentProps: {\n        codeLength: CODE_LENGTH,\n        createText: (countdown: number) => {\n          const text =\n            countdown > 0\n              ? $t('authentication.sendText', [countdown])\n              : $t('authentication.sendCode');\n          return text;\n        },\n        placeholder: $t('authentication.code'),\n        handleSendCode: async () => {\n          console.warn('发送验证码前校验等逻辑');\n          throw new Error('手机号校验失败');\n        },\n      },\n      fieldName: 'code',\n      label: $t('authentication.code'),\n      rules: z.string().length(CODE_LENGTH, {\n        message: $t('authentication.codeTip', [CODE_LENGTH]),\n      }),\n    },\n  ];\n});\n/**\n * 异步处理登录操作\n * Asynchronously handle the login process\n * @param values 登录表单数据\n */\nasync function handleLogin(values: Recordable<any>) {\n  void values;\n}\n</script>\n\n<template>\n  <AuthenticationCodeLogin\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleLogin\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/authentication/forget-password.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationForgetPassword, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'ForgetPassword' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: 'example@example.com',\n      },\n      fieldName: 'email',\n      label: $t('authentication.email'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.emailTip') })\n        .email($t('authentication.emailValidErrorTip')),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationForgetPassword\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/authentication/login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { BasicOption } from '@vben/types';\n\nimport { computed, markRaw } from 'vue';\n\nimport { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { useAuthStore } from '#/store';\n\ndefineOptions({ name: 'Login' });\n\nconst authStore = useAuthStore();\n\nconst MOCK_USER_OPTIONS: BasicOption[] = [\n  {\n    label: 'Super',\n    value: 'vben',\n  },\n  {\n    label: 'Admin',\n    value: 'admin',\n  },\n  {\n    label: 'User',\n    value: 'jack',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenSelect',\n      componentProps: {\n        options: MOCK_USER_OPTIONS,\n        placeholder: $t('authentication.selectAccount'),\n      },\n      fieldName: 'selectAccount',\n      label: $t('authentication.selectAccount'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.selectAccount') })\n        .optional()\n        .default('vben'),\n    },\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      dependencies: {\n        trigger(values, form) {\n          if (values.selectAccount) {\n            const findUser = MOCK_USER_OPTIONS.find(\n              (item) => item.value === values.selectAccount,\n            );\n            if (findUser) {\n              form.setValues({\n                password: '123456',\n                username: findUser.value,\n              });\n            }\n          }\n        },\n        triggerFields: ['selectAccount'],\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: markRaw(SliderCaptcha),\n      fieldName: 'captcha',\n      rules: z.boolean().refine((value) => value, {\n        message: $t('authentication.verifyRequiredTip'),\n      }),\n    },\n  ];\n});\n</script>\n\n<template>\n  <AuthenticationLogin\n    :form-schema=\"formSchema\"\n    :loading=\"authStore.loginLoading\"\n    @submit=\"authStore.authLogin\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/authentication/qrcode-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport { AuthenticationQrCodeLogin } from '@vben/common-ui';\nimport { LOGIN_PATH } from '@vben/constants';\n\ndefineOptions({ name: 'QrCodeLogin' });\n</script>\n\n<template>\n  <AuthenticationQrCodeLogin :login-path=\"LOGIN_PATH\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/authentication/register.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, h, ref } from 'vue';\n\nimport { AuthenticationRegister, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'Register' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      renderComponentContent() {\n        return {\n          strengthText: () => $t('authentication.passwordStrength'),\n        };\n      },\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.confirmPassword'),\n      },\n      dependencies: {\n        rules(values) {\n          const { password } = values;\n          return z\n            .string({ required_error: $t('authentication.passwordTip') })\n            .min(1, { message: $t('authentication.passwordTip') })\n            .refine((value) => value === password, {\n              message: $t('authentication.confirmPasswordTip'),\n            });\n        },\n        triggerFields: ['password'],\n      },\n      fieldName: 'confirmPassword',\n      label: $t('authentication.confirmPassword'),\n    },\n    {\n      component: 'VbenCheckbox',\n      fieldName: 'agreePolicy',\n      renderComponentContent: () => ({\n        default: () =>\n          h('span', [\n            $t('authentication.agree'),\n            h(\n              'a',\n              {\n                class: 'vben-link ml-1 ',\n                href: '',\n              },\n              `${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,\n            ),\n          ]),\n      }),\n      rules: z.boolean().refine((value) => !!value, {\n        message: $t('authentication.agreeTip'),\n      }),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationRegister\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/fallback/coming-soon.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback status=\"coming-soon\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/fallback/forbidden.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback403Demo' });\n</script>\n\n<template>\n  <Fallback status=\"403\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/fallback/internal-error.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback500Demo' });\n</script>\n\n<template>\n  <Fallback status=\"500\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/fallback/not-found.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback404Demo' });\n</script>\n\n<template>\n  <Fallback status=\"404\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/fallback/offline.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'FallbackOfflineDemo' });\n</script>\n\n<template>\n  <Fallback status=\"offline\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/profile/base-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { BasicOption } from '@vben/types';\n\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed, onMounted, ref } from 'vue';\n\nimport { ProfileBaseSetting } from '@vben/common-ui';\n\nimport { getUserInfoApi } from '#/api';\n\nconst profileBaseSettingRef = ref();\n\nconst MOCK_ROLES_OPTIONS: BasicOption[] = [\n  {\n    label: '管理员',\n    value: 'super',\n  },\n  {\n    label: '用户',\n    value: 'user',\n  },\n  {\n    label: '测试',\n    value: 'test',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'realName',\n      component: 'Input',\n      label: '姓名',\n    },\n    {\n      fieldName: 'username',\n      component: 'Input',\n      label: '用户名',\n    },\n    {\n      fieldName: 'roles',\n      component: 'Select',\n      componentProps: {\n        mode: 'tags',\n        options: MOCK_ROLES_OPTIONS,\n      },\n      label: '角色',\n    },\n    {\n      fieldName: 'introduction',\n      component: 'Textarea',\n      label: '个人简介',\n    },\n  ];\n});\n\nonMounted(async () => {\n  const data = await getUserInfoApi();\n  profileBaseSettingRef.value.getFormApi().setValues(data);\n});\n</script>\n<template>\n  <ProfileBaseSetting ref=\"profileBaseSettingRef\" :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/profile/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\n\nimport { Profile } from '@vben/common-ui';\nimport { useUserStore } from '@vben/stores';\n\nimport ProfileBase from './base-setting.vue';\nimport ProfileNotificationSetting from './notification-setting.vue';\nimport ProfilePasswordSetting from './password-setting.vue';\nimport ProfileSecuritySetting from './security-setting.vue';\n\nconst userStore = useUserStore();\n\nconst tabsValue = ref<string>('basic');\n\nconst tabs = ref([\n  {\n    label: '基本设置',\n    value: 'basic',\n  },\n  {\n    label: '安全设置',\n    value: 'security',\n  },\n  {\n    label: '修改密码',\n    value: 'password',\n  },\n  {\n    label: '新消息提醒',\n    value: 'notice',\n  },\n]);\n</script>\n<template>\n  <Profile\n    v-model:model-value=\"tabsValue\"\n    title=\"个人中心\"\n    :user-info=\"userStore.userInfo\"\n    :tabs=\"tabs\"\n  >\n    <template #content>\n      <ProfileBase v-if=\"tabsValue === 'basic'\" />\n      <ProfileSecuritySetting v-if=\"tabsValue === 'security'\" />\n      <ProfilePasswordSetting v-if=\"tabsValue === 'password'\" />\n      <ProfileNotificationSetting v-if=\"tabsValue === 'notice'\" />\n    </template>\n  </Profile>\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/profile/notification-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileNotificationSetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '其他用户的消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'systemMessage',\n      label: '系统消息',\n      description: '系统消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'todoTask',\n      label: '待办任务',\n      description: '待办任务将以站内信的形式通知',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileNotificationSetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/profile/password-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed } from 'vue';\n\nimport { ProfilePasswordSetting, z } from '@vben/common-ui';\n\nimport { ElMessage } from 'element-plus';\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'oldPassword',\n      label: '旧密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: '请输入旧密码',\n      },\n    },\n    {\n      fieldName: 'newPassword',\n      label: '新密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请输入新密码',\n      },\n    },\n    {\n      fieldName: 'confirmPassword',\n      label: '确认密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请再次输入新密码',\n      },\n      dependencies: {\n        rules(values) {\n          const { newPassword } = values;\n          return z\n            .string({ required_error: '请再次输入新密码' })\n            .min(1, { message: '请再次输入新密码' })\n            .refine((value) => value === newPassword, {\n              message: '两次输入的密码不一致',\n            });\n        },\n        triggerFields: ['newPassword'],\n      },\n    },\n  ];\n});\n\nfunction handleSubmit() {\n  ElMessage.success('密码修改成功');\n}\n</script>\n<template>\n  <ProfilePasswordSetting\n    class=\"w-1/3\"\n    :form-schema=\"formSchema\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/_core/profile/security-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileSecuritySetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '当前密码强度：强',\n    },\n    {\n      value: true,\n      fieldName: 'securityPhone',\n      label: '密保手机',\n      description: '已绑定手机：138****8293',\n    },\n    {\n      value: true,\n      fieldName: 'securityQuestion',\n      label: '密保问题',\n      description: '未设置密保问题，密保问题可有效保护账户安全',\n    },\n    {\n      value: true,\n      fieldName: 'securityEmail',\n      label: '备用邮箱',\n      description: '已绑定邮箱：ant***sign.com',\n    },\n    {\n      value: false,\n      fieldName: 'securityMfa',\n      label: 'MFA 设备',\n      description: '未绑定 MFA 设备，绑定后，可以进行二次确认',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileSecuritySetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        areaStyle: {},\n        data: [\n          111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,\n          36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,\n          111,\n        ],\n        itemStyle: {\n          color: '#5ab1ef',\n        },\n        smooth: true,\n        type: 'line',\n      },\n      {\n        areaStyle: {},\n        data: [\n          33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,\n          11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,\n        ],\n        itemStyle: {\n          color: '#019680',\n        },\n        smooth: true,\n        type: 'line',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          color: '#019680',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    // xAxis: {\n    //   axisTick: {\n    //     show: false,\n    //   },\n    //   boundaryGap: false,\n    //   data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n    //   type: 'category',\n    // },\n    xAxis: {\n      axisTick: {\n        show: false,\n      },\n      boundaryGap: false,\n      data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n      splitLine: {\n        lineStyle: {\n          type: 'solid',\n          width: 1,\n        },\n        show: true,\n      },\n      type: 'category',\n    },\n    yAxis: [\n      {\n        axisTick: {\n          show: false,\n        },\n        max: 80_000,\n        splitArea: {\n          show: true,\n        },\n        splitNumber: 4,\n        type: 'value',\n      },\n    ],\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: 0,\n      data: ['访问', '趋势'],\n    },\n    radar: {\n      indicator: [\n        {\n          name: '网页',\n        },\n        {\n          name: '移动端',\n        },\n        {\n          name: 'Ipad',\n        },\n        {\n          name: '客户端',\n        },\n        {\n          name: '第三方',\n        },\n        {\n          name: '其它',\n        },\n      ],\n      radius: '60%',\n      splitNumber: 8,\n    },\n    series: [\n      {\n        areaStyle: {\n          opacity: 1,\n          shadowBlur: 0,\n          shadowColor: 'rgba(0,0,0,.2)',\n          shadowOffsetX: 0,\n          shadowOffsetY: 10,\n        },\n        data: [\n          {\n            itemStyle: {\n              color: '#b6a2de',\n            },\n            name: '访问',\n            value: [90, 50, 86, 40, 50, 20],\n          },\n          {\n            itemStyle: {\n              color: '#5ab1ef',\n            },\n            name: '趋势',\n            value: [70, 75, 70, 76, 20, 85],\n          },\n        ],\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        symbolSize: 0,\n        type: 'radar',\n      },\n    ],\n    tooltip: {},\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 400;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        center: ['50%', '50%'],\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '外包', value: 500 },\n          { name: '定制', value: 310 },\n          { name: '技术支持', value: 274 },\n          { name: '远程', value: 400 },\n        ].toSorted((a, b) => {\n          return a.value - b.value;\n        }),\n        name: '商业占比',\n        radius: '80%',\n        roseType: 'radius',\n        type: 'pie',\n      },\n    ],\n\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: '2%',\n      left: 'center',\n    },\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 100;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        avoidLabelOverlap: false,\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '搜索引擎', value: 1048 },\n          { name: '直接访问', value: 735 },\n          { name: '邮件营销', value: 580 },\n          { name: '联盟广告', value: 484 },\n        ],\n        emphasis: {\n          label: {\n            fontSize: '12',\n            fontWeight: 'bold',\n            show: true,\n          },\n        },\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        label: {\n          position: 'center',\n          show: false,\n        },\n        labelLine: {\n          show: false,\n        },\n        name: '访问来源',\n        radius: ['40%', '65%'],\n        type: 'pie',\n      },\n    ],\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        barMaxWidth: 80,\n        // color: '#4f69fd',\n        data: [\n          3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,\n          3200, 4800,\n        ],\n        type: 'bar',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          // color: '#4f69fd',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    xAxis: {\n      data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),\n      type: 'category',\n    },\n    yAxis: {\n      max: 8000,\n      splitNumber: 4,\n      type: 'value',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/dashboard/analytics/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { AnalysisOverviewItem } from '@vben/common-ui';\nimport type { TabOption } from '@vben/types';\n\nimport {\n  AnalysisChartCard,\n  AnalysisChartsTabs,\n  AnalysisOverview,\n} from '@vben/common-ui';\nimport {\n  SvgBellIcon,\n  SvgCakeIcon,\n  SvgCardIcon,\n  SvgDownloadIcon,\n} from '@vben/icons';\n\nimport AnalyticsTrends from './analytics-trends.vue';\nimport AnalyticsVisitsData from './analytics-visits-data.vue';\nimport AnalyticsVisitsSales from './analytics-visits-sales.vue';\nimport AnalyticsVisitsSource from './analytics-visits-source.vue';\nimport AnalyticsVisits from './analytics-visits.vue';\n\nconst overviewItems: AnalysisOverviewItem[] = [\n  {\n    icon: SvgCardIcon,\n    title: '用户量',\n    totalTitle: '总用户量',\n    totalValue: 120_000,\n    value: 2000,\n  },\n  {\n    icon: SvgCakeIcon,\n    title: '访问量',\n    totalTitle: '总访问量',\n    totalValue: 500_000,\n    value: 20_000,\n  },\n  {\n    icon: SvgDownloadIcon,\n    title: '下载量',\n    totalTitle: '总下载量',\n    totalValue: 120_000,\n    value: 8000,\n  },\n  {\n    icon: SvgBellIcon,\n    title: '使用量',\n    totalTitle: '总使用量',\n    totalValue: 50_000,\n    value: 5000,\n  },\n];\n\nconst chartTabs: TabOption[] = [\n  {\n    label: '流量趋势',\n    value: 'trends',\n  },\n  {\n    label: '月访问量',\n    value: 'visits',\n  },\n];\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <AnalysisOverview :items=\"overviewItems\" />\n    <AnalysisChartsTabs :tabs=\"chartTabs\" class=\"mt-5\">\n      <template #trends>\n        <AnalyticsTrends />\n      </template>\n      <template #visits>\n        <AnalyticsVisits />\n      </template>\n    </AnalysisChartsTabs>\n\n    <div class=\"mt-5 w-full md:flex\">\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问数量\">\n        <AnalyticsVisitsData />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSource />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSales />\n      </AnalysisChartCard>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/dashboard/workspace/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  WorkbenchProjectItem,\n  WorkbenchQuickNavItem,\n  WorkbenchTodoItem,\n  WorkbenchTrendItem,\n} from '@vben/common-ui';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport {\n  AnalysisChartCard,\n  WorkbenchHeader,\n  WorkbenchProject,\n  WorkbenchQuickNav,\n  WorkbenchTodo,\n  WorkbenchTrends,\n} from '@vben/common-ui';\nimport { preferences } from '@vben/preferences';\nimport { useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';\n\nconst userStore = useUserStore();\n\n// 这是一个示例数据，实际项目中需要根据实际情况进行调整\n// url 也可以是内部路由，在 navTo 方法中识别处理，进行内部跳转\n// 例如：url: /dashboard/workspace\nconst projectItems: WorkbenchProjectItem[] = [\n  {\n    color: '',\n    content: '不要等待机会，而要创造机会。',\n    date: '2021-04-01',\n    group: '开源组',\n    icon: 'carbon:logo-github',\n    title: 'Github',\n    url: 'https://github.com',\n  },\n  {\n    color: '#3fb27f',\n    content: '现在的你决定将来的你。',\n    date: '2021-04-01',\n    group: '算法组',\n    icon: 'ion:logo-vue',\n    title: 'Vue',\n    url: 'https://vuejs.org',\n  },\n  {\n    color: '#e18525',\n    content: '没有什么才能比努力更重要。',\n    date: '2021-04-01',\n    group: '上班摸鱼',\n    icon: 'ion:logo-html5',\n    title: 'Html5',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',\n  },\n  {\n    color: '#bf0c2c',\n    content: '热情和欲望可以突破一切难关。',\n    date: '2021-04-01',\n    group: 'UI',\n    icon: 'ion:logo-angular',\n    title: 'Angular',\n    url: 'https://angular.io',\n  },\n  {\n    color: '#00d8ff',\n    content: '健康的身体是实现目标的基石。',\n    date: '2021-04-01',\n    group: '技术牛',\n    icon: 'bx:bxl-react',\n    title: 'React',\n    url: 'https://reactjs.org',\n  },\n  {\n    color: '#EBD94E',\n    content: '路是走出来的，而不是空想出来的。',\n    date: '2021-04-01',\n    group: '架构组',\n    icon: 'ion:logo-javascript',\n    title: 'Js',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',\n  },\n];\n\n// 同样，这里的 url 也可以使用以 http 开头的外部链接\nconst quickNavItems: WorkbenchQuickNavItem[] = [\n  {\n    color: '#1fdaca',\n    icon: 'ion:home-outline',\n    title: '首页',\n    url: '/',\n  },\n  {\n    color: '#bf0c2c',\n    icon: 'ion:grid-outline',\n    title: '仪表盘',\n    url: '/dashboard',\n  },\n  {\n    color: '#e18525',\n    icon: 'ion:layers-outline',\n    title: '组件',\n    url: '/demos/features/icons',\n  },\n  {\n    color: '#3fb27f',\n    icon: 'ion:settings-outline',\n    title: '系统管理',\n    url: '/demos/features/login-expired', // 这里的 URL 是示例，实际项目中需要根据实际情况进行调整\n  },\n  {\n    color: '#4daf1bc9',\n    icon: 'ion:key-outline',\n    title: '权限管理',\n    url: '/demos/access/page-control',\n  },\n  {\n    color: '#00d8ff',\n    icon: 'ion:bar-chart-outline',\n    title: '图表',\n    url: '/analytics',\n  },\n];\n\nconst todoItems = ref<WorkbenchTodoItem[]>([\n  {\n    completed: false,\n    content: `审查最近提交到Git仓库的前端代码，确保代码质量和规范。`,\n    date: '2024-07-30 11:00:00',\n    title: '审查前端代码提交',\n  },\n  {\n    completed: true,\n    content: `检查并优化系统性能，降低CPU使用率。`,\n    date: '2024-07-30 11:00:00',\n    title: '系统性能优化',\n  },\n  {\n    completed: false,\n    content: `进行系统安全检查，确保没有安全漏洞或未授权的访问。 `,\n    date: '2024-07-30 11:00:00',\n    title: '安全检查',\n  },\n  {\n    completed: false,\n    content: `更新项目中的所有npm依赖包，确保使用最新版本。`,\n    date: '2024-07-30 11:00:00',\n    title: '更新项目依赖',\n  },\n  {\n    completed: false,\n    content: `修复用户报告的页面UI显示问题，确保在不同浏览器中显示一致。 `,\n    date: '2024-07-30 11:00:00',\n    title: '修复UI显示问题',\n  },\n]);\nconst trendItems: WorkbenchTrendItem[] = [\n  {\n    avatar: 'svg:avatar-1',\n    content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,\n    date: '刚刚',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关注了 <a>威廉</a> `,\n    date: '1个小时前',\n    title: '艾文',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1天前',\n    title: '克里斯',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写一个Vite插件</a> `,\n    date: '2天前',\n    title: 'Vben',\n  },\n  {\n    avatar: 'svg:avatar-1',\n    content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化？</a>`,\n    date: '3天前',\n    title: '皮特',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关闭了问题 <a>如何运行项目</a> `,\n    date: '1周前',\n    title: '杰克',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1周前',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `推送了代码到 <a>Github</a>`,\n    date: '2021-04-01 20:00',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写使用 Admin Vben</a> `,\n    date: '2021-03-01 20:00',\n    title: 'Vben',\n  },\n];\n\nconst router = useRouter();\n\n// 这是一个示例方法，实际项目中需要根据实际情况进行调整\n// This is a sample method, adjust according to the actual project requirements\nfunction navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {\n  if (nav.url?.startsWith('http')) {\n    openWindow(nav.url);\n    return;\n  }\n  if (nav.url?.startsWith('/')) {\n    router.push(nav.url).catch((error) => {\n      console.error('Navigation failed:', error);\n    });\n  } else {\n    console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);\n  }\n}\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <WorkbenchHeader\n      :avatar=\"userStore.userInfo?.avatar || preferences.app.defaultAvatar\"\n    >\n      <template #title>\n        早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧！\n      </template>\n      <template #description> 今日晴，20℃ - 32℃！ </template>\n    </WorkbenchHeader>\n\n    <div class=\"mt-5 flex flex-col lg:flex-row\">\n      <div class=\"mr-4 w-full lg:w-3/5\">\n        <WorkbenchProject :items=\"projectItems\" title=\"项目\" @click=\"navTo\" />\n        <WorkbenchTrends :items=\"trendItems\" class=\"mt-5\" title=\"最新动态\" />\n      </div>\n      <div class=\"w-full lg:w-2/5\">\n        <WorkbenchQuickNav\n          :items=\"quickNavItems\"\n          class=\"mt-5 lg:mt-0\"\n          title=\"快捷导航\"\n          @click=\"navTo\"\n        />\n        <WorkbenchTodo :items=\"todoItems\" class=\"mt-5\" title=\"待办事项\" />\n        <AnalysisChartCard class=\"mt-5\" title=\"访问来源\">\n          <AnalyticsVisitsSource />\n        </AnalysisChartCard>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/demos/element/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport {\n  ElButton,\n  ElCard,\n  ElMessage,\n  ElNotification,\n  ElSegmented,\n  ElSpace,\n  ElTable,\n} from 'element-plus';\n\ntype NotificationType = 'error' | 'info' | 'success' | 'warning';\n\nfunction info() {\n  ElMessage.info('How many roads must a man walk down');\n}\n\nfunction error() {\n  ElMessage.error({\n    duration: 2500,\n    message: 'Once upon a time you dressed so fine',\n  });\n}\n\nfunction warning() {\n  ElMessage.warning('How many roads must a man walk down');\n}\nfunction success() {\n  ElMessage.success(\n    'Cause you walked hand in hand With another man in my place',\n  );\n}\n\nfunction notify(type: NotificationType) {\n  ElNotification({\n    duration: 2500,\n    message: '说点啥呢',\n    type,\n  });\n}\nconst tableData = [\n  { prop1: '1', prop2: 'A' },\n  { prop1: '2', prop2: 'B' },\n  { prop1: '3', prop2: 'C' },\n  { prop1: '4', prop2: 'D' },\n  { prop1: '5', prop2: 'E' },\n  { prop1: '6', prop2: 'F' },\n];\n\nconst segmentedValue = ref('Mon');\n\nconst segmentedOptions = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];\n</script>\n\n<template>\n  <Page\n    description=\"支持多语言，主题功能集成切换等\"\n    title=\"Element Plus组件使用演示\"\n  >\n    <div class=\"flex flex-wrap gap-5\">\n      <ElCard class=\"mb-5 w-auto\">\n        <template #header> 按钮 </template>\n        <ElSpace>\n          <ElButton text>Text</ElButton>\n          <ElButton>Default</ElButton>\n          <ElButton type=\"primary\"> Primary </ElButton>\n          <ElButton type=\"info\"> Info </ElButton>\n          <ElButton type=\"success\"> Success </ElButton>\n          <ElButton type=\"warning\"> Warning </ElButton>\n          <ElButton type=\"danger\"> Error </ElButton>\n        </ElSpace>\n      </ElCard>\n      <ElCard class=\"mb-5 w-80\">\n        <template #header> Message </template>\n        <ElSpace>\n          <ElButton type=\"info\" @click=\"info\"> 信息 </ElButton>\n          <ElButton type=\"danger\" @click=\"error\"> 错误 </ElButton>\n          <ElButton type=\"warning\" @click=\"warning\"> 警告 </ElButton>\n          <ElButton type=\"success\" @click=\"success\"> 成功 </ElButton>\n        </ElSpace>\n      </ElCard>\n      <ElCard class=\"mb-5 w-80\">\n        <template #header> Notification </template>\n        <ElSpace>\n          <ElButton type=\"info\" @click=\"notify('info')\"> 信息 </ElButton>\n          <ElButton type=\"danger\" @click=\"notify('error')\"> 错误 </ElButton>\n          <ElButton type=\"warning\" @click=\"notify('warning')\"> 警告 </ElButton>\n          <ElButton type=\"success\" @click=\"notify('success')\"> 成功 </ElButton>\n        </ElSpace>\n      </ElCard>\n      <ElCard class=\"mb-5 w-auto\">\n        <template #header> Segmented </template>\n        <ElSegmented\n          v-model=\"segmentedValue\"\n          :options=\"segmentedOptions\"\n          size=\"large\"\n        />\n      </ElCard>\n      <ElCard class=\"mb-5 w-80\">\n        <template #header> V-Loading </template>\n        <div class=\"flex-center size-72\" v-loading=\"true\">一些演示的内容</div>\n      </ElCard>\n      <ElCard class=\"mb-5 w-80\">\n        <ElTable :data=\"tableData\" stripe>\n          <ElTable.TableColumn label=\"测试列1\" prop=\"prop1\" />\n          <ElTable.TableColumn label=\"测试列2\" prop=\"prop2\" />\n        </ElTable>\n      </ElCard>\n    </div>\n  </Page>\n</template>\n"
  },
  {
    "path": "apps/web-ele/src/views/demos/form/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport { h } from 'vue';\n\nimport { Page, useVbenDrawer } from '@vben/common-ui';\n\nimport { ElButton, ElCard, ElCheckbox, ElMessage } from 'element-plus';\n\nimport { useVbenForm } from '#/adapter/form';\nimport { getAllMenusApi } from '#/api';\n\nconst [Form, formApi] = useVbenForm({\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  layout: 'horizontal',\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  // wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n  handleSubmit: (values) => {\n    ElMessage.success(`表单数据：${JSON.stringify(values)}`);\n  },\n  schema: [\n    {\n      component: 'IconPicker',\n      fieldName: 'icon',\n      label: 'IconPicker',\n    },\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'ApiSelect',\n      // 对应组件的参数\n      componentProps: {\n        // 菜单接口转options格式\n        afterFetch: (data: { name: string; path: string }[]) => {\n          return data.map((item: any) => ({\n            label: item.name,\n            value: item.path,\n          }));\n        },\n        // 菜单接口\n        api: getAllMenusApi,\n      },\n      // 字段名\n      fieldName: 'api',\n      // 界面显示的label\n      label: 'ApiSelect',\n    },\n    {\n      component: 'ApiTreeSelect',\n      // 对应组件的参数\n      componentProps: {\n        // 菜单接口\n        api: getAllMenusApi,\n        childrenField: 'children',\n        // 菜单接口转options格式\n        labelField: 'name',\n        valueField: 'path',\n      },\n      // 字段名\n      fieldName: 'apiTree',\n      // 界面显示的label\n      label: 'ApiTreeSelect',\n    },\n    {\n      component: 'Input',\n      fieldName: 'string',\n      label: 'String',\n    },\n    {\n      component: 'InputNumber',\n      fieldName: 'number',\n      label: 'Number',\n    },\n    {\n      component: 'RadioGroup',\n      fieldName: 'radio',\n      label: 'Radio',\n      componentProps: {\n        options: [\n          { value: 'A', label: 'A' },\n          { value: 'B', label: 'B' },\n          { value: 'C', label: 'C' },\n          { value: 'D', label: 'D' },\n          { value: 'E', label: 'E' },\n        ],\n      },\n    },\n    {\n      component: 'RadioGroup',\n      fieldName: 'radioButton',\n      label: 'RadioButton',\n      componentProps: {\n        isButton: true,\n        options: ['A', 'B', 'C', 'D', 'E', 'F'].map((v) => ({\n          value: v,\n          label: `选项${v}`,\n        })),\n      },\n    },\n    {\n      component: 'CheckboxGroup',\n      fieldName: 'checkbox',\n      label: 'Checkbox',\n      componentProps: {\n        options: ['A', 'B', 'C'].map((v) => ({ value: v, label: `选项${v}` })),\n      },\n    },\n    {\n      component: 'CheckboxGroup',\n      fieldName: 'checkbox1',\n      label: 'Checkbox1',\n      renderComponentContent: () => {\n        return {\n          default: () => {\n            return ['A', 'B', 'C', 'D'].map((v) =>\n              h(ElCheckbox, { label: v, value: v }),\n            );\n          },\n        };\n      },\n    },\n    {\n      component: 'CheckboxGroup',\n      fieldName: 'checkbotton',\n      label: 'CheckBotton',\n      componentProps: {\n        isButton: true,\n        options: [\n          { value: 'A', label: '选项A' },\n          { value: 'B', label: '选项B' },\n          { value: 'C', label: '选项C' },\n        ],\n      },\n    },\n    {\n      component: 'DatePicker',\n      fieldName: 'date',\n      label: 'Date',\n    },\n    {\n      component: 'Select',\n      fieldName: 'select',\n      label: 'Select',\n      componentProps: {\n        filterable: true,\n        options: [\n          { value: 'A', label: '选项A' },\n          { value: 'B', label: '选项B' },\n          { value: 'C', label: '选项C' },\n        ],\n      },\n    },\n  ],\n});\n\nconst [Drawer, drawerApi] = useVbenDrawer();\nfunction setFormValues() {\n  formApi.setValues({\n    string: 'string',\n    number: 123,\n    radio: 'B',\n    radioButton: 'C',\n    checkbox: ['A', 'C'],\n    checkbotton: ['B', 'C'],\n    checkbox1: ['A', 'B'],\n    date: new Date(),\n    select: 'B',\n  });\n}\n</script>\n<template>\n  <Page\n    description=\"我们重新包装了CheckboxGroup、RadioGroup、Select，可以通过options属性传入选项属性数组以自动生成选项\"\n    title=\"表单演示\"\n  >\n    <Drawer class=\"w-150\" title=\"基础表单示例\">\n      <Form />\n    </Drawer>\n    <ElCard>\n      <template #header>\n        <div class=\"flex items-center\">\n          <span class=\"flex-auto\">基础表单演示</span>\n          <ElButton type=\"primary\" @click=\"setFormValues\">设置表单值</ElButton>\n        </div>\n      </template>\n      <ElButton type=\"primary\" @click=\"drawerApi.open\"> 打开抽屉 </ElButton>\n    </ElCard>\n  </Page>\n</template>\n"
  },
  {
    "path": "apps/web-ele/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web-app.json\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"#/*\": [\"./src/*\"]\n    }\n  },\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }],\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\", \"src/**/*.vue\"]\n}\n"
  },
  {
    "path": "apps/web-ele/tsconfig.node.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"noEmit\": false\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "apps/web-ele/vite.config.ts",
    "content": "import { defineConfig } from '@vben/vite-config';\n\nimport ElementPlus from 'unplugin-element-plus/vite';\n\nexport default defineConfig(async () => {\n  return {\n    application: {},\n    vite: {\n      plugins: [\n        ElementPlus({\n          format: 'esm',\n        }),\n      ],\n      server: {\n        proxy: {\n          '/api': {\n            changeOrigin: true,\n            rewrite: (path) => path.replace(/^\\/api/, ''),\n            // mock代理目标地址\n            target: 'http://localhost:5320/api',\n            ws: true,\n          },\n        },\n      },\n    },\n  };\n});\n"
  },
  {
    "path": "apps/web-naive/index.html",
    "content": "<!doctype html>\n<html lang=\"zh\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n    <meta name=\"renderer\" content=\"webkit\" />\n    <meta name=\"description\" content=\"A Modern Back-end Management System\" />\n    <meta name=\"keywords\" content=\"Vben Admin Vue3 Vite\" />\n    <meta name=\"author\" content=\"Vben\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0\"\n    />\n    <!-- 由 vite 注入 VITE_APP_TITLE 变量，在 .env 文件内配置 -->\n    <title><%= VITE_APP_TITLE %></title>\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <script>\n      // 生产环境下注入百度统计\n      if (window._VBEN_ADMIN_PRO_APP_CONF_) {\n        var _hmt = _hmt || [];\n        (function () {\n          var hm = document.createElement('script');\n          hm.src =\n            'https://hm.baidu.com/hm.js?24bb3eb91dfe4ebfcbcee6952a107cb6';\n          var s = document.getElementsByTagName('script')[0];\n          s.parentNode.insertBefore(hm, s);\n        })();\n      }\n    </script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "apps/web-naive/package.json",
    "content": "{\n  \"name\": \"@vben/web-naive\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://vben.pro\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"apps/web-naive\"\n  },\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"vben\",\n    \"email\": \"ann.vben@gmail.com\",\n    \"url\": \"https://github.com/anncwb\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm vite build --mode production\",\n    \"build:analyze\": \"pnpm vite build --mode analyze\",\n    \"dev\": \"pnpm vite --mode development\",\n    \"preview\": \"vite preview\",\n    \"typecheck\": \"vue-tsc --noEmit --skipLibCheck\"\n  },\n  \"imports\": {\n    \"#/*\": \"./src/*\"\n  },\n  \"dependencies\": {\n    \"@vben/access\": \"workspace:*\",\n    \"@vben/common-ui\": \"workspace:*\",\n    \"@vben/constants\": \"workspace:*\",\n    \"@vben/hooks\": \"workspace:*\",\n    \"@vben/icons\": \"workspace:*\",\n    \"@vben/layouts\": \"workspace:*\",\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/plugins\": \"workspace:*\",\n    \"@vben/preferences\": \"workspace:*\",\n    \"@vben/request\": \"workspace:*\",\n    \"@vben/stores\": \"workspace:*\",\n    \"@vben/styles\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"naive-ui\": \"catalog:\",\n    \"pinia\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "apps/web-naive/src/adapter/component/index.ts",
    "content": "/**\n * 通用组件共同的使用的基础组件，原先放在 adapter/form 内部，限制了使用范围，这里提取出来，方便其他地方使用\n * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,\n */\n\nimport type { Component } from 'vue';\n\nimport type { BaseFormComponentType } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { defineAsyncComponent, defineComponent, h, ref } from 'vue';\n\nimport { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { message } from '#/adapter/naive';\n\nconst NButton = defineAsyncComponent(() =>\n  import('naive-ui/es/button').then((res) => res.NButton),\n);\nconst NCheckbox = defineAsyncComponent(() =>\n  import('naive-ui/es/checkbox').then((res) => res.NCheckbox),\n);\nconst NCheckboxGroup = defineAsyncComponent(() =>\n  import('naive-ui/es/checkbox').then((res) => res.NCheckboxGroup),\n);\nconst NDatePicker = defineAsyncComponent(() =>\n  import('naive-ui/es/date-picker').then((res) => res.NDatePicker),\n);\nconst NDivider = defineAsyncComponent(() =>\n  import('naive-ui/es/divider').then((res) => res.NDivider),\n);\nconst NInput = defineAsyncComponent(() =>\n  import('naive-ui/es/input').then((res) => res.NInput),\n);\nconst NInputNumber = defineAsyncComponent(() =>\n  import('naive-ui/es/input-number').then((res) => res.NInputNumber),\n);\nconst NRadio = defineAsyncComponent(() =>\n  import('naive-ui/es/radio').then((res) => res.NRadio),\n);\nconst NRadioButton = defineAsyncComponent(() =>\n  import('naive-ui/es/radio').then((res) => res.NRadioButton),\n);\nconst NRadioGroup = defineAsyncComponent(() =>\n  import('naive-ui/es/radio').then((res) => res.NRadioGroup),\n);\nconst NSelect = defineAsyncComponent(() =>\n  import('naive-ui/es/select').then((res) => res.NSelect),\n);\nconst NSpace = defineAsyncComponent(() =>\n  import('naive-ui/es/space').then((res) => res.NSpace),\n);\nconst NSwitch = defineAsyncComponent(() =>\n  import('naive-ui/es/switch').then((res) => res.NSwitch),\n);\nconst NTimePicker = defineAsyncComponent(() =>\n  import('naive-ui/es/time-picker').then((res) => res.NTimePicker),\n);\nconst NTreeSelect = defineAsyncComponent(() =>\n  import('naive-ui/es/tree-select').then((res) => res.NTreeSelect),\n);\nconst NUpload = defineAsyncComponent(() =>\n  import('naive-ui/es/upload').then((res) => res.NUpload),\n);\n\nconst withDefaultPlaceholder = <T extends Component>(\n  component: T,\n  type: 'input' | 'select',\n  componentProps: Recordable<any> = {},\n) => {\n  return defineComponent({\n    name: component.name,\n    inheritAttrs: false,\n    setup: (props: any, { attrs, expose, slots }) => {\n      const placeholder =\n        props?.placeholder ||\n        attrs?.placeholder ||\n        $t(`ui.placeholder.${type}`);\n      // 透传组件暴露的方法\n      const innerRef = ref();\n      expose(\n        new Proxy(\n          {},\n          {\n            get: (_target, key) => innerRef.value?.[key],\n            has: (_target, key) => key in (innerRef.value || {}),\n          },\n        ),\n      );\n      return () =>\n        h(\n          component,\n          { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },\n          slots,\n        );\n    },\n  });\n};\n\n// 这里需要自行根据业务组件库进行适配，需要用到的组件都需要在这里类型说明\nexport type ComponentType =\n  | 'ApiSelect'\n  | 'ApiTreeSelect'\n  | 'Checkbox'\n  | 'CheckboxGroup'\n  | 'DatePicker'\n  | 'Divider'\n  | 'IconPicker'\n  | 'Input'\n  | 'InputNumber'\n  | 'RadioGroup'\n  | 'Select'\n  | 'Space'\n  | 'Switch'\n  | 'TimePicker'\n  | 'TreeSelect'\n  | 'Upload'\n  | BaseFormComponentType;\n\nasync function initComponentAdapter() {\n  const components: Partial<Record<ComponentType, Component>> = {\n    // 如果你的组件体积比较大，可以使用异步加载\n    // Button: () =>\n    // import('xxx').then((res) => res.Button),\n\n    ApiSelect: withDefaultPlaceholder(\n      {\n        ...ApiComponent,\n        name: 'ApiSelect',\n      },\n      'select',\n      {\n        component: NSelect,\n        modelPropName: 'value',\n      },\n    ),\n    ApiTreeSelect: withDefaultPlaceholder(\n      {\n        ...ApiComponent,\n        name: 'ApiTreeSelect',\n      },\n      'select',\n      {\n        component: NTreeSelect,\n        nodeKey: 'value',\n        loadingSlot: 'arrow',\n        keyField: 'value',\n        modelPropName: 'value',\n        optionsPropName: 'options',\n        visibleEvent: 'onVisibleChange',\n      },\n    ),\n    Checkbox: NCheckbox,\n    CheckboxGroup: (props, { attrs, slots }) => {\n      let defaultSlot;\n      if (Reflect.has(slots, 'default')) {\n        defaultSlot = slots.default;\n      } else {\n        const { options } = attrs;\n        if (Array.isArray(options)) {\n          defaultSlot = () => options.map((option) => h(NCheckbox, option));\n        }\n      }\n      return h(\n        NCheckboxGroup,\n        { ...props, ...attrs },\n        { default: defaultSlot },\n      );\n    },\n    DatePicker: NDatePicker,\n    // 自定义默认按钮\n    DefaultButton: (props, { attrs, slots }) => {\n      return h(NButton, { ...props, attrs, type: 'default' }, slots);\n    },\n    // 自定义主要按钮\n    PrimaryButton: (props, { attrs, slots }) => {\n      return h(NButton, { ...props, attrs, type: 'primary' }, slots);\n    },\n    Divider: NDivider,\n    IconPicker: withDefaultPlaceholder(IconPicker, 'select', {\n      iconSlot: 'suffix',\n      inputComponent: NInput,\n    }),\n    Input: withDefaultPlaceholder(NInput, 'input'),\n    InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),\n    RadioGroup: (props, { attrs, slots }) => {\n      let defaultSlot;\n      if (Reflect.has(slots, 'default')) {\n        defaultSlot = slots.default;\n      } else {\n        const { options } = attrs;\n        if (Array.isArray(options)) {\n          defaultSlot = () =>\n            options.map((option) =>\n              h(attrs.isButton ? NRadioButton : NRadio, option),\n            );\n        }\n      }\n      const groupRender = h(\n        NRadioGroup,\n        { ...props, ...attrs },\n        { default: defaultSlot },\n      );\n      return attrs.isButton\n        ? h(NSpace, { vertical: true }, () => groupRender)\n        : groupRender;\n    },\n    Select: withDefaultPlaceholder(NSelect, 'select'),\n    Space: NSpace,\n    Switch: NSwitch,\n    TimePicker: NTimePicker,\n    TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),\n    Upload: NUpload,\n  };\n\n  // 将组件注册到全局共享状态中\n  globalShareState.setComponents(components);\n\n  // 定义全局共享状态中的消息提示\n  globalShareState.defineMessage({\n    // 复制成功消息提示\n    copyPreferencesSuccess: (title, content) => {\n      message.success(content || title, {\n        duration: 0,\n      });\n    },\n  });\n}\n\nexport { initComponentAdapter };\n"
  },
  {
    "path": "apps/web-naive/src/adapter/form.ts",
    "content": "import type {\n  VbenFormSchema as FormSchema,\n  VbenFormProps,\n} from '@vben/common-ui';\n\nimport type { ComponentType } from './component';\n\nimport { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nasync function initSetupVbenForm() {\n  setupVbenForm<ComponentType>({\n    config: {\n      // naive-ui组件的空值为null,不能是undefined，否则重置表单时不生效\n      emptyStateValue: null,\n      baseModelPropName: 'value',\n      modelPropNameMap: {\n        Checkbox: 'checked',\n        Radio: 'checked',\n        Upload: 'fileList',\n      },\n    },\n    defineRules: {\n      required: (value, _params, ctx) => {\n        if (value === undefined || value === null || value.length === 0) {\n          return $t('ui.formRules.required', [ctx.label]);\n        }\n        return true;\n      },\n      selectRequired: (value, _params, ctx) => {\n        if (value === undefined || value === null) {\n          return $t('ui.formRules.selectRequired', [ctx.label]);\n        }\n        return true;\n      },\n    },\n  });\n}\n\nconst useVbenForm = useForm<ComponentType>;\n\nexport { initSetupVbenForm, useVbenForm, z };\n\nexport type VbenFormSchema = FormSchema<ComponentType>;\nexport type { VbenFormProps };\n"
  },
  {
    "path": "apps/web-naive/src/adapter/naive.ts",
    "content": "import { computed } from 'vue';\n\nimport { preferences } from '@vben/preferences';\nimport '@vben/styles';\n\nimport { createDiscreteApi, darkTheme, lightTheme } from 'naive-ui';\n\nconst themeOverridesProviderProps = computed(() => ({\n  themeOverrides: preferences.theme.mode === 'light' ? lightTheme : darkTheme,\n}));\n\nconst themeProviderProps = computed(() => ({\n  theme: preferences.theme.mode === 'light' ? lightTheme : darkTheme,\n}));\n\nexport const { dialog, loadingBar, message, modal, notification } =\n  createDiscreteApi(\n    ['message', 'dialog', 'notification', 'loadingBar', 'modal'],\n    {\n      configProviderProps: themeProviderProps,\n      loadingBarProviderProps: themeOverridesProviderProps,\n      messageProviderProps: themeOverridesProviderProps,\n      notificationProviderProps: themeOverridesProviderProps,\n    },\n  );\n"
  },
  {
    "path": "apps/web-naive/src/adapter/vxe-table.ts",
    "content": "import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';\n\nimport { h } from 'vue';\n\nimport { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';\n\nimport { NButton, NImage } from 'naive-ui';\n\nimport { useVbenForm } from './form';\n\nsetupVbenVxeTable({\n  configVxeTable: (vxeUI) => {\n    vxeUI.setConfig({\n      grid: {\n        align: 'center',\n        border: false,\n        columnConfig: {\n          resizable: true,\n        },\n        minHeight: 180,\n        formConfig: {\n          // 全局禁用vxe-table的表单配置，使用formOptions\n          enabled: false,\n        },\n        proxyConfig: {\n          autoLoad: true,\n          response: {\n            result: 'items',\n            total: 'total',\n            list: 'items',\n          },\n          showActiveMsg: true,\n          showResponseMsg: false,\n        },\n        round: true,\n        showOverflow: true,\n        size: 'small',\n      } as VxeTableGridOptions,\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellImage' },\n    vxeUI.renderer.add('CellImage', {\n      renderTableDefault(renderOpts, params) {\n        const { props } = renderOpts;\n        const { column, row } = params;\n        return h(NImage, { src: row[column.field], ...props });\n      },\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellLink' },\n    vxeUI.renderer.add('CellLink', {\n      renderTableDefault(renderOpts) {\n        const { props } = renderOpts;\n        return h(\n          NButton,\n          { size: 'small', type: 'primary', quaternary: true },\n          { default: () => props?.text },\n        );\n      },\n    });\n\n    // 这里可以自行扩展 vxe-table 的全局配置，比如自定义格式化\n    // vxeUI.formats.add\n  },\n  useVbenForm,\n});\n\nexport { useVbenVxeGrid };\n\nexport type * from '@vben/plugins/vxe-table';\n"
  },
  {
    "path": "apps/web-naive/src/api/core/auth.ts",
    "content": "import { baseRequestClient, requestClient } from '#/api/request';\n\nexport namespace AuthApi {\n  /** 登录接口参数 */\n  export interface LoginParams {\n    password?: string;\n    username?: string;\n  }\n\n  /** 登录接口返回值 */\n  export interface LoginResult {\n    accessToken: string;\n  }\n\n  export interface RefreshTokenResult {\n    data: string;\n    status: number;\n  }\n}\n\n/**\n * 登录\n */\nexport async function loginApi(data: AuthApi.LoginParams) {\n  return requestClient.post<AuthApi.LoginResult>('/auth/login', data);\n}\n\n/**\n * 刷新accessToken\n */\nexport async function refreshTokenApi() {\n  return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {\n    withCredentials: true,\n  });\n}\n\n/**\n * 退出登录\n */\nexport async function logoutApi() {\n  return baseRequestClient.post('/auth/logout', {\n    withCredentials: true,\n  });\n}\n\n/**\n * 获取用户权限码\n */\nexport async function getAccessCodesApi() {\n  return requestClient.get<string[]>('/auth/codes');\n}\n"
  },
  {
    "path": "apps/web-naive/src/api/core/index.ts",
    "content": "export * from './auth';\nexport * from './menu';\nexport * from './user';\n"
  },
  {
    "path": "apps/web-naive/src/api/core/menu.ts",
    "content": "import type { RouteRecordStringComponent } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户所有菜单\n */\nexport async function getAllMenusApi() {\n  return requestClient.get<RouteRecordStringComponent[]>('/menu/all');\n}\n"
  },
  {
    "path": "apps/web-naive/src/api/core/user.ts",
    "content": "import type { UserInfo } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户信息\n */\nexport async function getUserInfoApi() {\n  return requestClient.get<UserInfo>('/user/info');\n}\n"
  },
  {
    "path": "apps/web-naive/src/api/index.ts",
    "content": "export * from './core';\n"
  },
  {
    "path": "apps/web-naive/src/api/request.ts",
    "content": "/**\n * 该文件可自行根据业务逻辑进行调整\n */\nimport type { RequestClientOptions } from '@vben/request';\n\nimport { useAppConfig } from '@vben/hooks';\nimport { preferences } from '@vben/preferences';\nimport {\n  authenticateResponseInterceptor,\n  defaultResponseInterceptor,\n  errorMessageResponseInterceptor,\n  RequestClient,\n} from '@vben/request';\nimport { useAccessStore } from '@vben/stores';\n\nimport { message } from '#/adapter/naive';\nimport { useAuthStore } from '#/store';\n\nimport { refreshTokenApi } from './core';\n\nconst { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n\nfunction createRequestClient(baseURL: string, options?: RequestClientOptions) {\n  const client = new RequestClient({\n    ...options,\n    baseURL,\n  });\n\n  /**\n   * 重新认证逻辑\n   */\n  async function doReAuthenticate() {\n    console.warn('Access token or refresh token is invalid or expired. ');\n    const accessStore = useAccessStore();\n    const authStore = useAuthStore();\n    accessStore.setAccessToken(null);\n    if (\n      preferences.app.loginExpiredMode === 'modal' &&\n      accessStore.isAccessChecked\n    ) {\n      accessStore.setLoginExpired(true);\n    } else {\n      await authStore.logout();\n    }\n  }\n\n  /**\n   * 刷新token逻辑\n   */\n  async function doRefreshToken() {\n    const accessStore = useAccessStore();\n    const resp = await refreshTokenApi();\n    const newToken = resp.data;\n    accessStore.setAccessToken(newToken);\n    return newToken;\n  }\n\n  function formatToken(token: null | string) {\n    return token ? `Bearer ${token}` : null;\n  }\n\n  // 请求头处理\n  client.addRequestInterceptor({\n    fulfilled: async (config) => {\n      const accessStore = useAccessStore();\n\n      config.headers.Authorization = formatToken(accessStore.accessToken);\n      config.headers['Accept-Language'] = preferences.app.locale;\n      return config;\n    },\n  });\n\n  // 处理返回的响应数据格式\n  client.addResponseInterceptor(\n    defaultResponseInterceptor({\n      codeField: 'code',\n      dataField: 'data',\n      successCode: 0,\n    }),\n  );\n\n  // token过期的处理\n  client.addResponseInterceptor(\n    authenticateResponseInterceptor({\n      client,\n      doReAuthenticate,\n      doRefreshToken,\n      enableRefreshToken: preferences.app.enableRefreshToken,\n      formatToken,\n    }),\n  );\n\n  // 通用的错误处理,如果没有进入上面的错误处理逻辑，就会进入这里\n  client.addResponseInterceptor(\n    errorMessageResponseInterceptor((msg: string, error) => {\n      // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理，根据不同的 code 做不同的提示，而不是直接使用 message.error 提示 msg\n      // 当前mock接口返回的错误字段是 error 或者 message\n      const responseData = error?.response?.data ?? {};\n      const errorMessage = responseData?.error ?? responseData?.message ?? '';\n      // 如果没有错误信息，则会根据状态码进行提示\n      message.error(errorMessage || msg);\n    }),\n  );\n\n  return client;\n}\n\nexport const requestClient = createRequestClient(apiURL, {\n  responseReturn: 'data',\n});\n\nexport const baseRequestClient = new RequestClient({ baseURL: apiURL });\n"
  },
  {
    "path": "apps/web-naive/src/app.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { GlobalThemeOverrides } from 'naive-ui';\n\nimport { computed } from 'vue';\n\nimport { useNaiveDesignTokens } from '@vben/hooks';\nimport { preferences } from '@vben/preferences';\n\nimport {\n  darkTheme,\n  dateEnUS,\n  dateZhCN,\n  enUS,\n  lightTheme,\n  NConfigProvider,\n  NMessageProvider,\n  NNotificationProvider,\n  zhCN,\n} from 'naive-ui';\n\ndefineOptions({ name: 'App' });\n\nconst { commonTokens } = useNaiveDesignTokens();\n\nconst tokenLocale = computed(() =>\n  preferences.app.locale === 'zh-CN' ? zhCN : enUS,\n);\nconst tokenDateLocale = computed(() =>\n  preferences.app.locale === 'zh-CN' ? dateZhCN : dateEnUS,\n);\nconst tokenTheme = computed(() =>\n  preferences.theme.mode === 'dark' ? darkTheme : lightTheme,\n);\n\nconst themeOverrides = computed((): GlobalThemeOverrides => {\n  return {\n    common: commonTokens,\n  };\n});\n</script>\n\n<template>\n  <NConfigProvider\n    :date-locale=\"tokenDateLocale\"\n    :locale=\"tokenLocale\"\n    :theme=\"tokenTheme\"\n    :theme-overrides=\"themeOverrides\"\n    class=\"h-full\"\n  >\n    <NNotificationProvider>\n      <NMessageProvider>\n        <RouterView />\n      </NMessageProvider>\n    </NNotificationProvider>\n  </NConfigProvider>\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/bootstrap.ts",
    "content": "import { createApp, watchEffect } from 'vue';\n\nimport { registerAccessDirective } from '@vben/access';\nimport { registerLoadingDirective } from '@vben/common-ui';\nimport { preferences } from '@vben/preferences';\nimport { initStores } from '@vben/stores';\nimport '@vben/styles';\nimport '@vben/styles/naive';\n\nimport { useTitle } from '@vueuse/core';\n\nimport { $t, setupI18n } from '#/locales';\n\nimport { initComponentAdapter } from './adapter/component';\nimport { initSetupVbenForm } from './adapter/form';\nimport App from './app.vue';\nimport { router } from './router';\n\nasync function bootstrap(namespace: string) {\n  // 初始化组件适配器\n  await initComponentAdapter();\n\n  // 初始化表单组件\n  await initSetupVbenForm();\n\n  // // 设置弹窗的默认配置\n  // setDefaultModalProps({\n  //   fullscreenButton: false,\n  // });\n  // // 设置抽屉的默认配置\n  // setDefaultDrawerProps({\n  //   // zIndex: 2000,\n  // });\n\n  const app = createApp(App);\n\n  // 注册v-loading指令\n  registerLoadingDirective(app, {\n    loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令\n    spinning: 'spinning',\n  });\n\n  // 国际化 i18n 配置\n  await setupI18n(app);\n\n  // 配置 pinia-tore\n  await initStores(app, { namespace });\n\n  // 安装权限指令\n  registerAccessDirective(app);\n\n  // 初始化 tippy\n  const { initTippy } = await import('@vben/common-ui/es/tippy');\n  initTippy(app);\n\n  // 配置路由及路由守卫\n  app.use(router);\n\n  // 配置Motion插件\n  const { MotionPlugin } = await import('@vben/plugins/motion');\n  app.use(MotionPlugin);\n\n  // 动态更新标题\n  watchEffect(() => {\n    if (preferences.app.dynamicTitle) {\n      const routeTitle = router.currentRoute.value.meta?.title;\n      const pageTitle =\n        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;\n      useTitle(pageTitle);\n    }\n  });\n\n  app.mount('#app');\n}\n\nexport { bootstrap };\n"
  },
  {
    "path": "apps/web-naive/src/layouts/auth.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { AuthPageLayout } from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst appName = computed(() => preferences.app.name);\nconst logo = computed(() => preferences.logo.source);\nconst logoDark = computed(() => preferences.logo.sourceDark);\n</script>\n\n<template>\n  <AuthPageLayout\n    :app-name=\"appName\"\n    :logo=\"logo\"\n    :logo-dark=\"logoDark\"\n    :page-description=\"$t('authentication.pageDesc')\"\n    :page-title=\"$t('authentication.pageTitle')\"\n  >\n    <!-- 自定义工具栏 -->\n    <!-- <template #toolbar></template> -->\n  </AuthPageLayout>\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/layouts/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { NotificationItem } from '@vben/layouts';\n\nimport { computed, ref, watch } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { AuthenticationLoginExpiredModal } from '@vben/common-ui';\nimport { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';\nimport { useWatermark } from '@vben/hooks';\nimport { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons';\nimport {\n  BasicLayout,\n  LockScreen,\n  Notification,\n  UserDropdown,\n} from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport { $t } from '#/locales';\nimport { useAuthStore } from '#/store';\nimport LoginForm from '#/views/_core/authentication/login.vue';\n\nconst notifications = ref<NotificationItem[]>([\n  {\n    id: 1,\n    avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',\n    date: '3小时前',\n    isRead: true,\n    message: '描述信息描述信息描述信息',\n    title: '收到了 14 份新周报',\n  },\n  {\n    id: 2,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '刚刚',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '朱偏右 回复了你',\n  },\n  {\n    id: 3,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '2024-01-01',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '曲丽丽 评论了你',\n  },\n  {\n    id: 4,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '代办提醒',\n  },\n  {\n    id: 5,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转Workspace示例',\n    link: '/workspace',\n  },\n  {\n    id: 6,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转外部链接示例',\n    link: 'https://doc.vben.pro',\n  },\n]);\n\nconst router = useRouter();\nconst userStore = useUserStore();\nconst authStore = useAuthStore();\nconst accessStore = useAccessStore();\nconst { destroyWatermark, updateWatermark } = useWatermark();\nconst showDot = computed(() =>\n  notifications.value.some((item) => !item.isRead),\n);\n\nconst menus = computed(() => [\n  {\n    handler: () => {\n      router.push({ name: 'Profile' });\n    },\n    icon: 'lucide:user',\n    text: $t('page.auth.profile'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_DOC_URL, {\n        target: '_blank',\n      });\n    },\n    icon: BookOpenText,\n    text: $t('ui.widgets.document'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_GITHUB_URL, {\n        target: '_blank',\n      });\n    },\n    icon: SvgGithubIcon,\n    text: 'GitHub',\n  },\n  {\n    handler: () => {\n      openWindow(`${VBEN_GITHUB_URL}/issues`, {\n        target: '_blank',\n      });\n    },\n    icon: CircleHelp,\n    text: $t('ui.widgets.qa'),\n  },\n]);\n\nconst avatar = computed(() => {\n  return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;\n});\n\nasync function handleLogout() {\n  await authStore.logout(false);\n}\n\nfunction handleNoticeClear() {\n  notifications.value = [];\n}\n\nfunction markRead(id: number | string) {\n  const item = notifications.value.find((item) => item.id === id);\n  if (item) {\n    item.isRead = true;\n  }\n}\n\nfunction remove(id: number | string) {\n  notifications.value = notifications.value.filter((item) => item.id !== id);\n}\n\nfunction handleMakeAll() {\n  notifications.value.forEach((item) => (item.isRead = true));\n}\n\nwatch(\n  () => ({\n    enable: preferences.app.watermark,\n    content: preferences.app.watermarkContent,\n  }),\n  async ({ enable, content }) => {\n    if (enable) {\n      await updateWatermark({\n        content:\n          content ||\n          `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,\n      });\n    } else {\n      destroyWatermark();\n    }\n  },\n  {\n    immediate: true,\n  },\n);\n</script>\n\n<template>\n  <BasicLayout @clear-preferences-and-logout=\"handleLogout\">\n    <template #user-dropdown>\n      <UserDropdown\n        :avatar\n        :menus\n        :text=\"userStore.userInfo?.realName\"\n        description=\"ann.vben@gmail.com\"\n        tag-text=\"Pro\"\n        @logout=\"handleLogout\"\n      />\n    </template>\n    <template #notification>\n      <Notification\n        :dot=\"showDot\"\n        :notifications=\"notifications\"\n        @clear=\"handleNoticeClear\"\n        @read=\"(item) => item.id && markRead(item.id)\"\n        @remove=\"(item) => item.id && remove(item.id)\"\n        @make-all=\"handleMakeAll\"\n      />\n    </template>\n    <template #extra>\n      <AuthenticationLoginExpiredModal\n        v-model:open=\"accessStore.loginExpired\"\n        :avatar\n      >\n        <LoginForm />\n      </AuthenticationLoginExpiredModal>\n    </template>\n    <template #lock-screen>\n      <LockScreen :avatar @to-login=\"handleLogout\" />\n    </template>\n  </BasicLayout>\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/layouts/index.ts",
    "content": "const BasicLayout = () => import('./basic.vue');\nconst AuthPageLayout = () => import('./auth.vue');\n\nconst IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);\n\nexport { AuthPageLayout, BasicLayout, IFrameView };\n"
  },
  {
    "path": "apps/web-naive/src/locales/README.md",
    "content": "# locale\n\n每个app使用的国际化可能不同，这里用于扩展国际化的功能，例如扩展 dayjs、antd组件库的多语言切换，以及app本身的国际化文件。\n"
  },
  {
    "path": "apps/web-naive/src/locales/index.ts",
    "content": "import type { App } from 'vue';\n\nimport type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';\n\nimport {\n  $t,\n  setupI18n as coreSetup,\n  loadLocalesMapFromDir,\n} from '@vben/locales';\nimport { preferences } from '@vben/preferences';\n\nconst modules = import.meta.glob('./langs/**/*.json');\n\nconst localesMap = loadLocalesMapFromDir(\n  /\\.\\/langs\\/([^/]+)\\/(.*)\\.json$/,\n  modules,\n);\n\n/**\n * 加载应用特有的语言包\n * 这里也可以改造为从服务端获取翻译数据\n * @param lang\n */\nasync function loadMessages(lang: SupportedLanguagesType) {\n  const appLocaleMessages = await localesMap[lang]?.();\n  return appLocaleMessages?.default;\n}\n\nasync function setupI18n(app: App, options: LocaleSetupOptions = {}) {\n  await coreSetup(app, {\n    defaultLocale: preferences.app.locale,\n    loadMessages,\n    missingWarn: !import.meta.env.PROD,\n    ...options,\n  });\n}\n\nexport { $t, setupI18n };\n"
  },
  {
    "path": "apps/web-naive/src/locales/langs/en-US/demos.json",
    "content": "{\n  \"title\": \"Demos\",\n  \"naive\": \"Naive UI\",\n  \"table\": \"Table\",\n  \"form\": \"Form\",\n  \"vben\": {\n    \"title\": \"Project\",\n    \"about\": \"About\",\n    \"document\": \"Document\",\n    \"antdv\": \"Ant Design Vue Version\",\n    \"antdv-next\": \"Antdv Next Version\",\n    \"naive-ui\": \"Naive UI Version\",\n    \"element-plus\": \"Element Plus Version\",\n    \"tdesign\": \"TDesign Vue Version\"\n  }\n}\n"
  },
  {
    "path": "apps/web-naive/src/locales/langs/en-US/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"Login\",\n    \"register\": \"Register\",\n    \"codeLogin\": \"Code Login\",\n    \"qrcodeLogin\": \"Qr Code Login\",\n    \"forgetPassword\": \"Forget Password\",\n    \"profile\": \"Profile\"\n  },\n  \"dashboard\": {\n    \"title\": \"Dashboard\",\n    \"analytics\": \"Analytics\",\n    \"workspace\": \"Workspace\"\n  }\n}\n"
  },
  {
    "path": "apps/web-naive/src/locales/langs/zh-CN/demos.json",
    "content": "{\n  \"title\": \"演示\",\n  \"naive\": \"Naive UI\",\n  \"table\": \"Table\",\n  \"form\": \"表单\",\n  \"vben\": {\n    \"title\": \"项目\",\n    \"about\": \"关于\",\n    \"document\": \"文档\",\n    \"antdv\": \"Ant Design Vue 版本\",\n    \"antdv-next\": \"Antdv Next 版本\",\n    \"naive-ui\": \"Naive UI 版本\",\n    \"element-plus\": \"Element Plus 版本\",\n    \"tdesign\": \"TDesign Vue 版本\"\n  }\n}\n"
  },
  {
    "path": "apps/web-naive/src/locales/langs/zh-CN/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"登录\",\n    \"register\": \"注册\",\n    \"codeLogin\": \"验证码登录\",\n    \"qrcodeLogin\": \"二维码登录\",\n    \"forgetPassword\": \"忘记密码\",\n    \"profile\": \"个人中心\"\n  },\n  \"dashboard\": {\n    \"title\": \"概览\",\n    \"analytics\": \"分析页\",\n    \"workspace\": \"工作台\"\n  }\n}\n"
  },
  {
    "path": "apps/web-naive/src/main.ts",
    "content": "import { initPreferences } from '@vben/preferences';\nimport { unmountGlobalLoading } from '@vben/utils';\n\nimport { overridesPreferences } from './preferences';\n\n/**\n * 应用初始化完成之后再进行页面加载渲染\n */\nasync function initApplication() {\n  // name用于指定项目唯一标识\n  // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据\n  const env = import.meta.env.PROD ? 'prod' : 'dev';\n  const appVersion = import.meta.env.VITE_APP_VERSION;\n  const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;\n\n  // app偏好设置初始化\n  await initPreferences({\n    namespace,\n    overrides: overridesPreferences,\n  });\n\n  // 启动应用并挂载\n  // vue应用主要逻辑及视图\n  const { bootstrap } = await import('./bootstrap');\n  await bootstrap(namespace);\n\n  // 移除并销毁loading\n  unmountGlobalLoading();\n}\n\ninitApplication();\n"
  },
  {
    "path": "apps/web-naive/src/preferences.ts",
    "content": "import { defineOverridesPreferences } from '@vben/preferences';\n\n/**\n * @description 项目配置文件\n * 只需要覆盖项目中的一部分配置，不需要的配置不用覆盖，会自动使用默认配置\n * !!! 更改配置后请清空缓存，否则可能不生效\n */\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    name: import.meta.env.VITE_APP_TITLE,\n  },\n});\n"
  },
  {
    "path": "apps/web-naive/src/router/access.ts",
    "content": "import type {\n  ComponentRecordType,\n  GenerateMenuAndRoutesOptions,\n} from '@vben/types';\n\nimport { generateAccessible } from '@vben/access';\nimport { preferences } from '@vben/preferences';\n\nimport { message } from '#/adapter/naive';\nimport { getAllMenusApi } from '#/api';\nimport { BasicLayout, IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');\n\nasync function generateAccess(options: GenerateMenuAndRoutesOptions) {\n  const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');\n\n  const layoutMap: ComponentRecordType = {\n    BasicLayout,\n    IFrameView,\n  };\n\n  return await generateAccessible(preferences.app.accessMode, {\n    ...options,\n    fetchMenuListAsync: async () => {\n      message.loading(`${$t('common.loadingMenu')}...`, {\n        duration: 1.5,\n      });\n      return await getAllMenusApi();\n    },\n    // 可以指定没有权限跳转403页面\n    forbiddenComponent,\n    // 如果 route.meta.menuVisibleWithForbidden = true\n    layoutMap,\n    pageMap,\n  });\n}\n\nexport { generateAccess };\n"
  },
  {
    "path": "apps/web-naive/src/router/guard.ts",
    "content": "import type { Router } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { startProgress, stopProgress } from '@vben/utils';\n\nimport { accessRoutes, coreRouteNames } from '#/router/routes';\nimport { useAuthStore } from '#/store';\n\nimport { generateAccess } from './access';\n\n/**\n * 通用守卫配置\n * @param router\n */\nfunction setupCommonGuard(router: Router) {\n  // 记录已经加载的页面\n  const loadedPaths = new Set<string>();\n\n  router.beforeEach((to) => {\n    to.meta.loaded = loadedPaths.has(to.path);\n\n    // 页面加载进度条\n    if (!to.meta.loaded && preferences.transition.progress) {\n      startProgress();\n    }\n    return true;\n  });\n\n  router.afterEach((to) => {\n    // 记录页面是否加载,如果已经加载，后续的页面切换动画等效果不在重复执行\n\n    loadedPaths.add(to.path);\n\n    // 关闭页面加载进度条\n    if (preferences.transition.progress) {\n      stopProgress();\n    }\n  });\n}\n\n/**\n * 权限访问守卫配置\n * @param router\n */\nfunction setupAccessGuard(router: Router) {\n  router.beforeEach(async (to, from) => {\n    const accessStore = useAccessStore();\n    const userStore = useUserStore();\n    const authStore = useAuthStore();\n\n    // 基本路由，这些路由不需要进入权限拦截\n    if (coreRouteNames.includes(to.name as string)) {\n      if (to.path === LOGIN_PATH && accessStore.accessToken) {\n        return decodeURIComponent(\n          (to.query?.redirect as string) ||\n            userStore.userInfo?.homePath ||\n            preferences.app.defaultHomePath,\n        );\n      }\n      return true;\n    }\n\n    // accessToken 检查\n    if (!accessStore.accessToken) {\n      // 明确声明忽略权限访问权限，则可以访问\n      if (to.meta.ignoreAccess) {\n        return true;\n      }\n\n      // 没有访问权限，跳转登录页面\n      if (to.fullPath !== LOGIN_PATH) {\n        return {\n          path: LOGIN_PATH,\n          // 如不需要，直接删除 query\n          query:\n            to.fullPath === preferences.app.defaultHomePath\n              ? {}\n              : { redirect: encodeURIComponent(to.fullPath) },\n          // 携带当前跳转的页面，登录后重新跳转该页面\n          replace: true,\n        };\n      }\n      return to;\n    }\n\n    // 是否已经生成过动态路由\n    if (accessStore.isAccessChecked) {\n      return true;\n    }\n    // 生成路由表\n    // 当前登录用户拥有的角色标识列表\n    const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());\n    const userRoles = userInfo.roles ?? [];\n\n    // 生成菜单和路由\n    const { accessibleMenus, accessibleRoutes } = await generateAccess({\n      roles: userRoles,\n      router,\n      // 则会在菜单中显示，但是访问会被重定向到403\n      routes: accessRoutes,\n    });\n\n    // 保存菜单信息和路由信息\n    accessStore.setAccessMenus(accessibleMenus);\n    accessStore.setAccessRoutes(accessibleRoutes);\n    accessStore.setIsAccessChecked(true);\n    const redirectPath = (from.query.redirect ??\n      (to.path === preferences.app.defaultHomePath\n        ? userInfo.homePath || preferences.app.defaultHomePath\n        : to.fullPath)) as string;\n\n    return {\n      ...router.resolve(decodeURIComponent(redirectPath)),\n      replace: true,\n    };\n  });\n}\n\n/**\n * 项目守卫配置\n * @param router\n */\nfunction createRouterGuard(router: Router) {\n  /** 通用 */\n  setupCommonGuard(router);\n  /** 权限访问 */\n  setupAccessGuard(router);\n}\n\nexport { createRouterGuard };\n"
  },
  {
    "path": "apps/web-naive/src/router/index.ts",
    "content": "import {\n  createRouter,\n  createWebHashHistory,\n  createWebHistory,\n} from 'vue-router';\n\nimport { resetStaticRoutes } from '@vben/utils';\n\nimport { createRouterGuard } from './guard';\nimport { routes } from './routes';\n\n/**\n *  @zh_CN 创建vue-router实例\n */\nconst router = createRouter({\n  history:\n    import.meta.env.VITE_ROUTER_HISTORY === 'hash'\n      ? createWebHashHistory(import.meta.env.VITE_BASE)\n      : createWebHistory(import.meta.env.VITE_BASE),\n  // 应该添加到路由的初始路由列表。\n  routes,\n  scrollBehavior: (to, _from, savedPosition) => {\n    if (savedPosition) {\n      return savedPosition;\n    }\n    return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };\n  },\n  // 是否应该禁止尾部斜杠。\n  // strict: true,\n});\n\nconst resetRoutes = () => resetStaticRoutes(router, routes);\n\n// 创建路由守卫\ncreateRouterGuard(router);\n\nexport { resetRoutes, router };\n"
  },
  {
    "path": "apps/web-naive/src/router/routes/core.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst BasicLayout = () => import('#/layouts/basic.vue');\nconst AuthPageLayout = () => import('#/layouts/auth.vue');\n/** 全局404页面 */\nconst fallbackNotFoundRoute: RouteRecordRaw = {\n  component: () => import('#/views/_core/fallback/not-found.vue'),\n  meta: {\n    hideInBreadcrumb: true,\n    hideInMenu: true,\n    hideInTab: true,\n    title: '404',\n  },\n  name: 'FallbackNotFound',\n  path: '/:path(.*)*',\n};\n\n/** 基本路由，这些路由是必须存在的 */\nconst coreRoutes: RouteRecordRaw[] = [\n  /**\n   * 根路由\n   * 使用基础布局，作为所有页面的父级容器，子级就不必配置BasicLayout。\n   * 此路由必须存在，且不应修改\n   */\n  {\n    component: BasicLayout,\n    meta: {\n      hideInBreadcrumb: true,\n      title: 'Root',\n    },\n    name: 'Root',\n    path: '/',\n    redirect: preferences.app.defaultHomePath,\n    children: [],\n  },\n  {\n    component: AuthPageLayout,\n    meta: {\n      hideInTab: true,\n      title: 'Authentication',\n    },\n    name: 'Authentication',\n    path: '/auth',\n    redirect: LOGIN_PATH,\n    children: [\n      {\n        name: 'Login',\n        path: 'login',\n        component: () => import('#/views/_core/authentication/login.vue'),\n        meta: {\n          title: $t('page.auth.login'),\n        },\n      },\n      {\n        name: 'CodeLogin',\n        path: 'code-login',\n        component: () => import('#/views/_core/authentication/code-login.vue'),\n        meta: {\n          title: $t('page.auth.codeLogin'),\n        },\n      },\n      {\n        name: 'QrCodeLogin',\n        path: 'qrcode-login',\n        component: () =>\n          import('#/views/_core/authentication/qrcode-login.vue'),\n        meta: {\n          title: $t('page.auth.qrcodeLogin'),\n        },\n      },\n      {\n        name: 'ForgetPassword',\n        path: 'forget-password',\n        component: () =>\n          import('#/views/_core/authentication/forget-password.vue'),\n        meta: {\n          title: $t('page.auth.forgetPassword'),\n        },\n      },\n      {\n        name: 'Register',\n        path: 'register',\n        component: () => import('#/views/_core/authentication/register.vue'),\n        meta: {\n          title: $t('page.auth.register'),\n        },\n      },\n    ],\n  },\n];\n\nexport { coreRoutes, fallbackNotFoundRoute };\n"
  },
  {
    "path": "apps/web-naive/src/router/routes/index.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { mergeRouteModules, traverseTreeValues } from '@vben/utils';\n\nimport { coreRoutes, fallbackNotFoundRoute } from './core';\n\nconst dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {\n  eager: true,\n});\n\n// 有需要可以自行打开注释，并创建文件夹\n// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });\n// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });\n\n/** 动态路由 */\nconst dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);\n\n/** 外部路由列表，访问这些页面可以不需要Layout，可能用于内嵌在别的系统(不会显示在菜单中) */\n// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);\n// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);\nconst staticRoutes: RouteRecordRaw[] = [];\nconst externalRoutes: RouteRecordRaw[] = [];\n\n/** 路由列表，由基本路由、外部路由和404兜底路由组成\n *  无需走权限验证（会一直显示在菜单中） */\nconst routes: RouteRecordRaw[] = [\n  ...coreRoutes,\n  ...externalRoutes,\n  fallbackNotFoundRoute,\n];\n\n/** 基本路由列表，这些路由不需要进入权限拦截 */\nconst coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);\n\n/** 有权限校验的路由列表，包含动态路由和静态路由 */\nconst accessRoutes = [...dynamicRoutes, ...staticRoutes];\nexport { accessRoutes, coreRouteNames, routes };\n"
  },
  {
    "path": "apps/web-naive/src/router/routes/modules/dashboard.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'lucide:layout-dashboard',\n      order: -1,\n      title: $t('page.dashboard.title'),\n    },\n    name: 'Dashboard',\n    path: '/dashboard',\n    children: [\n      {\n        name: 'Analytics',\n        path: '/analytics',\n        component: () => import('#/views/dashboard/analytics/index.vue'),\n        meta: {\n          affixTab: true,\n          icon: 'lucide:area-chart',\n          title: $t('page.dashboard.analytics'),\n        },\n      },\n      {\n        name: 'Workspace',\n        path: '/workspace',\n        component: () => import('#/views/dashboard/workspace/index.vue'),\n        meta: {\n          icon: 'carbon:workspace',\n          title: $t('page.dashboard.workspace'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-naive/src/router/routes/modules/demos.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'ic:baseline-view-in-ar',\n      keepAlive: true,\n      order: 1000,\n      title: $t('demos.title'),\n    },\n    name: 'Demos',\n    path: '/demos',\n    children: [\n      {\n        meta: {\n          title: $t('demos.naive'),\n        },\n        name: 'NaiveDemos',\n        path: '/demos/naive',\n        component: () => import('#/views/demos/naive/index.vue'),\n      },\n      {\n        meta: {\n          title: $t('demos.table'),\n        },\n        name: 'Table',\n        path: '/demos/table',\n        component: () => import('#/views/demos/table/index.vue'),\n      },\n      {\n        meta: {\n          title: $t('demos.form'),\n        },\n        name: 'Form',\n        path: '/demos/form',\n        component: () => import('#/views/demos/form/basic.vue'),\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-naive/src/router/routes/modules/vben.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport {\n  VBEN_ANT_PREVIEW_URL,\n  VBEN_ANTDV_NEXT_PREVIEW_URL,\n  VBEN_DOC_URL,\n  VBEN_ELE_PREVIEW_URL,\n  VBEN_GITHUB_URL,\n  VBEN_LOGO_URL,\n  VBEN_TD_PREVIEW_URL,\n} from '@vben/constants';\nimport {\n  SvgAntdvLogoIcon,\n  SvgAntdvNextLogoIcon,\n  SvgTDesignIcon,\n} from '@vben/icons';\n\nimport { IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      badgeType: 'dot',\n      icon: VBEN_LOGO_URL,\n      order: 9998,\n      title: $t('demos.vben.title'),\n    },\n    name: 'VbenProject',\n    path: '/vben-admin',\n    children: [\n      {\n        name: 'VbenDocument',\n        path: '/vben-admin/document',\n        component: IFrameView,\n        meta: {\n          icon: 'lucide:book-open-text',\n          link: VBEN_DOC_URL,\n          title: $t('demos.vben.document'),\n        },\n      },\n      {\n        name: 'VbenGithub',\n        path: '/vben-admin/github',\n        component: IFrameView,\n        meta: {\n          icon: 'mdi:github',\n          link: VBEN_GITHUB_URL,\n          title: 'Github',\n        },\n      },\n      {\n        name: 'VbenAntd',\n        path: '/vben-admin/antd',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgAntdvLogoIcon,\n          link: VBEN_ANT_PREVIEW_URL,\n          title: $t('demos.vben.antdv'),\n        },\n      },\n      {\n        name: 'VbenAntdVNext',\n        path: '/vben-admin/antdv-next',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgAntdvNextLogoIcon,\n          link: VBEN_ANTDV_NEXT_PREVIEW_URL,\n          title: $t('demos.vben.antdv-next'),\n        },\n      },\n      {\n        name: 'VbenTDesign',\n        path: '/vben-admin/tdesign',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgTDesignIcon,\n          link: VBEN_TD_PREVIEW_URL,\n          title: $t('demos.vben.tdesign'),\n        },\n      },\n      {\n        name: 'VbenElementPlus',\n        path: '/vben-admin/ele',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: 'logos:element',\n          link: VBEN_ELE_PREVIEW_URL,\n          title: $t('demos.vben.element-plus'),\n        },\n      },\n    ],\n  },\n  {\n    name: 'VbenAbout',\n    path: '/vben-admin/about',\n    component: () => import('#/views/_core/about/index.vue'),\n    meta: {\n      icon: 'lucide:copyright',\n      title: $t('demos.vben.about'),\n      order: 9999,\n    },\n  },\n  {\n    name: 'Profile',\n    path: '/profile',\n    component: () => import('#/views/_core/profile/index.vue'),\n    meta: {\n      icon: 'lucide:user',\n      hideInMenu: true,\n      title: $t('page.auth.profile'),\n    },\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-naive/src/store/auth.ts",
    "content": "import type { Recordable, UserInfo } from '@vben/types';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';\n\nimport { defineStore } from 'pinia';\n\nimport { notification } from '#/adapter/naive';\nimport { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';\nimport { $t } from '#/locales';\n\nexport const useAuthStore = defineStore('auth', () => {\n  const accessStore = useAccessStore();\n  const userStore = useUserStore();\n  const router = useRouter();\n\n  const loginLoading = ref(false);\n\n  /**\n   * 异步处理登录操作\n   * Asynchronously handle the login process\n   * @param params 登录表单数据\n   */\n  async function authLogin(\n    params: Recordable<any>,\n    onSuccess?: () => Promise<void> | void,\n  ) {\n    // 异步处理用户登录操作并获取 accessToken\n    let userInfo: null | UserInfo = null;\n    try {\n      loginLoading.value = true;\n      const { accessToken } = await loginApi(params);\n\n      // 如果成功获取到 accessToken\n      if (accessToken) {\n        // 将 accessToken 存储到 accessStore 中\n        accessStore.setAccessToken(accessToken);\n\n        // 获取用户信息并存储到 accessStore 中\n        const [fetchUserInfoResult, accessCodes] = await Promise.all([\n          fetchUserInfo(),\n          getAccessCodesApi(),\n        ]);\n\n        userInfo = fetchUserInfoResult;\n\n        userStore.setUserInfo(userInfo);\n        accessStore.setAccessCodes(accessCodes);\n\n        if (accessStore.loginExpired) {\n          accessStore.setLoginExpired(false);\n        } else {\n          onSuccess\n            ? await onSuccess?.()\n            : await router.push(\n                userInfo.homePath || preferences.app.defaultHomePath,\n              );\n        }\n\n        if (userInfo?.realName) {\n          notification.success({\n            content: $t('authentication.loginSuccess'),\n            description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,\n            duration: 3000,\n          });\n        }\n      }\n    } finally {\n      loginLoading.value = false;\n    }\n\n    return {\n      userInfo,\n    };\n  }\n\n  async function logout(redirect: boolean = true) {\n    try {\n      await logoutApi();\n    } catch {\n      // 不做任何处理\n    }\n    resetAllStores();\n    accessStore.setLoginExpired(false);\n\n    // 回登录页带上当前路由地址\n    await router.replace({\n      path: LOGIN_PATH,\n      query: redirect\n        ? {\n            redirect: encodeURIComponent(router.currentRoute.value.fullPath),\n          }\n        : {},\n    });\n  }\n\n  async function fetchUserInfo() {\n    const userInfo = await getUserInfoApi();\n    userStore.setUserInfo(userInfo);\n    return userInfo;\n  }\n\n  function $reset() {\n    loginLoading.value = false;\n  }\n\n  return {\n    $reset,\n    authLogin,\n    fetchUserInfo,\n    loginLoading,\n    logout,\n  };\n});\n"
  },
  {
    "path": "apps/web-naive/src/store/index.ts",
    "content": "export * from './auth';\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/README.md",
    "content": "# \\_core\n\n此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/about/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { About } from '@vben/common-ui';\n\ndefineOptions({ name: 'About' });\n</script>\n\n<template>\n  <About />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/authentication/code-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationCodeLogin, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'CodeLogin' });\n\nconst loading = ref(false);\nconst CODE_LENGTH = 6;\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.mobile'),\n      },\n      fieldName: 'phoneNumber',\n      label: $t('authentication.mobile'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.mobileTip') })\n        .refine((v) => /^\\d{11}$/.test(v), {\n          message: $t('authentication.mobileErrortip'),\n        }),\n    },\n    {\n      component: 'VbenPinInput',\n      componentProps: {\n        codeLength: CODE_LENGTH,\n        createText: (countdown: number) => {\n          const text =\n            countdown > 0\n              ? $t('authentication.sendText', [countdown])\n              : $t('authentication.sendCode');\n          return text;\n        },\n        placeholder: $t('authentication.code'),\n      },\n      fieldName: 'code',\n      label: $t('authentication.code'),\n      rules: z.string().length(CODE_LENGTH, {\n        message: $t('authentication.codeTip', [CODE_LENGTH]),\n      }),\n    },\n  ];\n});\n/**\n * 异步处理登录操作\n * Asynchronously handle the login process\n * @param values 登录表单数据\n */\nasync function handleLogin(values: Recordable<any>) {\n  void values;\n}\n</script>\n\n<template>\n  <AuthenticationCodeLogin\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleLogin\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/authentication/forget-password.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationForgetPassword, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'ForgetPassword' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: 'example@example.com',\n      },\n      fieldName: 'email',\n      label: $t('authentication.email'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.emailTip') })\n        .email($t('authentication.emailValidErrorTip')),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationForgetPassword\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/authentication/login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { BasicOption } from '@vben/types';\n\nimport { computed, markRaw } from 'vue';\n\nimport { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { useAuthStore } from '#/store';\n\ndefineOptions({ name: 'Login' });\n\nconst authStore = useAuthStore();\n\nconst MOCK_USER_OPTIONS: BasicOption[] = [\n  {\n    label: 'Super',\n    value: 'vben',\n  },\n  {\n    label: 'Admin',\n    value: 'admin',\n  },\n  {\n    label: 'User',\n    value: 'jack',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenSelect',\n      componentProps: {\n        options: MOCK_USER_OPTIONS,\n        placeholder: $t('authentication.selectAccount'),\n      },\n      fieldName: 'selectAccount',\n      label: $t('authentication.selectAccount'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.selectAccount') })\n        .optional()\n        .default('vben'),\n    },\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      dependencies: {\n        trigger(values, form) {\n          if (values.selectAccount) {\n            const findUser = MOCK_USER_OPTIONS.find(\n              (item) => item.value === values.selectAccount,\n            );\n            if (findUser) {\n              form.setValues({\n                password: '123456',\n                username: findUser.value,\n              });\n            }\n          }\n        },\n        triggerFields: ['selectAccount'],\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: markRaw(SliderCaptcha),\n      fieldName: 'captcha',\n      rules: z.boolean().refine((value) => value, {\n        message: $t('authentication.verifyRequiredTip'),\n      }),\n    },\n  ];\n});\n</script>\n\n<template>\n  <AuthenticationLogin\n    :form-schema=\"formSchema\"\n    :loading=\"authStore.loginLoading\"\n    @submit=\"authStore.authLogin\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/authentication/qrcode-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport { AuthenticationQrCodeLogin } from '@vben/common-ui';\nimport { LOGIN_PATH } from '@vben/constants';\n\ndefineOptions({ name: 'QrCodeLogin' });\n</script>\n\n<template>\n  <AuthenticationQrCodeLogin :login-path=\"LOGIN_PATH\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/authentication/register.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, h, ref } from 'vue';\n\nimport { AuthenticationRegister, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'Register' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      renderComponentContent() {\n        return {\n          strengthText: () => $t('authentication.passwordStrength'),\n        };\n      },\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.confirmPassword'),\n      },\n      dependencies: {\n        rules(values) {\n          const { password } = values;\n          return z\n            .string({ required_error: $t('authentication.passwordTip') })\n            .min(1, { message: $t('authentication.passwordTip') })\n            .refine((value) => value === password, {\n              message: $t('authentication.confirmPasswordTip'),\n            });\n        },\n        triggerFields: ['password'],\n      },\n      fieldName: 'confirmPassword',\n      label: $t('authentication.confirmPassword'),\n    },\n    {\n      component: 'VbenCheckbox',\n      fieldName: 'agreePolicy',\n      renderComponentContent: () => ({\n        default: () =>\n          h('span', [\n            $t('authentication.agree'),\n            h(\n              'a',\n              {\n                class: 'vben-link ml-1',\n                href: '',\n              },\n              `${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,\n            ),\n          ]),\n      }),\n      rules: z.boolean().refine((value) => !!value, {\n        message: $t('authentication.agreeTip'),\n      }),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationRegister\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/fallback/coming-soon.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback status=\"coming-soon\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/fallback/forbidden.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback403Demo' });\n</script>\n\n<template>\n  <Fallback status=\"403\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/fallback/internal-error.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback500Demo' });\n</script>\n\n<template>\n  <Fallback status=\"500\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/fallback/not-found.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback404Demo' });\n</script>\n\n<template>\n  <Fallback status=\"404\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/fallback/offline.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'FallbackOfflineDemo' });\n</script>\n\n<template>\n  <Fallback status=\"offline\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/profile/base-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { BasicOption } from '@vben/types';\n\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed, onMounted, ref } from 'vue';\n\nimport { ProfileBaseSetting } from '@vben/common-ui';\n\nimport { getUserInfoApi } from '#/api';\n\nconst profileBaseSettingRef = ref();\n\nconst MOCK_ROLES_OPTIONS: BasicOption[] = [\n  {\n    label: '管理员',\n    value: 'super',\n  },\n  {\n    label: '用户',\n    value: 'user',\n  },\n  {\n    label: '测试',\n    value: 'test',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'realName',\n      component: 'Input',\n      label: '姓名',\n    },\n    {\n      fieldName: 'username',\n      component: 'Input',\n      label: '用户名',\n    },\n    {\n      fieldName: 'roles',\n      component: 'Select',\n      componentProps: {\n        mode: 'tags',\n        options: MOCK_ROLES_OPTIONS,\n      },\n      label: '角色',\n    },\n    {\n      fieldName: 'introduction',\n      component: 'Textarea',\n      label: '个人简介',\n    },\n  ];\n});\n\nonMounted(async () => {\n  const data = await getUserInfoApi();\n  profileBaseSettingRef.value.getFormApi().setValues(data);\n});\n</script>\n<template>\n  <ProfileBaseSetting ref=\"profileBaseSettingRef\" :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/profile/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\n\nimport { Profile } from '@vben/common-ui';\nimport { useUserStore } from '@vben/stores';\n\nimport ProfileBase from './base-setting.vue';\nimport ProfileNotificationSetting from './notification-setting.vue';\nimport ProfilePasswordSetting from './password-setting.vue';\nimport ProfileSecuritySetting from './security-setting.vue';\n\nconst userStore = useUserStore();\n\nconst tabsValue = ref<string>('basic');\n\nconst tabs = ref([\n  {\n    label: '基本设置',\n    value: 'basic',\n  },\n  {\n    label: '安全设置',\n    value: 'security',\n  },\n  {\n    label: '修改密码',\n    value: 'password',\n  },\n  {\n    label: '新消息提醒',\n    value: 'notice',\n  },\n]);\n</script>\n<template>\n  <Profile\n    v-model:model-value=\"tabsValue\"\n    title=\"个人中心\"\n    :user-info=\"userStore.userInfo\"\n    :tabs=\"tabs\"\n  >\n    <template #content>\n      <ProfileBase v-if=\"tabsValue === 'basic'\" />\n      <ProfileSecuritySetting v-if=\"tabsValue === 'security'\" />\n      <ProfilePasswordSetting v-if=\"tabsValue === 'password'\" />\n      <ProfileNotificationSetting v-if=\"tabsValue === 'notice'\" />\n    </template>\n  </Profile>\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/profile/notification-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileNotificationSetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '其他用户的消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'systemMessage',\n      label: '系统消息',\n      description: '系统消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'todoTask',\n      label: '待办任务',\n      description: '待办任务将以站内信的形式通知',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileNotificationSetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/profile/password-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed } from 'vue';\n\nimport { ProfilePasswordSetting, z } from '@vben/common-ui';\n\nimport { message } from '#/adapter/naive';\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'oldPassword',\n      label: '旧密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: '请输入旧密码',\n      },\n    },\n    {\n      fieldName: 'newPassword',\n      label: '新密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请输入新密码',\n      },\n    },\n    {\n      fieldName: 'confirmPassword',\n      label: '确认密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请再次输入新密码',\n      },\n      dependencies: {\n        rules(values) {\n          const { newPassword } = values;\n          return z\n            .string({ required_error: '请再次输入新密码' })\n            .min(1, { message: '请再次输入新密码' })\n            .refine((value) => value === newPassword, {\n              message: '两次输入的密码不一致',\n            });\n        },\n        triggerFields: ['newPassword'],\n      },\n    },\n  ];\n});\n\nfunction handleSubmit() {\n  message.success('密码修改成功');\n}\n</script>\n<template>\n  <ProfilePasswordSetting\n    class=\"w-1/3\"\n    :form-schema=\"formSchema\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/_core/profile/security-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileSecuritySetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '当前密码强度：强',\n    },\n    {\n      value: true,\n      fieldName: 'securityPhone',\n      label: '密保手机',\n      description: '已绑定手机：138****8293',\n    },\n    {\n      value: true,\n      fieldName: 'securityQuestion',\n      label: '密保问题',\n      description: '未设置密保问题，密保问题可有效保护账户安全',\n    },\n    {\n      value: true,\n      fieldName: 'securityEmail',\n      label: '备用邮箱',\n      description: '已绑定邮箱：ant***sign.com',\n    },\n    {\n      value: false,\n      fieldName: 'securityMfa',\n      label: 'MFA 设备',\n      description: '未绑定 MFA 设备，绑定后，可以进行二次确认',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileSecuritySetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/dashboard/analytics/analytics-trends.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        areaStyle: {},\n        data: [\n          111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,\n          36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,\n          111,\n        ],\n        itemStyle: {\n          color: '#5ab1ef',\n        },\n        smooth: true,\n        type: 'line',\n      },\n      {\n        areaStyle: {},\n        data: [\n          33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,\n          11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,\n        ],\n        itemStyle: {\n          color: '#019680',\n        },\n        smooth: true,\n        type: 'line',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          color: '#019680',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    // xAxis: {\n    //   axisTick: {\n    //     show: false,\n    //   },\n    //   boundaryGap: false,\n    //   data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n    //   type: 'category',\n    // },\n    xAxis: {\n      axisTick: {\n        show: false,\n      },\n      boundaryGap: false,\n      data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n      splitLine: {\n        lineStyle: {\n          type: 'solid',\n          width: 1,\n        },\n        show: true,\n      },\n      type: 'category',\n    },\n    yAxis: [\n      {\n        axisTick: {\n          show: false,\n        },\n        max: 80_000,\n        splitArea: {\n          show: true,\n        },\n        splitNumber: 4,\n        type: 'value',\n      },\n    ],\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/dashboard/analytics/analytics-visits-data.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: 0,\n      data: ['访问', '趋势'],\n    },\n    radar: {\n      indicator: [\n        {\n          name: '网页',\n        },\n        {\n          name: '移动端',\n        },\n        {\n          name: 'Ipad',\n        },\n        {\n          name: '客户端',\n        },\n        {\n          name: '第三方',\n        },\n        {\n          name: '其它',\n        },\n      ],\n      radius: '60%',\n      splitNumber: 8,\n    },\n    series: [\n      {\n        areaStyle: {\n          opacity: 1,\n          shadowBlur: 0,\n          shadowColor: 'rgba(0,0,0,.2)',\n          shadowOffsetX: 0,\n          shadowOffsetY: 10,\n        },\n        data: [\n          {\n            itemStyle: {\n              color: '#b6a2de',\n            },\n            name: '访问',\n            value: [90, 50, 86, 40, 50, 20],\n          },\n          {\n            itemStyle: {\n              color: '#5ab1ef',\n            },\n            name: '趋势',\n            value: [70, 75, 70, 76, 20, 85],\n          },\n        ],\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        symbolSize: 0,\n        type: 'radar',\n      },\n    ],\n    tooltip: {},\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/dashboard/analytics/analytics-visits-sales.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 400;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        center: ['50%', '50%'],\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '外包', value: 500 },\n          { name: '定制', value: 310 },\n          { name: '技术支持', value: 274 },\n          { name: '远程', value: 400 },\n        ].toSorted((a, b) => {\n          return a.value - b.value;\n        }),\n        name: '商业占比',\n        radius: '80%',\n        roseType: 'radius',\n        type: 'pie',\n      },\n    ],\n\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/dashboard/analytics/analytics-visits-source.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: '2%',\n      left: 'center',\n    },\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 100;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        avoidLabelOverlap: false,\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '搜索引擎', value: 1048 },\n          { name: '直接访问', value: 735 },\n          { name: '邮件营销', value: 580 },\n          { name: '联盟广告', value: 484 },\n        ],\n        emphasis: {\n          label: {\n            fontSize: '12',\n            fontWeight: 'bold',\n            show: true,\n          },\n        },\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        label: {\n          position: 'center',\n          show: false,\n        },\n        labelLine: {\n          show: false,\n        },\n        name: '访问来源',\n        radius: ['40%', '65%'],\n        type: 'pie',\n      },\n    ],\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/dashboard/analytics/analytics-visits.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        barMaxWidth: 80,\n        // color: '#4f69fd',\n        data: [\n          3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,\n          3200, 4800,\n        ],\n        type: 'bar',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          // color: '#4f69fd',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    xAxis: {\n      data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),\n      type: 'category',\n    },\n    yAxis: {\n      max: 8000,\n      splitNumber: 4,\n      type: 'value',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/dashboard/analytics/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { AnalysisOverviewItem } from '@vben/common-ui';\nimport type { TabOption } from '@vben/types';\n\nimport {\n  AnalysisChartCard,\n  AnalysisChartsTabs,\n  AnalysisOverview,\n} from '@vben/common-ui';\nimport {\n  SvgBellIcon,\n  SvgCakeIcon,\n  SvgCardIcon,\n  SvgDownloadIcon,\n} from '@vben/icons';\n\nimport AnalyticsTrends from './analytics-trends.vue';\nimport AnalyticsVisitsData from './analytics-visits-data.vue';\nimport AnalyticsVisitsSales from './analytics-visits-sales.vue';\nimport AnalyticsVisitsSource from './analytics-visits-source.vue';\nimport AnalyticsVisits from './analytics-visits.vue';\n\nconst overviewItems: AnalysisOverviewItem[] = [\n  {\n    icon: SvgCardIcon,\n    title: '用户量',\n    totalTitle: '总用户量',\n    totalValue: 120_000,\n    value: 2000,\n  },\n  {\n    icon: SvgCakeIcon,\n    title: '访问量',\n    totalTitle: '总访问量',\n    totalValue: 500_000,\n    value: 20_000,\n  },\n  {\n    icon: SvgDownloadIcon,\n    title: '下载量',\n    totalTitle: '总下载量',\n    totalValue: 120_000,\n    value: 8000,\n  },\n  {\n    icon: SvgBellIcon,\n    title: '使用量',\n    totalTitle: '总使用量',\n    totalValue: 50_000,\n    value: 5000,\n  },\n];\n\nconst chartTabs: TabOption[] = [\n  {\n    label: '流量趋势',\n    value: 'trends',\n  },\n  {\n    label: '月访问量',\n    value: 'visits',\n  },\n];\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <AnalysisOverview :items=\"overviewItems\" />\n    <AnalysisChartsTabs :tabs=\"chartTabs\" class=\"mt-5\">\n      <template #trends>\n        <AnalyticsTrends />\n      </template>\n      <template #visits>\n        <AnalyticsVisits />\n      </template>\n    </AnalysisChartsTabs>\n\n    <div class=\"mt-5 w-full md:flex\">\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问数量\">\n        <AnalyticsVisitsData />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSource />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSales />\n      </AnalysisChartCard>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/dashboard/workspace/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  WorkbenchProjectItem,\n  WorkbenchQuickNavItem,\n  WorkbenchTodoItem,\n  WorkbenchTrendItem,\n} from '@vben/common-ui';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport {\n  AnalysisChartCard,\n  WorkbenchHeader,\n  WorkbenchProject,\n  WorkbenchQuickNav,\n  WorkbenchTodo,\n  WorkbenchTrends,\n} from '@vben/common-ui';\nimport { preferences } from '@vben/preferences';\nimport { useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';\n\nconst userStore = useUserStore();\n\n// 这是一个示例数据，实际项目中需要根据实际情况进行调整\n// url 也可以是内部路由，在 navTo 方法中识别处理，进行内部跳转\n// 例如：url: /dashboard/workspace\nconst projectItems: WorkbenchProjectItem[] = [\n  {\n    color: '',\n    content: '不要等待机会，而要创造机会。',\n    date: '2021-04-01',\n    group: '开源组',\n    icon: 'carbon:logo-github',\n    title: 'Github',\n    url: 'https://github.com',\n  },\n  {\n    color: '#3fb27f',\n    content: '现在的你决定将来的你。',\n    date: '2021-04-01',\n    group: '算法组',\n    icon: 'ion:logo-vue',\n    title: 'Vue',\n    url: 'https://vuejs.org',\n  },\n  {\n    color: '#e18525',\n    content: '没有什么才能比努力更重要。',\n    date: '2021-04-01',\n    group: '上班摸鱼',\n    icon: 'ion:logo-html5',\n    title: 'Html5',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',\n  },\n  {\n    color: '#bf0c2c',\n    content: '热情和欲望可以突破一切难关。',\n    date: '2021-04-01',\n    group: 'UI',\n    icon: 'ion:logo-angular',\n    title: 'Angular',\n    url: 'https://angular.io',\n  },\n  {\n    color: '#00d8ff',\n    content: '健康的身体是实现目标的基石。',\n    date: '2021-04-01',\n    group: '技术牛',\n    icon: 'bx:bxl-react',\n    title: 'React',\n    url: 'https://reactjs.org',\n  },\n  {\n    color: '#EBD94E',\n    content: '路是走出来的，而不是空想出来的。',\n    date: '2021-04-01',\n    group: '架构组',\n    icon: 'ion:logo-javascript',\n    title: 'Js',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',\n  },\n];\n\n// 同样，这里的 url 也可以使用以 http 开头的外部链接\nconst quickNavItems: WorkbenchQuickNavItem[] = [\n  {\n    color: '#1fdaca',\n    icon: 'ion:home-outline',\n    title: '首页',\n    url: '/',\n  },\n  {\n    color: '#bf0c2c',\n    icon: 'ion:grid-outline',\n    title: '仪表盘',\n    url: '/dashboard',\n  },\n  {\n    color: '#e18525',\n    icon: 'ion:layers-outline',\n    title: '组件',\n    url: '/demos/features/icons',\n  },\n  {\n    color: '#3fb27f',\n    icon: 'ion:settings-outline',\n    title: '系统管理',\n    url: '/demos/features/login-expired', // 这里的 URL 是示例，实际项目中需要根据实际情况进行调整\n  },\n  {\n    color: '#4daf1bc9',\n    icon: 'ion:key-outline',\n    title: '权限管理',\n    url: '/demos/access/page-control',\n  },\n  {\n    color: '#00d8ff',\n    icon: 'ion:bar-chart-outline',\n    title: '图表',\n    url: '/analytics',\n  },\n];\n\nconst todoItems = ref<WorkbenchTodoItem[]>([\n  {\n    completed: false,\n    content: `审查最近提交到Git仓库的前端代码，确保代码质量和规范。`,\n    date: '2024-07-30 11:00:00',\n    title: '审查前端代码提交',\n  },\n  {\n    completed: true,\n    content: `检查并优化系统性能，降低CPU使用率。`,\n    date: '2024-07-30 11:00:00',\n    title: '系统性能优化',\n  },\n  {\n    completed: false,\n    content: `进行系统安全检查，确保没有安全漏洞或未授权的访问。 `,\n    date: '2024-07-30 11:00:00',\n    title: '安全检查',\n  },\n  {\n    completed: false,\n    content: `更新项目中的所有npm依赖包，确保使用最新版本。`,\n    date: '2024-07-30 11:00:00',\n    title: '更新项目依赖',\n  },\n  {\n    completed: false,\n    content: `修复用户报告的页面UI显示问题，确保在不同浏览器中显示一致。 `,\n    date: '2024-07-30 11:00:00',\n    title: '修复UI显示问题',\n  },\n]);\nconst trendItems: WorkbenchTrendItem[] = [\n  {\n    avatar: 'svg:avatar-1',\n    content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,\n    date: '刚刚',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关注了 <a>威廉</a> `,\n    date: '1个小时前',\n    title: '艾文',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1天前',\n    title: '克里斯',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写一个Vite插件</a> `,\n    date: '2天前',\n    title: 'Vben',\n  },\n  {\n    avatar: 'svg:avatar-1',\n    content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化？</a>`,\n    date: '3天前',\n    title: '皮特',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关闭了问题 <a>如何运行项目</a> `,\n    date: '1周前',\n    title: '杰克',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1周前',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `推送了代码到 <a>Github</a>`,\n    date: '2021-04-01 20:00',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写使用 Admin Vben</a> `,\n    date: '2021-03-01 20:00',\n    title: 'Vben',\n  },\n];\n\nconst router = useRouter();\n\n// 这是一个示例方法，实际项目中需要根据实际情况进行调整\n// This is a sample method, adjust according to the actual project requirements\nfunction navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {\n  if (nav.url?.startsWith('http')) {\n    openWindow(nav.url);\n    return;\n  }\n  if (nav.url?.startsWith('/')) {\n    router.push(nav.url).catch((error) => {\n      console.error('Navigation failed:', error);\n    });\n  } else {\n    console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);\n  }\n}\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <WorkbenchHeader\n      :avatar=\"userStore.userInfo?.avatar || preferences.app.defaultAvatar\"\n    >\n      <template #title>\n        早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧！\n      </template>\n      <template #description> 今日晴，20℃ - 32℃！ </template>\n    </WorkbenchHeader>\n\n    <div class=\"mt-5 flex flex-col lg:flex-row\">\n      <div class=\"mr-4 w-full lg:w-3/5\">\n        <WorkbenchProject :items=\"projectItems\" title=\"项目\" @click=\"navTo\" />\n        <WorkbenchTrends :items=\"trendItems\" class=\"mt-5\" title=\"最新动态\" />\n      </div>\n      <div class=\"w-full lg:w-2/5\">\n        <WorkbenchQuickNav\n          :items=\"quickNavItems\"\n          class=\"mt-5 lg:mt-0\"\n          title=\"快捷导航\"\n          @click=\"navTo\"\n        />\n        <WorkbenchTodo :items=\"todoItems\" class=\"mt-5\" title=\"待办事项\" />\n        <AnalysisChartCard class=\"mt-5\" title=\"访问来源\">\n          <AnalyticsVisitsSource />\n        </AnalysisChartCard>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/demos/form/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Page, useVbenModal } from '@vben/common-ui';\n\nimport { NButton, NCard, useMessage } from 'naive-ui';\n\nimport { useVbenForm } from '#/adapter/form';\nimport { getAllMenusApi } from '#/api';\n\nimport modalDemo from './modal.vue';\n\nconst message = useMessage();\nconst [Form, formApi] = useVbenForm({\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  layout: 'horizontal',\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n  handleSubmit: (values) => {\n    message.success(`表单数据：${JSON.stringify(values)}`);\n  },\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'ApiSelect',\n      // 对应组件的参数\n      componentProps: {\n        // 菜单接口转options格式\n        afterFetch: (data: { name: string; path: string }[]) => {\n          return data.map((item: any) => ({\n            label: item.name,\n            value: item.path,\n          }));\n        },\n        // 菜单接口\n        api: getAllMenusApi,\n      },\n      // 字段名\n      fieldName: 'api',\n      // 界面显示的label\n      label: 'ApiSelect',\n      rules: 'required',\n    },\n    {\n      component: 'ApiTreeSelect',\n      // 对应组件的参数\n      componentProps: {\n        // 菜单接口\n        api: getAllMenusApi,\n        childrenField: 'children',\n        // 菜单接口转options格式\n        labelField: 'name',\n        valueField: 'path',\n      },\n      // 字段名\n      fieldName: 'apiTree',\n      // 界面显示的label\n      label: 'ApiTreeSelect',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      fieldName: 'string',\n      label: 'String',\n      rules: 'required',\n    },\n    {\n      component: 'InputNumber',\n      fieldName: 'number',\n      label: 'Number',\n      rules: 'required',\n    },\n    {\n      component: 'RadioGroup',\n      fieldName: 'radio',\n      label: 'Radio',\n      componentProps: {\n        options: [\n          { value: 'A', label: 'A' },\n          { value: 'B', label: 'B' },\n          { value: 'C', label: 'C' },\n          { value: 'D', label: 'D' },\n          { value: 'E', label: 'E' },\n        ],\n      },\n      rules: 'selectRequired',\n    },\n    {\n      component: 'RadioGroup',\n      fieldName: 'radioButton',\n      label: 'RadioButton',\n      componentProps: {\n        isButton: true,\n        class: 'flex flex-wrap', // 如果选项过多，可以添加class来自动折叠\n        options: [\n          { value: 'A', label: '选项A' },\n          { value: 'B', label: '选项B' },\n          { value: 'C', label: '选项C' },\n          { value: 'D', label: '选项D' },\n          { value: 'E', label: '选项E' },\n        ],\n      },\n      rules: 'selectRequired',\n    },\n    {\n      component: 'CheckboxGroup',\n      fieldName: 'checkbox',\n      label: 'Checkbox',\n      componentProps: {\n        options: [\n          { value: 'A', label: '选项A' },\n          { value: 'B', label: '选项B' },\n          { value: 'C', label: '选项C' },\n        ],\n      },\n      rules: 'selectRequired',\n    },\n    {\n      component: 'DatePicker',\n      fieldName: 'date',\n      label: 'Date',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      fieldName: 'textArea',\n      label: 'TextArea',\n      componentProps: {\n        type: 'textarea',\n      },\n      rules: 'required',\n    },\n  ],\n});\nfunction setFormValues() {\n  formApi.setValues({\n    string: 'string',\n    number: 123,\n    radio: 'B',\n    radioButton: 'C',\n    checkbox: ['A', 'C'],\n    date: Date.now(),\n  });\n}\n\nconst [Modal, modalApi] = useVbenModal({\n  connectedComponent: modalDemo,\n});\n</script>\n<template>\n  <Page\n    description=\"表单适配器重新包装了CheckboxGroup和RadioGroup，可以通过options属性传递选项数据（选项数据将作为子组件的属性）\"\n    title=\"表单演示\"\n  >\n    <NCard title=\"基础表单\">\n      <template #header-extra>\n        <NButton type=\"primary\" @click=\"setFormValues\">设置表单值</NButton>\n        <NButton type=\"primary\" @click=\"modalApi.open()\" class=\"ml-2\">\n          打开弹窗\n        </NButton>\n      </template>\n      <Form />\n    </NCard>\n    <Modal />\n  </Page>\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/demos/form/modal.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { useVbenForm } from '#/adapter/form';\n\ndefineOptions({\n  name: 'FormModelDemo',\n});\n\nconst [Form, formApi] = useVbenForm({\n  schema: [\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field1',\n      label: '字段1',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field2',\n      label: '字段2',\n      rules: 'required',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        options: [\n          { label: '选项1', value: '1' },\n          { label: '选项2', value: '2' },\n        ],\n        placeholder: '请输入',\n      },\n      fieldName: 'field3',\n      label: '字段3',\n      rules: 'required',\n    },\n  ],\n  showDefaultActions: false,\n});\n\nconst [Modal, modalApi] = useVbenModal({\n  fullscreenButton: false,\n  onCancel() {\n    modalApi.close();\n  },\n  onConfirm: async () => {\n    await formApi.validateAndSubmitForm();\n    // modalApi.close();\n  },\n  onOpenChange(isOpen: boolean) {\n    if (isOpen) {\n      const { values } = modalApi.getData<Record<string, any>>();\n      if (values) {\n        formApi.setValues(values);\n      }\n    }\n  },\n  title: '内嵌表单示例',\n});\n</script>\n<template>\n  <Modal>\n    <Form />\n  </Modal>\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/demos/naive/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { NotificationType } from 'naive-ui';\n\nimport { Page } from '@vben/common-ui';\n\nimport { NButton, NCard, NSpace, useMessage, useNotification } from 'naive-ui';\n\nconst notification = useNotification();\n\nconst message = useMessage();\nfunction error() {\n  message.error('Once upon a time you dressed so fine');\n}\n\nfunction warning() {\n  message.warning('How many roads must a man walk down');\n}\nfunction success() {\n  message.success('Cause you walked hand in hand With another man in my place');\n}\nfunction loading() {\n  message.loading(\n    'If I were you, I will realize that I love you more than any other guy',\n  );\n}\n\nfunction notify(type: NotificationType) {\n  notification[type]({\n    content: '说点啥呢',\n    duration: 2500,\n    keepAliveOnHover: true,\n    meta: '想不出来',\n  });\n}\n</script>\n\n<template>\n  <Page description=\"支持多语言，主题功能集成切换等\" title=\"naive组件使用演示\">\n    <NCard class=\"mb-5\" title=\"按钮\">\n      <NSpace>\n        <NButton>Default</NButton>\n        <NButton type=\"tertiary\"> Tertiary </NButton>\n        <NButton type=\"primary\"> Primary </NButton>\n        <NButton type=\"info\"> Info </NButton>\n        <NButton type=\"success\"> Success </NButton>\n        <NButton type=\"warning\"> Warning </NButton>\n        <NButton type=\"error\"> Error </NButton>\n      </NSpace>\n    </NCard>\n\n    <NCard class=\"mb-5\" title=\"Message\">\n      <NSpace>\n        <NButton type=\"error\" @click=\"error\"> 错误 </NButton>\n        <NButton type=\"warning\" @click=\"warning\"> 警告 </NButton>\n        <NButton type=\"success\" @click=\"success\"> 成功 </NButton>\n        <NButton type=\"primary\" @click=\"loading\"> 加载中 </NButton>\n      </NSpace>\n    </NCard>\n\n    <NCard class=\"mb-5\" title=\"Notification\">\n      <NSpace>\n        <NButton type=\"error\" @click=\"notify('error')\"> 错误 </NButton>\n        <NButton type=\"warning\" @click=\"notify('warning')\"> 警告 </NButton>\n        <NButton type=\"success\" @click=\"notify('success')\"> 成功 </NButton>\n        <NButton type=\"primary\" @click=\"notify('info')\"> 加载中 </NButton>\n      </NSpace>\n    </NCard>\n  </Page>\n</template>\n"
  },
  {
    "path": "apps/web-naive/src/views/demos/table/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { NDataTable } from 'naive-ui';\n\nconst columns = ref([\n  {\n    key: 'no',\n    title: 'No',\n  },\n  {\n    key: 'title',\n    title: 'Title',\n  },\n  {\n    key: 'length',\n    title: 'Length',\n  },\n]);\nconst data = [\n  { length: '4:18', no: 3, title: 'Wonderwall' },\n  { length: '4:48', no: 4, title: \"Don't Look Back in Anger\" },\n  { length: '7:27', no: 12, title: 'Champagne Supernova' },\n];\n</script>\n\n<template>\n  <Page\n    description=\"表单页用于向用户收集或验证信息，基础表单常见于数据项较少的表单场景。\"\n    title=\"NDataTable\"\n  >\n    <NDataTable :columns=\"columns\" :data=\"data\" />\n  </Page>\n</template>\n\n<style scoped></style>\n"
  },
  {
    "path": "apps/web-naive/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web-app.json\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"#/*\": [\"./src/*\"]\n    }\n  },\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }],\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\", \"src/**/*.vue\"]\n}\n"
  },
  {
    "path": "apps/web-naive/tsconfig.node.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"noEmit\": false\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "apps/web-naive/vite.config.ts",
    "content": "import { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    application: {},\n    vite: {\n      server: {\n        proxy: {\n          '/api': {\n            changeOrigin: true,\n            rewrite: (path) => path.replace(/^\\/api/, ''),\n            // mock代理目标地址\n            target: 'http://localhost:5320/api',\n            ws: true,\n          },\n        },\n      },\n    },\n  };\n});\n"
  },
  {
    "path": "apps/web-tdesign/index.html",
    "content": "<!doctype html>\n<html lang=\"zh\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n    <meta name=\"renderer\" content=\"webkit\" />\n    <meta name=\"description\" content=\"A Modern Back-end Management System\" />\n    <meta name=\"keywords\" content=\"Vben Admin Vue3 Vite\" />\n    <meta name=\"author\" content=\"Vben\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0\"\n    />\n    <!-- 由 vite 注入 VITE_APP_TITLE 变量，在 .env 文件内配置 -->\n    <title><%= VITE_APP_TITLE %></title>\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <script>\n      // 生产环境下注入百度统计\n      if (window._VBEN_ADMIN_PRO_APP_CONF_) {\n        var _hmt = _hmt || [];\n        (function () {\n          var hm = document.createElement('script');\n          hm.src =\n            'https://hm.baidu.com/hm.js?b38e689f40558f20a9a686d7f6f33edf';\n          var s = document.getElementsByTagName('script')[0];\n          s.parentNode.insertBefore(hm, s);\n        })();\n      }\n    </script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "apps/web-tdesign/package.json",
    "content": "{\n  \"name\": \"@vben/web-tdesign\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://vben.pro\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"apps/web-tdesign\"\n  },\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"vben\",\n    \"email\": \"ann.vben@gmail.com\",\n    \"url\": \"https://github.com/anncwb\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm vite build --mode production\",\n    \"build:analyze\": \"pnpm vite build --mode analyze\",\n    \"dev\": \"pnpm vite --mode development\",\n    \"preview\": \"vite preview\",\n    \"typecheck\": \"vue-tsc --noEmit --skipLibCheck\"\n  },\n  \"imports\": {\n    \"#/*\": \"./src/*\"\n  },\n  \"dependencies\": {\n    \"@vben/access\": \"workspace:*\",\n    \"@vben/common-ui\": \"workspace:*\",\n    \"@vben/constants\": \"workspace:*\",\n    \"@vben/hooks\": \"workspace:*\",\n    \"@vben/icons\": \"workspace:*\",\n    \"@vben/layouts\": \"workspace:*\",\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/plugins\": \"workspace:*\",\n    \"@vben/preferences\": \"workspace:*\",\n    \"@vben/request\": \"workspace:*\",\n    \"@vben/stores\": \"workspace:*\",\n    \"@vben/styles\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"dayjs\": \"catalog:\",\n    \"es-toolkit\": \"catalog:\",\n    \"pinia\": \"catalog:\",\n    \"tdesign-vue-next\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "apps/web-tdesign/src/adapter/component/index.ts",
    "content": "import type { Component } from 'vue';\n\nimport type { BaseFormComponentType } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { defineAsyncComponent, defineComponent, h, ref } from 'vue';\n\nimport { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { notification } from '#/adapter/tdesign';\n\n/**\n * 通用组件共同的使用的基础组件，原先放在 adapter/form 内部，限制了使用范围，这里提取出来，方便其他地方使用\n * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,\n */\n\nconst AutoComplete = defineAsyncComponent(\n  () => import('tdesign-vue-next/es/auto-complete'),\n);\nconst Button = defineAsyncComponent(() => import('tdesign-vue-next/es/button'));\nconst Checkbox = defineAsyncComponent(\n  () => import('tdesign-vue-next/es/checkbox'),\n);\nconst CheckboxGroup = defineAsyncComponent(() =>\n  import('tdesign-vue-next/es/checkbox').then((res) => res.CheckboxGroup),\n);\nconst DatePicker = defineAsyncComponent(\n  () => import('tdesign-vue-next/es/date-picker'),\n);\nconst Divider = defineAsyncComponent(\n  () => import('tdesign-vue-next/es/divider'),\n);\nconst Input = defineAsyncComponent(() => import('tdesign-vue-next/es/input'));\nconst InputNumber = defineAsyncComponent(\n  () => import('tdesign-vue-next/es/input-number'),\n);\n// const InputPassword = defineAsyncComponent(() =>\n//   import('tdesign-vue-next/es/input').then((res) => res.InputPassword),\n// );\n// const Mentions = defineAsyncComponent(\n//   () => import('tdesign-vue-next/es/mentions'),\n// );\nconst Radio = defineAsyncComponent(() => import('tdesign-vue-next/es/radio'));\nconst RadioGroup = defineAsyncComponent(() =>\n  import('tdesign-vue-next/es/radio').then((res) => res.RadioGroup),\n);\nconst RangePicker = defineAsyncComponent(() =>\n  import('tdesign-vue-next/es/date-picker').then((res) => res.DateRangePicker),\n);\nconst Rate = defineAsyncComponent(() => import('tdesign-vue-next/es/rate'));\nconst Select = defineAsyncComponent(() => import('tdesign-vue-next/es/select'));\nconst Space = defineAsyncComponent(() => import('tdesign-vue-next/es/space'));\nconst Switch = defineAsyncComponent(() => import('tdesign-vue-next/es/switch'));\nconst Textarea = defineAsyncComponent(\n  () => import('tdesign-vue-next/es/textarea'),\n);\nconst TimePicker = defineAsyncComponent(\n  () => import('tdesign-vue-next/es/time-picker'),\n);\nconst TreeSelect = defineAsyncComponent(\n  () => import('tdesign-vue-next/es/tree-select'),\n);\nconst Upload = defineAsyncComponent(() => import('tdesign-vue-next/es/upload'));\n\nconst withDefaultPlaceholder = <T extends Component>(\n  component: T,\n  type: 'input' | 'select',\n  componentProps: Recordable<any> = {},\n) => {\n  return defineComponent({\n    name: component.name,\n    inheritAttrs: false,\n    setup: (props: any, { attrs, expose, slots }) => {\n      const placeholder =\n        props?.placeholder ||\n        attrs?.placeholder ||\n        $t(`ui.placeholder.${type}`);\n      // 透传组件暴露的方法\n      const innerRef = ref();\n      expose(\n        new Proxy(\n          {},\n          {\n            get: (_target, key) => innerRef.value?.[key],\n            has: (_target, key) => key in (innerRef.value || {}),\n          },\n        ),\n      );\n      return () =>\n        h(\n          component,\n          { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },\n          slots,\n        );\n    },\n  });\n};\n\n// 这里需要自行根据业务组件库进行适配，需要用到的组件都需要在这里类型说明\nexport type ComponentType =\n  | 'ApiSelect'\n  | 'ApiTreeSelect'\n  | 'AutoComplete'\n  | 'Checkbox'\n  | 'CheckboxGroup'\n  | 'DatePicker'\n  | 'DefaultButton'\n  | 'Divider'\n  | 'IconPicker'\n  | 'Input'\n  | 'InputNumber'\n  // | 'InputPassword'\n  // | 'Mentions'\n  | 'PrimaryButton'\n  | 'Radio'\n  | 'RadioGroup'\n  | 'RangePicker'\n  | 'Rate'\n  | 'Select'\n  | 'Space'\n  | 'Switch'\n  | 'Textarea'\n  | 'TimePicker'\n  | 'TreeSelect'\n  | 'Upload'\n  | BaseFormComponentType;\n\nasync function initComponentAdapter() {\n  const components: Partial<Record<ComponentType, Component>> = {\n    // 如果你的组件体积比较大，可以使用异步加载\n    // Button: () =>\n    // import('xxx').then((res) => res.Button),\n    ApiSelect: withDefaultPlaceholder(\n      {\n        ...ApiComponent,\n        name: 'ApiSelect',\n      },\n      'select',\n      {\n        component: Select,\n        loadingSlot: 'suffixIcon',\n        visibleEvent: 'onDropdownVisibleChange',\n        modelPropName: 'value',\n      },\n    ),\n    ApiTreeSelect: withDefaultPlaceholder(\n      {\n        ...ApiComponent,\n        name: 'ApiTreeSelect',\n      },\n      'select',\n      {\n        component: TreeSelect,\n        fieldNames: { label: 'label', value: 'value', children: 'children' },\n        loadingSlot: 'suffixIcon',\n        modelPropName: 'value',\n        optionsPropName: 'treeData',\n        visibleEvent: 'onVisibleChange',\n      },\n    ),\n    AutoComplete,\n    Checkbox,\n    CheckboxGroup,\n    DatePicker,\n    // 自定义默认按钮\n    DefaultButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, theme: 'default' }, slots);\n    },\n    Divider,\n    IconPicker: withDefaultPlaceholder(IconPicker, 'select', {\n      iconSlot: 'addonAfter',\n      inputComponent: Input,\n      modelValueProp: 'value',\n    }),\n    Input: withDefaultPlaceholder(Input, 'input'),\n    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),\n    // InputPassword: withDefaultPlaceholder(InputPassword, 'input'),\n    // Mentions: withDefaultPlaceholder(Mentions, 'input'),\n    // 自定义主要按钮\n    PrimaryButton: (props, { attrs, slots }) => {\n      let ghost = false;\n      let variant = props.variant;\n      if (props.variant === 'ghost') {\n        ghost = true;\n        variant = 'base';\n      }\n      return h(\n        Button,\n        { ...props, ghost, variant, attrs, theme: 'primary' },\n        slots,\n      );\n    },\n    Radio,\n    RadioGroup,\n    RangePicker: (props, { attrs, slots }) => {\n      return h(\n        RangePicker,\n        { ...props, modelValue: props.modelValue ?? [], attrs },\n        slots,\n      );\n    },\n    Rate,\n    Select: withDefaultPlaceholder(Select, 'select'),\n    Space,\n    Switch,\n    Textarea: withDefaultPlaceholder(Textarea, 'input'),\n    TimePicker,\n    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),\n    Upload,\n  };\n\n  // 将组件注册到全局共享状态中\n  globalShareState.setComponents(components);\n\n  // 定义全局共享状态中的消息提示\n  globalShareState.defineMessage({\n    // 复制成功消息提示\n    copyPreferencesSuccess: (title, content) => {\n      notification.success({\n        title,\n        content,\n        placement: 'bottom-right',\n      });\n    },\n  });\n}\n\nexport { initComponentAdapter };\n"
  },
  {
    "path": "apps/web-tdesign/src/adapter/form.ts",
    "content": "import type {\n  VbenFormSchema as FormSchema,\n  VbenFormProps,\n} from '@vben/common-ui';\n\nimport type { ComponentType } from './component';\n\nimport { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nasync function initSetupVbenForm() {\n  setupVbenForm<ComponentType>({\n    config: {\n      // tdesign组件库默认都是 v-model:value\n      baseModelPropName: 'value',\n\n      // 一些组件是 v-model:checked 或者 v-model:fileList\n      modelPropNameMap: {\n        Checkbox: 'checked',\n        Radio: 'checked',\n        Switch: 'checked',\n        Upload: 'fileList',\n      },\n    },\n    defineRules: {\n      // 输入项目必填国际化适配\n      required: (value, _params, ctx) => {\n        if (value === undefined || value === null || value.length === 0) {\n          return $t('ui.formRules.required', [ctx.label]);\n        }\n        return true;\n      },\n      // 选择项目必填国际化适配\n      selectRequired: (value, _params, ctx) => {\n        if (value === undefined || value === null) {\n          return $t('ui.formRules.selectRequired', [ctx.label]);\n        }\n        return true;\n      },\n    },\n  });\n}\n\nconst useVbenForm = useForm<ComponentType>;\n\nexport { initSetupVbenForm, useVbenForm, z };\n\nexport type VbenFormSchema = FormSchema<ComponentType>;\nexport type { VbenFormProps };\n"
  },
  {
    "path": "apps/web-tdesign/src/adapter/tdesign.ts",
    "content": "export {\n  DialogPlugin as dialog,\n  MessagePlugin as message,\n  NotifyPlugin as notification,\n} from 'tdesign-vue-next';\n"
  },
  {
    "path": "apps/web-tdesign/src/adapter/vxe-table.ts",
    "content": "import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';\n\nimport { h } from 'vue';\n\nimport { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';\n\nimport { Button, Image } from 'tdesign-vue-next';\n\nimport { useVbenForm } from './form';\n\nsetupVbenVxeTable({\n  configVxeTable: (vxeUI) => {\n    vxeUI.setConfig({\n      grid: {\n        align: 'center',\n        border: false,\n        columnConfig: {\n          resizable: true,\n        },\n        minHeight: 180,\n        formConfig: {\n          // 全局禁用vxe-table的表单配置，使用formOptions\n          enabled: false,\n        },\n        proxyConfig: {\n          autoLoad: true,\n          response: {\n            result: 'items',\n            total: 'total',\n            list: 'items',\n          },\n          showActiveMsg: true,\n          showResponseMsg: false,\n        },\n        round: true,\n        showOverflow: true,\n        size: 'small',\n      } as VxeTableGridOptions,\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellImage' },\n    vxeUI.renderer.add('CellImage', {\n      renderTableDefault(renderOpts, params) {\n        const { props } = renderOpts;\n        const { column, row } = params;\n        return h(Image, { src: row[column.field], ...props });\n      },\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellLink' },\n    vxeUI.renderer.add('CellLink', {\n      renderTableDefault(renderOpts) {\n        const { props } = renderOpts;\n        return h(\n          Button,\n          { size: 'small', theme: 'primary', variant: 'text' },\n          { default: () => props?.text },\n        );\n      },\n    });\n\n    // 这里可以自行扩展 vxe-table 的全局配置，比如自定义格式化\n    // vxeUI.formats.add\n  },\n  useVbenForm,\n});\n\nexport { useVbenVxeGrid };\n\nexport type * from '@vben/plugins/vxe-table';\n"
  },
  {
    "path": "apps/web-tdesign/src/api/core/auth.ts",
    "content": "import { baseRequestClient, requestClient } from '#/api/request';\n\nexport namespace AuthApi {\n  /** 登录接口参数 */\n  export interface LoginParams {\n    password?: string;\n    username?: string;\n  }\n\n  /** 登录接口返回值 */\n  export interface LoginResult {\n    accessToken: string;\n  }\n\n  export interface RefreshTokenResult {\n    data: string;\n    status: number;\n  }\n}\n\n/**\n * 登录\n */\nexport async function loginApi(data: AuthApi.LoginParams) {\n  return requestClient.post<AuthApi.LoginResult>('/auth/login', data);\n}\n\n/**\n * 刷新accessToken\n */\nexport async function refreshTokenApi() {\n  return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {\n    withCredentials: true,\n  });\n}\n\n/**\n * 退出登录\n */\nexport async function logoutApi() {\n  return baseRequestClient.post('/auth/logout', {\n    withCredentials: true,\n  });\n}\n\n/**\n * 获取用户权限码\n */\nexport async function getAccessCodesApi() {\n  return requestClient.get<string[]>('/auth/codes');\n}\n"
  },
  {
    "path": "apps/web-tdesign/src/api/core/index.ts",
    "content": "export * from './auth';\nexport * from './menu';\nexport * from './user';\n"
  },
  {
    "path": "apps/web-tdesign/src/api/core/menu.ts",
    "content": "import type { RouteRecordStringComponent } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户所有菜单\n */\nexport async function getAllMenusApi() {\n  return requestClient.get<RouteRecordStringComponent[]>('/menu/all');\n}\n"
  },
  {
    "path": "apps/web-tdesign/src/api/core/user.ts",
    "content": "import type { UserInfo } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户信息\n */\nexport async function getUserInfoApi() {\n  return requestClient.get<UserInfo>('/user/info');\n}\n"
  },
  {
    "path": "apps/web-tdesign/src/api/index.ts",
    "content": "export * from './core';\n"
  },
  {
    "path": "apps/web-tdesign/src/api/request.ts",
    "content": "/**\n * 该文件可自行根据业务逻辑进行调整\n */\nimport type { RequestClientOptions } from '@vben/request';\n\nimport { useAppConfig } from '@vben/hooks';\nimport { preferences } from '@vben/preferences';\nimport {\n  authenticateResponseInterceptor,\n  defaultResponseInterceptor,\n  errorMessageResponseInterceptor,\n  RequestClient,\n} from '@vben/request';\nimport { useAccessStore } from '@vben/stores';\n\nimport { message } from '#/adapter/tdesign';\nimport { useAuthStore } from '#/store';\n\nimport { refreshTokenApi } from './core';\n\nconst { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n\nfunction createRequestClient(baseURL: string, options?: RequestClientOptions) {\n  const client = new RequestClient({\n    ...options,\n    baseURL,\n  });\n\n  /**\n   * 重新认证逻辑\n   */\n  async function doReAuthenticate() {\n    console.warn('Access token or refresh token is invalid or expired. ');\n    const accessStore = useAccessStore();\n    const authStore = useAuthStore();\n    accessStore.setAccessToken(null);\n    if (\n      preferences.app.loginExpiredMode === 'modal' &&\n      accessStore.isAccessChecked\n    ) {\n      accessStore.setLoginExpired(true);\n    } else {\n      await authStore.logout();\n    }\n  }\n\n  /**\n   * 刷新token逻辑\n   */\n  async function doRefreshToken() {\n    const accessStore = useAccessStore();\n    const resp = await refreshTokenApi();\n    const newToken = resp.data;\n    accessStore.setAccessToken(newToken);\n    return newToken;\n  }\n\n  function formatToken(token: null | string) {\n    return token ? `Bearer ${token}` : null;\n  }\n\n  // 请求头处理\n  client.addRequestInterceptor({\n    fulfilled: async (config) => {\n      const accessStore = useAccessStore();\n\n      config.headers.Authorization = formatToken(accessStore.accessToken);\n      config.headers['Accept-Language'] = preferences.app.locale;\n      return config;\n    },\n  });\n\n  // 处理返回的响应数据格式\n  client.addResponseInterceptor(\n    defaultResponseInterceptor({\n      codeField: 'code',\n      dataField: 'data',\n      successCode: 0,\n    }),\n  );\n\n  // token过期的处理\n  client.addResponseInterceptor(\n    authenticateResponseInterceptor({\n      client,\n      doReAuthenticate,\n      doRefreshToken,\n      enableRefreshToken: preferences.app.enableRefreshToken,\n      formatToken,\n    }),\n  );\n\n  // 通用的错误处理,如果没有进入上面的错误处理逻辑，就会进入这里\n  client.addResponseInterceptor(\n    errorMessageResponseInterceptor((msg: string, error) => {\n      // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理，根据不同的 code 做不同的提示，而不是直接使用 message.error 提示 msg\n      // 当前mock接口返回的错误字段是 error 或者 message\n      const responseData = error?.response?.data ?? {};\n      const errorMessage = responseData?.error ?? responseData?.message ?? '';\n      // 如果没有错误信息，则会根据状态码进行提示\n      message.error(errorMessage || msg);\n    }),\n  );\n\n  return client;\n}\n\nexport const requestClient = createRequestClient(apiURL, {\n  responseReturn: 'data',\n});\n\nexport const baseRequestClient = new RequestClient({ baseURL: apiURL });\n"
  },
  {
    "path": "apps/web-tdesign/src/app.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { GlobalConfigProvider } from 'tdesign-vue-next';\n\nimport { watch } from 'vue';\n\nimport { usePreferences } from '@vben/preferences';\n\nimport { merge } from 'es-toolkit/compat';\nimport { ConfigProvider } from 'tdesign-vue-next';\nimport zhConfig from 'tdesign-vue-next/es/locale/zh_CN';\n\ndefineOptions({ name: 'App' });\nconst { isDark } = usePreferences();\n\nwatch(\n  () => isDark.value,\n  (dark) => {\n    document.documentElement.setAttribute('theme-mode', dark ? 'dark' : '');\n  },\n  { immediate: true },\n);\n\nconst customConfig: GlobalConfigProvider = {\n  // 可以在此处定义更多自定义配置，具体可配置内容参看 API 文档\n  calendar: {},\n  table: {},\n  pagination: {},\n};\nconst globalConfig = merge(zhConfig, customConfig);\n</script>\n\n<template>\n  <ConfigProvider :global-config=\"globalConfig\">\n    <RouterView />\n  </ConfigProvider>\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/bootstrap.ts",
    "content": "import { createApp, watchEffect } from 'vue';\n\nimport { registerAccessDirective } from '@vben/access';\nimport { registerLoadingDirective } from '@vben/common-ui/es/loading';\nimport { preferences } from '@vben/preferences';\nimport { initStores } from '@vben/stores';\nimport '@vben/styles';\n// import '@vben/styles/antd';\n// 引入组件库的少量全局样式变量\n\nimport { useTitle } from '@vueuse/core';\n\nimport { $t, setupI18n } from '#/locales';\n\nimport { initComponentAdapter } from './adapter/component';\nimport { initSetupVbenForm } from './adapter/form';\nimport App from './app.vue';\nimport { router } from './router';\n\nimport 'tdesign-vue-next/es/style/index.css';\n\nasync function bootstrap(namespace: string) {\n  // 初始化组件适配器\n  await initComponentAdapter();\n\n  // 初始化表单组件\n  await initSetupVbenForm();\n\n  // // 设置弹窗的默认配置\n  // setDefaultModalProps({\n  //   fullscreenButton: false,\n  // });\n  // // 设置抽屉的默认配置\n  // setDefaultDrawerProps({\n  //   zIndex: 1020,\n  // });\n\n  const app = createApp(App);\n\n  // 注册v-loading指令\n  registerLoadingDirective(app, {\n    loading: 'loading', // 在这里可以自定义指令名称，也可以明确提供false表示不注册这个指令\n    spinning: 'spinning',\n  });\n\n  // 国际化 i18n 配置\n  await setupI18n(app);\n\n  // 配置 pinia-tore\n  await initStores(app, { namespace });\n\n  // 安装权限指令\n  registerAccessDirective(app);\n\n  // 初始化 tippy\n  const { initTippy } = await import('@vben/common-ui/es/tippy');\n  initTippy(app);\n\n  // 配置路由及路由守卫\n  app.use(router);\n\n  // 配置Motion插件\n  const { MotionPlugin } = await import('@vben/plugins/motion');\n  app.use(MotionPlugin);\n\n  // 动态更新标题\n  watchEffect(() => {\n    if (preferences.app.dynamicTitle) {\n      const routeTitle = router.currentRoute.value.meta?.title;\n      const pageTitle =\n        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;\n      useTitle(pageTitle);\n    }\n  });\n\n  app.mount('#app');\n}\n\nexport { bootstrap };\n"
  },
  {
    "path": "apps/web-tdesign/src/layouts/auth.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { AuthPageLayout } from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst appName = computed(() => preferences.app.name);\nconst logo = computed(() => preferences.logo.source);\n</script>\n\n<template>\n  <AuthPageLayout\n    :app-name=\"appName\"\n    :logo=\"logo\"\n    :page-description=\"$t('authentication.pageDesc')\"\n    :page-title=\"$t('authentication.pageTitle')\"\n  >\n    <!-- 自定义工具栏 -->\n    <!-- <template #toolbar></template> -->\n  </AuthPageLayout>\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/layouts/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { NotificationItem } from '@vben/layouts';\n\nimport { computed, ref, watch } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { AuthenticationLoginExpiredModal } from '@vben/common-ui';\nimport { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';\nimport { useWatermark } from '@vben/hooks';\nimport { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons';\nimport {\n  BasicLayout,\n  LockScreen,\n  Notification,\n  UserDropdown,\n} from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport { $t } from '#/locales';\nimport { useAuthStore } from '#/store';\nimport LoginForm from '#/views/_core/authentication/login.vue';\n\nconst notifications = ref<NotificationItem[]>([\n  {\n    id: 1,\n    avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',\n    date: '3小时前',\n    isRead: true,\n    message: '描述信息描述信息描述信息',\n    title: '收到了 14 份新周报',\n  },\n  {\n    id: 2,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '刚刚',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '朱偏右 回复了你',\n  },\n  {\n    id: 3,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '2024-01-01',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '曲丽丽 评论了你',\n  },\n  {\n    id: 4,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '代办提醒',\n  },\n  {\n    id: 5,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转Workspace示例',\n    link: '/workspace',\n  },\n  {\n    id: 6,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转外部链接示例',\n    link: 'https://doc.vben.pro',\n  },\n]);\n\nconst router = useRouter();\nconst userStore = useUserStore();\nconst authStore = useAuthStore();\nconst accessStore = useAccessStore();\nconst { destroyWatermark, updateWatermark } = useWatermark();\nconst showDot = computed(() =>\n  notifications.value.some((item) => !item.isRead),\n);\n\nconst menus = computed(() => [\n  {\n    handler: () => {\n      router.push({ name: 'Profile' });\n    },\n    icon: 'lucide:user',\n    text: $t('page.auth.profile'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_DOC_URL, {\n        target: '_blank',\n      });\n    },\n    icon: BookOpenText,\n    text: $t('ui.widgets.document'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_GITHUB_URL, {\n        target: '_blank',\n      });\n    },\n    icon: SvgGithubIcon,\n    text: 'GitHub',\n  },\n  {\n    handler: () => {\n      openWindow(`${VBEN_GITHUB_URL}/issues`, {\n        target: '_blank',\n      });\n    },\n    icon: CircleHelp,\n    text: $t('ui.widgets.qa'),\n  },\n]);\n\nconst avatar = computed(() => {\n  return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;\n});\n\nasync function handleLogout() {\n  await authStore.logout(false);\n}\n\nfunction handleNoticeClear() {\n  notifications.value = [];\n}\n\nfunction markRead(id: number | string) {\n  const item = notifications.value.find((item) => item.id === id);\n  if (item) {\n    item.isRead = true;\n  }\n}\n\nfunction remove(id: number | string) {\n  notifications.value = notifications.value.filter((item) => item.id !== id);\n}\n\nfunction handleMakeAll() {\n  notifications.value.forEach((item) => (item.isRead = true));\n}\nwatch(\n  () => ({\n    enable: preferences.app.watermark,\n    content: preferences.app.watermarkContent,\n  }),\n  async ({ enable, content }) => {\n    if (enable) {\n      await updateWatermark({\n        content:\n          content ||\n          `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,\n      });\n    } else {\n      destroyWatermark();\n    }\n  },\n  {\n    immediate: true,\n  },\n);\n</script>\n\n<template>\n  <BasicLayout @clear-preferences-and-logout=\"handleLogout\">\n    <template #user-dropdown>\n      <UserDropdown\n        :avatar\n        :menus\n        :text=\"userStore.userInfo?.realName\"\n        description=\"ann.vben@gmail.com\"\n        tag-text=\"Pro\"\n        @logout=\"handleLogout\"\n      />\n    </template>\n    <template #notification>\n      <Notification\n        :dot=\"showDot\"\n        :notifications=\"notifications\"\n        @clear=\"handleNoticeClear\"\n        @read=\"(item) => item.id && markRead(item.id)\"\n        @remove=\"(item) => item.id && remove(item.id)\"\n        @make-all=\"handleMakeAll\"\n      />\n    </template>\n    <template #extra>\n      <AuthenticationLoginExpiredModal\n        v-model:open=\"accessStore.loginExpired\"\n        :avatar\n      >\n        <LoginForm />\n      </AuthenticationLoginExpiredModal>\n    </template>\n    <template #lock-screen>\n      <LockScreen :avatar @to-login=\"handleLogout\" />\n    </template>\n  </BasicLayout>\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/layouts/index.ts",
    "content": "const BasicLayout = () => import('./basic.vue');\nconst AuthPageLayout = () => import('./auth.vue');\n\nconst IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);\n\nexport { AuthPageLayout, BasicLayout, IFrameView };\n"
  },
  {
    "path": "apps/web-tdesign/src/locales/README.md",
    "content": "# locale\n\n每个app使用的国际化可能不同，这里用于扩展国际化的功能，例如扩展 dayjs、antd组件库的多语言切换，以及app本身的国际化文件。\n"
  },
  {
    "path": "apps/web-tdesign/src/locales/index.ts",
    "content": "import type { App } from 'vue';\n\nimport type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';\n\nimport {\n  $t,\n  setupI18n as coreSetup,\n  loadLocalesMapFromDir,\n} from '@vben/locales';\nimport { preferences } from '@vben/preferences';\n\nimport dayjs from 'dayjs';\n\nconst modules = import.meta.glob('./langs/**/*.json');\n\nconst localesMap = loadLocalesMapFromDir(\n  /\\.\\/langs\\/([^/]+)\\/(.*)\\.json$/,\n  modules,\n);\n/**\n * 加载应用特有的语言包\n * 这里也可以改造为从服务端获取翻译数据\n * @param lang\n */\nasync function loadMessages(lang: SupportedLanguagesType) {\n  const [appLocaleMessages] = await Promise.all([\n    localesMap[lang]?.(),\n    loadThirdPartyMessage(lang),\n  ]);\n  return appLocaleMessages?.default;\n}\n\n/**\n * 加载第三方组件库的语言包\n * @param lang\n */\nasync function loadThirdPartyMessage(lang: SupportedLanguagesType) {\n  await loadDayjsLocale(lang);\n}\n\n/**\n * 加载dayjs的语言包\n * @param lang\n */\nasync function loadDayjsLocale(lang: SupportedLanguagesType) {\n  let locale;\n  switch (lang) {\n    case 'en-US': {\n      locale = await import('dayjs/locale/en');\n      break;\n    }\n    case 'zh-CN': {\n      locale = await import('dayjs/locale/zh-cn');\n      break;\n    }\n    // 默认使用英语\n    default: {\n      locale = await import('dayjs/locale/en');\n    }\n  }\n  if (locale) {\n    dayjs.locale(locale);\n  } else {\n    console.error(`Failed to load dayjs locale for ${lang}`);\n  }\n}\n\nasync function setupI18n(app: App, options: LocaleSetupOptions = {}) {\n  await coreSetup(app, {\n    defaultLocale: preferences.app.locale,\n    loadMessages,\n    missingWarn: !import.meta.env.PROD,\n    ...options,\n  });\n}\n\nexport { $t, setupI18n };\n"
  },
  {
    "path": "apps/web-tdesign/src/locales/langs/en-US/demos.json",
    "content": "{\n  \"title\": \"Demos\",\n  \"tdesign\": \"TDesign Vue\",\n  \"vben\": {\n    \"title\": \"Project\",\n    \"about\": \"About\",\n    \"document\": \"Document\",\n    \"antdv\": \"Ant Design Vue Version\",\n    \"antdv-next\": \"Antdv Next Version\",\n    \"naive-ui\": \"Naive UI Version\",\n    \"element-plus\": \"Element Plus Version\"\n  }\n}\n"
  },
  {
    "path": "apps/web-tdesign/src/locales/langs/en-US/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"Login\",\n    \"register\": \"Register\",\n    \"codeLogin\": \"Code Login\",\n    \"qrcodeLogin\": \"Qr Code Login\",\n    \"forgetPassword\": \"Forget Password\",\n    \"profile\": \"Profile\"\n  },\n  \"dashboard\": {\n    \"title\": \"Dashboard\",\n    \"analytics\": \"Analytics\",\n    \"workspace\": \"Workspace\"\n  }\n}\n"
  },
  {
    "path": "apps/web-tdesign/src/locales/langs/zh-CN/demos.json",
    "content": "{\n  \"title\": \"演示\",\n  \"tdesign\": \"TDesign Vue\",\n  \"vben\": {\n    \"title\": \"项目\",\n    \"about\": \"关于\",\n    \"document\": \"文档\",\n    \"antdv\": \"Ant Design Vue 版本\",\n    \"antdv-next\": \"Antdv Next 版本\",\n    \"naive-ui\": \"Naive UI 版本\",\n    \"element-plus\": \"Element Plus 版本\"\n  }\n}\n"
  },
  {
    "path": "apps/web-tdesign/src/locales/langs/zh-CN/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"登录\",\n    \"register\": \"注册\",\n    \"codeLogin\": \"验证码登录\",\n    \"qrcodeLogin\": \"二维码登录\",\n    \"forgetPassword\": \"忘记密码\",\n    \"profile\": \"个人中心\"\n  },\n  \"dashboard\": {\n    \"title\": \"概览\",\n    \"analytics\": \"分析页\",\n    \"workspace\": \"工作台\"\n  }\n}\n"
  },
  {
    "path": "apps/web-tdesign/src/main.ts",
    "content": "import { initPreferences } from '@vben/preferences';\nimport { unmountGlobalLoading } from '@vben/utils';\n\nimport { overridesPreferences } from './preferences';\n\n/**\n * 应用初始化完成之后再进行页面加载渲染\n */\nasync function initApplication() {\n  // name用于指定项目唯一标识\n  // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据\n  const env = import.meta.env.PROD ? 'prod' : 'dev';\n  const appVersion = import.meta.env.VITE_APP_VERSION;\n  const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;\n\n  // app偏好设置初始化\n  await initPreferences({\n    namespace,\n    overrides: overridesPreferences,\n  });\n\n  // 启动应用并挂载\n  // vue应用主要逻辑及视图\n  const { bootstrap } = await import('./bootstrap');\n  await bootstrap(namespace);\n\n  // 移除并销毁loading\n  unmountGlobalLoading();\n}\n\ninitApplication();\n"
  },
  {
    "path": "apps/web-tdesign/src/preferences.ts",
    "content": "import { defineOverridesPreferences } from '@vben/preferences';\n\n/**\n * @description 项目配置文件\n * 只需要覆盖项目中的一部分配置，不需要的配置不用覆盖，会自动使用默认配置\n * !!! 更改配置后请清空缓存，否则可能不生效\n */\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    name: import.meta.env.VITE_APP_TITLE,\n  },\n});\n"
  },
  {
    "path": "apps/web-tdesign/src/router/access.ts",
    "content": "import type {\n  ComponentRecordType,\n  GenerateMenuAndRoutesOptions,\n} from '@vben/types';\n\nimport { generateAccessible } from '@vben/access';\nimport { preferences } from '@vben/preferences';\n\nimport { message } from '#/adapter/tdesign';\nimport { getAllMenusApi } from '#/api';\nimport { BasicLayout, IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');\n\nasync function generateAccess(options: GenerateMenuAndRoutesOptions) {\n  const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');\n\n  const layoutMap: ComponentRecordType = {\n    BasicLayout,\n    IFrameView,\n  };\n\n  return await generateAccessible(preferences.app.accessMode, {\n    ...options,\n    fetchMenuListAsync: async () => {\n      message.loading({\n        content: `${$t('common.loadingMenu')}...`,\n        duration: 1500,\n      });\n      return await getAllMenusApi();\n    },\n    // 可以指定没有权限跳转403页面\n    forbiddenComponent,\n    // 如果 route.meta.menuVisibleWithForbidden = true\n    layoutMap,\n    pageMap,\n  });\n}\n\nexport { generateAccess };\n"
  },
  {
    "path": "apps/web-tdesign/src/router/guard.ts",
    "content": "import type { Router } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { startProgress, stopProgress } from '@vben/utils';\n\nimport { accessRoutes, coreRouteNames } from '#/router/routes';\nimport { useAuthStore } from '#/store';\n\nimport { generateAccess } from './access';\n\n/**\n * 通用守卫配置\n * @param router\n */\nfunction setupCommonGuard(router: Router) {\n  // 记录已经加载的页面\n  const loadedPaths = new Set<string>();\n\n  router.beforeEach((to) => {\n    to.meta.loaded = loadedPaths.has(to.path);\n\n    // 页面加载进度条\n    if (!to.meta.loaded && preferences.transition.progress) {\n      startProgress();\n    }\n    return true;\n  });\n\n  router.afterEach((to) => {\n    // 记录页面是否加载,如果已经加载，后续的页面切换动画等效果不在重复执行\n\n    loadedPaths.add(to.path);\n\n    // 关闭页面加载进度条\n    if (preferences.transition.progress) {\n      stopProgress();\n    }\n  });\n}\n\n/**\n * 权限访问守卫配置\n * @param router\n */\nfunction setupAccessGuard(router: Router) {\n  router.beforeEach(async (to, from) => {\n    const accessStore = useAccessStore();\n    const userStore = useUserStore();\n    const authStore = useAuthStore();\n\n    // 基本路由，这些路由不需要进入权限拦截\n    if (coreRouteNames.includes(to.name as string)) {\n      if (to.path === LOGIN_PATH && accessStore.accessToken) {\n        return decodeURIComponent(\n          (to.query?.redirect as string) ||\n            userStore.userInfo?.homePath ||\n            preferences.app.defaultHomePath,\n        );\n      }\n      return true;\n    }\n\n    // accessToken 检查\n    if (!accessStore.accessToken) {\n      // 明确声明忽略权限访问权限，则可以访问\n      if (to.meta.ignoreAccess) {\n        return true;\n      }\n\n      // 没有访问权限，跳转登录页面\n      if (to.fullPath !== LOGIN_PATH) {\n        return {\n          path: LOGIN_PATH,\n          // 如不需要，直接删除 query\n          query:\n            to.fullPath === preferences.app.defaultHomePath\n              ? {}\n              : { redirect: encodeURIComponent(to.fullPath) },\n          // 携带当前跳转的页面，登录后重新跳转该页面\n          replace: true,\n        };\n      }\n      return to;\n    }\n\n    // 是否已经生成过动态路由\n    if (accessStore.isAccessChecked) {\n      return true;\n    }\n\n    // 生成路由表\n    // 当前登录用户拥有的角色标识列表\n    const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());\n    const userRoles = userInfo.roles ?? [];\n\n    // 生成菜单和路由\n    const { accessibleMenus, accessibleRoutes } = await generateAccess({\n      roles: userRoles,\n      router,\n      // 则会在菜单中显示，但是访问会被重定向到403\n      routes: accessRoutes,\n    });\n\n    // 保存菜单信息和路由信息\n    accessStore.setAccessMenus(accessibleMenus);\n    accessStore.setAccessRoutes(accessibleRoutes);\n    accessStore.setIsAccessChecked(true);\n    const redirectPath = (from.query.redirect ??\n      (to.path === preferences.app.defaultHomePath\n        ? userInfo.homePath || preferences.app.defaultHomePath\n        : to.fullPath)) as string;\n\n    return {\n      ...router.resolve(decodeURIComponent(redirectPath)),\n      replace: true,\n    };\n  });\n}\n\n/**\n * 项目守卫配置\n * @param router\n */\nfunction createRouterGuard(router: Router) {\n  /** 通用 */\n  setupCommonGuard(router);\n  /** 权限访问 */\n  setupAccessGuard(router);\n}\n\nexport { createRouterGuard };\n"
  },
  {
    "path": "apps/web-tdesign/src/router/index.ts",
    "content": "import {\n  createRouter,\n  createWebHashHistory,\n  createWebHistory,\n} from 'vue-router';\n\nimport { resetStaticRoutes } from '@vben/utils';\n\nimport { createRouterGuard } from './guard';\nimport { routes } from './routes';\n\n/**\n *  @zh_CN 创建vue-router实例\n */\nconst router = createRouter({\n  history:\n    import.meta.env.VITE_ROUTER_HISTORY === 'hash'\n      ? createWebHashHistory(import.meta.env.VITE_BASE)\n      : createWebHistory(import.meta.env.VITE_BASE),\n  // 应该添加到路由的初始路由列表。\n  routes,\n  scrollBehavior: (to, _from, savedPosition) => {\n    if (savedPosition) {\n      return savedPosition;\n    }\n    return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };\n  },\n  // 是否应该禁止尾部斜杠。\n  // strict: true,\n});\n\nconst resetRoutes = () => resetStaticRoutes(router, routes);\n\n// 创建路由守卫\ncreateRouterGuard(router);\n\nexport { resetRoutes, router };\n"
  },
  {
    "path": "apps/web-tdesign/src/router/routes/core.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst BasicLayout = () => import('#/layouts/basic.vue');\nconst AuthPageLayout = () => import('#/layouts/auth.vue');\n/** 全局404页面 */\nconst fallbackNotFoundRoute: RouteRecordRaw = {\n  component: () => import('#/views/_core/fallback/not-found.vue'),\n  meta: {\n    hideInBreadcrumb: true,\n    hideInMenu: true,\n    hideInTab: true,\n    title: '404',\n  },\n  name: 'FallbackNotFound',\n  path: '/:path(.*)*',\n};\n\n/** 基本路由，这些路由是必须存在的 */\nconst coreRoutes: RouteRecordRaw[] = [\n  /**\n   * 根路由\n   * 使用基础布局，作为所有页面的父级容器，子级就不必配置BasicLayout。\n   * 此路由必须存在，且不应修改\n   */\n  {\n    component: BasicLayout,\n    meta: {\n      hideInBreadcrumb: true,\n      title: 'Root',\n    },\n    name: 'Root',\n    path: '/',\n    redirect: preferences.app.defaultHomePath,\n    children: [],\n  },\n  {\n    component: AuthPageLayout,\n    meta: {\n      hideInTab: true,\n      title: 'Authentication',\n    },\n    name: 'Authentication',\n    path: '/auth',\n    redirect: LOGIN_PATH,\n    children: [\n      {\n        name: 'Login',\n        path: 'login',\n        component: () => import('#/views/_core/authentication/login.vue'),\n        meta: {\n          title: $t('page.auth.login'),\n        },\n      },\n      {\n        name: 'CodeLogin',\n        path: 'code-login',\n        component: () => import('#/views/_core/authentication/code-login.vue'),\n        meta: {\n          title: $t('page.auth.codeLogin'),\n        },\n      },\n      {\n        name: 'QrCodeLogin',\n        path: 'qrcode-login',\n        component: () =>\n          import('#/views/_core/authentication/qrcode-login.vue'),\n        meta: {\n          title: $t('page.auth.qrcodeLogin'),\n        },\n      },\n      {\n        name: 'ForgetPassword',\n        path: 'forget-password',\n        component: () =>\n          import('#/views/_core/authentication/forget-password.vue'),\n        meta: {\n          title: $t('page.auth.forgetPassword'),\n        },\n      },\n      {\n        name: 'Register',\n        path: 'register',\n        component: () => import('#/views/_core/authentication/register.vue'),\n        meta: {\n          title: $t('page.auth.register'),\n        },\n      },\n    ],\n  },\n];\n\nexport { coreRoutes, fallbackNotFoundRoute };\n"
  },
  {
    "path": "apps/web-tdesign/src/router/routes/index.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { mergeRouteModules, traverseTreeValues } from '@vben/utils';\n\nimport { coreRoutes, fallbackNotFoundRoute } from './core';\n\nconst dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {\n  eager: true,\n});\n\n// 有需要可以自行打开注释，并创建文件夹\n// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });\n// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });\n\n/** 动态路由 */\nconst dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);\n\n/** 外部路由列表，访问这些页面可以不需要Layout，可能用于内嵌在别的系统(不会显示在菜单中) */\n// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);\n// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);\nconst staticRoutes: RouteRecordRaw[] = [];\nconst externalRoutes: RouteRecordRaw[] = [];\n\n/** 路由列表，由基本路由、外部路由和404兜底路由组成\n *  无需走权限验证（会一直显示在菜单中） */\nconst routes: RouteRecordRaw[] = [\n  ...coreRoutes,\n  ...externalRoutes,\n  fallbackNotFoundRoute,\n];\n\n/** 基本路由列表，这些路由不需要进入权限拦截 */\nconst coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);\n\n/** 有权限校验的路由列表，包含动态路由和静态路由 */\nconst accessRoutes = [...dynamicRoutes, ...staticRoutes];\nexport { accessRoutes, coreRouteNames, routes };\n"
  },
  {
    "path": "apps/web-tdesign/src/router/routes/modules/dashboard.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'lucide:layout-dashboard',\n      order: -1,\n      title: $t('page.dashboard.title'),\n    },\n    name: 'Dashboard',\n    path: '/dashboard',\n    children: [\n      {\n        name: 'Analytics',\n        path: '/analytics',\n        component: () => import('#/views/dashboard/analytics/index.vue'),\n        meta: {\n          affixTab: true,\n          icon: 'lucide:area-chart',\n          title: $t('page.dashboard.analytics'),\n        },\n      },\n      {\n        name: 'Workspace',\n        path: '/workspace',\n        component: () => import('#/views/dashboard/workspace/index.vue'),\n        meta: {\n          icon: 'carbon:workspace',\n          title: $t('page.dashboard.workspace'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-tdesign/src/router/routes/modules/demos.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'ic:baseline-view-in-ar',\n      keepAlive: true,\n      order: 1000,\n      title: $t('demos.title'),\n    },\n    name: 'Demos',\n    path: '/demos',\n    children: [\n      {\n        meta: {\n          title: $t('demos.tdesign'),\n        },\n        name: 'TDesignDemos',\n        path: '/demos/tdesign',\n        component: () => import('#/views/demos/tdesign/index.vue'),\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-tdesign/src/router/routes/modules/vben.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport {\n  VBEN_ANT_PREVIEW_URL,\n  VBEN_ANTDV_NEXT_PREVIEW_URL,\n  VBEN_DOC_URL,\n  VBEN_ELE_PREVIEW_URL,\n  VBEN_GITHUB_URL,\n  VBEN_LOGO_URL,\n  VBEN_NAIVE_PREVIEW_URL,\n} from '@vben/constants';\nimport { SvgAntdvLogoIcon, SvgAntdvNextLogoIcon } from '@vben/icons';\n\nimport { IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      badgeType: 'dot',\n      icon: VBEN_LOGO_URL,\n      order: 9998,\n      title: $t('demos.vben.title'),\n    },\n    name: 'VbenProject',\n    path: '/vben-admin',\n    children: [\n      {\n        name: 'VbenDocument',\n        path: '/vben-admin/document',\n        component: IFrameView,\n        meta: {\n          icon: 'lucide:book-open-text',\n          link: VBEN_DOC_URL,\n          title: $t('demos.vben.document'),\n        },\n      },\n      {\n        name: 'VbenGithub',\n        path: '/vben-admin/github',\n        component: IFrameView,\n        meta: {\n          icon: 'mdi:github',\n          link: VBEN_GITHUB_URL,\n          title: 'Github',\n        },\n      },\n      {\n        name: 'VbenNaive',\n        path: '/vben-admin/naive',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: 'logos:naiveui',\n          link: VBEN_NAIVE_PREVIEW_URL,\n          title: $t('demos.vben.naive-ui'),\n        },\n      },\n      {\n        name: 'VbenAntdv',\n        path: '/vben-admin/antdv',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgAntdvLogoIcon,\n          link: VBEN_ANT_PREVIEW_URL,\n          title: $t('demos.vben.antdv'),\n        },\n      },\n      {\n        name: 'VbenAntdVNext',\n        path: '/vben-admin/antdv-next',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgAntdvNextLogoIcon,\n          link: VBEN_ANTDV_NEXT_PREVIEW_URL,\n          title: $t('demos.vben.antdv-next'),\n        },\n      },\n      {\n        name: 'VbenElementPlus',\n        path: '/vben-admin/ele',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: 'logos:element',\n          link: VBEN_ELE_PREVIEW_URL,\n          title: $t('demos.vben.element-plus'),\n        },\n      },\n    ],\n  },\n  {\n    name: 'VbenAbout',\n    path: '/vben-admin/about',\n    component: () => import('#/views/_core/about/index.vue'),\n    meta: {\n      icon: 'lucide:copyright',\n      title: $t('demos.vben.about'),\n      order: 9999,\n    },\n  },\n  {\n    name: 'Profile',\n    path: '/profile',\n    component: () => import('#/views/_core/profile/index.vue'),\n    meta: {\n      icon: 'lucide:user',\n      hideInMenu: true,\n      title: $t('page.auth.profile'),\n    },\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "apps/web-tdesign/src/store/auth.ts",
    "content": "import type { Recordable, UserInfo } from '@vben/types';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';\n\nimport { defineStore } from 'pinia';\n\nimport { notification } from '#/adapter/tdesign';\nimport { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';\nimport { $t } from '#/locales';\n\nexport const useAuthStore = defineStore('auth', () => {\n  const accessStore = useAccessStore();\n  const userStore = useUserStore();\n  const router = useRouter();\n\n  const loginLoading = ref(false);\n\n  /**\n   * 异步处理登录操作\n   * Asynchronously handle the login process\n   * @param params 登录表单数据\n   */\n  async function authLogin(\n    params: Recordable<any>,\n    onSuccess?: () => Promise<void> | void,\n  ) {\n    // 异步处理用户登录操作并获取 accessToken\n    let userInfo: null | UserInfo = null;\n    try {\n      loginLoading.value = true;\n      const { accessToken } = await loginApi(params);\n\n      // 如果成功获取到 accessToken\n      if (accessToken) {\n        accessStore.setAccessToken(accessToken);\n        // 获取用户信息并存储到 accessStore 中\n        const [fetchUserInfoResult, accessCodes] = await Promise.all([\n          fetchUserInfo(),\n          getAccessCodesApi(),\n        ]);\n\n        userInfo = fetchUserInfoResult;\n\n        userStore.setUserInfo(userInfo);\n        accessStore.setAccessCodes(accessCodes);\n\n        if (accessStore.loginExpired) {\n          accessStore.setLoginExpired(false);\n        } else {\n          onSuccess\n            ? await onSuccess?.()\n            : await router.push(\n                userInfo.homePath || preferences.app.defaultHomePath,\n              );\n        }\n\n        if (userInfo?.realName) {\n          notification.success({\n            title: $t('authentication.loginSuccess'),\n            content: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,\n            duration: 3000,\n          });\n        }\n      }\n    } finally {\n      loginLoading.value = false;\n    }\n\n    return {\n      userInfo,\n    };\n  }\n\n  async function logout(redirect: boolean = true) {\n    try {\n      await logoutApi();\n    } catch {\n      // 不做任何处理\n    }\n    resetAllStores();\n    accessStore.setLoginExpired(false);\n\n    // 回登录页带上当前路由地址\n    await router.replace({\n      path: LOGIN_PATH,\n      query: redirect\n        ? {\n            redirect: encodeURIComponent(router.currentRoute.value.fullPath),\n          }\n        : {},\n    });\n  }\n\n  async function fetchUserInfo() {\n    const userInfo = await getUserInfoApi();\n    userStore.setUserInfo(userInfo);\n    return userInfo;\n  }\n\n  function $reset() {\n    loginLoading.value = false;\n  }\n\n  return {\n    $reset,\n    authLogin,\n    fetchUserInfo,\n    loginLoading,\n    logout,\n  };\n});\n"
  },
  {
    "path": "apps/web-tdesign/src/store/index.ts",
    "content": "export * from './auth';\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/README.md",
    "content": "# \\_core\n\n此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/about/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { About } from '@vben/common-ui';\n\ndefineOptions({ name: 'About' });\n</script>\n\n<template>\n  <About />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/authentication/code-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationCodeLogin, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'CodeLogin' });\n\nconst loading = ref(false);\nconst CODE_LENGTH = 6;\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.mobile'),\n      },\n      fieldName: 'phoneNumber',\n      label: $t('authentication.mobile'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.mobileTip') })\n        .refine((v) => /^\\d{11}$/.test(v), {\n          message: $t('authentication.mobileErrortip'),\n        }),\n    },\n    {\n      component: 'VbenPinInput',\n      componentProps: {\n        codeLength: CODE_LENGTH,\n        createText: (countdown: number) => {\n          const text =\n            countdown > 0\n              ? $t('authentication.sendText', [countdown])\n              : $t('authentication.sendCode');\n          return text;\n        },\n        placeholder: $t('authentication.code'),\n      },\n      fieldName: 'code',\n      label: $t('authentication.code'),\n      rules: z.string().length(CODE_LENGTH, {\n        message: $t('authentication.codeTip', [CODE_LENGTH]),\n      }),\n    },\n  ];\n});\n/**\n * 异步处理登录操作\n * Asynchronously handle the login process\n * @param values 登录表单数据\n */\nasync function handleLogin(values: Recordable<any>) {\n  void values;\n}\n</script>\n\n<template>\n  <AuthenticationCodeLogin\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleLogin\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/authentication/forget-password.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationForgetPassword, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'ForgetPassword' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: 'example@example.com',\n      },\n      fieldName: 'email',\n      label: $t('authentication.email'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.emailTip') })\n        .email($t('authentication.emailValidErrorTip')),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationForgetPassword\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/authentication/login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { BasicOption } from '@vben/types';\n\nimport { computed, markRaw } from 'vue';\n\nimport { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { useAuthStore } from '#/store';\n\ndefineOptions({ name: 'Login' });\n\nconst authStore = useAuthStore();\n\nconst MOCK_USER_OPTIONS: BasicOption[] = [\n  {\n    label: 'Super',\n    value: 'vben',\n  },\n  {\n    label: 'Admin',\n    value: 'admin',\n  },\n  {\n    label: 'User',\n    value: 'jack',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenSelect',\n      componentProps: {\n        options: MOCK_USER_OPTIONS,\n        placeholder: $t('authentication.selectAccount'),\n      },\n      fieldName: 'selectAccount',\n      label: $t('authentication.selectAccount'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.selectAccount') })\n        .optional()\n        .default('vben'),\n    },\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      dependencies: {\n        trigger(values, form) {\n          if (values.selectAccount) {\n            const findUser = MOCK_USER_OPTIONS.find(\n              (item) => item.value === values.selectAccount,\n            );\n            if (findUser) {\n              form.setValues({\n                password: '123456',\n                username: findUser.value,\n              });\n            }\n          }\n        },\n        triggerFields: ['selectAccount'],\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n\n    {\n      component: markRaw(SliderCaptcha),\n      fieldName: 'captcha',\n      rules: z.boolean().refine((value) => value, {\n        message: $t('authentication.verifyRequiredTip'),\n      }),\n    },\n  ];\n});\n</script>\n\n<template>\n  <AuthenticationLogin\n    :form-schema=\"formSchema\"\n    :loading=\"authStore.loginLoading\"\n    @submit=\"authStore.authLogin\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/authentication/qrcode-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport { AuthenticationQrCodeLogin } from '@vben/common-ui';\nimport { LOGIN_PATH } from '@vben/constants';\n\ndefineOptions({ name: 'QrCodeLogin' });\n</script>\n\n<template>\n  <AuthenticationQrCodeLogin :login-path=\"LOGIN_PATH\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/authentication/register.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, h, ref } from 'vue';\n\nimport { AuthenticationRegister, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'Register' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      renderComponentContent() {\n        return {\n          strengthText: () => $t('authentication.passwordStrength'),\n        };\n      },\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.confirmPassword'),\n      },\n      dependencies: {\n        rules(values) {\n          const { password } = values;\n          return z\n            .string({ required_error: $t('authentication.passwordTip') })\n            .min(1, { message: $t('authentication.passwordTip') })\n            .refine((value) => value === password, {\n              message: $t('authentication.confirmPasswordTip'),\n            });\n        },\n        triggerFields: ['password'],\n      },\n      fieldName: 'confirmPassword',\n      label: $t('authentication.confirmPassword'),\n    },\n    {\n      component: 'VbenCheckbox',\n      fieldName: 'agreePolicy',\n      renderComponentContent: () => ({\n        default: () =>\n          h('span', [\n            $t('authentication.agree'),\n            h(\n              'a',\n              {\n                class: 'vben-link ml-1 ',\n                href: '',\n              },\n              `${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,\n            ),\n          ]),\n      }),\n      rules: z.boolean().refine((value) => !!value, {\n        message: $t('authentication.agreeTip'),\n      }),\n    },\n  ];\n});\n\nasync function handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationRegister\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/fallback/coming-soon.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback status=\"coming-soon\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/fallback/forbidden.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback403Demo' });\n</script>\n\n<template>\n  <Fallback status=\"403\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/fallback/internal-error.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback500Demo' });\n</script>\n\n<template>\n  <Fallback status=\"500\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/fallback/not-found.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback404Demo' });\n</script>\n\n<template>\n  <Fallback status=\"404\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/fallback/offline.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'FallbackOfflineDemo' });\n</script>\n\n<template>\n  <Fallback status=\"offline\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/profile/base-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { BasicOption } from '@vben/types';\n\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed, onMounted, ref } from 'vue';\n\nimport { ProfileBaseSetting } from '@vben/common-ui';\n\nimport { getUserInfoApi } from '#/api';\n\nconst profileBaseSettingRef = ref();\n\nconst MOCK_ROLES_OPTIONS: BasicOption[] = [\n  {\n    label: '管理员',\n    value: 'super',\n  },\n  {\n    label: '用户',\n    value: 'user',\n  },\n  {\n    label: '测试',\n    value: 'test',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'realName',\n      component: 'Input',\n      label: '姓名',\n    },\n    {\n      fieldName: 'username',\n      component: 'Input',\n      label: '用户名',\n    },\n    {\n      fieldName: 'roles',\n      component: 'Select',\n      componentProps: {\n        mode: 'tags',\n        options: MOCK_ROLES_OPTIONS,\n      },\n      label: '角色',\n    },\n    {\n      fieldName: 'introduction',\n      component: 'Textarea',\n      label: '个人简介',\n    },\n  ];\n});\n\nonMounted(async () => {\n  const data = await getUserInfoApi();\n  profileBaseSettingRef.value.getFormApi().setValues(data);\n});\n</script>\n<template>\n  <ProfileBaseSetting ref=\"profileBaseSettingRef\" :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/profile/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\n\nimport { Profile } from '@vben/common-ui';\nimport { useUserStore } from '@vben/stores';\n\nimport ProfileBase from './base-setting.vue';\nimport ProfileNotificationSetting from './notification-setting.vue';\nimport ProfilePasswordSetting from './password-setting.vue';\nimport ProfileSecuritySetting from './security-setting.vue';\n\nconst userStore = useUserStore();\n\nconst tabsValue = ref<string>('basic');\n\nconst tabs = ref([\n  {\n    label: '基本设置',\n    value: 'basic',\n  },\n  {\n    label: '安全设置',\n    value: 'security',\n  },\n  {\n    label: '修改密码',\n    value: 'password',\n  },\n  {\n    label: '新消息提醒',\n    value: 'notice',\n  },\n]);\n</script>\n<template>\n  <Profile\n    v-model:model-value=\"tabsValue\"\n    title=\"个人中心\"\n    :user-info=\"userStore.userInfo\"\n    :tabs=\"tabs\"\n  >\n    <template #content>\n      <ProfileBase v-if=\"tabsValue === 'basic'\" />\n      <ProfileSecuritySetting v-if=\"tabsValue === 'security'\" />\n      <ProfilePasswordSetting v-if=\"tabsValue === 'password'\" />\n      <ProfileNotificationSetting v-if=\"tabsValue === 'notice'\" />\n    </template>\n  </Profile>\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/profile/notification-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileNotificationSetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '其他用户的消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'systemMessage',\n      label: '系统消息',\n      description: '系统消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'todoTask',\n      label: '待办任务',\n      description: '待办任务将以站内信的形式通知',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileNotificationSetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/profile/password-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed } from 'vue';\n\nimport { ProfilePasswordSetting, z } from '@vben/common-ui';\n\nimport { message } from '#/adapter/tdesign';\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'oldPassword',\n      label: '旧密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: '请输入旧密码',\n      },\n    },\n    {\n      fieldName: 'newPassword',\n      label: '新密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请输入新密码',\n      },\n    },\n    {\n      fieldName: 'confirmPassword',\n      label: '确认密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请再次输入新密码',\n      },\n      dependencies: {\n        rules(values) {\n          const { newPassword } = values;\n          return z\n            .string({ required_error: '请再次输入新密码' })\n            .min(1, { message: '请再次输入新密码' })\n            .refine((value) => value === newPassword, {\n              message: '两次输入的密码不一致',\n            });\n        },\n        triggerFields: ['newPassword'],\n      },\n    },\n  ];\n});\n\nfunction handleSubmit() {\n  message.success('密码修改成功');\n}\n</script>\n<template>\n  <ProfilePasswordSetting\n    class=\"w-1/3\"\n    :form-schema=\"formSchema\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/_core/profile/security-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileSecuritySetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '当前密码强度：强',\n    },\n    {\n      value: true,\n      fieldName: 'securityPhone',\n      label: '密保手机',\n      description: '已绑定手机：138****8293',\n    },\n    {\n      value: true,\n      fieldName: 'securityQuestion',\n      label: '密保问题',\n      description: '未设置密保问题，密保问题可有效保护账户安全',\n    },\n    {\n      value: true,\n      fieldName: 'securityEmail',\n      label: '备用邮箱',\n      description: '已绑定邮箱：ant***sign.com',\n    },\n    {\n      value: false,\n      fieldName: 'securityMfa',\n      label: 'MFA 设备',\n      description: '未绑定 MFA 设备，绑定后，可以进行二次确认',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileSecuritySetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/dashboard/analytics/analytics-trends.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        areaStyle: {},\n        data: [\n          111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,\n          36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,\n          111,\n        ],\n        itemStyle: {\n          color: '#5ab1ef',\n        },\n        smooth: true,\n        type: 'line',\n      },\n      {\n        areaStyle: {},\n        data: [\n          33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,\n          11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,\n        ],\n        itemStyle: {\n          color: '#019680',\n        },\n        smooth: true,\n        type: 'line',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          color: '#019680',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    // xAxis: {\n    //   axisTick: {\n    //     show: false,\n    //   },\n    //   boundaryGap: false,\n    //   data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n    //   type: 'category',\n    // },\n    xAxis: {\n      axisTick: {\n        show: false,\n      },\n      boundaryGap: false,\n      data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n      splitLine: {\n        lineStyle: {\n          type: 'solid',\n          width: 1,\n        },\n        show: true,\n      },\n      type: 'category',\n    },\n    yAxis: [\n      {\n        axisTick: {\n          show: false,\n        },\n        max: 80_000,\n        splitArea: {\n          show: true,\n        },\n        splitNumber: 4,\n        type: 'value',\n      },\n    ],\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/dashboard/analytics/analytics-visits-data.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: 0,\n      data: ['访问', '趋势'],\n    },\n    radar: {\n      indicator: [\n        {\n          name: '网页',\n        },\n        {\n          name: '移动端',\n        },\n        {\n          name: 'Ipad',\n        },\n        {\n          name: '客户端',\n        },\n        {\n          name: '第三方',\n        },\n        {\n          name: '其它',\n        },\n      ],\n      radius: '60%',\n      splitNumber: 8,\n    },\n    series: [\n      {\n        areaStyle: {\n          opacity: 1,\n          shadowBlur: 0,\n          shadowColor: 'rgba(0,0,0,.2)',\n          shadowOffsetX: 0,\n          shadowOffsetY: 10,\n        },\n        data: [\n          {\n            itemStyle: {\n              color: '#b6a2de',\n            },\n            name: '访问',\n            value: [90, 50, 86, 40, 50, 20],\n          },\n          {\n            itemStyle: {\n              color: '#5ab1ef',\n            },\n            name: '趋势',\n            value: [70, 75, 70, 76, 20, 85],\n          },\n        ],\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        symbolSize: 0,\n        type: 'radar',\n      },\n    ],\n    tooltip: {},\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/dashboard/analytics/analytics-visits-sales.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 400;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        center: ['50%', '50%'],\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '外包', value: 500 },\n          { name: '定制', value: 310 },\n          { name: '技术支持', value: 274 },\n          { name: '远程', value: 400 },\n        ].toSorted((a, b) => {\n          return a.value - b.value;\n        }),\n        name: '商业占比',\n        radius: '80%',\n        roseType: 'radius',\n        type: 'pie',\n      },\n    ],\n\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/dashboard/analytics/analytics-visits-source.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: '2%',\n      left: 'center',\n    },\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 100;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        avoidLabelOverlap: false,\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '搜索引擎', value: 1048 },\n          { name: '直接访问', value: 735 },\n          { name: '邮件营销', value: 580 },\n          { name: '联盟广告', value: 484 },\n        ],\n        emphasis: {\n          label: {\n            fontSize: '12',\n            fontWeight: 'bold',\n            show: true,\n          },\n        },\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        label: {\n          position: 'center',\n          show: false,\n        },\n        labelLine: {\n          show: false,\n        },\n        name: '访问来源',\n        radius: ['40%', '65%'],\n        type: 'pie',\n      },\n    ],\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/dashboard/analytics/analytics-visits.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        barMaxWidth: 80,\n        // color: '#4f69fd',\n        data: [\n          3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,\n          3200, 4800,\n        ],\n        type: 'bar',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          // color: '#4f69fd',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    xAxis: {\n      data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),\n      type: 'category',\n    },\n    yAxis: {\n      max: 8000,\n      splitNumber: 4,\n      type: 'value',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/dashboard/analytics/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { AnalysisOverviewItem } from '@vben/common-ui';\nimport type { TabOption } from '@vben/types';\n\nimport {\n  AnalysisChartCard,\n  AnalysisChartsTabs,\n  AnalysisOverview,\n} from '@vben/common-ui';\nimport {\n  SvgBellIcon,\n  SvgCakeIcon,\n  SvgCardIcon,\n  SvgDownloadIcon,\n} from '@vben/icons';\n\nimport AnalyticsTrends from './analytics-trends.vue';\nimport AnalyticsVisitsData from './analytics-visits-data.vue';\nimport AnalyticsVisitsSales from './analytics-visits-sales.vue';\nimport AnalyticsVisitsSource from './analytics-visits-source.vue';\nimport AnalyticsVisits from './analytics-visits.vue';\n\nconst overviewItems: AnalysisOverviewItem[] = [\n  {\n    icon: SvgCardIcon,\n    title: '用户量',\n    totalTitle: '总用户量',\n    totalValue: 120_000,\n    value: 2000,\n  },\n  {\n    icon: SvgCakeIcon,\n    title: '访问量',\n    totalTitle: '总访问量',\n    totalValue: 500_000,\n    value: 20_000,\n  },\n  {\n    icon: SvgDownloadIcon,\n    title: '下载量',\n    totalTitle: '总下载量',\n    totalValue: 120_000,\n    value: 8000,\n  },\n  {\n    icon: SvgBellIcon,\n    title: '使用量',\n    totalTitle: '总使用量',\n    totalValue: 50_000,\n    value: 5000,\n  },\n];\n\nconst chartTabs: TabOption[] = [\n  {\n    label: '流量趋势',\n    value: 'trends',\n  },\n  {\n    label: '月访问量',\n    value: 'visits',\n  },\n];\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <AnalysisOverview :items=\"overviewItems\" />\n    <AnalysisChartsTabs :tabs=\"chartTabs\" class=\"mt-5\">\n      <template #trends>\n        <AnalyticsTrends />\n      </template>\n      <template #visits>\n        <AnalyticsVisits />\n      </template>\n    </AnalysisChartsTabs>\n\n    <div class=\"mt-5 w-full md:flex\">\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问数量\">\n        <AnalyticsVisitsData />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSource />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSales />\n      </AnalysisChartCard>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/dashboard/workspace/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  WorkbenchProjectItem,\n  WorkbenchQuickNavItem,\n  WorkbenchTodoItem,\n  WorkbenchTrendItem,\n} from '@vben/common-ui';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport {\n  AnalysisChartCard,\n  WorkbenchHeader,\n  WorkbenchProject,\n  WorkbenchQuickNav,\n  WorkbenchTodo,\n  WorkbenchTrends,\n} from '@vben/common-ui';\nimport { preferences } from '@vben/preferences';\nimport { useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';\n\nconst userStore = useUserStore();\n\n// 这是一个示例数据，实际项目中需要根据实际情况进行调整\n// url 也可以是内部路由，在 navTo 方法中识别处理，进行内部跳转\n// 例如：url: /dashboard/workspace\nconst projectItems: WorkbenchProjectItem[] = [\n  {\n    color: '',\n    content: '不要等待机会，而要创造机会。',\n    date: '2021-04-01',\n    group: '开源组',\n    icon: 'carbon:logo-github',\n    title: 'Github',\n    url: 'https://github.com',\n  },\n  {\n    color: '#3fb27f',\n    content: '现在的你决定将来的你。',\n    date: '2021-04-01',\n    group: '算法组',\n    icon: 'ion:logo-vue',\n    title: 'Vue',\n    url: 'https://vuejs.org',\n  },\n  {\n    color: '#e18525',\n    content: '没有什么才能比努力更重要。',\n    date: '2021-04-01',\n    group: '上班摸鱼',\n    icon: 'ion:logo-html5',\n    title: 'Html5',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',\n  },\n  {\n    color: '#bf0c2c',\n    content: '热情和欲望可以突破一切难关。',\n    date: '2021-04-01',\n    group: 'UI',\n    icon: 'ion:logo-angular',\n    title: 'Angular',\n    url: 'https://angular.io',\n  },\n  {\n    color: '#00d8ff',\n    content: '健康的身体是实现目标的基石。',\n    date: '2021-04-01',\n    group: '技术牛',\n    icon: 'bx:bxl-react',\n    title: 'React',\n    url: 'https://reactjs.org',\n  },\n  {\n    color: '#EBD94E',\n    content: '路是走出来的，而不是空想出来的。',\n    date: '2021-04-01',\n    group: '架构组',\n    icon: 'ion:logo-javascript',\n    title: 'Js',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',\n  },\n];\n\n// 同样，这里的 url 也可以使用以 http 开头的外部链接\nconst quickNavItems: WorkbenchQuickNavItem[] = [\n  {\n    color: '#1fdaca',\n    icon: 'ion:home-outline',\n    title: '首页',\n    url: '/',\n  },\n  {\n    color: '#bf0c2c',\n    icon: 'ion:grid-outline',\n    title: '仪表盘',\n    url: '/dashboard',\n  },\n  {\n    color: '#e18525',\n    icon: 'ion:layers-outline',\n    title: '组件',\n    url: '/demos/features/icons',\n  },\n  {\n    color: '#3fb27f',\n    icon: 'ion:settings-outline',\n    title: '系统管理',\n    url: '/demos/features/login-expired', // 这里的 URL 是示例，实际项目中需要根据实际情况进行调整\n  },\n  {\n    color: '#4daf1bc9',\n    icon: 'ion:key-outline',\n    title: '权限管理',\n    url: '/demos/access/page-control',\n  },\n  {\n    color: '#00d8ff',\n    icon: 'ion:bar-chart-outline',\n    title: '图表',\n    url: '/analytics',\n  },\n];\n\nconst todoItems = ref<WorkbenchTodoItem[]>([\n  {\n    completed: false,\n    content: `审查最近提交到Git仓库的前端代码，确保代码质量和规范。`,\n    date: '2024-07-30 11:00:00',\n    title: '审查前端代码提交',\n  },\n  {\n    completed: true,\n    content: `检查并优化系统性能，降低CPU使用率。`,\n    date: '2024-07-30 11:00:00',\n    title: '系统性能优化',\n  },\n  {\n    completed: false,\n    content: `进行系统安全检查，确保没有安全漏洞或未授权的访问。 `,\n    date: '2024-07-30 11:00:00',\n    title: '安全检查',\n  },\n  {\n    completed: false,\n    content: `更新项目中的所有npm依赖包，确保使用最新版本。`,\n    date: '2024-07-30 11:00:00',\n    title: '更新项目依赖',\n  },\n  {\n    completed: false,\n    content: `修复用户报告的页面UI显示问题，确保在不同浏览器中显示一致。 `,\n    date: '2024-07-30 11:00:00',\n    title: '修复UI显示问题',\n  },\n]);\nconst trendItems: WorkbenchTrendItem[] = [\n  {\n    avatar: 'svg:avatar-1',\n    content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,\n    date: '刚刚',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关注了 <a>威廉</a> `,\n    date: '1个小时前',\n    title: '艾文',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1天前',\n    title: '克里斯',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写一个Vite插件</a> `,\n    date: '2天前',\n    title: 'Vben',\n  },\n  {\n    avatar: 'svg:avatar-1',\n    content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化？</a>`,\n    date: '3天前',\n    title: '皮特',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关闭了问题 <a>如何运行项目</a> `,\n    date: '1周前',\n    title: '杰克',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1周前',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `推送了代码到 <a>Github</a>`,\n    date: '2021-04-01 20:00',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写使用 Admin Vben</a> `,\n    date: '2021-03-01 20:00',\n    title: 'Vben',\n  },\n];\n\nconst router = useRouter();\n\n// 这是一个示例方法，实际项目中需要根据实际情况进行调整\n// This is a sample method, adjust according to the actual project requirements\nfunction navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {\n  if (nav.url?.startsWith('http')) {\n    openWindow(nav.url);\n    return;\n  }\n  if (nav.url?.startsWith('/')) {\n    router.push(nav.url).catch((error) => {\n      console.error('Navigation failed:', error);\n    });\n  } else {\n    console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);\n  }\n}\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <WorkbenchHeader\n      :avatar=\"userStore.userInfo?.avatar || preferences.app.defaultAvatar\"\n    >\n      <template #title>\n        早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧！\n      </template>\n      <template #description> 今日晴，20℃ - 32℃！ </template>\n    </WorkbenchHeader>\n\n    <div class=\"mt-5 flex flex-col lg:flex-row\">\n      <div class=\"mr-4 w-full lg:w-3/5\">\n        <WorkbenchProject :items=\"projectItems\" title=\"项目\" @click=\"navTo\" />\n        <WorkbenchTrends :items=\"trendItems\" class=\"mt-5\" title=\"最新动态\" />\n      </div>\n      <div class=\"w-full lg:w-2/5\">\n        <WorkbenchQuickNav\n          :items=\"quickNavItems\"\n          class=\"mt-5 lg:mt-0\"\n          title=\"快捷导航\"\n          @click=\"navTo\"\n        />\n        <WorkbenchTodo :items=\"todoItems\" class=\"mt-5\" title=\"待办事项\" />\n        <AnalysisChartCard class=\"mt-5\" title=\"访问来源\">\n          <AnalyticsVisitsSource />\n        </AnalysisChartCard>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/src/views/demos/tdesign/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Page } from '@vben/common-ui';\n\nimport { Button, Card, Space } from 'tdesign-vue-next';\n\nimport { message, notification } from '#/adapter/tdesign';\n\ntype NotificationType = 'error' | 'info' | 'success' | 'warning';\n\nfunction info() {\n  message.info('How many roads must a man walk down');\n}\n\nfunction error() {\n  message.error({\n    content: 'Once upon a time you dressed so fine',\n    duration: 2500,\n  });\n}\n\nfunction warning() {\n  message.warning('How many roads must a man walk down');\n}\nfunction success() {\n  message.success('Cause you walked hand in hand With another man in my place');\n}\n\nfunction notify(type: NotificationType) {\n  notification[type]({\n    duration: 2500,\n    title: '说点啥呢',\n  });\n}\n</script>\n\n<template>\n  <Page\n    description=\"支持多语言，主题功能集成切换等\"\n    title=\"TDesign Vue组件使用演示\"\n  >\n    <Card class=\"mb-5!\" title=\"按钮\">\n      <Space>\n        <Button>Default</Button>\n        <Button theme=\"primary\"> Primary </Button>\n        <Button theme=\"default\"> Info </Button>\n        <Button theme=\"danger\"> Error </Button>\n      </Space>\n    </Card>\n    <Card class=\"mb-5!\" title=\"Message\">\n      <Space>\n        <Button @click=\"info\"> 信息 </Button>\n        <Button theme=\"danger\" @click=\"error\"> 错误 </Button>\n        <Button @click=\"warning\"> 警告 </Button>\n        <Button @click=\"success\"> 成功 </Button>\n      </Space>\n    </Card>\n\n    <Card class=\"mb-5!\" title=\"Notification\">\n      <Space>\n        <Button @click=\"notify('info')\"> 信息 </Button>\n        <Button theme=\"danger\" @click=\"notify('error')\"> 错误 </Button>\n        <Button @click=\"notify('warning')\"> 警告 </Button>\n        <Button @click=\"notify('success')\"> 成功 </Button>\n      </Space>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "apps/web-tdesign/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web-app.json\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"#/*\": [\"./src/*\"]\n    }\n  },\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }],\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\", \"src/**/*.vue\"]\n}\n"
  },
  {
    "path": "apps/web-tdesign/tsconfig.node.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"noEmit\": false\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "apps/web-tdesign/vite.config.ts",
    "content": "import { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    application: {},\n    vite: {\n      server: {\n        proxy: {\n          '/api': {\n            changeOrigin: true,\n            rewrite: (path) => path.replace(/^\\/api/, ''),\n            // mock代理目标地址\n            target: 'http://localhost:5320/api',\n            ws: true,\n          },\n        },\n      },\n    },\n  };\n});\n"
  },
  {
    "path": "cspell.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json\",\n  \"version\": \"0.2\",\n  \"language\": \"en,en-US\",\n  \"allowCompoundWords\": true,\n  \"words\": [\n    \"acmr\",\n    \"antd\",\n    \"antdv\",\n    \"archiver\",\n    \"astro\",\n    \"axios\",\n    \"brotli\",\n    \"cascader\",\n    \"chatcmpl\",\n    \"clsx\",\n    \"dedup\",\n    \"defu\",\n    \"demi\",\n    \"dotenv\",\n    \"echart\",\n    \"echarts\",\n    \"ependencies\",\n    \"esbuild\",\n    \"esno\",\n    \"etag\",\n    \"execa\",\n    \"iconify\",\n    \"iconoir\",\n    \"intlify\",\n    \"isequal\",\n    \"jspm\",\n    \"lockb\",\n    \"lucide\",\n    \"minh\",\n    \"minw\",\n    \"mkdist\",\n    \"mockjs\",\n    \"naiveui\",\n    \"napi\",\n    \"nocheck\",\n    \"nolebase\",\n    \"noopener\",\n    \"noreferrer\",\n    \"nprogress\",\n    \"nuxt\",\n    \"organisation\",\n    \"oxfmt\",\n    \"oxlint\",\n    \"pinia\",\n    \"prefixs\",\n    \"publint\",\n    \"qrcode\",\n    \"reka\",\n    \"rollup\",\n    \"shadcn\",\n    \"sonner\",\n    \"sortablejs\",\n    \"styl\",\n    \"tabler\",\n    \"taze\",\n    \"tdesign\",\n    \"tsdown\",\n    \"tsgolint\",\n    \"turborepo\",\n    \"ui-kit\",\n    \"uicons\",\n    \"unplugin\",\n    \"unref\",\n    \"vben\",\n    \"vbenjs\",\n    \"vite\",\n    \"vitejs\",\n    \"vitepress\",\n    \"vitest\",\n    \"vnode\",\n    \"vueuse\",\n    \"yxxx\"\n  ],\n  \"ignorePaths\": [\n    \"**/*-dist/**\",\n    \"**/*.log\",\n    \"**/*.spec.ts\",\n    \"**/*.test.ts\",\n    \"**/__tests__/**\",\n    \"**/dist/**\",\n    \"**/icons/**\",\n    \"**/node_modules/**\",\n    \"pnpm-lock.yaml\"\n  ]\n}\n"
  },
  {
    "path": "docs/.vitepress/components/demo-preview.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport PreviewGroup from './preview-group.vue';\n\ninterface Props {\n  files?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), { files: '() => []' });\n\nconst parsedFiles = computed(() => {\n  try {\n    return JSON.parse(decodeURIComponent(props.files ?? ''));\n  } catch {\n    return [];\n  }\n});\n</script>\n\n<template>\n  <div class=\"relative rounded-xl border border-border shadow-float\">\n    <div\n      class=\"not-prose relative w-full overflow-x-auto rounded-t-lg px-4 py-6\"\n    >\n      <div class=\"flex w-full max-w-[700px] px-2\">\n        <ClientOnly>\n          <slot v-if=\"parsedFiles.length > 0\"></slot>\n          <div v-else class=\"text-sm text-destructive\">\n            <span class=\"rounded-sm bg-destructive p-1 text-foreground\">\n              ERROR:\n            </span>\n            The preview directory does not exist. Please check the 'dir'\n            parameter.\n          </div>\n        </ClientOnly>\n      </div>\n    </div>\n    <PreviewGroup v-if=\"parsedFiles.length > 0\" :files=\"parsedFiles\">\n      <template v-for=\"file in parsedFiles\" #[file]>\n        <slot :name=\"file\"></slot>\n      </template>\n    </PreviewGroup>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/components/index.ts",
    "content": "export { default as DemoPreview } from './demo-preview.vue';\n"
  },
  {
    "path": "docs/.vitepress/components/preview-group.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SetupContext } from 'vue';\n\nimport { computed, ref, useSlots } from 'vue';\n\nimport { VbenTooltip } from '@vben-core/shadcn-ui';\n\nimport { Code } from 'lucide-vue-next';\nimport {\n  TabsContent,\n  TabsIndicator,\n  TabsList,\n  TabsRoot,\n  TabsTrigger,\n} from 'reka-ui';\n\ndefineOptions({\n  inheritAttrs: false,\n});\n\nconst props = withDefaults(\n  defineProps<{\n    files?: string[];\n  }>(),\n  { files: () => [] },\n);\n\nconst open = ref(false);\n\nconst slots: SetupContext['slots'] = useSlots();\n\nconst tabs = computed(() => {\n  return props.files.map((file) => {\n    return {\n      component: slots[file],\n      label: file,\n    };\n  });\n});\n\nconst currentTab = ref('index.vue');\n\nconst toggleOpen = () => {\n  open.value = !open.value;\n};\n</script>\n\n<template>\n  <TabsRoot\n    v-model=\"currentTab\"\n    class=\"overflow-hidden rounded-b-xl border-t border-border bg-background-deep\"\n    @update:model-value=\"open = true\"\n  >\n    <div class=\"flex border-b-2 border-border bg-background pr-2\">\n      <div class=\"flex w-full items-center justify-between text-[13px]\">\n        <TabsList class=\"relative flex\">\n          <template v-if=\"open\">\n            <TabsIndicator\n              class=\"absolute bottom-0 left-0 h-[2px] w-(--reka-tabs-indicator-size) translate-x-(--reka-tabs-indicator-position) rounded-full transition-[width,transform] duration-300\"\n            >\n              <div class=\"size-full bg-(--vp-c-indigo-1)\"></div>\n            </TabsIndicator>\n            <TabsTrigger\n              v-for=\"(tab, index) in tabs\"\n              :key=\"index\"\n              :value=\"tab.label\"\n              class=\"border-box px-4 py-3 text-foreground data-[state=active]:text-(--vp-c-indigo-1)\"\n              tabindex=\"-1\"\n            >\n              {{ tab.label }}\n            </TabsTrigger>\n          </template>\n        </TabsList>\n\n        <div\n          :class=\"{\n            'py-2': !open,\n          }\"\n          class=\"flex items-center\"\n        >\n          <VbenTooltip side=\"top\">\n            <template #trigger>\n              <Code\n                class=\"size-7 cursor-pointer rounded-full p-1.5 hover:bg-accent\"\n                @click=\"toggleOpen\"\n              />\n            </template>\n            {{ open ? 'Collapse code' : 'Expand code' }}\n          </VbenTooltip>\n        </div>\n      </div>\n    </div>\n    <div\n      :class=\"`${open ? 'h-[unset] max-h-[80vh]' : 'h-0'}`\"\n      class=\"block overflow-y-scroll bg-(--vp-code-block-bg) transition-all duration-300\"\n    >\n      <TabsContent\n        v-for=\"tab in tabs\"\n        :key=\"tab.label\"\n        :value=\"tab.label\"\n        as-child\n        class=\"rounded-xl\"\n      >\n        <div class=\"relative rounded-xl text-foreground\">\n          <component :is=\"tab.component\" class=\"border-0\" />\n        </div>\n      </TabsContent>\n    </div>\n  </TabsRoot>\n</template>\n"
  },
  {
    "path": "docs/.vitepress/config/en.mts",
    "content": "import type { DefaultTheme } from 'vitepress';\n\nimport { defineConfig } from 'vitepress';\n\nimport { version } from '../../../package.json';\n\nexport const en = defineConfig({\n  description: 'Vben Admin & Enterprise level management system framework',\n  lang: 'en-US',\n  themeConfig: {\n    darkModeSwitchLabel: 'Theme',\n    darkModeSwitchTitle: 'Switch to Dark Mode',\n    docFooter: {\n      next: 'Next Page',\n      prev: 'Previous Page',\n    },\n    editLink: {\n      pattern:\n        'https://github.com/vbenjs/vue-vben-admin/edit/main/docs/src/:path',\n      text: 'Edit this page on GitHub',\n    },\n    footer: {\n      copyright: `Copyright © 2020-${new Date().getFullYear()} Vben`,\n      message: 'Released under the MIT License.',\n    },\n    langMenuLabel: 'Language',\n    lastUpdated: {\n      formatOptions: {\n        dateStyle: 'short',\n        timeStyle: 'medium',\n      },\n      text: 'Last updated on',\n    },\n    lightModeSwitchTitle: 'Switch to Light Mode',\n    nav: nav(),\n    outline: {\n      label: 'Navigate',\n    },\n    returnToTopLabel: 'Back to top',\n    sidebar: {\n      '/en/commercial/': {\n        base: '/en/commercial/',\n        items: sidebarCommercial(),\n      },\n      '/en/components/': {\n        base: '/en/components/',\n        items: sidebarComponents(),\n      },\n      '/en/guide/': { base: '/en/guide/', items: sidebarGuide() },\n    },\n  },\n});\n\nfunction sidebarGuide(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      collapsed: false,\n      text: 'Introduction',\n      items: [\n        {\n          link: 'introduction/vben',\n          text: 'About Vben Admin',\n        },\n        {\n          link: 'introduction/why',\n          text: 'Why Choose Us?',\n        },\n        { link: 'introduction/quick-start', text: 'Quick Start' },\n        { link: 'introduction/thin', text: 'Lite Version' },\n        {\n          base: '/',\n          link: 'components/introduction',\n          text: 'Components',\n        },\n      ],\n    },\n    {\n      text: 'Basics',\n      items: [\n        { link: 'essentials/concept', text: 'Basic Concepts' },\n        { link: 'essentials/development', text: 'Local Development' },\n        { link: 'essentials/route', text: 'Routing and Menu' },\n        { link: 'essentials/settings', text: 'Configuration' },\n        { link: 'essentials/icons', text: 'Icons' },\n        { link: 'essentials/styles', text: 'Styles' },\n        { link: 'essentials/external-module', text: 'External Modules' },\n        { link: 'essentials/build', text: 'Build and Deployment' },\n        { link: 'essentials/server', text: 'Server Interaction and Data Mock' },\n      ],\n    },\n    {\n      text: 'Advanced',\n      items: [\n        { link: 'in-depth/login', text: 'Login' },\n        { link: 'in-depth/theme', text: 'Theme' },\n        { link: 'in-depth/access', text: 'Access Control' },\n        { link: 'in-depth/locale', text: 'Internationalization' },\n        { link: 'in-depth/features', text: 'Common Features' },\n        { link: 'in-depth/check-updates', text: 'Check Updates' },\n        { link: 'in-depth/loading', text: 'Global Loading' },\n        { link: 'in-depth/ui-framework', text: 'UI Framework Switching' },\n      ],\n    },\n    {\n      text: 'Engineering',\n      items: [\n        { link: 'project/standard', text: 'Standards' },\n        { link: 'project/cli', text: 'CLI' },\n        { link: 'project/dir', text: 'Directory Explanation' },\n        { link: 'project/test', text: 'Unit Testing' },\n        { link: 'project/tailwindcss', text: 'Tailwind CSS' },\n        { link: 'project/changeset', text: 'Changeset' },\n        { link: 'project/vite', text: 'Vite Config' },\n      ],\n    },\n    {\n      text: 'Others',\n      items: [\n        { link: 'other/project-update', text: 'Project Update' },\n        { link: 'other/remove-code', text: 'Remove Code' },\n        { link: 'other/faq', text: 'FAQ' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarCommercial(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      link: 'community',\n      text: 'Community',\n    },\n    {\n      link: 'technical-support',\n      text: 'Technical-support',\n    },\n    {\n      link: 'customized',\n      text: 'Customized',\n    },\n  ];\n}\n\nfunction sidebarComponents(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: 'Components',\n      items: [\n        {\n          link: 'introduction',\n          text: 'Introduction',\n        },\n      ],\n    },\n    {\n      collapsed: false,\n      text: 'Layout UI',\n      items: [\n        {\n          link: 'layout-ui/page',\n          text: 'Page',\n        },\n      ],\n    },\n    {\n      collapsed: false,\n      text: 'Common UI',\n      items: [\n        {\n          link: 'common-ui/vben-api-component',\n          text: 'ApiComponent',\n        },\n        {\n          link: 'common-ui/vben-alert',\n          text: 'Alert',\n        },\n        {\n          link: 'common-ui/vben-modal',\n          text: 'Modal',\n        },\n        {\n          link: 'common-ui/vben-drawer',\n          text: 'Drawer',\n        },\n        {\n          link: 'common-ui/vben-form',\n          text: 'Form',\n        },\n        {\n          link: 'common-ui/vben-vxe-table',\n          text: 'Vxe Table',\n        },\n        {\n          link: 'common-ui/vben-count-to-animator',\n          text: 'CountToAnimator',\n        },\n        {\n          link: 'common-ui/vben-ellipsis-text',\n          text: 'EllipsisText',\n        },\n      ],\n    },\n  ];\n}\n\nfunction nav(): DefaultTheme.NavItem[] {\n  return [\n    {\n      activeMatch: '^/en/(guide|components)/',\n      text: 'Doc',\n      items: [\n        {\n          activeMatch: '^/en/guide/',\n          link: '/en/guide/introduction/vben',\n          text: 'Guide',\n        },\n        {\n          activeMatch: '^/en/components/',\n          link: '/en/components/introduction',\n          text: 'Components',\n        },\n        {\n          text: 'Historical Versions',\n          items: [\n            {\n              link: 'https://doc.vvbin.cn',\n              text: '2.x Version Documentation',\n            },\n          ],\n        },\n      ],\n    },\n    {\n      text: 'Demo',\n      items: [\n        {\n          text: 'Vben Admin',\n          items: [\n            {\n              link: 'https://www.vben.pro',\n              text: 'Demo Version',\n            },\n            {\n              link: 'https://ant.vben.pro',\n              text: 'Ant Design Vue Version',\n            },\n            {\n              link: 'https://naive.vben.pro',\n              text: 'Naive Version',\n            },\n            {\n              link: 'https://ele.vben.pro',\n              text: 'Element Plus Version',\n            },\n          ],\n        },\n        {\n          text: 'Others',\n          items: [\n            {\n              link: 'https://vben.vvbin.cn',\n              text: 'Vben Admin 2.x',\n            },\n          ],\n        },\n      ],\n    },\n    {\n      text: version,\n      items: [\n        {\n          link: 'https://github.com/vbenjs/vue-vben-admin/releases',\n          text: 'Changelog',\n        },\n        {\n          link: 'https://github.com/orgs/vbenjs/projects/5',\n          text: 'Roadmap',\n        },\n        {\n          link: 'https://github.com/vbenjs/vue-vben-admin/blob/main/.github/contributing.md',\n          text: 'Contribution',\n        },\n      ],\n    },\n    {\n      link: '/commercial/technical-support',\n      text: '🦄 Tech Support',\n    },\n    {\n      link: '/sponsor/personal',\n      text: '✨ Sponsor',\n    },\n    {\n      link: '/commercial/community',\n      text: '👨‍👦‍👦 Community',\n    },\n    // {\n    //   link: '/friend-links/',\n    //   text: '🤝 Friend Links',\n    // },\n  ];\n}\n"
  },
  {
    "path": "docs/.vitepress/config/index.mts",
    "content": "import { withPwa } from '@vite-pwa/vitepress';\nimport { defineConfigWithTheme } from 'vitepress';\n\nimport { en } from './en.mts';\nimport { shared } from './shared.mts';\nimport { zh } from './zh.mts';\n\nexport default withPwa(\n  defineConfigWithTheme({\n    ...shared,\n    locales: {\n      en: {\n        label: 'English',\n        lang: 'en',\n        link: '/en/',\n        ...en,\n      },\n      root: {\n        label: '简体中文',\n        lang: 'zh-CN',\n        ...zh,\n      },\n    },\n  }),\n);\n"
  },
  {
    "path": "docs/.vitepress/config/plugins/demo-preview.ts",
    "content": "import type { MarkdownEnv, MarkdownRenderer } from 'vitepress';\n\nimport crypto from 'node:crypto';\nimport { readdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport const rawPathRegexp =\n  /^(.+?(?:\\.([\\da-z]+))?)(#[\\w-]+)?(?: ?{(\\d+(?:[,-]\\d+)*)? ?(\\S+)?})? ?(?:\\[(.+)])?$/;\n\nfunction rawPathToToken(rawPath: string) {\n  const [\n    filepath = '',\n    extension = '',\n    region = '',\n    lines = '',\n    lang = '',\n    rawTitle = '',\n  ] = (rawPathRegexp.exec(rawPath) || []).slice(1);\n\n  const title = rawTitle || filepath.split('/').pop() || '';\n\n  return { extension, filepath, lang, lines, region, title };\n}\n\nexport const demoPreviewPlugin = (md: MarkdownRenderer) => {\n  md.core.ruler.after('inline', 'demo-preview', (state) => {\n    const insertComponentImport = (importString: string) => {\n      const index = state.tokens.findIndex(\n        (i) => i.type === 'html_block' && i.content.match(/<script setup>/g),\n      );\n      if (index === -1) {\n        const importComponent = new state.Token('html_block', '', 0);\n        importComponent.content = `<script setup>\\n${importString}\\n</script>\\n`;\n        state.tokens.splice(0, 0, importComponent);\n      } else {\n        if (state.tokens[index]) {\n          const content = state.tokens[index].content;\n          state.tokens[index].content = content.replace(\n            '</script>',\n            `${importString}\\n</script>`,\n          );\n        }\n      }\n    };\n    // Define the regular expression to match the desired pattern\n    const regex = /<DemoPreview[^>]*\\sdir=\"([^\"]*)\"/g;\n    // Iterate through the Markdown content and replace the pattern\n    state.src = state.src.replaceAll(regex, (_match, dir) => {\n      const componentDir = join(process.cwd(), 'src', dir).replaceAll(\n        '\\\\',\n        '/',\n      );\n\n      let childFiles: string[] = [];\n      let dirExists = true;\n\n      try {\n        childFiles =\n          readdirSync(componentDir, {\n            encoding: 'utf8',\n            recursive: false,\n            withFileTypes: false,\n          }) || [];\n      } catch {\n        dirExists = false;\n      }\n\n      if (!dirExists) {\n        return '';\n      }\n\n      const uniqueWord = generateContentHash(componentDir);\n\n      const ComponentName = `DemoComponent_${uniqueWord}`;\n      insertComponentImport(\n        `import ${ComponentName} from '${componentDir}/index.vue'`,\n      );\n      const { path: _path } = state.env as MarkdownEnv;\n\n      const index = state.tokens.findIndex((i) => i.content.match(regex));\n\n      if (!state.tokens[index]) {\n        return '';\n      }\n      const firstString = 'index.vue';\n      childFiles = childFiles.toSorted((a, b) => {\n        if (a === firstString) return -1;\n        if (b === firstString) return 1;\n        return a.localeCompare(b, 'en', { sensitivity: 'base' });\n      });\n      state.tokens[index].content =\n        `<DemoPreview files=\"${encodeURIComponent(JSON.stringify(childFiles))}\" ><${ComponentName}/>\n        `;\n\n      const _dummyToken = new state.Token('', '', 0);\n      const tokenArray: Array<typeof _dummyToken> = [];\n      childFiles.forEach((filename) => {\n        // const slotName = filename.replace(extname(filename), '');\n\n        const templateStart = new state.Token('html_inline', '', 0);\n        templateStart.content = `<template #${filename}>`;\n        tokenArray.push(templateStart);\n\n        const resolvedPath = join(componentDir, filename);\n\n        const { extension, filepath, lang, lines, title } =\n          rawPathToToken(resolvedPath);\n        // Add code tokens for each line\n        const token = new state.Token('fence', 'code', 0);\n        token.info = `${lang || extension}${lines ? `{${lines}}` : ''}${\n          title ? `[${title}]` : ''\n        }`;\n\n        token.content = `<<< ${filepath}`;\n        (token as any).src = [resolvedPath];\n        tokenArray.push(token);\n\n        const templateEnd = new state.Token('html_inline', '', 0);\n        templateEnd.content = '</template>';\n        tokenArray.push(templateEnd);\n      });\n      const endTag = new state.Token('html_inline', '', 0);\n      endTag.content = '</DemoPreview>';\n      tokenArray.push(endTag);\n\n      state.tokens.splice(index + 1, 0, ...tokenArray);\n\n      // console.log(\n      //   state.md.renderer.render(state.tokens, state?.options ?? [], state.env),\n      // );\n      return '';\n    });\n  });\n};\n\nfunction generateContentHash(input: string, length: number = 10): string {\n  // 使用 SHA-256 生成哈希值\n  const hash = crypto.createHash('sha256').update(input).digest('hex');\n\n  // 将哈希值转换为 Base36 编码，并取指定长度的字符作为结果\n  return Number.parseInt(hash, 16).toString(36).slice(0, length);\n}\n"
  },
  {
    "path": "docs/.vitepress/config/shared.mts",
    "content": "import type { PwaOptions } from '@vite-pwa/vitepress';\nimport type { HeadConfig } from 'vitepress';\n\nimport { resolve } from 'node:path';\n\nimport {\n  viteArchiverPlugin,\n  viteVxeTableImportsPlugin,\n} from '@vben/vite-config';\n\nimport {\n  GitChangelog,\n  GitChangelogMarkdownSection,\n} from '@nolebase/vitepress-plugin-git-changelog/vite';\nimport tailwindcss from '@tailwindcss/vite';\nimport { defineConfig, postcssIsolateStyles } from 'vitepress';\nimport {\n  groupIconMdPlugin,\n  groupIconVitePlugin,\n} from 'vitepress-plugin-group-icons';\n\nimport { demoPreviewPlugin } from './plugins/demo-preview';\nimport { search as zhSearch } from './zh.mts';\n\nexport const shared = defineConfig({\n  appearance: 'dark',\n  head: head(),\n  markdown: {\n    preConfig(md) {\n      md.use(demoPreviewPlugin);\n      md.use(groupIconMdPlugin);\n    },\n  },\n  pwa: pwa(),\n  srcDir: 'src',\n  themeConfig: {\n    i18nRouting: true,\n    logo: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',\n    search: {\n      options: {\n        locales: {\n          ...zhSearch,\n        },\n      },\n      provider: 'local',\n    },\n    siteTitle: 'Vben Admin',\n    socialLinks: [\n      { icon: 'github', link: 'https://github.com/vbenjs/vue-vben-admin' },\n    ],\n  },\n  title: 'Vben Admin',\n  vite: {\n    build: {\n      chunkSizeWarningLimit: Infinity,\n      minify: 'terser',\n    },\n    css: {\n      postcss: {\n        plugins: [postcssIsolateStyles({ includeFiles: [/vp-doc\\.css/] })],\n      },\n      preprocessorOptions: {\n        scss: {\n          api: 'modern',\n        },\n      },\n    },\n    json: {\n      stringify: true,\n    },\n    plugins: [\n      tailwindcss(),\n      GitChangelog({\n        mapAuthors: [\n          {\n            mapByNameAliases: ['Vben'],\n            name: 'vben',\n            username: 'anncwb',\n          },\n          {\n            name: 'vince',\n            username: 'vince292007',\n          },\n          {\n            name: 'Li Kui',\n            username: 'likui628',\n          },\n        ],\n        repoURL: () => 'https://github.com/vbenjs/vue-vben-admin',\n      }),\n      GitChangelogMarkdownSection(),\n      viteArchiverPlugin({ outputDir: '.vitepress' }),\n      groupIconVitePlugin(),\n      await viteVxeTableImportsPlugin(),\n    ],\n    server: {\n      fs: {\n        allow: ['../..'],\n      },\n      host: true,\n      port: 6173,\n    },\n\n    ssr: {\n      external: ['@vue/repl'],\n    },\n  },\n});\n\nfunction head(): HeadConfig[] {\n  return [\n    ['meta', { content: 'Vbenjs Team', name: 'author' }],\n    [\n      'meta',\n      {\n        content: 'vben, vitejs, vite, shacdn-ui, vue',\n        name: 'keywords',\n      },\n    ],\n    ['link', { href: '/favicon.ico', rel: 'icon', type: 'image/svg+xml' }],\n    [\n      'meta',\n      {\n        content:\n          'width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no',\n        name: 'viewport',\n      },\n    ],\n    ['meta', { content: 'vben admin docs', name: 'keywords' }],\n    ['link', { href: '/favicon.ico', rel: 'icon' }],\n    // [\n    //   'script',\n    //   {\n    //     src: 'https://cdn.tailwindcss.com',\n    //   },\n    // ],\n  ];\n}\n\nfunction pwa(): PwaOptions {\n  return {\n    includeManifestIcons: false,\n    manifest: {\n      description:\n        'Vben Admin is a modern admin dashboard template based on Vue 3. ',\n      icons: [\n        {\n          sizes: '192x192',\n          src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-192.png',\n          type: 'image/png',\n        },\n        {\n          sizes: '512x512',\n          src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-512.png',\n          type: 'image/png',\n        },\n      ],\n      id: '/',\n      name: 'Vben Admin Doc',\n      short_name: 'vben_admin_doc',\n      theme_color: '#ffffff',\n    },\n    outDir: resolve(process.cwd(), '.vitepress/dist'),\n    registerType: 'autoUpdate',\n    workbox: {\n      globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],\n      maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,\n    },\n  };\n}\n"
  },
  {
    "path": "docs/.vitepress/config/zh.mts",
    "content": "import type { DefaultTheme } from 'vitepress';\n\nimport { defineConfig } from 'vitepress';\n\nimport { version } from '../../../package.json';\n\nexport const zh = defineConfig({\n  description: 'Vben Admin & 企业级管理系统框架',\n  lang: 'zh-Hans',\n  themeConfig: {\n    darkModeSwitchLabel: '主题',\n    darkModeSwitchTitle: '切换到深色模式',\n    docFooter: {\n      next: '下一页',\n      prev: '上一页',\n    },\n    editLink: {\n      pattern:\n        'https://github.com/vbenjs/vue-vben-admin/edit/main/docs/src/:path',\n      text: '在 GitHub 上编辑此页面',\n    },\n    footer: {\n      copyright: `Copyright © 2020-${new Date().getFullYear()} Vben`,\n      message: '基于 MIT 许可发布.',\n    },\n    langMenuLabel: '多语言',\n    lastUpdated: {\n      formatOptions: {\n        dateStyle: 'short',\n        timeStyle: 'medium',\n      },\n      text: '最后更新于',\n    },\n    lightModeSwitchTitle: '切换到浅色模式',\n    nav: nav(),\n\n    outline: {\n      label: '页面导航',\n    },\n    returnToTopLabel: '回到顶部',\n\n    sidebar: {\n      '/commercial/': { base: '/commercial/', items: sidebarCommercial() },\n      '/components/': { base: '/components/', items: sidebarComponents() },\n      '/guide/': { base: '/guide/', items: sidebarGuide() },\n    },\n    sidebarMenuLabel: '菜单',\n  },\n});\n\nfunction sidebarGuide(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      collapsed: false,\n      text: '简介',\n      items: [\n        {\n          link: 'introduction/vben',\n          text: '关于 Vben Admin',\n        },\n        {\n          link: 'introduction/why',\n          text: '为什么选择我们?',\n        },\n        { link: 'introduction/quick-start', text: '快速开始' },\n        { link: 'introduction/thin', text: '精简版本' },\n        {\n          base: '/',\n          link: 'components/introduction',\n          text: '组件文档',\n        },\n      ],\n    },\n    {\n      text: '基础',\n      items: [\n        { link: 'essentials/concept', text: '基础概念' },\n        { link: 'essentials/development', text: '本地开发' },\n        { link: 'essentials/route', text: '路由和菜单' },\n        { link: 'essentials/settings', text: '配置' },\n        { link: 'essentials/icons', text: '图标' },\n        { link: 'essentials/styles', text: '样式' },\n        { link: 'essentials/external-module', text: '外部模块' },\n        { link: 'essentials/build', text: '构建与部署' },\n        { link: 'essentials/server', text: '服务端交互与数据Mock' },\n      ],\n    },\n    {\n      text: '深入',\n      items: [\n        { link: 'in-depth/login', text: '登录' },\n        // { link: 'in-depth/layout', text: '布局' },\n        { link: 'in-depth/theme', text: '主题' },\n        { link: 'in-depth/access', text: '权限' },\n        { link: 'in-depth/locale', text: '国际化' },\n        { link: 'in-depth/features', text: '常用功能' },\n        { link: 'in-depth/check-updates', text: '检查更新' },\n        { link: 'in-depth/loading', text: '全局loading' },\n        { link: 'in-depth/ui-framework', text: '组件库切换' },\n      ],\n    },\n    {\n      text: '工程',\n      items: [\n        { link: 'project/standard', text: '规范' },\n        { link: 'project/cli', text: 'CLI' },\n        { link: 'project/dir', text: '目录说明' },\n        { link: 'project/test', text: '单元测试' },\n        { link: 'project/tailwindcss', text: 'Tailwind CSS' },\n        { link: 'project/changeset', text: 'Changeset' },\n        { link: 'project/vite', text: 'Vite Config' },\n      ],\n    },\n    {\n      text: '其他',\n      items: [\n        { link: 'other/project-update', text: '项目更新' },\n        { link: 'other/remove-code', text: '移除代码' },\n        { link: 'other/faq', text: '常见问题' },\n      ],\n    },\n  ];\n}\n\nfunction sidebarCommercial(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      link: 'community',\n      text: '交流群',\n    },\n    {\n      link: 'technical-support',\n      text: '技术支持',\n    },\n    {\n      link: 'customized',\n      text: '定制开发',\n    },\n  ];\n}\n\nfunction sidebarComponents(): DefaultTheme.SidebarItem[] {\n  return [\n    {\n      text: '组件',\n      items: [\n        {\n          link: 'introduction',\n          text: '介绍',\n        },\n      ],\n    },\n    {\n      collapsed: false,\n      text: '布局组件',\n      items: [\n        {\n          link: 'layout-ui/page',\n          text: 'Page 页面',\n        },\n      ],\n    },\n    {\n      collapsed: false,\n      text: '通用组件',\n      items: [\n        {\n          link: 'common-ui/vben-api-component',\n          text: 'ApiComponent Api组件包装器',\n        },\n        {\n          link: 'common-ui/vben-alert',\n          text: 'Alert 轻量提示框',\n        },\n        {\n          link: 'common-ui/vben-modal',\n          text: 'Modal 模态框',\n        },\n        {\n          link: 'common-ui/vben-drawer',\n          text: 'Drawer 抽屉',\n        },\n        {\n          link: 'common-ui/vben-form',\n          text: 'Form 表单',\n        },\n        {\n          link: 'common-ui/vben-vxe-table',\n          text: 'Vxe Table 表格',\n        },\n        {\n          link: 'common-ui/vben-count-to-animator',\n          text: 'CountToAnimator 数字动画',\n        },\n        {\n          link: 'common-ui/vben-ellipsis-text',\n          text: 'EllipsisText 省略文本',\n        },\n      ],\n    },\n  ];\n}\n\nfunction nav(): DefaultTheme.NavItem[] {\n  return [\n    {\n      activeMatch: '^/(guide|components)/',\n      text: '文档',\n      items: [\n        {\n          activeMatch: '^/guide/',\n          link: '/guide/introduction/vben',\n          text: '指南',\n        },\n        {\n          activeMatch: '^/components/',\n          link: '/components/introduction',\n          text: '组件',\n        },\n        {\n          text: '历史版本',\n          items: [\n            {\n              link: 'https://doc.vvbin.cn',\n              text: '2.x版本文档',\n            },\n          ],\n        },\n      ],\n    },\n    {\n      text: '演示',\n      items: [\n        {\n          text: 'Vben Admin',\n          items: [\n            {\n              link: 'https://www.vben.pro',\n              text: '演示版本',\n            },\n            {\n              link: 'https://ant.vben.pro',\n              text: 'Ant Design Vue 版本',\n            },\n            {\n              link: 'https://naive.vben.pro',\n              text: 'Naive 版本',\n            },\n            {\n              link: 'https://ele.vben.pro',\n              text: 'Element Plus版本',\n            },\n          ],\n        },\n        {\n          text: '其他',\n          items: [\n            {\n              link: 'https://vben.vvbin.cn',\n              text: 'Vben Admin 2.x',\n            },\n          ],\n        },\n      ],\n    },\n    {\n      text: version,\n      items: [\n        {\n          link: 'https://github.com/vbenjs/vue-vben-admin/releases',\n          text: '更新日志',\n        },\n        {\n          link: 'https://github.com/orgs/vbenjs/projects/5',\n          text: '路线图',\n        },\n        {\n          link: 'https://github.com/vbenjs/vue-vben-admin/blob/main/.github/contributing.md',\n          text: '贡献',\n        },\n      ],\n    },\n    {\n      link: '/commercial/technical-support',\n      text: '🦄 技术支持',\n    },\n    {\n      link: '/sponsor/personal',\n      text: '✨ 赞助',\n    },\n    {\n      link: '/commercial/community',\n      text: '👨‍👦‍👦 交流群',\n      // items: [\n      //   {\n      //     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',\n      //     text: 'QQ频道',\n      //   },\n      //   {\n      //     link: 'https://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=mjZmlhgVzzUxvdxllB6C1vHpX8O8QRL0&authKey=DBdFbBwERmfaKY95JvRWqLCJIRGJAmKyZbrpzZ41EKDMZ5SR6MfbjOBaaNRN73fr&noverify=0&group_code=4286109',\n      //     text: 'QQ群',\n      //   },\n      //   {\n      //     link: 'https://discord.gg/VU62jTecad',\n      //     text: 'Discord',\n      //   },\n      // ],\n    },\n    // {\n    //   link: '/friend-links/',\n    //   text: '🤝 友情链接',\n    // },\n  ];\n}\n\nexport const search: DefaultTheme.AlgoliaSearchOptions['locales'] = {\n  root: {\n    placeholder: '搜索文档',\n    translations: {\n      button: {\n        buttonAriaLabel: '搜索文档',\n        buttonText: '搜索文档',\n      },\n      modal: {\n        errorScreen: {\n          helpText: '你可能需要检查你的网络连接',\n          titleText: '无法获取结果',\n        },\n        footer: {\n          closeText: '关闭',\n          navigateText: '切换',\n          searchByText: '搜索提供者',\n          selectText: '选择',\n        },\n        noResultsScreen: {\n          noResultsText: '无法找到相关结果',\n          reportMissingResultsLinkText: '点击反馈',\n          reportMissingResultsText: '你认为该查询应该有结果？',\n          suggestedQueryText: '你可以尝试查询',\n        },\n        searchBox: {\n          cancelButtonAriaLabel: '取消',\n          cancelButtonText: '取消',\n          resetButtonAriaLabel: '清除查询条件',\n          resetButtonTitle: '清除查询条件',\n        },\n        startScreen: {\n          favoriteSearchesTitle: '收藏',\n          noRecentSearchesText: '没有搜索历史',\n          recentSearchesTitle: '搜索历史',\n          removeFavoriteSearchButtonTitle: '从收藏中移除',\n          removeRecentSearchButtonTitle: '从搜索历史中移除',\n          saveRecentSearchButtonTitle: '保存至搜索历史',\n        },\n      },\n    },\n  },\n};\n"
  },
  {
    "path": "docs/.vitepress/theme/components/site-layout.vue",
    "content": "<script lang=\"ts\" setup>\nimport {\n  computed,\n  nextTick,\n  onBeforeUnmount,\n  onMounted,\n  ref,\n  watch,\n} from 'vue';\n\n// import { useAntdDesignTokens } from '@vben/hooks';\n// import { initPreferences } from '@vben/preferences';\nimport { ConfigProvider, theme } from 'ant-design-vue';\nimport mediumZoom from 'medium-zoom';\nimport { useRoute } from 'vitepress';\nimport DefaultTheme from 'vitepress/theme';\n\nconst { Layout } = DefaultTheme;\nconst route = useRoute();\n// const { tokens } = useAntdDesignTokens();\n\nconst initZoom = () => {\n  // mediumZoom('[data-zoomable]', { background: 'var(--vp-c-bg)' });\n  mediumZoom('.VPContent img', { background: 'var(--vp-c-bg)' });\n};\n\nconst isDark = ref(true);\n\nwatch(\n  () => route.path,\n  () => nextTick(() => initZoom()),\n);\n\n// initPreferences({\n//   namespace: 'docs',\n// });\n\nonMounted(() => {\n  initZoom();\n});\n\n// 使用该函数\nconst observer = watchDarkModeChange((dark) => {\n  isDark.value = dark;\n});\n\nonBeforeUnmount(() => {\n  observer?.disconnect();\n});\n\nfunction watchDarkModeChange(callback: (isDark: boolean) => void) {\n  if (typeof window === 'undefined') {\n    return;\n  }\n  const htmlElement = document.documentElement;\n\n  const observer = new MutationObserver(() => {\n    const isDark = htmlElement.classList.contains('dark');\n    callback(isDark);\n  });\n\n  observer.observe(htmlElement, {\n    attributeFilter: ['class'],\n    attributes: true,\n  });\n\n  const initialIsDark = htmlElement.classList.contains('dark');\n  callback(initialIsDark);\n\n  return observer;\n}\n\nconst tokenTheme = computed(() => {\n  const algorithm = isDark.value\n    ? [theme.darkAlgorithm]\n    : [theme.defaultAlgorithm];\n\n  return {\n    algorithm,\n    // token: tokens,\n  };\n});\n</script>\n\n<template>\n  <ConfigProvider :theme=\"tokenTheme\">\n    <Layout />\n  </ConfigProvider>\n</template>\n\n<style>\n.medium-zoom-overlay,\n.medium-zoom-image--opened {\n  z-index: 2147483647;\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/components/vben-contributors.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div class=\"vben-contributors vp-doc\">\n    <p>Contributors</p>\n    <a href=\"https://github.com/vbenjs/vue-vben-admin/graphs/contributors\">\n      <img\n        alt=\"Contributors\"\n        src=\"https://opencollective.com/vbenjs/contributors.svg?button=false\"\n      />\n    </a>\n  </div>\n</template>\n\n<style scoped>\n.vben-contributors {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  padding-top: 60px;\n\n  p {\n    margin-bottom: 50px;\n    font-size: 30px;\n    font-weight: 700;\n  }\n}\n</style>\n"
  },
  {
    "path": "docs/.vitepress/theme/index.ts",
    "content": "// https://vitepress.dev/guide/custom-theme\nimport type { EnhanceAppContext, Theme } from 'vitepress';\n\nimport { NolebaseGitChangelogPlugin } from '@nolebase/vitepress-plugin-git-changelog/client';\nimport DefaultTheme from 'vitepress/theme';\n\nimport { DemoPreview } from '../components';\nimport SiteLayout from './components/site-layout.vue';\nimport VbenContributors from './components/vben-contributors.vue';\nimport { initHmPlugin } from './plugins/hm';\n\nimport './styles';\n\nimport 'virtual:group-icons.css';\nimport '@nolebase/vitepress-plugin-git-changelog/client/style.css';\n\nexport default {\n  async enhanceApp(ctx: EnhanceAppContext) {\n    const { app } = ctx;\n    app.component('VbenContributors', VbenContributors);\n    app.component('DemoPreview', DemoPreview);\n    app.use(NolebaseGitChangelogPlugin);\n\n    // 百度统计\n    initHmPlugin();\n  },\n  extends: DefaultTheme,\n  Layout: SiteLayout,\n} satisfies Theme;\n"
  },
  {
    "path": "docs/.vitepress/theme/plugins/hm.ts",
    "content": "import { inBrowser } from 'vitepress';\n\nconst SITE_ID = '2e443a834727c065877c01d89921545e';\n\ndeclare global {\n  interface Window {\n    _hmt: any;\n  }\n}\n\nfunction registerAnalytics() {\n  window._hmt = window._hmt || [];\n  const script = document.createElement('script');\n  script.innerHTML = `var _hmt = _hmt || [];\n      (function() {\n        var hm = document.createElement(\"script\");\n        hm.src = \"https://hm.baidu.com/hm.js?${SITE_ID}\";\n        var s = document.getElementsByTagName(\"script\")[0];\n        s.parentNode.insertBefore(hm, s);\n      })()`;\n  document.querySelector('head')?.append(script);\n}\n\nexport function initHmPlugin() {\n  if (inBrowser && import.meta.env.PROD) {\n    registerAnalytics();\n  }\n}\n"
  },
  {
    "path": "docs/.vitepress/theme/styles/base.css",
    "content": "html.dark {\n  color-scheme: dark;\n}\n\n.dark .VPContent {\n  /* background-color: #14161a; */\n}\n\n.form-valid-error p {\n  margin: 0;\n}\n\n/* 顶部导航栏选中项样式 */\n.VPNavBarMenuLink,\n.VPNavBarMenuGroup {\n  border-bottom: 1px solid transparent;\n}\n\n.VPNavBarMenuLink.active,\n.VPNavBarMenuGroup.active {\n  border-bottom-color: var(--vp-c-brand-1);\n}\n"
  },
  {
    "path": "docs/.vitepress/theme/styles/index.ts",
    "content": "import '@vben/styles';\n\nimport './variables.css';\nimport './base.css';\n"
  },
  {
    "path": "docs/.vitepress/theme/styles/variables.css",
    "content": "/**\n * Customize default theme styling by overriding CSS variables:\n * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css\n */\n\n/**\n * Colors\n *\n * Each colors have exact same color scale system with 3 levels of solid\n * colors with different brightness, and 1 soft color.\n *\n * - `XXX-1`: The most solid color used mainly for colored text. It must\n *   satisfy the contrast ratio against when used on top of `XXX-soft`.\n *\n * - `XXX-2`: The color used mainly for hover state of the button.\n *\n * - `XXX-3`: The color for solid background, such as bg color of the button.\n *   It must satisfy the contrast ratio with pure white (#ffffff) text on\n *   top of it.\n *\n * - `XXX-soft`: The color used for subtle background such as custom container\n *   or badges. It must satisfy the contrast ratio when putting `XXX-1` colors\n *   on top of it.\n *\n *   The soft color must be semi transparent alpha channel. This is crucial\n *   because it allows adding multiple \"soft\" colors on top of each other\n *   to create a accent, such as when having inline code block inside\n *   custom containers.\n *\n * - `default`: The color used purely for subtle indication without any\n *   special meanings attched to it such as bg color for menu hover state.\n *\n * - `brand`: Used for primary brand colors, such as link text, button with\n *   brand theme, etc.\n *\n * - `tip`: Used to indicate useful information. The default theme uses the\n *   brand color for this by default.\n *\n * - `warning`: Used to indicate warning to the users. Used in custom\n *   container, badges, etc.\n *\n * - `danger`: Used to show error, or dangerous message to the users. Used\n *   in custom container, badges, etc.\n * -------------------------------------------------------------------------- */\n\n:root {\n  /* --vp-c-indigo-1: #4f69fd; */\n  --vp-c-default-1: var(--vp-c-gray-1);\n  --vp-c-default-2: var(--vp-c-gray-2);\n  --vp-c-default-3: var(--vp-c-gray-3);\n  --vp-c-default-soft: var(--vp-c-gray-soft);\n  --vp-c-brand-1: var(--vp-c-indigo-1);\n  --vp-c-brand-2: var(--vp-c-indigo-2);\n  --vp-c-brand-3: var(--vp-c-indigo-3);\n  --vp-c-brand-soft: var(--vp-c-indigo-soft);\n  --vp-c-tip-1: var(--vp-c-brand-1);\n  --vp-c-tip-2: var(--vp-c-brand-2);\n  --vp-c-tip-3: var(--vp-c-brand-3);\n  --vp-c-tip-soft: var(--vp-c-brand-soft);\n  --vp-c-warning-1: var(--vp-c-yellow-1);\n  --vp-c-warning-2: var(--vp-c-yellow-2);\n  --vp-c-warning-3: var(--vp-c-yellow-3);\n  --vp-c-warning-soft: var(--vp-c-yellow-soft);\n  --vp-c-danger-1: var(--vp-c-red-1);\n  --vp-c-danger-2: var(--vp-c-red-2);\n  --vp-c-danger-3: var(--vp-c-red-3);\n  --vp-c-danger-soft: var(--vp-c-red-soft);\n\n  /**\n * Component: Button\n * -------------------------------------------------------------------------- */\n\n  --vp-button-brand-border: transparent;\n  --vp-button-brand-text: var(--vp-c-white);\n  --vp-button-brand-bg: var(--vp-c-brand-3);\n  --vp-button-brand-hover-border: transparent;\n  --vp-button-brand-hover-text: var(--vp-c-white);\n  --vp-button-brand-hover-bg: var(--vp-c-brand-2);\n  --vp-button-brand-active-border: transparent;\n  --vp-button-brand-active-text: var(--vp-c-white);\n  --vp-button-brand-active-bg: var(--vp-c-brand-1);\n\n  /**\n * Component: Home\n * -------------------------------------------------------------------------- */\n\n  --vp-home-hero-name-color: transparent;\n  --vp-home-hero-name-background: linear-gradient(\n    120deg,\n    var(--vp-c-indigo-1) 30%,\n    #18cefe\n  );\n  --vp-home-hero-image-background-image: linear-gradient(\n    -45deg,\n    #18cefe 50%,\n    #c279ed 50%\n  );\n  --vp-home-hero-image-filter: blur(44px);\n\n  /**\n * Component: Custom Block\n * -------------------------------------------------------------------------- */\n  --vp-custom-block-tip-border: transparent;\n  --vp-custom-block-tip-text: var(--vp-c-text-1);\n  --vp-custom-block-tip-bg: var(--vp-c-brand-soft);\n  --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);\n\n  /**\n  * modal zIndex\n  */\n  --popup-z-index: 1000;\n}\n\n@media (min-width: 640px) {\n  :root {\n    --vp-home-hero-image-filter: blur(56px);\n  }\n}\n\n@media (min-width: 960px) {\n  :root {\n    --vp-home-hero-image-filter: blur(68px);\n  }\n}\n\n/**\n * Component: Algolia\n * -------------------------------------------------------------------------- */\n\n.DocSearch {\n  --docsearch-primary-color: var(--vp-c-brand-1) !important;\n}\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"@vben/docs\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"vitepress build\",\n    \"dev\": \"vitepress dev\",\n    \"docs:preview\": \"vitepress preview\"\n  },\n  \"imports\": {\n    \"#/*\": {\n      \"node\": \"./src/_env/node/*\",\n      \"default\": \"./src/_env/*\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/shadcn-ui\": \"workspace:*\",\n    \"@vben/common-ui\": \"workspace:*\",\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/plugins\": \"workspace:*\",\n    \"@vben/styles\": \"workspace:*\",\n    \"ant-design-vue\": \"catalog:\",\n    \"lucide-vue-next\": \"catalog:\",\n    \"medium-zoom\": \"catalog:\",\n    \"reka-ui\": \"catalog:\",\n    \"vitepress-plugin-group-icons\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@nolebase/vitepress-plugin-git-changelog\": \"catalog:\",\n    \"@tailwindcss/vite\": \"catalog:\",\n    \"@vben/tailwind-config\": \"workspace:*\",\n    \"@vben/vite-config\": \"workspace:*\",\n    \"@vite-pwa/vitepress\": \"catalog:\",\n    \"vitepress\": \"catalog:\",\n    \"vue\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "docs/src/_env/adapter/component.ts",
    "content": "/**\n * 通用组件共同的使用的基础组件，原先放在 adapter/form 内部，限制了使用范围，这里提取出来，方便其他地方使用\n * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,\n */\n\nimport type { Component, SetupContext } from 'vue';\n\nimport type { BaseFormComponentType } from '@vben/common-ui';\n\nimport { h } from 'vue';\n\nimport { globalShareState } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport {\n  AutoComplete,\n  Button,\n  Checkbox,\n  CheckboxGroup,\n  DatePicker,\n  Divider,\n  Input,\n  InputNumber,\n  InputPassword,\n  Mentions,\n  notification,\n  Radio,\n  RadioGroup,\n  RangePicker,\n  Rate,\n  Select,\n  Space,\n  Switch,\n  Textarea,\n  TimePicker,\n  TreeSelect,\n  Upload,\n} from 'ant-design-vue';\n\nconst withDefaultPlaceholder = <T extends Component>(\n  component: T,\n  type: 'input' | 'select',\n) => {\n  return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {\n    const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);\n    return h(component, { ...props, ...attrs, placeholder }, slots);\n  };\n};\n\n// 这里需要自行根据业务组件库进行适配，需要用到的组件都需要在这里类型说明\nexport type ComponentType =\n  | 'AutoComplete'\n  | 'Checkbox'\n  | 'CheckboxGroup'\n  | 'DatePicker'\n  | 'DefaultButton'\n  | 'Divider'\n  | 'Input'\n  | 'InputNumber'\n  | 'InputPassword'\n  | 'Mentions'\n  | 'PrimaryButton'\n  | 'Radio'\n  | 'RadioGroup'\n  | 'RangePicker'\n  | 'Rate'\n  | 'Select'\n  | 'Space'\n  | 'Switch'\n  | 'Textarea'\n  | 'TimePicker'\n  | 'TreeSelect'\n  | 'Upload'\n  | BaseFormComponentType;\n\nasync function initComponentAdapter() {\n  const components: Partial<Record<ComponentType, Component>> = {\n    // 如果你的组件体积比较大，可以使用异步加载\n    // Button: () =>\n    // import('xxx').then((res) => res.Button),\n\n    AutoComplete,\n    Checkbox,\n    CheckboxGroup,\n    DatePicker,\n    // 自定义默认按钮\n    DefaultButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'default' }, slots);\n    },\n    Divider,\n    Input: withDefaultPlaceholder(Input, 'input'),\n    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),\n    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),\n    Mentions: withDefaultPlaceholder(Mentions, 'input'),\n    // 自定义主要按钮\n    PrimaryButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'primary' }, slots);\n    },\n    Radio,\n    RadioGroup,\n    RangePicker,\n    Rate,\n    Select: withDefaultPlaceholder(Select, 'select'),\n    Space,\n    Switch,\n    Textarea: withDefaultPlaceholder(Textarea, 'input'),\n    TimePicker,\n    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),\n    Upload,\n  };\n\n  // 将组件注册到全局共享状态中\n  globalShareState.setComponents(components);\n\n  // 定义全局共享状态中的消息提示\n  globalShareState.defineMessage({\n    // 复制成功消息提示\n    copyPreferencesSuccess: (title, content) => {\n      notification.success({\n        description: content,\n        message: title,\n        placement: 'bottomRight',\n      });\n    },\n  });\n}\n\nexport { initComponentAdapter };\n"
  },
  {
    "path": "docs/src/_env/adapter/form.ts",
    "content": "import type {\n  VbenFormSchema as FormSchema,\n  VbenFormProps,\n} from '@vben/common-ui';\n\nimport type { ComponentType } from './component';\n\nimport { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { initComponentAdapter } from './component';\n\ninitComponentAdapter();\n\nsetupVbenForm<ComponentType>({\n  config: {\n    baseModelPropName: 'value',\n    // naive-ui组件的空值为null,不能是undefined，否则重置表单时不生效\n    emptyStateValue: null,\n    modelPropNameMap: {\n      Checkbox: 'checked',\n      Radio: 'checked',\n      Switch: 'checked',\n      Upload: 'fileList',\n    },\n  },\n  defineRules: {\n    required: (value, _params, ctx) => {\n      if (value === undefined || value === null || value.length === 0) {\n        return $t('ui.formRules.required', [ctx.label]);\n      }\n      return true;\n    },\n    selectRequired: (value, _params, ctx) => {\n      if (value === undefined || value === null) {\n        return $t('ui.formRules.selectRequired', [ctx.label]);\n      }\n      return true;\n    },\n  },\n});\n\nconst useVbenForm = useForm<ComponentType>;\n\nexport { useVbenForm, z };\n\nexport type VbenFormSchema = FormSchema<ComponentType>;\nexport type { VbenFormProps };\n"
  },
  {
    "path": "docs/src/_env/adapter/vxe-table.ts",
    "content": "import { h } from 'vue';\n\nimport { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';\n\nimport { Button, Image } from 'ant-design-vue';\n\nimport { useVbenForm } from './form';\n\nif (!import.meta.env.SSR) {\n  setupVbenVxeTable({\n    configVxeTable: (vxeUI) => {\n      vxeUI.setConfig({\n        grid: {\n          align: 'center',\n          border: false,\n          columnConfig: {\n            resizable: true,\n          },\n\n          formConfig: {\n            // 全局禁用vxe-table的表单配置，使用formOptions\n            enabled: false,\n          },\n          minHeight: 180,\n          proxyConfig: {\n            autoLoad: true,\n            response: {\n              result: 'items',\n              total: 'total',\n              list: 'items',\n            },\n            showActiveMsg: true,\n            showResponseMsg: false,\n          },\n          round: true,\n          showOverflow: true,\n          size: 'small',\n        },\n      });\n\n      // 表格配置项可以用 cellRender: { name: 'CellImage' },\n      vxeUI.renderer.add('CellImage', {\n        renderTableDefault(renderOpts, params) {\n          const { props } = renderOpts;\n          const { column, row } = params;\n          return h(Image, { src: row[column.field], ...props });\n        },\n      });\n\n      // 表格配置项可以用 cellRender: { name: 'CellLink' },\n      vxeUI.renderer.add('CellLink', {\n        renderTableDefault(renderOpts) {\n          const { props } = renderOpts;\n          return h(\n            Button,\n            { size: 'small', type: 'link' },\n            { default: () => props?.text },\n          );\n        },\n      });\n\n      // 这里可以自行扩展 vxe-table 的全局配置，比如自定义格式化\n      // vxeUI.formats.add\n    },\n    useVbenForm,\n  });\n}\n\nexport { useVbenVxeGrid };\n\nexport type * from '@vben/plugins/vxe-table';\n"
  },
  {
    "path": "docs/src/_env/node/adapter/form.ts",
    "content": "export const useVbenForm = () => {};\nexport const z = {};\nexport type VbenFormSchema = any;\nexport type VbenFormProps = any;\n"
  },
  {
    "path": "docs/src/_env/node/adapter/vxe-table.ts",
    "content": "export type * from '@vben/plugins/vxe-table';\n\nexport const useVbenVxeGrid = () => {};\n"
  },
  {
    "path": "docs/src/commercial/community.md",
    "content": "# 社区交流\n\n社区交流群主要是为了方便大家交流，提问，解答问题，分享经验等。偏自助方式，如果你有问题，可以通过以下方式加入社区交流群：\n\n- [QQ频道](https://pd.qq.com/s/16p8lvvob)：推荐！！！主要提供问题解答，分享经验等。\n- 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)，主要的使用者交流群。\n- [Discord](https://discord.com/invite/VU62jTecad): 主要提供问题解答，分享经验等。\n\n::: tip\n\n免费QQ群人数上限200，将会不定期清理。推荐加入QQ频道进行交流\n\n:::\n\n## 微信群\n\n作者主要通过微信群提供帮助，如果你有问题，可以通过以下方式加入微信群。\n\n通过微信联系作者，注明加群来意：\n\n::: tip\n\n因为微信群人数有限制，加微信群要求：\n\n- 通过[赞助](../sponsor/personal.md)任意金额。\n- 发送赞助`截图`，备注`加入微信群`即可。\n\n:::\n\n<img src=\"https://unpkg.com/@vbenjs/static-source@0.1.7/source/wechat.jpg\" style=\"width: 300px;\"/>\n"
  },
  {
    "path": "docs/src/commercial/customized.md",
    "content": "# 定制开发\n\n我们提供基于 Vben Admin 的技术支持服务及定制开发，基本需求我们都可以满足。\n\n详细需求可添加作者了解，并注明来意：\n\n- 通过邮箱联系开发者： [ann.vben@gmail.com](mailto:ann.vben@gmail.com)\n- 通过微信联系开发者：\n\n <img src=\"https://unpkg.com/@vbenjs/static-source@0.1.7/source/wechat.jpg\" style=\"width: 300px;\"/>\n\n我们会在第一时间回复您，定制费用根据需求而定。\n"
  },
  {
    "path": "docs/src/commercial/technical-support.md",
    "content": "# 技术支持\n\n## 问题反馈\n\n在使用项目的过程中，如果遇到问题，你可以先详细阅读本文档，未找到解决方案时，可以通过以下方式获取技术支持：\n\n- 通过 [GitHub Issues](https://github.com/vbenjs/vue-vben-admin/issues)\n- 通过 [GitHub Discussions](https://github.com/vbenjs/vue-vben-admin/discussions)\n"
  },
  {
    "path": "docs/src/components/common-ui/vben-alert.md",
    "content": "---\noutline: deep\n---\n\n# Vben Alert 轻量提示框\n\n`Alert` 提供了一组纯 JavaScript 调用的轻量提示框能力，适合快速创建 `alert`、`confirm`、`prompt` 这类简单交互。\n\n::: info 适用场景\n\n`Alert` 与 `Modal` 的能力有部分重叠，但更适合临时确认、简单提示和轻量输入场景。复杂弹窗仍然建议使用 `Vben Modal`。:::\n\n::: tip 注意\n\n通过 `alert`、`confirm`、`prompt` 动态创建的弹窗，在已经打开的情况下不支持 HMR 热更新。修改相关代码后，需要关闭后重新打开。:::\n\n## 基础用法\n\n使用 `alert` 创建只有确认按钮的提示框。\n\n<DemoPreview dir=\"demos/vben-alert/alert\" />\n\n使用 `confirm` 创建带确认和取消按钮的提示框。\n\n<DemoPreview dir=\"demos/vben-alert/confirm\" />\n\n使用 `prompt` 创建可接收用户输入的提示框。\n\n<DemoPreview dir=\"demos/vben-alert/prompt\" />\n\n## useAlertContext\n\n当 `content`、`footer` 或 `icon` 使用的是自定义组件时，可以在这些组件内部通过 `useAlertContext()` 获取当前弹窗上下文，并主动触发确认或取消。\n\n::: tip 注意\n\n`useAlertContext` 只能在 `setup` 或函数式组件中使用。:::\n\n### Methods\n\n| 方法      | 描述                   | 类型         | 版本要求 |\n| --------- | ---------------------- | ------------ | -------- |\n| doConfirm | 触发当前弹窗的确认操作 | `() => void` | `>5.5.4` |\n| doCancel  | 触发当前弹窗的取消操作 | `() => void` | `>5.5.4` |\n\n## 类型说明\n\n```ts\nexport type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';\n\nexport type BeforeCloseScope = {\n  isConfirm: boolean;\n};\n\nexport type AlertProps = {\n  beforeClose?: (\n    scope: BeforeCloseScope,\n  ) => boolean | Promise<boolean | undefined> | undefined;\n  bordered?: boolean;\n  buttonAlign?: 'center' | 'end' | 'start';\n  cancelText?: string;\n  centered?: boolean;\n  confirmText?: string;\n  containerClass?: string;\n  content: Component | string;\n  contentClass?: string;\n  contentMasking?: boolean;\n  footer?: Component | string;\n  icon?: Component | IconType;\n  overlayBlur?: number;\n  showCancel?: boolean;\n  title?: string;\n};\n\nexport type PromptProps<T = any> = {\n  beforeClose?: (scope: {\n    isConfirm: boolean;\n    value: T | undefined;\n  }) => boolean | Promise<boolean | undefined> | undefined;\n  component?: Component;\n  componentProps?: Recordable<any>;\n  componentSlots?:\n    | (() => any)\n    | Recordable<unknown>\n    | VNode\n    | VNodeArrayChildren;\n  defaultValue?: T;\n  modelPropName?: string;\n} & Omit<AlertProps, 'beforeClose'>;\n\nexport function alert(options: AlertProps): Promise<void>;\nexport function alert(\n  message: string,\n  options?: Partial<AlertProps>,\n): Promise<void>;\nexport function alert(\n  message: string,\n  title?: string,\n  options?: Partial<AlertProps>,\n): Promise<void>;\n\nexport async function prompt<T = any>(\n  options: Omit<AlertProps, 'beforeClose'> & {\n    beforeClose?: (\n      scope: BeforeCloseScope & {\n        value: T;\n      },\n    ) => boolean | Promise<boolean | undefined> | undefined;\n    component?: Component;\n    componentProps?: Recordable<any>;\n    defaultValue?: T;\n    modelPropName?: string;\n  },\n): Promise<T | undefined>;\n```\n"
  },
  {
    "path": "docs/src/components/common-ui/vben-api-component.md",
    "content": "---\noutline: deep\n---\n\n# Vben ApiComponent Api组件包装器\n\n框架提供的API“包装器”，它一般不独立使用，主要用于包装其它组件，为目标组件提供自动获取远程数据的能力，但仍然保持了目标组件的原始用法。\n\n::: info 写在前面\n\n我们在各个应用的组件适配器中，使用ApiComponent包装了Select、TreeSelect组件，使得这些组件可以自动获取远程数据并生成选项。其它类似的组件（比如Cascader）如有需要也可以参考示例代码自行进行包装。\n\n:::\n\n## 基础用法\n\n通过 `component` 传入其它组件的定义，并配置相关的其它属性（主要是一些名称映射）。包装组件将通过 `api` 获取数据（`beforeFetch`、`afterFetch` 将分别在 `api` 运行前、运行后被调用），使用 `resultField` 从中提取数组，使用 `valueField`、`labelField` 等来从数据中提取 value 和 label（如果提供了 `childrenField`，会将其作为树形结构递归处理每一级数据），之后将处理好的数据通过 `optionsPropName` 指定的属性传递给目标组件。\n\n::: details 包装级联选择器,点击下拉时开始加载远程数据\n\n```vue\n<script lang=\"ts\" setup>\nimport { ApiComponent } from '@vben/common-ui';\n\nimport { Cascader } from 'ant-design-vue';\n\nconst treeData: Record<string, any> = [\n  {\n    label: '浙江',\n    value: 'zhejiang',\n    children: [\n      {\n        value: 'hangzhou',\n        label: '杭州',\n        children: [\n          {\n            value: 'xihu',\n            label: '西湖',\n          },\n          {\n            value: 'sudi',\n            label: '苏堤',\n          },\n        ],\n      },\n      {\n        value: 'jiaxing',\n        label: '嘉兴',\n        children: [\n          {\n            value: 'wuzhen',\n            label: '乌镇',\n          },\n          {\n            value: 'meihuazhou',\n            label: '梅花洲',\n          },\n        ],\n      },\n      {\n        value: 'zhoushan',\n        label: '舟山',\n        children: [\n          {\n            value: 'putuoshan',\n            label: '普陀山',\n          },\n          {\n            value: 'taohuadao',\n            label: '桃花岛',\n          },\n        ],\n      },\n    ],\n  },\n  {\n    label: '江苏',\n    value: 'jiangsu',\n    children: [\n      {\n        value: 'nanjing',\n        label: '南京',\n        children: [\n          {\n            value: 'zhonghuamen',\n            label: '中华门',\n          },\n          {\n            value: 'zijinshan',\n            label: '紫金山',\n          },\n          {\n            value: 'yuhuatai',\n            label: '雨花台',\n          },\n        ],\n      },\n    ],\n  },\n];\n/**\n * 模拟请求接口\n */\nfunction fetchApi(): Promise<Record<string, any>> {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve(treeData);\n    }, 1000);\n  });\n}\n</script>\n<template>\n  <ApiComponent\n    :api=\"fetchApi\"\n    :component=\"Cascader\"\n    :immediate=\"false\"\n    children-field=\"children\"\n    loading-slot=\"suffixIcon\"\n    visible-event=\"onDropdownVisibleChange\"\n  />\n</template>\n```\n\n:::\n\n## 并发和缓存\n\n有些场景下可能需要使用多个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)\n\n## API\n\n### Props\n\n| 属性名 | 描述 | 类型 | 默认值 | 版本要求 |\n| --- | --- | --- | --- | --- |\n| modelValue(v-model) | 当前值 | `any` | - | - |\n| component | 欲包装的组件（以下称为目标组件） | `Component` | - | - |\n| numberToString | 是否将value从数字转为string | `boolean` | `false` | - |\n| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \\| Record<string, any>>` | - | - |\n| params | 传递给api的参数 | `Record<string, any>` | - | - |\n| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - | - |\n| labelField | label字段名 | `string` | `label` | - |\n| childrenField | 子级数据字段名，需要层级数据的组件可用 | `string` | `` | - |\n| valueField | value字段名 | `string` | `value` | - |\n| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` | - |\n| modelPropName | 目标组件的双向绑定属性名，默认为modelValue。部分组件可能为value | `string` | `modelValue` | - |\n| immediate | 是否立即调用api | `boolean` | `true` | - |\n| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` | - |\n| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - | - |\n| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - | - |\n| options | 直接传入选项数据，也作为api返回空数据时的后备数据 | `OptionsItem[]` | - | - |\n| visibleEvent | 触发重新请求数据的事件名 | `string` | - | - |\n| loadingSlot | 目标组件的插槽名称，用来显示一个\"加载中\"的图标 | `string` | - | - |\n| autoSelect | 自动设置选项 | `'first' \\| 'last' \\| 'one'\\| ((item: OptionsItem[]) => OptionsItem) \\| false` | `false` | >5.5.4 |\n\n#### autoSelect 自动设置选项\n\n如果当前值为undefined，在选项数据成功加载之后，自动从备选项中选择一个作为当前值。默认值为`false`，即不自动选择选项。注意：该属性不应用于多选组件。可选值有：\n\n- `\"first\"`：自动选择第一个选项\n- `\"last\"`：自动选择最后一个选项\n- `\"one\"`：有且仅有一个选项时，自动选择它\n- `自定义函数`：自定义选择逻辑，函数的参数为options，返回值为选择的选项\n- `false`：不自动选择选项\n\n### Methods\n\n| 方法 | 描述 | 类型 | 版本要求 |\n| --- | --- | --- | --- |\n| getComponentRef | 获取被包装的组件的实例 | ()=>T | >5.5.4 |\n| updateParam | 设置接口请求参数（将与params属性合并） | (newParams: Record<string, any>)=>void | >5.5.4 |\n| getOptions | 获取已加载的选项数据 | ()=>OptionsItem[] | >5.5.4 |\n| getValue | 获取当前值 | ()=>any | >5.5.4 |\n"
  },
  {
    "path": "docs/src/components/common-ui/vben-count-to-animator.md",
    "content": "---\noutline: deep\n---\n\n# Vben CountToAnimator 数字动画\n\n`CountToAnimator` 用于展示数字滚动动画效果。\n\n> 如果文档内没有覆盖到你需要的细节，可以结合在线示例一起查看。\n\n::: info 写在前面\n\n这是一个轻量数字动画组件。如果你需要完全不同的过渡控制方式，也可以直接使用原生动画方案或自行封装。:::\n\n## 基础用法\n\n通过 `start-val` 和 `end-val` 设置数字动画的起始值和结束值，配合 `duration` 控制动画时长。\n\n<DemoPreview dir=\"demos/vben-count-to-animator/basic\" />\n\n## 自定义前缀与分隔符\n\n通过 `prefix`、`suffix`、`separator` 和 `decimal` 可以控制展示格式。\n\n<DemoPreview dir=\"demos/vben-count-to-animator/custom\" />\n\n### Props\n\n| 属性名 | 描述 | 类型 | 默认值 |\n| --- | --- | --- | --- |\n| startVal | 起始值 | `number` | `0` |\n| endVal | 结束值 | `number` | `2021` |\n| duration | 动画持续时间 | `number` | `1500` |\n| autoplay | 是否自动播放 | `boolean` | `true` |\n| prefix | 前缀 | `string` | `''` |\n| suffix | 后缀 | `string` | `''` |\n| separator | 千分位分隔符 | `string` | `','` |\n| decimal | 小数点分隔符 | `string` | `'.'` |\n| color | 文本颜色 | `string` | `''` |\n| useEasing | 是否启用过渡预设 | `boolean` | `true` |\n| transition | 过渡预设名称 | `keyof typeof TransitionPresets` | `'linear'` |\n| decimals | 保留小数位数 | `number` | `0` |\n\n### Events\n\n| 事件名         | 描述                          | 类型             |\n| -------------- | ----------------------------- | ---------------- |\n| started        | 动画开始时触发                | `() => void`     |\n| finished       | 动画结束时触发                | `() => void`     |\n| ~~onStarted~~  | ~~已废弃，请改用 `started`~~  | ~~`() => void`~~ |\n| ~~onFinished~~ | ~~已废弃，请改用 `finished`~~ | ~~`() => void`~~ |\n\n### Methods\n\n| 方法名 | 描述                             | 类型         |\n| ------ | -------------------------------- | ------------ |\n| reset  | 重置为 `startVal` 并重新执行动画 | `() => void` |\n"
  },
  {
    "path": "docs/src/components/common-ui/vben-drawer.md",
    "content": "---\noutline: deep\n---\n\n# Vben Drawer 抽屉\n\n框架提供的抽屉组件，支持`自动高度`、`loading`等功能。\n\n> 如果文档内没有参数说明，可以尝试在在线示例内寻找\n\n::: info 写在前面\n\n如果你觉得现有组件的封装不够理想，或者不完全符合你的需求，大可以直接使用原生组件，亦或亲手封装一个适合的组件。框架提供的组件并非束缚，使用与否，完全取决于你的需求与自由。\n\n:::\n\n::: tip README\n\n下方示例代码中的，存在一些国际化、主题色未适配问题，这些问题只在文档内会出现，实际使用并不会有这些问题，可忽略，不必纠结。\n\n:::\n\n## 基础用法\n\n使用 `useVbenDrawer` 创建最基础的抽屉。\n\n<DemoPreview dir=\"demos/vben-drawer/basic\" />\n\n## 组件抽离\n\nDrawer 内的内容一般业务中，会比较复杂，所以我们可以将 drawer 内的内容抽离出来，也方便复用。通过 `connectedComponent` 参数，可以将内外组件进行连接，而不用其他任何操作。\n\n<DemoPreview dir=\"demos/vben-drawer/extra\" />\n\n## 自动计算高度\n\n弹窗会自动计算内容高度，超过一定高度会出现滚动条，同时结合 `loading` 效果以及使用 `prepend-footer` 插槽。\n\n<DemoPreview dir=\"demos/vben-drawer/auto-height\" />\n\n## 使用 Api\n\n通过 `drawerApi` 可以调用 drawer 的方法以及使用 `setState` 更新 drawer 的状态。\n\n<DemoPreview dir=\"demos/vben-drawer/dynamic\" />\n\n## 数据共享\n\n如果你使用了 `connectedComponent` 参数，那么内外组件会共享数据，比如一些表单回填等操作。可以用 `drawerApi` 来获取数据和设置数据，配合 `onOpenChange`，可以满足大部分的需求。\n\n<DemoPreview dir=\"demos/vben-drawer/shared-data\" />\n\n::: info 注意\n\n- `VbenDrawer` 组件对于参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`，那么 `setState` 将不会生效，这种情况下你可以通过 `slot` 或者 `props` 来更新状态。\n- 如果你使用到了 `connectedComponent` 参数，那么会存在 2 个`useVbenDrawer`, 此时，如果同时设置了相同的参数，那么以内部为准（也就是没有设置 connectedComponent 的代码）。比如 同时设置了 `onConfirm`，那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外，内外都会触发。\n- 使用了`connectedComponent`参数时，可以配置`destroyOnClose`属性来决定当关闭弹窗时，是否要销毁`connectedComponent`组件（重新创建`connectedComponent`组件，这将会把其内部所有的变量、状态、数据等恢复到初始状态。）。\n- 如果抽屉的默认行为不符合你的预期，可以在对应应用的 `apps/<app>/src/bootstrap.ts` 中修改 `setDefaultDrawerProps` 的参数来设置默认属性，例如修改默认 `zIndex` 等。\n\n:::\n\n## API\n\n```ts\n// Drawer 为弹窗组件\n// drawerApi 为弹窗的方法\nconst [Drawer, drawerApi] = useVbenDrawer({\n  // 属性\n  // 事件\n});\n```\n\n### Props\n\n所有属性都可以传入 `useVbenDrawer` 的第一个参数中。\n\n| 属性名 | 描述 | 类型 | 默认值 |\n| --- | --- | --- | --- |\n| appendToMain | 是否挂载到内容区域（默认挂载到body） | `boolean` | `false` |\n| connectedComponent | 连接另一个Drawer组件 | `Component` | - |\n| destroyOnClose | 关闭时销毁 | `boolean` | `false` |\n| title | 标题 | `string\\|slot` | - |\n| titleTooltip | 标题提示信息 | `string\\|slot` | - |\n| description | 描述信息 | `string\\|slot` | - |\n| isOpen | 弹窗打开状态 | `boolean` | `false` |\n| loading | 弹窗加载状态 | `boolean` | `false` |\n| closable | 显示关闭按钮 | `boolean` | `true` |\n| closeIconPlacement | 关闭按钮位置 | `'left'\\|'right'` | `right` |\n| modal | 显示遮罩 | `boolean` | `true` |\n| header | 显示header | `boolean` | `true` |\n| footer | 显示footer | `boolean\\|slot` | `true` |\n| confirmLoading | 确认按钮loading状态 | `boolean` | `false` |\n| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` |\n| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |\n| confirmText | 确认按钮文本 | `string\\|slot` | `确认` |\n| cancelText | 取消按钮文本 | `string\\|slot` | `取消` |\n| placement | 抽屉弹出位置 | `'left'\\|'right'\\|'top'\\|'bottom'` | `right` |\n| showCancelButton | 显示取消按钮 | `boolean` | `true` |\n| showConfirmButton | 显示确认按钮 | `boolean` | `true` |\n| class | modal的class，宽度通过这个配置 | `string` | - |\n| contentClass | modal内容区域的class | `string` | - |\n| footerClass | modal底部区域的class | `string` | - |\n| headerClass | modal顶部区域的class | `string` | - |\n| zIndex | 抽屉的ZIndex层级 | `number` | `1000` |\n| overlayBlur | 遮罩模糊度 | `number` | - |\n\n::: info appendToMain\n\n`appendToMain`可以指定将抽屉挂载到内容区域，打开抽屉时，内容区域以外的部分（标签栏、导航菜单等等）不会被遮挡。默认情况下，抽屉会挂载到body上。但是：挂载到内容区域时，作为页面根容器的`Page`组件，需要设置`auto-content-height`属性，以便抽屉能够正确计算高度。\n\n:::\n\n### Event\n\n以下事件，只有在 `useVbenDrawer({onCancel:()=>{}})` 中传入才会生效。\n\n| 事件名 | 描述 | 类型 | 版本限制 |\n| --- | --- | --- | --- |\n| onBeforeClose | 关闭前触发，返回 `false` 或 Promise reject 则禁止关闭 | `()=>Promise<boolean \\| undefined>\\|boolean\\|undefined` | >5.5.2 支持 Promise |\n| onCancel | 点击取消按钮触发 | `()=>void` | --- |\n| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.5.2 |\n| onConfirm | 点击确认按钮触发 | `()=>void` | --- |\n| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` | --- |\n| onOpened | 打开动画播放完毕时触发 | `()=>void` | >5.5.2 |\n\n### Slots\n\n除了上面的属性类型包含`slot`，还可以通过插槽来自定义弹窗的内容。\n\n| 插槽名         | 描述                                               |\n| -------------- | -------------------------------------------------- |\n| default        | 默认插槽 - 弹窗内容                                |\n| prepend-footer | 取消按钮左侧                                       |\n| center-footer  | 取消按钮和确认按钮中间（不使用 footer 插槽时有效） |\n| append-footer  | 确认按钮右侧                                       |\n| close-icon     | 关闭按钮图标                                       |\n| extra          | 额外内容(标题右侧)                                 |\n\n### drawerApi\n\n| 方法 | 描述 | 类型 | 版本限制 |\n| --- | --- | --- | --- |\n| setState | 动态设置抽屉状态属性 | `(((prev: DrawerState) => Partial<DrawerState>)\\| Partial<DrawerState>)=>drawerApi` |\n| open | 打开弹窗 | `()=>void` | --- |\n| close | 关闭弹窗 | `()=>void` | --- |\n| setData | 设置共享数据 | `<T>(data:T)=>drawerApi` | --- |\n| getData | 获取共享数据 | `<T>()=>T` | --- |\n| useStore | 获取可响应式状态 | - | --- |\n| lock | 将抽屉标记为提交中，锁定当前状态 | `(isLock:boolean)=>drawerApi` | >5.5.3 |\n| unlock | lock方法的反操作，解除抽屉的锁定状态，也是lock(false)的别名 | `()=>drawerApi` | >5.5.3 |\n\n::: info lock\n\n`lock`方法用于锁定抽屉的状态，一般用于提交数据的过程中防止用户重复提交或者抽屉被意外关闭、表单数据被改变等等。当处于锁定状态时，抽屉的确认按钮会变为loading状态，同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭抽屉、开启抽屉的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的抽屉时，会自动解锁。要主动解除这种状态，可以调用`unlock`方法或者再次调用lock方法并传入false参数。\n\n:::\n"
  },
  {
    "path": "docs/src/components/common-ui/vben-ellipsis-text.md",
    "content": "---\noutline: deep\n---\n\n# Vben EllipsisText 省略文本\n\n`EllipsisText` 用于展示超长文本，支持省略、Tooltip 提示以及点击展开收起。\n\n> 如果文档内没有覆盖到你需要的细节，可以结合在线示例一起查看。\n\n## 基础用法\n\n通过默认插槽提供文本内容，`maxWidth` 用于限制文本区域宽度。\n\n<DemoPreview dir=\"demos/vben-ellipsis-text/line\" />\n\n## 可折叠文本\n\n通过 `line` 设置折叠后的最大行数，通过 `expand` 开启点击展开与收起。\n\n<DemoPreview dir=\"demos/vben-ellipsis-text/expand\" />\n\n## 自定义提示浮层\n\n通过 `tooltip` 插槽自定义提示内容。\n\n<DemoPreview dir=\"demos/vben-ellipsis-text/tooltip\" />\n\n## 仅在省略时显示 Tooltip\n\n通过 `tooltip-when-ellipsis` 控制仅在文本被截断时显示 Tooltip。\n\n<DemoPreview dir=\"demos/vben-ellipsis-text/auto-display\" />\n\n## API\n\n### Props\n\n| 属性名 | 描述 | 类型 | 默认值 |\n| --- | --- | --- | --- |\n| expand | 是否支持点击展开或收起 | `boolean` | `false` |\n| line | 文本最大显示行数 | `number` | `1` |\n| maxWidth | 文本区域最大宽度 | `number \\| string` | `'100%'` |\n| placement | 提示浮层位置 | `'bottom' \\| 'left' \\| 'right' \\| 'top'` | `'top'` |\n| tooltip | 是否启用文本提示 | `boolean` | `true` |\n| tooltipWhenEllipsis | 是否仅在文本被截断时显示提示 | `boolean` | `false` |\n| ellipsisThreshold | 文本截断检测阈值，值越大判定越严格 | `number` | `3` |\n| tooltipBackgroundColor | 提示背景色 | `string` | `''` |\n| tooltipColor | 提示文字颜色 | `string` | `''` |\n| tooltipFontSize | 提示文字大小，单位 `px` | `number` | `14` |\n| tooltipMaxWidth | 提示内容最大宽度，单位 `px` | `number` | - |\n| tooltipOverlayStyle | 提示内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` |\n\n### Events\n\n| 事件名       | 描述               | 类型                          |\n| ------------ | ------------------ | ----------------------------- |\n| expandChange | 展开状态变化时触发 | `(isExpand: boolean) => void` |\n\n### Slots\n\n| 插槽名  | 描述                               |\n| ------- | ---------------------------------- |\n| tooltip | 开启文本提示时，用于自定义提示内容 |\n"
  },
  {
    "path": "docs/src/components/common-ui/vben-form.md",
    "content": "---\noutline: deep\n---\n\n# Vben Form 表单\n\n框架提供的表单组件，可适配 `Element Plus`、`Ant Design Vue`、`Naive UI` 等框架。\n\n> 如果文档内没有参数说明，可以尝试在在线示例内寻找\n\n::: info 写在前面\n\n如果你觉得现有组件的封装不够理想，或者不完全符合你的需求，大可以直接使用原生组件，亦或亲手封装一个适合的组件。框架提供的组件并非束缚，使用与否，完全取决于你的需求与自由。\n\n:::\n\n## 适配器\n\n表单底层使用 [vee-validate](https://vee-validate.logaretm.com/v4/) 进行表单验证，所以你可以使用 `vee-validate` 的所有功能。对于不同的 UI 框架，我们提供了适配器，以便更好的适配不同的 UI 框架。\n\n### 适配器说明\n\n每个应用都有不同的 UI 框架，所以在应用的 `src/adapter/form` 和 `src/adapter/component` 内部，你可以根据自己的需求，进行组件适配。下面是 `Ant Design Vue` 的适配器示例代码，可根据注释查看说明：\n\n::: details ant design vue 表单适配器\n\n```ts\nimport type {\n  VbenFormSchema as FormSchema,\n  VbenFormProps,\n} from '@vben/common-ui';\n\nimport type { ComponentType } from './component';\n\nimport { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { initComponentAdapter } from './component';\n\ninitComponentAdapter();\nsetupVbenForm<ComponentType>({\n  config: {\n    // ant design vue组件库默认都是 v-model:value\n    baseModelPropName: 'value',\n    // 一些组件库空值为 null，重置表单时需要和实际组件行为保持一致\n    emptyStateValue: null,\n    // 一些组件是 v-model:checked 或者 v-model:fileList\n    modelPropNameMap: {\n      Checkbox: 'checked',\n      Radio: 'checked',\n      Switch: 'checked',\n      Upload: 'fileList',\n    },\n  },\n  defineRules: {\n    // 输入项目必填国际化适配\n    required: (value, _params, ctx) => {\n      if (value === undefined || value === null || value.length === 0) {\n        return $t('ui.formRules.required', [ctx.label]);\n      }\n      return true;\n    },\n    // 选择项目必填国际化适配\n    selectRequired: (value, _params, ctx) => {\n      if (value === undefined || value === null) {\n        return $t('ui.formRules.selectRequired', [ctx.label]);\n      }\n      return true;\n    },\n  },\n});\n\nconst useVbenForm = useForm<ComponentType>;\n\nexport { useVbenForm, z };\nexport type VbenFormSchema = FormSchema<ComponentType>;\nexport type { VbenFormProps };\n```\n\n:::\n\n::: details ant design vue 组件适配器\n\n```ts\n/**\n * 通用组件共同的使用的基础组件，原先放在 adapter/form 内部，限制了使用范围，这里提取出来，方便其他地方使用\n * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,\n */\n\nimport type { BaseFormComponentType } from '@vben/common-ui';\n\nimport type { Component, SetupContext } from 'vue';\nimport { h } from 'vue';\n\nimport { globalShareState } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\nimport {\n  AutoComplete,\n  Button,\n  Checkbox,\n  CheckboxGroup,\n  DatePicker,\n  Divider,\n  Input,\n  InputNumber,\n  InputPassword,\n  Mentions,\n  notification,\n  Radio,\n  RadioGroup,\n  RangePicker,\n  Rate,\n  Select,\n  Space,\n  Switch,\n  Textarea,\n  TimePicker,\n  TreeSelect,\n  Upload,\n} from 'ant-design-vue';\n\nconst withDefaultPlaceholder = <T extends Component>(\n  component: T,\n  type: 'input' | 'select',\n) => {\n  return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {\n    const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);\n    return h(component, { ...props, ...attrs, placeholder }, slots);\n  };\n};\n\n// 这里需要自行根据业务组件库进行适配，需要用到的组件都需要在这里类型说明\nexport type ComponentType =\n  | 'AutoComplete'\n  | 'Checkbox'\n  | 'CheckboxGroup'\n  | 'DatePicker'\n  | 'DefaultButton'\n  | 'Divider'\n  | 'Input'\n  | 'InputNumber'\n  | 'InputPassword'\n  | 'Mentions'\n  | 'PrimaryButton'\n  | 'Radio'\n  | 'RadioGroup'\n  | 'RangePicker'\n  | 'Rate'\n  | 'Select'\n  | 'Space'\n  | 'Switch'\n  | 'Textarea'\n  | 'TimePicker'\n  | 'TreeSelect'\n  | 'Upload'\n  | BaseFormComponentType;\n\nasync function initComponentAdapter() {\n  const components: Partial<Record<ComponentType, Component>> = {\n    // 如果你的组件体积比较大，可以使用异步加载\n    // Button: () =>\n    // import('xxx').then((res) => res.Button),\n\n    AutoComplete,\n    Checkbox,\n    CheckboxGroup,\n    DatePicker,\n    // 自定义默认按钮\n    DefaultButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'default' }, slots);\n    },\n    Divider,\n    Input: withDefaultPlaceholder(Input, 'input'),\n    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),\n    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),\n    Mentions: withDefaultPlaceholder(Mentions, 'input'),\n    // 自定义主要按钮\n    PrimaryButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'primary' }, slots);\n    },\n    Radio,\n    RadioGroup,\n    RangePicker,\n    Rate,\n    Select: withDefaultPlaceholder(Select, 'select'),\n    Space,\n    Switch,\n    Textarea: withDefaultPlaceholder(Textarea, 'input'),\n    TimePicker,\n    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),\n    Upload,\n  };\n\n  // 将组件注册到全局共享状态中\n  globalShareState.setComponents(components);\n\n  // 定义全局共享状态中的消息提示\n  globalShareState.defineMessage({\n    // 复制成功消息提示\n    copyPreferencesSuccess: (title, content) => {\n      notification.success({\n        description: content,\n        message: title,\n        placement: 'bottomRight',\n      });\n    },\n  });\n}\n\nexport { initComponentAdapter };\n```\n\n:::\n\n## 基础用法\n\n::: tip README\n\n下方示例代码中的，存在一些国际化、主题色未适配问题，这些问题只在文档内会出现，实际使用并不会有这些问题，可忽略，不必纠结。\n\n:::\n\n使用 `useVbenForm` 创建最基础的表单。\n\n<DemoPreview dir=\"demos/vben-form/basic\" />\n\n## 查询表单\n\n查询表单是一种特殊的表单，用于查询数据。查询表单不会触发表单验证，只会触发查询事件。\n\n<DemoPreview dir=\"demos/vben-form/query\" />\n\n## 表单校验\n\n表单校验是一个非常重要的功能，可以通过 `rules` 属性进行校验。\n\n<DemoPreview dir=\"demos/vben-form/rules\" />\n\n## 表单联动\n\n表单联动是一个非常常见的功能，可以通过 `dependencies` 属性进行联动。\n\n_注意_ 需要指定 `dependencies` 的 `triggerFields` 属性，设置由谁的改动来触发，以便表单组件能够正确的联动。\n\n<DemoPreview dir=\"demos/vben-form/dynamic\" />\n\n## 自定义组件\n\n如果你的业务组件库没有提供某个组件，你可以自行封装一个组件，然后加到表单内部。\n\n<DemoPreview dir=\"demos/vben-form/custom\" />\n\n## 操作\n\n一些常见的表单操作。\n\n<DemoPreview dir=\"demos/vben-form/api\" />\n\n## API\n\n`useVbenForm` 返回一个数组，第一个元素是表单组件，第二个元素是表单的方法。\n\n```vue\n<script setup lang=\"ts\">\nimport { useVbenForm } from '#/adapter/form';\n\n// Form 为弹窗组件\n// formApi 为弹窗的方法\nconst [Form, formApi] = useVbenForm({\n  // 属性\n  // 事件\n});\n</script>\n\n<template>\n  <Form />\n</template>\n```\n\n### FormApi\n\nuseVbenForm 返回的第二个参数，是一个对象，包含了一些表单的方法。\n\n| 方法名 | 描述 | 类型 | 版本号 |\n| --- | --- | --- | --- |\n| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` | - |\n| validateAndSubmitForm | 提交并校验表单 | `(e:Event)=>Promise<Record<string,any>>` | - |\n| resetForm | 重置表单 | `()=>Promise<void>` | - |\n| setValues | 设置表单值, 默认会过滤不在schema中定义的field, 可通过filterFields形参关闭过滤 | `(fields: Record<string, any>, filterFields?: boolean, shouldValidate?: boolean) => Promise<void>` | - |\n| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` | - |\n| validate | 表单校验 | `()=>Promise<void>` | - |\n| validateField | 校验指定字段 | `(fieldName: string)=>Promise<ValidationResult<unknown>>` | - |\n| isFieldValid | 检查某个字段是否已通过校验 | `(fieldName: string)=>Promise<boolean>` | - |\n| resetValidate | 重置表单校验 | `()=>Promise<void>` | - |\n| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` | - |\n| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise<void>` | - |\n| setState | 设置组件状态（props） | `(stateOrFn:\\| ((prev: VbenFormProps) => Partial<VbenFormProps>)\\| Partial<VbenFormProps>)=>Promise<void>` | - |\n| getState | 获取组件状态（props） | `()=>Promise<VbenFormProps>` | - |\n| form | 表单对象实例，可以操作表单，见 [useForm](https://vee-validate.logaretm.com/v4/api/use-form/) | - | - |\n| getFieldComponentRef | 获取指定字段的组件实例 | `<T=unknown>(fieldName: string)=>T` | >5.5.3 |\n| getFocusedField | 获取当前已获得焦点的字段 | `()=>string\\|undefined` | >5.5.3 |\n\n## Props\n\n所有属性都可以传入 `useVbenForm` 的第一个参数中。\n\n| 属性名 | 描述 | 类型 | 默认值 |\n| --- | --- | --- | --- |\n| layout | 表单项布局 | `'horizontal' \\| 'vertical'\\| 'inline'` | `horizontal` |\n| showCollapseButton | 是否显示折叠按钮 | `boolean` | `false` |\n| wrapperClass | 表单的布局，基于tailwindcss | `any` | - |\n| actionWrapperClass | 表单操作区域class | `any` | - |\n| actionLayout | 表单操作按钮位置 | `'newLine' \\| 'rowEnd' \\| 'inline'` | `rowEnd` |\n| actionPosition | 表单操作按钮对齐方式 | `'left' \\| 'center' \\| 'right'` | `right` |\n| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \\| void` | - |\n| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \\| void` | - |\n| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - |\n| handleCollapsedChange | 表单收起展开状态变化回调 | `(collapsed: boolean) => void` | - |\n| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |\n| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |\n| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |\n| showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` |\n| collapsed | 是否折叠，在`showCollapseButton`为`true`时生效 | `boolean` | `false` |\n| collapseTriggerResize | 折叠时，触发`resize`事件 | `boolean` | `false` |\n| collapsedRows | 折叠时保持的行数 | `number` | `1` |\n| fieldMappingTime | 用于将表单内的数组值映射成 2 个字段 | `[string, [string, string],Nullable<string>\\|[string,string]\\|((any,string)=>any)?][]` | - |\n| commonConfig | 表单项的通用配置，每个配置都会传递到每个表单项，表单项可覆盖 | `FormCommonConfig` | - |\n| schema | 表单项的每一项配置 | `FormSchema[]` | - |\n| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |\n| submitOnChange | 字段值改变时提交表单(内部防抖，这个属性一般用于表格的搜索表单) | `boolean` | false |\n| compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false |\n| scrollToFirstError | 表单验证失败时是否自动滚动到第一个错误字段 | `boolean` | false |\n\n::: tip handleValuesChange\n\n`handleValuesChange` 回调函数的第一个参数`values`装载了表单改变后的当前值对象，第二个参数`fieldsChanged`是一个数组，包含了所有被改变的字段名。注意：第二个参数仅在v5.5.4(不含)以上版本可用，并且传递的是已在schema中定义的字段名。如果你使用了字段映射并且需要检查是哪些字段发生了变化的话，请注意该参数并不会包含映射后的字段名。\n\n:::\n\n::: tip fieldMappingTime\n\n此属性用于将表单内的数组值映射成 2 个字段，它应当传入一个数组，数组的每一项是一个映射规则，规则的第一个成员是一个字符串，表示需要映射的字段名，第二个成员是一个数组，表示映射后的字段名，第三个成员是一个可选的格式掩码，用于格式化日期时间字段；也可以提供一个格式化函数（参数分别为当前值和当前字段名，返回格式化后的值）。如果明确地将格式掩码设为null，则原值映射而不进行格式化（适用于非日期时间字段）。例如：`[['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD']]`，`timeRange`应当是一个至少具有2个成员的数组类型的值。Form会将`timeRange`的值前两个值分别按照格式掩码`YYYY-MM-DD`格式化后映射到`startTime`和`endTime`字段上。每一项的第三个参数是一个可选的格式掩码，\n\n:::\n\n### TS 类型说明\n\n::: details ActionButtonOptions\n\n```ts\nexport interface ActionButtonOptions {\n  /** 样式 */\n  class?: ClassType;\n  /** 是否禁用 */\n  disabled?: boolean;\n  /** 是否加载中 */\n  loading?: boolean;\n  /** 按钮大小 */\n  size?: ButtonVariantSize;\n  /** 按钮类型 */\n  variant?: ButtonVariants;\n  /** 是否显示 */\n  show?: boolean;\n  /** 按钮文本 */\n  content?: string;\n  /** 任意属性 */\n  [key: string]: any;\n}\n```\n\n:::\n\n::: details FormCommonConfig\n\n```ts\nexport interface FormCommonConfig {\n  /**\n   * 所有表单项的props\n   */\n  componentProps?: ComponentProps;\n  /**\n   * 所有表单项的控件样式\n   */\n  controlClass?: string;\n  /**\n   * 在表单项的Label后显示一个冒号\n   */\n  colon?: boolean;\n  /**\n   * 所有表单项的禁用状态\n   * @default false\n   */\n  disabled?: boolean;\n  /**\n   * 所有表单项的控件样式\n   * @default {}\n   */\n  formFieldProps?: Partial<typeof Field>;\n  /**\n   * 所有表单项的栅格布局\n   * @default \"\"\n   */\n  formItemClass?: (() => string) | string;\n  /**\n   * 隐藏所有表单项label\n   * @default false\n   */\n  hideLabel?: boolean;\n  /**\n   * 是否隐藏必填标记\n   * @default false\n   */\n  hideRequiredMark?: boolean;\n  /**\n   * 所有表单项的label样式\n   * @default \"\"\n   */\n  labelClass?: string;\n  /**\n   * 所有表单项的label宽度\n   */\n  labelWidth?: number;\n  /**\n   * 所有表单项的model属性名。使用自定义组件时可通过此配置指定组件的model属性名。已经在modelPropNameMap中注册的组件不受此配置影响\n   * @default \"modelValue\"\n   */\n  modelPropName?: string;\n  /**\n   * 所有表单项的wrapper样式\n   */\n  wrapperClass?: string;\n}\n```\n\n:::\n\n::: details FormSchema\n\n```ts\nexport interface FormSchema<\n  T extends BaseFormComponentType = BaseFormComponentType,\n> extends FormCommonConfig {\n  /** 组件 */\n  component: Component | T;\n  /** 组件参数 */\n  componentProps?: ComponentProps;\n  /** 默认值 */\n  defaultValue?: any;\n  /** 依赖 */\n  dependencies?: FormItemDependencies;\n  /** 描述 */\n  description?: string;\n  /** 字段名，也作为自定义插槽的名称 */\n  fieldName: string;\n  /** 帮助信息 */\n  help?: CustomRenderType;\n  /** 是否隐藏表单项 */\n  hide?: boolean;\n  /** 表单的标签（如果是一个string，会用于默认必选规则的消息提示） */\n  label?: CustomRenderType;\n  /** 自定义组件内部渲染  */\n  renderComponentContent?: RenderComponentContentType;\n  /** 字段规则 */\n  rules?: FormSchemaRuleType;\n  /** 后缀 */\n  suffix?: CustomRenderType;\n}\n```\n\n:::\n\n### 表单联动\n\n表单联动需要通过 schema 内的 `dependencies` 属性进行联动，允许您添加字段之间的依赖项，以根据其他字段的值控制字段。\n\n```ts\ndependencies: {\n  // 触发字段。只有这些字段值变动时，联动才会触发\n  triggerFields: ['name'],\n  // 动态判断当前字段是否需要显示，不显示则直接销毁\n  if(values,formApi){},\n  // 动态判断当前字段是否需要显示，不显示用css隐藏\n  show(values,formApi){},\n  // 动态判断当前字段是否需要禁用\n  disabled(values,formApi){},\n  // 字段变更时，都会触发该函数\n  trigger(values,formApi){},\n  // 动态rules\n  rules(values,formApi){},\n  // 动态必填\n  required(values,formApi){},\n  // 动态组件参数\n  componentProps(values,formApi){},\n}\n```\n\n### 表单校验\n\n表单校验需要通过 schema 内的 `rules` 属性进行配置。\n\nrules的值可以是字符串（预定义的校验规则名称），也可以是一个zod的schema。\n\n#### 预定义的校验规则\n\n```ts\n// 表示字段必填，默认会根据适配器的required进行国际化\n{\n  rules: 'required';\n}\n\n// 表示字段必填，默认会根据适配器的required进行国际化，用于下拉选择之类\n{\n  rules: 'selectRequired';\n}\n```\n\n#### zod\n\nrules也支持 zod 的 schema，可以进行更复杂的校验，zod 的使用请查看 [zod文档](https://zod.dev/)。\n\n```ts\nimport { z } from '#/adapter/form';\n\n// 基础类型\n{\n  rules: z.string().min(1, { message: '请输入字符串' });\n}\n\n// 可选(可以是undefined)，并且携带默认值。注意zod的optional不包括空字符串''\n{\n  rules: z.string().default('默认值').optional();\n}\n\n// 可以是空字符串、undefined或者一个邮箱地址(两种不同的用法)\n{\n  rules: z.union([z.string().email().optional(), z.literal('')]);\n}\n\n{\n  rules: z.string().email().or(z.literal('')).optional();\n}\n\n// 复杂校验\n{\n  z.string()\n    .min(1, { message: '请输入' })\n    .refine((value) => value === '123', {\n      message: '值必须为123',\n    });\n}\n```\n\n## Slots\n\n可以使用以下插槽在表单中插入自定义的内容\n\n| 插槽名        | 描述               |\n| ------------- | ------------------ |\n| reset-before  | 重置按钮之前的位置 |\n| submit-before | 提交按钮之前的位置 |\n| expand-before | 展开按钮之前的位置 |\n| expand-after  | 展开按钮之后的位置 |\n\n::: tip 字段插槽\n\n除了以上内置插槽之外，`schema`属性中每个字段的`fieldName`都可以作为插槽名称，这些字段插槽的优先级高于`component`定义的组件。也就是说，当提供了与`fieldName`同名的插槽时，这些插槽的内容将会作为这些字段的组件，此时`component`的值将会被忽略。\n\n:::\n"
  },
  {
    "path": "docs/src/components/common-ui/vben-modal.md",
    "content": "---\noutline: deep\n---\n\n# Vben Modal 模态框\n\n框架提供的模态框组件，支持`拖拽`、`全屏`、`自动高度`、`loading`等功能。\n\n> 如果文档内没有参数说明，可以尝试在在线示例内寻找\n\n::: info 写在前面\n\n如果你觉得现有组件的封装不够理想，或者不完全符合你的需求，大可以直接使用原生组件，亦或亲手封装一个适合的组件。框架提供的组件并非束缚，使用与否，完全取决于你的需求与自由。\n\n:::\n\n::: tip README\n\n下方示例代码中的，存在一些国际化、主题色未适配问题，这些问题只在文档内会出现，实际使用并不会有这些问题，可忽略，不必纠结。\n\n:::\n\n## 基础用法\n\n使用 `useVbenModal` 创建最基础的模态框。\n\n<DemoPreview dir=\"demos/vben-modal/basic\" />\n\n## 组件抽离\n\nModal 内的内容一般业务中，会比较复杂，所以我们可以将 modal 内的内容抽离出来，也方便复用。通过 `connectedComponent` 参数，可以将内外组件进行连接，而不用其他任何操作。\n\n<DemoPreview dir=\"demos/vben-modal/extra\" />\n\n## 开启拖拽\n\n通过 `draggable` 参数，可开启拖拽功能。\n\n<DemoPreview dir=\"demos/vben-modal/draggable\" />\n\n## 自动计算高度\n\n弹窗会自动计算内容高度，超过一定高度会出现滚动条，同时结合 `loading` 效果以及使用 `prepend-footer` 插槽。\n\n<DemoPreview dir=\"demos/vben-modal/auto-height\" />\n\n## 使用 Api\n\n通过 `modalApi` 可以调用 modal 的方法以及使用 `setState` 更新 modal 的状态。\n\n<DemoPreview dir=\"demos/vben-modal/dynamic\" />\n\n## 数据共享\n\n如果你使用了 `connectedComponent` 参数，那么内外组件会共享数据，比如一些表单回填等操作。可以用 `modalApi` 来获取数据和设置数据，配合 `onOpenChange`，可以满足大部分的需求。\n\n<DemoPreview dir=\"demos/vben-modal/shared-data\" />\n\n## 动画类型\n\n通过 `animationType` 属性可以控制弹窗的动画效果：\n\n- `slide`（默认）：从顶部向下滑动进入/退出\n- `scale`：缩放淡入/淡出效果\n\n<DemoPreview dir=\"demos/vben-modal/animation-type\" />\n\n::: info 注意\n\n- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`，那么 `setState` 将不会生效，这种情况下你可以通过 `slot` 或者 `props` 来更新状态。\n- 如果你使用到了 `connectedComponent` 参数，那么会存在 2 个`useVbenModal`, 此时，如果同时设置了相同的参数，那么以内部为准（也就是没有设置 connectedComponent 的代码）。比如 同时设置了 `onConfirm`，那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外，内外都会触发。另外，如果设置了`destroyOnClose`，内部Modal及其子组件会在被关闭后<b>完全销毁</b>。\n- 如果弹窗的默认行为不符合你的预期，可以在对应应用的 `apps/<app>/src/bootstrap.ts` 中修改 `setDefaultModalProps` 的参数来设置默认属性，例如默认隐藏全屏按钮、修改默认 `zIndex` 等。\n\n:::\n\n## API\n\n```ts\n// Modal 为弹窗组件\n// modalApi 为弹窗的方法\nconst [Modal, modalApi] = useVbenModal({\n  // 属性\n  // 事件\n});\n```\n\n### Props\n\n所有属性都可以传入 `useVbenModal` 的第一个参数中。\n\n| 属性名 | 描述 | 类型 | 默认值 |\n| --- | --- | --- | --- |\n| appendToMain | 是否挂载到内容区域（默认挂载到body） | `boolean` | `false` |\n| connectedComponent | 连接另一个Modal组件 | `Component` | - |\n| destroyOnClose | 关闭时销毁 | `boolean` | `false` |\n| title | 标题 | `string\\|slot` | - |\n| titleTooltip | 标题提示信息 | `string\\|slot` | - |\n| description | 描述信息 | `string\\|slot` | - |\n| isOpen | 弹窗打开状态 | `boolean` | `false` |\n| loading | 弹窗加载状态 | `boolean` | `false` |\n| fullscreen | 全屏显示 | `boolean` | `false` |\n| fullscreenButton | 显示全屏按钮 | `boolean` | `true` |\n| draggable | 可拖拽 | `boolean` | `false` |\n| closable | 显示关闭按钮 | `boolean` | `true` |\n| centered | 居中显示 | `boolean` | `false` |\n| modal | 显示遮罩 | `boolean` | `true` |\n| header | 显示header | `boolean` | `true` |\n| footer | 显示footer | `boolean\\|slot` | `true` |\n| confirmDisabled | 禁用确认按钮 | `boolean` | `false` |\n| confirmLoading | 确认按钮loading状态 | `boolean` | `false` |\n| closeOnClickModal | 点击遮罩关闭弹窗 | `boolean` | `true` |\n| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |\n| confirmText | 确认按钮文本 | `string\\|slot` | `确认` |\n| cancelText | 取消按钮文本 | `string\\|slot` | `取消` |\n| showCancelButton | 显示取消按钮 | `boolean` | `true` |\n| showConfirmButton | 显示确认按钮 | `boolean` | `true` |\n| class | modal的class，宽度通过这个配置 | `string` | - |\n| contentClass | modal内容区域的class | `string` | - |\n| footerClass | modal底部区域的class | `string` | - |\n| headerClass | modal顶部区域的class | `string` | - |\n| bordered | 是否显示border | `boolean` | `false` |\n| zIndex | 弹窗的ZIndex层级 | `number` | `1000` |\n| overlayBlur | 遮罩模糊度 | `number` | - |\n| animationType | 动画类型 | `'slide' \\| 'scale'` | `'slide'` |\n| submitting | 标记为提交中，锁定弹窗当前状态 | `boolean` | `false` |\n\n::: info appendToMain\n\n`appendToMain`可以指定将弹窗挂载到内容区域，打开这种弹窗时，内容区域以外的部分（标签栏、导航菜单等等）不会被遮挡。默认情况下，弹窗会挂载到body上。但是：挂载到内容区域时，作为页面根容器的`Page`组件，需要设置`auto-content-height`属性，以便弹窗能够正确计算高度。\n\n:::\n\n### Event\n\n以下事件，只有在 `useVbenModal({onCancel:()=>{}})` 中传入才会生效。\n\n| 事件名 | 描述 | 类型 | 版本号 |\n| --- | --- | --- | --- |\n| onBeforeClose | 关闭前触发，返回 `false`或者被`reject`则禁止关闭 | `()=>Promise<boolean>\\|boolean` | >5.5.2支持Promise |\n| onCancel | 点击取消按钮触发 | `()=>void` |  |\n| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.4.3 |\n| onConfirm | 点击确认按钮触发 | `()=>void` |  |\n| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` |  |\n| onOpened | 打开动画播放完毕时触发 | `()=>void` | >5.4.3 |\n\n### Slots\n\n除了上面的属性类型包含`slot`，还可以通过插槽来自定义弹窗的内容。\n\n| 插槽名         | 描述                                               |\n| -------------- | -------------------------------------------------- |\n| default        | 默认插槽 - 弹窗内容                                |\n| prepend-footer | 取消按钮左侧                                       |\n| center-footer  | 取消按钮和确认按钮中间（不使用 footer 插槽时有效） |\n| append-footer  | 确认按钮右侧                                       |\n\n### modalApi\n\n| 方法 | 描述 | 类型 | 版本 |\n| --- | --- | --- | --- |\n| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial<ModalState>)\\| Partial<ModalState>)=>modalApi` | - |\n| open | 打开弹窗 | `()=>void` | - |\n| close | 关闭弹窗 | `()=>void` | - |\n| setData | 设置共享数据 | `<T>(data:T)=>modalApi` | - |\n| getData | 获取共享数据 | `<T>()=>T` | - |\n| useStore | 获取可响应式状态 | - | - |\n| lock | 将弹窗标记为提交中，锁定当前状态 | `(isLock:boolean)=>modalApi` | >5.5.2 |\n| unlock | lock方法的反操作，解除弹窗的锁定状态，也是lock(false)的别名 | `()=>modalApi` | >5.5.3 |\n\n::: info lock\n\n`lock`方法用于锁定当前弹窗的状态，一般用于提交数据的过程中防止用户重复提交或者弹窗被意外关闭、表单数据被改变等等。当处于锁定状态时，弹窗的确认按钮会变为loading状态，同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭弹窗、开启弹窗的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的弹窗时，会自动解锁。要主动解除这种状态，可以调用`unlock`方法或者再次调用lock方法并传入false参数。\n\n:::\n"
  },
  {
    "path": "docs/src/components/common-ui/vben-vxe-table.md",
    "content": "---\noutline: deep\n---\n\n# Vben Vxe Table 表格\n\n`Vben Vxe Table` 基于 [vxe-table](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 和 `Vben Form` 做了二次封装，用于构建带搜索表单的列表页面。\n\n> 如果文档内没有覆盖到你需要的细节，可以结合在线示例和 [vxe-grid 官方 API](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 一起查看。\n\n::: info 写在前面\n\n如果现有封装不满足你的场景，可以直接使用原生 `vxe-table` 能力，或者在适配层中继续扩展。:::\n\n## 适配器\n\n底层表格基于 `vxe-table`，每个应用都可以在自己的适配层中配置默认行为、自定义渲染器以及与 UI 组件库的集成。\n\n### 适配器示例\n\n::: details vxe-table 表格适配器\n\n```ts\nimport { h } from 'vue';\n\nimport { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';\n\nimport { Button, Image } from 'ant-design-vue';\n\nimport { useVbenForm } from './form';\n\nsetupVbenVxeTable({\n  configVxeTable: (vxeUI) => {\n    vxeUI.setConfig({\n      grid: {\n        align: 'center',\n        border: false,\n        columnConfig: {\n          resizable: true,\n        },\n        minHeight: 180,\n        formConfig: {\n          enabled: false,\n        },\n        proxyConfig: {\n          autoLoad: true,\n          response: {\n            result: 'items',\n            total: 'total',\n            list: 'items',\n          },\n          showActiveMsg: true,\n          showResponseMsg: false,\n        },\n        round: true,\n        showOverflow: true,\n        size: 'small',\n      },\n    });\n\n    vxeUI.renderer.add('CellImage', {\n      renderTableDefault(_renderOpts, params) {\n        const { column, row } = params;\n        return h(Image, { src: row[column.field] });\n      },\n    });\n\n    vxeUI.renderer.add('CellLink', {\n      renderTableDefault(renderOpts) {\n        const { props } = renderOpts;\n        return h(\n          Button,\n          { size: 'small', type: 'link' },\n          { default: () => props?.text },\n        );\n      },\n    });\n  },\n  useVbenForm,\n});\n\nexport { useVbenVxeGrid };\n\nexport type * from '@vben/plugins/vxe-table';\n```\n\n:::\n\n## 基础表格\n\n通过 `useVbenVxeGrid` 创建一个基础表格。\n\n<DemoPreview dir=\"demos/vben-vxe-table/basic\" />\n\n## 远程加载\n\n通过配置 `proxyConfig.ajax.query` 实现远程数据加载。\n\n<DemoPreview dir=\"demos/vben-vxe-table/remote\" />\n\n## 树形表格\n\n树形表格的数据源通常是扁平结构，可以通过 `treeConfig` 转换为树形展示。\n\n```ts\ntreeConfig: {\n  transform: true,\n  parentField: 'parentId',\n  rowField: 'id',\n},\n```\n\n<DemoPreview dir=\"demos/vben-vxe-table/tree\" />\n\n## 固定列\n\n固定列可选值为 `'left' | 'right' | '' | null`。\n\n<DemoPreview dir=\"demos/vben-vxe-table/fixed\" />\n\n## 自定义单元格\n\n可以通过插槽或自定义渲染器实现单元格定制。\n\n```ts\nvxeUI.renderer.add('CellImage', {\n  renderTableDefault(_renderOpts, params) {\n    const { column, row } = params;\n    return h(Image, { src: row[column.field] } as any);\n  },\n});\n\nvxeUI.renderer.add('CellLink', {\n  renderTableDefault(renderOpts) {\n    const { props } = renderOpts;\n    return h(\n      Button,\n      { size: 'small', type: 'link' },\n      { default: () => props?.text },\n    );\n  },\n});\n```\n\n<DemoPreview dir=\"demos/vben-vxe-table/custom-cell\" />\n\n## 搜索表单\n\n搜索区域底层使用的是 `Vben Form`。启用搜索表单后，可以通过 `gridOptions.toolbarConfig.search = true` 在工具栏中显示搜索面板开关按钮。\n\n所有以 `form-` 开头的具名插槽都会自动转发到搜索表单。\n\n### 自定义分隔条\n\n启用搜索表单时，表单和表格主体之间默认会显示一个分隔条。可以通过 `separator` 调整或关闭它。\n\n```ts\nconst [Grid] = useVbenVxeGrid({\n  formOptions: {},\n  gridOptions: {},\n  separator: false,\n  // separator: { show: false },\n  // separator: { backgroundColor: 'rgba(100,100,0,0.5)' },\n});\n```\n\n<DemoPreview dir=\"demos/vben-vxe-table/form\" />\n\n## 单元格编辑\n\n通过设置 `editConfig.mode = 'cell'` 开启单元格编辑。\n\n<DemoPreview dir=\"demos/vben-vxe-table/edit-cell\" />\n\n## 行编辑\n\n通过设置 `editConfig.mode = 'row'` 开启整行编辑。\n\n<DemoPreview dir=\"demos/vben-vxe-table/edit-row\" />\n\n## 虚拟滚动\n\n通过 `scroll-y.enabled` 和 `scroll-y.gt` 组合开启纵向虚拟滚动。\n\n> 参考 [vxe-table 官方文档 - 虚拟滚动](https://vxetable.cn/v4/#/component/grid/scroll/vertical)\n\n<DemoPreview dir=\"demos/vben-vxe-table/virtual\" />\n\n## API\n\n`useVbenVxeGrid` 返回一个数组，第一个元素是表格组件，第二个元素是表格 API。\n\n```vue\n<script setup lang=\"ts\">\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nconst [Grid, gridApi] = useVbenVxeGrid({\n  gridOptions: {},\n  formOptions: {},\n  gridEvents: {},\n});\n</script>\n\n<template>\n  <Grid />\n</template>\n```\n\n### GridApi\n\n| 方法名 | 描述 | 类型 | 说明 |\n| --- | --- | --- | --- |\n| setLoading | 设置 loading 状态 | `(loading: boolean) => void` | - |\n| setGridOptions | 更新 `gridOptions` | `(options: Partial<VxeGridProps['gridOptions']>) => void` | - |\n| reload | 重新加载表格，并重置到初始分页 | `(params?: Record<string, any>) => void` | - |\n| query | 重新查询表格，保留当前分页 | `(params?: Record<string, any>) => void` | - |\n| grid | `vxe-grid` 实例 | `VxeGridInstance` | - |\n| formApi | 搜索表单 API 实例 | `FormApi` | - |\n| toggleSearchForm | 切换或指定搜索表单显示状态 | `(show?: boolean) => boolean` | 传入参数时强制设置；不传参数时在显示和隐藏之间切换，并返回当前状态 |\n\n## Props\n\n所有属性都通过 `useVbenVxeGrid` 的第一个参数传入。\n\n| 属性名 | 描述 | 类型 | 版本要求 |\n| --- | --- | --- | --- |\n| tableTitle | 表格标题 | `string` | - |\n| tableTitleHelp | 表格标题帮助信息 | `string` | - |\n| class | 外层容器的 class | `string` | - |\n| gridClass | `vxe-grid` 的 class | `string` | - |\n| gridOptions | `vxe-grid` 配置 | `DeepPartial<VxeTableGridOptions>` | - |\n| gridEvents | `vxe-grid` 事件 | `DeepPartial<VxeGridListeners>` | - |\n| formOptions | 搜索表单配置 | `VbenFormProps` | - |\n| showSearchForm | 是否显示搜索表单 | `boolean` | - |\n| separator | 搜索表单与表格主体之间的分隔条 | `boolean \\| SeparatorOptions` | `>5.5.4` |\n\n## Slots\n\n大部分插槽说明可参考 [vxe-table 官方文档](https://vxetable.cn/v4/#/grid/api)，这里列出封装层新增或约定的部分。\n\n| 插槽名          | 描述                                 |\n| --------------- | ------------------------------------ |\n| toolbar-actions | 工具栏左侧区域，位于标题附近         |\n| toolbar-tools   | 工具栏右侧区域，位于内置工具按钮左侧 |\n| table-title     | 自定义表格标题                       |\n\n::: info 搜索表单插槽\n\n当启用了搜索表单时，所有以 `form-` 开头的具名插槽都会被转发给表单。:::\n"
  },
  {
    "path": "docs/src/components/introduction.md",
    "content": "# 介绍\n\n::: info README\n\n该文档介绍的是框架组件的使用方法、属性、事件等。如果你觉得现有组件的封装不够理想，或者不完全符合你的需求，大可以直接使用原生组件，亦或亲手封装一个适合的组件。框架提供的组件并非束缚，使用与否，完全取决于你的需求与自由。\n\n:::\n\n## 布局组件\n\n布局组件一般在页面内容区域用作顶层容器组件，提供一些统一的布局样式和基本功能。\n\n## 通用组件\n\n通用组件是一些常用的组件，比如弹窗、抽屉、表单等。大部分基于 `Tailwind CSS` 实现，可适用于不同 UI 组件库的应用。\n"
  },
  {
    "path": "docs/src/components/layout-ui/page.md",
    "content": "---\noutline: deep\n---\n\n# Page 常规页面组件\n\n`Page` 是页面内容区最常用的顶层布局容器，内置了标题区、内容区和底部区三部分结构。\n\n::: info 写在前面\n\n这是一个基础页面容器。如果你的业务页面需要更复杂的布局，例如双列、侧边操作区或自定义滚动区域，建议在 `Page` 之上继续封装。:::\n\n## 基础用法\n\n直接将 `Page` 作为业务页面的根组件使用即可。\n\n### Props\n\n| 属性名 | 描述 | 类型 | 默认值 | 说明 |\n| --- | --- | --- | --- | --- |\n| title | 页面标题 | `string \\| slot` | - | - |\n| description | 页面描述 | `string \\| slot` | - | - |\n| contentClass | 内容区域的 class | `string` | - | - |\n| headerClass | 头部区域的 class | `string` | - | - |\n| footerClass | 底部区域的 class | `string` | - | - |\n| autoContentHeight | 根据可视内容高度自动计算内容区高度 | `boolean` | `false` | 开启后内容区会根据布局可视高度自动扣减头部和底部高度 |\n| heightOffset | 额外扣减的内容区高度偏移量 | `number` | `0` | 仅在 `autoContentHeight` 开启时生效，单位为 `px` |\n\n::: tip 注意\n\n如果 `title`、`description`、`extra` 三者都没有提供有效内容，无论是通过 `props` 还是 `slots`，头部区域都不会渲染。:::\n\n### Slots\n\n| 插槽名称    | 描述             |\n| ----------- | ---------------- |\n| default     | 页面内容         |\n| title       | 页面标题         |\n| description | 页面描述         |\n| extra       | 页面头部右侧内容 |\n| footer      | 页面底部内容     |\n"
  },
  {
    "path": "docs/src/demos/vben-alert/alert/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { h } from 'vue';\n\nimport { alert, VbenButton } from '@vben/common-ui';\n\nimport { Result } from 'ant-design-vue';\n\nfunction showAlert() {\n  alert('This is an alert message');\n}\n\nfunction showIconAlert() {\n  alert({\n    content: 'This is an alert message with icon',\n    icon: 'success',\n  });\n}\n\nfunction showCustomAlert() {\n  alert({\n    buttonAlign: 'center',\n    content: h(Result, {\n      status: 'success',\n      subTitle: '已成功创建订单。订单ID：2017182818828182881',\n      title: '操作成功',\n    }),\n  });\n}\n</script>\n<template>\n  <div class=\"flex gap-4\">\n    <VbenButton @click=\"showAlert\">Alert</VbenButton>\n    <VbenButton @click=\"showIconAlert\">Alert With Icon</VbenButton>\n    <VbenButton @click=\"showCustomAlert\">Alert With Custom Content</VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-alert/confirm/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { h, ref } from 'vue';\n\nimport { alert, confirm, VbenButton } from '@vben/common-ui';\n\nimport { Checkbox, message } from 'ant-design-vue';\n\nfunction showConfirm() {\n  confirm('This is an alert message')\n    .then(() => {\n      alert('Confirmed');\n    })\n    .catch(() => {\n      alert('Canceled');\n    });\n}\n\nfunction showIconConfirm() {\n  confirm({\n    content: 'This is an alert message with icon',\n    icon: 'success',\n  });\n}\n\nfunction showfooterConfirm() {\n  const checked = ref(false);\n  confirm({\n    cancelText: '不要虾扯蛋',\n    confirmText: '是的，我们都是NPC',\n    content:\n      '刚才发生的事情，为什么我似乎早就经历过一般？\\n我甚至能在事情发生过程中潜意识里预知到接下来会发生什么。\\n\\n听起来挺玄乎的，你有过这种感觉吗？',\n    footer: () =>\n      h(\n        Checkbox,\n        {\n          checked: checked.value,\n          class: 'flex-1',\n          'onUpdate:checked': (v) => (checked.value = v),\n        },\n        '不再提示',\n      ),\n    icon: 'question',\n    title: '未解之谜',\n  }).then(() => {\n    if (checked.value) {\n      message.success('我不会再拿这个问题烦你了');\n    } else {\n      message.info('下次还要继续问你哟');\n    }\n  });\n}\n\nfunction showAsyncConfirm() {\n  confirm({\n    beforeClose({ isConfirm }) {\n      if (isConfirm) {\n        // 这里可以执行一些异步操作。如果最终返回了false，将阻止关闭弹窗\n        return new Promise((resolve) => setTimeout(resolve, 2000));\n      }\n    },\n    content: 'This is an alert message with async confirm',\n    icon: 'success',\n  }).then(() => {\n    alert('Confirmed');\n  });\n}\n</script>\n<template>\n  <div class=\"flex gap-4\">\n    <VbenButton @click=\"showConfirm\">Confirm</VbenButton>\n    <VbenButton @click=\"showIconConfirm\">Confirm With Icon</VbenButton>\n    <VbenButton @click=\"showfooterConfirm\">Confirm With Footer</VbenButton>\n    <VbenButton @click=\"showAsyncConfirm\">Async Confirm</VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-alert/prompt/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { h } from 'vue';\n\nimport { alert, prompt, useAlertContext, VbenButton } from '@vben/common-ui';\n\nimport { Input, RadioGroup, Select } from 'ant-design-vue';\nimport { BadgeJapaneseYen } from 'lucide-vue-next';\n\nfunction showPrompt() {\n  prompt({\n    content: '请输入一些东西',\n  })\n    .then((val) => {\n      alert(`已收到你的输入：${val}`);\n    })\n    .catch(() => {\n      alert('Canceled');\n    });\n}\n\nfunction showSlotsPrompt() {\n  prompt({\n    component: () => {\n      // 获取弹窗上下文。注意：只能在setup或者函数式组件中调用\n      const { doConfirm } = useAlertContext();\n      return h(\n        Input,\n        {\n          onKeydown(e: KeyboardEvent) {\n            if (e.key === 'Enter') {\n              e.preventDefault();\n              // 调用弹窗提供的确认方法\n              doConfirm();\n            }\n          },\n          placeholder: '请输入',\n          prefix: '充值金额：',\n          type: 'number',\n        },\n        {\n          addonAfter: () => h(BadgeJapaneseYen),\n        },\n      );\n    },\n    content:\n      '此弹窗演示了如何使用自定义插槽，并且可以使用useAlertContext获取到弹窗的上下文。\\n在输入框中按下回车键会触发确认操作。',\n    icon: 'question',\n    modelPropName: 'value',\n  }).then((val) => {\n    if (val) alert(`你输入的是${val}`);\n  });\n}\n\nfunction showSelectPrompt() {\n  prompt({\n    component: Select,\n    componentProps: {\n      options: [\n        { label: 'Option A', value: 'Option A' },\n        { label: 'Option B', value: 'Option B' },\n        { label: 'Option C', value: 'Option C' },\n      ],\n      placeholder: '请选择',\n      // 弹窗会设置body的pointer-events为none，这回影响下拉框的点击事件\n      popupClassName: 'pointer-events-auto',\n    },\n    content: '此弹窗演示了如何使用component传递自定义组件',\n    icon: 'question',\n    modelPropName: 'value',\n  }).then((val) => {\n    if (val) {\n      alert(`你选择了${val}`);\n    }\n  });\n}\n\nfunction sleep(ms: number) {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction showAsyncPrompt() {\n  prompt({\n    async beforeClose(scope) {\n      if (scope.isConfirm) {\n        if (scope.value) {\n          // 模拟异步操作，如果不成功，可以返回false\n          await sleep(2000);\n        } else {\n          alert('请选择一个选项');\n          return false;\n        }\n      }\n    },\n    component: RadioGroup,\n    componentProps: {\n      class: 'flex flex-col',\n      options: [\n        { label: 'Option 1', value: 'option1' },\n        { label: 'Option 2', value: 'option2' },\n        { label: 'Option 3', value: 'option3' },\n      ],\n    },\n    content: '选择一个选项后再点击[确认]',\n    icon: 'question',\n    modelPropName: 'value',\n  }).then((val) => {\n    alert(`${val} 已设置。`);\n  });\n}\n</script>\n<template>\n  <div class=\"flex gap-4\">\n    <VbenButton @click=\"showPrompt\">Prompt</VbenButton>\n    <VbenButton @click=\"showSlotsPrompt\"> Prompt With slots </VbenButton>\n    <VbenButton @click=\"showSelectPrompt\">Prompt With Select</VbenButton>\n    <VbenButton @click=\"showAsyncPrompt\">Prompt With Async</VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-api-component/cascader/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ApiComponent } from '@vben/common-ui';\n\nimport { Cascader } from 'ant-design-vue';\n\nconst treeData: Record<string, any> = [\n  {\n    label: '浙江',\n    value: 'zhejiang',\n    children: [\n      {\n        value: 'hangzhou',\n        label: '杭州',\n        children: [\n          {\n            value: 'xihu',\n            label: '西湖',\n          },\n          {\n            value: 'sudi',\n            label: '苏堤',\n          },\n        ],\n      },\n      {\n        value: 'jiaxing',\n        label: '嘉兴',\n        children: [\n          {\n            value: 'wuzhen',\n            label: '乌镇',\n          },\n          {\n            value: 'meihuazhou',\n            label: '梅花洲',\n          },\n        ],\n      },\n      {\n        value: 'zhoushan',\n        label: '舟山',\n        children: [\n          {\n            value: 'putuoshan',\n            label: '普陀山',\n          },\n          {\n            value: 'taohuadao',\n            label: '桃花岛',\n          },\n        ],\n      },\n    ],\n  },\n  {\n    label: '江苏',\n    value: 'jiangsu',\n    children: [\n      {\n        value: 'nanjing',\n        label: '南京',\n        children: [\n          {\n            value: 'zhonghuamen',\n            label: '中华门',\n          },\n          {\n            value: 'zijinshan',\n            label: '紫金山',\n          },\n          {\n            value: 'yuhuatai',\n            label: '雨花台',\n          },\n        ],\n      },\n    ],\n  },\n];\n/**\n * 模拟请求接口\n */\nfunction fetchApi(): Promise<Record<string, any>> {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve(treeData);\n    }, 1000);\n  });\n}\n</script>\n<template>\n  <ApiComponent\n    :api=\"fetchApi\"\n    :component=\"Cascader\"\n    :immediate=\"false\"\n    children-field=\"children\"\n    loading-slot=\"suffixIcon\"\n    visible-event=\"onDropdownVisibleChange\"\n  />\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-count-to-animator/basic/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { VbenCountToAnimator } from '@vben/common-ui';\n</script>\n<template>\n  <VbenCountToAnimator :duration=\"3000\" :end-val=\"30000\" :start-val=\"1\" />\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-count-to-animator/custom/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { VbenCountToAnimator } from '@vben/common-ui';\n</script>\n<template>\n  <VbenCountToAnimator\n    :duration=\"3000\"\n    :end-val=\"2000000\"\n    :start-val=\"1\"\n    prefix=\"$\"\n    separator=\"/\"\n  />\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-drawer/auto-height/drawer.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { useVbenDrawer, VbenButton } from '@vben/common-ui';\n\nconst list = ref<number[]>([]);\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  onCancel() {\n    drawerApi.close();\n  },\n  onConfirm() {\n    console.log('onConfirm');\n  },\n  onOpenChange(isOpen) {\n    if (isOpen) {\n      handleUpdate(10);\n    }\n  },\n});\n\nfunction handleUpdate(len: number) {\n  drawerApi.setState({ loading: true });\n  setTimeout(() => {\n    list.value = Array.from({ length: len }, (_v, k) => k + 1);\n    drawerApi.setState({ loading: false });\n  }, 2000);\n}\n</script>\n<template>\n  <Drawer title=\"自动计算高度\">\n    <div\n      v-for=\"item in list\"\n      :key=\"item\"\n      class=\"flex-center h-55 w-full bg-muted even:bg-heavy\"\n    >\n      {{ item }}\n    </div>\n    <template #prepend-footer>\n      <VbenButton type=\"link\" @click=\"handleUpdate(6)\">\n        点击更新数据\n      </VbenButton>\n    </template>\n  </Drawer>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-drawer/auto-height/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenDrawer, VbenButton } from '@vben/common-ui';\n\nimport ExtraDrawer from './drawer.vue';\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  // 连接抽离的组件\n  connectedComponent: ExtraDrawer,\n});\n\nfunction open() {\n  drawerApi.open();\n}\n</script>\n\n<template>\n  <div>\n    <Drawer />\n    <VbenButton @click=\"open\">Open</VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-drawer/basic/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenDrawer, VbenButton } from '@vben/common-ui';\n\nconst [Drawer, drawerApi] = useVbenDrawer();\n</script>\n<template>\n  <div>\n    <VbenButton @click=\"() => drawerApi.open()\">Open</VbenButton>\n    <Drawer class=\"w-150\" title=\"基础示例\"> drawer content </Drawer>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-drawer/dynamic/drawer.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenDrawer, VbenButton } from '@vben/common-ui';\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  onCancel() {\n    drawerApi.close();\n  },\n  onConfirm() {\n    console.info('onConfirm');\n  },\n  title: '动态修改配置示例',\n});\n\nfunction handleUpdateTitle() {\n  drawerApi.setState({ title: '内部动态标题' });\n}\n</script>\n<template>\n  <Drawer>\n    <div class=\"flex-col-center\">\n      <VbenButton class=\"mb-3\" type=\"primary\" @click=\"handleUpdateTitle()\">\n        内部动态修改标题\n      </VbenButton>\n    </div>\n  </Drawer>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-drawer/dynamic/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenDrawer, VbenButton } from '@vben/common-ui';\n\nimport ExtraDrawer from './drawer.vue';\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  // 连接抽离的组件\n  connectedComponent: ExtraDrawer,\n});\n\nfunction open() {\n  drawerApi.open();\n}\n\nfunction handleUpdateTitle() {\n  drawerApi.setState({ title: '外部动态标题' }).open();\n}\n</script>\n\n<template>\n  <div>\n    <Drawer />\n\n    <VbenButton @click=\"open\">Open</VbenButton>\n    <VbenButton class=\"ml-2\" type=\"primary\" @click=\"handleUpdateTitle\">\n      从外部修改标题并打开\n    </VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-drawer/extra/drawer.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenDrawer } from '@vben/common-ui';\n\nconst [Drawer] = useVbenDrawer();\n</script>\n<template>\n  <Drawer title=\"组件抽离示例\"> extra drawer content </Drawer>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-drawer/extra/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenDrawer, VbenButton } from '@vben/common-ui';\n\nimport ExtraDrawer from './drawer.vue';\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  // 连接抽离的组件\n  connectedComponent: ExtraDrawer,\n});\n\nfunction open() {\n  drawerApi.open();\n}\n</script>\n\n<template>\n  <div>\n    <Drawer />\n    <VbenButton @click=\"open\">Open</VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-drawer/shared-data/drawer.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { useVbenDrawer } from '@vben/common-ui';\n\nconst data = ref();\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  onCancel() {\n    drawerApi.close();\n  },\n  onConfirm() {\n    console.info('onConfirm');\n  },\n  onOpenChange(isOpen: boolean) {\n    if (isOpen) {\n      data.value = drawerApi.getData<Record<string, any>>();\n    }\n  },\n});\n</script>\n<template>\n  <Drawer title=\"数据共享示例\">\n    <div class=\"flex-col-center\">外部传递数据： {{ data }}</div>\n  </Drawer>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-drawer/shared-data/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenDrawer, VbenButton } from '@vben/common-ui';\n\nimport ExtraDrawer from './drawer.vue';\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  // 连接抽离的组件\n  connectedComponent: ExtraDrawer,\n});\n\nfunction open() {\n  drawerApi\n    .setData({\n      content: '外部传递的数据 content',\n      payload: '外部传递的数据 payload',\n    })\n    .open();\n}\n</script>\n\n<template>\n  <div>\n    <Drawer />\n\n    <VbenButton @click=\"open\">Open</VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-ellipsis-text/auto-display/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { EllipsisText } from '@vben/common-ui';\n\nconst text = `\nVben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。\n`;\n</script>\n<template>\n  <EllipsisText :line=\"2\" :tooltip-when-ellipsis=\"true\">\n    {{ text }}\n  </EllipsisText>\n\n  <EllipsisText :line=\"3\" :tooltip-when-ellipsis=\"true\">\n    {{ text }}\n  </EllipsisText>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-ellipsis-text/expand/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { EllipsisText } from '@vben/common-ui';\n\nconst text = `\nVben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。\n`;\n</script>\n<template>\n  <EllipsisText :line=\"3\" expand>{{ text }}</EllipsisText>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-ellipsis-text/line/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { EllipsisText } from '@vben/common-ui';\n\nconst text = `\nVben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。\n`;\n</script>\n<template>\n  <EllipsisText :max-width=\"500\">{{ text }}</EllipsisText>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-ellipsis-text/tooltip/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { EllipsisText } from '@vben/common-ui';\n</script>\n<template>\n  <EllipsisText :max-width=\"240\">\n    住在我心里孤独的 孤独的海怪 痛苦之王 开始厌倦 深海的光 停滞的海浪\n    <template #tooltip>\n      <div style=\"text-align: center\">\n        《秦皇岛》<br />住在我心里孤独的<br />孤独的海怪 痛苦之王<br />开始厌倦\n        深海的光 停滞的海浪\n      </div>\n    </template>\n  </EllipsisText>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-form/api/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Button, message, Space } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst [BaseForm, formApi] = useVbenForm({\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  // 使用 tailwindcss grid布局\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  layout: 'horizontal',\n  // 水平布局，label和input在同一行\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入用户名',\n      },\n      // 字段名\n      fieldName: 'field1',\n      // 界面显示的label\n      label: 'field1',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      fieldName: 'fieldOptions',\n      label: '下拉选',\n    },\n  ],\n  wrapperClass: 'grid-cols-1 md:grid-cols-2',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n\nfunction handleClick(\n  action:\n    | 'batchAddSchema'\n    | 'batchDeleteSchema'\n    | 'disabled'\n    | 'hiddenAction'\n    | 'hiddenResetButton'\n    | 'hiddenSubmitButton'\n    | 'labelWidth'\n    | 'resetDisabled'\n    | 'resetLabelWidth'\n    | 'showAction'\n    | 'showResetButton'\n    | 'showSubmitButton'\n    | 'updateActionAlign'\n    | 'updateResetButton'\n    | 'updateSchema'\n    | 'updateSubmitButton',\n) {\n  switch (action) {\n    case 'batchAddSchema': {\n      formApi.setState((prev) => {\n        const currentSchema = prev?.schema ?? [];\n        const newSchema = [];\n        for (let i = 0; i < 2; i++) {\n          newSchema.push({\n            component: 'Input',\n            componentProps: {\n              placeholder: '请输入',\n            },\n            fieldName: `field${i}${Date.now()}`,\n            label: `field+`,\n          });\n        }\n        return {\n          schema: [...currentSchema, ...newSchema],\n        };\n      });\n      break;\n    }\n\n    case 'batchDeleteSchema': {\n      formApi.setState((prev) => {\n        const currentSchema = prev?.schema ?? [];\n        return {\n          schema: currentSchema.slice(0, -2),\n        };\n      });\n      break;\n    }\n    case 'disabled': {\n      formApi.setState({ commonConfig: { disabled: true } });\n      break;\n    }\n    case 'hiddenAction': {\n      formApi.setState({ showDefaultActions: false });\n      break;\n    }\n    case 'hiddenResetButton': {\n      formApi.setState({ resetButtonOptions: { show: false } });\n      break;\n    }\n    case 'hiddenSubmitButton': {\n      formApi.setState({ submitButtonOptions: { show: false } });\n      break;\n    }\n    case 'labelWidth': {\n      formApi.setState({\n        commonConfig: {\n          labelWidth: 150,\n        },\n      });\n      break;\n    }\n    case 'resetDisabled': {\n      formApi.setState({ commonConfig: { disabled: false } });\n      break;\n    }\n    case 'resetLabelWidth': {\n      formApi.setState({\n        commonConfig: {\n          labelWidth: 100,\n        },\n      });\n      break;\n    }\n    case 'showAction': {\n      formApi.setState({ showDefaultActions: true });\n      break;\n    }\n    case 'showResetButton': {\n      formApi.setState({ resetButtonOptions: { show: true } });\n      break;\n    }\n    case 'showSubmitButton': {\n      formApi.setState({ submitButtonOptions: { show: true } });\n      break;\n    }\n    case 'updateActionAlign': {\n      formApi.setState({\n        // 可以自行调整class\n        actionWrapperClass: 'text-center',\n      });\n      break;\n    }\n    case 'updateResetButton': {\n      formApi.setState({\n        resetButtonOptions: { disabled: true },\n      });\n      break;\n    }\n    case 'updateSchema': {\n      formApi.updateSchema([\n        {\n          componentProps: {\n            options: [\n              {\n                label: '选项1',\n                value: '1',\n              },\n              {\n                label: '选项2',\n                value: '2',\n              },\n              {\n                label: '选项3',\n                value: '3',\n              },\n            ],\n          },\n          fieldName: 'fieldOptions',\n        },\n      ]);\n      message.success('字段 `fieldOptions` 下拉选项更新成功。');\n      break;\n    }\n    case 'updateSubmitButton': {\n      formApi.setState({\n        submitButtonOptions: { loading: true },\n      });\n      break;\n    }\n  }\n}\n</script>\n\n<template>\n  <div>\n    <Space class=\"mb-5 flex-wrap\">\n      <Button @click=\"handleClick('updateSchema')\">updateSchema</Button>\n      <Button @click=\"handleClick('labelWidth')\">更改labelWidth</Button>\n      <Button @click=\"handleClick('resetLabelWidth')\">还原labelWidth</Button>\n      <Button @click=\"handleClick('disabled')\">禁用表单</Button>\n      <Button @click=\"handleClick('resetDisabled')\">解除禁用</Button>\n      <Button @click=\"handleClick('hiddenAction')\">隐藏操作按钮</Button>\n      <Button @click=\"handleClick('showAction')\">显示操作按钮</Button>\n      <Button @click=\"handleClick('hiddenResetButton')\">隐藏重置按钮</Button>\n      <Button @click=\"handleClick('showResetButton')\">显示重置按钮</Button>\n      <Button @click=\"handleClick('hiddenSubmitButton')\">隐藏提交按钮</Button>\n      <Button @click=\"handleClick('showSubmitButton')\">显示提交按钮</Button>\n      <Button @click=\"handleClick('updateResetButton')\">修改重置按钮</Button>\n      <Button @click=\"handleClick('updateSubmitButton')\">修改提交按钮</Button>\n      <Button @click=\"handleClick('updateActionAlign')\">\n        调整操作按钮位置\n      </Button>\n      <Button @click=\"handleClick('batchAddSchema')\"> 批量添加表单项 </Button>\n      <Button @click=\"handleClick('batchDeleteSchema')\">\n        批量删除表单项\n      </Button>\n    </Space>\n    <BaseForm />\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-form/basic/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { message } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst [BaseForm] = useVbenForm({\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  // 水平布局，label和input在同一行\n  layout: 'horizontal',\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入用户名',\n      },\n      // 字段名\n      fieldName: 'username',\n      // 界面显示的label\n      label: '字符串',\n    },\n    {\n      component: 'InputPassword',\n      componentProps: {\n        placeholder: '请输入密码',\n      },\n      fieldName: 'password',\n      label: '密码',\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'number',\n      label: '数字(带后缀)',\n      suffix: () => '¥',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      fieldName: 'options',\n      label: '下拉选',\n    },\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n      },\n      fieldName: 'radioGroup',\n      label: '单选组',\n    },\n    {\n      component: 'Radio',\n      fieldName: 'radio',\n      label: '',\n      renderComponentContent: () => {\n        return {\n          default: () => ['Radio'],\n        };\n      },\n    },\n    {\n      component: 'CheckboxGroup',\n      componentProps: {\n        name: 'cname',\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n      },\n      fieldName: 'checkboxGroup',\n      label: '多选组',\n    },\n    {\n      component: 'Checkbox',\n      fieldName: 'checkbox',\n      label: '',\n      renderComponentContent: () => {\n        return {\n          default: () => ['我已阅读并同意'],\n        };\n      },\n    },\n    {\n      component: 'Mentions',\n      componentProps: {\n        options: [\n          {\n            label: 'afc163',\n            value: 'afc163',\n          },\n          {\n            label: 'zombieJ',\n            value: 'zombieJ',\n          },\n        ],\n        placeholder: '请输入',\n      },\n      fieldName: 'mentions',\n      label: '提及',\n    },\n    {\n      component: 'Rate',\n      fieldName: 'rate',\n      label: '评分',\n    },\n    {\n      component: 'Switch',\n      componentProps: {\n        class: 'w-auto',\n      },\n      fieldName: 'switch',\n      label: '开关',\n    },\n    {\n      component: 'DatePicker',\n      fieldName: 'datePicker',\n      label: '日期选择框',\n    },\n    {\n      component: 'RangePicker',\n      fieldName: 'rangePicker',\n      label: '范围选择器',\n    },\n    {\n      component: 'TimePicker',\n      fieldName: 'timePicker',\n      label: '时间选择框',\n    },\n    {\n      component: 'TreeSelect',\n      componentProps: {\n        allowClear: true,\n        placeholder: '请选择',\n        showSearch: true,\n        treeData: [\n          {\n            label: 'root 1',\n            value: 'root 1',\n            children: [\n              {\n                label: 'parent 1',\n                value: 'parent 1',\n                children: [\n                  {\n                    label: 'parent 1-0',\n                    value: 'parent 1-0',\n                    children: [\n                      {\n                        label: 'my leaf',\n                        value: 'leaf1',\n                      },\n                      {\n                        label: 'your leaf',\n                        value: 'leaf2',\n                      },\n                    ],\n                  },\n                  {\n                    label: 'parent 1-1',\n                    value: 'parent 1-1',\n                  },\n                ],\n              },\n              {\n                label: 'parent 2',\n                value: 'parent 2',\n              },\n            ],\n          },\n        ],\n        treeNodeFilterProp: 'label',\n      },\n      fieldName: 'treeSelect',\n      label: '树选择',\n    },\n  ],\n  wrapperClass: 'grid-cols-1',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n</script>\n\n<template>\n  <BaseForm />\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-form/custom/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { h } from 'vue';\n\nimport { Input, message } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst [Form] = useVbenForm({\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n    labelClass: 'w-2/6',\n  },\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  // 水平布局，label和input在同一行\n  layout: 'horizontal',\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      fieldName: 'field',\n      label: '自定义后缀',\n      suffix: () => h('span', { class: 'text-red-600' }, '元'),\n    },\n    {\n      component: 'Input',\n      fieldName: 'field1',\n      label: '自定义组件slot',\n      renderComponentContent: () => ({\n        prefix: () => 'prefix',\n        suffix: () => 'suffix',\n      }),\n    },\n    {\n      component: h(Input, { placeholder: '请输入' }),\n      fieldName: 'field2',\n      label: '自定义组件',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      fieldName: 'field3',\n      label: '自定义组件(slot)',\n      rules: 'required',\n    },\n  ],\n  wrapperClass: 'grid-cols-1',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n</script>\n\n<template>\n  <Form>\n    <template #field3=\"slotProps\">\n      <Input placeholder=\"请输入\" v-bind=\"slotProps\" />\n    </template>\n  </Form>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-form/dynamic/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { message } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst [Form] = useVbenForm({\n  // 提交函数\n  handleSubmit: onSubmit,\n  schema: [\n    {\n      component: 'Input',\n      defaultValue: 'hidden value',\n      dependencies: {\n        show: false,\n        // 随意一个字段改变时，都会触发\n        triggerFields: ['field1Switch'],\n      },\n      fieldName: 'hiddenField',\n      label: '隐藏字段',\n    },\n    {\n      component: 'Switch',\n      defaultValue: true,\n      fieldName: 'field1Switch',\n      help: '通过Dom控制销毁',\n      label: '显示字段1',\n    },\n    {\n      component: 'Switch',\n      defaultValue: true,\n      fieldName: 'field2Switch',\n      help: '通过css控制隐藏',\n      label: '显示字段2',\n    },\n    {\n      component: 'Switch',\n      fieldName: 'field3Switch',\n      label: '禁用字段3',\n    },\n    {\n      component: 'Switch',\n      fieldName: 'field4Switch',\n      label: '字段4必填',\n    },\n    {\n      component: 'Input',\n      dependencies: {\n        if(values) {\n          return !!values.field1Switch;\n        },\n        // 只有指定的字段改变时，才会触发\n        triggerFields: ['field1Switch'],\n      },\n      // 字段名\n      fieldName: 'field1',\n      // 界面显示的label\n      label: '字段1',\n    },\n    {\n      component: 'Input',\n      dependencies: {\n        show(values) {\n          return !!values.field2Switch;\n        },\n        triggerFields: ['field2Switch'],\n      },\n      fieldName: 'field2',\n      label: '字段2',\n    },\n    {\n      component: 'Input',\n      dependencies: {\n        disabled(values) {\n          return !!values.field3Switch;\n        },\n        triggerFields: ['field3Switch'],\n      },\n      fieldName: 'field3',\n      label: '字段3',\n    },\n    {\n      component: 'Input',\n      dependencies: {\n        required(values) {\n          return !!values.field4Switch;\n        },\n        triggerFields: ['field4Switch'],\n      },\n      fieldName: 'field4',\n      label: '字段4',\n    },\n    {\n      component: 'Input',\n      dependencies: {\n        rules(values) {\n          if (values.field1 === '123') {\n            return 'required';\n          }\n          return null;\n        },\n        triggerFields: ['field1'],\n      },\n      fieldName: 'field5',\n      help: '当字段1的值为`123`时，必填',\n      label: '动态rules',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        class: 'w-full',\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      dependencies: {\n        componentProps(values) {\n          if (values.field2 === '123') {\n            return {\n              options: [\n                {\n                  label: '选项1',\n                  value: '1',\n                },\n                {\n                  label: '选项2',\n                  value: '2',\n                },\n                {\n                  label: '选项3',\n                  value: '3',\n                },\n              ],\n            };\n          }\n          return {};\n        },\n        triggerFields: ['field2'],\n      },\n      fieldName: 'field6',\n      help: '当字段2的值为`123`时，更改下拉选项',\n      label: '动态配置',\n    },\n  ],\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-2',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n</script>\n\n<template>\n  <Form />\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-form/query/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { message } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst [QueryForm] = useVbenForm({\n  // 默认展开\n  collapsed: false,\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  // 水平布局，label和input在同一行\n  layout: 'horizontal',\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入用户名',\n      },\n      // 字段名\n      fieldName: 'username',\n      // 界面显示的label\n      label: '字符串',\n    },\n    {\n      component: 'InputPassword',\n      componentProps: {\n        placeholder: '请输入密码',\n      },\n      fieldName: 'password',\n      label: '密码',\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'number',\n      label: '数字(带后缀)',\n      suffix: () => '¥',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      fieldName: 'options',\n      label: '下拉选',\n    },\n    {\n      component: 'DatePicker',\n      fieldName: 'datePicker',\n      label: '日期选择框',\n    },\n  ],\n  // 是否可展开\n  showCollapseButton: true,\n  submitButtonOptions: {\n    content: '查询',\n  },\n  wrapperClass: 'grid-cols-1 md:grid-cols-2',\n});\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n</script>\n\n<template>\n  <QueryForm />\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-form/rules/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { message } from 'ant-design-vue';\n\nimport { useVbenForm, z } from '#/adapter/form';\n\nconst [Form] = useVbenForm({\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  // 水平布局，label和input在同一行\n  scrollToFirstError: true,\n  layout: 'horizontal',\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入',\n      },\n      // 字段名\n      fieldName: 'field1',\n      // 界面显示的label\n      label: '字段1',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      defaultValue: '默认值',\n      fieldName: 'field2',\n      label: '默认值(必填)',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field3',\n      label: '默认值(非必填)',\n      rules: z.string().default('默认值').optional(),\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field31',\n      label: '自定义信息',\n      rules: z.string().min(1, { message: '最少输入1个字符' }),\n    },\n    {\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入',\n      },\n      // 字段名\n      fieldName: 'field4',\n      // 界面显示的label\n      label: '邮箱',\n      rules: z.string().email('请输入正确的邮箱'),\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'number',\n      label: '数字',\n      rules: 'required',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      defaultValue: undefined,\n      fieldName: 'options',\n      label: '下拉选',\n      rules: 'selectRequired',\n    },\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n      },\n      fieldName: 'radioGroup',\n      label: '单选组',\n      rules: 'selectRequired',\n    },\n    {\n      component: 'CheckboxGroup',\n      componentProps: {\n        name: 'cname',\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n      },\n      fieldName: 'checkboxGroup',\n      label: '多选组',\n      rules: 'selectRequired',\n    },\n    {\n      component: 'Checkbox',\n      fieldName: 'checkbox',\n      label: '',\n      renderComponentContent: () => {\n        return {\n          default: () => ['我已阅读并同意'],\n        };\n      },\n      rules: 'selectRequired',\n    },\n    {\n      component: 'DatePicker',\n      defaultValue: undefined,\n      fieldName: 'datePicker',\n      label: '日期选择框',\n      rules: 'selectRequired',\n    },\n    {\n      component: 'RangePicker',\n      defaultValue: undefined,\n      fieldName: 'rangePicker',\n      label: '区间选择框',\n      rules: 'selectRequired',\n    },\n    {\n      component: 'InputPassword',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'password',\n      label: '密码',\n      rules: 'required',\n    },\n  ],\n  wrapperClass: 'grid-cols-1',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n</script>\n\n<template>\n  <Form />\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/animation-type/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal, VbenButton } from '@vben/common-ui';\n\nconst [SlideModal, slideModalApi] = useVbenModal({\n  animationType: 'slide',\n});\n\nconst [ScaleModal, scaleModalApi] = useVbenModal({\n  animationType: 'scale',\n});\n\nfunction openSlideModal() {\n  slideModalApi.open();\n}\n\nfunction openScaleModal() {\n  scaleModalApi.open();\n}\n</script>\n\n<template>\n  <div class=\"space-y-4\">\n    <div class=\"flex gap-4\">\n      <VbenButton @click=\"openSlideModal\">滑动动画</VbenButton>\n      <VbenButton @click=\"openScaleModal\">缩放动画</VbenButton>\n    </div>\n\n    <SlideModal title=\"滑动动画示例\" class=\"w-125\">\n      <p>这是使用滑动动画的弹窗，从顶部向下滑动进入。</p>\n    </SlideModal>\n\n    <ScaleModal title=\"缩放动画示例\" class=\"w-125\">\n      <p>这是使用缩放动画的弹窗，以缩放淡入淡出的方式显示。</p>\n    </ScaleModal>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/auto-height/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal, VbenButton } from '@vben/common-ui';\n\nimport ExtraModal from './modal.vue';\n\nconst [Modal, modalApi] = useVbenModal({\n  // 连接抽离的组件\n  connectedComponent: ExtraModal,\n});\n\nfunction openModal() {\n  modalApi.open();\n}\n</script>\n\n<template>\n  <div>\n    <Modal />\n    <VbenButton @click=\"openModal\">Open</VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/auto-height/modal.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { useVbenModal, VbenButton } from '@vben/common-ui';\n\nconst list = ref<number[]>([]);\n\nconst [Modal, modalApi] = useVbenModal({\n  onCancel() {\n    modalApi.close();\n  },\n  onConfirm() {\n    console.log('onConfirm');\n  },\n  onOpenChange(isOpen) {\n    if (isOpen) {\n      handleUpdate(10);\n    }\n  },\n});\n\nfunction handleUpdate(len: number) {\n  modalApi.setState({ loading: true });\n  setTimeout(() => {\n    list.value = Array.from({ length: len }, (_v, k) => k + 1);\n    modalApi.setState({ loading: false });\n  }, 2000);\n}\n</script>\n<template>\n  <Modal title=\"自动计算高度\">\n    <div\n      v-for=\"item in list\"\n      :key=\"item\"\n      class=\"flex-center h-55 w-full bg-muted even:bg-heavy\"\n    >\n      {{ item }}\n    </div>\n    <template #prepend-footer>\n      <VbenButton type=\"link\" @click=\"handleUpdate(6)\">\n        点击更新数据\n      </VbenButton>\n    </template>\n  </Modal>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/basic/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal, VbenButton } from '@vben/common-ui';\n\nconst [Modal, modalApi] = useVbenModal();\n</script>\n<template>\n  <div>\n    <VbenButton @click=\"() => modalApi.open()\">Open</VbenButton>\n    <Modal class=\"w-150\" title=\"基础示例\"> modal content </Modal>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/draggable/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal, VbenButton } from '@vben/common-ui';\n\nimport ExtraModal from './modal.vue';\n\nconst [Modal, modalApi] = useVbenModal({\n  // 连接抽离的组件\n  connectedComponent: ExtraModal,\n});\n\nfunction openModal() {\n  modalApi.open();\n}\n</script>\n\n<template>\n  <div>\n    <Modal />\n    <VbenButton @click=\"openModal\">Open</VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/draggable/modal.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal } from '@vben/common-ui';\n\nconst [Modal] = useVbenModal({\n  draggable: true,\n});\n</script>\n<template>\n  <Modal title=\"拖拽示例\"> modal content </Modal>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/dynamic/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal, VbenButton } from '@vben/common-ui';\n\nimport ExtraModal from './modal.vue';\n\nconst [Modal, modalApi] = useVbenModal({\n  // 连接抽离的组件\n  connectedComponent: ExtraModal,\n});\n\nfunction openModal() {\n  modalApi.open();\n}\n\nfunction handleUpdateTitle() {\n  modalApi.setState({ title: '外部动态标题' }).open();\n}\n</script>\n\n<template>\n  <div>\n    <Modal />\n\n    <VbenButton @click=\"openModal\">Open</VbenButton>\n    <VbenButton class=\"ml-2\" type=\"primary\" @click=\"handleUpdateTitle\">\n      从外部修改标题并打开\n    </VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/dynamic/modal.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal, VbenButton } from '@vben/common-ui';\n\nconst [Modal, modalApi] = useVbenModal({\n  draggable: true,\n  onCancel() {\n    modalApi.close();\n  },\n  onConfirm() {\n    console.info('onConfirm');\n  },\n  title: '动态修改配置示例',\n});\n\nconst state = modalApi.useStore();\n\nfunction handleUpdateTitle() {\n  modalApi.setState({ title: '内部动态标题' });\n}\n\nfunction handleToggleFullscreen() {\n  modalApi.setState((prev) => {\n    return { ...prev, fullscreen: !prev.fullscreen };\n  });\n}\n</script>\n<template>\n  <Modal>\n    <div class=\"flex-col-center\">\n      <VbenButton class=\"mb-3\" type=\"primary\" @click=\"handleUpdateTitle()\">\n        内部动态修改标题\n      </VbenButton>\n      <VbenButton class=\"mb-3\" @click=\"handleToggleFullscreen()\">\n        {{ state.fullscreen ? '退出全屏' : '打开全屏' }}\n      </VbenButton>\n    </div>\n  </Modal>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/extra/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal, VbenButton } from '@vben/common-ui';\n\nimport ExtraModal from './modal.vue';\n\nconst [Modal, modalApi] = useVbenModal({\n  // 连接抽离的组件\n  connectedComponent: ExtraModal,\n});\n\nfunction openModal() {\n  modalApi.open();\n}\n</script>\n\n<template>\n  <div>\n    <Modal />\n    <VbenButton @click=\"openModal\">Open</VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/extra/modal.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal } from '@vben/common-ui';\n\nconst [Modal] = useVbenModal();\n</script>\n<template>\n  <Modal title=\"组件抽离示例\"> extra modal content </Modal>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/shared-data/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal, VbenButton } from '@vben/common-ui';\n\nimport ExtraModal from './modal.vue';\n\nconst [Modal, modalApi] = useVbenModal({\n  // 连接抽离的组件\n  connectedComponent: ExtraModal,\n});\n\nfunction openModal() {\n  modalApi\n    .setData({\n      content: '外部传递的数据 content',\n      payload: '外部传递的数据 payload',\n    })\n    .open();\n}\n</script>\n\n<template>\n  <div>\n    <Modal />\n\n    <VbenButton @click=\"openModal\">Open</VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-modal/shared-data/modal.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { useVbenModal } from '@vben/common-ui';\n\nconst data = ref();\n\nconst [Modal, modalApi] = useVbenModal({\n  onCancel() {\n    modalApi.close();\n  },\n  onConfirm() {\n    console.info('onConfirm');\n  },\n  onOpenChange(isOpen: boolean) {\n    if (isOpen) {\n      data.value = modalApi.getData<Record<string, any>>();\n    }\n  },\n});\n</script>\n<template>\n  <Modal title=\"数据共享示例\">\n    <div class=\"flex-col-center\">外部传递数据： {{ data }}</div>\n  </Modal>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/basic/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridListeners, VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Button, message } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nimport { MOCK_TABLE_DATA } from '../table-data';\n\ninterface RowType {\n  address: string;\n  age: number;\n  id: number;\n  name: string;\n  nickname: string;\n  role: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { field: 'name', title: 'Name' },\n    { field: 'age', sortable: true, title: 'Age' },\n    { field: 'nickname', title: 'Nickname' },\n    { field: 'role', title: 'Role' },\n    { field: 'address', showOverflow: true, title: 'Address' },\n  ],\n  data: MOCK_TABLE_DATA,\n  pagerConfig: {\n    enabled: false,\n  },\n  sortConfig: {\n    multiple: true,\n  },\n};\n\nconst gridEvents: VxeGridListeners<RowType> = {\n  cellClick: ({ row }) => {\n    message.info(`cell-click: ${row.name}`);\n  },\n};\n\nconst [Grid, gridApi] = useVbenVxeGrid({ gridEvents, gridOptions });\n\nconst showBorder = gridApi.useStore((state) => state.gridOptions?.border);\nconst showStripe = gridApi.useStore((state) => state.gridOptions?.stripe);\n\nfunction changeBorder() {\n  gridApi.setGridOptions({\n    border: !showBorder.value,\n  });\n}\n\nfunction changeStripe() {\n  gridApi.setGridOptions({\n    stripe: !showStripe.value,\n  });\n}\n\nfunction changeLoading() {\n  gridApi.setLoading(true);\n  setTimeout(() => {\n    gridApi.setLoading(false);\n  }, 2000);\n}\n</script>\n\n<template>\n  <!-- 此处的`vp-raw` 是为了适配文档的展示效果，实际使用时不需要 -->\n  <div class=\"vp-raw w-full\">\n    <Grid>\n      <template #toolbar-tools>\n        <Button class=\"mr-2\" type=\"primary\" @click=\"changeBorder\">\n          {{ showBorder ? '隐藏' : '显示' }}边框\n        </Button>\n        <Button class=\"mr-2\" type=\"primary\" @click=\"changeLoading\">\n          显示loading\n        </Button>\n        <Button class=\"mr-2\" type=\"primary\" @click=\"changeStripe\">\n          {{ showStripe ? '隐藏' : '显示' }}斑马纹\n        </Button>\n      </template>\n    </Grid>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/custom-cell/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Button, Image, Switch, Tag } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nimport { getExampleTableApi } from '../mock-api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  imageUrl: string;\n  open: boolean;\n  price: string;\n  productName: string;\n  releaseDate: string;\n  status: 'error' | 'success' | 'warning';\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  checkboxConfig: {\n    highlight: true,\n    labelField: 'name',\n  },\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { field: 'category', title: 'Category', width: 100 },\n    {\n      field: 'imageUrl',\n      slots: { default: 'image-url' },\n      title: 'Image',\n      width: 100,\n    },\n    {\n      cellRender: { name: 'CellImage' },\n      field: 'imageUrl2',\n      title: 'Render Image',\n      width: 130,\n    },\n    {\n      field: 'open',\n      slots: { default: 'open' },\n      title: 'Open',\n      width: 100,\n    },\n    {\n      field: 'status',\n      slots: { default: 'status' },\n      title: 'Status',\n      width: 100,\n    },\n    { field: 'color', title: 'Color', width: 100 },\n    { field: 'productName', title: 'Product Name', width: 200 },\n    { field: 'price', title: 'Price', width: 100 },\n    {\n      field: 'releaseDate',\n      formatter: 'formatDateTime',\n      title: 'Date',\n      width: 200,\n    },\n    {\n      cellRender: { name: 'CellLink', props: { text: '编辑' } },\n      field: 'action',\n      fixed: 'right',\n      title: '操作',\n      width: 120,\n    },\n  ],\n  keepSource: true,\n  pagerConfig: {},\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }) => {\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n        });\n      },\n    },\n  },\n};\n\nconst [Grid] = useVbenVxeGrid({ gridOptions });\n</script>\n\n<template>\n  <div class=\"vp-raw w-full\">\n    <Grid>\n      <template #image-url=\"{ row }\">\n        <Image :src=\"row.imageUrl\" height=\"30\" width=\"30\" />\n      </template>\n      <template #open=\"{ row }\">\n        <Switch v-model=\"row.open\" />\n      </template>\n      <template #status=\"{ row }\">\n        <Tag :color=\"row.color\">{{ row.status }}</Tag>\n      </template>\n      <template #action>\n        <Button type=\"link\">编辑</Button>\n      </template>\n    </Grid>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/edit-cell/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nimport { getExampleTableApi } from '../mock-api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  price: string;\n  productName: string;\n  releaseDate: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { editRender: { name: 'input' }, field: 'category', title: 'Category' },\n    { editRender: { name: 'input' }, field: 'color', title: 'Color' },\n    {\n      editRender: { name: 'input' },\n      field: 'productName',\n      title: 'Product Name',\n    },\n    { field: 'price', title: 'Price' },\n    { field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },\n  ],\n  editConfig: {\n    mode: 'cell',\n    trigger: 'click',\n  },\n  pagerConfig: {},\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }) => {\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n        });\n      },\n    },\n  },\n  showOverflow: true,\n};\n\nconst [Grid] = useVbenVxeGrid({ gridOptions });\n</script>\n\n<template>\n  <div class=\"vp-raw w-full\">\n    <Grid />\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/edit-row/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Button, message } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nimport { getExampleTableApi } from '../mock-api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  price: string;\n  productName: string;\n  releaseDate: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { editRender: { name: 'input' }, field: 'category', title: 'Category' },\n    { editRender: { name: 'input' }, field: 'color', title: 'Color' },\n    {\n      editRender: { name: 'input' },\n      field: 'productName',\n      title: 'Product Name',\n    },\n    { field: 'price', title: 'Price' },\n    { field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },\n    { slots: { default: 'action' }, title: '操作' },\n  ],\n  editConfig: {\n    mode: 'row',\n    trigger: 'click',\n  },\n  pagerConfig: {},\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }) => {\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n        });\n      },\n    },\n  },\n  showOverflow: true,\n};\n\nconst [Grid, gridApi] = useVbenVxeGrid({ gridOptions });\n\nfunction hasEditStatus(row: RowType) {\n  return gridApi.grid?.isEditByRow(row);\n}\n\nfunction editRowEvent(row: RowType) {\n  gridApi.grid?.setEditRow(row);\n}\n\nasync function saveRowEvent(row: RowType) {\n  await gridApi.grid?.clearEdit();\n\n  gridApi.setLoading(true);\n  setTimeout(() => {\n    gridApi.setLoading(false);\n    message.success({\n      content: `保存成功！category=${row.category}`,\n    });\n  }, 600);\n}\n\nconst cancelRowEvent = (_row: RowType) => {\n  gridApi.grid?.clearEdit();\n};\n</script>\n\n<template>\n  <div class=\"vp-raw w-full\">\n    <Grid>\n      <template #action=\"{ row }\">\n        <template v-if=\"hasEditStatus(row)\">\n          <Button type=\"link\" @click=\"saveRowEvent(row)\">保存</Button>\n          <Button type=\"link\" @click=\"cancelRowEvent(row)\">取消</Button>\n        </template>\n        <template v-else>\n          <Button type=\"link\" @click=\"editRowEvent(row)\">编辑</Button>\n        </template>\n      </template>\n    </Grid>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/fixed/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Button } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nimport { getExampleTableApi } from '../mock-api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  price: string;\n  productName: string;\n  releaseDate: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { fixed: 'left', title: '序号', type: 'seq', width: 50 },\n    { field: 'category', title: 'Category', width: 300 },\n    { field: 'color', title: 'Color', width: 300 },\n    { field: 'productName', title: 'Product Name', width: 300 },\n    { field: 'price', title: 'Price', width: 300 },\n    {\n      field: 'releaseDate',\n      formatter: 'formatDateTime',\n      title: 'DateTime',\n      width: 500,\n    },\n    {\n      field: 'action',\n      fixed: 'right',\n      slots: { default: 'action' },\n      title: '操作',\n      width: 120,\n    },\n  ],\n  pagerConfig: {},\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }) => {\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n        });\n      },\n    },\n  },\n  rowConfig: {\n    isHover: true,\n  },\n};\n\nconst [Grid] = useVbenVxeGrid({ gridOptions });\n</script>\n\n<template>\n  <div class=\"vp-raw w-full\">\n    <Grid>\n      <template #action>\n        <Button type=\"link\">编辑</Button>\n      </template>\n    </Grid>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/form/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormProps } from '#/adapter/form';\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { message } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nimport { getExampleTableApi } from '../mock-api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  price: string;\n  productName: string;\n  releaseDate: string;\n}\n\nconst formOptions: VbenFormProps = {\n  // 默认展开\n  collapsed: false,\n  schema: [\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: 'Please enter category',\n      },\n      defaultValue: '1',\n      fieldName: 'category',\n      label: 'Category',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: 'Please enter productName',\n      },\n      fieldName: 'productName',\n      label: 'ProductName',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: 'Please enter price',\n      },\n      fieldName: 'price',\n      label: 'Price',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        options: [\n          {\n            label: 'Color1',\n            value: '1',\n          },\n          {\n            label: 'Color2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n      },\n      fieldName: 'color',\n      label: 'Color',\n    },\n    {\n      component: 'DatePicker',\n      fieldName: 'datePicker',\n      label: 'Date',\n    },\n  ],\n  // 控制表单是否显示折叠按钮\n  showCollapseButton: true,\n  submitButtonOptions: {\n    content: '查询',\n  },\n  // 是否在字段值改变时提交表单\n  submitOnChange: false,\n  // 按下回车时是否提交表单\n  submitOnEnter: false,\n};\n\nconst gridOptions: VxeGridProps<RowType> = {\n  checkboxConfig: {\n    highlight: true,\n    labelField: 'name',\n  },\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { align: 'left', title: 'Name', type: 'checkbox', width: 100 },\n    { field: 'category', title: 'Category' },\n    { field: 'color', title: 'Color' },\n    { field: 'productName', title: 'Product Name' },\n    { field: 'price', title: 'Price' },\n    { field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },\n  ],\n  keepSource: true,\n  pagerConfig: {},\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }, formValues) => {\n        message.success(`Query params: ${JSON.stringify(formValues)}`);\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n          ...formValues,\n        });\n      },\n    },\n  },\n  toolbarConfig: {\n    // 是否显示搜索表单控制按钮\n    // @ts-ignore - 正式环境时有完整的类型声明\n    search: true,\n  },\n};\n\nconst [Grid] = useVbenVxeGrid({ formOptions, gridOptions });\n</script>\n\n<template>\n  <div class=\"vp-raw w-full\">\n    <Grid />\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/mock-api.ts",
    "content": "import { MOCK_API_DATA } from './table-data';\n\nexport namespace DemoTableApi {\n  export interface PageFetchParams {\n    [key: string]: any;\n    page: number;\n    pageSize: number;\n  }\n}\n\nexport function sleep(time = 1000) {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve(true);\n    }, time);\n  });\n}\n\n/**\n * 获取示例表格数据\n */\nasync function getExampleTableApi(params: DemoTableApi.PageFetchParams) {\n  return new Promise<{ items: any; total: number }>((resolve) => {\n    const { page, pageSize } = params;\n    const items = MOCK_API_DATA.slice((page - 1) * pageSize, page * pageSize);\n\n    sleep(1000).then(() => {\n      resolve({\n        total: items.length,\n        items,\n      });\n    });\n  });\n}\n\nexport { getExampleTableApi };\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/remote/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { DemoTableApi } from '../mock-api';\n\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Button } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nimport { MOCK_API_DATA } from '../table-data';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  price: string;\n  productName: string;\n  releaseDate: string;\n}\n\n// 数据实例\n// const MOCK_TREE_TABLE_DATA = [\n//   {\n//     date: '2020-08-01',\n//     id: 10_000,\n//     name: 'Test1',\n//     parentId: null,\n//     size: 1024,\n//     type: 'mp3',\n//   },\n// ]\n\nconst sleep = (time = 1000) => {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve(true);\n    }, time);\n  });\n};\n\n/**\n * 获取示例表格数据\n */\nasync function getExampleTableApi(params: DemoTableApi.PageFetchParams) {\n  return new Promise<{ items: any; total: number }>((resolve) => {\n    const { page, pageSize } = params;\n    const items = MOCK_API_DATA.slice((page - 1) * pageSize, page * pageSize);\n\n    sleep(1000).then(() => {\n      resolve({\n        total: items.length,\n        items,\n      });\n    });\n  });\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  checkboxConfig: {\n    highlight: true,\n    labelField: 'name',\n  },\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { align: 'left', title: 'Name', type: 'checkbox', width: 100 },\n    { field: 'category', title: 'Category' },\n    { field: 'color', title: 'Color' },\n    { field: 'productName', title: 'Product Name' },\n    { field: 'price', title: 'Price' },\n    { field: 'releaseDate', formatter: 'formatDateTime', title: 'DateTime' },\n  ],\n  exportConfig: {},\n  // height: 'auto', // 如果设置为 auto，则必须确保存在父节点且不允许存在相邻元素，否则会出现高度闪动问题\n  keepSource: true,\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }) => {\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n        });\n      },\n    },\n  },\n  toolbarConfig: {\n    custom: true,\n    export: true,\n    // import: true,\n    refresh: true,\n    zoom: true,\n  },\n};\n\nconst [Grid, gridApi] = useVbenVxeGrid({\n  gridOptions,\n});\n</script>\n\n<template>\n  <div class=\"vp-raw w-full\">\n    <Grid>\n      <template #toolbar-tools>\n        <Button class=\"mr-2\" type=\"primary\" @click=\"() => gridApi.query()\">\n          刷新当前页面\n        </Button>\n        <Button type=\"primary\" @click=\"() => gridApi.reload()\">\n          刷新并返回第一页\n        </Button>\n      </template>\n    </Grid>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/table-data.ts",
    "content": "interface TableRowData {\n  address: string;\n  age: number;\n  id: number;\n  name: string;\n  nickname: string;\n  role: string;\n}\n\nconst roles = ['User', 'Admin', 'Manager', 'Guest'];\n\nexport const MOCK_TABLE_DATA: TableRowData[] = (() => {\n  const data: TableRowData[] = [];\n  for (let i = 0; i < 10; i++) {\n    data.push({\n      address: `New York${i}`,\n      age: i + 1,\n      id: i,\n      name: `Test${i}`,\n      nickname: `Test${i}`,\n      role: roles[Math.floor(Math.random() * roles.length)] as string,\n    });\n  }\n  return data;\n})();\n\nexport const MOCK_TREE_TABLE_DATA = [\n  {\n    date: '2020-08-01',\n    id: 10_000,\n    name: 'Test1',\n    parentId: null,\n    size: 1024,\n    type: 'mp3',\n  },\n  {\n    date: '2021-04-01',\n    id: 10_050,\n    name: 'Test2',\n    parentId: null,\n    size: 0,\n    type: 'mp4',\n  },\n  {\n    date: '2020-03-01',\n    id: 24_300,\n    name: 'Test3',\n    parentId: 10_050,\n    size: 1024,\n    type: 'avi',\n  },\n  {\n    date: '2021-04-01',\n    id: 20_045,\n    name: 'Test4',\n    parentId: 24_300,\n    size: 600,\n    type: 'html',\n  },\n  {\n    date: '2021-04-01',\n    id: 10_053,\n    name: 'Test5',\n    parentId: 24_300,\n    size: 0,\n    type: 'avi',\n  },\n  {\n    date: '2021-10-01',\n    id: 24_330,\n    name: 'Test6',\n    parentId: 10_053,\n    size: 25,\n    type: 'txt',\n  },\n  {\n    date: '2020-01-01',\n    id: 21_011,\n    name: 'Test7',\n    parentId: 10_053,\n    size: 512,\n    type: 'pdf',\n  },\n  {\n    date: '2021-06-01',\n    id: 22_200,\n    name: 'Test8',\n    parentId: 10_053,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2020-11-01',\n    id: 23_666,\n    name: 'Test9',\n    parentId: null,\n    size: 2048,\n    type: 'xlsx',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_677,\n    name: 'Test10',\n    parentId: 23_666,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_671,\n    name: 'Test11',\n    parentId: 23_677,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_672,\n    name: 'Test12',\n    parentId: 23_677,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_688,\n    name: 'Test13',\n    parentId: 23_666,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_681,\n    name: 'Test14',\n    parentId: 23_688,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_682,\n    name: 'Test15',\n    parentId: 23_688,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2020-10-01',\n    id: 24_555,\n    name: 'Test16',\n    parentId: null,\n    size: 224,\n    type: 'avi',\n  },\n  {\n    date: '2021-06-01',\n    id: 24_566,\n    name: 'Test17',\n    parentId: 24_555,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 24_577,\n    name: 'Test18',\n    parentId: 24_555,\n    size: 1024,\n    type: 'js',\n  },\n];\n\nexport const MOCK_API_DATA = [\n  {\n    available: true,\n    category: 'Computers',\n    color: 'purple',\n    currency: 'NAD',\n    description:\n      'Ergonomic executive chair upholstered in bonded black leather and PVC padded seat and back for all-day comfort and support',\n    id: '45a613df-227a-4907-a89f-4a7f1252ca0c',\n    imageUrl: 'https://avatars.githubusercontent.com/u/62715097',\n    imageUrl2: 'https://avatars.githubusercontent.com/u/75395683',\n    inProduction: false,\n    open: true,\n    price: '48.89',\n    productName: 'Handcrafted Steel Salad',\n    quantity: 70,\n    rating: 3.780_582_329_574_367,\n    releaseDate: '2024-09-09T04:06:57.793Z',\n    status: 'error',\n    tags: ['Bespoke', 'Handmade', 'Luxurious'],\n    weight: 1.031_015_671_912_002_5,\n  },\n  {\n    available: true,\n    category: 'Toys',\n    color: 'green',\n    currency: 'CZK',\n    description:\n      'The Nagasaki Lander is the trademarked name of several series of Nagasaki sport bikes, that started with the 1984 ABC800J',\n    id: 'd02e5ee9-bc98-4de2-98fa-25a6567ecc19',\n    imageUrl: 'https://avatars.githubusercontent.com/u/51512330',\n    imageUrl2: 'https://avatars.githubusercontent.com/u/58698113',\n    inProduction: false,\n    open: false,\n    price: '68.15',\n    productName: 'Generic Cotton Gloves',\n    quantity: 3,\n    rating: 1.681_749_367_682_703_3,\n    releaseDate: '2024-06-16T09:00:36.806Z',\n    status: 'warning',\n    tags: ['Rustic', 'Handcrafted', 'Recycled'],\n    weight: 9.601_076_149_300_575,\n  },\n  {\n    available: true,\n    category: 'Beauty',\n    color: 'teal',\n    currency: 'OMR',\n    description:\n      'The Apollotech B340 is an affordable wireless mouse with reliable connectivity, 12 months battery life and modern design',\n    id: '2b72521c-225c-4e64-8030-611b76b10b37',\n    imageUrl: 'https://avatars.githubusercontent.com/u/50300075',\n    imageUrl2: 'https://avatars.githubusercontent.com/u/36541691',\n    inProduction: true,\n    open: true,\n    price: '696.94',\n    productName: 'Gorgeous Soft Ball',\n    quantity: 50,\n    rating: 2.361_581_777_372_057_5,\n    releaseDate: '2024-06-03T13:24:19.809Z',\n    status: 'warning',\n    tags: ['Gorgeous', 'Ergonomic', 'Licensed'],\n    weight: 8.882_340_049_286_19,\n  },\n  {\n    available: true,\n    category: 'Games',\n    color: 'silver',\n    currency: 'SOS',\n    description:\n      'Carbonite web goalkeeper gloves are ergonomically designed to give easy fit',\n    id: 'bafab694-3801-452c-b102-9eb519bd1143',\n    imageUrl: 'https://avatars.githubusercontent.com/u/89827115',\n    imageUrl2: 'https://avatars.githubusercontent.com/u/55952747',\n    inProduction: false,\n    open: false,\n    price: '553.84',\n    productName: 'Bespoke Soft Computer',\n    quantity: 29,\n    rating: 2.176_412_873_760_271_7,\n    releaseDate: '2024-09-17T12:16:27.034Z',\n    status: 'error',\n    tags: ['Elegant', 'Rustic', 'Recycled'],\n    weight: 9.653_285_869_978_038,\n  },\n  {\n    available: true,\n    category: 'Toys',\n    color: 'indigo',\n    currency: 'BIF',\n    description:\n      'Andy shoes are designed to keeping in mind durability as well as trends, the most stylish range of shoes & sandals',\n    id: 'bf6dea6b-2a55-441d-8773-937e03d99389',\n    imageUrl: 'https://avatars.githubusercontent.com/u/21431092',\n    imageUrl2: 'https://avatars.githubusercontent.com/u/3771350',\n    inProduction: true,\n    open: true,\n    price: '237.39',\n    productName: 'Handcrafted Cotton Mouse',\n    quantity: 54,\n    rating: 4.363_265_388_265_461,\n    releaseDate: '2023-10-23T13:42:34.947Z',\n    status: 'error',\n    tags: ['Unbranded', 'Handmade', 'Generic'],\n    weight: 9.513_203_612_535_571,\n  },\n  {\n    available: false,\n    category: 'Tools',\n    color: 'violet',\n    currency: 'TZS',\n    description:\n      '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',\n    id: '135ba6ab-32ee-4989-8189-5cfa658ef970',\n    imageUrl: 'https://avatars.githubusercontent.com/u/29946092',\n    imageUrl2: 'https://avatars.githubusercontent.com/u/23842994',\n    inProduction: false,\n    open: false,\n    price: '825.25',\n    productName: 'Awesome Bronze Ball',\n    quantity: 94,\n    rating: 4.251_159_804_726_753,\n    releaseDate: '2023-12-30T07:31:43.464Z',\n    status: 'warning',\n    tags: ['Handmade', 'Elegant', 'Unbranded'],\n    weight: 2.247_473_385_732_636_8,\n  },\n  {\n    available: true,\n    category: 'Automotive',\n    color: 'teal',\n    currency: 'BOB',\n    description: 'The Football Is Good For Training And Recreational Purposes',\n    id: '652ef256-7d4e-48b7-976c-7afaa781ea92',\n    imageUrl: 'https://avatars.githubusercontent.com/u/2531904',\n    imageUrl2: 'https://avatars.githubusercontent.com/u/15215990',\n    inProduction: false,\n    open: false,\n    price: '780.49',\n    productName: 'Oriental Rubber Pants',\n    quantity: 70,\n    rating: 2.636_323_417_377_916,\n    releaseDate: '2024-02-23T23:30:49.628Z',\n    status: 'success',\n    tags: ['Unbranded', 'Elegant', 'Unbranded'],\n    weight: 4.812_965_858_018_838,\n  },\n  {\n    available: false,\n    category: 'Garden',\n    color: 'plum',\n    currency: 'LRD',\n    description:\n      'The slim & simple Maple Gaming Keyboard from Dev Byte comes with a sleek body and 7- Color RGB LED Back-lighting for smart functionality',\n    id: '3ea24798-6589-40cc-85f0-ab78752244a0',\n    imageUrl: 'https://avatars.githubusercontent.com/u/23165285',\n    imageUrl2: 'https://avatars.githubusercontent.com/u/14595665',\n    inProduction: false,\n    open: true,\n    price: '583.85',\n    productName: 'Handcrafted Concrete Hat',\n    quantity: 15,\n    rating: 1.371_600_527_752_802_7,\n    releaseDate: '2024-03-02T19:40:50.255Z',\n    status: 'error',\n    tags: ['Rustic', 'Sleek', 'Ergonomic'],\n    weight: 4.926_949_366_405_728_4,\n  },\n  {\n    available: false,\n    category: 'Industrial',\n    color: 'salmon',\n    currency: 'AUD',\n    description:\n      'The Apollotech B340 is an affordable wireless mouse with reliable connectivity, 12 months battery life and modern design',\n    id: '997113dd-f6e4-4acc-9790-ef554c7498d1',\n    imageUrl: 'https://avatars.githubusercontent.com/u/49021914',\n    imageUrl2: 'https://avatars.githubusercontent.com/u/4690621',\n    inProduction: true,\n    open: false,\n    price: '67.99',\n    productName: 'Generic Rubber Bacon',\n    quantity: 68,\n    rating: 4.129_840_682_128_08,\n    releaseDate: '2023-12-17T01:40:25.415Z',\n    status: 'error',\n    tags: ['Oriental', 'Small', 'Handcrafted'],\n    weight: 1.080_114_331_801_906_4,\n  },\n  {\n    available: false,\n    category: 'Tools',\n    color: 'sky blue',\n    currency: 'NOK',\n    description:\n      'The Nagasaki Lander is the trademarked name of several series of Nagasaki sport bikes, that started with the 1984 ABC800J',\n    id: 'f697a250-6cb2-46c8-b0f7-871ab1f2fa8d',\n    imageUrl: 'https://avatars.githubusercontent.com/u/95928385',\n    imageUrl2: 'https://avatars.githubusercontent.com/u/47588244',\n    inProduction: false,\n    open: false,\n    price: '613.89',\n    productName: 'Gorgeous Frozen Ball',\n    quantity: 55,\n    rating: 1.646_947_205_998_534_6,\n    releaseDate: '2024-10-13T12:31:04.929Z',\n    status: 'warning',\n    tags: ['Handmade', 'Unbranded', 'Unbranded'],\n    weight: 9.430_690_557_758_114,\n  },\n];\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/tree/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Button } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nimport { MOCK_TREE_TABLE_DATA } from '../table-data';\n\ninterface RowType {\n  date: string;\n  id: number;\n  name: string;\n  parentId: null | number;\n  size: number;\n  type: string;\n}\n\n// 数据实例\n// const MOCK_TREE_TABLE_DATA = [\n//   {\n//     date: '2020-08-01',\n//     id: 10_000,\n//     name: 'Test1',\n//     parentId: null,\n//     size: 1024,\n//     type: 'mp3',\n//   },\n//   {\n//     date: '2021-04-01',\n//     id: 10_050,\n//     name: 'Test2',\n//     parentId: 10_000,\n//     size: 0,\n//     type: 'mp4',\n//   },\n// ];\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { type: 'seq', width: 70 },\n    { field: 'name', minWidth: 300, title: 'Name', treeNode: true },\n    { field: 'size', title: 'Size' },\n    { field: 'type', title: 'Type' },\n    { field: 'date', title: 'Date' },\n  ],\n  data: MOCK_TREE_TABLE_DATA,\n  pagerConfig: {\n    enabled: false,\n  },\n  treeConfig: {\n    parentField: 'parentId',\n    rowField: 'id',\n    transform: true,\n  },\n};\n\nconst [Grid, gridApi] = useVbenVxeGrid({ gridOptions });\n\nconst expandAll = () => {\n  gridApi.grid?.setAllTreeExpand(true);\n};\n\nconst collapseAll = () => {\n  gridApi.grid?.setAllTreeExpand(false);\n};\n</script>\n\n<template>\n  <div class=\"vp-raw h-75 w-full\">\n    <Grid>\n      <template #toolbar-tools>\n        <Button class=\"mr-2\" type=\"primary\" @click=\"expandAll\">\n          展开全部\n        </Button>\n        <Button type=\"primary\" @click=\"collapseAll\"> 折叠全部 </Button>\n      </template>\n    </Grid>\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/demos/vben-vxe-table/virtual/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { onMounted } from 'vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\ninterface RowType {\n  id: number;\n  name: string;\n  role: string;\n  sex: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { type: 'seq', width: 70 },\n    { field: 'name', title: 'Name' },\n    { field: 'role', title: 'Role' },\n    { field: 'sex', title: 'Sex' },\n  ],\n  data: [],\n  height: 'auto',\n  pagerConfig: {\n    enabled: false,\n  },\n  scrollY: {\n    enabled: true,\n    gt: 0,\n  },\n  showOverflow: true,\n};\n\nconst [Grid, gridApi] = useVbenVxeGrid({ gridOptions });\n\n// 模拟行数据\nconst loadList = (size = 200) => {\n  try {\n    const dataList: RowType[] = [];\n    for (let i = 0; i < size; i++) {\n      dataList.push({\n        id: 10_000 + i,\n        name: `Test${i}`,\n        role: 'Developer',\n        sex: '男',\n      });\n    }\n    gridApi.setGridOptions({ data: dataList });\n  } catch (error) {\n    console.error('Failed to load data:', error);\n    // Implement user-friendly error handling\n  }\n};\n\nonMounted(() => {\n  loadList(1000);\n});\n</script>\n\n<template>\n  <div class=\"vp-raw h-125 w-full\">\n    <Grid />\n  </div>\n</template>\n"
  },
  {
    "path": "docs/src/en/components/common-ui/vben-alert.md",
    "content": "---\noutline: deep\n---\n\n# Vben Alert\n\n`Alert` provides lightweight JavaScript-driven dialogs for simple `alert`, `confirm`, and `prompt` style interactions.\n\n## Basic Usage\n\nUse `alert` for a single confirm button dialog:\n\n<DemoPreview dir=\"demos/vben-alert/alert\" />\n\nUse `confirm` for confirm/cancel interactions:\n\n<DemoPreview dir=\"demos/vben-alert/confirm\" />\n\nUse `prompt` when you need simple user input:\n\n<DemoPreview dir=\"demos/vben-alert/prompt\" />\n\n## useAlertContext\n\nIf `content`, `footer`, or `icon` is rendered through a custom component, you can call `useAlertContext()` inside that component to access the current dialog actions.\n\n| Method      | Description                | Type         |\n| ----------- | -------------------------- | ------------ |\n| `doConfirm` | trigger the confirm action | `() => void` |\n| `doCancel`  | trigger the cancel action  | `() => void` |\n\n## Core Types\n\n```ts\nexport type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';\n\nexport type BeforeCloseScope = {\n  isConfirm: boolean;\n};\n\nexport type AlertProps = {\n  beforeClose?: (\n    scope: BeforeCloseScope,\n  ) => boolean | Promise<boolean | undefined> | undefined;\n  bordered?: boolean;\n  buttonAlign?: 'center' | 'end' | 'start';\n  cancelText?: string;\n  centered?: boolean;\n  confirmText?: string;\n  containerClass?: string;\n  content: Component | string;\n  contentClass?: string;\n  contentMasking?: boolean;\n  footer?: Component | string;\n  icon?: Component | IconType;\n  overlayBlur?: number;\n  showCancel?: boolean;\n  title?: string;\n};\n\nexport type PromptProps<T = any> = {\n  beforeClose?: (scope: {\n    isConfirm: boolean;\n    value: T | undefined;\n  }) => boolean | Promise<boolean | undefined> | undefined;\n  component?: Component;\n  componentProps?: Recordable<any>;\n  componentSlots?:\n    | (() => any)\n    | Recordable<unknown>\n    | VNode\n    | VNodeArrayChildren;\n  defaultValue?: T;\n  modelPropName?: string;\n} & Omit<AlertProps, 'beforeClose'>;\n```\n"
  },
  {
    "path": "docs/src/en/components/common-ui/vben-api-component.md",
    "content": "---\noutline: deep\n---\n\n# Vben ApiComponent\n\n`ApiComponent` is a wrapper used to attach remote-option loading behavior to an existing component while preserving the original component usage pattern.\n\n## Common Usage\n\nThe current wrapper flow is:\n\n- pass the target component through `component`\n- fetch remote data through `api`\n- transform data through `beforeFetch` and `afterFetch`\n- map remote fields through `resultField`, `valueField`, `labelField`, and `childrenField`\n- pass normalized options to the target component through `optionsPropName`\n\n```vue\n<script lang=\"ts\" setup>\nimport { ApiComponent } from '@vben/common-ui';\n\nimport { Cascader } from 'ant-design-vue';\n\nfunction fetchApi() {\n  return Promise.resolve([\n    {\n      label: 'Zhejiang',\n      value: 'zhejiang',\n      children: [{ label: 'Hangzhou', value: 'hangzhou' }],\n    },\n  ]);\n}\n</script>\n\n<template>\n  <ApiComponent\n    :api=\"fetchApi\"\n    :component=\"Cascader\"\n    :immediate=\"false\"\n    children-field=\"children\"\n    loading-slot=\"suffixIcon\"\n    visible-event=\"onDropdownVisibleChange\"\n  />\n</template>\n```\n\n## Current Props\n\n| Prop | Description | Type |\n| --- | --- | --- |\n| `component` | wrapped target component | `Component` |\n| `api` | remote request function | `(arg?: any) => Promise<any>` |\n| `params` | extra request params | `Record<string, any>` |\n| `beforeFetch` | hook before request | `AnyPromiseFunction` |\n| `afterFetch` | hook after request | `AnyPromiseFunction` |\n| `visibleEvent` | event name used to lazy-load data | `string` |\n| `loadingSlot` | slot name used to render the loading icon | `string` |\n| `modelPropName` | model prop name of the wrapped component | `string` |\n| `autoSelect` | auto-pick the first / last / only option, or use a custom function | `'first' \\| 'last' \\| 'one' \\| ((items) => item) \\| false` |\n\n## Exposed Methods\n\n| Method                   | Description                            |\n| ------------------------ | -------------------------------------- |\n| `getComponentRef()`      | returns the wrapped component instance |\n| `updateParam(newParams)` | merges and updates request params      |\n| `getOptions()`           | returns loaded options                 |\n| `getValue()`             | returns the current bound value        |\n"
  },
  {
    "path": "docs/src/en/components/common-ui/vben-count-to-animator.md",
    "content": "---\noutline: deep\n---\n\n# Vben CountToAnimator\n\n`CountToAnimator` renders animated number transitions.\n\n## Basic Usage\n\nUse `start-val`, `end-val`, and `duration` to control the animation range and timing.\n\n<DemoPreview dir=\"demos/vben-count-to-animator/basic\" />\n\n## Formatting\n\nUse `prefix`, `suffix`, `separator`, and `decimal` to control how the number is displayed.\n\n<DemoPreview dir=\"demos/vben-count-to-animator/custom\" />\n\n## Props\n\n| Prop | Description | Type | Default |\n| --- | --- | --- | --- |\n| `startVal` | starting value | `number` | `0` |\n| `endVal` | ending value | `number` | `2021` |\n| `duration` | animation duration in ms | `number` | `1500` |\n| `autoplay` | start automatically | `boolean` | `true` |\n| `prefix` | value prefix | `string` | `''` |\n| `suffix` | value suffix | `string` | `''` |\n| `separator` | thousands separator | `string` | `','` |\n| `decimal` | decimal separator | `string` | `'.'` |\n| `color` | text color | `string` | `''` |\n| `useEasing` | enable transition preset easing | `boolean` | `true` |\n| `transition` | transition preset name | `keyof typeof TransitionPresets` | `'linear'` |\n| `decimals` | decimal places to keep | `number` | `0` |\n\n## Events\n\n| Event        | Description                     | Type         |\n| ------------ | ------------------------------- | ------------ |\n| `started`    | fired when the animation starts | `() => void` |\n| `finished`   | fired when the animation ends   | `() => void` |\n| `onStarted`  | deprecated alias of `started`   | `() => void` |\n| `onFinished` | deprecated alias of `finished`  | `() => void` |\n\n## Exposed Methods\n\n| Method  | Description                       | Type         |\n| ------- | --------------------------------- | ------------ |\n| `reset` | reset to `startVal` and run again | `() => void` |\n"
  },
  {
    "path": "docs/src/en/components/common-ui/vben-drawer.md",
    "content": "---\noutline: deep\n---\n\n# Vben Drawer\n\n`Vben Drawer` is the shared drawer wrapper used by the framework. It supports auto-height layout, loading state, connected components, and an imperative API similar to the modal API.\n\n## Basic Usage\n\n```ts\nconst [Drawer, drawerApi] = useVbenDrawer({\n  // props\n  // events\n});\n```\n\n<DemoPreview dir=\"demos/vben-drawer/basic\" />\n\n## Current Usage Notes\n\n- If you use `connectedComponent`, the inner and outer components share data through `drawerApi.setData()` and `drawerApi.getData()`.\n- Default drawer behavior can be adjusted in `apps/<app>/src/bootstrap.ts` through `setDefaultDrawerProps(...)`.\n- `setState(...)` works on `DrawerState`, not `ModalState`.\n\n## Key Props\n\n| Prop | Description | Type |\n| --- | --- | --- |\n| `appendToMain` | mount inside the main content area instead of `body` | `boolean` |\n| `connectedComponent` | connect an inner component to the drawer wrapper | `Component` |\n| `closeIconPlacement` | position of the close icon | `'left' \\| 'right'` |\n| `placement` | drawer side | `'left' \\| 'right' \\| 'top' \\| 'bottom'` |\n| `overlayBlur` | blur amount for the overlay | `number` |\n| `submitting` | lock drawer interactions while submitting | `boolean` |\n\n## Events\n\n| Event | Description | Type |\n| --- | --- | --- |\n| `onBeforeClose` | called before close; returning `false` or rejecting prevents close | `() => Promise<boolean \\| undefined> \\| boolean \\| undefined` |\n| `onOpenChange` | called when open state changes | `(isOpen: boolean) => void` |\n| `onOpened` | called after open animation completes | `() => void` |\n| `onClosed` | called after close animation completes | `() => void` |\n\n## drawerApi\n\n| Method                  | Description                            |\n| ----------------------- | -------------------------------------- |\n| `setState(...)`         | updates drawer state                   |\n| `open()`                | opens the drawer                       |\n| `close()`               | closes the drawer                      |\n| `setData(data)`         | stores shared data                     |\n| `getData<T>()`          | reads shared data                      |\n| `lock(isLocked = true)` | locks the drawer into submitting state |\n| `unlock()`              | alias for `lock(false)`                |\n"
  },
  {
    "path": "docs/src/en/components/common-ui/vben-ellipsis-text.md",
    "content": "---\noutline: deep\n---\n\n# Vben EllipsisText\n\n`EllipsisText` displays long text with truncation, tooltip support, and optional expand/collapse behavior.\n\n## Basic Usage\n\nPass the text through the default slot and limit the visual width with `maxWidth`.\n\n<DemoPreview dir=\"demos/vben-ellipsis-text/line\" />\n\n## Current Props\n\n| Prop | Description | Type | Default |\n| --- | --- | --- | --- |\n| `expand` | allow click-to-expand behavior | `boolean` | `false` |\n| `line` | max visible line count | `number` | `1` |\n| `maxWidth` | max width of the text area | `number \\| string` | `'100%'` |\n| `placement` | tooltip placement | `'bottom' \\| 'left' \\| 'right' \\| 'top'` | `'top'` |\n| `tooltip` | enable tooltip | `boolean` | `true` |\n| `tooltipWhenEllipsis` | only show tooltip when text is actually truncated | `boolean` | `false` |\n| `ellipsisThreshold` | pixel threshold used when checking truncation | `number` | `3` |\n| `tooltipBackgroundColor` | tooltip background color | `string` | `''` |\n| `tooltipColor` | tooltip text color | `string` | `''` |\n| `tooltipFontSize` | tooltip font size in px | `number` | `14` |\n| `tooltipMaxWidth` | tooltip max width in px | `number` | - |\n| `tooltipOverlayStyle` | tooltip content style | `CSSProperties` | `{ textAlign: 'justify' }` |\n\n## Events\n\n| Event | Description | Type |\n| --- | --- | --- |\n| `expandChange` | fired when expand state changes | `(isExpand: boolean) => void` |\n\n## Slots\n\n| Slot      | Description            |\n| --------- | ---------------------- |\n| `tooltip` | custom tooltip content |\n"
  },
  {
    "path": "docs/src/en/components/common-ui/vben-form.md",
    "content": "---\noutline: deep\n---\n\n# Vben Form\n\n`Vben Form` is the shared form abstraction used across different UI-library variants such as `Ant Design Vue`, `Element Plus`, `Naive UI`, and other adapters added inside this repository.\n\n> If some details are not obvious from the docs, check the live demos as well.\n\n## Adapter Setup\n\nEach app keeps its own adapter layer under `src/adapter/form.ts` and `src/adapter/component/index.ts`.\n\nThe current adapter pattern is:\n\n- initialize the shared component adapter first\n- call `setupVbenForm(...)`\n- map special `v-model:*` prop names through `modelPropNameMap`\n- keep the form empty state aligned with the actual UI library behavior\n\n### Form Adapter Example\n\n```ts\nimport type {\n  VbenFormSchema as FormSchema,\n  VbenFormProps,\n} from '@vben/common-ui';\n\nimport type { ComponentType } from './component';\n\nimport { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { initComponentAdapter } from './component';\n\ninitComponentAdapter();\nsetupVbenForm<ComponentType>({\n  config: {\n    baseModelPropName: 'value',\n    emptyStateValue: null,\n    modelPropNameMap: {\n      Checkbox: 'checked',\n      Radio: 'checked',\n      Switch: 'checked',\n      Upload: 'fileList',\n    },\n  },\n  defineRules: {\n    required: (value, _params, ctx) => {\n      if (value === undefined || value === null || value.length === 0) {\n        return $t('ui.formRules.required', [ctx.label]);\n      }\n      return true;\n    },\n    selectRequired: (value, _params, ctx) => {\n      if (value === undefined || value === null) {\n        return $t('ui.formRules.selectRequired', [ctx.label]);\n      }\n      return true;\n    },\n  },\n});\n\nconst useVbenForm = useForm<ComponentType>;\n\nexport { useVbenForm, z };\nexport type VbenFormSchema = FormSchema<ComponentType>;\nexport type { VbenFormProps };\n```\n\n### Component Adapter Example\n\n```ts\nimport type { Component, SetupContext } from 'vue';\n\nimport type { BaseFormComponentType } from '@vben/common-ui';\n\nimport { h } from 'vue';\n\nimport { globalShareState } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\nimport {\n  AutoComplete,\n  Button,\n  Checkbox,\n  CheckboxGroup,\n  DatePicker,\n  Divider,\n  Input,\n  InputNumber,\n  InputPassword,\n  Mentions,\n  notification,\n  Radio,\n  RadioGroup,\n  RangePicker,\n  Rate,\n  Select,\n  Space,\n  Switch,\n  Textarea,\n  TimePicker,\n  TreeSelect,\n  Upload,\n} from 'ant-design-vue';\n\nconst withDefaultPlaceholder = <T extends Component>(\n  component: T,\n  type: 'input' | 'select',\n) => {\n  return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {\n    const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);\n    return h(component, { ...props, ...attrs, placeholder }, slots);\n  };\n};\n\nexport type ComponentType =\n  | 'AutoComplete'\n  | 'Checkbox'\n  | 'CheckboxGroup'\n  | 'DatePicker'\n  | 'DefaultButton'\n  | 'Divider'\n  | 'Input'\n  | 'InputNumber'\n  | 'InputPassword'\n  | 'Mentions'\n  | 'PrimaryButton'\n  | 'Radio'\n  | 'RadioGroup'\n  | 'RangePicker'\n  | 'Rate'\n  | 'Select'\n  | 'Space'\n  | 'Switch'\n  | 'Textarea'\n  | 'TimePicker'\n  | 'TreeSelect'\n  | 'Upload'\n  | BaseFormComponentType;\n\nasync function initComponentAdapter() {\n  const components: Partial<Record<ComponentType, Component>> = {\n    AutoComplete,\n    Checkbox,\n    CheckboxGroup,\n    DatePicker,\n    DefaultButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'default' }, slots);\n    },\n    Divider,\n    Input: withDefaultPlaceholder(Input, 'input'),\n    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),\n    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),\n    Mentions: withDefaultPlaceholder(Mentions, 'input'),\n    PrimaryButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'primary' }, slots);\n    },\n    Radio,\n    RadioGroup,\n    RangePicker,\n    Rate,\n    Select: withDefaultPlaceholder(Select, 'select'),\n    Space,\n    Switch,\n    Textarea: withDefaultPlaceholder(Textarea, 'input'),\n    TimePicker,\n    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),\n    Upload,\n  };\n\n  globalShareState.setComponents(components);\n  globalShareState.defineMessage({\n    copyPreferencesSuccess: (title, content) => {\n      notification.success({\n        description: content,\n        message: title,\n        placement: 'bottomRight',\n      });\n    },\n  });\n}\n\nexport { initComponentAdapter };\n```\n\n## Basic Usage\n\nCreate the form through `useVbenForm`:\n\n<DemoPreview dir=\"demos/vben-form/basic\" />\n\n## Key API Notes\n\n- `useVbenForm` returns `[Form, formApi]`\n- `formApi.getFieldComponentRef()` and `formApi.getFocusedField()` are available in current versions\n- `handleValuesChange(values, fieldsChanged)` includes the second parameter in newer versions\n- `fieldMappingTime` and `scrollToFirstError` are part of the current form props\n\n## Reference\n\nFor the complete Chinese API tables and more examples, see the Chinese component page if you need the full parameter matrix.\n"
  },
  {
    "path": "docs/src/en/components/common-ui/vben-modal.md",
    "content": "---\noutline: deep\n---\n\n# Vben Modal\n\n`Vben Modal` is the shared modal wrapper used by the framework. It supports draggable behavior, fullscreen mode, auto-height handling, loading state, connected components, and an imperative API.\n\n## Basic Usage\n\n```ts\nconst [Modal, modalApi] = useVbenModal({\n  // props\n  // events\n});\n```\n\n<DemoPreview dir=\"demos/vben-modal/basic\" />\n\n## Current Usage Notes\n\n- If you use `connectedComponent`, the inner and outer components share data through `modalApi.setData()` and `modalApi.getData()`.\n- When `connectedComponent` is present, avoid pushing extra modal props through the connected side. Prefer `useVbenModal(...)` or `modalApi.setState(...)`.\n- Default modal behavior can be adjusted in `apps/<app>/src/bootstrap.ts` through `setDefaultModalProps(...)`.\n\n## Key Props\n\n| Prop | Description | Type |\n| --- | --- | --- |\n| `appendToMain` | mount inside the main content area instead of `body` | `boolean` |\n| `connectedComponent` | connect an inner component to the modal wrapper | `Component` |\n| `animationType` | modal enter/leave animation | `'slide' \\| 'scale'` |\n| `fullscreenButton` | show or hide the fullscreen toggle | `boolean` |\n| `overlayBlur` | blur amount for the overlay | `number` |\n| `submitting` | lock modal interactions while submitting | `boolean` |\n\n## Events\n\n| Event | Description | Type |\n| --- | --- | --- |\n| `onBeforeClose` | called before close; returning `false` or rejecting prevents close | `() => Promise<boolean \\| undefined> \\| boolean \\| undefined` |\n| `onOpenChange` | called when open state changes | `(isOpen: boolean) => void` |\n| `onOpened` | called after open animation completes | `() => void` |\n| `onClosed` | called after close animation completes | `() => void` |\n\n## modalApi\n\n| Method                  | Description                           |\n| ----------------------- | ------------------------------------- |\n| `setState(...)`         | updates modal state                   |\n| `open()`                | opens the modal                       |\n| `close()`               | closes the modal                      |\n| `setData(data)`         | stores shared data                    |\n| `getData<T>()`          | reads shared data                     |\n| `lock(isLocked = true)` | locks the modal into submitting state |\n| `unlock()`              | alias for `lock(false)`               |\n"
  },
  {
    "path": "docs/src/en/components/common-ui/vben-vxe-table.md",
    "content": "---\noutline: deep\n---\n\n# Vben Vxe Table\n\n`Vben Vxe Table` wraps `vxe-table` together with `Vben Form` so you can build searchable data grids with a shared API.\n\n## Adapter Example\n\nThe current renderer adapter uses `renderTableDefault(...)` for table cell rendering:\n\n```ts\nvxeUI.renderer.add('CellImage', {\n  renderTableDefault(_renderOpts, params) {\n    const { column, row } = params;\n    return h(Image, { src: row[column.field] });\n  },\n});\n\nvxeUI.renderer.add('CellLink', {\n  renderTableDefault(renderOpts) {\n    const { props } = renderOpts;\n    return h(\n      Button,\n      { size: 'small', type: 'link' },\n      { default: () => props?.text },\n    );\n  },\n});\n```\n\n## Basic Usage\n\n```vue\n<script setup lang=\"ts\">\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nconst [Grid, gridApi] = useVbenVxeGrid({\n  gridOptions: {},\n  formOptions: {},\n  gridEvents: {},\n});\n</script>\n\n<template>\n  <Grid />\n</template>\n```\n\n<DemoPreview dir=\"demos/vben-vxe-table/basic\" />\n\n## GridApi\n\n| Method | Description | Type |\n| --- | --- | --- |\n| `setLoading` | update loading state | `(loading: boolean) => void` |\n| `setGridOptions` | merge new grid options | `(options: Partial<VxeGridProps['gridOptions']>) => void` |\n| `reload` | reload data and reset pagination | `(params?: Record<string, any>) => void` |\n| `query` | query data while keeping the current page | `(params?: Record<string, any>) => void` |\n| `grid` | `vxe-grid` instance | `VxeGridInstance` |\n| `formApi` | search form API | `FormApi` |\n| `toggleSearchForm` | toggle or force the search form visible state | `(show?: boolean) => boolean` |\n\n## Props\n\n| Prop | Description | Type |\n| --- | --- | --- |\n| `tableTitle` | table title | `string` |\n| `tableTitleHelp` | help text for the table title | `string` |\n| `class` | class for the outer container | `string` |\n| `gridClass` | class for the `vxe-grid` node | `string` |\n| `gridOptions` | `vxe-grid` options | `DeepPartial<VxeTableGridOptions>` |\n| `gridEvents` | `vxe-grid` event handlers | `DeepPartial<VxeGridListeners>` |\n| `formOptions` | search form options | `VbenFormProps` |\n| `showSearchForm` | whether the search form is visible | `boolean` |\n| `separator` | separator between the search form and table body | `boolean \\| SeparatorOptions` |\n\n## Slots\n\n| Slot              | Description                                             |\n| ----------------- | ------------------------------------------------------- |\n| `toolbar-actions` | left side of the toolbar, near the title                |\n| `toolbar-tools`   | right side of the toolbar, before built-in tool buttons |\n| `table-title`     | custom table title                                      |\n\nAll named slots starting with `form-` are forwarded to the search form.\n"
  },
  {
    "path": "docs/src/en/components/introduction.md",
    "content": "# Introduction\n\n::: info README\n\nThis section documents the framework components, including their usage patterns, configuration points, and major APIs. If the built-in wrappers do not fit your needs, you can always use native components directly or build your own abstractions.\n\n:::\n\n## Layout Components\n\nLayout components are usually used as top-level containers inside the page content area. They provide shared layout styles and some baseline behavior.\n\n## Common Components\n\nCommon components include frequently used UI building blocks such as modals, drawers, forms, and API-backed selectors. Most of them are implemented on top of shared Tailwind CSS and shadcn-vue based primitives, while still allowing each app to adapt them to its own UI library.\n"
  },
  {
    "path": "docs/src/en/components/layout-ui/page.md",
    "content": "---\noutline: deep\n---\n\n# Page\n\n`Page` is the standard top-level layout container for business pages. It provides a header area, a content area, and an optional footer area.\n\n## Props\n\n| Prop | Description | Type | Default |\n| --- | --- | --- | --- |\n| `title` | page title | `string \\| slot` | - |\n| `description` | page description | `string \\| slot` | - |\n| `contentClass` | class for the content area | `string` | - |\n| `headerClass` | class for the header area | `string` | - |\n| `footerClass` | class for the footer area | `string` | - |\n| `autoContentHeight` | auto-calculate the content area height from the visible layout height | `boolean` | `false` |\n| `heightOffset` | extra height offset subtracted from the content area when auto height is enabled | `number` | `0` |\n\n## Slots\n\n| Slot          | Description               |\n| ------------- | ------------------------- |\n| `default`     | page content              |\n| `title`       | custom title              |\n| `description` | custom description        |\n| `extra`       | right-side header content |\n| `footer`      | footer content            |\n"
  },
  {
    "path": "docs/src/en/guide/essentials/build.md",
    "content": "# Build and Deployment\n\n::: tip Preface\n\nSince 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.\n\n:::\n\n## Building\n\nAfter the project development is completed, execute the following command to build:\n\n**Note:** Please execute the following command in the project root directory.\n\n```bash\npnpm build\n```\n\nAfter 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/`\n\n## Preview\n\nBefore publishing, you can preview it locally in several ways, here are two:\n\n- Using the project's custom command for preview (recommended)\n\n**Note：** Please execute the following command in the project root directory.\n\n```bash\npnpm preview\n```\n\nAfter waiting for the build to succeed, visit `http://localhost:4173` to view the effect.\n\n- Local server preview\n\nYou can globally install a `serve` service on your computer, such as `live-server`,\n\n```bash\nnpm i -g live-server\n```\n\nThen execute the `live-server` command in the `dist` directory to view the effect locally.\n\n```bash\ncd apps/web-antd/dist\n# Local preview, default port 8080\nlive-server\n# Specify port\nlive-server --port 9000\n```\n\n## Compression\n\n### Enable `gzip` Compression\n\nTo enable during the build process, change the `.env.production` configuration:\n\n```bash\nVITE_COMPRESS=gzip\n```\n\n### Enable `brotli` Compression\n\nTo enable during the build process, change the `.env.production` configuration:\n\n```bash\nVITE_COMPRESS=brotli\n```\n\n### Enable Both `gzip` and `brotli` Compression\n\nTo enable during the build process, change the `.env.production` configuration:\n\n```bash\nVITE_COMPRESS=gzip,brotli\n```\n\n::: tip Note\n\nBoth `gzip` and `brotli` require specific modules to be installed for use.\n\n:::\n\n::: details gzip 与 brotli 在 nginx 内的配置\n\n```bash\nhttp {\n  # Enable gzip\n  gzip on;\n  # Enable gzip_static\n  # After enabling gzip_static, there might be errors, requiring the installation of specific modules. The installation method can be researched independently.\n  # Only with this enabled, the .gz files packaged by vue files will be effective; otherwise, there is no need to enable gzip for packaging.\n  gzip_static on;\n  gzip_proxied any;\n  gzip_min_length 1k;\n  gzip_buffers 4 16k;\n  # If nginx uses multiple layers of proxy, this must be set to enable gzip.\n  gzip_http_version 1.0;\n  gzip_comp_level 2;\n  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;\n  gzip_vary off;\n  gzip_disable \"MSIE [1-6]\\.\";\n\n  # Enable brotli compression\n  # Requires the installation of the corresponding nginx module, which can be researched independently.\n  # Can coexist with gzip without conflict.\n  brotli on;\n  brotli_comp_level 6;\n  brotli_buffers 16 8k;\n  brotli_min_length 20;\n  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;\n}\n```\n\n:::\n\n## Build Analysis\n\nIf 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`:\n\n```bash\npnpm run build:analyze\n```\n\nAfter running, you can see the specific distribution of sizes on the automatically opened page to analyze which dependencies are problematic.\n\n![Build analysis report](/guide/report.png)\n\n## Deployment\n\nA 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.\n\nFor 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.\n\n```bash\n# nginx configuration\nlocation / {\n  # Do not cache html to prevent cache from continuing to be effective after program updates\n  if ($request_filename ~* .*\\.(?:htm|html)$) {\n    add_header Cache-Control \"private, no-store, no-cache, must-revalidate, proxy-revalidate\";\n    access_log on;\n  }\n  # This is the storage path for the files inside the vue packaged dist folder\n  root   /srv/www/project/;\n  index  index.html index.htm;\n}\n```\n\nIf you find the resource path is incorrect during deployment, you just need to modify the `.env.production` file.\n\n```bash\n# Configure the change according to your own path\n# Note that it needs to start and end with /\nVITE_BASE=/\nVITE_BASE=/xxx/\n```\n\n### Integration of Frontend Routing and Server\n\nThe project uses vue-router for frontend routing, so you can choose between two modes: history and hash.\n\n- `hash` mode will append `#` to the URL by default.\n- `history` mode will not, but `history` mode requires server-side support.\n\nYou can modify the mode in `.env.production`:\n\n```bash\nVITE_ROUTER_HISTORY=hash\n```\n\n### Server Configuration for History Mode Routing\n\nEnabling `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)\n\nHere is an example of `nginx` configuration:\n\n#### Deployment at the Root Directory\n\n```bash {5}\nserver {\n  listen 80;\n  location / {\n    # For use with History mode\n    try_files $uri $uri/ /index.html;\n  }\n}\n```\n\n#### Deployment to a Non-root Directory\n\n- First, you need to change the `.env.production` configuration during packaging:\n\n```bash\nVITE_BASE = /sub/\n```\n\n- Then configure in the nginx configuration file\n\n```bash {8}\nserver {\n    listen       80;\n    server_name  localhost;\n    location /sub/ {\n      # This is the path where the vue packaged dist files are stored\n      alias   /srv/www/project/;\n      index index.html index.htm;\n      try_files $uri $uri/ /sub/index.html;\n    }\n}\n```\n\n## Cross-Domain Handling\n\nUsing nginx to handle cross-domain issues after project deployment\n\n1. Configure the frontend project API address in the `.env.production` file in the project directory:\n\n```bash\nVITE_GLOB_API_URL=/api\n```\n\n2. Configure nginx to forward requests to the backend\n\n```bash {10-11}\nserver {\n  listen       8080;\n  server_name  localhost;\n  # API proxy for solving cross-domain issues\n  location /api {\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    # Backend API address\n    proxy_pass http://110.110.1.1:8080/api;\n    rewrite \"^/api/(.*)$\" /$1 break;\n    proxy_redirect default;\n    add_header Access-Control-Allow-Origin *;\n    add_header Access-Control-Allow-Headers X-Requested-With;\n    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;\n  }\n}\n```\n"
  },
  {
    "path": "docs/src/en/guide/essentials/concept.md",
    "content": "# Basic Concepts\n\nIn 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.\n\n## Monorepo\n\nMonorepo 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.\n\n## Applications\n\nApplications 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.\n\n::: tip\n\nApplications 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.\n\n:::\n\n## Packages\n\nA 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.\n\nYou can consider these packages as independent `npm` packages, and they are used in the same way as `npm` packages.\n\n### Package Import\n\nImporting a package in `package.json`:\n\n```json {3}\n{\n  \"dependencies\": {\n    \"@vben/utils\": \"workspace:*\"\n  }\n}\n```\n\n### Package Usage\n\nImporting a package in the code:\n\n```ts\nimport { isString } from '@vben/utils';\n```\n\n## Aliases\n\nIn 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`.\n\n```json {3}\n{\n  \"imports\": {\n    \"#/*\": \"./src/*\"\n  }\n}\n```\n\nTo make these aliases recognizable by the IDE, we also need to configure them in `tsconfig.json`:\n\n```json {5}\n{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"#/*\": [\"src/*\"]\n    }\n  }\n}\n```\n\nThis way, you can use aliases in your code.\n"
  },
  {
    "path": "docs/src/en/guide/essentials/development.md",
    "content": "# Local Development {#development}\n\n::: tip Code Acquisition\n\nIf you haven't acquired the code yet, you can start by reading the documentation from [Quick Start](../introduction/quick-start.md).\n\n:::\n\n## Prerequisites\n\nFor a better development experience, we provide some tool configurations and project descriptions to facilitate your development.\n\n### Required Basic Knowledge\n\nThis 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:\n\n- [Vue3](https://vuejs.org/)\n- [Tailwind CSS](https://tailwindcss.com/)\n- [TypeScript](https://www.typescriptlang.org/)\n- [Vue Router](https://router.vuejs.org/)\n- [Vitejs](https://vitejs.dev/)\n- [Pnpm](https://pnpm.io/)\n- [Turbo](https://turbo.build/)\n\n### Tool Configuration\n\nIf 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:\n\n- [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Official Vue plugin (essential).\n- [Tailwind CSS](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) - Tailwind CSS autocomplete plugin.\n- [CSS Variable Autocomplete](https://marketplace.visualstudio.com/items?itemName=vunguyentuan.vscode-css-variables) - CSS variable autocomplete plugin.\n- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Iconify icon plugin.\n- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) - i18n plugin.\n- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - Script code linting.\n- [Oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) - Oxlint / Oxfmt integration.\n- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS formatting.\n- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - Spelling checker.\n- [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - .env file highlighting.\n\n## Npm Scripts\n\nNpm scripts are common configurations used in the project to perform common tasks such as starting the project, building the project, and more. The following scripts can be found in the `package.json` file at the root of the project.\n\nThe execution command is: `pnpm run [script]` or `npm run [script]`.\n\n```json\n{\n  \"scripts\": {\n    // Build the project\n    \"build\": \"cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build\",\n    // Build the project with analysis\n    \"build:analyze\": \"turbo build:analyze\",\n    // Build a local Docker image\n    \"build:docker\": \"./scripts/deploy/build-local-docker-image.sh\",\n    // Build the web-antd application separately\n    \"build:antd\": \"pnpm run build --filter=@vben/web-antd\",\n    // Build the web-antdv-next application separately\n    \"build:antdv-next\": \"pnpm run build --filter=@vben/web-antdv-next\",\n    // Build the documentation separately\n    \"build:docs\": \"pnpm run build --filter=@vben/docs\",\n    // Build the web-ele application separately\n    \"build:ele\": \"pnpm run build --filter=@vben/web-ele\",\n    // Build the web-naive application separately\n    \"build:naive\": \"pnpm run build --filter=@vben/web-naive\",\n    // Build the web-tdesign application separately\n    \"build:tdesign\": \"pnpm run build --filter=@vben/web-tdesign\",\n    // Build the playground application separately\n    \"build:play\": \"pnpm run build --filter=@vben/playground\",\n    // Changeset version management\n    \"changeset\": \"pnpm exec changeset\",\n    // Check for various issues in the project\n    \"check\": \"pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell\",\n    // Check for circular dependencies\n    \"check:circular\": \"vsh check-circular\",\n    // Check spelling\n    \"check:cspell\": \"cspell lint **/*.ts **/README.md .changeset/*.md --no-progress\",\n    // Check dependencies\n    \"check:dep\": \"vsh check-dep\",\n    // Check types\n    \"check:type\": \"turbo run typecheck\",\n    // Clean the project (delete node_modules, dist, .turbo, etc.)\n    \"clean\": \"node ./scripts/clean.mjs\",\n    // Commit code\n    \"commit\": \"czg\",\n    // Start the project (by default, the dev scripts of all packages in the entire repository will run)\n    \"dev\": \"turbo-run dev\",\n    // Start the web-antd application\n    \"dev:antd\": \"pnpm -F @vben/web-antd run dev\",\n    // Start the web-antdv-next application\n    \"dev:antdv-next\": \"pnpm -F @vben/web-antdv-next run dev\",\n    // Start the documentation\n    \"dev:docs\": \"pnpm -F @vben/docs run dev\",\n    // Start the web-ele application\n    \"dev:ele\": \"pnpm -F @vben/web-ele run dev\",\n    // Start the web-naive application\n    \"dev:naive\": \"pnpm -F @vben/web-naive run dev\",\n    // Start the web-tdesign application\n    \"dev:tdesign\": \"pnpm -F @vben/web-tdesign run dev\",\n    // Start the playground application\n    \"dev:play\": \"pnpm -F @vben/playground run dev\",\n    // Format code\n    \"format\": \"vsh lint --format\",\n    // Lint code\n    \"lint\": \"vsh lint\",\n    // After installing dependencies, execute the stub script for all packages\n    \"postinstall\": \"pnpm -r run stub --if-present\",\n    // Only allow using pnpm\n    \"preinstall\": \"npx only-allow pnpm\",\n    // Install lefthook\n    \"prepare\": \"pnpm exec lefthook install\",\n    // Preview the application\n    \"preview\": \"turbo-run preview\",\n    // Package specification check\n    \"publint\": \"vsh publint\",\n    // Delete all node_modules, yarn.lock, package-lock.json, and reinstall dependencies\n    \"reinstall\": \"pnpm clean --del-lock && pnpm install\",\n    // Run e2e tests\n    \"test:e2e\": \"turbo run test:e2e\",\n    // Run vitest unit tests\n    \"test:unit\": \"vitest run --dom\",\n    // Update project dependencies\n    \"update:deps\": \"npx taze -r -w\",\n    // Changeset generation and versioning\n    \"version\": \"pnpm exec changeset version && pnpm install --no-frozen-lockfile\"\n  }\n}\n```\n\n## Running the Project Locally\n\nTo 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:\n\n```bash\npnpm dev\n```\n\nIf you want to run a specific application directly, you can execute the following commands:\n\nTo run the `web-antd` application:\n\n```bash\npnpm dev:antd\n```\n\nTo run the `web-antdv-next` application:\n\n```bash\npnpm dev:antdv-next\n```\n\nTo run the `web-naive` application:\n\n```bash\npnpm dev:naive\n```\n\nTo run the `web-ele` application:\n\n```bash\npnpm dev:ele\n```\n\nTo run the `web-tdesign` application:\n\n```bash\npnpm dev:tdesign\n```\n\nTo run the `docs` application:\n\n```bash\npnpm dev:docs\n```\n\n### Distinguishing Build Environments\n\nIn actual business development, multiple environments are usually distinguished during the build process, such as the test environment `test` and the production environment `build`.\n\nAt this point, you can modify three files and add corresponding script configurations to distinguish between production environments.\n\nTake the addition of the test environment `test` to `@vben/web-antd` as an example:\n\n- `apps\\web-antd\\package.json`\n\n```json\n\"scripts\": {\n  \"build:prod\": \"pnpm vite build --mode production\",\n  \"build:test\": \"pnpm vite build --mode test\",\n  \"build:analyze\": \"pnpm vite build --mode analyze\",\n  \"dev\": \"pnpm vite --mode development\",\n  \"preview\": \"vite preview\",\n  \"typecheck\": \"vue-tsc --noEmit --skipLibCheck\"\n}\n```\n\nAdd the command `\"build:test\"` and change the original `\"build\"` to `\"build:prod\"` to avoid building packages for two environments simultaneously.\n\n- `package.json`\n\n```json\n\"scripts\": {\n    \"build\": \"cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build\",\n    \"build:analyze\": \"turbo build:analyze\",\n    \"build:antd\": \"pnpm run build --filter=@vben/web-antd\",\n    \"build-test:antd\": \"pnpm run build --filter=@vben/web-antd build:test\",\n\n    ······\n}\n```\n\nAdd the command to build the test environment in the root directory `package.json`.\n\n- `turbo.json`\n\n```json\n\"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\n        \"dist/**\",\n        \"dist.zip\",\n        \".vitepress/dist.zip\",\n        \".vitepress/dist/**\"\n      ]\n    },\n\n    \"build-test:antd\": {\n      \"dependsOn\": [\"@vben/web-antd#build:test\"],\n      \"outputs\": [\"dist/**\"]\n    },\n\n    \"@vben/web-antd#build:test\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"dist/**\"]\n    },\n\n    ······\n```\n\nAdd the relevant dependent commands in `turbo.json`.\n\n## Public Static Resources\n\nIf you need to use public static resources in the project, such as images and static HTML, and want to import them directly during development through `src=\"/xxx.png\"`.\n\nYou need to put the resources in the `public/static` directory of the corresponding project. The import path for the resources should be `src=\"/static/xxx.png\"`.\n\n## DevTools\n\nThe 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:\n\n```bash\nVITE_DEVTOOLS=true\n```\n\nOnce enabled, a Vue DevTools icon will appear at the bottom of the page during project runtime. Click it to open the DevTools.\n\n![Vue DevTools](/guide/devtools.png)\n\n## Running Documentation Locally\n\nTo run the documentation locally and make adjustments, you can execute the following command:\n\n```bash\npnpm dev:docs\n```\n\n## Troubleshooting\n\nIf you encounter dependency-related issues, you can try reinstalling the dependencies:\n\n```bash\n# Execute this command at the root of the project.\n# This command will delete all node_modules, yarn.lock, and package-lock.json files\n# and then reinstall dependencies (this process will be noticeably slower).\npnpm reinstall\n```\n"
  },
  {
    "path": "docs/src/en/guide/essentials/external-module.md",
    "content": "# External Modules\n\nIn 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:\n\n## Installing Dependencies\n\n::: tip Install dependencies into a specific package\n\n- Since the project uses [pnpm](https://pnpm.io/) as the package management tool, we need to use the `pnpm` command to install dependencies.\n- 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.\n\n:::\n\n```bash\n# cd /path/to/your/package\npnpm add ant-design-vue\n```\n\n## Usage\n\n### Global Import\n\n```ts\nimport { createApp } from 'vue';\nimport Antd from 'ant-design-vue';\nimport App from './App';\nimport 'ant-design-vue/dist/reset.css';\n\nconst app = createApp(App);\n\napp.use(Antd).mount('#app');\n```\n\n#### Usage\n\n```vue\n<template>\n  <a-button>text</a-button>\n</template>\n```\n\n### Partial Import\n\n```vue\n<script setup lang=\"ts\">\nimport { Button } from 'ant-design-vue';\n</script>\n\n<template>\n  <Button>text</Button>\n</template>\n```\n\n::: warning Note\n\n- If the component depends on styles, you also need to import the style file.\n\n:::\n"
  },
  {
    "path": "docs/src/en/guide/essentials/icons.md",
    "content": "# Icons\n\n::: tip About Icon Management\n\n- 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.\n- 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.\n\n:::\n\nThere are several ways to use icons in the project, you can choose according to the actual situation:\n\n## Iconify Icons <Badge text=\"Recommended\" type=\"tip\"/>\n\nIntegrated with the [iconify](https://github.com/iconify/iconify) icon library\n\n### Adding New Icons\n\nYou can add new icons in the `packages/icons/src/iconify` directory:\n\n```ts\n// packages/icons/src/iconify/index.ts\nimport { createIconifyIcon } from '@vben-core/icons';\n\nexport const MdiKeyboardEsc = createIconifyIcon('mdi:keyboard-esc');\n```\n\n### Usage\n\n```vue\n<script setup lang=\"ts\">\nimport { MdiKeyboardEsc } from '@vben/icons';\n</script>\n\n<template>\n  <!-- An icon with a width and height of 20px -->\n  <MdiKeyboardEsc class=\"size-5\" />\n</template>\n```\n\n## SVG Icons <Badge text=\"Recommended\" type=\"tip\"/>\n\nInstead of using Svg Sprite, SVG icons are directly imported,\n\n### Adding New Icons\n\nYou 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`:\n\n```ts\n// packages/icons/src/svg/index.ts\nimport { createIconifyIcon } from '@vben-core/icons';\n\nconst SvgTestIcon = createIconifyIcon('svg:test');\n\nexport { SvgTestIcon };\n```\n\n### Usage\n\n```vue\n<script setup lang=\"ts\">\nimport { SvgTestIcon } from '@vben/icons';\n</script>\n\n<template>\n  <!-- An icon with a width and height of 20px -->\n  <SvgTestIcon class=\"size-5\" />\n</template>\n```\n\n## Tailwind CSS Icons <Badge text=\"Not Recommended\" type=\"danger\"/>\n\n### Usage\n\nYou can use the icons by directly adding the Tailwind CSS icon class names, which can be found on [iconify](https://github.com/iconify/iconify) ：\n\n```vue\n<span class=\"icon-[mdi--ab-testing]\"></span>\n```\n"
  },
  {
    "path": "docs/src/en/guide/essentials/route.md",
    "content": "---\noutline: deep\n---\n\n# Routes and Menus\n\n::: info\n\nThis page is translated by machine translation and may not be very accurate.\n\n:::\n\nIn the project, the framework provides a basic routing system and **automatically generates the corresponding menu structure based on the routing files**.\n\n## Types of Routes\n\nRoutes 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.\n\nBoth static and dynamic routes go through permission control, which can be controlled by configuring the `authority` field in the `meta` property of the route.\n\n### Core Routes\n\nCore 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.\n\n::: tip\n\nCore 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.\n\n:::\n\n### Static Routes\n\nThe configuration of static routes is in the `src/router/routes/index` directory under the application. Open the commented file content:\n\n::: tip\n\nPermission 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.\n\n:::\n\n```ts\n// Uncomment if needed and create the folder\n// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); // [!code --]\nconst staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); // [!code ++]\n/** Dynamic routes */\nconst dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);\n\n/** External route list, these pages can be accessed without Layout, possibly used for embedding in other systems */\n// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles) // [!code --]\nconst externalRoutes: RouteRecordRaw[] = []; // [!code --]\nconst externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); // [!code ++]\n```\n\n### Dynamic Routes\n\nThe 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.\n\n## Route Definition\n\nThe configuration method of static routes and dynamic routes is the same. Below is the configuration of secondary and multi-level routes:\n\n### Secondary Routes\n\n::: details Secondary Route Example Code\n\n```ts\nimport type { RouteRecordRaw } from 'vue-router';\n\nimport { VBEN_LOGO_URL } from '@vben/constants';\n\nimport { BasicLayout } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      badgeType: 'dot',\n      badgeVariants: 'destructive',\n      icon: VBEN_LOGO_URL,\n      order: 9999,\n      title: $t('page.vben.title'),\n    },\n    name: 'VbenProject',\n    path: '/vben-admin',\n    redirect: '/vben-admin/about',\n    children: [\n      {\n        name: 'VbenAbout',\n        path: '/vben-admin/about',\n        component: () => import('#/views/_core/about/index.vue'),\n        meta: {\n          badgeType: 'dot',\n          badgeVariants: 'destructive',\n          icon: 'lucide:copyright',\n          title: $t('page.vben.about'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n```\n\n:::\n\n### Multi-level Routes\n\n::: tip\n\n- 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.\n- In most cases, the `redirect` property of the parent route does not need to be specified, it will default to the first child route.\n\n:::\n\n::: details Multi-level Route Example Code\n\n```ts\nimport type { RouteRecordRaw } from 'vue-router';\n\nimport { BasicLayout } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'ic:baseline-view-in-ar',\n      keepAlive: true,\n      order: 1000,\n      title: $t('demos.title'),\n    },\n    name: 'Demos',\n    path: '/demos',\n    redirect: '/demos/access',\n    children: [\n      // Nested menu\n      {\n        meta: {\n          icon: 'ic:round-menu',\n          title: $t('demos.nested.title'),\n        },\n        name: 'NestedDemos',\n        path: '/demos/nested',\n        redirect: '/demos/nested/menu1',\n        children: [\n          {\n            name: 'Menu1Demo',\n            path: '/demos/nested/menu1',\n            component: () => import('#/views/demos/nested/menu-1.vue'),\n            meta: {\n              icon: 'ic:round-menu',\n              keepAlive: true,\n              title: $t('demos.nested.menu1'),\n            },\n          },\n          {\n            name: 'Menu2Demo',\n            path: '/demos/nested/menu2',\n            meta: {\n              icon: 'ic:round-menu',\n              keepAlive: true,\n              title: $t('demos.nested.menu2'),\n            },\n            redirect: '/demos/nested/menu2/menu2-1',\n            children: [\n              {\n                name: 'Menu21Demo',\n                path: '/demos/nested/menu2/menu2-1',\n                component: () => import('#/views/demos/nested/menu-2-1.vue'),\n                meta: {\n                  icon: 'ic:round-menu',\n                  keepAlive: true,\n                  title: $t('demos.nested.menu2_1'),\n                },\n              },\n            ],\n          },\n          {\n            name: 'Menu3Demo',\n            path: '/demos/nested/menu3',\n            meta: {\n              icon: 'ic:round-menu',\n              title: $t('demos.nested.menu3'),\n            },\n            redirect: '/demos/nested/menu3/menu3-1',\n            children: [\n              {\n                name: 'Menu31Demo',\n                path: 'menu3-1',\n                component: () => import('#/views/demos/nested/menu-3-1.vue'),\n                meta: {\n                  icon: 'ic:round-menu',\n                  keepAlive: true,\n                  title: $t('demos.nested.menu3_1'),\n                },\n              },\n              {\n                name: 'Menu32Demo',\n                path: 'menu3-2',\n                meta: {\n                  icon: 'ic:round-menu',\n                  title: $t('demos.nested.menu3_2'),\n                },\n                redirect: '/demos/nested/menu3/menu3-2/menu3-2-1',\n                children: [\n                  {\n                    name: 'Menu321Demo',\n                    path: '/demos/nested/menu3/menu3-2/menu3-2-1',\n                    component: () =>\n                      import('#/views/demos/nested/menu-3-2-1.vue'),\n                    meta: {\n                      icon: 'ic:round-menu',\n                      keepAlive: true,\n                      title: $t('demos.nested.menu3_2_1'),\n                    },\n                  },\n                ],\n              },\n            ],\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport default routes;\n```\n\n:::\n\n## Adding a New Page\n\nTo add a new page, you only need to add a route and the corresponding page component.\n\n### Adding a Route\n\nAdd a route object in the corresponding route file, as follows:\n\n```ts\nimport type { RouteRecordRaw } from 'vue-router';\n\nimport { VBEN_LOGO_URL } from '@vben/constants';\n\nimport { BasicLayout } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'mdi:home',\n      title: $t('page.home.title'),\n    },\n    name: 'Home',\n    path: '/home',\n    redirect: '/home/index',\n    children: [\n      {\n        name: 'HomeIndex',\n        path: '/home/index',\n        component: () => import('#/views/home/index.vue'),\n        meta: {\n          icon: 'mdi:home',\n          title: $t('page.home.index'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n```\n\n### Adding a Page Component\n\nIn `#/views/home/`, add a new `index.vue` file, as follows:\n\n```vue\n<template>\n  <div>\n    <h1>home page</h1>\n  </div>\n</template>\n```\n\n### Verification\n\nAt this point, the page has been added. Visit `http://localhost:5555/home/index` to see the corresponding page.\n\n## Route Configuration\n\nThe route configuration items are mainly in the `meta` property of the route object. The following are common configuration items:\n\n```ts {5-8}\nconst routes = [\n  {\n    name: 'HomeIndex',\n    path: '/home/index',\n    meta: {\n      icon: 'mdi:home',\n      title: $t('page.home.index'),\n    },\n  },\n];\n```\n\n::: details Route Meta Configuration Type Definition\n\n```ts\ninterface RouteMeta {\n  /**\n   * Active icon (menu)\n   */\n  activeIcon?: string;\n  /**\n   * The currently active menu, sometimes you don't want to activate the existing menu, use this to activate the parent menu\n   */\n  activePath?: string;\n  /**\n   * Whether to fix the tab\n   * @default false\n   */\n  affixTab?: boolean;\n  /**\n   * The order of fixed tabs\n   * @default 0\n   */\n  affixTabOrder?: number;\n  /**\n   * Specific roles required to access\n   * @default []\n   */\n  authority?: string[];\n  /**\n   * Badge\n   */\n  badge?: string;\n  /**\n   * Badge type\n   */\n  badgeType?: 'dot' | 'normal';\n  /**\n   * Badge color\n   */\n  badgeVariants?:\n    | 'default'\n    | 'destructive'\n    | 'primary'\n    | 'success'\n    | 'warning'\n    | string;\n  /**\n   * The children of the current route are not displayed in the menu\n   * @default false\n   */\n  hideChildrenInMenu?: boolean;\n  /**\n   * The current route is not displayed in the breadcrumb\n   * @default false\n   */\n  hideInBreadcrumb?: boolean;\n  /**\n   * The current route is not displayed in the menu\n   * @default false\n   */\n  hideInMenu?: boolean;\n  /**\n   * The current route is not displayed in the tab\n   * @default false\n   */\n  hideInTab?: boolean;\n  /**\n   * Icon (menu/tab)\n   */\n  icon?: string;\n  /**\n   * iframe address\n   */\n  iframeSrc?: string;\n  /**\n   * Ignore permissions, can be accessed directly\n   * @default false\n   */\n  ignoreAccess?: boolean;\n  /**\n   * Enable KeepAlive cache\n   */\n  keepAlive?: boolean;\n  /**\n   * External link - jump path\n   */\n  link?: string;\n  /**\n   * Whether the route has been loaded\n   */\n  loaded?: boolean;\n  /**\n   * Maximum number of open tabs\n   * @default false\n   */\n  maxNumOfOpenTab?: number;\n  /**\n   * The menu can be seen, but access will be redirected to 403\n   */\n  menuVisibleWithForbidden?: boolean;\n  /**\n   * Open in a new window\n   */\n  openInNewWindow?: boolean;\n  /**\n   * Used for route -> menu sorting\n   */\n  order?: number;\n  /**\n   * Parameters carried by the menu\n   */\n  query?: Recordable;\n  /**\n   * Title name\n   */\n  title: string;\n}\n```\n\n:::\n\n### title\n\n- Type: `string`\n- Default: `''`\n\nUsed to configure the title of the page, which will be displayed in the menu and tab. Generally used with internationalization.\n\n### icon\n\n- Type: `string`\n- Default: `''`\n\nUsed 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.\n\n### activeIcon\n\n- Type: `string`\n- Default: `''`\n\nUsed 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.\n\n### keepAlive\n\n- Type: `boolean`\n- Default: `false`\n\nUsed 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.\n\n### hideInMenu\n\n- Type: `boolean`\n- Default: `false`\n\nUsed to configure whether the page is hidden in the menu. When hidden, the page will not be displayed in the menu.\n\n### hideInTab\n\n- Type: `boolean`\n- Default: `false`\n\nUsed to configure whether the page is hidden in the tab. When hidden, the page will not be displayed in the tab.\n\n### hideInBreadcrumb\n\n- Type: `boolean`\n- Default: `false`\n\nUsed to configure whether the page is hidden in the breadcrumb. When hidden, the page will not be displayed in the breadcrumb.\n\n### hideChildrenInMenu\n\n- Type: `boolean`\n- Default: `false`\n\nUsed to configure whether the subpages of the page are hidden in the menu. When hidden, the subpages will not be displayed in the menu.\n\n### authority\n\n- Type: `string[]`\n- Default: `[]`\n\nUsed to configure the permissions of the page. Only users with the corresponding permissions can access the page. If not configured, no permissions are required.\n\n### badge\n\n- Type: `string`\n- Default: `''`\n\nUsed to configure the badge of the page, which will be displayed in the menu.\n\n### badgeType\n\n- Type: `'dot' | 'normal'`\n- Default: `'normal'`\n\nUsed to configure the badge type of the page. `dot` is a small red dot, `normal` is text.\n\n### badgeVariants\n\n- Type: `'default' | 'destructive' | 'primary' | 'success' | 'warning' | string`\n- Default: `'success'`\n\nUsed to configure the badge color of the page.\n\n### activePath\n\n- Type: `string`\n- Default: `''`\n\nUsed to configure the currently active menu. Sometimes the page is not displayed in the menu, and this is used to activate the parent menu.\n\n### affixTab\n\n- Type: `boolean`\n- Default: `false`\n\nUsed to configure whether the page is fixed in the tab. When fixed, the page cannot be closed.\n\n### affixTabOrder\n\n- Type: `number`\n- Default: `0`\n\nUsed to configure the order of fixed tabs, sorted in ascending order.\n\n### iframeSrc\n\n- Type: `string`\n- Default: `''`\n\nUsed to configure the `iframe` address of the embedded page. When set, the corresponding page will be embedded in the current page.\n\n### ignoreAccess\n\n- Type: `boolean`\n- Default: `false`\n\nUsed to configure whether the page ignores permissions and can be accessed directly.\n\n### link\n\n- Type: `string`\n- Default: `''`\n\nUsed to configure the external link jump path, which will open in a new window.\n\n### maxNumOfOpenTab\n\n- Type: `number`\n- Default: `-1`\n\nUsed 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).\n\n### menuVisibleWithForbidden\n\n- Type: `boolean`\n- Default: `false`\n\nUsed to configure whether the page can be seen in the menu, but access will be redirected to 403.\n\n### openInNewWindow\n\n- Type: `boolean`\n- Default: `false`\n\nWhen set to `true`, the page will open in a new window.\n\n### order\n\n- Type: `number`\n- Default: `0`\n\nUsed to configure the sorting of the page, used for route to menu sorting.\n\n_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.\n\n### query\n\n- Type: `Recordable`\n- Default: `{}`\n\nUsed to configure the menu parameters of the page, which will be passed to the page in the menu.\n\n## Route Refresh\n\nThe route refresh method is as follows:\n\n```vue\n<script setup lang=\"ts\">\nimport { useRefresh } from '@vben/hooks';\n\nconst { refresh } = useRefresh();\n\n// Refresh the current route\nrefresh();\n</script>\n```\n"
  },
  {
    "path": "docs/src/en/guide/essentials/server.md",
    "content": "# Server Interaction and Data Mocking\n\n::: tip Note\n\nThis document explains how to use Mock data and interact with the server in a development environment, involving technologies such as:\n\n- [Nitro](https://nitro.unjs.io/) A lightweight backend server that can be deployed anywhere, used as a Mock server in the project.\n- [axios](https://axios-http.com/docs/intro) Used to send HTTP requests to interact with the server.\n\n:::\n\n## Interaction in Development Environment\n\nIf 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.\n\n### Local Development CORS Configuration\n\n::: tip Hint\n\nThe CORS configuration for local development has already been set up. If you have other requirements, you can add or adjust the configuration as needed.\n\n:::\n\n#### Configuring Local Development API Endpoint\n\nConfigure the API endpoint in the `.env.development` file at the project root directory, here it is set to `/api`:\n\n```bash\nVITE_GLOB_API_URL=/api\n```\n\n#### Configuring Development Server Proxy\n\nIn the development environment, if you need to handle CORS, configure the API endpoint in the `vite.config.mts` file under the corresponding application directory:\n\n```ts{8-16}\n// apps/web-antd/vite.config.mts\nimport { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    vite: {\n      server: {\n        proxy: {// [!code focus:11]\n          '/api': {\n            changeOrigin: true,\n            rewrite: (path) => path.replace(/^\\/api/, ''),\n            // mock proxy\n            target: 'http://localhost:5320/api',\n            ws: true,\n          },\n        },\n      },\n    },\n  };\n});\n```\n\n#### API Requests\n\nBased on the above configuration, we can use `/api` as the prefix for API requests in our frontend project, for example:\n\n```ts\nimport axios from 'axios';\n\naxios.get('/api/user').then((res) => {\n  console.log(res);\n});\n```\n\nAt this point, the request will be proxied to `http://localhost:5320/api/user`.\n\n::: warning Note\n\nFrom 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.\n\n:::\n\n### Configuration Without CORS\n\nIf 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`.\n\nConfigure the API endpoint in the `.env.development` file at the project root directory:\n\n```bash\nVITE_GLOB_API_URL=https://mock-napi.vben.pro/api\n```\n\n## Production Environment Interaction\n\n### API Endpoint Configuration\n\nConfigure the API endpoint in the `.env.production` file at the project root directory:\n\n```bash\nVITE_GLOB_API_URL=https://mock-napi.vben.pro/api\n```\n\n::: tip How to Dynamically Modify API Endpoint in Production\n\nVariables 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.\n\n:::\n\n### Cross-Origin Resource Sharing (CORS) Handling\n\nIn 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).\n\n## API Request Configuration\n\nThe 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.\n\n### Request Examples\n\n#### GET Request\n\n```ts\nimport { requestClient } from '#/api/request';\n\nexport async function getUserInfoApi() {\n  return requestClient.get<UserInfo>('/user/info');\n}\n```\n\n#### POST/PUT Request\n\n```ts\nimport { requestClient } from '#/api/request';\n\nexport async function saveUserApi(user: UserInfo) {\n  return requestClient.post<UserInfo>('/user', user);\n}\n\nexport async function saveUserApi(user: UserInfo) {\n  return requestClient.put<UserInfo>('/user', user);\n}\n\nexport async function saveUserApi(user: UserInfo) {\n  const url = user.id ? `/user/${user.id}` : '/user/';\n  return requestClient.request<UserInfo>(url, {\n    data: user,\n    // OR PUT\n    method: user.id ? 'PUT' : 'POST',\n  });\n}\n```\n\n#### DELETE Request\n\n```ts\nimport { requestClient } from '#/api/request';\n\nexport async function deleteUserApi(userId: number) {\n  return requestClient.delete<boolean>(`/user/${userId}`);\n}\n```\n\n### Request Configuration\n\nThe `src/api/request.ts` within the application can be configured according to the needs of your application:\n\n```ts\n/**\n * This file can be adjusted according to business logic\n */\nimport type { HttpResponse } from '@vben/request';\n\nimport { useAppConfig } from '@vben/hooks';\nimport { preferences } from '@vben/preferences';\nimport {\n  authenticateResponseInterceptor,\n  errorMessageResponseInterceptor,\n  RequestClient,\n} from '@vben/request';\nimport { useAccessStore } from '@vben/stores';\n\nimport { message } from 'ant-design-vue';\n\nimport { useAuthStore } from '#/store';\n\nimport { refreshTokenApi } from './core';\n\nconst { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n\nfunction createRequestClient(baseURL: string) {\n  const client = new RequestClient({\n    baseURL,\n  });\n\n  /**\n   * Re-authentication Logic\n   */\n  async function doReAuthenticate() {\n    console.warn('Access token or refresh token is invalid or expired. ');\n    const accessStore = useAccessStore();\n    const authStore = useAuthStore();\n    accessStore.setAccessToken(null);\n    if (preferences.app.loginExpiredMode === 'modal') {\n      accessStore.setLoginExpired(true);\n    } else {\n      await authStore.logout();\n    }\n  }\n\n  /**\n   * Refresh token Logic\n   */\n  async function doRefreshToken() {\n    const accessStore = useAccessStore();\n    const resp = await refreshTokenApi();\n    const newToken = resp.data;\n    accessStore.setAccessToken(newToken);\n    return newToken;\n  }\n\n  function formatToken(token: null | string) {\n    return token ? `Bearer ${token}` : null;\n  }\n\n  // Request Header Processing\n  client.addRequestInterceptor({\n    fulfilled: async (config) => {\n      const accessStore = useAccessStore();\n\n      config.headers.Authorization = formatToken(accessStore.accessToken);\n      config.headers['Accept-Language'] = preferences.app.locale;\n      return config;\n    },\n  });\n\n  // Deal Response Data\n  client.addResponseInterceptor<HttpResponse>({\n    fulfilled: (response) => {\n      const { data: responseData, status } = response;\n\n      const { code, data } = responseData;\n\n      if (status >= 200 && status < 400 && code === 0) {\n        return data;\n      }\n      throw Object.assign({}, response, { response });\n    },\n  });\n\n  // Handling Token Expiration\n  client.addResponseInterceptor(\n    authenticateResponseInterceptor({\n      client,\n      doReAuthenticate,\n      doRefreshToken,\n      enableRefreshToken: preferences.app.enableRefreshToken,\n      formatToken,\n    }),\n  );\n\n  // Generic error handling; if none of the above error handling logic is triggered, it will fall back to this.\n  client.addResponseInterceptor(\n    errorMessageResponseInterceptor((msg: string, error) => {\n      // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理，根据不同的 code 做不同的提示，而不是直接使用 message.error 提示 msg\n      // 当前mock接口返回的错误字段是 error 或者 message\n      const responseData = error?.response?.data ?? {};\n      const errorMessage = responseData?.error ?? responseData?.message ?? '';\n      // 如果没有错误信息，则会根据状态码进行提示\n      message.error(errorMessage || msg);\n    }),\n  );\n\n  return client;\n}\n\nexport const requestClient = createRequestClient(apiURL);\n\nexport const baseRequestClient = new RequestClient({ baseURL: apiURL });\n```\n\n### Multiple API Endpoints\n\nTo handle multiple API endpoints, simply create multiple `requestClient` instances, as follows:\n\n```ts\nconst { apiURL, otherApiURL } = useAppConfig(\n  import.meta.env,\n  import.meta.env.PROD,\n);\n\nexport const requestClient = createRequestClient(apiURL);\n\nexport const otherRequestClient = createRequestClient(otherApiURL);\n```\n\n## Refresh Token\n\nThe project provides a default logic for refreshing tokens. To enable it, follow the configuration below:\n\n- Ensure the refresh token feature is enabled\n\nAdjust the `preferences.ts` in the corresponding application directory to ensure `enableRefreshToken='true'`.\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    enableRefreshToken: true,\n  },\n});\n```\n\nConfigure the `doRefreshToken` method in `src/api/request.ts` as follows:\n\n```ts\n// Adjust this to your token format\nfunction formatToken(token: null | string) {\n  return token ? `Bearer ${token}` : null;\n}\n\n/**\n * Refresh token logic\n */\nasync function doRefreshToken() {\n  const accessStore = useAccessStore();\n  // Adjust this to your refresh token API\n  const resp = await refreshTokenApi();\n  const newToken = resp.data;\n  accessStore.setAccessToken(newToken);\n  return newToken;\n}\n```\n\n## Data Mocking\n\n::: tip Production Environment Mock\n\nThe new version no longer supports mock in the production environment. Please use real interfaces.\n\n:::\n\nMock 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.\n\nThe 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.\n\n### Using Nitro\n\nThe 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.\n\n[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/).\n\n## Disabling Mock Service\n\nSince 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.\n\n```bash\n# .env.development\nVITE_NITRO_MOCK=false\n```\n"
  },
  {
    "path": "docs/src/en/guide/essentials/settings.md",
    "content": "# Configuration\n\n## Environment Variable Configuration\n\nThe project's environment variable configuration is located in the application directory under `.env`, `.env.development`, `.env.production`.\n\nThe rules are consistent with [Vite Env Variables and Modes](https://vitejs.dev/guide/env-and-mode.html). The format is as follows:\n\n```bash\n.env                # Loaded in all environments\n.env.local          # Loaded in all environments, but ignored by git\n.env.[mode]         # Only loaded in the specified mode\n.env.[mode].local   # Only loaded in the specified mode, but ignored by git\n```\n\n::: tip\n\n- Only variables starting with `VITE_` will be embedded into the client-side package. You can access them in the project code like this:\n\n  ```ts\n  console.log(import.meta.env.VITE_PROT);\n  ```\n\n- Variables starting with `VITE_GLOB_*` will be added to the `_app.config.js` configuration file during packaging.\n\n:::\n\n## Environment Configuration Description\n\n::: code-group\n\n```bash [.env]\n# Application title\nVITE_APP_TITLE=Vben Admin\n\n# Application namespace, used as a prefix for caching, store, etc., to ensure isolation\nVITE_APP_NAMESPACE=vben-web-antd\n```\n\n```bash [.env.development]\n# Port Number\nVITE_PORT=5555\n\n# Public Path for Resources, must start and end with /\nVITE_BASE=/\n\n# API URL\nVITE_GLOB_API_URL=/api\n\n# Whether to enable Nitro Mock service, true to enable, false to disable\nVITE_NITRO_MOCK=true\n\n# Whether to open devtools, true to open, false to close\nVITE_DEVTOOLS=true\n\n# Whether to inject global loading\nVITE_INJECT_APP_LOADING=true\n\n# Whether to generate after packaging dist.zip\nVITE_ARCHIVER=true\n```\n\n```bash [.env.production]\n# Public Path for Resources, must start and end with /\nVITE_BASE=/\n\n# API URL\nVITE_GLOB_API_URL=https://mock-napi.vben.pro/api\n\n# Whether to enable compression, can be set to none, brotli, gzip\nVITE_COMPRESS=gzip\n\n# Whether to enable PWA\nVITE_PWA=false\n\n# vue-router mode\nVITE_ROUTER_HISTORY=hash\n\n# Whether to inject global loading\nVITE_INJECT_APP_LOADING=true\n\n# Whether to generate dist.zip after packaging\nVITE_ARCHIVER=true\n```\n\n:::\n\n## Dynamic Configuration in Production Environment\n\nWhen 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`.\n\n`_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:\n\n```ts\nwindow._VBEN_ADMIN_PRO_APP_CONF_ = {\n  VITE_GLOB_API_URL: 'https://mock-napi.vben.pro/api',\n};\nObject.freeze(window._VBEN_ADMIN_PRO_APP_CONF_);\nObject.defineProperty(window, '_VBEN_ADMIN_PRO_APP_CONF_', {\n  configurable: false,\n  writable: false,\n});\n```\n\n### Purpose\n\n`_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.\n\n### Usage\n\nTo access the variables inside `_app.config.js`, you need to use the `useAppConfig` method provided by `@vben/hooks`.\n\n```ts\nconst { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n```\n\n### Adding New\n\nTo add a new dynamically modifiable configuration item, simply follow the steps below:\n\n- 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:\n\n  ```bash\n  VITE_GLOB_OTHER_API_URL=https://mock-napi.vben.pro/other-api\n  ```\n\n- In `packages/types/global.d.ts`, add the corresponding type definition, such as:\n\n  ```ts\n  export interface VbenAdminProAppConfigRaw {\n    VITE_GLOB_API_URL: string;\n    VITE_GLOB_OTHER_API_URL: string; // [!code ++]\n  }\n\n  export interface ApplicationConfig {\n    apiURL: string;\n    otherApiURL: string; // [!code ++]\n  }\n  ```\n\n- In `packages/effects/hooks/src/use-app-config.ts`, add the corresponding configuration item, such as:\n\n  ```ts\n  export function useAppConfig(\n    env: Record<string, any>,\n    isProduction: boolean,\n  ): ApplicationConfig {\n    // In production environment, directly use the window._VBEN_ADMIN_PRO_APP_CONF_ global variable\n    const config = isProduction\n      ? window._VBEN_ADMIN_PRO_APP_CONF_\n      : (env as VbenAdminProAppConfigRaw);\n\n    const { VITE_GLOB_API_URL, VITE_GLOB_OTHER_API_URL } = config; // [!code ++]\n\n    return {\n      apiURL: VITE_GLOB_API_URL,\n      otherApiURL: VITE_GLOB_OTHER_API_URL, // [!code ++]\n    };\n  }\n  ```\n\nAt this point, you can use the `useAppConfig` method within the project to access the newly added configuration item.\n\n```ts\nconst { otherApiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n```\n\n::: warning Warning\n\nThe `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.\n\n:::\n\n## Preferences\n\nThe project offers a wide range of preference settings for dynamically configuring various features of the project:\n\n![](/guide/preferences.png)\n\nIf 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.\n\n```ts\nimport { useAppConfig } from '@vben/hooks';\nimport { defineOverridesPreferences } from '@vben/preferences';\n\n/**\n * @description Project configuration file\n * 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\n * !!! Please clear the cache after changing the configuration, otherwise it may not take effect\n */\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n});\n```\n\n### Framework default configuration\n\n::: details View the default configuration of the framework\n\n```ts\nconst defaultPreferences: Preferences = {\n  app: {\n    accessMode: 'frontend',\n    authPageLayout: 'panel-right',\n    checkUpdatesInterval: 1,\n    colorGrayMode: false,\n    colorWeakMode: false,\n    compact: false,\n    contentCompact: 'wide',\n    contentCompactWidth: 1200,\n    contentPadding: 0,\n    contentPaddingBottom: 0,\n    contentPaddingLeft: 0,\n    contentPaddingRight: 0,\n    contentPaddingTop: 0,\n    defaultAvatar:\n      'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',\n    defaultHomePath: '/analytics',\n    dynamicTitle: true,\n    enableCheckUpdates: true,\n    enablePreferences: true,\n    enableCopyPreferences: true,\n    enableRefreshToken: false,\n    isMobile: false,\n    layout: 'sidebar-nav',\n    locale: 'zh-CN',\n    loginExpiredMode: 'page',\n    name: 'Vben Admin',\n    preferencesButtonPosition: 'auto',\n    watermark: false,\n    zIndex: 200,\n  },\n  breadcrumb: {\n    enable: true,\n    hideOnlyOne: false,\n    showHome: false,\n    showIcon: true,\n    styleType: 'normal',\n  },\n  copyright: {\n    companyName: 'Vben',\n    companySiteLink: 'https://www.vben.pro',\n    date: '2024',\n    enable: true,\n    icp: '',\n    icpLink: '',\n    settingShow: true,\n  },\n  footer: {\n    enable: false,\n    fixed: false,\n    height: 32,\n  },\n  header: {\n    enable: true,\n    height: 50,\n    hidden: false,\n    menuAlign: 'start',\n    mode: 'fixed',\n  },\n  logo: {\n    enable: true,\n    fit: 'contain',\n    source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',\n    // sourceDark: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-dark.webp', // Optional: Dark theme logo\n  },\n  navigation: {\n    accordion: true,\n    split: true,\n    styleType: 'rounded',\n  },\n  shortcutKeys: {\n    enable: true,\n    globalLockScreen: true,\n    globalLogout: true,\n    globalPreferences: true,\n    globalSearch: true,\n  },\n  sidebar: {\n    autoActivateChild: false,\n    collapsed: false,\n    collapsedButton: true,\n    collapsedShowTitle: false,\n    collapseWidth: 60,\n    enable: true,\n    expandOnHover: true,\n    extraCollapse: false,\n    extraCollapsedWidth: 60,\n    fixedButton: true,\n    hidden: false,\n    mixedWidth: 80,\n    width: 224,\n  },\n  tabbar: {\n    draggable: true,\n    enable: true,\n    height: 38,\n    keepAlive: true,\n    maxCount: 0,\n    middleClickToClose: false,\n    persist: true,\n    showIcon: true,\n    showMaximize: true,\n    showMore: true,\n    styleType: 'chrome',\n    wheelable: true,\n  },\n  theme: {\n    builtinType: 'default',\n    colorDestructive: 'hsl(348 100% 61%)',\n    colorPrimary: 'hsl(212 100% 45%)',\n    colorSuccess: 'hsl(144 57% 58%)',\n    colorWarning: 'hsl(42 84% 61%)',\n    mode: 'dark',\n    radius: '0.5',\n    semiDarkHeader: false,\n    semiDarkSidebar: false,\n  },\n  transition: {\n    enable: true,\n    loading: true,\n    name: 'fade-slide',\n    progress: true,\n  },\n  widget: {\n    fullscreen: true,\n    globalSearch: true,\n    languageToggle: true,\n    lockScreen: true,\n    notification: true,\n    refresh: true,\n    sidebarToggle: true,\n    themeToggle: true,\n  },\n};\n```\n\n:::\n\n::: details View the default configuration type of the framework\n\n```ts\ninterface AppPreferences {\n  /** Permission mode */\n  accessMode: AccessModeType;\n  /** Layout of the login/registration page */\n  authPageLayout: AuthPageLayoutType;\n  /** Interval for checking updates */\n  checkUpdatesInterval: number;\n  /** Whether to enable gray mode */\n  colorGrayMode: boolean;\n  /** Whether to enable color weakness mode */\n  colorWeakMode: boolean;\n  /** Whether to enable compact mode */\n  compact: boolean;\n  /** Whether to enable content compact mode */\n  contentCompact: ContentCompactType;\n  /** Content compact width */\n  contentCompactWidth: number;\n  /** Content padding */\n  contentPadding: number;\n  /** Content bottom padding */\n  contentPaddingBottom: number;\n  /** Content left padding */\n  contentPaddingLeft: number;\n  /** Content right padding */\n  contentPaddingRight: number;\n  /** Content top padding */\n  contentPaddingTop: number;\n  // /** Default application avatar */\n  defaultAvatar: string;\n  /** Default homepage path */\n  defaultHomePath: string;\n  // /** Enable dynamic title */\n  dynamicTitle: boolean;\n  /** Whether to enable update checks */\n  enableCheckUpdates: boolean;\n  /** Whether to display preferences */\n  enablePreferences: boolean;\n  /** Whether to display copy preferences button */\n  enableCopyPreferences: boolean;\n  /**\n   * @zh_CN Whether to enable refreshToken\n   */\n  enableRefreshToken: boolean;\n  /** Whether it's mobile */\n  isMobile: boolean;\n  /** Layout method */\n  layout: LayoutType;\n  /** Supported languages */\n  locale: SupportedLanguagesType;\n  /** Login expiration mode */\n  loginExpiredMode: LoginExpiredModeType;\n  /** Application name */\n  name: string;\n  /** Position of the preferences button */\n  preferencesButtonPosition: PreferencesButtonPositionType;\n  /**\n   * @zh_CN Whether to enable watermark\n   */\n  watermark: boolean;\n  /** z-index */\n  zIndex: number;\n}\ninterface BreadcrumbPreferences {\n  /** Whether breadcrumbs are enabled */\n  enable: boolean;\n  /** Whether to hide breadcrumbs when there is only one */\n  hideOnlyOne: boolean;\n  /** Whether the home icon in breadcrumbs is visible */\n  showHome: boolean;\n  /** Whether the icon in breadcrumbs is visible */\n  showIcon: boolean;\n  /** Breadcrumb style */\n  styleType: BreadcrumbStyleType;\n}\n\ninterface CopyrightPreferences {\n  /** Copyright company name */\n  companyName: string;\n  /** Link to the copyright company's site */\n  companySiteLink: string;\n  /** Copyright date */\n  date: string;\n  /** Whether copyright is visible */\n  enable: boolean;\n  /** ICP number */\n  icp: string;\n  /** Link to the ICP */\n  icpLink: string;\n  /** Whether to show in settings panel */\n  settingShow?: boolean;\n}\n\ninterface FooterPreferences {\n  /** Whether the footer is visible */\n  enable: boolean;\n  /** Whether the footer is fixed */\n  fixed: boolean;\n  /** Footer height */\n  height: number;\n}\n\ninterface HeaderPreferences {\n  /** Whether the header is enabled */\n  enable: boolean;\n  /** Header height */\n  height: number;\n  /** Whether the header is hidden, css-hidden */\n  hidden: boolean;\n  /** Header menu alignment */\n  menuAlign: LayoutHeaderMenuAlignType;\n  /** Header display mode */\n  mode: LayoutHeaderModeType;\n}\n\ninterface LogoPreferences {\n  /** Whether the logo is visible */\n  enable: boolean;\n  /** Logo image fitting method */\n  fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n  /** Logo URL */\n  source: string;\n  /** Dark theme logo URL (optional, if not set, use source) */\n  sourceDark?: string;\n}\n\ninterface NavigationPreferences {\n  /** Navigation menu accordion mode */\n  accordion: boolean;\n  /** Whether the navigation menu is split, only effective in layout=mixed-nav */\n  split: boolean;\n  /** Navigation menu style */\n  styleType: NavigationStyleType;\n}\ninterface SidebarPreferences {\n  /** Automatically activate child menu when clicking on directory */\n  autoActivateChild: boolean;\n  /** Whether the sidebar is collapsed */\n  collapsed: boolean;\n  /** Whether the sidebar collapse button is visible */\n  collapsedButton: boolean;\n  /** Whether to show title when sidebar is collapsed */\n  collapsedShowTitle: boolean;\n  /** Sidebar collapse width */\n  collapseWidth: number;\n  /** Whether the sidebar is visible */\n  enable: boolean;\n  /** Menu auto-expand state */\n  expandOnHover: boolean;\n  /** Whether the sidebar extension area is collapsed */\n  extraCollapse: boolean;\n  /** Sidebar extension area collapse width */\n  extraCollapsedWidth: number;\n  /** Whether the sidebar fixed button is visible */\n  fixedButton: boolean;\n  /** Whether the sidebar is hidden - css */\n  hidden: boolean;\n  /** Mixed sidebar width */\n  mixedWidth: number;\n  /** Sidebar width */\n  width: number;\n}\n\ninterface ShortcutKeyPreferences {\n  /** Whether shortcut keys are enabled globally */\n  enable: boolean;\n  /** Whether the global lock screen shortcut is enabled */\n  globalLockScreen: boolean;\n  /** Whether the global logout shortcut is enabled */\n  globalLogout: boolean;\n  /** Whether the global preferences shortcut is enabled */\n  globalPreferences: boolean;\n  /** Whether the global search shortcut is enabled */\n  globalSearch: boolean;\n}\n\ninterface TabbarPreferences {\n  /** Whether dragging of multiple tabs is enabled */\n  draggable: boolean;\n  /** Whether multiple tabs are enabled */\n  enable: boolean;\n  /** Tab height */\n  height: number;\n  /** Whether tab caching is enabled */\n  keepAlive: boolean;\n  /** Maximum number of tabs */\n  maxCount: number;\n  /** Whether to close tab when middle-clicked */\n  middleClickToClose: boolean;\n  /** Whether tabs are persistent */\n  persist: boolean;\n  /** Whether icons in multiple tabs are enabled */\n  showIcon: boolean;\n  /** Whether to show the maximize button */\n  showMaximize: boolean;\n  /** Whether to show the more button */\n  showMore: boolean;\n  /** Tab style */\n  styleType: TabsStyleType;\n  /** Whether mouse wheel response is enabled */\n  wheelable: boolean;\n}\ninterface ThemePreferences {\n  /** Built-in theme name */\n  builtinType: BuiltinThemeType;\n  /** Destructive color */\n  colorDestructive: string;\n  /** Primary color */\n  colorPrimary: string;\n  /** Success color */\n  colorSuccess: string;\n  /** Warning color */\n  colorWarning: string;\n  /** Current theme */\n  mode: ThemeModeType;\n  /** Radius */\n  radius: string;\n  /** Whether to enable semi-dark header (only effective when theme='light') */\n  semiDarkHeader: boolean;\n  /** Whether to enable semi-dark sidebar (only effective when theme='light') */\n  semiDarkSidebar: boolean;\n}\n\ninterface TransitionPreferences {\n  /** Whether page transition animations are enabled */\n  enable: boolean;\n  // /** Whether page loading loading is enabled */\n  loading: boolean;\n  /** Page transition animation */\n  name: PageTransitionType | string;\n  /** Whether page loading progress animation is enabled */\n  progress: boolean;\n}\n\ninterface WidgetPreferences {\n  /** Whether fullscreen widgets are enabled */\n  fullscreen: boolean;\n  /** Whether global search widget is enabled */\n  globalSearch: boolean;\n  /** Whether language switch widget is enabled */\n  languageToggle: boolean;\n  /** Whether lock screen functionality is enabled */\n  lockScreen: boolean;\n  /** Whether notification widget is displayed */\n  notification: boolean;\n  /** Whether to show the refresh button */\n  refresh: boolean;\n  /** Whether sidebar show/hide widget is displayed */\n  sidebarToggle: boolean;\n  /** Whether theme switch widget is displayed */\n  themeToggle: boolean;\n}\ninterface Preferences {\n  /** Global configuration */\n  app: AppPreferences;\n  /** Header configuration */\n  breadcrumb: BreadcrumbPreferences;\n  /** Copyright configuration */\n  copyright: CopyrightPreferences;\n  /** Footer configuration */\n  footer: FooterPreferences;\n  /** Breadcrumb configuration */\n  header: HeaderPreferences;\n  /** Logo configuration */\n  logo: LogoPreferences;\n  /** Navigation configuration */\n  navigation: NavigationPreferences;\n  /** Shortcut key configuration */\n  shortcutKeys: ShortcutKeyPreferences;\n  /** Sidebar configuration */\n  sidebar: SidebarPreferences;\n  /** Tab bar configuration */\n  tabbar: TabbarPreferences;\n  /** Theme configuration */\n  theme: ThemePreferences;\n  /** Animation configuration */\n  transition: TransitionPreferences;\n  /** Widget configuration */\n  widget: WidgetPreferences;\n}\n```\n\n:::\n\n::: warning Warning\n\n- 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.\n- Any configuration item can be overridden. You just need to override it within the `overridesPreferences` method. Do not modify the default configuration file.\n- Please clear the cache after changing the configuration, otherwise it may not take effect.\n\n:::\n"
  },
  {
    "path": "docs/src/en/guide/essentials/styles.md",
    "content": "# Styles\n\n::: tip Preface\n\nFor 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.\n\n:::\n\n## Project Structure\n\nThe 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.\n\n## Scss\n\nThe project uses `scss` as the style preprocessor, allowing the use of `scss` features such as variables, functions, mixins, etc., within the project.\n\n```vue\n<style lang=\"scss\" scoped>\n$font-size: 30px;\n\n.box {\n  .title {\n    color: green;\n    font-size: $font-size;\n  }\n}\n</style>\n```\n\n## Postcss\n\nIf 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`.\n\n```vue\n<style scoped>\n.box {\n  --font-size: 30px;\n  .title {\n    color: green;\n    font-size: var(--font-size);\n  }\n}\n</style>\n```\n\n## Tailwind CSS\n\nThe project integrates [Tailwind CSS](https://tailwindcss.com/), allowing the use of `tailwindcss` class names to quickly build pages.\n\n```vue\n<template>\n  <div class=\"bg-white p-4\">\n    <p class=\"text-green\">hello world</p>\n  </div>\n</template>\n```\n\n## BEM Standard\n\nAnother 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.\n\n```vue\n<script lang=\"ts\" setup>\nimport { useNamespace } from '@vben/hooks';\n\nconst { b, e, is } = useNamespace('menu');\n</script>\n<template>\n  <div :class=\"[b()]\">\n    <div :class=\"[e('item'), is('active', true)]\">item1</div>\n  </div>\n</template>\n<style lang=\"scss\" scoped>\n// If you use it within the application, this line of code can be omitted as it has already been globally introduced in all applications\n@use '@vben/styles/global' as *;\n@include b('menu') {\n  color: black;\n\n  @include e('item') {\n    background-color: black;\n\n    @include is('active') {\n      background-color: red;\n    }\n  }\n}\n</style>\n```\n\n## CSS Modules\n\nAnother solution to address style conflicts is to use the `CSS Modules` modular approach. The usage method is as follows.\n\n```vue\n<template>\n  <p :class=\"$style.red\">This should be red</p>\n</template>\n\n<style module>\n.red {\n  color: red;\n}\n</style>\n```\n\nFor more usage, see the [CSS Modules official documentation](https://vuejs.org/api/sfc-css-features.html#css-modules).\n"
  },
  {
    "path": "docs/src/en/guide/in-depth/access.md",
    "content": "---\noutline: deep\n---\n\n# Access Control\n\nThe framework has built-in three types of access control methods:\n\n- Determining whether a menu or button can be accessed based on user roles\n- Determining whether a menu or button can be accessed through an API\n- Mixed mode: Using both frontend and backend access control simultaneously\n\n## Frontend Access Control\n\n**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.\n\n**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.\n\n### Steps\n\n- Ensure the current mode is set to frontend access control\n\nAdjust `preferences.ts` in the corresponding application directory to ensure `accessMode='frontend'`.\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    // Default value, optional\n    accessMode: 'frontend',\n  },\n});\n```\n\n- Configure route permissions\n\n#### If not configured, it is visible by default\n\n```ts {3}\n {\n    meta: {\n      authority: ['super'],\n    },\n},\n```\n\n- Ensure the roles returned by the interface match the permissions in the route table\n\nYou can look under `src/store/auth` in the application to find the following code:\n\n```ts\n// Set the login user information, ensuring that userInfo.roles is an array and contains permissions from the route table\n// For example: userInfo.roles=['super', 'admin']\nauthStore.setUserInfo(userInfo);\n```\n\nAt 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.\n\n### Menu Visible but Access Forbidden\n\nSometimes, 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.\n\n```ts\n{\n    meta: {\n      menuVisibleWithForbidden: true,\n    },\n},\n```\n\n## Backend Access Control\n\n**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.\n\n**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.\n\n### Steps\n\n- Ensure the current mode is set to backend access control\n\nAdjust `preferences.ts` in the corresponding application directory to ensure `accessMode='backend'`.\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    accessMode: 'backend',\n  },\n});\n```\n\n- Ensure the structure of the menu data returned by the interface is correct\n\nYou can look under `src/router/access.ts` in the application to find the following code:\n\n```ts\nasync function generateAccess(options: GenerateMenuAndRoutesOptions) {\n  return await generateAccessible(preferences.app.accessMode, {\n    fetchMenuListAsync: async () => {\n      // This interface is for the menu data returned by the backend\n      return await getAllMenus();\n    },\n  });\n}\n```\n\n- Interface returns menu data, see comments for explanation\n\n::: details Example of Interface Returning Menu Data\n\n```ts\nconst dashboardMenus = [\n  {\n    // Here, 'BasicLayout' is hardcoded and cannot be changed\n    component: 'BasicLayout',\n    meta: {\n      order: -1,\n      title: 'page.dashboard.title',\n    },\n    name: 'Dashboard',\n    path: '/',\n    redirect: '/analytics',\n    children: [\n      {\n        name: 'Analytics',\n        path: '/analytics',\n        // Here is the path of the page, need to remove 'views/' and '.vue'\n        component: '/dashboard/analytics/index',\n        meta: {\n          affixTab: true,\n          title: 'page.dashboard.analytics',\n        },\n      },\n      {\n        name: 'Workspace',\n        path: '/workspace',\n        component: '/dashboard/workspace/index',\n        meta: {\n          title: 'page.dashboard.workspace',\n        },\n      },\n    ],\n  },\n];\n```\n\n:::\n\nAt 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.\n\n## Mixed Access Control\n\n**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.\n\n**Advantages**: Combines the performance advantages of frontend control with the flexibility of backend control, suitable for complex business scenarios requiring permission management.\n\n### Steps\n\n- Ensure the current mode is set to mixed access control\n\nAdjust `preferences.ts` in the corresponding application directory to ensure `accessMode='mixed'`.\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    accessMode: 'mixed',\n  },\n});\n```\n\n- Configure frontend route permissions\n\nSame as the route permission configuration method in [Frontend Access Control](#frontend-access-control) mode.\n\n- Configure backend menu interface\n\nSame as the interface configuration method in [Backend Access Control](#backend-access-control) mode.\n\n- Ensure roles and permissions match\n\nMust satisfy both frontend route permission configuration and backend menu data return requirements, ensuring user roles match the permission configurations of both modes.\n\nAt this point, the configuration is complete. Mixed mode will automatically merge frontend and backend routes, providing complete access control functionality.\n\n## Fine-grained Control of Buttons\n\nIn some cases, we need to control the display of buttons with fine granularity. We can control the display of buttons through interfaces or roles.\n\n### Permission Code\n\nThe permission code is the code returned by the interface. The logic to determine whether a button is displayed is located under `src/store/auth`:\n\n```ts\nconst [fetchUserInfoResult, accessCodes] = await Promise.all([\n  fetchUserInfo(),\n  getAccessCodes(),\n]);\n\nuserInfo = fetchUserInfoResult;\nauthStore.setUserInfo(userInfo);\naccessStore.setAccessCodes(accessCodes);\n```\n\nLocate the `getAccessCodes` corresponding interface, which can be adjusted according to business logic.\n\nThe data structure returned by the permission code is an array of strings, for example: `['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010']`\n\nWith the permission codes, you can use the `AccessControl` component and API provided by `@vben/access` to show and hide buttons.\n\n#### Component Method\n\n```vue\n<script lang=\"ts\" setup>\nimport { AccessControl, useAccess } from '@vben/access';\n\nconst { accessMode, hasAccessByCodes } = useAccess();\n</script>\n\n<template>\n  <!-- You need to specify type=\"code\" -->\n  <AccessControl :codes=\"['AC_100100']\" type=\"code\">\n    <Button> Visible to Super account [\"AC_1000001\"] </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['AC_100030']\" type=\"code\">\n    <Button> Visible to Admin account [\"AC_100010\"] </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['AC_1000001']\" type=\"code\">\n    <Button> Visible to User account [\"AC_1000001\"] </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['AC_100100', 'AC_100010']\" type=\"code\">\n    <Button>\n      Visible to Super & Admin account [\"AC_100100\",\"AC_1000001\"]\n    </Button>\n  </AccessControl>\n</template>\n```\n\n#### API Method\n\n```vue\n<script lang=\"ts\" setup>\nimport { AccessControl, useAccess } from '@vben/access';\n\nconst { hasAccessByCodes } = useAccess();\n</script>\n\n<template>\n  <Button v-if=\"hasAccessByCodes(['AC_100100'])\">\n    Visible to Super account [\"AC_1000001\"]\n  </Button>\n  <Button v-if=\"hasAccessByCodes(['AC_100030'])\">\n    Visible to Admin account [\"AC_100010\"]\n  </Button>\n  <Button v-if=\"hasAccessByCodes(['AC_1000001'])\">\n    Visible to User account [\"AC_1000001\"]\n  </Button>\n  <Button v-if=\"hasAccessByCodes(['AC_100100', 'AC_1000001'])\">\n    Visible to Super & Admin account [\"AC_100100\",\"AC_1000001\"]\n  </Button>\n</template>\n```\n\n#### Directive Method\n\n> 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.\n\n```vue\n<template>\n  <Button class=\"mr-4\" v-access:code=\"'AC_100100'\">\n    Visible to Super account 'AC_100100'\n  </Button>\n  <Button class=\"mr-4\" v-access:code=\"['AC_100030']\">\n    Visible to Admin account [\"AC_100010\"]\n  </Button>\n  <Button class=\"mr-4\" v-access:code=\"['AC_1000001']\">\n    Visible to User account [\"AC_1000001\"]\n  </Button>\n  <Button class=\"mr-4\" v-access:code=\"['AC_100100', 'AC_1000001']\">\n    Visible to Super & Admin account [\"AC_100100\",\"AC_1000001\"]\n  </Button>\n</template>\n```\n\n### Roles\n\nThe method of determining roles does not require permission codes returned by the interface; it directly determines whether buttons are displayed based on roles.\n\n#### Component Method\n\n```vue\n<script lang=\"ts\" setup>\nimport { AccessControl } from '@vben/access';\n</script>\n\n<template>\n  <AccessControl :codes=\"['super']\">\n    <Button> Visible to Super account </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['admin']\">\n    <Button> Visible to Admin account </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['user']\">\n    <Button> Visible to User account </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['super', 'admin']\">\n    <Button> Super & Visible to Admin account </Button>\n  </AccessControl>\n</template>\n```\n\n#### API Method\n\n```vue\n<script lang=\"ts\" setup>\nimport { useAccess } from '@vben/access';\n\nconst { hasAccessByRoles } = useAccess();\n</script>\n\n<template>\n  <Button v-if=\"hasAccessByRoles(['super'])\"> Visible to Super account </Button>\n  <Button v-if=\"hasAccessByRoles(['admin'])\"> Visible to Admin account </Button>\n  <Button v-if=\"hasAccessByRoles(['user'])\"> Visible to User account </Button>\n  <Button v-if=\"hasAccessByRoles(['super', 'admin'])\">\n    Super & Visible to Admin account\n  </Button>\n</template>\n```\n\n#### Directive Method\n\n> 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.\n\n```vue\n<template>\n  <Button class=\"mr-4\" v-access:role=\"'super'\">\n    Visible to Super account\n  </Button>\n  <Button class=\"mr-4\" v-access:role=\"['admin']\">\n    Visible to Admin account\n  </Button>\n  <Button class=\"mr-4\" v-access:role=\"['user']\">\n    Visible to User account\n  </Button>\n  <Button class=\"mr-4\" v-access:role=\"['super', 'admin']\">\n    Super & Visible to Admin account\n  </Button>\n</template>\n```\n"
  },
  {
    "path": "docs/src/en/guide/in-depth/check-updates.md",
    "content": "# Check Updates\n\n## Introduction\n\nWhen 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).\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    // Whether to enable check for updates\n    enableCheckUpdates: true,\n    // The interval for checking updates, in minutes\n    checkUpdatesInterval: 1,\n  },\n});\n```\n\n## Effect\n\nWhen an update is detected, a prompt will pop up asking the user whether to refresh the page:\n\n![check-updates](/guide/update-notice.png)\n\n## Replacing with Other Update Checking Methods\n\nIf 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`.\n\n```ts\n// Replace this with your update checking logic\nasync function getVersionTag() {\n  try {\n    const response = await fetch('/', {\n      cache: 'no-cache',\n      method: 'HEAD',\n    });\n\n    return (\n      response.headers.get('etag') || response.headers.get('last-modified')\n    );\n  } catch {\n    console.error('Failed to fetch version tag');\n    return null;\n  }\n}\n```\n"
  },
  {
    "path": "docs/src/en/guide/in-depth/features.md",
    "content": "# Common Features\n\nA collection of some commonly used features.\n\n## Login Authentication Expiry\n\nWhen 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:\n\n### Redirect to Login Page\n\nUpon login timeout, it will redirect to the login page.\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    loginExpiredMode: 'page',\n  },\n});\n```\n\n### Open Login Popup\n\nWhen login times out, a login popup will open.\n\n![login-expired](/guide/login-expired.png)\n\nConfiguration:\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    loginExpiredMode: 'modal',\n  },\n});\n```\n\n## Dynamic Title\n\n- Default value: `true`\n\nWhen 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.\n\n```ts\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    dynamicTitle: true,\n  },\n});\n```\n\n## Page Watermark\n\n- Default value: `false`\n\nWhen enabled, the webpage will display a watermark. You can enable or disable this in the `preferences.ts` file in your application directory.\n\n```ts\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    watermark: true,\n  },\n});\n```\n\nIf 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/):\n\n```ts\nimport { useWatermark } from '@vben/hooks';\n\nconst { destroyWatermark, updateWatermark } = useWatermark();\n\nawait updateWatermark({\n  // watermark content\n  content: 'hello my watermark',\n});\n```\n"
  },
  {
    "path": "docs/src/en/guide/in-depth/layout.md",
    "content": "# Layout\n"
  },
  {
    "path": "docs/src/en/guide/in-depth/loading.md",
    "content": "# Global Loading\n\nGlobal loading refers to the loading effect that appears when the page is refreshed, usually a spinning icon:\n\n![Global loading spinner](/guide/loading.png)\n\n## Principle\n\nImplemented by the `vite-plugin-inject-app-loading` plugin, the plugin injects a global `loading html` into each application.\n\n## Disable\n\nIf you do not need global loading, you can disable it in the `.env` file:\n\n```bash\nVITE_INJECT_APP_LOADING=false\n```\n\n## Customization\n\nIf 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.\n\n::: tip\n\n- You can use the same syntax as in `index.html`, such as the `VITE_APP_TITLE` variable, to get the application's title.\n- You must ensure there is an element with `id=\"__app-loading__\"`.\n- Add a `hidden` class to the element with `id=\"__app-loading__\"`.\n- You must ensure there is a `style[data-app-loading=\"inject-css\"]` element.\n\n```html{1,4}\n<style data-app-loading=\"inject-css\">\n  #__app-loading__.hidden {\n    pointer-events: none;\n    visibility: hidden;\n    opacity: 0;\n    transition: all 1s ease-out;\n  }\n  /* ... */\n</style>\n<div id=\"__app-loading__\">\n  <!-- ... -->\n  <div class=\"title\"><%= VITE_APP_TITLE %></div>\n</div>\n```\n"
  },
  {
    "path": "docs/src/en/guide/in-depth/locale.md",
    "content": "# Internationalization\n\nThe project has integrated [Vue i18n](https://kazupon.github.io/vue-i18n/), and Chinese and English language packs have been configured.\n\n## IDE Plugin\n\nIf 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:\n\n![](/public/guide/locale.png)\n\n## Configure Default Language\n\nYou just need to override the default preferences. In the corresponding application, find the `src/preferences.ts` file and modify the value of `locale`:\n\n```ts {3}\nexport const overridesPreferences = defineOverridesPreferences({\n  app: {\n    locale: 'en-US',\n  },\n});\n```\n\n## Dynamic Language Switching\n\nSwitching languages consists of two parts:\n\n- Updating preferences\n- Loading the corresponding language pack\n\n```ts\nimport type { SupportedLanguagesType } from '@vben/locales';\nimport { loadLocaleMessages } from '@vben/locales';\nimport { updatePreferences } from '@vben/preferences';\n\nasync function updateLocale(value: string) {\n  // 1. Update preferences\n  const locale = value as SupportedLanguagesType;\n  updatePreferences({\n    app: {\n      locale,\n    },\n  });\n  // 2. Load the corresponding language pack\n  await loadLocaleMessages(locale);\n}\n\nupdateLocale('en-US');\n```\n\n## Adding Translation Texts\n\n::: warning Attention\n\n- Do not place business translation texts inside `@vben/locales` to better manage business and general translation texts.\n- When adding new translation texts and multiple language packs are available, ensure to add the corresponding texts in all language packs.\n\n:::\n\nTo add new translation texts, simply find `src/locales/langs/` in the corresponding application and add the texts accordingly, for example:\n\n**src/locales/langs/zh-CN/\\*.json**\n\n````ts\n```json\n{\n  \"about\": {\n    \"desc\": \"Vben Admin 是一个现代的管理模版。\"\n  }\n}\n````\n\n**src/locales/langs/en-US.ts**\n\n````ts\n```json\n{\n  \"about\": {\n    \"desc\": \"Vben Admin is a modern management template.\"\n  }\n}\n````\n\n## Using Translation Texts\n\nWith `@vben/locales`, you can easily use translation texts:\n\n### In Code\n\n```vue\n<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport { $t } from '@vben/locales';\n\nconst items = computed(() => [{ title: $t('about.desc') }]);\n</script>\n<template>\n  <div>{{ $t('about.desc') }}</div>\n  <template v-for=\"item in items.value\">\n    <div>{{ item.title }}</div>\n  </template>\n</template>\n```\n\n## Adding a New Language Pack\n\nIf you need to add a new language pack, follow these steps:\n\n- Add the corresponding language pack file in the `packages/locales/langs` directory, for example, `zh-TW.json`, and translate the respective texts.\n- In the corresponding application, locate the `src/locales/langs` file and add the new language pack `zh-TW.json`.\n- Add the corresponding language in `packages/constants/src/core.ts`:\n\n  ```ts\n  export interface LanguageOption {\n    label: string;\n    value: 'en-US' | 'zh-CN'; // [!code --]\n    value: 'en-US' | 'zh-CN' | 'zh-TW'; // [!code ++]\n  }\n  export const SUPPORT_LANGUAGES: LanguageOption[] = [\n    {\n      label: '简体中文',\n      value: 'zh-CN',\n    },\n    {\n      label: 'English',\n      value: 'en-US',\n    },\n    {\n      label: '繁体中文', // [!code ++]\n      value: 'zh-TW', // [!code ++]\n    },\n  ];\n  ```\n\n- In `packages/locales/typing.ts`, add a new TypeScript type:\n\n  ```ts\n  export type SupportedLanguagesType = 'en-US' | 'zh-CN'; // [!code --]\n  export type SupportedLanguagesType = 'en-US' | 'zh-CN' | 'zh-TW'; // [!code ++]\n  ```\n\nAt this point, you can use the newly added language pack in the project.\n\n## Interface Language Switching Function\n\nIf 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:\n\n```ts {3}\nexport const overridesPreferences = defineOverridesPreferences({\n  widget: {\n    languageToggle: false,\n  },\n});\n```\n\n## Remote Loading of Language Packs\n\n::: tip Tip\n\nWhen 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.\n\n:::\n\nEach 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:\n\n```ts {3-4}\nasync function loadMessages(lang: SupportedLanguagesType) {\n  const [appLocaleMessages] = await Promise.all([\n    // Modify here to load data via a remote interface\n    localesMap[lang](),\n    loadThirdPartyMessage(lang),\n  ]);\n  return appLocaleMessages.default;\n}\n```\n\n## Third-Party Language Packs\n\nDifferent 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:\n\n```ts\n/**\n * Load the dayjs language pack\n * @param lang\n */\nasync function loadDayjsLocale(lang: SupportedLanguagesType) {\n  let locale;\n  switch (lang) {\n    case 'zh-CN': {\n      locale = await import('dayjs/locale/zh-cn');\n      break;\n    }\n    case 'en-US': {\n      locale = await import('dayjs/locale/en');\n      break;\n    }\n    // Default to using English\n    default: {\n      locale = await import('dayjs/locale/en');\n    }\n  }\n  if (locale) {\n    dayjs.locale(locale);\n  } else {\n    console.error(`Failed to load dayjs locale for ${lang}`);\n  }\n}\n```\n\n## Removing Internationalization\n\nFirstly, 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:\n\n- Hide the language switching button on the interface, see: [Interface Language Switching Function](#interface-language-switching-function)\n- Modify the default language, see: [Configure Default Language](#configure-default-language)\n- Disable `vue-i18n` warning prompts, in the `src/locales/index.ts` file, modify `missingWarn` to `false`:\n\n  ```ts\n  async function setupI18n(app: App, options: LocaleSetupOptions = {}) {\n    await coreSetup(app, {\n      defaultLocale: preferences.app.locale,\n      loadMessages,\n      missingWarn: !import.meta.env.PROD, // [!code --]\n      missingWarn: false, // [!code ++]\n      ...options,\n    });\n  }\n  ```\n"
  },
  {
    "path": "docs/src/en/guide/in-depth/login.md",
    "content": "# Login\n\nThis document explains how to customize the login page of your application.\n\n## Login Page Adjustment\n\nIf 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.\n\n![login](/guide/login.png)\n\nYou just need to configure the `props` parameter of `AuthPageLayout` in `src/router/routes/core.ts` within your application:\n\n```ts {4-8}\n {\n    component: AuthPageLayout,\n    props: {\n      sloganImage: \"xxx/xxx.png\",\n      pageTitle: \"开箱即用的大型中后台管理系统\",\n      pageDescription: \"工程化、高性能、跨组件库的前端模版\",\n      toolbar: true,\n      toolbarList: ['color', 'language', 'layout', 'theme'],\n    }\n    // ...\n  },\n```\n\n::: tip\n\nIf these configurations do not meet your needs, you can implement your own login page. Simply implement your own `AuthPageLayout`.\n\n:::\n\n## Login Form Adjustment\n\nIf 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:\n\n```vue\n<AuthenticationLogin\n  :loading=\"authStore.loginLoading\"\n  @submit=\"authStore.authLogin\"\n/>\n```\n\n::: details AuthenticationLogin Component Props\n\n```ts\n{\n  /**\n   * @en Verification code login path\n   */\n  codeLoginPath?: string;\n  /**\n   * @en Forget password path\n   */\n  forgetPasswordPath?: string;\n\n  /**\n   * @en Whether it is in loading state\n   */\n  loading?: boolean;\n\n  /**\n   * @en QR code login path\n   */\n  qrCodeLoginPath?: string;\n\n  /**\n   * @en Registration path\n   */\n  registerPath?: string;\n\n  /**\n   * @en Whether to show verification code login\n   */\n  showCodeLogin?: boolean;\n  /**\n   * @en Whether to show forget password\n   */\n  showForgetPassword?: boolean;\n\n  /**\n   * @en Whether to show QR code login\n   */\n  showQrcodeLogin?: boolean;\n\n  /**\n   * @en Whether to show registration button\n   */\n  showRegister?: boolean;\n\n  /**\n   * @en Whether to show remember account\n   */\n  showRememberMe?: boolean;\n\n  /**\n   * @en Whether to show third-party login\n   */\n  showThirdPartyLogin?: boolean;\n\n  /**\n   * @en Login box subtitle\n   */\n  subTitle?: string;\n\n  /**\n   * @en Login box title\n   */\n  title?: string;\n}\n```\n\n:::\n\n::: tip\n\nIf these configurations do not meet your needs, you can implement your own login form and related login logic.\n\n:::\n"
  },
  {
    "path": "docs/src/en/guide/in-depth/theme.md",
    "content": "# Theme\n\nThe 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.\n\n## CSS Variables\n\nThe project follows the theme configuration of [shadcn-vue](https://www.shadcn-vue.com/themes.html), for example:\n\n```html\n<div class=\"bg-background text-foreground\" />\n```\n\nWe 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.\n\nFor the following components, `background` will be `hsl(var(--primary))`, and `foreground` will be `hsl(var(--primary-foreground))`.\n\n## Detailed List of CSS Variables\n\n::: warning Note\n\nThe colors inside CSS variables must use the `hsl` format, such as `0 0% 100%`, without adding `hsl()` and `,`.\n\n:::\n\nYou can check the list below to understand all the available variables.\n\n::: details Default theme CSS variables\n\n```css\n:root {\n  --font-family:\n    -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, 'Helvetica Neue',\n    arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',\n    'Segoe UI Symbol', 'Noto Color Emoji';\n\n  /* Default background color of <body />...etc */\n  --background: 0 0% 100%;\n\n  /* Main area background color */\n  --background-deep: 216 20.11% 95.47%;\n  --foreground: 210 6% 21%;\n\n  /* Background color for <Card /> */\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n\n  /* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n\n  /* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n\n  /* Theme Colors */\n\n  --primary: 212 100% 45%;\n  --primary-foreground: 0 0% 98%;\n\n  /* Used for destructive actions such as <Button variant=\"destructive\"> */\n\n  --destructive: 0 78% 68%;\n  --destructive-foreground: 0 0% 98%;\n\n  /* Used for success actions such as <message> */\n\n  --success: 144 57% 58%;\n  --success-foreground: 0 0% 98%;\n\n  /* Used for warning actions such as <message> */\n\n  --warning: 42 84% 61%;\n  --warning-foreground: 0 0% 98%;\n\n  /* Secondary colors for <Button /> */\n\n  --secondary: 240 5% 96%;\n  --secondary-foreground: 240 6% 10%;\n\n  /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */\n  --accent: 240 5% 96%;\n  --accent-hover: 200deg 10% 90%;\n  --accent-foreground: 240 6% 10%;\n\n  /* Darker color */\n  --heavy: 192deg 9.43% 89.61%;\n  --heavy-foreground: var(--accent-foreground);\n\n  /* Default border color */\n  --border: 240 5.9% 90%;\n\n  /* Border color for inputs such as <Input />, <Select />, <Textarea /> */\n  --input: 240deg 5.88% 90%;\n  --input-placeholder: 217 10.6% 65%;\n  --input-background: 0 0% 100%;\n\n  /* Used for focus ring */\n  --ring: 222.2 84% 4.9%;\n\n  /* Border radius for card, input and buttons */\n  --radius: 0.5rem;\n\n  /* ============= custom ============= */\n\n  /* overlay color */\n  --overlay: 0deg 0% 0% / 30%;\n\n  /* base font size */\n  --font-size-base: 16px;\n\n  /* =============component & UI============= */\n\n  /* menu */\n  --sidebar: 0 0% 100%;\n  --sidebar-deep: 216 20.11% 95.47%;\n  --menu: var(--sidebar);\n\n  /* header */\n  --header: 0 0% 100%;\n\n  accent-color: var(--primary);\n  color-scheme: light;\n}\n```\n\n:::\n\n::: details Default theme dark mode CSS variables\n\n```css\n.dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  /* Default background color of <body />...etc */\n  --background: 222.34deg 10.43% 12.27%;\n\n  /* Main area background color */\n  --background-deep: 220deg 13.06% 9%;\n  --foreground: 0 0% 95%;\n\n  /* Background color for <Card /> */\n  --card: 222.34deg 10.43% 12.27%;\n\n  /* --card: 222.2 84% 4.9%; */\n  --card-foreground: 210 40% 98%;\n\n  /* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */\n  --popover: 222.82deg 8.43% 12.27%;\n  --popover-foreground: 210 40% 98%;\n\n  /* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */\n  --muted: 220deg 6.82% 17.25%;\n  --muted-foreground: 215 20.2% 65.1%;\n\n  /* Theme Colors */\n\n  /* --primary: 245 82% 67%; */\n  --primary-foreground: 0 0% 98%;\n\n  /* Used for destructive actions such as <Button variant=\"destructive\"> */\n\n  --destructive: 0 78% 68%;\n  --destructive-foreground: 0 0% 98%;\n\n  /* Used for success actions such as <message> */\n\n  --success: 144 57% 58%;\n  --success-foreground: 0 0% 98%;\n\n  /* Used for warning actions such as <message> */\n\n  --warning: 42 84% 61%;\n  --warning-foreground: 0 0% 98%;\n\n  /* secondary color */\n  --secondary: 240 5% 17%;\n  --secondary-foreground: 0 0% 98%;\n\n  /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */\n  --accent: 0deg 0% 100% / 8%;\n  --accent-hover: 0deg 0% 100% / 12%;\n  --accent-foreground: 0 0% 98%;\n\n  /* Darker color */\n  --heavy: 0deg 0% 100% / 12%;\n  --heavy-foreground: var(--accent-foreground);\n\n  /* Default border color */\n  --border: 240 3.7% 15.9%;\n\n  /* Border color for inputs such as <Input />, <Select />, <Textarea /> */\n  --input: 0deg 0% 100% / 10%;\n  --input-placeholder: 218deg 11% 65%;\n  --input-background: 0deg 0% 100% / 5%;\n\n  /* Used for focus ring */\n  --ring: 222.2 84% 4.9%;\n\n  /* base radius */\n  --radius: 0.5rem;\n\n  /* ============= Custom ============= */\n\n  /* overlay color */\n  --overlay: 0deg 0% 0% / 40%;\n\n  /* base font size */\n  --font-size-base: 16px;\n\n  /* =============component & UI============= */\n\n  --sidebar: 222.34deg 10.43% 12.27%;\n  --sidebar-deep: 220deg 13.06% 9%;\n  --menu: var(--sidebar);\n  --header: 222.34deg 10.43% 12.27%;\n\n  color-scheme: dark;\n}\n```\n\n:::\n\n## Overriding Default CSS Variables\n\nYou only need to override the CSS variables you want to change in your project. For example, to change the default card background color, you can add the following content to your CSS file to override it:\n\n### Under the Default Theme\n\n```css\n:root {\n  /* Background color for <Card /> */\n  --card: 0 0% 30%;\n}\n```\n\n### In Dark Mode\n\n```css\n.dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  /* Background color for <Card /> */\n  --card: 222.34deg 10.43% 12.27%;\n}\n```\n\n## Changing the Brand Primary Color\n\n::: tip\n\n- You need to use the `hsl` color format.\n- You must clear the cache for the changes to take effect.\n- You can use [third-party tools](https://www.w3schools.com/colors/colors_hsl.asp) to convert colors.\n\n:::\n\nYou only need to customize the primary color in the `preferences.ts` file under the application directory:\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  theme: {\n    // Error color\n    colorDestructive: 'hsl(348 100% 61%)',\n    // Primary color\n    colorPrimary: 'hsl(212 100% 45%)',\n    // Success color\n    colorSuccess: 'hsl(144 57% 58%)',\n    // Warning color\n    colorWarning: 'hsl(42 84% 61%)',\n  },\n});\n```\n\n## Built-in Themes\n\nThe framework includes a variety of built-in themes, which you can configure in the `preferences.ts` file:\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  theme: {\n    builtinType: 'default',\n  },\n});\n```\n\n### Built-in Theme List\n\nThe framework includes 16 built-in themes and also supports custom themes. Theoretically, you can expand the themes without limit.\n\n::: details List of Built-in Theme Types\n\n```ts\ntype BuiltinThemeType =\n  | 'custom'\n  | 'deep-blue'\n  | 'deep-green'\n  | 'default'\n  | 'gray'\n  | 'green'\n  | 'neutral'\n  | 'orange'\n  | 'pink'\n  | 'red'\n  | 'rose'\n  | 'sky-blue'\n  | 'slate'\n  | 'stone'\n  | 'violet'\n  | 'yellow'\n  | 'zinc'\n  | (Record<never, never> & string);\n```\n\n:::\n\n::: details Built-in Theme CSS Variables - Light\n\n```css\n:root {\n  --font-family:\n    -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, 'Helvetica Neue',\n    arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',\n    'Segoe UI Symbol', 'Noto Color Emoji';\n\n  /* Default background color of <body />...etc */\n  --background: 0 0% 100%;\n\n  /* Main area background color */\n  --background-deep: 216 20.11% 95.47%;\n  --foreground: 222 84% 5%;\n\n  /* Background color for <Card /> */\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n\n  /* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n\n  /* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */\n\n  /* --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%; */\n\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n\n  /* Theme Colors */\n\n  --primary: 212 100% 45%;\n  --primary-foreground: 0 0% 98%;\n\n  /* Used for destructive actions such as <Button variant=\"destructive\"> */\n\n  --destructive: 0 78% 68%;\n  --destructive-foreground: 0 0% 98%;\n\n  /* Used for success actions such as <message> */\n\n  --success: 144 57% 58%;\n  --success-foreground: 0 0% 98%;\n\n  /* Used for warning actions such as <message> */\n\n  --warning: 42 84% 61%;\n  --warning-foreground: 0 0% 98%;\n\n  /* Secondary colors for <Button /> */\n\n  --secondary: 240 5% 96%;\n  --secondary-foreground: 240 6% 10%;\n\n  /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */\n  --accent: 240 5% 96%;\n  --accent-hover: 200deg 10% 90%;\n  --accent-foreground: 240 6% 10%;\n\n  /* Darker color */\n  --heavy: 192deg 9.43% 89.61%;\n  --heavy-foreground: var(--accent-foreground);\n\n  /* Default border color */\n  --border: 240 5.9% 90%;\n\n  /* Border color for inputs such as <Input />, <Select />, <Textarea /> */\n  --input: 240deg 5.88% 90%;\n  --input-placeholder: 217 10.6% 65%;\n  --input-background: 0 0% 100%;\n\n  /* Used for focus ring */\n  --ring: 222.2 84% 4.9%;\n\n  /* Border radius for card, input and buttons */\n  --radius: 0.5rem;\n\n  /* ============= custom ============= */\n\n  /* overlay color */\n  --overlay: 0deg 0% 0% / 30%;\n\n  /* base font size */\n  --font-size-base: 16px;\n\n  /* =============component & UI============= */\n\n  /* menu */\n  --sidebar: 0 0% 100%;\n  --sidebar-deep: 0 0% 100%;\n  --menu: var(--sidebar);\n\n  /* header */\n  --header: 0 0% 100%;\n\n  accent-color: var(--primary);\n  color-scheme: light;\n}\n\n[data-theme='violet'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 224 71.4% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 224 71.4% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 224 71.4% 4.1%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 220 14.3% 95.9%;\n  --secondary-foreground: 220.9 39.3% 11%;\n  --muted: 220 14.3% 95.9%;\n  --muted-foreground: 220 8.9% 46.1%;\n  --accent: 220 14.3% 95.9%;\n  --accent-foreground: 220.9 39.3% 11%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 220 13% 91%;\n  --input: 220 13% 91%;\n  --ring: 262.1 83.3% 57.8%;\n}\n\n[data-theme='pink'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 346.8 77.2% 49.8%;\n}\n\n[data-theme='rose'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 346.8 77.2% 49.8%;\n}\n\n[data-theme='sky-blue'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 222.2 84% 4.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n  --primary-foreground: 210 40% 98%;\n  --secondary: 210 40% 96.1%;\n  --secondary-foreground: 222.2 47.4% 11.2%;\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n  --accent: 210 40% 96.1%;\n  --accent-foreground: 222.2 47.4% 11.2%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 214.3 31.8% 91.4%;\n  --input: 214.3 31.8% 91.4%;\n  --ring: 221.2 83.2% 53.3%;\n}\n\n[data-theme='deep-blue'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 222.2 84% 4.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n  --primary-foreground: 210 40% 98%;\n  --secondary: 210 40% 96.1%;\n  --secondary-foreground: 222.2 47.4% 11.2%;\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n  --accent: 210 40% 96.1%;\n  --accent-foreground: 222.2 47.4% 11.2%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 214.3 31.8% 91.4%;\n  --input: 214.3 31.8% 91.4%;\n  --ring: 221.2 83.2% 53.3%;\n}\n\n[data-theme='green'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 142.1 76.2% 36.3%;\n}\n\n[data-theme='deep-green'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 142.1 76.2% 36.3%;\n}\n\n[data-theme='orange'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 20 14.3% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 20 14.3% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 20 14.3% 4.1%;\n  --primary-foreground: 60 9.1% 97.8%;\n  --secondary: 60 4.8% 95.9%;\n  --secondary-foreground: 24 9.8% 10%;\n  --muted: 60 4.8% 95.9%;\n  --muted-foreground: 25 5.3% 44.7%;\n  --accent: 60 4.8% 95.9%;\n  --accent-foreground: 24 9.8% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 20 5.9% 90%;\n  --input: 20 5.9% 90%;\n  --ring: 24.6 95% 53.1%;\n}\n\n[data-theme='yellow'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 20 14.3% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 20 14.3% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 20 14.3% 4.1%;\n  --primary-foreground: 26 83.3% 14.1%;\n  --secondary: 60 4.8% 95.9%;\n  --secondary-foreground: 24 9.8% 10%;\n  --muted: 60 4.8% 95.9%;\n  --muted-foreground: 25 5.3% 44.7%;\n  --accent: 60 4.8% 95.9%;\n  --accent-foreground: 24 9.8% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 20 5.9% 90%;\n  --input: 20 5.9% 90%;\n  --ring: 20 14.3% 4.1%;\n}\n\n[data-theme='zinc'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 0 0% 98%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 240 5.9% 10%;\n}\n\n[data-theme='neutral'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 0 0% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 0 0% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 0 0% 3.9%;\n  --primary-foreground: 0 0% 98%;\n  --secondary: 0 0% 96.1%;\n  --secondary-foreground: 0 0% 9%;\n  --muted: 0 0% 96.1%;\n  --muted-foreground: 0 0% 45.1%;\n  --accent: 0 0% 96.1%;\n  --accent-foreground: 0 0% 9%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 0 0% 89.8%;\n  --input: 0 0% 89.8%;\n  --ring: 0 0% 3.9%;\n}\n\n[data-theme='slate'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 222.2 84% 4.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n  --primary-foreground: 210 40% 98%;\n  --secondary: 210 40% 96.1%;\n  --secondary-foreground: 222.2 47.4% 11.2%;\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n  --accent: 210 40% 96.1%;\n  --accent-foreground: 222.2 47.4% 11.2%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 214.3 31.8% 91.4%;\n  --input: 214.3 31.8% 91.4%;\n  --ring: 222.2 84% 4.9%;\n}\n\n[data-theme='gray'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 224 71.4% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 224 71.4% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 224 71.4% 4.1%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 220 14.3% 95.9%;\n  --secondary-foreground: 220.9 39.3% 11%;\n  --muted: 220 14.3% 95.9%;\n  --muted-foreground: 220 8.9% 46.1%;\n  --accent: 220 14.3% 95.9%;\n  --accent-foreground: 220.9 39.3% 11%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 220 13% 91%;\n  --input: 220 13% 91%;\n  --ring: 224 71.4% 4.1%;\n}\n```\n\n:::\n\n::: details 内置主题css变量 - dark\n\n```css\n.dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  /* Default background color of <body />...etc */\n  --background: 222.34deg 10.43% 12.27%;\n\n  /* 主体区域背景色 */\n  --background-deep: 220deg 13.06% 9%;\n  --foreground: 0 0% 95%;\n\n  /* Background color for <Card /> */\n  --card: 222.34deg 10.43% 12.27%;\n\n  /* --card: 222.2 84% 4.9%; */\n  --card-foreground: 210 40% 98%;\n\n  /* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */\n  --popover: 222.82deg 8.43% 12.27%;\n  --popover-foreground: 210 40% 98%;\n\n  /* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */\n\n  /* --muted: 220deg 6.82% 17.25%; */\n\n  /* --muted-foreground: 215 20.2% 65.1%; */\n\n  --muted: 240 3.7% 15.9%;\n  --muted-foreground: 240 5% 64.9%;\n\n  /* 主题颜色 */\n\n  /* --primary: 245 82% 67%; */\n  --primary-foreground: 0 0% 98%;\n\n  /* Used for destructive actions such as <Button variant=\"destructive\"> */\n\n  --destructive: 0 78% 68%;\n  --destructive-foreground: 0 0% 98%;\n\n  /* Used for success actions such as <message> */\n\n  --success: 144 57% 58%;\n  --success-foreground: 0 0% 98%;\n\n  /* Used for warning actions such as <message> */\n\n  --warning: 42 84% 61%;\n  --warning-foreground: 0 0% 98%;\n\n  /* 颜色次要 */\n  --secondary: 240 5% 17%;\n  --secondary-foreground: 0 0% 98%;\n\n  /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */\n  --accent: 216 5% 19%;\n  --accent-hover: 216 5% 24%;\n  --accent-foreground: 0 0% 98%;\n\n  /* Darker color */\n  --heavy: 216 5% 24%;\n  --heavy-foreground: var(--accent-foreground);\n\n  /* Default border color */\n  --border: 240 3.7% 22%;\n\n  /* Border color for inputs such as <Input />, <Select />, <Textarea /> */\n  --input: 0deg 0% 100% / 10%;\n  --input-placeholder: 218deg 11% 65%;\n  --input-background: 0deg 0% 100% / 5%;\n\n  /* Used for focus ring */\n  --ring: 222.2 84% 4.9%;\n\n  /* 基本圆角大小 */\n  --radius: 0.5rem;\n\n  /* ============= Custom ============= */\n\n  /* overlay color */\n  --overlay: 0deg 0% 0% / 40%;\n\n  /* base font size */\n  --font-size-base: 16px;\n\n  /* =============component & UI============= */\n\n  --sidebar: 222.34deg 10.43% 12.27%;\n  --sidebar-deep: 220deg 13.06% 9%;\n  --menu: var(--sidebar);\n\n  /* header */\n  --header: 222.34deg 10.43% 12.27%;\n\n  color-scheme: dark;\n}\n\n.dark[data-theme='violet'],\n[data-theme='violet'] .dark {\n  --background: 224 71.4% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 210 20% 98%;\n  --card: 224 71.4% 4.1%;\n  --card-foreground: 210 20% 98%;\n  --popover: 224 71.4% 4.1%;\n  --popover-foreground: 210 20% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 215 27.9% 16.9%;\n  --secondary-foreground: 210 20% 98%;\n  --muted: 215 27.9% 16.9%;\n  --muted-foreground: 217.9 10.6% 64.9%;\n  --accent: 215 27.9% 16.9%;\n  --accent-foreground: 210 20% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 215 27.9% 16.9%;\n  --input: 215 27.9% 16.9%;\n  --ring: 263.4 70% 50.4%;\n  --sidebar: 224 71.4% 4.1%;\n  --sidebar-deep: 224 71.4% 4.1%;\n  --header: 224 71.4% 4.1%;\n}\n\n.dark[data-theme='pink'],\n[data-theme='pink'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 95%;\n  --card: 0 0% 9%;\n  --card-foreground: 0 0% 95%;\n  --popover: 0 0% 9%;\n  --popover-foreground: 0 0% 95%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 15%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 85.7% 97.3%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 346.8 77.2% 49.8%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='rose'],\n[data-theme='rose'] .dark {\n  --background: 0 0% 3.9%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 98%;\n  --card: 0 0% 3.9%;\n  --card-foreground: 0 0% 98%;\n  --popover: 0 0% 3.9%;\n  --popover-foreground: 0 0% 98%;\n  --primary-foreground: 0 85.7% 97.3%;\n  --secondary: 0 0% 14.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 14.9%;\n  --muted-foreground: 0 0% 63.9%;\n  --accent: 0 0% 14.9%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 0 0% 14.9%;\n  --input: 0 0% 14.9%;\n  --ring: 0 72.2% 50.6%;\n  --sidebar: 0 0% 3.9%;\n  --sidebar-deep: 0 0% 3.9%;\n  --header: 0 0% 3.9%;\n}\n\n.dark[data-theme='sky-blue'],\n[data-theme='sky-blue'] .dark {\n  --background: 222.2 84% 4.9%;\n  --background-deep: var(--background);\n  --foreground: 210 40% 98%;\n  --card: 222.2 84% 4.9%;\n  --card-foreground: 210 40% 98%;\n  --popover: 222.2 84% 4.9%;\n  --popover-foreground: 210 40% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 217.2 32.6% 17.5%;\n  --secondary-foreground: 210 40% 98%;\n  --muted: 217.2 32.6% 17.5%;\n  --muted-foreground: 215 20.2% 65.1%;\n  --accent: 217.2 32.6% 17.5%;\n  --accent-foreground: 210 40% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 217.2 32.6% 17.5%;\n  --input: 217.2 32.6% 17.5%;\n  --ring: 224.3 76.3% 48%;\n  --sidebar: 222.2 84% 4.9%;\n  --sidebar-deep: 222.2 84% 4.9%;\n  --header: 222.2 84% 4.9%;\n}\n\n.dark[data-theme='deep-blue'],\n[data-theme='deep-blue'] .dark {\n  --background: 222.2 84% 4.9%;\n  --background-deep: var(--background);\n  --foreground: 210 40% 98%;\n  --card: 222.2 84% 4.9%;\n  --card-foreground: 210 40% 98%;\n  --popover: 222.2 84% 4.9%;\n  --popover-foreground: 210 40% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 217.2 32.6% 17.5%;\n  --secondary-foreground: 210 40% 98%;\n  --muted: 217.2 32.6% 17.5%;\n  --muted-foreground: 215 20.2% 65.1%;\n  --accent: 217.2 32.6% 17.5%;\n  --accent-foreground: 210 40% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 217.2 32.6% 17.5%;\n  --input: 217.2 32.6% 17.5%;\n  --ring: 224.3 76.3% 48%;\n  --sidebar: 222.2 84% 4.9%;\n  --sidebar-deep: 222.2 84% 4.9%;\n  --header: 222.2 84% 4.9%;\n}\n\n.dark[data-theme='green'],\n[data-theme='green'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 95%;\n  --card: 24 9.8% 6%;\n  --card-foreground: 0 0% 95%;\n  --popover: 0 0% 9%;\n  --popover-foreground: 0 0% 95%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 15%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 85.7% 97.3%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 142.4 71.8% 29.2%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='deep-green'],\n[data-theme='deep-green'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 95%;\n  --card: 24 9.8% 6%;\n  --card-foreground: 0 0% 95%;\n  --popover: 0 0% 9%;\n  --popover-foreground: 0 0% 95%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 15%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 85.7% 97.3%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 142.4 71.8% 29.2%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='orange'],\n[data-theme='orange'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 60 9.1% 97.8%;\n  --card: 20 14.3% 4.1%;\n  --card-foreground: 60 9.1% 97.8%;\n  --popover: 20 14.3% 4.1%;\n  --popover-foreground: 60 9.1% 97.8%;\n  --primary-foreground: 60 9.1% 97.8%;\n  --secondary: 12 6.5% 15.1%;\n  --secondary-foreground: 60 9.1% 97.8%;\n  --muted: 12 6.5% 15.1%;\n  --muted-foreground: 24 5.4% 63.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 60 9.1% 97.8%;\n  --destructive: 0 72.2% 50.6%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 12 6.5% 15.1%;\n  --input: 12 6.5% 15.1%;\n  --ring: 20.5 90.2% 48.2%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='yellow'],\n[data-theme='yellow'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 60 9.1% 97.8%;\n  --card: 20 14.3% 4.1%;\n  --card-foreground: 60 9.1% 97.8%;\n  --popover: 20 14.3% 4.1%;\n  --popover-foreground: 60 9.1% 97.8%;\n  --primary-foreground: 26 83.3% 14.1%;\n  --secondary: 12 6.5% 15.1%;\n  --secondary-foreground: 60 9.1% 97.8%;\n  --muted: 12 6.5% 15.1%;\n  --muted-foreground: 24 5.4% 63.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 60 9.1% 97.8%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 12 6.5% 15.1%;\n  --input: 12 6.5% 15.1%;\n  --ring: 35.5 91.7% 32.9%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='zinc'],\n[data-theme='zinc'] .dark {\n  --background: 240 10% 3.9%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 98%;\n  --card: 240 10% 3.9%;\n  --card-foreground: 0 0% 98%;\n  --popover: 240 10% 3.9%;\n  --popover-foreground: 0 0% 98%;\n  --primary-foreground: 240 5.9% 10%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 240 3.7% 15.9%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 240 3.7% 15.9%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 240 4.9% 83.9%;\n  --sidebar: 240 10% 3.9%;\n  --sidebar-deep: 240 10% 3.9%;\n  --header: 240 4.9% 83.9%;\n}\n\n.dark[data-theme='neutral'],\n[data-theme='neutral'] .dark {\n  --background: 0 0% 3.9%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 98%;\n  --card: 0 0% 3.9%;\n  --card-foreground: 0 0% 98%;\n  --popover: 0 0% 3.9%;\n  --popover-foreground: 0 0% 98%;\n  --primary-foreground: 0 0% 9%;\n  --secondary: 0 0% 14.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 14.9%;\n  --muted-foreground: 0 0% 63.9%;\n  --accent: 0 0% 14.9%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 0 0% 14.9%;\n  --input: 0 0% 14.9%;\n  --ring: 0 0% 83.1%;\n  --sidebar: 0 0% 3.9%;\n  --sidebar-deep: 0 0% 3.9%;\n  --header: 0 0% 3.9%;\n}\n\n.dark[data-theme='slate'],\n[data-theme='slate'] .dark {\n  --background: 222.2 84% 4.9%;\n  --background-deep: var(--background);\n  --foreground: 210 40% 98%;\n  --card: 222.2 84% 4.9%;\n  --card-foreground: 210 40% 98%;\n  --popover: 222.2 84% 4.9%;\n  --popover-foreground: 210 40% 98%;\n  --primary-foreground: 222.2 47.4% 11.2%;\n  --secondary: 217.2 32.6% 17.5%;\n  --secondary-foreground: 210 40% 98%;\n  --muted: 217.2 32.6% 17.5%;\n  --muted-foreground: 215 20.2% 65.1%;\n  --accent: 217.2 32.6% 17.5%;\n  --accent-foreground: 210 40% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 217.2 32.6% 17.5%;\n  --input: 217.2 32.6% 17.5%;\n  --ring: 212.7 26.8% 83.9;\n  --sidebar: 222.2 84% 4.9%;\n  --sidebar-deep: 222.2 84% 4.9%;\n  --header: 222.2 84% 4.9%;\n}\n\n.dark[data-theme='gray'],\n[data-theme='gray'] .dark {\n  --background: 224 71.4% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 210 20% 98%;\n  --card: 224 71.4% 4.1%;\n  --card-foreground: 210 20% 98%;\n  --popover: 224 71.4% 4.1%;\n  --popover-foreground: 210 20% 98%;\n  --primary-foreground: 220.9 39.3% 11%;\n  --secondary: 215 27.9% 16.9%;\n  --secondary-foreground: 210 20% 98%;\n  --muted: 215 27.9% 16.9%;\n  --muted-foreground: 217.9 10.6% 64.9%;\n  --accent: 215 27.9% 16.9%;\n  --accent-foreground: 210 20% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 215 27.9% 16.9%;\n  --input: 215 27.9% 16.9%;\n  --ring: 216 12.2% 83.9%;\n  --sidebar: 224 71.4% 4.1%;\n  --sidebar-deep: 224 71.4% 4.1%;\n  --header: 224 71.4% 4.1%;\n}\n```\n\n:::\n\n## Adding a New Theme\n\nTo add a new theme, simply follow these steps:\n\n- Add a new theme configuration in the application's `src/preferences.ts`.\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  theme: {\n    builtinType: 'my-theme',\n  },\n});\n```\n\n- Add the theme's CSS variables to your CSS file.\n\n```css\n/* light */\n[data-theme='my-theme'] {\n  --foreground: 224 71.4% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 224 71.4% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 224 71.4% 4.1%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 220 14.3% 95.9%;\n  --secondary-foreground: 220.9 39.3% 11%;\n  --muted: 220 14.3% 95.9%;\n  --muted-foreground: 220 8.9% 46.1%;\n  --accent: 220 14.3% 95.9%;\n  --accent-foreground: 220.9 39.3% 11%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 220 13% 91%;\n  --input: 220 13% 91%;\n  --ring: 262.1 83.3% 57.8%;\n}\n\n/* dark */\n.dark[data-theme='my-theme'],\n[data-theme='my-theme'] .dark {\n  --background: 224 71.4% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 210 20% 98%;\n  --card: 224 71.4% 4.1%;\n  --card-foreground: 210 20% 98%;\n  --popover: 224 71.4% 4.1%;\n  --popover-foreground: 210 20% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 215 27.9% 16.9%;\n  --secondary-foreground: 210 20% 98%;\n  --muted: 215 27.9% 16.9%;\n  --muted-foreground: 217.9 10.6% 64.9%;\n  --accent: 215 27.9% 16.9%;\n  --accent-foreground: 210 20% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 215 27.9% 16.9%;\n  --input: 215 27.9% 16.9%;\n  --ring: 263.4 70% 50.4%;\n  --sidebar: 224 71.4% 4.1%;\n  --sidebar-deep: 224 71.4% 4.1%;\n}\n```\n\n## Dark Mode\n\nThe framework includes a variety of built-in themes, which you can configure in `preferences.ts`. The dark theme also uses CSS variables for configuration:\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  theme: {\n    mode: 'dark',\n  },\n});\n```\n\n## Customizing Sidebar Color\n\nThe sidebar color is configured through the `--sidebar` variable.\n\n### Under the Default Theme\n\n```css\n:root {\n  --sidebar: 0 0% 100%;\n}\n```\n\n### In Dark Mode\n\n```css\n.dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  --sidebar: 222.34deg 10.43% 12.27%;\n}\n```\n\n## Customizing Header Color\n\nThe header color is configured through the `--header` variable.\n\n### Under the Default Theme\n\n```css\n:root {\n  --header: 0 0% 100%;\n}\n```\n\n### In Dark Mode\n\n```css\n.dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  --header: 222.34deg 10.43% 12.27%;\n}\n```\n\n## Color Weakness Mode\n\nTypically used in special scenarios, you can set the application to color weakness mode. This can be configured in `preferences.ts`:\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    colorWeakMode: true,\n  },\n});\n```\n\n## Gray Mode\n\nTypically used in special scenarios, this mode grays out the webpage. You can configure it in `preferences.ts`:\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    colorGrayMode: true,\n  },\n});\n```\n"
  },
  {
    "path": "docs/src/en/guide/in-depth/ui-framework.md",
    "content": "# UI Framework Switching\n\n`Vue Admin` supports your freedom to choose the UI framework. The default UI framework for the demo site is `Ant Design Vue`, consistent with the older version. The framework also has built-in versions for `Element Plus` and `Naive UI`, allowing you to choose according to your preference.\n\n## Adding a New UI Framework\n\nIf you want to use a different UI framework, you only need to follow these steps:\n\n1. Create a new folder inside `apps`, for example, `apps/web-xxx`.\n2. Change the `name` field in `apps/web-xxx/package.json` to `web-xxx`.\n3. Remove dependencies and code from other UI frameworks and replace them with your chosen UI framework's logic, which requires minimal changes.\n4. Adjust the language files within `locales`.\n5. Adjust the components in `app.vue`.\n6. Adapt the theme of the UI framework to match `Vben Admin`.\n7. Adjust the application name in `.env`.\n8. Add a `dev:xxx` script in the root directory of the repository.\n9. Run `pnpm install` to install dependencies.\n"
  },
  {
    "path": "docs/src/en/guide/introduction/changelog.md",
    "content": "# CHANGE LOG\n\nTODO\n"
  },
  {
    "path": "docs/src/en/guide/introduction/quick-start.md",
    "content": "---\noutline: deep\n---\n\n# Quick Start {#quick-start}\n\n## Prerequisites\n\n::: info Environment Requirements\n\nBefore starting the project, ensure that your environment meets the following requirements:\n\n- [Node.js](https://nodejs.org/en) version 20.15.0 or above. It is recommended to use [fnm](https://github.com/Schniz/fnm), [nvm](https://github.com/nvm-sh/nvm), or directly use [pnpm](https://pnpm.io/cli/env) for version management.\n- [Git](https://git-scm.com/) any version.\n\nTo verify if your environment meets the above requirements, you can check the versions using the following commands:\n\n```bash\n# Ensure the correct node LTS version is displayed\nnode -v\n# Ensure the correct git version is displayed\ngit -v\n```\n\n:::\n\n## Starting the Project\n\n### Obtain the Source Code\n\n::: code-group\n\n```bash [GitHub]\n# Clone the code\ngit clone https://github.com/vbenjs/vue-vben-admin.git\n```\n\n```bash [Gitee]\n# Clone the code\n# The Gitee repository may not have the latest code\ngit clone https://gitee.com/annsion/vue-vben-admin.git\n```\n\n:::\n\n::: danger Caution\n\nEnsure that the directory where you store the code and all its parent directories do not contain Chinese, Korean, Japanese characters, or spaces, as this may cause errors when installing dependencies and starting the project.\n\n:::\n\n### Install Dependencies\n\nOpen a terminal in your code directory and execute the following commands:\n\n```bash\n# Enter the project directory\ncd vue-vben-admin\n\n# Enable the project-specified version of pnpm\nnpm i -g corepack\n\n# Install dependencies\npnpm install\n```\n\n::: tip Note\n\nThe project only supports using `pnpm` for installing dependencies. By default, `corepack` will be used to install the specified version of `pnpm`.\n\n:::\n\n### Run the Project\n\nExecute the following command to run the project:\n\n```bash\n# Start the project\npnpm dev\n```\n\nYou will see an output similar to the following, allowing you to select the project you want to run:\n\n```bash\n│\n◆  Select the app you need to run [dev]:\n│  ● @vben/web-antd\n│  ○ @vben/web-antdv-next\n│  ○ @vben/web-ele\n│  ○ @vben/web-naive\n│  ○ @vben/docs\n│  ○ @vben/playground\n└\n```\n\nNow, you can visit `http://localhost:5555` in your browser to view the project.\n"
  },
  {
    "path": "docs/src/en/guide/introduction/roadmap.md",
    "content": "# Roadmap\n\nTODO:\n"
  },
  {
    "path": "docs/src/en/guide/introduction/thin.md",
    "content": "# Slimmed-Down Version\n\nStarting from version `5.0`, we no longer provide slimmed-down repositories or branches. Our goal is to offer a more consistent development experience while reducing maintenance costs. Here’s how we introduce our project, slim down, and remove unnecessary features.\n\n## Application Slimming\n\nFirst, identify the version of the `UI` component library you need, and then delete the corresponding applications. For example, if you choose to use `Ant Design Vue`, you can delete the other applications. Simply remove the following two folders:\n\n```bash\napps/web-ele\napps/web-native\n\n```\n\n::: tip\n\nIf your project doesn’t include the `UI` component library you need, you can delete all other applications and create your own new application as needed.\n\n:::\n\n## Demo Code Slimming\n\nIf you don’t need demo code, you can simply delete the `playground` folder\n\n## Documentation Slimming\n\nIf you don’t need documentation, you can delete the `docs` folder.\n\n## Remove Mock Service\n\nIf you don’t need the `Mock` service, you can delete the `apps/backend-mock` folder. Also, remove the `VITE_NITRO_MOCK` variable from the `.env.development` file in your application.\n\n```bash\n# Whether to enable Nitro Mock service, true to enable, false to disable\nVITE_NITRO_MOCK=false\n```\n\n## Installing Dependencies\n\nNow that you’ve completed the slimming operations, you can install the dependencies and start your project:\n\n```bash\n# Run in the root directory\npnpm install\n\n```\n\n## Adjusting Commands\n\nAfter slimming down, you may need to adjust commands according to your project. In the `package.json` file in the root directory, you can adjust the `scripts` field and remove any commands you don’t need.\n\n```json\n{\n  \"scripts\": {\n    \"build:antd\": \"pnpm run build --filter=@vben/web-antd\",\n    \"build:docs\": \"pnpm run build --filter=@vben/docs\",\n    \"build:ele\": \"pnpm run build --filter=@vben/web-ele\",\n    \"build:naive\": \"pnpm run build --filter=@vben/web-naive\",\n    \"build:tdesign\": \"pnpm run build --filter=@vben/web-tdesign\",\n    \"build:play\": \"pnpm run build --filter=@vben/playground\",\n    \"dev:antd\": \"pnpm -F @vben/web-antd run dev\",\n    \"dev:docs\": \"pnpm -F @vben/docs run dev\",\n    \"dev:ele\": \"pnpm -F @vben/web-ele run dev\",\n    \"dev:play\": \"pnpm -F @vben/playground run dev\",\n    \"dev:naive\": \"pnpm -F @vben/web-naive run dev\"\n  }\n}\n```\n"
  },
  {
    "path": "docs/src/en/guide/introduction/vben.md",
    "content": "# About Vben Admin\n\n::: info You are reading the documentation for [Vben Admin](https://github.com/vbenjs/vue-vben-admin) version `5.0`!\n\n- Vben Admin 2.x is currently archived and only receives critical fixes.\n- The new version is not compatible with the old version. If you are using the old version (v2, v3), please refer to the [Vue Vben Admin 2.x Documentation](https://doc.vvbin.cn).\n- If you find any errors in the documentation, feel free to submit an issue to help us improve.\n- If you just want to experience it, you can check out the [Quick Start](./quick-start.md).\n\n:::\n\n[Vben Admin](https://github.com/vbenjs/vue-vben-admin) is a backend solution based on [Vue 3.0](https://github.com/vuejs/core), [Vite](https://github.com/vitejs/vite), and [TypeScript](https://www.typescriptlang.org/), aimed at providing an out-of-the-box solution for developing medium to large-scale projects. It includes features like component re-encapsulation, utilities, hooks, dynamic menus, permission validation, multi-theme configurations, and button-level permission control. The project uses the latest frontend technology stack, making it a good starting template for quickly building enterprise-level mid- to backend product prototypes. It can also serve as an example for learning `vue3`, `vite`, `ts`, and other mainstream technologies. The project will continue to follow the latest technologies and apply them within the project.\n\n## Features\n\n- **Latest Technology Stack**: Developed using cutting-edge frontend technologies like `Vue 3`, `Vite`, and `TypeScript`.\n- **Internationalization**: Built-in comprehensive internationalization solutions with multi-language support.\n- **Permission Validation**: Comprehensive permission validation solutions, including button-level permission control.\n- **Multi-Theme**: Built-in multiple theme configurations & dark mode to meet personalized needs.\n- **Dynamic Menu**: Supports dynamic menus that can display based on permissions.\n- **Mock Data**: High-performance local Mock data solution based on `Nitro`.\n- **Rich Components**: Provides a wide range of components to meet most business needs.\n- **Standardization**: Code quality is ensured with tools like `Oxfmt`, `Oxlint`, `ESLint`, `Stylelint`, `Publint`, and `CSpell`.\n- **Engineering**: Development efficiency is improved with tools like `Pnpm Monorepo`, `Turborepo`, and `Changeset`.\n- **Multi-UI Library Support**: Supports mainstream UI libraries like `Ant Design Vue`, `Ant Design Vue Next`, `Element Plus`, `Naive UI`, and `TDesign`, without being restricted to a specific framework.\n\n## Browser Support\n\n- **Local development** is recommended using the **latest version of Chrome**. **Versions below Chrome 80 are not supported**.\n\n- **Production environment** supports modern browsers, IE is not supported.\n\n| [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png\" alt=\"IE\" width=\"24px\" height=\"24px\"  />](http://godban.github.io/browsers-support-badges/)IE | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png\" alt=\" Edge\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)Edge | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png\" alt=\"Firefox\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)Firefox | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png\" alt=\"Chrome\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)Chrome | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png\" alt=\"Safari\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)Safari |\n| :-: | :-: | :-: | :-: | :-: |\n| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |\n\n## Contribution\n\n- [Vben Admin](https://github.com/vbenjs/vue-vben-admin) is still being actively updated. Contributions are welcome to help maintain and improve the project, aiming to create a better mid- to backend solution.\n- If you wish to join us, you can start by contributing in the following ways, and we will invite you to join based on your activity.\n\n::: info Join Us\n\n- Regularly submit `PRs`.\n- Provide valuable suggestions.\n- Participate in discussions and help resolve some `issues`.\n- Help maintain the documentation.\n\n:::\n"
  },
  {
    "path": "docs/src/en/guide/introduction/why.md",
    "content": "# Why Choose Us?\n\nFirst of all, we do not compare ourselves with other frameworks. We believe that every framework has its own characteristics and is suited for different scenarios. Our goal is to provide a simple and easy-to-use framework that allows developers to get started quickly and focus on developing business logic. Therefore, we will continue to improve and optimize our framework to offer a better experience.\n\n## Framework History\n\nStarting from Vue Vben Admin version 1.x, the framework has undergone numerous iterations and optimizations. From initially using `Vite 0.x` when there were no ready-made plugins available, we developed many custom plugins to bridge the gap between Webpack and Vite. Although many of these have since been replaced, our original intention has remained the same: to provide a simple and easy-to-use framework.\n\nAlthough the community maintained the project for a period, we have always closely monitored the development of Vue Vben Admin. We have witnessed many developers use Vben Admin and provide valuable suggestions and feedback. We are very grateful for everyone's support and contributions, which continue to drive us to improve Vben Admin. In the new version, we have continuously collected user feedback, started anew, and optimized the framework to provide a better user experience. Our goal is to enable developers to get started quickly and focus on developing business logic.\n"
  },
  {
    "path": "docs/src/en/guide/other/faq.md",
    "content": "# Frequently Asked Questions #{faq}\n\n::: tip Listed are some common questions\n\nIf you have a question, you can first look here. If not found, you can search or submit your question on [GitHub Issue](https://github.com/vbenjs/vue-vben-admin/issues), or if it's a discussion-type question, you can go to [GitHub Discussions](https://github.com/vbenjs/vue-vben-admin/discussions)\n\n:::\n\n## Instructions\n\nIf you encounter a problem, you can start looking from the following aspects:\n\n1. Search the corresponding module's GitHub repository [issue](https://github.com/vbenjs/vue-vben-admin/issues)\n2. Search for the problem on [Google](https://www.google.com)\n3. Search for the problem on [Baidu](https://www.baidu.com)\n4. If you can't find the issue in the list below, you can ask in [issues](https://github.com/vbenjs/vue-vben-admin/issues)\n5. If it's not a problem type and needs discussion, please go to [discussions](https://github.com/vbenjs/vue-vben-admin/discussions) to discuss\n\n## Dependency Issues\n\nIn a `Monorepo` project, it's important to get into the habit of running `pnpm install` after every `git pull` because new dependencies are often added. The project has configured automatic execution of `pnpm install` in `lefthook.yml`, but sometimes there might be issues. If it does not execute automatically, it is recommended to execute it manually once.\n\n## About Cache Update Issues\n\nThe project configuration is by default cached in `localStorage`, so some configurations may not change after a version update.\n\nThe solution is to modify the `version` number in `package.json` each time the code is updated. This is because the key for `localStorage` is based on the version number. Therefore, after an update, the configurations from a previous version will become invalid. Simply re-login to apply the new settings.\n\n## About Modifying Configuration Files\n\nWhen modifying environment files such as `.env` or the `vite.config.ts` file, Vite will automatically restart the service.\n\nThere's a chance that automatic restarts may encounter issues. Simply rerunning the project can resolve these problems.\n\n## Errors When Running Locally\n\nSince Vite does not transform code locally and the code uses relatively new syntax such as optional chaining, local development requires using a higher version of the browser (`Chrome 90+`).\n\n## Blank Page After Switching Pages\n\nThis issue occurs because route switching animations are enabled, and the corresponding page component has multiple root nodes. Adding a `<div></div>` at the outermost layer of the page can solve this problem.\n\n**Incorrect Example**\n\n```vue\n<template>\n  <!-- Annotations are also considered a node -->\n  <h1>text h1</h1>\n  <h2>text h2</h2>\n</template>\n```\n\n**正确示例**\n\n```vue\n<template>\n  <div>\n    <h1>text h1</h1>\n    <h2>text h2</h2>\n  </div>\n</template>\n```\n\n::: tip Tip\n\n- If you want to use multiple root tags, you can disable route switching animations.\n- Root comment nodes under `template` are also counted as a node.\n\n:::\n\n## My code works locally but not when packaged\n\nThe reason for this issue could be one of the following. You can check these reasons, and if there are other possibilities, feel free to add them:\n\n- The variable `ctx` was used, which is not exposed in the instance type. The Vue official documentation also advises against using this property as it is intended for internal use only.\n\n```ts\nimport { getCurrentInstance } from 'vue';\ngetCurrentInstance().ctx.xxxx;\n```\n\n## Dependency Installation Issues\n\n- If you cannot install dependencies or the startup reports an error, you can try executing `pnpm run reinstall`.\n- If you cannot install dependencies or encounter errors, you can try switching to a mobile hotspot for installing dependencies.\n- If that still doesn't work, you can configure a domestic mirror for installation.\n- You can also create a `.npmrc` file in the project root directory with the following content:\n\n```bash\n# .npmrc\nregistry = https://registry.npmmirror.com/\n```\n\n## Package File Too Large\n\n- First, the full version will be larger because it includes many library files. You can use the simplified version for development.\n\n- Secondly, it is recommended to enable gzip, which can reduce the size to about 1/3 of the original. Gzip can be enabled directly by the server. If so, the frontend does not need to build `.gz` format files. If the frontend has built `.gz` files, for example, with nginx, you need to enable the `gzip_static: on` option.\n\n- While enabling gzip, you can also enable `brotli` for better compression than gzip. Both can coexist.\n\n**Note**\n\n- gzip_static: This module requires additional installation in nginx, as the default nginx does not include this module.\n\n- Enabling `brotli` also requires additional nginx module installation.\n\n## Runtime Errors\n\nIf you encounter errors similar to the following, please check that the full project path (including all parent paths) does not contain Chinese, Japanese, or Korean characters. Otherwise, you will encounter a 404 error for the path, leading to the following issue:\n\n```ts\n[vite] Failed to resolve module import \"ant-design-vue/dist/antd.css-vben-adminode_modulesant-design-vuedistantd.css\". (imported by /@/setup/ant-design-vue/index.ts)\n```\n\n## Console Route Warning Issue\n\nIf you see the following warning in the console, and the page `can open normally`, you can ignore this warning.\n\nFuture versions of `vue-router` may provide an option to disable this warning.\n\n```ts\n[Vue Router warn]: No match found for location with path \"xxxx\"\n```\n\n## Startup Error\n\nIf you encounter the following error message, please check if your nodejs version meets the requirements.\n\n```bash\nTypeError: str.matchAll is not a function\nat Object.extractor (vue-vben-admin-main\\node_modules@purge-icons\\core\\dist\\index.js:146:27)\nat Extract (vue-vben-admin-main\\node_modules@purge-icons\\core\\dist\\index.js:173:54)\n```\n\n## nginx Deployment\n\nAfter deploying to `nginx`，you might encounter the following error:\n\n```bash\nFailed to load module script: Expected a JavaScript module script but the server responded with a MIME type of \"application/octet-stream\". Strict MIME type checking is enforced for module scripts per HTML spec.\n```\n\nSolution 1:\n\n```bash\nhttp {\n    #If there is such a configuration, it needs to be commented out\n    #include       mime.types;\n\n    types {\n      application/javascript js mjs;\n    }\n}\n```\n\nSolution 2：\n\nOpen the `mime.types` file under `nginx` and change `application/javascript js;` to `application/javascript js mjs;`\n"
  },
  {
    "path": "docs/src/en/guide/other/project-update.md",
    "content": "# PROJECT UPDATE\n\n## Why Can't It Be Updated Like a npm Plugin\n\nBecause the project is a complete project template, not a plugin or a package, it cannot be updated like a plugin. After you use the code, you will develop it further based on business needs, and you need to manually merge and upgrade.\n\n## What Should I Do\n\nThe project is managed using a `Monorepo` approach and has abstracted some of the more core code, such as `packages/@core`, `packages/effects`. As long as the business code has not modified this part of the code, you can directly pull the latest code and then merge it into your branch. You only need to handle some conflicts simply. Other folders will only make some minor adjustments, which will not affect the business code.\n\n::: tip Recommendation\n\nIt is recommended to follow the repository updates actively and merge them; do not accumulate over a long time, Otherwise, it will lead to too many merge conflicts and increase the difficulty of merging.\n\n:::\n\n## Updating Code Using Git\n\n1. Clone the code\n\n```bash\ngit clone https://github.com/vbenjs/vue-vben-admin.git\n```\n\n2. Add your company's git source address\n\n```bash\n# up is the source name, can be set arbitrarily\n# gitUrl is the latest open-source code\ngit remote add up gitUrl;\n```\n\n3. Push the code to your company's git\n\n```bash\n# Push the code to your company\n# main is the branch name, adjust according to your situation\ngit push up main\n# Sync the company's code\n# main is the branch name, adjust according to your situation\ngit pull up main\n```\n\n4. How to sync the latest open-source code\n\n```bash\ngit pull origin main\n```\n\n::: tip Tip\n\nWhen syncing the code, conflicts may occur. Just resolve the conflicts.\n\n:::\n"
  },
  {
    "path": "docs/src/en/guide/other/remove-code.md",
    "content": "# Remove Code\n\n## Remove Code\n\nIn the corresponding application's `index.html` file, find the following code and delete it:\n\n```html\n<!-- apps/web-antd -->\n<script>\n  var _hmt = _hmt || [];\n  (function () {\n    var hm = document.createElement('script');\n    hm.src = 'https://hm.baidu.com/hm.js?d20a01273820422b6aa2ee41b6c9414d';\n    var s = document.getElementsByTagName('script')[0];\n    s.parentNode.insertBefore(hm, s);\n  })();\n</script>\n```\n"
  },
  {
    "path": "docs/src/en/guide/project/changeset.md",
    "content": "# Changeset\n\nThe project has integrated [changeset](https://github.com/changesets/changesets) as a version management tool. Changeset is a version management tool that helps us better manage versions, generate changelogs, and automate releases.\n\nFor detailed usage, please refer to the official documentation. If you do not need it, you can ignore it directly.\n\n## Command Line\n\nThe changeset command is already built into the project:\n\n### Interactively Add Changesets\n\n```bash\npnpm run changeset\n```\n\n### Uniformly Increment Version Numbers\n\n```bash\npnpm run version\n```\n"
  },
  {
    "path": "docs/src/en/guide/project/cli.md",
    "content": "---\noutline: deep\n---\n\n# CLI\n\nIn the project, some command-line tools are provided for common operations, located in `scripts`.\n\n## vsh\n\nUsed for some project operations, such as cleaning the project, checking the project, etc.\n\n### Usage\n\n```bash\npnpm vsh [command] [options]\n```\n\n### vsh check-circular\n\nCheck for circular references throughout the project. If there are circular references, the modules involved will be output to the console.\n\n#### Usage\n\n```bash\npnpm vsh check-circular\n```\n\n#### Options\n\n| Option     | Description                                               |\n| ---------- | --------------------------------------------------------- |\n| `--staged` | Only check files in the git staging area, default `false` |\n\n### vsh check-dep\n\nCheck the dependency situation of the entire project and output `unused dependencies`, `uninstalled dependencies` information to the console.\n\n#### Usage\n\n```bash\npnpm vsh check-dep\n```\n\n### vsh lint\n\nLint checks the project to see if the code in the project conforms to standards.\n\n#### Usage\n\n```bash\npnpm vsh lint\n```\n\n#### Options\n\n| Option     | Description                                  |\n| ---------- | -------------------------------------------- |\n| `--format` | Check and try to fix errors, default `false` |\n\n### vsh publint\n\nPerform package standard checks on `Monorepo` projects to see if the packages in the project conform to standards.\n\n#### Usage\n\n```bash\npnpm vsh publint\n```\n\n#### Options\n\n| Option    | Description                          |\n| --------- | ------------------------------------ |\n| `--check` | Only perform checks, default `false` |\n\n### vsh code-workspace\n\nGenerate `vben-admin.code-workspace` file. Currently, it does not need to be executed manually and will be executed automatically when code is committed.\n\n#### Usage\n\n```bash\npnpm vsh code-workspace\n```\n\n#### Options\n\n| Option          | Description                                               |\n| --------------- | --------------------------------------------------------- |\n| `--auto-commit` | Automatically commit during `git commit`, default `false` |\n| `--spaces`      | Indentation format, default `2` spaces                    |\n\n## turbo-run\n\nUsed to quickly execute scripts in the large repository and provide option-based interactive selection.\n\n### Usage\n\n```bash\npnpm turbo-run [command]\n```\n\n### turbo-run dev\n\nQuickly execute the `dev` command and provide option-based interactive selection.\n"
  },
  {
    "path": "docs/src/en/guide/project/dir.md",
    "content": "# Directory Explanation\n\nThe repository uses Monorepo management, and the project structure is as follows:\n\n```bash\n.\n├── README.md # Project documentation\n├── apps # Project applications directory\n│   ├── backend-mock # Backend mock service application\n│   ├── web-antd # Frontend application based on Ant Design Vue\n│   ├── web-antdv-next # Frontend application based on Ant Design Vue Next\n│   ├── web-ele # Frontend application based on Element Plus\n│   ├── web-naive # Frontend application based on Naive UI\n│   └── web-tdesign # Frontend application based on TDesign\n├── cspell.json # CSpell configuration file\n├── docs # Project documentation directory\n├── eslint.config.mjs # ESLint configuration file\n├── lefthook.yml # Git hook configuration file\n├── internal # Internal tools directory\n│   ├── lint-configs # Code linting configurations\n│   │   ├── commitlint-config # Commitlint configuration\n│   │   ├── eslint-config # ESLint configuration\n│   │   ├── oxfmt-config # Oxfmt configuration\n│   │   ├── oxlint-config # Oxlint configuration\n│   │   └── stylelint-config # Stylelint configuration\n│   ├── node-utils # Node.js tools\n│   ├── tsconfig # Common tsconfig settings\n│   └── vite-config # Common Vite configuration\n├── oxfmt.config.ts # Oxfmt config entry\n├── oxlint.config.ts # Oxlint configuration file\n├── package.json # Project dependency configuration\n├── packages # Project packages directory\n│   ├── @core # Core package\n│   │   ├── base # Base package\n│   │   │   ├── design # Design related\n│   │   │   ├── icons # Icons\n│   │   │   ├── shared # Shared\n│   │   │   └── typings # Type definitions\n│   │   ├── composables # Composable APIs\n│   │   ├── preferences # Preferences\n│   │   └── ui-kit # UI component collection\n│   │       ├── layout-ui # Layout UI\n│   │       ├── menu-ui # Menu UI\n│   │       ├── shadcn-ui # shadcn UI\n│   │       └── tabs-ui # Tabs UI\n│   ├── constants # Constants\n│   ├── effects # Effects related packages\n│   │   ├── access # Access control\n│   │   ├── plugins # Large third-party dependency plugins\n│   │   ├── common-ui # Common UI\n│   │   ├── hooks # Composable APIs\n│   │   ├── layouts # Layouts\n│   │   └── request # Request\n│   ├── icons # Icons\n│   ├── locales # Internationalization\n│   ├── preferences # Preferences\n│   ├── stores # State management\n│   ├── styles # Styles\n│   ├── types # Type definitions\n│   └── utils # Utilities\n├── playground # Demo directory\n├── pnpm-lock.yaml # pnpm lock file\n├── pnpm-workspace.yaml # pnpm workspace configuration file\n├── scripts # Scripts directory\n│   ├── deploy # Deployment-related scripts\n│   ├── turbo-run # Turbo run script\n│   └── vsh # VSH script\n├── stylelint.config.mjs # Stylelint configuration file\n├── turbo.json # Turbo configuration file\n├── vben-admin.code-workspace # VS Code workspace configuration file\n└── vitest.config.ts # Vitest configuration file\n```\n"
  },
  {
    "path": "docs/src/en/guide/project/standard.md",
    "content": "# Standards\n\n::: tip Contributing Code\n\n- If you want to contribute code to the project, please ensure your code complies with the project's coding standards.\n- If you are using `vscode`, you need to install the following plugins:\n  - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - Script code checking\n  - [Oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) - Oxlint / Oxfmt integration\n  - [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - Word syntax checking\n  - [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS formatting\n\n:::\n\n## Purpose\n\nStudents with basic engineering literacy always pay attention to coding standards, and code style checking (Code Linting, simply called Lint) is an important means to ensure the consistency of coding standards.\n\nFollowing the corresponding coding standards has the following benefits:\n\n- Lower bug error rate\n- Efficient development efficiency\n- Higher readability\n\n## Tools\n\nThe project's configuration files are located in `internal/lint-configs`, where you can modify various lint configurations.\n\nThe project integrates the following code verification tools:\n\n- [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) for code formatting\n- [Oxlint](https://oxc.rs/docs/guide/usage/linter.html) for JavaScript / TypeScript linting\n- [ESLint](https://eslint.org/) for Vue, JSONC, YAML, and related rules\n- [Stylelint](https://stylelint.io/) for CSS style checking\n- [Commitlint](https://commitlint.js.org/) for checking the standard of git commit messages\n- [Publint](https://publint.dev/) for checking the standard of npm packages\n- [Cspell](https://cspell.org/) for checking spelling errors\n- [lefthook](https://github.com/evilmartians/lefthook) for managing Git hooks, automatically running code checks and formatting before commits\n\n## Oxfmt\n\nOxfmt is used to keep code formatting consistent across the repository.\n\n### Command\n\n```bash\npnpm oxfmt\npnpm oxfmt --check\n```\n\n### Configuration\n\nThe root Oxfmt entry file is `oxfmt.config.ts`, and its core configuration is located in `internal/lint-configs/oxfmt-config`.\n\n## Oxlint\n\nOxlint is the primary script linting tool for the repository.\n\n### Command\n\n```bash\npnpm oxlint\npnpm oxlint --fix\n```\n\n### Configuration\n\nThe core Oxlint configuration is located in `internal/lint-configs/oxlint-config`, and the root entry file is `oxlint.config.ts`.\n\n## ESLint\n\nESLint is used to complement Vue, JSONC, YAML, and related rules.\n\n### Command\n\n```bash\npnpm eslint . --cache\n```\n\n### Configuration\n\nThe ESLint configuration file is `eslint.config.mjs`, with its core configuration located in the `internal/lint-configs/eslint-config` directory, which can be modified according to project needs.\n\n## Stylelint\n\nStylelint is used to check the style of CSS within the project. Coupled with the editor's auto-fix feature, it can effectively unify the CSS style within the project.\n\n### Command\n\n```bash\npnpm stylelint \"**/*.{vue,css,less,scss}\" --cache\n```\n\n### Configuration\n\nThe Stylelint configuration file is `stylelint.config.mjs`, with its core configuration located in the `internal/lint-configs/stylelint-config` directory, which can be modified according to project needs.\n\n## CommitLint\n\nIn a team, everyone's git commit messages can vary widely, making it difficult to ensure standardization without a mechanism. You might think of using git's hook mechanism to write shell scripts to implement this. Of course, this is possible, but JavaScript has a good tool for this template: commitlint.\n\n### Configuration\n\nThe CommitLint configuration file is `.commitlintrc.js`, with its core configuration located in the `internal/lint-configs/commitlint-config` directory, which can be modified according to project needs.\n\n### Git Commit Standards\n\nRefer to [Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)\n\n- `feat` Add new features\n- `fix` Fix problems / BUGs\n- `style` Code style changes that do not affect the outcome\n- `perf` Optimization / performance improvement\n- `refactor` Refactoring\n- `revert` Revert changes\n- `test` Related to tests\n- `docs` Documentation / comments\n- `chore` Dependency updates / scaffold configuration changes\n- `workflow` Workflow improvements\n- `ci` Continuous integration\n- `types` Type modifications\n\n### Disabling Git Commit Standard Checks\n\nIf you want to disable Git commit standard checks, there are two ways:\n\n::: code-group\n\n```bash [Temporary disable]\ngit commit -m 'feat: add home page' --no-verify\n```\n\n```yaml [Long-term disable]\ncommit-msg:\n  commands:\n    # commitlint:\n    #   run: pnpm exec commitlint --edit $1\n```\n\n:::\n\nIf you changed `lefthook.yml`, reinstall hooks with:\n\n```bash\npnpm exec lefthook install\n```\n\n## Publint\n\nPublint is a tool for checking npm package standards, including package versioning, exports, and ESM package structure.\n\n### Command\n\n```bash\npnpm publint\n```\n\n## Cspell\n\nCspell is a tool for checking spelling errors in the code, avoiding bugs caused by spelling mistakes.\n\n### Command\n\n```bash\npnpm check:cspell\n```\n\n### Configuration\n\nThe cspell configuration file is `cspell.json`, which can be modified according to project needs.\n\n## Git Hook\n\nGit hooks are generally combined with various lints to check code style during git commits. If the check fails, the commit will not proceed. Developers need to modify and resubmit.\n\n### lefthook\n\nOne issue is that the check would verify all code, but in practice we usually only want to check the code being committed. This is where lefthook comes in.\n\nThe most effective solution is to perform lint checks locally before committing. A common practice is to use lefthook to perform checks before local submission.\n\nThe project defines corresponding hooks inside `lefthook.yml`:\n\n- `pre-commit`: Runs before commit, used for code formatting and checking\n  - `code-workspace`: Updates VSCode workspace configuration\n  - `lint-md`: Formats Markdown files\n  - `lint-vue`: Formats and checks Vue files\n  - `lint-js`: Formats and checks JavaScript / TypeScript files\n  - `lint-style`: Formats and checks style files\n  - `lint-package`: Formats `package.json`\n  - `lint-json`: Formats other JSON files\n- `post-merge`: Runs after merge, used for automatic dependency installation\n  - `install`: Runs `pnpm install` to install new dependencies\n- `commit-msg`: Runs during commit, used for checking commit message format\n  - `commitlint`: Uses commitlint to check commit messages\n\nCurrent hooks can be installed with:\n\n```bash\npnpm exec lefthook install\n```\n\n#### How to Disable lefthook\n\nIf you want to temporarily disable lefthook, use:\n\n```bash\ngit commit -m 'feat: add home page' --no-verify\n```\n\n#### How to Modify lefthook Configuration\n\nIf you want to modify lefthook's configuration, you can edit the `lefthook.yml` file. For example:\n\n```yaml\npre-commit:\n  parallel: true\n  commands:\n    lint-js:\n      run: pnpm oxfmt {staged_files} && pnpm oxlint --fix {staged_files} && pnpm eslint --cache --fix {staged_files}\n      glob: '*.{js,jsx,ts,tsx}'\n```\n\nWhere:\n\n- `parallel`: Whether to execute tasks in parallel\n- `commands`: Defines the list of tasks to execute\n- `run`: Command to execute\n- `glob`: File pattern to match\n- `{staged_files}`: Represents the list of staged files\n"
  },
  {
    "path": "docs/src/en/guide/project/tailwindcss.md",
    "content": "# Tailwind CSS\n\n[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework for quickly building custom designs. The current project uses **Tailwind CSS v4**.\n\n## Configuration\n\nThe project no longer maintains Tailwind through `tailwind.config.*` files. Theme definitions and scan scope are now managed through CSS and the shared Vite configuration.\n\n- Theme entry: `internal/tailwind-config/src/theme.css`\n- Vite integration: `internal/vite-config`\n\nIn `global.css`, you will see the Tailwind CSS v4 directives currently used by the project, such as:\n\n- `@source`\n- `@custom-variant`\n- `@theme`\n- `@theme inline`\n- `@utility`\n\n## How Packages Use Tailwind CSS\n\nThe project does not decide whether Tailwind CSS is enabled based on whether a package contains `tailwind.config.mjs`.\n\nApps and packages share `@vben/vite-config`, which integrates `@tailwindcss/vite`. The Tailwind scan scope is managed centrally in `@vben/tailwind-config`, backed by `internal/tailwind-config/src/theme.css`.\n\n::: tip Notes on using Tailwind CSS in packages\n\nIf you are building a pure SDK package and do not need Tailwind CSS, you do not need to add any legacy `tailwind.config.*` file.\n\n:::\n\n## `@apply` in Vue SFCs\n\nThe project applies a shared handling layer for `@apply` inside Vue single-file components. The related logic is located at:\n\n- `internal/vite-config/src/plugins/tailwind-reference.ts`\n\nWhen component styles use `@apply`, `@reference \"@vben/tailwind-config/theme\"` is injected automatically in most cases.\n"
  },
  {
    "path": "docs/src/en/guide/project/test.md",
    "content": "# Unit Testing\n\nThe project incorporates [Vitest](https://vitest.dev/) as the unit testing tool. Vitest is a test runner based on Vite, offering a simple API for writing test cases.\n\n## Writing Test Cases\n\nWithin the project, we follow the convention of naming test files with a `.test.ts` suffix or placing them inside a `__tests__` directory. For example, if you create a `utils.ts` file, then you would create a corresponding `utils.spec.ts` file in the same directory,\n\n```ts\n// utils.test.ts\nimport { expect, test } from 'vitest';\nimport { sum } from './sum';\n\ntest('adds 1 + 2 to equal 3', () => {\n  expect(sum(1, 2)).toBe(3);\n});\n```\n\n## Running Tests\n\nTo run the tests, execute the following command at the root of the monorepo:\n\n```bash\npnpm test:unit\n```\n\n## Existing Unit Tests\n\nThere are already some unit test cases in the project. You can search for files ending with .test.ts to view them. When you make changes to related code, you can run the unit tests to ensure the correctness of your code. It is recommended to maintain the coverage of unit tests during the development process and to run unit tests as part of the CI/CD process to ensure tests pass before deploying the project.\n\nExisting unit test status:\n\n![](/guide/test.png)\n"
  },
  {
    "path": "docs/src/en/guide/project/vite.md",
    "content": "# Vite Config\n\nThe project encapsulates a layer of Vite configuration and integrates some plugins for easy reuse across multiple packages and applications. The usage is as follows:\n\n## Application\n\n```ts\n// vite.config.mts\nimport { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    application: {},\n    // Vite configuration, override according to the official Vite documentation\n    vite: {},\n  };\n});\n```\n\n## Package\n\n```ts\n// vite.config.mts\nimport { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    library: {},\n    // Vite configuration, override according to the official Vite documentation\n    vite: {},\n  };\n});\n```\n"
  },
  {
    "path": "docs/src/en/index.md",
    "content": "---\n# https://vitepress.dev/reference/default-theme-home-page\nlayout: home\nsidebar: false\n\nhero:\n  name: Vben Admin\n  text: Enterprise-Level Management System Framework\n  tagline: Fully Upgraded, Ready to Use, Simple and Efficient\n  image:\n    src: https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp\n    alt: Vben Admin\n  actions:\n    - theme: brand\n      text: Get Started ->\n      link: /en/guide/introduction/vben\n    - theme: alt\n      text: Live Preview\n      link: https://www.vben.pro\n    - theme: alt\n      text: View on GitHub\n      link: https://github.com/vbenjs/vue-vben-admin\n\nfeatures:\n  - icon: 🚀\n    title: Latest Technology Stack\n    details: Based on the latest technology stack, including Vue3, Pinia, Vue Router, TypeScript, etc.\n    link: /en/guide/introduction/quick-start\n    linkText: Get Started\n  - icon: 🦄\n    title: Rich Configurations\n    details: An enterprise-level frontend solution for middle and back-end systems, offering a wealth of components, templates, and various preference settings.\n    link: /en/guide/essentials/settings\n    linkText: Configuration Documentation\n  - icon: 🎨\n    title: Theme Customization\n    details: Easily switch between various themes through simple configurations, catering to personalized needs.\n    link: /en/guide/in-depth/theme\n    linkText: Theme Documentation\n  - icon: 🌐\n    title: Internationalization\n    details: Built-in internationalization support with multiple languages to meet global needs.\n    link: /en/guide/in-depth/locale\n    linkText: Internationalization Documentation\n  - icon: 🔐\n    title: Access Control\n    details: Built-in access control solutions supporting various permission management methods to meet different access requirements.\n    link: /en/guide/in-depth/access\n    linkText: Access Documentation\n  - title: Vite\n    icon:\n      src: /logos/vite.svg\n    details: Modern frontend build tool with fast cold start and instant hot updates.\n    link: https://vitejs.dev/\n    linkText: Official Site\n  - title: Shadcn UI\n    icon:\n      src: /logos/shadcn-ui.svg\n    details: Core built on Shadcn UI + Tailwindcss, with business support for any UI framework.\n    link: https://www.shadcn-vue.com/\n    linkText: Official Site\n  - title: Turbo Repo\n    icon:\n      src: /logos/turborepo.svg\n    details: Standardized monorepo architecture using pnpm + monorepo + turbo for enterprise-level development standards.\n    link: https://turbo.build/\n    linkText: Official Site\n  - title: Nitro Mock Server\n    icon:\n      src: /logos/nitro.svg\n    details: Built-in Nitro Mock service makes your mock service more powerful.\n    link: https://nitro.unjs.io/\n    linkText: Official Site\n---\n\n<VbenContributors />\n"
  },
  {
    "path": "docs/src/friend-links/index.md",
    "content": "# 友情链接\n\n如果您的网站是与 Vben Admin 相关的，也属于开源项目，欢迎联系我们，我们会将与您的网站加入交换友情链接。\n\n## 交换方式\n\n### 添加作者，并注明来意\n\n- 通过邮箱联系作者： [ann.vben@gmail.com](mailto:ann.vben@gmail.com)\n- 通过微信联系作者：\n\n <img src=\"https://unpkg.com/@vbenjs/static-source@0.1.7/source/wechat.jpg\" style=\"width: 300px;\"/>\n\n### 提供资料\n\n提供您的网站名称、链接、描述、LOGO（可选）等信息，我们会在第一时间添加您的网站。\n\n### 友情链接\n\n- 在您的网站上添加我们的友情链接，链接如下：\n  - 名称：Vben Admin\n  - 链接：https://www.vben.pro\n  - 描述：Vben Admin 企业级开箱即用的中后台前端解决方案\n  - Logo：https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp\n\n我们将定期的检查友情链接，如果发现您的网站已经删除了我们的友情链接以及链接地址是否正确。\n"
  },
  {
    "path": "docs/src/guide/essentials/build.md",
    "content": "# 构建与部署\n\n::: tip 前言\n\n由于是展示项目，所以打包后相对较大，如果项目中没有用到的插件，可以删除对应的文件或者路由，不引用即可，没有引用就不会打包。\n\n:::\n\n## 构建\n\n项目开发完成之后，执行以下命令进行构建：\n\n**注意：** 请在项目根目录下执行以下命令\n\n```bash\npnpm build\n```\n\n构建打包成功之后，会在根目录生成对应的应用下的 `dist` 文件夹，里面就是构建打包好的文件，例如: `apps/web-antd/dist/`\n\n## 预览\n\n发布之前可以在本地进行预览，有多种方式，这里介绍两种：\n\n- 使用项目自定的命令进行预览(推荐)\n\n**注意：** 请在项目根目录下执行以下命令\n\n```bash\npnpm preview\n```\n\n等待构建成功后，访问 `http://localhost:4173` 即可查看效果。\n\n- 本地服务器预览\n\n可以在电脑全局安装 `serve` 服务，如 `live-server`,\n\n```bash\nnpm i -g live-server\n```\n\n然后在 `dist` 目录下执行 `live-server` 命令，即可在本地查看效果。\n\n```bash\ncd apps/web-antd/dist\n# 本地预览，默认端口8080\nlive-server\n# 指定端口\nlive-server --port=9000\n```\n\n## 压缩\n\n### 开启 `gzip` 压缩\n\n需要在打包的时候更改`.env.production`配置:\n\n```bash\nVITE_COMPRESS=gzip\n```\n\n### 开启 `brotli` 压缩\n\n需要在打包的时候更改`.env.production`配置:\n\n```bash\nVITE_COMPRESS=brotli\n```\n\n### 同时开启 `gzip` 和 `brotli` 压缩\n\n需要在打包的时候更改`.env.production`配置:\n\n```bash\nVITE_COMPRESS=gzip,brotli\n```\n\n::: tip 提示\n\n`gzip` 和 `brotli` 都需要安装特定模块才能使用。\n\n:::\n\n::: details gzip 与 brotli 在 nginx 内的配置\n\n```bash\nhttp {\n  # 开启gzip\n  gzip on;\n  # 开启gzip_static\n  # gzip_static 开启后可能会报错，需要安装相应的模块, 具体安装方式可以自行查询\n  # 只有这个开启，vue文件打包的.gz文件才会有效果，否则不需要开启gzip进行打包\n  gzip_static on;\n  gzip_proxied any;\n  gzip_min_length 1k;\n  gzip_buffers 4 16k;\n  #如果nginx中使用了多层代理 必须设置这个才可以开启gzip。\n  gzip_http_version 1.0;\n  gzip_comp_level 2;\n  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;\n  gzip_vary off;\n  gzip_disable \"MSIE [1-6]\\.\";\n\n  # 开启 brotli压缩\n  # 需要安装对应的nginx模块,具体安装方式可以自行查询\n  # 可以与gzip共存不会冲突\n  brotli on;\n  brotli_comp_level 6;\n  brotli_buffers 16 8k;\n  brotli_min_length 20;\n  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;\n}\n```\n\n:::\n\n## 构建分析\n\n如果你的构建文件很大，可以通过项目内置 [rollup-plugin-analyzer](https://github.com/doesdev/rollup-plugin-analyzer) 插件进行代码体积分析，从而优化你的代码。只需要在`根目录`下执行以下命令：\n\n```bash\npnpm run build:analyze\n```\n\n运行之后，在自动打开的页面可以看到具体的体积分布，以分析哪些依赖有问题。\n\n![Build analysis report](/guide/report.png)\n\n## 部署\n\n简单的部署只需要将最终生成的静态文件，dist 文件夹的静态文件发布到你的 cdn 或者静态服务器即可，需要注意的是其中的 index.html 通常会是你后台服务的入口页面，在确定了 js 和 css 的静态之后可能需要改变页面的引入路径。\n\n例如上传到 nginx 服务器，可以将 dist 文件夹下的文件上传到服务器的 `/srv/www/project/index.html` 目录下，然后访问配置好的域名即可。\n\n```bash\n# nginx配置\nlocation / {\n  # 不缓存html，防止程序更新后缓存继续生效\n  if ($request_filename ~* .*\\.(?:htm|html)$) {\n    add_header Cache-Control \"private, no-store, no-cache, must-revalidate, proxy-revalidate\";\n    access_log on;\n  }\n  # 这里是vue打包文件dist内的文件的存放路径\n  root   /srv/www/project/;\n  index  index.html index.htm;\n}\n```\n\n部署时可能会发现资源路径不对，只需要修改`.env.production`文件即可。\n\n```bash\n# 根据自己路径来配置更改\n# 注意需要以 / 开头和结尾\nVITE_BASE=/\nVITE_BASE=/xxx/\n```\n\n### 前端路由与服务端的结合\n\n项目前端路由使用的是 vue-router，所以你可以选择两种方式：history 和 hash。\n\n- `hash` 默认会在 url 后面拼接`#`\n- `history` 则不会，不过 `history` 需要服务器配合\n\n可在 `.env.production` 内进行 mode 修改\n\n```bash\nVITE_ROUTER_HISTORY=hash\n```\n\n### history 路由模式下服务端配置\n\n开启 `history` 模式需要服务器配置，更多的服务器配置详情可以看 [history-mode](https://router.vuejs.org/guide/essentials/history-mode.html#html5-mode)\n\n这里以 `nginx` 配置为例：\n\n#### 部署到根目录\n\n```bash {5}\nserver {\n  listen 80;\n  location / {\n    # 用于配合 History 使用\n    try_files $uri $uri/ /index.html;\n  }\n}\n```\n\n#### 部署到非根目录\n\n- 首先需要在打包的时候更改`.env.production`配置:\n\n```bash\nVITE_BASE = /sub/\n```\n\n- 然后在 nginx 配置文件中配置\n\n```bash {8}\nserver {\n    listen       80;\n    server_name  localhost;\n    location /sub/ {\n      # 这里是vue打包文件dist内的文件的存放路径\n      alias   /srv/www/project/;\n      index index.html index.htm;\n      try_files $uri $uri/ /sub/index.html;\n    }\n}\n```\n\n## 跨域处理\n\n使用 nginx 处理项目部署后的跨域问题\n\n1. 配置前端项目接口地址，在项目目录下的`.env.production`文件中配置：\n\n```bash\nVITE_GLOB_API_URL=/api\n```\n\n2. 在 nginx 配置请求转发到后台\n\n```bash {10-11}\nserver {\n  listen       8080;\n  server_name  localhost;\n  # 接口代理，用于解决跨域问题\n  location /api {\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    # 后台接口地址\n    proxy_pass http://110.110.1.1:8080/api;\n    rewrite \"^/api/(.*)$\" /$1 break;\n    proxy_redirect default;\n    add_header Access-Control-Allow-Origin *;\n    add_header Access-Control-Allow-Headers X-Requested-With;\n    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;\n  }\n}\n```\n"
  },
  {
    "path": "docs/src/guide/essentials/concept.md",
    "content": "# 基础概念\n\n新版本中，整体工程进行了重构，现在我们将会介绍一些基础概念，以便于你更好的理解整个文档。请务必仔细阅读这一部分。\n\n## 大仓\n\n大仓指的是整个项目的仓库，包含了所有的代码、包、应用、规范、文档、配置等，也就是一整个 `Monorepo` 目录的所有内容。\n\n## 应用\n\n应用指的是一个完整的项目，一个项目可以包含多个应用，这些项目可以复用大仓内的代码、包、规范等。应用都被放置在 `apps` 目录下。每个应用都是独立的，可以单独运行、构建、测试、部署，可以引入不同的组件库等等。\n\n::: tip\n\n应用不限于前端应用，也可以是后端应用、移动端应用等，例如 `apps/backend-mock`就是一个后端服务。\n\n:::\n\n## 包\n\n包指的是一个独立的模块，可以是一个组件、一个工具、一个库等。包可以被多个应用引用，也可以被其他包引用。包都被放置在 `packages` 目录下。\n\n对于这些包，你可以把它看作是一个独立的 `npm` 包，使用方式与 `npm` 包一样。\n\n### 包引入\n\n在 `package.json` 中引入包：\n\n```json {3}\n{\n  \"dependencies\": {\n    \"@vben/utils\": \"workspace:*\"\n  }\n}\n```\n\n### 包使用\n\n在代码中引入包：\n\n```ts\nimport { isString } from '@vben/utils';\n```\n\n## 别名\n\n在项目中，你可以看到一些 `#` 开头的路径，例如： `#/api`、`#/views`, 这些路径都是别名，用于快速定位到某个目录。它不是通过 `vite` 的 `alias` 实现的，而是通过 `Node.js` 本身的 [subpath imports](https://nodejs.org/api/packages.html#subpath-imports) 原理。只需要在 `package.json` 中配置 `imports` 字段即可。\n\n```json {3}\n{\n  \"imports\": {\n    \"#/*\": \"./src/*\"\n  }\n}\n```\n\n为了 IDE 能够识别这些别名，我们还需要在`tsconfig.json`内配置：\n\n```json {5}\n{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"#/*\": [\"src/*\"]\n    }\n  }\n}\n```\n\n这样，你就可以在代码中使用别名了。\n"
  },
  {
    "path": "docs/src/guide/essentials/development.md",
    "content": "# 本地开发 {#development}\n\n::: tip 代码获取\n\n如果你还没有获取代码，可以先从 [快速开始](../introduction/quick-start.md) 处开始阅读文档。\n\n:::\n\n## 前置准备\n\n为了更好的开发体验，我们提供了一些工具配置、项目说明，以便于您更好的开发。\n\n### 需要掌握的基础知识\n\n本项目需要一定前端基础知识，请确保掌握 Vue 的基础知识，以便能处理一些常见的问题。建议在开发前先学一下以下内容，提前了解和学习这些知识，会对项目理解非常有帮助:\n\n- [Vue3](https://vuejs.org/)\n- [Tailwind CSS](https://tailwindcss.com/)\n- [TypeScript](https://www.typescriptlang.org/)\n- [Vue Router](https://router.vuejs.org/)\n- [Vitejs](https://vitejs.dev/)\n- [Pnpm](https://pnpm.io/)\n- [Turbo](https://turbo.build/)\n\n### 工具配置\n\n如果您使用的 IDE 是 [vscode](https://code.visualstudio.com/)（推荐）的话，可以安装以下工具来提高开发效率及代码格式化:\n\n- [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Vue 官方插件（必备）。\n- [Tailwind CSS](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) - Tailwindcss 提示插件。\n- [CSS Variable Autocomplete](https://marketplace.visualstudio.com/items?itemName=vunguyentuan.vscode-css-variables) - CSS 变量提示插件。\n- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Iconify 图标插件。\n- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) - i18n 插件。\n- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - 脚本代码检查。\n- [Oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) - Oxlint / Oxfmt 集成。\n- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS 格式检查。\n- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - 单词语法检查。\n- [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - `.env` 文件高亮。\n\n## Npm Scripts\n\nnpm 脚本是项目常见的配置，用于执行一些常见的任务，比如启动项目、打包项目等。以下的脚本都可以在项目根目录的 `package.json` 文件中找到。\n\n执行方式为：`pnpm run [script]` 或 `npm run [script]`。\n\n```json\n{\n  \"scripts\": {\n    // 构建项目\n    \"build\": \"cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build\",\n    // 构建项目并分析\n    \"build:analyze\": \"turbo build:analyze\",\n    // 构建本地 docker 镜像\n    \"build:docker\": \"./scripts/deploy/build-local-docker-image.sh\",\n    // 单独构建 web-antd 应用\n    \"build:antd\": \"pnpm run build --filter=@vben/web-antd\",\n    // 单独构建 web-antdv-next 应用\n    \"build:antdv-next\": \"pnpm run build --filter=@vben/web-antdv-next\",\n    // 单独构建文档\n    \"build:docs\": \"pnpm run build --filter=@vben/docs\",\n    // 单独构建 web-ele 应用\n    \"build:ele\": \"pnpm run build --filter=@vben/web-ele\",\n    // 单独构建 web-naive 应用\n    \"build:naive\": \"pnpm run build --filter=@vben/web-naive\",\n    // 单独构建 web-tdesign 应用\n    \"build:tdesign\": \"pnpm run build --filter=@vben/web-tdesign\",\n    // 单独构建 playground 应用\n    \"build:play\": \"pnpm run build --filter=@vben/playground\",\n    // changeset 版本管理\n    \"changeset\": \"pnpm exec changeset\",\n    // 检查项目各种问题\n    \"check\": \"pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell\",\n    // 检查循环引用\n    \"check:circular\": \"vsh check-circular\",\n    // 检查拼写\n    \"check:cspell\": \"cspell lint **/*.ts **/README.md .changeset/*.md --no-progress\",\n    // 检查依赖\n    \"check:dep\": \"vsh check-dep\",\n    // 检查类型\n    \"check:type\": \"turbo run typecheck\",\n    // 清理项目（删除 node_modules、dist、.turbo）等目录\n    \"clean\": \"node ./scripts/clean.mjs\",\n    // 提交代码\n    \"commit\": \"czg\",\n    // 启动项目（默认会运行整个仓库所有包的 dev 脚本）\n    \"dev\": \"turbo-run dev\",\n    // 启动 web-antd 应用\n    \"dev:antd\": \"pnpm -F @vben/web-antd run dev\",\n    // 启动 web-antdv-next 应用\n    \"dev:antdv-next\": \"pnpm -F @vben/web-antdv-next run dev\",\n    // 启动文档\n    \"dev:docs\": \"pnpm -F @vben/docs run dev\",\n    // 启动 web-ele 应用\n    \"dev:ele\": \"pnpm -F @vben/web-ele run dev\",\n    // 启动 web-naive 应用\n    \"dev:naive\": \"pnpm -F @vben/web-naive run dev\",\n    // 启动 web-tdesign 应用\n    \"dev:tdesign\": \"pnpm -F @vben/web-tdesign run dev\",\n    // 启动演示应用\n    \"dev:play\": \"pnpm -F @vben/playground run dev\",\n    // 格式化代码\n    \"format\": \"vsh lint --format\",\n    // lint 代码\n    \"lint\": \"vsh lint\",\n    // 依赖安装完成之后，执行所有包的 stub 脚本\n    \"postinstall\": \"pnpm -r run stub --if-present\",\n    // 只允许使用 pnpm\n    \"preinstall\": \"npx only-allow pnpm\",\n    // 安装 lefthook\n    \"prepare\": \"pnpm exec lefthook install\",\n    // 预览应用\n    \"preview\": \"turbo-run preview\",\n    // 包规范检查\n    \"publint\": \"vsh publint\",\n    // 删除所有的 node_modules、yarn.lock、package-lock.json，重新安装依赖\n    \"reinstall\": \"pnpm clean --del-lock && pnpm install\",\n    // 运行 e2e 测试\n    \"test:e2e\": \"turbo run test:e2e\",\n    // 运行 vitest 单元测试\n    \"test:unit\": \"vitest run --dom\",\n    // 更新项目依赖\n    \"update:deps\": \"npx taze -r -w\",\n    // changeset 生成提交集\n    \"version\": \"pnpm exec changeset version && pnpm install --no-frozen-lockfile\"\n  }\n}\n```\n\n## 本地运行项目\n\n如需本地运行文档，并进行调整，可以执行以下命令，执行该命令，你可以选择需要的应用进行开发：\n\n```bash\npnpm dev\n```\n\n如果你想直接运行某个应用，可以执行以下命令：\n\n运行 `web-antd` 应用：\n\n```bash\npnpm dev:antd\n```\n\n运行 `web-antdv-next` 应用：\n\n```bash\npnpm dev:antdv-next\n```\n\n运行 `web-naive` 应用：\n\n```bash\npnpm dev:naive\n```\n\n运行 `web-ele` 应用：\n\n```bash\npnpm dev:ele\n```\n\n运行 `web-tdesign` 应用：\n\n```bash\npnpm dev:tdesign\n```\n\n运行 `docs` 应用：\n\n```bash\npnpm dev:docs\n```\n\n## 区分构建环境\n\n在实际的业务开发中，通常会在构建时区分多种环境，如测试环境 `test`、生产环境 `build` 等。\n\n此时可以修改三个文件，在其中增加对应的脚本配置来达到区分生产环境的效果。\n\n以 `@vben/web-antd` 添加测试环境 `test` 为例：\n\n- `apps\\web-antd\\package.json`\n\n```json\n\"scripts\": {\n  \"build:prod\": \"pnpm vite build --mode production\",\n  \"build:test\": \"pnpm vite build --mode test\",\n  \"build:analyze\": \"pnpm vite build --mode analyze\",\n  \"dev\": \"pnpm vite --mode development\",\n  \"preview\": \"vite preview\",\n  \"typecheck\": \"vue-tsc --noEmit --skipLibCheck\"\n},\n```\n\n增加命令 `\"build:test\"`，并将原 `\"build\"` 改为 `\"build:prod\"` 以避免同时构建两个环境的包。\n\n- `package.json`\n\n```json\n\"scripts\": {\n    \"build\": \"cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build\",\n    \"build:analyze\": \"turbo build:analyze\",\n    \"build:antd\": \"pnpm run build --filter=@vben/web-antd\",\n    \"build-test:antd\": \"pnpm run build --filter=@vben/web-antd build:test\",\n\n    ······\n}\n```\n\n在根目录 `package.json` 中加入构建测试环境的命令。\n\n- `turbo.json`\n\n```json\n\"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\n        \"dist/**\",\n        \"dist.zip\",\n        \".vitepress/dist.zip\",\n        \".vitepress/dist/**\"\n      ]\n    },\n\n    \"build-test:antd\": {\n      \"dependsOn\": [\"@vben/web-antd#build:test\"],\n      \"outputs\": [\"dist/**\"]\n    },\n\n    \"@vben/web-antd#build:test\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"dist/**\"]\n    },\n\n    ······\n```\n\n在 `turbo.json` 中加入相关依赖的命令。\n\n## 公共静态资源\n\n项目中需要使用到的公共静态资源，如：图片、静态 HTML 等，需要在开发中通过 `src=\"/xxx.png\"` 直接引入的。\n\n需要将资源放在对应项目的 `public/static` 目录下。引入的路径为：`src=\"/static/xxx.png\"`。\n\n## DevTools\n\n项目内置了 [Vue DevTools](https://github.com/vuejs/devtools-next) 插件，可以在开发过程中使用。默认关闭，可在 `.env.development` 内开启，并重新运行项目即可：\n\n```bash\nVITE_DEVTOOLS=true\n```\n\n开启后，项目运行会在页面底部显示一个 Vue DevTools 的图标，点击即可打开 DevTools。\n\n![Vue DevTools](/guide/devtools.png)\n\n## 本地运行文档\n\n如需本地运行文档，并进行调整，可以执行以下命令：\n\n```bash\npnpm dev:docs\n```\n\n## 问题解决\n\n如果你在使用过程中遇到依赖相关的问题，可以尝试以下重新安装依赖：\n\n```bash\n# 请在项目根目录下执行\n# 该命令会删除整个仓库所有的 node_modules、yarn.lock、package-lock.json 后\n# 再进行依赖重新安装（安装速度会明显变慢）。\npnpm reinstall\n```\n"
  },
  {
    "path": "docs/src/guide/essentials/external-module.md",
    "content": "# 外部模块\n\n除了项目默认引入的外部模块，有时我们还需要引入其他外部模块。我们以 [ant-design-vue](https://antdv.com/components/overview) 为例：\n\n## 安装依赖\n\n::: tip 安装依赖到指定包\n\n- 由于项目采用了 [pnpm](https://pnpm.io/) 作为包管理工具，所以我们需要使用 `pnpm` 命令来安装依赖。\n- 通过采用了 Monorepo 模块来管理项目，所以我们需要在指定包下安装依赖。安装依赖前请确保已经进入到指定包目录下。\n\n:::\n\n```bash\n# cd /path/to/your/package\npnpm add ant-design-vue\n```\n\n## 使用\n\n### 全局引入\n\n```ts\nimport { createApp } from 'vue';\nimport Antd from 'ant-design-vue';\nimport App from './App';\nimport 'ant-design-vue/dist/reset.css';\n\nconst app = createApp(App);\n\napp.use(Antd).mount('#app');\n```\n\n#### 使用\n\n```vue\n<template>\n  <a-button>text</a-button>\n</template>\n```\n\n### 局部引入\n\n```vue\n<script setup lang=\"ts\">\nimport { Button } from 'ant-design-vue';\n</script>\n\n<template>\n  <Button>text</Button>\n</template>\n```\n\n::: warning 注意\n\n- 如果组件有依赖样式，则需要再引入样式文件\n\n:::\n"
  },
  {
    "path": "docs/src/guide/essentials/icons.md",
    "content": "# 图标\n\n::: tip 关于图标的管理\n\n- 项目的图标主要由`@vben/icons`包提供，建议统一在该包内部管理，以便于统一管理和维护。\n- 如果你使用的是 `Vscode`，推荐安装 [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) 插件，可以方便的查找和使用图标。\n\n:::\n\n项目中有以下多种图标使用方式，可以根据实际情况选择使用：\n\n## Iconify 图标 <Badge text=\"推荐\" type=\"tip\"/>\n\n集成了 [iconify](https://github.com/iconify/iconify) 图标库\n\n### 新增\n\n可在 `packages/icons/src/iconify` 目录下新增图标：\n\n```ts\n// packages/icons/src/iconify/index.ts\nimport { createIconifyIcon } from '@vben-core/icons';\n\nexport const MdiKeyboardEsc = createIconifyIcon('mdi:keyboard-esc');\n```\n\n### 使用\n\n```vue\n<script setup lang=\"ts\">\nimport { MdiKeyboardEsc } from '@vben/icons';\n</script>\n\n<template>\n  <!-- 一个宽高为20px的图标 -->\n  <MdiKeyboardEsc class=\"size-5\" />\n</template>\n```\n\n## Svg 图标 <Badge text=\"推荐\" type=\"tip\"/>\n\n没有采用 Svg Sprite 的方式，而是直接引入 Svg 图标，\n\n### 新增\n\n可以在 `packages/icons/src/svg/icons` 目录下新增图标文件`test.svg`, 然后在 `packages/icons/src/svg/index.ts` 中引入：\n\n```ts\n// packages/icons/src/svg/index.ts\nimport { createIconifyIcon } from '@vben-core/icons';\n\nconst SvgTestIcon = createIconifyIcon('svg:test');\n\nexport { SvgTestIcon };\n```\n\n### 使用\n\n```vue\n<script setup lang=\"ts\">\nimport { SvgTestIcon } from '@vben/icons';\n</script>\n\n<template>\n  <!-- 一个宽高为20px的图标 -->\n  <SvgTestIcon class=\"size-5\" />\n</template>\n```\n\n## Tailwind CSS 图标\n\n### 使用\n\n直接添加 Tailwind CSS 的图标类名即可使用，图标类名可查看 [iconify](https://github.com/iconify/iconify) ：\n\n```vue\n<span class=\"icon-[mdi--ab-testing]\"></span>\n```\n"
  },
  {
    "path": "docs/src/guide/essentials/route.md",
    "content": "---\noutline: deep\n---\n\n# 路由和菜单\n\n在项目中，框架提供了一套基础的路由系统，并**根据路由文件自动生成对应的菜单结构**。\n\n## 路由类型\n\n路由分为核心路由、静态路由和动态路由，核心路由是框架内置的路由，包含了根路由、登录路由、404路由等；静态路由是在项目启动时就已经确定的路由；动态路由一般是在用户登录后，根据用户的权限动态生成的路由。\n\n静态路由和动态路由都会走权限控制，可以通过配置路由的 `meta` 属性中的 `authority` 字段来控制权限，可以参考[路由权限控制](https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/router/routes/modules/demos.ts)。\n\n### 核心路由\n\n核心路由是框架内置的路由，包含了根路由、登录路由、404路由等，核心路由的配置在应用下 `src/router/routes/core` 目录下\n\n::: tip\n\n核心路由主要用于框架的基础功能，因此不建议将业务相关的路由放在核心路由中，推荐将业务相关的路由放在静态路由或动态路由中。\n\n:::\n\n### 静态路由\n\n静态路由的配置在应用下 `src/router/routes/index` 目录下，打开注释的文件内容:\n\n::: tip\n\n权限控制是通过路由的 `meta` 属性中的 `authority` 字段来控制的，如果你的页面项目不需要权限控制，可以不设置 `authority` 字段。\n\n:::\n\n```ts\n// 有需要可以自行打开注释，并创建文件夹\n// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); // [!code --]\nconst staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); // [!code ++]\n/** 动态路由 */\nconst dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);\n\n/** 外部路由列表，访问这些页面可以不需要Layout，可能用于内嵌在别的系统 */\n// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles) // [!code --]\nconst externalRoutes: RouteRecordRaw[] = []; // [!code --]\nconst externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); // [!code ++]\n```\n\n### 动态路由\n\n动态路由的配置在对应应用 `src/router/routes/modules` 目录下，这个目录下存放了所有的路由文件。每个文件的内容格式如下，与 Vue Router 的路由配置格式一致，以下为二级路由和多级路由的配置。\n\n## 路由定义\n\n静态路由与动态路由的配置方式一致，以下为二级路由和多级路由的配置：\n\n### 二级路由\n\n::: details 二级路由示例代码\n\n```ts\nimport type { RouteRecordRaw } from 'vue-router';\n\nimport { VBEN_LOGO_URL } from '@vben/constants';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      badgeType: 'dot',\n      badgeVariants: 'destructive',\n      icon: VBEN_LOGO_URL,\n      order: 9999,\n      title: $t('page.vben.title'),\n    },\n    name: 'VbenProject',\n    path: '/vben-admin',\n    redirect: '/vben-admin/about',\n    children: [\n      {\n        name: 'VbenAbout',\n        path: '/vben-admin/about',\n        component: () => import('#/views/_core/about/index.vue'),\n        meta: {\n          badgeType: 'dot',\n          badgeVariants: 'destructive',\n          icon: 'lucide:copyright',\n          title: $t('page.vben.about'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n```\n\n:::\n\n### 多级路由\n\n::: tip\n\n- 如果没有特殊情况，父级路由的 `redirect` 属性，不需要指定，默认会指向第一个子路由。\n\n:::\n\n::: details 多级路由示例代码\n\n```ts\nimport type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'ic:baseline-view-in-ar',\n      keepAlive: true,\n      order: 1000,\n      title: $t('demos.title'),\n    },\n    name: 'Demos',\n    path: '/demos',\n    redirect: '/demos/access',\n    children: [\n      // 嵌套菜单\n      {\n        meta: {\n          icon: 'ic:round-menu',\n          title: $t('demos.nested.title'),\n        },\n        name: 'NestedDemos',\n        path: '/demos/nested',\n        redirect: '/demos/nested/menu1',\n        children: [\n          {\n            name: 'Menu1Demo',\n            path: '/demos/nested/menu1',\n            component: () => import('#/views/demos/nested/menu-1.vue'),\n            meta: {\n              icon: 'ic:round-menu',\n              keepAlive: true,\n              title: $t('demos.nested.menu1'),\n            },\n          },\n          {\n            name: 'Menu2Demo',\n            path: '/demos/nested/menu2',\n            meta: {\n              icon: 'ic:round-menu',\n              keepAlive: true,\n              title: $t('demos.nested.menu2'),\n            },\n            redirect: '/demos/nested/menu2/menu2-1',\n            children: [\n              {\n                name: 'Menu21Demo',\n                path: '/demos/nested/menu2/menu2-1',\n                component: () => import('#/views/demos/nested/menu-2-1.vue'),\n                meta: {\n                  icon: 'ic:round-menu',\n                  keepAlive: true,\n                  title: $t('demos.nested.menu2_1'),\n                },\n              },\n            ],\n          },\n          {\n            name: 'Menu3Demo',\n            path: '/demos/nested/menu3',\n            meta: {\n              icon: 'ic:round-menu',\n              title: $t('demos.nested.menu3'),\n            },\n            redirect: '/demos/nested/menu3/menu3-1',\n            children: [\n              {\n                name: 'Menu31Demo',\n                path: 'menu3-1',\n                component: () => import('#/views/demos/nested/menu-3-1.vue'),\n                meta: {\n                  icon: 'ic:round-menu',\n                  keepAlive: true,\n                  title: $t('demos.nested.menu3_1'),\n                },\n              },\n              {\n                name: 'Menu32Demo',\n                path: 'menu3-2',\n                meta: {\n                  icon: 'ic:round-menu',\n                  title: $t('demos.nested.menu3_2'),\n                },\n                redirect: '/demos/nested/menu3/menu3-2/menu3-2-1',\n                children: [\n                  {\n                    name: 'Menu321Demo',\n                    path: '/demos/nested/menu3/menu3-2/menu3-2-1',\n                    component: () =>\n                      import('#/views/demos/nested/menu-3-2-1.vue'),\n                    meta: {\n                      icon: 'ic:round-menu',\n                      keepAlive: true,\n                      title: $t('demos.nested.menu3_2_1'),\n                    },\n                  },\n                ],\n              },\n            ],\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport default routes;\n```\n\n:::\n\n## 新增页面\n\n新增一个页面，你只需要添加一个路由及对应的页面组件即可。\n\n### 添加路由\n\n在对应的路由文件中添加一个路由对象，如下：\n\n```ts\nimport type { RouteRecordRaw } from 'vue-router';\n\nimport { VBEN_LOGO_URL } from '@vben/constants';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'mdi:home',\n      title: $t('page.home.title'),\n    },\n    name: 'Home',\n    path: '/home',\n    redirect: '/home/index',\n    children: [\n      {\n        name: 'HomeIndex',\n        path: '/home/index',\n        component: () => import('#/views/home/index.vue'),\n        meta: {\n          icon: 'mdi:home',\n          title: $t('page.home.index'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n```\n\n### 添加页面组件\n\n在`#/views/home/`下，新增一个`index.vue`文件，如下：\n\n```vue\n<template>\n  <div>\n    <h1>home page</h1>\n  </div>\n</template>\n```\n\n### 验证\n\n到这里页面已添加完成，访问 `http://localhost:5555/home/index` 出现对应的页面即可。\n\n## 路由配置\n\n路由配置项主要在对象路由的 `meta` 属性中，以下为常用的配置项：\n\n```ts {5-8}\nconst routes = [\n  {\n    name: 'HomeIndex',\n    path: '/home/index',\n    meta: {\n      icon: 'mdi:home',\n      title: $t('page.home.index'),\n    },\n  },\n];\n```\n\n::: details 路由Meta配置类型定义\n\n```ts\ninterface RouteMeta {\n  /**\n   * 激活图标（菜单）\n   */\n  activeIcon?: string;\n  /**\n   * 当前激活的菜单，有时候不想激活现有菜单，需要激活父级菜单时使用\n   */\n  activePath?: string;\n  /**\n   * 是否固定标签页\n   * @default false\n   */\n  affixTab?: boolean;\n  /**\n   * 固定标签页的顺序\n   * @default 0\n   */\n  affixTabOrder?: number;\n  /**\n   * 需要特定的角色标识才可以访问\n   * @default []\n   */\n  authority?: string[];\n  /**\n   * 徽标\n   */\n  badge?: string;\n  /**\n   * 徽标类型\n   */\n  badgeType?: 'dot' | 'normal';\n  /**\n   * 徽标颜色\n   */\n  badgeVariants?:\n    | 'default'\n    | 'destructive'\n    | 'primary'\n    | 'success'\n    | 'warning'\n    | string;\n  /**\n   * 路由的完整路径作为key（默认true）\n   */\n  fullPathKey?: boolean;\n  /**\n   * 当前路由的子级在菜单中不展现\n   * @default false\n   */\n  hideChildrenInMenu?: boolean;\n  /**\n   * 当前路由在面包屑中不展现\n   * @default false\n   */\n  hideInBreadcrumb?: boolean;\n  /**\n   * 当前路由在菜单中不展现\n   * @default false\n   */\n  hideInMenu?: boolean;\n  /**\n   * 当前路由在标签页不展现\n   * @default false\n   */\n  hideInTab?: boolean;\n  /**\n   * 图标（菜单/tab）\n   */\n  icon?: string;\n  /**\n   * iframe 地址\n   */\n  iframeSrc?: string;\n  /**\n   * 忽略权限，直接可以访问\n   * @default false\n   */\n  ignoreAccess?: boolean;\n  /**\n   * 开启KeepAlive缓存\n   */\n  keepAlive?: boolean;\n  /**\n   * 外链-跳转路径\n   */\n  link?: string;\n  /**\n   * 路由是否已经加载过\n   */\n  loaded?: boolean;\n  /**\n   * 标签页最大打开数量\n   * @default false\n   */\n  maxNumOfOpenTab?: number;\n  /**\n   * 菜单可以看到，但是访问会被重定向到403\n   */\n  menuVisibleWithForbidden?: boolean;\n  /**\n   * 当前路由不使用基础布局（仅在顶级生效）\n   */\n  noBasicLayout?: boolean;\n  /**\n   * 在新窗口打开\n   */\n  openInNewWindow?: boolean;\n  /**\n   * 用于路由->菜单排序\n   */\n  order?: number;\n  /**\n   * 菜单所携带的参数\n   */\n  query?: Recordable;\n  /**\n   * 标题名称\n   */\n  title: string;\n}\n```\n\n:::\n\n### title\n\n- 类型：`string`\n- 默认值：`''`\n\n用于配置页面的标题，会在菜单和标签页中显示。一般会配合国际化使用。\n\n### icon\n\n- 类型：`string`\n- 默认值：`''`\n\n用于配置页面的图标，会在菜单和标签页中显示。一般会配合图标库使用，如果是`http`链接，会自动加载图片。\n\n### activeIcon\n\n- 类型：`string`\n- 默认值：`''`\n\n用于配置页面的激活图标，会在菜单中显示。一般会配合图标库使用，如果是`http`链接，会自动加载图片。\n\n### keepAlive\n\n- 类型：`boolean`\n- 默认值：`false`\n\n用于配置页面是否开启缓存，开启后页面会缓存，不会重新加载，仅在标签页启用时有效。\n\n### hideInMenu\n\n- 类型：`boolean`\n- 默认值：`false`\n\n用于配置页面是否在菜单中隐藏，隐藏后页面不会在菜单中显示。\n\n### hideInTab\n\n- 类型：`boolean`\n- 默认值：`false`\n\n用于配置页面是否在标签页中隐藏，隐藏后页面不会在标签页中显示。\n\n### hideInBreadcrumb\n\n- 类型：`boolean`\n- 默认值：`false`\n\n用于配置页面是否在面包屑中隐藏，隐藏后页面不会在面包屑中显示。\n\n### hideChildrenInMenu\n\n- 类型：`boolean`\n- 默认值：`false`\n\n用于配置页面的子页面是否在菜单中隐藏，隐藏后子页面不会在菜单中显示。\n\n### authority\n\n- 类型：`string[]`\n- 默认值：`[]`\n\n用于配置页面的权限，只有拥有对应权限的用户才能访问页面，不配置则不需要权限。\n\n### badge\n\n- 类型：`string`\n- 默认值：`''`\n\n用于配置页面的徽标，会在菜单显示。\n\n### badgeType\n\n- 类型：`'dot' | 'normal'`\n- 默认值：`'normal'`\n\n用于配置页面的徽标类型，`dot` 为小红点，`normal` 为文本。\n\n### badgeVariants\n\n- 类型：`'default' | 'destructive' | 'primary' | 'success' | 'warning' | string`\n- 默认值：`'success'`\n\n用于配置页面的徽标颜色。\n\n### fullPathKey\n\n- 类型：`boolean`\n- 默认值：`true`\n\n是否将路由的完整路径作为tab key（默认true）\n\n### activePath\n\n- 类型：`string`\n- 默认值：`''`\n\n用于配置当前激活的菜单，有时候页面没有显示在菜单内，需要激活父级菜单时使用。\n\n### affixTab\n\n- 类型：`boolean`\n- 默认值：`false`\n\n用于配置页面是否固定标签页，固定后页面不可关闭。\n\n### affixTabOrder\n\n- 类型：`number`\n- 默认值：`0`\n\n用于配置页面固定标签页的排序, 采用升序排序。\n\n### iframeSrc\n\n- 类型：`string`\n- 默认值：`''`\n\n用于配置内嵌页面的 `iframe` 地址，设置后会在当前页面内嵌对应的页面。\n\n### ignoreAccess\n\n- 类型：`boolean`\n- 默认值：`false`\n\n用于配置页面是否忽略权限，直接可以访问。\n\n### link\n\n- 类型：`string`\n- 默认值：`''`\n\n用于配置外链跳转路径，会在新窗口打开。\n\n### maxNumOfOpenTab\n\n- 类型：`number`\n- 默认值：`-1`\n\n用于配置标签页最大打开数量，设置后会在打开新标签页时自动关闭最早打开的标签页(仅在打开同名标签页时生效)。\n\n### menuVisibleWithForbidden\n\n- 类型：`boolean`\n- 默认值：`false`\n\n用于配置页面在菜单可以看到，但是访问会被重定向到403。\n\n### openInNewWindow\n\n- 类型：`boolean`\n- 默认值：`false`\n\n设置为 `true` 时，会在新窗口打开页面。\n\n### order\n\n- 类型：`number`\n- 默认值：`0`\n\n用于配置页面的排序，用于路由到菜单排序。\n\n_注意:_ 排序仅针对一级菜单有效，二级菜单的排序需要在对应的一级菜单中按代码顺序设置。\n\n### query\n\n- 类型：`Recordable`\n- 默认值：`{}`\n\n用于配置页面的菜单参数，会在菜单中传递给页面。\n\n### noBasicLayout\n\n- 类型：`boolean`\n- 默认值：`false`\n\n用于配置当前路由不使用基础布局，仅在顶级时生效。默认情况下，所有的路由都会被包裹在基础布局中（包含顶部以及侧边等导航部件），如果你的页面不需要这些部件，可以设置 `noBasicLayout` 为 `true`。\n\n### domCached\n\n- 类型：`boolean`\n- 默认值：`false`\n\n用于配置当前路由是否要将route对应dom元素缓存起来。对于一些复杂页面切换tab浏览器回流/重绘会导致卡顿， `domCached` 设为 `true`可解决该问题，但是也有代价：1、内存占用升高 2、vue的部分生命周期不会触发\n\n## 路由刷新\n\n路由刷新方式如下：\n\n```vue\n<script setup lang=\"ts\">\nimport { useRefresh } from '@vben/hooks';\n\nconst { refresh } = useRefresh();\n\n// 刷新当前路由\nrefresh();\n</script>\n```\n\n## 标签页与路由控制\n\n在某些场景下，需要单个路由打开多个标签页，或者修改路由的query不打开新的标签页\n\n每个标签页Tab使用唯一的key标识，设置Tab key有三种方式，优先级由高到低：\n\n- 使用路由query参数pageKey\n\n```vue\n<script setup lang=\"ts\">\nimport { useRouter } from 'vue-router';\n// 跳转路由\nconst router = useRouter();\nrouter.push({\n  path: 'path',\n  query: {\n    pageKey: 'key',\n  },\n});\n```\n\n- 路由的完整路径作为key\n\n`meta` 属性中的 `fullPathKey`不为false，则使用路由`fullPath`作为key\n\n- 路由的path作为key\n\n`meta` 属性中的 `fullPathKey`为false，则使用路由`path`作为key\n"
  },
  {
    "path": "docs/src/guide/essentials/server.md",
    "content": "# 服务端交互与数据Mock\n\n::: tip 说明\n\n本文档介绍如何在开发环境下使用 Mock 数据和与服务端进行交互，涉及到的技术有：\n\n- [Nitro](https://nitro.unjs.io/) 轻量级后端服务器，可部署在任何地方，项目用作于 Mock 服务器。\n- [axios](https://axios-http.com/docs/intro) 用于发送 HTTP 请求与服务端进行交互。\n\n:::\n\n## 开发环境交互\n\n如果前端应用和后端接口服务器没有运行在同一个主机上，你需要在开发环境下将接口请求代理到接口服务器。如果是同一个主机，可以直接请求具体的接口地址。\n\n### 本地开发跨域配置\n\n::: tip 提示\n\n本地开发跨域配置项目已经配置好了，如有其他需求，可以自行增加或者调整配置。\n\n:::\n\n#### 配置本地开发接口地址\n\n在项目根目录下的 `.env.development` 文件中配置接口地址，这里配置为 `/api`：\n\n```bash\nVITE_GLOB_API_URL=/api\n```\n\n#### 配置开发服务器代理\n\n开发环境时候，如果需要处理跨域，接口地址在对应的应用目录下的 `vite.config.mts` 文件中配置：\n\n```ts{8-16}\n// apps/web-antd/vite.config.mts\nimport { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    vite: {\n      server: {\n        proxy: {// [!code focus:11]\n          '/api': {\n            changeOrigin: true,\n            rewrite: (path) => path.replace(/^\\/api/, ''),\n            // mock代理目标地址\n            target: 'http://localhost:5320/api',\n            ws: true,\n          },\n        },\n      },\n    },\n  };\n});\n```\n\n#### 接口请求\n\n根据上面的配置，我们可以在前端项目中使用 `/api` 作为接口请求的前缀，例如：\n\n```ts\nimport axios from 'axios';\n\naxios.get('/api/user').then((res) => {\n  console.log(res);\n});\n```\n\n此时，请求会被代理到 `http://localhost:5320/api/user`。\n\n::: warning 注意\n\n从浏览器控制台的 Network 看，请求是 `http://localhost:5555/api/user`, 这是因为 proxy 配置不会改变本地请求的 url。\n\n:::\n\n### 没有跨域时的配置\n\n如果没有跨域问题，可以直接忽略 [配置开发服务器代理](./server.md#配置开发服务器代理) 配置，直接将接口地址设置在 `VITE_GLOB_API_URL`\n\n在项目根目录下的 `.env.development` 文件中配置接口地址：\n\n```bash\nVITE_GLOB_API_URL=https://mock-napi.vben.pro/api\n```\n\n## 生产环境交互\n\n### 接口地址配置\n\n在项目根目录下的 `.env.production` 文件中配置接口地址：\n\n```bash\nVITE_GLOB_API_URL=https://mock-napi.vben.pro/api\n```\n\n::: tip 打包如何动态修改接口地址\n\n`.env` 文件内的 `VITE_GLOB_*` 开头的变量会在打包的时候注入 `_app.config.js` 文件内。在 `dist/_app.config.js` 修改相应的接口地址后刷新页面即可，不需要在根据不同环境打包多次，一次打包可以用于多个不同接口环境的部署。\n\n:::\n\n### 跨域处理\n\n生产环境如果出现跨域问题，可以使用 `nginx` 代理接口地址 或者后台开启 `cors` 进行处理即可（可参考mock服务）。\n\n## 接口请求配置\n\n项目中默认自带了基于 `axios` 封装的基础的请求配置，核心由 `@vben/request` 包提供。项目没有过多的封装，只是简单的封装了一些常用的配置，如有其他需求，可以自行增加或者调整配置。针对不同的app，可能是用到了不同的组件库以及`store`,所以在应用目录下的`src/api/request.ts`文件夹下，有对应的请求配置文件,如`web-antd`项目下的`src/api/request.ts`文件,可以根据自己的需求进行配置。\n\n### 扩展的配置\n\n除了基础的Axios配置外，扩展了部分配置。\n\n```ts\ntype ExtendOptions<T = any> = {\n  /**\n   * 参数序列化方式。预置了几种针对数组的序列化类型\n   * - brackets: ids[]=1&ids[]=2&ids[]=3\n   * - comma: ids=1,2,3\n   * - indices: ids[0]=1&ids[1]=2&ids[2]=3\n   * - repeat: ids=1&ids=2&ids=3\n   * @default 'brackets'\n   */\n  paramsSerializer?:\n    | 'brackets'\n    | 'comma'\n    | 'indices'\n    | 'repeat'\n    | AxiosRequestConfig<T>['paramsSerializer'];\n  /**\n   * 响应数据的返回方式。\n   * - raw: 原始的AxiosResponse，包括headers、status等，不做是否成功请求的检查。\n   * - body: 返回响应数据的BODY部分（只会根据status检查请求是否成功，忽略对code的判断，这种情况下应由调用方检查请求是否成功）。\n   * - data: 解构响应的BODY数据，只返回其中的data节点数据（会检查status和code是否为成功状态）。\n   */\n  responseReturn?: 'body' | 'data' | 'raw';\n};\n```\n\n### 请求示例\n\n#### GET 请求\n\n```ts\nimport { requestClient } from '#/api/request';\n\nexport async function getUserInfoApi() {\n  return requestClient.get<UserInfo>('/user/info');\n}\n```\n\n#### POST/PUT 请求\n\n```ts\nimport { requestClient } from '#/api/request';\n\nexport async function saveUserApi(user: UserInfo) {\n  return requestClient.post<UserInfo>('/user', user);\n}\n\nexport async function saveUserApi(user: UserInfo) {\n  return requestClient.put<UserInfo>('/user', user);\n}\n\nexport async function saveUserApi(user: UserInfo) {\n  const url = user.id ? `/user/${user.id}` : '/user/';\n  return requestClient.request<UserInfo>(url, {\n    data: user,\n    // 或者 PUT\n    method: user.id ? 'PUT' : 'POST',\n  });\n}\n```\n\n#### DELETE 请求\n\n```ts\nimport { requestClient } from '#/api/request';\n\nexport async function deleteUserApi(userId: number) {\n  return requestClient.delete<boolean>(`/user/${userId}`);\n}\n```\n\n### 请求配置\n\n应用内的`src/api/request.ts`可以根据自己应用的情况的需求进行配置：\n\n```ts\n/**\n * 该文件可自行根据业务逻辑进行调整\n */\nimport type { HttpResponse } from '@vben/request';\n\nimport { useAppConfig } from '@vben/hooks';\nimport { preferences } from '@vben/preferences';\nimport {\n  authenticateResponseInterceptor,\n  errorMessageResponseInterceptor,\n  RequestClient,\n} from '@vben/request';\nimport { useAccessStore } from '@vben/stores';\n\nimport { message } from 'ant-design-vue';\n\nimport { useAuthStore } from '#/store';\n\nimport { refreshTokenApi } from './core';\n\nconst { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n\nfunction createRequestClient(baseURL: string) {\n  const client = new RequestClient({\n    baseURL,\n  });\n\n  /**\n   * 重新认证逻辑\n   */\n  async function doReAuthenticate() {\n    console.warn('Access token or refresh token is invalid or expired. ');\n    const accessStore = useAccessStore();\n    const authStore = useAuthStore();\n    accessStore.setAccessToken(null);\n    if (\n      preferences.app.loginExpiredMode === 'modal' &&\n      accessStore.isAccessChecked\n    ) {\n      accessStore.setLoginExpired(true);\n    } else {\n      await authStore.logout();\n    }\n  }\n\n  /**\n   * 刷新token逻辑\n   */\n  async function doRefreshToken() {\n    const accessStore = useAccessStore();\n    const resp = await refreshTokenApi();\n    const newToken = resp.data;\n    accessStore.setAccessToken(newToken);\n    return newToken;\n  }\n\n  function formatToken(token: null | string) {\n    return token ? `Bearer ${token}` : null;\n  }\n\n  // 请求头处理\n  client.addRequestInterceptor({\n    fulfilled: async (config) => {\n      const accessStore = useAccessStore();\n\n      config.headers.Authorization = formatToken(accessStore.accessToken);\n      config.headers['Accept-Language'] = preferences.app.locale;\n      return config;\n    },\n  });\n\n  // 处理返回的响应数据格式。会根据responseReturn指定的类型返回对应的数据\n  client.addResponseInterceptor(\n    defaultResponseInterceptor({\n      // 指定接口返回的数据中的 code 字段名\n      codeField: 'code',\n      // 指定接口返回的数据中装载了主要数据的字段名\n      dataField: 'data',\n      // 请求成功的 code 值，如果接口返回的 code 等于 successCode 则会认为是成功的请求\n      successCode: 0,\n    }),\n  );\n\n  // token过期的处理\n  client.addResponseInterceptor(\n    authenticateResponseInterceptor({\n      client,\n      doReAuthenticate,\n      doRefreshToken,\n      enableRefreshToken: preferences.app.enableRefreshToken,\n      formatToken,\n    }),\n  );\n\n  // 通用的错误处理,如果没有进入上面的错误处理逻辑，就会进入这里\n  client.addResponseInterceptor(\n    errorMessageResponseInterceptor((msg: string, error) => {\n      // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理，根据不同的 code 做不同的提示，而不是直接使用 message.error 提示 msg\n      // 当前mock接口返回的错误字段是 error 或者 message\n      const responseData = error?.response?.data ?? {};\n      const errorMessage = responseData?.error ?? responseData?.message ?? '';\n      // 如果没有错误信息，则会根据状态码进行提示\n      message.error(errorMessage || msg);\n    }),\n  );\n\n  return client;\n}\n\nexport const requestClient = createRequestClient(apiURL);\n\nexport const baseRequestClient = new RequestClient({ baseURL: apiURL });\n```\n\n### 多个接口地址\n\n只需要创建多个 `requestClient` 即可，如：\n\n```ts\nconst { apiURL, otherApiURL } = useAppConfig(\n  import.meta.env,\n  import.meta.env.PROD,\n);\n\nexport const requestClient = createRequestClient(apiURL);\n\nexport const otherRequestClient = createRequestClient(otherApiURL);\n```\n\n## 刷新Token\n\n项目中默认提供了刷新 Token 的逻辑，只需要按照下面的配置即可开启：\n\n- 确保当前启用了刷新 Token 的配置\n\n调整对应应用目录下的`preferences.ts`，确保`enableRefreshToken='true'`。\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    enableRefreshToken: true,\n  },\n});\n```\n\n在 `src/api/request.ts` 中配置 `doRefreshToken` 方法即可:\n\n```ts\n// 这里调整为你的token格式\nfunction formatToken(token: null | string) {\n  return token ? `Bearer ${token}` : null;\n}\n\n/**\n * 刷新token逻辑\n */\nasync function doRefreshToken() {\n  const accessStore = useAccessStore();\n  // 这里调整为你的刷新token接口\n  const resp = await refreshTokenApi();\n  const newToken = resp.data;\n  accessStore.setAccessToken(newToken);\n  return newToken;\n}\n```\n\n## 数据 Mock\n\n::: tip 生产环境 Mock\n\n新版本不再支持生产环境 mock，请使用真实接口。\n\n:::\n\nMock 数据是前端开发过程中必不可少的一环，是分离前后端开发的关键链路。通过预先跟服务器端约定好的接口，模拟请求数据甚至逻辑，能够让前端开发独立自主，不会被服务端的开发进程所阻塞。\n\n项目使用 [Nitro](https://nitro.unjs.io/) 来进行本地 mock 数据处理。其原理是本地额外启动一个后端服务，是一个真实的后端服务，可以处理请求，返回数据。\n\n### Nitro 使用\n\nMock 服务代码位于`apps/backend-mock`目录下，无需手动启动，已经集成在项目中，只需要在项目根目录下运行`pnpm dev`即可，运行成功之后，控制台会打印 `http://localhost:5320/api`, 访问该地址即可查看 mock 服务。\n\n[Nitro](https://nitro.unjs.io/) 语法简单，可以根据自己的需求进行配置及开发，具体配置可以查看 [Nitro 文档](https://nitro.unjs.io/)。\n\n## 关闭 Mock 服务\n\nmock的本质是一个真实的后端服务，如果不需要 mock 服务，可以在项目根目录下的 `.env.development` 文件中配置 `VITE_NITRO_MOCK=false` 即可关闭 mock 服务。\n\n```bash\n# .env.development\nVITE_NITRO_MOCK=false\n```\n"
  },
  {
    "path": "docs/src/guide/essentials/settings.md",
    "content": "# 配置\n\n## 环境变量配置\n\n项目的环境变量配置位于应用目录下的 `.env`、`.env.development`、`.env.production`。\n\n规则与 [Vite Env Variables and Modes](https://vitejs.dev/guide/env-and-mode.html) 一致。格式如下：\n\n```bash\n.env                # 在所有的环境中被载入\n.env.local          # 在所有的环境中被载入，但会被 git 忽略\n.env.[mode]         # 只在指定的模式中被载入\n.env.[mode].local   # 只在指定的模式中被载入，但会被 git 忽略\n```\n\n::: tip\n\n- 只有以 `VITE_` 开头的变量会被嵌入到客户端侧的包中，你可以在项目代码中这样访问它们：\n\n  ```ts\n  console.log(import.meta.env.VITE_PROT);\n  ```\n\n- 以 `VITE_GLOB_*` 开头的的变量，在打包的时候，会被加入 `_app.config.js`配置文件当中.\n\n:::\n\n## 环境配置说明\n\n::: code-group\n\n```bash [.env]\n# 应用标题\nVITE_APP_TITLE=Vben Admin\n\n# 应用命名空间，用于缓存、store等功能的前缀，确保隔离\nVITE_APP_NAMESPACE=vben-web-antd\n```\n\n```bash [.env.development]\n# 端口号\nVITE_PORT=5555\n\n# 资源公共路径,需要以 / 开头和结尾\nVITE_BASE=/\n\n# 接口地址\nVITE_GLOB_API_URL=/api\n\n# 是否开启 Nitro Mock服务，true 为开启，false 为关闭\nVITE_NITRO_MOCK=true\n\n# 是否打开 devtools，true 为打开，false 为关闭\nVITE_DEVTOOLS=true\n\n# 是否注入全局loading\nVITE_INJECT_APP_LOADING=true\n\n```\n\n```bash [.env.production]\n# 资源公共路径,需要以 / 开头和结尾\nVITE_BASE=/\n\n# 接口地址\nVITE_GLOB_API_URL=https://mock-napi.vben.pro/api\n\n# 是否开启压缩，可以设置为 none, brotli, gzip\nVITE_COMPRESS=gzip\n\n# 是否开启 PWA\nVITE_PWA=false\n\n# vue-router 的模式\nVITE_ROUTER_HISTORY=hash\n\n# 是否注入全局loading\nVITE_INJECT_APP_LOADING=true\n\n# 打包后是否生成dist.zip\nVITE_ARCHIVER=true\n\n```\n\n:::\n\n## 生产环境动态配置\n\n当在大仓根目录下，执行 `pnpm build`构建项目之后，会自动在对应的应用下生成 `dist/_app.config.js`文件并插入 `index.html`。\n\n`_app.config.js` 是一个动态配置文件，可以在项目构建之后，根据不同的环境动态修改配置。内容如下：\n\n```ts\nwindow._VBEN_ADMIN_PRO_APP_CONF_ = {\n  VITE_GLOB_API_URL: 'https://mock-napi.vben.pro/api',\n};\nObject.freeze(window._VBEN_ADMIN_PRO_APP_CONF_);\nObject.defineProperty(window, '_VBEN_ADMIN_PRO_APP_CONF_', {\n  configurable: false,\n  writable: false,\n});\n```\n\n### 作用\n\n`_app.config.js` 用于项目在打包后，需要动态修改配置的需求，如接口地址。不用重新进行打包，可在打包后修改 /`dist/_app.config.js` 内的变量，刷新即可更新代码内的局部变量。这里使用`js`文件，是为了确保配置文件加载顺序保持在前面。\n\n### 使用\n\n想要获取 `_app.config.js` 内的变量，需要使用`@vben/hooks`提供的 `useAppConfig`方法。\n\n```ts\nconst { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n```\n\n### 新增\n\n新增一个可动态修改的配置项，只需要按照如下步骤即可：\n\n- 首先在 `.env` 或者对应的开发环境配置文件内，新增需要可动态配置的变量，需要以 `VITE_GLOB_*` 开头的变量，如：\n\n  ```bash\n  VITE_GLOB_OTHER_API_URL=https://mock-napi.vben.pro/other-api\n  ```\n\n- 在 `packages/types/global.d.ts`,新增对应的类型定义，如：\n\n  ```ts\n  export interface VbenAdminProAppConfigRaw {\n    VITE_GLOB_API_URL: string;\n    VITE_GLOB_OTHER_API_URL: string; // [!code ++]\n  }\n\n  export interface ApplicationConfig {\n    apiURL: string;\n    otherApiURL: string; // [!code ++]\n  }\n  ```\n\n- 在 `packages/effects/hooks/src/use-app-config.ts` 中，新增对应的配置项，如：\n\n  ```ts\n  export function useAppConfig(\n    env: Record<string, any>,\n    isProduction: boolean,\n  ): ApplicationConfig {\n    // 生产环境下，直接使用 window._VBEN_ADMIN_PRO_APP_CONF_ 全局变量\n    const config = isProduction\n      ? window._VBEN_ADMIN_PRO_APP_CONF_\n      : (env as VbenAdminProAppConfigRaw);\n\n    const { VITE_GLOB_API_URL, VITE_GLOB_OTHER_API_URL } = config; // [!code ++]\n\n    return {\n      apiURL: VITE_GLOB_API_URL,\n      otherApiURL: VITE_GLOB_OTHER_API_URL, // [!code ++]\n    };\n  }\n  ```\n\n到这里，就可以在项目内使用 `useAppConfig`方法获取到新增的配置项了。\n\n```ts\nconst { otherApiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n```\n\n::: warning 注意\n\n`useAppConfig`方法只能在应用内使用，不要耦合到包内部去使用。这里传入 `import.meta.env`和`import.meta.env.PROD`是为了避免这种情况，一个纯粹的包，应避免使用特定构建工具的变量。\n\n:::\n\n## 偏好设置\n\n项目提供了非常丰富的偏好设置，用于动态配置项目的各种功能：\n\n![](/guide/preferences.png)\n\n如果你找不到文档说明，可以尝试自己配置好以后，点击`复制偏好设置`，覆盖项目默认即可。配置文件位于应用目录下的`preferences.ts`，在这里，你可以覆盖框架默认的配置，实现自定义配置。\n\n```ts\nimport { useAppConfig } from '@vben/hooks';\nimport { defineOverridesPreferences } from '@vben/preferences';\n\n/**\n * @description 项目配置文件\n * 只需要覆盖项目中的一部分配置，不需要的配置不用覆盖，会自动使用默认配置\n * !!! 更改配置后请清空缓存，否则可能不生效\n */\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n});\n```\n\n### 框架默认配置\n\n::: details 查看框架默认配置\n\n```ts\nconst defaultPreferences: Preferences = {\n  app: {\n    accessMode: 'frontend',\n    authPageLayout: 'panel-right',\n    checkUpdatesInterval: 1,\n    colorGrayMode: false,\n    colorWeakMode: false,\n    compact: false,\n    contentCompact: 'wide',\n    contentCompactWidth: 1200,\n    contentPadding: 0,\n    contentPaddingBottom: 0,\n    contentPaddingLeft: 0,\n    contentPaddingRight: 0,\n    contentPaddingTop: 0,\n    defaultAvatar:\n      'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',\n    defaultHomePath: '/analytics',\n    dynamicTitle: true,\n    enableCheckUpdates: true,\n    enablePreferences: true,\n    enableCopyPreferences: true,\n    enableRefreshToken: false,\n    isMobile: false,\n    layout: 'sidebar-nav',\n    locale: 'zh-CN',\n    loginExpiredMode: 'page',\n    name: 'Vben Admin',\n    preferencesButtonPosition: 'auto',\n    watermark: false,\n    zIndex: 200,\n  },\n  breadcrumb: {\n    enable: true,\n    hideOnlyOne: false,\n    showHome: false,\n    showIcon: true,\n    styleType: 'normal',\n  },\n  copyright: {\n    companyName: 'Vben',\n    companySiteLink: 'https://www.vben.pro',\n    date: '2024',\n    enable: true,\n    icp: '',\n    icpLink: '',\n    settingShow: true,\n  },\n  footer: {\n    enable: false,\n    fixed: false,\n    height: 32,\n  },\n  header: {\n    enable: true,\n    height: 50,\n    hidden: false,\n    menuAlign: 'start',\n    mode: 'fixed',\n  },\n  logo: {\n    enable: true,\n    fit: 'contain',\n    source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',\n    // sourceDark: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-dark.webp', // 可选：暗色主题logo\n  },\n  navigation: {\n    accordion: true,\n    split: true,\n    styleType: 'rounded',\n  },\n  shortcutKeys: {\n    enable: true,\n    globalLockScreen: true,\n    globalLogout: true,\n    globalPreferences: true,\n    globalSearch: true,\n  },\n  sidebar: {\n    autoActivateChild: false,\n    collapsed: false,\n    collapsedButton: true,\n    collapsedShowTitle: false,\n    collapseWidth: 60,\n    enable: true,\n    expandOnHover: true,\n    extraCollapse: false,\n    extraCollapsedWidth: 60,\n    fixedButton: true,\n    hidden: false,\n    mixedWidth: 80,\n    width: 224,\n  },\n  tabbar: {\n    draggable: true,\n    enable: true,\n    height: 38,\n    keepAlive: true,\n    maxCount: 0,\n    middleClickToClose: false,\n    persist: true,\n    showIcon: true,\n    showMaximize: true,\n    showMore: true,\n    styleType: 'chrome',\n    wheelable: true,\n  },\n  theme: {\n    builtinType: 'default',\n    colorDestructive: 'hsl(348 100% 61%)',\n    colorPrimary: 'hsl(212 100% 45%)',\n    colorSuccess: 'hsl(144 57% 58%)',\n    colorWarning: 'hsl(42 84% 61%)',\n    mode: 'dark',\n    radius: '0.5',\n    semiDarkHeader: false,\n    semiDarkSidebar: false,\n  },\n  transition: {\n    enable: true,\n    loading: true,\n    name: 'fade-slide',\n    progress: true,\n  },\n  widget: {\n    fullscreen: true,\n    globalSearch: true,\n    languageToggle: true,\n    lockScreen: true,\n    notification: true,\n    refresh: true,\n    sidebarToggle: true,\n    themeToggle: true,\n  },\n};\n```\n\n:::\n\n::: details 查看框架默认配置类型\n\n```ts\ninterface AppPreferences {\n  /** 权限模式 */\n  accessMode: AccessModeType;\n  /** 登录注册页面布局 */\n  authPageLayout: AuthPageLayoutType;\n  /** 检查更新轮询时间 */\n  checkUpdatesInterval: number;\n  /** 是否开启灰色模式 */\n  colorGrayMode: boolean;\n  /** 是否开启色弱模式 */\n  colorWeakMode: boolean;\n  /** 是否开启紧凑模式 */\n  compact: boolean;\n  /** 是否开启内容紧凑模式 */\n  contentCompact: ContentCompactType;\n  /** 内容紧凑宽度 */\n  contentCompactWidth: number;\n  /** 内容内边距 */\n  contentPadding: number;\n  /** 内容底部内边距 */\n  contentPaddingBottom: number;\n  /** 内容左侧内边距 */\n  contentPaddingLeft: number;\n  /** 内容右侧内边距 */\n  contentPaddingRight: number;\n  /** 内容顶部内边距 */\n  contentPaddingTop: number;\n  // /** 应用默认头像 */\n  defaultAvatar: string;\n  /** 默认首页地址 */\n  defaultHomePath: string;\n  // /** 开启动态标题 */\n  dynamicTitle: boolean;\n  /** 是否开启检查更新 */\n  enableCheckUpdates: boolean;\n  /** 是否显示偏好设置 */\n  enablePreferences: boolean;\n  /** 是否显示复制偏好设置按钮 */\n  enableCopyPreferences: boolean;\n  /**\n   * @zh_CN 是否开启refreshToken\n   */\n  enableRefreshToken: boolean;\n  /** 是否移动端 */\n  isMobile: boolean;\n  /** 布局方式 */\n  layout: LayoutType;\n  /** 支持的语言 */\n  locale: SupportedLanguagesType;\n  /** 登录过期模式 */\n  loginExpiredMode: LoginExpiredModeType;\n  /** 应用名 */\n  name: string;\n  /** 偏好设置按钮位置 */\n  preferencesButtonPosition: PreferencesButtonPositionType;\n  /**\n   * @zh_CN 是否开启水印\n   */\n  watermark: boolean;\n  /** z-index */\n  zIndex: number;\n}\n\ninterface BreadcrumbPreferences {\n  /** 面包屑是否启用 */\n  enable: boolean;\n  /** 面包屑是否只有一个时隐藏 */\n  hideOnlyOne: boolean;\n  /** 面包屑首页图标是否可见 */\n  showHome: boolean;\n  /** 面包屑图标是否可见 */\n  showIcon: boolean;\n  /** 面包屑风格 */\n  styleType: BreadcrumbStyleType;\n}\n\ninterface CopyrightPreferences {\n  /** 版权公司名 */\n  companyName: string;\n  /** 版权公司名链接 */\n  companySiteLink: string;\n  /** 版权日期 */\n  date: string;\n  /** 版权是否可见 */\n  enable: boolean;\n  /** 备案号 */\n  icp: string;\n  /** 备案号链接 */\n  icpLink: string;\n  /** 设置面板是否显示*/\n  settingShow?: boolean;\n}\n\ninterface FooterPreferences {\n  /** 底栏是否可见 */\n  enable: boolean;\n  /** 底栏是否固定 */\n  fixed: boolean;\n  /** 底栏高度 */\n  height: number;\n}\n\ninterface HeaderPreferences {\n  /** 顶栏是否启用 */\n  enable: boolean;\n  /** 顶栏高度 */\n  height: number;\n  /** 顶栏是否隐藏,css-隐藏 */\n  hidden: boolean;\n  /** 顶栏菜单位置 */\n  menuAlign: LayoutHeaderMenuAlignType;\n  /** header显示模式 */\n  mode: LayoutHeaderModeType;\n}\n\ninterface LogoPreferences {\n  /** logo是否可见 */\n  enable: boolean;\n  /** logo图片适应方式 */\n  fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n  /** logo地址 */\n  source: string;\n  /** 暗色主题logo地址 (可选，若不设置则使用 source) */\n  sourceDark?: string;\n}\n\ninterface NavigationPreferences {\n  /** 导航菜单手风琴模式 */\n  accordion: boolean;\n  /** 导航菜单是否切割，只在 layout=mixed-nav 生效 */\n  split: boolean;\n  /** 导航菜单风格 */\n  styleType: NavigationStyleType;\n}\n\ninterface SidebarPreferences {\n  /** 点击目录时自动激活子菜单   */\n  autoActivateChild: boolean;\n  /** 侧边栏是否折叠 */\n  collapsed: boolean;\n  /** 侧边栏折叠按钮是否可见 */\n  collapsedButton: boolean;\n  /** 侧边栏折叠时，是否显示title */\n  collapsedShowTitle: boolean;\n  /** 侧边栏折叠宽度 */\n  collapseWidth: number;\n  /** 侧边栏是否可见 */\n  enable: boolean;\n  /** 菜单自动展开状态 */\n  expandOnHover: boolean;\n  /** 侧边栏扩展区域是否折叠 */\n  extraCollapse: boolean;\n  /** 侧边栏扩展区域折叠宽度 */\n  extraCollapsedWidth: number;\n  /** 侧边栏固定按钮是否可见 */\n  fixedButton: boolean;\n  /** 侧边栏是否隐藏 - css */\n  hidden: boolean;\n  /** 混合侧边栏宽度 */\n  mixedWidth: number;\n  /** 侧边栏宽度 */\n  width: number;\n}\n\ninterface ShortcutKeyPreferences {\n  /** 是否启用快捷键-全局 */\n  enable: boolean;\n  /** 是否启用全局锁屏快捷键 */\n  globalLockScreen: boolean;\n  /** 是否启用全局注销快捷键 */\n  globalLogout: boolean;\n  /** 是否启用全局偏好设置快捷键 */\n  globalPreferences: boolean;\n  /** 是否启用全局搜索快捷键 */\n  globalSearch: boolean;\n}\n\ninterface TabbarPreferences {\n  /** 是否开启多标签页拖拽 */\n  draggable: boolean;\n  /** 是否开启多标签页 */\n  enable: boolean;\n  /** 标签页高度 */\n  height: number;\n  /** 开启标签页缓存功能 */\n  keepAlive: boolean;\n  /** 限制最大数量 */\n  maxCount: number;\n  /** 是否点击中键时关闭标签 */\n  middleClickToClose: boolean;\n  /** 是否持久化标签 */\n  persist: boolean;\n  /** 是否开启多标签页图标 */\n  showIcon: boolean;\n  /** 显示最大化按钮 */\n  showMaximize: boolean;\n  /** 显示更多按钮 */\n  showMore: boolean;\n  /** 标签页风格 */\n  styleType: TabsStyleType;\n  /** 是否开启鼠标滚轮响应 */\n  wheelable: boolean;\n}\n\ninterface ThemePreferences {\n  /** 内置主题名 */\n  builtinType: BuiltinThemeType;\n  /** 错误色 */\n  colorDestructive: string;\n  /** 主题色 */\n  colorPrimary: string;\n  /** 成功色 */\n  colorSuccess: string;\n  /** 警告色 */\n  colorWarning: string;\n  /** 当前主题 */\n  mode: ThemeModeType;\n  /** 圆角 */\n  radius: string;\n  /** 是否开启半深色header（只在theme='light'时生效） */\n  semiDarkHeader: boolean;\n  /** 是否开启半深色菜单（只在theme='light'时生效） */\n  semiDarkSidebar: boolean;\n}\n\ninterface TransitionPreferences {\n  /** 页面切换动画是否启用 */\n  enable: boolean;\n  // /** 是否开启页面加载loading */\n  loading: boolean;\n  /** 页面切换动画 */\n  name: PageTransitionType | string;\n  /** 是否开启页面加载进度动画 */\n  progress: boolean;\n}\n\ninterface WidgetPreferences {\n  /** 是否启用全屏部件 */\n  fullscreen: boolean;\n  /** 是否启用全局搜索部件 */\n  globalSearch: boolean;\n  /** 是否启用语言切换部件 */\n  languageToggle: boolean;\n  /** 是否开启锁屏功能 */\n  lockScreen: boolean;\n  /** 是否显示通知部件 */\n  notification: boolean;\n  /** 显示刷新按钮 */\n  refresh: boolean;\n  /** 是否显示侧边栏显示/隐藏部件 */\n  sidebarToggle: boolean;\n  /** 是否显示主题切换部件 */\n  themeToggle: boolean;\n}\n\ninterface Preferences {\n  /** 全局配置 */\n  app: AppPreferences;\n  /** 顶栏配置 */\n  breadcrumb: BreadcrumbPreferences;\n  /** 版权配置 */\n  copyright: CopyrightPreferences;\n  /** 底栏配置 */\n  footer: FooterPreferences;\n  /** 面包屑配置 */\n  header: HeaderPreferences;\n  /** logo配置 */\n  logo: LogoPreferences;\n  /** 导航配置 */\n  navigation: NavigationPreferences;\n  /** 快捷键配置 */\n  shortcutKeys: ShortcutKeyPreferences;\n  /** 侧边栏配置 */\n  sidebar: SidebarPreferences;\n  /** 标签页配置 */\n  tabbar: TabbarPreferences;\n  /** 主题配置 */\n  theme: ThemePreferences;\n  /** 动画配置 */\n  transition: TransitionPreferences;\n  /** 功能配置 */\n  widget: WidgetPreferences;\n}\n```\n\n:::\n\n::: warning 注意\n\n- `overridesPreferences`方法只需要覆盖项目中的一部分配置，不需要的配置不用覆盖，会自动使用默认配置。\n- 任何配置项都可以覆盖，只需要在`overridesPreferences`方法内覆盖即可，不要修改默认配置文件。\n- 更改配置后请清空缓存，否则可能不生效。\n\n:::\n"
  },
  {
    "path": "docs/src/guide/essentials/styles.md",
    "content": "# 样式\n\n::: tip 前言\n\n对于 vue 项目，[官方文档](https://vuejs.org/api/sfc-css-features.html#deep-selectors) 对语法已经有比较详细的介绍，这里主要是介绍项目中的样式文件结构和使用。\n\n:::\n\n## 项目结构\n\n项目中的样式文件存放在 `@vben/styles`，包含一些全局样式，如重置样式、全局变量等，它继承了 `@vben-core/design` 的样式和能力，可以根据项目需求进行覆盖。\n\n## Scss\n\n项目中使用 `scss` 作为样式预处理器，可以在项目中使用 `scss` 的特性，如变量、函数、混合等。\n\n```vue\n<style lang=\"scss\" scoped>\n$font-size: 30px;\n\n.box {\n  .title {\n    color: green;\n    font-size: $font-size;\n  }\n}\n</style>\n```\n\n## Postcss\n\n如果你不习惯使用 `scss`，也可以使用 `postcss`，它是一个更加强大的样式处理器，可以使用更多的插件，项目内置了 [postcss-nested](https://github.com/postcss/postcss-nested) 插件，配置 `Css Variables`，完全可以取代 `scss`。\n\n```vue\n<style scoped>\n.box {\n  --font-size: 30px;\n  .title {\n    color: green;\n    font-size: var(--font-size);\n  }\n}\n</style>\n```\n\n## Tailwind CSS\n\n项目中集成了 [Tailwind CSS](https://tailwindcss.com/)，可以在项目中使用 `tailwindcss` 的类名，快速构建页面。\n\n```vue\n<template>\n  <div class=\"bg-white p-4\">\n    <p class=\"text-green\">hello world</p>\n  </div>\n</template>\n```\n\n## BEM 规范\n\n样式冲突的另一种选择，是使用 `BEM` 规范。如果选择 `scss` ，建议使用 `BEM` 命名规范，可以更好的管理样式。项目默认提供了`useNamespace`函数，可以方便的生成命名空间。\n\n```vue\n<script lang=\"ts\" setup>\nimport { useNamespace } from '@vben/hooks';\n\nconst { b, e, is } = useNamespace('menu');\n</script>\n<template>\n  <div :class=\"[b()]\">\n    <div :class=\"[e('item'), is('active', true)]\">item1</div>\n  </div>\n</template>\n<style lang=\"scss\" scoped>\n// 如果你在应用内使用，这行代码可以省略，已经在所有的应用内全局引入了\n@use '@vben/styles/global' as *;\n@include b('menu') {\n  color: black;\n\n  @include e('item') {\n    background-color: black;\n\n    @include is('active') {\n      background-color: red;\n    }\n  }\n}\n</style>\n```\n\n## CSS Modules\n\n针对样式冲突问题，还有一种方案是使用 `CSS Modules` 模块化方案。使用方式如下。\n\n```vue\n<template>\n  <p :class=\"$style.red\">This should be red</p>\n</template>\n\n<style module>\n.red {\n  color: red;\n}\n</style>\n```\n\n更多用法可以见 [CSS Modules 官方文档](https://vuejs.org/api/sfc-css-features.html#css-modules)。\n"
  },
  {
    "path": "docs/src/guide/in-depth/access.md",
    "content": "---\noutline: deep\n---\n\n# 权限\n\n框架内置了三种权限控制方式：\n\n- 通过用户角色来判断菜单或者按钮是否可以访问\n- 通过接口来判断菜单或者按钮是否可以访问\n- 混合模式：同时使用前端和后端权限控制\n\n## 前端访问控制\n\n**实现原理**: 在前端固定写死路由的权限，指定路由有哪些权限可以查看。只初始化通用的路由，需要权限才能访问的路由没有被加入路由表内。在登录后或者其他方式获取用户角色后，通过角色去遍历路由表，获取该角色可以访问的路由表，生成路由表，再通过 `router.addRoute` 添加到路由实例，实现权限的过滤。\n\n**缺点**: 权限相对不自由，如果后台改动角色，前台也需要跟着改动。适合角色较固定的系统\n\n### 步骤\n\n- 确保当前模式为前端访问控制模式\n\n调整对应应用目录下的`preferences.ts`，确保`accessMode='frontend'`。\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    // 默认值，可不填\n    accessMode: 'frontend',\n  },\n});\n```\n\n- 配置路由权限\n\n**如果不配置，默认可见**\n\n```ts {3}\n {\n    meta: {\n      authority: ['super'],\n    },\n},\n```\n\n- 确保接口返回的角色和路由表的权限匹配\n\n可查看应用下的 `src/store/auth`,找到下面代码，\n\n```ts\n// 设置登录用户信息，需要确保 userInfo.roles 是一个数组，且包含路由表中的权限\n// 例如：userInfo.roles=['super', 'admin']\nauthStore.setUserInfo(userInfo);\n```\n\n到这里，就已经配置完成，你需要确保登录后，接口返回的角色和路由表的权限匹配，否则无法访问。\n\n### 菜单可见，但禁止访问\n\n有时候，我们需要菜单可见，但是禁止访问，可以通过下面的方式实现，设置 `menuVisibleWithForbidden` 为 `true`，此时菜单可见，但是禁止访问，会跳转403页面。\n\n```ts\n{\n    meta: {\n      menuVisibleWithForbidden: true,\n    },\n},\n```\n\n## 后端访问控制\n\n**实现原理**: 是通过接口动态生成路由表，且遵循一定的数据结构返回。前端根据需要处理该数据为可识别的结构，再通过 `router.addRoute` 添加到路由实例，实现权限的动态生成。\n\n**缺点**: 后端需要提供符合规范的数据结构，前端需要处理数据结构，适合权限较为复杂的系统。\n\n### 步骤\n\n- 确保当前模式为后端访问控制模式\n\n调整对应应用目录下的`preferences.ts`，确保`accessMode='backend'`。\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    accessMode: 'backend',\n  },\n});\n```\n\n- 确保接口返回的菜单数据结构正确\n\n可查看应用下的 `src/router/access.ts`,找到下面代码，\n\n```ts {5}\nasync function generateAccess(options: GenerateMenuAndRoutesOptions) {\n  return await generateAccessible(preferences.app.accessMode, {\n    fetchMenuListAsync: async () => {\n      // 这个接口为后端返回的菜单数据\n      return await getAllMenus();\n    },\n  });\n}\n```\n\n- 接口返回菜单数据，可看注释说明\n\n::: details 接口返回菜单数据示例\n\n```ts\nconst dashboardMenus = [\n  {\n    meta: {\n      order: -1,\n      title: 'page.dashboard.title',\n    },\n    name: 'Dashboard',\n    path: '/',\n    redirect: '/analytics',\n    children: [\n      {\n        name: 'Analytics',\n        path: '/analytics',\n        // 这里为页面的路径，需要去掉 views/ 和 .vue\n        component: '/dashboard/analytics/index',\n        meta: {\n          affixTab: true,\n          title: 'page.dashboard.analytics',\n        },\n      },\n      {\n        name: 'Workspace',\n        path: '/workspace',\n        component: '/dashboard/workspace/index',\n        meta: {\n          title: 'page.dashboard.workspace',\n        },\n      },\n    ],\n  },\n  {\n    name: 'Test',\n    path: '/test',\n    component: '/test/index',\n    meta: {\n      title: 'page.test',\n      // 部分特殊页面如果不需要基础布局（页面顶部和侧边栏），可将noBasicLayout设置为true\n      noBasicLayout: true,\n    },\n  },\n];\n```\n\n:::\n\n到这里，就已经配置完成，你需要确保登录后，接口返回的菜单格式正确，否则无法访问。\n\n## 混合访问控制\n\n**实现原理**: 混合模式同时结合了前端访问控制和后端访问控制两种方式。系统会并行处理前端固定路由权限和后端动态菜单数据，最终将两部分路由合并，提供更灵活的权限控制方案。\n\n**优点**: 兼具前端控制的性能优势和后端控制的灵活性，适合复杂业务场景下的权限管理。\n\n### 步骤\n\n- 确保当前模式为混合访问控制模式\n\n调整对应应用目录下的`preferences.ts`，确保`accessMode='mixed'`。\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    accessMode: 'mixed',\n  },\n});\n```\n\n- 配置前端路由权限\n\n同[前端访问控制](#前端访问控制)模式的路由权限配置方式。\n\n- 配置后端菜单接口\n\n同[后端访问控制](#后端访问控制)模式的接口配置方式。\n\n- 确保角色和权限匹配\n\n需要同时满足前端路由权限配置和后端菜单数据返回的要求，确保用户角色与两种模式的权限配置都匹配。\n\n到这里，就已经配置完成，混合模式会自动合并前端和后端的路由，提供完整的权限控制功能。\n\n## 按钮细粒度控制\n\n在某些情况下，我们需要对按钮进行细粒度的控制，我们可以借助接口或者角色来控制按钮的显示。\n\n### 权限码\n\n权限码为接口返回的权限码，通过权限码来判断按钮是否显示，逻辑在`src/store/auth`下：\n\n```ts\nconst [fetchUserInfoResult, accessCodes] = await Promise.all([\n  fetchUserInfo(),\n  getAccessCodes(),\n]);\n\nuserInfo = fetchUserInfoResult;\nauthStore.setUserInfo(userInfo);\naccessStore.setAccessCodes(accessCodes);\n```\n\n找到 `getAccessCodes` 对应的接口，可根据业务逻辑进行调整。\n\n权限码返回的数据结构为字符串数组，例如：`['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010']`\n\n有了权限码，就可以使用 `@vben/access` 提供的`AccessControl`组件及API来进行按钮的显示与隐藏。\n\n#### 组件方式\n\n```vue\n<script lang=\"ts\" setup>\nimport { AccessControl, useAccess } from '@vben/access';\n\nconst { accessMode, hasAccessByCodes } = useAccess();\n</script>\n\n<template>\n  <!-- 需要指明 type=\"code\" -->\n  <AccessControl :codes=\"['AC_100100']\" type=\"code\">\n    <Button> Super 账号可见 [\"AC_1000001\"] </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['AC_100030']\" type=\"code\">\n    <Button> Admin 账号可见 [\"AC_100010\"] </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['AC_1000001']\" type=\"code\">\n    <Button> User 账号可见 [\"AC_1000001\"] </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['AC_100100', 'AC_100010']\" type=\"code\">\n    <Button> Super & Admin 账号可见 [\"AC_100100\",\"AC_1000001\"] </Button>\n  </AccessControl>\n</template>\n```\n\n#### API方式\n\n```vue\n<script lang=\"ts\" setup>\nimport { AccessControl, useAccess } from '@vben/access';\n\nconst { hasAccessByCodes } = useAccess();\n</script>\n\n<template>\n  <Button v-if=\"hasAccessByCodes(['AC_100100'])\">\n    Super 账号可见 [\"AC_1000001\"]\n  </Button>\n  <Button v-if=\"hasAccessByCodes(['AC_100030'])\">\n    Admin 账号可见 [\"AC_100010\"]\n  </Button>\n  <Button v-if=\"hasAccessByCodes(['AC_1000001'])\">\n    User 账号可见 [\"AC_1000001\"]\n  </Button>\n  <Button v-if=\"hasAccessByCodes(['AC_100100', 'AC_1000001'])\">\n    Super & Admin 账号可见 [\"AC_100100\",\"AC_1000001\"]\n  </Button>\n</template>\n```\n\n#### 指令方式\n\n> 指令支持绑定单个或多个权限码。单个时可以直接传入字符串或数组中包含一个权限码，多个权限码则传入数组。\n\n```vue\n<template>\n  <Button class=\"mr-4\" v-access:code=\"'AC_100100'\">\n    Super 账号可见 'AC_100100'\n  </Button>\n  <Button class=\"mr-4\" v-access:code=\"['AC_100030']\">\n    Admin 账号可见 [\"AC_100010\"]\n  </Button>\n  <Button class=\"mr-4\" v-access:code=\"['AC_1000001']\">\n    User 账号可见 [\"AC_1000001\"]\n  </Button>\n  <Button class=\"mr-4\" v-access:code=\"['AC_100100', 'AC_1000001']\">\n    Super & Admin 账号可见 [\"AC_100100\",\"AC_1000001\"]\n  </Button>\n</template>\n```\n\n### 角色\n\n角色判断方式不需要接口返回的权限码，直接通过角色来判断按钮是否显示。\n\n#### 组件方式\n\n```vue\n<script lang=\"ts\" setup>\nimport { AccessControl } from '@vben/access';\n</script>\n\n<template>\n  <AccessControl :codes=\"['super']\">\n    <Button> Super 角色可见 </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['admin']\">\n    <Button> Admin 角色可见 </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['user']\">\n    <Button> User 角色可见 </Button>\n  </AccessControl>\n  <AccessControl :codes=\"['super', 'admin']\">\n    <Button> Super & Admin 角色可见 </Button>\n  </AccessControl>\n</template>\n```\n\n#### API方式\n\n```vue\n<script lang=\"ts\" setup>\nimport { useAccess } from '@vben/access';\n\nconst { hasAccessByRoles } = useAccess();\n</script>\n\n<template>\n  <Button v-if=\"hasAccessByRoles(['super'])\"> Super 账号可见 </Button>\n  <Button v-if=\"hasAccessByRoles(['admin'])\"> Admin 账号可见 </Button>\n  <Button v-if=\"hasAccessByRoles(['user'])\"> User 账号可见 </Button>\n  <Button v-if=\"hasAccessByRoles(['super', 'admin'])\">\n    Super & Admin 账号可见\n  </Button>\n</template>\n```\n\n#### 指令方式\n\n> 指令支持绑定单个或多个角色。单个时可以直接传入字符串或数组中包含一个角色，多个角色均可访问则传入数组。\n\n```vue\n<template>\n  <Button class=\"mr-4\" v-access:role=\"'super'\"> Super 角色可见 </Button>\n  <Button class=\"mr-4\" v-access:role=\"['super']\"> Super 角色可见 </Button>\n  <Button class=\"mr-4\" v-access:role=\"['admin']\"> Admin 角色可见 </Button>\n  <Button class=\"mr-4\" v-access:role=\"['user']\"> User 角色可见 </Button>\n  <Button class=\"mr-4\" v-access:role=\"['super', 'admin']\">\n    Super & Admin 角色可见\n  </Button>\n</template>\n```\n"
  },
  {
    "path": "docs/src/guide/in-depth/check-updates.md",
    "content": "# 检查更新\n\n## 介绍\n\n当网站有更新时，您可能需要检查更新。框架提供了这一功能，通过定时检查更新，您可以在应用的 preferences.ts 文件中配置 `checkUpdatesInterval`和 `enableCheckUpdates` 字段，以开启和设置检查更新的时间间隔（单位：分钟）。\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    // 是否开启检查更新\n    enableCheckUpdates: true,\n    // 检查更新的时间间隔，单位为分钟\n    checkUpdatesInterval: 1,\n  },\n});\n```\n\n## 效果\n\n检测到更新时，会弹出提示框，询问用户是否刷新页面：\n\n![check-updates](/guide/update-notice.png)\n\n## 替换为其他检查更新方式\n\n如果需要通过其他方式检查更新，例如通过接口来更灵活地控制更新逻辑（如强制刷新、显示更新内容等），你可以通过修改 `@vben/layouts` 下面的 `src/widgets/check-updates/check-updates.vue`文件来实现。\n\n```ts\n// 这里可以替换为你的检查更新逻辑\nasync function getVersionTag() {\n  try {\n    const response = await fetch('/', {\n      cache: 'no-cache',\n      method: 'HEAD',\n    });\n\n    return (\n      response.headers.get('etag') || response.headers.get('last-modified')\n    );\n  } catch {\n    console.error('Failed to fetch version tag');\n    return null;\n  }\n}\n```\n\n## 替换为第三方库检查更新方式\n\n如果需要通过其他方式检查更新，例如使用其他版本控制方式（chunkHash、version.json）、使用`Web Worker`在后台轮询更新、自定义检查更新时机（不使用轮询），你可以通过JS库`version-polling`来实现。\n\n```bash\npnpm add version-polling\n```\n\n以`apps/web-antd`项目为例，在项目入口文件`main.ts`或者`app.vue`添加以下代码\n\n```ts\nimport { h } from 'vue';\n\nimport { Button, notification } from 'ant-design-vue';\nimport { createVersionPolling } from 'version-polling';\n\ncreateVersionPolling({\n  silent: import.meta.env.MODE === 'development', // 开发环境下不检测\n  onUpdate: (self) => {\n    const key = `open${Date.now()}`;\n    notification.info({\n      message: '提示',\n      description: '检测到网页有更新, 是否刷新页面加载最新版本？',\n      btn: () =>\n        h(\n          Button,\n          {\n            type: 'primary',\n            size: 'small',\n            onClick: () => {\n              notification.close(key);\n              self.onRefresh();\n            },\n          },\n          { default: () => '刷新' },\n        ),\n      key,\n      duration: null,\n      placement: 'bottomRight',\n    });\n  },\n});\n```\n"
  },
  {
    "path": "docs/src/guide/in-depth/features.md",
    "content": "# 常用功能\n\n一些常用的功能合集。\n\n## 登录认证过期\n\n当接口返回`401`状态码时，框架会认为登录认证过期，登录超时会跳转到登录页或者打开登录弹窗。在应用目录下的`preferences.ts`可以配置：\n\n### 跳转登录页面\n\n登录超时会跳转到登录页\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    loginExpiredMode: 'page',\n  },\n});\n```\n\n### 打开登录弹窗\n\n登录超时会打开登录弹窗\n\n![](/guide/login-expired.png)\n\n配置：\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    loginExpiredMode: 'modal',\n  },\n});\n```\n\n## 动态标题\n\n- 默认值：`true`\n\n开启后网页标题随着路由的`title`而变化。在应用目录下的`preferences.ts`，开启或者关闭即可。\n\n```ts\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    dynamicTitle: true,\n  },\n});\n```\n\n## 页面水印\n\n- 默认值：`false`\n\n开启后网页会显示水印，在应用目录下的`preferences.ts`，开启或者关闭即可。\n\n```ts\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    watermark: true,\n  },\n});\n```\n\n如果你想更新水印的内容，可以这么做，参数可以参考 [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/)：\n\n```ts\nimport { useWatermark } from '@vben/hooks';\n\nconst { destroyWatermark, updateWatermark } = useWatermark();\n\nawait updateWatermark({\n  // 水印内容\n  content: 'hello my watermark',\n});\n```\n"
  },
  {
    "path": "docs/src/guide/in-depth/layout.md",
    "content": "# 布局\n"
  },
  {
    "path": "docs/src/guide/in-depth/loading.md",
    "content": "# 全局loading\n\n全局 loading 指的是页面刷新时出现的加载效果，通常是一个旋转的图标：\n\n![Global loading spinner](/guide/loading.png)\n\n## 原理\n\n由 `vite-plugin-inject-app-loading` 插件实现，插件会在每个应用都注入一个全局的 `loading html`。\n\n## 关闭\n\n如果你不需要全局 loading，可以在 `.env` 文件中关闭：\n\n```bash\nVITE_INJECT_APP_LOADING=false\n```\n\n## 自定义\n\n如果你想要自定义全局 loading，可以在应用目录下，与`index.html`同级，创建一个`loading.html`文件，插件会自动读取并注入。这个html可以自行定义样式和动画。\n\n::: tip\n\n- 你可以使用跟`index.html`一样的语法，比如`VITE_APP_TITLE`变量，来获取应用的标题。\n- 必须保证有一个`id=\"__app-loading__\"`的元素。\n- 给`id=\"__app-loading__\"`的元素，加一个 `hidden` class。\n- 必须保证有一个`style[data-app-loading=\"inject-css\"]`的元素。\n\n```html{1,4}\n<style data-app-loading=\"inject-css\">\n  #__app-loading__.hidden {\n    pointer-events: none;\n    visibility: hidden;\n    opacity: 0;\n    transition: all 1s ease-out;\n  }\n  /* ... */\n</style>\n<div id=\"__app-loading__\">\n  <!-- ... -->\n  <div class=\"title\"><%= VITE_APP_TITLE %></div>\n</div>\n```\n\n:::\n"
  },
  {
    "path": "docs/src/guide/in-depth/locale.md",
    "content": "# 国际化\n\n项目已经集成了 [Vue i18n](https://kazupon.github.io/vue-i18n/)，并且已经配置好了中文和英文的语言包。\n\n## IDE 插件\n\n如果你使用的 vscode 开发工具，则推荐安装 [i18n Ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) 这个插件。它可以帮助你更方便的管理国际化的文案，安装了该插件后，你的代码内可以实时看到对应的语言内容：\n\n![](/public/guide/locale.png)\n\n## 配置默认语言\n\n只需要覆盖默认的偏好设置即可，在对应的应用内，找到 `src/preferences.ts` 文件，修改 `locale` 的值即可：\n\n```ts {3}\nexport const overridesPreferences = defineOverridesPreferences({\n  app: {\n    locale: 'en-US',\n  },\n});\n```\n\n## 动态切换语言\n\n切换语言有两部分组成:\n\n- 更新偏好设置\n- 加载对应的语言包\n\n```ts\nimport type { SupportedLanguagesType } from '@vben/locales';\nimport { loadLocaleMessages } from '@vben/locales';\nimport { updatePreferences } from '@vben/preferences';\n\nasync function updateLocale(value: string) {\n  // 1. 更新偏好设置\n  const locale = value as SupportedLanguagesType;\n  updatePreferences({\n    app: {\n      locale,\n    },\n  });\n  // 2. 加载对应的语言包\n  await loadLocaleMessages(locale);\n}\n\nupdateLocale('en-US');\n```\n\n## 新增翻译文本\n\n::: warning 注意\n\n- 请不要将业务翻译文本放到 `@vben/locales` 内，这样可以更好的管理业务和通用的翻译文本。\n- 有多个语言包的情况下，新增翻译文本时，需要在所有语言包内新增对应的文本。\n\n:::\n\n新增翻译文本，只需要在对应的应用内，找到 `src/locales/langs/`，新增对应的文本即可，例:\n\n**src/locales/langs/zh-CN/\\*.json**\n\n````ts\n```json\n{\n  \"about\": {\n    \"desc\": \"Vben Admin 是一个现代的管理模版。\"\n  }\n}\n````\n\n**src/locales/langs/en-US.ts**\n\n````ts\n```json\n{\n  \"about\": {\n    \"desc\": \"Vben Admin is a modern management template.\"\n  }\n}\n````\n\n## 使用翻译文本\n\n通过 `@vben/locales`，你可以很方便的使用翻译文本：\n\n### 在代码中使用\n\n```vue\n<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport { $t } from '@vben/locales';\n\nconst items = computed(() => [{ title: $t('about.desc') }]);\n</script>\n<template>\n  <div>{{ $t('about.desc') }}</div>\n  <template v-for=\"item in items.value\">\n    <div>{{ item.title }}</div>\n  </template>\n</template>\n```\n\n## 新增语言包\n\n如果你需要新增语言包，需要按照以下步骤进行：\n\n- 在 `packages/locales/langs` 目录下新增对应的语言包文件，例：`zh-TW.json`，并翻译对应的文本。\n- 在对应的应用内，找到 `src/locales/langs` 文件，新增对应的语言包 `zh-TW.json`\n- 在 `packages/constants/src/core.ts`内，新增对应的语言：\n\n  ```ts\n  export interface LanguageOption {\n    label: string;\n    value: 'en-US' | 'zh-CN'; // [!code --]\n    value: 'en-US' | 'zh-CN' | 'zh-TW'; // [!code ++]\n  }\n  export const SUPPORT_LANGUAGES: LanguageOption[] = [\n    {\n      label: '简体中文',\n      value: 'zh-CN',\n    },\n    {\n      label: 'English',\n      value: 'en-US',\n    },\n    {\n      label: '繁体中文', // [!code ++]\n      value: 'zh-TW', // [!code ++]\n    },\n  ];\n  ```\n\n- 在 `packages/locales/typing.ts`内，新增 Typescript 类型：\n\n  ```ts\n  export type SupportedLanguagesType = 'en-US' | 'zh-CN'; // [!code --]\n  export type SupportedLanguagesType = 'en-US' | 'zh-CN' | 'zh-TW'; // [!code ++]\n  ```\n\n到这里，你就可以在项目内使用新增的语言包了。\n\n## 界面切换语言功能\n\n如果你想关闭界面上的语言切换显示按钮，在对应的应用内，找到 `src/preferences.ts` 文件，修改 `locale` 的值即可：\n\n```ts {3}\nexport const overridesPreferences = defineOverridesPreferences({\n  widget: {\n    languageToggle: false,\n  },\n});\n```\n\n## 远程加载语言包\n\n::: tip 提示\n\n通过项目自带的`request`工具进行接口请求时，默认请求头里会带上 [Accept-Language](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Language) ，服务端可根据请求头进行动态数据国际化处理。\n\n:::\n\n每个应用都有一个独立的语言包，它可以覆盖通用的语言配置，你可以通过远程加载的方式来获取对应的语言包，只需要在对应的应用内，找到 `src/locales/index.ts` 文件，修改 `loadMessages` 方法即可：\n\n```ts {3-4}\nasync function loadMessages(lang: SupportedLanguagesType) {\n  const [appLocaleMessages] = await Promise.all([\n    // 这里修改为远程接口加载数据即可\n    localesMap[lang](),\n    loadThirdPartyMessage(lang),\n  ]);\n  return appLocaleMessages.default;\n}\n```\n\n## 第三方语言包\n\n不同应用内使用的第三方组件库或者插件国际化方式可能不一致，所以需要差别处理。 如果你需要引入第三方的语言包，你可以在对应的应用内，找到 `src/locales/index.ts` 文件，修改 `loadThirdPartyMessage` 方法即可：\n\n```ts\n/**\n * 加载dayjs的语言包\n * @param lang\n */\nasync function loadDayjsLocale(lang: SupportedLanguagesType) {\n  let locale;\n  switch (lang) {\n    case 'zh-CN': {\n      locale = await import('dayjs/locale/zh-cn');\n      break;\n    }\n    case 'en-US': {\n      locale = await import('dayjs/locale/en');\n      break;\n    }\n    // 默认使用英语\n    default: {\n      locale = await import('dayjs/locale/en');\n    }\n  }\n  if (locale) {\n    dayjs.locale(locale);\n  } else {\n    console.error(`Failed to load dayjs locale for ${lang}`);\n  }\n}\n```\n\n## 移除国际化\n\n首先，不是很建议移除国际化，因为国际化是一个很好的开发习惯，但是如果你真的需要移除国际化，你可以直接使用中文文案，然后保留项目自带的语言包即可，整体开发体验不会影响。移除国际化的步骤如下：\n\n- 隐藏界面上的语言切换按钮，见：[界面切换语言功能](#界面切换语言功能)\n- 修改默认语言，见：[配置默认语言](#配置默认语言)\n- 关闭 `vue-i18n`的警告提示，在`src/locales/index.ts`文件内，修改`missingWarn`为`false`即可：\n\n  ```ts\n  async function setupI18n(app: App, options: LocaleSetupOptions = {}) {\n    await coreSetup(app, {\n      defaultLocale: preferences.app.locale,\n      loadMessages,\n      missingWarn: !import.meta.env.PROD, // [!code --]\n      missingWarn: false, // [!code ++]\n      ...options,\n    });\n  }\n  ```\n"
  },
  {
    "path": "docs/src/guide/in-depth/login.md",
    "content": "---\noutline: deep\n---\n\n# 登录\n\n本文介绍如何去改造自己的应用程序登录页以及如何快速的对接登录页面接口。\n\n## 登录页面调整\n\n如果你想调整登录页面的标题、描述和图标以及工具栏，你可以通过配置 `AuthPageLayout` 组件的参数来实现。\n\n![login](/guide/login.png)\n\n只需要在应用下的 `src/layouts/auth.vue` 内，配置`AuthPageLayout`的 `props`参数即可：\n\n```vue {2-7}\n<AuthPageLayout\n  :copyright=\"true\"\n  :toolbar=\"true\"\n  :toolbarList=\"['color', 'language', 'layout', 'theme']\"\n  :app-name=\"appName\"\n  :logo=\"logo\"\n  :page-description=\"$t('authentication.pageDesc')\"\n  :page-title=\"$t('authentication.pageTitle')\"\n>\n</AuthPageLayout>\n```\n\n## 登录表单调整\n\n如果你想调整登录表单的相关内容，你可以在应用下的 `src/views/_core/authentication/login.vue` 内，配置`AuthenticationLogin` 组件参数即可：\n\n```vue\n<AuthenticationLogin\n  :loading=\"authStore.loginLoading\"\n  @submit=\"authStore.authLogin\"\n/>\n```\n\n::: details AuthenticationLogin 组件参数\n\n```ts\n{\n  /**\n   * @zh_CN 验证码登录路径\n   */\n  codeLoginPath?: string;\n  /**\n   * @zh_CN 忘记密码路径\n   */\n  forgetPasswordPath?: string;\n\n  /**\n   * @zh_CN 是否处于加载处理状态\n   */\n  loading?: boolean;\n\n  /**\n   * @zh_CN 二维码登录路径\n   */\n  qrCodeLoginPath?: string;\n\n  /**\n   * @zh_CN 注册路径\n   */\n  registerPath?: string;\n\n  /**\n   * @zh_CN 是否显示验证码登录\n   */\n  showCodeLogin?: boolean;\n  /**\n   * @zh_CN 是否显示忘记密码\n   */\n  showForgetPassword?: boolean;\n\n  /**\n   * @zh_CN 是否显示二维码登录\n   */\n  showQrcodeLogin?: boolean;\n\n  /**\n   * @zh_CN 是否显示注册按钮\n   */\n  showRegister?: boolean;\n\n  /**\n   * @zh_CN 是否显示记住账号\n   */\n  showRememberMe?: boolean;\n\n  /**\n   * @zh_CN 是否显示第三方登录\n   */\n  showThirdPartyLogin?: boolean;\n\n  /**\n   * @zh_CN 登录框子标题\n   */\n  subTitle?: string;\n\n  /**\n   * @zh_CN 登录框标题\n   */\n  title?: string;\n\n}\n```\n\n:::\n\n::: tip Note\n\n如果这些配置不能满足你的需求，你可以自行实现登录表单及相关登录逻辑或者给我们提交 `PR`。\n\n:::\n\n## 接口对接流程\n\n这里将会快速的介绍如何快速对接自己的后端。\n\n### 前置条件\n\n- 首先文档用的后端服务，接口返回的格式统一如下：\n\n```ts\ninterface HttpResponse<T = any> {\n  /**\n   * 0 表示成功 其他表示失败\n   * 0 means success, others means fail\n   */\n  code: number;\n  data: T;\n  message: string;\n}\n```\n\n如果你不符合这个格式，你需要先阅读 [服务端交互](../essentials/server.md) 文档，改造你的`request.ts`配置。\n\n- 其次你需要在先将本地代理地址改为你的真实后端地址，你可以在应用下的 `vite.config.mts` 内配置：\n\n```ts\nimport { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    vite: {\n      server: {\n        proxy: {\n          '/api': {\n            changeOrigin: true,\n            rewrite: (path) => path.replace(/^\\/api/, ''),\n            // 这里改为你的真实接口地址\n            target: 'http://localhost:5320/api',\n            ws: true,\n          },\n        },\n      },\n    },\n  };\n});\n```\n\n### 登录接口\n\n为了能正常登录，你的后端最少需要提供 `2-3` 个接口：\n\n- 登录接口\n\n接口地址可在应用下的 `src/api/core/auth` 内修改，以下为默认接口地址：\n\n```ts\n/**\n * 登录\n */\nexport async function loginApi(data: AuthApi.LoginParams) {\n  return requestClient.post<AuthApi.LoginResult>('/auth/login', data);\n}\n\n/** 只需要保证登录接口返回值有 `accessToken` 字段即可 */\nexport interface LoginResult {\n  accessToken: string;\n}\n```\n\n- 获取用户信息接口\n\n接口地址可在应用下的 `src/api/core/user` 内修改，以下为默认接口地址：\n\n```ts\nexport async function getUserInfoApi() {\n  return requestClient.get<UserInfo>('/user/info');\n}\n\n/** 只需要保证登录接口返回值有以下字段即可，多的字段可以自行使用 */\nexport interface UserInfo {\n  roles: string[];\n  realName: string;\n}\n```\n\n- 获取权限码 (可选)\n\n这个接口用于获取用户的权限码，权限码是用于控制用户的权限的，接口地址可在应用下的 `src/api/core/auth` 内修改，以下为默认接口地址：\n\n```ts\nexport async function getAccessCodesApi() {\n  return requestClient.get<string[]>('/auth/codes');\n}\n```\n\n如果你不需要这个权限，你只需要把代码改为返回一个空数组即可。\n\n```ts {2}\nexport async function getAccessCodesApi() {\n  // 这里返回一个空数组即可\n  return [];\n}\n```\n"
  },
  {
    "path": "docs/src/guide/in-depth/theme.md",
    "content": "# 主题\n\n框架基于 [shadcn-vue](https://www.shadcn-vue.com/themes.html) 和 [tailwindcss](https://tailwindcss.com/) 构建，提供了丰富的主题配置，可以通过简单的配置实现各种主题切换，满足个性化需求。您可以选择使用 CSS 变量或 Tailwind CSS 实用程序类进行主题设置。\n\n## Css 变量\n\n项目遵循 [shadcn-vue](https://www.shadcn-vue.com/themes.html) 的主题配置，示例：\n\n```html\n<div class=\"bg-background text-foreground\" />\n```\n\n我们对颜色使用一个简单的约定。`background`变量用于组件的背景颜色，`foreground`变量用于文本颜色。\n\n以下组件的`background`将为`hsl(var(--primary))`，`foreground`将为`hsl(var(--primary-foreground))`。\n\n## 详细的CSS变量列表\n\n::: warning 注意\n\ncss 变量内的颜色，必须使用 `hsl` 格式，如 `0 0% 100%`，不需要加 `hsl()`和 `，`。\n\n:::\n\n你可以查看下面的CSS变量列表，以了解所有可用的变量。\n\n::: details 默认主题 css 变量\n\n```css\n:root {\n  --font-family:\n    -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, 'Helvetica Neue',\n    arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',\n    'Segoe UI Symbol', 'Noto Color Emoji';\n\n  /* Default background color of <body />...etc */\n  --background: 0 0% 100%;\n\n  /* 主体区域背景色 */\n  --background-deep: 216 20.11% 95.47%;\n  --foreground: 210 6% 21%;\n\n  /* Background color for <Card /> */\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n\n  /* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n\n  /* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n\n  /* 主题颜色 */\n\n  --primary: 212 100% 45%;\n  --primary-foreground: 0 0% 98%;\n\n  /* Used for destructive actions such as <Button variant=\"destructive\"> */\n\n  --destructive: 0 78% 68%;\n  --destructive-foreground: 0 0% 98%;\n\n  /* Used for success actions such as <message> */\n\n  --success: 144 57% 58%;\n  --success-foreground: 0 0% 98%;\n\n  /* Used for warning actions such as <message> */\n\n  --warning: 42 84% 61%;\n  --warning-foreground: 0 0% 98%;\n\n  /* Secondary colors for <Button /> */\n\n  --secondary: 240 5% 96%;\n  --secondary-foreground: 240 6% 10%;\n\n  /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */\n  --accent: 240 5% 96%;\n  --accent-hover: 200deg 10% 90%;\n  --accent-foreground: 240 6% 10%;\n\n  /* Darker color */\n  --heavy: 192deg 9.43% 89.61%;\n  --heavy-foreground: var(--accent-foreground);\n\n  /* Default border color */\n  --border: 240 5.9% 90%;\n\n  /* Border color for inputs such as <Input />, <Select />, <Textarea /> */\n  --input: 240deg 5.88% 90%;\n  --input-placeholder: 217 10.6% 65%;\n  --input-background: 0 0% 100%;\n\n  /* Used for focus ring */\n  --ring: 222.2 84% 4.9%;\n\n  /* Border radius for card, input and buttons */\n  --radius: 0.5rem;\n\n  /* ============= custom ============= */\n\n  /* 遮罩颜色 */\n  --overlay: 0deg 0% 0% / 30%;\n\n  /* 基本文字大小 */\n  --font-size-base: 16px;\n\n  /* =============component & UI============= */\n\n  /* menu */\n  --sidebar: 0 0% 100%;\n  --sidebar-deep: 216 20.11% 95.47%;\n  --menu: var(--sidebar);\n\n  /* header */\n  --header: 0 0% 100%;\n\n  accent-color: var(--primary);\n  color-scheme: light;\n}\n```\n\n:::\n\n::: details 默认主题黑暗模式 css 变量\n\n```css\n.dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  /* Default background color of <body />...etc */\n  --background: 222.34deg 10.43% 12.27%;\n\n  /* 主体区域背景色 */\n  --background-deep: 220deg 13.06% 9%;\n  --foreground: 0 0% 95%;\n\n  /* Background color for <Card /> */\n  --card: 222.34deg 10.43% 12.27%;\n\n  /* --card: 222.2 84% 4.9%; */\n  --card-foreground: 210 40% 98%;\n\n  /* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */\n  --popover: 222.82deg 8.43% 12.27%;\n  --popover-foreground: 210 40% 98%;\n\n  /* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */\n  --muted: 220deg 6.82% 17.25%;\n  --muted-foreground: 215 20.2% 65.1%;\n\n  /* 主题颜色 */\n\n  /* --primary: 245 82% 67%; */\n  --primary-foreground: 0 0% 98%;\n\n  /* Used for destructive actions such as <Button variant=\"destructive\"> */\n\n  --destructive: 0 78% 68%;\n  --destructive-foreground: 0 0% 98%;\n\n  /* Used for success actions such as <message> */\n\n  --success: 144 57% 58%;\n  --success-foreground: 0 0% 98%;\n\n  /* Used for warning actions such as <message> */\n\n  --warning: 42 84% 61%;\n  --warning-foreground: 0 0% 98%;\n\n  /* 颜色次要 */\n  --secondary: 240 5% 17%;\n  --secondary-foreground: 0 0% 98%;\n\n  /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */\n  --accent: 0deg 0% 100% / 8%;\n  --accent-hover: 0deg 0% 100% / 12%;\n  --accent-foreground: 0 0% 98%;\n\n  /* Darker color */\n  --heavy: 0deg 0% 100% / 12%;\n  --heavy-foreground: var(--accent-foreground);\n\n  /* Default border color */\n  --border: 240 3.7% 15.9%;\n\n  /* Border color for inputs such as <Input />, <Select />, <Textarea /> */\n  --input: 0deg 0% 100% / 10%;\n  --input-placeholder: 218deg 11% 65%;\n  --input-background: 0deg 0% 100% / 5%;\n\n  /* Used for focus ring */\n  --ring: 222.2 84% 4.9%;\n\n  /* 基本圆角大小 */\n  --radius: 0.5rem;\n\n  /* ============= Custom ============= */\n\n  /* 遮罩颜色 */\n  --overlay: 0deg 0% 0% / 40%;\n\n  /* 基本文字大小 */\n  --font-size-base: 16px;\n\n  /* =============component & UI============= */\n\n  --sidebar: 222.34deg 10.43% 12.27%;\n  --sidebar-deep: 220deg 13.06% 9%;\n  --menu: var(--sidebar);\n  --header: 222.34deg 10.43% 12.27%;\n\n  color-scheme: dark;\n}\n```\n\n:::\n\n## 覆盖默认的 CSS 变量\n\n你只需要在你的项目中覆盖你想要修改的 CSS 变量即可。例如，要更改默认卡片背景色，你可以在你的 CSS 文件中添加以下内容进行覆盖：\n\n### 默认主题下\n\n```css\n:root {\n  /* Background color for <Card /> */\n  --card: 0 0% 30%;\n}\n```\n\n### 黑暗模式下\n\n```css\n.dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  /* Background color for <Card /> */\n  --card: 222.34deg 10.43% 12.27%;\n}\n```\n\n## 更改品牌主色\n\n::: tip\n\n- 需要使用 `hsl` 格式颜色格式。\n- 修改后需要清空缓存才可生效。\n- 你可以借助 [第三方工具](https://www.w3schools.com/colors/colors_hsl.asp)来转换颜色。\n\n:::\n\n只需要在应用目录下的`preferences.ts`，自定义配置主色即可：\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  theme: {\n    // 错误色\n    colorDestructive: 'hsl(348 100% 61%)',\n    // 主题色\n    colorPrimary: 'hsl(212 100% 45%)',\n    // 成功色\n    colorSuccess: 'hsl(144 57% 58%)',\n    // 警告色\n    colorWarning: 'hsl(42 84% 61%)',\n  },\n});\n```\n\n## 内置主题\n\n框架中内置了多种主题，你可以在`preferences.ts`中进行配置：\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  theme: {\n    builtinType: 'default',\n  },\n});\n```\n\n### 内置主题列表\n\n框架内置了 16种主题，且还支持自定义主题。理论上，你可以无限制的扩展主题。\n\n::: details 内置主题类型列表\n\n```ts\ntype BuiltinThemeType =\n  | 'custom'\n  | 'deep-blue'\n  | 'deep-green'\n  | 'default'\n  | 'gray'\n  | 'green'\n  | 'neutral'\n  | 'orange'\n  | 'pink'\n  | 'red'\n  | 'rose'\n  | 'sky-blue'\n  | 'slate'\n  | 'stone'\n  | 'violet'\n  | 'yellow'\n  | 'zinc'\n  | (Record<never, never> & string);\n```\n\n:::\n\n::: details 内置主题css变量 - light\n\n```css\n:root {\n  --font-family:\n    -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, 'Helvetica Neue',\n    arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',\n    'Segoe UI Symbol', 'Noto Color Emoji';\n\n  /* Default background color of <body />...etc */\n  --background: 0 0% 100%;\n\n  /* 主体区域背景色 */\n  --background-deep: 216 20.11% 95.47%;\n  --foreground: 222 84% 5%;\n\n  /* Background color for <Card /> */\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n\n  /* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n\n  /* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */\n\n  /* --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%; */\n\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n\n  /* 主题颜色 */\n\n  --primary: 212 100% 45%;\n  --primary-foreground: 0 0% 98%;\n\n  /* Used for destructive actions such as <Button variant=\"destructive\"> */\n\n  --destructive: 0 78% 68%;\n  --destructive-foreground: 0 0% 98%;\n\n  /* Used for success actions such as <message> */\n\n  --success: 144 57% 58%;\n  --success-foreground: 0 0% 98%;\n\n  /* Used for warning actions such as <message> */\n\n  --warning: 42 84% 61%;\n  --warning-foreground: 0 0% 98%;\n\n  /* Secondary colors for <Button /> */\n\n  --secondary: 240 5% 96%;\n  --secondary-foreground: 240 6% 10%;\n\n  /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */\n  --accent: 240 5% 96%;\n  --accent-hover: 200deg 10% 90%;\n  --accent-foreground: 240 6% 10%;\n\n  /* Darker color */\n  --heavy: 192deg 9.43% 89.61%;\n  --heavy-foreground: var(--accent-foreground);\n\n  /* Default border color */\n  --border: 240 5.9% 90%;\n\n  /* Border color for inputs such as <Input />, <Select />, <Textarea /> */\n  --input: 240deg 5.88% 90%;\n  --input-placeholder: 217 10.6% 65%;\n  --input-background: 0 0% 100%;\n\n  /* Used for focus ring */\n  --ring: 222.2 84% 4.9%;\n\n  /* Border radius for card, input and buttons */\n  --radius: 0.5rem;\n\n  /* ============= custom ============= */\n\n  /* 遮罩颜色 */\n  --overlay: 0deg 0% 0% / 30%;\n\n  /* 基本文字大小 */\n  --font-size-base: 16px;\n\n  /* =============component & UI============= */\n\n  /* menu */\n  --sidebar: 0 0% 100%;\n  --sidebar-deep: 0 0% 100%;\n  --menu: var(--sidebar);\n\n  /* header */\n  --header: 0 0% 100%;\n\n  accent-color: var(--primary);\n  color-scheme: light;\n}\n\n[data-theme='violet'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 224 71.4% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 224 71.4% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 224 71.4% 4.1%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 220 14.3% 95.9%;\n  --secondary-foreground: 220.9 39.3% 11%;\n  --muted: 220 14.3% 95.9%;\n  --muted-foreground: 220 8.9% 46.1%;\n  --accent: 220 14.3% 95.9%;\n  --accent-foreground: 220.9 39.3% 11%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 220 13% 91%;\n  --input: 220 13% 91%;\n  --ring: 262.1 83.3% 57.8%;\n}\n\n[data-theme='pink'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 346.8 77.2% 49.8%;\n}\n\n[data-theme='rose'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 346.8 77.2% 49.8%;\n}\n\n[data-theme='sky-blue'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 222.2 84% 4.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n  --primary-foreground: 210 40% 98%;\n  --secondary: 210 40% 96.1%;\n  --secondary-foreground: 222.2 47.4% 11.2%;\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n  --accent: 210 40% 96.1%;\n  --accent-foreground: 222.2 47.4% 11.2%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 214.3 31.8% 91.4%;\n  --input: 214.3 31.8% 91.4%;\n  --ring: 221.2 83.2% 53.3%;\n}\n\n[data-theme='deep-blue'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 222.2 84% 4.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n  --primary-foreground: 210 40% 98%;\n  --secondary: 210 40% 96.1%;\n  --secondary-foreground: 222.2 47.4% 11.2%;\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n  --accent: 210 40% 96.1%;\n  --accent-foreground: 222.2 47.4% 11.2%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 214.3 31.8% 91.4%;\n  --input: 214.3 31.8% 91.4%;\n  --ring: 221.2 83.2% 53.3%;\n}\n\n[data-theme='green'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 142.1 76.2% 36.3%;\n}\n\n[data-theme='deep-green'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 142.1 76.2% 36.3%;\n}\n\n[data-theme='orange'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 20 14.3% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 20 14.3% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 20 14.3% 4.1%;\n  --primary-foreground: 60 9.1% 97.8%;\n  --secondary: 60 4.8% 95.9%;\n  --secondary-foreground: 24 9.8% 10%;\n  --muted: 60 4.8% 95.9%;\n  --muted-foreground: 25 5.3% 44.7%;\n  --accent: 60 4.8% 95.9%;\n  --accent-foreground: 24 9.8% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 20 5.9% 90%;\n  --input: 20 5.9% 90%;\n  --ring: 24.6 95% 53.1%;\n}\n\n[data-theme='yellow'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 20 14.3% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 20 14.3% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 20 14.3% 4.1%;\n  --primary-foreground: 26 83.3% 14.1%;\n  --secondary: 60 4.8% 95.9%;\n  --secondary-foreground: 24 9.8% 10%;\n  --muted: 60 4.8% 95.9%;\n  --muted-foreground: 25 5.3% 44.7%;\n  --accent: 60 4.8% 95.9%;\n  --accent-foreground: 24 9.8% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 20 5.9% 90%;\n  --input: 20 5.9% 90%;\n  --ring: 20 14.3% 4.1%;\n}\n\n[data-theme='zinc'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 0 0% 98%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 240 5.9% 10%;\n}\n\n[data-theme='neutral'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 0 0% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 0 0% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 0 0% 3.9%;\n  --primary-foreground: 0 0% 98%;\n  --secondary: 0 0% 96.1%;\n  --secondary-foreground: 0 0% 9%;\n  --muted: 0 0% 96.1%;\n  --muted-foreground: 0 0% 45.1%;\n  --accent: 0 0% 96.1%;\n  --accent-foreground: 0 0% 9%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 0 0% 89.8%;\n  --input: 0 0% 89.8%;\n  --ring: 0 0% 3.9%;\n}\n\n[data-theme='slate'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 222.2 84% 4.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n  --primary-foreground: 210 40% 98%;\n  --secondary: 210 40% 96.1%;\n  --secondary-foreground: 222.2 47.4% 11.2%;\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n  --accent: 210 40% 96.1%;\n  --accent-foreground: 222.2 47.4% 11.2%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 214.3 31.8% 91.4%;\n  --input: 214.3 31.8% 91.4%;\n  --ring: 222.2 84% 4.9%;\n}\n\n[data-theme='gray'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 224 71.4% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 224 71.4% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 224 71.4% 4.1%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 220 14.3% 95.9%;\n  --secondary-foreground: 220.9 39.3% 11%;\n  --muted: 220 14.3% 95.9%;\n  --muted-foreground: 220 8.9% 46.1%;\n  --accent: 220 14.3% 95.9%;\n  --accent-foreground: 220.9 39.3% 11%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 220 13% 91%;\n  --input: 220 13% 91%;\n  --ring: 224 71.4% 4.1%;\n}\n```\n\n:::\n\n::: details 内置主题css变量 - dark\n\n```css\n.dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  /* Default background color of <body />...etc */\n  --background: 222.34deg 10.43% 12.27%;\n\n  /* 主体区域背景色 */\n  --background-deep: 220deg 13.06% 9%;\n  --foreground: 0 0% 95%;\n\n  /* Background color for <Card /> */\n  --card: 222.34deg 10.43% 12.27%;\n\n  /* --card: 222.2 84% 4.9%; */\n  --card-foreground: 210 40% 98%;\n\n  /* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */\n  --popover: 222.82deg 8.43% 12.27%;\n  --popover-foreground: 210 40% 98%;\n\n  /* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */\n\n  /* --muted: 220deg 6.82% 17.25%; */\n\n  /* --muted-foreground: 215 20.2% 65.1%; */\n\n  --muted: 240 3.7% 15.9%;\n  --muted-foreground: 240 5% 64.9%;\n\n  /* 主题颜色 */\n\n  /* --primary: 245 82% 67%; */\n  --primary-foreground: 0 0% 98%;\n\n  /* Used for destructive actions such as <Button variant=\"destructive\"> */\n\n  --destructive: 0 78% 68%;\n  --destructive-foreground: 0 0% 98%;\n\n  /* Used for success actions such as <message> */\n\n  --success: 144 57% 58%;\n  --success-foreground: 0 0% 98%;\n\n  /* Used for warning actions such as <message> */\n\n  --warning: 42 84% 61%;\n  --warning-foreground: 0 0% 98%;\n\n  /* 颜色次要 */\n  --secondary: 240 5% 17%;\n  --secondary-foreground: 0 0% 98%;\n\n  /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */\n  --accent: 216 5% 19%;\n  --accent-hover: 216 5% 24%;\n  --accent-foreground: 0 0% 98%;\n\n  /* Darker color */\n  --heavy: 216 5% 24%;\n  --heavy-foreground: var(--accent-foreground);\n\n  /* Default border color */\n  --border: 240 3.7% 22%;\n\n  /* Border color for inputs such as <Input />, <Select />, <Textarea /> */\n  --input: 0deg 0% 100% / 10%;\n  --input-placeholder: 218deg 11% 65%;\n  --input-background: 0deg 0% 100% / 5%;\n\n  /* Used for focus ring */\n  --ring: 222.2 84% 4.9%;\n\n  /* 基本圆角大小 */\n  --radius: 0.5rem;\n\n  /* ============= Custom ============= */\n\n  /* 遮罩颜色 */\n  --overlay: 0deg 0% 0% / 40%;\n\n  /* 基本文字大小 */\n  --font-size-base: 16px;\n\n  /* =============component & UI============= */\n\n  --sidebar: 222.34deg 10.43% 12.27%;\n  --sidebar-deep: 220deg 13.06% 9%;\n  --menu: var(--sidebar);\n\n  /* header */\n  --header: 222.34deg 10.43% 12.27%;\n\n  color-scheme: dark;\n}\n\n.dark[data-theme='violet'],\n[data-theme='violet'] .dark {\n  --background: 224 71.4% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 210 20% 98%;\n  --card: 224 71.4% 4.1%;\n  --card-foreground: 210 20% 98%;\n  --popover: 224 71.4% 4.1%;\n  --popover-foreground: 210 20% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 215 27.9% 16.9%;\n  --secondary-foreground: 210 20% 98%;\n  --muted: 215 27.9% 16.9%;\n  --muted-foreground: 217.9 10.6% 64.9%;\n  --accent: 215 27.9% 16.9%;\n  --accent-foreground: 210 20% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 215 27.9% 16.9%;\n  --input: 215 27.9% 16.9%;\n  --ring: 263.4 70% 50.4%;\n  --sidebar: 224 71.4% 4.1%;\n  --sidebar-deep: 224 71.4% 4.1%;\n  --header: 224 71.4% 4.1%;\n}\n\n.dark[data-theme='pink'],\n[data-theme='pink'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 95%;\n  --card: 0 0% 9%;\n  --card-foreground: 0 0% 95%;\n  --popover: 0 0% 9%;\n  --popover-foreground: 0 0% 95%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 15%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 85.7% 97.3%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 346.8 77.2% 49.8%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='rose'],\n[data-theme='rose'] .dark {\n  --background: 0 0% 3.9%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 98%;\n  --card: 0 0% 3.9%;\n  --card-foreground: 0 0% 98%;\n  --popover: 0 0% 3.9%;\n  --popover-foreground: 0 0% 98%;\n  --primary-foreground: 0 85.7% 97.3%;\n  --secondary: 0 0% 14.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 14.9%;\n  --muted-foreground: 0 0% 63.9%;\n  --accent: 0 0% 14.9%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 0 0% 14.9%;\n  --input: 0 0% 14.9%;\n  --ring: 0 72.2% 50.6%;\n  --sidebar: 0 0% 3.9%;\n  --sidebar-deep: 0 0% 3.9%;\n  --header: 0 0% 3.9%;\n}\n\n.dark[data-theme='sky-blue'],\n[data-theme='sky-blue'] .dark {\n  --background: 222.2 84% 4.9%;\n  --background-deep: var(--background);\n  --foreground: 210 40% 98%;\n  --card: 222.2 84% 4.9%;\n  --card-foreground: 210 40% 98%;\n  --popover: 222.2 84% 4.9%;\n  --popover-foreground: 210 40% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 217.2 32.6% 17.5%;\n  --secondary-foreground: 210 40% 98%;\n  --muted: 217.2 32.6% 17.5%;\n  --muted-foreground: 215 20.2% 65.1%;\n  --accent: 217.2 32.6% 17.5%;\n  --accent-foreground: 210 40% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 217.2 32.6% 17.5%;\n  --input: 217.2 32.6% 17.5%;\n  --ring: 224.3 76.3% 48%;\n  --sidebar: 222.2 84% 4.9%;\n  --sidebar-deep: 222.2 84% 4.9%;\n  --header: 222.2 84% 4.9%;\n}\n\n.dark[data-theme='deep-blue'],\n[data-theme='deep-blue'] .dark {\n  --background: 222.2 84% 4.9%;\n  --background-deep: var(--background);\n  --foreground: 210 40% 98%;\n  --card: 222.2 84% 4.9%;\n  --card-foreground: 210 40% 98%;\n  --popover: 222.2 84% 4.9%;\n  --popover-foreground: 210 40% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 217.2 32.6% 17.5%;\n  --secondary-foreground: 210 40% 98%;\n  --muted: 217.2 32.6% 17.5%;\n  --muted-foreground: 215 20.2% 65.1%;\n  --accent: 217.2 32.6% 17.5%;\n  --accent-foreground: 210 40% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 217.2 32.6% 17.5%;\n  --input: 217.2 32.6% 17.5%;\n  --ring: 224.3 76.3% 48%;\n  --sidebar: 222.2 84% 4.9%;\n  --sidebar-deep: 222.2 84% 4.9%;\n  --header: 222.2 84% 4.9%;\n}\n\n.dark[data-theme='green'],\n[data-theme='green'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 95%;\n  --card: 24 9.8% 6%;\n  --card-foreground: 0 0% 95%;\n  --popover: 0 0% 9%;\n  --popover-foreground: 0 0% 95%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 15%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 85.7% 97.3%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 142.4 71.8% 29.2%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='deep-green'],\n[data-theme='deep-green'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 95%;\n  --card: 24 9.8% 6%;\n  --card-foreground: 0 0% 95%;\n  --popover: 0 0% 9%;\n  --popover-foreground: 0 0% 95%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 15%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 85.7% 97.3%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 142.4 71.8% 29.2%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='orange'],\n[data-theme='orange'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 60 9.1% 97.8%;\n  --card: 20 14.3% 4.1%;\n  --card-foreground: 60 9.1% 97.8%;\n  --popover: 20 14.3% 4.1%;\n  --popover-foreground: 60 9.1% 97.8%;\n  --primary-foreground: 60 9.1% 97.8%;\n  --secondary: 12 6.5% 15.1%;\n  --secondary-foreground: 60 9.1% 97.8%;\n  --muted: 12 6.5% 15.1%;\n  --muted-foreground: 24 5.4% 63.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 60 9.1% 97.8%;\n  --destructive: 0 72.2% 50.6%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 12 6.5% 15.1%;\n  --input: 12 6.5% 15.1%;\n  --ring: 20.5 90.2% 48.2%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='yellow'],\n[data-theme='yellow'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 60 9.1% 97.8%;\n  --card: 20 14.3% 4.1%;\n  --card-foreground: 60 9.1% 97.8%;\n  --popover: 20 14.3% 4.1%;\n  --popover-foreground: 60 9.1% 97.8%;\n  --primary-foreground: 26 83.3% 14.1%;\n  --secondary: 12 6.5% 15.1%;\n  --secondary-foreground: 60 9.1% 97.8%;\n  --muted: 12 6.5% 15.1%;\n  --muted-foreground: 24 5.4% 63.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 60 9.1% 97.8%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 12 6.5% 15.1%;\n  --input: 12 6.5% 15.1%;\n  --ring: 35.5 91.7% 32.9%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='zinc'],\n[data-theme='zinc'] .dark {\n  --background: 240 10% 3.9%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 98%;\n  --card: 240 10% 3.9%;\n  --card-foreground: 0 0% 98%;\n  --popover: 240 10% 3.9%;\n  --popover-foreground: 0 0% 98%;\n  --primary-foreground: 240 5.9% 10%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 240 3.7% 15.9%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 240 3.7% 15.9%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 240 4.9% 83.9%;\n  --sidebar: 240 10% 3.9%;\n  --sidebar-deep: 240 10% 3.9%;\n  --header: 240 4.9% 83.9%;\n}\n\n.dark[data-theme='neutral'],\n[data-theme='neutral'] .dark {\n  --background: 0 0% 3.9%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 98%;\n  --card: 0 0% 3.9%;\n  --card-foreground: 0 0% 98%;\n  --popover: 0 0% 3.9%;\n  --popover-foreground: 0 0% 98%;\n  --primary-foreground: 0 0% 9%;\n  --secondary: 0 0% 14.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 14.9%;\n  --muted-foreground: 0 0% 63.9%;\n  --accent: 0 0% 14.9%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 0 0% 14.9%;\n  --input: 0 0% 14.9%;\n  --ring: 0 0% 83.1%;\n  --sidebar: 0 0% 3.9%;\n  --sidebar-deep: 0 0% 3.9%;\n  --header: 0 0% 3.9%;\n}\n\n.dark[data-theme='slate'],\n[data-theme='slate'] .dark {\n  --background: 222.2 84% 4.9%;\n  --background-deep: var(--background);\n  --foreground: 210 40% 98%;\n  --card: 222.2 84% 4.9%;\n  --card-foreground: 210 40% 98%;\n  --popover: 222.2 84% 4.9%;\n  --popover-foreground: 210 40% 98%;\n  --primary-foreground: 222.2 47.4% 11.2%;\n  --secondary: 217.2 32.6% 17.5%;\n  --secondary-foreground: 210 40% 98%;\n  --muted: 217.2 32.6% 17.5%;\n  --muted-foreground: 215 20.2% 65.1%;\n  --accent: 217.2 32.6% 17.5%;\n  --accent-foreground: 210 40% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 217.2 32.6% 17.5%;\n  --input: 217.2 32.6% 17.5%;\n  --ring: 212.7 26.8% 83.9;\n  --sidebar: 222.2 84% 4.9%;\n  --sidebar-deep: 222.2 84% 4.9%;\n  --header: 222.2 84% 4.9%;\n}\n\n.dark[data-theme='gray'],\n[data-theme='gray'] .dark {\n  --background: 224 71.4% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 210 20% 98%;\n  --card: 224 71.4% 4.1%;\n  --card-foreground: 210 20% 98%;\n  --popover: 224 71.4% 4.1%;\n  --popover-foreground: 210 20% 98%;\n  --primary-foreground: 220.9 39.3% 11%;\n  --secondary: 215 27.9% 16.9%;\n  --secondary-foreground: 210 20% 98%;\n  --muted: 215 27.9% 16.9%;\n  --muted-foreground: 217.9 10.6% 64.9%;\n  --accent: 215 27.9% 16.9%;\n  --accent-foreground: 210 20% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 215 27.9% 16.9%;\n  --input: 215 27.9% 16.9%;\n  --ring: 216 12.2% 83.9%;\n  --sidebar: 224 71.4% 4.1%;\n  --sidebar-deep: 224 71.4% 4.1%;\n  --header: 224 71.4% 4.1%;\n}\n```\n\n:::\n\n## 新增主题\n\n想要新增主题，只需按照以下步骤进行：\n\n- 在应用的 `src/preferences.ts`内新增一个主题配置。\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  theme: {\n    builtinType: 'my-theme',\n  },\n});\n```\n\n- 在你的css文件中，新增主题的css变量。\n\n```css\n/* light */\n[data-theme='my-theme'] {\n  --foreground: 224 71.4% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 224 71.4% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 224 71.4% 4.1%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 220 14.3% 95.9%;\n  --secondary-foreground: 220.9 39.3% 11%;\n  --muted: 220 14.3% 95.9%;\n  --muted-foreground: 220 8.9% 46.1%;\n  --accent: 220 14.3% 95.9%;\n  --accent-foreground: 220.9 39.3% 11%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 220 13% 91%;\n  --input: 220 13% 91%;\n  --ring: 262.1 83.3% 57.8%;\n}\n\n/* dark */\n.dark[data-theme='my-theme'],\n[data-theme='my-theme'] .dark {\n  --background: 224 71.4% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 210 20% 98%;\n  --card: 224 71.4% 4.1%;\n  --card-foreground: 210 20% 98%;\n  --popover: 224 71.4% 4.1%;\n  --popover-foreground: 210 20% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 215 27.9% 16.9%;\n  --secondary-foreground: 210 20% 98%;\n  --muted: 215 27.9% 16.9%;\n  --muted-foreground: 217.9 10.6% 64.9%;\n  --accent: 215 27.9% 16.9%;\n  --accent-foreground: 210 20% 98%;\n  --destructive: 0 62.8% 30.6%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 215 27.9% 16.9%;\n  --input: 215 27.9% 16.9%;\n  --ring: 263.4 70% 50.4%;\n  --sidebar: 224 71.4% 4.1%;\n  --sidebar-deep: 224 71.4% 4.1%;\n}\n```\n\n## 黑暗模式\n\n框架中内置了多种主题，你可以在`preferences.ts`中进行配置，黑暗主题同样会读取css变量来进行配置：\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  theme: {\n    mode: 'dark',\n  },\n});\n```\n\n## 自定义侧边栏颜色\n\n侧边栏颜色通过`--sidebar`变量来配置\n\n### 默认主题下\n\n```css\n:root {\n  --sidebar: 0 0% 100%;\n}\n```\n\n### 黑暗模式下\n\n```css\n.dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  --sidebar: 222.34deg 10.43% 12.27%;\n}\n```\n\n## 自定义顶栏颜色\n\n侧边栏颜色通过`--header`变量来配置\n\n### 默认主题下\n\n```css\n:root {\n  --header: 0 0% 100%;\n}\n```\n\n### 黑暗模式下\n\n```css\n.dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  --header: 222.34deg 10.43% 12.27%;\n}\n```\n\n## 色弱模式\n\n一般用于特殊场景，将设置为色弱模式，你可以在`preferences.ts`中进行配置：\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    colorWeakMode: true,\n  },\n});\n```\n\n## 灰色模式\n\n一般用于特殊场景，将网页置灰，你可以在`preferences.ts`中进行配置：\n\n```ts\nimport { defineOverridesPreferences } from '@vben/preferences';\n\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    colorGrayMode: true,\n  },\n});\n```\n"
  },
  {
    "path": "docs/src/guide/in-depth/ui-framework.md",
    "content": "# 组件库切换\n\n`Vue Admin` 支持你自由选择组件库，目前演示站点的默认组件库是 `Ant Design Vue`，与旧版本保持一致。同时框架还内置了 `Element Plus` 版本和 `Naive UI` 版本，你可以根据自己的喜好选择。\n\n## 新增组件库应用\n\n如果你想用其他别的组件库，你只需要按以下步骤进行操作：\n\n1. 在`apps`内创建一个新的文件夹，例如`apps/web-xxx`。\n2. 更改`apps/web-xxx/package.json`的`name`字段为`web-xxx`。\n3. 移除其他组件库依赖及代码，并用你的组件库进行替换相应逻辑，需要改动的地方不多。\n4. 调整`locales`内的语言文件。\n5. 调整 `app.vue` 内的组件。\n6. 自行适配组件库的主题，与 `Vben Admin` 契合。\n7. 调整 `.env` 内的应用名\n8. 在大仓根目录增加 `dev:xxx` 脚本\n9. 执行 `pnpm install` 安装依赖\n"
  },
  {
    "path": "docs/src/guide/introduction/changelog.md",
    "content": "# 更新日志\n\nTODO\n"
  },
  {
    "path": "docs/src/guide/introduction/quick-start.md",
    "content": "---\noutline: deep\n---\n\n# 快速开始 {#quick-start}\n\n## 前置准备\n\n::: info 环境要求\n\n在启动项目前，你需要确保你的环境满足以下要求：\n\n- [Node.js](https://nodejs.org/en) 20.15.0 及以上版本，推荐使用 [fnm](https://github.com/Schniz/fnm) 、 [nvm](https://github.com/nvm-sh/nvm) 或者直接使用[pnpm](https://pnpm.io/cli/env) 进行版本管理。\n- [Git](https://git-scm.com/) 任意版本。\n\n验证你的环境是否满足以上要求，你可以通过以下命令查看版本：\n\n```bash\n# 出现相应 node LTS版本即可\nnode -v\n# 出现相应 git 版本即可\ngit -v\n```\n\n:::\n\n## 启动项目\n\n### 获取源码\n\n::: code-group\n\n```sh [GitHub]\n# clone 代码\ngit clone https://github.com/vbenjs/vue-vben-admin.git\n```\n\n```sh [Gitee]\n# clone 代码\n# Gitee 的代码可能不是最新的\ngit clone https://gitee.com/annsion/vue-vben-admin.git\n```\n\n:::\n\n::: danger 注意\n\n注意存放代码的目录及所有父级目录不能存在中文、韩文、日文以及空格，否则安装依赖后启动会出错。\n\n:::\n\n### 安装依赖\n\n在你的代码目录内打开终端，并执行以下命令:\n\n```bash\n# 进入项目目录\ncd vue-vben-admin\n\n# 使用项目指定的pnpm版本进行依赖安装\nnpm i -g corepack\n\n# 安装依赖\npnpm install\n```\n\n::: tip 注意\n\n- 项目只支持使用 `pnpm` 进行依赖安装，默认会使用 `corepack` 来安装指定版本的 `pnpm`。:\n- 如果你的网络环境无法访问npm源，你可以设置系统的环境变量`COREPACK_NPM_REGISTRY=https://registry.npmmirror.com`，然后再执行`pnpm install`。\n- 如果你不想使用`corepack`，你需要禁用`corepack`，然后使用你自己的`pnpm`进行安装。\n\n:::\n\n### 运行项目\n\n#### 选择项目\n\n执行以下命令运行项目:\n\n```bash\n# 启动项目\npnpm dev\n```\n\n此时，你会看到类似如下的输出，选择你需要运行的项目：\n\n```bash\n│\n◆  Select the app you need to run [dev]:\n│  ● @vben/web-antd\n│  ○ @vben/web-antdv-next\n│  ○ @vben/web-ele\n│  ○ @vben/web-naive\n│  ○ @vben/docs\n│  ● @vben/playground\n└\n```\n\n现在，你可以在浏览器访问 `http://localhost:5555` 查看项目。\n\n#### 运行指定项目\n\n如果你不想选择项目，可以直接运行以下命令运行你需要的应用：\n\n```bash\npnpm run dev:antd\npnpm run dev:ele\npnpm run dev:naive\npnpm run dev:docs\npnpm run dev:play\n```\n"
  },
  {
    "path": "docs/src/guide/introduction/roadmap.md",
    "content": "# 路线图\n\nTODO:\n"
  },
  {
    "path": "docs/src/guide/introduction/thin.md",
    "content": "---\noutline: deep\n---\n\n# 精简版本\n\n从 `5.0` 版本开始，我们不再提供精简的仓库或者分支。我们的目标是提供一个更加一致的开发体验，同时减少维护成本。在这里，我们将如何介绍自己的项目，如何去精简以及移除不需要的功能。\n\n## 应用精简\n\n首先，确认你需要的 `UI` 组件库版本，然后删除对应的应用，比如你选择使用 `Ant Design Vue`，那么你可以删除其他应用， 只需要删除下面两个文件夹即可：\n\n```bash\napps/web-ele\napps/web-naive\n\n```\n\n::: tip\n\n如果项目没有内置你需要的 `UI` 组件库应用，你可以直接全部删除其他应用。然后自行新建应用即可。\n\n:::\n\n## 演示代码精简\n\n如果你不需要演示代码，你可以直接删除 `playground` 文件夹。\n\n## 文档精简\n\n如果你不需要文档，你可以直接删除`docs`文件夹。\n\n## Mock 服务精简\n\n如果你不需要`Mock`服务，你可以直接删除`apps/backend-mock`文件夹。同时在你的应用下`.env.development`文件中删除`VITE_NITRO_MOCK`变量。\n\n```bash\n# 是否开启 Nitro Mock服务，true 为开启，false 为关闭\nVITE_NITRO_MOCK=false\n```\n\n## 安装依赖\n\n到这里，你已经完成了精简操作，接下来你可以安装依赖，并启动你的项目：\n\n```bash\n# 根目录下执行\npnpm install\n\n```\n\n## 命令调整\n\n在精简后，你可能需要根据你的项目调整命令，在根目录下的`package.json`文件中，你可以调整`scripts`字段，移除你不需要的命令。\n\n```json\n{\n  \"scripts\": {\n    \"build:antd\": \"pnpm run build --filter=@vben/web-antd\",\n    \"build:docs\": \"pnpm run build --filter=@vben/docs\",\n    \"build:ele\": \"pnpm run build --filter=@vben/web-ele\",\n    \"build:naive\": \"pnpm run build --filter=@vben/web-naive\",\n    \"build:tdesign\": \"pnpm run build --filter=@vben/web-tdesign\",\n    \"build:play\": \"pnpm run build --filter=@vben/playground\",\n    \"dev:antd\": \"pnpm -F @vben/web-antd run dev\",\n    \"dev:docs\": \"pnpm -F @vben/docs run dev\",\n    \"dev:ele\": \"pnpm -F @vben/web-ele run dev\",\n    \"dev:play\": \"pnpm -F @vben/playground run dev\",\n    \"dev:naive\": \"pnpm -F @vben/web-naive run dev\"\n  }\n}\n```\n\n## 其他\n\n如果你想更进一步精简，你可以删除参考以下文件或者文件夹的作用，判断自己是否需要，不需要删除即可：\n\n- `.changeset` 文件夹用于管理版本变更\n- `.github` 文件夹用于存放 GitHub 的配置文件\n- `.vscode` 文件夹用于存放 VSCode 的配置文件，如果你使用其他编辑器，可以删除\n- `./scripts/deploy` 文件夹用于存放部署脚本，如果你不需要docker部署，可以删除\n\n## 应用精简\n\n当你确定了某个应用，你还可以进一步精简：\n\n### 删除不需要的路由及页面\n\n- 在应用的 `src/router/routes` 文件中，你可以删除不需要的路由。其中 `core` 文件夹内，如果只需要登录和忘记密码，你可以删除其他路由，如忘记密码、注册等。路由删除后，你可以删除对应的页面文件，在 `src/views/_core` 文件夹中。\n\n- 在应用的 `src/router/routes` 文件中，你可以按需求删除不需要的路由，如`demos`、`vben` 目录等。路由删除后，你可以在 `src/views` 文件夹中删除对应的页面文件。\n\n### 删除不需要的组件\n\n- 在应用的 `packages/effects/common-ui/src/ui` 文件夹中，你可以删除不需要的组件，如`about`、`dashboard` 目录等。删除之前请先确保你的路由中没有引用到这些组件。\n"
  },
  {
    "path": "docs/src/guide/introduction/vben.md",
    "content": "# 关于 Vben Admin\n\n::: info 你正在阅读的是 [Vben Admin](https://github.com/vbenjs/vue-vben-admin) `5.0`版本的文档！\n\n- Vben Admin 2.x 目前已存档，仅进行重大问题修复。\n- 新版本与旧版本不兼容，如果你使用的是旧版本（v2、v3），请查看 [Vue Vben Admin 2.x 文档](https://doc.vvbin.cn)\n- 如发现文档有误，欢迎提交 [issue](https://github.com/vbenjs/vue-vben-admin/issues) 帮助我们改进。\n- 如果你只是想体验一下，你可以查看[快速开始](./quick-start.md)。\n\n:::\n\n[Vben Admin](https://github.com/vbenjs/vue-vben-admin) 是一个基于 [Vue3.0](https://github.com/vuejs/core)、[Vite](https://github.com/vitejs/vite)、 [TypeScript](https://www.typescriptlang.org/) 的中后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模板，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 `vue3`、`vite`、`ts` 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。\n\n## 特点\n\n- **最新技术栈**：使用 `Vue3`、`Vite`、`TypeScript` 等前端前沿技术开发。\n- **国际化**：内置完善的国际化方案，支持多语言切换。\n- **权限验证**：完善的权限验证方案，按钮级别权限控制。\n- **多主题**：内置多种主题配置和黑暗模式，满足个性化需求。\n- **动态菜单**：支持动态菜单，可以根据权限配置显示菜单。\n- **Mock 数据**：基于 `Nitro` 的本地高性能 Mock 数据方案。\n- **组件丰富**：提供了丰富的组件，可以满足大部分的业务需求。\n- **规范**：代码规范，使用 `Oxfmt`、`Oxlint`、`ESLint`、`Stylelint`、`Publint`、`CSpell` 等工具保证代码质量。\n- **工程化**：使用 `Pnpm Monorepo`、`Turborepo`、`Changeset` 等工具，提高开发效率。\n- **多UI库支持**：支持 `Ant Design Vue`、`Ant Design Vue Next`、`Element Plus`、`Naive UI`、`TDesign` 等主流 UI 库，不再限制于特定框架。\n\n## 浏览器支持\n\n- **本地开发**推荐使用`Chrome 最新版`浏览器，**不支持**`Chrome 80`以下版本。\n\n- **生产环境**支持现代浏览器，不支持 IE。\n\n| [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png\" alt=\"IE\" width=\"24px\" height=\"24px\"  />](http://godban.github.io/browsers-support-badges/)IE | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png\" alt=\" Edge\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)Edge | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png\" alt=\"Firefox\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)Firefox | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png\" alt=\"Chrome\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)Chrome | [<img src=\"https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png\" alt=\"Safari\" width=\"24px\" height=\"24px\" />](http://godban.github.io/browsers-support-badges/)Safari |\n| :-: | :-: | :-: | :-: | :-: |\n| 不支持 | last 2 versions | last 2 versions | last 2 versions | last 2 versions |\n\n## 贡献\n\n- [Vben Admin](https://github.com/vbenjs/vue-vben-admin) 还在持续更新中，本项目欢迎您的参与，共同维护，逐步完善，打造更好的中后台解决方案。\n- 如果你有兴趣加入我们，可以通过以下方式开始，我们会根据你的活跃度邀请你加入。\n\n::: info 加入我们\n\n- 长期提交 `PR`。\n- 提供有价值的建议。\n- 参与讨论，帮助解决 `issue`。\n- 共同维护文档。\n\n:::\n"
  },
  {
    "path": "docs/src/guide/introduction/why.md",
    "content": "# 为什么选择我们?\n\n::: info 写在前面\n\n我们不会去和其他框架做比较。我们认为每个框架都有自己的特点，适合不同的场景。我们的目标是提供一个简单、易用的框架，让开发者可以快速上手，专注于业务逻辑的开发。所以我们只会不断完善和优化我们的框架，提供更好的体验。\n\n:::\n\n我们致力于为开发者提供一个高效、现代、易用的前端框架。我们的解决方案基于最新的技术栈，如 Vue3、Vite 和 TypeScript，确保您在构建项目时始终走在技术的前沿。同时，我们注重代码的质量与规范，通过严格的工具链保证代码的一致性和可维护性。无论是初创项目还是企业级应用，我们的框架都能帮助您快速构建、迭代和部署。\n\n## 框架历程\n\n从 Vue Vben Admin 1.x 版本开始，框架经历了许多迭代和优化。从一开始使用 `Vite 0.x` 版本，没有现成的插件，开发了很多自定义插件来弥合 Webpack 和 Vite 之间的差异。虽然很多现在已经被代替，但是我们的初衷一直没有变，就是提供一个简单、易用的框架。\n\n虽然中间有段时间由社区维护，但我们一直密切关注 Vue Vben Admin 的发展。见证了许多开发者使用 Vben Admin，并提供了许多宝贵的建议和反馈。非常感谢大家的支持和贡献，这些都是我们持续改进 Vben Admin 的动力。新版本中，我们持续收集用户反馈，重新开始，不断优化框架，以提供更好的用户体验。我们的目标是让开发者能够快速上手，专注于业务逻辑的开发。\n\n## 单元测试\n\n单元测试是确保代码质量的基石。在开发过程中编写和执行单元测试，以捕捉潜在的错误并提升代码的可靠性。框架核心逻辑使用 `vitest` 做了单元测试，并在逐步增加覆盖率。通过单元测试，可以放心地进行代码重构，减少回归问题，从而提高整体开发效率。\n\n## 质量与规范\n\n我们始终高度重视代码的质量与规范。通过使用 Oxfmt、Oxlint、ESLint、Stylelint、Publint、CSpell 等工具来确保代码质量。我们的代码规范基于 Vue3、Vite、TypeScript 等现代前端技术制定，旨在提供一个简洁、易用的框架，使开发者能够快速上手并专注于业务逻辑的开发。\n"
  },
  {
    "path": "docs/src/guide/other/faq.md",
    "content": "# 常见问题 #{faq}\n\n::: tip 列举了一些常见的问题\n\n有问题可以先来这里寻找，如果没有可以在 [GitHub Issue](https://github.com/vbenjs/vue-vben-admin/issues) 搜索或者提交你的问题, 如果是讨论性的问题可以在 [GitHub Discussions](https://github.com/vbenjs/vue-vben-admin/discussions)\n\n:::\n\n## 说明\n\n遇到问题,可以先从以下几个方面查找\n\n1. 对应模块的 GitHub 仓库 [issue](https://github.com/vbenjs/vue-vben-admin/issues) 搜索\n2. 从[google](https://www.google.com)搜索问题\n3. 从[百度](https://www.baidu.com)搜索问题\n4. 在下面列表找不到问题可以到 issue 提问 [issues](https://github.com/vbenjs/vue-vben-admin/issues)\n5. 如果不是问题类型的，需要讨论的，请到 [discussions](https://github.com/vbenjs/vue-vben-admin/discussions) 讨论\n\n## 依赖问题\n\n在 `Monorepo` 项目下，需要养成每次 `git pull`代码都要执行`pnpm install`的习惯，因为经常会有新的依赖包加入，项目在`lefthook.yml`已经配置了自动执行`pnpm install`，但是有时候会出现问题，如果没有自动执行，建议手动执行一次。\n\n## 关于缓存更新问题\n\n项目配置默认是缓存在 `localStorage` 内，所以版本更新后可能有些配置没改变。\n\n解决方式是每次更新代码的时候修改 `package.json` 内的 `version` 版本号. 因为 localStorage 的 key 是根据版本号来的。所以更新后版本不同前面的配置会失效。重新登录即可\n\n## 关于修改配置文件的问题\n\n当修改 `.env` 等环境文件以及 `vite.config.ts` 文件时，vite 会自动重启服务。\n\n自动重启有几率出现问题，请重新运行项目即可解决.\n\n## 本地运行报错\n\n由于 vite 在本地没有转换代码，且代码中用到了可选链等比较新的语法。所以本地开发需要使用版本较高的浏览器(`Chrome 90+`)进行开发\n\n## 页面切换后页面空白\n\n这是由于开启了路由切换动画,且对应的页面组件存在多个根节点导致的，在页面最外层添加`<div></div>`即可\n\n**错误示例**\n\n```vue\n<template>\n  <!-- 注释也算一个节点 -->\n  <h1>text h1</h1>\n  <h2>text h2</h2>\n</template>\n```\n\n**正确示例**\n\n```vue\n<template>\n  <div>\n    <h1>text h1</h1>\n    <h2>text h2</h2>\n  </div>\n</template>\n```\n\n::: tip 提示\n\n- 如果想使用多个根标签，可以禁用路由切换动画\n- template 下面的根注释节点也算一个节点\n\n:::\n\n## 我的代码本地开发可以，打包就不行了\n\n目前发现这个原因可能有以下，可以从以下原因来排查，如果还有别的可能，欢迎补充\n\n- 使用了 ctx 这个变量，ctx 本身未暴露出在实例类型内，Vue官方也是说了不要用这个属性。这个属性只是用于内部使用。\n\n```ts\nimport { getCurrentInstance } from 'vue';\ngetCurrentInstance().ctx.xxxx;\n```\n\n## 依赖安装问题\n\n- 如果依赖安装不了或者启动报错可以尝试执行`pnpm run reinstall`。\n- 如果依赖安装不了或者报错，可以尝试切换手机热点来进行依赖安装。\n- 如果还是不行，可以自行配置国内镜像安装。\n- 也可以在项目根目录创建 `.npmrc` 文件，内容如下\n\n```bash\n# .npmrc\nregistry = https://registry.npmmirror.com/\n```\n\n## 打包文件过大\n\n- 首先，完整版由于引用了比较多的库文件，所以打包会比较大。可以使用精简版来进行开发\n\n- 其次建议开启 gzip，使用之后体积会只有原先 1/3 左右。gzip 可以由服务器直接开启。如果是这样，前端不需要构建 `.gz` 格式的文件，如果前端构建了 `.gz` 文件，以 nginx 为例，nginx 需要开启 `gzip_static: on` 这个选项。\n\n- 开启 gzip 的同时还可以同时开启 `brotli`，比 gzip 更好的压缩。两者可以共存\n\n**注意**\n\n- gzip_static: 这个模块需要 nginx 另外安装，默认的 nginx 没有安装这个模块。\n\n- 开启 `brotli` 也需要 nginx 另外安装模块\n\n## 运行错误\n\n如果出现类似以下错误，请检查项目全路径（包含所有父级路径）不能出现中文、日文、韩文。否则将会出现路径访问 404 导致以下问题\n\n```ts\n[vite] Failed to resolve module import \"ant-design-vue/dist/antd.css-vben-adminode_modulesant-design-vuedistantd.css\". (imported by /@/setup/ant-design-vue/index.ts)\n```\n\n## 控制台路由警告问题\n\n如果看到控制台有如下警告，且页面**能正常打开** 可以忽略该警告。\n\n后续 `vue-router` 可能会提供配置项来关闭警告\n\n```ts\n[Vue Router warn]: No match found for location with path \"xxxx\"\n```\n\n## 启动报错\n\n当出现以下错误信息时，请检查你的 nodejs 版本号是否符合要求\n\n```bash\nTypeError: str.matchAll is not a function\nat Object.extractor (vue-vben-admin-main\\node_modules@purge-icons\\core\\dist\\index.js:146:27)\nat Extract (vue-vben-admin-main\\node_modules@purge-icons\\core\\dist\\index.js:173:54)\n```\n\n## nginx 部署\n\n部署到 `nginx`后，可能会出现以下错误：\n\n```bash\nFailed to load module script: Expected a JavaScript module script but the server responded with a MIME type of \"application/octet-stream\". Strict MIME type checking is enforced for module scripts per HTML spec.\n```\n\n解决方式一：\n\n```bash\nhttp {\n    #如果有此项配置需要注释掉\n    #include       mime.types;\n\n    types {\n      application/javascript js mjs;\n    }\n}\n```\n\n解决方式二：\n\n进入 `nginx` 下的`mime.types`文件, 将`application/javascript js;` 修改为 `application/javascript js mjs;`\n"
  },
  {
    "path": "docs/src/guide/other/project-update.md",
    "content": "# 项目更新\n\n## 为什么无法像 npm 插件一样更新\n\n因为项目是一个完整的项目模版，不是一个插件或者安装包，无法像插件一样更新，你使用代码后，会根据业务需求，进行二次开发，需要自行手动合并升级。\n\n## 我需要怎么做\n\n项目采用了 `Monorepo` 的方式进行管理，并将一些比较核心的代码进行了抽离，比如 `packages/@core`、`packages/effects`，只要业务代码没有修改这部分代码，那么你可以直接拉取最新代码，然后合并到你的分支上，只需要简单的处理部分冲突即可。其余文件夹只会进行一些小的调整，不会对业务代码产生影响。\n\n::: tip 推荐\n\n建议关注仓库动态，积极去合并，不要长时间积累，否则将会导致合并冲突过多，增加合并难度。\n\n:::\n\n## 使用 Git 更新代码\n\n1. 克隆代码\n\n```bash\ngit clone https://github.com/vbenjs/vue-vben-admin.git\n```\n\n2. 添加自己的公司 git 源地址\n\n```bash\n# up 为源名称,可以随意设置\n# gitUrl为开源最新代码\ngit remote add up gitUrl;\n```\n\n3. 提交代码到自己公司 git\n\n```bash\n# 提交代码到自己公司\n# main为分支名 需要自行根据情况修改\ngit push up main\n\n# 同步公司的代码\n# main为分支名 需要自行根据情况修改\ngit pull up main\n```\n\n4. 如何同步开源最新代码\n\n```bash\ngit pull origin main\n```\n\n::: tip 提示\n\n同步代码的时候会出现冲突。只需要把冲突解决即可\n\n:::\n"
  },
  {
    "path": "docs/src/guide/other/remove-code.md",
    "content": "# 移除代码\n\n## 移除百度统计代码\n\n在对应应用的 `index.html` 文件中，找到如下代码，删除即可：\n\n```html\n<!-- apps/web-antd -->\n<script>\n  var _hmt = _hmt || [];\n  (function () {\n    var hm = document.createElement('script');\n    hm.src = 'https://hm.baidu.com/hm.js?d20a01273820422b6aa2ee41b6c9414d';\n    var s = document.getElementsByTagName('script')[0];\n    s.parentNode.insertBefore(hm, s);\n  })();\n</script>\n```\n"
  },
  {
    "path": "docs/src/guide/project/changeset.md",
    "content": "# Changeset\n\n项目内置了 [changeset](https://github.com/changesets/changesets) 作为版本管理工具。Changeset 是一个版本管理工具，它可以帮助我们更好的管理版本，生成 changelog，以及自动发布。\n\n详细使用方式可查看官方文档，这里不再阐述。如果你不需要它，可以直接忽略。\n\n## 命令行\n\nchangeset 命令在项目中已经内置：\n\n### 交互式填写变更集\n\n```bash\npnpm run changeset\n```\n\n### 统一提升版本号\n\n```bash\npnpm run version\n```\n"
  },
  {
    "path": "docs/src/guide/project/cli.md",
    "content": "---\noutline: deep\n---\n\n# CLI\n\n项目中，提供了一些命令行工具，用于一些常用的操作，代码位于 `scrips` 内。\n\n## vsh\n\n用于一些项目操作，如清理项目、检查项目等。\n\n### 用法\n\n```bash\npnpm vsh [command] [options]\n```\n\n### vsh check-circular\n\n检查整个项目循环引用，如果有循环引用，会在控制台输出循环引用的模块。\n\n#### 用法\n\n```bash\npnpm vsh check-circular\n```\n\n#### 选项\n\n| 选项       | 说明                                |\n| ---------- | ----------------------------------- |\n| `--staged` | 只检查git暂存区内的文件,默认`false` |\n\n### vsh check-dep\n\n检查整个项目依赖情况，并在控制台输出`未使用的依赖`、`未安装的依赖`信息\n\n#### 用法\n\n```bash\npnpm vsh check-dep\n```\n\n#### 选项\n\n| 选项             | 说明                                    |\n| ---------------- | --------------------------------------- |\n| `-r,--recursive` | 递归删除整个项目,默认`true`             |\n| `--del-lock`     | 是否删除`pnpm-lock.yaml`文件,默认`true` |\n\n### vsh lint\n\n对项目进行lint检查，检查项目中的代码是否符合规范。\n\n#### 用法\n\n```bash\npnpm vsh lint\n```\n\n#### 选项\n\n| 选项       | 说明                           |\n| ---------- | ------------------------------ |\n| `--format` | 检查并尝试修复错误,默认`false` |\n\n### vsh publint\n\n对 `Monorepo` 项目进行包规范检查，检查项目中的包是否符合规范。\n\n#### 用法\n\n```bash\npnpm vsh publint\n```\n\n#### 选项\n\n| 选项      | 说明                   |\n| --------- | ---------------------- |\n| `--check` | 仅执行检查,默认`false` |\n\n### vsh code-workspace\n\n生成 `vben-admin.code-workspace` 文件，目前不需要手动执行，会在代码提交时自动执行。\n\n#### 用法\n\n```bash\npnpm vsh code-workspace\n```\n\n#### 选项\n\n| 选项            | 说明                                   |\n| --------------- | -------------------------------------- |\n| `--auto-commit` | `git commit`时候，自动提交,默认`false` |\n| `--spaces`      | 缩进格式,默认 `2`个缩进                |\n\n## turbo-run\n\n用于快速执行大仓中脚本，并提供选项式交互选择。\n\n### 用法\n\n```bash\npnpm turbo-run [command]\n```\n\n### turbo-run dev\n\n快速执行`dev`命令，并提供选项式交互选择。\n"
  },
  {
    "path": "docs/src/guide/project/dir.md",
    "content": "# 目录说明\n\n目录使用 Monorepo 管理，项目结构如下：\n\n```bash\n.\n├── README.md # 项目说明文档\n├── apps # 项目应用目录\n│   ├── backend-mock # 后端模拟服务应用\n│   ├── web-antd # 基于 Ant Design Vue 的前端应用\n│   ├── web-antdv-next # 基于 Ant Design Vue Next 的前端应用\n│   ├── web-ele # 基于 Element Plus 的前端应用\n│   ├── web-naive # 基于 Naive UI 的前端应用\n│   └── web-tdesign # 基于 TDesign 的前端应用\n├── cspell.json # CSpell 配置文件\n├── docs # 项目文档目录\n├── eslint.config.mjs # ESLint 配置文件\n├── lefthook.yml # Git Hook 配置文件\n├── internal # 内部工具目录\n│   ├── lint-configs # 代码检查配置\n│   │   ├── commitlint-config # Commitlint 配置\n│   │   ├── eslint-config # ESLint 配置\n│   │   ├── oxfmt-config # Oxfmt 配置\n│   │   ├── oxlint-config # Oxlint 配置\n│   │   └── stylelint-config # Stylelint 配置\n│   ├── node-utils # Node.js 工具\n│   ├── tsconfig # 通用 tsconfig 配置\n│   └── vite-config # 通用 Vite 配置\n├── oxfmt.config.ts # Oxfmt 配置入口\n├── oxlint.config.ts # Oxlint 配置文件\n├── package.json # 项目依赖配置\n├── packages # 项目包目录\n│   ├── @core # 核心包\n│   │   ├── base # 基础包\n│   │   │   ├── design # 设计相关\n│   │   │   ├── icons # 图标\n│   │   │   ├── shared # 共享\n│   │   │   └── typings # 类型定义\n│   │   ├── composables # 组合式 API\n│   │   ├── preferences # 偏好设置\n│   │   └── ui-kit # UI 组件集合\n│   │       ├── layout-ui # 布局 UI\n│   │       ├── menu-ui # 菜单 UI\n│   │       ├── shadcn-ui # shadcn UI\n│   │       └── tabs-ui # 标签页 UI\n│   ├── constants # 常量\n│   ├── effects # 副作用相关包\n│   │   ├── access # 访问控制\n│   │   ├── plugins # 第三方大型依赖插件\n│   │   ├── common-ui # 通用 UI\n│   │   ├── hooks # 组合式 API\n│   │   ├── layouts # 布局\n│   │   └── request # 请求\n│   ├── icons # 图标\n│   ├── locales # 国际化\n│   ├── preferences # 偏好设置\n│   ├── stores # 状态管理\n│   ├── styles # 样式\n│   ├── types # 类型定义\n│   └── utils # 工具\n├── playground # 演示目录\n├── pnpm-lock.yaml # pnpm 锁定文件\n├── pnpm-workspace.yaml # pnpm 工作区配置文件\n├── scripts # 脚本目录\n│   ├── deploy # 部署相关脚本\n│   ├── turbo-run # Turbo 运行脚本\n│   └── vsh # VSH 脚本\n├── stylelint.config.mjs # Stylelint 配置文件\n├── turbo.json # Turbo 配置文件\n├── vben-admin.code-workspace # VS Code 工作区配置文件\n└── vitest.config.ts # Vitest 配置文件\n```\n"
  },
  {
    "path": "docs/src/guide/project/standard.md",
    "content": "# 规范\n\n::: tip 贡献代码\n\n- 如果你想向项目贡献代码，请确保你的代码符合项目的代码规范。\n- 如果你使用的是 `vscode`，需要安装以下插件：\n  - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - 脚本代码检查\n  - [Oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) - Oxlint / Oxfmt 集成\n  - [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) - 单词语法检查\n  - [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS 格式检查\n\n:::\n\n## 作用\n\n具备基本工程素养的同学都会注重编码规范，而代码风格检查（Code Linting，简称 Lint）是保障代码规范一致性的重要手段。\n\n遵循相应的代码规范有以下好处：\n\n- 较少 bug 错误率\n- 高效的开发效率\n- 更高的可读性\n\n## 工具\n\n项目的配置文件位于 `internal/lint-configs` 下，你可以在这里修改各种 lint 的配置。\n\n项目内集成了以下几种代码校验工具：\n\n- [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) 用于代码格式化\n- [Oxlint](https://oxc.rs/docs/guide/usage/linter.html) 用于 JavaScript / TypeScript 代码检查\n- [ESLint](https://eslint.org/) 用于 Vue、JSONC、YAML 等规则检查\n- [Stylelint](https://stylelint.io/) 用于 CSS 样式检查\n- [Commitlint](https://commitlint.js.org/) 用于检查 git 提交信息的规范\n- [Publint](https://publint.dev/) 用于检查 npm 包的规范\n- [Cspell](https://cspell.org/) 用于检查拼写错误\n- [lefthook](https://github.com/evilmartians/lefthook) 用于管理 Git hooks，在提交前自动运行代码校验和格式化\n\n## Oxfmt\n\nOxfmt 用于统一项目代码风格，统一缩进、引号、尾逗号等格式。\n\n### 命令\n\n```bash\npnpm oxfmt\npnpm oxfmt --check\n```\n\n### 配置\n\nOxfmt 的根目录入口文件为 `oxfmt.config.ts`，其核心配置位于 `internal/lint-configs/oxfmt-config` 目录下，可以根据项目需求进行修改。\n\n## Oxlint\n\nOxlint 是当前仓库的主要脚本 lint 工具，用于识别和报告 JavaScript / TypeScript 代码中的问题。\n\n### 命令\n\n```bash\npnpm oxlint\npnpm oxlint --fix\n```\n\n### 配置\n\nOxlint 的核心配置位于 `internal/lint-configs/oxlint-config` 目录下，根目录入口文件为 `oxlint.config.ts`。\n\n## ESLint\n\nESLint 用于补充 Vue、JSONC、YAML 等规则检查。\n\n### 命令\n\n```bash\npnpm eslint . --cache\n```\n\n### 配置\n\nESLint 配置文件为 `eslint.config.mjs`，其核心配置放在 `internal/lint-configs/eslint-config` 目录下，可以根据项目需求进行修改。\n\n## Stylelint\n\nStylelint 用于校验项目内部 CSS 的风格，加上编辑器的自动修复，可以很好的统一项目内部 CSS 风格。\n\n### 命令\n\n```bash\npnpm stylelint \"**/*.{vue,css,less,scss}\" --cache\n```\n\n### 配置\n\nStylelint 配置文件为 `stylelint.config.mjs`，其核心配置放在 `internal/lint-configs/stylelint-config` 目录下，可以根据项目需求进行修改。\n\n## CommitLint\n\n在一个团队中，每个人的 git 的 commit 信息都不一样，五花八门，没有一个机制很难保证规范化，如何才能规范化呢？可能你想到的是 git 的 hook 机制，去写 shell 脚本去实现。这当然可以，其实 JavaScript 有一个很好的工具可以实现这个模板，它就是 commitlint（用于校验 git 提交信息规范）。\n\n### 配置\n\nCommitLint 配置文件为 `.commitlintrc.js`，其核心配置放在 `internal/lint-configs/commitlint-config` 目录下，可以根据项目需求进行修改。\n\n### Git 提交规范\n\n参考 [Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)\n\n- `feat` 增加新功能\n- `fix` 修复问题 / BUG\n- `style` 代码风格相关无影响运行结果的\n- `perf` 优化 / 性能提升\n- `refactor` 重构\n- `revert` 撤销修改\n- `test` 测试相关\n- `docs` 文档 / 注释\n- `chore` 依赖更新 / 脚手架配置修改等\n- `workflow` 工作流改进\n- `ci` 持续集成\n- `types` 类型修改\n\n### 关闭 Git 提交规范检查\n\n如果你想关闭 Git 提交规范检查，有两种方式：\n\n::: code-group\n\n```bash [临时关闭]\ngit commit -m 'feat: add home page' --no-verify\n```\n\n```yaml [长期关闭]\ncommit-msg:\n  commands:\n    # commitlint:\n    #   run: pnpm exec commitlint --edit $1\n```\n\n:::\n\n如果修改了 `lefthook.yml`，请重新执行：\n\n```bash\npnpm exec lefthook install\n```\n\n## Publint\n\nPublint 是一个用于检查 npm 包规范的工具，可以检查包的版本号、导出方式以及 ESM 包结构等问题。\n\n### 命令\n\n```bash\npnpm publint\n```\n\n## Cspell\n\nCspell 是一个用于检查拼写错误的工具，可以检查代码中的拼写错误，避免因为拼写错误导致的 bug。\n\n### 命令\n\n```bash\npnpm check:cspell\n```\n\n### 配置\n\ncspell 配置文件为 `cspell.json`，可以根据项目需求进行修改。\n\n## Git Hook\n\ngit hook 一般结合各种 lint，在 git 提交代码的时候进行代码风格校验，如果校验没通过，则不会进行提交。需要开发者自行修改后再次进行提交。\n\n### lefthook\n\n有一个问题就是校验会校验全部代码，但是我们只想校验我们自己提交的代码，这个时候就可以使用 lefthook。\n\n最有效的解决方案就是将 lint 校验放到本地，常见做法是使用 lefthook 在本地提交之前先做一次 lint 校验。\n\n项目在 `lefthook.yml` 内部定义了相应的 hooks：\n\n- `pre-commit`: 在提交前运行，用于代码格式化和检查\n  - `code-workspace`: 更新 VSCode 工作区配置\n  - `lint-md`: 格式化 Markdown 文件\n  - `lint-vue`: 格式化并检查 Vue 文件\n  - `lint-js`: 格式化并检查 JavaScript / TypeScript 文件\n  - `lint-style`: 格式化并检查样式文件\n  - `lint-package`: 格式化 `package.json`\n  - `lint-json`: 格式化其他 JSON 文件\n- `post-merge`: 在合并后运行，用于自动安装依赖\n  - `install`: 运行 `pnpm install` 安装新依赖\n- `commit-msg`: 在提交时运行，用于检查提交信息格式\n  - `commitlint`: 使用 commitlint 检查提交信息\n\n当前 hooks 可通过以下命令安装：\n\n```bash\npnpm exec lefthook install\n```\n\n#### 如何关闭 lefthook\n\n如果你想临时关闭 lefthook，可以使用：\n\n```bash\ngit commit -m 'feat: add home page' --no-verify\n```\n\n#### 如何修改 lefthook 配置\n\n如果你想修改 lefthook 的配置，可以编辑 `lefthook.yml` 文件。例如：\n\n```yaml\npre-commit:\n  parallel: true\n  commands:\n    lint-js:\n      run: pnpm oxfmt {staged_files} && pnpm oxlint --fix {staged_files} && pnpm eslint --cache --fix {staged_files}\n      glob: '*.{js,jsx,ts,tsx}'\n```\n\n其中：\n\n- `parallel`: 是否并行执行任务\n- `commands`: 定义要执行的任务列表\n- `run`: 要执行的命令\n- `glob`: 匹配的文件模式\n- `{staged_files}`: 表示暂存的文件列表\n"
  },
  {
    "path": "docs/src/guide/project/tailwindcss.md",
    "content": "# Tailwind CSS\n\n[Tailwind CSS](https://tailwindcss.com/) 是一个实用性优先的 CSS 框架，用于快速构建自定义设计。当前项目使用的是 **Tailwind CSS v4**。\n\n## 配置\n\n项目当前不再通过 `tailwind.config.*` 文件维护 Tailwind 配置，主题与扫描范围都统一放在 CSS 与共享 Vite 配置中。\n\n- 主题入口：`internal/tailwind-config/src/theme.css`\n- Vite 集成：`internal/vite-config`\n\n在 `global.css` 中你会看到当前项目使用的 Tailwind CSS v4 指令，例如：\n\n- `@source`\n- `@custom-variant`\n- `@theme`\n- `@theme inline`\n- `@utility`\n\n## 包使用 Tailwind CSS 的方式\n\n当前项目不会根据某个包下是否存在 `tailwind.config.mjs` 来决定是否启用 Tailwind CSS。\n\n应用和包统一复用 `@vben/vite-config`，并由该配置接入 `@tailwindcss/vite`。Tailwind 的扫描范围则统一在 `@vben/tailwind-config` 对应的 `internal/tailwind-config/src/theme.css` 中维护。\n\n::: tip 包使用 Tailwind CSS 的说明\n\n如果你是纯粹的 SDK 包，不需要使用 Tailwind CSS，则无需额外增加旧版 `tailwind.config.*` 文件。\n\n:::\n\n## Vue SFC 中的 `@apply`\n\n项目对 Vue 单文件组件中的 `@apply` 做了统一处理，相关逻辑位于：\n\n- `internal/vite-config/src/plugins/tailwind-reference.ts`\n\n当组件样式中使用 `@apply` 时，会自动注入 `@reference \"@vben/tailwind-config/theme\"`，一般不需要手动补充。\n"
  },
  {
    "path": "docs/src/guide/project/test.md",
    "content": "# 单元测试\n\n项目内置了 [Vitest](https://vitest.dev/) 作为单元测试工具。Vitest 是一个基于 Vite 的测试运行器，它提供了一套简单的 API 来编写测试用例。\n\n## 编写测试用例\n\n在项目中，我们约定将测试文件名以 `.test.ts` 结尾，或者存放到`__tests__`目录内。例如，创建一个 `utils.ts` 文件，然后同级目录`utils.test.ts` 文件，\n\n```ts\n// utils.test.ts\nimport { expect, test } from 'vitest';\nimport { sum } from './sum';\n\ntest('adds 1 + 2 to equal 3', () => {\n  expect(sum(1, 2)).toBe(3);\n});\n```\n\n## 运行测试\n\n在大仓根目录下运行以下命令即可：\n\n```bash\npnpm test:unit\n```\n\n## 现有单元测试\n\n项目中已经有一些单元测试用例，可以搜索以`.test.ts`结尾的文件查看，在你更改到相关代码时，可以运行单元测试来保证代码的正确性，建议在开发过程中，保持单元测试的覆盖率，且同时在 CI/CD 流程中运行单元测试，保证测试通过在进行项目部署。\n\n现有单元测试情况：\n\n![](/guide/test.png)\n"
  },
  {
    "path": "docs/src/guide/project/vite.md",
    "content": "# Vite Config\n\n项目封装了一层vite配置，并集成了一些插件，方便在多个包以及应用内复用，使用方式如下：\n\n## 应用\n\n```ts\n// vite.config.mts\nimport { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    application: {},\n    // vite配置，参考vite官方文档进行覆盖\n    vite: {},\n  };\n});\n```\n\n## 包\n\n```ts\n// vite.config.mts\nimport { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    library: {},\n    // vite配置，参考vite官方文档进行覆盖\n    vite: {},\n  };\n});\n```\n"
  },
  {
    "path": "docs/src/index.md",
    "content": "---\n# https://vitepress.dev/reference/default-theme-home-page\nlayout: home\nsidebar: false\n\nhero:\n  name: Vben Admin\n  text: 企业级管理系统框架\n  tagline: 全新升级，开箱即用，简单高效\n  image:\n    src: https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp\n    alt: Vben Admin\n  actions:\n    - theme: brand\n      text: 快速开始 ->\n      link: /guide/introduction/vben\n    - theme: alt\n      text: 在线预览\n      link: https://www.vben.pro\n    - theme: alt\n      text: 在 GitHub 查看\n      link: https://github.com/vbenjs/vue-vben-admin\n    - theme: alt\n      text: DeepWiki 文档\n      link: https://deepwiki.com/vbenjs/vue-vben-admin\n\nfeatures:\n  - icon: 🚀\n    title: 最新技术栈\n    details: 基于 Vue3、Pinia、Vue Router、TypeScript、等最新技术栈。\n    link: /guide/introduction/quick-start\n    linkText: 快速开始\n  - icon: 🦄\n    title: 丰富的配置\n    details: 企业级中后台前端解决方案，提供丰富的组件和模板以及 N 种偏好设置组合方案。\n    link: /guide/essentials/settings\n    linkText: 配置文档\n  - icon: 🎨\n    title: 主题定制\n    details: 通过简单的配置，即可实现各种主题切换，满足个性化需求。\n    link: /guide/in-depth/theme\n    linkText: 主题文档\n  - icon: 🌐\n    title: 国际化\n    details: 内置国际化方案，支持多语言切换，满足国际化需求。\n    link: /guide/in-depth/locale\n    linkText: 国际化文档\n  - icon: 🔐\n    title: 权限管理\n    details: 内置权限管理方案，支持多种权限控制方式，满足各种权限需求。\n    link: /guide/in-depth/access\n    linkText: 权限文档\n  - title: Vite\n    icon:\n      src: /logos/vite.svg\n    details: 现代化的前端构建工具，快速冷启动，瞬间热更新。\n    link: https://vitejs.dev/\n    linkText: 官方站点\n  - title: Shadcn UI\n    icon:\n      src: /logos/shadcn-ui.svg\n    details: 核心基于 Shadcn UI + Tailwindcss，业务可支持任意的 UI 框架。\n    link: https://www.shadcn-vue.com/\n    linkText: 官方站点\n  - title: Turbo Repo\n    icon:\n      src: /logos/turborepo.svg\n    details: 规范且标准的大仓架构，使用 pnpm + monorepo + turbo 工程管理模式，提供企业级开发规范。\n    link: https://turbo.build/\n    linkText: 官方站点\n  - title: Nitro Mock Server\n    icon:\n      src: /logos/nitro.svg\n    details: 内置 Nitro Mock 服务，让你的 mock 服务更加强大。\n    link: https://nitro.unjs.io/\n    linkText: 官方站点\n---\n\n<!-- <script setup>\nimport {\n  VPTeamPage,\n  VPTeamPageTitle,\n  VPTeamMembers,\n  VPTeamPageSection\n} from 'vitepress/theme';\n\nconst members = [\n  {\n    avatar: 'https://avatars.githubusercontent.com/u/28132598?v=4',\n    name: 'Vben',\n    title: '创建者',\n    desc: 'Vben Admin以及相关生态的作者，负责项目的整体开发。',\n    links: [\n      { icon: 'github', link: 'https://github.com/anncwb' },\n    ]\n  },\n]\n</script>\n\n<VPTeamPage>\n  <VPTeamPageTitle>\n    <template #title>\n      核心成员介绍\n    </template>\n  </VPTeamPageTitle>\n  <VPTeamMembers\n    :members=\"members\"\n  />\n</VPTeamPage> -->\n\n<VbenContributors />\n"
  },
  {
    "path": "docs/src/sponsor/personal.md",
    "content": "# 赞助\n\n如果你觉得这个项目对你有帮助，你可以帮作者买一杯咖啡表示支持!\n\n![](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png)\n\n您的赞助将帮助我们：\n\n- 维持项目的基础设施，如服务器、域名、社群费用。\n- 支持开发者的贡献和加快新功能的开发。\n\n感谢所有现有的和未来的赞助者，您的支持对我们来说至关重要，让我们一起推动项目继续前行。\n"
  },
  {
    "path": "docs/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"#/*\": [\"./src/_env/*\"]\n    }\n  },\n  \"include\": [\n    \".vitepress/*.mts\",\n    \".vitepress/**/*.ts\",\n    \".vitepress/**/*.vue\",\n    \"src/*.mts\",\n    \"src/**/*.ts\",\n    \"src/**/*.vue\"\n  ],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import { defineConfig } from '@vben/eslint-config';\n\nexport default defineConfig();\n"
  },
  {
    "path": "internal/lint-configs/commitlint-config/index.mjs",
    "content": "import { execSync } from 'node:child_process';\n\nimport { getPackagesSync } from '@vben/node-utils';\n\nconst { packages } = getPackagesSync();\n\nconst allowedScopes = [\n  ...packages.map((pkg) => pkg.packageJson.name),\n  'project',\n  'style',\n  'lint',\n  'ci',\n  'dev',\n  'deploy',\n  'other',\n];\n\n// precomputed scope\nconst scopeComplete = execSync('git status --porcelain || true')\n  .toString()\n  .trim()\n  .split('\\n')\n  .find((r) => ~r.indexOf('M  src'))\n  ?.replaceAll(/(\\/)/g, '%%')\n  ?.match(/src%%((\\w|-)*)/)?.[1]\n  ?.replace(/s$/, '');\n\n/**\n * @type {import('cz-git').UserConfig}\n */\nconst userConfig = {\n  extends: ['@commitlint/config-conventional'],\n  plugins: ['commitlint-plugin-function-rules'],\n  prompt: {\n    /** @use `pnpm commit :f` */\n    alias: {\n      b: 'build: bump dependencies',\n      c: 'chore: update config',\n      f: 'docs: fix typos',\n      r: 'docs: update README',\n      s: 'style: update code format',\n    },\n    allowCustomIssuePrefixs: false,\n    // scopes: [...scopes, 'mock'],\n    allowEmptyIssuePrefixs: false,\n    customScopesAlign: scopeComplete ? 'bottom' : 'top',\n    defaultScope: scopeComplete,\n    // English\n    typesAppend: [\n      { name: 'workflow: workflow improvements', value: 'workflow' },\n      { name: 'types:    type definition file changes', value: 'types' },\n    ],\n\n    // 中英文对照版\n    // messages: {\n    //   type: '选择你要提交的类型 :',\n    //   scope: '选择一个提交范围 (可选):',\n    //   customScope: '请输入自定义的提交范围 :',\n    //   subject: '填写简短精炼的变更描述 :\\n',\n    //   body: '填写更加详细的变更描述 (可选)。使用 \"|\" 换行 :\\n',\n    //   breaking: '列举非兼容性重大的变更 (可选)。使用 \"|\" 换行 :\\n',\n    //   footerPrefixsSelect: '选择关联issue前缀 (可选):',\n    //   customFooterPrefixs: '输入自定义issue前缀 :',\n    //   footer: '列举关联issue (可选) 例如: #31, #I3244 :\\n',\n    //   confirmCommit: '是否提交或修改commit ?',\n    // },\n    // types: [\n    //   { value: 'feat', name: 'feat:     新增功能' },\n    //   { value: 'fix', name: 'fix:      修复缺陷' },\n    //   { value: 'docs', name: 'docs:     文档变更' },\n    //   { value: 'style', name: 'style:    代码格式' },\n    //   { value: 'refactor', name: 'refactor: 代码重构' },\n    //   { value: 'perf', name: 'perf:     性能优化' },\n    //   { value: 'test', name: 'test:     添加疏漏测试或已有测试改动' },\n    //   { value: 'build', name: 'build:    构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },\n    //   { value: 'ci', name: 'ci:       修改 CI 配置、脚本' },\n    //   { value: 'revert', name: 'revert:   回滚 commit' },\n    //   { value: 'chore', name: 'chore:    对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },\n    //   { value: 'wip', name: 'wip:      正在开发中' },\n    //   { value: 'workflow', name: 'workflow: 工作流程改进' },\n    //   { value: 'types', name: 'types:    类型定义文件修改' },\n    // ],\n    // emptyScopesAlias: 'empty:      不填写',\n    // customScopesAlias: 'custom:     自定义',\n  },\n  rules: {\n    /**\n     * type[scope]: [function] description\n     *\n     * ^^^^^^^^^^^^^^ empty line.\n     * - Something here\n     */\n    'body-leading-blank': [2, 'always'],\n    /**\n     * type[scope]: [function] description\n     *\n     * - something here\n     *\n     * ^^^^^^^^^^^^^^\n     */\n    'footer-leading-blank': [1, 'always'],\n    /**\n     * type[scope]: [function] description\n     *      ^^^^^\n     */\n    'function-rules/scope-enum': [\n      2, // level: error\n      'always',\n      (parsed) => {\n        if (!parsed.scope || allowedScopes.includes(parsed.scope)) {\n          return [true];\n        }\n\n        return [false, `scope must be one of ${allowedScopes.join(', ')}`];\n      },\n    ],\n    /**\n     * type[scope]: [function] description [No more than 108 characters]\n     *      ^^^^^\n     */\n    'header-max-length': [2, 'always', 108],\n\n    'scope-enum': [0],\n    'subject-case': [0],\n    'subject-empty': [2, 'never'],\n    'type-empty': [2, 'never'],\n    /**\n     * type[scope]: [function] description\n     * ^^^^\n     */\n    'type-enum': [\n      2,\n      'always',\n      [\n        'feat',\n        'fix',\n        'perf',\n        'style',\n        'docs',\n        'test',\n        'refactor',\n        'build',\n        'ci',\n        'chore',\n        'revert',\n        'types',\n        'release',\n      ],\n    ],\n  },\n};\n\nexport default userConfig;\n"
  },
  {
    "path": "internal/lint-configs/commitlint-config/package.json",
    "content": "{\n  \"name\": \"@vben/commitlint-config\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"internal/lint-configs/commitlint-config\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"./index.mjs\",\n  \"module\": \"./index.mjs\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./index.mjs\",\n      \"default\": \"./index.mjs\"\n    }\n  },\n  \"dependencies\": {\n    \"@commitlint/cli\": \"catalog:\",\n    \"@commitlint/config-conventional\": \"catalog:\",\n    \"@vben/node-utils\": \"workspace:*\",\n    \"commitlint-plugin-function-rules\": \"catalog:\",\n    \"cz-git\": \"catalog:\",\n    \"czg\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/package.json",
    "content": "{\n  \"name\": \"@vben/eslint-config\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"internal/lint-configs/eslint-config\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"stub\": \"pnpm exec tsdown\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.mjs\"\n    }\n  },\n  \"dependencies\": {\n    \"@eslint/js\": \"catalog:\",\n    \"@typescript-eslint/eslint-plugin\": \"catalog:\",\n    \"@typescript-eslint/parser\": \"catalog:\",\n    \"@vben/oxlint-config\": \"workspace:*\",\n    \"eslint\": \"catalog:\",\n    \"eslint-plugin-jsonc\": \"catalog:\",\n    \"eslint-plugin-n\": \"catalog:\",\n    \"eslint-plugin-perfectionist\": \"catalog:\",\n    \"eslint-plugin-pnpm\": \"catalog:\",\n    \"eslint-plugin-unicorn\": \"catalog:\",\n    \"eslint-plugin-unused-imports\": \"catalog:\",\n    \"eslint-plugin-vue\": \"catalog:\",\n    \"eslint-plugin-yml\": \"catalog:\",\n    \"globals\": \"catalog:\",\n    \"vue-eslint-parser\": \"catalog:\",\n    \"yaml-eslint-parser\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/ignores.ts",
    "content": "import type { Linter } from 'eslint';\n\nexport async function ignores(): Promise<Linter.Config[]> {\n  return [\n    {\n      ignores: [\n        '**/node_modules',\n        '**/dist',\n        '**/dist-*',\n        '**/*-dist',\n        '**/.husky',\n        '**/.nitro',\n        '**/.output',\n        '**/Dockerfile',\n        '**/package-lock.json',\n        '**/yarn.lock',\n        '**/pnpm-lock.yaml',\n        '**/bun.lockb',\n        '**/output',\n        '**/coverage',\n        '**/temp',\n        '**/.temp',\n        '**/tmp',\n        '**/.tmp',\n        '**/.history',\n        '**/.turbo',\n        '**/.nuxt',\n        '**/.next',\n        '**/.vercel',\n        '**/.changeset',\n        '**/.idea',\n        '**/.cache',\n        '**/.output',\n        '**/.vite-inspect',\n\n        '**/CHANGELOG*.md',\n        '**/*.min.*',\n        '**/LICENSE*',\n        '**/__snapshots__',\n        '**/*.snap',\n        '**/fixtures/**',\n        '**/.vitepress/cache/**',\n        '**/auto-import?(s).d.ts',\n        '**/components.d.ts',\n        '**/vite.config.mts.*',\n        '**/*.sh',\n        '**/*.ttf',\n        '**/*.woff',\n        '**/.github',\n        '**/lefthook.yml',\n\n        '**/.agent/**',\n        '**/.agents/**',\n        '**/.codex/**',\n        '**/.claude/**',\n        '**/.cursor/**',\n      ],\n    },\n  ];\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/index.ts",
    "content": "export * from './ignores';\nexport * from './javascript';\nexport * from './jsonc';\nexport * from './node';\nexport * from './perfectionist';\nexport * from './pnpm';\nexport * from './typescript';\nexport * from './unicorn';\nexport * from './vue';\nexport * from './yaml';\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/javascript.ts",
    "content": "import type { Linter } from 'eslint';\n\nimport js from '@eslint/js';\nimport pluginUnusedImports from 'eslint-plugin-unused-imports';\nimport globals from 'globals';\n\nconst rulesCoveredByOxlint = new Set([\n  'constructor-super',\n  'for-direction',\n  'getter-return',\n  'no-async-promise-executor',\n  'no-case-declarations',\n  'no-class-assign',\n  'no-compare-neg-zero',\n  'no-cond-assign',\n  'no-const-assign',\n  'no-constant-binary-expression',\n  'no-constant-condition',\n  'no-debugger',\n  'no-delete-var',\n  'no-dupe-args',\n  'no-dupe-class-members',\n  'no-dupe-else-if',\n  'no-dupe-keys',\n  'no-duplicate-case',\n  'no-empty',\n  'no-empty-character-class',\n  'no-empty-pattern',\n  'no-empty-static-block',\n  'no-ex-assign',\n  'no-extra-boolean-cast',\n  'no-fallthrough',\n  'no-func-assign',\n  'no-global-assign',\n  'no-import-assign',\n  'no-invalid-regexp',\n  'no-irregular-whitespace',\n  'no-loss-of-precision',\n  'no-misleading-character-class',\n  'no-new-native-nonconstructor',\n  'no-nonoctal-decimal-escape',\n  'no-obj-calls',\n  'no-prototype-builtins',\n  'no-redeclare',\n  'no-regex-spaces',\n  'no-self-assign',\n  'no-setter-return',\n  'no-shadow-restricted-names',\n  'no-sparse-arrays',\n  'no-this-before-super',\n  'no-unreachable',\n  'no-unsafe-finally',\n  'no-unsafe-negation',\n  'no-unsafe-optional-chaining',\n  'no-unused-labels',\n  'no-unused-private-class-members',\n  'no-unused-vars',\n  'no-useless-backreference',\n  'no-useless-catch',\n  'no-useless-escape',\n  'no-with',\n  'require-yield',\n  'use-isnan',\n  'valid-typeof',\n]);\n\nexport async function javascript(): Promise<Linter.Config[]> {\n  const recommendedRules = Object.fromEntries(\n    Object.entries(js.configs.recommended.rules).filter(\n      ([ruleName]) => !rulesCoveredByOxlint.has(ruleName),\n    ),\n  );\n\n  return [\n    {\n      languageOptions: {\n        ecmaVersion: 'latest',\n        globals: {\n          ...globals.browser,\n          ...globals.es2021,\n          ...globals.node,\n          document: 'readonly',\n          navigator: 'readonly',\n          window: 'readonly',\n        },\n        parserOptions: {\n          ecmaFeatures: {\n            jsx: true,\n          },\n          ecmaVersion: 'latest',\n          sourceType: 'module',\n        },\n        sourceType: 'module',\n      },\n      linterOptions: {\n        reportUnusedDisableDirectives: true,\n      },\n      plugins: {\n        'unused-imports': pluginUnusedImports,\n      },\n      rules: {\n        ...recommendedRules,\n        'dot-notation': ['error', { allowKeywords: true }],\n        'keyword-spacing': 'off',\n        'no-control-regex': 'error',\n        'no-empty-function': 'off',\n        'no-restricted-properties': [\n          'error',\n          {\n            message:\n              'Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.',\n            property: '__proto__',\n          },\n          {\n            message: 'Use `Object.defineProperty` instead.',\n            property: '__defineGetter__',\n          },\n          {\n            message: 'Use `Object.defineProperty` instead.',\n            property: '__defineSetter__',\n          },\n          {\n            message: 'Use `Object.getOwnPropertyDescriptor` instead.',\n            property: '__lookupGetter__',\n          },\n          {\n            message: 'Use `Object.getOwnPropertyDescriptor` instead.',\n            property: '__lookupSetter__',\n          },\n        ],\n        'no-restricted-syntax': [\n          'error',\n          'DebuggerStatement',\n          'LabeledStatement',\n          'WithStatement',\n          'TSEnumDeclaration[const=true]',\n          'TSExportAssignment',\n        ],\n        'no-undef': 'off',\n        'no-unreachable-loop': 'error',\n        'space-before-function-paren': 'off',\n\n        'unused-imports/no-unused-imports': 'error',\n        'unused-imports/no-unused-vars': [\n          'error',\n          {\n            args: 'after-used',\n            argsIgnorePattern: '^_',\n            vars: 'all',\n            varsIgnorePattern: '^_',\n          },\n        ],\n      },\n    },\n  ];\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/jsonc.ts",
    "content": "import type { Linter } from 'eslint';\n\nimport { interopDefault } from '../util';\n\nexport async function jsonc(): Promise<Linter.Config[]> {\n  const pluginJsonc = await interopDefault(import('eslint-plugin-jsonc'));\n\n  return [\n    {\n      files: ['**/*.json', '**/*.json5', '**/*.jsonc', '*.code-workspace'],\n      language: 'jsonc/x',\n      plugins: {\n        jsonc: pluginJsonc as any,\n      },\n      rules: {\n        'jsonc/no-bigint-literals': 'error',\n        'jsonc/no-binary-expression': 'error',\n        'jsonc/no-binary-numeric-literals': 'error',\n        'jsonc/no-dupe-keys': 'error',\n        'jsonc/no-escape-sequence-in-identifier': 'error',\n        'jsonc/no-floating-decimal': 'error',\n        'jsonc/no-hexadecimal-numeric-literals': 'error',\n        'jsonc/no-infinity': 'error',\n        'jsonc/no-multi-str': 'error',\n        'jsonc/no-nan': 'error',\n        'jsonc/no-number-props': 'error',\n        'jsonc/no-numeric-separators': 'error',\n        'jsonc/no-octal': 'error',\n        'jsonc/no-octal-escape': 'error',\n        'jsonc/no-octal-numeric-literals': 'error',\n        'jsonc/no-parenthesized': 'error',\n        'jsonc/no-plus-sign': 'error',\n        'jsonc/no-regexp-literals': 'error',\n        'jsonc/no-sparse-arrays': 'error',\n        'jsonc/no-template-literals': 'error',\n        'jsonc/no-undefined-value': 'error',\n        'jsonc/no-unicode-codepoint-escapes': 'error',\n        'jsonc/no-useless-escape': 'error',\n        'jsonc/space-unary-ops': 'error',\n        'jsonc/valid-json-number': 'error',\n        'jsonc/vue-custom-block/no-parsing-error': 'error',\n      },\n    },\n    sortTsconfig(),\n    sortPackageJson(),\n    sortCspellJson(),\n  ];\n}\n\nfunction sortPackageJson(): Linter.Config {\n  return {\n    files: ['**/package.json'],\n    rules: {\n      'jsonc/sort-array-values': [\n        'error',\n        {\n          order: { type: 'asc' },\n          pathPattern: '^files$|^pnpm.neverBuiltDependencies$',\n        },\n      ],\n      'jsonc/sort-keys': [\n        'error',\n        {\n          order: [\n            'name',\n            'version',\n            'description',\n            'private',\n            'keywords',\n            'homepage',\n            'bugs',\n            'repository',\n            'license',\n            'author',\n            'contributors',\n            'categories',\n            'funding',\n            'type',\n            'scripts',\n            'files',\n            'sideEffects',\n            'bin',\n            'main',\n            'module',\n            'unpkg',\n            'jsdelivr',\n            'types',\n            'typesVersions',\n            'imports',\n            'exports',\n            'publishConfig',\n            'icon',\n            'activationEvents',\n            'contributes',\n            'peerDependencies',\n            'peerDependenciesMeta',\n            'dependencies',\n            'optionalDependencies',\n            'devDependencies',\n            'engines',\n            'packageManager',\n            'pnpm',\n            'overrides',\n            'resolutions',\n            'husky',\n            'simple-git-hooks',\n            'lint-staged',\n            'eslintConfig',\n          ],\n          pathPattern: '^$',\n        },\n        {\n          order: { type: 'asc' },\n          pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$',\n        },\n        {\n          order: { type: 'asc' },\n          pathPattern: '^(?:resolutions|overrides|pnpm.overrides)$',\n        },\n        {\n          order: ['types', 'import', 'require', 'default'],\n          pathPattern: '^exports.*$',\n        },\n      ],\n    },\n  };\n}\n\nfunction sortCspellJson(): Linter.Config {\n  return {\n    files: ['**/cspell.json', '**/.cspell.json'],\n    rules: {\n      'jsonc/sort-array-values': [\n        'error',\n        {\n          order: { type: 'asc' },\n          pathPattern: '^words$|^ignorePaths$',\n        },\n      ],\n    },\n  };\n}\n\nfunction sortTsconfig(): Linter.Config {\n  return {\n    files: [\n      '**/tsconfig.json',\n      '**/tsconfig.*.json',\n      'internal/tsconfig/*.json',\n    ],\n    rules: {\n      'jsonc/sort-keys': [\n        'error',\n        {\n          order: [\n            'extends',\n            'compilerOptions',\n            'references',\n            'files',\n            'include',\n            'exclude',\n          ],\n          pathPattern: '^$',\n        },\n        {\n          order: [\n            /* Projects */\n            'incremental',\n            'composite',\n            'tsBuildInfoFile',\n            'disableSourceOfProjectReferenceRedirect',\n            'disableSolutionSearching',\n            'disableReferencedProjectLoad',\n            /* Language and Environment */\n            'target',\n            'jsx',\n            'jsxFactory',\n            'jsxFragmentFactory',\n            'jsxImportSource',\n            'lib',\n            'moduleDetection',\n            'noLib',\n            'reactNamespace',\n            'useDefineForClassFields',\n            'emitDecoratorMetadata',\n            'experimentalDecorators',\n            /* Modules */\n            'baseUrl',\n            'rootDir',\n            'rootDirs',\n            'customConditions',\n            'module',\n            'moduleResolution',\n            'moduleSuffixes',\n            'noResolve',\n            'paths',\n            'resolveJsonModule',\n            'resolvePackageJsonExports',\n            'resolvePackageJsonImports',\n            'typeRoots',\n            'types',\n            'allowArbitraryExtensions',\n            'allowImportingTsExtensions',\n            'allowUmdGlobalAccess',\n            /* JavaScript Support */\n            'allowJs',\n            'checkJs',\n            'maxNodeModuleJsDepth',\n            /* Type Checking */\n            'strict',\n            'strictBindCallApply',\n            'strictFunctionTypes',\n            'strictNullChecks',\n            'strictPropertyInitialization',\n            'allowUnreachableCode',\n            'allowUnusedLabels',\n            'alwaysStrict',\n            'exactOptionalPropertyTypes',\n            'noFallthroughCasesInSwitch',\n            'noImplicitAny',\n            'noImplicitOverride',\n            'noImplicitReturns',\n            'noImplicitThis',\n            'noPropertyAccessFromIndexSignature',\n            'noUncheckedIndexedAccess',\n            'noUnusedLocals',\n            'noUnusedParameters',\n            'useUnknownInCatchVariables',\n            /* Emit */\n            'declaration',\n            'declarationDir',\n            'declarationMap',\n            'downlevelIteration',\n            'emitBOM',\n            'emitDeclarationOnly',\n            'importHelpers',\n            'importsNotUsedAsValues',\n            'inlineSourceMap',\n            'inlineSources',\n            'mapRoot',\n            'newLine',\n            'noEmit',\n            'noEmitHelpers',\n            'noEmitOnError',\n            'outDir',\n            'outFile',\n            'preserveConstEnums',\n            'preserveValueImports',\n            'removeComments',\n            'sourceMap',\n            'sourceRoot',\n            'stripInternal',\n            /* Interop Constraints */\n            'allowSyntheticDefaultImports',\n            'esModuleInterop',\n            'forceConsistentCasingInFileNames',\n            'isolatedModules',\n            'preserveSymlinks',\n            'verbatimModuleSyntax',\n            /* Completeness */\n            'skipDefaultLibCheck',\n            'skipLibCheck',\n          ],\n          pathPattern: '^compilerOptions$',\n        },\n      ],\n    },\n  };\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/node.ts",
    "content": "import type { Linter } from 'eslint';\n\nimport { interopDefault } from '../util';\n\nexport async function node(): Promise<Linter.Config[]> {\n  const pluginNode = await interopDefault(import('eslint-plugin-n'));\n\n  return [\n    {\n      plugins: {\n        n: pluginNode,\n      },\n      rules: {\n        'n/handle-callback-err': ['error', '^(err|error)$'],\n        'n/no-deprecated-api': 'error',\n        'n/no-extraneous-import': [\n          'error',\n          {\n            allowModules: [\n              'tsdown',\n              'unplugin-vue',\n              '@vben/vite-config',\n              'vitest',\n              'vite',\n              '@vue/test-utils',\n              '@playwright/test',\n            ],\n          },\n        ],\n        // 'n/no-unpublished-import': 'off',\n        'n/no-unsupported-features/es-syntax': [\n          'error',\n          {\n            ignores: [],\n            version: '>=20.12.0',\n          },\n        ],\n        'n/prefer-global/buffer': ['error', 'never'],\n        // 'n/no-missing-import': 'off',\n        'n/prefer-global/process': ['error', 'never'],\n        'n/process-exit-as-throw': 'error',\n      },\n    },\n    {\n      files: [\n        '**/__tests__/**/*.?([cm])[jt]s?(x)',\n        '**/*.spec.?([cm])[jt]s?(x)',\n        '**/*.test.?([cm])[jt]s?(x)',\n        '**/*.bench.?([cm])[jt]s?(x)',\n        '**/*.benchmark.?([cm])[jt]s?(x)',\n      ],\n      rules: {\n        'n/prefer-global/process': 'off',\n      },\n    },\n    {\n      files: ['apps/backend-mock/**/**', 'docs/**/**'],\n      rules: {\n        'n/no-extraneous-import': 'off',\n        'n/prefer-global/buffer': 'off',\n        'n/prefer-global/process': 'off',\n      },\n    },\n    {\n      files: ['**/**/playwright.config.ts'],\n      rules: {\n        'n/prefer-global/buffer': 'off',\n        'n/prefer-global/process': 'off',\n      },\n    },\n    {\n      files: [\n        'scripts/**/*.?([cm])[jt]s?(x)',\n        'internal/**/*.?([cm])[jt]s?(x)',\n      ],\n      rules: {\n        'n/prefer-global/process': 'off',\n      },\n    },\n  ];\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/perfectionist.ts",
    "content": "import type { Linter } from 'eslint';\n\nimport { interopDefault } from '../util';\n\nexport async function perfectionist(): Promise<Linter.Config[]> {\n  const perfectionistPlugin = await interopDefault(\n    import('eslint-plugin-perfectionist'),\n  );\n\n  return [\n    perfectionistPlugin.configs['recommended-natural'],\n    {\n      rules: {\n        'perfectionist/sort-exports': [\n          'error',\n          {\n            order: 'asc',\n            type: 'natural',\n          },\n        ],\n        'perfectionist/sort-imports': [\n          'error',\n          {\n            customGroups: [\n              {\n                selector: 'type',\n                groupName: 'vben-core-type',\n                elementNamePattern: '^@vben-core/.+',\n              },\n              {\n                selector: 'type',\n                groupName: 'vben-type',\n                elementNamePattern: '^@vben/.+',\n              },\n              {\n                selector: 'type',\n                groupName: 'vue-type',\n                elementNamePattern: ['^vue$', '^vue-.+', '^@vue/.+'],\n              },\n              {\n                groupName: 'vben',\n                elementNamePattern: '^@vben/.+',\n              },\n              {\n                groupName: 'vben-core',\n                elementNamePattern: '^@vben-core/.+',\n              },\n              {\n                groupName: 'vue',\n                elementNamePattern: ['^vue$', '^vue-.+', '^@vue/.+'],\n              },\n            ],\n            environment: 'node',\n            groups: [\n              ['type-external', 'type-builtin', 'type-import'],\n              'vue-type',\n              'vben-type',\n              'vben-core-type',\n              ['type-parent', 'type-sibling', 'type-index'],\n              ['type-internal'],\n              'value-builtin',\n              'vue',\n              'vben',\n              'vben-core',\n              'value-external',\n              'value-internal',\n              ['value-parent', 'value-sibling', 'value-index'],\n              'side-effect',\n              'side-effect-style',\n              'style',\n              'ts-equals-import',\n              'unknown',\n            ],\n            internalPattern: ['^#/.+'],\n            newlinesBetween: 1,\n            order: 'asc',\n            type: 'natural',\n          },\n        ],\n        'perfectionist/sort-modules': 'off',\n        'perfectionist/sort-named-exports': [\n          'error',\n          {\n            order: 'asc',\n            type: 'natural',\n          },\n        ],\n        'perfectionist/sort-objects': [\n          'off',\n          {\n            customGroups: {\n              items: 'items',\n              list: 'list',\n              children: 'children',\n            },\n            groups: ['unknown', 'items', 'list', 'children'],\n            ignorePattern: ['children'],\n            order: 'asc',\n            type: 'natural',\n          },\n        ],\n      },\n    },\n  ];\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/pnpm.ts",
    "content": "import type { Linter } from 'eslint';\n\nimport { interopDefault } from '../util';\n\nexport async function pnpm(): Promise<Linter.Config[]> {\n  const [pluginPnpm, parserPnpm] = await Promise.all([\n    interopDefault(import('eslint-plugin-pnpm')),\n    interopDefault(import('yaml-eslint-parser')),\n  ] as const);\n\n  return [\n    {\n      files: ['package.json', '**/package.json'],\n      language: 'jsonc/x',\n      plugins: {\n        pnpm: pluginPnpm,\n      },\n      rules: {\n        'pnpm/json-enforce-catalog': 'error',\n        'pnpm/json-prefer-workspace-settings': 'error',\n        'pnpm/json-valid-catalog': 'error',\n      },\n    },\n    {\n      files: ['pnpm-workspace.yaml'],\n      languageOptions: {\n        parser: parserPnpm,\n      },\n      plugins: {\n        pnpm: pluginPnpm,\n      },\n      rules: {\n        'pnpm/yaml-no-duplicate-catalog-item': 'error',\n        'pnpm/yaml-no-unused-catalog-item': 'error',\n      },\n    },\n  ];\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/typescript.ts",
    "content": "import type { Linter } from 'eslint';\n\nimport { interopDefault } from '../util';\n\nconst rulesCoveredByOxlint = new Set([\n  '@typescript-eslint/ban-ts-comment',\n  '@typescript-eslint/no-non-null-assertion',\n  '@typescript-eslint/no-unused-expressions',\n  '@typescript-eslint/no-unused-vars',\n  '@typescript-eslint/triple-slash-reference',\n]);\n\nexport async function typescript(): Promise<Linter.Config[]> {\n  const [pluginTs, parserTs] = await Promise.all([\n    interopDefault(import('@typescript-eslint/eslint-plugin')),\n    interopDefault(import('@typescript-eslint/parser')),\n  ] as const);\n  const strictRules = Object.fromEntries(\n    Object.entries(pluginTs.configs.strict?.rules ?? {}).filter(\n      ([ruleName]) => !rulesCoveredByOxlint.has(ruleName),\n    ),\n  );\n\n  return [\n    {\n      files: ['**/*.?([cm])[jt]s?(x)'],\n      languageOptions: {\n        parser: parserTs,\n        parserOptions: {\n          createDefaultProgram: false,\n          ecmaFeatures: {\n            jsx: true,\n          },\n          ecmaVersion: 'latest',\n          extraFileExtensions: ['.vue'],\n          jsxPragma: 'React',\n          project: './tsconfig.*.json',\n          sourceType: 'module',\n        },\n      },\n      plugins: {\n        '@typescript-eslint': pluginTs as any,\n      },\n      rules: {\n        ...pluginTs.configs['eslint-recommended']?.overrides?.[0]?.rules,\n        ...strictRules,\n        // '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],\n        '@typescript-eslint/consistent-type-definitions': 'off',\n        '@typescript-eslint/explicit-function-return-type': 'off',\n        '@typescript-eslint/explicit-module-boundary-types': 'off',\n        '@typescript-eslint/no-explicit-any': 'off',\n        '@typescript-eslint/no-namespace': 'off',\n        '@typescript-eslint/no-use-before-define': 'off',\n        'unused-imports/no-unused-vars': 'off',\n      },\n    },\n  ];\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/unicorn.ts",
    "content": "import type { Linter } from 'eslint';\n\nimport { interopDefault } from '../util';\n\nconst rulesCoveredByOxlint = new Set([\n  'unicorn/consistent-function-scoping',\n  'unicorn/no-process-exit',\n  'unicorn/prefer-global-this',\n  'unicorn/prefer-module',\n]);\n\nexport async function unicorn(): Promise<Linter.Config[]> {\n  const pluginUnicorn = await interopDefault(import('eslint-plugin-unicorn'));\n  const recommendedRules = Object.fromEntries(\n    Object.entries(pluginUnicorn.configs.recommended.rules ?? {}).filter(\n      ([ruleName]) => !rulesCoveredByOxlint.has(ruleName),\n    ),\n  );\n\n  return [\n    {\n      plugins: {\n        unicorn: pluginUnicorn,\n      },\n      rules: {\n        ...recommendedRules,\n\n        'unicorn/better-regex': 'off',\n        'unicorn/consistent-destructuring': 'off',\n        'unicorn/expiring-todo-comments': 'off',\n        'unicorn/filename-case': 'off',\n        'unicorn/import-style': 'off',\n        'unicorn/no-array-for-each': 'off',\n        'unicorn/no-null': 'off',\n        'unicorn/no-useless-undefined': 'off',\n        'unicorn/prefer-at': 'off',\n        'unicorn/prefer-dom-node-text-content': 'off',\n        'unicorn/prefer-export-from': ['error', { ignoreUsedVariables: true }],\n        'unicorn/prefer-top-level-await': 'off',\n        'unicorn/prevent-abbreviations': 'off',\n      },\n    },\n  ];\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/vue.ts",
    "content": "import type { Linter } from 'eslint';\n\nimport { interopDefault } from '../util';\n\nexport async function vue(): Promise<Linter.Config[]> {\n  const [pluginVue, parserVue, parserTs] = await Promise.all([\n    interopDefault(import('eslint-plugin-vue')),\n    interopDefault(import('vue-eslint-parser')),\n    interopDefault(import('@typescript-eslint/parser')),\n  ] as const);\n\n  const flatEssential = pluginVue.configs?.['flat/essential'] || [];\n  const flatStronglyRecommended =\n    pluginVue.configs?.['flat/strongly-recommended'] || [];\n  const flatRecommended = pluginVue.configs?.['flat/recommended'] || [];\n\n  return [\n    ...flatEssential,\n    ...flatStronglyRecommended,\n    ...flatRecommended,\n    {\n      files: ['**/*.vue'],\n      languageOptions: {\n        // globals: {\n        //   computed: 'readonly',\n        //   defineEmits: 'readonly',\n        //   defineExpose: 'readonly',\n        //   defineProps: 'readonly',\n        //   onMounted: 'readonly',\n        //   onUnmounted: 'readonly',\n        //   reactive: 'readonly',\n        //   ref: 'readonly',\n        //   shallowReactive: 'readonly',\n        //   shallowRef: 'readonly',\n        //   toRef: 'readonly',\n        //   toRefs: 'readonly',\n        //   watch: 'readonly',\n        //   watchEffect: 'readonly',\n        // },\n        parser: parserVue,\n        parserOptions: {\n          ecmaFeatures: {\n            jsx: true,\n          },\n          extraFileExtensions: ['.vue'],\n          parser: parserTs,\n          sourceType: 'module',\n        },\n      },\n      plugins: {\n        vue: pluginVue,\n      },\n      processor: pluginVue.processors?.['.vue'],\n      rules: {\n        ...pluginVue.configs?.base?.rules,\n\n        'vue/attribute-hyphenation': [\n          'error',\n          'always',\n          {\n            ignore: [],\n          },\n        ],\n        'vue/attributes-order': 'off',\n        'vue/block-order': [\n          'error',\n          {\n            order: ['script', 'template', 'style'],\n          },\n        ],\n        'vue/component-name-in-template-casing': ['error', 'PascalCase'],\n        'vue/component-options-name-casing': ['error', 'PascalCase'],\n        'vue/custom-event-name-casing': ['error', 'camelCase'],\n        'vue/define-macros-order': [\n          'error',\n          {\n            order: [\n              'defineOptions',\n              'defineProps',\n              'defineEmits',\n              'defineSlots',\n            ],\n          },\n        ],\n        'vue/dot-location': ['error', 'property'],\n        'vue/dot-notation': ['error', { allowKeywords: true }],\n        'vue/eqeqeq': ['error', 'smart'],\n        'vue/html-closing-bracket-newline': 'error',\n        'vue/html-indent': 'off',\n        // 'vue/html-indent': ['error', 2],\n        'vue/html-quotes': ['error', 'double'],\n        'vue/html-self-closing': [\n          'error',\n          {\n            html: {\n              component: 'always',\n              normal: 'never',\n              void: 'always',\n            },\n            math: 'always',\n            svg: 'always',\n          },\n        ],\n        'vue/max-attributes-per-line': 'off',\n        'vue/multi-word-component-names': 'off',\n        'vue/multiline-html-element-content-newline': 'error',\n        'vue/no-empty-pattern': 'error',\n        'vue/no-extra-parens': ['error', 'functions'],\n        'vue/no-irregular-whitespace': 'error',\n        'vue/no-loss-of-precision': 'error',\n        'vue/no-reserved-component-names': 'off',\n        'vue/no-restricted-syntax': [\n          'error',\n          'DebuggerStatement',\n          'LabeledStatement',\n          'WithStatement',\n        ],\n        'vue/no-restricted-v-bind': ['error', '/^v-/'],\n        'vue/no-sparse-arrays': 'error',\n        'vue/no-unused-refs': 'error',\n        'vue/no-useless-v-bind': 'error',\n        'vue/object-shorthand': [\n          'error',\n          'always',\n          {\n            avoidQuotes: true,\n            ignoreConstructors: false,\n          },\n        ],\n        'vue/one-component-per-file': 'error',\n        'vue/prefer-separate-static-class': 'error',\n        'vue/prefer-template': 'error',\n        'vue/prop-name-casing': ['error', 'camelCase'],\n        'vue/require-default-prop': 'error',\n        'vue/require-explicit-emits': 'error',\n        'vue/require-prop-types': 'off',\n        'vue/singleline-html-element-content-newline': 'off',\n        'vue/space-infix-ops': 'error',\n        'vue/space-unary-ops': ['error', { nonwords: false, words: true }],\n        'vue/v-on-event-hyphenation': [\n          'error',\n          'always',\n          {\n            autofix: true,\n            ignore: [],\n          },\n        ],\n      },\n    },\n  ];\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/configs/yaml.ts",
    "content": "import type { Linter } from 'eslint';\n\nimport { interopDefault } from '../util';\n\nexport async function yaml(): Promise<Linter.Config[]> {\n  const [pluginYaml, parserYaml] = await Promise.all([\n    interopDefault(import('eslint-plugin-yml')),\n    interopDefault(import('yaml-eslint-parser')),\n  ] as const);\n\n  return [\n    {\n      files: ['**/*.y?(a)ml'],\n      plugins: {\n        yaml: pluginYaml,\n      },\n      languageOptions: {\n        parser: parserYaml,\n      },\n      rules: {\n        'style/spaced-comment': 'off',\n\n        'yaml/block-mapping': 'error',\n        'yaml/block-sequence': 'error',\n        'yaml/no-empty-key': 'error',\n        'yaml/no-empty-sequence-entry': 'error',\n        'yaml/no-irregular-whitespace': 'error',\n        'yaml/plain-scalar': 'error',\n\n        'yaml/vue-custom-block/no-parsing-error': 'error',\n\n        'yaml/block-mapping-question-indicator-newline': 'error',\n        'yaml/block-sequence-hyphen-indicator-newline': 'error',\n        'yaml/flow-mapping-curly-newline': 'error',\n        'yaml/flow-mapping-curly-spacing': 'error',\n        'yaml/flow-sequence-bracket-newline': 'error',\n        'yaml/flow-sequence-bracket-spacing': 'error',\n        'yaml/indent': ['error', 2],\n        'yaml/key-spacing': 'error',\n        'yaml/no-tab-indent': 'error',\n        'yaml/quotes': [\n          'error',\n          {\n            avoidEscape: true,\n            prefer: 'single',\n          },\n        ],\n        'yaml/spaced-comment': 'error',\n      },\n    },\n    {\n      files: ['pnpm-workspace.yaml'],\n      rules: {\n        'yaml/sort-keys': [\n          'error',\n          {\n            order: [\n              'packages',\n              'overrides',\n              'patchedDependencies',\n              'hoistPattern',\n              'catalog',\n              'catalogs',\n\n              'allowedDeprecatedVersions',\n              'allowNonAppliedPatches',\n              'configDependencies',\n              'ignoredBuiltDependencies',\n              'ignoredOptionalDependencies',\n              'neverBuiltDependencies',\n              'onlyBuiltDependencies',\n              'onlyBuiltDependenciesFile',\n              'packageExtensions',\n              'peerDependencyRules',\n              'supportedArchitectures',\n            ],\n            pathPattern: '^$',\n          },\n          {\n            order: { type: 'asc' },\n            pathPattern: '.*',\n          },\n        ],\n      },\n    },\n  ];\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/custom-config.ts",
    "content": "import type { Linter } from 'eslint';\n\nconst restrictedImportIgnores = ['**/vite.config.mts'];\n\nconst customConfig: Linter.Config[] = [\n  // shadcn-ui 内部组件是自动生成的，不做太多限制\n  {\n    files: ['packages/@core/ui-kit/shadcn-ui/**/**'],\n    rules: {\n      'vue/require-default-prop': 'off',\n    },\n  },\n  {\n    files: [\n      'apps/**/**',\n      'packages/effects/**/**',\n      'packages/utils/**/**',\n      'packages/types/**/**',\n      'packages/locales/**/**',\n    ],\n    ignores: restrictedImportIgnores,\n    rules: {\n      'perfectionist/sort-interfaces': 'off',\n    },\n  },\n  {\n    // apps内部的一些基础规则\n    files: ['apps/**/**'],\n    ignores: restrictedImportIgnores,\n    rules: {\n      'no-restricted-imports': [\n        'error',\n        {\n          patterns: [\n            {\n              group: ['#/api/*'],\n              message:\n                'The #/api package cannot be imported, please use the @core package itself',\n            },\n            {\n              group: ['#/layouts/*'],\n              message:\n                'The #/layouts package cannot be imported, please use the @core package itself',\n            },\n            {\n              group: ['#/locales/*'],\n              message:\n                'The #/locales package cannot be imported, please use the @core package itself',\n            },\n            {\n              group: ['#/stores/*'],\n              message:\n                'The #/stores package cannot be imported, please use the @core package itself',\n            },\n          ],\n        },\n      ],\n    },\n  },\n  {\n    // @core内部组件，不能引入@vben/* 里面的包\n    files: ['packages/@core/**/**'],\n    ignores: restrictedImportIgnores,\n    rules: {\n      'no-restricted-imports': [\n        'error',\n        {\n          patterns: [\n            {\n              group: ['@vben/*'],\n              message:\n                'The @core package cannot import the @vben package, please use the @core package itself',\n            },\n          ],\n        },\n      ],\n    },\n  },\n  {\n    // @core/shared内部组件，不能引入@vben/* 或者 @vben-core/* 里面的包\n    files: ['packages/@core/base/**/**'],\n    ignores: restrictedImportIgnores,\n    rules: {\n      'no-restricted-imports': [\n        'error',\n        {\n          patterns: [\n            {\n              group: ['@vben/*', '@vben-core/*'],\n              message:\n                'The @vben-core/shared package cannot import the @vben package, please use the @core/shared package itself',\n            },\n          ],\n        },\n      ],\n    },\n  },\n\n  {\n    // 不能引入@vben/*里面的包\n    files: [\n      'packages/types/**/**',\n      'packages/utils/**/**',\n      'packages/icons/**/**',\n      'packages/constants/**/**',\n      'packages/styles/**/**',\n      'packages/stores/**/**',\n      'packages/preferences/**/**',\n      'packages/locales/**/**',\n    ],\n    ignores: restrictedImportIgnores,\n    rules: {\n      'no-restricted-imports': [\n        'error',\n        {\n          patterns: [\n            {\n              group: ['@vben/*'],\n              message:\n                'The @vben package cannot be imported, please use the @core package itself',\n            },\n          ],\n        },\n      ],\n    },\n  },\n  // 后端模拟代码，不需要太多规则\n  {\n    files: ['apps/backend-mock/**/**', 'docs/**/**'],\n    rules: {\n      'no-console': 'off',\n    },\n  },\n  {\n    files: ['**/**/playwright.config.ts'],\n    rules: {\n      'no-console': 'off',\n    },\n  },\n  {\n    files: ['internal/**/**', 'scripts/**/**'],\n    rules: {\n      'no-console': 'off',\n    },\n  },\n  {\n    files: ['packages/@core/base/shared/src/utils/inference.ts'],\n    rules: {\n      'vue/prefer-import-from-vue': 'off',\n    },\n  },\n];\n\nexport { customConfig };\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/index.ts",
    "content": "import type { Linter } from 'eslint';\n\nimport {\n  ignores,\n  javascript,\n  jsonc,\n  node,\n  perfectionist,\n  pnpm,\n  typescript,\n  unicorn,\n  vue,\n  yaml,\n} from './configs';\nimport { customConfig } from './custom-config';\n\ntype FlatConfig = Linter.Config;\n\ntype FlatConfigPromise =\n  | FlatConfig\n  | FlatConfig[]\n  | Promise<FlatConfig>\n  | Promise<FlatConfig[]>;\n\nasync function defineConfig(config: FlatConfig[] = []) {\n  const configs: FlatConfigPromise[] = [\n    vue(),\n    javascript(),\n    ignores(),\n    typescript(),\n    jsonc(),\n    node(),\n    perfectionist(),\n    unicorn(),\n    yaml(),\n    pnpm(),\n    ...customConfig,\n    ...config,\n  ];\n\n  const resolved = await Promise.all(configs);\n\n  return resolved.flat();\n}\n\nexport { defineConfig };\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/src/util.ts",
    "content": "export type Awaitable<T> = Promise<T> | T;\n\nexport async function interopDefault<T>(\n  m: Awaitable<T>,\n): Promise<T extends { default: infer U } ? U : T> {\n  const resolved = await m;\n  return (resolved as any).default || resolved;\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "internal/lint-configs/eslint-config/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: true,\n  deps: {\n    skipNodeModulesBundle: true,\n  },\n  dts: {\n    resolver: 'tsc',\n  },\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "internal/lint-configs/oxfmt-config/package.json",
    "content": "{\n  \"name\": \"@vben/oxfmt-config\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"internal/lint-configs/oxfmt-config\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"stub\": \"pnpm exec tsdown\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.mjs\"\n    }\n  },\n  \"dependencies\": {\n    \"oxfmt\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "internal/lint-configs/oxfmt-config/src/index.ts",
    "content": "import { defineConfig as defineOxfmtConfig } from 'oxfmt';\n\ntype OxfmtConfig = Parameters<typeof defineOxfmtConfig>[0];\n\nconst oxfmtConfig: OxfmtConfig = defineOxfmtConfig({\n  printWidth: 80,\n  proseWrap: 'never',\n  semi: true,\n  singleQuote: true,\n  sortPackageJson: false,\n  trailingComma: 'all',\n  overrides: [\n    {\n      files: [\n        '*.json',\n        '*.json5',\n        '*.jsonc',\n        '*.code-workspace',\n        '**/*.json',\n        '**/*.json5',\n        '**/*.jsonc',\n        '**/*.code-workspace',\n      ],\n      options: {\n        trailingComma: 'none',\n      },\n    },\n  ],\n});\n\nfunction defineConfig(config: OxfmtConfig = {}): OxfmtConfig {\n  return defineOxfmtConfig({\n    ...oxfmtConfig,\n    ...config,\n  });\n}\n\nexport { defineConfig, oxfmtConfig };\nexport type { OxfmtConfig };\n"
  },
  {
    "path": "internal/lint-configs/oxfmt-config/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "internal/lint-configs/oxfmt-config/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: true,\n  dts: true,\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/package.json",
    "content": "{\n  \"name\": \"@vben/oxlint-config\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"internal/lint-configs/oxlint-config\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"stub\": \"pnpm exec tsdown\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.mjs\"\n    }\n  },\n  \"dependencies\": {\n    \"@eslint-community/eslint-plugin-eslint-comments\": \"catalog:\",\n    \"eslint-plugin-better-tailwindcss\": \"catalog:\",\n    \"eslint-plugin-command\": \"catalog:\",\n    \"oxlint\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/command.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst command: OxlintConfig = {\n  jsPlugins: [\n    {\n      name: 'command',\n      specifier: 'eslint-plugin-command',\n    },\n  ],\n  rules: {\n    'command/command': 'error',\n  },\n};\n\nexport { command };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/comments.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst comments: OxlintConfig = {\n  jsPlugins: [\n    {\n      name: 'eslint-comments',\n      specifier: '@eslint-community/eslint-plugin-eslint-comments',\n    },\n  ],\n  rules: {\n    'eslint-comments/no-aggregating-enable': 'error',\n    'eslint-comments/no-duplicate-disable': 'error',\n    'eslint-comments/no-unlimited-disable': 'error',\n    'eslint-comments/no-unused-enable': 'error',\n  },\n};\n\nexport { comments };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/ignores.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst ignores: OxlintConfig = {\n  ignorePatterns: [\n    '**/dist/**',\n    '**/node_modules/**',\n    'docs/**',\n    'playground/public/**',\n    '**/*.json',\n    '**/*.md',\n    '**/*.svg',\n    '**/*.yaml',\n    '**/*.yml',\n  ],\n};\n\nexport { ignores };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/import.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst importPluginConfig: OxlintConfig = {\n  rules: {\n    'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],\n    'import/first': 'error',\n    'import/no-duplicates': 'error',\n    'import/no-mutable-exports': 'error',\n    'import/no-named-as-default': 'off',\n    'import/no-named-as-default-member': 'off',\n    'import/no-named-default': 'error',\n    'import/no-self-import': 'error',\n    'import/no-unassigned-import': 'off',\n    'import/no-webpack-loader-syntax': 'error',\n  },\n};\n\nexport { importPluginConfig };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/index.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nimport { defineConfig as defineOxlintConfig } from 'oxlint';\n\nimport { command } from './command';\nimport { comments } from './comments';\nimport { ignores } from './ignores';\nimport { importPluginConfig } from './import';\nimport { javascript } from './javascript';\nimport { node } from './node';\nimport { overrides } from './overrides';\nimport { plugins } from './plugins';\nimport { tailwindcss } from './tailwindcss';\nimport { test } from './test';\nimport { typescript } from './typescript';\nimport { unicorn } from './unicorn';\nimport { vue } from './vue';\n\nfunction mergeOxlintConfigs(...configs: OxlintConfig[]): OxlintConfig {\n  const merged: OxlintConfig = {};\n\n  for (const config of configs) {\n    merged.categories =\n      merged.categories && config.categories\n        ? { ...merged.categories, ...config.categories }\n        : (config.categories ?? merged.categories);\n    merged.env =\n      merged.env && config.env\n        ? { ...merged.env, ...config.env }\n        : (config.env ?? merged.env);\n    merged.globals =\n      merged.globals && config.globals\n        ? { ...merged.globals, ...config.globals }\n        : (config.globals ?? merged.globals);\n    merged.ignorePatterns = [\n      ...(merged.ignorePatterns ?? []),\n      ...(config.ignorePatterns ?? []),\n    ];\n    merged.jsPlugins = [\n      ...new Set([...(merged.jsPlugins ?? []), ...(config.jsPlugins ?? [])]),\n    ];\n    merged.overrides = [\n      ...(merged.overrides ?? []),\n      ...(config.overrides ?? []),\n    ];\n    merged.plugins = [\n      ...new Set([...(merged.plugins ?? []), ...(config.plugins ?? [])]),\n    ];\n    merged.rules =\n      merged.rules && config.rules\n        ? { ...merged.rules, ...config.rules }\n        : (config.rules ?? merged.rules);\n    merged.settings =\n      merged.settings && config.settings\n        ? { ...merged.settings, ...config.settings }\n        : (config.settings ?? merged.settings);\n  }\n\n  return merged;\n}\n\nconst oxlintConfig = defineOxlintConfig(\n  mergeOxlintConfigs(\n    javascript,\n    command,\n    comments,\n    ignores,\n    plugins,\n    importPluginConfig,\n    node,\n    overrides,\n    tailwindcss,\n    test,\n    typescript,\n    unicorn,\n    vue,\n  ),\n);\n\nexport {\n  command,\n  comments,\n  ignores,\n  importPluginConfig,\n  javascript,\n  mergeOxlintConfigs,\n  node,\n  overrides,\n  oxlintConfig,\n  plugins,\n  tailwindcss,\n  test,\n  typescript,\n  unicorn,\n  vue,\n};\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/javascript.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst javascript: OxlintConfig = {\n  categories: {\n    correctness: 'error',\n    suspicious: 'warn',\n  },\n  env: {\n    browser: true,\n    es2021: true,\n    node: true,\n  },\n  globals: {\n    document: 'readonly',\n    navigator: 'readonly',\n    window: 'readonly',\n  },\n  rules: {\n    'accessor-pairs': [\n      'error',\n      {\n        enforceForClassMembers: true,\n        setWithoutGet: true,\n      },\n    ],\n    'array-callback-return': 'error',\n    'block-scoped-var': 'error',\n    'default-case-last': 'error',\n    eqeqeq: ['error', 'always'],\n    'eslint/no-unreachable': 'error',\n    'new-cap': [\n      'error',\n      {\n        capIsNew: false,\n        newIsCap: true,\n        properties: true,\n      },\n    ],\n    'no-alert': 'error',\n    'no-array-constructor': 'error',\n    'no-caller': 'error',\n    'no-case-declarations': 'error',\n    'no-console': ['error', { allow: ['warn', 'error'] }],\n    'no-control-regex': 'off',\n    'no-debugger': 'error',\n    'no-empty': ['error', { allowEmptyCatch: true }],\n    'no-fallthrough': 'error',\n    'no-new-func': 'error',\n    'no-new-object': 'error',\n    'no-new-symbol': 'error',\n    'no-labels': ['error', { allowLoop: false, allowSwitch: false }],\n    'no-lone-blocks': 'error',\n    'no-multi-str': 'error',\n    'no-octal': 'error',\n    'no-octal-escape': 'error',\n    'no-proto': 'error',\n    'no-prototype-builtins': 'error',\n    'no-redeclare': ['error', { builtinGlobals: false }],\n    'no-regex-spaces': 'error',\n    'no-self-compare': 'error',\n    'no-sequences': 'error',\n    'no-shadow': 'off',\n    'no-shadow-restricted-names': 'error',\n    'eslint/no-empty-function': [\n      'error',\n      {\n        allow: ['arrowFunctions', 'functions', 'methods'],\n      },\n    ],\n    'no-template-curly-in-string': 'error',\n    'no-throw-literal': 'error',\n    'no-undef-init': 'error',\n    'no-unused-expressions': [\n      'error',\n      {\n        allowShortCircuit: true,\n        allowTaggedTemplates: true,\n        allowTernary: true,\n      },\n    ],\n    'eslint/no-unused-vars': [\n      'error',\n      {\n        argsIgnorePattern: '^_',\n        varsIgnorePattern: '^_',\n      },\n    ],\n    'no-var': 'error',\n    'no-eval': 'error',\n    'no-iterator': 'error',\n    'no-new-wrappers': 'error',\n    'no-restricted-globals': [\n      'error',\n      { message: 'Use `globalThis` instead.', name: 'global' },\n      { message: 'Use `globalThis` instead.', name: 'self' },\n    ],\n    'no-useless-call': 'error',\n    'no-useless-computed-key': 'error',\n    'no-useless-constructor': 'error',\n    'no-useless-return': 'error',\n    'object-shorthand': [\n      'error',\n      'always',\n      {\n        avoidQuotes: true,\n        ignoreConstructors: false,\n      },\n    ],\n    'one-var': ['error', { initialized: 'never' }],\n    'prefer-const': [\n      'error',\n      {\n        destructuring: 'all',\n        ignoreReadBeforeAssign: true,\n      },\n    ],\n    'eslint/prefer-arrow-callback': [\n      'error',\n      {\n        allowNamedFunctions: false,\n        allowUnboundThis: true,\n      },\n    ],\n    'prefer-exponentiation-operator': 'error',\n    'prefer-promise-reject-errors': 'error',\n    'eslint/prefer-regex-literals': [\n      'error',\n      {\n        disallowRedundantWrapping: true,\n      },\n    ],\n    'prefer-rest-params': 'error',\n    'prefer-spread': 'error',\n    'prefer-template': 'error',\n    'spaced-comment': 'error',\n    'symbol-description': 'error',\n    'unicode-bom': ['error', 'never'],\n    'use-isnan': [\n      'error',\n      {\n        enforceForIndexOf: true,\n        enforceForSwitchCase: true,\n      },\n    ],\n    'valid-typeof': [\n      'error',\n      {\n        requireStringLiterals: true,\n      },\n    ],\n    'vars-on-top': 'error',\n    yoda: ['error', 'never'],\n  },\n};\n\nexport { javascript };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/node.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst node: OxlintConfig = {\n  rules: {\n    'node/no-exports-assign': 'error',\n    'node/no-new-require': 'error',\n    'node/no-path-concat': 'error',\n  },\n};\n\nexport { node };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/overrides.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst overrides: OxlintConfig = {\n  overrides: [\n    {\n      files: ['*.d.ts', '**/*.d.ts'],\n      rules: {\n        'import/no-unassigned-import': 'off',\n        'typescript/triple-slash-reference': 'off',\n      },\n    },\n    {\n      files: [\n        '**/__tests__/**/*.js',\n        '**/__tests__/**/*.cjs',\n        '**/__tests__/**/*.mjs',\n        '**/__tests__/**/*.jsx',\n        '**/__tests__/**/*.ts',\n        '**/__tests__/**/*.cts',\n        '**/__tests__/**/*.mts',\n        '**/__tests__/**/*.tsx',\n        '**/*.spec.js',\n        '**/*.spec.cjs',\n        '**/*.spec.mjs',\n        '**/*.spec.jsx',\n        '**/*.spec.ts',\n        '**/*.spec.cts',\n        '**/*.spec.mts',\n        '**/*.spec.tsx',\n        '**/*.test.js',\n        '**/*.test.cjs',\n        '**/*.test.mjs',\n        '**/*.test.jsx',\n        '**/*.test.ts',\n        '**/*.test.cts',\n        '**/*.test.mts',\n        '**/*.test.tsx',\n        '**/*.bench.js',\n        '**/*.bench.cjs',\n        '**/*.bench.mjs',\n        '**/*.bench.jsx',\n        '**/*.bench.ts',\n        '**/*.bench.cts',\n        '**/*.bench.mts',\n        '**/*.bench.tsx',\n        '**/*.benchmark.js',\n        '**/*.benchmark.cjs',\n        '**/*.benchmark.mjs',\n        '**/*.benchmark.jsx',\n        '**/*.benchmark.ts',\n        '**/*.benchmark.cts',\n        '**/*.benchmark.mts',\n        '**/*.benchmark.tsx',\n      ],\n      rules: {\n        'no-console': 'off',\n      },\n    },\n    {\n      files: ['packages/@core/base/shared/src/utils/inference.ts'],\n      rules: {\n        'vue/prefer-import-from-vue': 'off',\n      },\n    },\n    {\n      files: ['packages/@core/ui-kit/menu-ui/src/sub-menu.vue'],\n      rules: {\n        'import/no-self-import': 'off',\n      },\n    },\n    {\n      files: [\n        'scripts/**/*.js',\n        'scripts/**/*.cjs',\n        'scripts/**/*.mjs',\n        'scripts/**/*.jsx',\n        'scripts/**/*.ts',\n        'scripts/**/*.cts',\n        'scripts/**/*.mts',\n        'scripts/**/*.tsx',\n        'internal/**/*.js',\n        'internal/**/*.cjs',\n        'internal/**/*.mjs',\n        'internal/**/*.jsx',\n        'internal/**/*.ts',\n        'internal/**/*.cts',\n        'internal/**/*.mts',\n        'internal/**/*.tsx',\n      ],\n      rules: {\n        'no-console': 'off',\n        'unicorn/no-process-exit': 'off',\n      },\n    },\n  ],\n};\n\nexport { overrides };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/plugins.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst plugins: OxlintConfig = {\n  plugins: ['import', 'node', 'oxc', 'typescript', 'unicorn', 'vitest', 'vue'],\n};\n\nexport { plugins };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/tailwindcss.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nimport eslintPluginBetterTailwindcss from 'eslint-plugin-better-tailwindcss';\nimport { getDefaultSelectors } from 'eslint-plugin-better-tailwindcss/defaults';\nimport { SelectorKind } from 'eslint-plugin-better-tailwindcss/types';\n\nconst selectors = [\n  ...getDefaultSelectors(),\n  {\n    kind: SelectorKind.Attribute,\n    match: [{ type: 'objectValues' }],\n    name: '^classNames$',\n  },\n];\n\nconst settings = {\n  entryPoint: 'internal/tailwind-config/src/theme.css',\n  selectors,\n};\n\nconst tailwindcss: OxlintConfig = {\n  // Generated shadcn-ui internals are intentionally left unmanaged.\n  ignorePatterns: ['packages/@core/ui-kit/shadcn-ui/**/*'],\n  jsPlugins: [\n    {\n      name: 'better-tailwindcss',\n      specifier: 'eslint-plugin-better-tailwindcss',\n    },\n  ],\n  rules: {\n    ...eslintPluginBetterTailwindcss.configs.recommended.rules,\n    'better-tailwindcss/enforce-consistent-class-order': [\n      'error',\n      {\n        detectComponentClasses: true,\n        unknownClassOrder: 'asc',\n        unknownClassPosition: 'start',\n      },\n    ],\n    // Let Prettier own wrapping decisions to avoid ping-pong formatting.\n    'better-tailwindcss/enforce-consistent-line-wrapping': 'off',\n    'better-tailwindcss/no-unknown-classes': 'off',\n  },\n  settings: {\n    'better-tailwindcss': settings,\n    'eslint-plugin-better-tailwindcss': settings,\n  },\n};\n\nexport { tailwindcss };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/test.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst test: OxlintConfig = {\n  rules: {\n    'jest/no-conditional-expect': 'off',\n    'jest/require-to-throw-message': 'off',\n    'vitest/consistent-test-it': [\n      'error',\n      {\n        fn: 'it',\n        withinDescribe: 'it',\n      },\n    ],\n    'vitest/hoisted-apis-on-top': 'off',\n    'vitest/no-focused-tests': 'error',\n    'vitest/no-identical-title': 'error',\n    'vitest/no-import-node-test': 'error',\n    'vitest/prefer-hooks-in-order': 'error',\n    'vitest/prefer-lowercase-title': 'error',\n  },\n};\n\nexport { test };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/typescript.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst typescript: OxlintConfig = {\n  rules: {\n    'typescript/ban-ts-comment': 'error',\n    // Keep the first type-aware rollout conservative. These rules currently\n    // produce high-volume diagnostics and need file-by-file cleanup later.\n    'typescript/await-thenable': 'off',\n    'typescript/no-base-to-string': 'off',\n    'typescript/no-duplicate-type-constituents': 'off',\n    'typescript/no-floating-promises': 'off',\n    'typescript/no-misused-spread': 'off',\n    'typescript/no-non-null-assertion': 'error',\n    'typescript/no-redundant-type-constituents': 'off',\n    'typescript/no-unnecessary-boolean-literal-compare': 'off',\n    'typescript/no-unnecessary-type-assertion': 'off',\n    'typescript/no-unnecessary-type-arguments': 'off',\n    'typescript/no-unnecessary-template-expression': 'off',\n    'typescript/no-unsafe-enum-comparison': 'off',\n    'typescript/no-unsafe-type-assertion': 'off',\n    'typescript/no-var-requires': 'error',\n    'typescript/restrict-template-expressions': 'off',\n    'typescript/triple-slash-reference': 'error',\n    'typescript/unbound-method': 'off',\n  },\n};\n\nexport { typescript };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/unicorn.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst unicorn: OxlintConfig = {\n  rules: {\n    'unicorn/consistent-function-scoping': 'off',\n    'unicorn/no-process-exit': 'error',\n    'unicorn/no-single-promise-in-promise-methods': 'off',\n    'unicorn/no-useless-spread': 'off',\n    'unicorn/prefer-global-this': 'off',\n    'unicorn/prefer-module': 'error',\n  },\n};\n\nexport { unicorn };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/configs/vue.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nconst vue: OxlintConfig = {\n  rules: {\n    'vue/prefer-import-from-vue': 'error',\n  },\n};\n\nexport { vue };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/src/index.ts",
    "content": "import type { OxlintConfig } from 'oxlint';\n\nimport { defineConfig as defineOxlintConfig } from 'oxlint';\n\nimport { mergeOxlintConfigs, oxlintConfig } from './configs';\n\ntype VbenOxlintConfig = Omit<OxlintConfig, 'extends'> & {\n  extends?: OxlintConfig[];\n};\n\nfunction defineConfig(config: VbenOxlintConfig = {}) {\n  const { extends: extendedConfigs = [], ...restConfig } = config;\n\n  return defineOxlintConfig(\n    mergeOxlintConfigs(oxlintConfig, ...extendedConfigs, restConfig),\n  );\n}\n\nexport { defineConfig, oxlintConfig };\nexport * from './configs';\nexport type { OxlintConfig, VbenOxlintConfig };\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "internal/lint-configs/oxlint-config/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: true,\n  dts: true,\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "internal/lint-configs/stylelint-config/index.mjs",
    "content": "export default {\n  extends: ['stylelint-config-standard', 'stylelint-config-recess-order'],\n  ignoreFiles: [\n    '**/*.js',\n    '**/*.jsx',\n    '**/*.tsx',\n    '**/*.ts',\n    '**/*.json',\n    '**/*.md',\n  ],\n  overrides: [\n    {\n      customSyntax: 'postcss-html',\n      files: ['*.(html|vue)', '**/*.(html|vue)'],\n      rules: {\n        'selector-pseudo-class-no-unknown': [\n          true,\n          {\n            ignorePseudoClasses: ['global', 'deep'],\n          },\n        ],\n        'selector-pseudo-element-no-unknown': [\n          true,\n          {\n            ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'],\n          },\n        ],\n      },\n    },\n    {\n      customSyntax: 'postcss-scss',\n      extends: [\n        'stylelint-config-recommended-scss',\n        'stylelint-config-recommended-vue/scss',\n      ],\n      files: ['*.scss', '**/*.scss'],\n    },\n  ],\n  plugins: ['stylelint-order', '@stylistic/stylelint-plugin', 'stylelint-scss'],\n  rules: {\n    'at-rule-no-deprecated': null,\n    'at-rule-no-unknown': [\n      true,\n      {\n        ignoreAtRules: [\n          'extends',\n          'ignores',\n          'include',\n          'mixin',\n          'if',\n          'else',\n          'media',\n          'for',\n          'at-root',\n          'tailwind',\n          'apply',\n          'variants',\n          'responsive',\n          'screen',\n          'function',\n          'each',\n          'use',\n          'forward',\n          'return',\n          'reference',\n          'plugin',\n          'source',\n          'theme',\n          'utility',\n          'custom-variant',\n        ],\n      },\n    ],\n    'font-family-no-missing-generic-family-keyword': null,\n    'function-no-unknown': null,\n    'import-notation': null,\n    'media-feature-range-notation': null,\n    'named-grid-areas-no-invalid': null,\n    'nesting-selector-no-missing-scoping-root': null,\n    'no-descending-specificity': null,\n    'no-empty-source': null,\n    'order/order': [\n      [\n        'dollar-variables',\n        'custom-properties',\n        'at-rules',\n        'declarations',\n        {\n          name: 'supports',\n          type: 'at-rule',\n        },\n        {\n          name: 'media',\n          type: 'at-rule',\n        },\n        {\n          name: 'include',\n          type: 'at-rule',\n        },\n        'rules',\n      ],\n      { severity: 'error' },\n    ],\n    'rule-empty-line-before': [\n      'always',\n      {\n        ignore: ['after-comment', 'first-nested'],\n      },\n    ],\n    'scss/at-rule-no-unknown': [\n      true,\n      {\n        ignoreAtRules: [\n          'extends',\n          'ignores',\n          'include',\n          'mixin',\n          'if',\n          'else',\n          'media',\n          'for',\n          'at-root',\n          'tailwind',\n          'apply',\n          'variants',\n          'responsive',\n          'screen',\n          'function',\n          'each',\n          'use',\n          'forward',\n          'return',\n          'reference',\n          'plugin',\n          'source',\n          'theme',\n          'utility',\n          'custom-variant',\n        ],\n      },\n    ],\n    'scss/operator-no-newline-after': null,\n    'selector-class-pattern':\n      '^-?(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$',\n\n    'selector-not-notation': null,\n  },\n};\n"
  },
  {
    "path": "internal/lint-configs/stylelint-config/package.json",
    "content": "{\n  \"name\": \"@vben/stylelint-config\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"internal/lint-configs/stylelint-config\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"./index.mjs\",\n  \"module\": \"./index.mjs\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./index.mjs\",\n      \"default\": \"./index.mjs\"\n    }\n  },\n  \"dependencies\": {\n    \"@stylistic/stylelint-plugin\": \"catalog:\",\n    \"stylelint-config-recess-order\": \"catalog:\",\n    \"stylelint-scss\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"postcss\": \"catalog:\",\n    \"postcss-html\": \"catalog:\",\n    \"postcss-scss\": \"catalog:\",\n    \"stylelint\": \"catalog:\",\n    \"stylelint-config-recommended\": \"catalog:\",\n    \"stylelint-config-recommended-scss\": \"catalog:\",\n    \"stylelint-config-recommended-vue\": \"catalog:\",\n    \"stylelint-config-standard\": \"catalog:\",\n    \"stylelint-order\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "internal/node-utils/package.json",
    "content": "{\n  \"name\": \"@vben/node-utils\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"internal/node-utils\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"stub\": \"node ./scripts/build.mjs\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.mjs\",\n      \"default\": \"./dist/index.mjs\"\n    }\n  },\n  \"dependencies\": {\n    \"@changesets/git\": \"catalog:\",\n    \"@manypkg/get-packages\": \"catalog:\",\n    \"chalk\": \"catalog:\",\n    \"consola\": \"catalog:\",\n    \"dayjs\": \"catalog:\",\n    \"execa\": \"catalog:\",\n    \"find-up\": \"catalog:\",\n    \"ora\": \"catalog:\",\n    \"pkg-types\": \"catalog:\",\n    \"rimraf\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "internal/node-utils/scripts/build.mjs",
    "content": "import { spawnSync } from 'node:child_process';\n\nconst pnpmCommand =\n  process.env.npm_execpath && process.env.npm_execpath.endsWith('.cjs')\n    ? [process.execPath, process.env.npm_execpath]\n    : ['pnpm'];\n\nconst steps = [\n  ['exec', 'tsdown', '--no-dts'],\n  [\n    'exec',\n    'tsc',\n    '-p',\n    'tsconfig.build.json',\n    '--emitDeclarationOnly',\n    '--declaration',\n    '--outDir',\n    'dist',\n  ],\n];\n\nfor (const args of steps) {\n  const [command, ...commandArgs] = pnpmCommand;\n  let cmd = command;\n  if (cmd.includes(' ')) {\n    cmd = `\"${command}\"`;\n  }\n  const result = spawnSync(cmd, [...commandArgs, ...args], {\n    shell: true,\n    stdio: 'inherit',\n  });\n\n  if (result.error) {\n    throw result.error;\n  }\n\n  if (result.status !== 0) {\n    process.exit(result.status ?? 1);\n  }\n}\n"
  },
  {
    "path": "internal/node-utils/src/__tests__/hash.test.ts",
    "content": "import { createHash } from 'node:crypto';\n\nimport { describe, expect, it } from 'vitest';\n\nimport { generatorContentHash } from '../hash';\n\ndescribe('generatorContentHash', () => {\n  it('should generate an MD5 hash for the content', () => {\n    const content = 'example content';\n    const expectedHash = createHash('md5')\n      .update(content, 'utf8')\n      .digest('hex');\n    const actualHash = generatorContentHash(content);\n    expect(actualHash).toBe(expectedHash);\n  });\n\n  it('should generate an MD5 hash with specified length', () => {\n    const content = 'example content';\n    const hashLength = 10;\n    const generatedHash = generatorContentHash(content, hashLength);\n    expect(generatedHash).toHaveLength(hashLength);\n  });\n\n  it('should correctly generate the hash with specified length', () => {\n    const content = 'example content';\n    const hashLength = 8;\n    const expectedHash = createHash('md5')\n      .update(content, 'utf8')\n      .digest('hex')\n      .slice(0, hashLength);\n    const generatedHash = generatorContentHash(content, hashLength);\n    expect(generatedHash).toBe(expectedHash);\n  });\n\n  it('should return full hash if hash length parameter is not provided', () => {\n    const content = 'example content';\n    const expectedHash = createHash('md5')\n      .update(content, 'utf8')\n      .digest('hex');\n    const actualHash = generatorContentHash(content);\n    expect(actualHash).toBe(expectedHash);\n  });\n\n  it('should handle empty content', () => {\n    const content = '';\n    const expectedHash = createHash('md5')\n      .update(content, 'utf8')\n      .digest('hex');\n    const actualHash = generatorContentHash(content);\n    expect(actualHash).toBe(expectedHash);\n  });\n});\n"
  },
  {
    "path": "internal/node-utils/src/__tests__/path.test.ts",
    "content": "// pathUtils.test.ts\n\nimport { describe, expect, it } from 'vitest';\n\nimport { toPosixPath } from '../path';\n\ndescribe('toPosixPath', () => {\n  // 测试 Windows 风格路径到 POSIX 风格路径的转换\n  it('converts Windows-style paths to POSIX paths', () => {\n    const windowsPath = String.raw`C:\\Users\\Example\\file.txt`;\n    const expectedPosixPath = 'C:/Users/Example/file.txt';\n    expect(toPosixPath(windowsPath)).toBe(expectedPosixPath);\n  });\n\n  // 确认 POSIX 风格路径不会被改变\n  it('leaves POSIX-style paths unchanged', () => {\n    const posixPath = '/home/user/file.txt';\n    expect(toPosixPath(posixPath)).toBe(posixPath);\n  });\n\n  // 测试带有多个分隔符的路径\n  it('converts paths with mixed separators', () => {\n    const mixedPath = String.raw`C:/Users\\Example\\file.txt`;\n    const expectedPosixPath = 'C:/Users/Example/file.txt';\n    expect(toPosixPath(mixedPath)).toBe(expectedPosixPath);\n  });\n\n  // 测试空字符串\n  it('handles empty strings', () => {\n    const emptyPath = '';\n    expect(toPosixPath(emptyPath)).toBe('');\n  });\n\n  // 测试仅包含分隔符的路径\n  it('handles path with only separators', () => {\n    const separatorsPath = '\\\\\\\\\\\\';\n    const expectedPosixPath = '///';\n    expect(toPosixPath(separatorsPath)).toBe(expectedPosixPath);\n  });\n\n  // 测试不包含任何分隔符的路径\n  it('handles path without separators', () => {\n    const noSeparatorPath = 'file.txt';\n    expect(toPosixPath(noSeparatorPath)).toBe('file.txt');\n  });\n\n  // 测试以分隔符结尾的路径\n  it('handles path ending with a separator', () => {\n    const endingSeparatorPath = 'C:\\\\Users\\\\Example\\\\';\n    const expectedPosixPath = 'C:/Users/Example/';\n    expect(toPosixPath(endingSeparatorPath)).toBe(expectedPosixPath);\n  });\n\n  // 测试以分隔符开头的路径\n  it('handles path starting with a separator', () => {\n    const startingSeparatorPath = String.raw`\\Users\\Example`;\n    const expectedPosixPath = '/Users/Example';\n    expect(toPosixPath(startingSeparatorPath)).toBe(expectedPosixPath);\n  });\n\n  // 测试包含非法字符的路径\n  it('handles path with invalid characters', () => {\n    const invalidCharsPath = String.raw`C:\\Us*?ers\\Ex<ample>|file.txt`;\n    const expectedPosixPath = 'C:/Us*?ers/Ex<ample>|file.txt';\n    expect(toPosixPath(invalidCharsPath)).toBe(expectedPosixPath);\n  });\n});\n"
  },
  {
    "path": "internal/node-utils/src/constants.ts",
    "content": "enum UNICODE {\n  FAILURE = '\\u2716', // ✖\n  SUCCESS = '\\u2714', // ✔\n}\n\nexport { UNICODE };\n"
  },
  {
    "path": "internal/node-utils/src/date.ts",
    "content": "import dayjs from 'dayjs';\nimport timezone from 'dayjs/plugin/timezone.js';\nimport utc from 'dayjs/plugin/utc.js';\n\ndayjs.extend(utc);\ndayjs.extend(timezone);\n\ndayjs.tz.setDefault('Asia/Shanghai');\n\nconst dateUtil = dayjs;\n\nexport { dateUtil };\n"
  },
  {
    "path": "internal/node-utils/src/formatter.ts",
    "content": "import fs from 'node:fs/promises';\n\nimport { execa } from 'execa';\n\nasync function formatFile(filepath: string) {\n  await execa('oxfmt', [filepath], {\n    stdio: 'inherit',\n  });\n\n  return await fs.readFile(filepath, 'utf8');\n}\n\nexport { formatFile };\n"
  },
  {
    "path": "internal/node-utils/src/fs.ts",
    "content": "import { promises as fs } from 'node:fs';\nimport { dirname } from 'node:path';\n\nexport async function outputJSON(\n  filePath: string,\n  data: any,\n  spaces: number = 2,\n) {\n  try {\n    const dir = dirname(filePath);\n    await fs.mkdir(dir, { recursive: true });\n    const jsonData = JSON.stringify(data, null, spaces);\n    await fs.writeFile(filePath, jsonData, 'utf8');\n  } catch (error) {\n    console.error('Error writing JSON file:', error);\n    throw error;\n  }\n}\n\nexport async function ensureFile(filePath: string) {\n  try {\n    const dir = dirname(filePath);\n    await fs.mkdir(dir, { recursive: true });\n    await fs.writeFile(filePath, '', { flag: 'a' });\n  } catch (error) {\n    console.error('Error ensuring file:', error);\n    throw error;\n  }\n}\n\nexport async function readJSON(filePath: string) {\n  try {\n    const data = await fs.readFile(filePath, 'utf8');\n    return JSON.parse(data);\n  } catch (error) {\n    console.error('Error reading JSON file:', error);\n    throw error;\n  }\n}\n"
  },
  {
    "path": "internal/node-utils/src/git.ts",
    "content": "import path from 'node:path';\n\nimport { execa } from 'execa';\n\nexport * from '@changesets/git';\n\n/**\n * 获取暂存区文件\n */\nasync function getStagedFiles(): Promise<string[]> {\n  try {\n    const { stdout } = await execa('git', [\n      '-c',\n      'submodule.recurse=false',\n      'diff',\n      '--staged',\n      '--diff-filter=ACMR',\n      '--name-only',\n      '--ignore-submodules',\n      '-z',\n    ]);\n\n    let changedList = stdout ? stdout.replace(/\\0$/, '').split('\\0') : [];\n    changedList = changedList.map((item) => path.resolve(process.cwd(), item));\n    const changedSet = new Set(changedList);\n    changedSet.delete('');\n    return [...changedSet];\n  } catch (error) {\n    console.error('Failed to get staged files:', error);\n    return [];\n  }\n}\n\nexport { getStagedFiles };\n"
  },
  {
    "path": "internal/node-utils/src/hash.ts",
    "content": "import { createHash } from 'node:crypto';\n\n/**\n * 生产基于内容的 hash，可自定义长度\n * @param content\n * @param hashLSize\n */\nfunction generatorContentHash(content: string, hashLSize?: number) {\n  const hash = createHash('md5').update(content, 'utf8').digest('hex');\n\n  if (hashLSize) {\n    return hash.slice(0, hashLSize);\n  }\n\n  return hash;\n}\n\nexport { generatorContentHash };\n"
  },
  {
    "path": "internal/node-utils/src/index.ts",
    "content": "export * from './constants';\nexport * from './date';\nexport { formatFile } from './formatter';\nexport * from './fs';\nexport * from './git';\nexport { getStagedFiles, add as gitAdd } from './git';\nexport { generatorContentHash } from './hash';\nexport * from './monorepo';\nexport { toPosixPath } from './path';\nexport * from './spinner';\nexport type { Package } from '@manypkg/get-packages';\nexport { default as colors } from 'chalk';\nexport { consola } from 'consola';\nexport * from 'execa';\n\nexport { default as fs } from 'node:fs/promises';\n\nexport { type PackageJson, readPackageJSON } from 'pkg-types';\nexport { rimraf } from 'rimraf';\n"
  },
  {
    "path": "internal/node-utils/src/monorepo.ts",
    "content": "import type { Package } from '@manypkg/get-packages';\n\nimport { existsSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nimport * as manypkg from '@manypkg/get-packages';\nconst { getPackages: getPackagesFunc, getPackagesSync: getPackagesSyncFunc } =\n  manypkg;\n\n/**\n * 查找大仓的根目录\n * @param cwd\n */\nfunction findMonorepoRoot(cwd: string = process.cwd()) {\n  let currentDir = resolve(cwd);\n\n  while (true) {\n    if (existsSync(join(currentDir, 'pnpm-lock.yaml'))) {\n      return currentDir;\n    }\n\n    const parentDir = dirname(currentDir);\n    if (parentDir === currentDir) {\n      return '';\n    }\n\n    currentDir = parentDir;\n  }\n}\n\n/**\n * 获取大仓的所有包\n */\nfunction getPackagesSync() {\n  const root = findMonorepoRoot();\n  return getPackagesSyncFunc(root);\n}\n\n/**\n * 获取大仓的所有包\n */\nasync function getPackages() {\n  const root = findMonorepoRoot();\n\n  return await getPackagesFunc(root);\n}\n\n/**\n * 获取大仓指定的包\n */\nasync function getPackage(pkgName: string) {\n  const { packages } = await getPackages();\n  return packages.find((pkg: Package) => pkg.packageJson.name === pkgName);\n}\n\nexport { findMonorepoRoot, getPackage, getPackages, getPackagesSync };\n"
  },
  {
    "path": "internal/node-utils/src/path.ts",
    "content": "import { posix } from 'node:path';\n\n/**\n * 将给定的文件路径转换为 POSIX 风格。\n * @param {string} pathname - 原始文件路径。\n */\nfunction toPosixPath(pathname: string) {\n  return pathname.split(`\\\\`).join(posix.sep);\n}\n\nexport { toPosixPath };\n"
  },
  {
    "path": "internal/node-utils/src/spinner.ts",
    "content": "import type { Ora } from 'ora';\n\nimport ora from 'ora';\n\ninterface SpinnerOptions {\n  failedText?: string;\n  successText?: string;\n  title: string;\n}\nexport async function spinner<T>(\n  { failedText, successText, title }: SpinnerOptions,\n  callback: () => Promise<T>,\n): Promise<T> {\n  const loading: Ora = ora(title).start();\n\n  try {\n    const result = await callback();\n    loading.succeed(successText || 'Success!');\n    return result;\n  } catch (error) {\n    loading.fail(failedText || 'Failed!');\n    throw error;\n  } finally {\n    loading.stop();\n  }\n}\n"
  },
  {
    "path": "internal/node-utils/tsconfig.build.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"noEmit\": false\n  },\n  \"exclude\": [\"node_modules\", \"src/__tests__\"]\n}\n"
  },
  {
    "path": "internal/node-utils/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "internal/node-utils/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: false,\n  deps: {\n    skipNodeModulesBundle: true,\n  },\n  entry: ['src/index.ts'],\n  format: ['esm'],\n});\n"
  },
  {
    "path": "internal/tailwind-config/package.json",
    "content": "{\n  \"name\": \"@vben/tailwind-config\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"internal/tailwind-config\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"files\": [\n    \"src\"\n  ],\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"main\": \"./src/index.ts\",\n  \"module\": \"./src/index.ts\",\n  \"types\": \"./src/index.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    },\n    \"./theme\": {\n      \"default\": \"./src/theme.css\"\n    }\n  },\n  \"dependencies\": {\n    \"@iconify/tailwind4\": \"catalog:\",\n    \"@tailwindcss/typography\": \"catalog:\",\n    \"tailwindcss\": \"catalog:\",\n    \"tw-animate-css\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "internal/tailwind-config/src/index.ts",
    "content": "import './theme.css';\n"
  },
  {
    "path": "internal/tailwind-config/src/theme.css",
    "content": "@import 'tailwindcss';\n@import 'tw-animate-css';\n\n@plugin '@tailwindcss/typography';\n@plugin '@iconify/tailwind4';\n\n/* Monorepo source detection: scan all packages and apps for utility classes */\n@source '../../../packages/';\n@source '../../../apps/';\n@source '../../../docs/';\n@source '../../../playground/';\n\n/* Dark mode uses .dark class selector, not prefers-color-scheme */\n@custom-variant dark (&:is(.dark *));\n\n/* Explicitly pin Tailwind v4 dynamic spacing for classes like w-150/h-55. */\n@theme {\n  --spacing: 0.25rem;\n}\n\n@theme inline {\n  /* Font */\n  --font-sans: var(--font-family);\n\n  /* Border Radius */\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n\n  /* Box Shadow */\n  --shadow-float:\n    0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%),\n    0 9px 28px 8px rgb(0 0 0 / 5%);\n\n  /* Animations */\n  --animate-accordion-down: accordion-down 0.2s ease-out;\n  --animate-accordion-up: accordion-up 0.2s ease-out;\n  --animate-collapsible-down: collapsible-down 0.2s ease-in-out;\n  --animate-collapsible-up: collapsible-up 0.2s ease-in-out;\n  --animate-float: float 5s linear 0ms infinite;\n\n  /* ===== Semantic Colors (shadcn-ui) ===== */\n\n  --color-background: hsl(var(--background));\n  --color-background-deep: hsl(var(--background-deep));\n  --color-foreground: hsl(var(--foreground));\n  --color-card: hsl(var(--card));\n  --color-card-foreground: hsl(var(--card-foreground));\n  --color-popover: hsl(var(--popover));\n  --color-popover-foreground: hsl(var(--popover-foreground));\n  --color-muted: hsl(var(--muted));\n  --color-muted-foreground: hsl(var(--muted-foreground));\n  --color-accent: hsl(var(--accent));\n  --color-accent-foreground: hsl(var(--accent-foreground));\n  --color-accent-hover: hsl(var(--accent-hover));\n  --color-accent-lighter: hsl(var(--accent-lighter));\n  --color-border: hsl(var(--border));\n  --color-input: hsl(var(--input));\n  --color-input-background: hsl(var(--input-background));\n  --color-ring: hsl(var(--ring));\n  --color-secondary: hsl(var(--secondary));\n  --color-secondary-desc: hsl(var(--secondary-desc));\n  --color-secondary-foreground: hsl(var(--secondary-foreground));\n\n  /* ===== Custom Semantic Colors ===== */\n\n  --color-header: hsl(var(--header));\n  --color-heavy: hsl(var(--heavy));\n  --color-heavy-foreground: hsl(var(--heavy-foreground));\n  --color-main: hsl(var(--main));\n  --color-overlay: hsl(var(--overlay));\n  --color-overlay-content: hsl(var(--overlay-content));\n  --color-sidebar: hsl(var(--sidebar));\n  --color-sidebar-deep: hsl(var(--sidebar-deep));\n\n  /* ===== Primary Palette ===== */\n\n  --color-primary: hsl(var(--primary));\n  --color-primary-foreground: hsl(var(--primary-foreground));\n  --color-primary-50: hsl(var(--primary-50));\n  --color-primary-100: hsl(var(--primary-100));\n  --color-primary-200: hsl(var(--primary-200));\n  --color-primary-300: hsl(var(--primary-300));\n  --color-primary-400: hsl(var(--primary-400));\n  --color-primary-500: hsl(var(--primary-500));\n  --color-primary-600: hsl(var(--primary-600));\n  --color-primary-700: hsl(var(--primary-700));\n  --color-primary-active: hsl(var(--primary-700));\n  --color-primary-background-light: hsl(var(--primary-200));\n  --color-primary-background-lighter: hsl(var(--primary-100));\n  --color-primary-background-lightest: hsl(var(--primary-50));\n  --color-primary-border: hsl(var(--primary-400));\n  --color-primary-border-light: hsl(var(--primary-300));\n  --color-primary-hover: hsl(var(--primary-600));\n  --color-primary-text: hsl(var(--primary-500));\n  --color-primary-text-active: hsl(var(--primary-700));\n  --color-primary-text-hover: hsl(var(--primary-600));\n\n  /* ===== Destructive Palette ===== */\n\n  --color-destructive: hsl(var(--destructive));\n  --color-destructive-foreground: hsl(var(--destructive-foreground));\n  --color-destructive-50: hsl(var(--destructive-50));\n  --color-destructive-100: hsl(var(--destructive-100));\n  --color-destructive-200: hsl(var(--destructive-200));\n  --color-destructive-300: hsl(var(--destructive-300));\n  --color-destructive-400: hsl(var(--destructive-400));\n  --color-destructive-500: hsl(var(--destructive-500));\n  --color-destructive-600: hsl(var(--destructive-600));\n  --color-destructive-700: hsl(var(--destructive-700));\n  --color-destructive-active: hsl(var(--destructive-700));\n  --color-destructive-background-light: hsl(var(--destructive-200));\n  --color-destructive-background-lighter: hsl(var(--destructive-100));\n  --color-destructive-background-lightest: hsl(var(--destructive-50));\n  --color-destructive-border: hsl(var(--destructive-400));\n  --color-destructive-border-light: hsl(var(--destructive-300));\n  --color-destructive-hover: hsl(var(--destructive-600));\n  --color-destructive-text: hsl(var(--destructive-500));\n  --color-destructive-text-active: hsl(var(--destructive-700));\n  --color-destructive-text-hover: hsl(var(--destructive-600));\n\n  /* ===== Success Palette ===== */\n\n  --color-success: hsl(var(--success));\n  --color-success-foreground: hsl(var(--success-foreground));\n  --color-success-50: hsl(var(--success-50));\n  --color-success-100: hsl(var(--success-100));\n  --color-success-200: hsl(var(--success-200));\n  --color-success-300: hsl(var(--success-300));\n  --color-success-400: hsl(var(--success-400));\n  --color-success-500: hsl(var(--success-500));\n  --color-success-600: hsl(var(--success-600));\n  --color-success-700: hsl(var(--success-700));\n  --color-success-active: hsl(var(--success-700));\n  --color-success-background-light: hsl(var(--success-200));\n  --color-success-background-lighter: hsl(var(--success-100));\n  --color-success-background-lightest: hsl(var(--success-50));\n  --color-success-border: hsl(var(--success-400));\n  --color-success-border-light: hsl(var(--success-300));\n  --color-success-hover: hsl(var(--success-600));\n  --color-success-text: hsl(var(--success-500));\n  --color-success-text-active: hsl(var(--success-700));\n  --color-success-text-hover: hsl(var(--success-600));\n\n  /* ===== Warning Palette ===== */\n\n  --color-warning: hsl(var(--warning));\n  --color-warning-foreground: hsl(var(--warning-foreground));\n  --color-warning-50: hsl(var(--warning-50));\n  --color-warning-100: hsl(var(--warning-100));\n  --color-warning-200: hsl(var(--warning-200));\n  --color-warning-300: hsl(var(--warning-300));\n  --color-warning-400: hsl(var(--warning-400));\n  --color-warning-500: hsl(var(--warning-500));\n  --color-warning-600: hsl(var(--warning-600));\n  --color-warning-700: hsl(var(--warning-700));\n  --color-warning-active: hsl(var(--warning-700));\n  --color-warning-background-light: hsl(var(--warning-200));\n  --color-warning-background-lighter: hsl(var(--warning-100));\n  --color-warning-background-lightest: hsl(var(--warning-50));\n  --color-warning-border: hsl(var(--warning-400));\n  --color-warning-border-light: hsl(var(--warning-300));\n  --color-warning-hover: hsl(var(--warning-600));\n  --color-warning-text: hsl(var(--warning-500));\n  --color-warning-text-active: hsl(var(--warning-700));\n  --color-warning-text-hover: hsl(var(--warning-600));\n\n  /* ===== Green Palette (alias for success shades) ===== */\n\n  --color-green-50: hsl(var(--green-50));\n  --color-green-100: hsl(var(--green-100));\n  --color-green-200: hsl(var(--green-200));\n  --color-green-300: hsl(var(--green-300));\n  --color-green-400: hsl(var(--green-400));\n  --color-green-500: hsl(var(--green-500));\n  --color-green-600: hsl(var(--green-600));\n  --color-green-700: hsl(var(--green-700));\n  --color-green-active: hsl(var(--green-700));\n  --color-green-background-light: hsl(var(--green-200));\n  --color-green-background-lighter: hsl(var(--green-100));\n  --color-green-background-lightest: hsl(var(--green-50));\n  --color-green-border: hsl(var(--green-400));\n  --color-green-border-light: hsl(var(--green-300));\n  --color-green-foreground: hsl(var(--success-foreground));\n  --color-green-hover: hsl(var(--green-600));\n  --color-green-text: hsl(var(--green-500));\n  --color-green-text-active: hsl(var(--green-700));\n  --color-green-text-hover: hsl(var(--green-600));\n\n  /* ===== Red Palette (alias for destructive shades) ===== */\n\n  --color-red-50: hsl(var(--red-50));\n  --color-red-100: hsl(var(--red-100));\n  --color-red-200: hsl(var(--red-200));\n  --color-red-300: hsl(var(--red-300));\n  --color-red-400: hsl(var(--red-400));\n  --color-red-500: hsl(var(--red-500));\n  --color-red-600: hsl(var(--red-600));\n  --color-red-700: hsl(var(--red-700));\n  --color-red-active: hsl(var(--red-700));\n  --color-red-background-light: hsl(var(--red-200));\n  --color-red-background-lighter: hsl(var(--red-100));\n  --color-red-background-lightest: hsl(var(--red-50));\n  --color-red-border: hsl(var(--red-400));\n  --color-red-border-light: hsl(var(--red-300));\n  --color-red-foreground: hsl(var(--destructive-foreground));\n  --color-red-hover: hsl(var(--red-600));\n  --color-red-text: hsl(var(--red-500));\n  --color-red-text-active: hsl(var(--red-700));\n  --color-red-text-hover: hsl(var(--red-600));\n\n  /* ===== Yellow Palette (alias for warning shades) ===== */\n\n  --color-yellow-50: hsl(var(--yellow-50));\n  --color-yellow-100: hsl(var(--yellow-100));\n  --color-yellow-200: hsl(var(--yellow-200));\n  --color-yellow-300: hsl(var(--yellow-300));\n  --color-yellow-400: hsl(var(--yellow-400));\n  --color-yellow-500: hsl(var(--yellow-500));\n  --color-yellow-600: hsl(var(--yellow-600));\n  --color-yellow-700: hsl(var(--yellow-700));\n  --color-yellow-active: hsl(var(--yellow-700));\n  --color-yellow-background-light: hsl(var(--yellow-200));\n  --color-yellow-background-lighter: hsl(var(--yellow-100));\n  --color-yellow-background-lightest: hsl(var(--yellow-50));\n  --color-yellow-border: hsl(var(--yellow-400));\n  --color-yellow-border-light: hsl(var(--yellow-300));\n  --color-yellow-foreground: hsl(var(--warning-foreground));\n  --color-yellow-hover: hsl(var(--yellow-600));\n  --color-yellow-text: hsl(var(--yellow-500));\n  --color-yellow-text-active: hsl(var(--yellow-700));\n  --color-yellow-text-hover: hsl(var(--yellow-600));\n}\n\n/* Keyframes */\n@keyframes accordion-down {\n  from {\n    height: 0;\n  }\n\n  to {\n    height: var(--reka-accordion-content-height);\n  }\n}\n\n@keyframes accordion-up {\n  from {\n    height: var(--reka-accordion-content-height);\n  }\n\n  to {\n    height: 0;\n  }\n}\n\n@keyframes collapsible-down {\n  from {\n    height: 0;\n  }\n\n  to {\n    height: var(--reka-collapsible-content-height);\n  }\n}\n\n@keyframes collapsible-up {\n  from {\n    height: var(--reka-collapsible-content-height);\n  }\n\n  to {\n    height: 0;\n  }\n}\n\n@keyframes float {\n  0% {\n    transform: translateY(0);\n  }\n\n  50% {\n    transform: translateY(-20px);\n  }\n\n  100% {\n    transform: translateY(0);\n  }\n}\n\n/* Base styles */\n@layer base {\n  *,\n  ::after,\n  ::before {\n    @apply border-border outline-ring/50;\n\n    box-sizing: border-box;\n    border-style: solid;\n    border-width: 0;\n  }\n\n  html {\n    @apply text-foreground bg-background font-sans;\n\n    scroll-behavior: smooth;\n    font-size: var(--font-size-base, 16px);\n    font-variation-settings: normal;\n    font-synthesis-weight: none;\n    line-height: 1.15;\n    text-rendering: optimizelegibility;\n    text-size-adjust: 100%;\n    -webkit-tap-highlight-color: transparent;\n  }\n\n  #app,\n  body,\n  html {\n    @apply size-full;\n  }\n\n  body {\n    min-height: 100vh;\n  }\n\n  a,\n  a:active,\n  a:hover,\n  a:link,\n  a:visited {\n    @apply no-underline;\n  }\n\n  ::view-transition-new(root),\n  ::view-transition-old(root) {\n    @apply animate-none mix-blend-normal;\n  }\n\n  ::view-transition-old(root) {\n    @apply z-1;\n  }\n\n  ::view-transition-new(root) {\n    @apply z-2147483646;\n  }\n\n  html.dark::view-transition-old(root) {\n    @apply z-2147483646;\n  }\n\n  html.dark::view-transition-new(root) {\n    @apply z-1;\n  }\n\n  input::placeholder,\n  textarea::placeholder {\n    @apply opacity-100;\n  }\n\n  input[type='number']::-webkit-inner-spin-button,\n  input[type='number']::-webkit-outer-spin-button {\n    @apply m-0 appearance-none;\n  }\n\n  /* Only adjust scrollbar for non-macOS */\n  html:not([data-platform='macOs']) {\n    ::-webkit-scrollbar {\n      @apply h-2.5 w-2.5;\n    }\n\n    ::-webkit-scrollbar-thumb {\n      @apply bg-border rounded-sm border-none;\n    }\n\n    ::-webkit-scrollbar-track {\n      @apply rounded-sm border-none bg-transparent shadow-none;\n    }\n\n    ::-webkit-scrollbar-button {\n      @apply hidden;\n    }\n  }\n}\n\n/* Custom utilities (v4 @utility syntax) */\n@utility flex-center {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n@utility flex-col-center {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n}\n\n/* Component styles (complex selectors, not convertible to @utility) */\n.outline-box {\n  @apply outline-border relative cursor-pointer rounded-md p-1 outline-1;\n}\n\n.outline-box::after {\n  @apply absolute top-1/2 left-1/2 z-20 h-0 w-px rounded-sm opacity-0 outline-2 outline-transparent transition-all duration-300 content-[\"\"];\n}\n\n.outline-box.outline-box-active {\n  @apply outline-primary outline-2;\n}\n\n.outline-box.outline-box-active::after {\n  display: none;\n}\n\n.outline-box:not(.outline-box-active):hover::after {\n  @apply outline-primary top-0 left-0 h-full w-full p-1 opacity-100;\n}\n\n.vben-link {\n  @apply text-primary hover:text-primary-hover active:text-primary-active cursor-pointer;\n}\n\n.card-box {\n  @apply bg-card text-card-foreground border-border rounded-xl border;\n}\n\n/* Enter animations (converted from enterAnimationPlugin) */\n@keyframes enter-x-animation {\n  to {\n    opacity: 1;\n    transform: translateX(0);\n  }\n}\n\n@keyframes enter-y-animation {\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.enter-x:nth-child(1) {\n  opacity: 0;\n  transform: translateX(50px);\n  animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;\n}\n\n.enter-x:nth-child(2) {\n  opacity: 0;\n  transform: translateX(50px);\n  animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;\n}\n\n.enter-x:nth-child(3) {\n  opacity: 0;\n  transform: translateX(50px);\n  animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;\n}\n\n.enter-x:nth-child(4) {\n  opacity: 0;\n  transform: translateX(50px);\n  animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;\n}\n\n.enter-x:nth-child(5) {\n  opacity: 0;\n  transform: translateX(50px);\n  animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;\n}\n\n.enter-y:nth-child(1) {\n  opacity: 0;\n  transform: translateY(50px);\n  animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;\n}\n\n.enter-y:nth-child(2) {\n  opacity: 0;\n  transform: translateY(50px);\n  animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;\n}\n\n.enter-y:nth-child(3) {\n  opacity: 0;\n  transform: translateY(50px);\n  animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;\n}\n\n.enter-y:nth-child(4) {\n  opacity: 0;\n  transform: translateY(50px);\n  animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;\n}\n\n.enter-y:nth-child(5) {\n  opacity: 0;\n  transform: translateY(50px);\n  animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;\n}\n\n.-enter-x:nth-child(1) {\n  opacity: 0;\n  transform: translateX(-50px);\n  animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;\n}\n\n.-enter-x:nth-child(2) {\n  opacity: 0;\n  transform: translateX(-50px);\n  animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;\n}\n\n.-enter-x:nth-child(3) {\n  opacity: 0;\n  transform: translateX(-50px);\n  animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;\n}\n\n.-enter-x:nth-child(4) {\n  opacity: 0;\n  transform: translateX(-50px);\n  animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;\n}\n\n.-enter-x:nth-child(5) {\n  opacity: 0;\n  transform: translateX(-50px);\n  animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;\n}\n\n.-enter-y:nth-child(1) {\n  opacity: 0;\n  transform: translateY(-50px);\n  animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;\n}\n\n.-enter-y:nth-child(2) {\n  opacity: 0;\n  transform: translateY(-50px);\n  animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;\n}\n\n.-enter-y:nth-child(3) {\n  opacity: 0;\n  transform: translateY(-50px);\n  animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;\n}\n\n.-enter-y:nth-child(4) {\n  opacity: 0;\n  transform: translateY(-50px);\n  animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;\n}\n\n.-enter-y:nth-child(5) {\n  opacity: 0;\n  transform: translateY(-50px);\n  animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;\n}\n\nhtml.invert-mode {\n  @apply invert;\n}\n\nhtml.grayscale-mode {\n  @apply grayscale;\n}\n"
  },
  {
    "path": "internal/tsconfig/base.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Base\",\n  \"compilerOptions\": {\n    \"composite\": false,\n    \"target\": \"ESNext\",\n\n    \"moduleDetection\": \"force\",\n    \"experimentalDecorators\": true,\n\n    \"module\": \"ESNext\",\n\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n\n    \"strict\": true,\n    \"strictNullChecks\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitAny\": true,\n    \"noImplicitOverride\": true,\n    \"noImplicitThis\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n\n    \"inlineSources\": false,\n    \"noEmit\": true,\n    \"removeComments\": true,\n    \"sourceMap\": false,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"skipLibCheck\": true,\n    \"preserveWatchOutput\": true\n  },\n  \"exclude\": [\"**/node_modules/**\", \"**/dist/**\", \"**/.turbo/**\"]\n}\n"
  },
  {
    "path": "internal/tsconfig/library.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Web Application\",\n  \"extends\": \"./base.json\",\n  \"compilerOptions\": {\n    \"jsx\": \"preserve\",\n    \"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\"],\n    \"useDefineForClassFields\": true,\n    \"moduleResolution\": \"bundler\",\n    \"declaration\": true,\n    \"noEmit\": false\n  }\n}\n"
  },
  {
    "path": "internal/tsconfig/node.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Node Config\",\n  \"extends\": \"./base.json\",\n  \"compilerOptions\": {\n    \"composite\": false,\n    \"lib\": [\"ESNext\"],\n    \"moduleResolution\": \"bundler\",\n    \"types\": [\"node\"],\n    \"noImplicitAny\": true\n  }\n}\n"
  },
  {
    "path": "internal/tsconfig/package.json",
    "content": "{\n  \"name\": \"@vben/tsconfig\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"internal/tsconfig\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"files\": [\n    \"base.json\",\n    \"library.json\",\n    \"node.json\",\n    \"web-app.json\",\n    \"web.json\"\n  ],\n  \"dependencies\": {\n    \"@vben/types\": \"workspace:*\",\n    \"vite\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "internal/tsconfig/web-app.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Web Application\",\n  \"extends\": \"./web.json\",\n  \"compilerOptions\": {\n    \"types\": [\"vite/client\", \"@vben/types/global\"]\n  }\n}\n"
  },
  {
    "path": "internal/tsconfig/web.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Web Package\",\n  \"extends\": \"./base.json\",\n  \"compilerOptions\": {\n    \"jsx\": \"preserve\",\n    \"jsxImportSource\": \"vue\",\n    \"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\"],\n    \"useDefineForClassFields\": true,\n    \"moduleResolution\": \"bundler\",\n    \"types\": [\"vite/client\"],\n    \"declaration\": false\n  }\n}\n"
  },
  {
    "path": "internal/vite-config/package.json",
    "content": "{\n  \"name\": \"@vben/vite-config\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"internal/vite-config\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"stub\": \"pnpm exec tsdown\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./dist/index.mjs\"\n    }\n  },\n  \"dependencies\": {\n    \"@intlify/unplugin-vue-i18n\": \"catalog:\",\n    \"@jspm/generator\": \"catalog:\",\n    \"@tailwindcss/vite\": \"catalog:\",\n    \"@vben/node-utils\": \"workspace:*\",\n    \"archiver\": \"catalog:\",\n    \"cheerio\": \"catalog:\",\n    \"get-port\": \"catalog:\",\n    \"html-minifier-terser\": \"catalog:\",\n    \"nitropack\": \"catalog:\",\n    \"resolve.exports\": \"catalog:\",\n    \"vite-plugin-pwa\": \"catalog:\",\n    \"vite-plugin-vue-devtools\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@pnpm/workspace.read-manifest\": \"catalog:\",\n    \"@types/archiver\": \"catalog:\",\n    \"@types/html-minifier-terser\": \"catalog:\",\n    \"@vitejs/plugin-vue\": \"catalog:\",\n    \"@vitejs/plugin-vue-jsx\": \"catalog:\",\n    \"dayjs\": \"catalog:\",\n    \"dotenv\": \"catalog:\",\n    \"rollup-plugin-visualizer\": \"catalog:\",\n    \"sass\": \"catalog:\",\n    \"sass-embedded\": \"catalog:\",\n    \"unplugin-dts\": \"catalog:\",\n    \"vite\": \"catalog:\",\n    \"vite-plugin-compression\": \"catalog:\",\n    \"vite-plugin-html\": \"catalog:\",\n    \"vite-plugin-lazy-import\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "internal/vite-config/src/config/application.ts",
    "content": "import type { CSSOptions, UserConfig } from 'vite';\n\nimport type { DefineApplicationOptions } from '../typing';\n\nimport path, { relative } from 'node:path';\n\nimport { findMonorepoRoot } from '@vben/node-utils';\n\nimport { NodePackageImporter } from 'sass-embedded';\nimport { defineConfig, loadEnv, mergeConfig } from 'vite';\n\nimport { defaultImportmapOptions, getDefaultPwaOptions } from '../options';\nimport { loadApplicationPlugins } from '../plugins';\nimport { loadAndConvertEnv } from '../utils/env';\nimport { getCommonConfig } from './common';\n\nfunction defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) {\n  return defineConfig(async (config) => {\n    const options = await userConfigPromise?.(config);\n    const { appTitle, base, port, ...envConfig } = await loadAndConvertEnv();\n    const { command, mode } = config;\n    const { application = {}, vite = {} } = options || {};\n    const root = process.cwd();\n    const isBuild = command === 'build';\n    const env = loadEnv(mode, root);\n\n    const plugins = await loadApplicationPlugins({\n      archiver: true,\n      archiverPluginOptions: {},\n      compress: false,\n      compressTypes: ['brotli', 'gzip'],\n      devtools: true,\n      env,\n      extraAppConfig: true,\n      html: true,\n      i18n: true,\n      importmapOptions: defaultImportmapOptions,\n      injectAppLoading: true,\n      injectMetadata: true,\n      isBuild,\n      license: true,\n      mode,\n      nitroMock: !isBuild,\n      nitroMockOptions: {},\n      print: !isBuild,\n      printInfoMap: {\n        'Vben Admin Docs': 'https://doc.vben.pro',\n      },\n      pwa: true,\n      pwaOptions: getDefaultPwaOptions(appTitle),\n      vxeTableLazyImport: true,\n      ...envConfig,\n      ...application,\n    });\n\n    const { injectGlobalScss = true } = application;\n\n    const applicationConfig: UserConfig = {\n      base,\n      build: {\n        rolldownOptions: {\n          output: {\n            assetFileNames: '[ext]/[name]-[hash].[ext]',\n            chunkFileNames: 'js/[name]-[hash].js',\n            entryFileNames: 'jse/index-[name]-[hash].js',\n            minify: isBuild\n              ? {\n                  compress: {\n                    dropDebugger: true,\n                  },\n                }\n              : false,\n          },\n        },\n        target: 'es2015',\n      },\n      css: createCssOptions(injectGlobalScss),\n      plugins,\n      server: {\n        host: true,\n        port,\n        warmup: {\n          // 预热文件\n          clientFiles: [\n            './index.html',\n            './src/bootstrap.ts',\n            './src/{views,layouts,router,store,api,adapter}/*',\n          ],\n        },\n      },\n    };\n\n    const mergedCommonConfig = mergeConfig(\n      await getCommonConfig(),\n      applicationConfig,\n    );\n    return mergeConfig(mergedCommonConfig, vite);\n  });\n}\n\nfunction createCssOptions(injectGlobalScss = true): CSSOptions {\n  const root = findMonorepoRoot();\n  return {\n    preprocessorOptions: injectGlobalScss\n      ? {\n          scss: {\n            additionalData: (content: string, filepath: string) => {\n              const relativePath = relative(root, filepath);\n              // apps下的包注入全局样式\n              if (relativePath.startsWith(`apps${path.sep}`)) {\n                return `@use \"@vben/styles/global\" as *;\\n${content}`;\n              }\n              return content;\n            },\n            // api: 'modern',\n            importers: [new NodePackageImporter()],\n          },\n        }\n      : {},\n  };\n}\n\nexport { defineApplicationConfig };\n"
  },
  {
    "path": "internal/vite-config/src/config/common.ts",
    "content": "import type { UserConfig } from 'vite';\n\nasync function getCommonConfig(): Promise<UserConfig> {\n  return {\n    build: {\n      chunkSizeWarningLimit: 2000,\n      reportCompressedSize: false,\n      sourcemap: false,\n    },\n  };\n}\n\nexport { getCommonConfig };\n"
  },
  {
    "path": "internal/vite-config/src/config/index.ts",
    "content": "import type { DefineConfig, VbenViteConfig } from '../typing';\n\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { defineApplicationConfig } from './application';\nimport { defineLibraryConfig } from './library';\n\nexport * from './application';\nexport * from './library';\n\nfunction defineConfig(\n  userConfigPromise?: DefineConfig,\n  type: 'application' | 'auto' | 'library' = 'auto',\n): VbenViteConfig {\n  let projectType = type;\n\n  // 根据包是否存在 index.html,自动判断类型\n  if (projectType === 'auto') {\n    const htmlPath = join(process.cwd(), 'index.html');\n    projectType = existsSync(htmlPath) ? 'application' : 'library';\n  }\n\n  switch (projectType) {\n    case 'application': {\n      return defineApplicationConfig(userConfigPromise);\n    }\n    case 'library': {\n      return defineLibraryConfig(userConfigPromise);\n    }\n    default: {\n      throw new Error(`Unsupported project type: ${projectType}`);\n    }\n  }\n}\n\nexport { defineConfig };\n"
  },
  {
    "path": "internal/vite-config/src/config/library.ts",
    "content": "import type { ConfigEnv, UserConfig } from 'vite';\n\nimport type { DefineLibraryOptions } from '../typing';\n\nimport { readPackageJSON } from '@vben/node-utils';\n\nimport { defineConfig, mergeConfig } from 'vite';\n\nimport { loadLibraryPlugins } from '../plugins';\nimport { getCommonConfig } from './common';\n\nfunction defineLibraryConfig(userConfigPromise?: DefineLibraryOptions) {\n  return defineConfig(async (config: ConfigEnv) => {\n    const options = await userConfigPromise?.(config);\n    const { command, mode } = config;\n    const { library = {}, vite = {} } = options || {};\n    const root = process.cwd();\n    const isBuild = command === 'build';\n\n    const plugins = await loadLibraryPlugins({\n      dts: false,\n      injectMetadata: true,\n      isBuild,\n      mode,\n      ...library,\n    });\n\n    const { dependencies = {}, peerDependencies = {} } =\n      await readPackageJSON(root);\n\n    const externalPackages = [\n      ...Object.keys(dependencies),\n      ...Object.keys(peerDependencies),\n    ];\n\n    const packageConfig: UserConfig = {\n      build: {\n        lib: {\n          entry: 'src/index.ts',\n          fileName: () => 'index.mjs',\n          formats: ['es'],\n        },\n        rolldownOptions: {\n          external: (id) => {\n            return externalPackages.some(\n              (pkg) => id === pkg || id.startsWith(`${pkg}/`),\n            );\n          },\n        },\n      },\n      plugins,\n    };\n    const commonConfig = await getCommonConfig();\n    const mergedConmonConfig = mergeConfig(commonConfig, packageConfig);\n    return mergeConfig(mergedConmonConfig, vite);\n  });\n}\n\nexport { defineLibraryConfig };\n"
  },
  {
    "path": "internal/vite-config/src/index.ts",
    "content": "export * from './config';\nexport * from './options';\nexport * from './plugins';\nexport type * from './typing';\nexport { loadAndConvertEnv } from './utils/env';\n"
  },
  {
    "path": "internal/vite-config/src/options.ts",
    "content": "import type { Options as PwaPluginOptions } from 'vite-plugin-pwa';\n\nimport type { ImportmapPluginOptions } from './typing';\n\nconst isDevelopment = process.env.NODE_ENV === 'development';\n\nconst getDefaultPwaOptions = (name: string): Partial<PwaPluginOptions> => ({\n  manifest: {\n    description:\n      'Vben Admin is a modern admin dashboard template based on Vue 3. ',\n    icons: [\n      {\n        sizes: '192x192',\n        src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-192.png',\n        type: 'image/png',\n      },\n      {\n        sizes: '512x512',\n        src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-512.png',\n        type: 'image/png',\n      },\n    ],\n    name: `${name}${isDevelopment ? ' dev' : ''}`,\n    short_name: `${name}${isDevelopment ? ' dev' : ''}`,\n  },\n});\n\n/**\n * importmap CDN 暂时不开启，因为有些包不支持，且网络不稳定\n */\nconst defaultImportmapOptions: ImportmapPluginOptions = {\n  // 通过 Importmap CDN 方式引入,\n  // 目前只有esm.sh源兼容性好一点，jspm.io对于 esm 入口要求高\n  defaultProvider: 'esm.sh',\n  importmap: [\n    { name: 'vue' },\n    { name: 'pinia' },\n    { name: 'vue-router' },\n    // { name: 'vue-i18n' },\n    { name: 'dayjs' },\n    { name: 'vue-demi' },\n  ],\n};\n\nexport { defaultImportmapOptions, getDefaultPwaOptions };\n"
  },
  {
    "path": "internal/vite-config/src/plugins/archiver.ts",
    "content": "import type { PluginOption } from 'vite';\n\nimport type { ArchiverPluginOptions } from '../typing';\n\nimport fs from 'node:fs';\nimport fsp from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport archiver from 'archiver';\n\nexport const viteArchiverPlugin = (\n  options: ArchiverPluginOptions = {},\n): PluginOption => {\n  return {\n    apply: 'build',\n    closeBundle: {\n      handler() {\n        const { name = 'dist', outputDir = '.' } = options;\n\n        setTimeout(async () => {\n          const folderToZip = 'dist';\n\n          const zipOutputDir = join(process.cwd(), outputDir);\n          const zipOutputPath = join(zipOutputDir, `${name}.zip`);\n          try {\n            await fsp.mkdir(zipOutputDir, { recursive: true });\n          } catch {\n            // ignore\n          }\n\n          try {\n            await zipFolder(folderToZip, zipOutputPath);\n            console.log(`Folder has been zipped to: ${zipOutputPath}`);\n          } catch (error) {\n            console.error('Error zipping folder:', error);\n          }\n        }, 0);\n      },\n      order: 'post',\n    },\n    enforce: 'post',\n    name: 'vite:archiver',\n  };\n};\n\nasync function zipFolder(\n  folderPath: string,\n  outputPath: string,\n): Promise<void> {\n  return new Promise((resolve, reject) => {\n    const output = fs.createWriteStream(outputPath);\n    const archive = archiver('zip', {\n      zlib: { level: 9 }, // 设置压缩级别为 9 以实现最高压缩率\n    });\n\n    output.on('close', () => {\n      console.log(\n        `ZIP file created: ${outputPath} (${archive.pointer()} total bytes)`,\n      );\n      resolve();\n    });\n\n    archive.on('error', (err) => {\n      reject(err);\n    });\n\n    archive.pipe(output);\n\n    // 使用 directory 方法以流的方式压缩文件夹，减少内存消耗\n    archive.directory(folderPath, false);\n\n    // 流式处理完成\n    archive.finalize();\n  });\n}\n"
  },
  {
    "path": "internal/vite-config/src/plugins/extra-app-config.ts",
    "content": "import type { PluginOption } from 'vite';\n\nimport {\n  colors,\n  generatorContentHash,\n  readPackageJSON,\n} from '@vben/node-utils';\n\nimport { loadEnv } from '../utils/env';\n\ninterface PluginOptions {\n  isBuild: boolean;\n  root: string;\n}\n\nconst GLOBAL_CONFIG_FILE_NAME = '_app.config.js';\nconst VBEN_ADMIN_PRO_APP_CONF = '_VBEN_ADMIN_PRO_APP_CONF_';\n\n/**\n * 用于将配置文件抽离出来并注入到项目中\n * @returns\n */\n\nasync function viteExtraAppConfigPlugin({\n  isBuild,\n  root,\n}: PluginOptions): Promise<PluginOption | undefined> {\n  let publicPath: string;\n  let source: string;\n\n  if (!isBuild) {\n    return;\n  }\n\n  const { version = '' } = await readPackageJSON(root);\n\n  return {\n    async configResolved(config) {\n      publicPath = ensureTrailingSlash(config.base);\n      source = await getConfigSource();\n    },\n    async generateBundle() {\n      try {\n        this.emitFile({\n          fileName: GLOBAL_CONFIG_FILE_NAME,\n          source,\n          type: 'asset',\n        });\n\n        console.log(colors.cyan(`✨configuration file is build successfully!`));\n      } catch (error) {\n        console.log(\n          colors.red(\n            `configuration file configuration file failed to package:\\n${error}`,\n          ),\n        );\n      }\n    },\n    name: 'vite:extra-app-config',\n    async transformIndexHtml(html) {\n      const hash = `v=${version}-${generatorContentHash(source, 8)}`;\n\n      const appConfigSrc = `${publicPath}${GLOBAL_CONFIG_FILE_NAME}?${hash}`;\n\n      return {\n        html,\n        tags: [{ attrs: { src: appConfigSrc }, tag: 'script' }],\n      };\n    },\n  };\n}\n\nasync function getConfigSource() {\n  const config = await loadEnv();\n  const windowVariable = `window.${VBEN_ADMIN_PRO_APP_CONF}`;\n  // 确保变量不会被修改\n  let source = `${windowVariable}=${JSON.stringify(config)};`;\n  source += `\n    Object.freeze(${windowVariable});\n    Object.defineProperty(window, \"${VBEN_ADMIN_PRO_APP_CONF}\", {\n      configurable: false,\n      writable: false,\n    });\n  `.replaceAll(/\\s/g, '');\n  return source;\n}\n\nfunction ensureTrailingSlash(path: string) {\n  return path.endsWith('/') ? path : `${path}/`;\n}\n\nexport { viteExtraAppConfigPlugin };\n"
  },
  {
    "path": "internal/vite-config/src/plugins/importmap.ts",
    "content": "/**\n * 参考 https://github.com/jspm/vite-plugin-jspm，调整为需要的功能\n */\nimport type { GeneratorOptions } from '@jspm/generator';\nimport type { Plugin } from 'vite';\n\nimport { Generator } from '@jspm/generator';\nimport { load } from 'cheerio';\nimport { minify } from 'html-minifier-terser';\n\nconst DEFAULT_PROVIDER = 'jspm.io';\n\ntype pluginOptions = GeneratorOptions & {\n  debug?: boolean;\n  defaultProvider?: 'esm.sh' | 'jsdelivr' | 'jspm.io';\n  importmap?: Array<{ name: string; range?: string }>;\n};\n\n// async function getLatestVersionOfShims() {\n//   const result = await fetch('https://ga.jspm.io/npm:es-module-shims');\n//   const version = result.text();\n//   return version;\n// }\n\nasync function getShimsUrl(provide: string) {\n  // const version = await getLatestVersionOfShims();\n  const version = '1.10.0';\n\n  const shimsSubpath = `dist/es-module-shims.js`;\n  const providerShimsMap: Record<string, string> = {\n    'esm.sh': `https://esm.sh/es-module-shims@${version}/${shimsSubpath}`,\n    // unpkg: `https://unpkg.com/es-module-shims@${version}/${shimsSubpath}`,\n    jsdelivr: `https://cdn.jsdelivr.net/npm/es-module-shims@${version}/${shimsSubpath}`,\n\n    // 下面两个CDN不稳定，暂时不用\n    'jspm.io': `https://ga.jspm.io/npm:es-module-shims@${version}/${shimsSubpath}`,\n  };\n\n  return providerShimsMap[provide] || providerShimsMap[DEFAULT_PROVIDER];\n}\n\nlet generator: Generator;\n\nasync function viteImportMapPlugin(\n  pluginOptions?: pluginOptions,\n): Promise<Plugin[]> {\n  const { importmap } = pluginOptions || {};\n\n  let isSSR = false;\n  let isBuild = false;\n  let installed = false;\n  let installError: Error | null = null;\n\n  const options: pluginOptions = Object.assign(\n    {},\n    {\n      debug: false,\n      defaultProvider: 'jspm.io',\n      env: ['production', 'browser', 'module'],\n      importmap: [],\n    },\n    pluginOptions,\n  );\n\n  generator = new Generator({\n    ...options,\n    baseUrl: process.cwd(),\n  });\n\n  if (options?.debug) {\n    (async () => {\n      for await (const { message, type } of generator.logStream()) {\n        console.log(`${type}: ${message}`);\n      }\n    })();\n  }\n\n  const imports = options.inputMap?.imports ?? {};\n  const scopes = options.inputMap?.scopes ?? {};\n  const firstLayerKeys = Object.keys(scopes);\n  const inputMapScopes: string[] = [];\n  firstLayerKeys.forEach((key) => {\n    inputMapScopes.push(...Object.keys(scopes[key] || {}));\n  });\n  const inputMapImports = Object.keys(imports);\n\n  const allDepNames: string[] = [\n    ...(importmap?.map((item) => item.name) || []),\n    ...inputMapImports,\n    ...inputMapScopes,\n  ];\n  const depNames = new Set<string>(allDepNames);\n\n  const installDeps = importmap?.map((item) => ({\n    range: item.range,\n    target: item.name,\n  }));\n\n  return [\n    {\n      async config(_, { command, isSsrBuild }) {\n        isBuild = command === 'build';\n        isSSR = !!isSsrBuild;\n      },\n      enforce: 'pre',\n      name: 'importmap:external',\n      resolveId(id) {\n        if (isSSR || !isBuild) {\n          return null;\n        }\n\n        if (!depNames.has(id)) {\n          return null;\n        }\n        return { external: true, id };\n      },\n    },\n    {\n      enforce: 'post',\n      name: 'importmap:install',\n      async resolveId() {\n        if (isSSR || !isBuild || installed) {\n          return null;\n        }\n        try {\n          installed = true;\n          await Promise.allSettled(\n            (installDeps || []).map((dep) => generator.install(dep)),\n          );\n        } catch (error: any) {\n          installError = error;\n          installed = false;\n        }\n        return null;\n      },\n    },\n    {\n      buildEnd() {\n        // 未生成importmap时，抛出错误，防止被turbo缓存\n        if (!installed && !isSSR) {\n          installError && console.error(installError);\n          throw new Error('Importmap installation failed.');\n        }\n      },\n      enforce: 'post',\n      name: 'importmap:html',\n      transformIndexHtml: {\n        async handler(html) {\n          if (isSSR || !isBuild) {\n            return html;\n          }\n\n          const importmapJson = generator.getMap();\n\n          if (!importmapJson) {\n            return html;\n          }\n\n          const esModuleShimsSrc = await getShimsUrl(\n            options.defaultProvider || DEFAULT_PROVIDER,\n          );\n\n          const resultHtml = await injectShimsToHtml(\n            html,\n            esModuleShimsSrc || '',\n          );\n          html = await minify(resultHtml || html, {\n            collapseWhitespace: true,\n            minifyCSS: true,\n            minifyJS: true,\n            removeComments: false,\n          });\n\n          return {\n            html,\n            tags: [\n              {\n                attrs: {\n                  type: 'importmap',\n                },\n                injectTo: 'head-prepend',\n                tag: 'script',\n                children: `${JSON.stringify(importmapJson)}`,\n              },\n            ],\n          };\n        },\n        order: 'post',\n      },\n    },\n  ];\n}\n\nasync function injectShimsToHtml(html: string, esModuleShimUrl: string) {\n  const $ = load(html);\n\n  const $script = $(`script[type='module']`);\n\n  if (!$script) {\n    return;\n  }\n\n  const entry = $script.attr('src');\n\n  $script.removeAttr('type');\n  $script.removeAttr('crossorigin');\n  $script.removeAttr('src');\n  $script.html(`\nif (!HTMLScriptElement.supports || !HTMLScriptElement.supports('importmap')) {\n  self.importShim = function () {\n      const promise = new Promise((resolve, reject) => {\n          document.head.appendChild(\n              Object.assign(document.createElement('script'), {\n                  src: '${esModuleShimUrl}',\n                  crossorigin: 'anonymous',\n                  async: true,\n                  onload() {\n                      if (!importShim.$proxy) {\n                          resolve(importShim);\n                      } else {\n                          reject(new Error('No globalThis.importShim found:' + esModuleShimUrl));\n                      }\n                  },\n                  onerror(error) {\n                      reject(error);\n                  },\n              }),\n          );\n      });\n      importShim.$proxy = true;\n      return promise.then((importShim) => importShim(...arguments));\n  };\n}\n\nvar modules = ['${entry}'];\ntypeof importShim === 'function'\n  ? modules.forEach((moduleName) => importShim(moduleName))\n  : modules.forEach((moduleName) => import(moduleName));\n `);\n  $('body').after($script);\n  $('head').remove(`script[type='module']`);\n  return $.html();\n}\n\nexport { viteImportMapPlugin };\n"
  },
  {
    "path": "internal/vite-config/src/plugins/index.ts",
    "content": "import type { PluginOption } from 'vite';\n\nimport type {\n  ApplicationPluginOptions,\n  CommonPluginOptions,\n  ConditionPlugin,\n  LibraryPluginOptions,\n} from '../typing';\n\nimport viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';\nimport tailwindcss from '@tailwindcss/vite';\nimport viteVue from '@vitejs/plugin-vue';\nimport viteVueJsx from '@vitejs/plugin-vue-jsx';\nimport { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer';\nimport viteDtsPlugin from 'unplugin-dts/vite';\nimport viteCompressPlugin from 'vite-plugin-compression';\nimport { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html';\nimport { VitePWA } from 'vite-plugin-pwa';\nimport viteVueDevTools from 'vite-plugin-vue-devtools';\n\nimport { viteArchiverPlugin } from './archiver';\nimport { viteExtraAppConfigPlugin } from './extra-app-config';\nimport { viteImportMapPlugin } from './importmap';\nimport { viteInjectAppLoadingPlugin } from './inject-app-loading';\nimport { viteMetadataPlugin } from './inject-metadata';\nimport { viteLicensePlugin } from './license';\nimport { viteNitroMockPlugin } from './nitro-mock';\nimport { vitePrintPlugin } from './print';\nimport { viteTailwindReferencePlugin } from './tailwind-reference';\nimport { viteVxeTableImportsPlugin } from './vxe-table';\n\n/**\n * 获取条件成立的 vite 插件\n * @param conditionPlugins\n */\nasync function loadConditionPlugins(conditionPlugins: ConditionPlugin[]) {\n  const plugins: PluginOption[] = [];\n  for (const conditionPlugin of conditionPlugins) {\n    if (conditionPlugin.condition) {\n      const realPlugins = await conditionPlugin.plugins();\n      plugins.push(...realPlugins);\n    }\n  }\n  return plugins.flat();\n}\n\n/**\n * 根据条件获取通用的vite插件\n */\nasync function loadCommonPlugins(\n  options: CommonPluginOptions,\n): Promise<ConditionPlugin[]> {\n  const { devtools, injectMetadata, isBuild, visualizer } = options;\n  return [\n    {\n      condition: true,\n      plugins: () => [\n        viteVue({\n          script: {\n            defineModel: true,\n            // propsDestructure: true,\n          },\n        }),\n        viteVueJsx(),\n        viteTailwindReferencePlugin(),\n        tailwindcss(),\n      ],\n    },\n\n    {\n      condition: !isBuild && devtools,\n      plugins: () => [viteVueDevTools()],\n    },\n    {\n      condition: injectMetadata,\n      plugins: async () => [await viteMetadataPlugin()],\n    },\n    {\n      condition: isBuild && !!visualizer,\n      plugins: () => [\n        viteVisualizerPlugin({\n          filename: './node_modules/.cache/visualizer/stats.html',\n          gzipSize: true,\n          open: true,\n        }) as PluginOption,\n      ],\n    },\n  ];\n}\n\n/**\n * 根据条件获取应用类型的vite插件\n */\nasync function loadApplicationPlugins(\n  options: ApplicationPluginOptions,\n): Promise<PluginOption[]> {\n  // 单独取，否则commonOptions拿不到\n  const isBuild = options.isBuild;\n  const env = options.env;\n\n  const {\n    archiver,\n    archiverPluginOptions,\n    compress,\n    compressTypes,\n    extraAppConfig,\n    html,\n    i18n,\n    importmap,\n    importmapOptions,\n    injectAppLoading,\n    license,\n    nitroMock,\n    nitroMockOptions,\n    print,\n    printInfoMap,\n    pwa,\n    pwaOptions,\n    vxeTableLazyImport,\n    ...commonOptions\n  } = options;\n\n  const commonPlugins = await loadCommonPlugins(commonOptions);\n\n  return await loadConditionPlugins([\n    ...commonPlugins,\n    {\n      condition: i18n,\n      plugins: async () => {\n        return [\n          viteVueI18nPlugin({\n            compositionOnly: true,\n            fullInstall: true,\n            runtimeOnly: true,\n          }),\n        ];\n      },\n    },\n    {\n      condition: print,\n      plugins: async () => {\n        return [await vitePrintPlugin({ infoMap: printInfoMap })];\n      },\n    },\n    {\n      condition: vxeTableLazyImport,\n      plugins: async () => {\n        return [await viteVxeTableImportsPlugin()];\n      },\n    },\n    {\n      condition: nitroMock,\n      plugins: async () => {\n        return [await viteNitroMockPlugin(nitroMockOptions)];\n      },\n    },\n\n    {\n      condition: injectAppLoading,\n      plugins: async () => [await viteInjectAppLoadingPlugin(!!isBuild, env)],\n    },\n    {\n      condition: license,\n      plugins: async () => [await viteLicensePlugin()],\n    },\n    {\n      condition: pwa,\n      plugins: () =>\n        VitePWA({\n          injectRegister: false,\n          workbox: {\n            globPatterns: [],\n          },\n          ...pwaOptions,\n          manifest: {\n            display: 'standalone',\n            start_url: '/',\n            theme_color: '#ffffff',\n            ...pwaOptions?.manifest,\n          },\n        }),\n    },\n    {\n      condition: isBuild && !!compress,\n      plugins: () => {\n        const compressPlugins: PluginOption[] = [];\n        if (compressTypes?.includes('brotli')) {\n          compressPlugins.push(\n            viteCompressPlugin({ deleteOriginFile: false, ext: '.br' }),\n          );\n        }\n        if (compressTypes?.includes('gzip')) {\n          compressPlugins.push(\n            viteCompressPlugin({ deleteOriginFile: false, ext: '.gz' }),\n          );\n        }\n        return compressPlugins;\n      },\n    },\n    {\n      condition: !!html,\n      plugins: () => [viteHtmlPlugin({ minify: true })],\n    },\n    {\n      condition: isBuild && importmap,\n      plugins: () => {\n        return [viteImportMapPlugin(importmapOptions)];\n      },\n    },\n    {\n      condition: isBuild && extraAppConfig,\n      plugins: async () => [\n        await viteExtraAppConfigPlugin({ isBuild: true, root: process.cwd() }),\n      ],\n    },\n    {\n      condition: archiver,\n      plugins: async () => {\n        return [await viteArchiverPlugin(archiverPluginOptions)];\n      },\n    },\n  ]);\n}\n\n/**\n * 根据条件获取库类型的vite插件\n */\nasync function loadLibraryPlugins(\n  options: LibraryPluginOptions,\n): Promise<PluginOption[]> {\n  // 单独取，否则commonOptions拿不到\n  const isBuild = options.isBuild;\n  const { dts, ...commonOptions } = options;\n  const dtsOptions = typeof dts === 'object' ? dts : undefined;\n  const commonPlugins = await loadCommonPlugins(commonOptions);\n  return await loadConditionPlugins([\n    ...commonPlugins,\n    {\n      condition: isBuild && !!dts,\n      plugins: () => [viteDtsPlugin(dtsOptions)],\n    },\n  ]);\n}\n\nexport {\n  loadApplicationPlugins,\n  loadLibraryPlugins,\n  viteArchiverPlugin,\n  viteCompressPlugin,\n  viteDtsPlugin,\n  viteHtmlPlugin,\n  viteVisualizerPlugin,\n  viteVxeTableImportsPlugin,\n};\n"
  },
  {
    "path": "internal/vite-config/src/plugins/inject-app-loading/README.md",
    "content": "# inject-app-loading\n\n用于在应用加载时显示加载动画的插件，可自行选择加载动画的样式。\n"
  },
  {
    "path": "internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html",
    "content": "<style data-app-loading=\"inject-css\">\n  html {\n    /* same as ant-design-vue/dist/reset.css setting, avoid the title line-height changed */\n    line-height: 1.15;\n  }\n\n  .dark .loading {\n    background-color: #0d0d10;\n  }\n\n  .dark .loading .title {\n    color: rgb(255 255 255 / 85%);\n  }\n\n  .loading {\n    position: fixed;\n    top: 0;\n    left: 0;\n    z-index: 9999;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    height: 100%;\n    overflow: hidden;\n    pointer-events: none;\n    background-color: #f4f7f9;\n  }\n\n  .loading.hidden {\n    visibility: hidden;\n    opacity: 0;\n    transition: all 0.6s ease-out;\n  }\n\n  .loading .title {\n    margin-top: 36px;\n    font-size: 30px;\n    font-weight: 600;\n    color: rgb(0 0 0 / 85%);\n  }\n\n  .dot {\n    position: relative;\n    box-sizing: border-box;\n    display: inline-block;\n    width: 48px;\n    height: 48px;\n    margin-top: 30px;\n    font-size: 32px;\n    transform: rotate(45deg);\n    animation: rotate-ani 1.2s infinite linear;\n  }\n\n  .dot i {\n    position: absolute;\n    display: block;\n    width: 20px;\n    height: 20px;\n    background-color: hsl(var(--primary, 210 100% 50%));\n    border-radius: 100%;\n    opacity: 0.3;\n    transform: scale(0.75);\n    transform-origin: 50% 50%;\n    animation: spin-move-ani 1s infinite linear alternate;\n  }\n\n  .dot i:nth-child(1) {\n    top: 0;\n    left: 0;\n  }\n\n  .dot i:nth-child(2) {\n    top: 0;\n    right: 0;\n    animation-delay: 0.4s;\n  }\n\n  .dot i:nth-child(3) {\n    right: 0;\n    bottom: 0;\n    animation-delay: 0.8s;\n  }\n\n  .dot i:nth-child(4) {\n    bottom: 0;\n    left: 0;\n    animation-delay: 1.2s;\n  }\n\n  @keyframes rotate-ani {\n    to {\n      transform: rotate(405deg);\n    }\n  }\n\n  @keyframes spin-move-ani {\n    to {\n      opacity: 1;\n    }\n  }\n</style>\n<div class=\"loading\" id=\"__app-loading__\">\n  <span class=\"dot\"><i></i><i></i><i></i><i></i></span>\n  <div class=\"title\"><%= VITE_APP_TITLE %></div>\n</div>\n"
  },
  {
    "path": "internal/vite-config/src/plugins/inject-app-loading/default-loading.html",
    "content": "<style data-app-loading=\"inject-css\">\n  html {\n    /* same as ant-design-vue/dist/reset.css setting, avoid the title line-height changed */\n    line-height: 1.15;\n  }\n\n  .loading {\n    position: fixed;\n    top: 0;\n    left: 0;\n    z-index: 9999;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    height: 100%;\n    overflow: hidden;\n    background-color: #f4f7f9;\n\n    /* transition: all 0.8s ease-out; */\n  }\n\n  .loading.hidden {\n    pointer-events: none;\n    visibility: hidden;\n    opacity: 0;\n    transition: all 0.8s ease-out;\n  }\n\n  .dark .loading {\n    background: #0d0d10;\n  }\n\n  .title {\n    margin-top: 66px;\n    font-size: 28px;\n    font-weight: 600;\n    color: rgb(0 0 0 / 85%);\n  }\n\n  .dark .title {\n    color: #fff;\n  }\n\n  .loader {\n    position: relative;\n    width: 48px;\n    height: 48px;\n  }\n\n  .loader::before {\n    position: absolute;\n    top: 60px;\n    left: 0;\n    width: 48px;\n    height: 5px;\n    content: '';\n    background: hsl(var(--primary, 210 100% 50%) / 50%);\n    border-radius: 50%;\n    animation: shadow-ani 0.5s linear infinite;\n  }\n\n  .loader::after {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    content: '';\n    background: hsl(var(--primary, 210 100% 50%));\n    border-radius: 4px;\n    animation: jump-ani 0.5s linear infinite;\n  }\n\n  @keyframes jump-ani {\n    15% {\n      border-bottom-right-radius: 3px;\n    }\n\n    25% {\n      transform: translateY(9px) rotate(22.5deg);\n    }\n\n    50% {\n      border-bottom-right-radius: 40px;\n      transform: translateY(18px) scale(1, 0.9) rotate(45deg);\n    }\n\n    75% {\n      transform: translateY(9px) rotate(67.5deg);\n    }\n\n    100% {\n      transform: translateY(0) rotate(90deg);\n    }\n  }\n\n  @keyframes shadow-ani {\n    0%,\n    100% {\n      transform: scale(1, 1);\n    }\n\n    50% {\n      transform: scale(1.2, 1);\n    }\n  }\n</style>\n<div class=\"loading\" id=\"__app-loading__\">\n  <div class=\"loader\"></div>\n  <div class=\"title\"><%= VITE_APP_TITLE %></div>\n</div>\n"
  },
  {
    "path": "internal/vite-config/src/plugins/inject-app-loading/index.ts",
    "content": "import type { PluginOption } from 'vite';\n\nimport fs from 'node:fs';\nimport fsp from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { readPackageJSON } from '@vben/node-utils';\n\n/**\n * 用于生成将loading样式注入到项目中\n * 为多app提供loading样式，无需在每个 app -> index.html单独引入\n */\nasync function viteInjectAppLoadingPlugin(\n  isBuild: boolean,\n  env: Record<string, any> = {},\n  loadingTemplate = 'loading.html',\n): Promise<PluginOption | undefined> {\n  const loadingHtml = await getLoadingRawByHtmlTemplate(loadingTemplate);\n  const { version } = await readPackageJSON(process.cwd());\n  const envRaw = isBuild ? 'prod' : 'dev';\n  const cacheName = `'${env.VITE_APP_NAMESPACE}-${version}-${envRaw}-preferences-theme'`;\n\n  // 获取缓存的主题\n  // 保证黑暗主题下，刷新页面时，loading也是黑暗主题\n  const injectScript = `\n  <script data-app-loading=\"inject-js\">\n  var theme = localStorage.getItem(${cacheName});\n  document.documentElement.classList.toggle('dark', /dark/.test(theme));\n</script>\n`;\n\n  if (!loadingHtml) {\n    return;\n  }\n\n  return {\n    enforce: 'pre',\n    name: 'vite:inject-app-loading',\n    transformIndexHtml: {\n      handler(html) {\n        const re = /<body\\s*>/;\n        html = html.replace(re, `<body>${injectScript}${loadingHtml}`);\n        return html;\n      },\n      order: 'pre',\n    },\n  };\n}\n\n/**\n * 用于获取loading的html模板\n */\nasync function getLoadingRawByHtmlTemplate(loadingTemplate: string) {\n  // 支持在app内自定义loading模板，模版参考default-loading.html即可\n  let appLoadingPath = join(process.cwd(), loadingTemplate);\n\n  if (!fs.existsSync(appLoadingPath)) {\n    const __dirname = fileURLToPath(new URL('.', import.meta.url));\n    appLoadingPath = join(__dirname, './default-loading.html');\n  }\n\n  return await fsp.readFile(appLoadingPath, 'utf8');\n}\n\nexport { viteInjectAppLoadingPlugin };\n"
  },
  {
    "path": "internal/vite-config/src/plugins/inject-metadata.ts",
    "content": "import type { PluginOption } from 'vite';\n\nimport {\n  dateUtil,\n  findMonorepoRoot,\n  getPackages,\n  readPackageJSON,\n} from '@vben/node-utils';\n\nimport { readWorkspaceManifest } from '@pnpm/workspace.read-manifest';\n\nfunction resolvePackageVersion(\n  pkgsMeta: Record<string, string>,\n  name: string,\n  value: string,\n  catalog: Record<string, string>,\n) {\n  if (value.includes('catalog:')) {\n    return catalog[name];\n  }\n\n  if (value.includes('workspace')) {\n    return pkgsMeta[name];\n  }\n\n  return value;\n}\n\nasync function resolveMonorepoDependencies() {\n  const { packages } = await getPackages();\n  const manifest = await readWorkspaceManifest(findMonorepoRoot());\n  const catalog = manifest?.catalog || {};\n\n  const resultDevDependencies: Record<string, string | undefined> = {};\n  const resultDependencies: Record<string, string | undefined> = {};\n  const pkgsMeta: Record<string, string> = {};\n\n  for (const { packageJson } of packages) {\n    pkgsMeta[packageJson.name] = packageJson.version;\n  }\n\n  for (const { packageJson } of packages) {\n    const { dependencies = {}, devDependencies = {} } = packageJson;\n    for (const [key, value] of Object.entries(dependencies)) {\n      resultDependencies[key] = resolvePackageVersion(\n        pkgsMeta,\n        key,\n        value,\n        catalog,\n      );\n    }\n    for (const [key, value] of Object.entries(devDependencies)) {\n      resultDevDependencies[key] = resolvePackageVersion(\n        pkgsMeta,\n        key,\n        value,\n        catalog,\n      );\n    }\n  }\n  return {\n    dependencies: resultDependencies,\n    devDependencies: resultDevDependencies,\n  };\n}\n\n/**\n * 用于注入项目信息\n */\nasync function viteMetadataPlugin(\n  root = process.cwd(),\n): Promise<PluginOption | undefined> {\n  const { author, description, homepage, license, version } =\n    await readPackageJSON(root);\n\n  const buildTime = dateUtil().format('YYYY-MM-DD HH:mm:ss');\n\n  return {\n    async config() {\n      const { dependencies, devDependencies } =\n        await resolveMonorepoDependencies();\n\n      const isAuthorObject = typeof author === 'object';\n      const authorName = isAuthorObject ? author.name : author;\n      const authorEmail = isAuthorObject ? author.email : null;\n      const authorUrl = isAuthorObject ? author.url : null;\n\n      return {\n        define: {\n          __VBEN_ADMIN_METADATA__: JSON.stringify({\n            authorEmail,\n            authorName,\n            authorUrl,\n            buildTime,\n            dependencies,\n            description,\n            devDependencies,\n            homepage,\n            license,\n            version,\n          }),\n          'import.meta.env.VITE_APP_VERSION': JSON.stringify(version),\n        },\n      };\n    },\n    enforce: 'post',\n    name: 'vite:inject-metadata',\n  };\n}\n\nexport { viteMetadataPlugin };\n"
  },
  {
    "path": "internal/vite-config/src/plugins/license.ts",
    "content": "import type {\n  NormalizedOutputOptions,\n  OutputBundle,\n  OutputChunk,\n} from 'rollup';\nimport type { PluginOption } from 'vite';\n\nimport { EOL } from 'node:os';\n\nimport { dateUtil, readPackageJSON } from '@vben/node-utils';\n\n/**\n * 用于注入版权信息\n * @returns\n */\n\nasync function viteLicensePlugin(\n  root = process.cwd(),\n): Promise<PluginOption | undefined> {\n  const {\n    description = '',\n    homepage = '',\n    version = '',\n  } = await readPackageJSON(root);\n\n  return {\n    apply: 'build',\n    enforce: 'post',\n    generateBundle: {\n      handler: (_options: NormalizedOutputOptions, bundle: OutputBundle) => {\n        const date = dateUtil().format('YYYY-MM-DD ');\n        const copyrightText = `/*!\n  * Vben Admin\n  * Version: ${version}\n  * Author: vben\n  * Copyright (C) 2024 Vben\n  * License: MIT License\n  * Description: ${description}\n  * Date Created: ${date}\n  * Homepage: ${homepage}\n  * Contact: ann.vben@gmail.com\n*/\n              `.trim();\n\n        for (const [, fileContent] of Object.entries(bundle)) {\n          if (fileContent.type === 'chunk' && fileContent.isEntry) {\n            const chunkContent = fileContent as OutputChunk;\n            // 插入版权信息\n            const content = chunkContent.code;\n            const updatedContent = `${copyrightText}${EOL}${content}`;\n\n            // 更新bundle\n            (fileContent as OutputChunk).code = updatedContent;\n          }\n        }\n      },\n      order: 'post',\n    },\n    name: 'vite:license',\n  };\n}\n\nexport { viteLicensePlugin };\n"
  },
  {
    "path": "internal/vite-config/src/plugins/nitro-mock.ts",
    "content": "import type { PluginOption } from 'vite';\n\nimport type { NitroMockPluginOptions } from '../typing';\n\nimport { colors, consola, getPackage } from '@vben/node-utils';\n\nimport getPort from 'get-port';\nimport { build, createDevServer, createNitro, prepare } from 'nitropack';\n\nconst hmrKeyRe = /^runtimeConfig\\.|routeRules\\./;\n\nexport const viteNitroMockPlugin = ({\n  mockServerPackage = '@vben/backend-mock',\n  port = 5320,\n  verbose = true,\n}: NitroMockPluginOptions = {}): PluginOption => {\n  return {\n    async configureServer(server) {\n      const availablePort = await getPort({ port });\n      if (availablePort !== port) {\n        return;\n      }\n\n      const pkg = await getPackage(mockServerPackage);\n      if (!pkg) {\n        consola.log(\n          `Package ${mockServerPackage} not found. Skip mock server.`,\n        );\n        return;\n      }\n\n      runNitroServer(pkg.dir, port, verbose);\n\n      const _printUrls = server.printUrls;\n      server.printUrls = () => {\n        _printUrls();\n\n        consola.log(\n          `  ${colors.green('➜')}  ${colors.bold('Nitro Mock Server')}: ${colors.cyan(`http://localhost:${port}/api`)}`,\n        );\n      };\n    },\n    enforce: 'pre',\n    name: 'vite:mock-server',\n  };\n};\n\nasync function runNitroServer(rootDir: string, port: number, verbose: boolean) {\n  let nitro: any;\n  const reload = async () => {\n    if (nitro) {\n      consola.info('Restarting dev server...');\n      if ('unwatch' in nitro.options._c12) {\n        await nitro.options._c12.unwatch();\n      }\n      await nitro.close();\n    }\n    nitro = await createNitro(\n      {\n        dev: true,\n        preset: 'nitro-dev',\n        rootDir,\n      },\n      {\n        c12: {\n          async onUpdate({ getDiff, newConfig }) {\n            const diff = getDiff();\n            if (diff.length === 0) {\n              return;\n            }\n            verbose &&\n              consola.info(\n                `Nitro config updated:\\n${diff\n                  .map((entry) => `  ${entry.toString()}`)\n                  .join('\\n')}`,\n              );\n            await (diff.every((e) => hmrKeyRe.test(e.key))\n              ? nitro.updateConfig(newConfig.config)\n              : reload());\n          },\n        },\n        watch: true,\n      },\n    );\n    nitro.hooks.hookOnce('restart', reload);\n\n    const server = createDevServer(nitro);\n    await server.listen(port, { showURL: false });\n    await prepare(nitro);\n    await build(nitro);\n\n    if (verbose) {\n      console.log('');\n      consola.success(colors.bold(colors.green('Nitro Mock Server started.')));\n    }\n  };\n  return await reload();\n}\n"
  },
  {
    "path": "internal/vite-config/src/plugins/print.ts",
    "content": "import type { PluginOption } from 'vite';\n\nimport type { PrintPluginOptions } from '../typing';\n\nimport { colors } from '@vben/node-utils';\n\nexport const vitePrintPlugin = (\n  options: PrintPluginOptions = {},\n): PluginOption => {\n  const { infoMap = {} } = options;\n\n  return {\n    configureServer(server) {\n      const _printUrls = server.printUrls;\n      server.printUrls = () => {\n        _printUrls();\n\n        for (const [key, value] of Object.entries(infoMap)) {\n          console.log(\n            `  ${colors.green('➜')}  ${colors.bold(key)}: ${colors.cyan(value)}`,\n          );\n        }\n      };\n    },\n    enforce: 'pre',\n    name: 'vite:print-info',\n  };\n};\n"
  },
  {
    "path": "internal/vite-config/src/plugins/tailwind-reference.ts",
    "content": "import type { Plugin } from 'vite';\n\nconst REFERENCE_LINE = '@reference \"@vben/tailwind-config/theme\";\\n';\n\n/**\n * Auto-inject @reference into Vue SFC <style> blocks that use @apply.\n *\n * In Tailwind CSS v4, each Vue SFC <style scoped> block is processed as an\n * independent CSS module. If a style block uses @apply with custom theme\n * utilities (e.g. bg-primary, text-foreground), it needs access to the\n * @theme definition via @reference. This plugin auto-injects it so\n * individual components don't need to add it manually.\n */\nexport function viteTailwindReferencePlugin(): Plugin {\n  return {\n    enforce: 'pre',\n    name: 'vite:tailwind-reference',\n    transform(code, id) {\n      // Only process Vue SFC style blocks\n      if (!id.includes('.vue')) {\n        return null;\n      }\n      if (!id.includes('type=style')) {\n        return null;\n      }\n      // Skip if already has @reference\n      if (code.includes('@reference')) {\n        return null;\n      }\n      // Only inject if the style block uses @apply\n      if (!code.includes('@apply')) {\n        return null;\n      }\n      return {\n        code: REFERENCE_LINE + code,\n        map: null,\n      };\n    },\n  };\n}\n"
  },
  {
    "path": "internal/vite-config/src/plugins/vxe-table.ts",
    "content": "import type { PluginOption } from 'vite';\n\nimport { lazyImport, VxeResolver } from 'vite-plugin-lazy-import';\n\nasync function viteVxeTableImportsPlugin(): Promise<PluginOption> {\n  return [\n    lazyImport({\n      resolvers: [\n        VxeResolver({\n          libraryName: 'vxe-table',\n        }),\n        VxeResolver({\n          libraryName: 'vxe-pc-ui',\n        }),\n      ],\n    }),\n  ];\n}\n\nexport { viteVxeTableImportsPlugin };\n"
  },
  {
    "path": "internal/vite-config/src/typing.ts",
    "content": "import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';\nimport type { PluginOptions } from 'unplugin-dts';\nimport type {\n  ConfigEnv,\n  PluginOption,\n  UserConfig,\n  UserConfigFnPromise,\n} from 'vite';\nimport type { Options as PwaPluginOptions } from 'vite-plugin-pwa';\n\n/**\n * ImportMap 配置接口\n * @description 用于配置模块导入映射，支持自定义导入路径和范围\n * @example\n * ```typescript\n * {\n *   imports: {\n *     'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js'\n *   },\n *   scopes: {\n *     'https://site.com/': {\n *       'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js'\n *     }\n *   }\n * }\n * ```\n */\ninterface IImportMap {\n  /** 模块导入映射 */\n  imports?: Record<string, string>;\n  /** 作用域特定的导入映射 */\n  scopes?: {\n    [scope: string]: Record<string, string>;\n  };\n}\n\n/**\n * 打印插件配置选项\n * @description 用于配置控制台打印信息\n */\ninterface PrintPluginOptions {\n  /**\n   * 打印的数据映射\n   * @description 键值对形式的数据，将在控制台打印\n   * @example\n   * ```typescript\n   * {\n   *   'App Version': '1.0.0',\n   *   'Build Time': '2024-01-01'\n   * }\n   * ```\n   */\n  infoMap?: Record<string, string | undefined>;\n}\n\n/**\n * Nitro Mock 插件配置选项\n * @description 用于配置 Nitro Mock 服务器的行为\n */\ninterface NitroMockPluginOptions {\n  /**\n   * Mock 服务器包名\n   * @default '@vbenjs/nitro-mock'\n   */\n  mockServerPackage?: string;\n\n  /**\n   * Mock 服务端口\n   * @default 3000\n   */\n  port?: number;\n\n  /**\n   * 是否打印 Mock 日志\n   * @default false\n   */\n  verbose?: boolean;\n}\n\n/**\n * 归档插件配置选项\n * @description 用于配置构建产物的压缩归档\n */\ninterface ArchiverPluginOptions {\n  /**\n   * 输出文件名\n   * @default 'dist'\n   */\n  name?: string;\n  /**\n   * 输出目录\n   * @default '.'\n   */\n  outputDir?: string;\n}\n\n/**\n * ImportMap 插件配置\n * @description 用于配置模块的 CDN 导入\n */\ninterface ImportmapPluginOptions {\n  /**\n   * CDN 供应商\n   * @default 'jspm.io'\n   * @description 支持 esm.sh 和 jspm.io 两种 CDN 供应商\n   */\n  defaultProvider?: 'esm.sh' | 'jspm.io';\n  /**\n   * ImportMap 配置数组\n   * @description 配置需要从 CDN 导入的包\n   * @example\n   * ```typescript\n   * [\n   *   { name: 'vue' },\n   *   { name: 'pinia', range: '^2.0.0' }\n   * ]\n   * ```\n   */\n  importmap?: Array<{ name: string; range?: string }>;\n  /**\n   * 手动配置 ImportMap\n   * @description 自定义 ImportMap 配置\n   */\n  inputMap?: IImportMap;\n}\n\n/**\n * 条件插件配置\n * @description 用于根据条件动态加载插件\n */\ninterface ConditionPlugin {\n  /**\n   * 判断条件\n   * @description 当条件为 true 时加载插件\n   */\n  condition?: boolean;\n  /**\n   * 插件对象\n   * @description 返回插件数组或 Promise\n   */\n  plugins: () => PluginOption[] | PromiseLike<PluginOption[]>;\n}\n\n/**\n * 通用插件配置选项\n * @description 所有插件共用的基础配置\n */\ninterface CommonPluginOptions {\n  /**\n   * 是否开启开发工具\n   * @default false\n   */\n  devtools?: boolean;\n  /**\n   * 环境变量\n   * @description 自定义环境变量\n   */\n  env?: Record<string, any>;\n  /**\n   * 是否注入元数据\n   * @default true\n   */\n  injectMetadata?: boolean;\n  /**\n   * 是否为构建模式\n   * @default false\n   */\n  isBuild?: boolean;\n  /**\n   * 构建模式\n   * @default 'development'\n   */\n  mode?: string;\n  /**\n   * 是否开启依赖分析\n   * @default false\n   * @description 使用 rollup-plugin-visualizer 分析依赖\n   */\n  visualizer?: boolean | PluginVisualizerOptions;\n}\n\n/**\n * 应用插件配置选项\n * @description 用于配置应用构建时的插件选项\n */\ninterface ApplicationPluginOptions extends CommonPluginOptions {\n  /**\n   * 是否开启压缩归档\n   * @default false\n   * @description 开启后会在打包目录生成 zip 文件\n   */\n  archiver?: boolean;\n  /**\n   * 压缩归档插件配置\n   * @description 配置压缩归档的行为\n   */\n  archiverPluginOptions?: ArchiverPluginOptions;\n  /**\n   * 是否开启压缩\n   * @default false\n   * @description 支持 gzip 和 brotli 压缩\n   */\n  compress?: boolean;\n  /**\n   * 压缩类型\n   * @default ['gzip']\n   * @description 可选的压缩类型\n   */\n  compressTypes?: ('brotli' | 'gzip')[];\n  /**\n   * 是否抽离配置文件\n   * @default false\n   * @description 在构建时抽离配置文件\n   */\n  extraAppConfig?: boolean;\n  /**\n   * 是否开启 HTML 插件\n   * @default true\n   */\n  html?: boolean;\n  /**\n   * 是否开启国际化\n   * @default false\n   */\n  i18n?: boolean;\n  /**\n   * 是否开启 ImportMap CDN\n   * @default false\n   */\n  importmap?: boolean;\n  /**\n   * ImportMap 插件配置\n   */\n  importmapOptions?: ImportmapPluginOptions;\n  /**\n   * 是否注入应用加载动画\n   * @default true\n   */\n  injectAppLoading?: boolean;\n  /**\n   * 是否注入全局 SCSS\n   * @default true\n   */\n  injectGlobalScss?: boolean;\n  /**\n   * 是否注入版权信息\n   * @default true\n   */\n  license?: boolean;\n  /**\n   * 是否开启 Nitro Mock\n   * @default false\n   */\n  nitroMock?: boolean;\n  /**\n   * Nitro Mock 插件配置\n   */\n  nitroMockOptions?: NitroMockPluginOptions;\n  /**\n   * 是否开启控制台打印\n   * @default false\n   */\n  print?: boolean;\n  /**\n   * 打印插件配置\n   */\n  printInfoMap?: PrintPluginOptions['infoMap'];\n  /**\n   * 是否开启 PWA\n   * @default false\n   */\n  pwa?: boolean;\n  /**\n   * PWA 插件配置\n   */\n  pwaOptions?: Partial<PwaPluginOptions>;\n  /**\n   * 是否开启 VXE Table 懒加载\n   * @default false\n   */\n  vxeTableLazyImport?: boolean;\n}\n\n/**\n * 库插件配置选项\n * @description 用于配置库构建时的插件选项\n */\ninterface LibraryPluginOptions extends CommonPluginOptions {\n  /**\n   * 是否开启 DTS 输出\n   * @default true\n   * @description 生成 TypeScript 类型声明文件\n   */\n  dts?: boolean | PluginOptions;\n}\n\n/**\n * 应用配置选项类型\n */\ntype ApplicationOptions = ApplicationPluginOptions;\n\n/**\n * 库配置选项类型\n */\ntype LibraryOptions = LibraryPluginOptions;\n\n/**\n * 应用配置定义函数类型\n * @description 用于定义应用构建配置\n */\ntype DefineApplicationOptions = (config?: ConfigEnv) => Promise<{\n  /** 应用插件配置 */\n  application?: ApplicationOptions;\n  /** Vite 配置 */\n  vite?: UserConfig;\n}>;\n\n/**\n * 库配置定义函数类型\n * @description 用于定义库构建配置\n */\ntype DefineLibraryOptions = (config?: ConfigEnv) => Promise<{\n  /** 库插件配置 */\n  library?: LibraryOptions;\n  /** Vite 配置 */\n  vite?: UserConfig;\n}>;\n\n/**\n * 配置定义类型\n * @description 应用或库的配置定义\n */\ntype DefineConfig = DefineApplicationOptions | DefineLibraryOptions;\n\ntype VbenViteConfig = Promise<UserConfig> | UserConfig | UserConfigFnPromise;\n\nexport type {\n  ApplicationPluginOptions,\n  ArchiverPluginOptions,\n  CommonPluginOptions,\n  ConditionPlugin,\n  DefineApplicationOptions,\n  DefineConfig,\n  DefineLibraryOptions,\n  IImportMap,\n  ImportmapPluginOptions,\n  LibraryPluginOptions,\n  NitroMockPluginOptions,\n  PrintPluginOptions,\n  VbenViteConfig,\n};\n"
  },
  {
    "path": "internal/vite-config/src/utils/env.ts",
    "content": "import type { ApplicationPluginOptions } from '../typing';\n\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { fs } from '@vben/node-utils';\n\nimport dotenv from 'dotenv';\n\nconst getBoolean = (value: string | undefined) => value === 'true';\n\nconst getString = (value: string | undefined, fallback: string) =>\n  value ?? fallback;\n\nconst getNumber = (value: string | undefined, fallback: number) =>\n  Number(value) || fallback;\n\n/**\n * 获取当前环境下生效的配置文件名\n */\nfunction getConfFiles() {\n  const script = process.env.npm_lifecycle_script as string;\n  const reg = /--mode ([\\d_a-z]+)/;\n  const result = reg.exec(script);\n  let mode = 'production';\n  if (result) {\n    mode = result[1] as string;\n  }\n  return ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`];\n}\n\n/**\n * Get the environment variables starting with the specified prefix\n * @param match prefix\n * @param confFiles ext\n */\nasync function loadEnv<T = Record<string, string>>(\n  match = 'VITE_GLOB_',\n  confFiles = getConfFiles(),\n) {\n  let envConfig = {};\n\n  for (const confFile of confFiles) {\n    try {\n      const confFilePath = join(process.cwd(), confFile);\n      if (existsSync(confFilePath)) {\n        const envPath = await fs.readFile(confFilePath, {\n          encoding: 'utf8',\n        });\n        const env = dotenv.parse(envPath);\n        envConfig = { ...envConfig, ...env };\n      }\n    } catch (error) {\n      console.error(`Error while parsing ${confFile}`, error);\n    }\n  }\n  const reg = new RegExp(`^(${match})`);\n  Object.keys(envConfig).forEach((key) => {\n    if (!reg.test(key)) {\n      Reflect.deleteProperty(envConfig, key);\n    }\n  });\n  return envConfig as T;\n}\n\nasync function loadAndConvertEnv(\n  match = 'VITE_',\n  confFiles = getConfFiles(),\n): Promise<\n  Partial<ApplicationPluginOptions> & {\n    appTitle: string;\n    base: string;\n    port: number;\n  }\n> {\n  const envConfig = await loadEnv(match, confFiles);\n\n  const {\n    VITE_APP_TITLE,\n    VITE_ARCHIVER,\n    VITE_BASE,\n    VITE_COMPRESS,\n    VITE_DEVTOOLS,\n    VITE_INJECT_APP_LOADING,\n    VITE_NITRO_MOCK,\n    VITE_PORT,\n    VITE_PWA,\n    VITE_VISUALIZER,\n  } = envConfig;\n\n  const compressTypes = (VITE_COMPRESS ?? '')\n    .split(',')\n    .filter((item) => item === 'brotli' || item === 'gzip');\n\n  return {\n    appTitle: getString(VITE_APP_TITLE, 'Vben Admin'),\n    archiver: getBoolean(VITE_ARCHIVER),\n    base: getString(VITE_BASE, '/'),\n    compress: compressTypes.length > 0,\n    compressTypes,\n    devtools: getBoolean(VITE_DEVTOOLS),\n    injectAppLoading: getBoolean(VITE_INJECT_APP_LOADING),\n    nitroMock: getBoolean(VITE_NITRO_MOCK),\n    port: getNumber(VITE_PORT, 5173),\n    pwa: getBoolean(VITE_PWA),\n    visualizer: getBoolean(VITE_VISUALIZER),\n  };\n}\n\nexport { loadAndConvertEnv, loadEnv };\n"
  },
  {
    "path": "internal/vite-config/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "internal/vite-config/tsdown.config.ts",
    "content": "import { cp, mkdir } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { defineConfig } from 'tsdown';\n\nconst rootDir = dirname(fileURLToPath(import.meta.url));\nconst loadingAssets = ['default-loading-antd.html', 'default-loading.html'];\n\nexport default defineConfig({\n  clean: true,\n  deps: {\n    neverBundle: ['@vben/node-utils'],\n    skipNodeModulesBundle: true,\n  },\n  dts: {\n    resolver: 'tsc',\n  },\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  hooks: {\n    'build:done': async (context) => {\n      const outDir = context.options.outDir;\n      if (!outDir) {\n        return;\n      }\n\n      await mkdir(outDir, { recursive: true });\n\n      for (const file of loadingAssets) {\n        await cp(\n          join(rootDir, 'src/plugins/inject-app-loading', file),\n          join(outDir, file),\n        );\n      }\n    },\n  },\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "lefthook.yml",
    "content": "# EXAMPLE USAGE:\n#\n#   Refer for explanation to following link:\n#   https://lefthook.dev/configuration/\n#\n# pre-push:\n#   jobs:\n#     - name: packages audit\n#       tags:\n#         - frontend\n#         - security\n#       run: yarn audit\n#\n#     - name: gems audit\n#       tags:\n#         - backend\n#         - security\n#       run: bundle audit\n#\n# pre-commit:\n#   parallel: true\n#   jobs:\n#     - run: yarn eslint {staged_files}\n#       glob: \"*.{js,ts,jsx,tsx}\"\n#\n#     - name: rubocop\n#       glob: \"*.rb\"\n#       exclude:\n#         - config/application.rb\n#         - config/routes.rb\n#       run: bundle exec rubocop --force-exclusion {all_files}\n#\n#     - name: govet\n#       files: git ls-files -m\n#       glob: \"*.go\"\n#       run: go vet {files}\n#\n#     - script: \"hello.js\"\n#       runner: node\n#\n#     - script: \"hello.go\"\n#       runner: go run\n\npre-commit:\n  parallel: true\n  commands:\n    code-workspace:\n      run: pnpm vsh code-workspace --auto-commit\n    lint-md:\n      run: pnpm oxfmt {staged_files}\n      glob: '*.md'\n    lint-vue:\n      run: pnpm oxfmt {staged_files} && pnpm oxlint --fix {staged_files} && pnpm eslint --cache --fix {staged_files} && pnpm stylelint --fix --allow-empty-input {staged_files}\n      glob: '*.vue'\n    lint-js:\n      run: pnpm oxfmt {staged_files} && pnpm oxlint --fix {staged_files} && pnpm eslint --cache --fix {staged_files}\n      glob: '*.{js,jsx,ts,tsx}'\n    lint-style:\n      run: pnpm oxfmt {staged_files} && pnpm stylelint --fix --allow-empty-input {staged_files}\n      glob: '*.{scss,less,styl,html,vue,css}'\n    lint-package:\n      run: pnpm oxfmt {staged_files}\n      glob: 'package.json'\n    lint-json:\n      run: pnpm oxfmt {staged_files}\n      glob: '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}'\n\npost-merge:\n  commands:\n    install:\n      run: pnpm install\n\ncommit-msg:\n  commands:\n    commitlint:\n      run: pnpm exec commitlint --edit $1\n"
  },
  {
    "path": "oxfmt.config.ts",
    "content": "import { defineConfig } from '@vben/oxfmt-config';\n\nexport default defineConfig({\n  ignorePatterns: [\n    'dist',\n    'dev-dist',\n    '.local',\n    '.claude',\n    '.agent',\n    '.agents',\n    '.codex',\n    '.output.js',\n    'node_modules',\n    '.nvmrc',\n    'coverage',\n    'CODEOWNERS',\n    '.nitro',\n    '.output',\n    '**/*.svg',\n    '**/*.sh',\n    'public',\n    '.npmrc',\n    '*-lock.yaml',\n    'skills-lock.json',\n  ],\n});\n"
  },
  {
    "path": "oxlint.config.ts",
    "content": "import { oxlintConfig } from '@vben/oxlint-config';\n\nimport { defineConfig } from 'oxlint';\n\nexport default defineConfig(oxlintConfig);\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vben-admin-monorepo\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"keywords\": [\n    \"monorepo\",\n    \"turbo\",\n    \"vben\",\n    \"vben admin\",\n    \"vben pro\",\n    \"vue\",\n    \"vue admin\",\n    \"vue vben admin\",\n    \"vue vben admin pro\",\n    \"vue3\"\n  ],\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": \"vbenjs/vue-vben-admin.git\",\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"vben\",\n    \"email\": \"ann.vben@gmail.com\",\n    \"url\": \"https://github.com/anncwb\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build\",\n    \"build:analyze\": \"turbo build:analyze\",\n    \"build:antd\": \"pnpm run build --filter=@vben/web-antd\",\n    \"build:docker\": \"./scripts/deploy/build-local-docker-image.sh\",\n    \"build:docs\": \"pnpm run build --filter=@vben/docs\",\n    \"build:ele\": \"pnpm run build --filter=@vben/web-ele\",\n    \"build:naive\": \"pnpm run build --filter=@vben/web-naive\",\n    \"build:tdesign\": \"pnpm run build --filter=@vben/web-tdesign\",\n    \"build:play\": \"pnpm run build --filter=@vben/playground\",\n    \"changeset\": \"pnpm exec changeset\",\n    \"check\": \"pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell\",\n    \"check:circular\": \"vsh check-circular\",\n    \"check:cspell\": \"cspell lint \\\"**/*.ts\\\" \\\"**/README.md\\\" \\\".changeset/*.md\\\" --no-progress\",\n    \"check:dep\": \"vsh check-dep\",\n    \"check:type\": \"turbo run typecheck\",\n    \"clean\": \"node ./scripts/clean.mjs\",\n    \"commit\": \"czg\",\n    \"dev\": \"turbo-run dev\",\n    \"dev:antd\": \"pnpm -F @vben/web-antd run dev\",\n    \"dev:antdv-next\": \"pnpm -F @vben/web-antdv-next run dev\",\n    \"dev:docs\": \"pnpm -F @vben/docs run dev\",\n    \"dev:ele\": \"pnpm -F @vben/web-ele run dev\",\n    \"dev:naive\": \"pnpm -F @vben/web-naive run dev\",\n    \"dev:tdesign\": \"pnpm -F @vben/web-tdesign run dev\",\n    \"dev:play\": \"pnpm -F @vben/playground run dev\",\n    \"format\": \"vsh lint --format\",\n    \"lint\": \"vsh lint\",\n    \"postinstall\": \"pnpm -r run stub --if-present\",\n    \"preinstall\": \"npx only-allow pnpm\",\n    \"prepare\": \"pnpm exec lefthook install\",\n    \"preview\": \"turbo-run preview\",\n    \"publint\": \"vsh publint\",\n    \"reinstall\": \"pnpm clean --del-lock && pnpm install\",\n    \"test:unit\": \"vitest run --dom\",\n    \"test:e2e\": \"turbo run test:e2e\",\n    \"update:deps\": \"npx taze -r -w\",\n    \"version\": \"pnpm exec changeset version && pnpm install --no-frozen-lockfile\",\n    \"catalog\": \"pnpx codemod pnpm/catalog\"\n  },\n  \"devDependencies\": {\n    \"@changesets/changelog-github\": \"catalog:\",\n    \"@changesets/cli\": \"catalog:\",\n    \"@playwright/test\": \"catalog:\",\n    \"@tsdown/css\": \"catalog:\",\n    \"@types/node\": \"catalog:\",\n    \"@vben/commitlint-config\": \"workspace:*\",\n    \"@vben/eslint-config\": \"workspace:*\",\n    \"@vben/oxfmt-config\": \"workspace:*\",\n    \"@vben/oxlint-config\": \"workspace:*\",\n    \"@vben/stylelint-config\": \"workspace:*\",\n    \"@vben/tailwind-config\": \"workspace:*\",\n    \"@vben/tsconfig\": \"workspace:*\",\n    \"@vben/turbo-run\": \"workspace:*\",\n    \"@vben/vite-config\": \"workspace:*\",\n    \"@vben/vsh\": \"workspace:*\",\n    \"@vitejs/plugin-vue\": \"catalog:\",\n    \"@vitejs/plugin-vue-jsx\": \"catalog:\",\n    \"@vue/test-utils\": \"catalog:\",\n    \"cross-env\": \"catalog:\",\n    \"cspell\": \"catalog:\",\n    \"happy-dom\": \"catalog:\",\n    \"is-ci\": \"catalog:\",\n    \"lefthook\": \"catalog:\",\n    \"oxfmt\": \"catalog:\",\n    \"oxlint\": \"catalog:\",\n    \"oxlint-tsgolint\": \"catalog:\",\n    \"playwright\": \"catalog:\",\n    \"rimraf\": \"catalog:\",\n    \"tailwindcss\": \"catalog:\",\n    \"tsdown\": \"catalog:\",\n    \"turbo\": \"catalog:\",\n    \"typescript\": \"catalog:\",\n    \"unplugin-vue\": \"catalog:\",\n    \"vite\": \"catalog:\",\n    \"vitest\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-tsc\": \"catalog:\"\n  },\n  \"engines\": {\n    \"node\": \"^20.19.0 || ^22.18.0 || ^24.0.0\",\n    \"pnpm\": \">=10.0.0\"\n  },\n  \"packageManager\": \"pnpm@10.32.1\"\n}\n"
  },
  {
    "path": "packages/@core/README.md",
    "content": "# @vben-core\n\n系统一些比较基础的SDK和UI组件库，该目录后续完善后，可能会迁移出去或者发布到npm，请勿将任何业务逻辑和业务包放在该目录。\n"
  },
  {
    "path": "packages/@core/base/README.md",
    "content": "# base\n\n基础共享包，请勿引入 workspace 依赖\n\n-\n"
  },
  {
    "path": "packages/@core/base/design/package.json",
    "content": "{\n  \"name\": \"@vben-core/design\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@vben-core/base/design\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm vite build\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"files\": [\n    \"dist\",\n    \"src\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"exports\": {\n    \"./bem\": {\n      \"development\": \"./src/scss-bem/bem.scss\",\n      \"production\": \"./src/scss-bem/bem.scss\",\n      \"default\": \"./dist/bem.scss\"\n    },\n    \"./theme\": {\n      \"default\": \"./src/css/global.css\"\n    },\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"production\": \"./src/index.ts\",\n      \"default\": \"./dist/design.css\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"default\": \"./dist/index.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@iconify/json\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/base/design/src/css/global.css",
    "content": "@import '@vben/tailwind-config/theme';\n"
  },
  {
    "path": "packages/@core/base/design/src/css/nprogress.css",
    "content": "@reference \"@vben/tailwind-config/theme\";\n\n/* Make clicks pass-through */\n#nprogress {\n  @apply pointer-events-none;\n}\n\n#nprogress .bar {\n  @apply bg-primary fixed top-0 left-0 z-1031 h-0.5 w-full;\n}\n\n/* Fancy blur effect */\n#nprogress .peg {\n  @apply absolute right-0 block h-full w-25;\n\n  box-shadow:\n    0 0 10px hsl(var(--primary)),\n    0 0 5px hsl(var(--primary));\n  opacity: 1;\n  transform: rotate(3deg) translate(0, -4px);\n}\n\n/* Remove these to get rid of the spinner */\n#nprogress .spinner {\n  @apply fixed top-4 right-4 z-1031 block;\n}\n\n#nprogress .spinner-icon {\n  @apply border-t-primary border-l-primary size-4 rounded-full border-2 border-solid border-transparent;\n\n  animation: nprogress-spinner 400ms linear infinite;\n}\n\n.nprogress-custom-parent {\n  @apply relative overflow-hidden;\n}\n\n.nprogress-custom-parent #nprogress .spinner,\n.nprogress-custom-parent #nprogress .bar {\n  @apply absolute;\n}\n\n@keyframes nprogress-spinner {\n  0% {\n    transform: rotate(0deg);\n  }\n\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes nprogress-spinner {\n  0% {\n    transform: rotate(0deg);\n  }\n\n  100% {\n    transform: rotate(360deg);\n  }\n}\n"
  },
  {
    "path": "packages/@core/base/design/src/css/transition.css",
    "content": ".slide-up-enter-active,\n.slide-up-leave-active {\n  transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1);\n}\n\n.slide-up-move {\n  transition: transform 0.3s;\n}\n\n.slide-up-enter-from,\n.slide-up-leave-to {\n  opacity: 0;\n  transform: translateY(-15px);\n}\n\n.slide-down-enter-active,\n.slide-down-leave-active {\n  transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1);\n}\n\n.slide-down-move {\n  transition: transform 0.3s;\n}\n\n.slide-down-enter-from,\n.slide-down-leave-to {\n  opacity: 0;\n  transform: translateY(15px);\n}\n\n.slide-left-enter-active,\n.slide-left-leave-active {\n  transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1);\n}\n\n.slide-left-move {\n  transition: transform 0.3s;\n}\n\n.slide-left-enter-from,\n.slide-left-leave-to {\n  opacity: 0;\n  transform: translate(-15px);\n}\n\n.slide-right-enter-active,\n.slide-right-leave-active {\n  transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1);\n}\n\n.slide-right-move {\n  transition: transform 0.3s;\n}\n\n.slide-right-enter-from,\n.slide-right-leave-to {\n  opacity: 0;\n  transform: translate(15px);\n}\n\n.fade-transition-enter-active,\n.fade-transition-leave-active {\n  transition: opacity 0.2s ease-in-out;\n}\n\n.fade-transition-enter-from,\n.fade-transition-leave-to {\n  opacity: 0;\n}\n\n.fade-enter-active,\n.fade-leave-active {\n  transition: opacity 0.2s ease-in-out;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n}\n\n.fade-slide-leave-active,\n.fade-slide-enter-active {\n  transition: all 0.3s;\n}\n\n.fade-slide-enter-from {\n  opacity: 0;\n  transform: translate(-30px);\n}\n\n.fade-slide-leave-to {\n  opacity: 0;\n  transform: translate(30px);\n}\n\n.fade-down-enter-active,\n.fade-down-leave-active {\n  transition:\n    opacity 0.25s,\n    transform 0.3s;\n}\n\n.fade-down-enter-from {\n  opacity: 0;\n  transform: translateY(-10%);\n}\n\n.fade-down-leave-to {\n  opacity: 0;\n  transform: translateY(10%);\n}\n\n.fade-scale-leave-active,\n.fade-scale-enter-active {\n  transition: all 0.28s;\n}\n\n.fade-scale-enter-from {\n  opacity: 0;\n  transform: scale(1.2);\n}\n\n.fade-scale-leave-to {\n  opacity: 0;\n  transform: scale(0.8);\n}\n\n.fade-up-enter-active,\n.fade-up-leave-active {\n  transition:\n    opacity 0.2s,\n    transform 0.25s;\n}\n\n.fade-up-enter-from {\n  opacity: 0;\n  transform: translateY(10%);\n}\n\n.fade-up-leave-to {\n  opacity: 0;\n  transform: translateY(-10%);\n}\n\n@keyframes fade-slide {\n  0% {\n    opacity: 0;\n    transform: translate(-30px);\n  }\n\n  50% {\n    opacity: 1;\n  }\n\n  100% {\n    opacity: 0;\n    transform: translate(30px);\n  }\n}\n\n@keyframes fade {\n  0% {\n    opacity: 0;\n  }\n\n  50% {\n    opacity: 1;\n  }\n\n  100% {\n    opacity: 0;\n  }\n}\n\n@keyframes fade-up {\n  0% {\n    opacity: 0;\n    transform: translateY(10%);\n  }\n\n  50% {\n    opacity: 1;\n  }\n\n  100% {\n    opacity: 0;\n    transform: translateY(-10%);\n  }\n}\n\n@keyframes fade-down {\n  0% {\n    opacity: 0;\n    transform: translateY(-10%);\n  }\n\n  50% {\n    opacity: 1;\n  }\n\n  100% {\n    opacity: 0;\n    transform: translateY(10%);\n  }\n}\n\n.fade-slow {\n  animation: fade 3s infinite;\n}\n\n.fade-slide-slow {\n  animation: fade-slide 3s infinite;\n}\n\n.fade-up-slow {\n  animation: fade-up 3s infinite;\n}\n\n.fade-down-slow {\n  animation: fade-down 3s infinite;\n}\n\n.collapse-transition {\n  transition:\n    0.2s height ease-in-out,\n    0.2s padding-top ease-in-out,\n    0.2s padding-bottom ease-in-out;\n}\n\n.collapse-transition-leave-active,\n.collapse-transition-enter-active {\n  transition:\n    0.2s max-height ease-in-out,\n    0.2s padding-top ease-in-out,\n    0.2s margin-top ease-in-out;\n}\n"
  },
  {
    "path": "packages/@core/base/design/src/css/ui.css",
    "content": ".side-content {\n  animation-duration: 0.3s;\n  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.side-content[data-side='top'] {\n  animation-name: slide-up;\n}\n\n.side-content[data-side='bottom'] {\n  animation-name: slide-down;\n}\n\n.side-content[data-side='left'] {\n  animation-name: slide-left;\n}\n\n.side-content[data-side='right'] {\n  animation-name: slide-right;\n}\n\n.breadcrumb-transition-enter-active {\n  transition:\n    transform 0.4s cubic-bezier(0.76, 0, 0.24, 1),\n    opacity 0.4s cubic-bezier(0.76, 0, 0.24, 1);\n}\n\n.breadcrumb-transition-leave-active {\n  display: none;\n}\n\n.breadcrumb-transition-enter-from {\n  opacity: 0;\n  transform: translateX(30px) skewX(-30deg);\n}\n\n@keyframes slide-down {\n  from {\n    opacity: 0;\n    transform: translateY(50px);\n  }\n\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n@keyframes slide-left {\n  from {\n    opacity: 0;\n    transform: translateX(-50px);\n  }\n\n  to {\n    opacity: 1;\n    transform: translateX(0);\n  }\n}\n\n@keyframes slide-right {\n  from {\n    opacity: 0;\n    transform: translateX(50px);\n  }\n\n  to {\n    opacity: 1;\n    transform: translateX(0);\n  }\n}\n\n@keyframes slide-up {\n  from {\n    opacity: 0;\n    transform: translateY(-50px);\n  }\n\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.z-popup {\n  z-index: var(--popup-z-index);\n}\n\n@keyframes shrink {\n  0% {\n    transform: scale(1);\n  }\n\n  50% {\n    transform: scale(0.9);\n  }\n\n  100% {\n    transform: scale(1);\n  }\n}\n"
  },
  {
    "path": "packages/@core/base/design/src/design-tokens/dark.css",
    "content": ".dark,\n.dark[data-theme='custom'],\n.dark[data-theme='default'] {\n  /* Default background color of <body />...etc */\n  --background: 222.34deg 10.43% 12.27%;\n\n  /* 主体区域背景色 */\n  --background-deep: 220deg 13.06% 9%;\n  --foreground: 0 0% 95%;\n\n  /* Background color for <Card /> */\n  --card: 222.34deg 10.43% 12.27%;\n\n  /* --card: 222.2 84% 4.9%; */\n  --card-foreground: 210 40% 98%;\n\n  /* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */\n\n  /* --popover: 222.82deg 8.43% 12.27%; */\n\n  /* 弹出层的背景色与主题区域背景色太过接近  */\n  --popover: 0 0% 14.2%;\n  --popover-foreground: 210 40% 98%;\n\n  /* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */\n\n  /* --muted: 220deg 6.82% 17.25%; */\n\n  /* --muted-foreground: 215 20.2% 65.1%; */\n\n  --muted: 240 3.7% 15.9%;\n  --muted-foreground: 240 5% 64.9%;\n\n  /* 主题颜色 */\n\n  /* --primary: 245 82% 67%; */\n  --primary-foreground: 0 0% 98%;\n\n  /* Used for destructive actions such as <Button variant=\"destructive\"> */\n\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 0 0% 98%;\n\n  /* Used for success actions such as <message> */\n\n  --info: 180, 1.54%, 12.75%;\n  --info-foreground: 220, 4%, 58%;\n\n  /* Used for success actions such as <message> */\n\n  --success: 144 57% 58%;\n  --success-foreground: 0 0% 98%;\n\n  /* Used for warning actions such as <message> */\n\n  --warning: 42 84% 61%;\n  --warning-foreground: 0 0% 98%;\n\n  /* 颜色次要 */\n  --secondary: 240 5% 17%;\n  --secondary-foreground: 0 0% 98%;\n\n  /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */\n  --accent: 216 5% 19%;\n  --accent-dark: 240 0% 22%;\n  --accent-darker: 240 0% 26%;\n  --accent-lighter: 216 5% 12%;\n  --accent-hover: 216 5% 24%;\n  --accent-foreground: 0 0% 98%;\n\n  /* Darker color */\n  --heavy: 216 5% 24%;\n  --heavy-foreground: var(--accent-foreground);\n\n  /* Default border color */\n  --border: 240 3.7% 22%;\n\n  /* Border color for inputs such as <Input />, <Select />, <Textarea /> */\n  --input: 0deg 0% 100% / 10%;\n  --input-placeholder: 218deg 11% 65%;\n  --input-background: 0deg 0% 100% / 5%;\n\n  /* Used for focus ring */\n  --ring: 222.2 84% 4.9%;\n\n  /* 基本圆角大小 */\n  --radius: 0.5rem;\n\n  /* ============= Custom ============= */\n\n  /* 遮罩颜色 */\n  --overlay: 0deg 0% 0% / 40%;\n  --overlay-content: 0deg 0% 0% / 40%;\n\n  /* 基本文字大小 */\n  --font-size-base: 16px;\n\n  /* =============component & UI============= */\n\n  --sidebar: 222.34deg 10.43% 12.27%;\n  --sidebar-deep: 220deg 13.06% 9%;\n  --menu: var(--sidebar);\n\n  /* header */\n  --header: 222.34deg 10.43% 12.27%;\n\n  color-scheme: dark;\n}\n\n.dark[data-theme='violet'],\n[data-theme='violet'] .dark {\n  --background: 224 71.4% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 210 20% 98%;\n  --card: 224 71.4% 4.1%;\n  --card-foreground: 210 20% 98%;\n  --popover: 224 71.4% 4.1%;\n  --popover-foreground: 210 20% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 215 27.9% 16.9%;\n  --secondary-foreground: 210 20% 98%;\n  --muted: 215 27.9% 16.9%;\n  --muted-foreground: 217.9 10.6% 64.9%;\n  --accent: 215 27.9% 16.9%;\n  --accent-foreground: 210 20% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 215 27.9% 16.9%;\n  --input: 215 27.9% 16.9%;\n  --ring: 263.4 70% 50.4%;\n  --sidebar: 224 71.4% 4.1%;\n  --sidebar-deep: 224 71.4% 4.1%;\n  --header: 224 71.4% 4.1%;\n}\n\n.dark[data-theme='pink'],\n[data-theme='pink'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 95%;\n  --card: 0 0% 9%;\n  --card-foreground: 0 0% 95%;\n  --popover: 0 0% 9%;\n  --popover-foreground: 0 0% 95%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 15%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 0 85.7% 97.3%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 346.8 77.2% 49.8%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='rose'],\n[data-theme='rose'] .dark {\n  --background: 0 0% 3.9%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 98%;\n  --card: 0 0% 3.9%;\n  --card-foreground: 0 0% 98%;\n  --popover: 0 0% 3.9%;\n  --popover-foreground: 0 0% 98%;\n  --primary-foreground: 0 85.7% 97.3%;\n  --secondary: 0 0% 14.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 14.9%;\n  --muted-foreground: 0 0% 63.9%;\n  --accent: 0 0% 14.9%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 0 0% 14.9%;\n  --input: 0 0% 14.9%;\n  --ring: 0 72.2% 50.6%;\n  --sidebar: 0 0% 3.9%;\n  --sidebar-deep: 0 0% 3.9%;\n  --header: 0 0% 3.9%;\n}\n\n.dark[data-theme='sky-blue'],\n[data-theme='sky-blue'] .dark {\n  --background: 222.2 84% 4.9%;\n  --background-deep: var(--background);\n  --foreground: 210 40% 98%;\n  --card: 222.2 84% 4.9%;\n  --card-foreground: 210 40% 98%;\n  --popover: 222.2 84% 4.9%;\n  --popover-foreground: 210 40% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 217.2 32.6% 17.5%;\n  --secondary-foreground: 210 40% 98%;\n  --muted: 217.2 32.6% 17.5%;\n  --muted-foreground: 215 20.2% 65.1%;\n  --accent: 217.2 32.6% 17.5%;\n  --accent-foreground: 210 40% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 217.2 32.6% 17.5%;\n  --input: 217.2 32.6% 17.5%;\n  --ring: 224.3 76.3% 48%;\n  --sidebar: 222.2 84% 4.9%;\n  --sidebar-deep: 222.2 84% 4.9%;\n  --header: 222.2 84% 4.9%;\n}\n\n.dark[data-theme='deep-blue'],\n[data-theme='deep-blue'] .dark {\n  --background: 222.2 84% 4.9%;\n  --background-deep: var(--background);\n  --foreground: 210 40% 98%;\n  --card: 222.2 84% 4.9%;\n  --card-foreground: 210 40% 98%;\n  --popover: 222.2 84% 4.9%;\n  --popover-foreground: 210 40% 98%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 217.2 32.6% 17.5%;\n  --secondary-foreground: 210 40% 98%;\n  --muted: 217.2 32.6% 17.5%;\n  --muted-foreground: 215 20.2% 65.1%;\n  --accent: 217.2 32.6% 17.5%;\n  --accent-foreground: 210 40% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 217.2 32.6% 17.5%;\n  --input: 217.2 32.6% 17.5%;\n  --ring: 224.3 76.3% 48%;\n  --sidebar: 222.2 84% 4.9%;\n  --sidebar-deep: 222.2 84% 4.9%;\n  --header: 222.2 84% 4.9%;\n}\n\n.dark[data-theme='green'],\n[data-theme='green'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 95%;\n  --card: 24 9.8% 6%;\n  --card-foreground: 0 0% 95%;\n  --popover: 0 0% 9%;\n  --popover-foreground: 0 0% 95%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 15%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 0 85.7% 97.3%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 142.4 71.8% 29.2%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='deep-green'],\n[data-theme='deep-green'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 95%;\n  --card: 24 9.8% 6%;\n  --card-foreground: 0 0% 95%;\n  --popover: 0 0% 9%;\n  --popover-foreground: 0 0% 95%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 15%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 0 85.7% 97.3%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 142.4 71.8% 29.2%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='orange'],\n[data-theme='orange'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 60 9.1% 97.8%;\n  --card: 20 14.3% 4.1%;\n  --card-foreground: 60 9.1% 97.8%;\n  --popover: 20 14.3% 4.1%;\n  --popover-foreground: 60 9.1% 97.8%;\n  --primary-foreground: 60 9.1% 97.8%;\n  --secondary: 12 6.5% 15.1%;\n  --secondary-foreground: 60 9.1% 97.8%;\n  --muted: 12 6.5% 15.1%;\n  --muted-foreground: 24 5.4% 63.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 60 9.1% 97.8%;\n  --destructive: 0 72.2% 50.6%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 12 6.5% 15.1%;\n  --input: 12 6.5% 15.1%;\n  --ring: 20.5 90.2% 48.2%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='yellow'],\n[data-theme='yellow'] .dark {\n  --background: 20 14.3% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 60 9.1% 97.8%;\n  --card: 20 14.3% 4.1%;\n  --card-foreground: 60 9.1% 97.8%;\n  --popover: 20 14.3% 4.1%;\n  --popover-foreground: 60 9.1% 97.8%;\n  --primary-foreground: 26 83.3% 14.1%;\n  --secondary: 12 6.5% 15.1%;\n  --secondary-foreground: 60 9.1% 97.8%;\n  --muted: 12 6.5% 15.1%;\n  --muted-foreground: 24 5.4% 63.9%;\n  --accent: 12 6.5% 15.1%;\n  --accent-foreground: 60 9.1% 97.8%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 12 6.5% 15.1%;\n  --input: 12 6.5% 15.1%;\n  --ring: 35.5 91.7% 32.9%;\n  --sidebar: 20 14.3% 4.1%;\n  --sidebar-deep: 20 14.3% 4.1%;\n  --header: 20 14.3% 4.1%;\n}\n\n.dark[data-theme='zinc'],\n[data-theme='zinc'] .dark {\n  --background: 240 10% 3.9%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 98%;\n  --card: 240 10% 3.9%;\n  --card-foreground: 0 0% 98%;\n  --popover: 240 10% 3.9%;\n  --popover-foreground: 0 0% 98%;\n  --primary-foreground: 240 5.9% 10%;\n  --secondary: 240 3.7% 15.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 240 3.7% 15.9%;\n  --muted-foreground: 240 5% 64.9%;\n  --accent: 240 3.7% 15.9%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 3.7% 15.9%;\n  --input: 240 3.7% 15.9%;\n  --ring: 240 4.9% 83.9%;\n  --sidebar: 240 10% 3.9%;\n  --sidebar-deep: 240 10% 3.9%;\n  --header: 240 10% 3.9%;\n}\n\n.dark[data-theme='neutral'],\n[data-theme='neutral'] .dark {\n  --background: 0 0% 3.9%;\n  --background-deep: var(--background);\n  --foreground: 0 0% 98%;\n  --card: 0 0% 3.9%;\n  --card-foreground: 0 0% 98%;\n  --popover: 0 0% 3.9%;\n  --popover-foreground: 0 0% 98%;\n  --primary-foreground: 0 0% 9%;\n  --secondary: 0 0% 14.9%;\n  --secondary-foreground: 0 0% 98%;\n  --muted: 0 0% 14.9%;\n  --muted-foreground: 0 0% 63.9%;\n  --accent: 0 0% 14.9%;\n  --accent-foreground: 0 0% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 0 0% 14.9%;\n  --input: 0 0% 14.9%;\n  --ring: 0 0% 83.1%;\n  --sidebar: 0 0% 3.9%;\n  --sidebar-deep: 0 0% 3.9%;\n  --header: 0 0% 3.9%;\n}\n\n.dark[data-theme='slate'],\n[data-theme='slate'] .dark {\n  --background: 222.2 84% 4.9%;\n  --background-deep: var(--background);\n  --foreground: 210 40% 98%;\n  --card: 222.2 84% 4.9%;\n  --card-foreground: 210 40% 98%;\n  --popover: 222.2 84% 4.9%;\n  --popover-foreground: 210 40% 98%;\n  --primary-foreground: 222.2 47.4% 11.2%;\n  --secondary: 217.2 32.6% 17.5%;\n  --secondary-foreground: 210 40% 98%;\n  --muted: 217.2 32.6% 17.5%;\n  --muted-foreground: 215 20.2% 65.1%;\n  --accent: 217.2 32.6% 17.5%;\n  --accent-foreground: 210 40% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 217.2 32.6% 17.5%;\n  --input: 217.2 32.6% 17.5%;\n  --ring: 212.7 26.8% 83.9;\n  --sidebar: 222.2 84% 4.9%;\n  --sidebar-deep: 222.2 84% 4.9%;\n  --header: 222.2 84% 4.9%;\n}\n\n.dark[data-theme='gray'],\n[data-theme='gray'] .dark {\n  --background: 224 71.4% 4.1%;\n  --background-deep: var(--background);\n  --foreground: 210 20% 98%;\n  --card: 224 71.4% 4.1%;\n  --card-foreground: 210 20% 98%;\n  --popover: 224 71.4% 4.1%;\n  --popover-foreground: 210 20% 98%;\n  --primary-foreground: 220.9 39.3% 11%;\n  --secondary: 215 27.9% 16.9%;\n  --secondary-foreground: 210 20% 98%;\n  --muted: 215 27.9% 16.9%;\n  --muted-foreground: 217.9 10.6% 64.9%;\n  --accent: 215 27.9% 16.9%;\n  --accent-foreground: 210 20% 98%;\n  --destructive: 359.21 68.47% 56.47%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 215 27.9% 16.9%;\n  --input: 215 27.9% 16.9%;\n  --ring: 216 12.2% 83.9%;\n  --sidebar: 224 71.4% 4.1%;\n  --sidebar-deep: 224 71.4% 4.1%;\n  --header: 224 71.4% 4.1%;\n}\n"
  },
  {
    "path": "packages/@core/base/design/src/design-tokens/default.css",
    "content": ":root {\n  /** 弹出层的基础层级 **/\n  --popup-z-index: 2000;\n  --font-family:\n    -apple-system, blinkmacsystemfont, 'Segoe UI', roboto, 'Helvetica Neue',\n    arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',\n    'Segoe UI Symbol', 'Noto Color Emoji';\n\n  /* Default background color of <body />...etc */\n  --background: 0 0% 100%;\n\n  /* 主体区域背景色 */\n  --background-deep: 216 20.11% 95.47%;\n  --foreground: 210 6% 21%;\n\n  /* Background color for <Card /> */\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n\n  /* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n\n  /* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */\n\n  /* --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%; */\n\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n\n  /* 主题颜色 */\n\n  --primary: 212 100% 45%;\n  --primary-foreground: 0 0% 98%;\n\n  /* Used for destructive actions such as <Button variant=\"destructive\"> */\n\n  --destructive: 359.33 100% 65.1%;\n  --destructive-foreground: 0 0% 98%;\n\n  /* Used for success actions such as <message> */\n\n  --info: 240, 5%, 96%;\n  --info-foreground: 220, 4%, 58%;\n\n  /* Used for success actions such as <message> */\n\n  --success: 144 57% 58%;\n  --success-foreground: 0 0% 98%;\n\n  /* Used for warning actions such as <message> */\n\n  --warning: 42 84% 61%;\n  --warning-foreground: 0 0% 98%;\n\n  /* Secondary colors for <Button /> */\n\n  --secondary: 240 5% 96%;\n  --secondary-foreground: 240 6% 10%;\n\n  /* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */\n  --accent: 240 5% 96%;\n  --accent-dark: 216 14% 93%;\n  --accent-darker: 216 11% 91%;\n  --accent-lighter: 240 0% 98%;\n  --accent-hover: 200deg 10% 90%;\n  --accent-foreground: 240 6% 10%;\n\n  /* Darker color */\n  --heavy: 192deg 9.43% 89.61%;\n  --heavy-foreground: var(--accent-foreground);\n\n  /* Default border color */\n  --border: 240 5.9% 90%;\n\n  /* Border color for inputs such as <Input />, <Select />, <Textarea /> */\n  --input: 240deg 5.88% 90%;\n  --input-placeholder: 217 10.6% 65%;\n  --input-background: 0 0% 100%;\n\n  /* Used for focus ring */\n  --ring: 222.2 84% 4.9%;\n\n  /* Border radius for card, input and buttons */\n  --radius: 0.5rem;\n\n  /* ============= custom ============= */\n\n  /* 遮罩颜色 */\n  --overlay: 0 0% 0% / 45%;\n  --overlay-content: 0 0% 95% / 45%;\n\n  /* 基本文字大小 */\n  --font-size-base: 16px;\n  --menu-font-size: calc(var(--font-size-base) * 0.875);\n\n  /* =============component & UI============= */\n\n  /* menu */\n  --sidebar: 0 0% 100%;\n  --sidebar-deep: 0 0% 100%;\n  --menu: var(--sidebar);\n\n  /* header */\n  --header: 0 0% 100%;\n\n  accent-color: var(--primary);\n  color-scheme: light;\n}\n\n[data-theme='violet'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 224 71.4% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 224 71.4% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 224 71.4% 4.1%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 220 14.3% 95.9%;\n  --secondary-foreground: 220.9 39.3% 11%;\n  --muted: 220 14.3% 95.9%;\n  --muted-foreground: 220 8.9% 46.1%;\n  --accent: 220 14.3% 95.9%;\n  --accent-foreground: 220.9 39.3% 11%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 220 13% 91%;\n  --input: 220 13% 91%;\n  --ring: 262.1 83.3% 57.8%;\n}\n\n[data-theme='pink'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 346.8 77.2% 49.8%;\n}\n\n[data-theme='rose'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 346.8 77.2% 49.8%;\n}\n\n[data-theme='sky-blue'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 222.2 84% 4.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n  --primary-foreground: 210 40% 98%;\n  --secondary: 210 40% 96.1%;\n  --secondary-foreground: 222.2 47.4% 11.2%;\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n  --accent: 210 40% 96.1%;\n  --accent-foreground: 222.2 47.4% 11.2%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 214.3 31.8% 91.4%;\n  --input: 214.3 31.8% 91.4%;\n  --ring: 221.2 83.2% 53.3%;\n}\n\n[data-theme='deep-blue'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 222.2 84% 4.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n  --primary-foreground: 210 40% 98%;\n  --secondary: 210 40% 96.1%;\n  --secondary-foreground: 222.2 47.4% 11.2%;\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n  --accent: 210 40% 96.1%;\n  --accent-foreground: 222.2 47.4% 11.2%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 214.3 31.8% 91.4%;\n  --input: 214.3 31.8% 91.4%;\n  --ring: 221.2 83.2% 53.3%;\n}\n\n[data-theme='green'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 142.1 76.2% 36.3%;\n}\n\n[data-theme='deep-green'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 355.7 100% 97.3%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 142.1 76.2% 36.3%;\n}\n\n[data-theme='orange'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 20 14.3% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 20 14.3% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 20 14.3% 4.1%;\n  --primary-foreground: 60 9.1% 97.8%;\n  --secondary: 60 4.8% 95.9%;\n  --secondary-foreground: 24 9.8% 10%;\n  --muted: 60 4.8% 95.9%;\n  --muted-foreground: 25 5.3% 44.7%;\n  --accent: 60 4.8% 95.9%;\n  --accent-foreground: 24 9.8% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 20 5.9% 90%;\n  --input: 20 5.9% 90%;\n  --ring: 24.6 95% 53.1%;\n}\n\n[data-theme='yellow'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 20 14.3% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 20 14.3% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 20 14.3% 4.1%;\n  --primary-foreground: 26 83.3% 14.1%;\n  --secondary: 60 4.8% 95.9%;\n  --secondary-foreground: 24 9.8% 10%;\n  --muted: 60 4.8% 95.9%;\n  --muted-foreground: 25 5.3% 44.7%;\n  --accent: 60 4.8% 95.9%;\n  --accent-foreground: 24 9.8% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 60 9.1% 97.8%;\n  --border: 20 5.9% 90%;\n  --input: 20 5.9% 90%;\n  --ring: 20 14.3% 4.1%;\n}\n\n[data-theme='zinc'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 240 10% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 240 10% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 240 10% 3.9%;\n  --primary-foreground: 0 0% 98%;\n  --secondary: 240 4.8% 95.9%;\n  --secondary-foreground: 240 5.9% 10%;\n  --muted: 240 4.8% 95.9%;\n  --muted-foreground: 240 3.8% 46.1%;\n  --accent: 240 4.8% 95.9%;\n  --accent-foreground: 240 5.9% 10%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 240 5.9% 90%;\n  --input: 240 5.9% 90%;\n  --ring: 240 5.9% 10%;\n}\n\n[data-theme='neutral'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 0 0% 3.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 0 0% 3.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 0 0% 3.9%;\n  --primary-foreground: 0 0% 98%;\n  --secondary: 0 0% 96.1%;\n  --secondary-foreground: 0 0% 9%;\n  --muted: 0 0% 96.1%;\n  --muted-foreground: 0 0% 45.1%;\n  --accent: 0 0% 96.1%;\n  --accent-foreground: 0 0% 9%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 0 0% 98%;\n  --border: 0 0% 89.8%;\n  --input: 0 0% 89.8%;\n  --ring: 0 0% 3.9%;\n}\n\n[data-theme='slate'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 222.2 84% 4.9%;\n  --card: 0 0% 100%;\n  --card-foreground: 222.2 84% 4.9%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 222.2 84% 4.9%;\n  --primary-foreground: 210 40% 98%;\n  --secondary: 210 40% 96.1%;\n  --secondary-foreground: 222.2 47.4% 11.2%;\n  --muted: 210 40% 96.1%;\n  --muted-foreground: 215.4 16.3% 46.9%;\n  --accent: 210 40% 96.1%;\n  --accent-foreground: 222.2 47.4% 11.2%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 40% 98%;\n  --border: 214.3 31.8% 91.4%;\n  --input: 214.3 31.8% 91.4%;\n  --ring: 222.2 84% 4.9%;\n}\n\n[data-theme='gray'] {\n  /* --background: 0 0% 100%; */\n  --foreground: 224 71.4% 4.1%;\n  --card: 0 0% 100%;\n  --card-foreground: 224 71.4% 4.1%;\n  --popover: 0 0% 100%;\n  --popover-foreground: 224 71.4% 4.1%;\n  --primary-foreground: 210 20% 98%;\n  --secondary: 220 14.3% 95.9%;\n  --secondary-foreground: 220.9 39.3% 11%;\n  --muted: 220 14.3% 95.9%;\n  --muted-foreground: 220 8.9% 46.1%;\n  --accent: 220 14.3% 95.9%;\n  --accent-foreground: 220.9 39.3% 11%;\n  --destructive: 0 84.2% 60.2%;\n  --destructive-foreground: 210 20% 98%;\n  --border: 220 13% 91%;\n  --input: 220 13% 91%;\n  --ring: 224 71.4% 4.1%;\n}\n"
  },
  {
    "path": "packages/@core/base/design/src/design-tokens/index.ts",
    "content": "import './default.css';\nimport './dark.css';\n"
  },
  {
    "path": "packages/@core/base/design/src/index.ts",
    "content": "import './design-tokens';\n\nimport './css/global.css';\nimport './css/transition.css';\nimport './css/nprogress.css';\nimport './css/ui.css';\n"
  },
  {
    "path": "packages/@core/base/design/src/scss-bem/bem.scss",
    "content": "@forward './constants';\n\n@mixin b($block) {\n  $B: $namespace + '-' + $block !global;\n\n  .#{$B} {\n    @content;\n  }\n}\n\n@mixin e($name) {\n  @at-root {\n    &#{$element-separator}#{$name} {\n      @content;\n    }\n  }\n}\n\n@mixin m($name) {\n  @at-root {\n    &#{$modifier-separator}#{$name} {\n      @content;\n    }\n  }\n}\n\n// block__element.is-active {}\n@mixin is($state, $prefix: $state-prefix) {\n  @at-root {\n    &.#{$prefix}-#{$state} {\n      @content;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/@core/base/design/src/scss-bem/constants.scss",
    "content": "$namespace: 'vben' !default;\n$common-separator: '-' !default;\n$element-separator: '__' !default;\n$modifier-separator: '--' !default;\n$state-prefix: 'is' !default;\n"
  },
  {
    "path": "packages/@core/base/design/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/base/design/vite.config.ts",
    "content": "// eslint-disable-next-line no-restricted-imports\nimport { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    vite: {\n      publicDir: 'src/scss-bem',\n    },\n  };\n});\n"
  },
  {
    "path": "packages/@core/base/icons/package.json",
    "content": "{\n  \"name\": \"@vben-core/icons\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@vben-core/base/icons\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm exec tsdown\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"production\": \"./src/index.ts\",\n      \"default\": \"./dist/index.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"default\": \"./dist/index.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@iconify/vue\": \"catalog:\",\n    \"lucide-vue-next\": \"catalog:\",\n    \"vue\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/base/icons/src/create-icon.ts",
    "content": "import { defineComponent, h } from 'vue';\n\nimport { Icon } from '@iconify/vue';\n\nfunction createIconifyIcon(icon: string) {\n  return defineComponent({\n    name: `Icon-${icon}`,\n    setup(props, { attrs }) {\n      return () => h(Icon, { icon, ...props, ...attrs });\n    },\n  });\n}\n\nexport { createIconifyIcon };\n"
  },
  {
    "path": "packages/@core/base/icons/src/index.ts",
    "content": "export * from './create-icon';\n\nexport * from './lucide';\n\nexport type { IconifyIcon as IconifyIconStructure } from '@iconify/vue';\nexport {\n  addCollection,\n  addIcon,\n  Icon as IconifyIcon,\n  listIcons,\n} from '@iconify/vue';\n"
  },
  {
    "path": "packages/@core/base/icons/src/lucide.ts",
    "content": "export {\n  ArrowDown,\n  ArrowLeft,\n  ArrowLeftToLine,\n  ArrowRightLeft,\n  ArrowRightToLine,\n  ArrowUp,\n  ArrowUpToLine,\n  Bell,\n  BookOpenText,\n  Check,\n  ChevronDown,\n  ChevronLeft,\n  ChevronRight,\n  ChevronsLeft,\n  ChevronsRight,\n  Circle,\n  CircleAlert,\n  CircleCheckBig,\n  CircleHelp,\n  CircleX,\n  Copy,\n  CornerDownLeft,\n  Ellipsis,\n  Expand,\n  ExternalLink,\n  Eye,\n  EyeOff,\n  FoldHorizontal,\n  Fullscreen,\n  Github,\n  Grid,\n  Grip,\n  GripVertical,\n  Menu as IconDefault,\n  Inbox,\n  Info,\n  InspectionPanel,\n  Languages,\n  LayoutGrid,\n  LoaderCircle,\n  LockKeyhole,\n  LogOut,\n  MailCheck,\n  Maximize,\n  ArrowRightFromLine as MdiMenuClose,\n  ArrowLeftFromLine as MdiMenuOpen,\n  Menu,\n  Minimize,\n  Minimize2,\n  MoonStar,\n  Palette,\n  PanelLeft,\n  PanelRight,\n  Pin,\n  PinOff,\n  Plus,\n  RotateCw,\n  Search,\n  SearchX,\n  Settings,\n  Shrink,\n  Square,\n  SquareCheckBig,\n  SquareMinus,\n  Sun,\n  SunMoon,\n  SwatchBook,\n  UserRoundPen,\n  X,\n} from 'lucide-vue-next';\n"
  },
  {
    "path": "packages/@core/base/icons/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/base/icons/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: true,\n  dts: true,\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "packages/@core/base/shared/package.json",
    "content": "{\n  \"name\": \"@vben-core/shared\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@vben-core/base/shared\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm exec tsdown\",\n    \"stub\": \"pnpm run build\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"sideEffects\": false,\n  \"exports\": {\n    \"./constants\": {\n      \"types\": \"./src/constants/index.ts\",\n      \"development\": \"./src/constants/index.ts\",\n      \"production\": \"./src/constants/index.ts\",\n      \"default\": \"./dist/constants/index.mjs\"\n    },\n    \"./utils\": {\n      \"types\": \"./src/utils/index.ts\",\n      \"development\": \"./src/utils/index.ts\",\n      \"production\": \"./src/utils/index.ts\",\n      \"default\": \"./dist/utils/index.mjs\"\n    },\n    \"./color\": {\n      \"types\": \"./src/color/index.ts\",\n      \"development\": \"./src/color/index.ts\",\n      \"production\": \"./src/color/index.ts\",\n      \"default\": \"./dist/color/index.mjs\"\n    },\n    \"./cache\": {\n      \"types\": \"./src/cache/index.ts\",\n      \"development\": \"./src/cache/index.ts\",\n      \"production\": \"./src/cache/index.ts\",\n      \"default\": \"./dist/cache/index.mjs\"\n    },\n    \"./store\": {\n      \"types\": \"./src/store.ts\",\n      \"development\": \"./src/store.ts\",\n      \"production\": \"./src/store.ts\",\n      \"default\": \"./dist/store.mjs\"\n    },\n    \"./global-state\": {\n      \"types\": \"./src/global-state.ts\",\n      \"development\": \"./src/global-state.ts\",\n      \"production\": \"./src/global-state.ts\",\n      \"default\": \"./dist/global-state.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \"./constants\": {\n        \"types\": \"./dist/constants/index.d.ts\",\n        \"default\": \"./dist/constants/index.mjs\"\n      },\n      \"./utils\": {\n        \"types\": \"./dist/utils/index.d.ts\",\n        \"default\": \"./dist/utils/index.mjs\"\n      },\n      \"./color\": {\n        \"types\": \"./dist/color/index.d.ts\",\n        \"default\": \"./dist/color/index.mjs\"\n      },\n      \"./cache\": {\n        \"types\": \"./dist/cache/index.d.ts\",\n        \"default\": \"./dist/cache/index.mjs\"\n      },\n      \"./store\": {\n        \"types\": \"./dist/store.d.ts\",\n        \"default\": \"./dist/store.mjs\"\n      },\n      \"./global-state\": {\n        \"types\": \"./dist/global-state.d.ts\",\n        \"default\": \"./dist/global-state.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@ctrl/tinycolor\": \"catalog:\",\n    \"@tanstack/vue-store\": \"catalog:\",\n    \"@vue/shared\": \"catalog:\",\n    \"clsx\": \"catalog:\",\n    \"dayjs\": \"catalog:\",\n    \"defu\": \"catalog:\",\n    \"es-toolkit\": \"catalog:\",\n    \"lodash.clonedeep\": \"catalog:\",\n    \"nprogress\": \"catalog:\",\n    \"tailwind-merge\": \"catalog:\",\n    \"theme-colors\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/lodash.clonedeep\": \"catalog:\",\n    \"@types/nprogress\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/base/shared/src/cache/__tests__/storage-manager.test.ts",
    "content": "import { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { StorageManager } from '../storage-manager';\n\ndescribe('storageManager', () => {\n  let storageManager: StorageManager;\n\n  beforeEach(() => {\n    vi.useFakeTimers();\n    localStorage.clear();\n    storageManager = new StorageManager({\n      prefix: 'test_',\n    });\n  });\n\n  it('should set and get an item', () => {\n    storageManager.setItem('user', { age: 30, name: 'John Doe' });\n    const user = storageManager.getItem('user');\n    expect(user).toEqual({ age: 30, name: 'John Doe' });\n  });\n\n  it('should return default value if item does not exist', () => {\n    const user = storageManager.getItem('nonexistent', {\n      age: 0,\n      name: 'Default User',\n    });\n    expect(user).toEqual({ age: 0, name: 'Default User' });\n  });\n\n  it('should remove an item', () => {\n    storageManager.setItem('user', { age: 30, name: 'John Doe' });\n    storageManager.removeItem('user');\n    const user = storageManager.getItem('user');\n    expect(user).toBeNull();\n  });\n\n  it('should clear all items with the prefix', () => {\n    storageManager.setItem('user1', { age: 30, name: 'John Doe' });\n    storageManager.setItem('user2', { age: 25, name: 'Jane Doe' });\n    storageManager.clear();\n    expect(storageManager.getItem('user1')).toBeNull();\n    expect(storageManager.getItem('user2')).toBeNull();\n  });\n\n  it('should clear expired items', () => {\n    storageManager.setItem('user', { age: 30, name: 'John Doe' }, 1000); // 1秒过期\n    vi.advanceTimersByTime(1001); // 快进时间\n    storageManager.clearExpiredItems();\n    const user = storageManager.getItem('user');\n    expect(user).toBeNull();\n  });\n\n  it('should not clear non-expired items', () => {\n    storageManager.setItem('user', { age: 30, name: 'John Doe' }, 10_000); // 10秒过期\n    vi.advanceTimersByTime(5000); // 快进时间\n    storageManager.clearExpiredItems();\n    const user = storageManager.getItem('user');\n    expect(user).toEqual({ age: 30, name: 'John Doe' });\n  });\n\n  it('should handle JSON parse errors gracefully', () => {\n    localStorage.setItem('test_user', '{ invalid JSON }');\n    const user = storageManager.getItem('user', {\n      age: 0,\n      name: 'Default User',\n    });\n    expect(user).toEqual({ age: 0, name: 'Default User' });\n  });\n  it('should return null for non-existent items without default value', () => {\n    const user = storageManager.getItem('nonexistent');\n    expect(user).toBeNull();\n  });\n\n  it('should overwrite existing items', () => {\n    storageManager.setItem('user', { age: 30, name: 'John Doe' });\n    storageManager.setItem('user', { age: 25, name: 'Jane Doe' });\n    const user = storageManager.getItem('user');\n    expect(user).toEqual({ age: 25, name: 'Jane Doe' });\n  });\n\n  it('should handle items without expiry correctly', () => {\n    storageManager.setItem('user', { age: 30, name: 'John Doe' });\n    vi.advanceTimersByTime(5000);\n    const user = storageManager.getItem('user');\n    expect(user).toEqual({ age: 30, name: 'John Doe' });\n  });\n\n  it('should remove expired items when accessed', () => {\n    storageManager.setItem('user', { age: 30, name: 'John Doe' }, 1000); // 1秒过期\n    vi.advanceTimersByTime(1001); // 快进时间\n    const user = storageManager.getItem('user');\n    expect(user).toBeNull();\n  });\n\n  it('should not remove non-expired items when accessed', () => {\n    storageManager.setItem('user', { age: 30, name: 'John Doe' }, 10_000); // 10秒过期\n    vi.advanceTimersByTime(5000); // 快进时间\n    const user = storageManager.getItem('user');\n    expect(user).toEqual({ age: 30, name: 'John Doe' });\n  });\n\n  it('should handle multiple items with different expiry times', () => {\n    storageManager.setItem('user1', { age: 30, name: 'John Doe' }, 1000); // 1秒过期\n    storageManager.setItem('user2', { age: 25, name: 'Jane Doe' }, 2000); // 2秒过期\n    vi.advanceTimersByTime(1500); // 快进时间\n    storageManager.clearExpiredItems();\n    const user1 = storageManager.getItem('user1');\n    const user2 = storageManager.getItem('user2');\n    expect(user1).toBeNull();\n    expect(user2).toEqual({ age: 25, name: 'Jane Doe' });\n  });\n\n  it('should handle items with no expiry', () => {\n    storageManager.setItem('user', { age: 30, name: 'John Doe' });\n    vi.advanceTimersByTime(10_000); // 快进时间\n    storageManager.clearExpiredItems();\n    const user = storageManager.getItem('user');\n    expect(user).toEqual({ age: 30, name: 'John Doe' });\n  });\n\n  it('should clear all items correctly', () => {\n    storageManager.setItem('user1', { age: 30, name: 'John Doe' });\n    storageManager.setItem('user2', { age: 25, name: 'Jane Doe' });\n    storageManager.clear();\n    const user1 = storageManager.getItem('user1');\n    const user2 = storageManager.getItem('user2');\n    expect(user1).toBeNull();\n    expect(user2).toBeNull();\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/cache/index.ts",
    "content": "export * from './storage-manager';\n"
  },
  {
    "path": "packages/@core/base/shared/src/cache/storage-manager.ts",
    "content": "type StorageType = 'localStorage' | 'sessionStorage';\n\ninterface StorageManagerOptions {\n  prefix?: string;\n  storageType?: StorageType;\n}\n\ninterface StorageItem<T> {\n  expiry?: number;\n  value: T;\n}\n\nclass StorageManager {\n  private prefix: string;\n  private storage: Storage;\n\n  constructor({\n    prefix = '',\n    storageType = 'localStorage',\n  }: StorageManagerOptions = {}) {\n    this.prefix = prefix;\n    this.storage =\n      storageType === 'localStorage'\n        ? window.localStorage\n        : window.sessionStorage;\n  }\n\n  /**\n   * 清除所有带前缀的存储项\n   */\n  clear(): void {\n    const keysToRemove: string[] = [];\n    for (let i = 0; i < this.storage.length; i++) {\n      const key = this.storage.key(i);\n      if (key && key.startsWith(this.prefix)) {\n        keysToRemove.push(key);\n      }\n    }\n    keysToRemove.forEach((key) => this.storage.removeItem(key));\n  }\n\n  /**\n   * 清除所有过期的存储项\n   */\n  clearExpiredItems(): void {\n    for (let i = 0; i < this.storage.length; i++) {\n      const key = this.storage.key(i);\n      if (key && key.startsWith(this.prefix)) {\n        const shortKey = key.replace(this.prefix, '');\n        this.getItem(shortKey); // 调用 getItem 方法检查并移除过期项\n      }\n    }\n  }\n\n  /**\n   * 获取存储项\n   * @param key 键\n   * @param defaultValue 当项不存在或已过期时返回的默认值\n   * @returns 值，如果项已过期或解析错误则返回默认值\n   */\n  getItem<T>(key: string, defaultValue: null | T = null): null | T {\n    const fullKey = this.getFullKey(key);\n    const itemStr = this.storage.getItem(fullKey);\n    if (!itemStr) {\n      return defaultValue;\n    }\n\n    try {\n      const item: StorageItem<T> = JSON.parse(itemStr);\n      if (item.expiry && Date.now() > item.expiry) {\n        this.storage.removeItem(fullKey);\n        return defaultValue;\n      }\n      return item.value;\n    } catch (error) {\n      console.error(`Error parsing item with key \"${fullKey}\":`, error);\n      this.storage.removeItem(fullKey); // 如果解析失败，删除该项\n      return defaultValue;\n    }\n  }\n\n  /**\n   * 移除存储项\n   * @param key 键\n   */\n  removeItem(key: string): void {\n    const fullKey = this.getFullKey(key);\n    this.storage.removeItem(fullKey);\n  }\n\n  /**\n   * 设置存储项\n   * @param key 键\n   * @param value 值\n   * @param ttl 存活时间（毫秒）\n   */\n  setItem<T>(key: string, value: T, ttl?: number): void {\n    const fullKey = this.getFullKey(key);\n    const expiry = ttl ? Date.now() + ttl : undefined;\n    const item: StorageItem<T> = { expiry, value };\n    try {\n      this.storage.setItem(fullKey, JSON.stringify(item));\n    } catch (error) {\n      console.error(`Error setting item with key \"${fullKey}\":`, error);\n    }\n  }\n\n  /**\n   * 获取完整的存储键\n   * @param key 原始键\n   * @returns 带前缀的完整键\n   */\n  private getFullKey(key: string): string {\n    return `${this.prefix}-${key}`;\n  }\n}\n\nexport { StorageManager };\n"
  },
  {
    "path": "packages/@core/base/shared/src/cache/types.ts",
    "content": "type StorageType = 'localStorage' | 'sessionStorage';\n\ninterface StorageValue<T> {\n  data: T;\n  expiry: null | number;\n}\n\ninterface IStorageCache {\n  clear(): void;\n  getItem<T>(key: string): null | T;\n  key(index: number): null | string;\n  length(): number;\n  removeItem(key: string): void;\n  setItem<T>(key: string, value: T, expiryInMinutes?: number): void;\n}\n\nexport type { IStorageCache, StorageType, StorageValue };\n"
  },
  {
    "path": "packages/@core/base/shared/src/color/__tests__/convert.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport {\n  convertToHsl,\n  convertToHslCssVar,\n  convertToRgb,\n  isValidColor,\n} from '../convert';\n\ndescribe('color conversion functions', () => {\n  it('should correctly convert color to HSL format', () => {\n    const color = '#ff0000';\n    const expectedHsl = 'hsl(0 100% 50%)';\n    expect(convertToHsl(color)).toEqual(expectedHsl);\n  });\n\n  it('should correctly convert color with alpha to HSL format', () => {\n    const color = 'rgba(255, 0, 0, 0.5)';\n    const expectedHsl = 'hsl(0 100% 50%) 0.5';\n    expect(convertToHsl(color)).toEqual(expectedHsl);\n  });\n\n  it('should correctly convert color to HSL CSS variable format', () => {\n    const color = '#ff0000';\n    const expectedHsl = '0 100% 50%';\n    expect(convertToHslCssVar(color)).toEqual(expectedHsl);\n  });\n\n  it('should correctly convert color with alpha to HSL CSS variable format', () => {\n    const color = 'rgba(255, 0, 0, 0.5)';\n    const expectedHsl = '0 100% 50% / 0.5';\n    expect(convertToHslCssVar(color)).toEqual(expectedHsl);\n  });\n\n  it('should correctly convert color to RGB CSS variable format', () => {\n    const color = 'hsl(284, 100%, 50%)';\n    const expectedRgb = 'rgb(187, 0, 255)';\n    expect(convertToRgb(color)).toEqual(expectedRgb);\n  });\n\n  it('should correctly convert color with alpha to RGBA CSS variable format', () => {\n    const color = 'hsla(284, 100%, 50%, 0.92)';\n    const expectedRgba = 'rgba(187, 0, 255, 0.92)';\n    expect(convertToRgb(color)).toEqual(expectedRgba);\n  });\n});\n\ndescribe('isValidColor', () => {\n  it('isValidColor function', () => {\n    // 测试有效颜色\n    expect(isValidColor('blue')).toBe(true);\n    expect(isValidColor('#000000')).toBe(true);\n\n    // 测试无效颜色\n    expect(isValidColor('invalid color')).toBe(false);\n    expect(isValidColor()).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/color/color.ts",
    "content": "import { TinyColor } from '@ctrl/tinycolor';\n\nexport function isDarkColor(color: string) {\n  return new TinyColor(color).isDark();\n}\n\nexport function isLightColor(color: string) {\n  return new TinyColor(color).isLight();\n}\n"
  },
  {
    "path": "packages/@core/base/shared/src/color/convert.ts",
    "content": "import { TinyColor } from '@ctrl/tinycolor';\n\n/**\n * 将颜色转换为HSL格式。\n *\n * HSL是一种颜色模型，包括色相(Hue)、饱和度(Saturation)和亮度(Lightness)三个部分。\n *\n * @param {string} color 输入的颜色。\n * @returns {string} HSL格式的颜色字符串。\n */\nfunction convertToHsl(color: string): string {\n  const { a, h, l, s } = new TinyColor(color).toHsl();\n  const hsl = `hsl(${Math.round(h)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%)`;\n  return a < 1 ? `${hsl} ${a}` : hsl;\n}\n\n/**\n * 将颜色转换为HSL CSS变量。\n *\n * 这个函数与convertToHsl函数类似，但是返回的字符串格式稍有不同，\n * 以便可以作为CSS变量使用。\n *\n * @param {string} color 输入的颜色。\n * @returns {string} 可以作为CSS变量使用的HSL格式的颜色字符串。\n */\nfunction convertToHslCssVar(color: string): string {\n  const { a, h, l, s } = new TinyColor(color).toHsl();\n  const hsl = `${Math.round(h)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;\n  return a < 1 ? `${hsl} / ${a}` : hsl;\n}\n\n/**\n * 将颜色转换为RGB颜色字符串\n * TinyColor无法处理hsl内包含'deg'、'grad'、'rad'或'turn'的字符串\n * 比如 hsl(231deg 98% 65%)将被解析为rgb(0, 0, 0)\n * 这里在转换之前先将这些单位去掉\n * @param str 表示HLS颜色值的字符串\n * @returns 如果颜色值有效，则返回对应的RGB颜色字符串；如果无效，则返回rgb(0, 0, 0)\n */\nfunction convertToRgb(str: string): string {\n  return new TinyColor(str.replaceAll(/deg|grad|rad|turn/g, '')).toRgbString();\n}\n\n/**\n * 检查颜色是否有效\n * @param {string} color - 待检查的颜色\n * 如果颜色有效返回true，否则返回false\n */\nfunction isValidColor(color?: string) {\n  if (!color) {\n    return false;\n  }\n  return new TinyColor(color).isValid;\n}\n\nexport {\n  convertToHsl,\n  convertToHslCssVar,\n  convertToRgb,\n  isValidColor,\n  TinyColor,\n};\n"
  },
  {
    "path": "packages/@core/base/shared/src/color/generator.ts",
    "content": "import { getColors } from 'theme-colors';\n\nimport { convertToHslCssVar, TinyColor } from './convert';\n\ninterface ColorItem {\n  alias?: string;\n  color: string;\n  name: string;\n}\n\nfunction generatorColorVariables(colorItems: ColorItem[]) {\n  const colorVariables: Record<string, string> = {};\n\n  colorItems.forEach(({ alias, color, name }) => {\n    if (color) {\n      const colorsMap = getColors(new TinyColor(color).toHexString());\n\n      let mainColor = colorsMap['500'];\n\n      const colorKeys = Object.keys(colorsMap);\n\n      colorKeys.forEach((key) => {\n        const colorValue = colorsMap[key];\n\n        if (colorValue) {\n          const hslColor = convertToHslCssVar(colorValue);\n          colorVariables[`--${name}-${key}`] = hslColor;\n          if (alias) {\n            colorVariables[`--${alias}-${key}`] = hslColor;\n          }\n\n          if (key === '500') {\n            mainColor = hslColor;\n          }\n        }\n      });\n      if (alias && mainColor) {\n        colorVariables[`--${alias}`] = mainColor;\n      }\n    }\n  });\n  return colorVariables;\n}\n\nexport { generatorColorVariables };\n"
  },
  {
    "path": "packages/@core/base/shared/src/color/index.ts",
    "content": "export * from './color';\nexport * from './convert';\nexport * from './generator';\n"
  },
  {
    "path": "packages/@core/base/shared/src/constants/globals.ts",
    "content": "/** layout content 组件的高度 */\nexport const CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT = `--vben-content-height`;\n/** layout content 组件的宽度 */\nexport const CSS_VARIABLE_LAYOUT_CONTENT_WIDTH = `--vben-content-width`;\n/** layout header 组件的高度 */\nexport const CSS_VARIABLE_LAYOUT_HEADER_HEIGHT = `--vben-header-height`;\n/** layout footer 组件的高度 */\nexport const CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT = `--vben-footer-height`;\n\n/** 内容区域的组件ID */\nexport const ELEMENT_ID_MAIN_CONTENT = `__vben_main_content`;\n\n/**\n * @zh_CN 默认命名空间\n */\nexport const DEFAULT_NAMESPACE = 'vben';\n"
  },
  {
    "path": "packages/@core/base/shared/src/constants/index.ts",
    "content": "export * from './globals';\nexport * from './vben';\n"
  },
  {
    "path": "packages/@core/base/shared/src/constants/vben.ts",
    "content": "/**\n * @zh_CN GITHUB 仓库地址\n */\nexport const VBEN_GITHUB_URL = 'https://github.com/vbenjs/vue-vben-admin';\n\n/**\n * @zh_CN 文档地址\n */\nexport const VBEN_DOC_URL = 'https://doc.vben.pro';\n\n/**\n * @zh_CN Vben Logo\n */\nexport const VBEN_LOGO_URL =\n  'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp';\n\n/**\n * @zh_CN Vben Admin 首页地址\n */\nexport const VBEN_PREVIEW_URL = 'https://www.vben.pro';\n\nexport const VBEN_ANTDV_NEXT_PREVIEW_URL = 'https://antdv-next.vben.pro';\n\nexport const VBEN_ELE_PREVIEW_URL = 'https://ele.vben.pro';\n\nexport const VBEN_NAIVE_PREVIEW_URL = 'https://naive.vben.pro';\n\nexport const VBEN_ANT_PREVIEW_URL = 'https://ant.vben.pro';\n\nexport const VBEN_TD_PREVIEW_URL = 'https://tdesign.vben.pro';\n"
  },
  {
    "path": "packages/@core/base/shared/src/global-state.ts",
    "content": "/**\n * 全局复用的变量、组件、配置，各个模块之间共享\n * 通过单例模式实现,单例必须注意不受请求影响，例如用户信息这些需要根据请求获取的。后续如果有ssr需求，也不会影响\n */\n\ninterface ComponentsState {\n  [key: string]: any;\n}\n\ninterface MessageState {\n  copyPreferencesSuccess?: (title: string, content?: string) => void;\n}\n\nexport interface IGlobalSharedState {\n  components: ComponentsState;\n  message: MessageState;\n}\n\nclass GlobalShareState {\n  #components: ComponentsState = {};\n  #message: MessageState = {};\n\n  /**\n   * 定义框架内部各个场景的消息提示\n   */\n  public defineMessage({ copyPreferencesSuccess }: MessageState) {\n    this.#message = {\n      copyPreferencesSuccess,\n    };\n  }\n\n  public getComponents(): ComponentsState {\n    return this.#components;\n  }\n\n  public getMessage(): MessageState {\n    return this.#message;\n  }\n\n  public setComponents(value: ComponentsState) {\n    this.#components = value;\n  }\n}\n\nexport const globalShareState = new GlobalShareState();\n"
  },
  {
    "path": "packages/@core/base/shared/src/store.ts",
    "content": "export * from '@tanstack/vue-store';\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/date.test.ts",
    "content": "import dayjs from 'dayjs';\nimport timezone from 'dayjs/plugin/timezone.js';\nimport utc from 'dayjs/plugin/utc.js';\nimport { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport {\n  formatDate,\n  formatDateTime,\n  getCurrentTimezone,\n  getSystemTimezone,\n  isDate,\n  isDayjsObject,\n  setCurrentTimezone,\n} from '../date';\n\ndayjs.extend(utc);\ndayjs.extend(timezone);\n\ndescribe('dateUtils', () => {\n  const sampleISO = '2024-10-30T12:34:56Z';\n  const sampleTimestamp = Date.parse(sampleISO);\n\n  beforeEach(() => {\n    // 重置时区\n    dayjs.tz.setDefault();\n    setCurrentTimezone(); // 重置为系统默认\n  });\n\n  afterEach(() => {\n    vi.restoreAllMocks();\n  });\n\n  // ===============================\n  // formatDate\n  // ===============================\n  describe('formatDate', () => {\n    it('should format a valid ISO date string', () => {\n      const formatted = formatDate(sampleISO, 'YYYY/MM/DD');\n      expect(formatted).toMatch(/2024\\/10\\/30/);\n    });\n\n    it('should format a timestamp correctly', () => {\n      const formatted = formatDate(sampleTimestamp);\n      expect(formatted).toMatch(/2024-10-30/);\n    });\n\n    it('should format a Date object', () => {\n      const formatted = formatDate(new Date(sampleISO));\n      expect(formatted).toMatch(/2024-10-30/);\n    });\n\n    it('should format a dayjs object', () => {\n      const formatted = formatDate(dayjs(sampleISO));\n      expect(formatted).toMatch(/2024-10-30/);\n    });\n\n    it('should return original input if date is invalid', () => {\n      const invalid = 'not-a-date';\n      const spy = vi.spyOn(console, 'error').mockImplementation(() => {});\n      const formatted = formatDate(invalid);\n      expect(formatted).toBe(invalid);\n      expect(spy).toHaveBeenCalledOnce();\n    });\n\n    it('should apply given format', () => {\n      const formatted = formatDate(sampleISO, 'YYYY-MM-DD HH:mm');\n      expect(formatted).toMatch(/\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}/);\n    });\n  });\n\n  // ===============================\n  // formatDateTime\n  // ===============================\n  describe('formatDateTime', () => {\n    it('should format date into full datetime', () => {\n      const result = formatDateTime(sampleISO);\n      expect(result).toMatch(/2024-10-30 \\d{2}:\\d{2}:\\d{2}/);\n    });\n  });\n\n  // ===============================\n  // isDate\n  // ===============================\n  describe('isDate', () => {\n    it('should return true for Date instances', () => {\n      expect(isDate(new Date())).toBe(true);\n    });\n\n    it('should return false for non-Date values', () => {\n      expect(isDate('2024-10-30')).toBe(false);\n      expect(isDate(null)).toBe(false);\n      expect(isDate(undefined)).toBe(false);\n    });\n  });\n\n  // ===============================\n  // isDayjsObject\n  // ===============================\n  describe('isDayjsObject', () => {\n    it('should return true for dayjs objects', () => {\n      expect(isDayjsObject(dayjs())).toBe(true);\n    });\n\n    it('should return false for other values', () => {\n      expect(isDayjsObject(new Date())).toBe(false);\n      expect(isDayjsObject('string')).toBe(false);\n    });\n  });\n\n  // ===============================\n  // getSystemTimezone\n  // ===============================\n  describe('getSystemTimezone', () => {\n    it('should return a valid IANA timezone string', () => {\n      const tz = getSystemTimezone();\n      expect(typeof tz).toBe('string');\n      expect(tz).toMatch(/^[A-Z]+\\/[A-Z_]+/i);\n    });\n  });\n\n  // ===============================\n  // setCurrentTimezone / getCurrentTimezone\n  // ===============================\n  describe('setCurrentTimezone & getCurrentTimezone', () => {\n    it('should set and retrieve the current timezone', () => {\n      setCurrentTimezone('Asia/Shanghai');\n      expect(getCurrentTimezone()).toBe('Asia/Shanghai');\n    });\n\n    it('should reset to system timezone when called with no args', () => {\n      const guessed = getSystemTimezone();\n      setCurrentTimezone();\n      expect(getCurrentTimezone()).toBe(guessed);\n    });\n\n    it('should update dayjs default timezone', () => {\n      setCurrentTimezone('America/New_York');\n      const d = dayjs('2024-01-01T00:00:00Z');\n      // 校验时区转换生效（小时变化）\n      expect(d.tz().format('HH')).not.toBe('00');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/diff.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { diff } from '../diff';\n\ndescribe('diff function', () => {\n  it('should return an empty object when comparing identical objects', () => {\n    const obj1 = { a: 1, b: { c: 2 } };\n    const obj2 = { a: 1, b: { c: 2 } };\n    expect(diff(obj1, obj2)).toEqual(undefined);\n  });\n\n  it('should detect simple changes in primitive values', () => {\n    const obj1 = { a: 1, b: 2 };\n    const obj2 = { a: 1, b: 3 };\n    expect(diff(obj1, obj2)).toEqual({ b: 3 });\n  });\n\n  it('should detect nested object changes', () => {\n    const obj1 = { a: 1, b: { c: 2, d: 4 } };\n    const obj2 = { a: 1, b: { c: 3, d: 4 } };\n    expect(diff(obj1, obj2)).toEqual({ b: { c: 3 } });\n  });\n\n  it('should handle array changes', () => {\n    const obj1 = { a: [1, 2, 3], b: 2 };\n    const obj2 = { a: [1, 2, 4], b: 2 };\n    expect(diff(obj1, obj2)).toEqual({ a: [1, 2, 4] });\n  });\n\n  it('should handle added keys', () => {\n    const obj1 = { a: 1 };\n    const obj2 = { a: 1, b: 2 };\n    expect(diff(obj1, obj2)).toEqual({ b: 2 });\n  });\n\n  it('should handle removed keys', () => {\n    const obj1 = { a: 1, b: 2 };\n    const obj2 = { a: 1 };\n    expect(diff(obj1, obj2)).toEqual(undefined);\n  });\n\n  it('should handle boolean value changes', () => {\n    const obj1 = { a: true, b: false };\n    const obj2 = { a: true, b: true };\n    expect(diff(obj1, obj2)).toEqual({ b: true });\n  });\n\n  it('should handle null and undefined values', () => {\n    const obj1 = { a: null, b: undefined };\n    const obj2: any = { a: 1, b: undefined };\n    expect(diff(obj1, obj2)).toEqual({ a: 1 });\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/dom.test.ts",
    "content": "import { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { getElementVisibleRect } from '../dom';\n\ndescribe('getElementVisibleRect', () => {\n  // 设置浏览器视口尺寸的 mock\n  beforeEach(() => {\n    vi.spyOn(document.documentElement, 'clientHeight', 'get').mockReturnValue(\n      800,\n    );\n    vi.spyOn(window, 'innerHeight', 'get').mockReturnValue(800);\n    vi.spyOn(document.documentElement, 'clientWidth', 'get').mockReturnValue(\n      1000,\n    );\n    vi.spyOn(window, 'innerWidth', 'get').mockReturnValue(1000);\n  });\n\n  it('should return default rect if element is undefined', () => {\n    expect(getElementVisibleRect()).toEqual({\n      bottom: 0,\n      height: 0,\n      left: 0,\n      right: 0,\n      top: 0,\n      width: 0,\n    });\n  });\n\n  it('should return default rect if element is null', () => {\n    expect(getElementVisibleRect(null)).toEqual({\n      bottom: 0,\n      height: 0,\n      left: 0,\n      right: 0,\n      top: 0,\n      width: 0,\n    });\n  });\n\n  it('should return correct visible rect when element is fully visible', () => {\n    const element = {\n      getBoundingClientRect: () => ({\n        bottom: 400,\n        height: 300,\n        left: 200,\n        right: 600,\n        top: 100,\n        width: 400,\n      }),\n    } as HTMLElement;\n\n    expect(getElementVisibleRect(element)).toEqual({\n      bottom: 400,\n      height: 300,\n      left: 200,\n      right: 600,\n      top: 100,\n      width: 400,\n    });\n  });\n\n  it('should return correct visible rect when element is partially off-screen at the top', () => {\n    const element = {\n      getBoundingClientRect: () => ({\n        bottom: 200,\n        height: 250,\n        left: 100,\n        right: 500,\n        top: -50,\n        width: 400,\n      }),\n    } as HTMLElement;\n\n    expect(getElementVisibleRect(element)).toEqual({\n      bottom: 200,\n      height: 200,\n      left: 100,\n      right: 500,\n      top: 0,\n      width: 400,\n    });\n  });\n\n  it('should return correct visible rect when element is partially off-screen at the right', () => {\n    const element = {\n      getBoundingClientRect: () => ({\n        bottom: 400,\n        height: 300,\n        left: 800,\n        right: 1200,\n        top: 100,\n        width: 400,\n      }),\n    } as HTMLElement;\n\n    expect(getElementVisibleRect(element)).toEqual({\n      bottom: 400,\n      height: 300,\n      left: 800,\n      right: 1000,\n      top: 100,\n      width: 200,\n    });\n  });\n\n  it('should return all zeros when element is completely off-screen', () => {\n    const element = {\n      getBoundingClientRect: () => ({\n        bottom: 1200,\n        height: 300,\n        left: 1100,\n        right: 1400,\n        top: 900,\n        width: 300,\n      }),\n    } as HTMLElement;\n\n    expect(getElementVisibleRect(element)).toEqual({\n      bottom: 0,\n      height: 0,\n      left: 0,\n      right: 0,\n      top: 0,\n      width: 0,\n    });\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/inference.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport {\n  getFirstNonNullOrUndefined,\n  isBoolean,\n  isEmpty,\n  isHttpUrl,\n  isObject,\n  isUndefined,\n  isWindow,\n} from '../inference';\n\ndescribe('isHttpUrl', () => {\n  it(\"should return true when given 'http://example.com'\", () => {\n    expect(isHttpUrl('http://example.com')).toBe(true);\n  });\n\n  it(\"should return true when given 'https://example.com'\", () => {\n    expect(isHttpUrl('https://example.com')).toBe(true);\n  });\n\n  it(\"should return false when given 'ftp://example.com'\", () => {\n    expect(isHttpUrl('ftp://example.com')).toBe(false);\n  });\n\n  it(\"should return false when given 'example.com'\", () => {\n    expect(isHttpUrl('example.com')).toBe(false);\n  });\n});\n\ndescribe('isUndefined', () => {\n  it('isUndefined should return true for undefined values', () => {\n    expect(isUndefined()).toBe(true);\n  });\n\n  it('isUndefined should return false for null values', () => {\n    expect(isUndefined(null)).toBe(false);\n  });\n\n  it('isUndefined should return false for defined values', () => {\n    expect(isUndefined(0)).toBe(false);\n    expect(isUndefined('')).toBe(false);\n    expect(isUndefined(false)).toBe(false);\n  });\n\n  it('isUndefined should return false for objects and arrays', () => {\n    expect(isUndefined({})).toBe(false);\n    expect(isUndefined([])).toBe(false);\n  });\n});\n\ndescribe('isEmpty', () => {\n  it('should return true for empty string', () => {\n    expect(isEmpty('')).toBe(true);\n  });\n\n  it('should return true for empty array', () => {\n    expect(isEmpty([])).toBe(true);\n  });\n\n  it('should return true for empty object', () => {\n    expect(isEmpty({})).toBe(true);\n  });\n\n  it('should return false for non-empty string', () => {\n    expect(isEmpty('hello')).toBe(false);\n  });\n\n  it('should return false for non-empty array', () => {\n    expect(isEmpty([1, 2, 3])).toBe(false);\n  });\n\n  it('should return false for non-empty object', () => {\n    expect(isEmpty({ a: 1 })).toBe(false);\n  });\n\n  it('should return true for null or undefined', () => {\n    expect(isEmpty(null)).toBe(true);\n    expect(isEmpty()).toBe(true);\n  });\n\n  it('should return false for number or boolean', () => {\n    expect(isEmpty(0)).toBe(false);\n    expect(isEmpty(true)).toBe(false);\n  });\n});\n\ndescribe('isWindow', () => {\n  it('should return true for the window object', () => {\n    expect(isWindow(window)).toBe(true);\n  });\n\n  it('should return false for other objects', () => {\n    expect(isWindow({})).toBe(false);\n    expect(isWindow([])).toBe(false);\n    expect(isWindow(null)).toBe(false);\n  });\n});\n\ndescribe('isBoolean', () => {\n  it('should return true for boolean values', () => {\n    expect(isBoolean(true)).toBe(true);\n    expect(isBoolean(false)).toBe(true);\n  });\n\n  it('should return false for non-boolean values', () => {\n    expect(isBoolean(null)).toBe(false);\n    expect(isBoolean(42)).toBe(false);\n    expect(isBoolean('string')).toBe(false);\n    expect(isBoolean({})).toBe(false);\n    expect(isBoolean([])).toBe(false);\n  });\n});\n\ndescribe('isObject', () => {\n  it('should return true for objects', () => {\n    expect(isObject({})).toBe(true);\n    expect(isObject({ a: 1 })).toBe(true);\n  });\n\n  it('should return false for non-objects', () => {\n    expect(isObject(null)).toBe(false);\n    expect(isObject(42)).toBe(false);\n    expect(isObject('string')).toBe(false);\n    expect(isObject(true)).toBe(false);\n    expect(isObject([1, 2, 3])).toBe(true);\n    expect(isObject(new Date())).toBe(true);\n    expect(isObject(/regex/)).toBe(true);\n  });\n});\n\ndescribe('getFirstNonNullOrUndefined', () => {\n  describe('getFirstNonNullOrUndefined', () => {\n    it('should return the first non-null and non-undefined value for a number array', () => {\n      expect(getFirstNonNullOrUndefined<number>(undefined, null, 0, 42)).toBe(\n        0,\n      );\n      expect(getFirstNonNullOrUndefined<number>(null, undefined, 42, 123)).toBe(\n        42,\n      );\n    });\n\n    it('should return the first non-null and non-undefined value for a string array', () => {\n      expect(\n        getFirstNonNullOrUndefined<string>(undefined, null, '', 'hello'),\n      ).toBe('');\n      expect(\n        getFirstNonNullOrUndefined<string>(null, undefined, 'test', 'world'),\n      ).toBe('test');\n    });\n\n    it('should return undefined if all values are null or undefined', () => {\n      expect(getFirstNonNullOrUndefined(undefined, null)).toBeUndefined();\n      expect(getFirstNonNullOrUndefined(null)).toBeUndefined();\n    });\n\n    it('should work with a single value', () => {\n      expect(getFirstNonNullOrUndefined(42)).toBe(42);\n      expect(getFirstNonNullOrUndefined()).toBeUndefined();\n      expect(getFirstNonNullOrUndefined(null)).toBeUndefined();\n    });\n\n    it('should handle mixed types correctly', () => {\n      expect(\n        getFirstNonNullOrUndefined<number | object | string>(\n          undefined,\n          null,\n          'test',\n          123,\n          { key: 'value' },\n        ),\n      ).toBe('test');\n      expect(\n        getFirstNonNullOrUndefined<number | object | string>(\n          null,\n          undefined,\n          [1, 2, 3],\n          'string',\n        ),\n      ).toEqual([1, 2, 3]);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/letter.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport {\n  capitalizeFirstLetter,\n  kebabToCamelCase,\n  toCamelCase,\n  toLowerCaseFirstLetter,\n} from '../letter';\n\ndescribe('capitalizeFirstLetter', () => {\n  it('should capitalize the first letter of a string', () => {\n    expect(capitalizeFirstLetter('hello')).toBe('Hello');\n    expect(capitalizeFirstLetter('world')).toBe('World');\n  });\n\n  it('should handle empty strings', () => {\n    expect(capitalizeFirstLetter('')).toBe('');\n  });\n\n  it('should handle single character strings', () => {\n    expect(capitalizeFirstLetter('a')).toBe('A');\n    expect(capitalizeFirstLetter('b')).toBe('B');\n  });\n\n  it('should not change the case of other characters', () => {\n    expect(capitalizeFirstLetter('hElLo')).toBe('HElLo');\n  });\n});\n\ndescribe('toLowerCaseFirstLetter', () => {\n  it('should convert the first letter to lowercase', () => {\n    expect(toLowerCaseFirstLetter('CommonAppName')).toBe('commonAppName');\n    expect(toLowerCaseFirstLetter('AnotherKeyExample')).toBe(\n      'anotherKeyExample',\n    );\n  });\n\n  it('should return the same string if the first letter is already lowercase', () => {\n    expect(toLowerCaseFirstLetter('alreadyLowerCase')).toBe('alreadyLowerCase');\n  });\n\n  it('should handle empty strings', () => {\n    expect(toLowerCaseFirstLetter('')).toBe('');\n  });\n\n  it('should handle single character strings', () => {\n    expect(toLowerCaseFirstLetter('A')).toBe('a');\n    expect(toLowerCaseFirstLetter('a')).toBe('a');\n  });\n\n  it('should handle strings with only one uppercase letter', () => {\n    expect(toLowerCaseFirstLetter('A')).toBe('a');\n  });\n\n  it('should handle strings with special characters', () => {\n    expect(toLowerCaseFirstLetter('!Special')).toBe('!Special');\n    expect(toLowerCaseFirstLetter('123Number')).toBe('123Number');\n  });\n});\n\ndescribe('toCamelCase', () => {\n  it('should return the key if parentKey is empty', () => {\n    expect(toCamelCase('child', '')).toBe('child');\n  });\n\n  it('should combine parentKey and key in camel case', () => {\n    expect(toCamelCase('child', 'parent')).toBe('parentChild');\n  });\n\n  it('should handle empty key and parentKey', () => {\n    expect(toCamelCase('', '')).toBe('');\n  });\n\n  it('should handle key with capital letters', () => {\n    expect(toCamelCase('Child', 'parent')).toBe('parentChild');\n    expect(toCamelCase('Child', 'Parent')).toBe('ParentChild');\n  });\n});\n\ndescribe('kebabToCamelCase', () => {\n  it('should convert kebab-case to camelCase correctly', () => {\n    expect(kebabToCamelCase('my-component-name')).toBe('myComponentName');\n  });\n\n  it('should handle multiple consecutive hyphens', () => {\n    expect(kebabToCamelCase('my--component--name')).toBe('myComponentName');\n  });\n\n  it('should trim leading and trailing hyphens', () => {\n    expect(kebabToCamelCase('-my-component-name-')).toBe('myComponentName');\n  });\n\n  it('should preserve the case of the first word', () => {\n    expect(kebabToCamelCase('My-component-name')).toBe('MyComponentName');\n  });\n\n  it('should convert a single word correctly', () => {\n    expect(kebabToCamelCase('component')).toBe('component');\n  });\n\n  it('should return an empty string if input is empty', () => {\n    expect(kebabToCamelCase('')).toBe('');\n  });\n\n  it('should handle strings with no hyphens', () => {\n    expect(kebabToCamelCase('mycomponentname')).toBe('mycomponentname');\n  });\n\n  it('should handle strings with only hyphens', () => {\n    expect(kebabToCamelCase('---')).toBe('');\n  });\n\n  it('should handle mixed case inputs', () => {\n    expect(kebabToCamelCase('my-Component-Name')).toBe('myComponentName');\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/resources.test.ts",
    "content": "import { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { loadScript } from '../resources';\n\ndescribe('loadScript', () => {\n  beforeEach(() => {\n    // 每个测试前清空 head，保证环境干净\n    document.head.innerHTML = '';\n  });\n\n  it('should resolve when the script loads successfully', async () => {\n    // happy-dom v20+ auto-fires 'load' via handleDisabledFileLoadingAsSuccess\n    const promise = loadScript('/test-script.js');\n\n    const script = document.querySelector(\n      'script[src=\"/test-script.js\"]',\n    ) as HTMLScriptElement;\n    expect(script).toBeTruthy();\n\n    await expect(promise).resolves.toBeUndefined();\n  });\n\n  it('should not insert duplicate script and resolve immediately if already loaded', async () => {\n    // 先手动插入一个相同 src 的 script\n    const existing = document.createElement('script');\n    existing.src = 'bar.js';\n    document.head.append(existing);\n\n    // 再次调用\n    const promise = loadScript('bar.js');\n\n    // 立即 resolve\n    await expect(promise).resolves.toBeUndefined();\n\n    // head 中只保留一个\n    const scripts = document.head.querySelectorAll('script[src=\"bar.js\"]');\n    expect(scripts).toHaveLength(1);\n  });\n\n  it('should reject when the script fails to load', async () => {\n    let capturedScript: HTMLScriptElement | null = null;\n\n    // 拦截 append，捕获 script 元素但不插入 DOM，\n    // 防止 happy-dom v20+ 自动触发 load 事件\n    const appendSpy = vi\n      .spyOn(document.head, 'append')\n      .mockImplementation((...nodes) => {\n        for (const node of nodes) {\n          if (node instanceof HTMLScriptElement) {\n            capturedScript = node;\n          }\n        }\n      });\n\n    const promise = loadScript('error.js');\n\n    appendSpy.mockRestore();\n\n    expect(capturedScript).toBeTruthy();\n    if (!capturedScript) {\n      throw new Error('Expected the captured script element to exist');\n    }\n    capturedScript.dispatchEvent(new Event('error'));\n\n    await expect(promise).rejects.toThrow('Failed to load script: error.js');\n  });\n\n  it('should handle multiple concurrent calls and only insert one script tag', async () => {\n    const p1 = loadScript('/test-script.js');\n    const p2 = loadScript('/test-script.js');\n\n    // happy-dom v20+ auto-fires 'load'，两个 promise 都应该 resolve\n    await expect(p1).resolves.toBeUndefined();\n    await expect(p2).resolves.toBeUndefined();\n\n    // 只插入一次\n    const scripts = document.head.querySelectorAll(\n      'script[src=\"/test-script.js\"]',\n    );\n    expect(scripts).toHaveLength(1);\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/stack.test.ts",
    "content": "import { beforeEach, describe, expect, it } from 'vitest';\n\nimport { createStack, Stack } from '../stack';\n\ndescribe('stack', () => {\n  let stack: Stack<number>;\n\n  beforeEach(() => {\n    stack = new Stack<number>();\n  });\n\n  it('push & size should work', () => {\n    stack.push(1, 2);\n\n    expect(stack.size).toBe(2);\n  });\n\n  it('peek should return top element without removing it', () => {\n    stack.push(1, 2);\n\n    expect(stack.peek()).toBe(2);\n    expect(stack.size).toBe(2);\n  });\n\n  it('pop should remove and return top element', () => {\n    stack.push(1, 2);\n\n    expect(stack.pop()).toBe(2);\n    expect(stack.size).toBe(1);\n    expect(stack.peek()).toBe(1);\n  });\n\n  it('pop on empty stack should return undefined', () => {\n    expect(stack.pop()).toBeUndefined();\n    expect(stack.peek()).toBeUndefined();\n  });\n\n  it('clear should remove all elements', () => {\n    stack.push(1, 2);\n\n    stack.clear();\n\n    expect(stack.size).toBe(0);\n    expect(stack.peek()).toBeUndefined();\n  });\n\n  it('toArray should return a shallow copy', () => {\n    stack.push(1, 2);\n\n    const arr = stack.toArray();\n    arr.push(3);\n\n    expect(stack.size).toBe(2);\n    expect(stack.toArray()).toEqual([1, 2]);\n  });\n\n  it('dedup should remove existing item before push', () => {\n    stack.push(1, 2, 1);\n\n    expect(stack.toArray()).toEqual([2, 1]);\n    expect(stack.size).toBe(2);\n  });\n\n  it('dedup = false should allow duplicate items', () => {\n    const s = new Stack<number>(false);\n\n    s.push(1, 1, 1);\n\n    expect(s.toArray()).toEqual([1, 1, 1]);\n    expect(s.size).toBe(3);\n  });\n\n  it('remove should delete all matching items', () => {\n    stack.push(1, 2, 1);\n\n    stack.remove(1);\n\n    expect(stack.toArray()).toEqual([2]);\n    expect(stack.size).toBe(1);\n  });\n\n  it('maxSize should limit stack capacity', () => {\n    const s = new Stack<number>(true, 3);\n\n    s.push(1, 2, 3, 4);\n\n    expect(s.toArray()).toEqual([2, 3, 4]);\n    expect(s.size).toBe(3);\n  });\n\n  it('dedup + maxSize should work together', () => {\n    const s = new Stack<number>(true, 3);\n\n    s.push(1, 2, 3, 2); // 去重并重新入栈\n\n    expect(s.toArray()).toEqual([1, 3, 2]);\n    expect(s.size).toBe(3);\n  });\n\n  it('createStack should create a stack instance', () => {\n    const s = createStack<number>(true, 2);\n\n    s.push(1, 2, 3);\n\n    expect(s.toArray()).toEqual([2, 3]);\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/state-handler.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { StateHandler } from '../state-handler';\n\ndescribe('stateHandler', () => {\n  it('should resolve when condition is set to true', async () => {\n    const handler = new StateHandler();\n\n    // 模拟异步设置 condition 为 true\n    setTimeout(() => {\n      handler.setConditionTrue(); // 明确触发 condition 为 true\n    }, 10);\n\n    // 等待条件被设置为 true\n    await handler.waitForCondition();\n    expect(handler.isConditionTrue()).toBe(true);\n  });\n\n  it('should resolve immediately if condition is already true', async () => {\n    const handler = new StateHandler();\n    handler.setConditionTrue(); // 提前设置为 true\n\n    // 立即 resolve，因为 condition 已经是 true\n    await handler.waitForCondition();\n    expect(handler.isConditionTrue()).toBe(true);\n  });\n\n  it('should reject when condition is set to false after waiting', async () => {\n    const handler = new StateHandler();\n\n    // 模拟异步设置 condition 为 false\n    setTimeout(() => {\n      handler.setConditionFalse(); // 明确触发 condition 为 false\n    }, 10);\n\n    // 等待过程中，期望 Promise 被 reject\n    await expect(handler.waitForCondition()).rejects.toThrow();\n    expect(handler.isConditionTrue()).toBe(false);\n  });\n\n  it('should reset condition to false', () => {\n    const handler = new StateHandler();\n    handler.setConditionTrue(); // 设置为 true\n    handler.reset(); // 重置为 false\n\n    expect(handler.isConditionTrue()).toBe(false);\n  });\n\n  it('should resolve when condition is set to true after reset', async () => {\n    const handler = new StateHandler();\n    handler.reset(); // 确保初始为 false\n\n    setTimeout(() => {\n      handler.setConditionTrue(); // 重置后设置为 true\n    }, 10);\n\n    await handler.waitForCondition();\n    expect(handler.isConditionTrue()).toBe(true);\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/tree.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { filterTree, mapTree, traverseTreeValues } from '../tree';\n\ndescribe('traverseTreeValues', () => {\n  interface Node {\n    children?: Node[];\n    name: string;\n  }\n\n  type NodeValue = string;\n\n  const sampleTree: Node[] = [\n    {\n      name: 'A',\n      children: [\n        { name: 'B' },\n        {\n          name: 'C',\n          children: [{ name: 'D' }, { name: 'E' }],\n        },\n      ],\n    },\n    {\n      name: 'F',\n      children: [\n        { name: 'G' },\n        {\n          name: 'H',\n          children: [{ name: 'I' }],\n        },\n      ],\n    },\n  ];\n\n  it('traverses tree and returns all node values', () => {\n    const values = traverseTreeValues<Node, NodeValue>(\n      sampleTree,\n      (node) => node.name,\n      {\n        childProps: 'children',\n      },\n    );\n    expect(values).toEqual(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']);\n  });\n\n  it('handles empty tree', () => {\n    const values = traverseTreeValues<Node, NodeValue>([], (node) => node.name);\n    expect(values).toEqual([]);\n  });\n\n  it('handles tree with only root node', () => {\n    const rootNode = { name: 'A' };\n    const values = traverseTreeValues<Node, NodeValue>(\n      [rootNode],\n      (node) => node.name,\n    );\n    expect(values).toEqual(['A']);\n  });\n\n  it('handles tree with only leaf nodes', () => {\n    const leafNodes = [{ name: 'A' }, { name: 'B' }, { name: 'C' }];\n    const values = traverseTreeValues<Node, NodeValue>(\n      leafNodes,\n      (node) => node.name,\n    );\n    expect(values).toEqual(['A', 'B', 'C']);\n  });\n});\n\ndescribe('filterTree', () => {\n  const tree = [\n    {\n      id: 1,\n      children: [\n        { id: 2 },\n        { id: 3, children: [{ id: 4 }, { id: 5 }, { id: 6 }] },\n        { id: 7 },\n      ],\n    },\n    { id: 8, children: [{ id: 9 }, { id: 10 }] },\n    { id: 11 },\n  ];\n\n  it('should return all nodes when condition is always true', () => {\n    const result = filterTree(tree, () => true, { childProps: 'children' });\n    expect(result).toEqual(tree);\n  });\n\n  it('should return only root nodes when condition is always false', () => {\n    const result = filterTree(tree, () => false);\n    expect(result).toEqual([]);\n  });\n\n  it('should return nodes with even id values', () => {\n    const result = filterTree(tree, (node) => node.id % 2 === 0);\n    expect(result).toEqual([{ id: 8, children: [{ id: 10 }] }]);\n  });\n\n  it('should return nodes with odd id values and their ancestors', () => {\n    const result = filterTree(tree, (node) => node.id % 2 === 1);\n    expect(result).toEqual([\n      {\n        id: 1,\n        children: [{ id: 3, children: [{ id: 5 }] }, { id: 7 }],\n      },\n      { id: 11 },\n    ]);\n  });\n\n  it('should return nodes with \"leaf\" in their name', () => {\n    const tree = [\n      {\n        name: 'root',\n        children: [\n          { name: 'leaf 1' },\n          {\n            name: 'branch',\n            children: [{ name: 'leaf 2' }, { name: 'leaf 3' }],\n          },\n          { name: 'leaf 4' },\n        ],\n      },\n    ];\n    const result = filterTree(\n      tree,\n      (node) => node.name.includes('leaf') || node.name === 'root',\n    );\n    expect(result).toEqual([\n      {\n        name: 'root',\n        children: [{ name: 'leaf 1' }, { name: 'leaf 4' }],\n      },\n    ]);\n  });\n});\n\ndescribe('mapTree', () => {\n  it('map infinite depth tree using mapTree', () => {\n    const tree = [\n      {\n        id: 1,\n        name: 'node1',\n        children: [\n          { id: 2, name: 'node2' },\n          { id: 3, name: 'node3' },\n          {\n            id: 4,\n            name: 'node4',\n            children: [\n              {\n                id: 5,\n                name: 'node5',\n                children: [\n                  { id: 6, name: 'node6' },\n                  { id: 7, name: 'node7' },\n                ],\n              },\n              { id: 8, name: 'node8' },\n            ],\n          },\n        ],\n      },\n    ];\n    const newTree = mapTree(tree, (node) => ({\n      ...node,\n      name: `${node.name}-new`,\n    }));\n\n    expect(newTree).toEqual([\n      {\n        id: 1,\n        name: 'node1-new',\n        children: [\n          { id: 2, name: 'node2-new' },\n          { id: 3, name: 'node3-new' },\n          {\n            id: 4,\n            name: 'node4-new',\n            children: [\n              {\n                id: 5,\n                name: 'node5-new',\n                children: [\n                  { id: 6, name: 'node6-new' },\n                  { id: 7, name: 'node7-new' },\n                ],\n              },\n              { id: 8, name: 'node8-new' },\n            ],\n          },\n        ],\n      },\n    ]);\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/unique.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { uniqueByField } from '../unique';\n\ndescribe('uniqueByField', () => {\n  it('should return an array with unique items based on id field', () => {\n    const items = [\n      { id: 1, name: 'Item 1' },\n      { id: 2, name: 'Item 2' },\n      { id: 3, name: 'Item 3' },\n      { id: 1, name: 'Duplicate Item' },\n    ];\n\n    const uniqueItems = uniqueByField(items, 'id');\n\n    expect(uniqueItems).toHaveLength(3);\n    expect(uniqueItems).toEqual([\n      { id: 1, name: 'Item 1' },\n      { id: 2, name: 'Item 2' },\n      { id: 3, name: 'Item 3' },\n    ]);\n  });\n\n  it('should return an empty array when input array is empty', () => {\n    const items: any[] = []; // Empty array\n\n    const uniqueItems = uniqueByField(items, 'id');\n\n    // Assert expected results\n    expect(uniqueItems).toEqual([]);\n  });\n\n  it('should handle arrays with only one item correctly', () => {\n    const items = [{ id: 1, name: 'Item 1' }];\n\n    const uniqueItems = uniqueByField(items, 'id');\n\n    // Assert expected results\n    expect(uniqueItems).toHaveLength(1);\n    expect(uniqueItems).toEqual([{ id: 1, name: 'Item 1' }]);\n  });\n\n  it('should preserve the order of the first occurrence of each item', () => {\n    const items = [\n      { id: 2, name: 'Item 2' },\n      { id: 1, name: 'Item 1' },\n      { id: 3, name: 'Item 3' },\n      { id: 1, name: 'Duplicate Item' },\n    ];\n\n    const uniqueItems = uniqueByField(items, 'id');\n\n    // Assert expected results (order of first occurrences preserved)\n    expect(uniqueItems).toEqual([\n      { id: 2, name: 'Item 2' },\n      { id: 1, name: 'Item 1' },\n      { id: 3, name: 'Item 3' },\n    ]);\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/update-css-variables.test.ts",
    "content": "import { expect, it } from 'vitest';\n\nimport { updateCSSVariables } from '../update-css-variables';\n\nit('updateCSSVariables should update CSS variables in :root selector', () => {\n  // 模拟初始的内联样式表内容\n  const initialStyleContent = ':root { --primaryColor: red; }';\n  document.head.innerHTML = `<style id=\"custom-styles\">${initialStyleContent}</style>`;\n\n  // 要更新的CSS变量和它们的新值\n  const updatedVariables = {\n    fontSize: '16px',\n    primaryColor: 'blue',\n    secondaryColor: 'green',\n  };\n\n  // 调用函数来更新CSS变量\n  updateCSSVariables(updatedVariables, 'custom-styles');\n\n  // 获取更新后的样式内容\n  const styleElement = document.querySelector('#custom-styles');\n  const updatedStyleContent = styleElement ? styleElement.textContent : '';\n\n  // 检查更新后的样式内容是否包含正确的更新值\n  expect(\n    updatedStyleContent?.includes('primaryColor: blue;') &&\n      updatedStyleContent?.includes('secondaryColor: green;') &&\n      updatedStyleContent?.includes('fontSize: 16px;'),\n  ).toBe(true);\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/util.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { bindMethods, getNestedValue } from '../util';\n\nclass TestClass {\n  public value: string;\n\n  constructor(value: string) {\n    this.value = value;\n    bindMethods(this); // 调用通用方法\n  }\n\n  getValue() {\n    return this.value;\n  }\n\n  setValue(newValue: string) {\n    this.value = newValue;\n  }\n}\n\ndescribe('bindMethods', () => {\n  it('should bind methods to the instance correctly', () => {\n    const instance = new TestClass('initial');\n\n    // 解构方法\n    const { getValue } = instance;\n\n    // 检查 getValue 是否能正确调用，并且 this 绑定了 instance\n    expect(getValue()).toBe('initial');\n  });\n\n  it('should bind multiple methods', () => {\n    const instance = new TestClass('initial');\n\n    const { getValue, setValue } = instance;\n\n    // 检查 getValue 和 setValue 方法是否正确绑定了 this\n    setValue('newValue');\n    expect(getValue()).toBe('newValue');\n  });\n\n  it('should not bind non-function properties', () => {\n    const instance = new TestClass('initial');\n\n    // 检查普通属性是否保持原样\n    expect(instance.value).toBe('initial');\n  });\n\n  it('should not bind constructor method', () => {\n    const instance = new TestClass('test');\n\n    // 检查 constructor 是否没有被绑定\n    expect(instance.constructor.name).toBe('TestClass');\n  });\n\n  it('should not bind getter/setter properties', () => {\n    class TestWithGetterSetter {\n      get value() {\n        return this._value;\n      }\n\n      set value(newValue: string) {\n        this._value = newValue;\n      }\n\n      private _value: string = 'test';\n\n      constructor() {\n        bindMethods(this);\n      }\n    }\n\n    const instance = new TestWithGetterSetter();\n    const { value } = instance;\n\n    // Getter 和 setter 不应被绑定\n    expect(value).toBe('test');\n  });\n});\n\ndescribe('getNestedValue', () => {\n  interface UserProfile {\n    age: number;\n    name: string;\n  }\n\n  interface UserSettings {\n    theme: string;\n  }\n\n  interface Data {\n    user: {\n      profile: UserProfile;\n      settings: UserSettings;\n    };\n  }\n\n  const data: Data = {\n    user: {\n      profile: {\n        age: 25,\n        name: 'Alice',\n      },\n      settings: {\n        theme: 'dark',\n      },\n    },\n  };\n\n  it('should get a nested value when the path is valid', () => {\n    const result = getNestedValue(data, 'user.profile.name');\n    expect(result).toBe('Alice');\n  });\n\n  it('should return undefined for non-existent property', () => {\n    const result = getNestedValue(data, 'user.profile.gender');\n    expect(result).toBeUndefined();\n  });\n\n  it('should return undefined when accessing a non-existent deep path', () => {\n    const result = getNestedValue(data, 'user.nonexistent.field');\n    expect(result).toBeUndefined();\n  });\n\n  it('should return undefined if a middle level is undefined', () => {\n    const result = getNestedValue({ user: undefined }, 'user.profile.name');\n    expect(result).toBeUndefined();\n  });\n\n  it('should return the correct value for a nested setting', () => {\n    const result = getNestedValue(data, 'user.settings.theme');\n    expect(result).toBe('dark');\n  });\n\n  it('should work for a single-level path', () => {\n    const result = getNestedValue({ a: 1, b: 2 }, 'b');\n    expect(result).toBe(2);\n  });\n\n  it('should return the entire object if path is empty', () => {\n    expect(() => getNestedValue(data, '')()).toThrow();\n  });\n\n  it('should handle paths with array indexes', () => {\n    const complexData = { list: [{ name: 'Item1' }, { name: 'Item2' }] };\n    const result = getNestedValue(complexData, 'list.1.name');\n    expect(result).toBe('Item2');\n  });\n\n  it('should return undefined when accessing an out-of-bounds array index', () => {\n    const complexData = { list: [{ name: 'Item1' }] };\n    const result = getNestedValue(complexData, 'list.2.name');\n    expect(result).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/__tests__/window.test.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { openWindow } from '../window';\n\ndescribe('openWindow', () => {\n  // 保存原始的 window.open 函数\n  let originalOpen: typeof window.open;\n\n  beforeEach(() => {\n    originalOpen = window.open;\n  });\n\n  afterEach(() => {\n    window.open = originalOpen;\n  });\n\n  it('should call window.open with correct arguments', () => {\n    const url = 'https://example.com';\n    const options = { noopener: true, noreferrer: true, target: '_blank' };\n\n    window.open = vi.fn();\n\n    // 调用函数\n    openWindow(url, options);\n\n    // 验证 window.open 是否被正确地调用\n    expect(window.open).toHaveBeenCalledWith(\n      url,\n      options.target,\n      'noopener=yes,noreferrer=yes',\n    );\n  });\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/cn.ts",
    "content": "import type { ClassValue } from 'clsx';\n\nimport { clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nfunction cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n\nexport { cn };\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/date.ts",
    "content": "import dayjs from 'dayjs';\nimport timezone from 'dayjs/plugin/timezone.js';\nimport utc from 'dayjs/plugin/utc.js';\n\ndayjs.extend(utc);\ndayjs.extend(timezone);\n\ntype FormatDate = Date | dayjs.Dayjs | number | string;\n\ntype Format =\n  | 'HH'\n  | 'HH:mm'\n  | 'HH:mm:ss'\n  | 'YYYY'\n  | 'YYYY-MM'\n  | 'YYYY-MM-DD'\n  | 'YYYY-MM-DD HH'\n  | 'YYYY-MM-DD HH:mm'\n  | 'YYYY-MM-DD HH:mm:ss'\n  | (string & {});\n\nexport function formatDate(time?: FormatDate, format: Format = 'YYYY-MM-DD') {\n  try {\n    const date = dayjs.isDayjs(time) ? time : dayjs(time);\n    if (!date.isValid()) {\n      throw new Error('Invalid date');\n    }\n    return date.tz().format(format);\n  } catch (error) {\n    console.error(`Error formatting date: ${error}`);\n    return String(time ?? '');\n  }\n}\n\nexport function formatDateTime(time?: FormatDate) {\n  return formatDate(time, 'YYYY-MM-DD HH:mm:ss');\n}\n\nexport function isDate(value: any): value is Date {\n  return value instanceof Date;\n}\n\nexport function isDayjsObject(value: any): value is dayjs.Dayjs {\n  return dayjs.isDayjs(value);\n}\n\n/**\n * 获取当前时区\n * @returns 当前时区\n */\nexport const getSystemTimezone = () => {\n  return dayjs.tz.guess();\n};\n\n/**\n * 自定义设置的时区\n */\nlet currentTimezone = getSystemTimezone();\n\n/**\n * 设置默认时区\n * @param timezone\n */\nexport const setCurrentTimezone = (timezone?: string) => {\n  currentTimezone = timezone || getSystemTimezone();\n  dayjs.tz.setDefault(currentTimezone);\n};\n\n/**\n * 获取设置的时区\n * @returns 设置的时区\n */\nexport const getCurrentTimezone = () => {\n  return currentTimezone;\n};\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/diff.ts",
    "content": "// type Diff<T = any> = T;\n\n// 比较两个数组是否相等\n\nfunction arraysEqual<T>(a: T[], b: T[]): boolean {\n  if (a.length !== b.length) return false;\n  const counter = new Map<T, number>();\n  for (const value of a) {\n    counter.set(value, (counter.get(value) || 0) + 1);\n  }\n  for (const value of b) {\n    const count = counter.get(value);\n    if (count === undefined || count === 0) {\n      return false;\n    }\n    counter.set(value, count - 1);\n  }\n  return true;\n}\n\n// 深度对比两个值\n// function deepEqual<T>(oldVal: T, newVal: T): boolean {\n//   if (\n//     typeof oldVal === 'object' &&\n//     oldVal !== null &&\n//     typeof newVal === 'object' &&\n//     newVal !== null\n//   ) {\n//     return Array.isArray(oldVal) && Array.isArray(newVal)\n//       ? arraysEqual(oldVal, newVal)\n//       : diff(oldVal as any, newVal as any) === null;\n//   } else {\n//     return oldVal === newVal;\n//   }\n// }\n\n// // diff 函数\n// function diff<T extends object>(\n//   oldObj: T,\n//   newObj: T,\n//   ignoreFields: (keyof T)[] = [],\n// ): { [K in keyof T]?: Diff<T[K]> } | null {\n//   const difference: { [K in keyof T]?: Diff<T[K]> } = {};\n\n//   for (const key in oldObj) {\n//     if (ignoreFields.includes(key)) continue;\n//     const oldValue = oldObj[key];\n//     const newValue = newObj[key];\n\n//     if (!deepEqual(oldValue, newValue)) {\n//       difference[key] = newValue;\n//     }\n//   }\n\n//   return Object.keys(difference).length === 0 ? null : difference;\n// }\n\ntype DiffResult<T> = Partial<{\n  [K in keyof T]: T[K] extends object ? DiffResult<T[K]> : T[K];\n}>;\n\nfunction diff<T extends Record<string, any>>(obj1: T, obj2: T): DiffResult<T> {\n  function findDifferences(o1: any, o2: any): any {\n    if (Array.isArray(o1) && Array.isArray(o2)) {\n      if (!arraysEqual(o1, o2)) {\n        return o2;\n      }\n      return undefined;\n    }\n\n    if (\n      typeof o1 === 'object' &&\n      typeof o2 === 'object' &&\n      o1 !== null &&\n      o2 !== null\n    ) {\n      const diffResult: any = {};\n\n      const keys = new Set([...Object.keys(o1), ...Object.keys(o2)]);\n      keys.forEach((key) => {\n        const valueDiff = findDifferences(o1[key], o2[key]);\n        if (valueDiff !== undefined) {\n          diffResult[key] = valueDiff;\n        }\n      });\n\n      return Object.keys(diffResult).length > 0 ? diffResult : undefined;\n    }\n\n    return o1 === o2 ? undefined : o2;\n  }\n\n  return findDifferences(obj1, obj2);\n}\n\nexport { arraysEqual, diff };\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/dom.ts",
    "content": "export interface VisibleDomRect {\n  bottom: number;\n  height: number;\n  left: number;\n  right: number;\n  top: number;\n  width: number;\n}\n\n/**\n * 获取元素可见信息\n * @param element\n */\nexport function getElementVisibleRect(\n  element?: HTMLElement | null | undefined,\n): VisibleDomRect {\n  if (!element) {\n    return {\n      bottom: 0,\n      height: 0,\n      left: 0,\n      right: 0,\n      top: 0,\n      width: 0,\n    };\n  }\n  const rect = element.getBoundingClientRect();\n  const viewHeight = Math.max(\n    document.documentElement.clientHeight,\n    window.innerHeight,\n  );\n\n  const top = Math.max(rect.top, 0);\n  const bottom = Math.min(rect.bottom, viewHeight);\n\n  const viewWidth = Math.max(\n    document.documentElement.clientWidth,\n    window.innerWidth,\n  );\n\n  const left = Math.max(rect.left, 0);\n  const right = Math.min(rect.right, viewWidth);\n\n  // 如果元素完全不可见，则返回一个空的矩形\n  if (top >= viewHeight || bottom <= 0 || left >= viewWidth || right <= 0) {\n    return {\n      bottom: 0,\n      height: 0,\n      left: 0,\n      right: 0,\n      top: 0,\n      width: 0,\n    };\n  }\n\n  return {\n    bottom,\n    height: Math.max(0, bottom - top),\n    left,\n    right,\n    top,\n    width: Math.max(0, right - left),\n  };\n}\n\nexport function getScrollbarWidth() {\n  const scrollDiv = document.createElement('div');\n\n  scrollDiv.style.visibility = 'hidden';\n  scrollDiv.style.overflow = 'scroll';\n  scrollDiv.style.position = 'absolute';\n  scrollDiv.style.top = '-9999px';\n\n  document.body.append(scrollDiv);\n\n  const innerDiv = document.createElement('div');\n  scrollDiv.append(innerDiv);\n\n  const scrollbarWidth = scrollDiv.offsetWidth - innerDiv.offsetWidth;\n\n  scrollDiv.remove();\n  return scrollbarWidth;\n}\n\nexport function needsScrollbar() {\n  const doc = document.documentElement;\n  const body = document.body;\n\n  // 检查 body 的 overflow-y 样式\n  const overflowY = window.getComputedStyle(body).overflowY;\n\n  // 如果明确设置了需要滚动条的样式\n  if (overflowY === 'scroll' || overflowY === 'auto') {\n    return doc.scrollHeight > window.innerHeight;\n  }\n\n  // 在其他情况下，根据 scrollHeight 和 innerHeight 比较判断\n  return doc.scrollHeight > window.innerHeight;\n}\n\nexport function triggerWindowResize(): void {\n  // 创建一个新的 resize 事件\n  const resizeEvent = new Event('resize');\n\n  // 触发 window 的 resize 事件\n  window.dispatchEvent(resizeEvent);\n}\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/download.ts",
    "content": "import { openWindow } from './window';\n\ninterface DownloadOptions<T = string> {\n  fileName?: string;\n  source: T;\n  target?: string;\n}\n\nconst DEFAULT_FILENAME = 'downloaded_file';\n\n/**\n * 通过 URL 下载文件，支持跨域\n * @throws {Error} - 当下载失败时抛出错误\n */\nexport async function downloadFileFromUrl({\n  fileName,\n  source,\n  target = '_blank',\n}: DownloadOptions): Promise<void> {\n  if (!source || typeof source !== 'string') {\n    throw new Error('Invalid URL.');\n  }\n\n  const isChrome = window.navigator.userAgent.toLowerCase().includes('chrome');\n  const isSafari = window.navigator.userAgent.toLowerCase().includes('safari');\n\n  if (/iP/.test(window.navigator.userAgent)) {\n    console.error('Your browser does not support download!');\n    return;\n  }\n\n  if (isChrome || isSafari) {\n    triggerDownload(source, resolveFileName(source, fileName));\n    return;\n  }\n  if (!source.includes('?')) {\n    source += '?download';\n  }\n\n  openWindow(source, { target });\n}\n\n/**\n * 通过 Base64 下载文件\n */\nexport function downloadFileFromBase64({ fileName, source }: DownloadOptions) {\n  if (!source || typeof source !== 'string') {\n    throw new Error('Invalid Base64 data.');\n  }\n\n  const resolvedFileName = fileName || DEFAULT_FILENAME;\n  triggerDownload(source, resolvedFileName);\n}\n\n/**\n * 通过图片 URL 下载图片文件\n */\nexport async function downloadFileFromImageUrl({\n  fileName,\n  source,\n}: DownloadOptions) {\n  const base64 = await urlToBase64(source);\n  downloadFileFromBase64({ fileName, source: base64 });\n}\n\n/**\n * 通过 Blob 下载文件\n */\nexport function downloadFileFromBlob({\n  fileName = DEFAULT_FILENAME,\n  source,\n}: DownloadOptions<Blob>): void {\n  if (!(source instanceof Blob)) {\n    throw new TypeError('Invalid Blob data.');\n  }\n\n  const url = URL.createObjectURL(source);\n  triggerDownload(url, fileName);\n}\n\n/**\n * 下载文件，支持 Blob、字符串和其他 BlobPart 类型\n */\nexport function downloadFileFromBlobPart({\n  fileName = DEFAULT_FILENAME,\n  source,\n}: DownloadOptions<BlobPart>): void {\n  // 如果 data 不是 Blob，则转换为 Blob\n  const blob =\n    source instanceof Blob\n      ? source\n      : new Blob([source], { type: 'application/octet-stream' });\n\n  // 创建对象 URL 并触发下载\n  const url = URL.createObjectURL(blob);\n  triggerDownload(url, fileName);\n}\n\n/**\n * img url to base64\n * @param url\n */\nexport function urlToBase64(url: string, mineType?: string): Promise<string> {\n  return new Promise((resolve, reject) => {\n    let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null;\n    const ctx = canvas?.getContext('2d');\n    const img = new Image();\n    img.crossOrigin = '';\n    img.addEventListener('load', () => {\n      if (!canvas || !ctx) {\n        return reject(new Error('Failed to create canvas.'));\n      }\n      canvas.height = img.height;\n      canvas.width = img.width;\n      ctx.drawImage(img, 0, 0);\n      const dataURL = canvas.toDataURL(mineType || 'image/png');\n      canvas = null;\n      resolve(dataURL);\n    });\n    img.src = url;\n  });\n}\n\n/**\n * 通用下载触发函数\n * @param href - 文件下载的 URL\n * @param fileName - 下载文件的名称，如果未提供则自动识别\n * @param revokeDelay - 清理 URL 的延迟时间 (毫秒)\n */\nexport function triggerDownload(\n  href: string,\n  fileName: string | undefined,\n  revokeDelay: number = 100,\n): void {\n  const defaultFileName = 'downloaded_file';\n  const finalFileName = fileName || defaultFileName;\n\n  const link = document.createElement('a');\n  link.href = href;\n  link.download = finalFileName;\n  link.style.display = 'none';\n\n  if (link.download === undefined) {\n    link.setAttribute('target', '_blank');\n  }\n\n  document.body.append(link);\n  link.click();\n  link.remove();\n\n  // 清理临时 URL 以释放内存\n  setTimeout(() => URL.revokeObjectURL(href), revokeDelay);\n}\n\nfunction resolveFileName(url: string, fileName?: string): string {\n  return fileName || url.slice(url.lastIndexOf('/') + 1) || DEFAULT_FILENAME;\n}\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/index.ts",
    "content": "export * from './cn';\nexport * from './date';\nexport * from './diff';\nexport * from './dom';\nexport * from './download';\nexport * from './inference';\nexport * from './letter';\nexport * from './merge';\nexport * from './nprogress';\nexport * from './resources';\nexport * from './stack';\nexport * from './state-handler';\nexport * from './to';\nexport * from './tree';\nexport * from './unique';\nexport * from './update-css-variables';\nexport * from './util';\nexport * from './window';\nexport { get, isEqual, set } from 'es-toolkit/compat';\n// export { cloneDeep } from 'es-toolkit/object';\nexport { default as cloneDeep } from 'lodash.clonedeep';\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/inference.ts",
    "content": "import { isFunction, isObject, isString } from '@vue/shared';\n\n/**\n * 检查传入的值是否为undefined。\n *\n * @param {unknown} value 要检查的值。\n * @returns {boolean} 如果值是undefined，返回true，否则返回false。\n */\nfunction isUndefined(value?: unknown): value is undefined {\n  return value === undefined;\n}\n\n/**\n * 检查传入的值是否为boolean\n * @param value\n * @returns 如果值是布尔值，返回true，否则返回false。\n */\nfunction isBoolean(value: unknown): value is boolean {\n  return typeof value === 'boolean';\n}\n\n/**\n * 检查传入的值是否为空。\n *\n * 以下情况将被认为是空：\n * - 值为null。\n * - 值为undefined。\n * - 值为一个空字符串。\n * - 值为一个长度为0的数组。\n * - 值为一个没有元素的Map或Set。\n * - 值为一个没有属性的对象。\n *\n * @param {T} value 要检查的值。\n * @returns {boolean} 如果值为空，返回true，否则返回false。\n */\nfunction isEmpty<T = unknown>(value?: T): value is T {\n  if (value === null || value === undefined) {\n    return true;\n  }\n\n  if (Array.isArray(value) || isString(value)) {\n    return value.length === 0;\n  }\n\n  if (value instanceof Map || value instanceof Set) {\n    return value.size === 0;\n  }\n\n  if (isObject(value)) {\n    return Object.keys(value).length === 0;\n  }\n\n  return false;\n}\n\n/**\n * 检查传入的字符串是否为有效的HTTP或HTTPS URL。\n *\n * @param {string} url 要检查的字符串。\n * @return {boolean} 如果字符串是有效的HTTP或HTTPS URL，返回true，否则返回false。\n */\nfunction isHttpUrl(url?: string): boolean {\n  if (!url) {\n    return false;\n  }\n  // 使用正则表达式测试URL是否以http:// 或 https:// 开头\n  const httpRegex = /^https?:\\/\\/.*$/;\n  return httpRegex.test(url);\n}\n\n/**\n * 检查传入的值是否为window对象。\n *\n * @param {any} value 要检查的值。\n * @returns {boolean} 如果值是window对象，返回true，否则返回false。\n */\nfunction isWindow(value: any): value is Window {\n  return (\n    typeof window !== 'undefined' && value !== null && value === value.window\n  );\n}\n\n/**\n * 检查当前运行环境是否为Mac OS。\n *\n * 这个函数通过检查navigator.userAgent字符串来判断当前运行环境。\n * 如果userAgent字符串中包含\"macintosh\"或\"mac os x\"（不区分大小写），则认为当前环境是Mac OS。\n *\n * @returns {boolean} 如果当前环境是Mac OS，返回true，否则返回false。\n */\nfunction isMacOs(): boolean {\n  const macRegex = /macintosh|mac os x/i;\n  return macRegex.test(navigator.userAgent);\n}\n\n/**\n * 检查当前运行环境是否为Windows OS。\n *\n * 这个函数通过检查navigator.userAgent字符串来判断当前运行环境。\n * 如果userAgent字符串中包含\"windows\"或\"win32\"（不区分大小写），则认为当前环境是Windows OS。\n *\n * @returns {boolean} 如果当前环境是Windows OS，返回true，否则返回false。\n */\nfunction isWindowsOs(): boolean {\n  const windowsRegex = /windows|win32/i;\n  return windowsRegex.test(navigator.userAgent);\n}\n\n/**\n * 检查传入的值是否为数字\n * @param value\n */\nfunction isNumber(value: any): value is number {\n  return typeof value === 'number' && Number.isFinite(value);\n}\n\n/**\n * Returns the first value in the provided list that is neither `null` nor `undefined`.\n *\n * This function iterates over the input values and returns the first one that is\n * not strictly equal to `null` or `undefined`. If all values are either `null` or\n * `undefined`, it returns `undefined`.\n *\n * @template T - The type of the input values.\n * @param {...(T | null | undefined)[]} values - A list of values to evaluate.\n * @returns {T | undefined} - The first value that is not `null` or `undefined`, or `undefined` if none are found.\n *\n * @example\n * // Returns 42 because it is the first non-null, non-undefined value.\n * getFirstNonNullOrUndefined(undefined, null, 42, 'hello'); // 42\n *\n * @example\n * // Returns 'hello' because it is the first non-null, non-undefined value.\n * getFirstNonNullOrUndefined(null, undefined, 'hello', 123); // 'hello'\n *\n * @example\n * // Returns undefined because all values are either null or undefined.\n * getFirstNonNullOrUndefined(undefined, null); // undefined\n */\nfunction getFirstNonNullOrUndefined<T>(\n  ...values: (null | T | undefined)[]\n): T | undefined {\n  for (const value of values) {\n    if (value !== undefined && value !== null) {\n      return value;\n    }\n  }\n  return undefined;\n}\n\nexport {\n  getFirstNonNullOrUndefined,\n  isBoolean,\n  isEmpty,\n  isFunction,\n  isHttpUrl,\n  isMacOs,\n  isNumber,\n  isObject,\n  isString,\n  isUndefined,\n  isWindow,\n  isWindowsOs,\n};\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/letter.ts",
    "content": "/**\n * 将字符串的首字母大写\n * @param string\n */\nfunction capitalizeFirstLetter(string: string): string {\n  return string.charAt(0).toUpperCase() + string.slice(1);\n}\n\n/**\n * 将字符串的首字母转换为小写。\n *\n * @param str 要转换的字符串\n * @returns 首字母小写的字符串\n */\nfunction toLowerCaseFirstLetter(str: string): string {\n  if (!str) return str; // 如果字符串为空，直接返回\n  return str.charAt(0).toLowerCase() + str.slice(1);\n}\n\n/**\n *  生成驼峰命名法的键名\n * @param key\n * @param parentKey\n */\nfunction toCamelCase(key: string, parentKey: string): string {\n  if (!parentKey) {\n    return key;\n  }\n  return parentKey + key.charAt(0).toUpperCase() + key.slice(1);\n}\n\nfunction kebabToCamelCase(str: string): string {\n  return str\n    .split('-')\n    .filter(Boolean)\n    .map((word, index) =>\n      index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1),\n    )\n    .join('');\n}\n\nexport {\n  capitalizeFirstLetter,\n  kebabToCamelCase,\n  toCamelCase,\n  toLowerCaseFirstLetter,\n};\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/merge.ts",
    "content": "import { createDefu } from 'defu';\n\nexport { createDefu as createMerge, defu as merge } from 'defu';\n\nexport const mergeWithArrayOverride = createDefu((originObj, key, updates) => {\n  if (Array.isArray(originObj[key]) && Array.isArray(updates)) {\n    originObj[key] = updates;\n    return true;\n  }\n});\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/nprogress.ts",
    "content": "import type NProgress from 'nprogress';\n\n// 创建一个NProgress实例的变量，初始值为null\nlet nProgressInstance: null | typeof NProgress = null;\n\n/**\n * 动态加载NProgress库，并进行配置。\n * 此函数首先检查是否已经加载过NProgress库，如果已经加载过，则直接返回NProgress实例。\n * 否则，动态导入NProgress库，进行配置，然后返回NProgress实例。\n *\n * @returns  NProgress实例的Promise对象。\n */\nasync function loadNprogress() {\n  if (nProgressInstance) {\n    return nProgressInstance;\n  }\n  nProgressInstance = await import('nprogress');\n  nProgressInstance.configure({\n    showSpinner: true,\n    speed: 300,\n  });\n  return nProgressInstance;\n}\n\n/**\n * 开始显示进度条。\n * 此函数首先加载NProgress库，然后调用NProgress的start方法开始显示进度条。\n */\nasync function startProgress() {\n  const nprogress = await loadNprogress();\n  nprogress?.start();\n}\n\n/**\n * 停止显示进度条，并隐藏进度条。\n * 此函数首先加载NProgress库，然后调用NProgress的done方法停止并隐藏进度条。\n */\nasync function stopProgress() {\n  const nprogress = await loadNprogress();\n  nprogress?.done();\n}\n\nexport { startProgress, stopProgress };\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/resources.ts",
    "content": "/**\n * 加载js文件\n * @param src js文件地址\n */\nfunction loadScript(src: string) {\n  return new Promise<void>((resolve, reject) => {\n    if (document.querySelector(`script[src=\"${src}\"]`)) {\n      // 如果已经加载过，直接 resolve\n      return resolve();\n    }\n    const script = document.createElement('script');\n    script.src = src;\n    script.addEventListener('load', () => resolve());\n    script.addEventListener('error', () =>\n      reject(new Error(`Failed to load script: ${src}`)),\n    );\n    document.head.append(script);\n  });\n}\n\nexport { loadScript };\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/stack.ts",
    "content": "/**\n * @zh_CN 栈数据结构\n */\nexport class Stack<T> {\n  /**\n   * @zh_CN 栈内元素数量\n   */\n  get size() {\n    return this.items.length;\n  }\n  /**\n   * @zh_CN 是否去重\n   */\n  private readonly dedup: boolean;\n  /**\n   * @zh_CN 栈内元素\n   */\n  private items: T[] = [];\n\n  /**\n   * @zh_CN 栈的最大容量\n   */\n  private readonly maxSize?: number;\n\n  constructor(dedup = true, maxSize?: number) {\n    this.maxSize = maxSize;\n    this.dedup = dedup;\n  }\n\n  /**\n   * @zh_CN 清空栈内元素\n   */\n  clear() {\n    this.items.length = 0;\n  }\n\n  /**\n   * @zh_CN 查看栈顶元素\n   * @returns 栈顶元素\n   */\n  peek(): T | undefined {\n    return this.items[this.items.length - 1];\n  }\n\n  /**\n   * @zh_CN 出栈\n   * @returns 栈顶元素\n   */\n  pop(): T | undefined {\n    return this.items.pop();\n  }\n\n  /**\n   * @zh_CN 入栈\n   * @param items 要入栈的元素\n   */\n  push(...items: T[]) {\n    items.forEach((item) => {\n      // 去重\n      if (this.dedup) {\n        const index = this.items.indexOf(item);\n        if (index !== -1) {\n          this.items.splice(index, 1);\n        }\n      }\n      this.items.push(item);\n      if (this.maxSize && this.items.length > this.maxSize) {\n        this.items.splice(0, this.items.length - this.maxSize);\n      }\n    });\n  }\n  /**\n   * @zh_CN 移除栈内元素\n   * @param itemList 要移除的元素列表\n   */\n  remove(...itemList: T[]) {\n    this.items = this.items.filter((i) => !itemList.includes(i));\n  }\n  /**\n   * @zh_CN 保留栈内元素\n   * @param itemList 要保留的元素列表\n   */\n  retain(itemList: T[]) {\n    this.items = this.items.filter((i) => itemList.includes(i));\n  }\n\n  /**\n   * @zh_CN 转换为数组\n   * @returns 栈内元素数组\n   */\n  toArray(): T[] {\n    return [...this.items];\n  }\n}\n\n/**\n * @zh_CN 创建一个栈实例\n * @param dedup 是否去重\n * @param maxSize 栈的最大容量\n * @returns 栈实例\n */\nexport const createStack = <T>(dedup = true, maxSize?: number) =>\n  new Stack<T>(dedup, maxSize);\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/state-handler.ts",
    "content": "export class StateHandler {\n  private condition: boolean = false;\n  private rejectCondition: (() => void) | null = null;\n  private resolveCondition: (() => void) | null = null;\n\n  isConditionTrue(): boolean {\n    return this.condition;\n  }\n\n  reset() {\n    this.condition = false;\n    this.clearPromises();\n  }\n\n  // 触发状态为 false 时，reject\n  setConditionFalse() {\n    this.condition = false;\n    if (this.rejectCondition) {\n      this.rejectCondition();\n      this.clearPromises();\n    }\n  }\n\n  // 触发状态为 true 时，resolve\n  setConditionTrue() {\n    this.condition = true;\n    if (this.resolveCondition) {\n      this.resolveCondition();\n      this.clearPromises();\n    }\n  }\n\n  // 返回一个 Promise，等待 condition 变为 true\n  waitForCondition(): Promise<void> {\n    return new Promise((resolve, reject) => {\n      if (this.condition) {\n        resolve(); // 如果 condition 已经为 true，立即 resolve\n      } else {\n        this.resolveCondition = resolve;\n        this.rejectCondition = reject;\n      }\n    });\n  }\n\n  // 清理 resolve/reject 函数\n  private clearPromises() {\n    this.resolveCondition = null;\n    this.rejectCondition = null;\n  }\n}\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/to.ts",
    "content": "/**\n * @param { Readonly<Promise> } promise\n * @param {object=} errorExt - Additional Information you can pass to the err object\n * @return { Promise }\n */\nexport async function to<T, U = Error>(\n  promise: Readonly<Promise<T>>,\n  errorExt?: object,\n): Promise<[null, T] | [U, undefined]> {\n  try {\n    const data = await promise;\n    const result: [null, T] = [null, data];\n    return result;\n  } catch (error) {\n    if (errorExt) {\n      const parsedError = Object.assign({}, error, errorExt);\n      return [parsedError as U, undefined];\n    }\n    return [error as U, undefined];\n  }\n}\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/tree.ts",
    "content": "interface TreeConfigOptions {\n  // 子属性的名称，默认为'children'\n  childProps: string;\n}\n\n/**\n * @zh_CN 遍历树形结构，并返回所有节点中指定的值。\n * @param tree 树形结构数组\n * @param getValue 获取节点值的函数\n * @param options 作为子节点数组的可选属性名称。\n * @returns 所有节点中指定的值的数组\n */\nfunction traverseTreeValues<T, V>(\n  tree: T[],\n  getValue: (node: T) => V,\n  options?: TreeConfigOptions,\n): V[] {\n  const result: V[] = [];\n  const { childProps } = options || {\n    childProps: 'children',\n  };\n\n  const dfs = (treeNode: T) => {\n    const value = getValue(treeNode);\n    result.push(value);\n    const children = (treeNode as Record<string, any>)?.[childProps];\n    if (!children) {\n      return;\n    }\n    if (children.length > 0) {\n      for (const child of children) {\n        dfs(child);\n      }\n    }\n  };\n\n  for (const treeNode of tree) {\n    dfs(treeNode);\n  }\n  return result.filter(Boolean);\n}\n\n/**\n * 根据条件过滤给定树结构的节点，并以原有顺序返回所有匹配节点的数组。\n * @param tree 要过滤的树结构的根节点数组。\n * @param filter 用于匹配每个节点的条件。\n * @param options 作为子节点数组的可选属性名称。\n * @returns 包含所有匹配节点的数组。\n */\nfunction filterTree<T extends Record<string, any>>(\n  tree: T[],\n  filter: (node: T) => boolean,\n  options?: TreeConfigOptions,\n): T[] {\n  const { childProps } = options || {\n    childProps: 'children',\n  };\n\n  const _filterTree = (nodes: T[]): T[] => {\n    return nodes.filter((node: Record<string, any>) => {\n      if (filter(node as T)) {\n        if (node[childProps]) {\n          node[childProps] = _filterTree(node[childProps]);\n        }\n        return true;\n      }\n      return false;\n    });\n  };\n\n  return _filterTree(tree);\n}\n\n/**\n * 根据条件重新映射给定树结构的节\n * @param tree 要过滤的树结构的根节点数组。\n * @param mapper 用于map每个节点的条件。\n * @param options 作为子节点数组的可选属性名称。\n */\nfunction mapTree<T, V extends Record<string, any>>(\n  tree: T[],\n  mapper: (node: T) => V,\n  options?: TreeConfigOptions,\n): V[] {\n  const { childProps } = options || {\n    childProps: 'children',\n  };\n  return tree.map((node) => {\n    const mapperNode: Record<string, any> = mapper(node);\n    if (mapperNode[childProps]) {\n      mapperNode[childProps] = mapTree(mapperNode[childProps], mapper, options);\n    }\n    return mapperNode as V;\n  });\n}\n\n/**\n * 对树形结构数据进行递归排序\n * @param treeData - 树形数据数组\n * @param sortFunction - 排序函数，用于定义排序规则\n * @param options - 配置选项，包括子节点属性名\n * @returns 排序后的树形数据\n */\nfunction sortTree<T extends Record<string, any>>(\n  treeData: T[],\n  sortFunction: (a: T, b: T) => number,\n  options?: TreeConfigOptions,\n): T[] {\n  const { childProps } = options || {\n    childProps: 'children',\n  };\n\n  return treeData.toSorted(sortFunction).map((item) => {\n    const children = item[childProps];\n    if (children && Array.isArray(children) && children.length > 0) {\n      return {\n        ...item,\n        [childProps]: sortTree(children, sortFunction, options),\n      };\n    }\n    return item;\n  });\n}\n\nexport { filterTree, mapTree, sortTree, traverseTreeValues };\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/unique.ts",
    "content": "/**\n * 根据指定字段对对象数组进行去重\n * @param arr 要去重的对象数组\n * @param key 去重依据的字段名\n * @returns 去重后的对象数组\n */\nfunction uniqueByField<T>(arr: T[], key: keyof T): T[] {\n  const seen = new Map<any, T>();\n  return arr.filter((item) => {\n    const value = item[key];\n    return seen.has(value) ? false : (seen.set(value, item), true);\n  });\n}\n\nexport { uniqueByField };\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/update-css-variables.ts",
    "content": "/**\n * 更新 CSS 变量的函数\n * @param variables 要更新的 CSS 变量与其新值的映射\n */\nfunction updateCSSVariables(\n  variables: { [key: string]: string },\n  id = '__vben-styles__',\n): void {\n  // 获取或创建内联样式表元素\n  const styleElement =\n    document.querySelector(`#${id}`) || document.createElement('style');\n\n  styleElement.id = id;\n\n  // 构建要更新的 CSS 变量的样式文本\n  let cssText = ':root {';\n  for (const key in variables) {\n    if (Object.prototype.hasOwnProperty.call(variables, key)) {\n      cssText += `${key}: ${variables[key]};`;\n    }\n  }\n  cssText += '}';\n\n  // 将样式文本赋值给内联样式表\n  styleElement.textContent = cssText;\n\n  // 将内联样式表添加到文档头部\n  if (!document.querySelector(`#${id}`)) {\n    setTimeout(() => {\n      document.head.append(styleElement);\n    });\n  }\n}\n\nexport { updateCSSVariables };\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/util.ts",
    "content": "export function bindMethods<T extends object>(instance: T): void {\n  const prototype = Object.getPrototypeOf(instance);\n  const propertyNames = Object.getOwnPropertyNames(prototype);\n\n  propertyNames.forEach((propertyName) => {\n    const descriptor = Object.getOwnPropertyDescriptor(prototype, propertyName);\n    const propertyValue = instance[propertyName as keyof T];\n\n    if (\n      typeof propertyValue === 'function' &&\n      propertyName !== 'constructor' &&\n      descriptor &&\n      !descriptor.get &&\n      !descriptor.set\n    ) {\n      instance[propertyName as keyof T] = propertyValue.bind(instance);\n    }\n  });\n}\n\n/**\n * 获取嵌套对象的字段值\n * @param obj - 要查找的对象\n * @param path - 用于查找字段的路径，使用小数点分隔\n * @returns 字段值，或者未找到时返回 undefined\n */\nexport function getNestedValue<T>(obj: T, path: string): any {\n  if (typeof path !== 'string' || path.length === 0) {\n    throw new Error('Path must be a non-empty string');\n  }\n  // 把路径字符串按 \".\" 分割成数组\n  const keys = path.split('.') as (number | string)[];\n\n  let current: any = obj;\n\n  for (const key of keys) {\n    if (current === null || current === undefined) {\n      return undefined;\n    }\n    current = current[key as keyof typeof current];\n  }\n\n  return current;\n}\n"
  },
  {
    "path": "packages/@core/base/shared/src/utils/window.ts",
    "content": "interface OpenWindowOptions {\n  noopener?: boolean;\n  noreferrer?: boolean;\n  target?: '_blank' | '_parent' | '_self' | '_top' | string;\n}\n\n/**\n * 新窗口打开URL。\n *\n * @param url - 需要打开的网址。\n * @param options - 打开窗口的选项。\n */\nfunction openWindow(url: string, options: OpenWindowOptions = {}): void {\n  // 解构并设置默认值\n  const { noopener = true, noreferrer = true, target = '_blank' } = options;\n\n  // 基于选项创建特性字符串\n  const features = [noopener && 'noopener=yes', noreferrer && 'noreferrer=yes']\n    .filter(Boolean)\n    .join(',');\n\n  // 打开窗口\n  window.open(url, target, features);\n}\n\n/**\n * 在新窗口中打开路由。\n * @param path\n */\nfunction openRouteInNewWindow(path: string) {\n  const { hash, origin } = location;\n  const fullPath = path.startsWith('/') ? path : `/${path}`;\n  const url = `${origin}${hash && !fullPath.startsWith('/#') ? '/#' : ''}${fullPath}`;\n  openWindow(url, { target: '_blank' });\n}\n\nexport { openRouteInNewWindow, openWindow };\n"
  },
  {
    "path": "packages/@core/base/shared/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/library.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/base/shared/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: true,\n  dts: true,\n  entry: {\n    'cache/index': 'src/cache/index.ts',\n    'color/index': 'src/color/index.ts',\n    'constants/index': 'src/constants/index.ts',\n    'global-state': 'src/global-state.ts',\n    store: 'src/store.ts',\n    'utils/index': 'src/utils/index.ts',\n  },\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "packages/@core/base/typings/package.json",
    "content": "{\n  \"name\": \"@vben-core/typings\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@vben-core/base/typings\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm exec tsdown\"\n  },\n  \"files\": [\n    \"dist\",\n    \"vue-router.d.ts\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"production\": \"./src/index.ts\",\n      \"default\": \"./dist/index.mjs\"\n    },\n    \"./vue-router\": {\n      \"types\": \"./vue-router.d.ts\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"default\": \"./dist/index.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/base/typings/src/app.d.ts",
    "content": "type LayoutType =\n  | 'full-content'\n  | 'header-mixed-nav'\n  | 'header-nav'\n  | 'header-sidebar-nav'\n  | 'mixed-nav'\n  | 'sidebar-mixed-nav'\n  | 'sidebar-nav';\n\ntype ThemeModeType = 'auto' | 'dark' | 'light';\n\n/**\n * 偏好设置按钮位置\n * fixed 固定在右侧\n * header 顶栏\n * auto 自动\n */\ntype PreferencesButtonPositionType = 'auto' | 'fixed' | 'header';\n\ntype BuiltinThemeType =\n  | 'custom'\n  | 'deep-blue'\n  | 'deep-green'\n  | 'default'\n  | 'gray'\n  | 'green'\n  | 'neutral'\n  | 'orange'\n  | 'pink'\n  | 'red'\n  | 'rose'\n  | 'sky-blue'\n  | 'slate'\n  | 'stone'\n  | 'violet'\n  | 'yellow'\n  | 'zinc'\n  | (Record<never, never> & string);\n\ntype ContentCompactType = 'compact' | 'wide';\n\ntype LayoutHeaderModeType = 'auto' | 'auto-scroll' | 'fixed' | 'static';\ntype LayoutHeaderMenuAlignType = 'center' | 'end' | 'start';\n\n/**\n * 登录过期模式\n * modal 弹窗模式\n * page 页面模式\n */\ntype LoginExpiredModeType = 'modal' | 'page';\n\n/**\n * 面包屑样式\n * background 背景\n * normal 默认\n */\ntype BreadcrumbStyleType = 'background' | 'normal';\n\n/**\n * 权限模式\n * backend 后端权限模式\n * frontend 前端权限模式\n * mixed 混合权限模式\n */\ntype AccessModeType = 'backend' | 'frontend' | 'mixed';\n\n/**\n * 导航风格\n * plain 朴素\n * rounded 圆润\n */\ntype NavigationStyleType = 'plain' | 'rounded';\n\n/**\n * 标签栏风格\n * brisk 轻快\n * card 卡片\n * chrome 谷歌\n * plain 朴素\n */\ntype TabsStyleType = 'brisk' | 'card' | 'chrome' | 'plain';\n\n/**\n * 页面切换动画\n */\ntype PageTransitionType = 'fade' | 'fade-down' | 'fade-slide' | 'fade-up';\n\n/**\n * 页面切换动画\n * panel-center 居中布局\n * panel-left 居左布局\n * panel-right 居右布局\n */\ntype AuthPageLayoutType = 'panel-center' | 'panel-left' | 'panel-right';\n\n/**\n * 时区选项\n */\ninterface TimezoneOption {\n  label: string;\n  offset: number;\n  timezone: string;\n}\n\nexport type {\n  AccessModeType,\n  AuthPageLayoutType,\n  BreadcrumbStyleType,\n  BuiltinThemeType,\n  ContentCompactType,\n  LayoutHeaderMenuAlignType,\n  LayoutHeaderModeType,\n  LayoutType,\n  LoginExpiredModeType,\n  NavigationStyleType,\n  PageTransitionType,\n  PreferencesButtonPositionType,\n  TabsStyleType,\n  ThemeModeType,\n  TimezoneOption,\n};\n"
  },
  {
    "path": "packages/@core/base/typings/src/basic.d.ts",
    "content": "interface BasicOption {\n  label: string;\n  value: string;\n}\n\ntype SelectOption = BasicOption;\n\ntype TabOption = BasicOption;\n\ninterface BasicUserInfo {\n  /**\n   * 头像\n   */\n  avatar: string;\n  /**\n   * 用户昵称\n   */\n  realName: string;\n  /**\n   * 用户角色\n   */\n  roles?: string[];\n  /**\n   * 用户id\n   */\n  userId: string;\n  /**\n   * 用户名\n   */\n  username: string;\n}\n\ntype ClassType =\n  | Array<ClassType>\n  | boolean\n  | null\n  | object\n  | string\n  | undefined;\n\nexport type { BasicOption, BasicUserInfo, ClassType, SelectOption, TabOption };\n"
  },
  {
    "path": "packages/@core/base/typings/src/helper.d.ts",
    "content": "import type { ComputedRef, MaybeRef } from 'vue';\n\n/**\n * 类型级递归中增加深度计数\n */\ntype Increment<A extends unknown[]> = [...A, unknown];\n/**\n * 深层递归所有属性为可选\n */\ntype DeepPartial<\n  T,\n  D extends number = 10,\n  C extends unknown[] = [],\n> = C['length'] extends D\n  ? T\n  : T extends object\n    ? {\n        [P in keyof T]?: DeepPartial<T[P], D, Increment<C>>;\n      }\n    : T;\n\n/**\n * 深层递归所有属性为只读\n */\ntype DeepReadonly<\n  T,\n  D extends number = 10,\n  C extends unknown[] = [],\n> = C['length'] extends D\n  ? T\n  : T extends object\n    ? {\n        readonly [P in keyof T]: DeepReadonly<T[P], D, Increment<C>>;\n      }\n    : T;\n\n/**\n * 任意类型的异步函数\n */\n\ntype AnyPromiseFunction<T extends any[] = any[], R = void> = (\n  ...arg: T\n) => PromiseLike<R>;\n\n/**\n * 任意类型的普通函数\n */\ntype AnyNormalFunction<T extends any[] = any[], R = void> = (...arg: T) => R;\n\n/**\n * 任意类型的函数\n */\ntype AnyFunction<T extends any[] = any[], R = void> =\n  | AnyNormalFunction<T, R>\n  | AnyPromiseFunction<T, R>;\n\n/**\n *  T | null 包装\n */\ntype Nullable<T> = null | T;\n\n/**\n * T | Not null 包装\n */\ntype NonNullable<T> = T extends null | undefined ? never : T;\n\n/**\n * 字符串类型对象\n */\ntype Recordable<T> = Record<string, T>;\n\n/**\n * 字符串类型对象（只读）\n */\ninterface ReadonlyRecordable<T = any> {\n  readonly [key: string]: T;\n}\n\n/**\n * setTimeout 返回值类型\n */\ntype TimeoutHandle = ReturnType<typeof setTimeout>;\n\n/**\n * setInterval 返回值类型\n */\ntype IntervalHandle = ReturnType<typeof setInterval>;\n\n/**\n * 也许它是一个计算的 ref，或者一个 getter 函数\n *\n */\ntype MaybeReadonlyRef<T> = (() => T) | ComputedRef<T>;\n\n/**\n * 也许它是一个 ref，或者一个普通值，或者一个 getter 函数\n *\n */\ntype MaybeComputedRef<T> = MaybeReadonlyRef<T> | MaybeRef<T>;\n\ntype Merge<O extends object, T extends object> = {\n  [K in keyof O | keyof T]: K extends keyof T\n    ? T[K]\n    : K extends keyof O\n      ? O[K]\n      : never;\n};\n\n/**\n * T = [\n *  { name: string; age: number; },\n *  { sex: 'male' | 'female'; age: string }\n * ]\n * =>\n * MergeAll<T> = {\n *  name: string;\n *  sex: 'male' | 'female';\n *  age: string\n * }\n */\ntype MergeAll<\n  T extends object[],\n  R extends object = Record<string, any>,\n> = T extends [infer F extends object, ...infer Rest extends object[]]\n  ? MergeAll<Rest, Merge<R, F>>\n  : R;\n\ntype EmitType = (name: Name, ...args: any[]) => void;\n\ntype MaybePromise<T> = Promise<T> | T;\n\nexport type {\n  AnyFunction,\n  AnyNormalFunction,\n  AnyPromiseFunction,\n  DeepPartial,\n  DeepReadonly,\n  EmitType,\n  IntervalHandle,\n  MaybeComputedRef,\n  MaybePromise,\n  MaybeReadonlyRef,\n  Merge,\n  MergeAll,\n  NonNullable,\n  Nullable,\n  ReadonlyRecordable,\n  Recordable,\n  TimeoutHandle,\n};\n"
  },
  {
    "path": "packages/@core/base/typings/src/index.ts",
    "content": "export type * from './app';\nexport type * from './basic';\nexport type * from './helper';\nexport type * from './menu-record';\nexport type * from './tabs';\nexport type * from './vue-router';\n"
  },
  {
    "path": "packages/@core/base/typings/src/menu-record.ts",
    "content": "import type { Component } from 'vue';\nimport type { RouteRecordRaw } from 'vue-router';\n\nimport type { Recordable } from './helper';\n\n/**\n * 扩展路由原始对象\n */\ntype ExRouteRecordRaw = RouteRecordRaw & {\n  parent?: string;\n  parents?: string[];\n  path?: any;\n};\n\ninterface MenuRecordBadgeRaw {\n  /**\n   * 徽标\n   */\n  badge?: string;\n  /**\n   * 徽标类型\n   */\n  badgeType?: 'dot' | 'normal';\n  /**\n   * 徽标颜色\n   */\n  badgeVariants?: 'destructive' | 'primary' | string;\n}\n\n/**\n * 菜单原始对象\n */\ninterface MenuRecordRaw extends MenuRecordBadgeRaw {\n  /**\n   * 激活时的图标名\n   */\n  activeIcon?: string;\n  /**\n   * 子菜单\n   */\n  children?: MenuRecordRaw[];\n  /**\n   * 是否禁用菜单\n   * @default false\n   */\n  disabled?: boolean;\n  /**\n   * 图标名\n   */\n  icon?: Component | string;\n  /**\n   * 菜单名\n   */\n  name: string;\n  /**\n   * 排序号\n   */\n  order?: number;\n  /**\n   * 父级路径\n   */\n  parent?: string;\n  /**\n   * 所有父级路径\n   */\n  parents?: string[];\n  /**\n   * 菜单路径，唯一，可当作key\n   */\n  path: string;\n  /**\n   * 菜单参数\n   */\n  query?: Recordable<any>;\n  /**\n   * 是否显示菜单\n   * @default true\n   */\n  show?: boolean;\n}\n\nexport type { ExRouteRecordRaw, MenuRecordBadgeRaw, MenuRecordRaw };\n"
  },
  {
    "path": "packages/@core/base/typings/src/tabs.ts",
    "content": "import type { RouteLocationNormalized } from 'vue-router';\n\nexport interface TabDefinition extends RouteLocationNormalized {\n  /**\n   * 标签页的key\n   */\n  key?: string;\n}\n"
  },
  {
    "path": "packages/@core/base/typings/src/vue-router.d.ts",
    "content": "import type { Component } from 'vue';\nimport type { Router, RouteRecordRaw } from 'vue-router';\n\ninterface RouteMeta {\n  /**\n   * 激活图标（菜单/tab）\n   */\n  activeIcon?: string;\n  /**\n   * 当前激活的菜单，有时候不想激活现有菜单，需要激活父级菜单时使用\n   */\n  activePath?: string;\n  /**\n   * 是否固定标签页\n   * @default false\n   */\n  affixTab?: boolean;\n  /**\n   * 固定标签页的顺序\n   * @default 0\n   */\n  affixTabOrder?: number;\n  /**\n   * 需要特定的角色标识才可以访问\n   * @default []\n   */\n  authority?: string[];\n  /**\n   * 徽标\n   */\n  badge?: string;\n  /**\n   * 徽标类型\n   */\n  badgeType?: 'dot' | 'normal';\n  /**\n   * 徽标颜色\n   */\n  badgeVariants?:\n    | 'default'\n    | 'destructive'\n    | 'primary'\n    | 'success'\n    | 'warning'\n    | string;\n  /**\n   * 路由对应dom是否缓存起来\n   */\n  domCached?: boolean;\n  /**\n   * 路由的完整路径作为key（默认true）\n   */\n  fullPathKey?: boolean;\n  /**\n   * 当前路由的子级在菜单中不展现\n   * @default false\n   */\n  hideChildrenInMenu?: boolean;\n  /**\n   * 当前路由在面包屑中不展现\n   * @default false\n   */\n  hideInBreadcrumb?: boolean;\n  /**\n   * 当前路由在菜单中不展现\n   * @default false\n   */\n  hideInMenu?: boolean;\n  /**\n   * 当前路由在标签页不展现\n   * @default false\n   */\n  hideInTab?: boolean;\n  /**\n   * 图标（菜单/tab）\n   */\n  icon?: Component | string;\n  /**\n   * iframe 地址\n   */\n  iframeSrc?: string;\n  /**\n   * 忽略权限，直接可以访问\n   * @default false\n   */\n  ignoreAccess?: boolean;\n  /**\n   * 开启KeepAlive缓存\n   */\n  keepAlive?: boolean;\n  /**\n   * 外链-跳转路径\n   */\n  link?: string;\n  /**\n   * 路由是否已经加载过\n   */\n  loaded?: boolean;\n  /**\n   * 标签页最大打开数量\n   * @default -1\n   */\n  maxNumOfOpenTab?: number;\n  /**\n   * 菜单可以看到，但是访问会被重定向到403\n   */\n  menuVisibleWithForbidden?: boolean;\n  /**\n   * 不使用基础布局（仅在顶级生效）\n   */\n  noBasicLayout?: boolean;\n  /**\n   * 在新窗口打开\n   */\n  openInNewWindow?: boolean;\n  /**\n   * 用于路由->菜单排序\n   */\n  order?: number;\n  /**\n   * 菜单所携带的参数\n   */\n  query?: Recordable;\n  /**\n   * 标题名称\n   */\n  title: string;\n}\n\n// 定义递归类型以将 RouteRecordRaw 的 component 属性更改为 string\ntype RouteRecordStringComponent<T = string> = Omit<\n  RouteRecordRaw,\n  'children' | 'component'\n> & {\n  children?: RouteRecordStringComponent<T>[];\n  component: T;\n};\n\ntype ComponentRecordType = Record<string, () => Promise<Component>>;\n\ninterface GenerateMenuAndRoutesOptions {\n  fetchMenuListAsync?: () => Promise<RouteRecordStringComponent[]>;\n  forbiddenComponent?: RouteRecordRaw['component'];\n  layoutMap?: ComponentRecordType;\n  pageMap?: ComponentRecordType;\n  roles?: string[];\n  router: Router;\n  routes: RouteRecordRaw[];\n}\n\nexport type {\n  ComponentRecordType,\n  GenerateMenuAndRoutesOptions,\n  RouteMeta,\n  RouteRecordRaw,\n  RouteRecordStringComponent,\n};\n"
  },
  {
    "path": "packages/@core/base/typings/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/library.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/base/typings/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: true,\n  dts: true,\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "packages/@core/base/typings/vue-router.d.ts",
    "content": "import type { RouteMeta as IRouteMeta } from './dist/index.d.mts';\n\nimport 'vue-router';\n\ndeclare module 'vue-router' {\n  // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n  interface RouteMeta extends IRouteMeta {}\n}\n"
  },
  {
    "path": "packages/@core/composables/package.json",
    "content": "{\n  \"name\": \"@vben-core/composables\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@core/composables\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm exec tsdown\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"sideEffects\": false,\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"production\": \"./src/index.ts\",\n      \"default\": \"./dist/index.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"default\": \"./dist/index.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"reka-ui\": \"catalog:\",\n    \"sortablejs\": \"catalog:\",\n    \"vue\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/sortablejs\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/composables/src/__tests__/use-sortable.test.ts",
    "content": "import type { SortableOptions } from 'sortablejs';\n\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { useSortable } from '../use-sortable';\n\ndescribe('useSortable', () => {\n  beforeEach(() => {\n    vi.mock('sortablejs/modular/sortable.complete.esm.js', () => ({\n      default: {\n        create: vi.fn(),\n      },\n    }));\n  });\n  it('should call Sortable.create with the correct options', async () => {\n    // Create a mock element\n    const mockElement = document.createElement('div') as HTMLDivElement;\n\n    // Define custom options\n    const customOptions: SortableOptions = {\n      group: 'test-group',\n      sort: false,\n    };\n\n    // Use the useSortable function\n    const { initializeSortable } = useSortable(mockElement, customOptions);\n\n    // Initialize sortable\n    await initializeSortable();\n\n    // Import sortablejs to access the mocked create function\n    const Sortable =\n      // @ts-expect-error - This is a dynamic import\n      await import('sortablejs/modular/sortable.complete.esm.js');\n\n    // Verify that Sortable.create was called with the correct parameters\n    expect(Sortable.default.create).toHaveBeenCalledTimes(1);\n    expect(Sortable.default.create).toHaveBeenCalledWith(\n      mockElement,\n      expect.objectContaining({\n        animation: 300,\n        delay: 400,\n        delayOnTouchOnly: true,\n        ...customOptions,\n      }),\n    );\n  });\n});\n"
  },
  {
    "path": "packages/@core/composables/src/index.ts",
    "content": "export * from './use-is-mobile';\nexport * from './use-layout-style';\nexport * from './use-namespace';\nexport * from './use-priority-value';\nexport * from './use-scroll-lock';\nexport * from './use-simple-locale';\nexport * from './use-sortable';\nexport {\n  useEmitAsProps,\n  useForwardExpose,\n  useForwardProps,\n  useForwardPropsEmits,\n} from 'reka-ui';\n"
  },
  {
    "path": "packages/@core/composables/src/use-is-mobile.ts",
    "content": "import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';\n\nexport function useIsMobile() {\n  const breakpoints = useBreakpoints(breakpointsTailwind);\n  const isMobile = breakpoints.smaller('md');\n  return { isMobile };\n}\n"
  },
  {
    "path": "packages/@core/composables/src/use-layout-style.ts",
    "content": "import type { CSSProperties } from 'vue';\n\nimport type { VisibleDomRect } from '@vben-core/shared/utils';\n\nimport { computed, onMounted, onUnmounted, ref } from 'vue';\n\nimport {\n  CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT,\n  CSS_VARIABLE_LAYOUT_CONTENT_WIDTH,\n  CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT,\n  CSS_VARIABLE_LAYOUT_HEADER_HEIGHT,\n} from '@vben-core/shared/constants';\nimport { getElementVisibleRect } from '@vben-core/shared/utils';\n\nimport { useCssVar, useDebounceFn } from '@vueuse/core';\n\n/**\n * @zh_CN content style\n */\nexport function useLayoutContentStyle() {\n  let resizeObserver: null | ResizeObserver = null;\n  const contentElement = ref<HTMLDivElement | null>(null);\n  const visibleDomRect = ref<null | VisibleDomRect>(null);\n  const contentHeight = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT);\n  const contentWidth = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_WIDTH);\n\n  const overlayStyle = computed((): CSSProperties => {\n    const { height, left, top, width } = visibleDomRect.value ?? {};\n    return {\n      height: `${height}px`,\n      left: `${left}px`,\n      position: 'fixed',\n      top: `${top}px`,\n      width: `${width}px`,\n      zIndex: 150,\n    };\n  });\n\n  const debouncedCalcHeight = useDebounceFn(\n    (_entries: ResizeObserverEntry[]) => {\n      visibleDomRect.value = getElementVisibleRect(contentElement.value);\n      contentHeight.value = `${visibleDomRect.value.height}px`;\n      contentWidth.value = `${visibleDomRect.value.width}px`;\n    },\n    16,\n  );\n\n  onMounted(() => {\n    if (contentElement.value && !resizeObserver) {\n      resizeObserver = new ResizeObserver(debouncedCalcHeight);\n      resizeObserver.observe(contentElement.value);\n    }\n  });\n\n  onUnmounted(() => {\n    resizeObserver?.disconnect();\n    resizeObserver = null;\n  });\n\n  return { contentElement, overlayStyle, visibleDomRect };\n}\n\nexport function useLayoutHeaderStyle() {\n  const headerHeight = useCssVar(CSS_VARIABLE_LAYOUT_HEADER_HEIGHT);\n\n  return {\n    getLayoutHeaderHeight: () => {\n      return Number.parseInt(`${headerHeight.value}`, 10);\n    },\n    setLayoutHeaderHeight: (height: number) => {\n      headerHeight.value = `${height}px`;\n    },\n  };\n}\n\nexport function useLayoutFooterStyle() {\n  const footerHeight = useCssVar(CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT);\n\n  return {\n    getLayoutFooterHeight: () => {\n      return Number.parseInt(`${footerHeight.value}`, 10);\n    },\n    setLayoutFooterHeight: (height: number) => {\n      footerHeight.value = `${height}px`;\n    },\n  };\n}\n"
  },
  {
    "path": "packages/@core/composables/src/use-namespace.ts",
    "content": "import { DEFAULT_NAMESPACE } from '@vben-core/shared/constants';\n\n/**\n * @see copy https://github.com/element-plus/element-plus/blob/dev/packages/hooks/use-namespace/index.ts\n */\n\nconst statePrefix = 'is-';\n\nconst _bem = (\n  namespace: string,\n  block: string,\n  blockSuffix: string,\n  element: string,\n  modifier: string,\n) => {\n  let cls = `${namespace}-${block}`;\n  if (blockSuffix) {\n    cls += `-${blockSuffix}`;\n  }\n  if (element) {\n    cls += `__${element}`;\n  }\n  if (modifier) {\n    cls += `--${modifier}`;\n  }\n  return cls;\n};\n\nconst is: {\n  (name: string): string;\n  // eslint-disable-next-line @typescript-eslint/unified-signatures\n  (name: string, state: boolean | undefined): string;\n} = (name: string, ...args: [] | [boolean | undefined]) => {\n  const state = args.length > 0 ? args[0] : true;\n  return name && state ? `${statePrefix}${name}` : '';\n};\n\nconst useNamespace = (block: string) => {\n  const namespace = DEFAULT_NAMESPACE;\n  const b = (blockSuffix = '') => _bem(namespace, block, blockSuffix, '', '');\n  const e = (element?: string) =>\n    element ? _bem(namespace, block, '', element, '') : '';\n  const m = (modifier?: string) =>\n    modifier ? _bem(namespace, block, '', '', modifier) : '';\n  const be = (blockSuffix?: string, element?: string) =>\n    blockSuffix && element\n      ? _bem(namespace, block, blockSuffix, element, '')\n      : '';\n  const em = (element?: string, modifier?: string) =>\n    element && modifier ? _bem(namespace, block, '', element, modifier) : '';\n  const bm = (blockSuffix?: string, modifier?: string) =>\n    blockSuffix && modifier\n      ? _bem(namespace, block, blockSuffix, '', modifier)\n      : '';\n  const bem = (blockSuffix?: string, element?: string, modifier?: string) =>\n    blockSuffix && element && modifier\n      ? _bem(namespace, block, blockSuffix, element, modifier)\n      : '';\n\n  // for css var\n  // --el-xxx: value;\n  const cssVar = (object: Record<string, string>) => {\n    const styles: Record<string, string> = {};\n    for (const key in object) {\n      if (object[key]) {\n        styles[`--${namespace}-${key}`] = object[key];\n      }\n    }\n    return styles;\n  };\n  // with block\n  const cssVarBlock = (object: Record<string, string>) => {\n    const styles: Record<string, string> = {};\n    for (const key in object) {\n      if (object[key]) {\n        styles[`--${namespace}-${block}-${key}`] = object[key];\n      }\n    }\n    return styles;\n  };\n\n  const cssVarName = (name: string) => `--${namespace}-${name}`;\n  const cssVarBlockName = (name: string) => `--${namespace}-${block}-${name}`;\n\n  return {\n    b,\n    be,\n    bem,\n    bm,\n    // css\n    cssVar,\n    cssVarBlock,\n    cssVarBlockName,\n    cssVarName,\n    e,\n    em,\n    is,\n    m,\n    namespace,\n  };\n};\n\ntype UseNamespaceReturn = ReturnType<typeof useNamespace>;\n\nexport type { UseNamespaceReturn };\nexport { useNamespace };\n"
  },
  {
    "path": "packages/@core/composables/src/use-priority-value.ts",
    "content": "import type { ComputedRef, Ref } from 'vue';\n\nimport { computed, getCurrentInstance, unref, useAttrs, useSlots } from 'vue';\n\nimport {\n  getFirstNonNullOrUndefined,\n  kebabToCamelCase,\n} from '@vben-core/shared/utils';\n\n/**\n * 依次从插槽、attrs、props、state 中获取值\n * @param key\n * @param props\n * @param state\n */\nexport function usePriorityValue<\n  T extends Record<string, any>,\n  S extends Record<string, any>,\n  K extends keyof T = keyof T,\n>(key: K, props: T, state: Readonly<Ref<NoInfer<S>>> | undefined) {\n  const instance = getCurrentInstance();\n  const slots = useSlots();\n  const attrs = useAttrs() as T;\n\n  const value = computed((): T[K] => {\n    // props不管有没有传，都会有默认值，会影响这里的顺序，\n    // 通过判断原始props是否有值来判断是否传入\n    const rawProps = (instance?.vnode?.props || {}) as T;\n\n    const standardRawProps = {} as T;\n\n    for (const [key, value] of Object.entries(rawProps)) {\n      standardRawProps[kebabToCamelCase(key) as K] = value;\n    }\n    const propsKey =\n      standardRawProps?.[key] === undefined ? undefined : props[key];\n\n    // slot可以关闭\n    return getFirstNonNullOrUndefined(\n      slots[key as string],\n      attrs[key],\n      propsKey,\n      state?.value?.[key as keyof S],\n    ) as T[K];\n  });\n\n  return value;\n}\n\n/**\n * 批量获取state中的值（每个值都是ref）\n * @param props\n * @param state\n */\nexport function usePriorityValues<\n  T extends Record<string, any>,\n  S extends Ref<Record<string, any>> = Readonly<Ref<NoInfer<T>, NoInfer<T>>>,\n>(props: T, state: S | undefined) {\n  const result: { [K in keyof T]: ComputedRef<T[K]> } = {} as never;\n\n  (Object.keys(props) as (keyof T)[]).forEach((key) => {\n    result[key] = usePriorityValue(key as keyof typeof props, props, state);\n  });\n\n  return result;\n}\n\n/**\n * 批量获取state中的值（集中在一个computed，用于透传）\n * @param props\n * @param state\n */\nexport function useForwardPriorityValues<\n  T extends Record<string, any>,\n  S extends Ref<Record<string, any>> = Readonly<Ref<NoInfer<T>, NoInfer<T>>>,\n>(props: T, state: S | undefined) {\n  const computedResult: { [K in keyof T]: ComputedRef<T[K]> } = {} as never;\n\n  (Object.keys(props) as (keyof T)[]).forEach((key) => {\n    computedResult[key] = usePriorityValue(\n      key as keyof typeof props,\n      props,\n      state,\n    );\n  });\n\n  return computed(() => {\n    const unwrapResult: Record<string, any> = {};\n    Object.keys(props).forEach((key) => {\n      unwrapResult[key] = unref(computedResult[key]);\n    });\n    return unwrapResult as { [K in keyof T]: T[K] };\n  });\n}\n"
  },
  {
    "path": "packages/@core/composables/src/use-scroll-lock.ts",
    "content": "import { getScrollbarWidth, needsScrollbar } from '@vben-core/shared/utils';\n\nimport {\n  useScrollLock as _useScrollLock,\n  tryOnBeforeUnmount,\n  tryOnMounted,\n} from '@vueuse/core';\n\nexport const SCROLL_FIXED_CLASS = `_scroll__fixed_`;\n\nexport function useScrollLock() {\n  const isLocked = _useScrollLock(document.body);\n  const scrollbarWidth = getScrollbarWidth();\n\n  tryOnMounted(() => {\n    if (!needsScrollbar()) {\n      return;\n    }\n    document.body.style.paddingRight = `${scrollbarWidth}px`;\n\n    const layoutFixedNodes = document.querySelectorAll<HTMLElement>(\n      `.${SCROLL_FIXED_CLASS}`,\n    );\n    const nodes = [...layoutFixedNodes];\n    if (nodes.length > 0) {\n      nodes.forEach((node) => {\n        node.dataset.transition = node.style.transition;\n        node.style.transition = 'none';\n        node.style.paddingRight = `${scrollbarWidth}px`;\n      });\n    }\n    isLocked.value = true;\n  });\n\n  tryOnBeforeUnmount(() => {\n    if (!needsScrollbar()) {\n      return;\n    }\n    isLocked.value = false;\n    const layoutFixedNodes = document.querySelectorAll<HTMLElement>(\n      `.${SCROLL_FIXED_CLASS}`,\n    );\n    const nodes = [...layoutFixedNodes];\n    if (nodes.length > 0) {\n      nodes.forEach((node) => {\n        node.style.paddingRight = '';\n        requestAnimationFrame(() => {\n          node.style.transition = node.dataset.transition || '';\n        });\n      });\n    }\n    document.body.style.paddingRight = '';\n  });\n}\n"
  },
  {
    "path": "packages/@core/composables/src/use-simple-locale/README.md",
    "content": "# Simple i18n\n\nSimple i18 implementation\n"
  },
  {
    "path": "packages/@core/composables/src/use-simple-locale/index.ts",
    "content": "import type { Locale } from './messages';\n\nimport { computed, ref } from 'vue';\n\nimport { createSharedComposable } from '@vueuse/core';\n\nimport { getMessages } from './messages';\n\nexport const useSimpleLocale = createSharedComposable(() => {\n  const currentLocale = ref<Locale>('zh-CN');\n\n  const setSimpleLocale = (locale: Locale) => {\n    currentLocale.value = locale;\n  };\n\n  const $t = computed(() => {\n    const localeMessages = getMessages(currentLocale.value);\n    return (key: string) => {\n      return localeMessages[key] || key;\n    };\n  });\n  return {\n    $t,\n    currentLocale,\n    setSimpleLocale,\n  };\n});\n"
  },
  {
    "path": "packages/@core/composables/src/use-simple-locale/messages.ts",
    "content": "export type Locale = 'en-US' | 'zh-CN';\n\nexport const messages: Record<Locale, Record<string, string>> = {\n  'en-US': {\n    cancel: 'Cancel',\n    collapse: 'Collapse',\n    confirm: 'Confirm',\n    expand: 'Expand',\n    prompt: 'Prompt',\n    reset: 'Reset',\n    submit: 'Submit',\n  },\n  'zh-CN': {\n    cancel: '取消',\n    collapse: '收起',\n    confirm: '确认',\n    expand: '展开',\n    prompt: '提示',\n    reset: '重置',\n    submit: '提交',\n  },\n};\n\nexport const getMessages = (locale: Locale) => messages[locale];\n"
  },
  {
    "path": "packages/@core/composables/src/use-sortable.ts",
    "content": "import type { SortableOptions } from 'sortablejs';\nimport type Sortable from 'sortablejs';\n\nfunction useSortable<T extends HTMLElement>(\n  sortableContainer: T,\n  options: SortableOptions = {},\n) {\n  const initializeSortable = async () => {\n    const Sortable = await import(\n      // @ts-expect-error - This is a dynamic import\n      'sortablejs/modular/sortable.complete.esm.js'\n    );\n    const sortable = Sortable?.default?.create?.(sortableContainer, {\n      animation: 300,\n      delay: 400,\n      delayOnTouchOnly: true,\n      ...options,\n    });\n    return sortable as Sortable;\n  };\n\n  return {\n    initializeSortable,\n  };\n}\n\nexport { useSortable };\n\nexport type { Sortable };\n"
  },
  {
    "path": "packages/@core/composables/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/library.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/composables/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: true,\n  dts: true,\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`defaultPreferences immutability test > should not modify the config object 1`] = `\n{\n  \"app\": {\n    \"accessMode\": \"frontend\",\n    \"authPageLayout\": \"panel-right\",\n    \"checkUpdatesInterval\": 1,\n    \"colorGrayMode\": false,\n    \"colorWeakMode\": false,\n    \"compact\": false,\n    \"contentCompact\": \"wide\",\n    \"contentCompactWidth\": 1200,\n    \"contentPadding\": 0,\n    \"contentPaddingBottom\": 0,\n    \"contentPaddingLeft\": 0,\n    \"contentPaddingRight\": 0,\n    \"contentPaddingTop\": 0,\n    \"defaultAvatar\": \"https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp\",\n    \"defaultHomePath\": \"/analytics\",\n    \"dynamicTitle\": true,\n    \"enableCheckUpdates\": true,\n    \"enableCopyPreferences\": true,\n    \"enablePreferences\": true,\n    \"enableRefreshToken\": false,\n    \"enableStickyPreferencesNavigationBar\": true,\n    \"isMobile\": false,\n    \"layout\": \"sidebar-nav\",\n    \"locale\": \"zh-CN\",\n    \"loginExpiredMode\": \"page\",\n    \"name\": \"Vben Admin\",\n    \"preferencesButtonPosition\": \"auto\",\n    \"watermark\": false,\n    \"watermarkContent\": \"\",\n    \"zIndex\": 200,\n  },\n  \"breadcrumb\": {\n    \"enable\": true,\n    \"hideOnlyOne\": false,\n    \"showHome\": false,\n    \"showIcon\": true,\n    \"styleType\": \"normal\",\n  },\n  \"copyright\": {\n    \"companyName\": \"Vben\",\n    \"companySiteLink\": \"https://www.vben.pro\",\n    \"date\": \"2024\",\n    \"enable\": true,\n    \"icp\": \"\",\n    \"icpLink\": \"\",\n    \"settingShow\": true,\n  },\n  \"footer\": {\n    \"enable\": false,\n    \"fixed\": false,\n    \"height\": 32,\n  },\n  \"header\": {\n    \"enable\": true,\n    \"height\": 50,\n    \"hidden\": false,\n    \"menuAlign\": \"start\",\n    \"mode\": \"fixed\",\n  },\n  \"logo\": {\n    \"enable\": true,\n    \"fit\": \"contain\",\n    \"source\": \"https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp\",\n  },\n  \"navigation\": {\n    \"accordion\": true,\n    \"split\": true,\n    \"styleType\": \"rounded\",\n  },\n  \"shortcutKeys\": {\n    \"enable\": true,\n    \"globalLockScreen\": true,\n    \"globalLogout\": true,\n    \"globalPreferences\": true,\n    \"globalSearch\": true,\n  },\n  \"sidebar\": {\n    \"autoActivateChild\": false,\n    \"collapseWidth\": 60,\n    \"collapsed\": false,\n    \"collapsedButton\": true,\n    \"collapsedShowTitle\": false,\n    \"draggable\": true,\n    \"enable\": true,\n    \"expandOnHover\": true,\n    \"extraCollapse\": false,\n    \"extraCollapsedWidth\": 60,\n    \"fixedButton\": true,\n    \"hidden\": false,\n    \"mixedWidth\": 80,\n    \"width\": 224,\n  },\n  \"tabbar\": {\n    \"draggable\": true,\n    \"enable\": true,\n    \"height\": 38,\n    \"keepAlive\": true,\n    \"maxCount\": 0,\n    \"middleClickToClose\": false,\n    \"persist\": true,\n    \"showIcon\": true,\n    \"showMaximize\": true,\n    \"showMore\": true,\n    \"showRefresh\": true,\n    \"styleType\": \"chrome\",\n    \"visitHistory\": true,\n    \"wheelable\": true,\n  },\n  \"theme\": {\n    \"builtinType\": \"default\",\n    \"colorDestructive\": \"hsl(348 100% 61%)\",\n    \"colorPrimary\": \"hsl(212 100% 45%)\",\n    \"colorSuccess\": \"hsl(144 57% 58%)\",\n    \"colorWarning\": \"hsl(42 84% 61%)\",\n    \"fontSize\": 16,\n    \"mode\": \"dark\",\n    \"radius\": \"0.5\",\n    \"semiDarkHeader\": false,\n    \"semiDarkSidebar\": false,\n    \"semiDarkSidebarSub\": false,\n  },\n  \"transition\": {\n    \"enable\": true,\n    \"loading\": true,\n    \"name\": \"fade-slide\",\n    \"progress\": true,\n  },\n  \"widget\": {\n    \"fullscreen\": true,\n    \"globalSearch\": true,\n    \"languageToggle\": true,\n    \"lockScreen\": true,\n    \"notification\": true,\n    \"refresh\": true,\n    \"sidebarToggle\": true,\n    \"themeToggle\": true,\n    \"timezone\": true,\n  },\n}\n`;\n"
  },
  {
    "path": "packages/@core/preferences/__tests__/config.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { defaultPreferences } from '../src/config';\n\ndescribe('defaultPreferences immutability test', () => {\n  // 创建快照，确保默认配置对象不被修改\n  it('should not modify the config object', () => {\n    expect(defaultPreferences).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "packages/@core/preferences/__tests__/preferences.test.ts",
    "content": "import { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { defaultPreferences } from '../src/config';\nimport { PreferenceManager } from '../src/preferences';\nimport { isDarkTheme } from '../src/update-css-variables';\n\ndescribe('preferences', () => {\n  let preferenceManager: PreferenceManager;\n\n  // 模拟 window.matchMedia 方法\n  vi.stubGlobal(\n    'matchMedia',\n    vi.fn().mockImplementation((query) => ({\n      addEventListener: vi.fn(),\n      addListener: vi.fn(), // Deprecated\n      dispatchEvent: vi.fn(),\n      matches: query === '(prefers-color-scheme: dark)',\n      media: query,\n      onchange: null,\n      removeEventListener: vi.fn(),\n      removeListener: vi.fn(), // Deprecated\n    })),\n  );\n  beforeEach(() => {\n    preferenceManager = new PreferenceManager();\n  });\n\n  it('loads default preferences if no saved preferences found', () => {\n    const preferences = preferenceManager.getPreferences();\n    expect(preferences).toEqual(defaultPreferences);\n  });\n\n  it('initializes preferences with overrides', async () => {\n    const overrides: any = {\n      app: {\n        locale: 'en-US',\n      },\n    };\n    await preferenceManager.initPreferences({\n      namespace: 'testNamespace',\n      overrides,\n    });\n\n    // 等待防抖动操作完成\n    // await new Promise((resolve) => setTimeout(resolve, 300)); // 等待100毫秒\n\n    const expected = {\n      ...defaultPreferences,\n      app: {\n        ...defaultPreferences.app,\n        ...overrides.app,\n      },\n    };\n\n    expect(preferenceManager.getPreferences()).toEqual(expected);\n  });\n\n  it('updates theme mode correctly', () => {\n    preferenceManager.updatePreferences({\n      theme: {\n        mode: 'light',\n      },\n    });\n\n    expect(preferenceManager.getPreferences().theme.mode).toBe('light');\n  });\n\n  it('updates color modes correctly', () => {\n    preferenceManager.updatePreferences({\n      app: { colorGrayMode: true, colorWeakMode: true },\n    });\n\n    expect(preferenceManager.getPreferences().app.colorGrayMode).toBe(true);\n    expect(preferenceManager.getPreferences().app.colorWeakMode).toBe(true);\n  });\n\n  it('resets preferences to default', () => {\n    // 先更新一些偏好设置\n    preferenceManager.updatePreferences({\n      theme: {\n        mode: 'light',\n      },\n    });\n\n    // 然后重置偏好设置\n    preferenceManager.resetPreferences();\n\n    expect(preferenceManager.getPreferences()).toEqual(defaultPreferences);\n  });\n\n  it('updates isMobile correctly', () => {\n    // 模拟移动端状态\n    vi.stubGlobal(\n      'matchMedia',\n      vi.fn().mockImplementation((query) => ({\n        addEventListener: vi.fn(),\n        addListener: vi.fn(),\n        dispatchEvent: vi.fn(),\n        matches: query === '(max-width: 768px)',\n        media: query,\n        onchange: null,\n        removeEventListener: vi.fn(),\n        removeListener: vi.fn(),\n      })),\n    );\n\n    preferenceManager.updatePreferences({\n      app: { isMobile: true },\n    });\n\n    expect(preferenceManager.getPreferences().app.isMobile).toBe(true);\n  });\n\n  it('updates the locale preference correctly', () => {\n    preferenceManager.updatePreferences({\n      app: { locale: 'en-US' },\n    });\n\n    expect(preferenceManager.getPreferences().app.locale).toBe('en-US');\n  });\n\n  it('updates the sidebar width correctly', () => {\n    preferenceManager.updatePreferences({\n      sidebar: { width: 200 },\n    });\n\n    expect(preferenceManager.getPreferences().sidebar.width).toBe(200);\n  });\n  it('updates the sidebar collapse state correctly', () => {\n    preferenceManager.updatePreferences({\n      sidebar: { collapsed: true },\n    });\n\n    expect(preferenceManager.getPreferences().sidebar.collapsed).toBe(true);\n  });\n  it('updates the navigation style type correctly', () => {\n    preferenceManager.updatePreferences({\n      navigation: { styleType: 'flat' },\n    } as any);\n\n    expect(preferenceManager.getPreferences().navigation.styleType).toBe(\n      'flat',\n    );\n  });\n\n  it('resets preferences to default correctly', () => {\n    // 先更新一些偏好设置\n    preferenceManager.updatePreferences({\n      app: { locale: 'en-US' },\n      sidebar: { collapsed: true, width: 200 },\n      theme: {\n        mode: 'light',\n      },\n    });\n\n    // 然后重置偏好设置\n    preferenceManager.resetPreferences();\n\n    expect(preferenceManager.getPreferences()).toEqual(defaultPreferences);\n  });\n\n  it('does not update undefined preferences', () => {\n    const originalPreferences = preferenceManager.getPreferences();\n\n    preferenceManager.updatePreferences({\n      app: { nonexistentField: 'value' },\n    } as any);\n\n    expect(preferenceManager.getPreferences()).toEqual(originalPreferences);\n  });\n\n  it('reverts to default when a preference field is deleted', () => {\n    preferenceManager.updatePreferences({\n      app: { locale: 'en-US' },\n    });\n\n    preferenceManager.updatePreferences({\n      app: { locale: undefined },\n    });\n\n    expect(preferenceManager.getPreferences().app.locale).toBe('en-US');\n  });\n\n  it('ignores updates with invalid preference value types', () => {\n    const originalPreferences = preferenceManager.getPreferences();\n\n    preferenceManager.updatePreferences({\n      app: { isMobile: 'true' as unknown as boolean }, // 错误类型\n    });\n\n    expect(preferenceManager.getPreferences()).toEqual(originalPreferences);\n  });\n\n  it('merges nested preference objects correctly', () => {\n    preferenceManager.updatePreferences({\n      app: { name: 'New App Name' },\n    });\n\n    const expected = {\n      ...defaultPreferences,\n      app: {\n        ...defaultPreferences.app,\n        name: 'New App Name',\n      },\n    };\n\n    expect(preferenceManager.getPreferences()).toEqual(expected);\n  });\n\n  it('applies updates immediately after initialization', async () => {\n    const overrides: any = {\n      app: {\n        locale: 'en-US',\n      },\n    };\n\n    await preferenceManager.initPreferences(overrides);\n\n    preferenceManager.updatePreferences({\n      theme: { mode: 'light' },\n    });\n\n    expect(preferenceManager.getPreferences().theme.mode).toBe('light');\n  });\n});\n\ndescribe('isDarkTheme', () => {\n  it('should return true for dark theme', () => {\n    expect(isDarkTheme('dark')).toBe(true);\n  });\n\n  it('should return false for light theme', () => {\n    expect(isDarkTheme('light')).toBe(false);\n  });\n\n  it('should return system preference for auto theme', () => {\n    vi.spyOn(window, 'matchMedia').mockImplementation((query) => ({\n      addEventListener: vi.fn(),\n      addListener: vi.fn(), // Deprecated\n      dispatchEvent: vi.fn(),\n      matches: query === '(prefers-color-scheme: dark)',\n      media: query,\n      onchange: null,\n      removeEventListener: vi.fn(),\n      removeListener: vi.fn(), // Deprecated\n    }));\n\n    expect(isDarkTheme('auto')).toBe(true);\n    expect(window.matchMedia).toHaveBeenCalledWith(\n      '(prefers-color-scheme: dark)',\n    );\n  });\n});\n"
  },
  {
    "path": "packages/@core/preferences/package.json",
    "content": "{\n  \"name\": \"@vben-core/preferences\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@core/preferences\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"#build\": \"pnpm exec tsdown\"\n  },\n  \"files\": [\n    \"dist\",\n    \"src\"\n  ],\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"production\": \"./src/index.ts\",\n      \"default\": \"./dist/index.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"default\": \"./dist/index.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben-core/typings\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"vue\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/preferences/src/config.ts",
    "content": "import type { Preferences } from './types';\n\nconst defaultPreferences: Preferences = {\n  app: {\n    accessMode: 'frontend',\n    authPageLayout: 'panel-right',\n    checkUpdatesInterval: 1,\n    colorGrayMode: false,\n    colorWeakMode: false,\n    compact: false,\n    contentCompact: 'wide',\n    contentCompactWidth: 1200,\n    contentPadding: 0,\n    contentPaddingBottom: 0,\n    contentPaddingLeft: 0,\n    contentPaddingRight: 0,\n    contentPaddingTop: 0,\n    defaultAvatar:\n      'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',\n    defaultHomePath: '/analytics',\n    dynamicTitle: true,\n    enableCheckUpdates: true,\n    enableCopyPreferences: true,\n    enablePreferences: true,\n    enableRefreshToken: false,\n    enableStickyPreferencesNavigationBar: true,\n    isMobile: false,\n    layout: 'sidebar-nav',\n    locale: 'zh-CN',\n    loginExpiredMode: 'page',\n    name: 'Vben Admin',\n    preferencesButtonPosition: 'auto',\n    watermark: false,\n    watermarkContent: '',\n    zIndex: 200,\n  },\n  breadcrumb: {\n    enable: true,\n    hideOnlyOne: false,\n    showHome: false,\n    showIcon: true,\n    styleType: 'normal',\n  },\n  copyright: {\n    companyName: 'Vben',\n    companySiteLink: 'https://www.vben.pro',\n    date: '2024',\n    enable: true,\n    icp: '',\n    icpLink: '',\n    settingShow: true,\n  },\n  footer: {\n    enable: false,\n    fixed: false,\n    height: 32,\n  },\n  header: {\n    enable: true,\n    height: 50,\n    hidden: false,\n    menuAlign: 'start',\n    mode: 'fixed',\n  },\n\n  logo: {\n    enable: true,\n    fit: 'contain',\n    source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',\n  },\n  navigation: {\n    accordion: true,\n    split: true,\n    styleType: 'rounded',\n  },\n  shortcutKeys: {\n    enable: true,\n    globalLockScreen: true,\n    globalLogout: true,\n    globalPreferences: true,\n    globalSearch: true,\n  },\n  sidebar: {\n    autoActivateChild: false,\n    collapsed: false,\n    collapsedButton: true,\n    collapsedShowTitle: false,\n    collapseWidth: 60,\n    draggable: true,\n    enable: true,\n    expandOnHover: true,\n    extraCollapse: false,\n    extraCollapsedWidth: 60,\n    fixedButton: true,\n    hidden: false,\n    mixedWidth: 80,\n    width: 224,\n  },\n  tabbar: {\n    draggable: true,\n    enable: true,\n    height: 38,\n    keepAlive: true,\n    maxCount: 0,\n    middleClickToClose: false,\n    persist: true,\n    showIcon: true,\n    showMaximize: true,\n    showMore: true,\n    showRefresh: true,\n    styleType: 'chrome',\n    visitHistory: true,\n    wheelable: true,\n  },\n  theme: {\n    builtinType: 'default',\n    colorDestructive: 'hsl(348 100% 61%)',\n    colorPrimary: 'hsl(212 100% 45%)',\n    colorSuccess: 'hsl(144 57% 58%)',\n    colorWarning: 'hsl(42 84% 61%)',\n    mode: 'dark',\n    radius: '0.5',\n    fontSize: 16,\n    semiDarkHeader: false,\n    semiDarkSidebar: false,\n    semiDarkSidebarSub: false,\n  },\n  transition: {\n    enable: true,\n    loading: true,\n    name: 'fade-slide',\n    progress: true,\n  },\n  widget: {\n    fullscreen: true,\n    globalSearch: true,\n    languageToggle: true,\n    lockScreen: true,\n    notification: true,\n    refresh: true,\n    sidebarToggle: true,\n    themeToggle: true,\n    timezone: true,\n  },\n};\n\nexport { defaultPreferences };\n"
  },
  {
    "path": "packages/@core/preferences/src/constants.ts",
    "content": "import type { BuiltinThemeType, TimezoneOption } from '@vben-core/typings';\n\ninterface BuiltinThemePreset {\n  color: string;\n  darkPrimaryColor?: string;\n  primaryColor?: string;\n  type: BuiltinThemeType;\n}\n\nconst BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [\n  {\n    color: 'hsl(212 100% 45%)',\n    type: 'default',\n  },\n  {\n    color: 'hsl(245 82% 67%)',\n    type: 'violet',\n  },\n  {\n    color: 'hsl(347 77% 60%)',\n    type: 'pink',\n  },\n  {\n    color: 'hsl(42 84% 61%)',\n    type: 'yellow',\n  },\n  {\n    color: 'hsl(231 98% 65%)',\n    type: 'sky-blue',\n  },\n  {\n    color: 'hsl(161 90% 43%)',\n    type: 'green',\n  },\n  {\n    color: 'hsl(240 5% 26%)',\n    darkPrimaryColor: 'hsl(0 0% 98%)',\n    primaryColor: 'hsl(240 5.9% 10%)',\n    type: 'zinc',\n  },\n  {\n    color: 'hsl(181 84% 32%)',\n    type: 'deep-green',\n  },\n  {\n    color: 'hsl(211 91% 39%)',\n    type: 'deep-blue',\n  },\n  {\n    color: 'hsl(18 89% 40%)',\n    type: 'orange',\n  },\n  {\n    color: 'hsl(0 75% 42%)',\n    type: 'rose',\n  },\n  {\n    color: 'hsl(0 0% 25%)',\n    darkPrimaryColor: 'hsl(0 0% 98%)',\n    primaryColor: 'hsl(240 5.9% 10%)',\n    type: 'neutral',\n  },\n  {\n    color: 'hsl(215 25% 27%)',\n    darkPrimaryColor: 'hsl(0 0% 98%)',\n    primaryColor: 'hsl(240 5.9% 10%)',\n    type: 'slate',\n  },\n  {\n    color: 'hsl(217 19% 27%)',\n    darkPrimaryColor: 'hsl(0 0% 98%)',\n    primaryColor: 'hsl(240 5.9% 10%)',\n    type: 'gray',\n  },\n  {\n    color: '',\n    type: 'custom',\n  },\n];\n\n/**\n * 时区选项\n */\nconst DEFAULT_TIME_ZONE_OPTIONS: TimezoneOption[] = [\n  {\n    offset: -5,\n    timezone: 'America/New_York',\n    label: 'America/New_York(GMT-5)',\n  },\n  {\n    offset: 0,\n    timezone: 'Europe/London',\n    label: 'Europe/London(GMT0)',\n  },\n  {\n    offset: 8,\n    timezone: 'Asia/Shanghai',\n    label: 'Asia/Shanghai(GMT+8)',\n  },\n  {\n    offset: 9,\n    timezone: 'Asia/Tokyo',\n    label: 'Asia/Tokyo(GMT+9)',\n  },\n  {\n    offset: 9,\n    timezone: 'Asia/Seoul',\n    label: 'Asia/Seoul(GMT+9)',\n  },\n];\n\nexport const COLOR_PRESETS = [...BUILT_IN_THEME_PRESETS].slice(0, 7);\n\nexport { BUILT_IN_THEME_PRESETS, DEFAULT_TIME_ZONE_OPTIONS };\n\nexport type { BuiltinThemePreset };\n"
  },
  {
    "path": "packages/@core/preferences/src/index.ts",
    "content": "import type { Preferences } from './types';\n\nimport { preferencesManager } from './preferences';\n\nexport const {\n  getPreferences,\n  updatePreferences,\n  resetPreferences,\n  clearCache,\n  initPreferences,\n} = preferencesManager;\n\nexport const preferences: Preferences = getPreferences();\n\nexport { preferencesManager };\n\nexport * from './constants';\nexport type * from './types';\nexport * from './use-preferences';\n"
  },
  {
    "path": "packages/@core/preferences/src/preferences.ts",
    "content": "import type { DeepPartial } from '@vben-core/typings';\n\nimport type { InitialOptions, Preferences } from './types';\n\nimport { markRaw, reactive, readonly, watch } from 'vue';\n\nimport { StorageManager } from '@vben-core/shared/cache';\nimport { isMacOs, merge } from '@vben-core/shared/utils';\n\nimport {\n  breakpointsTailwind,\n  useBreakpoints,\n  useDebounceFn,\n} from '@vueuse/core';\n\nimport { defaultPreferences } from './config';\nimport { updateCSSVariables } from './update-css-variables';\n\nconst STORAGE_KEYS = {\n  MAIN: 'preferences',\n  LOCALE: 'preferences-locale',\n  THEME: 'preferences-theme',\n} as const;\n\nclass PreferenceManager {\n  private cache: StorageManager;\n  private debouncedSave: (preference: Preferences) => void;\n  private initialPreferences: Preferences = defaultPreferences;\n  private isInitialized = false;\n  private state: Preferences;\n\n  constructor() {\n    this.cache = new StorageManager();\n    this.state = reactive<Preferences>(\n      this.loadFromCache() || { ...defaultPreferences },\n    );\n    this.debouncedSave = useDebounceFn(\n      (preference) => this.saveToCache(preference),\n      150,\n    );\n  }\n\n  /**\n   * 清除所有缓存的偏好设置\n   */\n  clearCache = () => {\n    Object.values(STORAGE_KEYS).forEach((key) => this.cache.removeItem(key));\n  };\n\n  /**\n   * 获取初始化偏好设置\n   */\n  getInitialPreferences = () => {\n    return this.initialPreferences;\n  };\n\n  /**\n   * 获取当前偏好设置（只读）\n   */\n  getPreferences = () => {\n    return readonly(this.state);\n  };\n\n  /**\n   * 初始化偏好设置\n   * @param options - 初始化配置项\n   * @param options.namespace - 命名空间，用于隔离不同应用的配置\n   * @param options.overrides - 要覆盖的偏好设置\n   */\n  initPreferences = async ({ namespace, overrides }: InitialOptions) => {\n    // 防止重复初始化\n    if (this.isInitialized) {\n      return;\n    }\n\n    // 使用命名空间初始化存储管理器\n    this.cache = new StorageManager({ prefix: namespace });\n\n    // 合并初始偏好设置\n    this.initialPreferences = merge({}, overrides, defaultPreferences);\n\n    // 加载缓存的偏好设置并与初始配置合并\n    const cachedPreferences = this.loadFromCache() || {};\n    const mergedPreference = merge(\n      {},\n      cachedPreferences,\n      this.initialPreferences,\n    );\n\n    // 更新偏好设置\n    this.updatePreferences(mergedPreference);\n\n    // 设置监听器\n    this.setupWatcher();\n\n    // 初始化平台标识\n    this.initPlatform();\n\n    this.isInitialized = true;\n  };\n\n  /**\n   * 重置偏好设置到初始状态\n   */\n  resetPreferences = () => {\n    // 将状态重置为初始偏好设置\n    Object.assign(this.state, this.initialPreferences);\n\n    // 保存偏好设置至缓存\n    this.saveToCache(this.state);\n\n    // 直接触发 UI 更新\n    this.handleUpdates(this.state);\n  };\n\n  /**\n   * 更新偏好设置\n   * @param updates - 要更新的偏好设置\n   */\n  updatePreferences = (updates: DeepPartial<Preferences>) => {\n    // 深度合并更新内容和当前状态\n    const mergedState = merge({}, updates, markRaw(this.state));\n    Object.assign(this.state, mergedState);\n\n    // 根据更新的值执行更新\n    this.handleUpdates(updates);\n\n    // 保存到缓存\n    this.debouncedSave(this.state);\n  };\n\n  /**\n   * 处理更新\n   * @param updates - 更新的偏好设置\n   */\n  private handleUpdates(updates: DeepPartial<Preferences>) {\n    const { theme, app } = updates;\n\n    if (\n      theme &&\n      (Object.keys(theme).length > 0 || Reflect.has(theme, 'fontSize'))\n    ) {\n      updateCSSVariables(this.state);\n    }\n\n    if (\n      app &&\n      (Reflect.has(app, 'colorGrayMode') || Reflect.has(app, 'colorWeakMode'))\n    ) {\n      this.updateColorMode(this.state);\n    }\n  }\n\n  /**\n   * 初始化平台标识\n   */\n  private initPlatform() {\n    document.documentElement.dataset.platform = isMacOs() ? 'macOs' : 'window';\n  }\n\n  /**\n   * 从缓存加载偏好设置\n   * @returns 缓存的偏好设置，如果不存在则返回 null\n   */\n  private loadFromCache(): null | Preferences {\n    return this.cache.getItem<Preferences>(STORAGE_KEYS.MAIN);\n  }\n\n  /**\n   * 保存偏好设置到缓存\n   * @param preference - 要保存的偏好设置\n   */\n  private saveToCache(preference: Preferences) {\n    this.cache.setItem(STORAGE_KEYS.MAIN, preference);\n    this.cache.setItem(STORAGE_KEYS.LOCALE, preference.app.locale);\n    this.cache.setItem(STORAGE_KEYS.THEME, preference.theme.mode);\n  }\n\n  /**\n   * 监听状态和系统偏好设置的变化\n   */\n  private setupWatcher() {\n    if (this.isInitialized) {\n      return;\n    }\n\n    // 监听断点，判断是否移动端\n    const breakpoints = useBreakpoints(breakpointsTailwind);\n    const isMobile = breakpoints.smaller('md');\n\n    watch(\n      () => isMobile.value,\n      (val) => {\n        this.updatePreferences({\n          app: { isMobile: val },\n        });\n      },\n      { immediate: true },\n    );\n\n    // 监听系统主题偏好设置变化\n    window\n      .matchMedia('(prefers-color-scheme: dark)')\n      .addEventListener('change', ({ matches: isDark }) => {\n        // 仅在自动模式下跟随系统主题\n        if (this.state.theme.mode === 'auto') {\n          // 先应用实际的主题\n          this.updatePreferences({\n            theme: { mode: isDark ? 'dark' : 'light' },\n          });\n          // 再恢复为 auto 模式，保持跟随系统的状态\n          this.updatePreferences({\n            theme: { mode: 'auto' },\n          });\n        }\n      });\n  }\n\n  /**\n   * 更新页面颜色模式（灰色、色弱）\n   * @param preference - 偏好设置\n   */\n  private updateColorMode(preference: Preferences) {\n    const { colorGrayMode, colorWeakMode } = preference.app;\n    const dom = document.documentElement;\n\n    dom.classList.toggle('invert-mode', colorWeakMode);\n    dom.classList.toggle('grayscale-mode', colorGrayMode);\n  }\n}\n\nconst preferencesManager = new PreferenceManager();\n\nexport { PreferenceManager, preferencesManager };\n"
  },
  {
    "path": "packages/@core/preferences/src/types.ts",
    "content": "import type {\n  AccessModeType,\n  AuthPageLayoutType,\n  BreadcrumbStyleType,\n  BuiltinThemeType,\n  ContentCompactType,\n  DeepPartial,\n  LayoutHeaderMenuAlignType,\n  LayoutHeaderModeType,\n  LayoutType,\n  LoginExpiredModeType,\n  NavigationStyleType,\n  PageTransitionType,\n  PreferencesButtonPositionType,\n  TabsStyleType,\n  ThemeModeType,\n} from '@vben-core/typings';\n\ntype SupportedLanguagesType = 'en-US' | 'zh-CN';\n\ninterface AppPreferences {\n  /** 权限模式 */\n  accessMode: AccessModeType;\n  /** 登录注册页面布局 */\n  authPageLayout: AuthPageLayoutType;\n  /** 检查更新轮询时间 */\n  checkUpdatesInterval: number;\n  /** 是否开启灰色模式 */\n  colorGrayMode: boolean;\n  /** 是否开启色弱模式 */\n  colorWeakMode: boolean;\n  /** 是否开启紧凑模式 */\n  compact: boolean;\n  /** 是否开启内容紧凑模式 */\n  contentCompact: ContentCompactType;\n  /** 内容紧凑宽度 */\n  contentCompactWidth: number;\n  /** 内容内边距 */\n  contentPadding: number;\n  /** 内容底部内边距 */\n  contentPaddingBottom: number;\n  /** 内容左侧内边距 */\n  contentPaddingLeft: number;\n  /** 内容右侧内边距 */\n  contentPaddingRight: number;\n  /** 内容顶部内边距 */\n  contentPaddingTop: number;\n  // /** 应用默认头像 */\n  defaultAvatar: string;\n  /** 默认首页地址 */\n  defaultHomePath: string;\n  // /** 开启动态标题 */\n  dynamicTitle: boolean;\n  /** 是否开启检查更新 */\n  enableCheckUpdates: boolean;\n  /** 是否显示复制偏好设置按钮 */\n  enableCopyPreferences: boolean;\n  /** 是否显示偏好设置 */\n  enablePreferences: boolean;\n  /**\n   * @zh_CN 是否开启refreshToken\n   */\n  enableRefreshToken: boolean;\n  /**\n   * @zh_CN 是否开启首选项导航栏吸顶效果\n   */\n  enableStickyPreferencesNavigationBar: boolean;\n  /** 是否移动端 */\n  isMobile: boolean;\n  /** 布局方式 */\n  layout: LayoutType;\n  /** 支持的语言 */\n  locale: SupportedLanguagesType;\n  /** 登录过期模式 */\n  loginExpiredMode: LoginExpiredModeType;\n  /** 应用名 */\n  name: string;\n  /** 偏好设置按钮位置 */\n  preferencesButtonPosition: PreferencesButtonPositionType;\n  /**\n   * @zh_CN 是否开启水印\n   */\n  watermark: boolean;\n  /**\n   * @zh_CN 水印文案\n   */\n  watermarkContent: string;\n  /** z-index */\n  zIndex: number;\n}\n\ninterface BreadcrumbPreferences {\n  /** 面包屑是否启用 */\n  enable: boolean;\n  /** 面包屑是否只有一个时隐藏 */\n  hideOnlyOne: boolean;\n  /** 面包屑首页图标是否可见 */\n  showHome: boolean;\n  /** 面包屑图标是否可见 */\n  showIcon: boolean;\n  /** 面包屑风格 */\n  styleType: BreadcrumbStyleType;\n}\n\ninterface CopyrightPreferences {\n  /** 版权公司名 */\n  companyName: string;\n  /** 版权公司名链接 */\n  companySiteLink: string;\n  /** 版权日期 */\n  date: string;\n  /** 版权是否可见 */\n  enable: boolean;\n  /** 备案号 */\n  icp: string;\n  /** 备案号链接 */\n  icpLink: string;\n  /** 设置面板是否显示*/\n  settingShow?: boolean;\n}\n\ninterface FooterPreferences {\n  /** 底栏是否可见 */\n  enable: boolean;\n  /** 底栏是否固定 */\n  fixed: boolean;\n  /** 底栏高度 */\n  height: number;\n}\n\ninterface HeaderPreferences {\n  /** 顶栏是否启用 */\n  enable: boolean;\n  /** 顶栏高度 */\n  height: number;\n  /** 顶栏是否隐藏,css-隐藏 */\n  hidden: boolean;\n  /** 顶栏菜单位置 */\n  menuAlign: LayoutHeaderMenuAlignType;\n  /** header显示模式 */\n  mode: LayoutHeaderModeType;\n}\n\ninterface LogoPreferences {\n  /** logo是否可见 */\n  enable: boolean;\n  /** logo图片适应方式 */\n  fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n  /** logo地址 */\n  source: string;\n  /** 暗色主题logo地址 (可选，若不设置则使用 source) */\n  sourceDark?: string;\n}\n\ninterface NavigationPreferences {\n  /** 导航菜单手风琴模式 */\n  accordion: boolean;\n  /** 导航菜单是否切割，只在 layout=mixed-nav 生效 */\n  split: boolean;\n  /** 导航菜单风格 */\n  styleType: NavigationStyleType;\n}\n\ninterface SidebarPreferences {\n  /** 点击目录时自动激活子菜单   */\n  autoActivateChild: boolean;\n  /** 侧边栏是否折叠 */\n  collapsed: boolean;\n  /** 侧边栏折叠按钮是否可见 */\n  collapsedButton: boolean;\n  /** 侧边栏折叠时，是否显示title */\n  collapsedShowTitle: boolean;\n  /** 侧边栏折叠宽度 */\n  collapseWidth: number;\n  /** 侧边栏菜单拖拽 */\n  draggable: boolean;\n  /** 侧边栏是否可见 */\n  enable: boolean;\n  /** 菜单自动展开状态 */\n  expandOnHover: boolean;\n  /** 侧边栏扩展区域是否折叠 */\n  extraCollapse: boolean;\n  /** 侧边栏扩展区域折叠宽度 */\n  extraCollapsedWidth: number;\n  /** 侧边栏固定按钮是否可见 */\n  fixedButton: boolean;\n  /** 侧边栏是否隐藏 - css */\n  hidden: boolean;\n  /** 混合侧边栏宽度 */\n  mixedWidth: number;\n  /** 侧边栏宽度 */\n  width: number;\n}\n\ninterface ShortcutKeyPreferences {\n  /** 是否启用快捷键-全局 */\n  enable: boolean;\n  /** 是否启用全局锁屏快捷键 */\n  globalLockScreen: boolean;\n  /** 是否启用全局注销快捷键 */\n  globalLogout: boolean;\n  /** 是否启用全局偏好设置快捷键 */\n  globalPreferences: boolean;\n  /** 是否启用全局搜索快捷键 */\n  globalSearch: boolean;\n}\n\ninterface TabbarPreferences {\n  /** 是否开启多标签页拖拽 */\n  draggable: boolean;\n  /** 是否开启多标签页 */\n  enable: boolean;\n  /** 标签页高度 */\n  height: number;\n  /** 开启标签页缓存功能 */\n  keepAlive: boolean;\n  /** 限制最大数量 */\n  maxCount: number;\n  /** 是否点击中键时关闭标签 */\n  middleClickToClose: boolean;\n  /** 是否持久化标签 */\n  persist: boolean;\n  /** 是否开启多标签页图标 */\n  showIcon: boolean;\n  /** 显示最大化按钮 */\n  showMaximize: boolean;\n  /** 显示更多按钮 */\n  showMore: boolean;\n  /** 显示刷新按钮 */\n  showRefresh: boolean;\n  /** 标签页风格 */\n  styleType: TabsStyleType;\n  /** 是否开启访问历史记录 */\n  visitHistory: boolean;\n  /** 是否开启鼠标滚轮响应 */\n  wheelable: boolean;\n}\n\ninterface ThemePreferences {\n  /** 内置主题名 */\n  builtinType: BuiltinThemeType;\n  /** 错误色 */\n  colorDestructive: string;\n  /** 主题色 */\n  colorPrimary: string;\n  /** 成功色 */\n  colorSuccess: string;\n  /** 警告色 */\n  colorWarning: string;\n  /** 字体大小（单位：px） */\n  fontSize: number;\n  /** 当前主题 */\n  mode: ThemeModeType;\n  /** 圆角 */\n  radius: string;\n  /** 是否开启半深色header（只在theme='light'时生效） */\n  semiDarkHeader: boolean;\n  /** 是否开启半深色菜单（只在theme='light'时生效） */\n  semiDarkSidebar: boolean;\n  /** 是否开启半深色子菜单（只在theme='light'时生效） */\n  semiDarkSidebarSub: boolean;\n}\n\ninterface TransitionPreferences {\n  /** 页面切换动画是否启用 */\n  enable: boolean;\n  // /** 是否开启页面加载loading */\n  loading: boolean;\n  /** 页面切换动画 */\n  name: PageTransitionType | string;\n  /** 是否开启页面加载进度动画 */\n  progress: boolean;\n}\n\ninterface WidgetPreferences {\n  /** 是否启用全屏部件 */\n  fullscreen: boolean;\n  /** 是否启用全局搜索部件 */\n  globalSearch: boolean;\n  /** 是否启用语言切换部件 */\n  languageToggle: boolean;\n  /** 是否开启锁屏功能 */\n  lockScreen: boolean;\n  /** 是否显示通知部件 */\n  notification: boolean;\n  /** 显示刷新按钮 */\n  refresh: boolean;\n  /** 是否显示侧边栏显示/隐藏部件 */\n  sidebarToggle: boolean;\n  /** 是否显示主题切换部件 */\n  themeToggle: boolean;\n  /** 是否显示时区部件 */\n  timezone: boolean;\n}\n\ninterface Preferences {\n  /** 全局配置 */\n  app: AppPreferences;\n  /** 顶栏配置 */\n  breadcrumb: BreadcrumbPreferences;\n  /** 版权配置 */\n  copyright: CopyrightPreferences;\n  /** 底栏配置 */\n  footer: FooterPreferences;\n  /** 面包屑配置 */\n  header: HeaderPreferences;\n  /** logo配置 */\n  logo: LogoPreferences;\n  /** 导航配置 */\n  navigation: NavigationPreferences;\n  /** 快捷键配置 */\n  shortcutKeys: ShortcutKeyPreferences;\n  /** 侧边栏配置 */\n  sidebar: SidebarPreferences;\n  /** 标签页配置 */\n  tabbar: TabbarPreferences;\n  /** 主题配置 */\n  theme: ThemePreferences;\n  /** 动画配置 */\n  transition: TransitionPreferences;\n  /** 功能配置 */\n  widget: WidgetPreferences;\n}\n\ntype PreferencesKeys = keyof Preferences;\n\ninterface InitialOptions {\n  namespace: string;\n  overrides?: DeepPartial<Preferences>;\n}\nexport type {\n  AppPreferences,\n  BreadcrumbPreferences,\n  FooterPreferences,\n  HeaderPreferences,\n  InitialOptions,\n  LogoPreferences,\n  NavigationPreferences,\n  Preferences,\n  PreferencesKeys,\n  ShortcutKeyPreferences,\n  SidebarPreferences,\n  SupportedLanguagesType,\n  TabbarPreferences,\n  ThemePreferences,\n  TransitionPreferences,\n  WidgetPreferences,\n};\n"
  },
  {
    "path": "packages/@core/preferences/src/update-css-variables.ts",
    "content": "import type { Preferences } from './types';\n\nimport { generatorColorVariables } from '@vben-core/shared/color';\nimport { updateCSSVariables as executeUpdateCSSVariables } from '@vben-core/shared/utils';\n\nimport { BUILT_IN_THEME_PRESETS } from './constants';\n\n/**\n * 更新主题的 CSS 变量以及其他 CSS 变量\n * @param preferences - 当前偏好设置对象，它的主题值将被用来设置文档的主题。\n */\nfunction updateCSSVariables(preferences: Preferences) {\n  // 当修改到颜色变量时，更新 css 变量\n  const root = document.documentElement;\n  if (!root) {\n    return;\n  }\n\n  const theme = preferences?.theme ?? {};\n\n  const { builtinType, mode, radius } = theme;\n\n  // html 设置 dark 类\n  if (Reflect.has(theme, 'mode')) {\n    const dark = isDarkTheme(mode);\n    root.classList.toggle('dark', dark);\n  }\n\n  // html 设置 data-theme=[builtinType]\n  if (Reflect.has(theme, 'builtinType')) {\n    const rootTheme = root.dataset.theme;\n    if (rootTheme !== builtinType) {\n      root.dataset.theme = builtinType;\n    }\n  }\n\n  // 获取当前的内置主题\n  const currentBuiltType = [...BUILT_IN_THEME_PRESETS].find(\n    (item) => item.type === builtinType,\n  );\n\n  let builtinTypeColorPrimary: string | undefined = '';\n\n  if (currentBuiltType) {\n    const isDark = isDarkTheme(preferences.theme.mode);\n    // 设置不同主题的主要颜色\n    const color = isDark\n      ? currentBuiltType.darkPrimaryColor || currentBuiltType.primaryColor\n      : currentBuiltType.primaryColor;\n    builtinTypeColorPrimary = color || currentBuiltType.color;\n  }\n\n  // 如果内置主题颜色和自定义颜色都不存在，则不更新主题颜色\n  if (\n    builtinTypeColorPrimary ||\n    Reflect.has(theme, 'colorPrimary') ||\n    Reflect.has(theme, 'colorDestructive') ||\n    Reflect.has(theme, 'colorSuccess') ||\n    Reflect.has(theme, 'colorWarning')\n  ) {\n    // preferences.theme.colorPrimary = builtinTypeColorPrimary || colorPrimary;\n    updateMainColorVariables(preferences);\n  }\n\n  // 更新圆角\n  if (Reflect.has(theme, 'radius')) {\n    document.documentElement.style.setProperty('--radius', `${radius}rem`);\n  }\n\n  // 更新字体大小\n  if (Reflect.has(theme, 'fontSize')) {\n    const fontSize = theme.fontSize;\n    document.documentElement.style.setProperty(\n      '--font-size-base',\n      `${fontSize}px`,\n    );\n    document.documentElement.style.setProperty(\n      '--menu-font-size',\n      `calc(${fontSize}px * 0.875)`,\n    );\n  }\n}\n\n/**\n * 更新主要的 CSS 变量\n * @param  preference - 当前偏好设置对象，它的颜色值将被转换成 HSL 格式并设置为 CSS 变量。\n */\nfunction updateMainColorVariables(preference: Preferences) {\n  if (!preference.theme) {\n    return;\n  }\n  const { colorDestructive, colorPrimary, colorSuccess, colorWarning } =\n    preference.theme;\n\n  const colorVariables = generatorColorVariables([\n    { color: colorPrimary, name: 'primary' },\n    { alias: 'warning', color: colorWarning, name: 'yellow' },\n    { alias: 'success', color: colorSuccess, name: 'green' },\n    { alias: 'destructive', color: colorDestructive, name: 'red' },\n  ]);\n\n  // 要设置的 CSS 变量映射\n  const colorMappings = {\n    '--green-500': '--success',\n    '--primary-500': '--primary',\n    '--red-500': '--destructive',\n    '--yellow-500': '--warning',\n  };\n\n  // 统一处理颜色变量的更新\n  Object.entries(colorMappings).forEach(([sourceVar, targetVar]) => {\n    const colorValue = colorVariables[sourceVar];\n    if (colorValue) {\n      document.documentElement.style.setProperty(targetVar, colorValue);\n    }\n  });\n\n  executeUpdateCSSVariables(colorVariables);\n}\n\nfunction isDarkTheme(theme: string) {\n  let dark = theme === 'dark';\n  if (theme === 'auto') {\n    dark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n  }\n  return dark;\n}\n\nexport { isDarkTheme, updateCSSVariables };\n"
  },
  {
    "path": "packages/@core/preferences/src/use-preferences.ts",
    "content": "import { computed } from 'vue';\n\nimport { diff } from '@vben-core/shared/utils';\n\nimport { preferencesManager } from './preferences';\nimport { isDarkTheme } from './update-css-variables';\n\nfunction usePreferences() {\n  const preferences = preferencesManager.getPreferences();\n  const initialPreferences = preferencesManager.getInitialPreferences();\n  /**\n   * @zh_CN 计算偏好设置的变化\n   */\n  const diffPreference = computed(() => {\n    return diff(initialPreferences, preferences);\n  });\n\n  const appPreferences = computed(() => preferences.app);\n\n  const shortcutKeysPreferences = computed(() => preferences.shortcutKeys);\n\n  /**\n   * @zh_CN 判断是否为暗黑模式\n   * @param  preferences - 当前偏好设置对象，它的主题值将被用来判断是否为暗黑模式。\n   * @returns 如果主题为暗黑模式，返回 true，否则返回 false。\n   */\n  const isDark = computed(() => {\n    return isDarkTheme(preferences.theme.mode);\n  });\n\n  const locale = computed(() => {\n    return preferences.app.locale;\n  });\n\n  const isMobile = computed(() => {\n    return appPreferences.value.isMobile;\n  });\n\n  const theme = computed(() => {\n    return isDark.value ? 'dark' : 'light';\n  });\n\n  /**\n   * @zh_CN 布局方式\n   */\n  const layout = computed(() =>\n    isMobile.value ? 'sidebar-nav' : appPreferences.value.layout,\n  );\n\n  /**\n   * @zh_CN 是否显示顶栏\n   */\n  const isShowHeaderNav = computed(() => {\n    return preferences.header.enable;\n  });\n\n  /**\n   * @zh_CN 是否全屏显示content，不需要侧边、底部、顶部、tab区域\n   */\n  const isFullContent = computed(\n    () => appPreferences.value.layout === 'full-content',\n  );\n\n  /**\n   * @zh_CN 是否侧边导航模式\n   */\n  const isSideNav = computed(\n    () => appPreferences.value.layout === 'sidebar-nav',\n  );\n\n  /**\n   * @zh_CN 是否侧边混合模式\n   */\n  const isSideMixedNav = computed(\n    () => appPreferences.value.layout === 'sidebar-mixed-nav',\n  );\n\n  /**\n   * @zh_CN 是否为头部导航模式\n   */\n  const isHeaderNav = computed(\n    () => appPreferences.value.layout === 'header-nav',\n  );\n\n  /**\n   * @zh_CN 是否为头部混合导航模式\n   */\n  const isHeaderMixedNav = computed(\n    () => appPreferences.value.layout === 'header-mixed-nav',\n  );\n\n  /**\n   * @zh_CN 是否为顶部通栏+侧边导航模式\n   */\n  const isHeaderSidebarNav = computed(\n    () => appPreferences.value.layout === 'header-sidebar-nav',\n  );\n\n  /**\n   * @zh_CN 是否为混合导航模式\n   */\n  const isMixedNav = computed(\n    () => appPreferences.value.layout === 'mixed-nav',\n  );\n\n  /**\n   * @zh_CN 是否包含侧边导航模式\n   */\n  const isSideMode = computed(() => {\n    return (\n      isMixedNav.value ||\n      isSideMixedNav.value ||\n      isSideNav.value ||\n      isHeaderMixedNav.value ||\n      isHeaderSidebarNav.value\n    );\n  });\n\n  const sidebarCollapsed = computed(() => {\n    return preferences.sidebar.collapsed;\n  });\n\n  /**\n   * @zh_CN 是否开启keep-alive\n   * 在tabs可见以及开启keep-alive的情况下才开启\n   */\n  const keepAlive = computed(\n    () => preferences.tabbar.enable && preferences.tabbar.keepAlive,\n  );\n\n  /**\n   * @zh_CN 登录注册页面布局是否为左侧\n   */\n  const authPanelLeft = computed(() => {\n    return appPreferences.value.authPageLayout === 'panel-left';\n  });\n\n  /**\n   * @zh_CN 登录注册页面布局是否为右侧\n   */\n  const authPanelRight = computed(() => {\n    return appPreferences.value.authPageLayout === 'panel-right';\n  });\n\n  /**\n   * @zh_CN 登录注册页面布局是否为中间\n   */\n  const authPanelCenter = computed(() => {\n    return appPreferences.value.authPageLayout === 'panel-center';\n  });\n\n  /**\n   * @zh_CN 内容是否已经最大化\n   * 排除 full-content模式\n   */\n  const contentIsMaximize = computed(() => {\n    const headerIsHidden = preferences.header.hidden;\n    const sidebarIsHidden = preferences.sidebar.hidden;\n    return headerIsHidden && sidebarIsHidden && !isFullContent.value;\n  });\n\n  /**\n   * @zh_CN 是否启用全局搜索快捷键\n   */\n  const globalSearchShortcutKey = computed(() => {\n    const { enable, globalSearch } = shortcutKeysPreferences.value;\n    return enable && globalSearch;\n  });\n\n  /**\n   * @zh_CN 是否启用全局注销快捷键\n   */\n  const globalLogoutShortcutKey = computed(() => {\n    const { enable, globalLogout } = shortcutKeysPreferences.value;\n    return enable && globalLogout;\n  });\n\n  const globalLockScreenShortcutKey = computed(() => {\n    const { enable, globalLockScreen } = shortcutKeysPreferences.value;\n    return enable && globalLockScreen;\n  });\n\n  /**\n   * @zh_CN 偏好设置按钮位置\n   */\n  const preferencesButtonPosition = computed(() => {\n    const { enablePreferences, preferencesButtonPosition } = preferences.app;\n\n    // 如果没有启用偏好设置按钮\n    if (!enablePreferences) {\n      return {\n        fixed: false,\n        header: false,\n      };\n    }\n\n    const { header, sidebar } = preferences;\n    const headerHidden = header.hidden;\n    const sidebarHidden = sidebar.hidden;\n\n    const contentIsMaximize = headerHidden && sidebarHidden;\n\n    const isHeaderPosition = preferencesButtonPosition === 'header';\n\n    // 如果设置了固定位置\n    if (preferencesButtonPosition !== 'auto') {\n      return {\n        fixed: preferencesButtonPosition === 'fixed',\n        header: isHeaderPosition,\n      };\n    }\n\n    // 如果是全屏模式或者没有固定在顶部，\n    const fixed =\n      contentIsMaximize ||\n      isFullContent.value ||\n      isMobile.value ||\n      !isShowHeaderNav.value;\n\n    return {\n      fixed,\n      header: !fixed,\n    };\n  });\n\n  return {\n    authPanelCenter,\n    authPanelLeft,\n    authPanelRight,\n    contentIsMaximize,\n    diffPreference,\n    globalLockScreenShortcutKey,\n    globalLogoutShortcutKey,\n    globalSearchShortcutKey,\n    isDark,\n    isFullContent,\n    isHeaderMixedNav,\n    isHeaderNav,\n    isHeaderSidebarNav,\n    isMixedNav,\n    isMobile,\n    isSideMixedNav,\n    isSideMode,\n    isSideNav,\n    keepAlive,\n    layout,\n    locale,\n    preferencesButtonPosition,\n    sidebarCollapsed,\n    theme,\n  };\n}\n\nexport { usePreferences };\n"
  },
  {
    "path": "packages/@core/preferences/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\", \"__tests__\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/preferences/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: true,\n  dts: true,\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "packages/@core/ui-kit/README.md",
    "content": "# ui-kit\n\n用于管理公共组件、不同UI组件库封装的组件\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts",
    "content": "import { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { FormApi } from '../src/form-api';\n\ndescribe('formApi', () => {\n  let formApi: FormApi;\n\n  beforeEach(() => {\n    formApi = new FormApi();\n  });\n\n  it('should initialize with default state', () => {\n    expect(formApi.state).toEqual(\n      expect.objectContaining({\n        actionWrapperClass: '',\n        collapsed: false,\n        collapsedRows: 1,\n        commonConfig: {},\n        handleReset: undefined,\n        handleSubmit: undefined,\n        layout: 'horizontal',\n        resetButtonOptions: {},\n        schema: [],\n        showCollapseButton: false,\n        showDefaultActions: true,\n        submitButtonOptions: {},\n        wrapperClass: 'grid-cols-1',\n      }),\n    );\n    expect(formApi.isMounted).toBe(false);\n  });\n\n  it('should mount form actions', async () => {\n    const formActions: any = {\n      meta: {},\n      resetForm: vi.fn(),\n      setFieldValue: vi.fn(),\n      setValues: vi.fn(),\n      submitForm: vi.fn(),\n      validate: vi.fn(),\n      values: { name: 'test' },\n    };\n\n    await formApi.mount(formActions);\n    expect(formApi.isMounted).toBe(true);\n    expect(formApi.form).toEqual(formActions);\n  });\n\n  it('should get values from form', async () => {\n    const formActions: any = {\n      meta: {},\n      values: { name: 'test' },\n    };\n\n    await formApi.mount(formActions);\n    const values = await formApi.getValues();\n    expect(values).toEqual({ name: 'test' });\n  });\n\n  it('should set field value', async () => {\n    const setFieldValueMock = vi.fn();\n    const formActions: any = {\n      meta: {},\n      setFieldValue: setFieldValueMock,\n      values: { name: 'test' },\n    };\n\n    await formApi.mount(formActions);\n    await formApi.setFieldValue('name', 'new value');\n    expect(setFieldValueMock).toHaveBeenCalledWith(\n      'name',\n      'new value',\n      undefined,\n    );\n  });\n\n  it('should reset form', async () => {\n    const resetFormMock = vi.fn();\n    const formActions: any = {\n      meta: {},\n      resetForm: resetFormMock,\n      values: { name: 'test' },\n    };\n\n    await formApi.mount(formActions);\n    await formApi.resetForm();\n    expect(resetFormMock).toHaveBeenCalled();\n  });\n\n  it('should call handleSubmit on submit', async () => {\n    const handleSubmitMock = vi.fn();\n    const formActions: any = {\n      meta: {},\n      submitForm: vi.fn().mockResolvedValue(true),\n      values: { name: 'test' },\n    };\n\n    const state = {\n      handleSubmit: handleSubmitMock,\n    };\n\n    formApi.setState(state);\n    await formApi.mount(formActions);\n\n    const result = await formApi.submitForm();\n    expect(formActions.submitForm).toHaveBeenCalled();\n    expect(handleSubmitMock).toHaveBeenCalledWith({ name: 'test' });\n    expect(result).toEqual({ name: 'test' });\n  });\n\n  it('should unmount form and reset state', () => {\n    formApi.unmount();\n    expect(formApi.isMounted).toBe(false);\n  });\n\n  it('should validate form', async () => {\n    const validateMock = vi.fn().mockResolvedValue(true);\n    const formActions: any = {\n      meta: {},\n      validate: validateMock,\n    };\n\n    await formApi.mount(formActions);\n    const isValid = await formApi.validate();\n    expect(validateMock).toHaveBeenCalled();\n    expect(isValid).toBe(true);\n  });\n});\n\ndescribe('updateSchema', () => {\n  let instance: FormApi;\n\n  beforeEach(() => {\n    instance = new FormApi();\n    instance.state = {\n      schema: [\n        { component: 'text', fieldName: 'name' },\n        { component: 'number', fieldName: 'age', label: 'Age' },\n      ],\n    };\n  });\n\n  it('should update the schema correctly when fieldName matches', () => {\n    const newSchema = [\n      { component: 'text', fieldName: 'name' },\n      { component: 'number', fieldName: 'age', label: 'Age' },\n    ];\n\n    instance.updateSchema(newSchema);\n\n    expect(instance.state?.schema?.[0]?.component).toBe('text');\n    expect(instance.state?.schema?.[1]?.label).toBe('Age');\n  });\n\n  it('should log an error if fieldName is missing in some items', () => {\n    const newSchema: any[] = [\n      { component: 'textarea', fieldName: 'name' },\n      { component: 'number' },\n    ];\n\n    const consoleErrorSpy = vi\n      .spyOn(console, 'error')\n      .mockImplementation(() => {});\n\n    instance.updateSchema(newSchema);\n\n    expect(consoleErrorSpy).toHaveBeenCalledWith(\n      'All items in the schema array must have a valid `fieldName` property to be updated',\n    );\n  });\n\n  it('should not update schema if fieldName does not match', () => {\n    const newSchema = [{ component: 'textarea', fieldName: 'unknown' }];\n\n    instance.updateSchema(newSchema);\n\n    expect(instance.state?.schema?.[0]?.component).toBe('text');\n    expect(instance.state?.schema?.[1]?.component).toBe('number');\n  });\n\n  it('should not update schema if updatedMap is empty', () => {\n    const newSchema: any[] = [{ component: 'textarea' }];\n\n    instance.updateSchema(newSchema);\n\n    expect(instance.state?.schema?.[0]?.component).toBe('text');\n    expect(instance.state?.schema?.[1]?.component).toBe('number');\n  });\n});\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/package.json",
    "content": "{\n  \"name\": \"@vben-core/form-ui\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@vben-core/uikit/form-ui\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm exec tsdown\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"production\": \"./src/index.ts\",\n      \"default\": \"./dist/index.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"default\": \"./dist/index.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/composables\": \"workspace:*\",\n    \"@vben-core/icons\": \"workspace:*\",\n    \"@vben-core/shadcn-ui\": \"workspace:*\",\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben-core/typings\": \"workspace:*\",\n    \"@vee-validate/zod\": \"catalog:\",\n    \"@vueuse/core\": \"catalog:\",\n    \"vee-validate\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"zod\": \"catalog:\",\n    \"zod-defaults\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/components/form-actions.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed, toRaw, unref, watch } from 'vue';\n\nimport { useSimpleLocale } from '@vben-core/composables';\nimport { VbenExpandableArrow } from '@vben-core/shadcn-ui';\nimport { cn, isFunction, triggerWindowResize } from '@vben-core/shared/utils';\n\nimport { COMPONENT_MAP } from '../config';\nimport { injectFormProps } from '../use-form-context';\n\nconst { $t } = useSimpleLocale();\n\nconst [rootProps, form] = injectFormProps();\n\nconst collapsed = defineModel({ default: false });\n\nconst resetButtonOptions = computed(() => {\n  return {\n    content: `${$t.value('reset')}`,\n    show: true,\n    ...unref(rootProps).resetButtonOptions,\n  };\n});\n\nconst submitButtonOptions = computed(() => {\n  return {\n    content: `${$t.value('submit')}`,\n    show: true,\n    ...unref(rootProps).submitButtonOptions,\n  };\n});\n\n// const isQueryForm = computed(() => {\n//   return !!unref(rootProps).showCollapseButton;\n// });\n\nasync function handleSubmit(e: Event) {\n  e?.preventDefault();\n  e?.stopPropagation();\n  const props = unref(rootProps);\n  if (!props.formApi) {\n    return;\n  }\n\n  const { valid } = await props.formApi.validate();\n  if (!valid) {\n    return;\n  }\n\n  const values = toRaw(await props.formApi.getValues()) ?? {};\n  await props.handleSubmit?.(values);\n}\n\nasync function handleReset(e: Event) {\n  e?.preventDefault();\n  e?.stopPropagation();\n  const props = unref(rootProps);\n\n  const values = toRaw(await props.formApi?.getValues()) ?? {};\n\n  if (isFunction(props.handleReset)) {\n    await props.handleReset?.(values);\n  } else {\n    form.resetForm();\n  }\n}\n\nwatch(\n  () => collapsed.value,\n  () => {\n    const props = unref(rootProps);\n    if (props.collapseTriggerResize) {\n      triggerWindowResize();\n    }\n  },\n);\n\nconst actionWrapperClass = computed(() => {\n  const props = unref(rootProps);\n  const actionLayout = props.actionLayout || 'rowEnd';\n  const actionPosition = props.actionPosition || 'right';\n\n  const cls = [\n    'flex',\n    'items-center',\n    'gap-3',\n    props.compact ? 'pb-2' : 'pb-4',\n    props.layout === 'vertical' ? 'self-end' : 'self-center',\n    props.layout === 'inline' ? '' : 'w-full',\n    props.actionWrapperClass,\n  ];\n\n  switch (actionLayout) {\n    case 'newLine': {\n      cls.push('col-span-full');\n      break;\n    }\n    case 'rowEnd': {\n      cls.push('col-[-2/-1]');\n      break;\n    }\n    // 'inline' 不需要额外类名，保持默认\n  }\n\n  switch (actionPosition) {\n    case 'center': {\n      cls.push('justify-center');\n      break;\n    }\n    case 'left': {\n      cls.push('justify-start');\n      break;\n    }\n    default: {\n      // case 'right': 默认右对齐\n      cls.push('justify-end');\n      break;\n    }\n  }\n\n  return cls.join(' ');\n});\n\ndefineExpose({\n  handleReset,\n  handleSubmit,\n});\n</script>\n<template>\n  <div :class=\"cn(actionWrapperClass)\">\n    <template v-if=\"rootProps.actionButtonsReverse\">\n      <!-- 提交按钮前 -->\n      <slot name=\"submit-before\"></slot>\n\n      <component\n        :is=\"COMPONENT_MAP.PrimaryButton\"\n        v-if=\"submitButtonOptions.show\"\n        type=\"button\"\n        @click=\"handleSubmit\"\n        v-bind=\"submitButtonOptions\"\n      >\n        {{ submitButtonOptions.content }}\n      </component>\n    </template>\n\n    <!-- 重置按钮前 -->\n    <slot name=\"reset-before\"></slot>\n\n    <component\n      :is=\"COMPONENT_MAP.DefaultButton\"\n      v-if=\"resetButtonOptions.show\"\n      type=\"button\"\n      @click=\"handleReset\"\n      v-bind=\"resetButtonOptions\"\n    >\n      {{ resetButtonOptions.content }}\n    </component>\n\n    <template v-if=\"!rootProps.actionButtonsReverse\">\n      <!-- 提交按钮前 -->\n      <slot name=\"submit-before\"></slot>\n\n      <component\n        :is=\"COMPONENT_MAP.PrimaryButton\"\n        v-if=\"submitButtonOptions.show\"\n        type=\"button\"\n        @click=\"handleSubmit\"\n        v-bind=\"submitButtonOptions\"\n      >\n        {{ submitButtonOptions.content }}\n      </component>\n    </template>\n\n    <!-- 展开按钮前 -->\n    <slot name=\"expand-before\"></slot>\n\n    <VbenExpandableArrow\n      class=\"ml-[-0.3em]\"\n      v-if=\"rootProps.showCollapseButton\"\n      v-model:model-value=\"collapsed\"\n    >\n      <span>{{ collapsed ? $t('expand') : $t('collapse') }}</span>\n    </VbenExpandableArrow>\n\n    <!-- 展开按钮后 -->\n    <slot name=\"expand-after\"></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/config.ts",
    "content": "import type { Component } from 'vue';\n\nimport type {\n  BaseFormComponentType,\n  FormCommonConfig,\n  VbenFormAdapterOptions,\n} from './types';\n\nimport { h } from 'vue';\n\nimport {\n  VbenButton,\n  VbenCheckbox,\n  Input as VbenInput,\n  VbenInputPassword,\n  VbenPinInput,\n  VbenSelect,\n} from '@vben-core/shadcn-ui';\nimport { globalShareState } from '@vben-core/shared/global-state';\n\nimport { defineRule } from 'vee-validate';\n\nconst DEFAULT_MODEL_PROP_NAME = 'modelValue';\n\nexport const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};\n\nexport const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {\n  DefaultButton: h(VbenButton, { size: 'sm', variant: 'outline' }),\n  PrimaryButton: h(VbenButton, { size: 'sm', variant: 'default' }),\n  VbenCheckbox,\n  VbenInput,\n  VbenInputPassword,\n  VbenPinInput,\n  VbenSelect,\n};\n\nexport const COMPONENT_BIND_EVENT_MAP: Partial<\n  Record<BaseFormComponentType, string>\n> = {\n  VbenCheckbox: 'checked',\n};\n\nexport function setupVbenForm<\n  T extends BaseFormComponentType = BaseFormComponentType,\n>(options: VbenFormAdapterOptions<T>) {\n  const { config, defineRules } = options;\n\n  const {\n    disabledOnChangeListener = true,\n    disabledOnInputListener = true,\n    emptyStateValue = undefined,\n  } = (config || {}) as FormCommonConfig;\n\n  Object.assign(DEFAULT_FORM_COMMON_CONFIG, {\n    disabledOnChangeListener,\n    disabledOnInputListener,\n    emptyStateValue,\n  });\n\n  if (defineRules) {\n    for (const key of Object.keys(defineRules)) {\n      defineRule(key, defineRules[key as never]);\n    }\n  }\n\n  const baseModelPropName =\n    config?.baseModelPropName ?? DEFAULT_MODEL_PROP_NAME;\n  const modelPropNameMap = config?.modelPropNameMap as\n    | Record<BaseFormComponentType, string>\n    | undefined;\n\n  const components = globalShareState.getComponents();\n\n  for (const component of Object.keys(components)) {\n    const key = component as BaseFormComponentType;\n    COMPONENT_MAP[key] = components[component as never];\n\n    if (baseModelPropName !== DEFAULT_MODEL_PROP_NAME) {\n      COMPONENT_BIND_EVENT_MAP[key] = baseModelPropName;\n    }\n\n    // 覆盖特殊组件的modelPropName\n    if (modelPropNameMap && modelPropNameMap[key]) {\n      COMPONENT_BIND_EVENT_MAP[key] = modelPropNameMap[key];\n    }\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/form-api.ts",
    "content": "import type {\n  FormState,\n  GenericObject,\n  ResetFormOpts,\n  ValidationOptions,\n} from 'vee-validate';\n\nimport type { ComponentPublicInstance } from 'vue';\n\nimport type { Recordable } from '@vben-core/typings';\n\nimport type { FormActions, FormSchema, VbenFormProps } from './types';\n\nimport { isRef, toRaw } from 'vue';\n\nimport { Store } from '@vben-core/shared/store';\nimport {\n  bindMethods,\n  createMerge,\n  formatDate,\n  isDate,\n  isDayjsObject,\n  isFunction,\n  isObject,\n  mergeWithArrayOverride,\n  StateHandler,\n} from '@vben-core/shared/utils';\n\nfunction getDefaultState(): VbenFormProps {\n  return {\n    actionWrapperClass: '',\n    collapsed: false,\n    collapsedRows: 1,\n    collapseTriggerResize: false,\n    commonConfig: {},\n    handleReset: undefined,\n    handleSubmit: undefined,\n    handleValuesChange: undefined,\n    handleCollapsedChange: undefined,\n    layout: 'horizontal',\n    resetButtonOptions: {},\n    schema: [],\n    scrollToFirstError: false,\n    showCollapseButton: false,\n    showDefaultActions: true,\n    submitButtonOptions: {},\n    submitOnChange: false,\n    submitOnEnter: false,\n    wrapperClass: 'grid-cols-1',\n  };\n}\n\nexport class FormApi {\n  // private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;\n  public form = {} as FormActions;\n  isMounted = false;\n\n  public state: null | VbenFormProps = null;\n  stateHandler: StateHandler;\n\n  public store: Store<VbenFormProps>;\n\n  /**\n   * 组件实例映射\n   */\n  private componentRefMap: Map<string, unknown> = new Map();\n\n  // 最后一次点击提交时的表单值\n  private latestSubmissionValues: null | Recordable<any> = null;\n\n  private prevState: null | VbenFormProps = null;\n\n  constructor(options: VbenFormProps = {}) {\n    const { ...storeState } = options;\n\n    const defaultState = getDefaultState();\n\n    this.store = new Store<VbenFormProps>({\n      ...defaultState,\n      ...storeState,\n    });\n\n    this.store.subscribe((state) => {\n      this.prevState = this.state;\n      this.state = state;\n      this.updateState();\n    });\n\n    this.state = this.store.state;\n    this.stateHandler = new StateHandler();\n    bindMethods(this);\n  }\n\n  /**\n   * 获取字段组件实例\n   * @param fieldName 字段名\n   * @returns 组件实例\n   */\n  getFieldComponentRef<T = ComponentPublicInstance>(\n    fieldName: string,\n  ): T | undefined {\n    let target = this.componentRefMap.has(fieldName)\n      ? (this.componentRefMap.get(fieldName) as ComponentPublicInstance)\n      : undefined;\n    if (\n      target &&\n      target.$.type.name === 'AsyncComponentWrapper' &&\n      target.$.subTree.ref\n    ) {\n      if (Array.isArray(target.$.subTree.ref)) {\n        if (\n          target.$.subTree.ref.length > 0 &&\n          isRef(target.$.subTree.ref[0]?.r)\n        ) {\n          target = target.$.subTree.ref[0]?.r.value as ComponentPublicInstance;\n        }\n      } else if (isRef(target.$.subTree.ref.r)) {\n        target = target.$.subTree.ref.r.value as ComponentPublicInstance;\n      }\n    }\n    return target as T;\n  }\n\n  /**\n   * 获取当前聚焦的字段，如果没有聚焦的字段则返回undefined\n   */\n  getFocusedField() {\n    for (const fieldName of this.componentRefMap.keys()) {\n      const ref = this.getFieldComponentRef(fieldName);\n      if (ref) {\n        let el: HTMLElement | null = null;\n        if (ref instanceof HTMLElement) {\n          el = ref;\n        } else if (ref.$el instanceof HTMLElement) {\n          el = ref.$el;\n        }\n        if (!el) {\n          continue;\n        }\n        if (\n          el === document.activeElement ||\n          el.contains(document.activeElement)\n        ) {\n          return fieldName;\n        }\n      }\n    }\n    return undefined;\n  }\n\n  getLatestSubmissionValues() {\n    return this.latestSubmissionValues || {};\n  }\n\n  getState() {\n    return this.state;\n  }\n\n  async getValues<T = Recordable<any>>() {\n    const form = await this.getForm();\n    return (form.values ? this.handleRangeTimeValue(form.values) : {}) as T;\n  }\n\n  async isFieldValid(fieldName: string) {\n    const form = await this.getForm();\n    return form.isFieldValid(fieldName);\n  }\n\n  merge(formApi: FormApi) {\n    const chain = [this, formApi];\n    const proxy = new Proxy(formApi, {\n      get(target: any, prop: any) {\n        if (prop === 'merge') {\n          return (nextFormApi: FormApi) => {\n            chain.push(nextFormApi);\n            return proxy;\n          };\n        }\n        if (prop === 'submitAllForm') {\n          return async (needMerge: boolean = true) => {\n            try {\n              const results = await Promise.all(\n                chain.map(async (api) => {\n                  const validateResult = await api.validate();\n                  if (!validateResult.valid) {\n                    return;\n                  }\n                  const rawValues = toRaw((await api.getValues()) || {});\n                  return rawValues;\n                }),\n              );\n              if (needMerge) {\n                const mergedResults = Object.assign({}, ...results);\n                return mergedResults;\n              }\n              return results;\n            } catch (error) {\n              console.error('Validation error:', error);\n            }\n          };\n        }\n        return target[prop];\n      },\n    });\n\n    return proxy;\n  }\n\n  mount(formActions: FormActions, componentRefMap: Map<string, unknown>) {\n    if (!this.isMounted) {\n      Object.assign(this.form, formActions);\n      this.stateHandler.setConditionTrue();\n      this.setLatestSubmissionValues({\n        ...toRaw(this.handleRangeTimeValue(this.form.values)),\n      });\n      this.componentRefMap = componentRefMap;\n      this.isMounted = true;\n    }\n  }\n\n  /**\n   * 根据字段名移除表单项\n   * @param fields\n   */\n  async removeSchemaByFields(fields: string[]) {\n    const fieldSet = new Set(fields);\n    const schema = this.state?.schema ?? [];\n\n    const filterSchema = schema.filter((item) => !fieldSet.has(item.fieldName));\n\n    this.setState({\n      schema: filterSchema,\n    });\n  }\n\n  /**\n   * 重置表单\n   */\n  async resetForm(\n    state?: Partial<FormState<GenericObject>> | undefined,\n    opts?: Partial<ResetFormOpts>,\n  ) {\n    const form = await this.getForm();\n    return form.resetForm(state, opts);\n  }\n\n  async resetValidate() {\n    const form = await this.getForm();\n    const fields = Object.keys(form.errors.value);\n    fields.forEach((field) => {\n      form.setFieldError(field, undefined);\n    });\n  }\n\n  /**\n   * 滚动到第一个错误字段\n   * @param errors 验证错误对象\n   */\n  scrollToFirstError(errors: Record<string, any> | string) {\n    // https://github.com/logaretm/vee-validate/discussions/3835\n    const firstErrorFieldName =\n      typeof errors === 'string' ? errors : Object.keys(errors)[0];\n\n    if (!firstErrorFieldName) {\n      return;\n    }\n\n    let el = document.querySelector(\n      `[name=\"${firstErrorFieldName}\"]`,\n    ) as HTMLElement;\n\n    // 如果通过 name 属性找不到，尝试通过组件引用查找, 正常情况下不会走到这，怕哪天 vee-validate 改了 name 属性有个兜底的\n    if (!el) {\n      const componentRef = this.getFieldComponentRef(firstErrorFieldName);\n      if (componentRef && componentRef.$el instanceof HTMLElement) {\n        el = componentRef.$el;\n      }\n    }\n\n    if (el) {\n      // 滚动到错误字段，添加一些偏移量以确保字段完全可见\n      el.scrollIntoView({\n        behavior: 'smooth',\n        block: 'center',\n        inline: 'nearest',\n      });\n    }\n  }\n\n  async setFieldValue(field: string, value: any, shouldValidate?: boolean) {\n    const form = await this.getForm();\n    form.setFieldValue(field, value, shouldValidate);\n  }\n\n  setLatestSubmissionValues(values: null | Recordable<any>) {\n    this.latestSubmissionValues = { ...toRaw(values) };\n  }\n\n  setState(\n    stateOrFn:\n      | ((prev: VbenFormProps) => Partial<VbenFormProps>)\n      | Partial<VbenFormProps>,\n  ) {\n    if (isFunction(stateOrFn)) {\n      this.store.setState((prev) => {\n        return mergeWithArrayOverride(stateOrFn(prev), prev);\n      });\n    } else {\n      this.store.setState((prev) => mergeWithArrayOverride(stateOrFn, prev));\n    }\n  }\n\n  /**\n   * 设置表单值\n   * @param fields record\n   * @param filterFields 过滤不在schema中定义的字段 默认为true\n   * @param shouldValidate\n   */\n  async setValues(\n    fields: Record<string, any>,\n    filterFields: boolean = true,\n    shouldValidate: boolean = false,\n  ) {\n    const form = await this.getForm();\n    if (!filterFields) {\n      form.setValues(fields, shouldValidate);\n      return;\n    }\n\n    /**\n     * 合并算法有待改进，目前的算法不支持object类型的值。\n     * antd的日期时间相关组件的值类型为dayjs对象\n     * element-plus的日期时间相关组件的值类型可能为Date对象\n     * 以上两种类型需要排除深度合并\n     */\n    const fieldMergeFn = createMerge((obj, key, value) => {\n      if (key in obj) {\n        obj[key] =\n          !Array.isArray(obj[key]) &&\n          isObject(obj[key]) &&\n          !isDayjsObject(obj[key]) &&\n          !isDate(obj[key])\n            ? fieldMergeFn(value, obj[key])\n            : value;\n      }\n      return true;\n    });\n    const filteredFields = fieldMergeFn(fields, form.values);\n    form.setValues(filteredFields, shouldValidate);\n  }\n\n  async submitForm(e?: Event) {\n    e?.preventDefault();\n    e?.stopPropagation();\n    const form = await this.getForm();\n    await form.submitForm();\n    const rawValues = toRaw(await this.getValues());\n    await this.state?.handleSubmit?.(rawValues);\n\n    return rawValues;\n  }\n\n  unmount() {\n    this.form?.resetForm?.();\n    // this.state = null;\n    this.latestSubmissionValues = null;\n    this.isMounted = false;\n    this.stateHandler.reset();\n  }\n\n  updateSchema(schema: Partial<FormSchema>[]) {\n    const updated: Partial<FormSchema>[] = [...schema];\n    const hasField = updated.every(\n      (item) => Reflect.has(item, 'fieldName') && item.fieldName,\n    );\n\n    if (!hasField) {\n      console.error(\n        'All items in the schema array must have a valid `fieldName` property to be updated',\n      );\n      return;\n    }\n    const currentSchema = [...(this.state?.schema ?? [])];\n\n    const updatedMap: Record<string, any> = {};\n\n    updated.forEach((item) => {\n      if (item.fieldName) {\n        updatedMap[item.fieldName] = item;\n      }\n    });\n\n    currentSchema.forEach((schema, index) => {\n      const updatedData = updatedMap[schema.fieldName];\n      if (updatedData) {\n        currentSchema[index] = mergeWithArrayOverride(\n          updatedData,\n          schema,\n        ) as FormSchema;\n      }\n    });\n    this.setState({ schema: currentSchema });\n  }\n\n  async validate(opts?: Partial<ValidationOptions>) {\n    const form = await this.getForm();\n\n    const validateResult = await form.validate(opts);\n\n    if (Object.keys(validateResult?.errors ?? {}).length > 0) {\n      console.error('validate error', validateResult?.errors);\n\n      if (this.state?.scrollToFirstError) {\n        this.scrollToFirstError(validateResult.errors);\n      }\n    }\n    return validateResult;\n  }\n\n  async validateAndSubmitForm() {\n    const form = await this.getForm();\n    const { valid, errors } = await form.validate();\n    if (!valid) {\n      if (this.state?.scrollToFirstError) {\n        this.scrollToFirstError(errors);\n      }\n      return;\n    }\n    return await this.submitForm();\n  }\n\n  async validateField(fieldName: string, opts?: Partial<ValidationOptions>) {\n    const form = await this.getForm();\n    const validateResult = await form.validateField(fieldName, opts);\n\n    if (Object.keys(validateResult?.errors ?? {}).length > 0) {\n      console.error('validate error', validateResult?.errors);\n\n      if (this.state?.scrollToFirstError) {\n        this.scrollToFirstError(fieldName);\n      }\n    }\n    return validateResult;\n  }\n\n  private async getForm() {\n    if (!this.isMounted) {\n      // 等待form挂载\n      await this.stateHandler.waitForCondition();\n    }\n    if (!this.form?.meta) {\n      throw new Error('<VbenForm /> is not mounted');\n    }\n    return this.form;\n  }\n\n  private handleMultiFields = (originValues: Record<string, any>) => {\n    const arrayToStringFields = this.state?.arrayToStringFields;\n    if (!arrayToStringFields || !Array.isArray(arrayToStringFields)) {\n      return;\n    }\n\n    const processFields = (fields: string[], separator: string = ',') => {\n      this.processFields(fields, separator, originValues, (value, sep) => {\n        if (Array.isArray(value)) {\n          return value.join(sep);\n        } else if (typeof value === 'string') {\n          // 处理空字符串的情况\n          if (value === '') {\n            return [];\n          }\n          // 处理复杂分隔符的情况\n          const escapedSeparator = sep.replaceAll(\n            /[.*+?^${}()|[\\]\\\\]/g,\n            String.raw`\\$&`,\n          );\n          return value.split(new RegExp(escapedSeparator));\n        } else {\n          return value;\n        }\n      });\n    };\n\n    // 处理简单数组格式 ['field1', 'field2', ';'] 或 ['field1', 'field2']\n    if (arrayToStringFields.every((item) => typeof item === 'string')) {\n      const lastItem =\n        arrayToStringFields[arrayToStringFields.length - 1] || '';\n      const fields =\n        lastItem.length === 1\n          ? arrayToStringFields.slice(0, -1)\n          : arrayToStringFields;\n      const separator = lastItem.length === 1 ? lastItem : ',';\n      processFields(fields, separator);\n      return;\n    }\n\n    // 处理嵌套数组格式 [['field1'], ';']\n    arrayToStringFields.forEach((fieldConfig) => {\n      if (Array.isArray(fieldConfig)) {\n        const [fields, separator = ','] = fieldConfig;\n        // 根据类型定义，fields 应该始终是字符串数组\n        if (!Array.isArray(fields)) {\n          console.warn(\n            `Invalid field configuration: fields should be an array of strings, got ${typeof fields}`,\n          );\n          return;\n        }\n        processFields(fields, separator);\n      }\n    });\n  };\n\n  private handleRangeTimeValue = (originValues: Record<string, any>) => {\n    const values = { ...originValues };\n    const fieldMappingTime = this.state?.fieldMappingTime;\n\n    this.handleMultiFields(values);\n    if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) {\n      return values;\n    }\n\n    fieldMappingTime.forEach(\n      ([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => {\n        if (startTimeKey && endTimeKey && values[field] === null) {\n          Reflect.deleteProperty(values, startTimeKey);\n          Reflect.deleteProperty(values, endTimeKey);\n          // delete values[startTimeKey];\n          // delete values[endTimeKey];\n        }\n\n        if (!values[field]) {\n          Reflect.deleteProperty(values, field);\n          // delete values[field];\n          return;\n        }\n\n        const [startTime, endTime] = values[field];\n        if (format === null) {\n          values[startTimeKey] = startTime;\n          values[endTimeKey] = endTime;\n        } else if (isFunction(format)) {\n          values[startTimeKey] = format(startTime, startTimeKey);\n          values[endTimeKey] = format(endTime, endTimeKey);\n        } else {\n          const [startTimeFormat, endTimeFormat] = Array.isArray(format)\n            ? format\n            : [format, format];\n\n          values[startTimeKey] = startTime\n            ? formatDate(startTime, startTimeFormat)\n            : undefined;\n          values[endTimeKey] = endTime\n            ? formatDate(endTime, endTimeFormat)\n            : undefined;\n        }\n        // delete values[field];\n        Reflect.deleteProperty(values, field);\n      },\n    );\n    return values;\n  };\n\n  private processFields = (\n    fields: string[],\n    separator: string,\n    originValues: Record<string, any>,\n    transformFn: (value: any, separator: string) => any,\n  ) => {\n    fields.forEach((field) => {\n      const value = originValues[field];\n      if (value === undefined || value === null) {\n        return;\n      }\n      originValues[field] = transformFn(value, separator);\n    });\n  };\n\n  private updateState() {\n    const currentSchema = this.state?.schema ?? [];\n    const prevSchema = this.prevState?.schema ?? [];\n    // 进行了删除schema操作\n    if (currentSchema.length < prevSchema.length) {\n      const currentFields = new Set(\n        currentSchema.map((item) => item.fieldName),\n      );\n      const deletedSchema = prevSchema.filter(\n        (item) => !currentFields.has(item.fieldName),\n      );\n      for (const schema of deletedSchema) {\n        this.form?.setFieldValue?.(schema.fieldName, undefined);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/form-render/context.ts",
    "content": "import type { FormRenderProps } from '../types';\n\nimport { computed } from 'vue';\n\nimport { createContext } from '@vben-core/shadcn-ui';\n\nexport const [injectRenderFormProps, provideFormRenderProps] =\n  createContext<FormRenderProps>('FormRenderProps');\n\nexport const useFormContext = () => {\n  const formRenderProps = injectRenderFormProps();\n\n  const isVertical = computed(() => formRenderProps.layout === 'vertical');\n\n  const componentMap = computed(() => formRenderProps.componentMap);\n  const componentBindEventMap = computed(\n    () => formRenderProps.componentBindEventMap,\n  );\n  return {\n    componentBindEventMap,\n    componentMap,\n    isVertical,\n  };\n};\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/form-render/dependencies.ts",
    "content": "import type {\n  FormItemDependencies,\n  FormSchemaRuleType,\n  MaybeComponentProps,\n} from '../types';\n\nimport { computed, ref, watch } from 'vue';\n\nimport { get, isBoolean, isFunction } from '@vben-core/shared/utils';\n\nimport { useFormValues } from 'vee-validate';\n\nimport { injectRenderFormProps } from './context';\n\n/**\n * 解析Nested Objects对应的字段值\n * @param values 表单值\n * @param fieldName 字段名\n */\nfunction resolveValueByFieldName(\n  values: Record<string, any>,\n  fieldName: string,\n) {\n  // vee-validate：[] 表示禁用嵌套\n  if (fieldName.startsWith('[') && fieldName.endsWith(']')) {\n    const rawKey = fieldName.slice(1, -1);\n    return values[rawKey];\n  }\n\n  return get(values, fieldName);\n}\n\nexport default function useDependencies(\n  getDependencies: () => FormItemDependencies | undefined,\n) {\n  const values = useFormValues();\n\n  const formRenderProps = injectRenderFormProps();\n  const formApi = formRenderProps.form;\n\n  if (!formApi) {\n    throw new Error('Form api is required in useDependencies');\n  }\n\n  if (!values) {\n    throw new Error('useDependencies should be used within <VbenForm>');\n  }\n\n  const isIf = ref(true);\n  const isDisabled = ref(false);\n  const isShow = ref(true);\n  const isRequired = ref(false);\n  const dynamicComponentProps = ref<MaybeComponentProps>({});\n  const dynamicRules = ref<FormSchemaRuleType>();\n\n  const triggerFieldValues = computed(() => {\n    // 该字段可能会被多个字段触发\n    const triggerFields = getDependencies()?.triggerFields ?? [];\n    return triggerFields.map((dep) => {\n      return resolveValueByFieldName(values.value, dep);\n    });\n  });\n\n  const resetConditionState = () => {\n    isDisabled.value = false;\n    isIf.value = true;\n    isShow.value = true;\n    isRequired.value = false;\n    dynamicRules.value = undefined;\n    dynamicComponentProps.value = {};\n  };\n\n  watch(\n    [triggerFieldValues, getDependencies],\n    async ([_values, dependencies]) => {\n      if (!dependencies || !dependencies?.triggerFields?.length) {\n        return;\n      }\n      resetConditionState();\n      const {\n        componentProps,\n        disabled,\n        if: whenIf,\n        required,\n        rules,\n        show,\n        trigger,\n      } = dependencies;\n\n      // 1. 优先判断if，如果if为false，则不渲染dom，后续判断也不再执行\n      const formValues = values.value;\n\n      if (isFunction(whenIf)) {\n        isIf.value = !!(await whenIf(formValues, formApi));\n        // 不渲染\n        if (!isIf.value) return;\n      } else if (isBoolean(whenIf)) {\n        isIf.value = whenIf;\n        if (!isIf.value) return;\n      }\n\n      // 2. 判断show，如果show为false，则隐藏\n      if (isFunction(show)) {\n        isShow.value = !!(await show(formValues, formApi));\n      } else if (isBoolean(show)) {\n        isShow.value = show;\n      }\n\n      if (isFunction(componentProps)) {\n        dynamicComponentProps.value = await componentProps(formValues, formApi);\n      }\n\n      if (isFunction(rules)) {\n        dynamicRules.value = await rules(formValues, formApi);\n      }\n\n      if (isFunction(disabled)) {\n        isDisabled.value = !!(await disabled(formValues, formApi));\n      } else if (isBoolean(disabled)) {\n        isDisabled.value = disabled;\n      }\n\n      if (isFunction(required)) {\n        isRequired.value = !!(await required(formValues, formApi));\n      }\n\n      if (isFunction(trigger)) {\n        trigger(formValues, formApi);\n      }\n    },\n    { deep: true, immediate: true },\n  );\n\n  return {\n    dynamicComponentProps,\n    dynamicRules,\n    isDisabled,\n    isIf,\n    isRequired,\n    isShow,\n  };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/form-render/expandable.ts",
    "content": "import type { FormRenderProps } from '../types';\n\nimport { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue';\n\nimport {\n  breakpointsTailwind,\n  useBreakpoints,\n  useElementVisibility,\n} from '@vueuse/core';\n\n/**\n * 动态计算行数\n */\nexport function useExpandable(props: FormRenderProps) {\n  const wrapperRef = useTemplateRef<HTMLElement>('wrapperRef');\n  const isVisible = useElementVisibility(wrapperRef);\n  const rowMapping = ref<Record<number, number>>({});\n  // 是否已经计算过一次\n  const isCalculated = ref(false);\n\n  const breakpoints = useBreakpoints(breakpointsTailwind);\n\n  const keepFormItemIndex = computed(() => {\n    const rows = props.collapsedRows ?? 1;\n    const mapping = rowMapping.value;\n    let maxItem = 0;\n    for (let index = 1; index <= rows; index++) {\n      maxItem += mapping?.[index] ?? 0;\n    }\n    // 保持一行\n    return maxItem - 1 || 1;\n  });\n\n  watch(\n    [\n      () => props.showCollapseButton,\n      () => breakpoints.active().value,\n      () => props.schema?.length,\n      () => isVisible.value,\n    ],\n    async ([val]) => {\n      if (val) {\n        await nextTick();\n        rowMapping.value = {};\n        isCalculated.value = false;\n        await calculateRowMapping();\n      }\n    },\n  );\n\n  async function calculateRowMapping() {\n    if (!props.showCollapseButton) {\n      return;\n    }\n\n    await nextTick();\n    if (!wrapperRef.value) {\n      return;\n    }\n    // 小屏幕不计算\n    // if (breakpoints.smaller('sm').value) {\n    //   // 保持一行\n    //   rowMapping.value = { 1: 2 };\n    //   return;\n    // }\n\n    const formItems = [...wrapperRef.value.children];\n\n    const container = wrapperRef.value;\n    const containerStyles = window.getComputedStyle(container);\n    const rowHeights = containerStyles\n      .getPropertyValue('grid-template-rows')\n      .split(' ');\n\n    const containerRect = container?.getBoundingClientRect();\n\n    formItems.forEach((el) => {\n      const itemRect = el.getBoundingClientRect();\n\n      // 计算元素在第几行\n      const itemTop = itemRect.top - containerRect.top;\n      let rowStart = 0;\n      let cumulativeHeight = 0;\n\n      for (const [i, rowHeight] of rowHeights.entries()) {\n        cumulativeHeight += Number.parseFloat(rowHeight);\n        if (itemTop < cumulativeHeight) {\n          rowStart = i + 1;\n          break;\n        }\n      }\n      if (rowStart > (props?.collapsedRows ?? 1)) {\n        return;\n      }\n      rowMapping.value[rowStart] = (rowMapping.value[rowStart] ?? 0) + 1;\n      isCalculated.value = true;\n    });\n  }\n\n  onMounted(() => {\n    calculateRowMapping();\n  });\n\n  return { isCalculated, keepFormItemIndex, wrapperRef };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/form-render/form-field.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ZodType } from 'zod';\n\nimport type { FormActions, FormSchema, MaybeComponentProps } from '../types';\n\nimport { computed, nextTick, onUnmounted, useTemplateRef, watch } from 'vue';\n\nimport { CircleAlert } from '@vben-core/icons';\nimport {\n  FormControl,\n  FormDescription,\n  FormField,\n  FormItem,\n  FormMessage,\n  VbenRenderContent,\n  VbenTooltip,\n} from '@vben-core/shadcn-ui';\nimport { cn, isFunction, isObject, isString } from '@vben-core/shared/utils';\n\nimport { toTypedSchema } from '@vee-validate/zod';\nimport { useFieldError, useFormValues } from 'vee-validate';\n\nimport { injectComponentRefMap } from '../use-form-context';\nimport { injectRenderFormProps, useFormContext } from './context';\nimport useDependencies from './dependencies';\nimport FormLabel from './form-label.vue';\nimport { isEventObjectLike } from './helper';\n\ninterface Props extends FormSchema {}\n\nconst {\n  colon,\n  commonComponentProps,\n  component,\n  componentProps,\n  dependencies,\n  description,\n  disabled,\n  disabledOnChangeListener,\n  disabledOnInputListener,\n  emptyStateValue,\n  fieldName,\n  formFieldProps,\n  hide,\n  label,\n  labelClass,\n  labelWidth,\n  modelPropName,\n  renderComponentContent,\n  rules,\n} = defineProps<\n  Props & {\n    commonComponentProps: MaybeComponentProps;\n  }\n>();\n\nconst { componentBindEventMap, componentMap, isVertical } = useFormContext();\nconst formRenderProps = injectRenderFormProps();\nconst values = useFormValues();\nconst errors = useFieldError(fieldName);\nconst fieldComponentRef = useTemplateRef<HTMLInputElement>('fieldComponentRef');\nconst formApi = formRenderProps.form;\nconst compact = computed(() => formRenderProps.compact);\nconst isInValid = computed(() => errors.value?.length > 0);\n\nfunction getFormApi(): FormActions {\n  if (!formApi) {\n    throw new Error('Form api is required in <FormField />');\n  }\n\n  return formApi;\n}\n\nconst FieldComponent = computed(() => {\n  const finalComponent = isString(component)\n    ? componentMap.value[component]\n    : component;\n  if (!finalComponent) {\n    // 组件未注册\n    console.warn(`Component ${component} is not registered`);\n  }\n  return finalComponent;\n});\n\nconst {\n  dynamicComponentProps,\n  dynamicRules,\n  isDisabled,\n  isIf,\n  isRequired,\n  isShow,\n} = useDependencies(() => dependencies);\n\nconst labelStyle = computed(() => {\n  return labelClass?.includes('w-') || isVertical.value\n    ? {}\n    : {\n        width: `${labelWidth}px`,\n      };\n});\n\nconst currentRules = computed(() => {\n  return dynamicRules.value || rules;\n});\n\nconst visible = computed(() => {\n  return !hide && isIf.value && isShow.value;\n});\n\nconst shouldRequired = computed(() => {\n  if (!visible.value) {\n    return false;\n  }\n\n  if (!currentRules.value) {\n    return isRequired.value;\n  }\n\n  if (isRequired.value) {\n    return true;\n  }\n\n  if (isString(currentRules.value)) {\n    return ['required', 'selectRequired'].includes(currentRules.value);\n  }\n\n  let isOptional = currentRules?.value?.isOptional?.();\n\n  // 如果有设置默认值，则不是必填，需要特殊处理\n  const typeName = currentRules?.value?._def?.typeName;\n  if (typeName === 'ZodDefault') {\n    const innerType = currentRules?.value?._def.innerType;\n    if (innerType) {\n      isOptional = innerType.isOptional?.();\n    }\n  }\n\n  return !isOptional;\n});\n\nconst fieldRules = computed(() => {\n  if (!visible.value) {\n    return null;\n  }\n\n  let rules = currentRules.value;\n  if (!rules) {\n    return isRequired.value ? 'required' : null;\n  }\n\n  if (isString(rules)) {\n    return rules;\n  }\n\n  const isOptional = !shouldRequired.value;\n  if (!isOptional) {\n    const unwrappedRules = (rules as any)?.unwrap?.();\n    if (unwrappedRules) {\n      rules = unwrappedRules;\n    }\n  }\n  return toTypedSchema(rules as ZodType);\n});\n\nconst computedProps = computed(() => {\n  const finalComponentProps = isFunction(componentProps)\n    ? componentProps(values.value, getFormApi())\n    : componentProps;\n\n  return {\n    ...commonComponentProps,\n    ...finalComponentProps,\n    ...dynamicComponentProps.value,\n  };\n});\n\nwatch(\n  () => computedProps.value?.autofocus,\n  (value) => {\n    if (value === true) {\n      nextTick(() => {\n        autofocus();\n      });\n    }\n  },\n  { immediate: true },\n);\n\nconst shouldDisabled = computed(() => {\n  return isDisabled.value || disabled || computedProps.value?.disabled;\n});\n\nconst customContentRender = computed(() => {\n  if (!isFunction(renderComponentContent)) {\n    return {};\n  }\n  return renderComponentContent(values.value, getFormApi());\n});\n\nconst renderContentKey = computed(() => {\n  return Object.keys(customContentRender.value);\n});\n\nconst fieldProps = computed(() => {\n  const rules = fieldRules.value;\n  return {\n    keepValue: true,\n    label: isString(label) ? label : '',\n    ...(rules ? { rules } : {}),\n    ...(formFieldProps as Record<string, any>),\n  };\n});\n\nfunction fieldBindEvent(slotProps: Record<string, any>) {\n  const modelValue = slotProps.componentField.modelValue;\n  const handler = slotProps.componentField['onUpdate:modelValue'];\n\n  const bindEventField =\n    modelPropName ||\n    (isString(component) ? componentBindEventMap.value?.[component] : null);\n\n  let value = modelValue;\n  // antd design 的一些组件会传递一个 event 对象\n  if (modelValue && isObject(modelValue) && bindEventField) {\n    value = isEventObjectLike(modelValue)\n      ? modelValue?.target?.[bindEventField]\n      : (modelValue?.[bindEventField] ?? modelValue);\n  }\n\n  if (bindEventField) {\n    return {\n      [`onUpdate:${bindEventField}`]: handler,\n      [bindEventField]: value === undefined ? emptyStateValue : value,\n      onChange: disabledOnChangeListener\n        ? undefined\n        : (e: Record<string, any>) => {\n            const shouldUnwrap = isEventObjectLike(e);\n            const onChange = slotProps?.componentField?.onChange;\n            if (!shouldUnwrap) {\n              return onChange?.(e);\n            }\n\n            return onChange?.(e?.target?.[bindEventField] ?? e);\n          },\n      ...(disabledOnInputListener ? { onInput: undefined } : {}),\n    };\n  }\n  return {\n    ...(disabledOnInputListener ? { onInput: undefined } : {}),\n    ...(disabledOnChangeListener ? { onChange: undefined } : {}),\n  };\n}\n\nfunction createComponentProps(slotProps: Record<string, any>) {\n  const bindEvents = fieldBindEvent(slotProps);\n\n  const binds = {\n    ...slotProps.componentField,\n    ...computedProps.value,\n    ...bindEvents,\n    ...(Reflect.has(computedProps.value, 'onChange')\n      ? { onChange: computedProps.value.onChange }\n      : {}),\n    ...(Reflect.has(computedProps.value, 'onInput')\n      ? { onInput: computedProps.value.onInput }\n      : {}),\n  };\n\n  return binds;\n}\n\nfunction autofocus() {\n  if (\n    fieldComponentRef.value &&\n    isFunction(fieldComponentRef.value.focus) &&\n    // 检查当前是否有元素被聚焦\n    document.activeElement !== fieldComponentRef.value\n  ) {\n    fieldComponentRef.value?.focus?.();\n  }\n}\nconst componentRefMap = injectComponentRefMap();\nwatch(fieldComponentRef, (componentRef) => {\n  componentRefMap?.set(fieldName, componentRef);\n});\nonUnmounted(() => {\n  if (componentRefMap?.has(fieldName)) {\n    componentRefMap.delete(fieldName);\n  }\n});\n</script>\n\n<template>\n  <FormField\n    v-if=\"!hide && isIf\"\n    v-bind=\"fieldProps\"\n    v-slot=\"slotProps\"\n    :name=\"fieldName\"\n  >\n    <FormItem\n      v-show=\"isShow\"\n      :class=\"{\n        'form-valid-error': isInValid,\n        'form-is-required': shouldRequired,\n        'flex-col': isVertical,\n        'flex-row items-center': !isVertical,\n        'pb-4': !compact,\n        'pb-2': compact,\n      }\"\n      class=\"relative flex\"\n      v-bind=\"$attrs\"\n    >\n      <FormLabel\n        v-if=\"!hideLabel\"\n        :class=\"\n          cn(\n            'flex leading-6',\n            {\n              'mr-2 shrink-0 justify-end': !isVertical,\n              'mb-1 flex-row': isVertical,\n            },\n            labelClass,\n          )\n        \"\n        :help=\"help\"\n        :colon=\"colon\"\n        :label=\"label\"\n        :required=\"shouldRequired && !hideRequiredMark\"\n        :style=\"labelStyle\"\n      >\n        <template v-if=\"label\">\n          <VbenRenderContent :content=\"label\" />\n        </template>\n      </FormLabel>\n      <div class=\"flex-auto overflow-hidden p-px\">\n        <div :class=\"cn('relative flex w-full items-center', wrapperClass)\">\n          <FormControl :class=\"cn(controlClass)\">\n            <slot\n              v-bind=\"{\n                ...slotProps,\n                ...createComponentProps(slotProps),\n                disabled: shouldDisabled,\n                isInValid,\n              }\"\n            >\n              <component\n                :is=\"FieldComponent\"\n                ref=\"fieldComponentRef\"\n                :class=\"{\n                  'border-destructive hover:border-destructive/80 focus:border-destructive focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':\n                    isInValid,\n                }\"\n                v-bind=\"createComponentProps(slotProps)\"\n                :disabled=\"shouldDisabled\"\n              >\n                <template\n                  v-for=\"name in renderContentKey\"\n                  :key=\"name\"\n                  #[name]=\"renderSlotProps\"\n                >\n                  <VbenRenderContent\n                    :content=\"customContentRender[name]\"\n                    v-bind=\"{ ...renderSlotProps, formContext: slotProps }\"\n                  />\n                </template>\n                <!-- <slot></slot> -->\n              </component>\n              <VbenTooltip\n                v-if=\"compact && isInValid\"\n                :delay-duration=\"300\"\n                side=\"left\"\n              >\n                <template #trigger>\n                  <slot name=\"trigger\">\n                    <CircleAlert\n                      :class=\"\n                        cn(\n                          'inline-flex size-5 cursor-pointer text-foreground/80 hover:text-foreground',\n                        )\n                      \"\n                    />\n                  </slot>\n                </template>\n                <FormMessage />\n              </VbenTooltip>\n            </slot>\n          </FormControl>\n          <!-- 自定义后缀 -->\n          <div v-if=\"suffix\" class=\"ml-1\">\n            <VbenRenderContent :content=\"suffix\" />\n          </div>\n        </div>\n        <FormDescription v-if=\"description\" class=\"text-xs\">\n          <VbenRenderContent :content=\"description\" />\n        </FormDescription>\n\n        <Transition name=\"slide-up\" v-if=\"!compact\">\n          <FormMessage class=\"absolute\" />\n        </Transition>\n      </div>\n    </FormItem>\n  </FormField>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/form-render/form-label.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CustomRenderType } from '../types';\n\nimport {\n  FormLabel,\n  VbenHelpTooltip,\n  VbenRenderContent,\n} from '@vben-core/shadcn-ui';\nimport { cn } from '@vben-core/shared/utils';\n\ninterface Props {\n  class?: string;\n  colon?: boolean;\n  help?: CustomRenderType;\n  label?: CustomRenderType;\n  required?: boolean;\n}\n\nconst props = defineProps<Props>();\n</script>\n\n<template>\n  <FormLabel :class=\"cn('flex items-center', props.class)\">\n    <span v-if=\"required\" class=\"mr-0.5 text-destructive\">*</span>\n    <slot></slot>\n    <VbenHelpTooltip v-if=\"help\" trigger-class=\"size-3.5 ml-1\">\n      <VbenRenderContent :content=\"help\" />\n    </VbenHelpTooltip>\n    <span v-if=\"colon && label\" class=\"ml-0.5\">:</span>\n  </FormLabel>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/form-render/form.vue",
    "content": "<script setup lang=\"ts\">\nimport type { GenericObject } from 'vee-validate';\nimport type { ZodTypeAny } from 'zod';\n\nimport type {\n  FormCommonConfig,\n  FormRenderProps,\n  FormSchema,\n  FormShape,\n} from '../types';\n\nimport { computed } from 'vue';\n\nimport { Form } from '@vben-core/shadcn-ui';\nimport {\n  cn,\n  isFunction,\n  isString,\n  mergeWithArrayOverride,\n} from '@vben-core/shared/utils';\n\nimport { provideFormRenderProps } from './context';\nimport { useExpandable } from './expandable';\nimport FormField from './form-field.vue';\nimport { getBaseRules, getDefaultValueInZodStack } from './helper';\n\ninterface Props extends FormRenderProps {}\n\nconst props = withDefaults(\n  defineProps<Props & { globalCommonConfig?: FormCommonConfig }>(),\n  {\n    collapsedRows: 1,\n    commonConfig: () => ({}),\n    globalCommonConfig: () => ({}),\n    showCollapseButton: false,\n    wrapperClass: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',\n  },\n);\n\nconst emits = defineEmits<{\n  submit: [event: any];\n}>();\n\nconst wrapperClass = computed(() => {\n  const cls = ['flex'];\n  if (props.layout === 'inline') {\n    cls.push('flex-wrap gap-x-2');\n  } else {\n    cls.push(props.compact ? 'gap-x-2' : 'gap-x-4', 'flex-col grid');\n  }\n  return cn(...cls, props.wrapperClass);\n});\n\nprovideFormRenderProps(props);\n\n// @ts-expect-error unused\nconst { isCalculated, keepFormItemIndex, wrapperRef } = useExpandable(props);\n\nconst shapes = computed(() => {\n  const resultShapes: FormShape[] = [];\n  props.schema?.forEach((schema) => {\n    const { fieldName } = schema;\n    const rules = schema.rules as ZodTypeAny;\n\n    let typeName = '';\n    if (rules && !isString(rules)) {\n      typeName = rules._def.typeName;\n    }\n\n    const baseRules = getBaseRules(rules) as ZodTypeAny;\n\n    resultShapes.push({\n      default: getDefaultValueInZodStack(rules),\n      fieldName,\n      required: !['ZodNullable', 'ZodOptional'].includes(typeName),\n      rules: baseRules,\n    });\n  });\n  return resultShapes;\n});\n\nconst formComponent = computed(() => (props.form ? 'form' : Form));\n\nconst formComponentProps = computed(() => {\n  return props.form\n    ? {\n        onSubmit: props.form.handleSubmit((val) => emits('submit', val)),\n      }\n    : {\n        onSubmit: (val: GenericObject) => emits('submit', val),\n      };\n});\n\nconst formCollapsed = computed(() => {\n  return props.collapsed && isCalculated.value;\n});\n\nconst computedSchema = computed(\n  (): (Omit<FormSchema, 'formFieldProps'> & {\n    commonComponentProps: Record<string, any>;\n    formFieldProps: Record<string, any>;\n  })[] => {\n    const {\n      colon = false,\n      componentProps = {},\n      controlClass = '',\n      disabled,\n      disabledOnChangeListener = true,\n      disabledOnInputListener = true,\n      emptyStateValue = undefined,\n      formFieldProps = {},\n      formItemClass = '',\n      hideLabel = false,\n      hideRequiredMark = false,\n      labelClass = '',\n      labelWidth = 100,\n      modelPropName = '',\n      wrapperClass = '',\n    } = mergeWithArrayOverride(props.commonConfig, props.globalCommonConfig);\n    return (props.schema || []).map((schema, index) => {\n      const keepIndex = keepFormItemIndex.value;\n\n      const hidden =\n        // 折叠状态 & 显示折叠按钮 & 当前索引大于保留索引\n        props.showCollapseButton && !!formCollapsed.value && keepIndex\n          ? keepIndex <= index\n          : false;\n\n      // 处理函数形式的formItemClass\n      let resolvedSchemaFormItemClass = schema.formItemClass;\n      if (isFunction(schema.formItemClass)) {\n        try {\n          resolvedSchemaFormItemClass = schema.formItemClass();\n        } catch (error) {\n          console.error('Error calling formItemClass function:', error);\n          resolvedSchemaFormItemClass = '';\n        }\n      }\n\n      return {\n        colon,\n        disabled,\n        disabledOnChangeListener,\n        disabledOnInputListener,\n        emptyStateValue,\n        hideLabel,\n        hideRequiredMark,\n        labelWidth,\n        modelPropName,\n        wrapperClass,\n        ...schema,\n        commonComponentProps: componentProps,\n        componentProps: schema.componentProps,\n        controlClass: cn(controlClass, schema.controlClass),\n        formFieldProps: {\n          ...formFieldProps,\n          ...schema.formFieldProps,\n        },\n        formItemClass: cn(\n          'shrink-0',\n          { hidden },\n          formItemClass,\n          resolvedSchemaFormItemClass,\n        ),\n        labelClass: cn(labelClass, schema.labelClass),\n      };\n    });\n  },\n);\n</script>\n\n<template>\n  <component :is=\"formComponent\" v-bind=\"formComponentProps\">\n    <div ref=\"wrapperRef\" :class=\"wrapperClass\">\n      <template v-for=\"cSchema in computedSchema\" :key=\"cSchema.fieldName\">\n        <!-- <div v-if=\"$slots[cSchema.fieldName]\" :class=\"cSchema.formItemClass\">\n          <slot :definition=\"cSchema\" :name=\"cSchema.fieldName\"> </slot>\n        </div> -->\n        <FormField\n          v-bind=\"cSchema\"\n          :class=\"cSchema.formItemClass\"\n          :rules=\"cSchema.rules\"\n        >\n          <template #default=\"slotProps\">\n            <slot v-bind=\"slotProps\" :name=\"cSchema.fieldName\"> </slot>\n          </template>\n        </FormField>\n      </template>\n      <slot :shapes=\"shapes\"></slot>\n    </div>\n  </component>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/form-render/helper.ts",
    "content": "import type {\n  AnyZodObject,\n  ZodDefault,\n  ZodEffects,\n  ZodNumber,\n  ZodString,\n  ZodTypeAny,\n} from 'zod';\n\nimport { isObject, isString } from '@vben-core/shared/utils';\n\n/**\n * Get the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseRules<\n  ChildType extends AnyZodObject | ZodTypeAny = ZodTypeAny,\n>(schema: ChildType | ZodEffects<ChildType>): ChildType | null {\n  if (!schema || isString(schema)) return null;\n  if ('innerType' in schema._def)\n    return getBaseRules(schema._def.innerType as ChildType);\n\n  if ('schema' in schema._def)\n    return getBaseRules(schema._def.schema as ChildType);\n\n  return schema as ChildType;\n}\n\n/**\n * Search for a \"ZodDefault\" in the Zod stack and return its value.\n */\nexport function getDefaultValueInZodStack(schema: ZodTypeAny): any {\n  if (!schema || isString(schema)) {\n    return;\n  }\n  const typedSchema = schema as unknown as ZodDefault<ZodNumber | ZodString>;\n\n  if (typedSchema._def.typeName === 'ZodDefault')\n    return typedSchema._def.defaultValue();\n\n  if ('innerType' in typedSchema._def) {\n    return getDefaultValueInZodStack(\n      typedSchema._def.innerType as unknown as ZodTypeAny,\n    );\n  }\n  if ('schema' in typedSchema._def) {\n    return getDefaultValueInZodStack(\n      (typedSchema._def as any).schema as ZodTypeAny,\n    );\n  }\n\n  return undefined;\n}\n\nexport function isEventObjectLike(obj: any) {\n  if (!obj || !isObject(obj)) {\n    return false;\n  }\n  return Reflect.has(obj, 'target') && Reflect.has(obj, 'stopPropagation');\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/form-render/index.ts",
    "content": "export { default as FormField } from './form-field.vue';\nexport { default as FormLabel } from './form-label.vue';\nexport { default as Form } from './form.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/index.ts",
    "content": "export { setupVbenForm } from './config';\n\nexport type {\n  BaseFormComponentType,\n  ExtendedFormApi,\n  VbenFormProps,\n  FormSchema as VbenFormSchema,\n} from './types';\n\nexport * from './use-vben-form';\n// export { default as VbenForm } from './vben-form.vue';\nexport * as z from 'zod';\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/types.ts",
    "content": "import type { FieldOptions, FormContext, GenericObject } from 'vee-validate';\nimport type { ZodTypeAny } from 'zod';\n\nimport type { Component, HtmlHTMLAttributes, Ref } from 'vue';\n\nimport type { VbenButtonProps } from '@vben-core/shadcn-ui';\nimport type { ClassType, MaybeComputedRef } from '@vben-core/typings';\n\nimport type { FormApi } from './form-api';\n\nexport type FormLayout = 'horizontal' | 'inline' | 'vertical';\n\nexport type BaseFormComponentType =\n  | 'DefaultButton'\n  | 'PrimaryButton'\n  | 'VbenCheckbox'\n  | 'VbenInput'\n  | 'VbenInputPassword'\n  | 'VbenPinInput'\n  | 'VbenSelect'\n  | (Record<never, never> & string);\n\ntype Breakpoints = '2xl:' | '3xl:' | '' | 'lg:' | 'md:' | 'sm:' | 'xl:';\n\ntype GridCols = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;\n\nexport type WrapperClassType =\n  | `${Breakpoints}grid-cols-${GridCols}`\n  | (Record<never, never> & string);\n\nexport type FormItemClassType =\n  | `${Breakpoints}cols-end-${'auto' | GridCols}`\n  | `${Breakpoints}cols-span-${'auto' | 'full' | GridCols}`\n  | `${Breakpoints}cols-start-${'auto' | GridCols}`\n  | (Record<never, never> & string)\n  | WrapperClassType;\n\nexport type FormFieldOptions = Partial<\n  FieldOptions & {\n    validateOnBlur?: boolean;\n    validateOnChange?: boolean;\n    validateOnInput?: boolean;\n    validateOnModelUpdate?: boolean;\n  }\n>;\n\nexport interface FormShape {\n  /** 默认值 */\n  default?: any;\n  /** 字段名 */\n  fieldName: string;\n  /** 是否必填 */\n  required?: boolean;\n  rules?: ZodTypeAny;\n}\n\nexport type MaybeComponentPropKey =\n  | 'options'\n  | 'placeholder'\n  | 'title'\n  | keyof HtmlHTMLAttributes\n  | (Record<never, never> & string);\n\nexport type MaybeComponentProps = { [K in MaybeComponentPropKey]?: any };\n\nexport type FormActions = FormContext<GenericObject>;\n\nexport type CustomRenderType = (() => Component | string) | string;\n\nexport type FormSchemaRuleType =\n  | 'required'\n  | 'selectRequired'\n  | null\n  | (Record<never, never> & string)\n  | ZodTypeAny;\n\ntype FormItemDependenciesCondition<T = boolean | PromiseLike<boolean>> = (\n  value: Partial<Record<string, any>>,\n  actions: FormActions,\n) => T;\n\ntype FormItemDependenciesConditionWithRules = (\n  value: Partial<Record<string, any>>,\n  actions: FormActions,\n) => FormSchemaRuleType | PromiseLike<FormSchemaRuleType>;\n\ntype FormItemDependenciesConditionWithProps = (\n  value: Partial<Record<string, any>>,\n  actions: FormActions,\n) => MaybeComponentProps | PromiseLike<MaybeComponentProps>;\n\nexport interface FormItemDependencies {\n  /**\n   * 组件参数\n   * @returns 组件参数\n   */\n  componentProps?: FormItemDependenciesConditionWithProps;\n  /**\n   * 是否禁用\n   * @returns 是否禁用\n   */\n  disabled?: boolean | FormItemDependenciesCondition;\n  /**\n   * 是否渲染（删除dom）\n   * @returns 是否渲染\n   */\n  if?: boolean | FormItemDependenciesCondition;\n  /**\n   * 是否必填\n   * @returns 是否必填\n   */\n  required?: FormItemDependenciesCondition;\n  /**\n   * 字段规则\n   */\n  rules?: FormItemDependenciesConditionWithRules;\n  /**\n   * 是否隐藏(Css)\n   * @returns 是否隐藏\n   */\n  show?: boolean | FormItemDependenciesCondition;\n  /**\n   * 任意触发都会执行\n   */\n  trigger?: FormItemDependenciesCondition<void>;\n  /**\n   * 触发字段\n   */\n  triggerFields: string[];\n}\n\ntype ComponentProps =\n  | ((\n      value: Partial<Record<string, any>>,\n      actions: FormActions,\n    ) => MaybeComponentProps)\n  | MaybeComponentProps;\n\nexport interface FormCommonConfig {\n  /**\n   * 在Label后显示一个冒号\n   */\n  colon?: boolean;\n  /**\n   * 所有表单项的props\n   */\n  componentProps?: ComponentProps;\n  /**\n   * 所有表单项的控件样式\n   */\n  controlClass?: string;\n  /**\n   * 所有表单项的禁用状态\n   * @default false\n   */\n  disabled?: boolean;\n  /**\n   * 是否禁用所有表单项的change事件监听\n   * @default true\n   */\n  disabledOnChangeListener?: boolean;\n  /**\n   * 是否禁用所有表单项的input事件监听\n   * @default true\n   */\n  disabledOnInputListener?: boolean;\n  /**\n   * 所有表单项的空状态值,默认都是undefined，naive-ui的空状态值是null\n   */\n  emptyStateValue?: null | undefined;\n  /**\n   * 所有表单项的控件样式\n   * @default {}\n   */\n  formFieldProps?: FormFieldOptions;\n  /**\n   * 所有表单项的栅格布局，支持函数形式\n   * @default \"\"\n   */\n  formItemClass?: (() => string) | string;\n  /**\n   * 隐藏所有表单项label\n   * @default false\n   */\n  hideLabel?: boolean;\n  /**\n   * 是否隐藏必填标记\n   * @default false\n   */\n  hideRequiredMark?: boolean;\n  /**\n   * 所有表单项的label样式\n   * @default \"\"\n   */\n  labelClass?: string;\n  /**\n   * 所有表单项的label宽度\n   */\n  labelWidth?: number;\n  /**\n   * 所有表单项的model属性名\n   * @default \"modelValue\"\n   */\n  modelPropName?: string;\n  /**\n   * 所有表单项的wrapper样式\n   */\n  wrapperClass?: string;\n}\n\ntype RenderComponentContentType = (\n  value: Partial<Record<string, any>>,\n  api: FormActions,\n) => Record<string, any>;\n\nexport type HandleSubmitFn = (\n  values: Record<string, any>,\n) => Promise<void> | void;\n\nexport type HandleResetFn = (\n  values: Record<string, any>,\n) => Promise<void> | void;\n\nexport type FieldMappingTime = [\n  string,\n  [string, string],\n  (\n    | ((value: any, fieldName: string) => any)\n    | [string, string]\n    | null\n    | string\n  )?,\n][];\n\nexport type ArrayToStringFields = Array<\n  | [string[], string?] // 嵌套数组格式，可选分隔符\n  | string // 单个字段，使用默认分隔符\n  | string[] // 简单数组格式，最后一个元素可以是分隔符\n>;\n\nexport interface FormSchema<\n  T extends BaseFormComponentType = BaseFormComponentType,\n> extends FormCommonConfig {\n  /** 组件 */\n  component: Component | T;\n  /** 组件参数 */\n  componentProps?: ComponentProps;\n  /** 默认值 */\n  defaultValue?: any;\n  /** 依赖 */\n  dependencies?: FormItemDependencies;\n  /** 描述 */\n  description?: CustomRenderType;\n  /** 字段名 */\n  fieldName: string;\n  /** 帮助信息 */\n  help?: CustomRenderType;\n  /** 是否隐藏表单项 */\n  hide?: boolean;\n  /** 表单项 */\n  label?: CustomRenderType;\n  // 自定义组件内部渲染\n  renderComponentContent?: RenderComponentContentType;\n  /** 字段规则 */\n  rules?: FormSchemaRuleType;\n  /** 后缀 */\n  suffix?: CustomRenderType;\n}\n\nexport interface FormFieldProps extends FormSchema {\n  required?: boolean;\n}\n\nexport interface FormRenderProps<\n  T extends BaseFormComponentType = BaseFormComponentType,\n> {\n  /**\n   * 表单字段数组映射字符串配置 默认使用\",\"\n   */\n  arrayToStringFields?: ArrayToStringFields;\n  /**\n   * 是否折叠，在showCollapseButton=true下生效\n   * true:折叠 false:展开\n   */\n  collapsed?: boolean;\n  /**\n   * 折叠时保持行数\n   * @default 1\n   */\n  collapsedRows?: number;\n  /**\n   * 是否触发resize事件\n   * @default false\n   */\n  collapseTriggerResize?: boolean;\n  /**\n   * 表单项通用后备配置，当子项目没配置时使用这里的配置，子项目配置优先级高于此配置\n   */\n  commonConfig?: FormCommonConfig;\n  /**\n   * 紧凑模式（移除表单每一项底部为校验信息预留的空间）\n   */\n  compact?: boolean;\n  /**\n   * 组件v-model事件绑定\n   */\n  componentBindEventMap?: Partial<Record<BaseFormComponentType, string>>;\n  /**\n   * 组件集合\n   */\n  componentMap: Record<BaseFormComponentType, Component>;\n  /**\n   * 表单字段映射到时间格式\n   */\n  fieldMappingTime?: FieldMappingTime;\n  /**\n   * 表单实例\n   */\n  form?: FormContext<GenericObject>;\n  /**\n   * 表单项布局\n   */\n  layout?: FormLayout;\n  /**\n   * 表单定义\n   */\n  schema?: FormSchema<T>[];\n\n  /**\n   * 是否显示展开/折叠\n   */\n  showCollapseButton?: boolean;\n  /**\n   * 格式化日期\n   */\n\n  /**\n   * 表单栅格布局\n   * @default \"grid-cols-1\"\n   */\n  wrapperClass?: WrapperClassType;\n}\n\nexport interface ActionButtonOptions extends VbenButtonProps {\n  [key: string]: any;\n  content?: MaybeComputedRef<string>;\n  show?: boolean;\n}\n\nexport interface VbenFormProps<\n  T extends BaseFormComponentType = BaseFormComponentType,\n> extends Omit<\n  FormRenderProps<T>,\n  'componentBindEventMap' | 'componentMap' | 'form'\n> {\n  /**\n   * 操作按钮是否反转（提交按钮前置）\n   */\n  actionButtonsReverse?: boolean;\n  /**\n   * 操作按钮组的样式\n   * newLine: 在新行显示。rowEnd: 在行内显示，靠右对齐（默认）。inline: 使用grid默认样式\n   */\n  actionLayout?: 'inline' | 'newLine' | 'rowEnd';\n  /**\n   * 操作按钮组显示位置，默认靠右显示\n   */\n  actionPosition?: 'center' | 'left' | 'right';\n  /**\n   * 表单操作区域class\n   */\n  actionWrapperClass?: ClassType;\n  /**\n   * 表单字段数组映射字符串配置 默认使用\",\"\n   */\n  arrayToStringFields?: ArrayToStringFields;\n\n  /**\n   * 表单字段映射\n   */\n  fieldMappingTime?: FieldMappingTime;\n  /**\n   * 表单收起展开状态变化回调\n   */\n  handleCollapsedChange?: (collapsed: boolean) => void;\n  /**\n   * 表单重置回调\n   */\n  handleReset?: HandleResetFn;\n  /**\n   * 表单提交回调\n   */\n  handleSubmit?: HandleSubmitFn;\n  /**\n   * 表单值变化回调\n   */\n  handleValuesChange?: (\n    values: Record<string, any>,\n    fieldsChanged: string[],\n  ) => void;\n  /**\n   * 重置按钮参数\n   */\n  resetButtonOptions?: ActionButtonOptions;\n\n  /**\n   * 验证失败时是否自动滚动到第一个错误字段\n   * @default false\n   */\n  scrollToFirstError?: boolean;\n\n  /**\n   * 是否显示默认操作按钮\n   * @default true\n   */\n  showDefaultActions?: boolean;\n\n  /**\n   * 提交按钮参数\n   */\n  submitButtonOptions?: ActionButtonOptions;\n\n  /**\n   * 是否在字段值改变时提交表单\n   * @default false\n   */\n  submitOnChange?: boolean;\n\n  /**\n   * 是否在回车时提交表单\n   * @default false\n   */\n  submitOnEnter?: boolean;\n}\n\nexport type ExtendedFormApi = FormApi & {\n  useStore: <T = NoInfer<VbenFormProps>>(\n    selector?: (state: NoInfer<VbenFormProps>) => T,\n  ) => Readonly<Ref<T>>;\n};\n\nexport interface VbenFormAdapterOptions<\n  T extends BaseFormComponentType = BaseFormComponentType,\n> {\n  config?: {\n    baseModelPropName?: string;\n    disabledOnChangeListener?: boolean;\n    disabledOnInputListener?: boolean;\n    emptyStateValue?: null | undefined;\n    modelPropNameMap?: Partial<Record<T, string>>;\n  };\n  defineRules?: {\n    required?: (\n      value: any,\n      params: any,\n      ctx: Record<string, any>,\n    ) => boolean | string;\n    selectRequired?: (\n      value: any,\n      params: any,\n      ctx: Record<string, any>,\n    ) => boolean | string;\n  };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/use-form-context.ts",
    "content": "import type { ZodRawShape } from 'zod';\n\nimport type { ComputedRef } from 'vue';\n\nimport type { ExtendedFormApi, FormActions, VbenFormProps } from './types';\n\nimport { computed, unref, useSlots } from 'vue';\n\nimport { createContext } from '@vben-core/shadcn-ui';\nimport { isString, mergeWithArrayOverride, set } from '@vben-core/shared/utils';\n\nimport { useForm } from 'vee-validate';\nimport { object, ZodIntersection, ZodNumber, ZodObject, ZodString } from 'zod';\nimport { getDefaultsForSchema } from 'zod-defaults';\n\ntype ExtendFormProps = VbenFormProps & { formApi?: ExtendedFormApi };\n\nexport const [injectFormProps, provideFormProps] =\n  createContext<[ComputedRef<ExtendFormProps> | ExtendFormProps, FormActions]>(\n    'VbenFormProps',\n  );\n\nexport const [injectComponentRefMap, provideComponentRefMap] =\n  createContext<Map<string, unknown>>('ComponentRefMap');\n\nexport function useFormInitial(\n  props: ComputedRef<VbenFormProps> | VbenFormProps,\n) {\n  const slots = useSlots();\n  const initialValues = generateInitialValues();\n\n  const form = useForm({\n    ...(Object.keys(initialValues)?.length ? { initialValues } : {}),\n  });\n\n  const delegatedSlots = computed(() => {\n    const resultSlots: string[] = [];\n\n    for (const key of Object.keys(slots)) {\n      if (key !== 'default') {\n        resultSlots.push(key);\n      }\n    }\n    return resultSlots;\n  });\n\n  function generateInitialValues() {\n    const initialValues: Record<string, any> = {};\n\n    const zodObject: ZodRawShape = {};\n    (unref(props).schema || []).forEach((item) => {\n      if (Reflect.has(item, 'defaultValue')) {\n        set(initialValues, item.fieldName, item.defaultValue);\n      } else if (item.rules && !isString(item.rules)) {\n        // 检查规则是否适合提取默认值\n        const customDefaultValue = getCustomDefaultValue(item.rules);\n        zodObject[item.fieldName] = item.rules;\n        if (customDefaultValue !== undefined) {\n          initialValues[item.fieldName] = customDefaultValue;\n        }\n      }\n    });\n\n    const schemaInitialValues = getDefaultsForSchema(object(zodObject));\n\n    const zodDefaults: Record<string, any> = {};\n    for (const key in schemaInitialValues) {\n      set(zodDefaults, key, schemaInitialValues[key]);\n    }\n    return mergeWithArrayOverride(initialValues, zodDefaults);\n  }\n  // 自定义默认值提取逻辑\n  function getCustomDefaultValue(rule: any): any {\n    if (rule instanceof ZodString) {\n      return ''; // 默认为空字符串\n    } else if (rule instanceof ZodNumber) {\n      return null; // 默认为 null（避免显示 0）\n    } else if (rule instanceof ZodObject) {\n      // 递归提取嵌套对象的默认值\n      const defaultValues: Record<string, any> = {};\n      for (const [key, valueSchema] of Object.entries(rule.shape)) {\n        defaultValues[key] = getCustomDefaultValue(valueSchema);\n      }\n      return defaultValues;\n    } else if (rule instanceof ZodIntersection) {\n      // 对于交集类型，从schema 提取默认值\n      const leftDefaultValue = getCustomDefaultValue(rule._def.left);\n      const rightDefaultValue = getCustomDefaultValue(rule._def.right);\n\n      // 如果左右两边都能提取默认值，合并它们\n      if (\n        typeof leftDefaultValue === 'object' &&\n        typeof rightDefaultValue === 'object'\n      ) {\n        return { ...leftDefaultValue, ...rightDefaultValue };\n      }\n\n      // 否则优先使用左边的默认值\n      return leftDefaultValue ?? rightDefaultValue;\n    } else {\n      return undefined; // 其他类型不提供默认值\n    }\n  }\n\n  return {\n    delegatedSlots,\n    form,\n  };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/use-vben-form.ts",
    "content": "import type {\n  BaseFormComponentType,\n  ExtendedFormApi,\n  VbenFormProps,\n} from './types';\n\nimport { defineComponent, h, isReactive, onBeforeUnmount, watch } from 'vue';\n\nimport { useStore } from '@vben-core/shared/store';\n\nimport { FormApi } from './form-api';\nimport VbenUseForm from './vben-use-form.vue';\n\nexport function useVbenForm<\n  T extends BaseFormComponentType = BaseFormComponentType,\n>(options: VbenFormProps<T>) {\n  const IS_REACTIVE = isReactive(options);\n  const api = new FormApi(options);\n  const extendedApi: ExtendedFormApi = api as never;\n  extendedApi.useStore = (selector) => {\n    return useStore(api.store, selector);\n  };\n\n  const Form = defineComponent(\n    (props: VbenFormProps, { attrs, slots }) => {\n      onBeforeUnmount(() => {\n        api.unmount();\n      });\n      api.setState({ ...props, ...attrs });\n      return () =>\n        h(VbenUseForm, { ...props, ...attrs, formApi: extendedApi }, slots);\n    },\n    {\n      name: 'VbenUseForm',\n      inheritAttrs: false,\n    },\n  );\n  // Add reactivity support\n  if (IS_REACTIVE) {\n    watch(\n      () => options.schema,\n      () => {\n        api.setState({ schema: options.schema });\n      },\n      { immediate: true },\n    );\n  }\n\n  return [Form, extendedApi] as const;\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/vben-form.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VbenFormProps } from './types';\n\nimport { ref, watchEffect } from 'vue';\n\nimport { useForwardPropsEmits } from '@vben-core/composables';\n\nimport FormActions from './components/form-actions.vue';\nimport {\n  COMPONENT_BIND_EVENT_MAP,\n  COMPONENT_MAP,\n  DEFAULT_FORM_COMMON_CONFIG,\n} from './config';\nimport { Form } from './form-render';\nimport { provideFormProps, useFormInitial } from './use-form-context';\n\n// 通过 extends 会导致热更新卡死\ninterface Props extends VbenFormProps {}\nconst props = withDefaults(defineProps<Props>(), {\n  actionWrapperClass: '',\n  collapsed: false,\n  collapsedRows: 1,\n  commonConfig: () => ({}),\n  handleReset: undefined,\n  handleSubmit: undefined,\n  layout: 'horizontal',\n  resetButtonOptions: () => ({}),\n  showCollapseButton: false,\n  showDefaultActions: true,\n  submitButtonOptions: () => ({}),\n  wrapperClass: 'grid-cols-1',\n});\n\nconst forward = useForwardPropsEmits(props);\n\nconst currentCollapsed = ref(false);\n\nconst { delegatedSlots, form } = useFormInitial(props);\n\nprovideFormProps([props, form]);\n\nconst handleUpdateCollapsed = (value: boolean) => {\n  currentCollapsed.value = value;\n  // 触发收起展开状态变化回调\n  props.handleCollapsedChange?.(value);\n};\n\nwatchEffect(() => {\n  currentCollapsed.value = props.collapsed;\n});\n</script>\n\n<template>\n  <Form\n    v-bind=\"forward\"\n    :collapsed=\"currentCollapsed\"\n    :component-bind-event-map=\"COMPONENT_BIND_EVENT_MAP\"\n    :component-map=\"COMPONENT_MAP\"\n    :form=\"form\"\n    :global-common-config=\"DEFAULT_FORM_COMMON_CONFIG\"\n  >\n    <template\n      v-for=\"slotName in delegatedSlots\"\n      :key=\"slotName\"\n      #[slotName]=\"slotProps\"\n    >\n      <slot :name=\"slotName\" v-bind=\"slotProps\"></slot>\n    </template>\n    <template #default=\"slotProps\">\n      <slot v-bind=\"slotProps\">\n        <FormActions\n          v-if=\"showDefaultActions\"\n          :model-value=\"currentCollapsed\"\n          @update:model-value=\"handleUpdateCollapsed\"\n        />\n      </slot>\n    </template>\n  </Form>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/src/vben-use-form.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Recordable } from '@vben-core/typings';\n\nimport type { ExtendedFormApi, VbenFormProps } from './types';\n\n// import { toRaw, watch } from 'vue';\nimport { nextTick, onMounted, watch } from 'vue';\n\nimport { useForwardPriorityValues } from '@vben-core/composables';\nimport { cloneDeep, get, isEqual, set } from '@vben-core/shared/utils';\n\nimport { useDebounceFn } from '@vueuse/core';\n\nimport FormActions from './components/form-actions.vue';\nimport {\n  COMPONENT_BIND_EVENT_MAP,\n  COMPONENT_MAP,\n  DEFAULT_FORM_COMMON_CONFIG,\n} from './config';\nimport { Form } from './form-render';\nimport {\n  provideComponentRefMap,\n  provideFormProps,\n  useFormInitial,\n} from './use-form-context';\n// 通过 extends 会导致热更新卡死，所以重复写了一遍\ninterface Props extends VbenFormProps {\n  formApi?: ExtendedFormApi;\n}\n\nconst props = defineProps<Props>();\n\nconst state = props.formApi?.useStore?.();\n\nconst forward = useForwardPriorityValues(props, state);\n\nconst componentRefMap = new Map<string, unknown>();\n\nconst { delegatedSlots, form } = useFormInitial(forward);\n\nprovideFormProps([forward, form]);\nprovideComponentRefMap(componentRefMap);\n\nprops.formApi?.mount?.(form, componentRefMap);\n\nconst handleUpdateCollapsed = (value: boolean) => {\n  props.formApi?.setState({ collapsed: value });\n  // 触发收起展开状态变化回调\n  forward.value.handleCollapsedChange?.(value);\n};\n\nfunction handleKeyDownEnter(event: KeyboardEvent) {\n  if (!state?.value.submitOnEnter || !forward.value.formApi?.isMounted) {\n    return;\n  }\n  // 如果是 textarea 不阻止默认行为，否则会导致无法换行。\n  // 跳过 textarea 的回车提交处理\n  if (event.target instanceof HTMLTextAreaElement) {\n    return;\n  }\n  event.preventDefault();\n\n  forward.value.formApi?.validateAndSubmitForm();\n}\n\nconst handleValuesChangeDebounced = useDebounceFn(async () => {\n  state?.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm();\n}, 300);\n\nconst valuesCache: Recordable<any> = {};\n\nonMounted(async () => {\n  // 只在挂载后开始监听，form.values会有一个初始化的过程\n  await nextTick();\n  watch(\n    () => form.values,\n    async (newVal) => {\n      if (forward.value.handleValuesChange) {\n        const fields = state?.value.schema?.map((item) => {\n          return item.fieldName;\n        });\n\n        if (fields && fields.length > 0) {\n          const changedFields: string[] = [];\n          fields.forEach((field) => {\n            const newFieldValue = get(newVal, field);\n            const oldFieldValue = get(valuesCache, field);\n            if (!isEqual(newFieldValue, oldFieldValue)) {\n              changedFields.push(field);\n              set(valuesCache, field, newFieldValue);\n            }\n          });\n\n          if (changedFields.length > 0) {\n            // 调用handleValuesChange回调，传入所有表单值的深拷贝和变更的字段列表\n            const values = await forward.value.formApi?.getValues();\n            forward.value.handleValuesChange(\n              cloneDeep(values ?? {}) as Record<string, any>,\n              changedFields,\n            );\n          }\n        }\n      }\n      handleValuesChangeDebounced();\n    },\n    { deep: true },\n  );\n});\n</script>\n\n<template>\n  <Form\n    @keydown.enter=\"handleKeyDownEnter\"\n    v-bind=\"forward\"\n    :collapsed=\"state?.collapsed\"\n    :component-bind-event-map=\"COMPONENT_BIND_EVENT_MAP\"\n    :component-map=\"COMPONENT_MAP\"\n    :form=\"form\"\n    :global-common-config=\"DEFAULT_FORM_COMMON_CONFIG\"\n  >\n    <template\n      v-for=\"slotName in delegatedSlots\"\n      :key=\"slotName\"\n      #[slotName]=\"slotProps\"\n    >\n      <slot :name=\"slotName\" v-bind=\"slotProps\"></slot>\n    </template>\n    <template #default=\"slotProps\">\n      <slot v-bind=\"slotProps\">\n        <FormActions\n          v-if=\"forward.showDefaultActions\"\n          :model-value=\"state?.collapsed\"\n          @update:model-value=\"handleUpdateCollapsed\"\n        >\n          <template #reset-before=\"resetSlotProps\">\n            <slot name=\"reset-before\" v-bind=\"resetSlotProps\"></slot>\n          </template>\n          <template #submit-before=\"submitSlotProps\">\n            <slot name=\"submit-before\" v-bind=\"submitSlotProps\"></slot>\n          </template>\n          <template #expand-before=\"expandBeforeSlotProps\">\n            <slot name=\"expand-before\" v-bind=\"expandBeforeSlotProps\"></slot>\n          </template>\n          <template #expand-after=\"expandAfterSlotProps\">\n            <slot name=\"expand-after\" v-bind=\"expandAfterSlotProps\"></slot>\n          </template>\n        </FormActions>\n      </slot>\n    </template>\n  </Form>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\", \"__tests__\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/form-ui/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\nimport Vue from 'unplugin-vue/rolldown';\n\nexport default defineConfig({\n  clean: true,\n  deps: {\n    skipNodeModulesBundle: true,\n  },\n  dts: {\n    vue: true,\n  },\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n    js: '.mjs',\n  }),\n  platform: 'neutral',\n  plugins: [Vue({ isProduction: true })],\n  unbundle: true,\n});\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/package.json",
    "content": "{\n  \"name\": \"@vben-core/layout-ui\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@vben-core/uikit/layout-ui\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm exec tsdown\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"production\": \"./src/index.ts\",\n      \"default\": \"./dist/index.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"default\": \"./dist/index.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/composables\": \"workspace:*\",\n    \"@vben-core/icons\": \"workspace:*\",\n    \"@vben-core/shadcn-ui\": \"workspace:*\",\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben-core/typings\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"vue\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/components/index.ts",
    "content": "export { default as LayoutContent } from './layout-content.vue';\nexport { default as LayoutFooter } from './layout-footer.vue';\nexport { default as LayoutHeader } from './layout-header.vue';\nexport { default as LayoutSidebar } from './layout-sidebar.vue';\nexport { default as LayoutTabbar } from './layout-tabbar.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/components/layout-content.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\nimport type { ContentCompactType } from '@vben-core/typings';\n\nimport { computed } from 'vue';\n\nimport { useLayoutContentStyle } from '@vben-core/composables';\nimport { Slot } from '@vben-core/shadcn-ui';\n\ninterface Props {\n  /**\n   * 内容区域定宽\n   */\n  contentCompact: ContentCompactType;\n  /**\n   * 定宽布局宽度\n   */\n  contentCompactWidth: number;\n  padding: number;\n  paddingBottom: number;\n  paddingLeft: number;\n  paddingRight: number;\n  paddingTop: number;\n}\n\nconst props = withDefaults(defineProps<Props>(), {});\n\n// @ts-expect-error - unused\nconst { contentElement, overlayStyle } = useLayoutContentStyle();\n\nconst style = computed((): CSSProperties => {\n  const {\n    contentCompact,\n    padding,\n    paddingBottom,\n    paddingLeft,\n    paddingRight,\n    paddingTop,\n  } = props;\n\n  const compactStyle: CSSProperties =\n    contentCompact === 'compact'\n      ? { margin: '0 auto', width: `${props.contentCompactWidth}px` }\n      : {};\n  return {\n    ...compactStyle,\n    flex: 1,\n    padding: `${padding}px`,\n    paddingBottom: `${paddingBottom}px`,\n    paddingLeft: `${paddingLeft}px`,\n    paddingRight: `${paddingRight}px`,\n    paddingTop: `${paddingTop}px`,\n  };\n});\n</script>\n\n<template>\n  <main ref=\"contentElement\" :style=\"style\" class=\"relative bg-background-deep\">\n    <Slot :style=\"overlayStyle\">\n      <slot name=\"overlay\"></slot>\n    </Slot>\n    <slot></slot>\n  </main>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/components/layout-footer.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\nimport { computed } from 'vue';\n\ninterface Props {\n  /**\n   * 是否固定在底部\n   */\n  fixed?: boolean;\n  height: number;\n  /**\n   * 是否显示\n   * @default true\n   */\n  show?: boolean;\n  width: string;\n  zIndex: number;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  show: true,\n});\n\nconst style = computed((): CSSProperties => {\n  const { fixed, height, show, width, zIndex } = props;\n  return {\n    height: `${height}px`,\n    marginBottom: show ? '0' : `-${height}px`,\n    position: fixed ? 'fixed' : 'static',\n    width,\n    zIndex,\n  };\n});\n</script>\n\n<template>\n  <footer\n    :style=\"style\"\n    class=\"bottom-0 w-full bg-background-deep transition-all duration-200\"\n  >\n    <slot></slot>\n  </footer>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/components/layout-header.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\nimport { computed, useSlots } from 'vue';\n\ninterface Props {\n  /**\n   * 横屏\n   */\n  fullWidth: boolean;\n  /**\n   * 高度\n   */\n  height: number;\n  /**\n   * 是否移动端\n   */\n  isMobile: boolean;\n  /**\n   * 是否显示\n   */\n  show: boolean;\n  /**\n   * 侧边菜单宽度\n   */\n  sidebarWidth: number;\n  /**\n   * 主题\n   */\n  theme: string | undefined;\n  /**\n   * 宽度\n   */\n  width: string;\n  /**\n   * zIndex\n   */\n  zIndex: number;\n}\n\nconst props = withDefaults(defineProps<Props>(), {});\n\nconst slots = useSlots();\n\nconst style = computed((): CSSProperties => {\n  const { fullWidth, height, show } = props;\n  const right = !show || !fullWidth ? undefined : 0;\n\n  return {\n    height: `${height}px`,\n    marginTop: show ? 0 : `-${height}px`,\n    right,\n  };\n});\n\nconst logoStyle = computed((): CSSProperties => {\n  return {\n    minWidth: `${props.isMobile ? 40 : props.sidebarWidth}px`,\n  };\n});\n</script>\n\n<template>\n  <header\n    :class=\"theme\"\n    :style=\"style\"\n    class=\"top-0 flex w-full flex-[0_0_auto] items-center border-b border-border bg-header pl-2 transition-[margin-top] duration-200\"\n  >\n    <div v-if=\"slots.logo\" :style=\"logoStyle\">\n      <slot name=\"logo\"></slot>\n    </div>\n\n    <slot name=\"toggle-button\"> </slot>\n\n    <slot></slot>\n  </header>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\nimport { computed, onUnmounted, shallowRef, useSlots, watchEffect } from 'vue';\n\nimport { VbenScrollbar } from '@vben-core/shadcn-ui';\n\nimport { useScrollLock } from '@vueuse/core';\n\nimport { useSidebarDrag } from '../hooks/use-sidebar-drag';\nimport { SidebarCollapseButton, SidebarFixedButton } from './widgets';\n\ninterface Props {\n  /**\n   * 折叠区域高度\n   * @default 42\n   */\n  collapseHeight?: number;\n  /**\n   * 折叠宽度\n   * @default 48\n   */\n  collapseWidth?: number;\n  /**\n   * 隐藏的dom是否可见\n   * @default true\n   */\n  domVisible?: boolean;\n  /**\n   * 扩展区域宽度\n   */\n  extraWidth: number;\n  /**\n   * 固定扩展区域\n   * @default false\n   */\n  fixedExtra?: boolean;\n  /**\n   * 头部高度\n   */\n  headerHeight: number;\n  /**\n   * 是否侧边混合模式\n   * @default false\n   */\n  isSidebarMixed?: boolean;\n  /**\n   * 顶部margin\n   * @default 60\n   */\n  marginTop?: number;\n  /**\n   * 混合菜单宽度\n   * @default 80\n   */\n  mixedWidth?: number;\n  /**\n   * 顶部padding\n   * @default 60\n   */\n  paddingTop?: number;\n  /**\n   * 是否显示\n   * @default true\n   */\n  show?: boolean;\n  /**\n   * 显示折叠按钮\n   * @default true\n   */\n  showCollapseButton?: boolean;\n  /**\n   * 显示固定按钮\n   * @default true\n   */\n  showFixedButton?: boolean;\n  /**\n   * 主题\n   */\n  theme: string;\n  /**\n   * 子主题\n   */\n  themeSub: string;\n  /**\n   * 宽度\n   */\n  width: number;\n  /**\n   * zIndex\n   * @default 0\n   */\n  zIndex?: number;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  collapseHeight: 42,\n  collapseWidth: 48,\n  domVisible: true,\n  fixedExtra: false,\n  isSidebarMixed: false,\n  marginTop: 0,\n  mixedWidth: 70,\n  paddingTop: 0,\n  show: true,\n  showCollapseButton: true,\n  showFixedButton: true,\n  zIndex: 0,\n});\n\nconst emit = defineEmits<{ leave: []; 'update:width': [value: number] }>();\nconst draggable = defineModel<boolean>('draggable');\nconst collapse = defineModel<boolean>('collapse');\nconst extraCollapse = defineModel<boolean>('extraCollapse');\nconst expandOnHovering = defineModel<boolean>('expandOnHovering');\nconst expandOnHover = defineModel<boolean>('expandOnHover');\nconst extraVisible = defineModel<boolean>('extraVisible');\n\nconst isLocked = useScrollLock(document.body);\nconst slots = useSlots();\n\nconst asideRef = shallowRef<HTMLElement | null>(null);\nconst dragBarRef = shallowRef<HTMLElement | null>(null);\n\nconst hiddenSideStyle = computed((): CSSProperties => calcMenuWidthStyle(true));\n\nconst style = computed((): CSSProperties => {\n  const { isSidebarMixed, marginTop, paddingTop, zIndex } = props;\n\n  return {\n    '--scroll-shadow': 'var(--sidebar)',\n    ...calcMenuWidthStyle(false),\n    height: `calc(100% - ${marginTop}px)`,\n    marginTop: `${marginTop}px`,\n    paddingTop: `${paddingTop}px`,\n    zIndex,\n    ...(isSidebarMixed && extraVisible.value ? { transition: 'none' } : {}),\n  };\n});\n\nconst extraStyle = computed((): CSSProperties => {\n  const { extraWidth, show, width, zIndex } = props;\n\n  return {\n    left: `${width}px`,\n    width: extraVisible.value && show ? `${extraWidth}px` : 0,\n    zIndex,\n  };\n});\n\nconst extraTitleStyle = computed((): CSSProperties => {\n  const { headerHeight } = props;\n\n  return {\n    height: `${headerHeight - 1}px`,\n  };\n});\n\nconst contentWidthStyle = computed((): CSSProperties => {\n  const { fixedExtra, isSidebarMixed, mixedWidth } = props;\n  if (isSidebarMixed && fixedExtra) {\n    return { width: `${mixedWidth}px` };\n  }\n  return {};\n});\n\nconst contentStyle = computed((): CSSProperties => {\n  const { collapseHeight, headerHeight } = props;\n\n  return {\n    height: `calc(100% - ${headerHeight + collapseHeight}px)`,\n    paddingTop: '8px',\n    ...contentWidthStyle.value,\n  };\n});\n\nconst headerStyle = computed((): CSSProperties => {\n  const { headerHeight, isSidebarMixed } = props;\n\n  return {\n    ...(isSidebarMixed ? { display: 'flex', justifyContent: 'center' } : {}),\n    height: `${headerHeight - 1}px`,\n    ...contentWidthStyle.value,\n  };\n});\n\nconst extraContentStyle = computed((): CSSProperties => {\n  const { collapseHeight, headerHeight } = props;\n  return {\n    height: `calc(100% - ${headerHeight + collapseHeight}px)`,\n  };\n});\n\nconst collapseStyle = computed((): CSSProperties => {\n  return {\n    height: `${props.collapseHeight}px`,\n  };\n});\n\nwatchEffect(() => {\n  extraVisible.value = props.fixedExtra ? true : extraVisible.value;\n});\n\nfunction calcMenuWidthStyle(isHiddenDom: boolean): CSSProperties {\n  const {\n    collapseWidth,\n    extraWidth,\n    mixedWidth,\n    fixedExtra,\n    isSidebarMixed,\n    show,\n    width,\n  } = props;\n\n  let widthValue =\n    width === 0\n      ? '0px'\n      : `${width + (isSidebarMixed && fixedExtra && extraVisible.value ? extraWidth : 0)}px`;\n\n  if (isHiddenDom && expandOnHovering.value && !expandOnHover.value) {\n    widthValue = isSidebarMixed ? `${mixedWidth}px` : `${collapseWidth}px`;\n  }\n  return {\n    ...(widthValue === '0px' ? { overflow: 'hidden' } : {}),\n    flex: `0 0 ${widthValue}`,\n    marginLeft: show ? 0 : `-${widthValue}`,\n    maxWidth: widthValue,\n    minWidth: widthValue,\n    width: widthValue,\n  };\n}\n\nfunction handleMouseenter(e: MouseEvent) {\n  if (e?.offsetX < 10) {\n    return;\n  }\n\n  // 未开启和未折叠状态不生效\n  if (expandOnHover.value) {\n    return;\n  }\n  if (!expandOnHovering.value) {\n    collapse.value = false;\n  }\n  if (props.isSidebarMixed) {\n    isLocked.value = true;\n  }\n  expandOnHovering.value = true;\n}\n\nfunction handleMouseleave() {\n  emit('leave');\n  if (props.isSidebarMixed) {\n    isLocked.value = false;\n  }\n  if (expandOnHover.value) {\n    return;\n  }\n\n  expandOnHovering.value = false;\n  collapse.value = true;\n  extraVisible.value = false;\n}\n\nconst { startDrag, endDrag } = useSidebarDrag();\n\nconst handleDragSidebar = (e: MouseEvent) => {\n  const { isSidebarMixed, collapseWidth, width } = props;\n  const minLimit = isSidebarMixed ? width + collapseWidth : collapseWidth;\n  const maxLimit = isSidebarMixed ? width + 320 : 320;\n\n  startDrag(\n    e,\n    {\n      min: minLimit,\n      max: maxLimit,\n    },\n    {\n      target: asideRef.value,\n      dragBar: dragBarRef.value,\n    },\n    (newWidth) => {\n      if (isSidebarMixed) {\n        emit('update:width', newWidth - width);\n        extraCollapse.value = collapse.value =\n          newWidth - width <= collapseWidth;\n      } else {\n        emit('update:width', newWidth);\n        collapse.value = extraCollapse.value = newWidth <= collapseWidth;\n      }\n    },\n  );\n};\n\nonUnmounted(() => {\n  endDrag();\n});\n</script>\n\n<template>\n  <div\n    v-if=\"domVisible\"\n    :class=\"theme\"\n    :style=\"hiddenSideStyle\"\n    class=\"h-full transition-all duration-150\"\n  ></div>\n  <aside\n    ref=\"asideRef\"\n    :style=\"style\"\n    class=\"fixed left-0 top-0 h-full transition-all duration-150\"\n    :class=\"theme\"\n    @mouseenter=\"handleMouseenter\"\n    @mouseleave=\"handleMouseleave\"\n  >\n    <div\n      class=\"h-full\"\n      :class=\"[\n        {\n          'bg-sidebar-deep': isSidebarMixed,\n          'border-r border-border bg-sidebar': !isSidebarMixed,\n        },\n      ]\"\n      :style=\"{ width: `${width}px` }\"\n    >\n      <SidebarFixedButton\n        v-if=\"!collapse && !isSidebarMixed && showFixedButton\"\n        v-model:expand-on-hover=\"expandOnHover\"\n      />\n      <div v-if=\"slots.logo\" :style=\"headerStyle\">\n        <slot name=\"logo\"></slot>\n      </div>\n      <VbenScrollbar :style=\"contentStyle\" shadow shadow-border>\n        <slot></slot>\n      </VbenScrollbar>\n\n      <div :style=\"collapseStyle\"></div>\n      <SidebarCollapseButton\n        v-if=\"showCollapseButton && !isSidebarMixed\"\n        v-model:collapsed=\"collapse\"\n      />\n    </div>\n    <div\n      v-if=\"isSidebarMixed\"\n      :class=\"[\n        themeSub,\n        {\n          'border-l': extraVisible,\n        },\n      ]\"\n      :style=\"extraStyle\"\n      class=\"fixed top-0 h-full overflow-hidden border-r border-border bg-sidebar transition-all duration-200\"\n    >\n      <SidebarCollapseButton\n        v-if=\"isSidebarMixed && expandOnHover\"\n        v-model:collapsed=\"extraCollapse\"\n      />\n\n      <SidebarFixedButton\n        v-if=\"!extraCollapse\"\n        v-model:expand-on-hover=\"expandOnHover\"\n      />\n      <div v-if=\"!extraCollapse\" :style=\"extraTitleStyle\" class=\"pl-2\">\n        <slot name=\"extra-title\"></slot>\n      </div>\n      <VbenScrollbar\n        :style=\"extraContentStyle\"\n        class=\"border-border py-2\"\n        shadow\n        shadow-border\n      >\n        <slot name=\"extra\"></slot>\n      </VbenScrollbar>\n    </div>\n    <div\n      v-if=\"draggable\"\n      ref=\"dragBarRef\"\n      class=\"absolute inset-y-0 -right-px z-1000 w-0.5 cursor-col-resize hover:bg-primary\"\n      @mousedown=\"handleDragSidebar\"\n    ></div>\n  </aside>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\nimport { computed } from 'vue';\n\ninterface Props {\n  /**\n   * 高度\n   */\n  height: number;\n}\n\nconst props = withDefaults(defineProps<Props>(), {});\n\nconst style = computed((): CSSProperties => {\n  const { height } = props;\n  return {\n    height: `${height}px`,\n  };\n});\n</script>\n\n<template>\n  <section\n    :style=\"style\"\n    class=\"flex w-full border-b border-border bg-background transition-all\"\n  >\n    <slot></slot>\n  </section>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/components/widgets/index.ts",
    "content": "export { default as SidebarCollapseButton } from './sidebar-collapse-button.vue';\nexport { default as SidebarFixedButton } from './sidebar-fixed-button.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/components/widgets/sidebar-collapse-button.vue",
    "content": "<script setup lang=\"ts\">\nimport { ChevronsLeft, ChevronsRight } from '@vben-core/icons';\n\nconst collapsed = defineModel<boolean>('collapsed');\n\nfunction handleCollapsed() {\n  collapsed.value = !collapsed.value;\n}\n</script>\n\n<template>\n  <div\n    class=\"absolute bottom-2 left-3 z-10 flex-center cursor-pointer rounded-sm bg-accent p-1 text-foreground/60 hover:bg-accent-hover hover:text-foreground\"\n    @click.stop=\"handleCollapsed\"\n  >\n    <ChevronsRight v-if=\"collapsed\" class=\"size-4\" />\n    <ChevronsLeft v-else class=\"size-4\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/components/widgets/sidebar-fixed-button.vue",
    "content": "<script setup lang=\"ts\">\nimport { Pin, PinOff } from '@vben-core/icons';\n\nconst expandOnHover = defineModel<boolean>('expandOnHover');\n\nfunction toggleFixed() {\n  expandOnHover.value = !expandOnHover.value;\n}\n</script>\n\n<template>\n  <div\n    class=\"absolute right-3 bottom-2 z-10 flex-center cursor-pointer rounded-sm bg-accent p-1.25 text-foreground/60 transition-all duration-300 hover:bg-accent-hover hover:text-foreground\"\n    @click=\"toggleFixed\"\n  >\n    <PinOff v-if=\"!expandOnHover\" class=\"size-3.5\" />\n    <Pin v-else class=\"size-3.5\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/hooks/use-layout.ts",
    "content": "import type { LayoutType } from '@vben-core/typings';\n\nimport type { VbenLayoutProps } from '../vben-layout';\n\nimport { computed } from 'vue';\n\nexport function useLayout(props: VbenLayoutProps) {\n  const currentLayout = computed(() =>\n    props.isMobile ? 'sidebar-nav' : (props.layout as LayoutType),\n  );\n\n  /**\n   * 是否全屏显示content，不需要侧边、底部、顶部、tab区域\n   */\n  const isFullContent = computed(() => currentLayout.value === 'full-content');\n\n  /**\n   * 是否侧边混合模式\n   */\n  const isSidebarMixedNav = computed(\n    () => currentLayout.value === 'sidebar-mixed-nav',\n  );\n\n  /**\n   * 是否为头部导航模式\n   */\n  const isHeaderNav = computed(() => currentLayout.value === 'header-nav');\n\n  /**\n   * 是否为混合导航模式\n   */\n  const isMixedNav = computed(\n    () =>\n      currentLayout.value === 'mixed-nav' ||\n      currentLayout.value === 'header-sidebar-nav',\n  );\n\n  /**\n   * 是否为头部混合模式\n   */\n  const isHeaderMixedNav = computed(\n    () => currentLayout.value === 'header-mixed-nav',\n  );\n\n  return {\n    currentLayout,\n    isFullContent,\n    isHeaderMixedNav,\n    isHeaderNav,\n    isMixedNav,\n    isSidebarMixedNav,\n  };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/hooks/use-sidebar-drag.ts",
    "content": "import { ref } from 'vue';\n\ninterface DragOptions {\n  max: number;\n  min: number;\n}\n\ninterface DragElements {\n  dragBar: HTMLElement | null;\n  target: HTMLElement | null;\n}\n\ntype DragCallback = (newWidth: number) => void;\n\nexport function useSidebarDrag() {\n  const isDragging = ref(false);\n  let cleanup: (() => void) | null = null;\n  let dragOverlay: HTMLElement | null = null;\n\n  const startDrag = (\n    e: MouseEvent,\n    options: DragOptions,\n    elements: DragElements,\n    onDrag: DragCallback,\n  ) => {\n    const { min, max } = options;\n    const { dragBar, target } = elements;\n\n    if (isDragging.value || !dragBar || !target) return;\n\n    e.preventDefault();\n    e.stopPropagation();\n\n    isDragging.value = true;\n\n    const startX = e.clientX;\n    const startWidth = target.getBoundingClientRect().width;\n    const startLeft = dragBar.offsetLeft;\n\n    dragBar.classList.add('bg-primary');\n    dragBar.classList.remove('bg-primary/30');\n\n    const dragBarTransition = dragBar.style.transition;\n    const targetTransition = target.style.transition;\n\n    dragBar.style.transition = 'none';\n    target.style.transition = 'none';\n\n    dragOverlay = document.createElement('div');\n    dragOverlay.style.position = 'fixed';\n    dragOverlay.style.inset = '0';\n    dragOverlay.style.zIndex = '9999';\n    dragOverlay.style.cursor = 'col-resize';\n    dragOverlay.style.userSelect = 'none';\n    dragOverlay.style.outline = 'none';\n    dragOverlay.tabIndex = -1;\n    dragOverlay.style.background = 'rgba(0,0,0,0)';\n    document.body.append(dragOverlay);\n\n    const onMouseMove = (moveEvent: MouseEvent) => {\n      if (!isDragging.value || !dragBar || !target) {\n        endDrag();\n        return;\n      }\n\n      const deltaX = moveEvent.clientX - startX;\n      let currentWidth = startWidth + deltaX;\n\n      const isOutOfMin = currentWidth < min;\n      const isOutOfMax = currentWidth > max;\n      const isOutOfBounds = isOutOfMin || isOutOfMax;\n\n      if (isOutOfMin) currentWidth = min;\n      if (isOutOfMax) currentWidth = max;\n\n      const newLeft = startLeft + (currentWidth - startWidth);\n\n      if (dragOverlay)\n        dragOverlay.style.cursor = isOutOfBounds ? 'not-allowed' : 'col-resize';\n\n      dragBar.style.left = `${newLeft}px`;\n\n      if (isOutOfBounds) {\n        dragBar.classList.add('bg-primary/30');\n        dragBar.classList.remove('bg-primary');\n      } else {\n        dragBar.classList.add('bg-primary');\n        dragBar.classList.remove('bg-primary/30');\n      }\n    };\n\n    const onMouseUp = (upEvent: MouseEvent) => {\n      if (!isDragging.value || !dragBar || !target) {\n        endDrag();\n        return;\n      }\n\n      const deltaX = upEvent.clientX - startX;\n      let newWidth = startWidth + deltaX;\n\n      newWidth = Math.min(max, Math.max(min, newWidth));\n\n      dragBar.classList.remove('bg-primary', 'bg-primary/30');\n\n      try {\n        onDrag?.(Math.round(newWidth));\n      } finally {\n        endDrag();\n      }\n    };\n\n    document.addEventListener('mousemove', onMouseMove);\n    document.addEventListener('mouseup', onMouseUp);\n\n    cleanup = () => {\n      if (!cleanup) return;\n\n      document.removeEventListener('mousemove', onMouseMove);\n      document.removeEventListener('mouseup', onMouseUp);\n\n      if (dragBar) {\n        dragBar.style.transition = dragBarTransition;\n        dragBar.style.left = '';\n        dragBar.classList.remove('bg-primary', 'bg-primary/30');\n      }\n\n      if (target) {\n        target.style.transition = targetTransition;\n      }\n\n      if (dragOverlay) {\n        dragOverlay.remove();\n        dragOverlay = null;\n      }\n\n      isDragging.value = false;\n      cleanup = null;\n    };\n  };\n\n  const endDrag = () => {\n    cleanup?.();\n  };\n\n  return {\n    startDrag,\n    endDrag,\n    get isDragging() {\n      return isDragging.value;\n    },\n  };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/index.ts",
    "content": "export type * from './vben-layout';\nexport { default as VbenAdminLayout } from './vben-layout.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/vben-layout.ts",
    "content": "import type {\n  ContentCompactType,\n  LayoutHeaderModeType,\n  LayoutType,\n  ThemeModeType,\n} from '@vben-core/typings';\n\ninterface VbenLayoutProps {\n  /**\n   * 内容区域定宽\n   * @default 'wide'\n   */\n  contentCompact?: ContentCompactType;\n  /**\n   * 定宽布局宽度\n   * @default 1200\n   */\n  contentCompactWidth?: number;\n  /**\n   * padding\n   * @default 16\n   */\n  contentPadding?: number;\n  /**\n   * paddingBottom\n   * @default 16\n   */\n  contentPaddingBottom?: number;\n  /**\n   * paddingLeft\n   * @default 16\n   */\n  contentPaddingLeft?: number;\n  /**\n   * paddingRight\n   * @default 16\n   */\n  contentPaddingRight?: number;\n  /**\n   * paddingTop\n   * @default 16\n   */\n  contentPaddingTop?: number;\n  /**\n   * footer 是否可见\n   * @default false\n   */\n  footerEnable?: boolean;\n  /**\n   * footer 是否固定\n   * @default true\n   */\n  footerFixed?: boolean;\n  /**\n   * footer 高度\n   * @default 32\n   */\n  footerHeight?: number;\n\n  /**\n   * header高度\n   * @default 48\n   */\n  headerHeight?: number;\n  /**\n   * 顶栏是否隐藏\n   * @default false\n   */\n  headerHidden?: boolean;\n  /**\n   * header 显示模式\n   * @default 'fixed'\n   */\n  headerMode?: LayoutHeaderModeType;\n  /**\n   * header 顶栏主题\n   */\n  headerTheme?: ThemeModeType;\n  /**\n   * 是否显示header切换侧边栏按钮\n   * @default\n   */\n  headerToggleSidebarButton?: boolean;\n  /**\n   * header是否显示\n   * @default true\n   */\n  headerVisible?: boolean;\n  /**\n   * 是否移动端显示\n   * @default false\n   */\n  isMobile?: boolean;\n  /**\n   * 布局方式\n   * sidebar-nav 侧边菜单布局\n   * header-nav 顶部菜单布局\n   * mixed-nav 侧边&顶部菜单布局\n   * sidebar-mixed-nav 侧边混合菜单布局\n   * full-content 全屏内容布局\n   * @default sidebar-nav\n   */\n  layout?: LayoutType;\n  /**\n   * 侧边菜单折叠状态\n   * @default false\n   */\n  sidebarCollapse?: boolean;\n  /**\n   * 侧边菜单折叠按钮\n   * @default true\n   */\n  sidebarCollapsedButton?: boolean;\n  /**\n   * 侧边菜单是否折叠时，是否显示title\n   * @default true\n   */\n  sidebarCollapseShowTitle?: boolean;\n  /**\n   * 侧边栏是否可见\n   * @default true\n   */\n  sidebarEnable?: boolean;\n  /**\n   * 侧边菜单折叠额外宽度\n   * @default 48\n   */\n  sidebarExtraCollapsedWidth?: number;\n  /**\n   * 侧边菜单折叠按钮是否固定\n   * @default true\n   */\n  sidebarFixedButton?: boolean;\n  /**\n   * 侧边栏是否隐藏\n   * @default false\n   */\n  sidebarHidden?: boolean;\n  /**\n   * 混合侧边栏宽度\n   * @default 80\n   */\n  sidebarMixedWidth?: number;\n  /**\n   * 侧边栏\n   * @default dark\n   */\n  sidebarTheme?: ThemeModeType;\n  /**\n   * 侧边栏子栏\n   * @default dark\n   */\n  sidebarThemeSub?: ThemeModeType;\n  /**\n   * 侧边栏宽度\n   * @default 210\n   */\n  sidebarWidth?: number;\n  /**\n   *  侧边菜单折叠宽度\n   * @default 48\n   */\n  sideCollapseWidth?: number;\n  /**\n   * tab是否可见\n   * @default true\n   */\n  tabbarEnable?: boolean;\n  /**\n   * tab高度\n   * @default 30\n   */\n  tabbarHeight?: number;\n  /**\n   * zIndex\n   * @default 100\n   */\n  zIndex?: number;\n}\nexport type { VbenLayoutProps };\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/src/vben-layout.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\nimport type { VbenLayoutProps } from './vben-layout';\n\nimport { computed, ref, watch } from 'vue';\n\nimport {\n  SCROLL_FIXED_CLASS,\n  useLayoutFooterStyle,\n  useLayoutHeaderStyle,\n} from '@vben-core/composables';\nimport { IconifyIcon } from '@vben-core/icons';\nimport { VbenIconButton } from '@vben-core/shadcn-ui';\nimport { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';\n\nimport { useMouse, useScroll, useThrottleFn } from '@vueuse/core';\n\nimport {\n  LayoutContent,\n  LayoutFooter,\n  LayoutHeader,\n  LayoutSidebar,\n  LayoutTabbar,\n} from './components';\nimport { useLayout } from './hooks/use-layout';\n\ninterface Props extends VbenLayoutProps {}\n\ndefineOptions({\n  name: 'VbenLayout',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  contentCompact: 'wide',\n  contentCompactWidth: 1200,\n  contentPadding: 0,\n  contentPaddingBottom: 0,\n  contentPaddingLeft: 0,\n  contentPaddingRight: 0,\n  contentPaddingTop: 0,\n  footerEnable: false,\n  footerFixed: true,\n  footerHeight: 32,\n  headerHeight: 50,\n  headerHidden: false,\n  headerMode: 'fixed',\n  headerToggleSidebarButton: true,\n  headerVisible: true,\n  isMobile: false,\n  layout: 'sidebar-nav',\n  sidebarCollapsedButton: true,\n  sidebarCollapseShowTitle: false,\n  sidebarExtraCollapsedWidth: 60,\n  sidebarFixedButton: true,\n  sidebarHidden: false,\n  sidebarMixedWidth: 80,\n  sidebarTheme: 'dark',\n  sidebarThemeSub: 'dark',\n  sidebarWidth: 180,\n  sideCollapseWidth: 60,\n  tabbarEnable: true,\n  tabbarHeight: 40,\n  zIndex: 200,\n});\n\nconst emit = defineEmits<{\n  sideMouseLeave: [];\n  toggleSidebar: [];\n  'update:sidebar-width': [value: number];\n}>();\nconst sidebarDraggable = defineModel<boolean>('sidebarDraggable', {\n  default: true,\n});\nconst sidebarCollapse = defineModel<boolean>('sidebarCollapse', {\n  default: false,\n});\nconst sidebarExtraVisible = defineModel<boolean>('sidebarExtraVisible');\nconst sidebarExtraCollapse = defineModel<boolean>('sidebarExtraCollapse', {\n  default: false,\n});\nconst sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover', {\n  default: false,\n});\nconst sidebarEnable = defineModel<boolean>('sidebarEnable', { default: true });\n\n// side是否处于hover状态展开菜单中\nconst sidebarExpandOnHovering = ref(false);\nconst headerIsHidden = ref(false);\nconst contentRef = ref();\n\nconst {\n  arrivedState,\n  directions,\n  isScrolling,\n  y: scrollY,\n} = useScroll(document);\n\nconst { setLayoutHeaderHeight } = useLayoutHeaderStyle();\nconst { setLayoutFooterHeight } = useLayoutFooterStyle();\n\nconst { y: mouseY } = useMouse({ target: contentRef, type: 'client' });\n\nconst {\n  currentLayout,\n  isFullContent,\n  isHeaderMixedNav,\n  isHeaderNav,\n  isMixedNav,\n  isSidebarMixedNav,\n} = useLayout(props);\n\n/**\n * 顶栏是否自动隐藏\n */\nconst isHeaderAutoMode = computed(() => props.headerMode === 'auto');\n\nconst headerWrapperHeight = computed(() => {\n  let height = 0;\n  if (props.headerVisible && !props.headerHidden) {\n    height += props.headerHeight;\n  }\n  if (props.tabbarEnable) {\n    height += props.tabbarHeight;\n  }\n  return height;\n});\n\nconst getSideCollapseWidth = computed(() => {\n  const {\n    sidebarCollapseShowTitle,\n    sidebarExtraCollapsedWidth,\n    sideCollapseWidth,\n  } = props;\n\n  return sidebarCollapseShowTitle ||\n    isSidebarMixedNav.value ||\n    isHeaderMixedNav.value\n    ? sidebarExtraCollapsedWidth\n    : sideCollapseWidth;\n});\n\n/**\n * 动态获取侧边区域是否可见\n */\nconst sidebarEnableState = computed(() => {\n  return !isHeaderNav.value && sidebarEnable.value;\n});\n\n/**\n * 侧边区域离顶部高度\n */\nconst sidebarMarginTop = computed(() => {\n  const { headerHeight, isMobile } = props;\n  return isMixedNav.value && !isMobile ? headerHeight : 0;\n});\n\n/**\n * 动态获取侧边宽度\n */\nconst getSidebarWidth = computed(() => {\n  const { isMobile, sidebarHidden, sidebarMixedWidth, sidebarWidth } = props;\n  let width = 0;\n\n  if (sidebarHidden) {\n    return width;\n  }\n\n  if (\n    !sidebarEnableState.value ||\n    (sidebarHidden &&\n      !isSidebarMixedNav.value &&\n      !isMixedNav.value &&\n      !isHeaderMixedNav.value)\n  ) {\n    return width;\n  }\n\n  if ((isHeaderMixedNav.value || isSidebarMixedNav.value) && !isMobile) {\n    width = sidebarMixedWidth;\n  } else if (sidebarCollapse.value) {\n    width = isMobile ? 0 : getSideCollapseWidth.value;\n  } else {\n    width = sidebarWidth;\n  }\n  return width;\n});\n\n/**\n * 获取扩展区域宽度\n */\nconst sidebarExtraWidth = computed(() => {\n  const { sidebarExtraCollapsedWidth, sidebarWidth } = props;\n\n  return sidebarExtraCollapse.value ? sidebarExtraCollapsedWidth : sidebarWidth;\n});\n\n/**\n * 是否侧边栏模式，包含混合侧边\n */\nconst isSideMode = computed(\n  () =>\n    currentLayout.value === 'mixed-nav' ||\n    currentLayout.value === 'sidebar-mixed-nav' ||\n    currentLayout.value === 'sidebar-nav' ||\n    currentLayout.value === 'header-mixed-nav' ||\n    currentLayout.value === 'header-sidebar-nav',\n);\n\n/**\n * header fixed值\n */\nconst headerFixed = computed(() => {\n  const { headerMode } = props;\n  return (\n    isMixedNav.value ||\n    headerMode === 'fixed' ||\n    headerMode === 'auto-scroll' ||\n    headerMode === 'auto'\n  );\n});\n\nconst showSidebar = computed(() => {\n  return isSideMode.value && sidebarEnable.value && !props.sidebarHidden;\n});\n\n/**\n * 遮罩可见性\n */\nconst maskVisible = computed(() => !sidebarCollapse.value && props.isMobile);\n\nconst mainStyle = computed(() => {\n  let width = '100%';\n  let sidebarAndExtraWidth = 'unset';\n  if (\n    headerFixed.value &&\n    currentLayout.value !== 'header-nav' &&\n    currentLayout.value !== 'mixed-nav' &&\n    currentLayout.value !== 'header-sidebar-nav' &&\n    showSidebar.value &&\n    !props.isMobile\n  ) {\n    // fixed模式下生效\n    const isSideNavEffective =\n      (isSidebarMixedNav.value || isHeaderMixedNav.value) &&\n      sidebarExpandOnHover.value &&\n      sidebarExtraVisible.value;\n\n    if (isSideNavEffective) {\n      const sideCollapseWidth = props.sidebarMixedWidth;\n      const sideWidth = sidebarExtraCollapse.value\n        ? props.sidebarExtraCollapsedWidth\n        : props.sidebarWidth;\n\n      // 100% - 侧边菜单混合宽度 - 菜单宽度\n      sidebarAndExtraWidth = `${sideCollapseWidth + sideWidth}px`;\n      width = `calc(100% - ${sidebarAndExtraWidth})`;\n    } else {\n      let sidebarWidth = getSidebarWidth.value;\n      if (sidebarExpandOnHovering.value && !sidebarExpandOnHover.value) {\n        sidebarWidth =\n          isSidebarMixedNav.value || isHeaderMixedNav.value\n            ? props.sidebarMixedWidth\n            : getSideCollapseWidth.value;\n      }\n      sidebarAndExtraWidth = `${sidebarWidth}px`;\n      width = `calc(100% - ${sidebarAndExtraWidth})`;\n    }\n  }\n  return {\n    sidebarAndExtraWidth,\n    width,\n  };\n});\n\n// 计算 tabbar 的样式\nconst tabbarStyle = computed((): CSSProperties => {\n  let width: string;\n  let marginLeft = 0;\n\n  // 如果不是混合导航，tabbar 的宽度为 100%\n  if (!isMixedNav.value || props.sidebarHidden) {\n    width = '100%';\n  } else if (sidebarEnable.value) {\n    // 鼠标在侧边栏上时，且侧边栏展开时的宽度\n    const onHoveringWidth = sidebarExpandOnHover.value\n      ? props.sidebarWidth\n      : getSideCollapseWidth.value;\n\n    // 设置 marginLeft，根据侧边栏是否折叠来决定\n    marginLeft = sidebarCollapse.value\n      ? getSideCollapseWidth.value\n      : onHoveringWidth;\n\n    // 设置 tabbar 的宽度，计算方式为 100% 减去侧边栏的宽度\n    width = `calc(100% - ${sidebarCollapse.value ? getSidebarWidth.value : onHoveringWidth}px)`;\n  } else {\n    // 默认情况下，tabbar 的宽度为 100%\n    width = '100%';\n  }\n\n  return {\n    marginLeft: `${marginLeft}px`,\n    width,\n  };\n});\n\nconst contentStyle = computed((): CSSProperties => {\n  const fixed = headerFixed.value;\n\n  const { footerEnable, footerFixed, footerHeight } = props;\n  return {\n    marginTop:\n      fixed &&\n      !isFullContent.value &&\n      !headerIsHidden.value &&\n      (!isHeaderAutoMode.value || scrollY.value < headerWrapperHeight.value)\n        ? `${headerWrapperHeight.value}px`\n        : 0,\n    paddingBottom: `${footerEnable && footerFixed ? footerHeight : 0}px`,\n  };\n});\n\nconst headerZIndex = computed(() => {\n  const { zIndex } = props;\n  const offset = isMixedNav.value ? 1 : 0;\n  return zIndex + offset;\n});\n\nconst headerWrapperStyle = computed((): CSSProperties => {\n  const fixed = headerFixed.value;\n  return {\n    height: isFullContent.value ? '0' : `${headerWrapperHeight.value}px`,\n    left: isMixedNav.value ? 0 : mainStyle.value.sidebarAndExtraWidth,\n    position: fixed ? 'fixed' : 'static',\n    top:\n      headerIsHidden.value || isFullContent.value\n        ? `-${headerWrapperHeight.value}px`\n        : 0,\n    width: mainStyle.value.width,\n    'z-index': headerZIndex.value,\n  };\n});\n\n/**\n * 侧边栏z-index\n */\nconst sidebarZIndex = computed(() => {\n  const { isMobile, zIndex } = props;\n  let offset = isMobile || isSideMode.value ? 1 : -1;\n\n  if (isMixedNav.value) {\n    offset += 1;\n  }\n\n  return zIndex + offset;\n});\n\nconst footerWidth = computed(() => {\n  if (!props.footerFixed) {\n    return '100%';\n  }\n\n  return mainStyle.value.width;\n});\n\nconst maskStyle = computed((): CSSProperties => {\n  return { zIndex: props.zIndex };\n});\n\nconst showHeaderToggleButton = computed(() => {\n  return (\n    props.isMobile ||\n    (props.headerToggleSidebarButton &&\n      isSideMode.value &&\n      !isSidebarMixedNav.value &&\n      !isMixedNav.value &&\n      !props.isMobile)\n  );\n});\n\nconst showHeaderLogo = computed(() => {\n  return !isSideMode.value || isMixedNav.value || props.isMobile;\n});\n\nwatch(\n  () => props.isMobile,\n  (val) => {\n    if (val) {\n      sidebarCollapse.value = true;\n    }\n  },\n  {\n    immediate: true,\n  },\n);\n\nwatch(\n  [() => headerWrapperHeight.value, () => isFullContent.value],\n  ([height]) => {\n    setLayoutHeaderHeight(isFullContent.value ? 0 : height);\n  },\n  {\n    immediate: true,\n  },\n);\n\nwatch(\n  () => props.footerHeight,\n  (height: number) => {\n    setLayoutFooterHeight(height);\n  },\n  {\n    immediate: true,\n  },\n);\n\n{\n  const HEADER_TRIGGER_DISTANCE = 12;\n\n  watch(\n    [() => props.headerMode, () => mouseY.value, () => headerIsHidden.value],\n    () => {\n      if (!isHeaderAutoMode.value || isMixedNav.value || isFullContent.value) {\n        if (props.headerMode !== 'auto-scroll') {\n          headerIsHidden.value = false;\n        }\n        return;\n      }\n\n      const isInTriggerZone = mouseY.value <= HEADER_TRIGGER_DISTANCE;\n      const isInHeaderZone =\n        !headerIsHidden.value && mouseY.value <= headerWrapperHeight.value;\n\n      headerIsHidden.value = !(isInTriggerZone || isInHeaderZone);\n    },\n    {\n      immediate: true,\n    },\n  );\n}\n\n{\n  const checkHeaderIsHidden = useThrottleFn((top, bottom, topArrived) => {\n    if (scrollY.value < headerWrapperHeight.value) {\n      headerIsHidden.value = false;\n      return;\n    }\n    if (topArrived) {\n      headerIsHidden.value = false;\n      return;\n    }\n\n    if (top) {\n      headerIsHidden.value = false;\n    } else if (bottom) {\n      headerIsHidden.value = true;\n    }\n  }, 300);\n\n  watch(\n    () => scrollY.value,\n    () => {\n      if (\n        props.headerMode !== 'auto-scroll' ||\n        isMixedNav.value ||\n        isFullContent.value\n      ) {\n        return;\n      }\n      if (isScrolling.value) {\n        checkHeaderIsHidden(\n          directions.top,\n          directions.bottom,\n          arrivedState.top,\n        );\n      }\n    },\n  );\n}\n\nfunction handleClickMask() {\n  sidebarCollapse.value = true;\n}\n\nfunction handleHeaderToggle() {\n  if (props.isMobile) {\n    sidebarCollapse.value = false;\n  } else {\n    emit('toggleSidebar');\n  }\n}\n\nconst idMainContent = ELEMENT_ID_MAIN_CONTENT;\n</script>\n\n<template>\n  <div class=\"relative flex min-h-full w-full\">\n    <LayoutSidebar\n      v-if=\"sidebarEnableState\"\n      v-model:draggable=\"sidebarDraggable\"\n      v-model:collapse=\"sidebarCollapse\"\n      v-model:expand-on-hover=\"sidebarExpandOnHover\"\n      v-model:expand-on-hovering=\"sidebarExpandOnHovering\"\n      v-model:extra-collapse=\"sidebarExtraCollapse\"\n      v-model:extra-visible=\"sidebarExtraVisible\"\n      :show-collapse-button=\"sidebarCollapsedButton\"\n      :show-fixed-button=\"sidebarFixedButton\"\n      :collapse-width=\"getSideCollapseWidth\"\n      :dom-visible=\"!isMobile\"\n      :extra-width=\"sidebarExtraWidth\"\n      :fixed-extra=\"sidebarExpandOnHover\"\n      :header-height=\"isMixedNav ? 0 : headerHeight\"\n      :is-sidebar-mixed=\"isSidebarMixedNav || isHeaderMixedNav\"\n      :margin-top=\"sidebarMarginTop\"\n      :mixed-width=\"sidebarMixedWidth\"\n      :show=\"showSidebar\"\n      :theme=\"sidebarTheme\"\n      :theme-sub=\"sidebarThemeSub\"\n      :width=\"getSidebarWidth\"\n      :z-index=\"sidebarZIndex\"\n      @leave=\"() => emit('sideMouseLeave')\"\n      @update:width=\"(val) => emit('update:sidebar-width', val)\"\n    >\n      <template v-if=\"isSideMode && !isMixedNav\" #logo>\n        <slot name=\"logo\"></slot>\n      </template>\n\n      <template v-if=\"isSidebarMixedNav || isHeaderMixedNav\">\n        <slot name=\"mixed-menu\"></slot>\n      </template>\n      <template v-else>\n        <slot name=\"menu\"></slot>\n      </template>\n\n      <template #extra>\n        <slot name=\"side-extra\"></slot>\n      </template>\n      <template #extra-title>\n        <slot name=\"side-extra-title\"></slot>\n      </template>\n    </LayoutSidebar>\n\n    <div\n      ref=\"contentRef\"\n      class=\"flex flex-1 flex-col overflow-hidden transition-all duration-300 ease-in\"\n    >\n      <div\n        :class=\"[\n          {\n            'shadow-[0_16px_24px_hsl(var(--background))]': scrollY > 20,\n          },\n          SCROLL_FIXED_CLASS,\n        ]\"\n        :style=\"headerWrapperStyle\"\n        class=\"overflow-hidden transition-all duration-200\"\n      >\n        <LayoutHeader\n          v-if=\"headerVisible\"\n          :full-width=\"!isSideMode\"\n          :height=\"headerHeight\"\n          :is-mobile=\"isMobile\"\n          :show=\"!isFullContent && !headerHidden\"\n          :sidebar-width=\"sidebarWidth\"\n          :theme=\"headerTheme\"\n          :width=\"mainStyle.width\"\n          :z-index=\"headerZIndex\"\n        >\n          <template v-if=\"showHeaderLogo\" #logo>\n            <slot name=\"logo\"></slot>\n          </template>\n\n          <template #toggle-button>\n            <VbenIconButton\n              v-if=\"showHeaderToggleButton\"\n              class=\"my-0 mr-1 rounded-md\"\n              @click=\"handleHeaderToggle\"\n            >\n              <IconifyIcon v-if=\"showSidebar\" icon=\"ep:fold\" />\n              <IconifyIcon v-else icon=\"ep:expand\" />\n            </VbenIconButton>\n          </template>\n          <slot name=\"header\"></slot>\n        </LayoutHeader>\n\n        <LayoutTabbar\n          v-if=\"tabbarEnable\"\n          :height=\"tabbarHeight\"\n          :style=\"tabbarStyle\"\n        >\n          <slot name=\"tabbar\"></slot>\n        </LayoutTabbar>\n      </div>\n\n      <!-- </div> -->\n      <LayoutContent\n        :id=\"idMainContent\"\n        :content-compact=\"contentCompact\"\n        :content-compact-width=\"contentCompactWidth\"\n        :padding=\"contentPadding\"\n        :padding-bottom=\"contentPaddingBottom\"\n        :padding-left=\"contentPaddingLeft\"\n        :padding-right=\"contentPaddingRight\"\n        :padding-top=\"contentPaddingTop\"\n        :style=\"contentStyle\"\n        class=\"transition-[margin-top] duration-200\"\n      >\n        <slot name=\"content\"></slot>\n\n        <template #overlay>\n          <slot name=\"content-overlay\"></slot>\n        </template>\n      </LayoutContent>\n\n      <LayoutFooter\n        v-if=\"footerEnable\"\n        :fixed=\"footerFixed\"\n        :height=\"footerHeight\"\n        :show=\"!isFullContent\"\n        :width=\"footerWidth\"\n        :z-index=\"zIndex\"\n      >\n        <slot name=\"footer\"></slot>\n      </LayoutFooter>\n    </div>\n    <slot name=\"extra\"></slot>\n    <div\n      v-if=\"maskVisible\"\n      :style=\"maskStyle\"\n      class=\"fixed top-0 left-0 size-full bg-overlay transition-[background-color] duration-200\"\n      @click=\"handleClickMask\"\n    ></div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/layout-ui/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\nimport Vue from 'unplugin-vue/rolldown';\n\nexport default defineConfig({\n  clean: true,\n  deps: {\n    skipNodeModulesBundle: true,\n  },\n  dts: {\n    vue: true,\n  },\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n    js: '.mjs',\n  }),\n  platform: 'neutral',\n  plugins: [Vue({ isProduction: true })],\n  unbundle: true,\n});\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/README.md",
    "content": "# 菜单组件\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/package.json",
    "content": "{\n  \"name\": \"@vben-core/menu-ui\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@vben-core/uikit/menu-ui\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm exec tsdown\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"production\": \"./src/index.ts\",\n      \"default\": \"./dist/index.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"default\": \"./dist/index.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/composables\": \"workspace:*\",\n    \"@vben-core/design\": \"workspace:*\",\n    \"@vben-core/icons\": \"workspace:*\",\n    \"@vben-core/shadcn-ui\": \"workspace:*\",\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben-core/typings\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"qs\": \"catalog:\",\n    \"vue\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/qs\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/collapse-transition.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { RendererElement } from 'vue';\n\ndefineOptions({\n  name: 'CollapseTransition',\n});\n\nconst reset = (el: RendererElement) => {\n  el.style.maxHeight = '';\n  el.style.overflow = el.dataset.oldOverflow;\n  el.style.paddingTop = el.dataset.oldPaddingTop;\n  el.style.paddingBottom = el.dataset.oldPaddingBottom;\n};\n\nconst on = {\n  afterEnter(el: RendererElement) {\n    el.style.maxHeight = '';\n    el.style.overflow = el.dataset.oldOverflow;\n  },\n\n  afterLeave(el: RendererElement) {\n    reset(el);\n  },\n\n  beforeEnter(el: RendererElement) {\n    if (!el.dataset) el.dataset = {};\n\n    el.dataset.oldPaddingTop = el.style.paddingTop;\n    el.dataset.oldMarginTop = el.style.marginTop;\n\n    el.dataset.oldPaddingBottom = el.style.paddingBottom;\n    el.dataset.oldMarginBottom = el.style.marginBottom;\n    if (el.style.height) el.dataset.elExistsHeight = el.style.height;\n\n    el.style.maxHeight = 0;\n    el.style.paddingTop = 0;\n    el.style.marginTop = 0;\n    el.style.paddingBottom = 0;\n    el.style.marginBottom = 0;\n  },\n\n  beforeLeave(el: RendererElement) {\n    if (!el.dataset) el.dataset = {};\n    el.dataset.oldPaddingTop = el.style.paddingTop;\n    el.dataset.oldMarginTop = el.style.marginTop;\n    el.dataset.oldPaddingBottom = el.style.paddingBottom;\n    el.dataset.oldMarginBottom = el.style.marginBottom;\n    el.dataset.oldOverflow = el.style.overflow;\n    el.style.maxHeight = `${el.scrollHeight}px`;\n    el.style.overflow = 'hidden';\n  },\n\n  enter(el: RendererElement) {\n    requestAnimationFrame(() => {\n      el.dataset.oldOverflow = el.style.overflow;\n      if (el.dataset.elExistsHeight) {\n        el.style.maxHeight = el.dataset.elExistsHeight;\n      } else if (el.scrollHeight === 0) {\n        el.style.maxHeight = 0;\n      } else {\n        el.style.maxHeight = `${el.scrollHeight}px`;\n      }\n\n      el.style.paddingTop = el.dataset.oldPaddingTop;\n      el.style.paddingBottom = el.dataset.oldPaddingBottom;\n      el.style.marginTop = el.dataset.oldMarginTop;\n      el.style.marginBottom = el.dataset.oldMarginBottom;\n      el.style.overflow = 'hidden';\n    });\n  },\n\n  enterCancelled(el: RendererElement) {\n    reset(el);\n  },\n\n  leave(el: RendererElement) {\n    if (el.scrollHeight !== 0) {\n      el.style.maxHeight = 0;\n      el.style.paddingTop = 0;\n      el.style.paddingBottom = 0;\n      el.style.marginTop = 0;\n      el.style.marginBottom = 0;\n    }\n  },\n\n  leaveCancelled(el: RendererElement) {\n    reset(el);\n  },\n};\n</script>\n\n<template>\n  <transition name=\"collapse-transition\" v-on=\"on\">\n    <slot></slot>\n  </transition>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/index.ts",
    "content": "export { default as MenuBadge } from './menu-badge.vue';\nexport { default as MenuItem } from './menu-item.vue';\nexport { default as Menu } from './menu.vue';\nexport { default as SubMenu } from './sub-menu.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/menu-badge-dot.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\ninterface Props {\n  dotClass?: string;\n  dotStyle?: CSSProperties;\n}\n\nwithDefaults(defineProps<Props>(), {\n  dotClass: '',\n  dotStyle: () => ({}),\n});\n</script>\n<template>\n  <span class=\"relative mr-1 flex size-1.5\">\n    <span\n      :class=\"dotClass\"\n      :style=\"dotStyle\"\n      class=\"absolute inline-flex size-full animate-ping rounded-full opacity-75\"\n    >\n    </span>\n    <span\n      :class=\"dotClass\"\n      :style=\"dotStyle\"\n      class=\"relative inline-flex size-1.5 rounded-full\"\n    ></span>\n  </span>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/menu-badge.vue",
    "content": "<script setup lang=\"ts\">\nimport type { MenuRecordBadgeRaw } from '@vben-core/typings';\n\nimport { computed } from 'vue';\n\nimport { isValidColor } from '@vben-core/shared/color';\n\nimport BadgeDot from './menu-badge-dot.vue';\n\ninterface Props extends MenuRecordBadgeRaw {\n  hasChildren?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {});\n\nconst variantsMap: Record<string, string> = {\n  default: 'bg-green-500',\n  destructive: 'bg-destructive',\n  primary: 'bg-primary',\n  success: 'bg-green-500',\n  warning: 'bg-yellow-500',\n};\n\nconst isDot = computed(() => props.badgeType === 'dot');\n\nconst badgeClass = computed(() => {\n  const { badgeVariants } = props;\n\n  if (!badgeVariants) {\n    return variantsMap.default;\n  }\n\n  return variantsMap[badgeVariants] || badgeVariants;\n});\n\nconst badgeStyle = computed(() => {\n  if (badgeClass.value && isValidColor(badgeClass.value)) {\n    return {\n      backgroundColor: badgeClass.value,\n    };\n  }\n  return {};\n});\n</script>\n<template>\n  <span v-if=\"isDot || badge\" :class=\"$attrs.class\" class=\"absolute\">\n    <BadgeDot v-if=\"isDot\" :dot-class=\"badgeClass\" :dot-style=\"badgeStyle\" />\n    <div\n      v-else\n      :class=\"badgeClass\"\n      :style=\"badgeStyle\"\n      class=\"flex-center rounded-xl px-1.5 py-0.5 text-[10px] text-primary-foreground\"\n    >\n      {{ badge }}\n    </div>\n  </span>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/menu-item.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { MenuItemProps, MenuItemRegistered } from '../types';\n\nimport { computed, onBeforeUnmount, onMounted, reactive, useSlots } from 'vue';\n\nimport { useNamespace } from '@vben-core/composables';\nimport { VbenIcon, VbenTooltip } from '@vben-core/shadcn-ui';\n\nimport qs from 'qs';\n\nimport { MenuBadge } from '../components';\nimport { useMenu, useMenuContext, useSubMenuContext } from '../hooks';\n\ninterface Props extends MenuItemProps {}\n\ndefineOptions({ name: 'MenuItem' });\n\nconst props = withDefaults(defineProps<Props>(), {\n  disabled: false,\n});\n\nconst emit = defineEmits<{ click: [MenuItemRegistered] }>();\n\nconst slots = useSlots();\nconst { b, e, is } = useNamespace('menu-item');\nconst nsMenu = useNamespace('menu');\nconst rootMenu = useMenuContext();\nconst subMenu = useSubMenuContext();\nconst { parentMenu, parentPaths } = useMenu();\n\nconst active = computed(() => props.path === rootMenu?.activePath);\nconst menuIcon = computed(() =>\n  active.value ? props.activeIcon || props.icon : props.icon,\n);\n\nconst isTopLevelMenuItem = computed(\n  () => parentMenu.value?.type.name === 'Menu',\n);\n\nconst collapseShowTitle = computed(\n  () =>\n    rootMenu.props?.collapseShowTitle &&\n    isTopLevelMenuItem.value &&\n    rootMenu.props.collapse,\n);\n\nconst showTooltip = computed(\n  () =>\n    rootMenu.props.mode === 'vertical' &&\n    isTopLevelMenuItem.value &&\n    rootMenu.props?.collapse &&\n    slots.title,\n);\n\nconst item: MenuItemRegistered = reactive({\n  active,\n  parentPaths: parentPaths.value,\n  path: props.path || '',\n  query: props.query,\n});\n\n/**\n * 菜单项点击事件\n */\nfunction handleClick() {\n  if (props.disabled) {\n    return;\n  }\n  rootMenu?.handleMenuItemClick?.({\n    parentPaths: parentPaths.value,\n    path: props.path,\n  });\n  emit('click', item);\n}\n\nonMounted(() => {\n  subMenu?.addSubMenu?.(item);\n  rootMenu?.addMenuItem?.(item);\n});\n\nonBeforeUnmount(() => {\n  subMenu?.removeSubMenu?.(item);\n  rootMenu?.removeMenuItem?.(item);\n});\n</script>\n<template>\n  <a\n    :href=\"\n      (item.parentPaths.at(-1) ?? '') +\n      (item?.query ? `?${qs.stringify(item?.query)}` : '')\n    \"\n    @click.prevent.stop=\"handleClick\"\n  >\n    <li\n      :class=\"[\n        rootMenu.theme,\n        b(),\n        is('active', active),\n        is('disabled', disabled),\n        is('collapse-show-title', collapseShowTitle),\n      ]\"\n      role=\"menuitem\"\n    >\n      <!-- -->\n      <VbenTooltip\n        v-if=\"showTooltip\"\n        :content-class=\"[rootMenu.theme]\"\n        side=\"right\"\n      >\n        <template #trigger>\n          <div :class=\"[nsMenu.be('tooltip', 'trigger')]\">\n            <VbenIcon :class=\"nsMenu.e('icon')\" :icon=\"menuIcon\" fallback />\n            <slot></slot>\n            <span v-if=\"collapseShowTitle\" :class=\"nsMenu.e('name')\">\n              <slot name=\"title\"></slot>\n            </span>\n          </div>\n        </template>\n        <slot name=\"title\"></slot>\n      </VbenTooltip>\n      <div v-show=\"!showTooltip\" :class=\"[e('content')]\">\n        <MenuBadge\n          v-if=\"rootMenu.props.mode !== 'horizontal'\"\n          class=\"right-2\"\n          v-bind=\"props\"\n        />\n        <VbenIcon :class=\"nsMenu.e('icon')\" :icon=\"menuIcon\" />\n        <slot></slot>\n        <slot name=\"title\"></slot>\n      </div>\n    </li>\n  </a>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/menu.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { UseResizeObserverReturn } from '@vueuse/core';\n\nimport type { SetupContext, VNodeArrayChildren } from 'vue';\n\nimport type {\n  MenuItemClicked,\n  MenuItemRegistered,\n  MenuProps,\n  MenuProvider,\n} from '../types';\n\nimport {\n  computed,\n  nextTick,\n  reactive,\n  ref,\n  toRef,\n  useSlots,\n  watch,\n  watchEffect,\n} from 'vue';\n\nimport { useNamespace } from '@vben-core/composables';\nimport { Ellipsis } from '@vben-core/icons';\n\nimport { useResizeObserver } from '@vueuse/core';\n\nimport {\n  createMenuContext,\n  createSubMenuContext,\n  useMenuStyle,\n} from '../hooks';\nimport { useMenuScroll } from '../hooks/use-menu-scroll';\nimport { flattedChildren } from '../utils';\nimport SubMenu from './sub-menu.vue';\n\ninterface Props extends MenuProps {}\n\ndefineOptions({ name: 'Menu' });\n\nconst props = withDefaults(defineProps<Props>(), {\n  accordion: true,\n  collapse: false,\n  mode: 'vertical',\n  rounded: true,\n  theme: 'dark',\n  scrollToActive: false,\n});\n\nconst emit = defineEmits<{\n  close: [string, string[]];\n  open: [string, string[]];\n  select: [string, string[]];\n}>();\n\nconst { b, is } = useNamespace('menu');\nconst menuStyle = useMenuStyle();\nconst slots: SetupContext['slots'] = useSlots();\nconst menu = ref<HTMLUListElement>();\nconst sliceIndex = ref(-1);\nconst openedMenus = ref<MenuProvider['openedMenus']>(\n  props.defaultOpeneds && !props.collapse ? [...props.defaultOpeneds] : [],\n);\nconst activePath = ref<MenuProvider['activePath']>(props.defaultActive);\nconst items = ref<MenuProvider['items']>({});\nconst subMenus = ref<MenuProvider['subMenus']>({});\nconst mouseInChild = ref(false);\n\nconst isMenuPopup = computed<MenuProvider['isMenuPopup']>(() => {\n  return (\n    props.mode === 'horizontal' || (props.mode === 'vertical' && props.collapse)\n  );\n});\n\nconst getSlot = computed(() => {\n  // 更新插槽内容\n  const defaultSlots: VNodeArrayChildren = slots.default?.() ?? [];\n\n  const originalSlot = flattedChildren(defaultSlots) as VNodeArrayChildren;\n  const slotDefault =\n    sliceIndex.value === -1\n      ? originalSlot\n      : originalSlot.slice(0, sliceIndex.value);\n\n  const slotMore =\n    sliceIndex.value === -1 ? [] : originalSlot.slice(sliceIndex.value);\n\n  return { showSlotMore: slotMore.length > 0, slotDefault, slotMore };\n});\n\nwatch(\n  () => props.collapse,\n  (value) => {\n    if (value) openedMenus.value = [];\n  },\n);\n\nwatch(items.value, initMenu);\n\nwatch(\n  () => props.defaultActive,\n  (currentActive = '') => {\n    if (!items.value[currentActive]) {\n      activePath.value = '';\n    }\n    updateActiveName(currentActive);\n  },\n);\n\nlet resizeStopper: UseResizeObserverReturn['stop'];\nwatchEffect(() => {\n  if (props.mode === 'horizontal') {\n    resizeStopper = useResizeObserver(menu, handleResize).stop;\n  } else {\n    resizeStopper?.();\n  }\n});\n\n// 注入上下文\ncreateMenuContext(\n  reactive({\n    activePath,\n    addMenuItem,\n    addSubMenu,\n    closeMenu,\n    handleMenuItemClick,\n    handleSubMenuClick,\n    isMenuPopup,\n    openedMenus,\n    openMenu,\n    props,\n    removeMenuItem,\n    removeSubMenu,\n    subMenus,\n    theme: toRef(props, 'theme'),\n    items,\n  }),\n);\n\ncreateSubMenuContext({\n  addSubMenu,\n  level: 1,\n  mouseInChild,\n  removeSubMenu,\n});\n\nfunction calcMenuItemWidth(menuItem: HTMLElement) {\n  const computedStyle = getComputedStyle(menuItem);\n  const marginLeft = Number.parseInt(computedStyle.marginLeft, 10);\n  const marginRight = Number.parseInt(computedStyle.marginRight, 10);\n  return menuItem.offsetWidth + marginLeft + marginRight || 0;\n}\n\nfunction calcSliceIndex() {\n  if (!menu.value) {\n    return -1;\n  }\n  const items = [...(menu.value?.childNodes ?? [])].filter(\n    (item) =>\n      // remove comment type node #12634\n      item.nodeName !== '#comment' &&\n      (item.nodeName !== '#text' || item.nodeValue),\n  ) as HTMLElement[];\n\n  const moreItemWidth = 46;\n  const computedMenuStyle = getComputedStyle(menu?.value);\n\n  const paddingLeft = Number.parseInt(computedMenuStyle.paddingLeft, 10);\n  const paddingRight = Number.parseInt(computedMenuStyle.paddingRight, 10);\n  const menuWidth = menu.value?.clientWidth - paddingLeft - paddingRight;\n\n  let calcWidth = 0;\n  let sliceIndex = 0;\n  items.forEach((item, index) => {\n    calcWidth += calcMenuItemWidth(item);\n    if (calcWidth <= menuWidth - moreItemWidth) {\n      sliceIndex = index + 1;\n    }\n  });\n  return sliceIndex === items.length ? -1 : sliceIndex;\n}\n\nfunction debounce(fn: () => void, wait = 33.34) {\n  let timer: null | ReturnType<typeof setTimeout>;\n  return () => {\n    timer && clearTimeout(timer);\n    timer = setTimeout(() => {\n      fn();\n    }, wait);\n  };\n}\n\nlet isFirstTimeRender = true;\nfunction handleResize() {\n  if (sliceIndex.value === calcSliceIndex()) {\n    return;\n  }\n  const callback = () => {\n    sliceIndex.value = -1;\n    nextTick(() => {\n      sliceIndex.value = calcSliceIndex();\n    });\n  };\n  callback();\n  // // execute callback directly when first time resize to avoid shaking\n  isFirstTimeRender ? callback() : debounce(callback)();\n  isFirstTimeRender = false;\n}\n\nconst enableScroll = computed(\n  () => props.scrollToActive && props.mode === 'vertical' && !props.collapse,\n);\n\nuseMenuScroll(activePath, {\n  enable: enableScroll,\n  delay: 320,\n});\n\n// 监听 activePath 变化，自动滚动到激活项\n// watch(activePath, () => {\n//   scrollToActiveItem();\n// });\n\n// 默认展开菜单\nfunction initMenu() {\n  const parentPaths = getActivePaths();\n\n  // 展开该菜单项的路径上所有子菜单\n  // expand all subMenus of the menu item\n  parentPaths.forEach((path) => {\n    const subMenu = subMenus.value[path];\n    subMenu && openMenu(path, subMenu.parentPaths);\n  });\n}\n\nfunction updateActiveName(val: string) {\n  const itemsInData = items.value;\n  const item =\n    itemsInData[val] ||\n    (activePath.value && itemsInData[activePath.value]) ||\n    itemsInData[props.defaultActive || ''];\n\n  activePath.value = item ? item.path : val;\n}\n\nfunction handleMenuItemClick(data: MenuItemClicked) {\n  const { collapse, mode } = props;\n  if (mode === 'horizontal' || collapse) {\n    openedMenus.value = [];\n  }\n  const { parentPaths, path } = data;\n  if (!path || !parentPaths) {\n    return;\n  }\n\n  emit('select', path, parentPaths);\n}\n\nfunction handleSubMenuClick({ parentPaths, path }: MenuItemRegistered) {\n  const isOpened = openedMenus.value.includes(path);\n\n  if (isOpened) {\n    closeMenu(path, parentPaths);\n  } else {\n    openMenu(path, parentPaths);\n  }\n}\n\nfunction close(path: string) {\n  const i = openedMenus.value.indexOf(path);\n\n  if (i !== -1) {\n    openedMenus.value.splice(i, 1);\n  }\n}\n\n/**\n * 关闭、折叠菜单\n */\nfunction closeMenu(path: string, parentPaths: string[]) {\n  if (props.accordion) {\n    openedMenus.value = subMenus.value[path]?.parentPaths ?? [];\n  }\n\n  close(path);\n\n  emit('close', path, parentPaths);\n}\n\n/**\n * 点击展开菜单\n */\nfunction openMenu(path: string, parentPaths: string[]) {\n  if (openedMenus.value.includes(path)) {\n    return;\n  }\n  // 手风琴模式菜单\n  if (props.accordion) {\n    const activeParentPaths = getActivePaths();\n    if (activeParentPaths.includes(path)) {\n      parentPaths = activeParentPaths;\n    }\n    openedMenus.value = openedMenus.value.filter((path: string) =>\n      parentPaths.includes(path),\n    );\n  }\n  openedMenus.value.push(path);\n  emit('open', path, parentPaths);\n}\n\nfunction addMenuItem(item: MenuItemRegistered) {\n  items.value[item.path] = item;\n}\n\nfunction addSubMenu(subMenu: MenuItemRegistered) {\n  subMenus.value[subMenu.path] = subMenu;\n}\n\nfunction removeSubMenu(subMenu: MenuItemRegistered) {\n  Reflect.deleteProperty(subMenus.value, subMenu.path);\n}\n\nfunction removeMenuItem(item: MenuItemRegistered) {\n  Reflect.deleteProperty(items.value, item.path);\n}\n\nfunction getActivePaths() {\n  const activeItem = activePath.value && items.value[activePath.value];\n\n  if (!activeItem || props.mode === 'horizontal' || props.collapse) {\n    return [];\n  }\n\n  return activeItem.parentPaths;\n}\n</script>\n<template>\n  <ul\n    ref=\"menu\"\n    :class=\"[\n      theme,\n      b(),\n      is(mode, true),\n      is(theme, true),\n      is('rounded', rounded),\n      is('collapse', collapse),\n      is('menu-align', mode === 'horizontal'),\n    ]\"\n    :style=\"menuStyle\"\n    role=\"menu\"\n  >\n    <template v-if=\"mode === 'horizontal' && getSlot.showSlotMore\">\n      <template v-for=\"(item, index) in getSlot.slotDefault\" :key=\"index\">\n        <component :is=\"item\" />\n      </template>\n      <SubMenu is-sub-menu-more path=\"sub-menu-more\">\n        <template #title>\n          <Ellipsis class=\"size-4\" />\n        </template>\n        <template v-for=\"(item, index) in getSlot.slotMore\" :key=\"index\">\n          <component :is=\"item\" />\n        </template>\n      </SubMenu>\n    </template>\n    <template v-else>\n      <slot></slot>\n    </template>\n  </ul>\n</template>\n\n<style lang=\"scss\">\n$namespace: vben;\n\n@mixin menu-item-active {\n  color: var(--menu-item-active-color);\n  text-decoration: none;\n  cursor: pointer;\n  background: var(--menu-item-active-background-color);\n}\n\n@mixin menu-item {\n  position: relative;\n  display: flex;\n  // gap: 12px;\n  align-items: center;\n  height: var(--menu-item-height);\n  padding: var(--menu-item-padding-y) var(--menu-item-padding-x);\n  margin: 0 var(--menu-item-margin-x) var(--menu-item-margin-y)\n    var(--menu-item-margin-x);\n  font-size: var(--menu-font-size) !important;\n  color: var(--menu-item-color);\n  white-space: nowrap;\n  text-decoration: none;\n  cursor: pointer;\n  list-style: none;\n  background: var(--menu-item-background-color);\n  border: none;\n  border-radius: var(--menu-item-radius);\n  transition:\n    background 0.15s ease,\n    color 0.15s ease,\n    padding 0.15s ease,\n    border-color 0.15s ease;\n\n  &.is-disabled {\n    cursor: not-allowed;\n    background: none !important;\n    opacity: 0.25;\n  }\n\n  .#{$namespace}-menu__icon {\n    transition: transform 0.25s;\n  }\n\n  &:hover {\n    .#{$namespace}-menu__icon {\n      transform: scale(1.2);\n    }\n  }\n\n  &:hover,\n  &:focus {\n    outline: none;\n  }\n\n  * {\n    vertical-align: bottom;\n  }\n}\n\n@mixin menu-title {\n  max-width: var(--menu-title-width);\n  overflow: hidden;\n  text-overflow: ellipsis;\n  font-size: var(--menu-font-size) !important;\n  white-space: nowrap;\n  opacity: 1;\n}\n\n.is-menu-align {\n  justify-content: var(--menu-align, start);\n}\n\n.#{$namespace}-menu__popup-container,\n.#{$namespace}-menu {\n  --menu-title-width: 140px;\n  --menu-item-icon-size: var(--font-size-base, 16px);\n  --menu-item-height: 38px;\n  --menu-item-padding-y: 21px;\n  --menu-item-padding-x: 12px;\n  --menu-item-popup-padding-y: 20px;\n  --menu-item-popup-padding-x: 12px;\n  --menu-item-margin-y: 2px;\n  --menu-item-margin-x: 0px;\n  --menu-item-collapse-padding-y: 23.5px;\n  --menu-item-collapse-padding-x: 0px;\n  --menu-item-collapse-margin-y: 4px;\n  --menu-item-collapse-margin-x: 0px;\n  --menu-item-radius: 0px;\n  --menu-item-indent: 16px;\n\n  &.is-dark {\n    --menu-background-color: hsl(var(--menu));\n    // --menu-submenu-opened-background-color: hsl(var(--menu-opened-dark));\n    --menu-item-color: hsl(var(--foreground) / 80%);\n    --menu-item-background-color: var(--menu-background-color);\n    --menu-item-hover-color: hsl(var(--accent-foreground));\n    --menu-item-hover-background-color: hsl(var(--accent));\n    --menu-item-active-color: hsl(var(--accent-foreground));\n    --menu-item-active-background-color: hsl(var(--accent));\n    --menu-submenu-background-color: var(--menu-background-color);\n    --menu-submenu-hover-color: hsl(var(--accent-foreground));\n    --menu-submenu-hover-background-color: hsl(var(--accent));\n    --menu-submenu-active-color: hsl(var(--accent-foreground));\n    --menu-submenu-active-background-color: transparent;\n  }\n\n  &.is-light {\n    --menu-background-color: hsl(var(--menu));\n    // --menu-submenu-opened-background-color: hsl(var(--menu-opened));\n    --menu-item-color: hsl(var(--accent-foreground));\n    --menu-item-background-color: var(--menu-background-color);\n    --menu-item-hover-color: var(--menu-item-color);\n    --menu-item-hover-background-color: hsl(var(--accent));\n    --menu-item-active-color: hsl(var(--primary));\n    --menu-item-active-background-color: hsl(var(--primary) / 15%);\n    --menu-submenu-background-color: var(--menu-background-color);\n    --menu-submenu-hover-color: hsl(var(--primary));\n    --menu-submenu-hover-background-color: hsl(var(--accent));\n    --menu-submenu-active-color: hsl(var(--primary));\n    --menu-submenu-active-background-color: transparent;\n  }\n\n  &.is-rounded {\n    --menu-item-margin-x: 8px;\n    --menu-item-collapse-margin-x: 6px;\n    --menu-item-radius: 8px;\n  }\n\n  &.is-horizontal:not(.is-rounded) {\n    --menu-item-height: 40px;\n    --menu-item-radius: 6px;\n  }\n\n  &.is-horizontal.is-rounded {\n    --menu-item-height: 40px;\n    --menu-item-radius: 6px;\n    --menu-item-padding-x: 12px;\n  }\n\n  // .vben-menu__popup,\n  &.is-horizontal {\n    --menu-item-padding-y: 0px;\n    --menu-item-padding-x: 10px;\n    --menu-item-margin-y: 0px;\n    --menu-item-margin-x: 1px;\n    --menu-background-color: transparent;\n\n    &.is-dark {\n      --menu-background-color: hsl(var(--menu));\n      --menu-item-color: hsl(var(--foreground) / 80%);\n      --menu-item-background-color: var(--menu-background-color);\n      --menu-item-hover-color: hsl(var(--accent-foreground));\n      --menu-item-hover-background-color: hsl(var(--accent));\n      --menu-item-active-color: hsl(var(--accent-foreground));\n      --menu-item-active-background-color: hsl(var(--accent));\n      --menu-submenu-background-color: var(--menu-background-color);\n      --menu-submenu-hover-color: hsl(var(--accent-foreground));\n      --menu-submenu-hover-background-color: hsl(var(--accent));\n      --menu-submenu-active-color: hsl(var(--accent-foreground));\n      --menu-submenu-active-background-color: hsl(var(--accent));\n    }\n\n    &.is-light {\n      --menu-background-color: hsl(var(--menu));\n      --menu-item-color: hsl(var(--accent-foreground));\n      --menu-item-background-color: var(--menu-background-color);\n      --menu-item-hover-color: var(--menu-item-color);\n      --menu-item-hover-background-color: hsl(var(--accent));\n      --menu-item-active-color: hsl(var(--primary));\n      --menu-item-active-background-color: hsl(var(--primary) / 15%);\n      --menu-submenu-background-color: var(--menu-background-color);\n      --menu-submenu-hover-color: hsl(var(--primary));\n      --menu-submenu-hover-background-color: hsl(var(--accent));\n      --menu-submenu-active-color: hsl(var(--primary));\n      --menu-submenu-active-background-color: hsl(var(--primary) / 15%);\n    }\n  }\n}\n\n.#{$namespace}-menu {\n  position: relative;\n  box-sizing: border-box;\n  padding-left: 0;\n  margin: 0;\n  list-style: none;\n  background: hsl(var(--menu-background-color));\n\n  // 垂直菜单\n  &.is-vertical {\n    &:not(.#{$namespace}-menu.is-collapse) {\n      & .#{$namespace}-menu-item,\n      & .#{$namespace}-sub-menu-content,\n      & .#{$namespace}-menu-item-group__title {\n        padding-left: calc(\n          var(--menu-item-indent) + var(--menu-level) * var(--menu-item-indent)\n        );\n        white-space: nowrap;\n      }\n\n      & > .#{$namespace}-sub-menu {\n        & > .#{$namespace}-menu {\n          & > .#{$namespace}-menu-item {\n            padding-left: calc(\n              0px + var(--menu-item-indent) + var(--menu-level) *\n                var(--menu-item-indent)\n            );\n          }\n        }\n\n        & > .#{$namespace}-sub-menu-content {\n          padding-left: calc(var(--menu-item-indent) - 8px);\n        }\n      }\n      & > .#{$namespace}-menu-item {\n        padding-left: calc(var(--menu-item-indent) - 8px);\n      }\n    }\n  }\n\n  &.is-horizontal {\n    display: flex;\n    flex-wrap: nowrap;\n    max-width: 100%;\n    height: var(--height-horizontal-height);\n    border-right: none;\n\n    .#{$namespace}-menu-item {\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      height: var(--menu-item-height);\n      padding-right: calc(var(--menu-item-padding-x) + 6px);\n      margin: 0;\n      margin-right: 2px;\n      // border-bottom: 2px solid transparent;\n      border-radius: var(--menu-item-radius);\n    }\n\n    & > .#{$namespace}-sub-menu {\n      height: var(--menu-item-height);\n      margin-right: 2px;\n\n      &:focus,\n      &:hover {\n        outline: none;\n      }\n\n      & .#{$namespace}-sub-menu-content {\n        height: 100%;\n        padding-right: 40px;\n        // border-bottom: 2px solid transparent;\n        border-radius: var(--menu-item-radius);\n      }\n    }\n\n    & .#{$namespace}-menu-item:not(.is-disabled):hover,\n    & .#{$namespace}-menu-item:not(.is-disabled):focus {\n      outline: none;\n    }\n\n    & > .#{$namespace}-menu-item.is-active {\n      color: var(--menu-item-active-color);\n    }\n\n    // &.is-light {\n    //   & > .#{$namespace}-sub-menu {\n    //     &.is-active {\n    //       border-bottom: 2px solid var(--menu-item-active-color);\n    //     }\n    //     &:not(.is-active) .#{$namespace}-sub-menu-content {\n    //       &:hover {\n    //         border-bottom: 2px solid var(--menu-item-active-color);\n    //       }\n    //     }\n    //   }\n    //   & > .#{$namespace}-menu-item.is-active {\n    //     border-bottom: 2px solid var(--menu-item-active-color);\n    //   }\n\n    //   & .#{$namespace}-menu-item:not(.is-disabled):hover,\n    //   & .#{$namespace}-menu-item:not(.is-disabled):focus {\n    //     border-bottom: 2px solid var(--menu-item-active-color);\n    //   }\n    // }\n  }\n  // 折叠菜单\n\n  &.is-collapse {\n    .#{$namespace}-menu__icon {\n      margin-right: 0;\n    }\n    .#{$namespace}-sub-menu__icon-arrow {\n      display: none;\n    }\n\n    .#{$namespace}-sub-menu-content,\n    .#{$namespace}-menu-item {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: var(--menu-item-collapse-padding-y)\n        var(--menu-item-collapse-padding-x);\n      margin: var(--menu-item-collapse-margin-y)\n        var(--menu-item-collapse-margin-x);\n      transition: all 0.3s;\n\n      &.is-active {\n        background: var(--menu-item-active-background-color) !important;\n        border-radius: var(--menu-item-radius);\n      }\n    }\n\n    &.is-light {\n      .#{$namespace}-sub-menu-content,\n      .#{$namespace}-menu-item {\n        &.is-active {\n          // color: hsl(var(--primary-foreground)) !important;\n          background: var(--menu-item-active-background-color) !important;\n        }\n      }\n    }\n\n    &.is-rounded {\n      .#{$namespace}-sub-menu-content,\n      .#{$namespace}-menu-item {\n        &.is-collapse-show-title {\n          // padding: 32px 0 !important;\n          margin: 4px 8px !important;\n        }\n      }\n    }\n  }\n\n  &__popup-container {\n    max-width: 240px;\n    height: unset;\n    padding: 0;\n    background: var(--menu-background-color);\n  }\n\n  &__popup {\n    padding: 10px 0;\n    border-radius: var(--menu-item-radius);\n\n    .#{$namespace}-sub-menu-content,\n    .#{$namespace}-menu-item {\n      padding: var(--menu-item-popup-padding-y) var(--menu-item-popup-padding-x);\n    }\n  }\n\n  &__icon {\n    flex-shrink: 0;\n    width: var(--menu-item-icon-size);\n    height: var(--menu-item-icon-size);\n    margin-right: 8px;\n    vertical-align: middle;\n    text-align: center;\n  }\n}\n\n.#{$namespace}-menu-item {\n  fill: var(--menu-item-color);\n\n  @include menu-item;\n\n  &.is-active {\n    fill: var(--menu-item-active-color);\n\n    @include menu-item-active;\n  }\n\n  &__content {\n    display: inline-flex;\n    align-items: center;\n    width: 100%;\n    height: var(--menu-item-height);\n\n    span {\n      @include menu-title;\n    }\n  }\n\n  &.is-collapse-show-title {\n    padding: 32px 0 !important;\n    // margin: 4px 8px !important;\n    .#{$namespace}-menu-tooltip__trigger {\n      flex-direction: column;\n    }\n    .#{$namespace}-menu__icon {\n      display: block;\n      font-size: calc(var(--font-size-base, 16px) * 1.25) !important;\n      transition: all 0.25s ease;\n    }\n\n    .#{$namespace}-menu__name {\n      display: inline-flex;\n      margin-top: 8px;\n      margin-bottom: 0;\n      font-size: calc(var(--font-size-base, 16px) * 0.75);\n      font-weight: 400;\n      line-height: normal;\n      transition: all 0.25s ease;\n    }\n  }\n\n  &:not(.is-active):hover {\n    color: var(--menu-item-hover-color);\n    text-decoration: none;\n    cursor: pointer;\n    background: var(--menu-item-hover-background-color) !important;\n  }\n\n  .#{$namespace}-menu-tooltip__trigger {\n    position: absolute;\n    top: 0;\n    left: 0;\n    box-sizing: border-box;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    width: 100%;\n    height: 100%;\n    padding: 0 var(--menu-item-padding-x);\n    font-size: var(--menu-font-size) !important;\n    line-height: var(--menu-item-height);\n  }\n}\n\n.#{$namespace}-sub-menu {\n  padding-left: 0;\n  margin: 0;\n  list-style: none;\n  background: var(--menu-submenu-background-color);\n  fill: var(--menu-item-color);\n\n  &.is-active {\n    div[data-state='open'] > .#{$namespace}-sub-menu-content,\n    > .#{$namespace}-sub-menu-content {\n      // font-weight: 500;\n      color: var(--menu-submenu-active-color);\n      text-decoration: none;\n      cursor: pointer;\n      background: var(--menu-submenu-active-background-color);\n      fill: var(--menu-submenu-active-color);\n    }\n  }\n}\n\n.#{$namespace}-sub-menu-content {\n  height: var(--menu-item-height);\n  font-size: var(--menu-font-size) !important;\n\n  @include menu-item;\n\n  * {\n    font-size: inherit !important;\n  }\n\n  &__icon-arrow {\n    position: absolute;\n    top: 50%;\n    right: 10px;\n    width: inherit;\n    margin-top: -8px;\n    margin-right: 0;\n    // font-size: 16px;\n    font-weight: normal;\n    opacity: 1;\n    transition: transform 0.25s ease;\n  }\n\n  &__title {\n    @include menu-title;\n  }\n\n  &.is-collapse-show-title {\n    flex-direction: column;\n    padding: 32px 0 !important;\n    // margin: 4px 8px !important;\n    .#{$namespace}-menu__icon {\n      display: block;\n      font-size: 20px !important;\n      transition: all 0.25s ease;\n    }\n    .#{$namespace}-sub-menu-content__title {\n      display: inline-flex;\n      flex-shrink: 0;\n      margin-top: 8px;\n      margin-bottom: 0;\n      font-size: 12px;\n      font-weight: 400;\n      line-height: normal;\n      transition: all 0.25s ease;\n    }\n  }\n\n  &.is-more {\n    padding-right: 12px !important;\n  }\n\n  &:not(.is-active):hover {\n    //color: var(--menu-submenu-hover-color);\n    text-decoration: none;\n    cursor: pointer;\n    background: var(--menu-submenu-hover-background-color) !important;\n\n    // svg {\n    //   fill: var(--menu-submenu-hover-color);\n    // }\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/normal-menu/index.ts",
    "content": "export type * from './normal-menu';\nexport { default as NormalMenu } from './normal-menu.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.ts",
    "content": "import type { MenuRecordRaw } from '@vben-core/typings';\n\ninterface NormalMenuProps {\n  /**\n   * 菜单数据\n   */\n  activePath?: string;\n  /**\n   * 是否折叠\n   */\n  collapse?: boolean;\n  /**\n   * 菜单项\n   */\n  menus?: MenuRecordRaw[];\n  /**\n   * @zh_CN 是否圆润风格\n   * @default true\n   */\n  rounded?: boolean;\n  /**\n   * 主题\n   */\n  theme?: 'dark' | 'light';\n}\n\nexport type { NormalMenuProps };\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue",
    "content": "<script setup lang=\"ts\">\nimport type { MenuRecordRaw } from '@vben-core/typings';\n\nimport type { NormalMenuProps } from './normal-menu';\n\nimport { useNamespace } from '@vben-core/composables';\nimport { VbenIcon } from '@vben-core/shadcn-ui';\n\ninterface Props extends NormalMenuProps {}\n\ndefineOptions({\n  name: 'NormalMenu',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  activePath: '',\n  collapse: false,\n  menus: () => [],\n  theme: 'dark',\n});\n\nconst emit = defineEmits<{\n  enter: [MenuRecordRaw];\n  select: [MenuRecordRaw];\n}>();\n\nconst { b, e, is } = useNamespace('normal-menu');\n\nfunction menuIcon(menu: MenuRecordRaw) {\n  return props.activePath === menu.path\n    ? menu.activeIcon || menu.icon\n    : menu.icon;\n}\n</script>\n\n<template>\n  <ul\n    :class=\"[\n      theme,\n      b(),\n      is('collapse', collapse),\n      is(theme, true),\n      is('rounded', rounded),\n    ]\"\n    class=\"relative\"\n  >\n    <template v-for=\"menu in menus\" :key=\"menu.path\">\n      <li\n        :class=\"[e('item'), is('active', activePath === menu.path)]\"\n        @click=\"() => emit('select', menu)\"\n        @mouseenter=\"() => emit('enter', menu)\"\n      >\n        <VbenIcon :class=\"e('icon')\" :icon=\"menuIcon(menu)\" fallback />\n\n        <span :class=\"e('name')\" class=\"truncate\"> {{ menu.name }}</span>\n      </li>\n    </template>\n  </ul>\n</template>\n<style scoped>\n@reference \"@vben/tailwind-config/theme\";\n\n.vben-normal-menu {\n  --menu-item-margin-y: 4px;\n  --menu-item-margin-x: 0px;\n  --menu-item-padding-y: 9px;\n  --menu-item-padding-x: 0px;\n  --menu-item-radius: 0px;\n\n  height: calc(100% - 4px);\n}\n\n.vben-normal-menu.is-rounded {\n  --menu-item-radius: 6px;\n  --menu-item-margin-x: 8px;\n}\n\n.vben-normal-menu.is-dark .vben-normal-menu__item {\n  @apply text-foreground/80;\n}\n\n.vben-normal-menu.is-dark .vben-normal-menu__item:not(.is-active):hover {\n  @apply text-foreground;\n}\n\n.vben-normal-menu.is-dark\n  .vben-normal-menu__item.is-active\n  .vben-normal-menu__name,\n.vben-normal-menu.is-dark\n  .vben-normal-menu__item.is-active\n  .vben-normal-menu__icon {\n  @apply text-foreground;\n}\n\n.vben-normal-menu.is-collapse .vben-normal-menu__name {\n  width: 0;\n  height: 0;\n  margin-top: 0;\n  overflow: hidden;\n  opacity: 0;\n}\n\n.vben-normal-menu.is-collapse .vben-normal-menu__icon {\n  font-size: calc(var(--font-size-base, 16px) * 1.25);\n}\n\n.vben-normal-menu__item {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n\n  /* max-width: 64px; */\n\n  /* max-height: 64px; */\n  padding: var(--menu-item-padding-y) var(--menu-item-padding-x);\n  margin: var(--menu-item-margin-y) var(--menu-item-margin-x);\n  color: hsl(var(--foreground) / 90%);\n  cursor: pointer;\n  border-radius: var(--menu-item-radius);\n  transition:\n    background 0.15s ease,\n    padding 0.15s ease,\n    border-color 0.15s ease;\n}\n\n.vben-normal-menu__item.is-active {\n  @apply bg-primary text-primary dark:bg-accent;\n}\n\n.vben-normal-menu__item.is-active .vben-normal-menu__name,\n.vben-normal-menu__item.is-active .vben-normal-menu__icon {\n  @apply text-primary-foreground font-semibold;\n}\n\n.vben-normal-menu__item:not(.is-active):hover {\n  @apply bg-heavy text-primary dark:bg-accent dark:text-foreground;\n}\n\n.vben-normal-menu__item:hover .vben-normal-menu__icon {\n  transform: scale(1.2);\n}\n\n.vben-normal-menu__icon {\n  @apply max-h-5;\n\n  font-size: calc(var(--font-size-base, 16px) * 1.25);\n  transition: all 0.25s ease;\n}\n\n.vben-normal-menu__name {\n  @apply mt-2;\n\n  width: 100%;\n  margin-bottom: 0;\n  font-size: calc(var(--font-size-base, 16px) * 0.75);\n  font-weight: 400;\n  text-align: center;\n  transition: all 0.25s ease;\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/sub-menu-content.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { MenuItemProps } from '../types';\n\nimport { computed } from 'vue';\n\nimport { useNamespace } from '@vben-core/composables';\nimport { ChevronDown, ChevronRight } from '@vben-core/icons';\nimport { VbenIcon } from '@vben-core/shadcn-ui';\n\nimport { useMenuContext } from '../hooks';\n\ninterface Props extends MenuItemProps {\n  isMenuMore?: boolean;\n  isTopLevelMenuSubmenu: boolean;\n  level?: number;\n}\n\ndefineOptions({ name: 'SubMenuContent' });\n\nconst props = withDefaults(defineProps<Props>(), {\n  isMenuMore: false,\n  level: 0,\n});\n\nconst rootMenu = useMenuContext();\nconst { b, e, is } = useNamespace('sub-menu-content');\nconst nsMenu = useNamespace('menu');\n\nconst opened = computed(() => {\n  return rootMenu?.openedMenus.includes(props.path);\n});\n\nconst collapse = computed(() => {\n  return rootMenu.props.collapse;\n});\n\nconst isFirstLevel = computed(() => {\n  return props.level === 1;\n});\n\nconst getCollapseShowTitle = computed(() => {\n  return (\n    rootMenu.props.collapseShowTitle && isFirstLevel.value && collapse.value\n  );\n});\n\nconst mode = computed(() => {\n  return rootMenu?.props.mode;\n});\n\nconst showArrowIcon = computed(() => {\n  return mode.value === 'horizontal' || !(isFirstLevel.value && collapse.value);\n});\n\nconst hiddenTitle = computed(() => {\n  return (\n    mode.value === 'vertical' &&\n    isFirstLevel.value &&\n    collapse.value &&\n    !getCollapseShowTitle.value\n  );\n});\n\nconst iconComp = computed(() => {\n  return (mode.value === 'horizontal' && !isFirstLevel.value) ||\n    (mode.value === 'vertical' && collapse.value)\n    ? ChevronRight\n    : ChevronDown;\n});\n\nconst iconArrowStyle = computed(() => {\n  return opened.value ? { transform: `rotate(180deg)` } : {};\n});\n</script>\n<template>\n  <div\n    :class=\"[\n      b(),\n      is('collapse-show-title', getCollapseShowTitle),\n      is('more', isMenuMore),\n    ]\"\n  >\n    <slot></slot>\n\n    <VbenIcon\n      v-if=\"!isMenuMore\"\n      :class=\"nsMenu.e('icon')\"\n      :icon=\"icon\"\n      fallback\n    />\n\n    <div v-if=\"!hiddenTitle\" :class=\"[e('title')]\">\n      <slot name=\"title\"></slot>\n    </div>\n\n    <component\n      :is=\"iconComp\"\n      v-if=\"!isMenuMore\"\n      v-show=\"showArrowIcon\"\n      :class=\"[e('icon-arrow')]\"\n      :style=\"iconArrowStyle\"\n      class=\"size-4\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/components/sub-menu.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { HoverCardContentProps } from '@vben-core/shadcn-ui';\n\nimport type { MenuItemRegistered, MenuProvider, SubMenuProps } from '../types';\n\nimport { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue';\n\nimport { useNamespace } from '@vben-core/composables';\nimport { VbenHoverCard } from '@vben-core/shadcn-ui';\n\nimport {\n  createSubMenuContext,\n  useMenu,\n  useMenuContext,\n  useMenuStyle,\n  useSubMenuContext,\n} from '../hooks';\nimport CollapseTransition from './collapse-transition.vue';\nimport SubMenuContent from './sub-menu-content.vue';\n\ninterface Props extends SubMenuProps {\n  isSubMenuMore?: boolean;\n}\n\ndefineOptions({ name: 'SubMenu' });\n\nconst props = withDefaults(defineProps<Props>(), {\n  disabled: false,\n  isSubMenuMore: false,\n});\n\nconst { parentMenu, parentPaths } = useMenu();\nconst { b, is } = useNamespace('sub-menu');\nconst nsMenu = useNamespace('menu');\nconst rootMenu = useMenuContext();\nconst subMenu = useSubMenuContext();\nconst subMenuStyle = useMenuStyle(subMenu);\n\nconst mouseInChild = ref(false);\n\nconst items = ref<MenuProvider['items']>({});\nconst subMenus = ref<MenuProvider['subMenus']>({});\nconst timer = ref<null | ReturnType<typeof setTimeout>>(null);\n\ncreateSubMenuContext({\n  addSubMenu,\n  handleMouseleave,\n  level: (subMenu?.level ?? 0) + 1,\n  mouseInChild,\n  removeSubMenu,\n});\n\nconst opened = computed(() => {\n  return rootMenu?.openedMenus.includes(props.path);\n});\nconst isTopLevelMenuSubmenu = computed(\n  () => parentMenu.value?.type.name === 'Menu',\n);\nconst mode = computed(() => rootMenu?.props.mode ?? 'vertical');\nconst rounded = computed(() => rootMenu?.props.rounded);\nconst currentLevel = computed(() => subMenu?.level ?? 0);\nconst isFirstLevel = computed(() => {\n  return currentLevel.value === 1;\n});\n\nconst contentProps = computed((): HoverCardContentProps => {\n  const isHorizontal = mode.value === 'horizontal';\n  const side = isHorizontal && isFirstLevel.value ? 'bottom' : 'right';\n  return {\n    collisionPadding: { top: 20 },\n    side,\n    sideOffset: isHorizontal ? 5 : 10,\n  };\n});\n\nconst active = computed(() => {\n  let isActive = false;\n\n  Object.values(items.value).forEach((item) => {\n    if (item.active) {\n      isActive = true;\n    }\n  });\n\n  Object.values(subMenus.value).forEach((subItem) => {\n    if (subItem.active) {\n      isActive = true;\n    }\n  });\n  return isActive;\n});\n\nfunction addSubMenu(subMenu: MenuItemRegistered) {\n  subMenus.value[subMenu.path] = subMenu;\n}\n\nfunction removeSubMenu(subMenu: MenuItemRegistered) {\n  Reflect.deleteProperty(subMenus.value, subMenu.path);\n}\n\n/**\n * 点击submenu展开/关闭\n */\nfunction handleClick() {\n  const mode = rootMenu?.props.mode;\n  if (\n    // 当前菜单禁用时，不展开\n    props.disabled ||\n    (rootMenu?.props.collapse && mode === 'vertical') ||\n    // 水平模式下不展开\n    mode === 'horizontal'\n  ) {\n    return;\n  }\n\n  rootMenu?.handleSubMenuClick({\n    active: active.value,\n    parentPaths: parentPaths.value,\n    path: props.path,\n  });\n}\n\nfunction handleMouseenter(event: FocusEvent | MouseEvent, showTimeout = 300) {\n  if (event.type === 'focus') {\n    return;\n  }\n\n  if (\n    (!rootMenu?.props.collapse && rootMenu?.props.mode === 'vertical') ||\n    props.disabled\n  ) {\n    if (subMenu) {\n      subMenu.mouseInChild.value = true;\n    }\n    return;\n  }\n  if (subMenu) {\n    subMenu.mouseInChild.value = true;\n  }\n\n  timer.value && window.clearTimeout(timer.value);\n  timer.value = setTimeout(() => {\n    rootMenu?.openMenu(props.path, parentPaths.value);\n  }, showTimeout);\n  parentMenu.value?.vnode.el?.dispatchEvent(new MouseEvent('mouseenter'));\n}\n\nfunction handleMouseleave(deepDispatch = false) {\n  if (\n    !rootMenu?.props.collapse &&\n    rootMenu?.props.mode === 'vertical' &&\n    subMenu\n  ) {\n    subMenu.mouseInChild.value = false;\n    return;\n  }\n\n  timer.value && window.clearTimeout(timer.value);\n\n  if (subMenu) {\n    subMenu.mouseInChild.value = false;\n  }\n  timer.value = setTimeout(() => {\n    !mouseInChild.value && rootMenu?.closeMenu(props.path, parentPaths.value);\n  }, 300);\n\n  if (deepDispatch) {\n    subMenu?.handleMouseleave?.(true);\n  }\n}\n\nconst menuIcon = computed(() =>\n  active.value ? props.activeIcon || props.icon : props.icon,\n);\n\nconst item = reactive({\n  active,\n  parentPaths,\n  path: props.path,\n});\n\nonMounted(() => {\n  subMenu?.addSubMenu?.(item);\n  rootMenu?.addSubMenu?.(item);\n});\n\nonBeforeUnmount(() => {\n  subMenu?.removeSubMenu?.(item);\n  rootMenu?.removeSubMenu?.(item);\n});\n</script>\n<template>\n  <li\n    :class=\"[\n      b(),\n      is('opened', opened),\n      is('active', active),\n      is('disabled', disabled),\n    ]\"\n    @focus=\"handleMouseenter\"\n    @mouseenter=\"handleMouseenter\"\n    @mouseleave=\"() => handleMouseleave()\"\n  >\n    <template v-if=\"rootMenu.isMenuPopup\">\n      <VbenHoverCard\n        :content-class=\"[\n          rootMenu.theme,\n          nsMenu.e('popup-container'),\n          is(rootMenu.theme, true),\n          opened ? '' : 'hidden',\n          'overflow-auto',\n          'max-h-[calc(var(--reka-hover-card-content-available-height)-20px)]',\n          mode === 'horizontal' ? 'is-horizontal' : '',\n        ]\"\n        :content-props=\"contentProps\"\n        :open=\"true\"\n        :open-delay=\"0\"\n      >\n        <template #trigger>\n          <SubMenuContent\n            :class=\"is('active', active)\"\n            :icon=\"menuIcon\"\n            :is-menu-more=\"isSubMenuMore\"\n            :is-top-level-menu-submenu=\"isTopLevelMenuSubmenu\"\n            :level=\"currentLevel\"\n            :path=\"path\"\n            @click.stop=\"handleClick\"\n          >\n            <template #title>\n              <slot name=\"title\"></slot>\n            </template>\n          </SubMenuContent>\n        </template>\n        <div\n          :class=\"[nsMenu.is(mode, true), nsMenu.e('popup')]\"\n          @focus=\"(e) => handleMouseenter(e, 100)\"\n          @mouseenter=\"(e) => handleMouseenter(e, 100)\"\n          @mouseleave=\"() => handleMouseleave(true)\"\n        >\n          <ul\n            :class=\"[nsMenu.b(), is('rounded', rounded)]\"\n            :style=\"subMenuStyle\"\n          >\n            <slot></slot>\n          </ul>\n        </div>\n      </VbenHoverCard>\n    </template>\n\n    <template v-else>\n      <SubMenuContent\n        :class=\"is('active', active)\"\n        :icon=\"menuIcon\"\n        :is-menu-more=\"isSubMenuMore\"\n        :is-top-level-menu-submenu=\"isTopLevelMenuSubmenu\"\n        :level=\"currentLevel\"\n        :path=\"path\"\n        @click.stop=\"handleClick\"\n      >\n        <slot name=\"content\"></slot>\n        <template #title>\n          <slot name=\"title\"></slot>\n        </template>\n      </SubMenuContent>\n      <CollapseTransition>\n        <ul\n          v-show=\"opened\"\n          :class=\"[nsMenu.b(), is('rounded', rounded)]\"\n          :style=\"subMenuStyle\"\n        >\n          <slot></slot>\n        </ul>\n      </CollapseTransition>\n    </template>\n  </li>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/hooks/index.ts",
    "content": "export * from './use-menu';\nexport * from './use-menu-context';\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/hooks/use-menu-context.ts",
    "content": "import type { MenuProvider, SubMenuProvider } from '../types';\n\nimport { getCurrentInstance, inject, provide } from 'vue';\n\nimport { findComponentUpward } from '../utils';\n\nconst menuContextKey = Symbol('menuContext');\n\n/**\n * @zh_CN Provide menu context\n */\nfunction createMenuContext(injectMenuData: MenuProvider) {\n  provide(menuContextKey, injectMenuData);\n}\n\n/**\n * @zh_CN Provide menu context\n */\nfunction createSubMenuContext(injectSubMenuData: SubMenuProvider) {\n  const instance = getCurrentInstance();\n\n  provide(`subMenu:${instance?.uid}`, injectSubMenuData);\n}\n\n/**\n * @zh_CN Inject menu context\n */\nfunction useMenuContext() {\n  const instance = getCurrentInstance();\n  if (!instance) {\n    throw new Error('instance is required');\n  }\n  const rootMenu = inject(menuContextKey) as MenuProvider;\n  return rootMenu;\n}\n\n/**\n * @zh_CN Inject menu context\n */\nfunction useSubMenuContext() {\n  const instance = getCurrentInstance();\n  if (!instance) {\n    throw new Error('instance is required');\n  }\n  const parentMenu = findComponentUpward(instance, ['Menu', 'SubMenu']);\n  const subMenu = inject(`subMenu:${parentMenu?.uid}`) as SubMenuProvider;\n  return subMenu;\n}\n\nexport {\n  createMenuContext,\n  createSubMenuContext,\n  useMenuContext,\n  useSubMenuContext,\n};\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/hooks/use-menu-scroll.ts",
    "content": "import type { Ref } from 'vue';\n\nimport { watch } from 'vue';\n\nimport { useDebounceFn } from '@vueuse/core';\n\ninterface UseMenuScrollOptions {\n  delay?: number;\n  enable?: boolean | Ref<boolean>;\n}\n\nexport function useMenuScroll(\n  activePath: Ref<string | undefined>,\n  options: UseMenuScrollOptions = {},\n) {\n  const { enable = true, delay = 320 } = options;\n\n  function scrollToActiveItem() {\n    const isEnabled = typeof enable === 'boolean' ? enable : enable.value;\n    if (!isEnabled) return;\n\n    const activeElement = document.querySelector(\n      `aside li[role=menuitem].is-active`,\n    );\n    if (activeElement) {\n      activeElement.scrollIntoView({\n        behavior: 'smooth',\n        block: 'center',\n        inline: 'center',\n      });\n    }\n  }\n\n  const debouncedScroll = useDebounceFn(scrollToActiveItem, delay);\n\n  watch(activePath, () => {\n    const isEnabled = typeof enable === 'boolean' ? enable : enable.value;\n    if (!isEnabled) return;\n\n    debouncedScroll();\n  });\n\n  return {\n    scrollToActiveItem,\n  };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/hooks/use-menu.ts",
    "content": "import type { SubMenuProvider } from '../types';\n\nimport { computed, getCurrentInstance } from 'vue';\n\nimport { findComponentUpward } from '../utils';\n\nfunction useMenu() {\n  const instance = getCurrentInstance();\n  if (!instance) {\n    throw new Error('instance is required');\n  }\n\n  /**\n   * @zh_CN 获取所有父级菜单链路\n   */\n  const parentPaths = computed(() => {\n    let parent = instance.parent;\n    const paths: string[] = [instance.props.path as string];\n    while (parent?.type.name !== 'Menu') {\n      if (parent?.props.path) {\n        paths.unshift(parent.props.path as string);\n      }\n      parent = parent?.parent ?? null;\n    }\n\n    return paths;\n  });\n\n  const parentMenu = computed(() => {\n    return findComponentUpward(instance, ['Menu', 'SubMenu']);\n  });\n\n  return {\n    parentMenu,\n    parentPaths,\n  };\n}\n\nfunction useMenuStyle(menu?: SubMenuProvider) {\n  const subMenuStyle = computed(() => {\n    return {\n      '--menu-level': menu ? (menu?.level ?? 0 + 1) : 0,\n    };\n  });\n  return subMenuStyle;\n}\n\nexport { useMenu, useMenuStyle };\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/index.ts",
    "content": "export { default as MenuBadge } from './components/menu-badge.vue';\nexport * from './components/normal-menu';\nexport { default as Menu } from './menu.vue';\nexport type * from './types';\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/menu.vue",
    "content": "<script setup lang=\"ts\">\nimport type { MenuRecordRaw } from '@vben-core/typings';\n\nimport type { MenuProps } from './types';\n\nimport { useForwardProps } from '@vben-core/composables';\n\nimport { Menu } from './components';\nimport SubMenu from './sub-menu.vue';\n\ninterface Props extends MenuProps {\n  menus: MenuRecordRaw[];\n}\n\ndefineOptions({\n  name: 'MenuView',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  collapse: false,\n});\n\nconst forward = useForwardProps(props);\n</script>\n\n<template>\n  <Menu v-bind=\"forward\">\n    <template v-for=\"menu in menus\" :key=\"menu.path\">\n      <SubMenu :menu=\"menu\" />\n    </template>\n  </Menu>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/sub-menu.vue",
    "content": "<script setup lang=\"ts\">\nimport type { MenuRecordRaw } from '@vben-core/typings';\n\nimport { computed } from 'vue';\n\nimport { MenuBadge, MenuItem, SubMenu as SubMenuComp } from './components';\nimport SubMenu from './sub-menu.vue';\n\ninterface Props {\n  /**\n   * 菜单项\n   */\n  menu: MenuRecordRaw;\n}\n\ndefineOptions({\n  name: 'SubMenuUi',\n});\n\nconst props = withDefaults(defineProps<Props>(), {});\n\n/**\n * 判断是否有子节点，动态渲染 menu-item/sub-menu-item\n */\nconst hasChildren = computed(() => {\n  const { menu } = props;\n  return (\n    Reflect.has(menu, 'children') && !!menu.children && menu.children.length > 0\n  );\n});\n</script>\n\n<template>\n  <MenuItem\n    v-if=\"!hasChildren\"\n    :key=\"menu.path\"\n    :active-icon=\"menu.activeIcon\"\n    :badge=\"menu.badge\"\n    :badge-type=\"menu.badgeType\"\n    :badge-variants=\"menu.badgeVariants\"\n    :icon=\"menu.icon\"\n    :path=\"menu.path\"\n    :query=\"menu.query\"\n  >\n    <template #title>\n      <span>{{ menu.name }}</span>\n    </template>\n  </MenuItem>\n  <SubMenuComp\n    v-else\n    :key=\"`${menu.path}_sub`\"\n    :active-icon=\"menu.activeIcon\"\n    :icon=\"menu.icon\"\n    :path=\"menu.path\"\n  >\n    <template #content>\n      <MenuBadge\n        :badge=\"menu.badge\"\n        :badge-type=\"menu.badgeType\"\n        :badge-variants=\"menu.badgeVariants\"\n        class=\"right-6\"\n      />\n    </template>\n    <template #title>\n      <span>{{ menu.name }}</span>\n    </template>\n    <template v-for=\"childItem in menu.children || []\" :key=\"childItem.path\">\n      <SubMenu :menu=\"childItem\" />\n    </template>\n  </SubMenuComp>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/types.ts",
    "content": "import type { Component, Ref } from 'vue';\n\nimport type {\n  MenuRecordBadgeRaw,\n  Recordable,\n  ThemeModeType,\n} from '@vben-core/typings';\n\ninterface MenuProps {\n  /**\n   * @zh_CN 是否开启手风琴模式\n   * @default true\n   */\n  accordion?: boolean;\n  /**\n   * @zh_CN 菜单是否折叠\n   * @default false\n   */\n  collapse?: boolean;\n\n  /**\n   * @zh_CN 菜单折叠时是否显示菜单名称\n   * @default false\n   */\n  collapseShowTitle?: boolean;\n\n  /**\n   * @zh_CN 默认激活的菜单\n   */\n  defaultActive?: string;\n\n  /**\n   * @zh_CN 默认展开的菜单\n   */\n  defaultOpeneds?: string[];\n\n  /**\n   * @zh_CN 菜单模式\n   * @default vertical\n   */\n  mode?: 'horizontal' | 'vertical';\n\n  /**\n   * @zh_CN 是否圆润风格\n   * @default true\n   */\n  rounded?: boolean;\n\n  /**\n   * @zh_CN 是否自动滚动到激活的菜单项\n   * @default false\n   */\n  scrollToActive?: boolean;\n\n  /**\n   * @zh_CN 菜单主题\n   * @default dark\n   */\n  theme?: ThemeModeType;\n}\n\ninterface SubMenuProps extends MenuRecordBadgeRaw {\n  /**\n   * @zh_CN 激活图标\n   */\n  activeIcon?: string;\n  /**\n   * @zh_CN 是否禁用\n   */\n  disabled?: boolean;\n  /**\n   * @zh_CN 图标\n   */\n  icon?: Component | string;\n  /**\n   * @zh_CN submenu 名称\n   */\n  path: string;\n}\n\ninterface MenuItemProps extends MenuRecordBadgeRaw {\n  /**\n   * @zh_CN 图标\n   */\n  activeIcon?: string;\n  /**\n   * @zh_CN 是否禁用\n   */\n  disabled?: boolean;\n  /**\n   * @zh_CN 图标\n   */\n  icon?: Component | string;\n  /**\n   * @zh_CN menuitem 名称\n   */\n  path: string;\n  /**\n   * @zh_CN 菜单所携带的参数\n   */\n  query?: Recordable<any>;\n}\n\ninterface MenuItemRegistered {\n  active: boolean;\n  parentPaths: string[];\n  path: string;\n  query?: Recordable<any>;\n}\n\ninterface MenuItemClicked {\n  parentPaths: string[];\n  path: string;\n}\n\ninterface MenuProvider {\n  activePath?: string;\n  addMenuItem: (item: MenuItemRegistered) => void;\n\n  addSubMenu: (item: MenuItemRegistered) => void;\n  closeMenu: (path: string, parentLinks: string[]) => void;\n  handleMenuItemClick: (item: MenuItemClicked) => void;\n  handleSubMenuClick: (subMenu: MenuItemRegistered) => void;\n  isMenuPopup: boolean;\n  items: Record<string, MenuItemRegistered>;\n\n  openedMenus: string[];\n  openMenu: (path: string, parentLinks: string[]) => void;\n  props: MenuProps;\n  removeMenuItem: (item: MenuItemRegistered) => void;\n\n  removeSubMenu: (item: MenuItemRegistered) => void;\n\n  subMenus: Record<string, MenuItemRegistered>;\n  theme: string;\n}\n\ninterface SubMenuProvider {\n  addSubMenu: (item: MenuItemRegistered) => void;\n  handleMouseleave?: (deepDispatch: boolean) => void;\n  level: number;\n  mouseInChild: Ref<boolean>;\n  removeSubMenu: (item: MenuItemRegistered) => void;\n}\n\nexport type {\n  MenuItemClicked,\n  MenuItemProps,\n  MenuItemRegistered,\n  MenuProps,\n  MenuProvider,\n  SubMenuProps,\n  SubMenuProvider,\n};\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/src/utils/index.ts",
    "content": "import type {\n  ComponentInternalInstance,\n  VNode,\n  VNodeChild,\n  VNodeNormalizedChildren,\n} from 'vue';\n\nimport { isVNode } from 'vue';\n\ntype VNodeChildAtom = Exclude<VNodeChild, Array<any>>;\ntype RawSlots = Exclude<VNodeNormalizedChildren, Array<any> | null | string>;\n\ntype FlattenVNodes = Array<RawSlots | VNodeChildAtom>;\n\n/**\n * @zh_CN Find the parent component upward\n * @param instance\n * @param parentNames\n */\nfunction findComponentUpward(\n  instance: ComponentInternalInstance,\n  parentNames: string[],\n) {\n  let parent = instance.parent;\n  while (parent && !parentNames.includes(parent?.type?.name ?? '')) {\n    parent = parent.parent;\n  }\n  return parent;\n}\n\nconst flattedChildren = (\n  children: FlattenVNodes | VNode | VNodeNormalizedChildren,\n): FlattenVNodes => {\n  const vNodes = Array.isArray(children) ? children : [children];\n  const result: FlattenVNodes = [];\n\n  vNodes.forEach((child) => {\n    if (Array.isArray(child)) {\n      result.push(...flattedChildren(child));\n    } else if (isVNode(child) && Array.isArray(child.children)) {\n      result.push(...flattedChildren(child.children));\n    } else {\n      result.push(child);\n      if (isVNode(child) && child.component?.subTree) {\n        result.push(...flattedChildren(child.component.subTree));\n      }\n    }\n  });\n  return result;\n};\n\nexport { findComponentUpward, flattedChildren };\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/menu-ui/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\nimport Vue from 'unplugin-vue/rolldown';\n\nexport default defineConfig({\n  clean: true,\n  deps: {\n    skipNodeModulesBundle: true,\n  },\n  dts: {\n    vue: true,\n  },\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n    js: '.mjs',\n  }),\n  platform: 'neutral',\n  plugins: [Vue({ isProduction: true })],\n  unbundle: true,\n});\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/package.json",
    "content": "{\n  \"name\": \"@vben-core/popup-ui\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@vben-core/uikit/popup-ui\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm exec tsdown\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"production\": \"./src/index.ts\",\n      \"default\": \"./dist/index.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"default\": \"./dist/index.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/composables\": \"workspace:*\",\n    \"@vben-core/icons\": \"workspace:*\",\n    \"@vben-core/shadcn-ui\": \"workspace:*\",\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben-core/typings\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"vue\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/alert/AlertBuilder.ts",
    "content": "import type { Component, VNode } from 'vue';\n\nimport type { Recordable } from '@vben-core/typings';\n\nimport type { AlertProps, BeforeCloseScope, PromptProps } from './alert';\n\nimport { h, nextTick, ref, render } from 'vue';\n\nimport { useSimpleLocale } from '@vben-core/composables';\nimport { Input, VbenRenderContent } from '@vben-core/shadcn-ui';\nimport { isFunction, isString } from '@vben-core/shared/utils';\n\nimport Alert from './alert.vue';\n\nconst alerts = ref<Array<{ container: HTMLElement; instance: Component }>>([]);\n\nconst { $t } = useSimpleLocale();\n\nexport function vbenAlert(options: AlertProps): Promise<void>;\nexport function vbenAlert(\n  message: string,\n  options?: Partial<AlertProps>,\n): Promise<void>;\nexport function vbenAlert(\n  message: string,\n  title?: string,\n  options?: Partial<AlertProps>,\n): Promise<void>;\n\nexport function vbenAlert(\n  arg0: AlertProps | string,\n  arg1?: Partial<AlertProps> | string,\n  arg2?: Partial<AlertProps>,\n): Promise<void> {\n  return new Promise((resolve, reject) => {\n    const options: AlertProps = isString(arg0)\n      ? {\n          content: arg0,\n        }\n      : { ...arg0 };\n    if (arg1) {\n      if (isString(arg1)) {\n        options.title = arg1;\n      } else if (!isString(arg1)) {\n        // 如果第二个参数是对象，则合并到选项中\n        Object.assign(options, arg1);\n      }\n    }\n\n    if (arg2 && !isString(arg2)) {\n      Object.assign(options, arg2);\n    }\n    // 创建容器元素\n    const container = document.createElement('div');\n    document.body.append(container);\n\n    // 创建一个引用，用于在回调中访问实例\n    const alertRef = { container, instance: null as any };\n\n    const props: AlertProps & Recordable<any> = {\n      onClosed: (isConfirm: boolean) => {\n        // 移除组件实例以及创建的所有dom（恢复页面到打开前的状态）\n        // 从alerts数组中移除该实例\n        alerts.value = alerts.value.filter((item) => item !== alertRef);\n\n        // 从DOM中移除容器\n        render(null, container);\n        if (container.parentNode) {\n          container.remove();\n        }\n\n        // 解析 Promise，传递用户操作结果\n        if (isConfirm) {\n          resolve();\n        } else {\n          reject(new Error('dialog cancelled'));\n        }\n      },\n      ...options,\n      open: true,\n      title: options.title ?? $t.value('prompt'),\n    };\n\n    // 创建Alert组件的VNode\n    const vnode = h(Alert, props);\n\n    // 渲染组件到容器\n    render(vnode, container);\n\n    // 保存组件实例引用\n    alertRef.instance = vnode.component?.proxy as Component;\n\n    // 将实例和容器添加到alerts数组中\n    alerts.value.push(alertRef);\n  });\n}\n\nexport function vbenConfirm(options: AlertProps): Promise<void>;\nexport function vbenConfirm(\n  message: string,\n  options?: Partial<AlertProps>,\n): Promise<void>;\nexport function vbenConfirm(\n  message: string,\n  title?: string,\n  options?: Partial<AlertProps>,\n): Promise<void>;\n\nexport function vbenConfirm(\n  arg0: AlertProps | string,\n  arg1?: Partial<AlertProps> | string,\n  arg2?: Partial<AlertProps>,\n): Promise<void> {\n  const defaultProps: Partial<AlertProps> = {\n    showCancel: true,\n  };\n  if (!arg1) {\n    return isString(arg0)\n      ? vbenAlert(arg0, defaultProps)\n      : vbenAlert({ ...defaultProps, ...arg0 });\n  } else if (!arg2) {\n    return isString(arg1)\n      ? vbenAlert(arg0 as string, arg1, defaultProps)\n      : vbenAlert(arg0 as string, { ...defaultProps, ...arg1 });\n  }\n  return vbenAlert(arg0 as string, arg1 as string, {\n    ...defaultProps,\n    ...arg2,\n  });\n}\n\nexport async function vbenPrompt<T = any>(\n  options: PromptProps<T>,\n): Promise<T | undefined> {\n  const {\n    component: _component,\n    componentProps: _componentProps,\n    componentSlots,\n    content,\n    defaultValue,\n    modelPropName: _modelPropName,\n    ...delegated\n  } = options;\n\n  const modelValue = ref<T | undefined>(defaultValue);\n  const inputComponentRef = ref<null | VNode>(null);\n  const staticContents: Component[] = [\n    h(VbenRenderContent, { content, renderBr: true }),\n  ];\n\n  const modelPropName = _modelPropName || 'modelValue';\n  const componentProps = { ..._componentProps };\n\n  // 每次渲染时都会重新计算的内容函数\n  const contentRenderer = () => {\n    const currentProps = {\n      ...componentProps,\n      [modelPropName]: modelValue.value,\n      [`onUpdate:${modelPropName}`]: (val: T) => {\n        modelValue.value = val;\n      },\n    };\n\n    // 设置当前值\n\n    // 设置更新处理函数\n\n    // 创建输入组件\n    inputComponentRef.value = h(\n      _component || Input,\n      currentProps,\n      componentSlots,\n    );\n\n    // 返回包含静态内容和输入组件的数组\n    return h(\n      'div',\n      { class: 'flex flex-col gap-2' },\n      { default: () => [...staticContents, inputComponentRef.value] },\n    );\n  };\n\n  const props: AlertProps & Recordable<any> = {\n    ...delegated,\n    async beforeClose(scope: BeforeCloseScope) {\n      if (delegated.beforeClose) {\n        return await delegated.beforeClose({\n          ...scope,\n          value: modelValue.value,\n        });\n      }\n    },\n    // 使用函数形式，每次渲染都会重新计算内容\n    content: contentRenderer,\n    contentMasking: true,\n    async onOpened() {\n      await nextTick();\n      const componentRef: null | VNode = inputComponentRef.value;\n      if (componentRef) {\n        if (\n          componentRef.component?.exposed &&\n          isFunction(componentRef.component.exposed.focus)\n        ) {\n          componentRef.component.exposed.focus();\n        } else {\n          if (componentRef.el) {\n            if (\n              isFunction(componentRef.el.focus) &&\n              ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes(\n                componentRef.el.tagName,\n              )\n            ) {\n              componentRef.el.focus();\n            } else if (isFunction(componentRef.el.querySelector)) {\n              const focusableElement = componentRef.el.querySelector(\n                'input, select, textarea, button',\n              );\n              if (focusableElement && isFunction(focusableElement.focus)) {\n                focusableElement.focus();\n              }\n            } else if (\n              componentRef.el.nextElementSibling &&\n              isFunction(componentRef.el.nextElementSibling.focus)\n            ) {\n              componentRef.el.nextElementSibling.focus();\n            }\n          }\n        }\n      }\n    },\n  };\n\n  await vbenConfirm(props);\n  return modelValue.value;\n}\n\nexport function clearAllAlerts() {\n  alerts.value.forEach((alert) => {\n    // 从DOM中移除容器\n    render(null, alert.container);\n    if (alert.container.parentNode) {\n      alert.container.remove();\n    }\n  });\n  alerts.value = [];\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/alert/alert.ts",
    "content": "import type { Component, VNode, VNodeArrayChildren } from 'vue';\n\nimport type { Recordable } from '@vben-core/typings';\n\nimport { createContext } from '@vben-core/shadcn-ui';\n\nexport type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';\n\nexport type BeforeCloseScope = {\n  isConfirm: boolean;\n};\n\nexport type AlertProps = {\n  /** 关闭前的回调，如果返回false，则终止关闭 */\n  beforeClose?: (\n    scope: BeforeCloseScope,\n  ) => boolean | Promise<boolean | undefined> | undefined;\n  /** 边框 */\n  bordered?: boolean;\n  /**\n   * 按钮对齐方式\n   * @default 'end'\n   */\n  buttonAlign?: 'center' | 'end' | 'start';\n  /** 取消按钮的标题 */\n  cancelText?: string;\n  /** 是否居中显示 */\n  centered?: boolean;\n  /** 确认按钮的标题 */\n  confirmText?: string;\n  /** 弹窗容器的额外样式 */\n  containerClass?: string;\n  /** 弹窗提示内容 */\n  content: Component | string;\n  /** 弹窗内容的额外样式 */\n  contentClass?: string;\n  /** 执行beforeClose回调期间，在内容区域显示一个loading遮罩*/\n  contentMasking?: boolean;\n  /** 弹窗底部内容（与按钮在同一个容器中） */\n  footer?: Component | string;\n  /** 弹窗的图标（在标题的前面） */\n  icon?: Component | IconType;\n  /**\n   * 弹窗遮罩模糊效果\n   */\n  overlayBlur?: number;\n  /** 是否显示取消按钮 */\n  showCancel?: boolean;\n  /** 弹窗标题 */\n  title?: string;\n};\n\n/** Prompt属性 */\nexport type PromptProps<T = any> = {\n  /** 关闭前的回调，如果返回false，则终止关闭 */\n  beforeClose?: (scope: {\n    isConfirm: boolean;\n    value: T | undefined;\n  }) => boolean | Promise<boolean | undefined> | undefined;\n  /** 用于接受用户输入的组件 */\n  component?: Component;\n  /** 输入组件的属性 */\n  componentProps?: Recordable<any>;\n  /** 输入组件的插槽 */\n  componentSlots?:\n    | (() => any)\n    | Recordable<unknown>\n    | VNode\n    | VNodeArrayChildren;\n  /** 默认值 */\n  defaultValue?: T;\n  /** 输入组件的值属性名 */\n  modelPropName?: string;\n} & Omit<AlertProps, 'beforeClose'>;\n\n/**\n * Alert上下文\n */\nexport type AlertContext = {\n  /** 执行取消操作 */\n  doCancel: () => void;\n  /** 执行确认操作 */\n  doConfirm: () => void;\n};\n\nexport const [injectAlertContext, provideAlertContext] =\n  createContext<AlertContext>('VbenAlertContext');\n\n/**\n * 获取Alert上下文\n * @returns AlertContext\n */\nexport function useAlertContext() {\n  const context = injectAlertContext();\n  if (!context) {\n    throw new Error('useAlertContext must be used within an AlertProvider');\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/alert/alert.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Component } from 'vue';\n\nimport type { AlertProps } from './alert';\n\nimport { computed, h, nextTick, ref } from 'vue';\n\nimport { useSimpleLocale } from '@vben-core/composables';\nimport {\n  CircleAlert,\n  CircleCheckBig,\n  CircleHelp,\n  CircleX,\n  Info,\n  X,\n} from '@vben-core/icons';\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogTitle,\n  VbenButton,\n  VbenLoading,\n  VbenRenderContent,\n} from '@vben-core/shadcn-ui';\nimport { globalShareState } from '@vben-core/shared/global-state';\nimport { cn } from '@vben-core/shared/utils';\n\nimport { provideAlertContext } from './alert';\n\nconst props = withDefaults(defineProps<AlertProps>(), {\n  bordered: true,\n  buttonAlign: 'end',\n  centered: true,\n});\nconst emits = defineEmits(['closed', 'confirm', 'opened']);\nconst open = defineModel<boolean>('open', { default: false });\nconst { $t } = useSimpleLocale();\nconst components = globalShareState.getComponents();\nconst isConfirm = ref(false);\n\nfunction onAlertClosed() {\n  emits('closed', isConfirm.value);\n  isConfirm.value = false;\n}\n\nfunction onEscapeKeyDown() {\n  isConfirm.value = false;\n}\n\nconst getIconRender = computed(() => {\n  let iconRender: Component | null = null;\n  if (props.icon) {\n    if (typeof props.icon === 'string') {\n      switch (props.icon) {\n        case 'error': {\n          iconRender = h(CircleX, {\n            style: { color: 'hsl(var(--destructive))' },\n          });\n          break;\n        }\n        case 'info': {\n          iconRender = h(Info, { style: { color: 'hsl(var(--info))' } });\n          break;\n        }\n        case 'question': {\n          iconRender = CircleHelp;\n          break;\n        }\n        case 'success': {\n          iconRender = h(CircleCheckBig, {\n            style: { color: 'hsl(var(--success))' },\n          });\n          break;\n        }\n        case 'warning': {\n          iconRender = h(CircleAlert, {\n            style: { color: 'hsl(var(--warning))' },\n          });\n          break;\n        }\n        default: {\n          iconRender = null;\n          break;\n        }\n      }\n    }\n  } else {\n    iconRender = props.icon ?? null;\n  }\n  return iconRender;\n});\n\nfunction doCancel() {\n  handleCancel();\n  handleOpenChange(false);\n}\n\nfunction doConfirm() {\n  handleConfirm();\n  handleOpenChange(false);\n}\n\nprovideAlertContext({\n  doCancel,\n  doConfirm,\n});\n\nfunction handleConfirm() {\n  isConfirm.value = true;\n  emits('confirm');\n}\n\nfunction handleCancel() {\n  isConfirm.value = false;\n}\n\nconst loading = ref(false);\nasync function handleOpenChange(val: boolean) {\n  await nextTick(); // 等待标记isConfirm状态\n  if (!val && props.beforeClose) {\n    loading.value = true;\n    try {\n      const res = await props.beforeClose({ isConfirm: isConfirm.value });\n      if (res !== false) {\n        open.value = false;\n      }\n    } finally {\n      loading.value = false;\n    }\n  } else {\n    open.value = val;\n  }\n}\n</script>\n<template>\n  <AlertDialog :open=\"open\" @update:open=\"handleOpenChange\">\n    <AlertDialogContent\n      :open=\"open\"\n      :centered=\"centered\"\n      :overlay-blur=\"overlayBlur\"\n      @opened=\"emits('opened')\"\n      @closed=\"onAlertClosed\"\n      @escape-key-down=\"onEscapeKeyDown\"\n      :class=\"\n        cn(\n          containerClass,\n          'inset-x-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:w-130 sm:max-w-[80%] sm:rounded-(--radius)',\n          {\n            'border border-border': bordered,\n            'shadow-3xl': !bordered,\n          },\n        )\n      \"\n    >\n      <div :class=\"cn('relative flex-1 overflow-y-auto p-3', contentClass)\">\n        <AlertDialogTitle v-if=\"title\">\n          <div class=\"flex items-center\">\n            <component :is=\"getIconRender\" class=\"mr-2\" />\n            <span class=\"flex-auto\">{{ $t(title) }}</span>\n            <AlertDialogCancel v-if=\"showCancel\" as-child>\n              <VbenButton\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"rounded-full\"\n                :disabled=\"loading\"\n                @click=\"handleCancel\"\n              >\n                <X class=\"size-4 text-muted-foreground\" />\n              </VbenButton>\n            </AlertDialogCancel>\n          </div>\n        </AlertDialogTitle>\n        <AlertDialogDescription>\n          <div class=\"m-4 min-h-7.5\">\n            <VbenRenderContent :content=\"content\" render-br />\n          </div>\n          <VbenLoading v-if=\"loading && contentMasking\" :spinning=\"loading\" />\n        </AlertDialogDescription>\n        <div\n          class=\"flex items-center justify-end gap-x-2\"\n          :class=\"`justify-${buttonAlign}`\"\n        >\n          <VbenRenderContent :content=\"footer\" />\n          <AlertDialogCancel v-if=\"showCancel\" as-child>\n            <component\n              :is=\"components.DefaultButton || VbenButton\"\n              :disabled=\"loading\"\n              variant=\"ghost\"\n              @click=\"handleCancel\"\n            >\n              {{ cancelText || $t('cancel') }}\n            </component>\n          </AlertDialogCancel>\n          <AlertDialogAction as-child>\n            <component\n              :is=\"components.PrimaryButton || VbenButton\"\n              :loading=\"loading\"\n              @click=\"handleConfirm\"\n            >\n              {{ confirmText || $t('confirm') }}\n            </component>\n          </AlertDialogAction>\n        </div>\n      </div>\n    </AlertDialogContent>\n  </AlertDialog>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/alert/index.ts",
    "content": "export type {\n  AlertProps,\n  BeforeCloseScope,\n  IconType,\n  PromptProps,\n} from './alert';\nexport { useAlertContext } from './alert';\nexport { default as Alert } from './alert.vue';\nexport {\n  vbenAlert as alert,\n  clearAllAlerts,\n  vbenConfirm as confirm,\n  vbenPrompt as prompt,\n} from './AlertBuilder';\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts",
    "content": "import type { DrawerState } from '../drawer';\n\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { DrawerApi } from '../drawer-api';\n\n// 模拟 Store 类\nvi.mock('@vben-core/shared/store', () => {\n  return {\n    isFunction: (fn: any) => typeof fn === 'function',\n    Store: class {\n      get state() {\n        return this._state;\n      }\n      private _state: DrawerState;\n      private subscribers: Array<(state: DrawerState) => void> = [];\n\n      constructor(initialState: DrawerState) {\n        this._state = initialState;\n      }\n\n      setState(fn: (prev: DrawerState) => DrawerState) {\n        this._state = fn(this._state);\n        this.subscribers.forEach((sub) => sub(this._state));\n      }\n\n      subscribe(fn: (state: DrawerState) => void) {\n        this.subscribers.push(fn);\n        return { unsubscribe: () => {} };\n      }\n    },\n  };\n});\n\ndescribe('drawerApi', () => {\n  let drawerApi: DrawerApi;\n  let drawerState: DrawerState;\n\n  beforeEach(() => {\n    drawerApi = new DrawerApi();\n    drawerState = drawerApi.store.state;\n  });\n\n  it('should initialize with default state', () => {\n    expect(drawerState.isOpen).toBe(false);\n    expect(drawerState.cancelText).toBe(undefined);\n    expect(drawerState.confirmText).toBe(undefined);\n  });\n\n  it('should open the drawer', () => {\n    drawerApi.open();\n    expect(drawerApi.store.state.isOpen).toBe(true);\n  });\n\n  it('should close the drawer if onBeforeClose allows it', () => {\n    drawerApi.close();\n    expect(drawerApi.store.state.isOpen).toBe(false);\n  });\n\n  it('should not close the drawer if onBeforeClose returns false', () => {\n    const onBeforeClose = vi.fn(() => false);\n    const drawerApiWithHook = new DrawerApi({ onBeforeClose });\n    drawerApiWithHook.open();\n    drawerApiWithHook.close();\n    expect(drawerApiWithHook.store.state.isOpen).toBe(true);\n    expect(onBeforeClose).toHaveBeenCalled();\n  });\n\n  it('should trigger onCancel and keep drawer open if onCancel is provided', () => {\n    const onCancel = vi.fn();\n    const drawerApiWithHook = new DrawerApi({ onCancel });\n    drawerApiWithHook.open();\n    drawerApiWithHook.onCancel();\n    expect(onCancel).toHaveBeenCalled();\n    expect(drawerApiWithHook.store.state.isOpen).toBe(true); // 关闭逻辑不在 onCancel 内\n  });\n\n  it('should update shared data correctly', () => {\n    const testData = { key: 'value' };\n    drawerApi.setData(testData);\n    expect(drawerApi.getData()).toEqual(testData);\n  });\n\n  it('should set state correctly using an object', () => {\n    drawerApi.setState({ title: 'New Title' });\n    expect(drawerApi.store.state.title).toBe('New Title');\n  });\n\n  it('should set state correctly using a function', () => {\n    drawerApi.setState((prev) => ({ ...prev, confirmText: 'Yes' }));\n    expect(drawerApi.store.state.confirmText).toBe('Yes');\n  });\n\n  it('should call onOpenChange when state changes', () => {\n    const onOpenChange = vi.fn();\n    const drawerApiWithHook = new DrawerApi({ onOpenChange });\n    drawerApiWithHook.open();\n    expect(onOpenChange).toHaveBeenCalledWith(true);\n  });\n\n  it('should call onClosed callback when provided', () => {\n    const onClosed = vi.fn();\n    const drawerApiWithHook = new DrawerApi({ onClosed });\n    drawerApiWithHook.onClosed();\n    expect(onClosed).toHaveBeenCalled();\n  });\n\n  it('should call onOpened callback when provided', () => {\n    const onOpened = vi.fn();\n    const drawerApiWithHook = new DrawerApi({ onOpened });\n    drawerApiWithHook.open();\n    drawerApiWithHook.onOpened();\n    expect(onOpened).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts",
    "content": "import type { DrawerApiOptions, DrawerState } from './drawer';\n\nimport { Store } from '@vben-core/shared/store';\nimport { bindMethods, isFunction } from '@vben-core/shared/utils';\n\nexport class DrawerApi {\n  // 共享数据\n  public sharedData: Record<'payload', any> = {\n    payload: {},\n  };\n  public store: Store<DrawerState>;\n\n  private api: Pick<\n    DrawerApiOptions,\n    | 'onBeforeClose'\n    | 'onCancel'\n    | 'onClosed'\n    | 'onConfirm'\n    | 'onOpenChange'\n    | 'onOpened'\n  >;\n\n  // private prevState!: DrawerState;\n  private state!: DrawerState;\n\n  constructor(options: DrawerApiOptions = {}) {\n    const {\n      connectedComponent: _,\n      onBeforeClose,\n      onCancel,\n      onClosed,\n      onConfirm,\n      onOpenChange,\n      onOpened,\n      ...storeState\n    } = options;\n\n    const defaultState: DrawerState = {\n      class: '',\n      closable: true,\n      closeIconPlacement: 'right',\n      closeOnClickModal: true,\n      closeOnPressEscape: true,\n      confirmLoading: false,\n      contentClass: '',\n      footer: true,\n      header: true,\n      isOpen: false,\n      loading: false,\n      modal: true,\n      openAutoFocus: false,\n      placement: 'right',\n      showCancelButton: true,\n      showConfirmButton: true,\n      submitting: false,\n      title: '',\n    };\n\n    this.store = new Store<DrawerState>({\n      ...defaultState,\n      ...storeState,\n    });\n\n    this.store.subscribe((state) => {\n      const prevIsOpen = this.state?.isOpen;\n      this.state = state;\n      if (state?.isOpen !== prevIsOpen) {\n        this.api.onOpenChange?.(!!state?.isOpen);\n      }\n    });\n    this.state = this.store.state;\n    this.api = {\n      onBeforeClose,\n      onCancel,\n      onClosed,\n      onConfirm,\n      onOpenChange,\n      onOpened,\n    };\n    bindMethods(this);\n  }\n\n  /**\n   * 关闭抽屉\n   * @description 关闭抽屉时会调用 onBeforeClose 钩子函数，如果 onBeforeClose 返回 false，则不关闭弹窗\n   */\n  async close() {\n    // 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗\n    // 如果 onBeforeClose 返回 false，则不关闭弹窗\n    const allowClose = (await this.api.onBeforeClose?.()) ?? true;\n    if (allowClose) {\n      this.store.setState((prev) => ({\n        ...prev,\n        isOpen: false,\n        submitting: false,\n      }));\n    }\n  }\n\n  getData<T extends object = Record<string, any>>() {\n    return (this.sharedData?.payload ?? {}) as T;\n  }\n\n  /**\n   * 锁定抽屉状态（用于提交过程中的等待状态）\n   * @description 锁定状态将禁用默认的取消按钮，使用spinner覆盖抽屉内容，隐藏关闭按钮，阻止手动关闭弹窗，将默认的提交按钮标记为loading状态\n   * @param isLocked 是否锁定\n   */\n  lock(isLocked: boolean = true) {\n    return this.setState({ submitting: isLocked });\n  }\n\n  /**\n   * 取消操作\n   */\n  onCancel() {\n    if (this.api.onCancel) {\n      this.api.onCancel?.();\n    } else {\n      this.close();\n    }\n  }\n\n  /**\n   * 弹窗关闭动画播放完毕后的回调\n   */\n  onClosed() {\n    if (!this.state.isOpen) {\n      this.api.onClosed?.();\n    }\n  }\n\n  /**\n   * 确认操作\n   */\n  onConfirm() {\n    this.api.onConfirm?.();\n  }\n\n  /**\n   * 弹窗打开动画播放完毕后的回调\n   */\n  onOpened() {\n    if (this.state.isOpen) {\n      this.api.onOpened?.();\n    }\n  }\n\n  open() {\n    this.store.setState((prev) => ({ ...prev, isOpen: true }));\n  }\n\n  setData<T>(payload: T) {\n    this.sharedData.payload = payload;\n    return this;\n  }\n\n  setState(\n    stateOrFn:\n      | ((prev: DrawerState) => Partial<DrawerState>)\n      | Partial<DrawerState>,\n  ) {\n    if (isFunction(stateOrFn)) {\n      this.store.setState(stateOrFn);\n    } else {\n      this.store.setState((prev) => ({ ...prev, ...stateOrFn }));\n    }\n    return this;\n  }\n\n  /**\n   * 解除抽屉的锁定状态\n   * @description 解除由lock方法设置的锁定状态，是lock(false)的别名\n   */\n  unlock() {\n    return this.lock(false);\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts",
    "content": "import type { Component, Ref } from 'vue';\n\nimport type { ClassType, MaybePromise } from '@vben-core/typings';\n\nimport type { DrawerApi } from './drawer-api';\n\nexport type DrawerPlacement = 'bottom' | 'left' | 'right' | 'top';\n\nexport type CloseIconPlacement = 'left' | 'right';\n\nexport interface DrawerProps {\n  /**\n   * 是否挂载到内容区域\n   * @default false\n   */\n  appendToMain?: boolean;\n  /**\n   * 取消按钮文字\n   */\n  cancelText?: string;\n  class?: ClassType;\n  /**\n   * 是否显示关闭按钮\n   * @default true\n   */\n  closable?: boolean;\n  /**\n   * 关闭按钮的位置\n   */\n  closeIconPlacement?: CloseIconPlacement;\n  /**\n   * 点击弹窗遮罩是否关闭弹窗\n   * @default true\n   */\n  closeOnClickModal?: boolean;\n  /**\n   * 按下 ESC 键是否关闭弹窗\n   * @default true\n   */\n  closeOnPressEscape?: boolean;\n  /**\n   * 确定按钮 loading\n   * @default false\n   */\n  confirmLoading?: boolean;\n  /**\n   * 确定按钮文字\n   */\n  confirmText?: string;\n  contentClass?: string;\n  /**\n   * 弹窗描述\n   */\n  description?: string;\n  /**\n   * 在关闭时销毁抽屉\n   */\n  destroyOnClose?: boolean;\n  /**\n   * 是否显示底部\n   * @default true\n   */\n  footer?: boolean;\n  /**\n   * 弹窗底部样式\n   */\n  footerClass?: ClassType;\n  /**\n   * 是否显示顶栏\n   * @default true\n   */\n  header?: boolean;\n  /**\n   * 弹窗头部样式\n   */\n  headerClass?: ClassType;\n  /**\n   * 弹窗是否显示\n   * @default false\n   */\n  loading?: boolean;\n  /**\n   * 是否显示遮罩\n   * @default true\n   */\n  modal?: boolean;\n\n  /**\n   * 是否自动聚焦\n   */\n  openAutoFocus?: boolean;\n  /**\n   * 弹窗遮罩模糊效果\n   */\n  overlayBlur?: number;\n  /**\n   * 抽屉位置\n   * @default right\n   */\n  placement?: DrawerPlacement;\n\n  /**\n   * 是否显示取消按钮\n   * @default true\n   */\n  showCancelButton?: boolean;\n  /**\n   * 是否显示确认按钮\n   * @default true\n   */\n  showConfirmButton?: boolean;\n  /**\n   * 提交中（锁定抽屉状态）\n   */\n  submitting?: boolean;\n  /**\n   * 弹窗标题\n   */\n  title?: string;\n  /**\n   * 弹窗标题提示\n   */\n  titleTooltip?: string;\n  /**\n   * 抽屉层级\n   */\n  zIndex?: number;\n}\n\nexport interface DrawerState extends DrawerProps {\n  /** 弹窗打开状态 */\n  isOpen?: boolean;\n  /**\n   * 共享数据\n   */\n  sharedData?: Record<string, any>;\n}\n\nexport type ExtendedDrawerApi = DrawerApi & {\n  useStore: <T = NoInfer<DrawerState>>(\n    selector?: (state: NoInfer<DrawerState>) => T,\n  ) => Readonly<Ref<T>>;\n};\n\nexport interface DrawerApiOptions extends DrawerState {\n  /**\n   * 独立的抽屉组件\n   */\n  connectedComponent?: Component;\n  /**\n   * 关闭前的回调，返回 false 可以阻止关闭\n   * @returns\n   */\n  onBeforeClose?: () => MaybePromise<boolean | undefined>;\n  /**\n   * 点击取消按钮的回调\n   */\n  onCancel?: () => void;\n  /**\n   * 弹窗关闭动画结束的回调\n   * @returns\n   */\n  onClosed?: () => void;\n  /**\n   * 点击确定按钮的回调\n   */\n  onConfirm?: () => void;\n  /**\n   * 弹窗状态变化回调\n   * @param isOpen\n   * @returns\n   */\n  onOpenChange?: (isOpen: boolean) => void;\n  /**\n   * 弹窗打开动画结束的回调\n   * @returns\n   */\n  onOpened?: () => void;\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { DrawerProps, ExtendedDrawerApi } from './drawer';\n\nimport {\n  computed,\n  onDeactivated,\n  provide,\n  ref,\n  unref,\n  useId,\n  watch,\n} from 'vue';\n\nimport {\n  useIsMobile,\n  usePriorityValues,\n  useSimpleLocale,\n} from '@vben-core/composables';\nimport { X } from '@vben-core/icons';\nimport {\n  Separator,\n  Sheet,\n  SheetClose,\n  SheetContent,\n  SheetDescription,\n  SheetFooter,\n  SheetHeader,\n  SheetTitle,\n  VbenButton,\n  VbenHelpTooltip,\n  VbenIconButton,\n  VbenLoading,\n  VisuallyHidden,\n} from '@vben-core/shadcn-ui';\nimport { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';\nimport { globalShareState } from '@vben-core/shared/global-state';\nimport { cn } from '@vben-core/shared/utils';\n\ninterface Props extends DrawerProps {\n  drawerApi?: ExtendedDrawerApi;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  appendToMain: false,\n  closeIconPlacement: 'right',\n  destroyOnClose: false,\n  drawerApi: undefined,\n  submitting: false,\n  zIndex: 1000,\n});\n\nconst components = globalShareState.getComponents();\n\nconst id = useId();\nprovide('DISMISSABLE_DRAWER_ID', id);\n\n// @ts-expect-error unused\nconst wrapperRef = ref<HTMLElement>();\nconst { $t } = useSimpleLocale();\nconst { isMobile } = useIsMobile();\n\nconst state = props.drawerApi?.useStore?.();\n\nconst {\n  appendToMain,\n  cancelText,\n  class: drawerClass,\n  closable,\n  closeIconPlacement,\n  closeOnClickModal,\n  closeOnPressEscape,\n  confirmLoading,\n  confirmText,\n  contentClass,\n  description,\n  destroyOnClose,\n  footer: showFooter,\n  footerClass,\n  header: showHeader,\n  headerClass,\n  loading: showLoading,\n  modal,\n  openAutoFocus,\n  overlayBlur,\n  placement,\n  showCancelButton,\n  showConfirmButton,\n  submitting,\n  title,\n  titleTooltip,\n  zIndex,\n} = usePriorityValues(props, state);\n\n// watch(\n//   () => showLoading.value,\n//   (v) => {\n//     if (v && wrapperRef.value) {\n//       wrapperRef.value.scrollTo({\n//         // behavior: 'smooth',\n//         top: 0,\n//       });\n//     }\n//   },\n// );\n\n/**\n * 在开启keepAlive情况下 直接通过浏览器按钮/手势等返回 不会关闭弹窗\n */\nonDeactivated(() => {\n  // 如果弹窗没有被挂载到内容区域，则关闭弹窗\n  if (!appendToMain.value) {\n    props.drawerApi?.close();\n  }\n});\n\nfunction interactOutside(e: Event) {\n  if (!closeOnClickModal.value || submitting.value) {\n    e.preventDefault();\n  }\n}\nfunction escapeKeyDown(e: KeyboardEvent) {\n  if (!closeOnPressEscape.value || submitting.value) {\n    e.preventDefault();\n  }\n}\n// pointer-down-outside\nfunction pointerDownOutside(e: Event) {\n  const target = e.target as HTMLElement;\n  const dismissableDrawer = target?.dataset.dismissableDrawer;\n  if (\n    submitting.value ||\n    !closeOnClickModal.value ||\n    dismissableDrawer !== id\n  ) {\n    e.preventDefault();\n  }\n}\n\nfunction handerOpenAutoFocus(e: Event) {\n  if (!openAutoFocus.value) {\n    e?.preventDefault();\n  }\n}\n\nfunction handleFocusOutside(e: Event) {\n  e.preventDefault();\n  e.stopPropagation();\n}\n\nconst getAppendTo = computed(() => {\n  return appendToMain.value\n    ? `#${ELEMENT_ID_MAIN_CONTENT}>div:not(.absolute)>div`\n    : undefined;\n});\n\n/**\n * destroyOnClose功能完善\n */\n// 是否打开过\nconst hasOpened = ref(false);\nconst isClosed = ref(true);\nwatch(\n  () => state?.value?.isOpen,\n  (value) => {\n    isClosed.value = false;\n    if (value && !unref(hasOpened)) {\n      hasOpened.value = true;\n    }\n  },\n);\nfunction handleClosed() {\n  isClosed.value = true;\n  props.drawerApi?.onClosed();\n}\nconst getForceMount = computed(() => {\n  return !unref(destroyOnClose) && unref(hasOpened);\n});\n</script>\n<template>\n  <Sheet\n    :modal=\"false\"\n    :open=\"state?.isOpen\"\n    @update:open=\"() => drawerApi?.close()\"\n  >\n    <SheetContent\n      :append-to=\"getAppendTo\"\n      :class=\"\n        cn('flex w-130 flex-col', drawerClass, {\n          'w-full!': isMobile || placement === 'bottom' || placement === 'top',\n          'max-h-screen': placement === 'bottom' || placement === 'top',\n          hidden: isClosed,\n        })\n      \"\n      :modal=\"modal\"\n      :open=\"state?.isOpen\"\n      :side=\"placement\"\n      :z-index=\"zIndex\"\n      :force-mount=\"getForceMount\"\n      :overlay-blur=\"overlayBlur\"\n      @close-auto-focus=\"handleFocusOutside\"\n      @closed=\"handleClosed\"\n      @escape-key-down=\"escapeKeyDown\"\n      @focus-outside=\"handleFocusOutside\"\n      @interact-outside=\"interactOutside\"\n      @open-auto-focus=\"handerOpenAutoFocus\"\n      @opened=\"() => drawerApi?.onOpened()\"\n      @pointer-down-outside=\"pointerDownOutside\"\n    >\n      <SheetHeader\n        v-if=\"showHeader\"\n        :class=\"\n          cn(\n            'flex! flex-row items-center justify-between border-b px-6 py-5',\n            headerClass,\n            {\n              'px-4 py-3': closable,\n              'pl-2': closable && closeIconPlacement === 'left',\n            },\n          )\n        \"\n      >\n        <div class=\"flex items-center\">\n          <SheetClose\n            v-if=\"closable && closeIconPlacement === 'left'\"\n            as-child\n            :disabled=\"submitting\"\n            class=\"ml-0.5 cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary\"\n          >\n            <slot name=\"close-icon\">\n              <VbenIconButton>\n                <X class=\"size-4\" />\n              </VbenIconButton>\n            </slot>\n          </SheetClose>\n          <Separator\n            v-if=\"closable && closeIconPlacement === 'left'\"\n            class=\"mr-2 ml-1 h-8\"\n            decorative\n            orientation=\"vertical\"\n          />\n          <SheetTitle v-if=\"title\" class=\"text-left\">\n            <slot name=\"title\">\n              {{ title }}\n\n              <VbenHelpTooltip v-if=\"titleTooltip\" trigger-class=\"pb-1\">\n                {{ titleTooltip }}\n              </VbenHelpTooltip>\n            </slot>\n          </SheetTitle>\n          <SheetDescription v-if=\"description\" class=\"mt-1 text-xs\">\n            <slot name=\"description\">\n              {{ description }}\n            </slot>\n          </SheetDescription>\n        </div>\n\n        <VisuallyHidden v-if=\"!title || !description\">\n          <SheetTitle v-if=\"!title\" />\n          <SheetDescription v-if=\"!description\" />\n        </VisuallyHidden>\n\n        <div class=\"flex-center\">\n          <slot name=\"extra\"></slot>\n          <SheetClose\n            v-if=\"closable && closeIconPlacement === 'right'\"\n            as-child\n            :disabled=\"submitting\"\n            class=\"ml-0.5 cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary\"\n          >\n            <slot name=\"close-icon\">\n              <VbenIconButton>\n                <X class=\"size-4\" />\n              </VbenIconButton>\n            </slot>\n          </SheetClose>\n        </div>\n      </SheetHeader>\n      <template v-else>\n        <VisuallyHidden>\n          <SheetTitle />\n          <SheetDescription />\n        </VisuallyHidden>\n      </template>\n      <div\n        ref=\"wrapperRef\"\n        :class=\"\n          cn('relative flex-1 overflow-y-auto p-3', contentClass, {\n            'pointer-events-none': showLoading || submitting,\n          })\n        \"\n      >\n        <slot></slot>\n      </div>\n      <VbenLoading v-if=\"showLoading || submitting\" spinning />\n      <SheetFooter\n        v-if=\"showFooter\"\n        :class=\"\n          cn(\n            'w-full flex-row items-center justify-end border-t p-2 px-3',\n            footerClass,\n          )\n        \"\n      >\n        <slot name=\"prepend-footer\"></slot>\n        <slot name=\"footer\">\n          <component\n            :is=\"components.DefaultButton || VbenButton\"\n            v-if=\"showCancelButton\"\n            variant=\"ghost\"\n            :disabled=\"submitting\"\n            @click=\"() => drawerApi?.onCancel()\"\n          >\n            <slot name=\"cancelText\">\n              {{ cancelText || $t('cancel') }}\n            </slot>\n          </component>\n          <slot name=\"center-footer\"></slot>\n          <component\n            :is=\"components.PrimaryButton || VbenButton\"\n            v-if=\"showConfirmButton\"\n            :loading=\"confirmLoading || submitting\"\n            @click=\"() => drawerApi?.onConfirm()\"\n          >\n            <slot name=\"confirmText\">\n              {{ confirmText || $t('confirm') }}\n            </slot>\n          </component>\n        </slot>\n        <slot name=\"append-footer\"></slot>\n      </SheetFooter>\n    </SheetContent>\n  </Sheet>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/drawer/index.ts",
    "content": "export type * from './drawer';\nexport { default as VbenDrawer } from './drawer.vue';\nexport { setDefaultDrawerProps, useVbenDrawer } from './use-drawer';\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/drawer/use-drawer.ts",
    "content": "import type {\n  DrawerApiOptions,\n  DrawerProps,\n  ExtendedDrawerApi,\n} from './drawer';\n\nimport {\n  defineComponent,\n  h,\n  inject,\n  nextTick,\n  provide,\n  reactive,\n  ref,\n} from 'vue';\n\nimport { useStore } from '@vben-core/shared/store';\n\nimport { DrawerApi } from './drawer-api';\nimport VbenDrawer from './drawer.vue';\n\nconst USER_DRAWER_INJECT_KEY = Symbol('VBEN_DRAWER_INJECT');\n\nconst DEFAULT_DRAWER_PROPS: Partial<DrawerProps> = {};\n\nexport function setDefaultDrawerProps(props: Partial<DrawerProps>) {\n  Object.assign(DEFAULT_DRAWER_PROPS, props);\n}\n\nexport function useVbenDrawer<\n  TParentDrawerProps extends DrawerProps = DrawerProps,\n>(options: DrawerApiOptions = {}) {\n  // Drawer一般会抽离出来，所以如果有传入 connectedComponent，则表示为外部调用，与内部组件进行连接\n  // 外部的Drawer通过provide/inject传递api\n\n  const { connectedComponent } = options;\n  if (connectedComponent) {\n    const extendedApi = reactive({});\n    const isDrawerReady = ref(true);\n    const Drawer = defineComponent(\n      (props: TParentDrawerProps, { attrs, slots }) => {\n        provide(USER_DRAWER_INJECT_KEY, {\n          extendApi(api: ExtendedDrawerApi) {\n            // 不能直接给 reactive 赋值，会丢失响应\n            // 不能用 Object.assign,会丢失 api 的原型函数\n            Object.setPrototypeOf(extendedApi, api);\n          },\n          options,\n          async reCreateDrawer() {\n            isDrawerReady.value = false;\n            await nextTick();\n            isDrawerReady.value = true;\n          },\n        });\n        checkProps(extendedApi as ExtendedDrawerApi, {\n          ...props,\n          ...attrs,\n          ...slots,\n        });\n        return () =>\n          h(\n            isDrawerReady.value ? connectedComponent : 'div',\n            { ...props, ...attrs },\n            slots,\n          );\n      },\n      // eslint-disable-next-line vue/one-component-per-file\n      {\n        name: 'VbenParentDrawer',\n        inheritAttrs: false,\n      },\n    );\n\n    return [Drawer, extendedApi as ExtendedDrawerApi] as const;\n  }\n\n  const injectData = inject<any>(USER_DRAWER_INJECT_KEY, {});\n\n  const mergedOptions = {\n    ...DEFAULT_DRAWER_PROPS,\n    ...injectData.options,\n    ...options,\n  } as DrawerApiOptions;\n\n  mergedOptions.onOpenChange = (isOpen: boolean) => {\n    options.onOpenChange?.(isOpen);\n    injectData.options?.onOpenChange?.(isOpen);\n  };\n\n  const onClosed = mergedOptions.onClosed;\n  mergedOptions.onClosed = () => {\n    onClosed?.();\n    if (mergedOptions.destroyOnClose) {\n      injectData.reCreateDrawer?.();\n    }\n  };\n  const api = new DrawerApi(mergedOptions);\n\n  const extendedApi: ExtendedDrawerApi = api as never;\n\n  extendedApi.useStore = (selector) => {\n    return useStore(api.store, selector);\n  };\n\n  const Drawer = defineComponent(\n    (props: DrawerProps, { attrs, slots }) => {\n      return () =>\n        h(VbenDrawer, { ...props, ...attrs, drawerApi: extendedApi }, slots);\n    },\n    // eslint-disable-next-line vue/one-component-per-file\n    {\n      name: 'VbenDrawer',\n      inheritAttrs: false,\n    },\n  );\n  injectData.extendApi?.(extendedApi);\n  return [Drawer, extendedApi] as const;\n}\n\nasync function checkProps(api: ExtendedDrawerApi, attrs: Record<string, any>) {\n  if (!attrs || Object.keys(attrs).length === 0) {\n    return;\n  }\n  await nextTick();\n\n  const state = api?.store?.state;\n\n  if (!state) {\n    return;\n  }\n\n  const stateKeys = new Set(Object.keys(state));\n\n  for (const attr of Object.keys(attrs)) {\n    if (stateKeys.has(attr) && !['class'].includes(attr)) {\n      // connectedComponent存在时，不要传入Drawer的props，会造成复杂度提升，如果你需要修改Drawer的props，请使用 useVbenDrawer 或者api\n      console.warn(\n        `[Vben Drawer]: When 'connectedComponent' exists, do not set props or slots '${attr}', which will increase complexity. If you need to modify the props of Drawer, please use useVbenDrawer or api.`,\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/index.ts",
    "content": "export * from './alert';\nexport * from './drawer';\nexport * from './modal';\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts",
    "content": "import type { ModalState } from '../modal';\n\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { ModalApi } from '../modal-api';\n\nvi.mock('@vben-core/shared/store', () => {\n  return {\n    isFunction: (fn: any) => typeof fn === 'function',\n    Store: class {\n      get state() {\n        return this._state;\n      }\n      private _state: ModalState;\n      private subscribers: Array<(state: ModalState) => void> = [];\n\n      constructor(initialState: ModalState) {\n        this._state = initialState;\n      }\n\n      setState(fn: (prev: ModalState) => ModalState) {\n        this._state = fn(this._state);\n        this.subscribers.forEach((sub) => sub(this._state));\n      }\n\n      subscribe(fn: (state: ModalState) => void) {\n        this.subscribers.push(fn);\n        return { unsubscribe: () => {} };\n      }\n    },\n  };\n});\n\ndescribe('modalApi', () => {\n  let modalApi: ModalApi;\n  // 使用 modalState 而不是 state\n  let modalState: ModalState;\n\n  beforeEach(() => {\n    modalApi = new ModalApi();\n    // 获取 modalApi 内的 state\n    modalState = modalApi.store.state;\n  });\n\n  it('should initialize with default state', () => {\n    expect(modalState.isOpen).toBe(false);\n    expect(modalState.cancelText).toBe(undefined);\n    expect(modalState.confirmText).toBe(undefined);\n  });\n\n  it('should open the modal', () => {\n    modalApi.open();\n    expect(modalApi.store.state.isOpen).toBe(true);\n  });\n\n  it('should close the modal if onBeforeClose allows it', () => {\n    modalApi.close();\n    expect(modalApi.store.state.isOpen).toBe(false);\n  });\n\n  it('should not close the modal if onBeforeClose returns false', () => {\n    const onBeforeClose = vi.fn(() => false);\n    const modalApiWithHook = new ModalApi({ onBeforeClose });\n    modalApiWithHook.open();\n    modalApiWithHook.close();\n    expect(modalApiWithHook.store.state.isOpen).toBe(true);\n    expect(onBeforeClose).toHaveBeenCalled();\n  });\n\n  it('should trigger onCancel and close the modal if no onCancel hook is provided', () => {\n    const onCancel = vi.fn();\n    const modalApiWithHook = new ModalApi({ onCancel });\n    modalApiWithHook.open();\n    modalApiWithHook.onCancel();\n    expect(onCancel).toHaveBeenCalled();\n    expect(modalApiWithHook.store.state.isOpen).toBe(true);\n  });\n\n  it('should update shared data correctly', () => {\n    const testData = { key: 'value' };\n    modalApi.setData(testData);\n    expect(modalApi.getData()).toEqual(testData);\n  });\n\n  it('should set state correctly using an object', () => {\n    modalApi.setState({ title: 'New Title' });\n    expect(modalApi.store.state.title).toBe('New Title');\n  });\n\n  it('should set state correctly using a function', () => {\n    modalApi.setState((prev) => ({ ...prev, confirmText: 'Yes' }));\n    expect(modalApi.store.state.confirmText).toBe('Yes');\n  });\n\n  it('should call onOpenChange when state changes', () => {\n    const onOpenChange = vi.fn();\n    const modalApiWithHook = new ModalApi({ onOpenChange });\n    modalApiWithHook.open();\n    expect(onOpenChange).toHaveBeenCalledWith(true);\n  });\n\n  it('should call onClosed callback when provided', () => {\n    const onClosed = vi.fn();\n    const modalApiWithHook = new ModalApi({ onClosed });\n    modalApiWithHook.onClosed();\n    expect(onClosed).toHaveBeenCalled();\n  });\n\n  it('should call onOpened callback when provided', () => {\n    const onOpened = vi.fn();\n    const modalApiWithHook = new ModalApi({ onOpened });\n    modalApiWithHook.open();\n    modalApiWithHook.onOpened();\n    expect(onOpened).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/modal/index.ts",
    "content": "export type * from './modal';\nexport { default as VbenModal } from './modal.vue';\nexport { setDefaultModalProps, useVbenModal } from './use-modal';\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts",
    "content": "import type { ModalApiOptions, ModalState } from './modal';\n\nimport { Store } from '@vben-core/shared/store';\nimport { bindMethods, isFunction } from '@vben-core/shared/utils';\n\nexport class ModalApi {\n  // 共享数据\n  public sharedData: Record<'payload', any> = {\n    payload: {},\n  };\n  public store: Store<ModalState>;\n\n  private api: Pick<\n    ModalApiOptions,\n    | 'onBeforeClose'\n    | 'onCancel'\n    | 'onClosed'\n    | 'onConfirm'\n    | 'onOpenChange'\n    | 'onOpened'\n  >;\n\n  // private prevState!: ModalState;\n  private state!: ModalState;\n\n  constructor(options: ModalApiOptions = {}) {\n    const {\n      connectedComponent: _,\n      onBeforeClose,\n      onCancel,\n      onClosed,\n      onConfirm,\n      onOpenChange,\n      onOpened,\n      ...storeState\n    } = options;\n\n    const defaultState: ModalState = {\n      bordered: true,\n      centered: false,\n      class: '',\n      closeOnClickModal: true,\n      closeOnPressEscape: true,\n      confirmDisabled: false,\n      confirmLoading: false,\n      contentClass: '',\n      destroyOnClose: true,\n      draggable: false,\n      footer: true,\n      footerClass: '',\n      fullscreen: false,\n      fullscreenButton: true,\n      header: true,\n      headerClass: '',\n      isOpen: false,\n      loading: false,\n      modal: true,\n      openAutoFocus: false,\n      showCancelButton: true,\n      showConfirmButton: true,\n      title: '',\n      animationType: 'slide',\n    };\n\n    this.store = new Store<ModalState>({\n      ...defaultState,\n      ...storeState,\n    });\n\n    this.store.subscribe((state) => {\n      // 每次更新状态时，都会调用 onOpenChange 回调函数\n      const prevIsOpen = this.state?.isOpen;\n      this.state = state;\n      if (state?.isOpen !== prevIsOpen) {\n        this.api.onOpenChange?.(!!state?.isOpen);\n      }\n    });\n\n    this.state = this.store.state;\n\n    this.api = {\n      onBeforeClose,\n      onCancel,\n      onClosed,\n      onConfirm,\n      onOpenChange,\n      onOpened,\n    };\n    bindMethods(this);\n  }\n\n  /**\n   * 关闭弹窗\n   * @description 关闭弹窗时会调用 onBeforeClose 钩子函数，如果 onBeforeClose 返回 false，则不关闭弹窗\n   */\n  async close() {\n    // 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗\n    // 如果 onBeforeClose 返回 false，则不关闭弹窗\n    const allowClose = (await this.api.onBeforeClose?.()) ?? true;\n    if (allowClose) {\n      this.store.setState((prev) => ({\n        ...prev,\n        isOpen: false,\n      }));\n    }\n  }\n\n  getData<T extends object = Record<string, any>>() {\n    return (this.sharedData?.payload ?? {}) as T;\n  }\n\n  /**\n   * 锁定弹窗状态（用于提交过程中的等待状态）\n   * @description 锁定状态将禁用默认的取消按钮，使用spinner覆盖弹窗内容，隐藏关闭按钮，阻止手动关闭弹窗，将默认的提交按钮标记为loading状态\n   * @param isLocked 是否锁定\n   */\n  lock(isLocked = true) {\n    return this.setState({ submitting: isLocked });\n  }\n\n  /**\n   * 取消操作\n   */\n  onCancel() {\n    if (this.api.onCancel) {\n      this.api.onCancel?.();\n    } else {\n      this.close();\n    }\n  }\n\n  /**\n   * 弹窗关闭动画播放完毕后的回调\n   */\n  onClosed() {\n    if (!this.state.isOpen) {\n      this.api.onClosed?.();\n    }\n  }\n\n  /**\n   * 确认操作\n   */\n  onConfirm() {\n    this.api.onConfirm?.();\n  }\n\n  /**\n   * 弹窗打开动画播放完毕后的回调\n   */\n  onOpened() {\n    if (this.state.isOpen) {\n      this.api.onOpened?.();\n    }\n  }\n\n  open() {\n    this.store.setState((prev) => ({\n      ...prev,\n      isOpen: true,\n      submitting: false,\n    }));\n  }\n\n  setData<T>(payload: T) {\n    this.sharedData.payload = payload;\n    return this;\n  }\n\n  setState(\n    stateOrFn:\n      | ((prev: ModalState) => Partial<ModalState>)\n      | Partial<ModalState>,\n  ) {\n    if (isFunction(stateOrFn)) {\n      this.store.setState(stateOrFn);\n    } else {\n      this.store.setState((prev) => ({ ...prev, ...stateOrFn }));\n    }\n    return this;\n  }\n\n  /**\n   * 解除弹窗的锁定状态\n   * @description 解除由lock方法设置的锁定状态，是lock(false)的别名\n   */\n  unlock() {\n    return this.lock(false);\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/modal/modal.ts",
    "content": "import type { Component, Ref } from 'vue';\n\nimport type { ClassType, MaybePromise } from '@vben-core/typings';\n\nimport type { ModalApi } from './modal-api';\n\nexport interface ModalProps {\n  /**\n   * 动画类型\n   * @default 'slide'\n   */\n  animationType?: 'scale' | 'slide';\n  /**\n   * 是否要挂载到内容区域\n   * @default false\n   */\n  appendToMain?: boolean;\n  /**\n   * 是否显示边框\n   * @default false\n   */\n  bordered?: boolean;\n  /**\n   * 取消按钮文字\n   */\n  cancelText?: string;\n  /**\n   * 是否居中\n   * @default false\n   */\n  centered?: boolean;\n\n  class?: ClassType;\n\n  /**\n   * 是否显示右上角的关闭按钮\n   * @default true\n   */\n  closable?: boolean;\n  /**\n   * 点击弹窗遮罩是否关闭弹窗\n   * @default true\n   */\n  closeOnClickModal?: boolean;\n  /**\n   * 按下 ESC 键是否关闭弹窗\n   * @default true\n   */\n  closeOnPressEscape?: boolean;\n  /**\n   * 禁用确认按钮\n   */\n  confirmDisabled?: boolean;\n  /**\n   * 确定按钮 loading\n   * @default false\n   */\n  confirmLoading?: boolean;\n  /**\n   * 确定按钮文字\n   */\n  confirmText?: string;\n  contentClass?: ClassType;\n  /**\n   * 弹窗描述\n   */\n  description?: string;\n  /**\n   * 在关闭时销毁弹窗\n   */\n  destroyOnClose?: boolean;\n  /**\n   * 是否可拖拽\n   * @default false\n   */\n  draggable?: boolean;\n  /**\n   * 是否显示底部\n   * @default true\n   */\n  footer?: boolean;\n  footerClass?: ClassType;\n  /**\n   * 是否全屏\n   * @default false\n   */\n  fullscreen?: boolean;\n  /**\n   * 是否显示全屏按钮\n   * @default true\n   */\n  fullscreenButton?: boolean;\n  /**\n   * 是否显示顶栏\n   * @default true\n   */\n  header?: boolean;\n  headerClass?: ClassType;\n  /**\n   * 弹窗是否显示\n   * @default false\n   */\n  loading?: boolean;\n  /**\n   * 是否显示遮罩\n   * @default true\n   */\n  modal?: boolean;\n  /**\n   * 是否自动聚焦\n   */\n  openAutoFocus?: boolean;\n  /**\n   * 弹窗遮罩模糊效果\n   */\n  overlayBlur?: number;\n  /**\n   * 是否显示取消按钮\n   * @default true\n   */\n  showCancelButton?: boolean;\n  /**\n   * 是否显示确认按钮\n   * @default true\n   */\n  showConfirmButton?: boolean;\n  /**\n   * 提交中（锁定弹窗状态）\n   */\n  submitting?: boolean;\n  /**\n   * 弹窗标题\n   */\n  title?: string;\n  /**\n   * 弹窗标题提示\n   */\n  titleTooltip?: string;\n  /**\n   * 弹窗层级\n   */\n  zIndex?: number;\n}\n\nexport interface ModalState extends ModalProps {\n  /** 弹窗打开状态 */\n  isOpen?: boolean;\n  /**\n   * 共享数据\n   */\n  sharedData?: Record<string, any>;\n}\n\nexport type ExtendedModalApi = ModalApi & {\n  useStore: <T = NoInfer<ModalState>>(\n    selector?: (state: NoInfer<ModalState>) => T,\n  ) => Readonly<Ref<T>>;\n};\n\nexport interface ModalApiOptions extends ModalState {\n  /**\n   * 独立的弹窗组件\n   */\n  connectedComponent?: Component;\n  /**\n   * 关闭前的回调，返回 false 可以阻止关闭\n   * @returns\n   */\n  onBeforeClose?: () => MaybePromise<boolean | undefined>;\n  /**\n   * 点击取消按钮的回调\n   */\n  onCancel?: () => void;\n  /**\n   * 弹窗关闭动画结束的回调\n   * @returns\n   */\n  onClosed?: () => void;\n  /**\n   * 点击确定按钮的回调\n   */\n  onConfirm?: () => void;\n  /**\n   * 弹窗状态变化回调\n   * @param isOpen\n   * @returns\n   */\n  onOpenChange?: (isOpen: boolean) => void;\n  /**\n   * 弹窗打开动画结束的回调\n   * @returns\n   */\n  onOpened?: () => void;\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/modal/modal.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { ExtendedModalApi, ModalProps } from './modal';\n\nimport {\n  computed,\n  nextTick,\n  onDeactivated,\n  provide,\n  ref,\n  unref,\n  useId,\n  watch,\n} from 'vue';\n\nimport {\n  useIsMobile,\n  usePriorityValues,\n  useSimpleLocale,\n} from '@vben-core/composables';\nimport { Expand, Shrink } from '@vben-core/icons';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  VbenButton,\n  VbenHelpTooltip,\n  VbenIconButton,\n  VbenLoading,\n  VisuallyHidden,\n} from '@vben-core/shadcn-ui';\nimport { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';\nimport { globalShareState } from '@vben-core/shared/global-state';\nimport { cn } from '@vben-core/shared/utils';\n\nimport { useModalDraggable } from './use-modal-draggable';\n\ninterface Props extends ModalProps {\n  modalApi?: ExtendedModalApi;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  appendToMain: false,\n  destroyOnClose: false,\n  modalApi: undefined,\n});\n\nconst components = globalShareState.getComponents();\n\nconst contentRef = ref();\n// @ts-expect-error unused\nconst wrapperRef = ref<HTMLElement>();\nconst dialogRef = ref();\nconst headerRef = ref();\n// @ts-expect-error unused\nconst footerRef = ref();\n\nconst id = useId();\n\nprovide('DISMISSABLE_MODAL_ID', id);\n\nconst { $t } = useSimpleLocale();\nconst { isMobile } = useIsMobile();\nconst state = props.modalApi?.useStore?.();\n\nconst {\n  appendToMain,\n  bordered,\n  cancelText,\n  centered,\n  class: modalClass,\n  closable,\n  closeOnClickModal,\n  closeOnPressEscape,\n  confirmDisabled,\n  confirmLoading,\n  confirmText,\n  contentClass,\n  description,\n  destroyOnClose,\n  draggable,\n  footer: showFooter,\n  footerClass,\n  fullscreen,\n  fullscreenButton,\n  header,\n  headerClass,\n  loading: showLoading,\n  modal,\n  openAutoFocus,\n  overlayBlur,\n  showCancelButton,\n  showConfirmButton,\n  submitting,\n  title,\n  titleTooltip,\n  animationType,\n  zIndex,\n} = usePriorityValues(props, state);\n\nconst shouldFullscreen = computed(() => fullscreen.value || isMobile.value);\n\nconst shouldDraggable = computed(\n  () => draggable.value && !shouldFullscreen.value && header.value,\n);\n\nconst shouldCentered = computed(\n  () => centered.value && !shouldFullscreen.value,\n);\n\nconst getAppendTo = computed(() => {\n  return appendToMain.value\n    ? `#${ELEMENT_ID_MAIN_CONTENT}>div:not(.absolute)>div`\n    : undefined;\n});\n\nconst { dragging, transform } = useModalDraggable(\n  dialogRef,\n  headerRef,\n  shouldDraggable,\n  getAppendTo,\n  shouldCentered,\n);\n\nconst firstOpened = ref(false);\nconst isClosed = ref(true);\n\nwatch(\n  () => state?.value?.isOpen,\n  async (v) => {\n    if (v) {\n      isClosed.value = false;\n      if (!firstOpened.value) firstOpened.value = true;\n      await nextTick();\n      if (!contentRef.value) return;\n      const innerContentRef = contentRef.value.getContentRef();\n      dialogRef.value = innerContentRef.$el;\n      // reopen modal reassign value\n      const { offsetX, offsetY } = transform;\n      dialogRef.value.style.transform = shouldCentered.value\n        ? `translate(${offsetX}px, calc(-50% + ${offsetY}px))`\n        : `translate(${offsetX}px, ${offsetY}px)`;\n    }\n  },\n  { immediate: true },\n);\n\n// watch(\n//   () => [showLoading.value, submitting.value],\n//   ([l, s]) => {\n//     if ((s || l) && wrapperRef.value) {\n//       wrapperRef.value.scrollTo({\n//         // behavior: 'smooth',\n//         top: 0,\n//       });\n//     }\n//   },\n// );\n\n/**\n * 在开启keepAlive情况下 直接通过浏览器按钮/手势等返回 不会关闭弹窗\n */\nonDeactivated(() => {\n  // 如果弹窗没有被挂载到内容区域，则关闭弹窗\n  if (!appendToMain.value) {\n    props.modalApi?.close();\n  }\n});\n\nfunction handleFullscreen() {\n  props.modalApi?.setState((prev) => {\n    // if (prev.fullscreen) {\n    //   resetPosition();\n    // }\n    return { ...prev, fullscreen: !fullscreen.value };\n  });\n}\nfunction interactOutside(e: Event) {\n  if (!closeOnClickModal.value || submitting.value) {\n    e.preventDefault();\n    e.stopPropagation();\n  }\n}\nfunction escapeKeyDown(e: KeyboardEvent) {\n  if (!closeOnPressEscape.value || submitting.value) {\n    e.preventDefault();\n  }\n}\n\nfunction handleOpenAutoFocus(e: Event) {\n  if (!openAutoFocus.value) {\n    e?.preventDefault();\n  }\n}\n\n// pointer-down-outside\nfunction pointerDownOutside(e: Event) {\n  const target = e.target as HTMLElement;\n  const isDismissableModal = target?.dataset.dismissableModal;\n  if (\n    !closeOnClickModal.value ||\n    isDismissableModal !== id ||\n    submitting.value\n  ) {\n    e.preventDefault();\n    e.stopPropagation();\n  }\n}\n\nfunction handleFocusOutside(e: Event) {\n  e.preventDefault();\n  e.stopPropagation();\n}\n\nconst getForceMount = computed(() => {\n  return !unref(destroyOnClose) && unref(firstOpened);\n});\n\nconst handleOpened = () => {\n  requestAnimationFrame(() => {\n    props.modalApi?.onOpened();\n  });\n};\n\nfunction handleClosed() {\n  isClosed.value = true;\n  props.modalApi?.onClosed();\n}\n</script>\n<template>\n  <Dialog\n    :modal=\"false\"\n    :open=\"state?.isOpen\"\n    @update:open=\"() => (!submitting ? modalApi?.close() : undefined)\"\n  >\n    <DialogContent\n      ref=\"contentRef\"\n      :append-to=\"getAppendTo\"\n      :class=\"\n        cn(\n          'inset-x-0 top-[10vh] mx-auto flex max-h-[80%] w-130 flex-col p-0',\n          shouldFullscreen ? 'sm:rounded-none' : 'sm:rounded-(--radius)',\n          modalClass,\n          {\n            'border border-border': bordered,\n            'shadow-3xl': !bordered,\n            'top-0 left-0 size-full max-h-full translate-0!': shouldFullscreen,\n            'top-1/2': centered && !shouldFullscreen,\n            'duration-300': !dragging,\n            hidden: isClosed,\n          },\n        )\n      \"\n      :force-mount=\"getForceMount\"\n      :modal=\"modal\"\n      :open=\"state?.isOpen\"\n      :show-close=\"closable\"\n      :animation-type=\"animationType\"\n      :z-index=\"zIndex\"\n      :overlay-blur=\"overlayBlur\"\n      close-class=\"top-3\"\n      @close-auto-focus=\"handleFocusOutside\"\n      @closed=\"handleClosed\"\n      :close-disabled=\"submitting\"\n      @escape-key-down=\"escapeKeyDown\"\n      @focus-outside=\"handleFocusOutside\"\n      @interact-outside=\"interactOutside\"\n      @open-auto-focus=\"handleOpenAutoFocus\"\n      @opened=\"handleOpened\"\n      @pointer-down-outside=\"pointerDownOutside\"\n    >\n      <DialogHeader\n        ref=\"headerRef\"\n        :class=\"\n          cn(\n            'px-5 py-4',\n            {\n              'border-b': bordered,\n              hidden: !header,\n              'cursor-move select-none': shouldDraggable,\n            },\n            headerClass,\n          )\n        \"\n      >\n        <DialogTitle v-if=\"title\" class=\"text-left\">\n          <slot name=\"title\">\n            {{ title }}\n\n            <slot v-if=\"titleTooltip\" name=\"titleTooltip\">\n              <VbenHelpTooltip trigger-class=\"pb-1\">\n                {{ titleTooltip }}\n              </VbenHelpTooltip>\n            </slot>\n          </slot>\n        </DialogTitle>\n        <DialogDescription v-if=\"description\">\n          <slot name=\"description\">\n            {{ description }}\n          </slot>\n        </DialogDescription>\n        <VisuallyHidden v-if=\"!title || !description\">\n          <DialogTitle v-if=\"!title\" />\n          <DialogDescription v-if=\"!description\" />\n        </VisuallyHidden>\n      </DialogHeader>\n      <div\n        ref=\"wrapperRef\"\n        :class=\"\n          cn('relative min-h-40 flex-1 overflow-y-auto p-3', contentClass, {\n            'pointer-events-none': showLoading || submitting,\n          })\n        \"\n      >\n        <slot></slot>\n      </div>\n      <VbenLoading v-if=\"showLoading || submitting\" spinning />\n      <VbenIconButton\n        v-if=\"fullscreenButton\"\n        class=\"absolute top-3 right-10 flex-center hidden size-6 rounded-full px-1 text-lg text-foreground/80 opacity-70 transition-opacity hover:bg-accent hover:text-accent-foreground hover:opacity-100 focus:outline-hidden disabled:pointer-events-none sm:block\"\n        @click=\"handleFullscreen\"\n      >\n        <Shrink v-if=\"fullscreen\" class=\"size-3.5\" />\n        <Expand v-else class=\"size-3.5\" />\n      </VbenIconButton>\n\n      <DialogFooter\n        ref=\"footerRef\"\n        v-if=\"showFooter\"\n        :class=\"\n          cn(\n            'flex-row items-center justify-end p-2',\n            {\n              'border-t': bordered,\n            },\n            footerClass,\n          )\n        \"\n      >\n        <slot name=\"prepend-footer\"></slot>\n        <slot name=\"footer\">\n          <component\n            :is=\"components.DefaultButton || VbenButton\"\n            v-if=\"showCancelButton\"\n            variant=\"ghost\"\n            :disabled=\"submitting\"\n            @click=\"() => modalApi?.onCancel()\"\n          >\n            <slot name=\"cancelText\">\n              {{ cancelText || $t('cancel') }}\n            </slot>\n          </component>\n          <slot name=\"center-footer\"></slot>\n          <component\n            :is=\"components.PrimaryButton || VbenButton\"\n            v-if=\"showConfirmButton\"\n            :disabled=\"confirmDisabled\"\n            :loading=\"confirmLoading || submitting\"\n            @click=\"() => modalApi?.onConfirm()\"\n          >\n            <slot name=\"confirmText\">\n              {{ confirmText || $t('confirm') }}\n            </slot>\n          </component>\n        </slot>\n        <slot name=\"append-footer\"></slot>\n      </DialogFooter>\n    </DialogContent>\n  </Dialog>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/modal/use-modal-draggable.ts",
    "content": "/**\n * @copy https://github.com/element-plus/element-plus/blob/dev/packages/hooks/use-draggable/index.ts\n * 调整部分细节\n */\n\nimport type { ComputedRef, Ref } from 'vue';\n\nimport { onBeforeUnmount, onMounted, reactive, ref, watchEffect } from 'vue';\n\nimport { unrefElement } from '@vueuse/core';\n\nexport function useModalDraggable(\n  targetRef: Ref<HTMLElement | undefined>,\n  dragRef: Ref<HTMLElement | undefined>,\n  draggable: ComputedRef<boolean>,\n  containerSelector?: ComputedRef<string | undefined>,\n  centered?: ComputedRef<boolean>,\n) {\n  const transform = reactive({\n    offsetX: 0,\n    offsetY: 0,\n  });\n\n  const dragging = ref(false);\n\n  const onMousedown = (e: MouseEvent) => {\n    const downX = e.clientX;\n    const downY = e.clientY;\n\n    if (!targetRef.value) {\n      return;\n    }\n\n    const targetRect = targetRef.value.getBoundingClientRect();\n    const { offsetX, offsetY } = transform;\n    const targetLeft = targetRect.left;\n    const targetTop = targetRect.top;\n    const targetWidth = targetRect.width;\n    const targetHeight = targetRect.height;\n\n    let containerRect: DOMRect | null = null;\n\n    if (containerSelector?.value) {\n      const container = document.querySelector(containerSelector.value);\n      if (container) {\n        containerRect = container.getBoundingClientRect();\n      }\n    }\n\n    let maxLeft, maxTop, minLeft, minTop;\n    if (containerRect) {\n      minLeft = containerRect.left - targetLeft + offsetX;\n      maxLeft = containerRect.right - targetLeft - targetWidth + offsetX;\n      minTop = containerRect.top - targetTop + offsetY;\n      maxTop = containerRect.bottom - targetTop - targetHeight + offsetY;\n    } else {\n      const docElement = document.documentElement;\n      const clientWidth = docElement.clientWidth;\n      const clientHeight = docElement.clientHeight;\n      minLeft = -targetLeft + offsetX;\n      minTop = -targetTop + offsetY;\n      maxLeft = clientWidth - targetLeft - targetWidth + offsetX;\n      maxTop = clientHeight - targetTop - targetHeight + offsetY;\n    }\n\n    const onMousemove = (e: MouseEvent) => {\n      let moveX = offsetX + e.clientX - downX;\n      let moveY = offsetY + e.clientY - downY;\n\n      moveX = Math.min(Math.max(moveX, minLeft), maxLeft);\n      moveY = Math.min(Math.max(moveY, minTop), maxTop);\n\n      transform.offsetX = moveX;\n      transform.offsetY = moveY;\n\n      if (targetRef.value) {\n        const isCentered = centered?.value;\n        targetRef.value.style.transform = isCentered\n          ? `translate(${moveX}px, calc(-50% + ${moveY}px))`\n          : `translate(${moveX}px, ${moveY}px)`;\n        dragging.value = true;\n      }\n    };\n\n    const onMouseup = () => {\n      dragging.value = false;\n      document.removeEventListener('mousemove', onMousemove);\n      document.removeEventListener('mouseup', onMouseup);\n    };\n\n    document.addEventListener('mousemove', onMousemove);\n    document.addEventListener('mouseup', onMouseup);\n  };\n\n  const onDraggable = () => {\n    const dragDom = unrefElement(dragRef);\n    if (dragDom && targetRef.value) {\n      dragDom.addEventListener('mousedown', onMousedown);\n    }\n  };\n\n  const offDraggable = () => {\n    const dragDom = unrefElement(dragRef);\n    if (dragDom && targetRef.value) {\n      dragDom.removeEventListener('mousedown', onMousedown);\n    }\n  };\n\n  const resetPosition = () => {\n    transform.offsetX = 0;\n    transform.offsetY = 0;\n\n    const target = unrefElement(targetRef);\n    if (target) {\n      target.style.transform = '';\n    }\n  };\n\n  onMounted(() => {\n    watchEffect(() => {\n      if (draggable.value) {\n        onDraggable();\n      } else {\n        offDraggable();\n      }\n    });\n  });\n\n  onBeforeUnmount(() => {\n    offDraggable();\n  });\n\n  return {\n    dragging,\n    resetPosition,\n    transform,\n  };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts",
    "content": "import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal';\n\nimport {\n  defineComponent,\n  h,\n  inject,\n  nextTick,\n  provide,\n  reactive,\n  ref,\n} from 'vue';\n\nimport { useStore } from '@vben-core/shared/store';\n\nimport { ModalApi } from './modal-api';\nimport VbenModal from './modal.vue';\n\nconst USER_MODAL_INJECT_KEY = Symbol('VBEN_MODAL_INJECT');\n\nconst DEFAULT_MODAL_PROPS: Partial<ModalProps> = {};\n\nexport function setDefaultModalProps(props: Partial<ModalProps>) {\n  Object.assign(DEFAULT_MODAL_PROPS, props);\n}\n\nexport function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(\n  options: ModalApiOptions = {},\n) {\n  // Modal一般会抽离出来，所以如果有传入 connectedComponent，则表示为外部调用，与内部组件进行连接\n  // 外部的Modal通过provide/inject传递api\n\n  const { connectedComponent } = options;\n  if (connectedComponent) {\n    const extendedApi = reactive({});\n    const isModalReady = ref(true);\n    const Modal = defineComponent(\n      (props: TParentModalProps, { attrs, slots }) => {\n        provide(USER_MODAL_INJECT_KEY, {\n          extendApi(api: ExtendedModalApi) {\n            // 不能直接给 reactive 赋值，会丢失响应\n            // 不能用 Object.assign,会丢失 api 的原型函数\n            Object.setPrototypeOf(extendedApi, api);\n          },\n          consumed: false,\n          options,\n          async reCreateModal() {\n            isModalReady.value = false;\n            await nextTick();\n            isModalReady.value = true;\n          },\n        });\n        checkProps(extendedApi as ExtendedModalApi, {\n          ...props,\n          ...attrs,\n          ...slots,\n        });\n        return () =>\n          h(\n            isModalReady.value ? connectedComponent : 'div',\n            {\n              ...props,\n              ...attrs,\n            },\n            slots,\n          );\n      },\n      // eslint-disable-next-line vue/one-component-per-file\n      {\n        name: 'VbenParentModal',\n        inheritAttrs: false,\n      },\n    );\n\n    return [Modal, extendedApi as ExtendedModalApi] as const;\n  }\n\n  let injectData = inject<any>(USER_MODAL_INJECT_KEY, {});\n  // 这个数据已经被使用了，说明这个弹窗是嵌套的弹窗，不应该merge上层的配置\n  if (injectData.consumed) {\n    injectData = {};\n  } else {\n    injectData.consumed = true;\n  }\n\n  const mergedOptions = {\n    ...DEFAULT_MODAL_PROPS,\n    ...injectData.options,\n    ...options,\n  } as ModalApiOptions;\n\n  mergedOptions.onOpenChange = (isOpen: boolean) => {\n    options.onOpenChange?.(isOpen);\n    injectData.options?.onOpenChange?.(isOpen);\n  };\n\n  const onClosed = mergedOptions.onClosed;\n  mergedOptions.onClosed = () => {\n    onClosed?.();\n    if (mergedOptions.destroyOnClose) {\n      injectData.consumed = false;\n      injectData.reCreateModal?.();\n    }\n  };\n\n  const api = new ModalApi(mergedOptions);\n\n  const extendedApi: ExtendedModalApi = api as never;\n\n  extendedApi.useStore = (selector) => {\n    return useStore(api.store, selector);\n  };\n\n  const Modal = defineComponent(\n    (props: ModalProps, { attrs, slots }) => {\n      return () =>\n        h(\n          VbenModal,\n          {\n            ...props,\n            ...attrs,\n            modalApi: extendedApi,\n          },\n          slots,\n        );\n    },\n    // eslint-disable-next-line vue/one-component-per-file\n    {\n      name: 'VbenModal',\n      inheritAttrs: false,\n    },\n  );\n  injectData.extendApi?.(extendedApi);\n\n  return [Modal, extendedApi] as const;\n}\n\nasync function checkProps(api: ExtendedModalApi, attrs: Record<string, any>) {\n  if (!attrs || Object.keys(attrs).length === 0) {\n    return;\n  }\n  await nextTick();\n\n  const state = api?.store?.state;\n\n  if (!state) {\n    return;\n  }\n\n  const stateKeys = new Set(Object.keys(state));\n\n  for (const attr of Object.keys(attrs)) {\n    if (stateKeys.has(attr) && !['class'].includes(attr)) {\n      // connectedComponent存在时，不要传入Modal的props，会造成复杂度提升，如果你需要修改Modal的props，请使用 useModal 或者api\n      console.warn(\n        `[Vben Modal]: When 'connectedComponent' exists, do not set props or slots '${attr}', which will increase complexity. If you need to modify the props of Modal, please use useVbenModal or api.`,\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/popup-ui/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\nimport Vue from 'unplugin-vue/rolldown';\n\nexport default defineConfig({\n  clean: true,\n  deps: {\n    skipNodeModulesBundle: true,\n  },\n  dts: {\n    vue: true,\n  },\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n    js: '.mjs',\n  }),\n  platform: 'neutral',\n  plugins: [Vue({ isProduction: true })],\n  unbundle: true,\n});\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/components.json",
    "content": "{\n  \"$schema\": \"https://shadcn-vue.com/schema.json\",\n  \"style\": \"new-york\",\n  \"typescript\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"src/assets/index.css\",\n    \"baseColor\": \"slate\",\n    \"cssVariables\": true\n  },\n  \"aliases\": {\n    \"components\": \"@vben-core/shadcn-ui/components\",\n    \"utils\": \"@vben-core/shared/utils\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/package.json",
    "content": "{\n  \"name\": \"@vben-core/shadcn-ui\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@vben-core/uikit/shadcn-ui\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"files\": [\n    \"components.json\",\n    \"src\"\n  ],\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"main\": \"./src/index.ts\",\n  \"module\": \"./src/index.ts\",\n  \"types\": \"./src/index.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"types\": \"./src/index.ts\",\n        \"development\": \"./src/index.ts\",\n        \"default\": \"./src/index.ts\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/composables\": \"workspace:*\",\n    \"@vben-core/design\": \"workspace:*\",\n    \"@vben-core/icons\": \"workspace:*\",\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben-core/typings\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"class-variance-authority\": \"catalog:\",\n    \"lucide-vue-next\": \"catalog:\",\n    \"reka-ui\": \"catalog:\",\n    \"vee-validate\": \"catalog:\",\n    \"vue\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/assets/index.css",
    "content": "@reference \"@vben/tailwind-config/theme\";\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/avatar/avatar.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  AvatarFallbackProps,\n  AvatarImageProps,\n  AvatarRootProps,\n} from 'reka-ui';\n\nimport type { CSSProperties } from 'vue';\n\nimport type { ClassType } from '@vben-core/typings';\n\nimport { computed } from 'vue';\n\nimport { Avatar, AvatarFallback, AvatarImage } from '../../ui';\n\ninterface Props extends AvatarFallbackProps, AvatarImageProps, AvatarRootProps {\n  alt?: string;\n  class?: ClassType;\n  dot?: boolean;\n  dotClass?: ClassType;\n  fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n  size?: number;\n}\n\ndefineOptions({\n  inheritAttrs: false,\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  alt: 'avatar',\n  as: 'button',\n  dot: false,\n  dotClass: 'bg-green-500',\n  fit: 'cover',\n});\n\nconst imageStyle = computed<CSSProperties>(() => {\n  const { fit } = props;\n  if (fit) {\n    return { objectFit: fit };\n  }\n  return {};\n});\n\nconst text = computed(() => {\n  return props.alt.slice(-2).toUpperCase();\n});\n\nconst rootStyle = computed(() => {\n  return props.size !== undefined && props.size > 0\n    ? {\n        height: `${props.size}px`,\n        width: `${props.size}px`,\n      }\n    : {};\n});\n</script>\n\n<template>\n  <div\n    :class=\"props.class\"\n    :style=\"rootStyle\"\n    class=\"relative flex shrink-0 items-center\"\n  >\n    <Avatar :class=\"props.class\" class=\"size-full\">\n      <AvatarImage :alt=\"alt\" :src=\"src\" :style=\"imageStyle\" />\n      <AvatarFallback>{{ text }}</AvatarFallback>\n    </Avatar>\n    <span\n      v-if=\"dot\"\n      :class=\"dotClass\"\n      class=\"border-background absolute right-0 bottom-0 size-3 rounded-full border-2\"\n    >\n    </span>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/avatar/index.ts",
    "content": "export { default as VbenAvatar } from './avatar.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/back-top/back-top.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { BacktopProps } from './backtop';\n\nimport { computed } from 'vue';\n\nimport { ArrowUpToLine } from '@vben-core/icons';\n\nimport { VbenButton } from '../button';\nimport { useBackTop } from './use-backtop';\n\ninterface Props extends BacktopProps {}\n\ndefineOptions({ name: 'BackTop' });\n\nconst props = withDefaults(defineProps<Props>(), {\n  bottom: 20,\n  isGroup: false,\n  right: 24,\n  target: '',\n  visibilityHeight: 200,\n});\n\nconst backTopStyle = computed(() => ({\n  bottom: `${props.bottom}px`,\n  right: `${props.right}px`,\n}));\n\nconst { handleClick, visible } = useBackTop(props);\n</script>\n<template>\n  <transition name=\"fade-down\">\n    <VbenButton\n      v-if=\"visible\"\n      :style=\"backTopStyle\"\n      class=\"data z-popup bg-background shadow-float hover:bg-heavy dark:bg-accent dark:hover:bg-heavy fixed bottom-10 size-10 rounded-full duration-500\"\n      size=\"icon\"\n      variant=\"icon\"\n      @click=\"handleClick\"\n    >\n      <ArrowUpToLine class=\"size-4\" />\n    </VbenButton>\n  </transition>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/back-top/backtop.ts",
    "content": "export const backtopProps = {\n  /**\n   * @zh_CN bottom distance.\n   */\n  bottom: {\n    default: 40,\n    type: Number,\n  },\n  /**\n   * @zh_CN right distance.\n   */\n  right: {\n    default: 40,\n    type: Number,\n  },\n  /**\n   * @zh_CN the target to trigger scroll.\n   */\n  target: {\n    default: '',\n    type: String,\n  },\n  /**\n   * @zh_CN the button will not show until the scroll height reaches this value.\n   */\n  visibilityHeight: {\n    default: 200,\n    type: Number,\n  },\n} as const;\n\nexport interface BacktopProps {\n  bottom?: number;\n  isGroup?: boolean;\n  right?: number;\n  target?: string;\n  visibilityHeight?: number;\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/back-top/index.ts",
    "content": "export { default as VbenBackTop } from './back-top.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/back-top/use-backtop.ts",
    "content": "import type { BacktopProps } from './backtop';\n\nimport { onMounted, ref, shallowRef } from 'vue';\n\nimport { useEventListener, useThrottleFn } from '@vueuse/core';\n\nexport const useBackTop = (props: BacktopProps) => {\n  const el = shallowRef<HTMLElement>();\n  const container = shallowRef<Document | HTMLElement>();\n  const visible = ref(false);\n\n  const handleScroll = () => {\n    if (el.value) {\n      visible.value = el.value.scrollTop >= (props?.visibilityHeight ?? 0);\n    }\n  };\n\n  const handleClick = () => {\n    el.value?.scrollTo({ behavior: 'smooth', top: 0 });\n  };\n\n  const handleScrollThrottled = useThrottleFn(handleScroll, 300, true);\n\n  useEventListener(container, 'scroll', handleScrollThrottled);\n  onMounted(() => {\n    container.value = document;\n    el.value = document.documentElement;\n\n    if (props.target) {\n      el.value = document.querySelector<HTMLElement>(props.target) ?? undefined;\n\n      if (!el.value) {\n        throw new Error(`target does not exist: ${props.target}`);\n      }\n      container.value = el.value;\n    }\n    // Give visible an initial value, fix #13066\n    handleScroll();\n  });\n\n  return {\n    handleClick,\n    visible,\n  };\n};\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { BreadcrumbProps } from './types';\n\nimport { VbenIcon } from '../icon';\n\ninterface Props extends BreadcrumbProps {}\n\ndefineOptions({ name: 'Breadcrumb' });\nconst { breadcrumbs, showIcon } = defineProps<Props>();\n\nconst emit = defineEmits<{ select: [string] }>();\n\nfunction handleClick(index: number, path?: string) {\n  if (!path || index === breadcrumbs.length - 1) {\n    return;\n  }\n  emit('select', path);\n}\n</script>\n<template>\n  <ul class=\"flex\">\n    <TransitionGroup name=\"breadcrumb-transition\">\n      <template\n        v-for=\"(item, index) in breadcrumbs\"\n        :key=\"`${item.path}-${item.title}-${index}`\"\n      >\n        <li>\n          <a\n            href=\"javascript:void 0\"\n            @click.stop=\"handleClick(index, item.path)\"\n          >\n            <span class=\"flex-center z-10 h-full\">\n              <VbenIcon\n                v-if=\"showIcon\"\n                :icon=\"item.icon\"\n                class=\"mr-1 size-4 shrink-0\"\n              />\n              <span\n                :class=\"{\n                  'text-foreground font-normal':\n                    index === breadcrumbs.length - 1,\n                }\"\n                >{{ item.title }}\n              </span>\n            </span>\n          </a>\n        </li>\n      </template>\n    </TransitionGroup>\n  </ul>\n</template>\n<style scoped>\n@reference \"@vben/tailwind-config/theme\";\n\nli {\n  @apply h-7;\n}\n\nli a {\n  @apply bg-accent text-muted-foreground relative mr-9 flex h-7 items-center py-0 pr-2 pl-1.25 text-[13px];\n}\n\nli a > span {\n  @apply -ml-3;\n}\n\nli:first-child a > span {\n  @apply -ml-1;\n}\n\nli:first-child a {\n  @apply rounded-l-sm pl-3.75;\n}\n\nli:first-child a::before {\n  @apply border-none;\n}\n\nli:last-child a {\n  @apply rounded-r-sm pr-3.75;\n}\n\nli:last-child a::after {\n  @apply border-none;\n}\n\nli a::before,\nli a::after {\n  @apply border-accent absolute top-0 h-0 w-0 border-14 border-solid content-[''];\n}\n\nli a::before {\n  @apply -left-7 z-10 border-l-transparent;\n}\n\nli a::after {\n  @apply border-l-accent left-full border-transparent;\n}\n\nli:not(:last-child) a:hover {\n  @apply bg-accent-hover;\n}\n\nli:not(:last-child) a:hover::before {\n  @apply border-accent-hover border-l-transparent;\n}\n\nli:not(:last-child) a:hover::after {\n  @apply border-l-accent-hover;\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-view.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { BreadcrumbProps } from './types';\n\nimport { useForwardPropsEmits } from 'reka-ui';\n\nimport BreadcrumbBackground from './breadcrumb-background.vue';\nimport Breadcrumb from './breadcrumb.vue';\n\ninterface Props extends BreadcrumbProps {\n  class?: any;\n}\n\nconst props = withDefaults(defineProps<Props>(), {});\n\nconst emit = defineEmits<{ select: [string] }>();\n\nconst forward = useForwardPropsEmits(props, emit);\n</script>\n<template>\n  <Breadcrumb\n    v-if=\"styleType === 'normal'\"\n    v-bind=\"forward\"\n    class=\"vben-breadcrumb\"\n  />\n  <BreadcrumbBackground\n    v-if=\"styleType === 'background'\"\n    v-bind=\"forward\"\n    class=\"vben-breadcrumb\"\n  />\n</template>\n<style lang=\"scss\" scoped>\n/** 修复全局引入Antd时，ol和ul的默认样式会被修改的问题 */\n.vben-breadcrumb {\n  :deep(ol),\n  :deep(ul) {\n    margin-bottom: 0;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { BreadcrumbProps } from './types';\n\nimport { ChevronDown } from '@vben-core/icons';\n\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from '../../ui';\nimport { VbenIcon } from '../icon';\n\ninterface Props extends BreadcrumbProps {}\n\ndefineOptions({ name: 'Breadcrumb' });\nwithDefaults(defineProps<Props>(), {\n  showIcon: false,\n});\n\nconst emit = defineEmits<{ select: [string] }>();\n\nfunction handleClick(path?: string) {\n  if (!path) {\n    return;\n  }\n  emit('select', path);\n}\n</script>\n<template>\n  <Breadcrumb>\n    <BreadcrumbList>\n      <TransitionGroup name=\"breadcrumb-transition\">\n        <template\n          v-for=\"(item, index) in breadcrumbs\"\n          :key=\"`${item.path}-${item.title}-${index}`\"\n        >\n          <BreadcrumbItem>\n            <div v-if=\"item.items?.length ?? 0 > 0\">\n              <DropdownMenu>\n                <DropdownMenuTrigger class=\"flex items-center gap-1\">\n                  <VbenIcon v-if=\"showIcon\" :icon=\"item.icon\" class=\"size-5\" />\n                  {{ item.title }}\n                  <ChevronDown class=\"size-4\" />\n                </DropdownMenuTrigger>\n                <DropdownMenuContent align=\"start\">\n                  <template\n                    v-for=\"menuItem in item.items\"\n                    :key=\"`sub-${menuItem.path}`\"\n                  >\n                    <DropdownMenuItem @click.stop=\"handleClick(menuItem.path)\">\n                      {{ menuItem.title }}\n                    </DropdownMenuItem>\n                  </template>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </div>\n            <BreadcrumbLink\n              v-else-if=\"index !== breadcrumbs.length - 1\"\n              href=\"javascript:void 0\"\n              @click.stop=\"handleClick(item.path)\"\n            >\n              <div class=\"flex-center\">\n                <VbenIcon\n                  v-if=\"showIcon\"\n                  :class=\"{ 'size-5': item.isHome }\"\n                  :icon=\"item.icon\"\n                  class=\"mr-1 size-4\"\n                />\n                {{ item.title }}\n              </div>\n            </BreadcrumbLink>\n            <BreadcrumbPage v-else>\n              <div class=\"flex-center\">\n                <VbenIcon\n                  v-if=\"showIcon\"\n                  :class=\"{ 'size-5': item.isHome }\"\n                  :icon=\"item.icon\"\n                  class=\"mr-1 size-4\"\n                />\n                {{ item.title }}\n              </div>\n            </BreadcrumbPage>\n            <BreadcrumbSeparator\n              v-if=\"index < breadcrumbs.length - 1 && !item.isHome\"\n            />\n          </BreadcrumbItem>\n        </template>\n      </TransitionGroup>\n    </BreadcrumbList>\n  </Breadcrumb>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/index.ts",
    "content": "export { default as VbenBreadcrumbView } from './breadcrumb-view.vue';\n\nexport type * from './types';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/types.ts",
    "content": "import type { Component } from 'vue';\n\nimport type { BreadcrumbStyleType } from '@vben-core/typings';\n\nexport interface IBreadcrumb {\n  icon?: Component | string;\n  isHome?: boolean;\n  items?: IBreadcrumb[];\n  path?: string;\n  title?: string;\n}\n\nexport interface BreadcrumbProps {\n  breadcrumbs: IBreadcrumb[];\n  showIcon?: boolean;\n  styleType?: BreadcrumbStyleType;\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/button/button-group.vue",
    "content": "<script lang=\"ts\" setup>\nimport { cn } from '@vben-core/shared/utils';\n\ndefineOptions({ name: 'VbenButtonGroup' });\n\nwithDefaults(\n  defineProps<{\n    border?: boolean;\n    gap?: number;\n    size?: 'large' | 'middle' | 'small';\n  }>(),\n  { border: false, gap: 0, size: 'middle' },\n);\n</script>\n<template>\n  <div\n    :class=\"\n      cn(\n        'vben-button-group rounded-md',\n        `size-${size}`,\n        gap ? 'with-gap' : 'no-gap',\n        $attrs.class as string,\n      )\n    \"\n    :style=\"{ gap: gap ? `${gap}px` : '0px' }\"\n  >\n    <slot></slot>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n.vben-button-group {\n  display: inline-flex;\n\n  &.size-large :deep(button) {\n    height: 2.25rem;\n    padding: 0.5rem 0.75rem;\n    font-size: 0.875rem;\n    line-height: 1.25rem;\n\n    .icon-wrapper {\n      margin-right: 0.4rem;\n\n      svg {\n        width: 1rem;\n        height: 1rem;\n      }\n    }\n  }\n\n  &.size-middle :deep(button) {\n    height: 2rem;\n    padding: 0.25rem 0.5rem;\n    font-size: 0.75rem;\n    line-height: 1rem;\n\n    .icon-wrapper {\n      margin-right: 0.2rem;\n\n      svg {\n        width: 0.75rem;\n        height: 0.75rem;\n      }\n    }\n  }\n\n  &.size-small :deep(button) {\n    height: 1.75rem;\n    padding: 0.2rem 0.4rem;\n    font-size: 0.65rem;\n    line-height: 0.75rem;\n\n    .icon-wrapper {\n      margin-right: 0.1rem;\n\n      svg {\n        width: 0.65rem;\n        height: 0.65rem;\n      }\n    }\n  }\n\n  &.no-gap > :deep(button):nth-of-type(1) {\n    border-radius: calc(var(--radius) - 2px) 0 0 calc(var(--radius) - 2px);\n  }\n\n  &.no-gap > :deep(button):last-of-type {\n    border-radius: 0 calc(var(--radius) - 2px) calc(var(--radius) - 2px) 0;\n  }\n\n  &.no-gap {\n    :deep(button + button) {\n      border-left-width: 0;\n      border-radius: 0;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/button/button.ts",
    "content": "import type { AsTag } from 'reka-ui';\n\nimport type { Component } from 'vue';\n\nimport type { ButtonVariants, ButtonVariantSize } from '../../ui';\n\nexport interface VbenButtonProps {\n  /**\n   * The element or component this component should render as. Can be overwrite by `asChild`\n   * @defaultValue \"div\"\n   */\n  as?: AsTag | Component;\n  /**\n   * Change the default rendered element for the one passed as a child, merging their props and behavior.\n   *\n   * Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details.\n   */\n  asChild?: boolean;\n  class?: any;\n  disabled?: boolean;\n  loading?: boolean;\n  size?: ButtonVariantSize;\n  variant?: ButtonVariants;\n}\n\nexport type CustomRenderType = (() => Component | string) | string;\n\nexport type ValueType = boolean | number | string;\n\nexport interface VbenButtonGroupProps extends Pick<\n  VbenButtonProps,\n  'disabled'\n> {\n  /** 单选模式下允许清除选中 */\n  allowClear?: boolean;\n  /** 值改变前的回调 */\n  beforeChange?: (\n    value: ValueType,\n    isChecked: boolean,\n  ) => boolean | PromiseLike<boolean | undefined> | undefined;\n  /** 按钮样式 */\n  btnClass?: any;\n  /** 按钮间隔距离 */\n  gap?: number;\n  /** 多选模式下限制最多选择的数量。0表示不限制 */\n  maxCount?: number;\n  /** 是否允许多选 */\n  multiple?: boolean;\n  /** 选项 */\n  options?: { [key: string]: any; label: CustomRenderType; value: ValueType }[];\n  /** 显示图标 */\n  showIcon?: boolean;\n  /** 尺寸 */\n  size?: 'large' | 'middle' | 'small';\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/button/button.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VbenButtonProps } from './button';\n\nimport { computed } from 'vue';\n\nimport { LoaderCircle } from '@vben-core/icons';\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Primitive } from 'reka-ui';\n\nimport { buttonVariants } from '../../ui';\n\ninterface Props extends VbenButtonProps {}\n\nconst props = withDefaults(defineProps<Props>(), {\n  as: 'button',\n  class: '',\n  disabled: false,\n  loading: false,\n  size: 'default',\n  variant: 'default',\n});\n\nconst isDisabled = computed(() => {\n  return props.disabled || props.loading;\n});\n</script>\n\n<template>\n  <Primitive\n    :as=\"as\"\n    :as-child=\"asChild\"\n    :class=\"cn(buttonVariants({ variant, size }), props.class)\"\n    :disabled=\"isDisabled\"\n  >\n    <LoaderCircle\n      v-if=\"loading\"\n      class=\"text-md mr-2 size-4 shrink-0 animate-spin\"\n    />\n    <slot></slot>\n  </Primitive>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/button/check-button-group.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Arrayable } from '@vueuse/core';\n\nimport type { ValueType, VbenButtonGroupProps } from './button';\n\nimport { computed, ref, watch } from 'vue';\n\nimport { Circle, CircleCheckBig, LoaderCircle } from '@vben-core/icons';\nimport { cn, isFunction } from '@vben-core/shared/utils';\n\nimport { objectOmit } from '@vueuse/core';\n\nimport { VbenRenderContent } from '../render-content';\nimport VbenButtonGroup from './button-group.vue';\nimport Button from './button.vue';\n\nconst props = withDefaults(defineProps<VbenButtonGroupProps>(), {\n  gap: 0,\n  multiple: false,\n  showIcon: true,\n  size: 'middle',\n  allowClear: false,\n  maxCount: 0,\n});\nconst emit = defineEmits(['btnClick']);\nconst btnDefaultProps = computed(() => {\n  return {\n    ...objectOmit(props, ['options', 'btnClass', 'size', 'disabled']),\n    class: cn(props.btnClass),\n  };\n});\nconst modelValue = defineModel<Arrayable<ValueType> | undefined>();\n\nconst innerValue = ref<Array<ValueType>>([]);\nconst loadingValues = ref<Array<ValueType>>([]);\nwatch(\n  () => props.multiple,\n  (val) => {\n    if (val) {\n      modelValue.value = innerValue.value;\n    } else {\n      modelValue.value =\n        innerValue.value.length > 0 ? innerValue.value[0] : undefined;\n    }\n  },\n);\n\nwatch(\n  () => modelValue.value,\n  (val) => {\n    if (Array.isArray(val)) {\n      const arrVal = val.filter((v) => v !== undefined);\n      if (arrVal.length > 0) {\n        innerValue.value = props.multiple\n          ? [...arrVal]\n          : [arrVal[0] as ValueType];\n      } else {\n        innerValue.value = [];\n      }\n    } else {\n      innerValue.value = val === undefined ? [] : [val as ValueType];\n    }\n  },\n  { deep: true, immediate: true },\n);\n\nasync function onBtnClick(value: ValueType) {\n  if (props.beforeChange && isFunction(props.beforeChange)) {\n    try {\n      loadingValues.value.push(value);\n      const canChange = await props.beforeChange(\n        value,\n        !innerValue.value.includes(value),\n      );\n      if (canChange === false) {\n        return;\n      }\n    } finally {\n      loadingValues.value.splice(loadingValues.value.indexOf(value), 1);\n    }\n  }\n\n  if (props.multiple) {\n    if (innerValue.value.includes(value)) {\n      innerValue.value = innerValue.value.filter((item) => item !== value);\n    } else {\n      if (props.maxCount > 0 && innerValue.value.length >= props.maxCount) {\n        innerValue.value = innerValue.value.slice(0, props.maxCount - 1);\n      }\n      innerValue.value.push(value);\n    }\n    modelValue.value = innerValue.value;\n  } else {\n    if (props.allowClear && innerValue.value.includes(value)) {\n      innerValue.value = [];\n      modelValue.value = undefined;\n      emit('btnClick', undefined);\n      return;\n    } else {\n      innerValue.value = [value];\n      modelValue.value = value;\n    }\n  }\n  emit('btnClick', value);\n}\n</script>\n<template>\n  <VbenButtonGroup\n    :size=\"props.size\"\n    :gap=\"props.gap\"\n    class=\"vben-check-button-group\"\n  >\n    <Button\n      v-for=\"(btn, index) in props.options\"\n      :key=\"index\"\n      :class=\"cn('border', props.btnClass)\"\n      :disabled=\"\n        props.disabled ||\n        loadingValues.includes(btn.value) ||\n        (!props.multiple && loadingValues.length > 0)\n      \"\n      v-bind=\"btnDefaultProps\"\n      :variant=\"innerValue.includes(btn.value) ? 'default' : 'outline'\"\n      @click=\"onBtnClick(btn.value)\"\n      type=\"button\"\n    >\n      <div class=\"icon-wrapper\" v-if=\"props.showIcon\">\n        <slot\n          name=\"icon\"\n          :loading=\"loadingValues.includes(btn.value)\"\n          :checked=\"innerValue.includes(btn.value)\"\n        >\n          <LoaderCircle\n            class=\"animate-spin\"\n            v-if=\"loadingValues.includes(btn.value)\"\n          />\n          <CircleCheckBig v-else-if=\"innerValue.includes(btn.value)\" />\n          <Circle v-else />\n        </slot>\n      </div>\n      <slot name=\"option\" :label=\"btn.label\" :value=\"btn.value\" :data=\"btn\">\n        <VbenRenderContent :content=\"btn.label\" />\n      </slot>\n    </Button>\n  </VbenButtonGroup>\n</template>\n<style lang=\"scss\" scoped>\n.vben-check-button-group {\n  display: flex;\n  flex-wrap: wrap;\n\n  &:deep(.size-large) button {\n    .icon-wrapper {\n      margin-right: 0.3rem;\n\n      svg {\n        width: 1rem;\n        height: 1rem;\n      }\n    }\n  }\n\n  &:deep(.size-middle) button {\n    .icon-wrapper {\n      margin-right: 0.2rem;\n\n      svg {\n        width: 0.75rem;\n        height: 0.75rem;\n      }\n    }\n  }\n\n  &:deep(.size-small) button {\n    .icon-wrapper {\n      margin-right: 0.1rem;\n\n      svg {\n        width: 0.65rem;\n        height: 0.65rem;\n      }\n    }\n  }\n\n  &.no-gap > :deep(button):nth-of-type(1) {\n    border-right-width: 0;\n  }\n\n  &.no-gap {\n    :deep(button + button) {\n      margin-right: -1px;\n      border-left-width: 1px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/button/icon-button.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ButtonVariants } from '../../ui';\nimport type { VbenButtonProps } from './button';\n\nimport { computed, useSlots } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { VbenTooltip } from '../tooltip';\nimport VbenButton from './button.vue';\n\ninterface Props extends VbenButtonProps {\n  class?: any;\n  disabled?: boolean;\n  onClick?: () => void;\n  tooltip?: string;\n  tooltipDelayDuration?: number;\n  tooltipSide?: 'bottom' | 'left' | 'right' | 'top';\n  variant?: ButtonVariants;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  disabled: false,\n  onClick: () => {},\n  tooltipDelayDuration: 200,\n  tooltipSide: 'bottom',\n  variant: 'icon',\n});\n\nconst slots = useSlots();\n\nconst showTooltip = computed(() => !!slots.tooltip || !!props.tooltip);\n</script>\n\n<template>\n  <VbenButton\n    v-if=\"!showTooltip\"\n    :class=\"cn('rounded-full', props.class)\"\n    :disabled=\"disabled\"\n    :variant=\"variant\"\n    size=\"icon\"\n    @click=\"onClick\"\n  >\n    <slot></slot>\n  </VbenButton>\n\n  <VbenTooltip\n    v-else\n    :delay-duration=\"tooltipDelayDuration\"\n    :side=\"tooltipSide\"\n  >\n    <template #trigger>\n      <VbenButton\n        :class=\"cn('rounded-full', props.class)\"\n        :disabled=\"disabled\"\n        :variant=\"variant\"\n        size=\"icon\"\n        @click=\"onClick\"\n      >\n        <slot></slot>\n      </VbenButton>\n    </template>\n    <slot v-if=\"slots.tooltip\" name=\"tooltip\"> </slot>\n    <template v-else>\n      {{ tooltip }}\n    </template>\n  </VbenTooltip>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/button/index.ts",
    "content": "export type * from './button';\nexport { default as VbenButtonGroup } from './button-group.vue';\nexport { default as VbenButton } from './button.vue';\nexport { default as VbenCheckButtonGroup } from './check-button-group.vue';\nexport { default as VbenIconButton } from './icon-button.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/checkbox/checkbox.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CheckboxRootEmits, CheckboxRootProps } from 'reka-ui';\n\nimport { useId } from 'vue';\n\nimport { useForwardPropsEmits } from 'reka-ui';\n\nimport { Checkbox } from '../../ui/checkbox';\n\nconst props = defineProps<CheckboxRootProps & { indeterminate?: boolean }>();\n\nconst emits = defineEmits<CheckboxRootEmits>();\n\nconst checked = defineModel<boolean>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n\nconst id = useId();\n</script>\n\n<template>\n  <div class=\"flex items-center\">\n    <Checkbox v-bind=\"forwarded\" :id=\"id\" v-model=\"checked\" />\n    <label :for=\"id\" class=\"ml-2 cursor-pointer text-sm\"> <slot></slot> </label>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/checkbox/index.ts",
    "content": "export { default as VbenCheckbox } from './checkbox.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/context-menu/context-menu.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  ContextMenuContentProps,\n  ContextMenuRootEmits,\n  ContextMenuRootProps,\n} from 'reka-ui';\n\nimport type { ClassType } from '@vben-core/typings';\n\nimport type { IContextMenuItem } from './interface';\n\nimport { computed } from 'vue';\n\nimport { useForwardPropsEmits } from 'reka-ui';\n\nimport {\n  ContextMenu,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuTrigger,\n} from '../../ui/context-menu';\n\nconst props = defineProps<\n  ContextMenuRootProps & {\n    class?: ClassType;\n    contentClass?: ClassType;\n    contentProps?: ContextMenuContentProps;\n    handlerData?: Record<string, any>;\n    itemClass?: ClassType;\n    menus: (data: any) => IContextMenuItem[];\n  }\n>();\n\nconst emits = defineEmits<ContextMenuRootEmits>();\n\nconst delegatedProps = computed(() => {\n  const {\n    class: _cls,\n    contentClass: _,\n    contentProps: _cProps,\n    itemClass: _iCls,\n    ...delegated\n  } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n\nconst menusView = computed(() => {\n  return props.menus?.(props.handlerData);\n});\n\nfunction handleClick(menu: IContextMenuItem) {\n  if (menu.disabled) {\n    return;\n  }\n  menu?.handler?.(props.handlerData);\n}\n</script>\n\n<template>\n  <ContextMenu v-bind=\"forwarded\">\n    <ContextMenuTrigger as-child>\n      <slot></slot>\n    </ContextMenuTrigger>\n    <ContextMenuContent\n      :class=\"contentClass\"\n      v-bind=\"contentProps\"\n      class=\"side-content z-popup\"\n    >\n      <template v-for=\"menu in menusView\" :key=\"menu.key\">\n        <ContextMenuItem\n          v-if=\"!menu.hidden\"\n          :class=\"itemClass\"\n          :disabled=\"menu.disabled\"\n          :inset=\"menu.inset || !menu.icon\"\n          class=\"cursor-pointer\"\n          @click=\"handleClick(menu)\"\n        >\n          <component\n            :is=\"menu.icon\"\n            v-if=\"menu.icon\"\n            class=\"mr-2 size-4 text-lg\"\n          />\n\n          {{ menu.text }}\n          <ContextMenuShortcut v-if=\"menu.shortcut\">\n            {{ menu.shortcut }}\n          </ContextMenuShortcut>\n        </ContextMenuItem>\n        <ContextMenuSeparator v-if=\"menu.separator\" />\n      </template>\n    </ContextMenuContent>\n  </ContextMenu>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/context-menu/index.ts",
    "content": "export { default as VbenContextMenu } from './context-menu.vue';\n\nexport type * from './interface';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/context-menu/interface.ts",
    "content": "import type { Component } from 'vue';\n\ninterface IContextMenuItem {\n  /**\n   * @zh_CN 是否禁用\n   */\n  disabled?: boolean;\n  /**\n   * @zh_CN 点击事件处理\n   * @param data\n   */\n  handler?: (data: any) => void;\n  /**\n   * @zh_CN 是否隐藏\n   */\n  hidden?: boolean;\n  /**\n   * @zh_CN 图标\n   */\n  icon?: Component;\n  /**\n   * @zh_CN 是否显示图标\n   */\n  inset?: boolean;\n  /**\n   * @zh_CN 唯一标识\n   */\n  key: string;\n  /**\n   * @zh_CN 是否是分割线\n   */\n  separator?: boolean;\n  /**\n   * @zh_CN 快捷键\n   */\n  shortcut?: string;\n  /**\n   * @zh_CN 标题\n   */\n  text: string;\n}\nexport type { IContextMenuItem };\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/count-to-animator/count-to-animator.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed, onMounted, ref, unref, watch, watchEffect } from 'vue';\n\nimport { isNumber } from '@vben-core/shared/utils';\n\nimport { TransitionPresets, useTransition } from '@vueuse/core';\n\ninterface Props {\n  autoplay?: boolean;\n  color?: string;\n  decimal?: string;\n  decimals?: number;\n  duration?: number;\n  endVal?: number;\n  prefix?: string;\n  separator?: string;\n  startVal?: number;\n  suffix?: string;\n  transition?: keyof typeof TransitionPresets;\n  useEasing?: boolean;\n}\n\ndefineOptions({ name: 'CountToAnimator' });\n\nconst props = withDefaults(defineProps<Props>(), {\n  autoplay: true,\n  color: '',\n  decimal: '.',\n  decimals: 0,\n  duration: 1500,\n  endVal: 2021,\n  prefix: '',\n  separator: ',',\n  startVal: 0,\n  suffix: '',\n  transition: 'linear',\n  useEasing: true,\n});\n\nconst emit = defineEmits<{\n  finished: [];\n  /**\n   * @deprecated 请使用{@link finished}事件\n   */\n  onFinished: [];\n  /**\n   * @deprecated 请使用{@link started}事件\n   */\n  onStarted: [];\n  started: [];\n}>();\n\nconst source = ref(props.startVal);\nconst disabled = ref(false);\nlet outputValue = useTransition(source);\n\nconst value = computed(() => formatNumber(unref(outputValue)));\n\nwatchEffect(() => {\n  source.value = props.startVal;\n});\n\nwatch([() => props.startVal, () => props.endVal], () => {\n  if (props.autoplay) {\n    start();\n  }\n});\n\nonMounted(() => {\n  props.autoplay && start();\n});\n\nfunction start() {\n  run();\n  source.value = props.endVal;\n}\n\nfunction reset() {\n  source.value = props.startVal;\n  run();\n}\n\nfunction run() {\n  outputValue = useTransition(source, {\n    disabled,\n    duration: props.duration,\n    onFinished: () => {\n      emit('finished');\n      emit('onFinished');\n    },\n    onStarted: () => {\n      emit('started');\n      emit('onStarted');\n    },\n    ...(props.useEasing\n      ? { transition: TransitionPresets[props.transition] }\n      : {}),\n  });\n}\n\nfunction formatNumber(num: number | string) {\n  if (!num && num !== 0) {\n    return '';\n  }\n  const { decimal, decimals, prefix, separator, suffix } = props;\n  num = Number(num).toFixed(decimals);\n  num += '';\n\n  const x = num.split('.');\n  let x1 = x[0];\n  const x2 = x.length > 1 ? decimal + x[1] : '';\n\n  const rgx = /(\\d+)(\\d{3})/;\n  if (separator && !isNumber(separator) && x1) {\n    while (rgx.test(x1)) {\n      x1 = x1.replace(rgx, `$1${separator}$2`);\n    }\n  }\n  return prefix + x1 + x2 + suffix;\n}\n\ndefineExpose({ reset });\n</script>\n<template>\n  <span :style=\"{ color }\">\n    {{ value }}\n  </span>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/count-to-animator/index.ts",
    "content": "export { default as VbenCountToAnimator } from './count-to-animator.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/dropdown-menu/dropdown-menu.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  DropdownMenuProps,\n  VbenDropdownMenuItem as IDropdownMenuItem,\n} from './interface';\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '../../ui';\n\ninterface Props extends DropdownMenuProps {}\n\ndefineOptions({ name: 'DropdownMenu' });\nconst props = withDefaults(defineProps<Props>(), {});\n\nfunction handleItemClick(menu: IDropdownMenuItem) {\n  if (menu.disabled) {\n    return;\n  }\n  menu?.handler?.(props);\n}\n</script>\n<template>\n  <DropdownMenu>\n    <DropdownMenuTrigger class=\"flex h-full items-center gap-1\">\n      <slot></slot>\n    </DropdownMenuTrigger>\n    <DropdownMenuContent align=\"start\">\n      <DropdownMenuGroup>\n        <template v-for=\"menu in menus\" :key=\"menu.value\">\n          <DropdownMenuItem\n            :disabled=\"menu.disabled\"\n            class=\"text-foreground/80 data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground mb-1 cursor-pointer\"\n            @click=\"handleItemClick(menu)\"\n          >\n            <component :is=\"menu.icon\" v-if=\"menu.icon\" class=\"mr-2 size-4\" />\n            {{ menu.label }}\n          </DropdownMenuItem>\n          <DropdownMenuSeparator v-if=\"menu.separator\" class=\"bg-border\" />\n        </template>\n      </DropdownMenuGroup>\n    </DropdownMenuContent>\n  </DropdownMenu>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/dropdown-menu/dropdown-radio-menu.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { DropdownMenuProps } from './interface';\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from '../../ui';\n\ninterface Props extends DropdownMenuProps {}\n\ndefineOptions({ name: 'DropdownRadioMenu' });\nwithDefaults(defineProps<Props>(), {});\n\nconst modelValue = defineModel<string>();\n\nfunction handleItemClick(value: string) {\n  modelValue.value = value;\n}\n</script>\n<template>\n  <DropdownMenu>\n    <DropdownMenuTrigger as-child class=\"flex items-center gap-1\">\n      <slot></slot>\n    </DropdownMenuTrigger>\n    <DropdownMenuContent align=\"start\">\n      <DropdownMenuGroup>\n        <template v-for=\"menu in menus\" :key=\"menu.value\">\n          <DropdownMenuItem\n            :class=\"\n              menu.value === modelValue\n                ? 'bg-accent text-accent-foreground'\n                : ''\n            \"\n            class=\"text-foreground/80 data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground mb-1 cursor-pointer\"\n            @click=\"handleItemClick(menu.value)\"\n          >\n            <component :is=\"menu.icon\" v-if=\"menu.icon\" class=\"mr-2 size-4\" />\n            <span\n              v-if=\"!menu.icon\"\n              :class=\"menu.value === modelValue ? 'bg-foreground' : ''\"\n              class=\"mr-2 size-1.5 rounded-full\"\n            ></span>\n            {{ menu.label }}\n          </DropdownMenuItem>\n        </template>\n      </DropdownMenuGroup>\n    </DropdownMenuContent>\n  </DropdownMenu>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/dropdown-menu/index.ts",
    "content": "export { default as VbenDropdownMenu } from './dropdown-menu.vue';\nexport { default as VbenDropdownRadioMenu } from './dropdown-radio-menu.vue';\n\nexport type * from './interface';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/dropdown-menu/interface.ts",
    "content": "import type { Component } from 'vue';\n\ninterface VbenDropdownMenuItem {\n  disabled?: boolean;\n  /**\n   * @zh_CN 点击事件处理\n   * @param data\n   */\n  handler?: (data: any) => void;\n  /**\n   * @zh_CN 图标\n   */\n  icon?: Component;\n  /**\n   * @zh_CN 标题\n   */\n  label: string;\n  /**\n   * @zh_CN 是否是分割线\n   */\n  separator?: boolean;\n  /**\n   * @zh_CN 唯一标识\n   */\n  value: string;\n}\n\ninterface DropdownMenuProps {\n  menus: VbenDropdownMenuItem[];\n}\n\nexport type { DropdownMenuProps, VbenDropdownMenuItem };\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/expandable-arrow/expandable-arrow.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ChevronDown } from '@vben-core/icons';\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: string;\n}>();\n\n// 控制箭头展开/收起状态\nconst collapsed = defineModel({ default: false });\n</script>\n\n<template>\n  <div\n    :class=\"cn('vben-link inline-flex items-center', props.class)\"\n    @click=\"collapsed = !collapsed\"\n  >\n    <slot :is-expanded=\"collapsed\">\n      {{ collapsed }}\n      <!-- <span>{{ isExpanded ? '收起' : '展开' }}</span> -->\n    </slot>\n    <div\n      :class=\"{ 'rotate-180': !collapsed }\"\n      class=\"transition-transform duration-300\"\n    >\n      <slot name=\"icon\">\n        <ChevronDown class=\"size-4\" />\n      </slot>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/expandable-arrow/index.ts",
    "content": "export { default as VbenExpandableArrow } from './expandable-arrow.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/full-screen/full-screen.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Maximize, Minimize } from '@vben-core/icons';\n\nimport { useFullscreen } from '@vueuse/core';\n\nimport { VbenIconButton } from '../button';\n\ndefineOptions({ name: 'FullScreen' });\n\nconst { isFullscreen, toggle } = useFullscreen();\n\n// 重新检查全屏状态\nisFullscreen.value = !!(\n  document.fullscreenElement ||\n  // @ts-expect-error - vendor fullscreen APIs are not included in the standard DOM typings\n  document.webkitFullscreenElement ||\n  // @ts-expect-error - vendor fullscreen APIs are not included in the standard DOM typings\n  document.mozFullScreenElement ||\n  // @ts-expect-error - vendor fullscreen APIs are not included in the standard DOM typings\n  document.msFullscreenElement\n);\n</script>\n<template>\n  <VbenIconButton\n    class=\"hover:animate-[shrink_0.3s_ease-in-out]\"\n    @click=\"toggle\"\n  >\n    <Minimize v-if=\"isFullscreen\" class=\"text-foreground size-4\" />\n    <Maximize v-else class=\"text-foreground size-4\" />\n  </VbenIconButton>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/full-screen/index.ts",
    "content": "export { default as VbenFullScreen } from './full-screen.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/hover-card/hover-card.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  HoverCardContentProps,\n  HoverCardRootEmits,\n  HoverCardRootProps,\n} from 'reka-ui';\n\nimport type { ClassType } from '@vben-core/typings';\n\nimport { computed } from 'vue';\n\nimport { useForwardPropsEmits } from 'reka-ui';\n\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from '../../ui';\n\ninterface Props extends HoverCardRootProps {\n  class?: ClassType;\n  contentClass?: ClassType;\n  contentProps?: HoverCardContentProps;\n}\n\nconst props = defineProps<Props>();\n\nconst emits = defineEmits<HoverCardRootEmits>();\n\nconst delegatedProps = computed(() => {\n  const {\n    class: _cls,\n    contentClass: _,\n    contentProps: _cProps,\n    ...delegated\n  } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <HoverCard v-bind=\"forwarded\">\n    <HoverCardTrigger as-child class=\"h-full\">\n      <div class=\"h-full cursor-pointer\">\n        <slot name=\"trigger\"></slot>\n      </div>\n    </HoverCardTrigger>\n    <HoverCardContent\n      :class=\"contentClass\"\n      v-bind=\"contentProps\"\n      class=\"side-content z-popup\"\n    >\n      <slot></slot>\n    </HoverCardContent>\n  </HoverCard>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/hover-card/index.ts",
    "content": "export { default as VbenHoverCard } from './hover-card.vue';\nexport type { HoverCardContentProps } from 'reka-ui';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/icon/icon.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Component } from 'vue';\n\nimport { computed } from 'vue';\n\nimport { IconDefault, IconifyIcon } from '@vben-core/icons';\nimport {\n  isFunction,\n  isHttpUrl,\n  isObject,\n  isString,\n} from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  // 没有是否显示默认图标\n  fallback?: boolean;\n  icon?: Component | Function | string;\n}>();\n\nconst isRemoteIcon = computed(() => {\n  return isString(props.icon) && isHttpUrl(props.icon);\n});\n\nconst isComponent = computed(() => {\n  const { icon } = props;\n  return !isString(icon) && (isObject(icon) || isFunction(icon));\n});\n</script>\n\n<template>\n  <component :is=\"icon as Component\" v-if=\"isComponent\" v-bind=\"$attrs\" />\n  <img v-else-if=\"isRemoteIcon\" :src=\"icon as string\" v-bind=\"$attrs\" />\n  <IconifyIcon v-else-if=\"icon\" v-bind=\"$attrs\" :icon=\"icon as string\" />\n  <IconDefault v-else-if=\"fallback\" v-bind=\"$attrs\" />\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/icon/index.ts",
    "content": "export { default as VbenIcon } from './icon.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/index.ts",
    "content": "export * from './avatar';\nexport * from './back-top';\nexport * from './breadcrumb';\nexport * from './button';\nexport * from './checkbox';\nexport * from './context-menu';\nexport * from './count-to-animator';\nexport * from './dropdown-menu';\nexport * from './expandable-arrow';\nexport * from './full-screen';\nexport * from './hover-card';\nexport * from './icon';\nexport * from './input-password';\nexport * from './logo';\nexport * from './pin-input';\nexport * from './popover';\nexport * from './render-content';\nexport * from './scrollbar';\nexport * from './segmented';\nexport * from './select';\nexport * from './spine-text';\nexport * from './spinner';\nexport * from './tooltip';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/input-password/index.ts",
    "content": "export { default as VbenInputPassword } from './input-password.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/input-password/input-password.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref, useSlots } from 'vue';\n\nimport { Eye, EyeOff } from '@vben-core/icons';\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Input } from '../../ui';\nimport PasswordStrength from './password-strength.vue';\n\ninterface Props {\n  class?: any;\n  /**\n   * 是否显示密码强度\n   */\n  passwordStrength?: boolean;\n}\n\ndefineOptions({\n  inheritAttrs: false,\n});\n\nconst props = defineProps<Props>();\n\nconst modelValue = defineModel<string>();\n\nconst slots = useSlots();\n\nconst show = ref(false);\n</script>\n\n<template>\n  <div class=\"relative w-full\">\n    <Input\n      v-bind=\"$attrs\"\n      v-model=\"modelValue\"\n      :class=\"cn(props.class)\"\n      :type=\"show ? 'text' : 'password'\"\n    />\n    <template v-if=\"passwordStrength\">\n      <PasswordStrength :password=\"modelValue\" />\n      <p v-if=\"slots.strengthText\" class=\"text-muted-foreground mt-1.5 text-xs\">\n        <slot name=\"strengthText\"> </slot>\n      </p>\n    </template>\n    <div\n      :class=\"{\n        'top-3': !!passwordStrength,\n        'top-1/2 -translate-y-1/2 items-center': !passwordStrength,\n      }\"\n      class=\"text-foreground/60 hover:text-foreground absolute inset-y-0 right-0 flex cursor-pointer pr-3 text-lg leading-5\"\n      @click=\"show = !show\"\n    >\n      <Eye v-if=\"show\" class=\"size-4\" />\n      <EyeOff v-else class=\"size-4\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/input-password/password-strength.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nconst props = withDefaults(defineProps<{ password?: string }>(), {\n  password: '',\n});\n\nconst strengthList: string[] = [\n  '',\n  '#e74242',\n  '#ED6F6F',\n  '#EFBD47',\n  '#55D18780',\n  '#55D187',\n];\n\nconst currentStrength = computed(() => {\n  return checkPasswordStrength(props.password);\n});\n\nconst currentColor = computed(() => {\n  return strengthList[currentStrength.value];\n});\n\n/**\n * Check the strength of a password\n */\nfunction checkPasswordStrength(password: string) {\n  let strength = 0;\n\n  // Check length\n  if (password.length >= 8) strength++;\n\n  // Check for lowercase letters\n  if (/[a-z]/.test(password)) strength++;\n\n  // Check for uppercase letters\n  if (/[A-Z]/.test(password)) strength++;\n\n  // Check for numbers\n  if (/\\d/.test(password)) strength++;\n\n  // Check for special characters\n  if (/[^\\da-z]/i.test(password)) strength++;\n\n  return strength;\n}\n</script>\n\n<template>\n  <div class=\"relative mt-2 flex items-center justify-between\">\n    <template v-for=\"index in 5\" :key=\"index\">\n      <div\n        class=\"bg-heavy dark:bg-input-background relative mr-1 h-1.5 w-1/5 rounded-sm last:mr-0\"\n      >\n        <span\n          :style=\"{\n            backgroundColor: currentColor,\n            width: currentStrength >= index ? '100%' : '',\n          }\"\n          class=\"absolute left-0 h-full w-0 rounded-sm transition-all duration-500\"\n        ></span>\n      </div>\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/logo/index.ts",
    "content": "export { default as VbenLogo } from './logo.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { VbenAvatar } from '../avatar';\n\ninterface Props {\n  /**\n   * @zh_CN 是否收起文本\n   */\n  collapsed?: boolean;\n  /**\n   * @zh_CN Logo 图片适应方式\n   */\n  fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n  /**\n   * @zh_CN Logo 跳转地址\n   */\n  href?: string;\n  /**\n   * @zh_CN Logo 图片大小\n   */\n  logoSize?: number;\n  /**\n   * @zh_CN Logo 图标\n   */\n  src?: string;\n  /**\n   * @zh_CN 暗色主题 Logo 图标 (可选，若不设置则使用 src)\n   */\n  srcDark?: string;\n  /**\n   * @zh_CN Logo 文本\n   */\n  text: string;\n  /**\n   * @zh_CN Logo 主题\n   */\n  theme?: string;\n}\n\ndefineOptions({\n  name: 'VbenLogo',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  collapsed: false,\n  href: 'javascript:void 0',\n  logoSize: 32,\n  src: '',\n  srcDark: '',\n  theme: 'light',\n  fit: 'cover',\n});\n\n/**\n * @zh_CN 根据主题选择合适的 logo 图标\n */\nconst logoSrc = computed(() => {\n  // 如果是暗色主题且提供了 srcDark，则使用暗色主题的 logo\n  if (props.theme === 'dark' && props.srcDark) {\n    return props.srcDark;\n  }\n  // 否则使用默认的 src\n  return props.src;\n});\n</script>\n\n<template>\n  <div :class=\"theme\" class=\"flex h-full items-center text-lg\">\n    <a\n      :class=\"$attrs.class\"\n      :href=\"href\"\n      class=\"flex h-full items-center gap-2 overflow-hidden px-3 text-lg leading-normal transition-all duration-500\"\n    >\n      <VbenAvatar\n        v-if=\"logoSrc\"\n        :alt=\"text\"\n        :src=\"logoSrc\"\n        :size=\"logoSize\"\n        :fit=\"fit\"\n        class=\"relative rounded-none bg-transparent\"\n      />\n      <template v-if=\"!collapsed\">\n        <slot name=\"text\">\n          <span class=\"text-foreground truncate font-semibold text-nowrap\">\n            {{ text }}\n          </span>\n        </slot>\n      </template>\n    </a>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/pin-input/index.ts",
    "content": "export { default as VbenPinInput } from './input.vue';\n\nexport type * from './types';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/pin-input/input.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PinInputProps } from './types';\n\nimport { computed, onBeforeUnmount, ref, useId, watch } from 'vue';\n\nimport { PinInput, PinInputGroup, PinInputInput } from '../../ui';\nimport { VbenButton } from '../button';\n\ndefineOptions({\n  inheritAttrs: false,\n});\n\nconst {\n  codeLength = 6,\n  createText = async () => {},\n  disabled = false,\n  handleSendCode = async () => {},\n  loading = false,\n  maxTime = 60,\n} = defineProps<PinInputProps>();\n\nconst emit = defineEmits<{\n  complete: [];\n  sendError: [error: any];\n}>();\n\nconst timer = ref<ReturnType<typeof setTimeout>>();\n\nconst modelValue = defineModel<string>();\n\nconst inputValue = ref<string[]>([]);\nconst countdown = ref(0);\n\nconst btnText = computed(() => {\n  const countdownValue = countdown.value;\n  return createText?.(countdownValue);\n});\n\nconst btnLoading = computed(() => {\n  return loading || countdown.value > 0;\n});\n\nwatch(\n  () => modelValue.value,\n  () => {\n    inputValue.value = modelValue.value?.split('') ?? [];\n  },\n);\n\nwatch(inputValue, (val) => {\n  modelValue.value = val.join('');\n});\n\nfunction handleComplete(e: string[]) {\n  modelValue.value = e.join('');\n  emit('complete');\n}\n\nasync function handleSend(e: Event) {\n  try {\n    e?.preventDefault();\n    await handleSendCode();\n    countdown.value = maxTime;\n    startCountdown();\n  } catch (error) {\n    console.error('Failed to send code:', error);\n    // Consider emitting an error event or showing a notification\n    emit('sendError', error);\n  }\n}\n\nfunction startCountdown() {\n  if (countdown.value > 0) {\n    timer.value = setTimeout(() => {\n      countdown.value--;\n      startCountdown();\n    }, 1000);\n  }\n}\n\nonBeforeUnmount(() => {\n  countdown.value = 0;\n  clearTimeout(timer.value);\n});\n\nconst id = useId();\n\nconst pinType = 'text' as const;\n</script>\n\n<template>\n  <PinInput\n    :id=\"id\"\n    v-model=\"inputValue\"\n    :disabled=\"disabled\"\n    class=\"flex w-full justify-between\"\n    otp\n    placeholder=\"○\"\n    :type=\"pinType\"\n    @complete=\"handleComplete\"\n  >\n    <div class=\"relative flex w-full\">\n      <PinInputGroup class=\"mr-2\">\n        <PinInputInput\n          v-for=\"(item, index) in codeLength\"\n          :key=\"item\"\n          :index=\"index\"\n        />\n      </PinInputGroup>\n      <VbenButton\n        :disabled=\"disabled\"\n        :loading=\"btnLoading\"\n        class=\"grow\"\n        size=\"lg\"\n        variant=\"outline\"\n        @click=\"handleSend\"\n      >\n        {{ btnText }}\n      </VbenButton>\n    </div>\n  </PinInput>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/pin-input/types.ts",
    "content": "interface PinInputProps {\n  class?: any;\n  /**\n   * 验证码长度\n   */\n  codeLength?: number;\n  /**\n   * 发送验证码按钮文本\n   */\n  createText?: (countdown: number) => string;\n  /**\n   * 是否禁用\n   */\n  disabled?: boolean;\n  /**\n   * 自定义验证码发送逻辑\n   * @returns\n   */\n  handleSendCode?: () => Promise<void>;\n  /**\n   * 发送验证码按钮loading\n   */\n  loading?: boolean;\n  /**\n   * 最大重试时间\n   */\n  maxTime?: number;\n}\n\nexport type { PinInputProps };\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/popover/index.ts",
    "content": "export { default as VbenPopover } from './popover.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/popover/popover.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  PopoverContentProps,\n  PopoverRootEmits,\n  PopoverRootProps,\n} from 'reka-ui';\n\nimport type { ClassType } from '@vben-core/typings';\n\nimport { computed } from 'vue';\n\nimport { useForwardPropsEmits } from 'reka-ui';\n\nimport {\n  PopoverContent,\n  Popover as PopoverRoot,\n  PopoverTrigger,\n} from '../../ui';\n\ninterface Props extends PopoverRootProps {\n  class?: ClassType;\n  contentClass?: ClassType;\n  contentProps?: PopoverContentProps;\n  triggerClass?: ClassType;\n}\n\nconst props = withDefaults(defineProps<Props>(), {});\n\nconst emits = defineEmits<PopoverRootEmits>();\n\nconst delegatedProps = computed(() => {\n  const {\n    class: _cls,\n    contentClass: _,\n    contentProps: _cProps,\n    triggerClass: _tClass,\n    ...delegated\n  } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <PopoverRoot v-bind=\"forwarded\">\n    <PopoverTrigger :class=\"triggerClass\">\n      <slot name=\"trigger\"></slot>\n\n      <PopoverContent\n        :class=\"contentClass\"\n        class=\"side-content z-popup\"\n        v-bind=\"contentProps\"\n      >\n        <slot></slot>\n      </PopoverContent>\n    </PopoverTrigger>\n  </PopoverRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/render-content/index.ts",
    "content": "export { default as VbenRenderContent } from './render-content.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/render-content/render-content.vue",
    "content": "<script lang=\"ts\">\nimport type { Component, PropType } from 'vue';\n\nimport { defineComponent, h } from 'vue';\n\nimport { isFunction, isObject, isString } from '@vben-core/shared/utils';\n\nexport default defineComponent({\n  name: 'RenderContent',\n  props: {\n    content: {\n      default: undefined as\n        | PropType<(() => any) | Component | string>\n        | undefined,\n      type: [Object, String, Function],\n    },\n    renderBr: {\n      default: false,\n      type: Boolean,\n    },\n  },\n  setup(props, { attrs, slots }) {\n    return () => {\n      if (!props.content) {\n        return null;\n      }\n      const isComponent =\n        (isObject(props.content) || isFunction(props.content)) &&\n        props.content !== null;\n      if (!isComponent) {\n        if (props.renderBr && isString(props.content)) {\n          const lines = props.content.split('\\n');\n          const result = [];\n          for (const [i, line] of lines.entries()) {\n            result.push(h('p', { key: i }, line));\n            // if (i < lines.length - 1) {\n            //   result.push(h('br'));\n            // }\n          }\n          return result;\n        } else {\n          return props.content;\n        }\n      }\n      return h(\n        props.content as never,\n        {\n          ...attrs,\n          props: {\n            ...props,\n            ...attrs,\n          },\n        },\n        slots,\n      );\n    };\n  },\n});\n</script>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/index.ts",
    "content": "export { default as VbenScrollbar } from './scrollbar.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ClassType } from '@vben-core/typings';\n\nimport { computed, ref } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ScrollArea, ScrollBar } from '../../ui';\n\ninterface Props {\n  class?: ClassType;\n  horizontal?: boolean;\n  scrollBarClass?: ClassType;\n  shadow?: boolean;\n  shadowBorder?: boolean;\n  shadowBottom?: boolean;\n  shadowLeft?: boolean;\n  shadowRight?: boolean;\n  shadowTop?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  class: '',\n  horizontal: false,\n  shadow: false,\n  shadowBorder: false,\n  shadowBottom: true,\n  shadowLeft: false,\n  shadowRight: false,\n  shadowTop: true,\n});\n\nconst emit = defineEmits<{\n  scrollAt: [{ bottom: boolean; left: boolean; right: boolean; top: boolean }];\n}>();\n\nconst isAtTop = ref(true);\nconst isAtRight = ref(false);\nconst isAtBottom = ref(false);\nconst isAtLeft = ref(true);\n\n/**\n * We have to check if the scroll amount is close enough to some threshold in order to\n * more accurately calculate arrivedState. This is because scrollTop/scrollLeft are non-rounded\n * numbers, while scrollHeight/scrollWidth and clientHeight/clientWidth are rounded.\n * https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled\n */\nconst ARRIVED_STATE_THRESHOLD_PIXELS = 1;\n\nconst showShadowTop = computed(() => props.shadow && props.shadowTop);\nconst showShadowBottom = computed(() => props.shadow && props.shadowBottom);\nconst showShadowLeft = computed(() => props.shadow && props.shadowLeft);\nconst showShadowRight = computed(() => props.shadow && props.shadowRight);\n\nconst computedShadowClasses = computed(() => {\n  return {\n    'both-shadow':\n      !isAtLeft.value &&\n      !isAtRight.value &&\n      showShadowLeft.value &&\n      showShadowRight.value,\n    'left-shadow': !isAtLeft.value && showShadowLeft.value,\n    'right-shadow': !isAtRight.value && showShadowRight.value,\n  };\n});\n\nfunction handleScroll(event: Event) {\n  const target = event.target as HTMLElement;\n  const scrollTop = target?.scrollTop ?? 0;\n  const scrollLeft = target?.scrollLeft ?? 0;\n  const clientHeight = target?.clientHeight ?? 0;\n  const clientWidth = target?.clientWidth ?? 0;\n  const scrollHeight = target?.scrollHeight ?? 0;\n  const scrollWidth = target?.scrollWidth ?? 0;\n  isAtTop.value = scrollTop <= 0;\n  isAtLeft.value = scrollLeft <= 0;\n  isAtBottom.value =\n    Math.abs(scrollTop) + clientHeight >=\n    scrollHeight - ARRIVED_STATE_THRESHOLD_PIXELS;\n  isAtRight.value =\n    Math.abs(scrollLeft) + clientWidth >=\n    scrollWidth - ARRIVED_STATE_THRESHOLD_PIXELS;\n\n  emit('scrollAt', {\n    bottom: isAtBottom.value,\n    left: isAtLeft.value,\n    right: isAtRight.value,\n    top: isAtTop.value,\n  });\n}\n</script>\n\n<template>\n  <ScrollArea\n    :class=\"[cn(props.class), computedShadowClasses]\"\n    :on-scroll=\"handleScroll\"\n    class=\"vben-scrollbar relative\"\n  >\n    <div\n      v-if=\"showShadowTop\"\n      :class=\"{\n        'opacity-100': !isAtTop,\n        'border-border border-t': shadowBorder && !isAtTop,\n      }\"\n      class=\"scrollbar-top-shadow pointer-events-none absolute top-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]\"\n    ></div>\n    <slot></slot>\n    <div\n      v-if=\"showShadowBottom\"\n      :class=\"{\n        'opacity-100': !isAtTop && !isAtBottom,\n        'border-border border-b': shadowBorder && !isAtTop && !isAtBottom,\n      }\"\n      class=\"scrollbar-bottom-shadow pointer-events-none absolute bottom-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]\"\n    ></div>\n    <ScrollBar\n      v-if=\"horizontal\"\n      :class=\"scrollBarClass\"\n      orientation=\"horizontal\"\n    />\n  </ScrollArea>\n</template>\n\n<style scoped>\n.vben-scrollbar {\n  &:not(.both-shadow).left-shadow {\n    mask-image: linear-gradient(90deg, transparent, #000 16px);\n  }\n\n  &:not(.both-shadow).right-shadow {\n    mask-image: linear-gradient(\n      90deg,\n      #000 0%,\n      #000 calc(100% - 16px),\n      transparent\n    );\n  }\n\n  &.both-shadow {\n    mask-image: linear-gradient(\n      90deg,\n      transparent,\n      #000 16px,\n      #000 calc(100% - 16px),\n      transparent 100%\n    );\n  }\n}\n\n.scrollbar-top-shadow {\n  background: linear-gradient(\n    to bottom,\n    hsl(var(--scroll-shadow, var(--background))),\n    transparent\n  );\n}\n\n.scrollbar-bottom-shadow {\n  background: linear-gradient(\n    to top,\n    hsl(var(--scroll-shadow, var(--background))),\n    transparent\n  );\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/segmented/index.ts",
    "content": "export { default as VbenSegmented } from './segmented.vue';\n\nexport type * from './types';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SegmentedItem } from './types';\n\nimport { computed } from 'vue';\n\nimport { TabsTrigger } from 'reka-ui';\n\nimport { Tabs, TabsContent, TabsList } from '../../ui';\nimport TabsIndicator from './tabs-indicator.vue';\n\ninterface Props {\n  defaultValue?: string;\n  tabs?: SegmentedItem[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  defaultValue: '',\n  tabs: () => [],\n});\n\nconst activeTab = defineModel<string>();\n\nconst getDefaultValue = computed(() => {\n  return props.defaultValue || props.tabs[0]?.value;\n});\n\nconst tabsStyle = computed(() => {\n  return {\n    'grid-template-columns': `repeat(${props.tabs.length}, minmax(0, 1fr))`,\n  };\n});\n\nconst tabsIndicatorStyle = computed(() => {\n  return {\n    width: `${(100 / props.tabs.length).toFixed(0)}%`,\n  };\n});\n\nfunction activeClass(tab: string): string[] {\n  return tab === activeTab.value ? ['font-bold!', 'text-primary'] : [];\n}\n</script>\n\n<template>\n  <Tabs v-model=\"activeTab\" :default-value=\"getDefaultValue\">\n    <TabsList\n      :style=\"tabsStyle\"\n      class=\"bg-accent outline-heavy! relative grid w-full outline! outline-2!\"\n    >\n      <TabsIndicator :style=\"tabsIndicatorStyle\" />\n      <template v-for=\"tab in tabs\" :key=\"tab.value\">\n        <TabsTrigger\n          :value=\"tab.value\"\n          :class=\"activeClass(tab.value)\"\n          class=\"hover:text-primary z-20 inline-flex items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap disabled:pointer-events-none disabled:opacity-50\"\n        >\n          {{ tab.label }}\n        </TabsTrigger>\n      </template>\n    </TabsList>\n    <template v-for=\"tab in tabs\" :key=\"tab.value\">\n      <TabsContent :value=\"tab.value\">\n        <slot :name=\"tab.value\"></slot>\n      </TabsContent>\n    </template>\n  </Tabs>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/segmented/tabs-indicator.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TabsIndicatorProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { TabsIndicator, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<TabsIndicatorProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <TabsIndicator\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn(\n        'absolute bottom-0 left-0 z-10 h-full w-1/2 translate-x-(--reka-tabs-indicator-position) rounded-full px-0 py-1 pr-0.5 transition-[width,transform] duration-300',\n        props.class,\n      )\n    \"\n  >\n    <div\n      class=\"bg-background text-foreground inline-flex h-full w-full items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\"\n    >\n      <slot></slot>\n    </div>\n  </TabsIndicator>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/segmented/types.ts",
    "content": "interface SegmentedItem {\n  label: string;\n  value: string;\n}\n\nexport type { SegmentedItem };\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/select/index.ts",
    "content": "export { default as VbenSelect } from './select.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/select/select.vue",
    "content": "<script lang=\"ts\" setup>\nimport { CircleX } from '@vben-core/icons';\n\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '../../ui';\n\ninterface Props {\n  allowClear?: boolean;\n  class?: any;\n  options?: Array<{ label: string; value: string }>;\n  placeholder?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  allowClear: false,\n});\n\nconst modelValue = defineModel<string>();\n\nfunction handleClear() {\n  modelValue.value = undefined;\n}\n</script>\n<template>\n  <Select v-model=\"modelValue\">\n    <SelectTrigger :class=\"props.class\" class=\"flex w-full items-center\">\n      <SelectValue class=\"flex-auto text-left\" :placeholder=\"placeholder\" />\n      <CircleX\n        @pointerdown.stop\n        @click.stop.prevent=\"handleClear\"\n        v-if=\"allowClear && modelValue\"\n        data-clear-button\n        class=\"mr-1 size-4 cursor-pointer opacity-50 hover:opacity-100\"\n      />\n    </SelectTrigger>\n    <SelectContent>\n      <template v-for=\"item in options\" :key=\"item.value\">\n        <SelectItem :value=\"item.value\"> {{ item.label }} </SelectItem>\n      </template>\n    </SelectContent>\n  </Select>\n</template>\n\n<style lang=\"scss\" scoped>\nbutton[role='combobox'][data-placeholder] {\n  color: hsl(var(--muted-foreground));\n}\n\nbutton {\n  --ring: var(--primary);\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/spine-text/index.ts",
    "content": "export { default as VbenSpineText } from './spine-text.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/spine-text/spine-text.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nconst { animationDuration = 2, animationIterationCount = 'infinite' } =\n  defineProps<{\n    // 动画持续时间，单位秒\n    animationDuration?: number;\n    // 动画是否只执行一次\n    animationIterationCount?: 'infinite' | number;\n  }>();\n\nconst style = computed(() => {\n  return {\n    animation: `shine ${animationDuration}s linear ${animationIterationCount}`,\n  };\n});\n</script>\n<template>\n  <div :style=\"style\" class=\"vben-spine-text bg-clip-text! text-transparent\">\n    <slot></slot>\n  </div>\n</template>\n<style>\n.vben-spine-text {\n  background:\n    radial-gradient(circle at center, rgb(255 255 255 / 80%), #f000) -200% 50% /\n      200% 100% no-repeat,\n    #000;\n\n  /* animation: shine 3s linear infinite; */\n}\n\n.dark .vben-spine-text {\n  background:\n    radial-gradient(circle at center, rgb(24 24 26 / 80%), transparent) -200%\n      50% / 200% 100% no-repeat,\n    #f4f4f4;\n}\n\n@keyframes shine {\n  0% {\n    background-position: 200% 0%;\n  }\n\n  100% {\n    background-position: -200% 0%;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/spinner/index.ts",
    "content": "export { default as VbenLoading } from './loading.vue';\nexport { default as VbenSpinner } from './spinner.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref, watch } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\ninterface Props {\n  class?: string;\n  /**\n   * @zh_CN 最小加载时间\n   * @en_US Minimum loading time\n   */\n  minLoadingTime?: number;\n\n  /**\n   * @zh_CN loading状态开启\n   */\n  spinning?: boolean;\n  /**\n   * @zh_CN 文字\n   */\n  text?: string;\n}\n\ndefineOptions({\n  name: 'VbenLoading',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  minLoadingTime: 50,\n  text: '',\n});\n// const startTime = ref(0);\nconst showSpinner = ref(false);\nconst renderSpinner = ref(false);\nlet timer: ReturnType<typeof setTimeout> | undefined;\n\nwatch(\n  () => props.spinning,\n  (show) => {\n    if (!show) {\n      showSpinner.value = false;\n      timer && clearTimeout(timer);\n      return;\n    }\n\n    // startTime.value = performance.now();\n    timer = setTimeout(() => {\n      // const loadingTime = performance.now() - startTime.value;\n\n      showSpinner.value = true;\n      if (showSpinner.value) {\n        renderSpinner.value = true;\n      }\n    }, props.minLoadingTime);\n  },\n  {\n    immediate: true,\n  },\n);\n\nfunction onTransitionEnd() {\n  if (!showSpinner.value) {\n    renderSpinner.value = false;\n  }\n}\n</script>\n\n<template>\n  <div\n    :class=\"\n      cn(\n        'bg-overlay-content dark:bg-overlay absolute top-0 left-0 z-100 flex size-full flex-col items-center justify-center transition-all duration-500',\n        {\n          'invisible opacity-0': !showSpinner,\n        },\n        props.class,\n      )\n    \"\n    @transitionend=\"onTransitionEnd\"\n  >\n    <slot name=\"icon\" v-if=\"renderSpinner\">\n      <span class=\"dot relative inline-block size-9 text-3xl\">\n        <i\n          v-for=\"index in 4\"\n          :key=\"index\"\n          class=\"bg-primary absolute block size-4 origin-[50%_50%] scale-75 rounded-full opacity-30\"\n        ></i>\n      </span>\n    </slot>\n\n    <div v-if=\"text\" class=\"text-primary mt-4 text-xs\">{{ text }}</div>\n    <slot></slot>\n  </div>\n</template>\n\n<style scoped>\n.dot {\n  transform: rotate(45deg);\n  animation: rotate-ani 1.2s infinite linear;\n}\n\n.dot i {\n  animation: spin-move-ani 1s infinite linear alternate;\n}\n\n.dot i:nth-child(1) {\n  top: 0;\n  left: 0;\n}\n\n.dot i:nth-child(2) {\n  top: 0;\n  right: 0;\n  animation-delay: 0.4s;\n}\n\n.dot i:nth-child(3) {\n  right: 0;\n  bottom: 0;\n  animation-delay: 0.8s;\n}\n\n.dot i:nth-child(4) {\n  bottom: 0;\n  left: 0;\n  animation-delay: 1.2s;\n}\n\n@keyframes rotate-ani {\n  to {\n    transform: rotate(405deg);\n  }\n}\n\n@keyframes spin-move-ani {\n  to {\n    opacity: 1;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref, watch } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\ninterface Props {\n  class?: string;\n  /**\n   * @zh_CN 最小加载时间\n   * @en_US Minimum loading time\n   */\n  minLoadingTime?: number;\n  /**\n   * @zh_CN loading状态开启\n   */\n  spinning?: boolean;\n}\n\ndefineOptions({\n  name: 'VbenSpinner',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  minLoadingTime: 50,\n});\n// const startTime = ref(0);\nconst showSpinner = ref(false);\nconst renderSpinner = ref(false);\nconst timer = ref<ReturnType<typeof setTimeout>>();\n\nwatch(\n  () => props.spinning,\n  (show) => {\n    if (!show) {\n      showSpinner.value = false;\n      clearTimeout(timer.value);\n      return;\n    }\n\n    // startTime.value = performance.now();\n    timer.value = setTimeout(() => {\n      // const loadingTime = performance.now() - startTime.value;\n\n      showSpinner.value = true;\n      if (showSpinner.value) {\n        renderSpinner.value = true;\n      }\n    }, props.minLoadingTime);\n  },\n  {\n    immediate: true,\n  },\n);\n\nfunction onTransitionEnd() {\n  if (!showSpinner.value) {\n    renderSpinner.value = false;\n  }\n}\n</script>\n\n<template>\n  <div\n    :class=\"\n      cn(\n        'flex-center bg-overlay-content absolute top-0 left-0 z-100 size-full backdrop-blur-xs transition-all duration-500',\n        {\n          'invisible opacity-0': !showSpinner,\n        },\n        props.class,\n      )\n    \"\n    @transitionend=\"onTransitionEnd\"\n  >\n    <div\n      :class=\"{ paused: !renderSpinner }\"\n      v-if=\"renderSpinner\"\n      class=\"loader before:bg-primary/50 after:bg-primary relative size-12 before:absolute before:top-15 before:left-0 before:h-1.25 before:w-12 before:rounded-full before:content-[''] after:absolute after:top-0 after:left-0 after:h-full after:w-full after:rounded after:content-['']\"\n    ></div>\n  </div>\n</template>\n\n<style scoped>\n.paused {\n  &::before {\n    animation-play-state: paused !important;\n  }\n\n  &::after {\n    animation-play-state: paused !important;\n  }\n}\n\n.loader {\n  &::before {\n    animation: loader-shadow-ani 0.5s linear infinite;\n  }\n\n  &::after {\n    animation: loader-jump-ani 0.5s linear infinite;\n  }\n}\n\n@keyframes loader-jump-ani {\n  15% {\n    border-bottom-right-radius: 3px;\n  }\n\n  25% {\n    transform: translateY(9px) rotate(22.5deg);\n  }\n\n  50% {\n    border-bottom-right-radius: 40px;\n    transform: translateY(18px) scale(1, 0.9) rotate(45deg);\n  }\n\n  75% {\n    transform: translateY(9px) rotate(67.5deg);\n  }\n\n  100% {\n    transform: translateY(0) rotate(90deg);\n  }\n}\n\n@keyframes loader-shadow-ani {\n  0%,\n  100% {\n    transform: scale(1, 1);\n  }\n\n  50% {\n    transform: scale(1.2, 1);\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/tooltip/help-tooltip.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nimport { CircleHelp } from 'lucide-vue-next';\n\nimport Tooltip from './tooltip.vue';\n\ndefineOptions({\n  inheritAttrs: false,\n});\n\ndefineProps<{ triggerClass?: string }>();\n</script>\n\n<template>\n  <Tooltip :delay-duration=\"300\" side=\"right\">\n    <template #trigger>\n      <slot name=\"trigger\">\n        <CircleHelp\n          :class=\"\n            cn(\n              'text-foreground/80 hover:text-foreground inline-flex size-5 cursor-pointer',\n              triggerClass,\n            )\n          \"\n        />\n      </slot>\n    </template>\n    <slot></slot>\n  </Tooltip>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/tooltip/index.ts",
    "content": "export { default as VbenHelpTooltip } from './help-tooltip.vue';\nexport { default as VbenTooltip } from './tooltip.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/components/tooltip/tooltip.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TooltipContentProps } from 'reka-ui';\n\nimport type { StyleValue } from 'vue';\n\nimport type { ClassType } from '@vben-core/typings';\n\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from '../../ui';\n\ninterface Props {\n  contentClass?: ClassType;\n  contentStyle?: StyleValue;\n  delayDuration?: number;\n  side?: TooltipContentProps['side'];\n}\n\nwithDefaults(defineProps<Props>(), {\n  delayDuration: 0,\n  side: 'right',\n});\n</script>\n\n<template>\n  <TooltipProvider :delay-duration=\"delayDuration\">\n    <Tooltip>\n      <TooltipTrigger as-child tabindex=\"-1\">\n        <slot name=\"trigger\"></slot>\n      </TooltipTrigger>\n      <TooltipContent\n        :class=\"contentClass\"\n        :side=\"side\"\n        :style=\"contentStyle\"\n        class=\"side-content bg-accent text-popover-foreground rounded-md\"\n      >\n        <slot></slot>\n      </TooltipContent>\n    </Tooltip>\n  </TooltipProvider>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/index.ts",
    "content": "export * from './components';\nexport * from './ui';\nexport { createContext, Slot, VisuallyHidden } from 'reka-ui';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/accordion/Accordion.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AccordionRootEmits, AccordionRootProps } from 'reka-ui';\n\nimport { AccordionRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<AccordionRootProps>();\nconst emits = defineEmits<AccordionRootEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <AccordionRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </AccordionRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/accordion/AccordionContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AccordionContentProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { AccordionContent } from 'reka-ui';\n\nconst props = defineProps<AccordionContentProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <AccordionContent\n    v-bind=\"delegatedProps\"\n    class=\"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm\"\n  >\n    <div :class=\"cn('pt-0 pb-4', props.class)\">\n      <slot></slot>\n    </div>\n  </AccordionContent>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/accordion/AccordionItem.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AccordionItemProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { AccordionItem, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<AccordionItemProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <AccordionItem v-bind=\"forwardedProps\" :class=\"cn('border-b', props.class)\">\n    <slot></slot>\n  </AccordionItem>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/accordion/AccordionTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AccordionTriggerProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronDown } from 'lucide-vue-next';\nimport { AccordionHeader, AccordionTrigger } from 'reka-ui';\n\nconst props = defineProps<AccordionTriggerProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <AccordionHeader class=\"flex\">\n    <AccordionTrigger\n      v-bind=\"delegatedProps\"\n      :class=\"\n        cn(\n          'flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',\n          props.class,\n        )\n      \"\n    >\n      <slot></slot>\n      <slot name=\"icon\">\n        <ChevronDown\n          class=\"text-muted-foreground h-4 w-4 shrink-0 transition-transform duration-200\"\n        />\n      </slot>\n    </AccordionTrigger>\n  </AccordionHeader>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/accordion/index.ts",
    "content": "export { default as Accordion } from './Accordion.vue';\nexport { default as AccordionContent } from './AccordionContent.vue';\nexport { default as AccordionItem } from './AccordionItem.vue';\nexport { default as AccordionTrigger } from './AccordionTrigger.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialog.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AlertDialogEmits, AlertDialogProps } from 'reka-ui';\n\nimport { AlertDialogRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<AlertDialogProps>();\nconst emits = defineEmits<AlertDialogEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <AlertDialogRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </AlertDialogRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogAction.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AlertDialogActionProps } from 'reka-ui';\n\nimport { AlertDialogAction } from 'reka-ui';\n\nconst props = defineProps<AlertDialogActionProps>();\n</script>\n\n<template>\n  <AlertDialogAction v-bind=\"props\">\n    <slot></slot>\n  </AlertDialogAction>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogCancel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AlertDialogCancelProps } from 'reka-ui';\n\nimport { AlertDialogCancel } from 'reka-ui';\n\nconst props = defineProps<AlertDialogCancelProps>();\n</script>\n\n<template>\n  <AlertDialogCancel v-bind=\"props\">\n    <slot></slot>\n  </AlertDialogCancel>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AlertDialogContentEmits, AlertDialogContentProps } from 'reka-ui';\n\nimport type { ClassType } from '@vben-core/typings';\n\nimport { computed, ref } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport {\n  AlertDialogContent,\n  AlertDialogPortal,\n  useForwardPropsEmits,\n} from 'reka-ui';\n\nimport AlertDialogOverlay from './AlertDialogOverlay.vue';\n\nconst props = withDefaults(\n  defineProps<\n    AlertDialogContentProps & {\n      centered?: boolean;\n      class?: ClassType;\n      modal?: boolean;\n      open?: boolean;\n      overlayBlur?: number;\n      zIndex?: number;\n    }\n  >(),\n  { modal: true },\n);\nconst emits = defineEmits<\n  AlertDialogContentEmits & { close: []; closed: []; opened: [] }\n>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, modal: _modal, open: _open, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n\nconst contentRef = ref<InstanceType<typeof AlertDialogContent> | null>(null);\nfunction onAnimationEnd(event: AnimationEvent) {\n  // 只有在 contentRef 的动画结束时才触发 opened/closed 事件\n  if (event.target === contentRef.value?.$el) {\n    if (props.open) {\n      emits('opened');\n    } else {\n      emits('closed');\n    }\n  }\n}\ndefineExpose({\n  getContentRef: () => contentRef.value,\n});\n</script>\n\n<template>\n  <AlertDialogPortal>\n    <Transition name=\"fade\" appear>\n      <AlertDialogOverlay\n        v-if=\"open && modal\"\n        :style=\"{\n          ...(zIndex ? { zIndex } : {}),\n          position: 'fixed',\n          backdropFilter:\n            overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',\n        }\"\n        @click=\"() => emits('close')\"\n      />\n    </Transition>\n    <AlertDialogContent\n      ref=\"contentRef\"\n      :style=\"{ ...(zIndex ? { zIndex } : {}), position: 'fixed' }\"\n      @animationend=\"onAnimationEnd\"\n      v-bind=\"forwarded\"\n      :class=\"\n        cn(\n          'z-popup bg-background p-6 shadow-lg outline-hidden sm:rounded-xl',\n          'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',\n          'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',\n          {\n            'data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%]':\n              !centered,\n            'data-[state=closed]:slide-out-to-top-[148%] data-[state=open]:slide-in-from-top-[98%]':\n              centered,\n            'top-[10vh]': !centered,\n            'top-1/2 -translate-y-1/2': centered,\n          },\n          props.class,\n        )\n      \"\n    >\n      <slot></slot>\n    </AlertDialogContent>\n  </AlertDialogPortal>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogDescription.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { AlertDialogDescriptionProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { AlertDialogDescription, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<AlertDialogDescriptionProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <AlertDialogDescription\n    v-bind=\"forwardedProps\"\n    :class=\"cn('text-muted-foreground text-sm', props.class)\"\n  >\n    <slot></slot>\n  </AlertDialogDescription>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogOverlay.vue",
    "content": "<script setup lang=\"ts\">\nimport { useScrollLock } from '@vben-core/composables';\n\nuseScrollLock();\n</script>\n<template>\n  <div class=\"z-popup bg-overlay inset-0\"></div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/AlertDialogTitle.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AlertDialogTitleProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { AlertDialogTitle, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<AlertDialogTitleProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <AlertDialogTitle\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn('text-lg leading-none font-semibold tracking-tight', props.class)\n    \"\n  >\n    <slot></slot>\n  </AlertDialogTitle>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/alert-dialog/index.ts",
    "content": "export { default as AlertDialog } from './AlertDialog.vue';\nexport { default as AlertDialogAction } from './AlertDialogAction.vue';\nexport { default as AlertDialogCancel } from './AlertDialogCancel.vue';\nexport { default as AlertDialogContent } from './AlertDialogContent.vue';\nexport { default as AlertDialogDescription } from './AlertDialogDescription.vue';\nexport { default as AlertDialogTitle } from './AlertDialogTitle.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/avatar/Avatar.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AvatarVariants } from './avatar';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { AvatarRoot } from 'reka-ui';\n\nimport { avatarVariant } from './avatar';\n\nconst props = withDefaults(\n  defineProps<{\n    class?: any;\n    shape?: AvatarVariants['shape'];\n    size?: AvatarVariants['size'];\n  }>(),\n  {\n    shape: 'circle',\n    size: 'sm',\n  },\n);\n</script>\n\n<template>\n  <AvatarRoot :class=\"cn(avatarVariant({ size, shape }), props.class)\">\n    <slot></slot>\n  </AvatarRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/avatar/AvatarFallback.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AvatarFallbackProps } from 'reka-ui';\n\nimport { AvatarFallback } from 'reka-ui';\n\nconst props = defineProps<AvatarFallbackProps>();\n</script>\n\n<template>\n  <AvatarFallback v-bind=\"props\">\n    <slot></slot>\n  </AvatarFallback>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/avatar/AvatarImage.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AvatarImageProps } from 'reka-ui';\n\nimport { AvatarImage } from 'reka-ui';\n\nconst props = defineProps<AvatarImageProps>();\n</script>\n\n<template>\n  <AvatarImage v-bind=\"props\" class=\"h-full w-full object-cover\" />\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/avatar/avatar.ts",
    "content": "import type { VariantProps } from 'class-variance-authority';\n\nimport { cva } from 'class-variance-authority';\n\nexport const avatarVariant = cva(\n  'inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden',\n  {\n    variants: {\n      shape: {\n        circle: 'rounded-full',\n        square: 'rounded-md',\n      },\n      size: {\n        base: 'h-16 w-16 text-2xl',\n        lg: 'h-32 w-32 text-5xl',\n        sm: 'h-10 w-10 text-xs',\n      },\n    },\n  },\n);\n\nexport type AvatarVariants = VariantProps<typeof avatarVariant>;\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/avatar/index.ts",
    "content": "export * from './avatar';\nexport { default as Avatar } from './Avatar.vue';\nexport { default as AvatarFallback } from './AvatarFallback.vue';\nexport { default as AvatarImage } from './AvatarImage.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/badge/Badge.vue",
    "content": "<script setup lang=\"ts\">\nimport type { BadgeVariants } from './badge';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { badgeVariants } from './badge';\n\nconst props = defineProps<{\n  class?: any;\n  variant?: BadgeVariants['variant'];\n}>();\n</script>\n\n<template>\n  <div :class=\"cn(badgeVariants({ variant }), props.class)\">\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/badge/badge.ts",
    "content": "import type { VariantProps } from 'class-variance-authority';\n\nimport { cva } from 'class-variance-authority';\n\nexport const badgeVariants = cva(\n  'inline-flex items-center rounded-md border border-border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n  {\n    defaultVariants: {\n      variant: 'default',\n    },\n    variants: {\n      variant: {\n        default:\n          'border-transparent bg-accent hover:bg-accent text-primary-foreground shadow-sm',\n        destructive:\n          'border-transparent bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive-hover',\n        outline: 'text-foreground',\n        secondary:\n          'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',\n      },\n    },\n  },\n);\n\nexport type BadgeVariants = VariantProps<typeof badgeVariants>;\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/badge/index.ts",
    "content": "export * from './badge';\n\nexport { default as Badge } from './Badge.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/breadcrumb/Breadcrumb.vue",
    "content": "<script lang=\"ts\" setup>\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <nav :class=\"props.class\" aria-label=\"breadcrumb\" role=\"navigation\">\n    <slot></slot>\n  </nav>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/breadcrumb/BreadcrumbEllipsis.vue",
    "content": "<script lang=\"ts\" setup>\nimport { cn } from '@vben-core/shared/utils';\n\nimport { MoreHorizontal } from 'lucide-vue-next';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <span\n    :class=\"cn('flex h-9 w-9 items-center justify-center', props.class)\"\n    aria-hidden=\"true\"\n    role=\"presentation\"\n  >\n    <slot>\n      <MoreHorizontal class=\"h-4 w-4\" />\n    </slot>\n    <span class=\"sr-only\">More</span>\n  </span>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/breadcrumb/BreadcrumbItem.vue",
    "content": "<script lang=\"ts\" setup>\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <li\n    :class=\"\n      cn('hover:text-foreground inline-flex items-center gap-1.5', props.class)\n    \"\n  >\n    <slot></slot>\n  </li>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/breadcrumb/BreadcrumbLink.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { PrimitiveProps } from 'reka-ui';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Primitive } from 'reka-ui';\n\nconst props = withDefaults(defineProps<PrimitiveProps & { class?: any }>(), {\n  as: 'a',\n});\n</script>\n\n<template>\n  <Primitive\n    :as=\"as\"\n    :as-child=\"asChild\"\n    :class=\"cn('hover:text-foreground transition-colors', props.class)\"\n  >\n    <slot></slot>\n  </Primitive>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/breadcrumb/BreadcrumbList.vue",
    "content": "<script lang=\"ts\" setup>\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <ol\n    :class=\"\n      cn(\n        'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </ol>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/breadcrumb/BreadcrumbPage.vue",
    "content": "<script lang=\"ts\" setup>\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <span\n    :class=\"cn('text-foreground font-normal', props.class)\"\n    aria-current=\"page\"\n    aria-disabled=\"true\"\n    role=\"link\"\n  >\n    <slot></slot>\n  </span>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/breadcrumb/BreadcrumbSeparator.vue",
    "content": "<script lang=\"ts\" setup>\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronRight } from 'lucide-vue-next';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <li\n    :class=\"cn('[&>svg]:size-3.5', props.class)\"\n    aria-hidden=\"true\"\n    role=\"presentation\"\n  >\n    <slot>\n      <ChevronRight />\n    </slot>\n  </li>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/breadcrumb/index.ts",
    "content": "export { default as Breadcrumb } from './Breadcrumb.vue';\nexport { default as BreadcrumbEllipsis } from './BreadcrumbEllipsis.vue';\nexport { default as BreadcrumbItem } from './BreadcrumbItem.vue';\nexport { default as BreadcrumbLink } from './BreadcrumbLink.vue';\nexport { default as BreadcrumbList } from './BreadcrumbList.vue';\nexport { default as BreadcrumbPage } from './BreadcrumbPage.vue';\nexport { default as BreadcrumbSeparator } from './BreadcrumbSeparator.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/button/Button.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PrimitiveProps } from 'reka-ui';\n\nimport type { ButtonVariants, ButtonVariantSize } from './types';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Primitive } from 'reka-ui';\n\nimport { buttonVariants } from './button';\n\ninterface Props extends PrimitiveProps {\n  class?: any;\n  size?: ButtonVariantSize;\n  variant?: ButtonVariants;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  as: 'button',\n  class: '',\n});\n</script>\n\n<template>\n  <Primitive\n    :as=\"as\"\n    :as-child=\"asChild\"\n    :class=\"cn(buttonVariants({ variant, size }), props.class)\"\n  >\n    <slot></slot>\n  </Primitive>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/button/button.ts",
    "content": "import { cva } from 'class-variance-authority';\n\nexport const buttonVariants = cva(\n  'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-not-allowed  disabled:opacity-50',\n  {\n    defaultVariants: {\n      size: 'default',\n      variant: 'default',\n    },\n    variants: {\n      size: {\n        default: 'h-9 px-4 py-2',\n        icon: 'h-8 w-8 rounded-sm px-1 text-lg',\n        lg: 'h-10 rounded-md px-4',\n        sm: 'h-8 rounded-md px-2 text-xs',\n        xs: 'h-8 w-8 rounded-sm px-1 text-xs',\n      },\n      variant: {\n        default:\n          'bg-primary text-primary-foreground shadow hover:bg-primary/90',\n        destructive:\n          'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive-hover',\n        ghost: 'hover:bg-accent hover:text-accent-foreground',\n        heavy: 'hover:bg-heavy hover:text-heavy-foreground',\n        icon: 'hover:bg-accent hover:text-accent-foreground text-foreground/80',\n        link: 'text-primary underline-offset-4 hover:underline',\n        outline:\n          'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',\n        secondary:\n          'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',\n      },\n    },\n  },\n);\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/button/index.ts",
    "content": "export * from './button';\n\nexport { default as Button } from './Button.vue';\n\nexport type * from './types';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/button/types.ts",
    "content": "export type ButtonVariantSize =\n  | 'default'\n  | 'icon'\n  | 'lg'\n  | 'sm'\n  | 'xs'\n  | null\n  | undefined;\n\nexport type ButtonVariants =\n  | 'default'\n  | 'destructive'\n  | 'ghost'\n  | 'heavy'\n  | 'icon'\n  | 'link'\n  | 'outline'\n  | 'secondary'\n  | null\n  | undefined;\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/card/Card.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <div\n    :class=\"\n      cn(\n        'border-border bg-card text-card-foreground rounded-xl border',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/card/CardContent.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <div :class=\"cn('p-6 pt-0', props.class)\">\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/card/CardDescription.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <p :class=\"cn('text-muted-foreground text-sm', props.class)\">\n    <slot></slot>\n  </p>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/card/CardFooter.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <div :class=\"cn('flex items-center p-6 pt-0', props.class)\">\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/card/CardHeader.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <div :class=\"cn('flex flex-col gap-y-1.5 p-5', props.class)\">\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/card/CardTitle.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <h3 :class=\"cn('leading-none font-semibold tracking-tight', props.class)\">\n    <slot></slot>\n  </h3>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/card/index.ts",
    "content": "export { default as Card } from './Card.vue';\nexport { default as CardContent } from './CardContent.vue';\nexport { default as CardDescription } from './CardDescription.vue';\nexport { default as CardFooter } from './CardFooter.vue';\nexport { default as CardHeader } from './CardHeader.vue';\nexport { default as CardTitle } from './CardTitle.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/checkbox/Checkbox.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CheckboxRootEmits, CheckboxRootProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Check, Minus } from 'lucide-vue-next';\nimport { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<\n  CheckboxRootProps & { class?: any; indeterminate?: boolean }\n>();\nconst emits = defineEmits<CheckboxRootEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <CheckboxRoot\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'peer border-border hover:border-primary focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground h-4 w-4 shrink-0 rounded-sm border transition focus-visible:ring-1 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n        props.class,\n      )\n    \"\n  >\n    <CheckboxIndicator\n      class=\"flex h-full w-full items-center justify-center text-current\"\n    >\n      <slot>\n        <component :is=\"indeterminate ? Minus : Check\" class=\"h-4 w-4\" />\n      </slot>\n    </CheckboxIndicator>\n  </CheckboxRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/checkbox/index.ts",
    "content": "export { default as Checkbox } from './Checkbox.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenu.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ContextMenuRootEmits, ContextMenuRootProps } from 'reka-ui';\n\nimport { ContextMenuRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = withDefaults(defineProps<ContextMenuRootProps>(), {\n  modal: false,\n});\nconst emits = defineEmits<ContextMenuRootEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <ContextMenuRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </ContextMenuRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuCheckboxItem.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  ContextMenuCheckboxItemEmits,\n  ContextMenuCheckboxItemProps,\n} from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Check } from 'lucide-vue-next';\nimport {\n  ContextMenuCheckboxItem,\n  ContextMenuItemIndicator,\n  useForwardPropsEmits,\n} from 'reka-ui';\n\nconst props = defineProps<ContextMenuCheckboxItemProps & { class?: any }>();\nconst emits = defineEmits<ContextMenuCheckboxItemEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <ContextMenuCheckboxItem\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n        props.class,\n      )\n    \"\n  >\n    <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <ContextMenuItemIndicator>\n        <Check class=\"h-4 w-4\" />\n      </ContextMenuItemIndicator>\n    </span>\n    <slot></slot>\n  </ContextMenuCheckboxItem>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ContextMenuContentEmits, ContextMenuContentProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport {\n  ContextMenuContent,\n  ContextMenuPortal,\n  useForwardPropsEmits,\n} from 'reka-ui';\n\nconst props = defineProps<ContextMenuContentProps & { class?: any }>();\nconst emits = defineEmits<ContextMenuContentEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <ContextMenuPortal>\n    <ContextMenuContent\n      v-bind=\"forwarded\"\n      :class=\"\n        cn(\n          'z-popup border-border bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 min-w-32 overflow-hidden rounded-md border p-1 shadow-md',\n          props.class,\n        )\n      \"\n    >\n      <slot></slot>\n    </ContextMenuContent>\n  </ContextMenuPortal>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuGroup.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ContextMenuGroupProps } from 'reka-ui';\n\nimport { ContextMenuGroup } from 'reka-ui';\n\nconst props = defineProps<ContextMenuGroupProps>();\n</script>\n\n<template>\n  <ContextMenuGroup v-bind=\"props\">\n    <slot></slot>\n  </ContextMenuGroup>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuItem.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ContextMenuItemEmits, ContextMenuItemProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ContextMenuItem, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<\n  ContextMenuItemProps & { class?: any; inset?: boolean }\n>();\nconst emits = defineEmits<ContextMenuItemEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <ContextMenuItem\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n        inset && 'pl-8',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </ContextMenuItem>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuLabel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ContextMenuLabelProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ContextMenuLabel } from 'reka-ui';\n\nconst props = defineProps<\n  ContextMenuLabelProps & { class?: any; inset?: boolean }\n>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <ContextMenuLabel\n    v-bind=\"delegatedProps\"\n    :class=\"\n      cn(\n        'text-foreground px-2 py-1.5 text-sm font-semibold',\n        inset && 'pl-8',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </ContextMenuLabel>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuPortal.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ContextMenuPortalProps } from 'reka-ui';\n\nimport { ContextMenuPortal } from 'reka-ui';\n\nconst props = defineProps<ContextMenuPortalProps>();\n</script>\n\n<template>\n  <ContextMenuPortal v-bind=\"props\">\n    <slot></slot>\n  </ContextMenuPortal>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuRadioGroup.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  ContextMenuRadioGroupEmits,\n  ContextMenuRadioGroupProps,\n} from 'reka-ui';\n\nimport { ContextMenuRadioGroup, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<ContextMenuRadioGroupProps>();\nconst emits = defineEmits<ContextMenuRadioGroupEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <ContextMenuRadioGroup v-bind=\"forwarded\">\n    <slot></slot>\n  </ContextMenuRadioGroup>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuRadioItem.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  ContextMenuRadioItemEmits,\n  ContextMenuRadioItemProps,\n} from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Circle } from 'lucide-vue-next';\nimport {\n  ContextMenuItemIndicator,\n  ContextMenuRadioItem,\n  useForwardPropsEmits,\n} from 'reka-ui';\n\nconst props = defineProps<ContextMenuRadioItemProps & { class?: any }>();\nconst emits = defineEmits<ContextMenuRadioItemEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <ContextMenuRadioItem\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n        props.class,\n      )\n    \"\n  >\n    <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <ContextMenuItemIndicator>\n        <Circle class=\"h-2 w-2 fill-current\" />\n      </ContextMenuItemIndicator>\n    </span>\n    <slot></slot>\n  </ContextMenuRadioItem>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuSeparator.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ContextMenuSeparatorProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ContextMenuSeparator } from 'reka-ui';\n\nconst props = defineProps<ContextMenuSeparatorProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <ContextMenuSeparator\n    v-bind=\"delegatedProps\"\n    :class=\"cn('bg-border -mx-1 my-1 h-px', props.class)\"\n  />\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuShortcut.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <span\n    :class=\"\n      cn('text-muted-foreground ml-auto text-xs tracking-widest', props.class)\n    \"\n  >\n    <slot></slot>\n  </span>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuSub.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ContextMenuSubEmits, ContextMenuSubProps } from 'reka-ui';\n\nimport { ContextMenuSub, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<ContextMenuSubProps>();\nconst emits = defineEmits<ContextMenuSubEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <ContextMenuSub v-bind=\"forwarded\">\n    <slot></slot>\n  </ContextMenuSub>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuSubContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  DropdownMenuSubContentEmits,\n  DropdownMenuSubContentProps,\n} from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ContextMenuSubContent, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<DropdownMenuSubContentProps & { class?: any }>();\nconst emits = defineEmits<DropdownMenuSubContentEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <ContextMenuSubContent\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'z-50 min-w-32 overflow-hidden rounded-md',\n        'border-border border',\n        'bg-popover text-popover-foreground p-1 shadow-lg',\n        'data-[state=open]:animate-in data-[state=closed]:animate-out',\n        'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n        'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',\n        'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </ContextMenuSubContent>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuSubTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ContextMenuSubTriggerProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronRight } from 'lucide-vue-next';\nimport { ContextMenuSubTrigger, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<\n  ContextMenuSubTriggerProps & {\n    class?: any;\n    inset?: boolean;\n  }\n>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <ContextMenuSubTrigger\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn(\n        'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none',\n        inset && 'pl-8',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n    <ChevronRight class=\"ml-auto h-4 w-4\" />\n  </ContextMenuSubTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/ContextMenuTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ContextMenuTriggerProps } from 'reka-ui';\n\nimport { ContextMenuTrigger, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<ContextMenuTriggerProps>();\n\nconst forwardedProps = useForwardProps(props);\n</script>\n\n<template>\n  <ContextMenuTrigger v-bind=\"forwardedProps\">\n    <slot></slot>\n  </ContextMenuTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/context-menu/index.ts",
    "content": "export { default as ContextMenu } from './ContextMenu.vue';\nexport { default as ContextMenuCheckboxItem } from './ContextMenuCheckboxItem.vue';\nexport { default as ContextMenuContent } from './ContextMenuContent.vue';\nexport { default as ContextMenuGroup } from './ContextMenuGroup.vue';\nexport { default as ContextMenuItem } from './ContextMenuItem.vue';\nexport { default as ContextMenuLabel } from './ContextMenuLabel.vue';\nexport { default as ContextMenuRadioGroup } from './ContextMenuRadioGroup.vue';\nexport { default as ContextMenuRadioItem } from './ContextMenuRadioItem.vue';\nexport { default as ContextMenuSeparator } from './ContextMenuSeparator.vue';\nexport { default as ContextMenuShortcut } from './ContextMenuShortcut.vue';\nexport { default as ContextMenuSub } from './ContextMenuSub.vue';\nexport { default as ContextMenuSubContent } from './ContextMenuSubContent.vue';\nexport { default as ContextMenuSubTrigger } from './ContextMenuSubTrigger.vue';\nexport { default as ContextMenuTrigger } from './ContextMenuTrigger.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/Dialog.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogRootEmits, DialogRootProps } from 'reka-ui';\n\nimport { DialogRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<DialogRootProps>();\nconst emits = defineEmits<DialogRootEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <DialogRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </DialogRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogClose.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogCloseProps } from 'reka-ui';\n\nimport { DialogClose } from 'reka-ui';\n\nconst props = defineProps<DialogCloseProps>();\n</script>\n\n<template>\n  <DialogClose v-bind=\"props\">\n    <slot></slot>\n  </DialogClose>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogContentEmits, DialogContentProps } from 'reka-ui';\n\nimport type { ClassType } from '@vben-core/typings';\n\nimport { computed, ref } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { X } from 'lucide-vue-next';\nimport { DialogClose, DialogContent, useForwardPropsEmits } from 'reka-ui';\n\nimport DialogOverlay from './DialogOverlay.vue';\n\nconst props = withDefaults(\n  defineProps<\n    DialogContentProps & {\n      animationType?: 'scale' | 'slide';\n      appendTo?: HTMLElement | string;\n      class?: ClassType;\n      closeClass?: ClassType;\n      closeDisabled?: boolean;\n      modal?: boolean;\n      open?: boolean;\n      overlayBlur?: number;\n      showClose?: boolean;\n      zIndex?: number;\n    }\n  >(),\n  {\n    appendTo: 'body',\n    animationType: 'slide',\n    closeDisabled: false,\n    showClose: true,\n  },\n);\nconst emits = defineEmits<\n  DialogContentEmits & { close: []; closed: []; opened: [] }\n>();\n\nconst delegatedProps = computed(() => {\n  const {\n    class: _,\n    modal: _modal,\n    open: _open,\n    showClose: __,\n    animationType: ___,\n    ...delegated\n  } = props;\n\n  return delegated;\n});\n\nfunction isAppendToBody() {\n  return (\n    props.appendTo === 'body' ||\n    props.appendTo === document.body ||\n    !props.appendTo\n  );\n}\n\nconst position = computed(() => {\n  return isAppendToBody() ? 'fixed' : 'absolute';\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n\nconst contentRef = ref<InstanceType<typeof DialogContent> | null>(null);\nfunction onAnimationEnd(event: AnimationEvent) {\n  // 只有在 contentRef 的动画结束时才触发 opened/closed 事件\n  if (event.target === contentRef.value?.$el) {\n    if (props.open) {\n      emits('opened');\n    } else {\n      emits('closed');\n    }\n  }\n}\ndefineExpose({\n  getContentRef: () => contentRef.value,\n});\n</script>\n\n<template>\n  <Teleport defer :to=\"appendTo\">\n    <Transition name=\"fade\">\n      <DialogOverlay\n        v-if=\"open && modal\"\n        :style=\"{\n          ...(zIndex ? { zIndex } : {}),\n          position,\n          backdropFilter:\n            overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',\n        }\"\n        @click=\"() => emits('close')\"\n      />\n    </Transition>\n    <DialogContent\n      ref=\"contentRef\"\n      :style=\"{ ...(zIndex ? { zIndex } : {}), position }\"\n      @animationend=\"onAnimationEnd\"\n      v-bind=\"forwarded\"\n      :class=\"\n        cn(\n          'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 w-full p-6 shadow-lg outline-hidden sm:rounded-xl',\n          {\n            'data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%]':\n              animationType === 'slide',\n          },\n          props.class,\n        )\n      \"\n    >\n      <slot></slot>\n\n      <DialogClose\n        v-if=\"showClose\"\n        :disabled=\"closeDisabled\"\n        :class=\"\n          cn(\n            'flex-center text-foreground/80 hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-3 right-3 h-6 w-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none',\n            props.closeClass,\n          )\n        \"\n        @click=\"() => emits('close')\"\n      >\n        <X class=\"h-4 w-4\" />\n      </DialogClose>\n    </DialogContent>\n  </Teleport>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogDescription.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogDescriptionProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { DialogDescription, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<DialogDescriptionProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <DialogDescription\n    v-bind=\"forwardedProps\"\n    :class=\"cn('text-muted-foreground text-sm', props.class)\"\n  >\n    <slot></slot>\n  </DialogDescription>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogFooter.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{ class?: any }>();\n</script>\n\n<template>\n  <div\n    :class=\"\n      cn('flex flex-col-reverse flex-row justify-end gap-x-2', props.class)\n    \"\n  >\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogHeader.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <div\n    :class=\"cn('flex flex-col gap-y-1.5 text-center sm:text-left', props.class)\"\n  >\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogOverlay.vue",
    "content": "<script setup lang=\"ts\">\nimport { inject } from 'vue';\n\nimport { useScrollLock } from '@vben-core/composables';\n\nuseScrollLock();\nconst id = inject('DISMISSABLE_MODAL_ID');\n</script>\n<template>\n  <div :data-dismissable-modal=\"id\" class=\"z-popup bg-overlay inset-0\"></div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogScrollContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogContentEmits, DialogContentProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { X } from 'lucide-vue-next';\nimport {\n  DialogClose,\n  DialogContent,\n  DialogOverlay,\n  DialogPortal,\n  useForwardPropsEmits,\n} from 'reka-ui';\n\nconst props = withDefaults(\n  defineProps<DialogContentProps & { class?: any; zIndex?: number }>(),\n  { zIndex: 1000 },\n);\nconst emits = defineEmits<DialogContentEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <DialogPortal>\n    <DialogOverlay\n      :style=\"{ zIndex }\"\n      class=\"border-border data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 absolute inset-0 grid place-items-center overflow-y-auto border bg-black/80\"\n    >\n      <DialogContent\n        :class=\"\n          cn(\n            'border-border bg-background relative z-50 my-8 grid w-full max-w-lg gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',\n            props.class,\n          )\n        \"\n        :style=\"{ zIndex }\"\n        v-bind=\"forwarded\"\n        @pointer-down-outside=\"\n          (event) => {\n            const originalEvent = event.detail.originalEvent;\n            const target = originalEvent.target as HTMLElement;\n            if (\n              originalEvent.offsetX > target.clientWidth ||\n              originalEvent.offsetY > target.clientHeight\n            ) {\n              event.preventDefault();\n            }\n          }\n        \"\n      >\n        <slot></slot>\n\n        <DialogClose\n          class=\"hover:bg-secondary absolute top-4 right-4 rounded-md p-0.5 transition-colors\"\n        >\n          <X class=\"h-4 w-4\" />\n          <span class=\"sr-only\">Close</span>\n        </DialogClose>\n      </DialogContent>\n    </DialogOverlay>\n  </DialogPortal>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogTitle.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogTitleProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { DialogTitle, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<DialogTitleProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <DialogTitle\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn('text-lg leading-none font-semibold tracking-tight', props.class)\n    \"\n  >\n    <slot></slot>\n  </DialogTitle>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogTriggerProps } from 'reka-ui';\n\nimport { DialogTrigger } from 'reka-ui';\n\nconst props = defineProps<DialogTriggerProps>();\n</script>\n\n<template>\n  <DialogTrigger v-bind=\"props\">\n    <slot></slot>\n  </DialogTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dialog/index.ts",
    "content": "export { default as Dialog } from './Dialog.vue';\nexport { default as DialogClose } from './DialogClose.vue';\nexport { default as DialogContent } from './DialogContent.vue';\nexport { default as DialogDescription } from './DialogDescription.vue';\nexport { default as DialogFooter } from './DialogFooter.vue';\nexport { default as DialogHeader } from './DialogHeader.vue';\nexport { default as DialogScrollContent } from './DialogScrollContent.vue';\nexport { default as DialogTitle } from './DialogTitle.vue';\nexport { default as DialogTrigger } from './DialogTrigger.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenu.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DropdownMenuRootEmits, DropdownMenuRootProps } from 'reka-ui';\n\nimport { DropdownMenuRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = withDefaults(defineProps<DropdownMenuRootProps>(), {\n  modal: false,\n});\nconst emits = defineEmits<DropdownMenuRootEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <DropdownMenuRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </DropdownMenuRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuCheckboxItem.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  DropdownMenuCheckboxItemEmits,\n  DropdownMenuCheckboxItemProps,\n} from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Check } from 'lucide-vue-next';\nimport {\n  DropdownMenuCheckboxItem,\n  DropdownMenuItemIndicator,\n  useForwardPropsEmits,\n} from 'reka-ui';\n\nconst props = defineProps<DropdownMenuCheckboxItemProps & { class?: any }>();\nconst emits = defineEmits<DropdownMenuCheckboxItemEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <DropdownMenuCheckboxItem\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n        props.class,\n      )\n    \"\n  >\n    <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuItemIndicator>\n        <Check class=\"h-4 w-4\" />\n      </DropdownMenuItemIndicator>\n    </span>\n    <slot></slot>\n  </DropdownMenuCheckboxItem>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  DropdownMenuContentEmits,\n  DropdownMenuContentProps,\n} from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport {\n  DropdownMenuContent,\n  DropdownMenuPortal,\n  useForwardPropsEmits,\n} from 'reka-ui';\n\nconst props = withDefaults(\n  defineProps<DropdownMenuContentProps & { class?: any }>(),\n  {\n    sideOffset: 4,\n  },\n);\nconst emits = defineEmits<DropdownMenuContentEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <DropdownMenuPortal>\n    <DropdownMenuContent\n      v-bind=\"forwarded\"\n      :class=\"\n        cn(\n          'z-popup border-border bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 min-w-32 overflow-hidden rounded-md border p-1 shadow-md',\n          props.class,\n        )\n      \"\n    >\n      <slot></slot>\n    </DropdownMenuContent>\n  </DropdownMenuPortal>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuGroup.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DropdownMenuGroupProps } from 'reka-ui';\n\nimport { DropdownMenuGroup } from 'reka-ui';\n\nconst props = defineProps<DropdownMenuGroupProps>();\n</script>\n\n<template>\n  <DropdownMenuGroup v-bind=\"props\">\n    <slot></slot>\n  </DropdownMenuGroup>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuItem.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DropdownMenuItemProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { DropdownMenuItem, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<\n  DropdownMenuItemProps & { class?: any; inset?: boolean }\n>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <DropdownMenuItem\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn(\n        'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden transition-colors select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n        inset && 'pl-8',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </DropdownMenuItem>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuLabel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DropdownMenuLabelProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { DropdownMenuLabel, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<\n  DropdownMenuLabelProps & { class?: any; inset?: boolean }\n>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <DropdownMenuLabel\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)\n    \"\n  >\n    <slot></slot>\n  </DropdownMenuLabel>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuRadioGroup.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  DropdownMenuRadioGroupEmits,\n  DropdownMenuRadioGroupProps,\n} from 'reka-ui';\n\nimport { DropdownMenuRadioGroup, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<DropdownMenuRadioGroupProps>();\nconst emits = defineEmits<DropdownMenuRadioGroupEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <DropdownMenuRadioGroup v-bind=\"forwarded\">\n    <slot></slot>\n  </DropdownMenuRadioGroup>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuRadioItem.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  DropdownMenuRadioItemEmits,\n  DropdownMenuRadioItemProps,\n} from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Circle } from 'lucide-vue-next';\nimport {\n  DropdownMenuItemIndicator,\n  DropdownMenuRadioItem,\n  useForwardPropsEmits,\n} from 'reka-ui';\n\nconst props = defineProps<DropdownMenuRadioItemProps & { class?: any }>();\n\nconst emits = defineEmits<DropdownMenuRadioItemEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <DropdownMenuRadioItem\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n        props.class,\n      )\n    \"\n  >\n    <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuItemIndicator>\n        <Circle class=\"h-2 w-2 fill-current\" />\n      </DropdownMenuItemIndicator>\n    </span>\n    <slot></slot>\n  </DropdownMenuRadioItem>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuSeparator.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DropdownMenuSeparatorProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { DropdownMenuSeparator } from 'reka-ui';\n\nconst props = defineProps<\n  DropdownMenuSeparatorProps & {\n    class?: any;\n  }\n>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <DropdownMenuSeparator\n    v-bind=\"delegatedProps\"\n    :class=\"cn('bg-border -mx-1 my-1 h-px', props.class)\"\n  />\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuShortcut.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <span :class=\"cn('ml-auto text-xs tracking-widest opacity-60', props.class)\">\n    <slot></slot>\n  </span>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuSub.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DropdownMenuSubEmits, DropdownMenuSubProps } from 'reka-ui';\n\nimport { DropdownMenuSub, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<DropdownMenuSubProps>();\nconst emits = defineEmits<DropdownMenuSubEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <DropdownMenuSub v-bind=\"forwarded\">\n    <slot></slot>\n  </DropdownMenuSub>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuSubContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  DropdownMenuSubContentEmits,\n  DropdownMenuSubContentProps,\n} from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { DropdownMenuSubContent, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<DropdownMenuSubContentProps & { class?: any }>();\nconst emits = defineEmits<DropdownMenuSubContentEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <DropdownMenuSubContent\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'z-50 min-w-32 overflow-hidden rounded-md',\n        'border-border border',\n        'bg-popover text-popover-foreground p-1 shadow-lg',\n        'data-[state=open]:animate-in data-[state=closed]:animate-out',\n        'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n        'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',\n        'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </DropdownMenuSubContent>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuSubTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DropdownMenuSubTriggerProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronRight } from 'lucide-vue-next';\nimport { DropdownMenuSubTrigger, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<DropdownMenuSubTriggerProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <DropdownMenuSubTrigger\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn(\n        'focus:bg-accent data-[state=open]:bg-accent flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n    <ChevronRight class=\"ml-auto h-4 w-4\" />\n  </DropdownMenuSubTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/DropdownMenuTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DropdownMenuTriggerProps } from 'reka-ui';\n\nimport { DropdownMenuTrigger, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<DropdownMenuTriggerProps>();\n\nconst forwardedProps = useForwardProps(props);\n</script>\n\n<template>\n  <DropdownMenuTrigger class=\"outline-hidden\" v-bind=\"forwardedProps\">\n    <slot></slot>\n  </DropdownMenuTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/dropdown-menu/index.ts",
    "content": "export { default as DropdownMenu } from './DropdownMenu.vue';\n\nexport { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue';\nexport { default as DropdownMenuContent } from './DropdownMenuContent.vue';\nexport { default as DropdownMenuGroup } from './DropdownMenuGroup.vue';\nexport { default as DropdownMenuItem } from './DropdownMenuItem.vue';\nexport { default as DropdownMenuLabel } from './DropdownMenuLabel.vue';\nexport { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue';\nexport { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue';\nexport { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue';\nexport { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue';\nexport { default as DropdownMenuSub } from './DropdownMenuSub.vue';\nexport { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue';\nexport { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue';\nexport { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue';\nexport { DropdownMenuPortal } from 'reka-ui';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/form/FormControl.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Slot } from 'reka-ui';\n\nimport { useFormField } from './useFormField';\n\nconst { error, formDescriptionId, formItemId, formMessageId } = useFormField();\n</script>\n\n<template>\n  <Slot\n    :id=\"formItemId\"\n    :aria-describedby=\"\n      !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`\n    \"\n    :aria-invalid=\"!!error\"\n  >\n    <slot></slot>\n  </Slot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/form/FormDescription.vue",
    "content": "<script lang=\"ts\" setup>\nimport { cn } from '@vben-core/shared/utils';\n\nimport { useFormField } from './useFormField';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n\nconst { formDescriptionId } = useFormField();\n</script>\n\n<template>\n  <p\n    :id=\"formDescriptionId\"\n    :class=\"cn('text-muted-foreground text-sm', props.class)\"\n  >\n    <slot></slot>\n  </p>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/form/FormItem.vue",
    "content": "<script lang=\"ts\" setup>\nimport { provide, useId } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { FORM_ITEM_INJECTION_KEY } from './injectionKeys';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n\nconst id = useId() as string;\nprovide(FORM_ITEM_INJECTION_KEY, id);\n</script>\n\n<template>\n  <div :class=\"cn(props.class)\">\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/form/FormLabel.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { LabelProps } from 'reka-ui';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Label } from '../label';\nimport { useFormField } from './useFormField';\n\nconst props = defineProps<LabelProps & { class?: any }>();\n\nconst { formItemId } = useFormField();\n</script>\n\n<template>\n  <Label :class=\"cn(props.class)\" :for=\"formItemId\">\n    <slot></slot>\n  </Label>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/form/FormMessage.vue",
    "content": "<script lang=\"ts\" setup>\nimport { toValue } from 'vue';\n\nimport { ErrorMessage } from 'vee-validate';\n\nimport { useFormField } from './useFormField';\n\nconst { formMessageId, name } = useFormField();\n</script>\n\n<template>\n  <ErrorMessage\n    :id=\"formMessageId\"\n    :name=\"toValue(name)\"\n    as=\"p\"\n    class=\"text-destructive text-[0.8rem]\"\n  />\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/form/index.ts",
    "content": "export { default as FormControl } from './FormControl.vue';\nexport { default as FormDescription } from './FormDescription.vue';\nexport { default as FormItem } from './FormItem.vue';\nexport { default as FormLabel } from './FormLabel.vue';\nexport { default as FormMessage } from './FormMessage.vue';\nexport { FORM_ITEM_INJECTION_KEY } from './injectionKeys';\nexport {\n  Form,\n  Field as FormField,\n  FieldArray as FormFieldArray,\n} from 'vee-validate';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/form/injectionKeys.ts",
    "content": "import type { InjectionKey } from 'vue';\n\nexport const FORM_ITEM_INJECTION_KEY = Symbol() as InjectionKey<string>;\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/form/useFormField.ts",
    "content": "import { inject } from 'vue';\n\nimport {\n  FieldContextKey,\n  useFieldError,\n  useIsFieldDirty,\n  useIsFieldTouched,\n  useIsFieldValid,\n} from 'vee-validate';\n\nimport { FORM_ITEM_INJECTION_KEY } from './injectionKeys';\n\nexport function useFormField() {\n  const fieldContext = inject(FieldContextKey);\n  const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY);\n\n  if (!fieldContext)\n    throw new Error('useFormField should be used within <FormField>');\n\n  const { name } = fieldContext;\n  const id = fieldItemContext;\n\n  const fieldState = {\n    error: useFieldError(name),\n    isDirty: useIsFieldDirty(name),\n    isTouched: useIsFieldTouched(name),\n    valid: useIsFieldValid(name),\n  };\n\n  return {\n    formDescriptionId: `${id}-form-item-description`,\n    formItemId: `${id}-form-item`,\n    formMessageId: `${id}-form-item-message`,\n    id,\n    name,\n    ...fieldState,\n  };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/hover-card/HoverCard.vue",
    "content": "<script setup lang=\"ts\">\nimport type { HoverCardRootEmits, HoverCardRootProps } from 'reka-ui';\n\nimport { HoverCardRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<HoverCardRootProps>();\nconst emits = defineEmits<HoverCardRootEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <HoverCardRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </HoverCardRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/hover-card/HoverCardContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { HoverCardContentProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { HoverCardContent, HoverCardPortal, useForwardProps } from 'reka-ui';\n\nconst props = withDefaults(\n  defineProps<HoverCardContentProps & { class?: any }>(),\n  {\n    sideOffset: 4,\n  },\n);\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <HoverCardPortal>\n    <HoverCardContent\n      v-bind=\"forwardedProps\"\n      :class=\"\n        cn(\n          'z-popup border-border bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 w-64 rounded-md border p-4 shadow-md outline-hidden',\n          props.class,\n        )\n      \"\n    >\n      <slot></slot>\n    </HoverCardContent>\n  </HoverCardPortal>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/hover-card/HoverCardTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { HoverCardTriggerProps } from 'reka-ui';\n\nimport { HoverCardTrigger } from 'reka-ui';\n\nconst props = defineProps<HoverCardTriggerProps>();\n</script>\n\n<template>\n  <HoverCardTrigger v-bind=\"props\">\n    <slot></slot>\n  </HoverCardTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/hover-card/index.ts",
    "content": "export { default as HoverCard } from './HoverCard.vue';\nexport { default as HoverCardContent } from './HoverCardContent.vue';\nexport { default as HoverCardTrigger } from './HoverCardTrigger.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/index.ts",
    "content": "export * from './accordion';\nexport * from './alert-dialog';\nexport * from './avatar';\nexport * from './badge';\nexport * from './breadcrumb';\nexport * from './button';\nexport * from './card';\nexport * from './checkbox';\nexport * from './dialog';\nexport * from './dropdown-menu';\nexport * from './form';\nexport * from './hover-card';\nexport * from './input';\nexport * from './label';\nexport * from './number-field';\nexport * from './pagination';\nexport * from './pin-input';\nexport * from './popover';\nexport * from './radio-group';\nexport * from './resizable';\nexport * from './scroll-area';\nexport * from './select';\nexport * from './separator';\nexport * from './sheet';\nexport * from './switch';\nexport * from './tabs';\nexport * from './textarea';\nexport * from './toggle';\nexport * from './toggle-group';\nexport * from './tooltip';\nexport * from './tree';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/input/Input.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nimport { useVModel } from '@vueuse/core';\n\nconst props = defineProps<{\n  class?: any;\n  defaultValue?: number | string;\n  modelValue?: number | string;\n}>();\n\nconst emits = defineEmits<{\n  (e: 'update:modelValue', payload: number | string): void;\n}>();\n\nconst modelValue = useVModel(props, 'modelValue', emits, {\n  defaultValue: props.defaultValue,\n  passive: true,\n});\n</script>\n\n<template>\n  <input\n    v-model=\"modelValue\"\n    :class=\"\n      cn(\n        'border-input bg-background ring-offset-background placeholder:text-muted-foreground/50 focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-1 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n        props.class,\n      )\n    \"\n  />\n</template>\n<style lang=\"scss\" scoped>\ninput {\n  --ring: var(--primary);\n\n  &::-ms-reveal,\n  &::-ms-clear {\n    display: none;\n  }\n\n  &::-webkit-credentials-auto-fill-button,\n  &::-webkit-inner-spin-button,\n  &::-webkit-outer-spin-button {\n    display: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/input/index.ts",
    "content": "export { default as Input } from './Input.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/label/Label.vue",
    "content": "<script setup lang=\"ts\">\nimport type { LabelProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Label } from 'reka-ui';\n\nconst props = defineProps<LabelProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <Label\n    v-bind=\"delegatedProps\"\n    :class=\"\n      cn(\n        'text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </Label>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/label/index.ts",
    "content": "export { default as Label } from './Label.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/number-field/NumberField.vue",
    "content": "<script setup lang=\"ts\">\nimport type { NumberFieldRootEmits, NumberFieldRootProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { NumberFieldRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<NumberFieldRootProps & { class?: any }>();\nconst emits = defineEmits<NumberFieldRootEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <NumberFieldRoot v-bind=\"forwarded\" :class=\"cn('grid gap-1.5', props.class)\">\n    <slot></slot>\n  </NumberFieldRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/number-field/NumberFieldContent.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{\n  class?: any;\n}>();\n</script>\n\n<template>\n  <div\n    :class=\"\n      cn(\n        'relative [&>[data-slot=input]]:has-[[data-slot=decrement]]:pl-5 [&>[data-slot=input]]:has-[[data-slot=increment]]:pr-5',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/number-field/NumberFieldDecrement.vue",
    "content": "<script setup lang=\"ts\">\nimport type { NumberFieldDecrementProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Minus } from 'lucide-vue-next';\nimport { NumberFieldDecrement, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<NumberFieldDecrementProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <NumberFieldDecrement\n    data-slot=\"decrement\"\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'absolute top-1/2 left-0 -translate-y-1/2 p-3 disabled:cursor-not-allowed disabled:opacity-20',\n        props.class,\n      )\n    \"\n  >\n    <slot>\n      <Minus class=\"h-4 w-4\" />\n    </slot>\n  </NumberFieldDecrement>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/number-field/NumberFieldIncrement.vue",
    "content": "<script setup lang=\"ts\">\nimport type { NumberFieldIncrementProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Plus } from 'lucide-vue-next';\nimport { NumberFieldIncrement, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<NumberFieldIncrementProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <NumberFieldIncrement\n    data-slot=\"increment\"\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'absolute top-1/2 right-0 -translate-y-1/2 p-3 disabled:cursor-not-allowed disabled:opacity-20',\n        props.class,\n      )\n    \"\n  >\n    <slot>\n      <Plus class=\"h-4 w-4\" />\n    </slot>\n  </NumberFieldIncrement>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/number-field/NumberFieldInput.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nimport { NumberFieldInput } from 'reka-ui';\n</script>\n\n<template>\n  <NumberFieldInput\n    :class=\"\n      cn(\n        'border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent py-1 text-center text-sm shadow-xs transition-colors focus-visible:ring-1 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n      )\n    \"\n    data-slot=\"input\"\n  />\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/number-field/index.ts",
    "content": "export { default as NumberField } from './NumberField.vue';\nexport { default as NumberFieldContent } from './NumberFieldContent.vue';\nexport { default as NumberFieldDecrement } from './NumberFieldDecrement.vue';\nexport { default as NumberFieldIncrement } from './NumberFieldIncrement.vue';\nexport { default as NumberFieldInput } from './NumberFieldInput.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pagination/PaginationEllipsis.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PaginationEllipsisProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { MoreHorizontal } from 'lucide-vue-next';\nimport { PaginationEllipsis } from 'reka-ui';\n\nconst props = defineProps<PaginationEllipsisProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <PaginationEllipsis\n    v-bind=\"delegatedProps\"\n    :class=\"cn('flex size-8 items-center justify-center', props.class)\"\n  >\n    <slot>\n      <MoreHorizontal class=\"size-4\" />\n    </slot>\n  </PaginationEllipsis>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pagination/PaginationFirst.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PaginationFirstProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronsLeft } from 'lucide-vue-next';\nimport { PaginationFirst } from 'reka-ui';\n\nimport { Button } from '../button';\n\nconst props = withDefaults(\n  defineProps<PaginationFirstProps & { class?: any }>(),\n  {\n    asChild: true,\n  },\n);\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <PaginationFirst v-bind=\"delegatedProps\">\n    <Button :class=\"cn('size-8 p-0', props.class)\" variant=\"outline\">\n      <slot>\n        <ChevronsLeft class=\"size-4\" />\n      </slot>\n    </Button>\n  </PaginationFirst>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pagination/PaginationLast.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PaginationLastProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronsRight } from 'lucide-vue-next';\nimport { PaginationLast } from 'reka-ui';\n\nimport { Button } from '../button';\n\nconst props = withDefaults(\n  defineProps<PaginationLastProps & { class?: any }>(),\n  {\n    asChild: true,\n  },\n);\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <PaginationLast v-bind=\"delegatedProps\">\n    <Button :class=\"cn('size-8 p-0', props.class)\" variant=\"outline\">\n      <slot>\n        <ChevronsRight class=\"size-4\" />\n      </slot>\n    </Button>\n  </PaginationLast>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pagination/PaginationNext.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PaginationNextProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronRight } from 'lucide-vue-next';\nimport { PaginationNext } from 'reka-ui';\n\nimport { Button } from '../button';\n\nconst props = withDefaults(\n  defineProps<PaginationNextProps & { class?: any }>(),\n  {\n    asChild: true,\n  },\n);\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <PaginationNext v-bind=\"delegatedProps\">\n    <Button :class=\"cn('size-8 p-0', props.class)\" variant=\"outline\">\n      <slot>\n        <ChevronRight class=\"size-4\" />\n      </slot>\n    </Button>\n  </PaginationNext>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pagination/PaginationPrev.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PaginationPrevProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronLeft } from 'lucide-vue-next';\nimport { PaginationPrev } from 'reka-ui';\n\nimport { Button } from '../button';\n\nconst props = withDefaults(\n  defineProps<PaginationPrevProps & { class?: any }>(),\n  {\n    asChild: true,\n  },\n);\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <PaginationPrev v-bind=\"delegatedProps\">\n    <Button :class=\"cn('size-8 p-0', props.class)\" variant=\"outline\">\n      <slot>\n        <ChevronLeft class=\"size-4\" />\n      </slot>\n    </Button>\n  </PaginationPrev>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pagination/index.ts",
    "content": "export { default as PaginationEllipsis } from './PaginationEllipsis.vue';\nexport { default as PaginationFirst } from './PaginationFirst.vue';\nexport { default as PaginationLast } from './PaginationLast.vue';\nexport { default as PaginationNext } from './PaginationNext.vue';\nexport { default as PaginationPrev } from './PaginationPrev.vue';\nexport {\n  PaginationRoot as Pagination,\n  PaginationList,\n  PaginationListItem,\n} from 'reka-ui';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pin-input/PinInput.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PinInputRootEmits, PinInputRootProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { PinInputRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<PinInputRootProps & { class?: any }>();\nconst emits = defineEmits<PinInputRootEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <PinInputRoot\n    v-bind=\"forwarded\"\n    :class=\"cn('flex items-center gap-2', props.class)\"\n  >\n    <slot></slot>\n  </PinInputRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pin-input/PinInputGroup.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PrimitiveProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Primitive, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<PrimitiveProps & { class?: any }>();\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n  return delegated;\n});\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <Primitive\n    v-bind=\"forwardedProps\"\n    :class=\"cn('flex items-center', props.class)\"\n  >\n    <slot></slot>\n  </Primitive>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pin-input/PinInputInput.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PinInputInputProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { PinInputInput, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<PinInputInputProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <PinInputInput\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn(\n        'border-input bg-background relative flex h-10 w-8 items-center justify-center border-y border-r text-center text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md focus:relative focus:z-10 focus:ring-2 focus:outline-hidden md:w-10',\n        props.class,\n      )\n    \"\n  />\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pin-input/PinInputSeparator.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PrimitiveProps } from 'reka-ui';\n\nimport { Dot } from 'lucide-vue-next';\nimport { Primitive, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<PrimitiveProps>();\nconst forwardedProps = useForwardProps(props);\n</script>\n\n<template>\n  <Primitive v-bind=\"forwardedProps\">\n    <slot>\n      <Dot />\n    </slot>\n  </Primitive>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/pin-input/index.ts",
    "content": "export { default as PinInput } from './PinInput.vue';\nexport { default as PinInputGroup } from './PinInputGroup.vue';\nexport { default as PinInputInput } from './PinInputInput.vue';\nexport { default as PinInputSeparator } from './PinInputSeparator.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/popover/Popover.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PopoverRootEmits, PopoverRootProps } from 'reka-ui';\n\nimport { PopoverRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<PopoverRootProps>();\nconst emits = defineEmits<PopoverRootEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <PopoverRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </PopoverRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/popover/PopoverContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PopoverContentEmits, PopoverContentProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { PopoverContent, PopoverPortal, useForwardPropsEmits } from 'reka-ui';\n\ndefineOptions({\n  inheritAttrs: false,\n});\n\nconst props = withDefaults(\n  defineProps<PopoverContentProps & { class?: any }>(),\n  {\n    align: 'center',\n    sideOffset: 4,\n  },\n);\nconst emits = defineEmits<PopoverContentEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <PopoverPortal>\n    <PopoverContent\n      v-bind=\"{ ...forwarded, ...$attrs }\"\n      :class=\"\n        cn(\n          'border-border bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 w-72 rounded-md border p-4 shadow-md outline-hidden',\n          props.class,\n        )\n      \"\n    >\n      <slot></slot>\n    </PopoverContent>\n  </PopoverPortal>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/popover/PopoverTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PopoverTriggerProps } from 'reka-ui';\n\nimport { PopoverTrigger } from 'reka-ui';\n\nconst props = defineProps<PopoverTriggerProps>();\n</script>\n\n<template>\n  <PopoverTrigger v-bind=\"props\">\n    <slot></slot>\n  </PopoverTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/popover/index.ts",
    "content": "export { default as Popover } from './Popover.vue';\nexport { default as PopoverContent } from './PopoverContent.vue';\nexport { default as PopoverTrigger } from './PopoverTrigger.vue';\nexport { PopoverAnchor } from 'reka-ui';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/radio-group/RadioGroup.vue",
    "content": "<script setup lang=\"ts\">\nimport type { RadioGroupRootEmits, RadioGroupRootProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { RadioGroupRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<RadioGroupRootProps & { class?: any }>();\nconst emits = defineEmits<RadioGroupRootEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <RadioGroupRoot :class=\"cn('grid gap-2', props.class)\" v-bind=\"forwarded\">\n    <slot></slot>\n  </RadioGroupRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/radio-group/RadioGroupItem.vue",
    "content": "<script setup lang=\"ts\">\nimport type { RadioGroupItemProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Circle } from 'lucide-vue-next';\nimport { RadioGroupIndicator, RadioGroupItem, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<RadioGroupItemProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <RadioGroupItem\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn(\n        'border-primary text-primary focus-visible:ring-ring aspect-square h-4 w-4 rounded-full border shadow-sm focus:outline-hidden focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50',\n        props.class,\n      )\n    \"\n  >\n    <RadioGroupIndicator class=\"flex items-center justify-center\">\n      <Circle class=\"h-2.5 w-2.5 fill-current text-current\" />\n    </RadioGroupIndicator>\n  </RadioGroupItem>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/radio-group/index.ts",
    "content": "export { default as RadioGroup } from './RadioGroup.vue';\nexport { default as RadioGroupItem } from './RadioGroupItem.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/resizable/ResizableHandle.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  SplitterResizeHandleEmits,\n  SplitterResizeHandleProps,\n} from 'reka-ui';\n\nimport type { HTMLAttributes } from 'vue';\n\nimport { computed } from 'vue';\n\nimport { GripVertical } from '@vben-core/icons';\nimport { cn } from '@vben-core/shared/utils';\n\nimport { SplitterResizeHandle, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<\n  SplitterResizeHandleProps & {\n    class?: HTMLAttributes['class'];\n    withHandle?: boolean;\n  }\n>();\nconst emits = defineEmits<SplitterResizeHandleEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <SplitterResizeHandle\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden [&[data-orientation=vertical]]:h-px [&[data-orientation=vertical]]:w-full [&[data-orientation=vertical]]:after:left-0 [&[data-orientation=vertical]]:after:h-1 [&[data-orientation=vertical]]:after:w-full [&[data-orientation=vertical]]:after:translate-x-0 [&[data-orientation=vertical]]:after:-translate-y-1/2 [&[data-orientation=vertical]>div]:rotate-90',\n        props.class,\n      )\n    \"\n  >\n    <template v-if=\"props.withHandle\">\n      <div\n        class=\"bg-border z-10 flex h-4 w-3 items-center justify-center rounded-sm border\"\n      >\n        <GripVertical class=\"h-2.5 w-2.5\" />\n      </div>\n    </template>\n  </SplitterResizeHandle>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/resizable/ResizablePanelGroup.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SplitterGroupEmits, SplitterGroupProps } from 'reka-ui';\n\nimport type { HTMLAttributes } from 'vue';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { SplitterGroup, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<\n  SplitterGroupProps & { class?: HTMLAttributes['class'] }\n>();\nconst emits = defineEmits<SplitterGroupEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <SplitterGroup\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'flex h-full w-full data-[panel-group-direction=vertical]:flex-col',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </SplitterGroup>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/resizable/index.ts",
    "content": "export { default as ResizableHandle } from './ResizableHandle.vue';\nexport { default as ResizablePanelGroup } from './ResizablePanelGroup.vue';\nexport { SplitterPanel as ResizablePanel } from 'reka-ui';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/scroll-area/ScrollArea.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ScrollAreaRootProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ScrollAreaCorner, ScrollAreaRoot, ScrollAreaViewport } from 'reka-ui';\n\nimport ScrollBar from './ScrollBar.vue';\n\nconst props = withDefaults(\n  defineProps<\n    ScrollAreaRootProps & {\n      class?: any;\n      onScroll?: (event: Event) => void;\n      viewportProps?: { onScroll: (event: Event) => void };\n    }\n  >(),\n  {\n    onScroll: () => {},\n  },\n);\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n  return delegated;\n});\n</script>\n\n<template>\n  <ScrollAreaRoot\n    v-bind=\"delegatedProps\"\n    :class=\"cn('relative overflow-hidden', props.class)\"\n  >\n    <ScrollAreaViewport\n      as-child\n      class=\"h-full w-full rounded-[inherit] focus:outline-hidden\"\n      @scroll=\"onScroll\"\n    >\n      <slot></slot>\n    </ScrollAreaViewport>\n    <ScrollBar />\n    <ScrollAreaCorner />\n  </ScrollAreaRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/scroll-area/ScrollBar.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ScrollAreaScrollbarProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ScrollAreaScrollbar, ScrollAreaThumb } from 'reka-ui';\n\nconst props = withDefaults(\n  defineProps<ScrollAreaScrollbarProps & { class?: any }>(),\n  {\n    orientation: 'vertical',\n  },\n);\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <ScrollAreaScrollbar\n    v-bind=\"delegatedProps\"\n    :class=\"\n      cn(\n        'flex touch-none transition-colors select-none',\n        orientation === 'vertical' &&\n          'h-full w-2.5 border-l border-l-transparent p-px',\n        orientation === 'horizontal' &&\n          'h-2.5 flex-col border-t border-t-transparent p-px',\n        props.class,\n      )\n    \"\n  >\n    <ScrollAreaThumb class=\"bg-border relative flex-1 rounded-full\" />\n  </ScrollAreaScrollbar>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/scroll-area/index.ts",
    "content": "export { default as ScrollArea } from './ScrollArea.vue';\nexport { default as ScrollBar } from './ScrollBar.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/Select.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectRootEmits, SelectRootProps } from 'reka-ui';\n\nimport { SelectRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<SelectRootProps>();\nconst emits = defineEmits<SelectRootEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <SelectRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </SelectRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectContentEmits, SelectContentProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport {\n  SelectContent,\n  SelectPortal,\n  SelectViewport,\n  useForwardPropsEmits,\n} from 'reka-ui';\n\nimport SelectScrollDownButton from './SelectScrollDownButton.vue';\nimport SelectScrollUpButton from './SelectScrollUpButton.vue';\n\ndefineOptions({\n  inheritAttrs: false,\n});\n\nconst props = withDefaults(\n  defineProps<SelectContentProps & { class?: any }>(),\n  {\n    position: 'popper',\n  },\n);\nconst emits = defineEmits<SelectContentEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <SelectPortal>\n    <SelectContent\n      v-bind=\"{ ...forwarded, ...$attrs }\"\n      :class=\"\n        cn(\n          'z-popup border-border bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative max-h-96 min-w-32 overflow-hidden rounded-md border shadow-md',\n          position === 'popper' &&\n            'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n          props.class,\n        )\n      \"\n    >\n      <SelectScrollUpButton />\n      <SelectViewport\n        :class=\"\n          cn(\n            'p-1',\n            position === 'popper' &&\n              'h-(--reka-select-trigger-height) w-full min-w-(--reka-select-trigger-width)',\n          )\n        \"\n      >\n        <slot></slot>\n      </SelectViewport>\n      <SelectScrollDownButton />\n    </SelectContent>\n  </SelectPortal>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectGroup.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectGroupProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { SelectGroup } from 'reka-ui';\n\nconst props = defineProps<SelectGroupProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <SelectGroup :class=\"cn('w-full p-1', props.class)\" v-bind=\"delegatedProps\">\n    <slot></slot>\n  </SelectGroup>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectItem.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectItemProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Check } from 'lucide-vue-next';\nimport {\n  SelectItem,\n  SelectItemIndicator,\n  SelectItemText,\n  useForwardProps,\n} from 'reka-ui';\n\nconst props = defineProps<SelectItemProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <SelectItem\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn(\n        'focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n        props.class,\n      )\n    \"\n  >\n    <span class=\"absolute right-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <SelectItemIndicator>\n        <Check class=\"h-4 w-4\" />\n      </SelectItemIndicator>\n    </span>\n\n    <SelectItemText>\n      <slot></slot>\n    </SelectItemText>\n  </SelectItem>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectItemText.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectItemTextProps } from 'reka-ui';\n\nimport { SelectItemText } from 'reka-ui';\n\nconst props = defineProps<SelectItemTextProps>();\n</script>\n\n<template>\n  <SelectItemText v-bind=\"props\">\n    <slot></slot>\n  </SelectItemText>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectLabel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectLabelProps } from 'reka-ui';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { SelectLabel } from 'reka-ui';\n\nconst props = defineProps<SelectLabelProps & { class?: any }>();\n</script>\n\n<template>\n  <SelectLabel :class=\"cn('px-2 py-1.5 text-sm font-semibold', props.class)\">\n    <slot></slot>\n  </SelectLabel>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectScrollDownButton.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectScrollDownButtonProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronDown } from 'lucide-vue-next';\nimport { SelectScrollDownButton, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<SelectScrollDownButtonProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <SelectScrollDownButton\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn('flex cursor-default items-center justify-center py-1', props.class)\n    \"\n  >\n    <slot>\n      <ChevronDown class=\"h-4 w-4\" />\n    </slot>\n  </SelectScrollDownButton>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectScrollUpButton.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectScrollUpButtonProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronUp } from 'lucide-vue-next';\nimport { SelectScrollUpButton, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<SelectScrollUpButtonProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <SelectScrollUpButton\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn('flex cursor-default items-center justify-center py-1', props.class)\n    \"\n  >\n    <slot>\n      <ChevronUp class=\"h-4 w-4\" />\n    </slot>\n  </SelectScrollUpButton>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectSeparator.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectSeparatorProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { SelectSeparator } from 'reka-ui';\n\nconst props = defineProps<SelectSeparatorProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <SelectSeparator\n    v-bind=\"delegatedProps\"\n    :class=\"cn('bg-muted -mx-1 my-1 h-px', props.class)\"\n  />\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectTriggerProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ChevronDown } from 'lucide-vue-next';\nimport { SelectIcon, SelectTrigger, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<SelectTriggerProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <SelectTrigger\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn(\n        'border-input ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs focus:ring-1 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n    <SelectIcon as-child>\n      <ChevronDown class=\"h-4 w-4 opacity-50\" />\n    </SelectIcon>\n  </SelectTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/SelectValue.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectValueProps } from 'reka-ui';\n\nimport { SelectValue } from 'reka-ui';\n\nconst props = defineProps<SelectValueProps>();\n</script>\n\n<template>\n  <SelectValue v-bind=\"props\">\n    <slot></slot>\n  </SelectValue>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/select/index.ts",
    "content": "export { default as Select } from './Select.vue';\nexport { default as SelectContent } from './SelectContent.vue';\nexport { default as SelectGroup } from './SelectGroup.vue';\nexport { default as SelectItem } from './SelectItem.vue';\nexport { default as SelectItemText } from './SelectItemText.vue';\nexport { default as SelectLabel } from './SelectLabel.vue';\nexport { default as SelectScrollDownButton } from './SelectScrollDownButton.vue';\nexport { default as SelectScrollUpButton } from './SelectScrollUpButton.vue';\nexport { default as SelectSeparator } from './SelectSeparator.vue';\nexport { default as SelectTrigger } from './SelectTrigger.vue';\nexport { default as SelectValue } from './SelectValue.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/separator/Separator.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SeparatorProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Separator } from 'reka-ui';\n\nconst props = defineProps<SeparatorProps & { class?: any; label?: string }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <Separator\n    v-bind=\"delegatedProps\"\n    :class=\"\n      cn(\n        'bg-border relative shrink-0',\n        props.orientation === 'vertical' ? 'h-full w-px' : 'h-px w-full',\n        props.class,\n      )\n    \"\n  >\n    <span\n      v-if=\"props.label\"\n      :class=\"\n        cn(\n          'bg-background text-muted-foreground absolute top-1/2 left-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center justify-center text-xs',\n          props.orientation === 'vertical'\n            ? 'w-0.25 px-1 py-2'\n            : 'h-0.25 px-2 py-1',\n        )\n      \"\n    >\n      {{ props.label }}\n    </span>\n  </Separator>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/separator/index.ts",
    "content": "export { default as Separator } from './Separator.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/Sheet.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogRootEmits, DialogRootProps } from 'reka-ui';\n\nimport { DialogRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<DialogRootProps>();\nconst emits = defineEmits<DialogRootEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <DialogRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </DialogRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/SheetClose.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogCloseProps } from 'reka-ui';\n\nimport { DialogClose } from 'reka-ui';\n\nconst props = defineProps<DialogCloseProps>();\n</script>\n\n<template>\n  <DialogClose v-bind=\"props\">\n    <slot></slot>\n  </DialogClose>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/SheetContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogContentEmits, DialogContentProps } from 'reka-ui';\n\nimport type { SheetVariants } from './sheet';\n\nimport { computed, ref } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { DialogContent, useForwardPropsEmits } from 'reka-ui';\n\nimport { sheetVariants } from './sheet';\nimport SheetOverlay from './SheetOverlay.vue';\n\ninterface SheetContentProps extends DialogContentProps {\n  appendTo?: HTMLElement | string;\n  class?: any;\n  modal?: boolean;\n  open?: boolean;\n  overlayBlur?: number;\n  side?: SheetVariants['side'];\n  zIndex?: number;\n}\n\ndefineOptions({\n  inheritAttrs: false,\n});\n\nconst props = withDefaults(defineProps<SheetContentProps>(), {\n  appendTo: 'body',\n});\n\nconst emits = defineEmits<\n  DialogContentEmits & { close: []; closed: []; opened: [] }\n>();\n\nconst delegatedProps = computed(() => {\n  const {\n    class: _,\n    modal: _modal,\n    open: _open,\n    side: _side,\n    ...delegated\n  } = props;\n\n  return delegated;\n});\n\nfunction isAppendToBody() {\n  return (\n    props.appendTo === 'body' ||\n    props.appendTo === document.body ||\n    !props.appendTo\n  );\n}\n\nconst position = computed(() => {\n  return isAppendToBody() ? 'fixed' : 'absolute';\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\nconst contentRef = ref<InstanceType<typeof DialogContent> | null>(null);\nfunction onAnimationEnd(event: AnimationEvent) {\n  // 只有在 contentRef 的动画结束时才触发 opened/closed 事件\n  if (event.target === contentRef.value?.$el) {\n    if (props.open) {\n      emits('opened');\n    } else {\n      emits('closed');\n    }\n  }\n}\n</script>\n\n<template>\n  <Teleport defer :to=\"appendTo\">\n    <Transition name=\"fade\">\n      <SheetOverlay\n        v-if=\"open && modal\"\n        :style=\"{\n          ...(zIndex ? { zIndex } : {}),\n          position,\n          backdropFilter:\n            overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',\n        }\"\n      />\n    </Transition>\n    <DialogContent\n      ref=\"contentRef\"\n      :class=\"cn('z-popup', sheetVariants({ side }), props.class)\"\n      :style=\"{\n        ...(zIndex ? { zIndex } : {}),\n        position,\n      }\"\n      @animationend=\"onAnimationEnd\"\n      v-bind=\"{ ...forwarded, ...$attrs }\"\n    >\n      <slot></slot>\n\n      <!-- <DialogClose\n        class=\"data-[state=open]:bg-secondary absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none\"\n      >\n        <Cross2Icon class=\"h-5 w-\" />\n      </DialogClose> -->\n    </DialogContent>\n  </Teleport>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/SheetDescription.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogDescriptionProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { DialogDescription } from 'reka-ui';\n\nconst props = defineProps<DialogDescriptionProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <DialogDescription\n    :class=\"cn('text-muted-foreground text-sm', props.class)\"\n    v-bind=\"delegatedProps\"\n  >\n    <slot></slot>\n  </DialogDescription>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/SheetFooter.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{ class?: any }>();\n</script>\n\n<template>\n  <div\n    :class=\"\n      cn('flex flex-col-reverse flex-row justify-end gap-x-2', props.class)\n    \"\n  >\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/SheetHeader.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nconst props = defineProps<{ class?: any }>();\n</script>\n\n<template>\n  <div :class=\"cn('flex flex-col text-center sm:text-left', props.class)\">\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/SheetOverlay.vue",
    "content": "<script setup lang=\"ts\">\nimport { inject } from 'vue';\n\nimport { useScrollLock } from '@vben-core/composables';\n\nuseScrollLock();\nconst id = inject('DISMISSABLE_DRAWER_ID');\n</script>\n<template>\n  <div :data-dismissable-drawer=\"id\" class=\"z-popup bg-overlay inset-0\"></div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/SheetTitle.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogTitleProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { DialogTitle } from 'reka-ui';\n\nconst props = defineProps<DialogTitleProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <DialogTitle\n    :class=\"cn('text-foreground font-medium', props.class)\"\n    v-bind=\"delegatedProps\"\n  >\n    <slot></slot>\n  </DialogTitle>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/SheetTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { DialogTriggerProps } from 'reka-ui';\n\nimport { DialogTrigger } from 'reka-ui';\n\nconst props = defineProps<DialogTriggerProps>();\n</script>\n\n<template>\n  <DialogTrigger v-bind=\"props\">\n    <slot></slot>\n  </DialogTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/index.ts",
    "content": "export * from './sheet';\nexport { default as Sheet } from './Sheet.vue';\nexport { default as SheetClose } from './SheetClose.vue';\nexport { default as SheetContent } from './SheetContent.vue';\nexport { default as SheetDescription } from './SheetDescription.vue';\nexport { default as SheetFooter } from './SheetFooter.vue';\nexport { default as SheetHeader } from './SheetHeader.vue';\nexport { default as SheetTitle } from './SheetTitle.vue';\n\nexport { default as SheetTrigger } from './SheetTrigger.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/sheet/sheet.ts",
    "content": "import type { VariantProps } from 'class-variance-authority';\n\nimport { cva } from 'class-variance-authority';\n\nexport const sheetVariants = cva(\n  'bg-background shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 border-border',\n  {\n    defaultVariants: {\n      side: 'right',\n    },\n    variants: {\n      side: {\n        bottom:\n          'inset-x-0 bottom-0 border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',\n        left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left ',\n        right:\n          'inset-y-0 right-0 w-3/4 border-l  data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',\n        top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',\n      },\n    },\n  },\n);\n\nexport type SheetVariants = VariantProps<typeof sheetVariants>;\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/switch/Switch.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SwitchRootEmits, SwitchRootProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { SwitchRoot, SwitchThumb, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<SwitchRootProps & { class?: any }>();\n\nconst emits = defineEmits<SwitchRootEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <SwitchRoot\n    v-bind=\"forwarded\"\n    :class=\"\n      cn(\n        'peer focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-xs transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n        props.class,\n      )\n    \"\n  >\n    <SwitchThumb\n      :class=\"\n        cn(\n          'bg-background pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',\n        )\n      \"\n    />\n  </SwitchRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/switch/index.ts",
    "content": "export { default as Switch } from './Switch.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tabs/Tabs.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TabsRootEmits, TabsRootProps } from 'reka-ui';\n\nimport { TabsRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<TabsRootProps>();\nconst emits = defineEmits<TabsRootEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <TabsRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </TabsRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tabs/TabsContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TabsContentProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { TabsContent } from 'reka-ui';\n\nconst props = defineProps<TabsContentProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <TabsContent\n    :class=\"\n      cn(\n        'ring-offset-background focus-visible:ring-ring mt-2 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n        props.class,\n      )\n    \"\n    v-bind=\"delegatedProps\"\n  >\n    <slot></slot>\n  </TabsContent>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tabs/TabsList.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TabsListProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { TabsList } from 'reka-ui';\n\nconst props = defineProps<TabsListProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n</script>\n\n<template>\n  <TabsList\n    v-bind=\"delegatedProps\"\n    :class=\"\n      cn(\n        'bg-muted text-muted-foreground inline-flex h-9 items-center justify-center rounded-md p-1',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </TabsList>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tabs/TabsTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TabsTriggerProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { TabsTrigger, useForwardProps } from 'reka-ui';\n\nconst props = defineProps<TabsTriggerProps & { class?: any }>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <TabsTrigger\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn(\n        'ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm',\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </TabsTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tabs/index.ts",
    "content": "export { default as Tabs } from './Tabs.vue';\nexport { default as TabsContent } from './TabsContent.vue';\nexport { default as TabsList } from './TabsList.vue';\nexport { default as TabsTrigger } from './TabsTrigger.vue';\nexport { TabsIndicator } from 'reka-ui';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/textarea/Textarea.vue",
    "content": "<script setup lang=\"ts\">\nimport { cn } from '@vben-core/shared/utils';\n\nimport { useVModel } from '@vueuse/core';\n\nconst props = defineProps<{\n  class?: any;\n  defaultValue?: number | string;\n  modelValue?: number | string;\n}>();\n\nconst emits = defineEmits<{\n  (e: 'update:modelValue', payload: number | string): void;\n}>();\n\nconst modelValue = useVModel(props, 'modelValue', emits, {\n  defaultValue: props.defaultValue,\n  passive: true,\n});\n</script>\n\n<template>\n  <textarea\n    v-model=\"modelValue\"\n    :class=\"\n      cn(\n        'border-input placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-15 w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs focus-visible:ring-1 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n        props.class,\n      )\n    \"\n  ></textarea>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/textarea/index.ts",
    "content": "export { default as Textarea } from './Textarea.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/toggle/Toggle.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ToggleEmits, ToggleProps } from 'reka-ui';\n\nimport type { ToggleVariants } from './toggle';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { Toggle, useForwardPropsEmits } from 'reka-ui';\n\nimport { toggleVariants } from './toggle';\n\nconst props = withDefaults(\n  defineProps<\n    ToggleProps & {\n      class?: any;\n      size?: ToggleVariants['size'];\n      variant?: ToggleVariants['variant'];\n    }\n  >(),\n  {\n    disabled: false,\n    size: 'default',\n    variant: 'default',\n  },\n);\n\nconst emits = defineEmits<ToggleEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, size: _size, variant: _variant, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <Toggle\n    v-bind=\"forwarded\"\n    :class=\"cn(toggleVariants({ variant, size }), props.class)\"\n  >\n    <slot></slot>\n  </Toggle>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/toggle/index.ts",
    "content": "export * from './toggle';\nexport { default as Toggle } from './Toggle.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/toggle/toggle.ts",
    "content": "import type { VariantProps } from 'class-variance-authority';\n\nimport { cva } from 'class-variance-authority';\n\nexport const toggleVariants = cva(\n  'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',\n  {\n    defaultVariants: {\n      size: 'default',\n      variant: 'default',\n    },\n    variants: {\n      size: {\n        default: 'h-9 px-3',\n        lg: 'h-10 px-3',\n        sm: 'h-8 px-2',\n      },\n      variant: {\n        default: 'bg-transparent',\n        outline:\n          'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',\n      },\n    },\n  },\n);\n\nexport type ToggleVariants = VariantProps<typeof toggleVariants>;\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/toggle-group/ToggleGroup.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VariantProps } from 'class-variance-authority';\nimport type { ToggleGroupRootEmits, ToggleGroupRootProps } from 'reka-ui';\n\nimport type { toggleVariants } from '../toggle';\n\nimport { computed, provide } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ToggleGroupRoot, useForwardPropsEmits } from 'reka-ui';\n\ntype ToggleGroupVariants = VariantProps<typeof toggleVariants>;\n\nconst props = defineProps<\n  ToggleGroupRootProps & {\n    class?: any;\n    size?: ToggleGroupVariants['size'];\n    variant?: ToggleGroupVariants['variant'];\n  }\n>();\nconst emits = defineEmits<ToggleGroupRootEmits>();\n\nprovide('toggleGroup', {\n  size: props.size,\n  variant: props.variant,\n});\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <ToggleGroupRoot\n    v-bind=\"forwarded\"\n    :class=\"cn('flex items-center justify-center gap-1', props.class)\"\n  >\n    <slot></slot>\n  </ToggleGroupRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/toggle-group/ToggleGroupItem.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VariantProps } from 'class-variance-authority';\nimport type { ToggleGroupItemProps } from 'reka-ui';\n\nimport { computed, inject } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { ToggleGroupItem, useForwardProps } from 'reka-ui';\n\nimport { toggleVariants } from '../toggle';\n\ntype ToggleGroupVariants = VariantProps<typeof toggleVariants>;\n\nconst props = defineProps<\n  ToggleGroupItemProps & {\n    class?: any;\n    size?: ToggleGroupVariants['size'];\n    variant?: ToggleGroupVariants['variant'];\n  }\n>();\n\nconst context = inject<ToggleGroupVariants>('toggleGroup');\n\nconst delegatedProps = computed(() => {\n  const { class: _, size: _size, variant: _variant, ...delegated } = props;\n  return delegated;\n});\n\nconst forwardedProps = useForwardProps(delegatedProps);\n</script>\n\n<template>\n  <ToggleGroupItem\n    v-bind=\"forwardedProps\"\n    :class=\"\n      cn(\n        toggleVariants({\n          variant: context?.variant || variant,\n          size: context?.size || size,\n        }),\n        props.class,\n      )\n    \"\n  >\n    <slot></slot>\n  </ToggleGroupItem>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/toggle-group/index.ts",
    "content": "export { default as ToggleGroup } from './ToggleGroup.vue';\nexport { default as ToggleGroupItem } from './ToggleGroupItem.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tooltip/Tooltip.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TooltipRootEmits, TooltipRootProps } from 'reka-ui';\n\nimport { TooltipRoot, useForwardPropsEmits } from 'reka-ui';\n\nconst props = defineProps<TooltipRootProps>();\nconst emits = defineEmits<TooltipRootEmits>();\n\nconst forwarded = useForwardPropsEmits(props, emits);\n</script>\n\n<template>\n  <TooltipRoot v-bind=\"forwarded\">\n    <slot></slot>\n  </TooltipRoot>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tooltip/TooltipContent.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TooltipContentEmits, TooltipContentProps } from 'reka-ui';\n\nimport { computed } from 'vue';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { TooltipContent, TooltipPortal, useForwardPropsEmits } from 'reka-ui';\n\ndefineOptions({\n  inheritAttrs: false,\n});\n\nconst props = withDefaults(\n  defineProps<TooltipContentProps & { class?: any }>(),\n  {\n    class: '',\n    side: 'right',\n    sideOffset: 5,\n  },\n);\n\nconst emits = defineEmits<TooltipContentEmits>();\n\nconst delegatedProps = computed(() => {\n  const { class: _, ...delegated } = props;\n\n  return delegated;\n});\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits);\n</script>\n\n<template>\n  <TooltipPortal>\n    <TooltipContent\n      v-bind=\"{ ...forwarded, ...$attrs }\"\n      :class=\"\n        cn(\n          'z-popup border-border bg-accent text-accent-foreground shadow-float animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 overflow-hidden rounded-sm border px-4 py-2 text-xs',\n          props.class,\n        )\n      \"\n    >\n      <slot></slot>\n    </TooltipContent>\n  </TooltipPortal>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tooltip/TooltipProvider.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TooltipProviderProps } from 'reka-ui';\n\nimport { TooltipProvider } from 'reka-ui';\n\nconst props = defineProps<TooltipProviderProps>();\n</script>\n\n<template>\n  <TooltipProvider v-bind=\"props\">\n    <slot></slot>\n  </TooltipProvider>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tooltip/TooltipTrigger.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TooltipTriggerProps } from 'reka-ui';\n\nimport { TooltipTrigger } from 'reka-ui';\n\nconst props = defineProps<TooltipTriggerProps>();\n</script>\n\n<template>\n  <TooltipTrigger v-bind=\"props\">\n    <slot></slot>\n  </TooltipTrigger>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tooltip/index.ts",
    "content": "export { default as Tooltip } from './Tooltip.vue';\nexport { default as TooltipContent } from './TooltipContent.vue';\nexport { default as TooltipProvider } from './TooltipProvider.vue';\nexport { default as TooltipTrigger } from './TooltipTrigger.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tree/index.ts",
    "content": "export { default as VbenTree } from './tree.vue';\nexport type { TreeProps } from './types';\nexport { treePropsDefaults } from './types';\nexport type { FlattenedItem } from 'reka-ui';\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Arrayable } from '@vueuse/core';\nimport type { FlattenedItem } from 'reka-ui';\n\nimport type { ClassType, Recordable } from '@vben-core/typings';\n\nimport type { TreeProps } from './types';\n\nimport { onMounted, ref, watchEffect } from 'vue';\n\nimport { ChevronRight, IconifyIcon } from '@vben-core/icons';\nimport { cn, get } from '@vben-core/shared/utils';\n\nimport { TreeItem, TreeRoot } from 'reka-ui';\n\nimport { Checkbox } from '../checkbox';\nimport { treePropsDefaults } from './types';\n\nconst props = withDefaults(defineProps<TreeProps>(), treePropsDefaults());\n\nconst emits = defineEmits<{\n  expand: [value: FlattenedItem<Recordable<any>>];\n  select: [value: FlattenedItem<Recordable<any>>];\n}>();\n\ninterface InnerFlattenItem<T = Recordable<any>, P = number | string> {\n  hasChildren: boolean;\n  id: P;\n  level: number;\n  parentId: null | P;\n  parents: P[];\n  value: T;\n}\n\nfunction flatten<T = Recordable<any>, P = number | string>(\n  items: T[],\n  childrenField: string = 'children',\n  level = 0,\n  parentId: null | P = null,\n  parents: P[] = [],\n): InnerFlattenItem<T, P>[] {\n  const result: InnerFlattenItem<T, P>[] = [];\n  items.forEach((item) => {\n    const children = get(item, childrenField) as Array<T>;\n    const id = get(item, props.valueField) as P;\n    const val: InnerFlattenItem<T, P> = {\n      hasChildren: Array.isArray(children) && children.length > 0,\n      id,\n      level,\n      parentId,\n      parents: [...parents],\n      value: item,\n    };\n    result.push(val);\n    if (val.hasChildren)\n      result.push(\n        ...flatten(children, childrenField, level + 1, id, [...parents, id]),\n      );\n  });\n  return result;\n}\n\nconst flattenData = ref<Array<InnerFlattenItem>>([]);\nconst modelValue = defineModel<Arrayable<number | string>>();\nconst expanded = ref<Array<number | string>>(props.defaultExpandedKeys ?? []);\n\nconst treeValue = ref();\nlet lastTreeData: any = null;\n\nonMounted(() => {\n  watchEffect(() => {\n    flattenData.value = flatten(props.treeData, props.childrenField);\n    updateTreeValue();\n\n    // 只在 treeData 变化时执行展开\n    const currentTreeData = JSON.stringify(props.treeData);\n    if (lastTreeData !== currentTreeData) {\n      lastTreeData = currentTreeData;\n      if (\n        props.defaultExpandedLevel !== undefined &&\n        props.defaultExpandedLevel > 0\n      ) {\n        expandToLevel(props.defaultExpandedLevel);\n      }\n    }\n  });\n});\n\nfunction getItemByValue(value: number | string) {\n  return flattenData.value.find(\n    (item) => get(item.value, props.valueField) === value,\n  )?.value;\n}\n\nfunction updateTreeValue() {\n  const val = modelValue.value;\n  if (val === undefined) {\n    treeValue.value = props.multiple ? [] : undefined;\n  } else if (Array.isArray(val)) {\n    if (val.length === 0) {\n      treeValue.value = [];\n    } else {\n      const filteredValues = val.filter((v) => {\n        const item = getItemByValue(v);\n        return item && !get(item, props.disabledField);\n      });\n      treeValue.value = filteredValues.map((v) => getItemByValue(v));\n\n      if (filteredValues.length !== val.length) {\n        modelValue.value = filteredValues;\n      }\n    }\n  } else {\n    const item = getItemByValue(val);\n    if (item && !get(item, props.disabledField)) {\n      treeValue.value = item;\n    } else {\n      treeValue.value = props.multiple ? [] : undefined;\n      modelValue.value = props.multiple ? [] : undefined;\n    }\n  }\n}\n\nfunction updateModelValue(val: Arrayable<Recordable<any>>) {\n  if (Array.isArray(val)) {\n    const filteredVal = val.filter((v) => !get(v, props.disabledField));\n    modelValue.value = filteredVal.map((v) => get(v, props.valueField));\n  } else {\n    if (val && !get(val, props.disabledField)) {\n      modelValue.value = get(val, props.valueField);\n    }\n  }\n}\n\nfunction expandToLevel(level: number) {\n  const keys: string[] = [];\n  flattenData.value.forEach((item) => {\n    if (item.level <= level - 1) {\n      keys.push(get(item.value, props.valueField));\n    }\n  });\n  expanded.value = keys;\n}\n\nfunction collapseNodes(value: Arrayable<number | string>) {\n  const keys = new Set(Array.isArray(value) ? value : [value]);\n  expanded.value = expanded.value.filter((key) => !keys.has(key));\n}\n\nfunction expandNodes(value: Arrayable<number | string>) {\n  const keys = [...(Array.isArray(value) ? value : [value])];\n  keys.forEach((key) => {\n    if (expanded.value.includes(key)) return;\n    const item = getItemByValue(key);\n    if (item) {\n      expanded.value.push(key);\n    }\n  });\n}\n\nfunction expandAll() {\n  expanded.value = flattenData.value\n    .filter((item) => item.hasChildren)\n    .map((item) => get(item.value, props.valueField));\n}\n\nfunction collapseAll() {\n  expanded.value = [];\n}\n\nfunction checkAll() {\n  if (!props.multiple) return;\n  modelValue.value = [\n    ...new Set(\n      flattenData.value\n        .filter((item) => !get(item.value, props.disabledField))\n        .map((item) => get(item.value, props.valueField)),\n    ),\n  ];\n  updateTreeValue();\n}\n\nfunction unCheckAll() {\n  if (!props.multiple) return;\n  modelValue.value = [];\n  updateTreeValue();\n}\n\nfunction isNodeDisabled(item: FlattenedItem<Recordable<any>>) {\n  return props.disabled || get(item.value, props.disabledField);\n}\n\nfunction onToggle(item: FlattenedItem<Recordable<any>>) {\n  emits('expand', item);\n}\nfunction onSelect(item: FlattenedItem<Recordable<any>>, isSelected: boolean) {\n  if (isNodeDisabled(item)) {\n    return;\n  }\n\n  if (\n    !props.checkStrictly &&\n    props.multiple &&\n    props.autoCheckParent &&\n    isSelected\n  ) {\n    flattenData.value\n      .find((i) => {\n        return (\n          get(i.value, props.valueField) === get(item.value, props.valueField)\n        );\n      })\n      ?.parents?.filter((item) => !get(item, props.disabledField))\n      ?.forEach((p) => {\n        if (Array.isArray(modelValue.value) && !modelValue.value.includes(p)) {\n          modelValue.value.push(p);\n        }\n      });\n  }\n  if (\n    !props.checkStrictly &&\n    props.multiple &&\n    props.autoCheckParent &&\n    !isSelected\n  ) {\n    flattenData.value\n      .find((i) => {\n        return (\n          get(i.value, props.valueField) === get(item.value, props.valueField)\n        );\n      })\n      ?.parents?.filter((item) => !get(item, props.disabledField))\n      ?.toReversed()\n      .forEach((p) => {\n        const children = flattenData.value.filter((i) => {\n          return (\n            i.parents.length > 0 &&\n            i.parents.includes(p) &&\n            i.id !== item._id &&\n            i.parentId === p\n          );\n        });\n        if (Array.isArray(modelValue.value)) {\n          const hasSelectedChild = children.some((child) =>\n            (modelValue.value as unknown[]).includes(\n              get(child.value, props.valueField),\n            ),\n          );\n          if (!hasSelectedChild) {\n            const index = modelValue.value.indexOf(p);\n            if (index !== -1) {\n              modelValue.value.splice(index, 1);\n            }\n          }\n        }\n      });\n  }\n  updateTreeValue();\n  emits('select', item);\n}\n\ndefineExpose({\n  collapseAll,\n  collapseNodes,\n  expandAll,\n  expandNodes,\n  checkAll,\n  unCheckAll,\n  expandToLevel,\n  getItemByValue,\n});\n</script>\n<template>\n  <TreeRoot\n    :get-key=\"(item) => get(item, valueField)\"\n    :get-children=\"(item) => get(item, childrenField)\"\n    :items=\"treeData\"\n    :model-value=\"treeValue\"\n    v-model:expanded=\"expanded as string[]\"\n    :default-expanded=\"defaultExpandedKeys as string[]\"\n    :propagate-select=\"!checkStrictly\"\n    :multiple=\"multiple\"\n    :disabled=\"disabled\"\n    :selection-behavior=\"allowClear || multiple ? 'toggle' : 'replace'\"\n    @update:model-value=\"updateModelValue\"\n    v-slot=\"{ flattenItems }\"\n    :class=\"\n      cn(\n        'text-blackA11 container list-none rounded-lg text-sm font-medium select-none',\n        $attrs.class as unknown as ClassType,\n        bordered ? 'border' : '',\n      )\n    \"\n  >\n    <div\n      :class=\"\n        cn('my-0.5 flex w-full items-center p-1', bordered ? 'border-b' : '')\n      \"\n      v-if=\"$slots.header\"\n    >\n      <slot name=\"header\"> </slot>\n    </div>\n    <div\n      :class=\"\n        cn('my-0.5 flex w-full items-center p-1', bordered ? 'border-b' : '')\n      \"\n      v-if=\"treeData.length > 0\"\n    >\n      <div\n        class=\"flex size-5 flex-1 cursor-pointer items-center\"\n        @click=\"() => (expanded?.length > 0 ? collapseAll() : expandAll())\"\n      >\n        <ChevronRight\n          :class=\"{ 'rotate-90': expanded?.length > 0 }\"\n          class=\"text-foreground/80 hover:text-foreground size-4 cursor-pointer transition\"\n        />\n        <Checkbox\n          v-if=\"multiple\"\n          @click.stop\n          @update:model-value=\"\n            (checked: boolean | 'indeterminate') =>\n              checked === true ? checkAll() : unCheckAll()\n          \"\n        />\n      </div>\n    </div>\n    <TransitionGroup :name=\"transition ? 'fade' : ''\">\n      <TreeItem\n        v-for=\"item in flattenItems\"\n        v-slot=\"{\n          isExpanded,\n          isSelected,\n          isIndeterminate,\n          handleSelect,\n          handleToggle,\n        }\"\n        :key=\"item._id\"\n        :style=\"{ 'margin-left': `${item.level - 1}rem` }\"\n        :class=\"\n          cn('cursor-pointer', getNodeClass?.(item), {\n            'data-[selected]:bg-accent': !multiple,\n            'text-foreground/50 cursor-not-allowed': isNodeDisabled(item),\n          })\n        \"\n        v-bind=\"\n          Object.assign(item.bind, {\n            onfocus: isNodeDisabled(item) ? 'this.blur()' : undefined,\n            disabled: isNodeDisabled(item),\n          })\n        \"\n        @select=\"\n          (event: any) => {\n            if (isNodeDisabled(item)) {\n              event.preventDefault();\n              event.stopPropagation();\n              return;\n            }\n            if (event.detail.originalEvent.type === 'click') {\n              event.preventDefault();\n            }\n            onSelect(item, event.detail.isSelected);\n          }\n        \"\n        @toggle=\"\n          (event: any) => {\n            if (event.detail.originalEvent.type === 'click') {\n              event.preventDefault();\n            }\n            !isNodeDisabled(item) && onToggle(item);\n          }\n        \"\n        class=\"tree-node focus:ring-grass8 my-0.5 flex items-center rounded p-1 outline-hidden focus:ring-2\"\n      >\n        <ChevronRight\n          v-if=\"\n            item.hasChildren &&\n            Array.isArray(item.value[childrenField]) &&\n            item.value[childrenField].length > 0\n          \"\n          class=\"text-foreground/80 hover:text-foreground size-4 cursor-pointer transition\"\n          :class=\"{ 'rotate-90': isExpanded }\"\n          @click.stop=\"\n            () => {\n              handleToggle();\n              onToggle(item);\n            }\n          \"\n        />\n        <div v-else class=\"h-4 w-4\"></div>\n        <div class=\"flex items-center gap-1\">\n          <Checkbox\n            v-if=\"multiple\"\n            :model-value=\"isSelected && !isNodeDisabled(item)\"\n            :disabled=\"isNodeDisabled(item)\"\n            :indeterminate=\"isIndeterminate && !isNodeDisabled(item)\"\n            @click=\"\n              (event: MouseEvent) => {\n                if (isNodeDisabled(item)) {\n                  event.preventDefault();\n                  event.stopPropagation();\n                  return;\n                }\n                handleSelect();\n              }\n            \"\n          />\n          <div\n            class=\"flex items-center gap-1\"\n            @click=\"\n              (event: MouseEvent) => {\n                if (isNodeDisabled(item)) {\n                  event.preventDefault();\n                  event.stopPropagation();\n                  return;\n                }\n                handleSelect();\n              }\n            \"\n          >\n            <slot name=\"node\" v-bind=\"item\">\n              <IconifyIcon\n                class=\"size-4\"\n                v-if=\"showIcon && get(item.value, iconField)\"\n                :icon=\"get(item.value, iconField)\"\n              />\n              {{ get(item.value, labelField) }}\n            </slot>\n          </div>\n        </div>\n        <div class=\"h-4 w-4\"></div>\n      </TreeItem>\n    </TransitionGroup>\n    <div\n      :class=\"\n        cn('my-0.5 flex w-full items-center p-1', bordered ? 'border-t' : '')\n      \"\n      v-if=\"$slots.footer\"\n    >\n      <slot name=\"footer\"> </slot>\n    </div>\n  </TreeRoot>\n</template>\n<style lang=\"scss\" scoped>\n.container {\n  position: relative;\n  padding: 0;\n  list-style-type: none;\n}\n\n.item {\n  box-sizing: border-box;\n  width: 100%;\n  height: 30px;\n  background-color: #f3f3f3;\n  border: 1px solid #666;\n}\n\n/* 1. 声明过渡效果 */\n.fade-move,\n.fade-enter-active,\n.fade-leave-active {\n  transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);\n}\n\n/* 2. 声明进入和离开的状态 */\n.fade-enter-from,\n.fade-leave-to {\n  opacity: 0;\n  transform: scaleY(0.01) translate(30px, 0);\n}\n\n/* 3. 确保离开的项目被移除出了布局流\n      以便正确地计算移动时的动画效果。 */\n.fade-leave-active {\n  position: absolute;\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts",
    "content": "import type { Arrayable } from '@vueuse/core';\nimport type { FlattenedItem } from 'reka-ui';\n\nimport type { Recordable } from '@vben-core/typings';\n\nexport interface TreeProps {\n  /** 单选时允许取消已有选项 */\n  allowClear?: boolean;\n  /** 非关联选择时，自动选中上级节点 */\n  autoCheckParent?: boolean;\n  /** 显示边框 */\n  bordered?: boolean;\n  /** 取消父子关联选择 */\n  checkStrictly?: boolean;\n  /** 子级字段名 */\n  childrenField?: string;\n  /** 默认展开的键 */\n  defaultExpandedKeys?: Array<number | string>;\n  /** 默认展开的级别（优先级高于defaultExpandedKeys） */\n  defaultExpandedLevel?: number;\n  /** 默认值 */\n  defaultValue?: Arrayable<number | string>;\n  /** 禁用 */\n  disabled?: boolean;\n  /** 禁用字段名 */\n  disabledField?: string;\n  /** 自定义节点类名 */\n  getNodeClass?: (item: FlattenedItem<Recordable<any>>) => string;\n  iconField?: string;\n  /** label字段 */\n  labelField?: string;\n  /** 是否多选 */\n  multiple?: boolean;\n  /** 显示由iconField指定的图标 */\n  showIcon?: boolean;\n  /** 启用展开收缩动画 */\n  transition?: boolean;\n  /** 树数据 */\n  treeData: Recordable<any>[];\n  /** 值字段 */\n  valueField?: string;\n}\n\nexport function treePropsDefaults() {\n  return {\n    allowClear: false,\n    autoCheckParent: true,\n    bordered: false,\n    checkStrictly: false,\n    defaultExpandedKeys: () => [],\n    defaultExpandedLevel: 0,\n    disabled: false,\n    disabledField: 'disabled',\n    iconField: 'icon',\n    labelField: 'label',\n    multiple: false,\n    showIcon: true,\n    transition: true,\n    valueField: 'value',\n    childrenField: 'children',\n  };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/shadcn-ui/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@vben-core/shadcn-ui/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/package.json",
    "content": "{\n  \"name\": \"@vben-core/tabs-ui\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/@vben-core/uikit/tabs-ui\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm exec tsdown\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"development\": \"./src/index.ts\",\n      \"production\": \"./src/index.ts\",\n      \"default\": \"./dist/index.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"exports\": {\n      \".\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"default\": \"./dist/index.mjs\"\n      }\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/composables\": \"workspace:*\",\n    \"@vben-core/design\": \"workspace:*\",\n    \"@vben-core/icons\": \"workspace:*\",\n    \"@vben-core/shadcn-ui\": \"workspace:*\",\n    \"@vben-core/typings\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"vue\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/components/index.ts",
    "content": "export { default as TabsChrome } from './tabs-chrome/tabs.vue';\nexport { default as Tabs } from './tabs/tabs.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { TabDefinition } from '@vben-core/typings';\n\nimport type { TabConfig, TabsProps } from '../../types';\n\nimport { computed } from 'vue';\n\nimport { Pin, X } from '@vben-core/icons';\nimport { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';\n\ninterface Props extends TabsProps {}\n\ndefineOptions({\n  name: 'VbenTabs',\n\n  inheritAttrs: false,\n});\nconst props = withDefaults(defineProps<Props>(), {\n  contentClass: 'vben-tabs-content',\n  contextMenus: () => [],\n  tabs: () => [],\n});\n\nconst emit = defineEmits<{\n  close: [string];\n  unpin: [TabDefinition];\n}>();\nconst active = defineModel<string>('active');\n\nconst typeWithClass = computed(() => {\n  const typeClasses: Record<string, { content: string }> = {\n    brisk: {\n      content: `h-full after:content-['']  after:absolute after:bottom-0 after:left-0 after:w-full after:h-[1.5px] after:bg-primary after:scale-x-0 after:transition-[transform] after:ease-out after:duration-300 hover:after:scale-x-100 after:origin-left [&.is-active]:after:scale-x-100 [&:not(:first-child)]:border-l last:border-r last:border-r border-border`,\n    },\n    card: {\n      content:\n        'h-[calc(100%-6px)] rounded-md ml-2 border border-border  transition-all',\n    },\n    plain: {\n      content:\n        'h-full [&:not(:first-child)]:border-l last:border-r border-border',\n    },\n  };\n\n  return typeClasses[props.styleType || 'plain'] || { content: '' };\n});\n\nconst tabsView = computed(() => {\n  return props.tabs.map((tab) => {\n    const { fullPath, meta, name, path, key } = tab || {};\n    const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};\n    return {\n      affixTab: !!affixTab,\n      closable: Reflect.has(meta, 'tabClosable') ? !!tabClosable : true,\n      fullPath,\n      icon: icon as string,\n      key,\n      meta,\n      name,\n      path,\n      title: (newTabTitle || title || name) as string,\n    } as TabConfig;\n  });\n});\n\nfunction onMouseDown(e: MouseEvent, tab: TabConfig) {\n  if (\n    e.button === 1 &&\n    tab.closable &&\n    !tab.affixTab &&\n    tabsView.value.length > 1 &&\n    props.middleClickToClose\n  ) {\n    e.preventDefault();\n    e.stopPropagation();\n    emit('close', tab.key);\n  }\n}\n</script>\n\n<template>\n  <div\n    :class=\"contentClass\"\n    class=\"relative flex! h-full w-max items-center overflow-hidden pr-6\"\n  >\n    <TransitionGroup name=\"slide-left\">\n      <div\n        v-for=\"(tab, i) in tabsView\"\n        :key=\"tab.key\"\n        :class=\"[\n          {\n            'is-active bg-primary/15 dark:bg-accent': tab.key === active,\n            draggable: !tab.affixTab,\n            'affix-tab': tab.affixTab,\n          },\n          typeWithClass.content,\n        ]\"\n        :data-index=\"i\"\n        class=\"group tab-item translate-all relative flex cursor-pointer select-none [&:not(.is-active)]:hover:bg-accent\"\n        data-tab-item=\"true\"\n        @click=\"active = tab.key\"\n        @mousedown=\"onMouseDown($event, tab)\"\n      >\n        <VbenContextMenu\n          :handler-data=\"tab\"\n          :menus=\"contextMenus\"\n          :modal=\"false\"\n          item-class=\"pr-6\"\n        >\n          <div class=\"relative flex size-full items-center\">\n            <!-- extra -->\n            <div\n              class=\"absolute top-1/2 right-1.5 z-3 translate-y-[-50%] overflow-hidden\"\n            >\n              <!-- close-icon -->\n              <X\n                v-show=\"!tab.affixTab && tabsView.length > 1 && tab.closable\"\n                class=\"size-3 cursor-pointer rounded-full stroke-accent-foreground/80 transition-all group-[.is-active]:text-primary hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:dark:text-accent-foreground\"\n                @click.stop=\"() => emit('close', tab.key)\"\n              />\n              <Pin\n                v-show=\"tab.affixTab && tabsView.length > 1 && tab.closable\"\n                class=\"mt-px size-3.5 cursor-pointer rounded-full transition-all group-[.is-active]:text-primary hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:dark:text-accent-foreground\"\n                @click.stop=\"() => emit('unpin', tab)\"\n              />\n            </div>\n\n            <!-- tab-item-main -->\n            <div\n              class=\"mx-3 mr-4 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-3 text-accent-foreground transition-all duration-300 group-[.is-active]:text-primary group-[.is-active]:dark:text-accent-foreground\"\n            >\n              <VbenIcon\n                v-if=\"showIcon\"\n                :icon=\"tab.icon\"\n                class=\"mr-2 flex size-4 items-center overflow-hidden group-hover:animate-[shrink_0.3s_ease-in-out]\"\n                fallback\n              />\n\n              <span class=\"flex-1 overflow-hidden text-sm whitespace-nowrap\">\n                {{ tab.title }}\n              </span>\n            </div>\n          </div>\n        </VbenContextMenu>\n      </div>\n    </TransitionGroup>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TabDefinition } from '@vben-core/typings';\n\nimport type { TabConfig, TabsProps } from '../../types';\n\nimport { computed, ref } from 'vue';\n\nimport { Pin, X } from '@vben-core/icons';\nimport { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';\n\ninterface Props extends TabsProps {}\n\ndefineOptions({\n  name: 'VbenTabsChrome',\n  inheritAttrs: false,\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  contentClass: 'vben-tabs-content',\n  contextMenus: () => [],\n  gap: 7,\n  tabs: () => [],\n});\n\nconst emit = defineEmits<{\n  close: [string];\n  unpin: [TabDefinition];\n}>();\nconst active = defineModel<string>('active');\n\n// @ts-expect-error - unused\nconst contentRef = ref();\n// @ts-expect-error - unused\nconst tabRef = ref();\n\nconst style = computed(() => {\n  const { gap } = props;\n  return {\n    '--gap': `${gap}px`,\n  };\n});\n\nconst tabsView = computed(() => {\n  return props.tabs.map((tab) => {\n    const { fullPath, meta, name, path, key } = tab || {};\n    const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};\n    return {\n      affixTab: !!affixTab,\n      closable: Reflect.has(meta, 'tabClosable') ? !!tabClosable : true,\n      fullPath,\n      icon: icon as string,\n      key,\n      meta,\n      name,\n      path,\n      title: (newTabTitle || title || name) as string,\n    } as TabConfig;\n  });\n});\n\nfunction onMouseDown(e: MouseEvent, tab: TabConfig) {\n  if (\n    e.button === 1 &&\n    tab.closable &&\n    !tab.affixTab &&\n    tabsView.value.length > 1 &&\n    props.middleClickToClose\n  ) {\n    e.preventDefault();\n    e.stopPropagation();\n    emit('close', tab.key);\n  }\n}\n</script>\n\n<template>\n  <div\n    ref=\"contentRef\"\n    :class=\"contentClass\"\n    :style=\"style\"\n    class=\"tabs-chrome flex! h-full w-max overflow-y-hidden pr-6\"\n  >\n    <TransitionGroup name=\"slide-left\">\n      <div\n        v-for=\"(tab, i) in tabsView\"\n        :key=\"tab.key\"\n        ref=\"tabRef\"\n        :class=\"[\n          {\n            'is-active': tab.key === active,\n            draggable: !tab.affixTab,\n            'affix-tab': tab.affixTab,\n          },\n        ]\"\n        :data-active-tab=\"active\"\n        :data-index=\"i\"\n        class=\"draggable group tabs-chrome__item translate-all relative -mr-3 flex h-full items-center select-none\"\n        data-tab-item=\"true\"\n        @click=\"active = tab.key\"\n        @mousedown=\"onMouseDown($event, tab)\"\n      >\n        <VbenContextMenu\n          :handler-data=\"tab\"\n          :menus=\"contextMenus\"\n          :modal=\"false\"\n          item-class=\"pr-6\"\n        >\n          <div class=\"relative size-full px-1\">\n            <!-- divider -->\n            <div\n              v-if=\"i !== 0 && tab.key !== active\"\n              class=\"tabs-chrome__divider absolute top-1/2 left-(--gap) z-0 h-4 w-px translate-y-[-50%] bg-border transition-all\"\n            ></div>\n            <!-- background -->\n            <div\n              class=\"tabs-chrome__background absolute z-[-1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150\"\n            >\n              <div\n                class=\"tabs-chrome__background-content h-full rounded-tl-(--gap) rounded-tr-(--gap) duration-150 group-[.is-active]:bg-primary/15 group-[.is-active]:dark:bg-accent\"\n              ></div>\n              <svg\n                class=\"tabs-chrome__background-before absolute bottom-0 -left-px fill-transparent transition-all duration-150 group-[.is-active]:fill-primary/15 group-[.is-active]:dark:fill-accent\"\n                height=\"7\"\n                width=\"7\"\n              >\n                <path d=\"M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z\" />\n              </svg>\n              <svg\n                class=\"tabs-chrome__background-after absolute -right-px bottom-0 fill-transparent transition-all duration-150 group-[.is-active]:fill-primary/15 group-[.is-active]:dark:fill-accent\"\n                height=\"7\"\n                width=\"7\"\n              >\n                <path d=\"M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z\" />\n              </svg>\n            </div>\n\n            <!-- extra -->\n            <div\n              class=\"tabs-chrome__extra absolute top-1/2 right-(--gap) z-3 size-4 translate-y-[-50%]\"\n            >\n              <!-- close-icon -->\n              <X\n                v-show=\"!tab.affixTab && tabsView.length > 1 && tab.closable\"\n                class=\"mt-0.5 size-3 cursor-pointer rounded-full stroke-accent-foreground/80 text-accent-foreground/80 transition-all group-[.is-active]:text-accent-foreground hover:bg-accent hover:stroke-accent-foreground\"\n                @click.stop=\"() => emit('close', tab.key)\"\n              />\n              <Pin\n                v-show=\"tab.affixTab && tabsView.length > 1 && tab.closable\"\n                class=\"mt-px size-3.5 cursor-pointer rounded-full text-accent-foreground/80 transition-all group-[.is-active]:text-accent-foreground hover:text-accent-foreground\"\n                @click.stop=\"() => emit('unpin', tab)\"\n              />\n            </div>\n\n            <!-- tab-item-main -->\n            <div\n              class=\"tabs-chrome__item-main z-2 mx-[calc(var(--gap)*2)] my-0 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-4 pl-2 text-accent-foreground duration-150 group-[.is-active]:text-primary group-[.is-active]:dark:text-accent-foreground\"\n            >\n              <VbenIcon\n                v-if=\"showIcon\"\n                :icon=\"tab.icon\"\n                class=\"mr-1 flex size-4 items-center overflow-hidden group-hover:animate-[shrink_0.3s_ease-in-out]\"\n              />\n\n              <span class=\"flex-1 overflow-hidden text-sm whitespace-nowrap\">\n                {{ tab.title }}\n              </span>\n            </div>\n          </div>\n        </VbenContextMenu>\n      </div>\n    </TransitionGroup>\n  </div>\n</template>\n\n<style scoped>\n@reference \"@vben/tailwind-config/theme\";\n\n.tabs-chrome__item:not(.dragging) {\n  @apply cursor-pointer;\n}\n\n.tabs-chrome__item:not(.dragging):hover:not(.is-active)\n  + .tabs-chrome__item\n  .tabs-chrome__divider {\n  @apply opacity-0;\n}\n\n.tabs-chrome__item:not(.dragging):hover:not(.is-active) .tabs-chrome__divider {\n  @apply opacity-0;\n}\n\n.tabs-chrome__item:not(.dragging):hover:not(.is-active)\n  .tabs-chrome__background {\n  @apply pb-0.5;\n}\n\n.tabs-chrome__item:not(.dragging):hover:not(.is-active)\n  .tabs-chrome__background-content {\n  @apply bg-accent mx-0.5 rounded-md;\n}\n\n.tabs-chrome__item:not(.dragging).is-active {\n  @apply z-[2];\n}\n\n.tabs-chrome__item:not(.dragging).is-active\n  + .tabs-chrome__item\n  .tabs-chrome__divider {\n  @apply opacity-0!;\n}\n</style>\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/components/widgets/index.ts",
    "content": "export { default as TabsToolMore } from './tool-more.vue';\nexport { default as TabsToolRefresh } from './tool-refresh.vue';\nexport { default as TabsToolScreen } from './tool-screen.vue';\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-more.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { DropdownMenuProps } from '@vben-core/shadcn-ui';\n\nimport { LayoutGrid } from '@vben-core/icons';\nimport { VbenDropdownMenu } from '@vben-core/shadcn-ui';\n\ndefineProps<DropdownMenuProps>();\n</script>\n\n<template>\n  <VbenDropdownMenu :menus=\"menus\" :modal=\"false\">\n    <div\n      class=\"flex-center h-full cursor-pointer border-l border-border px-2 text-lg font-semibold text-muted-foreground hover:bg-muted hover:text-foreground\"\n    >\n      <LayoutGrid class=\"size-4\" />\n    </div>\n  </VbenDropdownMenu>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-refresh.vue",
    "content": "<script lang=\"ts\" setup>\nimport { RotateCw } from '@vben-core/icons';\n\nconst emit = defineEmits(['refresh']);\n\nconst handleRefresh = () => {\n  emit('refresh');\n};\n</script>\n\n<template>\n  <div\n    class=\"flex-center h-full cursor-pointer border-l border-border px-2 text-lg font-semibold text-muted-foreground hover:bg-muted hover:text-foreground\"\n    @click=\"handleRefresh\"\n  >\n    <RotateCw class=\"size-4\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-screen.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fullscreen, Minimize2 } from '@vben-core/icons';\n\nconst screen = defineModel<boolean>('screen');\n\nfunction toggleScreen() {\n  screen.value = !screen.value;\n}\n</script>\n\n<template>\n  <div\n    class=\"flex-center h-full cursor-pointer border-l border-border px-2 text-lg font-semibold text-muted-foreground hover:bg-muted hover:text-foreground\"\n    @click=\"toggleScreen\"\n  >\n    <Minimize2 v-if=\"screen\" class=\"size-4\" />\n    <Fullscreen v-else class=\"size-4\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/index.ts",
    "content": "export * from './components/widgets';\nexport { default as TabsView } from './tabs-view.vue';\nexport type { IContextMenuItem } from '@vben-core/shadcn-ui';\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/tabs-view.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TabsEmits, TabsProps } from './types';\n\nimport { useForwardPropsEmits } from '@vben-core/composables';\nimport { ChevronsLeft, ChevronsRight } from '@vben-core/icons';\nimport { VbenScrollbar } from '@vben-core/shadcn-ui';\n\nimport { Tabs, TabsChrome } from './components';\nimport { useTabsDrag } from './use-tabs-drag';\nimport { useTabsViewScroll } from './use-tabs-view-scroll';\n\ninterface Props extends TabsProps {}\n\ndefineOptions({\n  name: 'TabsView',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  contentClass: 'vben-tabs-content',\n  draggable: true,\n  styleType: 'chrome',\n  wheelable: true,\n});\n\nconst emit = defineEmits<TabsEmits>();\n\nconst forward = useForwardPropsEmits(props, emit);\n\nconst {\n  handleScrollAt,\n  handleWheel,\n  // @ts-expect-error unused\n  scrollbarRef,\n  scrollDirection,\n  scrollIsAtLeft,\n  scrollIsAtRight,\n  showScrollButton,\n} = useTabsViewScroll(props);\n\nfunction onWheel(e: WheelEvent) {\n  if (props.wheelable) {\n    handleWheel(e);\n    e.stopPropagation();\n    e.preventDefault();\n  }\n}\n\nuseTabsDrag(props, emit);\n</script>\n\n<template>\n  <div class=\"flex h-full flex-1 overflow-hidden\">\n    <!-- 左侧滚动按钮 -->\n    <span\n      v-show=\"showScrollButton\"\n      :class=\"{\n        'cursor-pointer text-muted-foreground hover:bg-muted': !scrollIsAtLeft,\n        'pointer-events-none opacity-30': scrollIsAtLeft,\n      }\"\n      class=\"border-r px-2\"\n      @click=\"scrollDirection('left')\"\n    >\n      <ChevronsLeft class=\"size-4 h-full\" />\n    </span>\n\n    <div\n      :class=\"{\n        'pt-0.75': styleType === 'chrome',\n      }\"\n      class=\"size-full flex-1 overflow-hidden\"\n    >\n      <VbenScrollbar\n        ref=\"scrollbarRef\"\n        :shadow-bottom=\"false\"\n        :shadow-top=\"false\"\n        class=\"h-full\"\n        horizontal\n        scroll-bar-class=\"z-10 hidden \"\n        shadow\n        shadow-left\n        shadow-right\n        @scroll-at=\"handleScrollAt\"\n        @wheel=\"onWheel\"\n      >\n        <TabsChrome\n          v-if=\"styleType === 'chrome'\"\n          v-bind=\"{ ...forward, ...$attrs, ...$props }\"\n        />\n\n        <Tabs v-else v-bind=\"{ ...forward, ...$attrs, ...$props }\" />\n      </VbenScrollbar>\n    </div>\n\n    <!-- 右侧滚动按钮 -->\n    <span\n      v-show=\"showScrollButton\"\n      :class=\"{\n        'cursor-pointer text-muted-foreground hover:bg-muted': !scrollIsAtRight,\n        'pointer-events-none opacity-30': scrollIsAtRight,\n      }\"\n      class=\"cursor-pointer border-l px-2 text-muted-foreground hover:bg-muted\"\n      @click=\"scrollDirection('right')\"\n    >\n      <ChevronsRight class=\"size-4 h-full\" />\n    </span>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/types.ts",
    "content": "import type { IContextMenuItem } from '@vben-core/shadcn-ui';\nimport type { TabDefinition, TabsStyleType } from '@vben-core/typings';\n\nexport type TabsEmits = {\n  close: [string];\n  sortTabs: [number, number];\n  unpin: [TabDefinition];\n};\n\nexport interface TabsProps {\n  active?: string;\n  /**\n   * @zh_CN content class\n   * @default tabs-chrome\n   */\n  contentClass?: string;\n  /**\n   * @zh_CN 右键菜单\n   */\n  contextMenus?: (data: any) => IContextMenuItem[];\n  /**\n   * @zh_CN 是否可以拖拽\n   */\n  draggable?: boolean;\n  /**\n   * @zh_CN 间隙\n   * @default 7\n   * 仅限 tabs-chrome\n   */\n  gap?: number;\n  /**\n   * @zh_CN tab 最大宽度\n   * 仅限 tabs-chrome\n   */\n  maxWidth?: number;\n  /**\n   * @zh_CN 点击中键时关闭Tab\n   */\n  middleClickToClose?: boolean;\n\n  /**\n   * @zh_CN tab最小宽度\n   * 仅限 tabs-chrome\n   */\n  minWidth?: number;\n\n  /**\n   * @zh_CN 是否显示图标\n   */\n  showIcon?: boolean;\n  /**\n   * @zh_CN 标签页风格\n   */\n  styleType?: TabsStyleType;\n\n  /**\n   * @zh_CN 选项卡数据\n   */\n  tabs?: TabDefinition[];\n\n  /**\n   * @zh_CN 是否响应滚轮事件\n   */\n  wheelable?: boolean;\n}\n\nexport interface TabConfig extends TabDefinition {\n  affixTab: boolean;\n  closable: boolean;\n  icon: string;\n  key: string;\n  title: string;\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/use-tabs-drag.ts",
    "content": "import type { Sortable } from '@vben-core/composables';\nimport type { EmitType } from '@vben-core/typings';\n\nimport type { TabsProps } from './types';\n\nimport { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';\n\nimport { useIsMobile, useSortable } from '@vben-core/composables';\n\n// 可能会找到拖拽的子元素，这里需要确保拖拽的dom时tab元素\nfunction findParentElement(element: HTMLElement) {\n  const parentCls = 'group';\n  return element.classList.contains(parentCls)\n    ? element\n    : element.closest(`.${parentCls}`);\n}\n\nexport function useTabsDrag(props: TabsProps, emit: EmitType) {\n  const sortableInstance = ref<null | Sortable>(null);\n\n  async function initTabsSortable() {\n    await nextTick();\n\n    const el = document.querySelectorAll(\n      `.${props.contentClass}`,\n    )?.[0] as HTMLElement;\n\n    if (!el) {\n      console.warn('Element not found for sortable initialization');\n      return;\n    }\n\n    const resetElState = async () => {\n      el.style.cursor = 'default';\n      // el.classList.remove('dragging');\n      el.querySelector('.draggable')?.classList.remove('dragging');\n    };\n\n    const { initializeSortable } = useSortable(el, {\n      filter: (_evt, target: HTMLElement) => {\n        const parent = findParentElement(target);\n        const draggable = parent?.classList.contains('draggable');\n        return !draggable || !props.draggable;\n      },\n      onEnd(evt) {\n        const { newIndex, oldIndex } = evt;\n        // const fromElement = evt.item;\n        const { srcElement } = (evt as any).originalEvent;\n\n        if (!srcElement) {\n          resetElState();\n          return;\n        }\n\n        const srcParent = findParentElement(srcElement);\n\n        if (!srcParent) {\n          resetElState();\n          return;\n        }\n\n        if (!srcParent.classList.contains('draggable')) {\n          resetElState();\n\n          return;\n        }\n\n        if (\n          oldIndex !== undefined &&\n          newIndex !== undefined &&\n          !Number.isNaN(oldIndex) &&\n          !Number.isNaN(newIndex) &&\n          oldIndex !== newIndex\n        ) {\n          emit('sortTabs', oldIndex, newIndex);\n        }\n        resetElState();\n      },\n      onMove(evt) {\n        const parent = findParentElement(evt.related);\n        if (parent?.classList.contains('draggable') && props.draggable) {\n          const isCurrentAffix = evt.dragged.classList.contains('affix-tab');\n          const isRelatedAffix = evt.related.classList.contains('affix-tab');\n          // 不允许在固定的tab和非固定的tab之间互相拖拽\n          return isCurrentAffix === isRelatedAffix;\n        } else {\n          return false;\n        }\n      },\n      onStart: () => {\n        el.style.cursor = 'grabbing';\n        el.querySelector('.draggable')?.classList.add('dragging');\n        // el.classList.add('dragging');\n      },\n    });\n\n    sortableInstance.value = await initializeSortable();\n  }\n\n  async function init() {\n    const { isMobile } = useIsMobile();\n\n    // 移动端下tab不需要拖拽\n    if (isMobile.value) {\n      return;\n    }\n    await nextTick();\n    initTabsSortable();\n  }\n\n  onMounted(init);\n\n  watch(\n    () => props.styleType,\n    () => {\n      sortableInstance.value?.destroy();\n      init();\n    },\n  );\n\n  onUnmounted(() => {\n    sortableInstance.value?.destroy();\n  });\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/src/use-tabs-view-scroll.ts",
    "content": "import type { TabsProps } from './types';\n\nimport { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';\n\nimport { VbenScrollbar } from '@vben-core/shadcn-ui';\n\nimport { useDebounceFn } from '@vueuse/core';\n\ntype DomElement = Element | null | undefined;\n\nexport function useTabsViewScroll(props: TabsProps) {\n  let resizeObserver: null | ResizeObserver = null;\n  let mutationObserver: MutationObserver | null = null;\n  let tabItemCount = 0;\n  const scrollbarRef = ref<InstanceType<typeof VbenScrollbar> | null>(null);\n  const scrollViewportEl = ref<DomElement>(null);\n  const showScrollButton = ref(false);\n  const scrollIsAtLeft = ref(true);\n  const scrollIsAtRight = ref(false);\n\n  function getScrollClientWidth() {\n    const scrollbarEl = scrollbarRef.value?.$el;\n    if (!scrollbarEl || !scrollViewportEl.value) return {};\n\n    const scrollbarWidth = scrollbarEl.clientWidth;\n    const scrollViewWidth = scrollViewportEl.value.clientWidth;\n\n    return {\n      scrollbarWidth,\n      scrollViewWidth,\n    };\n  }\n\n  function scrollDirection(\n    direction: 'left' | 'right',\n    distance: number = 150,\n  ) {\n    const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth();\n\n    if (!scrollbarWidth || !scrollViewWidth) return;\n\n    if (scrollbarWidth > scrollViewWidth) return;\n\n    scrollViewportEl.value?.scrollBy({\n      behavior: 'smooth',\n      left:\n        direction === 'left'\n          ? -(scrollbarWidth - distance)\n          : +(scrollbarWidth - distance),\n    });\n  }\n\n  async function initScrollbar() {\n    await nextTick();\n\n    const scrollbarEl = scrollbarRef.value?.$el;\n    if (!scrollbarEl) {\n      return;\n    }\n\n    const viewportEl = scrollbarEl?.querySelector(\n      'div[data-reka-scroll-area-viewport]',\n    );\n\n    scrollViewportEl.value = viewportEl;\n    calcShowScrollbarButton();\n\n    await nextTick();\n    scrollToActiveIntoView();\n\n    // 监听大小变化\n    resizeObserver?.disconnect();\n    resizeObserver = new ResizeObserver(\n      useDebounceFn((_entries: ResizeObserverEntry[]) => {\n        calcShowScrollbarButton();\n        scrollToActiveIntoView();\n      }, 100),\n    );\n    resizeObserver.observe(viewportEl);\n\n    tabItemCount = props.tabs?.length || 0;\n    mutationObserver?.disconnect();\n    // 使用 MutationObserver 仅监听子节点数量变化\n    mutationObserver = new MutationObserver(() => {\n      const count = viewportEl.querySelectorAll(\n        `div[data-tab-item=\"true\"]`,\n      ).length;\n\n      if (count > tabItemCount) {\n        scrollToActiveIntoView();\n      }\n\n      if (count !== tabItemCount) {\n        calcShowScrollbarButton();\n        tabItemCount = count;\n      }\n    });\n\n    // 配置为仅监听子节点的添加和移除\n    mutationObserver.observe(viewportEl, {\n      attributes: false,\n      childList: true,\n      subtree: true,\n    });\n  }\n\n  async function scrollToActiveIntoView() {\n    if (!scrollViewportEl.value) {\n      return;\n    }\n    await nextTick();\n    const viewportEl = scrollViewportEl.value;\n    const { scrollbarWidth } = getScrollClientWidth();\n    const { scrollWidth } = viewportEl;\n\n    if (scrollbarWidth >= scrollWidth) {\n      return;\n    }\n\n    requestAnimationFrame(() => {\n      const activeItem = viewportEl?.querySelector('.is-active');\n      activeItem?.scrollIntoView({ behavior: 'smooth', inline: 'start' });\n    });\n  }\n\n  /**\n   * 计算tabs 宽度，用于判断是否显示左右滚动按钮\n   */\n  async function calcShowScrollbarButton() {\n    if (!scrollViewportEl.value) {\n      return;\n    }\n\n    const { scrollbarWidth } = getScrollClientWidth();\n\n    showScrollButton.value =\n      scrollViewportEl.value.scrollWidth > scrollbarWidth;\n  }\n\n  const handleScrollAt = useDebounceFn(({ left, right }) => {\n    scrollIsAtLeft.value = left;\n    scrollIsAtRight.value = right;\n  }, 100);\n\n  function handleWheel({ deltaY }: WheelEvent) {\n    scrollViewportEl.value?.scrollBy({\n      // behavior: 'smooth',\n      left: deltaY * 3,\n    });\n  }\n\n  watch(\n    () => props.active,\n    async () => {\n      // 200为了等待 tab 切换动画完成\n      // setTimeout(() => {\n      scrollToActiveIntoView();\n      // }, 300);\n    },\n    {\n      flush: 'post',\n    },\n  );\n\n  // watch(\n  //   () => props.tabs?.length,\n  //   async () => {\n  //     await nextTick();\n  //     calcShowScrollbarButton();\n  //   },\n  //   {\n  //     flush: 'post',\n  //   },\n  // );\n\n  watch(\n    () => props.styleType,\n    () => {\n      initScrollbar();\n    },\n  );\n\n  onMounted(initScrollbar);\n\n  onUnmounted(() => {\n    resizeObserver?.disconnect();\n    mutationObserver?.disconnect();\n    resizeObserver = null;\n    mutationObserver = null;\n  });\n\n  return {\n    handleScrollAt,\n    handleWheel,\n    initScrollbar,\n    scrollbarRef,\n    scrollDirection,\n    scrollIsAtLeft,\n    scrollIsAtRight,\n    showScrollButton,\n  };\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/@core/ui-kit/tabs-ui/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\nimport Vue from 'unplugin-vue/rolldown';\n\nexport default defineConfig({\n  clean: true,\n  deps: {\n    skipNodeModulesBundle: true,\n  },\n  dts: {\n    vue: true,\n  },\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n    js: '.mjs',\n  }),\n  platform: 'neutral',\n  plugins: [Vue({ isProduction: true })],\n  unbundle: true,\n});\n"
  },
  {
    "path": "packages/constants/README.md",
    "content": "# @vben/constants\n\n用于多个 `app` 公用的常量，继承了 `@vben-core/shared/constants` 的所有能力。业务上有通用常量可以放在这里。\n\n## 用法\n\n### 添加依赖\n\n```bash\n# 进入目标应用目录，例如 apps/xxxx-app\n# cd apps/xxxx-app\npnpm add @vben/constants\n```\n\n### 使用\n\n```ts\nimport { LOGIN_PATH } from '@vben/constants';\n```\n"
  },
  {
    "path": "packages/constants/package.json",
    "content": "{\n  \"name\": \"@vben/constants\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/constants\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/shared\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "packages/constants/src/core.ts",
    "content": "/**\n * @zh_CN 登录页面 url 地址\n */\nexport const LOGIN_PATH = '/auth/login';\n\nexport interface LanguageOption {\n  label: string;\n  value: 'en-US' | 'zh-CN';\n}\n\n/**\n * Supported languages\n */\nexport const SUPPORT_LANGUAGES: LanguageOption[] = [\n  {\n    label: '简体中文',\n    value: 'zh-CN',\n  },\n  {\n    label: 'English',\n    value: 'en-US',\n  },\n];\n"
  },
  {
    "path": "packages/constants/src/index.ts",
    "content": "export * from './core';\nexport * from '@vben-core/shared/constants';\n"
  },
  {
    "path": "packages/constants/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/effects/README.md",
    "content": "## Effects 目录\n\n`effects` 目录专门用于存放与轻微耦合相关的代码和逻辑。如果你的包具有以下特点，建议将其放置在 `effects` 目录下：\n\n- **状态管理**：使用状态管理框架 `pinia`，并包含处理副作用（如异步操作、API 调用）的部分。\n- **用户偏好设置**：使用 `@vben-core/preferences` 处理用户偏好设置，涉及本地存储或浏览器缓存逻辑（如使用 `localStorage`）。\n- **导航和路由**：处理导航、页面跳转等场景，需要管理路由变化的逻辑。\n- **组件库依赖**：包含与特定组件库紧密耦合或依赖大型仓库的部分。\n\n通过将相关代码归类到 `effects` 目录，可以使项目结构更加清晰，便于维护和扩展。\n"
  },
  {
    "path": "packages/effects/access/package.json",
    "content": "{\n  \"name\": \"@vben/access\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/effects/permissions\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben/preferences\": \"workspace:*\",\n    \"@vben/stores\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"vue\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/effects/access/src/access-control.vue",
    "content": "<!--\n Access control component for fine-grained access control.\n TODO: 可以扩展更完善的功能：\n 1. 支持多个权限码，只要有一个权限码满足即可 或者 多个权限码全部满足\n 2. 支持多个角色，只要有一个角色满足即可 或者 多个角色全部满足\n 3. 支持自定义权限码和角色的判断逻辑\n-->\n<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { useAccess } from './use-access';\n\ninterface Props {\n  /**\n   * Specified codes is visible\n   * @default []\n   */\n  codes?: string[];\n\n  /**\n   * 通过什么方式来控制组件，如果是 role，则传入角色，如果是 code，则传入权限码\n   * @default 'role'\n   */\n  type?: 'code' | 'role';\n}\n\ndefineOptions({\n  name: 'AccessControl',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  codes: () => [],\n  type: 'role',\n});\n\nconst { hasAccessByCodes, hasAccessByRoles } = useAccess();\n\nconst hasAuth = computed(() => {\n  const { codes, type } = props;\n  return type === 'role' ? hasAccessByRoles(codes) : hasAccessByCodes(codes);\n});\n</script>\n\n<template>\n  <slot v-if=\"!codes\"></slot>\n  <slot v-else-if=\"hasAuth\"></slot>\n</template>\n"
  },
  {
    "path": "packages/effects/access/src/accessible.ts",
    "content": "import type { Component, DefineComponent } from 'vue';\n\nimport type {\n  AccessModeType,\n  GenerateMenuAndRoutesOptions,\n  RouteRecordRaw,\n} from '@vben/types';\n\nimport { defineComponent, h } from 'vue';\n\nimport {\n  cloneDeep,\n  generateMenus,\n  generateRoutesByBackend,\n  generateRoutesByFrontend,\n  isFunction,\n  isString,\n  mapTree,\n} from '@vben/utils';\n\nasync function generateAccessible(\n  mode: AccessModeType,\n  options: GenerateMenuAndRoutesOptions,\n) {\n  const { router } = options;\n\n  options.routes = cloneDeep(options.routes);\n\n  // 生成路由\n  const accessibleRoutes = await generateRoutes(mode, options);\n\n  const root = router.getRoutes().find((item) => item.path === '/');\n\n  // 获取已有的路由名称列表\n  const names = root?.children?.map((item) => item.name) ?? [];\n\n  // 动态添加到router实例内\n  accessibleRoutes.forEach((route) => {\n    if (root && !route.meta?.noBasicLayout) {\n      // 为了兼容之前的版本用法，如果包含子路由，则将component移除，以免出现多层BasicLayout\n      // 如果你的项目已经跟进了本次修改，移除了所有自定义菜单首级的BasicLayout，可以将这段if代码删除\n      if (route.children && route.children.length > 0) {\n        delete route.component;\n      }\n      // 根据router name判断，如果路由已经存在，则不再添加\n      if (names?.includes(route.name)) {\n        // 找到已存在的路由索引并更新，不更新会造成切换用户时，一级目录未更新，homePath 在二级目录导致的404问题\n        const index = root.children?.findIndex(\n          (item) => item.name === route.name,\n        );\n        if (index !== undefined && index !== -1 && root.children) {\n          root.children[index] = route;\n        }\n      } else {\n        root.children?.push(route);\n      }\n    } else {\n      router.addRoute(route);\n    }\n  });\n\n  if (root) {\n    if (root.name) {\n      router.removeRoute(root.name);\n    }\n    router.addRoute(root);\n  }\n\n  // 生成菜单\n  const accessibleMenus = generateMenus(accessibleRoutes, options.router);\n\n  return { accessibleMenus, accessibleRoutes };\n}\n\n/**\n * Generate routes\n * @param mode\n * @param options\n */\nasync function generateRoutes(\n  mode: AccessModeType,\n  options: GenerateMenuAndRoutesOptions,\n) {\n  const { forbiddenComponent, roles, routes } = options;\n\n  let resultRoutes: RouteRecordRaw[] = routes;\n  switch (mode) {\n    case 'backend': {\n      resultRoutes = await generateRoutesByBackend(options);\n      break;\n    }\n    case 'frontend': {\n      resultRoutes = await generateRoutesByFrontend(\n        routes,\n        roles || [],\n        forbiddenComponent,\n      );\n      break;\n    }\n    case 'mixed': {\n      const [frontend_resultRoutes, backend_resultRoutes] = await Promise.all([\n        generateRoutesByFrontend(routes, roles || [], forbiddenComponent),\n        generateRoutesByBackend(options),\n      ]);\n      resultRoutes = mergeRoutesByName(\n        backend_resultRoutes,\n        frontend_resultRoutes,\n      );\n      break;\n    }\n  }\n\n  /**\n   * 调整路由树，做以下处理：\n   * 1. 对未添加redirect的路由添加redirect\n   * 2. 将懒加载的组件名称修改为当前路由的名称（如果启用了keep-alive的话）\n   */\n  resultRoutes = mapTree(resultRoutes, (route) => {\n    // 重新包装component，使用与路由名称相同的name以支持keep-alive的条件缓存。\n    if (\n      route.meta?.keepAlive &&\n      isFunction(route.component) &&\n      route.name &&\n      isString(route.name)\n    ) {\n      const originalComponent = route.component as () => Promise<{\n        default: Component | DefineComponent;\n      }>;\n      route.component = async () => {\n        const component = await originalComponent();\n        if (!component.default) return component;\n        return defineComponent({\n          name: route.name as string,\n          setup(props, { attrs, slots }) {\n            return () => h(component.default, { ...props, ...attrs }, slots);\n          },\n        });\n      };\n    }\n\n    // 如果有redirect或者没有子路由，则直接返回\n    if (route.redirect || !route.children || route.children.length === 0) {\n      return route;\n    }\n    const firstChild = route.children[0];\n\n    // 如果子路由不是以/开头，则直接返回,这种情况需要计算全部父级的path才能得出正确的path，这里不做处理\n    if (!firstChild?.path || !firstChild.path.startsWith('/')) {\n      return route;\n    }\n\n    route.redirect = firstChild.path;\n    return route;\n  });\n\n  return resultRoutes;\n}\n\n/**\n * 根据 name 合并前后端路由\n * @param baseRoutes 后端路由\n * @param extraRoutes 前端路由\n */\nfunction mergeRoutesByName(\n  baseRoutes: RouteRecordRaw[],\n  extraRoutes: RouteRecordRaw[],\n): RouteRecordRaw[] {\n  const result: RouteRecordRaw[] = [];\n  const routeMap = new Map<string, RouteRecordRaw>();\n\n  for (const route of baseRoutes) {\n    const clone = { ...route } as RouteRecordRaw;\n    result.push(clone);\n    if (clone.name && isString(clone.name)) {\n      routeMap.set(clone.name as string, clone);\n    }\n  }\n\n  for (const route of extraRoutes) {\n    if (\n      route.name &&\n      isString(route.name) &&\n      routeMap.has(route.name as string)\n    ) {\n      const existing = routeMap.get(route.name as string);\n      if (!existing) {\n        continue;\n      }\n      const existingChildren = existing.children ?? [];\n      const routeChildren = route.children ?? [];\n\n      const merged = {\n        ...route,\n        ...existing, // keep backend as base\n        meta: {\n          ...route.meta,\n          ...existing.meta, // backend meta wins on conflicts\n        },\n      } as RouteRecordRaw;\n\n      if (existingChildren.length > 0 || routeChildren.length > 0) {\n        merged.children = mergeRoutesByName(existingChildren, routeChildren);\n      }\n\n      Object.assign(existing, merged);\n    } else {\n      const clone = { ...route } as RouteRecordRaw;\n      result.push(clone);\n      if (clone.name && isString(clone.name)) {\n        routeMap.set(clone.name as string, clone);\n      }\n    }\n  }\n\n  return result;\n}\n\nexport { generateAccessible };\n"
  },
  {
    "path": "packages/effects/access/src/directive.ts",
    "content": "/**\n * Global authority directive\n * Used for fine-grained control of component permissions\n * @Example v-access:role=\"[ROLE_NAME]\" or v-access:role=\"ROLE_NAME\"\n * @Example v-access:code=\"[ROLE_CODE]\" or v-access:code=\"ROLE_CODE\"\n */\nimport type { App, Directive, DirectiveBinding } from 'vue';\n\nimport { useAccess } from './use-access';\n\nfunction isAccessible(\n  el: Element,\n  binding: DirectiveBinding<string | string[]>,\n) {\n  const { accessMode, hasAccessByCodes, hasAccessByRoles } = useAccess();\n\n  const value = binding.value;\n\n  if (!value) return;\n  const authMethod =\n    accessMode.value === 'frontend' && binding.arg === 'role'\n      ? hasAccessByRoles\n      : hasAccessByCodes;\n\n  const values = Array.isArray(value) ? value : [value];\n\n  if (!authMethod(values)) {\n    el?.remove();\n  }\n}\n\nconst mounted = (el: Element, binding: DirectiveBinding<string | string[]>) => {\n  isAccessible(el, binding);\n};\n\nconst authDirective: Directive = {\n  mounted,\n};\n\nexport function registerAccessDirective(app: App) {\n  app.directive('access', authDirective);\n}\n"
  },
  {
    "path": "packages/effects/access/src/index.ts",
    "content": "export { default as AccessControl } from './access-control.vue';\nexport * from './accessible';\nexport * from './directive';\nexport * from './use-access';\n"
  },
  {
    "path": "packages/effects/access/src/use-access.ts",
    "content": "import { computed } from 'vue';\n\nimport { preferences, updatePreferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\n\nfunction useAccess() {\n  const accessStore = useAccessStore();\n  const userStore = useUserStore();\n  const accessMode = computed(() => {\n    return preferences.app.accessMode;\n  });\n\n  /**\n   * 基于角色判断是否有权限\n   * @description: Determine whether there is permission，The role is judged by the user's role\n   * @param roles\n   */\n  function hasAccessByRoles(roles: string[]) {\n    const userRoleSet = new Set(userStore.userRoles);\n    const intersection = roles.filter((item) => userRoleSet.has(item));\n    return intersection.length > 0;\n  }\n\n  /**\n   * 基于权限码判断是否有权限\n   * @description: Determine whether there is permission，The permission code is judged by the user's permission code\n   * @param codes\n   */\n  function hasAccessByCodes(codes: string[]) {\n    const userCodesSet = new Set(accessStore.accessCodes);\n\n    const intersection = codes.filter((item) => userCodesSet.has(item));\n    return intersection.length > 0;\n  }\n\n  async function toggleAccessMode() {\n    updatePreferences({\n      app: {\n        accessMode:\n          preferences.app.accessMode === 'frontend' ? 'backend' : 'frontend',\n      },\n    });\n  }\n\n  return {\n    accessMode,\n    hasAccessByCodes,\n    hasAccessByRoles,\n    toggleAccessMode,\n  };\n}\n\nexport { useAccess };\n"
  },
  {
    "path": "packages/effects/access/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/effects/common-ui/package.json",
    "content": "{\n  \"name\": \"@vben/common-ui\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/effects/common-ui\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    },\n    \"./es/tippy\": {\n      \"types\": \"./src/components/tippy/index.ts\",\n      \"default\": \"./src/components/tippy/index.ts\"\n    },\n    \"./es/loading\": {\n      \"types\": \"./src/components/loading/index.ts\",\n      \"default\": \"./src/components/loading/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/design\": \"workspace:*\",\n    \"@vben-core/form-ui\": \"workspace:*\",\n    \"@vben-core/popup-ui\": \"workspace:*\",\n    \"@vben-core/preferences\": \"workspace:*\",\n    \"@vben-core/shadcn-ui\": \"workspace:*\",\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben/constants\": \"workspace:*\",\n    \"@vben/hooks\": \"workspace:*\",\n    \"@vben/icons\": \"workspace:*\",\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"@vueuse/integrations\": \"catalog:\",\n    \"json-bigint\": \"catalog:\",\n    \"qrcode\": \"catalog:\",\n    \"tippy.js\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-json-viewer\": \"catalog:\",\n    \"vue-router\": \"catalog:\",\n    \"vue-tippy\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/json-bigint\": \"catalog:\",\n    \"@types/qrcode\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/api-component/api-component.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Component } from 'vue';\n\nimport type { AnyPromiseFunction } from '@vben/types';\n\nimport { computed, nextTick, ref, unref, useAttrs, watch } from 'vue';\n\nimport { LoaderCircle } from '@vben/icons';\n\nimport { cloneDeep, get, isEqual, isFunction } from '@vben-core/shared/utils';\n\nimport { objectOmit } from '@vueuse/core';\n\ntype OptionsItem = {\n  [name: string]: any;\n  children?: OptionsItem[];\n  disabled?: boolean;\n  label?: string;\n  value?: string;\n};\n\ninterface Props {\n  /** 组件 */\n  component: Component;\n  /** 是否将value从数字转为string */\n  numberToString?: boolean;\n  /** 获取options数据的函数 */\n  api?: (arg?: any) => Promise<OptionsItem[] | Record<string, any>>;\n  /** 传递给api的参数 */\n  params?: Record<string, any>;\n  /** 从api返回的结果中提取options数组的字段名 */\n  resultField?: string;\n  /** label字段名 */\n  labelField?: string;\n  /** children字段名，需要层级数据的组件可用 */\n  childrenField?: string;\n  /** value字段名 */\n  valueField?: string;\n  /** disabled字段名 */\n  disabledField?: string;\n  /** 组件接收options数据的属性名 */\n  optionsPropName?: string;\n  /** 是否立即调用api */\n  immediate?: boolean;\n  /** 每次`visibleEvent`事件发生时都重新请求数据 */\n  alwaysLoad?: boolean;\n  /** 在api请求之前的回调函数 */\n  beforeFetch?: AnyPromiseFunction<any, any>;\n  /** 在api请求之后的回调函数 */\n  afterFetch?: AnyPromiseFunction<any, any>;\n  /** 直接传入选项数据，也作为api返回空数据时的后备数据 */\n  options?: OptionsItem[];\n  /** 组件的插槽名称，用来显示一个\"加载中\"的图标 */\n  loadingSlot?: string;\n  /** 触发api请求的事件名 */\n  visibleEvent?: string;\n  /** 组件的v-model属性名，默认为modelValue。部分组件可能为value */\n  modelPropName?: string;\n  /**\n   * 自动选择\n   * - `first`：自动选择第一个选项\n   * - `last`：自动选择最后一个选项\n   * - `one`: 当请求的结果只有一个选项时，自动选择该选项\n   * - 函数：自定义选择逻辑，函数的参数为请求的结果数组，返回值为选择的选项\n   * - false：不自动选择(默认)\n   */\n  autoSelect?:\n    | 'first'\n    | 'last'\n    | 'one'\n    | ((item: OptionsItem[]) => OptionsItem)\n    | false;\n}\n\ndefineOptions({ name: 'ApiComponent', inheritAttrs: false });\n\nconst props = withDefaults(defineProps<Props>(), {\n  labelField: 'label',\n  valueField: 'value',\n  disabledField: 'disabled',\n  childrenField: '',\n  optionsPropName: 'options',\n  resultField: '',\n  visibleEvent: '',\n  numberToString: false,\n  params: () => ({}),\n  immediate: true,\n  alwaysLoad: false,\n  loadingSlot: '',\n  beforeFetch: undefined,\n  afterFetch: undefined,\n  modelPropName: 'modelValue',\n  api: undefined,\n  autoSelect: false,\n  options: () => [],\n});\n\nconst emit = defineEmits<{\n  optionsChange: [OptionsItem[]];\n}>();\n\nconst modelValue = defineModel<any>({ default: undefined });\n\nconst attrs = useAttrs();\nconst innerParams = ref({});\nconst refOptions = ref<OptionsItem[]>([]);\nconst loading = ref(false);\n// 首次是否加载过了\nconst isFirstLoaded = ref(false);\n// 标记是否有待处理的请求\nconst hasPendingRequest = ref(false);\n\nconst getOptions = computed(() => {\n  const {\n    labelField,\n    valueField,\n    disabledField,\n    childrenField,\n    numberToString,\n  } = props;\n\n  const refOptionsData = unref(refOptions);\n\n  function transformData(data: OptionsItem[]): OptionsItem[] {\n    return data.map((item) => {\n      const value = get(item, valueField);\n      const disabled = get(item, disabledField);\n      return {\n        ...objectOmit(item, [labelField, valueField, disabled, childrenField]),\n        label: get(item, labelField),\n        value: numberToString ? `${value}` : value,\n        disabled: get(item, disabledField),\n        ...(childrenField && item[childrenField]\n          ? { children: transformData(item[childrenField]) }\n          : {}),\n      };\n    });\n  }\n\n  const data: OptionsItem[] = transformData(refOptionsData);\n\n  return data.length > 0 ? data : props.options;\n});\n\nconst bindProps = computed(() => {\n  return {\n    [props.modelPropName]: unref(modelValue),\n    [props.optionsPropName]: unref(getOptions),\n    [`onUpdate:${props.modelPropName}`]: (val: string) => {\n      modelValue.value = val;\n    },\n    ...objectOmit(attrs, [`onUpdate:${props.modelPropName}`]),\n    ...(props.visibleEvent\n      ? {\n          [props.visibleEvent]: handleFetchForVisible,\n        }\n      : {}),\n  };\n});\n\nasync function fetchApi() {\n  const { api, beforeFetch, afterFetch, resultField } = props;\n\n  if (!api || !isFunction(api)) {\n    return;\n  }\n\n  // 如果正在加载，标记有待处理的请求并返回\n  if (loading.value) {\n    hasPendingRequest.value = true;\n    return;\n  }\n\n  refOptions.value = [];\n  try {\n    loading.value = true;\n    let finalParams = unref(mergedParams);\n    if (beforeFetch && isFunction(beforeFetch)) {\n      finalParams = (await beforeFetch(cloneDeep(finalParams))) || finalParams;\n    }\n    let res = await api(finalParams);\n    if (afterFetch && isFunction(afterFetch)) {\n      res = (await afterFetch(res)) || res;\n    }\n    isFirstLoaded.value = true;\n    if (Array.isArray(res)) {\n      refOptions.value = res;\n      emitChange();\n      return;\n    }\n    if (resultField) {\n      refOptions.value = get(res, resultField) || [];\n    }\n    emitChange();\n  } catch (error) {\n    console.warn(error);\n    // reset status\n    isFirstLoaded.value = false;\n  } finally {\n    loading.value = false;\n    // 如果有待处理的请求，立即触发新的请求\n    if (hasPendingRequest.value) {\n      hasPendingRequest.value = false;\n      // 使用 nextTick 确保状态更新完成后再触发新请求\n      await nextTick();\n      fetchApi();\n    }\n  }\n}\n\nasync function handleFetchForVisible(visible: boolean) {\n  if (visible) {\n    if (props.alwaysLoad) {\n      await fetchApi();\n    } else if (!props.immediate && !unref(isFirstLoaded)) {\n      await fetchApi();\n    }\n  }\n}\n\nconst mergedParams = computed(() => {\n  return {\n    ...props.params,\n    ...unref(innerParams),\n  };\n});\n\nwatch(\n  mergedParams,\n  (value, oldValue) => {\n    if (isEqual(value, oldValue)) {\n      return;\n    }\n    fetchApi();\n  },\n  { deep: true, immediate: props.immediate },\n);\n\nfunction emitChange() {\n  if (\n    modelValue.value === undefined &&\n    props.autoSelect &&\n    unref(getOptions).length > 0\n  ) {\n    let firstOption;\n    if (isFunction(props.autoSelect)) {\n      firstOption = props.autoSelect(unref(getOptions));\n    } else {\n      switch (props.autoSelect) {\n        case 'first': {\n          firstOption = unref(getOptions)[0];\n          break;\n        }\n        case 'last': {\n          firstOption = unref(getOptions)[unref(getOptions).length - 1];\n          break;\n        }\n        case 'one': {\n          if (unref(getOptions).length === 1) {\n            firstOption = unref(getOptions)[0];\n          }\n          break;\n        }\n      }\n    }\n\n    if (firstOption) modelValue.value = firstOption.value;\n  }\n  emit('optionsChange', unref(getOptions));\n}\nconst componentRef = ref();\ndefineExpose({\n  /** 获取options数据 */\n  getOptions: () => unref(getOptions),\n  /** 获取当前值 */\n  getValue: () => unref(modelValue),\n  /** 获取被包装的组件实例 */\n  getComponentRef: <T = any>() => componentRef.value as T,\n  /** 更新Api参数 */\n  updateParam(newParams: Record<string, any>) {\n    innerParams.value = newParams;\n  },\n});\n</script>\n<template>\n  <component\n    :is=\"component\"\n    v-bind=\"bindProps\"\n    :placeholder=\"$attrs.placeholder\"\n    ref=\"componentRef\"\n  >\n    <template v-for=\"item in Object.keys($slots)\" #[item]=\"data\">\n      <slot :name=\"item\" v-bind=\"data || {}\"></slot>\n    </template>\n    <template v-if=\"loadingSlot && loading\" #[loadingSlot]>\n      <LoaderCircle class=\"animate-spin\" />\n    </template>\n  </component>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/api-component/index.ts",
    "content": "export { default as ApiComponent } from './api-component.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/hooks/useCaptchaPoints.ts",
    "content": "import type { CaptchaPoint } from '../types';\n\nimport { reactive } from 'vue';\n\nexport function useCaptchaPoints() {\n  const points = reactive<CaptchaPoint[]>([]);\n  function addPoint(point: CaptchaPoint) {\n    points.push(point);\n  }\n\n  function clearPoints() {\n    points.splice(0);\n  }\n  return {\n    addPoint,\n    clearPoints,\n    points,\n  };\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/index.ts",
    "content": "export { default as PointSelectionCaptcha } from './point-selection-captcha/index.vue';\nexport { default as PointSelectionCaptchaCard } from './point-selection-captcha/index.vue';\n\nexport { default as SliderCaptcha } from './slider-captcha/index.vue';\nexport { default as SliderRotateCaptcha } from './slider-rotate-captcha/index.vue';\nexport { default as SliderTranslateCaptcha } from './slider-translate-captcha/index.vue';\nexport type * from './types';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/point-selection-captcha/index.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CaptchaPoint, PointSelectionCaptchaProps } from '../types';\n\nimport { RotateCw } from '@vben/icons';\nimport { $t } from '@vben/locales';\n\nimport { VbenButton, VbenIconButton } from '@vben-core/shadcn-ui';\n\nimport { useCaptchaPoints } from '../hooks/useCaptchaPoints';\nimport CaptchaCard from './point-selection-captcha-card.vue';\n\nconst props = withDefaults(defineProps<PointSelectionCaptchaProps>(), {\n  height: '220px',\n  hintImage: '',\n  hintText: '',\n  paddingX: '12px',\n  paddingY: '16px',\n  showConfirm: false,\n  title: '',\n  width: '300px',\n});\nconst emit = defineEmits<{\n  click: [CaptchaPoint];\n  confirm: [Array<CaptchaPoint>, clear: () => void];\n  refresh: [];\n}>();\nconst { addPoint, clearPoints, points } = useCaptchaPoints();\n\nif (!props.hintImage && !props.hintText) {\n  console.warn('At least one of hint image or hint text must be provided');\n}\n\nconst POINT_OFFSET = 11;\n\nfunction getElementPosition(element: HTMLElement) {\n  const rect = element.getBoundingClientRect();\n  return {\n    x: rect.left + window.scrollX,\n    y: rect.top + window.scrollY,\n  };\n}\n\nfunction handleClick(e: MouseEvent) {\n  try {\n    const dom = e.currentTarget as HTMLElement;\n    if (!dom) throw new Error('Element not found');\n\n    const { x: domX, y: domY } = getElementPosition(dom);\n\n    const mouseX = e.clientX + window.scrollX;\n    const mouseY = e.clientY + window.scrollY;\n\n    if (typeof mouseX !== 'number' || typeof mouseY !== 'number') {\n      throw new TypeError('Mouse coordinates not found');\n    }\n\n    const xPos = mouseX - domX;\n    const yPos = mouseY - domY;\n\n    const rect = dom.getBoundingClientRect();\n\n    // 点击位置边界校验\n    if (xPos < 0 || yPos < 0 || xPos > rect.width || yPos > rect.height) {\n      console.warn('Click position is out of the valid range');\n      return;\n    }\n\n    const x = Math.ceil(xPos);\n    const y = Math.ceil(yPos);\n\n    const point = {\n      i: points.length,\n      t: Date.now(),\n      x,\n      y,\n    };\n\n    addPoint(point);\n\n    emit('click', point);\n    e.stopPropagation();\n    e.preventDefault();\n  } catch (error) {\n    console.error('Error in handleClick:', error);\n  }\n}\n\nfunction clear() {\n  try {\n    clearPoints();\n  } catch (error) {\n    console.error('Error in clear:', error);\n  }\n}\n\nfunction handleRefresh() {\n  try {\n    clear();\n    emit('refresh');\n  } catch (error) {\n    console.error('Error in handleRefresh:', error);\n  }\n}\n\nfunction handleConfirm() {\n  if (!props.showConfirm) return;\n  try {\n    emit('confirm', points, clear);\n  } catch (error) {\n    console.error('Error in handleConfirm:', error);\n  }\n}\n</script>\n<template>\n  <CaptchaCard\n    :captcha-image=\"captchaImage\"\n    :height=\"height\"\n    :padding-x=\"paddingX\"\n    :padding-y=\"paddingY\"\n    :title=\"title\"\n    :width=\"width\"\n    @click=\"handleClick\"\n  >\n    <template #title>\n      <slot name=\"title\">{{ $t('ui.captcha.title') }}</slot>\n    </template>\n\n    <template #extra>\n      <VbenIconButton\n        :aria-label=\"$t('ui.captcha.refreshAriaLabel')\"\n        class=\"ml-1\"\n        @click=\"handleRefresh\"\n      >\n        <RotateCw class=\"size-5\" />\n      </VbenIconButton>\n      <VbenButton\n        v-if=\"showConfirm\"\n        :aria-label=\"$t('ui.captcha.confirmAriaLabel')\"\n        class=\"ml-2\"\n        size=\"sm\"\n        @click=\"handleConfirm\"\n      >\n        {{ $t('ui.captcha.confirm') }}\n      </VbenButton>\n    </template>\n\n    <div\n      v-for=\"(point, index) in points\"\n      :key=\"index\"\n      :aria-label=\"$t('ui.captcha.pointAriaLabel') + (index + 1)\"\n      :style=\"{\n        top: `${point.y - POINT_OFFSET}px`,\n        left: `${point.x - POINT_OFFSET}px`,\n      }\"\n      class=\"absolute z-20 flex-center size-5 cursor-default rounded-full border-2 border-primary-50 bg-primary text-primary-50\"\n      role=\"button\"\n      tabindex=\"0\"\n    >\n      {{ index + 1 }}\n    </div>\n    <template #footer>\n      <img\n        v-if=\"hintImage\"\n        :alt=\"$t('ui.captcha.alt')\"\n        :src=\"hintImage\"\n        class=\"h-10 w-full rounded-sm border border-border\"\n      />\n      <div\n        v-else-if=\"hintText\"\n        class=\"flex-center h-10 w-full rounded-sm border border-border\"\n      >\n        {{ `${$t('ui.captcha.clickInOrder')}` + `【${hintText}】` }}\n      </div>\n    </template>\n  </CaptchaCard>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/point-selection-captcha/point-selection-captcha-card.vue",
    "content": "<script setup lang=\"ts\">\nimport type { PointSelectionCaptchaCardProps } from '../types';\n\nimport { computed } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport {\n  Card,\n  CardContent,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from '@vben-core/shadcn-ui';\n\nconst props = withDefaults(defineProps<PointSelectionCaptchaCardProps>(), {\n  height: '220px',\n  paddingX: '12px',\n  paddingY: '16px',\n  title: '',\n  width: '300px',\n});\n\nconst emit = defineEmits<{\n  click: [MouseEvent];\n}>();\n\nconst parseValue = (value: number | string) => {\n  if (typeof value === 'number') {\n    return value;\n  }\n  const parsed = Number.parseFloat(value);\n  return Number.isNaN(parsed) ? 0 : parsed;\n};\n\nconst rootStyles = computed(() => ({\n  padding: `${parseValue(props.paddingY)}px ${parseValue(props.paddingX)}px`,\n  width: `${parseValue(props.width) + parseValue(props.paddingX) * 2}px`,\n}));\n\nconst captchaStyles = computed(() => {\n  return {\n    height: `${parseValue(props.height)}px`,\n    width: `${parseValue(props.width)}px`,\n  };\n});\n\nfunction handleClick(e: MouseEvent) {\n  emit('click', e);\n}\n</script>\n<template>\n  <Card :style=\"rootStyles\" aria-labelledby=\"captcha-title\" role=\"region\">\n    <CardHeader class=\"p-0\">\n      <CardTitle id=\"captcha-title\" class=\"flex items-center justify-between\">\n        <template v-if=\"$slots.title\">\n          <slot name=\"title\">{{ $t('ui.captcha.title') }}</slot>\n        </template>\n        <template v-else>\n          <span>{{ title }}</span>\n        </template>\n        <div class=\"flex items-center justify-end\">\n          <slot name=\"extra\"></slot>\n        </div>\n      </CardTitle>\n    </CardHeader>\n    <CardContent\n      class=\"relative mt-2 flex w-full overflow-hidden rounded-sm p-0\"\n    >\n      <img\n        v-show=\"captchaImage\"\n        :alt=\"$t('ui.captcha.alt')\"\n        :src=\"captchaImage\"\n        :style=\"captchaStyles\"\n        class=\"relative z-10\"\n        @click=\"handleClick\"\n      />\n      <div class=\"absolute inset-0\">\n        <slot></slot>\n      </div>\n    </CardContent>\n    <CardFooter class=\"mt-2 flex justify-between p-0\">\n      <slot name=\"footer\"></slot>\n    </CardFooter>\n  </Card>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/slider-captcha/index.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  CaptchaVerifyPassingData,\n  SliderCaptchaProps,\n  SliderRotateVerifyPassingData,\n} from '../types';\n\nimport { reactive, unref, useTemplateRef, watch, watchEffect } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport { cn } from '@vben-core/shared/utils';\n\nimport { useTimeoutFn } from '@vueuse/core';\n\nimport SliderCaptchaAction from './slider-captcha-action.vue';\nimport SliderCaptchaBar from './slider-captcha-bar.vue';\nimport SliderCaptchaContent from './slider-captcha-content.vue';\n\nconst props = withDefaults(defineProps<SliderCaptchaProps>(), {\n  actionStyle: () => ({}),\n  barStyle: () => ({}),\n  contentStyle: () => ({}),\n  isSlot: false,\n  successText: '',\n  text: '',\n  wrapperStyle: () => ({}),\n});\n\nconst emit = defineEmits<{\n  end: [MouseEvent | TouchEvent];\n  move: [SliderRotateVerifyPassingData];\n  start: [MouseEvent | TouchEvent];\n  success: [CaptchaVerifyPassingData];\n}>();\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst state = reactive({\n  endTime: 0,\n  isMoving: false,\n  isPassing: false,\n  moveDistance: 0,\n  startTime: 0,\n  toLeft: false,\n});\n\ndefineExpose({\n  resume,\n});\n\nconst wrapperRef = useTemplateRef<HTMLDivElement>('wrapperRef');\nconst barRef = useTemplateRef<InstanceType<typeof SliderCaptchaBar>>('barRef');\nconst contentRef =\n  useTemplateRef<InstanceType<typeof SliderCaptchaContent>>('contentRef');\nconst actionRef =\n  useTemplateRef<InstanceType<typeof SliderCaptchaAction>>('actionRef');\n\nwatch(\n  () => state.isPassing,\n  (isPassing) => {\n    if (isPassing) {\n      const { endTime, startTime } = state;\n      const time = (endTime - startTime) / 1000;\n      emit('success', { isPassing, time: time.toFixed(1) });\n      modelValue.value = isPassing;\n    }\n  },\n);\n\nwatchEffect(() => {\n  state.isPassing = !!modelValue.value;\n});\n\nfunction getEventPageX(e: MouseEvent | TouchEvent): number {\n  if ('pageX' in e) {\n    return e.pageX;\n  } else if ('touches' in e && e.touches[0]) {\n    return e.touches[0].pageX;\n  }\n  return 0;\n}\n\nfunction handleDragStart(e: MouseEvent | TouchEvent) {\n  if (state.isPassing) {\n    return;\n  }\n  const actionEl = actionRef.value;\n  const actionStyle = actionEl?.getStyle();\n  if (!actionEl || !actionStyle) return;\n  emit('start', e);\n\n  state.moveDistance =\n    getEventPageX(e) -\n    Number.parseInt(actionStyle.left.replace('px', '') || '0', 10);\n  state.startTime = Date.now();\n  state.isMoving = true;\n}\n\nfunction getOffset(actionEl?: HTMLDivElement | null) {\n  const wrapperWidth = wrapperRef.value?.offsetWidth ?? 220;\n  const actionWidth = actionEl?.offsetWidth ?? 40;\n  const offset = wrapperWidth - actionWidth - 6;\n  return { actionWidth, offset, wrapperWidth };\n}\n\nfunction handleDragMoving(e: MouseEvent | TouchEvent) {\n  const { isMoving, moveDistance } = state;\n  if (isMoving) {\n    const actionEl = unref(actionRef);\n    const barEl = unref(barRef);\n    if (!actionEl || !barEl) return;\n    const actionNode = actionEl.getEl();\n    if (!actionNode) return;\n    const { actionWidth, offset, wrapperWidth } = getOffset(actionNode);\n    const moveX = getEventPageX(e) - moveDistance;\n\n    emit('move', {\n      event: e,\n      moveDistance,\n      moveX,\n    });\n    if (moveX > 0 && moveX <= offset) {\n      actionEl.setLeft(`${moveX}px`);\n      barEl.setWidth(`${moveX + actionWidth / 2}px`);\n    } else if (moveX > offset) {\n      actionEl.setLeft(`${wrapperWidth - actionWidth}px`);\n      barEl.setWidth(`${wrapperWidth - actionWidth / 2}px`);\n      if (!props.isSlot) {\n        checkPass();\n      }\n    }\n  }\n}\n\nfunction handleDragOver(e: MouseEvent | TouchEvent) {\n  const { isMoving, isPassing, moveDistance } = state;\n  if (isMoving && !isPassing) {\n    emit('end', e);\n    const actionEl = actionRef.value;\n    const barEl = unref(barRef);\n    if (!actionEl || !barEl) return;\n    const moveX = getEventPageX(e) - moveDistance;\n    const actionNode = actionEl.getEl();\n    if (!actionNode) return;\n    const { actionWidth, offset, wrapperWidth } = getOffset(actionNode);\n    if (moveX < offset) {\n      if (props.isSlot) {\n        setTimeout(() => {\n          if (modelValue.value) {\n            const contentEl = unref(contentRef);\n            const contentNode = contentEl?.getEl();\n            const barNode = barEl.getEl();\n            if (contentNode && barNode) {\n              contentNode.style.width = `${Number.parseInt(barNode.style.width || '0', 10)}px`;\n            }\n          } else {\n            resume();\n          }\n        }, 0);\n      } else {\n        resume();\n      }\n    } else {\n      actionEl.setLeft(`${wrapperWidth - actionWidth}px`);\n      barEl.setWidth(`${wrapperWidth - actionWidth / 2}px`);\n      checkPass();\n    }\n    state.isMoving = false;\n  }\n}\n\nfunction checkPass() {\n  if (props.isSlot) {\n    resume();\n    return;\n  }\n  state.endTime = Date.now();\n  state.isPassing = true;\n  state.isMoving = false;\n}\n\nfunction resume() {\n  state.isMoving = false;\n  state.isPassing = false;\n  state.moveDistance = 0;\n  state.toLeft = false;\n  state.startTime = 0;\n  state.endTime = 0;\n  const actionEl = unref(actionRef);\n  const barEl = unref(barRef);\n  const contentEl = unref(contentRef);\n  if (!actionEl || !barEl || !contentEl) return;\n\n  const contentNode = contentEl.getEl();\n  if (!contentNode) return;\n\n  contentNode.style.width = '100%';\n  state.toLeft = true;\n  useTimeoutFn(() => {\n    state.toLeft = false;\n    actionEl.setLeft('0');\n    barEl.setWidth('0');\n  }, 300);\n}\n</script>\n\n<template>\n  <div\n    ref=\"wrapperRef\"\n    :class=\"\n      cn(\n        'relative flex h-10 w-full items-center overflow-hidden rounded-md border border-border bg-background-deep text-center',\n        props.class,\n      )\n    \"\n    :style=\"wrapperStyle\"\n    @mouseleave=\"handleDragOver\"\n    @mousemove=\"handleDragMoving\"\n    @mouseup=\"handleDragOver\"\n    @touchend=\"handleDragOver\"\n    @touchmove=\"handleDragMoving\"\n  >\n    <SliderCaptchaBar\n      ref=\"barRef\"\n      :bar-style=\"barStyle\"\n      :to-left=\"state.toLeft\"\n    />\n    <SliderCaptchaContent\n      ref=\"contentRef\"\n      :content-style=\"contentStyle\"\n      :is-passing=\"state.isPassing\"\n      :success-text=\"successText || $t('ui.captcha.sliderSuccessText')\"\n      :text=\"text || $t('ui.captcha.sliderDefaultText')\"\n    >\n      <template v-if=\"$slots.text\" #text>\n        <slot :is-passing=\"state.isPassing\" name=\"text\"></slot>\n      </template>\n    </SliderCaptchaContent>\n\n    <SliderCaptchaAction\n      ref=\"actionRef\"\n      :action-style=\"actionStyle\"\n      :is-passing=\"state.isPassing\"\n      :to-left=\"state.toLeft\"\n      @mousedown=\"handleDragStart\"\n      @touchstart=\"handleDragStart\"\n    >\n      <template v-if=\"$slots.actionIcon\" #icon>\n        <slot :is-passing=\"state.isPassing\" name=\"actionIcon\"></slot>\n      </template>\n    </SliderCaptchaAction>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/slider-captcha/slider-captcha-action.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\nimport { computed, ref, useTemplateRef } from 'vue';\n\nimport { Check, ChevronsRight } from '@vben/icons';\n\nimport { Slot } from '@vben-core/shadcn-ui';\n\nconst props = defineProps<{\n  actionStyle: CSSProperties;\n  isPassing: boolean;\n  toLeft: boolean;\n}>();\n\nconst actionRef = useTemplateRef<HTMLDivElement>('actionRef');\n\nconst left = ref('0');\n\nconst style = computed(() => {\n  const { actionStyle } = props;\n  return {\n    ...actionStyle,\n    left: left.value,\n  };\n});\n\nconst isDragging = computed(() => {\n  const currentLeft = Number.parseInt(left.value as string);\n\n  return currentLeft > 10 && !props.isPassing;\n});\n\ndefineExpose({\n  getEl: () => {\n    return actionRef.value;\n  },\n  getStyle: () => {\n    return actionRef?.value?.style;\n  },\n  setLeft: (val: string) => {\n    left.value = val;\n  },\n});\n</script>\n\n<template>\n  <div\n    ref=\"actionRef\"\n    :class=\"{\n      'transition-width left-0! duration-300': toLeft,\n      'rounded-md': isDragging,\n    }\"\n    :style=\"style\"\n    class=\"absolute top-0 left-0 flex-center h-full cursor-move bg-background px-3.5 shadow-md dark:bg-accent\"\n    name=\"captcha-action\"\n  >\n    <Slot :is-passing=\"isPassing\" class=\"size-4 text-foreground/60\">\n      <slot name=\"icon\">\n        <ChevronsRight v-if=\"!isPassing\" />\n        <Check v-else />\n      </slot>\n    </Slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/slider-captcha/slider-captcha-bar.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\nimport { computed, ref, useTemplateRef } from 'vue';\n\nconst props = defineProps<{\n  barStyle: CSSProperties;\n  toLeft: boolean;\n}>();\n\nconst barRef = useTemplateRef<HTMLDivElement>('barRef');\n\nconst width = ref('0');\n\nconst style = computed(() => {\n  const { barStyle } = props;\n  return {\n    ...barStyle,\n    width: width.value,\n  };\n});\n\ndefineExpose({\n  getEl: () => {\n    return barRef.value;\n  },\n  setWidth: (val: string) => {\n    width.value = val;\n  },\n});\n</script>\n\n<template>\n  <div\n    ref=\"barRef\"\n    :class=\"toLeft && 'transition-width w-0! duration-300'\"\n    :style=\"style\"\n    class=\"absolute h-full bg-success\"\n  ></div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/slider-captcha/slider-captcha-content.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\nimport { computed, useTemplateRef } from 'vue';\n\nimport { VbenSpineText } from '@vben-core/shadcn-ui';\n\nconst props = defineProps<{\n  contentStyle: CSSProperties;\n  isPassing: boolean;\n  successText: string;\n  text: string;\n}>();\n\nconst contentRef = useTemplateRef<HTMLDivElement>('contentRef');\n\nconst style = computed(() => {\n  const { contentStyle } = props;\n\n  return {\n    ...contentStyle,\n  };\n});\n\ndefineExpose({\n  getEl: () => {\n    return contentRef.value;\n  },\n});\n</script>\n\n<template>\n  <div\n    ref=\"contentRef\"\n    :class=\"{\n      [$style.success]: isPassing,\n    }\"\n    :style=\"style\"\n    class=\"absolute top-0 flex-center size-full text-xs select-none\"\n  >\n    <slot name=\"text\">\n      <VbenSpineText class=\"flex h-full items-center\">\n        {{ isPassing ? successText : text }}\n      </VbenSpineText>\n    </slot>\n  </div>\n</template>\n\n<style module>\n.success {\n  -webkit-text-fill-color: hsl(0deg 0% 98%);\n}\n</style>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/slider-rotate-captcha/index.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  CaptchaVerifyPassingData,\n  SliderCaptchaActionType,\n  SliderRotateCaptchaProps,\n  SliderRotateVerifyPassingData,\n} from '../types';\n\nimport { computed, reactive, unref, useTemplateRef, watch } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport { useTimeoutFn } from '@vueuse/core';\n\nimport SliderCaptcha from '../slider-captcha/index.vue';\n\nconst props = withDefaults(defineProps<SliderRotateCaptchaProps>(), {\n  defaultTip: '',\n  diffDegree: 20,\n  imageSize: 260,\n  maxDegree: 300,\n  minDegree: 120,\n  src: '',\n});\n\nconst emit = defineEmits<{\n  success: [CaptchaVerifyPassingData];\n}>();\n\nconst slideBarRef = useTemplateRef<SliderCaptchaActionType>('slideBarRef');\n\nconst state = reactive({\n  currentRotate: 0,\n  dragging: false,\n  endTime: 0,\n  imgStyle: {},\n  isPassing: false,\n  randomRotate: 0,\n  showTip: false,\n  startTime: 0,\n  toOrigin: false,\n});\n\nconst modalValue = defineModel<boolean>({ default: false });\n\nwatch(\n  () => state.isPassing,\n  (isPassing) => {\n    if (isPassing) {\n      const { endTime, startTime } = state;\n      const time = (endTime - startTime) / 1000;\n      emit('success', { isPassing, time: time.toFixed(1) });\n    }\n    modalValue.value = isPassing;\n  },\n);\n\nconst getImgWrapStyleRef = computed(() => {\n  const { imageSize, imageWrapperStyle } = props;\n  return {\n    height: `${imageSize}px`,\n    width: `${imageSize}px`,\n    ...imageWrapperStyle,\n  };\n});\n\nconst getFactorRef = computed(() => {\n  const { maxDegree, minDegree } = props;\n  if (minDegree > maxDegree) {\n    console.warn('minDegree should not be greater than maxDegree');\n  }\n\n  if (minDegree === maxDegree) {\n    return Math.floor(1 + Math.random() * 1) / 10 + 1;\n  }\n  return 1;\n});\n\nfunction handleStart() {\n  state.startTime = Date.now();\n}\n\nfunction handleDragBarMove(data: SliderRotateVerifyPassingData) {\n  state.dragging = true;\n  const { imageSize, maxDegree } = props;\n  const { moveX } = data;\n  const denominator = imageSize;\n  if (denominator === 0) {\n    return;\n  }\n  const currentRotate = Math.ceil(\n    (moveX / denominator) * 1.5 * maxDegree * unref(getFactorRef),\n  );\n  state.currentRotate = currentRotate;\n  setImgRotate(state.randomRotate - currentRotate);\n}\n\nfunction handleImgOnLoad() {\n  const { maxDegree, minDegree } = props;\n  const ranRotate = Math.floor(\n    minDegree + Math.random() * (maxDegree - minDegree),\n  ); // 生成随机角度\n  state.randomRotate = ranRotate;\n  setImgRotate(ranRotate);\n}\n\nfunction handleDragEnd() {\n  const { currentRotate, randomRotate } = state;\n  const { diffDegree } = props;\n\n  if (Math.abs(randomRotate - currentRotate) >= (diffDegree || 20)) {\n    setImgRotate(randomRotate);\n    state.toOrigin = true;\n    useTimeoutFn(() => {\n      state.toOrigin = false;\n      state.showTip = true;\n      //  时间与动画时间保持一致\n    }, 300);\n  } else {\n    checkPass();\n  }\n  state.showTip = true;\n  state.dragging = false;\n}\n\nfunction setImgRotate(deg: number) {\n  state.imgStyle = {\n    transform: `rotateZ(${deg}deg)`,\n  };\n}\n\nfunction checkPass() {\n  state.isPassing = true;\n  state.endTime = Date.now();\n}\n\nfunction resume() {\n  state.showTip = false;\n  const basicEl = unref(slideBarRef);\n  if (!basicEl) {\n    return;\n  }\n  state.isPassing = false;\n\n  basicEl.resume();\n  handleImgOnLoad();\n}\n\nconst imgCls = computed(() => {\n  return state.toOrigin ? ['transition-transform duration-300'] : [];\n});\n\nconst verifyTip = computed(() => {\n  return state.isPassing\n    ? $t('ui.captcha.sliderRotateSuccessTip', [\n        ((state.endTime - state.startTime) / 1000).toFixed(1),\n      ])\n    : $t('ui.captcha.sliderRotateFailTip');\n});\n\ndefineExpose({\n  resume,\n});\n</script>\n\n<template>\n  <div class=\"relative flex flex-col items-center\">\n    <div\n      :style=\"getImgWrapStyleRef\"\n      class=\"relative cursor-pointer overflow-hidden rounded-full border border-border shadow-md\"\n    >\n      <img\n        :class=\"imgCls\"\n        :src=\"src\"\n        :style=\"state.imgStyle\"\n        alt=\"verify\"\n        class=\"w-full rounded-full\"\n        @click=\"resume\"\n        @load=\"handleImgOnLoad\"\n      />\n      <div\n        class=\"absolute bottom-3 left-0 z-10 block h-7 w-full text-center text-xs leading-[30px] text-white\"\n      >\n        <div\n          v-if=\"state.showTip\"\n          :class=\"{\n            'bg-success/80': state.isPassing,\n            'bg-destructive/80': !state.isPassing,\n          }\"\n        >\n          {{ verifyTip }}\n        </div>\n        <div v-if=\"!state.dragging\" class=\"bg-black/30\">\n          {{ defaultTip || $t('ui.captcha.sliderRotateDefaultTip') }}\n        </div>\n      </div>\n    </div>\n\n    <SliderCaptcha\n      ref=\"slideBarRef\"\n      v-model=\"modalValue\"\n      class=\"mt-5\"\n      is-slot\n      @end=\"handleDragEnd\"\n      @move=\"handleDragBarMove\"\n      @start=\"handleStart\"\n    >\n      <template v-for=\"(_, key) in $slots\" :key=\"key\" #[key]=\"slotProps\">\n        <slot :name=\"key\" v-bind=\"slotProps\"></slot>\n      </template>\n    </SliderCaptcha>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  CaptchaVerifyPassingData,\n  SliderCaptchaActionType,\n  SliderRotateVerifyPassingData,\n  SliderTranslateCaptchaProps,\n} from '../types';\n\nimport {\n  computed,\n  onMounted,\n  reactive,\n  ref,\n  unref,\n  useTemplateRef,\n  watch,\n} from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport SliderCaptcha from '../slider-captcha/index.vue';\n\nconst props = withDefaults(defineProps<SliderTranslateCaptchaProps>(), {\n  defaultTip: '',\n  canvasWidth: 420,\n  canvasHeight: 280,\n  squareLength: 42,\n  circleRadius: 10,\n  src: '',\n  diffDistance: 3,\n});\n\nconst emit = defineEmits<{\n  success: [CaptchaVerifyPassingData];\n}>();\n\nconst PI: number = Math.PI;\nconst canvasOpr = {\n  clip: 'clip',\n  fill: 'fill',\n} as const;\n\ntype CanvasOpr = (typeof canvasOpr)[keyof typeof canvasOpr];\n\nconst modalValue = defineModel<boolean>({ default: false });\n\nconst slideBarRef = useTemplateRef<SliderCaptchaActionType>('slideBarRef');\nconst puzzleCanvasRef = useTemplateRef<HTMLCanvasElement>('puzzleCanvasRef');\nconst pieceCanvasRef = useTemplateRef<HTMLCanvasElement>('pieceCanvasRef');\n\nconst state = reactive({\n  dragging: false,\n  startTime: 0,\n  endTime: 0,\n  pieceX: 0,\n  pieceY: 0,\n  moveDistance: 0,\n  isPassing: false,\n  showTip: false,\n});\n\nconst left = ref('0');\n\nconst pieceStyle = computed(() => {\n  return {\n    left: left.value,\n  };\n});\n\nfunction setLeft(val: string) {\n  left.value = val;\n}\n\nconst verifyTip = computed(() => {\n  return state.isPassing\n    ? $t('ui.captcha.sliderTranslateSuccessTip', [\n        ((state.endTime - state.startTime) / 1000).toFixed(1),\n      ])\n    : $t('ui.captcha.sliderTranslateFailTip');\n});\nfunction handleStart() {\n  state.startTime = Date.now();\n}\n\nfunction handleDragBarMove(data: SliderRotateVerifyPassingData) {\n  state.dragging = true;\n  const { moveX } = data;\n  state.moveDistance = moveX;\n  setLeft(`${moveX}px`);\n}\n\nfunction handleDragEnd() {\n  const { pieceX } = state;\n  const { diffDistance } = props;\n\n  if (Math.abs(pieceX - state.moveDistance) >= (diffDistance || 3)) {\n    setLeft('0');\n    state.moveDistance = 0;\n  } else {\n    checkPass();\n  }\n  state.showTip = true;\n  state.dragging = false;\n}\n\nfunction checkPass() {\n  state.isPassing = true;\n  state.endTime = Date.now();\n}\n\nwatch(\n  () => state.isPassing,\n  (isPassing) => {\n    if (isPassing) {\n      const { endTime, startTime } = state;\n      const time = (endTime - startTime) / 1000;\n      emit('success', { isPassing, time: time.toFixed(1) });\n    }\n    modalValue.value = isPassing;\n  },\n);\n\nfunction resetCanvas() {\n  const { canvasWidth, canvasHeight } = props;\n  const puzzleCanvas = unref(puzzleCanvasRef);\n  const pieceCanvas = unref(pieceCanvasRef);\n  if (!puzzleCanvas || !pieceCanvas) return;\n  pieceCanvas.width = canvasWidth;\n  const puzzleCanvasCtx = puzzleCanvas.getContext('2d');\n  // Canvas2D: Multiple readback operations using getImageData\n  // are faster with the willReadFrequently attribute set to true.\n  // See: https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently (anonymous)\n  const pieceCanvasCtx = pieceCanvas.getContext('2d', {\n    willReadFrequently: true,\n  });\n  if (!puzzleCanvasCtx || !pieceCanvasCtx) return;\n  puzzleCanvasCtx.clearRect(0, 0, canvasWidth, canvasHeight);\n  pieceCanvasCtx.clearRect(0, 0, canvasWidth, canvasHeight);\n}\n\nfunction initCanvas() {\n  const { canvasWidth, canvasHeight, squareLength, circleRadius, src } = props;\n  const puzzleCanvas = unref(puzzleCanvasRef);\n  const pieceCanvas = unref(pieceCanvasRef);\n  if (!puzzleCanvas || !pieceCanvas) return;\n  const puzzleCanvasCtx = puzzleCanvas.getContext('2d');\n  // Canvas2D: Multiple readback operations using getImageData\n  // are faster with the willReadFrequently attribute set to true.\n  // See: https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently (anonymous)\n  const pieceCanvasCtx = pieceCanvas.getContext('2d', {\n    willReadFrequently: true,\n  });\n  if (!puzzleCanvasCtx || !pieceCanvasCtx) return;\n  const img = new Image();\n  // 解决跨域\n  img.crossOrigin = 'Anonymous';\n  img.src = src;\n  img.addEventListener('load', () => {\n    draw(puzzleCanvasCtx, pieceCanvasCtx);\n    puzzleCanvasCtx.drawImage(img, 0, 0, canvasWidth, canvasHeight);\n    pieceCanvasCtx.drawImage(img, 0, 0, canvasWidth, canvasHeight);\n    const pieceLength = squareLength + 2 * circleRadius + 3;\n    const sx = state.pieceX;\n    const sy = state.pieceY - 2 * circleRadius - 1;\n    const imageData = pieceCanvasCtx.getImageData(\n      sx,\n      sy,\n      pieceLength,\n      pieceLength,\n    );\n    pieceCanvas.width = pieceLength;\n    pieceCanvasCtx.putImageData(imageData, 0, sy);\n    setLeft('0');\n  });\n}\n\nfunction getRandomNumberByRange(start: number, end: number) {\n  return Math.round(Math.random() * (end - start) + start);\n}\n\n// 绘制拼图\nfunction draw(ctx1: CanvasRenderingContext2D, ctx2: CanvasRenderingContext2D) {\n  const { canvasWidth, canvasHeight, squareLength, circleRadius } = props;\n  state.pieceX = getRandomNumberByRange(\n    squareLength + 2 * circleRadius,\n    canvasWidth - (squareLength + 2 * circleRadius),\n  );\n  state.pieceY = getRandomNumberByRange(\n    3 * circleRadius,\n    canvasHeight - (squareLength + 2 * circleRadius),\n  );\n  drawPiece(ctx1, state.pieceX, state.pieceY, canvasOpr.fill);\n  drawPiece(ctx2, state.pieceX, state.pieceY, canvasOpr.clip);\n}\n\n// 绘制拼图切块\nfunction drawPiece(\n  ctx: CanvasRenderingContext2D,\n  x: number,\n  y: number,\n  opr: CanvasOpr,\n) {\n  const { squareLength, circleRadius } = props;\n  ctx.beginPath();\n  ctx.moveTo(x, y);\n  ctx.arc(\n    x + squareLength / 2,\n    y - circleRadius + 2,\n    circleRadius,\n    0.72 * PI,\n    2.26 * PI,\n  );\n  ctx.lineTo(x + squareLength, y);\n  ctx.arc(\n    x + squareLength + circleRadius - 2,\n    y + squareLength / 2,\n    circleRadius,\n    1.21 * PI,\n    2.78 * PI,\n  );\n  ctx.lineTo(x + squareLength, y + squareLength);\n  ctx.lineTo(x, y + squareLength);\n  ctx.arc(\n    x + circleRadius - 2,\n    y + squareLength / 2,\n    circleRadius + 0.4,\n    2.76 * PI,\n    1.24 * PI,\n    true,\n  );\n  ctx.lineTo(x, y);\n  ctx.lineWidth = 2;\n  ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';\n  ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';\n  ctx.stroke();\n  opr === canvasOpr.clip ? ctx.clip() : ctx.fill();\n  ctx.globalCompositeOperation = 'destination-over';\n}\n\nfunction resume() {\n  state.showTip = false;\n  const basicEl = unref(slideBarRef);\n  if (!basicEl) {\n    return;\n  }\n  state.dragging = false;\n  state.isPassing = false;\n  state.pieceX = 0;\n  state.pieceY = 0;\n\n  basicEl.resume();\n  resetCanvas();\n  initCanvas();\n}\n\nonMounted(() => {\n  initCanvas();\n});\n</script>\n\n<template>\n  <div class=\"relative flex flex-col items-center\">\n    <div\n      class=\"relative flex cursor-pointer overflow-hidden border border-border shadow-md\"\n    >\n      <canvas\n        ref=\"puzzleCanvasRef\"\n        :width=\"canvasWidth\"\n        :height=\"canvasHeight\"\n        @click=\"resume\"\n      ></canvas>\n      <canvas\n        ref=\"pieceCanvasRef\"\n        :width=\"canvasWidth\"\n        :height=\"canvasHeight\"\n        :style=\"pieceStyle\"\n        class=\"absolute\"\n        @click=\"resume\"\n      ></canvas>\n      <div\n        class=\"absolute bottom-3 left-0 z-10 block h-15 w-full text-center text-xs leading-[30px] text-white\"\n      >\n        <div\n          v-if=\"state.showTip\"\n          :class=\"{\n            'bg-success/80': state.isPassing,\n            'bg-destructive/80': !state.isPassing,\n          }\"\n        >\n          {{ verifyTip }}\n        </div>\n        <div v-if=\"!state.dragging\" class=\"bg-black/30\">\n          {{ defaultTip || $t('ui.captcha.sliderTranslateDefaultTip') }}\n        </div>\n      </div>\n    </div>\n    <SliderCaptcha\n      ref=\"slideBarRef\"\n      v-model=\"modalValue\"\n      class=\"mt-5\"\n      is-slot\n      @end=\"handleDragEnd\"\n      @move=\"handleDragBarMove\"\n      @start=\"handleStart\"\n    >\n      <template v-for=\"(_, key) in $slots\" :key=\"key\" #[key]=\"slotProps\">\n        <slot :name=\"key\" v-bind=\"slotProps\"></slot>\n      </template>\n    </SliderCaptcha>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/captcha/types.ts",
    "content": "import type { CSSProperties } from 'vue';\n\nimport type { ClassType } from '@vben/types';\n\nexport interface CaptchaData {\n  /**\n   * x\n   */\n  x: number;\n  /**\n   * y\n   */\n  y: number;\n  /**\n   * 时间戳\n   */\n  t: number;\n}\nexport interface CaptchaPoint extends CaptchaData {\n  /**\n   * 数据索引\n   */\n  i: number;\n}\nexport interface PointSelectionCaptchaCardProps {\n  /**\n   * 验证码图片\n   */\n  captchaImage: string;\n  /**\n   * 验证码图片高度\n   * @default '220px'\n   */\n  height?: number | string;\n  /**\n   * 水平内边距\n   * @default '12px'\n   */\n  paddingX?: number | string;\n  /**\n   * 垂直内边距\n   * @default '16px'\n   */\n  paddingY?: number | string;\n  /**\n   * 标题\n   * @default '请按图依次点击'\n   */\n  title?: string;\n  /**\n   * 验证码图片宽度\n   * @default '300px'\n   */\n  width?: number | string;\n}\n\nexport interface PointSelectionCaptchaProps extends PointSelectionCaptchaCardProps {\n  /**\n   * 是否展示确定按钮\n   * @default false\n   */\n  showConfirm?: boolean;\n  /**\n   * 提示图片\n   * @default ''\n   */\n  hintImage?: string;\n  /**\n   * 提示文本\n   * @default ''\n   */\n  hintText?: string;\n}\n\nexport interface SliderCaptchaProps {\n  class?: ClassType;\n  /**\n   * @description 滑块的样式\n   * @default {}\n   */\n  actionStyle?: CSSProperties;\n\n  /**\n   * @description 滑块条的样式\n   * @default {}\n   */\n  barStyle?: CSSProperties;\n\n  /**\n   * @description 内容的样式\n   * @default {}\n   */\n  contentStyle?: CSSProperties;\n\n  /**\n   * @description 组件的样式\n   * @default {}\n   */\n  wrapperStyle?: CSSProperties;\n\n  /**\n   * @description 是否作为插槽使用，用于联动组件，可参考旋转校验组件\n   * @default false\n   */\n  isSlot?: boolean;\n\n  /**\n   * @description 验证成功的提示\n   * @default '验证通过'\n   */\n  successText?: string;\n\n  /**\n   * @description 提示文字\n   * @default '请按住滑块拖动'\n   */\n  text?: string;\n}\n\nexport interface SliderRotateCaptchaProps {\n  /**\n   * @description 旋转的角度\n   * @default 20\n   */\n  diffDegree?: number;\n\n  /**\n   * @description 图片的宽度\n   * @default 260\n   */\n  imageSize?: number;\n\n  /**\n   * @description 图片的样式\n   * @default {}\n   */\n  imageWrapperStyle?: CSSProperties;\n\n  /**\n   * @description 最大旋转角度\n   * @default 270\n   */\n  maxDegree?: number;\n\n  /**\n   * @description 最小旋转角度\n   * @default 90\n   */\n  minDegree?: number;\n\n  /**\n   * @description 图片的地址\n   */\n  src?: string;\n  /**\n   * @description 默认提示文本\n   */\n  defaultTip?: string;\n}\n\nexport interface SliderTranslateCaptchaProps {\n  /**\n   * @description 拼图的宽度\n   * @default 420\n   */\n  canvasWidth?: number;\n  /**\n   * @description 拼图的高度\n   * @default 280\n   */\n  canvasHeight?: number;\n  /**\n   * @description 切块上正方形的长度\n   * @default 42\n   */\n  squareLength?: number;\n  /**\n   * @description 切块上圆形的半径\n   * @default 10\n   */\n  circleRadius?: number;\n  /**\n   * @description 图片的地址\n   */\n  src?: string;\n  /**\n   * @description 允许的最大差距\n   * @default 3\n   */\n  diffDistance?: number;\n  /**\n   * @description 默认提示文本\n   */\n  defaultTip?: string;\n}\n\nexport interface CaptchaVerifyPassingData {\n  isPassing: boolean;\n  time: number | string;\n}\n\nexport interface SliderCaptchaActionType {\n  resume: () => void;\n}\n\nexport interface SliderRotateVerifyPassingData {\n  event: MouseEvent | TouchEvent;\n  moveDistance: number;\n  moveX: number;\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/col-page/col-page.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { ColPageProps } from './types';\n\nimport { computed, ref, useSlots } from 'vue';\n\nimport {\n  ResizableHandle,\n  ResizablePanel,\n  ResizablePanelGroup,\n} from '@vben-core/shadcn-ui';\n\nimport Page from '../page/page.vue';\n\ndefineOptions({\n  name: 'ColPage',\n  inheritAttrs: false,\n});\n\nconst props = withDefaults(defineProps<ColPageProps>(), {\n  leftWidth: 30,\n  rightWidth: 70,\n  resizable: true,\n});\n\nconst delegatedProps = computed(() => {\n  const { leftWidth: _, ...delegated } = props;\n  return delegated;\n});\n\nconst slots = useSlots();\n\nconst delegatedSlots = computed(() => {\n  const resultSlots: string[] = [];\n\n  for (const key of Object.keys(slots)) {\n    if (!['default', 'left'].includes(key)) {\n      resultSlots.push(key);\n    }\n  }\n  return resultSlots;\n});\n\nconst leftPanelRef = ref<InstanceType<typeof ResizablePanel>>();\n\nfunction expandLeft() {\n  leftPanelRef.value?.expand();\n}\n\nfunction collapseLeft() {\n  leftPanelRef.value?.collapse();\n}\n\ndefineExpose({\n  expandLeft,\n  collapseLeft,\n});\n</script>\n<template>\n  <Page v-bind=\"delegatedProps\">\n    <!-- 继承默认的slot -->\n    <template\n      v-for=\"slotName in delegatedSlots\"\n      :key=\"slotName\"\n      #[slotName]=\"slotProps\"\n    >\n      <slot :name=\"slotName\" v-bind=\"slotProps\"></slot>\n    </template>\n\n    <ResizablePanelGroup class=\"w-full\" direction=\"horizontal\">\n      <ResizablePanel\n        ref=\"leftPanelRef\"\n        :collapsed-size=\"leftCollapsedWidth\"\n        :collapsible=\"leftCollapsible\"\n        :default-size=\"leftWidth\"\n        :max-size=\"leftMaxWidth\"\n        :min-size=\"leftMinWidth\"\n      >\n        <template #default=\"slotProps\">\n          <slot\n            name=\"left\"\n            v-bind=\"{\n              ...slotProps,\n              expand: expandLeft,\n              collapse: collapseLeft,\n            }\"\n          ></slot>\n        </template>\n      </ResizablePanel>\n      <ResizableHandle\n        v-if=\"resizable\"\n        :style=\"{ backgroundColor: splitLine ? undefined : 'transparent' }\"\n        :with-handle=\"splitHandle\"\n      />\n      <ResizablePanel\n        :collapsed-size=\"rightCollapsedWidth\"\n        :collapsible=\"rightCollapsible\"\n        :default-size=\"rightWidth\"\n        :max-size=\"rightMaxWidth\"\n        :min-size=\"rightMinWidth\"\n      >\n        <template #default>\n          <slot></slot>\n        </template>\n      </ResizablePanel>\n    </ResizablePanelGroup>\n  </Page>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/col-page/index.ts",
    "content": "export { default as ColPage } from './col-page.vue';\nexport * from './types';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/col-page/types.ts",
    "content": "import type { PageProps } from '../page/types';\n\nexport interface ColPageProps extends PageProps {\n  /**\n   * 左侧宽度\n   * @default 30\n   */\n  leftWidth?: number;\n  leftMinWidth?: number;\n  leftMaxWidth?: number;\n  leftCollapsedWidth?: number;\n  leftCollapsible?: boolean;\n  /**\n   * 右侧宽度\n   * @default 70\n   */\n  rightWidth?: number;\n  rightMinWidth?: number;\n  rightCollapsedWidth?: number;\n  rightMaxWidth?: number;\n  rightCollapsible?: boolean;\n\n  resizable?: boolean;\n  splitLine?: boolean;\n  splitHandle?: boolean;\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/count-to/count-to.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { CountToProps } from './types';\n\nimport { computed, onMounted, ref, watch } from 'vue';\n\nimport { isString } from '@vben-core/shared/utils';\n\nimport { TransitionPresets, useTransition } from '@vueuse/core';\n\nconst props = withDefaults(defineProps<CountToProps>(), {\n  startVal: 0,\n  duration: 2000,\n  separator: ',',\n  decimal: '.',\n  decimals: 0,\n  delay: 0,\n  transition: () => TransitionPresets.easeOutExpo,\n});\n\nconst emit = defineEmits(['started', 'finished']);\n\nconst lastValue = ref(props.startVal);\n\nonMounted(() => {\n  lastValue.value = props.endVal;\n});\n\nwatch(\n  () => props.endVal,\n  (val) => {\n    lastValue.value = val;\n  },\n);\n\nconst currentValue = useTransition(lastValue, {\n  delay: computed(() => props.delay),\n  duration: computed(() => props.duration),\n  disabled: computed(() => props.disabled),\n  transition: computed(() => {\n    return isString(props.transition)\n      ? TransitionPresets[props.transition]\n      : props.transition;\n  }),\n  onStarted() {\n    emit('started');\n  },\n  onFinished() {\n    emit('finished');\n  },\n});\n\nconst numMain = computed(() => {\n  const result = currentValue.value\n    .toFixed(props.decimals)\n    .split('.')[0]\n    ?.replaceAll(/\\B(?=(\\d{3})+(?!\\d))/g, props.separator);\n  return result;\n});\n\nconst numDec = computed(() => {\n  return (\n    props.decimal + currentValue.value.toFixed(props.decimals).split('.')[1]\n  );\n});\n</script>\n<template>\n  <div class=\"count-to\" v-bind=\"$attrs\">\n    <slot name=\"prefix\">\n      <div\n        class=\"count-to-prefix\"\n        :style=\"prefixStyle\"\n        :class=\"prefixClass\"\n        v-if=\"prefix\"\n      >\n        {{ prefix }}\n      </div>\n    </slot>\n    <div class=\"count-to-main\" :class=\"mainClass\" :style=\"mainStyle\">\n      <span>{{ numMain }}</span>\n      <span\n        class=\"count-to-main-decimal\"\n        v-if=\"decimals > 0\"\n        :class=\"decimalClass\"\n        :style=\"decimalStyle\"\n      >\n        {{ numDec }}\n      </span>\n    </div>\n    <slot name=\"suffix\">\n      <div\n        class=\"count-to-suffix\"\n        :style=\"suffixStyle\"\n        :class=\"suffixClass\"\n        v-if=\"suffix\"\n      >\n        {{ suffix }}\n      </div>\n    </slot>\n  </div>\n</template>\n<style lang=\"scss\" scoped>\n.count-to {\n  display: flex;\n  align-items: baseline;\n\n  &-prefix {\n    // font-size: 1rem;\n  }\n\n  &-suffix {\n    // font-size: 1rem;\n  }\n\n  &-main {\n    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n    // font-size: 1.5rem;\n\n    &-decimal {\n      // font-size: 0.8rem;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/count-to/index.ts",
    "content": "export { default as CountTo } from './count-to.vue';\nexport * from './types';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/count-to/types.ts",
    "content": "import type { CubicBezierPoints, EasingFunction } from '@vueuse/core';\n\nimport type { StyleValue } from 'vue';\n\nimport { TransitionPresets as TransitionPresetsData } from '@vueuse/core';\n\nexport type TransitionPresets = keyof typeof TransitionPresetsData;\n\nexport const TransitionPresetsKeys = Object.keys(\n  TransitionPresetsData,\n) as TransitionPresets[];\n\nexport interface CountToProps {\n  /** 初始值 */\n  startVal?: number;\n  /** 当前值 */\n  endVal: number;\n  /** 是否禁用动画 */\n  disabled?: boolean;\n  /** 延迟动画开始的时间 */\n  delay?: number;\n  /** 持续时间  */\n  duration?: number;\n  /** 小数位数  */\n  decimals?: number;\n  /** 小数点  */\n  decimal?: string;\n  /** 分隔符  */\n  separator?: string;\n  /** 前缀  */\n  prefix?: string;\n  /** 后缀  */\n  suffix?: string;\n  /** 过渡效果  */\n  transition?: CubicBezierPoints | EasingFunction | TransitionPresets;\n  /** 整数部分的类名 */\n  mainClass?: string;\n  /** 小数部分的类名 */\n  decimalClass?: string;\n  /** 前缀部分的类名 */\n  prefixClass?: string;\n  /** 后缀部分的类名 */\n  suffixClass?: string;\n\n  /** 整数部分的样式 */\n  mainStyle?: StyleValue;\n  /** 小数部分的样式 */\n  decimalStyle?: StyleValue;\n  /** 前缀部分的样式 */\n  prefixStyle?: StyleValue;\n  /** 后缀部分的样式 */\n  suffixStyle?: StyleValue;\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/cropper/cropper.vue",
    "content": "<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, ref, watch } from 'vue';\n\n// 定义组件参数\nconst props = defineProps<{\n  /** 裁剪比例 格式如 '1:1', '16:9', '3:4' 等（非必填） */\n  aspectRatio?: string;\n  /** 容器高度（默认400） */\n  height?: number;\n  /** 图片地址 */\n  img: string;\n  /** 容器宽度（默认500） */\n  width?: number;\n}>();\n\nconst CROPPER_CONSTANTS = {\n  MIN_WIDTH: 60 as const,\n  MIN_HEIGHT: 60 as const,\n  DEFAULT_WIDTH: 500 as const,\n  DEFAULT_HEIGHT: 400 as const,\n  PADDING_RATIO: 0.1 as const,\n  MAX_PADDING: 50 as const,\n} as const;\n\ntype Point = [number, number]; // [clientX, clientY]\ntype Dimension = [number, number, number, number]; // [top, right, bottom, left]\n\n// 拖拽点类型\ntype DragAction =\n  | 'bottom'\n  | 'bottom-left'\n  | 'bottom-right'\n  | 'left'\n  | 'move'\n  | 'right'\n  | 'top'\n  | 'top-left'\n  | 'top-right';\n\n// DOM 引用\nconst containerRef = ref<HTMLDivElement | null>(null);\nconst bgImageRef = ref<HTMLImageElement | null>(null);\n// const maskRef = ref<HTMLDivElement | null>(null);\nconst maskViewRef = ref<HTMLDivElement | null>(null);\nconst cropperRef = ref<HTMLDivElement | null>(null);\n// const cropperViewRef = ref<HTMLDivElement | null>(null);\n\n// 响应式数据\nconst isCropperVisible = ref<boolean>(false);\nconst validAspectRatio = ref<null | number>(null); // 有效比例值（null表示无固定比例）\nconst containerWidth = ref<number>(\n  props.width ?? CROPPER_CONSTANTS.DEFAULT_WIDTH,\n);\nconst containerHeight = ref<number>(\n  props.height ?? CROPPER_CONSTANTS.DEFAULT_HEIGHT,\n);\n\n// 裁剪区域尺寸（top, right, bottom, left）\nconst currentDimension = ref<Dimension>([50, 50, 50, 50]);\nconst initDimension = ref<Dimension>([50, 50, 50, 50]);\n\n// 拖拽状态\nconst dragging = ref<boolean>(false);\nconst startPoint = ref<Point>([0, 0]);\nconst startDimension = ref<Dimension>([0, 0, 0, 0]);\nconst direction = ref<Dimension>([0, 0, 0, 0]);\nconst moving = ref<boolean>(false);\n\n/**\n * 计算图片的适配尺寸，保证完整显示且不超过最大宽高限制\n */\nconst calculateImageFitSize = () => {\n  if (!bgImageRef.value) return;\n\n  // 获取图片原始尺寸\n  const imgWidth = bgImageRef.value.naturalWidth;\n  const imgHeight = bgImageRef.value.naturalHeight;\n\n  if (imgWidth === 0 || imgHeight === 0) return;\n\n  // 计算缩放比例（使用传入的width/height，默认500/400）\n  const widthRatio =\n    (props.width ?? CROPPER_CONSTANTS.DEFAULT_WIDTH) / imgWidth;\n  const heightRatio =\n    (props.height ?? CROPPER_CONSTANTS.DEFAULT_HEIGHT) / imgHeight;\n  const scaleRatio = Math.min(widthRatio, heightRatio, 1); // 不放大图片，只缩小\n\n  // 计算适配后的容器尺寸\n  const fitWidth = Math.floor(imgWidth * scaleRatio);\n  const fitHeight = Math.floor(imgHeight * scaleRatio);\n\n  containerWidth.value = fitWidth;\n  containerHeight.value = fitHeight;\n\n  // 重置裁剪框初始尺寸（基于新的容器尺寸）\n  const padding = Math.min(\n    CROPPER_CONSTANTS.MAX_PADDING,\n    Math.floor(fitWidth * CROPPER_CONSTANTS.PADDING_RATIO),\n    Math.floor(fitHeight * CROPPER_CONSTANTS.PADDING_RATIO),\n  );\n\n  initDimension.value = [padding, padding, padding, padding];\n  currentDimension.value = [padding, padding, padding, padding];\n};\n\n/**\n * 验证并解析比例字符串\n * @returns {number|null} 比例值 (width/height)，解析失败返回null\n */\nconst parseAndValidateAspectRatio = (): null | number => {\n  // 如果未传入比例参数，直接返回null\n  if (!props.aspectRatio) {\n    return null;\n  }\n\n  // 验证比例格式\n  const ratioRegex = /^[1-9]\\d*:[1-9]\\d*$/;\n  if (!ratioRegex.test(props.aspectRatio)) {\n    console.warn('裁剪比例格式错误，应为 \"数字:数字\" 格式，如 \"16:9\"');\n    return null;\n  }\n\n  // 解析比例\n  const [width, height] = props.aspectRatio.split(':').map(Number);\n\n  // 验证解析结果有效性\n  if (Number.isNaN(width) || Number.isNaN(height) || !width || !height) {\n    console.warn('裁剪比例解析失败，宽高必须为正整数');\n    return null;\n  }\n\n  return width / height;\n};\n\n/**\n * 设置裁剪区域尺寸\n * @param {Dimension} dimension - [top, right, bottom, left]\n */\nconst setDimension = (dimension: Dimension) => {\n  currentDimension.value = [...dimension];\n  if (maskViewRef.value) {\n    maskViewRef.value.style.clipPath = `inset(${dimension[0]}px ${dimension[1]}px ${dimension[2]}px ${dimension[3]}px)`;\n  }\n};\n\n/**\n * 调整裁剪区域至指定比例\n */\nconst adjustCropperToAspectRatio = () => {\n  if (!cropperRef.value) return;\n\n  // 验证并解析比例\n  validAspectRatio.value = parseAndValidateAspectRatio();\n\n  // 如果无有效比例，使用初始尺寸，不强制固定比例\n  if (validAspectRatio.value === null) {\n    setDimension(initDimension.value);\n    return;\n  }\n\n  // 有有效比例，按比例调整裁剪框\n  const ratio = validAspectRatio.value;\n  const containerWidthVal = containerWidth.value;\n  const containerHeightVal = containerHeight.value;\n\n  // 根据比例计算裁剪框尺寸\n  let newHeight: number, newWidth: number;\n\n  // 先按宽度优先计算\n  newWidth = containerWidthVal;\n  newHeight = newWidth / ratio;\n\n  // 如果高度超出容器，按高度优先计算\n  if (newHeight > containerHeightVal) {\n    newHeight = containerHeightVal;\n    newWidth = newHeight * ratio;\n  }\n\n  // 居中显示\n  const leftRight = (containerWidthVal - newWidth) / 2;\n  const topBottom = (containerHeightVal - newHeight) / 2;\n\n  const newDimension: Dimension = [topBottom, leftRight, topBottom, leftRight];\n\n  setDimension(newDimension);\n};\n\n/**\n * 创建裁剪器\n */\nconst createCropper = () => {\n  // 计算图片适配尺寸\n  calculateImageFitSize();\n\n  isCropperVisible.value = true;\n  adjustCropperToAspectRatio();\n};\n\n/**\n * 处理鼠标按下事件\n * @param {MouseEvent} e - 鼠标事件\n * @param {DragAction} action - 操作类型\n */\nconst handleMouseDown = (e: MouseEvent, action: DragAction) => {\n  dragging.value = true;\n  startPoint.value = [e.clientX, e.clientY];\n  startDimension.value = [...currentDimension.value];\n  direction.value = [0, 0, 0, 0];\n  moving.value = false;\n\n  // 处理移动\n  if (action === 'move') {\n    direction.value[0] = 1;\n    direction.value[2] = -1;\n    direction.value[3] = 1;\n    direction.value[1] = -1;\n    moving.value = true;\n    return;\n  }\n\n  // 处理拖拽方向\n  switch (action) {\n    case 'bottom': {\n      direction.value[2] = -1;\n      break;\n    }\n    case 'bottom-left': {\n      direction.value[2] = -1;\n      direction.value[3] = 1;\n      break;\n    }\n    case 'bottom-right': {\n      direction.value[2] = -1;\n      direction.value[1] = -1;\n      break;\n    }\n    case 'left': {\n      direction.value[3] = 1;\n      break;\n    }\n    case 'right': {\n      direction.value[1] = -1;\n      break;\n    }\n    case 'top': {\n      direction.value[0] = 1;\n      break;\n    }\n    case 'top-left': {\n      direction.value[0] = 1;\n      direction.value[3] = 1;\n      break;\n    }\n    case 'top-right': {\n      direction.value[0] = 1;\n      direction.value[1] = -1;\n      break;\n    }\n  }\n};\n\n/**\n * 处理鼠标移动事件\n * @param {MouseEvent} e - 鼠标事件\n */\nconst handleMouseMove = (e: MouseEvent) => {\n  if (!dragging.value || !cropperRef.value) return;\n\n  const { clientX, clientY } = e;\n  const diffX = clientX - startPoint.value[0];\n  const diffY = clientY - startPoint.value[1];\n\n  // 处理移动裁剪框\n  if (moving.value) {\n    handleMoveCropBox(diffX, diffY);\n    return;\n  }\n\n  // 无有效比例\n  if (validAspectRatio.value === null) {\n    handleFreeAspectResize(diffX, diffY);\n  } else {\n    handleFixedAspectResize(diffX, diffY);\n  }\n};\n\nconst handleMoveCropBox = (diffX: number, diffY: number) => {\n  const newDimension = [...startDimension.value] as Dimension;\n\n  // 计算临时偏移后的位置\n  const tempTop = startDimension.value[0] + diffY;\n  const tempLeft = startDimension.value[3] + diffX;\n\n  // 计算裁剪框的固定尺寸\n  const cropWidth =\n    containerWidth.value - startDimension.value[3] - startDimension.value[1];\n  const cropHeight =\n    containerHeight.value - startDimension.value[0] - startDimension.value[2];\n\n  // 边界限制：确保裁剪框完全在容器内，且尺寸不变\n  // 顶部边界：top >= 0，且 bottom = 容器高度 - top - 裁剪高度 >= 0\n  newDimension[0] = Math.max(\n    0,\n    Math.min(tempTop, containerHeight.value - cropHeight),\n  );\n  // 底部边界：bottom = 容器高度 - top - 裁剪高度（由top推导，无需额外计算）\n  newDimension[2] = containerHeight.value - newDimension[0] - cropHeight;\n  // 左侧边界：left >= 0，且 right = 容器宽度 - left - 裁剪宽度 >= 0\n  newDimension[3] = Math.max(\n    0,\n    Math.min(tempLeft, containerWidth.value - cropWidth),\n  );\n  // 右侧边界：right = 容器宽度 - left - 裁剪宽度（由left推导，无需额外计算）\n  newDimension[1] = containerWidth.value - newDimension[3] - cropWidth;\n\n  // 强制保证尺寸不变（兜底）\n  const finalWidth = containerWidth.value - newDimension[3] - newDimension[1];\n  const finalHeight = containerHeight.value - newDimension[0] - newDimension[2];\n\n  if (finalWidth !== cropWidth) {\n    newDimension[1] = containerWidth.value - newDimension[3] - cropWidth;\n  }\n\n  if (finalHeight !== cropHeight) {\n    newDimension[2] = containerHeight.value - newDimension[0] - cropHeight;\n  }\n\n  // 更新裁剪区域（仅位置变化，尺寸/比例完全不变）\n  setDimension(newDimension);\n};\n\nconst handleFreeAspectResize = (diffX: number, diffY: number) => {\n  const cropperWidth = containerWidth.value;\n  const cropperHeight = containerHeight.value;\n  const currentDimensionNew: Dimension = [0, 0, 0, 0];\n\n  // 计算新的尺寸，确保不小于最小值\n  currentDimensionNew[0] = Math.min(\n    Math.max(startDimension.value[0] + direction.value[0] * diffY, 0),\n    cropperHeight - CROPPER_CONSTANTS.MIN_HEIGHT,\n  );\n\n  currentDimensionNew[1] = Math.min(\n    Math.max(startDimension.value[1] + direction.value[1] * diffX, 0),\n    cropperWidth - CROPPER_CONSTANTS.MIN_WIDTH,\n  );\n\n  currentDimensionNew[2] = Math.min(\n    Math.max(startDimension.value[2] + direction.value[2] * diffY, 0),\n    cropperHeight - CROPPER_CONSTANTS.MIN_HEIGHT,\n  );\n\n  currentDimensionNew[3] = Math.min(\n    Math.max(startDimension.value[3] + direction.value[3] * diffX, 0),\n    cropperWidth - CROPPER_CONSTANTS.MIN_WIDTH,\n  );\n\n  // 确保裁剪区域宽度和高度不小于最小值\n  const newWidth =\n    cropperWidth - currentDimensionNew[3] - currentDimensionNew[1];\n  const newHeight =\n    cropperHeight - currentDimensionNew[0] - currentDimensionNew[2];\n\n  if (newWidth < CROPPER_CONSTANTS.MIN_WIDTH) {\n    if (direction.value[3] === 1) {\n      currentDimensionNew[3] =\n        cropperWidth - currentDimensionNew[1] - CROPPER_CONSTANTS.MIN_WIDTH;\n    } else {\n      currentDimensionNew[1] =\n        cropperWidth - currentDimensionNew[3] - CROPPER_CONSTANTS.MIN_WIDTH;\n    }\n  }\n\n  if (newHeight < CROPPER_CONSTANTS.MIN_HEIGHT) {\n    if (direction.value[0] === 1) {\n      currentDimensionNew[0] =\n        cropperHeight - currentDimensionNew[2] - CROPPER_CONSTANTS.MIN_HEIGHT;\n    } else {\n      currentDimensionNew[2] =\n        cropperHeight - currentDimensionNew[0] - CROPPER_CONSTANTS.MIN_HEIGHT;\n    }\n  }\n\n  setDimension(currentDimensionNew);\n};\n\nconst handleFixedAspectResize = (diffX: number, diffY: number) => {\n  if (validAspectRatio.value === null) return;\n  const cropperWidth = containerWidth.value;\n  const cropperHeight = containerHeight.value;\n  // 有有效比例 - 固定比例裁剪\n  const ratio = validAspectRatio.value;\n  const currentWidth =\n    cropperWidth - startDimension.value[3] - startDimension.value[1];\n  const currentHeight =\n    cropperHeight - startDimension.value[0] - startDimension.value[2];\n\n  let newHeight: number, newWidth: number;\n  let widthChange = 0;\n  let heightChange = 0;\n\n  // 计算宽度/高度变化量\n  if (direction.value[3] === 1) widthChange = -diffX;\n  else if (direction.value[1] === -1) widthChange = diffX;\n\n  if (direction.value[0] === 1) heightChange = -diffY;\n  else if (direction.value[2] === -1) heightChange = diffY;\n\n  const isCornerDrag =\n    (direction.value[3] === 1 || direction.value[1] === -1) &&\n    (direction.value[0] === 1 || direction.value[2] === -1);\n\n  // 计算新尺寸\n  if (isCornerDrag) {\n    if (Math.abs(widthChange) > Math.abs(heightChange)) {\n      newWidth = Math.max(\n        CROPPER_CONSTANTS.MIN_WIDTH,\n        currentWidth + widthChange,\n      );\n      newHeight = newWidth / ratio;\n    } else {\n      newHeight = Math.max(\n        CROPPER_CONSTANTS.MIN_HEIGHT,\n        currentHeight + heightChange,\n      );\n      newWidth = newHeight * ratio;\n    }\n  } else {\n    if (direction.value[3] === 1 || direction.value[1] === -1) {\n      newWidth = Math.max(\n        CROPPER_CONSTANTS.MIN_WIDTH,\n        currentWidth + widthChange,\n      );\n      newHeight = newWidth / ratio;\n    } else {\n      newHeight = Math.max(\n        CROPPER_CONSTANTS.MIN_HEIGHT,\n        currentHeight + heightChange,\n      );\n      newWidth = newHeight * ratio;\n    }\n  }\n\n  // 限制最大尺寸\n  const maxWidth = cropperWidth;\n  const maxHeight = cropperHeight;\n\n  if (newWidth > maxWidth) {\n    newWidth = maxWidth;\n    newHeight = newWidth / ratio;\n  }\n\n  if (newHeight > maxHeight) {\n    newHeight = maxHeight;\n    newWidth = newHeight * ratio;\n  }\n\n  // 计算新的位置\n  let newLeft = startDimension.value[3];\n  let newTop = startDimension.value[0];\n  let newRight = startDimension.value[1];\n  let newBottom = startDimension.value[2];\n\n  // 根据拖拽方向调整位置\n  if (direction.value[3] === 1) {\n    newLeft = cropperWidth - newWidth - startDimension.value[1];\n  } else if (direction.value[1] === -1) {\n    newRight = cropperWidth - newWidth - startDimension.value[3];\n  } else if (!isCornerDrag) {\n    // 居中调整\n    const currentHorizontalCenter = startDimension.value[3] + currentWidth / 2;\n    newLeft = Math.max(\n      0,\n      Math.min(cropperWidth - newWidth, currentHorizontalCenter - newWidth / 2),\n    );\n    newRight = cropperWidth - newWidth - newLeft;\n  }\n\n  if (direction.value[0] === 1) {\n    newTop = cropperHeight - newHeight - startDimension.value[2];\n  } else if (direction.value[2] === -1) {\n    newBottom = cropperHeight - newHeight - startDimension.value[0];\n  } else if (!isCornerDrag) {\n    // 居中调整\n    const currentVerticalCenter = startDimension.value[0] + currentHeight / 2;\n    newTop = Math.max(\n      0,\n      Math.min(\n        cropperHeight - newHeight,\n        currentVerticalCenter - newHeight / 2,\n      ),\n    );\n    newBottom = cropperHeight - newHeight - newTop;\n  }\n\n  // 边界检查\n  newLeft = Math.max(0, newLeft);\n  newTop = Math.max(0, newTop);\n  newRight = Math.max(0, newRight);\n  newBottom = Math.max(0, newBottom);\n\n  const newDimension: Dimension = [newTop, newRight, newBottom, newLeft];\n  setDimension(newDimension);\n};\n\n/**\n * 处理鼠标抬起事件\n */\nconst handleMouseUp = () => {\n  dragging.value = false;\n  moving.value = false;\n  direction.value = [0, 0, 0, 0];\n};\n\n/**\n * 处理图片加载完成\n */\nconst handleImageLoad = () => {\n  createCropper();\n};\n\n/**\n * 裁剪图片\n * @param {'image/jpeg' | 'image/png'} format - 输出图片格式\n * @param {number} quality - 压缩质量（0-1）\n * @param {'blob' | 'base64'} outputType - 输出类型\n * @param {number} targetWidth - 目标宽度（可选，不传则为原始裁剪宽度）\n * @param {number} targetHeight - 目标高度（可选，不传则为原始裁剪高度）\n */\nconst getCropImage = async (\n  format: 'image/jpeg' | 'image/png' = 'image/png',\n  quality: number = 0.92,\n  outputType: 'base64' | 'blob' = 'blob',\n  targetWidth?: number,\n  targetHeight?: number,\n): Promise<Blob | string | undefined> => {\n  if (!props.img || !bgImageRef.value || !containerRef.value) return;\n\n  // 质量参数边界修正：强制限制在 0-1 区间，防止传入非法值报错\n  const validQuality = Math.max(0, Math.min(1, quality));\n\n  // 创建临时图片对象获取原始尺寸\n  const tempImg = new Image();\n  // 跨域图片处理：仅对非同源的网络图片设置跨域匿名\n  if (props.img.startsWith('http://') || props.img.startsWith('https://')) {\n    try {\n      const url = new URL(props.img);\n      if (url.origin !== location.origin) {\n        tempImg.crossOrigin = 'anonymous';\n      }\n    } catch {\n      // Invalid URL，跳过跨域配置，不中断执行\n    }\n  }\n\n  // 等待临时图片加载完成\n  await new Promise<void>((resolve, reject) => {\n    const timeout = setTimeout(() => {\n      tempImg.removeEventListener('load', handleLoad);\n      tempImg.removeEventListener('error', handleError);\n      reject(new Error('图片加载超时，超时时间10秒'));\n    }, 10_000);\n    const handleLoad = () => {\n      clearTimeout(timeout);\n      tempImg.removeEventListener('load', handleLoad);\n      tempImg.removeEventListener('error', handleError);\n      resolve();\n    };\n\n    const handleError = (err: ErrorEvent) => {\n      clearTimeout(timeout);\n      tempImg.removeEventListener('load', handleLoad);\n      tempImg.removeEventListener('error', handleError);\n      reject(new Error(`图片加载失败: ${err.message}`));\n    };\n\n    tempImg.addEventListener('load', handleLoad);\n    tempImg.addEventListener('error', handleError);\n    tempImg.src = props.img;\n  });\n\n  const containerRect = containerRef.value.getBoundingClientRect();\n  const imgRect = bgImageRef.value.getBoundingClientRect();\n\n  // 1. 计算图片在容器内的渲染参数\n  const containerWidth = containerRect.width;\n  const containerHeight = containerRect.height;\n  const renderedImgWidth = imgRect.width;\n  const renderedImgHeight = imgRect.height;\n  const imgOffsetX = (containerWidth - renderedImgWidth) / 2;\n  const imgOffsetY = (containerHeight - renderedImgHeight) / 2;\n\n  // 2. 计算裁剪框在容器内的实际坐标\n  const [cropTop, cropRight, cropBottom, cropLeft] = currentDimension.value;\n  const cropBoxWidth = containerWidth - cropLeft - cropRight;\n  const cropBoxHeight = containerHeight - cropTop - cropBottom;\n\n  // 3. 将裁剪框坐标转换为图片上的坐标（考虑图片偏移）\n  const cropOnImgX = cropLeft - imgOffsetX;\n  const cropOnImgY = cropTop - imgOffsetY;\n\n  // 4. 计算渲染图片到原始图片的缩放比例（保留原始像素）\n  const scaleX = tempImg.width / renderedImgWidth;\n  const scaleY = tempImg.height / renderedImgHeight;\n\n  // 5. 映射到原始图片的裁剪区域（精确到原始像素，防止越界）\n  const originalCropX = Math.max(0, Math.floor(cropOnImgX * scaleX));\n  const originalCropY = Math.max(0, Math.floor(cropOnImgY * scaleY));\n  const originalCropWidth = Math.min(\n    Math.floor(cropBoxWidth * scaleX),\n    tempImg.width - originalCropX,\n  );\n  const originalCropHeight = Math.min(\n    Math.floor(cropBoxHeight * scaleY),\n    tempImg.height - originalCropY,\n  );\n\n  // 边界校验：裁剪尺寸非法则返回\n  if (originalCropWidth <= 0 || originalCropHeight <= 0) return;\n\n  // 6. 处理高清屏适配（解决Retina屏模糊）\n  const dpr = window.devicePixelRatio || 1;\n\n  // 最终画布尺寸（优先使用传入的目标尺寸，无则用原始裁剪尺寸）\n  const finalWidth = targetWidth ? Math.max(1, targetWidth) : originalCropWidth;\n  const finalHeight = targetHeight\n    ? Math.max(1, targetHeight)\n    : originalCropHeight;\n\n  // 创建画布并获取绘制上下文\n  const canvas = document.createElement('canvas');\n  const ctx = canvas.getContext('2d');\n  if (!ctx) return;\n\n  // 画布物理尺寸（乘以设备像素比，保证高清无模糊）\n  canvas.width = finalWidth * dpr;\n  canvas.height = finalHeight * dpr;\n\n  // 画布显示尺寸（视觉尺寸，和最终展示一致）\n  canvas.style.width = `${finalWidth}px`;\n  canvas.style.height = `${finalHeight}px`;\n\n  // 缩放画布上下文，适配高清屏DPR\n  ctx.scale(dpr, dpr);\n\n  // 7. 绘制裁剪后的图片（使用原始像素绘制，保证清晰度）\n  ctx.drawImage(\n    tempImg,\n    originalCropX, // 原始图片裁剪起始X（精确像素）\n    originalCropY, // 原始图片裁剪起始Y（精确像素）\n    originalCropWidth, // 原始图片裁剪宽度（精确像素）\n    originalCropHeight, // 原始图片裁剪高度（精确像素）\n    0, // 画布绘制起始X\n    0, // 画布绘制起始Y\n    finalWidth, // 画布绘制宽度（目标尺寸）\n    finalHeight, // 画布绘制高度（目标尺寸）\n  );\n\n  try {\n    return outputType === 'base64'\n      ? canvas.toDataURL(format, validQuality)\n      : new Promise<Blob>((resolve) => {\n          canvas.toBlob(\n            (blob) => {\n              // 兜底：如果blob生成失败，返回空Blob（防止null）\n              resolve(blob || new Blob([], { type: format }));\n            },\n            format,\n            validQuality,\n          );\n        });\n  } catch (error) {\n    console.error('图片导出失败:', error);\n  }\n};\n\n// 监听比例变化，重新调整裁剪框\nwatch(() => props.aspectRatio, adjustCropperToAspectRatio);\n\n// 监听width/height变化，重新计算尺寸\nwatch([() => props.width, () => props.height], () => {\n  calculateImageFitSize();\n  adjustCropperToAspectRatio();\n});\n\n// 组件挂载时注册全局事件\nonMounted(() => {\n  document.addEventListener('mousemove', handleMouseMove);\n  document.addEventListener('mouseup', handleMouseUp);\n\n  // 如果图片已经加载完成，手动触发创建裁剪器\n  if (\n    bgImageRef.value &&\n    bgImageRef.value.complete &&\n    bgImageRef.value.naturalWidth > 0\n  ) {\n    createCropper();\n  }\n});\n\n// 组件卸载时清理\nonUnmounted(() => {\n  document.removeEventListener('mousemove', handleMouseMove);\n  document.removeEventListener('mouseup', handleMouseUp);\n});\n\ndefineExpose({ getCropImage });\n</script>\n\n<template>\n  <div\n    :style=\"{\n      width: `${width || CROPPER_CONSTANTS.DEFAULT_WIDTH}px`,\n      height: `${height || CROPPER_CONSTANTS.DEFAULT_HEIGHT}px`,\n    }\"\n    class=\"cropper-action-wrapper\"\n  >\n    <div\n      ref=\"containerRef\"\n      class=\"cropper-container\"\n      :style=\"{\n        width: `${containerWidth}px`,\n        height: `${containerHeight}px`,\n      }\"\n    >\n      <!-- 原图展示 - 自适应尺寸 -->\n      <img\n        ref=\"bgImageRef\"\n        class=\"cropper-image\"\n        :src=\"img\"\n        @load=\"handleImageLoad\"\n        :style=\"{\n          maxWidth: '100%',\n          maxHeight: '100%',\n          objectFit: 'contain',\n        }\"\n        alt=\"裁剪原图\"\n      />\n\n      <!-- 遮罩层 -->\n      <div\n        class=\"cropper-mask\"\n        :style=\"{\n          display: isCropperVisible ? 'block' : 'none',\n          width: '100%',\n          height: '100%',\n        }\"\n      >\n        <div\n          ref=\"maskViewRef\"\n          class=\"cropper-mask-view\"\n          :style=\"{\n            backgroundImage: `url(${img})`,\n            backgroundSize: 'contain',\n            backgroundPosition: 'center',\n            backgroundRepeat: 'no-repeat',\n            clipPath: `inset(${currentDimension[0]}px ${currentDimension[1]}px ${currentDimension[2]}px ${currentDimension[3]}px)`,\n            width: '100%',\n            height: '100%',\n          }\"\n        ></div>\n      </div>\n\n      <!-- 裁剪框 -->\n      <div\n        ref=\"cropperRef\"\n        class=\"cropper-box\"\n        :style=\"{\n          display: isCropperVisible ? 'block' : 'none',\n          width: '100%',\n          height: '100%',\n        }\"\n      >\n        <div\n          class=\"cropper-view\"\n          :style=\"{\n            inset: `${currentDimension[0]}px ${currentDimension[1]}px ${currentDimension[2]}px ${currentDimension[3]}px`,\n          }\"\n        >\n          <!-- 裁剪框辅助线-->\n          <span class=\"cropper-dashed-h\"></span>\n          <span class=\"cropper-dashed-v\"></span>\n\n          <!-- 裁剪框拖拽区域 -->\n          <span\n            class=\"cropper-move-area\"\n            @mousedown=\"handleMouseDown($event, 'move')\"\n          ></span>\n\n          <!-- 边框线 -->\n          <span class=\"cropper-line-e\"></span>\n          <span class=\"cropper-line-n\"></span>\n          <span class=\"cropper-line-w\"></span>\n          <span class=\"cropper-line-s\"></span>\n\n          <!-- 边角拖拽点 -->\n          <span\n            class=\"cropper-point cropper-point-ne\"\n            @mousedown=\"handleMouseDown($event, 'top-right')\"\n          >\n            <span class=\"cropper-point-inner\"></span>\n          </span>\n          <span\n            class=\"cropper-point cropper-point-nw\"\n            @mousedown=\"handleMouseDown($event, 'top-left')\"\n          >\n            <span class=\"cropper-point-inner\"></span>\n          </span>\n          <span\n            class=\"cropper-point cropper-point-sw\"\n            @mousedown=\"handleMouseDown($event, 'bottom-left')\"\n          >\n            <span class=\"cropper-point-inner\"></span>\n          </span>\n          <span\n            class=\"cropper-point cropper-point-se\"\n            @mousedown=\"handleMouseDown($event, 'bottom-right')\"\n          >\n            <span class=\"cropper-point-inner\"></span>\n          </span>\n\n          <!-- 边中点拖拽点 -->\n          <span\n            class=\"cropper-point cropper-point-e\"\n            @mousedown=\"handleMouseDown($event, 'right')\"\n          >\n            <span class=\"cropper-point-inner\"></span>\n          </span>\n          <span\n            class=\"cropper-point cropper-point-n\"\n            @mousedown=\"handleMouseDown($event, 'top')\"\n          >\n            <span class=\"cropper-point-inner\"></span>\n          </span>\n          <span\n            class=\"cropper-point cropper-point-w\"\n            @mousedown=\"handleMouseDown($event, 'left')\"\n          >\n            <span class=\"cropper-point-inner\"></span>\n          </span>\n          <span\n            class=\"cropper-point cropper-point-s\"\n            @mousedown=\"handleMouseDown($event, 'bottom')\"\n          >\n            <span class=\"cropper-point-inner\"></span>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<style scoped>\n@reference \"@vben/tailwind-config/theme\";\n\n.cropper-action-wrapper {\n  @apply box-border flex items-center justify-center;\n\n  background-color: transparent;\n\n  /* 马赛克背景 */\n  background-image:\n    linear-gradient(45deg, #ccc 25%, transparent 25%),\n    linear-gradient(-45deg, #ccc 25%, transparent 25%),\n    linear-gradient(45deg, transparent 75%, #ccc 75%),\n    linear-gradient(-45deg, transparent 75%, #ccc 75%);\n  background-position:\n    0 0,\n    0 10px,\n    10px -10px,\n    -10px 0;\n  background-size: 20px 20px;\n}\n\n.cropper-container {\n  @apply relative;\n}\n\n.cropper-image {\n  @apply block;\n}\n\n/* 遮罩层 */\n.cropper-mask {\n  @apply absolute top-0 left-0 bg-black/50;\n}\n\n.cropper-mask-view {\n  @apply absolute top-0 left-0;\n}\n\n/* 裁剪框 */\n.cropper-box {\n  @apply absolute top-0 left-0 z-10;\n}\n\n.cropper-view {\n  @apply absolute top-0 right-0 bottom-0 left-0 outline-1 outline-blue-500 select-none;\n}\n\n/* 裁剪框辅助线 */\n.cropper-dashed-h {\n  @apply absolute top-1/3 left-0 block h-1/3 w-full border-t border-b border-dashed border-gray-200/50;\n}\n\n.cropper-dashed-v {\n  @apply absolute top-0 left-1/3 block h-full w-1/3 border-r border-l border-dashed border-gray-200/50;\n}\n\n/* 裁剪框拖拽区域 */\n.cropper-move-area {\n  @apply absolute top-0 left-0 block h-full w-full cursor-move bg-white/10;\n}\n\n/* 边框拖拽线 */\n.cropper-line-e,\n.cropper-line-n,\n.cropper-line-w,\n.cropper-line-s {\n  @apply absolute block bg-blue-500/10;\n}\n\n.cropper-line-e {\n  @apply top-0 -right-0.75 h-full w-1;\n}\n\n.cropper-line-n {\n  @apply -top-0.75 left-0 h-1 w-full;\n}\n\n.cropper-line-w {\n  @apply top-0 -left-0.75 h-full w-1;\n}\n\n.cropper-line-s {\n  @apply -bottom-0.75 left-0 h-1 w-full;\n}\n\n/* 拖拽点 */\n.cropper-point {\n  @apply absolute flex h-2 w-2 items-center justify-center bg-blue-500;\n}\n\n.cropper-point-inner {\n  @apply block h-1.5 w-1.5 bg-white;\n}\n\n/* 边角拖拽点位置和光标 */\n.cropper-point-ne {\n  @apply -top-1.25 -right-1.25 cursor-ne-resize;\n}\n\n.cropper-point-nw {\n  @apply -top-1.25 -left-1.25 cursor-nw-resize;\n}\n\n.cropper-point-sw {\n  @apply -bottom-1.25 -left-1.25 cursor-sw-resize;\n}\n\n.cropper-point-se {\n  @apply -right-1.25 -bottom-1.25 cursor-se-resize;\n}\n\n/* 边中点拖拽点位置和光标 */\n.cropper-point-e {\n  @apply top-1/2 -right-1.25 -mt-1 cursor-e-resize;\n}\n\n.cropper-point-n {\n  @apply -top-1.25 left-1/2 -ml-1 cursor-n-resize;\n}\n\n.cropper-point-w {\n  @apply top-1/2 -left-1.25 -mt-1 cursor-w-resize;\n}\n\n.cropper-point-s {\n  @apply -bottom-1.25 left-1/2 -ml-1 cursor-s-resize;\n}\n</style>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/cropper/index.ts",
    "content": "export { default as VCropper } from './cropper.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/ellipsis-text/ellipsis-text.vue",
    "content": "<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue';\n\nimport {\n  computed,\n  onBeforeUnmount,\n  onMounted,\n  onUpdated,\n  ref,\n  watchEffect,\n} from 'vue';\n\nimport { VbenTooltip } from '@vben-core/shadcn-ui';\n\nimport { useElementSize } from '@vueuse/core';\n\ninterface Props {\n  /**\n   * 是否启用点击文本展开全部\n   * @default false\n   */\n  expand?: boolean;\n  /**\n   * 文本最大行数\n   * @default 1\n   */\n  line?: number;\n  /**\n   * 文本最大宽度\n   * @default '100%'\n   */\n  maxWidth?: number | string;\n  /**\n   * 提示框位置\n   * @default 'top'\n   */\n  placement?: 'bottom' | 'left' | 'right' | 'top';\n  /**\n   * 是否启用文本提示框\n   * @default true\n   */\n  tooltip?: boolean;\n  /**\n   * 是否只在文本被截断时显示提示框\n   * @default false\n   */\n  tooltipWhenEllipsis?: boolean;\n  /**\n   * 文本截断检测的像素差异阈值，越大则判断越严格\n   * @default 3\n   */\n  ellipsisThreshold?: number;\n  /**\n   * 提示框背景颜色，优先级高于 overlayStyle\n   */\n  tooltipBackgroundColor?: string;\n  /**\n   * 提示文本字体颜色，优先级高于 overlayStyle\n   */\n  tooltipColor?: string;\n  /**\n   * 提示文本字体大小，单位px，优先级高于 overlayStyle\n   */\n  tooltipFontSize?: number;\n  /**\n   * 提示框内容最大宽度，单位px，默认不设置时，提示文本内容自动与展示文本宽度保持一致\n   */\n  tooltipMaxWidth?: number;\n  /**\n   * 提示框内容区域样式\n   * @default { textAlign: 'justify' }\n   */\n  tooltipOverlayStyle?: CSSProperties;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  expand: false,\n  line: 1,\n  maxWidth: '100%',\n  placement: 'top',\n  tooltip: true,\n  tooltipWhenEllipsis: false,\n  ellipsisThreshold: 3,\n  tooltipBackgroundColor: '',\n  tooltipColor: '',\n  tooltipFontSize: 14,\n  tooltipMaxWidth: undefined,\n  tooltipOverlayStyle: () => ({ textAlign: 'justify' }),\n});\n\nconst emit = defineEmits<{ expandChange: [boolean] }>();\n\nconst textMaxWidth = computed(() => {\n  if (typeof props.maxWidth === 'number') {\n    return `${props.maxWidth}px`;\n  }\n  return props.maxWidth;\n});\nconst ellipsis = ref();\nconst isExpand = ref(false);\nconst defaultTooltipMaxWidth = ref();\nconst isEllipsis = ref(false);\n\nconst { width: eleWidth } = useElementSize(ellipsis);\n\n// 检测文本是否被截断\nconst checkEllipsis = () => {\n  if (!ellipsis.value || !props.tooltipWhenEllipsis) return;\n\n  const element = ellipsis.value;\n\n  const originalText = element.textContent || '';\n  const originalTrimmed = originalText.trim();\n\n  // 对于空文本直接返回 false\n  if (!originalTrimmed) {\n    isEllipsis.value = false;\n    return;\n  }\n\n  const widthDiff = element.scrollWidth - element.clientWidth;\n  const heightDiff = element.scrollHeight - element.clientHeight;\n\n  // 使用足够大的差异阈值确保只有真正被截断的文本才会显示 tooltip\n  isEllipsis.value =\n    props.line === 1\n      ? widthDiff > props.ellipsisThreshold\n      : heightDiff > props.ellipsisThreshold;\n};\n\n// 使用 ResizeObserver 监听尺寸变化\nlet resizeObserver: null | ResizeObserver = null;\n\nonMounted(() => {\n  if (typeof ResizeObserver !== 'undefined' && props.tooltipWhenEllipsis) {\n    resizeObserver = new ResizeObserver(() => {\n      checkEllipsis();\n    });\n\n    if (ellipsis.value) {\n      resizeObserver.observe(ellipsis.value);\n    }\n  }\n\n  // 初始检测\n  checkEllipsis();\n});\n\n// 使用onUpdated钩子检测内容变化\nonUpdated(() => {\n  if (props.tooltipWhenEllipsis) {\n    checkEllipsis();\n  }\n});\n\nonBeforeUnmount(() => {\n  if (resizeObserver) {\n    resizeObserver.disconnect();\n    resizeObserver = null;\n  }\n});\n\nwatchEffect(\n  () => {\n    if (props.tooltip && eleWidth.value) {\n      defaultTooltipMaxWidth.value =\n        props.tooltipMaxWidth ?? eleWidth.value + 24;\n    }\n  },\n  { flush: 'post' },\n);\n\nfunction onExpand() {\n  isExpand.value = !isExpand.value;\n  emit('expandChange', isExpand.value);\n  if (props.tooltipWhenEllipsis) {\n    checkEllipsis();\n  }\n}\n\nfunction handleExpand() {\n  props.expand && onExpand();\n}\n</script>\n<template>\n  <div>\n    <VbenTooltip\n      :content-style=\"{\n        ...tooltipOverlayStyle,\n        maxWidth: `${defaultTooltipMaxWidth}px`,\n        fontSize: `${tooltipFontSize}px`,\n        color: tooltipColor,\n        backgroundColor: tooltipBackgroundColor,\n      }\"\n      :disabled=\"\n        !props.tooltip || isExpand || (props.tooltipWhenEllipsis && !isEllipsis)\n      \"\n      :side=\"placement\"\n    >\n      <slot name=\"tooltip\">\n        <slot></slot>\n      </slot>\n\n      <template #trigger>\n        <div\n          ref=\"ellipsis\"\n          :class=\"{\n            'cursor-pointer!': expand,\n            ['block truncate']: line === 1,\n            [$style.ellipsisMultiLine]: line > 1,\n          }\"\n          :style=\"{\n            '-webkit-line-clamp': isExpand ? '' : line,\n            'max-width': textMaxWidth,\n          }\"\n          class=\"cursor-text overflow-hidden\"\n          @click=\"handleExpand\"\n          v-bind=\"$attrs\"\n        >\n          <slot></slot>\n        </div>\n      </template>\n    </VbenTooltip>\n  </div>\n</template>\n\n<style module>\n.ellipsisMultiLine {\n  display: -webkit-box;\n  -webkit-box-orient: vertical;\n}\n</style>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/ellipsis-text/index.ts",
    "content": "export { default as EllipsisText } from './ellipsis-text.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/icon-picker/icon-picker.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VNode } from 'vue';\n\nimport { computed, ref, useAttrs, watch, watchEffect } from 'vue';\n\nimport { usePagination } from '@vben/hooks';\nimport { EmptyIcon, Grip, listIcons } from '@vben/icons';\nimport { $t } from '@vben/locales';\n\nimport {\n  Button,\n  Input,\n  Pagination,\n  PaginationEllipsis,\n  PaginationFirst,\n  PaginationLast,\n  PaginationList,\n  PaginationListItem,\n  PaginationNext,\n  PaginationPrev,\n  VbenIcon,\n  VbenIconButton,\n  VbenPopover,\n} from '@vben-core/shadcn-ui';\nimport { isFunction } from '@vben-core/shared/utils';\n\nimport { objectOmit, refDebounced, watchDebounced } from '@vueuse/core';\n\nimport { fetchIconsData } from './icons';\n\ninterface Props {\n  pageSize?: number;\n  /** 图标集的名字 */\n  prefix?: string;\n  /** 是否自动请求API以获得图标集的数据.提供prefix时有效 */\n  autoFetchApi?: boolean;\n  /**\n   * 图标列表\n   */\n  icons?: string[];\n  /** Input组件 */\n  inputComponent?: VNode;\n  /** 图标插槽名，预览图标将被渲染到此插槽中 */\n  iconSlot?: string;\n  /** input组件的值属性名称 */\n  modelValueProp?: string;\n  /** 图标样式 */\n  iconClass?: string;\n  type?: 'icon' | 'input';\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  prefix: 'ant-design',\n  pageSize: 36,\n  icons: () => [],\n  iconSlot: 'default',\n  iconClass: 'size-4',\n  autoFetchApi: true,\n  modelValueProp: 'modelValue',\n  inputComponent: undefined,\n  type: 'input',\n});\n\nconst emit = defineEmits<{\n  change: [string];\n}>();\n\nconst attrs = useAttrs();\n\nconst modelValue = defineModel({ default: '', type: String });\n\nconst visible = ref(false);\nconst currentSelect = ref('');\nconst keyword = ref('');\nconst keywordDebounce = refDebounced(keyword, 300);\nconst innerIcons = ref<string[]>([]);\n\nwatchDebounced(\n  () => props.prefix,\n  async (prefix) => {\n    if (prefix && prefix !== 'svg' && props.autoFetchApi) {\n      innerIcons.value = await fetchIconsData(prefix);\n    }\n  },\n  { immediate: true, debounce: 500, maxWait: 1000 },\n);\n\nconst currentList = computed(() => {\n  try {\n    if (props.prefix) {\n      if (\n        props.prefix !== 'svg' &&\n        props.autoFetchApi &&\n        props.icons.length === 0\n      ) {\n        return innerIcons.value;\n      }\n      const icons = listIcons('', props.prefix);\n      if (icons.length === 0) {\n        console.warn(`No icons found for prefix: ${props.prefix}`);\n      }\n      return icons;\n    } else {\n      return props.icons;\n    }\n  } catch (error) {\n    console.error('Failed to load icons:', error);\n    return [];\n  }\n});\n\nconst showList = computed(() => {\n  return currentList.value.filter((item) =>\n    item.includes(keywordDebounce.value),\n  );\n});\n\nconst { paginationList, total, setCurrentPage, currentPage } = usePagination(\n  showList,\n  props.pageSize,\n);\n\nwatchEffect(() => {\n  currentSelect.value = modelValue.value;\n});\n\nwatch(\n  () => currentSelect.value,\n  (v) => {\n    emit('change', v);\n  },\n);\n\nconst handleClick = (icon: string) => {\n  currentSelect.value = icon;\n  modelValue.value = icon;\n  close();\n};\n\nconst handlePageChange = (page: number) => {\n  setCurrentPage(page);\n};\n\nfunction toggleOpenState() {\n  visible.value = !visible.value;\n}\n\nfunction open() {\n  visible.value = true;\n}\n\nfunction close() {\n  visible.value = false;\n}\n\nfunction onKeywordChange(v: string) {\n  keyword.value = v;\n}\n\nconst searchInputProps = computed(() => {\n  return {\n    placeholder: $t('ui.iconPicker.search'),\n    [props.modelValueProp]: keyword.value,\n    [`onUpdate:${props.modelValueProp}`]: onKeywordChange,\n    class: 'mx-2',\n  };\n});\n\nfunction updateCurrentSelect(v: string) {\n  currentSelect.value = v;\n  const eventKey = `onUpdate:${props.modelValueProp}`;\n  if (attrs[eventKey] && isFunction(attrs[eventKey])) {\n    attrs[eventKey](v);\n  }\n}\nconst getBindAttrs = computed(() => {\n  return objectOmit(attrs, [`onUpdate:${props.modelValueProp}`]);\n});\n\ndefineExpose({ toggleOpenState, open, close });\n</script>\n<template>\n  <VbenPopover\n    v-model:open=\"visible\"\n    :content-props=\"{ align: 'end', alignOffset: -11, sideOffset: 8 }\"\n    content-class=\"p-0 pt-3 w-full\"\n    trigger-class=\"w-full\"\n  >\n    <template #trigger>\n      <template v-if=\"props.type === 'input'\">\n        <component\n          v-if=\"props.inputComponent\"\n          :is=\"inputComponent\"\n          :[modelValueProp]=\"currentSelect\"\n          :placeholder=\"$t('ui.iconPicker.placeholder')\"\n          role=\"combobox\"\n          :aria-label=\"$t('ui.iconPicker.placeholder')\"\n          aria-expanded=\"visible\"\n          :[`onUpdate:${modelValueProp}`]=\"updateCurrentSelect\"\n          v-bind=\"getBindAttrs\"\n        >\n          <template #[iconSlot]>\n            <VbenIcon\n              :icon=\"currentSelect || Grip\"\n              class=\"size-4\"\n              aria-hidden=\"true\"\n            />\n          </template>\n        </component>\n        <div class=\"relative w-full\" v-else>\n          <Input\n            v-bind=\"$attrs\"\n            v-model=\"currentSelect\"\n            :placeholder=\"$t('ui.iconPicker.placeholder')\"\n            class=\"h-8 w-full pr-8\"\n            role=\"combobox\"\n            :aria-label=\"$t('ui.iconPicker.placeholder')\"\n            aria-expanded=\"visible\"\n          />\n          <VbenIcon\n            :icon=\"currentSelect || Grip\"\n            class=\"absolute top-1 right-1 size-6\"\n            aria-hidden=\"true\"\n          />\n        </div>\n      </template>\n      <VbenIcon\n        :icon=\"currentSelect || Grip\"\n        v-else\n        class=\"size-4\"\n        v-bind=\"$attrs\"\n      />\n    </template>\n    <div class=\"mb-2 flex w-full\">\n      <component\n        v-if=\"inputComponent\"\n        :is=\"inputComponent\"\n        v-bind=\"searchInputProps\"\n      />\n      <Input\n        v-else\n        class=\"mx-2 h-8 w-full\"\n        :placeholder=\"$t('ui.iconPicker.search')\"\n        v-model=\"keyword\"\n      />\n    </div>\n\n    <template v-if=\"paginationList.length > 0\">\n      <div class=\"grid max-h-90 w-full grid-cols-6 justify-items-center\">\n        <VbenIconButton\n          v-for=\"(item, index) in paginationList\"\n          :key=\"index\"\n          :tooltip=\"item\"\n          tooltip-side=\"top\"\n          @click=\"handleClick(item)\"\n        >\n          <VbenIcon\n            :class=\"{\n              'text-primary transition-all': currentSelect === item,\n            }\"\n            :icon=\"item\"\n          />\n        </VbenIconButton>\n      </div>\n      <div\n        v-if=\"total >= pageSize\"\n        class=\"flex-center flex justify-end overflow-hidden border-t py-2 pr-3\"\n      >\n        <Pagination\n          :items-per-page=\"36\"\n          :sibling-count=\"1\"\n          :total=\"total\"\n          show-edges\n          size=\"small\"\n          @update:page=\"handlePageChange\"\n        >\n          <PaginationList\n            v-slot=\"{ items }\"\n            class=\"flex w-full items-center gap-1\"\n          >\n            <PaginationFirst class=\"size-5\" />\n            <PaginationPrev class=\"size-5\" />\n            <template v-for=\"(item, index) in items\">\n              <PaginationListItem\n                v-if=\"item.type === 'page'\"\n                :key=\"index\"\n                :value=\"item.value\"\n                as-child\n              >\n                <Button\n                  :variant=\"item.value === currentPage ? 'default' : 'outline'\"\n                  class=\"size-5 p-0 text-sm\"\n                >\n                  {{ item.value }}\n                </Button>\n              </PaginationListItem>\n              <PaginationEllipsis\n                v-else\n                :key=\"item.type\"\n                :index=\"index\"\n                class=\"size-5\"\n              />\n            </template>\n            <PaginationNext class=\"size-5\" />\n            <PaginationLast class=\"size-5\" />\n          </PaginationList>\n        </Pagination>\n      </div>\n    </template>\n\n    <template v-else>\n      <div class=\"flex-col-center min-h-37.5 w-full text-muted-foreground\">\n        <EmptyIcon class=\"size-10\" />\n        <div class=\"mt-1 text-sm\">{{ $t('common.noData') }}</div>\n      </div>\n    </template>\n  </VbenPopover>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/icon-picker/icons.ts",
    "content": "import type { Recordable } from '@vben/types';\n\n/**\n * 一个缓存对象，在不刷新页面时，无需重复请求远程接口\n */\nexport const ICONS_MAP: Recordable<string[]> = {};\n\ninterface IconifyResponse {\n  prefix: string;\n  total: number;\n  title: string;\n  uncategorized?: string[];\n  categories?: Recordable<string[]>;\n  aliases?: Recordable<string>;\n}\n\nconst PENDING_REQUESTS: Recordable<Promise<string[]>> = {};\n\n/**\n * 通过Iconify接口获取图标集数据。\n * 同一时间多个图标选择器同时请求同一个图标集时，实际上只会发起一次请求（所有请求共享同一份结果）。\n * 请求结果会被缓存，刷新页面前同一个图标集不会再次请求\n * @param prefix 图标集名称\n * @returns 图标集中包含的所有图标名称\n */\nexport async function fetchIconsData(prefix: string): Promise<string[]> {\n  if (Reflect.has(ICONS_MAP, prefix) && ICONS_MAP[prefix]) {\n    return ICONS_MAP[prefix];\n  }\n  if (Reflect.has(PENDING_REQUESTS, prefix) && PENDING_REQUESTS[prefix]) {\n    return PENDING_REQUESTS[prefix];\n  }\n  PENDING_REQUESTS[prefix] = (async () => {\n    try {\n      const controller = new AbortController();\n      const timeoutId = setTimeout(() => controller.abort(), 1000 * 10);\n      const response: IconifyResponse = await fetch(\n        `https://api.iconify.design/collection?prefix=${prefix}`,\n        { signal: controller.signal },\n      ).then((res) => res.json());\n      clearTimeout(timeoutId);\n      const list = response.uncategorized || [];\n      if (response.categories) {\n        for (const category in response.categories) {\n          list.push(...(response.categories[category] || []));\n        }\n      }\n      ICONS_MAP[prefix] = list.map((v) => `${prefix}:${v}`);\n    } catch (error) {\n      console.error(`Failed to fetch icons for prefix ${prefix}:`, error);\n      return [] as string[];\n    }\n    return ICONS_MAP[prefix];\n  })();\n  return PENDING_REQUESTS[prefix];\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/icon-picker/index.ts",
    "content": "export { default as IconPicker } from './icon-picker.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/index.ts",
    "content": "export * from './api-component';\nexport * from './captcha';\nexport * from './col-page';\nexport * from './count-to';\nexport * from './cropper';\nexport * from './ellipsis-text';\nexport * from './icon-picker';\nexport * from './json-viewer';\nexport * from './loading';\nexport * from './page';\nexport * from './resize';\nexport * from './tippy';\nexport * from './tree';\nexport * from '@vben-core/form-ui';\nexport * from '@vben-core/popup-ui';\n\n// 给文档用\nexport {\n  VbenAvatar,\n  VbenButton,\n  VbenButtonGroup,\n  VbenCheckbox,\n  VbenCheckButtonGroup,\n  VbenContextMenu,\n  VbenCountToAnimator,\n  VbenFullScreen,\n  VbenInputPassword,\n  VbenLoading,\n  VbenLogo,\n  VbenPinInput,\n  VbenSelect,\n  VbenSpinner,\n} from '@vben-core/shadcn-ui';\n\nexport type { FlattenedItem } from '@vben-core/shadcn-ui';\nexport { globalShareState } from '@vben-core/shared/global-state';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/json-viewer/index.ts",
    "content": "export { default as JsonViewer } from './index.vue';\n\nexport * from './types';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/json-viewer/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { SetupContext } from 'vue';\n\nimport type { Recordable } from '@vben/types';\n\nimport type {\n  JsonViewerAction,\n  JsonViewerProps,\n  JsonViewerToggle,\n  JsonViewerValue,\n} from './types';\n\nimport { computed, useAttrs } from 'vue';\n// @ts-expect-error - vue-json-viewer does not expose compatible typings for this import path\nimport VueJsonViewer from 'vue-json-viewer';\n\nimport { $t } from '@vben/locales';\n\nimport { isBoolean } from '@vben-core/shared/utils';\n\nimport JsonBigint from 'json-bigint';\n\ndefineOptions({ name: 'JsonViewer' });\n\nconst props = withDefaults(defineProps<JsonViewerProps>(), {\n  expandDepth: 1,\n  copyable: false,\n  sort: false,\n  boxed: false,\n  theme: 'default-json-theme',\n  expanded: false,\n  previewMode: false,\n  showArrayIndex: true,\n  showDoubleQuotes: false,\n});\n\nconst emit = defineEmits<{\n  click: [event: MouseEvent];\n  copied: [event: JsonViewerAction];\n  keyClick: [key: string];\n  toggle: [param: JsonViewerToggle];\n  valueClick: [value: JsonViewerValue];\n}>();\n\nconst attrs: SetupContext['attrs'] = useAttrs();\n\nfunction handleClick(event: MouseEvent) {\n  if (\n    event.target instanceof HTMLElement &&\n    event.target.classList.contains('jv-item')\n  ) {\n    const pathNode = event.target.closest('.jv-push');\n    if (!pathNode || !pathNode.hasAttribute('path')) {\n      return;\n    }\n    const param: JsonViewerValue = {\n      el: event.target,\n      path: pathNode.getAttribute('path') || '',\n      depth: Number(pathNode.getAttribute('depth')) || 0,\n      value: event.target.textContent || undefined,\n    };\n\n    param.value = JSON.parse(param.value);\n    emit('valueClick', param);\n  }\n  emit('click', event);\n}\n\n// 支持显示 bigint 数据，如较长的订单号\nconst jsonData = computed<Record<string, any>>(() => {\n  if (typeof props.value !== 'string') {\n    return props.value || {};\n  }\n\n  try {\n    return JsonBigint({ storeAsString: true }).parse(props.value);\n  } catch (error) {\n    console.error('JSON parse error:', error);\n    return {};\n  }\n});\n\nconst bindProps = computed<Recordable<any>>(() => {\n  const copyable = {\n    copyText: $t('ui.jsonViewer.copy'),\n    copiedText: $t('ui.jsonViewer.copied'),\n    timeout: 2000,\n    ...(isBoolean(props.copyable) ? {} : props.copyable),\n  };\n\n  return {\n    ...props,\n    ...attrs,\n    value: jsonData.value,\n    onCopied: (event: JsonViewerAction) => emit('copied', event),\n    onKeyclick: (key: string) => emit('keyClick', key),\n    onClick: (event: MouseEvent) => handleClick(event),\n    copyable: props.copyable ? copyable : false,\n  };\n});\n</script>\n<template>\n  <VueJsonViewer v-bind=\"bindProps\">\n    <template #copy=\"slotProps\">\n      <slot name=\"copy\" v-bind=\"slotProps\"></slot>\n    </template>\n  </VueJsonViewer>\n</template>\n<style lang=\"scss\">\n@use './style.scss';\n</style>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/json-viewer/style.scss",
    "content": ".default-json-theme {\n  font-family: Consolas, Menlo, Courier, monospace;\n  font-size: 14px;\n  color: hsl(var(--foreground));\n  white-space: nowrap;\n  background: hsl(var(--background));\n\n  &.jv-container.boxed {\n    border: 1px solid hsl(var(--border));\n  }\n\n  .jv-ellipsis {\n    display: inline-block;\n    padding: 0 4px 2px;\n    font-size: 0.9em;\n    line-height: 0.9;\n    vertical-align: 2px;\n    color: hsl(var(--secondary-foreground));\n    cursor: pointer;\n    user-select: none;\n    background-color: hsl(var(--secondary));\n    border-radius: 3px;\n  }\n\n  .jv-button {\n    color: hsl(var(--primary));\n  }\n\n  .jv-key {\n    color: hsl(var(--heavy-foreground));\n  }\n\n  .jv-item {\n    &.jv-array {\n      color: hsl(var(--heavy-foreground));\n    }\n\n    &.jv-boolean {\n      color: hsl(var(--red-400));\n    }\n\n    &.jv-function {\n      color: hsl(var(--destructive-foreground));\n    }\n\n    &.jv-number {\n      color: hsl(var(--info-foreground));\n    }\n\n    &.jv-number-float {\n      color: hsl(var(--info-foreground));\n    }\n\n    &.jv-number-integer {\n      color: hsl(var(--info-foreground));\n    }\n\n    &.jv-object {\n      color: hsl(var(--accent-darker));\n    }\n\n    &.jv-undefined {\n      color: hsl(var(--secondary-foreground));\n    }\n\n    &.jv-string {\n      color: hsl(var(--primary));\n      overflow-wrap: break-word;\n      white-space: normal;\n    }\n  }\n\n  &.jv-container .jv-code {\n    padding: 10px;\n\n    &.boxed:not(.open) {\n      padding-bottom: 20px;\n      margin-bottom: 10px;\n    }\n\n    &.open {\n      padding-bottom: 10px;\n    }\n\n    .jv-toggle {\n      &::before {\n        padding: 0 2px;\n        border-radius: 2px;\n      }\n\n      &:hover {\n        &::before {\n          background: hsl(var(--accent-foreground));\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/json-viewer/types.ts",
    "content": "export interface JsonViewerProps {\n  /** 要展示的结构数据 */\n  value: any;\n  /** 展开深度 */\n  expandDepth?: number;\n  /** 是否可复制 */\n  copyable?: boolean;\n  /** 是否排序 */\n  sort?: boolean;\n  /** 显示边框 */\n  boxed?: boolean;\n  /** 主题 */\n  theme?: string;\n  /** 是否展开 */\n  expanded?: boolean;\n  /** 时间格式化函数 */\n  timeformat?: (time: Date | number | string) => string;\n  /** 预览模式 */\n  previewMode?: boolean;\n  /** 显示数组索引 */\n  showArrayIndex?: boolean;\n  /** 显示双引号 */\n  showDoubleQuotes?: boolean;\n}\n\nexport interface JsonViewerAction {\n  action: string;\n  text: string;\n  trigger: HTMLElement;\n}\n\nexport interface JsonViewerValue {\n  value: any;\n  path: string;\n  depth: number;\n  el: HTMLElement;\n}\n\nexport interface JsonViewerToggle {\n  /** 鼠标事件 */\n  event: MouseEvent;\n  /** 当前展开状态 */\n  open: boolean;\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/loading/directive.ts",
    "content": "import type { App, Directive, DirectiveBinding } from 'vue';\n\nimport { h, render } from 'vue';\n\nimport { VbenLoading, VbenSpinner } from '@vben-core/shadcn-ui';\nimport { isString } from '@vben-core/shared/utils';\n\nconst LOADING_INSTANCE_KEY = Symbol('loading');\nconst SPINNER_INSTANCE_KEY = Symbol('spinner');\n\nconst CLASS_NAME_RELATIVE = 'spinner-parent--relative';\n\nconst loadingDirective: Directive = {\n  mounted(el, binding) {\n    const instance = h(VbenLoading, getOptions(binding));\n    render(instance, el);\n\n    el.classList.add(CLASS_NAME_RELATIVE);\n    el[LOADING_INSTANCE_KEY] = instance;\n  },\n  unmounted(el) {\n    const instance = el[LOADING_INSTANCE_KEY];\n    el.classList.remove(CLASS_NAME_RELATIVE);\n    render(null, el);\n    instance.el.remove();\n\n    el[LOADING_INSTANCE_KEY] = null;\n  },\n\n  updated(el, binding) {\n    const instance = el[LOADING_INSTANCE_KEY];\n    const options = getOptions(binding);\n    if (options && instance?.component) {\n      try {\n        Object.keys(options).forEach((key) => {\n          instance.component.props[key] = options[key];\n        });\n        instance.component.update();\n      } catch (error) {\n        console.error(\n          'Failed to update loading component in directive:',\n          error,\n        );\n      }\n    }\n  },\n};\n\nfunction getOptions(binding: DirectiveBinding) {\n  if (binding.value === undefined) {\n    return { spinning: true };\n  } else if (typeof binding.value === 'boolean') {\n    return { spinning: binding.value };\n  } else {\n    return { ...binding.value };\n  }\n}\n\nconst spinningDirective: Directive = {\n  mounted(el, binding) {\n    const instance = h(VbenSpinner, getOptions(binding));\n    render(instance, el);\n\n    el.classList.add(CLASS_NAME_RELATIVE);\n    el[SPINNER_INSTANCE_KEY] = instance;\n  },\n  unmounted(el) {\n    const instance = el[SPINNER_INSTANCE_KEY];\n    el.classList.remove(CLASS_NAME_RELATIVE);\n    render(null, el);\n    instance.el.remove();\n\n    el[SPINNER_INSTANCE_KEY] = null;\n  },\n\n  updated(el, binding) {\n    const instance = el[SPINNER_INSTANCE_KEY];\n    const options = getOptions(binding);\n    if (options && instance?.component) {\n      try {\n        Object.keys(options).forEach((key) => {\n          instance.component.props[key] = options[key];\n        });\n        instance.component.update();\n      } catch (error) {\n        console.error(\n          'Failed to update spinner component in directive:',\n          error,\n        );\n      }\n    }\n  },\n};\n\ntype loadingDirectiveParams = {\n  /** 是否注册loading指令。如果提供一个string，则将指令注册为指定的名称 */\n  loading?: boolean | string;\n  /** 是否注册spinning指令。如果提供一个string，则将指令注册为指定的名称 */\n  spinning?: boolean | string;\n};\n\n/**\n * 注册loading指令\n * @param app\n * @param params\n */\nexport function registerLoadingDirective(\n  app: App,\n  params?: loadingDirectiveParams,\n) {\n  // 注入一个样式供指令使用，确保容器是相对定位\n  const style = document.createElement('style');\n  style.id = CLASS_NAME_RELATIVE;\n  style.innerHTML = `\n    .${CLASS_NAME_RELATIVE} {\n      position: relative !important;\n    }\n  `;\n  document.head.append(style);\n  if (params?.loading !== false) {\n    app.directive(\n      isString(params?.loading) ? params.loading : 'loading',\n      loadingDirective,\n    );\n  }\n  if (params?.spinning !== false) {\n    app.directive(\n      isString(params?.spinning) ? params.spinning : 'spinning',\n      spinningDirective,\n    );\n  }\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/loading/index.ts",
    "content": "export * from './directive';\nexport { default as Loading } from './loading.vue';\nexport { default as Spinner } from './spinner.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/loading/loading.vue",
    "content": "<script lang=\"ts\" setup>\nimport { VbenLoading } from '@vben-core/shadcn-ui';\nimport { cn } from '@vben-core/shared/utils';\n\ninterface LoadingProps {\n  class?: string;\n  /**\n   * @zh_CN 最小加载时间\n   * @en_US Minimum loading time\n   */\n  minLoadingTime?: number;\n\n  /**\n   * @zh_CN loading状态开启\n   */\n  spinning?: boolean;\n  /**\n   * @zh_CN 文字\n   */\n  text?: string;\n}\n\ndefineOptions({ name: 'Loading' });\nconst props = defineProps<LoadingProps>();\n</script>\n<template>\n  <div :class=\"cn('relative min-h-20', props.class)\">\n    <slot></slot>\n    <VbenLoading\n      :min-loading-time=\"props.minLoadingTime\"\n      :spinning=\"props.spinning\"\n      :text=\"props.text\"\n    >\n      <template v-if=\"$slots.icon\" #icon>\n        <slot name=\"icon\"></slot>\n      </template>\n    </VbenLoading>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/loading/spinner.vue",
    "content": "<script lang=\"ts\" setup>\nimport { VbenSpinner } from '@vben-core/shadcn-ui';\nimport { cn } from '@vben-core/shared/utils';\n\ninterface SpinnerProps {\n  class?: string;\n  /**\n   * @zh_CN 最小加载时间\n   * @en_US Minimum loading time\n   */\n  minLoadingTime?: number;\n  /**\n   * @zh_CN loading状态开启\n   */\n  spinning?: boolean;\n}\ndefineOptions({ name: 'Spinner' });\nconst props = defineProps<SpinnerProps>();\n</script>\n<template>\n  <div :class=\"cn('relative min-h-20', props.class)\">\n    <slot></slot>\n    <VbenSpinner\n      :min-loading-time=\"props.minLoadingTime\"\n      :spinning=\"props.spinning\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/page/__tests__/page.test.ts",
    "content": "import { mount } from '@vue/test-utils';\n\nimport { describe, expect, it } from 'vitest';\n\nimport { Page } from '..';\n\ndescribe('page.vue', () => {\n  it('renders title when passed', () => {\n    const wrapper = mount(Page, {\n      props: {\n        title: 'Test Title',\n      },\n    });\n\n    expect(wrapper.text()).toContain('Test Title');\n  });\n\n  it('renders description when passed', () => {\n    const wrapper = mount(Page, {\n      props: {\n        description: 'Test Description',\n      },\n    });\n\n    expect(wrapper.text()).toContain('Test Description');\n  });\n\n  it('renders default slot content', () => {\n    const wrapper = mount(Page, {\n      slots: {\n        default: '<p>Default Slot Content</p>',\n      },\n    });\n\n    expect(wrapper.html()).toContain('<p>Default Slot Content</p>');\n  });\n\n  it('renders footer slot when showFooter is true', () => {\n    const wrapper = mount(Page, {\n      props: {\n        showFooter: true,\n      },\n      slots: {\n        footer: '<p>Footer Slot Content</p>',\n      },\n    });\n\n    expect(wrapper.html()).toContain('<p>Footer Slot Content</p>');\n  });\n\n  it('applies the custom contentClass', () => {\n    const wrapper = mount(Page, {\n      props: {\n        contentClass: 'custom-class',\n      },\n    });\n\n    const contentDiv = wrapper.find('.p-4');\n    expect(contentDiv.classes()).toContain('custom-class');\n  });\n\n  it('does not render title slot if title prop is provided', () => {\n    const wrapper = mount(Page, {\n      props: {\n        title: 'Test Title',\n      },\n      slots: {\n        title: '<p>Title Slot Content</p>',\n      },\n    });\n\n    expect(wrapper.text()).toContain('Title Slot Content');\n    expect(wrapper.html()).not.toContain('Test Title');\n  });\n\n  it('does not render description slot if description prop is provided', () => {\n    const wrapper = mount(Page, {\n      props: {\n        description: 'Test Description',\n      },\n      slots: {\n        description: '<p>Description Slot Content</p>',\n      },\n    });\n\n    expect(wrapper.text()).toContain('Description Slot Content');\n    expect(wrapper.html()).not.toContain('Test Description');\n  });\n});\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/page/index.ts",
    "content": "export { default as Page } from './page.vue';\nexport * from './types';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/page/page.vue",
    "content": "<script setup lang=\"ts\">\nimport type { StyleValue } from 'vue';\n\nimport type { PageProps } from './types';\n\nimport { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';\n\nimport { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT } from '@vben-core/shared/constants';\nimport { cn } from '@vben-core/shared/utils';\n\ndefineOptions({\n  name: 'Page',\n});\n\nconst { autoContentHeight = false, heightOffset = 0 } =\n  defineProps<PageProps>();\n\nconst headerHeight = ref(0);\nconst footerHeight = ref(0);\nconst shouldAutoHeight = ref(false);\n\nconst headerRef = useTemplateRef<HTMLDivElement>('headerRef');\nconst footerRef = useTemplateRef<HTMLDivElement>('footerRef');\n\nconst contentStyle = computed<StyleValue>(() => {\n  if (autoContentHeight) {\n    return {\n      height: `calc(var(${CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT}) - ${headerHeight.value}px - ${footerHeight.value}px - ${typeof heightOffset === 'number' ? `${heightOffset}px` : heightOffset})`,\n      overflowY: shouldAutoHeight.value ? 'auto' : 'unset',\n    };\n  }\n  return {};\n});\n\nasync function calcContentHeight() {\n  if (!autoContentHeight) {\n    return;\n  }\n  await nextTick();\n  headerHeight.value = headerRef.value?.offsetHeight || 0;\n  footerHeight.value = footerRef.value?.offsetHeight || 0;\n  setTimeout(() => {\n    shouldAutoHeight.value = true;\n  }, 30);\n}\n\nonMounted(() => {\n  calcContentHeight();\n});\n</script>\n\n<template>\n  <div class=\"relative flex min-h-full flex-col\">\n    <div\n      v-if=\"\n        description ||\n        $slots.description ||\n        title ||\n        $slots.title ||\n        $slots.extra\n      \"\n      ref=\"headerRef\"\n      :class=\"\n        cn(\n          'relative flex items-end border-b border-border bg-card px-6 py-4',\n          headerClass,\n        )\n      \"\n    >\n      <div class=\"flex-auto\">\n        <slot name=\"title\">\n          <div v-if=\"title\" class=\"mb-2 flex text-lg font-semibold\">\n            {{ title }}\n          </div>\n        </slot>\n\n        <slot name=\"description\">\n          <p v-if=\"description\" class=\"text-muted-foreground\">\n            {{ description }}\n          </p>\n        </slot>\n      </div>\n\n      <div v-if=\"$slots.extra\">\n        <slot name=\"extra\"></slot>\n      </div>\n    </div>\n\n    <div :class=\"cn('h-full p-4', contentClass)\" :style=\"contentStyle\">\n      <slot></slot>\n    </div>\n    <div\n      v-if=\"$slots.footer\"\n      ref=\"footerRef\"\n      :class=\"cn('align-center flex bg-card px-6 py-4', footerClass)\"\n    >\n      <slot name=\"footer\"></slot>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/page/types.ts",
    "content": "export interface PageProps {\n  title?: string;\n  description?: string;\n  contentClass?: string;\n  /**\n   * 根据content可见高度自适应\n   */\n  autoContentHeight?: boolean;\n  headerClass?: string;\n  footerClass?: string;\n  /**\n   * Custom height offset value (in pixels) to adjust content area sizing\n   * when used with autoContentHeight\n   * @default 0\n   */\n  heightOffset?: number;\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/resize/index.ts",
    "content": "export { default as VResize } from './resize.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/resize/resize.vue",
    "content": "<script lang=\"ts\" setup>\n/**\n * This components is refactored from vue-drag-resize: https://github.com/kirillmurashov/vue-drag-resize\n */\n\nimport {\n  computed,\n  getCurrentInstance,\n  nextTick,\n  onBeforeUnmount,\n  onMounted,\n  ref,\n  toRefs,\n  watch,\n} from 'vue';\n\nconst props = defineProps({\n  stickSize: {\n    type: Number,\n    default: 8,\n  },\n  parentScaleX: {\n    type: Number,\n    default: 1,\n  },\n  parentScaleY: {\n    type: Number,\n    default: 1,\n  },\n  isActive: {\n    type: Boolean,\n    default: false,\n  },\n  preventActiveBehavior: {\n    type: Boolean,\n    default: false,\n  },\n  isDraggable: {\n    type: Boolean,\n    default: true,\n  },\n  isResizable: {\n    type: Boolean,\n    default: true,\n  },\n  aspectRatio: {\n    type: Boolean,\n    default: false,\n  },\n  parentLimitation: {\n    type: Boolean,\n    default: false,\n  },\n  snapToGrid: {\n    type: Boolean,\n    default: false,\n  },\n  gridX: {\n    type: Number,\n    default: 50,\n    validator(val: number) {\n      return val >= 0;\n    },\n  },\n  gridY: {\n    type: Number,\n    default: 50,\n    validator(val: number) {\n      return val >= 0;\n    },\n  },\n  parentW: {\n    type: Number,\n    default: 0,\n    validator(val: number) {\n      return val >= 0;\n    },\n  },\n  parentH: {\n    type: Number,\n    default: 0,\n    validator(val: number) {\n      return val >= 0;\n    },\n  },\n  w: {\n    type: [String, Number],\n    default: 200,\n    validator(val: number) {\n      return typeof val === 'string' ? val === 'auto' : val >= 0;\n    },\n  },\n  h: {\n    type: [String, Number],\n    default: 200,\n    validator(val: number) {\n      return typeof val === 'string' ? val === 'auto' : val >= 0;\n    },\n  },\n  minw: {\n    type: Number,\n    default: 50,\n    validator(val: number) {\n      return val >= 0;\n    },\n  },\n  minh: {\n    type: Number,\n    default: 50,\n    validator(val: number) {\n      return val >= 0;\n    },\n  },\n  x: {\n    type: Number,\n    default: 0,\n    validator(val: number) {\n      return typeof val === 'number';\n    },\n  },\n  y: {\n    type: Number,\n    default: 0,\n    validator(val: number) {\n      return typeof val === 'number';\n    },\n  },\n  z: {\n    type: [String, Number],\n    default: 'auto',\n    validator(val: number) {\n      return typeof val === 'string' ? val === 'auto' : val >= 0;\n    },\n  },\n  dragHandle: {\n    type: String,\n    default: null,\n  },\n  dragCancel: {\n    type: String,\n    default: null,\n  },\n  sticks: {\n    type: Array<'bl' | 'bm' | 'br' | 'ml' | 'mr' | 'tl' | 'tm' | 'tr'>,\n    default() {\n      return ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'];\n    },\n  },\n  axis: {\n    type: String,\n    default: 'both',\n    validator(val: string) {\n      return ['both', 'none', 'x', 'y'].includes(val);\n    },\n  },\n  contentClass: {\n    type: String,\n    required: false,\n    default: '',\n  },\n});\n\nconst emit = defineEmits([\n  'clicked',\n  'dragging',\n  'dragstop',\n  'resizing',\n  'resizestop',\n  'activated',\n  'deactivated',\n]);\n\nconst styleMapping = {\n  y: {\n    t: 'top',\n    m: 'marginTop',\n    b: 'bottom',\n  },\n  x: {\n    l: 'left',\n    m: 'marginLeft',\n    r: 'right',\n  },\n};\n\nfunction addEvents(events: Map<string, (...args: any[]) => void>) {\n  events.forEach((cb, eventName) => {\n    document.documentElement.addEventListener(eventName, cb);\n  });\n}\n\nfunction removeEvents(events: Map<string, (...args: any[]) => void>) {\n  events.forEach((cb, eventName) => {\n    document.documentElement.removeEventListener(eventName, cb);\n  });\n}\n\nconst {\n  stickSize,\n  parentScaleX,\n  parentScaleY,\n  isActive,\n  preventActiveBehavior,\n  isDraggable,\n  isResizable,\n  aspectRatio,\n  parentLimitation,\n  snapToGrid,\n  gridX,\n  gridY,\n  parentW,\n  parentH,\n  w,\n  h,\n  minw,\n  minh,\n  x,\n  y,\n  z,\n  dragHandle,\n  dragCancel,\n  sticks,\n  axis,\n  contentClass,\n} = toRefs(props);\n\n// states\nconst active = ref(false);\nconst zIndex = ref<null | number>(null);\nconst parentWidth = ref<null | number>(null);\nconst parentHeight = ref<null | number>(null);\nconst left = ref<null | number>(null);\nconst top = ref<null | number>(null);\nconst right = ref<null | number>(null);\nconst bottom = ref<null | number>(null);\n\nconst aspectFactor = ref<null | number>(null);\n\n// state end\n\nconst stickDrag = ref(false);\nconst bodyDrag = ref(false);\nconst dimensionsBeforeMove = ref({\n  pointerX: 0,\n  pointerY: 0,\n  x: 0,\n  y: 0,\n  w: 0,\n  h: 0,\n  top: 0,\n  right: 0,\n  bottom: 0,\n  left: 0,\n  width: 0,\n  height: 0,\n});\nconst limits = ref({\n  left: { min: null as null | number, max: null as null | number },\n  right: { min: null as null | number, max: null as null | number },\n  top: { min: null as null | number, max: null as null | number },\n  bottom: { min: null as null | number, max: null as null | number },\n});\nconst currentStick = ref<null | string>(null);\n\nconst parentElement = ref<HTMLElement | null>(null);\n\nfunction getPointerPosition(\n  ev: Partial<{\n    pageX: number;\n    pageY: number;\n    touches: ArrayLike<{ pageX: number; pageY: number }>;\n  }>,\n): null | { pointerX: number; pointerY: number } {\n  const touch = ev.touches?.[0];\n  const pointerX = ev.pageX ?? touch?.pageX;\n  const pointerY = ev.pageY ?? touch?.pageY;\n\n  if (\n    pointerX === null ||\n    pointerX === undefined ||\n    pointerY === null ||\n    pointerY === undefined\n  ) {\n    return null;\n  }\n\n  return { pointerX, pointerY };\n}\n\nconst width = computed(() => {\n  const currentParentWidth = parentWidth.value;\n  const currentLeft = left.value;\n  const currentRight = right.value;\n\n  if (\n    currentParentWidth === null ||\n    currentLeft === null ||\n    currentRight === null\n  ) {\n    return 0;\n  }\n\n  return currentParentWidth - currentLeft - currentRight;\n});\n\nconst height = computed(() => {\n  const currentParentHeight = parentHeight.value;\n  const currentTop = top.value;\n  const currentBottom = bottom.value;\n\n  if (\n    currentParentHeight === null ||\n    currentTop === null ||\n    currentBottom === null\n  ) {\n    return 0;\n  }\n\n  return currentParentHeight - currentTop - currentBottom;\n});\n\nconst rect = computed(() => ({\n  left: Math.round(left.value ?? 0),\n  top: Math.round(top.value ?? 0),\n  width: Math.round(width.value),\n  height: Math.round(height.value),\n}));\n\nconst saveDimensionsBeforeMove = ({\n  pointerX,\n  pointerY,\n}: {\n  pointerX: number;\n  pointerY: number;\n}) => {\n  dimensionsBeforeMove.value.pointerX = pointerX;\n  dimensionsBeforeMove.value.pointerY = pointerY;\n\n  dimensionsBeforeMove.value.left = left.value as number;\n  dimensionsBeforeMove.value.right = right.value as number;\n  dimensionsBeforeMove.value.top = top.value as number;\n  dimensionsBeforeMove.value.bottom = bottom.value as number;\n\n  dimensionsBeforeMove.value.width = width.value as number;\n  dimensionsBeforeMove.value.height = height.value as number;\n\n  aspectFactor.value = width.value / height.value;\n};\n\nconst sideCorrectionByLimit = (\n  limit: { max: number; min: number },\n  current: number,\n) => {\n  let value = current;\n\n  if (limit.min !== null && current < limit.min) {\n    value = limit.min;\n  } else if (limit.max !== null && limit.max < current) {\n    value = limit.max;\n  }\n\n  return value;\n};\n\nconst rectCorrectionByLimit = (rect: {\n  newBottom: number;\n  newLeft: number;\n  newRight: number;\n  newTop: number;\n}) => {\n  // const { limits } = this;\n  let { newRight, newLeft, newBottom, newTop } = rect;\n\n  type RectRange = {\n    max: number;\n    min: number;\n  };\n\n  newLeft = sideCorrectionByLimit(limits.value.left as RectRange, newLeft);\n  newRight = sideCorrectionByLimit(limits.value.right as RectRange, newRight);\n  newTop = sideCorrectionByLimit(limits.value.top as RectRange, newTop);\n  newBottom = sideCorrectionByLimit(\n    limits.value.bottom as RectRange,\n    newBottom,\n  );\n\n  return {\n    newLeft,\n    newRight,\n    newTop,\n    newBottom,\n  };\n};\n\nconst rectCorrectionByAspectRatio = (rect: {\n  newBottom: number;\n  newLeft: number;\n  newRight: number;\n  newTop: number;\n}) => {\n  let { newLeft, newRight, newTop, newBottom } = rect;\n  const currentParentWidth = parentWidth.value;\n  const currentParentHeight = parentHeight.value;\n  const stick = currentStick.value;\n  const factor = aspectFactor.value;\n\n  if (\n    currentParentWidth === null ||\n    currentParentHeight === null ||\n    !stick ||\n    factor === null\n  ) {\n    return { newLeft, newRight, newTop, newBottom };\n  }\n\n  let newWidth = currentParentWidth - newLeft - newRight;\n  let newHeight = currentParentHeight - newTop - newBottom;\n\n  if (stick[1] === 'm') {\n    const deltaHeight = newHeight - dimensionsBeforeMove.value.height;\n\n    newLeft -= (deltaHeight * factor) / 2;\n    newRight -= (deltaHeight * factor) / 2;\n  } else if (stick[0] === 'm') {\n    const deltaWidth = newWidth - dimensionsBeforeMove.value.width;\n\n    newTop -= deltaWidth / factor / 2;\n    newBottom -= deltaWidth / factor / 2;\n  } else if (newWidth / newHeight > factor) {\n    newWidth = factor * newHeight;\n\n    if (stick[1] === 'l') {\n      newLeft = currentParentWidth - newRight - newWidth;\n    } else {\n      newRight = currentParentWidth - newLeft - newWidth;\n    }\n  } else {\n    newHeight = newWidth / factor;\n\n    if (stick[0] === 't') {\n      newTop = currentParentHeight - newBottom - newHeight;\n    } else {\n      newBottom = currentParentHeight - newTop - newHeight;\n    }\n  }\n\n  return { newLeft, newRight, newTop, newBottom };\n};\n\nconst stickMove = (delta: { x: number; y: number }) => {\n  const stick = currentStick.value;\n\n  if (!stick) {\n    return;\n  }\n\n  let newTop = dimensionsBeforeMove.value.top;\n  let newBottom = dimensionsBeforeMove.value.bottom;\n  let newLeft = dimensionsBeforeMove.value.left;\n  let newRight = dimensionsBeforeMove.value.right;\n  switch (stick[0]) {\n    case 'b': {\n      newBottom = dimensionsBeforeMove.value.bottom + delta.y;\n\n      if (snapToGrid.value) {\n        newBottom =\n          (parentHeight.value as number) -\n          Math.round(\n            ((parentHeight.value as number) - newBottom) / gridY.value,\n          ) *\n            gridY.value;\n      }\n\n      break;\n    }\n\n    case 't': {\n      newTop = dimensionsBeforeMove.value.top - delta.y;\n\n      if (snapToGrid.value) {\n        newTop = Math.round(newTop / gridY.value) * gridY.value;\n      }\n\n      break;\n    }\n    default: {\n      break;\n    }\n  }\n\n  switch (stick[1]) {\n    case 'l': {\n      newLeft = dimensionsBeforeMove.value.left - delta.x;\n\n      if (snapToGrid.value) {\n        newLeft = Math.round(newLeft / gridX.value) * gridX.value;\n      }\n\n      break;\n    }\n\n    case 'r': {\n      newRight = dimensionsBeforeMove.value.right + delta.x;\n\n      if (snapToGrid.value) {\n        newRight =\n          (parentWidth.value as number) -\n          Math.round(((parentWidth.value as number) - newRight) / gridX.value) *\n            gridX.value;\n      }\n\n      break;\n    }\n    default: {\n      break;\n    }\n  }\n\n  ({ newLeft, newRight, newTop, newBottom } = rectCorrectionByLimit({\n    newLeft,\n    newRight,\n    newTop,\n    newBottom,\n  }));\n\n  if (aspectRatio.value) {\n    ({ newLeft, newRight, newTop, newBottom } = rectCorrectionByAspectRatio({\n      newLeft,\n      newRight,\n      newTop,\n      newBottom,\n    }));\n  }\n\n  left.value = newLeft;\n  right.value = newRight;\n  top.value = newTop;\n  bottom.value = newBottom;\n\n  emit('resizing', rect.value);\n};\n\nconst stickUp = () => {\n  stickDrag.value = false;\n  // dimensionsBeforeMove.value = {\n  //   pointerX: 0,\n  //   pointerY: 0,\n  //   x: 0,\n  //   y: 0,\n  //   w: 0,\n  //   h: 0,\n  // };\n\n  Object.assign(dimensionsBeforeMove.value, {\n    pointerX: 0,\n    pointerY: 0,\n    x: 0,\n    y: 0,\n    w: 0,\n    h: 0,\n  });\n\n  limits.value = {\n    left: { min: null, max: null },\n    right: { min: null, max: null },\n    top: { min: null, max: null },\n    bottom: { min: null, max: null },\n  };\n\n  emit('resizing', rect.value);\n  emit('resizestop', rect.value);\n};\n\nconst calcDragLimitation = () => {\n  return {\n    left: { min: 0, max: (parentWidth.value as number) - width.value },\n    right: { min: 0, max: (parentWidth.value as number) - width.value },\n    top: { min: 0, max: (parentHeight.value as number) - height.value },\n    bottom: { min: 0, max: (parentHeight.value as number) - height.value },\n  };\n};\n\nconst calcResizeLimits = () => {\n  const parentLim = parentLimitation.value ? 0 : null;\n  const currentAspectFactor = aspectFactor.value;\n  const currentLeft = left.value;\n  const currentRight = right.value;\n  const currentTop = top.value;\n  const currentBottom = bottom.value;\n  const stick = currentStick.value;\n\n  if (\n    currentLeft === null ||\n    currentRight === null ||\n    currentTop === null ||\n    currentBottom === null\n  ) {\n    return {\n      left: { min: parentLim, max: parentLim },\n      right: { min: parentLim, max: parentLim },\n      top: { min: parentLim, max: parentLim },\n      bottom: { min: parentLim, max: parentLim },\n    };\n  }\n\n  let minWidth = minw.value;\n  let minHeight = minh.value;\n\n  if (aspectRatio.value && currentAspectFactor) {\n    if (minWidth / minHeight > currentAspectFactor) {\n      minHeight = minWidth / currentAspectFactor;\n    } else {\n      minWidth = currentAspectFactor * minHeight;\n    }\n  }\n\n  const limits = {\n    left: {\n      min: parentLim,\n      max: currentLeft + (width.value - minWidth),\n    },\n    right: {\n      min: parentLim,\n      max: currentRight + (width.value - minWidth),\n    },\n    top: {\n      min: parentLim,\n      max: currentTop + (height.value - minHeight),\n    },\n    bottom: {\n      min: parentLim,\n      max: currentBottom + (height.value - minHeight),\n    },\n  };\n\n  if (aspectRatio.value && currentAspectFactor) {\n    const aspectLimits = {\n      left: {\n        min:\n          currentLeft -\n          Math.min(currentTop, currentBottom) * currentAspectFactor * 2,\n        max:\n          currentLeft +\n          ((height.value - minHeight) / 2) * currentAspectFactor * 2,\n      },\n      right: {\n        min:\n          currentRight -\n          Math.min(currentTop, currentBottom) * currentAspectFactor * 2,\n        max:\n          currentRight +\n          ((height.value - minHeight) / 2) * currentAspectFactor * 2,\n      },\n      top: {\n        min:\n          currentTop -\n          (Math.min(currentLeft, currentRight) / currentAspectFactor) * 2,\n        max:\n          currentTop + ((width.value - minWidth) / 2 / currentAspectFactor) * 2,\n      },\n      bottom: {\n        min:\n          currentBottom -\n          (Math.min(currentLeft, currentRight) / currentAspectFactor) * 2,\n        max:\n          currentBottom +\n          ((width.value - minWidth) / 2 / currentAspectFactor) * 2,\n      },\n    };\n\n    if (stick?.[0] === 'm') {\n      limits.left = {\n        min: Math.max(\n          limits.left.min ?? aspectLimits.left.min,\n          aspectLimits.left.min,\n        ),\n        max: Math.min(limits.left.max, aspectLimits.left.max),\n      };\n      limits.right = {\n        min: Math.max(\n          limits.right.min ?? aspectLimits.right.min,\n          aspectLimits.right.min,\n        ),\n        max: Math.min(limits.right.max, aspectLimits.right.max),\n      };\n    } else if (stick?.[1] === 'm') {\n      limits.top = {\n        min: Math.max(\n          limits.top.min ?? aspectLimits.top.min,\n          aspectLimits.top.min,\n        ),\n        max: Math.min(limits.top.max, aspectLimits.top.max),\n      };\n      limits.bottom = {\n        min: Math.max(\n          limits.bottom.min ?? aspectLimits.bottom.min,\n          aspectLimits.bottom.min,\n        ),\n        max: Math.min(limits.bottom.max, aspectLimits.bottom.max),\n      };\n    }\n  }\n\n  return limits;\n};\n\nconst positionStyle = computed(() => ({\n  top: `${top.value ?? 0}px`,\n  left: `${left.value ?? 0}px`,\n  zIndex: zIndex.value ?? 'auto',\n}));\n\nconst sizeStyle = computed(() => ({\n  width: w.value === 'auto' ? 'auto' : `${width.value}px`,\n  height: h.value === 'auto' ? 'auto' : `${height.value}px`,\n}));\n\nconst stickStyles = computed(() => (stick: string) => {\n  const stickStyle = {\n    width: `${stickSize.value / parentScaleX.value}px`,\n    height: `${stickSize.value / parentScaleY.value}px`,\n    [styleMapping.y[stick[0] as 'b' | 'm' | 't'] as 'height' | 'width']:\n      `${stickSize.value / parentScaleX.value / -2}px`,\n    [styleMapping.x[stick[1] as 'l' | 'm' | 'r'] as 'height' | 'width']:\n      `${stickSize.value / parentScaleX.value / -2}px`,\n  };\n  return stickStyle;\n});\n\nconst bodyMove = (delta: { x: number; y: number }) => {\n  let newTop = dimensionsBeforeMove.value.top - delta.y;\n  let newBottom = dimensionsBeforeMove.value.bottom + delta.y;\n  let newLeft = dimensionsBeforeMove.value.left - delta.x;\n  let newRight = dimensionsBeforeMove.value.right + delta.x;\n\n  if (snapToGrid.value) {\n    let alignTop = true;\n    let alignLeft = true;\n\n    let diffT = newTop - Math.floor(newTop / gridY.value) * gridY.value;\n    let diffB =\n      (parentHeight.value as number) -\n      newBottom -\n      Math.floor(((parentHeight.value as number) - newBottom) / gridY.value) *\n        gridY.value;\n    let diffL = newLeft - Math.floor(newLeft / gridX.value) * gridX.value;\n    let diffR =\n      (parentWidth.value as number) -\n      newRight -\n      Math.floor(((parentWidth.value as number) - newRight) / gridX.value) *\n        gridX.value;\n\n    if (diffT > gridY.value / 2) {\n      diffT -= gridY.value;\n    }\n    if (diffB > gridY.value / 2) {\n      diffB -= gridY.value;\n    }\n    if (diffL > gridX.value / 2) {\n      diffL -= gridX.value;\n    }\n    if (diffR > gridX.value / 2) {\n      diffR -= gridX.value;\n    }\n\n    if (Math.abs(diffB) < Math.abs(diffT)) {\n      alignTop = false;\n    }\n    if (Math.abs(diffR) < Math.abs(diffL)) {\n      alignLeft = false;\n    }\n\n    newTop -= alignTop ? diffT : diffB;\n    newBottom = (parentHeight.value as number) - height.value - newTop;\n    newLeft -= alignLeft ? diffL : diffR;\n    newRight = (parentWidth.value as number) - width.value - newLeft;\n  }\n\n  ({\n    newLeft: left.value,\n    newRight: right.value,\n    newTop: top.value,\n    newBottom: bottom.value,\n  } = rectCorrectionByLimit({ newLeft, newRight, newTop, newBottom }));\n\n  emit('dragging', rect.value);\n};\n\nconst bodyUp = () => {\n  bodyDrag.value = false;\n  emit('dragging', rect.value);\n  emit('dragstop', rect.value);\n\n  // dimensionsBeforeMove.value = { pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0 };\n  Object.assign(dimensionsBeforeMove.value, {\n    pointerX: 0,\n    pointerY: 0,\n    x: 0,\n    y: 0,\n    w: 0,\n    h: 0,\n  });\n\n  limits.value = {\n    left: { min: null, max: null },\n    right: { min: null, max: null },\n    top: { min: null, max: null },\n    bottom: { min: null, max: null },\n  };\n};\n\nconst stickDown = (\n  stick: string,\n  ev: { pageX: any; pageY: any; touches?: any },\n  force = false,\n) => {\n  if ((!isResizable.value || !active.value) && !force) {\n    return;\n  }\n\n  const pointerPosition = getPointerPosition(ev);\n\n  if (!pointerPosition) {\n    return;\n  }\n\n  stickDrag.value = true;\n\n  saveDimensionsBeforeMove(pointerPosition);\n\n  currentStick.value = stick;\n\n  limits.value = calcResizeLimits();\n};\n\nconst move = (ev: MouseEvent & TouchEvent) => {\n  if (!stickDrag.value && !bodyDrag.value) {\n    return;\n  }\n\n  ev.stopPropagation();\n\n  const pointerPosition = getPointerPosition(ev);\n\n  if (!pointerPosition) {\n    return;\n  }\n\n  const delta = {\n    x:\n      (dimensionsBeforeMove.value.pointerX - pointerPosition.pointerX) /\n      parentScaleX.value,\n    y:\n      (dimensionsBeforeMove.value.pointerY - pointerPosition.pointerY) /\n      parentScaleY.value,\n  };\n\n  if (stickDrag.value) {\n    stickMove(delta);\n  }\n\n  if (bodyDrag.value) {\n    switch (axis.value) {\n      case 'none': {\n        return;\n      }\n      case 'x': {\n        delta.y = 0;\n\n        break;\n      }\n      case 'y': {\n        delta.x = 0;\n\n        break;\n      }\n      // No default\n    }\n    bodyMove(delta);\n  }\n};\n\nconst up = () => {\n  if (stickDrag.value) {\n    stickUp();\n  } else if (bodyDrag.value) {\n    bodyUp();\n  }\n};\n\nconst deselect = () => {\n  if (preventActiveBehavior.value) {\n    return;\n  }\n  active.value = false;\n};\n\nconst domEvents = ref(\n  new Map([\n    ['mousedown', deselect],\n    ['mouseleave', up],\n    ['mousemove', move],\n    ['mouseup', up],\n    ['touchcancel', up],\n    ['touchend', up],\n    ['touchmove', move],\n    ['touchstart', up],\n  ]),\n);\n\nconst container = ref<HTMLDivElement>();\n\nonMounted(() => {\n  const currentInstance = getCurrentInstance();\n  const $el = currentInstance?.vnode.el as HTMLElement;\n\n  parentElement.value = $el?.parentNode as HTMLElement;\n  parentWidth.value = parentW.value ?? parentElement.value?.clientWidth;\n  parentHeight.value = parentH.value ?? parentElement.value?.clientHeight;\n\n  left.value = x.value;\n  top.value = y.value;\n  const containerElement = container.value;\n  const contentWidth =\n    w.value === 'auto'\n      ? (containerElement?.scrollWidth ?? 0)\n      : (w.value as number);\n  const contentHeight =\n    h.value === 'auto'\n      ? (containerElement?.scrollHeight ?? 0)\n      : (h.value as number);\n  right.value = (parentWidth.value ?? 0) - contentWidth - (left.value ?? 0);\n  bottom.value = (parentHeight.value ?? 0) - contentHeight - (top.value ?? 0);\n\n  addEvents(domEvents.value);\n\n  if (dragHandle.value) {\n    [...($el?.querySelectorAll(dragHandle.value) || [])].forEach(\n      (dragHandle) => {\n        (dragHandle as HTMLElement).dataset.dragHandle = String(\n          currentInstance?.uid,\n        );\n      },\n    );\n  }\n\n  if (dragCancel.value) {\n    [...($el?.querySelectorAll(dragCancel.value) || [])].forEach(\n      (cancelHandle) => {\n        (cancelHandle as HTMLElement).dataset.dragCancel = String(\n          currentInstance?.uid,\n        );\n      },\n    );\n  }\n});\nonBeforeUnmount(() => {\n  removeEvents(domEvents.value);\n});\n\nconst bodyDown = (ev: MouseEvent & TouchEvent) => {\n  const { target, button } = ev;\n  const targetElement = target instanceof HTMLElement ? target : null;\n  const uid = getCurrentInstance()?.uid.toString();\n\n  if (!preventActiveBehavior.value) {\n    active.value = true;\n  }\n\n  if (button && button !== 0) {\n    return;\n  }\n\n  emit('clicked', ev);\n\n  if (!active.value) {\n    return;\n  }\n\n  if (\n    dragHandle.value &&\n    targetElement &&\n    targetElement.dataset.dragHandle !== uid\n  ) {\n    return;\n  }\n\n  if (dragCancel.value && targetElement?.dataset.dragCancel === uid) {\n    return;\n  }\n\n  if (ev.stopPropagation !== undefined) {\n    ev.stopPropagation();\n  }\n\n  if (ev.preventDefault !== undefined) {\n    ev.preventDefault();\n  }\n\n  const pointerPosition = getPointerPosition(ev);\n\n  if (!pointerPosition) {\n    return;\n  }\n\n  if (isDraggable.value) {\n    bodyDrag.value = true;\n  }\n\n  saveDimensionsBeforeMove(pointerPosition);\n\n  if (parentLimitation.value) {\n    limits.value = calcDragLimitation();\n  }\n};\n\nwatch(\n  () => active.value,\n  (isActive) => {\n    if (isActive) {\n      emit('activated');\n    } else {\n      emit('deactivated');\n    }\n  },\n);\n\nwatch(\n  () => isActive.value,\n  (val) => {\n    active.value = val;\n  },\n  { immediate: true },\n);\n\nwatch(\n  () => z.value,\n  (val) => {\n    if (typeof val === 'number' && val >= 0) {\n      zIndex.value = val;\n    } else if (val === 'auto') {\n      zIndex.value = null;\n    }\n  },\n  { immediate: true },\n);\n\nwatch(\n  () => x.value,\n  (newVal, oldVal) => {\n    const currentLeft = left.value;\n    const currentTop = top.value;\n\n    if (\n      stickDrag.value ||\n      bodyDrag.value ||\n      currentLeft === null ||\n      currentTop === null ||\n      newVal === currentLeft\n    ) {\n      return;\n    }\n\n    const delta = oldVal - newVal;\n\n    bodyDown({ pageX: currentLeft, pageY: currentTop } as MouseEvent &\n      TouchEvent);\n    bodyMove({ x: delta, y: 0 });\n\n    nextTick(() => {\n      bodyUp();\n    });\n  },\n);\n\nwatch(\n  () => y.value,\n  (newVal, oldVal) => {\n    const currentLeft = left.value;\n    const currentTop = top.value;\n\n    if (\n      stickDrag.value ||\n      bodyDrag.value ||\n      currentLeft === null ||\n      currentTop === null ||\n      newVal === currentTop\n    ) {\n      return;\n    }\n\n    const delta = oldVal - newVal;\n\n    bodyDown({ pageX: currentLeft, pageY: currentTop } as MouseEvent &\n      TouchEvent);\n    bodyMove({ x: 0, y: delta });\n\n    nextTick(() => {\n      bodyUp();\n    });\n  },\n);\n\nwatch(\n  () => w.value,\n  (newVal, oldVal) => {\n    const currentRight = right.value;\n    const currentTop = top.value;\n\n    if (stickDrag.value || bodyDrag.value || newVal === width.value) {\n      return;\n    }\n\n    if (currentRight === null || currentTop === null) {\n      return;\n    }\n\n    const stick = 'mr';\n    const delta = (oldVal as number) - (newVal as number);\n\n    stickDown(\n      stick,\n      { pageX: currentRight, pageY: currentTop + height.value / 2 },\n      true,\n    );\n    stickMove({ x: delta, y: 0 });\n\n    nextTick(() => {\n      stickUp();\n    });\n  },\n);\n\nwatch(\n  () => h.value,\n  (newVal, oldVal) => {\n    const currentLeft = left.value;\n    const currentBottom = bottom.value;\n\n    if (stickDrag.value || bodyDrag.value || newVal === height.value) {\n      return;\n    }\n\n    if (currentLeft === null || currentBottom === null) {\n      return;\n    }\n\n    const stick = 'bm';\n    const delta = (oldVal as number) - (newVal as number);\n\n    stickDown(\n      stick,\n      { pageX: currentLeft + width.value / 2, pageY: currentBottom },\n      true,\n    );\n    stickMove({ x: 0, y: delta });\n\n    nextTick(() => {\n      stickUp();\n    });\n  },\n);\n\nwatch(\n  () => parentW.value,\n  (val) => {\n    right.value = val - width.value - (left.value ?? 0);\n    parentWidth.value = val;\n  },\n);\n\nwatch(\n  () => parentH.value,\n  (val) => {\n    bottom.value = val - height.value - (top.value ?? 0);\n    parentHeight.value = val;\n  },\n);\n</script>\n\n<template>\n  <div\n    :class=\"`${active || isActive ? 'active' : 'inactive'} ${contentClass ? contentClass : ''}`\"\n    :style=\"positionStyle\"\n    class=\"resize\"\n    @mousedown=\"bodyDown($event as TouchEvent & MouseEvent)\"\n    @touchend=\"up\"\n    @touchstart=\"bodyDown($event as TouchEvent & MouseEvent)\"\n  >\n    <div ref=\"container\" :style=\"sizeStyle\" class=\"content-container\">\n      <slot></slot>\n    </div>\n    <div\n      v-for=\"(stick, index) of sticks\"\n      :key=\"index\"\n      :class=\"[`resize-stick-${stick}`, isResizable ? '' : 'not-resizable']\"\n      :style=\"stickStyles(stick)\"\n      class=\"resize-stick\"\n      @mousedown.stop.prevent=\"\n        stickDown(stick, $event as TouchEvent & MouseEvent)\n      \"\n      @touchstart.stop.prevent=\"\n        stickDown(stick, $event as TouchEvent & MouseEvent)\n      \"\n    ></div>\n  </div>\n</template>\n\n<style lang=\"css\" scoped>\n.resize {\n  position: absolute;\n  box-sizing: border-box;\n}\n\n.resize.active::before {\n  position: absolute;\n  top: 0;\n  left: 0;\n  box-sizing: border-box;\n  width: 100%;\n  height: 100%;\n  outline: 1px dashed #d6d6d6;\n  content: '';\n}\n\n.resize-stick {\n  position: absolute;\n  box-sizing: border-box;\n  font-size: 1px;\n  background: #fff;\n  border: 1px solid #6c6c6c;\n  box-shadow: 0 0 2px #bbb;\n}\n\n.inactive .resize-stick {\n  display: none;\n}\n\n.resize-stick-tl,\n.resize-stick-br {\n  cursor: nwse-resize;\n}\n\n.resize-stick-tm,\n.resize-stick-bm {\n  left: 50%;\n  cursor: ns-resize;\n}\n\n.resize-stick-tr,\n.resize-stick-bl {\n  cursor: nesw-resize;\n}\n\n.resize-stick-ml,\n.resize-stick-mr {\n  top: 50%;\n  cursor: ew-resize;\n}\n\n.resize-stick.not-resizable {\n  display: none;\n}\n\n.content-container {\n  position: relative;\n  display: block;\n}\n</style>\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/tippy/directive.ts",
    "content": "import type { ComputedRef, Directive } from 'vue';\n\nimport { useTippy } from 'vue-tippy';\n\nexport default function useTippyDirective(isDark: ComputedRef<boolean>) {\n  const directive: Directive = {\n    mounted(el, binding, vnode) {\n      const opts =\n        typeof binding.value === 'string'\n          ? { content: binding.value }\n          : binding.value || {};\n\n      const modifiers = Object.keys(binding.modifiers || {});\n      const placement = modifiers.find((modifier) => modifier !== 'arrow');\n      const withArrow = modifiers.includes('arrow');\n\n      if (placement) {\n        opts.placement = opts.placement || placement;\n      }\n\n      if (withArrow) {\n        opts.arrow = opts.arrow === undefined ? true : opts.arrow;\n      }\n\n      if (vnode.props && vnode.props.onTippyShow) {\n        opts.onShow = function (...args: any[]) {\n          return vnode.props?.onTippyShow(...args);\n        };\n      }\n\n      if (vnode.props && vnode.props.onTippyShown) {\n        opts.onShown = function (...args: any[]) {\n          return vnode.props?.onTippyShown(...args);\n        };\n      }\n\n      if (vnode.props && vnode.props.onTippyHidden) {\n        opts.onHidden = function (...args: any[]) {\n          return vnode.props?.onTippyHidden(...args);\n        };\n      }\n\n      if (vnode.props && vnode.props.onTippyHide) {\n        opts.onHide = function (...args: any[]) {\n          return vnode.props?.onTippyHide(...args);\n        };\n      }\n\n      if (vnode.props && vnode.props.onTippyMount) {\n        opts.onMount = function (...args: any[]) {\n          return vnode.props?.onTippyMount(...args);\n        };\n      }\n\n      if (el.getAttribute('title') && !opts.content) {\n        opts.content = el.getAttribute('title');\n        el.removeAttribute('title');\n      }\n\n      if (el.getAttribute('content') && !opts.content) {\n        opts.content = el.getAttribute('content');\n      }\n\n      useTippy(el, opts);\n    },\n    unmounted(el) {\n      if (el.$tippy) {\n        el.$tippy.destroy();\n      } else if (el._tippy) {\n        el._tippy.destroy();\n      }\n    },\n\n    updated(el, binding) {\n      const opts =\n        typeof binding.value === 'string'\n          ? { content: binding.value, theme: isDark.value ? '' : 'light' }\n          : Object.assign(\n              { theme: isDark.value ? '' : 'light' },\n              binding.value,\n            );\n\n      if (el.getAttribute('title') && !opts.content) {\n        opts.content = el.getAttribute('title');\n        el.removeAttribute('title');\n      }\n\n      if (el.getAttribute('content') && !opts.content) {\n        opts.content = el.getAttribute('content');\n      }\n\n      if (el.$tippy) {\n        el.$tippy.setProps(opts || {});\n      } else if (el._tippy) {\n        el._tippy.setProps(opts || {});\n      }\n    },\n  };\n  return directive;\n}\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/tippy/index.ts",
    "content": "import type { DefaultProps, Props } from 'tippy.js';\n\nimport type { App, SetupContext } from 'vue';\n\nimport { h, watchEffect } from 'vue';\nimport { setDefaultProps, Tippy as TippyComponent } from 'vue-tippy';\n\nimport { usePreferences } from '@vben-core/preferences';\n\nimport useTippyDirective from './directive';\n\nimport 'tippy.js/dist/tippy.css';\nimport 'tippy.js/dist/backdrop.css';\nimport 'tippy.js/themes/light.css';\nimport 'tippy.js/animations/scale.css';\nimport 'tippy.js/animations/shift-toward.css';\nimport 'tippy.js/animations/shift-away.css';\nimport 'tippy.js/animations/perspective.css';\n\nconst { isDark } = usePreferences();\nexport type TippyProps = Partial<\n  Props & {\n    animation?:\n      | 'fade'\n      | 'perspective'\n      | 'scale'\n      | 'shift-away'\n      | 'shift-toward'\n      | boolean;\n    theme?: 'auto' | 'dark' | 'light';\n  }\n>;\n\nexport function initTippy(app: App<Element>, options?: DefaultProps) {\n  setDefaultProps({\n    allowHTML: true,\n    delay: [500, 200],\n    theme: isDark.value ? '' : 'light',\n    ...options,\n  });\n  if (!options || !Reflect.has(options, 'theme') || options.theme === 'auto') {\n    watchEffect(() => {\n      setDefaultProps({ theme: isDark.value ? '' : 'light' });\n    });\n  }\n\n  app.directive('tippy', useTippyDirective(isDark));\n}\n\nexport const Tippy = (props: any, { attrs, slots }: SetupContext) => {\n  let theme: string = (attrs.theme as string) ?? 'auto';\n  if (theme === 'auto') {\n    theme = isDark.value ? '' : 'light';\n  }\n  if (theme === 'dark') {\n    theme = '';\n  }\n  return h(\n    TippyComponent,\n    {\n      ...props,\n      ...attrs,\n      theme,\n    },\n    slots,\n  );\n};\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/tree/index.ts",
    "content": "export { default as Tree } from './tree.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/components/tree/tree.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TreeProps } from '@vben-core/shadcn-ui';\n\nimport { Inbox } from '@vben/icons';\nimport { $t } from '@vben/locales';\n\nimport { treePropsDefaults, VbenTree } from '@vben-core/shadcn-ui';\n\nconst props = withDefaults(defineProps<TreeProps>(), treePropsDefaults());\n</script>\n\n<template>\n  <VbenTree v-if=\"props.treeData?.length > 0\" v-bind=\"props\">\n    <template v-for=\"(_, key) in $slots\" :key=\"key\" #[key]=\"slotProps\">\n      <slot :name=\"key\" v-bind=\"slotProps\"> </slot>\n    </template>\n  </VbenTree>\n  <div\n    v-else\n    class=\"flex-col-center cursor-pointer rounded-lg border p-10 text-sm font-medium text-muted-foreground\"\n  >\n    <Inbox class=\"size-10\" />\n    <div class=\"mt-1\">{{ $t('common.noData') }}</div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/index.ts",
    "content": "export * from './components';\nexport * from './ui';\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/about/about.ts",
    "content": "import type { Component } from 'vue';\n\ninterface AboutProps {\n  description?: string;\n  name?: string;\n  title?: string;\n}\n\ninterface DescriptionItem {\n  content: Component | string;\n  title: string;\n}\n\nexport type { AboutProps, DescriptionItem };\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/about/about.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AboutProps, DescriptionItem } from './about';\n\nimport { h } from 'vue';\n\nimport {\n  VBEN_DOC_URL,\n  VBEN_GITHUB_URL,\n  VBEN_PREVIEW_URL,\n} from '@vben/constants';\n\nimport { VbenRenderContent } from '@vben-core/shadcn-ui';\n\nimport { Page } from '../../components';\n\ninterface Props extends AboutProps {}\n\ndefineOptions({\n  name: 'AboutUI',\n});\n\nwithDefaults(defineProps<Props>(), {\n  description:\n    '是一个现代化开箱即用的中后台解决方案，采用最新的技术栈，包括 Vue 3.0、Vite、TailwindCSS 和 TypeScript 等前沿技术，代码规范严谨，提供丰富的配置选项，旨在为中大型项目的开发提供现成的开箱即用解决方案及丰富的示例，同时，它也是学习和深入前端技术的一个极佳示例。',\n  name: 'Vben Admin',\n  title: '关于项目',\n});\n\ndeclare global {\n  const __VBEN_ADMIN_METADATA__: {\n    authorEmail: string;\n    authorName: string;\n    authorUrl: string;\n    buildTime: string;\n    dependencies: Record<string, string>;\n    description: string;\n    devDependencies: Record<string, string>;\n    homepage: string;\n    license: string;\n    repositoryUrl: string;\n    version: string;\n  };\n}\n\nconst renderLink = (href: string, text: string) =>\n  h(\n    'a',\n    { href, target: '_blank', class: 'vben-link' },\n    { default: () => text },\n  );\n\nconst {\n  authorEmail,\n  authorName,\n  authorUrl,\n  buildTime,\n  dependencies = {},\n  devDependencies = {},\n  homepage,\n  license,\n  version,\n  // vite inject-metadata 插件注入的全局变量\n} = __VBEN_ADMIN_METADATA__ || {};\n\nconst vbenDescriptionItems: DescriptionItem[] = [\n  {\n    content: version,\n    title: '版本号',\n  },\n  {\n    content: license,\n    title: '开源许可协议',\n  },\n  {\n    content: buildTime,\n    title: '最后构建时间',\n  },\n  {\n    content: renderLink(homepage, '点击查看'),\n    title: '主页',\n  },\n  {\n    content: renderLink(VBEN_DOC_URL, '点击查看'),\n    title: '文档地址',\n  },\n  {\n    content: renderLink(VBEN_PREVIEW_URL, '点击查看'),\n    title: '预览地址',\n  },\n  {\n    content: renderLink(VBEN_GITHUB_URL, '点击查看'),\n    title: 'Github',\n  },\n  {\n    content: h('div', [\n      renderLink(authorUrl, `${authorName}  `),\n      renderLink(`mailto:${authorEmail}`, authorEmail),\n    ]),\n    title: '作者',\n  },\n];\n\nconst dependenciesItems = Object.keys(dependencies).map((key) => ({\n  content: dependencies[key],\n  title: key,\n}));\n\nconst devDependenciesItems = Object.keys(devDependencies).map((key) => ({\n  content: devDependencies[key],\n  title: key,\n}));\n</script>\n\n<template>\n  <Page :title=\"title\">\n    <template #description>\n      <p class=\"mt-3 text-sm/6 text-foreground\">\n        <a :href=\"VBEN_GITHUB_URL\" class=\"vben-link\" target=\"_blank\">\n          {{ name }}\n        </a>\n        {{ description }}\n      </p>\n    </template>\n    <div class=\"card-box p-5\">\n      <div>\n        <h5 class=\"text-lg text-foreground\">基本信息</h5>\n      </div>\n      <div class=\"mt-4\">\n        <dl class=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4\">\n          <template v-for=\"item in vbenDescriptionItems\" :key=\"item.title\">\n            <div class=\"border-t border-border px-4 py-6 sm:col-span-1 sm:px-0\">\n              <dt class=\"text-sm/6 font-medium text-foreground\">\n                {{ item.title }}\n              </dt>\n              <dd class=\"mt-1 text-sm/6 text-foreground sm:mt-2\">\n                <VbenRenderContent :content=\"item.content\" />\n              </dd>\n            </div>\n          </template>\n        </dl>\n      </div>\n    </div>\n\n    <div class=\"card-box mt-6 p-5\">\n      <div>\n        <h5 class=\"text-lg text-foreground\">生产环境依赖</h5>\n      </div>\n      <div class=\"mt-4\">\n        <dl class=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4\">\n          <template v-for=\"item in dependenciesItems\" :key=\"item.title\">\n            <div class=\"border-t border-border px-4 py-3 sm:col-span-1 sm:px-0\">\n              <dt class=\"text-sm text-foreground\">\n                {{ item.title }}\n              </dt>\n              <dd class=\"mt-1 text-sm text-foreground/80 sm:mt-2\">\n                <VbenRenderContent :content=\"item.content\" />\n              </dd>\n            </div>\n          </template>\n        </dl>\n      </div>\n    </div>\n    <div class=\"card-box mt-6 p-5\">\n      <div>\n        <h5 class=\"text-lg text-foreground\">开发环境依赖</h5>\n      </div>\n      <div class=\"mt-4\">\n        <dl class=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4\">\n          <template v-for=\"item in devDependenciesItems\" :key=\"item.title\">\n            <div class=\"border-t border-border px-4 py-3 sm:col-span-1 sm:px-0\">\n              <dt class=\"text-sm text-foreground\">\n                {{ item.title }}\n              </dt>\n              <dd class=\"mt-1 text-sm text-foreground/80 sm:mt-2\">\n                <VbenRenderContent :content=\"item.content\" />\n              </dd>\n            </div>\n          </template>\n        </dl>\n      </div>\n    </div>\n  </Page>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/about/index.ts",
    "content": "export { default as About } from './about.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/auth-title.vue",
    "content": "<template>\n  <div class=\"mb-7 sm:mx-auto sm:w-full sm:max-w-md\">\n    <h2\n      class=\"mb-3 text-3xl/9 font-bold tracking-tight text-foreground lg:text-4xl\"\n    >\n      <slot></slot>\n    </h2>\n\n    <p class=\"lg:text-md text-sm text-muted-foreground\">\n      <slot name=\"desc\"></slot>\n    </p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/code-login.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Recordable } from '@vben/types';\n\nimport type { VbenFormSchema } from '@vben-core/form-ui';\n\nimport { computed, reactive } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { $t } from '@vben/locales';\n\nimport { useVbenForm } from '@vben-core/form-ui';\nimport { VbenButton } from '@vben-core/shadcn-ui';\n\nimport Title from './auth-title.vue';\n\ninterface Props {\n  formSchema: VbenFormSchema[];\n  /**\n   * @zh_CN 是否处于加载处理状态\n   */\n  loading?: boolean;\n  /**\n   * @zh_CN 登录路径\n   */\n  loginPath?: string;\n  /**\n   * @zh_CN 标题\n   */\n  title?: string;\n  /**\n   * @zh_CN 描述\n   */\n  subTitle?: string;\n  /**\n   * @zh_CN 按钮文本\n   */\n  submitButtonText?: string;\n  /**\n   * @zh_CN 是否显示返回按钮\n   */\n  showBack?: boolean;\n}\n\ndefineOptions({\n  name: 'AuthenticationCodeLogin',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  loading: false,\n  showBack: true,\n  loginPath: '/auth/login',\n  submitButtonText: '',\n  subTitle: '',\n  title: '',\n});\n\nconst emit = defineEmits<{\n  submit: [Recordable<any>];\n}>();\n\nconst router = useRouter();\n\nconst [Form, formApi] = useVbenForm(\n  reactive({\n    commonConfig: {\n      hideLabel: true,\n      hideRequiredMark: true,\n    },\n    schema: computed(() => props.formSchema),\n    showDefaultActions: false,\n  }),\n);\n\nasync function handleSubmit() {\n  const { valid } = await formApi.validate();\n  const values = await formApi.getValues();\n  if (valid) {\n    emit('submit', values);\n  }\n}\n\nfunction goToLogin() {\n  router.push(props.loginPath);\n}\n\ndefineExpose({\n  getFormApi: () => formApi,\n});\n</script>\n\n<template>\n  <div>\n    <Title>\n      <slot name=\"title\">\n        {{ title || $t('authentication.welcomeBack') }} 📲\n      </slot>\n      <template #desc>\n        <span class=\"text-muted-foreground\">\n          <slot name=\"subTitle\">\n            {{ subTitle || $t('authentication.codeSubtitle') }}\n          </slot>\n        </span>\n      </template>\n    </Title>\n    <Form />\n    <VbenButton\n      :class=\"{\n        'cursor-wait': loading,\n      }\"\n      :loading=\"loading\"\n      class=\"w-full\"\n      @click=\"handleSubmit\"\n    >\n      <slot name=\"submitButtonText\">\n        {{ submitButtonText || $t('common.login') }}\n      </slot>\n    </VbenButton>\n    <VbenButton\n      v-if=\"showBack\"\n      class=\"mt-4 w-full\"\n      variant=\"outline\"\n      @click=\"goToLogin()\"\n    >\n      {{ $t('common.back') }}\n    </VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/dingding-login.vue",
    "content": "<script setup lang=\"ts\">\nimport { useRoute } from 'vue-router';\n\nimport { SvgDingDingIcon } from '@vben/icons';\nimport { $t } from '@vben/locales';\n\nimport { alert, useVbenModal } from '@vben-core/popup-ui';\nimport { VbenIconButton } from '@vben-core/shadcn-ui';\nimport { loadScript } from '@vben-core/shared/utils';\n\ninterface Props {\n  clientId: string;\n  corpId: string;\n  // 登录回调地址\n  redirectUri?: string;\n  // 是否内嵌二维码登录\n  isQrCode?: boolean;\n}\n\nconst props = defineProps<Props>();\n\nconst route = useRoute();\n\nconst [Modal, modalApi] = useVbenModal({\n  header: false,\n  footer: false,\n  fullscreenButton: false,\n  class: 'size-75.5 dingding-qrcode-login-modal',\n  onOpened() {\n    handleQrCodeLogin();\n  },\n});\n\nconst getRedirectUri = () => {\n  const { redirectUri } = props;\n  if (redirectUri) {\n    return redirectUri;\n  }\n  return window.location.origin + route.fullPath;\n};\n\n/**\n * 内嵌二维码登录\n */\nconst handleQrCodeLogin = async () => {\n  const { clientId, corpId } = props;\n  if (!(window as any).DTFrameLogin) {\n    // 二维码登录 加载资源\n    await loadScript(\n      'https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js',\n    );\n  }\n  (window as any).DTFrameLogin(\n    {\n      id: 'dingding_qrcode_login_element',\n      width: 300,\n      height: 300,\n    },\n    {\n      // 注意：redirect_uri 需为完整URL，扫码后钉钉会带code跳转到这里\n      redirect_uri: encodeURIComponent(getRedirectUri()),\n      client_id: clientId,\n      scope: 'openid corpid',\n      response_type: 'code',\n      state: '1',\n      prompt: 'consent',\n      corpId,\n    },\n    (loginResult: any) => {\n      const { redirectUrl } = loginResult;\n      // 这里可以直接进行重定向\n      window.location.href = redirectUrl;\n    },\n    (errorMsg: string) => {\n      // 这里一般需要展示登录失败的具体原因\n      alert(`Login Error: ${errorMsg}`);\n    },\n  );\n};\n\nconst handleLogin = () => {\n  const { clientId, corpId, isQrCode } = props;\n  if (isQrCode) {\n    // 内嵌二维码登录\n    modalApi.open();\n  } else {\n    window.location.href = `https://login.dingtalk.com/oauth2/auth?redirect_uri=${encodeURIComponent(getRedirectUri())}&response_type=code&client_id=${clientId}&scope=openid&corpid=${corpId}&prompt=consent`;\n  }\n};\n</script>\n\n<template>\n  <div>\n    <VbenIconButton\n      @click=\"handleLogin\"\n      :tooltip=\"$t('authentication.dingdingLogin')\"\n      tooltip-side=\"top\"\n    >\n      <SvgDingDingIcon />\n    </VbenIconButton>\n    <Modal>\n      <div id=\"dingding_qrcode_login_element\"></div>\n    </Modal>\n  </div>\n</template>\n\n<style>\n.dingding-qrcode-login-modal {\n  .relative {\n    padding: 0 !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/forget-password.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VbenFormSchema } from '@vben-core/form-ui';\n\nimport { computed, reactive } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { $t } from '@vben/locales';\n\nimport { useVbenForm } from '@vben-core/form-ui';\nimport { VbenButton } from '@vben-core/shadcn-ui';\n\nimport Title from './auth-title.vue';\n\ninterface Props {\n  formSchema: VbenFormSchema[];\n  /**\n   * @zh_CN 是否处于加载处理状态\n   */\n  loading?: boolean;\n  /**\n   * @zh_CN 登录路径\n   */\n  loginPath?: string;\n  /**\n   * @zh_CN 标题\n   */\n  title?: string;\n  /**\n   * @zh_CN 描述\n   */\n  subTitle?: string;\n  /**\n   * @zh_CN 按钮文本\n   */\n  submitButtonText?: string;\n}\n\ndefineOptions({\n  name: 'ForgetPassword',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  loading: false,\n  loginPath: '/auth/login',\n  submitButtonText: '',\n  subTitle: '',\n  title: '',\n});\n\nconst emit = defineEmits<{\n  submit: [Record<string, any>];\n}>();\n\nconst [Form, formApi] = useVbenForm(\n  reactive({\n    commonConfig: {\n      hideLabel: true,\n      hideRequiredMark: true,\n    },\n    schema: computed(() => props.formSchema),\n    showDefaultActions: false,\n  }),\n);\n\nconst router = useRouter();\n\nasync function handleSubmit() {\n  const { valid } = await formApi.validate();\n  const values = await formApi.getValues();\n  if (valid) {\n    emit('submit', values);\n  }\n}\n\nfunction goToLogin() {\n  router.push(props.loginPath);\n}\n\ndefineExpose({\n  getFormApi: () => formApi,\n});\n</script>\n\n<template>\n  <div>\n    <Title>\n      <slot name=\"title\">\n        {{ title || $t('authentication.forgetPassword') }} 🤦🏻‍♂️\n      </slot>\n      <template #desc>\n        <slot name=\"subTitle\">\n          {{ subTitle || $t('authentication.forgetPasswordSubtitle') }}\n        </slot>\n      </template>\n    </Title>\n    <Form />\n\n    <div>\n      <VbenButton\n        :class=\"{\n          'cursor-wait': loading,\n        }\"\n        aria-label=\"submit\"\n        class=\"mt-2 w-full\"\n        @click=\"handleSubmit\"\n      >\n        <slot name=\"submitButtonText\">\n          {{ submitButtonText || $t('authentication.sendResetLink') }}\n        </slot>\n      </VbenButton>\n      <VbenButton class=\"mt-4 w-full\" variant=\"outline\" @click=\"goToLogin()\">\n        {{ $t('common.back') }}\n      </VbenButton>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/index.ts",
    "content": "export { default as AuthenticationCodeLogin } from './code-login.vue';\nexport { default as AuthenticationForgetPassword } from './forget-password.vue';\nexport { default as AuthenticationLoginExpiredModal } from './login-expired-modal.vue';\nexport { default as AuthenticationLogin } from './login.vue';\nexport { default as AuthenticationQrCodeLogin } from './qrcode-login.vue';\nexport { default as AuthenticationRegister } from './register.vue';\nexport type { AuthenticationProps } from './types';\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/login-expired-modal.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AuthenticationProps } from './types';\n\nimport { computed, watch } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport { useVbenModal } from '@vben-core/popup-ui';\nimport { Slot, VbenAvatar } from '@vben-core/shadcn-ui';\n\ninterface Props extends AuthenticationProps {\n  avatar?: string;\n  zIndex?: number;\n}\n\ndefineOptions({\n  name: 'LoginExpiredModal',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  avatar: '',\n  zIndex: 0,\n});\n\nconst open = defineModel<boolean>('open');\n\nconst [Modal, modalApi] = useVbenModal();\n\nwatch(\n  () => open.value,\n  (val) => {\n    modalApi.setState({ isOpen: val });\n  },\n);\n\nconst getZIndex = computed(() => {\n  return props.zIndex || calcZIndex();\n});\n\n/**\n * 排除ant-message和loading:9999的z-index\n */\nconst zIndexExcludeClass = ['ant-message', 'loading'];\nfunction isZIndexExcludeClass(element: Element) {\n  return zIndexExcludeClass.some((className) =>\n    element.classList.contains(className),\n  );\n}\n\n/**\n * 获取最大的zIndex值\n */\nfunction calcZIndex() {\n  let maxZ = 0;\n  const elements = document.querySelectorAll('*');\n  [...elements].forEach((element) => {\n    const style = window.getComputedStyle(element);\n    const zIndex = style.getPropertyValue('z-index');\n    if (\n      zIndex &&\n      !Number.isNaN(Number.parseInt(zIndex)) &&\n      !isZIndexExcludeClass(element)\n    ) {\n      maxZ = Math.max(maxZ, Number.parseInt(zIndex));\n    }\n  });\n  return maxZ + 1;\n}\n</script>\n\n<template>\n  <div>\n    <Modal\n      :closable=\"false\"\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n      :footer=\"false\"\n      :fullscreen-button=\"false\"\n      :header=\"false\"\n      :z-index=\"getZIndex\"\n      class=\"border-none px-10 py-6 text-center shadow-xl sm:w-150 sm:rounded-2xl md:h-[unset]\"\n    >\n      <VbenAvatar :src=\"avatar\" class=\"mx-auto mb-6 size-20\" />\n      <Slot\n        :show-forget-password=\"false\"\n        :show-register=\"false\"\n        :show-remember-me=\"false\"\n        :sub-title=\"$t('authentication.loginAgainSubTitle')\"\n        :title=\"$t('authentication.loginAgainTitle')\"\n      >\n        <slot> </slot>\n      </Slot>\n    </Modal>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/login.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Recordable } from '@vben/types';\n\nimport type { VbenFormSchema } from '@vben-core/form-ui';\n\nimport type { AuthenticationProps } from './types';\n\nimport { computed, onMounted, reactive, ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { $t } from '@vben/locales';\n\nimport { useVbenForm } from '@vben-core/form-ui';\nimport { VbenButton, VbenCheckbox } from '@vben-core/shadcn-ui';\n\nimport Title from './auth-title.vue';\nimport ThirdPartyLogin from './third-party-login.vue';\n\ninterface Props extends AuthenticationProps {\n  formSchema?: VbenFormSchema[];\n}\n\ndefineOptions({\n  name: 'AuthenticationLogin',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  codeLoginPath: '/auth/code-login',\n  forgetPasswordPath: '/auth/forget-password',\n  formSchema: () => [],\n  loading: false,\n  qrCodeLoginPath: '/auth/qrcode-login',\n  registerPath: '/auth/register',\n  showCodeLogin: true,\n  showForgetPassword: true,\n  showQrcodeLogin: true,\n  showRegister: true,\n  showRememberMe: true,\n  showThirdPartyLogin: true,\n  submitButtonText: '',\n  subTitle: '',\n  title: '',\n});\n\nconst emit = defineEmits<{\n  submit: [Recordable<any>];\n}>();\n\nconst [Form, formApi] = useVbenForm(\n  reactive({\n    commonConfig: {\n      hideLabel: true,\n      hideRequiredMark: true,\n    },\n    schema: computed(() => props.formSchema),\n    showDefaultActions: false,\n  }),\n);\nconst router = useRouter();\n\nconst REMEMBER_ME_KEY = `REMEMBER_ME_USERNAME_${location.hostname}`;\n\nconst localUsername = localStorage.getItem(REMEMBER_ME_KEY) || '';\n\nconst rememberMe = ref(!!localUsername);\n\nasync function handleSubmit() {\n  const { valid } = await formApi.validate();\n  const values = await formApi.getValues();\n  if (valid) {\n    localStorage.setItem(\n      REMEMBER_ME_KEY,\n      rememberMe.value ? values?.username : '',\n    );\n    emit('submit', values);\n  }\n}\n\nfunction handleGo(path: string) {\n  router.push(path);\n}\n\nonMounted(() => {\n  if (localUsername) {\n    formApi.setFieldValue('username', localUsername);\n  }\n});\n\ndefineExpose({\n  getFormApi: () => formApi,\n});\n</script>\n\n<template>\n  <div @keydown.enter.prevent=\"handleSubmit\">\n    <slot name=\"title\">\n      <Title>\n        <slot name=\"title\">\n          {{ title || `${$t('authentication.welcomeBack')} 👋🏻` }}\n        </slot>\n        <template #desc>\n          <span class=\"text-muted-foreground\">\n            <slot name=\"subTitle\">\n              {{ subTitle || $t('authentication.loginSubtitle') }}\n            </slot>\n          </span>\n        </template>\n      </Title>\n    </slot>\n\n    <Form />\n\n    <div\n      v-if=\"showRememberMe || showForgetPassword\"\n      class=\"mb-6 flex justify-between\"\n    >\n      <div class=\"flex-center\">\n        <VbenCheckbox\n          v-if=\"showRememberMe\"\n          v-model=\"rememberMe\"\n          name=\"rememberMe\"\n        >\n          {{ $t('authentication.rememberMe') }}\n        </VbenCheckbox>\n      </div>\n\n      <span\n        v-if=\"showForgetPassword\"\n        class=\"vben-link text-sm font-normal\"\n        @click=\"handleGo(forgetPasswordPath)\"\n      >\n        {{ $t('authentication.forgetPassword') }}\n      </span>\n    </div>\n    <VbenButton\n      :class=\"{\n        'cursor-wait': loading,\n      }\"\n      :loading=\"loading\"\n      aria-label=\"login\"\n      class=\"w-full\"\n      @click=\"handleSubmit\"\n    >\n      {{ submitButtonText || $t('common.login') }}\n    </VbenButton>\n\n    <div\n      v-if=\"showCodeLogin || showQrcodeLogin\"\n      class=\"mt-4 mb-2 flex items-center justify-between\"\n    >\n      <VbenButton\n        v-if=\"showCodeLogin\"\n        class=\"w-1/2\"\n        variant=\"outline\"\n        @click=\"handleGo(codeLoginPath)\"\n      >\n        {{ $t('authentication.mobileLogin') }}\n      </VbenButton>\n      <VbenButton\n        v-if=\"showQrcodeLogin\"\n        class=\"ml-4 w-1/2\"\n        variant=\"outline\"\n        @click=\"handleGo(qrCodeLoginPath)\"\n      >\n        {{ $t('authentication.qrcodeLogin') }}\n      </VbenButton>\n    </div>\n\n    <!-- 第三方登录 -->\n    <slot name=\"third-party-login\">\n      <ThirdPartyLogin v-if=\"showThirdPartyLogin\" />\n    </slot>\n\n    <slot name=\"to-register\">\n      <div v-if=\"showRegister\" class=\"mt-3 text-center text-sm\">\n        {{ $t('authentication.accountTip') }}\n        <span\n          class=\"vben-link text-sm font-normal\"\n          @click=\"handleGo(registerPath)\"\n        >\n          {{ $t('authentication.createAccount') }}\n        </span>\n      </div>\n    </slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/qrcode-login.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { $t } from '@vben/locales';\n\nimport { VbenButton } from '@vben-core/shadcn-ui';\n\nimport { useQRCode } from '@vueuse/integrations/useQRCode';\n\nimport Title from './auth-title.vue';\n\ninterface Props {\n  /**\n   * @zh_CN 是否处于加载处理状态\n   */\n  loading?: boolean;\n  /**\n   * @zh_CN 登录路径\n   */\n  loginPath?: string;\n  /**\n   * @zh_CN 标题\n   */\n  title?: string;\n  /**\n   * @zh_CN 描述\n   */\n  subTitle?: string;\n  /**\n   * @zh_CN 按钮文本\n   */\n  submitButtonText?: string;\n  /**\n   * @zh_CN 描述\n   */\n  description?: string;\n  /**\n   * @zh_CN 是否显示返回按钮\n   */\n  showBack?: boolean;\n}\n\ndefineOptions({\n  name: 'AuthenticationQrCodeLogin',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  description: '',\n  loading: false,\n  showBack: true,\n  loginPath: '/auth/login',\n  submitButtonText: '',\n  subTitle: '',\n  title: '',\n});\n\nconst router = useRouter();\n\nconst text = ref('https://vben.vvbin.cn');\n\nconst qrcode = useQRCode(text, {\n  errorCorrectionLevel: 'H',\n  margin: 4,\n});\n\nfunction goToLogin() {\n  router.push(props.loginPath);\n}\n</script>\n\n<template>\n  <div>\n    <Title>\n      <slot name=\"title\">\n        {{ title || $t('authentication.welcomeBack') }} 📱\n      </slot>\n      <template #desc>\n        <span class=\"text-muted-foreground\">\n          <slot name=\"subTitle\">\n            {{ subTitle || $t('authentication.qrcodeSubtitle') }}\n          </slot>\n        </span>\n      </template>\n    </Title>\n\n    <div class=\"mt-6 flex-col-center\">\n      <img :src=\"qrcode\" alt=\"qrcode\" class=\"w-1/2\" />\n      <p class=\"mt-4 text-sm text-muted-foreground\">\n        <slot name=\"description\">\n          {{ description || $t('authentication.qrcodePrompt') }}\n        </slot>\n      </p>\n    </div>\n\n    <VbenButton\n      v-if=\"showBack\"\n      class=\"mt-4 w-full\"\n      variant=\"outline\"\n      @click=\"goToLogin()\"\n    >\n      {{ $t('common.back') }}\n    </VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/register.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Recordable } from '@vben/types';\n\nimport type { VbenFormSchema } from '@vben-core/form-ui';\n\nimport { computed, reactive } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { $t } from '@vben/locales';\n\nimport { useVbenForm } from '@vben-core/form-ui';\nimport { VbenButton } from '@vben-core/shadcn-ui';\n\nimport Title from './auth-title.vue';\n\ninterface Props {\n  formSchema?: VbenFormSchema[];\n  /**\n   * @zh_CN 是否处于加载处理状态\n   */\n  loading?: boolean;\n  /**\n   * @zh_CN 登录路径\n   */\n  loginPath?: string;\n  /**\n   * @zh_CN 标题\n   */\n  title?: string;\n  /**\n   * @zh_CN 描述\n   */\n  subTitle?: string;\n  /**\n   * @zh_CN 按钮文本\n   */\n  submitButtonText?: string;\n}\n\ndefineOptions({\n  name: 'RegisterForm',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  formSchema: () => [],\n  loading: false,\n  loginPath: '/auth/login',\n  submitButtonText: '',\n  subTitle: '',\n  title: '',\n});\n\nconst emit = defineEmits<{\n  submit: [Recordable<any>];\n}>();\n\nconst [Form, formApi] = useVbenForm(\n  reactive({\n    commonConfig: {\n      hideLabel: true,\n      hideRequiredMark: true,\n    },\n    schema: computed(() => props.formSchema),\n    showDefaultActions: false,\n  }),\n);\n\nconst router = useRouter();\n\nasync function handleSubmit() {\n  const { valid } = await formApi.validate();\n  const values = await formApi.getValues();\n  if (valid) {\n    emit('submit', values as { password: string; username: string });\n  }\n}\n\nfunction goToLogin() {\n  router.push(props.loginPath);\n}\n\ndefineExpose({\n  getFormApi: () => formApi,\n});\n</script>\n\n<template>\n  <div>\n    <Title>\n      <slot name=\"title\">\n        {{ title || $t('authentication.createAnAccount') }} 🚀\n      </slot>\n      <template #desc>\n        <slot name=\"subTitle\">\n          {{ subTitle || $t('authentication.signUpSubtitle') }}\n        </slot>\n      </template>\n    </Title>\n    <Form />\n\n    <VbenButton\n      :class=\"{\n        'cursor-wait': loading,\n      }\"\n      :loading=\"loading\"\n      aria-label=\"register\"\n      class=\"mt-2 w-full\"\n      @click=\"handleSubmit\"\n    >\n      <slot name=\"submitButtonText\">\n        {{ submitButtonText || $t('authentication.signUp') }}\n      </slot>\n    </VbenButton>\n    <div class=\"mt-4 text-center text-sm\">\n      {{ $t('authentication.alreadyHaveAccount') }}\n      <span class=\"vben-link text-sm font-normal\" @click=\"goToLogin()\">\n        {{ $t('authentication.goToLogin') }}\n      </span>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/third-party-login.vue",
    "content": "<script setup lang=\"ts\">\nimport { useAppConfig } from '@vben/hooks';\nimport {\n  SvgGithubIcon,\n  SvgGoogleIcon,\n  SvgQQChatIcon,\n  SvgWeChatIcon,\n} from '@vben/icons';\nimport { $t } from '@vben/locales';\n\nimport { VbenIconButton } from '@vben-core/shadcn-ui';\n\nimport DingdingLogin from './dingding-login.vue';\n\ndefineOptions({\n  name: 'ThirdPartyLogin',\n});\n\nconst {\n  auth: { dingding: dingdingAuthConfig },\n} = useAppConfig(import.meta.env, import.meta.env.PROD);\n</script>\n\n<template>\n  <div class=\"w-full sm:mx-auto md:max-w-md\">\n    <div class=\"mt-4 flex items-center justify-between\">\n      <span class=\"w-[35%] border-b border-input dark:border-gray-600\"></span>\n      <span class=\"text-center text-xs text-muted-foreground uppercase\">\n        {{ $t('authentication.thirdPartyLogin') }}\n      </span>\n      <span class=\"w-[35%] border-b border-input dark:border-gray-600\"></span>\n    </div>\n\n    <div class=\"mt-4 flex flex-wrap justify-center\">\n      <VbenIconButton\n        :tooltip=\"$t('authentication.wechatLogin')\"\n        tooltip-side=\"top\"\n        class=\"mb-3\"\n      >\n        <SvgWeChatIcon />\n      </VbenIconButton>\n      <VbenIconButton\n        :tooltip=\"$t('authentication.qqLogin')\"\n        tooltip-side=\"top\"\n        class=\"mb-3\"\n      >\n        <SvgQQChatIcon />\n      </VbenIconButton>\n      <VbenIconButton\n        :tooltip=\"$t('authentication.githubLogin')\"\n        tooltip-side=\"top\"\n        class=\"mb-3\"\n      >\n        <SvgGithubIcon />\n      </VbenIconButton>\n      <VbenIconButton\n        :tooltip=\"$t('authentication.googleLogin')\"\n        tooltip-side=\"top\"\n        class=\"mb-3\"\n      >\n        <SvgGoogleIcon />\n      </VbenIconButton>\n      <DingdingLogin\n        v-if=\"dingdingAuthConfig\"\n        :corp-id=\"dingdingAuthConfig.corpId\"\n        :client-id=\"dingdingAuthConfig.clientId\"\n        class=\"mb-3\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/authentication/types.ts",
    "content": "interface AuthenticationProps {\n  /**\n   * @zh_CN 验证码登录路径\n   */\n  codeLoginPath?: string;\n  /**\n   * @zh_CN 忘记密码路径\n   */\n  forgetPasswordPath?: string;\n\n  /**\n   * @zh_CN 是否处于加载处理状态\n   */\n  loading?: boolean;\n\n  /**\n   * @zh_CN 二维码登录路径\n   */\n  qrCodeLoginPath?: string;\n\n  /**\n   * @zh_CN 注册路径\n   */\n  registerPath?: string;\n\n  /**\n   * @zh_CN 是否显示验证码登录\n   */\n  showCodeLogin?: boolean;\n  /**\n   * @zh_CN 是否显示忘记密码\n   */\n  showForgetPassword?: boolean;\n\n  /**\n   * @zh_CN 是否显示二维码登录\n   */\n  showQrcodeLogin?: boolean;\n\n  /**\n   * @zh_CN 是否显示注册按钮\n   */\n  showRegister?: boolean;\n\n  /**\n   * @zh_CN 是否显示记住账号\n   */\n  showRememberMe?: boolean;\n\n  /**\n   * @zh_CN 是否显示第三方登录\n   */\n  showThirdPartyLogin?: boolean;\n\n  /**\n   * @zh_CN 登录框子标题\n   */\n  subTitle?: string;\n\n  /**\n   * @zh_CN 登录框标题\n   */\n  title?: string;\n  /**\n   * @zh_CN 提交按钮文本\n   */\n  submitButtonText?: string;\n}\n\nexport type { AuthenticationProps };\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/analysis/analysis-chart-card.vue",
    "content": "<script setup lang=\"ts\">\nimport { Card, CardContent, CardHeader, CardTitle } from '@vben-core/shadcn-ui';\n\ninterface Props {\n  title: string;\n}\n\ndefineOptions({\n  name: 'AnalysisChartCard',\n});\n\nwithDefaults(defineProps<Props>(), {});\n</script>\n\n<template>\n  <Card>\n    <CardHeader>\n      <CardTitle class=\"text-xl\">{{ title }}</CardTitle>\n    </CardHeader>\n    <CardContent>\n      <slot></slot>\n    </CardContent>\n  </Card>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/analysis/analysis-charts-tabs.vue",
    "content": "<script setup lang=\"ts\">\nimport type { TabOption } from '@vben/types';\n\nimport { computed } from 'vue';\n\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@vben-core/shadcn-ui';\n\ninterface Props {\n  tabs?: TabOption[];\n}\n\ndefineOptions({\n  name: 'AnalysisChartsTabs',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  tabs: () => [],\n});\n\nconst defaultValue = computed(() => {\n  return props.tabs?.[0]?.value;\n});\n</script>\n\n<template>\n  <div class=\"card-box w-full px-4 pt-3 pb-5\">\n    <Tabs :default-value=\"defaultValue\">\n      <TabsList>\n        <template v-for=\"tab in tabs\" :key=\"tab.label\">\n          <TabsTrigger :value=\"tab.value\"> {{ tab.label }} </TabsTrigger>\n        </template>\n      </TabsList>\n      <template v-for=\"tab in tabs\" :key=\"tab.label\">\n        <TabsContent :value=\"tab.value\" class=\"pt-4\">\n          <slot :name=\"tab.value\"></slot>\n        </TabsContent>\n      </template>\n    </Tabs>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/analysis/analysis-overview.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AnalysisOverviewItem } from '../typing';\n\nimport {\n  Card,\n  CardContent,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n  VbenCountToAnimator,\n  VbenIcon,\n} from '@vben-core/shadcn-ui';\n\ninterface Props {\n  items?: AnalysisOverviewItem[];\n}\n\ndefineOptions({\n  name: 'AnalysisOverview',\n});\n\nwithDefaults(defineProps<Props>(), {\n  items: () => [],\n});\n</script>\n\n<template>\n  <div class=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4\">\n    <template v-for=\"item in items\" :key=\"item.title\">\n      <Card :title=\"item.title\" class=\"w-full\">\n        <CardHeader>\n          <CardTitle class=\"text-xl\">{{ item.title }}</CardTitle>\n        </CardHeader>\n\n        <CardContent class=\"flex items-center justify-between\">\n          <VbenCountToAnimator\n            :end-val=\"item.value\"\n            :start-val=\"1\"\n            class=\"text-xl\"\n            prefix=\"\"\n          />\n          <VbenIcon :icon=\"item.icon\" class=\"size-8 shrink-0\" />\n        </CardContent>\n        <CardFooter class=\"justify-between\">\n          <span>{{ item.totalTitle }}</span>\n          <VbenCountToAnimator\n            :end-val=\"item.totalValue\"\n            :start-val=\"1\"\n            prefix=\"\"\n          />\n        </CardFooter>\n      </Card>\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/analysis/index.ts",
    "content": "export { default as AnalysisChartCard } from './analysis-chart-card.vue';\nexport { default as AnalysisChartsTabs } from './analysis-charts-tabs.vue';\nexport { default as AnalysisOverview } from './analysis-overview.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/index.ts",
    "content": "export * from './analysis';\nexport type * from './typing';\nexport * from './workbench';\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/typing.ts",
    "content": "import type { Component } from 'vue';\n\ninterface AnalysisOverviewItem {\n  icon: Component | string;\n  title: string;\n  totalTitle: string;\n  totalValue: number;\n  value: number;\n}\n\ninterface WorkbenchProjectItem {\n  color?: string;\n  content: string;\n  date: string;\n  group: string;\n  icon: Component | string;\n  title: string;\n  url?: string;\n}\n\ninterface WorkbenchTrendItem {\n  avatar: string;\n  content: string;\n  date: string;\n  title: string;\n}\n\ninterface WorkbenchTodoItem {\n  completed: boolean;\n  content: string;\n  date: string;\n  title: string;\n}\n\ninterface WorkbenchQuickNavItem {\n  color?: string;\n  icon: Component | string;\n  title: string;\n  url?: string;\n}\n\nexport type {\n  AnalysisOverviewItem,\n  WorkbenchProjectItem,\n  WorkbenchQuickNavItem,\n  WorkbenchTodoItem,\n  WorkbenchTrendItem,\n};\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/workbench/index.ts",
    "content": "export { default as WorkbenchHeader } from './workbench-header.vue';\nexport { default as WorkbenchProject } from './workbench-project.vue';\nexport { default as WorkbenchQuickNav } from './workbench-quick-nav.vue';\nexport { default as WorkbenchTodo } from './workbench-todo.vue';\nexport { default as WorkbenchTrends } from './workbench-trends.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/workbench/workbench-header.vue",
    "content": "<script lang=\"ts\" setup>\nimport { VbenAvatar } from '@vben-core/shadcn-ui';\n\ninterface Props {\n  avatar?: string;\n}\n\ndefineOptions({\n  name: 'WorkbenchHeader',\n});\n\nwithDefaults(defineProps<Props>(), {\n  avatar: '',\n});\n</script>\n<template>\n  <div class=\"card-box p-4 py-6 lg:flex\">\n    <VbenAvatar :src=\"avatar\" class=\"size-20\" />\n    <div\n      v-if=\"$slots.title || $slots.description\"\n      class=\"flex flex-col justify-center md:mt-0 md:ml-6\"\n    >\n      <h1 v-if=\"$slots.title\" class=\"text-md font-semibold md:text-xl\">\n        <slot name=\"title\"></slot>\n      </h1>\n      <span v-if=\"$slots.description\" class=\"mt-1 text-foreground/80\">\n        <slot name=\"description\"></slot>\n      </span>\n    </div>\n    <div class=\"mt-4 flex flex-1 justify-end md:mt-0\">\n      <div class=\"flex flex-col justify-center text-right\">\n        <span class=\"text-foreground/80\"> 待办 </span>\n        <span class=\"text-2xl\">2/10</span>\n      </div>\n\n      <div class=\"mx-12 flex flex-col justify-center text-right md:mx-16\">\n        <span class=\"text-foreground/80\"> 项目 </span>\n        <span class=\"text-2xl\">8</span>\n      </div>\n      <div class=\"mr-4 flex flex-col justify-center text-right md:mr-10\">\n        <span class=\"text-foreground/80\"> 团队 </span>\n        <span class=\"text-2xl\">300</span>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/workbench/workbench-project.vue",
    "content": "<script setup lang=\"ts\">\nimport type { WorkbenchProjectItem } from '../typing';\n\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle,\n  VbenIcon,\n} from '@vben-core/shadcn-ui';\n\ninterface Props {\n  items?: WorkbenchProjectItem[];\n  title: string;\n}\n\ndefineOptions({\n  name: 'WorkbenchProject',\n});\n\nwithDefaults(defineProps<Props>(), {\n  items: () => [],\n});\n\ndefineEmits(['click']);\n</script>\n\n<template>\n  <Card>\n    <CardHeader class=\"py-4\">\n      <CardTitle class=\"text-lg\">{{ title }}</CardTitle>\n    </CardHeader>\n    <CardContent class=\"flex flex-wrap p-0\">\n      <template v-for=\"(item, index) in items\" :key=\"item.title\">\n        <div\n          :class=\"{\n            'border-r-0': index % 3 === 2,\n            'border-b-0': index < 3,\n            'pb-4': index > 2,\n            'rounded-bl-xl': index === items.length - 3,\n            'rounded-br-xl': index === items.length - 1,\n          }\"\n          class=\"group w-full cursor-pointer border-t border-r border-border p-4 transition-all hover:shadow-xl md:w-1/2 lg:w-1/3\"\n        >\n          <div class=\"flex items-center\">\n            <VbenIcon\n              :color=\"item.color\"\n              :icon=\"item.icon\"\n              class=\"size-8 transition-all duration-300 group-hover:scale-110\"\n              @click=\"$emit('click', item)\"\n            />\n            <span class=\"ml-4 text-lg font-medium\">{{ item.title }}</span>\n          </div>\n          <div class=\"mt-4 flex h-10 text-foreground/80\">\n            {{ item.content }}\n          </div>\n          <div class=\"flex justify-between text-foreground/80\">\n            <span>{{ item.group }}</span>\n            <span>{{ item.date }}</span>\n          </div>\n        </div>\n      </template>\n    </CardContent>\n  </Card>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/workbench/workbench-quick-nav.vue",
    "content": "<script setup lang=\"ts\">\nimport type { WorkbenchQuickNavItem } from '../typing';\n\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle,\n  VbenIcon,\n} from '@vben-core/shadcn-ui';\n\ninterface Props {\n  items?: WorkbenchQuickNavItem[];\n  title: string;\n}\n\ndefineOptions({\n  name: 'WorkbenchQuickNav',\n});\n\nwithDefaults(defineProps<Props>(), {\n  items: () => [],\n});\n\ndefineEmits(['click']);\n</script>\n\n<template>\n  <Card>\n    <CardHeader class=\"py-4\">\n      <CardTitle class=\"text-lg\">{{ title }}</CardTitle>\n    </CardHeader>\n    <CardContent class=\"flex flex-wrap p-0\">\n      <template v-for=\"(item, index) in items\" :key=\"item.title\">\n        <div\n          :class=\"{\n            'border-r-0': index % 3 === 2,\n            'border-b-0': index < 3,\n            'pb-4': index > 2,\n            'rounded-bl-xl': index === items.length - 3,\n            'rounded-br-xl': index === items.length - 1,\n          }\"\n          class=\"group flex-col-center w-1/3 cursor-pointer border-t border-r border-border py-8 hover:shadow-xl\"\n          @click=\"$emit('click', item)\"\n        >\n          <VbenIcon\n            :color=\"item.color\"\n            :icon=\"item.icon\"\n            class=\"size-7 transition-all duration-300 group-hover:scale-125\"\n          />\n          <span class=\"text-md mt-2 truncate\">{{ item.title }}</span>\n        </div>\n      </template>\n    </CardContent>\n  </Card>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/workbench/workbench-todo.vue",
    "content": "<script setup lang=\"ts\">\nimport type { WorkbenchTodoItem } from '../typing';\n\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle,\n  VbenCheckbox,\n} from '@vben-core/shadcn-ui';\n\ninterface Props {\n  items?: WorkbenchTodoItem[];\n  title: string;\n}\n\ndefineOptions({\n  name: 'WorkbenchTodo',\n});\n\nwithDefaults(defineProps<Props>(), {\n  items: () => [],\n});\n</script>\n\n<template>\n  <Card>\n    <CardHeader class=\"py-4\">\n      <CardTitle class=\"text-lg\">{{ title }}</CardTitle>\n    </CardHeader>\n    <CardContent class=\"flex flex-wrap p-5 pt-0\">\n      <ul class=\"w-full divide-y divide-border\" role=\"list\">\n        <li\n          v-for=\"item in items\"\n          :key=\"item.title\"\n          :class=\"{\n            'line-through opacity-60 select-none': item.completed,\n          }\"\n          class=\"flex cursor-pointer justify-between gap-x-6 py-5\"\n        >\n          <div class=\"flex min-w-0 items-center gap-x-4\">\n            <VbenCheckbox v-model=\"item.completed\" name=\"completed\" />\n            <div class=\"min-w-0 flex-auto\">\n              <p class=\"text-sm/6 font-semibold text-foreground\">\n                {{ item.title }}\n              </p>\n              <!-- eslint-disable vue/no-v-html -->\n              <p\n                class=\"mt-1 truncate text-xs/5 text-foreground/80 *:text-primary\"\n                v-html=\"item.content\"\n              ></p>\n            </div>\n          </div>\n          <div class=\"hidden h-full shrink-0 sm:flex sm:flex-col sm:items-end\">\n            <span class=\"mt-6 text-xs/6 text-foreground/80\">\n              {{ item.date }}\n            </span>\n          </div>\n        </li>\n      </ul>\n    </CardContent>\n  </Card>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/dashboard/workbench/workbench-trends.vue",
    "content": "<script setup lang=\"ts\">\nimport type { WorkbenchTrendItem } from '../typing';\n\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle,\n  VbenIcon,\n} from '@vben-core/shadcn-ui';\n\ninterface Props {\n  items?: WorkbenchTrendItem[];\n  title: string;\n}\n\ndefineOptions({\n  name: 'WorkbenchTrends',\n});\n\nwithDefaults(defineProps<Props>(), {\n  items: () => [],\n});\n</script>\n\n<template>\n  <Card>\n    <CardHeader class=\"py-4\">\n      <CardTitle class=\"text-lg\">{{ title }}</CardTitle>\n    </CardHeader>\n    <CardContent class=\"flex flex-wrap p-5 pt-0\">\n      <ul class=\"w-full divide-y divide-border\" role=\"list\">\n        <li\n          v-for=\"item in items\"\n          :key=\"item.title\"\n          class=\"flex justify-between gap-x-6 py-5\"\n        >\n          <div class=\"flex min-w-0 items-center gap-x-4\">\n            <VbenIcon\n              :icon=\"item.avatar\"\n              alt=\"\"\n              class=\"size-10 flex-none rounded-full\"\n            />\n            <div class=\"min-w-0 flex-auto\">\n              <p class=\"text-sm/6 font-semibold text-foreground\">\n                {{ item.title }}\n              </p>\n              <!-- eslint-disable vue/no-v-html -->\n              <p\n                class=\"mt-1 truncate text-xs/5 text-foreground/80 *:text-primary\"\n                v-html=\"item.content\"\n              ></p>\n            </div>\n          </div>\n          <div class=\"hidden h-full shrink-0 sm:flex sm:flex-col sm:items-end\">\n            <span class=\"mt-6 text-xs/6 text-foreground/80\">\n              {{ item.date }}\n            </span>\n          </div>\n        </li>\n      </ul>\n    </CardContent>\n  </Card>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/fallback/fallback.ts",
    "content": "interface FallbackProps {\n  /**\n   * 描述\n   */\n  description?: string;\n  /**\n   *  @zh_CN 首页路由地址\n   *  @default /\n   */\n  homePath?: string;\n  /**\n   * @zh_CN 默认显示的图片\n   * @default pageNotFoundSvg\n   */\n  image?: string;\n  /**\n   *  @zh_CN 内置类型\n   */\n  status?: '403' | '404' | '500' | 'coming-soon' | 'offline';\n  /**\n   *  @zh_CN 页面提示语\n   */\n  title?: string;\n}\nexport type { FallbackProps };\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/fallback/fallback.vue",
    "content": "<script setup lang=\"ts\">\nimport type { FallbackProps } from './fallback';\n\nimport { computed, defineAsyncComponent } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { ArrowLeft, RotateCw } from '@vben/icons';\nimport { $t } from '@vben/locales';\n\nimport { VbenButton } from '@vben-core/shadcn-ui';\n\ninterface Props extends FallbackProps {}\n\ndefineOptions({\n  name: 'Fallback',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  description: '',\n  homePath: '/',\n  image: '',\n  showBack: true,\n  status: 'coming-soon',\n  title: '',\n});\n\nconst Icon403 = defineAsyncComponent(() => import('./icons/icon-403.vue'));\nconst Icon404 = defineAsyncComponent(() => import('./icons/icon-404.vue'));\nconst Icon500 = defineAsyncComponent(() => import('./icons/icon-500.vue'));\nconst IconHello = defineAsyncComponent(\n  () => import('./icons/icon-coming-soon.vue'),\n);\nconst IconOffline = defineAsyncComponent(\n  () => import('./icons/icon-offline.vue'),\n);\n\nconst titleText = computed(() => {\n  if (props.title) {\n    return props.title;\n  }\n\n  switch (props.status) {\n    case '403': {\n      return $t('ui.fallback.forbidden');\n    }\n    case '404': {\n      return $t('ui.fallback.pageNotFound');\n    }\n    case '500': {\n      return $t('ui.fallback.internalError');\n    }\n    case 'coming-soon': {\n      return $t('ui.fallback.comingSoon');\n    }\n    case 'offline': {\n      return $t('ui.fallback.offlineError');\n    }\n    default: {\n      return '';\n    }\n  }\n});\n\nconst descText = computed(() => {\n  if (props.description) {\n    return props.description;\n  }\n  switch (props.status) {\n    case '403': {\n      return $t('ui.fallback.forbiddenDesc');\n    }\n    case '404': {\n      return $t('ui.fallback.pageNotFoundDesc');\n    }\n    case '500': {\n      return $t('ui.fallback.internalErrorDesc');\n    }\n    case 'offline': {\n      return $t('ui.fallback.offlineErrorDesc');\n    }\n    default: {\n      return '';\n    }\n  }\n});\n\nconst fallbackIcon = computed(() => {\n  switch (props.status) {\n    case '403': {\n      return Icon403;\n    }\n    case '404': {\n      return Icon404;\n    }\n    case '500': {\n      return Icon500;\n    }\n    case 'coming-soon': {\n      return IconHello;\n    }\n    case 'offline': {\n      return IconOffline;\n    }\n    default: {\n      return null;\n    }\n  }\n});\n\nconst showBack = computed(() => {\n  return props.status === '403' || props.status === '404';\n});\n\nconst showRefresh = computed(() => {\n  return props.status === '500' || props.status === 'offline';\n});\n\nconst { push } = useRouter();\n\n// 返回首页\nfunction back() {\n  push(props.homePath);\n}\n\nfunction refresh() {\n  location.reload();\n}\n</script>\n\n<template>\n  <div class=\"flex-col-center size-full duration-300\">\n    <img v-if=\"image\" :src=\"image\" class=\"md:1/3 w-1/2 lg:w-1/4\" />\n    <component\n      :is=\"fallbackIcon\"\n      v-else-if=\"fallbackIcon\"\n      class=\"md:1/3 h-1/3 w-1/2 lg:w-1/4\"\n    />\n    <div class=\"flex-col-center\">\n      <slot v-if=\"$slots.title\" name=\"title\"></slot>\n      <p\n        v-else-if=\"titleText\"\n        class=\"mt-8 text-2xl text-foreground md:text-3xl lg:text-4xl\"\n      >\n        {{ titleText }}\n      </p>\n      <slot v-if=\"$slots.describe\" name=\"describe\"></slot>\n      <p\n        v-else-if=\"descText\"\n        class=\"md:text-md my-4 text-muted-foreground lg:text-lg\"\n      >\n        {{ descText }}\n      </p>\n      <slot v-if=\"$slots.action\" name=\"action\"></slot>\n      <VbenButton v-else-if=\"showBack\" size=\"lg\" @click=\"back\">\n        <ArrowLeft class=\"mr-2 size-4\" />\n        {{ $t('common.backToHome') }}\n      </VbenButton>\n      <VbenButton v-else-if=\"showRefresh\" size=\"lg\" @click=\"refresh\">\n        <RotateCw class=\"mr-2 size-4\" />\n        {{ $t('common.refresh') }}\n      </VbenButton>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/fallback/icons/icon-403.vue",
    "content": "<template>\n  <svg\n    height=\"659.29778\"\n    viewBox=\"0 0 586 659.29778\"\n    width=\"586\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n  >\n    <circle cx=\"332.47856\" cy=\"254\" fill=\"#f2f2f2\" r=\"254.00001\" />\n    <path\n      d=\"M498.46363,113.58835H33.17063c-.99774-.02133-1.78931-.84746-1.76797-1.84521,.02069-.96771,.80026-1.74727,1.76797-1.76796H498.46363c.99774,.02133,1.78931,.84746,1.76794,1.84521-.02069,.96771-.80023,1.74727-1.76794,1.76796Z\"\n      fill=\"#cacaca\"\n    />\n    <rect\n      fill=\"#fff\"\n      height=\"34.98639\"\n      rx=\"17.49318\"\n      ry=\"17.49318\"\n      width=\"163.61147\"\n      x=\"193.77441\"\n      y=\"174.47256\"\n    />\n    <path\n      d=\"M128.17493,244.44534H422.98542c9.66122,0,17.49316,7.83197,17.49316,17.49319h0c0,9.66122-7.83194,17.49319-17.49316,17.49319H128.17493c-9.66122,0-17.49318-7.83197-17.49318-17.49319h0c0-9.66122,7.83196-17.49319,17.49318-17.49319Z\"\n      fill=\"#fff\"\n    />\n    <path\n      d=\"M128.17493,314.41812H422.98542c9.66122,0,17.49316,7.83197,17.49316,17.49319h0c0,9.66122-7.83194,17.49319-17.49316,17.49319H128.17493c-9.66122,0-17.49318-7.83197-17.49318-17.49319h0c0-9.66122,7.83196-17.49319,17.49318-17.49319Z\"\n      fill=\"#fff\"\n    />\n    <path\n      d=\"M91.64085,657.75932l-.69385-.06793c-23.54068-2.42871-44.82135-15.08929-58.18845-34.61835-3.66138-5.44159-6.62299-11.32251-8.815-17.50409l-.21069-.58966,.62375-.05048c7.44699-.59924,15.09732-1.86292,18.49585-2.46417l-21.91473-7.42511-.1355-.65033c-1.29926-6.10406,1.24612-12.38458,6.4285-15.86176,5.19641-3.64447,12.08731-3.76111,17.40405-.29449,2.38599,1.52399,4.88162,3.03339,7.29489,4.49359,8.29321,5.01636,16.8688,10.20337,23.29828,17.30121,9.74951,10.97778,14.02298,25.76984,11.63,40.25562l4.7829,17.47595Z\"\n      fill=\"#f2f2f2\"\n    />\n    <polygon\n      fill=\"#a0616a\"\n      points=\"171.30016 646.86102 182.10017 646.85999 187.23916 605.198 171.29716 605.19897 171.30016 646.86102\"\n    />\n    <path\n      d=\"M170.9192,658.12816l33.21436-.00122v-.41998c-.00049-7.13965-5.78833-12.92737-12.92798-12.92773h-.00079l-6.06702-4.60278-11.3197,4.60345-2.89941,.00012,.00055,13.34814Z\"\n      fill=\"#2f2e41\"\n    />\n    <polygon\n      fill=\"#a0616a\"\n      points=\"84.74116 616.94501 93.38016 623.42603 122.49316 593.185 109.74116 583.61902 84.74116 616.94501\"\n    />\n    <path\n      d=\"M77.67448,625.72966l26.569,19.93188,.25208-.336c4.2843-5.71136,3.12799-13.81433-2.58279-18.09937l-.00064-.00049-2.09079-7.32275-11.81735-3.11102-2.31931-1.73993-8.01019,10.67767Z\"\n      fill=\"#2f2e41\"\n    />\n    <path\n      d=\"M120.64463,451.35271s.59625,16.26422,1.3483,29.30737c.12335,2.13916-4.88821,4.46301-4.75842,6.7901,.08609,1.54395,1.02808,3.04486,1.1156,4.65472,.09235,1.69897-1.20822,3.20282-1.1156,4.95984,.09052,1.71667,1.57422,3.6853,1.66373,5.44244,.96317,18.9093,4.45459,41.54633,.9584,47.87439-1.72299,3.11871-23.68533,46.32446-23.68533,46.32446,0,0,12.23666,18.35498,15.73285,12.23663,4.61771-8.08099,40.20615-45.88745,40.20615-53.10712,0-7.21088,8.23346-61.25323,8.23346-61.25323l5.74103,31.98169,2.63239,6.33655-.82715,3.71997,1.70117,5.02045,.09192,4.96838,1.65619,9.22614s-4.98199,71.88159-2.17633,73.88312c2.81439,2.01038,16.44086,5.62018,18.04901,2.01038,1.59955-3.6098,12.0108-75.01947,12.0108-75.01947,0,0,1.6781-32.72424,3.49622-63.14111,.1048-1.76556,1.34607-3.89825,1.4422-5.63763,.11365-2.01898-.67297-4.64111-.56818-6.599,.11365-2.24628,1.11005-3.82831,1.20618-5.97852,.74292-16.6156-3.42761-36.84912-4.7561-38.84192-4.01202-6.01343-7.62177-10.82074-7.62177-10.82074,0,0-54.03558-17.75403-68.47485,.28625l-3.30185,25.37585Z\"\n      fill=\"#2f2e41\"\n    />\n    <path\n      d=\"M174.53779,284.10378l-21.4209-4.28418-9.9964,13.56656h0c-18.65262,18.34058-18.93359,34.52753-15.60379,60.47382v36.41553l-2.41,24.41187s-8.53156,17.84521,.26788,22.00006,66.59857,3.80066,72.117,2.14209,.73517-3.69482-.71399-11.4245c-2.72211-14.51929-.90131-7.51562-.71399-12.13849,2.68585-66.31363-3.57013-93.5379-4.20544-100.69376l-10.89398-19.75858-6.42639-10.71042Z\"\n      fill=\"#3f3d56\"\n    />\n    <path\n      d=\"M287.43909,337.57097c-2.23248,4.23007-7.47144,5.84943-11.70148,3.61694-.45099-.23804-.88013-.51541-1.28229-.82895l-46.26044,29.37308,.13336-15.9924,44.93842-26.07846c3.20093-3.58887,8.70514-3.90332,12.29401-.70239,3.00305,2.67844,3.7796,7.0657,1.87842,10.61218Z\"\n      fill=\"#a0616a\"\n    />\n    <path\n      d=\"M157.62488,302.62425l-5.26666-.55807c-4.86633-.50473-9.64093,1.57941-12.57947,5.491-1.12549,1.48346-1.9339,3.18253-2.37491,4.99164l-.00317,.01447c-1.32108,5.44534,.75095,11.15201,5.25803,14.48117l18.19031,13.41101c12.76544,17.24899,36.75653,28.69272,64.89832,37.98978l43.74274-27.16666-15.47186-18.73843-30.00336,16.0798-44.59833-34.52374-.0257-.02075-16.97424-10.936-4.79169-.5152Z\"\n      fill=\"#3f3d56\"\n    />\n    <circle cx=\"167.29993\" cy=\"248.60526\" fill=\"#a0616a\" r=\"24.9798\" />\n    <path\n      d=\"M167.8769,273.59047c-.20135,.00662-.4032,.01108-.6048,.01657-.0863,.22388-.17938,.44583-.2868,.66357l.8916-.68015Z\"\n      fill=\"#2f2e41\"\n    />\n    <path\n      d=\"M174.73243,249.29823c.03918,.24612,.09912,.48846,.17914,.72449-.03302-.24731-.09308-.49026-.17914-.72449Z\"\n      fill=\"#2f2e41\"\n    />\n    <path\n      d=\"M192.59852,224.6942c-1.0282,3.19272-1.94586-.85715-5.32825-.12869-4.06885,.87625-8.80377,.57532-12.13586-1.91879-4.96478-3.64273-11.39874-4.62335-17.22333-2.62509-5.70154,2.01706-15.25348,3.43933-16.73907,9.30179-.51642,2.03781-.7215,4.24933-1.97321,5.9382-1.09436,1.47662-2.82166,2.31854-4.26608,3.45499-4.87726,3.83743-1.14954,14.73981,1.15881,20.50046,2.30838,5.76065,7.60355,9.95721,13.42526,12.10678,5.63281,2.07977,11.7464,2.44662,17.75531,2.28317,1.04517-2.7106,.59363-5.84137-.26874-8.65134-.93359-3.04199-2.31592-5.97791-2.70593-9.13599s.46643-6.74527,3.11444-8.50986c2.4339-1.62192,6.39465-.63388,7.32062,1.98843-.54028-3.27841,2.7807-6.4509,6.20508-7.00882,3.67651-.599,7.35291,.72833,11.01886,1.38901s2.36475-14.77301,.64209-18.98425Z\"\n      fill=\"#2f2e41\"\n    />\n    <circle\n      cx=\"281.3585\"\n      cy=\"285.71051\"\n      fill=\"hsl(var(--primary))\"\n      r=\"51.12006\"\n      transform=\"translate(-26.58509 542.54478) rotate(-85.26884)\"\n    />\n    <path\n      d=\"M294.78675,264.41051l-13.42828,13.42828-13.42828-13.42828c-2.17371-2.17374-5.69806-2.17374-7.87177,0s-2.17371,5.69803,0,7.87177l13.42828,13.42828-13.42828,13.42828c-2.17169,2.17575-2.1684,5.70007,.00739,7.87177,2.17285,2.16879,5.69153,2.16879,7.86438-.00003l13.42828-13.42828,13.42828,13.42828c2.17578,2.17169,5.70007,2.1684,7.87177-.00735,2.16882-2.17288,2.16882-5.6915,0-7.86438l-13.42828-13.42828,13.42828-13.42828c2.17371-2.17374,2.17371-5.69803,0-7.87177s-5.69806-2.17374-7.87177,0h0Z\"\n      fill=\"#fff\"\n    />\n    <path\n      d=\"M261.21387,242.74385c1.5069,4.53946-.95154,9.44101-5.49097,10.94791-.48401,.16064-.9812,.27823-1.4859,.35141l-10.83051,53.71692-11.44788-11.16785,12.29266-50.48209c-.37366-4.7944,3.21008-8.98395,8.00452-9.3576,4.01166-.31265,7.71509,2.16425,8.95807,5.9913Z\"\n      fill=\"#a0616a\"\n    />\n    <path\n      d=\"M146.12519,312.22478l-4.04883,3.41412c-3.73322,3.16214-5.53476,8.05035-4.74649,12.87888,.29129,1.83917,.95773,3.59879,1.95786,5.16949l.00824,.0123c3.01477,4.72311,8.5672,7.17865,14.08978,6.23117l22.27075-3.84171c21.28461,2.72995,46.15155-6.65967,72.34302-20.53055l10.67969-50.37274-24.23297-1.80811-9.16821,32.78271-55.78815,8.28149-.03278,.00415-19.64294,4.67767-3.68896,3.1011Z\"\n      fill=\"#3f3d56\"\n    />\n    <path\n      d=\"M272.93684,658.99046l-271.75,.30731c-.65759-.00214-1.18896-.53693-1.18683-1.19452,.00211-.6546,.53223-1.18469,1.18683-1.18683l271.75-.30731c.65759,.00214,1.18896,.53693,1.18683,1.19452-.00208,.6546-.53223,1.18469-1.18683,1.18683Z\"\n      fill=\"#cacaca\"\n    />\n    <g>\n      <ellipse\n        cx=\"56.77685\"\n        cy=\"82.05834\"\n        fill=\"#3f3d56\"\n        rx=\"8.45661\"\n        ry=\"8.64507\"\n      />\n      <ellipse\n        cx=\"85.9906\"\n        cy=\"82.05834\"\n        fill=\"#3f3d56\"\n        rx=\"8.45661\"\n        ry=\"8.64507\"\n      />\n      <ellipse\n        cx=\"115.20435\"\n        cy=\"82.05834\"\n        fill=\"#3f3d56\"\n        rx=\"8.45661\"\n        ry=\"8.64507\"\n      />\n      <path\n        d=\"M148.51577,88.89113c-.25977,0-.51904-.10059-.71484-.30078l-5.70605-5.83301c-.38037-.38867-.38037-1.00977,0-1.39844l5.70605-5.83252c.38721-.39453,1.021-.40088,1.41406-.01562,.39502,.38623,.40186,1.01953,.01562,1.41406l-5.02197,5.1333,5.02197,5.13379c.38623,.39453,.37939,1.02783-.01562,1.41406-.19434,.19043-.44678,.28516-.69922,.28516Z\"\n        fill=\"#3f3d56\"\n      />\n      <path\n        d=\"M158.10415,88.89113c-.25244,0-.50488-.09473-.69922-.28516-.39502-.38623-.40186-1.01904-.01562-1.41406l5.02148-5.13379-5.02148-5.1333c-.38623-.39453-.37939-1.02783,.01562-1.41406,.39404-.38672,1.02783-.37939,1.41406,.01562l5.70557,5.83252c.38037,.38867,.38037,1.00977,0,1.39844l-5.70557,5.83301c-.1958,.2002-.45508,.30078-.71484,.30078Z\"\n        fill=\"#3f3d56\"\n      />\n      <path\n        d=\"M456.61398,74.41416h-10.60999c-1.21002,0-2.19,.97998-2.19,2.19v10.62c0,1.21002,.97998,2.19,2.19,2.19h10.60999c1.21002,0,2.20001-.97998,2.20001-2.19v-10.62c0-1.21002-.98999-2.19-2.20001-2.19Z\"\n        fill=\"#3f3d56\"\n      />\n      <path\n        d=\"M430.61398,74.41416h-10.60999c-1.21002,0-2.19,.97998-2.19,2.19v10.62c0,1.21002,.97998,2.19,2.19,2.19h10.60999c1.21002,0,2.20001-.97998,2.20001-2.19v-10.62c0-1.21002-.98999-2.19-2.20001-2.19Z\"\n        fill=\"#3f3d56\"\n      />\n      <path\n        d=\"M481.11398,74.91416h-10.60999c-1.21002,0-2.19,.97998-2.19,2.19v10.62c0,1.21002,.97998,2.19,2.19,2.19h10.60999c1.21002,0,2.20001-.97998,2.20001-2.19v-10.62c0-1.21002-.98999-2.19-2.20001-2.19Z\"\n        fill=\"#3f3d56\"\n      />\n      <path\n        d=\"M321.19229,78.95414h-84.81c-1.48004,0-2.67004,1.20001-2.67004,2.67004s1.19,2.66998,2.67004,2.66998h84.81c1.46997,0,2.66998-1.20001,2.66998-2.66998s-1.20001-2.67004-2.66998-2.67004Z\"\n        fill=\"#3f3d56\"\n      />\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/fallback/icons/icon-404.vue",
    "content": "<template>\n  <svg\n    height=\"571\"\n    viewBox=\"0 0 860 571\"\n    width=\"860\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n  >\n    <path\n      d=\"M605.66974,324.95306c-7.66934-12.68446-16.7572-26.22768-30.98954-30.36953-16.482-4.7965-33.4132,4.73193-47.77473,14.13453a1392.15692,1392.15692,0,0,0-123.89338,91.28311l.04331.49238q46.22556-3.1878,92.451-6.37554c22.26532-1.53546,45.29557-3.2827,64.97195-13.8156,7.46652-3.99683,14.74475-9.33579,23.20555-9.70782,10.51175-.46217,19.67733,6.87923,26.8802,14.54931,42.60731,45.371,54.937,114.75409,102.73817,154.61591A1516.99453,1516.99453,0,0,0,605.66974,324.95306Z\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M867.57068,709.78146c-4.71167-5.94958-6.6369-7.343-11.28457-13.34761q-56.7644-73.41638-106.70791-151.79237-33.92354-53.23-64.48275-108.50439-14.54864-26.2781-28.29961-52.96872-10.67044-20.6952-20.8646-41.63793c-1.94358-3.98782-3.8321-7.99393-5.71122-12.00922-4.42788-9.44232-8.77341-18.93047-13.43943-28.24449-5.31686-10.61572-11.789-21.74485-21.55259-28.877a29.40493,29.40493,0,0,0-15.31855-5.89458c-7.948-.51336-15.28184,2.76855-22.17568,6.35295-50.43859,26.301-97.65922,59.27589-140.3696,96.79771A730.77816,730.77816,0,0,0,303.32241,496.24719c-1.008,1.43927-3.39164.06417-2.37419-1.38422q6.00933-8.49818,12.25681-16.81288A734.817,734.817,0,0,1,500.80465,303.06436q18.24824-11.82581,37.18269-22.54245c6.36206-3.60275,12.75188-7.15967,19.25136-10.49653,6.37146-3.27274,13.13683-6.21547,20.41563-6.32547,24.7701-.385,37.59539,27.66695,46.40506,46.54248q4.15283,8.9106,8.40636,17.76626,16.0748,33.62106,33.38729,66.628,10.68453,20.379,21.83683,40.51955,34.7071,62.71816,73.77854,122.897c34.5059,53.1429,68.73651,100.08874,108.04585,149.78472C870.59617,709.21309,868.662,711.17491,867.57068,709.78146Z\"\n      fill=\"#e4e4e4\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M414.91613,355.804c-1.43911-1.60428-2.86927-3.20856-4.31777-4.81284-11.42244-12.63259-23.6788-25.11847-39.3644-32.36067a57.11025,57.11025,0,0,0-23.92679-5.54622c-8.56213.02753-16.93178,2.27348-24.84306,5.41792-3.74034,1.49427-7.39831,3.1902-11.00078,4.99614-4.11634,2.07182-8.15927,4.28118-12.1834,6.50883q-11.33112,6.27044-22.36816,13.09089-21.9606,13.57221-42.54566,29.21623-10.67111,8.11311-20.90174,16.75788-9.51557,8.03054-18.64618,16.492c-1.30169,1.20091-3.24527-.74255-1.94358-1.94347,1.60428-1.49428,3.22691-2.97938,4.84955-4.44613q6.87547-6.21546,13.9712-12.19257,12.93921-10.91827,26.54851-20.99312,21.16293-15.67614,43.78288-29.22541,11.30361-6.76545,22.91829-12.96259c2.33794-1.24675,4.70318-2.466,7.09572-3.6211a113.11578,113.11578,0,0,1,16.86777-6.86632,60.0063,60.0063,0,0,1,25.476-2.50265,66.32706,66.32706,0,0,1,23.50512,8.1314c15.40091,8.60812,27.34573,21.919,38.97,34.90915C418.03337,355.17141,416.09875,357.12405,414.91613,355.804Z\"\n      fill=\"#e4e4e4\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M730.47659,486.71092l36.90462-13.498,18.32327-6.70183c5.96758-2.18267,11.92082-4.66747,18.08988-6.23036a28.53871,28.53871,0,0,1,16.37356.20862,37.73753,37.73753,0,0,1,12.771,7.91666,103.63965,103.63965,0,0,1,10.47487,11.18643c3.98932,4.79426,7.91971,9.63877,11.86772,14.46706q24.44136,29.89094,48.56307,60.04134,24.12117,30.14991,47.91981,60.556,23.85681,30.48041,47.38548,61.21573,2.88229,3.76518,5.75966,7.53415c1.0598,1.38809,3.44949.01962,2.37472-1.38808Q983.582,650.9742,959.54931,620.184q-24.09177-30.86383-48.51647-61.46586-24.42421-30.60141-49.17853-60.93743-6.16706-7.55761-12.35445-15.09858c-3.47953-4.24073-6.91983-8.52718-10.73628-12.47427-7.00539-7.24516-15.75772-13.64794-26.23437-13.82166-6.15972-.10214-12.121,1.85248-17.844,3.92287-6.16968,2.232-12.32455,4.50571-18.48633,6.75941l-37.16269,13.59243-9.29067,3.3981c-1.64875.603-.93651,3.2619.73111,2.652Z\"\n      fill=\"#e4e4e4\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M366.37741,334.52609c-18.75411-9.63866-42.77137-7.75087-60.00508,4.29119a855.84708,855.84708,0,0,1,97.37056,22.72581C390.4603,353.75916,380.07013,341.5635,366.37741,334.52609Z\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M306.18775,338.7841l-3.61042,2.93462c1.22123-1.02713,2.4908-1.99013,3.795-2.90144C306.31073,338.80665,306.24935,338.79473,306.18775,338.7841Z\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M831.54929,486.84576c-3.6328-4.42207-7.56046-9.05222-12.99421-10.84836l-5.07308.20008A575.436,575.436,0,0,0,966.74929,651.418Q899.14929,569.13192,831.54929,486.84576Z\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M516.08388,450.36652A37.4811,37.4811,0,0,0,531.015,471.32518c2.82017,1.92011,6.15681,3.76209,7.12158,7.03463a8.37858,8.37858,0,0,1-.87362,6.1499,24.88351,24.88351,0,0,1-3.86126,5.04137l-.13667.512c-6.99843-4.14731-13.65641-9.3934-17.52227-16.55115s-4.40553-16.53895.34116-23.14544\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M749.08388,653.36652A37.4811,37.4811,0,0,0,764.015,674.32518c2.82017,1.92011,6.15681,3.76209,7.12158,7.03463a8.37858,8.37858,0,0,1-.87362,6.1499,24.88351,24.88351,0,0,1-3.86126,5.04137l-.13667.512c-6.99843-4.14731-13.65641-9.3934-17.52227-16.55115s-4.40553-16.53895.34116-23.14544\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M284.08388,639.36652A37.4811,37.4811,0,0,0,299.015,660.32518c2.82017,1.92011,6.15681,3.76209,7.12158,7.03463a8.37858,8.37858,0,0,1-.87362,6.1499,24.88351,24.88351,0,0,1-3.86126,5.04137l-.13667.512c-6.99843-4.14731-13.65641-9.3934-17.52227-16.55115s-4.40553-16.53895.34116-23.14544\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <circle cx=\"649.24878\" cy=\"51\" fill=\"hsl(var(--primary))\" r=\"51\" />\n    <path\n      d=\"M911.21851,176.29639c-24.7168-3.34094-52.93512,10.01868-59.34131,34.12353a21.59653,21.59653,0,0,0-41.09351,2.10871l2.82972,2.02667a372.27461,372.27461,0,0,0,160.65881-.72638C957.07935,195.76,935.93537,179.63727,911.21851,176.29639Z\"\n      fill=\"#f0f0f0\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M805.21851,244.29639c-24.7168-3.34094-52.93512,10.01868-59.34131,34.12353a21.59653,21.59653,0,0,0-41.09351,2.10871l2.82972,2.02667a372.27461,372.27461,0,0,0,160.65881-.72638C851.07935,263.76,829.93537,247.63727,805.21851,244.29639Z\"\n      fill=\"#f0f0f0\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M1020.94552,257.15423a.98189.98189,0,0,1-.30176-.04688C756.237,173.48919,523.19942,184.42376,374.26388,208.32122c-20.26856,3.251-40.59131,7.00586-60.40381,11.16113-5.05811,1.05957-10.30567,2.19532-15.59668,3.37793-6.31885,1.40723-12.55371,2.85645-18.53223,4.30567q-3.873.917-7.59472,1.84863c-3.75831.92773-7.57178,1.89453-11.65967,2.957-4.56787,1.17774-9.209,2.41309-13.79737,3.67188a.44239.44239,0,0,1-.05127.01465l.00049.001c-5.18261,1.415-10.33789,2.8711-15.32324,4.3252-2.69824.77929-5.30371,1.54785-7.79932,2.30664-.2788.07715-.52587.15136-.77636.22754l-.53614.16308c-.31054.09473-.61718.1875-.92382.27539l-.01953.00586.00048.001-.81152.252c-.96777.293-1.91211.5791-2.84082.86426-24.54492,7.56641-38.03809,12.94922-38.17139,13.00195a1,1,0,1,1-.74414-1.85644c.13428-.05274,13.69336-5.46289,38.32764-13.05762.93213-.28613,1.87891-.57226,2.84961-.86621l.7539-.23438c.02588-.00976.05176-.01757.07813-.02539.30518-.08691.60986-.17968.91943-.27343l.53711-.16309c.26758-.08105.53125-.16113.80127-.23535,2.47852-.75391,5.09278-1.52441,7.79785-2.30664,4.98731-1.45508,10.14746-2.91113,15.334-4.32813.01611-.00586.03271-.00976.04883-.01464v-.001c4.60449-1.2627,9.26269-2.50293,13.84521-3.68457,4.09424-1.06348,7.915-2.03223,11.67969-2.96192q3.73755-.93017,7.60937-1.85253c5.98536-1.45118,12.23291-2.90235,18.563-4.3125,5.29932-1.1836,10.55567-2.32227,15.62207-3.38282,19.84326-4.16211,40.19776-7.92285,60.49707-11.17871C523.09591,182.415,756.46749,171.46282,1021.2463,255.2011a.99974.99974,0,0,1-.30078,1.95313Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M432.92309,584.266a6.72948,6.72948,0,0,0-1.7-2.67,6.42983,6.42983,0,0,0-.92-.71c-2.61-1.74-6.51-2.13-8.99,0a5.81012,5.81012,0,0,0-.69.71q-1.11,1.365-2.28,2.67c-1.28,1.46-2.59,2.87-3.96,4.24-.39.38-.78.77-1.18,1.15-.23.23-.46.45-.69.67-.88.84-1.78,1.65-2.69,2.45-.48.43-.96.85-1.45,1.26-.73.61-1.46,1.22-2.2,1.81-.07.05-.14.1-.21.16-.02.01-.03.03-.05.04-.01,0-.02,0-.03.02a.17861.17861,0,0,0-.07.05c-.22.15-.37.25-.48.34.04-.01995.08-.05.12-.07-.18.14-.37.28-.55.42-1.75,1.29-3.54,2.53-5.37,3.69a99.21022,99.21022,0,0,1-14.22,7.55c-.33.13-.67.27-1.01.4a85.96993,85.96993,0,0,1-40.85,6.02q-2.13008-.165-4.26-.45c-1.64-.24-3.27-.53-4.89-.86a97.93186,97.93186,0,0,1-18.02-5.44,118.65185,118.65185,0,0,1-20.66-12.12c-1-.71-2.01-1.42-3.02-2.11,1.15-2.82,2.28-5.64,3.38-8.48.55-1.37,1.08-2.74,1.6-4.12,4.09-10.63,7.93-21.36,11.61-32.13q5.58-16.365,10.53-32.92.51-1.68.99-3.36,2.595-8.745,4.98-17.53c.15-.56994.31-1.12994.45-1.7q.68994-2.52,1.35-5.04c1-3.79-1.26-8.32-5.24-9.23a7.63441,7.63441,0,0,0-9.22,5.24c-.43,1.62-.86,3.23-1.3,4.85q-3.165,11.74494-6.66,23.41-.51,1.68-1.02,3.36-7.71,25.41-16.93,50.31-1.11,3.015-2.25,6.01c-.37.98-.74,1.96-1.12,2.94-.73,1.93-1.48,3.86-2.23,5.79-.43006,1.13-.87006,2.26-1.31,3.38-.29.71-.57,1.42-.85,2.12a41.80941,41.80941,0,0,0-8.81-2.12l-.48-.06a27.397,27.397,0,0,0-7.01.06,23.91419,23.91419,0,0,0-17.24,10.66c-4.77,7.51-4.71,18.25,1.98,24.63,6.89,6.57,17.32,6.52,25.43,2.41a28.35124,28.35124,0,0,0,10.52-9.86,50.56939,50.56939,0,0,0,2.74-4.65c.21.14.42.28.63.43.8.56,1.6,1.13,2.39,1.69a111.73777,111.73777,0,0,0,14.51,8.91,108.35887,108.35887,0,0,0,34.62,10.47c.27.03.53.07.8.1,1.33.17,2.67.3,4.01.41a103.78229,103.78229,0,0,0,55.58-11.36q2.175-1.125,4.31-2.36,3.315-1.92,6.48-4.08c1.15-.78,2.27-1.57,3.38-2.4a101.04244,101.04244,0,0,0,13.51-11.95q2.35491-2.475,4.51-5.11005a8.0612,8.0612,0,0,0,2.2-5.3A7.5644,7.5644,0,0,0,432.92309,584.266Zm-165.59,23.82c.21-.15.42-.31.62-.47C267.89312,607.766,267.60308,607.936,267.33312,608.086Zm3.21-3.23c-.23.26-.44.52-.67.78a23.36609,23.36609,0,0,1-2.25,2.2c-.11.1-.23.2-.35.29a.00976.00976,0,0,0-.01.01,3.80417,3.80417,0,0,0-.42005.22q-.645.39-1.31994.72a17.00459,17.00459,0,0,1-2.71.75,16.79925,16.79925,0,0,1-2.13.02h-.02a14.82252,14.82252,0,0,1-1.45-.4c-.24-.12-.47-.25994-.7-.4-.09-.08-.17005-.16-.22-.21a2.44015,2.44015,0,0,1-.26995-.29.0098.0098,0,0,0-.01-.01c-.11005-.2-.23005-.4-.34-.6a.031.031,0,0,1-.01-.02c-.08-.25-.15-.51-.21-.77a12.51066,12.51066,0,0,1,.01-1.37,13.4675,13.4675,0,0,1,.54-1.88,11.06776,11.06776,0,0,1,.69-1.26c.02-.04.12-.2.23-.38.01-.01.01-.01.01-.02.15-.17.3-.35.46-.51.27-.3.56-.56.85-.83a18.02212,18.02212,0,0,1,1.75-1.01,19.48061,19.48061,0,0,1,2.93-.79,24.98945,24.98945,0,0,1,4.41.04,30.30134,30.30134,0,0,1,4.1,1.01,36.94452,36.94452,0,0,1-2.77,4.54C270.6231,604.746,270.58312,604.806,270.54308,604.856Zm-11.12-3.29a2.18029,2.18029,0,0,1-.31.38995A1.40868,1.40868,0,0,1,259.42309,601.566Z\"\n      fill=\"hsl(var(--foreground))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M402.86309,482.136q-.13494,4.71-.27,9.42-.285,10.455-.59,20.92-.315,11.775-.66,23.54-.165,6.07507-.34,12.15-.465,16.365-.92,32.72c-.03,1.13-.07,2.25-.1,3.38q-.225,8.11506-.45,16.23-.255,8.805-.5,17.61-.18,6.59994-.37,13.21-1.34994,47.895-2.7,95.79a7.64844,7.64844,0,0,1-7.5,7.5,7.56114,7.56114,0,0,1-7.5-7.5q.75-26.94,1.52-53.88.675-24.36,1.37-48.72.225-8.025.45-16.06.345-12.09.68-24.18c.03-1.13.07-2.25.1-3.38.02-.99.05-1.97.08-2.96q.66-23.475,1.32-46.96.27-9.24.52-18.49.3-10.545.6-21.08c.09-3.09.17005-6.17.26-9.26a7.64844,7.64844,0,0,1,7.5-7.5A7.56116,7.56116,0,0,1,402.86309,482.136Z\"\n      fill=\"hsl(var(--foreground))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M814.29118,484.2172a893.23753,893.23753,0,0,1-28.16112,87.94127c-3.007,7.94641-6.08319,15.877-9.3715,23.71185l.75606-1.7916a54.58274,54.58274,0,0,1-5.58953,10.61184q-.22935.32119-.46685.63642,1.16559-1.49043.4428-.589c-.25405.30065-.5049.60219-.7676.89546a23.66436,23.66436,0,0,1-2.2489,2.20318q-.30139.25767-.61188.5043l.93783-.729c-.10884.25668-.87275.59747-1.11067.74287a18.25362,18.25362,0,0,1-2.40479,1.21853l1.7916-.75606a19.0859,19.0859,0,0,1-4.23122,1.16069l1.9938-.26791a17.02055,17.02055,0,0,1-4.29785.046l1.99379.2679a14.0022,14.0022,0,0,1-3.40493-.917l1.79159.75606a12.01175,12.01175,0,0,1-1.67882-.89614c-.27135-.17688-1.10526-.80852-.01487.02461,1.13336.86595.14562.07434-.08763-.15584-.19427-.19171-.36962-.4-.55974-.595-.88208-.90454.99637,1.55662.39689.49858a18.18179,18.18179,0,0,1-.87827-1.63672l.75606,1.7916a11.92493,11.92493,0,0,1-.728-2.65143l.26791,1.9938a13.65147,13.65147,0,0,1-.00316-3.40491l-.2679,1.9938a15.96371,15.96371,0,0,1,.99486-3.68011l-.75606,1.7916a16.72914,16.72914,0,0,1,1.17794-2.29848,6.72934,6.72934,0,0,1,.72851-1.0714c.04915.01594-1.26865,1.51278-.56937.757.1829-.19767.354-.40592.539-.602.29617-.31382.61354-.60082.92561-.89791,1.04458-.99442-1.46188.966-.25652.17907a19.0489,19.0489,0,0,1,2.74925-1.49923l-1.79159.75606a20.31136,20.31136,0,0,1,4.99523-1.33984l-1.9938.2679a25.62828,25.62828,0,0,1,6.46062.07647l-1.9938-.2679a33.21056,33.21056,0,0,1,7.89178,2.2199l-1.7916-.75606c5.38965,2.31383,10.16308,5.74926,14.928,9.118a111.94962,111.94962,0,0,0,14.50615,8.9065,108.38849,108.38849,0,0,0,34.62226,10.47371,103.93268,103.93268,0,0,0,92.58557-36.75192,8.07773,8.07773,0,0,0,2.1967-5.3033,7.63232,7.63232,0,0,0-2.1967-5.3033c-2.75154-2.52586-7.94926-3.239-10.6066,0a95.63575,95.63575,0,0,1-8.10664,8.72692q-2.01736,1.914-4.14232,3.70983-1.21364,1.02588-2.46086,2.01121c-.3934.31081-1.61863,1.13807.26309-.19744-.43135.30614-.845.64036-1.27058.95478a99.26881,99.26881,0,0,1-20.33215,11.56478l1.79159-.75606a96.8364,96.8364,0,0,1-24.17119,6.62249l1.99379-.2679a97.64308,97.64308,0,0,1-25.75362-.03807l1.99379.2679a99.79982,99.79982,0,0,1-24.857-6.77027l1.7916.75607a116.02515,116.02515,0,0,1-21.7364-12.59112,86.87725,86.87725,0,0,0-11.113-6.99417,42.8238,42.8238,0,0,0-14.43784-4.38851c-9.43884-1.11076-19.0571,2.56562-24.24624,10.72035-4.77557,7.50482-4.71394,18.24362,1.97369,24.62519,6.8877,6.5725,17.31846,6.51693,25.43556,2.40567,7.81741-3.95946,12.51288-12.18539,15.815-19.94186,7.43109-17.45514,14.01023-35.31364,20.1399-53.263q9.09651-26.63712,16.49855-53.81332.91661-3.36581,1.80683-6.73869c1.001-3.78869-1.26094-8.32-5.23829-9.22589a7.63317,7.63317,0,0,0-9.22589,5.23829Z\"\n      fill=\"hsl(var(--foreground))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M889.12382,482.13557l-2.69954,95.79311-2.68548,95.29418-1.5185,53.88362a7.56465,7.56465,0,0,0,7.5,7.5,7.64923,7.64923,0,0,0,7.5-7.5l2.69955-95.79311,2.68548-95.29418,1.51849-53.88362a7.56465,7.56465,0,0,0-7.5-7.5,7.64923,7.64923,0,0,0-7.5,7.5Z\"\n      fill=\"hsl(var(--foreground))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M629.52566,700.36106h2.32885V594.31942h54.32863v-2.32291H631.85451V547.25214H673.8102q-.92256-1.17339-1.89893-2.31694H631.85451V515.38231c-.7703-.32846-1.54659-.64493-2.32885-.9435V544.9352h-45.652V507.07c-.78227.03583-1.55258.08959-2.3289.15527v37.71h-36.4201V516.68409c-.78227.34636-1.55258.71061-2.31694,1.0928V544.9352h-30.6158v2.31694h30.6158v44.74437h-30.6158v2.32291h30.6158V700.36106h2.31694V594.31942a36.41283,36.41283,0,0,1,36.4201,36.42007v69.62157h2.3289V594.31942h45.652Zm-84.401-108.36455V547.25214h36.4201v44.74437Zm38.749,0V547.25214h.91362a44.74135,44.74135,0,0,1,44.73842,44.74437Z\"\n      opacity=\"0.2\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M615.30309,668.566a63.05854,63.05854,0,0,1-20.05,33.7c-.74.64-1.48,1.26-2.25,1.87q-2.805.25506-5.57.52c-1.53.14-3.04.29-4.54.43l-.27.03-.19-1.64-.76-6.64a37.623,37.623,0,0,1-3.3-32.44c2.64-7.12,7.42-13.41,12.12-19.65,6.49-8.62,12.8-17.14,13.03-27.65a60.54415,60.54415,0,0,1,7.9,13.33,16.432,16.432,0,0,0-5.12,3.76995c-.41.45-.82,1.08-.54,1.62006.24.46.84.57,1.36.62994,1.25.13,2.51.26,3.76.39,1,.11,2,.21,3,.32a63.99025,63.99025,0,0,1,2.45,12.18A61.18851,61.18851,0,0,1,615.30309,668.566Z\"\n      fill=\"hsl(var(--foreground))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M648.50311,642.356c-5.9,4.29-9.35,10.46-12.03,17.26a16.62776,16.62776,0,0,0-7.17,4.58c-.41.45-.82,1.08-.54,1.62006.24.46.84.57,1.36.62994,1.25.13,2.51.26,3.76.39-2.68,8.04-5.14,16.36-9.88,23.15a36.98942,36.98942,0,0,1-12.03,10.91,38.49166,38.49166,0,0,1-4.02,1.99q-7.62.585-14.95,1.25-2.805.25506-5.57.52c-1.53.14-3.04.29-4.54.43q-.015-.825,0-1.65a63.30382,63.30382,0,0,1,15.25-39.86c.45-.52.91-1.03,1.38-1.54a61.7925,61.7925,0,0,1,16.81-12.7A62.65425,62.65425,0,0,1,648.50311,642.356Z\"\n      fill=\"hsl(var(--primary))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M589.16308,699.526l-1.15,3.4-.58,1.73c-1.53.14-3.04.29-4.54.43l-.27.03c-1.66.17-3.31.34-4.96.51-.43-.5-.86-1.01-1.28-1.53a62.03045,62.03045,0,0,1,8.07-87.11c-1.32,6.91.22,13.53,2.75,20.1-.27.11-.53.22-.78.34a16.432,16.432,0,0,0-5.12,3.76995c-.41.45-.82,1.08-.54,1.62006.24.46.84.57,1.36.62994,1.25.13,2.51.26,3.76.39,1,.11,2,.21,3,.32q.705.075,1.41.15c.07.15.13.29.2.44,2.85,6.18,5.92,12.39,7.65,18.83a43.66591,43.66591,0,0,1,1.02,4.91A37.604,37.604,0,0,1,589.16308,699.526Z\"\n      fill=\"hsl(var(--primary))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M689.82123,554.48655c-8.60876-16.79219-21.94605-30.92088-37.63219-41.30357a114.2374,114.2374,0,0,0-52.5626-18.37992q-3.69043-.33535-7.399-.39281c-2.92141-.04371-46.866,12.63176-61.58712,22.98214a114.29462,114.29462,0,0,0-35.333,39.527,102.49972,102.49972,0,0,0-12.12557,51.6334,113.56387,113.56387,0,0,0,14.70268,51.47577,110.47507,110.47507,0,0,0,36.44425,38.74592C549.66655,708.561,565.07375,734.51,583.1831,735.426c18.24576.923,39.05418-23.55495,55.6951-30.98707a104.42533,104.42533,0,0,0,41.72554-34.005,110.24964,110.24964,0,0,0,19.599-48.94777c2.57368-18.08313,1.37415-36.73271-4.80123-54.01627a111.85969,111.85969,0,0,0-5.58024-12.9833c-1.77961-3.50519-6.996-4.7959-10.26142-2.69063a7.67979,7.67979,0,0,0-2.69064,10.26142q1.56766,3.08773,2.91536,6.27758l-.75606-1.7916a101.15088,101.15088,0,0,1,6.87641,25.53816l-.26791-1.99379a109.2286,109.2286,0,0,1-.06613,28.68252l.26791-1.9938a109.73379,109.73379,0,0,1-7.55462,27.67419l.75606-1.79159a104.212,104.212,0,0,1-6.67151,13.09835q-1.92308,3.18563-4.08062,6.22159c-.63172.8881-1.28287,1.761-1.939,2.63114-.85625,1.13555,1.16691-1.48321.28228-.36941-.15068.18972-.30049.3801-.45182.5693q-.68121.85165-1.3818,1.68765a93.61337,93.61337,0,0,1-10.17647,10.38359q-1.36615,1.19232-2.77786,2.33115c-.46871.37832-.932.77269-1.42079,1.12472.01861-.0134,1.57956-1.19945.65556-.511-.2905.21644-.57851.43619-.86961.65184q-2.90994,2.1558-5.97433,4.092a103.48509,103.48509,0,0,1-14.75565,7.7131l1.7916-.75606a109.21493,109.21493,0,0,1-27.59663,7.55154l1.9938-.26791a108.15361,108.15361,0,0,1-28.58907.0506l1.99379.2679a99.835,99.835,0,0,1-25.09531-6.78448l1.79159.75607a93.64314,93.64314,0,0,1-13.41605-6.99094q-3.17437-2-6.18358-4.24743c-.2862-.21359-.56992-.43038-.855-.64549-.9155-.69088.65765.50965.67021.51787a19.16864,19.16864,0,0,1-1.535-1.22469q-1.45353-1.18358-2.86136-2.4218a101.98931,101.98931,0,0,1-10.49319-10.70945q-1.21308-1.43379-2.37407-2.91054c-.33524-.4263-.9465-1.29026.40424.5289-.17775-.23939-.36206-.47414-.54159-.71223q-.64657-.85751-1.27568-1.72793-2.203-3.048-4.18787-6.24586a109.29037,109.29037,0,0,1-7.8054-15.10831l.75606,1.7916a106.58753,106.58753,0,0,1-7.34039-26.837l.26791,1.9938a97.86589,97.86589,0,0,1-.04843-25.63587l-.2679,1.9938A94.673,94.673,0,0,1,505.27587,570.55l-.75606,1.7916a101.55725,101.55725,0,0,1,7.19519-13.85624q2.0655-3.32328,4.37767-6.4847.52528-.71832,1.06244-1.42786c.324-.4279,1.215-1.49333-.30537.38842.14906-.18449.29252-.37428.43942-.56041q1.26882-1.60756,2.59959-3.1649A107.40164,107.40164,0,0,1,530.772,536.21508q1.47408-1.29171,2.99464-2.52906.6909-.56218,1.39108-1.11284c.18664-.14673.37574-.29073.56152-.43858-1.99743,1.58953-.555.43261-.10157.09288q3.13393-2.34833,6.43534-4.46134a103.64393,103.64393,0,0,1,15.38655-8.10791l-1.7916.75606c7.76008-3.25839,42.14086-10.9492,48.394-10.10973l-1.99379-.26791A106.22471,106.22471,0,0,1,628.768,517.419l-1.7916-.75606a110.31334,110.31334,0,0,1,12.6002,6.32922q3.04344,1.78405,5.96742,3.76252,1.38351.93658,2.73809,1.915.677.48917,1.34626.98885c.24789.185.49386.37253.74135.558,1.03924.779-1.43148-1.1281-.34209-.26655a110.84261,110.84261,0,0,1,10.36783,9.2532q2.401,2.445,4.63686,5.04515,1.14659,1.33419,2.24643,2.70757c.36436.45495,1.60506,2.101.08448.08457.37165.49285.74744.98239,1.11436,1.47884a97.97718,97.97718,0,0,1,8.39161,13.53807c1.79317,3.49775,6.98675,4.80186,10.26142,2.69064A7.67666,7.67666,0,0,0,689.82123,554.48655Z\"\n      fill=\"hsl(var(--foreground))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M602.43116,676.88167a3.77983,3.77983,0,0,1-2.73939-6.55137c.09531-.37882.16368-.65085.259-1.02968q-.05115-.12366-.1029-.24717c-3.47987-8.29769-25.685,14.83336-26.645,22.63179a30.029,30.029,0,0,0,.52714,10.32752A120.39223,120.39223,0,0,1,562.77838,652.01a116.20247,116.20247,0,0,1,.72078-12.96332q.59712-5.293,1.65679-10.51055a121.78667,121.78667,0,0,1,24.1515-51.61646c6.87378.38364,12.898-.66348,13.47967-13.98532.10346-2.36972,1.86113-4.42156,2.24841-6.756-.65621.08607-1.32321.13985-1.97941.18285-.20444.0107-.41958.02149-.624.03228l-.07709.00346a3.745,3.745,0,0,1-3.07566-6.10115q.425-.52305.85054-1.04557c.43036-.53793.87143-1.06507,1.30171-1.60292a1.865,1.865,0,0,0,.13986-.16144c.49494-.61322.98971-1.21564,1.48465-1.82885a10.82911,10.82911,0,0,0-3.55014-3.43169c-4.95941-2.90463-11.80146-.89293-15.38389,3.59313-3.59313,4.486-4.27083,10.77947-3.023,16.3843a43.39764,43.39764,0,0,0,6.003,13.3828c-.269.34429-.54872.67779-.81765,1.02209a122.57366,122.57366,0,0,0-12.79359,20.2681c1.0163-7.93863-11.41159-36.60795-16.21776-42.68052-5.773-7.29409-17.61108-4.11077-18.62815,5.13562q-.01476.13428-.02884.26849,1.07082.60411,2.0964,1.28237a5.12707,5.12707,0,0,1-2.06713,9.33031l-.10452.01613c-9.55573,13.64367,21.07745,49.1547,28.74518,41.18139a125.11045,125.11045,0,0,0-6.73449,31.69282,118.66429,118.66429,0,0,0,.08607,19.15986l-.03231-.22593C558.90163,648.154,529.674,627.51374,521.139,629.233c-4.91675.99041-9.75952.76525-9.01293,5.72484q.01788.11874.03635.2375a34.4418,34.4418,0,0,1,3.862,1.86105q1.07082.60423,2.09639,1.28237a5.12712,5.12712,0,0,1-2.06712,9.33039l-.10464.01606c-.07528.01079-.13987.02157-.21507.03237-4.34967,14.96631,27.90735,39.12,47.5177,31.43461h.01081a125.07484,125.07484,0,0,0,8.402,24.52806H601.679c.10765-.3335.20443-.67779.3013-1.01129a34.102,34.102,0,0,1-8.30521-.49477c2.22693-2.73257,4.45377-5.48664,6.6807-8.21913a1.86122,1.86122,0,0,0,.13986-.16135c1.12956-1.39849,2.26992-2.78627,3.39948-4.18476l.00061-.00173a49.95232,49.95232,0,0,0-1.46367-12.72495Zm-34.37066-67.613.0158-.02133-.0158.04282Zm-6.64832,59.93237-.25822-.58084c.01079-.41957.01079-.83914,0-1.26942,0-.11845-.0215-.23672-.0215-.35508.09678.74228.18285,1.48464.29042,2.22692Z\"\n      fill=\"hsl(var(--foreground))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <circle cx=\"95.24878\" cy=\"439\" fill=\"hsl(var(--foreground))\" r=\"11\" />\n    <circle cx=\"227.24878\" cy=\"559\" fill=\"hsl(var(--foreground))\" r=\"11\" />\n    <circle cx=\"728.24878\" cy=\"559\" fill=\"hsl(var(--foreground))\" r=\"11\" />\n    <circle cx=\"755.24878\" cy=\"419\" fill=\"hsl(var(--foreground))\" r=\"11\" />\n    <circle cx=\"723.24878\" cy=\"317\" fill=\"hsl(var(--foreground))\" r=\"11\" />\n    <path\n      d=\"M434.1831,583.426a10.949,10.949,0,1,1-.21-2.16A10.9921,10.9921,0,0,1,434.1831,583.426Z\"\n      fill=\"hsl(var(--foreground))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <circle cx=\"484.24878\" cy=\"349\" fill=\"hsl(var(--foreground))\" r=\"11\" />\n    <path\n      d=\"M545.1831,513.426a10.949,10.949,0,1,1-.21-2.16A10.9921,10.9921,0,0,1,545.1831,513.426Z\"\n      fill=\"hsl(var(--foreground))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <path\n      d=\"M403.1831,481.426a10.949,10.949,0,1,1-.21-2.16A10.9921,10.9921,0,0,1,403.1831,481.426Z\"\n      fill=\"hsl(var(--foreground))\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n    <circle cx=\"599.24878\" cy=\"443\" fill=\"hsl(var(--foreground))\" r=\"11\" />\n    <circle cx=\"426.24878\" cy=\"338\" fill=\"hsl(var(--foreground))\" r=\"16\" />\n    <path\n      d=\"M1028.875,735.26666l-857.75.30733a1.19068,1.19068,0,1,1,0-2.38136l857.75-.30734a1.19069,1.19069,0,0,1,0,2.38137Z\"\n      fill=\"#cacaca\"\n      transform=\"translate(-169.93432 -164.42601)\"\n    />\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/fallback/icons/icon-500.vue",
    "content": "<template>\n  <svg\n    height=\"699\"\n    viewBox=\"0 0 1119 699\"\n    width=\"1119\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n  >\n    <title>server down</title>\n    <circle cx=\"292.60911\" cy=\"213\" fill=\"#f2f2f2\" r=\"213\" />\n    <path\n      d=\"M31.39089,151.64237c0,77.49789,48.6181,140.20819,108.70073,140.20819\"\n      fill=\"#2f2e41\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <path\n      d=\"M140.09162,291.85056c0-78.36865,54.255-141.78356,121.30372-141.78356\"\n      fill=\"hsl(var(--primary))\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <path\n      d=\"M70.77521,158.66768c0,73.61476,31.00285,133.18288,69.31641,133.18288\"\n      fill=\"hsl(var(--primary))\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <path\n      d=\"M140.09162,291.85056c0-100.13772,62.7103-181.16788,140.20819-181.16788\"\n      fill=\"#2f2e41\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <path\n      d=\"M117.22379,292.83905s15.41555-.47479,20.06141-3.783,23.713-7.2585,24.86553-1.95278,23.16671,26.38821,5.76263,26.5286-40.43935-2.711-45.07627-5.53549S117.22379,292.83905,117.22379,292.83905Z\"\n      fill=\"#a8a8a8\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <path\n      d=\"M168.224,311.78489c-17.40408.14042-40.43933-2.71094-45.07626-5.53548-3.53126-2.151-4.93843-9.86945-5.40926-13.43043-.32607.014-.51463.02-.51463.02s.97638,12.43276,5.61331,15.2573,27.67217,5.67589,45.07626,5.53547c5.02386-.04052,6.7592-1.82793,6.66391-4.47526C173.87935,310.756,171.96329,311.75474,168.224,311.78489Z\"\n      opacity=\"0.2\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <ellipse cx=\"198.60911\" cy=\"424.5\" fill=\"#3f3d56\" rx=\"187\" ry=\"25.43993\" />\n    <ellipse cx=\"198.60911\" cy=\"424.5\" opacity=\"0.1\" rx=\"157\" ry=\"21.35866\" />\n    <ellipse cx=\"836.60911\" cy=\"660.5\" fill=\"#3f3d56\" rx=\"283\" ry=\"38.5\" />\n    <ellipse cx=\"310.60911\" cy=\"645.5\" fill=\"#3f3d56\" rx=\"170\" ry=\"23.12721\" />\n    <path\n      d=\"M494,726.5c90,23,263-30,282-90\"\n      fill=\"none\"\n      stroke=\"#2f2e41\"\n      stroke-miterlimit=\"10\"\n      stroke-width=\"2\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <path\n      d=\"M341,359.5s130-36,138,80-107,149-17,172\"\n      fill=\"none\"\n      stroke=\"#2f2e41\"\n      stroke-miterlimit=\"10\"\n      stroke-width=\"2\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <path\n      d=\"M215.40233,637.78332s39.0723-10.82,41.47675,24.04449-32.15951,44.78287-5.10946,51.69566\"\n      fill=\"none\"\n      stroke=\"#2f2e41\"\n      stroke-miterlimit=\"10\"\n      stroke-width=\"2\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <path\n      d=\"M810.09554,663.73988,802.218,714.03505s-38.78182,20.60284-11.51335,21.20881,155.73324,0,155.73324,0,24.84461,0-14.54318-21.81478l-7.87756-52.719Z\"\n      fill=\"#2f2e41\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <path\n      d=\"M785.21906,734.69812c6.193-5.51039,16.9989-11.252,16.9989-11.252l7.87756-50.2952,113.9216.10717,7.87756,49.582c9.185,5.08711,14.8749,8.987,18.20362,11.97818,5.05882-1.15422,10.58716-5.44353-18.20362-21.38921l-7.87756-52.719-113.9216,3.02983L802.218,714.03506S769.62985,731.34968,785.21906,734.69812Z\"\n      opacity=\"0.1\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <rect\n      fill=\"#2f2e41\"\n      height=\"357.51989\"\n      rx=\"18.04568\"\n      width=\"513.25314\"\n      x=\"578.43291\"\n      y=\"212.68859\"\n    />\n    <rect\n      fill=\"#3f3d56\"\n      height=\"267.83694\"\n      width=\"478.71308\"\n      x=\"595.70294\"\n      y=\"231.77652\"\n    />\n    <circle cx=\"835.05948\" cy=\"223.29299\" fill=\"#f2f2f2\" r=\"3.02983\" />\n    <path\n      d=\"M1123.07694,621.32226V652.6628a18.04341,18.04341,0,0,1-18.04568,18.04568H627.86949A18.04341,18.04341,0,0,1,609.8238,652.6628V621.32226Z\"\n      fill=\"#2f2e41\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <polygon\n      fill=\"#2f2e41\"\n      points=\"968.978 667.466 968.978 673.526 642.968 673.526 642.968 668.678 643.417 667.466 651.452 645.651 962.312 645.651 968.978 667.466\"\n    />\n    <path\n      d=\"M1125.828,762.03359c-.59383,2.539-2.83591,5.21743-7.90178,7.75032-18.179,9.08949-55.1429-2.42386-55.1429-2.42386s-28.4804-4.84773-28.4804-17.573a22.72457,22.72457,0,0,1,2.49658-1.48459c7.64294-4.04351,32.98449-14.02122,77.9177.42248a18.73921,18.73921,0,0,1,8.54106,5.59715C1125.07908,756.45353,1126.50669,759.15715,1125.828,762.03359Z\"\n      fill=\"#2f2e41\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <path\n      d=\"M1125.828,762.03359c-22.251,8.526-42.0843,9.1622-62.43871-4.975-10.26507-7.12617-19.59089-8.88955-26.58979-8.75618,7.64294-4.04351,32.98449-14.02122,77.9177.42248a18.73921,18.73921,0,0,1,8.54106,5.59715C1125.07908,756.45353,1126.50669,759.15715,1125.828,762.03359Z\"\n      opacity=\"0.1\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <ellipse\n      cx=\"1066.53846\"\n      cy=\"654.13477\"\n      fill=\"#f2f2f2\"\n      rx=\"7.87756\"\n      ry=\"2.42386\"\n    />\n    <circle cx=\"835.05948\" cy=\"545.66686\" fill=\"#f2f2f2\" r=\"11.51335\" />\n    <polygon\n      opacity=\"0.1\"\n      points=\"968.978 667.466 968.978 673.526 642.968 673.526 642.968 668.678 643.417 667.466 968.978 667.466\"\n    />\n    <rect fill=\"#2f2e41\" height=\"242\" width=\"208\" x=\"108.60911\" y=\"159\" />\n    <rect fill=\"#3f3d56\" height=\"86\" width=\"250\" x=\"87.60911\" y=\"135\" />\n    <rect fill=\"#3f3d56\" height=\"86\" width=\"250\" x=\"87.60911\" y=\"237\" />\n    <rect fill=\"#3f3d56\" height=\"86\" width=\"250\" x=\"87.60911\" y=\"339\" />\n    <rect\n      fill=\"#6c63ff\"\n      height=\"16\"\n      opacity=\"0.4\"\n      width=\"16\"\n      x=\"271.60911\"\n      y=\"150\"\n    />\n    <rect\n      fill=\"#6c63ff\"\n      height=\"16\"\n      opacity=\"0.8\"\n      width=\"16\"\n      x=\"294.60911\"\n      y=\"150\"\n    />\n    <rect fill=\"#6c63ff\" height=\"16\" width=\"16\" x=\"317.60911\" y=\"150\" />\n    <rect\n      fill=\"#6c63ff\"\n      height=\"16\"\n      opacity=\"0.4\"\n      width=\"16\"\n      x=\"271.60911\"\n      y=\"251\"\n    />\n    <rect\n      fill=\"#6c63ff\"\n      height=\"16\"\n      opacity=\"0.8\"\n      width=\"16\"\n      x=\"294.60911\"\n      y=\"251\"\n    />\n    <rect fill=\"#6c63ff\" height=\"16\" width=\"16\" x=\"317.60911\" y=\"251\" />\n    <rect\n      fill=\"#6c63ff\"\n      height=\"16\"\n      opacity=\"0.4\"\n      width=\"16\"\n      x=\"271.60911\"\n      y=\"352\"\n    />\n    <rect\n      fill=\"#6c63ff\"\n      height=\"16\"\n      opacity=\"0.8\"\n      width=\"16\"\n      x=\"294.60911\"\n      y=\"352\"\n    />\n    <rect fill=\"#6c63ff\" height=\"16\" width=\"16\" x=\"317.60911\" y=\"352\" />\n    <circle cx=\"316.60911\" cy=\"538\" fill=\"#2f2e41\" r=\"79\" />\n    <rect fill=\"#2f2e41\" height=\"43\" width=\"24\" x=\"280.60911\" y=\"600\" />\n    <rect fill=\"#2f2e41\" height=\"43\" width=\"24\" x=\"328.60911\" y=\"600\" />\n    <ellipse cx=\"300.60911\" cy=\"643.5\" fill=\"#2f2e41\" rx=\"20\" ry=\"7.5\" />\n    <ellipse cx=\"348.60911\" cy=\"642.5\" fill=\"#2f2e41\" rx=\"20\" ry=\"7.5\" />\n    <circle cx=\"318.60911\" cy=\"518\" fill=\"#fff\" r=\"27\" />\n    <circle cx=\"318.60911\" cy=\"518\" fill=\"#3f3d56\" r=\"9\" />\n    <path\n      d=\"M271.36733,565.03228c-6.37889-28.56758,14.01185-57.43392,45.544-64.47477s62.2651,10.41,68.644,38.9776-14.51861,39.10379-46.05075,46.14464S277.74622,593.59986,271.36733,565.03228Z\"\n      fill=\"#6c63ff\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n    <ellipse\n      cx=\"417.21511\"\n      cy=\"611.34365\"\n      fill=\"#2f2e41\"\n      rx=\"39.5\"\n      ry=\"12.40027\"\n      transform=\"translate(-238.28665 112.98044) rotate(-23.17116)\"\n    />\n    <ellipse\n      cx=\"269.21511\"\n      cy=\"664.34365\"\n      fill=\"#2f2e41\"\n      rx=\"39.5\"\n      ry=\"12.40027\"\n      transform=\"translate(-271.07969 59.02084) rotate(-23.17116)\"\n    />\n    <path\n      d=\"M394,661.5c0,7.732-19.90861,23-42,23s-43-14.268-43-22,20.90861-6,43-6S394,653.768,394,661.5Z\"\n      fill=\"#fff\"\n      transform=\"translate(-31.39089 -100.5)\"\n    />\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/fallback/icons/icon-coming-soon.vue",
    "content": "<template>\n  <svg\n    data-name=\"Layer 1\"\n    height=\"424.8366\"\n    viewBox=\"0 0 979.32677 424.8366\"\n    width=\"979.32677\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n  >\n    <path\n      d=\"M993.71816,412.83936H419.142a9.19888,9.19888,0,0,0,0,18.39776H435.417V651.3026a9.19888,9.19888,0,0,0,18.39776,0l.1398-220.06548h461.1557l42.52,220.06548a9.19887,9.19887,0,1,0,18.39775,0l2.67633-220.06548h15.01383a9.19888,9.19888,0,0,0,0-18.39776Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M518.73716,371.85047v38.9547H421.141a19.48915,19.48915,0,1,1-1.35523-38.95474q.67739-.02358,1.35523,0Z\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M521.13449,410.50552a1.49881,1.49881,0,0,1-1.49822,1.49822H419.40273a20.52615,20.52615,0,0,1,0-41.05229H519.63627a1.49827,1.49827,0,1,1,0,2.99653H419.40273a17.52964,17.52964,0,0,0,0,35.05924H519.63627A1.49883,1.49883,0,0,1,521.13449,410.50552Z\"\n      fill=\"hsl(var(--primary))\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M518.73716,380.84H413.85905a.29966.29966,0,0,1-.00552-.59929H518.73716a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M518.73716,388.03169H413.85905a.29966.29966,0,0,1-.00552-.59929H518.73716a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M518.73716,395.22332H413.85905a.29966.29966,0,0,1-.00552-.59929H518.73716a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M518.73716,402.41487H413.85905a.29966.29966,0,0,1-.00552-.59929H518.73716a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M500.33941,330.80932v38.95474H402.74324a19.48915,19.48915,0,0,1-1.35522-38.95474q.67737-.02358,1.35522,0Z\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M502.73673,369.46442a1.49885,1.49885,0,0,1-1.49822,1.49826H401.005a20.52614,20.52614,0,0,1,0-41.05229H501.23851a1.49826,1.49826,0,1,1,0,2.99652H401.005a17.52964,17.52964,0,0,0,0,35.05928H501.23851A1.49884,1.49884,0,0,1,502.73673,369.46442Z\"\n      fill=\"#3f3d56\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M500.33941,339.79886H395.4613a.29966.29966,0,0,1-.00553-.59929H500.33941a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M500.33941,346.99054H395.4613a.29966.29966,0,0,1-.00553-.59929H500.33941a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M500.33941,354.18217H395.4613a.29966.29966,0,0,1-.00553-.59929H500.33941a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M500.33941,361.37376H395.4613a.29966.29966,0,0,1-.00553-.59929H500.33941a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M613.87355,550.68347V516.71838a5.661,5.661,0,0,0-5.66085-5.66085H479.4284a5.661,5.661,0,0,0-5.66084,5.66085v33.96509Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <rect\n      fill=\"#ccc\"\n      height=\"43.87158\"\n      width=\"140.10602\"\n      x=\"363.43092\"\n      y=\"325.83868\"\n    />\n    <path\n      d=\"M473.76756,620.02887V653.994a5.661,5.661,0,0,0,5.66084,5.66084H608.2127a5.661,5.661,0,0,0,5.66085-5.66084V620.02887Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <circle cx=\"432.77633\" cy=\"294.70402\" fill=\"#fff\" r=\"4.24564\" />\n    <circle cx=\"432.77633\" cy=\"351.3125\" fill=\"#fff\" r=\"4.24564\" />\n    <circle cx=\"433.00385\" cy=\"406.72228\" fill=\"#fff\" r=\"4.24564\" />\n    <path\n      d=\"M597.989,472.33053v38.9547H500.39287a19.48916,19.48916,0,0,1-1.35647-38.9547q.678-.02358,1.35647,0Z\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M600.38637,510.98558a1.49881,1.49881,0,0,1-1.49822,1.49822H498.65461a20.52615,20.52615,0,0,1-.0247-41.05229H598.88815a1.49827,1.49827,0,1,1,0,2.99653H498.65461a17.52963,17.52963,0,0,0,0,35.05923H598.88815A1.49885,1.49885,0,0,1,600.38637,510.98558Z\"\n      fill=\"#3f3d56\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M597.989,481.32H493.111a.29966.29966,0,0,1-.00553-.59929H597.98913a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M597.989,488.51175H493.111a.29966.29966,0,0,1-.00553-.59929H597.98913a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M597.989,495.70338H493.111a.29966.29966,0,0,1-.00553-.59929H597.98913a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M597.989,502.89493H493.111a.29966.29966,0,0,1-.00553-.59929H597.98913a.29966.29966,0,0,1,0,.59929Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M483.36747,317.81415H438.90162a2.74745,2.74745,0,0,0-1.21689.28306l-11.22288,5.61835a2.0452,2.0452,0,0,0,0,3.76443l11.22288,5.61835a2.74718,2.74718,0,0,0,1.21689.28306h44.46585a2.33381,2.33381,0,0,0,2.4628-2.16532v-11.2367A2.3338,2.3338,0,0,0,483.36747,317.81415Z\"\n      fill=\"#3f3d56\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M485.83027,319.97947v11.2367a2.33383,2.33383,0,0,1-2.4628,2.16532h-8.8589V317.81415h8.8589A2.33383,2.33383,0,0,1,485.83027,319.97947Z\"\n      fill=\"hsl(var(--primary))\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M216.78083,537.99332a35.33951,35.33951,0,0,0,34.12552-6.01134c11.95262-10.03214,15.70013-26.56,18.74934-41.864q4.50949-22.63308,9.019-45.26617l-18.88217,13.00153c-13.57891,9.34993-27.46375,18.99939-36.86572,32.54233S209.42082,522.42587,216.975,537.08\"\n      fill=\"#e6e6e6\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M218.39489,592.79741c-1.91113-13.92071-3.87625-28.0202-2.53572-42.09016,1.19057-12.4956,5.00277-24.70032,12.764-34.70734a57.73582,57.73582,0,0,1,14.81307-13.42309c1.48131-.935,2.84468,1.41257,1.36983,2.34348a54.88844,54.88844,0,0,0-21.71125,26.19626c-4.72684,12.02273-5.48591,25.12848-4.67135,37.90006.4926,7.72345,1.53656,15.39627,2.58859,23.05926a1.40615,1.40615,0,0,1-.94781,1.66928,1.3653,1.3653,0,0,1-1.6693-.94781Z\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M236.80246,568.16434a26.01425,26.01425,0,0,0,22.6665,11.69871c11.47417-.54466,21.04-8.55293,29.651-16.15584l25.46969-22.48783-16.85671-.80672c-12.12234-.58011-24.55745-1.12124-36.10356,2.617s-22.19457,12.73508-24.30583,24.68624\"\n      fill=\"#e6e6e6\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M212.99392,600.79976c9.19853-16.27621,19.86805-34.36538,38.93262-40.14695A43.445,43.445,0,0,1,268.3022,558.962c1.73863.14991,1.30448,2.82994-.431,2.6803a40.36111,40.36111,0,0,0-26.133,6.91386c-7.36852,5.01554-13.10573,11.98848-17.96161,19.383-2.97439,4.52936-5.63867,9.25082-8.30346,13.966-.85161,1.50687-3.34078.41915-2.47922-1.10534Z\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M198.25523,617.93168a19.69836,19.69836,0,0,1,12.0709-16.49847v-9.40956h15.782v9.70608a19.68812,19.68812,0,0,1,11.41362,16.202l3.711,43.13835H194.54417Z\"\n      fill=\"#f2f2f2\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M734.973,411.955l-4.69488-1.97685-3.22067-23.53551h-42.889l-3.491,23.43936-4.20031,2.10013a.99744.99744,0,0,0,.44611,1.88955h57.66283A.99739.99739,0,0,0,734.973,411.955Z\"\n      fill=\"#e6e6e6\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M811.1898,389.574H600.50692a4.174,4.174,0,0,1-4.16467-4.174V355.69092H815.35446V385.4A4.17408,4.17408,0,0,1,811.1898,389.574Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M815.57469,369.73213H596.15V242.61337a5.0375,5.0375,0,0,1,5.03186-5.03167h209.361a5.03755,5.03755,0,0,1,5.03191,5.03167Z\"\n      fill=\"#3f3d56\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M802.46932,360.50584h-193.214a3.88344,3.88344,0,0,1-3.87919-3.87908V250.68707a3.88365,3.88365,0,0,1,3.87919-3.87932h193.214a3.88366,3.88366,0,0,1,3.8792,3.87932V356.62676A3.88345,3.88345,0,0,1,802.46932,360.50584Z\"\n      fill=\"#fff\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M751.57964,397.88662a11.6159,11.6159,0,0,1,17.666,2.27241l26.13446-4.64642,6.69716,15.19317-36.99908,6.04328a11.67883,11.67883,0,0,1-13.49855-18.86244Z\"\n      fill=\"#ffb6b6\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M775.77611,417.286l27.24571-.33963,3.44882-.04668,55.43253-.69843s15.05312-14.3609,28.16068-29.1465l-1.83719-13.28833A54.29159,54.29159,0,0,0,870.023,340.1519C851.24988,352.696,840.363,377.52559,840.363,377.52559l-34.37018,8.22071-3.43848.82227-21.35608,5.10326Z\"\n      fill=\"hsl(var(--primary))\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M915.25011,498.96167H864.39249c0,2.17915-55.59414,3.94772-55.59414,3.94772a20.30858,20.30858,0,0,0-3.33166,3.15818,19.59694,19.59694,0,0,0-4.58,12.63271v3.15818a19.74588,19.74588,0,0,0,19.73861,19.73861h94.62478a19.75579,19.75579,0,0,0,19.73862-19.73861v-3.15818A19.76607,19.76607,0,0,0,915.25011,498.96167Z\"\n      fill=\"#e4e4e4\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <rect\n      fill=\"#e4e4e4\"\n      height=\"118.48951\"\n      width=\"20.52816\"\n      x=\"747.4019\"\n      y=\"303.23122\"\n    />\n    <path\n      d=\"M799.31222,658.58132c0,2.218,31.10721.858,69.47992.858s69.47991,1.36012,69.47991-.858-31.1072-19.807-69.47991-19.807S799.31222,656.36323,799.31222,658.58132Z\"\n      fill=\"#e4e4e4\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <polygon\n      fill=\"#ffb6b6\"\n      points=\"675.186 407.461 659.908 407.46 652.64 348.531 675.188 348.532 675.186 407.461\"\n    />\n    <path\n      d=\"M789.41863,659.852l-49.2623-.00183v-.62309a19.17528,19.17528,0,0,1,19.17426-19.17395h.00122l30.08773.00122Z\"\n      fill=\"#2f2e41\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <polygon\n      fill=\"#ffb6b6\"\n      points=\"630.031 407.461 614.753 407.46 607.485 348.531 630.033 348.532 630.031 407.461\"\n    />\n    <path\n      d=\"M744.2636,659.852l-49.2623-.00183v-.62309a19.1753,19.1753,0,0,1,19.17426-19.17395h.00122l30.08773.00122Z\"\n      fill=\"#2f2e41\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <circle cx=\"766.88656\" cy=\"41.63615\" fill=\"#ffb6b6\" r=\"26.56401\" />\n    <path\n      d=\"M920.21655,461.22417s8.91308,47.1307-24.99958,53.13247-82.86639,10.21993-82.86639,10.21993L790.36706,627.14324l-29.53443-2.63675s3.928-123.46737,13.5876-133.127,70.71212-38.58282,70.71212-38.58282Z\"\n      fill=\"#2f2e41\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M853.98286,441.47135,839.151,456.35062s-107.0941,17.25-111.22553,41.9852c-6.23747,37.34427-13.60493,118.552-13.60493,118.552l32.1988-2.41491,12.62647-92.31123,51.5182-11.71874L869.27729,478.5Z\"\n      fill=\"#2f2e41\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M902.78526,263.36115c-2.6223-4.94751-5.95413-14.80785-11.24679-16.63657a42.07731,42.07731,0,0,0-9.05841-1.92972l-8.99618,3.46009,4.89616-3.808q-1.42988-.08519-2.85817-.13928l-6.0699,2.33453,3.10542-2.41532c-5.65883-.05808-11.5.53031-15.88468,3.9752-3.73817,2.93677-7.44169,14.06185-8.04057,18.77753a35.9171,35.9171,0,0,0,.6603,13.53055l1.53716,1.46166a18.85936,18.85936,0,0,0,1.206-3.83883,18.18056,18.18056,0,0,1,8.70263-11.80641l.08368-.0472c2.5782-1.451,5.7065-1.3841,8.66308-1.27769l14.04158.50527c3.37829.12158,7.01608.33533,9.64978,2.45443a15.888,15.888,0,0,1,3.85826,5.58929c1.30868,2.6414,3.8661,12.60418,3.8661,12.60418s1.44689-1.88062,2.1404-.48092a48.39766,48.39766,0,0,0,2.01437-11.23347A22.00877,22.00877,0,0,0,902.78526,263.36115Z\"\n      fill=\"#2f2e41\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M995.69426,290.88349A11.61582,11.61582,0,0,0,985.181,305.26136l-21.3614,15.75722,6.40951,15.31674,29.8539-22.67594a11.67883,11.67883,0,0,0-4.38876-22.77589Z\"\n      fill=\"#ffb6b6\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M992.25627,323.052l-53.551,59.4744s-25.60913-8.19816-45.41466-17.08624l-8.8977-27.32787a54.34329,54.34329,0,0,1-2.60112-19.66442c27.45606-7.306,59.391,19.87863,59.391,19.87863l40.08517-31.39877Z\"\n      fill=\"hsl(var(--primary))\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M867.301,465.6169c-9.554-3.30029-19.43312-6.71277-30.08912-7.99385l-.45773-.05533.12632-.443c11.03073-38.7308,8.27761-63.50657,2.87195-100.72306a37.59072,37.59072,0,0,1,21.5483-39.50121l.06542-.02958,30.43436-1.93391.06935-.00423,22.13437,6.50989a15.18313,15.18313,0,0,1,10.86724,14.83111c-.23987,12.23937.26868,25.9043.80711,40.37114,1.20787,32.45569,2.45686,66.01647-4.63045,87.79166l-.03718.11412-.09462.07416a36.09883,36.09883,0,0,1-23.08086,8.10758C887.90057,472.73235,877.76186,469.23034,867.301,465.6169Z\"\n      fill=\"hsl(var(--primary))\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n    <path\n      d=\"M1088.24817,662.4183H111.75183a1.41521,1.41521,0,1,1,0-2.83042h976.49634a1.41521,1.41521,0,1,1,0,2.83042Z\"\n      fill=\"#ccc\"\n      transform=\"translate(-110.33661 -237.5817)\"\n    />\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/fallback/icons/icon-offline.vue",
    "content": "<template>\n  <svg\n    height=\"458.68642\"\n    viewBox=\"0 0 656 458.68642\"\n    width=\"656\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n  >\n    <rect fill=\"#3f3d56\" height=\"2\" width=\"656\" y=\"434.34322\" />\n    <g>\n      <path\n        d=\"M471.97092,210.81397c-6.0733-36.41747-37.72842-64.16942-75.86423-64.16942H240.14931c-38.12099,0-69.76869,27.72972-75.86421,64.12497-.70358,4.16241-1.06653,8.44331-1.06653,12.80573v135.88599c0,4.36237,.36295,8.63589,1.06653,12.79831,4.85126,28.99625,25.92996,52.49686,53.58563,60.84393,7.05095,2.13306,14.53143,3.28104,22.27859,3.28104h155.9574c7.74716,0,15.22763-1.14798,22.27859-3.28104,27.66309-8.35449,48.74921-31.86993,53.58563-60.88837,.6962-4.14758,1.05911-8.40628,1.05911-12.75388V223.57525c0-4.34758-.36292-8.61369-1.05911-12.76128h-.00003Zm-62.66592,222.28954c-4.2883,.76285-8.69516,1.16281-13.19827,1.16281H240.14931c-4.50313,0-8.90997-.39999-13.19829-1.16281-35.01768-6.22885-61.60677-36.83228-61.60677-73.64224v-45.10526c0-127.45004,103.31242-165.58582,230.76244-165.58582,41.31314,0,74.80505,33.49194,74.80505,74.80505v135.88599c-100.29059,13.42047-26.58911,67.41339-61.60678,73.64224l.00003,.00003Z\"\n        fill=\"#3f3d56\"\n      />\n      <polygon\n        fill=\"hsl(var(--primary))\"\n        points=\"349.16196 249.18644 355.16196 288.18642 443.16196 276.18642 434.66196 230.6195 349.16196 249.18644\"\n      />\n      <rect\n        fill=\"#2f2e41\"\n        height=\"37.66125\"\n        width=\"36.38461\"\n        x=\"381.84177\"\n        y=\"30.34218\"\n      />\n      <polygon\n        fill=\"#ffb6b6\"\n        points=\"385.16196 70.18643 394.16196 43.18643 411.70447 43.18643 412.62653 70.18643 385.16196 70.18643\"\n      />\n      <polygon\n        isolation=\"isolate\"\n        opacity=\".1\"\n        points=\"385.16196 70.18643 394.16196 43.18643 411.70447 43.18643 412.62653 70.18643 385.16196 70.18643\"\n      />\n      <path\n        d=\"M394.66196,310.68642l-1,104-1,8v11.48425l15,1.51575,1-23s16-45,12-80-2-25-2-25l-24,3Z\"\n        fill=\"#ffb6b6\"\n      />\n      <path\n        d=\"M404.18408,318.85363l-36.90134,97.23831-1.97873,7.81567-4.1777,10.69742-14.52368-4.04477,7.43539-21.78796s1.46619-47.7373,17.92432-78.88422,10.9574-22.5596,10.9574-22.5596l21.26434,11.52512v.00003Z\"\n        fill=\"#ffb6b6\"\n      />\n      <path\n        d=\"M385.16196,67.18643l-27,12,17.23959,89.01208-2.72385,127.75565-18,38s-3.01575,21.73227,27.98425,7.73227,66-18,66-18l-8.5-58.5-7.5-153.5,1-34-22-14s-26.5,3.5-26.5,3.50001Z\"\n        fill=\"#2f2e41\"\n      />\n      <path\n        d=\"M370.1243,335.34322l-29.96231-50.15677,34.23959-116.98792-16.23959-89.01208,28.49045-12.19685s14.74915,14.36248,14.74915,26.20894-31.27728,242.1447-31.27728,242.1447v-.00003Z\"\n        fill=\"#e6e6e6\"\n      />\n      <path\n        d=\"M435.1243,325.34322l-27.19693-233.62811c-.34341-2.94999,.16013-5.93678,1.45178-8.6111l7.78284-16.11441,30.5,8.69685-12.26041,95.51208,32.76041,93.98792-33.03769,60.15677Z\"\n        fill=\"#e6e6e6\"\n      />\n      <path\n        d=\"M410.66196,433.68642s-19-11-21-5-3,11-3,11c0,0-5,19,10,19s14-8.64172,14-8.64172v-16.35828Z\"\n        fill=\"#2f2e41\"\n      />\n      <path\n        d=\"M344.53574,427.60598s21.69977-3.33459,21.3801,2.9819c-.3197,6.31647-1.20709,11.33768-1.20709,11.33768,0,0-2.25433,19.51712-16.22662,14.06046s-9.89713-13.14252-9.89713-13.14252l5.95078-15.23749-.00003-.00003Z\"\n        fill=\"#2f2e41\"\n      />\n      <circle cx=\"404.10297\" cy=\"33.02146\" fill=\"#ffb6b6\" r=\"24.85993\" />\n      <path\n        d=\"M423.96469,10.86766c-1.15707-6.12936-7.44913-10.27514-13.66504-10.79501s-12.30453,1.82726-17.90228,4.57921c-3.79456,1.86548-7.53061,3.96811-10.60425,6.87182s-5.46063,6.69692-6.01202,10.88913c-.19507,1.48324-.1698,3.03289-.77692,4.40016-.75845,1.708-2.38654,2.86795-3.36917,4.4576-1.76227,2.85096-.95267,6.99858,1.75238,8.97753-3.40024,1.44912-6.89398,2.96069-9.48602,5.59563s-4.08878,6.70308-2.66644,10.11462c.50323,1.20699,1.33481,2.26349,1.76489,3.49843,.81668,2.34499,.03943,5.00909-1.40924,7.02585s-3.49316,3.51228-5.50174,4.97226c5.16196,1.01177,10.43097,1.80015,15.66992,1.32811s10.49707-2.30805,14.29086-5.95176c3.79379-3.64371,5.88083-9.26437,4.51974-14.34539-1.04269-3.89231-3.95898-7.30301-3.95712-11.33256,.00143-3.09747,1.7431-5.89158,3.4249-8.49271,3.67291-5.68066,7.34579-11.36132,11.01868-17.04197,.66068-1.02183,1.35739-2.07924,2.4014-2.70425,1.77606-1.06326,4.0798-.59568,5.95227,.28683,1.87244,.88252,3.58304,2.14867,5.57941,2.69585,4.07452,1.11677,8.80106-1.44789,10.08575-5.47261\"\n        fill=\"#2f2e41\"\n      />\n      <path\n        d=\"M409.27951,61.42523c-2.07159,2.0061-5.05701,2.65225-7.82379,3.46516s-5.70978,2.09141-6.95499,4.69243c-1.22101,2.55043-.33459,5.78793,1.68692,7.76505s4.95816,2.80999,7.78555,2.77077c2.82736-.03922,5.58282-.86796,8.24176-1.8301,7.27054-2.63087,14.15665-6.32148,20.37314-10.919-4.02679-1.11411-6.66107-5.81614-5.50836-9.83205,.93768-3.26677,3.80499-5.54528,5.75616-8.32809,3.35959-4.79151,3.91925-11.10753,2.80676-16.85277-1.11246-5.74524-3.73163-11.07097-6.32358-16.3176-.81934-1.65853-1.65805-3.34513-2.93619-4.68245-1.27814-1.33731-3.08783-2.29539-4.92776-2.10379-3.05334,.31795-5.00302,3.66989-5.02377,6.7397s1.32593,5.95491,2.34732,8.84988c1.05231,2.98259,1.78381,6.14409,1.50146,9.29425-.2366,2.63989-1.19669,5.21132-2.74811,7.36029-1.19809,1.65954-2.72479,3.05223-4.0275,4.63097-1.00714,1.22055-1.90009,2.60309-2.16486,4.16321-.48181,2.83914,1.18356,5.71186,.72714,8.55519-.48248,3.0056-3.6452,5.3067-6.65341,4.84085\"\n        fill=\"#2f2e41\"\n      />\n      <g>\n        <circle\n          cx=\"333.2486\"\n          cy=\"323.64455\"\n          fill=\"hsl(var(--primary))\"\n          r=\"85\"\n        />\n        <g>\n          <path\n            d=\"M384.17838,316.82296h-10.56668c-1.64377-9.68713-6.7168-18.46011-14.2923-24.71729-17.43427-14.39993-43.24109-11.94022-57.64099,5.49411-.04913,.05563-.09644,.11282-.14169,.17151-1.15063,1.49146-.87427,3.63333,.61716,4.784,1.49118,1.1507,3.63306,.87448,4.78394-.61697,6.25537-7.5788,15.72369-12.40167,26.31064-12.40167,16.20853,.00195,30.17899,11.40631,33.42572,27.28629h-9.31805c-.3988,.00012-.78458,.13992-1.09082,.39502-.72375,.60281-.82175,1.6781-.21915,2.40186l13.41125,16.09894c.06577,.07889,.13855,.1517,.21759,.21747,.72324,.60327,1.79871,.50583,2.40186-.21747l13.41125-16.09894c.25504-.30624,.3949-.69223,.39514-1.09082,.00027-.94186-.763-1.70566-1.70486-1.70605v.00003Z\"\n            fill=\"#fff\"\n          />\n          <path\n            d=\"M364.34329,344.7337c-1.49146-1.15063-3.63333-.87433-4.78394,.6171-4.96201,6.00781-11.83066,10.13629-19.46436,11.69922-18.46167,3.77988-36.49231-8.12213-40.27225-26.58392h9.3183c.94186-.0004,1.70514-.76419,1.70486-1.70605-.00027-.39853-.14011-.78452-.39514-1.09082l-13.41125-16.09888c-.60312-.72336-1.67862-.8208-2.40186-.21753-.07904,.06577-.15182,.13855-.21759,.21753l-13.41125,16.09888c-.6026,.72375-.50461,1.7991,.21915,2.40186,.30624,.25516,.69205,.3949,1.09082,.39502h10.56641c1.64404,9.68723,6.7168,18.46011,14.29254,24.71729,17.43427,14.39999,43.24109,11.94022,57.64099-5.49405,.04913-.05569,.09619-.11295,.14142-.17163,1.15088-1.49146,.87454-3.63327-.61691-4.784h.00006Z\"\n            fill=\"#fff\"\n          />\n        </g>\n      </g>\n      <path\n        id=\"uuid-da16df1e-5659-4232-96f6-61e8c639a9ec-574\"\n        d=\"M356.98148,237.19363c-1.02939,7.36621-5.66458,12.80598-10.35239,12.15012-4.68781-.65588-7.65225-7.15837-6.62149-14.52707,.37137-2.94914,1.4436-5.76646,3.12701-8.21626l4.75577-31.15587,14.57297,2.54338-6.23553,30.44414c.94736,2.81844,1.20581,5.82278,.75369,8.76157h-.00003Z\"\n        fill=\"#ffb6b6\"\n      />\n      <path\n        d=\"M369.66196,77.68643s-15-5-17,13-4,39.99999-4,39.99999c0,0-9,21-5,32s11,3.3307,4,12.66534-6.02478,40.04724-6.02478,40.04724l22.52478-1.13387s12.5-82.57875,12.5-84.57875-7-52-7-52v.00004Z\"\n        fill=\"#e6e6e6\"\n      />\n      <g>\n        <path\n          id=\"uuid-6bf35aa9-e432-4b51-af77-8f4eb19e6e42-575\"\n          d=\"M467.16132,233.84998c.27881,7.43257-3.33017,13.60114-8.06033,13.7778s-8.78937-5.70491-9.06732-13.14017c-.15176-2.96857,.40961-5.93028,1.63712-8.63741l-.78369-31.507,14.79315-.05261-.798,31.0659c1.42709,2.60854,2.20859,5.52095,2.27905,8.49347l.00003,.00002Z\"\n          fill=\"#ffb6b6\"\n        />\n        <path\n          d=\"M444.06961,77.34876s15.08694-4.73121,16.76505,13.30165,3.28473,51.06508,3.28473,51.06508c0,0,8.62338,21.15744,4.42749,32.08421s-11.05774,3.13365-4.22565,12.59187c6.83212,9.45822,4.37997,36.13126,4.37997,36.13126l-22.50095-1.53612s-10.09427-78.77167-10.05853-80.77133,7.92792-62.86664,7.92792-62.86664l-.00003,.00002Z\"\n          fill=\"#e6e6e6\"\n        />\n      </g>\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/fallback/index.ts",
    "content": "export type * from './fallback';\nexport { default as Fallback } from './fallback.vue';\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/index.ts",
    "content": "export * from './about';\nexport * from './authentication';\nexport * from './dashboard';\nexport * from './fallback';\nexport * from './profile';\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/profile/base-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Recordable } from '@vben/types';\n\nimport type { VbenFormSchema } from '@vben-core/form-ui';\n\nimport { computed, reactive } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport { useVbenForm } from '@vben-core/form-ui';\nimport { VbenButton } from '@vben-core/shadcn-ui';\n\ninterface Props {\n  formSchema?: VbenFormSchema[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  formSchema: () => [],\n});\n\nconst emit = defineEmits<{\n  submit: [Recordable<any>];\n}>();\n\nconst [Form, formApi] = useVbenForm(\n  reactive({\n    commonConfig: {\n      // 所有表单项\n      componentProps: {\n        class: 'w-full',\n      },\n    },\n    layout: 'horizontal',\n    schema: computed(() => props.formSchema),\n    showDefaultActions: false,\n  }),\n);\n\nasync function handleSubmit() {\n  const { valid } = await formApi.validate();\n  const values = await formApi.getValues();\n  if (valid) {\n    emit('submit', values);\n  }\n}\n\ndefineExpose({\n  getFormApi: () => formApi,\n});\n</script>\n<template>\n  <div @keydown.enter.prevent=\"handleSubmit\">\n    <Form />\n    <VbenButton type=\"submit\" class=\"mt-4\" @click=\"handleSubmit\">\n      {{ $t('profile.updateBasicProfile') }}\n    </VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/profile/index.ts",
    "content": "export { default as ProfileBaseSetting } from './base-setting.vue';\nexport { default as ProfileNotificationSetting } from './notification-setting.vue';\nexport { default as ProfilePasswordSetting } from './password-setting.vue';\nexport { default as Profile } from './profile.vue';\nexport { default as ProfileSecuritySetting } from './security-setting.vue';\nexport type * from './types';\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/profile/notification-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Recordable } from '@vben/types';\n\nimport type { SettingProps } from './types';\n\nimport {\n  Form,\n  FormControl,\n  FormDescription,\n  FormField,\n  FormItem,\n  FormLabel,\n  Switch,\n} from '@vben-core/shadcn-ui';\n\nwithDefaults(defineProps<SettingProps>(), {\n  formSchema: () => [],\n});\n\nconst emit = defineEmits<{\n  change: [Recordable<any>];\n}>();\n\nfunction handleChange(fieldName: string, value: boolean) {\n  emit('change', { fieldName, value });\n}\n</script>\n<template>\n  <Form class=\"space-y-8\">\n    <div class=\"space-y-4\">\n      <template v-for=\"item in formSchema\" :key=\"item.fieldName\">\n        <FormField type=\"checkbox\" :name=\"item.fieldName\">\n          <FormItem\n            class=\"flex flex-row items-center justify-between rounded-lg border p-4\"\n          >\n            <div class=\"space-y-0.5\">\n              <FormLabel class=\"text-base\"> {{ item.label }} </FormLabel>\n              <FormDescription>\n                {{ item.description }}\n              </FormDescription>\n            </div>\n            <FormControl>\n              <Switch\n                :model-value=\"item.value\"\n                @update:model-value=\"handleChange(item.fieldName, $event)\"\n              />\n            </FormControl>\n          </FormItem>\n        </FormField>\n      </template>\n    </div>\n  </Form>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/profile/password-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Recordable } from '@vben/types';\n\nimport type { VbenFormSchema } from '@vben-core/form-ui';\n\nimport { computed, reactive } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport { useVbenForm } from '@vben-core/form-ui';\nimport { VbenButton } from '@vben-core/shadcn-ui';\n\ninterface Props {\n  formSchema?: VbenFormSchema[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  formSchema: () => [],\n});\n\nconst emit = defineEmits<{\n  submit: [Recordable<any>];\n}>();\n\nconst [Form, formApi] = useVbenForm(\n  reactive({\n    commonConfig: {\n      labelWidth: 130,\n      // 所有表单项\n      componentProps: {\n        class: 'w-full',\n      },\n    },\n    layout: 'horizontal',\n    schema: computed(() => props.formSchema),\n    showDefaultActions: false,\n  }),\n);\n\nasync function handleSubmit() {\n  const { valid } = await formApi.validate();\n  const values = await formApi.getValues();\n  if (valid) {\n    emit('submit', values);\n  }\n}\n\ndefineExpose({\n  getFormApi: () => formApi,\n});\n</script>\n<template>\n  <div>\n    <Form />\n    <VbenButton type=\"submit\" class=\"mt-4\" @click=\"handleSubmit\">\n      {{ $t('profile.updatePassword') }}\n    </VbenButton>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/profile/profile.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Props } from './types';\n\nimport { preferences } from '@vben-core/preferences';\nimport {\n  Card,\n  Separator,\n  Tabs,\n  TabsList,\n  TabsTrigger,\n  VbenAvatar,\n} from '@vben-core/shadcn-ui';\n\nimport { Page } from '../../components';\n\ndefineOptions({\n  name: 'ProfileUI',\n});\n\nwithDefaults(defineProps<Props>(), {\n  title: '关于项目',\n  tabs: () => [],\n});\n\nconst tabsValue = defineModel<string>('modelValue');\n</script>\n<template>\n  <Page auto-content-height>\n    <div class=\"flex size-full\">\n      <Card class=\"w-1/6 flex-none\">\n        <div class=\"mt-4 flex-col-center h-40 gap-4\">\n          <VbenAvatar\n            :src=\"userInfo?.avatar ?? preferences.app.defaultAvatar\"\n            class=\"size-20\"\n          />\n          <span class=\"text-lg font-semibold\">\n            {{ userInfo?.realName ?? '' }}\n          </span>\n          <span class=\"text-sm text-foreground/80\">\n            {{ userInfo?.username ?? '' }}\n          </span>\n        </div>\n        <Separator class=\"my-4\" />\n        <Tabs v-model=\"tabsValue\" orientation=\"vertical\" class=\"m-4\">\n          <TabsList class=\"grid w-full grid-cols-1 bg-card\">\n            <TabsTrigger\n              v-for=\"tab in tabs\"\n              :key=\"tab.value\"\n              :value=\"tab.value\"\n              class=\"h-12 justify-start data-[state=active]:bg-primary data-[state=active]:text-primary-foreground\"\n            >\n              {{ tab.label }}\n            </TabsTrigger>\n          </TabsList>\n        </Tabs>\n      </Card>\n      <Card class=\"ml-4 w-5/6 flex-auto p-8\">\n        <slot name=\"content\"></slot>\n      </Card>\n    </div>\n  </Page>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/profile/security-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Recordable } from '@vben/types';\n\nimport type { SettingProps } from './types';\n\nimport {\n  Form,\n  FormControl,\n  FormDescription,\n  FormField,\n  FormItem,\n  FormLabel,\n  Switch,\n} from '@vben-core/shadcn-ui';\n\nwithDefaults(defineProps<SettingProps>(), {\n  formSchema: () => [],\n});\n\nconst emit = defineEmits<{\n  change: [Recordable<any>];\n}>();\n\nfunction handleChange(fieldName: string, value: boolean) {\n  emit('change', { fieldName, value });\n}\n</script>\n<template>\n  <Form class=\"space-y-8\">\n    <div class=\"space-y-4\">\n      <template v-for=\"item in formSchema\" :key=\"item.fieldName\">\n        <FormField type=\"checkbox\" :name=\"item.fieldName\">\n          <FormItem\n            class=\"flex flex-row items-center justify-between rounded-lg border p-4\"\n          >\n            <div class=\"space-y-0.5\">\n              <FormLabel class=\"text-base\"> {{ item.label }} </FormLabel>\n              <FormDescription>\n                {{ item.description }}\n              </FormDescription>\n            </div>\n            <FormControl>\n              <Switch\n                :model-value=\"item.value\"\n                @update:model-value=\"handleChange(item.fieldName, $event)\"\n              />\n            </FormControl>\n          </FormItem>\n        </FormField>\n      </template>\n    </div>\n  </Form>\n</template>\n"
  },
  {
    "path": "packages/effects/common-ui/src/ui/profile/types.ts",
    "content": "import type { BasicUserInfo } from '@vben/types';\n\nexport interface Props {\n  title?: string;\n  userInfo: BasicUserInfo | null;\n  tabs: {\n    label: string;\n    value: string;\n  }[];\n}\n\nexport interface FormSchemaItem {\n  description: string;\n  fieldName: string;\n  label: string;\n  value: boolean;\n}\n\nexport interface SettingProps {\n  formSchema: FormSchemaItem[];\n}\n"
  },
  {
    "path": "packages/effects/common-ui/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/effects/hooks/README.md",
    "content": "# @vben/hooks\n\n用于多个 `app` 公用的 hook，继承了 `@vben/hooks` 的所有能力。业务上有通用 hooks 可以放在这里。\n\n## 用法\n\n### 添加依赖\n\n```bash\n# 进入目标应用目录，例如 apps/xxxx-app\n# cd apps/xxxx-app\npnpm add @vben/hooks\n```\n\n### 使用\n\n```ts\nimport { useNamespace } from '@vben/hooks';\n```\n"
  },
  {
    "path": "packages/effects/hooks/package.json",
    "content": "{\n  \"name\": \"@vben/hooks\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/effects/hooks\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/composables\": \"workspace:*\",\n    \"@vben/preferences\": \"workspace:*\",\n    \"@vben/stores\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\",\n    \"watermark-js-plus\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/effects/hooks/src/index.ts",
    "content": "export * from './use-app-config';\nexport * from './use-content-maximize';\nexport * from './use-design-tokens';\nexport * from './use-hover-toggle';\nexport * from './use-pagination';\nexport * from './use-refresh';\nexport * from './use-tabs';\nexport * from './use-watermark';\nexport * from '@vben-core/composables';\n"
  },
  {
    "path": "packages/effects/hooks/src/use-app-config.ts",
    "content": "import type {\n  ApplicationConfig,\n  VbenAdminProAppConfigRaw,\n} from '@vben/types/global';\n\n/**\n * 由 vite-inject-app-config 注入的全局配置\n */\nexport function useAppConfig(\n  env: Record<string, any>,\n  isProduction: boolean,\n): ApplicationConfig {\n  // 生产环境下，直接使用 window._VBEN_ADMIN_PRO_APP_CONF_ 全局变量\n  const config = isProduction\n    ? window._VBEN_ADMIN_PRO_APP_CONF_\n    : (env as VbenAdminProAppConfigRaw);\n\n  const {\n    VITE_GLOB_API_URL,\n    VITE_GLOB_AUTH_DINGDING_CORP_ID,\n    VITE_GLOB_AUTH_DINGDING_CLIENT_ID,\n  } = config;\n\n  const applicationConfig: ApplicationConfig = {\n    apiURL: VITE_GLOB_API_URL,\n    auth: {},\n  };\n  if (VITE_GLOB_AUTH_DINGDING_CORP_ID && VITE_GLOB_AUTH_DINGDING_CLIENT_ID) {\n    applicationConfig.auth.dingding = {\n      clientId: VITE_GLOB_AUTH_DINGDING_CLIENT_ID,\n      corpId: VITE_GLOB_AUTH_DINGDING_CORP_ID,\n    };\n  }\n\n  return applicationConfig;\n}\n"
  },
  {
    "path": "packages/effects/hooks/src/use-content-maximize.ts",
    "content": "import { updatePreferences, usePreferences } from '@vben/preferences';\n/**\n * 主体区域最大化\n */\nexport function useContentMaximize() {\n  const { contentIsMaximize } = usePreferences();\n\n  function toggleMaximize() {\n    const isMaximize = contentIsMaximize.value;\n\n    updatePreferences({\n      header: {\n        hidden: !isMaximize,\n      },\n      sidebar: {\n        hidden: !isMaximize,\n      },\n    });\n  }\n  return {\n    contentIsMaximize,\n    toggleMaximize,\n  };\n}\n"
  },
  {
    "path": "packages/effects/hooks/src/use-design-tokens.ts",
    "content": "import { reactive, watch } from 'vue';\n\nimport { preferences, usePreferences } from '@vben/preferences';\nimport { convertToRgb, updateCSSVariables } from '@vben/utils';\n\n/**\n * 用于适配各个框架的设计系统\n */\n\nexport function useAntdDesignTokens() {\n  const rootStyles = getComputedStyle(document.documentElement);\n\n  const tokens = reactive({\n    borderRadius: '' as any,\n    colorBgBase: '',\n    colorBgContainer: '',\n    colorBgElevated: '',\n    colorBgLayout: '',\n    colorBgMask: '',\n    colorBorder: '',\n    colorBorderSecondary: '',\n    colorError: '',\n    colorInfo: '',\n    colorPrimary: '',\n    colorSuccess: '',\n    colorTextBase: '',\n    colorWarning: '',\n    zIndexPopupBase: 2000, // 调整基础弹层层级，避免下拉等组件被弹窗或者最大化状态下的表格遮挡\n  });\n\n  const getCssVariableValue = (variable: string, isColor: boolean = true) => {\n    const value = rootStyles.getPropertyValue(variable);\n    return isColor ? `hsl(${value})` : value;\n  };\n\n  watch(\n    () => preferences.theme,\n    () => {\n      tokens.colorPrimary = getCssVariableValue('--primary');\n\n      tokens.colorInfo = getCssVariableValue('--primary');\n\n      tokens.colorError = getCssVariableValue('--destructive');\n\n      tokens.colorWarning = getCssVariableValue('--warning');\n\n      tokens.colorSuccess = getCssVariableValue('--success');\n\n      tokens.colorTextBase = getCssVariableValue('--foreground');\n\n      getCssVariableValue('--primary-foreground');\n\n      tokens.colorBorderSecondary = tokens.colorBorder =\n        getCssVariableValue('--border');\n\n      tokens.colorBgElevated = getCssVariableValue('--popover');\n\n      tokens.colorBgContainer = getCssVariableValue('--card');\n\n      tokens.colorBgBase = getCssVariableValue('--background');\n\n      const radius = Number.parseFloat(getCssVariableValue('--radius', false));\n      // 1rem = 16px\n      tokens.borderRadius = radius * 16;\n\n      tokens.colorBgLayout = getCssVariableValue('--background-deep');\n      tokens.colorBgMask = getCssVariableValue('--overlay');\n    },\n    { immediate: true },\n  );\n\n  return {\n    tokens,\n  };\n}\n\nexport function useNaiveDesignTokens() {\n  const rootStyles = getComputedStyle(document.documentElement);\n\n  const commonTokens = reactive({\n    baseColor: '',\n    bodyColor: '',\n    borderColor: '',\n    borderRadius: '',\n    cardColor: '',\n    dividerColor: '',\n    errorColor: '',\n    errorColorHover: '',\n    errorColorPressed: '',\n    errorColorSuppl: '',\n    invertedColor: '',\n    modalColor: '',\n    popoverColor: '',\n    primaryColor: '',\n    primaryColorHover: '',\n    primaryColorPressed: '',\n    primaryColorSuppl: '',\n    successColor: '',\n    successColorHover: '',\n    successColorPressed: '',\n    successColorSuppl: '',\n    tableColor: '',\n    textColorBase: '',\n    warningColor: '',\n    warningColorHover: '',\n    warningColorPressed: '',\n    warningColorSuppl: '',\n  });\n\n  const getCssVariableValue = (variable: string, isColor: boolean = true) => {\n    const value = rootStyles.getPropertyValue(variable);\n    return isColor ? convertToRgb(`hsl(${value})`) : value;\n  };\n\n  watch(\n    () => preferences.theme,\n    () => {\n      commonTokens.primaryColor = getCssVariableValue('--primary');\n      commonTokens.primaryColorHover = getCssVariableValue('--primary-600');\n      commonTokens.primaryColorPressed = getCssVariableValue('--primary-700');\n      commonTokens.primaryColorSuppl = getCssVariableValue('--primary-800');\n\n      commonTokens.errorColor = getCssVariableValue('--destructive');\n      commonTokens.errorColorHover = getCssVariableValue('--destructive-600');\n      commonTokens.errorColorPressed = getCssVariableValue('--destructive-700');\n      commonTokens.errorColorSuppl = getCssVariableValue('--destructive-800');\n\n      commonTokens.warningColor = getCssVariableValue('--warning');\n      commonTokens.warningColorHover = getCssVariableValue('--warning-600');\n      commonTokens.warningColorPressed = getCssVariableValue('--warning-700');\n      commonTokens.warningColorSuppl = getCssVariableValue('--warning-800');\n\n      commonTokens.successColor = getCssVariableValue('--success');\n      commonTokens.successColorHover = getCssVariableValue('--success-600');\n      commonTokens.successColorPressed = getCssVariableValue('--success-700');\n      commonTokens.successColorSuppl = getCssVariableValue('--success-800');\n\n      commonTokens.textColorBase = getCssVariableValue('--foreground');\n\n      commonTokens.baseColor = getCssVariableValue('--primary-foreground');\n\n      commonTokens.dividerColor = commonTokens.borderColor =\n        getCssVariableValue('--border');\n\n      commonTokens.modalColor = commonTokens.popoverColor =\n        getCssVariableValue('--popover');\n\n      commonTokens.tableColor = commonTokens.cardColor =\n        getCssVariableValue('--card');\n\n      commonTokens.bodyColor = getCssVariableValue('--background');\n      commonTokens.invertedColor = getCssVariableValue('--background-deep');\n\n      commonTokens.borderRadius = getCssVariableValue('--radius', false);\n    },\n    { immediate: true },\n  );\n  return {\n    commonTokens,\n  };\n}\n\nexport function useElementPlusDesignTokens() {\n  const { isDark } = usePreferences();\n  const rootStyles = getComputedStyle(document.documentElement);\n\n  const getCssVariableValueRaw = (variable: string) => {\n    return rootStyles.getPropertyValue(variable);\n  };\n\n  const getCssVariableValue = (variable: string, isColor: boolean = true) => {\n    const value = getCssVariableValueRaw(variable);\n    return isColor ? convertToRgb(`hsl(${value})`) : value;\n  };\n\n  watch(\n    () => preferences.theme,\n    () => {\n      const background = getCssVariableValue('--background');\n      const border = getCssVariableValue('--border');\n      const accent = getCssVariableValue('--accent');\n\n      const variables: Record<string, string> = {\n        '--el-bg-color': background,\n        '--el-bg-color-overlay': getCssVariableValue('--popover'),\n        '--el-bg-color-page': getCssVariableValue('--background-deep'),\n        '--el-border-color': border,\n        '--el-border-color-dark': border,\n        '--el-border-color-extra-light': border,\n        '--el-border-color-hover': accent,\n        '--el-border-color-light': border,\n        '--el-border-color-lighter': border,\n\n        '--el-border-radius-base': getCssVariableValue('--radius', false),\n        '--el-color-danger': getCssVariableValue('--destructive-500'),\n        '--el-color-danger-dark-2': isDark.value\n          ? getCssVariableValue('--destructive-400')\n          : getCssVariableValue('--destructive-600'),\n        '--el-color-danger-light-3': isDark.value\n          ? getCssVariableValue('--destructive-600')\n          : getCssVariableValue('--destructive-400'),\n        '--el-color-danger-light-5': isDark.value\n          ? getCssVariableValue('--destructive-700')\n          : getCssVariableValue('--destructive-300'),\n        '--el-color-danger-light-7': isDark.value\n          ? getCssVariableValue('--destructive-800')\n          : getCssVariableValue('--destructive-200'),\n        '--el-color-danger-light-8': isDark.value\n          ? getCssVariableValue('--destructive-900')\n          : getCssVariableValue('--destructive-100'),\n        '--el-color-danger-light-9': isDark.value\n          ? getCssVariableValue('--destructive-950')\n          : getCssVariableValue('--destructive-50'),\n\n        '--el-color-error': getCssVariableValue('--destructive-500'),\n        '--el-color-error-dark-2': isDark.value\n          ? getCssVariableValue('--destructive-400')\n          : getCssVariableValue('--destructive-600'),\n        '--el-color-error-light-3': isDark.value\n          ? getCssVariableValue('--destructive-600')\n          : getCssVariableValue('--destructive-400'),\n        '--el-color-error-light-5': isDark.value\n          ? getCssVariableValue('--destructive-700')\n          : getCssVariableValue('--destructive-300'),\n        '--el-color-error-light-7': isDark.value\n          ? getCssVariableValue('--destructive-800')\n          : getCssVariableValue('--destructive-200'),\n        '--el-color-error-light-8': isDark.value\n          ? getCssVariableValue('--destructive-900')\n          : getCssVariableValue('--destructive-100'),\n        '--el-color-error-light-9': isDark.value\n          ? getCssVariableValue('--destructive-950')\n          : getCssVariableValue('--destructive-50'),\n\n        '--el-color-info-light-5': border,\n        '--el-color-info-light-8': border,\n        '--el-color-info-light-9': getCssVariableValue('--info'), // getCssVariableValue('--secondary'),\n\n        '--el-color-primary': getCssVariableValue('--primary-500'),\n        '--el-color-primary-dark-2': isDark.value\n          ? getCssVariableValue('--primary-400')\n          : getCssVariableValue('--primary-600'),\n        '--el-color-primary-light-3': isDark.value\n          ? getCssVariableValue('--primary-600')\n          : getCssVariableValue('--primary-400'),\n        '--el-color-primary-light-5': isDark.value\n          ? getCssVariableValue('--primary-700')\n          : getCssVariableValue('--primary-300'),\n        '--el-color-primary-light-7': isDark.value\n          ? getCssVariableValue('--primary-800')\n          : getCssVariableValue('--primary-200'),\n        '--el-color-primary-light-8': isDark.value\n          ? getCssVariableValue('--primary-900')\n          : getCssVariableValue('--primary-100'),\n        '--el-color-primary-light-9': isDark.value\n          ? getCssVariableValue('--primary-950')\n          : getCssVariableValue('--primary-50'),\n\n        '--el-color-success': getCssVariableValue('--success-500'),\n        '--el-color-success-dark-2': isDark.value\n          ? getCssVariableValue('--success-400')\n          : getCssVariableValue('--success-600'),\n        '--el-color-success-light-3': isDark.value\n          ? getCssVariableValue('--success-600')\n          : getCssVariableValue('--success-400'),\n        '--el-color-success-light-5': isDark.value\n          ? getCssVariableValue('--success-700')\n          : getCssVariableValue('--success-300'),\n        '--el-color-success-light-7': isDark.value\n          ? getCssVariableValue('--success-800')\n          : getCssVariableValue('--success-200'),\n        '--el-color-success-light-8': isDark.value\n          ? getCssVariableValue('--success-900')\n          : getCssVariableValue('--success-100'),\n        '--el-color-success-light-9': isDark.value\n          ? getCssVariableValue('--success-950')\n          : getCssVariableValue('--success-50'),\n\n        '--el-color-warning': getCssVariableValue('--warning-500'),\n        '--el-color-warning-dark-2': isDark.value\n          ? getCssVariableValue('--warning-400')\n          : getCssVariableValue('--warning-600'),\n        '--el-color-warning-light-3': isDark.value\n          ? getCssVariableValue('--warning-600')\n          : getCssVariableValue('--warning-400'),\n        '--el-color-warning-light-5': isDark.value\n          ? getCssVariableValue('--warning-700')\n          : getCssVariableValue('--warning-300'),\n        '--el-color-warning-light-7': isDark.value\n          ? getCssVariableValue('--warning-800')\n          : getCssVariableValue('--warning-200'),\n        '--el-color-warning-light-8': isDark.value\n          ? getCssVariableValue('--warning-900')\n          : getCssVariableValue('--warning-100'),\n        '--el-color-warning-light-9': isDark.value\n          ? getCssVariableValue('--warning-950')\n          : getCssVariableValue('--warning-50'),\n\n        '--el-fill-color': getCssVariableValue('--accent'),\n        '--el-fill-color-blank': background,\n        '--el-fill-color-light': getCssVariableValue('--accent'),\n        '--el-fill-color-lighter': getCssVariableValue('--accent-lighter'),\n\n        '--el-fill-color-dark': getCssVariableValue('--accent-dark'),\n        '--el-fill-color-darker': getCssVariableValue('--accent-darker'),\n\n        // 解决ElLoading背景色问题\n        '--el-mask-color': isDark.value\n          ? 'rgba(0,0,0,.8)'\n          : 'rgba(255,255,255,.9)',\n\n        '--el-text-color-primary': getCssVariableValue('--foreground'),\n\n        '--el-text-color-regular': getCssVariableValue('--foreground'),\n      };\n\n      updateCSSVariables(variables, `__vben_design_styles__`);\n    },\n    { immediate: true },\n  );\n}\n"
  },
  {
    "path": "packages/effects/hooks/src/use-hover-toggle.ts",
    "content": "import type { Arrayable, MaybeElementRef } from '@vueuse/core';\n\nimport type { Ref } from 'vue';\n\nimport { computed, effectScope, onUnmounted, ref, unref, watch } from 'vue';\n\nimport { isFunction } from '@vben/utils';\n\nimport { useElementHover } from '@vueuse/core';\n\ninterface HoverDelayOptions {\n  /** 鼠标进入延迟时间 */\n  enterDelay?: (() => number) | number;\n  /** 鼠标离开延迟时间 */\n  leaveDelay?: (() => number) | number;\n}\n\nconst DEFAULT_LEAVE_DELAY = 500; // 鼠标离开延迟时间，默认为 500ms\nconst DEFAULT_ENTER_DELAY = 0; // 鼠标进入延迟时间，默认为 0（立即响应）\n\n/**\n * 监测鼠标是否在元素内部，如果在元素内部则返回 true，否则返回 false\n * @param refElement 所有需要检测的元素。支持单个元素、元素数组或响应式引用的元素数组。如果鼠标在任何一个元素内部都会返回 true\n * @param delay 延迟更新状态的时间，可以是数字或包含进入/离开延迟的配置对象\n * @returns 返回一个数组，第一个元素是一个 ref，表示鼠标是否在元素内部，第二个元素是一个控制器，可以通过 enable 和 disable 方法来控制监听器的启用和禁用\n */\nexport function useHoverToggle(\n  refElement: Arrayable<MaybeElementRef> | Ref<HTMLElement[] | null>,\n  delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY,\n) {\n  // 兼容旧版本API\n  const normalizedOptions: HoverDelayOptions =\n    typeof delay === 'number' || isFunction(delay)\n      ? { enterDelay: DEFAULT_ENTER_DELAY, leaveDelay: delay }\n      : {\n          enterDelay: DEFAULT_ENTER_DELAY,\n          leaveDelay: DEFAULT_LEAVE_DELAY,\n          ...delay,\n        };\n\n  const value = ref(false);\n  const enterTimer = ref<ReturnType<typeof setTimeout> | undefined>();\n  const leaveTimer = ref<ReturnType<typeof setTimeout> | undefined>();\n  const hoverScopes = ref<ReturnType<typeof effectScope>[]>([]);\n\n  // 使用计算属性包装 refElement，使其响应式变化\n  const refs = computed(() => {\n    const raw = unref(refElement);\n    if (raw === null) return [];\n    return Array.isArray(raw) ? raw : [raw];\n  });\n  // 存储所有 hover 状态\n  const isHovers = ref<Array<Ref<boolean>>>([]);\n\n  // 更新 hover 监听的函数\n  function updateHovers() {\n    // 停止并清理之前的作用域\n    hoverScopes.value.forEach((scope) => scope.stop());\n    hoverScopes.value = [];\n\n    isHovers.value = refs.value.map((refEle) => {\n      if (!refEle) {\n        return ref(false);\n      }\n      const eleRef = computed(() => {\n        const ele = unref(refEle);\n        return ele instanceof Element ? ele : (ele?.$el as Element);\n      });\n\n      // 为每个元素创建独立的作用域\n      const scope = effectScope();\n      const hoverRef = scope.run(() => useElementHover(eleRef)) || ref(false);\n      hoverScopes.value.push(scope);\n\n      return hoverRef;\n    });\n  }\n\n  // 监听元素数量变化，避免过度执行\n  const elementsCount = computed(() => {\n    const raw = unref(refElement);\n    if (raw === null) return 0;\n    return Array.isArray(raw) ? raw.length : 1;\n  });\n\n  // 初始设置\n  updateHovers();\n\n  // 只在元素数量变化时重新设置监听器\n  const stopWatcher = watch(elementsCount, updateHovers, { deep: false });\n\n  const isOutsideAll = computed(() => isHovers.value.every((v) => !v.value));\n\n  function clearTimers() {\n    if (enterTimer.value) {\n      clearTimeout(enterTimer.value);\n      enterTimer.value = undefined;\n    }\n    if (leaveTimer.value) {\n      clearTimeout(leaveTimer.value);\n      leaveTimer.value = undefined;\n    }\n  }\n\n  function setValueDelay(val: boolean) {\n    clearTimers();\n\n    if (val) {\n      // 鼠标进入\n      const enterDelay = normalizedOptions.enterDelay ?? DEFAULT_ENTER_DELAY;\n      const delayTime = isFunction(enterDelay) ? enterDelay() : enterDelay;\n\n      if (delayTime <= 0) {\n        value.value = true;\n      } else {\n        enterTimer.value = setTimeout(() => {\n          value.value = true;\n          enterTimer.value = undefined;\n        }, delayTime);\n      }\n    } else {\n      // 鼠标离开\n      const leaveDelay = normalizedOptions.leaveDelay ?? DEFAULT_LEAVE_DELAY;\n      const delayTime = isFunction(leaveDelay) ? leaveDelay() : leaveDelay;\n\n      if (delayTime <= 0) {\n        value.value = false;\n      } else {\n        leaveTimer.value = setTimeout(() => {\n          value.value = false;\n          leaveTimer.value = undefined;\n        }, delayTime);\n      }\n    }\n  }\n\n  const hoverWatcher = watch(\n    isOutsideAll,\n    (val) => {\n      setValueDelay(!val);\n    },\n    { immediate: true },\n  );\n\n  const controller = {\n    enable() {\n      hoverWatcher.resume();\n    },\n    disable() {\n      hoverWatcher.pause();\n    },\n  };\n\n  onUnmounted(() => {\n    clearTimers();\n    // 停止监听器\n    stopWatcher();\n    // 停止所有剩余的作用域\n    hoverScopes.value.forEach((scope) => scope.stop());\n  });\n\n  return [value, controller] as [typeof value, typeof controller];\n}\n"
  },
  {
    "path": "packages/effects/hooks/src/use-pagination.ts",
    "content": "import type { Ref } from 'vue';\n\nimport { computed, ref, unref, watch } from 'vue';\n\n/**\n * Paginates an array of items\n * @param list The array to paginate\n * @param pageNo The current page number (1-based)\n * @param pageSize Number of items per page\n * @returns Paginated array slice\n * @throws {Error} If pageNo or pageSize are invalid\n */\nfunction pagination<T = any>(list: T[], pageNo: number, pageSize: number): T[] {\n  if (pageNo < 1) throw new Error('Page number must be positive');\n  if (pageSize < 1) throw new Error('Page size must be positive');\n\n  const offset = (pageNo - 1) * Number(pageSize);\n  const ret =\n    offset + pageSize >= list.length\n      ? list.slice(offset)\n      : list.slice(offset, offset + pageSize);\n  return ret;\n}\n\nexport function usePagination<T = any>(\n  list: Ref<T[]>,\n  pageSize: number,\n  totalChangeToFirstPage = true,\n) {\n  const currentPage = ref(1);\n  const pageSizeRef = ref(pageSize);\n\n  const totalPages = computed(() =>\n    Math.ceil(unref(list).length / unref(pageSizeRef)),\n  );\n\n  const paginationList = computed(() => {\n    return pagination(unref(list), unref(currentPage), unref(pageSizeRef));\n  });\n\n  const total = computed(() => {\n    return unref(list).length;\n  });\n\n  if (totalChangeToFirstPage) {\n    watch(total, () => {\n      setCurrentPage(1);\n    });\n  }\n\n  function setCurrentPage(page: number) {\n    if (page === 1 && unref(totalPages) === 0) {\n      currentPage.value = 1;\n    } else {\n      if (page < 1 || page > unref(totalPages)) {\n        throw new Error('Invalid page number');\n      }\n      currentPage.value = page;\n    }\n  }\n\n  function setPageSize(pageSize: number) {\n    if (pageSize < 1) {\n      throw new Error('Page size must be positive');\n    }\n    pageSizeRef.value = pageSize;\n    // Reset to first page to prevent invalid state\n    currentPage.value = 1;\n  }\n\n  return { setCurrentPage, total, setPageSize, paginationList, currentPage };\n}\n"
  },
  {
    "path": "packages/effects/hooks/src/use-refresh.ts",
    "content": "import { useRouter } from 'vue-router';\n\nimport { useTabbarStore } from '@vben/stores';\n\nexport function useRefresh() {\n  const router = useRouter();\n  const tabbarStore = useTabbarStore();\n\n  async function refresh() {\n    await tabbarStore.refresh(router);\n  }\n\n  return {\n    refresh,\n  };\n}\n"
  },
  {
    "path": "packages/effects/hooks/src/use-tabs.ts",
    "content": "import type { ComputedRef } from 'vue';\nimport type { RouteLocationNormalized } from 'vue-router';\n\nimport { useRoute, useRouter } from 'vue-router';\n\nimport { useTabbarStore } from '@vben/stores';\n\nexport function useTabs() {\n  const router = useRouter();\n  const route = useRoute();\n  const tabbarStore = useTabbarStore();\n\n  async function closeLeftTabs(tab?: RouteLocationNormalized) {\n    await tabbarStore.closeLeftTabs(tab || route);\n  }\n\n  async function closeAllTabs() {\n    await tabbarStore.closeAllTabs(router);\n  }\n\n  async function closeRightTabs(tab?: RouteLocationNormalized) {\n    await tabbarStore.closeRightTabs(tab || route);\n  }\n\n  async function closeOtherTabs(tab?: RouteLocationNormalized) {\n    await tabbarStore.closeOtherTabs(tab || route);\n  }\n\n  async function closeCurrentTab(tab?: RouteLocationNormalized) {\n    await tabbarStore.closeTab(tab || route, router);\n  }\n\n  async function pinTab(tab?: RouteLocationNormalized) {\n    await tabbarStore.pinTab(tab || route);\n  }\n\n  async function unpinTab(tab?: RouteLocationNormalized) {\n    await tabbarStore.unpinTab(tab || route);\n  }\n\n  async function toggleTabPin(tab?: RouteLocationNormalized) {\n    await tabbarStore.toggleTabPin(tab || route);\n  }\n\n  async function refreshTab(name?: string) {\n    await tabbarStore.refresh(name || router);\n  }\n\n  async function openTabInNewWindow(tab?: RouteLocationNormalized) {\n    await tabbarStore.openTabInNewWindow(tab || route);\n  }\n\n  async function closeTabByKey(key: string) {\n    await tabbarStore.closeTabByKey(key, router);\n  }\n\n  /**\n   * 设置当前标签页的标题\n   *\n   * @description 支持设置静态标题字符串或动态计算标题\n   * @description 动态标题会在每次渲染时重新计算,适用于多语言或状态相关的标题\n   *\n   * @param title - 标题内容\n   *   - 静态标题: 直接传入字符串\n   *   - 动态标题: 传入 ComputedRef\n   *\n   * @example\n   * // 静态标题\n   * setTabTitle('标签页')\n   *\n   * // 动态标题(多语言)\n   * setTabTitle(computed(() => t('page.title')))\n   */\n  async function setTabTitle(title: ComputedRef<string> | string) {\n    tabbarStore.setUpdateTime();\n    await tabbarStore.setTabTitle(route, title);\n  }\n\n  async function resetTabTitle() {\n    tabbarStore.setUpdateTime();\n    await tabbarStore.resetTabTitle(route);\n  }\n\n  /**\n   * 获取操作是否禁用\n   * @param tab\n   */\n  function getTabDisableState(tab: RouteLocationNormalized = route) {\n    const tabs = tabbarStore.getTabs;\n    const affixTabs = tabbarStore.affixTabs;\n    const index = tabs.findIndex((item) => item.path === tab.path);\n\n    const disabled = tabs.length <= 1;\n\n    const { meta } = tab;\n    const affixTab = meta?.affixTab ?? false;\n    const isCurrentTab = route.path === tab.path;\n\n    // 当前处于最左侧或者减去固定标签页的数量等于0\n    const disabledCloseLeft =\n      index === 0 || index - affixTabs.length <= 0 || !isCurrentTab;\n\n    const disabledCloseRight = !isCurrentTab || index === tabs.length - 1;\n\n    const disabledCloseOther =\n      disabled || !isCurrentTab || tabs.length - affixTabs.length <= 1;\n    return {\n      disabledCloseAll: disabled,\n      disabledCloseCurrent: !!affixTab || disabled,\n      disabledCloseLeft,\n      disabledCloseOther,\n      disabledCloseRight,\n      disabledRefresh: !isCurrentTab,\n    };\n  }\n\n  return {\n    closeAllTabs,\n    closeCurrentTab,\n    closeLeftTabs,\n    closeOtherTabs,\n    closeRightTabs,\n    closeTabByKey,\n    getTabDisableState,\n    openTabInNewWindow,\n    pinTab,\n    refreshTab,\n    resetTabTitle,\n    setTabTitle,\n    toggleTabPin,\n    unpinTab,\n  };\n}\n"
  },
  {
    "path": "packages/effects/hooks/src/use-watermark.ts",
    "content": "import type { Watermark, WatermarkOptions } from 'watermark-js-plus';\n\nimport { nextTick, onUnmounted, readonly, ref } from 'vue';\n\nconst watermark = ref<Watermark>();\nconst unmountedHooked = ref<boolean>(false);\nconst cachedOptions = ref<Partial<WatermarkOptions>>({\n  advancedStyle: {\n    colorStops: [\n      {\n        color: 'gray',\n        offset: 0,\n      },\n      {\n        color: 'gray',\n        offset: 1,\n      },\n    ],\n    type: 'linear',\n  },\n  // fontSize: '20px',\n  content: '',\n  contentType: 'multi-line-text',\n  globalAlpha: 0.25,\n  gridLayoutOptions: {\n    cols: 2,\n    gap: [20, 20],\n    matrix: [\n      [1, 0],\n      [0, 1],\n    ],\n    rows: 2,\n  },\n  height: 200,\n  layout: 'grid',\n  rotate: 30,\n  width: 160,\n});\n\nexport function useWatermark() {\n  async function initWatermark(options: Partial<WatermarkOptions>) {\n    const { Watermark } = await import('watermark-js-plus');\n\n    cachedOptions.value = {\n      ...cachedOptions.value,\n      ...options,\n    };\n    watermark.value = new Watermark(cachedOptions.value);\n    await watermark.value?.create();\n  }\n\n  async function updateWatermark(options: Partial<WatermarkOptions>) {\n    if (watermark.value) {\n      await nextTick();\n      await watermark.value?.changeOptions({\n        ...cachedOptions.value,\n        ...options,\n      });\n    } else {\n      await initWatermark(options);\n    }\n  }\n\n  function destroyWatermark() {\n    if (watermark.value) {\n      watermark.value.destroy();\n      watermark.value = undefined;\n    }\n  }\n\n  // 只在第一次调用时注册卸载钩子，防止重复注册以致于在路由切换时销毁了水印\n  if (!unmountedHooked.value) {\n    unmountedHooked.value = true;\n    onUnmounted(() => {\n      destroyWatermark();\n    });\n  }\n\n  return {\n    destroyWatermark,\n    updateWatermark,\n    watermark: readonly(watermark),\n  };\n}\n"
  },
  {
    "path": "packages/effects/hooks/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"compilerOptions\": {\n    \"types\": [\"vite/client\", \"@vben/types/global\"]\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/effects/layouts/package.json",
    "content": "{\n  \"name\": \"@vben/layouts\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/effects/layouts\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/composables\": \"workspace:*\",\n    \"@vben-core/design\": \"workspace:*\",\n    \"@vben-core/form-ui\": \"workspace:*\",\n    \"@vben-core/layout-ui\": \"workspace:*\",\n    \"@vben-core/menu-ui\": \"workspace:*\",\n    \"@vben-core/popup-ui\": \"workspace:*\",\n    \"@vben-core/shadcn-ui\": \"workspace:*\",\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben-core/tabs-ui\": \"workspace:*\",\n    \"@vben/constants\": \"workspace:*\",\n    \"@vben/hooks\": \"workspace:*\",\n    \"@vben/icons\": \"workspace:*\",\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/preferences\": \"workspace:*\",\n    \"@vben/stores\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/effects/layouts/src/authentication/authentication.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ToolbarType } from './types';\n\nimport { computed } from 'vue';\n\nimport { preferences, usePreferences } from '@vben/preferences';\n\nimport { Copyright } from '../basic/copyright';\nimport AuthenticationFormView from './form.vue';\nimport SloganIcon from './icons/slogan.vue';\nimport Toolbar from './toolbar.vue';\n\ninterface Props {\n  appName?: string;\n  logo?: string;\n  logoDark?: string;\n  pageTitle?: string;\n  pageDescription?: string;\n  sloganImage?: string;\n  toolbar?: boolean;\n  copyright?: boolean;\n  toolbarList?: ToolbarType[];\n  clickLogo?: () => void;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  appName: '',\n  copyright: true,\n  logo: '',\n  logoDark: '',\n  pageDescription: '',\n  pageTitle: '',\n  sloganImage: '',\n  toolbar: true,\n  toolbarList: () => ['color', 'language', 'layout', 'theme'],\n  clickLogo: () => {},\n});\n\nconst { authPanelCenter, authPanelLeft, authPanelRight, isDark } =\n  usePreferences();\n\n/**\n * @zh_CN 根据主题选择合适的 logo 图标\n */\nconst logoSrc = computed(() => {\n  // 如果是暗色主题且提供了 logoDark，则使用暗色主题的 logo\n  if (isDark.value && props.logoDark) {\n    return props.logoDark;\n  }\n  // 否则使用默认的 logo\n  return props.logo;\n});\n</script>\n\n<template>\n  <div\n    :class=\"[isDark ? 'dark' : '']\"\n    class=\"flex min-h-full flex-1 overflow-x-hidden select-none\"\n  >\n    <template v-if=\"toolbar\">\n      <slot name=\"toolbar\">\n        <Toolbar :toolbar-list=\"toolbarList\" />\n      </slot>\n    </template>\n    <!-- 左侧认证面板 -->\n    <AuthenticationFormView\n      v-if=\"authPanelLeft\"\n      class=\"min-h-full w-2/5 flex-1\"\n      data-side=\"left\"\n    >\n      <template v-if=\"copyright\" #copyright>\n        <slot name=\"copyright\">\n          <Copyright\n            v-if=\"preferences.copyright.enable\"\n            v-bind=\"preferences.copyright\"\n          />\n        </slot>\n      </template>\n    </AuthenticationFormView>\n\n    <slot name=\"logo\">\n      <!-- 头部 Logo 和应用名称 -->\n      <div\n        v-if=\"logoSrc || appName\"\n        class=\"absolute top-0 left-0 z-10 flex flex-1\"\n        @click=\"clickLogo\"\n      >\n        <div\n          class=\"mt-4 ml-4 flex flex-1 items-center text-foreground sm:top-6 sm:left-6 lg:text-foreground\"\n        >\n          <img\n            v-if=\"logoSrc\"\n            :key=\"logoSrc\"\n            :alt=\"appName\"\n            :src=\"logoSrc\"\n            class=\"mr-2\"\n            width=\"42\"\n          />\n          <p v-if=\"appName\" class=\"m-0 text-xl font-medium\">\n            {{ appName }}\n          </p>\n        </div>\n      </div>\n    </slot>\n\n    <!-- 系统介绍 -->\n    <div v-if=\"!authPanelCenter\" class=\"relative hidden w-0 flex-1 lg:block\">\n      <div\n        class=\"absolute inset-0 size-full bg-background-deep dark:bg-[#070709]\"\n      >\n        <div class=\"login-background absolute top-0 left-0 size-full\"></div>\n        <div\n          :key=\"authPanelLeft ? 'left' : authPanelRight ? 'right' : 'center'\"\n          class=\"mr-20 flex-col-center h-full\"\n          :class=\"{\n            'enter-x': authPanelLeft,\n            '-enter-x': authPanelRight,\n          }\"\n        >\n          <template v-if=\"sloganImage\">\n            <img\n              :alt=\"appName\"\n              :src=\"sloganImage\"\n              class=\"h-64 w-2/5 animate-float\"\n            />\n          </template>\n          <SloganIcon v-else :alt=\"appName\" class=\"h-64 w-2/5 animate-float\" />\n          <div class=\"text-1xl mt-6 font-sans text-foreground lg:text-2xl\">\n            {{ pageTitle }}\n          </div>\n          <div class=\"mt-2 dark:text-muted-foreground\">\n            {{ pageDescription }}\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <!-- 中心认证面板 -->\n    <div v-if=\"authPanelCenter\" class=\"relative flex-center w-full\">\n      <div class=\"login-background absolute top-0 left-0 size-full\"></div>\n      <AuthenticationFormView\n        class=\"w-full rounded-3xl pb-20 shadow-float shadow-primary/5 md:w-2/3 md:bg-background lg:w-1/2 xl:w-[36%]\"\n        data-side=\"bottom\"\n      >\n        <template v-if=\"copyright\" #copyright>\n          <slot name=\"copyright\">\n            <Copyright\n              v-if=\"preferences.copyright.enable\"\n              v-bind=\"preferences.copyright\"\n            />\n          </slot>\n        </template>\n      </AuthenticationFormView>\n    </div>\n\n    <!-- 右侧认证面板 -->\n    <AuthenticationFormView\n      v-if=\"authPanelRight\"\n      class=\"min-h-full w-2/5 flex-1\"\n      data-side=\"right\"\n    >\n      <template v-if=\"copyright\" #copyright>\n        <slot name=\"copyright\">\n          <Copyright\n            v-if=\"preferences.copyright.enable\"\n            v-bind=\"preferences.copyright\"\n          />\n        </slot>\n      </template>\n    </AuthenticationFormView>\n  </div>\n</template>\n\n<style scoped>\n.login-background {\n  background: linear-gradient(\n    154deg,\n    #07070915 30%,\n    hsl(var(--primary) / 30%) 48%,\n    #07070915 64%\n  );\n  filter: blur(100px);\n}\n\n.dark {\n  .login-background {\n    background: linear-gradient(\n      154deg,\n      #07070915 30%,\n      hsl(var(--primary) / 20%) 48%,\n      #07070915 64%\n    );\n    filter: blur(100px);\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/effects/layouts/src/authentication/form.vue",
    "content": "<script setup lang=\"ts\">\ndefineOptions({\n  name: 'AuthenticationFormView',\n});\n\ndefineProps<{\n  dataSide?: 'bottom' | 'left' | 'right' | 'top';\n}>();\n</script>\n\n<template>\n  <div\n    class=\"relative flex-col-center bg-background px-6 py-10 lg:flex-initial lg:px-8 dark:bg-background-deep\"\n  >\n    <slot></slot>\n    <!-- Router View with Transition and KeepAlive -->\n    <RouterView v-slot=\"{ Component, route }\">\n      <Transition appear mode=\"out-in\" name=\"slide-right\">\n        <KeepAlive :include=\"['Login']\">\n          <component\n            :is=\"Component\"\n            :key=\"route.fullPath\"\n            class=\"side-content mt-6 w-full sm:mx-auto md:max-w-md\"\n            :data-side=\"dataSide\"\n          />\n        </KeepAlive>\n      </Transition>\n    </RouterView>\n\n    <!-- Footer Copyright -->\n\n    <div\n      class=\"absolute bottom-3 flex text-center text-xs text-muted-foreground\"\n    >\n      <slot name=\"copyright\"> </slot>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/authentication/icons/slogan.vue",
    "content": "<template>\n  <svg\n    enable-background=\"new 0 0 800 800\"\n    version=\"1.1\"\n    viewBox=\"0 0 800 800\"\n    x=\"0px\"\n    xml:space=\"preserve\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n    y=\"0px\"\n  >\n    <filter\n      id=\"filter-95\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"124.9%\"\n      width=\"114.5%\"\n      x=\"-7.3%\"\n      y=\"-12.5%\"\n    >\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"4.21434086\" />\n    </filter>\n    <filter\n      id=\"filter-79\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"101.4%\"\n      width=\"100.8%\"\n      x=\"-0.4%\"\n      y=\"-0.7%\"\n    >\n      <feOffset dx=\"0\" dy=\"-1\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.498880026 0\"\n      />\n    </filter>\n    <filter\n      id=\"filter-71\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"142.5%\"\n      width=\"120.1%\"\n      x=\"-10.1%\"\n      y=\"-21.3%\"\n    >\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"4.21434086\" />\n    </filter>\n    <filter\n      id=\"filter-59\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"181.0%\"\n      width=\"133.4%\"\n      x=\"-16.7%\"\n      y=\"-40.5%\"\n    >\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"4.85695311\" />\n    </filter>\n    <filter\n      id=\"filter-56\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"181.0%\"\n      width=\"133.4%\"\n      x=\"-16.7%\"\n      y=\"-40.5%\"\n    >\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"2.91417187\" />\n    </filter>\n    <filter\n      id=\"filter-53\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"100.9%\"\n      width=\"100.5%\"\n      x=\"-0.2%\"\n      y=\"-0.4%\"\n    >\n      <feOffset dx=\"0\" dy=\"-1\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.498880026 0\"\n      />\n    </filter>\n    <filter\n      id=\"filter-50\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"241.5%\"\n      width=\"643.8%\"\n      x=\"-271.9%\"\n      y=\"-70.7%\"\n    >\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"16.2954513\" />\n    </filter>\n    <filter\n      id=\"filter-5\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"100.8%\"\n      width=\"100.4%\"\n      x=\"-0.2%\"\n      y=\"-0.4%\"\n    >\n      <feOffset dx=\"0\" dy=\"-3\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.498880026 0\"\n      />\n    </filter>\n    <filter\n      id=\"filter-46\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"100.7%\"\n      width=\"100.4%\"\n      x=\"-0.2%\"\n      y=\"-0.3%\"\n    >\n      <feOffset dx=\"0\" dy=\"-1\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.498880026 0\"\n      />\n    </filter>\n    <filter\n      id=\"filter-43\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"241.5%\"\n      width=\"643.8%\"\n      x=\"-271.9%\"\n      y=\"-70.7%\"\n    >\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"20.3693141\" />\n    </filter>\n    <filter\n      id=\"filter-39\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"113.3%\"\n      width=\"107.0%\"\n      x=\"-3.5%\"\n      y=\"-6.7%\"\n    >\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"3.51195071\" />\n    </filter>\n    <filter\n      id=\"filter-34\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"103.6%\"\n      width=\"102.1%\"\n      x=\"-1.1%\"\n      y=\"-1.8%\"\n    >\n      <feOffset dx=\"0\" dy=\"-1\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.833806818 0\"\n      />\n    </filter>\n    <filter\n      id=\"filter-29\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"103.6%\"\n      width=\"102.1%\"\n      x=\"-1.1%\"\n      y=\"-1.8%\"\n    >\n      <feOffset dx=\"0\" dy=\"-1\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.833806818 0\"\n      />\n    </filter>\n    <filter\n      id=\"filter-24\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"103.6%\"\n      width=\"102.1%\"\n      x=\"-1.1%\"\n      y=\"-1.8%\"\n    >\n      <feOffset dx=\"0\" dy=\"-1\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.833806818 0\"\n      />\n    </filter>\n    <filter\n      id=\"filter-20\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"102.3%\"\n      width=\"101.3%\"\n      x=\"-0.7%\"\n      y=\"-1.1%\"\n    >\n      <feOffset dx=\"0\" dy=\"-3\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.498880026 0\"\n      />\n    </filter>\n    <filter\n      id=\"filter-14\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"103.6%\"\n      width=\"102.1%\"\n      x=\"-1.0%\"\n      y=\"-1.8%\"\n    >\n      <feOffset dx=\"0\" dy=\"-1\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.833806818 0\"\n      />\n    </filter>\n    <filter\n      id=\"filter-109\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"102.5%\"\n      width=\"101.4%\"\n      x=\"-0.7%\"\n      y=\"-1.2%\"\n    >\n      <feOffset dx=\"0\" dy=\"-1\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.833806818 0\"\n      />\n    </filter>\n    <filter\n      id=\"filter-106\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"149.1%\"\n      width=\"128.6%\"\n      x=\"-14.3%\"\n      y=\"-24.5%\"\n    >\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"4.71559623\" />\n    </filter>\n    <filter\n      id=\"filter-100\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"133.5%\"\n      width=\"119.4%\"\n      x=\"-9.7%\"\n      y=\"-16.8%\"\n    >\n      <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"4.21434086\" />\n    </filter>\n    <filter\n      id=\"filter-10\"\n      filterUnits=\"objectBoundingBox\"\n      height=\"100.7%\"\n      width=\"100.4%\"\n      x=\"-0.2%\"\n      y=\"-0.3%\"\n    >\n      <feOffset dx=\"0\" dy=\"-3\" in=\"SourceAlpha\" result=\"shadowOffsetInner1\" />\n      <feComposite\n        in=\"shadowOffsetInner1\"\n        in2=\"SourceAlpha\"\n        k2=\"-1\"\n        k3=\"1\"\n        operator=\"arithmetic\"\n        result=\"shadowInnerInner1\"\n      />\n      <feColorMatrix\n        in=\"shadowInnerInner1\"\n        type=\"matrix\"\n        values=\"0 0 0 0 1   0 0 0 0 1   0 0 0 0 1  0 0 0 0.498880026 0\"\n      />\n    </filter>\n    <title>87667-SVG8</title>\n    <g>\n      <g id=\"Group备份\" transform=\"translate(40.000000, 350.000000)\">\n        <linearGradient\n          id=\"Shape_1_\"\n          gradientTransform=\"matrix(352.3498 0 0 -229.3688 -21376.5684 335815.1875)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"62.7934\"\n          x2=\"61.7099\"\n          y1=\"1462.9332\"\n          y2=\"1462.535\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #0043fb\" />\n          <stop offset=\"1\" style=\"stop-color: #3684ff\" />\n        </linearGradient>\n        <path\n          id=\"Shape\"\n          d=\"M368,428.9c1.4,0.8,3.2,0.8,4.6,0l344.4-198.3c2.8-1.6,3.4-5.4,1.2-7.8l-16.3-22.7\n\t\t\tL370.3,395.7v31.6l-3.2,1L368,428.9z\"\n          fill=\"url(#Shape_1_)\"\n        />\n\n        <linearGradient\n          id=\"SVGID_1_\"\n          gradientTransform=\"matrix(369.5781 0 0 -241.7468 -22811.1367 353968.8438)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"63.31\"\n          x2=\"61.5984\"\n          y1=\"1462.5217\"\n          y2=\"1463.1935\"\n        >\n          <stop offset=\"5.400000e-03\" style=\"stop-color: #00c6fb\" />\n          <stop offset=\"1\" style=\"stop-color: #005bea\" />\n        </linearGradient>\n        <path\n          d=\"M370.3,427.3c0,1.3-1.4,2.1-2.5,1.4L3.2,217.3c-2.8-1.6-3.4-5.4-1.2-7.8l17.8-22.3l350.5,208.4\n\t\t\tV427.3L370.3,427.3z\"\n          fill=\"url(#SVGID_1_)\"\n        />\n        <g>\n          <g>\n            <linearGradient\n              id=\"path-4_1_\"\n              gradientTransform=\"matrix(689.8593 0 0 -397.2563 -42654.3008 582014.8125)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"62.5423\"\n              x2=\"62.1013\"\n              y1=\"1464.4305\"\n              y2=\"1464.7888\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n              <stop offset=\"1\" style=\"stop-color: #00209e\" />\n            </linearGradient>\n            <path\n              id=\"path-4\"\n              clip-rule=\"evenodd\"\n              d=\"M355.7,393.8L18,198.7\n\t\t\t\t\tc-4-2.3-4-8.1,0-10.4L337.8,3.6c7.4-4.3,16.5-4.3,24,0l340.1,196.5c4,2.3,4,8.1,0,10.4L384.4,393.8\n\t\t\t\t\tC375.5,398.9,364.6,398.9,355.7,393.8z\"\n              fill=\"url(#path-4_1_)\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n          <g filter=\"url(#filter-5)\">\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M355.7,393.8L18,198.7c-4-2.3-4-8.1,0-10.4L337.8,3.6c7.4-4.3,16.5-4.3,24,0\n\t\t\t\t\tl340.1,196.5c4,2.3,4,8.1,0,10.4L384.4,393.8C375.5,398.9,364.6,398.9,355.7,393.8z\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n        </g>\n        <path\n          id=\"Shape备份\"\n          d=\"M356.1,376L49.6,199c-3.6-2.1-3.6-7.4,0-9.5\n\t\t\tL339.8,21.9c6.7-3.9,15-3.9,21.7,0l308.6,178.3c3.6,2.1,3.6,7.4,0,9.5L382.1,376C374.1,380.7,364.1,380.7,356.1,376z\"\n          fill=\"none\"\n          stroke=\"#FFFFFF\"\n          stroke-width=\"0.9511\"\n        />\n      </g>\n      <g id=\"Group\" transform=\"translate(0.000000, 259.000000)\">\n        <linearGradient\n          id=\"SVGID_2_\"\n          gradientTransform=\"matrix(391.4998 0 0 -254.8542 -39423.0664 396432.7188)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"102.8224\"\n          x2=\"101.7388\"\n          y1=\"1554.3767\"\n          y2=\"1553.9784\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #0043fb\" />\n          <stop offset=\"1\" style=\"stop-color: #3684ff\" />\n        </linearGradient>\n        <path\n          d=\"M408.9,476.5c1.6,0.9,3.5,0.9,5.1,0l382.6-220.3c3.1-1.8,3.8-6,1.3-8.7l-18.1-25.2L411.4,439.6\n\t\t\tv35.2l-3.5,1.1L408.9,476.5z\"\n          fill=\"url(#SVGID_2_)\"\n        />\n\n        <linearGradient\n          id=\"SVGID_3_\"\n          gradientTransform=\"matrix(410.6423 0 0 -268.6075 -41782.7344 417855)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"103.3376\"\n          x2=\"101.626\"\n          y1=\"1553.9424\"\n          y2=\"1554.6141\"\n        >\n          <stop offset=\"5.400000e-03\" style=\"stop-color: #00c6fb\" />\n          <stop offset=\"1\" style=\"stop-color: #005bea\" />\n        </linearGradient>\n        <path\n          d=\"M411.4,474.8c0,1.4-1.5,2.3-2.8,1.6L3.6,241.5c-3.1-1.8-3.8-6-1.3-8.7L22,208l389.4,231.6V474.8\n\t\t\tL411.4,474.8z\"\n          fill=\"url(#SVGID_3_)\"\n        />\n        <g>\n          <g>\n            <linearGradient\n              id=\"path-9_1_\"\n              gradientTransform=\"matrix(766.5103 0 0 -441.3958 -78065.4141 686963.125)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"102.5571\"\n              x2=\"102.1161\"\n              y1=\"1555.6865\"\n              y2=\"1556.0448\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n              <stop offset=\"1\" style=\"stop-color: #00209e\" />\n            </linearGradient>\n            <path\n              id=\"path-9\"\n              clip-rule=\"evenodd\"\n              d=\"M395.2,437.6L20,220.8\n\t\t\t\t\tc-4.5-2.6-4.5-9,0-11.6L375.3,4c8.2-4.8,18.4-4.8,26.6,0l377.9,218.3c4.5,2.6,4.5,9,0,11.6L427.1,437.6\n\t\t\t\t\tC417.2,443.3,405.1,443.3,395.2,437.6z\"\n              fill=\"url(#path-9_1_)\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n          <g filter=\"url(#filter-10)\">\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M395.2,437.6L20,220.8c-4.5-2.6-4.5-9,0-11.6L375.3,4c8.2-4.8,18.4-4.8,26.6,0\n\t\t\t\t\tl377.9,218.3c4.5,2.6,4.5,9,0,11.6L427.1,437.6C417.2,443.3,405.1,443.3,395.2,437.6z\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n        </g>\n        <path\n          d=\"M395.6,417.8L55.2,221.1c-4.1-2.3-4.1-8.2,0-10.5L377.6,24.4\n\t\t\tc7.5-4.3,16.7-4.3,24.2,0l342.9,198.1c4.1,2.3,4.1,8.2,0,10.5L424.6,417.8C415.6,423,404.6,423,395.6,417.8z\"\n          fill=\"none\"\n          stroke=\"#FFFFFF\"\n          stroke-width=\"1.0568\"\n        />\n      </g>\n      <g id=\"编组备份-4\" transform=\"translate(386.000000, 561.000000)\">\n        <g id=\"编组备份-3\" transform=\"translate(0.000000, 69.529529)\">\n          <g id=\"Stroke-1\">\n            <g>\n              <linearGradient\n                id=\"path-13_1_\"\n                gradientTransform=\"matrix(47.9924 0 0 -27.7074 13731.9688 31874.9453)\"\n                gradientUnits=\"userSpaceOnUse\"\n                x1=\"-285.6253\"\n                x2=\"-285.6253\"\n                y1=\"1149.4111\"\n                y2=\"1150.4111\"\n              >\n                <stop offset=\"0\" style=\"stop-color: #3a3aff\" />\n                <stop offset=\"1\" style=\"stop-color: #0707d6\" />\n              </linearGradient>\n              <polygon\n                id=\"path-13\"\n                clip-rule=\"evenodd\"\n                fill=\"url(#path-13_1_)\"\n                fill-rule=\"evenodd\"\n                points=\"0.1,13.9 24.1,27.8 48.1,13.9\n\t\t\t\t\t\t24.1,0.1 \t\t\t\t\t\"\n              />\n            </g>\n            <g>\n              <linearGradient\n                id=\"SVGID_4_\"\n                gradientTransform=\"matrix(47.9924 0 0 -27.7074 13731.9688 31874.9453)\"\n                gradientUnits=\"userSpaceOnUse\"\n                x1=\"-285.6253\"\n                x2=\"-285.6253\"\n                y1=\"1149.4111\"\n                y2=\"1150.4294\"\n              >\n                <stop\n                  offset=\"0\"\n                  style=\"stop-color: #8fa0ff; stop-opacity: 0.467\"\n                />\n                <stop\n                  offset=\"1\"\n                  style=\"stop-color: #0f0f0f; stop-opacity: 0.06\"\n                />\n                <stop offset=\"1\" style=\"stop-color: #2f59e1; stop-opacity: 0\" />\n              </linearGradient>\n              <polygon\n                clip-rule=\"evenodd\"\n                fill=\"url(#SVGID_4_)\"\n                fill-rule=\"evenodd\"\n                points=\"0.1,13.9 24.1,27.8 48.1,13.9 24.1,0.1\n\t\t\t\t\t\t\"\n              />\n            </g>\n            <g filter=\"url(#filter-14)\">\n              <polygon\n                clip-rule=\"evenodd\"\n                fill-rule=\"evenodd\"\n                points=\"0.1,13.9 24.1,27.8 48.1,13.9 24.1,0.1 \t\t\t\t\t\"\n              />\n            </g>\n          </g>\n\n          <linearGradient\n            id=\"Stroke-3_1_\"\n            gradientTransform=\"matrix(23.9962 0 0 -41.5628 6916.9932 48336.7578)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"-287.7007\"\n            x2=\"-287.815\"\n            y1=\"1162.8857\"\n            y2=\"1161.1971\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #4f3eff\" />\n            <stop offset=\"1\" style=\"stop-color: #060071\" />\n          </linearGradient>\n          <polygon\n            id=\"Stroke-3\"\n            clip-rule=\"evenodd\"\n            fill=\"url(#Stroke-3_1_)\"\n            fill-rule=\"evenodd\"\n            points=\"24.1,55.5 24.1,27.8 0.1,13.9\n\t\t\t\t0.1,41.6 \t\t\t\"\n          />\n\n          <linearGradient\n            id=\"Stroke-5_1_\"\n            gradientTransform=\"matrix(23.9962 0 0 -41.5628 6941.0435 48336.7578)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"-287.7007\"\n            x2=\"-287.815\"\n            y1=\"1162.8857\"\n            y2=\"1161.1971\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #1200ca\" />\n            <stop offset=\"1\" style=\"stop-color: #060071\" />\n          </linearGradient>\n          <polygon\n            id=\"Stroke-5\"\n            clip-rule=\"evenodd\"\n            fill=\"url(#Stroke-5_1_)\"\n            fill-rule=\"evenodd\"\n            points=\"24.1,55.5 24.1,27.8\n\t\t\t\t48.1,13.9 48.1,41.6 \t\t\t\"\n          />\n        </g>\n\n        <linearGradient\n          id=\"矩形_1_\"\n          gradientTransform=\"matrix(48.245 0 0 -97.3101 13803.5742 121267.3516)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-285.6142\"\n          x2=\"-285.6142\"\n          y1=\"1246.0574\"\n          y2=\"1244.9515\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff; stop-opacity: 0\" />\n          <stop offset=\"1\" style=\"stop-color: #fff\" />\n        </linearGradient>\n\n        <polygon\n          id=\"矩形\"\n          clip-rule=\"evenodd\"\n          enable-background=\"new    \"\n          fill=\"url(#矩形_1_)\"\n          fill-rule=\"evenodd\"\n          opacity=\"0.1977\"\n          points=\"\n\t\t\t0,0 48.2,0 48.2,83.7 24.1,97.3 0,83.7 \t\t\"\n        />\n      </g>\n      <g id=\"编组-4\" transform=\"translate(71.000000, 199.000000)\">\n        <g id=\"Shape备份-2\">\n          <g>\n            <linearGradient\n              id=\"path-19_1_\"\n              gradientTransform=\"matrix(228.3947 0 0 -131.5657 -6978.2363 212124.2031)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"31.2434\"\n              x2=\"30.8024\"\n              y1=\"1610.2603\"\n              y2=\"1610.6188\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n              <stop offset=\"1\" style=\"stop-color: #00209e\" />\n            </linearGradient>\n            <path\n              id=\"path-19\"\n              clip-rule=\"evenodd\"\n              d=\"M63.4,312.7l-62-35.9\n\t\t\t\t\tc-1.9-1.1-1.9-3.8,0-4.9l151.1-87.4c3.5-2,7.8-2,11.3,0l63.1,36.6c1.9,1.1,1.9,3.8,0,4.9L77,312.7\n\t\t\t\t\tC72.8,315.2,67.6,315.2,63.4,312.7z\"\n              fill=\"url(#path-19_1_)\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n          <g filter=\"url(#filter-20)\">\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M63.4,312.7l-62-35.9c-1.9-1.1-1.9-3.8,0-4.9l151.1-87.4c3.5-2,7.8-2,11.3,0\n\t\t\t\t\tl63.1,36.6c1.9,1.1,1.9,3.8,0,4.9L77,312.7C72.8,315.2,67.6,315.2,63.4,312.7z\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n        </g>\n        <g id=\"编组-13\" transform=\"translate(45.000000, 0.000000)\">\n          <g id=\"编组备份-17\" transform=\"translate(90.066225, 54.000000)\">\n            <g>\n              <g>\n                <linearGradient\n                  id=\"path-23_1_\"\n                  gradientTransform=\"matrix(47.5983 0 0 -27.4798 5055.4995 41979.2891)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-105.7092\"\n                  x2=\"-105.7092\"\n                  y1=\"1526.6367\"\n                  y2=\"1527.6367\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #3a3aff\" />\n                  <stop offset=\"1\" style=\"stop-color: #0707d6\" />\n                </linearGradient>\n                <polygon\n                  id=\"path-23\"\n                  clip-rule=\"evenodd\"\n                  fill=\"url(#path-23_1_)\"\n                  fill-rule=\"evenodd\"\n                  points=\"0.1,13.8 23.9,27.6\n\t\t\t\t\t\t\t47.7,13.8 23.9,0.1 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g>\n                <linearGradient\n                  id=\"SVGID_5_\"\n                  gradientTransform=\"matrix(47.5983 0 0 -27.4798 5055.4995 41979.2891)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-105.7092\"\n                  x2=\"-105.7092\"\n                  y1=\"1526.6367\"\n                  y2=\"1527.655\"\n                >\n                  <stop\n                    offset=\"0\"\n                    style=\"stop-color: #8fa0ff; stop-opacity: 0.467\"\n                  />\n                  <stop\n                    offset=\"1\"\n                    style=\"stop-color: #0f0f0f; stop-opacity: 0.06\"\n                  />\n                  <stop\n                    offset=\"1\"\n                    style=\"stop-color: #2f59e1; stop-opacity: 0\"\n                  />\n                </linearGradient>\n                <polygon\n                  clip-rule=\"evenodd\"\n                  fill=\"url(#SVGID_5_)\"\n                  fill-rule=\"evenodd\"\n                  points=\"0.1,13.8 23.9,27.6 47.7,13.8 23.9,0.1\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                />\n              </g>\n              <g filter=\"url(#filter-24)\">\n                <polygon\n                  clip-rule=\"evenodd\"\n                  fill-rule=\"evenodd\"\n                  points=\"0.1,13.8 23.9,27.6 47.7,13.8 23.9,0.1 \t\t\t\t\t\t\"\n                />\n              </g>\n            </g>\n\n            <linearGradient\n              id=\"SVGID_6_\"\n              gradientTransform=\"matrix(23.7991 0 0 -168.4388 2578.7588 262543.8125)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-107.8491\"\n              x2=\"-107.8559\"\n              y1=\"1558.8499\"\n              y2=\"1557.1613\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #4f3eff\" />\n              <stop offset=\"1\" style=\"stop-color: #060071\" />\n            </linearGradient>\n            <polygon\n              clip-rule=\"evenodd\"\n              fill=\"url(#SVGID_6_)\"\n              fill-rule=\"evenodd\"\n              points=\"23.9,182.3 23.9,27.6 0.1,13.8 0.1,168.5\n\t\t\t\t\t\"\n            />\n\n            <linearGradient\n              id=\"SVGID_7_\"\n              gradientTransform=\"matrix(23.7991 0 0 -168.4388 2602.6116 262543.8125)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-107.8491\"\n              x2=\"-107.8559\"\n              y1=\"1558.8499\"\n              y2=\"1557.1613\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #1200ca\" />\n              <stop offset=\"1\" style=\"stop-color: #060071\" />\n            </linearGradient>\n            <polygon\n              clip-rule=\"evenodd\"\n              fill=\"url(#SVGID_7_)\"\n              fill-rule=\"evenodd\"\n              points=\"23.9,182.3 23.9,27.6 47.7,13.8 47.7,168.5\n\t\t\t\t\t\t\t\t\t\"\n            />\n          </g>\n          <g id=\"编组备份-16\" transform=\"translate(45.562914, 134.529801)\">\n            <g>\n              <g>\n                <linearGradient\n                  id=\"path-28_1_\"\n                  gradientTransform=\"matrix(47.5983 0 0 -27.4798 2937.218 39766.3438)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-61.7058\"\n                  x2=\"-60.7058\"\n                  y1=\"1446.6069\"\n                  y2=\"1446.6069\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #bda0e5\" />\n                  <stop offset=\"1\" style=\"stop-color: #5f3abd\" />\n                </linearGradient>\n                <polygon\n                  id=\"path-28\"\n                  clip-rule=\"evenodd\"\n                  fill=\"url(#path-28_1_)\"\n                  fill-rule=\"evenodd\"\n                  points=\"0.1,13.8 23.9,27.6\n\t\t\t\t\t\t\t47.7,13.8 23.9,0.1 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g>\n                <linearGradient\n                  id=\"SVGID_8_\"\n                  gradientTransform=\"matrix(47.5983 0 0 -27.4798 2937.218 39766.3438)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-61.2058\"\n                  x2=\"-61.2058\"\n                  y1=\"1446.1069\"\n                  y2=\"1447.1252\"\n                >\n                  <stop\n                    offset=\"0\"\n                    style=\"stop-color: #8fa0ff; stop-opacity: 0.467\"\n                  />\n                  <stop\n                    offset=\"1\"\n                    style=\"stop-color: #0f0f0f; stop-opacity: 0.06\"\n                  />\n                  <stop\n                    offset=\"1\"\n                    style=\"stop-color: #2f59e1; stop-opacity: 0\"\n                  />\n                </linearGradient>\n                <polygon\n                  clip-rule=\"evenodd\"\n                  fill=\"url(#SVGID_8_)\"\n                  fill-rule=\"evenodd\"\n                  points=\"0.1,13.8 23.9,27.6 47.7,13.8 23.9,0.1\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                />\n              </g>\n              <g filter=\"url(#filter-29)\">\n                <polygon\n                  clip-rule=\"evenodd\"\n                  fill-rule=\"evenodd\"\n                  points=\"0.1,13.8 23.9,27.6 47.7,13.8 23.9,0.1 \t\t\t\t\t\t\"\n                />\n              </g>\n            </g>\n\n            <linearGradient\n              id=\"SVGID_9_\"\n              gradientTransform=\"matrix(23.7991 0 0 -114.399 1519.618 168778.2344)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-63.3488\"\n              x2=\"-63.3488\"\n              y1=\"1475.4679\"\n              y2=\"1474.0222\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #4f3eff\" />\n              <stop offset=\"1\" style=\"stop-color: #f77\" />\n            </linearGradient>\n            <polygon\n              clip-rule=\"evenodd\"\n              fill=\"url(#SVGID_9_)\"\n              fill-rule=\"evenodd\"\n              points=\"23.9,128.2 23.9,27.6 0.1,13.8 0.1,114.5\n\t\t\t\t\t\"\n            />\n\n            <linearGradient\n              id=\"SVGID_10_\"\n              gradientTransform=\"matrix(23.7991 0 0 -114.399 1543.4708 168778.2344)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-63.3488\"\n              x2=\"-63.3488\"\n              y1=\"1475.4679\"\n              y2=\"1474.0222\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #4f3eff\" />\n              <stop offset=\"1\" style=\"stop-color: #d73838\" />\n            </linearGradient>\n            <polygon\n              clip-rule=\"evenodd\"\n              fill=\"url(#SVGID_10_)\"\n              fill-rule=\"evenodd\"\n              points=\"23.9,128.2 23.9,27.6 47.7,13.8 47.7,114.5\n\t\t\t\t\t\t\t\t\t\"\n            />\n          </g>\n          <g id=\"编组备份-18\" transform=\"translate(0.000000, 0.271523)\">\n            <g>\n              <g>\n                <linearGradient\n                  id=\"path-33_1_\"\n                  gradientTransform=\"matrix(47.5983 0 0 -27.4798 768.5011 43638.7383)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-16.1429\"\n                  x2=\"-15.1429\"\n                  y1=\"1580.8652\"\n                  y2=\"1580.8652\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #8afffc\" />\n                  <stop offset=\"1\" style=\"stop-color: #003dde\" />\n                </linearGradient>\n                <polygon\n                  id=\"path-33\"\n                  clip-rule=\"evenodd\"\n                  fill=\"url(#path-33_1_)\"\n                  fill-rule=\"evenodd\"\n                  points=\"0.1,196.8 23.9,210.6\n\t\t\t\t\t\t\t47.7,196.8 23.9,183.1 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g>\n                <linearGradient\n                  id=\"SVGID_11_\"\n                  gradientTransform=\"matrix(47.5983 0 0 -27.4798 768.5011 43638.7383)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-15.6429\"\n                  x2=\"-15.6429\"\n                  y1=\"1580.3652\"\n                  y2=\"1581.3835\"\n                >\n                  <stop\n                    offset=\"0\"\n                    style=\"stop-color: #8fa0ff; stop-opacity: 0.467\"\n                  />\n                  <stop\n                    offset=\"1\"\n                    style=\"stop-color: #0f0f0f; stop-opacity: 0.06\"\n                  />\n                  <stop\n                    offset=\"1\"\n                    style=\"stop-color: #2f59e1; stop-opacity: 0\"\n                  />\n                </linearGradient>\n                <polygon\n                  clip-rule=\"evenodd\"\n                  fill=\"url(#SVGID_11_)\"\n                  fill-rule=\"evenodd\"\n                  points=\"0.1,196.8 23.9,210.6 47.7,196.8\n\t\t\t\t\t\t\t23.9,183.1 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g filter=\"url(#filter-34)\">\n                <polygon\n                  clip-rule=\"evenodd\"\n                  fill-rule=\"evenodd\"\n                  points=\"0.1,196.8 23.9,210.6 47.7,196.8 23.9,183.1 \t\t\t\t\t\t\"\n                />\n              </g>\n            </g>\n\n            <linearGradient\n              id=\"SVGID_12_\"\n              gradientTransform=\"matrix(48 0 0 -80.5525 774 129477.0156)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-15.625\"\n              x2=\"-15.625\"\n              y1=\"1604.7488\"\n              y2=\"1605.7488\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #45efed\" />\n              <stop offset=\"1\" style=\"stop-color: #d0ffed; stop-opacity: 0\" />\n            </linearGradient>\n            <polygon\n              clip-rule=\"evenodd\"\n              enable-background=\"new    \"\n              fill=\"url(#SVGID_12_)\"\n              fill-rule=\"evenodd\"\n              opacity=\"0.3\"\n              points=\"\n\t\t\t\t\t0,130 48,130 48,196.7 24,210.6 0,196.7 \t\t\t\t\"\n            />\n\n            <linearGradient\n              id=\"矩形备份_1_\"\n              gradientTransform=\"matrix(48 0 0 -80.5525 820 129428.7578)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-15.625\"\n              x2=\"-15.625\"\n              y1=\"1604.7488\"\n              y2=\"1605.7488\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #45efed\" />\n              <stop offset=\"1\" style=\"stop-color: #d0ffed; stop-opacity: 0\" />\n            </linearGradient>\n\n            <polygon\n              id=\"矩形备份\"\n              clip-rule=\"evenodd\"\n              enable-background=\"new    \"\n              fill=\"url(#矩形备份_1_)\"\n              fill-rule=\"evenodd\"\n              opacity=\"0.3\"\n              points=\"\n\t\t\t\t\t46,81.7 94,81.7 94,148.5 70,162.3 46,148.5 \t\t\t\t\"\n            />\n\n            <linearGradient\n              id=\"矩形备份-4_1_\"\n              gradientTransform=\"matrix(48 0 0 -80.5525 863 129347.75)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-15.625\"\n              x2=\"-15.625\"\n              y1=\"1604.7488\"\n              y2=\"1605.7488\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #45efed\" />\n              <stop offset=\"1\" style=\"stop-color: #d0ffed; stop-opacity: 0\" />\n            </linearGradient>\n\n            <polygon\n              id=\"矩形备份-4\"\n              clip-rule=\"evenodd\"\n              enable-background=\"new    \"\n              fill=\"url(#矩形备份-4_1_)\"\n              fill-rule=\"evenodd\"\n              opacity=\"0.3\"\n              points=\"\n\t\t\t\t\t89,0.7 137,0.7 137,67.5 113,81.3 89,67.5 \t\t\t\t\"\n            />\n\n            <linearGradient\n              id=\"SVGID_13_\"\n              gradientTransform=\"matrix(23.7991 0 0 -93.207 435.2595 150023.5938)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-17.8122\"\n              x2=\"-17.7572\"\n              y1=\"1607.5193\"\n              y2=\"1606.3573\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #68dfff\" />\n              <stop offset=\"1\" style=\"stop-color: #004a8b\" />\n            </linearGradient>\n            <polygon\n              clip-rule=\"evenodd\"\n              fill=\"url(#SVGID_13_)\"\n              fill-rule=\"evenodd\"\n              points=\"23.9,290 23.9,210.6 0.1,196.8 0.1,276.3\n\t\t\t\t\t\"\n            />\n\n            <linearGradient\n              id=\"SVGID_14_\"\n              gradientTransform=\"matrix(23.7991 0 0 -93.207 459.1122 150023.5938)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-17.8038\"\n              x2=\"-17.7678\"\n              y1=\"1607.3881\"\n              y2=\"1606.5361\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #0567de\" />\n              <stop offset=\"1\" style=\"stop-color: #00048b\" />\n            </linearGradient>\n            <polygon\n              clip-rule=\"evenodd\"\n              fill=\"url(#SVGID_14_)\"\n              fill-rule=\"evenodd\"\n              points=\"23.9,290 23.9,210.6 47.7,196.8 47.7,276.3\n\t\t\t\t\t\t\t\t\t\"\n            />\n          </g>\n        </g>\n      </g>\n      <g id=\"编组-9\" transform=\"translate(286.000000, 26.000000)\">\n        <g id=\"编组-3\" transform=\"translate(0.000000, 326.797996)\">\n          <g id=\"椭圆形\" filter=\"url(#filter-39)\">\n            <linearGradient\n              id=\"SVGID_15_\"\n              gradientTransform=\"matrix(298.9761 -31.4237 -16.5194 -157.172 79324.2969 223473.4688)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-183.6863\"\n              x2=\"-184.3618\"\n              y1=\"1458.1069\"\n              y2=\"1457.9841\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #1b0f9c; stop-opacity: 0\" />\n              <stop offset=\"1\" style=\"stop-color: #060071\" />\n            </linearGradient>\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M161.7,23.6c82.6-8.7,153.2,19.5,157.7,62.9\n\t\t\t\t\tc4.6,43.4-58.7,85.6-141.2,94.3C95.7,189.5,25,161.3,20.5,117.9S79.1,32.3,161.7,23.6z\"\n              fill=\"url(#SVGID_15_)\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n          <g transform=\"translate(0.000000, 32.309947)\">\n            <g id=\"形状结合\">\n              <linearGradient\n                id=\"SVGID_16_\"\n                gradientTransform=\"matrix(252.8605 0 0 -153.121 46628.3242 218334.6719)\"\n                gradientUnits=\"userSpaceOnUse\"\n                x1=\"-184.3435\"\n                x2=\"-183.4387\"\n                y1=\"1425.4349\"\n                y2=\"1425.3547\"\n              >\n                <stop offset=\"0\" style=\"stop-color: #3b76df\" />\n                <stop offset=\"1\" style=\"stop-color: #0c2ea7\" />\n              </linearGradient>\n              <path\n                clip-rule=\"evenodd\"\n                d=\"M36.9,21.3c49.2-28.4,129.8-28.4,179,0\n\t\t\t\t\t\tc9.7,5.6,17.5,12.2,23.4,19.4l13.5,0v39.3l-0.1,0c-1.3,19.3-13.6,38.3-36.8,51.7c-49.2,28.4-129.8,28.4-179,0\n\t\t\t\t\t\tC13.7,118.4,1.4,99.4,0.1,80.1l-0.1,0V40.7l13.5,0C19.4,33.5,27.2,26.9,36.9,21.3z\"\n                fill=\"url(#SVGID_16_)\"\n                fill-rule=\"evenodd\"\n              />\n            </g>\n            <defs>\n              <filter\n                id=\"Adobe_OpacityMaskFilter\"\n                filterUnits=\"userSpaceOnUse\"\n                height=\"86.4\"\n                width=\"22.5\"\n                x=\"18.3\"\n                y=\"59.7\"\n              >\n                <feColorMatrix\n                  type=\"matrix\"\n                  values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                />\n              </filter>\n            </defs>\n            <mask\n              id=\"mask-42_1_\"\n              height=\"86.4\"\n              maskUnits=\"userSpaceOnUse\"\n              width=\"22.5\"\n              x=\"18.3\"\n              y=\"59.7\"\n            >\n              <g filter=\"url(#Adobe_OpacityMaskFilter)\">\n                <path\n                  id=\"path-41_1_\"\n                  clip-rule=\"evenodd\"\n                  d=\"M36.9,21.3c49.2-28.4,129.8-28.4,179,0\n\t\t\t\t\t\t\tc9.7,5.6,17.5,12.2,23.4,19.4l13.5,0v39.3l-0.1,0c-1.3,19.3-13.6,38.3-36.8,51.7c-49.2,28.4-129.8,28.4-179,0\n\t\t\t\t\t\t\tC13.7,118.4,1.4,99.4,0.1,80.1l-0.1,0V40.7l13.5,0C19.4,33.5,27.2,26.9,36.9,21.3z\"\n                  fill=\"#FFFFFF\"\n                  fill-rule=\"evenodd\"\n                />\n              </g>\n            </mask>\n            <g filter=\"url(#filter-43)\" mask=\"url(#mask-42_1_)\">\n              <rect\n                clip-rule=\"evenodd\"\n                fill=\"#FFFFFF\"\n                fill-rule=\"evenodd\"\n                height=\"86.4\"\n                width=\"22.5\"\n                x=\"18.3\"\n                y=\"59.7\"\n              />\n            </g>\n          </g>\n          <g id=\"Stroke-1备份\">\n            <g>\n              <linearGradient\n                id=\"path-45_1_\"\n                gradientTransform=\"matrix(252.8604 0 0 -146.0971 46628.3203 212993.0469)\"\n                gradientUnits=\"userSpaceOnUse\"\n                x1=\"-183.7127\"\n                x2=\"-184.1537\"\n                y1=\"1457.2306\"\n                y2=\"1457.5913\"\n              >\n                <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n                <stop offset=\"1\" style=\"stop-color: #00209e\" />\n              </linearGradient>\n              <path\n                id=\"path-45\"\n                clip-rule=\"evenodd\"\n                d=\"M36.9,21.3c-49.2,28.4-49.2,75,0,103.4\n\t\t\t\t\t\tc49.2,28.4,129.8,28.4,179,0c49.2-28.4,49.2-75,0-103.4C166.7-7.1,86.2-7.1,36.9,21.3z\"\n                fill=\"url(#path-45_1_)\"\n                fill-rule=\"evenodd\"\n              />\n            </g>\n            <g filter=\"url(#filter-46)\">\n              <path\n                clip-rule=\"evenodd\"\n                d=\"M36.9,21.3c-49.2,28.4-49.2,75,0,103.4c49.2,28.4,129.8,28.4,179,0\n\t\t\t\t\t\tc49.2-28.4,49.2-75,0-103.4C166.7-7.1,86.2-7.1,36.9,21.3z\"\n                fill-rule=\"evenodd\"\n              />\n            </g>\n          </g>\n        </g>\n        <g id=\"编组-3备份\" transform=\"translate(24.286045, 0.000000)\">\n          <g transform=\"translate(1.000000, 323.847957)\">\n            <g>\n              <linearGradient\n                id=\"SVGID_17_\"\n                gradientTransform=\"matrix(202.2884 0 0 -122.4968 42438.1328 178783.5781)\"\n                gradientUnits=\"userSpaceOnUse\"\n                x1=\"-209.8152\"\n                x2=\"-208.7883\"\n                y1=\"1459.0104\"\n                y2=\"1458.9812\"\n              >\n                <stop offset=\"0\" style=\"stop-color: #8dcbff\" />\n                <stop offset=\"0.1196\" style=\"stop-color: #cafbff\" />\n                <stop offset=\"0.768\" style=\"stop-color: #7feeff\" />\n                <stop offset=\"1\" style=\"stop-color: #adcbff\" />\n              </linearGradient>\n              <path\n                clip-rule=\"evenodd\"\n                d=\"M29.5,17.1c39.4-22.8,103.8-22.8,143.2,0\n\t\t\t\t\t\tc7.8,4.5,14,9.8,18.7,15.5l10.8,0v31.5l-0.1,0c-1.1,15.5-10.9,30.6-29.4,41.4c-39.4,22.8-103.8,22.8-143.2,0\n\t\t\t\t\t\tC11,94.7,1.2,79.5,0.1,64.1l-0.1,0V32.6l10.8,0C15.5,26.8,21.8,21.6,29.5,17.1z\"\n                fill=\"url(#SVGID_17_)\"\n                fill-rule=\"evenodd\"\n              />\n            </g>\n            <defs>\n              <filter\n                id=\"Adobe_OpacityMaskFilter_1_\"\n                filterUnits=\"userSpaceOnUse\"\n                height=\"69.1\"\n                width=\"18\"\n                x=\"14.6\"\n                y=\"47.8\"\n              >\n                <feColorMatrix\n                  type=\"matrix\"\n                  values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                />\n              </filter>\n            </defs>\n            <mask\n              id=\"mask-49_1_\"\n              height=\"69.1\"\n              maskUnits=\"userSpaceOnUse\"\n              width=\"18\"\n              x=\"14.6\"\n              y=\"47.8\"\n            >\n              <g filter=\"url(#Adobe_OpacityMaskFilter_1_)\">\n                <path\n                  id=\"path-48_1_\"\n                  clip-rule=\"evenodd\"\n                  d=\"M29.5,17.1c39.4-22.8,103.8-22.8,143.2,0\n\t\t\t\t\t\t\tc7.8,4.5,14,9.8,18.7,15.5l10.8,0v31.5l-0.1,0c-1.1,15.5-10.9,30.6-29.4,41.4c-39.4,22.8-103.8,22.8-143.2,0\n\t\t\t\t\t\t\tC11,94.7,1.2,79.5,0.1,64.1l-0.1,0V32.6l10.8,0C15.5,26.8,21.8,21.6,29.5,17.1z\"\n                  fill=\"#FFFFFF\"\n                  fill-rule=\"evenodd\"\n                />\n              </g>\n            </mask>\n            <g filter=\"url(#filter-50)\" mask=\"url(#mask-49_1_)\">\n              <rect\n                clip-rule=\"evenodd\"\n                fill=\"#FFFFFF\"\n                fill-rule=\"evenodd\"\n                height=\"69.1\"\n                width=\"18\"\n                x=\"14.6\"\n                y=\"47.8\"\n              />\n            </g>\n          </g>\n          <g>\n            <g>\n              <linearGradient\n                id=\"path-52_1_\"\n                gradientTransform=\"matrix(202.2884 0 0 -116.8777 42236.8438 208684.4531)\"\n                gradientUnits=\"userSpaceOnUse\"\n                x1=\"-208.0996\"\n                x2=\"-208.5406\"\n                y1=\"1782.2883\"\n                y2=\"1782.6489\"\n              >\n                <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n                <stop offset=\"1\" style=\"stop-color: #00209e\" />\n              </linearGradient>\n              <path\n                id=\"path-52\"\n                clip-rule=\"evenodd\"\n                d=\"M30.5,315.1c-39.4,22.8-39.4,60,0,82.7\n\t\t\t\t\t\tc39.4,22.8,103.8,22.8,143.2,0c39.4-22.8,39.4-60,0-82.7C134.4,292.3,69.9,292.3,30.5,315.1z\"\n                fill=\"url(#path-52_1_)\"\n                fill-rule=\"evenodd\"\n              />\n            </g>\n            <g filter=\"url(#filter-53)\">\n              <path\n                clip-rule=\"evenodd\"\n                d=\"M30.5,315.1c-39.4,22.8-39.4,60,0,82.7c39.4,22.8,103.8,22.8,143.2,0\n\t\t\t\t\t\tc39.4-22.8,39.4-60,0-82.7C134.4,292.3,69.9,292.3,30.5,315.1z\"\n                fill-rule=\"evenodd\"\n              />\n            </g>\n          </g>\n\n          <linearGradient\n            id=\"SVGID_18_\"\n            gradientTransform=\"matrix(202.5744 0 0 -414.8777 42296.1367 742296.8125)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"-208.2896\"\n            x2=\"-208.2896\"\n            y1=\"1788.1943\"\n            y2=\"1789.1943\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #45efed\" />\n            <stop offset=\"1\" style=\"stop-color: #d0ffed; stop-opacity: 0\" />\n          </linearGradient>\n          <path\n            clip-rule=\"evenodd\"\n            d=\"M202.7,0\n\t\t\t\tl0,350.2c3.1,17-6.5,34.7-29,47.6c-39.4,22.8-103.8,22.8-143.2,0C12,387.1,2.2,373.1,1.1,359l-0.4,0V0H202.7z\"\n            enable-background=\"new    \"\n            fill=\"url(#SVGID_18_)\"\n            fill-rule=\"evenodd\"\n            opacity=\"0.3\"\n          />\n        </g>\n      </g>\n      <g id=\"编组-11备份\" transform=\"translate(329.000000, 539.000000)\">\n        <g id=\"编组-6备份\" transform=\"translate(0.000000, 4.148936)\">\n          <g filter=\"url(#filter-56)\">\n            <linearGradient\n              id=\"SVGID_19_\"\n              gradientTransform=\"matrix(52.2766 0 0 -21.5745 11984.5537 26508.6328)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-227.9383\"\n              x2=\"-228.6138\"\n              y1=\"1226.9152\"\n              y2=\"1226.8395\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #1b0f9c; stop-opacity: 0\" />\n              <stop offset=\"1\" style=\"stop-color: #060071\" />\n            </linearGradient>\n            <ellipse\n              clip-rule=\"evenodd\"\n              cx=\"41.9\"\n              cy=\"39.8\"\n              fill=\"url(#SVGID_19_)\"\n              fill-rule=\"evenodd\"\n              rx=\"26.1\"\n              ry=\"10.8\"\n            />\n          </g>\n\n          <radialGradient\n            id=\"SVGID_20_\"\n            cx=\"-228.8779\"\n            cy=\"1253.5115\"\n            gradientTransform=\"matrix(48.9574 0 0 -48.9574 11215.3408 61379.1055)\"\n            gradientUnits=\"userSpaceOnUse\"\n            r=\"0.9018\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #dedbff\" />\n            <stop offset=\"0.3987\" style=\"stop-color: #4165fc\" />\n            <stop offset=\"1\" style=\"stop-color: #0a00c2\" />\n          </radialGradient>\n          <circle\n            clip-rule=\"evenodd\"\n            cx=\"24.5\"\n            cy=\"24.5\"\n            fill=\"url(#SVGID_20_)\"\n            fill-rule=\"evenodd\"\n            r=\"24.5\"\n          />\n        </g>\n        <g id=\"编组-6\" transform=\"translate(23.510638, 0.000000)\">\n          <g filter=\"url(#filter-59)\">\n            <linearGradient\n              id=\"SVGID_21_\"\n              gradientTransform=\"matrix(87.1277 0 0 -35.9574 21954.6836 45008.2383)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-250.6685\"\n              x2=\"-251.344\"\n              y1=\"1249.9197\"\n              y2=\"1249.844\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #1b0f9c; stop-opacity: 0\" />\n              <stop offset=\"1\" style=\"stop-color: #060071\" />\n            </linearGradient>\n            <ellipse\n              clip-rule=\"evenodd\"\n              cx=\"69.8\"\n              cy=\"66.4\"\n              fill=\"url(#SVGID_21_)\"\n              fill-rule=\"evenodd\"\n              rx=\"43.6\"\n              ry=\"18\"\n            />\n          </g>\n\n          <radialGradient\n            id=\"SVGID_22_\"\n            cx=\"-251.5552\"\n            cy=\"1265.9697\"\n            gradientTransform=\"matrix(81.5957 0 0 -81.5957 20542.6016 103315.0391)\"\n            gradientUnits=\"userSpaceOnUse\"\n            r=\"0.9018\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #dedbff\" />\n            <stop offset=\"0.3987\" style=\"stop-color: #4165fc\" />\n            <stop offset=\"1\" style=\"stop-color: #0a00c2\" />\n          </radialGradient>\n          <circle\n            clip-rule=\"evenodd\"\n            cx=\"40.8\"\n            cy=\"40.8\"\n            fill=\"url(#SVGID_22_)\"\n            fill-rule=\"evenodd\"\n            r=\"40.8\"\n          />\n        </g>\n      </g>\n\n      <g\n        id=\"编组备份-9\"\n        opacity=\"0.5\"\n        transform=\"translate(488.000000, 172.000000) scale(-1, 1) translate(-488.000000, -172.000000) translate(449.000000, 122.000000)\"\n      >\n        <linearGradient\n          id=\"SVGID_23_\"\n          gradientTransform=\"matrix(-75.8604 0 0 -99.5101 32139.0957 167716.875)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"423.6246\"\n          x2=\"424.2521\"\n          y1=\"1683.8809\"\n          y2=\"1685.3489\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff\" />\n          <stop offset=\"1\" style=\"stop-color: #00c6fb\" />\n        </linearGradient>\n        <path\n          d=\"M72.5,0l3.8,2.1L75.1,4l-1.6,0.6l-0.7,49.2c0,1.6-0.9,3.4-2.1,4.2l-0.2,0.1L5.7,95.5l0,0l-0.7,4\n\t\t\tl-3.7-2.1c-0.1,0-0.1-0.1-0.2-0.1l0,0c-0.4-0.3-0.7-0.9-0.7-1.8l0,0l0.8-51.8c0-1.7,1-3.6,2.3-4.3l0,0L71.2,0.3\n\t\t\tC71.7,0,72.1-0.1,72.5,0L72.5,0z\"\n          fill=\"url(#SVGID_23_)\"\n        />\n\n        <linearGradient\n          id=\"SVGID_24_\"\n          gradientTransform=\"matrix(-73.087 0 0 -97.5101 30963.9746 164327.5781)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"423.5395\"\n          x2=\"424.1461\"\n          y1=\"1683.6631\"\n          y2=\"1685.1311\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #005bea\" />\n          <stop offset=\"1\" style=\"stop-color: #00c6fb\" />\n        </linearGradient>\n        <path\n          d=\"M6.3,99.2l67.8-39.1c1.2-0.7,2.3-2.7,2.3-4.3L77.1,4c0-1.7-1-2.4-2.3-1.7L7,41.4\n\t\t\tc-1.2,0.7-2.3,2.7-2.3,4.3L4,97.5C4,99.2,5,100,6.3,99.2z\"\n          fill=\"url(#SVGID_24_)\"\n        />\n        <g\n          id=\"XMLID_902_\"\n          opacity=\"0.3\"\n          transform=\"translate(51.463000, 21.500000)\"\n        >\n          <path\n            id=\"XMLID_905_\"\n            d=\"M17.9,4.7L1.7,14.1c-0.5,0.3-1.2,0.1-1.5-0.4C0.1,13.5,0,13.3,0,13.1\n\t\t\t\tc0-1.2,0.6-2.2,1.6-2.8l16.3-9.4c0.5-0.3,1.2-0.1,1.5,0.4c0.1,0.2,0.1,0.4,0.1,0.5C19.5,3,18.9,4.1,17.9,4.7z\"\n            fill=\"#FFFFFF\"\n          />\n          <path\n            id=\"XMLID_905_备份\"\n            d=\"M18.2,14.6L5.5,21.9c-0.4,0.2-0.9,0.1-1.1-0.3c-0.1-0.1-0.1-0.3-0.1-0.4\n\t\t\t\tc0-0.9,0.5-1.7,1.3-2.2l12.6-7.3c0.4-0.2,0.9-0.1,1.1,0.3c0.1,0.1,0.1,0.3,0.1,0.4C19.4,13.3,18.9,14.2,18.2,14.6z\"\n            fill=\"#FFFFFF\"\n          />\n          <path\n            id=\"XMLID_905_备份-2\"\n            d=\"M18.2,22.3L5.5,29.6c-0.4,0.2-0.9,0.1-1.1-0.3c-0.1-0.1-0.1-0.3-0.1-0.4\n\t\t\t\tc0-0.9,0.5-1.7,1.3-2.2l12.6-7.3c0.4-0.2,0.9-0.1,1.1,0.3c0.1,0.1,0.1,0.3,0.1,0.4C19.4,21,18.9,21.8,18.2,22.3z\"\n            fill=\"#FFFFFF\"\n          />\n          <path\n            id=\"XMLID_905_备份-3\"\n            d=\"M18.2,29.9L5.5,37.2c-0.4,0.2-0.9,0.1-1.1-0.3c-0.1-0.1-0.1-0.3-0.1-0.4\n\t\t\t\tc0-0.9,0.5-1.7,1.3-2.2L18.2,27c0.4-0.2,0.9-0.1,1.1,0.3c0.1,0.1,0.1,0.3,0.1,0.4C19.4,28.7,18.9,29.5,18.2,29.9z\"\n            fill=\"#FFFFFF\"\n          />\n        </g>\n\n        <linearGradient\n          id=\"SVGID_25_\"\n          gradientTransform=\"matrix(-43.8881 0 0 -83.0886 18579.1777 139875)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"422.0028\"\n          x2=\"421.7111\"\n          y1=\"1683.5527\"\n          y2=\"1682.4626\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff\" />\n          <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n        </linearGradient>\n        <polygon\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_25_)\"\n          opacity=\"0.4\"\n          points=\"50,16.6 28.7,86.3 51,73.4 72.6,3.2 \t\t\"\n        />\n\n        <linearGradient\n          id=\"XMLID_22_\"\n          gradientTransform=\"matrix(-6.6055 0 0 -25.864 2720.3372 42898.332)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"408.911\"\n          x2=\"408.7796\"\n          y1=\"1654.5455\"\n          y2=\"1655.6864\"\n        >\n          <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n          <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n          <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n          <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n          <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n          <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n        </linearGradient>\n\n        <linearGradient\n          id=\"XMLID_23_\"\n          gradientTransform=\"matrix(-6.6055 0 0 -25.864 2720.3372 42898.332)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"409.0583\"\n          x2=\"409.0583\"\n          y1=\"1656.3246\"\n          y2=\"1655.3246\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff\" />\n          <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n        </linearGradient>\n        <polygon\n          id=\"XMLID_875_\"\n          fill=\"url(#XMLID_22_)\"\n          points=\"21.6,81.2 15,85 15,63\n\t\t\t21.6,59.2 \t\t\"\n          stroke=\"url(#XMLID_23_)\"\n          stroke-width=\"0.5\"\n        />\n\n        <linearGradient\n          id=\"XMLID_24_\"\n          gradientTransform=\"matrix(-6.6055 0 0 -32.7555 2732.1479 54570.1641)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"409.0159\"\n          x2=\"408.9963\"\n          y1=\"1663.0452\"\n          y2=\"1663.531\"\n        >\n          <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n          <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n          <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n          <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n          <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n          <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n        </linearGradient>\n\n        <linearGradient\n          id=\"XMLID_25_\"\n          gradientTransform=\"matrix(-6.6055 0 0 -32.7555 2732.1479 54570.1641)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"409.0583\"\n          x2=\"409.0583\"\n          y1=\"1664.5974\"\n          y2=\"1663.5974\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff\" />\n          <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n        </linearGradient>\n        <polygon\n          id=\"XMLID_874_\"\n          fill=\"url(#XMLID_24_)\"\n          points=\"33.4,74.4 26.8,78.2\n\t\t\t26.8,49.3 33.4,45.4 \t\t\"\n          stroke=\"url(#XMLID_25_)\"\n          stroke-width=\"0.5\"\n        />\n\n        <linearGradient\n          id=\"XMLID_26_\"\n          gradientTransform=\"matrix(-6.6055 0 0 -39.6465 2744.3577 66240.9141)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"408.98\"\n          x2=\"408.9241\"\n          y1=\"1668.4514\"\n          y2=\"1669.1958\"\n        >\n          <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n          <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n          <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n          <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n          <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n          <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n        </linearGradient>\n\n        <linearGradient\n          id=\"XMLID_27_\"\n          gradientTransform=\"matrix(-6.6055 0 0 -39.6465 2744.3577 66240.9141)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"409.0583\"\n          x2=\"409.0583\"\n          y1=\"1669.994\"\n          y2=\"1668.994\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff\" />\n          <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n        </linearGradient>\n        <polygon\n          id=\"XMLID_873_\"\n          fill=\"url(#XMLID_26_)\"\n          points=\"45.6,67.3 39,71.1 39,35.3\n\t\t\t45.6,31.5 \t\t\"\n          stroke=\"url(#XMLID_27_)\"\n          stroke-width=\"0.5\"\n        />\n      </g>\n      <g id=\"人备份\" transform=\"translate(364.000000, 134.000000)\">\n        <path\n          id=\"Path\"\n          clip-rule=\"evenodd\"\n          d=\"M78,41.6c0,0,6.5-5.1,7.6-5.4c7.2-2.4,8-1,9.4,0.7\n\t\t\tc1.4,1.6-4,4.2-5.4,5.7c-0.7,0.8,4.5-1,3.9,1c-0.5,1.5-4.9,2.2-5.6,2.9s-5.3,3.8-5.3,3.8L78,41.6z\"\n          fill=\"#F7C19C\"\n          fill-rule=\"evenodd\"\n        />\n\n        <linearGradient\n          id=\"SVGID_26_\"\n          gradientTransform=\"matrix(66.553 0 0 -42.1921 17559.9336 70059.375)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-263.0341\"\n          x2=\"-263.2938\"\n          y1=\"1659.3563\"\n          y2=\"1659.0363\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #3158d3\" />\n          <stop offset=\"1\" style=\"stop-color: #306ef3\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M53,81.7c5.3-2,34.6-33.6,34.6-33.6\n\t\t\tc-3.3-7.5-9.4-7.6-9.4-7.6L50.5,61.6c0,0-24.3-19.7-28.7-21.7s12.7,33,12.7,33S47.8,83.7,53,81.7z\"\n          fill=\"url(#SVGID_26_)\"\n          fill-rule=\"evenodd\"\n        />\n        <g\n          id=\"Oval\"\n          enable-background=\"new    \"\n          filter=\"url(#filter-71)\"\n          opacity=\"0.5267\"\n        >\n          <linearGradient\n            id=\"SVGID_27_\"\n            gradientTransform=\"matrix(125.5104 0 0 -59.4816 33000.125 99355.7422)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"-261.8036\"\n            x2=\"-262.479\"\n            y1=\"1666.1232\"\n            y2=\"1666.0233\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #1b0f9c; stop-opacity: 0\" />\n            <stop offset=\"1\" style=\"stop-color: #060071\" />\n          </linearGradient>\n          <ellipse\n            clip-rule=\"evenodd\"\n            cx=\"77.2\"\n            cy=\"256.5\"\n            fill=\"url(#SVGID_27_)\"\n            fill-rule=\"evenodd\"\n            rx=\"62.8\"\n            ry=\"29.7\"\n          />\n        </g>\n\n        <linearGradient\n          id=\"SVGID_28_\"\n          gradientTransform=\"matrix(37.5807 0 0 -16.9728 9948.6885 27801.6328)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-264.5023\"\n          x2=\"-262.5973\"\n          y1=\"1623.2751\"\n          y2=\"1622.7434\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #434343\" />\n          <stop offset=\"1\" style=\"stop-color: #122142\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M6.3,242.5c-1.9,1.4-7.6,6.1-5.2,12.6\n\t\t\tc2.4,6.5,8.7,4.1,12.8,2.9c4.1-1.2,9.3-4.2,15.3-5.1c6-0.9,12.9-5.5,5.8-8.6C30.3,242.2,6.3,242.5,6.3,242.5z\"\n          fill=\"url(#SVGID_28_)\"\n          fill-rule=\"evenodd\"\n        />\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M51.7,252.1l-2.2,9.5c0,0-5.1,0.3-8.5-1.4\n\t\t\tc-1.1-0.5-2-1.3-2.5-2.3l2.8-13.2l10.3-65.3l-9.3-33.8L16.9,246.6c0,0-3.1,1.6-6.5,0.4c-0.7-0.3-3.6-2-4.1-2.9l29.2-106.8l20,4.8\n\t\t\tl8.5,45L51.7,252.1z\"\n          fill=\"#EFAD81\"\n          fill-rule=\"evenodd\"\n        />\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M34.8,39.4l1.4-8.4c0,0-2-21.1,3.8-23.2s18.3-0.3,18.4,10\n\t\t\tc0.1,10.4-0.1,19.3-4.6,19.1c-4.5-0.2-5.3-1-5.3-1l0.1,8.1L34.8,39.4z\"\n          fill=\"#F7C19C\"\n          fill-rule=\"evenodd\"\n        />\n\n        <linearGradient\n          id=\"SVGID_29_\"\n          gradientTransform=\"matrix(65.856 0 0 -139.1891 17361.0918 233445.5938)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-263.1151\"\n          x2=\"-263.0032\"\n          y1=\"1676.3293\"\n          y2=\"1675.5613\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #021814\" />\n          <stop offset=\"1\" style=\"stop-color: #424fec\" />\n        </linearGradient>\n        <path\n          d=\"M17.5,117.5l1.9,57.1L4.8,238c0,0,3.3,5.5,18.1,4.1c0,0,16.4-56.8,18-59.2\n\t\t\tc1.6-2.4,4.4-21.7,4.4-21.7s2.5,29.8,1.7,32.9c-0.8,3.2-8.4,56.9-8.4,56.9s3.1,7.4,17,5.2c0,0,15-62,15-67.8v-62.1L17.5,117.5z\"\n          fill=\"url(#SVGID_29_)\"\n        />\n\n        <linearGradient\n          id=\"SVGID_30_\"\n          gradientTransform=\"matrix(38.3908 0 0 -17.2271 10193.9805 28243.3203)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-264.4597\"\n          x2=\"-262.5546\"\n          y1=\"1624.1052\"\n          y2=\"1623.5803\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #434343\" />\n          <stop offset=\"1\" style=\"stop-color: #122142\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M50.9,258.4c0,0-2.9,1.7-6.7,0.7c-3.8-1-4.4-3-5.6-3.1\n\t\t\tc-1.2-0.1-5,6.2-5,11.4c0,3.4,4.2,6.7,13.4,5.6c10.7-1.3,9.7,1,19-1c9.3-1.9,6.1-7.6,0.7-8.2c-5.4-0.6-10.7-1.5-12.7-3\n\t\t\tC52.1,259.4,50.9,258.4,50.9,258.4z\"\n          fill=\"url(#SVGID_30_)\"\n          fill-rule=\"evenodd\"\n        />\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M129.6,67.3c-0.6,0.2-1.7,0.5-2.9,0.8c-0.6-0.4-1.4-2.4-2-2.7\n\t\t\tc-1.2-0.5-3.1,3.8-3.9,4.9l-4.9,2.6l-0.9,9.2c2.5,0.1,5-1.1,7.5-1.4c1.4-0.3,13.7-6.6,15.5-10.8C139,67.4,133.6,65.7,129.6,67.3z\"\n          fill=\"#F7C19C\"\n          fill-rule=\"evenodd\"\n        />\n\n        <linearGradient\n          id=\"SVGID_31_\"\n          gradientTransform=\"matrix(107.5405 0 0 -93.2162 28291.4941 155960.2031)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-261.9086\"\n          x2=\"-262.8504\"\n          y1=\"1672.3391\"\n          y2=\"1672.2106\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #77f7e1\" />\n          <stop offset=\"1\" style=\"stop-color: #424fec\" />\n        </linearGradient>\n        <path\n          d=\"M76.5,100.3c5.7,0.2,44.9-17.6,44.9-17.6c-0.2-8.2-5.8-10.7-5.8-10.7l-33.7,8.6\n\t\t\tc0,0-14.7-27.5-18-31.1C60.7,46,49.1,40,49.1,40S39,43.3,34.6,35c0,0-3.2-0.8-8.7-0.8S12.3,46,14.1,69.2s2,53,2,53\n\t\t\ts16.7,7.8,54.5,4.2l-7.9-41.5C62.8,84.9,70.9,100.2,76.5,100.3z\"\n          fill=\"url(#SVGID_31_)\"\n        />\n\n        <linearGradient\n          id=\"SVGID_32_\"\n          gradientTransform=\"matrix(33.4128 0 0 -34.3382 8885.7266 56796.8203)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-264.4242\"\n          x2=\"-265.0254\"\n          y1=\"1653.8715\"\n          y2=\"1653.0199\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #253c6f\" />\n          <stop offset=\"1\" style=\"stop-color: #3068e8\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M53,18.9c0,0,10.3,1.5,10-5.4c-0.2-6.9-4.7-3.6-5.9-9.4\n\t\t\tc-1.4-7-11-2.3-22.3,0c-14.1,2.8,4.7,43.2,4.6,26.7s1.7-4.4,7.1-5.3c0.8-0.6-1.1-2.6,0-3.9C48.3,19.9,53,18.9,53,18.9z\"\n          fill=\"url(#SVGID_32_)\"\n          fill-rule=\"evenodd\"\n        />\n      </g>\n      <g id=\"Stroke-1备份-3\" opacity=\"0.2645\">\n        <g filter=\"url(#filter-79)\">\n          <path\n            id=\"path-78_1_\"\n            clip-rule=\"evenodd\"\n            d=\"M322.9,141.3c-49.2,28.4-49.2,75,0,103.4\n\t\t\t\tc49.2,28.4,129.8,28.4,179,0c49.2-28.4,49.2-75,0-103.4C452.7,112.9,372.2,112.9,322.9,141.3z\"\n            fill-rule=\"evenodd\"\n          />\n        </g>\n        <g>\n          <linearGradient\n            id=\"SVGID_33_\"\n            gradientTransform=\"matrix(252.8604 0 0 -146.0971 -25403.7617 264655.8438)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"102.0966\"\n            x2=\"102.0966\"\n            y1=\"1810.3647\"\n            y2=\"1809.6846\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #fff; stop-opacity: 0\" />\n            <stop offset=\"1\" style=\"stop-color: #fff\" />\n          </linearGradient>\n          <path\n            d=\"M322.9,141.3c-49.2,28.4-49.2,75,0,103.4c49.2,28.4,129.8,28.4,179,0\n\t\t\t\tc49.2-28.4,49.2-75,0-103.4C452.7,112.9,372.2,112.9,322.9,141.3z\"\n            fill=\"none\"\n            stroke=\"url(#SVGID_33_)\"\n          />\n        </g>\n      </g>\n\n      <linearGradient\n        id=\"Stroke-1备份-4_1_\"\n        gradientTransform=\"matrix(224 0 0 -130 -22445.5664 235373.9844)\"\n        gradientUnits=\"userSpaceOnUse\"\n        x1=\"102.0446\"\n        x2=\"102.0446\"\n        y1=\"1809.8226\"\n        y2=\"1809.215\"\n      >\n        <stop offset=\"0\" style=\"stop-color: #fff\" />\n        <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n      </linearGradient>\n      <path\n        id=\"Stroke-1备份-4\"\n        d=\"\n\t\tM333.1,116c-43.6,25.3-43.6,66.7,0,92c43.6,25.3,115,25.3,158.6,0c43.6-25.3,43.6-66.7,0-92C448.1,90.7,376.8,90.7,333.1,116z\"\n        enable-background=\"new    \"\n        fill=\"none\"\n        opacity=\"0.2645\"\n        stroke=\"url(#Stroke-1备份-4_1_)\"\n      />\n\n      <linearGradient\n        id=\"Stroke-1备份-5_1_\"\n        gradientTransform=\"matrix(224 0 0 -130 -22445.5664 235434.9375)\"\n        gradientUnits=\"userSpaceOnUse\"\n        x1=\"102.0446\"\n        x2=\"102.0446\"\n        y1=\"1809.8226\"\n        y2=\"1809.215\"\n      >\n        <stop offset=\"0\" style=\"stop-color: #fff\" />\n        <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n      </linearGradient>\n\n      <path\n        id=\"Stroke-1备份-5\"\n        d=\"\n\t\tM333.1,177c-43.6,25.3-43.6,66.7,0,92c43.6,25.3,115,25.3,158.6,0c43.6-25.3,43.6-66.7,0-92C448.1,151.7,376.8,151.7,333.1,177z\"\n        enable-background=\"new    \"\n        fill=\"none\"\n        opacity=\"7.956659e-02\"\n        stroke=\"url(#Stroke-1备份-5_1_)\"\n      />\n      <g id=\"编组-15\" transform=\"translate(286.000000, 69.000000)\">\n        <linearGradient\n          id=\"SVGID_34_\"\n          gradientTransform=\"matrix(84.647 0 0 -130.0737 15677.0488 226435.8125)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-184.6048\"\n          x2=\"-184.7916\"\n          y1=\"1739.7522\"\n          y2=\"1740.8326\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n          <stop offset=\"1\" style=\"stop-color: #00209e\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M84.6,54\n\t\t\tv71.1c0,3.8-2.9,5.8-6.4,4.6c-14-5-27.2-11.1-39.4-18.1C25.4,103.8,14,95.3,4.7,86.3C2.1,83.6,0,78.4,0,74.6V3.5\n\t\t\tc0-3.8,2.1-4.6,4.7-1.9c9.3,9,20.6,17.6,34.1,25.3C51,33.9,64.2,39.9,78.2,45C81.8,46.2,84.6,50.2,84.6,54z\"\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_34_)\"\n          fill-rule=\"evenodd\"\n          opacity=\"0.5\"\n        />\n\n        <linearGradient\n          id=\"SVGID_35_\"\n          gradientTransform=\"matrix(42.8864 0 0 -55.1569 8005.1216 95474.0859)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-186.8636\"\n          x2=\"-185.8636\"\n          y1=\"1729.7074\"\n          y2=\"1729.7074\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4c83ff\" />\n          <stop offset=\"1\" style=\"stop-color: #2afadf\" />\n        </linearGradient>\n        <path\n          d=\"M33.5,44.1c11.8,6.8,21.4,23.4,21.4,37\n\t\t\tc0,13.6-9.7,19.1-21.5,12.3C21.6,86.6,12,70,12,56.4C12.1,42.8,21.7,37.3,33.5,44.1z M33.4,85.5c8,4.6,14.6,0.9,14.6-8.3\n\t\t\tc0-9.2-6.5-20.5-14.5-25.1c-8-4.6-14.6-0.9-14.6,8.3C18.9,69.6,25.4,80.9,33.4,85.5L33.4,85.5z\"\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_35_)\"\n          opacity=\"0.4\"\n        />\n\n        <linearGradient\n          id=\"SVGID_36_\"\n          gradientTransform=\"matrix(21.3932 0 0 -37.017 4071.8147 63756.6836)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-189.5823\"\n          x2=\"-188.5823\"\n          y1=\"1720.6718\"\n          y2=\"1720.6718\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4c83ff\" />\n          <stop offset=\"1\" style=\"stop-color: #2afadf\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M33.5,44.1c11.8,6.8,21.4,23.4,21.4,37l-6.9-4\n\t\t\tc0-9.2-6.5-20.5-14.5-25.1L33.5,44.1z\"\n          fill=\"url(#SVGID_36_)\"\n          fill-rule=\"evenodd\"\n        />\n      </g>\n\n      <g\n        id=\"编组-15备份\"\n        transform=\"translate(524.500000, 179.500000) scale(-1, 1) translate(-524.500000, -179.500000) translate(482.000000, 114.000000)\"\n      >\n        <linearGradient\n          id=\"SVGID_37_\"\n          gradientTransform=\"matrix(-84.647 0 0 -130.0737 39258.8555 220582.5)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"463.1948\"\n          x2=\"463.008\"\n          y1=\"1694.7522\"\n          y2=\"1695.8326\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n          <stop offset=\"1\" style=\"stop-color: #00209e\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M84.6,54\n\t\t\tv71.1c0,3.8-2.9,5.8-6.4,4.6c-14-5-27.2-11.1-39.4-18.1C25.4,103.8,14,95.3,4.7,86.3C2.1,83.6,0,78.4,0,74.6V3.5\n\t\t\tc0-3.8,2.1-4.6,4.7-1.9c9.3,9,20.6,17.6,34.1,25.3C51,33.9,64.2,39.9,78.2,45C81.8,46.2,84.6,50.2,84.6,54z\"\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_37_)\"\n          fill-rule=\"evenodd\"\n          opacity=\"0.5\"\n        />\n\n        <linearGradient\n          id=\"SVGID_38_\"\n          gradientTransform=\"matrix(-42.8864 0 0 -55.1569 19852.209 92992.0234)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"462.6216\"\n          x2=\"463.1363\"\n          y1=\"1684.7074\"\n          y2=\"1684.7074\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #ccff70\" />\n          <stop offset=\"1\" style=\"stop-color: #2afadf\" />\n        </linearGradient>\n        <path\n          d=\"M33.5,44.1c11.8,6.8,21.4,23.4,21.4,37\n\t\t\tc0,13.6-9.7,19.1-21.5,12.3C21.6,86.6,12,70,12,56.4C12.1,42.8,21.7,37.3,33.5,44.1z M33.4,85.5c8,4.6,14.6,0.9,14.6-8.3\n\t\t\tc0-9.2-6.5-20.5-14.5-25.1c-8-4.6-14.6-0.9-14.6,8.3C18.9,69.6,25.4,80.9,33.4,85.5L33.4,85.5z\"\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_38_)\"\n          opacity=\"0.4\"\n        />\n\n        <linearGradient\n          id=\"SVGID_39_\"\n          gradientTransform=\"matrix(-21.3932 0 0 -37.017 9879.3115 62090.918)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"461.0466\"\n          x2=\"462.0466\"\n          y1=\"1675.6718\"\n          y2=\"1675.6718\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4c83ff\" />\n          <stop offset=\"1\" style=\"stop-color: #2afa91\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M33.5,44.1c11.8,6.8,21.4,23.4,21.4,37l-6.9-4\n\t\t\tc0-9.2-6.5-20.5-14.5-25.1L33.5,44.1z\"\n          fill=\"url(#SVGID_39_)\"\n          fill-rule=\"evenodd\"\n        />\n      </g>\n      <g id=\"编组\" transform=\"translate(457.000000, 235.000000)\">\n        <linearGradient\n          id=\"SVGID_40_\"\n          gradientTransform=\"matrix(94.2244 0 0 -135.8154 33551.6523 213971.6875)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-355.4666\"\n          x2=\"-355.6789\"\n          y1=\"1574.0848\"\n          y2=\"1575.1653\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n          <stop offset=\"1\" style=\"stop-color: #00209e\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M36.3,166.1\n\t\t\tc-9.3,4.2-19.3,7.7-29.7,10.5c-3.6,1-6.6-1.4-6.6-5.1v-44.7c0-3.8,3-7.5,6.6-8.4C17,115.5,27,112,36.3,107.8\n\t\t\tc4.8-2.2,9.5-4.6,13.9-7.2c28.4-16.5,43.1-38,44-59.6v54.4c0.8,23-13.8,46-44,63.6C45.8,161.5,41.1,163.9,36.3,166.1z\"\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_40_)\"\n          fill-rule=\"evenodd\"\n          opacity=\"0.5\"\n        />\n\n        <linearGradient\n          id=\"SVGID_41_\"\n          gradientTransform=\"matrix(19.3679 0 0 -68.3208 7052.4458 107110.6953)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-359.7519\"\n          x2=\"-359.7874\"\n          y1=\"1566.6797\"\n          y2=\"1567.7601\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n          <stop offset=\"1\" style=\"stop-color: #00209e\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M79.4,59.9\n\t\t\tc-2.4-2.9-4.6-8.1-4.6-11.8V3.5c0-3.8,2.2-4.6,4.6-1.8c17.4,20.9,19.5,45,6.1,66.6C83.7,65.5,81.7,62.7,79.4,59.9z\"\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_41_)\"\n          fill-rule=\"evenodd\"\n          opacity=\"0.5\"\n        />\n\n        <linearGradient\n          id=\"SVGID_42_\"\n          gradientTransform=\"matrix(5.707 0 0 -15.38 2179.3494 23459.5469)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-372.3728\"\n          x2=\"-372.3728\"\n          y1=\"1517.4354\"\n          y2=\"1516.2238\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4c83ff\" />\n          <stop offset=\"1\" style=\"stop-color: #2afadf\" />\n        </linearGradient>\n        <polygon\n          clip-rule=\"evenodd\"\n          fill=\"url(#SVGID_42_)\"\n          fill-rule=\"evenodd\"\n          points=\"57.1,146.8 57,135.5 51.4,138.7 51.4,150.9\n\t\t\t\"\n        />\n\n        <linearGradient\n          id=\"Path备份_1_\"\n          gradientTransform=\"matrix(5.707 0 0 -15.38 2199.9822 23445.7344)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-372.3728\"\n          x2=\"-372.3728\"\n          y1=\"1517.4354\"\n          y2=\"1516.2238\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4c83ff\" />\n          <stop offset=\"1\" style=\"stop-color: #2afadf\" />\n        </linearGradient>\n        <polygon\n          id=\"Path备份\"\n          clip-rule=\"evenodd\"\n          fill=\"url(#Path备份_1_)\"\n          fill-rule=\"evenodd\"\n          points=\"77.7,133 77.7,121.7\n\t\t\t72,124.9 72,137 \t\t\"\n        />\n\n        <linearGradient\n          id=\"SVGID_43_\"\n          gradientTransform=\"matrix(5.791 0 0 -44.69 2175.166 69834.2344)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-372.1319\"\n          x2=\"-372.1109\"\n          y1=\"1562.8905\"\n          y2=\"1558.8889\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4c83ff\" />\n          <stop offset=\"1\" style=\"stop-color: #2afadf\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M23.2,165.4c-1.9,0.7-3.8,1.4-5.7,2.1l-0.1-42.8\n\t\t\tc1.9-0.6,3.8-1.3,5.7-1.9L23.2,165.4z\"\n          fill=\"url(#SVGID_43_)\"\n          fill-rule=\"evenodd\"\n        />\n\n        <linearGradient\n          id=\"SVGID_44_\"\n          gradientTransform=\"matrix(5.754 0 0 -21.635 2173.4099 33364.9844)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-372.2268\"\n          x2=\"-372.2268\"\n          y1=\"1536.7212\"\n          y2=\"1535.3361\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4c83ff\" />\n          <stop offset=\"1\" style=\"stop-color: #2afadf\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M34.5,160.5c-1,0.5-2,1-3.1,1.4\n\t\t\tc-0.8,0.4-1.7,0.8-2.6,1.1L28.7,144c0,0,0.1,0,0.1,0c0.9-0.4,1.7-0.8,2.6-1.1c1-0.5,2-0.9,3-1.4L34.5,160.5z\"\n          fill=\"url(#SVGID_44_)\"\n          fill-rule=\"evenodd\"\n        />\n\n        <linearGradient\n          id=\"SVGID_45_\"\n          gradientTransform=\"matrix(5.78 0 0 -42.615 2194.2666 66542.2656)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-372.1471\"\n          x2=\"-372.1471\"\n          y1=\"1559.7903\"\n          y2=\"1558.4763\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4c83ff\" />\n          <stop offset=\"1\" style=\"stop-color: #2afadf\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M46.1,154.2c-0.6,0.4-1.2,0.7-1.9,1.1\n\t\t\tc-1.2,0.7-2.5,1.4-3.8,2.1l-0.1-39.3l5.7-3.3L46.1,154.2z\"\n          fill=\"url(#SVGID_45_)\"\n          fill-rule=\"evenodd\"\n        />\n\n        <linearGradient\n          id=\"Path备份-2_1_\"\n          gradientTransform=\"matrix(5.78 0 0 -28.2102 2214.8997 43745.1641)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"-372.1471\"\n          x2=\"-372.1471\"\n          y1=\"1547.6044\"\n          y2=\"1546.2904\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4c83ff\" />\n          <stop offset=\"1\" style=\"stop-color: #2afadf\" />\n        </linearGradient>\n        <path\n          id=\"Path备份-2\"\n          clip-rule=\"evenodd\"\n          d=\"M66.8,140.4\n\t\t\tc-0.6,0.4-1.2,0.7-1.9,1.1c-1.2,0.7-2.5,1.4-3.8,2.1L61,118.7l5.7-3.3L66.8,140.4z\"\n          fill=\"url(#Path备份-2_1_)\"\n          fill-rule=\"evenodd\"\n        />\n      </g>\n\n      <g\n        id=\"编组备份\"\n        transform=\"translate(316.500000, 268.500000) scale(-1, 1) translate(-316.500000, -268.500000) translate(269.000000, 180.000000)\"\n      >\n        <linearGradient\n          id=\"SVGID_46_\"\n          gradientTransform=\"matrix(-94.2244 0 0 -135.8154 24584.7852 221441.5469)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"260.3016\"\n          x2=\"260.0893\"\n          y1=\"1629.0848\"\n          y2=\"1630.1653\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n          <stop offset=\"1\" style=\"stop-color: #00209e\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M36.3,166.1\n\t\t\tc-9.3,4.2-19.3,7.7-29.7,10.5c-3.6,1-6.6-1.4-6.6-5.1v-44.7c0-3.8,3-7.5,6.6-8.4C17,115.5,27,112,36.3,107.8\n\t\t\tc4.8-2.2,9.5-4.6,13.9-7.2c28.4-16.5,43.1-38,44-59.6v54.4c0.8,23-13.8,46-44,63.6C45.8,161.5,41.1,163.9,36.3,166.1z\"\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_46_)\"\n          fill-rule=\"evenodd\"\n          opacity=\"0.5\"\n        />\n\n        <linearGradient\n          id=\"SVGID_47_\"\n          gradientTransform=\"matrix(-19.3679 0 0 -68.3208 5047.2349 110868.3359)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"256.219\"\n          x2=\"256.1836\"\n          y1=\"1621.6797\"\n          y2=\"1622.7601\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #4587ef\" />\n          <stop offset=\"1\" style=\"stop-color: #00209e\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M79.4,59.9\n\t\t\tc-2.4-2.9-4.6-8.1-4.6-11.8V3.5c0-3.8,2.2-4.6,4.6-1.8c17.4,20.9,19.5,45,6.1,66.6C83.7,65.5,81.7,62.7,79.4,59.9z\"\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_47_)\"\n          fill-rule=\"evenodd\"\n          opacity=\"0.5\"\n        />\n\n        <linearGradient\n          id=\"路径_1_\"\n          gradientTransform=\"matrix(-65.0245 0 0 -44.112 16953.9395 71322.6641)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"259.3418\"\n          x2=\"258.6121\"\n          y1=\"1614.2808\"\n          y2=\"1614.0671\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #f8ff24\" />\n          <stop offset=\"1\" style=\"stop-color: #fff\" />\n        </linearGradient>\n\n        <path\n          id=\"路径\"\n          d=\"\n\t\t\tM84.5,110.9c-11.6-15.5-17.4-14.7-17.4,2.4c0,25.7-11.4,29.9-19.6,17.1c-8.2-12.9-23.3-14.6-28,13.6\"\n          enable-background=\"new    \"\n          fill=\"none\"\n          opacity=\"0.6\"\n          stroke=\"url(#路径_1_)\"\n          stroke-linecap=\"round\"\n          stroke-width=\"3.5247\"\n        />\n      </g>\n      <g id=\"编组-11\" transform=\"translate(558.000000, 306.000000)\">\n        <g id=\"product3\" transform=\"translate(11.447727, 0.000000)\">\n          <g filter=\"url(#filter-95)\">\n            <linearGradient\n              id=\"SVGID_48_\"\n              gradientTransform=\"matrix(174.1648 0 0 -101.4708 81515.7969 152491.3594)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-467.0351\"\n              x2=\"-467.7105\"\n              y1=\"1501.2344\"\n              y2=\"1501.0835\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #1b0f9c; stop-opacity: 0\" />\n              <stop offset=\"1\" style=\"stop-color: #060071\" />\n            </linearGradient>\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M171.8,157.9c4.3,2.5,4.3,6.5,0,8.9L81.2,220\n\t\t\t\t\tc-4.8,2.5-10.6,2.5-15.4,0L4.1,184.4c-4.3-2.5-4.3-6.5,0-8.9l90.5-53.2c4.8-2.5,10.6-2.5,15.4,0L171.8,157.9z\"\n              fill=\"url(#SVGID_48_)\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n          <g id=\"编组-2\" transform=\"translate(0.862170, 124.812382)\">\n            <linearGradient\n              id=\"SVGID_49_\"\n              gradientTransform=\"matrix(145.0025 0 0 -83.8574 68008.0859 115279.1875)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-469.0133\"\n              x2=\"-468.0133\"\n              y1=\"1374.2056\"\n              y2=\"1374.2056\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #6ecaf9\" />\n              <stop offset=\"0.35\" style=\"stop-color: #a1ddfb\" />\n              <stop offset=\"1\" style=\"stop-color: #fff\" />\n            </linearGradient>\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M141.8,37.5c4.3,2.5,4.3,6.5,0,8.9L80.4,82\n\t\t\t\t\tc-4.8,2.5-10.6,2.5-15.4,0L3.2,46.4c-4.3-2.5-4.3-6.5,0-8.9L64.6,1.8c4.8-2.5,10.6-2.5,15.4,0L141.8,37.5z\"\n              fill=\"url(#SVGID_49_)\"\n              fill-rule=\"evenodd\"\n            />\n\n            <linearGradient\n              id=\"SVGID_50_\"\n              gradientTransform=\"matrix(132.2205 0 0 -73.006 62028.5859 100235.5703)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-469.0813\"\n              x2=\"-468.0813\"\n              y1=\"1372.403\"\n              y2=\"1372.403\"\n            >\n              <stop offset=\"0.12\" style=\"stop-color: #5c90fe\" />\n              <stop offset=\"0.56\" style=\"stop-color: #466cf5\" />\n              <stop offset=\"0.69\" style=\"stop-color: #4265f3\" />\n              <stop offset=\"0.87\" style=\"stop-color: #3853ef\" />\n              <stop offset=\"1\" style=\"stop-color: #2e42eb\" />\n            </linearGradient>\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M72.8,78.5c-1.7,0.1-3.4-0.3-5-1.1L6.4,42L67.3,6.5\n\t\t\t\t\tc1.5-0.8,3.2-1.1,5-1.1c1.7-0.1,3.4,0.3,5,1.1L138.6,42L77.7,77.3C76.2,78.1,74.5,78.5,72.8,78.5z\"\n              fill=\"url(#SVGID_50_)\"\n              fill-rule=\"evenodd\"\n            />\n\n            <linearGradient\n              id=\"SVGID_51_\"\n              gradientTransform=\"matrix(144.9687 0 0 -54.9245 67992.2734 75196.0781)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-469.0135\"\n              x2=\"-468.0135\"\n              y1=\"1367.8169\"\n              y2=\"1367.8169\"\n            >\n              <stop offset=\"0.12\" style=\"stop-color: #5c90fe\" />\n              <stop offset=\"0.56\" style=\"stop-color: #466cf5\" />\n              <stop offset=\"0.69\" style=\"stop-color: #4265f3\" />\n              <stop offset=\"0.87\" style=\"stop-color: #3853ef\" />\n              <stop offset=\"1\" style=\"stop-color: #2e42eb\" />\n            </linearGradient>\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M145,42c0,0.1,0,0.2,0,0.3s0,0.2,0,0.3s0,0.2-0.1,0.3\n\t\t\t\t\tc-0.1,0.1-0.1,0.2-0.1,0.3s-0.1,0.2-0.1,0.3c0,0.1-0.1,0.2-0.2,0.3l-0.2,0.2c-0.1,0.1-0.2,0.3-0.3,0.4l-0.2,0.2\n\t\t\t\t\tc-0.1,0.2-0.3,0.3-0.5,0.5l-0.2,0.2l-0.3,0.3l-0.3,0.2l-0.4,0.3l-0.4,0.3L80.5,82c-0.4,0.2-0.7,0.4-1.1,0.5l-0.3,0.1\n\t\t\t\t\tc-0.3,0.1-0.6,0.2-0.9,0.3l-0.3,0.1l-0.6,0.2l-0.4,0.1l-0.6,0.1l-0.4,0.1c-0.4,0.1-0.8,0.1-1.2,0.2h-0.4h-0.7H72h-0.6h-0.5\n\t\t\t\t\tl-0.6,0l-0.5-0.1l-0.6-0.1l-0.5-0.1l-0.7-0.2l-0.4-0.1c-0.4-0.1-0.8-0.2-1.2-0.4l-0.2-0.1c-0.5-0.2-1-0.4-1.5-0.7L3.2,46.5\n\t\t\t\t\tC1.1,45.2,0,43.6,0,42v13.3c0,1.6,1.1,3.2,3.2,4.5L65,95.4c0.5,0.3,1,0.5,1.5,0.7h0.1h0.1c0.4,0.2,0.8,0.3,1.2,0.4h0.1l0.3,0.1\n\t\t\t\t\tl0.7,0.2h0.2h0.3l0.6,0.1h0.2h0.3l0.5,0.1h0.3h0.3H72h1.6h0.7h0.3h0.1c0.4,0,0.8-0.1,1.2-0.2H76l0.4-0.1l0.5,0.1l0.4-0.1\n\t\t\t\t\tl0.6-0.2H78l0.2-0.1c0.3-0.1,0.6-0.2,0.9-0.3l0.3-0.1c0.4-0.2,0.7-0.3,1.1-0.5l61.4-35.6l0.1-0.1l0.3-0.2l0.4-0.3l0.3-0.2\n\t\t\t\t\tl0.3-0.3l0.1-0.1l0.1-0.1c0.2-0.2,0.3-0.3,0.5-0.5L144,58l0.1-0.1c0.1-0.1,0.2-0.2,0.3-0.4l0.1-0.2v-0.1\n\t\t\t\t\tc0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.1,0.1-0.2v-0.1c0-0.1,0.1-0.2,0.1-0.3c0-0.1,0-0.2,0.1-0.2c0.1-0.1,0,0,0-0.1s0-0.2,0-0.3\n\t\t\t\t\ts0-0.2,0-0.2c0-0.1,0-0.1,0-0.1l0,0L145,42z\"\n              fill=\"url(#SVGID_51_)\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n          <g id=\"编组-2备份\" transform=\"translate(8.400000, 103.200000)\">\n            <g\n              id=\"Path备份-3\"\n              enable-background=\"new    \"\n              filter=\"url(#filter-100)\"\n              opacity=\"0.6817\"\n            >\n              <linearGradient\n                id=\"SVGID_52_\"\n                gradientTransform=\"matrix(130.5022 0 0 -75.4716 62201.1797 105298.6953)\"\n                gradientUnits=\"userSpaceOnUse\"\n                x1=\"-475.6305\"\n                x2=\"-476.3059\"\n                y1=\"1394.5802\"\n                y2=\"1394.4315\"\n              >\n                <stop offset=\"0\" style=\"stop-color: #1b0f9c; stop-opacity: 0\" />\n                <stop offset=\"1\" style=\"stop-color: #060071\" />\n              </linearGradient>\n              <path\n                clip-rule=\"evenodd\"\n                d=\"M127.6,51.7c3.9,2.2,3.9,5.8,0,8L72.3,91.8\n\t\t\t\t\t\tc-4.4,2.2-9.5,2.2-13.9,0L2.9,59.7c-3.8-2.2-3.9-5.8,0-8l55.2-32.1c4.4-2.2,9.5-2.2,13.9,0L127.6,51.7z\"\n                fill=\"url(#SVGID_52_)\"\n                fill-rule=\"evenodd\"\n              />\n            </g>\n\n            <linearGradient\n              id=\"SVGID_53_\"\n              gradientTransform=\"matrix(130.5022 0 0 -75.4716 62201.1797 105280.6953)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-476.6293\"\n              x2=\"-475.6293\"\n              y1=\"1394.4705\"\n              y2=\"1394.4705\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #6ecaf9\" />\n              <stop offset=\"0.35\" style=\"stop-color: #a1ddfb\" />\n              <stop offset=\"1\" style=\"stop-color: #fff\" />\n            </linearGradient>\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M127.6,33.7c3.9,2.2,3.9,5.8,0,8L72.3,73.8\n\t\t\t\t\tc-4.4,2.2-9.5,2.2-13.9,0L2.9,41.7c-3.8-2.2-3.9-5.8,0-8L58.1,1.7c4.4-2.2,9.5-2.2,13.9,0L127.6,33.7z\"\n              fill=\"url(#SVGID_53_)\"\n              fill-rule=\"evenodd\"\n            />\n\n            <linearGradient\n              id=\"SVGID_54_\"\n              gradientTransform=\"matrix(118.9985 0 0 -65.7054 56732.918 91530.375)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-476.7049\"\n              x2=\"-475.7049\"\n              y1=\"1392.4675\"\n              y2=\"1392.4675\"\n            >\n              <stop offset=\"0.12\" style=\"stop-color: #5c90fe\" />\n              <stop offset=\"0.56\" style=\"stop-color: #466cf5\" />\n              <stop offset=\"0.69\" style=\"stop-color: #4265f3\" />\n              <stop offset=\"0.87\" style=\"stop-color: #3853ef\" />\n              <stop offset=\"1\" style=\"stop-color: #2e42eb\" />\n            </linearGradient>\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M65.5,70.6c-1.6,0.1-3.1-0.3-4.5-1L5.8,37.8L60.6,5.9\n\t\t\t\t\tc1.4-0.7,2.9-1,4.5-1c1.6,0,3.1,0.3,4.5,1l55.2,31.9L69.9,69.6C68.6,70.3,67,70.6,65.5,70.6z\"\n              fill=\"url(#SVGID_54_)\"\n              fill-rule=\"evenodd\"\n            />\n\n            <linearGradient\n              id=\"SVGID_55_\"\n              gradientTransform=\"matrix(130.4718 0 0 -49.432 62186.7266 68643.1094)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-476.6295\"\n              x2=\"-475.6295\"\n              y1=\"1387.3719\"\n              y2=\"1387.3719\"\n            >\n              <stop offset=\"0.12\" style=\"stop-color: #5c90fe\" />\n              <stop offset=\"0.56\" style=\"stop-color: #466cf5\" />\n              <stop offset=\"0.69\" style=\"stop-color: #4265f3\" />\n              <stop offset=\"0.87\" style=\"stop-color: #3853ef\" />\n              <stop offset=\"1\" style=\"stop-color: #2e42eb\" />\n            </linearGradient>\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M130.5,37.8c0,0.1,0,0.2,0,0.3c0,0.1,0,0.2,0,0.3\n\t\t\t\t\ts0,0.2-0.1,0.3c-0.1,0.1,0,0.2-0.1,0.3s-0.1,0.2-0.1,0.2c0,0.1-0.1,0.2-0.1,0.3l-0.1,0.2c-0.1,0.1-0.1,0.2-0.2,0.3l-0.2,0.2\n\t\t\t\t\tc-0.1,0.2-0.3,0.3-0.4,0.4l-0.2,0.2l-0.3,0.3l-0.3,0.2l-0.3,0.2l-0.4,0.2L72.4,73.8c-0.3,0.2-0.6,0.3-1,0.5l-0.3,0.1\n\t\t\t\t\tc-0.3,0.1-0.5,0.2-0.8,0.3l-0.3,0.1l-0.5,0.1L69.2,75l-0.5,0.1l-0.4,0.1c-0.4,0.1-0.7,0.1-1.1,0.2h-0.3h-0.7h-1.4h-0.5h-0.5\n\t\t\t\t\tl-0.5,0l-0.4-0.1l-0.5-0.1L62,75.2L61.4,75L61,74.9c-0.4-0.1-0.7-0.2-1-0.4l-0.1-0.1c-0.5-0.2-0.9-0.4-1.3-0.6L2.9,41.8\n\t\t\t\t\tC1,40.7,0,39.3,0,37.8v12c0,1.5,1,2.9,2.9,4l55.6,32.1c0.4,0.2,0.9,0.5,1.3,0.6h0.1H60c0.3,0.1,0.7,0.3,1,0.3h0.1l0.3,0.1\n\t\t\t\t\tl0.6,0.1h0.2h0.2l0.5,0.1h0.2h0.2l0.5,0.1h0.2h0.3h0.5h1.4h0.7h0.3h0.1c0.4,0,0.7-0.1,1.1-0.2h0.1l0.3-0.1l0.4,0.1l0.4-0.1\n\t\t\t\t\tl0.5-0.1h0.1l0.2-0.1c0.3-0.1,0.5-0.2,0.8-0.3l0.3-0.1c0.3-0.1,0.7-0.3,1-0.5l55.2-32.1l0.1-0.1l0.3-0.2l0.3-0.2l0.3-0.2\n\t\t\t\t\tl0.3-0.3l0.1,0l0.1-0.1c0.1-0.1,0.3-0.3,0.4-0.4l0.1-0.1l0.1-0.1c0.1-0.1,0.2-0.2,0.2-0.3l0.1-0.1v-0.1c0.1-0.1,0.1-0.2,0.1-0.3\n\t\t\t\t\tc0-0.1,0.1-0.1,0.1-0.2V51c0-0.1,0.1-0.2,0.1-0.3s0-0.1,0.1-0.2c0.1-0.1,0,0,0,0s0-0.2,0-0.3c0-0.1,0-0.1,0-0.2s0-0.1,0-0.1l0,0\n\t\t\t\t\tL130.5,37.8z\"\n              fill=\"url(#SVGID_55_)\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n          <path\n            clip-rule=\"evenodd\"\n            d=\"M63.9,130.7l0.1,0.4\n\t\t\t\tL63.9,130.7z\"\n            fill=\"#666666\"\n            fill-rule=\"evenodd\"\n            stroke=\"#000000\"\n            stroke-width=\"0.6\"\n          />\n          <path\n            clip-rule=\"evenodd\"\n            d=\"M63.8,130l0.1,0.4\n\t\t\t\tL63.8,130z\"\n            fill=\"#666666\"\n            fill-rule=\"evenodd\"\n            stroke=\"#000000\"\n            stroke-width=\"0.6\"\n          />\n\n          <linearGradient\n            id=\"SVGID_56_\"\n            gradientTransform=\"matrix(132.2205 0 0 -178.9849 61915.457 269544.7812)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"-467.7177\"\n            x2=\"-467.7209\"\n            y1=\"1504.9636\"\n            y2=\"1506.2531\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #00dcf3\" />\n            <stop\n              offset=\"1.000000e-02\"\n              style=\"stop-color: #00dbf3; stop-opacity: 0.97\"\n            />\n            <stop\n              offset=\"8.000000e-02\"\n              style=\"stop-color: #00d0ef; stop-opacity: 0.77\"\n            />\n            <stop\n              offset=\"0.16\"\n              style=\"stop-color: #00c7ec; stop-opacity: 0.59\"\n            />\n            <stop\n              offset=\"0.24\"\n              style=\"stop-color: #00beea; stop-opacity: 0.43\"\n            />\n            <stop\n              offset=\"0.32\"\n              style=\"stop-color: #00b7e8; stop-opacity: 0.3\"\n            />\n            <stop\n              offset=\"0.42\"\n              style=\"stop-color: #00b2e6; stop-opacity: 0.19\"\n            />\n            <stop\n              offset=\"0.52\"\n              style=\"stop-color: #00ade5; stop-opacity: 0.1\"\n            />\n            <stop\n              offset=\"0.63\"\n              style=\"stop-color: #00aae4; stop-opacity: 4e-2\"\n            />\n            <stop\n              offset=\"0.77\"\n              style=\"stop-color: #00a9e3; stop-opacity: 1e-2\"\n            />\n            <stop offset=\"1\" style=\"stop-color: #00a8e3; stop-opacity: 0\" />\n          </linearGradient>\n          <path\n            clip-rule=\"evenodd\"\n            d=\"M7.3,0.1v142.4l61.4,35.4c1.5,0.8,3.3,1.1,5,1.1\n\t\t\t\tc1.7,0.1,3.4-0.3,5-1.1l60.9-35.4V0L7.3,0.1z\"\n            fill=\"url(#SVGID_56_)\"\n            fill-rule=\"evenodd\"\n          />\n        </g>\n        <g id=\"编组备份-8\" transform=\"translate(47.447727, 63.600000)\">\n          <g\n            enable-background=\"new    \"\n            filter=\"url(#filter-106)\"\n            opacity=\"0.3003\"\n          >\n            <linearGradient\n              id=\"SVGID_57_\"\n              gradientTransform=\"matrix(99.0186 0 0 -57.6674 49956.1875 82510.0469)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-503.4795\"\n              x2=\"-504.155\"\n              y1=\"1430.0212\"\n              y2=\"1429.8705\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #1b0f9c; stop-opacity: 0\" />\n              <stop offset=\"1\" style=\"stop-color: #060071\" />\n            </linearGradient>\n            <polygon\n              clip-rule=\"evenodd\"\n              fill=\"url(#SVGID_57_)\"\n              fill-rule=\"evenodd\"\n              points=\"3.5,59.5 38.6,79.7 102.5,42.3 67.4,22.1\n\t\t\t\t\t\"\n            />\n          </g>\n          <g>\n            <g>\n              <linearGradient\n                id=\"path-108_1_\"\n                gradientTransform=\"matrix(70.176 0 0 -40.5146 35432.1289 57650.0859)\"\n                gradientUnits=\"userSpaceOnUse\"\n                x1=\"-504.9012\"\n                x2=\"-503.9012\"\n                y1=\"1422.4436\"\n                y2=\"1422.4436\"\n              >\n                <stop offset=\"0\" style=\"stop-color: #fff\" />\n                <stop offset=\"1\" style=\"stop-color: #00a2ff\" />\n              </linearGradient>\n              <polygon\n                id=\"path-108\"\n                clip-rule=\"evenodd\"\n                fill=\"url(#path-108_1_)\"\n                fill-rule=\"evenodd\"\n                points=\"0.2,20.4 35.3,40.6\n\t\t\t\t\t\t70.4,20.4 35.3,0.1 \t\t\t\t\t\"\n              />\n            </g>\n            <g filter=\"url(#filter-109)\">\n              <polygon\n                clip-rule=\"evenodd\"\n                fill-rule=\"evenodd\"\n                points=\"0.2,20.4 35.3,40.6 70.4,20.4 35.3,0.1 \t\t\t\t\t\"\n              />\n            </g>\n          </g>\n\n          <linearGradient\n            id=\"SVGID_58_\"\n            gradientTransform=\"matrix(35.088 0 0 -60.7744 17767.0781 87007.4453)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"-505.8155\"\n            x2=\"-505.9297\"\n            y1=\"1431.3086\"\n            y2=\"1429.8619\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #7bedff\" />\n            <stop offset=\"1\" style=\"stop-color: #060071\" />\n          </linearGradient>\n          <polygon\n            clip-rule=\"evenodd\"\n            fill=\"url(#SVGID_58_)\"\n            fill-rule=\"evenodd\"\n            points=\"35.2,81.1 35.2,40.6 0.1,20.4 0.1,60.9 \t\t\t\"\n          />\n          <g id=\"编组-10\" transform=\"translate(4.431004, 28.063025)\">\n            <g id=\"编组-8\">\n              <g id=\"Shape备份-3\" transform=\"translate(0.000000, -0.000000)\">\n                <g id=\"蒙版\">\n                  <linearGradient\n                    id=\"SVGID_59_\"\n                    gradientTransform=\"matrix(6.3405 0 0 -10.9369 3322.1904 14513.207)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"-523.204\"\n                    x2=\"-524.0609\"\n                    y1=\"1326.9464\"\n                    y2=\"1325.4684\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #fb6583\" />\n                    <stop offset=\"0.1302\" style=\"stop-color: #fc7688\" />\n                    <stop offset=\"0.5277\" style=\"stop-color: #fda797\" />\n                    <stop offset=\"0.8302\" style=\"stop-color: #ffc6a0\" />\n                    <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_59_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_2_\"\n                    filterUnits=\"userSpaceOnUse\"\n                    height=\"10.9\"\n                    width=\"6.3\"\n                    x=\"0\"\n                    y=\"3.6\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask\n                  id=\"mask-113_1_\"\n                  height=\"10.9\"\n                  maskUnits=\"userSpaceOnUse\"\n                  width=\"6.3\"\n                  x=\"0\"\n                  y=\"3.6\"\n                >\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_2_)\">\n                    <polygon\n                      id=\"path-112_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_60_\"\n                  gradientTransform=\"matrix(-6.3405 0 0 -10.9369 -3111.8501 14516.8223)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-491.1827\"\n                  x2=\"-491.4043\"\n                  y1=\"1326.7101\"\n                  y2=\"1326.2715\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #fb6583\" />\n                  <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_60_)\"\n                  mask=\"url(#mask-113_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g id=\"Shape备份-4\" transform=\"translate(9.630049, 5.468450)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_61_\"\n                    gradientTransform=\"matrix(6.3405 0 0 -10.9369 3383.2495 14453.3994)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"-532.7115\"\n                    x2=\"-533.1328\"\n                    y1=\"1319.3007\"\n                    y2=\"1321.1917\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_61_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_3_\"\n                    filterUnits=\"userSpaceOnUse\"\n                    height=\"10.9\"\n                    width=\"6.3\"\n                    x=\"0\"\n                    y=\"3.6\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask\n                  id=\"mask-117_1_\"\n                  height=\"10.9\"\n                  maskUnits=\"userSpaceOnUse\"\n                  width=\"6.3\"\n                  x=\"0\"\n                  y=\"3.6\"\n                >\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_3_)\">\n                    <polygon\n                      id=\"path-116_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_62_\"\n                  gradientTransform=\"matrix(-6.3405 0 0 -10.9369 -3172.9092 14457.0146)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-500.7126\"\n                  x2=\"-501.1153\"\n                  y1=\"1321.2085\"\n                  y2=\"1320.8575\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_62_)\"\n                  mask=\"url(#mask-117_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g id=\"Shape备份-5\" transform=\"translate(19.201017, 11.077510)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_63_\"\n                    gradientTransform=\"matrix(6.3405 0 0 -10.9369 3443.9341 14392.0537)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"-542.2824\"\n                    x2=\"-542.7037\"\n                    y1=\"1313.6917\"\n                    y2=\"1315.5826\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_63_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_4_\"\n                    filterUnits=\"userSpaceOnUse\"\n                    height=\"10.9\"\n                    width=\"6.3\"\n                    x=\"0\"\n                    y=\"3.6\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask\n                  id=\"mask-120_1_\"\n                  height=\"10.9\"\n                  maskUnits=\"userSpaceOnUse\"\n                  width=\"6.3\"\n                  x=\"0\"\n                  y=\"3.6\"\n                >\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_4_)\">\n                    <polygon\n                      id=\"path-119_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_64_\"\n                  gradientTransform=\"matrix(-6.3405 0 0 -10.9369 -3233.5935 14395.6689)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-510.2835\"\n                  x2=\"-510.6862\"\n                  y1=\"1315.5994\"\n                  y2=\"1315.2484\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_64_)\"\n                  mask=\"url(#mask-120_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n            </g>\n            <g id=\"编组-8备份\" transform=\"translate(0.000000, 11.077510)\">\n              <g transform=\"translate(0.000000, -0.000000)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_65_\"\n                    gradientTransform=\"matrix(6.3405 0 0 -10.9369 3322.1904 14392.0537)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"-523.204\"\n                    x2=\"-524.0609\"\n                    y1=\"1315.8689\"\n                    y2=\"1314.3909\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #fb6583\" />\n                    <stop offset=\"0.1302\" style=\"stop-color: #fc7688\" />\n                    <stop offset=\"0.5277\" style=\"stop-color: #fda797\" />\n                    <stop offset=\"0.8302\" style=\"stop-color: #ffc6a0\" />\n                    <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_65_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_5_\"\n                    filterUnits=\"userSpaceOnUse\"\n                    height=\"10.9\"\n                    width=\"6.3\"\n                    x=\"0\"\n                    y=\"3.6\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask\n                  id=\"mask-122_1_\"\n                  height=\"10.9\"\n                  maskUnits=\"userSpaceOnUse\"\n                  width=\"6.3\"\n                  x=\"0\"\n                  y=\"3.6\"\n                >\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_5_)\">\n                    <polygon\n                      id=\"path-121_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_66_\"\n                  gradientTransform=\"matrix(-6.3405 0 0 -10.9369 -3111.8501 14395.6689)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-491.1827\"\n                  x2=\"-491.4043\"\n                  y1=\"1315.6326\"\n                  y2=\"1315.194\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #fb6583\" />\n                  <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_66_)\"\n                  mask=\"url(#mask-122_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g transform=\"translate(9.630049, 5.468450)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_67_\"\n                    gradientTransform=\"matrix(6.3405 0 0 -10.9369 3383.2495 14332.2451)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"-532.7115\"\n                    x2=\"-533.1328\"\n                    y1=\"1308.2231\"\n                    y2=\"1310.1143\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_67_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_6_\"\n                    filterUnits=\"userSpaceOnUse\"\n                    height=\"10.9\"\n                    width=\"6.3\"\n                    x=\"0\"\n                    y=\"3.6\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask\n                  id=\"mask-124_1_\"\n                  height=\"10.9\"\n                  maskUnits=\"userSpaceOnUse\"\n                  width=\"6.3\"\n                  x=\"0\"\n                  y=\"3.6\"\n                >\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_6_)\">\n                    <polygon\n                      id=\"path-123_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_68_\"\n                  gradientTransform=\"matrix(-6.3405 0 0 -10.9369 -3172.9092 14335.8613)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-500.7126\"\n                  x2=\"-501.1153\"\n                  y1=\"1310.131\"\n                  y2=\"1309.78\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_68_)\"\n                  mask=\"url(#mask-124_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g transform=\"translate(19.201017, 11.077510)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_69_\"\n                    gradientTransform=\"matrix(6.3405 0 0 -10.9369 3443.9341 14270.8994)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"-542.2824\"\n                    x2=\"-542.7037\"\n                    y1=\"1302.6141\"\n                    y2=\"1304.5051\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_69_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_7_\"\n                    filterUnits=\"userSpaceOnUse\"\n                    height=\"10.9\"\n                    width=\"6.3\"\n                    x=\"0\"\n                    y=\"3.6\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask\n                  id=\"mask-126_1_\"\n                  height=\"10.9\"\n                  maskUnits=\"userSpaceOnUse\"\n                  width=\"6.3\"\n                  x=\"0\"\n                  y=\"3.6\"\n                >\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_7_)\">\n                    <polygon\n                      id=\"path-125_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_70_\"\n                  gradientTransform=\"matrix(-6.3405 0 0 -10.9369 -3233.5935 14274.5156)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-510.2835\"\n                  x2=\"-510.6862\"\n                  y1=\"1304.5219\"\n                  y2=\"1304.1709\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_70_)\"\n                  mask=\"url(#mask-126_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n            </g>\n            <g id=\"编组-8备份-2\" transform=\"translate(0.000000, 22.155020)\">\n              <g transform=\"translate(0.000000, -0.000000)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_71_\"\n                    gradientTransform=\"matrix(6.3405 0 0 -10.9369 3322.1904 14270.8994)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"-523.204\"\n                    x2=\"-524.0609\"\n                    y1=\"1304.7914\"\n                    y2=\"1303.3134\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #fb6583\" />\n                    <stop offset=\"0.1302\" style=\"stop-color: #fc7688\" />\n                    <stop offset=\"0.5277\" style=\"stop-color: #fda797\" />\n                    <stop offset=\"0.8302\" style=\"stop-color: #ffc6a0\" />\n                    <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_71_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_8_\"\n                    filterUnits=\"userSpaceOnUse\"\n                    height=\"10.9\"\n                    width=\"6.3\"\n                    x=\"0\"\n                    y=\"3.6\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask\n                  id=\"mask-128_1_\"\n                  height=\"10.9\"\n                  maskUnits=\"userSpaceOnUse\"\n                  width=\"6.3\"\n                  x=\"0\"\n                  y=\"3.6\"\n                >\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_8_)\">\n                    <polygon\n                      id=\"path-127_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_72_\"\n                  gradientTransform=\"matrix(-6.3405 0 0 -10.9369 -3111.8501 14274.5156)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-491.1827\"\n                  x2=\"-491.4043\"\n                  y1=\"1304.5552\"\n                  y2=\"1304.1165\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #fb6583\" />\n                  <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_72_)\"\n                  mask=\"url(#mask-128_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g transform=\"translate(9.630049, 5.468450)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_73_\"\n                    gradientTransform=\"matrix(6.3405 0 0 -10.9369 3383.2495 14211.0918)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"-532.7115\"\n                    x2=\"-533.1328\"\n                    y1=\"1297.1456\"\n                    y2=\"1299.0367\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_73_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_9_\"\n                    filterUnits=\"userSpaceOnUse\"\n                    height=\"10.9\"\n                    width=\"6.3\"\n                    x=\"0\"\n                    y=\"3.6\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask\n                  id=\"mask-130_1_\"\n                  height=\"10.9\"\n                  maskUnits=\"userSpaceOnUse\"\n                  width=\"6.3\"\n                  x=\"0\"\n                  y=\"3.6\"\n                >\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_9_)\">\n                    <polygon\n                      id=\"path-129_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_74_\"\n                  gradientTransform=\"matrix(-6.3405 0 0 -10.9369 -3172.9092 14214.708)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-500.7126\"\n                  x2=\"-501.1153\"\n                  y1=\"1299.0535\"\n                  y2=\"1298.7025\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_74_)\"\n                  mask=\"url(#mask-130_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g transform=\"translate(19.201017, 11.077510)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_75_\"\n                    gradientTransform=\"matrix(6.3405 0 0 -10.9369 3443.9341 14149.7461)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"-542.2824\"\n                    x2=\"-542.7037\"\n                    y1=\"1291.5366\"\n                    y2=\"1293.4276\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_75_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_10_\"\n                    filterUnits=\"userSpaceOnUse\"\n                    height=\"10.9\"\n                    width=\"6.3\"\n                    x=\"0\"\n                    y=\"3.6\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask\n                  id=\"mask-132_1_\"\n                  height=\"10.9\"\n                  maskUnits=\"userSpaceOnUse\"\n                  width=\"6.3\"\n                  x=\"0\"\n                  y=\"3.6\"\n                >\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_10_)\">\n                    <polygon\n                      id=\"path-131_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_76_\"\n                  gradientTransform=\"matrix(-6.3405 0 0 -10.9369 -3233.5935 14153.3623)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"-510.2835\"\n                  x2=\"-510.6862\"\n                  y1=\"1293.4443\"\n                  y2=\"1293.0934\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_76_)\"\n                  mask=\"url(#mask-132_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n            </g>\n          </g>\n\n          <linearGradient\n            id=\"SVGID_77_\"\n            gradientTransform=\"matrix(35.088 0 0 -60.7744 17802.2441 87007.4453)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"-505.8547\"\n            x2=\"-505.8547\"\n            y1=\"1431.6647\"\n            y2=\"1430.4622\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #7bedff\" />\n            <stop offset=\"1\" style=\"stop-color: #060071\" />\n          </linearGradient>\n          <polygon\n            clip-rule=\"evenodd\"\n            fill=\"url(#SVGID_77_)\"\n            fill-rule=\"evenodd\"\n            points=\"35.3,81.1 35.3,40.6 70.4,20.4 70.4,60.9\n\t\t\t\t\"\n          />\n\n          <g\n            id=\"编组-10备份-4\"\n            transform=\"translate(53.703560, 51.063025) scale(-1, 1) translate(-53.703560, -51.063025) translate(40.203560, 28.063025)\"\n          >\n            <g>\n              <g transform=\"translate(0.000000, -0.000000)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_78_\"\n                    gradientTransform=\"matrix(-6.3405 0 0 -10.9369 3516.198 14513.207)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"553.8022\"\n                    x2=\"552.9454\"\n                    y1=\"1326.9464\"\n                    y2=\"1325.4684\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #fb6583\" />\n                    <stop offset=\"0.1302\" style=\"stop-color: #fc7688\" />\n                    <stop offset=\"0.5277\" style=\"stop-color: #fda797\" />\n                    <stop offset=\"0.8302\" style=\"stop-color: #ffc6a0\" />\n                    <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_78_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_11_\"\n                    filterUnits=\"userSpaceOnUse\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask id=\"mask-135_1_\" maskUnits=\"userSpaceOnUse\">\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_11_)\">\n                    <polygon\n                      id=\"path-134_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_79_\"\n                  gradientTransform=\"matrix(6.3405 0 0 -10.9369 -3713.8577 14516.8223)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"586.1296\"\n                  x2=\"585.908\"\n                  y1=\"1326.7101\"\n                  y2=\"1326.2715\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #fb6583\" />\n                  <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_79_)\"\n                  mask=\"url(#mask-135_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g transform=\"translate(9.630049, 5.468450)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_80_\"\n                    gradientTransform=\"matrix(-6.3405 0 0 -10.9369 3455.1392 14453.3994)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"544.0497\"\n                    x2=\"543.6284\"\n                    y1=\"1319.3007\"\n                    y2=\"1321.1917\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_80_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_12_\"\n                    filterUnits=\"userSpaceOnUse\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask id=\"mask-137_1_\" maskUnits=\"userSpaceOnUse\">\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_12_)\">\n                    <polygon\n                      id=\"path-136_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_81_\"\n                  gradientTransform=\"matrix(6.3405 0 0 -10.9369 -3652.7986 14457.0146)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"576.3993\"\n                  x2=\"575.9966\"\n                  y1=\"1321.2085\"\n                  y2=\"1320.8575\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_81_)\"\n                  mask=\"url(#mask-137_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g transform=\"translate(19.201017, 11.077510)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_82_\"\n                    gradientTransform=\"matrix(-6.3405 0 0 -10.9369 3394.4546 14392.0537)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"534.4787\"\n                    x2=\"534.0574\"\n                    y1=\"1313.6917\"\n                    y2=\"1315.5826\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_82_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_13_\"\n                    filterUnits=\"userSpaceOnUse\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask id=\"mask-139_1_\" maskUnits=\"userSpaceOnUse\">\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_13_)\">\n                    <polygon\n                      id=\"path-138_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_83_\"\n                  gradientTransform=\"matrix(6.3405 0 0 -10.9369 -3592.114 14395.6689)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"566.8283\"\n                  x2=\"566.4257\"\n                  y1=\"1315.5994\"\n                  y2=\"1315.2484\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_83_)\"\n                  mask=\"url(#mask-139_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n            </g>\n            <g transform=\"translate(0.000000, 11.077510)\">\n              <g transform=\"translate(0.000000, -0.000000)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_84_\"\n                    gradientTransform=\"matrix(-6.3405 0 0 -10.9369 3516.198 14392.0537)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"553.8022\"\n                    x2=\"552.9454\"\n                    y1=\"1315.8689\"\n                    y2=\"1314.3909\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #fb6583\" />\n                    <stop offset=\"0.1302\" style=\"stop-color: #fc7688\" />\n                    <stop offset=\"0.5277\" style=\"stop-color: #fda797\" />\n                    <stop offset=\"0.8302\" style=\"stop-color: #ffc6a0\" />\n                    <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_84_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_14_\"\n                    filterUnits=\"userSpaceOnUse\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask id=\"mask-141_1_\" maskUnits=\"userSpaceOnUse\">\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_14_)\">\n                    <polygon\n                      id=\"path-140_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_85_\"\n                  gradientTransform=\"matrix(6.3405 0 0 -10.9369 -3713.8577 14395.6689)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"586.1296\"\n                  x2=\"585.908\"\n                  y1=\"1315.6326\"\n                  y2=\"1315.194\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #fb6583\" />\n                  <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_85_)\"\n                  mask=\"url(#mask-141_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g transform=\"translate(9.630049, 5.468450)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_86_\"\n                    gradientTransform=\"matrix(-6.3405 0 0 -10.9369 3455.1392 14332.2451)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"544.0497\"\n                    x2=\"543.6284\"\n                    y1=\"1308.2231\"\n                    y2=\"1310.1143\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_86_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_15_\"\n                    filterUnits=\"userSpaceOnUse\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask id=\"mask-143_1_\" maskUnits=\"userSpaceOnUse\">\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_15_)\">\n                    <polygon\n                      id=\"path-142_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_87_\"\n                  gradientTransform=\"matrix(6.3405 0 0 -10.9369 -3652.7986 14335.8613)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"576.3993\"\n                  x2=\"575.9966\"\n                  y1=\"1310.131\"\n                  y2=\"1309.78\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_87_)\"\n                  mask=\"url(#mask-143_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g transform=\"translate(19.201017, 11.077510)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_88_\"\n                    gradientTransform=\"matrix(-6.3405 0 0 -10.9369 3394.4546 14270.8994)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"534.4787\"\n                    x2=\"534.0574\"\n                    y1=\"1302.6141\"\n                    y2=\"1304.5051\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_88_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_16_\"\n                    filterUnits=\"userSpaceOnUse\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask id=\"mask-145_1_\" maskUnits=\"userSpaceOnUse\">\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_16_)\">\n                    <polygon\n                      id=\"path-144_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_89_\"\n                  gradientTransform=\"matrix(6.3405 0 0 -10.9369 -3592.114 14274.5156)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"566.8283\"\n                  x2=\"566.4257\"\n                  y1=\"1304.5219\"\n                  y2=\"1304.1709\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_89_)\"\n                  mask=\"url(#mask-145_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n            </g>\n            <g transform=\"translate(0.000000, 22.155020)\">\n              <g transform=\"translate(0.000000, -0.000000)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_90_\"\n                    gradientTransform=\"matrix(-6.3405 0 0 -10.9369 3516.198 14270.8994)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"553.8022\"\n                    x2=\"552.9454\"\n                    y1=\"1304.7914\"\n                    y2=\"1303.3134\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #fb6583\" />\n                    <stop offset=\"0.1302\" style=\"stop-color: #fc7688\" />\n                    <stop offset=\"0.5277\" style=\"stop-color: #fda797\" />\n                    <stop offset=\"0.8302\" style=\"stop-color: #ffc6a0\" />\n                    <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_90_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_17_\"\n                    filterUnits=\"userSpaceOnUse\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask id=\"mask-147_1_\" maskUnits=\"userSpaceOnUse\">\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_17_)\">\n                    <polygon\n                      id=\"path-146_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_91_\"\n                  gradientTransform=\"matrix(6.3405 0 0 -10.9369 -3713.8577 14274.5156)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"586.1296\"\n                  x2=\"585.908\"\n                  y1=\"1304.5552\"\n                  y2=\"1304.1165\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #fb6583\" />\n                  <stop offset=\"1\" style=\"stop-color: #ffd1a3\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_91_)\"\n                  mask=\"url(#mask-147_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g transform=\"translate(9.630049, 5.468450)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_92_\"\n                    gradientTransform=\"matrix(-6.3405 0 0 -10.9369 3455.1392 14211.0918)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"544.0497\"\n                    x2=\"543.6284\"\n                    y1=\"1297.1456\"\n                    y2=\"1299.0367\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_92_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_18_\"\n                    filterUnits=\"userSpaceOnUse\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask id=\"mask-149_1_\" maskUnits=\"userSpaceOnUse\">\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_18_)\">\n                    <polygon\n                      id=\"path-148_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_93_\"\n                  gradientTransform=\"matrix(6.3405 0 0 -10.9369 -3652.7986 14214.708)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"576.3993\"\n                  x2=\"575.9966\"\n                  y1=\"1299.0535\"\n                  y2=\"1298.7025\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_93_)\"\n                  mask=\"url(#mask-149_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n              <g transform=\"translate(19.201017, 11.077510)\">\n                <g>\n                  <linearGradient\n                    id=\"SVGID_94_\"\n                    gradientTransform=\"matrix(-6.3405 0 0 -10.9369 3394.4546 14149.7461)\"\n                    gradientUnits=\"userSpaceOnUse\"\n                    x1=\"534.4787\"\n                    x2=\"534.0574\"\n                    y1=\"1291.5366\"\n                    y2=\"1293.4276\"\n                  >\n                    <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n                    <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n                    <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n                    <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n                    <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n                    <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                  </linearGradient>\n                  <polygon\n                    fill=\"url(#SVGID_94_)\"\n                    points=\"0,7.3 6.3,10.9 6.3,3.7 0,0 \t\t\t\t\t\t\t\"\n                  />\n                </g>\n                <defs>\n                  <filter\n                    id=\"Adobe_OpacityMaskFilter_19_\"\n                    filterUnits=\"userSpaceOnUse\"\n                  >\n                    <feColorMatrix\n                      type=\"matrix\"\n                      values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                    />\n                  </filter>\n                </defs>\n                <mask id=\"mask-151_1_\" maskUnits=\"userSpaceOnUse\">\n                  <g filter=\"url(#Adobe_OpacityMaskFilter_19_)\">\n                    <polygon\n                      id=\"path-150_1_\"\n                      clip-rule=\"evenodd\"\n                      fill=\"#FFFFFF\"\n                      fill-rule=\"evenodd\"\n                      points=\"0,7.3 6.3,10.9 6.3,3.7 0,0\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"\n                    />\n                  </g>\n                </mask>\n\n                <linearGradient\n                  id=\"SVGID_95_\"\n                  gradientTransform=\"matrix(6.3405 0 0 -10.9369 -3592.114 14153.3623)\"\n                  gradientUnits=\"userSpaceOnUse\"\n                  x1=\"566.8283\"\n                  x2=\"566.4257\"\n                  y1=\"1293.4443\"\n                  y2=\"1293.0934\"\n                >\n                  <stop offset=\"0\" style=\"stop-color: #00c4ba\" />\n                  <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n                </linearGradient>\n                <polygon\n                  fill=\"url(#SVGID_95_)\"\n                  mask=\"url(#mask-151_1_)\"\n                  points=\"6.3,10.9 0,14.6 0,7.3 6.3,3.6 \t\t\t\t\t\t\"\n                />\n              </g>\n            </g>\n          </g>\n        </g>\n\n        <g\n          id=\"椭圆\"\n          transform=\"translate(83.218192, 86.783330) rotate(5.000000) translate(-83.218192, -86.783330) translate(7.218192, 10.783330)\"\n        >\n          <g id=\"编组-5\" transform=\"translate(8.161827, 36.521835)\">\n            <linearGradient\n              id=\"SVGID_96_\"\n              gradientTransform=\"matrix(124.0018 47.351 0.1479 -31.3606 42600.2344 62547.3867)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-344.7103\"\n              x2=\"-344.7103\"\n              y1=\"1473.182\"\n              y2=\"1472.182\"\n            >\n              <stop\n                offset=\"0\"\n                style=\"stop-color: #5cd7e8; stop-opacity: 0.1893\"\n              />\n              <stop offset=\"1\" style=\"stop-color: #fff\" />\n            </linearGradient>\n\n            <ellipse\n              cx=\"68\"\n              cy=\"38.7\"\n              fill=\"none\"\n              rx=\"14.6\"\n              ry=\"66.6\"\n              stroke=\"url(#SVGID_96_)\"\n              stroke-width=\"0.6\"\n              transform=\"matrix(-0.3746 0.9272 -0.9272 -0.3746 129.3914 -9.7912)\"\n            />\n\n            <ellipse\n              clip-rule=\"evenodd\"\n              cx=\"60.3\"\n              cy=\"51.5\"\n              fill=\"#FFFFFF\"\n              fill-rule=\"evenodd\"\n              rx=\"1.7\"\n              ry=\"1.7\"\n              transform=\"matrix(2.765398e-06 1 -1 2.765398e-06 111.8035 -8.8797)\"\n            />\n\n            <ellipse\n              id=\"椭圆形备份\"\n              clip-rule=\"evenodd\"\n              cx=\"86\"\n              cy=\"30.8\"\n              enable-background=\"new    \"\n              fill=\"#FFFFFF\"\n              fill-rule=\"evenodd\"\n              opacity=\"0.2662\"\n              rx=\"1.1\"\n              ry=\"1\"\n              transform=\"matrix(2.765396e-06 1 -1 2.765396e-06 116.7571 -55.2401)\"\n            />\n          </g>\n\n          <g\n            transform=\"translate(76.180160, 76.074544) rotate(-45.000000) translate(-76.180160, -76.074544) translate(8.680160, 37.074544)\"\n          >\n            <linearGradient\n              id=\"SVGID_97_\"\n              gradientTransform=\"matrix(87.4224 55.5937 87.656 11.2551 42330.7227 60598.5664)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"-1242.9404\"\n              x2=\"-1242.9404\"\n              y1=\"757.5463\"\n              y2=\"756.5463\"\n            >\n              <stop\n                offset=\"0\"\n                style=\"stop-color: #5cd7e8; stop-opacity: 0.1893\"\n              />\n              <stop offset=\"1\" style=\"stop-color: #fff\" />\n            </linearGradient>\n\n            <ellipse\n              cx=\"67.9\"\n              cy=\"38.7\"\n              fill=\"none\"\n              rx=\"66.5\"\n              ry=\"14.6\"\n              stroke=\"url(#SVGID_97_)\"\n              stroke-width=\"0.6\"\n              transform=\"matrix(-0.9272 -0.3746 0.3746 -0.9272 116.2823 100.0875)\"\n            />\n\n            <ellipse\n              clip-rule=\"evenodd\"\n              cx=\"31.9\"\n              cy=\"37\"\n              fill=\"#FFFFFF\"\n              fill-rule=\"evenodd\"\n              rx=\"1.7\"\n              ry=\"1.7\"\n              transform=\"matrix(-1 -5.213811e-07 5.213811e-07 -1 63.8725 74.0324)\"\n            />\n\n            <ellipse\n              clip-rule=\"evenodd\"\n              cx=\"85.8\"\n              cy=\"30.8\"\n              enable-background=\"new    \"\n              fill=\"#FFFFFF\"\n              fill-rule=\"evenodd\"\n              opacity=\"0.2662\"\n              rx=\"1\"\n              ry=\"1.1\"\n              transform=\"matrix(-1 2.765399e-06 -2.765399e-06 -1 171.6696 61.5172)\"\n            />\n          </g>\n        </g>\n      </g>\n\n      <g\n        id=\"编组备份-11\"\n        transform=\"translate(694.000000, 158.500000) scale(-1, 1) translate(-694.000000, -158.500000) translate(622.000000, 65.000000)\"\n      >\n        <linearGradient\n          id=\"SVGID_98_\"\n          gradientTransform=\"matrix(-142.0107 0 0 -186.2829 94193.1797 325470.9375)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"663.2509\"\n          x2=\"663.8784\"\n          y1=\"1745.6415\"\n          y2=\"1747.1095\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff\" />\n          <stop offset=\"1\" style=\"stop-color: #00c6fb\" />\n        </linearGradient>\n        <path\n          d=\"M134.9,0l7.1,4l-2.2,3.6l-3,1.2l-1.4,92c0,3-1.8,6.5-4,8l-0.2,0.1L10,178.8l0,0l-1.2,7.5l-7-3.9\n\t\t\tc-0.1-0.1-0.3-0.1-0.4-0.2l0,0c-0.8-0.6-1.3-1.7-1.3-3.3l0,0l1.4-97.1c0-3.1,1.9-6.7,4.2-8.1l0,0L132.6,0.5\n\t\t\tC133.5,0,134.3-0.1,134.9,0L134.9,0z\"\n          fill=\"url(#SVGID_98_)\"\n        />\n\n        <linearGradient\n          id=\"SVGID_99_\"\n          gradientTransform=\"matrix(-136.8189 0 0 -182.5389 90752.4922 318912.7812)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"662.8271\"\n          x2=\"662.9656\"\n          y1=\"1746.7982\"\n          y2=\"1746.3707\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #276cfc\" />\n          <stop offset=\"1\" style=\"stop-color: #84ffd1\" />\n        </linearGradient>\n        <path\n          clip-rule=\"evenodd\"\n          d=\"M11,185.8l126.9-73.2c2.3-1.4,4.2-5,4.2-8.1l1.4-97.1\n\t\t\tc0-3.1-1.9-4.6-4.2-3.2L12.4,77.4c-2.3,1.4-4.2,5-4.2,8.1l-1.4,97.1C6.8,185.7,8.7,187.1,11,185.8z\"\n          fill=\"url(#SVGID_99_)\"\n          fill-rule=\"evenodd\"\n        />\n        <g opacity=\"0.3\" transform=\"translate(95.618736, 40.248000)\">\n          <path\n            d=\"M33.5,8.7L3.1,26.3c-1,0.6-2.2,0.2-2.8-0.7\n\t\t\t\tc-0.2-0.3-0.3-0.7-0.3-1c0-2.2,1.2-4.2,3-5.2L33.5,1.7c1-0.6,2.2-0.2,2.8,0.7c0.2,0.3,0.3,0.7,0.3,1C36.6,5.7,35.4,7.7,33.5,8.7z\n\t\t\t\t\"\n            enable-background=\"new    \"\n            fill=\"#FFFFFF\"\n            opacity=\"0.3007\"\n          />\n          <path\n            d=\"M34,27.4L10.3,41c-0.8,0.4-1.7,0.2-2.1-0.6\n\t\t\t\tC8,40.2,8,40,8,39.7c0-1.7,0.9-3.2,2.4-4.1L34,22c0.8-0.4,1.7-0.2,2.1,0.6c0.1,0.2,0.2,0.5,0.2,0.8C36.3,25,35.4,26.5,34,27.4z\"\n            enable-background=\"new    \"\n            fill=\"#FFFFFF\"\n            opacity=\"0.3007\"\n          />\n          <path\n            d=\"M34,41.7L10.3,55.4c-0.8,0.4-1.7,0.2-2.1-0.6\n\t\t\t\tC8,54.6,8,54.3,8,54c0-1.7,0.9-3.2,2.4-4.1L34,36.3c0.8-0.4,1.7-0.2,2.1,0.6c0.1,0.2,0.2,0.5,0.2,0.8\n\t\t\t\tC36.3,39.3,35.4,40.9,34,41.7z\"\n            enable-background=\"new    \"\n            fill=\"#FFFFFF\"\n            opacity=\"0.3007\"\n          />\n          <path\n            d=\"M34,56L10.3,69.7c-0.8,0.4-1.7,0.2-2.1-0.6\n\t\t\t\tC8,68.9,8,68.6,8,68.3c0-1.7,0.9-3.2,2.4-4.1L34,50.6c0.8-0.4,1.7-0.2,2.1,0.6c0.1,0.2,0.2,0.5,0.2,0.8\n\t\t\t\tC36.3,53.6,35.4,55.2,34,56z\"\n            enable-background=\"new    \"\n            fill=\"#FFFFFF\"\n            opacity=\"0.3007\"\n          />\n        </g>\n\n        <linearGradient\n          id=\"SVGID_100_\"\n          gradientTransform=\"matrix(-82.1586 0 0 -155.5418 54504.3555 271598.7188)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"662.0853\"\n          x2=\"661.7936\"\n          y1=\"1746.2543\"\n          y2=\"1745.1642\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff\" />\n          <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n        </linearGradient>\n        <polygon\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_100_)\"\n          opacity=\"0.4\"\n          points=\"93,31 53,161.5 94.8,137.4 135.2,6 \t\t\"\n        />\n\n        <linearGradient\n          id=\"SVGID_101_\"\n          gradientTransform=\"matrix(-12.3655 0 0 -48.4174 8136.0479 83952.2812)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"655.1039\"\n          x2=\"654.9725\"\n          y1=\"1729.8617\"\n          y2=\"1731.0027\"\n        >\n          <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n          <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n          <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n          <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n          <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n          <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n        </linearGradient>\n\n        <linearGradient\n          id=\"SVGID_102_\"\n          gradientTransform=\"matrix(-12.3655 0 0 -48.4174 8136.0479 83952.2812)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"655.2512\"\n          x2=\"655.2512\"\n          y1=\"1731.6409\"\n          y2=\"1730.6409\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff\" />\n          <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n        </linearGradient>\n\n        <polygon\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_101_)\"\n          opacity=\"0.3007\"\n          points=\"\n\t\t\t39.7,152 27.4,159.1 27.4,117.9 39.7,110.7 \t\t\"\n          stroke=\"url(#SVGID_102_)\"\n          stroke-width=\"0.5\"\n        />\n\n        <g\n          transform=\"translate(72.980000, 101.800000) scale(-1, 1) translate(-72.980000, -101.800000) translate(13.480000, 28.800000)\"\n        >\n          <linearGradient\n            id=\"SVGID_103_\"\n            gradientTransform=\"matrix(116.289 0 0 -102.7515 61913.2812 176111.3281)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"-531.9647\"\n            x2=\"-531.5446\"\n            y1=\"1713.5308\"\n            y2=\"1713.0157\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #fff\" />\n            <stop offset=\"1\" style=\"stop-color: #fff\" />\n          </linearGradient>\n\n          <linearGradient\n            id=\"SVGID_104_\"\n            gradientTransform=\"matrix(116.289 0 0 -102.7515 61913.2812 176111.3281)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"-531.3857\"\n            x2=\"-532.1153\"\n            y1=\"1713.6992\"\n            y2=\"1713.3367\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #f8ff24\" />\n            <stop offset=\"1\" style=\"stop-color: #fff\" />\n          </linearGradient>\n          <path\n            clip-rule=\"evenodd\"\n            d=\"\n\t\t\t\tM117.6,103.3c-6-3.4-9-14.2-11.9-24.6c-2.9-10.2-5.7-20.7-11.4-24c-5.7-3.3-8.6,3.9-11.4,10.8c-2.9,7.1-5.9,14.4-11.9,10.9\n\t\t\t\tc-6-3.5-9-14.2-11.8-24.6c-2.9-10.2-5.8-20.7-11.4-24s-8.6,3.9-11.4,10.8c-2.9,7.1-5.9,14.4-11.9,10.9c-6-3.4-9-14.2-11.8-24.6\n\t\t\t\tC9.9,14.9,7,4.3,1.3,1.1V0.5c6,3.4,9,14.2,11.9,24.6c2.8,10.2,5.7,20.7,11.4,24c5.7,3.3,8.6-3.9,11.4-10.8\n\t\t\t\tc2.9-7.1,5.9-14.4,11.8-10.9s9,14.2,11.9,24.6c2.9,10.2,5.7,20.7,11.4,24c5.7,3.2,8.6-3.9,11.4-10.8c2.9-7.1,5.9-14.4,11.8-10.9\n\t\t\t\tc6,3.5,9,14.2,11.9,24.6c2.9,10.2,5.8,20.7,11.4,24V103.3z\"\n            fill=\"url(#SVGID_103_)\"\n            fill-rule=\"evenodd\"\n            stroke=\"url(#SVGID_104_)\"\n            stroke-width=\"1.4256\"\n          />\n\n          <linearGradient\n            id=\"路径-2_1_\"\n            gradientTransform=\"matrix(117.4617 0 0 -144.8008 62535.8438 248597.6562)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"-531.8884\"\n            x2=\"-531.8884\"\n            y1=\"1716.8223\"\n            y2=\"1715.8223\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #fff\" />\n            <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n          </linearGradient>\n          <path\n            id=\"路径-2\"\n            clip-rule=\"evenodd\"\n            d=\"M0.6,0.5v77.2l117,67.6l0.5-42\n\t\t\t\tc-3.3-3.2-5.5-5.6-6.5-7.3c-4.4-7.6-5.6-14.4-6.1-18.2c-1.1-7.7-9.1-26.4-15.2-23.3c-6.1,3.1-10.8,27.4-17.9,23.3\n\t\t\t\tc-7-4-15.5-32-17.3-39.4S46,22.8,41.4,29.7c-1,1.5-4.9,9.1-6,11.5c-7.7,15.9-13.7,7.5-19.1-5C14.1,31.1,9.8,15.4,6.2,6.8\n\t\t\t\tC5,3.9,3.2,1.8,0.6,0.5z\"\n            fill=\"url(#路径-2_1_)\"\n            fill-rule=\"evenodd\"\n          />\n          <g id=\"编组-7\" transform=\"translate(41.723113, 19.522022)\">\n            <path\n              id=\"Fill-11\"\n              clip-rule=\"evenodd\"\n              d=\"\n\t\t\t\t\tM5.4,0.8c3,1.9,5.4,6.4,5.4,10.1c0,3.7-2.4,5.2-5.4,3.4C2.4,12.4,0,7.9,0,4.2C0,0.4,2.4-1.1,5.4,0.8\"\n              enable-background=\"new    \"\n              fill=\"#AA69FF\"\n              fill-rule=\"evenodd\"\n              opacity=\"0.6\"\n            />\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M9.7,10.7\n\t\t\t\t\tc0,3.2-2,4.5-4.3,2.9C3,12,1.1,8.1,1.1,4.9c0-3.2,2-4.5,4.3-2.9S9.7,7.5,9.7,10.7z\"\n              enable-background=\"new    \"\n              fill=\"#7005FF\"\n              fill-rule=\"evenodd\"\n              opacity=\"0.6\"\n            />\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M7.4,9c0,1.6-1,2.2-2.2,1.4C4.1,9.6,3.1,7.6,3.1,6\n\t\t\t\t\tc0-1.6,1-2.2,2.2-1.4S7.4,7.4,7.4,9z\"\n              fill=\"#E7C769\"\n              fill-rule=\"evenodd\"\n            />\n          </g>\n        </g>\n\n        <linearGradient\n          id=\"SVGID_105_\"\n          gradientTransform=\"matrix(-12.3655 0 0 -61.3183 8158.1587 106537.3281)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"655.2088\"\n          x2=\"655.1892\"\n          y1=\"1734.5078\"\n          y2=\"1734.9935\"\n        >\n          <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n          <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n          <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n          <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n          <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n          <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n        </linearGradient>\n\n        <linearGradient\n          id=\"SVGID_106_\"\n          gradientTransform=\"matrix(-12.3655 0 0 -61.3183 8158.1587 106537.3281)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"655.2512\"\n          x2=\"655.2512\"\n          y1=\"1736.0601\"\n          y2=\"1735.0601\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff\" />\n          <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n        </linearGradient>\n\n        <polygon\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_105_)\"\n          opacity=\"0.3007\"\n          points=\"\n\t\t\t61.8,139.2 49.5,146.4 49.5,92.2 61.8,85.1 \t\t\"\n          stroke=\"url(#SVGID_106_)\"\n          stroke-width=\"0.5\"\n        />\n\n        <linearGradient\n          id=\"SVGID_107_\"\n          gradientTransform=\"matrix(-12.3655 0 0 -74.2182 8181.0142 129120.2578)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"655.1729\"\n          x2=\"655.117\"\n          y1=\"1737.4004\"\n          y2=\"1738.1447\"\n        >\n          <stop offset=\"5.400000e-03\" style=\"stop-color: #130cb5\" />\n          <stop offset=\"0.2823\" style=\"stop-color: #285ecb\" />\n          <stop offset=\"0.538\" style=\"stop-color: #3aa3dd\" />\n          <stop offset=\"0.75\" style=\"stop-color: #47d5eb\" />\n          <stop offset=\"0.9102\" style=\"stop-color: #4ff4f3\" />\n          <stop offset=\"1\" style=\"stop-color: #52fff6\" />\n        </linearGradient>\n\n        <linearGradient\n          id=\"SVGID_108_\"\n          gradientTransform=\"matrix(-12.3655 0 0 -74.2182 8181.0142 129120.2578)\"\n          gradientUnits=\"userSpaceOnUse\"\n          x1=\"655.2512\"\n          x2=\"655.2512\"\n          y1=\"1738.9429\"\n          y2=\"1737.9429\"\n        >\n          <stop offset=\"0\" style=\"stop-color: #fff\" />\n          <stop offset=\"1\" style=\"stop-color: #fff; stop-opacity: 0\" />\n        </linearGradient>\n\n        <polygon\n          enable-background=\"new    \"\n          fill=\"url(#SVGID_107_)\"\n          opacity=\"0.3007\"\n          points=\"\n\t\t\t84.7,126 72.3,133.2 72.3,66.1 84.7,59 \t\t\"\n          stroke=\"url(#SVGID_108_)\"\n          stroke-width=\"0.5\"\n        />\n      </g>\n      <g id=\"编组-16\" transform=\"translate(51.000000, 152.000000)\">\n        <g id=\"云\">\n          <g transform=\"translate(0.000000, 0.000000)\">\n            <g>\n              <linearGradient\n                id=\"SVGID_109_\"\n                gradientTransform=\"matrix(100.2748 0 0 -95.0757 -5012.0137 157345.375)\"\n                gradientUnits=\"userSpaceOnUse\"\n                x1=\"49.8504\"\n                x2=\"50.8986\"\n                y1=\"1654.3923\"\n                y2=\"1654.5082\"\n              >\n                <stop offset=\"0\" style=\"stop-color: #544cde\" />\n                <stop offset=\"0.5868\" style=\"stop-color: #a7a0ff\" />\n                <stop offset=\"1\" style=\"stop-color: #7b74ff\" />\n              </linearGradient>\n              <path\n                clip-rule=\"evenodd\"\n                d=\"M70.3,14.7c0,0.3,0,0.5,0,0.7\n\t\t\t\t\t\tc1.5-1.4,3.2-2.6,4.8-3.5c5.6-3.2,10.6-3.3,14-0.8l11.1,6.6l-7.1,11c-2.1,10-8.9,20.5-17.2,25.7L23.9,84.6L18,95.1L6.2,88l0,0\n\t\t\t\t\t\tC2.4,85.8,0,81.2,0,74.5c0-13.1,9.2-29.2,20.6-35.8c3-1.7,5.9-2.7,8.4-2.8c1.1-12.6,9.9-26.9,20.5-33\n\t\t\t\t\t\tC61-3.8,70.3,1.6,70.3,14.7z\"\n                fill=\"url(#SVGID_109_)\"\n                fill-rule=\"evenodd\"\n              />\n            </g>\n            <defs>\n              <filter\n                id=\"Adobe_OpacityMaskFilter_20_\"\n                filterUnits=\"userSpaceOnUse\"\n                height=\"67.1\"\n                width=\"76.3\"\n                x=\"58.3\"\n                y=\"-13.8\"\n              >\n                <feColorMatrix\n                  type=\"matrix\"\n                  values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                />\n              </filter>\n            </defs>\n            <mask\n              id=\"mask-168_1_\"\n              height=\"67.1\"\n              maskUnits=\"userSpaceOnUse\"\n              width=\"76.3\"\n              x=\"58.3\"\n              y=\"-13.8\"\n            >\n              <g filter=\"url(#Adobe_OpacityMaskFilter_20_)\">\n                <path\n                  id=\"path-167_1_\"\n                  clip-rule=\"evenodd\"\n                  d=\"M70.3,14.7c0,0.3,0,0.5,0,0.7\n\t\t\t\t\t\t\tc1.5-1.4,3.2-2.6,4.8-3.5c5.6-3.2,10.6-3.3,14-0.8l11.1,6.6l-7.1,11c-2.1,10-8.9,20.5-17.2,25.7L23.9,84.6L18,95.1L6.2,88l0,0\n\t\t\t\t\t\t\tC2.4,85.8,0,81.2,0,74.5c0-13.1,9.2-29.2,20.6-35.8c3-1.7,5.9-2.7,8.4-2.8c1.1-12.6,9.9-26.9,20.5-33\n\t\t\t\t\t\t\tC61-3.8,70.3,1.6,70.3,14.7z\"\n                  fill=\"#FFFFFF\"\n                  fill-rule=\"evenodd\"\n                />\n              </g>\n            </mask>\n\n            <linearGradient\n              id=\"矩形备份-3_1_\"\n              gradientTransform=\"matrix(76.2914 0 0 -67.1042 -3730.5828 110741.1094)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"49.8816\"\n              x2=\"50.4973\"\n              y1=\"1649.8344\"\n              y2=\"1650.4406\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #4f3eff\" />\n              <stop offset=\"0.4096\" style=\"stop-color: #9c92ff\" />\n              <stop offset=\"0.7979\" style=\"stop-color: #2519da\" />\n              <stop offset=\"1\" style=\"stop-color: #0d04ab\" />\n            </linearGradient>\n\n            <path\n              id=\"矩形备份-3\"\n              clip-rule=\"evenodd\"\n              d=\"\n\t\t\t\t\tM100.1-13.4l29.3,15.6l5.2,15.3C112,43,96.4,54.9,87.8,53.2S69.3,34.5,58.3,2.2c10.3-7,18.4-11.6,24.2-13.7\n\t\t\t\t\tS94.1-14.3,100.1-13.4z\"\n              fill=\"url(#矩形备份-3_1_)\"\n              fill-rule=\"evenodd\"\n              mask=\"url(#mask-168_1_)\"\n            />\n            <defs>\n              <filter\n                id=\"Adobe_OpacityMaskFilter_21_\"\n                filterUnits=\"userSpaceOnUse\"\n                height=\"67.1\"\n                width=\"76.3\"\n                x=\"0\"\n                y=\"-13.8\"\n              >\n                <feColorMatrix\n                  type=\"matrix\"\n                  values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                />\n              </filter>\n            </defs>\n            <mask\n              id=\"mask-168_2_\"\n              height=\"67.1\"\n              maskUnits=\"userSpaceOnUse\"\n              width=\"76.3\"\n              x=\"0\"\n              y=\"-13.8\"\n            >\n              <g filter=\"url(#Adobe_OpacityMaskFilter_21_)\">\n                <path\n                  id=\"path-167_2_\"\n                  clip-rule=\"evenodd\"\n                  d=\"M70.3,14.7c0,0.3,0,0.5,0,0.7\n\t\t\t\t\t\t\tc1.5-1.4,3.2-2.6,4.8-3.5c5.6-3.2,10.6-3.3,14-0.8l11.1,6.6l-7.1,11c-2.1,10-8.9,20.5-17.2,25.7L23.9,84.6L18,95.1L6.2,88l0,0\n\t\t\t\t\t\t\tC2.4,85.8,0,81.2,0,74.5c0-13.1,9.2-29.2,20.6-35.8c3-1.7,5.9-2.7,8.4-2.8c1.1-12.6,9.9-26.9,20.5-33\n\t\t\t\t\t\t\tC61-3.8,70.3,1.6,70.3,14.7z\"\n                  fill=\"#FFFFFF\"\n                  fill-rule=\"evenodd\"\n                />\n              </g>\n            </mask>\n\n            <linearGradient\n              id=\"矩形备份-2_1_\"\n              gradientTransform=\"matrix(76.2914 0 0 -67.1042 -3788.8608 110741.1094)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"49.8816\"\n              x2=\"50.4973\"\n              y1=\"1649.8344\"\n              y2=\"1650.4406\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #4f3eff\" />\n              <stop offset=\"0.4096\" style=\"stop-color: #9c92ff\" />\n              <stop offset=\"0.7979\" style=\"stop-color: #2519da\" />\n              <stop offset=\"1\" style=\"stop-color: #0d04ab\" />\n            </linearGradient>\n\n            <path\n              id=\"矩形备份-2\"\n              clip-rule=\"evenodd\"\n              d=\"\n\t\t\t\t\tM41.8-13.4L71.1,2.2l5.2,15.3C53.7,43,38.1,54.9,29.5,53.2S11,34.5,0,2.2c10.3-7,18.4-11.6,24.2-13.7S35.8-14.3,41.8-13.4z\"\n              fill=\"url(#矩形备份-2_1_)\"\n              fill-rule=\"evenodd\"\n              mask=\"url(#mask-168_2_)\"\n            />\n            <defs>\n              <filter\n                id=\"Adobe_OpacityMaskFilter_22_\"\n                filterUnits=\"userSpaceOnUse\"\n                height=\"67.1\"\n                width=\"76.3\"\n                x=\"-12.7\"\n                y=\"35.5\"\n              >\n                <feColorMatrix\n                  type=\"matrix\"\n                  values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n                />\n              </filter>\n            </defs>\n            <mask\n              id=\"mask-168_3_\"\n              height=\"67.1\"\n              maskUnits=\"userSpaceOnUse\"\n              width=\"76.3\"\n              x=\"-12.7\"\n              y=\"35.5\"\n            >\n              <g filter=\"url(#Adobe_OpacityMaskFilter_22_)\">\n                <path\n                  id=\"path-167_3_\"\n                  clip-rule=\"evenodd\"\n                  d=\"M70.3,14.7c0,0.3,0,0.5,0,0.7\n\t\t\t\t\t\t\tc1.5-1.4,3.2-2.6,4.8-3.5c5.6-3.2,10.6-3.3,14-0.8l11.1,6.6l-7.1,11c-2.1,10-8.9,20.5-17.2,25.7L23.9,84.6L18,95.1L6.2,88l0,0\n\t\t\t\t\t\t\tC2.4,85.8,0,81.2,0,74.5c0-13.1,9.2-29.2,20.6-35.8c3-1.7,5.9-2.7,8.4-2.8c1.1-12.6,9.9-26.9,20.5-33\n\t\t\t\t\t\t\tC61-3.8,70.3,1.6,70.3,14.7z\"\n                  fill=\"#FFFFFF\"\n                  fill-rule=\"evenodd\"\n                />\n              </g>\n            </mask>\n\n            <linearGradient\n              id=\"SVGID_110_\"\n              gradientTransform=\"matrix(76.2914 0 0 -67.1042 -3801.5762 110790.3594)\"\n              gradientUnits=\"userSpaceOnUse\"\n              x1=\"49.8816\"\n              x2=\"50.4973\"\n              y1=\"1649.8344\"\n              y2=\"1650.4406\"\n            >\n              <stop offset=\"0\" style=\"stop-color: #4f3eff\" />\n              <stop offset=\"0.4096\" style=\"stop-color: #9c92ff\" />\n              <stop offset=\"0.7979\" style=\"stop-color: #2519da\" />\n              <stop offset=\"1\" style=\"stop-color: #0d04ab\" />\n            </linearGradient>\n            <path\n              clip-rule=\"evenodd\"\n              d=\"M29.1,35.9l29.3,15.6\n\t\t\t\t\tl5.2,15.3C41,92.3,25.4,104.1,16.8,102.4c-8.7-1.7-18.5-18.7-29.5-50.9c10.3-7,18.4-11.6,24.2-13.7S23.1,35,29.1,35.9z\"\n              fill=\"url(#SVGID_110_)\"\n              fill-rule=\"evenodd\"\n              mask=\"url(#mask-168_3_)\"\n            />\n          </g>\n\n          <linearGradient\n            id=\"Fill-1备份_1_\"\n            gradientTransform=\"matrix(93.8386 0 0 -89.2609 -4672.3262 147666.9375)\"\n            gradientUnits=\"userSpaceOnUse\"\n            x1=\"50.6106\"\n            x2=\"50.1002\"\n            y1=\"1654.2188\"\n            y2=\"1652.9083\"\n          >\n            <stop offset=\"0\" style=\"stop-color: #5e51e4\" />\n            <stop offset=\"1\" style=\"stop-color: #060071\" />\n          </linearGradient>\n          <path\n            id=\"Fill-1备份\"\n            clip-rule=\"evenodd\"\n            d=\"M86.6,18.8\n\t\t\t\tc10.3-6,18.6-1.1,18.7,10.7c0,11.6-7.9,25.6-17.9,31.8l-55.2,32l-0.4,0.2c-11.3,6.3-20.3,0.9-20.4-12.1\n\t\t\t\tc0-13.1,9.2-29.2,20.6-35.8c3-1.7,5.9-2.7,8.4-2.8c1.1-12.6,9.9-26.9,20.5-33c11.4-6.6,20.7-1.3,20.8,11.9c0,0.3,0,0.5,0,0.7\n\t\t\t\tC83.3,21,84.9,19.7,86.6,18.8\"\n            fill=\"url(#Fill-1备份_1_)\"\n            fill-rule=\"evenodd\"\n          />\n        </g>\n      </g>\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/authentication/index.ts",
    "content": "export { default as AuthPageLayout } from './authentication.vue';\nexport * from './types';\n"
  },
  {
    "path": "packages/effects/layouts/src/authentication/toolbar.vue",
    "content": "<script setup lang=\"ts\">\nimport type { ToolbarType } from './types';\n\nimport { computed } from 'vue';\n\nimport { preferences } from '@vben/preferences';\n\nimport {\n  AuthenticationColorToggle,\n  AuthenticationLayoutToggle,\n  LanguageToggle,\n  ThemeToggle,\n} from '../widgets';\n\ninterface Props {\n  toolbarList?: ToolbarType[];\n}\n\ndefineOptions({\n  name: 'AuthenticationToolbar',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  toolbarList: () => ['color', 'language', 'layout', 'theme'],\n});\n\nconst showColor = computed(() => props.toolbarList.includes('color'));\nconst showLayout = computed(() => props.toolbarList.includes('layout'));\nconst showLanguage = computed(() => props.toolbarList.includes('language'));\nconst showTheme = computed(() => props.toolbarList.includes('theme'));\n</script>\n\n<template>\n  <div\n    :class=\"{\n      'rounded-3xl bg-accent px-3 py-1': toolbarList.length > 1,\n    }\"\n    class=\"absolute top-4 right-2 z-10 flex-center\"\n  >\n    <!-- Only show on medium and larger screens -->\n    <div class=\"hidden md:flex\">\n      <AuthenticationColorToggle v-if=\"showColor\" />\n      <AuthenticationLayoutToggle v-if=\"showLayout\" />\n    </div>\n    <!-- Always show Language and Theme toggles -->\n    <LanguageToggle v-if=\"showLanguage && preferences.widget.languageToggle\" />\n    <ThemeToggle v-if=\"showTheme && preferences.widget.themeToggle\" />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/authentication/types.ts",
    "content": "export type ToolbarType = 'color' | 'language' | 'layout' | 'theme';\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/README.md",
    "content": "## layout\n\n### header\n\n- 支持N个自定义插槽，命名方式：header-right-n，header-left-n\n- header-left-n ，排序方式：0-19 ,breadcrumb 21-x\n- header-right-n ，排序方式：0-49，global-search，51-59，theme-toggle，61-69，language-toggle，71-79，fullscreen，81-89，notification，91-149，user-dropdown，151-x\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/content/content-spinner.vue",
    "content": "<script lang=\"ts\" setup>\nimport { VbenSpinner } from '@vben-core/shadcn-ui';\n\nimport { useContentSpinner } from './use-content-spinner';\n\ndefineOptions({ name: 'LayoutContentSpinner' });\n\nconst { spinning } = useContentSpinner();\n</script>\n<template>\n  <VbenSpinner :spinning=\"spinning\" />\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/content/content.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';\n\nimport { unref } from 'vue';\nimport { RouterView } from 'vue-router';\n\nimport { usePreferences } from '@vben/preferences';\nimport { getTabKey, storeToRefs, useTabbarStore } from '@vben/stores';\n\nimport { transformComponent, useLayoutHook } from '../../hooks';\nimport { IFrameRouterView } from '../../iframe';\nimport { RouteCachedPage, RouteCachedView } from '../../route-cached';\n\ndefineOptions({ name: 'LayoutContent' });\n\nconst tabbarStore = useTabbarStore();\nconst { keepAlive } = usePreferences();\n\nconst { getCachedTabs, getExcludeCachedTabs, renderRouteView } =\n  storeToRefs(tabbarStore);\n\nconst { getEnabledTransition, getTransitionName } = useLayoutHook();\n\n/**\n * 是否显示component\n * @param route\n */\nconst showComponent = (route: RouteLocationNormalizedLoadedGeneric) => {\n  return !route.meta.domCached && unref(renderRouteView);\n};\n</script>\n\n<template>\n  <div class=\"relative h-full\">\n    <IFrameRouterView />\n    <RouteCachedView />\n    <RouterView v-slot=\"{ Component, route }\">\n      <RouteCachedPage\n        :component=\"Component\"\n        :route=\"route\"\n        v-if=\"route.meta.domCached\"\n      />\n      <Transition\n        v-if=\"getEnabledTransition\"\n        :name=\"getTransitionName(route)\"\n        appear\n        mode=\"out-in\"\n      >\n        <KeepAlive\n          v-if=\"keepAlive\"\n          :exclude=\"getExcludeCachedTabs\"\n          :include=\"getCachedTabs\"\n        >\n          <component\n            :is=\"transformComponent(Component, route)\"\n            v-if=\"showComponent(route)\"\n            v-show=\"!route.meta.iframeSrc\"\n            :key=\"getTabKey(route)\"\n          />\n        </KeepAlive>\n        <component\n          :is=\"Component\"\n          v-else-if=\"showComponent(route)\"\n          :key=\"getTabKey(route)\"\n        />\n      </Transition>\n      <template v-else>\n        <KeepAlive\n          v-if=\"keepAlive\"\n          :exclude=\"getExcludeCachedTabs\"\n          :include=\"getCachedTabs\"\n        >\n          <component\n            :is=\"transformComponent(Component, route)\"\n            v-if=\"showComponent(route)\"\n            v-show=\"!route.meta.iframeSrc\"\n            :key=\"getTabKey(route)\"\n          />\n        </KeepAlive>\n        <component\n          :is=\"Component\"\n          v-else-if=\"showComponent(route)\"\n          :key=\"getTabKey(route)\"\n        />\n      </template>\n    </RouterView>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/content/index.ts",
    "content": "export { default as LayoutContentSpinner } from './content-spinner.vue';\nexport { default as LayoutContent } from './content.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/content/use-content-spinner.ts",
    "content": "import { computed, ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { preferences } from '@vben/preferences';\n\nfunction useContentSpinner() {\n  const spinning = ref(false);\n  const startTime = ref(0);\n  const router = useRouter();\n  const minShowTime = 500; // 最小显示时间\n  const enableLoading = computed(() => preferences.transition.loading);\n\n  // 结束加载动画\n  const onEnd = () => {\n    if (!enableLoading.value) {\n      return;\n    }\n    const processTime = performance.now() - startTime.value;\n    if (processTime < minShowTime) {\n      setTimeout(() => {\n        spinning.value = false;\n      }, minShowTime - processTime);\n    } else {\n      spinning.value = false;\n    }\n  };\n\n  // 路由前置守卫\n  router.beforeEach((to) => {\n    if (to.meta.loaded || !enableLoading.value || to.meta.iframeSrc) {\n      return true;\n    }\n    startTime.value = performance.now();\n    spinning.value = true;\n    return true;\n  });\n\n  // 路由后置守卫\n  router.afterEach((to) => {\n    if (to.meta.loaded || !enableLoading.value || to.meta.iframeSrc) {\n      return true;\n    }\n    onEnd();\n    return true;\n  });\n\n  return { spinning };\n}\n\nexport { useContentSpinner };\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/copyright/copyright.vue",
    "content": "<script lang=\"ts\" setup>\ninterface Props {\n  companyName?: string;\n  companySiteLink?: string;\n  date?: string;\n  icp?: string;\n  icpLink?: string;\n}\n\ndefineOptions({\n  name: 'Copyright',\n});\n\nwithDefaults(defineProps<Props>(), {\n  companyName: 'Vben Admin',\n  companySiteLink: '',\n  date: '2024',\n  icp: '',\n  icpLink: '',\n});\n</script>\n\n<template>\n  <div class=\"text-md flex-center\">\n    <!-- ICP Link -->\n    <a\n      v-if=\"icp\"\n      :href=\"icpLink || 'javascript:void(0)'\"\n      class=\"mx-1 hover:text-primary-hover\"\n      target=\"_blank\"\n    >\n      {{ icp }}\n    </a>\n\n    <!-- Copyright Text -->\n    Copyright © {{ date }}\n\n    <!-- Company Link -->\n    <a\n      v-if=\"companyName\"\n      :href=\"companySiteLink || 'javascript:void(0)'\"\n      class=\"mx-1 hover:text-primary-hover\"\n      target=\"_blank\"\n    >\n      {{ companyName }}\n    </a>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/copyright/index.ts",
    "content": "export { default as Copyright } from './copyright.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/footer/footer.vue",
    "content": "<script lang=\"ts\" setup>\ndefineOptions({\n  name: 'LayoutFooter',\n});\n</script>\n\n<template>\n  <div class=\"relative flex-center size-full text-xs text-muted-foreground\">\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/footer/index.ts",
    "content": "export { default as LayoutFooter } from './footer.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/header/header.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed, useSlots } from 'vue';\n\nimport { useRefresh } from '@vben/hooks';\nimport { RotateCw } from '@vben/icons';\nimport { preferences, usePreferences } from '@vben/preferences';\nimport { useAccessStore } from '@vben/stores';\n\nimport { VbenFullScreen, VbenIconButton } from '@vben-core/shadcn-ui';\n\nimport {\n  GlobalSearch,\n  LanguageToggle,\n  PreferencesButton,\n  ThemeToggle,\n  TimezoneButton,\n} from '../../widgets';\n\ninterface Props {\n  /**\n   * Logo 主题\n   */\n  theme?: string;\n}\n\ndefineOptions({\n  name: 'LayoutHeader',\n});\n\nwithDefaults(defineProps<Props>(), {\n  theme: 'light',\n});\n\nconst emit = defineEmits<{ clearPreferencesAndLogout: [] }>();\n\nconst REFERENCE_VALUE = 50;\n\nconst accessStore = useAccessStore();\nconst { globalSearchShortcutKey, preferencesButtonPosition } = usePreferences();\nconst slots = useSlots();\nconst { refresh } = useRefresh();\n\nconst rightSlots = computed(() => {\n  const list = [{ index: REFERENCE_VALUE + 100, name: 'user-dropdown' }];\n  if (preferences.widget.globalSearch) {\n    list.push({\n      index: REFERENCE_VALUE,\n      name: 'global-search',\n    });\n  }\n\n  if (preferencesButtonPosition.value.header) {\n    list.push({\n      index: REFERENCE_VALUE + 10,\n      name: 'preferences',\n    });\n  }\n  if (preferences.widget.themeToggle) {\n    list.push({\n      index: REFERENCE_VALUE + 20,\n      name: 'theme-toggle',\n    });\n  }\n  if (preferences.widget.languageToggle) {\n    list.push({\n      index: REFERENCE_VALUE + 30,\n      name: 'language-toggle',\n    });\n  }\n  if (preferences.widget.timezone) {\n    list.push({\n      index: REFERENCE_VALUE + 40,\n      name: 'timezone',\n    });\n  }\n  if (preferences.widget.fullscreen) {\n    list.push({\n      index: REFERENCE_VALUE + 50,\n      name: 'fullscreen',\n    });\n  }\n  if (preferences.widget.notification) {\n    list.push({\n      index: REFERENCE_VALUE + 60,\n      name: 'notification',\n    });\n  }\n\n  Object.keys(slots).forEach((key) => {\n    const name = key.split('-');\n    if (key.startsWith('header-right')) {\n      list.push({ index: Number(name[2]), name: key });\n    }\n  });\n  return list.toSorted((a, b) => a.index - b.index);\n});\n\nconst leftSlots = computed(() => {\n  const list: Array<{ index: number; name: string }> = [];\n\n  if (preferences.widget.refresh) {\n    list.push({\n      index: 0,\n      name: 'refresh',\n    });\n  }\n\n  Object.keys(slots).forEach((key) => {\n    const name = key.split('-');\n    if (key.startsWith('header-left')) {\n      list.push({ index: Number(name[2]), name: key });\n    }\n  });\n  return list.toSorted((a, b) => a.index - b.index);\n});\n\nfunction clearPreferencesAndLogout() {\n  emit('clearPreferencesAndLogout');\n}\n</script>\n\n<template>\n  <template\n    v-for=\"slot in leftSlots.filter((item) => item.index < REFERENCE_VALUE)\"\n    :key=\"slot.name\"\n  >\n    <slot :name=\"slot.name\">\n      <template v-if=\"slot.name === 'refresh'\">\n        <VbenIconButton class=\"my-0 mr-1 rounded-md\" @click=\"refresh\">\n          <RotateCw class=\"size-4\" />\n        </VbenIconButton>\n      </template>\n    </slot>\n  </template>\n  <div class=\"flex-center hidden lg:block\">\n    <slot name=\"breadcrumb\"></slot>\n  </div>\n  <template\n    v-for=\"slot in leftSlots.filter((item) => item.index > REFERENCE_VALUE)\"\n    :key=\"slot.name\"\n  >\n    <slot :name=\"slot.name\"></slot>\n  </template>\n  <div\n    :class=\"`menu-align-${preferences.header.menuAlign}`\"\n    class=\"flex h-full min-w-0 flex-1 items-center\"\n  >\n    <slot name=\"menu\"></slot>\n  </div>\n  <div class=\"flex h-full min-w-0 shrink-0 items-center\">\n    <template v-for=\"slot in rightSlots\" :key=\"slot.name\">\n      <slot :name=\"slot.name\">\n        <template v-if=\"slot.name === 'global-search'\">\n          <GlobalSearch\n            :enable-shortcut-key=\"globalSearchShortcutKey\"\n            :menus=\"accessStore.accessMenus\"\n            class=\"mr-1 sm:mr-4\"\n          />\n        </template>\n\n        <template v-else-if=\"slot.name === 'preferences'\">\n          <PreferencesButton\n            class=\"mr-1\"\n            @clear-preferences-and-logout=\"clearPreferencesAndLogout\"\n          />\n        </template>\n        <template v-else-if=\"slot.name === 'theme-toggle'\">\n          <ThemeToggle class=\"mt-0.5 mr-1\" />\n        </template>\n        <template v-else-if=\"slot.name === 'language-toggle'\">\n          <LanguageToggle class=\"mr-1\" />\n        </template>\n        <template v-else-if=\"slot.name === 'fullscreen'\">\n          <VbenFullScreen class=\"mr-1\" />\n        </template>\n        <template v-else-if=\"slot.name === 'timezone'\">\n          <TimezoneButton class=\"mt-0.5 mr-1\" />\n        </template>\n      </slot>\n    </template>\n  </div>\n</template>\n<style lang=\"scss\" scoped>\n.menu-align-start {\n  --menu-align: start;\n}\n\n.menu-align-center {\n  --menu-align: center;\n}\n\n.menu-align-end {\n  --menu-align: end;\n}\n</style>\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/header/index.ts",
    "content": "export { default as LayoutHeader } from './header.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/index.ts",
    "content": "export { default as BasicLayout } from './layout.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/layout.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { SetupContext } from 'vue';\nimport type { RouteLocationNormalizedLoaded } from 'vue-router';\n\nimport type { MenuRecordRaw } from '@vben/types';\n\nimport { computed, onMounted, useSlots, watch } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport { useRefresh } from '@vben/hooks';\nimport { $t, i18n } from '@vben/locales';\nimport {\n  preferences,\n  updatePreferences,\n  usePreferences,\n} from '@vben/preferences';\nimport { useAccessStore, useTabbarStore, useTimezoneStore } from '@vben/stores';\nimport { cloneDeep, mapTree } from '@vben/utils';\n\nimport { VbenAdminLayout } from '@vben-core/layout-ui';\nimport { VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';\n\nimport { Breadcrumb, CheckUpdates, Preferences } from '../widgets';\nimport { LayoutContent, LayoutContentSpinner } from './content';\nimport { Copyright } from './copyright';\nimport { LayoutFooter } from './footer';\nimport { LayoutHeader } from './header';\nimport {\n  LayoutExtraMenu,\n  LayoutMenu,\n  LayoutMixedMenu,\n  useExtraMenu,\n  useMixedMenu,\n} from './menu';\nimport { LayoutTabbar } from './tabbar';\n\ndefineOptions({ name: 'BasicLayout' });\n\nconst emit = defineEmits<{ clearPreferencesAndLogout: []; clickLogo: [] }>();\n\nconst {\n  isDark,\n  isHeaderNav,\n  isMixedNav,\n  isMobile,\n  isSideMixedNav,\n  isHeaderMixedNav,\n  isHeaderSidebarNav,\n  layout,\n  preferencesButtonPosition,\n  sidebarCollapsed,\n  theme,\n} = usePreferences();\nconst accessStore = useAccessStore();\nconst timezoneStore = useTimezoneStore();\nconst { refresh } = useRefresh();\n\nconst sidebarTheme = computed(() => {\n  const dark = isDark.value || preferences.theme.semiDarkSidebar;\n  return dark ? 'dark' : 'light';\n});\n\nconst sidebarThemeSub = computed(() => {\n  const dark = isDark.value || preferences.theme.semiDarkSidebarSub;\n  return dark ? 'dark' : 'light';\n});\n\nconst headerTheme = computed(() => {\n  const dark = isDark.value || preferences.theme.semiDarkHeader;\n  return dark ? 'dark' : 'light';\n});\n\nconst logoClass = computed(() => {\n  const { collapsedShowTitle } = preferences.sidebar;\n  const classes: string[] = [];\n\n  if (collapsedShowTitle && sidebarCollapsed.value && !isMixedNav.value) {\n    classes.push('mx-auto');\n  }\n\n  if (isSideMixedNav.value) {\n    classes.push('flex-center');\n  }\n\n  return classes.join(' ');\n});\n\nconst isMenuRounded = computed(() => {\n  return preferences.navigation.styleType === 'rounded';\n});\n\nconst logoCollapsed = computed(() => {\n  if (isMobile.value && sidebarCollapsed.value) {\n    return true;\n  }\n  if (isHeaderNav.value || isMixedNav.value || isHeaderSidebarNav.value) {\n    return false;\n  }\n  return (\n    sidebarCollapsed.value || isSideMixedNav.value || isHeaderMixedNav.value\n  );\n});\n\nconst showHeaderNav = computed(() => {\n  return (\n    !isMobile.value &&\n    (isHeaderNav.value || isMixedNav.value || isHeaderMixedNav.value)\n  );\n});\n\nconst {\n  handleMenuSelect,\n  handleMenuOpen,\n  headerActive,\n  headerMenus,\n  sidebarActive,\n  sidebarMenus,\n  mixHeaderMenus,\n  sidebarVisible,\n} = useMixedMenu();\n\n// 侧边多列菜单\nconst {\n  extraActiveMenu,\n  extraMenus,\n  handleDefaultSelect,\n  handleMenuMouseEnter,\n  handleMixedMenuSelect,\n  handleSideMouseLeave,\n  sidebarExtraVisible,\n} = useExtraMenu(mixHeaderMenus);\n\n/**\n * 包装菜单，翻译菜单名称\n * @param menus 原始菜单数据\n * @param deep 是否深度包装。对于双列布局，只需要包装第一层，因为更深层的数据会在扩展菜单中重新包装\n */\nfunction wrapperMenus(menus: MenuRecordRaw[], deep: boolean = true) {\n  return deep\n    ? mapTree(menus, (item) => {\n        return { ...cloneDeep(item), name: $t(item.name) };\n      })\n    : menus.map((item) => {\n        return { ...cloneDeep(item), name: $t(item.name) };\n      });\n}\n\nfunction toggleSidebar() {\n  updatePreferences({\n    sidebar: {\n      hidden: !preferences.sidebar.hidden,\n    },\n  });\n}\n\nfunction clearPreferencesAndLogout() {\n  emit('clearPreferencesAndLogout');\n}\n\nfunction clickLogo() {\n  emit('clickLogo');\n}\n\nfunction autoCollapseMenuByRouteMeta(route: RouteLocationNormalizedLoaded) {\n  // 只在双列模式下生效\n  if (\n    ['header-mixed-nav', 'sidebar-mixed-nav'].includes(\n      preferences.app.layout,\n    ) &&\n    route.meta &&\n    route.meta.hideInMenu\n  ) {\n    sidebarExtraVisible.value = false;\n  }\n}\n\nconst route = useRoute();\n\nonMounted(() => {\n  autoCollapseMenuByRouteMeta(route);\n});\n\nwatch(\n  () => preferences.app.layout,\n  async (val) => {\n    if (val === 'sidebar-mixed-nav' && preferences.sidebar.hidden) {\n      updatePreferences({\n        sidebar: {\n          hidden: false,\n        },\n      });\n    }\n  },\n);\n\nconst tabbarStore = useTabbarStore();\n\nfunction refreshAll() {\n  tabbarStore.cachedTabs.clear();\n  refresh();\n}\n\n// 语言更新后，刷新页面\n// i18n.global.locale会在preference.app.locale变更之后才会更新，因此watchpreference.app.locale是不合适的，刷新页面时可能语言配置尚未完全加载完成\nwatch(i18n.global.locale, refreshAll, { flush: 'post' });\n\n// 时区更新后，刷新页面\nwatch(() => timezoneStore.timezone, refreshAll, { flush: 'post' });\n\nconst slots: SetupContext['slots'] = useSlots();\nconst headerSlots = computed(() => {\n  return Object.keys(slots).filter((key) => key.startsWith('header-'));\n});\n</script>\n\n<template>\n  <VbenAdminLayout\n    v-model:sidebar-extra-visible=\"sidebarExtraVisible\"\n    :content-compact=\"preferences.app.contentCompact\"\n    :content-compact-width=\"preferences.app.contentCompactWidth\"\n    :content-padding=\"preferences.app.contentPadding\"\n    :content-padding-bottom=\"preferences.app.contentPaddingBottom\"\n    :content-padding-left=\"preferences.app.contentPaddingLeft\"\n    :content-padding-right=\"preferences.app.contentPaddingRight\"\n    :content-padding-top=\"preferences.app.contentPaddingTop\"\n    :footer-enable=\"preferences.footer.enable\"\n    :footer-fixed=\"preferences.footer.fixed\"\n    :footer-height=\"preferences.footer.height\"\n    :header-height=\"preferences.header.height\"\n    :header-hidden=\"preferences.header.hidden\"\n    :header-mode=\"preferences.header.mode\"\n    :header-theme=\"headerTheme\"\n    :header-toggle-sidebar-button=\"preferences.widget.sidebarToggle\"\n    :header-visible=\"preferences.header.enable\"\n    :is-mobile=\"preferences.app.isMobile\"\n    :layout=\"layout\"\n    :sidebar-draggable=\"preferences.sidebar.draggable\"\n    :sidebar-collapse=\"preferences.sidebar.collapsed\"\n    :sidebar-collapse-show-title=\"preferences.sidebar.collapsedShowTitle\"\n    :sidebar-enable=\"sidebarVisible\"\n    :sidebar-collapsed-button=\"preferences.sidebar.collapsedButton\"\n    :sidebar-fixed-button=\"preferences.sidebar.fixedButton\"\n    :sidebar-expand-on-hover=\"preferences.sidebar.expandOnHover\"\n    :sidebar-extra-collapse=\"preferences.sidebar.extraCollapse\"\n    :sidebar-extra-collapsed-width=\"preferences.sidebar.extraCollapsedWidth\"\n    :sidebar-hidden=\"preferences.sidebar.hidden\"\n    :sidebar-mixed-width=\"preferences.sidebar.mixedWidth\"\n    :sidebar-theme=\"sidebarTheme\"\n    :sidebar-theme-sub=\"sidebarThemeSub\"\n    :sidebar-width=\"preferences.sidebar.width\"\n    :side-collapse-width=\"preferences.sidebar.collapseWidth\"\n    :tabbar-enable=\"preferences.tabbar.enable\"\n    :tabbar-height=\"preferences.tabbar.height\"\n    :z-index=\"preferences.app.zIndex\"\n    @side-mouse-leave=\"handleSideMouseLeave\"\n    @toggle-sidebar=\"toggleSidebar\"\n    @update:sidebar-collapse=\"\n      (value: boolean) => updatePreferences({ sidebar: { collapsed: value } })\n    \"\n    @update:sidebar-enable=\"\n      (value: boolean) => updatePreferences({ sidebar: { enable: value } })\n    \"\n    @update:sidebar-expand-on-hover=\"\n      (value: boolean) =>\n        updatePreferences({ sidebar: { expandOnHover: value } })\n    \"\n    @update:sidebar-extra-collapse=\"\n      (value: boolean) =>\n        updatePreferences({ sidebar: { extraCollapse: value } })\n    \"\n    @update:sidebar-width=\"\n      (value: number) => updatePreferences({ sidebar: { width: value } })\n    \"\n  >\n    <!-- logo -->\n    <template #logo>\n      <VbenLogo\n        v-if=\"preferences.logo.enable\"\n        :fit=\"preferences.logo.fit\"\n        :class=\"logoClass\"\n        :collapsed=\"logoCollapsed\"\n        :src=\"preferences.logo.source\"\n        :src-dark=\"preferences.logo.sourceDark\"\n        :text=\"preferences.app.name\"\n        :theme=\"showHeaderNav ? headerTheme : theme\"\n        @click=\"clickLogo\"\n      >\n        <template v-if=\"$slots['logo-text']\" #text>\n          <slot name=\"logo-text\"></slot>\n        </template>\n      </VbenLogo>\n    </template>\n    <!-- 头部区域 -->\n    <template #header>\n      <LayoutHeader\n        :theme=\"theme\"\n        @clear-preferences-and-logout=\"clearPreferencesAndLogout\"\n      >\n        <template\n          v-if=\"!showHeaderNav && preferences.breadcrumb.enable\"\n          #breadcrumb\n        >\n          <Breadcrumb\n            :hide-when-only-one=\"preferences.breadcrumb.hideOnlyOne\"\n            :show-home=\"preferences.breadcrumb.showHome\"\n            :show-icon=\"preferences.breadcrumb.showIcon\"\n            :type=\"preferences.breadcrumb.styleType\"\n          />\n        </template>\n        <template v-if=\"showHeaderNav\" #menu>\n          <LayoutMenu\n            :default-active=\"headerActive\"\n            :menus=\"wrapperMenus(headerMenus)\"\n            :rounded=\"isMenuRounded\"\n            :theme=\"headerTheme\"\n            class=\"w-full\"\n            mode=\"horizontal\"\n            @select=\"handleMenuSelect\"\n          />\n        </template>\n        <template #user-dropdown>\n          <slot name=\"user-dropdown\"></slot>\n        </template>\n        <template #notification>\n          <slot name=\"notification\"></slot>\n        </template>\n        <template #timezone>\n          <slot name=\"timezone\"></slot>\n        </template>\n        <template v-for=\"item in headerSlots\" #[item]>\n          <slot :name=\"item\"></slot>\n        </template>\n      </LayoutHeader>\n    </template>\n    <!-- 侧边菜单区域 -->\n    <template #menu>\n      <LayoutMenu\n        :accordion=\"preferences.navigation.accordion\"\n        :collapse=\"preferences.sidebar.collapsed\"\n        :collapse-show-title=\"preferences.sidebar.collapsedShowTitle\"\n        :default-active=\"sidebarActive\"\n        :menus=\"wrapperMenus(sidebarMenus)\"\n        :rounded=\"isMenuRounded\"\n        :theme=\"sidebarTheme\"\n        mode=\"vertical\"\n        @open=\"handleMenuOpen\"\n        @select=\"handleMenuSelect\"\n      />\n    </template>\n    <template #mixed-menu>\n      <LayoutMixedMenu\n        :active-path=\"extraActiveMenu\"\n        :menus=\"wrapperMenus(mixHeaderMenus, false)\"\n        :rounded=\"isMenuRounded\"\n        :theme=\"sidebarTheme\"\n        @default-select=\"handleDefaultSelect\"\n        @enter=\"handleMenuMouseEnter\"\n        @select=\"handleMixedMenuSelect\"\n      />\n    </template>\n    <!-- 侧边额外区域 -->\n    <template #side-extra>\n      <LayoutExtraMenu\n        :accordion=\"preferences.navigation.accordion\"\n        :collapse=\"preferences.sidebar.extraCollapse\"\n        :menus=\"wrapperMenus(extraMenus)\"\n        :rounded=\"isMenuRounded\"\n        :theme=\"sidebarThemeSub\"\n      />\n    </template>\n    <template #side-extra-title>\n      <VbenLogo\n        v-if=\"preferences.logo.enable\"\n        :fit=\"preferences.logo.fit\"\n        :text=\"preferences.app.name\"\n        :theme=\"theme\"\n      >\n        <template v-if=\"$slots['logo-text']\" #text>\n          <slot name=\"logo-text\"></slot>\n        </template>\n      </VbenLogo>\n    </template>\n\n    <template #tabbar>\n      <LayoutTabbar\n        v-if=\"preferences.tabbar.enable\"\n        :show-icon=\"preferences.tabbar.showIcon\"\n        :theme=\"theme\"\n      />\n    </template>\n\n    <!-- 主体内容 -->\n    <template #content>\n      <LayoutContent />\n    </template>\n\n    <template v-if=\"preferences.transition.loading\" #content-overlay>\n      <LayoutContentSpinner />\n    </template>\n\n    <!-- 页脚 -->\n    <template v-if=\"preferences.footer.enable\" #footer>\n      <LayoutFooter>\n        <Copyright\n          v-if=\"preferences.copyright.enable\"\n          v-bind=\"preferences.copyright\"\n        />\n      </LayoutFooter>\n    </template>\n\n    <template #extra>\n      <slot name=\"extra\"></slot>\n      <CheckUpdates\n        v-if=\"preferences.app.enableCheckUpdates\"\n        :check-updates-interval=\"preferences.app.checkUpdatesInterval\"\n      />\n\n      <Transition v-if=\"preferences.widget.lockScreen\" name=\"slide-up\">\n        <slot v-if=\"accessStore.isLockScreen\" name=\"lock-screen\"></slot>\n      </Transition>\n\n      <template v-if=\"preferencesButtonPosition.fixed\">\n        <Preferences\n          class=\"fixed top-1/2 right-0 z-100 -translate-y-1/2 transform\"\n          @clear-preferences-and-logout=\"clearPreferencesAndLogout\"\n        />\n      </template>\n      <VbenBackTop />\n    </template>\n  </VbenAdminLayout>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/menu/extra-menu.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { MenuRecordRaw } from '@vben/types';\n\nimport type { MenuProps } from '@vben-core/menu-ui';\n\nimport { useRoute } from 'vue-router';\n\nimport { Menu } from '@vben-core/menu-ui';\n\nimport { useNavigation } from './use-navigation';\n\ninterface Props extends MenuProps {\n  collapse?: boolean;\n  menus?: MenuRecordRaw[];\n}\n\nwithDefaults(defineProps<Props>(), {\n  accordion: true,\n  menus: () => [],\n});\n\nconst route = useRoute();\nconst { navigation } = useNavigation();\n\nasync function handleSelect(key: string) {\n  await navigation(key);\n}\n</script>\n\n<template>\n  <Menu\n    :accordion=\"accordion\"\n    :collapse=\"collapse\"\n    :default-active=\"route.meta?.activePath || route.path\"\n    :menus=\"menus\"\n    :rounded=\"rounded\"\n    :theme=\"theme\"\n    mode=\"vertical\"\n    @select=\"handleSelect\"\n  />\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/menu/index.ts",
    "content": "export { default as LayoutExtraMenu } from './extra-menu.vue';\nexport { default as LayoutMenu } from './menu.vue';\nexport { default as LayoutMixedMenu } from './mixed-menu.vue';\nexport * from './use-extra-menu';\nexport * from './use-mixed-menu';\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/menu/menu.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { MenuRecordRaw } from '@vben/types';\n\nimport type { MenuProps } from '@vben-core/menu-ui';\n\nimport { Menu } from '@vben-core/menu-ui';\n\ninterface Props extends MenuProps {\n  menus?: MenuRecordRaw[];\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  accordion: true,\n  menus: () => [],\n});\n\nconst emit = defineEmits<{\n  open: [string, string[]];\n  select: [string, string?];\n}>();\n\nfunction handleMenuSelect(key: string) {\n  emit('select', key, props.mode);\n}\n\nfunction handleMenuOpen(key: string, path: string[]) {\n  emit('open', key, path);\n}\n</script>\n\n<template>\n  <Menu\n    :accordion=\"accordion\"\n    :collapse=\"collapse\"\n    :collapse-show-title=\"collapseShowTitle\"\n    :default-active=\"defaultActive\"\n    :menus=\"menus\"\n    :mode=\"mode\"\n    :rounded=\"rounded\"\n    scroll-to-active\n    :theme=\"theme\"\n    @open=\"handleMenuOpen\"\n    @select=\"handleMenuSelect\"\n  />\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/menu/mixed-menu.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { MenuRecordRaw } from '@vben/types';\n\nimport type { NormalMenuProps } from '@vben-core/menu-ui';\n\nimport { onBeforeMount } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport { findMenuByPath } from '@vben/utils';\n\nimport { NormalMenu } from '@vben-core/menu-ui';\n\ninterface Props extends NormalMenuProps {}\n\nconst props = defineProps<Props>();\n\nconst emit = defineEmits<{\n  defaultSelect: [MenuRecordRaw, MenuRecordRaw?];\n  enter: [MenuRecordRaw];\n  select: [MenuRecordRaw];\n}>();\n\nconst route = useRoute();\n\nonBeforeMount(() => {\n  const menu = findMenuByPath(props.menus || [], route.path);\n  if (menu) {\n    const rootMenu = (props.menus || []).find(\n      (item) => item.path === menu.parents?.[0],\n    );\n    emit('defaultSelect', menu, rootMenu);\n  }\n});\n</script>\n\n<template>\n  <NormalMenu\n    :active-path=\"activePath\"\n    :collapse=\"collapse\"\n    :menus=\"menus\"\n    :rounded=\"rounded\"\n    :theme=\"theme\"\n    @enter=\"(menu) => emit('enter', menu)\"\n    @select=\"(menu) => emit('select', menu)\"\n  />\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/menu/use-extra-menu.ts",
    "content": "import type { ComputedRef } from 'vue';\n\nimport type { MenuRecordRaw } from '@vben/types';\n\nimport { computed, ref, watch } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore } from '@vben/stores';\nimport { findRootMenuByPath } from '@vben/utils';\n\nimport { useNavigation } from './use-navigation';\n\nfunction useExtraMenu(useRootMenus?: ComputedRef<MenuRecordRaw[]>) {\n  const accessStore = useAccessStore();\n  const { navigation, willOpenedByWindow } = useNavigation();\n\n  const menus = computed(() => useRootMenus?.value ?? accessStore.accessMenus);\n\n  /** 记录当前顶级菜单下哪个子菜单最后激活 */\n  const defaultSubMap = new Map<string, string>();\n  const extraRootMenus = ref<MenuRecordRaw[]>([]);\n  const route = useRoute();\n  const extraMenus = ref<MenuRecordRaw[]>([]);\n  const sidebarExtraVisible = ref<boolean>(false);\n  const extraActiveMenu = ref('');\n  const parentLevel = computed(() =>\n    preferences.app.layout === 'header-mixed-nav' ? 1 : 0,\n  );\n\n  /**\n   * 选择混合菜单事件\n   * @param menu\n   */\n  const handleMixedMenuSelect = async (menu: MenuRecordRaw) => {\n    const _extraMenus = menu?.children ?? [];\n    const hasChildren = _extraMenus.length > 0;\n\n    if (!willOpenedByWindow(menu.path)) {\n      extraMenus.value = _extraMenus ?? [];\n      extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path;\n      sidebarExtraVisible.value = hasChildren;\n    }\n\n    if (!hasChildren) {\n      await navigation(menu.path);\n    } else if (preferences.sidebar.autoActivateChild) {\n      await navigation(\n        defaultSubMap.has(menu.path)\n          ? (defaultSubMap.get(menu.path) as string)\n          : menu.path,\n      );\n    }\n  };\n\n  /**\n   * 选择默认菜单事件\n   * @param menu\n   * @param rootMenu\n   */\n  const handleDefaultSelect = async (\n    menu: MenuRecordRaw,\n    rootMenu?: MenuRecordRaw,\n  ) => {\n    extraMenus.value = rootMenu?.children ?? extraRootMenus.value ?? [];\n    extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path;\n\n    if (preferences.sidebar.expandOnHover) {\n      sidebarExtraVisible.value = extraMenus.value.length > 0;\n    }\n  };\n\n  /**\n   * 侧边菜单鼠标移出事件\n   */\n  const handleSideMouseLeave = () => {\n    if (preferences.sidebar.expandOnHover) {\n      return;\n    }\n\n    const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath(\n      menus.value,\n      route.path,\n    );\n    extraActiveMenu.value = rootMenuPath ?? findMenu?.path ?? '';\n    extraMenus.value = rootMenu?.children ?? [];\n  };\n\n  const handleMenuMouseEnter = (menu: MenuRecordRaw) => {\n    if (!preferences.sidebar.expandOnHover) {\n      const { findMenu } = findRootMenuByPath(menus.value, menu.path);\n      extraMenus.value = findMenu?.children ?? [];\n      extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path;\n      sidebarExtraVisible.value = extraMenus.value.length > 0;\n    }\n  };\n\n  function calcExtraMenus(path: string) {\n    const currentPath = route.meta?.activePath || path;\n    const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath(\n      menus.value,\n      currentPath,\n      parentLevel.value,\n    );\n    extraRootMenus.value = rootMenu?.children ?? [];\n    if (rootMenuPath) defaultSubMap.set(rootMenuPath, currentPath);\n    extraActiveMenu.value = rootMenuPath ?? findMenu?.path ?? '';\n    extraMenus.value = rootMenu?.children ?? [];\n    if (preferences.sidebar.expandOnHover) {\n      sidebarExtraVisible.value = extraMenus.value.length > 0;\n    }\n  }\n\n  watch(\n    () => [route.path, preferences.app.layout],\n    ([path]) => {\n      calcExtraMenus(path || '');\n    },\n    { immediate: true },\n  );\n\n  return {\n    extraActiveMenu,\n    extraMenus,\n    handleDefaultSelect,\n    handleMenuMouseEnter,\n    handleMixedMenuSelect,\n    handleSideMouseLeave,\n    sidebarExtraVisible,\n  };\n}\n\nexport { useExtraMenu };\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/menu/use-mixed-menu.ts",
    "content": "import type { MenuRecordRaw } from '@vben/types';\n\nimport { computed, onBeforeMount, ref, watch } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport { preferences, usePreferences } from '@vben/preferences';\nimport { useAccessStore } from '@vben/stores';\nimport { findRootMenuByPath } from '@vben/utils';\n\nimport { useNavigation } from './use-navigation';\n\nfunction useMixedMenu() {\n  const { navigation, willOpenedByWindow } = useNavigation();\n  const accessStore = useAccessStore();\n  const route = useRoute();\n  const splitSideMenus = ref<MenuRecordRaw[]>([]);\n  const rootMenuPath = ref<string>('');\n  const mixedRootMenuPath = ref<string>('');\n  const mixExtraMenus = ref<MenuRecordRaw[]>([]);\n  /** 记录当前顶级菜单下哪个子菜单最后激活 */\n  const defaultSubMap = new Map<string, string>();\n  const { isMixedNav, isHeaderMixedNav } = usePreferences();\n\n  const needSplit = computed(\n    () =>\n      (preferences.navigation.split && isMixedNav.value) ||\n      isHeaderMixedNav.value,\n  );\n\n  const sidebarVisible = computed(() => {\n    const enableSidebar = preferences.sidebar.enable;\n    if (needSplit.value) {\n      return enableSidebar && splitSideMenus.value.length > 0;\n    }\n    return enableSidebar;\n  });\n  const menus = computed(() => accessStore.accessMenus);\n\n  /**\n   * 头部菜单\n   */\n  const headerMenus = computed(() => {\n    if (!needSplit.value) {\n      return menus.value;\n    }\n    return menus.value.map((item) => {\n      return {\n        ...item,\n        children: [],\n      };\n    });\n  });\n\n  /**\n   * 侧边菜单\n   */\n  const sidebarMenus = computed(() => {\n    return needSplit.value ? splitSideMenus.value : menus.value;\n  });\n\n  const mixHeaderMenus = computed(() => {\n    return isHeaderMixedNav.value ? sidebarMenus.value : headerMenus.value;\n  });\n\n  /**\n   * 侧边菜单激活路径\n   */\n  const sidebarActive = computed(() => {\n    return (route?.meta?.activePath as string) ?? route.path;\n  });\n\n  /**\n   * 头部菜单激活路径\n   */\n  const headerActive = computed(() => {\n    if (!needSplit.value) {\n      return route.meta?.activePath ?? route.path;\n    }\n    return rootMenuPath.value;\n  });\n\n  /**\n   * 菜单点击事件处理\n   * @param key 菜单路径\n   * @param mode 菜单模式\n   */\n  const handleMenuSelect = (key: string, mode?: string) => {\n    if (!needSplit.value || mode === 'vertical') {\n      navigation(key);\n      return;\n    }\n    const rootMenu = menus.value.find((item) => item.path === key);\n    const _splitSideMenus = rootMenu?.children ?? [];\n\n    if (!willOpenedByWindow(key)) {\n      rootMenuPath.value = rootMenu?.path ?? '';\n      splitSideMenus.value = _splitSideMenus;\n    }\n\n    if (_splitSideMenus.length === 0) {\n      navigation(key);\n    } else if (rootMenu && preferences.sidebar.autoActivateChild) {\n      navigation(\n        defaultSubMap.has(rootMenu.path)\n          ? (defaultSubMap.get(rootMenu.path) as string)\n          : rootMenu.path,\n      );\n    }\n  };\n\n  /**\n   * 侧边菜单展开事件\n   * @param key 路由路径\n   * @param parentsPath 父级路径\n   */\n  const handleMenuOpen = (key: string, parentsPath: string[]) => {\n    if (parentsPath.length <= 1 && preferences.sidebar.autoActivateChild) {\n      navigation(\n        defaultSubMap.has(key) ? (defaultSubMap.get(key) as string) : key,\n      );\n    }\n  };\n\n  /**\n   * 计算侧边菜单\n   * @param path 路由路径\n   */\n  function calcSideMenus(path: string = route.path) {\n    let { rootMenu } = findRootMenuByPath(menus.value, path);\n    if (!rootMenu) {\n      rootMenu = menus.value.find((item) => item.path === path);\n    }\n    const result = findRootMenuByPath(rootMenu?.children || [], path, 1);\n    mixedRootMenuPath.value = result.rootMenuPath ?? '';\n    mixExtraMenus.value = result.rootMenu?.children ?? [];\n    rootMenuPath.value = rootMenu?.path ?? '';\n    splitSideMenus.value = rootMenu?.children ?? [];\n  }\n\n  watch(\n    () => route.path,\n    (path) => {\n      const currentPath = route?.meta?.activePath ?? route?.meta?.link ?? path;\n      if (willOpenedByWindow(currentPath)) {\n        return;\n      }\n      calcSideMenus(currentPath);\n      if (rootMenuPath.value)\n        defaultSubMap.set(rootMenuPath.value, currentPath);\n    },\n    { immediate: true },\n  );\n\n  // 初始化计算侧边菜单\n  onBeforeMount(() => {\n    calcSideMenus(route.meta?.activePath || route.path);\n  });\n\n  return {\n    handleMenuSelect,\n    handleMenuOpen,\n    headerActive,\n    headerMenus,\n    sidebarActive,\n    sidebarMenus,\n    mixHeaderMenus,\n    mixExtraMenus,\n    sidebarVisible,\n  };\n}\n\nexport { useMixedMenu };\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/menu/use-navigation.ts",
    "content": "import type { RouteRecordNormalized } from 'vue-router';\n\nimport { useRouter } from 'vue-router';\n\nimport { isHttpUrl, openRouteInNewWindow, openWindow } from '@vben/utils';\n\nfunction useNavigation() {\n  const router = useRouter();\n  const routeMetaMap = new Map<string, RouteRecordNormalized>();\n\n  // 初始化路由映射\n  const initRouteMetaMap = () => {\n    const routes = router.getRoutes();\n    routes.forEach((route) => {\n      routeMetaMap.set(route.path, route);\n    });\n  };\n\n  initRouteMetaMap();\n\n  // 监听路由变化\n  router.afterEach(() => {\n    initRouteMetaMap();\n  });\n\n  // 检查是否应该在新窗口打开\n  const shouldOpenInNewWindow = (path: string): boolean => {\n    if (isHttpUrl(path)) {\n      return true;\n    }\n    const route = routeMetaMap.get(path);\n    // 如果有外链或者设置了在新窗口打开，返回 true\n    return !!(route?.meta?.link || route?.meta?.openInNewWindow);\n  };\n\n  const resolveHref = (path: string): string => {\n    return router.resolve(path).href;\n  };\n\n  const navigation = async (path: string) => {\n    try {\n      const route = routeMetaMap.get(path);\n      const { openInNewWindow = false, query = {}, link } = route?.meta ?? {};\n\n      // 检查是否有外链\n      if (link && typeof link === 'string') {\n        openWindow(link, { target: '_blank' });\n        return;\n      }\n\n      if (isHttpUrl(path)) {\n        openWindow(path, { target: '_blank' });\n      } else if (openInNewWindow) {\n        openRouteInNewWindow(resolveHref(path));\n      } else {\n        await router.push({\n          path,\n          query,\n        });\n      }\n    } catch (error) {\n      console.error('Navigation failed:', error);\n      throw error;\n    }\n  };\n\n  const willOpenedByWindow = (path: string) => {\n    return shouldOpenInNewWindow(path);\n  };\n\n  return { navigation, willOpenedByWindow };\n}\n\nexport { useNavigation };\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/tabbar/index.ts",
    "content": "export { default as LayoutTabbar } from './tabbar.vue';\nexport * from './use-tabbar';\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/tabbar/tabbar.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport { useContentMaximize, useTabs } from '@vben/hooks';\nimport { preferences } from '@vben/preferences';\nimport { useTabbarStore } from '@vben/stores';\n\nimport {\n  TabsToolMore,\n  TabsToolRefresh,\n  TabsToolScreen,\n  TabsView,\n} from '@vben-core/tabs-ui';\n\nimport { useTabbar } from './use-tabbar';\n\ndefineOptions({\n  name: 'LayoutTabbar',\n});\n\ndefineProps<{ showIcon?: boolean; theme?: string }>();\n\nconst route = useRoute();\nconst tabbarStore = useTabbarStore();\nconst { contentIsMaximize, toggleMaximize } = useContentMaximize();\nconst { refreshTab, unpinTab } = useTabs();\n\nconst {\n  createContextMenus,\n  currentActive,\n  currentTabs,\n  handleClick,\n  handleClose,\n} = useTabbar();\n\nconst menus = computed(() => {\n  const tab = tabbarStore.getTabByKey(currentActive.value);\n  const menus = createContextMenus(tab);\n  return menus.map((item) => {\n    return {\n      ...item,\n      label: item.text,\n      value: item.key,\n    };\n  });\n});\n\n// 刷新后如果不保持tab状态，关闭其他tab\nif (!preferences.tabbar.persist) {\n  tabbarStore.closeOtherTabs(route);\n}\n</script>\n\n<template>\n  <TabsView\n    :active=\"currentActive\"\n    :class=\"theme\"\n    :context-menus=\"createContextMenus\"\n    :draggable=\"preferences.tabbar.draggable\"\n    :show-icon=\"showIcon\"\n    :style-type=\"preferences.tabbar.styleType\"\n    :tabs=\"currentTabs\"\n    :wheelable=\"preferences.tabbar.wheelable\"\n    :middle-click-to-close=\"preferences.tabbar.middleClickToClose\"\n    @close=\"handleClose\"\n    @sort-tabs=\"tabbarStore.sortTabs\"\n    @unpin=\"unpinTab\"\n    @update:active=\"handleClick\"\n  />\n  <div class=\"flex-center h-full\">\n    <TabsToolMore v-if=\"preferences.tabbar.showMore\" :menus=\"menus\" />\n    <TabsToolRefresh\n      v-if=\"preferences.tabbar.showRefresh\"\n      @refresh=\"refreshTab\"\n    />\n    <TabsToolScreen\n      v-if=\"preferences.tabbar.showMaximize\"\n      :screen=\"contentIsMaximize\"\n      @change=\"toggleMaximize\"\n      @update:screen=\"toggleMaximize\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/basic/tabbar/use-tabbar.ts",
    "content": "import type { RouteLocationNormalizedGeneric } from 'vue-router';\n\nimport type { TabDefinition } from '@vben/types';\n\nimport type { IContextMenuItem } from '@vben-core/tabs-ui';\n\nimport { computed, ref, watch } from 'vue';\nimport { useRoute, useRouter } from 'vue-router';\n\nimport { useContentMaximize, useTabs } from '@vben/hooks';\nimport {\n  ArrowLeftToLine,\n  ArrowRightLeft,\n  ArrowRightToLine,\n  ExternalLink,\n  FoldHorizontal,\n  Fullscreen,\n  Minimize2,\n  Pin,\n  PinOff,\n  RotateCw,\n  X,\n} from '@vben/icons';\nimport { $t, useI18n } from '@vben/locales';\nimport { getTabKey, useAccessStore, useTabbarStore } from '@vben/stores';\nimport { filterTree } from '@vben/utils';\n\nexport function useTabbar() {\n  const router = useRouter();\n  const route = useRoute();\n  const accessStore = useAccessStore();\n  const tabbarStore = useTabbarStore();\n  const { contentIsMaximize, toggleMaximize } = useContentMaximize();\n  const {\n    closeAllTabs,\n    closeCurrentTab,\n    closeLeftTabs,\n    closeOtherTabs,\n    closeRightTabs,\n    closeTabByKey,\n    getTabDisableState,\n    openTabInNewWindow,\n    refreshTab,\n    toggleTabPin,\n  } = useTabs();\n\n  /**\n   * 当前路径对应的tab的key\n   */\n  const currentActive = computed(() => {\n    return getTabKey(route);\n  });\n\n  const { locale } = useI18n();\n  const currentTabs = ref<RouteLocationNormalizedGeneric[]>();\n  watch(\n    [\n      () => tabbarStore.getTabs,\n      () => tabbarStore.updateTime,\n      () => locale.value,\n    ],\n    ([tabs]) => {\n      currentTabs.value = tabs.map((item) => wrapperTabLocale(item));\n    },\n  );\n\n  /**\n   * 初始化固定标签页\n   */\n  const initAffixTabs = () => {\n    const affixTabs = filterTree(router.getRoutes(), (route) => {\n      return !!route.meta?.affixTab;\n    });\n    tabbarStore.setAffixTabs(affixTabs);\n  };\n\n  // 点击tab,跳转路由\n  const handleClick = (key: string) => {\n    const { fullPath, path } = tabbarStore.getTabByKey(key);\n    router.push(fullPath || path);\n  };\n\n  // 关闭tab\n  const handleClose = async (key: string) => {\n    await closeTabByKey(key);\n  };\n\n  function wrapperTabLocale(tab: RouteLocationNormalizedGeneric) {\n    return {\n      ...tab,\n      meta: {\n        ...tab?.meta,\n        title: $t(tab?.meta?.title as string),\n      },\n    };\n  }\n\n  watch(\n    () => accessStore.accessMenus,\n    () => {\n      initAffixTabs();\n    },\n    { immediate: true },\n  );\n\n  watch(\n    () => route.fullPath,\n    () => {\n      const meta = route.matched?.[route.matched.length - 1]?.meta;\n      tabbarStore.addTab({\n        ...route,\n        meta: meta || route.meta,\n      });\n    },\n    { immediate: true },\n  );\n\n  const createContextMenus = (tab: TabDefinition) => {\n    const {\n      disabledCloseAll,\n      disabledCloseCurrent,\n      disabledCloseLeft,\n      disabledCloseOther,\n      disabledCloseRight,\n      disabledRefresh,\n    } = getTabDisableState(tab);\n\n    const affixTab = tab?.meta?.affixTab ?? false;\n\n    const menus: IContextMenuItem[] = [\n      {\n        disabled: disabledCloseCurrent,\n        handler: async () => {\n          await closeCurrentTab(tab);\n        },\n        icon: X,\n        key: 'close',\n        text: $t('preferences.tabbar.contextMenu.close'),\n      },\n      {\n        handler: async () => {\n          await toggleTabPin(tab);\n        },\n        icon: affixTab ? PinOff : Pin,\n        key: 'affix',\n        text: affixTab\n          ? $t('preferences.tabbar.contextMenu.unpin')\n          : $t('preferences.tabbar.contextMenu.pin'),\n      },\n      {\n        handler: async () => {\n          if (!contentIsMaximize.value) {\n            await router.push(tab.fullPath);\n          }\n          toggleMaximize();\n        },\n        icon: contentIsMaximize.value ? Minimize2 : Fullscreen,\n        key: contentIsMaximize.value ? 'restore-maximize' : 'maximize',\n        text: contentIsMaximize.value\n          ? $t('preferences.tabbar.contextMenu.restoreMaximize')\n          : $t('preferences.tabbar.contextMenu.maximize'),\n      },\n      {\n        disabled: disabledRefresh,\n        handler: () => refreshTab(),\n        icon: RotateCw,\n        key: 'reload',\n        text: $t('preferences.tabbar.contextMenu.reload'),\n      },\n      {\n        handler: async () => {\n          await openTabInNewWindow(tab);\n        },\n        icon: ExternalLink,\n        key: 'open-in-new-window',\n        separator: true,\n        text: $t('preferences.tabbar.contextMenu.openInNewWindow'),\n      },\n\n      {\n        disabled: disabledCloseLeft,\n        handler: async () => {\n          await closeLeftTabs(tab);\n        },\n        icon: ArrowLeftToLine,\n        key: 'close-left',\n        text: $t('preferences.tabbar.contextMenu.closeLeft'),\n      },\n      {\n        disabled: disabledCloseRight,\n        handler: async () => {\n          await closeRightTabs(tab);\n        },\n        icon: ArrowRightToLine,\n        key: 'close-right',\n        separator: true,\n        text: $t('preferences.tabbar.contextMenu.closeRight'),\n      },\n      {\n        disabled: disabledCloseOther,\n        handler: async () => {\n          await closeOtherTabs(tab);\n        },\n        icon: FoldHorizontal,\n        key: 'close-other',\n        text: $t('preferences.tabbar.contextMenu.closeOther'),\n      },\n      {\n        disabled: disabledCloseAll,\n        handler: closeAllTabs,\n        icon: ArrowRightLeft,\n        key: 'close-all',\n        text: $t('preferences.tabbar.contextMenu.closeAll'),\n      },\n    ];\n\n    return menus.filter((item) => tabbarStore.getMenuList.includes(item.key));\n  };\n\n  return {\n    createContextMenus,\n    currentActive,\n    currentTabs,\n    handleClick,\n    handleClose,\n  };\n}\n"
  },
  {
    "path": "packages/effects/layouts/src/hooks/index.ts",
    "content": "import type { VNode } from 'vue';\nimport type {\n  RouteLocationNormalizedLoaded,\n  RouteLocationNormalizedLoadedGeneric,\n} from 'vue-router';\n\nimport { computed } from 'vue';\n\nimport { preferences, usePreferences } from '@vben/preferences';\n\n/**\n * 转换组件，自动添加 name\n * @param component\n * @param route\n */\nexport function transformComponent(\n  component: VNode,\n  route: RouteLocationNormalizedLoadedGeneric,\n) {\n  // 组件视图未找到，如果有设置后备视图，则返回后备视图，如果没有，则抛出错误\n  if (!component) {\n    console.error(\n      'Component view not found，please check the route configuration',\n    );\n    return undefined;\n  }\n\n  const routeName = route.name as string;\n  // 如果组件没有 name，则直接返回\n  if (!routeName) {\n    return component;\n  }\n  const componentName = (component?.type as any)?.name;\n\n  // 已经设置过 name，则直接返回\n  if (componentName) {\n    return component;\n  }\n\n  // componentName 与 routeName 一致，则直接返回\n  if (componentName === routeName) {\n    return component;\n  }\n\n  // 设置 name\n  component.type ||= {};\n  (component.type as any).name = routeName;\n\n  return component;\n}\n\n/**\n * Layout相关hook\n */\nexport function useLayoutHook() {\n  const { keepAlive } = usePreferences();\n  /**\n   * 是否使用动画\n   */\n  const getEnabledTransition = computed(() => {\n    const { transition } = preferences;\n    const transitionName = transition.name;\n    return transitionName && transition.enable;\n  });\n\n  /**\n   * 获取路由过渡动画\n   * @param _route\n   */\n  function getTransitionName(_route: RouteLocationNormalizedLoaded) {\n    // 如果偏好设置未设置，则不使用动画\n    const { tabbar, transition } = preferences;\n    const transitionName = transition.name;\n    if (!transitionName || !transition.enable) {\n      return;\n    }\n\n    // 标签页未启用或者未开启缓存，则使用全局配置动画\n    if (!tabbar.enable || !keepAlive) {\n      return transitionName;\n    }\n\n    // 如果页面已经加载过，则不使用动画\n    // if (route.meta.loaded) {\n    //   return;\n    // }\n    // 已经打开且已经加载过的页面不使用动画\n    // const inTabs = getCachedTabs.value.includes(route.name as string);\n\n    // return inTabs && route.meta.loaded ? undefined : transitionName;\n    return transitionName;\n  }\n\n  return {\n    getEnabledTransition,\n    getTransitionName,\n  };\n}\n"
  },
  {
    "path": "packages/effects/layouts/src/iframe/iframe-router-view.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { RouteLocationNormalized } from 'vue-router';\n\nimport { computed, ref } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport { preferences } from '@vben/preferences';\nimport { useTabbarStore } from '@vben/stores';\n\nimport { VbenSpinner } from '@vben-core/shadcn-ui';\n\ndefineOptions({ name: 'IFrameRouterView' });\n\nconst spinningList = ref<boolean[]>([]);\nconst tabbarStore = useTabbarStore();\nconst route = useRoute();\n\nconst enableTabbar = computed(() => preferences.tabbar.enable);\n\nconst iframeRoutes = computed(() => {\n  if (!enableTabbar.value) {\n    return route.meta.iframeSrc ? [route] : [];\n  }\n  return tabbarStore.getTabs.filter((tab) => !!tab.meta?.iframeSrc);\n});\n\nconst tabNames = computed(\n  () => new Set(iframeRoutes.value.map((item) => item.name as string)),\n);\n\nconst showIframe = computed(() => iframeRoutes.value.length > 0);\n\nfunction routeShow(tabItem: RouteLocationNormalized) {\n  return tabItem.name === route.name;\n}\n\nfunction canRender(tabItem: RouteLocationNormalized) {\n  const { meta, name } = tabItem;\n\n  if (!name || !tabbarStore.renderRouteView) {\n    return false;\n  }\n\n  if (!enableTabbar.value) {\n    return routeShow(tabItem);\n  }\n\n  // 跟随 keepAlive 状态,与其他tab页保持一致\n  if (\n    !meta?.keepAlive &&\n    tabNames.value.has(name as string) &&\n    name !== route.name\n  ) {\n    return false;\n  }\n  return tabbarStore.getTabs.some((tab) => tab.name === name);\n}\n\nfunction hideLoading(index: number) {\n  spinningList.value[index] = false;\n}\n\nfunction showSpinning(index: number) {\n  const curSpinning = spinningList.value[index];\n  // 首次加载时显示loading\n  return curSpinning === undefined ? true : curSpinning;\n}\n</script>\n<template>\n  <template v-if=\"showIframe\">\n    <template v-for=\"(item, index) in iframeRoutes\" :key=\"item.fullPath\">\n      <div\n        v-if=\"canRender(item)\"\n        v-show=\"routeShow(item)\"\n        class=\"relative size-full\"\n      >\n        <VbenSpinner :spinning=\"showSpinning(index)\" />\n        <iframe\n          :src=\"item.meta.iframeSrc as string\"\n          class=\"size-full\"\n          @load=\"hideLoading(index)\"\n        ></iframe>\n      </div>\n    </template>\n  </template>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/iframe/iframe-view.vue",
    "content": "<template>\n  <div></div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/iframe/index.ts",
    "content": "export { default as IFrameRouterView } from './iframe-router-view.vue';\nexport { default as IFrameView } from './iframe-view.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/index.ts",
    "content": "export * from './authentication';\nexport * from './basic';\nexport * from './iframe';\nexport * from './widgets';\n"
  },
  {
    "path": "packages/effects/layouts/src/route-cached/index.ts",
    "content": "export { default as RouteCachedPage } from './route-cached-page.vue';\nexport { default as RouteCachedView } from './route-cached-view.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/route-cached/route-cached-page.vue",
    "content": "<!-- 本组件用于获取缓存的route并保存到pinia -->\n<script setup lang=\"ts\">\nimport type { VNode } from 'vue';\nimport type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';\n\nimport { watch } from 'vue';\n\nimport { useTabbarStore } from '@vben/stores';\n\ninterface Props {\n  component?: VNode;\n  route: RouteLocationNormalizedLoadedGeneric;\n}\n\n/**\n * 这是页面缓存组件，不做任何的的实际渲染\n */\ndefineOptions({\n  render() {\n    return null;\n  },\n});\nconst props = defineProps<Props>();\n\nconst { addCachedRoute } = useTabbarStore();\n\nwatch(\n  () => props.route,\n  () => {\n    if (props.component && props.route.meta.domCached) {\n      addCachedRoute(props.component, props.route);\n    }\n  },\n  { immediate: true },\n);\n</script>\n"
  },
  {
    "path": "packages/effects/layouts/src/route-cached/route-cached-view.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed, unref, watch } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport { preferences } from '@vben/preferences';\nimport { getTabKey, storeToRefs, useTabbarStore } from '@vben/stores';\n\nimport { transformComponent, useLayoutHook } from '../hooks';\n\nconst route = useRoute();\n\nconst tabbarStore = useTabbarStore();\n\nconst { getTabs, getCachedRoutes, getExcludeCachedTabs } =\n  storeToRefs(tabbarStore);\nconst { removeCachedRoute } = tabbarStore;\n\nconst { getEnabledTransition, getTransitionName } = useLayoutHook();\n\n/**\n * 是否启用tab\n */\nconst enableTabbar = computed(() => preferences.tabbar.enable);\n\nconst computedCachedRouteKeys = computed(() => {\n  if (!unref(enableTabbar)) {\n    return [];\n  }\n  return unref(getTabs)\n    .filter((item) => item.meta.domCached)\n    .map((item) => getTabKey(item));\n});\n\n/**\n * 监听缓存路由变化，删除不存在的缓存路由\n */\nwatch(computedCachedRouteKeys, (keys) => {\n  unref(getCachedRoutes).forEach((item) => {\n    if (!keys.includes(item.key)) {\n      removeCachedRoute(item.key);\n    }\n  });\n});\n\n/**\n * 所有缓存的route\n */\nconst computedCachedRoutes = computed(() => {\n  if (!unref(enableTabbar)) {\n    return [];\n  }\n  // 刷新路由可刷新缓存\n  const excludeCachedTabKeys = unref(getExcludeCachedTabs);\n  return [...unref(getCachedRoutes).values()].filter((item) => {\n    const componentType: any = item.component.type || {};\n    let componentName = componentType.name;\n    if (!componentName) {\n      componentName = item.route.name;\n    }\n    return !excludeCachedTabKeys.includes(componentName);\n  });\n});\n\n/**\n * 是否显示\n */\nconst computedShowView = computed(() => unref(computedCachedRoutes).length > 0);\n\nconst computedCurrentRouteKey = computed(() => {\n  return getTabKey(route);\n});\n</script>\n\n<template>\n  <template v-if=\"computedShowView\">\n    <template v-for=\"item in computedCachedRoutes\" :key=\"item.key\">\n      <Transition\n        v-if=\"getEnabledTransition\"\n        appear\n        mode=\"out-in\"\n        :name=\"getTransitionName(item.route)\"\n      >\n        <component\n          v-show=\"item.key === computedCurrentRouteKey\"\n          :is=\"transformComponent(item.component, item.route)\"\n        />\n      </Transition>\n      <template v-else>\n        <component\n          v-show=\"item.key === computedCurrentRouteKey\"\n          :is=\"transformComponent(item.component, item.route)\"\n        />\n      </template>\n    </template>\n  </template>\n</template>\n\n<style scoped></style>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/breadcrumb.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { BreadcrumbStyleType } from '@vben/types';\n\nimport type { IBreadcrumb } from '@vben-core/shadcn-ui';\n\nimport { computed } from 'vue';\nimport { useRoute, useRouter } from 'vue-router';\n\nimport { $t } from '@vben/locales';\n\nimport { VbenBreadcrumbView } from '@vben-core/shadcn-ui';\n\ninterface Props {\n  hideWhenOnlyOne?: boolean;\n  showHome?: boolean;\n  showIcon?: boolean;\n  type?: BreadcrumbStyleType;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  showHome: false,\n  showIcon: false,\n  type: 'normal',\n});\n\nconst route = useRoute();\nconst router = useRouter();\n\nconst breadcrumbs = computed((): IBreadcrumb[] => {\n  const matched = route.matched;\n\n  const resultBreadcrumb: IBreadcrumb[] = [];\n\n  for (const match of matched) {\n    const { meta, path } = match;\n    const { hideChildrenInMenu, hideInBreadcrumb, icon, name, title } =\n      meta || {};\n    if (hideInBreadcrumb || hideChildrenInMenu || !path) {\n      continue;\n    }\n\n    resultBreadcrumb.push({\n      icon,\n      path: path || route.path,\n      title: title ? $t((title || name) as string) : '',\n    });\n  }\n  if (props.showHome) {\n    resultBreadcrumb.unshift({\n      icon: 'mdi:home-outline',\n      isHome: true,\n      path: '/',\n    });\n  }\n  if (props.hideWhenOnlyOne && resultBreadcrumb.length === 1) {\n    return [];\n  }\n\n  return resultBreadcrumb;\n});\n\nfunction handleSelect(path: string) {\n  router.push(path);\n}\n</script>\n<template>\n  <VbenBreadcrumbView\n    :breadcrumbs=\"breadcrumbs\"\n    :show-icon=\"showIcon\"\n    :style-type=\"type\"\n    class=\"ml-2\"\n    @select=\"handleSelect\"\n  />\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/check-updates/check-updates.vue",
    "content": "<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, ref } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport { useVbenModal } from '@vben-core/popup-ui';\n\ninterface Props {\n  // 轮询时间，分钟\n  checkUpdatesInterval?: number;\n  // 检查更新的地址\n  checkUpdateUrl?: string;\n}\n\ndefineOptions({ name: 'CheckUpdates' });\n\nconst props = withDefaults(defineProps<Props>(), {\n  checkUpdatesInterval: 1,\n  checkUpdateUrl: import.meta.env.BASE_URL || '/',\n});\n\nlet isCheckingUpdates = false;\nconst currentVersionTag = ref('');\nconst lastVersionTag = ref('');\nconst timer = ref<ReturnType<typeof setInterval>>();\n\nconst [UpdateNoticeModal, modalApi] = useVbenModal({\n  closable: false,\n  closeOnPressEscape: false,\n  closeOnClickModal: false,\n  onConfirm() {\n    lastVersionTag.value = currentVersionTag.value;\n    window.location.reload();\n    // handleSubmitLogout();\n  },\n});\n\nasync function getVersionTag() {\n  try {\n    if (\n      location.hostname === 'localhost' ||\n      location.hostname === '127.0.0.1'\n    ) {\n      return null;\n    }\n    const response = await fetch(props.checkUpdateUrl, {\n      cache: 'no-cache',\n      method: 'HEAD',\n      redirect: 'manual',\n    });\n\n    return (\n      response.headers.get('etag') || response.headers.get('last-modified')\n    );\n  } catch {\n    console.error('Failed to fetch version tag');\n    return null;\n  }\n}\n\nasync function checkForUpdates() {\n  const versionTag = await getVersionTag();\n  if (!versionTag) {\n    return;\n  }\n\n  // 首次运行时不提示更新\n  if (!lastVersionTag.value) {\n    lastVersionTag.value = versionTag;\n    return;\n  }\n\n  if (lastVersionTag.value !== versionTag && versionTag) {\n    clearInterval(timer.value);\n    handleNotice(versionTag);\n  }\n}\nfunction handleNotice(versionTag: string) {\n  currentVersionTag.value = versionTag;\n  modalApi.open();\n}\n\nfunction start() {\n  if (props.checkUpdatesInterval <= 0) {\n    return;\n  }\n\n  // 每 checkUpdatesInterval(默认值为1) 分钟检查一次\n  timer.value = setInterval(\n    checkForUpdates,\n    props.checkUpdatesInterval * 60 * 1000,\n  );\n}\n\nfunction handleVisibilitychange() {\n  if (document.hidden) {\n    stop();\n  } else {\n    if (!isCheckingUpdates) {\n      isCheckingUpdates = true;\n      checkForUpdates().finally(() => {\n        isCheckingUpdates = false;\n        start();\n      });\n    }\n  }\n}\n\nfunction stop() {\n  clearInterval(timer.value);\n}\n\nonMounted(() => {\n  start();\n  document.addEventListener('visibilitychange', handleVisibilitychange);\n});\n\nonUnmounted(() => {\n  stop();\n  document.removeEventListener('visibilitychange', handleVisibilitychange);\n});\n</script>\n<template>\n  <UpdateNoticeModal\n    :cancel-text=\"$t('common.cancel')\"\n    :confirm-text=\"$t('common.refresh')\"\n    :fullscreen-button=\"false\"\n    :title=\"$t('ui.widgets.checkUpdatesTitle')\"\n    centered\n    content-class=\"px-8 min-h-10\"\n    footer-class=\"border-none mb-3 mr-3\"\n    header-class=\"border-none\"\n  >\n    {{ $t('ui.widgets.checkUpdatesDescription') }}\n  </UpdateNoticeModal>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/check-updates/index.ts",
    "content": "export { default as CheckUpdates } from './check-updates.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/color-toggle.vue",
    "content": "<script setup lang=\"ts\">\nimport type { BuiltinThemeType } from '@vben/types';\n\nimport { Palette } from '@vben/icons';\nimport {\n  COLOR_PRESETS,\n  preferences,\n  updatePreferences,\n} from '@vben/preferences';\n\nimport { VbenIconButton } from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'AuthenticationColorToggle',\n});\n\nfunction handleUpdate(colorPrimary: string, type: BuiltinThemeType) {\n  updatePreferences({\n    theme: {\n      colorPrimary,\n      builtinType: type,\n    },\n  });\n}\n</script>\n\n<template>\n  <div class=\"group relative flex items-center overflow-hidden\">\n    <div\n      class=\"flex w-0 overflow-hidden transition-all duration-500 ease-out group-hover:w-60\"\n    >\n      <template v-for=\"preset in COLOR_PRESETS\" :key=\"preset.color\">\n        <VbenIconButton\n          class=\"flex-center shrink-0\"\n          @click=\"handleUpdate(preset.color, preset.type)\"\n        >\n          <div\n            :style=\"{ backgroundColor: preset.color }\"\n            class=\"relative flex-center size-5 rounded-full hover:scale-110\"\n          >\n            <svg\n              v-if=\"preferences.theme.builtinType === preset.type\"\n              class=\"size-3.5 text-white\"\n              height=\"1em\"\n              viewBox=\"0 0 15 15\"\n              width=\"1em\"\n            >\n              <path\n                clip-rule=\"evenodd\"\n                d=\"M11.467 3.727c.289.189.37.576.181.865l-4.25 6.5a.625.625 0 0 1-.944.12l-2.75-2.5a.625.625 0 0 1 .841-.925l2.208 2.007l3.849-5.886a.625.625 0 0 1 .865-.181\"\n                fill=\"currentColor\"\n                fill-rule=\"evenodd\"\n              />\n            </svg>\n          </div>\n        </VbenIconButton>\n      </template>\n    </div>\n\n    <VbenIconButton>\n      <Palette class=\"size-4 text-primary\" />\n    </VbenIconButton>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/global-search/global-search.vue",
    "content": "<script setup lang=\"ts\">\nimport type { MenuRecordRaw } from '@vben/types';\n\nimport { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';\n\nimport {\n  ArrowDown,\n  ArrowUp,\n  CornerDownLeft,\n  MdiKeyboardEsc,\n  Search,\n} from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport { isWindowsOs } from '@vben/utils';\n\nimport { useVbenModal } from '@vben-core/popup-ui';\n\nimport { useMagicKeys, whenever } from '@vueuse/core';\n\nimport SearchPanel from './search-panel.vue';\n\ndefineOptions({\n  name: 'GlobalSearch',\n});\n\nconst props = withDefaults(\n  defineProps<{ enableShortcutKey?: boolean; menus?: MenuRecordRaw[] }>(),\n  {\n    enableShortcutKey: true,\n    menus: () => [],\n  },\n);\n\nconst keyword = ref('');\nconst searchInputRef = ref<HTMLInputElement>();\n\nconst [Modal, modalApi] = useVbenModal({\n  onCancel() {\n    modalApi.close();\n  },\n  onOpenChange(isOpen: boolean) {\n    if (!isOpen) {\n      keyword.value = '';\n    }\n  },\n});\nconst open = modalApi.useStore((state) => state.isOpen);\n\nfunction handleClose() {\n  modalApi.close();\n  keyword.value = '';\n}\n\nconst keys = useMagicKeys();\nconst cmd = isWindowsOs() ? keys['ctrl+k'] : keys['cmd+k'];\nif (cmd) {\n  whenever(cmd, () => {\n    if (props.enableShortcutKey) {\n      modalApi.open();\n    }\n  });\n}\n\nwhenever(open, () => {\n  nextTick(() => {\n    searchInputRef.value?.focus();\n  });\n});\n\nconst preventDefaultBrowserSearchHotKey = (event: KeyboardEvent) => {\n  if (event.key?.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {\n    event.preventDefault();\n  }\n};\n\nconst toggleKeydownListener = () => {\n  if (props.enableShortcutKey) {\n    window.addEventListener('keydown', preventDefaultBrowserSearchHotKey);\n  } else {\n    window.removeEventListener('keydown', preventDefaultBrowserSearchHotKey);\n  }\n};\n\nconst toggleOpen = () => {\n  open.value ? modalApi.close() : modalApi.open();\n};\n\nwatch(() => props.enableShortcutKey, toggleKeydownListener);\n\nonMounted(() => {\n  toggleKeydownListener();\n\n  onUnmounted(() => {\n    window.removeEventListener('keydown', preventDefaultBrowserSearchHotKey);\n  });\n});\n</script>\n\n<template>\n  <div>\n    <Modal\n      :fullscreen-button=\"false\"\n      class=\"w-150\"\n      header-class=\"py-2 border-b\"\n    >\n      <template #title>\n        <div class=\"flex items-center\">\n          <Search class=\"mr-2 size-4 text-muted-foreground\" />\n          <input\n            ref=\"searchInputRef\"\n            v-model=\"keyword\"\n            :placeholder=\"$t('ui.widgets.search.searchNavigate')\"\n            class=\"ring-none w-[80%] rounded-md border border-none bg-transparent p-2 pl-0 text-sm font-normal ring-0 ring-offset-transparent outline-hidden placeholder:text-muted-foreground focus-visible:ring-transparent\"\n          />\n        </div>\n      </template>\n\n      <SearchPanel :keyword=\"keyword\" :menus=\"menus\" @close=\"handleClose\" />\n      <template #footer>\n        <div class=\"flex w-full justify-start text-xs\">\n          <div class=\"mr-2 flex items-center\">\n            <CornerDownLeft class=\"mr-1 size-3\" />\n            {{ $t('ui.widgets.search.select') }}\n          </div>\n          <div class=\"mr-2 flex items-center\">\n            <ArrowUp class=\"mr-1 size-3\" />\n            <ArrowDown class=\"mr-1 size-3\" />\n            {{ $t('ui.widgets.search.navigate') }}\n          </div>\n          <div class=\"flex items-center\">\n            <MdiKeyboardEsc class=\"mr-1 size-3\" />\n            {{ $t('ui.widgets.search.close') }}\n          </div>\n        </div>\n      </template>\n    </Modal>\n    <div\n      class=\"group flex h-8 cursor-pointer items-center gap-3 rounded-2xl border-none bg-none px-2 py-0.5 outline-hidden md:bg-accent\"\n      @click=\"toggleOpen()\"\n    >\n      <Search\n        class=\"size-4 text-muted-foreground group-hover:text-foreground group-hover:opacity-100\"\n      />\n      <span\n        class=\"hidden text-xs text-muted-foreground duration-300 group-hover:text-foreground md:block\"\n      >\n        {{ $t('ui.widgets.search.title') }}\n      </span>\n      <span\n        v-if=\"enableShortcutKey\"\n        class=\"relative hidden rounded-sm rounded-r-xl border-foreground/60 bg-background px-1.5 py-1 text-xs leading-none text-muted-foreground group-hover:text-foreground group-hover:opacity-100 md:block\"\n      >\n        {{ isWindowsOs() ? 'Ctrl' : '⌘' }}\n        <kbd>K</kbd>\n      </span>\n      <span v-else></span>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/global-search/index.ts",
    "content": "export { default as GlobalSearch } from './global-search.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/global-search/search-panel.vue",
    "content": "<script setup lang=\"ts\">\nimport type { MenuRecordRaw } from '@vben/types';\n\nimport { nextTick, onMounted, ref, shallowRef, watch } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { SearchX, X } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport { mapTree, traverseTreeValues, uniqueByField } from '@vben/utils';\n\nimport { VbenIcon, VbenScrollbar } from '@vben-core/shadcn-ui';\nimport { isHttpUrl } from '@vben-core/shared/utils';\n\nimport { onKeyStroke, useLocalStorage, useThrottleFn } from '@vueuse/core';\n\ndefineOptions({\n  name: 'SearchPanel',\n});\n\nconst props = withDefaults(\n  defineProps<{ keyword?: string; menus?: MenuRecordRaw[] }>(),\n  {\n    keyword: '',\n    menus: () => [],\n  },\n);\nconst emit = defineEmits<{ close: [] }>();\n\nconst router = useRouter();\nconst searchHistory = useLocalStorage<MenuRecordRaw[]>(\n  `__search-history-${location.hostname}__`,\n  [],\n);\nconst activeIndex = ref(-1);\nconst searchItems = shallowRef<MenuRecordRaw[]>([]);\nconst searchResults = ref<MenuRecordRaw[]>([]);\n\nconst handleSearch = useThrottleFn(search, 200);\n\n// 搜索函数，用于根据搜索关键词查找匹配的菜单项\nfunction search(searchKey: string) {\n  // 去除搜索关键词的前后空格\n  searchKey = searchKey.trim();\n\n  // 如果搜索关键词为空，清空搜索结果并返回\n  if (!searchKey) {\n    searchResults.value = [];\n    return;\n  }\n\n  // 使用搜索关键词创建正则表达式\n  const reg = createSearchReg(searchKey);\n\n  // 初始化结果数组\n  const results: MenuRecordRaw[] = [];\n\n  // 遍历搜索项\n  traverseTreeValues(searchItems.value, (item) => {\n    // 如果菜单项的名称匹配正则表达式，将其添加到结果数组中\n    if (reg.test(item.name?.toLowerCase())) {\n      results.push(item);\n    }\n  });\n\n  // 更新搜索结果\n  searchResults.value = results;\n\n  // 如果有搜索结果，设置索引为 0\n  if (results.length > 0) {\n    activeIndex.value = 0;\n  }\n\n  // 赋值索引为 0\n  activeIndex.value = 0;\n}\n\n// When the keyboard up and down keys move to an invisible place\n// the scroll bar needs to scroll automatically\nfunction scrollIntoView() {\n  const element = document.querySelector(\n    `[data-search-item=\"${activeIndex.value}\"]`,\n  );\n\n  if (element) {\n    element.scrollIntoView({ block: 'nearest' });\n  }\n}\n\n// enter keyboard event\nasync function handleEnter() {\n  if (searchResults.value.length === 0) {\n    return;\n  }\n  const result = searchResults.value;\n  const index = activeIndex.value;\n  if (result.length === 0 || index < 0) {\n    return;\n  }\n  const to = result[index];\n  if (to) {\n    searchHistory.value = uniqueByField([...searchHistory.value, to], 'path');\n    handleClose();\n    await nextTick();\n    if (isHttpUrl(to.path)) {\n      window.open(to.path, '_blank');\n    } else {\n      router.push({ path: to.path, replace: true });\n    }\n  }\n}\n\n// Arrow key up\nfunction handleUp() {\n  if (searchResults.value.length === 0) {\n    return;\n  }\n  activeIndex.value--;\n  if (activeIndex.value < 0) {\n    activeIndex.value = searchResults.value.length - 1;\n  }\n  scrollIntoView();\n}\n\n// Arrow key down\nfunction handleDown() {\n  if (searchResults.value.length === 0) {\n    return;\n  }\n  activeIndex.value++;\n  if (activeIndex.value > searchResults.value.length - 1) {\n    activeIndex.value = 0;\n  }\n  scrollIntoView();\n}\n\n// close search modal\nfunction handleClose() {\n  searchResults.value = [];\n  emit('close');\n}\n\n// Activate when the mouse moves to a certain line\nfunction handleMouseenter(e: MouseEvent) {\n  const index = (e.target as HTMLElement)?.dataset.index;\n  activeIndex.value = Number(index);\n}\n\nfunction removeItem(index: number) {\n  if (props.keyword) {\n    searchResults.value.splice(index, 1);\n  } else {\n    searchHistory.value.splice(index, 1);\n  }\n  activeIndex.value = Math.max(activeIndex.value - 1, 0);\n  scrollIntoView();\n}\n\n// 存储所有需要转义的特殊字符\nconst code = new Set([\n  '$',\n  '(',\n  ')',\n  '*',\n  '+',\n  '.',\n  '?',\n  '[',\n  '\\\\',\n  ']',\n  '^',\n  '{',\n  '|',\n  '}',\n]);\n\n// 转换函数，用于转义特殊字符\nfunction transform(c: string) {\n  // 如果字符在特殊字符列表中，返回转义后的字符\n  // 如果不在，返回字符本身\n  return code.has(c) ? `\\\\${c}` : c;\n}\n\n// 创建搜索正则表达式\nfunction createSearchReg(key: string) {\n  // 将输入的字符串拆分为单个字符\n  // 对每个字符进行转义\n  // 然后用'.*'连接所有字符，创建正则表达式\n  const keys = [...key].map((item) => transform(item)).join('.*');\n  // 返回创建的正则表达式\n  return new RegExp(`.*${keys}.*`);\n}\n\nwatch(\n  () => props.keyword,\n  (val) => {\n    if (val) {\n      handleSearch(val);\n    } else {\n      searchResults.value = [...searchHistory.value];\n    }\n  },\n);\n\nonMounted(() => {\n  searchItems.value = mapTree(props.menus, (item) => {\n    return {\n      ...item,\n      name: $t(item?.name),\n    };\n  });\n  if (searchHistory.value.length > 0) {\n    searchResults.value = searchHistory.value;\n  }\n  // enter search\n  onKeyStroke('Enter', handleEnter);\n  // Monitor keyboard arrow keys\n  onKeyStroke('ArrowUp', handleUp);\n  onKeyStroke('ArrowDown', handleDown);\n  // esc close\n  onKeyStroke('Escape', handleClose);\n});\n</script>\n\n<template>\n  <VbenScrollbar>\n    <div class=\"flex! h-full justify-center px-2 sm:max-h-112.5\">\n      <!-- 无搜索结果 -->\n      <div\n        v-if=\"keyword && searchResults.length === 0\"\n        class=\"text-center text-muted-foreground\"\n      >\n        <SearchX class=\"mx-auto mt-4 size-12\" />\n        <p class=\"mt-6 mb-10 text-xs\">\n          {{ $t('ui.widgets.search.noResults') }}\n          <span class=\"text-sm font-medium text-foreground\">\n            \"{{ keyword }}\"\n          </span>\n        </p>\n      </div>\n      <!-- 历史搜索记录 & 没有搜索结果 -->\n      <div\n        v-if=\"!keyword && searchResults.length === 0\"\n        class=\"text-center text-muted-foreground\"\n      >\n        <p class=\"my-10 text-xs\">\n          {{ $t('ui.widgets.search.noRecent') }}\n        </p>\n      </div>\n\n      <ul v-show=\"searchResults.length > 0\" class=\"w-full\">\n        <li\n          v-if=\"searchHistory.length > 0 && !keyword\"\n          class=\"mb-2 text-xs text-muted-foreground\"\n        >\n          {{ $t('ui.widgets.search.recent') }}\n        </li>\n        <li\n          v-for=\"(item, index) in uniqueByField(searchResults, 'path')\"\n          :key=\"item.path\"\n          :class=\"\n            activeIndex === index\n              ? 'active bg-primary text-primary-foreground'\n              : ''\n          \"\n          :data-index=\"index\"\n          :data-search-item=\"index\"\n          class=\"group mb-3 flex-center w-full cursor-pointer rounded-lg bg-accent p-4\"\n          @click=\"handleEnter\"\n          @mouseenter=\"handleMouseenter\"\n        >\n          <VbenIcon :icon=\"item.icon\" class=\"mr-2 size-5 shrink-0\" fallback />\n\n          <span class=\"flex-1\">{{ item.name }}</span>\n          <div\n            class=\"flex-center rounded-full p-1 hover:scale-110 hover:text-primary-foreground dark:hover:bg-accent\"\n            @click.stop=\"removeItem(index)\"\n          >\n            <X class=\"size-4\" />\n          </div>\n        </li>\n      </ul>\n    </div>\n  </VbenScrollbar>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/index.ts",
    "content": "export { default as Breadcrumb } from './breadcrumb.vue';\nexport * from './check-updates';\nexport { default as AuthenticationColorToggle } from './color-toggle.vue';\nexport * from './global-search';\nexport { default as LanguageToggle } from './language-toggle.vue';\nexport { default as AuthenticationLayoutToggle } from './layout-toggle.vue';\nexport * from './lock-screen';\nexport * from './notification';\nexport * from './preferences';\nexport * from './theme-toggle';\nexport * from './timezone';\nexport * from './user-dropdown';\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/language-toggle.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SupportedLanguagesType } from '@vben/locales';\n\nimport { SUPPORT_LANGUAGES } from '@vben/constants';\nimport { Languages } from '@vben/icons';\nimport { loadLocaleMessages } from '@vben/locales';\nimport { preferences, updatePreferences } from '@vben/preferences';\n\nimport { VbenDropdownRadioMenu, VbenIconButton } from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'LanguageToggle',\n});\n\nasync function handleUpdate(value: string | undefined) {\n  if (!value) return;\n  const locale = value as SupportedLanguagesType;\n  updatePreferences({\n    app: {\n      locale,\n    },\n  });\n  await loadLocaleMessages(locale);\n}\n</script>\n\n<template>\n  <div>\n    <VbenDropdownRadioMenu\n      :menus=\"SUPPORT_LANGUAGES\"\n      :model-value=\"preferences.app.locale\"\n      @update:model-value=\"handleUpdate\"\n    >\n      <VbenIconButton class=\"hover:animate-[shrink_0.3s_ease-in-out]\">\n        <Languages class=\"size-4 text-foreground\" />\n      </VbenIconButton>\n    </VbenDropdownRadioMenu>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/layout-toggle.vue",
    "content": "<script setup lang=\"ts\">\nimport type { AuthPageLayoutType } from '@vben/types';\n\nimport type { VbenDropdownMenuItem } from '@vben-core/shadcn-ui';\n\nimport { computed } from 'vue';\n\nimport { InspectionPanel, PanelLeft, PanelRight } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport {\n  preferences,\n  updatePreferences,\n  usePreferences,\n} from '@vben/preferences';\n\nimport { VbenDropdownRadioMenu, VbenIconButton } from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'AuthenticationLayoutToggle',\n});\n\nconst menus = computed((): VbenDropdownMenuItem[] => [\n  {\n    icon: PanelLeft,\n    label: $t('authentication.layout.alignLeft'),\n    value: 'panel-left',\n  },\n  {\n    icon: InspectionPanel,\n    label: $t('authentication.layout.center'),\n    value: 'panel-center',\n  },\n  {\n    icon: PanelRight,\n    label: $t('authentication.layout.alignRight'),\n    value: 'panel-right',\n  },\n]);\n\nconst { authPanelCenter, authPanelLeft, authPanelRight } = usePreferences();\n\nfunction handleUpdate(value: string | undefined) {\n  if (!value) return;\n  updatePreferences({\n    app: {\n      authPageLayout: value as AuthPageLayoutType,\n    },\n  });\n}\n</script>\n\n<template>\n  <VbenDropdownRadioMenu\n    :menus=\"menus\"\n    :model-value=\"preferences.app.authPageLayout\"\n    @update:model-value=\"handleUpdate\"\n  >\n    <VbenIconButton>\n      <PanelRight v-if=\"authPanelRight\" class=\"size-4\" />\n      <PanelLeft v-if=\"authPanelLeft\" class=\"size-4\" />\n      <InspectionPanel v-if=\"authPanelCenter\" class=\"size-4\" />\n    </VbenIconButton>\n  </VbenDropdownRadioMenu>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/lock-screen/index.ts",
    "content": "export { default as LockScreenModal } from './lock-screen-modal.vue';\nexport { default as LockScreen } from './lock-screen.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/lock-screen/lock-screen-modal.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Recordable } from '@vben/types';\n\nimport { computed, reactive } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport { useVbenForm, z } from '@vben-core/form-ui';\nimport { useVbenModal } from '@vben-core/popup-ui';\nimport { VbenAvatar, VbenButton } from '@vben-core/shadcn-ui';\n\ninterface Props {\n  avatar?: string;\n  text?: string;\n}\n\ndefineOptions({\n  name: 'LockScreenModal',\n});\n\nwithDefaults(defineProps<Props>(), {\n  avatar: '',\n  text: '',\n});\n\nconst emit = defineEmits<{\n  submit: [Recordable<any>];\n}>();\n\nconst [Form, { resetForm, validate, getValues, getFieldComponentRef }] =\n  useVbenForm(\n    reactive({\n      commonConfig: {\n        hideLabel: true,\n        hideRequiredMark: true,\n      },\n      schema: computed(() => [\n        {\n          component: 'VbenInputPassword' as const,\n          componentProps: {\n            placeholder: $t('ui.widgets.lockScreen.placeholder'),\n          },\n          fieldName: 'lockScreenPassword',\n          formFieldProps: { validateOnBlur: false },\n          label: $t('authentication.password'),\n          rules: z\n            .string()\n            .min(1, { message: $t('ui.widgets.lockScreen.placeholder') }),\n        },\n      ]),\n      showDefaultActions: false,\n    }),\n  );\n\nconst [Modal] = useVbenModal({\n  onConfirm() {\n    handleSubmit();\n  },\n  onOpenChange(isOpen) {\n    if (isOpen) {\n      resetForm();\n    }\n  },\n  onOpened() {\n    requestAnimationFrame(() => {\n      getFieldComponentRef('lockScreenPassword')\n        ?.$el?.querySelector('[name=\"lockScreenPassword\"]')\n        ?.focus();\n    });\n  },\n});\n\nasync function handleSubmit() {\n  const { valid } = await validate();\n  const values = await getValues();\n  if (valid) {\n    emit('submit', values?.lockScreenPassword);\n  }\n}\n</script>\n\n<template>\n  <Modal\n    :footer=\"false\"\n    :fullscreen-button=\"false\"\n    :title=\"$t('ui.widgets.lockScreen.title')\"\n  >\n    <div\n      class=\"mb-10 flex w-full flex-col items-center px-10\"\n      @keydown.enter.prevent=\"handleSubmit\"\n    >\n      <div class=\"w-full\">\n        <div class=\"ml-2 flex w-full flex-col items-center\">\n          <VbenAvatar\n            :src=\"avatar\"\n            class=\"size-20\"\n            dot-class=\"bottom-0 right-1 border-2 size-4 bg-green-500\"\n          />\n          <div class=\"my-6 flex items-center font-medium text-foreground\">\n            {{ text }}\n          </div>\n        </div>\n        <Form />\n        <VbenButton class=\"mt-1 w-full\" @click=\"handleSubmit\">\n          {{ $t('ui.widgets.lockScreen.screenButton') }}\n        </VbenButton>\n      </div>\n    </div>\n  </Modal>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/lock-screen/lock-screen.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed, reactive, ref } from 'vue';\n\nimport { LockKeyhole } from '@vben/icons';\nimport { $t, useI18n } from '@vben/locales';\nimport { storeToRefs, useAccessStore } from '@vben/stores';\n\nimport { useScrollLock } from '@vben-core/composables';\nimport { useVbenForm, z } from '@vben-core/form-ui';\nimport { VbenAvatar, VbenButton } from '@vben-core/shadcn-ui';\n\nimport { useDateFormat, useNow } from '@vueuse/core';\n\ninterface Props {\n  avatar?: string;\n}\n\ndefineOptions({\n  name: 'LockScreen',\n});\n\nwithDefaults(defineProps<Props>(), {\n  avatar: '',\n});\n\ndefineEmits<{ toLogin: [] }>();\n\nconst { locale } = useI18n();\nconst accessStore = useAccessStore();\n\nconst now = useNow();\nconst meridiem = useDateFormat(now, 'A');\nconst hour = useDateFormat(now, 'HH');\nconst minute = useDateFormat(now, 'mm');\nconst date = useDateFormat(now, 'YYYY-MM-DD dddd', { locales: locale.value });\n\nconst showUnlockForm = ref(false);\nconst { lockScreenPassword } = storeToRefs(accessStore);\n\nconst [Form, { form, validate, getFieldComponentRef }] = useVbenForm(\n  reactive({\n    commonConfig: {\n      hideLabel: true,\n      hideRequiredMark: true,\n    },\n    schema: computed(() => [\n      {\n        component: 'VbenInputPassword' as const,\n        componentProps: {\n          placeholder: $t('ui.widgets.lockScreen.placeholder'),\n        },\n        fieldName: 'password',\n        label: $t('authentication.password'),\n        rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n      },\n    ]),\n    showDefaultActions: false,\n  }),\n);\n\nconst validPass = computed(\n  () => lockScreenPassword?.value === form?.values?.password,\n);\n\nasync function handleSubmit() {\n  const { valid } = await validate();\n  if (valid) {\n    if (validPass.value) {\n      accessStore.unlockScreen();\n    } else {\n      form.setFieldError('password', $t('authentication.passwordErrorTip'));\n    }\n  }\n}\n\nfunction toggleUnlockForm() {\n  showUnlockForm.value = !showUnlockForm.value;\n  if (showUnlockForm.value) {\n    requestAnimationFrame(() => {\n      getFieldComponentRef('password')\n        ?.$el?.querySelector('[name=\"password\"]')\n        ?.focus();\n    });\n  }\n}\n\nuseScrollLock();\n</script>\n\n<template>\n  <div class=\"fixed z-2000 size-full bg-background\">\n    <transition name=\"slide-left\">\n      <div v-show=\"!showUnlockForm\" class=\"size-full\">\n        <div\n          class=\"group fixed top-6 left-1/2 z-2001 flex-col-center -translate-x-1/2 cursor-pointer text-xl font-semibold text-foreground/80 hover:text-foreground\"\n          @click=\"toggleUnlockForm\"\n        >\n          <LockKeyhole\n            class=\"size-5 transition-all duration-300 group-hover:scale-125\"\n          />\n          <span>{{ $t('ui.widgets.lockScreen.unlock') }}</span>\n        </div>\n        <div class=\"flex-center size-full\">\n          <div class=\"flex w-full justify-center gap-4 px-4 sm:gap-6 md:gap-8\">\n            <div\n              class=\"relative flex-center h-35 w-35 rounded-xl bg-accent text-[36px] sm:h-40 sm:w-40 sm:text-[42px] md:h-50 md:w-50 md:text-[72px]\"\n            >\n              <span\n                class=\"absolute top-3 left-3 text-xs font-semibold sm:text-sm md:text-xl\"\n              >\n                {{ meridiem }}\n              </span>\n              {{ hour }}\n            </div>\n            <div\n              class=\"flex-center h-35 w-35 rounded-xl bg-accent text-[36px] sm:h-40 sm:w-40 sm:text-[42px] md:h-50 md:w-50 md:text-[72px]\"\n            >\n              {{ minute }}\n            </div>\n          </div>\n        </div>\n      </div>\n    </transition>\n\n    <transition name=\"slide-right\">\n      <div\n        v-if=\"showUnlockForm\"\n        class=\"flex-center size-full\"\n        @keydown.enter.prevent=\"handleSubmit\"\n      >\n        <div class=\"mb-10 flex-col-center w-[90%] max-w-75 px-4\">\n          <VbenAvatar :src=\"avatar\" class=\"enter-x mb-6 size-20\" />\n          <div class=\"enter-x mb-2 w-full items-center\">\n            <Form />\n          </div>\n          <VbenButton class=\"enter-x w-full\" @click=\"handleSubmit\">\n            {{ $t('ui.widgets.lockScreen.entry') }}\n          </VbenButton>\n          <VbenButton\n            class=\"enter-x my-2 w-full\"\n            variant=\"ghost\"\n            @click=\"$emit('toLogin')\"\n          >\n            {{ $t('ui.widgets.lockScreen.backToLogin') }}\n          </VbenButton>\n          <VbenButton\n            class=\"enter-x mr-2 w-full\"\n            variant=\"ghost\"\n            @click=\"toggleUnlockForm\"\n          >\n            {{ $t('common.back') }}\n          </VbenButton>\n        </div>\n      </div>\n    </transition>\n\n    <div\n      class=\"enter-y absolute bottom-5 w-full text-center text-xl md:text-2xl xl:text-xl 2xl:text-3xl\"\n    >\n      <div v-if=\"showUnlockForm\" class=\"enter-x mb-2 text-2xl md:text-3xl\">\n        {{ hour }}:{{ minute }}\n        <span class=\"text-base md:text-lg\">{{ meridiem }}</span>\n      </div>\n      <div class=\"text-xl md:text-3xl\">{{ date }}</div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/notification/index.ts",
    "content": "export { default as Notification } from './notification.vue';\n\nexport type * from './types';\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/notification/notification.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { NotificationItem } from './types';\n\nimport { useRouter } from 'vue-router';\n\nimport { Bell, CircleCheckBig, CircleX, MailCheck } from '@vben/icons';\nimport { $t } from '@vben/locales';\n\nimport {\n  VbenButton,\n  VbenIconButton,\n  VbenPopover,\n  VbenScrollbar,\n} from '@vben-core/shadcn-ui';\n\nimport { useToggle } from '@vueuse/core';\n\ninterface Props {\n  /**\n   * 显示圆点\n   */\n  dot?: boolean;\n  /**\n   * 消息列表\n   */\n  notifications?: NotificationItem[];\n}\n\ndefineOptions({ name: 'NotificationPopup' });\n\nwithDefaults(defineProps<Props>(), {\n  dot: false,\n  notifications: () => [],\n});\n\nconst emit = defineEmits<{\n  clear: [];\n  makeAll: [];\n  read: [NotificationItem];\n  remove: [NotificationItem];\n  viewAll: [];\n}>();\n\nconst router = useRouter();\nconst [open, toggle] = useToggle();\n\nfunction close() {\n  open.value = false;\n}\n\nfunction handleViewAll() {\n  emit('viewAll');\n  close();\n}\n\nfunction handleMakeAll() {\n  emit('makeAll');\n}\n\nfunction handleClear() {\n  emit('clear');\n}\n\nfunction handleClick(item: NotificationItem) {\n  // 如果通知项有链接，点击时跳转\n  if (item.link) {\n    navigateTo(item.link, item.query, item.state);\n  }\n}\n\nfunction navigateTo(\n  link: string,\n  query?: Record<string, any>,\n  state?: Record<string, any>,\n) {\n  if (link.startsWith('http://') || link.startsWith('https://')) {\n    // 外部链接，在新标签页打开\n    window.open(link, '_blank');\n  } else {\n    // 内部路由链接，支持 query 参数和 state\n    router.push({\n      path: link,\n      query: query || {},\n      state,\n    });\n  }\n}\n</script>\n<template>\n  <VbenPopover v-model:open=\"open\" content-class=\"relative right-2 w-90 p-0\">\n    <template #trigger>\n      <div class=\"mr-2 flex-center h-full\" @click.stop=\"toggle()\">\n        <VbenIconButton class=\"bell-button relative text-foreground\">\n          <span\n            v-if=\"dot\"\n            class=\"absolute top-0.5 right-0.5 size-2 rounded-sm bg-primary\"\n          ></span>\n          <Bell class=\"size-4\" />\n        </VbenIconButton>\n      </div>\n    </template>\n\n    <div class=\"relative\">\n      <div class=\"flex items-center justify-between p-4 py-3\">\n        <div class=\"text-foreground\">{{ $t('ui.widgets.notifications') }}</div>\n        <VbenIconButton\n          :disabled=\"notifications.length <= 0\"\n          :tooltip=\"$t('ui.widgets.markAllAsRead')\"\n          @click=\"handleMakeAll\"\n        >\n          <MailCheck class=\"size-4\" />\n        </VbenIconButton>\n      </div>\n      <VbenScrollbar v-if=\"notifications.length > 0\">\n        <ul class=\"flex! max-h-90 w-full flex-col\">\n          <template v-for=\"item in notifications\" :key=\"item.id ?? item.title\">\n            <li\n              class=\"relative flex w-full cursor-pointer items-start gap-5 border-t border-border p-3 hover:bg-accent\"\n              @click=\"handleClick(item)\"\n            >\n              <span\n                v-if=\"!item.isRead\"\n                class=\"absolute top-2 right-2 size-2 rounded-sm bg-primary\"\n              ></span>\n\n              <span\n                class=\"relative flex size-10 shrink-0 overflow-hidden rounded-full\"\n              >\n                <img\n                  :src=\"item.avatar\"\n                  class=\"aspect-square size-full object-cover\"\n                />\n              </span>\n              <div class=\"flex flex-col gap-1 leading-none\">\n                <p class=\"font-semibold\">{{ item.title }}</p>\n                <p class=\"my-1 line-clamp-2 text-xs text-muted-foreground\">\n                  {{ item.message }}\n                </p>\n                <p class=\"line-clamp-2 text-xs text-muted-foreground\">\n                  {{ item.date }}\n                </p>\n              </div>\n              <div\n                class=\"absolute top-1/2 right-3 flex -translate-y-1/2 flex-col gap-2\"\n              >\n                <VbenIconButton\n                  v-if=\"!item.isRead\"\n                  size=\"xs\"\n                  variant=\"ghost\"\n                  class=\"h-6 px-2\"\n                  :tooltip=\"$t('common.confirm')\"\n                  @click.stop=\"emit('read', item)\"\n                >\n                  <CircleCheckBig class=\"size-4\" />\n                </VbenIconButton>\n                <VbenIconButton\n                  v-if=\"item.isRead\"\n                  size=\"xs\"\n                  variant=\"ghost\"\n                  class=\"h-6 px-2 text-destructive\"\n                  :tooltip=\"$t('common.delete')\"\n                  @click.stop=\"emit('remove', item)\"\n                >\n                  <CircleX class=\"size-4\" />\n                </VbenIconButton>\n              </div>\n            </li>\n          </template>\n        </ul>\n      </VbenScrollbar>\n\n      <template v-else>\n        <div class=\"flex-center min-h-37.5 w-full text-muted-foreground\">\n          {{ $t('common.noData') }}\n        </div>\n      </template>\n\n      <div\n        class=\"flex items-center justify-between border-t border-border px-4 py-3\"\n      >\n        <VbenButton\n          :disabled=\"notifications.length <= 0\"\n          size=\"sm\"\n          variant=\"ghost\"\n          @click=\"handleClear\"\n        >\n          {{ $t('ui.widgets.clearNotifications') }}\n        </VbenButton>\n        <VbenButton size=\"sm\" @click=\"handleViewAll\">\n          {{ $t('ui.widgets.viewAll') }}\n        </VbenButton>\n      </div>\n    </div>\n  </VbenPopover>\n</template>\n\n<style scoped>\n:deep(.bell-button) {\n  &:hover {\n    svg {\n      animation: bell-ring 1s both;\n    }\n  }\n}\n\n@keyframes bell-ring {\n  0%,\n  100% {\n    transform-origin: top;\n  }\n\n  15% {\n    transform: rotateZ(10deg);\n  }\n\n  30% {\n    transform: rotateZ(-10deg);\n  }\n\n  45% {\n    transform: rotateZ(5deg);\n  }\n\n  60% {\n    transform: rotateZ(-5deg);\n  }\n\n  75% {\n    transform: rotateZ(2deg);\n  }\n}\n</style>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/notification/types.ts",
    "content": "interface NotificationItem {\n  id: number | string;\n  avatar: string;\n  date: string;\n  isRead?: boolean;\n  message: string;\n  title: string;\n  /**\n   * 跳转链接，可以是路由路径或完整 URL\n   * @example '/dashboard' 或 'https://example.com'\n   */\n  link?: string;\n  query?: Record<string, any>;\n  state?: Record<string, any>;\n}\n\nexport type { NotificationItem };\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/block.vue",
    "content": "<script setup lang=\"ts\">\ninterface Props {\n  title?: string;\n}\n\ndefineOptions({\n  name: 'PreferenceBlock',\n});\n\nwithDefaults(defineProps<Props>(), {\n  title: '',\n});\n</script>\n\n<template>\n  <div class=\"flex flex-col py-4\">\n    <h3 class=\"mb-3 leading-none font-semibold tracking-tight\">\n      {{ title }}\n    </h3>\n    <slot></slot>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/checkbox-item.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectOption } from '@vben/types';\n\nimport { useSlots } from 'vue';\n\nimport { CircleHelp } from '@vben/icons';\n\nimport { VbenCheckButtonGroup, VbenTooltip } from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'PreferenceCheckboxItem',\n});\n\nwithDefaults(\n  defineProps<{\n    disabled?: boolean;\n    items?: SelectOption[];\n    multiple?: boolean;\n    onBtnClick?: (value: string) => void;\n    placeholder?: string;\n  }>(),\n  {\n    disabled: false,\n    placeholder: '',\n    items: () => [],\n    onBtnClick: () => {},\n    multiple: false,\n  },\n);\n\nconst inputValue = defineModel<string[]>();\n\nconst slots = useSlots();\n</script>\n\n<template>\n  <div\n    :class=\"{\n      'hover:bg-accent': !slots.tip,\n      'pointer-events-none opacity-50': disabled,\n    }\"\n    class=\"my-1 flex w-full items-center justify-between rounded-md px-2 py-1\"\n  >\n    <span class=\"flex items-center text-sm\">\n      <slot></slot>\n\n      <VbenTooltip v-if=\"slots.tip\" side=\"bottom\">\n        <template #trigger>\n          <CircleHelp class=\"ml-1 size-3 cursor-help\" />\n        </template>\n        <slot name=\"tip\"></slot>\n      </VbenTooltip>\n    </span>\n    <VbenCheckButtonGroup\n      v-model=\"inputValue\"\n      class=\"h-8 w-41.25\"\n      :options=\"items\"\n      :disabled=\"disabled\"\n      :multiple=\"multiple\"\n      @btn-click=\"onBtnClick\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/general/animation.vue",
    "content": "<script setup lang=\"ts\">\nimport { $t } from '@vben/locales';\n\nimport SwitchItem from '../switch-item.vue';\n\ndefineOptions({\n  name: 'PreferenceAnimation',\n});\n\nconst transitionProgress = defineModel<boolean>('transitionProgress', {\n  // 默认值\n  default: false,\n});\nconst transitionName = defineModel<string>('transitionName');\nconst transitionEnable = defineModel<boolean>('transitionEnable');\nconst transitionLoading = defineModel<boolean>('transitionLoading');\n\nconst transitionPreset = ['fade', 'fade-slide', 'fade-up', 'fade-down'];\n\nfunction handleClick(value: string) {\n  transitionName.value = value;\n}\n</script>\n\n<template>\n  <SwitchItem v-model=\"transitionProgress\">\n    {{ $t('preferences.animation.progress') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"transitionLoading\">\n    {{ $t('preferences.animation.loading') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"transitionEnable\">\n    {{ $t('preferences.animation.transition') }}\n  </SwitchItem>\n  <div\n    v-if=\"transitionEnable\"\n    class=\"mt-3 mb-2 flex justify-between gap-3 px-2\"\n  >\n    <div\n      v-for=\"item in transitionPreset\"\n      :key=\"item\"\n      :class=\"{\n        'outline-box-active': transitionName === item,\n      }\"\n      class=\"outline-box p-2\"\n      @click=\"handleClick(item)\"\n    >\n      <div :class=\"`${item}-slow`\" class=\"h-10 w-12 rounded-md bg-accent\"></div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/general/general.vue",
    "content": "<script setup lang=\"ts\">\nimport { SUPPORT_LANGUAGES } from '@vben/constants';\nimport { $t } from '@vben/locales';\n\nimport InputItem from '../input-item.vue';\nimport SelectItem from '../select-item.vue';\nimport SwitchItem from '../switch-item.vue';\n\ndefineOptions({\n  name: 'PreferenceGeneralConfig',\n});\n\nconst appLocale = defineModel<string>('appLocale');\nconst appDynamicTitle = defineModel<boolean>('appDynamicTitle');\nconst appWatermark = defineModel<boolean>('appWatermark');\nconst appWatermarkContent = defineModel<string>('appWatermarkContent');\nconst appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');\nconst appEnableCopyPreferences = defineModel<boolean>(\n  'appEnableCopyPreferences',\n);\n</script>\n\n<template>\n  <SelectItem v-model=\"appLocale\" :items=\"SUPPORT_LANGUAGES\">\n    {{ $t('preferences.language') }}\n  </SelectItem>\n  <SwitchItem v-model=\"appDynamicTitle\">\n    {{ $t('preferences.dynamicTitle') }}\n  </SwitchItem>\n  <SwitchItem\n    v-model=\"appWatermark\"\n    @update:model-value=\"\n      (val) => {\n        if (!val) appWatermarkContent = '';\n      }\n    \"\n  >\n    {{ $t('preferences.watermark') }}\n  </SwitchItem>\n  <InputItem\n    v-if=\"appWatermark\"\n    v-model=\"appWatermarkContent\"\n    :placeholder=\"$t('preferences.watermarkContent')\"\n  >\n    {{ $t('preferences.watermarkContent') }}\n  </InputItem>\n  <SwitchItem v-model=\"appEnableCheckUpdates\">\n    {{ $t('preferences.checkUpdates') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"appEnableCopyPreferences\">\n    {{ $t('preferences.enableCopyPreferences') }}\n  </SwitchItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/index.ts",
    "content": "export { default as Block } from './block.vue';\nexport { default as Animation } from './general/animation.vue';\nexport { default as General } from './general/general.vue';\nexport { default as Breadcrumb } from './layout/breadcrumb.vue';\nexport { default as Content } from './layout/content.vue';\nexport { default as Copyright } from './layout/copyright.vue';\nexport { default as Footer } from './layout/footer.vue';\nexport { default as Header } from './layout/header.vue';\nexport { default as Layout } from './layout/layout.vue';\nexport { default as Navigation } from './layout/navigation.vue';\nexport { default as Sidebar } from './layout/sidebar.vue';\nexport { default as Tabbar } from './layout/tabbar.vue';\nexport { default as Widget } from './layout/widget.vue';\nexport { default as GlobalShortcutKeys } from './shortcut-keys/global.vue';\nexport { default as SwitchItem } from './switch-item.vue';\nexport { default as BuiltinTheme } from './theme/builtin.vue';\nexport { default as ColorMode } from './theme/color-mode.vue';\nexport { default as FontSize } from './theme/font-size.vue';\nexport { default as Radius } from './theme/radius.vue';\nexport { default as Theme } from './theme/theme.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/input-item.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectOption } from '@vben/types';\n\nimport { useSlots } from 'vue';\n\nimport { CircleHelp, CircleX } from '@vben/icons';\n\nimport { Input, VbenTooltip } from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'PreferenceSelectItem',\n});\n\nwithDefaults(\n  defineProps<{\n    disabled?: boolean;\n    items?: SelectOption[];\n    placeholder?: string;\n  }>(),\n  {\n    disabled: false,\n    placeholder: '',\n    items: () => [],\n  },\n);\n\nconst inputValue = defineModel<string>();\n\nconst slots = useSlots();\n</script>\n\n<template>\n  <div\n    :class=\"{\n      'hover:bg-accent': !slots.tip,\n      'pointer-events-none opacity-50': disabled,\n    }\"\n    class=\"my-1 flex w-full items-center justify-between rounded-md px-2 py-1\"\n  >\n    <span class=\"flex items-center text-sm\">\n      <slot></slot>\n\n      <VbenTooltip v-if=\"slots.tip\" side=\"bottom\">\n        <template #trigger>\n          <CircleHelp class=\"ml-1 size-3 cursor-help\" />\n        </template>\n        <slot name=\"tip\"></slot>\n      </VbenTooltip>\n    </span>\n    <div class=\"relative\">\n      <Input\n        v-model=\"inputValue\"\n        class=\"h-8 w-41.25\"\n        :placeholder=\"placeholder\"\n      />\n      <CircleX\n        v-if=\"inputValue\"\n        class=\"absolute top-1/2 right-2 size-3 -translate-y-1/2 transform cursor-pointer text-foreground/60 hover:text-foreground\"\n        @click=\"() => (inputValue = '')\"\n      />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/layout/breadcrumb.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectOption } from '@vben/types';\n\nimport { computed } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport SwitchItem from '../switch-item.vue';\nimport ToggleItem from '../toggle-item.vue';\n\ndefineOptions({\n  name: 'PreferenceBreadcrumbConfig',\n});\n\nconst props = defineProps<{ disabled?: boolean }>();\n\nconst breadcrumbEnable = defineModel<boolean>('breadcrumbEnable');\nconst breadcrumbShowIcon = defineModel<boolean>('breadcrumbShowIcon');\nconst breadcrumbStyleType = defineModel<string>('breadcrumbStyleType');\nconst breadcrumbShowHome = defineModel<boolean>('breadcrumbShowHome');\nconst breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');\n\nconst typeItems: SelectOption[] = [\n  { label: $t('preferences.normal'), value: 'normal' },\n  { label: $t('preferences.breadcrumb.background'), value: 'background' },\n];\n\nconst disableItem = computed(() => {\n  return !breadcrumbEnable.value || props.disabled;\n});\n</script>\n\n<template>\n  <SwitchItem v-model=\"breadcrumbEnable\" :disabled=\"disabled\">\n    {{ $t('preferences.breadcrumb.enable') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"breadcrumbHideOnlyOne\" :disabled=\"disableItem\">\n    {{ $t('preferences.breadcrumb.hideOnlyOne') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"breadcrumbShowIcon\" :disabled=\"disableItem\">\n    {{ $t('preferences.breadcrumb.icon') }}\n  </SwitchItem>\n  <SwitchItem\n    v-model=\"breadcrumbShowHome\"\n    :disabled=\"disableItem || !breadcrumbShowIcon\"\n  >\n    {{ $t('preferences.breadcrumb.home') }}\n  </SwitchItem>\n  <ToggleItem\n    v-model=\"breadcrumbStyleType\"\n    :disabled=\"disableItem\"\n    :items=\"typeItems\"\n  >\n    {{ $t('preferences.breadcrumb.style') }}\n  </ToggleItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/layout/content.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Component } from 'vue';\n\nimport { computed } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport { ContentCompact, ContentWide } from '../../icons';\n\ndefineOptions({\n  name: 'PreferenceLayoutContent',\n});\n\nconst modelValue = defineModel<string>({ default: 'wide' });\n\nconst components: Record<string, Component> = {\n  compact: ContentCompact,\n  wide: ContentWide,\n};\n\nconst PRESET = computed(() => [\n  {\n    name: $t('preferences.wide'),\n    type: 'wide',\n  },\n  {\n    name: $t('preferences.compact'),\n    type: 'compact',\n  },\n]);\n\nfunction activeClass(theme: string): string[] {\n  return theme === modelValue.value ? ['outline-box-active'] : [];\n}\n</script>\n\n<template>\n  <div class=\"flex w-full gap-5\">\n    <template v-for=\"theme in PRESET\" :key=\"theme.name\">\n      <div\n        class=\"flex w-25 cursor-pointer flex-col\"\n        @click=\"modelValue = theme.type\"\n      >\n        <div :class=\"activeClass(theme.type)\" class=\"outline-box flex-center\">\n          <component :is=\"components[theme.type]\" />\n        </div>\n        <div class=\"mt-2 text-center text-xs text-muted-foreground\">\n          {{ theme.name }}\n        </div>\n      </div>\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/layout/copyright.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport InputItem from '../input-item.vue';\nimport SwitchItem from '../switch-item.vue';\n\nconst props = defineProps<{ disabled: boolean }>();\n\nconst copyrightEnable = defineModel<boolean>('copyrightEnable');\nconst copyrightDate = defineModel<string>('copyrightDate');\nconst copyrightIcp = defineModel<string>('copyrightIcp');\nconst copyrightIcpLink = defineModel<string>('copyrightIcpLink');\nconst copyrightCompanyName = defineModel<string>('copyrightCompanyName');\nconst copyrightCompanySiteLink = defineModel<string>(\n  'copyrightCompanySiteLink',\n);\n\nconst itemDisabled = computed(() => props.disabled || !copyrightEnable.value);\n</script>\n\n<template>\n  <SwitchItem v-model=\"copyrightEnable\" :disabled=\"disabled\">\n    {{ $t('preferences.copyright.enable') }}\n  </SwitchItem>\n\n  <InputItem v-model=\"copyrightCompanyName\" :disabled=\"itemDisabled\">\n    {{ $t('preferences.copyright.companyName') }}\n  </InputItem>\n  <InputItem v-model=\"copyrightCompanySiteLink\" :disabled=\"itemDisabled\">\n    {{ $t('preferences.copyright.companySiteLink') }}\n  </InputItem>\n  <InputItem v-model=\"copyrightDate\" :disabled=\"itemDisabled\">\n    {{ $t('preferences.copyright.date') }}\n  </InputItem>\n\n  <InputItem v-model=\"copyrightIcp\" :disabled=\"itemDisabled\">\n    {{ $t('preferences.copyright.icp') }}\n  </InputItem>\n  <InputItem v-model=\"copyrightIcpLink\" :disabled=\"itemDisabled\">\n    {{ $t('preferences.copyright.icpLink') }}\n  </InputItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/layout/footer.vue",
    "content": "<script setup lang=\"ts\">\nimport { $t } from '@vben/locales';\n\nimport SwitchItem from '../switch-item.vue';\n\nconst footerEnable = defineModel<boolean>('footerEnable');\nconst footerFixed = defineModel<boolean>('footerFixed');\n</script>\n\n<template>\n  <SwitchItem v-model=\"footerEnable\">\n    {{ $t('preferences.footer.visible') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"footerFixed\" :disabled=\"!footerEnable\">\n    {{ $t('preferences.footer.fixed') }}\n  </SwitchItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/layout/header.vue",
    "content": "<script setup lang=\"ts\">\nimport type {\n  LayoutHeaderMenuAlignType,\n  LayoutHeaderModeType,\n  SelectOption,\n} from '@vben/types';\n\nimport { $t } from '@vben/locales';\n\nimport SelectItem from '../select-item.vue';\nimport SwitchItem from '../switch-item.vue';\nimport ToggleItem from '../toggle-item.vue';\n\ndefineProps<{ disabled: boolean }>();\n\nconst headerEnable = defineModel<boolean>('headerEnable');\nconst headerMode = defineModel<LayoutHeaderModeType>('headerMode');\nconst headerMenuAlign =\n  defineModel<LayoutHeaderMenuAlignType>('headerMenuAlign');\n\nconst localeItems: SelectOption[] = [\n  {\n    label: $t('preferences.header.modeStatic'),\n    value: 'static',\n  },\n  {\n    label: $t('preferences.header.modeFixed'),\n    value: 'fixed',\n  },\n  {\n    label: $t('preferences.header.modeAuto'),\n    value: 'auto',\n  },\n  {\n    label: $t('preferences.header.modeAutoScroll'),\n    value: 'auto-scroll',\n  },\n];\n\nconst headerMenuAlignItems: SelectOption[] = [\n  {\n    label: $t('preferences.header.menuAlignStart'),\n    value: 'start',\n  },\n  {\n    label: $t('preferences.header.menuAlignCenter'),\n    value: 'center',\n  },\n  {\n    label: $t('preferences.header.menuAlignEnd'),\n    value: 'end',\n  },\n];\n</script>\n\n<template>\n  <SwitchItem v-model=\"headerEnable\" :disabled=\"disabled\">\n    {{ $t('preferences.header.visible') }}\n  </SwitchItem>\n  <SelectItem\n    v-model=\"headerMode\"\n    :disabled=\"!headerEnable\"\n    :items=\"localeItems\"\n  >\n    {{ $t('preferences.mode') }}\n  </SelectItem>\n  <ToggleItem\n    v-model=\"headerMenuAlign\"\n    :disabled=\"!headerEnable\"\n    :items=\"headerMenuAlignItems\"\n  >\n    {{ $t('preferences.header.menuAlign') }}\n  </ToggleItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/layout/layout.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Component } from 'vue';\n\nimport type { LayoutType } from '@vben/types';\n\nimport { computed } from 'vue';\n\nimport { CircleHelp } from '@vben/icons';\nimport { $t } from '@vben/locales';\n\nimport { VbenTooltip } from '@vben-core/shadcn-ui';\n\nimport {\n  FullContent,\n  HeaderMixedNav,\n  HeaderNav,\n  HeaderSidebarNav,\n  MixedNav,\n  SidebarMixedNav,\n  SidebarNav,\n} from '../../icons';\n\ninterface PresetItem {\n  name: string;\n  tip: string;\n  type: LayoutType;\n}\n\ndefineOptions({\n  name: 'PreferenceLayout',\n});\n\nconst modelValue = defineModel<LayoutType>({ default: 'sidebar-nav' });\n\nconst components: Record<LayoutType, Component> = {\n  'full-content': FullContent,\n  'header-nav': HeaderNav,\n  'mixed-nav': MixedNav,\n  'sidebar-mixed-nav': SidebarMixedNav,\n  'sidebar-nav': SidebarNav,\n  'header-mixed-nav': HeaderMixedNav,\n  'header-sidebar-nav': HeaderSidebarNav,\n};\n\nconst PRESET = computed((): PresetItem[] => [\n  {\n    name: $t('preferences.vertical'),\n    tip: $t('preferences.verticalTip'),\n    type: 'sidebar-nav',\n  },\n  {\n    name: $t('preferences.twoColumn'),\n    tip: $t('preferences.twoColumnTip'),\n    type: 'sidebar-mixed-nav',\n  },\n  {\n    name: $t('preferences.horizontal'),\n    tip: $t('preferences.horizontalTip'),\n    type: 'header-nav',\n  },\n  {\n    name: $t('preferences.headerSidebarNav'),\n    tip: $t('preferences.headerSidebarNavTip'),\n    type: 'header-sidebar-nav',\n  },\n  {\n    name: $t('preferences.mixedMenu'),\n    tip: $t('preferences.mixedMenuTip'),\n    type: 'mixed-nav',\n  },\n  {\n    name: $t('preferences.headerTwoColumn'),\n    tip: $t('preferences.headerTwoColumnTip'),\n    type: 'header-mixed-nav',\n  },\n  {\n    name: $t('preferences.fullContent'),\n    tip: $t('preferences.fullContentTip'),\n    type: 'full-content',\n  },\n]);\n\nfunction activeClass(theme: string): string[] {\n  return theme === modelValue.value ? ['outline-box-active'] : [];\n}\n</script>\n\n<template>\n  <div class=\"flex w-full flex-wrap gap-5\">\n    <template v-for=\"theme in PRESET\" :key=\"theme.name\">\n      <div\n        class=\"flex w-25 cursor-pointer flex-col\"\n        @click=\"modelValue = theme.type\"\n      >\n        <div :class=\"activeClass(theme.type)\" class=\"outline-box flex-center\">\n          <component :is=\"components[theme.type]\" />\n        </div>\n        <div\n          class=\"mt-2 flex-center text-center text-xs text-muted-foreground hover:text-foreground\"\n        >\n          {{ theme.name }}\n          <VbenTooltip v-if=\"theme.tip\" side=\"bottom\">\n            <template #trigger>\n              <CircleHelp class=\"ml-1 size-3 cursor-help\" />\n            </template>\n            {{ theme.tip }}\n          </VbenTooltip>\n        </div>\n      </div>\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/layout/navigation.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectOption } from '@vben/types';\n\nimport { $t } from '@vben/locales';\n\nimport SwitchItem from '../switch-item.vue';\nimport ToggleItem from '../toggle-item.vue';\n\ndefineOptions({\n  name: 'PreferenceNavigationConfig',\n});\n\ndefineProps<{ disabled?: boolean; disabledNavigationSplit?: boolean }>();\n\nconst navigationStyleType = defineModel<string>('navigationStyleType');\nconst navigationSplit = defineModel<boolean>('navigationSplit');\nconst navigationAccordion = defineModel<boolean>('navigationAccordion');\n\nconst stylesItems: SelectOption[] = [\n  { label: $t('preferences.rounded'), value: 'rounded' },\n  { label: $t('preferences.plain'), value: 'plain' },\n];\n</script>\n\n<template>\n  <ToggleItem\n    v-model=\"navigationStyleType\"\n    :disabled=\"disabled\"\n    :items=\"stylesItems\"\n  >\n    {{ $t('preferences.navigationMenu.style') }}\n  </ToggleItem>\n  <SwitchItem\n    v-model=\"navigationSplit\"\n    :disabled=\"disabledNavigationSplit || disabled\"\n  >\n    {{ $t('preferences.navigationMenu.split') }}\n    <template #tip>\n      {{ $t('preferences.navigationMenu.splitTip') }}\n    </template>\n  </SwitchItem>\n  <SwitchItem v-model=\"navigationAccordion\" :disabled=\"disabled\">\n    {{ $t('preferences.navigationMenu.accordion') }}\n  </SwitchItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/layout/sidebar.vue",
    "content": "<script setup lang=\"ts\">\nimport type { LayoutType } from '@vben/types';\n\nimport { onMounted } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport CheckboxItem from '../checkbox-item.vue';\nimport NumberFieldItem from '../number-field-item.vue';\nimport SwitchItem from '../switch-item.vue';\n\ndefineProps<{ currentLayout?: LayoutType; disabled: boolean }>();\n\nconst sidebarEnable = defineModel<boolean>('sidebarEnable');\nconst sidebarWidth = defineModel<number>('sidebarWidth');\nconst sidebarCollapsedShowTitle = defineModel<boolean>(\n  'sidebarCollapsedShowTitle',\n);\nconst sidebarAutoActivateChild = defineModel<boolean>(\n  'sidebarAutoActivateChild',\n);\nconst sidebarDraggable = defineModel<boolean>('sidebarDraggable');\nconst sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');\nconst sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');\n\nconst sidebarButtons = defineModel<string[]>('sidebarButtons', { default: [] });\nconst sidebarCollapsedButton = defineModel<boolean>('sidebarCollapsedButton');\nconst sidebarFixedButton = defineModel<boolean>('sidebarFixedButton');\n\nonMounted(() => {\n  if (\n    sidebarCollapsedButton.value &&\n    !sidebarButtons.value.includes('collapsed')\n  ) {\n    sidebarButtons.value.push('collapsed');\n  }\n  if (sidebarFixedButton.value && !sidebarButtons.value.includes('fixed')) {\n    sidebarButtons.value.push('fixed');\n  }\n});\n\nconst handleCheckboxChange = () => {\n  sidebarCollapsedButton.value = !!sidebarButtons.value.includes('collapsed');\n  sidebarFixedButton.value = !!sidebarButtons.value.includes('fixed');\n};\n</script>\n\n<template>\n  <SwitchItem v-model=\"sidebarEnable\" :disabled=\"disabled\">\n    {{ $t('preferences.sidebar.visible') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"sidebarDraggable\" :disabled=\"!sidebarEnable || disabled\">\n    {{ $t('preferences.sidebar.draggable') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"sidebarCollapsed\" :disabled=\"!sidebarEnable || disabled\">\n    {{ $t('preferences.sidebar.collapsed') }}\n  </SwitchItem>\n  <SwitchItem\n    v-model=\"sidebarExpandOnHover\"\n    :disabled=\"!sidebarEnable || disabled || !sidebarCollapsed\"\n    :tip=\"$t('preferences.sidebar.expandOnHoverTip')\"\n  >\n    {{ $t('preferences.sidebar.expandOnHover') }}\n  </SwitchItem>\n  <SwitchItem\n    v-model=\"sidebarCollapsedShowTitle\"\n    :disabled=\"!sidebarEnable || disabled || !sidebarCollapsed\"\n  >\n    {{ $t('preferences.sidebar.collapsedShowTitle') }}\n  </SwitchItem>\n  <SwitchItem\n    v-model=\"sidebarAutoActivateChild\"\n    :disabled=\"\n      !sidebarEnable ||\n      !['sidebar-mixed-nav', 'mixed-nav', 'header-mixed-nav'].includes(\n        currentLayout as string,\n      ) ||\n      disabled\n    \"\n    :tip=\"$t('preferences.sidebar.autoActivateChildTip')\"\n  >\n    {{ $t('preferences.sidebar.autoActivateChild') }}\n  </SwitchItem>\n  <CheckboxItem\n    :items=\"[\n      { label: $t('preferences.sidebar.buttonCollapsed'), value: 'collapsed' },\n      { label: $t('preferences.sidebar.buttonFixed'), value: 'fixed' },\n    ]\"\n    multiple\n    v-model=\"sidebarButtons\"\n    :on-btn-click=\"handleCheckboxChange\"\n  >\n    {{ $t('preferences.sidebar.buttons') }}\n  </CheckboxItem>\n  <NumberFieldItem\n    v-model=\"sidebarWidth\"\n    :disabled=\"!sidebarEnable || disabled\"\n    :max=\"320\"\n    :min=\"160\"\n    :step=\"10\"\n  >\n    {{ $t('preferences.sidebar.width') }}\n  </NumberFieldItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/layout/tabbar.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectOption } from '@vben/types';\n\nimport { computed } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport NumberFieldItem from '../number-field-item.vue';\nimport SelectItem from '../select-item.vue';\nimport SwitchItem from '../switch-item.vue';\n\ndefineOptions({\n  name: 'PreferenceTabsConfig',\n});\n\ndefineProps<{ disabled?: boolean }>();\n\nconst tabbarEnable = defineModel<boolean>('tabbarEnable');\nconst tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');\nconst tabbarPersist = defineModel<boolean>('tabbarPersist');\nconst tabbarVisitHistory = defineModel<boolean>('tabbarVisitHistory');\nconst tabbarDraggable = defineModel<boolean>('tabbarDraggable');\nconst tabbarWheelable = defineModel<boolean>('tabbarWheelable');\nconst tabbarStyleType = defineModel<string>('tabbarStyleType');\nconst tabbarShowMore = defineModel<boolean>('tabbarShowMore');\nconst tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');\nconst tabbarMaxCount = defineModel<number>('tabbarMaxCount');\nconst tabbarMiddleClickToClose = defineModel<boolean>(\n  'tabbarMiddleClickToClose',\n);\n\nconst styleItems = computed((): SelectOption[] => [\n  {\n    label: $t('preferences.tabbar.styleType.chrome'),\n    value: 'chrome',\n  },\n  {\n    label: $t('preferences.tabbar.styleType.plain'),\n    value: 'plain',\n  },\n  {\n    label: $t('preferences.tabbar.styleType.card'),\n    value: 'card',\n  },\n\n  {\n    label: $t('preferences.tabbar.styleType.brisk'),\n    value: 'brisk',\n  },\n]);\n</script>\n\n<template>\n  <SwitchItem v-model=\"tabbarEnable\" :disabled=\"disabled\">\n    {{ $t('preferences.tabbar.enable') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"tabbarPersist\" :disabled=\"!tabbarEnable\">\n    {{ $t('preferences.tabbar.persist') }}\n  </SwitchItem>\n  <SwitchItem\n    v-model=\"tabbarVisitHistory\"\n    :disabled=\"!tabbarEnable\"\n    :tip=\"$t('preferences.tabbar.visitHistoryTip')\"\n  >\n    {{ $t('preferences.tabbar.visitHistory') }}\n  </SwitchItem>\n  <NumberFieldItem\n    v-model=\"tabbarMaxCount\"\n    :disabled=\"!tabbarEnable\"\n    :max=\"30\"\n    :min=\"0\"\n    :step=\"5\"\n    :tip=\"$t('preferences.tabbar.maxCountTip')\"\n  >\n    {{ $t('preferences.tabbar.maxCount') }}\n  </NumberFieldItem>\n  <SwitchItem v-model=\"tabbarDraggable\" :disabled=\"!tabbarEnable\">\n    {{ $t('preferences.tabbar.draggable') }}\n  </SwitchItem>\n  <SwitchItem\n    v-model=\"tabbarWheelable\"\n    :disabled=\"!tabbarEnable\"\n    :tip=\"$t('preferences.tabbar.wheelableTip')\"\n  >\n    {{ $t('preferences.tabbar.wheelable') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"tabbarMiddleClickToClose\" :disabled=\"!tabbarEnable\">\n    {{ $t('preferences.tabbar.middleClickClose') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"tabbarShowIcon\" :disabled=\"!tabbarEnable\">\n    {{ $t('preferences.tabbar.icon') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"tabbarShowMore\" :disabled=\"!tabbarEnable\">\n    {{ $t('preferences.tabbar.showMore') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"tabbarShowMaximize\" :disabled=\"!tabbarEnable\">\n    {{ $t('preferences.tabbar.showMaximize') }}\n  </SwitchItem>\n  <SelectItem v-model=\"tabbarStyleType\" :items=\"styleItems\">\n    {{ $t('preferences.tabbar.styleType.title') }}\n  </SelectItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/layout/widget.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectOption } from '@vben/types';\n\nimport { computed } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport SelectItem from '../select-item.vue';\nimport SwitchItem from '../switch-item.vue';\n\ndefineOptions({\n  name: 'PreferenceInterfaceControl',\n});\n\nconst widgetGlobalSearch = defineModel<boolean>('widgetGlobalSearch');\nconst widgetFullscreen = defineModel<boolean>('widgetFullscreen');\nconst widgetLanguageToggle = defineModel<boolean>('widgetLanguageToggle');\nconst widgetNotification = defineModel<boolean>('widgetNotification');\nconst widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');\nconst widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');\nconst widgetLockScreen = defineModel<boolean>('widgetLockScreen');\nconst appPreferencesButtonPosition = defineModel<string>(\n  'appPreferencesButtonPosition',\n);\nconst widgetRefresh = defineModel<boolean>('widgetRefresh');\n\nconst positionItems = computed((): SelectOption[] => [\n  {\n    label: $t('preferences.position.auto'),\n    value: 'auto',\n  },\n  {\n    label: $t('preferences.position.header'),\n    value: 'header',\n  },\n  {\n    label: $t('preferences.position.fixed'),\n    value: 'fixed',\n  },\n]);\n</script>\n\n<template>\n  <SwitchItem v-model=\"widgetGlobalSearch\">\n    {{ $t('preferences.widget.globalSearch') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"widgetThemeToggle\">\n    {{ $t('preferences.widget.themeToggle') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"widgetLanguageToggle\">\n    {{ $t('preferences.widget.languageToggle') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"widgetFullscreen\">\n    {{ $t('preferences.widget.fullscreen') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"widgetNotification\">\n    {{ $t('preferences.widget.notification') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"widgetLockScreen\">\n    {{ $t('preferences.widget.lockScreen') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"widgetSidebarToggle\">\n    {{ $t('preferences.widget.sidebarToggle') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"widgetRefresh\">\n    {{ $t('preferences.widget.refresh') }}\n  </SwitchItem>\n  <SelectItem v-model=\"appPreferencesButtonPosition\" :items=\"positionItems\">\n    {{ $t('preferences.position.title') }}\n  </SelectItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/number-field-item.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectOption } from '@vben/types';\n\nimport { useSlots } from 'vue';\n\nimport { CircleHelp } from '@vben/icons';\n\nimport {\n  NumberField,\n  NumberFieldContent,\n  NumberFieldDecrement,\n  NumberFieldIncrement,\n  NumberFieldInput,\n  VbenTooltip,\n} from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'PreferenceSelectItem',\n});\n\nwithDefaults(\n  defineProps<{\n    disabled?: boolean;\n    items?: SelectOption[];\n    placeholder?: string;\n    tip?: string;\n  }>(),\n  {\n    disabled: false,\n    placeholder: '',\n    tip: '',\n    items: () => [],\n  },\n);\n\nconst inputValue = defineModel<number>();\n\nconst slots = useSlots();\n</script>\n\n<template>\n  <div\n    :class=\"{\n      'hover:bg-accent': !slots.tip,\n      'pointer-events-none opacity-50': disabled,\n    }\"\n    class=\"my-1 flex w-full items-center justify-between rounded-md px-2 py-1\"\n  >\n    <span class=\"flex items-center text-sm\">\n      <slot></slot>\n\n      <VbenTooltip v-if=\"slots.tip || tip\" side=\"bottom\">\n        <template #trigger>\n          <CircleHelp class=\"ml-1 size-3 cursor-help\" />\n        </template>\n        <slot name=\"tip\">\n          <template v-if=\"tip\">\n            <p v-for=\"(line, index) in tip.split('\\n')\" :key=\"index\">\n              {{ line }}\n            </p>\n          </template>\n        </slot>\n      </VbenTooltip>\n    </span>\n\n    <NumberField v-model=\"inputValue\" v-bind=\"$attrs\" class=\"w-41.25\">\n      <NumberFieldContent>\n        <NumberFieldDecrement />\n        <NumberFieldInput />\n        <NumberFieldIncrement />\n      </NumberFieldContent>\n    </NumberField>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/select-item.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectOption } from '@vben/types';\n\nimport { useSlots } from 'vue';\n\nimport { CircleHelp } from '@vben/icons';\n\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n  VbenTooltip,\n} from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'PreferenceSelectItem',\n});\n\nwithDefaults(\n  defineProps<{\n    disabled?: boolean;\n    items?: SelectOption[];\n    placeholder?: string;\n  }>(),\n  {\n    disabled: false,\n    placeholder: '',\n    items: () => [],\n  },\n);\n\nconst selectValue = defineModel<string>();\n\nconst slots = useSlots();\n</script>\n\n<template>\n  <div\n    :class=\"{\n      'hover:bg-accent': !slots.tip,\n      'pointer-events-none opacity-50': disabled,\n    }\"\n    class=\"my-1 flex w-full items-center justify-between rounded-md px-2 py-1\"\n  >\n    <span class=\"flex items-center text-sm\">\n      <slot></slot>\n\n      <VbenTooltip v-if=\"slots.tip\" side=\"bottom\">\n        <template #trigger>\n          <CircleHelp class=\"ml-1 size-3 cursor-help\" />\n        </template>\n        <slot name=\"tip\"></slot>\n      </VbenTooltip>\n    </span>\n    <Select v-model=\"selectValue\">\n      <SelectTrigger class=\"h-8 w-41.25\">\n        <SelectValue :placeholder=\"placeholder\" />\n      </SelectTrigger>\n      <SelectContent>\n        <template v-for=\"item in items\" :key=\"item.value\">\n          <SelectItem :value=\"item.value\"> {{ item.label }} </SelectItem>\n        </template>\n      </SelectContent>\n    </Select>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/shortcut-keys/global.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { $t } from '@vben/locales';\nimport { isWindowsOs } from '@vben/utils';\n\nimport SwitchItem from '../switch-item.vue';\n\ndefineOptions({\n  name: 'PreferenceGeneralConfig',\n});\n\nconst shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable');\nconst shortcutKeysGlobalSearch = defineModel<boolean>(\n  'shortcutKeysGlobalSearch',\n);\nconst shortcutKeysLogout = defineModel<boolean>('shortcutKeysLogout');\n// const shortcutKeysPreferences = defineModel<boolean>('shortcutKeysPreferences');\nconst shortcutKeysLockScreen = defineModel<boolean>('shortcutKeysLockScreen');\n\nconst altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));\n</script>\n\n<template>\n  <SwitchItem v-model=\"shortcutKeysEnable\">\n    {{ $t('preferences.shortcutKeys.title') }}\n  </SwitchItem>\n  <SwitchItem\n    v-model=\"shortcutKeysGlobalSearch\"\n    :disabled=\"!shortcutKeysEnable\"\n  >\n    {{ $t('preferences.shortcutKeys.search') }}\n    <template #shortcut>\n      {{ isWindowsOs() ? 'Ctrl' : '⌘' }}\n      <kbd> K </kbd>\n    </template>\n  </SwitchItem>\n  <SwitchItem v-model=\"shortcutKeysLogout\" :disabled=\"!shortcutKeysEnable\">\n    {{ $t('preferences.shortcutKeys.logout') }}\n    <template #shortcut> {{ altView }} Q </template>\n  </SwitchItem>\n  <!-- <SwitchItem v-model=\"shortcutKeysPreferences\" :disabled=\"!shortcutKeysEnable\">\n    {{ $t('preferences.shortcutKeys.preferences') }}\n    <template #shortcut> {{ altView }} , </template>\n  </SwitchItem> -->\n  <SwitchItem v-model=\"shortcutKeysLockScreen\" :disabled=\"!shortcutKeysEnable\">\n    {{ $t('ui.widgets.lockScreen.title') }}\n    <template #shortcut> {{ altView }} L </template>\n  </SwitchItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/switch-item.vue",
    "content": "<script setup lang=\"ts\">\nimport { useSlots } from 'vue';\n\nimport { CircleHelp } from '@vben/icons';\n\nimport { Switch, VbenTooltip } from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'PreferenceSwitchItem',\n});\n\nwithDefaults(defineProps<{ disabled?: boolean; tip?: string }>(), {\n  disabled: false,\n  tip: '',\n});\n\nconst checked = defineModel<boolean>();\n\nconst slots = useSlots();\n\nfunction handleClick() {\n  checked.value = !checked.value;\n}\n</script>\n\n<template>\n  <div\n    :class=\"{\n      'pointer-events-none opacity-50': disabled,\n    }\"\n    class=\"my-1 flex w-full items-center justify-between rounded-md px-2 py-2.5 hover:bg-accent\"\n    @click=\"handleClick\"\n  >\n    <span class=\"flex items-center text-sm\">\n      <slot></slot>\n\n      <VbenTooltip v-if=\"slots.tip || tip\" side=\"bottom\">\n        <template #trigger>\n          <CircleHelp class=\"ml-1 size-3 cursor-help\" />\n        </template>\n        <slot name=\"tip\">\n          <template v-if=\"tip\">\n            <p v-for=\"(line, index) in tip.split('\\n')\" :key=\"index\">\n              {{ line }}\n            </p>\n          </template>\n        </slot>\n      </VbenTooltip>\n    </span>\n    <span v-if=\"$slots.shortcut\" class=\"mr-2 ml-auto text-xs opacity-60\">\n      <slot name=\"shortcut\"></slot>\n    </span>\n    <Switch v-model=\"checked\" @click.stop />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/theme/builtin.vue",
    "content": "<script setup lang=\"ts\">\nimport type { BuiltinThemePreset } from '@vben/preferences';\nimport type { BuiltinThemeType } from '@vben/types';\n\nimport { computed, ref, watch } from 'vue';\n\nimport { UserRoundPen } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport { BUILT_IN_THEME_PRESETS } from '@vben/preferences';\nimport { convertToHsl, TinyColor } from '@vben/utils';\n\nimport { useThrottleFn } from '@vueuse/core';\n\ndefineOptions({\n  name: 'PreferenceBuiltinTheme',\n});\n\nconst props = defineProps<{ isDark: boolean }>();\n\nconst colorInput = ref();\nconst modelValue = defineModel<BuiltinThemeType>({ default: 'default' });\nconst themeColorPrimary = defineModel<string>('themeColorPrimary');\n\nconst updateThemeColorPrimary = useThrottleFn(\n  (value: string) => {\n    themeColorPrimary.value = value;\n  },\n  300,\n  true,\n  true,\n);\n\nconst inputValue = computed(() => {\n  return new TinyColor(themeColorPrimary.value || '').toHexString();\n});\n\nconst builtinThemePresets = computed(() => {\n  return [...BUILT_IN_THEME_PRESETS];\n});\n\nfunction typeView(name: BuiltinThemeType) {\n  switch (name) {\n    case 'custom': {\n      return $t('preferences.theme.builtin.custom');\n    }\n    case 'deep-blue': {\n      return $t('preferences.theme.builtin.deepBlue');\n    }\n    case 'deep-green': {\n      return $t('preferences.theme.builtin.deepGreen');\n    }\n    case 'default': {\n      return $t('preferences.theme.builtin.default');\n    }\n    case 'gray': {\n      return $t('preferences.theme.builtin.gray');\n    }\n    case 'green': {\n      return $t('preferences.theme.builtin.green');\n    }\n    case 'neutral': {\n      return $t('preferences.theme.builtin.neutral');\n    }\n    case 'orange': {\n      return $t('preferences.theme.builtin.orange');\n    }\n    case 'pink': {\n      return $t('preferences.theme.builtin.pink');\n    }\n    case 'rose': {\n      return $t('preferences.theme.builtin.rose');\n    }\n    case 'sky-blue': {\n      return $t('preferences.theme.builtin.skyBlue');\n    }\n    case 'slate': {\n      return $t('preferences.theme.builtin.slate');\n    }\n    case 'violet': {\n      return $t('preferences.theme.builtin.violet');\n    }\n    case 'yellow': {\n      return $t('preferences.theme.builtin.yellow');\n    }\n    case 'zinc': {\n      return $t('preferences.theme.builtin.zinc');\n    }\n  }\n}\n\nfunction handleSelect(theme: BuiltinThemePreset) {\n  modelValue.value = theme.type;\n}\n\nfunction handleInputChange(e: Event) {\n  const target = e.target as HTMLInputElement;\n  updateThemeColorPrimary(convertToHsl(target.value));\n}\n\nfunction selectColor() {\n  colorInput.value?.[0]?.click?.();\n}\n\nwatch(\n  () => [modelValue.value, props.isDark] as [BuiltinThemeType, boolean],\n  ([themeType, isDark], [_, isDarkPrev]) => {\n    const theme = builtinThemePresets.value.find(\n      (item) => item.type === themeType,\n    );\n    if (theme) {\n      const primaryColor = isDark\n        ? theme.darkPrimaryColor || theme.primaryColor\n        : theme.primaryColor;\n\n      if (!(theme.type === 'custom' && isDark !== isDarkPrev)) {\n        themeColorPrimary.value = primaryColor || theme.color;\n      }\n    }\n  },\n);\n</script>\n\n<template>\n  <div class=\"flex w-full flex-wrap justify-between\">\n    <template v-for=\"theme in builtinThemePresets\" :key=\"theme.type\">\n      <div class=\"flex cursor-pointer flex-col\" @click=\"handleSelect(theme)\">\n        <div\n          :class=\"{\n            'outline-box-active': theme.type === modelValue,\n          }\"\n          class=\"group outline-box flex-center cursor-pointer\"\n        >\n          <template v-if=\"theme.type !== 'custom'\">\n            <div\n              :style=\"{ backgroundColor: theme.color }\"\n              class=\"mx-9 my-2 size-5 rounded-md\"\n            ></div>\n          </template>\n          <template v-else>\n            <div class=\"size-full px-9 py-2\" @click.stop=\"selectColor\">\n              <div class=\"relative flex-center size-5 rounded-sm\">\n                <UserRoundPen\n                  class=\"absolute z-1 size-5 opacity-60 group-hover:opacity-100\"\n                />\n                <input\n                  ref=\"colorInput\"\n                  :value=\"inputValue\"\n                  class=\"absolute inset-0 opacity-0\"\n                  type=\"color\"\n                  @input=\"handleInputChange\"\n                />\n              </div>\n            </div>\n          </template>\n        </div>\n        <div class=\"my-2 text-center text-xs text-muted-foreground\">\n          {{ typeView(theme.type) }}\n        </div>\n      </div>\n    </template>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/theme/color-mode.vue",
    "content": "<script setup lang=\"ts\">\nimport { $t } from '@vben/locales';\n\nimport SwitchItem from '../switch-item.vue';\n\ndefineOptions({\n  name: 'PreferenceColorMode',\n});\n\nconst appColorWeakMode = defineModel<boolean>('appColorWeakMode', {\n  default: false,\n});\n\nconst appColorGrayMode = defineModel<boolean>('appColorGrayMode', {\n  default: false,\n});\n</script>\n\n<template>\n  <SwitchItem v-model=\"appColorWeakMode\">\n    {{ $t('preferences.theme.weakMode') }}\n  </SwitchItem>\n  <SwitchItem v-model=\"appColorGrayMode\">\n    {{ $t('preferences.theme.grayMode') }}\n  </SwitchItem>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/theme/font-size.vue",
    "content": "<script setup lang=\"ts\">\nimport { watch } from 'vue';\n\nimport { $t } from '@vben/locales';\n\nimport {\n  NumberField,\n  NumberFieldContent,\n  NumberFieldDecrement,\n  NumberFieldIncrement,\n  NumberFieldInput,\n} from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'PreferenceFontSize',\n});\n\nconst modelValue = defineModel<number>({\n  default: 16,\n});\n\nconst min = 15;\nconst max = 22;\nconst step = 1;\n\n// 限制输入值在 min 和 max 之间\nwatch(\n  modelValue,\n  (newValue) => {\n    if (newValue < min) {\n      modelValue.value = min;\n    } else if (newValue > max) {\n      modelValue.value = max;\n    }\n  },\n  { immediate: true },\n);\n</script>\n\n<template>\n  <div class=\"flex w-full flex-col gap-4\">\n    <div class=\"flex items-center gap-2\">\n      <NumberField\n        v-model=\"modelValue\"\n        :max=\"max\"\n        :min=\"min\"\n        :step=\"step\"\n        class=\"w-full\"\n      >\n        <NumberFieldContent>\n          <NumberFieldDecrement />\n          <NumberFieldInput />\n          <NumberFieldIncrement />\n        </NumberFieldContent>\n      </NumberField>\n      <span class=\"text-xs whitespace-nowrap text-muted-foreground\">px</span>\n    </div>\n    <div class=\"text-xs text-muted-foreground\">\n      {{ $t('preferences.theme.fontSizeTip') }}\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/theme/radius.vue",
    "content": "<script setup lang=\"ts\">\nimport { ToggleGroup, ToggleGroupItem } from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'PreferenceColorMode',\n});\n\nconst modelValue = defineModel<string | undefined>('themeRadius', {\n  default: '0.5',\n});\n\nconst items = [\n  { label: '0', value: '0' },\n  { label: '0.25', value: '0.25' },\n  { label: '0.5', value: '0.5' },\n  { label: '0.75', value: '0.75' },\n  { label: '1', value: '1' },\n];\n</script>\n\n<template>\n  <ToggleGroup\n    v-model=\"modelValue\"\n    class=\"gap-2\"\n    size=\"sm\"\n    type=\"single\"\n    variant=\"outline\"\n  >\n    <template v-for=\"item in items\" :key=\"item.value\">\n      <ToggleGroupItem\n        :value=\"item.value\"\n        class=\"h-7 w-16 rounded-sm data-[state=on]:bg-primary data-[state=on]:text-primary-foreground\"\n      >\n        {{ item.label }}\n      </ToggleGroupItem>\n    </template>\n  </ToggleGroup>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/theme/theme.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Component } from 'vue';\n\nimport type { ThemeModeType } from '@vben/types';\n\nimport { watch } from 'vue';\n\nimport { MoonStar, Sun, SunMoon } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport { usePreferences } from '@vben/preferences';\n\nimport SwitchItem from '../switch-item.vue';\n\ndefineOptions({\n  name: 'PreferenceTheme',\n});\n\nconst modelValue = defineModel<string>({ default: 'auto' });\nconst themeSemiDarkSidebar = defineModel<boolean>('themeSemiDarkSidebar');\nconst themeSemiDarkSidebarSub = defineModel<boolean>('themeSemiDarkSidebarSub');\nconst themeSemiDarkHeader = defineModel<boolean>('themeSemiDarkHeader');\n\nconst { layout } = usePreferences();\n\nwatch(\n  () => themeSemiDarkSidebar.value,\n  () => {\n    if (!themeSemiDarkSidebar.value) {\n      themeSemiDarkSidebarSub.value = themeSemiDarkSidebar.value;\n    }\n  },\n);\n\nconst THEME_PRESET: Array<{ icon: Component; name: ThemeModeType }> = [\n  {\n    icon: Sun,\n    name: 'light',\n  },\n  {\n    icon: MoonStar,\n    name: 'dark',\n  },\n  {\n    icon: SunMoon,\n    name: 'auto',\n  },\n];\n\nfunction activeClass(theme: string): string[] {\n  return theme === modelValue.value ? ['outline-box-active'] : [];\n}\n\nfunction nameView(name: string) {\n  switch (name) {\n    case 'auto': {\n      return $t('preferences.followSystem');\n    }\n    case 'dark': {\n      return $t('preferences.theme.dark');\n    }\n    case 'light': {\n      return $t('preferences.theme.light');\n    }\n  }\n}\n</script>\n\n<template>\n  <div class=\"flex w-full flex-wrap justify-between\">\n    <template v-for=\"theme in THEME_PRESET\" :key=\"theme.name\">\n      <div\n        class=\"flex cursor-pointer flex-col\"\n        @click=\"modelValue = theme.name\"\n      >\n        <div\n          :class=\"activeClass(theme.name)\"\n          class=\"outline-box flex-center py-4\"\n        >\n          <component :is=\"theme.icon\" class=\"mx-9 size-5\" />\n        </div>\n        <div class=\"mt-2 text-center text-xs text-muted-foreground\">\n          {{ nameView(theme.name) }}\n        </div>\n      </div>\n    </template>\n\n    <SwitchItem\n      v-model=\"themeSemiDarkSidebar\"\n      :disabled=\"\n        modelValue === 'dark' ||\n        layout === 'header-nav' ||\n        layout === 'full-content'\n      \"\n      :tip=\"$t('preferences.theme.darkSidebarTip')\"\n      class=\"mt-6\"\n    >\n      {{ $t('preferences.theme.darkSidebar') }}\n    </SwitchItem>\n    <SwitchItem\n      v-model=\"themeSemiDarkSidebarSub\"\n      :disabled=\"\n        modelValue === 'dark' ||\n        (layout !== 'header-mixed-nav' && layout !== 'sidebar-mixed-nav') ||\n        !themeSemiDarkSidebar\n      \"\n      :tip=\"$t('preferences.theme.darkSidebarSubTip')\"\n    >\n      {{ $t('preferences.theme.darkSidebarSub') }}\n    </SwitchItem>\n    <SwitchItem v-model=\"themeSemiDarkHeader\" :disabled=\"modelValue === 'dark'\">\n      {{ $t('preferences.theme.darkHeader') }}\n    </SwitchItem>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/blocks/toggle-item.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SelectOption } from '@vben/types';\n\nimport { ToggleGroup, ToggleGroupItem } from '@vben-core/shadcn-ui';\n\ndefineOptions({\n  name: 'PreferenceToggleItem',\n});\n\nwithDefaults(defineProps<{ disabled?: boolean; items?: SelectOption[] }>(), {\n  disabled: false,\n  items: () => [],\n});\n\nconst modelValue = defineModel<string>();\n</script>\n\n<template>\n  <div\n    :class=\"{\n      'pointer-events-none opacity-50': disabled,\n    }\"\n    class=\"flex w-full items-center justify-between rounded-md p-2 hover:bg-accent\"\n    disabled\n  >\n    <span class=\"text-sm\">\n      <slot></slot>\n    </span>\n    <ToggleGroup\n      v-model=\"modelValue\"\n      class=\"gap-2\"\n      size=\"sm\"\n      type=\"single\"\n      variant=\"outline\"\n    >\n      <template v-for=\"item in items\" :key=\"item.value\">\n        <ToggleGroupItem\n          :value=\"item.value\"\n          class=\"h-7 rounded-sm data-[state=on]:bg-primary data-[state=on]:text-primary-foreground\"\n        >\n          {{ item.label }}\n        </ToggleGroupItem>\n      </template>\n    </ToggleGroup>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/icons/content-compact.vue",
    "content": "<template>\n  <svg\n    class=\"custom-radio-image\"\n    fill=\"none\"\n    height=\"66\"\n    width=\"104\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <g>\n      <rect\n        id=\"svg_1\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.02\"\n        height=\"66\"\n        rx=\"4\"\n        stroke=\"null\"\n        width=\"104\"\n        x=\"0.13514\"\n        y=\"0.13514\"\n      />\n      <rect\n        id=\"svg_8\"\n        fill=\"hsl(var(--primary))\"\n        height=\"9.07027\"\n        stroke=\"null\"\n        width=\"104.07934\"\n        x=\"-0.07419\"\n        y=\"-0.05773\"\n      />\n      <rect\n        id=\"svg_3\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"15.58168\"\n        y=\"3.20832\"\n      />\n      <path\n        id=\"svg_12\"\n        d=\"m98.19822,2.872c0,-0.54338 0.45662,-1 1,-1l1.925,0c0.54338,0 1,0.45662 1,1l0,2.4c0,0.54338 -0.45662,1 -1,1l-1.925,0c-0.54338,0 -1,-0.45662 -1,-1l0,-2.4z\"\n        fill=\"#ffffff\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_13\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.51892\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"41.98275\"\n        x=\"45.37589\"\n        y=\"13.53192\"\n      />\n      <path\n        id=\"svg_14\"\n        d=\"m16.4123,15.53192c0,-1.08676 0.74096,-2 1.62271,-2l21.74653,0c0.88175,0 1.62271,0.91324 1.62271,2l0,17.24865c0,1.08676 -0.74096,2 -1.62271,2l-21.74653,0c-0.88175,0 -1.62271,-0.91324 -1.62271,-2l0,-17.24865z\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_15\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.65405\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"71.10636\"\n        x=\"16.54743\"\n        y=\"39.34689\"\n      />\n      <rect\n        id=\"svg_21\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"28.14924\"\n        y=\"3.07319\"\n      />\n      <rect\n        id=\"svg_22\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"41.25735\"\n        y=\"3.20832\"\n      />\n      <rect\n        id=\"svg_23\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"54.23033\"\n        y=\"3.07319\"\n      />\n      <rect\n        id=\"svg_4\"\n        fill=\"#ffffff\"\n        height=\"7.13843\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"7.78397\"\n        x=\"1.5327\"\n        y=\"0.881\"\n      />\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/icons/full-content.vue",
    "content": "<template>\n  <svg\n    class=\"custom-radio-image\"\n    fill=\"none\"\n    height=\"66\"\n    width=\"104\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <g>\n      <path\n        id=\"svg_1\"\n        d=\"m0.13514,4.13514c0,-2.17352 1.82648,-4 4,-4l96,0c2.17352,0 4,1.82648 4,4l0,58c0,2.17352 -1.82648,4 -4,4l-96,0c-2.17352,0 -4,-1.82648 -4,-4l0,-58z\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.02\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_13\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"26.57155\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"53.18333\"\n        x=\"45.79979\"\n        y=\"3.77232\"\n      />\n      <path\n        id=\"svg_14\"\n        d=\"m4.28142,5.96169c0,-1.37748 1.06465,-2.53502 2.33158,-2.53502l31.2463,0c1.26693,0 2.33158,1.15754 2.33158,2.53502l0,21.86282c0,1.37748 -1.06465,2.53502 -2.33158,2.53502l-31.2463,0c-1.26693,0 -2.33158,-1.15754 -2.33158,-2.53502l0,-21.86282z\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_15\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"25.02247\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"94.39371\"\n        x=\"4.56735\"\n        y=\"34.92584\"\n      />\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/icons/header-mixed-nav.vue",
    "content": "<template>\n  <svg\n    class=\"custom-radio-image\"\n    fill=\"none\"\n    height=\"66\"\n    width=\"104\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <g>\n      <rect\n        id=\"svg_1\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.02\"\n        height=\"66\"\n        rx=\"4\"\n        stroke=\"null\"\n        width=\"104\"\n        x=\"0.13514\"\n        y=\"0.13514\"\n      />\n      <path\n        id=\"svg_2\"\n        d=\"m-3.37838,3.7543a1.93401,4.02457 0 0 1 1.93401,-4.02457l11.3488,0l0,66.40541l-11.3488,0a1.93401,4.02457 0 0 1 -1.93401,-4.02457l0,-58.35627z\"\n        fill=\"hsl(var(--primary))\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_3\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"5.47439\"\n        x=\"1.64059\"\n        y=\"15.46086\"\n      />\n      <rect\n        id=\"svg_4\"\n        fill=\"#ffffff\"\n        height=\"7.67897\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"8.18938\"\n        x=\"0.58676\"\n        y=\"1.42154\"\n      />\n      <rect\n        id=\"svg_8\"\n        fill=\"hsl(var(--primary))\"\n        height=\"9.07027\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"75.91967\"\n        x=\"25.38277\"\n        y=\"1.42876\"\n      />\n      <rect\n        id=\"svg_9\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"27.91529\"\n        y=\"3.69284\"\n      />\n      <rect\n        id=\"svg_10\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"80.75054\"\n        y=\"3.62876\"\n      />\n      <rect\n        id=\"svg_11\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"87.78868\"\n        y=\"3.69981\"\n      />\n      <rect\n        id=\"svg_12\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"94.6847\"\n        y=\"3.62876\"\n      />\n      <rect\n        id=\"svg_13\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.51892\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"42.9287\"\n        x=\"58.75427\"\n        y=\"14.613\"\n      />\n      <rect\n        id=\"svg_14\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"20.97838\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"28.36894\"\n        x=\"26.14342\"\n        y=\"14.613\"\n      />\n      <rect\n        id=\"svg_15\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.65405\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"75.09493\"\n        x=\"26.34264\"\n        y=\"39.68822\"\n      />\n      <rect\n        id=\"svg_5\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"5.47439\"\n        x=\"1.79832\"\n        y=\"28.39462\"\n      />\n      <rect\n        id=\"svg_6\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"5.47439\"\n        x=\"1.64059\"\n        y=\"41.80156\"\n      />\n      <rect\n        id=\"svg_7\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"5.47439\"\n        x=\"1.64059\"\n        y=\"55.36623\"\n      />\n      <rect\n        id=\"svg_16\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"65.72065\"\n        stroke=\"null\"\n        width=\"12.49265\"\n        x=\"9.85477\"\n        y=\"-0.02618\"\n      />\n      <rect\n        id=\"svg_21\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"35.14924\"\n        y=\"4.07319\"\n      />\n      <rect\n        id=\"svg_22\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"47.25735\"\n        y=\"4.20832\"\n      />\n      <rect\n        id=\"svg_23\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"59.23033\"\n        y=\"4.07319\"\n      />\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/icons/header-nav.vue",
    "content": "<template>\n  <svg\n    class=\"custom-radio-image\"\n    fill=\"none\"\n    height=\"66\"\n    width=\"104\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <g>\n      <rect\n        id=\"svg_1\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.02\"\n        height=\"66\"\n        rx=\"4\"\n        stroke=\"null\"\n        width=\"104\"\n        x=\"0.13514\"\n        y=\"0.13514\"\n      />\n      <rect\n        id=\"svg_8\"\n        fill=\"hsl(var(--primary))\"\n        height=\"9.07027\"\n        stroke=\"null\"\n        width=\"104.07934\"\n        x=\"-0.07419\"\n        y=\"-0.05773\"\n      />\n      <rect\n        id=\"svg_3\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"15.58168\"\n        y=\"3.20832\"\n      />\n      <path\n        id=\"svg_12\"\n        d=\"m98.19822,2.872c0,-0.54338 0.45662,-1 1,-1l1.925,0c0.54338,0 1,0.45662 1,1l0,2.4c0,0.54338 -0.45662,1 -1,1l-1.925,0c-0.54338,0 -1,-0.45662 -1,-1l0,-2.4z\"\n        fill=\"#ffffff\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_13\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.51892\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"53.60438\"\n        x=\"43.484\"\n        y=\"13.66705\"\n      />\n      <path\n        id=\"svg_14\"\n        d=\"m3.43932,15.53192c0,-1.08676 1.03344,-2 2.26323,-2l30.33036,0c1.22979,0 2.26323,0.91324 2.26323,2l0,17.24865c0,1.08676 -1.03344,2 -2.26323,2l-30.33036,0c-1.22979,0 -2.26323,-0.91324 -2.26323,-2l0,-17.24865z\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_15\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.65405\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"95.02528\"\n        x=\"3.30419\"\n        y=\"39.34689\"\n      />\n      <rect\n        id=\"svg_21\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"28.14924\"\n        y=\"3.07319\"\n      />\n      <rect\n        id=\"svg_22\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"41.25735\"\n        y=\"3.20832\"\n      />\n      <rect\n        id=\"svg_23\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"54.23033\"\n        y=\"3.07319\"\n      />\n      <rect\n        id=\"svg_4\"\n        fill=\"#ffffff\"\n        height=\"7.13843\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"7.78397\"\n        x=\"1.5327\"\n        y=\"0.881\"\n      />\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/icons/header-sidebar-nav.vue",
    "content": "<template>\n  <svg\n    class=\"custom-radio-image\"\n    fill=\"none\"\n    height=\"66\"\n    width=\"104\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <g>\n      <rect\n        id=\"svg_1\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.02\"\n        height=\"66\"\n        rx=\"4\"\n        stroke=\"null\"\n        width=\"104\"\n        x=\"0.13514\"\n        y=\"0.13514\"\n      />\n      <rect\n        id=\"svg_8\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"9.07027\"\n        stroke=\"null\"\n        width=\"104.07934\"\n        x=\"-0.07419\"\n        y=\"-0.05773\"\n      />\n      <rect\n        id=\"svg_3\"\n        fill=\"#b2b2b2\"\n        height=\"1.689\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"6.52486\"\n        x=\"10.08168\"\n        y=\"3.50832\"\n      />\n      <rect\n        id=\"svg_10\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"80.75054\"\n        y=\"2.89362\"\n      />\n      <rect\n        id=\"svg_11\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"87.58249\"\n        y=\"2.89362\"\n      />\n      <path\n        id=\"svg_12\"\n        d=\"m98.19822,2.872c0,-0.54338 0.45662,-1 1,-1l1.925,0c0.54338,0 1,0.45662 1,1l0,2.4c0,0.54338 -0.45662,1 -1,1l-1.925,0c-0.54338,0 -1,-0.45662 -1,-1l0,-2.4z\"\n        fill=\"#ffffff\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_13\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.51892\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"44.13071\"\n        x=\"53.37873\"\n        y=\"13.45652\"\n      />\n      <path\n        id=\"svg_14\"\n        d=\"m19.4393,15.74245c0,-1.08676 0.79001,-2 1.73013,-2l23.18605,0c0.94011,0 1.73013,0.91324 1.73013,2l0,17.24865c0,1.08676 -0.79001,2 -1.73013,2l-23.18605,0c-0.94011,0 -1.73013,-0.91324 -1.73013,-2l0,-17.24865z\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_15\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.65405\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"78.39372\"\n        x=\"19.93575\"\n        y=\"39.34689\"\n      />\n      <rect\n        id=\"svg_21\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"28.14924\"\n        y=\"3.07319\"\n      />\n      <rect\n        id=\"svg_22\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"41.25735\"\n        y=\"3.20832\"\n      />\n      <rect\n        id=\"svg_23\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"54.23033\"\n        y=\"3.07319\"\n      />\n      <rect\n        id=\"svg_4\"\n        fill=\"#ffffff\"\n        height=\"5.13843\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"5.78397\"\n        x=\"1.5327\"\n        y=\"1.081\"\n      />\n      <rect\n        id=\"svg_5\"\n        fill=\"hsl(var(--primary))\"\n        height=\"56.81191\"\n        stroke=\"null\"\n        width=\"15.44642\"\n        x=\"-0.06423\"\n        y=\"9.03113\"\n      />\n      <path\n        id=\"svg_2\"\n        d=\"m2.38669,15.38074c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z\"\n        fill=\"#fff\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <path\n        id=\"svg_6\"\n        d=\"m2.38669,28.43336c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z\"\n        fill=\"#fff\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <path\n        id=\"svg_7\"\n        d=\"m2.17616,41.27545c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z\"\n        fill=\"#fff\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <path\n        id=\"svg_9\"\n        d=\"m2.17616,54.32806c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z\"\n        fill=\"#fff\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/icons/index.ts",
    "content": "import HeaderNav from './header-nav.vue';\n\nexport { default as ContentCompact } from './content-compact.vue';\nexport { default as FullContent } from './full-content.vue';\nexport { default as HeaderMixedNav } from './header-mixed-nav.vue';\nexport { default as HeaderSidebarNav } from './header-sidebar-nav.vue';\nexport { default as MixedNav } from './mixed-nav.vue';\nexport { default as SidebarMixedNav } from './sidebar-mixed-nav.vue';\nexport { default as SidebarNav } from './sidebar-nav.vue';\n\nconst ContentWide = HeaderNav;\nexport { ContentWide, HeaderNav };\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/icons/mixed-nav.vue",
    "content": "<template>\n  <svg\n    class=\"custom-radio-image\"\n    fill=\"none\"\n    height=\"66\"\n    width=\"104\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <g>\n      <rect\n        id=\"svg_1\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.02\"\n        height=\"66\"\n        rx=\"4\"\n        stroke=\"null\"\n        width=\"104\"\n        x=\"0.13514\"\n        y=\"0.13514\"\n      />\n      <rect\n        id=\"svg_8\"\n        fill=\"hsl(var(--primary))\"\n        height=\"9.07027\"\n        stroke=\"null\"\n        width=\"104.07934\"\n        x=\"-0.07419\"\n        y=\"-0.05773\"\n      />\n      <rect\n        id=\"svg_3\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"15.58168\"\n        y=\"3.20832\"\n      />\n      <path\n        id=\"svg_12\"\n        d=\"m98.19822,2.872c0,-0.54338 0.45662,-1 1,-1l1.925,0c0.54338,0 1,0.45662 1,1l0,2.4c0,0.54338 -0.45662,1 -1,1l-1.925,0c-0.54338,0 -1,-0.45662 -1,-1l0,-2.4z\"\n        fill=\"#ffffff\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_13\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.51892\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"44.13071\"\n        x=\"53.37873\"\n        y=\"13.45652\"\n      />\n      <path\n        id=\"svg_14\"\n        d=\"m19.4393,15.74245c0,-1.08676 0.79001,-2 1.73013,-2l23.18605,0c0.94011,0 1.73013,0.91324 1.73013,2l0,17.24865c0,1.08676 -0.79001,2 -1.73013,2l-23.18605,0c-0.94011,0 -1.73013,-0.91324 -1.73013,-2l0,-17.24865z\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_15\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.65405\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"78.39372\"\n        x=\"19.93575\"\n        y=\"39.34689\"\n      />\n      <rect\n        id=\"svg_21\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"28.14924\"\n        y=\"3.07319\"\n      />\n      <rect\n        id=\"svg_22\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"41.25735\"\n        y=\"3.20832\"\n      />\n      <rect\n        id=\"svg_23\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"7.52486\"\n        x=\"54.23033\"\n        y=\"3.07319\"\n      />\n      <rect\n        id=\"svg_4\"\n        fill=\"#ffffff\"\n        height=\"7.13843\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"7.78397\"\n        x=\"1.5327\"\n        y=\"0.881\"\n      />\n      <rect\n        id=\"svg_5\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"56.81191\"\n        stroke=\"null\"\n        width=\"15.44642\"\n        x=\"-0.06423\"\n        y=\"9.03113\"\n      />\n      <path\n        id=\"svg_2\"\n        d=\"m2.38669,15.38074c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <path\n        id=\"svg_6\"\n        d=\"m2.38669,28.43336c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <path\n        id=\"svg_7\"\n        d=\"m2.17616,41.27545c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n      <path\n        id=\"svg_9\"\n        d=\"m2.17616,54.32806c0,-0.20384 0.27195,-0.37513 0.59557,-0.37513l7.98149,0c0.32362,0 0.59557,0.17129 0.59557,0.37513l0,3.23525c0,0.20384 -0.27195,0.37513 -0.59557,0.37513l-7.98149,0c-0.32362,0 -0.59557,-0.17129 -0.59557,-0.37513l0,-3.23525z\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        opacity=\"undefined\"\n        stroke=\"null\"\n      />\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/icons/setting.vue",
    "content": "<template>\n  <svg\n    height=\"1em\"\n    viewBox=\"0 0 24 24\"\n    width=\"1em\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M19.9 12.66a1 1 0 0 1 0-1.32l1.28-1.44a1 1 0 0 0 .12-1.17l-2-3.46a1 1 0 0 0-1.07-.48l-1.88.38a1 1 0 0 1-1.15-.66l-.61-1.83a1 1 0 0 0-.95-.68h-4a1 1 0 0 0-1 .68l-.56 1.83a1 1 0 0 1-1.15.66L5 4.79a1 1 0 0 0-1 .48L2 8.73a1 1 0 0 0 .1 1.17l1.27 1.44a1 1 0 0 1 0 1.32L2.1 14.1a1 1 0 0 0-.1 1.17l2 3.46a1 1 0 0 0 1.07.48l1.88-.38a1 1 0 0 1 1.15.66l.61 1.83a1 1 0 0 0 1 .68h4a1 1 0 0 0 .95-.68l.61-1.83a1 1 0 0 1 1.15-.66l1.88.38a1 1 0 0 0 1.07-.48l2-3.46a1 1 0 0 0-.12-1.17ZM18.41 14l.8.9l-1.28 2.22l-1.18-.24a3 3 0 0 0-3.45 2L12.92 20h-2.56L10 18.86a3 3 0 0 0-3.45-2l-1.18.24l-1.3-2.21l.8-.9a3 3 0 0 0 0-4l-.8-.9l1.28-2.2l1.18.24a3 3 0 0 0 3.45-2L10.36 4h2.56l.38 1.14a3 3 0 0 0 3.45 2l1.18-.24l1.28 2.22l-.8.9a3 3 0 0 0 0 3.98m-6.77-6a4 4 0 1 0 4 4a4 4 0 0 0-4-4m0 6a2 2 0 1 1 2-2a2 2 0 0 1-2 2\"\n    />\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/icons/sidebar-mixed-nav.vue",
    "content": "<template>\n  <svg\n    class=\"custom-radio-image\"\n    fill=\"none\"\n    height=\"66\"\n    width=\"104\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <g>\n      <rect\n        id=\"svg_1\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.02\"\n        height=\"66\"\n        rx=\"4\"\n        stroke=\"null\"\n        width=\"104\"\n        x=\"0.13514\"\n        y=\"0.13514\"\n      />\n      <path\n        id=\"svg_2\"\n        d=\"m-3.37838,3.7543a1.93401,4.02457 0 0 1 1.93401,-4.02457l11.3488,0l0,66.40541l-11.3488,0a1.93401,4.02457 0 0 1 -1.93401,-4.02457l0,-58.35627z\"\n        fill=\"hsl(var(--primary))\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_3\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"5.47439\"\n        x=\"1.64059\"\n        y=\"15.46086\"\n      />\n      <rect\n        id=\"svg_4\"\n        fill=\"#ffffff\"\n        height=\"7.67897\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"8.18938\"\n        x=\"0.58676\"\n        y=\"1.42154\"\n      />\n      <rect\n        id=\"svg_8\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"9.07027\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"75.91967\"\n        x=\"25.38277\"\n        y=\"1.42876\"\n      />\n      <rect\n        id=\"svg_9\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"27.91529\"\n        y=\"3.69284\"\n      />\n      <rect\n        id=\"svg_10\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"80.75054\"\n        y=\"3.62876\"\n      />\n      <rect\n        id=\"svg_11\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"87.78868\"\n        y=\"3.69981\"\n      />\n      <rect\n        id=\"svg_12\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"94.6847\"\n        y=\"3.62876\"\n      />\n      <rect\n        id=\"svg_13\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.51892\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"42.9287\"\n        x=\"58.75427\"\n        y=\"14.613\"\n      />\n      <rect\n        id=\"svg_14\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"20.97838\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"28.36894\"\n        x=\"26.14342\"\n        y=\"14.613\"\n      />\n      <rect\n        id=\"svg_15\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.65405\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"75.09493\"\n        x=\"26.34264\"\n        y=\"39.68822\"\n      />\n      <rect\n        id=\"svg_5\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"5.47439\"\n        x=\"1.79832\"\n        y=\"28.39462\"\n      />\n      <rect\n        id=\"svg_6\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"5.47439\"\n        x=\"1.64059\"\n        y=\"41.80156\"\n      />\n      <rect\n        id=\"svg_7\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        stroke=\"null\"\n        width=\"5.47439\"\n        x=\"1.64059\"\n        y=\"55.36623\"\n      />\n      <rect\n        id=\"svg_16\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"65.72065\"\n        stroke=\"null\"\n        width=\"12.49265\"\n        x=\"9.85477\"\n        y=\"-0.02618\"\n      />\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/icons/sidebar-nav.vue",
    "content": "<template>\n  <svg\n    class=\"custom-radio-image\"\n    fill=\"none\"\n    height=\"66\"\n    width=\"104\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <g>\n      <rect\n        id=\"svg_1\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.02\"\n        height=\"66\"\n        rx=\"4\"\n        stroke=\"null\"\n        width=\"104\"\n      />\n      <path\n        id=\"svg_2\"\n        d=\"m-3.37838,3.61916a4.4919,4.02457 0 0 1 4.4919,-4.02457l26.35848,0l0,66.40541l-26.35848,0a4.4919,4.02457 0 0 1 -4.4919,-4.02457l0,-58.35627z\"\n        fill=\"hsl(var(--primary))\"\n        stroke=\"null\"\n      />\n      <rect\n        id=\"svg_3\"\n        fill=\"#e5e5e5\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        width=\"17.66\"\n        x=\"4.906\"\n        y=\"23.884\"\n      />\n      <rect\n        id=\"svg_4\"\n        fill=\"#ffffff\"\n        height=\"9.706\"\n        rx=\"2\"\n        width=\"9.811\"\n        x=\"8.83\"\n        y=\"5.881\"\n      />\n      <path\n        id=\"svg_5\"\n        d=\"m4.906,35.833c0,-0.75801 0.63699,-1.395 1.395,-1.395l14.87,0c0.75801,0 1.395,0.63699 1.395,1.395l0,-0.001c0,0.75801 -0.63699,1.395 -1.395,1.395l-14.87,0c-0.75801,0 -1.395,-0.63699 -1.395,-1.395l0,0.001z\"\n        fill=\"#ffffff\"\n        opacity=\"undefined\"\n      />\n      <rect\n        id=\"svg_6\"\n        fill=\"#ffffff\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        width=\"17.66\"\n        x=\"4.906\"\n        y=\"44.992\"\n      />\n      <rect\n        id=\"svg_7\"\n        fill=\"#ffffff\"\n        height=\"2.789\"\n        rx=\"1.395\"\n        width=\"17.66\"\n        x=\"4.906\"\n        y=\"55.546\"\n      />\n      <rect\n        id=\"svg_8\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"9.07027\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"73.53879\"\n        x=\"28.97986\"\n        y=\"1.42876\"\n      />\n      <rect\n        id=\"svg_9\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"32.039\"\n        y=\"3.89903\"\n      />\n      <rect\n        id=\"svg_10\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"80.75054\"\n        y=\"3.62876\"\n      />\n      <rect\n        id=\"svg_11\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"87.58249\"\n        y=\"3.49362\"\n      />\n      <rect\n        id=\"svg_12\"\n        fill=\"#b2b2b2\"\n        height=\"4.4\"\n        rx=\"1\"\n        stroke=\"null\"\n        width=\"3.925\"\n        x=\"94.6847\"\n        y=\"3.62876\"\n      />\n      <rect\n        id=\"svg_13\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.51892\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"45.63141\"\n        x=\"56.05157\"\n        y=\"14.613\"\n      />\n      <rect\n        id=\"svg_14\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"20.97838\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"22.82978\"\n        x=\"29.38527\"\n        y=\"14.613\"\n      />\n      <rect\n        id=\"svg_15\"\n        fill=\"currentColor\"\n        fill-opacity=\"0.08\"\n        height=\"21.65405\"\n        rx=\"2\"\n        stroke=\"null\"\n        width=\"72.45771\"\n        x=\"28.97986\"\n        y=\"39.48203\"\n      />\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/index.ts",
    "content": "export { default as PreferencesButton } from './preferences-button.vue';\nexport { default as Preferences } from './preferences.vue';\nexport * from './use-open-preferences';\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/preferences-button.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Settings } from '@vben/icons';\n\nimport { VbenIconButton } from '@vben-core/shadcn-ui';\n\nimport Preferences from './preferences.vue';\n\nconst emit = defineEmits<{ clearPreferencesAndLogout: [] }>();\n\nfunction clearPreferencesAndLogout() {\n  emit('clearPreferencesAndLogout');\n}\n</script>\n<template>\n  <Preferences @clear-preferences-and-logout=\"clearPreferencesAndLogout\">\n    <VbenIconButton class=\"hover:animate-[shrink_0.3s_ease-in-out]\">\n      <Settings class=\"size-4 text-foreground\" />\n    </VbenIconButton>\n  </Preferences>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue",
    "content": "<script setup lang=\"ts\">\nimport type { SupportedLanguagesType } from '@vben/locales';\nimport type {\n  BreadcrumbStyleType,\n  BuiltinThemeType,\n  ContentCompactType,\n  LayoutHeaderMenuAlignType,\n  LayoutHeaderModeType,\n  LayoutType,\n  NavigationStyleType,\n  PreferencesButtonPositionType,\n  ThemeModeType,\n} from '@vben/types';\n\nimport type { SegmentedItem } from '@vben-core/shadcn-ui';\n\nimport { computed, ref } from 'vue';\n\nimport { Copy, Pin, PinOff, RotateCw } from '@vben/icons';\nimport { $t, loadLocaleMessages } from '@vben/locales';\nimport {\n  clearCache,\n  preferences,\n  resetPreferences,\n  usePreferences,\n} from '@vben/preferences';\n\nimport { useVbenDrawer } from '@vben-core/popup-ui';\nimport {\n  VbenButton,\n  VbenIconButton,\n  VbenSegmented,\n} from '@vben-core/shadcn-ui';\nimport { globalShareState } from '@vben-core/shared/global-state';\n\nimport { useClipboard } from '@vueuse/core';\n\nimport {\n  Animation,\n  Block,\n  Breadcrumb,\n  BuiltinTheme,\n  ColorMode,\n  Content,\n  Copyright,\n  FontSize,\n  Footer,\n  General,\n  GlobalShortcutKeys,\n  Header,\n  Layout,\n  Navigation,\n  Radius,\n  Sidebar,\n  Tabbar,\n  Theme,\n  Widget,\n} from './blocks';\n\nconst emit = defineEmits<{ clearPreferencesAndLogout: [] }>();\n\nconst message = globalShareState.getMessage();\n\nconst appLocale = defineModel<SupportedLanguagesType>('appLocale');\nconst appDynamicTitle = defineModel<boolean>('appDynamicTitle');\nconst appLayout = defineModel<LayoutType>('appLayout');\nconst appColorGrayMode = defineModel<boolean>('appColorGrayMode');\nconst appColorWeakMode = defineModel<boolean>('appColorWeakMode');\nconst appContentCompact = defineModel<ContentCompactType>('appContentCompact');\nconst appWatermark = defineModel<boolean>('appWatermark');\nconst appWatermarkContent = defineModel<string>('appWatermarkContent');\nconst appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');\nconst appEnableCopyPreferences = defineModel<boolean>(\n  'appEnableCopyPreferences',\n);\nconst appEnableStickyPreferencesNavigationBar = defineModel<boolean>(\n  'appEnableStickyPreferencesNavigationBar',\n);\nconst appPreferencesButtonPosition = defineModel<PreferencesButtonPositionType>(\n  'appPreferencesButtonPosition',\n);\n\nconst transitionProgress = defineModel<boolean>('transitionProgress');\nconst transitionName = defineModel<string>('transitionName');\nconst transitionLoading = defineModel<boolean>('transitionLoading');\nconst transitionEnable = defineModel<boolean>('transitionEnable');\n\nconst themeColorPrimary = defineModel<string>('themeColorPrimary');\nconst themeBuiltinType = defineModel<BuiltinThemeType>('themeBuiltinType');\nconst themeMode = defineModel<ThemeModeType>('themeMode');\nconst themeRadius = defineModel<string>('themeRadius');\nconst themeFontSize = defineModel<number>('themeFontSize');\nconst themeSemiDarkSidebar = defineModel<boolean>('themeSemiDarkSidebar');\nconst themeSemiDarkSidebarSub = defineModel<boolean>('themeSemiDarkSidebarSub');\nconst themeSemiDarkHeader = defineModel<boolean>('themeSemiDarkHeader');\n\nconst sidebarEnable = defineModel<boolean>('sidebarEnable');\nconst sidebarWidth = defineModel<number>('sidebarWidth');\nconst sidebarDraggable = defineModel<boolean>('sidebarDraggable');\nconst sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');\nconst sidebarCollapsedShowTitle = defineModel<boolean>(\n  'sidebarCollapsedShowTitle',\n);\nconst sidebarAutoActivateChild = defineModel<boolean>(\n  'sidebarAutoActivateChild',\n);\nconst sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');\nconst sidebarCollapsedButton = defineModel<boolean>('sidebarCollapsedButton');\nconst sidebarFixedButton = defineModel<boolean>('sidebarFixedButton');\nconst headerEnable = defineModel<boolean>('headerEnable');\nconst headerMode = defineModel<LayoutHeaderModeType>('headerMode');\nconst headerMenuAlign =\n  defineModel<LayoutHeaderMenuAlignType>('headerMenuAlign');\n\nconst breadcrumbEnable = defineModel<boolean>('breadcrumbEnable');\nconst breadcrumbShowIcon = defineModel<boolean>('breadcrumbShowIcon');\nconst breadcrumbShowHome = defineModel<boolean>('breadcrumbShowHome');\nconst breadcrumbStyleType = defineModel<BreadcrumbStyleType>(\n  'breadcrumbStyleType',\n);\nconst breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');\n\nconst tabbarEnable = defineModel<boolean>('tabbarEnable');\nconst tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');\nconst tabbarShowMore = defineModel<boolean>('tabbarShowMore');\nconst tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');\nconst tabbarPersist = defineModel<boolean>('tabbarPersist');\nconst tabbarVisitHistory = defineModel<boolean>('tabbarVisitHistory');\nconst tabbarDraggable = defineModel<boolean>('tabbarDraggable');\nconst tabbarWheelable = defineModel<boolean>('tabbarWheelable');\nconst tabbarStyleType = defineModel<string>('tabbarStyleType');\nconst tabbarMaxCount = defineModel<number>('tabbarMaxCount');\nconst tabbarMiddleClickToClose = defineModel<boolean>(\n  'tabbarMiddleClickToClose',\n);\n\nconst navigationStyleType = defineModel<NavigationStyleType>(\n  'navigationStyleType',\n);\nconst navigationSplit = defineModel<boolean>('navigationSplit');\nconst navigationAccordion = defineModel<boolean>('navigationAccordion');\n\n// const logoVisible = defineModel<boolean>('logoVisible');\n\nconst footerEnable = defineModel<boolean>('footerEnable');\nconst footerFixed = defineModel<boolean>('footerFixed');\n\nconst copyrightSettingShow = defineModel<boolean>('copyrightSettingShow');\nconst copyrightEnable = defineModel<boolean>('copyrightEnable');\nconst copyrightCompanyName = defineModel<string>('copyrightCompanyName');\nconst copyrightCompanySiteLink = defineModel<string>(\n  'copyrightCompanySiteLink',\n);\nconst copyrightDate = defineModel<string>('copyrightDate');\nconst copyrightIcp = defineModel<string>('copyrightIcp');\nconst copyrightIcpLink = defineModel<string>('copyrightIcpLink');\n\nconst shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable');\nconst shortcutKeysGlobalSearch = defineModel<boolean>(\n  'shortcutKeysGlobalSearch',\n);\nconst shortcutKeysGlobalLogout = defineModel<boolean>(\n  'shortcutKeysGlobalLogout',\n);\n\nconst shortcutKeysGlobalLockScreen = defineModel<boolean>(\n  'shortcutKeysGlobalLockScreen',\n);\n\nconst widgetGlobalSearch = defineModel<boolean>('widgetGlobalSearch');\nconst widgetFullscreen = defineModel<boolean>('widgetFullscreen');\nconst widgetLanguageToggle = defineModel<boolean>('widgetLanguageToggle');\nconst widgetNotification = defineModel<boolean>('widgetNotification');\nconst widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');\nconst widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');\nconst widgetLockScreen = defineModel<boolean>('widgetLockScreen');\nconst widgetRefresh = defineModel<boolean>('widgetRefresh');\n\nconst {\n  diffPreference,\n  isDark,\n  isFullContent,\n  isHeaderNav,\n  isHeaderSidebarNav,\n  isMixedNav,\n  isSideMixedNav,\n  isSideMode,\n  isSideNav,\n} = usePreferences();\nconst { copy } = useClipboard({ legacy: true });\n\nconst [Drawer] = useVbenDrawer();\n\nconst activeTab = ref('appearance');\n\nconst tabs = computed((): SegmentedItem[] => {\n  return [\n    {\n      label: $t('preferences.appearance'),\n      value: 'appearance',\n    },\n    {\n      label: $t('preferences.layout'),\n      value: 'layout',\n    },\n    {\n      label: $t('preferences.shortcutKeys.title'),\n      value: 'shortcutKey',\n    },\n    {\n      label: $t('preferences.general'),\n      value: 'general',\n    },\n  ];\n});\n\nconst showBreadcrumbConfig = computed(() => {\n  return (\n    !isFullContent.value &&\n    !isMixedNav.value &&\n    !isHeaderNav.value &&\n    preferences.header.enable\n  );\n});\n\nasync function handleCopy() {\n  await copy(JSON.stringify(diffPreference.value, null, 2));\n\n  message.copyPreferencesSuccess?.(\n    $t('preferences.copyPreferencesSuccessTitle'),\n    $t('preferences.copyPreferencesSuccess'),\n  );\n}\n\nasync function handleClearCache() {\n  resetPreferences();\n  clearCache();\n  emit('clearPreferencesAndLogout');\n}\n\nasync function handleReset() {\n  if (!diffPreference.value) {\n    return;\n  }\n  resetPreferences();\n  await loadLocaleMessages(preferences.app.locale);\n}\n</script>\n\n<template>\n  <div>\n    <Drawer\n      :description=\"$t('preferences.subtitle')\"\n      :title=\"$t('preferences.title')\"\n      class=\"border-0! sm:max-w-sm\"\n    >\n      <template #extra>\n        <div class=\"flex items-center\">\n          <VbenIconButton\n            :disabled=\"!diffPreference\"\n            :tooltip=\"$t('preferences.resetTip')\"\n            class=\"relative\"\n            @click=\"handleReset\"\n          >\n            <span\n              v-if=\"diffPreference\"\n              class=\"absolute top-0.5 right-0.5 size-2 rounded-sm bg-primary\"\n            ></span>\n            <RotateCw class=\"size-4\" />\n          </VbenIconButton>\n          <VbenIconButton\n            :tooltip=\"\n              appEnableStickyPreferencesNavigationBar\n                ? $t('preferences.disableStickyPreferencesNavigationBar')\n                : $t('preferences.enableStickyPreferencesNavigationBar')\n            \"\n            class=\"relative\"\n            @click=\"\n              () =>\n                (appEnableStickyPreferencesNavigationBar =\n                  !appEnableStickyPreferencesNavigationBar)\n            \"\n          >\n            <PinOff\n              v-if=\"appEnableStickyPreferencesNavigationBar\"\n              class=\"size-4\"\n            />\n            <Pin v-else class=\"size-4\" />\n          </VbenIconButton>\n        </div>\n      </template>\n\n      <div>\n        <VbenSegmented\n          v-model=\"activeTab\"\n          :tabs=\"tabs\"\n          :class=\"{\n            'sticky-tabs-header': appEnableStickyPreferencesNavigationBar,\n          }\"\n        >\n          <template #general>\n            <Block :title=\"$t('preferences.general')\">\n              <General\n                v-model:app-dynamic-title=\"appDynamicTitle\"\n                v-model:app-enable-check-updates=\"appEnableCheckUpdates\"\n                v-model:app-enable-copy-preferences=\"appEnableCopyPreferences\"\n                v-model:app-locale=\"appLocale\"\n                v-model:app-watermark=\"appWatermark\"\n                v-model:app-watermark-content=\"appWatermarkContent\"\n              />\n            </Block>\n\n            <Block :title=\"$t('preferences.animation.title')\">\n              <Animation\n                v-model:transition-enable=\"transitionEnable\"\n                v-model:transition-loading=\"transitionLoading\"\n                v-model:transition-name=\"transitionName\"\n                v-model:transition-progress=\"transitionProgress\"\n              />\n            </Block>\n          </template>\n          <template #appearance>\n            <Block :title=\"$t('preferences.theme.title')\">\n              <Theme\n                v-model=\"themeMode\"\n                v-model:theme-semi-dark-header=\"themeSemiDarkHeader\"\n                v-model:theme-semi-dark-sidebar=\"themeSemiDarkSidebar\"\n                v-model:theme-semi-dark-sidebar-sub=\"themeSemiDarkSidebarSub\"\n              />\n            </Block>\n            <Block :title=\"$t('preferences.theme.builtin.title')\">\n              <BuiltinTheme\n                v-model=\"themeBuiltinType\"\n                v-model:theme-color-primary=\"themeColorPrimary\"\n                :is-dark=\"isDark\"\n              />\n            </Block>\n            <Block :title=\"$t('preferences.theme.radius')\">\n              <Radius v-model=\"themeRadius\" />\n            </Block>\n            <Block :title=\"$t('preferences.theme.fontSize')\">\n              <FontSize v-model=\"themeFontSize\" />\n            </Block>\n            <Block :title=\"$t('preferences.other')\">\n              <ColorMode\n                v-model:app-color-gray-mode=\"appColorGrayMode\"\n                v-model:app-color-weak-mode=\"appColorWeakMode\"\n              />\n            </Block>\n          </template>\n          <template #layout>\n            <Block :title=\"$t('preferences.layout')\">\n              <Layout v-model=\"appLayout\" />\n            </Block>\n            <Block :title=\"$t('preferences.content')\">\n              <Content v-model=\"appContentCompact\" />\n            </Block>\n\n            <Block :title=\"$t('preferences.sidebar.title')\">\n              <Sidebar\n                v-model:sidebar-auto-activate-child=\"sidebarAutoActivateChild\"\n                v-model:sidebar-draggable=\"sidebarDraggable\"\n                v-model:sidebar-collapsed=\"sidebarCollapsed\"\n                v-model:sidebar-collapsed-show-title=\"sidebarCollapsedShowTitle\"\n                v-model:sidebar-enable=\"sidebarEnable\"\n                v-model:sidebar-expand-on-hover=\"sidebarExpandOnHover\"\n                v-model:sidebar-width=\"sidebarWidth\"\n                v-model:sidebar-collapsed-button=\"sidebarCollapsedButton\"\n                v-model:sidebar-fixed-button=\"sidebarFixedButton\"\n                :current-layout=\"appLayout\"\n                :disabled=\"!isSideMode\"\n              />\n            </Block>\n\n            <Block :title=\"$t('preferences.header.title')\">\n              <Header\n                v-model:header-enable=\"headerEnable\"\n                v-model:header-menu-align=\"headerMenuAlign\"\n                v-model:header-mode=\"headerMode\"\n                :disabled=\"isFullContent\"\n              />\n            </Block>\n\n            <Block :title=\"$t('preferences.navigationMenu.title')\">\n              <Navigation\n                v-model:navigation-accordion=\"navigationAccordion\"\n                v-model:navigation-split=\"navigationSplit\"\n                v-model:navigation-style-type=\"navigationStyleType\"\n                :disabled=\"isFullContent\"\n                :disabled-navigation-split=\"!isMixedNav\"\n              />\n            </Block>\n\n            <Block :title=\"$t('preferences.breadcrumb.title')\">\n              <Breadcrumb\n                v-model:breadcrumb-enable=\"breadcrumbEnable\"\n                v-model:breadcrumb-hide-only-one=\"breadcrumbHideOnlyOne\"\n                v-model:breadcrumb-show-home=\"breadcrumbShowHome\"\n                v-model:breadcrumb-show-icon=\"breadcrumbShowIcon\"\n                v-model:breadcrumb-style-type=\"breadcrumbStyleType\"\n                :disabled=\"\n                  !showBreadcrumbConfig ||\n                  !(isSideNav || isSideMixedNav || isHeaderSidebarNav)\n                \"\n              />\n            </Block>\n            <Block :title=\"$t('preferences.tabbar.title')\">\n              <Tabbar\n                v-model:tabbar-draggable=\"tabbarDraggable\"\n                v-model:tabbar-enable=\"tabbarEnable\"\n                v-model:tabbar-persist=\"tabbarPersist\"\n                v-model:tabbar-visit-history=\"tabbarVisitHistory\"\n                v-model:tabbar-show-icon=\"tabbarShowIcon\"\n                v-model:tabbar-show-maximize=\"tabbarShowMaximize\"\n                v-model:tabbar-show-more=\"tabbarShowMore\"\n                v-model:tabbar-style-type=\"tabbarStyleType\"\n                v-model:tabbar-wheelable=\"tabbarWheelable\"\n                v-model:tabbar-max-count=\"tabbarMaxCount\"\n                v-model:tabbar-middle-click-to-close=\"tabbarMiddleClickToClose\"\n              />\n            </Block>\n            <Block :title=\"$t('preferences.widget.title')\">\n              <Widget\n                v-model:app-preferences-button-position=\"\n                  appPreferencesButtonPosition\n                \"\n                v-model:widget-fullscreen=\"widgetFullscreen\"\n                v-model:widget-global-search=\"widgetGlobalSearch\"\n                v-model:widget-language-toggle=\"widgetLanguageToggle\"\n                v-model:widget-lock-screen=\"widgetLockScreen\"\n                v-model:widget-notification=\"widgetNotification\"\n                v-model:widget-refresh=\"widgetRefresh\"\n                v-model:widget-sidebar-toggle=\"widgetSidebarToggle\"\n                v-model:widget-theme-toggle=\"widgetThemeToggle\"\n              />\n            </Block>\n            <Block :title=\"$t('preferences.footer.title')\">\n              <Footer\n                v-model:footer-enable=\"footerEnable\"\n                v-model:footer-fixed=\"footerFixed\"\n              />\n            </Block>\n            <Block\n              v-if=\"copyrightSettingShow\"\n              :title=\"$t('preferences.copyright.title')\"\n            >\n              <Copyright\n                v-model:copyright-company-name=\"copyrightCompanyName\"\n                v-model:copyright-company-site-link=\"copyrightCompanySiteLink\"\n                v-model:copyright-date=\"copyrightDate\"\n                v-model:copyright-enable=\"copyrightEnable\"\n                v-model:copyright-icp=\"copyrightIcp\"\n                v-model:copyright-icp-link=\"copyrightIcpLink\"\n                :disabled=\"!footerEnable\"\n              />\n            </Block>\n          </template>\n\n          <template #shortcutKey>\n            <Block :title=\"$t('preferences.shortcutKeys.global')\">\n              <GlobalShortcutKeys\n                v-model:shortcut-keys-enable=\"shortcutKeysEnable\"\n                v-model:shortcut-keys-global-search=\"shortcutKeysGlobalSearch\"\n                v-model:shortcut-keys-lock-screen=\"shortcutKeysGlobalLockScreen\"\n                v-model:shortcut-keys-logout=\"shortcutKeysGlobalLogout\"\n              />\n            </Block>\n          </template>\n        </VbenSegmented>\n      </div>\n\n      <template #footer>\n        <VbenButton\n          v-if=\"appEnableCopyPreferences\"\n          :disabled=\"!diffPreference\"\n          class=\"mx-4 w-full\"\n          size=\"sm\"\n          variant=\"default\"\n          @click=\"handleCopy\"\n        >\n          <Copy class=\"mr-2 size-3\" />\n          {{ $t('preferences.copyPreferences') }}\n        </VbenButton>\n        <VbenButton\n          :disabled=\"!diffPreference\"\n          class=\"mr-4 w-full\"\n          size=\"sm\"\n          variant=\"ghost\"\n          @click=\"handleClearCache\"\n        >\n          {{ $t('preferences.clearAndLogout') }}\n        </VbenButton>\n      </template>\n    </Drawer>\n  </div>\n</template>\n\n<style scoped>\n:deep(.sticky-tabs-header [role='tablist']) {\n  @apply -top-3 z-9999 sticky;\n}\n</style>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/preferences.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { Settings } from '@vben/icons';\nimport { $t, loadLocaleMessages } from '@vben/locales';\nimport { preferences, updatePreferences } from '@vben/preferences';\nimport { capitalizeFirstLetter } from '@vben/utils';\n\nimport { useVbenDrawer } from '@vben-core/popup-ui';\nimport { VbenButton } from '@vben-core/shadcn-ui';\n\nimport PreferencesDrawer from './preferences-drawer.vue';\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  connectedComponent: PreferencesDrawer,\n});\n\n/**\n * preferences 转成 vue props\n * preferences.widget.fullscreen=>widgetFullscreen\n */\nconst attrs = computed(() => {\n  const result: Record<string, any> = {};\n  for (const [key, value] of Object.entries(preferences)) {\n    for (const [subKey, subValue] of Object.entries(value)) {\n      result[`${key}${capitalizeFirstLetter(subKey)}`] = subValue;\n    }\n  }\n  return result;\n});\n\n/**\n * preferences 转成 vue listener\n * preferences.widget.fullscreen=>@update:widgetFullscreen\n */\nconst listen = computed(() => {\n  const result: Record<string, any> = {};\n  for (const [key, value] of Object.entries(preferences)) {\n    if (typeof value === 'object') {\n      for (const subKey of Object.keys(value)) {\n        result[`update:${key}${capitalizeFirstLetter(subKey)}`] = (\n          val: any,\n        ) => {\n          updatePreferences({ [key]: { [subKey]: val } });\n          if (key === 'app' && subKey === 'locale') {\n            loadLocaleMessages(val);\n          }\n        };\n      }\n    } else {\n      result[key] = value;\n    }\n  }\n  return result;\n});\n</script>\n<template>\n  <div>\n    <Drawer v-bind=\"{ ...$attrs, ...attrs }\" v-on=\"listen\" />\n\n    <div @click=\"() => drawerApi.open()\">\n      <slot>\n        <VbenButton\n          :title=\"$t('preferences.title')\"\n          class=\"flex-col-center size-10 cursor-pointer rounded-l-lg rounded-r-none border-none bg-primary\"\n        >\n          <Settings class=\"size-5\" />\n        </VbenButton>\n      </slot>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/preferences/use-open-preferences.ts",
    "content": "import { ref } from 'vue';\n\nconst openPreferences = ref(false);\n\nfunction useOpenPreferences() {\n  function handleOpenPreference() {\n    openPreferences.value = true;\n  }\n\n  return {\n    handleOpenPreference,\n    openPreferences,\n  };\n}\n\nexport { useOpenPreferences };\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/theme-toggle/index.ts",
    "content": "export { default as ThemeToggle } from './theme-toggle.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/theme-toggle/theme-button.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed, nextTick } from 'vue';\n\nimport { VbenButton } from '@vben-core/shadcn-ui';\n\ninterface Props {\n  /**\n   * 类型\n   */\n  type?: 'icon' | 'normal';\n}\n\ndefineOptions({\n  name: 'ThemeToggleButton',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  type: 'normal',\n});\n\nconst isDark = defineModel<boolean>();\n\nconst theme = computed(() => {\n  return isDark.value ? 'light' : 'dark';\n});\n\nconst bindProps = computed(() => {\n  const type = props.type;\n\n  return type === 'normal'\n    ? {\n        variant: 'heavy' as const,\n      }\n    : {\n        class: 'rounded-full',\n        size: 'icon' as const,\n        style: { padding: '7px' },\n        variant: 'icon' as const,\n      };\n});\n\nfunction toggleTheme(event: MouseEvent) {\n  const isAppearanceTransition =\n    // @ts-expect-error - startViewTransition is not available in the current DOM lib target\n    document.startViewTransition &&\n    !window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n  if (!isAppearanceTransition || !event) {\n    isDark.value = !isDark.value;\n    return;\n  }\n  const x = event.clientX;\n  const y = event.clientY;\n  const endRadius = Math.hypot(\n    Math.max(x, innerWidth - x),\n    Math.max(y, innerHeight - y),\n  );\n  const transition = document.startViewTransition(async () => {\n    isDark.value = !isDark.value;\n    await nextTick();\n  });\n  transition.ready.then(() => {\n    const clipPath = [\n      `circle(0px at ${x}px ${y}px)`,\n      `circle(${endRadius}px at ${x}px ${y}px)`,\n    ];\n    const animate = document.documentElement.animate(\n      {\n        clipPath: isDark.value ? [...clipPath].toReversed() : clipPath,\n      },\n      {\n        duration: 450,\n        easing: 'ease-in',\n        pseudoElement: isDark.value\n          ? '::view-transition-old(root)'\n          : '::view-transition-new(root)',\n      },\n    );\n    animate.onfinish = () => {\n      transition.skipTransition();\n    };\n  });\n}\n</script>\n\n<template>\n  <VbenButton\n    :aria-label=\"theme\"\n    :class=\"[`is-${theme}`]\"\n    aria-live=\"polite\"\n    class=\"theme-toggle cursor-pointer border-none bg-none hover:animate-[shrink_0.3s_ease-in-out]\"\n    v-bind=\"bindProps\"\n    @click.stop=\"toggleTheme\"\n  >\n    <svg aria-hidden=\"true\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\">\n      <mask id=\"theme-toggle-moon\" class=\"theme-toggle__moon\">\n        <rect fill=\"white\" height=\"100%\" width=\"100%\" x=\"0\" y=\"0\" />\n        <circle cx=\"40\" cy=\"8\" fill=\"black\" r=\"11\" />\n      </mask>\n      <circle\n        id=\"sun\"\n        class=\"theme-toggle__sun fill-foreground/90\"\n        cx=\"12\"\n        cy=\"12\"\n        mask=\"url(#theme-toggle-moon)\"\n        r=\"11\"\n      />\n      <g class=\"theme-toggle__sun-beams stroke-foreground/90 stroke-2\">\n        <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\" />\n        <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\" />\n        <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\" />\n        <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\" />\n        <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\" />\n        <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\" />\n        <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\" />\n        <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\" />\n      </g>\n    </svg>\n  </VbenButton>\n</template>\n\n<style scoped>\n@reference \"@vben/tailwind-config/theme\";\n\n.theme-toggle__moon > circle {\n  transition: transform 0.5s cubic-bezier(0, 0, 0.3, 1);\n}\n\n.theme-toggle__sun {\n  stroke: none;\n  transform-origin: center center;\n  transition: transform 1.6s cubic-bezier(0.25, 0, 0.2, 1);\n}\n\n.theme-toggle__sun-beams {\n  transform-origin: center center;\n  transition:\n    transform 1.6s cubic-bezier(0.5, 1.5, 0.75, 1.25),\n    opacity 0.6s cubic-bezier(0.25, 0, 0.3, 1);\n}\n\n.theme-toggle.is-light .theme-toggle__sun {\n  @apply scale-50;\n}\n\n.theme-toggle.is-light .theme-toggle__sun-beams {\n  transform: rotateZ(0.25turn);\n}\n\n.theme-toggle.is-dark .theme-toggle__moon > circle {\n  transform: translateX(-20px);\n}\n\n.theme-toggle.is-dark .theme-toggle__sun-beams {\n  @apply opacity-0;\n}\n\n.theme-toggle:hover > svg .theme-toggle__sun {\n  fill: hsl(var(--foreground));\n}\n\n.theme-toggle:hover > svg .theme-toggle__sun-beams {\n  stroke: hsl(var(--foreground));\n}\n</style>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/theme-toggle/theme-toggle.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { ThemeModeType } from '@vben/types';\n\nimport { MoonStar, Sun, SunMoon } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport {\n  preferences,\n  updatePreferences,\n  usePreferences,\n} from '@vben/preferences';\n\nimport {\n  ToggleGroup,\n  ToggleGroupItem,\n  VbenTooltip,\n} from '@vben-core/shadcn-ui';\n\nimport ThemeButton from './theme-button.vue';\n\ndefineOptions({\n  name: 'ThemeToggle',\n});\n\nwithDefaults(defineProps<{ shouldOnHover?: boolean }>(), {\n  shouldOnHover: false,\n});\n\nfunction handleChange(isDark: boolean | undefined) {\n  updatePreferences({\n    theme: { mode: isDark ? 'dark' : 'light' },\n  });\n}\n\nconst { isDark } = usePreferences();\n\nconst PRESETS = [\n  {\n    icon: Sun,\n    name: 'light',\n    title: $t('preferences.theme.light'),\n  },\n  {\n    icon: MoonStar,\n    name: 'dark',\n    title: $t('preferences.theme.dark'),\n  },\n  {\n    icon: SunMoon,\n    name: 'auto',\n    title: $t('preferences.followSystem'),\n  },\n];\n</script>\n<template>\n  <div>\n    <VbenTooltip :disabled=\"!shouldOnHover\" side=\"bottom\">\n      <template #trigger>\n        <ThemeButton\n          :model-value=\"isDark\"\n          type=\"icon\"\n          @update:model-value=\"handleChange\"\n        />\n      </template>\n      <ToggleGroup\n        :model-value=\"preferences.theme.mode\"\n        class=\"gap-2\"\n        type=\"single\"\n        variant=\"outline\"\n        @update:model-value=\"\n          (val) => updatePreferences({ theme: { mode: val as ThemeModeType } })\n        \"\n      >\n        <ToggleGroupItem\n          v-for=\"item in PRESETS\"\n          :key=\"item.name\"\n          :value=\"item.name\"\n        >\n          <component :is=\"item.icon\" class=\"size-5\" />\n        </ToggleGroupItem>\n      </ToggleGroup>\n    </VbenTooltip>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/timezone/index.ts",
    "content": "export { default as TimezoneButton } from './timezone-button.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/timezone/timezone-button.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref, unref } from 'vue';\n\nimport { createIconifyIcon } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport { useTimezoneStore } from '@vben/stores';\n\nimport { useVbenModal } from '@vben-core/popup-ui';\nimport {\n  RadioGroup,\n  RadioGroupItem,\n  VbenIconButton,\n} from '@vben-core/shadcn-ui';\n\nconst TimezoneIcon = createIconifyIcon('fluent-mdl2:world-clock');\n\nconst timezoneStore = useTimezoneStore();\n\nconst timezoneRef = ref<string | undefined>();\n\nconst timezoneOptionsRef = ref<\n  {\n    label: string;\n    value: string;\n  }[]\n>([]);\n\nconst [Modal, modalApi] = useVbenModal({\n  fullscreenButton: false,\n  onConfirm: async () => {\n    try {\n      modalApi.setState({ confirmLoading: true });\n      const timezone = unref(timezoneRef);\n      if (timezone) {\n        await timezoneStore.setTimezone(timezone);\n      }\n      modalApi.close();\n    } finally {\n      modalApi.setState({ confirmLoading: false });\n    }\n  },\n  async onOpenChange(isOpen) {\n    if (isOpen) {\n      timezoneRef.value = unref(timezoneStore.timezone);\n      timezoneOptionsRef.value = await timezoneStore.getTimezoneOptions();\n    }\n  },\n});\n\nconst handleClick = () => {\n  modalApi.open();\n};\n</script>\n\n<template>\n  <div>\n    <VbenIconButton\n      :tooltip=\"$t('ui.widgets.timezone.setTimezone')\"\n      class=\"hover:animate-[shrink_0.3s_ease-in-out]\"\n      @click=\"handleClick\"\n    >\n      <TimezoneIcon class=\"size-4 text-foreground\" />\n    </VbenIconButton>\n    <Modal :title=\"$t('ui.widgets.timezone.setTimezone')\">\n      <div class=\"timezone-container\">\n        <RadioGroup v-model=\"timezoneRef\" class=\"flex flex-col gap-2\">\n          <div\n            class=\"flex cursor-pointer items-center gap-2\"\n            v-for=\"item in timezoneOptionsRef\"\n            :key=\"`container${item.value}`\"\n          >\n            <RadioGroupItem :id=\"item.value\" :value=\"item.value\" />\n            <label :for=\"item.value\" class=\"cursor-pointer\">{{\n              item.label\n            }}</label>\n          </div>\n        </RadioGroup>\n      </div>\n    </Modal>\n  </div>\n</template>\n\n<style scoped>\n.timezone-container {\n  @apply pl-5;\n}\n</style>\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/user-dropdown/index.ts",
    "content": "export { default as UserDropdown } from './user-dropdown.vue';\n"
  },
  {
    "path": "packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Component } from 'vue';\n\nimport type { AnyFunction } from '@vben/types';\n\nimport { computed, useTemplateRef, watch } from 'vue';\n\nimport { useHoverToggle } from '@vben/hooks';\nimport { LockKeyhole, LogOut } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport { preferences, usePreferences } from '@vben/preferences';\nimport { useAccessStore } from '@vben/stores';\nimport { isWindowsOs } from '@vben/utils';\n\nimport { useVbenModal } from '@vben-core/popup-ui';\nimport {\n  Badge,\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n  VbenAvatar,\n  VbenIcon,\n} from '@vben-core/shadcn-ui';\n\nimport { useMagicKeys, whenever } from '@vueuse/core';\n\nimport { LockScreenModal } from '../lock-screen';\n\ninterface Props {\n  /**\n   * 头像\n   */\n  avatar?: string;\n  /**\n   * @zh_CN 描述\n   */\n  description?: string;\n  /**\n   * 是否启用快捷键\n   */\n  enableShortcutKey?: boolean;\n  /**\n   * 菜单数组\n   */\n  menus?: Array<{\n    handler: AnyFunction;\n    icon?: Component | Function | string;\n    text: string;\n  }>;\n\n  /**\n   * 标签文本\n   */\n  tagText?: string;\n  /**\n   * 文本\n   */\n  text?: string;\n  /** 触发方式 */\n  trigger?: 'both' | 'click' | 'hover';\n  /** hover触发时，延迟响应的时间 */\n  hoverDelay?: number;\n}\n\ndefineOptions({\n  name: 'UserDropdown',\n});\n\nconst props = withDefaults(defineProps<Props>(), {\n  avatar: '',\n  description: '',\n  enableShortcutKey: true,\n  menus: () => [],\n  showShortcutKey: true,\n  tagText: '',\n  text: '',\n  trigger: 'click',\n  hoverDelay: 500,\n});\n\nconst emit = defineEmits<{ logout: [] }>();\n\nconst { globalLockScreenShortcutKey, globalLogoutShortcutKey } =\n  usePreferences();\nconst accessStore = useAccessStore();\nconst [LockModal, lockModalApi] = useVbenModal({\n  connectedComponent: LockScreenModal,\n});\nconst [LogoutModal, logoutModalApi] = useVbenModal({\n  onConfirm() {\n    handleSubmitLogout();\n  },\n});\n\nconst refTrigger = useTemplateRef('refTrigger');\nconst refContent = useTemplateRef('refContent');\nconst [openPopover, hoverWatcher] = useHoverToggle(\n  [refTrigger, refContent],\n  () => props.hoverDelay,\n);\n\nwatch(\n  () => props.trigger === 'hover' || props.trigger === 'both',\n  (val) => {\n    if (val) {\n      hoverWatcher.enable();\n    } else {\n      hoverWatcher.disable();\n    }\n  },\n  {\n    immediate: true,\n  },\n);\n\nconst altView = computed(() => (isWindowsOs() ? 'Alt' : '⌥'));\n\nconst enableLogoutShortcutKey = computed(() => {\n  return props.enableShortcutKey && globalLogoutShortcutKey.value;\n});\n\nconst enableLockScreenShortcutKey = computed(() => {\n  return props.enableShortcutKey && globalLockScreenShortcutKey.value;\n});\n\nconst enableShortcutKey = computed(() => {\n  return props.enableShortcutKey && preferences.shortcutKeys.enable;\n});\n\nfunction handleOpenLock() {\n  lockModalApi.open();\n}\n\nfunction handleSubmitLock(lockScreenPassword: string) {\n  lockModalApi.close();\n  accessStore.lockScreen(lockScreenPassword);\n}\n\nfunction handleLogout() {\n  // emit\n  logoutModalApi.open();\n  openPopover.value = false;\n}\n\nfunction handleSubmitLogout() {\n  emit('logout');\n  logoutModalApi.close();\n}\n\nif (enableShortcutKey.value) {\n  const keys = useMagicKeys();\n  const logoutKey = keys['Alt+KeyQ'];\n  const lockKey = keys['Alt+KeyL'];\n\n  if (logoutKey) {\n    whenever(logoutKey, () => {\n      if (enableLogoutShortcutKey.value) {\n        handleLogout();\n      }\n    });\n  }\n\n  if (lockKey) {\n    whenever(lockKey, () => {\n      if (enableLockScreenShortcutKey.value) {\n        handleOpenLock();\n      }\n    });\n  }\n}\n</script>\n\n<template>\n  <LockModal\n    v-if=\"preferences.widget.lockScreen\"\n    :avatar=\"avatar\"\n    :text=\"text\"\n    @submit=\"handleSubmitLock\"\n  />\n\n  <LogoutModal\n    :cancel-text=\"$t('common.cancel')\"\n    :confirm-text=\"$t('common.confirm')\"\n    :fullscreen-button=\"false\"\n    :title=\"$t('common.prompt')\"\n    centered\n    content-class=\"px-8 min-h-10\"\n    footer-class=\"border-none mb-3 mr-3\"\n    header-class=\"border-none\"\n  >\n    {{ $t('ui.widgets.logoutTip') }}\n  </LogoutModal>\n\n  <DropdownMenu v-model:open=\"openPopover\">\n    <DropdownMenuTrigger ref=\"refTrigger\" :disabled=\"props.trigger === 'hover'\">\n      <div class=\"mr-2 ml-1 cursor-pointer rounded-full p-1.5 hover:bg-accent\">\n        <div class=\"flex-center hover:text-accent-foreground\">\n          <VbenAvatar :alt=\"text\" :src=\"avatar\" class=\"size-8\" dot />\n        </div>\n      </div>\n    </DropdownMenuTrigger>\n    <DropdownMenuContent class=\"mr-2 min-w-60 p-0 pb-1\">\n      <div ref=\"refContent\">\n        <DropdownMenuLabel class=\"flex items-center p-3\">\n          <VbenAvatar\n            :alt=\"text\"\n            :src=\"avatar\"\n            class=\"size-12\"\n            dot\n            dot-class=\"bottom-0 right-1 border-2 size-4 bg-green-500\"\n          />\n          <div class=\"ml-2 w-full\">\n            <div\n              v-if=\"tagText || text || $slots.tagText\"\n              class=\"mb-1 flex items-center text-sm font-medium text-foreground\"\n            >\n              {{ text }}\n              <slot name=\"tagText\">\n                <Badge v-if=\"tagText\" class=\"ml-2 text-green-400\">\n                  {{ tagText }}\n                </Badge>\n              </slot>\n            </div>\n            <div class=\"text-xs font-normal text-muted-foreground\">\n              {{ description }}\n            </div>\n          </div>\n        </DropdownMenuLabel>\n        <DropdownMenuSeparator v-if=\"menus?.length\" />\n        <DropdownMenuItem\n          v-for=\"menu in menus\"\n          :key=\"menu.text\"\n          class=\"mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8\"\n          @click=\"menu.handler\"\n        >\n          <VbenIcon :icon=\"menu.icon\" class=\"mr-2 size-4\" />\n          {{ menu.text }}\n        </DropdownMenuItem>\n        <DropdownMenuSeparator />\n        <DropdownMenuItem\n          v-if=\"preferences.widget.lockScreen\"\n          class=\"mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8\"\n          @click=\"handleOpenLock\"\n        >\n          <LockKeyhole class=\"mr-2 size-4\" />\n          {{ $t('ui.widgets.lockScreen.title') }}\n          <DropdownMenuShortcut v-if=\"enableLockScreenShortcutKey\">\n            {{ altView }} L\n          </DropdownMenuShortcut>\n        </DropdownMenuItem>\n        <DropdownMenuSeparator v-if=\"preferences.widget.lockScreen\" />\n        <DropdownMenuItem\n          class=\"mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8\"\n          @click=\"handleLogout\"\n        >\n          <LogOut class=\"mr-2 size-4\" />\n          {{ $t('common.logout') }}\n          <DropdownMenuShortcut v-if=\"enableLogoutShortcutKey\">\n            {{ altView }} Q\n          </DropdownMenuShortcut>\n        </DropdownMenuItem>\n      </div>\n    </DropdownMenuContent>\n  </DropdownMenu>\n</template>\n"
  },
  {
    "path": "packages/effects/layouts/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/effects/plugins/README.md",
    "content": "# @vben/plugins\n\n该目录用于存放项目中集成的第三方库及其相关插件。每个插件都包含了可重用的逻辑、配置和组件，方便在项目中进行统一管理和调用。\n\n## 注意\n\n所有的第三方插件都必须以 `subpath` 形式引入，例：\n\n以 `echarts` 为例，引入方式如下：\n\n**packages.json**\n\n```json\n\"exports\": {\n    \"./echarts\": {\n      \"types\": \"./src/echarts/index.ts\",\n      \"default\": \"./src/echarts/index.ts\"\n    }\n  }\n```\n\n**使用方式**\n\n```ts\nimport { useEcharts } from '@vben/plugins/echarts';\n```\n\n这样做的好处是，应用可以自行选择是否使用插件，而不会因为插件的引入及副作用而导致打包体积增大，只引入需要的插件即可。\n"
  },
  {
    "path": "packages/effects/plugins/package.json",
    "content": "{\n  \"name\": \"@vben/plugins\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/effects/plugins\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \"./echarts\": {\n      \"types\": \"./src/echarts/index.ts\",\n      \"default\": \"./src/echarts/index.ts\"\n    },\n    \"./vxe-table\": {\n      \"types\": \"./src/vxe-table/index.ts\",\n      \"default\": \"./src/vxe-table/index.ts\"\n    },\n    \"./motion\": {\n      \"types\": \"./src/motion/index.ts\",\n      \"default\": \"./src/motion/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/design\": \"workspace:*\",\n    \"@vben-core/form-ui\": \"workspace:*\",\n    \"@vben-core/shadcn-ui\": \"workspace:*\",\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben/hooks\": \"workspace:*\",\n    \"@vben/icons\": \"workspace:*\",\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/preferences\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"@vueuse/motion\": \"catalog:\",\n    \"echarts\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vxe-pc-ui\": \"catalog:\",\n    \"vxe-table\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/effects/plugins/src/echarts/echarts-ui.vue",
    "content": "<script setup lang=\"ts\">\ninterface Props {\n  height?: string;\n  width?: string;\n}\n\nwithDefaults(defineProps<Props>(), {\n  height: '300px',\n  width: '100%',\n});\n</script>\n\n<template>\n  <div v-bind=\"$attrs\" :style=\"{ height, width }\"></div>\n</template>\n"
  },
  {
    "path": "packages/effects/plugins/src/echarts/echarts.ts",
    "content": "import type {\n  // 系列类型的定义后缀都为 SeriesOption\n  BarSeriesOption,\n  LineSeriesOption,\n} from 'echarts/charts';\nimport type {\n  DatasetComponentOption,\n  GridComponentOption,\n  // 组件类型的定义后缀都为 ComponentOption\n  TitleComponentOption,\n  TooltipComponentOption,\n} from 'echarts/components';\nimport type { ComposeOption } from 'echarts/core';\n\nimport { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts';\nimport {\n  // 数据集组件\n  DatasetComponent,\n  GridComponent,\n  LegendComponent,\n  TitleComponent,\n  ToolboxComponent,\n  TooltipComponent,\n  // 内置数据转换器组件 (filter, sort)\n  TransformComponent,\n} from 'echarts/components';\nimport * as echarts from 'echarts/core';\nimport {\n  LabelLayout,\n  LegacyGridContainLabel,\n  UniversalTransition,\n} from 'echarts/features';\nimport { CanvasRenderer } from 'echarts/renderers';\n\n// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型\nexport type ECOption = ComposeOption<\n  | BarSeriesOption\n  | DatasetComponentOption\n  | GridComponentOption\n  | LineSeriesOption\n  | TitleComponentOption\n  | TooltipComponentOption\n>;\n\n// 注册必须的组件\necharts.use([\n  TitleComponent,\n  PieChart,\n  RadarChart,\n  TooltipComponent,\n  GridComponent,\n  DatasetComponent,\n  TransformComponent,\n  BarChart,\n  LineChart,\n  LabelLayout,\n  LegacyGridContainLabel,\n  UniversalTransition,\n  CanvasRenderer,\n  LegendComponent,\n  ToolboxComponent,\n]);\n\nexport default echarts;\n"
  },
  {
    "path": "packages/effects/plugins/src/echarts/index.ts",
    "content": "export * from './echarts';\nexport { default as EchartsUI } from './echarts-ui.vue';\nexport * from './use-echarts';\n"
  },
  {
    "path": "packages/effects/plugins/src/echarts/use-echarts.ts",
    "content": "import type { EChartsOption } from 'echarts';\n\nimport type { Ref } from 'vue';\n\nimport type { Nullable } from '@vben/types';\n\nimport type EchartsUI from './echarts-ui.vue';\n\nimport {\n  computed,\n  nextTick,\n  onActivated,\n  onBeforeUnmount,\n  onDeactivated,\n  onMounted,\n  ref,\n  unref,\n  watch,\n} from 'vue';\n\nimport { usePreferences } from '@vben/preferences';\n\nimport {\n  tryOnUnmounted,\n  useDebounceFn,\n  useResizeObserver,\n  useTimeoutFn,\n  useWindowSize,\n} from '@vueuse/core';\n\nimport echarts from './echarts';\n\ntype EchartsUIType = typeof EchartsUI | undefined;\n\ntype EchartsThemeType = 'dark' | 'light' | null;\n\nfunction useEcharts(chartRef: Ref<EchartsUIType>) {\n  let chartInstance: echarts.ECharts | null = null;\n  let cacheOptions: EChartsOption = {};\n  // echarts是否处于激活状态\n  const isActiveRef = ref(false);\n\n  const { isDark } = usePreferences();\n  const { height, width } = useWindowSize();\n  const resizeHandler: () => void = useDebounceFn(resize, 200);\n\n  const getChartEl = (): HTMLElement | null => {\n    const refValue = chartRef?.value as unknown;\n    if (!refValue) return null;\n    if (refValue instanceof HTMLElement) {\n      return refValue;\n    }\n    const maybeComponent = refValue as { $el?: HTMLElement };\n    return maybeComponent.$el ?? null;\n  };\n\n  onMounted(() => (isActiveRef.value = true));\n  onActivated(() => (isActiveRef.value = true));\n  onDeactivated(() => (isActiveRef.value = false));\n  onBeforeUnmount(() => (isActiveRef.value = false));\n\n  const isElHidden = (el: HTMLElement | null): boolean => {\n    if (!el) return true;\n    return el.offsetHeight === 0 || el.offsetWidth === 0;\n  };\n\n  const getOptions = computed((): EChartsOption => {\n    if (!isDark.value) {\n      return {};\n    }\n\n    return {\n      backgroundColor: 'transparent',\n    };\n  });\n\n  const initCharts = (t?: EchartsThemeType) => {\n    const el = chartRef?.value?.$el;\n    if (!el) {\n      return;\n    }\n    chartInstance = echarts.init(el, t || isDark.value ? 'dark' : null);\n\n    return chartInstance;\n  };\n\n  const renderEcharts = (\n    options: EChartsOption,\n    clear = true,\n  ): Promise<Nullable<echarts.ECharts>> => {\n    if (!unref(isActiveRef)) {\n      return Promise.resolve(null);\n    }\n    cacheOptions = options;\n    const currentOptions = {\n      ...options,\n      ...getOptions.value,\n    };\n    return new Promise((resolve) => {\n      if (chartRef.value?.offsetHeight === 0) {\n        useTimeoutFn(async () => {\n          resolve(await renderEcharts(currentOptions));\n        }, 30);\n        return;\n      }\n      nextTick(() => {\n        const el = getChartEl();\n        if (isElHidden(el)) {\n          useTimeoutFn(async () => {\n            resolve(await renderEcharts(currentOptions));\n          }, 30);\n          return;\n        }\n        useTimeoutFn(() => {\n          if (!chartInstance || chartInstance?.getDom() !== el) {\n            chartInstance?.dispose();\n            const instance = initCharts();\n            if (!instance) return;\n            chartInstance = instance;\n          }\n          clear && chartInstance?.clear();\n          chartInstance?.setOption(currentOptions);\n          resolve(chartInstance);\n        }, 30);\n      });\n    });\n  };\n\n  const updateData = (\n    option: EChartsOption,\n    notMerge = false, // false = 合并（保留动画），true = 完全替换\n    lazyUpdate = false, // true 时不立即重绘，适合短时间内多次调用\n  ): Promise<echarts.ECharts | null> => {\n    return new Promise((resolve) => {\n      nextTick(() => {\n        if (!chartInstance) {\n          // 还没初始化 → 当作首次渲染\n          renderEcharts(option).then(resolve);\n          return;\n        }\n\n        // 合并你原有的全局配置（比如 backgroundColor）\n        const finalOption = {\n          ...option,\n          ...getOptions.value,\n        };\n\n        chartInstance.setOption(finalOption, {\n          notMerge,\n          lazyUpdate,\n          // silent: true,     // 如果追求极致性能可开启（关闭所有事件）\n        });\n\n        resolve(chartInstance);\n      });\n    });\n  };\n\n  function resize() {\n    const el = getChartEl();\n    if (isElHidden(el)) {\n      return;\n    }\n    chartInstance?.resize({\n      animation: {\n        duration: 300,\n        easing: 'quadraticIn',\n      },\n    });\n  }\n\n  watch([width, height], () => {\n    resizeHandler?.();\n  });\n\n  useResizeObserver(chartRef as never, resizeHandler);\n\n  watch([isDark, isActiveRef], () => {\n    if (chartInstance && unref(isActiveRef)) {\n      chartInstance.dispose();\n      initCharts();\n      renderEcharts(cacheOptions);\n      resize();\n    }\n  });\n\n  tryOnUnmounted(() => {\n    // 销毁实例，释放资源\n    chartInstance?.dispose();\n  });\n  return {\n    isActive: isActiveRef,\n    renderEcharts,\n    resize,\n    updateData,\n    getChartInstance: () => chartInstance,\n  };\n}\n\nexport { useEcharts };\n\nexport type { EchartsUIType };\n"
  },
  {
    "path": "packages/effects/plugins/src/motion/index.ts",
    "content": "export * from './types';\n\nexport {\n  MotionComponent as Motion,\n  MotionDirective,\n  MotionGroupComponent as MotionGroup,\n  MotionPlugin,\n} from '@vueuse/motion';\n"
  },
  {
    "path": "packages/effects/plugins/src/motion/types.ts",
    "content": "export const MotionPresets = [\n  'fade',\n  'fadeVisible',\n  'fadeVisibleOnce',\n  'rollBottom',\n  'rollLeft',\n  'rollRight',\n  'rollTop',\n  'rollVisibleBottom',\n  'rollVisibleLeft',\n  'rollVisibleRight',\n  'rollVisibleTop',\n  'pop',\n  'popVisible',\n  'popVisibleOnce',\n  'slideBottom',\n  'slideLeft',\n  'slideRight',\n  'slideTop',\n  'slideVisibleBottom',\n  'slideVisibleLeft',\n  'slideVisibleRight',\n  'slideVisibleTop',\n] as const;\n\nexport type MotionPreset = (typeof MotionPresets)[number];\n"
  },
  {
    "path": "packages/effects/plugins/src/vxe-table/api.ts",
    "content": "import type { VxeGridInstance } from 'vxe-table';\n\nimport type { ExtendedFormApi } from '@vben-core/form-ui';\n\nimport type { VxeGridProps } from './types';\n\nimport { toRaw } from 'vue';\n\nimport { Store } from '@vben-core/shared/store';\nimport {\n  bindMethods,\n  isBoolean,\n  isFunction,\n  mergeWithArrayOverride,\n  StateHandler,\n} from '@vben-core/shared/utils';\n\nfunction getDefaultState(): VxeGridProps {\n  return {\n    class: '',\n    gridClass: '',\n    gridOptions: {},\n    gridEvents: {},\n    formOptions: undefined,\n    showSearchForm: true,\n  };\n}\n\nexport class VxeGridApi<T extends Record<string, any> = any> {\n  public formApi = {} as ExtendedFormApi;\n\n  // private prevState: null | VxeGridProps = null;\n  public grid = {} as VxeGridInstance<T>;\n  public state: null | VxeGridProps<T> = null;\n\n  public store: Store<VxeGridProps<T>>;\n\n  private isMounted = false;\n\n  private stateHandler: StateHandler;\n\n  constructor(options: VxeGridProps = {}) {\n    const storeState = { ...options };\n\n    const defaultState = getDefaultState();\n    this.store = new Store<VxeGridProps>(\n      mergeWithArrayOverride(storeState, defaultState),\n    );\n\n    this.store.subscribe((state) => {\n      // this.prevState = this.state;\n      this.state = state;\n    });\n\n    this.state = this.store.state;\n    this.stateHandler = new StateHandler();\n    bindMethods(this);\n  }\n\n  mount(instance: null | VxeGridInstance, formApi: ExtendedFormApi) {\n    if (!this.isMounted && instance) {\n      this.grid = instance;\n      this.formApi = formApi;\n      this.stateHandler.setConditionTrue();\n      this.isMounted = true;\n    }\n  }\n\n  async query(params: Record<string, any> = {}) {\n    try {\n      await this.grid.commitProxy('query', toRaw(params));\n    } catch (error) {\n      console.error('Error occurred while querying:', error);\n    }\n  }\n\n  async reload(params: Record<string, any> = {}) {\n    try {\n      await this.grid.commitProxy('reload', toRaw(params));\n    } catch (error) {\n      console.error('Error occurred while reloading:', error);\n    }\n  }\n\n  setGridOptions(options: Partial<VxeGridProps['gridOptions']>) {\n    this.setState({\n      gridOptions: options,\n    });\n  }\n\n  setLoading(isLoading: boolean) {\n    this.setState({\n      gridOptions: {\n        loading: isLoading,\n      },\n    });\n  }\n\n  setState(\n    stateOrFn:\n      | ((prev: VxeGridProps<T>) => Partial<VxeGridProps<T>>)\n      | Partial<VxeGridProps<T>>,\n  ) {\n    if (isFunction(stateOrFn)) {\n      this.store.setState((prev) => {\n        return mergeWithArrayOverride(stateOrFn(prev), prev);\n      });\n    } else {\n      this.store.setState((prev) => mergeWithArrayOverride(stateOrFn, prev));\n    }\n  }\n\n  toggleSearchForm(show?: boolean) {\n    this.setState({\n      showSearchForm: isBoolean(show) ? show : !this.state?.showSearchForm,\n    });\n    // nextTick(() => {\n    //   this.grid.recalculate();\n    // });\n    return this.state?.showSearchForm;\n  }\n\n  unmount() {\n    this.isMounted = false;\n    this.stateHandler.reset();\n  }\n}\n"
  },
  {
    "path": "packages/effects/plugins/src/vxe-table/extends.ts",
    "content": "import type { VxeGridProps, VxeUIExport } from 'vxe-table';\n\nimport type { Recordable } from '@vben/types';\n\nimport type { VxeGridApi } from './api';\n\nimport { formatDate, formatDateTime, isFunction } from '@vben/utils';\n\nexport function extendProxyOptions(\n  api: VxeGridApi,\n  options: VxeGridProps,\n  getFormValues: () => Recordable<any>,\n) {\n  [\n    'query',\n    'querySuccess',\n    'queryError',\n    'queryAll',\n    'queryAllSuccess',\n    'queryAllError',\n  ].forEach((key) => {\n    extendProxyOption(key, api, options, getFormValues);\n  });\n}\n\nfunction extendProxyOption(\n  key: string,\n  api: VxeGridApi,\n  options: VxeGridProps,\n  getFormValues: () => Recordable<any>,\n) {\n  const { proxyConfig } = options;\n  const configFn = (proxyConfig?.ajax as Recordable<any>)?.[key];\n  if (!isFunction(configFn)) {\n    return options;\n  }\n\n  const wrapperFn = async (\n    params: Recordable<any>,\n    customValues: Recordable<any>,\n    ...args: Recordable<any>[]\n  ) => {\n    const formValues = getFormValues();\n    const data = await configFn(\n      params,\n      {\n        /**\n         * 开启toolbarConfig.refresh功能\n         * 点击刷新按钮 这里的值为PointerEvent 会携带错误参数\n         */\n        ...(customValues instanceof PointerEvent ? {} : customValues),\n        ...formValues,\n      },\n      ...args,\n    );\n    return data;\n  };\n  api.setState({\n    gridOptions: {\n      proxyConfig: {\n        ajax: {\n          [key]: wrapperFn,\n        },\n      },\n    },\n  });\n}\n\nexport function extendsDefaultFormatter(vxeUI: VxeUIExport) {\n  vxeUI.formats.add('formatDate', {\n    tableCellFormatMethod({ cellValue }) {\n      return formatDate(cellValue);\n    },\n  });\n\n  vxeUI.formats.add('formatDateTime', {\n    tableCellFormatMethod({ cellValue }) {\n      return formatDateTime(cellValue);\n    },\n  });\n}\n"
  },
  {
    "path": "packages/effects/plugins/src/vxe-table/index.ts",
    "content": "export { setupVbenVxeTable } from './init';\nexport type { VxeTableGridOptions } from './types';\nexport * from './use-vxe-grid';\n\nexport { default as VbenVxeGrid } from './use-vxe-grid.vue';\nexport type {\n  VxeGridListeners,\n  VxeGridProps,\n  VxeGridPropTypes,\n} from 'vxe-table';\n"
  },
  {
    "path": "packages/effects/plugins/src/vxe-table/init.ts",
    "content": "import type { SetupVxeTable } from './types';\n\nimport { defineComponent, watch } from 'vue';\n\nimport { usePreferences } from '@vben/preferences';\n\nimport { useVbenForm } from '@vben-core/form-ui';\n\nimport {\n  VxeButton,\n  VxeCheckbox,\n\n  // VxeFormGather,\n  // VxeForm,\n  // VxeFormItem,\n  VxeIcon,\n  VxeInput,\n  VxeLoading,\n  VxeModal,\n  VxeNumberInput,\n  VxePager,\n  // VxeList,\n  // VxeModal,\n  // VxeOptgroup,\n  // VxeOption,\n  // VxePulldown,\n  // VxeRadio,\n  // VxeRadioButton,\n  VxeRadioGroup,\n  VxeSelect,\n  VxeTooltip,\n  VxeUI,\n  VxeUpload,\n  // VxeSwitch,\n  // VxeTextarea,\n} from 'vxe-pc-ui';\nimport enUS from 'vxe-pc-ui/lib/language/en-US';\n// 导入默认的语言\nimport zhCN from 'vxe-pc-ui/lib/language/zh-CN';\nimport {\n  VxeColgroup,\n  VxeColumn,\n  VxeGrid,\n  VxeTable,\n  VxeToolbar,\n} from 'vxe-table';\n\nimport { extendsDefaultFormatter } from './extends';\n\n// 是否加载过\nlet isInit = false;\n\nlet tableFormFactory: typeof useVbenForm | undefined;\n\nfunction normalizeVxeLocale<T extends Record<string, any>>(localeModule: T) {\n  return (\n    localeModule &&\n    typeof localeModule === 'object' &&\n    'default' in localeModule\n      ? localeModule.default\n      : localeModule\n  ) as T;\n}\n\nexport const useTableForm: typeof useVbenForm = ((...args) => {\n  if (!tableFormFactory) {\n    throw new Error('useTableForm is not initialized');\n  }\n\n  return tableFormFactory(...args);\n}) as typeof useVbenForm;\n\n// 部分组件，如果没注册，vxe-table 会报错，这里实际没用组件，只是为了不报错，同时可以减少打包体积\nconst createVirtualComponent = (name = '') => {\n  return defineComponent({\n    name,\n  });\n};\n\nexport function initVxeTable() {\n  if (isInit) {\n    return;\n  }\n\n  VxeUI.component(VxeTable);\n  VxeUI.component(VxeColumn);\n  VxeUI.component(VxeColgroup);\n  VxeUI.component(VxeGrid);\n  VxeUI.component(VxeToolbar);\n\n  VxeUI.component(VxeButton);\n  // VxeUI.component(VxeButtonGroup);\n  VxeUI.component(VxeCheckbox);\n  // VxeUI.component(VxeCheckboxGroup);\n  VxeUI.component(createVirtualComponent('VxeForm'));\n  // VxeUI.component(VxeFormGather);\n  // VxeUI.component(VxeFormItem);\n  VxeUI.component(VxeIcon);\n  VxeUI.component(VxeInput);\n  // VxeUI.component(VxeList);\n  VxeUI.component(VxeLoading);\n  VxeUI.component(VxeModal);\n  VxeUI.component(VxeNumberInput);\n  // VxeUI.component(VxeOptgroup);\n  // VxeUI.component(VxeOption);\n  VxeUI.component(VxePager);\n  // VxeUI.component(VxePulldown);\n  // VxeUI.component(VxeRadio);\n  // VxeUI.component(VxeRadioButton);\n  VxeUI.component(VxeRadioGroup);\n  VxeUI.component(VxeSelect);\n  // VxeUI.component(VxeSwitch);\n  // VxeUI.component(VxeTextarea);\n  VxeUI.component(VxeTooltip);\n  VxeUI.component(VxeUpload);\n\n  isInit = true;\n}\n\nexport function setupVbenVxeTable(setupOptions: SetupVxeTable) {\n  const { configVxeTable, useVbenForm } = setupOptions;\n\n  initVxeTable();\n  tableFormFactory = useVbenForm;\n\n  const { isDark, locale } = usePreferences();\n\n  const localMap = {\n    'zh-CN': normalizeVxeLocale(zhCN),\n    'en-US': normalizeVxeLocale(enUS),\n  };\n\n  watch(\n    [() => isDark.value, () => locale.value],\n    ([isDarkValue, localeValue]) => {\n      VxeUI.setTheme(isDarkValue ? 'dark' : 'light');\n      VxeUI.setI18n(localeValue, localMap[localeValue]);\n      VxeUI.setLanguage(localeValue);\n    },\n    {\n      immediate: true,\n    },\n  );\n\n  extendsDefaultFormatter(VxeUI);\n\n  configVxeTable(VxeUI);\n}\n"
  },
  {
    "path": "packages/effects/plugins/src/vxe-table/style.css",
    "content": "@reference \"@vben/tailwind-config/theme\";\n\n:root .vxe-grid {\n  --vxe-ui-font-color: hsl(var(--foreground));\n  --vxe-ui-font-primary-color: hsl(var(--primary));\n\n  /* --vxe-ui-font-lighten-color: #babdc0;\n  --vxe-ui-font-darken-color: #86898e; */\n  --vxe-ui-font-disabled-color: hsl(var(--foreground) / 50%);\n\n  /* base */\n  --vxe-ui-base-popup-border-color: hsl(var(--border));\n  --vxe-ui-input-disabled-color: hsl(var(--border) / 60%);\n\n  /* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */\n\n  /* layout */\n  --vxe-ui-layout-background-color: hsl(var(--background));\n  --vxe-ui-table-resizable-line-color: hsl(var(--heavy));\n\n  /* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent));\n  --vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */\n\n  /* input */\n  --vxe-ui-input-border-color: hsl(var(--border));\n\n  /* --vxe-ui-input-placeholder-color: #8d9095; */\n\n  /* --vxe-ui-input-disabled-background-color: #262727; */\n\n  /* loading */\n  --vxe-ui-loading-background-color: hsl(var(--overlay-content));\n\n  /* table */\n  --vxe-ui-table-header-background-color: hsl(var(--accent));\n  --vxe-ui-table-border-color: hsl(var(--border));\n  --vxe-ui-table-row-hover-background-color: hsl(var(--accent-hover));\n  --vxe-ui-table-row-striped-background-color: hsl(var(--accent) / 60%);\n  --vxe-ui-table-row-hover-striped-background-color: hsl(var(--accent));\n  --vxe-ui-table-row-radio-checked-background-color: hsl(var(--accent));\n  --vxe-ui-table-row-hover-radio-checked-background-color: hsl(\n    var(--accent-hover)\n  );\n  --vxe-ui-table-row-checkbox-checked-background-color: hsl(var(--accent));\n  --vxe-ui-table-row-hover-checkbox-checked-background-color: hsl(\n    var(--accent-hover)\n  );\n  --vxe-ui-table-row-current-background-color: hsl(var(--accent));\n  --vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover));\n  --vxe-ui-font-primary-tinge-color: hsl(var(--primary));\n  --vxe-ui-font-primary-lighten-color: hsl(var(--primary) / 60%);\n  --vxe-ui-font-primary-darken-color: hsl(var(--primary));\n\n  height: auto !important;\n\n  /* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */\n}\n\n.vxe-pager {\n  .vxe-pager--prev-btn:not(.is--disabled):active,\n  .vxe-pager--next-btn:not(.is--disabled):active,\n  .vxe-pager--num-btn:not(.is--disabled):active,\n  .vxe-pager--jump-prev:not(.is--disabled):active,\n  .vxe-pager--jump-next:not(.is--disabled):active,\n  .vxe-pager--prev-btn:not(.is--disabled):focus,\n  .vxe-pager--next-btn:not(.is--disabled):focus,\n  .vxe-pager--num-btn:not(.is--disabled):focus,\n  .vxe-pager--jump-prev:not(.is--disabled):focus,\n  .vxe-pager--jump-next:not(.is--disabled):focus {\n    color: hsl(var(--accent-foreground));\n    background-color: hsl(var(--accent));\n    border: 1px solid hsl(var(--border));\n    box-shadow: 0 0 0 1px hsl(var(--border));\n  }\n\n  .vxe-pager--wrapper {\n    display: flex;\n    align-items: center;\n  }\n\n  .vxe-pager--sizes {\n    margin-right: auto;\n  }\n}\n\n.vxe-pager--wrapper {\n  @apply justify-center md:justify-end;\n}\n\n.vxe-tools--operate {\n  margin-right: 0.25rem;\n  margin-left: 0.75rem;\n}\n\n.vxe-table-custom--checkbox-option:hover {\n  background: none !important;\n}\n\n.vxe-toolbar {\n  padding: 0;\n}\n\n.vxe-buttons--wrapper:not(:empty),\n.vxe-tools--operate:not(:empty),\n.vxe-tools--wrapper:not(:empty) {\n  padding: 0.6em 0;\n}\n\n.vxe-tools--operate:not(:has(button)) {\n  margin-left: 0;\n}\n\n.vxe-grid--layout-header-wrapper {\n  overflow: visible;\n}\n\n.vxe-grid--layout-body-content-wrapper {\n  overflow: hidden;\n}\n"
  },
  {
    "path": "packages/effects/plugins/src/vxe-table/types.ts",
    "content": "import type {\n  VxeGridListeners,\n  VxeGridPropTypes,\n  VxeGridProps as VxeTableGridProps,\n  VxeUIExport,\n} from 'vxe-table';\n\nimport type { Ref } from 'vue';\n\nimport type { ClassType, DeepPartial } from '@vben/types';\n\nimport type { BaseFormComponentType, VbenFormProps } from '@vben-core/form-ui';\n\nimport type { VxeGridApi } from './api';\n\nimport { useVbenForm } from '@vben-core/form-ui';\n\nexport interface VxePaginationInfo {\n  currentPage: number;\n  pageSize: number;\n  total: number;\n}\n\ninterface ToolbarConfigOptions extends VxeGridPropTypes.ToolbarConfig {\n  /** 是否显示切换搜索表单的按钮 */\n  search?: boolean;\n}\n\nexport interface VxeTableGridOptions<T = any> extends VxeTableGridProps<T> {\n  /** 工具栏配置 */\n  toolbarConfig?: ToolbarConfigOptions;\n}\n\nexport interface SeparatorOptions {\n  show?: boolean;\n  backgroundColor?: string;\n}\n\nexport interface VxeGridProps<\n  T extends Record<string, any> = any,\n  D extends BaseFormComponentType = BaseFormComponentType,\n> {\n  /**\n   * 标题\n   */\n  tableTitle?: string;\n  /**\n   * 标题帮助\n   */\n  tableTitleHelp?: string;\n  /**\n   * 组件class\n   */\n  class?: ClassType;\n  /**\n   * vxe-grid class\n   */\n  gridClass?: ClassType;\n  /**\n   * vxe-grid 配置\n   */\n  gridOptions?: DeepPartial<VxeTableGridOptions<T>>;\n  /**\n   * vxe-grid 事件\n   */\n  gridEvents?: DeepPartial<VxeGridListeners<T>>;\n  /**\n   * 表单配置\n   */\n  formOptions?: VbenFormProps<D>;\n  /**\n   * 显示搜索表单\n   */\n  showSearchForm?: boolean;\n  /**\n   * 搜索表单与表格主体之间的分隔条\n   */\n  separator?: boolean | SeparatorOptions;\n}\n\nexport type ExtendedVxeGridApi<\n  D extends Record<string, any> = any,\n  F extends BaseFormComponentType = BaseFormComponentType,\n> = VxeGridApi<D> & {\n  useStore: <T = NoInfer<VxeGridProps<D, F>>>(\n    selector?: (state: NoInfer<VxeGridProps<any, any>>) => T,\n  ) => Readonly<Ref<T>>;\n};\n\nexport interface SetupVxeTable {\n  configVxeTable: (ui: VxeUIExport) => void;\n  useVbenForm: typeof useVbenForm;\n}\n"
  },
  {
    "path": "packages/effects/plugins/src/vxe-table/use-vxe-grid.ts",
    "content": "import type { VxeGridSlots, VxeGridSlotTypes } from 'vxe-table';\n\nimport type { SlotsType } from 'vue';\n\nimport type { BaseFormComponentType } from '@vben-core/form-ui';\n\nimport type { ExtendedVxeGridApi, VxeGridProps } from './types';\n\nimport { defineComponent, h, onBeforeUnmount } from 'vue';\n\nimport { useStore } from '@vben-core/shared/store';\n\nimport { VxeGridApi } from './api';\nimport VxeGrid from './use-vxe-grid.vue';\n\ntype FilteredSlots<T> = {\n  [K in keyof VxeGridSlots<T> as K extends 'form'\n    ? never\n    : K]: VxeGridSlots<T>[K];\n};\n\nexport function useVbenVxeGrid<\n  T extends Record<string, any> = any,\n  D extends BaseFormComponentType = BaseFormComponentType,\n>(options: VxeGridProps<T, D>) {\n  // const IS_REACTIVE = isReactive(options);\n  const api = new VxeGridApi(options);\n  const extendedApi: ExtendedVxeGridApi<T, D> = api as ExtendedVxeGridApi<T, D>;\n  extendedApi.useStore = (selector) => {\n    return useStore(api.store, selector);\n  };\n\n  const Grid = defineComponent(\n    (props: VxeGridProps<T>, { attrs, slots }) => {\n      onBeforeUnmount(() => {\n        api.unmount();\n      });\n      api.setState({ ...props, ...attrs });\n      return () => h(VxeGrid, { ...props, ...attrs, api: extendedApi }, slots);\n    },\n    {\n      name: 'VbenVxeGrid',\n      inheritAttrs: false,\n      slots: Object as SlotsType<\n        {\n          // 表格标题\n          'table-title': undefined;\n          // 工具栏左侧部分\n          'toolbar-actions': VxeGridSlotTypes.DefaultSlotParams<T>;\n          // 工具栏右侧部分\n          'toolbar-tools': VxeGridSlotTypes.DefaultSlotParams<T>;\n        } & FilteredSlots<T>\n      >,\n    },\n  );\n  // Add reactivity support\n  // if (IS_REACTIVE) {\n  //   watch(\n  //     () => options,\n  //     () => {\n  //       api.setState(options);\n  //     },\n  //     { immediate: true },\n  //   );\n  // }\n\n  return [Grid, extendedApi] as const;\n}\n\nexport type UseVbenVxeGrid = typeof useVbenVxeGrid;\n"
  },
  {
    "path": "packages/effects/plugins/src/vxe-table/use-vxe-grid.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  VxeGridDefines,\n  VxeGridInstance,\n  VxeGridListeners,\n  VxeGridPropTypes,\n  VxeGridProps as VxeTableGridProps,\n  VxeToolbarPropTypes,\n} from 'vxe-table';\n\nimport type { SetupContext } from 'vue';\n\nimport type { VbenFormProps } from '@vben-core/form-ui';\n\nimport type { ExtendedVxeGridApi, VxeGridProps } from './types';\n\nimport {\n  computed,\n  nextTick,\n  onMounted,\n  onUnmounted,\n  toRaw,\n  useSlots,\n  useTemplateRef,\n  watch,\n} from 'vue';\n\nimport { usePriorityValues } from '@vben/hooks';\nimport { EmptyIcon } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport { usePreferences } from '@vben/preferences';\nimport {\n  cloneDeep,\n  cn,\n  isBoolean,\n  isEqual,\n  mergeWithArrayOverride,\n} from '@vben/utils';\n\nimport { VbenHelpTooltip, VbenLoading } from '@vben-core/shadcn-ui';\n\nimport { VxeButton } from 'vxe-pc-ui';\nimport { VxeGrid, VxeUI } from 'vxe-table';\n\nimport { extendProxyOptions } from './extends';\nimport { useTableForm } from './init';\n\nimport 'vxe-table/styles/cssvar.scss';\nimport 'vxe-pc-ui/styles/cssvar.scss';\nimport './style.css';\n\ninterface Props extends VxeGridProps {\n  api: ExtendedVxeGridApi;\n}\n\nconst props = withDefaults(defineProps<Props>(), {});\n\nconst FORM_SLOT_PREFIX = 'form-';\n\nconst TOOLBAR_ACTIONS = 'toolbar-actions';\nconst TOOLBAR_TOOLS = 'toolbar-tools';\nconst TABLE_TITLE = 'table-title';\n\nconst gridRef = useTemplateRef<VxeGridInstance>('gridRef');\n\nconst state = props.api?.useStore?.();\n\nconst {\n  gridOptions,\n  class: className,\n  gridClass,\n  gridEvents,\n  formOptions,\n  tableTitle,\n  tableTitleHelp,\n  showSearchForm,\n  separator,\n} = usePriorityValues(props, state);\n\nconst { isMobile } = usePreferences();\nconst isSeparator = computed(() => {\n  if (\n    !formOptions.value ||\n    showSearchForm.value === false ||\n    separator.value === false\n  ) {\n    return false;\n  }\n  if (separator.value === true || separator.value === undefined) {\n    return true;\n  }\n  return separator.value.show !== false;\n});\nconst separatorBg = computed(() => {\n  return !separator.value ||\n    isBoolean(separator.value) ||\n    !separator.value.backgroundColor\n    ? undefined\n    : separator.value.backgroundColor;\n});\nconst slots: SetupContext['slots'] = useSlots();\n\nconst [Form, formApi] = useTableForm({\n  compact: true,\n  handleSubmit: async () => {\n    const formValues = await formApi.getValues();\n    formApi.setLatestSubmissionValues(toRaw(formValues));\n    props.api.reload(formValues);\n  },\n  handleReset: async () => {\n    const prevValues = await formApi.getValues();\n    await formApi.resetForm();\n    const formValues = await formApi.getValues();\n    formApi.setLatestSubmissionValues(formValues);\n    // 如果值发生了变化，submitOnChange会触发刷新。所以只在submitOnChange为false或者值没有发生变化时，手动刷新\n    if (isEqual(prevValues, formValues) || !formOptions.value?.submitOnChange) {\n      props.api.reload(formValues);\n    }\n  },\n  commonConfig: {\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  showCollapseButton: true,\n  submitButtonOptions: {\n    content: computed(() => $t('common.search')),\n  },\n  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n});\n\nconst showTableTitle = computed(() => {\n  return !!slots[TABLE_TITLE]?.() || tableTitle.value;\n});\n\nconst showToolbar = computed(() => {\n  return (\n    !!slots[TOOLBAR_ACTIONS]?.() ||\n    !!slots[TOOLBAR_TOOLS]?.() ||\n    showTableTitle.value\n  );\n});\n\nconst toolbarOptions = computed(() => {\n  const slotActions = slots[TOOLBAR_ACTIONS]?.();\n  const slotTools = slots[TOOLBAR_TOOLS]?.();\n  const searchBtn: VxeToolbarPropTypes.ToolConfig = {\n    code: 'search',\n    icon: 'vxe-icon-search',\n    circle: true,\n    status: showSearchForm.value ? 'primary' : undefined,\n    title: showSearchForm.value\n      ? $t('common.hideSearchPanel')\n      : $t('common.showSearchPanel'),\n  };\n  // 将搜索按钮合并到用户配置的toolbarConfig.tools中\n  const toolbarConfig: VxeGridPropTypes.ToolbarConfig = {\n    tools: (gridOptions.value?.toolbarConfig?.tools ??\n      []) as VxeToolbarPropTypes.ToolConfig[],\n  };\n  if (gridOptions.value?.toolbarConfig?.search && !!formOptions.value) {\n    toolbarConfig.tools = Array.isArray(toolbarConfig.tools)\n      ? [...toolbarConfig.tools, searchBtn]\n      : [searchBtn];\n  }\n\n  if (!showToolbar.value) {\n    toolbarConfig.enabled = false;\n    return { toolbarConfig };\n  }\n\n  // 强制使用固定的toolbar配置，不允许用户自定义\n  // 减少配置的复杂度，以及后续维护的成本\n  toolbarConfig.slots = {\n    ...(slotActions || showTableTitle.value\n      ? { buttons: TOOLBAR_ACTIONS }\n      : {}),\n    ...(slotTools ? { tools: TOOLBAR_TOOLS } : {}),\n  };\n  return { toolbarConfig };\n});\n\nconst options = computed(() => {\n  const globalGridConfig = VxeUI?.getConfig()?.grid ?? {};\n\n  const mergedOptions: VxeTableGridProps = cloneDeep(\n    mergeWithArrayOverride(\n      {},\n      toRaw(toolbarOptions.value),\n      toRaw(gridOptions.value),\n      globalGridConfig,\n    ),\n  );\n\n  if (mergedOptions.proxyConfig) {\n    const { ajax } = mergedOptions.proxyConfig;\n    mergedOptions.proxyConfig.enabled = !!ajax;\n    // 不自动加载数据, 由组件控制\n    mergedOptions.proxyConfig.autoLoad = false;\n  }\n\n  if (mergedOptions.pagerConfig) {\n    const mobileLayouts = [\n      'PrevJump',\n      'PrevPage',\n      'Number',\n      'NextPage',\n      'NextJump',\n    ] as any;\n    const layouts = [\n      'Total',\n      'Sizes',\n      'Home',\n      ...mobileLayouts,\n      'End',\n    ] as readonly string[];\n    mergedOptions.pagerConfig = mergeWithArrayOverride(\n      {},\n      mergedOptions.pagerConfig,\n      {\n        pageSize: 20,\n        background: true,\n        pageSizes: [10, 20, 30, 50, 100, 200],\n        className: 'mt-2 w-full',\n        layouts: isMobile.value ? mobileLayouts : layouts,\n        size: 'mini' as const,\n      },\n    );\n  }\n  if (mergedOptions.formConfig) {\n    mergedOptions.formConfig.enabled = false;\n  }\n  return mergedOptions;\n});\n\nfunction onToolbarToolClick(event: VxeGridDefines.ToolbarToolClickEventParams) {\n  if (event.code === 'search') {\n    onSearchBtnClick();\n  }\n  (\n    gridEvents.value?.toolbarToolClick as VxeGridListeners['toolbarToolClick']\n  )?.(event);\n}\n\nfunction onSearchBtnClick() {\n  props.api?.toggleSearchForm?.();\n}\n\nconst events = computed(() => {\n  return {\n    ...gridEvents.value,\n    toolbarToolClick: onToolbarToolClick,\n  };\n});\n\nconst delegatedSlots = computed(() => {\n  const resultSlots: string[] = [];\n\n  for (const key of Object.keys(slots)) {\n    if (\n      !['empty', 'form', 'loading', TOOLBAR_ACTIONS, TOOLBAR_TOOLS].includes(\n        key,\n      )\n    ) {\n      resultSlots.push(key);\n    }\n  }\n  return resultSlots;\n});\n\nconst delegatedFormSlots = computed(() => {\n  const resultSlots: string[] = [];\n\n  for (const key of Object.keys(slots)) {\n    if (key.startsWith(FORM_SLOT_PREFIX)) {\n      resultSlots.push(key);\n    }\n  }\n  return resultSlots.map((key) => key.replace(FORM_SLOT_PREFIX, ''));\n});\n\nconst showDefaultEmpty = computed(() => {\n  // 检查是否有原生的 VXE Table 空状态配置\n  const hasEmptyText = options.value.emptyText !== undefined;\n  const hasEmptyRender = options.value.emptyRender !== undefined;\n\n  // 如果有原生配置，就不显示默认的空状态\n  return !hasEmptyText && !hasEmptyRender;\n});\n\nasync function init() {\n  await nextTick();\n  const globalGridConfig = VxeUI?.getConfig()?.grid ?? {};\n  const defaultGridOptions: VxeTableGridProps = mergeWithArrayOverride(\n    {},\n    toRaw(gridOptions.value),\n    toRaw(globalGridConfig),\n  );\n  // 内部主动加载数据，防止form的默认值影响\n  const autoLoad = defaultGridOptions.proxyConfig?.autoLoad;\n  const enableProxyConfig = options.value.proxyConfig?.enabled;\n  if (enableProxyConfig && autoLoad) {\n    props.api.grid.commitProxy?.(\n      'query',\n      formOptions.value ? ((await formApi.getValues()) ?? {}) : {},\n    );\n    // props.api.reload(formApi.form?.values ?? {});\n  }\n\n  // form 由 vben-form代替，所以不适配formConfig，这里给出警告\n  const formConfig = gridOptions.value?.formConfig;\n  // 处理某个页面加载多个Table时，第2个之后的Table初始化报出警告\n  // 因为第一次初始化之后会把defaultGridOptions和gridOptions合并后缓存进State\n  if (formConfig && formConfig.enabled) {\n    console.warn(\n      '[Vben Vxe Table]: The formConfig in the grid is not supported, please use the `formOptions` props',\n    );\n  }\n  props.api?.setState?.({ gridOptions: defaultGridOptions });\n  // form 由 vben-form 代替，所以需要保证query相关事件可以拿到参数\n  extendProxyOptions(props.api, defaultGridOptions, () =>\n    formApi.getLatestSubmissionValues(),\n  );\n}\n\n// formOptions支持响应式\nwatch(\n  formOptions,\n  () => {\n    formApi.setState((prev) => {\n      const finalFormOptions: VbenFormProps = mergeWithArrayOverride(\n        {},\n        formOptions.value,\n        prev,\n      );\n      return {\n        ...finalFormOptions,\n        collapseTriggerResize: !!finalFormOptions.showCollapseButton,\n      };\n    });\n  },\n  {\n    immediate: true,\n  },\n);\n\nconst isCompactForm = computed(() => {\n  return formApi.getState()?.compact;\n});\n\nonMounted(() => {\n  props.api?.mount?.(gridRef.value, formApi);\n  init();\n});\n\nonUnmounted(() => {\n  formApi?.unmount?.();\n  props.api?.unmount?.();\n});\n</script>\n\n<template>\n  <div :class=\"cn('h-full rounded-md bg-card', className)\">\n    <VxeGrid\n      ref=\"gridRef\"\n      :class=\"\n        cn(\n          'p-2',\n          {\n            'pt-0': showToolbar && !formOptions,\n          },\n          gridClass,\n        )\n      \"\n      v-bind=\"options\"\n      v-on=\"events\"\n    >\n      <!-- 左侧操作区域或者title -->\n      <template v-if=\"showToolbar\" #toolbar-actions=\"slotProps\">\n        <slot v-if=\"showTableTitle\" name=\"table-title\">\n          <div class=\"flex-center gap-1 text-[1rem] font-bold\">\n            {{ tableTitle }}\n            <VbenHelpTooltip v-if=\"tableTitleHelp\">\n              {{ tableTitleHelp }}\n            </VbenHelpTooltip>\n          </div>\n        </slot>\n        <slot name=\"toolbar-actions\" v-bind=\"slotProps\"> </slot>\n      </template>\n\n      <!-- 继承默认的slot -->\n      <template\n        v-for=\"slotName in delegatedSlots\"\n        :key=\"slotName\"\n        #[slotName]=\"slotProps\"\n      >\n        <slot :name=\"slotName\" v-bind=\"slotProps\"></slot>\n      </template>\n      <template #toolbar-tools=\"slotProps\">\n        <slot name=\"toolbar-tools\" v-bind=\"slotProps\"></slot>\n        <VxeButton\n          icon=\"vxe-icon-search\"\n          circle\n          class=\"ml-2\"\n          v-if=\"gridOptions?.toolbarConfig?.search && !!formOptions\"\n          :status=\"showSearchForm ? 'primary' : undefined\"\n          :title=\"$t('common.search')\"\n          @click=\"onSearchBtnClick\"\n        />\n      </template>\n\n      <!-- form表单 -->\n      <template #form>\n        <div\n          v-if=\"formOptions\"\n          v-show=\"showSearchForm !== false\"\n          :class=\"\n            cn(\n              'relative rounded-sm py-3',\n              isCompactForm\n                ? isSeparator\n                  ? 'pb-8'\n                  : 'pb-4'\n                : isSeparator\n                  ? 'pb-4'\n                  : 'pb-0',\n            )\n          \"\n        >\n          <slot name=\"form\">\n            <Form>\n              <template\n                v-for=\"slotName in delegatedFormSlots\"\n                :key=\"slotName\"\n                #[slotName]=\"slotProps\"\n              >\n                <slot\n                  :name=\"`${FORM_SLOT_PREFIX}${slotName}`\"\n                  v-bind=\"slotProps\"\n                ></slot>\n              </template>\n              <template #reset-before=\"slotProps\">\n                <slot name=\"reset-before\" v-bind=\"slotProps\"></slot>\n              </template>\n              <template #submit-before=\"slotProps\">\n                <slot name=\"submit-before\" v-bind=\"slotProps\"></slot>\n              </template>\n              <template #expand-before=\"slotProps\">\n                <slot name=\"expand-before\" v-bind=\"slotProps\"></slot>\n              </template>\n              <template #expand-after=\"slotProps\">\n                <slot name=\"expand-after\" v-bind=\"slotProps\"></slot>\n              </template>\n            </Form>\n          </slot>\n          <div\n            v-if=\"isSeparator\"\n            :style=\"{\n              ...(separatorBg ? { backgroundColor: separatorBg } : undefined),\n            }\"\n            class=\"absolute bottom-1 -left-2 z-100 h-2 w-[calc(100%+1rem)] overflow-hidden bg-background-deep md:bottom-2 md:h-3\"\n          ></div>\n        </div>\n      </template>\n      <!-- loading -->\n      <template #loading>\n        <slot name=\"loading\">\n          <VbenLoading :spinning=\"true\" />\n        </slot>\n      </template>\n      <!-- 统一控状态 -->\n      <template v-if=\"showDefaultEmpty\" #empty>\n        <slot name=\"empty\">\n          <EmptyIcon class=\"mx-auto\" />\n          <div class=\"mt-2\">{{ $t('common.noData') }}</div>\n        </slot>\n      </template>\n    </VxeGrid>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/effects/plugins/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/effects/request/package.json",
    "content": "{\n  \"name\": \"@vben/request\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/effects/request\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"axios\": \"catalog:\",\n    \"qs\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/qs\": \"catalog:\",\n    \"axios-mock-adapter\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/effects/request/src/index.ts",
    "content": "export * from './request-client';\nexport * from 'axios';\n"
  },
  {
    "path": "packages/effects/request/src/request-client/index.ts",
    "content": "export * from './preset-interceptors';\nexport * from './request-client';\nexport type * from './types';\n"
  },
  {
    "path": "packages/effects/request/src/request-client/modules/downloader.test.ts",
    "content": "import type { AxiosRequestConfig } from 'axios';\n\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { FileDownloader } from './downloader';\n\ndescribe('fileDownloader', () => {\n  let fileDownloader: FileDownloader;\n  const mockAxiosInstance = {\n    get: vi.fn(),\n  } as any;\n\n  beforeEach(() => {\n    fileDownloader = new FileDownloader(mockAxiosInstance);\n  });\n\n  it('should create an instance of FileDownloader', () => {\n    expect(fileDownloader).toBeInstanceOf(FileDownloader);\n  });\n\n  it('should download a file and return a Blob', async () => {\n    const url = 'https://example.com/file';\n    const mockBlob = new Blob(['file content'], { type: 'text/plain' });\n    const mockResponse: Blob = mockBlob;\n\n    mockAxiosInstance.get.mockResolvedValueOnce(mockResponse);\n\n    const result = await fileDownloader.download(url);\n\n    expect(result).toBeInstanceOf(Blob);\n    expect(result).toEqual(mockBlob);\n    expect(mockAxiosInstance.get).toHaveBeenCalledWith(url, {\n      method: 'GET',\n      responseType: 'blob',\n      responseReturn: 'body',\n    });\n  });\n\n  it('should merge provided config with default config', async () => {\n    const url = 'https://example.com/file';\n    const mockBlob = new Blob(['file content'], { type: 'text/plain' });\n    const mockResponse: Blob = mockBlob;\n\n    mockAxiosInstance.get.mockResolvedValueOnce(mockResponse);\n\n    const customConfig: AxiosRequestConfig = {\n      headers: { 'Custom-Header': 'value' },\n    };\n\n    const result = await fileDownloader.download(url, customConfig);\n    expect(result).toBeInstanceOf(Blob);\n    expect(result).toEqual(mockBlob);\n    expect(mockAxiosInstance.get).toHaveBeenCalledWith(url, {\n      ...customConfig,\n      method: 'GET',\n      responseType: 'blob',\n      responseReturn: 'body',\n    });\n  });\n\n  it('should handle errors gracefully', async () => {\n    const url = 'https://example.com/file';\n    mockAxiosInstance.get.mockRejectedValueOnce(new Error('Network Error'));\n    await expect(fileDownloader.download(url)).rejects.toThrow('Network Error');\n  });\n\n  it('should handle empty URL gracefully', async () => {\n    const url = '';\n    mockAxiosInstance.get.mockRejectedValueOnce(\n      new Error('Request failed with status code 404'),\n    );\n\n    await expect(fileDownloader.download(url)).rejects.toThrow(\n      'Request failed with status code 404',\n    );\n  });\n\n  it('should handle null URL gracefully', async () => {\n    const url = null as unknown as string;\n    mockAxiosInstance.get.mockRejectedValueOnce(\n      new Error('Request failed with status code 404'),\n    );\n\n    await expect(fileDownloader.download(url)).rejects.toThrow(\n      'Request failed with status code 404',\n    );\n  });\n});\n\ndescribe('fileDownloader use other method', () => {\n  let fileDownloader: FileDownloader;\n\n  it('should call request using get', async () => {\n    const url = 'https://example.com/file';\n    const mockBlob = new Blob(['file content'], { type: 'text/plain' });\n    const mockResponse: Blob = mockBlob;\n\n    const mockAxiosInstance = {\n      request: vi.fn(),\n    } as any;\n\n    fileDownloader = new FileDownloader(mockAxiosInstance);\n\n    mockAxiosInstance.request.mockResolvedValueOnce(mockResponse);\n\n    const result = await fileDownloader.download(url);\n\n    expect(result).toBeInstanceOf(Blob);\n    expect(result).toEqual(mockBlob);\n    expect(mockAxiosInstance.request).toHaveBeenCalledWith(url, {\n      method: 'GET',\n      responseType: 'blob',\n      responseReturn: 'body',\n    });\n  });\n\n  it('should call post', async () => {\n    const url = 'https://example.com/file';\n\n    const mockAxiosInstance = {\n      post: vi.fn(),\n    } as any;\n\n    fileDownloader = new FileDownloader(mockAxiosInstance);\n\n    const customConfig: AxiosRequestConfig = {\n      method: 'POST',\n      data: { name: 'aa' },\n    };\n\n    await fileDownloader.download(url, customConfig);\n\n    expect(mockAxiosInstance.post).toHaveBeenCalledWith(\n      url,\n      { name: 'aa' },\n      {\n        method: 'POST',\n        responseType: 'blob',\n        responseReturn: 'body',\n      },\n    );\n  });\n\n  it('should handle errors gracefully', async () => {\n    const url = 'https://example.com/file';\n    const mockAxiosInstance = {\n      post: vi.fn(),\n    } as any;\n\n    fileDownloader = new FileDownloader(mockAxiosInstance);\n    await expect(() =>\n      fileDownloader.download(url, { method: 'postt' }),\n    ).rejects.toThrow(\n      'RequestClient does not support method \"POSTT\". Please ensure the method is properly implemented in your RequestClient instance.',\n    );\n  });\n});\n"
  },
  {
    "path": "packages/effects/request/src/request-client/modules/downloader.ts",
    "content": "import type { RequestClient } from '../request-client';\nimport type { RequestClientConfig } from '../types';\n\ntype DownloadRequestConfig = {\n  /**\n   * 定义期望获得的数据类型。\n   * raw: 原始的AxiosResponse，包括headers、status等。\n   * body: 只返回响应数据的BODY部分(Blob)\n   */\n  responseReturn?: 'body' | 'raw';\n} & Omit<RequestClientConfig, 'responseReturn'>;\n\nclass FileDownloader {\n  private client: RequestClient;\n\n  constructor(client: RequestClient) {\n    this.client = client;\n  }\n  /**\n   * 下载文件\n   * @param url 文件的完整链接\n   * @param config 配置信息，可选。\n   * @returns 如果config.responseReturn为'body'，则返回Blob(默认)，否则返回RequestResponse<Blob>\n   */\n  public async download<T = Blob>(\n    url: string,\n    config?: DownloadRequestConfig,\n  ): Promise<T> {\n    const finalConfig: DownloadRequestConfig = {\n      responseReturn: 'body',\n      method: 'GET',\n      ...config,\n      responseType: 'blob',\n    };\n\n    // Prefer a generic request if available; otherwise, dispatch to method-specific calls.\n    const method = (finalConfig.method || 'GET').toUpperCase();\n    const clientAny = this.client as any;\n\n    if (typeof clientAny.request === 'function') {\n      return await clientAny.request(url, finalConfig);\n    }\n    const lower = method.toLowerCase();\n\n    if (typeof clientAny[lower] === 'function') {\n      if (['POST', 'PUT'].includes(method)) {\n        const { data, ...rest } = finalConfig as Record<string, any>;\n        return await clientAny[lower](url, data, rest);\n      }\n\n      return await clientAny[lower](url, finalConfig);\n    }\n\n    throw new Error(\n      `RequestClient does not support method \"${method}\". Please ensure the method is properly implemented in your RequestClient instance.`,\n    );\n  }\n}\n\nexport { FileDownloader };\n"
  },
  {
    "path": "packages/effects/request/src/request-client/modules/interceptor.ts",
    "content": "import type { AxiosInstance, AxiosResponse } from 'axios';\n\nimport type {\n  RequestInterceptorConfig,\n  ResponseInterceptorConfig,\n} from '../types';\n\nconst defaultRequestInterceptorConfig: RequestInterceptorConfig = {\n  fulfilled: (response) => response,\n  rejected: (error) => Promise.reject(error),\n};\n\nconst defaultResponseInterceptorConfig: ResponseInterceptorConfig = {\n  fulfilled: (response: AxiosResponse) => response,\n  rejected: (error) => Promise.reject(error),\n};\n\nclass InterceptorManager {\n  private axiosInstance: AxiosInstance;\n\n  constructor(instance: AxiosInstance) {\n    this.axiosInstance = instance;\n  }\n\n  addRequestInterceptor({\n    fulfilled,\n    rejected,\n  }: RequestInterceptorConfig = defaultRequestInterceptorConfig) {\n    this.axiosInstance.interceptors.request.use(fulfilled, rejected);\n  }\n\n  addResponseInterceptor<T = any>({\n    fulfilled,\n    rejected,\n  }: ResponseInterceptorConfig<T> = defaultResponseInterceptorConfig) {\n    this.axiosInstance.interceptors.response.use(fulfilled, rejected);\n  }\n}\n\nexport { InterceptorManager };\n"
  },
  {
    "path": "packages/effects/request/src/request-client/modules/sse.test.ts",
    "content": "import type { RequestClient } from '../request-client';\n\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { SSE } from './sse';\n\n// 模拟 TextDecoder\nconst OriginalTextDecoder = globalThis.TextDecoder;\n\nbeforeEach(() => {\n  vi.stubGlobal(\n    'TextDecoder',\n    class {\n      private decoder = new OriginalTextDecoder();\n      decode(value: Uint8Array, opts?: any) {\n        return this.decoder.decode(value, opts);\n      }\n    },\n  );\n});\n\n// 创建 fetch mock\nconst createFetchMock = (chunks: string[], ok = true) => {\n  const encoder = new TextEncoder();\n  let index = 0;\n  return vi.fn().mockResolvedValue({\n    ok,\n    status: ok ? 200 : 500,\n    body: {\n      getReader: () => ({\n        read: async () => {\n          if (index < chunks.length) {\n            return { done: false, value: encoder.encode(chunks[index++]) };\n          }\n          return { done: true, value: undefined };\n        },\n      }),\n    },\n  });\n};\n\ndescribe('sSE', () => {\n  let client: RequestClient;\n  let sse: SSE;\n\n  beforeEach(() => {\n    vi.restoreAllMocks();\n    client = {\n      getBaseUrl: () => 'http://localhost',\n      instance: {\n        interceptors: {\n          request: {\n            handlers: [],\n          },\n        },\n      },\n    } as unknown as RequestClient;\n    sse = new SSE(client);\n  });\n\n  it('should call requestSSE when postSSE is used', async () => {\n    const spy = vi.spyOn(sse, 'requestSSE').mockResolvedValue(undefined);\n    await sse.postSSE('/test', { foo: 'bar' }, { headers: { a: '1' } });\n    expect(spy).toHaveBeenCalledWith(\n      '/test',\n      { foo: 'bar' },\n      {\n        headers: { a: '1' },\n        method: 'POST',\n      },\n    );\n  });\n\n  it('should throw error if fetch response not ok', async () => {\n    vi.stubGlobal('fetch', createFetchMock([], false));\n    await expect(sse.requestSSE('/bad')).rejects.toThrow(\n      'HTTP error! status: 500',\n    );\n  });\n\n  it('should trigger onMessage and onEnd callbacks', async () => {\n    const messages: string[] = [];\n    const onMessage = vi.fn((msg: string) => messages.push(msg));\n    const onEnd = vi.fn();\n\n    vi.stubGlobal('fetch', createFetchMock(['hello', ' world']));\n\n    await sse.requestSSE('/sse', undefined, { onMessage, onEnd });\n\n    expect(onMessage).toHaveBeenCalledTimes(2);\n    expect(messages.join('')).toBe('hello world');\n    // onEnd 不再带参数\n    expect(onEnd).toHaveBeenCalled();\n  });\n\n  it('should apply request interceptors', async () => {\n    const interceptor = vi.fn(async (config) => {\n      config.headers['x-test'] = 'intercepted';\n      return config;\n    });\n    (client.instance.interceptors.request as any).handlers.push({\n      fulfilled: interceptor,\n    });\n\n    // 创建 fetch mock，并挂到全局\n    const fetchMock = createFetchMock(['data']);\n    vi.stubGlobal('fetch', fetchMock);\n\n    await sse.requestSSE('/sse', undefined, {});\n\n    expect(interceptor).toHaveBeenCalled();\n    expect(fetchMock).toHaveBeenCalledWith(\n      'http://localhost/sse',\n      expect.objectContaining({\n        headers: expect.any(Headers),\n      }),\n    );\n\n    const calls = fetchMock.mock?.calls;\n    expect(calls).toBeDefined();\n    expect(calls?.length).toBeGreaterThan(0);\n\n    const init = calls?.[0]?.[1] as RequestInit;\n    expect(init).toBeDefined();\n\n    const headers = init?.headers as Headers;\n    expect(headers?.get('x-test')).toBe('intercepted');\n    expect(headers?.get('accept')).toBe('text/event-stream');\n  });\n\n  it('should throw error when no reader', async () => {\n    vi.stubGlobal(\n      'fetch',\n      vi.fn().mockResolvedValue({\n        ok: true,\n        status: 200,\n        body: null,\n      }),\n    );\n    await expect(sse.requestSSE('/sse')).rejects.toThrow('No reader');\n  });\n});\n"
  },
  {
    "path": "packages/effects/request/src/request-client/modules/sse.ts",
    "content": "import type { AxiosRequestHeaders, InternalAxiosRequestConfig } from 'axios';\n\nimport type { RequestClient } from '../request-client';\nimport type { SseRequestOptions } from '../types';\n\n/**\n * SSE模块\n */\nclass SSE {\n  private client: RequestClient;\n\n  constructor(client: RequestClient) {\n    this.client = client;\n  }\n\n  public async postSSE(\n    url: string,\n    data?: any,\n    requestOptions?: SseRequestOptions,\n  ) {\n    return this.requestSSE(url, data, {\n      ...requestOptions,\n      method: 'POST',\n    });\n  }\n\n  /**\n   * SSE请求方法\n   * @param url - 请求URL\n   * @param data - 请求数据\n   * @param requestOptions - SSE请求选项\n   */\n  public async requestSSE(\n    url: string,\n    data?: any,\n    requestOptions?: SseRequestOptions,\n  ) {\n    const baseUrl = this.client.getBaseUrl() || '';\n\n    let axiosConfig: InternalAxiosRequestConfig<any> = {\n      url,\n      method: (requestOptions?.method as any) ?? 'GET',\n      headers: {} as AxiosRequestHeaders,\n    };\n    const requestInterceptors = this.client.instance.interceptors\n      .request as any;\n    if (\n      requestInterceptors.handlers &&\n      requestInterceptors.handlers.length > 0\n    ) {\n      for (const handler of requestInterceptors.handlers) {\n        if (typeof handler?.fulfilled === 'function') {\n          const next = await handler.fulfilled(axiosConfig as any);\n          if (next) axiosConfig = next as InternalAxiosRequestConfig<any>;\n        }\n      }\n    }\n\n    const merged = new Headers();\n    Object.entries(\n      (axiosConfig.headers ?? {}) as Record<string, string>,\n    ).forEach(([k, v]) => merged.set(k, String(v)));\n    if (requestOptions?.headers) {\n      new Headers(requestOptions.headers).forEach((v, k) => merged.set(k, v));\n    }\n    if (!merged.has('accept')) {\n      merged.set('accept', 'text/event-stream');\n    }\n\n    let bodyInit = requestOptions?.body ?? data;\n    const ct = (merged.get('content-type') || '').toLowerCase();\n    if (\n      bodyInit &&\n      typeof bodyInit === 'object' &&\n      !ArrayBuffer.isView(bodyInit as any) &&\n      !(bodyInit instanceof ArrayBuffer) &&\n      !(bodyInit instanceof Blob) &&\n      !(bodyInit instanceof FormData) &&\n      ct.includes('application/json')\n    ) {\n      bodyInit = JSON.stringify(bodyInit);\n    }\n    const requestInit: RequestInit = {\n      ...requestOptions,\n      method: axiosConfig.method,\n      headers: merged,\n      body: bodyInit,\n    };\n\n    const response = await fetch(safeJoinUrl(baseUrl, url), requestInit);\n    if (!response.ok) {\n      throw new Error(`HTTP error! status: ${response.status}`);\n    }\n\n    const reader = response.body?.getReader();\n    const decoder = new TextDecoder();\n\n    if (!reader) {\n      throw new Error('No reader');\n    }\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) {\n        decoder.decode(new Uint8Array(0), { stream: false });\n        requestOptions?.onEnd?.();\n        reader.releaseLock?.();\n        break;\n      }\n      const content = decoder.decode(value, { stream: true });\n      requestOptions?.onMessage?.(content);\n    }\n  }\n}\n\nfunction safeJoinUrl(baseUrl: string | undefined, url: string): string {\n  if (!baseUrl) {\n    return url; // 没有 baseUrl，直接返回 url\n  }\n\n  // 如果 url 本身就是绝对地址，直接返回\n  if (/^https?:\\/\\//i.test(url)) {\n    return url;\n  }\n\n  // 如果 baseUrl 是完整 URL，就用 new URL\n  if (/^https?:\\/\\//i.test(baseUrl)) {\n    return new URL(url, baseUrl).toString();\n  }\n\n  // 否则，当作路径拼接\n  return `${baseUrl.replace(/\\/+$/, '')}/${url.replace(/^\\/+/, '')}`;\n}\n\nexport { SSE };\n"
  },
  {
    "path": "packages/effects/request/src/request-client/modules/uploader.test.ts",
    "content": "import type { AxiosRequestConfig, AxiosResponse } from 'axios';\n\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { FileUploader } from './uploader';\n\ndescribe('fileUploader', () => {\n  let fileUploader: FileUploader;\n  // Mock the AxiosInstance\n  const mockAxiosInstance = {\n    post: vi.fn(),\n  } as any;\n\n  beforeEach(() => {\n    fileUploader = new FileUploader(mockAxiosInstance);\n  });\n\n  it('should create an instance of FileUploader', () => {\n    expect(fileUploader).toBeInstanceOf(FileUploader);\n  });\n\n  it('should upload a file and return the response', async () => {\n    const url = 'https://example.com/upload';\n    const file = new File(['file content'], 'test.txt', { type: 'text/plain' });\n    const mockResponse: AxiosResponse = {\n      config: {} as any,\n      data: { success: true },\n      headers: {},\n      status: 200,\n      statusText: 'OK',\n    };\n\n    (\n      mockAxiosInstance.post as unknown as ReturnType<typeof vi.fn>\n    ).mockResolvedValueOnce(mockResponse);\n\n    const result = await fileUploader.upload(url, { file });\n    expect(result).toEqual(mockResponse);\n    expect(mockAxiosInstance.post).toHaveBeenCalledWith(\n      url,\n      expect.any(FormData),\n      {\n        headers: {\n          'Content-Type': 'multipart/form-data',\n        },\n      },\n    );\n  });\n\n  it('should merge provided config with default config', async () => {\n    const url = 'https://example.com/upload';\n    const file = new File(['file content'], 'test.txt', { type: 'text/plain' });\n    const mockResponse: AxiosResponse = {\n      config: {} as any,\n      data: { success: true },\n      headers: {},\n      status: 200,\n      statusText: 'OK',\n    };\n\n    (\n      mockAxiosInstance.post as unknown as ReturnType<typeof vi.fn>\n    ).mockResolvedValueOnce(mockResponse);\n\n    const customConfig: AxiosRequestConfig = {\n      headers: { 'Custom-Header': 'value' },\n    };\n\n    const result = await fileUploader.upload(url, { file }, customConfig);\n    expect(result).toEqual(mockResponse);\n    expect(mockAxiosInstance.post).toHaveBeenCalledWith(\n      url,\n      expect.any(FormData),\n      {\n        headers: {\n          'Content-Type': 'multipart/form-data',\n          'Custom-Header': 'value',\n        },\n      },\n    );\n  });\n\n  it('should handle errors gracefully', async () => {\n    const url = 'https://example.com/upload';\n    const file = new File(['file content'], 'test.txt', { type: 'text/plain' });\n    (\n      mockAxiosInstance.post as unknown as ReturnType<typeof vi.fn>\n    ).mockRejectedValueOnce(new Error('Network Error'));\n\n    await expect(fileUploader.upload(url, { file })).rejects.toThrow(\n      'Network Error',\n    );\n  });\n\n  it('should handle empty URL gracefully', async () => {\n    const url = '';\n    const file = new File(['file content'], 'test.txt', { type: 'text/plain' });\n    (\n      mockAxiosInstance.post as unknown as ReturnType<typeof vi.fn>\n    ).mockRejectedValueOnce(new Error('Request failed with status code 404'));\n\n    await expect(fileUploader.upload(url, { file })).rejects.toThrow(\n      'Request failed with status code 404',\n    );\n  });\n\n  it('should handle null URL gracefully', async () => {\n    const url = null as unknown as string;\n    const file = new File(['file content'], 'test.txt', { type: 'text/plain' });\n    (\n      mockAxiosInstance.post as unknown as ReturnType<typeof vi.fn>\n    ).mockRejectedValueOnce(new Error('Request failed with status code 404'));\n\n    await expect(fileUploader.upload(url, { file })).rejects.toThrow(\n      'Request failed with status code 404',\n    );\n  });\n});\n"
  },
  {
    "path": "packages/effects/request/src/request-client/modules/uploader.ts",
    "content": "import type { RequestClient } from '../request-client';\nimport type { RequestClientConfig } from '../types';\n\nimport { isUndefined } from '@vben/utils';\n\nclass FileUploader {\n  private client: RequestClient;\n\n  constructor(client: RequestClient) {\n    this.client = client;\n  }\n\n  public async upload<T = any>(\n    url: string,\n    data: Record<string, any> & { file: Blob | File },\n    config?: RequestClientConfig,\n  ): Promise<T> {\n    const formData = new FormData();\n\n    Object.entries(data).forEach(([key, value]) => {\n      if (Array.isArray(value)) {\n        value.forEach((item, index) => {\n          !isUndefined(item) && formData.append(`${key}[${index}]`, item);\n        });\n      } else {\n        !isUndefined(value) && formData.append(key, value);\n      }\n    });\n\n    const finalConfig: RequestClientConfig = {\n      ...config,\n      headers: {\n        'Content-Type': 'multipart/form-data',\n        ...config?.headers,\n      },\n    };\n\n    return this.client.post(url, formData, finalConfig);\n  }\n}\n\nexport { FileUploader };\n"
  },
  {
    "path": "packages/effects/request/src/request-client/preset-interceptors.ts",
    "content": "import type { RequestClient } from './request-client';\nimport type { MakeErrorMessageFn, ResponseInterceptorConfig } from './types';\n\nimport { $t } from '@vben/locales';\nimport { isFunction } from '@vben/utils';\n\nimport axios from 'axios';\n\nexport const defaultResponseInterceptor = ({\n  codeField = 'code',\n  dataField = 'data',\n  successCode = 0,\n}: {\n  /** 响应数据中代表访问结果的字段名 */\n  codeField: string;\n  /** 响应数据中装载实际数据的字段名，或者提供一个函数从响应数据中解析需要返回的数据 */\n  dataField: ((response: any) => any) | string;\n  /** 当codeField所指定的字段值与successCode相同时，代表接口访问成功。如果提供一个函数，则返回true代表接口访问成功 */\n  successCode: ((code: any) => boolean) | number | string;\n}): ResponseInterceptorConfig => {\n  return {\n    fulfilled: (response) => {\n      const { config, data: responseData, status } = response;\n\n      if (config.responseReturn === 'raw') {\n        return response;\n      }\n\n      if (status >= 200 && status < 400) {\n        if (config.responseReturn === 'body') {\n          return responseData;\n        } else if (\n          isFunction(successCode)\n            ? successCode(responseData[codeField])\n            : responseData[codeField] === successCode\n        ) {\n          return isFunction(dataField)\n            ? dataField(responseData)\n            : responseData[dataField];\n        }\n      }\n      throw Object.assign({}, response, { response });\n    },\n  };\n};\n\nexport const authenticateResponseInterceptor = ({\n  client,\n  doReAuthenticate,\n  doRefreshToken,\n  enableRefreshToken,\n  formatToken,\n}: {\n  client: RequestClient;\n  doReAuthenticate: () => Promise<void>;\n  doRefreshToken: () => Promise<string>;\n  enableRefreshToken: boolean;\n  formatToken: (token: string) => null | string;\n}): ResponseInterceptorConfig => {\n  return {\n    rejected: async (error) => {\n      const { config, response } = error;\n      // 如果不是 401 错误，直接抛出异常\n      if (response?.status !== 401) {\n        throw error;\n      }\n      // 判断是否启用了 refreshToken 功能\n      // 如果没有启用或者已经是重试请求了，直接跳转到重新登录\n      if (!enableRefreshToken || config.__isRetryRequest) {\n        await doReAuthenticate();\n        throw error;\n      }\n      // 如果正在刷新 token，则将请求加入队列，等待刷新完成\n      if (client.isRefreshing) {\n        return new Promise((resolve) => {\n          client.refreshTokenQueue.push((newToken: string) => {\n            config.headers.Authorization = formatToken(newToken);\n            resolve(client.request(config.url, { ...config }));\n          });\n        });\n      }\n\n      // 标记开始刷新 token\n      client.isRefreshing = true;\n      // 标记当前请求为重试请求，避免无限循环\n      config.__isRetryRequest = true;\n\n      try {\n        const newToken = await doRefreshToken();\n\n        // 处理队列中的请求\n        client.refreshTokenQueue.forEach((callback) => callback(newToken));\n        // 清空队列\n        client.refreshTokenQueue = [];\n\n        return client.request(error.config.url, { ...error.config });\n      } catch (refreshError) {\n        // 如果刷新 token 失败，处理错误（如强制登出或跳转登录页面）\n        client.refreshTokenQueue.forEach((callback) => callback(''));\n        client.refreshTokenQueue = [];\n        console.error('Refresh token failed, please login again.');\n        await doReAuthenticate();\n\n        throw refreshError;\n      } finally {\n        client.isRefreshing = false;\n      }\n    },\n  };\n};\n\nexport const errorMessageResponseInterceptor = (\n  makeErrorMessage?: MakeErrorMessageFn,\n): ResponseInterceptorConfig => {\n  return {\n    rejected: (error: any) => {\n      if (axios.isCancel(error)) {\n        return Promise.reject(error);\n      }\n\n      const err: string = error?.toString?.() ?? '';\n      let errMsg = '';\n      if (err?.includes('Network Error')) {\n        errMsg = $t('ui.fallback.http.networkError');\n      } else if (error?.message?.includes?.('timeout')) {\n        errMsg = $t('ui.fallback.http.requestTimeout');\n      }\n      if (errMsg) {\n        makeErrorMessage?.(errMsg, error);\n        return Promise.reject(error);\n      }\n\n      let errorMessage: string;\n      const status = error?.response?.status;\n\n      switch (status) {\n        case 400: {\n          errorMessage = $t('ui.fallback.http.badRequest');\n          break;\n        }\n        case 401: {\n          errorMessage = $t('ui.fallback.http.unauthorized');\n          break;\n        }\n        case 403: {\n          errorMessage = $t('ui.fallback.http.forbidden');\n          break;\n        }\n        case 404: {\n          errorMessage = $t('ui.fallback.http.notFound');\n          break;\n        }\n        case 408: {\n          errorMessage = $t('ui.fallback.http.requestTimeout');\n          break;\n        }\n        default: {\n          errorMessage = $t('ui.fallback.http.internalServerError');\n        }\n      }\n      makeErrorMessage?.(errorMessage, error);\n      return Promise.reject(error);\n    },\n  };\n};\n"
  },
  {
    "path": "packages/effects/request/src/request-client/request-client.test.ts",
    "content": "import axios from 'axios';\nimport MockAdapter from 'axios-mock-adapter';\nimport { afterEach, beforeEach, describe, expect, it } from 'vitest';\n\nimport { RequestClient } from './request-client';\n\ndescribe('requestClient', () => {\n  let mock: MockAdapter;\n  let requestClient: RequestClient;\n\n  beforeEach(() => {\n    mock = new MockAdapter(axios);\n    requestClient = new RequestClient();\n  });\n\n  afterEach(() => {\n    mock.reset();\n  });\n\n  it('should successfully make a GET request', async () => {\n    mock.onGet('test/url').reply(200, { data: 'response' });\n\n    const response = await requestClient.get('test/url');\n\n    expect(response.data).toEqual({ data: 'response' });\n  });\n\n  it('should successfully make a POST request', async () => {\n    const postData = { key: 'value' };\n    const mockData = { data: 'response' };\n    mock.onPost('/test/post', postData).reply(200, mockData);\n    const response = await requestClient.post('/test/post', postData);\n    expect(response.data).toEqual(mockData);\n  });\n\n  it('should successfully make a PUT request', async () => {\n    const putData = { key: 'updatedValue' };\n    const mockData = { data: 'updated response' };\n    mock.onPut('/test/put', putData).reply(200, mockData);\n    const response = await requestClient.put('/test/put', putData);\n    expect(response.data).toEqual(mockData);\n  });\n\n  it('should successfully make a DELETE request', async () => {\n    const mockData = { data: 'delete response' };\n    mock.onDelete('/test/delete').reply(200, mockData);\n    const response = await requestClient.delete('/test/delete');\n    expect(response.data).toEqual(mockData);\n  });\n\n  it('should handle network errors', async () => {\n    mock.onGet('/test/error').networkError();\n    try {\n      await requestClient.get('/test/error');\n      expect(true).toBe(false);\n    } catch (error: any) {\n      expect(error.isAxiosError).toBe(true);\n      expect(error.message).toBe('Network Error');\n    }\n  });\n\n  it('should handle timeout', async () => {\n    mock.onGet('/test/timeout').timeout();\n    try {\n      await requestClient.get('/test/timeout');\n      expect(true).toBe(false);\n    } catch (error: any) {\n      expect(error.isAxiosError).toBe(true);\n      expect(error.code).toBe('ECONNABORTED');\n    }\n  });\n\n  it('should successfully upload a file', async () => {\n    const fileData = new Blob(['file contents'], { type: 'text/plain' });\n\n    mock.onPost('/test/upload').reply((config) => {\n      return config.data instanceof FormData && config.data.has('file')\n        ? [200, { data: 'file uploaded' }]\n        : [400, { error: 'Bad Request' }];\n    });\n\n    const response = await requestClient.upload('/test/upload', {\n      file: fileData,\n    });\n    expect(response.data).toEqual({ data: 'file uploaded' });\n  });\n\n  it('should successfully download a file as a blob', async () => {\n    const mockFileContent = new Blob(['mock file content'], {\n      type: 'text/plain',\n    });\n\n    mock.onGet('/test/download').reply(200, mockFileContent);\n\n    const res = await requestClient.download('/test/download');\n\n    expect(res.data).toBeInstanceOf(Blob);\n  });\n});\n"
  },
  {
    "path": "packages/effects/request/src/request-client/request-client.ts",
    "content": "import type { AxiosInstance, AxiosResponse } from 'axios';\n\nimport type { RequestClientConfig, RequestClientOptions } from './types';\n\nimport { bindMethods, isString, merge } from '@vben/utils';\n\nimport axios from 'axios';\nimport qs from 'qs';\n\nimport { FileDownloader } from './modules/downloader';\nimport { InterceptorManager } from './modules/interceptor';\nimport { SSE } from './modules/sse';\nimport { FileUploader } from './modules/uploader';\n\nfunction getParamsSerializer(\n  paramsSerializer: RequestClientOptions['paramsSerializer'],\n) {\n  if (isString(paramsSerializer)) {\n    switch (paramsSerializer) {\n      case 'brackets': {\n        return (params: any) =>\n          qs.stringify(params, { arrayFormat: 'brackets' });\n      }\n      case 'comma': {\n        return (params: any) => qs.stringify(params, { arrayFormat: 'comma' });\n      }\n      case 'indices': {\n        return (params: any) =>\n          qs.stringify(params, { arrayFormat: 'indices' });\n      }\n      case 'repeat': {\n        return (params: any) => qs.stringify(params, { arrayFormat: 'repeat' });\n      }\n    }\n  }\n  return paramsSerializer;\n}\n\nclass RequestClient {\n  public addRequestInterceptor: InterceptorManager['addRequestInterceptor'];\n\n  public addResponseInterceptor: InterceptorManager['addResponseInterceptor'];\n  public download: FileDownloader['download'];\n\n  public readonly instance: AxiosInstance;\n  // 是否正在刷新token\n  public isRefreshing = false;\n  public postSSE: SSE['postSSE'];\n  // 刷新token队列\n  public refreshTokenQueue: ((token: string) => void)[] = [];\n  public requestSSE: SSE['requestSSE'];\n  public upload: FileUploader['upload'];\n\n  /**\n   * 构造函数，用于创建Axios实例\n   * @param options - Axios请求配置，可选\n   */\n  constructor(options: RequestClientOptions = {}) {\n    // 合并默认配置和传入的配置\n    const defaultConfig: RequestClientOptions = {\n      headers: {\n        'Content-Type': 'application/json;charset=utf-8',\n      },\n      responseReturn: 'raw',\n      // 默认超时时间\n      timeout: 10_000,\n    };\n    const { ...axiosConfig } = options;\n    const requestConfig = merge(axiosConfig, defaultConfig);\n    requestConfig.paramsSerializer = getParamsSerializer(\n      requestConfig.paramsSerializer,\n    );\n    this.instance = axios.create(requestConfig);\n\n    bindMethods(this);\n\n    // 实例化拦截器管理器\n    const interceptorManager = new InterceptorManager(this.instance);\n    this.addRequestInterceptor =\n      interceptorManager.addRequestInterceptor.bind(interceptorManager);\n    this.addResponseInterceptor =\n      interceptorManager.addResponseInterceptor.bind(interceptorManager);\n\n    // 实例化文件上传器\n    const fileUploader = new FileUploader(this);\n    this.upload = fileUploader.upload.bind(fileUploader);\n    // 实例化文件下载器\n    const fileDownloader = new FileDownloader(this);\n    this.download = fileDownloader.download.bind(fileDownloader);\n    // 实例化SSE模块\n    const sse = new SSE(this);\n    this.postSSE = sse.postSSE.bind(sse);\n    this.requestSSE = sse.requestSSE.bind(sse);\n  }\n\n  /**\n   * DELETE请求方法\n   */\n  public delete<T = any>(\n    url: string,\n    config?: RequestClientConfig,\n  ): Promise<T> {\n    return this.request<T>(url, { ...config, method: 'DELETE' });\n  }\n\n  /**\n   * GET请求方法\n   */\n  public get<T = any>(url: string, config?: RequestClientConfig): Promise<T> {\n    return this.request<T>(url, { ...config, method: 'GET' });\n  }\n\n  /**\n   * 获取基础URL\n   */\n  public getBaseUrl() {\n    return this.instance.defaults.baseURL;\n  }\n\n  /**\n   * POST请求方法\n   */\n  public post<T = any>(\n    url: string,\n    data?: any,\n    config?: RequestClientConfig,\n  ): Promise<T> {\n    return this.request<T>(url, { ...config, data, method: 'POST' });\n  }\n\n  /**\n   * PUT请求方法\n   */\n  public put<T = any>(\n    url: string,\n    data?: any,\n    config?: RequestClientConfig,\n  ): Promise<T> {\n    return this.request<T>(url, { ...config, data, method: 'PUT' });\n  }\n\n  /**\n   * 通用的请求方法\n   */\n  public async request<T>(\n    url: string,\n    config: RequestClientConfig,\n  ): Promise<T> {\n    try {\n      const response: AxiosResponse<T> = await this.instance({\n        url,\n        ...config,\n        ...(config.paramsSerializer\n          ? { paramsSerializer: getParamsSerializer(config.paramsSerializer) }\n          : {}),\n      });\n      return response as T;\n    } catch (error: any) {\n      throw error.response ? error.response.data : error;\n    }\n  }\n}\n\nexport { RequestClient };\n"
  },
  {
    "path": "packages/effects/request/src/request-client/types.ts",
    "content": "import type {\n  AxiosRequestConfig,\n  AxiosResponse,\n  CreateAxiosDefaults,\n  InternalAxiosRequestConfig,\n} from 'axios';\n\ntype ExtendOptions<T = any> = {\n  /**\n   * 参数序列化方式。预置的有\n   * - brackets: ids[]=1&ids[]=2&ids[]=3\n   * - comma: ids=1,2,3\n   * - indices: ids[0]=1&ids[1]=2&ids[2]=3\n   * - repeat: ids=1&ids=2&ids=3\n   */\n  paramsSerializer?:\n    | 'brackets'\n    | 'comma'\n    | 'indices'\n    | 'repeat'\n    | AxiosRequestConfig<T>['paramsSerializer'];\n  /**\n   * 响应数据的返回方式。\n   * - raw: 原始的AxiosResponse，包括headers、status等，不做是否成功请求的检查。\n   * - body: 返回响应数据的BODY部分（只会根据status检查请求是否成功，忽略对code的判断，这种情况下应由调用方检查请求是否成功）。\n   * - data: 解构响应的BODY数据，只返回其中的data节点数据（会检查status和code是否为成功状态）。\n   */\n  responseReturn?: 'body' | 'data' | 'raw';\n};\ntype RequestClientConfig<T = any> = AxiosRequestConfig<T> & ExtendOptions<T>;\n\ntype RequestResponse<T = any> = AxiosResponse<T> & {\n  config: RequestClientConfig<T>;\n};\n\ntype RequestContentType =\n  | 'application/json;charset=utf-8'\n  | 'application/octet-stream;charset=utf-8'\n  | 'application/x-www-form-urlencoded;charset=utf-8'\n  | 'multipart/form-data;charset=utf-8';\n\ntype RequestClientOptions = CreateAxiosDefaults & ExtendOptions;\n\n/**\n * SSE 请求选项\n */\ninterface SseRequestOptions extends RequestInit {\n  onMessage?: (message: string) => void;\n  onEnd?: () => void;\n}\n\ninterface RequestInterceptorConfig {\n  fulfilled?: (\n    config: ExtendOptions & InternalAxiosRequestConfig,\n  ) =>\n    | (ExtendOptions & InternalAxiosRequestConfig<any>)\n    | Promise<ExtendOptions & InternalAxiosRequestConfig<any>>;\n  rejected?: (error: any) => any;\n}\n\ninterface ResponseInterceptorConfig<T = any> {\n  fulfilled?: (\n    response: RequestResponse<T>,\n  ) => Promise<RequestResponse> | RequestResponse;\n  rejected?: (error: any) => any;\n}\n\ntype MakeErrorMessageFn = (message: string, error: any) => void;\n\ninterface HttpResponse<T = any> {\n  /**\n   * 0 表示成功 其他表示失败\n   * 0 means success, others means fail\n   */\n  code: number;\n  data: T;\n  message: string;\n}\n\nexport type {\n  HttpResponse,\n  MakeErrorMessageFn,\n  RequestClientConfig,\n  RequestClientOptions,\n  RequestContentType,\n  RequestInterceptorConfig,\n  RequestResponse,\n  ResponseInterceptorConfig,\n  SseRequestOptions,\n};\n"
  },
  {
    "path": "packages/effects/request/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/icons/README.md",
    "content": "# @vben/icons\n\n用于多个 `app` 公用的图标文件，继承了 `@vben-core/icons` 的所有能力。业务上有通用图标可以放在这里。\n\n## 用法\n\n### 添加依赖\n\n```bash\n# 进入目标应用目录，例如 apps/xxxx-app\n# cd apps/xxxx-app\npnpm add @vben/icons\n```\n\n### 使用\n\n```ts\nimport { X } from '@vben/icons';\n```\n"
  },
  {
    "path": "packages/icons/package.json",
    "content": "{\n  \"name\": \"@vben/icons\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/icons\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/icons\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "packages/icons/src/iconify/index.ts",
    "content": "import { createIconifyIcon } from '@vben-core/icons';\n\nexport * from '@vben-core/icons';\n\nexport const MdiKeyboardEsc = createIconifyIcon('mdi:keyboard-esc');\n"
  },
  {
    "path": "packages/icons/src/icons/empty-icon.vue",
    "content": "<template>\n  <svg\n    height=\"41\"\n    viewBox=\"0 0 64 41\"\n    width=\"64\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(0 1)\">\n      <ellipse\n        cx=\"32\"\n        cy=\"33\"\n        fill=\"hsl(var(--background-deep))\"\n        rx=\"32\"\n        ry=\"7\"\n      />\n      <g fill-rule=\"nonzero\" stroke=\"hsl(var(--heavy))\">\n        <path\n          d=\"M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z\"\n        />\n        <path\n          d=\"M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z\"\n          fill=\"hsl(var(--accent))\"\n        />\n      </g>\n    </g>\n  </svg>\n</template>\n"
  },
  {
    "path": "packages/icons/src/index.ts",
    "content": "export * from './iconify';\nexport { default as EmptyIcon } from './icons/empty-icon.vue';\nexport * from './svg';\n"
  },
  {
    "path": "packages/icons/src/svg/index.ts",
    "content": "import { createIconifyIcon } from '@vben-core/icons';\n\nimport './load.js';\n\nconst SvgAvatar1Icon = createIconifyIcon('svg:avatar-1');\nconst SvgAvatar2Icon = createIconifyIcon('svg:avatar-2');\nconst SvgAvatar3Icon = createIconifyIcon('svg:avatar-3');\nconst SvgAvatar4Icon = createIconifyIcon('svg:avatar-4');\nconst SvgDownloadIcon = createIconifyIcon('svg:download');\nconst SvgCardIcon = createIconifyIcon('svg:card');\nconst SvgBellIcon = createIconifyIcon('svg:bell');\nconst SvgCakeIcon = createIconifyIcon('svg:cake');\nconst SvgAntdvLogoIcon = createIconifyIcon('svg:antdv-logo');\nconst SvgGithubIcon = createIconifyIcon('svg:github');\nconst SvgGoogleIcon = createIconifyIcon('svg:google');\nconst SvgQQChatIcon = createIconifyIcon('svg:qqchat');\nconst SvgWeChatIcon = createIconifyIcon('svg:wechat');\nconst SvgDingDingIcon = createIconifyIcon('svg:dingding');\nconst SvgTDesignIcon = createIconifyIcon('svg:tdesign-logo');\nconst SvgAntdvNextLogoIcon = createIconifyIcon('svg:antdv-next-logo');\n\nexport {\n  SvgAntdvLogoIcon,\n  SvgAntdvNextLogoIcon,\n  SvgAvatar1Icon,\n  SvgAvatar2Icon,\n  SvgAvatar3Icon,\n  SvgAvatar4Icon,\n  SvgBellIcon,\n  SvgCakeIcon,\n  SvgCardIcon,\n  SvgDingDingIcon,\n  SvgDownloadIcon,\n  SvgGithubIcon,\n  SvgGoogleIcon,\n  SvgQQChatIcon,\n  SvgTDesignIcon,\n  SvgWeChatIcon,\n};\n"
  },
  {
    "path": "packages/icons/src/svg/load.ts",
    "content": "import type { IconifyIconStructure } from '@vben-core/icons';\n\nimport { addIcon } from '@vben-core/icons';\n\nloadSvgIcons();\n\nfunction parseSvg(svgData: string): IconifyIconStructure {\n  const parser = new DOMParser();\n  const xmlDoc = parser.parseFromString(svgData, 'image/svg+xml');\n  const svgElement = xmlDoc.documentElement;\n\n  // 提取 SVG 根元素的关键样式属性\n  const getAttrs = (el: Element, attrs: string[]) =>\n    attrs\n      .map((attr) =>\n        el.hasAttribute(attr) ? `${attr}=\"${el.getAttribute(attr)}\"` : '',\n      )\n      .filter(Boolean)\n      .join(' ');\n\n  const rootAttrs = getAttrs(svgElement, [\n    'fill',\n    'stroke',\n    'fill-rule',\n    'stroke-width',\n  ]);\n\n  const svgContent = [...svgElement.childNodes]\n    .filter((node) => node.nodeType === Node.ELEMENT_NODE)\n    .map((node) => new XMLSerializer().serializeToString(node))\n    .join('');\n  // 若根有属性，用一个 g 标签包裹内容并继承属性\n  const body = rootAttrs ? `<g ${rootAttrs}>${svgContent}</g>` : svgContent;\n\n  const viewBoxValue = svgElement.getAttribute('viewBox') || '';\n  const [left, top, width, height] = viewBoxValue.split(' ').map((val) => {\n    const num = Number(val);\n    return Number.isNaN(num) ? undefined : num;\n  });\n\n  return {\n    body,\n    height,\n    left,\n    top,\n    width,\n  };\n}\n\n/**\n * 自定义的svg图片转化为组件\n * @example ./svg/avatar.svg\n * <Icon icon=\"svg:avatar\"></Icon>\n */\nasync function loadSvgIcons() {\n  const svgEagers = import.meta.glob('./icons/**', {\n    eager: true,\n    query: '?raw',\n  });\n\n  await Promise.all(\n    Object.entries(svgEagers).map((svg) => {\n      const [key, body] = svg as [string, string | { default: string }];\n\n      // ./icons/xxxx.svg => xxxxxx\n      const start = key.lastIndexOf('/') + 1;\n      const end = key.lastIndexOf('.');\n      const iconName = key.slice(start, end);\n\n      return addIcon(`svg:${iconName}`, {\n        ...parseSvg(typeof body === 'object' ? body.default : body),\n      });\n    }),\n  );\n}\n"
  },
  {
    "path": "packages/icons/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/locales/package.json",
    "content": "{\n  \"name\": \"@vben/locales\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/locales\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@intlify/core-base\": \"catalog:\",\n    \"@vben-core/composables\": \"workspace:*\",\n    \"vue\": \"catalog:\",\n    \"vue-i18n\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/locales/src/i18n.ts",
    "content": "import type { App } from 'vue';\nimport type { Locale } from 'vue-i18n';\n\nimport type {\n  ImportLocaleFn,\n  LoadMessageFn,\n  LocaleSetupOptions,\n  SupportedLanguagesType,\n} from './typing';\n\nimport { unref } from 'vue';\nimport { createI18n } from 'vue-i18n';\n\nimport { useSimpleLocale } from '@vben-core/composables';\n\nconst i18n = createI18n({\n  globalInjection: true,\n  legacy: false,\n  locale: '',\n  messages: {},\n});\n\nconst modules = import.meta.glob('./langs/**/*.json');\n\nconst { setSimpleLocale } = useSimpleLocale();\n\nconst localesMap = loadLocalesMapFromDir(\n  /\\.\\/langs\\/([^/]+)\\/(.*)\\.json$/,\n  modules,\n);\nlet loadMessages: LoadMessageFn;\n\n/**\n * Load locale modules\n * @param modules\n */\nfunction loadLocalesMap(modules: Record<string, () => Promise<unknown>>) {\n  const localesMap: Record<Locale, ImportLocaleFn> = {};\n\n  for (const [path, loadLocale] of Object.entries(modules)) {\n    const key = path.match(/([\\w-]*)\\.(json)/)?.[1];\n    if (key) {\n      localesMap[key] = loadLocale as ImportLocaleFn;\n    }\n  }\n  return localesMap;\n}\n\n/**\n * Load locale modules with directory structure\n * @param regexp - Regular expression to match language and file names\n * @param modules - The modules object containing paths and import functions\n * @returns A map of locales to their corresponding import functions\n */\nfunction loadLocalesMapFromDir(\n  regexp: RegExp,\n  modules: Record<string, () => Promise<unknown>>,\n): Record<Locale, ImportLocaleFn> {\n  const localesRaw: Record<Locale, Record<string, () => Promise<unknown>>> = {};\n  const localesMap: Record<Locale, ImportLocaleFn> = {};\n\n  // Iterate over the modules to extract language and file names\n  for (const path in modules) {\n    const match = path.match(regexp);\n    if (match) {\n      const [_, locale, fileName] = match;\n      if (locale && fileName) {\n        if (!localesRaw[locale]) {\n          localesRaw[locale] = {};\n        }\n        if (modules[path]) {\n          localesRaw[locale][fileName] = modules[path];\n        }\n      }\n    }\n  }\n\n  // Convert raw locale data into async import functions\n  for (const [locale, files] of Object.entries(localesRaw)) {\n    localesMap[locale] = async () => {\n      const messages: Record<string, any> = {};\n      for (const [fileName, importFn] of Object.entries(files)) {\n        messages[fileName] = ((await importFn()) as any)?.default;\n      }\n      return { default: messages };\n    };\n  }\n\n  return localesMap;\n}\n\n/**\n * Set i18n language\n * @param locale\n */\nfunction setI18nLanguage(locale: Locale) {\n  i18n.global.locale.value = locale;\n\n  document?.querySelector('html')?.setAttribute('lang', locale);\n}\n\nasync function setupI18n(app: App, options: LocaleSetupOptions = {}) {\n  const { defaultLocale = 'zh-CN' } = options;\n  // app可以自行扩展一些第三方库和组件库的国际化\n  loadMessages = options.loadMessages || (async () => ({}));\n  app.use(i18n);\n  await loadLocaleMessages(defaultLocale);\n\n  // 在控制台打印警告\n  i18n.global.setMissingHandler((locale, key) => {\n    if (options.missingWarn && key.includes('.')) {\n      console.warn(\n        `[intlify] Not found '${key}' key in '${locale}' locale messages.`,\n      );\n    }\n  });\n}\n\n/**\n * Load locale messages\n * @param lang\n */\nasync function loadLocaleMessages(lang: SupportedLanguagesType) {\n  if (unref(i18n.global.locale) === lang) {\n    return setI18nLanguage(lang);\n  }\n  setSimpleLocale(lang);\n\n  const message = await localesMap[lang]?.();\n\n  if (message?.default) {\n    i18n.global.setLocaleMessage(lang, message.default);\n  }\n\n  const mergeMessage = await loadMessages(lang);\n  i18n.global.mergeLocaleMessage(lang, mergeMessage);\n\n  return setI18nLanguage(lang);\n}\n\nexport {\n  i18n,\n  loadLocaleMessages,\n  loadLocalesMap,\n  loadLocalesMapFromDir,\n  setupI18n,\n};\n"
  },
  {
    "path": "packages/locales/src/index.ts",
    "content": "import {\n  i18n,\n  loadLocaleMessages,\n  loadLocalesMap,\n  loadLocalesMapFromDir,\n  setupI18n,\n} from './i18n';\n\nconst $t = i18n.global.t;\nconst $te = i18n.global.te;\n\nexport {\n  $t,\n  $te,\n  i18n,\n  loadLocaleMessages,\n  loadLocalesMap,\n  loadLocalesMapFromDir,\n  setupI18n,\n};\nexport {\n  type ImportLocaleFn,\n  type LocaleSetupOptions,\n  type SupportedLanguagesType,\n} from './typing';\nexport type { CompileError } from '@intlify/core-base';\n\nexport { useI18n } from 'vue-i18n';\n\nexport type { Locale } from 'vue-i18n';\n"
  },
  {
    "path": "packages/locales/src/langs/en-US/authentication.json",
    "content": "{\n  \"welcomeBack\": \"Welcome Back\",\n  \"pageTitle\": \"Plug-and-play Admin system\",\n  \"pageDesc\": \"Efficient, versatile frontend template\",\n  \"loginSuccess\": \"Login Successful\",\n  \"loginSuccessDesc\": \"Welcome Back\",\n  \"loginSubtitle\": \"Enter your account details to manage your projects\",\n  \"selectAccount\": \"Quick Select Account\",\n  \"username\": \"Username\",\n  \"password\": \"Password\",\n  \"usernameTip\": \"Please enter username\",\n  \"passwordErrorTip\": \"Password is incorrect\",\n  \"passwordTip\": \"Please enter password\",\n  \"verifyRequiredTip\": \"Please complete the verification first\",\n  \"rememberMe\": \"Remember Me\",\n  \"createAnAccount\": \"Create an Account\",\n  \"createAccount\": \"Create Account\",\n  \"alreadyHaveAccount\": \"Already have an account?\",\n  \"accountTip\": \"Don't have an account?\",\n  \"signUp\": \"Sign Up\",\n  \"signUpSubtitle\": \"Make managing your applications simple and fun\",\n  \"confirmPassword\": \"Confirm Password\",\n  \"confirmPasswordTip\": \"The passwords do not match\",\n  \"agree\": \"I agree to\",\n  \"privacyPolicy\": \"Privacy-policy\",\n  \"terms\": \"Terms\",\n  \"agreeTip\": \"Please agree to the Privacy Policy and Terms\",\n  \"goToLogin\": \"Login instead\",\n  \"passwordStrength\": \"Use 8 or more characters with a mix of letters, numbers & symbols\",\n  \"forgetPassword\": \"Forget Password?\",\n  \"forgetPasswordSubtitle\": \"Enter your email and we'll send you instructions to reset your password\",\n  \"emailTip\": \"Please enter email\",\n  \"emailValidErrorTip\": \"The email format you entered is incorrect\",\n  \"sendResetLink\": \"Send Reset Link\",\n  \"email\": \"Email\",\n  \"qrcodeSubtitle\": \"Scan the QR code with your phone to login\",\n  \"qrcodePrompt\": \"Click 'Confirm' after scanning to complete login\",\n  \"qrcodeLogin\": \"QR Code Login\",\n  \"wechatLogin\": \"Wechat Login\",\n  \"qqLogin\": \"QQ Login\",\n  \"githubLogin\": \"Github Login\",\n  \"googleLogin\": \"Google Login\",\n  \"dingdingLogin\": \"Dingding Login\",\n  \"codeSubtitle\": \"Enter your phone number to start managing your project\",\n  \"code\": \"Security code\",\n  \"codeTip\": \"Security code required {0} characters\",\n  \"mobile\": \"Mobile\",\n  \"mobileLogin\": \"Mobile Login\",\n  \"mobileTip\": \"Please enter mobile number\",\n  \"mobileErrortip\": \"The phone number format is incorrect\",\n  \"sendCode\": \"Get Security code\",\n  \"sendText\": \"Resend in {0}s\",\n  \"thirdPartyLogin\": \"Or continue with\",\n  \"weChat\": \"WeChat\",\n  \"qq\": \"QQ\",\n  \"gitHub\": \"GitHub\",\n  \"google\": \"Google\",\n  \"loginAgainTitle\": \"Please Log In Again\",\n  \"loginAgainSubTitle\": \"Your login session has expired. Please log in again to continue.\",\n  \"layout\": {\n    \"center\": \"Align Center\",\n    \"alignLeft\": \"Align Left\",\n    \"alignRight\": \"Align Right\"\n  }\n}\n"
  },
  {
    "path": "packages/locales/src/langs/en-US/common.json",
    "content": "{\n  \"back\": \"Back\",\n  \"backToHome\": \"Back To Home\",\n  \"login\": \"Login\",\n  \"logout\": \"Logout\",\n  \"prompt\": \"Prompt\",\n  \"cancel\": \"Cancel\",\n  \"confirm\": \"Confirm\",\n  \"reset\": \"Reset\",\n  \"noData\": \"No Data\",\n  \"refresh\": \"Refresh\",\n  \"loadingMenu\": \"Loading Menu\",\n  \"query\": \"Search\",\n  \"search\": \"Search\",\n  \"enabled\": \"Enabled\",\n  \"disabled\": \"Disabled\",\n  \"edit\": \"Edit\",\n  \"delete\": \"Delete\",\n  \"create\": \"Create\",\n  \"yes\": \"Yes\",\n  \"no\": \"No\",\n  \"showSearchPanel\": \"Show search panel\",\n  \"hideSearchPanel\": \"Hide search panel\"\n}\n"
  },
  {
    "path": "packages/locales/src/langs/en-US/preferences.json",
    "content": "{\n  \"title\": \"Preferences\",\n  \"subtitle\": \"Customize Preferences & Preview in Real Time\",\n  \"enableStickyPreferencesNavigationBar\": \"Enable sticky preferences navigation bar\",\n  \"disableStickyPreferencesNavigationBar\": \"Disable sticky preferences navigation bar\",\n  \"resetTip\": \"Data has changed, click to reset\",\n  \"resetTitle\": \"Reset Preferences\",\n  \"resetSuccess\": \"Preferences reset successfully\",\n  \"appearance\": \"Appearance\",\n  \"layout\": \"Layout\",\n  \"content\": \"Content\",\n  \"other\": \"Other\",\n  \"wide\": \"Wide\",\n  \"compact\": \"Fixed\",\n  \"followSystem\": \"Follow System\",\n  \"vertical\": \"Vertical\",\n  \"verticalTip\": \"Side vertical menu mode\",\n  \"horizontal\": \"Horizontal\",\n  \"horizontalTip\": \"Horizontal menu mode, all menus displayed at the top\",\n  \"twoColumn\": \"Two Column\",\n  \"twoColumnTip\": \"Vertical Two Column Menu Mode\",\n  \"headerSidebarNav\": \"Header Vertical\",\n  \"headerSidebarNavTip\": \"Header Full Width, Sidebar Navigation Mode\",\n  \"headerTwoColumn\": \"Header Two Column\",\n  \"headerTwoColumnTip\": \"Header Navigation & Sidebar Two Column co-exists\",\n  \"mixedMenu\": \"Mixed Menu\",\n  \"mixedMenuTip\": \"Vertical & Horizontal Menu Co-exists\",\n  \"fullContent\": \"Full Content\",\n  \"fullContentTip\": \"Only display content body, hide all menus\",\n  \"normal\": \"Normal\",\n  \"plain\": \"Plain\",\n  \"rounded\": \"Rounded\",\n  \"copyPreferences\": \"Copy Preferences\",\n  \"enableCopyPreferences\": \"Show copy preferences button\",\n  \"copyPreferencesSuccessTitle\": \"Copy successful\",\n  \"copyPreferencesSuccess\": \"Copy successful, please override in `src/preferences.ts` under app\",\n  \"clearAndLogout\": \"Clear Cache & Logout\",\n  \"mode\": \"Mode\",\n  \"general\": \"General\",\n  \"language\": \"Language\",\n  \"dynamicTitle\": \"Dynamic Title\",\n  \"watermark\": \"Watermark\",\n  \"watermarkContent\": \"Please input Watermark content\",\n  \"checkUpdates\": \"Periodic update check\",\n  \"position\": {\n    \"title\": \"Preferences Postion\",\n    \"header\": \"Header\",\n    \"auto\": \"Auto\",\n    \"fixed\": \"Fixed\"\n  },\n  \"sidebar\": {\n    \"buttons\": \"Show Buttons\",\n    \"buttonFixed\": \"Fixed\",\n    \"buttonCollapsed\": \"Collapsed\",\n    \"title\": \"Sidebar\",\n    \"width\": \"Width\",\n    \"visible\": \"Show Sidebar\",\n    \"draggable\": \"Drag Sidebar Menu\",\n    \"collapsed\": \"Collpase Menu\",\n    \"collapsedShowTitle\": \"Show Menu Title\",\n    \"autoActivateChild\": \"Auto Activate SubMenu\",\n    \"autoActivateChildTip\": \"`Enabled` to automatically activate the submenu while click menu.\",\n    \"expandOnHover\": \"Expand On Hover\",\n    \"expandOnHoverTip\": \"When the mouse hovers over menu, \\n `Enabled` to expand children menus \\n `Disabled` to expand whole sidebar.\"\n  },\n  \"tabbar\": {\n    \"title\": \"Tabbar\",\n    \"enable\": \"Enable Tab Bar\",\n    \"icon\": \"Show Tabbar Icon\",\n    \"showMore\": \"Show More Button\",\n    \"showMaximize\": \"Show Maximize Button\",\n    \"persist\": \"Persist Tabs\",\n    \"visitHistory\": \"Visit History\",\n    \"visitHistoryTip\": \"When enabled, the tab bar records tab visit history. \\nClosing the current tab will automatically select the last opened tab.\",\n    \"maxCount\": \"Max Count of Tabs\",\n    \"maxCountTip\": \"When the number of tabs exceeds the maximum,\\nthe oldest tab will be closed.\\n Set to 0 to disable count checking.\",\n    \"draggable\": \"Enable Draggable Sort\",\n    \"wheelable\": \"Support Mouse Wheel\",\n    \"middleClickClose\": \"Close Tab when Mouse Middle Button Click\",\n    \"wheelableTip\": \"When enabled, the Tabbar area responds to vertical scrolling events of the scroll wheel.\",\n    \"styleType\": {\n      \"title\": \"Tabs Style\",\n      \"chrome\": \"Chrome\",\n      \"card\": \"Card\",\n      \"plain\": \"Plain\",\n      \"brisk\": \"Brisk\"\n    },\n    \"contextMenu\": {\n      \"reload\": \"Reload\",\n      \"close\": \"Close\",\n      \"pin\": \"Pin\",\n      \"unpin\": \"Unpin\",\n      \"closeLeft\": \"Close Left Tabs\",\n      \"closeRight\": \"Close Right Tabs\",\n      \"closeOther\": \"Close Other Tabs\",\n      \"closeAll\": \"Close All Tabs\",\n      \"openInNewWindow\": \"Open in New Window\",\n      \"maximize\": \"Maximize\",\n      \"restoreMaximize\": \"Restore\"\n    }\n  },\n  \"navigationMenu\": {\n    \"title\": \"Navigation Menu\",\n    \"style\": \"Navigation Menu Style\",\n    \"accordion\": \"Sidebar Accordion Menu\",\n    \"split\": \"Navigation Menu Separation\",\n    \"splitTip\": \"When enabled, the sidebar displays the top bar's submenu\"\n  },\n  \"breadcrumb\": {\n    \"title\": \"Breadcrumb\",\n    \"home\": \"Show Home Button\",\n    \"enable\": \"Enable Breadcrumb\",\n    \"icon\": \"Show Breadcrumb Icon\",\n    \"background\": \"background\",\n    \"style\": \"Breadcrumb Style\",\n    \"hideOnlyOne\": \"Hidden when only one\"\n  },\n  \"animation\": {\n    \"title\": \"Animation\",\n    \"loading\": \"Page Loading\",\n    \"transition\": \"Page Transition\",\n    \"progress\": \"Page Progress\"\n  },\n  \"theme\": {\n    \"title\": \"Theme\",\n    \"radius\": \"Radius\",\n    \"fontSize\": \"Font Size\",\n    \"fontSizeTip\": \"Adjust global font size with real-time preview\",\n    \"light\": \"Light\",\n    \"dark\": \"Dark\",\n    \"darkSidebar\": \"Semi Dark Sidebar\",\n    \"darkSidebarTip\": \"It can be enabled when the theme is light, and the layout is neither 'Horizontal' nor 'Full Content'.\",\n    \"darkSidebarSub\": \"Semi Dark Sidebar Sub\",\n    \"darkSidebarSubTip\": \"It can be enabled when the theme is light, the semi dark sidebar is enabled, and the layout uses 'Two-Column' menu mode.\",\n    \"darkHeader\": \"Semi Dark Header\",\n    \"weakMode\": \"Weak Mode\",\n    \"grayMode\": \"Gray Mode\",\n    \"builtin\": {\n      \"title\": \"Built-in\",\n      \"default\": \"Default\",\n      \"violet\": \"Violet\",\n      \"pink\": \"Pink\",\n      \"rose\": \"Rose\",\n      \"skyBlue\": \"Sky Blue\",\n      \"deepBlue\": \"Deep Blue\",\n      \"green\": \"Green\",\n      \"deepGreen\": \"Deep Green\",\n      \"orange\": \"Orange\",\n      \"yellow\": \"Yellow\",\n      \"zinc\": \"Zinc\",\n      \"neutral\": \"Neutral\",\n      \"slate\": \"Slate\",\n      \"gray\": \"Gray\",\n      \"custom\": \"Custom\"\n    }\n  },\n  \"header\": {\n    \"title\": \"Header\",\n    \"visible\": \"Show Header\",\n    \"modeStatic\": \"Static\",\n    \"modeFixed\": \"Fixed\",\n    \"modeAuto\": \"Auto hide & Show\",\n    \"modeAutoScroll\": \"Scroll to Hide & Show\",\n    \"menuAlign\": \"Menu Align\",\n    \"menuAlignStart\": \"Start\",\n    \"menuAlignEnd\": \"End\",\n    \"menuAlignCenter\": \"Center\"\n  },\n  \"footer\": {\n    \"title\": \"Footer\",\n    \"visible\": \"Show Footer\",\n    \"fixed\": \"Fixed at Bottom\"\n  },\n  \"copyright\": {\n    \"title\": \"Copyright\",\n    \"enable\": \"Enable Copyright\",\n    \"companyName\": \"Company Name\",\n    \"companySiteLink\": \"Company Site Link\",\n    \"date\": \"Date\",\n    \"icp\": \"ICP License Number\",\n    \"icpLink\": \"ICP Site Link\"\n  },\n  \"shortcutKeys\": {\n    \"title\": \"Shortcut Keys\",\n    \"global\": \"Global\",\n    \"search\": \"Global Search\",\n    \"logout\": \"Logout\",\n    \"preferences\": \"Preferences\"\n  },\n  \"widget\": {\n    \"title\": \"Widget\",\n    \"globalSearch\": \"Enable Global Search\",\n    \"fullscreen\": \"Enable Fullscreen\",\n    \"themeToggle\": \"Enable Theme Toggle\",\n    \"languageToggle\": \"Enable Language Toggle\",\n    \"notification\": \"Enable Notification\",\n    \"sidebarToggle\": \"Enable Sidebar Toggle\",\n    \"lockScreen\": \"Enable Lock Screen\",\n    \"refresh\": \"Enable Refresh\"\n  }\n}\n"
  },
  {
    "path": "packages/locales/src/langs/en-US/profile.json",
    "content": "{\n  \"updatePassword\": \"Update Password\",\n  \"updateBasicProfile\": \"Update Basic Profile\"\n}\n"
  },
  {
    "path": "packages/locales/src/langs/en-US/ui.json",
    "content": "{\n  \"formRules\": {\n    \"required\": \"Please enter {0}\",\n    \"selectRequired\": \"Please select {0}\",\n    \"minLength\": \"{0} must be at least {1} characters\",\n    \"maxLength\": \"{0} can be at most {1} characters\",\n    \"length\": \"{0} must be {1} characters long\",\n    \"alreadyExists\": \"{0} `{1}` already exists\",\n    \"startWith\": \"{0} must start with `{1}`\",\n    \"invalidURL\": \"Please input a valid URL\",\n    \"sizeLimit\": \"The file size cannot exceed {0}MB\",\n    \"previewWarning\": \"Unable to open the file, there is no available URL or preview address\"\n  },\n  \"actionTitle\": {\n    \"edit\": \"Modify {0}\",\n    \"create\": \"Create {0}\",\n    \"delete\": \"Delete {0}\",\n    \"view\": \"View {0}\"\n  },\n  \"actionMessage\": {\n    \"deleteConfirm\": \"Are you sure to delete {0}?\",\n    \"deleting\": \"Deleting {0} ...\",\n    \"deleteSuccess\": \"{0} deleted successfully\",\n    \"operationSuccess\": \"Operation succeeded\",\n    \"operationFailed\": \"Operation failed\"\n  },\n  \"placeholder\": {\n    \"input\": \"Please enter\",\n    \"select\": \"Please select\",\n    \"upload\": \"Click to upload\"\n  },\n  \"captcha\": {\n    \"title\": \"Please complete the security verification\",\n    \"sliderSuccessText\": \"Passed\",\n    \"sliderDefaultText\": \"Slider and drag\",\n    \"alt\": \"Supports img tag src attribute value\",\n    \"sliderRotateDefaultTip\": \"Click picture to refresh\",\n    \"sliderTranslateDefaultTip\": \"Click picture to refresh\",\n    \"sliderRotateFailTip\": \"Validation failed\",\n    \"sliderRotateSuccessTip\": \"Validation successful, time {0} seconds\",\n    \"sliderTranslateFailTip\": \"Validation failed\",\n    \"sliderTranslateSuccessTip\": \"Validation successful, time {0} seconds\",\n    \"refreshAriaLabel\": \"Refresh captcha\",\n    \"confirmAriaLabel\": \"Confirm selection\",\n    \"confirm\": \"Confirm\",\n    \"pointAriaLabel\": \"Click point\",\n    \"clickInOrder\": \"Please click in order\"\n  },\n  \"iconPicker\": {\n    \"placeholder\": \"Select an icon\",\n    \"search\": \"Search icon...\"\n  },\n  \"jsonViewer\": {\n    \"copy\": \"Copy\",\n    \"copied\": \"Copied\"\n  },\n  \"crop\": {\n    \"title\": \"Image Cropping\",\n    \"titleTip\": \"Cropping Ratio {0}\",\n    \"confirm\": \"Crop\",\n    \"cancel\": \"Cancel cropping\",\n    \"errorTip\": \"Cropping error\"\n  },\n  \"fallback\": {\n    \"pageNotFound\": \"Oops! Page Not Found\",\n    \"pageNotFoundDesc\": \"Sorry, we couldn't find the page you were looking for.\",\n    \"forbidden\": \"Oops! Access Denied\",\n    \"forbiddenDesc\": \"Sorry, but you don't have permission to access this page.\",\n    \"internalError\": \"Oops! Something Went Wrong\",\n    \"internalErrorDesc\": \"Sorry, but the server encountered an error.\",\n    \"offline\": \"Offline Page\",\n    \"offlineError\": \"Oops! Network Error\",\n    \"offlineErrorDesc\": \"Sorry, can't connect to the internet. Check your connection.\",\n    \"comingSoon\": \"Coming Soon\",\n    \"http\": {\n      \"requestTimeout\": \"The request timed out. Please try again later.\",\n      \"networkError\": \"A network error occurred. Please check your internet connection and try again.\",\n      \"badRequest\": \"Bad Request. Please check your input and try again.\",\n      \"unauthorized\": \"Unauthorized. Please log in to continue.\",\n      \"forbidden\": \"Forbidden. You do not have permission to access this resource.\",\n      \"notFound\": \"Not Found. The requested resource could not be found.\",\n      \"internalServerError\": \"Internal Server Error. Something went wrong on our end. Please try again later.\"\n    }\n  },\n  \"widgets\": {\n    \"document\": \"Document\",\n    \"qa\": \"Q&A\",\n    \"setting\": \"Settings\",\n    \"logoutTip\": \"Do you want to logout?\",\n    \"viewAll\": \"View All Messages\",\n    \"notifications\": \"Notifications\",\n    \"markAllAsRead\": \"Make All as Read\",\n    \"clearNotifications\": \"Clear\",\n    \"checkUpdatesTitle\": \"New Version Available\",\n    \"checkUpdatesDescription\": \"Click to refresh and get the latest version\",\n    \"search\": {\n      \"title\": \"Search\",\n      \"searchNavigate\": \"Search Navigation\",\n      \"select\": \"Select\",\n      \"navigate\": \"Navigate\",\n      \"close\": \"Close\",\n      \"noResults\": \"No Search Results Found\",\n      \"noRecent\": \"No Search History\",\n      \"recent\": \"Search History\"\n    },\n    \"lockScreen\": {\n      \"title\": \"Lock Screen\",\n      \"screenButton\": \"Locking\",\n      \"password\": \"Password\",\n      \"placeholder\": \"Please enter password\",\n      \"unlock\": \"Click to unlock\",\n      \"errorPasswordTip\": \"Password error, please re-enter\",\n      \"backToLogin\": \"Back to login\",\n      \"entry\": \"Enter the system\"\n    },\n    \"timezone\": {\n      \"setTimezone\": \"Set Timezone\",\n      \"setSuccess\": \"Timezone set successfully\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/locales/src/langs/zh-CN/authentication.json",
    "content": "{\n  \"welcomeBack\": \"欢迎回来\",\n  \"pageTitle\": \"开箱即用的大型中后台管理系统\",\n  \"pageDesc\": \"工程化、高性能、跨组件库的前端模版\",\n  \"loginSuccess\": \"登录成功\",\n  \"loginSuccessDesc\": \"欢迎回来\",\n  \"loginSubtitle\": \"请输入您的帐户信息以开始管理您的项目\",\n  \"selectAccount\": \"快速选择账号\",\n  \"username\": \"账号\",\n  \"password\": \"密码\",\n  \"usernameTip\": \"请输入用户名\",\n  \"passwordTip\": \"请输入密码\",\n  \"verifyRequiredTip\": \"请先完成验证\",\n  \"passwordErrorTip\": \"密码错误\",\n  \"rememberMe\": \"记住账号\",\n  \"createAnAccount\": \"创建一个账号\",\n  \"createAccount\": \"创建账号\",\n  \"alreadyHaveAccount\": \"已经有账号了?\",\n  \"accountTip\": \"还没有账号?\",\n  \"signUp\": \"注册\",\n  \"signUpSubtitle\": \"让您的应用程序管理变得简单而有趣\",\n  \"confirmPassword\": \"确认密码\",\n  \"confirmPasswordTip\": \"两次输入的密码不一致\",\n  \"agree\": \"我同意\",\n  \"privacyPolicy\": \"隐私政策\",\n  \"terms\": \"条款\",\n  \"agreeTip\": \"请同意隐私政策和条款\",\n  \"goToLogin\": \"去登录\",\n  \"passwordStrength\": \"使用 8 个或更多字符，混合字母、数字和符号\",\n  \"forgetPassword\": \"忘记密码?\",\n  \"forgetPasswordSubtitle\": \"输入您的电子邮件，我们将向您发送重置密码的连接\",\n  \"emailTip\": \"请输入邮箱\",\n  \"emailValidErrorTip\": \"你输入的邮箱格式不正确\",\n  \"sendResetLink\": \"发送重置链接\",\n  \"email\": \"邮箱\",\n  \"qrcodeSubtitle\": \"请用手机扫描二维码登录\",\n  \"qrcodePrompt\": \"扫码后点击 '确认'，即可完成登录\",\n  \"qrcodeLogin\": \"扫码登录\",\n  \"wechatLogin\": \"微信登录\",\n  \"qqLogin\": \"QQ登录\",\n  \"githubLogin\": \"Github登录\",\n  \"googleLogin\": \"Google登录\",\n  \"dingdingLogin\": \"钉钉登录\",\n  \"codeSubtitle\": \"请输入您的手机号码以开始管理您的项目\",\n  \"code\": \"验证码\",\n  \"codeTip\": \"请输入{0}位验证码\",\n  \"mobile\": \"手机号码\",\n  \"mobileTip\": \"请输入手机号\",\n  \"mobileErrortip\": \"手机号码格式错误\",\n  \"mobileLogin\": \"手机号登录\",\n  \"sendCode\": \"获取验证码\",\n  \"sendText\": \"{0}秒后重新获取\",\n  \"thirdPartyLogin\": \"其他登录方式\",\n  \"weChat\": \"微信\",\n  \"qq\": \"QQ\",\n  \"gitHub\": \"GitHub\",\n  \"google\": \"Google\",\n  \"loginAgainTitle\": \"重新登录\",\n  \"loginAgainSubTitle\": \"您的登录状态已过期，请重新登录以继续。\",\n  \"layout\": {\n    \"center\": \"居中\",\n    \"alignLeft\": \"居左\",\n    \"alignRight\": \"居右\"\n  }\n}\n"
  },
  {
    "path": "packages/locales/src/langs/zh-CN/common.json",
    "content": "{\n  \"back\": \"返回\",\n  \"backToHome\": \"返回首页\",\n  \"login\": \"登录\",\n  \"logout\": \"退出登录\",\n  \"prompt\": \"提示\",\n  \"cancel\": \"取消\",\n  \"confirm\": \"确认\",\n  \"reset\": \"重置\",\n  \"noData\": \"暂无数据\",\n  \"refresh\": \"刷新\",\n  \"loadingMenu\": \"加载菜单中\",\n  \"query\": \"查询\",\n  \"search\": \"搜索\",\n  \"enabled\": \"已启用\",\n  \"disabled\": \"已禁用\",\n  \"edit\": \"修改\",\n  \"delete\": \"删除\",\n  \"create\": \"新增\",\n  \"yes\": \"是\",\n  \"no\": \"否\",\n  \"showSearchPanel\": \"显示搜索面板\",\n  \"hideSearchPanel\": \"隐藏搜索面板\"\n}\n"
  },
  {
    "path": "packages/locales/src/langs/zh-CN/preferences.json",
    "content": "{\n  \"title\": \"偏好设置\",\n  \"subtitle\": \"自定义偏好设置 & 实时预览\",\n  \"enableStickyPreferencesNavigationBar\": \"开启首选项导航栏吸顶效果\",\n  \"disableStickyPreferencesNavigationBar\": \"关闭首选项导航栏吸顶效果\",\n  \"resetTitle\": \"重置偏好设置\",\n  \"resetTip\": \"数据有变化，点击可进行重置\",\n  \"resetSuccess\": \"重置偏好设置成功\",\n  \"appearance\": \"外观\",\n  \"layout\": \"布局\",\n  \"content\": \"内容\",\n  \"other\": \"其它\",\n  \"wide\": \"流式\",\n  \"compact\": \"定宽\",\n  \"followSystem\": \"跟随系统\",\n  \"vertical\": \"垂直\",\n  \"verticalTip\": \"侧边垂直菜单模式\",\n  \"horizontal\": \"水平\",\n  \"horizontalTip\": \"水平菜单模式，菜单全部显示在顶部\",\n  \"twoColumn\": \"双列菜单\",\n  \"twoColumnTip\": \"垂直双列菜单模式\",\n  \"headerSidebarNav\": \"侧边导航\",\n  \"headerSidebarNavTip\": \"顶部通栏，侧边导航模式\",\n  \"headerTwoColumn\": \"混合双列\",\n  \"headerTwoColumnTip\": \"双列、水平菜单共存模式\",\n  \"mixedMenu\": \"混合垂直\",\n  \"mixedMenuTip\": \"垂直水平菜单共存\",\n  \"fullContent\": \"内容全屏\",\n  \"fullContentTip\": \"不显示任何菜单，只显示内容主体\",\n  \"normal\": \"常规\",\n  \"plain\": \"朴素\",\n  \"rounded\": \"圆润\",\n  \"copyPreferences\": \"复制偏好设置\",\n  \"enableCopyPreferences\": \"显示复制偏好设置按钮\",\n  \"copyPreferencesSuccessTitle\": \"复制成功\",\n  \"copyPreferencesSuccess\": \"复制成功，请在 app 下的 `src/preferences.ts`内进行覆盖\",\n  \"clearAndLogout\": \"清空缓存 & 退出登录\",\n  \"mode\": \"模式\",\n  \"general\": \"通用\",\n  \"language\": \"语言\",\n  \"dynamicTitle\": \"动态标题\",\n  \"watermark\": \"水印\",\n  \"watermarkContent\": \"请输入水印文案\",\n  \"checkUpdates\": \"定时检查更新\",\n  \"position\": {\n    \"title\": \"偏好设置位置\",\n    \"header\": \"顶栏\",\n    \"auto\": \"自动\",\n    \"fixed\": \"固定\"\n  },\n  \"sidebar\": {\n    \"buttons\": \"显示按钮\",\n    \"buttonFixed\": \"固定按钮\",\n    \"buttonCollapsed\": \"折叠按钮\",\n    \"title\": \"侧边栏\",\n    \"width\": \"宽度\",\n    \"visible\": \"显示侧边栏\",\n    \"draggable\": \"侧边栏菜单拖拽\",\n    \"collapsed\": \"折叠菜单\",\n    \"collapsedShowTitle\": \"折叠显示菜单名\",\n    \"autoActivateChild\": \"自动激活子菜单\",\n    \"autoActivateChildTip\": \"点击顶层菜单时,自动激活第一个子菜单或者上一次激活的子菜单\",\n    \"expandOnHover\": \"鼠标悬停展开\",\n    \"expandOnHoverTip\": \"鼠标在折叠区域悬浮时，`启用`则展开当前子菜单，`禁用`则展开整个侧边栏\"\n  },\n  \"tabbar\": {\n    \"title\": \"标签栏\",\n    \"enable\": \"启用标签栏\",\n    \"icon\": \"显示标签栏图标\",\n    \"showMore\": \"显示更多按钮\",\n    \"showMaximize\": \"显示最大化按钮\",\n    \"persist\": \"持久化标签页\",\n    \"visitHistory\": \"访问历史记录\",\n    \"visitHistoryTip\": \"开启后，标签栏会记录标签访问历史\\n关闭当前标签，会自动选中上一个打开的标签\",\n    \"maxCount\": \"最大标签数\",\n    \"maxCountTip\": \"每次打开新的标签时如果超过最大标签数，\\n会自动关闭一个最先打开的标签\\n设置为 0 则不限制\",\n    \"draggable\": \"启动拖拽排序\",\n    \"wheelable\": \"启用纵向滚轮响应\",\n    \"middleClickClose\": \"点击鼠标中键关闭标签页\",\n    \"wheelableTip\": \"开启后，标签栏区域可以响应滚轮的纵向滚动事件。\\n关闭时，只能响应系统的横向滚动事件（需要按下Shift再滚动滚轮）\",\n    \"styleType\": {\n      \"title\": \"标签页风格\",\n      \"chrome\": \"谷歌\",\n      \"card\": \"卡片\",\n      \"plain\": \"朴素\",\n      \"brisk\": \"轻快\"\n    },\n    \"contextMenu\": {\n      \"reload\": \"重新加载\",\n      \"close\": \"关闭\",\n      \"pin\": \"固定\",\n      \"unpin\": \"取消固定\",\n      \"closeLeft\": \"关闭左侧标签页\",\n      \"closeRight\": \"关闭右侧标签页\",\n      \"closeOther\": \"关闭其它标签页\",\n      \"closeAll\": \"关闭全部标签页\",\n      \"openInNewWindow\": \"在新窗口打开\",\n      \"maximize\": \"最大化\",\n      \"restoreMaximize\": \"还原\"\n    }\n  },\n  \"navigationMenu\": {\n    \"title\": \"导航菜单\",\n    \"style\": \"导航菜单风格\",\n    \"accordion\": \"侧边导航菜单手风琴模式\",\n    \"split\": \"导航菜单分离\",\n    \"splitTip\": \"开启时，侧边栏显示顶栏对应菜单的子菜单\"\n  },\n  \"breadcrumb\": {\n    \"title\": \"面包屑导航\",\n    \"enable\": \"开启面包屑导航\",\n    \"icon\": \"显示面包屑图标\",\n    \"home\": \"显示首页按钮\",\n    \"style\": \"面包屑风格\",\n    \"hideOnlyOne\": \"仅有一个时隐藏\",\n    \"background\": \"背景\"\n  },\n  \"animation\": {\n    \"title\": \"动画\",\n    \"loading\": \"页面切换 Loading\",\n    \"transition\": \"页面切换动画\",\n    \"progress\": \"页面切换进度条\"\n  },\n  \"theme\": {\n    \"title\": \"主题\",\n    \"radius\": \"圆角\",\n    \"fontSize\": \"字体大小\",\n    \"fontSizeTip\": \"调整全局字体大小，实时预览效果\",\n    \"light\": \"浅色\",\n    \"dark\": \"深色\",\n    \"darkSidebar\": \"深色侧边栏\",\n    \"darkSidebarTip\": \"当主题为浅色，布局不为水平菜单或不为内容全屏时可开启\",\n    \"darkSidebarSub\": \"深色侧边栏子栏\",\n    \"darkSidebarSubTip\": \"当主题为浅色，开启深色侧边栏且布局使用双列菜单模式时可开启\",\n    \"darkHeader\": \"深色顶栏\",\n    \"weakMode\": \"色弱模式\",\n    \"grayMode\": \"灰色模式\",\n    \"builtin\": {\n      \"title\": \"内置主题\",\n      \"default\": \"默认\",\n      \"violet\": \"紫罗兰\",\n      \"pink\": \"樱花粉\",\n      \"rose\": \"玫瑰红\",\n      \"skyBlue\": \"天蓝色\",\n      \"deepBlue\": \"深蓝色\",\n      \"green\": \"浅绿色\",\n      \"deepGreen\": \"深绿色\",\n      \"orange\": \"橙黄色\",\n      \"yellow\": \"柠檬黄\",\n      \"zinc\": \"锌色灰\",\n      \"neutral\": \"中性色\",\n      \"slate\": \"石板灰\",\n      \"gray\": \"中灰色\",\n      \"custom\": \"自定义\"\n    }\n  },\n  \"header\": {\n    \"title\": \"顶栏\",\n    \"modeStatic\": \"静止\",\n    \"modeFixed\": \"固定\",\n    \"modeAuto\": \"自动隐藏和显示\",\n    \"modeAutoScroll\": \"滚动隐藏和显示\",\n    \"visible\": \"显示顶栏\",\n    \"menuAlign\": \"菜单位置\",\n    \"menuAlignStart\": \"左侧\",\n    \"menuAlignEnd\": \"右侧\",\n    \"menuAlignCenter\": \"居中\"\n  },\n  \"footer\": {\n    \"title\": \"底栏\",\n    \"visible\": \"显示底栏\",\n    \"fixed\": \"固定在底部\"\n  },\n  \"copyright\": {\n    \"title\": \"版权\",\n    \"enable\": \"启用版权\",\n    \"companyName\": \"公司名\",\n    \"companySiteLink\": \"公司主页\",\n    \"date\": \"日期\",\n    \"icp\": \"ICP 备案号\",\n    \"icpLink\": \"ICP 网站链接\"\n  },\n  \"shortcutKeys\": {\n    \"title\": \"快捷键\",\n    \"global\": \"全局\",\n    \"search\": \"全局搜索\",\n    \"logout\": \"退出登录\",\n    \"preferences\": \"偏好设置\"\n  },\n  \"widget\": {\n    \"title\": \"小部件\",\n    \"globalSearch\": \"启用全局搜索\",\n    \"fullscreen\": \"启用全屏\",\n    \"themeToggle\": \"启用主题切换\",\n    \"languageToggle\": \"启用语言切换\",\n    \"notification\": \"启用通知\",\n    \"sidebarToggle\": \"启用侧边栏切换\",\n    \"lockScreen\": \"启用锁屏\",\n    \"refresh\": \"启用刷新\"\n  }\n}\n"
  },
  {
    "path": "packages/locales/src/langs/zh-CN/profile.json",
    "content": "{\n  \"updatePassword\": \"更新密码\",\n  \"updateBasicProfile\": \"更新基本信息\"\n}\n"
  },
  {
    "path": "packages/locales/src/langs/zh-CN/ui.json",
    "content": "{\n  \"formRules\": {\n    \"required\": \"请输入{0}\",\n    \"selectRequired\": \"请选择{0}\",\n    \"minLength\": \"{0}至少{1}个字符\",\n    \"maxLength\": \"{0}最多{1}个字符\",\n    \"length\": \"{0}长度必须为{1}个字符\",\n    \"alreadyExists\": \"{0} `{1}` 已存在\",\n    \"startWith\": \"{0}必须以 {1} 开头\",\n    \"invalidURL\": \"请输入有效的链接\",\n    \"sizeLimit\": \"文件大小不能超过 {0}MB\",\n    \"previewWarning\": \"无法打开文件，没有可用的URL或预览地址\"\n  },\n  \"actionTitle\": {\n    \"edit\": \"修改{0}\",\n    \"create\": \"新增{0}\",\n    \"delete\": \"删除{0}\",\n    \"view\": \"查看{0}\"\n  },\n  \"actionMessage\": {\n    \"deleteConfirm\": \"确定删除 {0} 吗？\",\n    \"deleting\": \"正在删除 {0} ...\",\n    \"deleteSuccess\": \"{0} 删除成功\",\n    \"operationSuccess\": \"操作成功\",\n    \"operationFailed\": \"操作失败\"\n  },\n  \"placeholder\": {\n    \"input\": \"请输入\",\n    \"select\": \"请选择\",\n    \"upload\": \"点击上传\"\n  },\n  \"captcha\": {\n    \"title\": \"请完成安全验证\",\n    \"sliderSuccessText\": \"验证通过\",\n    \"sliderDefaultText\": \"请按住滑块拖动\",\n    \"sliderRotateDefaultTip\": \"点击图片可刷新\",\n    \"sliderTranslateDefaultTip\": \"点击图片可刷新\",\n    \"sliderRotateFailTip\": \"验证失败\",\n    \"sliderRotateSuccessTip\": \"验证成功，耗时{0}秒\",\n    \"sliderTranslateFailTip\": \"验证失败\",\n    \"sliderTranslateSuccessTip\": \"验证成功，耗时{0}秒\",\n    \"alt\": \"支持img标签src属性值\",\n    \"refreshAriaLabel\": \"刷新验证码\",\n    \"confirmAriaLabel\": \"确认选择\",\n    \"confirm\": \"确认\",\n    \"pointAriaLabel\": \"点击点\",\n    \"clickInOrder\": \"请依次点击\"\n  },\n  \"iconPicker\": {\n    \"placeholder\": \"选择一个图标\",\n    \"search\": \"搜索图标...\"\n  },\n  \"jsonViewer\": {\n    \"copy\": \"复制\",\n    \"copied\": \"已复制\"\n  },\n  \"crop\": {\n    \"title\": \"图片裁剪\",\n    \"titleTip\": \"裁剪比例 {0}\",\n    \"confirm\": \"裁剪\",\n    \"cancel\": \"取消裁剪\",\n    \"errorTip\": \"裁剪错误\"\n  },\n  \"fallback\": {\n    \"pageNotFound\": \"哎呀！未找到页面\",\n    \"pageNotFoundDesc\": \"抱歉，我们无法找到您要找的页面。\",\n    \"forbidden\": \"哎呀！访问被拒绝\",\n    \"forbiddenDesc\": \"抱歉，您没有权限访问此页面。\",\n    \"internalError\": \"哎呀！出错了\",\n    \"internalErrorDesc\": \"抱歉，服务器遇到错误。\",\n    \"offline\": \"离线页面\",\n    \"offlineError\": \"哎呀！网络错误\",\n    \"offlineErrorDesc\": \"抱歉，无法连接到互联网，请检查您的网络连接并重试。\",\n    \"comingSoon\": \"即将推出\",\n    \"http\": {\n      \"requestTimeout\": \"请求超时，请稍后再试。\",\n      \"networkError\": \"网络异常，请检查您的网络连接后重试。\",\n      \"badRequest\": \"请求错误。请检查您的输入并重试。\",\n      \"unauthorized\": \"登录认证过期，请重新登录后继续。\",\n      \"forbidden\": \"禁止访问, 您没有权限访问此资源。\",\n      \"notFound\": \"未找到, 请求的资源不存在。\",\n      \"internalServerError\": \"内部服务器错误，请稍后再试。\"\n    }\n  },\n  \"widgets\": {\n    \"document\": \"文档\",\n    \"qa\": \"问题 & 帮助\",\n    \"setting\": \"设置\",\n    \"logoutTip\": \"是否退出登录？\",\n    \"viewAll\": \"查看所有消息\",\n    \"notifications\": \"通知\",\n    \"markAllAsRead\": \"全部标记为已读\",\n    \"clearNotifications\": \"清空\",\n    \"checkUpdatesTitle\": \"新版本可用\",\n    \"checkUpdatesDescription\": \"点击刷新以获取最新版本\",\n    \"search\": {\n      \"title\": \"搜索\",\n      \"searchNavigate\": \"搜索导航菜单\",\n      \"select\": \"选择\",\n      \"navigate\": \"导航\",\n      \"close\": \"关闭\",\n      \"noResults\": \"未找到搜索结果\",\n      \"noRecent\": \"没有搜索历史\",\n      \"recent\": \"搜索历史\"\n    },\n    \"lockScreen\": {\n      \"title\": \"锁定屏幕\",\n      \"screenButton\": \"锁定\",\n      \"password\": \"密码\",\n      \"placeholder\": \"请输入锁屏密码\",\n      \"unlock\": \"点击解锁\",\n      \"errorPasswordTip\": \"密码错误，请重新输入\",\n      \"backToLogin\": \"返回登录\",\n      \"entry\": \"进入系统\"\n    },\n    \"timezone\": {\n      \"setTimezone\": \"设置时区\",\n      \"setSuccess\": \"时区设置成功\"\n    }\n  }\n}\n"
  },
  {
    "path": "packages/locales/src/typing.ts",
    "content": "export type SupportedLanguagesType = 'en-US' | 'zh-CN';\n\nexport type ImportLocaleFn = () => Promise<{ default: Record<string, string> }>;\n\nexport type LoadMessageFn = (\n  lang: SupportedLanguagesType,\n) => Promise<Record<string, string> | undefined>;\n\nexport interface LocaleSetupOptions {\n  /**\n   * Default language\n   * @default zh-CN\n   */\n  defaultLocale?: SupportedLanguagesType;\n  /**\n   * Load message function\n   * @param lang\n   * @returns\n   */\n  loadMessages?: LoadMessageFn;\n  /**\n   * Whether to warn when the key is not found\n   */\n  missingWarn?: boolean;\n}\n"
  },
  {
    "path": "packages/locales/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/preferences/package.json",
    "content": "{\n  \"name\": \"@vben/preferences\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/preferences\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/preferences\": \"workspace:*\",\n    \"@vben-core/typings\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "packages/preferences/src/index.ts",
    "content": "import type { Preferences } from '@vben-core/preferences';\nimport type { DeepPartial } from '@vben-core/typings';\n\n/**\n * 如果你想所有的app都使用相同的默认偏好设置，你可以在这里定义\n * 而不是去修改 @vben-core/preferences 中的默认偏好设置\n * @param preferences\n * @returns\n */\n\nfunction defineOverridesPreferences(preferences: DeepPartial<Preferences>) {\n  return preferences;\n}\n\nexport { defineOverridesPreferences };\n\nexport * from '@vben-core/preferences';\n"
  },
  {
    "path": "packages/preferences/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/stores/package.json",
    "content": "{\n  \"name\": \"@vben/stores\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/stores\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/preferences\": \"workspace:*\",\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben-core/typings\": \"workspace:*\",\n    \"pinia\": \"catalog:\",\n    \"pinia-plugin-persistedstate\": \"catalog:\",\n    \"secure-ls\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/stores/shim-pinia.d.ts",
    "content": "// https://github.com/vuejs/pinia/issues/2098\ndeclare module 'pinia' {\n  export function acceptHMRUpdate(\n    initialUseStore: any | StoreDefinition,\n    hot: any,\n  ): (newModule: any) => any;\n}\n\nexport { acceptHMRUpdate };\n"
  },
  {
    "path": "packages/stores/src/index.ts",
    "content": "export * from './modules';\nexport * from './setup';\nexport { defineStore, storeToRefs } from 'pinia';\n"
  },
  {
    "path": "packages/stores/src/modules/access.test.ts",
    "content": "import { createPinia, setActivePinia } from 'pinia';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport { useAccessStore } from './access';\n\ndescribe('useAccessStore', () => {\n  beforeEach(() => {\n    setActivePinia(createPinia());\n  });\n\n  it('updates accessMenus state', () => {\n    const store = useAccessStore();\n    expect(store.accessMenus).toEqual([]);\n    store.setAccessMenus([{ name: 'Dashboard', path: '/dashboard' }]);\n    expect(store.accessMenus).toEqual([\n      { name: 'Dashboard', path: '/dashboard' },\n    ]);\n  });\n\n  it('updates accessToken state correctly', () => {\n    const store = useAccessStore();\n    expect(store.accessToken).toBeNull(); // 初始状态\n    store.setAccessToken('abc123');\n    expect(store.accessToken).toBe('abc123');\n  });\n\n  it('returns the correct accessToken', () => {\n    const store = useAccessStore();\n    store.setAccessToken('xyz789');\n    expect(store.accessToken).toBe('xyz789');\n  });\n\n  // 测试设置空的访问菜单列表\n  it('handles empty accessMenus correctly', () => {\n    const store = useAccessStore();\n    store.setAccessMenus([]);\n    expect(store.accessMenus).toEqual([]);\n  });\n\n  // 测试设置空的访问路由列表\n  it('handles empty accessRoutes correctly', () => {\n    const store = useAccessStore();\n    store.setAccessRoutes([]);\n    expect(store.accessRoutes).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "packages/stores/src/modules/access.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport type { MenuRecordRaw } from '@vben-core/typings';\n\nimport { acceptHMRUpdate, defineStore } from 'pinia';\n\ntype AccessToken = null | string;\n\ninterface AccessState {\n  /**\n   * 权限码\n   */\n  accessCodes: string[];\n  /**\n   * 可访问的菜单列表\n   */\n  accessMenus: MenuRecordRaw[];\n  /**\n   * 可访问的路由列表\n   */\n  accessRoutes: RouteRecordRaw[];\n  /**\n   * 登录 accessToken\n   */\n  accessToken: AccessToken;\n  /**\n   * 是否已经检查过权限\n   */\n  isAccessChecked: boolean;\n  /**\n   * 是否锁屏状态\n   */\n  isLockScreen: boolean;\n  /**\n   * 锁屏密码\n   */\n  lockScreenPassword?: string;\n  /**\n   * 登录是否过期\n   */\n  loginExpired: boolean;\n  /**\n   * 登录 accessToken\n   */\n  refreshToken: AccessToken;\n}\n\n/**\n * @zh_CN 访问权限相关\n */\nexport const useAccessStore = defineStore('core-access', {\n  actions: {\n    getMenuByPath(path: string) {\n      function findMenu(\n        menus: MenuRecordRaw[],\n        path: string,\n      ): MenuRecordRaw | undefined {\n        for (const menu of menus) {\n          if (menu.path === path) {\n            return menu;\n          }\n          if (menu.children) {\n            const matched = findMenu(menu.children, path);\n            if (matched) {\n              return matched;\n            }\n          }\n        }\n      }\n      return findMenu(this.accessMenus, path);\n    },\n    lockScreen(password: string) {\n      this.isLockScreen = true;\n      this.lockScreenPassword = password;\n    },\n    setAccessCodes(codes: string[]) {\n      this.accessCodes = codes;\n    },\n    setAccessMenus(menus: MenuRecordRaw[]) {\n      this.accessMenus = menus;\n    },\n    setAccessRoutes(routes: RouteRecordRaw[]) {\n      this.accessRoutes = routes;\n    },\n    setAccessToken(token: AccessToken) {\n      this.accessToken = token;\n    },\n    setIsAccessChecked(isAccessChecked: boolean) {\n      this.isAccessChecked = isAccessChecked;\n    },\n    setLoginExpired(loginExpired: boolean) {\n      this.loginExpired = loginExpired;\n    },\n    setRefreshToken(token: AccessToken) {\n      this.refreshToken = token;\n    },\n    unlockScreen() {\n      this.isLockScreen = false;\n      this.lockScreenPassword = undefined;\n    },\n  },\n  persist: {\n    // 持久化\n    pick: [\n      'accessToken',\n      'refreshToken',\n      'accessCodes',\n      'isLockScreen',\n      'lockScreenPassword',\n    ],\n  },\n  state: (): AccessState => ({\n    accessCodes: [],\n    accessMenus: [],\n    accessRoutes: [],\n    accessToken: null,\n    isAccessChecked: false,\n    isLockScreen: false,\n    lockScreenPassword: undefined,\n    loginExpired: false,\n    refreshToken: null,\n  }),\n});\n\n// 解决热更新问题\nconst hot = import.meta.hot;\nif (hot) {\n  hot.accept(acceptHMRUpdate(useAccessStore, hot));\n}\n"
  },
  {
    "path": "packages/stores/src/modules/index.ts",
    "content": "export * from './access';\nexport * from './tabbar';\nexport * from './timezone';\nexport * from './user';\n"
  },
  {
    "path": "packages/stores/src/modules/tabbar.test.ts",
    "content": "import { createRouter, createWebHistory } from 'vue-router';\n\nimport { createPinia, setActivePinia } from 'pinia';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { useTabbarStore } from './tabbar';\n\ndescribe('useAccessStore', () => {\n  const router = createRouter({\n    history: createWebHistory(),\n    routes: [],\n  });\n  router.push = vi.fn();\n  router.replace = vi.fn();\n  beforeEach(() => {\n    setActivePinia(createPinia());\n    vi.clearAllMocks();\n  });\n\n  it('adds a new tab', () => {\n    const store = useTabbarStore();\n    const tab: any = {\n      fullPath: '/home',\n      meta: {},\n      key: '/home',\n      name: 'Home',\n      path: '/home',\n    };\n    const addNewTab = store.addTab(tab);\n    expect(store.tabs.length).toBe(1);\n    expect(store.tabs[0]).toEqual(addNewTab);\n  });\n\n  it('adds a new tab if it does not exist', () => {\n    const store = useTabbarStore();\n    const newTab: any = {\n      fullPath: '/new',\n      meta: {},\n      name: 'New',\n      path: '/new',\n    };\n    const addNewTab = store.addTab(newTab);\n    expect(store.tabs).toContainEqual(addNewTab);\n  });\n\n  it('updates an existing tab instead of adding a new one', () => {\n    const store = useTabbarStore();\n    const initialTab: any = {\n      fullPath: '/existing',\n      meta: {\n        fullPathKey: false,\n      },\n      name: 'Existing',\n      path: '/existing',\n      query: {},\n    };\n    store.addTab(initialTab);\n    const updatedTab = { ...initialTab, query: { id: '1' } };\n    store.addTab(updatedTab);\n    expect(store.tabs.length).toBe(1);\n    expect(store.tabs[0]?.query).toEqual({ id: '1' });\n  });\n\n  it('closes all tabs', async () => {\n    const store = useTabbarStore();\n    store.addTab({\n      fullPath: '/home',\n      meta: {},\n      name: 'Home',\n      path: '/home',\n    } as any);\n    router.replace = vi.fn();\n\n    await store.closeAllTabs(router);\n\n    expect(store.tabs.length).toBe(1);\n  });\n\n  it('closes a non-affix tab', () => {\n    const store = useTabbarStore();\n    const tab: any = {\n      fullPath: '/closable',\n      meta: {},\n      name: 'Closable',\n      path: '/closable',\n    };\n    store.tabs.push(tab);\n    store._close(tab);\n    expect(store.tabs.length).toBe(0);\n  });\n\n  it('does not close an affix tab', () => {\n    const store = useTabbarStore();\n    const affixTab: any = {\n      fullPath: '/affix',\n      meta: { affixTab: true },\n      name: 'Affix',\n      path: '/affix',\n    };\n    store.tabs.push(affixTab);\n    store._close(affixTab);\n    expect(store.tabs.length).toBe(1); // Affix tab should not be closed\n  });\n\n  it('returns all cache tabs', () => {\n    const store = useTabbarStore();\n    store.cachedTabs.add('Home');\n    store.cachedTabs.add('About');\n    expect(store.getCachedTabs).toEqual(['Home', 'About']);\n  });\n\n  it('returns all tabs, including affix tabs', () => {\n    const store = useTabbarStore();\n    const normalTab: any = {\n      fullPath: '/normal',\n      meta: {},\n      name: 'Normal',\n      path: '/normal',\n    };\n    const affixTab: any = {\n      fullPath: '/affix',\n      meta: { affixTab: true },\n      name: 'Affix',\n      path: '/affix',\n    };\n    store.tabs.push(normalTab);\n    store.affixTabs.push(affixTab);\n    expect(store.getTabs).toContainEqual(normalTab);\n    expect(store.affixTabs).toContainEqual(affixTab);\n  });\n\n  it('navigates to a specific tab', async () => {\n    const store = useTabbarStore();\n    const tab: any = { meta: {}, name: 'Dashboard', path: '/dashboard' };\n\n    await store._goToTab(tab, router);\n\n    expect(router.replace).toHaveBeenCalledWith({\n      params: {},\n      path: '/dashboard',\n      query: {},\n    });\n  });\n\n  it('closes multiple tabs by paths', async () => {\n    const store = useTabbarStore();\n    store.addTab({\n      fullPath: '/home',\n      meta: {},\n      name: 'Home',\n      path: '/home',\n    } as any);\n    store.addTab({\n      fullPath: '/about',\n      meta: {},\n      name: 'About',\n      path: '/about',\n    } as any);\n    store.addTab({\n      fullPath: '/contact',\n      meta: {},\n      name: 'Contact',\n      path: '/contact',\n    } as any);\n\n    await store._bulkCloseByKeys(['/home', '/contact']);\n\n    expect(store.tabs).toHaveLength(1);\n    expect(store.tabs[0]?.name).toBe('About');\n  });\n\n  it('closes all tabs to the left of the specified tab', async () => {\n    const store = useTabbarStore();\n    store.addTab({\n      fullPath: '/home',\n      meta: {},\n      name: 'Home',\n      path: '/home',\n    } as any);\n    store.addTab({\n      fullPath: '/about',\n      meta: {},\n      name: 'About',\n      path: '/about',\n    } as any);\n    const targetTab: any = {\n      fullPath: '/contact',\n      meta: {},\n      name: 'Contact',\n      path: '/contact',\n    };\n    const addTargetTab = store.addTab(targetTab);\n    await store.closeLeftTabs(addTargetTab);\n\n    expect(store.tabs).toHaveLength(1);\n    expect(store.tabs[0]?.name).toBe('Contact');\n  });\n\n  it('closes all tabs except the specified tab', async () => {\n    const store = useTabbarStore();\n    store.addTab({\n      fullPath: '/home',\n      meta: {},\n      name: 'Home',\n      path: '/home',\n    } as any);\n    const targetTab: any = {\n      fullPath: '/about',\n      meta: {},\n      name: 'About',\n      path: '/about',\n    };\n    const addTargetTab = store.addTab(targetTab);\n    store.addTab({\n      fullPath: '/contact',\n      meta: {},\n      name: 'Contact',\n      path: '/contact',\n    } as any);\n\n    await store.closeOtherTabs(addTargetTab);\n\n    expect(store.tabs).toHaveLength(1);\n    expect(store.tabs[0]?.name).toBe('About');\n  });\n\n  it('closes all tabs to the right of the specified tab', async () => {\n    const store = useTabbarStore();\n    const targetTab: any = {\n      fullPath: '/home',\n      meta: {},\n      name: 'Home',\n      path: '/home',\n    };\n    const addTargetTab = store.addTab(targetTab);\n    store.addTab({\n      fullPath: '/about',\n      meta: {},\n      name: 'About',\n      path: '/about',\n    } as any);\n    store.addTab({\n      fullPath: '/contact',\n      meta: {},\n      name: 'Contact',\n      path: '/contact',\n    } as any);\n\n    await store.closeRightTabs(addTargetTab);\n\n    expect(store.tabs).toHaveLength(1);\n    expect(store.tabs[0]?.name).toBe('Home');\n  });\n\n  it('closes the tab with the specified key', async () => {\n    const store = useTabbarStore();\n    const keyToClose = '/about';\n    store.addTab({\n      fullPath: '/home',\n      meta: {},\n      name: 'Home',\n      path: '/home',\n    } as any);\n    store.addTab({\n      fullPath: keyToClose,\n      meta: {},\n      name: 'About',\n      path: '/about',\n    } as any);\n    store.addTab({\n      fullPath: '/contact',\n      meta: {},\n      name: 'Contact',\n      path: '/contact',\n    } as any);\n\n    await store.closeTabByKey(keyToClose, router);\n\n    expect(store.tabs).toHaveLength(2);\n    expect(\n      store.tabs.find((tab) => tab.fullPath === keyToClose),\n    ).toBeUndefined();\n  });\n\n  it('refreshes the current tab', async () => {\n    const store = useTabbarStore();\n    const currentTab: any = {\n      fullPath: '/dashboard',\n      meta: { name: 'Dashboard' },\n      name: 'Dashboard',\n      path: '/dashboard',\n    };\n    router.currentRoute.value = currentTab;\n\n    await store.refresh(router);\n\n    expect(store.excludeCachedTabs.has('Dashboard')).toBe(false);\n    expect(store.renderRouteView).toBe(true);\n  });\n});\n"
  },
  {
    "path": "packages/stores/src/modules/tabbar.ts",
    "content": "import type { ComputedRef, VNode } from 'vue';\nimport type {\n  RouteLocationNormalized,\n  RouteLocationNormalizedLoaded,\n  RouteLocationNormalizedLoadedGeneric,\n  Router,\n  RouteRecordNormalized,\n} from 'vue-router';\n\nimport type { TabDefinition } from '@vben-core/typings';\n\nimport { markRaw, toRaw } from 'vue';\n\nimport { preferences } from '@vben-core/preferences';\nimport {\n  createStack,\n  openRouteInNewWindow,\n  Stack,\n  startProgress,\n  stopProgress,\n} from '@vben-core/shared/utils';\n\nimport { acceptHMRUpdate, defineStore } from 'pinia';\n\ninterface RouteCached {\n  component: VNode;\n  key: string;\n  route: RouteLocationNormalizedLoadedGeneric;\n}\n\ninterface TabbarState {\n  cachedRoutes: Map<string, RouteCached>;\n  /**\n   * @zh_CN 当前打开的标签页列表缓存\n   */\n  cachedTabs: Set<string>;\n  /**\n   * @zh_CN 拖拽结束的索引\n   */\n  dragEndIndex: number;\n  /**\n   * @zh_CN 需要排除缓存的标签页\n   */\n  excludeCachedTabs: Set<string>;\n  /**\n   * @zh_CN 标签右键菜单列表\n   */\n  menuList: string[];\n  /**\n   * @zh_CN 是否刷新\n   */\n  renderRouteView?: boolean;\n  /**\n   * @zh_CN 当前打开的标签页列表\n   */\n  tabs: TabDefinition[];\n  /**\n   * @zh_CN 更新时间，用于一些更新场景，使用watch深度监听的话，会损耗性能\n   */\n  updateTime?: number;\n  /**\n   * @zh_CN 上一个标签页打开的标签\n   */\n  visitHistory: Stack<string>;\n}\n\n/**\n * @zh_CN 访问历史记录最大数量\n */\nconst MAX_VISIT_HISTORY = 50;\n\n/**\n * @zh_CN 访问权限相关\n */\nexport const useTabbarStore = defineStore('core-tabbar', {\n  actions: {\n    /**\n     * Close tabs in bulk\n     */\n    async _bulkCloseByKeys(keys: string[]) {\n      const keySet = new Set(keys);\n      this.tabs = this.tabs.filter(\n        (item) => !keySet.has(getTabKeyFromTab(item)),\n      );\n      if (isVisitHistory()) {\n        this.visitHistory.remove(...keys);\n      }\n\n      await this.updateCacheTabs();\n    },\n    /**\n     * @zh_CN 关闭标签页\n     * @param tab\n     */\n    _close(tab: TabDefinition) {\n      if (isAffixTab(tab)) {\n        return;\n      }\n      const index = this.tabs.findIndex((item) => equalTab(item, tab));\n      index !== -1 && this.tabs.splice(index, 1);\n    },\n    /**\n     * @zh_CN 跳转到默认标签页\n     */\n    async _goToDefaultTab(router: Router) {\n      if (this.getTabs.length <= 0) {\n        return;\n      }\n      const firstTab = this.getTabs[0];\n      if (firstTab) {\n        await this._goToTab(firstTab, router);\n      }\n    },\n    /**\n     * @zh_CN 跳转到标签页\n     * @param tab\n     * @param router\n     */\n    async _goToTab(tab: TabDefinition, router: Router) {\n      const { params, path, query } = tab;\n      const toParams = {\n        params: params || {},\n        path,\n        query: query || {},\n      };\n      await router.replace(toParams);\n    },\n    /**\n     * @zh_CN 添加标签页\n     * @param routeTab\n     */\n    addTab(routeTab: TabDefinition): TabDefinition {\n      let tab = cloneTab(routeTab);\n      if (!tab.key) {\n        tab.key = getTabKey(routeTab);\n      }\n      if (!isTabShown(tab)) {\n        return tab;\n      }\n\n      const tabIndex = this.tabs.findIndex((item) => {\n        return equalTab(item, tab);\n      });\n\n      if (tabIndex === -1) {\n        const maxCount = preferences.tabbar.maxCount;\n        // 获取动态路由打开数，超过 0 即代表需要控制打开数\n        const maxNumOfOpenTab = (routeTab?.meta?.maxNumOfOpenTab ??\n          -1) as number;\n        // 如果动态路由层级大于 0 了，那么就要限制该路由的打开数限制了\n        // 获取到已经打开的动态路由数, 判断是否大于某一个值\n        if (\n          maxNumOfOpenTab > 0 &&\n          this.tabs.filter((tab) => tab.name === routeTab.name).length >=\n            maxNumOfOpenTab\n        ) {\n          // 关闭第一个\n          const index = this.tabs.findIndex(\n            (item) => item.name === routeTab.name,\n          );\n          index !== -1 && this.tabs.splice(index, 1);\n        } else if (maxCount > 0 && this.tabs.length >= maxCount) {\n          // 关闭第一个\n          const index = this.tabs.findIndex(\n            (item) =>\n              !Reflect.has(item.meta, 'affixTab') || !item.meta.affixTab,\n          );\n          index !== -1 && this.tabs.splice(index, 1);\n        }\n        this.tabs.push(tab);\n      } else {\n        // 页面已经存在，不重复添加选项卡，只更新选项卡参数\n        const currentTab = toRaw(this.tabs)[tabIndex];\n        const mergedTab = {\n          ...currentTab,\n          ...tab,\n          meta: { ...currentTab?.meta, ...tab.meta },\n        };\n        if (currentTab) {\n          const curMeta = currentTab.meta;\n          if (Reflect.has(curMeta, 'affixTab')) {\n            mergedTab.meta.affixTab = curMeta.affixTab;\n          }\n          if (Reflect.has(curMeta, 'newTabTitle')) {\n            mergedTab.meta.newTabTitle = curMeta.newTabTitle;\n          }\n        }\n        tab = mergedTab;\n        this.tabs.splice(tabIndex, 1, mergedTab);\n      }\n      this.updateCacheTabs();\n      // 添加访问历史记录\n      if (isVisitHistory()) {\n        this.visitHistory.push(tab.key as string);\n      }\n      return tab;\n    },\n    /**\n     * @zh_CN 关闭所有标签页\n     */\n    async closeAllTabs(router: Router) {\n      const newTabs = this.tabs.filter((tab) => isAffixTab(tab));\n      this.tabs = newTabs.length > 0 ? newTabs : [...this.tabs].splice(0, 1);\n      // 设置访问历史记录\n      if (isVisitHistory()) {\n        this.visitHistory.retain(\n          this.tabs.map((item) => getTabKeyFromTab(item)),\n        );\n      }\n      await this._goToDefaultTab(router);\n      this.updateCacheTabs();\n    },\n    /**\n     * @zh_CN 关闭左侧标签页\n     * @param tab\n     */\n    async closeLeftTabs(tab: TabDefinition) {\n      const index = this.tabs.findIndex((item) => equalTab(item, tab));\n\n      if (index < 1) {\n        return;\n      }\n\n      const leftTabs = this.tabs.slice(0, index);\n      const keys: string[] = [];\n\n      for (const item of leftTabs) {\n        if (!isAffixTab(item)) {\n          keys.push(item.key as string);\n        }\n      }\n      await this._bulkCloseByKeys(keys);\n    },\n    /**\n     * @zh_CN 关闭其他标签页\n     * @param tab\n     */\n    async closeOtherTabs(tab: TabDefinition) {\n      const closeKeys = this.tabs.map((item) => getTabKeyFromTab(item));\n\n      const keys: string[] = [];\n\n      for (const key of closeKeys) {\n        if (key !== getTabKeyFromTab(tab)) {\n          const closeTab = this.tabs.find(\n            (item) => getTabKeyFromTab(item) === key,\n          );\n          if (!closeTab) {\n            continue;\n          }\n          if (!isAffixTab(closeTab)) {\n            keys.push(closeTab.key as string);\n          }\n        }\n      }\n      await this._bulkCloseByKeys(keys);\n    },\n    /**\n     * @zh_CN 关闭右侧标签页\n     * @param tab\n     */\n    async closeRightTabs(tab: TabDefinition) {\n      const index = this.tabs.findIndex((item) => equalTab(item, tab));\n\n      if (index !== -1 && index < this.tabs.length - 1) {\n        const rightTabs = this.tabs.slice(index + 1);\n\n        const keys: string[] = [];\n        for (const item of rightTabs) {\n          if (!isAffixTab(item)) {\n            keys.push(item.key as string);\n          }\n        }\n        await this._bulkCloseByKeys(keys);\n      }\n    },\n\n    /**\n     * @zh_CN 关闭标签页\n     * @param tab\n     * @param router\n     */\n    async closeTab(tab: TabDefinition, router: Router) {\n      const { currentRoute } = router;\n      const currentTabKey = getTabKey(currentRoute.value);\n      // 关闭不是激活选项卡\n      if (currentTabKey !== getTabKeyFromTab(tab)) {\n        this._close(tab);\n        this.updateCacheTabs();\n        // 移除访问历史记录\n        if (isVisitHistory()) {\n          this.visitHistory.remove(getTabKeyFromTab(tab));\n        }\n        return;\n      }\n      if (this.getTabs.length <= 1) {\n        console.error('Failed to close the tab; only one tab remains open.');\n        return;\n      }\n      // 从访问历史记录中移除当前关闭的tab\n      if (isVisitHistory()) {\n        this.visitHistory.remove(currentTabKey);\n        this._close(tab);\n\n        let previousTab: TabDefinition | undefined;\n        let previousTabKey: string | undefined;\n        while (true) {\n          previousTabKey = this.visitHistory.pop();\n          if (!previousTabKey) {\n            break;\n          }\n          previousTab = this.getTabByKey(previousTabKey);\n          if (previousTab) {\n            break;\n          }\n        }\n        await (previousTab\n          ? this._goToTab(previousTab, router)\n          : this._goToDefaultTab(router));\n        return;\n      }\n      // 未开启访问历史记录，直接跳转下一个或上一个tab\n      const index = this.getTabs.findIndex(\n        (item) => getTabKeyFromTab(item) === getTabKey(currentRoute.value),\n      );\n\n      const before = this.getTabs[index - 1];\n      const after = this.getTabs[index + 1];\n\n      // 下一个tab存在，跳转到下一个\n      if (after) {\n        this._close(tab);\n        await this._goToTab(after, router);\n        // 上一个tab存在，跳转到上一个\n      } else if (before) {\n        this._close(tab);\n        await this._goToTab(before, router);\n      }\n    },\n\n    /**\n     * @zh_CN 通过key关闭标签页\n     * @param key\n     * @param router\n     */\n    async closeTabByKey(key: string, router: Router) {\n      const originKey = decodeURIComponent(key);\n      const index = this.tabs.findIndex(\n        (item) => getTabKeyFromTab(item) === originKey,\n      );\n      if (index === -1) {\n        return;\n      }\n\n      const tab = this.tabs[index];\n      if (tab) {\n        await this.closeTab(tab, router);\n      }\n    },\n\n    /**\n     * 根据tab的key获取tab\n     * @param key\n     */\n    getTabByKey(key: string) {\n      return this.getTabs.find(\n        (item) => getTabKeyFromTab(item) === key,\n      ) as TabDefinition;\n    },\n    /**\n     * @zh_CN 新窗口打开标签页\n     * @param tab\n     */\n    async openTabInNewWindow(tab: TabDefinition) {\n      openRouteInNewWindow(tab.fullPath || tab.path);\n    },\n\n    /**\n     * @zh_CN 固定标签页\n     * @param tab\n     */\n    async pinTab(tab: TabDefinition) {\n      const index = this.tabs.findIndex((item) => equalTab(item, tab));\n      if (index === -1) {\n        return;\n      }\n      const oldTab = this.tabs[index];\n      tab.meta.affixTab = true;\n      tab.meta.title = oldTab?.meta?.title as string;\n      // this.addTab(tab);\n      this.tabs.splice(index, 1, tab);\n      // 过滤固定tabs，后面更改affixTabOrder的值的话可能会有问题，目前行464排序affixTabs没有设置值\n      const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));\n      // 获得固定tabs的index\n      const newIndex = affixTabs.findIndex((item) => equalTab(item, tab));\n      // 交换位置重新排序\n      await this.sortTabs(index, newIndex);\n    },\n\n    /**\n     * 刷新标签页\n     */\n    async refresh(router: Router | string) {\n      // 如果是Router路由，那么就根据当前路由刷新\n      // 如果是string字符串，为路由名称，则定向刷新指定标签页，不能是当前路由名称，否则不会刷新\n      if (typeof router === 'string') {\n        return await this.refreshByName(router);\n      }\n\n      const { currentRoute } = router;\n      const { name } = currentRoute.value;\n\n      this.excludeCachedTabs.add(name as string);\n      this.renderRouteView = false;\n      startProgress();\n\n      await new Promise((resolve) => setTimeout(resolve, 200));\n\n      this.excludeCachedTabs.delete(name as string);\n      this.renderRouteView = true;\n      stopProgress();\n    },\n\n    /**\n     * 根据路由名称刷新指定标签页\n     */\n    async refreshByName(name: string) {\n      this.excludeCachedTabs.add(name);\n      await new Promise((resolve) => setTimeout(resolve, 200));\n      this.excludeCachedTabs.delete(name);\n    },\n\n    /**\n     * @zh_CN 重置标签页标题\n     */\n    async resetTabTitle(tab: TabDefinition) {\n      if (tab?.meta?.newTabTitle) {\n        return;\n      }\n      const findTab = this.tabs.find((item) => equalTab(item, tab));\n      if (findTab) {\n        findTab.meta.newTabTitle = undefined;\n        await this.updateCacheTabs();\n      }\n    },\n\n    /**\n     * 设置固定标签页\n     * @param tabs\n     */\n    setAffixTabs(tabs: RouteRecordNormalized[]) {\n      for (const tab of tabs) {\n        tab.meta.affixTab = true;\n        this.addTab(routeToTab(tab));\n      }\n    },\n\n    /**\n     * @zh_CN 更新菜单列表\n     * @param list\n     */\n    setMenuList(list: string[]) {\n      this.menuList = list;\n    },\n\n    /**\n     * @zh_CN 设置标签页标题\n     *\n     * @zh_CN 支持设置静态标题字符串或计算属性作为动态标题\n     * @zh_CN 当标题为计算属性时,标题会随计算属性值变化而自动更新\n     * @zh_CN 适用于需要根据状态或多语言动态更新标题的场景\n     *\n     * @param {TabDefinition} tab - 标签页对象\n     * @param {ComputedRef<string> | string} title - 标题内容,支持静态字符串或计算属性\n     *\n     * @example\n     * // 设置静态标题\n     * setTabTitle(tab, '新标签页');\n     *\n     * @example\n     * // 设置动态标题\n     * setTabTitle(tab, computed(() => t('common.dashboard')));\n     */\n    async setTabTitle(tab: TabDefinition, title: ComputedRef<string> | string) {\n      const findTab = this.tabs.find((item) => equalTab(item, tab));\n\n      if (findTab) {\n        findTab.meta.newTabTitle = title;\n\n        await this.updateCacheTabs();\n      }\n    },\n    setUpdateTime() {\n      this.updateTime = Date.now();\n    },\n    /**\n     * @zh_CN 设置标签页顺序\n     * @param oldIndex\n     * @param newIndex\n     */\n    async sortTabs(oldIndex: number, newIndex: number) {\n      const currentTab = this.tabs[oldIndex];\n      if (!currentTab) {\n        return;\n      }\n      this.tabs.splice(oldIndex, 1);\n      this.tabs.splice(newIndex, 0, currentTab);\n      this.dragEndIndex = this.dragEndIndex + 1;\n    },\n\n    /**\n     * @zh_CN 切换固定标签页\n     * @param tab\n     */\n    async toggleTabPin(tab: TabDefinition) {\n      const affixTab = tab?.meta?.affixTab ?? false;\n\n      await (affixTab ? this.unpinTab(tab) : this.pinTab(tab));\n    },\n\n    /**\n     * @zh_CN 取消固定标签页\n     * @param tab\n     */\n    async unpinTab(tab: TabDefinition) {\n      const index = this.tabs.findIndex((item) => equalTab(item, tab));\n      if (index === -1) {\n        return;\n      }\n      const oldTab = this.tabs[index];\n      tab.meta.affixTab = false;\n      tab.meta.title = oldTab?.meta?.title as string;\n      // this.addTab(tab);\n      this.tabs.splice(index, 1, tab);\n      // 过滤固定tabs，后面更改affixTabOrder的值的话可能会有问题，目前行464排序affixTabs没有设置值\n      const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));\n      // 获得固定tabs的index,使用固定tabs的下一个位置也就是活动tabs的第一个位置\n      const newIndex = affixTabs.length;\n      // 交换位置重新排序\n      await this.sortTabs(index, newIndex);\n    },\n    /**\n     * 根据当前打开的选项卡更新缓存\n     */\n    async updateCacheTabs() {\n      const cacheMap = new Set<string>();\n\n      for (const tab of this.tabs) {\n        // 跳过不需要持久化的标签页\n        const keepAlive = tab.meta?.keepAlive;\n        if (!keepAlive) {\n          continue;\n        }\n        (tab.matched || []).forEach((t, i) => {\n          if (i > 0) {\n            cacheMap.add(t.name as string);\n          }\n        });\n\n        const name = tab.name as string;\n        cacheMap.add(name);\n      }\n      this.cachedTabs = cacheMap;\n    },\n    /**\n     * 添加缓存的route\n     * @param component\n     * @param route\n     */\n    addCachedRoute(component: VNode, route: RouteLocationNormalizedLoaded) {\n      const key = getTabKey(route);\n      if (this.cachedRoutes.has(key)) {\n        return;\n      }\n      this.cachedRoutes.set(key, {\n        key,\n        component: markRaw(component),\n        route: markRaw(route),\n      });\n    },\n    removeCachedRoute(key: string) {\n      this.cachedRoutes.delete(key);\n    },\n  },\n  getters: {\n    affixTabs(): TabDefinition[] {\n      const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));\n\n      return affixTabs.toSorted((a, b) => {\n        const orderA = (a.meta?.affixTabOrder ?? 0) as number;\n        const orderB = (b.meta?.affixTabOrder ?? 0) as number;\n        return orderA - orderB;\n      });\n    },\n    getCachedTabs(): string[] {\n      return [...this.cachedTabs];\n    },\n    getExcludeCachedTabs(): string[] {\n      return [...this.excludeCachedTabs];\n    },\n    getMenuList(): string[] {\n      return this.menuList;\n    },\n    getTabs(): TabDefinition[] {\n      const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab));\n      return [...this.affixTabs, ...normalTabs].filter(Boolean);\n    },\n    getCachedRoutes(): Map<string, RouteCached> {\n      return this.cachedRoutes;\n    },\n  },\n  persist: [\n    // tabs不需要保存在localStorage\n    {\n      pick: ['tabs', 'visitHistory'],\n      storage: sessionStorage,\n      serializer: {\n        serialize: JSON.stringify,\n        deserialize(value: string) {\n          const parsed = JSON.parse(value);\n          // Stack 类实例经 JSON 序列化后会变成普通对象 {dedup, items, maxSize}，\n          // 丢失所有方法和 getter，需要重新构建 Stack 实例\n          if (parsed.visitHistory && !(parsed.visitHistory instanceof Stack)) {\n            const raw = parsed.visitHistory;\n            const stack = createStack<string>(true, MAX_VISIT_HISTORY);\n            if (Array.isArray(raw.items)) {\n              stack.push(...raw.items);\n            }\n            parsed.visitHistory = stack;\n          }\n          return parsed;\n        },\n      },\n    },\n  ],\n  state: (): TabbarState => ({\n    visitHistory: createStack<string>(true, MAX_VISIT_HISTORY),\n    cachedRoutes: new Map<string, RouteCached>(),\n    cachedTabs: new Set(),\n    dragEndIndex: 0,\n    excludeCachedTabs: new Set(),\n    menuList: [\n      'close',\n      'affix',\n      'maximize',\n      'reload',\n      'open-in-new-window',\n      'close-left',\n      'close-right',\n      'close-other',\n      'close-all',\n    ],\n    renderRouteView: true,\n    tabs: [],\n    updateTime: Date.now(),\n  }),\n});\n\n// 解决热更新问题\nconst hot = import.meta.hot;\nif (hot) {\n  hot.accept(acceptHMRUpdate(useTabbarStore, hot));\n}\n\n/**\n * @zh_CN 克隆路由,防止路由被修改\n * @param route\n */\nfunction cloneTab(route: TabDefinition): TabDefinition {\n  if (!route) {\n    return route;\n  }\n  const { matched, meta, ...opt } = route;\n  return {\n    ...opt,\n    matched: (matched\n      ? matched.map((item) => ({\n          meta: item.meta,\n          name: item.name,\n          path: item.path,\n        }))\n      : undefined) as RouteRecordNormalized[],\n    meta: {\n      ...meta,\n      newTabTitle: meta.newTabTitle,\n    },\n  };\n}\n\n/**\n * @zh_CN 是否是固定标签页\n * @param tab\n */\nfunction isAffixTab(tab: TabDefinition) {\n  return tab?.meta?.affixTab ?? false;\n}\n\n/**\n * @zh_CN 是否显示标签\n * @param tab\n */\nfunction isTabShown(tab: TabDefinition) {\n  const matched = tab?.matched ?? [];\n  return !tab.meta.hideInTab && matched.every((item) => !item.meta.hideInTab);\n}\n\n/**\n * 从route获取tab页的key\n * @param tab\n */\nfunction getTabKey(tab: RouteLocationNormalized | RouteRecordNormalized) {\n  const {\n    fullPath,\n    path,\n    meta: { fullPathKey } = {},\n    query = {},\n  } = tab as RouteLocationNormalized;\n  // pageKey可能是数组（查询参数重复时可能出现）\n  const pageKey = Array.isArray(query.pageKey)\n    ? query.pageKey[0]\n    : query.pageKey;\n  let rawKey;\n  if (pageKey) {\n    rawKey = pageKey;\n  } else {\n    rawKey = fullPathKey === false ? path : (fullPath ?? path);\n  }\n  try {\n    return decodeURIComponent(rawKey);\n  } catch {\n    return rawKey;\n  }\n}\n\n/**\n * @zh_CN 是否开启访问历史记录\n */\nfunction isVisitHistory() {\n  return preferences.tabbar.visitHistory;\n}\n\n/**\n * 从tab获取tab页的key\n * 如果tab没有key,那么就从route获取key\n * @param tab\n */\nfunction getTabKeyFromTab(tab: TabDefinition): string {\n  return tab.key ?? getTabKey(tab);\n}\n\n/**\n * 比较两个tab是否相等\n * @param a\n * @param b\n */\nfunction equalTab(a: TabDefinition, b: TabDefinition) {\n  return getTabKeyFromTab(a) === getTabKeyFromTab(b);\n}\n\nfunction routeToTab(route: RouteRecordNormalized) {\n  return {\n    meta: route.meta,\n    name: route.name,\n    path: route.path,\n    key: getTabKey(route),\n  } as TabDefinition;\n}\n\nexport { getTabKey };\n"
  },
  {
    "path": "packages/stores/src/modules/timezone.ts",
    "content": "import { ref, unref } from 'vue';\n\nimport { DEFAULT_TIME_ZONE_OPTIONS } from '@vben-core/preferences';\nimport {\n  getCurrentTimezone,\n  setCurrentTimezone,\n} from '@vben-core/shared/utils';\n\nimport { acceptHMRUpdate, defineStore } from 'pinia';\n\ninterface TimezoneHandler {\n  getTimezone?: () => Promise<null | string | undefined>;\n  getTimezoneOptions?: () => Promise<\n    {\n      label: string;\n      value: string;\n    }[]\n  >;\n  setTimezone?: (timezone: string) => Promise<void>;\n}\n\n/**\n * 默认时区处理模块\n * 时区存储基于pinia存储插件\n */\nconst getDefaultTimezoneHandler = (): TimezoneHandler => {\n  return {\n    getTimezoneOptions: () => {\n      return Promise.resolve(\n        DEFAULT_TIME_ZONE_OPTIONS.map((item) => {\n          return {\n            label: item.label,\n            value: item.timezone,\n          };\n        }),\n      );\n    },\n  };\n};\n\n/**\n * 自定义时区处理模块\n */\nlet customTimezoneHandler: null | Partial<TimezoneHandler> = null;\nconst setTimezoneHandler = (handler: Partial<TimezoneHandler>) => {\n  customTimezoneHandler = handler;\n};\n\n/**\n * 获取时区处理模块\n */\nconst getTimezoneHandler = () => {\n  return {\n    ...getDefaultTimezoneHandler(),\n    ...customTimezoneHandler,\n  };\n};\n\n/**\n * timezone支持模块\n */\nconst useTimezoneStore = defineStore(\n  'core-timezone',\n  () => {\n    const timezoneRef = ref(getCurrentTimezone());\n\n    /**\n     * 初始化时区\n     * Initialize the timezone\n     */\n    async function initTimezone() {\n      const timezoneHandler = getTimezoneHandler();\n      const timezone = await timezoneHandler.getTimezone?.();\n      if (timezone) {\n        timezoneRef.value = timezone;\n      }\n      // 设置dayjs默认时区\n      setCurrentTimezone(unref(timezoneRef));\n    }\n\n    /**\n     * 设置时区\n     * Set the timezone\n     * @param timezone 时区字符串\n     */\n    async function setTimezone(timezone: string) {\n      const timezoneHandler = getTimezoneHandler();\n      await timezoneHandler.setTimezone?.(timezone);\n      timezoneRef.value = timezone;\n      // 设置dayjs默认时区\n      setCurrentTimezone(timezone);\n    }\n\n    /**\n     * 获取时区选项\n     * Get the timezone options\n     */\n    async function getTimezoneOptions() {\n      const timezoneHandler = getTimezoneHandler();\n      return (await timezoneHandler.getTimezoneOptions?.()) || [];\n    }\n\n    initTimezone().catch((error) => {\n      console.error('Failed to initialize timezone during store setup:', error);\n    });\n\n    function $reset() {\n      timezoneRef.value = getCurrentTimezone();\n    }\n\n    return {\n      timezone: timezoneRef,\n      setTimezone,\n      getTimezoneOptions,\n      $reset,\n    };\n  },\n  {\n    persist: {\n      // 持久化\n      pick: ['timezone'],\n    },\n  },\n);\n\nexport { setTimezoneHandler, useTimezoneStore };\n\n// 解决热更新问题\nconst hot = import.meta.hot;\nif (hot) {\n  hot.accept(acceptHMRUpdate(useTimezoneStore, hot));\n}\n"
  },
  {
    "path": "packages/stores/src/modules/user.test.ts",
    "content": "import { createPinia, setActivePinia } from 'pinia';\nimport { beforeEach, describe, expect, it } from 'vitest';\n\nimport { useUserStore } from './user';\n\ndescribe('useUserStore', () => {\n  beforeEach(() => {\n    setActivePinia(createPinia());\n  });\n\n  it('returns correct userInfo', () => {\n    const store = useUserStore();\n    const userInfo: any = { name: 'Jane Doe', roles: [{ value: 'user' }] };\n    store.setUserInfo(userInfo);\n    expect(store.userInfo).toEqual(userInfo);\n  });\n\n  // 测试重置用户信息时的行为\n  it('clears userInfo and userRoles when setting null userInfo', () => {\n    const store = useUserStore();\n    store.setUserInfo({\n      roles: [{ roleName: 'User', value: 'user' }],\n    } as any);\n    expect(store.userInfo).not.toBeNull();\n    expect(store.userRoles.length).toBeGreaterThan(0);\n\n    store.setUserInfo(null as any);\n    expect(store.userInfo).toBeNull();\n    expect(store.userRoles).toEqual([]);\n  });\n\n  // 测试在没有用户角色时返回空数组\n  it('returns an empty array for userRoles if not set', () => {\n    const store = useUserStore();\n    expect(store.userRoles).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "packages/stores/src/modules/user.ts",
    "content": "import { acceptHMRUpdate, defineStore } from 'pinia';\n\ninterface BasicUserInfo {\n  [key: string]: any;\n  /**\n   * 头像\n   */\n  avatar: string;\n  /**\n   * 用户昵称\n   */\n  realName: string;\n  /**\n   * 用户角色\n   */\n  roles?: string[];\n  /**\n   * 用户id\n   */\n  userId: string;\n  /**\n   * 用户名\n   */\n  username: string;\n}\n\ninterface AccessState {\n  /**\n   * 用户信息\n   */\n  userInfo: BasicUserInfo | null;\n  /**\n   * 用户角色\n   */\n  userRoles: string[];\n}\n\n/**\n * @zh_CN 用户信息相关\n */\nexport const useUserStore = defineStore('core-user', {\n  actions: {\n    setUserInfo(userInfo: BasicUserInfo | null) {\n      // 设置用户信息\n      this.userInfo = userInfo;\n      // 设置角色信息\n      const roles = userInfo?.roles ?? [];\n      this.setUserRoles(roles);\n    },\n    setUserRoles(roles: string[]) {\n      this.userRoles = roles;\n    },\n  },\n  state: (): AccessState => ({\n    userInfo: null,\n    userRoles: [],\n  }),\n});\n\n// 解决热更新问题\nconst hot = import.meta.hot;\nif (hot) {\n  hot.accept(acceptHMRUpdate(useUserStore, hot));\n}\n"
  },
  {
    "path": "packages/stores/src/setup.ts",
    "content": "import type { Pinia } from 'pinia';\n\nimport type { App } from 'vue';\n\nimport { createPinia } from 'pinia';\nimport SecureLS from 'secure-ls';\n\nlet pinia: Pinia;\n\ntype SecureLSStorage = {\n  get(key: string): any;\n  set(key: string, value: unknown): void;\n};\n\ntype SecureLSCtor = new (config?: {\n  encodingType?: string;\n  encryptionSecret?: string;\n  isCompression?: boolean;\n  metaKey?: string;\n}) => SecureLSStorage;\n\nconst secureLSModule = SecureLS as unknown as {\n  default?: SecureLSCtor;\n  SecureLS?: SecureLSCtor;\n};\n\nconst SecureLSConstructor =\n  secureLSModule.default ??\n  secureLSModule.SecureLS ??\n  (SecureLS as unknown as SecureLSCtor);\n\nexport interface InitStoreOptions {\n  /**\n   * @zh_CN 应用名,由于 @vben/stores 是公用的，后续可能有多个app，为了防止多个app缓存冲突，可在这里配置应用名,应用名将被用于持久化的前缀\n   */\n  namespace: string;\n}\n\n/**\n * @zh_CN 初始化pinia\n */\nexport async function initStores(app: App, options: InitStoreOptions) {\n  const { createPersistedState } = await import('pinia-plugin-persistedstate');\n  pinia = createPinia();\n  const { namespace } = options;\n  const ls = new SecureLSConstructor({\n    encodingType: 'aes',\n    encryptionSecret: import.meta.env.VITE_APP_STORE_SECURE_KEY,\n    isCompression: true,\n    metaKey: `${namespace}-secure-meta`,\n  });\n  pinia.use(\n    createPersistedState({\n      // key $appName-$store.id\n      key: (storeKey) => `${namespace}-${storeKey}`,\n      storage: import.meta.env.DEV\n        ? localStorage\n        : {\n            getItem(key) {\n              return ls.get(key);\n            },\n            setItem(key, value) {\n              ls.set(key, value);\n            },\n          },\n    }),\n  );\n  app.use(pinia);\n  return pinia;\n}\n\nexport function resetAllStores() {\n  if (!pinia) {\n    console.error('Pinia is not installed');\n    return;\n  }\n  const allStores = (pinia as any)._s;\n  for (const [_key, store] of allStores) {\n    store.$reset();\n  }\n}\n"
  },
  {
    "path": "packages/stores/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\", \"shim-pinia.d.ts\"]\n}\n"
  },
  {
    "path": "packages/styles/README.md",
    "content": "# @vben/styles\n\n用于多个 `app` 公用的样式文件，继承了 `@vben-core/design` 的所有能力。业务上有通用的样式文件可以放在这里。\n\n## 用法\n\n### 添加依赖\n\n```bash\n# 进入目标应用目录，例如 apps/xxxx-app\n# cd apps/xxxx-app\npnpm add @vben/styles\n```\n\n### 使用\n\n```ts\nimport '@vben/styles';\n```\n"
  },
  {
    "path": "packages/styles/package.json",
    "content": "{\n  \"name\": \"@vben/styles\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/styles\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    },\n    \"./antd\": {\n      \"default\": \"./src/antd/index.css\"\n    },\n    \"./antdv-next\": {\n      \"default\": \"./src/antdv-next/index.css\"\n    },\n    \"./ele\": {\n      \"default\": \"./src/ele/index.css\"\n    },\n    \"./naive\": {\n      \"default\": \"./src/naive/index.css\"\n    },\n    \"./global\": {\n      \"default\": \"./src/global/index.scss\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/design\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "packages/styles/src/antd/index.css",
    "content": "/* ant-design-vue 组件库的一些样式重置 */\n\n.ant-app {\n  width: 100%;\n  height: 100%;\n  overscroll-behavior: none;\n  color: inherit;\n}\n\n.ant-btn {\n  .anticon {\n    display: inline-flex;\n  }\n\n  /* * 修复按钮添加图标时的位置问题 */\n  > svg {\n    display: inline-block;\n  }\n\n  > svg + span {\n    margin-inline-start: 6px;\n  }\n}\n\n.ant-tag {\n  > svg {\n    display: inline-block;\n  }\n\n  > svg + span {\n    margin-inline-start: 4px;\n  }\n}\n\n.ant-message-notice-content,\n.ant-notification-notice {\n  &:is(.dark *) {\n    border-color: hsl(var(--border) / 60%);\n    border-width: 1px;\n  }\n}\n\n.form-valid-error {\n  /** select 选择器的样式 */\n\n  .ant-select:not(.valid-success) .ant-select-selector:not(.valid-success) {\n    border-color: hsl(var(--destructive)) !important;\n  }\n\n  .ant-select-focused .ant-select-selector {\n    box-shadow: 0 0 0 2px rgb(255 38 5 / 6%) !important;\n  }\n\n  /** 数字输入框样式 */\n  .ant-input-number-focused {\n    box-shadow: 0 0 0 2px rgb(255 38 5 / 6%);\n  }\n\n  /** 密码输入框样式 */\n  .ant-input-affix-wrapper:hover {\n    border-color: hsl(var(--destructive));\n    box-shadow: 0 0 0 2px rgb(255 38 5 / 6%);\n  }\n\n  .ant-input:not(.valid-success) {\n    border-color: hsl(var(--destructive)) !important;\n  }\n}\n\n/** 区间选择器下面来回切换时的样式 */\n.ant-app .form-valid-error .ant-picker-active-bar {\n  background-color: hsl(var(--destructive));\n}\n\n/** 时间选择器的样式 */\n.ant-app .form-valid-error .ant-picker-focused {\n  box-shadow: 0 0 0 2px rgb(255 38 5 / 6%);\n}\n"
  },
  {
    "path": "packages/styles/src/antdv-next/index.css",
    "content": "/* antdv-next 组件库的一些样式重置 */\n\n.ant-app {\n  width: 100%;\n  height: 100%;\n  overscroll-behavior: none;\n  color: inherit;\n}\n\n.ant-btn {\n  .anticon {\n    display: inline-flex;\n  }\n\n  /* * 修复按钮添加图标时的位置问题 */\n  > .ant-btn-icon {\n    svg {\n      display: inline-block;\n    }\n\n    svg + span {\n      margin-inline-start: 6px;\n    }\n  }\n}\n\n.ant-tag {\n  > svg {\n    display: inline-block;\n  }\n\n  > svg + span {\n    margin-inline-start: 4px;\n  }\n}\n\n.ant-message-notice-content,\n.ant-notification-notice {\n  &:is(.dark *) {\n    border-color: hsl(var(--border) / 60%);\n    border-width: 1px;\n  }\n}\n\n.form-valid-error {\n  /** select 选择器的样式 */\n\n  .ant-select:not(.valid-success) {\n    border-color: hsl(var(--destructive)) !important;\n  }\n\n  .ant-select-focused {\n    box-shadow: 0 0 0 2px rgb(255 38 5 / 6%) !important;\n  }\n\n  /** 数字输入框样式 */\n  .ant-input-number-focused {\n    box-shadow: 0 0 0 2px rgb(255 38 5 / 6%);\n  }\n\n  /** 密码输入框样式 */\n  .ant-input-affix-wrapper:hover {\n    border-color: hsl(var(--destructive));\n    box-shadow: 0 0 0 2px rgb(255 38 5 / 6%);\n  }\n\n  .ant-input:not(.valid-success) {\n    border-color: hsl(var(--destructive)) !important;\n  }\n}\n\n/** 区间选择器下面来回切换时的样式 */\n.ant-app .form-valid-error .ant-picker-active-bar {\n  background-color: hsl(var(--destructive));\n}\n\n/** 时间选择器的样式 */\n.ant-app .form-valid-error .ant-picker-focused {\n  box-shadow: 0 0 0 2px rgb(255 38 5 / 6%);\n}\n"
  },
  {
    "path": "packages/styles/src/ele/index.css",
    "content": ".el-card {\n  --el-card-border-radius: var(--radius) !important;\n}\n\n.form-valid-error {\n  /** select 选择器的样式 */\n  .el-select .el-select__wrapper {\n    box-shadow: 0 0 0 1px var(--el-color-danger) inset;\n  }\n\n  /** input 选择器的样式 */\n  .el-input .el-input__wrapper {\n    box-shadow: 0 0 0 1px var(--el-color-danger) inset;\n  }\n\n  /** radio和checkbox 选择器的样式 */\n  .el-radio .el-radio__inner,\n  .el-checkbox .el-checkbox__inner {\n    border: 1px solid var(--el-color-danger);\n  }\n\n  .el-checkbox-button .el-checkbox-button__inner,\n  .el-radio-button .el-radio-button__inner {\n    border: 1px solid var(--el-color-danger);\n  }\n\n  .el-checkbox-button:first-child .el-checkbox-button__inner,\n  .el-radio-button:first-child .el-radio-button__inner {\n    border-left: 1px solid var(--el-color-danger);\n  }\n\n  .el-checkbox-button:not(:first-child) .el-checkbox-button__inner,\n  .el-radio-button:not(:first-child) .el-radio-button__inner {\n    border-left: none;\n  }\n\n  .el-textarea .el-textarea__inner {\n    border: 1px solid var(--el-color-danger);\n  }\n}\n\nhtml .el-loading-mask {\n  z-index: 1000;\n}\n"
  },
  {
    "path": "packages/styles/src/global/index.scss",
    "content": "@use '@vben-core/design/bem' as *;\n"
  },
  {
    "path": "packages/styles/src/index.ts",
    "content": "import '@vben-core/design';\n"
  },
  {
    "path": "packages/styles/src/naive/index.css",
    "content": ".form-valid-error {\n  .n-base-selection__state-border,\n  .n-input__state-border,\n  .n-radio-group__splitor {\n    border: var(--n-border-error);\n  }\n\n  .n-radio-group .n-radio-button,\n  .n-radio-group .n-radio-group__splitor {\n    --n-button-border-color: rgb(255 56 96);\n  }\n\n  .n-radio__dot {\n    --n-box-shadow: inset 0 0 0 1px rgb(255 56 96);\n  }\n\n  .n-checkbox-box__border {\n    --n-border: 1px solid rgb(255 56 96);\n  }\n}\n"
  },
  {
    "path": "packages/styles/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/types/README.md",
    "content": "# @vben/types\n\n用于多个 `app` 公用的工具类型，继承了 `@vben-core/typings` 的所有能力。业务上有通用的类型定义可以放在这里。\n\n## 用法\n\n### 添加依赖\n\n```bash\n# 进入目标应用目录，例如 apps/xxxx-app\n# cd apps/xxxx-app\npnpm add @vben/types\n```\n\n### 使用\n\n```ts\n// 推荐加上 type\nimport type { SelectOption } from '@vben/types';\n```\n"
  },
  {
    "path": "packages/types/global.d.ts",
    "content": "import type { RouteMeta as IRouteMeta } from '@vben-core/typings';\n\nimport 'vue-router';\n\ndeclare module 'vue-router' {\n  // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n  interface RouteMeta extends IRouteMeta {}\n}\n\nexport interface VbenAdminProAppConfigRaw {\n  VITE_GLOB_API_URL: string;\n  VITE_GLOB_AUTH_DINGDING_CLIENT_ID: string;\n  VITE_GLOB_AUTH_DINGDING_CORP_ID: string;\n}\n\ninterface AuthConfig {\n  dingding?: {\n    clientId: string;\n    corpId: string;\n  };\n}\n\nexport interface ApplicationConfig {\n  apiURL: string;\n  auth: AuthConfig;\n}\n\ndeclare global {\n  interface Window {\n    _VBEN_ADMIN_PRO_APP_CONF_: VbenAdminProAppConfigRaw;\n  }\n}\n"
  },
  {
    "path": "packages/types/package.json",
    "content": "{\n  \"name\": \"@vben/types\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/types\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    },\n    \"./global\": {\n      \"types\": \"./global.d.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/typings\": \"workspace:*\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/types/src/index.ts",
    "content": "export type * from './user';\nexport type * from '@vben-core/typings';\n"
  },
  {
    "path": "packages/types/src/user.ts",
    "content": "import type { BasicUserInfo } from '@vben-core/typings';\n\n/** 用户信息 */\ninterface UserInfo extends BasicUserInfo {\n  /**\n   * 用户描述\n   */\n  desc: string;\n  /**\n   * 首页地址\n   */\n  homePath: string;\n\n  /**\n   * accessToken\n   */\n  token: string;\n}\n\nexport type { UserInfo };\n"
  },
  {
    "path": "packages/types/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/utils/README.md",
    "content": "# @vben/utils\n\n用于多个 `app` 公用的工具包，继承了 `@vben-core/shared/utils` 的所有能力。业务上有通用的工具函数可以放在这里。\n\n## 用法\n\n### 添加依赖\n\n```bash\n# 进入目标应用目录，例如 apps/xxxx-app\n# cd apps/xxxx-app\npnpm add @vben/utils\n```\n\n### 使用\n\n```ts\nimport { isString } from '@vben/utils';\n```\n"
  },
  {
    "path": "packages/utils/package.json",
    "content": "{\n  \"name\": \"@vben/utils\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://github.com/vbenjs/vue-vben-admin\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"packages/utils\"\n  },\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"sideEffects\": [\n    \"**/*.css\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"types\": \"./src/index.ts\",\n      \"default\": \"./src/index.ts\"\n    }\n  },\n  \"dependencies\": {\n    \"@vben-core/shared\": \"workspace:*\",\n    \"@vben-core/typings\": \"workspace:*\",\n    \"vue-router\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/utils/src/helpers/__tests__/find-menu-by-path.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\n\nimport { findMenuByPath, findRootMenuByPath } from '../find-menu-by-path';\n\n// 示例菜单数据\nconst menus: any[] = [\n  { path: '/', children: [] },\n  { path: '/about', children: [] },\n  {\n    path: '/contact',\n    children: [\n      { path: '/contact/email', children: [] },\n      { path: '/contact/phone', children: [] },\n    ],\n  },\n  {\n    path: '/services',\n    children: [\n      { path: '/services/design', children: [] },\n      {\n        path: '/services/development',\n        children: [{ path: '/services/development/web', children: [] }],\n      },\n    ],\n  },\n];\n\ndescribe('menu Finder Tests', () => {\n  it('finds a top-level menu', () => {\n    const menu = findMenuByPath(menus, '/about');\n    expect(menu).toBeDefined();\n    expect(menu?.path).toBe('/about');\n  });\n\n  it('finds a nested menu', () => {\n    const menu = findMenuByPath(menus, '/services/development/web');\n    expect(menu).toBeDefined();\n    expect(menu?.path).toBe('/services/development/web');\n  });\n\n  it('returns null for a non-existent path', () => {\n    const menu = findMenuByPath(menus, '/non-existent');\n    expect(menu).toBeNull();\n  });\n\n  it('handles empty menus list', () => {\n    const menu = findMenuByPath([], '/about');\n    expect(menu).toBeNull();\n  });\n\n  it('handles menu items without children', () => {\n    const menu = findMenuByPath(\n      [{ path: '/only', children: undefined }] as any[],\n      '/only',\n    );\n    expect(menu).toBeDefined();\n    expect(menu?.path).toBe('/only');\n  });\n\n  it('finds root menu by path', () => {\n    const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath(\n      menus,\n      '/services/development/web',\n    );\n\n    expect(findMenu).toBeDefined();\n    expect(rootMenu).toBeUndefined();\n    expect(rootMenuPath).toBeUndefined();\n    expect(findMenu?.path).toBe('/services/development/web');\n  });\n\n  it('returns null for undefined or empty path', () => {\n    const menuUndefinedPath = findMenuByPath(menus);\n    const menuEmptyPath = findMenuByPath(menus, '');\n    expect(menuUndefinedPath).toBeNull();\n    expect(menuEmptyPath).toBeNull();\n  });\n\n  it('checks for root menu when path does not exist', () => {\n    const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath(\n      menus,\n      '/non-existent',\n    );\n    expect(findMenu).toBeNull();\n    expect(rootMenu).toBeUndefined();\n    expect(rootMenuPath).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/helpers/__tests__/generate-menus.test.ts",
    "content": "import type { Router, RouteRecordRaw } from 'vue-router';\n\nimport { createRouter, createWebHistory } from 'vue-router';\n\nimport { describe, expect, it, vi } from 'vitest';\n\nimport { generateMenus } from '../generate-menus';\n\n// Nested route setup to test child inclusion and hideChildrenInMenu functionality\n\ndescribe('generateMenus', () => {\n  // 模拟路由数据\n  const mockRoutes = [\n    {\n      meta: { icon: 'home-icon', title: '首页' },\n      name: 'home',\n      path: '/home',\n    },\n    {\n      meta: { hideChildrenInMenu: true, icon: 'about-icon', title: '关于' },\n      name: 'about',\n      path: '/about',\n      children: [\n        {\n          path: 'team',\n          name: 'team',\n          meta: { icon: 'team-icon', title: '团队' },\n        },\n      ],\n    },\n  ] as RouteRecordRaw[];\n\n  // 模拟 Vue 路由器实例\n  const mockRouter = {\n    getRoutes: vi.fn(() => [\n      { name: 'home', path: '/home' },\n      { name: 'about', path: '/about' },\n      { name: 'team', path: '/about/team' },\n    ]),\n  };\n\n  it('the correct menu list should be generated according to the route', async () => {\n    const expectedMenus = [\n      {\n        badge: undefined,\n        badgeType: undefined,\n        badgeVariants: undefined,\n        icon: 'home-icon',\n        name: '首页',\n        order: undefined,\n        parent: undefined,\n        parents: undefined,\n        path: '/home',\n        show: true,\n        children: [],\n      },\n      {\n        badge: undefined,\n        badgeType: undefined,\n        badgeVariants: undefined,\n        icon: 'about-icon',\n        name: '关于',\n        order: undefined,\n        parent: undefined,\n        parents: undefined,\n        path: '/about',\n        show: true,\n        children: [],\n      },\n    ];\n\n    const menus = generateMenus(mockRoutes, mockRouter as any);\n    expect(menus).toEqual(expectedMenus);\n  });\n\n  it('includes additional meta properties in menu items', async () => {\n    const mockRoutesWithMeta = [\n      {\n        meta: { icon: 'user-icon', order: 1, title: 'Profile' },\n        name: 'profile',\n        path: '/profile',\n      },\n    ] as RouteRecordRaw[];\n\n    const menus = generateMenus(mockRoutesWithMeta, mockRouter as any);\n    expect(menus).toEqual([\n      {\n        badge: undefined,\n        badgeType: undefined,\n        badgeVariants: undefined,\n        icon: 'user-icon',\n        name: 'Profile',\n        order: 1,\n        parent: undefined,\n        parents: undefined,\n        path: '/profile',\n        show: true,\n        children: [],\n      },\n    ]);\n  });\n\n  it('handles dynamic route parameters correctly', async () => {\n    const mockRoutesWithParams = [\n      {\n        meta: { icon: 'details-icon', title: 'User Details' },\n        name: 'userDetails',\n        path: '/users/:userId',\n      },\n    ] as RouteRecordRaw[];\n\n    const menus = generateMenus(mockRoutesWithParams, mockRouter as any);\n    expect(menus).toEqual([\n      {\n        badge: undefined,\n        badgeType: undefined,\n        badgeVariants: undefined,\n        icon: 'details-icon',\n        name: 'User Details',\n        order: undefined,\n        parent: undefined,\n        parents: undefined,\n        path: '/users/:userId',\n        show: true,\n        children: [],\n      },\n    ]);\n  });\n\n  it('processes routes with redirects correctly', async () => {\n    const mockRoutesWithRedirect = [\n      {\n        name: 'redirectedRoute',\n        path: '/old-path',\n        redirect: '/new-path',\n      },\n      {\n        meta: { icon: 'path-icon', title: 'New Path' },\n        name: 'newPath',\n        path: '/new-path',\n      },\n    ] as RouteRecordRaw[];\n\n    const menus = generateMenus(mockRoutesWithRedirect, mockRouter as any);\n    expect(menus).toEqual([\n      // Assuming your generateMenus function excludes redirect routes from the menu\n      {\n        badge: undefined,\n        badgeType: undefined,\n        badgeVariants: undefined,\n        icon: undefined,\n        name: 'redirectedRoute',\n        order: undefined,\n        parent: undefined,\n        parents: undefined,\n        path: '/old-path',\n        show: true,\n        children: [],\n      },\n      {\n        badge: undefined,\n        badgeType: undefined,\n        badgeVariants: undefined,\n        icon: 'path-icon',\n        name: 'New Path',\n        order: undefined,\n        parent: undefined,\n        parents: undefined,\n        path: '/new-path',\n        show: true,\n        children: [],\n      },\n    ]);\n  });\n\n  const routes: any = [\n    {\n      meta: { order: 2, title: 'Home' },\n      name: 'home',\n      path: '/',\n    },\n    {\n      meta: { order: 1, title: 'About' },\n      name: 'about',\n      path: '/about',\n    },\n  ];\n\n  const router: Router = createRouter({\n    history: createWebHistory(),\n    routes,\n  });\n\n  it('should generate menu list with correct order', async () => {\n    const menus = generateMenus(routes, router);\n    const expectedMenus = [\n      {\n        badge: undefined,\n        badgeType: undefined,\n        badgeVariants: undefined,\n        icon: undefined,\n        name: 'About',\n        order: 1,\n        parent: undefined,\n        parents: undefined,\n        path: '/about',\n        show: true,\n        children: [],\n      },\n      {\n        badge: undefined,\n        badgeType: undefined,\n        badgeVariants: undefined,\n        icon: undefined,\n        name: 'Home',\n        order: 2,\n        parent: undefined,\n        parents: undefined,\n        path: '/',\n        show: true,\n        children: [],\n      },\n    ];\n\n    expect(menus).toEqual(expectedMenus);\n  });\n\n  it('should handle empty routes', async () => {\n    const emptyRoutes: any[] = [];\n    const menus = generateMenus(emptyRoutes, router);\n    expect(menus).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/helpers/__tests__/generate-routes-frontend.test.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { describe, expect, it } from 'vitest';\n\nimport {\n  generateRoutesByFrontend,\n  hasAuthority,\n} from '../generate-routes-frontend';\n\n// Mock 路由数据\nconst mockRoutes = [\n  {\n    meta: {\n      authority: ['admin', 'user'],\n      hideInMenu: false,\n    },\n    path: '/dashboard',\n    children: [\n      {\n        path: '/dashboard/overview',\n        meta: { authority: ['admin'], hideInMenu: false },\n      },\n      {\n        path: '/dashboard/stats',\n        meta: { authority: ['user'], hideInMenu: true },\n      },\n    ],\n  },\n  {\n    meta: { authority: ['admin'], hideInMenu: false },\n    path: '/settings',\n  },\n  {\n    meta: { hideInMenu: false },\n    path: '/profile',\n  },\n] as RouteRecordRaw[];\n\ndescribe('hasAuthority', () => {\n  it('should return true if there is no authority defined', () => {\n    expect(hasAuthority(mockRoutes[2], ['admin'])).toBe(true);\n  });\n\n  it('should return true if the user has the required authority', () => {\n    expect(hasAuthority(mockRoutes[0], ['admin'])).toBe(true);\n  });\n\n  it('should return false if the user does not have the required authority', () => {\n    expect(hasAuthority(mockRoutes[1], ['user'])).toBe(false);\n  });\n});\n\ndescribe('generateRoutesByFrontend', () => {\n  it('should handle routes without children', async () => {\n    const generatedRoutes = await generateRoutesByFrontend(mockRoutes, [\n      'user',\n    ]);\n    expect(generatedRoutes).toEqual(\n      expect.arrayContaining([\n        expect.objectContaining({\n          path: '/profile', // This route has no children and should be included\n        }),\n      ]),\n    );\n  });\n\n  it('should handle empty roles array', async () => {\n    const generatedRoutes = await generateRoutesByFrontend(mockRoutes, []);\n    expect(generatedRoutes).toEqual(\n      expect.arrayContaining([\n        // Only routes without authority should be included\n        expect.objectContaining({\n          path: '/profile',\n        }),\n      ]),\n    );\n    expect(generatedRoutes).not.toEqual(\n      expect.arrayContaining([\n        expect.objectContaining({\n          path: '/dashboard',\n        }),\n        expect.objectContaining({\n          path: '/settings',\n        }),\n      ]),\n    );\n  });\n\n  it('should handle missing meta fields', async () => {\n    const routesWithMissingMeta = [\n      { path: '/path1' }, // No meta\n      { meta: {}, path: '/path2' }, // Empty meta\n      { meta: { authority: ['admin'] }, path: '/path3' }, // Only authority\n    ];\n    const generatedRoutes = await generateRoutesByFrontend(\n      routesWithMissingMeta as RouteRecordRaw[],\n      ['admin'],\n    );\n    expect(generatedRoutes).toEqual([\n      { path: '/path1' },\n      { meta: {}, path: '/path2' },\n      { meta: { authority: ['admin'] }, path: '/path3' },\n    ]);\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/helpers/__tests__/merge-route-modules.test.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport type { RouteModuleType } from '../merge-route-modules';\n\nimport { describe, expect, it } from 'vitest';\n\nimport { mergeRouteModules } from '../merge-route-modules';\n\ndescribe('mergeRouteModules', () => {\n  it('should merge route modules correctly', () => {\n    const routeModules: Record<string, RouteModuleType> = {\n      './dynamic-routes/about.ts': {\n        default: [\n          {\n            component: () => Promise.resolve({ template: '<div>About</div>' }),\n            name: 'About',\n            path: '/about',\n          },\n        ],\n      },\n      './dynamic-routes/home.ts': {\n        default: [\n          {\n            component: () => Promise.resolve({ template: '<div>Home</div>' }),\n            name: 'Home',\n            path: '/',\n          },\n        ],\n      },\n    };\n\n    const expectedRoutes: RouteRecordRaw[] = [\n      {\n        component: expect.any(Function),\n        name: 'About',\n        path: '/about',\n      },\n      {\n        component: expect.any(Function),\n        name: 'Home',\n        path: '/',\n      },\n    ];\n\n    const mergedRoutes = mergeRouteModules(routeModules);\n    expect(mergedRoutes).toEqual(expectedRoutes);\n  });\n\n  it('should handle empty modules', () => {\n    const routeModules: Record<string, RouteModuleType> = {};\n    const expectedRoutes: RouteRecordRaw[] = [];\n\n    const mergedRoutes = mergeRouteModules(routeModules);\n    expect(mergedRoutes).toEqual(expectedRoutes);\n  });\n\n  it('should handle modules with no default export', () => {\n    const routeModules: Record<string, RouteModuleType> = {\n      './dynamic-routes/empty.ts': {\n        default: [],\n      },\n    };\n    const expectedRoutes: RouteRecordRaw[] = [];\n\n    const mergedRoutes = mergeRouteModules(routeModules);\n    expect(mergedRoutes).toEqual(expectedRoutes);\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/helpers/find-menu-by-path.ts",
    "content": "import type { MenuRecordRaw } from '@vben-core/typings';\n\nfunction findMenuByPath(\n  list: MenuRecordRaw[],\n  path?: string,\n): MenuRecordRaw | null {\n  for (const menu of list) {\n    if (menu.path === path) {\n      return menu;\n    }\n    const findMenu = menu.children && findMenuByPath(menu.children, path);\n    if (findMenu) {\n      return findMenu;\n    }\n  }\n  return null;\n}\n\n/**\n * 查找根菜单\n * @param menus\n * @param path\n */\nfunction findRootMenuByPath(menus: MenuRecordRaw[], path?: string, level = 0) {\n  const findMenu = findMenuByPath(menus, path);\n  const rootMenuPath = findMenu?.parents?.[level];\n  const rootMenu = rootMenuPath\n    ? menus.find((item) => item.path === rootMenuPath)\n    : undefined;\n  return {\n    findMenu,\n    rootMenu,\n    rootMenuPath,\n  };\n}\n\nexport { findMenuByPath, findRootMenuByPath };\n"
  },
  {
    "path": "packages/utils/src/helpers/generate-menus.ts",
    "content": "import type { Router, RouteRecordRaw } from 'vue-router';\n\nimport type {\n  ExRouteRecordRaw,\n  MenuRecordRaw,\n  RouteMeta,\n} from '@vben-core/typings';\n\nimport { filterTree, mapTree, sortTree } from '@vben-core/shared/utils';\n\n/**\n * 根据 routes 生成菜单列表\n * @param routes - 路由配置列表\n * @param router - Vue Router 实例\n * @returns 生成的菜单列表\n */\nfunction generateMenus(\n  routes: RouteRecordRaw[],\n  router: Router,\n): MenuRecordRaw[] {\n  // 将路由列表转换为一个以 name 为键的对象映射\n  const finalRoutesMap: { [key: string]: string } = Object.fromEntries(\n    router.getRoutes().map(({ name, path }) => [name, path]),\n  );\n\n  let menus = mapTree<ExRouteRecordRaw, MenuRecordRaw>(routes, (route) => {\n    // 获取最终的路由路径\n    const path = finalRoutesMap[route.name as string] ?? route.path ?? '';\n\n    const {\n      meta = {} as RouteMeta,\n      name: routeName,\n      redirect,\n      children = [],\n    } = route;\n    const {\n      activeIcon,\n      badge,\n      badgeType,\n      badgeVariants,\n      hideChildrenInMenu = false,\n      icon,\n      link,\n      order,\n      title = '',\n      query,\n    } = meta;\n\n    // 确保菜单名称不为空\n    const name = (title || routeName || '') as string;\n\n    // 处理子菜单\n    const resultChildren = hideChildrenInMenu\n      ? []\n      : ((children as MenuRecordRaw[]) ?? []);\n\n    // 设置子菜单的父子关系\n    if (resultChildren.length > 0) {\n      resultChildren.forEach((child) => {\n        child.parents = [...(route.parents ?? []), path];\n        child.parent = path;\n      });\n    }\n\n    // 确定最终路径\n    const resultPath = hideChildrenInMenu ? redirect || path : link || path;\n\n    return {\n      activeIcon,\n      badge,\n      badgeType,\n      badgeVariants,\n      icon,\n      name,\n      query,\n      order,\n      parent: route.parent,\n      parents: route.parents,\n      path: resultPath,\n      show: !meta.hideInMenu,\n      children: resultChildren,\n    };\n  });\n\n  // 对菜单进行排序，避免order=0时被替换成999的问题\n  menus = sortTree(menus, (a, b) => (a?.order ?? 999) - (b?.order ?? 999));\n\n  // 过滤掉隐藏的菜单项\n  return filterTree(menus, (menu) => !!menu.show);\n}\n\nexport { generateMenus };\n"
  },
  {
    "path": "packages/utils/src/helpers/generate-routes-backend.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport type {\n  ComponentRecordType,\n  GenerateMenuAndRoutesOptions,\n  RouteRecordStringComponent,\n} from '@vben-core/typings';\n\nimport { mapTree } from '@vben-core/shared/utils';\n\n/**\n * 判断路由是否在菜单中显示但访问时展示 403（让用户知悉功能并申请权限）\n */\nfunction menuHasVisibleWithForbidden(route: RouteRecordRaw): boolean {\n  return !!route.meta?.menuVisibleWithForbidden;\n}\n\n/**\n * 动态生成路由 - 后端方式\n * 对 meta.menuVisibleWithForbidden 为 true 的项直接替换为 403 组件，让用户知悉功能并申请权限。\n */\nasync function generateRoutesByBackend(\n  options: GenerateMenuAndRoutesOptions,\n): Promise<RouteRecordRaw[]> {\n  const {\n    fetchMenuListAsync,\n    layoutMap = {},\n    pageMap = {},\n    forbiddenComponent,\n  } = options;\n\n  try {\n    const menuRoutes = await fetchMenuListAsync?.();\n    if (!menuRoutes) {\n      return [];\n    }\n\n    const normalizePageMap: ComponentRecordType = {};\n\n    for (const [key, value] of Object.entries(pageMap)) {\n      normalizePageMap[normalizeViewPath(key)] = value;\n    }\n\n    let routes = convertRoutes(menuRoutes, layoutMap, normalizePageMap);\n\n    if (forbiddenComponent) {\n      routes = mapTree(routes, (route) => {\n        if (menuHasVisibleWithForbidden(route)) {\n          route.component = forbiddenComponent;\n        }\n        return route;\n      });\n    }\n\n    return routes;\n  } catch (error) {\n    console.error(error);\n    throw error;\n  }\n}\n\nfunction convertRoutes(\n  routes: RouteRecordStringComponent[],\n  layoutMap: ComponentRecordType,\n  pageMap: ComponentRecordType,\n): RouteRecordRaw[] {\n  return mapTree(routes, (node) => {\n    const route = node as unknown as RouteRecordRaw;\n    const { component, name } = node;\n\n    if (!name) {\n      console.error('route name is required', route);\n    }\n\n    // layout转换\n    if (component && layoutMap[component]) {\n      route.component = layoutMap[component];\n      // 页面组件转换\n    } else if (component) {\n      const normalizePath = normalizeViewPath(component);\n      const pageKey = normalizePath.endsWith('.vue')\n        ? normalizePath\n        : `${normalizePath}.vue`;\n      if (pageMap[pageKey]) {\n        route.component = pageMap[pageKey];\n      } else {\n        console.error(`route component is invalid: ${pageKey}`, route);\n        route.component = pageMap['/_core/fallback/not-found.vue'];\n      }\n    }\n\n    return route;\n  });\n}\n\nfunction normalizeViewPath(path: string): string {\n  // 去除相对路径前缀\n  const normalizedPath = path.replace(/^(\\.\\/|\\.\\.\\/)+/, '');\n\n  // 确保路径以 '/' 开头\n  const viewPath = normalizedPath.startsWith('/')\n    ? normalizedPath\n    : `/${normalizedPath}`;\n\n  // 这里耦合了vben-admin的目录结构\n  return viewPath.replace(/^\\/views/, '');\n}\nexport { generateRoutesByBackend };\n"
  },
  {
    "path": "packages/utils/src/helpers/generate-routes-frontend.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { filterTree, mapTree } from '@vben-core/shared/utils';\n\n/**\n * 动态生成路由 - 前端方式\n */\nasync function generateRoutesByFrontend(\n  routes: RouteRecordRaw[],\n  roles: string[],\n  forbiddenComponent?: RouteRecordRaw['component'],\n): Promise<RouteRecordRaw[]> {\n  // 根据角色标识过滤路由表,判断当前用户是否拥有指定权限\n  const finalRoutes = filterTree(routes, (route) => {\n    return hasAuthority(route, roles);\n  });\n\n  if (!forbiddenComponent) {\n    return finalRoutes;\n  }\n\n  // 如果有禁止访问的页面，将禁止访问的页面替换为403页面\n  return mapTree(finalRoutes, (route) => {\n    if (menuHasVisibleWithForbidden(route)) {\n      route.component = forbiddenComponent;\n    }\n    return route;\n  });\n}\n\n/**\n * 判断路由是否有权限访问\n * @param route\n * @param access\n */\nfunction hasAuthority(route: RouteRecordRaw, access: string[]) {\n  const authority = route.meta?.authority;\n  if (!authority) {\n    return true;\n  }\n  const canAccess = access.some((value) => authority.includes(value));\n\n  return canAccess || (!canAccess && menuHasVisibleWithForbidden(route));\n}\n\n/**\n * 判断路由是否在菜单中显示，但是访问会被重定向到403\n * @param route\n */\nfunction menuHasVisibleWithForbidden(route: RouteRecordRaw) {\n  return (\n    !!route.meta?.authority &&\n    Reflect.has(route.meta || {}, 'menuVisibleWithForbidden') &&\n    !!route.meta?.menuVisibleWithForbidden\n  );\n}\n\nexport { generateRoutesByFrontend, hasAuthority };\n"
  },
  {
    "path": "packages/utils/src/helpers/get-popup-container.ts",
    "content": "/**\n * If the node is holding inside a form, return the form element,\n * otherwise return the parent node of the given element or\n * the document body if the element is not provided.\n */\nexport function getPopupContainer(node?: HTMLElement): HTMLElement {\n  return (\n    node?.closest('form') ?? (node?.parentNode as HTMLElement) ?? document.body\n  );\n}\n"
  },
  {
    "path": "packages/utils/src/helpers/index.ts",
    "content": "export * from './find-menu-by-path';\nexport * from './generate-menus';\nexport * from './generate-routes-backend';\nexport * from './generate-routes-frontend';\nexport * from './get-popup-container';\nexport * from './merge-route-modules';\nexport * from './reset-routes';\nexport * from './unmount-global-loading';\n"
  },
  {
    "path": "packages/utils/src/helpers/merge-route-modules.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\n// 定义模块类型\ninterface RouteModuleType {\n  default: RouteRecordRaw[];\n}\n\n/**\n * 合并动态路由模块的默认导出\n * @param routeModules 动态导入的路由模块对象\n * @returns 合并后的路由配置数组\n */\nfunction mergeRouteModules(\n  routeModules: Record<string, unknown>,\n): RouteRecordRaw[] {\n  const mergedRoutes: RouteRecordRaw[] = [];\n\n  for (const routeModule of Object.values(routeModules)) {\n    const moduleRoutes = (routeModule as RouteModuleType)?.default ?? [];\n    mergedRoutes.push(...moduleRoutes);\n  }\n\n  return mergedRoutes;\n}\n\nexport { mergeRouteModules };\n\nexport type { RouteModuleType };\n"
  },
  {
    "path": "packages/utils/src/helpers/reset-routes.ts",
    "content": "import type { Router, RouteRecordName, RouteRecordRaw } from 'vue-router';\n\nimport { traverseTreeValues } from '@vben-core/shared/utils';\n\n/**\n * @zh_CN 重置所有路由，如有指定白名单除外\n */\nexport function resetStaticRoutes(router: Router, routes: RouteRecordRaw[]) {\n  // 获取静态路由所有节点包含子节点的 name，并排除不存在 name 字段的路由\n  const staticRouteNames = traverseTreeValues<\n    RouteRecordRaw,\n    RouteRecordName | undefined\n  >(routes, (route) => {\n    // 这些路由需要指定 name，防止在路由重置时，不能删除没有指定 name 的路由\n    if (!route.name) {\n      console.warn(\n        `The route with the path ${route.path} needs to have the field name specified.`,\n      );\n    }\n    return route.name;\n  });\n\n  const { getRoutes, hasRoute, removeRoute } = router;\n  const allRoutes = getRoutes();\n  allRoutes.forEach(({ name }) => {\n    // 存在于路由表且非白名单才需要删除\n    if (name && !staticRouteNames.includes(name) && hasRoute(name)) {\n      removeRoute(name);\n    }\n  });\n}\n"
  },
  {
    "path": "packages/utils/src/helpers/unmount-global-loading.ts",
    "content": "/**\n * 移除并销毁loading\n * 放在这里是而不是放在 index.html 的app标签内，是因为这样比较不会生硬，渲染过快可能会有闪烁\n * 通过先添加css动画隐藏，在动画结束后在移除loading节点来改善体验\n * 不好的地方是会增加一些代码量\n * 自定义loading可以见：https://doc.vben.pro/guide/in-depth/loading.html\n */\nexport function unmountGlobalLoading() {\n  // 查找全局 loading 元素\n  const loadingElement = document.querySelector('#__app-loading__');\n\n  if (loadingElement) {\n    // 添加隐藏类，触发过渡动画\n    loadingElement.classList.add('hidden');\n\n    // 查找所有需要移除的注入 loading 元素\n    const injectLoadingElements = document.querySelectorAll(\n      '[data-app-loading^=\"inject\"]',\n    );\n\n    // 当过渡动画结束时，移除 loading 元素和所有注入的 loading 元素\n    loadingElement.addEventListener(\n      'transitionend',\n      () => {\n        loadingElement.remove(); // 移除 loading 元素\n        injectLoadingElements.forEach((el) => el.remove()); // 移除所有注入的 loading 元素\n      },\n      { once: true },\n    ); // 确保事件只触发一次\n  }\n}\n"
  },
  {
    "path": "packages/utils/src/index.ts",
    "content": "export * from './helpers';\nexport * from '@vben-core/shared/cache';\nexport * from '@vben-core/shared/color';\nexport * from '@vben-core/shared/utils';\n"
  },
  {
    "path": "packages/utils/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/library.json\",\n  \"compilerOptions\": {\n    \"types\": [\"@vben-core/typings/vue-router\"]\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "playground/__tests__/e2e/auth-login.spec.ts",
    "content": "import { expect, test } from '@playwright/test';\n\nimport { authLogin } from './common/auth';\n\ntest.beforeEach(async ({ page }) => {\n  await page.goto('/');\n});\n\ntest.describe('Auth Login Page Tests', () => {\n  test('check title and page elements', async ({ page }) => {\n    // 获取页面标题并断言标题包含 'Vben Admin'\n    const title = await page.title();\n    expect(title).toContain('Vben Admin');\n  });\n\n  // 测试用例: 成功登录\n  test('should successfully login with valid credentials', async ({ page }) => {\n    await authLogin(page);\n  });\n});\n"
  },
  {
    "path": "playground/__tests__/e2e/common/auth.ts",
    "content": "import type { Page } from '@playwright/test';\n\nimport { expect } from '@playwright/test';\n\nexport async function authLogin(page: Page) {\n  // 确保登录表单正常\n  const usernameInput = await page.locator(`input[name='username']`);\n  await expect(usernameInput).toBeVisible();\n\n  const passwordInput = await page.locator(`input[name='password']`);\n  await expect(passwordInput).toBeVisible();\n\n  const sliderCaptcha = await page.locator(`div[name='captcha']`);\n  const sliderCaptchaAction = await page.locator(`div[name='captcha-action']`);\n  await expect(sliderCaptcha).toBeVisible();\n  await expect(sliderCaptchaAction).toBeVisible();\n\n  // 拖动验证码滑块\n  // 获取拖动按钮的位置\n  const sliderCaptchaBox = await sliderCaptcha.boundingBox();\n  if (!sliderCaptchaBox) throw new Error('滑块未找到');\n\n  const actionBoundingBox = await sliderCaptchaAction.boundingBox();\n  if (!actionBoundingBox) throw new Error('要拖动的按钮未找到');\n\n  // 计算起始位置和目标位置\n  const startX = actionBoundingBox.x + actionBoundingBox.width / 2; // div 中心的 x 坐标\n  const startY = actionBoundingBox.y + actionBoundingBox.height / 2; // div 中心的 y 坐标\n\n  const targetX = startX + sliderCaptchaBox.width + actionBoundingBox.width; // 向右拖动容器的宽度\n  const targetY = startY; // y 坐标保持不变\n\n  // 模拟鼠标拖动\n  await page.mouse.move(startX, startY); // 移动到 action 的中心\n  await page.mouse.down(); // 按下鼠标\n  await page.mouse.move(targetX, targetY, { steps: 20 }); // 拖动到目标位置\n  await page.mouse.up(); // 松开鼠标\n\n  // 在拖动后进行断言，检查action是否在预期位置,\n  const newActionBoundingBox = await sliderCaptchaAction.boundingBox();\n  expect(newActionBoundingBox?.x).toBeGreaterThan(actionBoundingBox.x);\n\n  // 到这里已经校验成功，点击进行登录\n  await page.waitForTimeout(300);\n  await page.getByRole('button', { name: 'login' }).click();\n}\n"
  },
  {
    "path": "playground/index.html",
    "content": "<!doctype html>\n<html lang=\"zh\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n    <meta name=\"renderer\" content=\"webkit\" />\n    <meta name=\"description\" content=\"A Modern Back-end Management System\" />\n    <meta name=\"keywords\" content=\"Vben Admin Vue3 Vite\" />\n    <meta name=\"author\" content=\"Vben\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0\"\n    />\n    <!-- 由 vite 注入 VITE_APP_TITLE 变量，在 .env 文件内配置 -->\n    <title><%= VITE_APP_TITLE %></title>\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <script>\n      // 生产环境下注入百度统计\n      if (window._VBEN_ADMIN_PRO_APP_CONF_) {\n        var _hmt = _hmt || [];\n        (function () {\n          var hm = document.createElement('script');\n          hm.src =\n            'https://hm.baidu.com/hm.js?d20a01273820422b6aa2ee41b6c9414d';\n          var s = document.getElementsByTagName('script')[0];\n          s.parentNode.insertBefore(hm, s);\n        })();\n      }\n    </script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "playground/package.json",
    "content": "{\n  \"name\": \"@vben/playground\",\n  \"version\": \"5.7.0\",\n  \"homepage\": \"https://vben.pro\",\n  \"bugs\": \"https://github.com/vbenjs/vue-vben-admin/issues\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vbenjs/vue-vben-admin.git\",\n    \"directory\": \"playground\"\n  },\n  \"license\": \"MIT\",\n  \"author\": {\n    \"name\": \"vben\",\n    \"email\": \"ann.vben@gmail.com\",\n    \"url\": \"https://github.com/anncwb\"\n  },\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"pnpm vite build --mode production\",\n    \"build:analyze\": \"pnpm vite build --mode analyze\",\n    \"dev\": \"pnpm vite --mode development\",\n    \"preview\": \"vite preview\",\n    \"typecheck\": \"vue-tsc --noEmit --skipLibCheck\",\n    \"test:e2e\": \"playwright test\",\n    \"test:e2e-ui\": \"playwright test --ui\",\n    \"test:e2e-codegen\": \"playwright codegen\"\n  },\n  \"imports\": {\n    \"#/*\": \"./src/*\"\n  },\n  \"dependencies\": {\n    \"@tanstack/vue-query\": \"catalog:\",\n    \"@vben-core/design\": \"workspace:*\",\n    \"@vben-core/menu-ui\": \"workspace:*\",\n    \"@vben-core/shadcn-ui\": \"workspace:*\",\n    \"@vben/access\": \"workspace:*\",\n    \"@vben/common-ui\": \"workspace:*\",\n    \"@vben/constants\": \"workspace:*\",\n    \"@vben/hooks\": \"workspace:*\",\n    \"@vben/icons\": \"workspace:*\",\n    \"@vben/layouts\": \"workspace:*\",\n    \"@vben/locales\": \"workspace:*\",\n    \"@vben/plugins\": \"workspace:*\",\n    \"@vben/preferences\": \"workspace:*\",\n    \"@vben/request\": \"workspace:*\",\n    \"@vben/stores\": \"workspace:*\",\n    \"@vben/styles\": \"workspace:*\",\n    \"@vben/types\": \"workspace:*\",\n    \"@vben/utils\": \"workspace:*\",\n    \"@vueuse/core\": \"catalog:\",\n    \"ant-design-vue\": \"catalog:\",\n    \"dayjs\": \"catalog:\",\n    \"json-bigint\": \"catalog:\",\n    \"pinia\": \"catalog:\",\n    \"vue\": \"catalog:\",\n    \"vue-router\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@types/json-bigint\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "playground/playwright.config.ts",
    "content": "import type { PlaywrightTestConfig } from '@playwright/test';\n\nimport { devices } from '@playwright/test';\n\n/**\n * Read environment variables from file.\n * https://github.com/motdotla/dotenv\n */\n// require('dotenv').config();\n\n/**\n * See https://playwright.dev/docs/test-configuration.\n */\nconst config: PlaywrightTestConfig = {\n  expect: {\n    /**\n     * Maximum time expect() should wait for the condition to be met.\n     * For example in `await expect(locator).toHaveText();`\n     */\n    timeout: 5000,\n  },\n  /* Fail the build on CI if you accidentally left test.only in the source code. */\n  forbidOnly: !!process.env.CI,\n  /* Folder for test artifacts such as screenshots, videos, traces, etc. */\n  outputDir: 'node_modules/.e2e/test-results/',\n  /* Configure projects for major browsers */\n  projects: [\n    {\n      name: 'chromium',\n      use: {\n        ...devices['Desktop Chrome'],\n      },\n    },\n    // {\n    //   name: 'firefox',\n    //   use: {\n    //     ...devices['Desktop Firefox'],\n    //   },\n    // },\n    // {\n    //   name: 'webkit',\n    //   use: {\n    //     ...devices['Desktop Safari'],\n    //   },\n    // },\n\n    /* Test against mobile viewports. */\n    // {\n    //   name: 'Mobile Chrome',\n    //   use: {\n    //     ...devices['Pixel 5'],\n    //   },\n    // },\n    // {\n    //   name: 'Mobile Safari',\n    //   use: {\n    //     ...devices['iPhone 12'],\n    //   },\n    // },\n\n    /* Test against branded browsers. */\n    // {\n    //   name: 'Microsoft Edge',\n    //   use: {\n    //     channel: 'msedge',\n    //   },\n    // },\n    // {\n    //   name: 'Google Chrome',\n    //   use: {\n    //     channel: 'chrome',\n    //   },\n    // },\n  ],\n  /* Reporter to use. See https://playwright.dev/docs/test-reporters */\n  reporter: [\n    ['list'],\n    ['html', { outputFolder: 'node_modules/.e2e/test-results' }],\n  ],\n  /* Retry on CI only */\n  retries: process.env.CI ? 2 : 0,\n  testDir: './__tests__/e2e',\n  /* Maximum time one test can run for. */\n  timeout: 30 * 1000,\n  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */\n  use: {\n    /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */\n    actionTimeout: 0,\n    /* Base URL to use in actions like `await page.goto('/')`. */\n    baseURL: 'http://localhost:5555',\n    /* Only on CI systems run the tests headless */\n    headless: !!process.env.CI,\n    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */\n    trace: 'retain-on-failure',\n  },\n\n  /* Run your local dev server before starting the tests */\n  webServer: {\n    command: process.env.CI ? 'pnpm preview --port 5555' : 'pnpm dev',\n    port: 5555,\n    reuseExistingServer: !process.env.CI,\n  },\n\n  /* Opt out of parallel tests on CI. */\n  workers: process.env.CI ? 1 : undefined,\n};\n\nexport default config;\n"
  },
  {
    "path": "playground/src/adapter/component/index.ts",
    "content": "/**\n * 通用组件共同的使用的基础组件，原先放在 adapter/form 内部，限制了使用范围，这里提取出来，方便其他地方使用\n * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,\n */\n\n/* eslint-disable vue/one-component-per-file */\n\nimport type {\n  UploadChangeParam,\n  UploadFile,\n  UploadProps,\n} from 'ant-design-vue';\n\nimport type { Component, Ref } from 'vue';\n\nimport type { BaseFormComponentType } from '@vben/common-ui';\nimport type { Sortable } from '@vben/hooks';\nimport type { Recordable } from '@vben/types';\n\nimport {\n  computed,\n  defineAsyncComponent,\n  defineComponent,\n  h,\n  nextTick,\n  onMounted,\n  onUnmounted,\n  ref,\n  render,\n  unref,\n  watch,\n} from 'vue';\n\nimport {\n  ApiComponent,\n  globalShareState,\n  IconPicker,\n  VCropper,\n} from '@vben/common-ui';\nimport { useSortable } from '@vben/hooks';\nimport { IconifyIcon } from '@vben/icons';\nimport { $t } from '@vben/locales';\nimport { isEmpty } from '@vben/utils';\n\nimport { message, Modal, notification } from 'ant-design-vue';\n\nconst AutoComplete = defineAsyncComponent(\n  () => import('ant-design-vue/es/auto-complete'),\n);\nconst Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));\nconst Checkbox = defineAsyncComponent(\n  () => import('ant-design-vue/es/checkbox'),\n);\nconst CheckboxGroup = defineAsyncComponent(() =>\n  import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),\n);\nconst DatePicker = defineAsyncComponent(\n  () => import('ant-design-vue/es/date-picker'),\n);\nconst Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));\nconst Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));\nconst InputNumber = defineAsyncComponent(\n  () => import('ant-design-vue/es/input-number'),\n);\nconst InputPassword = defineAsyncComponent(() =>\n  import('ant-design-vue/es/input').then((res) => res.InputPassword),\n);\nconst Mentions = defineAsyncComponent(\n  () => import('ant-design-vue/es/mentions'),\n);\nconst Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));\nconst RadioGroup = defineAsyncComponent(() =>\n  import('ant-design-vue/es/radio').then((res) => res.RadioGroup),\n);\nconst RangePicker = defineAsyncComponent(() =>\n  import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),\n);\nconst Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));\nconst Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));\nconst Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));\nconst Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));\nconst Textarea = defineAsyncComponent(() =>\n  import('ant-design-vue/es/input').then((res) => res.Textarea),\n);\nconst TimePicker = defineAsyncComponent(\n  () => import('ant-design-vue/es/time-picker'),\n);\nconst TreeSelect = defineAsyncComponent(\n  () => import('ant-design-vue/es/tree-select'),\n);\nconst Cascader = defineAsyncComponent(\n  () => import('ant-design-vue/es/cascader'),\n);\nconst Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));\nconst Image = defineAsyncComponent(() => import('ant-design-vue/es/image'));\nconst PreviewGroup = defineAsyncComponent(() =>\n  import('ant-design-vue/es/image').then((res) => res.ImagePreviewGroup),\n);\n\nconst withDefaultPlaceholder = <T extends Component>(\n  component: T,\n  type: 'input' | 'select',\n  componentProps: Recordable<any> = {},\n) => {\n  return defineComponent({\n    name: component.name,\n    inheritAttrs: false,\n    setup: (props: any, { attrs, expose, slots }) => {\n      const placeholder =\n        props?.placeholder ||\n        attrs?.placeholder ||\n        $t(`ui.placeholder.${type}`);\n      // 透传组件暴露的方法\n      const innerRef = ref();\n      expose(\n        new Proxy(\n          {},\n          {\n            get: (_target, key) => innerRef.value?.[key],\n            has: (_target, key) => key in (innerRef.value || {}),\n          },\n        ),\n      );\n      return () =>\n        h(\n          component,\n          { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },\n          slots,\n        );\n    },\n  });\n};\n\nconst IMAGE_EXTENSIONS = new Set([\n  'bmp',\n  'gif',\n  'jpeg',\n  'jpg',\n  'png',\n  'svg',\n  'webp',\n]);\n\n/**\n * 检查是否为图片文件\n */\nfunction isImageFile(file: UploadFile): boolean {\n  if (file.url) {\n    try {\n      const pathname = new URL(file.url, 'http://localhost').pathname;\n      const ext = pathname.split('.').pop()?.toLowerCase();\n      return ext ? IMAGE_EXTENSIONS.has(ext) : false;\n    } catch {\n      const ext = file.url?.split('.').pop()?.toLowerCase();\n      return ext ? IMAGE_EXTENSIONS.has(ext) : false;\n    }\n  }\n  if (!file.type) {\n    const ext = file.name?.split('.').pop()?.toLowerCase();\n    return ext ? IMAGE_EXTENSIONS.has(ext) : false;\n  }\n  return file.type.startsWith('image/');\n}\n\n/**\n * 创建默认的上传按钮插槽\n */\nfunction createDefaultUploadSlots(listType: string, placeholder: string) {\n  if (listType === 'picture-card') {\n    return { default: () => placeholder };\n  }\n  return {\n    default: () =>\n      h(\n        Button,\n        {\n          icon: h(IconifyIcon, {\n            icon: 'ant-design:upload-outlined',\n            class: 'mb-1 size-4',\n          }),\n        },\n        () => placeholder,\n      ),\n  };\n}\n\n/**\n * 获取文件的 Base64\n */\nfunction getBase64(file: File): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.readAsDataURL(file);\n    reader.addEventListener('load', () => resolve(reader.result as string));\n    reader.addEventListener('error', reject);\n  });\n}\n\n/**\n * 预览图片\n */\nasync function previewImage(\n  file: UploadFile,\n  visible: Ref<boolean>,\n  fileList: Ref<UploadProps['fileList']>,\n) {\n  // 非图片文件直接打开链接\n  if (!isImageFile(file)) {\n    const url = file.url || file.preview;\n    if (url) {\n      window.open(url, '_blank');\n    } else {\n      message.error($t('ui.formRules.previewWarning'));\n    }\n    return;\n  }\n\n  const [ImageComponent, PreviewGroupComponent] = await Promise.all([\n    Image,\n    PreviewGroup,\n  ]);\n\n  // 过滤图片文件并生成预览\n  const imageFiles = (unref(fileList) || []).filter((f) => isImageFile(f));\n\n  for (const imgFile of imageFiles) {\n    if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {\n      imgFile.preview = await getBase64(imgFile.originFileObj);\n    }\n  }\n\n  const container = document.createElement('div');\n  document.body.append(container);\n  let isUnmounted = false;\n\n  const currentIndex = imageFiles.findIndex((f) => f.uid === file.uid);\n\n  const PreviewWrapper = {\n    setup() {\n      return () => {\n        if (isUnmounted) return null;\n        return h(\n          PreviewGroupComponent,\n          {\n            class: 'hidden',\n            preview: {\n              visible: visible.value,\n              current: currentIndex,\n              onVisibleChange: (value: boolean) => {\n                visible.value = value;\n                if (!value) {\n                  setTimeout(() => {\n                    if (!isUnmounted && container) {\n                      isUnmounted = true;\n                      render(null, container);\n                      container.remove();\n                    }\n                  }, 300);\n                }\n              },\n            },\n          },\n          () =>\n            imageFiles.map((imgFile) =>\n              h(ImageComponent, {\n                key: imgFile.uid,\n                src: imgFile.url || imgFile.preview,\n              }),\n            ),\n        );\n      };\n    },\n  };\n\n  render(h(PreviewWrapper), container);\n}\n\n/**\n * 图片裁剪操作\n */\nfunction cropImage(file: File, aspectRatio: string | undefined) {\n  return new Promise<Blob | string | undefined>((resolve, reject) => {\n    const container = document.createElement('div');\n    document.body.append(container);\n\n    let isUnmounted = false;\n    let objectUrl: null | string = null;\n\n    const open = ref<boolean>(true);\n    const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);\n\n    const closeModal = () => {\n      open.value = false;\n      setTimeout(() => {\n        if (!isUnmounted && container) {\n          if (objectUrl) {\n            URL.revokeObjectURL(objectUrl);\n          }\n          isUnmounted = true;\n          render(null, container);\n          container.remove();\n        }\n      }, 300);\n    };\n\n    const CropperWrapper = {\n      setup() {\n        return () => {\n          if (isUnmounted) return null;\n          if (!objectUrl) {\n            objectUrl = URL.createObjectURL(file);\n          }\n          return h(\n            Modal,\n            {\n              open: open.value,\n              title: h('div', {}, [\n                $t('ui.crop.title'),\n                h(\n                  'span',\n                  {\n                    class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,\n                  },\n                  $t('ui.crop.titleTip', [aspectRatio]),\n                ),\n              ]),\n              centered: true,\n              width: 548,\n              keyboard: false,\n              maskClosable: false,\n              closable: false,\n              cancelText: $t('common.cancel'),\n              okText: $t('ui.crop.confirm'),\n              destroyOnClose: true,\n              onOk: async () => {\n                const cropper = cropperRef.value;\n                if (!cropper) {\n                  reject(new Error('Cropper not found'));\n                  closeModal();\n                  return;\n                }\n                try {\n                  const dataUrl = await cropper.getCropImage();\n                  if (dataUrl) {\n                    resolve(dataUrl);\n                  } else {\n                    reject(new Error($t('ui.crop.errorTip')));\n                  }\n                } catch {\n                  reject(new Error($t('ui.crop.errorTip')));\n                } finally {\n                  closeModal();\n                }\n              },\n              onCancel() {\n                resolve('');\n                closeModal();\n              },\n            },\n            () =>\n              h(VCropper, {\n                ref: (ref: any) => (cropperRef.value = ref),\n                img: objectUrl as string,\n                aspectRatio,\n              }),\n          );\n        };\n      },\n    };\n\n    render(h(CropperWrapper), container);\n  });\n}\n\n/**\n * 带预览功能的上传组件\n */\nconst withPreviewUpload = () => {\n  return defineComponent({\n    name: Upload.name,\n    emits: ['update:modelValue'],\n    setup(\n      props: any,\n      { attrs, slots, emit }: { attrs: any; emit: any; slots: any },\n    ) {\n      const previewVisible = ref<boolean>(false);\n      const placeholder = attrs?.placeholder || $t('ui.placeholder.upload');\n      const listType = attrs?.listType || attrs?.['list-type'] || 'text';\n      const fileList = ref<UploadProps['fileList']>(\n        attrs?.fileList || attrs?.['file-list'] || [],\n      );\n\n      const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']);\n      const aspectRatio = computed(\n        () => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],\n      );\n\n      const handleBeforeUpload = async (\n        file: UploadFile,\n        originFileList: Array<File>,\n      ) => {\n        // 文件大小限制\n        if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {\n          message.error($t('ui.formRules.sizeLimit', [maxSize.value]));\n          file.status = 'removed';\n          return false;\n        }\n\n        // 图片裁剪处理\n        if (\n          attrs.crop &&\n          !attrs.multiple &&\n          originFileList[0] &&\n          isImageFile(file)\n        ) {\n          file.status = 'removed';\n          const blob = await cropImage(originFileList[0], aspectRatio.value);\n          if (!blob) {\n            throw new Error($t('ui.crop.errorTip'));\n          }\n          return blob;\n        }\n\n        return attrs.beforeUpload?.(file) ?? true;\n      };\n\n      const handleChange = (event: UploadChangeParam) => {\n        try {\n          attrs.handleChange?.(event);\n          attrs.onHandleChange?.(event);\n        } catch (error) {\n          console.error(error);\n        }\n        fileList.value = event.fileList.filter(\n          (file) => file.status !== 'removed',\n        );\n        emit(\n          'update:modelValue',\n          event.fileList?.length ? fileList.value : undefined,\n        );\n      };\n\n      const handlePreview = async (file: UploadFile) => {\n        previewVisible.value = true;\n        await previewImage(file, previewVisible, fileList);\n      };\n\n      const renderUploadButton = () => {\n        if (attrs.disabled) return null;\n        return isEmpty(slots)\n          ? createDefaultUploadSlots(listType, placeholder)\n          : slots;\n      };\n\n      // 拖拽排序\n      const draggable = computed(\n        () => (attrs.draggable ?? false) && !attrs.disabled,\n      );\n      const uploadId = `upload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n      const sortableInstance = ref<null | Sortable>(null);\n\n      const styleId = `upload-drag-style-${uploadId}`;\n\n      function injectDragStyle() {\n        if (!document.querySelector(`[id=\"${styleId}\"]`)) {\n          const style = document.createElement('style');\n          style.id = styleId;\n          style.textContent = `\n            [data-upload-id=\"${uploadId}\"] .ant-upload-list-item { cursor: move; }\n            [data-upload-id=\"${uploadId}\"] .ant-upload-list-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); }\n          `;\n          document.head.append(style);\n        }\n      }\n\n      function removeDragStyle() {\n        document.querySelector(`[id=\"${styleId}\"]`)?.remove();\n      }\n\n      async function initSortable(retryCount = 0) {\n        if (!draggable.value) return;\n\n        injectDragStyle();\n        await nextTick();\n        await new Promise((resolve) => setTimeout(resolve, 100));\n\n        const container = document.querySelector(\n          `[data-upload-id=\"${uploadId}\"] .ant-upload-list`,\n        ) as HTMLElement;\n\n        if (!container) {\n          if (retryCount < 5) {\n            setTimeout(() => initSortable(retryCount + 1), 200);\n          }\n          return;\n        }\n\n        const { initializeSortable } = useSortable(container, {\n          animation: 300,\n          delay: 400,\n          delayOnTouchOnly: true,\n          filter:\n            '.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading',\n          onEnd: (evt) => {\n            const { oldIndex, newIndex } = evt;\n            if (\n              oldIndex === undefined ||\n              newIndex === undefined ||\n              oldIndex === newIndex\n            ) {\n              return;\n            }\n\n            const list = [...(fileList.value || [])];\n            const [movedItem] = list.splice(oldIndex, 1);\n            if (movedItem) {\n              list.splice(newIndex, 0, movedItem);\n              fileList.value = list;\n            }\n\n            attrs.onDragSort?.(oldIndex, newIndex);\n            emit('update:modelValue', fileList.value);\n          },\n        });\n\n        sortableInstance.value = await initializeSortable();\n      }\n\n      // 监听表单值变化\n      watch(\n        () => attrs.modelValue,\n        (res) => {\n          fileList.value = res;\n        },\n      );\n\n      onMounted(initSortable);\n      onUnmounted(() => {\n        sortableInstance.value?.destroy();\n        removeDragStyle();\n      });\n\n      return () =>\n        h(\n          'div',\n          { 'data-upload-id': uploadId, class: 'w-full' },\n          h(\n            Upload,\n            {\n              ...props,\n              ...attrs,\n              fileList: fileList.value,\n              beforeUpload: handleBeforeUpload,\n              onChange: handleChange,\n              onPreview: handlePreview,\n            },\n            renderUploadButton() as any,\n          ),\n        );\n    },\n  });\n};\n\n// 这里需要自行根据业务组件库进行适配，需要用到的组件都需要在这里类型说明\nexport type ComponentType =\n  | 'ApiCascader'\n  | 'ApiSelect'\n  | 'ApiTreeSelect'\n  | 'AutoComplete'\n  | 'Cascader'\n  | 'Checkbox'\n  | 'CheckboxGroup'\n  | 'DatePicker'\n  | 'DefaultButton'\n  | 'Divider'\n  | 'IconPicker'\n  | 'Input'\n  | 'InputNumber'\n  | 'InputPassword'\n  | 'Mentions'\n  | 'PrimaryButton'\n  | 'Radio'\n  | 'RadioGroup'\n  | 'RangePicker'\n  | 'Rate'\n  | 'Select'\n  | 'Space'\n  | 'Switch'\n  | 'Textarea'\n  | 'TimePicker'\n  | 'TreeSelect'\n  | 'Upload'\n  | BaseFormComponentType;\n\nasync function initComponentAdapter() {\n  const components: Partial<Record<ComponentType, Component>> = {\n    // 如果你的组件体积比较大，可以使用异步加载\n    // Button: () =>\n    // import('xxx').then((res) => res.Button),\n\n    ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {\n      component: Cascader,\n      fieldNames: { label: 'label', value: 'value', children: 'children' },\n      loadingSlot: 'suffixIcon',\n      modelPropName: 'value',\n      visibleEvent: 'onVisibleChange',\n    }),\n    ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {\n      component: Select,\n      loadingSlot: 'suffixIcon',\n      modelPropName: 'value',\n      visibleEvent: 'onVisibleChange',\n    }),\n    ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {\n      component: TreeSelect,\n      fieldNames: { label: 'label', value: 'value', children: 'children' },\n      loadingSlot: 'suffixIcon',\n      modelPropName: 'value',\n      optionsPropName: 'treeData',\n      visibleEvent: 'onVisibleChange',\n    }),\n    AutoComplete,\n    Cascader,\n    Checkbox,\n    CheckboxGroup,\n    DatePicker,\n    // 自定义默认按钮\n    DefaultButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'default' }, slots);\n    },\n    Divider,\n    IconPicker: withDefaultPlaceholder(IconPicker, 'select', {\n      iconSlot: 'addonAfter',\n      inputComponent: Input,\n      modelValueProp: 'value',\n    }),\n    Input: withDefaultPlaceholder(Input, 'input'),\n    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),\n    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),\n    Mentions: withDefaultPlaceholder(Mentions, 'input'),\n    // 自定义主要按钮\n    PrimaryButton: (props, { attrs, slots }) => {\n      return h(Button, { ...props, attrs, type: 'primary' }, slots);\n    },\n    Radio,\n    RadioGroup,\n    RangePicker,\n    Rate,\n    Select: withDefaultPlaceholder(Select, 'select'),\n    Space,\n    Switch,\n    Textarea: withDefaultPlaceholder(Textarea, 'input'),\n    TimePicker,\n    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),\n    Upload: withPreviewUpload(),\n  };\n\n  // 将组件注册到全局共享状态中\n  globalShareState.setComponents(components);\n\n  // 定义全局共享状态中的消息提示\n  globalShareState.defineMessage({\n    // 复制成功消息提示\n    copyPreferencesSuccess: (title, content) => {\n      notification.success({\n        description: content,\n        message: title,\n        placement: 'bottomRight',\n      });\n    },\n  });\n}\n\nexport { initComponentAdapter };\n"
  },
  {
    "path": "playground/src/adapter/form.ts",
    "content": "import type {\n  VbenFormSchema as FormSchema,\n  VbenFormProps,\n} from '@vben/common-ui';\n\nimport type { ComponentType } from './component';\n\nimport { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nasync function initSetupVbenForm() {\n  setupVbenForm<ComponentType>({\n    config: {\n      // ant design vue组件库默认都是 v-model:value\n      baseModelPropName: 'value',\n      // 一些组件是 v-model:checked 或者 v-model:fileList\n      modelPropNameMap: {\n        Checkbox: 'checked',\n        Radio: 'checked',\n        Switch: 'checked',\n        Upload: 'fileList',\n      },\n    },\n    defineRules: {\n      // 输入项目必填国际化适配\n      required: (value, _params, ctx) => {\n        if (value === undefined || value === null || value.length === 0) {\n          return $t('ui.formRules.required', [ctx.label]);\n        }\n        return true;\n      },\n      // 选择项目必填国际化适配\n      selectRequired: (value, _params, ctx) => {\n        if (value === undefined || value === null) {\n          return $t('ui.formRules.selectRequired', [ctx.label]);\n        }\n        return true;\n      },\n    },\n  });\n}\n\nconst useVbenForm = useForm<ComponentType>;\n\nexport { initSetupVbenForm, useVbenForm, z };\nexport type VbenFormSchema = FormSchema<ComponentType>;\nexport type { VbenFormProps };\n"
  },
  {
    "path": "playground/src/adapter/vxe-table.ts",
    "content": "import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';\nimport type { Recordable } from '@vben/types';\n\nimport type { ComponentType } from './component';\n\nimport { h } from 'vue';\n\nimport { IconifyIcon } from '@vben/icons';\nimport { $te } from '@vben/locales';\nimport {\n  setupVbenVxeTable,\n  useVbenVxeGrid as useGrid,\n} from '@vben/plugins/vxe-table';\nimport { get, isFunction, isString } from '@vben/utils';\n\nimport { objectOmit } from '@vueuse/core';\nimport { Button, Image, Popconfirm, Switch, Tag } from 'ant-design-vue';\n\nimport { $t } from '#/locales';\n\nimport { useVbenForm } from './form';\n\nsetupVbenVxeTable({\n  configVxeTable: (vxeUI) => {\n    vxeUI.setConfig({\n      grid: {\n        align: 'center',\n        border: false,\n        columnConfig: {\n          resizable: true,\n        },\n\n        formConfig: {\n          // 全局禁用vxe-table的表单配置，使用formOptions\n          enabled: false,\n        },\n        minHeight: 180,\n        proxyConfig: {\n          autoLoad: true,\n          response: {\n            result: 'items',\n            total: 'total',\n            list: '',\n          },\n          showActiveMsg: true,\n          showResponseMsg: false,\n        },\n        round: true,\n        showOverflow: true,\n        size: 'small',\n      } as VxeTableGridOptions,\n    });\n\n    /**\n     * 解决vxeTable在热更新时可能会出错的问题\n     */\n    vxeUI.renderer.forEach((_item, key) => {\n      if (key.startsWith('Cell')) {\n        vxeUI.renderer.delete(key);\n      }\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellImage' },\n    vxeUI.renderer.add('CellImage', {\n      renderTableDefault(renderOpts, params) {\n        const { props } = renderOpts;\n        const { column, row } = params;\n        return h(Image, { src: row[column.field], ...props });\n      },\n    });\n\n    // 表格配置项可以用 cellRender: { name: 'CellLink' },\n    vxeUI.renderer.add('CellLink', {\n      renderTableDefault(renderOpts) {\n        const { props } = renderOpts;\n        return h(\n          Button,\n          { size: 'small', type: 'link' },\n          { default: () => props?.text },\n        );\n      },\n    });\n\n    // 单元格渲染： Tag\n    vxeUI.renderer.add('CellTag', {\n      renderTableDefault({ options, props }, { column, row }) {\n        const value = get(row, column.field);\n        const tagOptions = options ?? [\n          { color: 'success', label: $t('common.enabled'), value: 1 },\n          { color: 'error', label: $t('common.disabled'), value: 0 },\n        ];\n        const tagItem = tagOptions.find((item) => item.value === value);\n        return h(\n          Tag,\n          {\n            ...props,\n            ...objectOmit(tagItem ?? {}, ['label']),\n          },\n          { default: () => tagItem?.label ?? value },\n        );\n      },\n    });\n\n    vxeUI.renderer.add('CellSwitch', {\n      renderTableDefault({ attrs, props }, { column, row }) {\n        const loadingKey = `__loading_${column.field}`;\n        const finallyProps = {\n          checkedChildren: $t('common.enabled'),\n          checkedValue: 1,\n          unCheckedChildren: $t('common.disabled'),\n          unCheckedValue: 0,\n          ...props,\n          checked: row[column.field],\n          loading: row[loadingKey] ?? false,\n          'onUpdate:checked': onChange,\n        };\n        async function onChange(newVal: any) {\n          row[loadingKey] = true;\n          try {\n            const result = await attrs?.beforeChange?.(newVal, row);\n            if (result !== false) {\n              row[column.field] = newVal;\n            }\n          } finally {\n            row[loadingKey] = false;\n          }\n        }\n        return h(Switch, finallyProps);\n      },\n    });\n\n    /**\n     * 注册表格的操作按钮渲染器\n     */\n    vxeUI.renderer.add('CellOperation', {\n      renderTableDefault({ attrs, options, props }, { column, row }) {\n        const defaultProps = { size: 'small', type: 'link', ...props };\n        let align: string;\n        switch (column.align) {\n          case 'center': {\n            align = 'center';\n            break;\n          }\n          case 'left': {\n            align = 'start';\n            break;\n          }\n          default: {\n            align = 'end';\n            break;\n          }\n        }\n        const presets: Recordable<Recordable<any>> = {\n          delete: {\n            danger: true,\n            text: $t('common.delete'),\n          },\n          edit: {\n            text: $t('common.edit'),\n          },\n        };\n        const operations: Array<Recordable<any>> = (\n          options || ['edit', 'delete']\n        )\n          .map((opt) => {\n            if (isString(opt)) {\n              return presets[opt]\n                ? { code: opt, ...presets[opt], ...defaultProps }\n                : {\n                    code: opt,\n                    text: $te(`common.${opt}`) ? $t(`common.${opt}`) : opt,\n                    ...defaultProps,\n                  };\n            } else {\n              return { ...defaultProps, ...presets[opt.code], ...opt };\n            }\n          })\n          .map((opt) => {\n            const optBtn: Recordable<any> = {};\n            Object.keys(opt).forEach((key) => {\n              optBtn[key] = isFunction(opt[key]) ? opt[key](row) : opt[key];\n            });\n            return optBtn;\n          })\n          .filter((opt) => opt.show !== false);\n\n        function renderBtn(opt: Recordable<any>, listen = true) {\n          return h(\n            Button,\n            {\n              ...props,\n              ...opt,\n              icon: undefined,\n              onClick: listen\n                ? () =>\n                    attrs?.onClick?.({\n                      code: opt.code,\n                      row,\n                    })\n                : undefined,\n            },\n            {\n              default: () => {\n                const content = [];\n                if (opt.icon) {\n                  content.push(\n                    h(IconifyIcon, { class: 'size-5', icon: opt.icon }),\n                  );\n                }\n                content.push(opt.text);\n                return content;\n              },\n            },\n          );\n        }\n\n        function renderConfirm(opt: Recordable<any>) {\n          let viewportWrapper: HTMLElement | null = null;\n          return h(\n            Popconfirm,\n            {\n              /**\n               * 当popconfirm用在固定列中时，将固定列作为弹窗的容器时可能会因为固定列较窄而无法容纳弹窗\n               * 将表格主体区域作为弹窗容器时又会因为固定列的层级较高而遮挡弹窗\n               * 将body或者表格视口区域作为弹窗容器时又会导致弹窗无法跟随表格滚动。\n               * 鉴于以上各种情况，一种折中的解决方案是弹出层展示时，禁止操作表格的滚动条。\n               * 这样既解决了弹窗的遮挡问题，又不至于让弹窗随着表格的滚动而跑出视口区域。\n               */\n              getPopupContainer(el) {\n                viewportWrapper = el.closest('.vxe-table--viewport-wrapper');\n                return document.body;\n              },\n              placement: 'topLeft',\n              title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']),\n              ...props,\n              ...opt,\n              icon: undefined,\n              onOpenChange: (open: boolean) => {\n                // 当弹窗打开时，禁止表格的滚动\n                if (open) {\n                  viewportWrapper?.style.setProperty('pointer-events', 'none');\n                } else {\n                  viewportWrapper?.style.removeProperty('pointer-events');\n                }\n              },\n              onConfirm: () => {\n                attrs?.onClick?.({\n                  code: opt.code,\n                  row,\n                });\n              },\n            },\n            {\n              default: () => renderBtn({ ...opt }, false),\n              description: () =>\n                h(\n                  'div',\n                  { class: 'truncate' },\n                  $t('ui.actionMessage.deleteConfirm', [\n                    row[attrs?.nameField || 'name'],\n                  ]),\n                ),\n            },\n          );\n        }\n\n        const btns = operations.map((opt) =>\n          opt.code === 'delete' ? renderConfirm(opt) : renderBtn(opt),\n        );\n        return h(\n          'div',\n          {\n            class: 'flex table-operations',\n            style: { justifyContent: align },\n          },\n          btns,\n        );\n      },\n    });\n\n    // 这里可以自行扩展 vxe-table 的全局配置，比如自定义格式化\n    // vxeUI.formats.add\n  },\n  useVbenForm,\n});\n\nexport const useVbenVxeGrid = <T extends Record<string, any>>(\n  ...rest: Parameters<typeof useGrid<T, ComponentType>>\n) => useGrid<T, ComponentType>(...rest);\n\nexport type OnActionClickParams<T = Recordable<any>> = {\n  code: string;\n  row: T;\n};\nexport type OnActionClickFn<T = Recordable<any>> = (\n  params: OnActionClickParams<T>,\n) => void;\nexport type * from '@vben/plugins/vxe-table';\n"
  },
  {
    "path": "playground/src/api/core/auth.ts",
    "content": "import { baseRequestClient, requestClient } from '#/api/request';\n\nexport namespace AuthApi {\n  /** 登录接口参数 */\n  export interface LoginParams {\n    password?: string;\n    username?: string;\n  }\n\n  /** 登录接口返回值 */\n  export interface LoginResult {\n    accessToken: string;\n  }\n\n  export interface RefreshTokenResult {\n    data: string;\n    status: number;\n  }\n}\n\n/**\n * 登录\n */\nexport async function loginApi(data: AuthApi.LoginParams) {\n  return requestClient.post<AuthApi.LoginResult>('/auth/login', data, {\n    withCredentials: true,\n  });\n}\n\n/**\n * 刷新accessToken\n */\nexport async function refreshTokenApi() {\n  return baseRequestClient.post<AuthApi.RefreshTokenResult>(\n    '/auth/refresh',\n    null,\n    {\n      withCredentials: true,\n    },\n  );\n}\n\n/**\n * 退出登录\n */\nexport async function logoutApi() {\n  return baseRequestClient.post('/auth/logout', null, {\n    withCredentials: true,\n  });\n}\n\n/**\n * 获取用户权限码\n */\nexport async function getAccessCodesApi() {\n  return requestClient.get<string[]>('/auth/codes');\n}\n"
  },
  {
    "path": "playground/src/api/core/index.ts",
    "content": "export * from './auth';\nexport * from './menu';\nexport * from './timezone';\nexport * from './user';\n"
  },
  {
    "path": "playground/src/api/core/menu.ts",
    "content": "import type { RouteRecordStringComponent } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户所有菜单\n */\nexport async function getAllMenusApi() {\n  return requestClient.get<RouteRecordStringComponent[]>('/menu/all');\n}\n"
  },
  {
    "path": "playground/src/api/core/timezone.ts",
    "content": "import { requestClient } from '#/api/request';\n\n/**\n * 获取系统支持的时区列表\n */\nexport async function getTimezoneOptionsApi() {\n  return await requestClient.get<\n    {\n      label: string;\n      value: string;\n    }[]\n  >('/timezone/getTimezoneOptions');\n}\n/**\n * 获取用户时区\n */\nexport async function getTimezoneApi(): Promise<null | string | undefined> {\n  return requestClient.get<null | string | undefined>('/timezone/getTimezone');\n}\n/**\n * 设置用户时区\n * @param timezone 时区\n */\nexport async function setTimezoneApi(timezone: string): Promise<void> {\n  return requestClient.post('/timezone/setTimezone', { timezone });\n}\n"
  },
  {
    "path": "playground/src/api/core/user.ts",
    "content": "import type { UserInfo } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 获取用户信息\n */\nexport async function getUserInfoApi() {\n  return requestClient.get<UserInfo>('/user/info');\n}\n"
  },
  {
    "path": "playground/src/api/examples/download.ts",
    "content": "import type { RequestResponse } from '@vben/request';\n\nimport { requestClient } from '../request';\n\n/**\n * 下载文件，获取Blob\n * @returns Blob\n */\nasync function downloadFile1() {\n  return requestClient.download<Blob>(\n    'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',\n  );\n}\n\n/**\n * 下载文件，获取完整的Response\n * @returns RequestResponse<Blob>\n */\nasync function downloadFile2() {\n  return requestClient.download<RequestResponse<Blob>>(\n    'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',\n    {\n      responseReturn: 'raw',\n    },\n  );\n}\n\nexport { downloadFile1, downloadFile2 };\n"
  },
  {
    "path": "playground/src/api/examples/index.ts",
    "content": "export * from './status';\nexport * from './table';\n"
  },
  {
    "path": "playground/src/api/examples/json-bigint.ts",
    "content": "import { requestClient } from '#/api/request';\n\n/**\n * 发起请求\n */\nasync function getBigIntData() {\n  return requestClient.get('/demo/bigint');\n}\n\nexport { getBigIntData };\n"
  },
  {
    "path": "playground/src/api/examples/params.ts",
    "content": "import type { Recordable } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\n/**\n * 发起数组请求\n */\nasync function getParamsData(\n  params: Recordable<any>,\n  type: 'brackets' | 'comma' | 'indices' | 'repeat',\n) {\n  return requestClient.get('/status', {\n    params,\n    paramsSerializer: type,\n    responseReturn: 'raw',\n  });\n}\n\nexport { getParamsData };\n"
  },
  {
    "path": "playground/src/api/examples/status.ts",
    "content": "import { requestClient } from '#/api/request';\n\n/**\n * 模拟任意状态码\n */\nasync function getMockStatusApi(status: string) {\n  return requestClient.get('/status', { params: { status } });\n}\n\nexport { getMockStatusApi };\n"
  },
  {
    "path": "playground/src/api/examples/table.ts",
    "content": "import { requestClient } from '#/api/request';\n\nexport namespace DemoTableApi {\n  export interface PageFetchParams {\n    [key: string]: any;\n    page: number;\n    pageSize: number;\n  }\n}\n\n/**\n * 获取示例表格数据\n */\nasync function getExampleTableApi(params: DemoTableApi.PageFetchParams) {\n  return requestClient.get('/table/list', { params });\n}\n\nexport { getExampleTableApi };\n"
  },
  {
    "path": "playground/src/api/examples/upload.ts",
    "content": "import { requestClient } from '#/api/request';\n\ninterface UploadFileParams {\n  file: File;\n  onError?: (error: Error) => void;\n  onProgress?: (progress: { percent: number }) => void;\n  onSuccess?: (data: any, file: File) => void;\n}\nexport async function upload_file({\n  file,\n  onError,\n  onProgress,\n  onSuccess,\n}: UploadFileParams) {\n  try {\n    onProgress?.({ percent: 0 });\n\n    const data = await requestClient.upload('/upload', { file });\n\n    onProgress?.({ percent: 100 });\n    onSuccess?.(data, file);\n  } catch (error) {\n    onError?.(error instanceof Error ? error : new Error(String(error)));\n  }\n}\n"
  },
  {
    "path": "playground/src/api/index.ts",
    "content": "export * from './core';\nexport * from './examples';\nexport * from './system';\n"
  },
  {
    "path": "playground/src/api/request.ts",
    "content": "/**\n * 该文件可自行根据业务逻辑进行调整\n */\nimport type { AxiosResponseHeaders, RequestClientOptions } from '@vben/request';\n\nimport { useAppConfig } from '@vben/hooks';\nimport { preferences } from '@vben/preferences';\nimport {\n  authenticateResponseInterceptor,\n  defaultResponseInterceptor,\n  errorMessageResponseInterceptor,\n  RequestClient,\n} from '@vben/request';\nimport { useAccessStore } from '@vben/stores';\nimport { cloneDeep } from '@vben/utils';\n\nimport { message } from 'ant-design-vue';\nimport JSONBigInt from 'json-bigint';\n\nimport { useAuthStore } from '#/store';\n\nimport { refreshTokenApi } from './core';\n\nconst { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);\n\nfunction createRequestClient(baseURL: string, options?: RequestClientOptions) {\n  const client = new RequestClient({\n    ...options,\n    baseURL,\n    transformResponse: (data: any, header: AxiosResponseHeaders) => {\n      // storeAsString指示将BigInt存储为字符串，设为false则会存储为内置的BigInt类型\n      if (\n        header.getContentType()?.toString().includes('application/json') &&\n        typeof data === 'string'\n      ) {\n        return cloneDeep(\n          JSONBigInt({ storeAsString: true, strict: true }).parse(data),\n        );\n      }\n      return data;\n    },\n  });\n\n  /**\n   * 重新认证逻辑\n   */\n  async function doReAuthenticate() {\n    console.warn('Access token or refresh token is invalid or expired. ');\n    const accessStore = useAccessStore();\n    const authStore = useAuthStore();\n    accessStore.setAccessToken(null);\n    if (\n      preferences.app.loginExpiredMode === 'modal' &&\n      accessStore.isAccessChecked\n    ) {\n      accessStore.setLoginExpired(true);\n    } else {\n      await authStore.logout();\n    }\n  }\n\n  /**\n   * 刷新token逻辑\n   */\n  async function doRefreshToken() {\n    const accessStore = useAccessStore();\n    const resp = await refreshTokenApi();\n    const newToken = resp.data;\n    accessStore.setAccessToken(newToken);\n    return newToken;\n  }\n\n  function formatToken(token: null | string) {\n    return token ? `Bearer ${token}` : null;\n  }\n\n  // 请求头处理\n  client.addRequestInterceptor({\n    fulfilled: async (config) => {\n      const accessStore = useAccessStore();\n\n      config.headers.Authorization = formatToken(accessStore.accessToken);\n      config.headers['Accept-Language'] = preferences.app.locale;\n      return config;\n    },\n  });\n\n  // 处理返回的响应数据格式\n  client.addResponseInterceptor(\n    defaultResponseInterceptor({\n      codeField: 'code',\n      dataField: 'data',\n      successCode: 0,\n    }),\n  );\n\n  // token过期的处理\n  client.addResponseInterceptor(\n    authenticateResponseInterceptor({\n      client,\n      doReAuthenticate,\n      doRefreshToken,\n      enableRefreshToken: preferences.app.enableRefreshToken,\n      formatToken,\n    }),\n  );\n\n  // 通用的错误处理,如果没有进入上面的错误处理逻辑，就会进入这里\n  client.addResponseInterceptor(\n    errorMessageResponseInterceptor((msg: string, error) => {\n      // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理，根据不同的 code 做不同的提示，而不是直接使用 message.error 提示 msg\n      // 当前mock接口返回的错误字段是 error 或者 message\n      const responseData = error?.response?.data ?? {};\n      const errorMessage = responseData?.error ?? responseData?.message ?? '';\n      // 如果没有错误信息，则会根据状态码进行提示\n      message.error(errorMessage || msg);\n    }),\n  );\n\n  return client;\n}\n\nexport const requestClient = createRequestClient(apiURL, {\n  responseReturn: 'data',\n});\n\nexport const baseRequestClient = new RequestClient({ baseURL: apiURL });\n\nexport interface PageFetchParams {\n  [key: string]: any;\n  pageNo?: number;\n  pageSize?: number;\n}\n"
  },
  {
    "path": "playground/src/api/system/dept.ts",
    "content": "import { requestClient } from '#/api/request';\n\nexport namespace SystemDeptApi {\n  export interface SystemDept {\n    [key: string]: any;\n    children?: SystemDept[];\n    id: string;\n    name: string;\n    remark?: string;\n    status: 0 | 1;\n  }\n}\n\n/**\n * 获取部门列表数据\n */\nasync function getDeptList() {\n  return requestClient.get<Array<SystemDeptApi.SystemDept>>(\n    '/system/dept/list',\n  );\n}\n\n/**\n * 创建部门\n * @param data 部门数据\n */\nasync function createDept(\n  data: Omit<SystemDeptApi.SystemDept, 'children' | 'id'>,\n) {\n  return requestClient.post('/system/dept', data);\n}\n\n/**\n * 更新部门\n *\n * @param id 部门 ID\n * @param data 部门数据\n */\nasync function updateDept(\n  id: string,\n  data: Omit<SystemDeptApi.SystemDept, 'children' | 'id'>,\n) {\n  return requestClient.put(`/system/dept/${id}`, data);\n}\n\n/**\n * 删除部门\n * @param id 部门 ID\n */\nasync function deleteDept(id: string) {\n  return requestClient.delete(`/system/dept/${id}`);\n}\n\nexport { createDept, deleteDept, getDeptList, updateDept };\n"
  },
  {
    "path": "playground/src/api/system/index.ts",
    "content": "export * from './dept';\nexport * from './menu';\nexport * from './role';\n"
  },
  {
    "path": "playground/src/api/system/menu.ts",
    "content": "import type { Recordable } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\nexport namespace SystemMenuApi {\n  /** 徽标颜色集合 */\n  export const BadgeVariants = [\n    'default',\n    'destructive',\n    'primary',\n    'success',\n    'warning',\n  ] as const;\n  /** 徽标类型集合 */\n  export const BadgeTypes = ['dot', 'normal'] as const;\n  /** 菜单类型集合 */\n  export const MenuTypes = [\n    'catalog',\n    'menu',\n    'embedded',\n    'link',\n    'button',\n  ] as const;\n  /** 系统菜单 */\n  export interface SystemMenu {\n    [key: string]: any;\n    /** 后端权限标识 */\n    authCode: string;\n    /** 子级 */\n    children?: SystemMenu[];\n    /** 组件 */\n    component?: string;\n    /** 菜单ID */\n    id: string;\n    /** 菜单元数据 */\n    meta?: {\n      /** 激活时显示的图标 */\n      activeIcon?: string;\n      /** 作为路由时，需要激活的菜单的Path */\n      activePath?: string;\n      /** 固定在标签栏 */\n      affixTab?: boolean;\n      /** 在标签栏固定的顺序 */\n      affixTabOrder?: number;\n      /** 徽标内容(当徽标类型为normal时有效) */\n      badge?: string;\n      /** 徽标类型 */\n      badgeType?: (typeof BadgeTypes)[number];\n      /** 徽标颜色 */\n      badgeVariants?: (typeof BadgeVariants)[number];\n      /** 在菜单中隐藏下级 */\n      hideChildrenInMenu?: boolean;\n      /** 在面包屑中隐藏 */\n      hideInBreadcrumb?: boolean;\n      /** 在菜单中隐藏 */\n      hideInMenu?: boolean;\n      /** 在标签栏中隐藏 */\n      hideInTab?: boolean;\n      /** 菜单图标 */\n      icon?: string;\n      /** 内嵌Iframe的URL */\n      iframeSrc?: string;\n      /** 是否缓存页面 */\n      keepAlive?: boolean;\n      /** 外链页面的URL */\n      link?: string;\n      /** 同一个路由最大打开的标签数 */\n      maxNumOfOpenTab?: number;\n      /** 无需基础布局 */\n      noBasicLayout?: boolean;\n      /** 是否在新窗口打开 */\n      openInNewWindow?: boolean;\n      /** 菜单排序 */\n      order?: number;\n      /** 额外的路由参数 */\n      query?: Recordable<any>;\n      /** 菜单标题 */\n      title?: string;\n    };\n    /** 菜单名称 */\n    name: string;\n    /** 路由路径 */\n    path: string;\n    /** 父级ID */\n    pid: string;\n    /** 重定向 */\n    redirect?: string;\n    /** 菜单类型 */\n    type: (typeof MenuTypes)[number];\n  }\n}\n\n/**\n * 获取菜单数据列表\n */\nasync function getMenuList() {\n  return requestClient.get<Array<SystemMenuApi.SystemMenu>>(\n    '/system/menu/list',\n  );\n}\n\nasync function isMenuNameExists(\n  name: string,\n  id?: SystemMenuApi.SystemMenu['id'],\n) {\n  return requestClient.get<boolean>('/system/menu/name-exists', {\n    params: { id, name },\n  });\n}\n\nasync function isMenuPathExists(\n  path: string,\n  id?: SystemMenuApi.SystemMenu['id'],\n) {\n  return requestClient.get<boolean>('/system/menu/path-exists', {\n    params: { id, path },\n  });\n}\n\n/**\n * 创建菜单\n * @param data 菜单数据\n */\nasync function createMenu(\n  data: Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>,\n) {\n  return requestClient.post('/system/menu', data);\n}\n\n/**\n * 更新菜单\n *\n * @param id 菜单 ID\n * @param data 菜单数据\n */\nasync function updateMenu(\n  id: string,\n  data: Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>,\n) {\n  return requestClient.put(`/system/menu/${id}`, data);\n}\n\n/**\n * 删除菜单\n * @param id 菜单 ID\n */\nasync function deleteMenu(id: string) {\n  return requestClient.delete(`/system/menu/${id}`);\n}\n\nexport {\n  createMenu,\n  deleteMenu,\n  getMenuList,\n  isMenuNameExists,\n  isMenuPathExists,\n  updateMenu,\n};\n"
  },
  {
    "path": "playground/src/api/system/role.ts",
    "content": "import type { Recordable } from '@vben/types';\n\nimport { requestClient } from '#/api/request';\n\nexport namespace SystemRoleApi {\n  export interface SystemRole {\n    [key: string]: any;\n    id: string;\n    name: string;\n    permissions: string[];\n    remark?: string;\n    status: 0 | 1;\n  }\n}\n\n/**\n * 获取角色列表数据\n */\nasync function getRoleList(params: Recordable<any>) {\n  return requestClient.get<Array<SystemRoleApi.SystemRole>>(\n    '/system/role/list',\n    { params },\n  );\n}\n\n/**\n * 创建角色\n * @param data 角色数据\n */\nasync function createRole(data: Omit<SystemRoleApi.SystemRole, 'id'>) {\n  return requestClient.post('/system/role', data);\n}\n\n/**\n * 更新角色\n *\n * @param id 角色 ID\n * @param data 角色数据\n */\nasync function updateRole(\n  id: string,\n  data: Omit<SystemRoleApi.SystemRole, 'id'>,\n) {\n  return requestClient.put(`/system/role/${id}`, data);\n}\n\n/**\n * 删除角色\n * @param id 角色 ID\n */\nasync function deleteRole(id: string) {\n  return requestClient.delete(`/system/role/${id}`);\n}\n\nexport { createRole, deleteRole, getRoleList, updateRole };\n"
  },
  {
    "path": "playground/src/app.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { useAntdDesignTokens } from '@vben/hooks';\nimport { preferences, usePreferences } from '@vben/preferences';\n\nimport { App, ConfigProvider, theme } from 'ant-design-vue';\n\nimport { antdLocale } from '#/locales';\n\ndefineOptions({ name: 'App' });\n\nconst { isDark } = usePreferences();\nconst { tokens } = useAntdDesignTokens();\n\nconst tokenTheme = computed(() => {\n  const algorithm = isDark.value\n    ? [theme.darkAlgorithm]\n    : [theme.defaultAlgorithm];\n\n  // antd 紧凑模式算法\n  if (preferences.app.compact) {\n    algorithm.push(theme.compactAlgorithm);\n  }\n\n  return {\n    algorithm,\n    token: tokens,\n  };\n});\n</script>\n\n<template>\n  <ConfigProvider :locale=\"antdLocale\" :theme=\"tokenTheme\">\n    <App>\n      <RouterView />\n    </App>\n  </ConfigProvider>\n</template>\n"
  },
  {
    "path": "playground/src/bootstrap.ts",
    "content": "import { createApp, watchEffect } from 'vue';\n\nimport { registerAccessDirective } from '@vben/access';\nimport { registerLoadingDirective } from '@vben/common-ui';\nimport { preferences } from '@vben/preferences';\nimport { initStores } from '@vben/stores';\nimport '@vben/styles';\nimport '@vben/styles/antd';\n\nimport { useTitle } from '@vueuse/core';\n\nimport { $t, setupI18n } from '#/locales';\nimport { router } from '#/router';\n\nimport { initComponentAdapter } from './adapter/component';\nimport { initSetupVbenForm } from './adapter/form';\nimport App from './app.vue';\nimport { initTimezone } from './timezone-init';\n\nasync function bootstrap(namespace: string) {\n  // 初始化组件适配器\n  await initComponentAdapter();\n\n  // 初始化表单组件\n  await initSetupVbenForm();\n\n  // 设置弹窗的默认配置\n  // setDefaultModalProps({\n  //   fullscreenButton: false,\n  // });\n  // 设置抽屉的默认配置\n  // setDefaultDrawerProps({\n  //   zIndex: 1020,\n  // });\n\n  const app = createApp(App);\n\n  // 注册v-loading指令\n  registerLoadingDirective(app, {\n    loading: 'loading', // 在这里可以自定义指令名称，也可以明确提供false表示不注册这个指令\n    spinning: 'spinning',\n  });\n\n  // 国际化 i18n 配置\n  await setupI18n(app);\n\n  // 配置 pinia-tore\n  await initStores(app, { namespace });\n\n  // 初始化时区HANDLER\n  initTimezone();\n\n  // 安装权限指令\n  registerAccessDirective(app);\n\n  // 初始化 tippy\n  const { initTippy } = await import('@vben/common-ui/es/tippy');\n  initTippy(app);\n\n  // 配置路由及路由守卫\n  app.use(router);\n\n  // 配置@tanstack/vue-query\n  const { VueQueryPlugin } = await import('@tanstack/vue-query');\n  app.use(VueQueryPlugin);\n\n  // 配置Motion插件\n  const { MotionPlugin } = await import('@vben/plugins/motion');\n  app.use(MotionPlugin);\n\n  // 动态更新标题\n  watchEffect(() => {\n    if (preferences.app.dynamicTitle) {\n      const routeTitle = router.currentRoute.value.meta?.title;\n      const pageTitle =\n        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;\n      useTitle(pageTitle);\n    }\n  });\n\n  app.mount('#app');\n}\n\nexport { bootstrap };\n"
  },
  {
    "path": "playground/src/layouts/auth.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\n\nimport { AuthPageLayout } from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst appName = computed(() => preferences.app.name);\nconst logo = computed(() => preferences.logo.source);\nconst clickLogo = () => {};\n</script>\n\n<template>\n  <AuthPageLayout\n    :app-name=\"appName\"\n    :logo=\"logo\"\n    :page-description=\"$t('authentication.pageDesc')\"\n    :page-title=\"$t('authentication.pageTitle')\"\n    :click-logo=\"clickLogo\"\n  >\n    <!-- 自定义工具栏 -->\n    <!-- <template #toolbar></template> -->\n  </AuthPageLayout>\n</template>\n"
  },
  {
    "path": "playground/src/layouts/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { NotificationItem } from '@vben/layouts';\n\nimport { computed, onBeforeMount, ref, watch } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { AuthenticationLoginExpiredModal } from '@vben/common-ui';\nimport { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';\nimport { useWatermark } from '@vben/hooks';\nimport { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons';\nimport {\n  BasicLayout,\n  LockScreen,\n  Notification,\n  UserDropdown,\n} from '@vben/layouts';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useTabbarStore, useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport { $t } from '#/locales';\nimport { useAuthStore } from '#/store';\nimport LoginForm from '#/views/_core/authentication/login.vue';\n\nconst { setMenuList } = useTabbarStore();\nsetMenuList([\n  'close',\n  'affix',\n  'maximize',\n  'reload',\n  'open-in-new-window',\n  'close-left',\n  'close-right',\n  'close-other',\n  'close-all',\n]);\n\nconst notifications = ref<NotificationItem[]>([\n  {\n    id: 1,\n    avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',\n    date: '3小时前',\n    isRead: true,\n    message: '描述信息描述信息描述信息',\n    title: '收到了 14 份新周报',\n  },\n  {\n    id: 2,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '刚刚',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '朱偏右 回复了你',\n  },\n  {\n    id: 3,\n    avatar: 'https://avatar.vercel.sh/1',\n    date: '2024-01-01',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '曲丽丽 评论了你',\n  },\n  {\n    id: 4,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '代办提醒',\n  },\n  {\n    id: 5,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转Workspace示例',\n    link: '/workspace',\n  },\n  {\n    id: 6,\n    avatar: 'https://avatar.vercel.sh/satori',\n    date: '1天前',\n    isRead: false,\n    message: '描述信息描述信息描述信息',\n    title: '跳转外部链接示例',\n    link: 'https://doc.vben.pro',\n  },\n]);\n\nconst router = useRouter();\nconst userStore = useUserStore();\nconst authStore = useAuthStore();\nconst accessStore = useAccessStore();\nconst { destroyWatermark, updateWatermark } = useWatermark();\nconst showDot = computed(() =>\n  notifications.value.some((item) => !item.isRead),\n);\n\nconst menus = computed(() => [\n  {\n    handler: () => {\n      router.push({ name: 'Profile' });\n    },\n    icon: 'lucide:user',\n    text: $t('page.auth.profile'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_DOC_URL, {\n        target: '_blank',\n      });\n    },\n    icon: BookOpenText,\n    text: $t('ui.widgets.document'),\n  },\n  {\n    handler: () => {\n      openWindow(VBEN_GITHUB_URL, {\n        target: '_blank',\n      });\n    },\n    icon: SvgGithubIcon,\n    text: 'GitHub',\n  },\n  {\n    handler: () => {\n      openWindow(`${VBEN_GITHUB_URL}/issues`, {\n        target: '_blank',\n      });\n    },\n    icon: CircleHelp,\n    text: $t('ui.widgets.qa'),\n  },\n]);\n\nconst avatar = computed(() => {\n  return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;\n});\n\nasync function handleLogout() {\n  await authStore.logout(false);\n}\n\nfunction handleNoticeClear() {\n  notifications.value = [];\n}\n\nfunction markRead(id: number | string) {\n  const item = notifications.value.find((item) => item.id === id);\n  if (item) {\n    item.isRead = true;\n  }\n}\n\nfunction remove(id: number | string) {\n  notifications.value = notifications.value.filter((item) => item.id !== id);\n}\n\nfunction handleMakeAll() {\n  notifications.value.forEach((item) => (item.isRead = true));\n}\n\nfunction handleClickLogo() {}\n\nwatch(\n  () => ({\n    enable: preferences.app.watermark,\n    content: preferences.app.watermarkContent,\n  }),\n  async ({ enable, content }) => {\n    if (enable) {\n      await updateWatermark({\n        content:\n          content ||\n          `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,\n      });\n    } else {\n      destroyWatermark();\n    }\n  },\n  {\n    immediate: true,\n  },\n);\n\nonBeforeMount(() => {\n  if (preferences.app.watermark) {\n    destroyWatermark();\n  }\n});\n</script>\n\n<template>\n  <BasicLayout\n    @clear-preferences-and-logout=\"handleLogout\"\n    @click-logo=\"handleClickLogo\"\n  >\n    <template #user-dropdown>\n      <UserDropdown\n        :avatar\n        :menus\n        :text=\"userStore.userInfo?.realName\"\n        description=\"ann.vben@gmail.com\"\n        tag-text=\"Pro\"\n        trigger=\"both\"\n        @logout=\"handleLogout\"\n      />\n    </template>\n    <template #notification>\n      <Notification\n        :dot=\"showDot\"\n        :notifications=\"notifications\"\n        @clear=\"handleNoticeClear\"\n        @read=\"(item) => item.id && markRead(item.id)\"\n        @remove=\"(item) => item.id && remove(item.id)\"\n        @make-all=\"handleMakeAll\"\n      />\n    </template>\n    <template #extra>\n      <AuthenticationLoginExpiredModal\n        v-model:open=\"accessStore.loginExpired\"\n        :avatar\n      >\n        <LoginForm />\n      </AuthenticationLoginExpiredModal>\n    </template>\n    <template #lock-screen>\n      <LockScreen :avatar @to-login=\"handleLogout\" />\n    </template>\n  </BasicLayout>\n</template>\n"
  },
  {
    "path": "playground/src/layouts/index.ts",
    "content": "const BasicLayout = () => import('./basic.vue');\nconst AuthPageLayout = () => import('./auth.vue');\n\nconst IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);\n\nexport { AuthPageLayout, BasicLayout, IFrameView };\n"
  },
  {
    "path": "playground/src/locales/README.md",
    "content": "# locale\n\n每个app使用的国际化可能不同，这里用于扩展国际化的功能，例如扩展 dayjs、antd组件库的多语言切换，以及app本身的国际化文件。\n"
  },
  {
    "path": "playground/src/locales/index.ts",
    "content": "import type { Locale } from 'ant-design-vue/es/locale';\n\nimport type { App } from 'vue';\n\nimport type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';\n\nimport { ref } from 'vue';\n\nimport {\n  $t,\n  setupI18n as coreSetup,\n  loadLocalesMapFromDir,\n} from '@vben/locales';\nimport { preferences } from '@vben/preferences';\n\nimport antdEnLocale from 'ant-design-vue/es/locale/en_US';\nimport antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN';\nimport dayjs from 'dayjs';\n\nconst antdLocale = ref<Locale>(antdDefaultLocale);\n\nconst modules = import.meta.glob('./langs/**/*.json');\n\nconst localesMap = loadLocalesMapFromDir(\n  /\\.\\/langs\\/([^/]+)\\/(.*)\\.json$/,\n  modules,\n);\n/**\n * 加载应用特有的语言包\n * 这里也可以改造为从服务端获取翻译数据\n * @param lang\n */\nasync function loadMessages(lang: SupportedLanguagesType) {\n  const [appLocaleMessages] = await Promise.all([\n    localesMap[lang]?.(),\n    loadThirdPartyMessage(lang),\n  ]);\n  return appLocaleMessages?.default;\n}\n\n/**\n * 加载第三方组件库的语言包\n * @param lang\n */\nasync function loadThirdPartyMessage(lang: SupportedLanguagesType) {\n  await Promise.all([loadAntdLocale(lang), loadDayjsLocale(lang)]);\n}\n\n/**\n * 加载dayjs的语言包\n * @param lang\n */\nasync function loadDayjsLocale(lang: SupportedLanguagesType) {\n  let locale;\n  switch (lang) {\n    case 'en-US': {\n      locale = await import('dayjs/locale/en');\n      break;\n    }\n    case 'zh-CN': {\n      locale = await import('dayjs/locale/zh-cn');\n      break;\n    }\n    // 默认使用英语\n    default: {\n      locale = await import('dayjs/locale/en');\n    }\n  }\n  if (locale) {\n    dayjs.locale(locale);\n  } else {\n    console.error(`Failed to load dayjs locale for ${lang}`);\n  }\n}\n\n/**\n * 加载antd的语言包\n * @param lang\n */\nasync function loadAntdLocale(lang: SupportedLanguagesType) {\n  switch (lang) {\n    case 'en-US': {\n      antdLocale.value = antdEnLocale;\n      break;\n    }\n    case 'zh-CN': {\n      antdLocale.value = antdDefaultLocale;\n      break;\n    }\n  }\n}\n\nasync function setupI18n(app: App, options: LocaleSetupOptions = {}) {\n  await coreSetup(app, {\n    defaultLocale: preferences.app.locale,\n    loadMessages,\n    missingWarn: !import.meta.env.PROD,\n    ...options,\n  });\n}\n\nexport { $t, antdLocale, setupI18n };\n"
  },
  {
    "path": "playground/src/locales/langs/en-US/demos.json",
    "content": "{\n  \"title\": \"Demos\",\n  \"access\": {\n    \"frontendPermissions\": \"Frontend Permissions\",\n    \"backendPermissions\": \"Backend Permissions\",\n    \"pageAccess\": \"Page Access\",\n    \"buttonControl\": \"Button Control\",\n    \"menuVisible403\": \"Menu Visible(403)\",\n    \"superVisible\": \"Visible to Super\",\n    \"adminVisible\": \"Visible to Admin\",\n    \"userVisible\": \"Visible to User\"\n  },\n  \"nested\": {\n    \"title\": \"Nested Menu\",\n    \"menu1\": \"Menu 1\",\n    \"menu2\": \"Menu 2\",\n    \"menu2_1\": \"Menu 2-1\",\n    \"menu3\": \"Menu 3\",\n    \"menu3_1\": \"Menu 3-1\",\n    \"menu3_2\": \"Menu 3-2\",\n    \"menu3_2_1\": \"Menu 3-2-1\"\n  },\n  \"outside\": {\n    \"title\": \"External Pages\",\n    \"embedded\": \"Embedded\",\n    \"externalLink\": \"External Link\"\n  },\n  \"badge\": {\n    \"title\": \"Menu Badge\",\n    \"dot\": \"Dot Badge\",\n    \"text\": \"Text Badge\",\n    \"color\": \"Badge Color\"\n  },\n  \"activeIcon\": {\n    \"title\": \"Active Menu Icon\",\n    \"children\": \"Children Active Icon\"\n  },\n  \"fallback\": {\n    \"title\": \"Fallback Page\"\n  },\n  \"features\": {\n    \"title\": \"Features\",\n    \"hideChildrenInMenu\": \"Hide Menu Children\",\n    \"loginExpired\": \"Login Expired\",\n    \"icons\": \"Icons\",\n    \"watermark\": \"Watermark\",\n    \"tabs\": \"Tabs\",\n    \"tabDetail\": \"Tab Detail Page\",\n    \"fullScreen\": \"FullScreen\",\n    \"clipboard\": \"Clipboard\",\n    \"menuWithQuery\": \"Menu With Query\",\n    \"openInNewWindow\": \"Open in New Window\",\n    \"fileDownload\": \"File Download\"\n  },\n  \"breadcrumb\": {\n    \"navigation\": \"Breadcrumb Navigation\",\n    \"lateral\": \"Lateral Mode\",\n    \"lateralDetail\": \"Lateral Mode Detail\",\n    \"level\": \"Level Mode\",\n    \"levelDetail\": \"Level Mode Detail\"\n  },\n  \"vben\": {\n    \"title\": \"Project\",\n    \"about\": \"About\",\n    \"document\": \"Document\",\n    \"antdv\": \"Ant Design Vue Version\",\n    \"antdv-next\": \"Antdv Next Version\",\n    \"naive-ui\": \"Naive UI Version\",\n    \"element-plus\": \"Element Plus Version\",\n    \"tdesign\": \"TDesign Vue Version\"\n  }\n}\n"
  },
  {
    "path": "playground/src/locales/langs/en-US/examples.json",
    "content": "{\n  \"title\": \"Examples\",\n  \"modal\": {\n    \"title\": \"Modal\"\n  },\n  \"drawer\": {\n    \"title\": \"Drawer\"\n  },\n  \"ellipsis\": {\n    \"title\": \"EllipsisText\"\n  },\n  \"form\": {\n    \"title\": \"Form\",\n    \"basic\": \"Basic Form\",\n    \"layout\": \"Custom Layout\",\n    \"query\": \"Query Form\",\n    \"rules\": \"Form Rules\",\n    \"dynamic\": \"Dynamic Form\",\n    \"custom\": \"Custom Component\",\n    \"api\": \"Api\",\n    \"merge\": \"Merge Form\",\n    \"scrollToError\": \"Scroll to Error Field\",\n    \"upload-error\": \"Partial file upload failed\",\n    \"upload-urls\": \"Urls after file upload\",\n    \"file\": \"file\",\n    \"crop-image\": \"Crop image\",\n    \"upload-image\": \"Click to upload image\"\n  },\n  \"vxeTable\": {\n    \"title\": \"Vxe Table\",\n    \"basic\": \"Basic Table\",\n    \"remote\": \"Remote Load\",\n    \"tree\": \"Tree Table\",\n    \"fixed\": \"Fixed Header/Column\",\n    \"virtual\": \"Virtual Scroll\",\n    \"editCell\": \"Edit Cell\",\n    \"editRow\": \"Edit Row\",\n    \"custom-cell\": \"Custom Cell\",\n    \"form\": \"Form Table\"\n  },\n  \"captcha\": {\n    \"title\": \"Captcha\",\n    \"pointSelection\": \"Point Selection Captcha\",\n    \"sliderCaptcha\": \"Slider Captcha\",\n    \"sliderRotateCaptcha\": \"Rotate Captcha\",\n    \"sliderTranslateCaptcha\": \"Translate Captcha\",\n    \"captchaCardTitle\": \"Please complete the security verification\",\n    \"pageDescription\": \"Verify user identity by clicking on specific locations in the image.\",\n    \"pageTitle\": \"Captcha Component Example\",\n    \"basic\": \"Basic Usage\",\n    \"titlePlaceholder\": \"Captcha Title Text\",\n    \"captchaImageUrlPlaceholder\": \"Captcha Image (supports img tag src attribute value)\",\n    \"hintImage\": \"Hint Image\",\n    \"hintText\": \"Hint Text\",\n    \"hintImagePlaceholder\": \"Hint Image (supports img tag src attribute value)\",\n    \"hintTextPlaceholder\": \"Hint Text\",\n    \"showConfirm\": \"Show Confirm\",\n    \"hideConfirm\": \"Hide Confirm\",\n    \"widthPlaceholder\": \"Captcha Image Width Default 300px\",\n    \"heightPlaceholder\": \"Captcha Image Height Default 220px\",\n    \"paddingXPlaceholder\": \"Horizontal Padding Default 12px\",\n    \"paddingYPlaceholder\": \"Vertical Padding Default 16px\",\n    \"index\": \"Index:\",\n    \"timestamp\": \"Timestamp:\",\n    \"x\": \"x:\",\n    \"y\": \"y:\"\n  },\n  \"resize\": {\n    \"title\": \"Resize\"\n  },\n  \"layout\": {\n    \"col-page\": \"ColPage Layout\"\n  },\n  \"button-group\": {\n    \"title\": \"Button Group\"\n  },\n  \"function\": {\n    \"contentMenu\": \"Content Menu\"\n  },\n  \"cropper\": {\n    \"title\": \"Cropper\"\n  }\n}\n"
  },
  {
    "path": "playground/src/locales/langs/en-US/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"Login\",\n    \"register\": \"Register\",\n    \"codeLogin\": \"Code Login\",\n    \"qrcodeLogin\": \"Qr Code Login\",\n    \"forgetPassword\": \"Forget Password\",\n    \"sendingCode\": \"SMS Code is sending...\",\n    \"codeSentTo\": \"Code has been sent to {0}\",\n    \"profile\": \"Profile\"\n  },\n  \"dashboard\": {\n    \"title\": \"Dashboard\",\n    \"analytics\": \"Analytics\",\n    \"workspace\": \"Workspace\"\n  }\n}\n"
  },
  {
    "path": "playground/src/locales/langs/en-US/system.json",
    "content": "{\n  \"title\": \"System Management\",\n  \"dept\": {\n    \"name\": \"Department\",\n    \"title\": \"Department Management\",\n    \"deptName\": \"Department Name\",\n    \"status\": \"Status\",\n    \"createTime\": \"Create Time\",\n    \"remark\": \"Remark\",\n    \"operation\": \"Operation\",\n    \"parentDept\": \"Parent Department\"\n  },\n  \"menu\": {\n    \"title\": \"Menu Management\",\n    \"parent\": \"Parent Menu\",\n    \"menuTitle\": \"Title\",\n    \"menuName\": \"Menu Name\",\n    \"name\": \"Menu\",\n    \"type\": \"Type\",\n    \"typeCatalog\": \"Catalog\",\n    \"typeMenu\": \"Menu\",\n    \"typeButton\": \"Button\",\n    \"typeLink\": \"Link\",\n    \"typeEmbedded\": \"Embedded\",\n    \"icon\": \"Icon\",\n    \"activeIcon\": \"Active Icon\",\n    \"activePath\": \"Active Path\",\n    \"path\": \"Route Path\",\n    \"component\": \"Component\",\n    \"status\": \"Status\",\n    \"authCode\": \"Auth Code\",\n    \"badge\": \"Badge\",\n    \"operation\": \"Operation\",\n    \"linkSrc\": \"Link Address\",\n    \"affixTab\": \"Affix In Tabs\",\n    \"keepAlive\": \"Keep Alive\",\n    \"hideInMenu\": \"Hide In Menu\",\n    \"hideInTab\": \"Hide In Tabbar\",\n    \"hideChildrenInMenu\": \"Hide Children In Menu\",\n    \"hideInBreadcrumb\": \"Hide In Breadcrumb\",\n    \"advancedSettings\": \"Other Settings\",\n    \"activePathMustExist\": \"The path could not find a valid menu\",\n    \"activePathHelp\": \"When jumping to the current route, \\nthe menu path that needs to be activated must be specified when it does not display in the navigation menu.\",\n    \"badgeType\": {\n      \"title\": \"Badge Type\",\n      \"dot\": \"Dot\",\n      \"normal\": \"Text\",\n      \"none\": \"None\"\n    },\n    \"badgeVariants\": \"Badge Style\"\n  },\n  \"role\": {\n    \"title\": \"Role Management\",\n    \"list\": \"Role List\",\n    \"name\": \"Role\",\n    \"roleName\": \"Role Name\",\n    \"id\": \"Role ID\",\n    \"status\": \"Status\",\n    \"remark\": \"Remark\",\n    \"createTime\": \"Creation Time\",\n    \"operation\": \"Operation\",\n    \"permissions\": \"Permissions\",\n    \"setPermissions\": \"Permissions\"\n  }\n}\n"
  },
  {
    "path": "playground/src/locales/langs/zh-CN/demos.json",
    "content": "{\n  \"title\": \"演示\",\n  \"access\": {\n    \"frontendPermissions\": \"前端权限\",\n    \"backendPermissions\": \"后端权限\",\n    \"pageAccess\": \"页面访问\",\n    \"buttonControl\": \"按钮控制\",\n    \"menuVisible403\": \"菜单可见(403)\",\n    \"superVisible\": \"Super 可见\",\n    \"adminVisible\": \"Admin 可见\",\n    \"userVisible\": \"User 可见\"\n  },\n  \"nested\": {\n    \"title\": \"嵌套菜单\",\n    \"menu1\": \"菜单 1\",\n    \"menu2\": \"菜单 2\",\n    \"menu2_1\": \"菜单 2-1\",\n    \"menu3\": \"菜单 3\",\n    \"menu3_1\": \"菜单 3-1\",\n    \"menu3_2\": \"菜单 3-2\",\n    \"menu3_2_1\": \"菜单 3-2-1\"\n  },\n  \"outside\": {\n    \"title\": \"外部页面\",\n    \"embedded\": \"内嵌\",\n    \"externalLink\": \"外链\"\n  },\n  \"badge\": {\n    \"title\": \"菜单徽标\",\n    \"dot\": \"点徽标\",\n    \"text\": \"文本徽标\",\n    \"color\": \"徽标颜色\"\n  },\n  \"activeIcon\": {\n    \"title\": \"菜单激活图标\",\n    \"children\": \"子级激活图标\"\n  },\n  \"fallback\": {\n    \"title\": \"缺省页\"\n  },\n  \"features\": {\n    \"title\": \"功能\",\n    \"hideChildrenInMenu\": \"隐藏子菜单\",\n    \"loginExpired\": \"登录过期\",\n    \"icons\": \"图标\",\n    \"watermark\": \"水印\",\n    \"tabs\": \"标签页\",\n    \"tabDetail\": \"标签详情页\",\n    \"fullScreen\": \"全屏\",\n    \"clipboard\": \"剪贴板\",\n    \"menuWithQuery\": \"带参菜单\",\n    \"openInNewWindow\": \"新窗口打开\",\n    \"fileDownload\": \"文件下载\",\n    \"requestParamsSerializer\": \"参数序列化\"\n  },\n  \"breadcrumb\": {\n    \"navigation\": \"面包屑导航\",\n    \"lateral\": \"平级模式\",\n    \"level\": \"层级模式\",\n    \"levelDetail\": \"层级模式详情\",\n    \"lateralDetail\": \"平级模式详情\"\n  },\n  \"vben\": {\n    \"title\": \"项目\",\n    \"about\": \"关于\",\n    \"document\": \"文档\",\n    \"antdv\": \"Ant Design Vue 版本\",\n    \"antdv-next\": \"Antdv Next 版本\",\n    \"naive-ui\": \"Naive UI 版本\",\n    \"element-plus\": \"Element Plus 版本\",\n    \"tdesign\": \"TDesign Vue 版本\"\n  }\n}\n"
  },
  {
    "path": "playground/src/locales/langs/zh-CN/examples.json",
    "content": "{\n  \"title\": \"示例\",\n  \"modal\": {\n    \"title\": \"弹窗\"\n  },\n  \"drawer\": {\n    \"title\": \"抽屉\"\n  },\n  \"ellipsis\": {\n    \"title\": \"文本省略\"\n  },\n  \"resize\": {\n    \"title\": \"拖动调整\"\n  },\n  \"form\": {\n    \"title\": \"表单\",\n    \"basic\": \"基础表单\",\n    \"layout\": \"自定义布局\",\n    \"query\": \"查询表单\",\n    \"rules\": \"表单校验\",\n    \"dynamic\": \"动态表单\",\n    \"custom\": \"自定义组件\",\n    \"api\": \"Api\",\n    \"merge\": \"合并表单\",\n    \"scrollToError\": \"滚动到错误字段\",\n    \"upload-error\": \"部分文件上传失败\",\n    \"upload-urls\": \"文件上传后的网址\",\n    \"file\": \"文件\",\n    \"crop-image\": \"裁剪图片\",\n    \"upload-image\": \"点击上传图片\"\n  },\n  \"vxeTable\": {\n    \"title\": \"Vxe 表格\",\n    \"basic\": \"基础表格\",\n    \"remote\": \"远程加载\",\n    \"tree\": \"树形表格\",\n    \"fixed\": \"固定表头/列\",\n    \"virtual\": \"虚拟滚动\",\n    \"editCell\": \"单元格编辑\",\n    \"editRow\": \"行编辑\",\n    \"custom-cell\": \"自定义单元格\",\n    \"form\": \"搜索表单\"\n  },\n  \"captcha\": {\n    \"title\": \"验证码\",\n    \"pointSelection\": \"点选验证\",\n    \"sliderCaptcha\": \"滑块验证\",\n    \"sliderRotateCaptcha\": \"旋转验证\",\n    \"sliderTranslateCaptcha\": \"拼图滑块验证\",\n    \"captchaCardTitle\": \"请完成安全验证\",\n    \"pageDescription\": \"通过点击图片中的特定位置来验证用户身份。\",\n    \"pageTitle\": \"验证码组件示例\",\n    \"basic\": \"基本使用\",\n    \"titlePlaceholder\": \"验证码标题文案\",\n    \"captchaImageUrlPlaceholder\": \"验证码图片（支持img标签src属性值）\",\n    \"hintImage\": \"提示图片\",\n    \"hintText\": \"提示文本\",\n    \"hintImagePlaceholder\": \"提示图片（支持img标签src属性值）\",\n    \"hintTextPlaceholder\": \"提示文本\",\n    \"showConfirm\": \"展示确认\",\n    \"hideConfirm\": \"隐藏确认\",\n    \"widthPlaceholder\": \"验证码图片宽度 默认300px\",\n    \"heightPlaceholder\": \"验证码图片高度 默认220px\",\n    \"paddingXPlaceholder\": \"水平内边距 默认12px\",\n    \"paddingYPlaceholder\": \"垂直内边距 默认16px\",\n    \"index\": \"索引：\",\n    \"timestamp\": \"时间戳：\",\n    \"x\": \"x：\",\n    \"y\": \"y：\"\n  },\n  \"layout\": {\n    \"col-page\": \"双列布局\"\n  },\n  \"button-group\": {\n    \"title\": \"按钮组\"\n  },\n  \"function\": {\n    \"contentMenu\": \"上下文菜单\"\n  },\n  \"cropper\": {\n    \"title\": \"图片裁剪\"\n  }\n}\n"
  },
  {
    "path": "playground/src/locales/langs/zh-CN/page.json",
    "content": "{\n  \"auth\": {\n    \"login\": \"登录\",\n    \"register\": \"注册\",\n    \"codeLogin\": \"验证码登录\",\n    \"qrcodeLogin\": \"二维码登录\",\n    \"forgetPassword\": \"忘记密码\",\n    \"sendingCode\": \"正在发送验证码\",\n    \"codeSentTo\": \"验证码已发送至{0}\",\n    \"profile\": \"个人中心\"\n  },\n  \"dashboard\": {\n    \"title\": \"概览\",\n    \"analytics\": \"分析页\",\n    \"workspace\": \"工作台\"\n  }\n}\n"
  },
  {
    "path": "playground/src/locales/langs/zh-CN/system.json",
    "content": "{\n  \"title\": \"系统管理\",\n  \"dept\": {\n    \"list\": \"部门列表\",\n    \"createTime\": \"创建时间\",\n    \"deptName\": \"部门名称\",\n    \"name\": \"部门\",\n    \"operation\": \"操作\",\n    \"parentDept\": \"上级部门\",\n    \"remark\": \"备注\",\n    \"status\": \"状态\",\n    \"title\": \"部门管理\"\n  },\n  \"menu\": {\n    \"list\": \"菜单列表\",\n    \"activeIcon\": \"激活图标\",\n    \"activePath\": \"激活路径\",\n    \"activePathHelp\": \"跳转到当前路由时，需要激活的菜单路径。\\n当不在导航菜单中显示时，需要指定激活路径\",\n    \"activePathMustExist\": \"该路径未能找到有效的菜单\",\n    \"advancedSettings\": \"其它设置\",\n    \"affixTab\": \"固定在标签\",\n    \"authCode\": \"权限标识\",\n    \"badge\": \"徽章内容\",\n    \"badgeVariants\": \"徽标样式\",\n    \"badgeType\": {\n      \"dot\": \"点\",\n      \"none\": \"无\",\n      \"normal\": \"文字\",\n      \"title\": \"徽标类型\"\n    },\n    \"component\": \"页面组件\",\n    \"hideChildrenInMenu\": \"隐藏子菜单\",\n    \"hideInBreadcrumb\": \"在面包屑中隐藏\",\n    \"hideInMenu\": \"隐藏菜单\",\n    \"hideInTab\": \"在标签栏中隐藏\",\n    \"icon\": \"图标\",\n    \"keepAlive\": \"缓存标签页\",\n    \"linkSrc\": \"链接地址\",\n    \"menuName\": \"菜单名称\",\n    \"menuTitle\": \"标题\",\n    \"name\": \"菜单\",\n    \"operation\": \"操作\",\n    \"parent\": \"上级菜单\",\n    \"path\": \"路由地址\",\n    \"status\": \"状态\",\n    \"title\": \"菜单管理\",\n    \"type\": \"类型\",\n    \"typeButton\": \"按钮\",\n    \"typeCatalog\": \"目录\",\n    \"typeEmbedded\": \"内嵌\",\n    \"typeLink\": \"外链\",\n    \"typeMenu\": \"菜单\"\n  },\n  \"role\": {\n    \"title\": \"角色管理\",\n    \"list\": \"角色列表\",\n    \"name\": \"角色\",\n    \"roleName\": \"角色名称\",\n    \"id\": \"角色ID\",\n    \"status\": \"状态\",\n    \"remark\": \"备注\",\n    \"createTime\": \"创建时间\",\n    \"operation\": \"操作\",\n    \"permissions\": \"权限\",\n    \"setPermissions\": \"授权\"\n  }\n}\n"
  },
  {
    "path": "playground/src/main.ts",
    "content": "import { initPreferences } from '@vben/preferences';\nimport { unmountGlobalLoading } from '@vben/utils';\n\nimport { overridesPreferences } from './preferences';\n\n/**\n * 应用初始化完成之后再进行页面加载渲染\n */\nasync function initApplication() {\n  // name用于指定项目唯一标识\n  // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据\n  const env = import.meta.env.PROD ? 'prod' : 'dev';\n  const appVersion = import.meta.env.VITE_APP_VERSION;\n  const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;\n\n  // app偏好设置初始化\n  await initPreferences({\n    namespace,\n    overrides: overridesPreferences,\n  });\n\n  // 启动应用并挂载\n  // vue应用主要逻辑及视图\n  const { bootstrap } = await import('./bootstrap');\n  await bootstrap(namespace);\n\n  // 移除并销毁loading\n  unmountGlobalLoading();\n}\n\ninitApplication();\n"
  },
  {
    "path": "playground/src/preferences.ts",
    "content": "import { defineOverridesPreferences } from '@vben/preferences';\n\n/**\n * @description 项目配置文件\n * 只需要覆盖项目中的一部分配置，不需要的配置不用覆盖，会自动使用默认配置\n * !!! 更改配置后请清空缓存，否则可能不生效\n */\nexport const overridesPreferences = defineOverridesPreferences({\n  // overrides\n  app: {\n    name: import.meta.env.VITE_APP_TITLE,\n  },\n});\n"
  },
  {
    "path": "playground/src/router/access.ts",
    "content": "import type {\n  ComponentRecordType,\n  GenerateMenuAndRoutesOptions,\n} from '@vben/types';\n\nimport { generateAccessible } from '@vben/access';\nimport { preferences } from '@vben/preferences';\n\nimport { message } from 'ant-design-vue';\n\nimport { getAllMenusApi } from '#/api';\nimport { BasicLayout, IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');\n\nasync function generateAccess(options: GenerateMenuAndRoutesOptions) {\n  const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');\n\n  const layoutMap: ComponentRecordType = {\n    BasicLayout,\n    IFrameView,\n  };\n\n  return await generateAccessible(preferences.app.accessMode, {\n    ...options,\n    fetchMenuListAsync: async () => {\n      message.loading({\n        content: `${$t('common.loadingMenu')}...`,\n        duration: 1.5,\n      });\n      return await getAllMenusApi();\n    },\n    // 可以指定没有权限跳转403页面\n    forbiddenComponent,\n    // 如果 route.meta.menuVisibleWithForbidden = true\n    layoutMap,\n    pageMap,\n  });\n}\n\nexport { generateAccess };\n"
  },
  {
    "path": "playground/src/router/guard.ts",
    "content": "import type { Router } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { useAccessStore, useUserStore } from '@vben/stores';\nimport { startProgress, stopProgress } from '@vben/utils';\n\nimport { accessRoutes, coreRouteNames } from '#/router/routes';\nimport { useAuthStore } from '#/store';\n\nimport { generateAccess } from './access';\n\n/**\n * 通用守卫配置\n * @param router\n */\nfunction setupCommonGuard(router: Router) {\n  // 记录已经加载的页面\n  const loadedPaths = new Set<string>();\n\n  router.beforeEach((to) => {\n    to.meta.loaded = loadedPaths.has(to.path);\n\n    // 页面加载进度条\n    if (!to.meta.loaded && preferences.transition.progress) {\n      startProgress();\n    }\n    return true;\n  });\n\n  router.afterEach((to) => {\n    // 记录页面是否加载,如果已经加载，后续的页面切换动画等效果不在重复执行\n    loadedPaths.add(to.path);\n\n    // 关闭页面加载进度条\n    if (preferences.transition.progress) {\n      stopProgress();\n    }\n  });\n}\n\n/**\n * 权限访问守卫配置\n * @param router\n */\nfunction setupAccessGuard(router: Router) {\n  router.beforeEach(async (to, from) => {\n    const accessStore = useAccessStore();\n    const userStore = useUserStore();\n    const authStore = useAuthStore();\n    // 基本路由，这些路由不需要进入权限拦截\n    if (coreRouteNames.includes(to.name as string)) {\n      if (to.path === LOGIN_PATH && accessStore.accessToken) {\n        return decodeURIComponent(\n          (to.query?.redirect as string) ||\n            userStore.userInfo?.homePath ||\n            preferences.app.defaultHomePath,\n        );\n      }\n      return true;\n    }\n\n    // accessToken 检查\n    if (!accessStore.accessToken) {\n      // 明确声明忽略权限访问权限，则可以访问\n      if (to.meta.ignoreAccess) {\n        return true;\n      }\n\n      // 没有访问权限，跳转登录页面\n      if (to.fullPath !== LOGIN_PATH) {\n        return {\n          path: LOGIN_PATH,\n          // 如不需要，直接删除 query\n          query:\n            to.fullPath === preferences.app.defaultHomePath\n              ? {}\n              : { redirect: encodeURIComponent(to.fullPath) },\n          // 携带当前跳转的页面，登录后重新跳转该页面\n          replace: true,\n        };\n      }\n      return to;\n    }\n\n    // 是否已经生成过动态路由\n    if (accessStore.isAccessChecked) {\n      return true;\n    }\n\n    // 生成路由表\n    // 当前登录用户拥有的角色标识列表\n    const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());\n    const userRoles = userInfo.roles ?? [];\n\n    // 生成菜单和路由\n    const { accessibleMenus, accessibleRoutes } = await generateAccess({\n      roles: userRoles,\n      router,\n      // 则会在菜单中显示，但是访问会被重定向到403\n      routes: accessRoutes,\n    });\n\n    // 保存菜单信息和路由信息\n    accessStore.setAccessMenus(accessibleMenus);\n    accessStore.setAccessRoutes(accessibleRoutes);\n    accessStore.setIsAccessChecked(true);\n    let redirectPath: string;\n    if (from.query.redirect) {\n      redirectPath = from.query.redirect as string;\n    } else if (to.fullPath === preferences.app.defaultHomePath) {\n      redirectPath = preferences.app.defaultHomePath;\n    } else if (userInfo.homePath && to.fullPath === userInfo.homePath) {\n      redirectPath = userInfo.homePath;\n    } else {\n      redirectPath = to.fullPath;\n    }\n    return {\n      ...router.resolve(decodeURIComponent(redirectPath)),\n      replace: true,\n    };\n  });\n}\n\n/**\n * 项目守卫配置\n * @param router\n */\nfunction createRouterGuard(router: Router) {\n  /** 通用 */\n  setupCommonGuard(router);\n  /** 权限访问 */\n  setupAccessGuard(router);\n}\n\nexport { createRouterGuard };\n"
  },
  {
    "path": "playground/src/router/index.ts",
    "content": "import {\n  createRouter,\n  createWebHashHistory,\n  createWebHistory,\n} from 'vue-router';\n\nimport { resetStaticRoutes } from '@vben/utils';\n\nimport { createRouterGuard } from './guard';\nimport { routes } from './routes';\n\n/**\n *  @zh_CN 创建vue-router实例\n */\nconst router = createRouter({\n  history:\n    import.meta.env.VITE_ROUTER_HISTORY === 'hash'\n      ? createWebHashHistory(import.meta.env.VITE_BASE)\n      : createWebHistory(import.meta.env.VITE_BASE),\n  // 应该添加到路由的初始路由列表。\n  routes,\n  scrollBehavior: (to, _from, savedPosition) => {\n    if (savedPosition) {\n      return savedPosition;\n    }\n    return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };\n  },\n  // 是否应该禁止尾部斜杠。\n  // strict: true,\n});\n\nconst resetRoutes = () => resetStaticRoutes(router, routes);\n\n// 创建路由守卫\ncreateRouterGuard(router);\n\nexport { resetRoutes, router };\n"
  },
  {
    "path": "playground/src/router/routes/core.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\n\nimport { $t } from '#/locales';\n\nconst BasicLayout = () => import('#/layouts/basic.vue');\nconst AuthPageLayout = () => import('#/layouts/auth.vue');\n/** 全局404页面 */\nconst fallbackNotFoundRoute: RouteRecordRaw = {\n  component: () => import('#/views/_core/fallback/not-found.vue'),\n  meta: {\n    hideInBreadcrumb: true,\n    hideInMenu: true,\n    hideInTab: true,\n    title: '404',\n  },\n  name: 'FallbackNotFound',\n  path: '/:path(.*)*',\n};\n\n/** 基本路由，这些路由是必须存在的 */\nconst coreRoutes: RouteRecordRaw[] = [\n  /**\n   * 根路由\n   * 使用基础布局，作为所有页面的父级容器，子级就不必配置BasicLayout。\n   * 此路由必须存在，且不应修改\n   */\n  {\n    component: BasicLayout,\n    meta: {\n      hideInBreadcrumb: true,\n      title: 'Root',\n    },\n    name: 'Root',\n    path: '/',\n    redirect: preferences.app.defaultHomePath,\n    children: [],\n  },\n  {\n    component: AuthPageLayout,\n    meta: {\n      hideInTab: true,\n      title: 'Authentication',\n    },\n    name: 'Authentication',\n    path: '/auth',\n    redirect: LOGIN_PATH,\n    children: [\n      {\n        name: 'Login',\n        path: 'login',\n        component: () => import('#/views/_core/authentication/login.vue'),\n        meta: {\n          title: $t('page.auth.login'),\n        },\n      },\n      {\n        name: 'CodeLogin',\n        path: 'code-login',\n        component: () => import('#/views/_core/authentication/code-login.vue'),\n        meta: {\n          title: $t('page.auth.codeLogin'),\n        },\n      },\n      {\n        name: 'QrCodeLogin',\n        path: 'qrcode-login',\n        component: () =>\n          import('#/views/_core/authentication/qrcode-login.vue'),\n        meta: {\n          title: $t('page.auth.qrcodeLogin'),\n        },\n      },\n      {\n        name: 'ForgetPassword',\n        path: 'forget-password',\n        component: () =>\n          import('#/views/_core/authentication/forget-password.vue'),\n        meta: {\n          title: $t('page.auth.forgetPassword'),\n        },\n      },\n      {\n        name: 'Register',\n        path: 'register',\n        component: () => import('#/views/_core/authentication/register.vue'),\n        meta: {\n          title: $t('page.auth.register'),\n        },\n      },\n    ],\n  },\n];\n\nexport { coreRoutes, fallbackNotFoundRoute };\n"
  },
  {
    "path": "playground/src/router/routes/index.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { mergeRouteModules, traverseTreeValues } from '@vben/utils';\n\nimport { coreRoutes, fallbackNotFoundRoute } from './core';\n\nconst dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {\n  eager: true,\n});\n\n// 有需要可以自行打开注释，并创建文件夹\n// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });\n// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });\n\n/** 动态路由 */\nconst dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);\n\n/** 外部路由列表，访问这些页面可以不需要Layout，可能用于内嵌在别的系统(不会显示在菜单中) */\n// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);\n// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);\nconst staticRoutes: RouteRecordRaw[] = [];\nconst externalRoutes: RouteRecordRaw[] = [];\n\n/** 路由列表，由基本路由、外部路由和404兜底路由组成\n *  无需走权限验证（会一直显示在菜单中） */\nconst routes: RouteRecordRaw[] = [\n  ...coreRoutes,\n  ...externalRoutes,\n  fallbackNotFoundRoute,\n];\n\n/** 基本路由列表，这些路由不需要进入权限拦截 */\nconst coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);\n\n/** 有权限校验的路由列表，包含动态路由和静态路由 */\nconst accessRoutes = [...dynamicRoutes, ...staticRoutes];\n\nconst componentKeys: string[] = Object.keys(\n  import.meta.glob('../../views/**/*.vue'),\n)\n  .filter((item) => !item.includes('/modules/'))\n  .map((v) => {\n    const path = v.replace('../../views/', '/');\n    return path.endsWith('.vue') ? path.slice(0, -4) : path;\n  });\n\nexport { accessRoutes, componentKeys, coreRouteNames, routes };\n"
  },
  {
    "path": "playground/src/router/routes/modules/dashboard.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'lucide:layout-dashboard',\n      order: -1,\n      title: $t('page.dashboard.title'),\n    },\n    name: 'Dashboard',\n    path: '/dashboard',\n    children: [\n      {\n        name: 'Analytics',\n        path: '/analytics',\n        component: () => import('#/views/dashboard/analytics/index.vue'),\n        meta: {\n          affixTab: true,\n          icon: 'lucide:area-chart',\n          title: $t('page.dashboard.analytics'),\n          keepAlive: true,\n        },\n      },\n      {\n        name: 'Workspace',\n        path: '/workspace',\n        component: () => import('#/views/dashboard/workspace/index.vue'),\n        meta: {\n          icon: 'carbon:workspace',\n          title: $t('page.dashboard.workspace'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "playground/src/router/routes/modules/demos.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'ic:baseline-view-in-ar',\n      keepAlive: true,\n      order: 1000,\n      title: $t('demos.title'),\n    },\n    name: 'Demos',\n    path: '/demos',\n    children: [\n      // 权限控制\n      {\n        meta: {\n          icon: 'mdi:shield-key-outline',\n          title: $t('demos.access.frontendPermissions'),\n        },\n        name: 'AccessDemos',\n        path: '/demos/access',\n        children: [\n          {\n            name: 'AccessPageControlDemo',\n            path: '/demos/access/page-control',\n            component: () => import('#/views/demos/access/index.vue'),\n            meta: {\n              icon: 'mdi:page-previous-outline',\n              title: $t('demos.access.pageAccess'),\n              domCached: true,\n            },\n          },\n          {\n            name: 'AccessButtonControlDemo',\n            path: '/demos/access/button-control',\n            component: () => import('#/views/demos/access/button-control.vue'),\n            meta: {\n              icon: 'mdi:button-cursor',\n              title: $t('demos.access.buttonControl'),\n            },\n          },\n          {\n            name: 'AccessMenuVisible403Demo',\n            path: '/demos/access/menu-visible-403',\n            component: () =>\n              import('#/views/demos/access/menu-visible-403.vue'),\n            meta: {\n              authority: ['no-body'],\n              icon: 'mdi:button-cursor',\n              menuVisibleWithForbidden: true,\n              title: $t('demos.access.menuVisible403'),\n            },\n          },\n          {\n            name: 'AccessSuperVisibleDemo',\n            path: '/demos/access/super-visible',\n            component: () => import('#/views/demos/access/super-visible.vue'),\n            meta: {\n              authority: ['super'],\n              icon: 'mdi:button-cursor',\n              title: $t('demos.access.superVisible'),\n            },\n          },\n          {\n            name: 'AccessAdminVisibleDemo',\n            path: '/demos/access/admin-visible',\n            component: () => import('#/views/demos/access/admin-visible.vue'),\n            meta: {\n              authority: ['admin'],\n              icon: 'mdi:button-cursor',\n              title: $t('demos.access.adminVisible'),\n            },\n          },\n          {\n            name: 'AccessUserVisibleDemo',\n            path: '/demos/access/user-visible',\n            component: () => import('#/views/demos/access/user-visible.vue'),\n            meta: {\n              authority: ['user'],\n              icon: 'mdi:button-cursor',\n              title: $t('demos.access.userVisible'),\n            },\n          },\n        ],\n      },\n      // 功能\n      {\n        meta: {\n          icon: 'mdi:feature-highlight',\n          title: $t('demos.features.title'),\n        },\n        name: 'FeaturesDemos',\n        path: '/demos/features',\n        children: [\n          {\n            name: 'LoginExpiredDemo',\n            path: '/demos/features/login-expired',\n            component: () =>\n              import('#/views/demos/features/login-expired/index.vue'),\n            meta: {\n              icon: 'mdi:encryption-expiration',\n              title: $t('demos.features.loginExpired'),\n            },\n          },\n          {\n            name: 'IconsDemo',\n            path: '/demos/features/icons',\n            component: () => import('#/views/demos/features/icons/index.vue'),\n            meta: {\n              icon: 'lucide:annoyed',\n              title: $t('demos.features.icons'),\n            },\n          },\n          {\n            name: 'WatermarkDemo',\n            path: '/demos/features/watermark',\n            component: () =>\n              import('#/views/demos/features/watermark/index.vue'),\n            meta: {\n              icon: 'lucide:tags',\n              title: $t('demos.features.watermark'),\n            },\n          },\n          {\n            name: 'FeatureTabsDemo',\n            path: '/demos/features/tabs',\n            component: () => import('#/views/demos/features/tabs/index.vue'),\n            meta: {\n              icon: 'lucide:app-window',\n              title: $t('demos.features.tabs'),\n            },\n          },\n          {\n            name: 'FeatureTabDetailDemo',\n            path: '/demos/features/tabs/detail/:id',\n            component: () =>\n              import('#/views/demos/features/tabs/tab-detail.vue'),\n            meta: {\n              activePath: '/demos/features/tabs',\n              hideInMenu: true,\n              maxNumOfOpenTab: 3,\n              title: $t('demos.features.tabDetail'),\n            },\n          },\n          {\n            name: 'HideChildrenInMenuParentDemo',\n            path: '/demos/features/hide-menu-children',\n            meta: {\n              hideChildrenInMenu: true,\n              icon: 'ic:round-menu',\n              title: $t('demos.features.hideChildrenInMenu'),\n            },\n            children: [\n              {\n                name: 'HideChildrenInMenuDemo',\n                path: '',\n                component: () =>\n                  import('#/views/demos/features/hide-menu-children/parent.vue'),\n                meta: {\n                  // hideInMenu: true,\n                  title: $t('demos.features.hideChildrenInMenu'),\n                },\n              },\n              {\n                name: 'HideChildrenInMenuChildrenDemo',\n                path: '/demos/features/hide-menu-children/children',\n                component: () =>\n                  import('#/views/demos/features/hide-menu-children/children.vue'),\n                meta: {\n                  activePath: '/demos/features/hide-menu-children',\n                  title: $t('demos.features.hideChildrenInMenu'),\n                },\n              },\n            ],\n          },\n          {\n            name: 'FullScreenDemo',\n            path: '/demos/features/full-screen',\n            component: () =>\n              import('#/views/demos/features/full-screen/index.vue'),\n            meta: {\n              icon: 'lucide:fullscreen',\n              title: $t('demos.features.fullScreen'),\n            },\n          },\n          {\n            name: 'FileDownloadDemo',\n            path: '/demos/features/file-download',\n            component: () =>\n              import('#/views/demos/features/file-download/index.vue'),\n            meta: {\n              icon: 'lucide:hard-drive-download',\n              title: $t('demos.features.fileDownload'),\n            },\n          },\n          {\n            name: 'ClipboardDemo',\n            path: '/demos/features/clipboard',\n            component: () =>\n              import('#/views/demos/features/clipboard/index.vue'),\n            meta: {\n              icon: 'lucide:copy',\n              title: $t('demos.features.clipboard'),\n            },\n          },\n          {\n            name: 'MenuQueryDemo',\n            path: '/demos/menu-query',\n            component: () =>\n              import('#/views/demos/features/menu-query/index.vue'),\n            meta: {\n              icon: 'lucide:curly-braces',\n              query: {\n                id: 1,\n              },\n              title: $t('demos.features.menuWithQuery'),\n            },\n          },\n          {\n            name: 'NewWindowDemo',\n            path: '/demos/new-window',\n            component: () =>\n              import('#/views/demos/features/new-window/index.vue'),\n            meta: {\n              icon: 'lucide:app-window',\n              openInNewWindow: true,\n              title: $t('demos.features.openInNewWindow'),\n            },\n          },\n          {\n            name: 'VueQueryDemo',\n            path: '/demos/features/vue-query',\n            component: () =>\n              import('#/views/demos/features/vue-query/index.vue'),\n            meta: {\n              icon: 'lucide:git-pull-request-arrow',\n              title: 'Tanstack Query',\n            },\n          },\n          {\n            name: 'RequestParamsSerializerDemo',\n            path: '/demos/features/request-params-serializer',\n            component: () =>\n              import('#/views/demos/features/request-params-serializer/index.vue'),\n            meta: {\n              icon: 'lucide:git-pull-request-arrow',\n              title: $t('demos.features.requestParamsSerializer'),\n            },\n          },\n          {\n            name: 'BigIntDemo',\n            path: '/demos/features/json-bigint',\n            component: () =>\n              import('#/views/demos/features/json-bigint/index.vue'),\n            meta: {\n              icon: 'lucide:grape',\n              title: 'JSON BigInt',\n            },\n          },\n        ],\n      },\n      // 面包屑导航\n      {\n        name: 'BreadcrumbDemos',\n        path: '/demos/breadcrumb',\n        meta: {\n          icon: 'lucide:navigation',\n          title: $t('demos.breadcrumb.navigation'),\n        },\n        children: [\n          {\n            name: 'BreadcrumbLateralDemo',\n            path: '/demos/breadcrumb/lateral',\n            component: () => import('#/views/demos/breadcrumb/lateral.vue'),\n            meta: {\n              icon: 'lucide:navigation',\n              title: $t('demos.breadcrumb.lateral'),\n            },\n          },\n          {\n            name: 'BreadcrumbLateralDetailDemo',\n            path: '/demos/breadcrumb/lateral-detail',\n            component: () =>\n              import('#/views/demos/breadcrumb/lateral-detail.vue'),\n            meta: {\n              activePath: '/demos/breadcrumb/lateral',\n              hideInMenu: true,\n              title: $t('demos.breadcrumb.lateralDetail'),\n            },\n          },\n          {\n            name: 'BreadcrumbLevelDemo',\n            path: '/demos/breadcrumb/level',\n            meta: {\n              icon: 'lucide:navigation',\n              title: $t('demos.breadcrumb.level'),\n            },\n            children: [\n              {\n                name: 'BreadcrumbLevelDetailDemo',\n                path: '/demos/breadcrumb/level/detail',\n                component: () =>\n                  import('#/views/demos/breadcrumb/level-detail.vue'),\n                meta: {\n                  title: $t('demos.breadcrumb.levelDetail'),\n                },\n              },\n            ],\n          },\n        ],\n      },\n      // 缺省页\n      {\n        meta: {\n          icon: 'mdi:lightbulb-error-outline',\n          title: $t('demos.fallback.title'),\n        },\n        name: 'FallbackDemos',\n        path: '/demos/fallback',\n        children: [\n          {\n            name: 'Fallback403Demo',\n            path: '/demos/fallback/403',\n            component: () => import('#/views/_core/fallback/forbidden.vue'),\n            meta: {\n              icon: 'mdi:do-not-disturb-alt',\n              title: '403',\n            },\n          },\n          {\n            name: 'Fallback404Demo',\n            path: '/demos/fallback/404',\n            component: () => import('#/views/_core/fallback/not-found.vue'),\n            meta: {\n              icon: 'mdi:table-off',\n              title: '404',\n            },\n          },\n          {\n            name: 'Fallback500Demo',\n            path: '/demos/fallback/500',\n            component: () =>\n              import('#/views/_core/fallback/internal-error.vue'),\n            meta: {\n              icon: 'mdi:server-network-off',\n              title: '500',\n            },\n          },\n          {\n            name: 'FallbackOfflineDemo',\n            path: '/demos/fallback/offline',\n            component: () => import('#/views/_core/fallback/offline.vue'),\n            meta: {\n              icon: 'mdi:offline',\n              title: $t('ui.fallback.offline'),\n            },\n          },\n        ],\n      },\n      // 菜单徽标\n      {\n        meta: {\n          badgeType: 'dot',\n          badgeVariants: 'destructive',\n          icon: 'lucide:circle-dot',\n          title: $t('demos.badge.title'),\n        },\n        name: 'BadgeDemos',\n        path: '/demos/badge',\n        children: [\n          {\n            name: 'BadgeDotDemo',\n            component: () => import('#/views/demos/badge/index.vue'),\n            path: '/demos/badge/dot',\n            meta: {\n              badgeType: 'dot',\n              icon: 'lucide:square-dot',\n              title: $t('demos.badge.dot'),\n            },\n          },\n          {\n            name: 'BadgeTextDemo',\n            component: () => import('#/views/demos/badge/index.vue'),\n            path: '/demos/badge/text',\n            meta: {\n              badge: '10',\n              icon: 'lucide:square-dot',\n              title: $t('demos.badge.text'),\n            },\n          },\n          {\n            name: 'BadgeColorDemo',\n            component: () => import('#/views/demos/badge/index.vue'),\n            path: '/demos/badge/color',\n            meta: {\n              badge: 'Hot',\n              badgeVariants: 'destructive',\n              icon: 'lucide:square-dot',\n              title: $t('demos.badge.color'),\n            },\n          },\n        ],\n      },\n      // 菜单激活图标\n      {\n        meta: {\n          activeIcon: 'fluent-emoji:radioactive',\n          icon: 'bi:radioactive',\n          title: $t('demos.activeIcon.title'),\n        },\n        name: 'ActiveIconDemos',\n        path: '/demos/active-icon',\n        children: [\n          {\n            name: 'ActiveIconDemo',\n            component: () => import('#/views/demos/active-icon/index.vue'),\n            path: '/demos/active-icon/children',\n            meta: {\n              activeIcon: 'fluent-emoji:radioactive',\n              icon: 'bi:radioactive',\n              title: $t('demos.activeIcon.children'),\n            },\n          },\n        ],\n      },\n      // 外部链接\n      {\n        meta: {\n          icon: 'ic:round-settings-input-composite',\n          title: $t('demos.outside.title'),\n        },\n        name: 'OutsideDemos',\n        path: '/demos/outside',\n        children: [\n          {\n            name: 'IframeDemos',\n            path: '/demos/outside/iframe',\n            meta: {\n              icon: 'mdi:newspaper-variant-outline',\n              title: $t('demos.outside.embedded'),\n            },\n            children: [\n              {\n                name: 'VueDocumentDemo',\n                path: '/demos/outside/iframe/vue-document',\n                component: IFrameView,\n                meta: {\n                  icon: 'logos:vue',\n                  iframeSrc: 'https://cn.vuejs.org/',\n                  keepAlive: true,\n                  title: 'Vue',\n                },\n              },\n              {\n                name: 'TailwindcssDemo',\n                path: '/demos/outside/iframe/tailwindcss',\n                component: IFrameView,\n                meta: {\n                  icon: 'devicon:tailwindcss',\n                  iframeSrc: 'https://tailwindcss.com/',\n                  // keepAlive: true,\n                  title: 'Tailwindcss',\n                },\n              },\n            ],\n          },\n          {\n            name: 'ExternalLinkDemos',\n            path: '/demos/outside/external-link',\n            meta: {\n              icon: 'mdi:newspaper-variant-multiple-outline',\n              title: $t('demos.outside.externalLink'),\n            },\n            children: [\n              {\n                name: 'ViteDemo',\n                path: '/demos/outside/external-link/vite',\n                component: IFrameView,\n                meta: {\n                  icon: 'logos:vitejs',\n                  link: 'https://vitejs.dev/',\n                  title: 'Vite',\n                },\n              },\n              {\n                name: 'VueUseDemo',\n                path: '/demos/outside/external-link/vue-use',\n                component: IFrameView,\n                meta: {\n                  icon: 'logos:vueuse',\n                  link: 'https://vueuse.org',\n                  title: 'VueUse',\n                },\n              },\n            ],\n          },\n        ],\n      },\n      // 嵌套菜单\n      {\n        meta: {\n          icon: 'ic:round-menu',\n          title: $t('demos.nested.title'),\n        },\n        name: 'NestedDemos',\n        path: '/demos/nested',\n        children: [\n          {\n            name: 'Menu1Demo',\n            path: '/demos/nested/menu1',\n            component: () => import('#/views/demos/nested/menu-1.vue'),\n            meta: {\n              icon: 'ic:round-menu',\n              keepAlive: true,\n              title: $t('demos.nested.menu1'),\n            },\n          },\n          {\n            name: 'Menu2Demo',\n            path: '/demos/nested/menu2',\n            meta: {\n              icon: 'ic:round-menu',\n              keepAlive: true,\n              title: $t('demos.nested.menu2'),\n            },\n            children: [\n              {\n                name: 'Menu21Demo',\n                path: '/demos/nested/menu2/menu2-1',\n                component: () => import('#/views/demos/nested/menu-2-1.vue'),\n                meta: {\n                  icon: 'ic:round-menu',\n                  keepAlive: true,\n                  title: $t('demos.nested.menu2_1'),\n                },\n              },\n            ],\n          },\n          {\n            name: 'Menu3Demo',\n            path: '/demos/nested/menu3',\n            meta: {\n              icon: 'ic:round-menu',\n              title: $t('demos.nested.menu3'),\n            },\n            children: [\n              {\n                name: 'Menu31Demo',\n                path: '/demos/nested/menu3/menu3-1',\n                component: () => import('#/views/demos/nested/menu-3-1.vue'),\n                meta: {\n                  icon: 'ic:round-menu',\n                  keepAlive: true,\n                  title: $t('demos.nested.menu3_1'),\n                },\n              },\n              {\n                name: 'Menu32Demo',\n                path: '/demos/nested/menu3/menu3-2',\n                meta: {\n                  icon: 'ic:round-menu',\n                  title: $t('demos.nested.menu3_2'),\n                },\n                children: [\n                  {\n                    name: 'Menu321Demo',\n                    path: '/demos/nested/menu3/menu3-2/menu3-2-1',\n                    component: () =>\n                      import('#/views/demos/nested/menu-3-2-1.vue'),\n                    meta: {\n                      icon: 'ic:round-menu',\n                      keepAlive: true,\n                      title: $t('demos.nested.menu3_2_1'),\n                    },\n                  },\n                ],\n              },\n            ],\n          },\n        ],\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "playground/src/router/routes/modules/examples.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'ion:layers-outline',\n      keepAlive: true,\n      order: 1000,\n      title: $t('examples.title'),\n    },\n    name: 'Examples',\n    path: '/examples',\n    children: [\n      {\n        name: 'FormExample',\n        path: '/examples/form',\n        meta: {\n          icon: 'mdi:form-select',\n          title: $t('examples.form.title'),\n        },\n        children: [\n          {\n            name: 'FormBasicExample',\n            path: '/examples/form/basic',\n            component: () => import('#/views/examples/form/basic.vue'),\n            meta: {\n              title: $t('examples.form.basic'),\n            },\n          },\n          {\n            name: 'FormQueryExample',\n            path: '/examples/form/query',\n            component: () => import('#/views/examples/form/query.vue'),\n            meta: {\n              title: $t('examples.form.query'),\n            },\n          },\n          {\n            name: 'FormRulesExample',\n            path: '/examples/form/rules',\n            component: () => import('#/views/examples/form/rules.vue'),\n            meta: {\n              title: $t('examples.form.rules'),\n            },\n          },\n          {\n            name: 'FormDynamicExample',\n            path: '/examples/form/dynamic',\n            component: () => import('#/views/examples/form/dynamic.vue'),\n            meta: {\n              title: $t('examples.form.dynamic'),\n            },\n          },\n          {\n            name: 'FormLayoutExample',\n            path: '/examples/form/custom-layout',\n            component: () => import('#/views/examples/form/custom-layout.vue'),\n            meta: {\n              title: $t('examples.form.layout'),\n            },\n          },\n          {\n            name: 'FormCustomExample',\n            path: '/examples/form/custom',\n            component: () => import('#/views/examples/form/custom.vue'),\n            meta: {\n              title: $t('examples.form.custom'),\n            },\n          },\n          {\n            name: 'FormApiExample',\n            path: '/examples/form/api',\n            component: () => import('#/views/examples/form/api.vue'),\n            meta: {\n              title: $t('examples.form.api'),\n            },\n          },\n          {\n            name: 'FormMergeExample',\n            path: '/examples/form/merge',\n            component: () => import('#/views/examples/form/merge.vue'),\n            meta: {\n              title: $t('examples.form.merge'),\n            },\n          },\n          {\n            name: 'FormScrollToErrorExample',\n            path: '/examples/form/scroll-to-error-test',\n            component: () =>\n              import('#/views/examples/form/scroll-to-error-test.vue'),\n            meta: {\n              title: $t('examples.form.scrollToError'),\n            },\n          },\n        ],\n      },\n      {\n        name: 'VxeTableExample',\n        path: '/examples/vxe-table',\n        meta: {\n          icon: 'lucide:table',\n          title: $t('examples.vxeTable.title'),\n        },\n        children: [\n          {\n            name: 'VxeTableBasicExample',\n            path: '/examples/vxe-table/basic',\n            component: () => import('#/views/examples/vxe-table/basic.vue'),\n            meta: {\n              title: $t('examples.vxeTable.basic'),\n            },\n          },\n          {\n            name: 'VxeTableRemoteExample',\n            path: '/examples/vxe-table/remote',\n            component: () => import('#/views/examples/vxe-table/remote.vue'),\n            meta: {\n              title: $t('examples.vxeTable.remote'),\n            },\n          },\n          {\n            name: 'VxeTableTreeExample',\n            path: '/examples/vxe-table/tree',\n            component: () => import('#/views/examples/vxe-table/tree.vue'),\n            meta: {\n              title: $t('examples.vxeTable.tree'),\n            },\n          },\n          {\n            name: 'VxeTableFixedExample',\n            path: '/examples/vxe-table/fixed',\n            component: () => import('#/views/examples/vxe-table/fixed.vue'),\n            meta: {\n              title: $t('examples.vxeTable.fixed'),\n            },\n          },\n          {\n            name: 'VxeTableCustomCellExample',\n            path: '/examples/vxe-table/custom-cell',\n            component: () =>\n              import('#/views/examples/vxe-table/custom-cell.vue'),\n            meta: {\n              title: $t('examples.vxeTable.custom-cell'),\n            },\n          },\n          {\n            name: 'VxeTableFormExample',\n            path: '/examples/vxe-table/form',\n            component: () => import('#/views/examples/vxe-table/form.vue'),\n            meta: {\n              title: $t('examples.vxeTable.form'),\n            },\n          },\n          {\n            name: 'VxeTableEditCellExample',\n            path: '/examples/vxe-table/edit-cell',\n            component: () => import('#/views/examples/vxe-table/edit-cell.vue'),\n            meta: {\n              title: $t('examples.vxeTable.editCell'),\n            },\n          },\n          {\n            name: 'VxeTableEditRowExample',\n            path: '/examples/vxe-table/edit-row',\n            component: () => import('#/views/examples/vxe-table/edit-row.vue'),\n            meta: {\n              title: $t('examples.vxeTable.editRow'),\n            },\n          },\n          {\n            name: 'VxeTableVirtualExample',\n            path: '/examples/vxe-table/virtual',\n            component: () => import('#/views/examples/vxe-table/virtual.vue'),\n            meta: {\n              title: $t('examples.vxeTable.virtual'),\n            },\n          },\n        ],\n      },\n      {\n        name: 'CaptchaExample',\n        path: '/examples/captcha',\n        meta: {\n          icon: 'logos:recaptcha',\n          title: $t('examples.captcha.title'),\n        },\n        children: [\n          {\n            name: 'DragVerifyExample',\n            path: '/examples/captcha/slider',\n            component: () =>\n              import('#/views/examples/captcha/slider-captcha.vue'),\n            meta: {\n              title: $t('examples.captcha.sliderCaptcha'),\n            },\n          },\n          {\n            name: 'RotateVerifyExample',\n            path: '/examples/captcha/slider-rotate',\n            component: () =>\n              import('#/views/examples/captcha/slider-rotate-captcha.vue'),\n            meta: {\n              title: $t('examples.captcha.sliderRotateCaptcha'),\n            },\n          },\n          {\n            name: 'TranslateVerifyExample',\n            path: '/examples/captcha/slider-translate',\n            component: () =>\n              import('#/views/examples/captcha/slider-translate-captcha.vue'),\n            meta: {\n              title: $t('examples.captcha.sliderTranslateCaptcha'),\n            },\n          },\n          {\n            name: 'CaptchaPointSelectionExample',\n            path: '/examples/captcha/point-selection',\n            component: () =>\n              import('#/views/examples/captcha/point-selection-captcha.vue'),\n            meta: {\n              title: $t('examples.captcha.pointSelection'),\n            },\n          },\n        ],\n      },\n      {\n        name: 'ModalExample',\n        path: '/examples/modal',\n        component: () => import('#/views/examples/modal/index.vue'),\n        meta: {\n          icon: 'system-uicons:window-content',\n          keepAlive: true,\n          title: $t('examples.modal.title'),\n        },\n      },\n      {\n        name: 'DrawerExample',\n        path: '/examples/drawer',\n        component: () => import('#/views/examples/drawer/index.vue'),\n        meta: {\n          icon: 'iconoir:drawer',\n          keepAlive: true,\n          title: $t('examples.drawer.title'),\n        },\n      },\n      {\n        name: 'EllipsisExample',\n        path: '/examples/ellipsis',\n        component: () => import('#/views/examples/ellipsis/index.vue'),\n        meta: {\n          icon: 'ion:ellipsis-horizontal',\n          title: $t('examples.ellipsis.title'),\n        },\n      },\n      {\n        name: 'VueResizeDemo',\n        path: '/demos/resize/basic',\n        component: () => import('#/views/examples/resize/basic.vue'),\n        meta: {\n          icon: 'material-symbols:resize',\n          title: $t('examples.resize.title'),\n        },\n      },\n      {\n        name: 'ColPageDemo',\n        path: '/examples/layout/col-page',\n        component: () => import('#/views/examples/layout/col-page.vue'),\n        meta: {\n          badge: 'Alpha',\n          badgeVariants: 'destructive',\n          icon: 'material-symbols:horizontal-distribute',\n          title: $t('examples.layout.col-page'),\n        },\n      },\n      {\n        name: 'TippyDemo',\n        path: '/examples/tippy',\n        component: () => import('#/views/examples/tippy/index.vue'),\n        meta: {\n          icon: 'mdi:message-settings-outline',\n          title: 'Tippy',\n        },\n      },\n      {\n        name: 'JsonViewer',\n        path: '/examples/json-viewer',\n        component: () => import('#/views/examples/json-viewer/index.vue'),\n        meta: {\n          icon: 'tabler:json',\n          title: 'JsonViewer',\n        },\n      },\n      {\n        name: 'Motion',\n        path: '/examples/motion',\n        component: () => import('#/views/examples/motion/index.vue'),\n        meta: {\n          icon: 'mdi:animation-play',\n          title: 'Motion',\n        },\n      },\n      {\n        name: 'CountTo',\n        path: '/examples/count-to',\n        component: () => import('#/views/examples/count-to/index.vue'),\n        meta: {\n          icon: 'mdi:animation-play',\n          title: 'CountTo',\n        },\n      },\n      {\n        name: 'Loading',\n        path: '/examples/loading',\n        component: () => import('#/views/examples/loading/index.vue'),\n        meta: {\n          icon: 'mdi:circle-double',\n          title: 'Loading',\n        },\n      },\n      {\n        name: 'ButtonGroup',\n        path: '/examples/button-group',\n        component: () => import('#/views/examples/button-group/index.vue'),\n        meta: {\n          icon: 'mdi:check-circle',\n          title: $t('examples.button-group.title'),\n        },\n      },\n      {\n        name: 'ContextMenu',\n        path: '/examples/context-menu',\n        component: () => import('#/views/examples/context-menu/index.vue'),\n        meta: {\n          icon: 'mdi:menu',\n          title: $t('examples.function.contentMenu'),\n        },\n      },\n      {\n        name: 'CropperDemo',\n        path: '/examples/cropper',\n        component: () => import('#/views/examples/cropper/index.vue'),\n        meta: {\n          icon: 'mdi:crop',\n          title: $t('examples.cropper.title'),\n        },\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "playground/src/router/routes/modules/system.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      icon: 'ion:settings-outline',\n      order: 9997,\n      title: $t('system.title'),\n    },\n    name: 'System',\n    path: '/system',\n    children: [\n      {\n        path: '/system/role',\n        name: 'SystemRole',\n        meta: {\n          icon: 'mdi:account-group',\n          title: $t('system.role.title'),\n        },\n        component: () => import('#/views/system/role/list.vue'),\n      },\n      {\n        path: '/system/menu',\n        name: 'SystemMenu',\n        meta: {\n          icon: 'mdi:menu',\n          title: $t('system.menu.title'),\n        },\n        component: () => import('#/views/system/menu/list.vue'),\n      },\n      {\n        path: '/system/dept',\n        name: 'SystemDept',\n        meta: {\n          icon: 'charm:organisation',\n          title: $t('system.dept.title'),\n        },\n        component: () => import('#/views/system/dept/list.vue'),\n      },\n    ],\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "playground/src/router/routes/modules/vben.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\nimport {\n  VBEN_ANT_PREVIEW_URL,\n  VBEN_ANTDV_NEXT_PREVIEW_URL,\n  VBEN_DOC_URL,\n  VBEN_ELE_PREVIEW_URL,\n  VBEN_GITHUB_URL,\n  VBEN_LOGO_URL,\n  VBEN_NAIVE_PREVIEW_URL,\n  VBEN_TD_PREVIEW_URL,\n} from '@vben/constants';\nimport {\n  SvgAntdvLogoIcon,\n  SvgAntdvNextLogoIcon,\n  SvgTDesignIcon,\n} from '@vben/icons';\n\nimport { IFrameView } from '#/layouts';\nimport { $t } from '#/locales';\n\nconst routes: RouteRecordRaw[] = [\n  {\n    meta: {\n      badgeType: 'dot',\n      icon: VBEN_LOGO_URL,\n      order: 9998,\n      title: $t('demos.vben.title'),\n    },\n    name: 'VbenProject',\n    path: '/vben-admin',\n    children: [\n      {\n        name: 'VbenDocument',\n        path: '/vben-admin/document',\n        component: IFrameView,\n        meta: {\n          icon: 'lucide:book-open-text',\n          link: VBEN_DOC_URL,\n          title: $t('demos.vben.document'),\n        },\n      },\n      {\n        name: 'VbenGithub',\n        path: '/vben-admin/github',\n        component: IFrameView,\n        meta: {\n          icon: 'mdi:github',\n          link: VBEN_GITHUB_URL,\n          title: 'Github',\n        },\n      },\n      {\n        name: 'VbenAntdv',\n        path: '/vben-admin/antdv',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgAntdvLogoIcon,\n          link: VBEN_ANT_PREVIEW_URL,\n          title: $t('demos.vben.antdv'),\n        },\n      },\n      {\n        name: 'VbenAntdVNext',\n        path: '/vben-admin/antdv-next',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgAntdvNextLogoIcon,\n          link: VBEN_ANTDV_NEXT_PREVIEW_URL,\n          title: $t('demos.vben.antdv-next'),\n        },\n      },\n\n      {\n        name: 'VbenNaive',\n        path: '/vben-admin/naive',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: 'logos:naiveui',\n          link: VBEN_NAIVE_PREVIEW_URL,\n          title: $t('demos.vben.naive-ui'),\n        },\n      },\n      {\n        name: 'VbenElementPlus',\n        path: '/vben-admin/ele',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: 'logos:element',\n          link: VBEN_ELE_PREVIEW_URL,\n          title: $t('demos.vben.element-plus'),\n        },\n      },\n      {\n        name: 'VbenTDesign',\n        path: '/vben-admin/tdesign',\n        component: IFrameView,\n        meta: {\n          badgeType: 'dot',\n          icon: SvgTDesignIcon,\n          link: VBEN_TD_PREVIEW_URL,\n          title: $t('demos.vben.tdesign'),\n        },\n      },\n    ],\n  },\n  {\n    component: () => import('#/views/_core/about/index.vue'),\n    meta: {\n      icon: 'lucide:copyright',\n      order: 9999,\n      title: $t('demos.vben.about'),\n    },\n    name: 'VbenAbout',\n    path: '/vben-admin/about',\n  },\n  {\n    name: 'Profile',\n    path: '/profile',\n    component: () => import('#/views/_core/profile/index.vue'),\n    meta: {\n      icon: 'lucide:user',\n      hideInMenu: true,\n      title: $t('page.auth.profile'),\n    },\n  },\n];\n\nexport default routes;\n"
  },
  {
    "path": "playground/src/store/auth.ts",
    "content": "import type { Recordable, UserInfo } from '@vben/types';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { LOGIN_PATH } from '@vben/constants';\nimport { preferences } from '@vben/preferences';\nimport { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';\n\nimport { notification } from 'ant-design-vue';\nimport { defineStore } from 'pinia';\n\nimport { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';\nimport { $t } from '#/locales';\n\nexport const useAuthStore = defineStore('auth', () => {\n  const accessStore = useAccessStore();\n  const userStore = useUserStore();\n  const router = useRouter();\n\n  const loginLoading = ref(false);\n\n  /**\n   * 异步处理登录操作\n   * Asynchronously handle the login process\n   * @param params 登录表单数据\n   * @param onSuccess 成功之后的回调函数\n   */\n  async function authLogin(\n    params: Recordable<any>,\n    onSuccess?: () => Promise<void> | void,\n  ) {\n    // 异步处理用户登录操作并获取 accessToken\n    let userInfo: null | UserInfo = null;\n    try {\n      loginLoading.value = true;\n      const { accessToken } = await loginApi(params);\n\n      // 如果成功获取到 accessToken\n      if (accessToken) {\n        accessStore.setAccessToken(accessToken);\n\n        // 获取用户信息并存储到 accessStore 中\n        const [fetchUserInfoResult, accessCodes] = await Promise.all([\n          fetchUserInfo(),\n          getAccessCodesApi(),\n        ]);\n\n        userInfo = fetchUserInfoResult;\n\n        userStore.setUserInfo(userInfo);\n        accessStore.setAccessCodes(accessCodes);\n\n        if (accessStore.loginExpired) {\n          accessStore.setLoginExpired(false);\n        } else {\n          onSuccess\n            ? await onSuccess?.()\n            : await router.push(\n                userInfo.homePath || preferences.app.defaultHomePath,\n              );\n        }\n\n        if (userInfo?.realName) {\n          notification.success({\n            description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,\n            duration: 3,\n            message: $t('authentication.loginSuccess'),\n          });\n        }\n      }\n    } finally {\n      loginLoading.value = false;\n    }\n\n    return {\n      userInfo,\n    };\n  }\n\n  const isLoggingOut = ref(false); // 正在 logout 标识, 防止 /logout 死循环.\n\n  async function logout(redirect: boolean = true) {\n    if (isLoggingOut.value) return; // 正在登出中, 说明已进入循环, 直接返回.\n    isLoggingOut.value = true; // 设置 标识\n\n    try {\n      await logoutApi();\n    } catch {\n      // 不做任何处理\n    } finally {\n      isLoggingOut.value = false; // 重置 标识\n\n      resetAllStores();\n      accessStore.setLoginExpired(false);\n    }\n\n    // 回登录页带上当前路由地址\n    await router.replace({\n      path: LOGIN_PATH,\n      query: redirect\n        ? {\n            redirect: encodeURIComponent(router.currentRoute.value.fullPath),\n          }\n        : {},\n    });\n  }\n\n  async function fetchUserInfo() {\n    const userInfo = await getUserInfoApi();\n    userStore.setUserInfo(userInfo);\n    return userInfo;\n  }\n\n  function $reset() {\n    loginLoading.value = false;\n  }\n\n  return {\n    $reset,\n    authLogin,\n    fetchUserInfo,\n    loginLoading,\n    logout,\n  };\n});\n"
  },
  {
    "path": "playground/src/store/index.ts",
    "content": "export * from './auth';\n"
  },
  {
    "path": "playground/src/timezone-init.ts",
    "content": "import { setTimezoneHandler } from '@vben/stores';\n\nimport { getTimezoneApi, getTimezoneOptionsApi, setTimezoneApi } from '#/api';\n\n/**\n * 初始化时区处理，通过API保存时区设置\n */\nexport function initTimezone() {\n  setTimezoneHandler({\n    getTimezone() {\n      return getTimezoneApi();\n    },\n    setTimezone(timezone: string) {\n      return setTimezoneApi(timezone);\n    },\n    getTimezoneOptions() {\n      return getTimezoneOptionsApi();\n    },\n  });\n}\n"
  },
  {
    "path": "playground/src/views/_core/README.md",
    "content": "# \\_core\n\n此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。\n"
  },
  {
    "path": "playground/src/views/_core/about/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { About } from '@vben/common-ui';\n\ndefineOptions({ name: 'About' });\n</script>\n\n<template>\n  <About />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/authentication/code-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, ref, useTemplateRef } from 'vue';\n\nimport { AuthenticationCodeLogin, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { message } from 'ant-design-vue';\n\ndefineOptions({ name: 'CodeLogin' });\n\nconst loading = ref(false);\nconst CODE_LENGTH = 6;\nconst loginRef =\n  useTemplateRef<InstanceType<typeof AuthenticationCodeLogin>>('loginRef');\nfunction sendCodeApi(phoneNumber: string) {\n  message.loading({\n    content: $t('page.auth.sendingCode'),\n    duration: 0,\n    key: 'sending-code',\n  });\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      message.success({\n        content: $t('page.auth.codeSentTo', [phoneNumber]),\n        duration: 3,\n        key: 'sending-code',\n      });\n      resolve({ code: '123456', phoneNumber });\n    }, 3000);\n  });\n}\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.mobile'),\n      },\n      fieldName: 'phoneNumber',\n      label: $t('authentication.mobile'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.mobileTip') })\n        .refine((v) => /^\\d{11}$/.test(v), {\n          message: $t('authentication.mobileErrortip'),\n        }),\n    },\n    {\n      component: 'VbenPinInput',\n      componentProps: {\n        codeLength: CODE_LENGTH,\n        createText: (countdown: number) => {\n          const text =\n            countdown > 0\n              ? $t('authentication.sendText', [countdown])\n              : $t('authentication.sendCode');\n          return text;\n        },\n        handleSendCode: async () => {\n          // 模拟发送验证码\n          // Simulate sending verification code\n          loading.value = true;\n          const formApi = loginRef.value?.getFormApi();\n          if (!formApi) {\n            loading.value = false;\n            throw new Error('formApi is not ready');\n          }\n          await formApi.validateField('phoneNumber');\n          const isPhoneReady = await formApi.isFieldValid('phoneNumber');\n          if (!isPhoneReady) {\n            loading.value = false;\n            throw new Error('Phone number is not Ready');\n          }\n          const { phoneNumber } = await formApi.getValues();\n          await sendCodeApi(phoneNumber);\n          loading.value = false;\n        },\n        placeholder: $t('authentication.code'),\n      },\n      fieldName: 'code',\n      label: $t('authentication.code'),\n      rules: z.string().length(CODE_LENGTH, {\n        message: $t('authentication.codeTip', [CODE_LENGTH]),\n      }),\n    },\n  ];\n});\n/**\n * 异步处理登录操作\n * Asynchronously handle the login process\n * @param values 登录表单数据\n */\nasync function handleLogin(values: Recordable<any>) {\n  void values;\n}\n</script>\n\n<template>\n  <AuthenticationCodeLogin\n    ref=\"loginRef\"\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleLogin\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/authentication/forget-password.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\n\nimport { computed, ref } from 'vue';\n\nimport { AuthenticationForgetPassword, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'ForgetPassword' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: 'example@example.com',\n      },\n      fieldName: 'email',\n      label: $t('authentication.email'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.emailTip') })\n        .email($t('authentication.emailValidErrorTip')),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Record<string, any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationForgetPassword\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/authentication/login.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { BasicOption, Recordable } from '@vben/types';\n\nimport { computed, markRaw, useTemplateRef } from 'vue';\n\nimport { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\nimport { useAuthStore } from '#/store';\n\ndefineOptions({ name: 'Login' });\n\nconst authStore = useAuthStore();\n\nconst MOCK_USER_OPTIONS: BasicOption[] = [\n  {\n    label: 'Super',\n    value: 'vben',\n  },\n  {\n    label: 'Admin',\n    value: 'admin',\n  },\n  {\n    label: 'User',\n    value: 'jack',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenSelect',\n      // componentProps(_values, form) {\n      //   return {\n      //     'onUpdate:modelValue': (value: string) => {\n      //       const findItem = MOCK_USER_OPTIONS.find(\n      //         (item) => item.value === value,\n      //       );\n      //       if (findItem) {\n      //         form.setValues({\n      //           password: '123456',\n      //           username: findItem.label,\n      //         });\n      //       }\n      //     },\n      //     options: MOCK_USER_OPTIONS,\n      //     placeholder: $t('authentication.selectAccount'),\n      //   };\n      // },\n      componentProps: {\n        options: MOCK_USER_OPTIONS,\n        placeholder: $t('authentication.selectAccount'),\n      },\n      fieldName: 'selectAccount',\n      label: $t('authentication.selectAccount'),\n      rules: z\n        .string()\n        .min(1, { message: $t('authentication.selectAccount') })\n        .optional()\n        .default('vben'),\n    },\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      dependencies: {\n        trigger(values, form) {\n          if (values.selectAccount) {\n            const findUser = MOCK_USER_OPTIONS.find(\n              (item) => item.value === values.selectAccount,\n            );\n            if (findUser) {\n              form.setValues({\n                password: '123456',\n                username: findUser.value,\n              });\n            }\n          }\n        },\n        triggerFields: ['selectAccount'],\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: markRaw(SliderCaptcha),\n      fieldName: 'captcha',\n      rules: z.boolean().refine((value) => value, {\n        message: $t('authentication.verifyRequiredTip'),\n      }),\n    },\n  ];\n});\n\nconst loginRef =\n  useTemplateRef<InstanceType<typeof AuthenticationLogin>>('loginRef');\n\nasync function onSubmit(params: Recordable<any>) {\n  authStore.authLogin(params).catch(() => {\n    // 登陆失败，刷新验证码的演示\n    const formApi = loginRef.value?.getFormApi();\n    // 重置验证码组件的值\n    formApi?.setFieldValue('captcha', false, false);\n    // 使用表单API获取验证码组件实例，并调用其resume方法来重置验证码\n    formApi\n      ?.getFieldComponentRef<InstanceType<typeof SliderCaptcha>>('captcha')\n      ?.resume();\n  });\n}\n</script>\n\n<template>\n  <AuthenticationLogin\n    ref=\"loginRef\"\n    :form-schema=\"formSchema\"\n    :loading=\"authStore.loginLoading\"\n    @submit=\"onSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/authentication/qrcode-login.vue",
    "content": "<script lang=\"ts\" setup>\nimport { AuthenticationQrCodeLogin } from '@vben/common-ui';\nimport { LOGIN_PATH } from '@vben/constants';\n\ndefineOptions({ name: 'QrCodeLogin' });\n</script>\n\n<template>\n  <AuthenticationQrCodeLogin :login-path=\"LOGIN_PATH\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/authentication/register.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormSchema } from '@vben/common-ui';\nimport type { Recordable } from '@vben/types';\n\nimport { computed, h, ref } from 'vue';\n\nimport { AuthenticationRegister, z } from '@vben/common-ui';\nimport { $t } from '@vben/locales';\n\ndefineOptions({ name: 'Register' });\n\nconst loading = ref(false);\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      component: 'VbenInput',\n      componentProps: {\n        placeholder: $t('authentication.usernameTip'),\n      },\n      fieldName: 'username',\n      label: $t('authentication.username'),\n      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: $t('authentication.password'),\n      },\n      fieldName: 'password',\n      label: $t('authentication.password'),\n      renderComponentContent() {\n        return {\n          strengthText: () => $t('authentication.passwordStrength'),\n        };\n      },\n      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),\n    },\n    {\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: $t('authentication.confirmPassword'),\n      },\n      dependencies: {\n        rules(values) {\n          const { password } = values;\n          return z\n            .string({ required_error: $t('authentication.passwordTip') })\n            .min(1, { message: $t('authentication.passwordTip') })\n            .refine((value) => value === password, {\n              message: $t('authentication.confirmPasswordTip'),\n            });\n        },\n        triggerFields: ['password'],\n      },\n      fieldName: 'confirmPassword',\n      label: $t('authentication.confirmPassword'),\n    },\n    {\n      component: 'VbenCheckbox',\n      fieldName: 'agreePolicy',\n      renderComponentContent: () => ({\n        default: () =>\n          h('span', [\n            $t('authentication.agree'),\n            h(\n              'a',\n              {\n                class: 'vben-link ml-1 ',\n                href: '',\n              },\n              `${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,\n            ),\n          ]),\n      }),\n      rules: z.boolean().refine((value) => !!value, {\n        message: $t('authentication.agreeTip'),\n      }),\n    },\n  ];\n});\n\nfunction handleSubmit(value: Recordable<any>) {\n  void value;\n}\n</script>\n\n<template>\n  <AuthenticationRegister\n    :form-schema=\"formSchema\"\n    :loading=\"loading\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/fallback/coming-soon.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback status=\"coming-soon\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/fallback/forbidden.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback403Demo' });\n</script>\n\n<template>\n  <Fallback status=\"403\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/fallback/internal-error.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback500Demo' });\n</script>\n\n<template>\n  <Fallback status=\"500\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/fallback/not-found.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'Fallback404Demo' });\n</script>\n\n<template>\n  <Fallback status=\"404\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/fallback/offline.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n\ndefineOptions({ name: 'FallbackOfflineDemo' });\n</script>\n\n<template>\n  <Fallback status=\"offline\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/profile/base-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { BasicOption } from '@vben/types';\n\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed, onMounted, ref } from 'vue';\n\nimport { ProfileBaseSetting } from '@vben/common-ui';\n\nimport { getUserInfoApi } from '#/api';\n\nconst profileBaseSettingRef = ref();\n\nconst MOCK_ROLES_OPTIONS: BasicOption[] = [\n  {\n    label: '管理员',\n    value: 'super',\n  },\n  {\n    label: '用户',\n    value: 'user',\n  },\n  {\n    label: '测试',\n    value: 'test',\n  },\n];\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'realName',\n      component: 'Input',\n      label: '姓名',\n    },\n    {\n      fieldName: 'username',\n      component: 'Input',\n      label: '用户名',\n    },\n    {\n      fieldName: 'roles',\n      component: 'Select',\n      componentProps: {\n        mode: 'tags',\n        options: MOCK_ROLES_OPTIONS,\n      },\n      label: '角色',\n    },\n    {\n      fieldName: 'introduction',\n      component: 'Textarea',\n      label: '个人简介',\n    },\n  ];\n});\n\nonMounted(async () => {\n  const data = await getUserInfoApi();\n  profileBaseSettingRef.value.getFormApi().setValues(data);\n});\n</script>\n<template>\n  <ProfileBaseSetting ref=\"profileBaseSettingRef\" :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/profile/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\n\nimport { Profile } from '@vben/common-ui';\nimport { useUserStore } from '@vben/stores';\n\nimport ProfileBase from './base-setting.vue';\nimport ProfileNotificationSetting from './notification-setting.vue';\nimport ProfilePasswordSetting from './password-setting.vue';\nimport ProfileSecuritySetting from './security-setting.vue';\n\nconst userStore = useUserStore();\n\nconst tabsValue = ref<string>('basic');\n\nconst tabs = ref([\n  {\n    label: '基本设置',\n    value: 'basic',\n  },\n  {\n    label: '安全设置',\n    value: 'security',\n  },\n  {\n    label: '修改密码',\n    value: 'password',\n  },\n  {\n    label: '新消息提醒',\n    value: 'notice',\n  },\n]);\n</script>\n<template>\n  <Profile\n    v-model:model-value=\"tabsValue\"\n    title=\"个人中心\"\n    :user-info=\"userStore.userInfo\"\n    :tabs=\"tabs\"\n  >\n    <template #content>\n      <ProfileBase v-if=\"tabsValue === 'basic'\" />\n      <ProfileSecuritySetting v-if=\"tabsValue === 'security'\" />\n      <ProfilePasswordSetting v-if=\"tabsValue === 'password'\" />\n      <ProfileNotificationSetting v-if=\"tabsValue === 'notice'\" />\n    </template>\n  </Profile>\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/profile/notification-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileNotificationSetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '其他用户的消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'systemMessage',\n      label: '系统消息',\n      description: '系统消息将以站内信的形式通知',\n    },\n    {\n      value: true,\n      fieldName: 'todoTask',\n      label: '待办任务',\n      description: '待办任务将以站内信的形式通知',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileNotificationSetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/profile/password-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed } from 'vue';\n\nimport { ProfilePasswordSetting, z } from '@vben/common-ui';\n\nimport { message } from 'ant-design-vue';\n\nconst formSchema = computed((): VbenFormSchema[] => {\n  return [\n    {\n      fieldName: 'oldPassword',\n      label: '旧密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        placeholder: '请输入旧密码',\n      },\n    },\n    {\n      fieldName: 'newPassword',\n      label: '新密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请输入新密码',\n      },\n    },\n    {\n      fieldName: 'confirmPassword',\n      label: '确认密码',\n      component: 'VbenInputPassword',\n      componentProps: {\n        passwordStrength: true,\n        placeholder: '请再次输入新密码',\n      },\n      dependencies: {\n        rules(values) {\n          const { newPassword } = values;\n          return z\n            .string({ required_error: '请再次输入新密码' })\n            .min(1, { message: '请再次输入新密码' })\n            .refine((value) => value === newPassword, {\n              message: '两次输入的密码不一致',\n            });\n        },\n        triggerFields: ['newPassword'],\n      },\n    },\n  ];\n});\n\nfunction handleSubmit() {\n  message.success('密码修改成功');\n}\n</script>\n<template>\n  <ProfilePasswordSetting\n    class=\"w-1/3\"\n    :form-schema=\"formSchema\"\n    @submit=\"handleSubmit\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/_core/profile/security-setting.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { ProfileSecuritySetting } from '@vben/common-ui';\n\nconst formSchema = computed(() => {\n  return [\n    {\n      value: true,\n      fieldName: 'accountPassword',\n      label: '账户密码',\n      description: '当前密码强度：强',\n    },\n    {\n      value: true,\n      fieldName: 'securityPhone',\n      label: '密保手机',\n      description: '已绑定手机：138****8293',\n    },\n    {\n      value: true,\n      fieldName: 'securityQuestion',\n      label: '密保问题',\n      description: '未设置密保问题，密保问题可有效保护账户安全',\n    },\n    {\n      value: true,\n      fieldName: 'securityEmail',\n      label: '备用邮箱',\n      description: '已绑定邮箱：ant***sign.com',\n    },\n    {\n      value: false,\n      fieldName: 'securityMfa',\n      label: 'MFA 设备',\n      description: '未绑定 MFA 设备，绑定后，可以进行二次确认',\n    },\n  ];\n});\n</script>\n<template>\n  <ProfileSecuritySetting :form-schema=\"formSchema\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/dashboard/analytics/analytics-trends.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        areaStyle: {},\n        data: [\n          111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,\n          36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,\n          111,\n        ],\n        itemStyle: {\n          color: '#5ab1ef',\n        },\n        smooth: true,\n        type: 'line',\n      },\n      {\n        areaStyle: {},\n        data: [\n          33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,\n          11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,\n        ],\n        itemStyle: {\n          color: '#019680',\n        },\n        smooth: true,\n        type: 'line',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          color: '#019680',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    // xAxis: {\n    //   axisTick: {\n    //     show: false,\n    //   },\n    //   boundaryGap: false,\n    //   data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n    //   type: 'category',\n    // },\n    xAxis: {\n      axisTick: {\n        show: false,\n      },\n      boundaryGap: false,\n      data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),\n      splitLine: {\n        lineStyle: {\n          type: 'solid',\n          width: 1,\n        },\n        show: true,\n      },\n      type: 'category',\n    },\n    yAxis: [\n      {\n        axisTick: {\n          show: false,\n        },\n        max: 80_000,\n        splitArea: {\n          show: true,\n        },\n        splitNumber: 4,\n        type: 'value',\n      },\n    ],\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/dashboard/analytics/analytics-visits-data.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: 0,\n      data: ['访问', '趋势'],\n    },\n    radar: {\n      indicator: [\n        {\n          name: '网页',\n        },\n        {\n          name: '移动端',\n        },\n        {\n          name: 'Ipad',\n        },\n        {\n          name: '客户端',\n        },\n        {\n          name: '第三方',\n        },\n        {\n          name: '其它',\n        },\n      ],\n      radius: '60%',\n      splitNumber: 8,\n    },\n    series: [\n      {\n        areaStyle: {\n          opacity: 1,\n          shadowBlur: 0,\n          shadowColor: 'rgba(0,0,0,.2)',\n          shadowOffsetX: 0,\n          shadowOffsetY: 10,\n        },\n        data: [\n          {\n            itemStyle: {\n              color: '#b6a2de',\n            },\n            name: '访问',\n            value: [90, 50, 86, 40, 50, 20],\n          },\n          {\n            itemStyle: {\n              color: '#5ab1ef',\n            },\n            name: '趋势',\n            value: [70, 75, 70, 76, 20, 85],\n          },\n        ],\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        symbolSize: 0,\n        type: 'radar',\n      },\n    ],\n    tooltip: {},\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/dashboard/analytics/analytics-visits-sales.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 400;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        center: ['50%', '50%'],\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '外包', value: 500 },\n          { name: '定制', value: 310 },\n          { name: '技术支持', value: 274 },\n          { name: '远程', value: 400 },\n        ].toSorted((a, b) => {\n          return a.value - b.value;\n        }),\n        name: '商业占比',\n        radius: '80%',\n        roseType: 'radius',\n        type: 'pie',\n      },\n    ],\n\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/dashboard/analytics/analytics-visits-source.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    legend: {\n      bottom: '2%',\n      left: 'center',\n    },\n    series: [\n      {\n        animationDelay() {\n          return Math.random() * 100;\n        },\n        animationEasing: 'exponentialInOut',\n        animationType: 'scale',\n        avoidLabelOverlap: false,\n        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],\n        data: [\n          { name: '搜索引擎', value: 1048 },\n          { name: '直接访问', value: 735 },\n          { name: '邮件营销', value: 580 },\n          { name: '联盟广告', value: 484 },\n        ],\n        emphasis: {\n          label: {\n            fontSize: '12',\n            fontWeight: 'bold',\n            show: true,\n          },\n        },\n        itemStyle: {\n          // borderColor: '#fff',\n          borderRadius: 10,\n          borderWidth: 2,\n        },\n        label: {\n          position: 'center',\n          show: false,\n        },\n        labelLine: {\n          show: false,\n        },\n        name: '访问来源',\n        radius: ['40%', '65%'],\n        type: 'pie',\n      },\n    ],\n    tooltip: {\n      trigger: 'item',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/dashboard/analytics/analytics-visits.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EchartsUIType } from '@vben/plugins/echarts';\n\nimport { onMounted, ref } from 'vue';\n\nimport { EchartsUI, useEcharts } from '@vben/plugins/echarts';\n\nconst chartRef = ref<EchartsUIType>();\nconst { renderEcharts } = useEcharts(chartRef);\n\nonMounted(() => {\n  renderEcharts({\n    grid: {\n      bottom: 0,\n      containLabel: true,\n      left: '1%',\n      right: '1%',\n      top: '2 %',\n    },\n    series: [\n      {\n        barMaxWidth: 80,\n        // color: '#4f69fd',\n        data: [\n          3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,\n          3200, 4800,\n        ],\n        type: 'bar',\n      },\n    ],\n    tooltip: {\n      axisPointer: {\n        lineStyle: {\n          // color: '#4f69fd',\n          width: 1,\n        },\n      },\n      trigger: 'axis',\n    },\n    xAxis: {\n      data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),\n      type: 'category',\n    },\n    yAxis: {\n      max: 8000,\n      splitNumber: 4,\n      type: 'value',\n    },\n  });\n});\n</script>\n\n<template>\n  <EchartsUI ref=\"chartRef\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/dashboard/analytics/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { AnalysisOverviewItem } from '@vben/common-ui';\nimport type { TabOption } from '@vben/types';\n\nimport {\n  AnalysisChartCard,\n  AnalysisChartsTabs,\n  AnalysisOverview,\n} from '@vben/common-ui';\nimport {\n  SvgBellIcon,\n  SvgCakeIcon,\n  SvgCardIcon,\n  SvgDownloadIcon,\n} from '@vben/icons';\n\nimport AnalyticsTrends from './analytics-trends.vue';\nimport AnalyticsVisitsData from './analytics-visits-data.vue';\nimport AnalyticsVisitsSales from './analytics-visits-sales.vue';\nimport AnalyticsVisitsSource from './analytics-visits-source.vue';\nimport AnalyticsVisits from './analytics-visits.vue';\n\nconst overviewItems: AnalysisOverviewItem[] = [\n  {\n    icon: SvgCardIcon,\n    title: '用户量',\n    totalTitle: '总用户量',\n    totalValue: 120_000,\n    value: 2000,\n  },\n  {\n    icon: SvgCakeIcon,\n    title: '访问量',\n    totalTitle: '总访问量',\n    totalValue: 500_000,\n    value: 20_000,\n  },\n  {\n    icon: SvgDownloadIcon,\n    title: '下载量',\n    totalTitle: '总下载量',\n    totalValue: 120_000,\n    value: 8000,\n  },\n  {\n    icon: SvgBellIcon,\n    title: '使用量',\n    totalTitle: '总使用量',\n    totalValue: 50_000,\n    value: 5000,\n  },\n];\n\nconst chartTabs: TabOption[] = [\n  {\n    label: '流量趋势',\n    value: 'trends',\n  },\n  {\n    label: '月访问量',\n    value: 'visits',\n  },\n];\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <AnalysisOverview :items=\"overviewItems\" />\n    <AnalysisChartsTabs :tabs=\"chartTabs\" class=\"mt-5\">\n      <template #trends>\n        <AnalyticsTrends />\n      </template>\n      <template #visits>\n        <AnalyticsVisits />\n      </template>\n    </AnalysisChartsTabs>\n\n    <div class=\"mt-5 w-full md:flex\">\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问数量\">\n        <AnalyticsVisitsData />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:mr-4 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSource />\n      </AnalysisChartCard>\n      <AnalysisChartCard class=\"mt-5 md:mt-0 md:w-1/3\" title=\"访问来源\">\n        <AnalyticsVisitsSales />\n      </AnalysisChartCard>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/src/views/dashboard/workspace/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  WorkbenchProjectItem,\n  WorkbenchQuickNavItem,\n  WorkbenchTodoItem,\n  WorkbenchTrendItem,\n} from '@vben/common-ui';\n\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport {\n  AnalysisChartCard,\n  WorkbenchHeader,\n  WorkbenchProject,\n  WorkbenchQuickNav,\n  WorkbenchTodo,\n  WorkbenchTrends,\n} from '@vben/common-ui';\nimport { preferences } from '@vben/preferences';\nimport { useUserStore } from '@vben/stores';\nimport { openWindow } from '@vben/utils';\n\nimport AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';\n\nconst userStore = useUserStore();\n\n// 这是一个示例数据，实际项目中需要根据实际情况进行调整\n// url 也可以是内部路由，在 navTo 方法中识别处理，进行内部跳转\n// 例如：url: /dashboard/workspace\nconst projectItems: WorkbenchProjectItem[] = [\n  {\n    color: '',\n    content: '不要等待机会，而要创造机会。',\n    date: '2021-04-01',\n    group: '开源组',\n    icon: 'carbon:logo-github',\n    title: 'Github',\n    url: 'https://github.com',\n  },\n  {\n    color: '#3fb27f',\n    content: '现在的你决定将来的你。',\n    date: '2021-04-01',\n    group: '算法组',\n    icon: 'ion:logo-vue',\n    title: 'Vue',\n    url: 'https://vuejs.org',\n  },\n  {\n    color: '#e18525',\n    content: '没有什么才能比努力更重要。',\n    date: '2021-04-01',\n    group: '上班摸鱼',\n    icon: 'ion:logo-html5',\n    title: 'Html5',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',\n  },\n  {\n    color: '#bf0c2c',\n    content: '热情和欲望可以突破一切难关。',\n    date: '2021-04-01',\n    group: 'UI',\n    icon: 'ion:logo-angular',\n    title: 'Angular',\n    url: 'https://angular.io',\n  },\n  {\n    color: '#00d8ff',\n    content: '健康的身体是实现目标的基石。',\n    date: '2021-04-01',\n    group: '技术牛',\n    icon: 'bx:bxl-react',\n    title: 'React',\n    url: 'https://reactjs.org',\n  },\n  {\n    color: '#EBD94E',\n    content: '路是走出来的，而不是空想出来的。',\n    date: '2021-04-01',\n    group: '架构组',\n    icon: 'ion:logo-javascript',\n    title: 'Js',\n    url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',\n  },\n];\n\n// 同样，这里的 url 也可以使用以 http 开头的外部链接\nconst quickNavItems: WorkbenchQuickNavItem[] = [\n  {\n    color: '#1fdaca',\n    icon: 'ion:home-outline',\n    title: '首页',\n    url: '/',\n  },\n  {\n    color: '#bf0c2c',\n    icon: 'ion:grid-outline',\n    title: '仪表盘',\n    url: '/dashboard',\n  },\n  {\n    color: '#e18525',\n    icon: 'ion:layers-outline',\n    title: '组件',\n    url: '/demos/features/icons',\n  },\n  {\n    color: '#3fb27f',\n    icon: 'ion:settings-outline',\n    title: '系统管理',\n    url: '/demos/features/login-expired', // 这里的 URL 是示例，实际项目中需要根据实际情况进行调整\n  },\n  {\n    color: '#4daf1bc9',\n    icon: 'ion:key-outline',\n    title: '权限管理',\n    url: '/demos/access/page-control',\n  },\n  {\n    color: '#00d8ff',\n    icon: 'ion:bar-chart-outline',\n    title: '图表',\n    url: '/analytics',\n  },\n];\n\nconst todoItems = ref<WorkbenchTodoItem[]>([\n  {\n    completed: false,\n    content: `审查最近提交到Git仓库的前端代码，确保代码质量和规范。`,\n    date: '2024-07-30 11:00:00',\n    title: '审查前端代码提交',\n  },\n  {\n    completed: true,\n    content: `检查并优化系统性能，降低CPU使用率。`,\n    date: '2024-07-30 11:00:00',\n    title: '系统性能优化',\n  },\n  {\n    completed: false,\n    content: `进行系统安全检查，确保没有安全漏洞或未授权的访问。 `,\n    date: '2024-07-30 11:00:00',\n    title: '安全检查',\n  },\n  {\n    completed: false,\n    content: `更新项目中的所有npm依赖包，确保使用最新版本。`,\n    date: '2024-07-30 11:00:00',\n    title: '更新项目依赖',\n  },\n  {\n    completed: false,\n    content: `修复用户报告的页面UI显示问题，确保在不同浏览器中显示一致。 `,\n    date: '2024-07-30 11:00:00',\n    title: '修复UI显示问题',\n  },\n]);\nconst trendItems: WorkbenchTrendItem[] = [\n  {\n    avatar: 'svg:avatar-1',\n    content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,\n    date: '刚刚',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关注了 <a>威廉</a> `,\n    date: '1个小时前',\n    title: '艾文',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1天前',\n    title: '克里斯',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写一个Vite插件</a> `,\n    date: '2天前',\n    title: 'Vben',\n  },\n  {\n    avatar: 'svg:avatar-1',\n    content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化？</a>`,\n    date: '3天前',\n    title: '皮特',\n  },\n  {\n    avatar: 'svg:avatar-2',\n    content: `关闭了问题 <a>如何运行项目</a> `,\n    date: '1周前',\n    title: '杰克',\n  },\n  {\n    avatar: 'svg:avatar-3',\n    content: `发布了 <a>个人动态</a> `,\n    date: '1周前',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `推送了代码到 <a>Github</a>`,\n    date: '2021-04-01 20:00',\n    title: '威廉',\n  },\n  {\n    avatar: 'svg:avatar-4',\n    content: `发表文章 <a>如何编写使用 Admin Vben</a> `,\n    date: '2021-03-01 20:00',\n    title: 'Vben',\n  },\n];\n\nconst router = useRouter();\n\n// 这是一个示例方法，实际项目中需要根据实际情况进行调整\n// This is a sample method, adjust according to the actual project requirements\nfunction navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {\n  if (nav.url?.startsWith('http')) {\n    openWindow(nav.url);\n    return;\n  }\n  if (nav.url?.startsWith('/')) {\n    router.push(nav.url).catch((error) => {\n      console.error('Navigation failed:', error);\n    });\n  } else {\n    console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);\n  }\n}\n</script>\n\n<template>\n  <div class=\"p-5\">\n    <WorkbenchHeader\n      :avatar=\"userStore.userInfo?.avatar || preferences.app.defaultAvatar\"\n    >\n      <template #title>\n        早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧！\n      </template>\n      <template #description> 今日晴，20℃ - 32℃！ </template>\n    </WorkbenchHeader>\n\n    <div class=\"mt-5 flex flex-col lg:flex-row\">\n      <div class=\"mr-4 w-full lg:w-3/5\">\n        <WorkbenchProject :items=\"projectItems\" title=\"项目\" @click=\"navTo\" />\n        <WorkbenchTrends :items=\"trendItems\" class=\"mt-5\" title=\"最新动态\" />\n      </div>\n      <div class=\"w-full lg:w-2/5\">\n        <WorkbenchQuickNav\n          :items=\"quickNavItems\"\n          class=\"mt-5 lg:mt-0\"\n          title=\"快捷导航\"\n          @click=\"navTo\"\n        />\n        <WorkbenchTodo :items=\"todoItems\" class=\"mt-5\" title=\"待办事项\" />\n        <AnalysisChartCard class=\"mt-5\" title=\"访问来源\">\n          <AnalyticsVisitsSource />\n        </AnalysisChartCard>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/access/admin-visible.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback\n    description=\"当前页面仅 Admin 账号可见\"\n    status=\"coming-soon\"\n    title=\"页面访问测试\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/access/button-control.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Recordable } from '@vben/types';\n\nimport { useRouter } from 'vue-router';\n\nimport { AccessControl, useAccess } from '@vben/access';\nimport { Page } from '@vben/common-ui';\nimport { resetAllStores, useUserStore } from '@vben/stores';\n\nimport { Button, Card } from 'ant-design-vue';\n\nimport { useAuthStore } from '#/store';\n\nconst accounts: Record<string, Recordable<any>> = {\n  admin: {\n    password: '123456',\n    username: 'admin',\n  },\n  super: {\n    password: '123456',\n    username: 'vben',\n  },\n  user: {\n    password: '123456',\n    username: 'jack',\n  },\n};\n\nconst { accessMode, hasAccessByCodes } = useAccess();\nconst authStore = useAuthStore();\nconst userStore = useUserStore();\nconst router = useRouter();\n\nfunction roleButtonType(role: string) {\n  return userStore.userRoles.includes(role) ? 'primary' : 'default';\n}\n\nasync function changeAccount(role: string) {\n  if (userStore.userRoles.includes(role)) {\n    return;\n  }\n\n  const account = accounts[role];\n  resetAllStores();\n  if (account) {\n    await authStore.authLogin(account, async () => {\n      router.go(0);\n    });\n  }\n}\n</script>\n\n<template>\n  <Page\n    :title=\"`${accessMode === 'frontend' ? '前端' : '后端'}按钮访问权限演示`\"\n    description=\"切换不同的账号，观察按钮变化。\"\n  >\n    <Card class=\"mb-5\">\n      <template #title>\n        <span class=\"font-semibold\">当前角色:</span>\n        <span class=\"mx-4 text-lg text-primary\">\n          {{ userStore.userRoles?.[0] }}\n        </span>\n      </template>\n\n      <Button :type=\"roleButtonType('super')\" @click=\"changeAccount('super')\">\n        切换为 Super 账号\n      </Button>\n\n      <Button\n        :type=\"roleButtonType('admin')\"\n        class=\"mx-4\"\n        @click=\"changeAccount('admin')\"\n      >\n        切换为 Admin 账号\n      </Button>\n      <Button :type=\"roleButtonType('user')\" @click=\"changeAccount('user')\">\n        切换为 User 账号\n      </Button>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"组件形式控制 - 权限码\">\n      <AccessControl :codes=\"['AC_100100']\" type=\"code\">\n        <Button class=\"mr-4\"> Super 账号可见 [\"AC_100100\"] </Button>\n      </AccessControl>\n      <AccessControl :codes=\"['AC_100030']\" type=\"code\">\n        <Button class=\"mr-4\"> Admin 账号可见 [\"AC_100030\"] </Button>\n      </AccessControl>\n      <AccessControl :codes=\"['AC_1000001']\" type=\"code\">\n        <Button class=\"mr-4\"> User 账号可见 [\"AC_1000001\"] </Button>\n      </AccessControl>\n      <AccessControl :codes=\"['AC_100100', 'AC_100030']\" type=\"code\">\n        <Button class=\"mr-4\">\n          Super & Admin 账号可见 [\"AC_100100\",\"AC_100030\"]\n        </Button>\n      </AccessControl>\n    </Card>\n\n    <Card\n      v-if=\"accessMode === 'frontend'\"\n      class=\"mb-5\"\n      title=\"组件形式控制 - 角色\"\n    >\n      <AccessControl :codes=\"['super']\" type=\"role\">\n        <Button class=\"mr-4\"> Super 角色可见 </Button>\n      </AccessControl>\n      <AccessControl :codes=\"['admin']\" type=\"role\">\n        <Button class=\"mr-4\"> Admin 角色可见 </Button>\n      </AccessControl>\n      <AccessControl :codes=\"['user']\" type=\"role\">\n        <Button class=\"mr-4\"> User 角色可见 </Button>\n      </AccessControl>\n      <AccessControl :codes=\"['super', 'admin']\" type=\"role\">\n        <Button class=\"mr-4\"> Super & Admin 角色可见 </Button>\n      </AccessControl>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"函数形式控制\">\n      <Button v-if=\"hasAccessByCodes(['AC_100100'])\" class=\"mr-4\">\n        Super 账号可见 [\"AC_100100\"]\n      </Button>\n      <Button v-if=\"hasAccessByCodes(['AC_100030'])\" class=\"mr-4\">\n        Admin 账号可见 [\"AC_100030\"]\n      </Button>\n      <Button v-if=\"hasAccessByCodes(['AC_1000001'])\" class=\"mr-4\">\n        User 账号可见 [\"AC_1000001\"]\n      </Button>\n      <Button v-if=\"hasAccessByCodes(['AC_100100', 'AC_100030'])\" class=\"mr-4\">\n        Super & Admin 账号可见 [\"AC_100100\",\"AC_100030\"]\n      </Button>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"指令方式 - 权限码\">\n      <Button class=\"mr-4\" v-access:code=\"['AC_100100']\">\n        Super 账号可见 [\"AC_100100\"]\n      </Button>\n      <Button class=\"mr-4\" v-access:code=\"['AC_100030']\">\n        Admin 账号可见 [\"AC_100030\"]\n      </Button>\n      <Button class=\"mr-4\" v-access:code=\"['AC_1000001']\">\n        User 账号可见 [\"AC_1000001\"]\n      </Button>\n      <Button class=\"mr-4\" v-access:code=\"['AC_100100', 'AC_100030']\">\n        Super & Admin 账号可见 [\"AC_100100\",\"AC_100030\"]\n      </Button>\n    </Card>\n\n    <Card v-if=\"accessMode === 'frontend'\" class=\"mb-5\" title=\"指令方式 - 角色\">\n      <Button class=\"mr-4\" v-access:role=\"['super']\"> Super 角色可见 </Button>\n      <Button class=\"mr-4\" v-access:role=\"['admin']\"> Admin 角色可见 </Button>\n      <Button class=\"mr-4\" v-access:role=\"['user']\"> User 角色可见 </Button>\n      <Button class=\"mr-4\" v-access:role=\"['super', 'admin']\">\n        Super & Admin 角色可见\n      </Button>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/access/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Recordable } from '@vben/types';\n\nimport { useRouter } from 'vue-router';\n\nimport { useAccess } from '@vben/access';\nimport { Page } from '@vben/common-ui';\nimport { resetAllStores, useUserStore } from '@vben/stores';\n\nimport { Button, Card } from 'ant-design-vue';\n\nimport { useAuthStore } from '#/store';\n\nconst accounts: Record<string, Recordable<any>> = {\n  admin: {\n    password: '123456',\n    username: 'admin',\n  },\n  super: {\n    password: '123456',\n    username: 'vben',\n  },\n  user: {\n    password: '123456',\n    username: 'jack',\n  },\n};\n\nconst { accessMode, toggleAccessMode } = useAccess();\nconst userStore = useUserStore();\nconst accessStore = useAuthStore();\nconst router = useRouter();\n\nfunction roleButtonType(role: string) {\n  return userStore.userRoles.includes(role) ? 'primary' : 'default';\n}\n\nasync function changeAccount(role: string) {\n  if (userStore.userRoles.includes(role)) {\n    return;\n  }\n\n  const account = accounts[role];\n  resetAllStores();\n  if (account) {\n    await accessStore.authLogin(account, async () => {\n      router.go(0);\n    });\n  }\n}\n\nasync function handleToggleAccessMode() {\n  if (!accounts.super) {\n    return;\n  }\n  await toggleAccessMode();\n  resetAllStores();\n\n  await accessStore.authLogin(accounts.super, async () => {\n    setTimeout(() => {\n      router.go(0);\n    }, 150);\n  });\n}\n</script>\n\n<template>\n  <Page\n    :title=\"`${accessMode === 'frontend' ? '前端' : '后端'}页面访问权限演示`\"\n    description=\"切换不同的账号，观察左侧菜单变化。\"\n  >\n    <Card class=\"mb-5\" title=\"权限模式\">\n      <span class=\"font-semibold\">当前权限模式:</span>\n      <span class=\"mx-4 text-primary\">{{\n        accessMode === 'frontend' ? '前端权限控制' : '后端权限控制'\n      }}</span>\n      <Button type=\"primary\" @click=\"handleToggleAccessMode\">\n        切换为{{ accessMode === 'frontend' ? '后端' : '前端' }}权限模式\n      </Button>\n    </Card>\n    <Card title=\"账号切换\">\n      <Button :type=\"roleButtonType('super')\" @click=\"changeAccount('super')\">\n        切换为 Super 账号\n      </Button>\n\n      <Button\n        :type=\"roleButtonType('admin')\"\n        class=\"mx-4\"\n        @click=\"changeAccount('admin')\"\n      >\n        切换为 Admin 账号\n      </Button>\n      <Button :type=\"roleButtonType('user')\" @click=\"changeAccount('user')\">\n        切换为 User 账号\n      </Button>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/access/menu-visible-403.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback\n    description=\"当前页面用户不可见，会被重定向到403页面\"\n    status=\"coming-soon\"\n    title=\"页面访问测试\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/access/super-visible.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback\n    description=\"当前页面仅 Super 账号可见\"\n    status=\"coming-soon\"\n    title=\"页面访问测试\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/access/user-visible.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback\n    description=\"当前页面仅 User 账号可见\"\n    status=\"coming-soon\"\n    title=\"页面访问测试\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/active-icon/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback\n    description=\"用于菜单激活显示不同的图标\"\n    status=\"coming-soon\"\n    title=\"激活图标示例\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/badge/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { reactive } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport { Page } from '@vben/common-ui';\nimport { useAccessStore } from '@vben/stores';\n\nimport { MenuBadge } from '@vben-core/menu-ui';\n\nimport { Button, Card, Radio, RadioGroup } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst colors = [\n  { label: '预设：默认', value: 'default' },\n  { label: '预设：关键', value: 'destructive' },\n  { label: '预设：主要', value: 'primary' },\n  { label: '预设：成功', value: 'success' },\n  { label: '自定义', value: 'bg-gray-200 text-black' },\n];\n\nconst route = useRoute();\nconst accessStore = useAccessStore();\nconst menu = accessStore.getMenuByPath(route.path);\nconst badgeProps = reactive({\n  badge: menu?.badge as string,\n  badgeType: menu?.badge ? 'normal' : (menu?.badgeType as 'dot' | 'normal'),\n  badgeVariants: menu?.badgeVariants as string,\n});\n\nconst [Form] = useVbenForm({\n  handleValuesChange(values) {\n    badgeProps.badge = values.badge;\n    badgeProps.badgeType = values.badgeType;\n    badgeProps.badgeVariants = values.badgeVariants;\n  },\n  schema: [\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        buttonStyle: 'solid',\n        options: [\n          { label: '点徽标', value: 'dot' },\n          { label: '文字徽标', value: 'normal' },\n        ],\n        optionType: 'button',\n      },\n      defaultValue: badgeProps.badgeType,\n      fieldName: 'badgeType',\n      label: '类型',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        maxLength: 4,\n        placeholder: '请输入徽标内容',\n        style: { width: '200px' },\n      },\n      defaultValue: badgeProps.badge,\n      fieldName: 'badge',\n      label: '徽标内容',\n    },\n    {\n      component: 'RadioGroup',\n      defaultValue: badgeProps.badgeVariants,\n      fieldName: 'badgeVariants',\n      label: '颜色',\n    },\n    {\n      component: 'Input',\n      fieldName: 'action',\n    },\n  ],\n  showDefaultActions: false,\n});\n\nfunction updateMenuBadge() {\n  if (menu) {\n    menu.badge = badgeProps.badge;\n    menu.badgeType = badgeProps.badgeType;\n    menu.badgeVariants = badgeProps.badgeVariants;\n  }\n}\n</script>\n\n<template>\n  <Page\n    description=\"菜单项上可以显示徽标，这些徽标可以主动更新\"\n    title=\"菜单徽标\"\n  >\n    <Card title=\"徽标更新\">\n      <Form>\n        <template #badgeVariants=\"slotProps\">\n          <RadioGroup v-bind=\"slotProps\">\n            <Radio\n              v-for=\"color in colors\"\n              :key=\"color.value\"\n              :value=\"color.value\"\n            >\n              <div\n                :title=\"color.label\"\n                class=\"flex h-3.5 w-12.5 items-center justify-start\"\n              >\n                <MenuBadge\n                  v-bind=\"{ ...badgeProps, badgeVariants: color.value }\"\n                />\n              </div>\n            </Radio>\n          </RadioGroup>\n        </template>\n        <template #action>\n          <Button type=\"primary\" @click=\"updateMenuBadge\">更新徽标</Button>\n        </template>\n      </Form>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/breadcrumb/lateral-detail.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useRouter } from 'vue-router';\n\nimport { Fallback } from '@vben/common-ui';\n\nimport { Button } from 'ant-design-vue';\n\nconst router = useRouter();\n</script>\n\n<template>\n  <Fallback\n    description=\"面包屑导航-平级模式-详情页\"\n    status=\"coming-soon\"\n    title=\"注意观察面包屑导航变化\"\n  >\n    <template #action>\n      <Button @click=\"router.go(-1)\">返回</Button>\n    </template>\n  </Fallback>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/breadcrumb/lateral.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useRouter } from 'vue-router';\n\nimport { Fallback } from '@vben/common-ui';\n\nimport { Button } from 'ant-design-vue';\n\nconst router = useRouter();\n\nfunction details() {\n  router.push({ name: 'BreadcrumbLateralDetailDemo' });\n}\n</script>\n\n<template>\n  <Fallback\n    description=\"点击查看详情，并观察面包屑导航变化\"\n    status=\"coming-soon\"\n    title=\"面包屑导航-平级模式\"\n  >\n    <template #action>\n      <Button type=\"primary\" @click=\"details\">点击查看详情</Button>\n    </template>\n  </Fallback>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/breadcrumb/level-detail.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback\n    description=\"面包屑导航-层级模式-详情页\"\n    status=\"coming-soon\"\n    title=\"注意观察面包屑导航变化\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/clipboard/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { useClipboard } from '@vueuse/core';\nimport { Button, Card, Input } from 'ant-design-vue';\n\nconst source = ref('Hello');\nconst { copy, text } = useClipboard({ legacy: true, source });\n</script>\n\n<template>\n  <Page title=\"剪切板示例\">\n    <Card title=\"基本使用\">\n      <p class=\"mb-3\">\n        Current copied: <code>{{ text || 'none' }}</code>\n      </p>\n      <div class=\"flex\">\n        <Input v-model:value=\"source\" class=\"mr-3 flex w-50\" />\n        <Button type=\"primary\" @click=\"copy(source)\"> Copy </Button>\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/file-download/base64.ts",
    "content": "export default `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAACXBIWXMAAAsSAAALEgHS3X78AAAAAXNSR0IArs4c6QAAIABJREFUeF7tvQmYZFd1H37eVnv1Nj2rZjQaSSONNJJBCyDABiTAQAxxTGLH4W8HCJgQ5/+3wRAwccDg8NlOjM1nxzGOiQGDHf4QiDFgvLCK3SAZtC8zo5596Z7ea3/Lzfc7956q20/V3dUzPa2e7nqfRtX16r377rv3nHPP8jvnOtQ/+iOwiUfA2cTv3n/1/ghQnwH6RLCpR6DPAJt6+vsv32eAPg1s6hHoM8Cmnv7+y/cZoE8Dm3oE+gywqae///J9BujTwKYegT4DbOrp7798nwH6NLCpR6DPAJt6+vsv32eAPg1s6hHoM8Cmnv7+y/cZoE8Dm3oE+gywqae///J9BujTwKYegT4DbOrp7798nwH6NLCpR6DPAJt6+vsv32eAPg1s6hHoM8Cmnv7+y/cZoE8Dm3oE+gywqae///J9BujTwKYegT4DPEXT/9WxsRzVav7+wT3ZphsVG6EqF0Ma9FrxkBMGo+5h5wp0LYmpGDXVDr/hbcf3KBefyxScI40bWt/yivUH9u7dO/0UvcKGeGyfAVZxGo99dvpN3hPZX1ORMxrFCbmJoiRR5LoOJQlRHCl+mh84FOKPUJGjiOJYkeMQuZ6eDvyNA9dHnkMe7nP4P1JKX6fQrudQcThDuZJPczvqn6/9yMxrbti9e3IVX2nDN9VngIuc4m+fOJHf9/nRx2uz8e6ompDnOVRvxZTxXGrGCSml+DPne0zACROxItdxKGFqdqkZRhS4DhV8n+bDiLKeS1GS8DUtIsoQUStRfD9+a8UJt4+jkPEIbDWyK0/5YZecrUnt9J7pW665ZuvjF/lqm+L2PgNcxDQf/+LkW/2HS78zf77JhNoKI3LIoRDi3hyuQ5SAzoXgIeGJyMOqoAU7xWAE0tcoUuRaBfvwm+c4bYKvRzGvEDnP4/uiRP8+XPKpNJqj8k6P3JJDEwPVD13xjNLrLuL1NsWtfQa4wGk+/Sdzn29Nuj/RrCU02wopA0kea8IfyPg014rahNqMYyZ2+8DAO4Yp8Hfe96gWxe1LioFHjShpMwerRlg9zBWlwKdqGPH3wHWZcXBudE+BijtdcosOnWnWv733ZYXnXuArborb+gxwAdN8/EOzn61M+q9oVBLyw5hYKpt2sr7H6kylpYlTDhCprAz2arDY4wuBR7VwIUNUw5ilfdbHGuEwA4DoE1Lch8FMQNmcS+WtOWYCxyc6l2t87co7C3dewGtuilv6DLDCaT7+halfqzyUfW+9klBUh+LTOSC165HW+1MCn3zoQrABlOLVAN+hvuQ8lxowaH2XEkPwWA1wfzOKn9QO2gATiNqE77AvcC0+YSOUtmRpZG9Abp7IyTo0v7P+O1sOFN62wlfdFJf3GWAF03zo5OTu5NP5Y83J2K03FhK/EDSag4EKQ9U+8LvvONRInV/u8SBoqFbyKddjFcEBVsNqwAa1MZJLWZ+GjVHsb9Huo7G9M0+//urh+5Z73mb7vc8AK5jxQx+oRY0zkVdrhKzmhJZin5bKaLbpuJRVCbmBx9Ld1uGFSXpRh6SLLceljNKMlX6+tANVi1eBkkdbry6QkyHyhvQ0f4u+Ftx5553RCl55w1/aZ4Aep/jo1+Y+OvlV5+fhiIewVa3QeHe0l6fbgdOLDTAEuBHaS/YAbKMVoicfCWFV0edlBYDtgP7kfW0LDO3xySsjyEDUClqnSj+a3d3jK2+Ky/oM0MM0/2BsbCj/2W1T1anEgd4/mA1othkukOjpZlinjxaqQT08alUuwSoARvA9h1WhwhaXvBE91bM7qr+39YbSW1blQRugkT4D9DCJUH3mz8VeWGstSvRpiS7qj918zfWokHQ8OytdNeT6pVYWXAMjGl6hoWxAuaJHQztzlB1x2DWaNBQd2j9x7c37tx/p4dU3/CV9Blhmio99ff6D579Mr/fDiP3yKz0ixyEfnh9ySDlEUGkChW/ap49P2AhhmCxQdcBAOKDz29finM0A3RhN+gjDG27SoR15Km/3yCsRORnAMlSSe5HrrfRdNuL1fQZYYlYfe+z0aPLXg+O12cSJmxEpK8Kbvk0IfTGprn01mhESR/+twQ2KGSRGUMxcExiGQXjLPkLDFGCg5Q6GTChFjuvSSM5jJgBUQrxCzdHw7vLNmRcs185G/73PAEvM8OMfqIXzZ2M/7e/vdgsYAECInCHO0IGk1wSM/wsDcATYc0kBJ2R+g6wHA+B+eI0g1XEv2vBxp+EDPAOknwGWyDAHvkeO/oZ77cMxOIxsLkNDeYe27C2QXyJWhWAUn9sx+y/23DD06Y1O5Eu9X58BFhmdk9+ffe+5z7u/BtRl3GTsZtdDiBskCB0f0hxqi6gpIFrPSHh8alp2GDRXSxzKK20TtFUZxArIpZzSQTCcB8G3EAHmdp028YNpwBxYTZixLKgE2oSrNFbEkeOs61JxJEsjuwJyi0RuQccH7i/fm7n99tsXf8ENzh19Bugywd89dGhg4DO7ZrtFe21fvH2rnF/s94YDou5I6IbjMZHj0GoREe4FQQtTwF4AAxRUzCuELCewJcBo9oE20ioTYg2INAOXhANGsWCFQPxYCZoUTpbvzIxucDpf9PX6DNBlaA79Sa1eG09yjbnmAoPTvhT6uOjiaclfdz3KJzrwpSW4JnwhepHiQvg2MWupriW6MAeelTHMgvO4L5PxqBomC5gKLIFnCANJf3l1cIjKmYCKgxka3BZQZlh7hfCg2R3V3916Q+mtm5EJ+gyQmvUz9838u3Of9f+Ik1EM1gdqjAtIMymyjV0Qm0cJxUZlSTMIVBPXEDMGWuwCECqIWNqCqs4qE4xffhb0f5elPNqQQ5gObYFRbI8S5xmYVWCpSUUMozQU0MjuLLkFIifQeKGxXWe2Xn/9rvObjQn6DJCa8UffX02qMzrghQNECkKDMQodXg4QoK3b4xqcA5PIeRC5/C3EjGtE4oudwBIekdqE2tdjdVFsGOtzOFoOkmr039pJqhml6Kj2vTaTsloEncp0m43vRNFIzqeRKwoLvEKxE9fyL/CLfQbYbCNgve/jf1abrB1PRqJmTLHRm0W3lk9IcfbdmxVB1Bh8F9cmiBAS2bYLArMaSDwATAC6BAELI8g5gT500+txLbw+YAowJ75DxZKVA9/BHLLKdINRuIFP20qe9gqViZycw9Dp+kDjy4O35V+0mUigvwKY2T724NQrqn+T/Wx1JmTgGohX/PMsjVO4HvmOz6rrMxHiH7sujcSWwYWNAIIVF6e4K+vGEJa22EYIXKpFWre3Vxx0UyLJIPzQqF14hhjYtoqFtks+tSHWer0w2CQHSTsBlW2vEOwBIjpzxfQr9l438vnNwgR9BjAz/dgf1OLKZOwu5vMHQ8CgLScRVVyfDWAhaNHN8V3+1r78xKgqHf0fRApCRzuLHTYATlQtXMueIGg0lhdImCJtUOM7XKdgJDAImBPvIJ4oPxdQSRFt2V2g4mgHK4TnfOuqo/k79+1rbAYm6DMAER3+q8q9cz9Qt0Jw1xpR2/gUvzoIvpQgTNWRoiJNZaWwI7uQs/Ld912KTarjkwbbAhChHUh2eI9gzCLnHfq6xAKg4kiwy3a1iiq2QMITEVYAKFcIqOFOfM9QssB96gUeDfguje4uUGGHy7nEOBpu8/TA83NclmWjH5ueAY4cOX3l7MfKx5I4oWpL43EkggvJyfq+CTQJpiftDQKRQLJDcqcjwQJvwCerKMZI1l4ejfORFQUEjvsh1XMGdgGmAAGLWoX7uD9mJRA7AZ4jGM1Qm2wGEU+TjS2SezzfozJyB0QVKlCbCSo7G388ciD/7/oMsMFH4PE/rMbzE4nbrEOr1m5I0b1BNOL9EbXE9vaIjYBz2vB1FkSBObBlSqGIdIcBiwPXZlXMahWugfqE69EWVJycWQmartvGCGF1qLk+B9AiE/YCw4q6ZUeDGV5hBcckFiHGsfQ3mw8orxRt2Z6ngd0euTkNmMNx4sqJ6665ZtuhjUwCm3oFOPH1yv+a+jr9qyjs+PyhKkDXBmwZ0Vk7SCUuUTFiRbriHhCipMbjfhCcECCkNohdAmOs4rDPX7cPYgURgyghybECaebR/kswJVYErAxQxdASVpM2wI5/R391vEIwRGBppMwL0+pn6Wc0XI+fg+9FwKaVou1XFylbdshDGiUzUNLM3+nl+gywAUfgofHxkvPR4nx9PqGw3mL92wav4ZWhmoixKlgdrfNraQ1pXjRGMUgGf4sur92UOiUSBAmiw3cYxmAQSHMQrBirxSSmqusx44CIhaGwGqAtGM4Ch8AKAOLW6hFWD+21EqNcsEMMmjPBNfQf7ITnwDgW4xr3BPkMFRJFxQGfRq/Mkb/VlKGDy3UovKdwS+YZG5AE+JU27Qrw+IcbM5Vj4WAjUuS2OtldQkgy4SAcDUVImHjxO75DEuPAd/wDceEcmEaMT0hc8cTgHhAyfgOha9tAEzcIGvcB89Mil41VfDKjqJhqZoXh3ABFBLUomyBQpp8v/RJPDz6bjtdmCPRDq1teO3aAfuC8vIufR/05oquuyFNhq9vOI8a5c7tnXrBn//DdG5EJNiUDHHtg4uUzf5X/3EwtIT+OmKggcUHEQuDi1xeDkqEGBqcjKgvsAzEuNbFrRhH8D4gLBAwpjFgBDhAiVhoY2LgfvqU20A11Q41KBJicXkG0dO8QdcyrgVZljGvUTq4xapPAJNCGqGPoCxhNQHiMG0piAnYJzIuVoFj2aM+eHPnbOqShlEqyd23MBJpNyQAPv6+qRPURghe3JohE1BJ84jsbuyY5XevqxIQLFQSH+PsFj4Pz8OagTYnUaomsiRm6e+zgPs104l4Fw4A4OVpsoURt75OsJLAfcGiIhf6sc4U4BOK0d0lDsWFDaIMYh2SY4W+NcdLJOFCX8B65gSzt3paj/KDLtoBj8sYmt8++beeNQ7+z0VaBTccAY39Zu3f2weTWsBlTPezAk0Uai1ojBCwTro1WnZAiDADmAENILIAJzniDQIqQrkL4uA7SGMSp/UCaOXA99HkcsgrpxBeTSpkyxDFhsClw2DEAAdelCVSYS5Cr3HcTrba9REIIhZxP2wYC2rI7w2A5iQ0w89wpmOyNwwabigHGxqavmv94MBY2FbXq0Ps1UcOLAwKBFAVxgKAFQyN+dfwOHz6IBlJ03hjIop4ITA4qBgYV14MoRb3Bd7ElwBxoR6K3tvEqgS8mcMNM8jc+bWxSmgzBtLA5RLeXPomLVRhOVhhWjcz1wnRQhYYGMrRnf5GA4kYesRxHbji//+C12w5vHPLfZEbwY79bU5W5mKJGSKjvIB4dSSmEx0fOCaAMRJs2cnEdGEAIXeAPwgwCcBOPi/wu3hlGjSotieUQSd1ecSxdXq4CAWPVge4uf+M3GMk4oD6dDzJUsorswp0L9ykOqFwgevkuDCLPBKNkSFFuIEO7t2rvJ1QhOZrX1T88fEvx3/QZ4DIcgeNfqX7myBejnyy5iiqxJgZIdyEG8aBAUotLFOoKAk+ip8PNCWNWCEc8KWgLnh2bIdAeJLG0j7aE0cT7IwxgS3r0ib1AJmDWKburVSRZBdLEm2YgIXxp22YiELqsduLexXe8A94xk8+wNwjxEWzm4eccCrIOTZSb39r7T/I/ehlO/6Jd3hQq0NjY2NDkX2+fmj0ZOgOeokqk1QsYjJCakKgsIUl7cEA8ogZBP8d5DnYZDw2CS7hO9HS4I+FJsiW6MJQ98mIfgHjxfKwIsCmwQkjFCFyDSRH9HJ/4LmoT2hPitdsWJhX9XuII3Jbpn6hC3fombWGFyeUC2j6UYeIPkKhgjvkrwm9d9RN9BrjsBMBDf9poVk5EmUojpjhRVHA04dm6sa0Lw3iFLi/wAZG6IA4BmUkgCQQmrlBchzbRNmwLIVROqDHeFq2fL5Q7jOe3vEpaXdE2g32I/x/nbImPZ8GkRmwA9+B7NokX5Bq0odDG2JaVRewPtAmmAZMXcz7lSy7lM64KEtfxco6KG8qh0eandv/8wE9fdgSwRIc3/Apw8rtzv3n6m+474tmEqqE2es8dn6Dde7a0JbiA2xhQZgxXcRfCXy+HTZQiYUWSw/jkDDATYBJJbJctESITxmAj2HPIibULUgieYwpuB9bAdYKQxA6Yg+tQYIqRiisU5+XA77xSmRUJq5Ncj2twKZgX9onEPdr9ifW+Y8NZlxkA1awDMJdPqt5KnPJt9X++567R/9NngMtkBD75yU96Nx37iehcgyiuRmxAQoLjGPKJZowqpL3m+hDJmtap0xLZ/h0RXLTLfn8DXViYzavbFn3c7oeca0MfuoTnax4iv5opMuYzH2tbBf3gf1YivbyL9AfqmYD8qr5HhShuu2vlWi7M1Ywol3FpIOsxA0QtRb4BxmEnnFt+HVV2N9ax4V7Inp4ffKTZSMZa2enmwoJRkJxJK6ZhT0MBPAvi0G16BeJgM4nAD0SqdhtISFkQ1sJVBIEnLc3FZrA9NdJezfOYmdBuNtZGa8X3Fnh4lnq2zQS9TnLYiinIeDToKi7XCOIPAofqVR6/5Lb3lDdcOcVex+ayY/tjh6ZeMfGJzGenYyKnburvGGNQXnri1CRtu2LLgmisrRPbLy36t0h+fIetIBAIXCuANjAYIrC2p6buuQSpnT7ASGgLDAHpXLRcmKL2SDtgCL2UOOTGnQoVom7hOrQBFQjPkhXAVpE4zuABz+SQnyBYl/DK4mM/sjChIOuxCmQf7HJ9/cyWg3v2TF12hLBMhzcsA3z/96tqquKQN6+LnnGAy9Ww4rb3BqpQxqVqU0Of00EmqCoSsGq4OkcYh62HQ4WRUoaiS9tlEW0Vwy6RItJbGAvPlxKILG6NoczV5FyHHKP3g5Gg04NE2ZNl1Bv0jPvnaJgGG928O2VHrWu5LmXw/ia7jO8PE0IJxQT6f8ZjaAS2Kx7IuFSPFOV9h6ZzNPmid5Q3ZPGsDckAhz5Ve2T6wfjAXNMkqSeKImM8gniFAcAUzckKjQ7l2lh9Idi0OzEtSNr4f7Mk2PkAuFYHuzS2n5PYLeOViR97hLVLGepGoN83PE2kuAffQawhKBmqkKtxPXAzcbkU3i+s86wQqw5KISKdUu/DzZtl5Mx39lgZJGk9Id5cA03HlSZlcj6rP1E5IK8WUVzw+VN5Do28cb54+65dtY0m/fE+G44Bjj4y+ZyzX8p9a/5szDm17JkxLkioEJy1xVBi7NelCDbe/OQ8jY6W2oAxnuiUFZwmYFuyo/201Bc3qUhdkfjyHc2n1SJpA8Tp+FIYC/aCZgD7XhA380XgUi6GVDdeIhiz7AbVTMQrhQHdNZGFBuyRWQlwP54VRwkhd7kQ6JVGBS7FOY+cSkSjdyXvu+3FQ/9hIxL/hmSAez7UTKZPx45T0apP2n0ICRtE2IV9ocen6DtUjdLx1Q7RiVSXdEkBxUn7kKwamaldkHKI/10YiFUdFLxiCLb23sghZdNxPyRvnWA3xHorJZcYtlyI4zbj2Ek8EgtA2yDiOWzXil3qXZeGwkg/z7xzE7tUgqEUUSPRK0Yrq+HarktUQpwkcKnSVOpf/EZxoUGwwThhQ60AR79a/csj9zn/LBmPqIZsrXjhbixCrB2CI8LOpI2sT8npaRreWm5P72LGsD3/QtzdPiGtYWwOhE8ufzIbEw0af4owBuAZYCov2zGEu3l97JUH71P1PLYJ8K74G5/SJmwe2C65MKZG4NNAFOnIs+tSDWXozOHkfSrlHKo0FgqAm99Q3339ntFTG4zmF7zOhmGAQ4cmByb/Njc7M+eQW9ETjWwvSDcPRqSF6U/7+udAHGFEE2emaNvOkQUDlCZuW7JD6opxDQAa2gXBYbeXIgtUbQeI+gMVrO1NQgkW45Nntd5smhdzIKtTDhF34zl4l7zfcZ/KO2BFgiqVM1IfXiTYIxVPl2qHKgYGkQAZGJOwl7GBgoP4eaUEY/gO5WO9j/GWW5K/uPOV5Z/byMS/oVSgH3yk2To/EQfOVEgAOmPdBtFD0MEIhAqAJb9gDELG9puitCAQEHIlVLQ969AMOVTocT9fSFkQI4i2AXeiCUihTQSwclHClZlFf0d/wJTCOLhX3KPSFkdoQaji2QHSM07YQIanCNcJY0LNYU+VQ2w0Q/fHMRP47b+5kC4YIU7IixKqWd5YMMAcBshs5O14DrmNMHr1u0rBRif+DcMAx783//5j33XfVD8PyaYTwSFpxRMyoBJWD6A/wxsE/zekHOYc31l2G+nsnJujgZECzz0IDteKezP9KZ4ikbRSFlGSZ/j3WNsbTejmcDX65vlm9RBQnniBQNya8DWhA2KBQyLApThm5tC5wWxQkGe8XHg/BNAQOMNqEylAGbSBDgZQTehZihK0m9Nw7lYeFe90sG7QbOB3x7+a33XjVVvP9BngMhiBRx+dKFe+OTB7/lzswOcPosUBPRfqAPv+TVAIXg82LsEMrYiqGZ+KrYgCp+Pbn04ccqbmaWBrmSO2bUlr/PCiX9uf7UJUXKwKKkfSZjAQGTw2eIYEpmw3rHimjJ1LsFpwDgSPA+qNqDGIYeA7DrwTmFeAdZy4g+1RYx19xt9cjzRJGP3qZD1SdW2PNKD2gCmyLlWyHmGx472PieiGfdVPvPRnhn72Mpj6VeniZW8DPPyx5uyJk/GAOxsy4XC2kwS1gMc30VWpwYOVAeoJIqbQ20FAYBJgbUAss4FPST2iARUTSgd2M3BB5C0A2LxOES3bfJxNiJBHwsSPqG3OIxc+dbPKQOXBefSF8f9JZ8VCHyDFIek1qlTbDSDqQXhzXM3A+A1qGtQuEDO+g3kEq8DlUJhE4OxPSEHvB0PlAlYPw6xDsH1wLuMTwVZH2ODNbw82HNxhKU65rBng5P3Trzl5d+bDMHydKKH5yGGXHyYVhIxDvCMgMkhHHGxUIhCFoFCccFwABwgCf0O6Tk/M08jWcttoZW9KAoLT3h0QI8B00OfzxlGIZ+Leth7uezRkQRugl4sqg/vtKnR4/ryvSyJCXZMDqxgmCR4hvBvagMqF5+Cc/Y5PmmgsefB9ctaYQ0ErpjDjEXY+cLYENBcBKsq6Ei9Tr3j15PZrd+wYXxXRepk0ctkygFLK+94H6tH0NFGrBtef1vl1BQX9CakJKY9VgItTYVJMDc0E+/IG+h6OJ5kVAKoDVJ+oEVOu0aJ8KcOEWbYImenKJZqJdWAp42lvkBA+CFPwOFBZQPTtAlWua1YcMJ4GuLFkN2oLJD5WCOj3sAPwCQMb74N+QsWT1QbnwLAMnDOeHLCEGMyIlMHb08h55KMIABim4LJwALzfySIa7FCzoei5B2ofe8E/G/jXlwndrlo3L1sGeOwT9ePHj6s9yZTWa0GkIDK76gGIGgQE9UbryJpQocKAYEBoYBKoI6wKYEdIAy1gXqlHtMVFDR/t6RFbIYR71XM4uQbPA6SArwcRGqGLZ0FVAVPARoBk72yu0dkbWJ6NvgjsGUzDwTADrRbUaLdMMDxXmJ1dvVykV98LQxkesQhwjEiRk3GpWfCpmnepEnSUNoeS6D2/mN0UXp8051yWDHDk4fMvmvhy/otQfWDhgXCYAE3hKpaWxssCFYd3YDThfyEsEIquhIBCtEBELgx44rxTj8ip1GloKM+QAVF18Cy7bj+iqWAOqOOMrQHa0rhExeMoeH5xeWpDtbNPmADxoO+DEUMTMWbYhokvyGSxbQDCt2aTmQ9eJgOcg7HPJRCB/2klTPwKhF8KaNpLqOUTZSLiz3/74pPD+/btm1k1sXoZNXRZMsC9H2kl589EjpoT1UHjX0QaSkoivrMENsQPNUkIEoSF81g/QEiCF8I9cAuWjKGKVcCdrVJ+pMi4IakNBGM0aiXMFGCOgqfrBYn+DgaA+tWGHiPVMIppPvAZ3iAqj+B02OljPFa4J218g5DRvpQ90dWftaFt1xkSVQjvgcBuBDXINFYZytJ0WRN9M4D7lOhZN4Z//NPP3vhl0BfjycuOAY59sfq3hx9yXhLNJtQIO0Wi8II2EcuLwSUpXhYQJQgIyej2kcK9tX8CYXG1t3pEhYypx2nUrQFjEyxsSXtsGHINkJ0PYu+4LnVmVgeBaN8r6o0Q/lJCVK6Rd9FlFLWahnfjFQP/4H8Fk+Rdms8FdG5YR5Jjl6iBZBcKG+97dT5/GQnsVe/qZcUAjz4xcf3MFwqPzsxq1QcGJFQO6OCyaVx6hEAkw2HEbk85oNMPR6iJiWpt2nXILsRGxNatEyBhBInpRrevRxTOVWnbSLFtgGLgxOsjXiRcDf1dDHId5dXBqaUOrBR4FwHYyXexK5jQmxEjRLFKYIWyGQalzdnFySUZtc0SZR2K52NSLlF1UEv+WbM7vCwvb3re/PC+fcObUvWR+bisGOAfP9KKpyYjt14lVj9kZ0R5GanCJswgLyeenjSsUbVicjId4hQCRHuiGknbWAXyzZAyxYCqMRGSA4VosRrgb6g4ksoIAxzMgD6IqgKXrDCDGMUgboZtwDdvvFEgcsA2oHLZqpD8jWulr2CGLMMhdBsgflwX+Q5F9ZhqQ1maKxKd2uIy1geHmxC9sDDxe696xfa3rLpIvcwavGwY4MQ3K//z8Xvc11VmFQdtbGIF4RSM5LNlrbhF2eCFhEf2U2BlhzDeB0uAGQZ8NwY15hHBLhAhE6PnsCo07BNjaXA+9LXBCg+UztLSWxexSuVoYJx9LKbeYLWBaiIeJHGZulFCia+ZyJ4ovIsk3MCzJCodG71IlYwVNT2icCDLfT+zw6HZosuqT+SBOZLWJ1/uZy8zWr0k3b0sGEAplf2HD9Qb5yeJGphRcwihiKrQ7WUgGaF3Q51hAhSLUGgejAFPEVyPogoJ4YFh4OM3nhXk4YbT8xSUi7wCQKC24cy+rr0vwTJBYIJBqh423+jgecTmkFWGVwG4ao3El4iujfeHpNcb3mnm57gDkKCpGkNhxiHVSKhbDAF1AAAgAElEQVRSDLhvp7c5NFV2mfBh/OL47886X962bVvlklDUZdboZcEA932sWZkYj4vNmmYA23hMS1V7ZWDd2WBqNOVoBByrC5DYBniGOROia6tLlsHKg8T5tsDeEGXPzlEwVKA4oze6kNImUqcHl9s++6Z4mvCD6YP0m41sa3NrXGIb5ZDwUOlkZbO9PDhnMwCInHMEmgklA1maGiB6/AqPIpgOANd5RC8v1f7iF55X2vAw5175cN0zwLEHpn/+8JezHw2r1E7YaBOIEFMzZrCXED/0YDYMgX6E9EbEF/j3QKsToDhgY1gdMkTEtUJZsurVIm0DyIACUpCrhTRi6goJnl6Y0jae25NgcantucHzxK2JSyRQZu8bIOqPbeRK6iTek3kKaFDDRQh41YeyVCWHTl/h0KHtnSnOUlT//IszGuraP3gE1j0DwOc/cSpy7GwlEHrZZEC15xG5rAabL4YhfmslijJd3J8gPI2l72xizSqM5SKFdMUOijiEsGFvoLJ05twMbR0pMjQBun6bKY0dYUtx25BND7q9gslqIDBrMWrRB+kL3g3XQf3B39w3UH/BpWZTUbPoMyPNDhJ97xqPZrOdFeXD+05tyNImF8PL65oBDn26+vCxY84NM9OKbVPJUYHE4/LlRoUR6dsOCIUJxYHW6XGIP5+lpRXFlZeX64xJbHBDqOjQWQmwIghR4xOAsp1hRNPA1FieJLPAtCWLrEoevDKRxinJ80Sflwm025eVSfou7k1u3wf4T791C3sNwBipJ1QtB1yNem6U6KHdLk1Ysv7HSvUP/e6txdddDLFsxHvXLQOMHZq849gX8t9pVDTYbQFUwaAcRR/ubBdEnHTS8DXxw+BlH7/BxQAewUaxqZUPSQlJinZ4ReDisB0CBdELtoidi0Zc6y31iFWhfNbVcGVjbMv9jBMyK5Kt3gheCefEdSmEJckvAnGAHVELXPKihXYPfkeMC94ep+hRraUoynpUAdCurGhsp0ePjUJAAJaBKHkS/vAOT++C1z8WjMC6ZQCGO5yOnGaoKMa/lLdDVAdblWGJaurxgPCF8EBoIHT8DrVBSoXYpUyE6ERCC96GrzUqEKcjmn7A7Qp7YFcrpPFM0HZFgvCRg4xniqtS7xRpUKoGmgy1Bjq8eHYkm0zeE6ucrHC2miepi1HGoSRU5KB0YUQ0kwuoNaBoruDQt692qZnTxXUTl+hTO6f27N+95WSf9p88AuuSAY5+pfrpI/c7r5ybVjpbKeUpEWKxX4fVEiPRRZURvdn2lIDQ7ToNQshsD1h7gImOLa5G3Cc6uu2F2u4mNJFgcztN4OLREVelQBP0pni6j/bzbVshra6xuoMGDTfHgXZx+lCnfP03AHBTeZ/CnE+T24ge2eXS+KBOCsK/W4Yb3/3Itfln94m/+wisOwZ44Ilz26f/ZuBsbUa1vT52ppP9NyQoozmt3RZFrbHVChAhpLWtY4OuxLhcjjgWu26m0qJdRY0itn366KPe7d3hAJ2sVugDGAmqjXhwFnt2C1h/uF2h6+ddUk1kdSlyix7/7WRdSqoxzQZEjWKOpf/YDo/u37NwSu97xsbb2G65+VrJ7+uOAf7xI63o/LnIi1rEui0O8eoIBkagAyAiNoaNdIa0FhcorhXhKWoGVoL0CrDcYAnxpyU1PzNJaMB1KJ9xqNbUhA7iZx3dbE0KBpVVRj7xTMEfdXs+l0I0BWqDCJUtNJZfoaQEHpIFzCGhCoi/kKOGj82sHbpvl0PNvJb+UNfe2zh55yt+bM/X5BlfHRvLBc1msMMfagPgksBpb4MXNp2B5cbD/t0P3IbjxvAH8OGGqh1cOxvN1HHuRw8cmF9Jm2t97bpigGPfnn//2Pe9N8HrYyVgtY1FkaTotLgvRW0QwkPQSNyHIDJRS9IDC109zzutr3wIgK+frFbUTVsHnAiR3qQTDMNzOjvDaPtB+suFbE1FCuDj2HuDRBXY1wanzRWgERKWshVoEIk6fA0SfDTzNGDgBh7VBnyaHXLo0V0uncgTzfkOlQPNhC/aFd59v/KeX0E9VDQJHy6Yw9PPw94f+JQtsDkx3jqPoTHxP3IDIpB6r6b0oEKFOaJtgcNmS8lTCMPQsO80trTibzzz7NQvrocdJ1c++5eIRe+5555g/r6bW7MnIpozbh0QqejBMCxR04bLhjBuxuDcjZ4cJbr+DibMB8ozScj3XIqM77QVR4T91VE6EPcyoQJ3D91fosXWu7nmulgl5GGnSKVLLXoOEKgxDeYyNF2tK2m/EbUo52tHSxh3tPyIN75WFEehozj32LYg4MNPVL5QoDCK0Bf9ozUrsZflbw2VUNbF7mSKHLh5HXLmPZ+icpaq122hY1dGnPBeyzo0uls3A7i4Ug7qYLVdyL1Mn0CiUtCoXm5dcA3S6zFdw1lFo1mi7YFDIxnEZYiKBn+1pRl943Ul/wWO40iqxoqfczE3rBsG+O7fhWfPTdB2dzamOYgk9AzLf0jkBg4x7RhC5V3aYxAwkQtUKIgOyEuDtceEAzmMT67JY+gRuB6sEEHGoTjSoDqFVC6xmgOHMq2EWgggyx69XH65Q7RV5dADh8fojqddrcfdVNzCZLN7lNUXrlHeuQ9ZM9iko82h5pm4F0sEngG/pqZ1aiGTBRvUISfYPBr5/HGGqNnUwwBAnjdIVB3B7o1EJ3MJNQNF3tU+NUOH3xns1GgrKPrxGEcL79dmDKxIvBpwJT2iZkhko7jxfF61rGqTEpvhecCKhD4aMpZXLejdVnk6txUV7Ss4tDOrGEeFelw+qmY7RD9SD3/lZYOZ918MMV/IveuCAU79cPq133+g9KHJSaJKSxMtqwugDxdITIuIgcXB+o7JxRZHBkAGnkl0aQ/KB0R1LbD5WplMu9gbikMB3Lao2Ekr/cj2Ug5Xi9h95YAmfOOJ4UE017MdYvz/6D8uy+QMQxjIQqgcyjmKmkrbMEYz4f6yxwvElNH7gmXqeoo4tRFepIRoGqUPiw414QoloiPNkOJnZ6i4i2im5lCtoYmdUzTBd5L4b8qfoGAGiBYCwFY1hYDwG84LEcv59Heb4HIZ/Sxpj13S1mKXR0TaIRoqKLq64NC+nOL+DcGzpWFWNNqKv/H6kv+8CyHkC73nKWcApZT7d5+KwzNnlItCtU4joUZMpLIuB7UEFmBvQWq/bNFJqAqKgSDNORQ3FJVQDCoFRZZ78gpbnpocYkO3NeXwzpGYLwhu+HVA7DgQHINkxwbSU7NN2r4rT9Uq9vJVDIlY2BdcC2GuK7K1TBtCgHgG2y5KE64cYVFRUH3yVIjCxDaNEQhIv5x2FM17KHDl0Im5mB4frNFVLx1iAgTx4wDB4TtWUPleby4k6kzQ+b0XAgLjpGv94jlot9uB9nE9Vgrci+uwImC1vG5Q0Y1FfRdWgJLJVdjejP/+NWX/Jb30ZzWuecoZ4Htfaow9MeZdNVchiqEmVGNyhgNewgERlsDQQs154asvQIAq4OMTDiJBwkLSckDMJJjwnZZ0F8MaRA5ixz0g3rqRzvI7nvHVf7iX7nrWbZTLO1Svd9R1/AWm4WoMVvPtlEqkKhqGsb0/UUGRDwlvboLExHPEU4XVQ2IgUOfwDmdI0SRyEiKiqSSmQ82zdOB1V1K95VCz1bGd0RYkq6yQdhaobdyK9E1liS4YYFkR8Il/tlplXygqEogdK4G0acNY8Hc2Q1TMKrqh7NC2DFHZV1TyHGqidpPn0HX18Pd+ajCzJsk6TykDPPHw+PMe+uHw3WfPaEKHqgLijVCTE1XPjKSGGgO9XAYUG9xhO59uh43iFHW125WQzhmzJ5FN9HlH44xkBRBiPD/Xoh0jGYqeXO2cu2HjjbKQ9DA+LWLu1tckA+iG/gfCl+rkyFyEuoNz6DvkAn6bIEWoVT6rEpoOI5qr1Gjvz47S/EBHnRHVposG1+4CFkdTC4z1euj+oq6k9XzcJOfanwaXJYyRfje7/fRvYAD8832tnt46TLQ7p9geAGhRcpP++fHpm6+9duTB1ZDyS7XxlDGAUsr5ymei8PgJ8uabelBgmMoGztzpRPFmFqhm1j4EAh3G7AbkCK0RY0KEjN/hDSA0kTMRsUTXxhfDFLA6wKhsdbYbwu/a5u1ghtDWdD2iMtx5KHhlJL30xyZ8e6AV7Hg2pk0CC/Ru2CgomQiCw2oBBkCSekv3yc4fk9UEbcKunlSKJhTREQUVMabzMxVKhk/S1n95CzWaJgJtyjGK/i5MgFUABC9uz/RqaktoeQdRd2y1J33dUoSebse2H8A4aBfnRgqKbhvQ3qEyKlNbFFkPyHv3JfYOPWUM8Pg36n/7j4/6Lzkz1a7MvcBo4gEEvADS3uxeAj84VBshFKgpOaPTQ+qyf1w55Ds6FiDECkaAZIeuD5UI9+NvEELR2plOpD5LPTOD0HQeO3Kannbtrq4Gc+Ij8V2rTosdSUAEL6rK4X2goumdWUwYoI0QFcKUlsCM2OCyqhShVPMTIvnna1SZmaR9b72e85NxwOsFG8pWZSBlQVBiB1xyaWqcF8tJXJsBS3miqwcU3Vwm2pLp7HuMNsqt5PFfLHnXX8p+PyUMcOTI1JUPfbd8DCmOsw3ijR/Ea9N2CUByNsI28WMTZ2YEE+Fl4jfEzFLSEC2IGOdhaIoKJC8JJsHggyFA4ND18Rv+4T4wCe6zoc/jcyHtGAraKsOCdK3UzEBNgcPHtgPknHwyihM2QV6R19ABKvaQGvtBmBY5xyBu2AGzjqJDiuh4q0XVepPmxifpwBuvpuqwy4YuVrH0AekKY7MVaWkLY1R0cyNb2rcspsosdn45gkyrUWGTKOiSgYwVBWM1kCc6MKTo+gLRoK/nDYZxNVb0wlb06ucPZj663DMv9PenhAG+/rmodWRMBTpY3tuRGA5x4eO0DiF4nBJJbxvFQti2cQlpDdXJVjPsNuGtASPMgvgmp2jnaGfXmDiryGt2hs22ObBagIDN5urcJNAcTPCm/AqugY6PcyBuxjIZphGfv/QFCW0zjqJxRfRwGFIzimnizASFzuO07z+8pO3xsfvO6g4YMauJXvz2aRWkm/tzqZmA90Y8TN28QYvdK5Hl5drGCnb7FqI9ObXAK4T7Xn7o4ezBgwe7sHlvtLPcinTxrayghRPfq/6P7z+SecOJc5r7G4lDkMyYfBMHareG36GqiB4PohS8jejWttFq6+OCERJpzFLPcmvaRqL45PF72wNDRIePnaYb9u5KxW5196DWuMbFKG3r6hFax8dRM5vRiWtVtkVNSx2OxVmMgnbwHWrSCVL0mEpoHsk3M/NUGz9Ne999C7cPb4wYs+kpsAm+G8HCE4N7xa0Je2gx784KpnfZS7sxhHiGdpcVPa3sUMHTW1vlsZdDrBA1jt6WcS5J7dI1XQGwmcWJh4anT55S3mxdEyQmm111EHfI6zU+cjAF9HmoRD72vcp4/B0S1jXrubgIQW9gEuyIIkkm+E1cj4L1TztwbCYAw8B2QBtob7Ke0JWjHvv820hP40rFd4HqSL6ASHsYv1n4481qIH1sF98yBjpWBK4jatkq7Lo0JFSBR9hR9JBSNBVGVGuFNHVukq7+me3UuqqwQPqbLQJ6hjvIKrEstV7EBbBFWo2Ysvmli4LJI7hwdY7owKCiG4raPgpMlBjMvLcef/xVg/6rLqJLXW9dUwb45ufD+ZNnqDQ+o4M0MGo5ad10TQxJ0cFxHnp6hPIkKDtirsN5u+qCSG7djg5c2Ydg+dvJLqgi3d4tXROhfUxHRMcOH6KnH9jfPi3qkjATVixOtJFItK+xN/gdxA0GEMmO67jIlUXwnfjDQpsBPYfPf5YUjSmi0yqh89U6zc3MU338Qbr6N1/K7mJIb8Y9+dqNifHsprNfqB5/sYQGNK+BRvXcFBggFyi6fVjDJWAP4BDP0I+fm7julj3bDvXcYA8XrhkDnH5w+k333Fd6/7HTeoKZVE1lNhA+A9IMPkQYAfuXAPfD1wISbDxAkqoo78crAABnvEuvZhodVNIrAmQQpDtsBPwu0jhohpRkA/5NnonrJ+dC2jagV1xbhRIpLzp+G6djeY3gamRiNBOHa2xpb88JwyBMOfU2QyREFVJ0XhE9YlSfmbkKVU4fpat+45lM9FwYzPLdo81uMIWloAuL3bMUzXSLEXS7HgxZr7aoUF5ZFiaYFdHjnSWtCg34iqES9vF2JEms4rGqjS3Wr3tOny6cvXtrdXxcqw7zDZ2tBE8OJl7bAFq9EYgAzgmkAKqPRHTxDLkWxIrzck4IFud4AwhDwKLj2wQtqoaoSfjETurY9WUIerwZGdnfi8sWWkYtE5BpH0atELAJEOucANlTQKrFGUaxIQ7SDwgFuD2nkUhD1Pb6VGoNmp+Yov0/tY0aBwa6Gr6rSA+r0lSEldBo7M06UXYF5XcBrSjlFO3IEt06qL1C7MAw8zEYJg+/segdXJWOrlVZlO/8bTh+9DhtPTvd6bYYtyJ5YcziReU7Clc9KSfWfmtLNKeDUenvEhPgHRMlQtsKKcoErFaByJEbALXlvvsfomc/7SCBkLXR3EmhZEwbbzrR/UAbuE8+FwuSpe8WOALMILR9lBQ9FIU0W63T/OQshbPH6NrfeAG7RSFd04Esaa8btmetjNvlCFLecbnr8DtUoZKBSlyVVzScWgXumq6++BnbS1/qpa3lrrnkK8Cxx2Ze+N1vFL90etJsS2qkIUvxMKJW4C+AEbAxanAzqOrAm14YzD8bbyZiKiqNbcjiZb1WRE4GuHljPxij1g4yiRokn1zrBy7P+ZB2bQkoDDUzyGF7lHiCTD/g25fnc61Q6x69snXgzN0ywNgmMK5Q3kAv0VifR5KYqlFMk1OzVDt3lK55z7OZUcVLwz5+wEMsThTsX9orhH1fYDOsxQG9P2wpyiMTJnVgB1YgdjlQh3Gzw95dOgcmGCgousNEiQdkCTDXrpYqdMkZ4PMfD9Wps0hv1D0X/V0kMbw38PzYHhoQixNGFPseM4AckMb2XPrYaA67Olo2QnosbcIDQ8H4FQiyXAviB8qyjNazARuzIHLbjYl2QNAgcvYwmd95HgFpaOlzaAs95vSFlIdHjDnYCAJSw+2AOOP9K46iB5Wi8VZI03NVqk/P0s7nFch/7q6eVJ/ldP5LzQTLGb5gTmHU5VYEMDj+XTOgWBWyEaN4j1yszv1y3t1xse90SRngB19uPvDQ4+5N0xVt+NpQAzxYCIUlN7Ysxa6NnMiieAdHcaCB8BU2kjM7PwL7E5gaP7aaYXt7ug0M9HwYxml36Dz87cfO0I37dvJvdjaigf2381zslYH7bQJqYhhLcCvNrO3+GNWN8x1MrACJLwh4nVJEh8OQZqp1qkzOUGvmcbr+PS+lOYPs7HWywQgwKMFoi8UJem1rqetEqvM1iFvUI8oV7I2bFt4tDGDbCEu1D/UNqNGbBh2CKlTGJuMWxd7UjN79E+XgPRfzLpeMAY4eOvfse7818u0T4x18ijCAEI2oD04rIoXNaq0DRjC8NvCi4KVZNTJ6u4N9s8x2R91WUpuA5Xfsowj3KLIOkRYpBwh+ei6krQMBS3yR/HZfxNePfkDtMWVGdX0hI+nRosQgwAzot7wfr/om3gGVJxom8qdM5lWs8f0ziuhx4/WZOD9DjfGjdOA/PYfmrIp46NNiPvynSvq36kReQASiXsrYbTWQGITYAFFgnENQg1pNoky2k4tsrxJ4X6hCg3lFzxp0aDB4sj3w0yfHd1x99fZzF8oEl4YBlHK+9tmoNXaMfIE7gPih7kD10eqEDjDBFpDNIGyVhCu2md0bUb4cORcg+lKzRa1chr0/OlBG5DdbFCG0ucjbpA1qIUwY3uOxQ+dOzdBVe4ba0lzUHPRnUUlueRDS9oXtOpWtevEpxAvJzFluALqB0YnoBwkCXiFNTc9Tc26edj8nR87zdzOUYSkdfi2CWksR13KqjNy7HCRC0qUlQV/ukyjx3rKiZw7ps7ZR7ClqvDXrrMDPtPBtLgkDPPL12pfveyy4a3JKZ02BIODyNCtlW/WRh7vYyRw5AM2QWtmgTcdCWLINEvz2obWbZ4hNrq19c1kamxlxjI8fz2TpHMUMZ8bfAkyDxJ+eatAVW3LsvRHG4JReE7RCH7h6BKADgG1bXiHkKGRh2FnuThleIfj2KmDS/oDYBO4F6g8YAdL/iCI6yV6fBlWmZqk1c5RueOcLqJrRhu5iySpPldRvr54m2FWZrVNpcCEN2ok2ac89agYIoeM3e3WQVQKfckAVwrRfb6LENgPMhopuTJK//Lmy/8oLWQVWnQEOHx6/9tHvDx86dNyUFDQgNfbKpIxYtgEQCEp0EgyMVFs9YYbhims6UcIGubmtiBJLbbKlrjAKzokhirZA5DCmTEoxTc+1aPuWDM0aA12MXAHOSa66nb4ogyxtpKPI+B0qEiSzVDfhdzRJ+vqdOq7V46Taqg8SXGZPHKaD77iDJk1dAMmLBpdJIArn8OpAel4IE6zmqgE7IJPpnlsMqQ7D2CZmvL/YDml1h8euC3IUhjOYYCCn6GBZ2wM516H5GDWT9Jz+wpnpg/v3b3l4pUyw6gzwd/87jI+dJLfa6iSkpLOrdIKK1u1jIypwToJOXP3Z7MqC5PW2rWD0bNG7RRqz18i8uQ2qk+AVJL2tr3M0FT7/++6nZ9/6I6yCMcbI0uflmcxABrfTjRHE3hAcD+4TAmsn96fSJGF0g4HOO4qOGZgzB7zGz9PWZ3jk3XkNCwa7AsNKJ3ap67slwFxo+2EroiBlvy3XFlYAz5h83QxiqSoj17AqarxCSKB5wQiAcpgvhyHTEDg7cw5diGt0VRngni/NvufwWOFdEzMaySjoTfH2SMQXsAYmDux+0oqo5vtSCK0dUZX0QBClDS8G4YKwBXMjkVdbsuN5IGpIZwlKifqF9iA1GtWQtmzzKap38gZwTTp4JQZ7WteV8wy54FpE5tOsVKL7S6ohViOpSoF+AeN/Gsk2YYsarYiBbuH043Tte19CNfjMkeBiql4wSiSVbLIcEXOqIxfiWo4cL/z3VjOhjKlg10srIHYQta0Spcd1KVcqosTwbu0uansATFAxYzQSOLS1GX3l35SDF/bSF7lmVRkAPn+oPjgg9QVazGXH2YPiUNBoUZjLsFuRUYxG8oOobQkswSepKILvYArB2Jjy+PwstCHBQhA+7hEGklXCVpEqlZBGhwKCpBGQucYL6XiAnZYnKo3ZTKaNeWAmM/WH0oPIW69CzTFtgfC5bhDq5hj4BFQfBLwqgDlPz1F94jQdeMdtVDEJ5V1qdfG72qtL+hpISa6DtAaBr5VCHITgFlSeMy66pQxkWyXCuyHPAWN5+4jOJU5HiX98fOaWW3YP/7BXJlg1Bhj74eRrv/ntgQ+dn1/o7093RKK7XObDRFWhooBohcg7eKBOJDXdjrgrobeDeMU/b8OOxQawGQKM98jDj9JNBw90HaPlaocuVy2NIc6m+BSYHp4eeHxwTrLB5hxFT9iqz8QUXXGrR+5L9nF2l12/qNeJXMvrIMkbtQaVBi1LdZkOCJFjn7fsRWzSBGMYqxvqC901QpTzUHaxQ8aeUo23Zt2evUKrxgCf/NNQnZ/RBawk33bBMmNgDeLZ0bifTnYUiF985+JTBzFDynLqnFED0KZAkMV9Kvq6fMfqYHtroCNipYb0rcxrpGc6oCUrxAKXplRmgFfIoC8lP18IPV2cgs/DSLV2YwUTGAQAqz4gfnh9pit1qtcaVD/1D3T1f/mpZV2eNo1BGgIKvVi+74UYx70yUdjq+PJ7uWc5V+liKZNLtT1UIrqmrOhgSavIUlcI99zWTH75RWXvD3rp26oxwIf/IFTw+YtvXx5uQxFsA1VUAUhFSF3YDAwblmXeUol4AJOEAtT6NNFXieaK2iRoSjyXty81Kha+M2wBQLNGRNVKhUa3aYey6NbijRK9XfouKpoQrwwWGArBnxg6rTGQ8UwQHe9GY5YwXMflCI09Mm8CXo8arM98pU4zJw7RwV+9g2pFnXJoE24v3hq7xg8/6xLq/FAZ8S/t1emF0CAB2mqoGUjBBQmDIDC2ILoM9RiuVhQVsIr4SmxgwATItmbUAgZAf3o1iFeFAcbGx3d85sPDZ0C80PNtLw3r+MYFCVUE38EIAn0WG0DwNZDiICZxRbK/3DICoeszkzBsuFNHhifBSGwQvGRx4X6GJ6C0yMQ07dg9zK45WTVwG+/PK356vZkkX8+rgVkSmNDMtcI8bQPZuDbFEAbhckFa8y4VbkvRrEN0PKX6bHm6osEX7aca7klVdbAJK53YIlCHpQpa9USYPV6EMYPvvl4NqWhyJXq8dcFli8Eg2GBH/dFFkBS25wgNYowHikQ/MkS0L89pk7zqyvHMWuun7xzKfmq5Pq4KAxw+PHXT5z5dfgBRXZHM6aCV+MtFz2dpYEVHUfXByQIHqg9bJWGjNNLBMiFYjtaaZBKoN+zaND54DISoIiBuEG/VUn0Eh8MZVcY/L2qW7dFh/72R8OhD2r2JPqaltDAWzk9jh0rAOUz1CXh9DiUxnZurUrPRotqp++na9/64hldcoE9/uQlezd+XA7t1e9ZS6o8t7dOSX+5LA+jwDPEiASZxLUosllBtWqdQQvhh7DOKpt+cdTrVDBYZiFVjgE98svwAngyIAqK+Aj8Qrw90bnmYTdwMC0ClMBSVxabTVnIJG7cm2or+S5KJ3Wk7Wiw7rUu0laOMKC3SjGhs7Am66eB1C4YBKgoYwPAXM50EsNhLZVYfXCOHVJ6W33FzOwYBCWYYBm0B3ZlVDsMdgPUR1ef85Cw1zp+iG992O503iD+po2lHUFeTeFejrbAZUWBqNPXS3nK6/1JtdAuSpa+HR2gbssdKDrtEt2ZQNVuXV0SZxf+cT/vznvzEVWGA06fnRv/XB9wJbNUjR7bWZPQmqtxQbgAAABvWSURBVLdJ0oltmOJK1HG1t/+Ue1l3NsWecM6OIAsQzX4VGWgYzLIMMnEbSEOzGlIJu0YYghbdHszRzYgV7w1gDrYqw8Y1jE/DECaPnz09dUdRHjgnh2iS9N9SCucYKQJaC3V9GoA6n5mg3NVNtfVfHnSk1EgvBIVreqnGlm5rtfKCV1IOpVElyhXhLSLKGa8PIA/Q5W0vUC+Ebr9PWhXaNaLoQNGhskecQsnVuM38/HZxjRgAHfytt4wrZ3CAgOtp5QE81oYoG4dENG8wMLADYAMwOVr4epMSrI1A49sHYYk+LmlxeEGJvfCOQakCsCBOvDZLaOzeUototOzz6mLHAvC3+OnRPzxX1BecF78/+sBxBo/oPKQL9v2SvpuanSB09I/tG7NSsZfLVIOeVEQPRRE1UdIQ0v/cEbr+XT9Gs8Z2sCd4Me+NZHst9ntaFbO/X6xHqFf4srxHmkhxvttqkFZ70ozLSFEgR5cQ0+UC0W0mMmwHTPH+fzS4fP7wqqwA6Phv/NKp8VZpcGtuoLMKgAig30pQC8SLB0JKiiqB39huML8JpFh0cUhpLG/IlhJ1B8TJsGLjZRE3sCSayISHrZiOPHGIbrqx4/PnrYYso1quFdUF39EupD8TvzGQIVXAcHLI9QK3kOT7tksWwUCj+oxxaZOQAHeYPX2Kbvrlm2k892SQ23LR3V5XidW+rjrbpOJgl9JuSz3IljZdGKA9B6ZmKZqyGcL2EEkmmZCzzUywA24cVrwqQ+MRgnaUiv58FBs7LX2sGgMcOnTumg/+Sf5wrpRhbL+ozRyUNIPBXpNWTCoAOt94e8x3diFaFRKgwogx23ahWfq2SO92fR6zciDcGnmejjxXQxodCbjsIphD2u/mauSBMNTLG7dYxao40YazvRT55FBIioq8hZJ2e2KlQxag5AuDT1qOInh/ThDR2SiiqUqNk9uLV8zQyL++fckMr15Vll7cpMsRwFK/C0FeiNol7dqrR7eVQX5nl7LJK2C3p+UNgXsU7lDpBys2CID6Ol/gqrJigSWqKX7OuZR8YjQNrn7y264aA6DpX/33p5o0MJgJSlneLDpdyw4MAMJkSWqpHKLXsy8fLys2gLV7EfviDYOIILbVJryIMAMIHXo/sCNuEDBhimQW20J0e9Hp5V55jmiPVdgSxo8vaFR8YmVmJjZeJOwKiRNsOBMRor2o5vxYGFIjAtxhnhrjY7Tvvc9dkvh7JTbZygiPBSr0Uh3Q2wvF7mjPbs8E04henyY/wfyDoNvKibVSyH22B4jHFLv/AEZiQHTyHWNQyBMhV8DwRHs+bg5b//v9V+V+ZrlxWVUGOHToUPZ/fnB7wwG2fyDH+3GBESQABsJm/d8QuXCsGKxsExj9myfW+E4xWKLmiK4uiFGOEUDE5/x2aXVce+TwCbpu/542XkcGQiSLPEegyvhdnscSHIn5DlHZlFdpkKIcwaUJw13r/AhsDSPFUmIFfF9H9ZEML1Z9Th6lg2+5lSawKFuFa5ebIPyeZoqL1el7eWadNzztnuC+2P1tCZ9Sf7pdb6sx7MKGvm/UQskRkIK6wizIPsOBgltYOcAcQ8MaFsGEPONQbkTXFv1Q61h+3759Zr+cxd94VRkAj/mz3z/7sfvHsj9Ho2XKhBE5OZ+BYVBnBLkp0lYkPmNnjAojObhp0JfsU2fBPtp714nUxjNwP6R/Gu4gHht7KHjTDUArjL6P32DEQttlDxUCeCACcggMkKWOjintcFlyGMOOopxyOLcXbR62Al71ap1azmN0w+vuars9u01JN5VG7IJeV4ZeiLuXa8SL08u1cs1KIA1pTBDAdUiVhHTHqgM1SCS9QC/wCWYAo4BB4U0qoaJ0RlE9dqiQVRTPOrR3S9T8q11BT0ClVWcADMabXndceQNlymI/5ozPRiWkPvb+wgPheoQaxBh9Y3TivrZBauXB4py4LSU6K6oPe4iszdgUEiSaEc3MTNMVu7cxdYswYvXFbBpnqztslJvnsd1ganIWTURbGAL9mCNFQ6g2ByYzjAKJD3UI7eNZUH2mEfBSCc21Qu31GT9K+977HKYT7JO1VtHblRAvrhUJjv5V556c5bXS9kTKyw6T9v1c38ioN7JHMkeCjdmKvogNYucHsJMCO4dCjXKJSkWifIMoQsYeoNYJ0Uf9qcE79m+Z66W/l4QBnnji3Pb/9j7vbHYwR1ExxwSGnUIFpAbPkGB3RIIyA4geD0xA4D0JFyOS3lYBONkdxGoivnPTDRoZyemNpcVukHKFRl+3IQtwzyJnANcKZkfiFVLrx0aY4tl6+yJstKHzm6Gp8f69qCznKDoK47fVonlUdzg/TQf+v2tpNn9hG1WsxPfey4T3cg0ILpvtXe/nNk3uQVrvT3vc5Pk4z/8MI0i2GyQ/GAY7foq6IwyAa8MW9oN2yMWWtzB8MxoiDTvNTYgGm43HvndbvjvUt8vLXxIGwHM++Hun/uLR44VXBfmA4oxPQU6DPPB/LixroTwXELZkYFkux3Q4QxhAXKV4CVF9tg4F7SQJRITBfEifhGdK7pP2OIBFigY91Cbt1AGyCR5eHjF4RUXBc8X9CVWJ7RVHUZWIzmAbIwS8miFNn52g/I5JGn3ts3gbI0itXuvyr4WevxgzAO+TLy7rQXzS7WLEsiFscD2svxsq43qmSGAxSTE2A0hj8OxAwkMdAuELjJq3eEJsCQMO5ECgN93AnHA9BMxTi+hjN1PmdsfpOSPikjEAXujNrzueuOWy4xcz5JW0H1k8PEK8eAExgttZVAYJyohOK0gVAQ/kum13l8QBEHxDew8+9BA9/ek3a2iDeIwMPBl6vsQA2jYI8hAMFEHsAfHng/9qjqKSchjSUDAqEVQeEDswPmAMqEL4hEGMgJf4/M+dHqfW3Bhd95/upNlVQGheqA3QdhqIW3GZJQC6db7UyzrRucZOaEkbt+lQFH6HyxOSO11XiOcFq7VxiQqyVRlbDTZG26Xua1sA1yNIiVX91X7t9e/cX/zTlfT+kjLAYydOXPGB92ZPZuARGsob96d+pF9vkVfKsOeFpZ3JA5YsJ47ESklxEHOjRXEuo4kfVSRct+3XR3tzMw3aOZIjuC3bkWBzfzszy/LWsMpjorUwXiEyYMiOiFcHS7px1UKvRwQ4MME4e9DQDtQeGPgnDdyBvT6njtENb72lDXPutbLySiZvta+9ELCb3Qch3HS/IMnFkBW053IgOYkPcBol9jOGBwgqU1NRbhgq0EIUwK5mdO7eZwUrrhR3SRkAA/GHv3n2m2Pj2ecGhQypUpZhzFydzRC84HUETw+1BOVRklzAniEGxxkkqESPpeoC2sfOkgC7lYseJY7bxt8LklO8SgJ94G06LbhyjO9GQouQtGMGMpniFcKnxETlOiS5wPCF23Oy1qDJ0+M0emNE+Z880FNJw5US8mpHjIU4a/MRFcqLV3brtZ/iDRKjGt+hsrD91CDKwj8Dh4SVHM/7HSBYaQieGcUUBgATYLVQdUVO3mGpL3gimYMzT18e9tCt/5ecAfDQt7zhdEKFPKtCUS7DFR9UnJDva4JlCENTV2sWrw1L+qYufSJRYokuS36ANrwUnTx2gq699kq+105Z5MExxrCdpI6/pVK0eHPsIBd7loxqY3t7JM8BGB+gPKH6QE06h5KGKqHZMGKvT+3k9+n6//xPaR7tGOZquzNNhBnjcqFqzWKEuJzdwHqy3z1neDlczmLPhMsSxCqS3f4OY5ZNAKi5JpILaS5eH/wuVeLQPvrAAVKzcoNx0C6rPuaA5EclOQmkgk9+J5h85s/eNPr9XhnUvm5NGOCb9526/lP/PXg0t6VAYSGra35Gsa7rw3q+YinvGM8PXESJ55Ib6g2x2cVoxHMS64pxEjeAz39gq6nuIFgjy+sjTMGrhfldVCScEzemGLjw98Obw0nyxsMjWWicuWZ+BzPATJxw9E4up0Jdznzm+GE6+LZnUrVEVAf6Ee0AVWoVEBKp1SvkYbmJtTEyy1272O+AOmfz/ooyyiTqK3sB2G1zzoVJ8IH6B4KFAcxwh5b+tGW2GMTiRQKTAAzHwg/GsIrIcXxmGOQFo4Axfrshrnz963eUn3+h770mDIDO/dFvnf6bQycyL2XXaC7DyS0c1EKBKxA7DFnePdlsGC05xMY/6UJcoOy5AbLhVmB9zpw7Sddcs699niVJGJMHy8gY3fjEiiKrAfbdRXBLiF/gDLL6QOAIA/B2TdirGIW7PG0r6BVCUcMhxvk/GEc0U2vQzNnztO8VIxQfHKGqJbVkcpaT0Bc6iRd7H1SMXK53D5X46LttfSqS3HaHghFkk24ui24G2k5zxH3teIABOSI4ZkN4UXsUzMMhLqhHEdHEMy9M9WnPycUO3kruf/MbzikXHqFyjrPBVC4gB6wMFB8kPbuDdEk0jJHt/pS4gUSEo0bEQa/hkRxhVYhRY4jVKW1jCJwZ99mReQ68GXeQ52OfXUUZTqAw3rpUdTpRhzhoZ70sVgJsXn0cO7iHIU3PVGh+9nt07dtfThVMktmbV25Zid4uK8RKxvZSXCv6OD4jxAZ6rrWgo7lcAwhuaGvghMh5fq3zEv3luUcSE++DZjwR0PnzRCVIfZ+oagzgT3vntj/3aTvGL+bd12wFQCe/8J1Tz/37DwffhCoE6o4Dn3yj4oiLUoheYgO4T4hZiJ/98LWQRgcCzjOwoRCSFMO5xDCq4oSlN1Co2GmSP9FgN8FhOAVGLbxBU4gRmJqmYE43gLKmvT44YPg+aJCe8Prs/fVbKAiJZrvVSzSzBFw/u36Nr9z2Ti63m0vaZui2+cVKEaLVuQYVLQh7N2JazDvEnhrohSm3GBu3qTACoA9ghgUJ9Wa8mfhNsIXdoLFmIDkyYDyz8ot36FnN2T/97HOGXn8xxI9715QB8MB3/vsTJ2tB6QoOkOUCdmcKMWLyJC6Aa/G3eIfEPcp5AM2IDh16nG646cYF749r25h9M2BCEKB3SBQB0eGZgvGHClQzeB4xbqWuJxOoAtYkoQJWJxC+o3gTbYiew9Uazc5WaOfLBsi5tsSEzZNo7cGbnqTlCL3bpC5lMHfbGmkxdatXNczO5DJD0JYZ4sIUYky7NHspm5KOEAvBi+3AqlTSItfNsPQX2AT6PxwTPXzbxak+T4kKZB7q/Mr/O5k4oLpiltg9GkZ6F0hseuF7vFkGZX1KooSg+yeZgJym3jEGEh3lTQaHsjqjC4C7jK/LoAgmxypVyAwV6VqjoiLxrjMSDZNOGXEghjJWEKQ5IgAGT0+mEVMm63OSCyK+JxD0arU4yWXmxBN07TtvpxCgOmxe3WPQ6WKl18Xe36i2KFfsfSdHCXjZ/n6p7CwuSymbIlJd1Bz26BjDVwJhbdiz2fvMQbAyJAqriu0tGGIcEQY5wF0NmIRLdPpp2B/FWRUQ+JqvAKwKffnEy7746fwXYBDH2YASED10vxAbneodJJXnccwAA8EYIUTMAo8NX+VGVCjkF8ApeMWIYt7pkQvrWgJCcEJScU28QCzluWSLIhRlBwQaCS8RoM8w3LBTOSnKQP3xXGp4APQpmnEdekIldGK2QrX5Kt34yh00vzugeQPSsmvzQPdHVxZ4gVJ1PnshZFtyy98SXLN/E89SL5J+pW5YxuQD4oDCtCaC3nZvArNv1e7p9k6M6UFQ0yS+iHcIKwnPMVRWc402AomKyMQLNNYHJSdfn59/w2/dNPDBXsasl2ueEgZAx977llOPTMeFA1gBoAolgDiohN2h8PYoUyYFpdPhMYqbETVDRVNTM7Rrz1ZNVK1Eg+AwcshCM1lZWDlg4NrYncV0Y4E+iEcI3+Hn9yJF2Loo8l3yWjGFGZezv+phQmdaEY1RQlOtFs2dG6crf+0AJ6XAUBSi4oCbIQoxwm0mWA7jvxQBL0fcyxE2G6Io1bI6WkTb3Ynm7GQVSHjG9VjZlNql2dlGlaERBvMjblUhXEj/IpCpxpDe1awf+8FzClf1Qti9XvOUMcC73/1ud27ql2Jsfu2MFLUtgHkx26NCPw/DhHwTDm4h4tuIaGQgoIoxMhExxgrC+qHU7TEeIY4gG1WIK7lZniXZqFsGCc1xTSErwCWBMawKGdJqUKOR0Ezg0NFKjU4A7jw+SVF1nKHO3WDOyEjDefsAkbC0W0RNWqkHaCmjdzHPU3UupNGtwZP61ivRpJPkxUiWz26pj6zTG9iLuEQ5AQqqkYkTcNWIpiIHtS9BC1pL5pxx+C1O3LJKHGu96FPGAOjD333hxP/z939f+nMYxGExR5kk5t3j4R1CbAD4fs93eFUAMxw+8hjdfPNBBs/hkMyytkFjqRZ2kgt+l2oRILz2TvWINcSKXN9lAoe+3zQE344Qm5jCdBTTjO/ShMH7yD5embEn6Iq/+LklIQ9gBKhfi9XxXMwoXuw80I+wNVbiWu2VuBe7bimQXLeqEWkjeqnnc5Vu8frAHmhpyAOMXzGovzI6ddPBvVseutj3SN//lDIAOvP2XziahLmykyllqREElE1iSjDzBiAH0R41Y2o1QhodznGhLCiLWDHEZSq4H5b6kOSRIqws8Bb5qDYHW4JFikOxIWjJ9MIndEuoOxkFpKeODg+Qw8ktxcShCphSKXoicGl+rkLHHMUJ7sFDD5OqHFX7/vyXnGlfw3Kxly+6D3UH/0SiS1wgrb4s58FZjSivTDqIqVnvJLosp0qliWW58ig9g+kkV8PEaHSqo6IkDsn1OkY5r9ou0a2q+vkvPaf0itUmfiaJS9HoStpUSjlvfuP5xMv75G0tc4xbgWhrIa8EDJpqxW3VRwKJnB1mPEdt/AgnV+g8ZCwSwPuA6AWCbWsdYZRQ4LuET8Yk8RKt7Q2uWxom1FQJhb5LUaRoqtaiEyrU25lOzVI4d4b8sftppHYiLv32u7zqXq3oLjBWO6VF29Jafk/r6fiOVQq2hB1EA1NDRUjXDUU7xivLZRV7OdoYfdmaKrUDZS9tXMg1C4pfGYMIqg/eAUyJjKKkAaGlQ/BOxmHvD95vOEmih5+RjipcSC+63/OUMwC6dffdp5/3/388c3cwUqSc73CNUIScEJ2FSB87fJiuO7CfQhMJE+ONq0AbQm7BXvAdioEVksrQgC/EMSlYZuZNpZ6Pljk6chzBRRonjApNAldjgMKYWo5D9URRrRnR2SikSqVBx1VEk1PH2sT/vKc97d7/+Ot3/fxdM6OPYcm2jeBudf5FZ+8Gj+7m6QEz2MR/MfihKIzJNxCR1SOhVEt22D31kxjA7IU20V6oO8qUD1FwVxcccnyHUNEHoLezt66+3m93a10wADr0zrd/9+NHZ/f87Gh5gOsGuagdBDWmEdHQQIaqoVZrYD3ik7E9wNTBW+M71EKqnAH8wAuEyhS4jv92HO1XxmH8/6xJYZXJeZRzXF5NolZEju+RD1UHnp9Y0VyIim4JnZmp0ANRlY1ekfw3X3X1mS/+zev3oNmbPjr58MmrR25AMomoNXY1N1wDYoYdgJ6IVLdXAiF2WZolVNHOkTDwiuXSJLt5gdg/D5/6KiTnCAF1iwvYxIXfGeZs75mMjfNMRYekqiiqxGyH4Vq/6FIQOBTD9e0T/XBfb5UdLoaZ1w0D4CXe9Pp7Hz4X77phy/YB9vvCEK5Upqg8uIXBbey/h5enFWuCZgwEkZsBrFozi5yHKgSYRdxKyM/7fA+YoX0Y5Rzt4h78FiPvgEFZimphTOdJ0fjZGc75nahV6PyJQ0QzT6hyMJFcMbp18t5vvHm7PfhbP3iu3rpmay436LQxMJDYUvmZVx2UdjRGrNzLMs7oxd1sAvu6C0moXyr55EKJB9AGQBRs+Ww/B0A2oERB7IA/4DpdakXj+sPZmMK5mIpRRTXmYqc8RKqZLzheHKtWueTcveeHxdtvv712of3r9b51xQDo9Cvf/Jmfaow97f+M7hrgWvQ7rxikWq1FhUKGkBKJgWw2QyoUslStNimbRb6A3gHe9z2KEAxrRlQsZqlSaVLJpGLKgMzN6eIymQyuTSiT8fl63I+j6rvUQC2jiQpNJTFNVufpuDdDQ4+covr0V5mDbtm/879+6+vveke3Qb7rl/76td993o9/yC955KJqsZVeuBLPSK8T2Mt13fbx7eW+9DXdIA5p5hK/Pie3IzCI4KEh46SiJX44n5A6O0NqGhkTwAElVBiIlSqNUL7W/N6Zt155x4X070LuWXcMIC/xjBf+4YeHys97DbCdWWfQwJthHHkclZWjVQ9Z0ss5eIuwbSerS6mjXmlygAwlvhlmAWLHfq5cKDchFxtn1yqkopgqM+dpzK3S8MQszYdnklAd5eve+c5Xjb7jF39sernBfubbv/Er/zhy4Hf9vYOUGfFZt8XBcQD8Q0kXg0BFdhOw70BBMijM+MwZAW6SP/h1ZFNpc45hyZGWtAlq5piaqYAUiJEJ6QtUJl+LeAjgBiZhHec4omt2E4HqBCnN+bowxgPEYnTkFs8AA4inB0YqVgF+B2uDEXuFgooTVxNeUVvnGkTzdVITc0SVOUrmz8DTwDVyHNejggru/9xr/NvuvPPOHk365Wagt9/XLQNI9//gz7+6+zN/dt9vnZ8a/Mmp8145k81TGDYpWAyMbm6Mwhb5droRqx/do5+tVoM8z6dq8zwrImECcESdMt786S1D2e9s3bP9Wx/+/X/yx/v37++C8l96oO9RKvi3r/3cG+8f2f0+tfPKjK4R0x1/w0wLakIMxNQ75E1DsPEa6B+1+dmeMTa9YfIEQCgEAuEVWOSQyDpn0QGxB5wBrGveasZ84qwPj4zeOIFLGNqVyJahKcRwEinZXWmQmq1ywEKdmiRVO0/+uQnKTUypfHY2cZOIMnv2fepVL9v9q7/95pdp6fIUHOueAZ6CMbnkj/zcPfcUvvPNmXKlGm89/8TYSORkS6f80eFWS62sFkmSFJTrNl2VAA2SVZR4zdhzkFunKM4r5YSO6zZJxT3Veagn/pZ2kTDXpUYU5xPHfdKejq1mNNBtkDJZfy5Tn2sVSvmjeyvHxka25I4P771qcnDAP//in7xm9s4eShVe8sFPPaDPAGs94v3nrasR6DPAupqOfmfWegT6DLDWI95/3roagT4DrKvp6HdmrUegzwBrPeL9562rEegzwLqajn5n1noE+gyw1iPef966GoE+A6yr6eh3Zq1HoM8Aaz3i/eetqxHoM8C6mo5+Z9Z6BPoMsNYj3n/euhqBPgOsq+nod2atR6DPAGs94v3nrasR6DPAupqOfmfWegT6DLDWI95/3roagT4DrKvp6HdmrUegzwBrPeL9562rEegzwLqajn5n1noE+gyw1iPef966GoE+A6yr6eh3Zq1HoM8Aaz3i/eetqxHoM8C6mo5+Z9Z6BPoMsNYj3n/euhqBPgOsq+nod2atR6DPAGs94v3nrasR6DPAupqOfmfWegT6DLDWI95/3roagT4DrKvp6HdmrUegzwBrPeL9562rEegzwLqajn5n1noE+gyw1iPef966GoH/C1zAroKuwIcHAAAAAElFTkSuQmCC`;\n"
  },
  {
    "path": "playground/src/views/demos/features/file-download/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\n\nimport { Page } from '@vben/common-ui';\nimport {\n  downloadFileFromBase64,\n  downloadFileFromBlobPart,\n  downloadFileFromImageUrl,\n  downloadFileFromUrl,\n} from '@vben/utils';\n\nimport { Button, Card } from 'ant-design-vue';\n\nimport { downloadFile1, downloadFile2 } from '#/api/examples/download';\n\nimport imageBase64 from './base64';\n\nconst downloadResult = ref('');\n\nfunction getBlob() {\n  downloadFile1().then((res) => {\n    downloadResult.value = `获取Blob成功，长度：${res.size}`;\n  });\n}\n\nfunction getResponse() {\n  downloadFile2().then((res) => {\n    downloadResult.value = `获取Response成功，headers：${JSON.stringify(res.headers)},长度：${res.data.size}`;\n  });\n}\n</script>\n\n<template>\n  <Page title=\"文件下载示例\">\n    <Card title=\"根据文件地址下载文件\">\n      <Button\n        type=\"primary\"\n        @click=\"\n          downloadFileFromUrl({\n            source:\n              'https://codeload.github.com/vbenjs/vue-vben-admin-doc/zip/main',\n            target: '_self',\n          })\n        \"\n      >\n        Download File\n      </Button>\n    </Card>\n\n    <Card class=\"my-5\" title=\"根据地址下载图片\">\n      <Button\n        type=\"primary\"\n        @click=\"\n          downloadFileFromImageUrl({\n            source:\n              'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',\n            fileName: 'vben-logo.png',\n          })\n        \"\n      >\n        Download File\n      </Button>\n    </Card>\n\n    <Card class=\"my-5\" title=\"base64流下载\">\n      <Button\n        type=\"primary\"\n        @click=\"\n          downloadFileFromBase64({\n            source: imageBase64,\n            fileName: 'image.png',\n          })\n        \"\n      >\n        Download Image\n      </Button>\n    </Card>\n    <Card class=\"my-5\" title=\"文本下载\">\n      <Button\n        type=\"primary\"\n        @click=\"\n          downloadFileFromBlobPart({\n            source: 'text content',\n            fileName: 'test.txt',\n          })\n        \"\n      >\n        Download TxT\n      </Button>\n    </Card>\n\n    <Card class=\"my-5\" title=\"Request download\">\n      <Button type=\"primary\" @click=\"getBlob\"> 获取Blob </Button>\n      <Button type=\"primary\" class=\"ml-4\" @click=\"getResponse\">\n        获取Response\n      </Button>\n      <div class=\"mt-4\">{{ downloadResult }}</div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/full-screen/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { useFullscreen } from '@vueuse/core';\nimport { Button, Card } from 'ant-design-vue';\n\nconst domRef = ref<HTMLElement>();\n\nconst { enter, exit, isFullscreen, toggle } = useFullscreen();\n\nconst { isFullscreen: isDomFullscreen, toggle: toggleDom } =\n  useFullscreen(domRef);\n</script>\n\n<template>\n  <Page title=\"全屏示例\">\n    <Card title=\"Window Full Screen\">\n      <div class=\"flex flex-wrap items-center gap-4\">\n        <Button :disabled=\"isFullscreen\" type=\"primary\" @click=\"enter\">\n          Enter Window Full Screen\n        </Button>\n        <Button @click=\"toggle\"> Toggle Window Full Screen </Button>\n\n        <Button :disabled=\"!isFullscreen\" danger @click=\"exit\">\n          Exit Window Full Screen\n        </Button>\n\n        <span class=\"text-nowrap\"> Current State: {{ isFullscreen }} </span>\n      </div>\n    </Card>\n\n    <Card class=\"mt-5\" title=\"Dom Full Screen\">\n      <Button type=\"primary\" @click=\"toggleDom\"> Enter Dom Full Screen </Button>\n    </Card>\n\n    <div\n      ref=\"domRef\"\n      class=\"mx-auto mt-10 flex-center h-64 w-1/2 rounded-md bg-yellow-400\"\n    >\n      <Button class=\"mr-2\" type=\"primary\" @click=\"toggleDom\">\n        {{ isDomFullscreen ? 'Exit Dom Full Screen' : 'Enter Dom Full Screen' }}\n      </Button>\n    </div>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/hide-menu-children/children.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback, VbenButton } from '@vben/common-ui';\nimport { useTabs } from '@vben/hooks';\nimport { X } from '@vben/icons';\n\nconst { closeCurrentTab } = useTabs();\n</script>\n\n<template>\n  <Fallback\n    description=\"当前路由在菜单中不可见\"\n    status=\"coming-soon\"\n    title=\"被隐藏的子菜单\"\n    show-back\n  >\n    <template #action>\n      <VbenButton size=\"lg\" @click=\"closeCurrentTab()\">\n        <X class=\"mr-2 size-4\" />\n        关闭当前标签页\n      </VbenButton>\n    </template>\n  </Fallback>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/hide-menu-children/parent.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback\n    :description=\"`当前路由：${String($route.name)}，子菜单不可见`\"\n    status=\"coming-soon\"\n    title=\"隐藏子菜单\"\n  >\n    <template #action>\n      <RouterLink to=\"/demos/features/hide-menu-children/children\">\n        打开子路由\n      </RouterLink>\n    </template>\n  </Fallback>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/icons/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { h, ref } from 'vue';\n\nimport { IconPicker, Page } from '@vben/common-ui';\nimport {\n  MdiKeyboardEsc,\n  SvgAvatar1Icon,\n  SvgAvatar2Icon,\n  SvgAvatar3Icon,\n  SvgAvatar4Icon,\n  SvgBellIcon,\n  SvgCakeIcon,\n  SvgCardIcon,\n  SvgDownloadIcon,\n  SvgGithubIcon,\n  SvgGoogleIcon,\n  SvgQQChatIcon,\n  SvgWeChatIcon,\n} from '@vben/icons';\n\nimport { Card, Input } from 'ant-design-vue';\n\nconst iconValue1 = ref('ant-design:trademark-outlined');\nconst iconValue2 = ref('svg:avatar-1');\nconst iconValue3 = ref('mdi:alien-outline');\nconst iconValue4 = ref('mdi-light:book-multiple');\n\nconst inputComponent = h(Input);\n</script>\n\n<template>\n  <Page title=\"图标\">\n    <template #description>\n      <div class=\"mt-2 text-foreground/80\">\n        图标可在\n        <a\n          class=\"text-primary\"\n          href=\"https://icon-sets.iconify.design/\"\n          target=\"_blank\"\n        >\n          Iconify\n        </a>\n        中查找，支持多种图标库，如 Material Design, Font Awesome, Jam Icons 等。\n      </div>\n    </template>\n\n    <Card class=\"mb-5\" title=\"Iconify\">\n      <div class=\"flex items-center gap-5\">\n        <SvgGithubIcon class=\"size-8\" />\n        <SvgGoogleIcon class=\"size-8\" />\n        <SvgQQChatIcon class=\"size-8\" />\n        <SvgWeChatIcon class=\"size-8\" />\n        <MdiKeyboardEsc class=\"size-8\" />\n      </div>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"Svg Icons\">\n      <div class=\"flex items-center gap-5\">\n        <SvgAvatar1Icon class=\"size-8\" />\n        <SvgAvatar2Icon class=\"size-8 text-red-500\" />\n        <SvgAvatar3Icon class=\"size-8 text-green-500\" />\n        <SvgAvatar4Icon class=\"size-8\" />\n        <SvgCakeIcon class=\"size-8\" />\n        <SvgBellIcon class=\"size-8\" />\n        <SvgCardIcon class=\"size-8\" />\n        <SvgDownloadIcon class=\"size-8\" />\n      </div>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"Tailwind CSS\">\n      <div class=\"flex items-center gap-5 text-3xl\">\n        <span class=\"icon-[ant-design--alipay-circle-outlined]\"></span>\n        <span class=\"icon-[ant-design--account-book-filled]\"></span>\n        <span class=\"icon-[ant-design--container-outlined]\"></span>\n        <span class=\"icon-[svg-spinners--wind-toy]\"></span>\n        <span class=\"icon-[svg-spinners--blocks-wave]\"></span>\n        <span class=\"icon-[line-md--compass-filled-loop]\"></span>\n      </div>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"图标选择器\">\n      <div class=\"mb-5 flex items-center gap-5\">\n        <span>原始样式(Iconify):</span>\n        <IconPicker v-model=\"iconValue1\" class=\"w-50\" />\n      </div>\n      <div class=\"mb-5 flex items-center gap-5\">\n        <span>原始样式(svg):</span>\n        <IconPicker v-model=\"iconValue2\" class=\"w-50\" prefix=\"svg\" />\n      </div>\n      <div class=\"mb-5 flex items-center gap-5\">\n        <span>自定义Input:</span>\n        <IconPicker\n          :input-component=\"inputComponent\"\n          v-model=\"iconValue3\"\n          icon-slot=\"addonAfter\"\n          model-value-prop=\"value\"\n          prefix=\"mdi\"\n        />\n      </div>\n      <div class=\"flex items-center gap-5\">\n        <span>显示为一个Icon:</span>\n        <Input\n          v-model:value=\"iconValue4\"\n          allow-clear\n          placeholder=\"点击这里选择图标\"\n          class=\"w-75\"\n        >\n          <template #addonAfter>\n            <IconPicker v-model=\"iconValue4\" prefix=\"mdi-light\" type=\"icon\" />\n          </template>\n        </Input>\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/json-bigint/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Alert, Button, Card } from 'ant-design-vue';\n\nimport { getBigIntData } from '#/api/examples/json-bigint';\n\nconst response = ref('');\nfunction fetchData() {\n  getBigIntData().then((res) => {\n    response.value = res;\n  });\n}\n</script>\n<template>\n  <Page\n    title=\"JSON BigInt Support\"\n    description=\"解析后端返回的长整数（long/bigInt）。代码位置：playground/src/api/request.ts中的transformResponse\"\n  >\n    <Card>\n      <Alert>\n        <template #message>\n          有些后端接口返回的ID是长整数，但javascript原生的JSON解析是不支持超过2^53-1的长整数的。\n          这种情况可以建议后端返回数据前将长整数转换为字符串类型。如果后端不接受我们的建议😡……\n          <br />\n          下面的按钮点击后会发起请求，接口返回的JSON数据中的id字段是超出整数范围的数字，已自动将其解析为字符串\n        </template>\n      </Alert>\n      <Button class=\"mt-4\" type=\"primary\" @click=\"fetchData\">发起请求</Button>\n      <div>\n        <pre>\n        {{ response }}\n        </pre>\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/login-expired/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { LoginExpiredModeType } from '@vben/types';\n\nimport { Page } from '@vben/common-ui';\nimport { preferences, updatePreferences } from '@vben/preferences';\n\nimport { Button, Card } from 'ant-design-vue';\n\nimport { getMockStatusApi } from '#/api';\n\nasync function handleClick(type: LoginExpiredModeType) {\n  const loginExpiredMode = preferences.app.loginExpiredMode;\n\n  updatePreferences({ app: { loginExpiredMode: type } });\n  await getMockStatusApi('401');\n  updatePreferences({ app: { loginExpiredMode } });\n}\n</script>\n\n<template>\n  <Page title=\"登录过期演示\">\n    <template #description>\n      <div class=\"mt-2 text-foreground/80\">\n        接口请求遇到401状态码时，需要重新登录。有两种方式：\n        <p>1.转到登录页，登录成功后跳转回原页面</p>\n        <p>\n          2.弹出重新登录弹窗，登录后关闭弹窗，不进行任何页面跳转（刷新后还是会跳转登录页面）\n        </p>\n      </div>\n    </template>\n\n    <Card class=\"mb-5\" title=\"跳转登录页面方式\">\n      <Button type=\"primary\" @click=\"handleClick('page')\"> 点击触发 </Button>\n    </Card>\n    <Card class=\"mb-5\" title=\"登录弹窗方式\">\n      <Button type=\"primary\" @click=\"handleClick('modal')\"> 点击触发 </Button>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/menu-query/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback\n    description=\"点击菜单，将会带上参数\"\n    status=\"coming-soon\"\n    title=\"菜单带参示例\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/new-window/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback\n    description=\"当前页面已在新窗口内打开\"\n    status=\"coming-soon\"\n    title=\"新窗口打开页面\"\n  />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/request-params-serializer/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed, ref, watchEffect } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Card, Radio, RadioGroup } from 'ant-design-vue';\n\nimport { getParamsData } from '#/api/examples/params';\n\nconst params = { ids: [2512, 3241, 4255] };\nconst paramsSerializer = ref<'brackets' | 'comma' | 'indices' | 'repeat'>(\n  'brackets',\n);\nconst response = ref('');\nconst paramsStr = computed(() => {\n  // 写一段代码，从完整的URL中提取参数部分\n  const url = response.value;\n  return new URL(url).searchParams.toString();\n});\n\nwatchEffect(() => {\n  getParamsData(params, paramsSerializer.value).then((res) => {\n    response.value = res.request.responseURL;\n  });\n});\n</script>\n<template>\n  <Page\n    title=\"请求参数序列化\"\n    description=\"不同的后台接口可能对数组类型的GET参数的解析方式不同，我们预置了几种数组序列化方式，通过配置 paramsSerializer 来实现不同的序列化方式\"\n  >\n    <Card>\n      <RadioGroup v-model:value=\"paramsSerializer\" name=\"paramsSerializer\">\n        <Radio value=\"brackets\">brackets</Radio>\n        <Radio value=\"comma\">comma</Radio>\n        <Radio value=\"indices\">indices</Radio>\n        <Radio value=\"repeat\">repeat</Radio>\n      </RadioGroup>\n      <div class=\"mt-4 flex flex-col gap-4\">\n        <div>\n          <h3>需要提交的参数</h3>\n          <div>{{ JSON.stringify(params, null, 2) }}</div>\n        </div>\n        <template v-if=\"response\">\n          <div>\n            <h3>访问地址</h3>\n            <pre>{{ response }}</pre>\n          </div>\n          <div>\n            <h3>参数字符串</h3>\n            <pre>{{ paramsStr }}</pre>\n          </div>\n          <div>\n            <h3>参数解码</h3>\n            <pre>{{ decodeURIComponent(paramsStr) }}</pre>\n          </div>\n        </template>\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/tabs/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport { Page } from '@vben/common-ui';\nimport { useTabs } from '@vben/hooks';\n\nimport { Button, Card, Input } from 'ant-design-vue';\n\nconst router = useRouter();\nconst newTabTitle = ref('');\n\nconst {\n  closeAllTabs,\n  closeCurrentTab,\n  closeLeftTabs,\n  closeOtherTabs,\n  closeRightTabs,\n  closeTabByKey,\n  refreshTab,\n  resetTabTitle,\n  setTabTitle,\n} = useTabs();\n\nfunction openTab() {\n  // 这里就是路由跳转，也可以用path\n  router.push({ name: 'VbenAbout' });\n}\n\nfunction openTabWithParams(id: number) {\n  // 这里就是路由跳转，也可以用path\n  router.push({ name: 'FeatureTabDetailDemo', params: { id } });\n}\n\nfunction reset() {\n  newTabTitle.value = '';\n  resetTabTitle();\n}\n</script>\n\n<template>\n  <Page description=\"用于需要操作标签页的场景\" title=\"标签页\">\n    <Card class=\"mb-5\" title=\"打开/关闭标签页\">\n      <div class=\"mb-3 text-foreground/80\">\n        如果标签页存在，直接跳转切换。如果标签页不存在，则打开新的标签页。\n      </div>\n      <div class=\"flex flex-wrap gap-3\">\n        <Button type=\"primary\" @click=\"openTab\"> 打开 \"关于\" 标签页 </Button>\n        <Button type=\"primary\" @click=\"closeTabByKey('/vben-admin/about')\">\n          关闭 \"关于\" 标签页\n        </Button>\n      </div>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"标签页操作\">\n      <div class=\"mb-3 text-foreground/80\">用于动态控制标签页的各种操作</div>\n      <div class=\"flex flex-wrap gap-3\">\n        <Button type=\"primary\" @click=\"closeCurrentTab()\">\n          关闭当前标签页\n        </Button>\n        <Button type=\"primary\" @click=\"closeLeftTabs()\">\n          关闭左侧标签页\n        </Button>\n        <Button type=\"primary\" @click=\"closeRightTabs()\">\n          关闭右侧标签页\n        </Button>\n        <Button type=\"primary\" @click=\"closeAllTabs()\"> 关闭所有标签页 </Button>\n        <Button type=\"primary\" @click=\"closeOtherTabs()\">\n          关闭其他标签页\n        </Button>\n        <Button type=\"primary\" @click=\"refreshTab()\"> 刷新当前标签页 </Button>\n      </div>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"动态标题\">\n      <div class=\"mb-3 text-foreground/80\">\n        该操作不会影响页面标题，仅修改Tab标题\n      </div>\n      <div class=\"flex flex-wrap items-center gap-3\">\n        <Input\n          v-model:value=\"newTabTitle\"\n          class=\"w-40\"\n          placeholder=\"请输入新标题\"\n        />\n        <Button type=\"primary\" @click=\"() => setTabTitle(newTabTitle)\">\n          修改\n        </Button>\n        <Button @click=\"reset\"> 重置 </Button>\n      </div>\n    </Card>\n\n    <Card class=\"mb-5\" title=\"最大打开数量\">\n      <div class=\"mb-3 text-foreground/80\">\n        限制带参数的tab打开的最大数量，由 `route.meta.maxNumOfOpenTab` 控制\n      </div>\n      <div class=\"flex flex-wrap items-center gap-3\">\n        <template v-for=\"item in 5\" :key=\"item\">\n          <Button type=\"primary\" @click=\"openTabWithParams(item)\">\n            打开{{ item }}详情页\n          </Button>\n        </template>\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/tabs/tab-detail.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed } from 'vue';\nimport { useRoute } from 'vue-router';\n\nimport { Page } from '@vben/common-ui';\nimport { useTabs } from '@vben/hooks';\n\nconst route = useRoute();\n\nconst { setTabTitle } = useTabs();\n\nconst index = computed(() => {\n  return route.params?.id ?? -1;\n});\n\nsetTabTitle(`No.${index.value} - 详情信息`);\n</script>\n\n<template>\n  <Page :title=\"`标签页${index}详情页`\">\n    <template #description> {{ index }} - 详情页内容在此 </template>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/vue-query/concurrency-caching.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Recordable } from '@vben/types';\n\nimport { useQuery } from '@tanstack/vue-query';\n\nimport { useVbenForm } from '#/adapter/form';\nimport { getMenuList } from '#/api';\n\nconst queryKey = ['demo', 'api', 'options'];\nconst count = 4;\n\nconst { dataUpdatedAt, promise: fetchDataFn } = useQuery({\n  // 在组件渲染期间预取数据\n  experimental_prefetchInRender: true,\n  // 获取接口数据的函数\n  queryFn: getMenuList,\n  queryKey,\n  // 每次组件挂载时都重新获取数据。如果不需要每次都重新获取就不要设置为always\n  refetchOnMount: 'always',\n  // 缓存时间\n  staleTime: 1000 * 60 * 5,\n});\n\nasync function fetchOptions() {\n  return await fetchDataFn.value;\n}\n\nconst schema = [];\n\nfor (let i = 0; i < count; i++) {\n  schema.push({\n    component: 'ApiSelect',\n    componentProps: {\n      api: fetchOptions,\n      class: 'w-full',\n      filterOption: (input: string, option: Recordable<any>) => {\n        return option.label.toLowerCase().includes(input.toLowerCase());\n      },\n      labelField: 'name',\n      showSearch: true,\n      valueField: 'id',\n    },\n    fieldName: `field${i}`,\n    label: `Select ${i}`,\n  });\n}\n\nconst [Form] = useVbenForm({\n  schema,\n  showDefaultActions: false,\n});\n</script>\n<template>\n  <div>\n    <div class=\"mb-2 flex gap-2\">\n      <div>以下{{ count }}个组件共用一个数据源。</div>\n      <div>缓存更新时间：{{ new Date(dataUpdatedAt).toLocaleString() }}</div>\n    </div>\n    <Form />\n  </div>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/vue-query/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { Page } from '@vben/common-ui';\n\nimport { refAutoReset } from '@vueuse/core';\nimport { Button, Card, Empty } from 'ant-design-vue';\n\nimport ConcurrencyCaching from './concurrency-caching.vue';\nimport InfiniteQueries from './infinite-queries.vue';\nimport PaginatedQueries from './paginated-queries.vue';\nimport QueryRetries from './query-retries.vue';\n\nconst showCaching = refAutoReset(true, 1000);\n</script>\n\n<template>\n  <Page title=\"Vue Query示例\">\n    <div class=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n      <Card title=\"分页查询\">\n        <PaginatedQueries />\n      </Card>\n      <Card title=\"无限滚动\">\n        <InfiniteQueries class=\"h-75 overflow-auto\" />\n      </Card>\n      <Card title=\"错误重试\">\n        <QueryRetries />\n      </Card>\n      <Card\n        title=\"并发和缓存\"\n        v-spinning=\"!showCaching\"\n        :body-style=\"{ minHeight: '330px' }\"\n      >\n        <template #extra>\n          <Button @click=\"showCaching = false\">重新加载</Button>\n        </template>\n        <ConcurrencyCaching v-if=\"showCaching\" />\n        <Empty v-else description=\"正在加载...\" />\n      </Card>\n    </div>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/vue-query/infinite-queries.vue",
    "content": "<script setup lang=\"ts\">\nimport type { IProducts } from './typing';\n\nimport { useInfiniteQuery } from '@tanstack/vue-query';\nimport { Button } from 'ant-design-vue';\n\nconst LIMIT = 10;\nconst fetchProducts = async ({ pageParam = 0 }): Promise<IProducts> => {\n  const res = await fetch(\n    `https://dummyjson.com/products?limit=${LIMIT}&skip=${pageParam * LIMIT}`,\n  );\n  return res.json();\n};\n\nconst {\n  data,\n  error,\n  fetchNextPage,\n  hasNextPage,\n  isError,\n  isFetching,\n  isFetchingNextPage,\n  isPending,\n} = useInfiniteQuery({\n  getNextPageParam: (current, allPages) => {\n    const nextPage = allPages.length + 1;\n    const lastPage = current.skip + current.limit;\n    if (lastPage === current.total) return;\n    return nextPage;\n  },\n  initialPageParam: 0,\n  queryFn: fetchProducts,\n  queryKey: ['products'],\n});\n</script>\n\n<template>\n  <div>\n    <span v-if=\"isPending\">加载...</span>\n    <span v-else-if=\"isError\">出错了: {{ error }}</span>\n    <div v-else-if=\"data\">\n      <span v-if=\"isFetching && !isFetchingNextPage\">Fetching...</span>\n      <ul v-for=\"(group, index) in data.pages\" :key=\"index\">\n        <li v-for=\"product in group.products\" :key=\"product.id\">\n          {{ product.title }}\n        </li>\n      </ul>\n      <Button\n        :disabled=\"!hasNextPage || isFetchingNextPage\"\n        @click=\"() => fetchNextPage()\"\n      >\n        <span v-if=\"isFetchingNextPage\">加载中...</span>\n        <span v-else-if=\"hasNextPage\">加载更多</span>\n        <span v-else>没有更多了</span>\n      </Button>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/vue-query/paginated-queries.vue",
    "content": "<script setup lang=\"ts\">\nimport type { Ref } from 'vue';\n\nimport type { IProducts } from './typing';\n\nimport { ref } from 'vue';\n\nimport { keepPreviousData, useQuery } from '@tanstack/vue-query';\nimport { Button } from 'ant-design-vue';\n\nconst LIMIT = 10;\nconst fetcher = async (page: Ref<number>): Promise<IProducts> => {\n  const res = await fetch(\n    `https://dummyjson.com/products?limit=${LIMIT}&skip=${(page.value - 1) * LIMIT}`,\n  );\n  return res.json();\n};\n\nconst page = ref(1);\nconst { data, error, isError, isPending, isPlaceholderData } = useQuery({\n  // The data from the last successful fetch is available while new data is being requested.\n  placeholderData: keepPreviousData,\n  queryFn: () => fetcher(page),\n  queryKey: ['products', page],\n});\nconst prevPage = () => {\n  page.value = Math.max(page.value - 1, 1);\n};\nconst nextPage = () => {\n  if (!isPlaceholderData.value) {\n    page.value = page.value + 1;\n  }\n};\n</script>\n\n<template>\n  <div class=\"flex gap-4\">\n    <Button size=\"small\" @click=\"prevPage\">上一页</Button>\n    <p>当前页: {{ page }}</p>\n    <Button size=\"small\" @click=\"nextPage\">下一页</Button>\n  </div>\n  <div class=\"p-4\">\n    <div v-if=\"isPending\">加载中...</div>\n    <div v-else-if=\"isError\">出错了: {{ error }}</div>\n    <div v-else-if=\"data\">\n      <ul>\n        <li v-for=\"item in data.products\" :key=\"item.id\">\n          {{ item.title }}\n        </li>\n      </ul>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/vue-query/query-retries.vue",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue';\n\nimport { useQuery } from '@tanstack/vue-query';\nimport { Button } from 'ant-design-vue';\n\nconst count = ref(-1);\nasync function fetchApi() {\n  count.value += 1;\n  return new Promise((_resolve, reject) => {\n    setTimeout(() => {\n      reject(new Error('something went wrong!'));\n    }, 1000);\n  });\n}\n\nconst { error, isFetching, refetch } = useQuery({\n  enabled: false, // Disable automatic refetching when the query mounts\n  queryFn: fetchApi,\n  queryKey: ['queryKey'],\n  retry: 3, // Will retry failed requests 3 times before displaying an error\n});\n\nconst onClick = async () => {\n  count.value = -1;\n  await refetch();\n};\n</script>\n\n<template>\n  <Button :loading=\"isFetching\" @click=\"onClick\"> 发起错误重试 </Button>\n  <p v-if=\"count > 0\" class=\"my-3\">重试次数{{ count }}</p>\n  <p>{{ error }}</p>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/features/vue-query/typing.ts",
    "content": "export interface IProducts {\n  limit: number;\n  products: {\n    brand: string;\n    category: string;\n    description: string;\n    discountPercentage: string;\n    id: string;\n    images: string[];\n    price: string;\n    rating: string;\n    stock: string;\n    thumbnail: string;\n    title: string;\n  }[];\n  skip: number;\n  total: number;\n}\n"
  },
  {
    "path": "playground/src/views/demos/features/watermark/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Page } from '@vben/common-ui';\nimport { useWatermark } from '@vben/hooks';\n\nimport { Button, Card } from 'ant-design-vue';\n\nconst { destroyWatermark, updateWatermark, watermark } = useWatermark();\n\nasync function recreateWaterMark() {\n  destroyWatermark();\n  await createWaterMark();\n}\n\nasync function createWaterMark() {\n  await updateWatermark({\n    advancedStyle: {\n      colorStops: [\n        {\n          color: 'red',\n          offset: 0,\n        },\n        {\n          color: 'blue',\n          offset: 1,\n        },\n      ],\n      type: 'linear',\n    },\n    content: `hello my watermark\\n${new Date().toLocaleString()}`,\n    globalAlpha: 0.5,\n    gridLayoutOptions: {\n      cols: 2,\n      gap: [20, 20],\n      matrix: [\n        [1, 0],\n        [0, 1],\n      ],\n      rows: 2,\n    },\n    height: 200,\n    layout: 'grid',\n    rotate: 22,\n    width: 200,\n  });\n}\n</script>\n\n<template>\n  <Page title=\"水印\">\n    <template #description>\n      <div class=\"mt-2 text-foreground/80\">\n        水印使用了\n        <a\n          class=\"text-primary\"\n          href=\"https://zhensherlock.github.io/watermark-js-plus/\"\n          target=\"_blank\"\n        >\n          watermark-js-plus\n        </a>\n        开源插件，详细配置可见插件配置。\n      </div>\n    </template>\n\n    <Card title=\"使用\">\n      <Button\n        :disabled=\"!!watermark\"\n        class=\"mr-2\"\n        type=\"primary\"\n        @click=\"recreateWaterMark\"\n      >\n        创建水印\n      </Button>\n      <Button\n        :disabled=\"!watermark\"\n        class=\"mr-2\"\n        type=\"primary\"\n        @click=\"createWaterMark\"\n      >\n        更新水印\n      </Button>\n      <Button :disabled=\"!watermark\" danger @click=\"destroyWatermark\">\n        移除水印\n      </Button>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/nested/menu-1.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback status=\"coming-soon\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/nested/menu-2-1.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback status=\"coming-soon\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/nested/menu-3-1.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback status=\"coming-soon\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/demos/nested/menu-3-2-1.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Fallback } from '@vben/common-ui';\n</script>\n\n<template>\n  <Fallback status=\"coming-soon\" />\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/button-group/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Recordable } from '@vben/types';\n\nimport { reactive, ref } from 'vue';\n\nimport {\n  Page,\n  VbenButton,\n  VbenButtonGroup,\n  VbenCheckButtonGroup,\n} from '@vben/common-ui';\nimport { LoaderCircle, Square, SquareCheckBig } from '@vben/icons';\n\nimport { Button, Card, message } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst radioValue = ref<string | undefined>('a');\nconst checkValue = ref(['a', 'b']);\n\nconst options = [\n  { label: '选项1', value: 'a' },\n  { label: '选项2', value: 'b', num: 999 },\n  { label: '选项3', value: 'c' },\n  { label: '选项4', value: 'd' },\n  { label: '选项5', value: 'e' },\n  { label: '选项6', value: 'f' },\n];\n\nfunction resetValues() {\n  radioValue.value = undefined;\n  checkValue.value = [];\n}\n\nfunction beforeChange(v: any, isChecked: boolean) {\n  return new Promise((resolve) => {\n    message.loading({\n      content: `正在设置${v}为${isChecked ? '选中' : '未选中'}...`,\n      duration: 0,\n      key: 'beforeChange',\n    });\n    setTimeout(() => {\n      message.success({ content: `${v} 已设置成功`, key: 'beforeChange' });\n      resolve(true);\n    }, 2000);\n  });\n}\n\nconst compProps = reactive({\n  beforeChange: undefined,\n  disabled: false,\n  gap: 0,\n  showIcon: true,\n  size: 'middle',\n  allowClear: false,\n} as Recordable<any>);\n\nconst [Form] = useVbenForm({\n  handleValuesChange(values) {\n    Object.keys(values).forEach((k) => {\n      if (k === 'beforeChange') {\n        compProps[k] = values[k] ? beforeChange : undefined;\n      } else {\n        compProps[k] = values[k];\n      }\n    });\n  },\n  commonConfig: {\n    labelWidth: 150,\n  },\n  schema: [\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        options: [\n          { label: '大', value: 'large' },\n          { label: '中', value: 'middle' },\n          { label: '小', value: 'small' },\n        ],\n      },\n      defaultValue: compProps.size,\n      fieldName: 'size',\n      label: '尺寸',\n    },\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        options: [\n          { label: '无', value: 0 },\n          { label: '小', value: 5 },\n          { label: '中', value: 15 },\n          { label: '大', value: 30 },\n        ],\n      },\n      defaultValue: compProps.gap,\n      fieldName: 'gap',\n      label: '间距',\n    },\n    {\n      component: 'Switch',\n      defaultValue: compProps.showIcon,\n      fieldName: 'showIcon',\n      label: '显示图标',\n    },\n    {\n      component: 'Switch',\n      defaultValue: compProps.disabled,\n      fieldName: 'disabled',\n      label: '禁用',\n    },\n    {\n      component: 'Switch',\n      defaultValue: false,\n      fieldName: 'beforeChange',\n      label: '前置回调',\n    },\n    {\n      component: 'Switch',\n      defaultValue: false,\n      fieldName: 'allowClear',\n      label: '允许清除',\n      help: '单选时是否允许取消选中（值为undefined）',\n    },\n    {\n      component: 'InputNumber',\n      defaultValue: 0,\n      fieldName: 'maxCount',\n      label: '最大选中数量',\n      help: '多选时有效，0表示不限制',\n    },\n  ],\n  showDefaultActions: false,\n  submitOnChange: true,\n});\n\nfunction onBtnClick(value: any) {\n  const opt = options.find((o) => o.value === value);\n  if (opt) {\n    message.success(`点击了按钮${opt.label}，value = ${value}`);\n  }\n}\n</script>\n<template>\n  <Page\n    title=\"VbenButtonGroup 按钮组\"\n    description=\"VbenButtonGroup是一个按钮容器，用于包裹一组按钮，协调整体样式。VbenCheckButtonGroup则可以作为一个表单组件，提供单选或多选功能\"\n  >\n    <Card title=\"基本用法\">\n      <template #extra>\n        <Button type=\"primary\" @click=\"resetValues\">清空值</Button>\n      </template>\n      <p class=\"mt-4\">按钮组：</p>\n      <div class=\"mt-2 flex flex-col gap-2\">\n        <VbenButtonGroup v-bind=\"compProps\" border>\n          <VbenButton\n            v-for=\"btn in options\"\n            :key=\"btn.value\"\n            variant=\"link\"\n            @click=\"onBtnClick(btn.value)\"\n          >\n            {{ btn.label }}\n          </VbenButton>\n        </VbenButtonGroup>\n        <VbenButtonGroup v-bind=\"compProps\" border>\n          <VbenButton\n            v-for=\"btn in options\"\n            :key=\"btn.value\"\n            variant=\"outline\"\n            @click=\"onBtnClick(btn.value)\"\n          >\n            {{ btn.label }}\n          </VbenButton>\n        </VbenButtonGroup>\n      </div>\n      <p class=\"mt-4\">单选：{{ radioValue }}</p>\n      <div class=\"mt-2 flex flex-col gap-2\">\n        <VbenCheckButtonGroup\n          v-model=\"radioValue\"\n          :options=\"options\"\n          v-bind=\"compProps\"\n        />\n      </div>\n      <p class=\"mt-4\">单选插槽：{{ radioValue }}</p>\n      <div class=\"mt-2 flex flex-col gap-2\">\n        <VbenCheckButtonGroup\n          v-model=\"radioValue\"\n          :options=\"options\"\n          v-bind=\"compProps\"\n        >\n          <template #option=\"{ label, value, data }\">\n            <div class=\"flex items-center\">\n              <span>{{ label }}</span>\n              <span class=\"ml-2 text-gray-400\">{{ value }}</span>\n              <span v-if=\"data.num\" class=\"white ml-2\">{{ data.num }}</span>\n            </div>\n          </template>\n        </VbenCheckButtonGroup>\n      </div>\n      <p class=\"mt-4\">多选{{ checkValue }}</p>\n      <div class=\"mt-2 flex flex-col gap-2\">\n        <VbenCheckButtonGroup\n          v-model=\"checkValue\"\n          multiple\n          :options=\"options\"\n          v-bind=\"compProps\"\n        />\n      </div>\n      <p class=\"mt-4\">自定义图标{{ checkValue }}</p>\n      <div class=\"mt-2 flex flex-col gap-2\">\n        <VbenCheckButtonGroup\n          v-model=\"checkValue\"\n          multiple\n          :options=\"options\"\n          v-bind=\"compProps\"\n        >\n          <template #icon=\"{ loading, checked }\">\n            <LoaderCircle class=\"animate-spin\" v-if=\"loading\" />\n            <SquareCheckBig v-else-if=\"checked\" />\n            <Square v-else />\n          </template>\n        </VbenCheckButtonGroup>\n      </div>\n    </Card>\n\n    <Card title=\"设置\" class=\"mt-4\">\n      <Form />\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/captcha/point-selection-captcha.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { CaptchaPoint } from '@vben/common-ui';\n\nimport { reactive, ref } from 'vue';\n\nimport { Page, PointSelectionCaptcha } from '@vben/common-ui';\n\nimport { Card, Input, InputNumber, message, Switch } from 'ant-design-vue';\n\nimport { $t } from '#/locales';\n\nconst DEFAULT_CAPTCHA_IMAGE =\n  'https://unpkg.com/@vbenjs/static-source@0.1.7/source/default-captcha-image.jpeg';\n\nconst DEFAULT_HINT_IMAGE =\n  'https://unpkg.com/@vbenjs/static-source@0.1.7/source/default-hint-image.png';\n\nconst selectedPoints = ref<CaptchaPoint[]>([]);\nconst params = reactive({\n  captchaImage: '',\n  captchaImageUrl: DEFAULT_CAPTCHA_IMAGE,\n  height: undefined,\n  hintImage: '',\n  hintImageUrl: DEFAULT_HINT_IMAGE,\n  hintText: '唇，燕，碴，找',\n  paddingX: undefined,\n  paddingY: undefined,\n  showConfirm: true,\n  showHintImage: false,\n  title: '',\n  width: undefined,\n});\nconst handleConfirm = (points: CaptchaPoint[], clear: () => void) => {\n  message.success({\n    content: `captcha points: ${JSON.stringify(points)}`,\n  });\n  clear();\n  selectedPoints.value = [];\n};\nconst handleRefresh = () => {\n  selectedPoints.value = [];\n};\nconst handleClick = (point: CaptchaPoint) => {\n  selectedPoints.value.push(point);\n};\n</script>\n\n<template>\n  <Page\n    :description=\"$t('examples.captcha.pageDescription')\"\n    :title=\"$t('examples.captcha.pageTitle')\"\n  >\n    <Card :title=\"$t('examples.captcha.basic')\" class=\"mb-4 overflow-x-auto\">\n      <div class=\"mb-3 flex items-center justify-start\">\n        <Input\n          v-model:value=\"params.title\"\n          :placeholder=\"$t('examples.captcha.titlePlaceholder')\"\n          class=\"w-64\"\n        />\n        <Input\n          v-model:value=\"params.captchaImageUrl\"\n          :placeholder=\"$t('examples.captcha.captchaImageUrlPlaceholder')\"\n          class=\"ml-8 w-64\"\n        />\n        <div class=\"ml-8 flex w-96 items-center\">\n          <Switch\n            v-model=\"params.showHintImage\"\n            :checked-children=\"$t('examples.captcha.hintImage')\"\n            :un-checked-children=\"$t('examples.captcha.hintText')\"\n            class=\"mr-4 w-40\"\n          />\n          <Input\n            v-show=\"params.showHintImage\"\n            v-model:value=\"params.hintImageUrl\"\n            :placeholder=\"$t('examples.captcha.hintImagePlaceholder')\"\n          />\n          <Input\n            v-show=\"!params.showHintImage\"\n            v-model:value=\"params.hintText\"\n            :placeholder=\"$t('examples.captcha.hintTextPlaceholder')\"\n          />\n        </div>\n\n        <Switch\n          v-model=\"params.showConfirm\"\n          :checked-children=\"$t('examples.captcha.showConfirm')\"\n          :un-checked-children=\"$t('examples.captcha.hideConfirm')\"\n          class=\"ml-8 w-28\"\n        />\n      </div>\n      <div class=\"mb-3 flex items-center justify-start\">\n        <div>\n          <InputNumber\n            v-model:value=\"params.width\"\n            :min=\"1\"\n            :placeholder=\"$t('examples.captcha.widthPlaceholder')\"\n            :precision=\"0\"\n            :step=\"1\"\n            class=\"w-64\"\n          >\n            <template #addonAfter>px</template>\n          </InputNumber>\n        </div>\n        <div class=\"ml-8\">\n          <InputNumber\n            v-model:value=\"params.height\"\n            :min=\"1\"\n            :placeholder=\"$t('examples.captcha.heightPlaceholder')\"\n            :precision=\"0\"\n            :step=\"1\"\n            class=\"w-64\"\n          >\n            <template #addonAfter>px</template>\n          </InputNumber>\n        </div>\n        <div class=\"ml-8\">\n          <InputNumber\n            v-model:value=\"params.paddingX\"\n            :min=\"1\"\n            :placeholder=\"$t('examples.captcha.paddingXPlaceholder')\"\n            :precision=\"0\"\n            :step=\"1\"\n            class=\"w-64\"\n          >\n            <template #addonAfter>px</template>\n          </InputNumber>\n        </div>\n        <div class=\"ml-8\">\n          <InputNumber\n            v-model:value=\"params.paddingY\"\n            :min=\"1\"\n            :placeholder=\"$t('examples.captcha.paddingYPlaceholder')\"\n            :precision=\"0\"\n            :step=\"1\"\n            class=\"w-64\"\n          >\n            <template #addonAfter>px</template>\n          </InputNumber>\n        </div>\n      </div>\n\n      <PointSelectionCaptcha\n        :captcha-image=\"params.captchaImageUrl || params.captchaImage\"\n        :height=\"params.height || 220\"\n        :hint-image=\"\n          params.showHintImage ? params.hintImageUrl || params.hintImage : ''\n        \"\n        :hint-text=\"params.hintText\"\n        :padding-x=\"params.paddingX\"\n        :padding-y=\"params.paddingY\"\n        :show-confirm=\"params.showConfirm\"\n        :width=\"params.width || 300\"\n        class=\"float-left\"\n        @click=\"handleClick\"\n        @confirm=\"handleConfirm\"\n        @refresh=\"handleRefresh\"\n      >\n        <template #title>\n          {{ params.title || $t('examples.captcha.captchaCardTitle') }}\n        </template>\n      </PointSelectionCaptcha>\n\n      <ol class=\"float-left p-5\">\n        <li v-for=\"point in selectedPoints\" :key=\"point.i\" class=\"flex\">\n          <span class=\"mr-3 w-16\">{{\n            $t('examples.captcha.index') + point.i\n          }}</span>\n          <span class=\"mr-3 w-52\">{{\n            $t('examples.captcha.timestamp') + point.t\n          }}</span>\n          <span class=\"mr-3 w-16\">{{\n            $t('examples.captcha.x') + point.x\n          }}</span>\n          <span class=\"mr-3 w-16\">{{\n            $t('examples.captcha.y') + point.y\n          }}</span>\n        </li>\n      </ol>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/captcha/slider-captcha.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  CaptchaVerifyPassingData,\n  SliderCaptchaActionType,\n} from '@vben/common-ui';\n\nimport { ref } from 'vue';\n\nimport { Page, SliderCaptcha } from '@vben/common-ui';\nimport { Bell, Sun } from '@vben/icons';\n\nimport { Button, Card, message } from 'ant-design-vue';\n\nfunction handleSuccess(data: CaptchaVerifyPassingData) {\n  const { time } = data;\n  message.success(`校验成功,耗时${time}秒`);\n}\nfunction handleBtnClick(elRef?: SliderCaptchaActionType) {\n  if (!elRef) {\n    return;\n  }\n  elRef.resume();\n}\n\nconst el1 = ref<SliderCaptchaActionType>();\nconst el2 = ref<SliderCaptchaActionType>();\nconst el3 = ref<SliderCaptchaActionType>();\nconst el4 = ref<SliderCaptchaActionType>();\nconst el5 = ref<SliderCaptchaActionType>();\nconst el6 = ref<SliderCaptchaActionType>();\n</script>\n\n<template>\n  <Page description=\"用于前端简单的拖动校验场景\" title=\"滑块校验\">\n    <Card class=\"mb-5\" title=\"基础示例\">\n      <div class=\"flex-center p-4 px-[30%]\">\n        <SliderCaptcha ref=\"el1\" @success=\"handleSuccess\" />\n        <Button class=\"ml-2\" type=\"primary\" @click=\"handleBtnClick(el1)\">\n          还原\n        </Button>\n      </div>\n    </Card>\n    <Card class=\"mb-5\" title=\"自定义圆角\">\n      <div class=\"flex-center p-4 px-[30%]\">\n        <SliderCaptcha\n          ref=\"el2\"\n          class=\"rounded-full\"\n          @success=\"handleSuccess\"\n        />\n        <Button class=\"ml-2\" type=\"primary\" @click=\"handleBtnClick(el2)\">\n          还原\n        </Button>\n      </div>\n    </Card>\n    <Card class=\"mb-5\" title=\"自定义背景色\">\n      <div class=\"flex-center p-4 px-[30%]\">\n        <SliderCaptcha\n          ref=\"el3\"\n          :bar-style=\"{\n            backgroundColor: '#018ffb',\n          }\"\n          success-text=\"校验成功\"\n          text=\"拖动以进行校验\"\n          @success=\"handleSuccess\"\n        />\n        <Button class=\"ml-2\" type=\"primary\" @click=\"handleBtnClick(el3)\">\n          还原\n        </Button>\n      </div>\n    </Card>\n    <Card class=\"mb-5\" title=\"自定义拖拽图标\">\n      <div class=\"flex-center p-4 px-[30%]\">\n        <SliderCaptcha ref=\"el4\" @success=\"handleSuccess\">\n          <template #actionIcon=\"{ isPassing }\">\n            <Bell v-if=\"isPassing\" />\n            <Sun v-else />\n          </template>\n        </SliderCaptcha>\n        <Button class=\"ml-2\" type=\"primary\" @click=\"handleBtnClick(el4)\">\n          还原\n        </Button>\n      </div>\n    </Card>\n    <Card class=\"mb-5\" title=\"自定义文本\">\n      <div class=\"flex-center p-4 px-[30%]\">\n        <SliderCaptcha\n          ref=\"el5\"\n          success-text=\"成功\"\n          text=\"拖动\"\n          @success=\"handleSuccess\"\n        />\n        <Button class=\"ml-2\" type=\"primary\" @click=\"handleBtnClick(el5)\">\n          还原\n        </Button>\n      </div>\n    </Card>\n    <Card class=\"mb-5\" title=\"自定义内容(slot)\">\n      <div class=\"flex-center p-4 px-[30%]\">\n        <SliderCaptcha ref=\"el6\" @success=\"handleSuccess\">\n          <template #text=\"{ isPassing }\">\n            <template v-if=\"isPassing\">\n              <Bell class=\"mr-2 size-4\" />\n              成功\n            </template>\n            <template v-else>\n              拖动\n              <Sun class=\"ml-2 size-4\" />\n            </template>\n          </template>\n        </SliderCaptcha>\n        <Button class=\"ml-2\" type=\"primary\" @click=\"handleBtnClick(el6)\">\n          还原\n        </Button>\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/captcha/slider-rotate-captcha.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\nimport { Page, SliderRotateCaptcha } from '@vben/common-ui';\nimport { preferences } from '@vben/preferences';\nimport { useUserStore } from '@vben/stores';\n\nimport { Card, message } from 'ant-design-vue';\n\nconst userStore = useUserStore();\nfunction handleSuccess() {\n  message.success('success!');\n}\n\nconst avatar = computed(() => {\n  return userStore.userInfo?.avatar || preferences.app.defaultAvatar;\n});\n</script>\n\n<template>\n  <Page description=\"用于前端简单的拖动校验场景\" title=\"滑块旋转校验\">\n    <Card class=\"mb-5\" title=\"基本示例\">\n      <div class=\"flex-center p-4\">\n        <SliderRotateCaptcha :src=\"avatar\" @success=\"handleSuccess\" />\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/captcha/slider-translate-captcha.vue",
    "content": "<script setup lang=\"ts\">\nimport { Page, SliderTranslateCaptcha } from '@vben/common-ui';\n\nimport { Card, message } from 'ant-design-vue';\n\nfunction handleSuccess() {\n  message.success('success!');\n}\n</script>\n\n<template>\n  <Page\n    description=\"用于前端简单的拼图滑块水平拖动校验场景\"\n    title=\"拼图滑块校验\"\n  >\n    <Card class=\"mb-5\" title=\"基本示例\">\n      <div class=\"flex-center p-4\">\n        <SliderTranslateCaptcha\n          src=\"https://unpkg.com/@vbenjs/static-source@0.1.7/source/pro-avatar.webp\"\n          :canvas-width=\"420\"\n          :canvas-height=\"420\"\n          @success=\"handleSuccess\"\n        />\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/context-menu/index.vue",
    "content": "<script setup lang=\"ts\">\nimport { Page } from '@vben/common-ui';\n\nimport { VbenContextMenu } from '@vben-core/shadcn-ui';\n\nimport { Button, Card, message } from 'ant-design-vue';\n\nconst needHidden = (role: string) => {\n  return role === 'user';\n};\n\nconst contextMenus = () => {\n  return [\n    {\n      text: '刷新',\n      key: 'refresh',\n      handler: (data: any) => {\n        message.success('刷新成功', data);\n      },\n      hidden: needHidden('admin'),\n    },\n    {\n      text: '关闭当前',\n      key: 'close-current',\n      handler: (data: any) => {\n        message.success('关闭当前', data);\n      },\n      hidden: needHidden('user'),\n    },\n    {\n      text: '关闭其他',\n      key: 'close-other',\n      handler: (data: any) => {\n        message.success('关闭其他', data);\n      },\n    },\n    {\n      text: '关闭所有',\n      key: 'close-all',\n      handler: (data: any) => {\n        message.success('关闭所有', data);\n      },\n    },\n  ];\n};\n</script>\n\n<template>\n  <Page title=\"Context Menu 上下文菜单\">\n    <Card title=\"基本使用\">\n      <div>一共四个菜单（刷新、关闭当前、关闭其他、关闭所有）</div>\n      <br />\n      <br />\n      <VbenContextMenu :menus=\"contextMenus\" :modal=\"true\" item-class=\"pr-6\">\n        <Button> 右键点击我打开上下文菜单(有隐藏项) </Button>\n      </VbenContextMenu>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/count-to/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { CountToProps, TransitionPresets } from '@vben/common-ui';\n\nimport { reactive } from 'vue';\n\nimport { CountTo, Page, TransitionPresetsKeys } from '@vben/common-ui';\nimport { IconifyIcon } from '@vben/icons';\n\nimport {\n  Button,\n  Card,\n  Col,\n  Form,\n  FormItem,\n  Input,\n  InputNumber,\n  message,\n  Row,\n  Select,\n  Switch,\n} from 'ant-design-vue';\n\nconst props = reactive<CountToProps & { transition: TransitionPresets }>({\n  decimal: '.',\n  decimals: 2,\n  decimalStyle: {\n    fontSize: 'small',\n    fontStyle: 'italic',\n  },\n  delay: 0,\n  disabled: false,\n  duration: 2000,\n  endVal: 100_000,\n  mainStyle: {\n    color: 'hsl(var(--primary))',\n    fontSize: 'xx-large',\n    fontWeight: 'bold',\n  },\n  prefix: '￥',\n  prefixStyle: {\n    paddingRight: '0.5rem',\n  },\n  separator: ',',\n  startVal: 0,\n  suffix: '元',\n  suffixStyle: {\n    paddingLeft: '0.5rem',\n  },\n  transition: 'easeOutQuart',\n});\n\nfunction changeNumber() {\n  props.endVal =\n    Math.floor(Math.random() * 100_000_000) / 10 ** (props.decimals || 0);\n}\n\nfunction openDocumentation() {\n  window.open('https://vueuse.org/core/useTransition/', '_blank');\n}\n\nfunction onStarted() {\n  message.loading({\n    content: '动画已开始',\n    duration: 0,\n    key: 'animator-info',\n  });\n}\n\nfunction onFinished() {\n  message.success({\n    content: '动画已结束',\n    duration: 2,\n    key: 'animator-info',\n  });\n}\n</script>\n<template>\n  <Page title=\"CountTo\" description=\"数字滚动动画组件。使用\">\n    <template #description>\n      <span>\n        使用useTransition封装的数字滚动动画组件，每次改变当前值都会产生过渡动画。\n      </span>\n      <Button type=\"link\" @click=\"openDocumentation\">\n        查看useTransition文档\n      </Button>\n    </template>\n    <Card title=\"基本用法\">\n      <div class=\"flex-center w-full pb-4\">\n        <CountTo v-bind=\"props\" @started=\"onStarted\" @finished=\"onFinished\" />\n      </div>\n      <Form :model=\"props\">\n        <Row :gutter=\"20\">\n          <Col :span=\"8\">\n            <FormItem label=\"初始值\" name=\"startVal\">\n              <InputNumber v-model:value=\"props.startVal\" />\n            </FormItem>\n          </Col>\n          <Col :span=\"8\">\n            <FormItem label=\"当前值\" name=\"endVal\">\n              <InputNumber\n                v-model:value=\"props.endVal\"\n                class=\"w-full\"\n                :precision=\"props.decimals\"\n              >\n                <template #addonAfter>\n                  <IconifyIcon\n                    v-tippy=\"`设置一个随机值`\"\n                    class=\"size-5 cursor-pointer outline-hidden\"\n                    icon=\"ix:random-filled\"\n                    @click=\"changeNumber\"\n                  />\n                </template>\n              </InputNumber>\n            </FormItem>\n          </Col>\n          <Col :span=\"8\">\n            <FormItem label=\"禁用动画\" name=\"disabled\">\n              <Switch v-model=\"props.disabled\" />\n            </FormItem>\n          </Col>\n          <Col :span=\"8\">\n            <FormItem label=\"延迟动画\" name=\"delay\">\n              <InputNumber v-model:value=\"props.delay\" :min=\"0\" />\n            </FormItem>\n          </Col>\n          <Col :span=\"8\">\n            <FormItem label=\"持续时间\" name=\"duration\">\n              <InputNumber v-model:value=\"props.duration\" :min=\"0\" />\n            </FormItem>\n          </Col>\n\n          <Col :span=\"8\">\n            <FormItem label=\"小数位数\" name=\"decimals\">\n              <InputNumber\n                v-model:value=\"props.decimals\"\n                :min=\"0\"\n                :precision=\"0\"\n              />\n            </FormItem>\n          </Col>\n          <Col :span=\"8\">\n            <FormItem label=\"分隔符\" name=\"separator\">\n              <Input v-model:value=\"props.separator\" />\n            </FormItem>\n          </Col>\n          <Col :span=\"8\">\n            <FormItem label=\"小数点\" name=\"decimal\">\n              <Input v-model:value=\"props.decimal\" />\n            </FormItem>\n          </Col>\n          <Col :span=\"8\">\n            <FormItem label=\"动画\" name=\"transition\">\n              <Select v-model:value=\"props.transition\">\n                <Select.Option\n                  v-for=\"preset in TransitionPresetsKeys\"\n                  :key=\"preset\"\n                  :value=\"preset\"\n                >\n                  {{ preset }}\n                </Select.Option>\n              </Select>\n            </FormItem>\n          </Col>\n          <Col :span=\"8\">\n            <FormItem label=\"前缀\" name=\"prefix\">\n              <Input v-model:value=\"props.prefix\" />\n            </FormItem>\n          </Col>\n          <Col :span=\"8\">\n            <FormItem label=\"后缀\" name=\"suffix\">\n              <Input v-model:value=\"props.suffix\" />\n            </FormItem>\n          </Col>\n        </Row>\n      </Form>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/cropper/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { UploadChangeParam } from 'ant-design-vue';\n\nimport { ref } from 'vue';\n\nimport { Page, VCropper } from '@vben/common-ui';\n\nimport { Button, Card, Select, Upload } from 'ant-design-vue';\n\nconst options = [\n  { label: '1:1', value: '1:1' },\n  { label: '16:9', value: '16:9' },\n  { label: '不限制', value: '' },\n];\n\nconst cropperRef = ref<InstanceType<typeof VCropper>>();\n\nconst cropLoading = ref(false);\nconst validAspectRatio = ref<string | undefined>('1:1');\nconst imgUrl = ref('');\nconst cropperImg = ref();\n\nconst selectImgFile = (event: UploadChangeParam) => {\n  const file = event.fileList[0]?.originFileObj;\n  if (!file) return;\n\n  if (!file.type.startsWith('image/')) {\n    console.error('请上传图片文件');\n    return;\n  }\n\n  const reader = new FileReader();\n  reader.addEventListener('load', (e) => {\n    imgUrl.value = e.target?.result as string;\n  });\n  reader.addEventListener('error', () => {\n    console.error('Failed to read file');\n  });\n\n  reader.readAsDataURL(file);\n};\n\nconst cropImage = async () => {\n  if (!cropperRef.value) return;\n  cropLoading.value = true;\n  try {\n    cropperImg.value = await cropperRef.value.getCropImage(\n      'image/jpeg',\n      0.92,\n      'base64',\n    );\n  } catch (error) {\n    console.error('图片裁剪失败:', error);\n  } finally {\n    cropLoading.value = false;\n  }\n};\n\n/**\n * 下载图片\n */\nconst downloadImage = () => {\n  if (!cropperImg.value) return;\n\n  const link = document.createElement('a');\n  link.download = `cropped-image-${Date.now()}.png`;\n  link.href = cropperImg.value;\n  link.click();\n};\n</script>\n<template>\n  <Page\n    title=\"VCropper 图片裁剪\"\n    description=\"VCropper是一个图片裁剪组件，提供基础的图片裁剪功能。\"\n  >\n    <Card>\n      <div class=\"image-cropper-container\">\n        <div class=\"cropper-ratio-display\">\n          <label class=\"ratio-label\">当前裁剪比例：</label>\n          <Select\n            class=\"w-24\"\n            v-model:value=\"validAspectRatio\"\n            :options=\"options\"\n          />\n          <Upload\n            :max-count=\"1\"\n            :show-upload-list=\"false\"\n            :before-upload=\"() => false\"\n            @change=\"selectImgFile\"\n          >\n            <Button>上传图片</Button>\n          </Upload>\n        </div>\n\n        <div v-if=\"imgUrl\" class=\"cropper-main-wrapper\">\n          <VCropper\n            ref=\"cropperRef\"\n            :img=\"imgUrl\"\n            :aspect-ratio=\"validAspectRatio\"\n            :width=\"600\"\n            :height=\"600\"\n          />\n\n          <!-- 操作按钮组 -->\n          <div class=\"cropper-btn-group\">\n            <Button :loading=\"cropLoading\" @click=\"cropImage\" type=\"primary\">\n              裁剪\n            </Button>\n            <Button v-if=\"cropperImg\" @click=\"downloadImage\" danger>\n              下载图片\n            </Button>\n          </div>\n\n          <!-- 裁剪预览 -->\n          <img\n            v-if=\"cropperImg\"\n            class=\"h-full w-80\"\n            :src=\"cropperImg\"\n            alt=\"裁剪预览\"\n          />\n        </div>\n      </div>\n    </Card>\n  </Page>\n</template>\n<style scoped>\n/* 比例展示区域 */\n.cropper-ratio-display {\n  @apply my-2.5 flex items-center justify-start gap-4;\n}\n\n.ratio-label {\n  @apply text-sm font-medium;\n}\n\n/* 主裁剪区域 */\n.cropper-main-wrapper {\n  @apply flex items-center gap-4;\n}\n\n.cropper-btn-group {\n  @apply flex flex-col gap-2;\n}\n</style>\n"
  },
  {
    "path": "playground/src/views/examples/doc-button.vue",
    "content": "<script lang=\"ts\" setup>\nimport { VBEN_DOC_URL } from '@vben/constants';\nimport { openWindow } from '@vben/utils';\n\nimport { Button } from 'ant-design-vue';\n\nconst props = defineProps<{ path: string }>();\n\nfunction handleClick() {\n  // 如果没有.html，打开页面时可能会出现404\n  const path =\n    VBEN_DOC_URL +\n    (props.path.toLowerCase().endsWith('.html')\n      ? props.path\n      : `${props.path}.html`);\n  openWindow(path);\n}\n</script>\n\n<template>\n  <Button @click=\"handleClick\">查看组件文档</Button>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/drawer/auto-height-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { useVbenDrawer } from '@vben/common-ui';\n\nimport { Button, message } from 'ant-design-vue';\n\nconst list = ref<number[]>([]);\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  onCancel() {\n    drawerApi.close();\n  },\n  onConfirm() {\n    message.info('onConfirm');\n    // drawerApi.close();\n  },\n  onOpenChange(isOpen) {\n    if (isOpen) {\n      handleUpdate(10);\n    }\n  },\n});\n\nfunction handleUpdate(len: number) {\n  drawerApi.setState({ loading: true });\n  setTimeout(() => {\n    list.value = Array.from({ length: len }, (_v, k) => k + 1);\n    drawerApi.setState({ loading: false });\n  }, 2000);\n}\n</script>\n<template>\n  <Drawer title=\"自动计算高度\">\n    <div\n      v-for=\"item in list\"\n      :key=\"item\"\n      class=\"flex-center h-55 w-full bg-muted even:bg-heavy\"\n    >\n      {{ item }}\n    </div>\n\n    <template #prepend-footer>\n      <Button type=\"link\" @click=\"handleUpdate(6)\">点击更新数据</Button>\n    </template>\n  </Drawer>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/drawer/base-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenDrawer } from '@vben/common-ui';\n\nimport { Button, message } from 'ant-design-vue';\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  onCancel() {\n    drawerApi.close();\n  },\n  onClosed() {\n    drawerApi.setState({ overlayBlur: 0, placement: 'right' });\n  },\n  onConfirm() {\n    message.info('onConfirm');\n    // drawerApi.close();\n  },\n});\n\nfunction lockDrawer() {\n  drawerApi.lock();\n  setTimeout(() => {\n    drawerApi.unlock();\n  }, 3000);\n}\n</script>\n<template>\n  <Drawer title=\"基础抽屉示例\" title-tooltip=\"标题提示内容\">\n    <template #extra> extra </template>\n    base demo\n    <Button type=\"primary\" @click=\"lockDrawer\">锁定抽屉状态</Button>\n    <!-- <template #prepend-footer> slot </template> -->\n    <!-- <template #append-footer> prepend slot </template> -->\n    <!-- <template #center-footer> center slot </template> -->\n  </Drawer>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/drawer/dynamic-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenDrawer } from '@vben/common-ui';\n\nimport { Button, message } from 'ant-design-vue';\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  onCancel() {\n    drawerApi.close();\n  },\n  onConfirm() {\n    message.info('onConfirm');\n    // drawerApi.close();\n  },\n  title: '动态修改配置示例',\n});\n\n// const state = drawerApi.useStore();\n\nfunction handleUpdateTitle() {\n  drawerApi.setState({ title: '内部动态标题' });\n}\n</script>\n<template>\n  <Drawer>\n    <div class=\"flex-col-center\">\n      <Button class=\"mb-3\" type=\"primary\" @click=\"handleUpdateTitle()\">\n        内部动态修改标题\n      </Button>\n    </div>\n  </Drawer>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/drawer/form-drawer-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenDrawer } from '@vben/common-ui';\n\nimport { useVbenForm } from '#/adapter/form';\n\ndefineOptions({\n  name: 'FormDrawerDemo',\n});\n\nconst [Form, formApi] = useVbenForm({\n  schema: [\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field1',\n      label: '字段1',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field2',\n      label: '字段2',\n      rules: 'required',\n    },\n  ],\n  showDefaultActions: false,\n});\nconst [Drawer, drawerApi] = useVbenDrawer({\n  onCancel() {\n    drawerApi.close();\n  },\n  onConfirm: async () => {\n    await formApi.submitForm();\n    drawerApi.close();\n  },\n  onOpenChange(isOpen: boolean) {\n    if (isOpen) {\n      const { values } = drawerApi.getData<Record<string, any>>();\n      if (values) {\n        formApi.setValues(values);\n      }\n    }\n  },\n  title: '内嵌表单示例',\n});\n</script>\n<template>\n  <Drawer>\n    <Form />\n  </Drawer>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/drawer/in-content-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { useVbenDrawer } from '@vben/common-ui';\n\nimport { Input, message } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst value = ref('');\n\nconst [Form] = useVbenForm({\n  schema: [\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: 'KeepAlive测试：内部组件',\n      },\n      fieldName: 'field1',\n      hideLabel: true,\n      label: '字段1',\n    },\n  ],\n  showDefaultActions: false,\n});\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  destroyOnClose: false,\n  onCancel() {\n    drawerApi.close();\n  },\n  onConfirm() {\n    message.info('onConfirm');\n    // drawerApi.close();\n  },\n});\n</script>\n<template>\n  <Drawer append-to-main title=\"基础抽屉示例\" title-tooltip=\"标题提示内容\">\n    <template #extra> extra </template>\n    此弹窗指定在内容区域打开，并且在关闭之后弹窗内容不会被销毁\n    <Input\n      v-model:value=\"value\"\n      placeholder=\"KeepAlive测试:connectedComponent\"\n    />\n    <Form />\n  </Drawer>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/drawer/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { DrawerPlacement, DrawerState } from '@vben/common-ui';\n\nimport { Page, useVbenDrawer } from '@vben/common-ui';\n\nimport { Button, Card } from 'ant-design-vue';\n\nimport DocButton from '../doc-button.vue';\nimport AutoHeightDemo from './auto-height-demo.vue';\nimport BaseDemo from './base-demo.vue';\nimport DynamicDemo from './dynamic-demo.vue';\nimport FormDrawerDemo from './form-drawer-demo.vue';\nimport inContentDemo from './in-content-demo.vue';\nimport SharedDataDemo from './shared-data-demo.vue';\n\ndefineOptions({ name: 'DrawerExample' });\nconst [BaseDrawer, baseDrawerApi] = useVbenDrawer({\n  // 连接抽离的组件\n  connectedComponent: BaseDemo,\n  // placement: 'left',\n});\n\nconst [InContentDrawer, inContentDrawerApi] = useVbenDrawer({\n  // 连接抽离的组件\n  connectedComponent: inContentDemo,\n  // placement: 'left',\n});\n\nconst [AutoHeightDrawer, autoHeightDrawerApi] = useVbenDrawer({\n  connectedComponent: AutoHeightDemo,\n});\n\nconst [DynamicDrawer, dynamicDrawerApi] = useVbenDrawer({\n  connectedComponent: DynamicDemo,\n});\n\nconst [SharedDataDrawer, sharedDrawerApi] = useVbenDrawer({\n  connectedComponent: SharedDataDemo,\n});\n\nconst [FormDrawer, formDrawerApi] = useVbenDrawer({\n  connectedComponent: FormDrawerDemo,\n});\n\nfunction openBaseDrawer(placement: DrawerPlacement = 'right') {\n  baseDrawerApi.setState({ placement }).open();\n}\n\nfunction openBlurDrawer() {\n  baseDrawerApi.setState({ overlayBlur: 5 }).open();\n}\n\nfunction openInContentDrawer(placement: DrawerPlacement = 'right') {\n  const state: Partial<DrawerState> = { class: '', placement };\n  if (placement === 'top') {\n    // 页面顶部区域的层级只有200，所以设置一个低于200的值，抽屉从顶部滑出来的时候才比较合适\n    state.zIndex = 199;\n  }\n  inContentDrawerApi.setState(state).open();\n}\n\nfunction openMaxContentDrawer() {\n  // 这里只是用来演示方便。实际上自己使用的时候可以直接将这些配置写在Drawer的属性里\n  inContentDrawerApi.setState({ class: 'w-full', placement: 'right' }).open();\n}\n\nfunction openAutoHeightDrawer() {\n  autoHeightDrawerApi.open();\n}\n\nfunction openDynamicDrawer() {\n  dynamicDrawerApi.open();\n}\n\nfunction handleUpdateTitle() {\n  dynamicDrawerApi.setState({ title: '外部动态标题' }).open();\n}\n\nfunction openSharedDrawer() {\n  sharedDrawerApi\n    .setData({\n      content: '外部传递的数据 content',\n      payload: '外部传递的数据 payload',\n    })\n    .open();\n}\n\nfunction openFormDrawer() {\n  formDrawerApi\n    .setData({\n      // 表单值\n      values: { field1: 'abc', field2: '123' },\n    })\n    .open();\n}\n</script>\n\n<template>\n  <Page\n    auto-content-height\n    description=\"抽屉组件通常用于在当前页面上显示一个覆盖层，用以展示重要信息或提供用户交互界面。\"\n    title=\"抽屉组件示例\"\n  >\n    <template #extra>\n      <DocButton path=\"/components/common-ui/vben-drawer\" />\n    </template>\n    <BaseDrawer />\n    <InContentDrawer />\n    <AutoHeightDrawer />\n    <DynamicDrawer />\n    <SharedDataDrawer />\n    <FormDrawer />\n\n    <Card class=\"mb-4\" title=\"基本使用\">\n      <p class=\"mb-3\">一个基础的抽屉示例</p>\n      <Button class=\"mb-2\" type=\"primary\" @click=\"openBaseDrawer('right')\">\n        右侧打开\n      </Button>\n      <Button\n        class=\"mb-2 ml-2\"\n        type=\"primary\"\n        @click=\"openBaseDrawer('bottom')\"\n      >\n        底部打开\n      </Button>\n      <Button class=\"mb-2 ml-2\" type=\"primary\" @click=\"openBaseDrawer('left')\">\n        左侧打开\n      </Button>\n      <Button class=\"mb-2 ml-2\" type=\"primary\" @click=\"openBaseDrawer('top')\">\n        顶部打开\n      </Button>\n      <Button class=\"mb-2 ml-2\" type=\"primary\" @click=\"openBlurDrawer\">\n        遮罩层模糊效果\n      </Button>\n    </Card>\n\n    <Card class=\"mb-4\" title=\"在内容区域打开\">\n      <p class=\"mb-3\">指定抽屉在内容区域打开，不会覆盖顶部和左侧菜单等区域</p>\n      <Button class=\"mb-2\" type=\"primary\" @click=\"openInContentDrawer('right')\">\n        右侧打开\n      </Button>\n      <Button\n        class=\"mb-2 ml-2\"\n        type=\"primary\"\n        @click=\"openInContentDrawer('bottom')\"\n      >\n        底部打开\n      </Button>\n      <Button\n        class=\"mb-2 ml-2\"\n        type=\"primary\"\n        @click=\"openInContentDrawer('left')\"\n      >\n        左侧打开\n      </Button>\n      <Button\n        class=\"mb-2 ml-2\"\n        type=\"primary\"\n        @click=\"openInContentDrawer('top')\"\n      >\n        顶部打开\n      </Button>\n      <Button class=\"mb-2 ml-2\" type=\"primary\" @click=\"openMaxContentDrawer\">\n        内容区域全屏打开\n      </Button>\n    </Card>\n\n    <Card class=\"mb-4\" title=\"内容高度自适应滚动\">\n      <p class=\"mb-3\">可根据内容自动计算滚动高度</p>\n      <Button type=\"primary\" @click=\"openAutoHeightDrawer\">打开抽屉</Button>\n    </Card>\n\n    <Card class=\"mb-4\" title=\"动态配置示例\">\n      <p class=\"mb-3\">通过 setState 动态调整抽屉数据</p>\n      <Button type=\"primary\" @click=\"openDynamicDrawer\">打开抽屉</Button>\n      <Button class=\"ml-2\" type=\"primary\" @click=\"handleUpdateTitle\">\n        从外部修改标题并打开\n      </Button>\n    </Card>\n\n    <Card class=\"mb-4\" title=\"内外数据共享示例\">\n      <p class=\"mb-3\">通过共享 sharedData 来进行数据交互</p>\n      <Button type=\"primary\" @click=\"openSharedDrawer\">\n        打开抽屉并传递数据\n      </Button>\n    </Card>\n\n    <Card class=\"mb-4\" title=\"表单抽屉示例\">\n      <p class=\"mb-3\">打开抽屉并设置表单schema以及数据</p>\n      <Button type=\"primary\" @click=\"openFormDrawer\">\n        打开抽屉并设置表单schema以及数据\n      </Button>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/drawer/shared-data-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { useVbenDrawer } from '@vben/common-ui';\n\nimport { message } from 'ant-design-vue';\n\nconst data = ref();\n\nconst [Drawer, drawerApi] = useVbenDrawer({\n  onCancel() {\n    drawerApi.close();\n  },\n  onConfirm() {\n    message.info('onConfirm');\n    // drawerApi.close();\n  },\n  onOpenChange(isOpen: boolean) {\n    if (isOpen) {\n      data.value = drawerApi.getData<Record<string, any>>();\n    }\n  },\n});\n</script>\n<template>\n  <Drawer title=\"数据共享示例\">\n    <div class=\"flex-col-center\">外部传递数据： {{ data }}</div>\n  </Drawer>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/ellipsis/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { EllipsisText, Page } from '@vben/common-ui';\n\nimport { Card } from 'ant-design-vue';\n\nimport DocButton from '../doc-button.vue';\n\nconst longText = `Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案，目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈，可以作为项目的启动模版，以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例，用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术，并将其应用在项目中。`;\n\nconst text = ref(longText);\n</script>\n\n<template>\n  <Page\n    description=\"用于多行文本省略，支持点击展开和自定义内容。\"\n    title=\"文本省略组件示例\"\n  >\n    <template #extra>\n      <DocButton class=\"mb-2\" path=\"/components/common-ui/vben-ellipsis-text\" />\n    </template>\n    <Card class=\"mb-4\" title=\"基本使用\">\n      <EllipsisText :max-width=\"500\">{{ text }}</EllipsisText>\n    </Card>\n\n    <Card class=\"mb-4\" title=\"多行省略\">\n      <EllipsisText :line=\"2\">{{ text }}</EllipsisText>\n    </Card>\n\n    <Card class=\"mb-4\" title=\"点击展开\">\n      <EllipsisText :line=\"3\" expand>{{ text }}</EllipsisText>\n    </Card>\n    <Card class=\"mb-4\" title=\"自定义内容\">\n      <EllipsisText :max-width=\"240\">\n        住在我心里孤独的 孤独的海怪 痛苦之王 开始厌倦 深海的光 停滞的海浪\n        <template #tooltip>\n          <div style=\"text-align: center\">\n            《秦皇岛》<br />住在我心里孤独的<br />孤独的海怪 痛苦之王<br />开始厌倦\n            深海的光 停滞的海浪\n          </div>\n        </template>\n      </EllipsisText>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/form/api.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { RefSelectProps } from 'ant-design-vue/es/select';\n\nimport { ref } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Button, Card, message, Space } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst isReverseActionButtons = ref(false);\n\nconst [BaseForm, formApi] = useVbenForm({\n  // 翻转操作按钮的位置\n  actionButtonsReverse: isReverseActionButtons.value,\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  // 使用 tailwindcss grid布局\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  layout: 'horizontal',\n  // 水平布局，label和input在同一行\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入用户名',\n      },\n      // 字段名\n      fieldName: 'field1',\n      // 界面显示的label\n      label: 'field1',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field2',\n      label: 'field2',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      fieldName: 'fieldOptions',\n      label: '下拉选',\n    },\n  ],\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n\nfunction handleClick(\n  action:\n    | 'batchAddSchema'\n    | 'batchDeleteSchema'\n    | 'componentRef'\n    | 'disabled'\n    | 'hiddenAction'\n    | 'hiddenResetButton'\n    | 'hiddenSubmitButton'\n    | 'labelWidth'\n    | 'resetDisabled'\n    | 'resetLabelWidth'\n    | 'reverseActionButtons'\n    | 'showAction'\n    | 'showResetButton'\n    | 'showSubmitButton'\n    | 'updateActionAlign'\n    | 'updateResetButton'\n    | 'updateSchema'\n    | 'updateSubmitButton',\n) {\n  switch (action) {\n    case 'batchAddSchema': {\n      formApi.setState((prev) => {\n        const currentSchema = prev?.schema ?? [];\n        const newSchema = [];\n        for (let i = 0; i < 3; i++) {\n          newSchema.push({\n            component: 'Input',\n            componentProps: {\n              placeholder: '请输入',\n            },\n            fieldName: `field${i}${Date.now()}`,\n            label: `field+`,\n          });\n        }\n        return {\n          schema: [...currentSchema, ...newSchema],\n        };\n      });\n      break;\n    }\n\n    case 'batchDeleteSchema': {\n      formApi.setState((prev) => {\n        const currentSchema = prev?.schema ?? [];\n        return {\n          schema: currentSchema.slice(0, -3),\n        };\n      });\n      break;\n    }\n    case 'componentRef': {\n      // 获取下拉组件的实例，并调用它的focus方法\n      formApi.getFieldComponentRef<RefSelectProps>('fieldOptions')?.focus?.();\n      break;\n    }\n    case 'disabled': {\n      formApi.setState({ commonConfig: { disabled: true } });\n      break;\n    }\n    case 'hiddenAction': {\n      formApi.setState({ showDefaultActions: false });\n      break;\n    }\n    case 'hiddenResetButton': {\n      formApi.setState({ resetButtonOptions: { show: false } });\n      break;\n    }\n    case 'hiddenSubmitButton': {\n      formApi.setState({ submitButtonOptions: { show: false } });\n      break;\n    }\n    case 'labelWidth': {\n      formApi.setState({\n        commonConfig: {\n          labelWidth: 150,\n        },\n      });\n      break;\n    }\n    case 'resetDisabled': {\n      formApi.setState({ commonConfig: { disabled: false } });\n      break;\n    }\n    case 'resetLabelWidth': {\n      formApi.setState({\n        commonConfig: {\n          labelWidth: 100,\n        },\n      });\n      break;\n    }\n    case 'reverseActionButtons': {\n      isReverseActionButtons.value = !isReverseActionButtons.value;\n      formApi.setState({ actionButtonsReverse: isReverseActionButtons.value });\n      break;\n    }\n    case 'showAction': {\n      formApi.setState({ showDefaultActions: true });\n      break;\n    }\n    case 'showResetButton': {\n      formApi.setState({ resetButtonOptions: { show: true } });\n      break;\n    }\n    case 'showSubmitButton': {\n      formApi.setState({ submitButtonOptions: { show: true } });\n      break;\n    }\n\n    case 'updateActionAlign': {\n      formApi.setState({\n        // 可以自行调整class\n        actionWrapperClass: 'text-center',\n      });\n      break;\n    }\n    case 'updateResetButton': {\n      formApi.setState({\n        resetButtonOptions: { disabled: true },\n      });\n      break;\n    }\n    case 'updateSchema': {\n      formApi.updateSchema([\n        {\n          componentProps: {\n            options: [\n              {\n                label: '选项1',\n                value: '1',\n              },\n              {\n                label: '选项2',\n                value: '2',\n              },\n              {\n                label: '选项3',\n                value: '3',\n              },\n            ],\n          },\n          fieldName: 'fieldOptions',\n        },\n      ]);\n      message.success('字段 `fieldOptions` 下拉选项更新成功。');\n      break;\n    }\n    case 'updateSubmitButton': {\n      formApi.setState({\n        submitButtonOptions: { loading: true },\n      });\n      break;\n    }\n  }\n}\n</script>\n\n<template>\n  <Page description=\"表单组件api操作示例。\" title=\"表单组件\">\n    <Space class=\"mb-5 flex-wrap\">\n      <Button @click=\"handleClick('updateSchema')\">updateSchema</Button>\n      <Button @click=\"handleClick('labelWidth')\">更改labelWidth</Button>\n      <Button @click=\"handleClick('resetLabelWidth')\">还原labelWidth</Button>\n      <Button @click=\"handleClick('disabled')\">禁用表单</Button>\n      <Button @click=\"handleClick('resetDisabled')\">解除禁用</Button>\n      <Button @click=\"handleClick('reverseActionButtons')\">\n        翻转操作按钮位置\n      </Button>\n      <Button @click=\"handleClick('hiddenAction')\">隐藏操作按钮</Button>\n      <Button @click=\"handleClick('showAction')\">显示操作按钮</Button>\n      <Button @click=\"handleClick('hiddenResetButton')\">隐藏重置按钮</Button>\n      <Button @click=\"handleClick('showResetButton')\">显示重置按钮</Button>\n      <Button @click=\"handleClick('hiddenSubmitButton')\">隐藏提交按钮</Button>\n      <Button @click=\"handleClick('showSubmitButton')\">显示提交按钮</Button>\n      <Button @click=\"handleClick('updateResetButton')\">修改重置按钮</Button>\n      <Button @click=\"handleClick('updateSubmitButton')\">修改提交按钮</Button>\n      <Button @click=\"handleClick('updateActionAlign')\">\n        调整操作按钮位置\n      </Button>\n      <Button @click=\"handleClick('batchAddSchema')\"> 批量添加表单项 </Button>\n      <Button @click=\"handleClick('batchDeleteSchema')\">\n        批量删除表单项\n      </Button>\n      <Button @click=\"handleClick('componentRef')\">下拉组件获取焦点</Button>\n    </Space>\n    <Card title=\"操作示例\">\n      <BaseForm />\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/form/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { UploadFile } from 'ant-design-vue';\n\nimport { h, ref, toRaw } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { useDebounceFn } from '@vueuse/core';\nimport { Button, Card, message, Spin, Tag } from 'ant-design-vue';\nimport dayjs from 'dayjs';\n\nimport { useVbenForm, z } from '#/adapter/form';\nimport { getAllMenusApi } from '#/api';\nimport { upload_file } from '#/api/examples/upload';\nimport { $t } from '#/locales';\n\nimport DocButton from '../doc-button.vue';\n\nconst keyword = ref('');\nconst fetching = ref(false);\n// 模拟远程获取数据\nfunction fetchRemoteOptions({ keyword = '选项' }: Record<string, any>) {\n  fetching.value = true;\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      const options = Array.from({ length: 10 }).map((_, index) => ({\n        label: `${keyword}-${index}`,\n        value: `${keyword}-${index}`,\n      }));\n      resolve(options);\n      fetching.value = false;\n    }, 1000);\n  });\n}\n\nconst [BaseForm, baseFormApi] = useVbenForm({\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 在label后显示一个冒号\n    colon: true,\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  fieldMappingTime: [['rangePicker', ['startTime', 'endTime'], 'YYYY-MM-DD']],\n  // 提交函数\n  handleSubmit: onSubmit,\n  handleValuesChange(_values, fieldsChanged) {\n    message.info(`表单以下字段发生变化：${fieldsChanged.join('，')}`);\n  },\n\n  // 垂直布局，label和input在不同行，值为vertical\n  // 水平布局，label和input在同一行\n  layout: 'horizontal',\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入用户名',\n      },\n      // 字段名\n      fieldName: 'username',\n      // 界面显示的label\n      label: '字符串',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      fieldName: 'desc',\n      // 界面显示的description\n      description: '这是表单描述',\n      label: '字符串(带描述)',\n    },\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'ApiSelect',\n      // 对应组件的参数\n      componentProps: {\n        // 菜单接口转options格式\n        afterFetch: (data: { name: string; path: string }[]) => {\n          return data.map((item: any) => ({\n            label: item.name,\n            value: item.path,\n          }));\n        },\n        // 菜单接口\n        api: getAllMenusApi,\n        autoSelect: 'first',\n      },\n      // 字段名\n      fieldName: 'api',\n      // 界面显示的label\n      label: 'ApiSelect',\n    },\n    {\n      component: 'ApiSelect',\n      // 对应组件的参数\n      componentProps: () => {\n        return {\n          api: fetchRemoteOptions,\n          // 禁止本地过滤\n          filterOption: false,\n          // 如果正在获取数据，使用插槽显示一个loading\n          notFoundContent: fetching.value ? undefined : null,\n          // 搜索词变化时记录下来， 使用useDebounceFn防抖。\n          onSearch: useDebounceFn((value: string) => {\n            keyword.value = value;\n          }, 300),\n          // 远程搜索参数。当搜索词变化时，params也会更新\n          params: {\n            keyword: keyword.value || undefined,\n          },\n          showSearch: true,\n        };\n      },\n      // 字段名\n      fieldName: 'remoteSearch',\n      // 界面显示的label\n      label: '远程搜索',\n      renderComponentContent: () => {\n        return {\n          notFoundContent: fetching.value ? h(Spin) : undefined,\n        };\n      },\n      rules: 'selectRequired',\n    },\n    {\n      component: 'ApiTreeSelect',\n      // 对应组件的参数\n      componentProps: {\n        // 菜单接口\n        api: getAllMenusApi,\n        // 菜单接口转options格式\n        labelField: 'name',\n        valueField: 'path',\n        childrenField: 'children',\n      },\n      // 字段名\n      fieldName: 'apiTree',\n      // 界面显示的label\n      label: 'ApiTreeSelect',\n    },\n    {\n      component: 'InputPassword',\n      componentProps: {\n        placeholder: '请输入密码',\n      },\n      fieldName: 'password',\n      label: '密码',\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'number',\n      label: '数字(带后缀)',\n      suffix: () => '¥',\n    },\n    {\n      component: 'IconPicker',\n      fieldName: 'icon',\n      label: '图标',\n    },\n    {\n      colon: false,\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      fieldName: 'options',\n      label: () => h(Tag, { color: 'warning' }, () => '😎自定义：'),\n    },\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n      },\n      fieldName: 'radioGroup',\n      label: '单选组',\n    },\n    {\n      component: 'Radio',\n      fieldName: 'radio',\n      label: '',\n      renderComponentContent: () => {\n        return {\n          default: () => ['Radio'],\n        };\n      },\n    },\n    {\n      component: 'CheckboxGroup',\n      componentProps: {\n        name: 'cname',\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n      },\n      fieldName: 'checkboxGroup',\n      label: '多选组',\n    },\n    {\n      component: 'Checkbox',\n      fieldName: 'checkbox',\n      label: '',\n      renderComponentContent: () => {\n        return {\n          default: () => ['我已阅读并同意'],\n        };\n      },\n      rules: z\n        .boolean()\n        .refine((v) => v, { message: '为什么不同意？勾上它！' }),\n    },\n    {\n      component: 'Mentions',\n      componentProps: {\n        options: [\n          {\n            label: 'afc163',\n            value: 'afc163',\n          },\n          {\n            label: 'zombieJ',\n            value: 'zombieJ',\n          },\n        ],\n        placeholder: '请输入',\n      },\n      fieldName: 'mentions',\n      label: '提及',\n    },\n    {\n      component: 'Rate',\n      fieldName: 'rate',\n      label: '评分',\n    },\n    {\n      component: 'Switch',\n      componentProps: {\n        class: 'w-auto',\n      },\n      fieldName: 'switch',\n      help: () =>\n        ['这是一个多行帮助信息', '第二行', '第三行'].map((v) => h('p', v)),\n      label: '开关',\n    },\n    {\n      component: 'DatePicker',\n      fieldName: 'datePicker',\n      label: '日期选择框',\n    },\n    {\n      component: 'RangePicker',\n      fieldName: 'rangePicker',\n      label: '范围选择器',\n    },\n    {\n      component: 'TimePicker',\n      fieldName: 'timePicker',\n      label: '时间选择框',\n    },\n    {\n      component: 'TreeSelect',\n      componentProps: {\n        allowClear: true,\n        placeholder: '请选择',\n        showSearch: true,\n        treeData: [\n          {\n            label: 'root 1',\n            value: 'root 1',\n            children: [\n              {\n                label: 'parent 1',\n                value: 'parent 1',\n                children: [\n                  {\n                    label: 'parent 1-0',\n                    value: 'parent 1-0',\n                    children: [\n                      {\n                        label: 'my leaf',\n                        value: 'leaf1',\n                      },\n                      {\n                        label: 'your leaf',\n                        value: 'leaf2',\n                      },\n                    ],\n                  },\n                  {\n                    label: 'parent 1-1',\n                    value: 'parent 1-1',\n                  },\n                ],\n              },\n              {\n                label: 'parent 2',\n                value: 'parent 2',\n              },\n            ],\n          },\n        ],\n        treeNodeFilterProp: 'label',\n      },\n      fieldName: 'treeSelect',\n      label: '树选择',\n    },\n    {\n      component: 'Upload',\n      componentProps: {\n        // 更多属性见：https://ant.design/components/upload-cn\n        accept: '.png,.jpg,.jpeg',\n        // 自动携带认证信息\n        customRequest: upload_file,\n        disabled: false,\n        maxCount: 3,\n        // 单位：MB\n        maxSize: 2,\n        multiple: false,\n        showUploadList: true,\n        // 上传列表的内建样式，支持四种基本样式 text, picture, picture-card 和 picture-circle\n        listType: 'picture-card',\n        draggable: true, // 启用拖拽排序\n        // onChange事件已被重写，如需自定义请在此基础上扩展\n        handleChange: ({ file }: { file: UploadFile }) => {\n          const { name, status } = file;\n          if (status === 'done') {\n            message.success(`${name} ${$t('examples.form.upload-success')}`);\n          } else if (status === 'error') {\n            message.error(`${name} ${$t('examples.form.upload-fail')}`);\n          }\n        },\n        onDragSort: (oldIndex: number, newIndex: number) => {\n          console.warn(`图片从 ${oldIndex} 移动到 ${newIndex}`);\n        },\n      },\n      fieldName: 'files',\n      label: $t('examples.form.file'),\n      renderComponentContent: () => {\n        return {\n          default: () => $t('examples.form.upload-image'),\n        };\n      },\n      rules: 'selectRequired',\n    },\n    {\n      component: 'Upload',\n      componentProps: {\n        accept: '.png,.jpg,.jpeg',\n        customRequest: upload_file,\n        maxCount: 1,\n        maxSize: 2,\n        listType: 'picture-card',\n        // 是否启用图片裁剪(多选或者非图片不唤起裁剪框)\n        crop: true,\n        // 裁剪比例\n        aspectRatio: '1:1',\n      },\n      fieldName: 'cropImage',\n      label: $t('examples.form.crop-image'),\n      renderComponentContent: () => {\n        return {\n          default: () => $t('examples.form.upload-image'),\n        };\n      },\n      rules: 'selectRequired',\n    },\n  ],\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  const files = toRaw(values.files) as UploadFile[];\n  const cropImage = (toRaw(values.cropImage) ?? []) as UploadFile[];\n  const doneFiles = files.filter((file) => file.status === 'done');\n  const failedFiles = files.filter((file) => file.status !== 'done');\n  const doneCrop = cropImage.filter((file) => file.status === 'done');\n  const failedCrop = cropImage.filter((file) => file.status !== 'done');\n\n  const msg = [\n    ...doneFiles.map((file) => file.response?.url || file.url),\n    ...failedFiles.map((file) => file.name),\n  ].join(', ');\n  const msgCrop = [\n    ...doneCrop.map((file) => file.response?.url || file.url),\n    ...failedCrop.map((file) => file.name),\n  ].join(', ');\n\n  if (failedFiles.length === 0) {\n    message.success({\n      content: `${$t('examples.form.upload-urls')}: ${msg}`,\n    });\n  } else {\n    message.error({\n      content: `${$t('examples.form.upload-error')}: ${msg}`,\n    });\n    return;\n  }\n  if (doneCrop.length > 0 && failedCrop.length === 0) {\n    message.success({\n      content: `${$t('examples.form.upload-urls')}: ${msgCrop}`,\n    });\n  } else if (failedCrop.length > 0) {\n    message.error({\n      content: `${$t('examples.form.upload-error')}: ${msgCrop}`,\n    });\n    return;\n  }\n  // 如果需要可提交前替换为需要的urls\n  values.files = doneFiles.map((file) => file.response?.url || file.url);\n  values.cropImage = doneCrop.map((file) => file.response?.url || file.url);\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n\nfunction handleSetFormValue() {\n  /**\n   * 设置表单值(多个)\n   */\n  baseFormApi.setValues({\n    checkboxGroup: ['1'],\n    datePicker: dayjs('2022-01-01'),\n    files: [\n      {\n        name: 'example.png',\n        status: 'done',\n        uid: '-1',\n        url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',\n      },\n    ],\n    mentions: '@afc163',\n    number: 3,\n    options: '1',\n    password: '2',\n    radioGroup: '1',\n    rangePicker: [dayjs('2022-01-01'), dayjs('2022-01-02')],\n    rate: 3,\n    switch: true,\n    timePicker: dayjs('2022-01-01 12:00:00'),\n    treeSelect: 'leaf1',\n    username: '1',\n  });\n\n  // 设置单个表单值\n  baseFormApi.setFieldValue('checkbox', true);\n}\n</script>\n\n<template>\n  <Page\n    content-class=\"flex flex-col gap-4\"\n    description=\"表单组件基础示例，请注意，该页面用到的参数代码会添加一些简单注释，方便理解，请仔细查看。\"\n    title=\"表单组件\"\n  >\n    <template #description>\n      <div class=\"text-muted-foreground\">\n        <p>\n          表单组件基础示例，请注意，该页面用到的参数代码会添加一些简单注释，方便理解，请仔细查看。\n        </p>\n      </div>\n    </template>\n    <template #extra>\n      <DocButton class=\"mb-2\" path=\"/components/common-ui/vben-form\" />\n    </template>\n    <Card title=\"基础示例\">\n      <template #extra>\n        <Button type=\"primary\" @click=\"handleSetFormValue\">设置表单值</Button>\n      </template>\n      <BaseForm />\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/form/custom-layout.vue",
    "content": "<script lang=\"ts\" setup>\nimport { h } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Card } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nimport DocButton from '../doc-button.vue';\n\nconst [CustomLayoutForm] = useVbenForm({\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  layout: 'horizontal',\n  schema: [\n    {\n      component: 'Select',\n      fieldName: 'field1',\n      label: '字符串',\n    },\n    {\n      component: 'TreeSelect',\n      fieldName: 'field2',\n      label: '字符串',\n    },\n    {\n      component: 'Mentions',\n      fieldName: 'field3',\n      label: '字符串',\n    },\n    {\n      component: 'Input',\n      fieldName: 'field4',\n      label: '字符串',\n    },\n    {\n      component: 'InputNumber',\n      fieldName: 'field5',\n      // 从第三列开始 相当于中间空了一列\n      formItemClass: 'col-start-3',\n      label: '前面空了一列',\n    },\n    {\n      component: 'Divider',\n      fieldName: '_divider',\n      formItemClass: 'col-span-3',\n      hideLabel: true,\n      renderComponentContent: () => {\n        return {\n          default: () => h('div', '分割线'),\n        };\n      },\n    },\n    {\n      component: 'Textarea',\n      fieldName: 'field6',\n      // 占满三列空间 基线对齐\n      formItemClass: 'col-span-3 items-baseline',\n      label: '占满三列',\n    },\n    {\n      component: 'Input',\n      fieldName: 'field7',\n      // 占满2列空间 从第二列开始 相当于前面空了一列\n      formItemClass: 'col-span-2 col-start-2',\n      label: '占满2列',\n    },\n    {\n      component: 'Input',\n      fieldName: 'field8',\n      // 左右留空\n      formItemClass: 'col-start-2',\n      label: '左右留空',\n    },\n    {\n      component: 'InputPassword',\n      fieldName: 'field9',\n      formItemClass: 'col-start-1',\n      label: '字符串',\n    },\n  ],\n  // 一共三列\n  wrapperClass: 'grid-cols-3',\n});\n</script>\n\n<template>\n  <Page\n    content-class=\"flex flex-col gap-4\"\n    description=\"使用tailwind自定义表单项的布局\"\n    title=\"表单自定义布局\"\n  >\n    <template #description>\n      <div class=\"text-muted-foreground\">\n        <p>使用tailwind自定义表单项的布局，使用Divider分割表单。</p>\n      </div>\n    </template>\n    <template #extra>\n      <DocButton class=\"mb-2\" path=\"/components/common-ui/vben-form\" />\n    </template>\n    <Card title=\"使用tailwind自定义布局\">\n      <CustomLayoutForm />\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/form/custom.vue",
    "content": "<script lang=\"ts\" setup>\nimport { h, markRaw } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Card, Input, message } from 'ant-design-vue';\n\nimport { useVbenForm, z } from '#/adapter/form';\n\nimport TwoFields from './modules/two-fields.vue';\n\nconst [Form] = useVbenForm({\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n    labelClass: 'w-2/6',\n  },\n  fieldMappingTime: [['field4', ['phoneType', 'phoneNumber'], null]],\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  // 水平布局，label和input在同一行\n  layout: 'horizontal',\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      fieldName: 'field',\n      label: '自定义后缀',\n      suffix: () => h('span', { class: 'text-red-600' }, '元'),\n    },\n    {\n      component: 'Input',\n      fieldName: 'field1',\n      label: '自定义组件slot',\n      renderComponentContent: () => ({\n        prefix: () => 'prefix',\n        suffix: () => 'suffix',\n      }),\n    },\n    {\n      component: h(Input, { placeholder: '请输入Field2' }),\n      fieldName: 'field2',\n      label: '自定义组件',\n      modelPropName: 'value',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      fieldName: 'field3',\n      label: '自定义组件(slot)',\n      rules: 'required',\n    },\n    {\n      component: markRaw(TwoFields),\n      defaultValue: [undefined, ''],\n      disabledOnChangeListener: false,\n      fieldName: 'field4',\n      formItemClass: 'col-span-1',\n      label: '组合字段',\n      rules: z\n        .array(z.string().optional())\n        .length(2, '请选择类型并输入手机号码')\n        .refine((v) => !!v[0], {\n          message: '请选择类型',\n        })\n        .refine((v) => !!v[1] && v[1] !== '', {\n          message: '　　　　　　　输入手机号码',\n        })\n        .refine((v) => v[1]?.match(/^1[3-9]\\d{9}$/), {\n          // 使用全角空格占位，将错误提示文字挤到手机号码输入框的下面\n          message: '　　　　　　　号码格式不正确',\n        }),\n    },\n  ],\n  // 中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-2',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n</script>\n\n<template>\n  <Page description=\"表单组件自定义示例\" title=\"表单组件\">\n    <Card title=\"基础示例\">\n      <Form>\n        <template #field3=\"slotProps\">\n          <Input placeholder=\"请输入\" v-bind=\"slotProps\" />\n        </template>\n      </Form>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/form/dynamic.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Page } from '@vben/common-ui';\n\nimport { Button, Card, message } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst [Form, formApi] = useVbenForm({\n  // 提交函数\n  handleSubmit: onSubmit,\n  schema: [\n    {\n      component: 'Input',\n      defaultValue: 'hidden value',\n      dependencies: {\n        show: false,\n        // 随意一个字段改变时，都会触发\n        triggerFields: ['field1Switch'],\n      },\n      fieldName: 'hiddenField',\n      label: '隐藏字段',\n    },\n    {\n      component: 'Switch',\n      defaultValue: true,\n      fieldName: 'field1Switch',\n      help: '通过Dom控制销毁',\n      label: '显示字段1',\n    },\n    {\n      component: 'Switch',\n      defaultValue: true,\n      fieldName: 'field2Switch',\n      help: '通过css控制隐藏',\n      label: '显示字段2',\n    },\n    {\n      component: 'Switch',\n      fieldName: 'field3Switch',\n      label: '禁用字段3',\n    },\n    {\n      component: 'Switch',\n      fieldName: 'field4Switch',\n      label: '字段4必填',\n    },\n    {\n      component: 'Input',\n      dependencies: {\n        if(values) {\n          return !!values.field1Switch;\n        },\n        // 只有指定的字段改变时，才会触发\n        triggerFields: ['field1Switch'],\n      },\n      // 字段名\n      fieldName: 'field1',\n      // 界面显示的label\n      label: '字段1',\n    },\n    {\n      component: 'Input',\n      dependencies: {\n        show(values) {\n          return !!values.field2Switch;\n        },\n        triggerFields: ['field2Switch'],\n      },\n      fieldName: 'field2',\n      label: '字段2',\n    },\n    {\n      component: 'Input',\n      dependencies: {\n        disabled(values) {\n          return !!values.field3Switch;\n        },\n        triggerFields: ['field3Switch'],\n      },\n      fieldName: 'field3',\n      label: '字段3',\n    },\n    {\n      component: 'Input',\n      dependencies: {\n        required(values) {\n          return !!values.field4Switch;\n        },\n        triggerFields: ['field4Switch'],\n      },\n      fieldName: 'field4',\n      label: '字段4',\n    },\n    {\n      component: 'Input',\n      dependencies: {\n        rules(values) {\n          if (values.field1 === '123') {\n            return 'required';\n          }\n          return null;\n        },\n        triggerFields: ['field1'],\n      },\n      fieldName: 'field5',\n      help: '当字段1的值为`123`时，必填',\n      label: '动态rules',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        class: 'w-full',\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      dependencies: {\n        componentProps(values) {\n          if (values.field2 === '123') {\n            return {\n              options: [\n                {\n                  label: '选项1',\n                  value: '1',\n                },\n                {\n                  label: '选项2',\n                  value: '2',\n                },\n                {\n                  label: '选项3',\n                  value: '3',\n                },\n              ],\n            };\n          }\n          return {};\n        },\n        triggerFields: ['field2'],\n      },\n      fieldName: 'field6',\n      help: '当字段2的值为`123`时，更改下拉选项',\n      label: '动态配置',\n    },\n    {\n      component: 'Input',\n      fieldName: 'field7',\n      label: '字段7',\n    },\n  ],\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-3 lg:grid-cols-4',\n});\n\nconst [SyncForm] = useVbenForm({\n  handleSubmit: onSubmit,\n  schema: [\n    {\n      component: 'Input',\n      // 字段名\n      fieldName: 'field1',\n      // 界面显示的label\n      label: '字段1',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        disabled: true,\n      },\n      dependencies: {\n        trigger(values, form) {\n          form.setFieldValue('field2', values.field1);\n        },\n        // 只有指定的字段改变时，才会触发\n        triggerFields: ['field1'],\n      },\n      // 字段名\n      fieldName: 'field2',\n      // 界面显示的label\n      label: '字段2',\n    },\n  ],\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-3 lg:grid-cols-4',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n\nfunction handleDelete() {\n  formApi.setState((prev) => {\n    return {\n      schema: prev.schema?.filter((item) => item.fieldName !== 'field7'),\n    };\n  });\n}\n\nfunction handleAdd() {\n  formApi.setState((prev) => {\n    return {\n      schema: [\n        ...(prev?.schema ?? []),\n        {\n          component: 'Input',\n          fieldName: `field${Date.now()}`,\n          label: '字段+',\n        },\n      ],\n    };\n  });\n}\n\nfunction handleUpdate() {\n  formApi.setState((prev) => {\n    return {\n      schema: prev.schema?.map((item) => {\n        if (item.fieldName === 'field3') {\n          return {\n            ...item,\n            label: '字段3-修改',\n          };\n        }\n        return item;\n      }),\n    };\n  });\n}\n</script>\n\n<template>\n  <Page\n    description=\"表单组件动态联动示例，包含了常用的场景。增删改，本质上是修改schema，你也可以通过 `setState` 动态修改schema。\"\n    title=\"表单组件\"\n  >\n    <Card title=\"表单动态联动示例\">\n      <template #extra>\n        <Button class=\"mr-2\" @click=\"handleUpdate\">修改字段3</Button>\n        <Button class=\"mr-2\" @click=\"handleDelete\">删除字段7</Button>\n        <Button @click=\"handleAdd\">添加字段</Button>\n      </template>\n      <Form />\n    </Card>\n\n    <Card class=\"mt-5\" title=\"字段同步，字段1数据与字段2数据同步\">\n      <SyncForm />\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/form/merge.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Button, Card, message, Step, Steps, Switch } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst currentTab = ref(0);\nfunction onFirstSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form1 values: ${JSON.stringify(values)}`,\n  });\n  currentTab.value = 1;\n}\nfunction onSecondReset() {\n  currentTab.value = 0;\n}\nfunction onSecondSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form2 values: ${JSON.stringify(values)}`,\n  });\n}\n\nconst [FirstForm, firstFormApi] = useVbenForm({\n  commonConfig: {\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  handleSubmit: onFirstSubmit,\n  layout: 'horizontal',\n  resetButtonOptions: {\n    show: false,\n  },\n  schema: [\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'formFirst',\n      label: '表单1字段',\n      rules: 'required',\n    },\n  ],\n  submitButtonOptions: {\n    content: '下一步',\n  },\n  wrapperClass: 'grid-cols-1 md:grid-cols-1 lg:grid-cols-1',\n});\nconst [SecondForm, secondFormApi] = useVbenForm({\n  commonConfig: {\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  handleReset: onSecondReset,\n  handleSubmit: onSecondSubmit,\n  layout: 'horizontal',\n  resetButtonOptions: {\n    content: '上一步',\n  },\n  schema: [\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'formSecond',\n      label: '表单2字段',\n      rules: 'required',\n    },\n  ],\n  wrapperClass: 'grid-cols-1 md:grid-cols-1 lg:grid-cols-1',\n});\nconst needMerge = ref(true);\nasync function handleMergeSubmit() {\n  const values = await firstFormApi\n    .merge(secondFormApi)\n    .submitAllForm(needMerge.value);\n  message.success({\n    content: `merged form values: ${JSON.stringify(values)}`,\n  });\n}\n</script>\n\n<template>\n  <Page\n    description=\"表单组件合并示例：在某些场景下，例如分步表单，需要合并多个表单并统一提交。默认情况下，使用 Object.assign 规则合并表单。如果需要特殊处理数据，可以传入 false。\"\n    title=\"表单组件\"\n  >\n    <Card title=\"基础示例\">\n      <template #extra>\n        <Switch\n          v-model=\"needMerge\"\n          checked-children=\"开启字段合并\"\n          class=\"mr-4\"\n          un-checked-children=\"关闭字段合并\"\n        />\n        <Button type=\"primary\" @click=\"handleMergeSubmit\">合并提交</Button>\n      </template>\n      <div class=\"mx-auto max-w-lg\">\n        <Steps :current=\"currentTab\" class=\"steps\">\n          <Step title=\"表单1\" />\n          <Step title=\"表单2\" />\n        </Steps>\n        <div class=\"p-20\">\n          <FirstForm v-show=\"currentTab === 0\" />\n          <SecondForm v-show=\"currentTab === 1\" />\n        </div>\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/form/modules/two-fields.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Input, Select } from 'ant-design-vue';\n\nconst emit = defineEmits(['blur', 'change']);\n\nconst modelValue = defineModel<[string, string]>({\n  default: () => [undefined, undefined],\n});\n\nfunction onChange() {\n  emit('change', modelValue.value);\n}\n</script>\n<template>\n  <div class=\"flex w-full gap-1\">\n    <Select\n      v-model:value=\"modelValue[0]\"\n      class=\"w-20\"\n      placeholder=\"类型\"\n      allow-clear\n      :class=\"{ 'valid-success': !!modelValue[0] }\"\n      :options=\"[\n        { label: '个人', value: 'personal' },\n        { label: '工作', value: 'work' },\n        { label: '私密', value: 'private' },\n      ]\"\n      @blur=\"emit('blur')\"\n      @change=\"onChange\"\n    />\n    <Input\n      placeholder=\"请输入11位手机号码\"\n      class=\"flex-1\"\n      allow-clear\n      :class=\"{ 'valid-success': modelValue[1]?.match(/^1[3-9]\\d{9}$/) }\"\n      v-model:value=\"modelValue[1]\"\n      :maxlength=\"11\"\n      type=\"tel\"\n      @blur=\"emit('blur')\"\n      @change=\"onChange\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/form/query.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Page } from '@vben/common-ui';\n\nimport { Card, message } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst [QueryForm] = useVbenForm({\n  // 默认展开\n  collapsed: false,\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  // 水平布局，label和input在同一行\n  layout: 'horizontal',\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入用户名',\n      },\n      // 字段名\n      fieldName: 'username',\n      // 界面显示的label\n      label: '字符串',\n    },\n    {\n      component: 'InputPassword',\n      componentProps: {\n        placeholder: '请输入密码',\n      },\n      fieldName: 'password',\n      label: '密码',\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'number',\n      label: '数字(带后缀)',\n      suffix: () => '¥',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      fieldName: 'options',\n      label: '下拉选',\n    },\n    {\n      component: 'DatePicker',\n      fieldName: 'datePicker',\n      label: '日期选择框',\n    },\n  ],\n  // 是否可展开\n  showCollapseButton: true,\n  submitButtonOptions: {\n    content: '查询',\n  },\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n});\n\nconst [InlineForm] = useVbenForm({\n  layout: 'inline',\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入用户名',\n      },\n      // 字段名\n      fieldName: 'username',\n      // 界面显示的label\n      label: '字符串',\n    },\n    {\n      component: 'InputPassword',\n      componentProps: {\n        placeholder: '请输入密码',\n      },\n      fieldName: 'password',\n      label: '密码',\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'number',\n      label: '数字(带后缀)',\n      suffix: () => '¥',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      fieldName: 'options',\n      label: '下拉选',\n    },\n  ],\n});\n\nconst [QueryForm1] = useVbenForm({\n  // 默认展开\n  collapsed: true,\n  collapsedRows: 2,\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  // 水平布局，label和input在同一行\n  layout: 'horizontal',\n  schema: (() => {\n    const schema = [];\n    for (let index = 0; index < 14; index++) {\n      schema.push({\n        // 组件需要在 #/adapter.ts内注册，并加上类型\n        component: 'Input',\n        // 字段名\n        fieldName: `field${index}`,\n        // 界面显示的label\n        label: `字段${index}`,\n      });\n    }\n    return schema;\n  })(),\n  // 是否可展开\n  showCollapseButton: true,\n  submitButtonOptions: {\n    content: '查询',\n  },\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n});\n\nconst [QueryForm2] = useVbenForm({\n  // 操作按钮组 newLine: 在新行显示。rowEnd: 在行内显示，靠右对齐（默认）。inline: 使用grid默认样式\n  actionLayout: 'newLine',\n  actionPosition: 'left', // 操作按钮组在左侧显示\n  // 默认折叠\n  collapsed: true,\n  collapsedRows: 3,\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  // 水平布局，label和input在同一行\n  layout: 'vertical',\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入用户名',\n      },\n      // 字段名\n      fieldName: 'username',\n      // 界面显示的label\n      label: '字符串',\n    },\n    {\n      component: 'InputPassword',\n      componentProps: {\n        placeholder: '请输入密码',\n      },\n      fieldName: 'password',\n      label: '密码',\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'number',\n      label: '数字(带后缀)',\n      suffix: () => '¥',\n    },\n    {\n      component: 'DatePicker',\n      fieldName: 'datePicker',\n      label: '日期选择框',\n    },\n  ],\n  // 是否可展开\n  showCollapseButton: true,\n  submitButtonOptions: {\n    content: '查询',\n  },\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n</script>\n\n<template>\n  <Page\n    description=\"查询表单，常用语和表格组合使用，可进行收缩展开。\"\n    title=\"表单组件\"\n  >\n    <Card class=\"mb-5\" title=\"查询表单，默认展开\">\n      <QueryForm />\n    </Card>\n\n    <Card class=\"mb-5\" title=\"查询表单，单行表单\">\n      <InlineForm />\n    </Card>\n\n    <Card class=\"mb-5\" title=\"查询表单，默认展开，垂直布局\">\n      <QueryForm2 />\n    </Card>\n\n    <Card title=\"查询表单，默认折叠，折叠时保留2行\">\n      <QueryForm1 />\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/form/rules.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Page } from '@vben/common-ui';\n\nimport { Button, Card, message } from 'ant-design-vue';\n\nimport { useVbenForm, z } from '#/adapter/form';\n\nconst [Form, formApi] = useVbenForm({\n  // 所有表单项共用，可单独在表单内覆盖\n  commonConfig: {\n    // 所有表单项\n    componentProps: {\n      class: 'w-full',\n    },\n  },\n  // 提交函数\n  handleSubmit: onSubmit,\n  // 垂直布局，label和input在不同行，值为vertical\n  // 水平布局，label和input在同一行\n  layout: 'horizontal',\n  schema: [\n    {\n      // 组件需要在 #/adapter.ts内注册，并加上类型\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入',\n      },\n      // 字段名\n      fieldName: 'field1',\n      // 界面显示的label\n      label: '字段1',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      defaultValue: '默认值',\n      fieldName: 'field2',\n      label: '默认值(必填)',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field3',\n      label: '默认值(非必填)',\n      rules: z.string().default('默认值').optional(),\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field31',\n      label: '自定义信息',\n      rules: z.string().min(1, { message: '最少输入1个字符' }),\n    },\n    {\n      component: 'Input',\n      // 对应组件的参数\n      componentProps: {\n        placeholder: '请输入',\n      },\n      // 字段名\n      fieldName: 'field4',\n      // 界面显示的label\n      label: '邮箱',\n      rules: z.string().email('请输入正确的邮箱'),\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'number',\n      label: '数字',\n      rules: 'required',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        filterOption: true,\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n        showSearch: true,\n      },\n      defaultValue: undefined,\n      fieldName: 'options',\n      label: '下拉选',\n      rules: 'selectRequired',\n    },\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n      },\n      fieldName: 'radioGroup',\n      label: '单选组',\n      rules: 'selectRequired',\n    },\n    {\n      component: 'CheckboxGroup',\n      componentProps: {\n        name: 'cname',\n        options: [\n          {\n            label: '选项1',\n            value: '1',\n          },\n          {\n            label: '选项2',\n            value: '2',\n          },\n        ],\n      },\n      fieldName: 'checkboxGroup',\n      label: '多选组',\n      rules: 'selectRequired',\n    },\n    {\n      component: 'Checkbox',\n      fieldName: 'checkbox',\n      label: '',\n      renderComponentContent: () => {\n        return {\n          default: () => ['我已阅读并同意'],\n        };\n      },\n      rules: z.boolean().refine((value) => value, {\n        message: '请勾选',\n      }),\n    },\n    {\n      component: 'DatePicker',\n      defaultValue: undefined,\n      fieldName: 'datePicker',\n      label: '日期选择框',\n      rules: 'selectRequired',\n    },\n    {\n      component: 'RangePicker',\n      defaultValue: undefined,\n      fieldName: 'rangePicker',\n      label: '区间选择框',\n      rules: 'selectRequired',\n    },\n    {\n      component: 'InputPassword',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'password',\n      label: '密码',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'input-blur',\n      formFieldProps: {\n        validateOnChange: false,\n        validateOnModelUpdate: false,\n      },\n      help: 'blur时才会触发校验',\n      label: 'blur触发',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'input-async',\n      label: '异步校验',\n      rules: z\n        .string()\n        .min(3, '用户名至少需要3个字符')\n        .refine(\n          async (username) => {\n            // 假设这是一个异步函数，模拟检查用户名是否已存在\n            const checkUsernameExists = async (\n              username: string,\n            ): Promise<boolean> => {\n              await new Promise((resolve) => setTimeout(resolve, 1000));\n              return username === 'existingUser';\n            };\n            const exists = await checkUsernameExists(username);\n            return !exists;\n          },\n          {\n            message: '用户名已存在',\n          },\n        ),\n    },\n  ],\n  // 大屏一行显示3个，中屏一行显示2个，小屏一行显示1个\n  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.success({\n    content: `form values: ${JSON.stringify(values)}`,\n  });\n}\n</script>\n\n<template>\n  <Page description=\"表单校验示例\" title=\"表单组件\">\n    <Card title=\"基础组件校验示例\">\n      <template #extra>\n        <Button @click=\"() => formApi.validate()\">校验表单</Button>\n        <Button class=\"mx-2\" @click=\"() => formApi.resetValidate()\">\n          清空校验信息\n        </Button>\n      </template>\n      <Form />\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/form/scroll-to-error-test.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Button, Card, Switch } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\ndefineOptions({\n  name: 'ScrollToErrorTest',\n});\n\nconst scrollEnabled = ref(true);\n\nconst [Form, formApi] = useVbenForm({\n  scrollToFirstError: scrollEnabled.value,\n  schema: [\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入用户名',\n      },\n      fieldName: 'username',\n      label: '用户名',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入邮箱',\n      },\n      fieldName: 'email',\n      label: '邮箱',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入手机号',\n      },\n      fieldName: 'phone',\n      label: '手机号',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入地址',\n      },\n      fieldName: 'address',\n      label: '地址',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入备注',\n      },\n      fieldName: 'remark',\n      label: '备注',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入公司名称',\n      },\n      fieldName: 'company',\n      label: '公司名称',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入职位',\n      },\n      fieldName: 'position',\n      label: '职位',\n      rules: 'required',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        options: [\n          { label: '男', value: 'male' },\n          { label: '女', value: 'female' },\n        ],\n        placeholder: '请选择性别',\n      },\n      fieldName: 'gender',\n      label: '性别',\n      rules: 'selectRequired',\n    },\n  ],\n  showDefaultActions: false,\n});\n\n// 测试 validateAndSubmitForm（验证并提交）\nasync function testValidateAndSubmit() {\n  await formApi.validateAndSubmitForm();\n}\n\n// 测试 validate（手动验证整个表单）\nasync function testValidate() {\n  await formApi.validate();\n}\n\n// 测试 validateField（验证单个字段）\nasync function testValidateField() {\n  await formApi.validateField('username');\n}\n\n// 切换滚动功能\nfunction toggleScrollToError() {\n  formApi.setState({ scrollToFirstError: scrollEnabled.value });\n}\n\n// 填充部分数据测试\nasync function fillPartialData() {\n  await formApi.resetForm();\n  await formApi.setFieldValue('username', '测试用户');\n  await formApi.setFieldValue('email', 'test@example.com');\n}\n</script>\n\n<template>\n  <Page\n    description=\"测试表单验证失败时自动滚动到错误字段的功能\"\n    title=\"滚动到错误字段测试\"\n  >\n    <Card title=\"功能测试\">\n      <template #extra>\n        <div class=\"flex items-center gap-2\">\n          <Switch v-model=\"scrollEnabled\" @change=\"toggleScrollToError\" />\n          <span>启用滚动到错误字段</span>\n        </div>\n      </template>\n\n      <div class=\"space-y-4\">\n        <div class=\"rounded-sm bg-blue-50 p-4\">\n          <h3 class=\"mb-2 font-medium\">测试说明：</h3>\n          <ul class=\"list-inside list-disc space-y-1 text-sm\">\n            <li>所有验证方法在验证失败时都会自动滚动到第一个错误字段</li>\n            <li>可以通过右上角的开关控制是否启用自动滚动功能</li>\n          </ul>\n        </div>\n\n        <div class=\"rounded-sm border p-4\">\n          <h4 class=\"mb-3 font-medium\">验证方法测试：</h4>\n          <div class=\"flex flex-wrap gap-2\">\n            <Button type=\"primary\" @click=\"testValidateAndSubmit\">\n              测试 validateAndSubmitForm()\n            </Button>\n            <Button @click=\"testValidate\"> 测试 validate() </Button>\n            <Button @click=\"testValidateField\"> 测试 validateField() </Button>\n          </div>\n          <div class=\"mt-2 text-xs text-gray-500\">\n            <p>• validateAndSubmitForm(): 验证表单并提交</p>\n            <p>• validate(): 手动验证整个表单</p>\n            <p>• validateField(): 验证单个字段（这里测试用户名字段）</p>\n          </div>\n        </div>\n\n        <div class=\"rounded-sm border p-4\">\n          <h4 class=\"mb-3 font-medium\">数据填充测试：</h4>\n          <div class=\"flex flex-wrap gap-2\">\n            <Button @click=\"fillPartialData\"> 填充部分数据 </Button>\n            <Button @click=\"() => formApi.resetForm()\"> 清空表单 </Button>\n          </div>\n          <div class=\"mt-2 text-xs text-gray-500\">\n            <p>• 填充部分数据后验证，会滚动到第一个错误字段</p>\n          </div>\n        </div>\n\n        <Form />\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/json-viewer/data.ts",
    "content": "export const json1 = {\n  additionalInfo: {\n    author: 'Your Name',\n    debug: true,\n    version: '1.3.10',\n    versionCode: 132,\n  },\n  additionalNotes: 'This JSON is used for demonstration purposes',\n  tools: [\n    {\n      description: 'Description of Tool 1',\n      name: 'Tool 1',\n    },\n    {\n      description: 'Description of Tool 2',\n      name: 'Tool 2',\n    },\n    {\n      description: 'Description of Tool 3',\n      name: 'Tool 3',\n    },\n    {\n      description: 'Description of Tool 4',\n      name: 'Tool 4',\n    },\n  ],\n};\n\nexport const json2 = JSON.parse(`\n  {\n\t\"id\": \"chatgpt-123\",\n\t\"object\": \"chat.completion\",\n\t\"created\": 1677652288,\n\t\"model\": \"gpt-3.5-turbo-0613\",\n\t\"system_fingerprint\": \"fp_44709d6fcb\",\n\t\"choices\": [{\n\t\t\"index\": 0,\n\t\t\"message\": {\n\t\t\t\"role\": \"assistant\",\n\t\t\t\"content\": \"Hello there, how may I assist you today?\"\n\t\t},\n\t\t\"finish_reason\": \"stop\"\n\t}],\n\t\"usage\": {\n\t\t\"prompt_tokens\": 9,\n\t\t\"completion_tokens\": 12,\n\t\t\"total_tokens\": 21,\n    \"debug_mode\": true\n\t},\n  \"debug\": {\n    \"startAt\": \"2021-08-01T00:00:00Z\",\n    \"logs\": [\n      {\n        \"timestamp\": \"2021-08-01T00:00:00Z\",\n        \"message\": \"This is a debug message\",\n        \"extra\":[ \"extra1\", \"extra2\" ]\n      },\n      {\n        \"timestamp\": \"2021-08-01T00:00:01Z\",\n        \"message\": \"This is another debug message\",\n        \"extra\":[ \"extra3\", \"extra4\" ]\n      }\n    ]\n  }\n}\n  `);\n"
  },
  {
    "path": "playground/src/views/examples/json-viewer/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { JsonViewerAction, JsonViewerValue } from '@vben/common-ui';\n\nimport { JsonViewer, Page } from '@vben/common-ui';\n\nimport { Card, message } from 'ant-design-vue';\n\nimport { json1, json2 } from './data';\n\nfunction handleKeyClick(key: string) {\n  message.info(`点击了Key ${key}`);\n}\n\nfunction handleValueClick(value: JsonViewerValue) {\n  message.info(`点击了Value ${JSON.stringify(value)}`);\n}\n\nfunction handleCopied(_event: JsonViewerAction) {\n  message.success('已复制JSON');\n}\n</script>\n<template>\n  <Page\n    title=\"Json Viewer\"\n    description=\"一个渲染 JSON 结构数据的组件，支持复制、展开等，简单易用\"\n  >\n    <Card title=\"默认配置\">\n      <JsonViewer :value=\"json1\" />\n    </Card>\n    <Card title=\"可复制、默认展开3层、显示边框、事件处理\" class=\"mt-4\">\n      <JsonViewer\n        :value=\"json2\"\n        :expand-depth=\"3\"\n        copyable\n        :sort=\"false\"\n        @key-click=\"handleKeyClick\"\n        @value-click=\"handleValueClick\"\n        @copied=\"handleCopied\"\n        boxed\n      />\n    </Card>\n    <Card title=\"预览模式\" class=\"mt-4\">\n      <JsonViewer\n        :value=\"json2\"\n        copyable\n        preview-mode\n        :show-array-index=\"false\"\n      />\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/layout/col-page.vue",
    "content": "<script lang=\"ts\" setup>\nimport { reactive, ref } from 'vue';\n\nimport { ColPage } from '@vben/common-ui';\nimport { IconifyIcon } from '@vben/icons';\n\nimport {\n  Alert,\n  Button,\n  Card,\n  Checkbox,\n  Slider,\n  Tag,\n  Tooltip,\n} from 'ant-design-vue';\n\nconst props = reactive({\n  leftCollapsedWidth: 5,\n  leftCollapsible: true,\n  leftMaxWidth: 50,\n  leftMinWidth: 20,\n  leftWidth: 30,\n  resizable: true,\n  rightWidth: 70,\n  splitHandle: true,\n  splitLine: true,\n});\nconst leftMinWidth = ref(props.leftMinWidth || 1);\nconst leftMaxWidth = ref(props.leftMaxWidth || 100);\n</script>\n<template>\n  <ColPage\n    auto-content-height\n    description=\"ColPage 是一个双列布局组件，支持左侧折叠、拖拽调整宽度等功能。\"\n    v-bind=\"props\"\n    title=\"ColPage 双列布局组件\"\n  >\n    <template #title>\n      <span class=\"mr-2 text-2xl font-bold\">ColPage 双列布局组件</span>\n      <Tag color=\"hsl(var(--destructive))\">Alpha</Tag>\n    </template>\n    <template #left=\"{ isCollapsed, expand }\">\n      <div v-if=\"isCollapsed\" @click=\"expand\">\n        <Tooltip title=\"点击展开左侧\">\n          <Button shape=\"circle\" type=\"primary\" class=\"flex-center\">\n            <template #icon>\n              <IconifyIcon class=\"text-2xl\" icon=\"bi:arrow-right\" />\n            </template>\n          </Button>\n        </Tooltip>\n      </div>\n      <div\n        v-else\n        :style=\"{ minWidth: '200px' }\"\n        class=\"mr-2 rounded-(--radius) border border-border bg-card p-2\"\n      >\n        <p>这里是左侧内容</p>\n        <p>这里是左侧内容</p>\n        <p>这里是左侧内容</p>\n        <p>这里是左侧内容</p>\n        <p>这里是左侧内容</p>\n      </div>\n    </template>\n    <Card class=\"ml-2\" title=\"基本使用\">\n      <div class=\"flex flex-col gap-2\">\n        <div class=\"flex gap-2\">\n          <Checkbox v-model=\"props.resizable\">可拖动调整宽度</Checkbox>\n          <Checkbox v-model=\"props.splitLine\">显示拖动分隔线</Checkbox>\n          <Checkbox v-model=\"props.splitHandle\">显示拖动手柄</Checkbox>\n          <Checkbox v-model=\"props.leftCollapsible\"> 左侧可折叠 </Checkbox>\n        </div>\n        <div class=\"flex items-center gap-2\">\n          <span>左侧最小宽度百分比：</span>\n          <Slider\n            v-model:value=\"leftMinWidth\"\n            :max=\"props.leftMaxWidth - 1\"\n            :min=\"1\"\n            class=\"w-25\"\n            @after-change=\"(value) => (props.leftMinWidth = value as number)\"\n          />\n          <span>左侧最大宽度百分比：</span>\n          <Slider\n            v-model:value=\"props.leftMaxWidth\"\n            :max=\"100\"\n            :min=\"leftMaxWidth + 1\"\n            class=\"w-25\"\n            @after-change=\"(value) => (props.leftMaxWidth = value as number)\"\n          />\n        </div>\n        <Alert message=\"实验性的组件\" show-icon type=\"warning\">\n          <template #description>\n            <p>\n              双列布局组件是一个在Page组件上扩展的相对基础的布局组件，支持左侧折叠（当拖拽导致左侧宽度比最小宽度还要小时，还可以进入折叠状态）、拖拽调整宽度等功能。\n            </p>\n            <p>以上宽度设置的数值是百分比，最小值为1，最大值为100。</p>\n            <p class=\"font-bold text-red-600\">\n              这是一个实验性的组件，用法可能会发生变动，也可能最终不会被采用。在其用法正式出现在文档中之前，不建议在生产环境中使用。\n            </p>\n          </template>\n        </Alert>\n      </div>\n    </Card>\n  </ColPage>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/loading/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Loading, Page, Spinner } from '@vben/common-ui';\nimport { IconifyIcon } from '@vben/icons';\n\nimport { refAutoReset } from '@vueuse/core';\nimport { Button, Card, Spin } from 'ant-design-vue';\n\nconst spinning = refAutoReset(false, 3000);\nconst loading = refAutoReset(false, 3000);\n\nconst spinningV = refAutoReset(false, 3000);\nconst loadingV = refAutoReset(false, 3000);\n</script>\n<template>\n  <Page\n    title=\"Vben Loading\"\n    description=\"加载中状态组件。这个组件可以为其它作为容器的组件添加一个加载中的遮罩层。使用它们时，容器需要relative定位。\"\n  >\n    <Card title=\"Antd Spin\">\n      <template #actions>这是Antd 组件库自带的Spin组件演示</template>\n      <Spin :spinning=\"spinning\" tip=\"加载中...\">\n        <Button type=\"primary\" @click=\"spinning = true\">显示Spin</Button>\n      </Spin>\n    </Card>\n\n    <Card title=\"Vben Loading\" v-loading=\"loadingV\" class=\"mt-4\">\n      <template #extra>\n        <Button type=\"primary\" @click=\"loadingV = true\">\n          v-loading 指令\n        </Button>\n      </template>\n      <template #actions>\n        Loading组件可以设置文字，并且也提供了icon插槽用于替换加载图标。\n      </template>\n      <div class=\"flex gap-4\">\n        <div class=\"size-40\">\n          <Loading\n            :spinning=\"loading\"\n            text=\"正在加载...\"\n            class=\"flex-center size-full\"\n          >\n            <Button type=\"primary\" @click=\"loading = true\">默认动画</Button>\n          </Loading>\n        </div>\n        <div class=\"size-40\">\n          <Loading :spinning=\"loading\" class=\"flex-center size-full\">\n            <Button type=\"primary\" @click=\"loading = true\">自定义动画1</Button>\n            <template #icon>\n              <IconifyIcon\n                icon=\"svg-spinners:ring-resize\"\n                class=\"size-10 text-primary\"\n              />\n            </template>\n          </Loading>\n        </div>\n        <div class=\"size-40\">\n          <Loading :spinning=\"loading\" class=\"flex-center size-full\">\n            <Button type=\"primary\" @click=\"loading = true\">自定义动画2</Button>\n            <template #icon>\n              <IconifyIcon\n                icon=\"svg-spinners:bars-scale\"\n                class=\"size-10 text-primary\"\n              />\n            </template>\n          </Loading>\n        </div>\n      </div>\n    </Card>\n\n    <Card\n      title=\"Vben Spinner\"\n      v-spinning=\"spinningV\"\n      class=\"mt-4 overflow-hidden\"\n      :body-style=\"{\n        position: 'relative',\n        overflow: 'hidden',\n      }\"\n    >\n      <template #extra>\n        <Button type=\"primary\" @click=\"spinningV = true\">\n          v-spinning 指令\n        </Button>\n      </template>\n      <template #actions>\n        Spinner组件是Loading组件的一个特例，只有一个固定的统一样式。\n      </template>\n      <Spinner :spinning=\"spinning\" class=\"flex-center size-40\">\n        <Button type=\"primary\" @click=\"spinning = true\">显示Spinner</Button>\n      </Spinner>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/modal/auto-height-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { Button, message } from 'ant-design-vue';\n\nconst list = ref<number[]>([]);\n\nconst [Modal, modalApi] = useVbenModal({\n  onCancel() {\n    modalApi.close();\n  },\n  onConfirm() {\n    message.info('onConfirm');\n  },\n  onOpenChange(isOpen) {\n    if (isOpen) {\n      handleUpdate();\n    }\n  },\n});\n\nfunction handleUpdate(len?: number) {\n  modalApi.setState({ confirmDisabled: true, loading: true });\n  setTimeout(() => {\n    list.value = Array.from(\n      { length: len ?? Math.floor(Math.random() * 10) + 1 },\n      (_v, k) => k + 1,\n    );\n    modalApi.setState({ confirmDisabled: false, loading: false });\n  }, 2000);\n}\n</script>\n\n<template>\n  <Modal title=\"自动计算高度\">\n    <div\n      v-for=\"item in list\"\n      :key=\"item\"\n      class=\"flex-center h-55 w-full bg-muted even:bg-heavy\"\n    >\n      {{ item }}\n    </div>\n    <template #prepend-footer>\n      <Button type=\"link\" @click=\"handleUpdate()\">点击更新数据</Button>\n    </template>\n  </Modal>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/modal/base-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { Button, message } from 'ant-design-vue';\n\nconst [Modal, modalApi] = useVbenModal({\n  onCancel() {\n    modalApi.close();\n  },\n  onClosed() {\n    message.info('onClosed：关闭动画结束');\n  },\n  onConfirm() {\n    message.info('onConfirm');\n    // modalApi.close();\n  },\n  onOpened() {\n    message.info('onOpened：打开动画结束');\n  },\n});\n\nfunction lockModal() {\n  modalApi.lock();\n  setTimeout(() => {\n    modalApi.unlock();\n  }, 3000);\n}\n</script>\n<template>\n  <Modal class=\"w-150\" title=\"基础弹窗示例\" title-tooltip=\"标题提示内容\">\n    base demo\n    <Button type=\"primary\" @click=\"lockModal\">锁定弹窗</Button>\n  </Modal>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/modal/blur-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref, watch } from 'vue';\n\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { Slider } from 'ant-design-vue';\n\nconst blur = ref(5);\nconst [Modal, modalApi] = useVbenModal({\n  overlayBlur: blur.value,\n});\nwatch(blur, (val) => {\n  modalApi.setState({\n    overlayBlur: val,\n  });\n});\n</script>\n<template>\n  <Modal title=\"遮罩层模糊\">\n    <p>调整滑块来改变遮罩层模糊程度：{{ blur }}</p>\n    <Slider v-model:value=\"blur\" :max=\"30\" :min=\"0\" />\n  </Modal>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/modal/drag-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { message } from 'ant-design-vue';\n\nconst [Modal, modalApi] = useVbenModal({\n  draggable: true,\n  onCancel() {\n    modalApi.close();\n  },\n  onConfirm() {\n    message.info('onConfirm');\n    // modalApi.close();\n  },\n});\n</script>\n<template>\n  <Modal title=\"可拖拽示例\"> 鼠标移动到 header 上，可拖拽弹窗 </Modal>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/modal/dynamic-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { Button, message } from 'ant-design-vue';\n\nconst [Modal, modalApi] = useVbenModal({\n  draggable: true,\n  onCancel() {\n    modalApi.close();\n  },\n  onConfirm() {\n    message.info('onConfirm');\n    // modalApi.close();\n  },\n  title: '动态修改配置示例',\n});\n\nconst state = modalApi.useStore();\n\nfunction handleUpdateTitle() {\n  modalApi.setState({ title: '内部动态标题' });\n}\n\nfunction handleToggleFullscreen() {\n  modalApi.setState((prev) => {\n    return { ...prev, fullscreen: !prev.fullscreen };\n  });\n}\n</script>\n<template>\n  <Modal>\n    <div class=\"flex-col-center\">\n      <Button class=\"mb-3\" type=\"primary\" @click=\"handleUpdateTitle()\">\n        内部动态修改标题\n      </Button>\n      <Button class=\"mb-3\" type=\"primary\" @click=\"handleToggleFullscreen()\">\n        {{ state.fullscreen ? '退出全屏' : '打开全屏' }}\n      </Button>\n    </div>\n  </Modal>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/modal/form-modal-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { message } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\ndefineOptions({\n  name: 'FormModelDemo',\n});\n\nconst [Form, formApi] = useVbenForm({\n  handleSubmit: onSubmit,\n  schema: [\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field1',\n      label: '字段1',\n      rules: 'required',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        placeholder: '请输入',\n      },\n      fieldName: 'field2',\n      label: '字段2',\n      rules: 'required',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        options: [\n          { label: '选项1', value: '1' },\n          { label: '选项2', value: '2' },\n        ],\n        placeholder: '请输入',\n      },\n      fieldName: 'field3',\n      label: '字段3',\n      rules: 'required',\n    },\n  ],\n  showDefaultActions: false,\n});\n\nconst [Modal, modalApi] = useVbenModal({\n  fullscreenButton: false,\n  onCancel() {\n    modalApi.close();\n  },\n  onConfirm: async () => {\n    await formApi.validateAndSubmitForm();\n    // modalApi.close();\n  },\n  onOpenChange(isOpen: boolean) {\n    if (isOpen) {\n      const { values } = modalApi.getData<Record<string, any>>();\n      if (values) {\n        formApi.setValues(values);\n      }\n    }\n  },\n  title: '内嵌表单示例',\n});\n\nfunction onSubmit(values: Record<string, any>) {\n  message.loading({\n    content: '正在提交中...',\n    duration: 0,\n    key: 'is-form-submitting',\n  });\n  modalApi.lock();\n  setTimeout(() => {\n    modalApi.close();\n    message.success({\n      content: `提交成功：${JSON.stringify(values)}`,\n      duration: 2,\n      key: 'is-form-submitting',\n    });\n  }, 3000);\n}\n</script>\n<template>\n  <Modal>\n    <Form />\n  </Modal>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/modal/in-content-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { Input, message } from 'ant-design-vue';\n\nconst [Modal, modalApi] = useVbenModal({\n  destroyOnClose: false,\n  onCancel() {\n    modalApi.close();\n  },\n  onConfirm() {\n    message.info('onConfirm');\n    // modalApi.close();\n  },\n});\nconst value = ref();\n</script>\n<template>\n  <Modal\n    append-to-main\n    class=\"w-150\"\n    title=\"基础弹窗示例\"\n    title-tooltip=\"标题提示内容\"\n  >\n    此弹窗指定在内容区域打开，并且在关闭之后弹窗内容不会被销毁\n    <Input v-model:value=\"value\" placeholder=\"KeepAlive测试\" />\n  </Modal>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/modal/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { onBeforeUnmount } from 'vue';\n\nimport {\n  alert,\n  clearAllAlerts,\n  confirm,\n  Page,\n  prompt,\n  useVbenModal,\n} from '@vben/common-ui';\n\nimport { Button, Card, Flex, message } from 'ant-design-vue';\n\nimport DocButton from '../doc-button.vue';\nimport AutoHeightDemo from './auto-height-demo.vue';\nimport BaseDemo from './base-demo.vue';\nimport BlurDemo from './blur-demo.vue';\nimport DragDemo from './drag-demo.vue';\nimport DynamicDemo from './dynamic-demo.vue';\nimport FormModalDemo from './form-modal-demo.vue';\nimport InContentModalDemo from './in-content-demo.vue';\nimport NestedDemo from './nested-demo.vue';\nimport SharedDataDemo from './shared-data-demo.vue';\n\ndefineOptions({ name: 'ModalExample' });\n\nconst [BaseModal, baseModalApi] = useVbenModal({\n  // 连接抽离的组件\n  connectedComponent: BaseDemo,\n});\n\nconst [InContentModal, inContentModalApi] = useVbenModal({\n  // 连接抽离的组件\n  connectedComponent: InContentModalDemo,\n});\n\nconst [AutoHeightModal, autoHeightModalApi] = useVbenModal({\n  connectedComponent: AutoHeightDemo,\n});\n\nconst [DragModal, dragModalApi] = useVbenModal({\n  connectedComponent: DragDemo,\n});\n\nconst [DynamicModal, dynamicModalApi] = useVbenModal({\n  connectedComponent: DynamicDemo,\n});\n\nconst [SharedDataModal, sharedModalApi] = useVbenModal({\n  connectedComponent: SharedDataDemo,\n});\n\nconst [FormModal, formModalApi] = useVbenModal({\n  connectedComponent: FormModalDemo,\n});\n\nconst [NestedModal, nestedModalApi] = useVbenModal({\n  connectedComponent: NestedDemo,\n});\n\nconst [BlurModal, blurModalApi] = useVbenModal({\n  connectedComponent: BlurDemo,\n});\n\nfunction openBaseModal() {\n  baseModalApi.open();\n}\n\nfunction openInContentModal() {\n  inContentModalApi.open();\n}\n\nfunction openAutoHeightModal() {\n  autoHeightModalApi.open();\n}\n\nfunction openDragModal() {\n  dragModalApi.open();\n}\n\nfunction openDynamicModal() {\n  dynamicModalApi.open();\n}\n\nfunction openSharedModal() {\n  sharedModalApi\n    .setData({\n      content: '外部传递的数据 content',\n      payload: '外部传递的数据 payload',\n    })\n    .open();\n}\n\nfunction openNestedModal() {\n  nestedModalApi.open();\n}\n\nfunction openBlurModal() {\n  blurModalApi.open();\n}\n\nfunction handleUpdateTitle() {\n  dynamicModalApi.setState({ title: '外部动态标题' }).open();\n}\n\nfunction openFormModal() {\n  formModalApi\n    .setData({\n      // 表单值\n      values: { field1: 'abc', field2: '123' },\n    })\n    .open();\n}\n\nfunction openAlert() {\n  alert({\n    content: '这是一个弹窗',\n    icon: 'success',\n  }).then(() => {\n    message.info('用户关闭了弹窗');\n  });\n}\n\nonBeforeUnmount(() => {\n  // 清除所有弹窗\n  clearAllAlerts();\n});\n\nfunction openConfirm() {\n  confirm({\n    beforeClose({ isConfirm }) {\n      if (!isConfirm) return;\n      // 这里可以做一些异步操作\n      return new Promise((resolve) => {\n        setTimeout(() => {\n          resolve(true);\n        }, 1000);\n      });\n    },\n    centered: false,\n    content: '这是一个确认弹窗',\n    icon: 'question',\n  })\n    .then(() => {\n      message.success('用户确认了操作');\n    })\n    .catch(() => {\n      message.error('用户取消了操作');\n    });\n}\n\nasync function openPrompt() {\n  prompt<string>({\n    async beforeClose({ isConfirm, value }) {\n      if (isConfirm && value === '芝士') {\n        message.error('不能吃芝士');\n        return false;\n      }\n    },\n    componentProps: { placeholder: '不能吃芝士...' },\n    content: '中午吃了什么？',\n    icon: 'question',\n    overlayBlur: 3,\n  })\n    .then((res) => {\n      message.success(`用户输入了：${res}`);\n    })\n    .catch(() => {\n      message.error('用户取消了输入');\n    });\n}\n</script>\n\n<template>\n  <Page\n    auto-content-height\n    description=\"弹窗组件常用于在不离开当前页面的情况下，显示额外的信息、表单或操作提示，更多api请查看组件文档。\"\n    title=\"弹窗组件示例\"\n  >\n    <template #extra>\n      <DocButton path=\"/components/common-ui/vben-modal\" />\n    </template>\n    <BaseModal />\n    <InContentModal />\n    <AutoHeightModal />\n    <DragModal />\n    <DynamicModal />\n    <SharedDataModal />\n    <FormModal />\n    <NestedModal />\n    <BlurModal />\n    <Flex wrap=\"wrap\" class=\"w-full\" gap=\"10\">\n      <Card class=\"w-75\" title=\"基本使用\">\n        <p>一个基础的弹窗示例</p>\n        <template #actions>\n          <Button type=\"primary\" @click=\"openBaseModal\">打开弹窗</Button>\n        </template>\n      </Card>\n\n      <Card class=\"w-75\" title=\"指定容器+关闭后不销毁\">\n        <p>在内容区域打开弹窗的示例</p>\n        <template #actions>\n          <Button type=\"primary\" @click=\"openInContentModal\">打开弹窗</Button>\n        </template>\n      </Card>\n\n      <Card class=\"w-75\" title=\"内容高度自适应\">\n        <p>可根据内容并自动调整高度</p>\n        <template #actions>\n          <Button type=\"primary\" @click=\"openAutoHeightModal\">\n            打开弹窗\n          </Button>\n        </template>\n      </Card>\n\n      <Card class=\"w-75\" title=\"可拖拽示例\">\n        <p>配置 draggable 可开启拖拽功能</p>\n        <template #actions>\n          <Button type=\"primary\" @click=\"openDragModal\"> 打开弹窗 </Button>\n        </template>\n      </Card>\n\n      <Card class=\"w-75\" title=\"动态配置示例\">\n        <p>通过 setState 动态调整弹窗数据</p>\n        <template #extra>\n          <Button type=\"link\" @click=\"openDynamicModal\">打开弹窗</Button>\n        </template>\n        <template #actions>\n          <Button type=\"primary\" @click=\"handleUpdateTitle\">\n            外部修改标题并打开\n          </Button>\n        </template>\n      </Card>\n\n      <Card class=\"w-75\" title=\"内外数据共享示例\">\n        <p>通过共享 sharedData 来进行数据交互</p>\n        <template #actions>\n          <Button type=\"primary\" @click=\"openSharedModal\">\n            打开弹窗并传递数据\n          </Button>\n        </template>\n      </Card>\n\n      <Card class=\"w-75\" title=\"表单弹窗示例\">\n        <p>弹窗与表单结合</p>\n        <template #actions>\n          <Button type=\"primary\" @click=\"openFormModal\"> 打开表单弹窗 </Button>\n        </template>\n      </Card>\n\n      <Card class=\"w-75\" title=\"嵌套弹窗示例\">\n        <p>在已经打开的弹窗中再次打开弹窗</p>\n        <template #actions>\n          <Button type=\"primary\" @click=\"openNestedModal\">打开嵌套弹窗</Button>\n        </template>\n      </Card>\n\n      <Card class=\"w-75\" title=\"遮罩模糊示例\">\n        <p>遮罩层应用类似毛玻璃的模糊效果</p>\n        <template #actions>\n          <Button type=\"primary\" @click=\"openBlurModal\">打开弹窗</Button>\n        </template>\n      </Card>\n      <Card class=\"w-75\" title=\"轻量提示弹窗\">\n        <template #extra>\n          <DocButton path=\"/components/common-ui/vben-alert\" />\n        </template>\n        <p>通过快捷方法创建动态提示弹窗，适合一些轻量的提示和确认、输入等</p>\n        <template #actions>\n          <Button type=\"primary\" @click=\"openAlert\">Alert</Button>\n          <Button type=\"primary\" @click=\"openConfirm\">Confirm</Button>\n          <Button type=\"primary\" @click=\"openPrompt\">Prompt</Button>\n        </template>\n      </Card>\n    </Flex>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/modal/nested-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { Button } from 'ant-design-vue';\n\nimport DragDemo from './drag-demo.vue';\n\nconst [Modal] = useVbenModal({\n  destroyOnClose: true,\n});\nconst [BaseModal, baseModalApi] = useVbenModal({\n  connectedComponent: DragDemo,\n});\n\nfunction openNestedModal() {\n  baseModalApi.open();\n}\n</script>\n<template>\n  <Modal title=\"嵌套弹窗示例\">\n    <Button @click=\"openNestedModal\" type=\"primary\">打开子弹窗</Button>\n    <BaseModal />\n  </Modal>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/modal/shared-data-demo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { message } from 'ant-design-vue';\n\nconst data = ref();\n\nconst [Modal, modalApi] = useVbenModal({\n  onCancel() {\n    modalApi.close();\n  },\n  onConfirm() {\n    message.info('onConfirm');\n    // modalApi.close();\n  },\n  onOpenChange(isOpen: boolean) {\n    if (isOpen) {\n      data.value = modalApi.getData<Record<string, any>>();\n    }\n  },\n});\n</script>\n<template>\n  <Modal title=\"数据共享示例\">\n    <div class=\"flex-col-center\">外部传递数据： {{ data }}</div>\n  </Modal>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/motion/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport { reactive } from 'vue';\n\nimport { Page } from '@vben/common-ui';\nimport { Motion, MotionGroup, MotionPresets } from '@vben/plugins/motion';\n\nimport { refAutoReset, watchDebounced } from '@vueuse/core';\nimport {\n  Button,\n  Card,\n  Col,\n  Form,\n  FormItem,\n  InputNumber,\n  Row,\n  Select,\n} from 'ant-design-vue';\n// 本例子用不到visible类型的动画。带有VisibleOnce和Visible的类型会在组件进入视口被显示时执行动画，\nconst presets = MotionPresets.filter((v) => !v.includes('Visible'));\nconst showCard1 = refAutoReset(true, 100);\nconst showCard2 = refAutoReset(true, 100);\nconst showCard3 = refAutoReset(true, 100);\nconst motionProps = reactive({\n  delay: 0,\n  duration: 300,\n  enter: { scale: 1 },\n  hovered: { scale: 1.1, transition: { delay: 0, duration: 50 } },\n  preset: 'fade',\n  tapped: { scale: 0.9, transition: { delay: 0, duration: 50 } },\n});\n\nconst motionGroupProps = reactive({\n  delay: 0,\n  duration: 300,\n  enter: { scale: 1 },\n  hovered: { scale: 1.1, transition: { delay: 0, duration: 50 } },\n  preset: 'fade',\n  tapped: { scale: 0.9, transition: { delay: 0, duration: 50 } },\n});\n\nwatchDebounced(\n  motionProps,\n  () => {\n    showCard2.value = false;\n  },\n  { debounce: 200, deep: true },\n);\n\nwatchDebounced(\n  motionGroupProps,\n  () => {\n    showCard3.value = false;\n  },\n  { debounce: 200, deep: true },\n);\n\nfunction openDocPage() {\n  window.open('https://motion.vueuse.org/', '_blank');\n}\n</script>\n<template>\n  <Page title=\"Motion\">\n    <template #description>\n      <span>一个易于使用的为其它组件赋予动画效果的组件。</span>\n      <Button type=\"link\" @click=\"openDocPage\">查看文档</Button>\n    </template>\n    <Card title=\"使用指令\" :body-style=\"{ minHeight: '5rem' }\">\n      <template #extra>\n        <Button type=\"primary\" @click=\"showCard1 = false\">重载</Button>\n      </template>\n      <div>\n        <div class=\"relative flex gap-2 overflow-hidden\" v-if=\"showCard1\">\n          <Button v-motion-fade-visible>fade</Button>\n          <Button v-motion-pop-visible :duration=\"500\">pop</Button>\n          <Button v-motion-slide-left>slide-left</Button>\n          <Button v-motion-slide-right>slide-right</Button>\n          <Button v-motion-slide-bottom>slide-bottom</Button>\n          <Button v-motion-slide-top>slide-top</Button>\n        </div>\n      </div>\n    </Card>\n    <Card\n      class=\"mt-2\"\n      title=\"使用组件（将内部作为一个整体添加动画）\"\n      :body-style=\"{ padding: 0 }\"\n    >\n      <div class=\"relative flex-center min-h-32 gap-2 overflow-hidden\">\n        <Motion\n          v-bind=\"motionProps\"\n          v-if=\"showCard2\"\n          class=\"flex items-center gap-2\"\n        >\n          <Button size=\"large\">这个按钮在显示时会有动画效果</Button>\n          <span>附属组件，会作为整体处理动画</span>\n        </Motion>\n      </div>\n      <div class=\"relative flex-center min-h-32 gap-2 overflow-hidden\">\n        <div v-if=\"showCard2\" class=\"flex items-center gap-2\">\n          <span>顺序延迟</span>\n          <Motion\n            v-bind=\"{\n              ...motionProps,\n              delay: motionProps.delay + 100 * i,\n            }\"\n            v-for=\"i in 5\"\n            :key=\"i\"\n          >\n            <Button size=\"large\">按钮{{ i }}</Button>\n          </Motion>\n        </div>\n      </div>\n      <div>\n        <Form :model=\"motionProps\" :label-col=\"{ span: 10 }\">\n          <Row>\n            <Col :span=\"8\">\n              <FormItem prop=\"preset\" label=\"动画效果\">\n                <Select v-model:value=\"motionProps.preset\">\n                  <Select.Option\n                    :value=\"preset\"\n                    v-for=\"preset in presets\"\n                    :key=\"preset\"\n                  >\n                    {{ preset }}\n                  </Select.Option>\n                </Select>\n              </FormItem>\n            </Col>\n            <Col :span=\"8\">\n              <FormItem prop=\"duration\" label=\"持续时间\">\n                <InputNumber v-model:value=\"motionProps.duration\" />\n              </FormItem>\n            </Col>\n            <Col :span=\"8\">\n              <FormItem prop=\"delay\" label=\"延迟动画\">\n                <InputNumber v-model:value=\"motionProps.delay\" />\n              </FormItem>\n            </Col>\n            <Col :span=\"8\">\n              <FormItem prop=\"hovered.scale\" label=\"Hover缩放\">\n                <InputNumber v-model:value=\"motionProps.hovered.scale\" />\n              </FormItem>\n            </Col>\n            <Col :span=\"8\">\n              <FormItem prop=\"hovered.tapped\" label=\"按下时缩放\">\n                <InputNumber v-model:value=\"motionProps.tapped.scale\" />\n              </FormItem>\n            </Col>\n          </Row>\n        </Form>\n      </div>\n    </Card>\n    <Card\n      class=\"mt-2\"\n      title=\"分组动画（每个子元素都会应用相同的独立动画）\"\n      :body-style=\"{ padding: 0 }\"\n    >\n      <div class=\"relative flex-center min-h-32 gap-2 overflow-hidden\">\n        <MotionGroup v-bind=\"motionGroupProps\" v-if=\"showCard3\">\n          <Button size=\"large\">按钮1</Button>\n          <Button size=\"large\">按钮2</Button>\n          <Button size=\"large\">按钮3</Button>\n          <Button size=\"large\">按钮4</Button>\n          <Button size=\"large\">按钮5</Button>\n        </MotionGroup>\n      </div>\n      <div>\n        <Form :model=\"motionGroupProps\" :label-col=\"{ span: 10 }\">\n          <Row>\n            <Col :span=\"8\">\n              <FormItem prop=\"preset\" label=\"动画效果\">\n                <Select v-model:value=\"motionGroupProps.preset\">\n                  <Select.Option\n                    :value=\"preset\"\n                    v-for=\"preset in presets\"\n                    :key=\"preset\"\n                  >\n                    {{ preset }}\n                  </Select.Option>\n                </Select>\n              </FormItem>\n            </Col>\n            <Col :span=\"8\">\n              <FormItem prop=\"duration\" label=\"持续时间\">\n                <InputNumber v-model:value=\"motionGroupProps.duration\" />\n              </FormItem>\n            </Col>\n            <Col :span=\"8\">\n              <FormItem prop=\"delay\" label=\"延迟动画\">\n                <InputNumber v-model:value=\"motionGroupProps.delay\" />\n              </FormItem>\n            </Col>\n            <Col :span=\"8\">\n              <FormItem prop=\"hovered.scale\" label=\"Hover缩放\">\n                <InputNumber v-model:value=\"motionGroupProps.hovered.scale\" />\n              </FormItem>\n            </Col>\n            <Col :span=\"8\">\n              <FormItem prop=\"hovered.tapped\" label=\"按下时缩放\">\n                <InputNumber v-model:value=\"motionGroupProps.tapped.scale\" />\n              </FormItem>\n            </Col>\n          </Row>\n        </Form>\n      </div>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/resize/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ref } from 'vue';\n\nimport { Page, VResize } from '@vben/common-ui';\n\nconst colorMap = ['red', 'green', 'yellow', 'gray'];\n\ntype TSize = {\n  height: number;\n  left: number;\n  top: number;\n  width: number;\n};\n\nconst sizeList = ref<TSize[]>([\n  { height: 200, left: 200, top: 200, width: 200 },\n  { height: 300, left: 300, top: 300, width: 300 },\n  { height: 400, left: 400, top: 400, width: 400 },\n  { height: 500, left: 500, top: 500, width: 500 },\n]);\n\nconst resize = (size?: TSize, rect?: TSize) => {\n  if (!size || !rect) return;\n\n  size.height = rect.height;\n  size.left = rect.left;\n  size.top = rect.top;\n  size.width = rect.width;\n};\n</script>\n\n<template>\n  <Page description=\"Resize组件基础示例\" title=\"Resize组件\">\n    <div class=\"m-4 bg-blue-500 p-48 text-xl\">\n      <div v-for=\"size in sizeList\" :key=\"size.width\">\n        {{\n          `width: ${size.width}px, height: ${size.height}px, top: ${size.top}px, left: ${size.left}px`\n        }}\n      </div>\n    </div>\n\n    <template v-for=\"(_, idx) of 4\" :key=\"idx\">\n      <VResize\n        :h=\"100 * (idx + 1)\"\n        :w=\"100 * (idx + 1)\"\n        :x=\"100 * (idx + 1)\"\n        :y=\"100 * (idx + 1)\"\n        @dragging=\"(rect) => resize(sizeList[idx], rect)\"\n        @resizing=\"(rect) => resize(sizeList[idx], rect)\"\n      >\n        <div\n          :style=\"{ backgroundColor: colorMap[idx] }\"\n          class=\"size-full\"\n        ></div>\n      </VResize>\n    </template>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/tippy/index.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { TippyProps } from '@vben/common-ui';\n\nimport { reactive } from 'vue';\n\nimport { Page, Tippy } from '@vben/common-ui';\n\nimport { Button, Card, Flex } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\n\nconst tippyProps = reactive<TippyProps>({\n  animation: 'shift-away',\n  arrow: true,\n  content: '这是一个提示',\n  delay: [200, 200],\n  duration: 200,\n  followCursor: false,\n  hideOnClick: false,\n  inertia: true,\n  maxWidth: 'none',\n  placement: 'top',\n  theme: 'dark',\n  trigger: 'mouseenter focusin',\n});\n\nfunction parseBoolean(value: string) {\n  switch (value) {\n    case 'false': {\n      return false;\n    }\n    case 'true': {\n      return true;\n    }\n    default: {\n      return value;\n    }\n  }\n}\n\nconst [Form] = useVbenForm({\n  handleValuesChange(values) {\n    Object.assign(tippyProps, {\n      ...values,\n      delay: [values.delay1, values.delay2],\n      followCursor: parseBoolean(values.followCursor),\n      hideOnClick: parseBoolean(values.hideOnClick),\n      trigger: values.trigger.join(' '),\n    });\n  },\n  schema: [\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        buttonStyle: 'solid',\n        class: 'w-full',\n        options: [\n          { label: '自动', value: 'auto' },\n          { label: '暗色', value: 'dark' },\n          { label: '亮色', value: 'light' },\n        ],\n        optionType: 'button',\n      },\n      defaultValue: tippyProps.theme,\n      fieldName: 'theme',\n      label: '主题',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        class: 'w-full',\n        options: [\n          { label: '向上滑入', value: 'shift-away' },\n          { label: '向下滑入', value: 'shift-toward' },\n          { label: '缩放', value: 'scale' },\n          { label: '透视', value: 'perspective' },\n          { label: '淡入', value: 'fade' },\n        ],\n      },\n      defaultValue: tippyProps.animation,\n      fieldName: 'animation',\n      label: '动画类型',\n    },\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        buttonStyle: 'solid',\n        options: [\n          { label: '是', value: true },\n          { label: '否', value: false },\n        ],\n        optionType: 'button',\n      },\n      defaultValue: tippyProps.inertia,\n      fieldName: 'inertia',\n      label: '动画惯性',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        class: 'w-full',\n        options: [\n          { label: '顶部', value: 'top' },\n          { label: '顶左', value: 'top-start' },\n          { label: '顶右', value: 'top-end' },\n          { label: '底部', value: 'bottom' },\n          { label: '底左', value: 'bottom-start' },\n          { label: '底右', value: 'bottom-end' },\n          { label: '左侧', value: 'left' },\n          { label: '左上', value: 'left-start' },\n          { label: '左下', value: 'left-end' },\n          { label: '右侧', value: 'right' },\n          { label: '右上', value: 'right-start' },\n          { label: '右下', value: 'right-end' },\n        ],\n      },\n      defaultValue: tippyProps.placement,\n      fieldName: 'placement',\n      label: '位置',\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        addonAfter: '毫秒',\n      },\n      defaultValue: tippyProps.duration,\n      fieldName: 'duration',\n      label: '动画时长',\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        addonAfter: '毫秒',\n      },\n      defaultValue: 100,\n      fieldName: 'delay1',\n      label: '显示延时',\n    },\n    {\n      component: 'InputNumber',\n      componentProps: {\n        addonAfter: '毫秒',\n      },\n      defaultValue: 100,\n      fieldName: 'delay2',\n      label: '隐藏延时',\n    },\n    {\n      component: 'Input',\n      defaultValue: tippyProps.content,\n      fieldName: 'content',\n      label: '内容',\n    },\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        buttonStyle: 'solid',\n        options: [\n          { label: '是', value: true },\n          { label: '否', value: false },\n        ],\n        optionType: 'button',\n      },\n      defaultValue: tippyProps.arrow,\n      fieldName: 'arrow',\n      label: '指示箭头',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        class: 'w-full',\n        options: [\n          { label: '不跟随', value: 'false' },\n          { label: '完全跟随', value: 'true' },\n          { label: '仅横向', value: 'horizontal' },\n          { label: '仅纵向', value: 'vertical' },\n          { label: '仅初始', value: 'initial' },\n        ],\n      },\n      defaultValue: tippyProps.followCursor?.toString(),\n      fieldName: 'followCursor',\n      label: '跟随指针',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        class: 'w-full',\n        mode: 'multiple',\n        options: [\n          { label: '鼠标移入', value: 'mouseenter' },\n          { label: '被点击', value: 'click' },\n          { label: '获得焦点', value: 'focusin' },\n          { label: '无触发，仅手动', value: 'manual' },\n        ],\n      },\n      defaultValue: tippyProps.trigger?.split(' '),\n      fieldName: 'trigger',\n      label: '触发方式',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        class: 'w-full',\n        options: [\n          { label: '否', value: 'false' },\n          { label: '是', value: 'true' },\n          { label: '仅内部', value: 'toggle' },\n        ],\n      },\n      defaultValue: tippyProps.hideOnClick?.toString(),\n      dependencies: {\n        componentProps(_, formAction) {\n          return {\n            disabled: !formAction.values.trigger.includes('click'),\n          };\n        },\n        triggerFields: ['trigger'],\n      },\n      fieldName: 'hideOnClick',\n      help: '只有在触发方式为`click`时才有效',\n      label: '点击后隐藏',\n    },\n    {\n      component: 'Input',\n      componentProps: {\n        allowClear: true,\n        placeholder: 'none、200px',\n      },\n      defaultValue: tippyProps.maxWidth,\n      fieldName: 'maxWidth',\n      label: '最大宽度',\n    },\n  ],\n  showDefaultActions: false,\n  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n});\n\nfunction goDoc() {\n  window.open('https://atomiks.github.io/tippyjs/v6/all-props/');\n}\n</script>\n<template>\n  <Page title=\"Tippy\">\n    <template #description>\n      <div class=\"flex items-center\">\n        <p>\n          Tippy\n          是一个轻量级的提示工具库，它可以用来创建各种交互式提示，如工具提示、引导提示等。\n        </p>\n        <Button type=\"link\" size=\"small\" @click=\"goDoc\">查看文档</Button>\n      </div>\n    </template>\n    <Card title=\"指令形式使用\">\n      <p class=\"mb-4\">\n        指令形式使用比较简洁，直接在需要展示tooltip的组件上用v-tippy传递配置，适用于固定内容的工具提示。\n      </p>\n      <Flex warp=\"warp\" gap=\"20\" align=\"center\">\n        <Button v-tippy=\"'这是一个提示，使用了默认的配置'\">默认配置</Button>\n\n        <Button\n          v-tippy=\"{ theme: 'light', content: '这是一个提示，总是light主题' }\"\n        >\n          指定主题\n        </Button>\n        <Button\n          v-tippy=\"{\n            theme: 'light',\n            content: '这个提示将在点燃组件100毫秒后激活',\n            delay: 100,\n          }\"\n        >\n          指定延时\n        </Button>\n        <Button\n          v-tippy=\"{\n            content: '本提示的动画为`scale`',\n            animation: 'scale',\n          }\"\n        >\n          指定动画\n        </Button>\n      </Flex>\n    </Card>\n    <Card title=\"组件形式使用\" class=\"mt-4\">\n      <div class=\"flex w-full justify-center\">\n        <Tippy v-bind=\"tippyProps\">\n          <Button>鼠标移到这个组件上来体验效果</Button>\n        </Tippy>\n      </div>\n\n      <Form class=\"mt-4\" />\n      <template #actions>\n        <p\n          class=\"cursor-default text-secondary-foreground hover:text-secondary-foreground\"\n        >\n          更多配置请\n          <Button type=\"link\" size=\"small\" @click=\"goDoc\">查看文档</Button>\n          ，这里只列出了一些常用的配置\n        </p>\n      </template>\n    </Card>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/vxe-table/basic.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridListeners, VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Button, message } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nimport DocButton from '../doc-button.vue';\nimport { MOCK_TABLE_DATA } from './table-data';\n\ninterface RowType {\n  address: string;\n  age: number;\n  id: number;\n  name: string;\n  nickname: string;\n  role: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { field: 'name', title: 'Name' },\n    { field: 'age', sortable: true, title: 'Age' },\n    { field: 'nickname', title: 'Nickname' },\n    { field: 'role', title: 'Role' },\n    { field: 'address', showOverflow: true, title: 'Address' },\n  ],\n  data: MOCK_TABLE_DATA,\n  pagerConfig: {\n    enabled: false,\n  },\n  sortConfig: {\n    multiple: true,\n  },\n};\n\nconst gridEvents: VxeGridListeners<RowType> = {\n  cellClick: ({ row }) => {\n    message.info(`cell-click: ${row.name}`);\n  },\n};\n\nconst [Grid, gridApi] = useVbenVxeGrid<RowType>({\n  // 放开注释查看表单组件的类型\n  // formOptions: {\n  //   schema: [\n  //     {\n  //       component: 'Switch',\n  //       fieldName: 'name',\n  //     },\n  //   ],\n  // },\n  gridEvents,\n  gridOptions,\n});\n\n// 放开注释查看当前表格实例的类型\n// gridApi.grid\n\nconst showBorder = gridApi.useStore((state) => state.gridOptions?.border);\nconst showStripe = gridApi.useStore((state) => state.gridOptions?.stripe);\n\nfunction changeBorder() {\n  gridApi.setGridOptions({\n    border: !showBorder.value,\n  });\n}\n\nfunction changeStripe() {\n  gridApi.setGridOptions({\n    stripe: !showStripe.value,\n  });\n}\n\nfunction changeLoading() {\n  gridApi.setLoading(true);\n  setTimeout(() => {\n    gridApi.setLoading(false);\n  }, 2000);\n}\n</script>\n\n<template>\n  <Page\n    description=\"表格组件常用于快速开发数据展示与交互界面，示例数据为静态数据。该组件是对vxe-table进行简单的二次封装，大部分属性与方法与vxe-table保持一致。\"\n    title=\"表格基础示例\"\n  >\n    <template #extra>\n      <DocButton path=\"/components/common-ui/vben-vxe-table\" />\n    </template>\n    <Grid table-title=\"基础列表\" table-title-help=\"提示\">\n      <!-- <template #toolbar-actions>\n        <Button class=\"mr-2\" type=\"primary\">左侧插槽</Button>\n      </template> -->\n      <template #toolbar-tools>\n        <Button class=\"mr-2\" type=\"primary\" @click=\"changeBorder\">\n          {{ showBorder ? '隐藏' : '显示' }}边框\n        </Button>\n        <Button class=\"mr-2\" type=\"primary\" @click=\"changeLoading\">\n          显示loading\n        </Button>\n        <Button type=\"primary\" @click=\"changeStripe\">\n          {{ showStripe ? '隐藏' : '显示' }}斑马纹\n        </Button>\n      </template>\n    </Grid>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/vxe-table/custom-cell.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Button, Image, Switch, Tag } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\nimport { getExampleTableApi } from '#/api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  imageUrl: string;\n  open: boolean;\n  price: string;\n  productName: string;\n  releaseDate: string;\n  status: 'error' | 'success' | 'warning';\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  checkboxConfig: {\n    highlight: true,\n    labelField: 'name',\n  },\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { field: 'category', title: 'Category', width: 100 },\n    {\n      field: 'imageUrl',\n      slots: { default: 'image-url' },\n      title: 'Image',\n      width: 100,\n    },\n    {\n      cellRender: { name: 'CellImage' },\n      field: 'imageUrl2',\n      title: 'Render Image',\n      width: 130,\n    },\n    {\n      field: 'open',\n      slots: { default: 'open' },\n      title: 'Open',\n      width: 100,\n    },\n    {\n      field: 'status',\n      slots: { default: 'status' },\n      title: 'Status',\n      width: 100,\n    },\n    { field: 'color', title: 'Color', width: 100 },\n    { field: 'productName', title: 'Product Name', width: 200 },\n    { field: 'price', title: 'Price', width: 100 },\n    {\n      field: 'releaseDate',\n      formatter: 'formatDateTime',\n      title: 'Date',\n      width: 200,\n    },\n    {\n      cellRender: { name: 'CellLink', props: { text: '编辑' } },\n      field: 'action',\n      fixed: 'right',\n      title: '操作',\n      width: 120,\n    },\n  ],\n  height: 'auto',\n  keepSource: true,\n  pagerConfig: {},\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }) => {\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n        });\n      },\n    },\n  },\n  showOverflow: false,\n};\n\nconst [Grid] = useVbenVxeGrid({ gridOptions });\n</script>\n\n<template>\n  <Page auto-content-height>\n    <Grid>\n      <template #image-url=\"{ row }\">\n        <Image :src=\"row.imageUrl\" height=\"30\" width=\"30\" />\n      </template>\n      <template #open=\"{ row }\">\n        <Switch v-model=\"row.open\" />\n      </template>\n      <template #status=\"{ row }\">\n        <Tag :color=\"row.color\">{{ row.status }}</Tag>\n      </template>\n      <template #action>\n        <Button type=\"link\">编辑</Button>\n      </template>\n    </Grid>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/vxe-table/edit-cell.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Page } from '@vben/common-ui';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\nimport { getExampleTableApi } from '#/api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  price: string;\n  productName: string;\n  releaseDate: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { editRender: { name: 'input' }, field: 'category', title: 'Category' },\n    { editRender: { name: 'input' }, field: 'color', title: 'Color' },\n    {\n      editRender: { name: 'input' },\n      field: 'productName',\n      title: 'Product Name',\n    },\n    { field: 'price', title: 'Price' },\n    { field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },\n  ],\n  editConfig: {\n    mode: 'cell',\n    trigger: 'click',\n  },\n  height: 'auto',\n  pagerConfig: {},\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }) => {\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n        });\n      },\n    },\n  },\n  showOverflow: true,\n};\n\nconst [Grid] = useVbenVxeGrid({ gridOptions });\n</script>\n\n<template>\n  <Page auto-content-height>\n    <Grid />\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/vxe-table/edit-row.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Button, message } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\nimport { getExampleTableApi } from '#/api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  price: string;\n  productName: string;\n  releaseDate: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { editRender: { name: 'input' }, field: 'category', title: 'Category' },\n    { editRender: { name: 'input' }, field: 'color', title: 'Color' },\n    {\n      editRender: { name: 'input' },\n      field: 'productName',\n      title: 'Product Name',\n    },\n    { field: 'price', title: 'Price' },\n    { field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },\n    { slots: { default: 'action' }, title: '操作' },\n  ],\n  editConfig: {\n    mode: 'row',\n    trigger: 'click',\n  },\n  height: 'auto',\n  pagerConfig: {},\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }) => {\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n        });\n      },\n    },\n  },\n  showOverflow: true,\n};\n\nconst [Grid, gridApi] = useVbenVxeGrid({ gridOptions });\n\nfunction hasEditStatus(row: RowType) {\n  return gridApi.grid?.isEditByRow(row);\n}\n\nfunction editRowEvent(row: RowType) {\n  gridApi.grid?.setEditRow(row);\n}\n\nasync function saveRowEvent(row: RowType) {\n  await gridApi.grid?.clearEdit();\n\n  gridApi.setLoading(true);\n  setTimeout(() => {\n    gridApi.setLoading(false);\n    message.success({\n      content: `保存成功！category=${row.category}`,\n    });\n  }, 600);\n}\n\nconst cancelRowEvent = (_row: RowType) => {\n  gridApi.grid?.clearEdit();\n};\n</script>\n\n<template>\n  <Page auto-content-height>\n    <Grid>\n      <template #action=\"{ row }\">\n        <template v-if=\"hasEditStatus(row)\">\n          <Button type=\"link\" @click=\"saveRowEvent(row)\">保存</Button>\n          <Button type=\"link\" @click=\"cancelRowEvent(row)\">取消</Button>\n        </template>\n        <template v-else>\n          <Button type=\"link\" @click=\"editRowEvent(row)\">编辑</Button>\n        </template>\n      </template>\n    </Grid>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/vxe-table/fixed.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Button } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\nimport { getExampleTableApi } from '#/api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  price: string;\n  productName: string;\n  releaseDate: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { fixed: 'left', title: '序号', type: 'seq', width: 50 },\n    { field: 'category', title: 'Category', width: 300 },\n    { field: 'color', title: 'Color', width: 300 },\n    { field: 'productName', title: 'Product Name', width: 300 },\n    { field: 'price', title: 'Price', width: 300 },\n    {\n      field: 'releaseDate',\n      formatter: 'formatDateTime',\n      title: 'DateTime',\n      width: 500,\n    },\n    {\n      field: 'action',\n      fixed: 'right',\n      slots: { default: 'action' },\n      title: '操作',\n      width: 120,\n    },\n  ],\n  height: 'auto',\n  pagerConfig: {},\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }) => {\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n        });\n      },\n    },\n  },\n  rowConfig: {\n    isHover: true,\n  },\n};\n\nconst [Grid] = useVbenVxeGrid({ gridOptions });\n</script>\n\n<template>\n  <Page auto-content-height>\n    <Grid>\n      <template #action>\n        <Button type=\"link\">编辑</Button>\n      </template>\n    </Grid>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/vxe-table/form.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VbenFormProps } from '#/adapter/form';\nimport type { VxeTableGridOptions } from '#/adapter/vxe-table';\n\nimport { Page } from '@vben/common-ui';\n\nimport { message } from 'ant-design-vue';\nimport dayjs from 'dayjs';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\nimport { getExampleTableApi } from '#/api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  price: string;\n  productName: string;\n  releaseDate: string;\n}\n\nconst formOptions: VbenFormProps = {\n  // 默认展开\n  collapsed: false,\n  fieldMappingTime: [['date', ['start', 'end']]],\n  schema: [\n    {\n      component: 'Input',\n      defaultValue: '1',\n      fieldName: 'category',\n      label: 'Category',\n    },\n    {\n      component: 'Input',\n      fieldName: 'productName',\n      label: 'ProductName',\n    },\n    {\n      component: 'Input',\n      fieldName: 'price',\n      label: 'Price',\n    },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        options: [\n          {\n            label: 'Color1',\n            value: '1',\n          },\n          {\n            label: 'Color2',\n            value: '2',\n          },\n        ],\n        placeholder: '请选择',\n      },\n      fieldName: 'color',\n      label: 'Color',\n    },\n    {\n      component: 'RangePicker',\n      defaultValue: [dayjs().subtract(7, 'days'), dayjs()],\n      fieldName: 'date',\n      label: 'Date',\n    },\n  ],\n  // 控制表单是否显示折叠按钮\n  showCollapseButton: true,\n  // 是否在字段值改变时提交表单\n  submitOnChange: true,\n  // 按下回车时是否提交表单\n  submitOnEnter: false,\n};\n\nconst gridOptions: VxeTableGridOptions<RowType> = {\n  checkboxConfig: {\n    highlight: true,\n    labelField: 'name',\n  },\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { align: 'left', title: 'Name', type: 'checkbox', width: 100 },\n    { field: 'category', title: 'Category' },\n    { field: 'color', title: 'Color' },\n    { field: 'productName', title: 'Product Name' },\n    { field: 'price', title: 'Price' },\n    { field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },\n  ],\n  exportConfig: {},\n  height: 'auto',\n  keepSource: true,\n  pagerConfig: {},\n  proxyConfig: {\n    ajax: {\n      query: async ({ page }, formValues) => {\n        message.success(`Query params: ${JSON.stringify(formValues)}`);\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n          ...formValues,\n        });\n      },\n    },\n  },\n  toolbarConfig: {\n    custom: true,\n    export: true,\n    refresh: true,\n    resizable: true,\n    search: true,\n    zoom: true,\n  },\n};\n\nconst [Grid] = useVbenVxeGrid({\n  formOptions,\n  gridOptions,\n});\n</script>\n\n<template>\n  <Page auto-content-height>\n    <Grid />\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/vxe-table/remote.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Button } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\nimport { getExampleTableApi } from '#/api';\n\ninterface RowType {\n  category: string;\n  color: string;\n  id: string;\n  price: string;\n  productName: string;\n  releaseDate: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  checkboxConfig: {\n    highlight: true,\n    labelField: 'name',\n  },\n  columns: [\n    { title: '序号', type: 'seq', width: 50 },\n    { align: 'left', title: 'Name', type: 'checkbox', width: 100 },\n    { field: 'category', sortable: true, title: 'Category' },\n    { field: 'color', sortable: true, title: 'Color' },\n    { field: 'productName', sortable: true, title: 'Product Name' },\n    { field: 'price', sortable: true, title: 'Price' },\n    { field: 'releaseDate', formatter: 'formatDateTime', title: 'DateTime' },\n  ],\n  exportConfig: {},\n  height: 'auto',\n  keepSource: true,\n  proxyConfig: {\n    ajax: {\n      query: async ({ page, sort }) => {\n        return await getExampleTableApi({\n          page: page.currentPage,\n          pageSize: page.pageSize,\n          sortBy: sort.field,\n          sortOrder: sort.order,\n        });\n      },\n    },\n    sort: true,\n  },\n  sortConfig: {\n    defaultSort: { field: 'category', order: 'desc' },\n    remote: true,\n  },\n  toolbarConfig: {\n    custom: true,\n    export: true,\n    // import: true,\n    refresh: true,\n    zoom: true,\n  },\n};\n\nconst [Grid, gridApi] = useVbenVxeGrid({\n  gridOptions,\n});\n</script>\n\n<template>\n  <Page auto-content-height>\n    <Grid table-title=\"数据列表\" table-title-help=\"提示\">\n      <template #toolbar-tools>\n        <Button class=\"mr-2\" type=\"primary\" @click=\"() => gridApi.query()\">\n          刷新当前页面\n        </Button>\n        <Button type=\"primary\" @click=\"() => gridApi.reload()\">\n          刷新并返回第一页\n        </Button>\n      </template>\n    </Grid>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/vxe-table/table-data.ts",
    "content": "interface TableRowData {\n  address: string;\n  age: number;\n  id: number;\n  name: string;\n  nickname: string;\n  role: string;\n}\n\nconst roles = ['User', 'Admin', 'Manager', 'Guest'];\n\nexport const MOCK_TABLE_DATA: TableRowData[] = (() => {\n  const data: TableRowData[] = [];\n  for (let i = 0; i < 40; i++) {\n    data.push({\n      address: `New York${i}`,\n      age: i + 1,\n      id: i,\n      name: `Test${i}`,\n      nickname: `Test${i}`,\n      role: roles[Math.floor(Math.random() * roles.length)] as string,\n    });\n  }\n  return data;\n})();\n\nexport const MOCK_TREE_TABLE_DATA = [\n  {\n    date: '2020-08-01',\n    id: 10_000,\n    name: 'Test1',\n    parentId: null,\n    size: 1024,\n    type: 'mp3',\n  },\n  {\n    date: '2021-04-01',\n    id: 10_050,\n    name: 'Test2',\n    parentId: null,\n    size: 0,\n    type: 'mp4',\n  },\n  {\n    date: '2020-03-01',\n    id: 24_300,\n    name: 'Test3',\n    parentId: 10_050,\n    size: 1024,\n    type: 'avi',\n  },\n  {\n    date: '2021-04-01',\n    id: 20_045,\n    name: 'Test4',\n    parentId: 24_300,\n    size: 600,\n    type: 'html',\n  },\n  {\n    date: '2021-04-01',\n    id: 10_053,\n    name: 'Test5',\n    parentId: 24_300,\n    size: 0,\n    type: 'avi',\n  },\n  {\n    date: '2021-10-01',\n    id: 24_330,\n    name: 'Test6',\n    parentId: 10_053,\n    size: 25,\n    type: 'txt',\n  },\n  {\n    date: '2020-01-01',\n    id: 21_011,\n    name: 'Test7',\n    parentId: 10_053,\n    size: 512,\n    type: 'pdf',\n  },\n  {\n    date: '2021-06-01',\n    id: 22_200,\n    name: 'Test8',\n    parentId: 10_053,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2020-11-01',\n    id: 23_666,\n    name: 'Test9',\n    parentId: null,\n    size: 2048,\n    type: 'xlsx',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_677,\n    name: 'Test10',\n    parentId: 23_666,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_671,\n    name: 'Test11',\n    parentId: 23_677,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_672,\n    name: 'Test12',\n    parentId: 23_677,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_688,\n    name: 'Test13',\n    parentId: 23_666,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_681,\n    name: 'Test14',\n    parentId: 23_688,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 23_682,\n    name: 'Test15',\n    parentId: 23_688,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2020-10-01',\n    id: 24_555,\n    name: 'Test16',\n    parentId: null,\n    size: 224,\n    type: 'avi',\n  },\n  {\n    date: '2021-06-01',\n    id: 24_566,\n    name: 'Test17',\n    parentId: 24_555,\n    size: 1024,\n    type: 'js',\n  },\n  {\n    date: '2021-06-01',\n    id: 24_577,\n    name: 'Test18',\n    parentId: 24_555,\n    size: 1024,\n    type: 'js',\n  },\n];\n"
  },
  {
    "path": "playground/src/views/examples/vxe-table/tree.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { Page } from '@vben/common-ui';\n\nimport { Button } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\nimport { MOCK_TREE_TABLE_DATA } from './table-data';\n\ninterface RowType {\n  date: string;\n  id: number;\n  name: string;\n  parentId: null | number;\n  size: number;\n  type: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { type: 'seq', width: 70 },\n    { field: 'name', minWidth: 300, title: 'Name', treeNode: true },\n    { field: 'size', title: 'Size' },\n    { field: 'type', title: 'Type' },\n    { field: 'date', title: 'Date' },\n  ],\n  data: MOCK_TREE_TABLE_DATA,\n  pagerConfig: {\n    enabled: false,\n  },\n  treeConfig: {\n    parentField: 'parentId',\n    rowField: 'id',\n    transform: true,\n  },\n};\n\nconst [Grid, gridApi] = useVbenVxeGrid({ gridOptions });\n\nconst expandAll = () => {\n  gridApi.grid?.setAllTreeExpand(true);\n};\n\nconst collapseAll = () => {\n  gridApi.grid?.setAllTreeExpand(false);\n};\n</script>\n\n<template>\n  <Page>\n    <Grid table-title=\"数据列表\" table-title-help=\"提示\">\n      <template #toolbar-tools>\n        <Button class=\"mr-2\" type=\"primary\" @click=\"expandAll\">\n          展开全部\n        </Button>\n        <Button type=\"primary\" @click=\"collapseAll\"> 折叠全部 </Button>\n      </template>\n    </Grid>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/examples/vxe-table/virtual.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { VxeGridProps } from '#/adapter/vxe-table';\n\nimport { onMounted } from 'vue';\n\nimport { Page } from '@vben/common-ui';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\n\ninterface RowType {\n  id: number;\n  name: string;\n  role: string;\n  sex: string;\n}\n\nconst gridOptions: VxeGridProps<RowType> = {\n  columns: [\n    { type: 'seq', width: 70 },\n    { field: 'name', title: 'Name' },\n    { field: 'role', title: 'Role' },\n    { field: 'sex', title: 'Sex' },\n  ],\n  data: [],\n  height: 'auto',\n  pagerConfig: {\n    enabled: false,\n  },\n  scrollY: {\n    enabled: true,\n    gt: 0,\n  },\n  showOverflow: true,\n};\n\nconst [Grid, gridApi] = useVbenVxeGrid({ gridOptions });\n\n// 模拟行数据\nconst loadList = (size = 200) => {\n  try {\n    const dataList: RowType[] = [];\n    for (let i = 0; i < size; i++) {\n      dataList.push({\n        id: 10_000 + i,\n        name: `Test${i}`,\n        role: 'Developer',\n        sex: '男',\n      });\n    }\n    gridApi.setGridOptions({ data: dataList });\n  } catch (error) {\n    console.error('Failed to load data:', error);\n    // Implement user-friendly error handling\n  }\n};\n\nonMounted(() => {\n  loadList(1000);\n});\n</script>\n\n<template>\n  <Page auto-content-height>\n    <Grid />\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/system/dept/data.ts",
    "content": "import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';\n\nimport type { VbenFormSchema } from '#/adapter/form';\nimport type { OnActionClickFn } from '#/adapter/vxe-table';\nimport type { SystemDeptApi } from '#/api/system/dept';\n\nimport { z } from '#/adapter/form';\nimport { getDeptList } from '#/api/system/dept';\nimport { $t } from '#/locales';\n\n/**\n * 获取编辑表单的字段配置。如果没有使用多语言，可以直接export一个数组常量\n */\nexport function useSchema(): VbenFormSchema[] {\n  return [\n    {\n      component: 'Input',\n      fieldName: 'name',\n      label: $t('system.dept.deptName'),\n      rules: z\n        .string()\n        .min(2, $t('ui.formRules.minLength', [$t('system.dept.deptName'), 2]))\n        .max(\n          20,\n          $t('ui.formRules.maxLength', [$t('system.dept.deptName'), 20]),\n        ),\n    },\n    {\n      component: 'ApiTreeSelect',\n      componentProps: {\n        allowClear: true,\n        api: getDeptList,\n        class: 'w-full',\n        labelField: 'name',\n        valueField: 'id',\n        childrenField: 'children',\n      },\n      fieldName: 'pid',\n      label: $t('system.dept.parentDept'),\n    },\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        buttonStyle: 'solid',\n        options: [\n          { label: $t('common.enabled'), value: 1 },\n          { label: $t('common.disabled'), value: 0 },\n        ],\n        optionType: 'button',\n      },\n      defaultValue: 1,\n      fieldName: 'status',\n      label: $t('system.dept.status'),\n    },\n    {\n      component: 'Textarea',\n      componentProps: {\n        maxLength: 50,\n        rows: 3,\n        showCount: true,\n      },\n      fieldName: 'remark',\n      label: $t('system.dept.remark'),\n      rules: z\n        .string()\n        .max(50, $t('ui.formRules.maxLength', [$t('system.dept.remark'), 50]))\n        .optional(),\n    },\n  ];\n}\n\n/**\n * 获取表格列配置\n * @description 使用函数的形式返回列数据而不是直接export一个Array常量，是为了响应语言切换时重新翻译表头\n * @param onActionClick 表格操作按钮点击事件\n */\nexport function useColumns(\n  onActionClick?: OnActionClickFn<SystemDeptApi.SystemDept>,\n): VxeTableGridOptions<SystemDeptApi.SystemDept>['columns'] {\n  return [\n    {\n      align: 'left',\n      field: 'name',\n      fixed: 'left',\n      title: $t('system.dept.deptName'),\n      treeNode: true,\n      width: 150,\n    },\n    {\n      cellRender: { name: 'CellTag' },\n      field: 'status',\n      title: $t('system.dept.status'),\n      width: 100,\n    },\n    {\n      field: 'createTime',\n      title: $t('system.dept.createTime'),\n      width: 180,\n    },\n    {\n      field: 'remark',\n      title: $t('system.dept.remark'),\n    },\n    {\n      align: 'right',\n      cellRender: {\n        attrs: {\n          nameField: 'name',\n          nameTitle: $t('system.dept.name'),\n          onClick: onActionClick,\n        },\n        name: 'CellOperation',\n        options: [\n          {\n            code: 'append',\n            text: '新增下级',\n          },\n          'edit', // 默认的编辑按钮\n          {\n            code: 'delete', // 默认的删除按钮\n            disabled: (row: SystemDeptApi.SystemDept) => {\n              return !!(row.children && row.children.length > 0);\n            },\n          },\n        ],\n      },\n      field: 'operation',\n      fixed: 'right',\n      headerAlign: 'center',\n      showOverflow: false,\n      title: $t('system.dept.operation'),\n      width: 200,\n    },\n  ];\n}\n"
  },
  {
    "path": "playground/src/views/system/dept/list.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  OnActionClickParams,\n  VxeTableGridOptions,\n} from '#/adapter/vxe-table';\nimport type { SystemDeptApi } from '#/api/system/dept';\n\nimport { Page, useVbenModal } from '@vben/common-ui';\nimport { Plus } from '@vben/icons';\n\nimport { Button, message } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\nimport { deleteDept, getDeptList } from '#/api/system/dept';\nimport { $t } from '#/locales';\n\nimport { useColumns } from './data';\nimport Form from './modules/form.vue';\n\nconst [FormModal, formModalApi] = useVbenModal({\n  connectedComponent: Form,\n  destroyOnClose: true,\n});\n\n/**\n * 编辑部门\n * @param row\n */\nfunction onEdit(row: SystemDeptApi.SystemDept) {\n  formModalApi.setData(row).open();\n}\n\n/**\n * 添加下级部门\n * @param row\n */\nfunction onAppend(row: SystemDeptApi.SystemDept) {\n  formModalApi.setData({ pid: row.id }).open();\n}\n\n/**\n * 创建新部门\n */\nfunction onCreate() {\n  formModalApi.setData(null).open();\n}\n\n/**\n * 删除部门\n * @param row\n */\nfunction onDelete(row: SystemDeptApi.SystemDept) {\n  const hideLoading = message.loading({\n    content: $t('ui.actionMessage.deleting', [row.name]),\n    duration: 0,\n    key: 'action_process_msg',\n  });\n  deleteDept(row.id)\n    .then(() => {\n      message.success({\n        content: $t('ui.actionMessage.deleteSuccess', [row.name]),\n        key: 'action_process_msg',\n      });\n      refreshGrid();\n    })\n    .catch(() => {\n      hideLoading();\n    });\n}\n\n/**\n * 表格操作按钮的回调函数\n */\nfunction onActionClick({\n  code,\n  row,\n}: OnActionClickParams<SystemDeptApi.SystemDept>) {\n  switch (code) {\n    case 'append': {\n      onAppend(row);\n      break;\n    }\n    case 'delete': {\n      onDelete(row);\n      break;\n    }\n    case 'edit': {\n      onEdit(row);\n      break;\n    }\n  }\n}\n\nconst [Grid, gridApi] = useVbenVxeGrid({\n  gridEvents: {},\n  gridOptions: {\n    columns: useColumns(onActionClick),\n    height: 'auto',\n    keepSource: true,\n    pagerConfig: {\n      enabled: false,\n    },\n    proxyConfig: {\n      ajax: {\n        query: async (_params) => {\n          return await getDeptList();\n        },\n      },\n    },\n    toolbarConfig: {\n      custom: true,\n      export: false,\n      refresh: true,\n      zoom: true,\n    },\n    treeConfig: {\n      parentField: 'pid',\n      rowField: 'id',\n      transform: false,\n    },\n  } as VxeTableGridOptions,\n});\n\n/**\n * 刷新表格\n */\nfunction refreshGrid() {\n  gridApi.query();\n}\n</script>\n<template>\n  <Page auto-content-height>\n    <FormModal @success=\"refreshGrid\" />\n    <Grid table-title=\"部门列表\">\n      <template #toolbar-tools>\n        <Button type=\"primary\" @click=\"onCreate\">\n          <Plus class=\"size-5\" />\n          {{ $t('ui.actionTitle.create', [$t('system.dept.name')]) }}\n        </Button>\n      </template>\n    </Grid>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/system/dept/modules/form.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { SystemDeptApi } from '#/api/system/dept';\n\nimport { computed, ref } from 'vue';\n\nimport { useVbenModal } from '@vben/common-ui';\n\nimport { Button } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\nimport { createDept, updateDept } from '#/api/system/dept';\nimport { $t } from '#/locales';\n\nimport { useSchema } from '../data';\n\nconst emit = defineEmits(['success']);\nconst formData = ref<SystemDeptApi.SystemDept>();\nconst getTitle = computed(() => {\n  return formData.value?.id\n    ? $t('ui.actionTitle.edit', [$t('system.dept.name')])\n    : $t('ui.actionTitle.create', [$t('system.dept.name')]);\n});\n\nconst [Form, formApi] = useVbenForm({\n  layout: 'vertical',\n  schema: useSchema(),\n  showDefaultActions: false,\n});\n\nfunction resetForm() {\n  formApi.resetForm();\n  formApi.setValues(formData.value || {});\n}\n\nconst [Modal, modalApi] = useVbenModal({\n  async onConfirm() {\n    const { valid } = await formApi.validate();\n    if (valid) {\n      modalApi.lock();\n      const data = await formApi.getValues();\n      try {\n        await (formData.value?.id\n          ? updateDept(formData.value.id, data)\n          : createDept(data));\n        modalApi.close();\n        emit('success');\n      } finally {\n        modalApi.lock(false);\n      }\n    }\n  },\n  onOpenChange(isOpen) {\n    if (isOpen) {\n      const data = modalApi.getData<SystemDeptApi.SystemDept>();\n      if (data) {\n        if (data.pid === 0) {\n          data.pid = undefined;\n        }\n        formData.value = data;\n        formApi.setValues(formData.value);\n      }\n    }\n  },\n});\n</script>\n\n<template>\n  <Modal :title=\"getTitle\">\n    <Form class=\"mx-4\" />\n    <template #prepend-footer>\n      <div class=\"flex-auto\">\n        <Button type=\"primary\" danger @click=\"resetForm\">\n          {{ $t('common.reset') }}\n        </Button>\n      </div>\n    </template>\n  </Modal>\n</template>\n"
  },
  {
    "path": "playground/src/views/system/menu/data.ts",
    "content": "import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';\nimport type { SystemMenuApi } from '#/api/system/menu';\n\nimport { $t } from '#/locales';\n\nexport function getMenuTypeOptions() {\n  return [\n    {\n      color: 'processing',\n      label: $t('system.menu.typeCatalog'),\n      value: 'catalog',\n    },\n    { color: 'default', label: $t('system.menu.typeMenu'), value: 'menu' },\n    { color: 'error', label: $t('system.menu.typeButton'), value: 'button' },\n    {\n      color: 'success',\n      label: $t('system.menu.typeEmbedded'),\n      value: 'embedded',\n    },\n    { color: 'warning', label: $t('system.menu.typeLink'), value: 'link' },\n  ];\n}\n\nexport function useColumns(\n  onActionClick: OnActionClickFn<SystemMenuApi.SystemMenu>,\n): VxeTableGridOptions<SystemMenuApi.SystemMenu>['columns'] {\n  return [\n    {\n      align: 'left',\n      field: 'meta.title',\n      fixed: 'left',\n      slots: { default: 'title' },\n      title: $t('system.menu.menuTitle'),\n      treeNode: true,\n      width: 250,\n    },\n    {\n      align: 'center',\n      cellRender: { name: 'CellTag', options: getMenuTypeOptions() },\n      field: 'type',\n      title: $t('system.menu.type'),\n      width: 100,\n    },\n    {\n      field: 'authCode',\n      title: $t('system.menu.authCode'),\n      width: 200,\n    },\n    {\n      align: 'left',\n      field: 'path',\n      title: $t('system.menu.path'),\n      width: 200,\n    },\n\n    {\n      align: 'left',\n      field: 'component',\n      formatter: ({ row }) => {\n        switch (row.type) {\n          case 'catalog':\n          case 'menu': {\n            return row.component ?? '';\n          }\n          case 'embedded': {\n            return row.meta?.iframeSrc ?? '';\n          }\n          case 'link': {\n            return row.meta?.link ?? '';\n          }\n        }\n        return '';\n      },\n      minWidth: 200,\n      title: $t('system.menu.component'),\n    },\n    {\n      cellRender: { name: 'CellTag' },\n      field: 'status',\n      title: $t('system.menu.status'),\n      width: 100,\n    },\n\n    {\n      align: 'right',\n      cellRender: {\n        attrs: {\n          nameField: 'name',\n          onClick: onActionClick,\n        },\n        name: 'CellOperation',\n        options: [\n          {\n            code: 'append',\n            text: '新增下级',\n          },\n          'edit', // 默认的编辑按钮\n          'delete', // 默认的删除按钮\n        ],\n      },\n      field: 'operation',\n      fixed: 'right',\n      headerAlign: 'center',\n      showOverflow: false,\n      title: $t('system.menu.operation'),\n      width: 200,\n    },\n  ];\n}\n"
  },
  {
    "path": "playground/src/views/system/menu/list.vue",
    "content": "<script lang=\"ts\" setup>\nimport type {\n  OnActionClickParams,\n  VxeTableGridOptions,\n} from '#/adapter/vxe-table';\n\nimport { Page, useVbenDrawer } from '@vben/common-ui';\nimport { IconifyIcon, Plus } from '@vben/icons';\nimport { $t } from '@vben/locales';\n\nimport { MenuBadge } from '@vben-core/menu-ui';\n\nimport { Button, message } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\nimport { deleteMenu, getMenuList, SystemMenuApi } from '#/api/system/menu';\n\nimport { useColumns } from './data';\nimport Form from './modules/form.vue';\n\nconst [FormDrawer, formDrawerApi] = useVbenDrawer({\n  connectedComponent: Form,\n  destroyOnClose: true,\n});\n\nconst [Grid, gridApi] = useVbenVxeGrid({\n  gridOptions: {\n    columns: useColumns(onActionClick),\n    height: 'auto',\n    keepSource: true,\n    pagerConfig: {\n      enabled: false,\n    },\n    proxyConfig: {\n      ajax: {\n        query: async (_params) => {\n          return await getMenuList();\n        },\n      },\n    },\n    rowConfig: {\n      keyField: 'id',\n    },\n    toolbarConfig: {\n      custom: true,\n      export: false,\n      refresh: true,\n      zoom: true,\n    },\n    treeConfig: {\n      parentField: 'pid',\n      rowField: 'id',\n      transform: false,\n    },\n  } as VxeTableGridOptions,\n});\n\nfunction onActionClick({\n  code,\n  row,\n}: OnActionClickParams<SystemMenuApi.SystemMenu>) {\n  switch (code) {\n    case 'append': {\n      onAppend(row);\n      break;\n    }\n    case 'delete': {\n      onDelete(row);\n      break;\n    }\n    case 'edit': {\n      onEdit(row);\n      break;\n    }\n    default: {\n      break;\n    }\n  }\n}\n\nfunction onRefresh() {\n  gridApi.query();\n}\nfunction onEdit(row: SystemMenuApi.SystemMenu) {\n  formDrawerApi.setData(row).open();\n}\nfunction onCreate() {\n  formDrawerApi.setData({}).open();\n}\nfunction onAppend(row: SystemMenuApi.SystemMenu) {\n  formDrawerApi.setData({ pid: row.id }).open();\n}\n\nfunction onDelete(row: SystemMenuApi.SystemMenu) {\n  const hideLoading = message.loading({\n    content: $t('ui.actionMessage.deleting', [row.name]),\n    duration: 0,\n    key: 'action_process_msg',\n  });\n  deleteMenu(row.id)\n    .then(() => {\n      message.success({\n        content: $t('ui.actionMessage.deleteSuccess', [row.name]),\n        key: 'action_process_msg',\n      });\n      onRefresh();\n    })\n    .catch(() => {\n      hideLoading();\n    });\n}\n</script>\n<template>\n  <Page auto-content-height>\n    <FormDrawer @success=\"onRefresh\" />\n    <Grid>\n      <template #toolbar-tools>\n        <Button type=\"primary\" @click=\"onCreate\">\n          <Plus class=\"size-5\" />\n          {{ $t('ui.actionTitle.create', [$t('system.menu.name')]) }}\n        </Button>\n      </template>\n      <template #title=\"{ row }\">\n        <div class=\"flex w-full items-center gap-1\">\n          <div class=\"size-5 shrink-0\">\n            <IconifyIcon\n              v-if=\"row.type === 'button'\"\n              icon=\"carbon:security\"\n              class=\"size-full\"\n            />\n            <IconifyIcon\n              v-else-if=\"row.meta?.icon\"\n              :icon=\"row.meta?.icon || 'carbon:circle-dash'\"\n              class=\"size-full\"\n            />\n          </div>\n          <span class=\"flex-auto\">{{ $t(row.meta?.title) }}</span>\n          <div class=\"items-center justify-end\"></div>\n        </div>\n        <MenuBadge\n          v-if=\"row.meta?.badgeType\"\n          class=\"menu-badge\"\n          :badge=\"row.meta.badge\"\n          :badge-type=\"row.meta.badgeType\"\n          :badge-variants=\"row.meta.badgeVariants\"\n        />\n      </template>\n    </Grid>\n  </Page>\n</template>\n<style lang=\"scss\" scoped>\n.menu-badge {\n  top: 50%;\n  right: 0;\n  transform: translateY(-50%);\n\n  & > :deep(div) {\n    padding-top: 0;\n    padding-bottom: 0;\n  }\n}\n</style>\n"
  },
  {
    "path": "playground/src/views/system/menu/modules/form.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { ChangeEvent } from 'ant-design-vue/es/_util/EventInterface';\n\nimport type { Recordable } from '@vben/types';\n\nimport type { VbenFormSchema } from '#/adapter/form';\n\nimport { computed, h, ref } from 'vue';\n\nimport { useVbenDrawer } from '@vben/common-ui';\nimport { IconifyIcon } from '@vben/icons';\nimport { $te } from '@vben/locales';\nimport { getPopupContainer } from '@vben/utils';\n\nimport { breakpointsTailwind, useBreakpoints } from '@vueuse/core';\n\nimport { useVbenForm, z } from '#/adapter/form';\nimport {\n  createMenu,\n  getMenuList,\n  isMenuNameExists,\n  isMenuPathExists,\n  SystemMenuApi,\n  updateMenu,\n} from '#/api/system/menu';\nimport { $t } from '#/locales';\nimport { componentKeys } from '#/router/routes';\n\nimport { getMenuTypeOptions } from '../data';\n\nconst emit = defineEmits<{\n  success: [];\n}>();\nconst formData = ref<SystemMenuApi.SystemMenu>();\nconst titleSuffix = ref<string>();\nconst schema: VbenFormSchema[] = [\n  {\n    component: 'RadioGroup',\n    componentProps: {\n      buttonStyle: 'solid',\n      options: getMenuTypeOptions(),\n      optionType: 'button',\n    },\n    defaultValue: 'menu',\n    fieldName: 'type',\n    formItemClass: 'col-span-2 md:col-span-2',\n    label: $t('system.menu.type'),\n  },\n  {\n    component: 'Input',\n    fieldName: 'name',\n    label: $t('system.menu.menuName'),\n    rules: z\n      .string()\n      .min(2, $t('ui.formRules.minLength', [$t('system.menu.menuName'), 2]))\n      .max(30, $t('ui.formRules.maxLength', [$t('system.menu.menuName'), 30]))\n      .refine(\n        async (value: string) => {\n          return !(await isMenuNameExists(value, formData.value?.id));\n        },\n        (value) => ({\n          message: $t('ui.formRules.alreadyExists', [\n            $t('system.menu.menuName'),\n            value,\n          ]),\n        }),\n      ),\n  },\n  {\n    component: 'ApiTreeSelect',\n    componentProps: {\n      api: getMenuList,\n      class: 'w-full',\n      filterTreeNode(input: string, node: Recordable<any>) {\n        if (!input || input.length === 0) {\n          return true;\n        }\n        const title: string = node.meta?.title ?? '';\n        if (!title) return false;\n        return title.includes(input) || $t(title).includes(input);\n      },\n      getPopupContainer,\n      labelField: 'meta.title',\n      showSearch: true,\n      treeDefaultExpandAll: true,\n      valueField: 'id',\n      childrenField: 'children',\n    },\n    fieldName: 'pid',\n    label: $t('system.menu.parent'),\n    renderComponentContent() {\n      return {\n        title({ label, meta }: { label: string; meta: Recordable<any> }) {\n          const coms = [];\n          if (!label) return '';\n          if (meta?.icon) {\n            coms.push(h(IconifyIcon, { class: 'size-4', icon: meta.icon }));\n          }\n          coms.push(h('span', { class: '' }, $t(label || '')));\n          return h('div', { class: 'flex items-center gap-1' }, coms);\n        },\n      };\n    },\n  },\n  {\n    component: 'Input',\n    componentProps() {\n      // 不需要处理多语言时就无需这么做\n      return {\n        ...(titleSuffix.value && { addonAfter: titleSuffix.value }),\n        onChange({ target: { value } }: ChangeEvent) {\n          titleSuffix.value = value && $te(value) ? $t(value) : undefined;\n        },\n      };\n    },\n    fieldName: 'meta.title',\n    label: $t('system.menu.menuTitle'),\n    rules: 'required',\n  },\n  {\n    component: 'Input',\n    dependencies: {\n      show: (values) => {\n        return ['catalog', 'embedded', 'menu'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'path',\n    label: $t('system.menu.path'),\n    rules: z\n      .string()\n      .min(2, $t('ui.formRules.minLength', [$t('system.menu.path'), 2]))\n      .max(100, $t('ui.formRules.maxLength', [$t('system.menu.path'), 100]))\n      .refine(\n        (value: string) => {\n          return value.startsWith('/');\n        },\n        $t('ui.formRules.startWith', [$t('system.menu.path'), '/']),\n      )\n      .refine(\n        async (value: string) => {\n          return !(await isMenuPathExists(value, formData.value?.id));\n        },\n        (value) => ({\n          message: $t('ui.formRules.alreadyExists', [\n            $t('system.menu.path'),\n            value,\n          ]),\n        }),\n      ),\n  },\n  {\n    component: 'Input',\n    dependencies: {\n      show: (values) => {\n        return ['embedded', 'menu'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'activePath',\n    help: $t('system.menu.activePathHelp'),\n    label: $t('system.menu.activePath'),\n    rules: z\n      .string()\n      .min(2, $t('ui.formRules.minLength', [$t('system.menu.path'), 2]))\n      .max(100, $t('ui.formRules.maxLength', [$t('system.menu.path'), 100]))\n      .refine(\n        (value: string) => {\n          return value.startsWith('/');\n        },\n        $t('ui.formRules.startWith', [$t('system.menu.path'), '/']),\n      )\n      .refine(async (value: string) => {\n        return await isMenuPathExists(value, formData.value?.id);\n      }, $t('system.menu.activePathMustExist'))\n      .optional(),\n  },\n  {\n    component: 'IconPicker',\n    componentProps: {\n      prefix: 'carbon',\n    },\n    dependencies: {\n      show: (values) => {\n        return ['catalog', 'embedded', 'link', 'menu'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.icon',\n    label: $t('system.menu.icon'),\n  },\n  {\n    component: 'IconPicker',\n    componentProps: {\n      prefix: 'carbon',\n    },\n    dependencies: {\n      show: (values) => {\n        return ['catalog', 'embedded', 'menu'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.activeIcon',\n    label: $t('system.menu.activeIcon'),\n  },\n  {\n    component: 'AutoComplete',\n    componentProps: {\n      allowClear: true,\n      class: 'w-full',\n      filterOption(input: string, option: { value: string }) {\n        return option.value.toLowerCase().includes(input.toLowerCase());\n      },\n      options: componentKeys.map((v) => ({ value: v })),\n    },\n    dependencies: {\n      rules: (values) => {\n        return values.type === 'menu' ? 'required' : null;\n      },\n      show: (values) => {\n        return values.type === 'menu';\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'component',\n    label: $t('system.menu.component'),\n  },\n  {\n    component: 'Input',\n    dependencies: {\n      show: (values) => {\n        return ['embedded', 'link'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'linkSrc',\n    label: $t('system.menu.linkSrc'),\n    rules: z.string().url($t('ui.formRules.invalidURL')),\n  },\n  {\n    component: 'Input',\n    dependencies: {\n      rules: (values) => {\n        return values.type === 'button' ? 'required' : null;\n      },\n      show: (values) => {\n        return ['button', 'catalog', 'embedded', 'menu'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'authCode',\n    label: $t('system.menu.authCode'),\n  },\n  {\n    component: 'RadioGroup',\n    componentProps: {\n      buttonStyle: 'solid',\n      options: [\n        { label: $t('common.enabled'), value: 1 },\n        { label: $t('common.disabled'), value: 0 },\n      ],\n      optionType: 'button',\n    },\n    defaultValue: 1,\n    fieldName: 'status',\n    label: $t('system.menu.status'),\n  },\n  {\n    component: 'Select',\n    componentProps: {\n      allowClear: true,\n      class: 'w-full',\n      options: [\n        { label: $t('system.menu.badgeType.dot'), value: 'dot' },\n        { label: $t('system.menu.badgeType.normal'), value: 'normal' },\n      ],\n    },\n    dependencies: {\n      show: (values) => {\n        return values.type !== 'button';\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.badgeType',\n    label: $t('system.menu.badgeType.title'),\n  },\n  {\n    component: 'Input',\n    componentProps: (values) => {\n      return {\n        allowClear: true,\n        class: 'w-full',\n        disabled: values.meta?.badgeType !== 'normal',\n      };\n    },\n    dependencies: {\n      show: (values) => {\n        return values.type !== 'button';\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.badge',\n    label: $t('system.menu.badge'),\n  },\n  {\n    component: 'Select',\n    componentProps: {\n      allowClear: true,\n      class: 'w-full',\n      options: SystemMenuApi.BadgeVariants.map((v) => ({\n        label: v,\n        value: v,\n      })),\n    },\n    dependencies: {\n      show: (values) => {\n        return values.type !== 'button';\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.badgeVariants',\n    label: $t('system.menu.badgeVariants'),\n  },\n  {\n    component: 'Divider',\n    dependencies: {\n      show: (values) => {\n        return !['button', 'link'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'divider1',\n    formItemClass: 'col-span-2 md:col-span-2 pb-0',\n    hideLabel: true,\n    renderComponentContent() {\n      return {\n        default: () => $t('system.menu.advancedSettings'),\n      };\n    },\n  },\n  {\n    component: 'Checkbox',\n    dependencies: {\n      show: (values) => {\n        return ['menu'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.keepAlive',\n    renderComponentContent() {\n      return {\n        default: () => $t('system.menu.keepAlive'),\n      };\n    },\n  },\n  {\n    component: 'Checkbox',\n    dependencies: {\n      show: (values) => {\n        return ['embedded', 'menu'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.affixTab',\n    renderComponentContent() {\n      return {\n        default: () => $t('system.menu.affixTab'),\n      };\n    },\n  },\n  {\n    component: 'Checkbox',\n    dependencies: {\n      show: (values) => {\n        return !['button'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.hideInMenu',\n    renderComponentContent() {\n      return {\n        default: () => $t('system.menu.hideInMenu'),\n      };\n    },\n  },\n  {\n    component: 'Checkbox',\n    dependencies: {\n      show: (values) => {\n        return ['catalog', 'menu'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.hideChildrenInMenu',\n    renderComponentContent() {\n      return {\n        default: () => $t('system.menu.hideChildrenInMenu'),\n      };\n    },\n  },\n  {\n    component: 'Checkbox',\n    dependencies: {\n      show: (values) => {\n        return !['button', 'link'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.hideInBreadcrumb',\n    renderComponentContent() {\n      return {\n        default: () => $t('system.menu.hideInBreadcrumb'),\n      };\n    },\n  },\n  {\n    component: 'Checkbox',\n    dependencies: {\n      show: (values) => {\n        return !['button', 'link'].includes(values.type);\n      },\n      triggerFields: ['type'],\n    },\n    fieldName: 'meta.hideInTab',\n    renderComponentContent() {\n      return {\n        default: () => $t('system.menu.hideInTab'),\n      };\n    },\n  },\n];\n\nconst breakpoints = useBreakpoints(breakpointsTailwind);\nconst isHorizontal = computed(() => breakpoints.greaterOrEqual('md').value);\n\nconst [Form, formApi] = useVbenForm({\n  commonConfig: {\n    colon: true,\n    formItemClass: 'col-span-2 md:col-span-1',\n  },\n  schema,\n  showDefaultActions: false,\n  wrapperClass: 'grid-cols-2 gap-x-4',\n});\nconst [Drawer, drawerApi] = useVbenDrawer({\n  onConfirm: onSubmit,\n  onOpenChange(isOpen) {\n    if (isOpen) {\n      const data = drawerApi.getData<SystemMenuApi.SystemMenu>();\n      if (data?.type === 'link') {\n        data.linkSrc = data.meta?.link;\n      } else if (data?.type === 'embedded') {\n        data.linkSrc = data.meta?.iframeSrc;\n      }\n      if (data) {\n        formData.value = data;\n        formApi.setValues(formData.value);\n        titleSuffix.value = formData.value.meta?.title\n          ? $t(formData.value.meta.title)\n          : '';\n      } else {\n        formApi.resetForm();\n        titleSuffix.value = '';\n      }\n    }\n  },\n});\n\nasync function onSubmit() {\n  const { valid } = await formApi.validate();\n  if (valid) {\n    drawerApi.lock();\n    const data =\n      await formApi.getValues<\n        Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>\n      >();\n    if (data.type === 'link') {\n      data.meta = { ...data.meta, link: data.linkSrc };\n    } else if (data.type === 'embedded') {\n      data.meta = { ...data.meta, iframeSrc: data.linkSrc };\n    }\n    delete data.linkSrc;\n    try {\n      await (formData.value?.id\n        ? updateMenu(formData.value.id, data)\n        : createMenu(data));\n      drawerApi.close();\n      emit('success');\n    } finally {\n      drawerApi.unlock();\n    }\n  }\n}\nconst getDrawerTitle = computed(() =>\n  formData.value?.id\n    ? $t('ui.actionTitle.edit', [$t('system.menu.name')])\n    : $t('ui.actionTitle.create', [$t('system.menu.name')]),\n);\n</script>\n<template>\n  <Drawer class=\"w-full max-w-200\" :title=\"getDrawerTitle\">\n    <Form class=\"mx-4\" :layout=\"isHorizontal ? 'horizontal' : 'vertical'\" />\n  </Drawer>\n</template>\n"
  },
  {
    "path": "playground/src/views/system/role/data.ts",
    "content": "import type { VbenFormSchema } from '#/adapter/form';\nimport type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';\nimport type { SystemRoleApi } from '#/api';\n\nimport { $t } from '#/locales';\n\nexport function useFormSchema(): VbenFormSchema[] {\n  return [\n    {\n      component: 'Input',\n      fieldName: 'name',\n      label: $t('system.role.roleName'),\n      rules: 'required',\n    },\n    {\n      component: 'RadioGroup',\n      componentProps: {\n        buttonStyle: 'solid',\n        options: [\n          { label: $t('common.enabled'), value: 1 },\n          { label: $t('common.disabled'), value: 0 },\n        ],\n        optionType: 'button',\n      },\n      defaultValue: 1,\n      fieldName: 'status',\n      label: $t('system.role.status'),\n    },\n    {\n      component: 'Textarea',\n      fieldName: 'remark',\n      label: $t('system.role.remark'),\n    },\n    {\n      component: 'Input',\n      fieldName: 'permissions',\n      formItemClass: 'items-start',\n      label: $t('system.role.setPermissions'),\n      modelPropName: 'modelValue',\n    },\n  ];\n}\n\nexport function useGridFormSchema(): VbenFormSchema[] {\n  return [\n    {\n      component: 'Input',\n      fieldName: 'name',\n      label: $t('system.role.roleName'),\n    },\n    { component: 'Input', fieldName: 'id', label: $t('system.role.id') },\n    {\n      component: 'Select',\n      componentProps: {\n        allowClear: true,\n        options: [\n          { label: $t('common.enabled'), value: 1 },\n          { label: $t('common.disabled'), value: 0 },\n        ],\n      },\n      fieldName: 'status',\n      label: $t('system.role.status'),\n    },\n    {\n      component: 'Input',\n      fieldName: 'remark',\n      label: $t('system.role.remark'),\n    },\n    {\n      component: 'RangePicker',\n      fieldName: 'createTime',\n      label: $t('system.role.createTime'),\n    },\n  ];\n}\n\nexport function useColumns<T = SystemRoleApi.SystemRole>(\n  onActionClick: OnActionClickFn<T>,\n  onStatusChange?: (newStatus: any, row: T) => PromiseLike<boolean | undefined>,\n): VxeTableGridOptions['columns'] {\n  return [\n    {\n      field: 'name',\n      title: $t('system.role.roleName'),\n      width: 200,\n    },\n    {\n      field: 'id',\n      title: $t('system.role.id'),\n      width: 200,\n    },\n    {\n      cellRender: {\n        attrs: { beforeChange: onStatusChange },\n        name: onStatusChange ? 'CellSwitch' : 'CellTag',\n      },\n      field: 'status',\n      title: $t('system.role.status'),\n      width: 100,\n    },\n    {\n      field: 'remark',\n      minWidth: 100,\n      title: $t('system.role.remark'),\n    },\n    {\n      field: 'createTime',\n      title: $t('system.role.createTime'),\n      width: 200,\n    },\n    {\n      align: 'center',\n      cellRender: {\n        attrs: {\n          nameField: 'name',\n          nameTitle: $t('system.role.name'),\n          onClick: onActionClick,\n        },\n        name: 'CellOperation',\n      },\n      field: 'operation',\n      fixed: 'right',\n      title: $t('system.role.operation'),\n      width: 130,\n    },\n  ];\n}\n"
  },
  {
    "path": "playground/src/views/system/role/list.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { Recordable } from '@vben/types';\n\nimport type {\n  OnActionClickParams,\n  VxeTableGridOptions,\n} from '#/adapter/vxe-table';\nimport type { SystemRoleApi } from '#/api';\n\nimport { Page, useVbenDrawer } from '@vben/common-ui';\nimport { Plus } from '@vben/icons';\n\nimport { Button, message, Modal } from 'ant-design-vue';\n\nimport { useVbenVxeGrid } from '#/adapter/vxe-table';\nimport { deleteRole, getRoleList, updateRole } from '#/api';\nimport { $t } from '#/locales';\n\nimport { useColumns, useGridFormSchema } from './data';\nimport Form from './modules/form.vue';\n\nconst [FormDrawer, formDrawerApi] = useVbenDrawer({\n  connectedComponent: Form,\n  destroyOnClose: true,\n});\n\nconst [Grid, gridApi] = useVbenVxeGrid({\n  formOptions: {\n    fieldMappingTime: [['createTime', ['startTime', 'endTime']]],\n    schema: useGridFormSchema(),\n    submitOnChange: true,\n  },\n  gridOptions: {\n    columns: useColumns(onActionClick, onStatusChange),\n    height: 'auto',\n    keepSource: true,\n    proxyConfig: {\n      ajax: {\n        query: async ({ page }, formValues) => {\n          return await getRoleList({\n            page: page.currentPage,\n            pageSize: page.pageSize,\n            ...formValues,\n          });\n        },\n      },\n    },\n    rowConfig: {\n      keyField: 'id',\n    },\n\n    toolbarConfig: {\n      custom: true,\n      export: false,\n      refresh: true,\n      search: true,\n      zoom: true,\n    },\n  } as VxeTableGridOptions<SystemRoleApi.SystemRole>,\n});\n\nfunction onActionClick(e: OnActionClickParams<SystemRoleApi.SystemRole>) {\n  switch (e.code) {\n    case 'delete': {\n      onDelete(e.row);\n      break;\n    }\n    case 'edit': {\n      onEdit(e.row);\n      break;\n    }\n  }\n}\n\n/**\n * 将Antd的Modal.confirm封装为promise，方便在异步函数中调用。\n * @param content 提示内容\n * @param title 提示标题\n */\nfunction confirm(content: string, title: string) {\n  return new Promise((reslove, reject) => {\n    Modal.confirm({\n      content,\n      onCancel() {\n        reject(new Error('已取消'));\n      },\n      onOk() {\n        reslove(true);\n      },\n      title,\n    });\n  });\n}\n\n/**\n * 状态开关即将改变\n * @param newStatus 期望改变的状态值\n * @param row 行数据\n * @returns 返回false则中止改变，返回其他值（undefined、true）则允许改变\n */\nasync function onStatusChange(\n  newStatus: number,\n  row: SystemRoleApi.SystemRole,\n) {\n  const status: Recordable<string> = {\n    0: '禁用',\n    1: '启用',\n  };\n  try {\n    await confirm(\n      `你要将${row.name}的状态切换为 【${status[newStatus.toString()]}】 吗？`,\n      `切换状态`,\n    );\n    await updateRole(row.id, { status: newStatus });\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nfunction onEdit(row: SystemRoleApi.SystemRole) {\n  formDrawerApi.setData(row).open();\n}\n\nfunction onDelete(row: SystemRoleApi.SystemRole) {\n  const hideLoading = message.loading({\n    content: $t('ui.actionMessage.deleting', [row.name]),\n    duration: 0,\n    key: 'action_process_msg',\n  });\n  deleteRole(row.id)\n    .then(() => {\n      message.success({\n        content: $t('ui.actionMessage.deleteSuccess', [row.name]),\n        key: 'action_process_msg',\n      });\n      onRefresh();\n    })\n    .catch(() => {\n      hideLoading();\n    });\n}\n\nfunction onRefresh() {\n  gridApi.query();\n}\n\nfunction onCreate() {\n  formDrawerApi.setData({}).open();\n}\n</script>\n<template>\n  <Page auto-content-height>\n    <FormDrawer @success=\"onRefresh\" />\n    <Grid :table-title=\"$t('system.role.list')\">\n      <template #toolbar-tools>\n        <Button type=\"primary\" @click=\"onCreate\">\n          <Plus class=\"size-5\" />\n          {{ $t('ui.actionTitle.create', [$t('system.role.name')]) }}\n        </Button>\n      </template>\n    </Grid>\n  </Page>\n</template>\n"
  },
  {
    "path": "playground/src/views/system/role/modules/form.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { DataNode } from 'ant-design-vue/es/tree';\n\nimport type { Recordable } from '@vben/types';\n\nimport type { SystemRoleApi } from '#/api/system/role';\n\nimport { computed, nextTick, ref } from 'vue';\n\nimport { Tree, useVbenDrawer } from '@vben/common-ui';\nimport { IconifyIcon } from '@vben/icons';\n\nimport { Spin } from 'ant-design-vue';\n\nimport { useVbenForm } from '#/adapter/form';\nimport { getMenuList } from '#/api/system/menu';\nimport { createRole, updateRole } from '#/api/system/role';\nimport { $t } from '#/locales';\n\nimport { useFormSchema } from '../data';\n\nconst emits = defineEmits(['success']);\n\nconst formData = ref<SystemRoleApi.SystemRole>();\n\nconst [Form, formApi] = useVbenForm({\n  schema: useFormSchema(),\n  showDefaultActions: false,\n});\n\nconst permissions = ref<DataNode[]>([]);\nconst loadingPermissions = ref(false);\n\nconst id = ref();\nconst [Drawer, drawerApi] = useVbenDrawer({\n  async onConfirm() {\n    const { valid } = await formApi.validate();\n    if (!valid) return;\n    const values = await formApi.getValues();\n    drawerApi.lock();\n    (id.value ? updateRole(id.value, values) : createRole(values))\n      .then(() => {\n        emits('success');\n        drawerApi.close();\n      })\n      .catch(() => {\n        drawerApi.unlock();\n      });\n  },\n\n  async onOpenChange(isOpen) {\n    if (isOpen) {\n      const data = drawerApi.getData<SystemRoleApi.SystemRole>();\n      formApi.resetForm();\n\n      if (data) {\n        formData.value = data;\n        id.value = data.id;\n      } else {\n        id.value = undefined;\n      }\n\n      if (permissions.value.length === 0) {\n        await loadPermissions();\n      }\n      // Wait for Vue to flush DOM updates (form fields mounted)\n      await nextTick();\n      if (data) {\n        formApi.setValues(data);\n      }\n    }\n  },\n});\n\nasync function loadPermissions() {\n  loadingPermissions.value = true;\n  try {\n    const res = await getMenuList();\n    permissions.value = res as unknown as DataNode[];\n  } finally {\n    loadingPermissions.value = false;\n  }\n}\n\nconst getDrawerTitle = computed(() => {\n  return formData.value?.id\n    ? $t('common.edit', $t('system.role.name'))\n    : $t('common.create', $t('system.role.name'));\n});\n\nfunction getNodeClass(node: Recordable<any>) {\n  const classes: string[] = [];\n  if (node.value?.type === 'button') {\n    classes.push('inline-flex');\n  }\n\n  return classes.join(' ');\n}\n</script>\n<template>\n  <Drawer :title=\"getDrawerTitle\">\n    <Form>\n      <template #permissions=\"slotProps\">\n        <Spin :spinning=\"loadingPermissions\" wrapper-class-name=\"w-full\">\n          <Tree\n            :tree-data=\"permissions\"\n            multiple\n            bordered\n            :default-expanded-level=\"2\"\n            :get-node-class=\"getNodeClass\"\n            v-bind=\"slotProps\"\n            value-field=\"id\"\n            label-field=\"meta.title\"\n            icon-field=\"meta.icon\"\n          >\n            <template #node=\"{ value }\">\n              <IconifyIcon v-if=\"value.meta.icon\" :icon=\"value.meta.icon\" />\n              {{ $t(value.meta.title) }}\n            </template>\n          </Tree>\n        </Spin>\n      </template>\n    </Form>\n  </Drawer>\n</template>\n<style lang=\"css\" scoped>\n:deep(.ant-tree-title) {\n  .tree-actions {\n    @apply ml-5 hidden;\n  }\n}\n\n:deep(.ant-tree-title:hover) {\n  .tree-actions {\n    @apply ml-5 flex flex-auto justify-end;\n  }\n}\n</style>\n"
  },
  {
    "path": "playground/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/web-app.json\",\n  \"compilerOptions\": {\n    \"paths\": {\n      \"#/*\": [\"./src/*\"]\n    }\n  },\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }],\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\", \"src/**/*.vue\"]\n}\n"
  },
  {
    "path": "playground/tsconfig.node.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"noEmit\": false\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "playground/vite.config.ts",
    "content": "import { defineConfig } from '@vben/vite-config';\n\nexport default defineConfig(async () => {\n  return {\n    application: {},\n    vite: {\n      server: {\n        proxy: {\n          '/api': {\n            changeOrigin: true,\n            rewrite: (path) => path.replace(/^\\/api/, ''),\n            // mock代理目标地址\n            target: 'http://localhost:5320/api',\n            ws: true,\n          },\n        },\n      },\n    },\n  };\n});\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - internal/*\n  - internal/lint-configs/*\n  - packages/*\n  - packages/@core/base/*\n  - packages/@core/ui-kit/*\n  - packages/@core/forward/*\n  - packages/@core/*\n  - packages/effects/*\n  - packages/business/*\n  - apps/*\n  - scripts/*\n  - docs\n  - playground\n\noverrides:\n  '@ast-grep/napi': 'catalog:'\n  '@ctrl/tinycolor': 'catalog:'\n  clsx: 'catalog:'\n  pinia: 'catalog:'\n  vue: 'catalog:'\ncatalog:\n  '@ast-grep/napi': ^0.42.0\n  '@changesets/changelog-github': ^0.6.0\n  '@changesets/cli': ^2.30.0\n  '@changesets/git': ^3.0.4\n  '@clack/prompts': ^1.1.0\n  '@commitlint/cli': ^20.5.0\n  '@commitlint/config-conventional': ^20.5.0\n  '@ctrl/tinycolor': ^4.2.0\n  '@eslint-community/eslint-plugin-eslint-comments': ^4.7.1\n  '@eslint/js': ^10.0.1\n  '@faker-js/faker': ^10.3.0\n  '@iconify/json': ^2.2.451\n  '@iconify/tailwind4': ^1.2.3\n  '@iconify/vue': ^5.0.0\n  '@intlify/core-base': ^11.3.0\n  '@intlify/unplugin-vue-i18n': ^11.0.7\n  '@jspm/generator': ^2.12.0\n  '@manypkg/get-packages': ^3.1.0\n  '@nolebase/vitepress-plugin-git-changelog': ^2.18.2\n  '@playwright/test': ^1.58.2\n  '@pnpm/workspace.read-manifest': ^1000.3.0\n  '@stylistic/stylelint-plugin': ^5.0.1\n  '@tailwindcss/typography': ^0.5.19\n  '@tailwindcss/vite': ^4.2.2\n  '@tanstack/vue-query': ^5.92.10\n  '@tanstack/vue-store': ^0.9.2\n  '@tsdown/css': ^0.21.4\n  '@types/archiver': ^7.0.0\n  '@types/html-minifier-terser': ^7.0.2\n  '@types/json-bigint': ^1.0.4\n  '@types/jsonwebtoken': ^9.0.10\n  '@types/lodash.clonedeep': ^4.5.9\n  '@types/node': ^25.5.0\n  '@types/nprogress': ^0.2.3\n  '@types/qrcode': ^1.5.6\n  '@types/qs': ^6.15.0\n  '@types/sortablejs': ^1.15.9\n  '@typescript-eslint/eslint-plugin': ^8.57.1\n  '@typescript-eslint/parser': ^8.57.1\n  '@vee-validate/zod': ^4.15.1\n  '@vite-pwa/vitepress': ^1.1.0\n  '@vitejs/plugin-vue': ^6.0.5\n  '@vitejs/plugin-vue-jsx': ^5.1.5\n  '@vue/shared': ^3.5.30\n  '@vue/test-utils': ^2.4.6\n  '@vueuse/core': ^14.2.1\n  '@vueuse/integrations': ^14.2.1\n  '@vueuse/motion': ^3.0.3\n  ant-design-vue: ^4.2.6\n  antdv-next: ^1.1.5\n  archiver: ^7.0.1\n  axios: ^1.13.6\n  axios-mock-adapter: ^2.1.0\n  cac: ^7.0.0\n  chalk: ^5.6.2\n  cheerio: ^1.2.0\n  circular-dependency-scanner: ^3.0.1\n  class-variance-authority: ^0.7.1\n  clsx: ^2.1.1\n  commitlint-plugin-function-rules: ^4.3.2\n  consola: ^3.4.2\n  cross-env: ^10.1.0\n  cspell: ^9.7.0\n  cz-git: ^1.12.0\n  czg: ^1.12.0\n  dayjs: ^1.11.20\n  defu: ^6.1.4\n  depcheck: ^1.4.7\n  dotenv: ^17.3.1\n  echarts: ^6.0.0\n  element-plus: ^2.13.5\n  es-toolkit: ^1.45.1\n  eslint: ^10.0.3\n  eslint-plugin-better-tailwindcss: ^4.3.2\n  eslint-plugin-command: ^3.5.2\n  eslint-plugin-jsonc: ^3.1.2\n  eslint-plugin-n: ^17.24.0\n  eslint-plugin-perfectionist: ^5.7.0\n  eslint-plugin-pnpm: ^1.6.0\n  eslint-plugin-unicorn: ^63.0.0\n  eslint-plugin-unused-imports: ^4.4.1\n  eslint-plugin-vue: ^10.8.0\n  eslint-plugin-yml: ^3.3.1\n  execa: ^9.6.1\n  find-up: ^8.0.0\n  get-port: ^7.1.0\n  globals: ^17.4.0\n  h3: ^1.15.8\n  happy-dom: ^20.8.4\n  html-minifier-terser: ^7.2.0\n  is-ci: ^4.1.0\n  json-bigint: ^1.0.0\n  jsonwebtoken: ^9.0.3\n  lefthook: ^2.1.4\n  lodash.clonedeep: ^4.5.0\n  lucide-vue-next: ^0.577.0\n  medium-zoom: ^1.1.0\n  naive-ui: ^2.44.1\n  nitropack: ^2.13.1\n  nprogress: ^0.2.0\n  ora: ^9.3.0\n  oxfmt: ^0.41.0\n  oxlint: ^1.56.0\n  oxlint-tsgolint: ^0.17.0\n  pinia: ^3.0.4\n  pinia-plugin-persistedstate: ^4.7.1\n  pkg-types: ^2.3.0\n  playwright: ^1.58.2\n  postcss: ^8.5.8\n  postcss-html: ^1.8.1\n  postcss-scss: ^4.0.9\n  publint: ^0.3.18\n  qrcode: ^1.5.4\n  qs: ^6.15.0\n  reka-ui: ^2.9.2\n  resolve.exports: ^2.0.3\n  rimraf: ^6.1.3\n  rollup-plugin-visualizer: ^7.0.1\n  sass: ^1.98.0\n  sass-embedded: ^1.98.0\n  secure-ls: ^2.0.0\n  sortablejs: ^1.15.7\n  stylelint: ^17.4.0\n  stylelint-config-recess-order: ^7.7.0\n  stylelint-config-recommended: ^18.0.0\n  stylelint-config-recommended-scss: ^17.0.0\n  stylelint-config-recommended-vue: ^1.6.1\n  stylelint-config-standard: ^40.0.0\n  stylelint-order: ^8.1.1\n  stylelint-scss: ^7.0.0\n  tailwind-merge: ^3.5.0\n  tailwindcss: ^4.2.2\n  tdesign-vue-next: ^1.18.5\n  theme-colors: ^0.1.0\n  tippy.js: ^6.3.7\n  tsdown: ^0.21.4\n  turbo: ^2.8.19\n  tw-animate-css: ^1.4.0\n  typescript: ^5.9.3\n  unplugin-dts: ^1.0.0-beta.6\n  unplugin-element-plus: ^0.11.2\n  unplugin-vue: ^7.1.1\n  vee-validate: ^4.15.1\n  vite: ^8.0.0\n  vite-plugin-compression: ^0.5.1\n  vite-plugin-html: ^3.2.2\n  vite-plugin-lazy-import: ^1.0.7\n  vite-plugin-pwa: ^1.2.0\n  vite-plugin-vue-devtools: ^8.1.0\n  vitepress: ^1.6.4\n  vitepress-plugin-group-icons: ^1.7.1\n  vitest: ^4.1.0\n  vue: ^3.5.30\n  vue-eslint-parser: ^10.4.0\n  vue-i18n: ^11.3.0\n  vue-json-viewer: ^3.0.4\n  vue-router: ^5.0.3\n  vue-tippy: ^6.7.1\n  vue-tsc: ^3.2.6\n  vxe-pc-ui: ^4.13.10\n  vxe-table: ^4.18.5\n  watermark-js-plus: ^1.6.3\n  yaml-eslint-parser: ^2.0.0\n  zod: ^3.25.76\n  zod-defaults: 0.1.3\n"
  },
  {
    "path": "scripts/clean.mjs",
    "content": "import { promises as fs } from 'node:fs';\nimport { join, normalize } from 'node:path';\n\nconst rootDir = process.cwd();\n\n// 控制并发数量，避免创建过多的并发任务\nconst CONCURRENCY_LIMIT = 10;\n\n// 需要跳过的目录，避免进入这些目录进行清理\nconst SKIP_DIRS = new Set(['.DS_Store', '.git', '.idea', '.vscode']);\n\n/**\n * 处理单个文件/目录项\n * @param {string} currentDir - 当前目录路径\n * @param {string} item - 文件/目录名\n * @param {string[]} targets - 要删除的目标列表\n * @param {number} _depth - 当前递归深度\n * @returns {Promise<boolean>} - 是否需要进一步递归处理\n */\nasync function processItem(currentDir, item, targets, _depth) {\n  // 跳过特殊目录\n  if (SKIP_DIRS.has(item)) {\n    return false;\n  }\n\n  try {\n    const itemPath = normalize(join(currentDir, item));\n\n    if (targets.includes(item)) {\n      // 匹配到目标目录或文件时直接删除\n      await fs.rm(itemPath, { force: true, recursive: true });\n      console.log(`✅ Deleted: ${itemPath}`);\n      return false; // 已删除，无需递归\n    }\n\n    // 使用 readdir 的 withFileTypes 选项，避免额外的 lstat 调用\n    return true; // 可能需要递归，由调用方决定\n  } catch (error) {\n    // 更详细的错误信息\n    if (error.code === 'ENOENT') {\n      // 文件不存在，可能已被删除，这是正常情况\n      return false;\n    } else if (error.code === 'EPERM' || error.code === 'EACCES') {\n      console.error(`❌ Permission denied: ${item} in ${currentDir}`);\n    } else {\n      console.error(\n        `❌ Error handling item ${item} in ${currentDir}: ${error.message}`,\n      );\n    }\n    return false;\n  }\n}\n\n/**\n * 递归查找并删除目标目录（并发优化版本）\n * @param {string} currentDir - 当前遍历的目录路径\n * @param {string[]} targets - 要删除的目标列表\n * @param {number} depth - 当前递归深度，避免过深递归\n */\nasync function cleanTargetsRecursively(currentDir, targets, depth = 0) {\n  // 限制递归深度，避免无限递归\n  if (depth > 10) {\n    console.warn(`Max recursion depth reached at: ${currentDir}`);\n    return;\n  }\n\n  let dirents;\n  try {\n    // 使用 withFileTypes 选项，一次性获取文件类型信息，避免后续 lstat 调用\n    dirents = await fs.readdir(currentDir, { withFileTypes: true });\n  } catch (error) {\n    // 如果无法读取目录，可能已被删除或权限不足\n    console.warn(`Cannot read directory ${currentDir}: ${error.message}`);\n    return;\n  }\n\n  // 分批处理，控制并发数量\n  for (let i = 0; i < dirents.length; i += CONCURRENCY_LIMIT) {\n    const batch = dirents.slice(i, i + CONCURRENCY_LIMIT);\n\n    const tasks = batch.map(async (dirent) => {\n      const item = dirent.name;\n      const shouldRecurse = await processItem(currentDir, item, targets, depth);\n\n      // 如果是目录且没有被删除，则递归处理\n      if (shouldRecurse && dirent.isDirectory()) {\n        const itemPath = normalize(join(currentDir, item));\n        return cleanTargetsRecursively(itemPath, targets, depth + 1);\n      }\n\n      return null;\n    });\n\n    // 并发执行当前批次的任务\n    const results = await Promise.allSettled(tasks);\n\n    // 检查是否有失败的任务（可选：用于调试）\n    const failedTasks = results.filter(\n      (result) => result.status === 'rejected',\n    );\n    if (failedTasks.length > 0) {\n      console.warn(\n        `${failedTasks.length} tasks failed in batch starting at index ${i} in directory: ${currentDir}`,\n      );\n    }\n  }\n}\n\n(async function startCleanup() {\n  // 要删除的目录及文件名称\n  const targets = ['node_modules', 'dist', '.turbo', 'dist.zip'];\n  const deleteLockFile = process.argv.includes('--del-lock');\n  const cleanupTargets = [...targets];\n\n  if (deleteLockFile) {\n    cleanupTargets.push('pnpm-lock.yaml');\n  }\n\n  console.log(\n    `🚀 Starting cleanup of targets: ${cleanupTargets.join(', ')} from root: ${rootDir}`,\n  );\n\n  const startTime = Date.now();\n\n  try {\n    // 先统计要删除的目标数量\n    console.log('📊 Scanning for cleanup targets...');\n\n    await cleanTargetsRecursively(rootDir, cleanupTargets);\n\n    const endTime = Date.now();\n    const duration = (endTime - startTime) / 1000;\n\n    console.log(\n      `✨ Cleanup process completed successfully in ${duration.toFixed(2)}s`,\n    );\n  } catch (error) {\n    console.error(`💥 Unexpected error during cleanup: ${error.message}`);\n    process.exit(1);\n  }\n})();\n"
  },
  {
    "path": "scripts/deploy/Dockerfile",
    "content": "FROM node:22-slim AS builder\n\n# --max-old-space-size\nENV PNPM_HOME=\"/pnpm\"\nENV PATH=\"$PNPM_HOME:$PATH\"\nENV NODE_OPTIONS=--max-old-space-size=8192\nENV TZ=Asia/Shanghai\n\nRUN npm i -g corepack\n\nWORKDIR /app\n\n# copy package.json and pnpm-lock.yaml to workspace\nCOPY . /app\n\n# 安装依赖\nRUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile\nRUN pnpm run build --filter=\\!./docs\n\nRUN echo \"Builder Success 🎉\"\n\nFROM nginx:stable-alpine AS production\n\n# 配置 nginx\nRUN echo \"types { application/javascript js mjs; }\" > /etc/nginx/conf.d/mjs.conf \\\n    && rm -rf /etc/nginx/conf.d/default.conf\n\n# 复制构建产物\nCOPY --from=builder /app/playground/dist /usr/share/nginx/html\n\n# 复制 nginx 配置\nCOPY --from=builder /app/scripts/deploy/nginx.conf /etc/nginx/nginx.conf\n\nEXPOSE 8080\n\n# 启动 nginx\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n"
  },
  {
    "path": "scripts/deploy/build-local-docker-image.sh",
    "content": "#!/bin/bash\n\nSCRIPT_DIR=$( cd -- \"$( dirname -- \"${BASH_SOURCE[0]}\" )\" &> /dev/null && pwd )\nLOG_FILE=${SCRIPT_DIR}/build-local-docker-image.log\nERROR=\"\"\nIMAGE_NAME=\"vben-admin-local\"\n\nfunction stop_and_remove_container() {\n    # Stop and remove the existing container\n    docker stop ${IMAGE_NAME} >/dev/null 2>&1\n    docker rm ${IMAGE_NAME} >/dev/null 2>&1\n}\n\nfunction remove_image() {\n    # Remove the existing image\n    docker rmi vben-admin-pro >/dev/null 2>&1\n}\n\nfunction install_dependencies() {\n    # Install all dependencies\n    cd ${SCRIPT_DIR}\n    pnpm install || ERROR=\"install_dependencies failed\"\n}\n\nfunction build_image() {\n    # build docker\n    docker build ../../ -f Dockerfile -t ${IMAGE_NAME} || ERROR=\"build_image failed\"\n}\n\nfunction log_message() {\n    if [[ ${ERROR} != \"\" ]];\n    then\n        >&2 echo \"build failed, Please check build-local-docker-image.log for more details\"\n        >&2 echo \"ERROR: ${ERROR}\"\n        exit 1\n    else\n        echo \"docker image with tag '${IMAGE_NAME}' built sussessfully. Use below sample command to run the container\"\n        echo \"\"\n        echo \"docker run -d -p 8010:8080 --name ${IMAGE_NAME} ${IMAGE_NAME}\"\n    fi\n}\n\necho \"Info: Stopping and removing existing container and image\" | tee ${LOG_FILE}\nstop_and_remove_container\nremove_image\n\necho \"Info: Installing dependencies\" | tee -a ${LOG_FILE}\ninstall_dependencies 1>> ${LOG_FILE} 2>> ${LOG_FILE}\n\nif [[ ${ERROR} == \"\" ]]; then\n    echo \"Info: Building docker image\" | tee -a ${LOG_FILE}\n    build_image 1>> ${LOG_FILE} 2>> ${LOG_FILE}\nfi\n\nlog_message | tee -a ${LOG_FILE}\n"
  },
  {
    "path": "scripts/deploy/nginx.conf",
    "content": "\n#user  nobody;\nworker_processes 1;\n\n#error_log  logs/error.log;\n#error_log  logs/error.log  notice;\n#error_log  logs/error.log  info;\n\n#pid        logs/nginx.pid;\n\n\nevents {\n  worker_connections 1024;\n}\n\n\nhttp {\n  include mime.types;\n  default_type application/octet-stream;\n\n  types {\n    application/javascript  js mjs;\n    text/css                css;\n    text/html               html;\n  }\n\n  sendfile on;\n  # tcp_nopush     on;\n\n  #keepalive_timeout  0;\n  # keepalive_timeout 65;\n\n  # gzip on;\n  # gzip_buffers 32 16k;\n  # gzip_comp_level 6;\n  # gzip_min_length 1k;\n  # gzip_static on;\n  # gzip_types text/plain\n  #   text/css\n  #   application/javascript\n  #   application/json\n  #   application/x-javascript\n  #   text/xml\n  #   application/xml\n  #   application/xml+rss\n  #   text/javascript; #设置压缩的文件类型\n  # gzip_vary on;\n\n  server {\n    listen 8080;\n    server_name localhost;\n\n    location / {\n      root /usr/share/nginx/html;\n      try_files $uri $uri/ /index.html;\n      index index.html;\n      # Enable CORS\n      add_header 'Access-Control-Allow-Origin' '*';\n      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';\n      add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';\n      if ($request_method = 'OPTIONS') {\n        add_header 'Access-Control-Max-Age' 1728000;\n        add_header 'Content-Type' 'text/plain charset=UTF-8';\n        add_header 'Content-Length' 0;\n        return 204;\n      }\n    }\n\n    error_page 500 502 503 504 /50x.html;\n\n    location = /50x.html {\n        root /usr/share/nginx/html;\n    }\n  }\n}\n"
  },
  {
    "path": "scripts/turbo-run/README.md",
    "content": "# @vben/turbo-run\n\n`turbo-run` 是一个命令行工具，允许你在多个包中并行运行命令。它提供了一个交互式的界面，让你可以选择要运行命令的包。\n\n## 特性\n\n- 🚀 交互式选择要运行的包\n- 📦 支持 monorepo 项目结构\n- 🔍 自动检测可用的命令\n- 🎯 精确过滤目标包\n\n## 安装\n\n```bash\npnpm add -D @vben/turbo-run\n```\n\n## 使用方法\n\n基本语法：\n\n```bash\nturbo-run [script]\n```\n\n例如，如果你想运行 `dev` 命令：\n\n```bash\nturbo-run dev\n```\n\n工具会自动检测哪些包有 `dev` 命令，并提供一个交互式界面让你选择要运行的包。\n\n## 示例\n\n假设你的项目中有以下包：\n\n- `@vben/app`\n- `@vben/admin`\n- `@vben/website`\n\n当你运行：\n\n```bash\nturbo-run dev\n```\n\n工具会：\n\n1. 检测哪些包有 `dev` 命令\n2. 显示一个交互式选择界面\n3. 让你选择要运行命令的包\n4. 使用 `pnpm --filter` 在选定的包中运行命令\n\n## 注意事项\n\n- 确保你的项目使用 pnpm 作为包管理器\n- 确保目标包在 `package.json` 中定义了相应的脚本命令\n- 该工具需要在 monorepo 项目的根目录下运行\n"
  },
  {
    "path": "scripts/turbo-run/bin/turbo-run.mjs",
    "content": "#!/usr/bin/env node\n\nimport('../dist/index.mjs');\n"
  },
  {
    "path": "scripts/turbo-run/package.json",
    "content": "{\n  \"name\": \"@vben/turbo-run\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"stub\": \"pnpm exec tsdown\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"bin\": {\n    \"turbo-run\": \"./bin/turbo-run.mjs\"\n  },\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"exports\": {\n    \".\": {\n      \"default\": \"./dist/index.mjs\"\n    },\n    \"./package.json\": \"./package.json\"\n  },\n  \"dependencies\": {\n    \"@clack/prompts\": \"catalog:\",\n    \"@vben/node-utils\": \"workspace:*\",\n    \"cac\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "scripts/turbo-run/src/index.ts",
    "content": "import { consola } from '@vben/node-utils';\n\nimport { cac } from 'cac';\n\nimport { run } from './run';\n\ntry {\n  const turboRun = cac('turbo-run');\n\n  turboRun\n    .command('[script]')\n    .usage(`Run turbo interactively.`)\n    .action(async (command: string) => {\n      run({ command });\n    });\n\n  turboRun.usage('turbo-run');\n  turboRun.help();\n  turboRun.parse();\n} catch (error) {\n  consola.error(error);\n  process.exit(1);\n}\n"
  },
  {
    "path": "scripts/turbo-run/src/run.ts",
    "content": "import { execaCommand, getPackages } from '@vben/node-utils';\n\nimport { cancel, isCancel, select } from '@clack/prompts';\n\ninterface RunOptions {\n  command?: string;\n}\n\nexport async function run(options: RunOptions) {\n  const { command } = options;\n  if (!command) {\n    console.error('Please enter the command to run');\n    process.exit(1);\n  }\n  const { packages } = await getPackages();\n  // const appPkgs = await findApps(process.cwd(), packages);\n  // const websitePkg = packages.find(\n  //   (item) => item.packageJson.name === '@vben/website',\n  // );\n\n  // 只显示有对应命令的包\n  const selectPkgs = packages.filter((pkg) => {\n    return (pkg?.packageJson as Record<string, any>)?.scripts?.[command];\n  });\n\n  let selectPkg: string | symbol;\n  if (selectPkgs.length > 1) {\n    selectPkg = await select<string>({\n      message: `Select the app you need to run [${command}]:`,\n      options: selectPkgs.map((item) => ({\n        label: item?.packageJson.name,\n        value: item?.packageJson.name,\n      })),\n    });\n\n    if (isCancel(selectPkg) || !selectPkg) {\n      cancel('👋 Has cancelled');\n      process.exit(0);\n    }\n  } else {\n    selectPkg = selectPkgs[0]?.packageJson?.name ?? '';\n  }\n\n  if (!selectPkg) {\n    console.error('No app found');\n    process.exit(1);\n  }\n\n  execaCommand(`pnpm --filter=${selectPkg} run ${command}`, {\n    stdio: 'inherit',\n  });\n}\n\n/**\n * 过滤app包\n * @param root\n * @param packages\n */\n// async function findApps(root: string, packages: Package[]) {\n//   // apps内的\n//   const appPackages = packages.filter((pkg) => {\n//     const viteConfigExists = fs.existsSync(join(pkg.dir, 'vite.config.mts'));\n//     return pkg.dir.startsWith(join(root, 'apps')) && viteConfigExists;\n//   });\n\n//   return appPackages;\n// }\n"
  },
  {
    "path": "scripts/turbo-run/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "scripts/turbo-run/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: true,\n  dts: true,\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "scripts/vsh/README.md",
    "content": "# @vben/vsh\n\n一个 Shell 脚本工具集合，用于 Vue Vben Admin 项目的开发和管理。\n\n## 功能特性\n\n- 🚀 基于 Node.js 的现代化 Shell 工具\n- 📦 支持模块化开发和按需加载\n- 🔍 提供依赖检查和分析功能\n- 🔄 支持循环依赖扫描\n- 📝 提供包发布检查功能\n\n## 安装\n\n```bash\n# 使用 pnpm 安装\npnpm add -D @vben/vsh\n\n# 或者使用 npm\nnpm install -D @vben/vsh\n\n# 或者使用 yarn\nyarn add -D @vben/vsh\n```\n\n## 使用方法\n\n### 全局安装\n\n```bash\n# 全局安装\npnpm add -g @vben/vsh\n\n# 使用 vsh 命令\nvsh [command]\n```\n\n### 本地使用\n\n```bash\n# 在 package.json 中添加脚本\n{\n  \"scripts\": {\n    \"vsh\": \"vsh\"\n  }\n}\n\n# 运行命令\npnpm vsh [command]\n```\n\n## 命令列表\n\n- `vsh check-deps`: 检查项目依赖\n- `vsh scan-circular`: 扫描循环依赖\n- `vsh publish-check`: 检查包发布配置\n"
  },
  {
    "path": "scripts/vsh/bin/vsh.mjs",
    "content": "#!/usr/bin/env node\n\nimport('../dist/index.mjs');\n"
  },
  {
    "path": "scripts/vsh/env.d.ts",
    "content": "declare module 'circular-dependency-scanner';\n"
  },
  {
    "path": "scripts/vsh/package.json",
    "content": "{\n  \"name\": \"@vben/vsh\",\n  \"version\": \"5.7.0\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"stub\": \"pnpm exec tsdown\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"bin\": {\n    \"vsh\": \"./bin/vsh.mjs\"\n  },\n  \"main\": \"./dist/index.mjs\",\n  \"module\": \"./dist/index.mjs\",\n  \"exports\": {\n    \".\": {\n      \"default\": \"./dist/index.mjs\"\n    },\n    \"./package.json\": \"./package.json\"\n  },\n  \"dependencies\": {\n    \"@vben/node-utils\": \"workspace:*\",\n    \"cac\": \"catalog:\",\n    \"circular-dependency-scanner\": \"catalog:\",\n    \"depcheck\": \"catalog:\",\n    \"publint\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "scripts/vsh/src/check-circular/index.ts",
    "content": "import type { CAC } from 'cac';\n\nimport { access, mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { tmpdir } from 'node:os';\nimport { extname, join } from 'node:path';\n\nimport { execa, getStagedFiles } from '@vben/node-utils';\n\nconst require = createRequire(import.meta.url);\nconst circularScannerCli =\n  require.resolve('circular-dependency-scanner/dist/cli.js');\n\n// 默认配置\nconst DEFAULT_CONFIG = {\n  allowedExtensions: ['.cjs', '.js', '.jsx', '.mjs', '.ts', '.tsx', '.vue'],\n  ignoreDirs: [\n    'dist',\n    '.turbo',\n    'output',\n    '.cache',\n    'scripts',\n    'internal',\n    'packages/effects/request/src/',\n    'packages/@core/ui-kit/menu-ui/src/',\n    'packages/@core/ui-kit/popup-ui/src/',\n  ],\n  threshold: 0, // 循环依赖的阈值\n} as const;\n\n// 类型定义\ntype CircularDependencyResult = string[];\n\ninterface CheckCircularConfig {\n  allowedExtensions?: string[];\n  ignoreDirs?: string[];\n  threshold?: number;\n}\n\ninterface CommandOptions {\n  config?: CheckCircularConfig;\n  staged: boolean;\n  verbose: boolean;\n}\n\n// 缓存机制\nconst cache = new Map<string, CircularDependencyResult[]>();\n\nasync function detectCircularDependencies({\n  cwd,\n  ignorePattern,\n  staged,\n}: {\n  cwd: string;\n  ignorePattern: string;\n  staged: boolean;\n}): Promise<CircularDependencyResult[]> {\n  const tempDir = await mkdtemp(join(tmpdir(), 'vsh-check-circular-'));\n  const outputFile = join(tempDir, 'circles.json');\n\n  try {\n    const args = [circularScannerCli, cwd, '--output', outputFile];\n\n    if (staged) {\n      args.push('--absolute');\n    }\n\n    args.push('--ignore', ignorePattern);\n\n    await execa(process.execPath, args, {\n      cwd,\n    });\n\n    await access(outputFile);\n    const output = await readFile(outputFile, 'utf8');\n    return JSON.parse(output) as CircularDependencyResult[];\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {\n      return [];\n    }\n    throw error;\n  } finally {\n    await rm(tempDir, { force: true, recursive: true });\n  }\n}\n\n/**\n * 格式化循环依赖的输出\n * @param circles - 循环依赖结果\n */\nfunction formatCircles(circles: CircularDependencyResult[]): void {\n  if (circles.length === 0) {\n    console.log('✅ No circular dependencies found');\n    return;\n  }\n\n  console.log('⚠️ Circular dependencies found:');\n  circles.forEach((circle, index) => {\n    console.log(`\\nCircular dependency #${index + 1}:`);\n    circle.forEach((file) => console.log(`  → ${file}`));\n  });\n}\n\n/**\n * 检查项目中的循环依赖\n * @param options - 检查选项\n * @param options.staged - 是否只检查暂存区文件\n * @param options.verbose - 是否显示详细信息\n * @param options.config - 自定义配置\n * @returns Promise<void>\n */\nasync function checkCircular({\n  config = {},\n  staged,\n  verbose,\n}: CommandOptions): Promise<void> {\n  try {\n    // 合并配置\n    const finalConfig = {\n      ...DEFAULT_CONFIG,\n      ...config,\n    };\n\n    // 生成忽略模式\n    const ignorePattern = `**/{${finalConfig.ignoreDirs.join(',')}}/**`;\n\n    // 检查缓存\n    const cacheKey = `${staged}-${process.cwd()}-${ignorePattern}`;\n    if (cache.has(cacheKey)) {\n      const cachedResults = cache.get(cacheKey);\n      if (cachedResults && verbose) {\n        formatCircles(cachedResults);\n      }\n      return;\n    }\n\n    // 检测循环依赖\n    const results = await detectCircularDependencies({\n      cwd: process.cwd(),\n      ignorePattern,\n      staged,\n    });\n\n    if (staged) {\n      let files = await getStagedFiles();\n      const allowedExtensions = new Set(finalConfig.allowedExtensions);\n\n      // 过滤文件列表\n      files = files.filter((file) => allowedExtensions.has(extname(file)));\n\n      const circularFiles: CircularDependencyResult[] = [];\n\n      for (const file of files) {\n        for (const result of results) {\n          const resultFiles = result.flat();\n          if (resultFiles.includes(file)) {\n            circularFiles.push(result);\n          }\n        }\n      }\n\n      // 更新缓存\n      cache.set(cacheKey, circularFiles);\n      if (verbose) {\n        formatCircles(circularFiles);\n      }\n    } else {\n      // 更新缓存\n      cache.set(cacheKey, results);\n      if (verbose) {\n        formatCircles(results);\n      }\n    }\n\n    // 如果发现循环依赖，只输出警告信息\n    if (results.length > 0) {\n      console.log(\n        '\\n⚠️ Warning: Circular dependencies found, please check and fix',\n      );\n    }\n  } catch (error) {\n    console.error(\n      '❌ Error checking circular dependencies:',\n      error instanceof Error ? error.message : error,\n    );\n  }\n}\n\n/**\n * 定义检查循环依赖的命令\n * @param cac - CAC实例\n */\nfunction defineCheckCircularCommand(cac: CAC): void {\n  cac\n    .command('check-circular')\n    .option('--staged', 'Only check staged files')\n    .option('--verbose', 'Show detailed information')\n    .option('--threshold <number>', 'Threshold for circular dependencies', {\n      default: 0,\n    })\n    .option('--ignore-dirs <dirs>', 'Directories to ignore, comma separated')\n    .usage('Analyze project circular dependencies')\n    .action(async ({ ignoreDirs, staged, threshold, verbose }) => {\n      const config: CheckCircularConfig = {\n        threshold: Number(threshold),\n        ...(ignoreDirs && { ignoreDirs: ignoreDirs.split(',') }),\n      };\n\n      await checkCircular({\n        config,\n        staged,\n        verbose: verbose ?? true,\n      });\n    });\n}\n\nexport { type CheckCircularConfig, defineCheckCircularCommand };\n"
  },
  {
    "path": "scripts/vsh/src/check-dep/index.ts",
    "content": "import type { CAC } from 'cac';\n\nimport { getPackages } from '@vben/node-utils';\n\nimport depcheck from 'depcheck';\n\n// 默认配置\nconst DEFAULT_CONFIG = {\n  // 需要忽略的依赖匹配\n  ignoreMatches: [\n    'vite',\n    'vitest',\n    'tsdown',\n    '@vben/tailwind-config',\n    '@vben/tsconfig',\n    '@vben/vite-config',\n    '@types/*',\n    '@vben-core/design',\n  ],\n  // 需要忽略的包\n  ignorePackages: [\n    '@vben/backend-mock',\n    '@vben/commitlint-config',\n    '@vben/eslint-config',\n    '@vben/node-utils',\n    '@vben/oxfmt-config',\n    '@vben/oxlint-config',\n    '@vben/stylelint-config',\n    '@vben/tsconfig',\n    '@vben/vite-config',\n    '@vben/vsh',\n  ],\n  // 需要忽略的文件模式\n  ignorePatterns: ['dist', 'node_modules', 'public'],\n};\n\ninterface DepcheckResult {\n  dependencies: string[];\n  devDependencies: string[];\n  missing: Record<string, string[]>;\n}\n\ninterface DepcheckConfig {\n  ignoreMatches?: string[];\n  ignorePackages?: string[];\n  ignorePatterns?: string[];\n}\n\ninterface PackageInfo {\n  dir: string;\n  packageJson: {\n    name: string;\n  };\n}\n\n/**\n * 清理依赖检查结果\n * @param unused - 依赖检查结果\n */\nfunction cleanDepcheckResult(unused: DepcheckResult): void {\n  // 删除file:前缀的依赖提示，该依赖是本地依赖\n  Reflect.deleteProperty(unused.missing, 'file:');\n\n  // 清理路径依赖\n  Object.keys(unused.missing).forEach((key) => {\n    unused.missing[key] = (unused.missing[key] || []).filter(\n      (item: string) => !item.startsWith('/'),\n    );\n    if (unused.missing[key].length === 0) {\n      Reflect.deleteProperty(unused.missing, key);\n    }\n  });\n}\n\n/**\n * 格式化依赖检查结果\n * @param pkgName - 包名\n * @param unused - 依赖检查结果\n */\nfunction formatDepcheckResult(pkgName: string, unused: DepcheckResult): void {\n  const hasIssues =\n    Object.keys(unused.missing).length > 0 ||\n    unused.dependencies.length > 0 ||\n    unused.devDependencies.length > 0;\n\n  if (!hasIssues) {\n    return;\n  }\n\n  console.log('\\n📦 Package:', pkgName);\n\n  if (Object.keys(unused.missing).length > 0) {\n    console.log('❌ Missing dependencies:');\n    Object.entries(unused.missing).forEach(([dep, files]) => {\n      console.log(`  - ${dep}:`);\n      files.forEach((file) => console.log(`    → ${file}`));\n    });\n  }\n\n  if (unused.dependencies.length > 0) {\n    console.log('⚠️ Unused dependencies:');\n    unused.dependencies.forEach((dep) => console.log(`  - ${dep}`));\n  }\n\n  if (unused.devDependencies.length > 0) {\n    console.log('⚠️ Unused devDependencies:');\n    unused.devDependencies.forEach((dep) => console.log(`  - ${dep}`));\n  }\n}\n\n/**\n * 运行依赖检查\n * @param config - 配置选项\n */\nasync function runDepcheck(config: DepcheckConfig = {}): Promise<void> {\n  try {\n    const finalConfig = {\n      ...DEFAULT_CONFIG,\n      ...config,\n    };\n\n    const { packages } = await getPackages();\n\n    let hasIssues = false;\n\n    await Promise.all(\n      packages.map(async (pkg: PackageInfo) => {\n        // 跳过需要忽略的包\n        if (finalConfig.ignorePackages.includes(pkg.packageJson.name)) {\n          return;\n        }\n\n        const unused = await depcheck(pkg.dir, {\n          ignoreMatches: finalConfig.ignoreMatches,\n          ignorePatterns: finalConfig.ignorePatterns,\n        });\n\n        cleanDepcheckResult(unused);\n\n        const pkgHasIssues =\n          Object.keys(unused.missing).length > 0 ||\n          unused.dependencies.length > 0 ||\n          unused.devDependencies.length > 0;\n\n        if (pkgHasIssues) {\n          hasIssues = true;\n          formatDepcheckResult(pkg.packageJson.name, unused);\n        }\n      }),\n    );\n\n    if (!hasIssues) {\n      console.log('\\n✅ Dependency check completed, no issues found');\n    }\n  } catch (error) {\n    console.error(\n      '❌ Dependency check failed:',\n      error instanceof Error ? error.message : error,\n    );\n  }\n}\n\n/**\n * 定义依赖检查命令\n * @param cac - CAC实例\n */\nfunction defineDepcheckCommand(cac: CAC): void {\n  cac\n    .command('check-dep')\n    .option(\n      '--ignore-packages <packages>',\n      'Packages to ignore, comma separated',\n    )\n    .option(\n      '--ignore-matches <matches>',\n      'Dependency patterns to ignore, comma separated',\n    )\n    .option(\n      '--ignore-patterns <patterns>',\n      'File patterns to ignore, comma separated',\n    )\n    .usage('Analyze project dependencies')\n    .action(async ({ ignoreMatches, ignorePackages, ignorePatterns }) => {\n      const config: DepcheckConfig = {\n        ...(ignorePackages && { ignorePackages: ignorePackages.split(',') }),\n        ...(ignoreMatches && { ignoreMatches: ignoreMatches.split(',') }),\n        ...(ignorePatterns && { ignorePatterns: ignorePatterns.split(',') }),\n      };\n\n      await runDepcheck(config);\n    });\n}\n\nexport { defineDepcheckCommand, type DepcheckConfig };\n"
  },
  {
    "path": "scripts/vsh/src/code-workspace/index.ts",
    "content": "import type { CAC } from 'cac';\n\nimport { join, relative } from 'node:path';\n\nimport {\n  colors,\n  consola,\n  findMonorepoRoot,\n  formatFile,\n  getPackages,\n  gitAdd,\n  outputJSON,\n  toPosixPath,\n} from '@vben/node-utils';\n\nconst CODE_WORKSPACE_FILE = join('vben-admin.code-workspace');\n\ninterface CodeWorkspaceCommandOptions {\n  autoCommit?: boolean;\n  spaces?: number;\n}\n\nasync function createCodeWorkspace({\n  autoCommit = false,\n  spaces = 2,\n}: CodeWorkspaceCommandOptions) {\n  const { packages, rootDir } = await getPackages();\n\n  let folders = packages.map((pkg) => {\n    const { dir, packageJson } = pkg;\n    return {\n      name: packageJson.name,\n      path: toPosixPath(relative(rootDir, dir)),\n    };\n  });\n\n  folders = folders.filter(Boolean);\n\n  const monorepoRoot = findMonorepoRoot();\n  const outputPath = join(monorepoRoot, CODE_WORKSPACE_FILE);\n  await outputJSON(outputPath, { folders }, spaces);\n\n  await formatFile(outputPath);\n  if (autoCommit) {\n    await gitAdd(CODE_WORKSPACE_FILE, monorepoRoot);\n  }\n}\n\nasync function runCodeWorkspace({\n  autoCommit,\n  spaces,\n}: CodeWorkspaceCommandOptions) {\n  await createCodeWorkspace({\n    autoCommit,\n    spaces,\n  });\n  if (autoCommit) {\n    return;\n  }\n  consola.log('');\n  consola.success(colors.green(`${CODE_WORKSPACE_FILE} is updated!`));\n  consola.log('');\n}\n\nfunction defineCodeWorkspaceCommand(cac: CAC) {\n  cac\n    .command('code-workspace')\n    .usage('Update the `.code-workspace` file')\n    .option('--spaces [number]', '.code-workspace JSON file spaces.', {\n      default: 2,\n    })\n    .option('--auto-commit', 'auto commit .code-workspace JSON file.', {\n      default: false,\n    })\n    .action(runCodeWorkspace);\n}\n\nexport { defineCodeWorkspaceCommand };\n"
  },
  {
    "path": "scripts/vsh/src/index.ts",
    "content": "import { colors, consola } from '@vben/node-utils';\n\nimport { cac } from 'cac';\n\nimport { version } from '../package.json';\nimport { defineCheckCircularCommand } from './check-circular';\nimport { defineDepcheckCommand } from './check-dep';\nimport { defineCodeWorkspaceCommand } from './code-workspace';\nimport { defineLintCommand } from './lint';\nimport { definePubLintCommand } from './publint';\n\n// 命令描述\nconst COMMAND_DESCRIPTIONS = {\n  'check-circular': 'Check for circular dependencies',\n  'check-dep': 'Check for unused dependencies',\n  'code-workspace': 'Manage VS Code workspace settings',\n  lint: 'Run linting on the project',\n  publint: 'Check package.json files for publishing standards',\n} as const;\n\n/**\n * Initialize and run the CLI\n */\nasync function main(): Promise<void> {\n  try {\n    const vsh = cac('vsh');\n\n    // Register commands\n    defineLintCommand(vsh);\n    definePubLintCommand(vsh);\n    defineCodeWorkspaceCommand(vsh);\n    defineCheckCircularCommand(vsh);\n    defineDepcheckCommand(vsh);\n\n    // Set up CLI\n    vsh.usage('vsh <command> [options]');\n    vsh.help();\n    vsh.version(version);\n\n    // Parse arguments without auto-running to detect unknown commands\n    // Note: cac v7 removed EventEmitter; use matchedCommand after parse instead\n    vsh.parse(undefined, { run: false });\n\n    if (!vsh.matchedCommand && vsh.args.length > 0) {\n      const unknownCmd = String(vsh.args[0]);\n      consola.error(\n        colors.red(`Invalid command: ${unknownCmd}`),\n        '\\n',\n        colors.yellow('Available commands:'),\n        '\\n',\n        Object.entries(COMMAND_DESCRIPTIONS)\n          .map(([name, desc]) => `  ${colors.cyan(name)} - ${desc}`)\n          .join('\\n'),\n      );\n      process.exit(1);\n    }\n\n    await vsh.runMatchedCommand();\n  } catch (error) {\n    consola.error(\n      colors.red('An unexpected error occurred:'),\n      '\\n',\n      error instanceof Error ? error.message : error,\n    );\n    process.exit(1);\n  }\n}\n\n// Run the CLI\nmain().catch((error) => {\n  consola.error(\n    colors.red('Failed to start CLI:'),\n    '\\n',\n    error instanceof Error ? error.message : error,\n  );\n  process.exit(1);\n});\n"
  },
  {
    "path": "scripts/vsh/src/lint/index.ts",
    "content": "import type { CAC } from 'cac';\n\nimport { execaCommand } from '@vben/node-utils';\n\ninterface LintCommandOptions {\n  /**\n   * Format lint problem.\n   */\n  format?: boolean;\n}\n\nasync function runLint({ format }: LintCommandOptions) {\n  // process.env.FORCE_COLOR = '3';\n\n  if (format) {\n    await execaCommand(`stylelint \"**/*.{vue,css,less,scss}\" --cache --fix`, {\n      stdio: 'inherit',\n    });\n    await execaCommand(`oxfmt`, {\n      stdio: 'inherit',\n    });\n    await execaCommand(`oxlint --fix`, {\n      stdio: 'inherit',\n    });\n    await execaCommand(`eslint . --cache --fix`, {\n      stdio: 'inherit',\n    });\n    return;\n  }\n  await Promise.all([\n    execaCommand(`oxfmt --check`, {\n      stdio: 'inherit',\n    }),\n    execaCommand(`oxlint`, {\n      stdio: 'inherit',\n    }),\n    execaCommand(`eslint . --cache`, {\n      stdio: 'inherit',\n    }),\n    execaCommand(`stylelint \"**/*.{vue,css,less,scss}\" --cache`, {\n      stdio: 'inherit',\n    }),\n  ]);\n}\n\nfunction defineLintCommand(cac: CAC) {\n  cac\n    .command('lint')\n    .usage('Batch execute project lint check.')\n    .option('--format', 'Format lint problem.')\n    .action(runLint);\n}\n\nexport { defineLintCommand };\n"
  },
  {
    "path": "scripts/vsh/src/publint/index.ts",
    "content": "import type { CAC } from 'cac';\nimport type { Result } from 'publint';\n\nimport { basename, dirname, join } from 'node:path';\n\nimport {\n  colors,\n  consola,\n  ensureFile,\n  findMonorepoRoot,\n  generatorContentHash,\n  getPackages,\n  outputJSON,\n  readJSON,\n  UNICODE,\n} from '@vben/node-utils';\n\nimport { publint } from 'publint';\nimport { formatMessage } from 'publint/utils';\n\nconst CACHE_FILE = join(\n  'node_modules',\n  '.cache',\n  'publint',\n  '.pkglintcache.json',\n);\n\ninterface PubLintCommandOptions {\n  /**\n   * Only errors are checked, no program exit is performed\n   */\n  check?: boolean;\n}\n\n/**\n * Get files that require lint\n * @param files\n */\nasync function getLintFiles(files: string[] = []) {\n  const lintFiles: string[] = [];\n\n  if (files?.length > 0) {\n    return files.filter((file) => basename(file) === 'package.json');\n  }\n\n  const { packages } = await getPackages();\n\n  for (const { dir } of packages) {\n    lintFiles.push(join(dir, 'package.json'));\n  }\n  return lintFiles;\n}\n\nfunction getCacheFile() {\n  const root = findMonorepoRoot();\n  return join(root, CACHE_FILE);\n}\n\nasync function readCache(cacheFile: string) {\n  try {\n    await ensureFile(cacheFile);\n    return await readJSON(cacheFile);\n  } catch {\n    return {};\n  }\n}\n\nasync function runPublint(files: string[], { check }: PubLintCommandOptions) {\n  const lintFiles = await getLintFiles(files);\n  const cacheFile = getCacheFile();\n\n  const cacheData = await readCache(cacheFile);\n  const cache: Record<string, { hash: string; result: Result }> = cacheData;\n\n  const results = await Promise.all(\n    lintFiles.map(async (file) => {\n      try {\n        const pkgJson = await readJSON(file);\n\n        if (pkgJson.private) {\n          return null;\n        }\n\n        Reflect.deleteProperty(pkgJson, 'dependencies');\n        Reflect.deleteProperty(pkgJson, 'devDependencies');\n        Reflect.deleteProperty(pkgJson, 'peerDependencies');\n        const content = JSON.stringify(pkgJson);\n        const hash = generatorContentHash(content);\n\n        const publintResult: Result =\n          cache?.[file]?.hash === hash\n            ? (cache?.[file]?.result ?? [])\n            : await publint({\n                level: 'suggestion',\n                pkgDir: dirname(file),\n                strict: true,\n              });\n\n        cache[file] = {\n          hash,\n          result: publintResult,\n        };\n\n        return { pkgJson, pkgPath: file, publintResult };\n      } catch {\n        return null;\n      }\n    }),\n  );\n\n  await outputJSON(cacheFile, cache);\n  printResult(results, check);\n}\n\nfunction printResult(\n  results: Array<null | {\n    pkgJson: Record<string, number | string>;\n    pkgPath: string;\n    publintResult: Result;\n  }>,\n  check?: boolean,\n) {\n  let errorCount = 0;\n  let warningCount = 0;\n  let suggestionsCount = 0;\n\n  for (const result of results) {\n    if (!result) {\n      continue;\n    }\n    const { pkgJson, pkgPath, publintResult } = result;\n    const messages = publintResult?.messages ?? [];\n    if (messages?.length < 1) {\n      continue;\n    }\n\n    consola.log('');\n    consola.log(pkgPath);\n    for (const message of messages) {\n      switch (message.type) {\n        case 'error': {\n          errorCount++;\n\n          break;\n        }\n        case 'suggestion': {\n          suggestionsCount++;\n          break;\n        }\n        case 'warning': {\n          warningCount++;\n\n          break;\n        }\n        // No default\n      }\n      const ruleUrl = `https://publint.dev/rules#${message.code.toLocaleLowerCase()}`;\n      consola.log(\n        `  ${formatMessage(message, pkgJson)}${colors.dim(` ${ruleUrl}`)}`,\n      );\n    }\n  }\n\n  const totalCount = warningCount + errorCount + suggestionsCount;\n  if (totalCount > 0) {\n    consola.error(\n      colors.red(\n        `${UNICODE.FAILURE} ${totalCount} problem (${errorCount} errors, ${warningCount} warnings, ${suggestionsCount} suggestions)`,\n      ),\n    );\n    !check && process.exit(1);\n  } else {\n    consola.log(colors.green(`${UNICODE.SUCCESS} No problem`));\n  }\n}\n\nfunction definePubLintCommand(cac: CAC) {\n  cac\n    .command('publint [...files]')\n    .usage('Check if the monorepo package conforms to the publint standard.')\n    .option('--check', 'Only errors are checked, no program exit is performed.')\n    .action(runPublint);\n}\n\nexport { definePubLintCommand };\n"
  },
  {
    "path": "scripts/vsh/tsconfig.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"extends\": \"@vben/tsconfig/node.json\",\n  \"include\": [\"src\", \"env.d.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "scripts/vsh/tsdown.config.ts",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n  clean: true,\n  dts: true,\n  entry: ['src/index.ts'],\n  format: ['esm'],\n  outExtensions: () => ({\n    dts: '.d.ts',\n  }),\n});\n"
  },
  {
    "path": "stylelint.config.mjs",
    "content": "export default {\n  extends: ['@vben/stylelint-config'],\n  root: true,\n};\n"
  },
  {
    "path": "tea.yaml",
    "content": "# https://tea.xyz/what-is-this-file\n---\nversion: 1.0.0\ncodeOwners:\n  - '0xB33cc732DFc15Cd39eF50Fb165c876E24417E48f'\nquorum: 1\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"globalDependencies\": [\n    \"pnpm-lock.yaml\",\n    \"**/.env.*local\",\n    \"**/tsconfig*.json\",\n    \"internal/node-utils/*.json\",\n    \"internal/node-utils/src/**/*.ts\",\n    \"internal/vite-config/*.json\",\n    \"internal/vite-config/src/**/*.ts\",\n    \"scripts/*/src/**/*.ts\",\n    \"scripts/*/src/**/*.json\"\n  ],\n  \"globalEnv\": [\"NODE_ENV\"],\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\n        \"dist/**\",\n        \"dist.zip\",\n        \".vitepress/dist.zip\",\n        \".vitepress/dist/**\"\n      ]\n    },\n    \"preview\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"dist/**\"]\n    },\n    \"build:analyze\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"dist/**\"]\n    },\n    \"@vben/backend-mock#build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\".nitro/**\", \".output/**\"]\n    },\n    \"test:e2e\": {},\n    \"dev\": {\n      \"dependsOn\": [],\n      \"outputs\": [],\n      \"cache\": false,\n      \"persistent\": true\n    },\n    \"typecheck\": {\n      \"outputs\": []\n    }\n  }\n}\n"
  },
  {
    "path": "vben-admin.code-workspace",
    "content": "{\n  \"folders\": [\n    {\n      \"name\": \"@vben/backend-mock\",\n      \"path\": \"apps/backend-mock\"\n    },\n    {\n      \"name\": \"@vben/web-antd\",\n      \"path\": \"apps/web-antd\"\n    },\n    {\n      \"name\": \"@vben/web-antdv-next\",\n      \"path\": \"apps/web-antdv-next\"\n    },\n    {\n      \"name\": \"@vben/web-ele\",\n      \"path\": \"apps/web-ele\"\n    },\n    {\n      \"name\": \"@vben/web-naive\",\n      \"path\": \"apps/web-naive\"\n    },\n    {\n      \"name\": \"@vben/web-tdesign\",\n      \"path\": \"apps/web-tdesign\"\n    },\n    {\n      \"name\": \"@vben/docs\",\n      \"path\": \"docs\"\n    },\n    {\n      \"name\": \"@vben/commitlint-config\",\n      \"path\": \"internal/lint-configs/commitlint-config\"\n    },\n    {\n      \"name\": \"@vben/eslint-config\",\n      \"path\": \"internal/lint-configs/eslint-config\"\n    },\n    {\n      \"name\": \"@vben/oxfmt-config\",\n      \"path\": \"internal/lint-configs/oxfmt-config\"\n    },\n    {\n      \"name\": \"@vben/oxlint-config\",\n      \"path\": \"internal/lint-configs/oxlint-config\"\n    },\n    {\n      \"name\": \"@vben/stylelint-config\",\n      \"path\": \"internal/lint-configs/stylelint-config\"\n    },\n    {\n      \"name\": \"@vben/node-utils\",\n      \"path\": \"internal/node-utils\"\n    },\n    {\n      \"name\": \"@vben/tailwind-config\",\n      \"path\": \"internal/tailwind-config\"\n    },\n    {\n      \"name\": \"@vben/tsconfig\",\n      \"path\": \"internal/tsconfig\"\n    },\n    {\n      \"name\": \"@vben/vite-config\",\n      \"path\": \"internal/vite-config\"\n    },\n    {\n      \"name\": \"@vben-core/design\",\n      \"path\": \"packages/@core/base/design\"\n    },\n    {\n      \"name\": \"@vben-core/icons\",\n      \"path\": \"packages/@core/base/icons\"\n    },\n    {\n      \"name\": \"@vben-core/shared\",\n      \"path\": \"packages/@core/base/shared\"\n    },\n    {\n      \"name\": \"@vben-core/typings\",\n      \"path\": \"packages/@core/base/typings\"\n    },\n    {\n      \"name\": \"@vben-core/composables\",\n      \"path\": \"packages/@core/composables\"\n    },\n    {\n      \"name\": \"@vben-core/preferences\",\n      \"path\": \"packages/@core/preferences\"\n    },\n    {\n      \"name\": \"@vben-core/form-ui\",\n      \"path\": \"packages/@core/ui-kit/form-ui\"\n    },\n    {\n      \"name\": \"@vben-core/layout-ui\",\n      \"path\": \"packages/@core/ui-kit/layout-ui\"\n    },\n    {\n      \"name\": \"@vben-core/menu-ui\",\n      \"path\": \"packages/@core/ui-kit/menu-ui\"\n    },\n    {\n      \"name\": \"@vben-core/popup-ui\",\n      \"path\": \"packages/@core/ui-kit/popup-ui\"\n    },\n    {\n      \"name\": \"@vben-core/shadcn-ui\",\n      \"path\": \"packages/@core/ui-kit/shadcn-ui\"\n    },\n    {\n      \"name\": \"@vben-core/tabs-ui\",\n      \"path\": \"packages/@core/ui-kit/tabs-ui\"\n    },\n    {\n      \"name\": \"@vben/constants\",\n      \"path\": \"packages/constants\"\n    },\n    {\n      \"name\": \"@vben/access\",\n      \"path\": \"packages/effects/access\"\n    },\n    {\n      \"name\": \"@vben/common-ui\",\n      \"path\": \"packages/effects/common-ui\"\n    },\n    {\n      \"name\": \"@vben/hooks\",\n      \"path\": \"packages/effects/hooks\"\n    },\n    {\n      \"name\": \"@vben/layouts\",\n      \"path\": \"packages/effects/layouts\"\n    },\n    {\n      \"name\": \"@vben/plugins\",\n      \"path\": \"packages/effects/plugins\"\n    },\n    {\n      \"name\": \"@vben/request\",\n      \"path\": \"packages/effects/request\"\n    },\n    {\n      \"name\": \"@vben/icons\",\n      \"path\": \"packages/icons\"\n    },\n    {\n      \"name\": \"@vben/locales\",\n      \"path\": \"packages/locales\"\n    },\n    {\n      \"name\": \"@vben/preferences\",\n      \"path\": \"packages/preferences\"\n    },\n    {\n      \"name\": \"@vben/stores\",\n      \"path\": \"packages/stores\"\n    },\n    {\n      \"name\": \"@vben/styles\",\n      \"path\": \"packages/styles\"\n    },\n    {\n      \"name\": \"@vben/types\",\n      \"path\": \"packages/types\"\n    },\n    {\n      \"name\": \"@vben/utils\",\n      \"path\": \"packages/utils\"\n    },\n    {\n      \"name\": \"@vben/playground\",\n      \"path\": \"playground\"\n    },\n    {\n      \"name\": \"@vben/turbo-run\",\n      \"path\": \"scripts/turbo-run\"\n    },\n    {\n      \"name\": \"@vben/vsh\",\n      \"path\": \"scripts/vsh\"\n    }\n  ]\n}\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import Vue from '@vitejs/plugin-vue';\nimport VueJsx from '@vitejs/plugin-vue-jsx';\nimport { configDefaults, defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  plugins: [Vue(), VueJsx()],\n  test: {\n    environment: 'happy-dom',\n    environmentOptions: {\n      happyDOM: {\n        settings: {\n          // happy-dom v20+ disables JS evaluation by default (security fix).\n          // Treat disabled script loading as success to preserve test behavior.\n          handleDisabledFileLoadingAsSuccess: true,\n        },\n      },\n    },\n    exclude: [\n      ...configDefaults.exclude,\n      '**/e2e/**',\n      '**/dist/**',\n      '**/.{idea,git,cache,output,temp}/**',\n      '**/node_modules/**',\n      '**/{stylelint,eslint}.config.*',\n      '**/{oxfmt,oxlint}.config.*',\n    ],\n  },\n});\n"
  }
]