浏览代码

Merge branch 'release/bug-349'

cc12458 3 天之前
父节点
当前提交
ccb0a36624
共有 100 个文件被更改,包括 1221 次插入2664 次删除
  1. 0 5
      .changeset/README.md
  2. 0 18
      .changeset/config.json
  3. 0 14
      .github/CODEOWNERS
  4. 0 74
      .github/ISSUE_TEMPLATE/bug-report.yml
  5. 0 38
      .github/ISSUE_TEMPLATE/docs.yml
  6. 0 70
      .github/ISSUE_TEMPLATE/feature-request.yml
  7. 0 40
      .github/actions/setup-node/action.yml
  8. 0 89
      .github/commit-convention.md
  9. 0 39
      .github/config.yml
  10. 0 40
      .github/contributing.md
  11. 0 17
      .github/dependabot.yml
  12. 0 33
      .github/pull_request_template.md
  13. 0 61
      .github/release-drafter.yml
  14. 0 13
      .github/semantic.yml
  15. 0 48
      .github/workflows/build.yml
  16. 0 42
      .github/workflows/changeset-version.yml
  17. 0 125
      .github/workflows/ci.yml
  18. 0 94
      .github/workflows/codeql.yml
  19. 0 172
      .github/workflows/deploy.yml
  20. 0 25
      .github/workflows/draft.yml
  21. 0 31
      .github/workflows/issue-close-require.yml
  22. 0 46
      .github/workflows/issue-labeled.yml
  23. 0 24
      .github/workflows/lock.yml
  24. 0 80
      .github/workflows/release-tag.yml
  25. 0 19
      .github/workflows/rerun.yml
  26. 0 41
      .github/workflows/semantic-pull-request.yml
  27. 0 19
      .github/workflows/stale.yml
  28. 0 3
      apps/backend-mock/.env
  29. 0 15
      apps/backend-mock/README.md
  30. 0 16
      apps/backend-mock/api/auth/codes.ts
  31. 0 42
      apps/backend-mock/api/auth/login.post.ts
  32. 0 17
      apps/backend-mock/api/auth/logout.post.ts
  33. 0 35
      apps/backend-mock/api/auth/refresh.post.ts
  34. 0 32
      apps/backend-mock/api/demo/bigint.ts
  35. 0 15
      apps/backend-mock/api/menu/all.ts
  36. 0 8
      apps/backend-mock/api/status.ts
  37. 0 16
      apps/backend-mock/api/system/dept/.post.ts
  38. 0 16
      apps/backend-mock/api/system/dept/[id].delete.ts
  39. 0 16
      apps/backend-mock/api/system/dept/[id].put.ts
  40. 0 62
      apps/backend-mock/api/system/dept/list.ts
  41. 0 13
      apps/backend-mock/api/system/menu/list.ts
  42. 0 29
      apps/backend-mock/api/system/menu/name-exists.ts
  43. 0 29
      apps/backend-mock/api/system/menu/path-exists.ts
  44. 0 84
      apps/backend-mock/api/system/role/list.ts
  45. 0 117
      apps/backend-mock/api/table/list.ts
  46. 0 3
      apps/backend-mock/api/test.get.ts
  47. 0 3
      apps/backend-mock/api/test.post.ts
  48. 0 14
      apps/backend-mock/api/upload.ts
  49. 0 11
      apps/backend-mock/api/user/info.ts
  50. 0 7
      apps/backend-mock/error.ts
  51. 0 20
      apps/backend-mock/middleware/1.api.ts
  52. 0 20
      apps/backend-mock/nitro.config.ts
  53. 0 21
      apps/backend-mock/package.json
  54. 0 15
      apps/backend-mock/routes/[...].ts
  55. 0 4
      apps/backend-mock/tsconfig.build.json
  56. 0 3
      apps/backend-mock/tsconfig.json
  57. 0 28
      apps/backend-mock/utils/cookie-utils.ts
  58. 0 77
      apps/backend-mock/utils/jwt-utils.ts
  59. 0 390
      apps/backend-mock/utils/mock-data.ts
  60. 0 70
      apps/backend-mock/utils/response.ts
  61. 2 2
      apps/health-remedy/.env
  62. 4 5
      apps/health-remedy/.env.development
  63. 5 3
      apps/health-remedy/.env.production
  64. 18 0
      apps/health-remedy/index.html
  65. 11 15
      apps/health-remedy/package.json
  66. 0 0
      apps/health-remedy/postcss.config.mjs
  67. 51 0
      apps/health-remedy/public/database/menu.json
  68. 0 0
      apps/health-remedy/public/favicon.ico
  69. 107 0
      apps/health-remedy/src/adapter/component/Avatar.vue
  70. 3 0
      apps/health-remedy/src/adapter/component/index.ts
  71. 0 0
      apps/health-remedy/src/adapter/form.ts
  72. 22 14
      apps/health-remedy/src/adapter/vxe-table.ts
  73. 87 0
      apps/health-remedy/src/api/index.ts
  74. 87 0
      apps/health-remedy/src/api/method/access.ts
  75. 182 0
      apps/health-remedy/src/api/method/business.ts
  76. 20 0
      apps/health-remedy/src/api/method/common.ts
  77. 141 0
      apps/health-remedy/src/api/method/system.ts
  78. 43 0
      apps/health-remedy/src/api/model/department.ts
  79. 46 0
      apps/health-remedy/src/api/model/doctor.ts
  80. 22 0
      apps/health-remedy/src/api/model/index.ts
  81. 42 0
      apps/health-remedy/src/api/model/menu.ts
  82. 25 0
      apps/health-remedy/src/api/model/role.ts
  83. 32 0
      apps/health-remedy/src/api/model/user.ts
  84. 0 0
      apps/health-remedy/src/app.vue
  85. 2 1
      apps/health-remedy/src/bootstrap.ts
  86. 102 0
      apps/health-remedy/src/components/import/database.modal.vue
  87. 32 49
      apps/health-remedy/src/core/authentication/login.vue
  88. 0 0
      apps/health-remedy/src/core/fallback/coming-soon.vue
  89. 0 0
      apps/health-remedy/src/core/fallback/forbidden.vue
  90. 0 0
      apps/health-remedy/src/core/fallback/internal-error.vue
  91. 11 0
      apps/health-remedy/src/core/fallback/not-found.vue
  92. 0 0
      apps/health-remedy/src/core/fallback/offline.vue
  93. 5 2
      apps/health-remedy/src/layouts/auth.vue
  94. 79 0
      apps/health-remedy/src/layouts/basic.vue
  95. 0 0
      apps/health-remedy/src/layouts/index.ts
  96. 0 0
      apps/health-remedy/src/locales/index.ts
  97. 5 0
      apps/health-remedy/src/locales/langs/zh-CN/authentication.json
  98. 30 0
      apps/health-remedy/src/locales/langs/zh-CN/business.json
  99. 5 0
      apps/health-remedy/src/locales/langs/zh-CN/common.json
  100. 0 5
      apps/health-remedy/src/locales/langs/zh-CN/page.json

+ 0 - 5
.changeset/README.md

@@ -1,5 +0,0 @@
-# Changesets
-
-Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets)
-
-We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

+ 0 - 18
.changeset/config.json

@@ -1,18 +0,0 @@
-{
-  "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
-  "changelog": [
-    "@changesets/changelog-github",
-    { "repo": "vbenjs/vue-vben-admin" }
-  ],
-  "commit": false,
-  "fixed": [["@vben-core/*", "@vben/*"]],
-  "snapshot": {
-    "prereleaseTemplate": "{tag}-{datetime}"
-  },
-  "privatePackages": { "version": true, "tag": true },
-  "linked": [],
-  "access": "public",
-  "baseBranch": "main",
-  "updateInternalDependencies": "patch",
-  "ignore": []
-}

+ 0 - 14
.github/CODEOWNERS

@@ -1,14 +0,0 @@
-# default onwer
-* anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
-
-# vben core onwer
-/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
-/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
-/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
-/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
-/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
-/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com
-
-# vben team onwer
-apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com
-docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com

+ 0 - 74
.github/ISSUE_TEMPLATE/bug-report.yml

@@ -1,74 +0,0 @@
-name: 🐞 Bug Report
-description: Report an issue with Vben Admin to help us make it better.
-title: 'Bug: '
-labels: ['bug: pending triage']
-
-body:
-  - type: markdown
-    attributes:
-      value: |
-        Thanks for taking the time to fill out this bug report!
-  - type: dropdown
-    id: version
-    attributes:
-      label: Version
-      description: What version of our software are you running?
-      options:
-        - Vben Admin V5
-        - Vben Admin V2
-      default: 0
-    validations:
-      required: true
-
-  - type: textarea
-    id: bug-desc
-    attributes:
-      label: Describe the bug?
-      description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks!
-      placeholder: Bug Description
-    validations:
-      required: true
-
-  - type: textarea
-    id: reproduction
-    attributes:
-      label: Reproduction
-      description: Please provide a link to [StackBlitz](https://stackblitz.com/fork/github/vitest-dev/vitest/tree/main/examples/basic?initialPath=__vitest__/) (you can also use [examples](https://github.com/vitest-dev/vitest/tree/main/examples)) or a github repo that can reproduce the problem you ran into. A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "needs reproduction" label. If no reproduction is provided after 3 days, it will be auto-closed.
-      placeholder: Reproduction
-    validations:
-      required: true
-
-  - type: textarea
-    id: system-info
-    attributes:
-      label: System Info
-      description: Output of `npx envinfo --system --npmPackages '{vue}' --binaries --browsers`
-      render: shell
-      placeholder: System, Binaries, Browsers
-    validations:
-      required: true
-
-  - type: textarea
-    id: logs
-    attributes:
-      label: Relevant log output
-      description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
-      render: shell
-
-  - type: checkboxes
-    id: terms
-    attributes:
-      label: Validations
-      description: Before submitting the issue, please make sure you do the following
-      # description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com).
-      options:
-        - label: Read the [docs](https://doc.vben.pro/)
-          required: true
-        - label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
-          required: true
-        - label: I have searched the [existing issues](https://github.com/vbenjs/vue-vben-admin/issues) and checked that my issue does not duplicate any existing issues.
-          required: true
-        - label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/vbenjs/vue-vben-admin/discussions) or join our [Discord Chat Server](https://discord.gg/8GuAdwDhj6).
-          required: true
-        - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
-          required: true

+ 0 - 38
.github/ISSUE_TEMPLATE/docs.yml

@@ -1,38 +0,0 @@
-name: 📚 Documentation
-description: Report an issue with Vben Admin Website to help us make it better.
-title: 'Docs: '
-labels: [documentation]
-body:
-  - type: markdown
-    attributes:
-      value: |
-        Thanks for taking the time to fill out this issue!
-  - type: checkboxes
-    id: documentation_is
-    attributes:
-      label: Documentation is
-      options:
-        - label: Missing
-        - label: Outdated
-        - label: Confusing
-        - label: Not sure?
-  - type: textarea
-    id: description
-    attributes:
-      label: Explain in Detail
-      description: A clear and concise description of your suggestion. If you intend to submit a PR for this issue, tell us in the description. Thanks!
-      placeholder: The description of ... page is not clear. I thought it meant ... but it wasn't.
-    validations:
-      required: true
-  - type: textarea
-    id: suggestion
-    attributes:
-      label: Your Suggestion for Changes
-    validations:
-      required: true
-  - type: textarea
-    id: reproduction-steps
-    attributes:
-      label: Steps to reproduce
-      description: Please provide any reproduction steps that may need to be described. E.g. if it happens only when running the dev or build script make sure it's clear which one to use.
-      placeholder: Run `pnpm install` followed by `pnpm run docs:dev`

+ 0 - 70
.github/ISSUE_TEMPLATE/feature-request.yml

@@ -1,70 +0,0 @@
-name: ✨ New Feature Proposal
-description: Propose a new feature to be added to Vben Admin
-title: 'FEATURE: '
-labels: ['enhancement: pending triage']
-body:
-  - type: markdown
-    attributes:
-      value: |
-        Thank you for suggesting a feature for our project! Please fill out the information below to help us understand and implement your request!
-  - type: dropdown
-    id: version
-    attributes:
-      label: Version
-      description: What version of our software are you running?
-      options:
-        - Vben Admin V5
-        - Vben Admin V2
-      default: 0
-    validations:
-      required: true
-
-  - type: textarea
-    id: description
-    attributes:
-      label: Description
-      description: A detailed description of the feature request.
-      placeholder: Please describe the feature you would like to see, and why it would be useful.
-    validations:
-      required: true
-
-  - type: textarea
-    id: proposed-solution
-    attributes:
-      label: Proposed Solution
-      description: A clear and concise description of what you want to happen.
-      placeholder: Describe the solution you'd like to see
-    validations:
-      required: true
-
-  - type: textarea
-    id: alternatives
-    attributes:
-      label: Alternatives Considered
-      description: |
-        A clear and concise description of any alternative solutions or features you've considered.
-      placeholder: Describe any alternative solutions or features you've considered
-    validations:
-      required: false
-
-  - type: input
-    id: additional-context
-    attributes:
-      label: Additional Context
-      description: Add any other context or screenshots about the feature request here.
-      placeholder: Any additional information
-    validations:
-      required: false
-
-  - type: checkboxes
-    id: checkboxes
-    attributes:
-      label: Validations
-      description: Before submitting the issue, please make sure you do the following
-      options:
-        - label: Read the [docs](https://doc.vben.pro/)
-          required: true
-        - label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
-          required: true
-        - label: I have searched the [existing issues](https://github.com/vbenjs/vue-vben-admin/issues) and checked that my issue does not duplicate any existing issues.
-          required: true

+ 0 - 40
.github/actions/setup-node/action.yml

@@ -1,40 +0,0 @@
-name: 'Setup Node'
-
-description: 'Setup node and pnpm'
-
-runs:
-  using: 'composite'
-  steps:
-    - name: Install pnpm
-      uses: pnpm/action-setup@v4
-
-    - name: Install Node.js
-      uses: actions/setup-node@v4
-      with:
-        node-version-file: .node-version
-        cache: 'pnpm'
-
-    - name: Get pnpm store directory
-      shell: bash
-      run: |
-        echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
-
-    - uses: actions/cache@v4
-      name: Setup pnpm cache
-      if: ${{ github.ref_name == 'main' }}
-      with:
-        path: ${{ env.STORE_PATH }}
-        key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
-        restore-keys: |
-          ${{ runner.os }}-pnpm-store-
-
-    - uses: actions/cache/restore@v4
-      if: ${{ github.ref_name != 'main' }}
-      with:
-        path: ${{ env.STORE_PATH }}
-        key: |
-          ${{ runner.os }}-pnpm-store-
-
-    - name: Install dependencies
-      shell: bash
-      run: pnpm install --frozen-lockfile

+ 0 - 89
.github/commit-convention.md

@@ -1,89 +0,0 @@
-## Git Commit Message Convention
-
-> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
-
-#### TL;DR:
-
-Messages must be matched by the following regex:
-
-```js
-/^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|types|wip): .{1,50}/;
-```
-
-#### Examples
-
-Appears under "Features" header, `dev` subheader:
-
-```
-feat(dev): add 'comments' option
-```
-
-Appears under "Bug Fixes" header, `dev` subheader, with a link to issue #28:
-
-```
-fix(dev): fix dev error
-
-close #28
-```
-
-Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation:
-
-```
-perf(build): remove 'foo' option
-
-BREAKING CHANGE: The 'foo' option has been removed.
-```
-
-The 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.
-
-```
-revert: feat(compiler): add 'comments' option
-
-This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
-```
-
-### Full Message Format
-
-A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
-
-```
-<type>(<scope>): <subject>
-<BLANK LINE>
-<body>
-<BLANK LINE>
-<footer>
-```
-
-The **header** is mandatory and the **scope** of the header is optional.
-
-### Revert
-
-If 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.
-
-### Type
-
-If 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.
-
-Other prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.
-
-### Scope
-
-The scope could be anything specifying the place of the commit change. For example `dev`, `build`, `workflow`, `cli` etc...
-
-### Subject
-
-The subject contains a succinct description of the change:
-
-- use the imperative, present tense: "change" not "changed" nor "changes"
-- don't capitalize the first letter
-- no dot (.) at the end
-
-### Body
-
-Just 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.
-
-### Footer
-
-The footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.
-
-**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.

+ 0 - 39
.github/config.yml

@@ -1,39 +0,0 @@
-# Prevent issues being created without using the template
-blank_issues_enabled: false
-checkIssueTemplate: true
-checkPullRequestTemplate: true
-
-contact_links:
-  - name: 💬 Discord Chat
-    url: https://discord.gg/8GuAdwDhj6
-    about: Ask questions and discuss with other Vben users in real time.
-
-  - name: ❓ Questions & Discussions
-    url: https://github.com/@vbenjs/vue-vben-admin/discussions
-    about: Use GitHub discussions for message-board style questions and discussions.
-
-# Comment to be posted to on PRs from first time contributors in your repository
-newPRWelcomeComment: |
-  💖 Thanks for opening this pull request! 💖
-  Please be patient and we will get back to you as soon as we can.
-
-# Comment to be posted to on pull requests merged by a first time user
-firstPRMergeComment: >
-  Thanks for your contribution!  🎉🎉🎉
-
-
-# Comment to be posted to on first time issues
-newIssueWelcomeComment: >
-  Thanks for opening your first issue! Be sure to follow the issue template and provide every bit of information to help the developers!
-
-
-# *OPTIONAL* default titles to check against for lack of descriptiveness
-# MUST BE ALL LOWERCASE
-requestInfoDefaultTitles:
-  - update readme.md
-  - updates
-
-# *Required* Comment to reply with
-requestInfoReplyComment: >
-  Thanks for filing this issue/PR! It would be much appreciated if you could provide us with more information so we can effectively analyze the situation in context.
-

+ 0 - 40
.github/contributing.md

@@ -1,40 +0,0 @@
-# Vben Admin Contributing Guide
-
-Hi! We're really excited that you are interested in contributing to Vben Admin. Before submitting your contribution, please make sure to take a moment and read through the following guidelines:
-
-- [Pull Request Guidelines](#pull-request-guidelines)
-
-## Contributor Code of Conduct
-
-As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
-
-We are committed to making participation in this project a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
-
-Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
-
-Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
-
-## Pull Request Guidelines
-
-- Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch.
-
-- If adding a new feature:
-  - Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
-
-- If fixing bug:
-  - Provide a detailed description of the bug in the PR. Live demo preferred.
-
-- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.
-
-## Development Setup
-
-You will need [pnpm](https://pnpm.io/)
-
-After cloning the repo, run:
-
-```bash
-# install the dependencies of the project
-$ pnpm install
-# start the project
-$ pnpm run dev
-```

+ 0 - 17
.github/dependabot.yml

@@ -1,17 +0,0 @@
-version: 2
-updates:
-  - package-ecosystem: npm
-    directory: '/'
-    schedule:
-      interval: daily
-    groups:
-      non-breaking-changes:
-        update-types: [minor, patch]
-
-  - package-ecosystem: github-actions
-    directory: '/'
-    schedule:
-      interval: weekly
-    groups:
-      non-breaking-changes:
-        update-types: [minor, patch]

+ 0 - 33
.github/pull_request_template.md

@@ -1,33 +0,0 @@
-## Description
-
-<!-- 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.
-
- -->
-
-<!-- You can also add additional context here -->
-
-## Type of change
-
-Please delete options that are not relevant.
-
-- [ ] Bug fix (non-breaking change which fixes an issue)
-- [ ] New feature (non-breaking change which adds functionality)
-- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
-- [ ] This change requires a documentation update
-- [ ] Please, don't make changes to `pnpm-lock.yaml` unless you introduce a new test example.
-
-## Checklist
-
-> ℹ️ Check all checkboxes - this will indicate that you have done everything in accordance with the rules in [CONTRIBUTING](contributing.md).
-
-- [ ] If you introduce new functionality, document it. You can run documentation with `pnpm run docs:dev` command.
-- [ ] Run the tests with `pnpm test`.
-- [ ] Changes in changelog are generated from PR name. Please, make sure that it explains your changes in an understandable manner. Please, prefix changeset messages with `feat:`, `fix:`, `perf:`, `docs:`, or `chore:`.
-- [ ] My code follows the style guidelines of this project
-- [ ] I have performed a self-review of my own code
-- [ ] I have commented my code, particularly in hard-to-understand areas
-- [ ] I have made corresponding changes to the documentation
-- [ ] My changes generate no new warnings
-- [ ] I have added tests that prove my fix is effective or that my feature works
-- [ ] New and existing unit tests pass locally with my changes
-- [ ] Any dependent changes have been merged and published in downstream modules

+ 0 - 61
.github/release-drafter.yml

@@ -1,61 +0,0 @@
-name-template: 'v$RESOLVED_VERSION'
-tag-template: 'v$RESOLVED_VERSION'
-version-template: $MAJOR.$MINOR.$PATCH
-change-template: '* $TITLE (#$NUMBER) @$AUTHOR'
-template: |
-  # What's Changed
-
-  $CHANGES
-
-  **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
-
-categories:
-  - title: '🚀 Features'
-    labels:
-      - 'feature'
-  - title: '🐞 Bug Fixes'
-    labels:
-      - 'bug'
-  - title: '📈 Performance & Enhancement'
-    labels:
-      - 'perf'
-      - 'enhancement'
-  - title: 📝 Documentation
-    labels:
-      - 'documentation'
-  - title: 👻 Maintenance
-    labels:
-      - 'chore'
-      - 'dependencies'
-    # collapse-after: 12
-  - title: 🚦 Tests
-    labels:
-      - 'tests'
-  - title: 'Breaking'
-    label: 'breaking'
-
-version-resolver:
-  major:
-    labels:
-      - 'major'
-      - 'breaking'
-  minor:
-    labels:
-      - 'minor'
-  patch:
-    labels:
-      - 'feature'
-      - 'patch'
-      - 'bug'
-      - 'maintenance'
-      - 'docs'
-      - 'dependencies'
-      - 'security'
-
-exclude-labels:
-  - 'skip-changelog'
-  - 'no-changelog'
-  - 'changelog'
-  - 'bump versions'
-  - 'reverted'
-  - 'invalid'

+ 0 - 13
.github/semantic.yml

@@ -1,13 +0,0 @@
-titleAndCommits: true
-types:
-  - feat
-  - fix
-  - docs
-  - chore
-  - style
-  - refactor
-  - perf
-  - test
-  - build
-  - ci
-  - revert

+ 0 - 48
.github/workflows/build.yml

@@ -1,48 +0,0 @@
-# name: Dependabot post-update
-name: Build detection
-on:
-  pull_request_target:
-    types: [opened, synchronize, reopened]
-    branches:
-      - main
-
-env:
-  HUSKY: '0'
-
-concurrency:
-  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
-  cancel-in-progress: true
-
-permissions:
-  contents: read
-  pull-requests: write
-
-jobs:
-  post-update:
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    # if: ${{ github.actor == 'dependabot[bot]' }}
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os:
-          - ubuntu-latest
-          # - macos-latest
-          - windows-latest
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Checkout out pull request
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        run: |
-          gh pr checkout ${{ github.event.pull_request.number }}
-
-      - name: Setup Node
-        uses: ./.github/actions/setup-node
-
-      - name: Build
-        run: |
-          pnpm run build

+ 0 - 42
.github/workflows/changeset-version.yml

@@ -1,42 +0,0 @@
-# https://github.com/changesets/action
-name: Changeset version
-
-on:
-  workflow_dispatch:
-  pull_request:
-    types:
-      - closed
-    branches:
-      - main
-
-permissions:
-  pull-requests: write
-  contents: write
-
-env:
-  CI: true
-
-jobs:
-  version:
-    if: (github.event.pull_request.merged || github.event_name == 'workflow_dispatch') && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
-    # if: github.repository == 'vbenjs/vue-vben-admin'
-    timeout-minutes: 15
-    runs-on: ubuntu-latest
-
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Setup Node
-        uses: ./.github/actions/setup-node
-
-      - name: Create Release Pull Request
-        uses: changesets/action@v1
-        with:
-          version: pnpm run version
-          commit: 'chore: bump versions'
-          title: 'chore: bump versions'
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 0 - 125
.github/workflows/ci.yml

@@ -1,125 +0,0 @@
-name: CI
-
-on:
-  pull_request:
-  push:
-    branches:
-      - main
-      - 'releases/*'
-
-permissions:
-  contents: read
-
-env:
-  CI: true
-  TZ: Asia/Shanghai
-
-jobs:
-  test:
-    name: Test
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os:
-          - ubuntu-latest
-          # - macos-latest
-          - windows-latest
-    timeout-minutes: 20
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Install pnpm
-        uses: pnpm/action-setup@v4
-        with:
-          run_install: false
-
-      - name: Setup Node
-        uses: ./.github/actions/setup-node
-
-      # - name: Check Git version
-      #   run: git --version
-
-      # - name: Setup mock Git user
-      #   run: git config --global user.email "you@example.com" && git config --global user.name "Your Name"
-
-      - name: Vitest tests
-        run: pnpm run test:unit
-
-      # - name: Upload coverage
-      #   uses: codecov/codecov-action@v4
-      #   with:
-      #     token: ${{ secrets.CODECOV_TOKEN }}
-
-  lint:
-    name: Lint
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os:
-          - ubuntu-latest
-          # - macos-latest
-          - windows-latest
-
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Setup Node
-        uses: ./.github/actions/setup-node
-
-      - name: Lint
-        run: pnpm run lint
-
-  check:
-    name: Check
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ${{ matrix.os }}
-    timeout-minutes: 20
-    strategy:
-      matrix:
-        os:
-          - ubuntu-latest
-          # - macos-latest
-          - windows-latest
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Setup Node
-        uses: ./.github/actions/setup-node
-
-      - name: Typecheck
-        run: pnpm check:type
-
-      # From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions
-      - name: Check workflow files
-        if: runner.os == 'Linux'
-        run: |
-          bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
-          ./actionlint -color -shellcheck=""
-
-  ci-ok:
-    name: CI OK
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    needs: [test, check, lint]
-    env:
-      FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
-    steps:
-      - name: Check for failure
-        run: |
-          echo $FAILURE
-          if [ "$FAILURE" = "false" ]; then
-            exit 0
-          else
-            exit 1
-          fi

+ 0 - 94
.github/workflows/codeql.yml

@@ -1,94 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
-name: 'CodeQL'
-
-on:
-  push:
-    branches: ['main']
-  pull_request:
-    branches: ['main']
-  schedule:
-    - cron: '35 0 * * 0'
-
-jobs:
-  analyze:
-    name: Analyze (${{ matrix.language }})
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    # Runner size impacts CodeQL analysis time. To learn more, please see:
-    #   - https://gh.io/recommended-hardware-resources-for-running-codeql
-    #   - https://gh.io/supported-runners-and-hardware-resources
-    #   - https://gh.io/using-larger-runners (GitHub.com only)
-    # Consider using larger runners or machines with greater resources for possible analysis time improvements.
-    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
-    timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
-    permissions:
-      # required for all workflows
-      security-events: write
-
-      # required to fetch internal or private CodeQL packs
-      packages: read
-
-      # only required for workflows in private repositories
-      actions: read
-      contents: read
-
-    strategy:
-      fail-fast: false
-      matrix:
-        include:
-          - language: javascript-typescript
-            build-mode: none
-        # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
-        # Use `c-cpp` to analyze code written in C, C++ or both
-        # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
-        # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
-        # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
-        # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
-        # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
-        # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v4
-
-      # Initializes the CodeQL tools for scanning.
-      - name: Initialize CodeQL
-        uses: github/codeql-action/init@v3
-        with:
-          languages: ${{ matrix.language }}
-          build-mode: ${{ matrix.build-mode }}
-          # If you wish to specify custom queries, you can do so here or in a config file.
-          # By default, queries listed here will override any specified in a config file.
-          # Prefix the list here with "+" to use these queries and those in the config file.
-
-          # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
-          # queries: security-extended,security-and-quality
-
-      # If the analyze step fails for one of the languages you are analyzing with
-      # "We were unable to automatically build your code", modify the matrix above
-      # to set the build mode to "manual" for that language. Then modify this step
-      # to build your code.
-      # ℹ️ Command-line programs to run using the OS shell.
-      # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
-      - if: matrix.build-mode == 'manual'
-        shell: bash
-        run: |
-          echo 'If you are using a "manual" build mode for one or more of the' \
-            'languages you are analyzing, replace this with the commands to build' \
-            'your code, for example:'
-          echo '  make bootstrap'
-          echo '  make release'
-          exit 1
-
-      - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v3
-        with:
-          category: '/language:${{matrix.language}}'

+ 0 - 172
.github/workflows/deploy.yml

@@ -1,172 +0,0 @@
-name: Deploy Website on push
-
-on:
-  push:
-    branches:
-      - main
-
-jobs:
-  deploy-playground-ftp:
-    name: Deploy Push Playground Ftp
-    if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Sed Config Base
-        shell: bash
-        run: |
-          sed -i  "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./playground/.env.production
-          sed -i  "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./playground/.env.production
-          cat ./playground/.env.production
-
-      - name: Setup Node
-        uses: ./.github/actions/setup-node
-
-      - name: Build
-        run: pnpm build:play
-
-      - name: Sync Playground files
-        uses: SamKirkland/FTP-Deploy-Action@v4.3.5
-        with:
-          server: ${{ secrets.PRO_FTP_HOST }}
-          username: ${{ secrets.WEB_PLAYGROUND_FTP_ACCOUNT }}
-          password: ${{ secrets.WEB_PLAYGROUND_FTP_PWSSWORD }}
-          local-dir: ./playground/dist/
-
-  deploy-docs-ftp:
-    name: Deploy Push Docs Ftp
-    if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Setup Node
-        uses: ./.github/actions/setup-node
-
-      - name: Build
-        run: pnpm build:docs
-
-      - name: Sync Docs files
-        uses: SamKirkland/FTP-Deploy-Action@v4.3.5
-        with:
-          server: ${{ secrets.PRO_FTP_HOST }}
-          username: ${{ secrets.WEBSITE_FTP_ACCOUNT }}
-          password: ${{ secrets.WEBSITE_FTP_PASSWORD }}
-          local-dir: ./docs/.vitepress/dist/
-
-  deploy-antd-ftp:
-    name: Deploy Push Antd Ftp
-    if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Sed Config Base
-        shell: bash
-        run: |
-          sed -i  "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-antd/.env.production
-          sed -i  "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-antd/.env.production
-          cat ./apps/web-antd/.env.production
-
-      - name: Setup Node
-        uses: ./.github/actions/setup-node
-
-      - name: Build
-        run: pnpm run build:antd
-
-      - name: Sync files
-        uses: SamKirkland/FTP-Deploy-Action@v4.3.5
-        with:
-          server: ${{ secrets.PRO_FTP_HOST }}
-          username: ${{ secrets.WEB_ANTD_FTP_ACCOUNT }}
-          password: ${{ secrets.WEB_ANTD_FTP_PASSWORD }}
-          local-dir: ./apps/web-antd/dist/
-
-  deploy-ele-ftp:
-    name: Deploy Push Element Ftp
-    if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Sed Config Base
-        shell: bash
-        run: |
-          sed -i  "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-ele/.env.production
-          sed -i  "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-ele/.env.production
-          cat ./apps/web-ele/.env.production
-
-      - name: Setup Node
-        uses: ./.github/actions/setup-node
-
-      - name: Build
-        run: pnpm run build:ele
-
-      - name: Sync files
-        uses: SamKirkland/FTP-Deploy-Action@v4.3.5
-        with:
-          server: ${{ secrets.PRO_FTP_HOST }}
-          username: ${{ secrets.WEB_ELE_FTP_ACCOUNT }}
-          password: ${{ secrets.WEB_ELE_FTP_PASSWORD }}
-          local-dir: ./apps/web-ele/dist/
-
-  deploy-naive-ftp:
-    name: Deploy Push Naive Ftp
-    if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      - name: Sed Config Base
-        shell: bash
-        run: |
-          sed -i  "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-naive/.env.production
-          sed -i  "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-naive/.env.production
-          cat ./apps/web-naive/.env.production
-
-      - name: Setup Node
-        uses: ./.github/actions/setup-node
-
-      - name: Build
-        run: pnpm run build:naive
-
-      - name: Sync files
-        uses: SamKirkland/FTP-Deploy-Action@v4.3.5
-        with:
-          server: ${{ secrets.PRO_FTP_HOST }}
-          username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}
-          password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}
-          local-dir: ./apps/web-naive/dist/
-
-  rerun-on-failure:
-    name: Rerun on failure
-    needs:
-      - deploy-playground-ftp
-      - deploy-docs-ftp
-      - deploy-antd-ftp
-      - deploy-ele-ftp
-      - deploy-naive-ftp
-    if: failure() && fromJSON(github.run_attempt) < 10
-    runs-on: ubuntu-latest
-    steps:
-      - name: Retry ${{ fromJSON(github.run_attempt) }} of 10
-        env:
-          GH_REPO: ${{ github.repository }}
-          GH_TOKEN: ${{ github.token }}
-        run: gh workflow run rerun.yml -F run_id=${{ github.run_id }}

+ 0 - 25
.github/workflows/draft.yml

@@ -1,25 +0,0 @@
-name: Release Drafter
-
-on:
-  push:
-    branches:
-      - main
-
-permissions:
-  contents: read
-  pull-requests: write
-
-jobs:
-  update_release_draft:
-    permissions:
-      # write permission is required to create a github release
-      contents: write
-      # write permission is required for autolabeler
-      # otherwise, read permission is required at least
-      pull-requests: write
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      - uses: release-drafter/release-drafter@v6
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 0 - 31
.github/workflows/issue-close-require.yml

@@ -1,31 +0,0 @@
-# 每天零点运行一次,它会检查所有带有 "need reproduction" 标签的 Issues。如果这些 Issues 在过去的 3 天内没有任何活动,它们将会被自动关闭。这有助于保持 Issue 列表的整洁,并且提醒用户在必要时提供更多的信息。
-name: Issue Close Require
-
-# 触发条件:每天零点
-on:
-  workflow_dispatch:
-  schedule:
-    - cron: '0 0 * * *'
-
-permissions:
-  pull-requests: write
-  contents: write
-  issues: write
-
-jobs:
-  close-issues:
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      # 关闭未活动的 Issues
-      - name: Close Inactive Issues
-        uses: actions/stale@v9
-        with:
-          days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
-          stale-issue-label: needs-reproduction # Label that flags an issue as stale.
-          only-labels: needs-reproduction # Only process these issues
-          days-before-issue-close: 3
-          ignore-updates: true
-          remove-stale-when-updated: false
-          close-issue-message: This issue was closed because it was open for 3 days without a valid reproduction.
-          close-issue-label: closed-by-action

+ 0 - 46
.github/workflows/issue-labeled.yml

@@ -1,46 +0,0 @@
-name: Label Based Actions
-
-on:
-  issues:
-    types: [labeled]
-  # pull_request:
-  #   types: [labeled]
-
-permissions:
-  issues: write
-  pull-requests: write
-  contents: write
-
-jobs:
-  reply-labeled:
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      - name: remove enhancement pending
-        if: github.event.label.name == 'enhancement'
-        uses: actions-cool/issues-helper@v3
-        with:
-          actions: 'remove-labels'
-          token: ${{ secrets.GITHUB_TOKEN }}
-          issue-number: ${{ github.event.issue.number }}
-          labels: 'enhancement: pending triage'
-
-      - name: remove bug pending
-        if: github.event.label.name == 'bug'
-        uses: actions-cool/issues-helper@v3
-        with:
-          actions: 'remove-labels'
-          token: ${{ secrets.GITHUB_TOKEN }}
-          issue-number: ${{ github.event.issue.number }}
-          labels: 'bug: pending triage'
-
-      - name: needs reproduction
-        if: github.event.label.name == 'needs reproduction'
-        uses: actions-cool/issues-helper@v3
-        with:
-          actions: 'create-comment, remove-labels'
-          token: ${{ secrets.GITHUB_TOKEN }}
-          issue-number: ${{ github.event.issue.number }}
-          body: |
-            Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `needs reproduction` will be closed if no activities in 3 days.
-          labels: 'bug: pending triage'

+ 0 - 24
.github/workflows/lock.yml

@@ -1,24 +0,0 @@
-name: Lock Threads
-
-on:
-  schedule:
-    - cron: '0 0 * * *'
-  workflow_dispatch:
-
-permissions:
-  issues: write
-  pull-requests: write
-
-jobs:
-  action:
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      - uses: dessant/lock-threads@v5
-        with:
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-          issue-inactive-days: '14'
-          issue-lock-reason: ''
-          pr-inactive-days: '30'
-          pr-lock-reason: ''
-          process-only: 'issues, prs'

+ 0 - 80
.github/workflows/release-tag.yml

@@ -1,80 +0,0 @@
-name: Create Release Tag
-
-on:
-  push:
-    tags:
-      - 'v*.*.*' # Push events to matching v*, i.e. v1.0, v20.15.10
-
-env:
-  HUSKY: '0'
-
-permissions:
-  pull-requests: write
-  contents: write
-
-jobs:
-  build:
-    name: Create Release
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        node-version: [20]
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
-
-      # - name: Checkout code
-      #   uses: actions/checkout@v4
-      #   with:
-      #     fetch-depth: 0
-
-      # - name: Install pnpm
-      #   uses: pnpm/action-setup@v4
-
-      # - name: Use Node.js ${{ matrix.node-version }}
-      #   uses: actions/setup-node@v4
-      #   with:
-      #     node-version: ${{ matrix.node-version }}
-      #     cache: "pnpm"
-
-      # - name: Install dependencies
-      #   run: pnpm install --frozen-lockfile
-
-      # - name: Test and Build
-      #   run: |
-      #     pnpm run test
-      #     pnpm run build
-
-      - name: version
-        id: version
-        run: |
-          tag=${GITHUB_REF/refs\/tags\//}
-          version=${tag#v}
-          major=${version%%.*}
-          echo "tag=${tag}" >> $GITHUB_OUTPUT
-          echo "version=${version}" >> $GITHUB_OUTPUT
-          echo "major=${major}" >> $GITHUB_OUTPUT
-
-      - uses: release-drafter/release-drafter@v6
-        with:
-          version: ${{ steps.version.outputs.version }}
-          publish: true
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-      # - name: force update major tag
-      #   run: |
-      #     git tag v${{ steps.version.outputs.major }} ${{ steps.version.outputs.tag }} -f
-      #     git push origin refs/tags/v${{ steps.version.outputs.major }} -f
-
-      # - name: Create Release for Tag
-      #   id: release_tag
-      #   uses: ncipollo/release-action@v1
-      #   with:
-      #     token: ${{ secrets.GITHUB_TOKEN }}
-      #     generateReleaseNotes: "true"
-      #     body: |
-      #       > Please refer to [CHANGELOG.md](https://github.com/vbenjs/vue-vben-admin/blob/main/CHANGELOG.md) for details.

+ 0 - 19
.github/workflows/rerun.yml

@@ -1,19 +0,0 @@
-name: Rerun workflow
-
-on:
-  workflow_dispatch:
-    inputs:
-      run_id:
-        description: The workflow id to relanch
-        required: true
-jobs:
-  rerun:
-    runs-on: ubuntu-latest
-    steps:
-      - name: rerun ${{ inputs.run_id }}
-        env:
-          GH_REPO: ${{ github.repository }}
-          GH_TOKEN: ${{ github.token }}
-        run: |
-          gh run watch ${{ inputs.run_id }} > /dev/null 2>&1
-          gh run rerun ${{ inputs.run_id }} --failed

+ 0 - 41
.github/workflows/semantic-pull-request.yml

@@ -1,41 +0,0 @@
-name: Semantic Pull Request
-
-on:
-  pull_request_target:
-    types:
-      - opened
-      - edited
-      - synchronize
-
-jobs:
-  main:
-    name: Semantic Pull Request
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      - name: Validate PR title
-        uses: amannn/action-semantic-pull-request@v5
-        with:
-          wip: true
-          subjectPattern: ^(?![A-Z]).+$
-          subjectPatternError: |
-            The subject "{subject}" found in the pull request title "{title}"
-            didn't match the configured pattern. Please ensure that the subject
-            doesn't start with an uppercase character.
-          requireScope: false
-          types: |
-            fix
-            feat
-            docs
-            style
-            refactor
-            perf
-            test
-            build
-            ci
-            chore
-            revert
-            types
-            release
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 0 - 19
.github/workflows/stale.yml

@@ -1,19 +0,0 @@
-name: 'Close stale issues'
-
-on:
-  schedule:
-    - cron: '0 1 * * *'
-
-jobs:
-  stale:
-    if: github.repository == 'vbenjs/vue-vben-admin'
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/stale@v9
-        with:
-          repo-token: ${{ secrets.GITHUB_TOKEN }}
-          stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
-          stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
-          exempt-issue-labels: 'bug,enhancement'
-          days-before-stale: 60
-          days-before-close: 7

+ 0 - 3
apps/backend-mock/.env

@@ -1,3 +0,0 @@
-PORT=5320
-ACCESS_TOKEN_SECRET=access_token_secret
-REFRESH_TOKEN_SECRET=refresh_token_secret

+ 0 - 15
apps/backend-mock/README.md

@@ -1,15 +0,0 @@
-# @vben/backend-mock
-
-## Description
-
-Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。
-
-## Running the app
-
-```bash
-# development
-$ pnpm run start
-
-# production mode
-$ pnpm run build
-```

+ 0 - 16
apps/backend-mock/api/auth/codes.ts

@@ -1,16 +0,0 @@
-import { eventHandler } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import { MOCK_CODES } from '~/utils/mock-data';
-import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
-
-export default eventHandler((event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-
-  const codes =
-    MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? [];
-
-  return useResponseSuccess(codes);
-});

+ 0 - 42
apps/backend-mock/api/auth/login.post.ts

@@ -1,42 +0,0 @@
-import { defineEventHandler, readBody, setResponseStatus } from 'h3';
-import {
-  clearRefreshTokenCookie,
-  setRefreshTokenCookie,
-} from '~/utils/cookie-utils';
-import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils';
-import { MOCK_USERS } from '~/utils/mock-data';
-import {
-  forbiddenResponse,
-  useResponseError,
-  useResponseSuccess,
-} from '~/utils/response';
-
-export default defineEventHandler(async (event) => {
-  const { password, username } = await readBody(event);
-  if (!password || !username) {
-    setResponseStatus(event, 400);
-    return useResponseError(
-      'BadRequestException',
-      'Username and password are required',
-    );
-  }
-
-  const findUser = MOCK_USERS.find(
-    (item) => item.username === username && item.password === password,
-  );
-
-  if (!findUser) {
-    clearRefreshTokenCookie(event);
-    return forbiddenResponse(event, 'Username or password is incorrect.');
-  }
-
-  const accessToken = generateAccessToken(findUser);
-  const refreshToken = generateRefreshToken(findUser);
-
-  setRefreshTokenCookie(event, refreshToken);
-
-  return useResponseSuccess({
-    ...findUser,
-    accessToken,
-  });
-});

+ 0 - 17
apps/backend-mock/api/auth/logout.post.ts

@@ -1,17 +0,0 @@
-import { defineEventHandler } from 'h3';
-import {
-  clearRefreshTokenCookie,
-  getRefreshTokenFromCookie,
-} from '~/utils/cookie-utils';
-import { useResponseSuccess } from '~/utils/response';
-
-export default defineEventHandler(async (event) => {
-  const refreshToken = getRefreshTokenFromCookie(event);
-  if (!refreshToken) {
-    return useResponseSuccess('');
-  }
-
-  clearRefreshTokenCookie(event);
-
-  return useResponseSuccess('');
-});

+ 0 - 35
apps/backend-mock/api/auth/refresh.post.ts

@@ -1,35 +0,0 @@
-import { defineEventHandler } from 'h3';
-import {
-  clearRefreshTokenCookie,
-  getRefreshTokenFromCookie,
-  setRefreshTokenCookie,
-} from '~/utils/cookie-utils';
-import { generateAccessToken, verifyRefreshToken } from '~/utils/jwt-utils';
-import { MOCK_USERS } from '~/utils/mock-data';
-import { forbiddenResponse } from '~/utils/response';
-
-export default defineEventHandler(async (event) => {
-  const refreshToken = getRefreshTokenFromCookie(event);
-  if (!refreshToken) {
-    return forbiddenResponse(event);
-  }
-
-  clearRefreshTokenCookie(event);
-
-  const userinfo = verifyRefreshToken(refreshToken);
-  if (!userinfo) {
-    return forbiddenResponse(event);
-  }
-
-  const findUser = MOCK_USERS.find(
-    (item) => item.username === userinfo.username,
-  );
-  if (!findUser) {
-    return forbiddenResponse(event);
-  }
-  const accessToken = generateAccessToken(findUser);
-
-  setRefreshTokenCookie(event, refreshToken);
-
-  return accessToken;
-});

+ 0 - 32
apps/backend-mock/api/demo/bigint.ts

@@ -1,32 +0,0 @@
-import { eventHandler, setHeader } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import { unAuthorizedResponse } from '~/utils/response';
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-  const data = `
-  {
-    "code": 0,
-    "message": "success",
-    "data": [
-              {
-                "id": 123456789012345678901234567890123456789012345678901234567890,
-                "name": "John Doe",
-                "age": 30,
-                "email": "john-doe@demo.com"
-                },
-                {
-                "id": 987654321098765432109876543210987654321098765432109876543210,
-                "name": "Jane Smith",
-                "age": 25,
-                "email": "jane@demo.com"
-                }
-            ]
-  }
-  `;
-  setHeader(event, 'Content-Type', 'application/json');
-  return data;
-});

+ 0 - 15
apps/backend-mock/api/menu/all.ts

@@ -1,15 +0,0 @@
-import { eventHandler } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import { MOCK_MENUS } from '~/utils/mock-data';
-import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-
-  const menus =
-    MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? [];
-  return useResponseSuccess(menus);
-});

+ 0 - 8
apps/backend-mock/api/status.ts

@@ -1,8 +0,0 @@
-import { eventHandler, getQuery, setResponseStatus } from 'h3';
-import { useResponseError } from '~/utils/response';
-
-export default eventHandler((event) => {
-  const { status } = getQuery(event);
-  setResponseStatus(event, Number(status));
-  return useResponseError(`${status}`);
-});

+ 0 - 16
apps/backend-mock/api/system/dept/.post.ts

@@ -1,16 +0,0 @@
-import { eventHandler } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import {
-  sleep,
-  unAuthorizedResponse,
-  useResponseSuccess,
-} from '~/utils/response';
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-  await sleep(600);
-  return useResponseSuccess(null);
-});

+ 0 - 16
apps/backend-mock/api/system/dept/[id].delete.ts

@@ -1,16 +0,0 @@
-import { eventHandler } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import {
-  sleep,
-  unAuthorizedResponse,
-  useResponseSuccess,
-} from '~/utils/response';
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-  await sleep(1000);
-  return useResponseSuccess(null);
-});

+ 0 - 16
apps/backend-mock/api/system/dept/[id].put.ts

@@ -1,16 +0,0 @@
-import { eventHandler } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import {
-  sleep,
-  unAuthorizedResponse,
-  useResponseSuccess,
-} from '~/utils/response';
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-  await sleep(2000);
-  return useResponseSuccess(null);
-});

+ 0 - 62
apps/backend-mock/api/system/dept/list.ts

@@ -1,62 +0,0 @@
-import { faker } from '@faker-js/faker';
-import { eventHandler } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
-
-const formatterCN = new Intl.DateTimeFormat('zh-CN', {
-  timeZone: 'Asia/Shanghai',
-  year: 'numeric',
-  month: '2-digit',
-  day: '2-digit',
-  hour: '2-digit',
-  minute: '2-digit',
-  second: '2-digit',
-});
-
-function generateMockDataList(count: number) {
-  const dataList = [];
-
-  for (let i = 0; i < count; i++) {
-    const dataItem: Record<string, any> = {
-      id: faker.string.uuid(),
-      pid: 0,
-      name: faker.commerce.department(),
-      status: faker.helpers.arrayElement([0, 1]),
-      createTime: formatterCN.format(
-        faker.date.between({ from: '2021-01-01', to: '2022-12-31' }),
-      ),
-      remark: faker.lorem.sentence(),
-    };
-    if (faker.datatype.boolean()) {
-      dataItem.children = Array.from(
-        { length: faker.number.int({ min: 1, max: 5 }) },
-        () => ({
-          id: faker.string.uuid(),
-          pid: dataItem.id,
-          name: faker.commerce.department(),
-          status: faker.helpers.arrayElement([0, 1]),
-          createTime: formatterCN.format(
-            faker.date.between({ from: '2023-01-01', to: '2023-12-31' }),
-          ),
-          remark: faker.lorem.sentence(),
-        }),
-      );
-    }
-    dataList.push(dataItem);
-  }
-
-  return dataList;
-}
-
-const mockData = generateMockDataList(10);
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-
-  const listData = structuredClone(mockData);
-
-  return useResponseSuccess(listData);
-});

+ 0 - 13
apps/backend-mock/api/system/menu/list.ts

@@ -1,13 +0,0 @@
-import { eventHandler } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import { MOCK_MENU_LIST } from '~/utils/mock-data';
-import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-
-  return useResponseSuccess(MOCK_MENU_LIST);
-});

+ 0 - 29
apps/backend-mock/api/system/menu/name-exists.ts

@@ -1,29 +0,0 @@
-import { eventHandler, getQuery } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import { MOCK_MENU_LIST } from '~/utils/mock-data';
-import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
-
-const namesMap: Record<string, any> = {};
-
-function getNames(menus: any[]) {
-  menus.forEach((menu) => {
-    namesMap[menu.name] = String(menu.id);
-    if (menu.children) {
-      getNames(menu.children);
-    }
-  });
-}
-getNames(MOCK_MENU_LIST);
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-  const { id, name } = getQuery(event);
-
-  return (name as string) in namesMap &&
-    (!id || namesMap[name as string] !== String(id))
-    ? useResponseSuccess(true)
-    : useResponseSuccess(false);
-});

+ 0 - 29
apps/backend-mock/api/system/menu/path-exists.ts

@@ -1,29 +0,0 @@
-import { eventHandler, getQuery } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import { MOCK_MENU_LIST } from '~/utils/mock-data';
-import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
-
-const pathMap: Record<string, any> = { '/': 0 };
-
-function getPaths(menus: any[]) {
-  menus.forEach((menu) => {
-    pathMap[menu.path] = String(menu.id);
-    if (menu.children) {
-      getPaths(menu.children);
-    }
-  });
-}
-getPaths(MOCK_MENU_LIST);
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-  const { id, path } = getQuery(event);
-
-  return (path as string) in pathMap &&
-    (!id || pathMap[path as string] !== String(id))
-    ? useResponseSuccess(true)
-    : useResponseSuccess(false);
-});

+ 0 - 84
apps/backend-mock/api/system/role/list.ts

@@ -1,84 +0,0 @@
-import { faker } from '@faker-js/faker';
-import { eventHandler, getQuery } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data';
-import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
-
-const formatterCN = new Intl.DateTimeFormat('zh-CN', {
-  timeZone: 'Asia/Shanghai',
-  year: 'numeric',
-  month: '2-digit',
-  day: '2-digit',
-  hour: '2-digit',
-  minute: '2-digit',
-  second: '2-digit',
-});
-
-const menuIds = getMenuIds(MOCK_MENU_LIST);
-
-function generateMockDataList(count: number) {
-  const dataList = [];
-
-  for (let i = 0; i < count; i++) {
-    const dataItem: Record<string, any> = {
-      id: faker.string.uuid(),
-      name: faker.commerce.product(),
-      status: faker.helpers.arrayElement([0, 1]),
-      createTime: formatterCN.format(
-        faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),
-      ),
-      permissions: faker.helpers.arrayElements(menuIds),
-      remark: faker.lorem.sentence(),
-    };
-
-    dataList.push(dataItem);
-  }
-
-  return dataList;
-}
-
-const mockData = generateMockDataList(100);
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-
-  const {
-    page = 1,
-    pageSize = 20,
-    name,
-    id,
-    remark,
-    startTime,
-    endTime,
-    status,
-  } = getQuery(event);
-  let listData = structuredClone(mockData);
-  if (name) {
-    listData = listData.filter((item) =>
-      item.name.toLowerCase().includes(String(name).toLowerCase()),
-    );
-  }
-  if (id) {
-    listData = listData.filter((item) =>
-      item.id.toLowerCase().includes(String(id).toLowerCase()),
-    );
-  }
-  if (remark) {
-    listData = listData.filter((item) =>
-      item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),
-    );
-  }
-  if (startTime) {
-    listData = listData.filter((item) => item.createTime >= startTime);
-  }
-  if (endTime) {
-    listData = listData.filter((item) => item.createTime <= endTime);
-  }
-  if (['0', '1'].includes(status as string)) {
-    listData = listData.filter((item) => item.status === Number(status));
-  }
-  return usePageResponseSuccess(page as string, pageSize as string, listData);
-});

+ 0 - 117
apps/backend-mock/api/table/list.ts

@@ -1,117 +0,0 @@
-import { faker } from '@faker-js/faker';
-import { eventHandler, getQuery } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import {
-  sleep,
-  unAuthorizedResponse,
-  usePageResponseSuccess,
-} from '~/utils/response';
-
-function generateMockDataList(count: number) {
-  const dataList = [];
-
-  for (let i = 0; i < count; i++) {
-    const dataItem = {
-      id: faker.string.uuid(),
-      imageUrl: faker.image.avatar(),
-      imageUrl2: faker.image.avatar(),
-      open: faker.datatype.boolean(),
-      status: faker.helpers.arrayElement(['success', 'error', 'warning']),
-      productName: faker.commerce.productName(),
-      price: faker.commerce.price(),
-      currency: faker.finance.currencyCode(),
-      quantity: faker.number.int({ min: 1, max: 100 }),
-      available: faker.datatype.boolean(),
-      category: faker.commerce.department(),
-      releaseDate: faker.date.past(),
-      rating: faker.number.float({ min: 1, max: 5 }),
-      description: faker.commerce.productDescription(),
-      weight: faker.number.float({ min: 0.1, max: 10 }),
-      color: faker.color.human(),
-      inProduction: faker.datatype.boolean(),
-      tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()),
-    };
-
-    dataList.push(dataItem);
-  }
-
-  return dataList;
-}
-
-const mockData = generateMockDataList(100);
-
-export default eventHandler(async (event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-
-  await sleep(600);
-
-  const { page, pageSize, sortBy, sortOrder } = getQuery(event);
-  // 规范化分页参数,处理 string[]
-  const pageRaw = Array.isArray(page) ? page[0] : page;
-  const pageSizeRaw = Array.isArray(pageSize) ? pageSize[0] : pageSize;
-  const pageNumber = Math.max(
-    1,
-    Number.parseInt(String(pageRaw ?? '1'), 10) || 1,
-  );
-  const pageSizeNumber = Math.min(
-    100,
-    Math.max(1, Number.parseInt(String(pageSizeRaw ?? '10'), 10) || 10),
-  );
-  const listData = structuredClone(mockData);
-
-  // 规范化 query 入参,兼容 string[]
-  const sortKeyRaw = Array.isArray(sortBy) ? sortBy[0] : sortBy;
-  const sortOrderRaw = Array.isArray(sortOrder) ? sortOrder[0] : sortOrder;
-  // 检查 sortBy 是否是 listData 元素的合法属性键
-  if (
-    typeof sortKeyRaw === 'string' &&
-    listData[0] &&
-    Object.prototype.hasOwnProperty.call(listData[0], sortKeyRaw)
-  ) {
-    // 定义数组元素的类型
-    type ItemType = (typeof listData)[0];
-    const sortKey = sortKeyRaw as keyof ItemType; // 将 sortBy 断言为合法键
-    const isDesc = sortOrderRaw === 'desc';
-    listData.sort((a, b) => {
-      const aValue = a[sortKey] as unknown;
-      const bValue = b[sortKey] as unknown;
-
-      let result = 0;
-
-      if (typeof aValue === 'number' && typeof bValue === 'number') {
-        result = aValue - bValue;
-      } else if (aValue instanceof Date && bValue instanceof Date) {
-        result = aValue.getTime() - bValue.getTime();
-      } else if (typeof aValue === 'boolean' && typeof bValue === 'boolean') {
-        if (aValue === bValue) {
-          result = 0;
-        } else {
-          result = aValue ? 1 : -1;
-        }
-      } else {
-        const aStr = String(aValue);
-        const bStr = String(bValue);
-        const aNum = Number(aStr);
-        const bNum = Number(bStr);
-        result =
-          Number.isFinite(aNum) && Number.isFinite(bNum)
-            ? aNum - bNum
-            : aStr.localeCompare(bStr, undefined, {
-                numeric: true,
-                sensitivity: 'base',
-              });
-      }
-
-      return isDesc ? -result : result;
-    });
-  }
-
-  return usePageResponseSuccess(
-    String(pageNumber),
-    String(pageSizeNumber),
-    listData,
-  );
-});

+ 0 - 3
apps/backend-mock/api/test.get.ts

@@ -1,3 +0,0 @@
-import { defineEventHandler } from 'h3';
-
-export default defineEventHandler(() => 'Test get handler');

+ 0 - 3
apps/backend-mock/api/test.post.ts

@@ -1,3 +0,0 @@
-import { defineEventHandler } from 'h3';
-
-export default defineEventHandler(() => 'Test post handler');

+ 0 - 14
apps/backend-mock/api/upload.ts

@@ -1,14 +0,0 @@
-import { eventHandler } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
-
-export default eventHandler((event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-  return useResponseSuccess({
-    url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
-  });
-  // return useResponseError("test")
-});

+ 0 - 11
apps/backend-mock/api/user/info.ts

@@ -1,11 +0,0 @@
-import { eventHandler } from 'h3';
-import { verifyAccessToken } from '~/utils/jwt-utils';
-import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
-
-export default eventHandler((event) => {
-  const userinfo = verifyAccessToken(event);
-  if (!userinfo) {
-    return unAuthorizedResponse(event);
-  }
-  return useResponseSuccess(userinfo);
-});

+ 0 - 7
apps/backend-mock/error.ts

@@ -1,7 +0,0 @@
-import type { NitroErrorHandler } from 'nitropack';
-
-const errorHandler: NitroErrorHandler = function (error, event) {
-  event.node.res.end(`[Error Handler] ${error.stack}`);
-};
-
-export default errorHandler;

+ 0 - 20
apps/backend-mock/middleware/1.api.ts

@@ -1,20 +0,0 @@
-import { defineEventHandler } from 'h3';
-import { forbiddenResponse, sleep } from '~/utils/response';
-
-export default defineEventHandler(async (event) => {
-  event.node.res.setHeader(
-    'Access-Control-Allow-Origin',
-    event.headers.get('Origin') ?? '*',
-  );
-  if (event.method === 'OPTIONS') {
-    event.node.res.statusCode = 204;
-    event.node.res.statusMessage = 'No Content.';
-    return 'OK';
-  } else if (
-    ['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) &&
-    event.path.startsWith('/api/system/')
-  ) {
-    await sleep(Math.floor(Math.random() * 2000));
-    return forbiddenResponse(event, '演示环境,禁止修改');
-  }
-});

+ 0 - 20
apps/backend-mock/nitro.config.ts

@@ -1,20 +0,0 @@
-import errorHandler from './error';
-
-process.env.COMPATIBILITY_DATE = new Date().toISOString();
-export default defineNitroConfig({
-  devErrorHandler: errorHandler,
-  errorHandler: '~/error',
-  routeRules: {
-    '/api/**': {
-      cors: true,
-      headers: {
-        'Access-Control-Allow-Credentials': 'true',
-        'Access-Control-Allow-Headers':
-          'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
-        'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
-        'Access-Control-Allow-Origin': '*',
-        'Access-Control-Expose-Headers': '*',
-      },
-    },
-  },
-});

+ 0 - 21
apps/backend-mock/package.json

@@ -1,21 +0,0 @@
-{
-  "name": "@vben/backend-mock",
-  "version": "0.0.1",
-  "description": "",
-  "private": true,
-  "license": "MIT",
-  "author": "",
-  "scripts": {
-    "build": "nitro build",
-    "start": "nitro dev"
-  },
-  "dependencies": {
-    "@faker-js/faker": "catalog:",
-    "jsonwebtoken": "catalog:",
-    "nitropack": "catalog:"
-  },
-  "devDependencies": {
-    "@types/jsonwebtoken": "catalog:",
-    "h3": "catalog:"
-  }
-}

+ 0 - 15
apps/backend-mock/routes/[...].ts

@@ -1,15 +0,0 @@
-import { defineEventHandler } from 'h3';
-
-export default defineEventHandler(() => {
-  return `
-<h1>Hello Vben Admin</h1>
-<h2>Mock service is starting</h2>
-<ul>
-<li><a href="/api/user">/api/user/info</a></li>
-<li><a href="/api/menu">/api/menu/all</a></li>
-<li><a href="/api/auth/codes">/api/auth/codes</a></li>
-<li><a href="/api/auth/login">/api/auth/login</a></li>
-<li><a href="/api/upload">/api/upload</a></li>
-</ul>
-`;
-});

+ 0 - 4
apps/backend-mock/tsconfig.build.json

@@ -1,4 +0,0 @@
-{
-  "extends": "./tsconfig.json",
-  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
-}

+ 0 - 3
apps/backend-mock/tsconfig.json

@@ -1,3 +0,0 @@
-{
-  "extends": "./.nitro/types/tsconfig.json"
-}

+ 0 - 28
apps/backend-mock/utils/cookie-utils.ts

@@ -1,28 +0,0 @@
-import type { EventHandlerRequest, H3Event } from 'h3';
-
-import { deleteCookie, getCookie, setCookie } from 'h3';
-
-export function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) {
-  deleteCookie(event, 'jwt', {
-    httpOnly: true,
-    sameSite: 'none',
-    secure: true,
-  });
-}
-
-export function setRefreshTokenCookie(
-  event: H3Event<EventHandlerRequest>,
-  refreshToken: string,
-) {
-  setCookie(event, 'jwt', refreshToken, {
-    httpOnly: true,
-    maxAge: 24 * 60 * 60, // unit: seconds
-    sameSite: 'none',
-    secure: true,
-  });
-}
-
-export function getRefreshTokenFromCookie(event: H3Event<EventHandlerRequest>) {
-  const refreshToken = getCookie(event, 'jwt');
-  return refreshToken;
-}

+ 0 - 77
apps/backend-mock/utils/jwt-utils.ts

@@ -1,77 +0,0 @@
-import type { EventHandlerRequest, H3Event } from 'h3';
-
-import type { UserInfo } from './mock-data';
-
-import { getHeader } from 'h3';
-import jwt from 'jsonwebtoken';
-
-import { MOCK_USERS } from './mock-data';
-
-// TODO: Replace with your own secret key
-const ACCESS_TOKEN_SECRET = 'access_token_secret';
-const REFRESH_TOKEN_SECRET = 'refresh_token_secret';
-
-export interface UserPayload extends UserInfo {
-  iat: number;
-  exp: number;
-}
-
-export function generateAccessToken(user: UserInfo) {
-  return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' });
-}
-
-export function generateRefreshToken(user: UserInfo) {
-  return jwt.sign(user, REFRESH_TOKEN_SECRET, {
-    expiresIn: '30d',
-  });
-}
-
-export function verifyAccessToken(
-  event: H3Event<EventHandlerRequest>,
-): null | Omit<UserInfo, 'password'> {
-  const authHeader = getHeader(event, 'Authorization');
-  if (!authHeader?.startsWith('Bearer')) {
-    return null;
-  }
-
-  const tokenParts = authHeader.split(' ');
-  if (tokenParts.length !== 2) {
-    return null;
-  }
-  const token = tokenParts[1] as string;
-  try {
-    const decoded = jwt.verify(
-      token,
-      ACCESS_TOKEN_SECRET,
-    ) as unknown as UserPayload;
-
-    const username = decoded.username;
-    const user = MOCK_USERS.find((item) => item.username === username);
-    if (!user) {
-      return null;
-    }
-    const { password: _pwd, ...userinfo } = user;
-    return userinfo;
-  } catch {
-    return null;
-  }
-}
-
-export function verifyRefreshToken(
-  token: string,
-): null | Omit<UserInfo, 'password'> {
-  try {
-    const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload;
-    const username = decoded.username;
-    const user = MOCK_USERS.find(
-      (item) => item.username === username,
-    ) as UserInfo;
-    if (!user) {
-      return null;
-    }
-    const { password: _pwd, ...userinfo } = user;
-    return userinfo;
-  } catch {
-    return null;
-  }
-}

+ 0 - 390
apps/backend-mock/utils/mock-data.ts

@@ -1,390 +0,0 @@
-export interface UserInfo {
-  id: number;
-  password: string;
-  realName: string;
-  roles: string[];
-  username: string;
-  homePath?: string;
-}
-
-export const MOCK_USERS: UserInfo[] = [
-  {
-    id: 0,
-    password: '123456',
-    realName: 'Vben',
-    roles: ['super'],
-    username: 'vben',
-  },
-  {
-    id: 1,
-    password: '123456',
-    realName: 'Admin',
-    roles: ['admin'],
-    username: 'admin',
-    homePath: '/workspace',
-  },
-  {
-    id: 2,
-    password: '123456',
-    realName: 'Jack',
-    roles: ['user'],
-    username: 'jack',
-    homePath: '/analytics',
-  },
-];
-
-export const MOCK_CODES = [
-  // super
-  {
-    codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'],
-    username: 'vben',
-  },
-  {
-    // admin
-    codes: ['AC_100010', 'AC_100020', 'AC_100030'],
-    username: 'admin',
-  },
-  {
-    // user
-    codes: ['AC_1000001', 'AC_1000002'],
-    username: 'jack',
-  },
-];
-
-const dashboardMenus = [
-  {
-    meta: {
-      order: -1,
-      title: 'page.dashboard.title',
-    },
-    name: 'Dashboard',
-    path: '/dashboard',
-    redirect: '/analytics',
-    children: [
-      {
-        name: 'Analytics',
-        path: '/analytics',
-        component: '/dashboard/analytics/index',
-        meta: {
-          affixTab: true,
-          title: 'page.dashboard.analytics',
-        },
-      },
-      {
-        name: 'Workspace',
-        path: '/workspace',
-        component: '/dashboard/workspace/index',
-        meta: {
-          title: 'page.dashboard.workspace',
-        },
-      },
-    ],
-  },
-];
-
-const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
-  const roleWithMenus = {
-    admin: {
-      component: '/demos/access/admin-visible',
-      meta: {
-        icon: 'mdi:button-cursor',
-        title: 'demos.access.adminVisible',
-      },
-      name: 'AccessAdminVisibleDemo',
-      path: '/demos/access/admin-visible',
-    },
-    super: {
-      component: '/demos/access/super-visible',
-      meta: {
-        icon: 'mdi:button-cursor',
-        title: 'demos.access.superVisible',
-      },
-      name: 'AccessSuperVisibleDemo',
-      path: '/demos/access/super-visible',
-    },
-    user: {
-      component: '/demos/access/user-visible',
-      meta: {
-        icon: 'mdi:button-cursor',
-        title: 'demos.access.userVisible',
-      },
-      name: 'AccessUserVisibleDemo',
-      path: '/demos/access/user-visible',
-    },
-  };
-
-  return [
-    {
-      meta: {
-        icon: 'ic:baseline-view-in-ar',
-        keepAlive: true,
-        order: 1000,
-        title: 'demos.title',
-      },
-      name: 'Demos',
-      path: '/demos',
-      redirect: '/demos/access',
-      children: [
-        {
-          name: 'AccessDemos',
-          path: '/demosaccess',
-          meta: {
-            icon: 'mdi:cloud-key-outline',
-            title: 'demos.access.backendPermissions',
-          },
-          redirect: '/demos/access/page-control',
-          children: [
-            {
-              name: 'AccessPageControlDemo',
-              path: '/demos/access/page-control',
-              component: '/demos/access/index',
-              meta: {
-                icon: 'mdi:page-previous-outline',
-                title: 'demos.access.pageAccess',
-              },
-            },
-            {
-              name: 'AccessButtonControlDemo',
-              path: '/demos/access/button-control',
-              component: '/demos/access/button-control',
-              meta: {
-                icon: 'mdi:button-cursor',
-                title: 'demos.access.buttonControl',
-              },
-            },
-            {
-              name: 'AccessMenuVisible403Demo',
-              path: '/demos/access/menu-visible-403',
-              component: '/demos/access/menu-visible-403',
-              meta: {
-                authority: ['no-body'],
-                icon: 'mdi:button-cursor',
-                menuVisibleWithForbidden: true,
-                title: 'demos.access.menuVisible403',
-              },
-            },
-            roleWithMenus[role],
-          ],
-        },
-      ],
-    },
-  ];
-};
-
-export const MOCK_MENUS = [
-  {
-    menus: [...dashboardMenus, ...createDemosMenus('super')],
-    username: 'vben',
-  },
-  {
-    menus: [...dashboardMenus, ...createDemosMenus('admin')],
-    username: 'admin',
-  },
-  {
-    menus: [...dashboardMenus, ...createDemosMenus('user')],
-    username: 'jack',
-  },
-];
-
-export const MOCK_MENU_LIST = [
-  {
-    id: 1,
-    name: 'Workspace',
-    status: 1,
-    type: 'menu',
-    icon: 'mdi:dashboard',
-    path: '/workspace',
-    component: '/dashboard/workspace/index',
-    meta: {
-      icon: 'carbon:workspace',
-      title: 'page.dashboard.workspace',
-      affixTab: true,
-      order: 0,
-    },
-  },
-  {
-    id: 2,
-    meta: {
-      icon: 'carbon:settings',
-      order: 9997,
-      title: 'system.title',
-      badge: 'new',
-      badgeType: 'normal',
-      badgeVariants: 'primary',
-    },
-    status: 1,
-    type: 'catalog',
-    name: 'System',
-    path: '/system',
-    children: [
-      {
-        id: 201,
-        pid: 2,
-        path: '/system/menu',
-        name: 'SystemMenu',
-        authCode: 'System:Menu:List',
-        status: 1,
-        type: 'menu',
-        meta: {
-          icon: 'carbon:menu',
-          title: 'system.menu.title',
-        },
-        component: '/system/menu/list',
-        children: [
-          {
-            id: 20_101,
-            pid: 201,
-            name: 'SystemMenuCreate',
-            status: 1,
-            type: 'button',
-            authCode: 'System:Menu:Create',
-            meta: { title: 'common.create' },
-          },
-          {
-            id: 20_102,
-            pid: 201,
-            name: 'SystemMenuEdit',
-            status: 1,
-            type: 'button',
-            authCode: 'System:Menu:Edit',
-            meta: { title: 'common.edit' },
-          },
-          {
-            id: 20_103,
-            pid: 201,
-            name: 'SystemMenuDelete',
-            status: 1,
-            type: 'button',
-            authCode: 'System:Menu:Delete',
-            meta: { title: 'common.delete' },
-          },
-        ],
-      },
-      {
-        id: 202,
-        pid: 2,
-        path: '/system/dept',
-        name: 'SystemDept',
-        status: 1,
-        type: 'menu',
-        authCode: 'System:Dept:List',
-        meta: {
-          icon: 'carbon:container-services',
-          title: 'system.dept.title',
-        },
-        component: '/system/dept/list',
-        children: [
-          {
-            id: 20_401,
-            pid: 201,
-            name: 'SystemDeptCreate',
-            status: 1,
-            type: 'button',
-            authCode: 'System:Dept:Create',
-            meta: { title: 'common.create' },
-          },
-          {
-            id: 20_402,
-            pid: 201,
-            name: 'SystemDeptEdit',
-            status: 1,
-            type: 'button',
-            authCode: 'System:Dept:Edit',
-            meta: { title: 'common.edit' },
-          },
-          {
-            id: 20_403,
-            pid: 201,
-            name: 'SystemDeptDelete',
-            status: 1,
-            type: 'button',
-            authCode: 'System:Dept:Delete',
-            meta: { title: 'common.delete' },
-          },
-        ],
-      },
-    ],
-  },
-  {
-    id: 9,
-    meta: {
-      badgeType: 'dot',
-      order: 9998,
-      title: 'demos.vben.title',
-      icon: 'carbon:data-center',
-    },
-    name: 'Project',
-    path: '/vben-admin',
-    type: 'catalog',
-    status: 1,
-    children: [
-      {
-        id: 901,
-        pid: 9,
-        name: 'VbenDocument',
-        path: '/vben-admin/document',
-        component: 'IFrameView',
-        type: 'embedded',
-        status: 1,
-        meta: {
-          icon: 'carbon:book',
-          iframeSrc: 'https://doc.vben.pro',
-          title: 'demos.vben.document',
-        },
-      },
-      {
-        id: 902,
-        pid: 9,
-        name: 'VbenGithub',
-        path: '/vben-admin/github',
-        component: 'IFrameView',
-        type: 'link',
-        status: 1,
-        meta: {
-          icon: 'carbon:logo-github',
-          link: 'https://github.com/vbenjs/vue-vben-admin',
-          title: 'Github',
-        },
-      },
-      {
-        id: 903,
-        pid: 9,
-        name: 'VbenAntdv',
-        path: '/vben-admin/antdv',
-        component: 'IFrameView',
-        type: 'link',
-        status: 0,
-        meta: {
-          icon: 'carbon:hexagon-vertical-solid',
-          badgeType: 'dot',
-          link: 'https://ant.vben.pro',
-          title: 'demos.vben.antdv',
-        },
-      },
-    ],
-  },
-  {
-    id: 10,
-    component: '_core/about/index',
-    type: 'menu',
-    status: 1,
-    meta: {
-      icon: 'lucide:copyright',
-      order: 9999,
-      title: 'demos.vben.about',
-    },
-    name: 'About',
-    path: '/about',
-  },
-];
-
-export function getMenuIds(menus: any[]) {
-  const ids: number[] = [];
-  menus.forEach((item) => {
-    ids.push(item.id);
-    if (item.children && item.children.length > 0) {
-      ids.push(...getMenuIds(item.children));
-    }
-  });
-  return ids;
-}

+ 0 - 70
apps/backend-mock/utils/response.ts

@@ -1,70 +0,0 @@
-import type { EventHandlerRequest, H3Event } from 'h3';
-
-import { setResponseStatus } from 'h3';
-
-export function useResponseSuccess<T = any>(data: T) {
-  return {
-    code: 0,
-    data,
-    error: null,
-    message: 'ok',
-  };
-}
-
-export function usePageResponseSuccess<T = any>(
-  page: number | string,
-  pageSize: number | string,
-  list: T[],
-  { message = 'ok' } = {},
-) {
-  const pageData = pagination(
-    Number.parseInt(`${page}`),
-    Number.parseInt(`${pageSize}`),
-    list,
-  );
-
-  return {
-    ...useResponseSuccess({
-      items: pageData,
-      total: list.length,
-    }),
-    message,
-  };
-}
-
-export function useResponseError(message: string, error: any = null) {
-  return {
-    code: -1,
-    data: null,
-    error,
-    message,
-  };
-}
-
-export function forbiddenResponse(
-  event: H3Event<EventHandlerRequest>,
-  message = 'Forbidden Exception',
-) {
-  setResponseStatus(event, 403);
-  return useResponseError(message, message);
-}
-
-export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
-  setResponseStatus(event, 401);
-  return useResponseError('Unauthorized Exception', 'Unauthorized Exception');
-}
-
-export function sleep(ms: number) {
-  return new Promise((resolve) => setTimeout(resolve, ms));
-}
-
-export function pagination<T = any>(
-  pageNo: number,
-  pageSize: number,
-  array: T[],
-): T[] {
-  const offset = (pageNo - 1) * Number(pageSize);
-  return offset + Number(pageSize) >= array.length
-    ? array.slice(offset)
-    : array.slice(offset, offset + Number(pageSize));
-}

+ 2 - 2
apps/web-antd/.env → apps/health-remedy/.env

@@ -1,8 +1,8 @@
 # 应用标题
-VITE_APP_TITLE=Vben Admin Antd
+VITE_APP_TITLE=中医智能辅助诊疗系统
 
 # 应用命名空间,用于缓存、store等功能的前缀,确保隔离
-VITE_APP_NAMESPACE=vben-web-antd
+VITE_APP_NAMESPACE=@six.admin/health_remedy
 
 # 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
 VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key

+ 4 - 5
apps/web-antd/.env.development → apps/health-remedy/.env.development

@@ -4,13 +4,12 @@ VITE_PORT=5666
 VITE_BASE=/
 
 # 接口地址
-VITE_GLOB_API_URL=/api
-
-# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
-VITE_NITRO_MOCK=true
+VITE_GLOB_API_URL=
+# 智能导诊接口地址
+VITE_GLOB_API_HEALTH_REMEDY=/dz
 
 # 是否打开 devtools,true 为打开,false 为关闭
-VITE_DEVTOOLS=false
+VITE_DEVTOOLS=true
 
 # 是否注入全局loading
 VITE_INJECT_APP_LOADING=true

+ 5 - 3
apps/web-antd/.env.production → apps/health-remedy/.env.production

@@ -1,10 +1,12 @@
-VITE_BASE=/
+VITE_BASE=/dz/p/
 
 # 接口地址
-VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
+VITE_GLOB_API_URL=
+# 智能导诊接口地址
+VITE_GLOB_API_HOSPITAL_GUIDE=/dz
 
 # 是否开启压缩,可以设置为 none, brotli, gzip
-VITE_COMPRESS=none
+VITE_COMPRESS=brotli,gzip
 
 # 是否开启 PWA
 VITE_PWA=false

+ 18 - 0
apps/health-remedy/index.html

@@ -0,0 +1,18 @@
+<!doctype html>
+<html lang="zh">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <meta name="renderer" content="webkit" />
+    <meta
+      name="viewport"
+      content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
+    />
+    <title><%= VITE_APP_TITLE %></title>
+    <link rel="icon" href="/favicon.ico" />
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 11 - 15
apps/web-antd/package.json → apps/health-remedy/package.json

@@ -1,19 +1,9 @@
 {
-  "name": "@vben/web-antd",
-  "version": "5.5.9",
-  "homepage": "https://vben.pro",
-  "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/vbenjs/vue-vben-admin.git",
-    "directory": "apps/web-antd"
-  },
+  "name": "@six/health-remedy",
+  "version": "0.0.0",
+  "homepage": "",
+  "bugs": "",
   "license": "MIT",
-  "author": {
-    "name": "vben",
-    "email": "ann.vben@gmail.com",
-    "url": "https://github.com/anncwb"
-  },
   "type": "module",
   "scripts": {
     "build": "pnpm vite build --mode production",
@@ -26,6 +16,8 @@
     "#/*": "./src/*"
   },
   "dependencies": {
+    "@six/request": "workspace:*",
+    "@vben-core/menu-ui": "workspace:*",
     "@vben/access": "workspace:*",
     "@vben/common-ui": "workspace:*",
     "@vben/constants": "workspace:*",
@@ -35,16 +27,20 @@
     "@vben/locales": "workspace:*",
     "@vben/plugins": "workspace:*",
     "@vben/preferences": "workspace:*",
-    "@vben/request": "workspace:*",
     "@vben/stores": "workspace:*",
     "@vben/styles": "workspace:*",
     "@vben/types": "workspace:*",
     "@vben/utils": "workspace:*",
     "@vueuse/core": "catalog:",
+    "@vueuse/router": "catalog:",
+    "alova": "catalog:",
     "ant-design-vue": "catalog:",
     "dayjs": "catalog:",
     "pinia": "catalog:",
     "vue": "catalog:",
     "vue-router": "catalog:"
+  },
+  "devDependencies": {
+    "@vben-core/typings": "workspace:*"
   }
 }

+ 0 - 0
apps/web-antd/postcss.config.mjs → apps/health-remedy/postcss.config.mjs


+ 51 - 0
apps/health-remedy/public/database/menu.json

@@ -0,0 +1,51 @@
+[
+  {
+    "meta": {
+      "icon": "charm:organisation",
+      "order": 2,
+      "title": "business.dept.title"
+    },
+    "name": "BusinessDepartment",
+    "path": "/business/dept",
+    "component": "/business/department/list"
+  },
+  {
+    "meta": {
+      "icon": "mdi:account",
+      "order": 3,
+      "title": "business.doctor.title"
+    },
+    "name": "BusinessDoctor",
+    "path": "/business/doctor",
+    "component": "/business/doctor/list"
+  },
+  {
+    "meta": {
+      "icon": "ion:settings-outline",
+      "order": 9997,
+      "title": "system.title"
+    },
+    "name": "System",
+    "path": "/system",
+    "children": [
+      {
+        "path": "/system/role",
+        "name": "SystemRole",
+        "meta": {
+          "icon": "mdi:account-group",
+          "title": "system.role.title"
+        },
+        "component": "/system/role/list"
+      },
+      {
+        "path": "/system/user",
+        "name": "SystemUser",
+        "meta": {
+          "icon": "charm:organisation",
+          "title": "system.user.title"
+        },
+        "component": "/system/user/list"
+      }
+    ]
+  }
+]

+ 0 - 0
apps/health-remedy/public/favicon.ico


+ 107 - 0
apps/health-remedy/src/adapter/component/Avatar.vue

@@ -0,0 +1,107 @@
+<script setup lang="ts">
+import type { UploadChangeParam } from 'ant-design-vue';
+
+import { ref, watchEffect } from 'vue';
+
+import { useRequest } from 'alova/client';
+import { Modal, Upload } from 'ant-design-vue';
+
+import { uploadFileMethod } from '#/api/method/common';
+
+interface UploadFile extends File {
+  uid?: string;
+  status: 'done' | 'error' | 'removed' | 'success' | 'uploading';
+  thumbUrl: string;
+  url: string;
+}
+
+defineOptions({
+  inheritAttrs: false,
+});
+
+function getBase64(img: Blob, callback: (base64Url: string) => void) {
+  const reader = new FileReader();
+  reader.addEventListener('load', () => callback(reader.result as string), {
+    once: true,
+  });
+  reader.readAsDataURL(img);
+}
+
+const { loading, send: upload } = useRequest(uploadFileMethod, {
+  immediate: false,
+});
+
+const avatar = defineModel<string | void>('value', { default: void 0 });
+const fileList = ref<UploadFile[]>([]);
+watchEffect(() => {
+  const url = avatar.value ?? '';
+  if (url) {
+    const name = url.split('/').pop() ?? 'avatar.png';
+    fileList.value = [{ uid: '-1', status: 'done', name, url } as UploadFile];
+  } else {
+    fileList.value = [];
+  }
+});
+
+const previewVisible = ref(false);
+const previewImage = ref('');
+const handleCancel = () => {
+  previewVisible.value = false;
+};
+const onPreviewHandle = async (file: UploadFile) => {
+  previewImage.value = file.url || file.thumbUrl;
+  previewVisible.value = true;
+};
+
+const onChangeHandle = async (info: UploadChangeParam<UploadFile>) => {
+  info.file.status ??= 'uploading';
+  if (info.file.status === 'uploading') {
+    getBase64(info.file, (thumbUrl) => {
+      info.file.thumbUrl = thumbUrl;
+    });
+    try {
+      const url = await upload(info.file);
+      info.file.status = 'done';
+      avatar.value = url;
+    } catch {
+      info.file.status = 'error';
+      avatar.value = void 0;
+    }
+  }
+};
+
+const onRemoveHandle = () => {
+  avatar.value = void 0;
+};
+</script>
+
+<template>
+  <div>
+    <Upload
+      v-bind="$attrs"
+      v-model:file-list="fileList"
+      accept="image/*"
+      :max-count="1"
+      name="avatar"
+      list-type="picture-card"
+      class="avatar-uploader"
+      :disabled="loading"
+      :before-upload="() => false"
+      @change="onChangeHandle"
+      @remove="onRemoveHandle"
+      @preview="onPreviewHandle"
+    >
+      <div v-if="!fileList?.length" class="ant-upload-text">上传</div>
+    </Upload>
+    <Modal
+      :open="previewVisible"
+      title="头像"
+      :footer="null"
+      @cancel="handleCancel"
+    >
+      <img alt="example" style="width: 100%" :src="previewImage" />
+    </Modal>
+  </div>
+</template>
+
+<style scoped></style>

+ 3 - 0
apps/web-antd/src/adapter/component/index.ts → apps/health-remedy/src/adapter/component/index.ts

@@ -60,6 +60,7 @@ const TreeSelect = defineAsyncComponent(
   () => import('ant-design-vue/es/tree-select'),
 );
 const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
+const Avatar = defineAsyncComponent(() => import('./Avatar.vue'));
 
 const withDefaultPlaceholder = <T extends Component>(
   component: T,
@@ -100,6 +101,7 @@ export type ComponentType =
   | 'ApiSelect'
   | 'ApiTreeSelect'
   | 'AutoComplete'
+  | 'Avatar'
   | 'Checkbox'
   | 'CheckboxGroup'
   | 'DatePicker'
@@ -190,6 +192,7 @@ async function initComponentAdapter() {
     TimePicker,
     TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
     Upload,
+    Avatar,
   };
 
   // 将组件注册到全局共享状态中

+ 0 - 0
apps/web-antd/src/adapter/form.ts → apps/health-remedy/src/adapter/form.ts


+ 22 - 14
playground/src/adapter/vxe-table.ts → apps/health-remedy/src/adapter/vxe-table.ts

@@ -3,7 +3,7 @@ import type { Recordable } from '@vben/types';
 
 import type { ComponentType } from './component';
 
-import { h } from 'vue';
+import { h, resolveDirective, withDirectives } from 'vue';
 
 import { IconifyIcon } from '@vben/icons';
 import { $te } from '@vben/locales';
@@ -29,18 +29,17 @@ setupVbenVxeTable({
         columnConfig: {
           resizable: true,
         },
-
+        minHeight: 180,
         formConfig: {
           // 全局禁用vxe-table的表单配置,使用formOptions
           enabled: false,
         },
-        minHeight: 180,
         proxyConfig: {
           autoLoad: true,
           response: {
             result: 'items',
             total: 'total',
-            list: '',
+            list: 'items',
           },
           showActiveMsg: true,
           showResponseMsg: false,
@@ -51,14 +50,14 @@ setupVbenVxeTable({
       } as VxeTableGridOptions,
     });
 
-    /**
-     * 解决vxeTable在热更新时可能会出错的问题
-     */
-    vxeUI.renderer.forEach((_item, key) => {
-      if (key.startsWith('Cell')) {
-        vxeUI.renderer.delete(key);
-      }
-    });
+    if (import.meta.env.DEV) {
+      // 解决vxeTable在热更新时可能会出错的问题
+      vxeUI.renderer.forEach((_item, key) => {
+        if (key.startsWith('Cell')) {
+          vxeUI.renderer.delete(key);
+        }
+      });
+    }
 
     // 表格配置项可以用 cellRender: { name: 'CellImage' },
     vxeUI.renderer.add('CellImage', {
@@ -103,12 +102,13 @@ setupVbenVxeTable({
     vxeUI.renderer.add('CellSwitch', {
       renderTableDefault({ attrs, props }, { column, row }) {
         const loadingKey = `__loading_${column.field}`;
+        const { accessRole, accessCode, _props } = props ?? {};
         const finallyProps = {
           checkedChildren: $t('common.enabled'),
           checkedValue: 1,
           unCheckedChildren: $t('common.disabled'),
           unCheckedValue: 0,
-          ...props,
+          ..._props,
           checked: row[column.field],
           loading: row[loadingKey] ?? false,
           'onUpdate:checked': onChange,
@@ -124,7 +124,15 @@ setupVbenVxeTable({
             row[loadingKey] = false;
           }
         }
-        return h(Switch, finallyProps);
+        const access = resolveDirective('access');
+        const modifiers = { disabled: true };
+        return withDirectives(
+          h(Switch, finallyProps),
+          [
+            accessCode && [access, accessCode, 'code', modifiers],
+            accessRole && [access, accessRole, 'role', modifiers],
+          ].filter(Boolean),
+        );
       },
     });
 

+ 87 - 0
apps/health-remedy/src/api/index.ts

@@ -0,0 +1,87 @@
+import type { Recordable } from '@vben-core/typings';
+
+import createRequestClient from '@six/request';
+import { message } from 'ant-design-vue';
+
+import { useAuthStore } from '#/store';
+
+import '@six/request/alova';
+
+export * from './method/access';
+export * from './method/business';
+export * from './method/common';
+export * from './method/system';
+
+export const http = createRequestClient({
+  id: import.meta.env.VITE_APP_NAMESPACE?.split('/').pop() ?? 'health-remedy',
+  transform(body, method) {
+    /* prettier-ignore */
+    if (body === null || typeof body !== 'object') return { code: 0, data: body, message: 'ok' };
+    const { ResultCode: code, ResultInfo: message, Data: data, ...r } = body;
+    const result = { code, message, data, ...r };
+
+    if (
+      result.data?.TotalPageCount !== void 0 &&
+      Array.isArray(result.data?.Items)
+    ) {
+      const {
+        TotalPageCount: total,
+        PageIndex: page,
+        PageSize: size,
+        Items: items,
+      } = result.data;
+      result.data = { total, items, data: { page, size, total } };
+    }
+
+    /* 额外处理登录接口 */
+    if (method.meta?.login || method.meta?.authRole === 'login') {
+      const { token, ...data } = result.data ?? {};
+      result.data = {
+        accessToken: token,
+        refreshToken: null,
+        ...data,
+      };
+    }
+    return result;
+  },
+});
+http.interceptor('error', async (error) => {
+  if (error?.code === 401) await useAuthStore().logout();
+  message.error(error.message ?? `服务错误 (${error.code})`).then();
+});
+
+export const database = createRequestClient({
+  baseURL: `${import.meta.env.BASE_URL}database`,
+  transform(body) {
+    return { code: 0, data: body, message: 'ok' } as any;
+  },
+});
+
+export type TransformData<T = any> = Recordable<T>;
+
+export interface TransformList<T = TransformData> {
+  total: number;
+  items: T[];
+  data?: { page: number; size: number; total: number };
+}
+
+export interface TransformBody<T> {
+  code: number;
+  data: T;
+  message?: string;
+}
+
+export interface TransformBlob {
+  fileName: string;
+  source: Blob;
+}
+
+export interface TransformRecord {
+  id: string;
+  createUser?: string;
+  createTime?: string;
+  updateUser?: string;
+  updateTime?: string;
+  lastTime?: string;
+  lastUser?: string;
+}

+ 87 - 0
apps/health-remedy/src/api/method/access.ts

@@ -0,0 +1,87 @@
+import type { UserInfo } from '@vben/types';
+
+import type { SystemModel, TransformData } from '#/api';
+
+import { http } from '#/api';
+import { fromRole } from '#/api/model';
+import { fromMenus } from '#/api/model/menu';
+
+export namespace AccessModel {
+  export interface LoginParams {
+    password: 'string';
+    username: 'string';
+  }
+
+  export interface LoginResponse {
+    accessToken: string;
+    refreshToken?: string;
+
+    [K: string]: any;
+  }
+}
+
+export function loginMethod(data: AccessModel.LoginParams) {
+  return http.post<AccessModel.LoginResponse>(`/login`, data, {
+    meta: { login: true, visitor: true },
+  });
+}
+
+export function getAccessMenuMethod(permissions?: string[]) {
+  return http.post<SystemModel.Menu[], TransformData[]>(
+    `/admin/right_RoleMgr/allMenu`,
+    void 0,
+    {
+      transform(data) {
+        const menus = fromMenus(data);
+        if (!permissions?.length) return menus;
+
+        const forEach = (
+          permissions: Set<string>,
+          menus: SystemModel.Menu[],
+          parentMenu: SystemModel.Menu[] = [],
+        ) => {
+          for (const menu of menus) {
+            const id = menu.id;
+            if (permissions.has(id)) {
+              permissions.delete(id);
+              if (menu.type === 'menu') parentMenu.push(menu);
+              else if (menu.type === 'catalog' && menu.children?.length) {
+                const parent = { ...menu, children: [] };
+                parentMenu.push(parent);
+                forEach(permissions, menu.children, parent.children);
+              }
+            } else if (menu.type === 'catalog' && menu.children?.length) {
+              forEach(permissions, menu.children, parentMenu);
+            }
+            if (permissions.size === 0) break;
+          }
+          return parentMenu;
+        };
+
+        return forEach(new Set(permissions), menus);
+      },
+    },
+  );
+}
+
+export function getUserInfoMethod(token?: string) {
+  return http.get<UserInfo & { roles: SystemModel.Role[] }, TransformData>(
+    `/getInfo`,
+    {
+      headers: { Authorization: token },
+      transform(data, headers) {
+        return {
+          avatar: data?.headImage,
+          realName: '',
+          roles: data?.roles?.map(fromRole) ?? [],
+          userId: data?.userid,
+          username: data?.username,
+          // user
+          token: token ?? headers.get('authorization') ?? '',
+          homePath: '',
+          desc: '',
+        } satisfies UserInfo;
+      },
+    },
+  );
+}

+ 182 - 0
apps/health-remedy/src/api/method/business.ts

@@ -0,0 +1,182 @@
+import type {
+  TransformBlob,
+  TransformBody,
+  TransformData,
+  TransformList,
+  TransformRecord,
+} from '#/api';
+
+import { downloadFileFromBlob } from '@vben/utils';
+
+import { http } from '#/api';
+import {
+  fromDepartment,
+  fromDoctor,
+  toDepartment,
+  toDoctor,
+} from '#/api/model';
+
+export namespace BusinessModel {
+  export interface Department extends TransformRecord {
+    id: string;
+    name: string;
+    code?: string;
+    description?: string;
+    organization?: { name: string };
+    parent?: Department;
+    children?: Department[];
+    registerLink?: string;
+  }
+  export interface Doctor extends TransformRecord {
+    id: string;
+    name: string;
+    code?: string;
+    avatar?: string;
+    description?: string;
+    worker?: string;
+    department?: Department;
+    titleOfClinical?: string;
+    titleOfTeach?: string;
+    titleOf?: string;
+    adeptAt?: string;
+    registerLink?: string;
+  }
+}
+
+export function listDepartmentsMethod(
+  page = 1,
+  size = 20,
+  query?: Partial<BusinessModel.Department>,
+) {
+  return http.post<TransformList<BusinessModel.Department>, TransformList>(
+    `/basis/department/listPage`,
+    toDepartment(query),
+    {
+      params: { page, limit: size },
+      transform({ items, ...data }) {
+        return { ...data, items: items.map((item) => fromDepartment(item)) };
+      },
+    },
+  );
+}
+
+export function treeDepartmentsMethod() {
+  return http.post<BusinessModel.Department[], TransformData[]>(
+    `/basis/department/list`,
+    void 0,
+    {
+      transform(data) {
+        return data.map((item) => fromDepartment(item));
+      },
+    },
+  );
+}
+
+export function editDepartmentMethod(data: Partial<BusinessModel.Department>) {
+  return http.post(
+    data?.id ? `/basis/department/update` : `/basis/department/add`,
+    toDepartment(data),
+  );
+}
+
+export function deleteDepartmentMethod(
+  data: Pick<BusinessModel.Department, 'id'>,
+) {
+  return deleteDepartmentsMethod([data]);
+}
+
+export function deleteDepartmentsMethod(
+  params: Pick<BusinessModel.Department, 'id'>[],
+) {
+  return http.post(`/basis/department/batchDelete`, void 0, {
+    params: { ids: params.map((item) => item.id).join(',') },
+  });
+}
+
+export function downloadDepartmentTemplateMethod(filename = '科室模板.xlsx') {
+  return http.get(`/basis/department/downExcel`, {
+    params: { fileName: filename },
+    transform(data: TransformBlob) {
+      downloadFileFromBlob(data);
+      return data;
+    },
+  });
+}
+export function uploadDepartmentFileMethod(file: File) {
+  const data = new FormData();
+  data.append('file', file);
+  return http.post(`/basis/department/importExcel`, data, {
+    meta: { notParseResponseBody: true },
+    transform(data: TransformBody<unknown>) {
+      if (data.code === 0) {
+        const [_, count = ''] = data.message?.match(/入库成功(\d+)条/) ?? [];
+        if (+count > 0)
+          return { count: +count, message: data.message ?? `导入成功` };
+      }
+      // eslint-disable-next-line no-throw-literal
+      throw { message: data.message ?? `导入失败` };
+    },
+  });
+}
+
+export function listDoctorsMethod(
+  page = 1,
+  size = 20,
+  query?: Partial<BusinessModel.Doctor>,
+) {
+  return http.post<TransformList<BusinessModel.Doctor>, TransformList>(
+    `/basis/doctor/listPage`,
+    toDoctor(query),
+    {
+      params: { page, limit: size },
+      transform({ items, ...data }) {
+        return { ...data, items: items.map((item) => fromDoctor(item)) };
+      },
+    },
+  );
+}
+
+export function editDoctorMethod(data: Partial<BusinessModel.Doctor>) {
+  return http.post(
+    data?.id ? `/basis/doctor/update` : `/basis/doctor/add`,
+    toDoctor(data),
+  );
+}
+
+export function deleteDoctorMethod(data: Pick<BusinessModel.Doctor, 'id'>) {
+  return deleteDoctorsMethod([data]);
+}
+
+export function deleteDoctorsMethod(
+  params: Pick<BusinessModel.Doctor, 'id'>[],
+) {
+  return http.post(`/basis/doctor/batchDelete`, void 0, {
+    params: { ids: params.map((item) => item.id).join(',') },
+  });
+}
+
+export function downloadDoctorTemplateMethod(filename = '医生模板.xlsx') {
+  return http.get(`/basis/doctor/downExcel`, {
+    params: { fileName: filename },
+    transform(data: TransformBlob) {
+      downloadFileFromBlob(data);
+      return data;
+    },
+  });
+}
+export function uploadDoctorFileMethod(file: File) {
+  const data = new FormData();
+  data.append('file', file);
+  return http.post(`/basis/doctor/importExcel`, data, {
+    meta: { notParseResponseBody: true },
+    transform(data: TransformBody<unknown>) {
+      if (data.code === 0) {
+        const [_, count = ''] = data.message?.match(/入库成功(\d+)条/) ?? [];
+        if (+count > 0)
+          return { count: +count, message: data.message ?? `导入成功` };
+      }
+      // eslint-disable-next-line no-throw-literal
+      throw { message: data.message ?? `导入失败` };
+    },
+  });
+}

+ 20 - 0
apps/health-remedy/src/api/method/common.ts

@@ -0,0 +1,20 @@
+import type { TransformBody } from '#/api';
+
+import { http } from '#/api';
+
+export function uploadFileMethod(file: File) {
+  const data = new FormData();
+  data.append('file', file);
+  return http.post(`/common/upload`, data, {
+    meta: { notParseResponseBody: true },
+    transform(data: TransformBody<unknown>) {
+      if (data.code === 0 && data.message?.startsWith('http'))
+        return data.message;
+      else if (data.code === 0) {
+        return 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png';
+      }
+      // eslint-disable-next-line no-throw-literal
+      throw { message: data.message ?? `上传失败` };
+    },
+  });
+}

+ 141 - 0
apps/health-remedy/src/api/method/system.ts

@@ -0,0 +1,141 @@
+import type { RouteMeta } from 'vue-router';
+
+import type { TransformData, TransformList, TransformRecord } from '#/api';
+
+import { http } from '#/api';
+import { fromRole, fromUser, toRole, toUser } from '#/api/model';
+import { fromMenus } from '#/api/model/menu';
+
+export namespace SystemModel {
+  export interface Role extends TransformRecord {
+    [key: string]: any;
+
+    id: string;
+    name: string;
+    code?: string;
+    permissions: string[];
+    remark?: string;
+    status: 0 | 1;
+  }
+
+  export interface User extends TransformRecord {
+    id: string;
+    access: string;
+    name: string;
+    worker?: string;
+    mobile?: string;
+    roles?: Array<Role | string>;
+
+    password?: string;
+  }
+
+  export interface Menu {
+    type: 'button' | 'catalog' | 'menu';
+    id: string;
+    pid?: string;
+    name: string;
+    path: string;
+    component?: string;
+    meta: Partial<RouteMeta>;
+    children?: Menu[];
+  }
+}
+
+export function listRolesMethod(page = 1, size = 20, query?: TransformData) {
+  return http.post<TransformList<SystemModel.Role>, TransformList>(
+    `/admin/right_RoleMgr/listPain`,
+    query,
+    {
+      params: { page, limit: size },
+      transform({ items, ...data }) {
+        return { ...data, items: items.map((item) => fromRole(item)) };
+      },
+    },
+  );
+}
+
+export function optionsRoleMethod() {
+  return http.get<SystemModel.Role[], TransformData[]>(
+    `/admin/right_RoleMgr/optionselect`,
+    {
+      transform(data) {
+        return data.map((item) => fromRole(item));
+      },
+    },
+  );
+}
+
+export function editRoleMethod(data: Partial<SystemModel.Role>) {
+  return http.post(
+    data.id ? `/admin/right_RoleMgr/update` : `/admin/right_RoleMgr/Add`,
+    toRole(data),
+  );
+}
+
+export function updateRoleStatusMethod(
+  id: string,
+  data: Partial<Omit<SystemModel.Role, 'id'>>,
+) {
+  const { pid, stateSel } = toRole({ ...data, id });
+  return http.put(`/admin/right_RoleMgr/changeStatus`, { pid, stateSel });
+}
+
+export function deleteRoleMethod(data: Pick<SystemModel.User, 'id'>) {
+  return deleteRolesMethod([data]);
+}
+
+export function deleteRolesMethod(params: Pick<SystemModel.User, 'id'>[]) {
+  return http.post(`/admin/right_RoleMgr/BatchDelete`, void 0, {
+    params: { ids: params.map((item) => item.id).join(',') },
+  });
+}
+
+export function listUsersMethod(page = 1, size = 20, query?: SystemModel.User) {
+  return http.post<TransformList<SystemModel.User>, TransformList>(
+    `/portal/userMgr/listPain`,
+    toUser(query),
+    {
+      params: { page, limit: size },
+      transform({ items, ...data }) {
+        return { ...data, items: items.map((item) => fromUser(item)) };
+      },
+    },
+  );
+}
+
+export function editUserMethod(data: Partial<SystemModel.User>) {
+  return http.post(
+    data?.id ? `/portal/userMgr/update` : `/portal/userMgr/Add`,
+    toUser(data),
+  );
+}
+
+export function getUserMethod(id: string) {
+  return http.get<SystemModel.User, TransformData>(`/portal/userMgr/${id}`, {
+    transform(data) {
+      return fromUser(data);
+    },
+  });
+}
+
+export function deleteUserMethod(data: Pick<SystemModel.User, 'id'>) {
+  return deleteUsersMethod([data]);
+}
+
+export function deleteUsersMethod(params: Pick<SystemModel.User, 'id'>[]) {
+  return http.post(`/portal/userMgr/BatchDelete`, void 0, {
+    params: { ids: params.map((item) => item.id).join(',') },
+  });
+}
+
+export function getMenusMethod() {
+  return http.post<SystemModel.Menu[], TransformData[]>(
+    `/admin/right_RoleMgr/allMenu`,
+    void 0,
+    {
+      transform(data) {
+        return fromMenus(data);
+      },
+    },
+  );
+}

+ 43 - 0
apps/health-remedy/src/api/model/department.ts

@@ -0,0 +1,43 @@
+import type { BusinessModel, TransformData } from '#/api';
+
+import { fromRow } from '#/api/model';
+
+export function fromDepartment(data?: TransformData): BusinessModel.Department {
+  return {
+    ...fromRow(data),
+    id: data?.pid,
+    name: data?.deptName,
+    code: data?.deptCode,
+    description: data?.introduce,
+    organization: data?.hospitalName
+      ? <any>{
+          code: data?.hospitalCode,
+          name: data?.hospitalName,
+        }
+      : void 0,
+    parent: data?.parentDeptName
+      ? <BusinessModel.Department>{
+          code: data?.parentDeptCode,
+          name: data?.parentDeptName,
+        }
+      : void 0,
+    children:
+      data?.children?.map((item: TransformData) => fromDepartment(item)) ?? [],
+    registerLink: data?.registerUrl,
+  };
+}
+
+export function toDepartment(
+  data?: Partial<BusinessModel.Department>,
+): TransformData {
+  return {
+    pid: data?.id,
+    deptName: data?.name,
+    deptCode: data?.code,
+    introduce: data?.description,
+    hospitalName: data?.organization?.name,
+    parentDeptCode: data?.parent?.code,
+    parentDeptName: data?.parent?.name,
+    registerUrl: data?.registerLink,
+  };
+}

+ 46 - 0
apps/health-remedy/src/api/model/doctor.ts

@@ -0,0 +1,46 @@
+import type { BusinessModel, TransformData } from '#/api';
+
+import { fromRow } from '#/api/model';
+
+export function fromDoctor(data?: TransformData): BusinessModel.Doctor {
+  return {
+    ...fromRow(data),
+    id: data?.id,
+    name: data?.doctorName,
+    description: data?.introduce,
+    titleOf: data?.title,
+    titleOfClinical: data?.clinicalTitle,
+    titleOfTeach: data?.teachTitle,
+    avatar: data?.headImage,
+    worker: data?.doctorCode,
+    adeptAt: data?.expertiseArea,
+    department: data?.deptName
+      ? <BusinessModel.Department>{
+          code: data?.deptCode,
+          name: data?.deptName,
+        }
+      : void 0,
+    registerLink: data?.registerUrl,
+  };
+}
+
+export function toDoctor(data?: Partial<BusinessModel.Doctor>): TransformData {
+  return {
+    id: data?.id,
+    doctorName: data?.name,
+    doctorCode: data?.worker,
+    headImage: data?.avatar,
+
+    title: data?.titleOf,
+    clinicalTitle: data?.titleOfClinical,
+    teachTitle: data?.titleOfTeach,
+
+    deptCode: data?.department?.code,
+    deptName: data?.department?.name,
+
+    expertiseArea: data?.adeptAt,
+    introduce: data?.description,
+
+    registerUrl: data?.registerLink,
+  };
+}

+ 22 - 0
apps/health-remedy/src/api/model/index.ts

@@ -0,0 +1,22 @@
+import type { TransformData, TransformRecord } from '#/api';
+
+export * from './department';
+export * from './doctor';
+export * from './role';
+export * from './user';
+
+export function fromRow(data?: TransformData): TransformRecord {
+  const createUser = data?.createUser;
+  const createTime = data?.createTime ?? data?.createDate;
+  const updateUser = data?.updateUser;
+  const updateTime = data?.updateTime ?? data?.updateDate;
+  return {
+    id: data?.id,
+    createUser,
+    createTime,
+    updateUser,
+    updateTime,
+    lastTime: updateTime || createTime,
+    lastUser: updateUser || createUser,
+  };
+}

+ 42 - 0
apps/health-remedy/src/api/model/menu.ts

@@ -0,0 +1,42 @@
+import type { SystemModel, TransformData } from '#/api';
+
+export function fromMenus(menus: TransformData[]): SystemModel.Menu[] {
+  const getType = (menu: TransformData): SystemModel.Menu['type'] => {
+    if (menu.type) return menu.type;
+    if (menu.component && menu.children === null) return 'menu';
+    return menu.component ? 'catalog' : 'button';
+  };
+  return Array.isArray(menus)
+    ? menus
+        .map((menu: TransformData) => {
+          menu.meta ??= {};
+          menu.meta.order ??= menu?.orderNum ?? -1;
+          return {
+            type: getType(menu),
+            id: menu.id ?? menu.meta.id,
+            pid: menu.parentId,
+            name: menu.name,
+            path: menu.path,
+            component: menu.component,
+            meta: fromMenuMeta(menu.meta),
+            children: fromMenus(menu.children),
+          } satisfies SystemModel.Menu;
+        })
+        .sort((a, b) => (a.meta.order ?? -1) - (b.meta.order ?? -1))
+    : [];
+}
+
+type MenuMeta = SystemModel.Menu['meta'];
+
+export function getDefaultMenuMeta(meta?: MenuMeta): MenuMeta {
+  return Object.assign(
+    {
+      keepAlive: true,
+    },
+    meta,
+  );
+}
+
+function fromMenuMeta(meta: TransformData): MenuMeta {
+  return getDefaultMenuMeta(meta satisfies MenuMeta);
+}

+ 25 - 0
apps/health-remedy/src/api/model/role.ts

@@ -0,0 +1,25 @@
+import type { SystemModel, TransformData } from '#/api';
+
+import { fromRow } from '#/api/model/index';
+
+export function fromRole(data?: TransformData): SystemModel.Role {
+  return {
+    ...fromRow(data),
+    id: data?.pid,
+    name: data?.rolename,
+    code: data?.rolecode,
+    remark: data?.remark,
+    status: ({ '0': 1, '1': 0 } as const)[<string>data?.stateSel ?? 1] ?? 1,
+    permissions: Array.isArray(data?.menuIds) ? data?.menuIds : [],
+  };
+}
+
+export function toRole(data?: Partial<SystemModel.Role>): TransformData {
+  return {
+    pid: data?.id,
+    rolename: data?.name,
+    remark: data?.remark,
+    stateSel: data?.status === 0 ? '1' : '0',
+    menuIds: data?.permissions ?? [],
+  };
+}

+ 32 - 0
apps/health-remedy/src/api/model/user.ts

@@ -0,0 +1,32 @@
+import type { SystemModel, TransformData } from '#/api';
+
+import { fromRole, fromRow, toRole } from '#/api/model';
+
+export function fromUser(data?: TransformData): SystemModel.User {
+  return {
+    ...fromRow(data),
+    id: data?.pid,
+    access: data?.userid,
+    name: data?.username,
+    worker: data?.jobnumber,
+    mobile: data?.mobile,
+    roles: data?.roles?.map((item: TransformData) => fromRole(item)) ?? [],
+  };
+}
+
+export function toUser(data?: Partial<SystemModel.User>): TransformData {
+  const roles =
+    data?.roles?.map((item) =>
+      typeof item === 'string' ? { pid: item } : toRole(item),
+    ) ?? [];
+  return {
+    pid: data?.id,
+    userid: data?.access,
+    username: data?.name,
+    password: data?.password,
+    jobnumber: data?.worker,
+    mobile: data?.mobile,
+    roles: roles.length > 0 ? roles : void 0,
+    roleIds: roles.map((item) => item.pid).join(',') || void 0,
+  };
+}

+ 0 - 0
apps/web-antd/src/app.vue → apps/health-remedy/src/app.vue


+ 2 - 1
apps/web-antd/src/bootstrap.ts → apps/health-remedy/src/bootstrap.ts

@@ -14,7 +14,7 @@ import { $t, setupI18n } from '#/locales';
 import { initComponentAdapter } from './adapter/component';
 import { initSetupVbenForm } from './adapter/form';
 import App from './app.vue';
-import { router } from './router';
+// import { router } from './router';
 
 async function bootstrap(namespace: string) {
   // 初始化组件适配器
@@ -54,6 +54,7 @@ async function bootstrap(namespace: string) {
   initTippy(app);
 
   // 配置路由及路由守卫
+  const { router } = await import('./router');
   app.use(router);
 
   // 配置Motion插件

+ 102 - 0
apps/health-remedy/src/components/import/database.modal.vue

@@ -0,0 +1,102 @@
+<script setup lang="ts">
+import type { UploadChangeParam } from 'ant-design-vue';
+
+import { shallowRef, triggerRef } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { FilePlus } from '@vben/icons';
+import { $t } from '@vben/locales';
+
+import { message, UploadDragger } from 'ant-design-vue';
+
+export interface ImportDatabaseProps<U = { count: number; message: string }> {
+  accept?: string;
+  upload?: (file: File) => Promise<U>;
+}
+
+defineOptions({
+  name: 'ImportDatabaseModal',
+});
+
+const emit = defineEmits(['success']);
+
+const props = shallowRef<ImportDatabaseProps>({
+  accept: '*/*',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  showConfirmButton: false,
+  async onConfirm() {},
+  onOpenChange(isOpen) {
+    if (isOpen) {
+      const data = modalApi.getData<ImportDatabaseProps>();
+      Object.assign(props.value, data);
+      triggerRef(props);
+    }
+  },
+});
+
+const onAcceptError = (_files: File[]) => {
+  message.warn(`请上传正确格式的文件`);
+};
+
+async function onChangeHandle(info: UploadChangeParam<File>) {
+  const accept = props.value.accept ?? '*/*';
+  if (info.file.type && accept !== '*/*' && !accept.includes(info.file.type))
+    return onAcceptError(info.fileList);
+
+  modalApi.lock();
+  try {
+    const data = await props.value.upload?.(info.file);
+    if (data?.count) {
+      message.success(data.message);
+      await modalApi.close();
+      emit('success');
+    }
+  } catch (error: any) {
+    message.error(error.message);
+  } finally {
+    modalApi.lock(false);
+  }
+}
+</script>
+
+<template>
+  <Modal
+    class="import-database-modal-wrapper"
+    :draggable="true"
+    :fullscreen-button="false"
+    :confirm-text="$t('ui.actionTitle.import')"
+  >
+    <UploadDragger
+      class="size-full"
+      name="file"
+      :show-upload-list="false"
+      :accept="props.accept"
+      :before-upload="() => false"
+      @change="onChangeHandle"
+      @reject="onAcceptError"
+    >
+      <p class="ant-upload-drag-icon text-center">
+        <FilePlus class="text-primary inline size-24" />
+      </p>
+      <span class="ant-upload-text">单击或将文件拖到此区域进行上传</span>
+    </UploadDragger>
+    <template v-if="$slots['prepend-footer']" #prepend-footer>
+      <slot name="prepend-footer"></slot>
+    </template>
+  </Modal>
+</template>
+
+<style scoped lang="scss">
+:deep(.ant-upload-btn) {
+  display: flex !important;
+  flex-direction: column;
+  justify-content: center;
+  padding: 0 !important;
+}
+
+:global(.import-database-modal-wrapper) {
+  height: 520px;
+}
+</style>

+ 32 - 49
apps/web-ele/src/views/_core/authentication/login.vue → apps/health-remedy/src/core/authentication/login.vue

@@ -1,70 +1,28 @@
 <script lang="ts" setup>
 import type { VbenFormSchema } from '@vben/common-ui';
-import type { BasicOption } from '@vben/types';
 
-import { computed, markRaw } from 'vue';
+import type { Recordable } from '@vben-core/typings';
+
+import { computed, markRaw, ref, useTemplateRef } from 'vue';
 
 import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
 
+import { message } from 'ant-design-vue';
+
 import { useAuthStore } from '#/store';
 
 defineOptions({ name: 'Login' });
 
 const authStore = useAuthStore();
 
-const MOCK_USER_OPTIONS: BasicOption[] = [
-  {
-    label: 'Super',
-    value: 'vben',
-  },
-  {
-    label: 'Admin',
-    value: 'admin',
-  },
-  {
-    label: 'User',
-    value: 'jack',
-  },
-];
-
 const formSchema = computed((): VbenFormSchema[] => {
   return [
-    {
-      component: 'VbenSelect',
-      componentProps: {
-        options: MOCK_USER_OPTIONS,
-        placeholder: $t('authentication.selectAccount'),
-      },
-      fieldName: 'selectAccount',
-      label: $t('authentication.selectAccount'),
-      rules: z
-        .string()
-        .min(1, { message: $t('authentication.selectAccount') })
-        .optional()
-        .default('vben'),
-    },
     {
       component: 'VbenInput',
       componentProps: {
         placeholder: $t('authentication.usernameTip'),
       },
-      dependencies: {
-        trigger(values, form) {
-          if (values.selectAccount) {
-            const findUser = MOCK_USER_OPTIONS.find(
-              (item) => item.value === values.selectAccount,
-            );
-            if (findUser) {
-              form.setValues({
-                password: '123456',
-                username: findUser.value,
-              });
-            }
-          }
-        },
-        triggerFields: ['selectAccount'],
-      },
       fieldName: 'username',
       label: $t('authentication.username'),
       rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
@@ -87,12 +45,37 @@ const formSchema = computed((): VbenFormSchema[] => {
     },
   ];
 });
+
+const loginRef = useTemplateRef('loginRef');
+const loading = ref(false);
+async function handle(data: Recordable<any>) {
+  loading.value = true;
+  try {
+    await authStore.login(data as any);
+  } catch (error: any) {
+    message.error(error?.message ?? `登录失败`);
+    const formApi = loginRef.value?.getFormApi();
+    formApi?.setFieldValue('captcha', false, false);
+    formApi
+      ?.getFieldComponentRef<InstanceType<typeof SliderCaptcha>>('captcha')
+      ?.resume();
+  } finally {
+    loading.value = false;
+  }
+}
 </script>
 
 <template>
   <AuthenticationLogin
+    ref="loginRef"
     :form-schema="formSchema"
-    :loading="authStore.loginLoading"
-    @submit="authStore.authLogin"
+    :loading="loading"
+    @submit="handle"
+    :show-remember-me="false"
+    :show-forget-password="false"
+    :show-code-login="false"
+    :show-qrcode-login="false"
+    :show-third-party-login="false"
+    :show-register="false"
   />
 </template>

+ 0 - 0
apps/web-antd/src/views/_core/fallback/coming-soon.vue → apps/health-remedy/src/core/fallback/coming-soon.vue


+ 0 - 0
apps/web-antd/src/views/_core/fallback/forbidden.vue → apps/health-remedy/src/core/fallback/forbidden.vue


+ 0 - 0
apps/web-antd/src/views/_core/fallback/internal-error.vue → apps/health-remedy/src/core/fallback/internal-error.vue


+ 11 - 0
apps/health-remedy/src/core/fallback/not-found.vue

@@ -0,0 +1,11 @@
+<script lang="ts" setup>
+import { Fallback } from '@vben/common-ui';
+import { useUserStore } from '@vben/stores';
+
+defineOptions({ name: 'Fallback404Demo' });
+const userStore = useUserStore();
+</script>
+
+<template>
+  <Fallback status="404" :home-path="userStore.userInfo?.homePath" />
+</template>

+ 0 - 0
apps/web-antd/src/views/_core/fallback/offline.vue → apps/health-remedy/src/core/fallback/offline.vue


+ 5 - 2
apps/web-antd/src/layouts/auth.vue → apps/health-remedy/src/layouts/auth.vue

@@ -7,7 +7,9 @@ import { preferences } from '@vben/preferences';
 import { $t } from '#/locales';
 
 const appName = computed(() => preferences.app.name);
-const logo = computed(() => preferences.logo.source);
+const logo = computed(() =>
+  preferences.logo.enable ? preferences.logo.source : void 0,
+);
 </script>
 
 <template>
@@ -16,8 +18,9 @@ const logo = computed(() => preferences.logo.source);
     :logo="logo"
     :page-description="$t('authentication.pageDesc')"
     :page-title="$t('authentication.pageTitle')"
+    :toolbar-list="['color', 'theme']"
   >
     <!-- 自定义工具栏 -->
-    <!-- <template #toolbar></template> -->
+    <!--<template #toolbar></template>-->
   </AuthPageLayout>
 </template>

+ 79 - 0
apps/health-remedy/src/layouts/basic.vue

@@ -0,0 +1,79 @@
+<script lang="ts" setup>
+import type { NotificationItem } from '@vben/layouts';
+
+import { computed, ref, watch } from 'vue';
+
+import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
+import { useWatermark } from '@vben/hooks';
+import { BasicLayout, LockScreen, UserDropdown } from '@vben/layouts';
+import { preferences } from '@vben/preferences';
+import { useAccessStore, useUserStore } from '@vben/stores';
+
+import LoginForm from '#/core/authentication/login.vue';
+import { useAuthStore } from '#/store';
+
+const notifications = ref<NotificationItem[]>([]);
+
+const userStore = useUserStore();
+const authStore = useAuthStore();
+const accessStore = useAccessStore();
+const { destroyWatermark, updateWatermark } = useWatermark();
+
+computed(() => notifications.value.some((item) => !item.isRead));
+
+const avatar = computed(() => {
+  return userStore.userInfo?.avatar || preferences.app.defaultAvatar;
+});
+const text = computed(() => {
+  return (
+    userStore.userInfo?.realName ||
+    userStore.userInfo?.userName ||
+    userStore.userInfo?.username
+  );
+});
+
+async function handleLogout() {
+  await authStore.logout(false);
+}
+
+watch(
+  () => preferences.app.watermark,
+  async (enable) => {
+    if (enable) {
+      await updateWatermark({
+        content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
+      });
+    } else {
+      destroyWatermark();
+    }
+  },
+  {
+    immediate: true,
+  },
+);
+</script>
+
+<template>
+  <BasicLayout @clear-preferences-and-logout="handleLogout">
+    <template #user-dropdown>
+      <UserDropdown
+        :avatar
+        :text
+        :menus="[]"
+        :description="userStore.userInfo?.desc"
+        @logout="handleLogout"
+      />
+    </template>
+    <template #extra>
+      <AuthenticationLoginExpiredModal
+        v-model:open="accessStore.loginExpired"
+        :avatar
+      >
+        <LoginForm />
+      </AuthenticationLoginExpiredModal>
+    </template>
+    <template #lock-screen>
+      <LockScreen :avatar @to-login="handleLogout" />
+    </template>
+  </BasicLayout>
+</template>

+ 0 - 0
apps/web-antd/src/layouts/index.ts → apps/health-remedy/src/layouts/index.ts


+ 0 - 0
apps/web-antd/src/locales/index.ts → apps/health-remedy/src/locales/index.ts


+ 5 - 0
apps/health-remedy/src/locales/langs/zh-CN/authentication.json

@@ -0,0 +1,5 @@
+{
+  "welcomeBack": "欢迎回来",
+  "pageTitle": "中医智能导诊管理系统",
+  "pageDesc": "智能、高效、管理"
+}

+ 30 - 0
apps/health-remedy/src/locales/langs/zh-CN/business.json

@@ -0,0 +1,30 @@
+{
+  "dept": {
+    "_": "科室",
+    "title": "科室维护",
+    "list": "科室列表",
+    "name": "科室名称",
+    "code": "科室编码",
+    "superior": "上级科室",
+    "description": "科室介绍",
+    "registerLink": "挂号连接"
+  },
+  "doctor": {
+    "_": "医生",
+    "title": "医生维护",
+    "list": "医生列表",
+    "name": "医生姓名",
+    "avatar": "头像",
+    "worker": "工号",
+    "titleOf": "头衔",
+    "titleOfClinical": "临床职称",
+    "titleOfTeach": "教学职称",
+    "description": "医生介绍",
+    "adeptAt": "擅长领域",
+    "registerLink": "挂号连接"
+  },
+  "organization": {
+    "_": "机构",
+    "name": "机构名称"
+  }
+}

+ 5 - 0
apps/health-remedy/src/locales/langs/zh-CN/common.json

@@ -0,0 +1,5 @@
+{
+  "status": "状态",
+  "enabled": "已启用",
+  "disabled": "已禁用"
+}

+ 0 - 5
apps/web-ele/src/locales/langs/zh-CN/page.json → apps/health-remedy/src/locales/langs/zh-CN/page.json

@@ -5,10 +5,5 @@
     "codeLogin": "验证码登录",
     "qrcodeLogin": "二维码登录",
     "forgetPassword": "忘记密码"
-  },
-  "dashboard": {
-    "title": "概览",
-    "analytics": "分析页",
-    "workspace": "工作台"
   }
 }

部分文件因为文件数量过多而无法显示