feat: 添加完整的前端管理系统 (VbenAdmin)
- 添加基于 VbenAdmin + Vue3 + Element Plus 的前端管理系统 - 包含完整的 UI 组件库和工具链 - 支持多应用架构 (web-ele, backend-mock, playground) - 包含完整的开发规范和配置 - 修复 admin 目录的子模块问题,确保正确提交
This commit is contained in:
1
admin
1
admin
Submodule admin deleted from cf6c4c9aae
4
admin/.browserslistrc
Normal file
4
admin/.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
||||
5
admin/.changeset/README.md
Normal file
5
admin/.changeset/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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)
|
||||
18
admin/.changeset/config.json
Normal file
18
admin/.changeset/config.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$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": []
|
||||
}
|
||||
1
admin/.commitlintrc.js
Normal file
1
admin/.commitlintrc.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/commitlint-config';
|
||||
7
admin/.dockerignore
Normal file
7
admin/.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
.git
|
||||
.gitignore
|
||||
*.md
|
||||
dist
|
||||
.turbo
|
||||
dist.zip
|
||||
18
admin/.editorconfig
Normal file
18
admin/.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=lf
|
||||
insert_final_newline=true
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
max_line_length = 100
|
||||
trim_trailing_whitespace = true
|
||||
quote_type = single
|
||||
|
||||
[*.{yml,yaml,json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
11
admin/.gitattributes
vendored
Normal file
11
admin/.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings
|
||||
|
||||
# Automatically normalize line endings (to LF) for all text-based files.
|
||||
* text=auto eol=lf
|
||||
|
||||
# Declare files that will always have CRLF line endings on checkout.
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary
|
||||
2
admin/.gitconfig
Normal file
2
admin/.gitconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
[core]
|
||||
ignorecase = false
|
||||
14
admin/.github/CODEOWNERS
vendored
Normal file
14
admin/.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# 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
|
||||
74
admin/.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
74
admin/.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
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
|
||||
38
admin/.github/ISSUE_TEMPLATE/docs.yml
vendored
Normal file
38
admin/.github/ISSUE_TEMPLATE/docs.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
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`
|
||||
70
admin/.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
70
admin/.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
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
|
||||
40
admin/.github/actions/setup-node/action.yml
vendored
Normal file
40
admin/.github/actions/setup-node/action.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
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
|
||||
89
admin/.github/commit-convention.md
vendored
Normal file
89
admin/.github/commit-convention.md
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
## 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.
|
||||
39
admin/.github/config.yml
vendored
Normal file
39
admin/.github/config.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# 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.
|
||||
|
||||
40
admin/.github/contributing.md
vendored
Normal file
40
admin/.github/contributing.md
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
# 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
|
||||
```
|
||||
17
admin/.github/dependabot.yml
vendored
Normal file
17
admin/.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
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]
|
||||
33
admin/.github/pull_request_template.md
vendored
Normal file
33
admin/.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
## 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
|
||||
61
admin/.github/release-drafter.yml
vendored
Normal file
61
admin/.github/release-drafter.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
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'
|
||||
13
admin/.github/semantic.yml
vendored
Normal file
13
admin/.github/semantic.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
titleAndCommits: true
|
||||
types:
|
||||
- feat
|
||||
- fix
|
||||
- docs
|
||||
- chore
|
||||
- style
|
||||
- refactor
|
||||
- perf
|
||||
- test
|
||||
- build
|
||||
- ci
|
||||
- revert
|
||||
48
admin/.github/workflows/build.yml
vendored
Normal file
48
admin/.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# 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
|
||||
42
admin/.github/workflows/changeset-version.yml
vendored
Normal file
42
admin/.github/workflows/changeset-version.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# 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 }}
|
||||
125
admin/.github/workflows/ci.yml
vendored
Normal file
125
admin/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
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
|
||||
94
admin/.github/workflows/codeql.yml
vendored
Normal file
94
admin/.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
# 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}}'
|
||||
172
admin/.github/workflows/deploy.yml
vendored
Normal file
172
admin/.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
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 }}
|
||||
25
admin/.github/workflows/draft.yml
vendored
Normal file
25
admin/.github/workflows/draft.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
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 }}
|
||||
31
admin/.github/workflows/issue-close-require.yml
vendored
Normal file
31
admin/.github/workflows/issue-close-require.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# 每天零点运行一次,它会检查所有带有 "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
|
||||
46
admin/.github/workflows/issue-labeled.yml
vendored
Normal file
46
admin/.github/workflows/issue-labeled.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
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'
|
||||
24
admin/.github/workflows/lock.yml
vendored
Normal file
24
admin/.github/workflows/lock.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
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'
|
||||
80
admin/.github/workflows/release-tag.yml
vendored
Normal file
80
admin/.github/workflows/release-tag.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
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.
|
||||
19
admin/.github/workflows/rerun.yml
vendored
Normal file
19
admin/.github/workflows/rerun.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
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
|
||||
41
admin/.github/workflows/semantic-pull-request.yml
vendored
Normal file
41
admin/.github/workflows/semantic-pull-request.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
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 }}
|
||||
19
admin/.github/workflows/stale.yml
vendored
Normal file
19
admin/.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
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
|
||||
52
admin/.gitignore
vendored
Normal file
52
admin/.gitignore
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
dist.zip
|
||||
dist.tar
|
||||
dist.war
|
||||
.nitro
|
||||
.output
|
||||
*-dist.zip
|
||||
*-dist.tar
|
||||
*-dist.war
|
||||
coverage
|
||||
*.local
|
||||
**/.vitepress/cache
|
||||
.cache
|
||||
.turbo
|
||||
.temp
|
||||
dev-dist
|
||||
.stylelintcache
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
.VSCodeCounter
|
||||
**/backend-mock/data
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
.eslintcache
|
||||
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
vite.config.mts.*
|
||||
vite.config.mjs.*
|
||||
vite.config.js.*
|
||||
vite.config.ts.*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
# .vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.history
|
||||
.cursor
|
||||
6
admin/.gitpod.yml
Normal file
6
admin/.gitpod.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
ports:
|
||||
- port: 5555
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- init: npm i -g corepack && pnpm install
|
||||
command: pnpm run dev:play
|
||||
1
admin/.node-version
Normal file
1
admin/.node-version
Normal file
@@ -0,0 +1 @@
|
||||
22.1.0
|
||||
13
admin/.npmrc
Normal file
13
admin/.npmrc
Normal file
@@ -0,0 +1,13 @@
|
||||
registry = "https://registry.npmmirror.com"
|
||||
public-hoist-pattern[]=lefthook
|
||||
public-hoist-pattern[]=eslint
|
||||
public-hoist-pattern[]=prettier
|
||||
public-hoist-pattern[]=prettier-plugin-tailwindcss
|
||||
public-hoist-pattern[]=stylelint
|
||||
public-hoist-pattern[]=*postcss*
|
||||
public-hoist-pattern[]=@commitlint/*
|
||||
public-hoist-pattern[]=czg
|
||||
|
||||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
dedupe-peer-dependents=true
|
||||
18
admin/.prettierignore
Normal file
18
admin/.prettierignore
Normal file
@@ -0,0 +1,18 @@
|
||||
dist
|
||||
dev-dist
|
||||
.local
|
||||
.output.js
|
||||
node_modules
|
||||
.nvmrc
|
||||
coverage
|
||||
CODEOWNERS
|
||||
.nitro
|
||||
.output
|
||||
|
||||
|
||||
**/*.svg
|
||||
**/*.sh
|
||||
|
||||
public
|
||||
.npmrc
|
||||
*-lock.yaml
|
||||
1
admin/.prettierrc.mjs
Normal file
1
admin/.prettierrc.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/prettier-config';
|
||||
4
admin/.stylelintignore
Normal file
4
admin/.stylelintignore
Normal file
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
public
|
||||
__tests__
|
||||
coverage
|
||||
9
admin/LICENSE
Normal file
9
admin/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-present, Vben
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
157
admin/README.ja-JP.md
Normal file
157
admin/README.ja-JP.md
Normal file
@@ -0,0 +1,157 @@
|
||||
<div align="center">
|
||||
<a href="https://github.com/anncwb/vue-vben-admin">
|
||||
<img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp">
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
<h1>Vue Vben Admin</h1>
|
||||
</div>
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
||||
|
||||
**日本語** | [English](./README.md) | [中文](./README.zh-CN.md)
|
||||
|
||||
## 紹介
|
||||
|
||||
Vue Vben Adminは、最新の`vue3`、`vite`、`TypeScript`などの主流技術を使用して開発された、無料でオープンソースの中・後端テンプレートです。すぐに使える中・後端のフロントエンドソリューションとして、学習の参考にもなります。
|
||||
|
||||
## アップグレード通知
|
||||
|
||||
これは最新バージョン `5.0` であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。
|
||||
|
||||
## 特徴
|
||||
|
||||
- **最新技術スタック**:Vue 3やViteなどの最先端フロントエンド技術で開発
|
||||
- **TypeScript**:アプリケーション規模のJavaScriptのための言語
|
||||
- **テーマ**:複数のテーマカラーが利用可能で、カスタマイズオプションも豊富
|
||||
- **国際化**:完全な内蔵国際化サポート
|
||||
- **権限管理**:動的ルートベースの権限生成ソリューションを内蔵
|
||||
|
||||
## プレビュー
|
||||
|
||||
- [Vben Admin](https://vben.pro/) - フルバージョンの中国語サイト
|
||||
|
||||
テストアカウント:vben/123456
|
||||
|
||||
<div align="center">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
|
||||
</div>
|
||||
|
||||
### Gitpodを使用
|
||||
|
||||
Gitpod(GitHub用の無料オンライン開発環境)でプロジェクトを開き、すぐにコーディングを開始します。
|
||||
|
||||
[](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)
|
||||
|
||||
## ドキュメント
|
||||
|
||||
[ドキュメント](https://doc.vben.pro/)
|
||||
|
||||
## インストールと使用
|
||||
|
||||
1. プロジェクトコードを取得
|
||||
|
||||
```bash
|
||||
git clone https://github.com/vbenjs/vue-vben-admin.git
|
||||
```
|
||||
|
||||
2. 依存関係のインストール
|
||||
|
||||
```bash
|
||||
cd vue-vben-admin
|
||||
npm i -g corepack
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. 実行
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
4. ビルド
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 変更ログ
|
||||
|
||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||
|
||||
## 貢献方法
|
||||
|
||||
ご参加をお待ちしておりますするか、Pull Requestを送信してください。
|
||||
|
||||
**Pull Request プロセス:**
|
||||
|
||||
1. コードをフォーク
|
||||
2. 自分のブランチを作成:`git checkout -b feat/xxxx`
|
||||
3. 変更をコミット:`git commit -am 'feat(function): add xxxxx'`
|
||||
4. ブランチをプッシュ:`git push origin feat/xxxx`
|
||||
5. `pull request`を送信
|
||||
|
||||
## Git貢献提出規則
|
||||
|
||||
参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 規則 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
|
||||
- `feat` 新機能の追加
|
||||
- `fix` 問題/バグの修正
|
||||
- `style` コードスタイルに関連し、実行結果に影響しない
|
||||
- `perf` 最適化/パフォーマンス向上
|
||||
- `refactor` リファクタリング
|
||||
- `revert` 変更の取り消し
|
||||
- `test` テスト関連
|
||||
- `docs` ドキュメント/注釈
|
||||
- `chore` 依存関係の更新/スキャフォールディング設定の変更など
|
||||
- `ci` 継続的インテグレーション
|
||||
- `types` 型定義ファイルの変更
|
||||
|
||||
## ブラウザサポート
|
||||
|
||||
ローカル開発には `Chrome 80+` ブラウザを推奨します
|
||||
|
||||
モダンブラウザをサポートし、IEはサポートしません
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :-: | :-: | :-: | :-: |
|
||||
| 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン |
|
||||
|
||||
## メンテナー
|
||||
|
||||
[@Vben](https://github.com/anncwb)
|
||||
|
||||
## スター歴史
|
||||
|
||||
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
||||
|
||||
## 寄付
|
||||
|
||||
このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます!
|
||||
|
||||

|
||||
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
|
||||
## 貢献者
|
||||
|
||||
<a href="https://openomy.app/github/vbenjs/vue-vben-admin" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=vbenjs/vue-vben-admin&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||
<img alt="Contributors" src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" />
|
||||
</a>
|
||||
|
||||
## Discord
|
||||
|
||||
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)
|
||||
|
||||
## ライセンス
|
||||
|
||||
[MIT © Vben-2020](./LICENSE)
|
||||
157
admin/README.md
Normal file
157
admin/README.md
Normal file
@@ -0,0 +1,157 @@
|
||||
<div align="center">
|
||||
<a href="https://github.com/anncwb/vue-vben-admin">
|
||||
<img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp">
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
<h1>Vue Vben Admin</h1>
|
||||
</div>
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
||||
|
||||
**English** | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md)
|
||||
|
||||
## Introduction
|
||||
|
||||
Vue Vben Admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite`, `TypeScript` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference.
|
||||
|
||||
## Upgrade Notice
|
||||
|
||||
This is the latest version, 5.0, and it is not compatible with previous versions. If you are starting a new project, it is recommended to use the latest version. If you wish to view the old version, please use the [v2 branch](https://github.com/vbenjs/vue-vben-admin/tree/v2).
|
||||
|
||||
## Features
|
||||
|
||||
- **Latest Technology Stack**: Developed with cutting-edge front-end technologies like Vue 3 and Vite
|
||||
- **TypeScript**: A language for application-scale JavaScript
|
||||
- **Themes**: Multiple theme colors available with customizable options
|
||||
- **Internationalization**: Comprehensive built-in internationalization support
|
||||
- **Permissions**: Built-in solution for dynamic route-based permission generation
|
||||
|
||||
## Preview
|
||||
|
||||
- [Vben Admin](https://vben.pro/) - Full version Chinese site
|
||||
|
||||
Test Account: vben/123456
|
||||
|
||||
<div align="center">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
|
||||
</div>
|
||||
|
||||
### Use Gitpod
|
||||
|
||||
Open the project in Gitpod (free online dev environment for GitHub) and start coding immediately.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)
|
||||
|
||||
## Documentation
|
||||
|
||||
[Document](https://doc.vben.pro/)
|
||||
|
||||
## Install and Use
|
||||
|
||||
1. Get the project code
|
||||
|
||||
```bash
|
||||
git clone https://github.com/vbenjs/vue-vben-admin.git
|
||||
```
|
||||
|
||||
2. Install dependencies
|
||||
|
||||
```bash
|
||||
cd vue-vben-admin
|
||||
npm i -g corepack
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. Run
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
4. Build
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Change Log
|
||||
|
||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||
|
||||
## How to Contribute
|
||||
|
||||
You are very welcome to join! [Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) or submit a Pull Request.
|
||||
|
||||
**Pull Request Process:**
|
||||
|
||||
1. Fork the code
|
||||
2. Create your branch: `git checkout -b feat/xxxx`
|
||||
3. Submit your changes: `git commit -am 'feat(function): add xxxxx'`
|
||||
4. Push your branch: `git push origin feat/xxxx`
|
||||
5. Submit `pull request`
|
||||
|
||||
## Git Contribution Submission Specification
|
||||
|
||||
Reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
|
||||
- `feat` Add new features
|
||||
- `fix` Fix the problem/BUG
|
||||
- `style` The code style is related and does not affect the running result
|
||||
- `perf` Optimization/performance improvement
|
||||
- `refactor` Refactor
|
||||
- `revert` Undo edit
|
||||
- `test` Test related
|
||||
- `docs` Documentation/notes
|
||||
- `chore` Dependency update/scaffolding configuration modification etc.
|
||||
- `ci` Continuous integration
|
||||
- `types` Type definition file changes
|
||||
|
||||
## Browser Support
|
||||
|
||||
The `Chrome 80+` browser is recommended for local development
|
||||
|
||||
Support modern browsers, not IE
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :-: | :-: | :-: | :-: |
|
||||
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## Maintainer
|
||||
|
||||
[@Vben](https://github.com/anncwb)
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
||||
|
||||
## Donate
|
||||
|
||||
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
|
||||
|
||||

|
||||
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aee;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://openomy.app/github/vbenjs/vue-vben-admin" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=vbenjs/vue-vben-admin&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||
<img alt="Contributors" src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" />
|
||||
</a>
|
||||
|
||||
## Discord
|
||||
|
||||
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)
|
||||
|
||||
## License
|
||||
|
||||
[MIT © Vben-2020](./LICENSE)
|
||||
157
admin/README.zh-CN.md
Normal file
157
admin/README.zh-CN.md
Normal file
@@ -0,0 +1,157 @@
|
||||
<div align="center">
|
||||
<a href="https://github.com/anncwb/vue-vben-admin">
|
||||
<img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp">
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
<h1>Vue Vben Admin</h1>
|
||||
</div>
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
||||
|
||||
**中文** | [English](./README.md) | [日本語](./README.ja-JP.md)
|
||||
|
||||
## 简介
|
||||
|
||||
Vue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的中后台模板,它采用了最新的 Vue 3、Vite、TypeScript 等主流技术开发,开箱即用,可用于中后台前端开发,也适合学习参考。
|
||||
|
||||
## 升级提示
|
||||
|
||||
该版本为最新版本 `5.0`,与其他版本不兼容,如果你是新项目,建议使用最新版本。如果你想查看旧版本,请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2)
|
||||
|
||||
## 特性
|
||||
|
||||
- **最新技术栈**:使用 Vue3/vite 等前端前沿技术开发
|
||||
- **TypeScript**:应用程序级 JavaScript 的语言
|
||||
- **主题**:提供多套主题色彩,可配置自定义主题
|
||||
- **国际化**:内置完善的国际化方案
|
||||
- **权限**:内置完善的动态路由权限生成方案
|
||||
|
||||
## 预览
|
||||
|
||||
- [Vben Admin](https://vben.pro/) - 完整版中文站点
|
||||
|
||||
测试账号:vben/123456
|
||||
|
||||
<div align="center">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
|
||||
</div>
|
||||
|
||||
### 使用 Gitpod
|
||||
|
||||
在 Gitpod(适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码。
|
||||
|
||||
[](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)
|
||||
|
||||
## 文档
|
||||
|
||||
[文档地址](https://doc.vben.pro/)
|
||||
|
||||
## 安装使用
|
||||
|
||||
1. 获取项目代码
|
||||
|
||||
```bash
|
||||
git clone https://github.com/vbenjs/vue-vben-admin.git
|
||||
```
|
||||
|
||||
2. 安装依赖
|
||||
|
||||
```bash
|
||||
cd vue-vben-admin
|
||||
npm i -g corepack
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. 运行
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
4. 打包
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 更新日志
|
||||
|
||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||
|
||||
## 如何贡献
|
||||
|
||||
非常欢迎你的加入 或者提交一个 Pull Request。
|
||||
|
||||
**Pull Request 流程:**
|
||||
|
||||
1. Fork 代码
|
||||
2. 创建自己的分支:`git checkout -b feature/xxxx`
|
||||
3. 提交你的修改:`git commit -am 'feat(function): add xxxxx'`
|
||||
4. 推送您的分支:`git push origin feature/xxxx`
|
||||
5. 提交 `pull request`
|
||||
|
||||
## Git 贡献提交规范
|
||||
|
||||
参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
|
||||
- `feat` 增加新功能
|
||||
- `fix` 修复问题/BUG
|
||||
- `style` 代码风格相关无影响运行结果的
|
||||
- `perf` 优化/性能提升
|
||||
- `refactor` 重构
|
||||
- `revert` 撤销修改
|
||||
- `test` 测试相关
|
||||
- `docs` 文档/注释
|
||||
- `chore` 依赖更新/脚手架配置修改等
|
||||
- `ci` 持续集成
|
||||
- `types` 类型定义文件更改
|
||||
|
||||
## 浏览器支持
|
||||
|
||||
本地开发推荐使用 `Chrome 80+` 浏览器
|
||||
|
||||
支持现代浏览器,不支持 IE
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :-: | :-: | :-: | :-: |
|
||||
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## 维护者
|
||||
|
||||
[@Vben](https://github.com/anncwb)
|
||||
|
||||
## Star 历史
|
||||
|
||||
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
||||
|
||||
## 捐赠
|
||||
|
||||
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
|
||||
|
||||

|
||||
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
|
||||
## 贡献者
|
||||
|
||||
<a href="https://openomy.app/github/vbenjs/vue-vben-admin" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=vbenjs/vue-vben-admin&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||
<img alt="Contributors" src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" />
|
||||
</a>
|
||||
|
||||
## Discord
|
||||
|
||||
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)
|
||||
|
||||
## 许可证
|
||||
|
||||
[MIT © Vben-2020](./LICENSE)
|
||||
15
admin/apps/backend-mock/README.md
Normal file
15
admin/apps/backend-mock/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# @vben/backend-mock
|
||||
|
||||
## Description
|
||||
|
||||
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。
|
||||
|
||||
## Running the app
|
||||
|
||||
```bash
|
||||
# development
|
||||
$ pnpm run start
|
||||
|
||||
# production mode
|
||||
$ pnpm run build
|
||||
```
|
||||
16
admin/apps/backend-mock/api/auth/codes.ts
Normal file
16
admin/apps/backend-mock/api/auth/codes.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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);
|
||||
});
|
||||
42
admin/apps/backend-mock/api/auth/login.post.ts
Normal file
42
admin/apps/backend-mock/api/auth/login.post.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
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,
|
||||
});
|
||||
});
|
||||
17
admin/apps/backend-mock/api/auth/logout.post.ts
Normal file
17
admin/apps/backend-mock/api/auth/logout.post.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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('');
|
||||
});
|
||||
35
admin/apps/backend-mock/api/auth/refresh.post.ts
Normal file
35
admin/apps/backend-mock/api/auth/refresh.post.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
});
|
||||
32
admin/apps/backend-mock/api/demo/bigint.ts
Normal file
32
admin/apps/backend-mock/api/demo/bigint.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
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;
|
||||
});
|
||||
15
admin/apps/backend-mock/api/menu/all.ts
Normal file
15
admin/apps/backend-mock/api/menu/all.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
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);
|
||||
});
|
||||
8
admin/apps/backend-mock/api/status.ts
Normal file
8
admin/apps/backend-mock/api/status.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
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}`);
|
||||
});
|
||||
16
admin/apps/backend-mock/api/system/dept/.post.ts
Normal file
16
admin/apps/backend-mock/api/system/dept/.post.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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);
|
||||
});
|
||||
16
admin/apps/backend-mock/api/system/dept/[id].delete.ts
Normal file
16
admin/apps/backend-mock/api/system/dept/[id].delete.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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);
|
||||
});
|
||||
16
admin/apps/backend-mock/api/system/dept/[id].put.ts
Normal file
16
admin/apps/backend-mock/api/system/dept/[id].put.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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);
|
||||
});
|
||||
62
admin/apps/backend-mock/api/system/dept/list.ts
Normal file
62
admin/apps/backend-mock/api/system/dept/list.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
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);
|
||||
});
|
||||
13
admin/apps/backend-mock/api/system/menu/list.ts
Normal file
13
admin/apps/backend-mock/api/system/menu/list.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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);
|
||||
});
|
||||
29
admin/apps/backend-mock/api/system/menu/name-exists.ts
Normal file
29
admin/apps/backend-mock/api/system/menu/name-exists.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
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);
|
||||
});
|
||||
29
admin/apps/backend-mock/api/system/menu/path-exists.ts
Normal file
29
admin/apps/backend-mock/api/system/menu/path-exists.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
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);
|
||||
});
|
||||
84
admin/apps/backend-mock/api/system/role/list.ts
Normal file
84
admin/apps/backend-mock/api/system/role/list.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
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);
|
||||
});
|
||||
117
admin/apps/backend-mock/api/table/list.ts
Normal file
117
admin/apps/backend-mock/api/table/list.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
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,
|
||||
);
|
||||
});
|
||||
3
admin/apps/backend-mock/api/test.get.ts
Normal file
3
admin/apps/backend-mock/api/test.get.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { defineEventHandler } from 'h3';
|
||||
|
||||
export default defineEventHandler(() => 'Test get handler');
|
||||
3
admin/apps/backend-mock/api/test.post.ts
Normal file
3
admin/apps/backend-mock/api/test.post.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { defineEventHandler } from 'h3';
|
||||
|
||||
export default defineEventHandler(() => 'Test post handler');
|
||||
14
admin/apps/backend-mock/api/upload.ts
Normal file
14
admin/apps/backend-mock/api/upload.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
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")
|
||||
});
|
||||
11
admin/apps/backend-mock/api/user/info.ts
Normal file
11
admin/apps/backend-mock/api/user/info.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
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);
|
||||
});
|
||||
7
admin/apps/backend-mock/error.ts
Normal file
7
admin/apps/backend-mock/error.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { NitroErrorHandler } from 'nitropack';
|
||||
|
||||
const errorHandler: NitroErrorHandler = function (error, event) {
|
||||
event.node.res.end(`[Error Handler] ${error.stack}`);
|
||||
};
|
||||
|
||||
export default errorHandler;
|
||||
20
admin/apps/backend-mock/middleware/1.api.ts
Normal file
20
admin/apps/backend-mock/middleware/1.api.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
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, '演示环境,禁止修改');
|
||||
}
|
||||
});
|
||||
20
admin/apps/backend-mock/nitro.config.ts
Normal file
20
admin/apps/backend-mock/nitro.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
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': '*',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
21
admin/apps/backend-mock/package.json
Normal file
21
admin/apps/backend-mock/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"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:"
|
||||
}
|
||||
}
|
||||
15
admin/apps/backend-mock/routes/[...].ts
Normal file
15
admin/apps/backend-mock/routes/[...].ts
Normal file
@@ -0,0 +1,15 @@
|
||||
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>
|
||||
`;
|
||||
});
|
||||
4
admin/apps/backend-mock/tsconfig.build.json
Normal file
4
admin/apps/backend-mock/tsconfig.build.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
3
admin/apps/backend-mock/tsconfig.json
Normal file
3
admin/apps/backend-mock/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./.nitro/types/tsconfig.json"
|
||||
}
|
||||
28
admin/apps/backend-mock/utils/cookie-utils.ts
Normal file
28
admin/apps/backend-mock/utils/cookie-utils.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
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;
|
||||
}
|
||||
77
admin/apps/backend-mock/utils/jwt-utils.ts
Normal file
77
admin/apps/backend-mock/utils/jwt-utils.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
390
admin/apps/backend-mock/utils/mock-data.ts
Normal file
390
admin/apps/backend-mock/utils/mock-data.ts
Normal file
@@ -0,0 +1,390 @@
|
||||
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;
|
||||
}
|
||||
70
admin/apps/backend-mock/utils/response.ts
Normal file
70
admin/apps/backend-mock/utils/response.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
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));
|
||||
}
|
||||
7
admin/apps/web-ele/.env.analyze
Normal file
7
admin/apps/web-ele/.env.analyze
Normal file
@@ -0,0 +1,7 @@
|
||||
# public path
|
||||
VITE_BASE=/
|
||||
|
||||
# Basic interface address SPA
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
||||
VITE_VISUALIZER=true
|
||||
16
admin/apps/web-ele/.env.development
Normal file
16
admin/apps/web-ele/.env.development
Normal file
@@ -0,0 +1,16 @@
|
||||
# 端口号
|
||||
VITE_PORT=5777
|
||||
|
||||
VITE_BASE=/
|
||||
|
||||
# 接口地址
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
||||
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
||||
VITE_NITRO_MOCK=true
|
||||
|
||||
# 是否打开 devtools,true 为打开,false 为关闭
|
||||
VITE_DEVTOOLS=false
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
19
admin/apps/web-ele/.env.production
Normal file
19
admin/apps/web-ele/.env.production
Normal file
@@ -0,0 +1,19 @@
|
||||
VITE_BASE=/
|
||||
|
||||
# 接口地址
|
||||
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
|
||||
|
||||
# 是否开启压缩,可以设置为 none, brotli, gzip
|
||||
VITE_COMPRESS=none
|
||||
|
||||
# 是否开启 PWA
|
||||
VITE_PWA=false
|
||||
|
||||
# vue-router 的模式
|
||||
VITE_ROUTER_HISTORY=hash
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# 打包后是否生成dist.zip
|
||||
VITE_ARCHIVER=true
|
||||
35
admin/apps/web-ele/index.html
Normal file
35
admin/apps/web-ele/index.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!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="description" content="A Modern Back-end Management System" />
|
||||
<meta name="keywords" content="Vben Admin Vue3 Vite" />
|
||||
<meta name="author" content="Vben" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
||||
/>
|
||||
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
|
||||
<title><%= VITE_APP_TITLE %></title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<script>
|
||||
// 生产环境下注入百度统计
|
||||
if (window._VBEN_ADMIN_PRO_APP_CONF_) {
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement('script');
|
||||
hm.src =
|
||||
'https://hm.baidu.com/hm.js?97352b16ed2df8c3860cf5a1a65fb4dd';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
53
admin/apps/web-ele/package.json
Normal file
53
admin/apps/web-ele/package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@vben/web-ele",
|
||||
"version": "5.5.9",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "apps/web-ele"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "vben",
|
||||
"email": "ann.vben@gmail.com",
|
||||
"url": "https://github.com/anncwb"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm vite build --mode production",
|
||||
"build:analyze": "pnpm vite build --mode analyze",
|
||||
"dev": "pnpm vite --mode development",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "vue-tsc --noEmit --skipLibCheck"
|
||||
},
|
||||
"imports": {
|
||||
"#/*": "./src/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben/access": "workspace:*",
|
||||
"@vben/common-ui": "workspace:*",
|
||||
"@vben/constants": "workspace:*",
|
||||
"@vben/hooks": "workspace:*",
|
||||
"@vben/icons": "workspace:*",
|
||||
"@vben/layouts": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/plugins": "workspace:*",
|
||||
"@vben/preferences": "workspace:*",
|
||||
"@vben/request": "workspace:*",
|
||||
"@vben/stores": "workspace:*",
|
||||
"@vben/styles": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"element-plus": "catalog:",
|
||||
"pinia": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"vue-router": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"unplugin-element-plus": "catalog:"
|
||||
}
|
||||
}
|
||||
1
admin/apps/web-ele/postcss.config.mjs
Normal file
1
admin/apps/web-ele/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/tailwind-config/postcss';
|
||||
BIN
admin/apps/web-ele/public/favicon.ico
Normal file
BIN
admin/apps/web-ele/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
331
admin/apps/web-ele/src/adapter/component/index.ts
Normal file
331
admin/apps/web-ele/src/adapter/component/index.ts
Normal file
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
|
||||
|
||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElNotification } from 'element-plus';
|
||||
|
||||
const ElButton = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/button/index'),
|
||||
import('element-plus/es/components/button/style/css'),
|
||||
]).then(([res]) => res.ElButton),
|
||||
);
|
||||
const ElCheckbox = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/checkbox/index'),
|
||||
import('element-plus/es/components/checkbox/style/css'),
|
||||
]).then(([res]) => res.ElCheckbox),
|
||||
);
|
||||
const ElCheckboxButton = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/checkbox/index'),
|
||||
import('element-plus/es/components/checkbox-button/style/css'),
|
||||
]).then(([res]) => res.ElCheckboxButton),
|
||||
);
|
||||
const ElCheckboxGroup = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/checkbox/index'),
|
||||
import('element-plus/es/components/checkbox-group/style/css'),
|
||||
]).then(([res]) => res.ElCheckboxGroup),
|
||||
);
|
||||
const ElDatePicker = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/date-picker/index'),
|
||||
import('element-plus/es/components/date-picker/style/css'),
|
||||
]).then(([res]) => res.ElDatePicker),
|
||||
);
|
||||
const ElDivider = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/divider/index'),
|
||||
import('element-plus/es/components/divider/style/css'),
|
||||
]).then(([res]) => res.ElDivider),
|
||||
);
|
||||
const ElInput = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/input/index'),
|
||||
import('element-plus/es/components/input/style/css'),
|
||||
]).then(([res]) => res.ElInput),
|
||||
);
|
||||
const ElInputNumber = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/input-number/index'),
|
||||
import('element-plus/es/components/input-number/style/css'),
|
||||
]).then(([res]) => res.ElInputNumber),
|
||||
);
|
||||
const ElRadio = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/radio/index'),
|
||||
import('element-plus/es/components/radio/style/css'),
|
||||
]).then(([res]) => res.ElRadio),
|
||||
);
|
||||
const ElRadioButton = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/radio/index'),
|
||||
import('element-plus/es/components/radio-button/style/css'),
|
||||
]).then(([res]) => res.ElRadioButton),
|
||||
);
|
||||
const ElRadioGroup = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/radio/index'),
|
||||
import('element-plus/es/components/radio-group/style/css'),
|
||||
]).then(([res]) => res.ElRadioGroup),
|
||||
);
|
||||
const ElSelectV2 = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/select-v2/index'),
|
||||
import('element-plus/es/components/select-v2/style/css'),
|
||||
]).then(([res]) => res.ElSelectV2),
|
||||
);
|
||||
const ElSpace = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/space/index'),
|
||||
import('element-plus/es/components/space/style/css'),
|
||||
]).then(([res]) => res.ElSpace),
|
||||
);
|
||||
const ElSwitch = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/switch/index'),
|
||||
import('element-plus/es/components/switch/style/css'),
|
||||
]).then(([res]) => res.ElSwitch),
|
||||
);
|
||||
const ElTimePicker = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/time-picker/index'),
|
||||
import('element-plus/es/components/time-picker/style/css'),
|
||||
]).then(([res]) => res.ElTimePicker),
|
||||
);
|
||||
const ElTreeSelect = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/tree-select/index'),
|
||||
import('element-plus/es/components/tree-select/style/css'),
|
||||
]).then(([res]) => res.ElTreeSelect),
|
||||
);
|
||||
const ElUpload = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/upload/index'),
|
||||
import('element-plus/es/components/upload/style/css'),
|
||||
]).then(([res]) => res.ElUpload),
|
||||
);
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
componentProps: Recordable<any> = {},
|
||||
) => {
|
||||
return defineComponent({
|
||||
name: component.name,
|
||||
inheritAttrs: false,
|
||||
setup: (props: any, { attrs, expose, slots }) => {
|
||||
const placeholder =
|
||||
props?.placeholder ||
|
||||
attrs?.placeholder ||
|
||||
$t(`ui.placeholder.${type}`);
|
||||
// 透传组件暴露的方法
|
||||
const innerRef = ref();
|
||||
expose(
|
||||
new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_target, key) => innerRef.value?.[key],
|
||||
has: (_target, key) => key in (innerRef.value || {}),
|
||||
},
|
||||
),
|
||||
);
|
||||
return () =>
|
||||
h(
|
||||
component,
|
||||
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
|
||||
slots,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'ApiSelect'
|
||||
| 'ApiTreeSelect'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'IconPicker'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'RadioGroup'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
ApiSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiSelect',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: ElSelectV2,
|
||||
loadingSlot: 'loading',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
},
|
||||
),
|
||||
ApiTreeSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiTreeSelect',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: ElTreeSelect,
|
||||
props: { label: 'label', children: 'children' },
|
||||
nodeKey: 'value',
|
||||
loadingSlot: 'loading',
|
||||
optionsPropName: 'data',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
},
|
||||
),
|
||||
Checkbox: ElCheckbox,
|
||||
CheckboxGroup: (props, { attrs, slots }) => {
|
||||
let defaultSlot;
|
||||
if (Reflect.has(slots, 'default')) {
|
||||
defaultSlot = slots.default;
|
||||
} else {
|
||||
const { options, isButton } = attrs;
|
||||
if (Array.isArray(options)) {
|
||||
defaultSlot = () =>
|
||||
options.map((option) =>
|
||||
h(isButton ? ElCheckboxButton : ElCheckbox, option),
|
||||
);
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElCheckboxGroup,
|
||||
{ ...props, ...attrs },
|
||||
{ ...slots, default: defaultSlot },
|
||||
);
|
||||
},
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
||||
},
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider: ElDivider,
|
||||
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
|
||||
iconSlot: 'append',
|
||||
modelValueProp: 'model-value',
|
||||
inputComponent: ElInput,
|
||||
}),
|
||||
Input: withDefaultPlaceholder(ElInput, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
||||
RadioGroup: (props, { attrs, slots }) => {
|
||||
let defaultSlot;
|
||||
if (Reflect.has(slots, 'default')) {
|
||||
defaultSlot = slots.default;
|
||||
} else {
|
||||
const { options } = attrs;
|
||||
if (Array.isArray(options)) {
|
||||
defaultSlot = () =>
|
||||
options.map((option) =>
|
||||
h(attrs.isButton ? ElRadioButton : ElRadio, option),
|
||||
);
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElRadioGroup,
|
||||
{ ...props, ...attrs },
|
||||
{ ...slots, default: defaultSlot },
|
||||
);
|
||||
},
|
||||
Select: (props, { attrs, slots }) => {
|
||||
return h(ElSelectV2, { ...props, attrs }, slots);
|
||||
},
|
||||
Space: ElSpace,
|
||||
Switch: ElSwitch,
|
||||
TimePicker: (props, { attrs, slots }) => {
|
||||
const { name, id, isRange } = props;
|
||||
const extraProps: Recordable<any> = {};
|
||||
if (isRange) {
|
||||
if (name && !Array.isArray(name)) {
|
||||
extraProps.name = [name, `${name}_end`];
|
||||
}
|
||||
if (id && !Array.isArray(id)) {
|
||||
extraProps.id = [id, `${id}_end`];
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElTimePicker,
|
||||
{
|
||||
...props,
|
||||
...attrs,
|
||||
...extraProps,
|
||||
},
|
||||
slots,
|
||||
);
|
||||
},
|
||||
DatePicker: (props, { attrs, slots }) => {
|
||||
const { name, id, type } = props;
|
||||
const extraProps: Recordable<any> = {};
|
||||
if (type && type.includes('range')) {
|
||||
if (name && !Array.isArray(name)) {
|
||||
extraProps.name = [name, `${name}_end`];
|
||||
}
|
||||
if (id && !Array.isArray(id)) {
|
||||
extraProps.id = [id, `${id}_end`];
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElDatePicker,
|
||||
{
|
||||
...props,
|
||||
...attrs,
|
||||
...extraProps,
|
||||
},
|
||||
slots,
|
||||
);
|
||||
},
|
||||
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
|
||||
Upload: ElUpload,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
ElNotification({
|
||||
title,
|
||||
message: content,
|
||||
position: 'bottom-right',
|
||||
duration: 0,
|
||||
type: 'success',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
||||
41
admin/apps/web-ele/src/adapter/form.ts
Normal file
41
admin/apps/web-ele/src/adapter/form.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type {
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
async function initSetupVbenForm() {
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
modelPropNameMap: {
|
||||
Upload: 'fileList',
|
||||
CheckboxGroup: 'model-value',
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
selectRequired: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null) {
|
||||
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { initSetupVbenForm, useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
70
admin/apps/web-ele/src/adapter/vxe-table.ts
Normal file
70
admin/apps/web-ele/src/adapter/vxe-table.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
|
||||
import { ElButton, ElImage } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from './form';
|
||||
|
||||
setupVbenVxeTable({
|
||||
configVxeTable: (vxeUI) => {
|
||||
vxeUI.setConfig({
|
||||
grid: {
|
||||
align: 'center',
|
||||
border: false,
|
||||
columnConfig: {
|
||||
resizable: true,
|
||||
},
|
||||
minHeight: 180,
|
||||
formConfig: {
|
||||
// 全局禁用vxe-table的表单配置,使用formOptions
|
||||
enabled: false,
|
||||
},
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
response: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
list: 'items',
|
||||
},
|
||||
showActiveMsg: true,
|
||||
showResponseMsg: false,
|
||||
},
|
||||
round: true,
|
||||
showOverflow: true,
|
||||
size: 'small',
|
||||
} as VxeTableGridOptions,
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||
vxeUI.renderer.add('CellImage', {
|
||||
renderTableDefault(_renderOpts, params) {
|
||||
const { column, row } = params;
|
||||
const src = row[column.field];
|
||||
return h(ElImage, { src, previewSrcList: [src] });
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||
vxeUI.renderer.add('CellLink', {
|
||||
renderTableDefault(renderOpts) {
|
||||
const { props } = renderOpts;
|
||||
return h(
|
||||
ElButton,
|
||||
{ size: 'small', link: true },
|
||||
{ default: () => props?.text },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||
// vxeUI.formats.add
|
||||
},
|
||||
useVbenForm,
|
||||
});
|
||||
|
||||
export { useVbenVxeGrid };
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
||||
334
admin/apps/web-ele/src/api/common/auth.ts
Normal file
334
admin/apps/web-ele/src/api/common/auth.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 用户相关接口
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
realname: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
mobile?: string;
|
||||
status: number;
|
||||
roles: Role[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateUserDto {
|
||||
username: string;
|
||||
realname: string;
|
||||
password: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
mobile?: string;
|
||||
role_ids: string[];
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface UpdateUserDto {
|
||||
realname?: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
mobile?: string;
|
||||
role_ids?: string[];
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export interface UserListParams {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
username?: string;
|
||||
realname?: string;
|
||||
status?: number;
|
||||
role_id?: string;
|
||||
}
|
||||
|
||||
// 角色相关接口
|
||||
export interface Role {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
sort: number;
|
||||
remark?: string;
|
||||
status: number;
|
||||
permissions: Permission[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateRoleDto {
|
||||
name: string;
|
||||
code: string;
|
||||
sort: number;
|
||||
remark?: string;
|
||||
status: number;
|
||||
permission_ids: string[];
|
||||
}
|
||||
|
||||
export interface UpdateRoleDto {
|
||||
name?: string;
|
||||
code?: string;
|
||||
sort?: number;
|
||||
remark?: string;
|
||||
status?: number;
|
||||
permission_ids?: string[];
|
||||
}
|
||||
|
||||
export interface RoleListParams {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
name?: string;
|
||||
code?: string;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
// 权限相关接口
|
||||
export interface Permission {
|
||||
id: string;
|
||||
parent_id?: string;
|
||||
name: string;
|
||||
code: string;
|
||||
type: 'button' | 'menu';
|
||||
path?: string;
|
||||
component?: string;
|
||||
icon?: string;
|
||||
sort: number;
|
||||
status: number;
|
||||
children?: Permission[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// 菜单相关接口
|
||||
export interface Menu {
|
||||
id: string;
|
||||
parent_id?: string;
|
||||
type: 'button' | 'directory' | 'menu';
|
||||
name: string;
|
||||
title: string;
|
||||
path?: string;
|
||||
component?: string;
|
||||
icon?: string;
|
||||
sort: number;
|
||||
is_link: boolean;
|
||||
link_url?: string;
|
||||
is_show: boolean;
|
||||
permission?: string;
|
||||
status: number;
|
||||
children?: Menu[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateMenuDto {
|
||||
parent_id?: string;
|
||||
type: 'button' | 'directory' | 'menu';
|
||||
name: string;
|
||||
title: string;
|
||||
path?: string;
|
||||
component?: string;
|
||||
icon?: string;
|
||||
sort: number;
|
||||
is_link: boolean;
|
||||
link_url?: string;
|
||||
is_show: boolean;
|
||||
permission?: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface UpdateMenuDto {
|
||||
parent_id?: string;
|
||||
type?: 'button' | 'directory' | 'menu';
|
||||
name?: string;
|
||||
title?: string;
|
||||
path?: string;
|
||||
component?: string;
|
||||
icon?: string;
|
||||
sort?: number;
|
||||
is_link?: boolean;
|
||||
link_url?: string;
|
||||
is_show?: boolean;
|
||||
permission?: string;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export interface MenuListParams {
|
||||
name?: string;
|
||||
title?: string;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
// API 接口定义
|
||||
|
||||
// 用户管理接口
|
||||
export const getUserListApi = (params: UserListParams) => {
|
||||
return requestClient.get<{
|
||||
limit: number;
|
||||
list: User[];
|
||||
page: number;
|
||||
total: number;
|
||||
}>('/api/system/users', { params });
|
||||
};
|
||||
|
||||
export const getUserByIdApi = (id: string) => {
|
||||
return requestClient.get<User>(`/api/system/users/${id}`);
|
||||
};
|
||||
|
||||
export const createUserApi = (data: CreateUserDto) => {
|
||||
return requestClient.post<User>('/api/system/users', data);
|
||||
};
|
||||
|
||||
export const updateUserApi = (id: string, data: UpdateUserDto) => {
|
||||
return requestClient.put<User>(`/api/system/users/${id}`, data);
|
||||
};
|
||||
|
||||
export const deleteUserApi = (id: string) => {
|
||||
return requestClient.delete(`/api/system/users/${id}`);
|
||||
};
|
||||
|
||||
export const batchDeleteAdminApi = (ids: string[]) => {
|
||||
return requestClient.delete('/admin/user/batch-delete', { data: { ids } });
|
||||
};
|
||||
|
||||
// 管理员相关接口
|
||||
export interface AdminUser {
|
||||
id: string;
|
||||
username: string;
|
||||
realname: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
mobile?: string;
|
||||
status: number;
|
||||
roles: Role[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateAdminParams {
|
||||
username: string;
|
||||
realname: string;
|
||||
password: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
mobile?: string;
|
||||
role_ids: string[];
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface UpdateAdminParams {
|
||||
realname?: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
mobile?: string;
|
||||
role_ids?: string[];
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export const getAdminListApi = (params: UserListParams) => {
|
||||
return requestClient.get('/admin/user/list', {
|
||||
params,
|
||||
});
|
||||
};
|
||||
|
||||
export const createAdminApi = (data: CreateAdminParams) => {
|
||||
return requestClient.post('/admin/user/create', data);
|
||||
};
|
||||
|
||||
export const updateAdminApi = (id: string, data: UpdateAdminParams) => {
|
||||
return requestClient.put(`/admin/user/update/${id}`, data);
|
||||
};
|
||||
|
||||
export const deleteAdminApi = (id: string) => {
|
||||
return requestClient.delete(`/admin/user/delete/${id}`);
|
||||
};
|
||||
|
||||
export const setAdminRolesApi = (id: string, role_ids: string[]) => {
|
||||
return requestClient.put(`/admin/user/roles/${id}`, { role_ids });
|
||||
};
|
||||
|
||||
export const getAdminRolesApi = (id: string) => {
|
||||
return requestClient.get(`/admin/user/roles/${id}`);
|
||||
};
|
||||
|
||||
export const lockUserApi = (id: string) => {
|
||||
return requestClient.post(`/api/system/users/${id}/lock`);
|
||||
};
|
||||
|
||||
export const unlockUserApi = (id: string) => {
|
||||
return requestClient.post(`/api/system/users/${id}/unlock`);
|
||||
};
|
||||
|
||||
export const resetPasswordApi = (id: string, password: string) => {
|
||||
return requestClient.post(`/api/system/users/${id}/reset-password`, {
|
||||
password,
|
||||
});
|
||||
};
|
||||
|
||||
// 角色管理接口
|
||||
export const getRoleListApi = (params: RoleListParams) => {
|
||||
return requestClient.get<{
|
||||
limit: number;
|
||||
list: Role[];
|
||||
page: number;
|
||||
total: number;
|
||||
}>('/api/system/roles', { params });
|
||||
};
|
||||
|
||||
export const getAllRolesApi = () => {
|
||||
return requestClient.get<Role[]>('/api/system/roles/all');
|
||||
};
|
||||
|
||||
export const getRoleByIdApi = (id: string) => {
|
||||
return requestClient.get<Role>(`/api/system/roles/${id}`);
|
||||
};
|
||||
|
||||
export const createRoleApi = (data: CreateRoleDto) => {
|
||||
return requestClient.post<Role>('/api/system/roles', data);
|
||||
};
|
||||
|
||||
export const updateRoleApi = (id: string, data: UpdateRoleDto) => {
|
||||
return requestClient.put<Role>(`/api/system/roles/${id}`, data);
|
||||
};
|
||||
|
||||
export const deleteRoleApi = (id: string) => {
|
||||
return requestClient.delete(`/api/system/roles/${id}`);
|
||||
};
|
||||
|
||||
// 权限管理接口
|
||||
export const getPermissionListApi = () => {
|
||||
return requestClient.get<Permission[]>('/api/system/permissions');
|
||||
};
|
||||
|
||||
export const getPermissionTreeApi = () => {
|
||||
return requestClient.get<Permission[]>('/api/system/permissions/tree');
|
||||
};
|
||||
|
||||
// 菜单管理接口
|
||||
export const getMenuListApi = (params: MenuListParams) => {
|
||||
return requestClient.get<Menu[]>('/api/system/menus', { params });
|
||||
};
|
||||
|
||||
export const getMenuTreeApi = () => {
|
||||
return requestClient.get<Menu[]>('/api/system/menus/tree');
|
||||
};
|
||||
|
||||
export const getMenuByIdApi = (id: string) => {
|
||||
return requestClient.get<Menu>(`/api/system/menus/${id}`);
|
||||
};
|
||||
|
||||
export const createMenuApi = (data: CreateMenuDto) => {
|
||||
return requestClient.post<Menu>('/api/system/menus', data);
|
||||
};
|
||||
|
||||
export const updateMenuApi = (id: string, data: UpdateMenuDto) => {
|
||||
return requestClient.put<Menu>(`/api/system/menus/${id}`, data);
|
||||
};
|
||||
|
||||
export const deleteMenuApi = (id: string) => {
|
||||
return requestClient.delete(`/api/system/menus/${id}`);
|
||||
};
|
||||
|
||||
// 获取用户菜单(用于导航)
|
||||
export const getUserMenusApi = () => {
|
||||
return requestClient.get<Menu[]>('/api/system/menus/user');
|
||||
};
|
||||
350
admin/apps/web-ele/src/api/common/common.ts
Normal file
350
admin/apps/web-ele/src/api/common/common.ts
Normal file
@@ -0,0 +1,350 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 文件上传相关接口
|
||||
export interface UploadFile {
|
||||
id: string;
|
||||
original_name: string;
|
||||
filename: string;
|
||||
path: string;
|
||||
url: string;
|
||||
mime_type: string;
|
||||
size: number;
|
||||
driver: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface UploadResponse {
|
||||
file: UploadFile;
|
||||
url: string;
|
||||
}
|
||||
|
||||
// 字典相关接口
|
||||
export interface Dictionary {
|
||||
id: string;
|
||||
parent_id?: string;
|
||||
name: string;
|
||||
code: string;
|
||||
value?: string;
|
||||
sort: number;
|
||||
status: number;
|
||||
remark?: string;
|
||||
children?: Dictionary[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateDictionaryDto {
|
||||
parent_id?: string;
|
||||
name: string;
|
||||
code: string;
|
||||
value?: string;
|
||||
sort: number;
|
||||
status: number;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface UpdateDictionaryDto {
|
||||
parent_id?: string;
|
||||
name?: string;
|
||||
code?: string;
|
||||
value?: string;
|
||||
sort?: number;
|
||||
status?: number;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface DictionaryListParams {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
name?: string;
|
||||
code?: string;
|
||||
status?: number;
|
||||
parent_id?: string;
|
||||
}
|
||||
|
||||
// 系统日志相关接口
|
||||
export interface SystemLog {
|
||||
id: string;
|
||||
user_id?: string;
|
||||
username?: string;
|
||||
action: string;
|
||||
method: string;
|
||||
url: string;
|
||||
ip: string;
|
||||
user_agent: string;
|
||||
request_data?: any;
|
||||
response_data?: any;
|
||||
status_code: number;
|
||||
duration: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface LogListParams {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
user_id?: string;
|
||||
username?: string;
|
||||
action?: string;
|
||||
method?: string;
|
||||
status_code?: number;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
}
|
||||
|
||||
// 系统信息接口
|
||||
export interface SystemInfo {
|
||||
server: {
|
||||
arch: string;
|
||||
memory_usage: {
|
||||
external: number;
|
||||
heapTotal: number;
|
||||
heapUsed: number;
|
||||
rss: number;
|
||||
};
|
||||
node_version: string;
|
||||
os: string;
|
||||
uptime: number;
|
||||
};
|
||||
database: {
|
||||
size: string;
|
||||
type: string;
|
||||
version: string;
|
||||
};
|
||||
redis?: {
|
||||
connected_clients: number;
|
||||
memory: string;
|
||||
version: string;
|
||||
};
|
||||
application: {
|
||||
environment: string;
|
||||
name: string;
|
||||
timezone: string;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
// API 接口定义
|
||||
|
||||
// 文件上传接口
|
||||
export const uploadFileApi = (file: File, type?: string) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
if (type) {
|
||||
formData.append('type', type);
|
||||
}
|
||||
return requestClient.post<UploadResponse>('/api/system/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const uploadMultipleFilesApi = (files: File[], type?: string) => {
|
||||
const formData = new FormData();
|
||||
files.forEach((file) => {
|
||||
formData.append('files', file);
|
||||
});
|
||||
if (type) {
|
||||
formData.append('type', type);
|
||||
}
|
||||
return requestClient.post<UploadResponse[]>(
|
||||
'/api/system/upload/multiple',
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const getFileListApi = (params: {
|
||||
filename?: string;
|
||||
limit?: number;
|
||||
page?: number;
|
||||
type?: string;
|
||||
}) => {
|
||||
return requestClient.get<{
|
||||
limit: number;
|
||||
list: UploadFile[];
|
||||
page: number;
|
||||
total: number;
|
||||
}>('/api/system/files', { params });
|
||||
};
|
||||
|
||||
export const deleteFileApi = (id: string) => {
|
||||
return requestClient.delete(`/api/system/files/${id}`);
|
||||
};
|
||||
|
||||
// 字典管理接口
|
||||
export const getDictionaryListApi = (params: DictionaryListParams) => {
|
||||
return requestClient.get<{
|
||||
limit: number;
|
||||
list: Dictionary[];
|
||||
page: number;
|
||||
total: number;
|
||||
}>('/api/system/dictionaries', { params });
|
||||
};
|
||||
|
||||
export const getDictionaryTreeApi = (code?: string) => {
|
||||
return requestClient.get<Dictionary[]>('/api/system/dictionaries/tree', {
|
||||
params: { code },
|
||||
});
|
||||
};
|
||||
|
||||
export const getDictionaryByIdApi = (id: string) => {
|
||||
return requestClient.get<Dictionary>(`/api/system/dictionaries/${id}`);
|
||||
};
|
||||
|
||||
export const getDictionaryByCodeApi = (code: string) => {
|
||||
return requestClient.get<Dictionary[]>(
|
||||
`/api/system/dictionaries/code/${code}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const createDictionaryApi = (data: CreateDictionaryDto) => {
|
||||
return requestClient.post<Dictionary>('/api/system/dictionaries', data);
|
||||
};
|
||||
|
||||
export const updateDictionaryApi = (id: string, data: UpdateDictionaryDto) => {
|
||||
return requestClient.put<Dictionary>(`/api/system/dictionaries/${id}`, data);
|
||||
};
|
||||
|
||||
export const deleteDictionaryApi = (id: string) => {
|
||||
return requestClient.delete(`/api/system/dictionaries/${id}`);
|
||||
};
|
||||
|
||||
// 系统日志接口
|
||||
export const getSystemLogListApi = (params: LogListParams) => {
|
||||
return requestClient.get<{
|
||||
limit: number;
|
||||
list: SystemLog[];
|
||||
page: number;
|
||||
total: number;
|
||||
}>('/api/system/logs', { params });
|
||||
};
|
||||
|
||||
export const getSystemLogByIdApi = (id: string) => {
|
||||
return requestClient.get<SystemLog>(`/api/system/logs/${id}`);
|
||||
};
|
||||
|
||||
export const clearSystemLogsApi = (days?: number) => {
|
||||
return requestClient.delete('/api/system/logs/clear', {
|
||||
params: { days },
|
||||
});
|
||||
};
|
||||
|
||||
export const exportSystemLogsApi = (params: LogListParams) => {
|
||||
return requestClient.get('/api/system/logs/export', {
|
||||
params,
|
||||
responseType: 'blob',
|
||||
});
|
||||
};
|
||||
|
||||
// 系统信息接口
|
||||
export const getSystemInfoApi = () => {
|
||||
return requestClient.get<SystemInfo>('/api/system/info');
|
||||
};
|
||||
|
||||
// 健康检查接口
|
||||
export const getHealthCheckApi = () => {
|
||||
return requestClient.get<{
|
||||
checks: {
|
||||
database: { responseTime?: number; status: 'down' | 'up' };
|
||||
disk: { status: 'critical' | 'ok' | 'warning'; usage: number };
|
||||
memory: { status: 'critical' | 'ok' | 'warning'; usage: number };
|
||||
redis?: { responseTime?: number; status: 'down' | 'up' };
|
||||
};
|
||||
status: 'error' | 'ok';
|
||||
timestamp: string;
|
||||
uptime: number;
|
||||
}>('/api/system/health');
|
||||
};
|
||||
|
||||
// 缓存管理接口
|
||||
export const getCacheInfoApi = () => {
|
||||
return requestClient.get<{
|
||||
hits: number;
|
||||
keys: number;
|
||||
memory: string;
|
||||
misses: number;
|
||||
}>('/api/system/cache/info');
|
||||
};
|
||||
|
||||
export const clearCacheApi = (pattern?: string) => {
|
||||
return requestClient.delete('/api/system/cache/clear', {
|
||||
params: { pattern },
|
||||
});
|
||||
};
|
||||
|
||||
// 系统配置接口
|
||||
export const getSystemConfigApi = () => {
|
||||
return requestClient.get<Record<string, any>>('/api/system/config');
|
||||
};
|
||||
|
||||
export const updateSystemConfigApi = (data: Record<string, any>) => {
|
||||
return requestClient.put('/api/system/config', data);
|
||||
};
|
||||
|
||||
// 数据备份接口
|
||||
export const createBackupApi = () => {
|
||||
return requestClient.post<{
|
||||
created_at: string;
|
||||
filename: string;
|
||||
size: number;
|
||||
}>('/api/system/backup');
|
||||
};
|
||||
|
||||
export const getBackupListApi = () => {
|
||||
return requestClient.get<
|
||||
{
|
||||
created_at: string;
|
||||
filename: string;
|
||||
size: number;
|
||||
}[]
|
||||
>('/api/system/backup/list');
|
||||
};
|
||||
|
||||
export const downloadBackupApi = (filename: string) => {
|
||||
return requestClient.get(`/api/system/backup/download/${filename}`, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteBackupApi = (filename: string) => {
|
||||
return requestClient.delete(`/api/system/backup/${filename}`);
|
||||
};
|
||||
|
||||
// 系统通知接口
|
||||
export interface SystemNotification {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
type: 'error' | 'info' | 'success' | 'warning';
|
||||
is_read: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export const getNotificationListApi = (params: {
|
||||
is_read?: boolean;
|
||||
limit?: number;
|
||||
page?: number;
|
||||
}) => {
|
||||
return requestClient.get<{
|
||||
list: SystemNotification[];
|
||||
total: number;
|
||||
unread_count: number;
|
||||
}>('/api/system/notifications', { params });
|
||||
};
|
||||
|
||||
export const markNotificationReadApi = (id: string) => {
|
||||
return requestClient.post(`/api/system/notifications/${id}/read`);
|
||||
};
|
||||
|
||||
export const markAllNotificationsReadApi = () => {
|
||||
return requestClient.post('/api/system/notifications/read-all');
|
||||
};
|
||||
|
||||
export const deleteNotificationApi = (id: string) => {
|
||||
return requestClient.delete(`/api/system/notifications/${id}`);
|
||||
};
|
||||
141
admin/apps/web-ele/src/api/common/email.ts
Normal file
141
admin/apps/web-ele/src/api/common/email.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 邮件配置接口
|
||||
*/
|
||||
export interface EmailConfig {
|
||||
// SMTP配置
|
||||
smtp_host?: string;
|
||||
smtp_port?: number;
|
||||
smtp_username?: string;
|
||||
smtp_password?: string;
|
||||
smtp_from_email?: string;
|
||||
smtp_from_name?: string;
|
||||
smtp_encryption?: string;
|
||||
smtp_enabled?: boolean;
|
||||
|
||||
// 邮件模板配置
|
||||
register_subject?: string;
|
||||
register_content?: string;
|
||||
register_enabled?: boolean;
|
||||
reset_subject?: string;
|
||||
reset_content?: string;
|
||||
reset_enabled?: boolean;
|
||||
notify_subject?: string;
|
||||
notify_content?: string;
|
||||
notify_enabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮件模板预览数据
|
||||
*/
|
||||
export interface EmailTemplatePreview {
|
||||
subject: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邮件配置
|
||||
* @param type 配置类型 smtp | template
|
||||
*/
|
||||
export function getEmailConfigApi(type: string) {
|
||||
return requestClient.get<EmailConfig>(`/api/common/email/config/${type}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新邮件配置
|
||||
* @param type 配置类型 smtp | template
|
||||
* @param data 配置数据
|
||||
*/
|
||||
export function updateEmailConfigApi(type: string, data: Partial<EmailConfig>) {
|
||||
return requestClient.put(`/api/common/email/config/${type}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试邮件发送
|
||||
* @param email 收件人邮箱
|
||||
* @param type 邮件类型 register | reset | notify
|
||||
*/
|
||||
export function testEmailApi(email: string, type: string) {
|
||||
return requestClient.post('/api/common/email/test', {
|
||||
email,
|
||||
type,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览邮件模板
|
||||
* @param type 模板类型 register | reset | notify
|
||||
*/
|
||||
export function previewEmailTemplateApi(type: string) {
|
||||
return requestClient.get<EmailTemplatePreview>(`/api/common/email/template/preview/${type}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置邮件配置
|
||||
* @param type 配置类型 smtp | template
|
||||
*/
|
||||
export function resetEmailConfigApi(type: string) {
|
||||
return requestClient.post(`/api/common/email/config/reset/${type}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邮件发送统计
|
||||
*/
|
||||
export function getEmailStatsApi() {
|
||||
return requestClient.get('/api/common/email/stats');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邮件发送日志
|
||||
* @param params 查询参数
|
||||
*/
|
||||
export function getEmailLogsApi(params?: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
type?: string;
|
||||
status?: string;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
}) {
|
||||
return requestClient.get('/api/common/email/logs', { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空邮件发送队列
|
||||
*/
|
||||
export function clearEmailQueueApi() {
|
||||
return requestClient.post('/api/common/email/queue/clear');
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试失败的邮件
|
||||
* @param id 邮件ID
|
||||
*/
|
||||
export function retryEmailApi(id: number) {
|
||||
return requestClient.post(`/api/common/email/retry/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量重试失败的邮件
|
||||
* @param ids 邮件ID数组
|
||||
*/
|
||||
export function batchRetryEmailApi(ids: number[]) {
|
||||
return requestClient.post('/api/common/email/batch-retry', { ids });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除邮件日志
|
||||
* @param id 邮件ID
|
||||
*/
|
||||
export function deleteEmailLogApi(id: number) {
|
||||
return requestClient.delete(`/api/common/email/log/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除邮件日志
|
||||
* @param ids 邮件ID数组
|
||||
*/
|
||||
export function batchDeleteEmailLogApi(ids: number[]) {
|
||||
return requestClient.delete('/api/common/email/logs', { data: { ids } });
|
||||
}
|
||||
10
admin/apps/web-ele/src/api/common/index.ts
Normal file
10
admin/apps/web-ele/src/api/common/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// 统一导出系统管理相关的所有API接口
|
||||
|
||||
// 认证授权相关
|
||||
export * from './auth';
|
||||
|
||||
// 通用功能相关
|
||||
export * from './common';
|
||||
|
||||
// 系统设置相关
|
||||
export * from './settings';
|
||||
211
admin/apps/web-ele/src/api/common/login.ts
Normal file
211
admin/apps/web-ele/src/api/common/login.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
import { requestClient } from '#/api/request'
|
||||
|
||||
// 登录配置接口
|
||||
export interface LoginConfig {
|
||||
methods?: {
|
||||
username_enabled: boolean
|
||||
mobile_enabled: boolean
|
||||
email_enabled: boolean
|
||||
sms_enabled: boolean
|
||||
oauth_enabled: boolean
|
||||
guest_enabled: boolean
|
||||
}
|
||||
security?: {
|
||||
captcha_enabled: boolean
|
||||
captcha_type: 'image' | 'slide' | 'click'
|
||||
max_fail_attempts: number
|
||||
lock_duration: number
|
||||
password_strength_enabled: boolean
|
||||
password_min_length: number
|
||||
password_complexity: string[]
|
||||
password_expire_enabled: boolean
|
||||
password_expire_days: number
|
||||
single_sign_on_enabled: boolean
|
||||
}
|
||||
oauth?: {
|
||||
wechat_enabled: boolean
|
||||
wechat_app_id: string
|
||||
wechat_secret: string
|
||||
wechat_redirect_uri: string
|
||||
qq_enabled: boolean
|
||||
qq_app_id: string
|
||||
qq_secret: string
|
||||
qq_redirect_uri: string
|
||||
github_enabled: boolean
|
||||
github_client_id: string
|
||||
github_secret: string
|
||||
github_redirect_uri: string
|
||||
}
|
||||
register?: {
|
||||
register_enabled: boolean
|
||||
register_methods: string[]
|
||||
register_verification: 'none' | 'email' | 'sms' | 'manual'
|
||||
register_captcha_enabled: boolean
|
||||
agreement_required: boolean
|
||||
agreement_content: string
|
||||
default_role: string
|
||||
reward_enabled: boolean
|
||||
reward_points: number
|
||||
reward_balance: number
|
||||
}
|
||||
}
|
||||
|
||||
// 登录统计接口
|
||||
export interface LoginStats {
|
||||
total_logins: number
|
||||
today_logins: number
|
||||
failed_logins: number
|
||||
locked_accounts: number
|
||||
oauth_logins: number
|
||||
guest_logins: number
|
||||
}
|
||||
|
||||
// 登录记录接口
|
||||
export interface LoginRecord {
|
||||
id: number
|
||||
user_id: number
|
||||
username: string
|
||||
login_type: 'username' | 'mobile' | 'email' | 'sms' | 'oauth' | 'guest'
|
||||
login_ip: string
|
||||
login_location: string
|
||||
user_agent: string
|
||||
login_time: string
|
||||
logout_time?: string
|
||||
status: 'success' | 'failed' | 'locked'
|
||||
fail_reason?: string
|
||||
}
|
||||
|
||||
// 在线用户接口
|
||||
export interface OnlineUser {
|
||||
id: number
|
||||
user_id: number
|
||||
username: string
|
||||
nickname: string
|
||||
avatar: string
|
||||
login_ip: string
|
||||
login_location: string
|
||||
login_time: string
|
||||
last_activity: string
|
||||
device_type: string
|
||||
browser: string
|
||||
os: string
|
||||
}
|
||||
|
||||
// 获取登录配置
|
||||
export function getLoginConfigApi() {
|
||||
return requestClient.get<LoginConfig>('/api/common/login/config')
|
||||
}
|
||||
|
||||
// 更新登录配置
|
||||
export function updateLoginConfigApi(data: {
|
||||
type: 'methods' | 'security' | 'oauth' | 'register'
|
||||
config: any
|
||||
}) {
|
||||
return requestClient.put('/api/common/login/config', data)
|
||||
}
|
||||
|
||||
// 重置登录配置
|
||||
export function resetLoginConfigApi(type: 'methods' | 'security' | 'oauth' | 'register') {
|
||||
return requestClient.post('/api/common/login/config/reset', { type })
|
||||
}
|
||||
|
||||
// 测试登录配置
|
||||
export function testLoginConfigApi(data: {
|
||||
type: 'captcha' | 'oauth' | 'sms'
|
||||
config: any
|
||||
}) {
|
||||
return requestClient.post('/api/common/login/config/test', data)
|
||||
}
|
||||
|
||||
// 获取登录统计
|
||||
export function getLoginStatsApi() {
|
||||
return requestClient.get<LoginStats>('/api/common/login/stats')
|
||||
}
|
||||
|
||||
// 获取登录记录
|
||||
export function getLoginRecordsApi(params?: {
|
||||
page?: number
|
||||
limit?: number
|
||||
user_id?: number
|
||||
login_type?: string
|
||||
status?: string
|
||||
start_time?: string
|
||||
end_time?: string
|
||||
}) {
|
||||
return requestClient.get<{
|
||||
list: LoginRecord[]
|
||||
total: number
|
||||
page: number
|
||||
limit: number
|
||||
}>('/api/common/login/records', { params })
|
||||
}
|
||||
|
||||
// 获取在线用户
|
||||
export function getOnlineUsersApi(params?: {
|
||||
page?: number
|
||||
limit?: number
|
||||
username?: string
|
||||
device_type?: string
|
||||
}) {
|
||||
return requestClient.get<{
|
||||
list: OnlineUser[]
|
||||
total: number
|
||||
page: number
|
||||
limit: number
|
||||
}>('/api/common/login/online', { params })
|
||||
}
|
||||
|
||||
// 强制下线用户
|
||||
export function forceLogoutApi(userId: number) {
|
||||
return requestClient.post('/api/common/login/force-logout', { user_id: userId })
|
||||
}
|
||||
|
||||
// 批量强制下线用户
|
||||
export function batchForceLogoutApi(userIds: number[]) {
|
||||
return requestClient.post('/api/common/login/batch-force-logout', { user_ids: userIds })
|
||||
}
|
||||
|
||||
// 解锁账户
|
||||
export function unlockAccountApi(userId: number) {
|
||||
return requestClient.post('/api/common/login/unlock-account', { user_id: userId })
|
||||
}
|
||||
|
||||
// 批量解锁账户
|
||||
export function batchUnlockAccountApi(userIds: number[]) {
|
||||
return requestClient.post('/api/common/login/batch-unlock-account', { user_ids: userIds })
|
||||
}
|
||||
|
||||
// 清理登录记录
|
||||
export function cleanLoginRecordsApi(data: {
|
||||
days?: number
|
||||
status?: string
|
||||
}) {
|
||||
return requestClient.post('/api/common/login/clean-records', data)
|
||||
}
|
||||
|
||||
// 导出登录记录
|
||||
export function exportLoginRecordsApi(params?: {
|
||||
user_id?: number
|
||||
login_type?: string
|
||||
status?: string
|
||||
start_time?: string
|
||||
end_time?: string
|
||||
}) {
|
||||
return requestClient.get('/api/common/login/export-records', {
|
||||
params,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// 验证登录配置
|
||||
export function validateLoginConfigApi(data: {
|
||||
type: 'methods' | 'security' | 'oauth' | 'register'
|
||||
config: any
|
||||
}) {
|
||||
return requestClient.post('/api/common/login/config/validate', data)
|
||||
}
|
||||
|
||||
// 获取登录配置模板
|
||||
export function getLoginConfigTemplateApi(type: 'methods' | 'security' | 'oauth' | 'register') {
|
||||
return requestClient.get(`/api/common/login/config/template/${type}`)
|
||||
}
|
||||
220
admin/apps/web-ele/src/api/common/payment.ts
Normal file
220
admin/apps/web-ele/src/api/common/payment.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import { requestClient } from '#/api/request'
|
||||
|
||||
// 支付配置接口
|
||||
export interface PaymentConfig {
|
||||
// 支付宝配置
|
||||
alipay?: {
|
||||
alipay_app_id?: string
|
||||
alipay_gateway_url?: string
|
||||
alipay_private_key?: string
|
||||
alipay_public_key?: string
|
||||
alipay_sign_type?: string
|
||||
alipay_charset?: string
|
||||
alipay_notify_url?: string
|
||||
alipay_return_url?: string
|
||||
alipay_enabled?: boolean
|
||||
}
|
||||
// 微信支付配置
|
||||
wechat?: {
|
||||
wechat_app_id?: string
|
||||
wechat_mch_id?: string
|
||||
wechat_key?: string
|
||||
wechat_secret?: string
|
||||
wechat_cert_path?: string
|
||||
wechat_key_path?: string
|
||||
wechat_notify_url?: string
|
||||
wechat_trade_type?: string
|
||||
wechat_enabled?: boolean
|
||||
}
|
||||
// 通用配置
|
||||
general?: {
|
||||
default_method?: string
|
||||
timeout?: number
|
||||
min_amount?: number
|
||||
max_amount?: number
|
||||
success_url?: string
|
||||
fail_url?: string
|
||||
balance_enabled?: boolean
|
||||
points_enabled?: boolean
|
||||
points_ratio?: number
|
||||
}
|
||||
}
|
||||
|
||||
// 支付统计接口
|
||||
export interface PaymentStats {
|
||||
total_amount: number
|
||||
total_count: number
|
||||
success_count: number
|
||||
fail_count: number
|
||||
pending_count: number
|
||||
today_amount: number
|
||||
today_count: number
|
||||
alipay_amount: number
|
||||
alipay_count: number
|
||||
wechat_amount: number
|
||||
wechat_count: number
|
||||
balance_amount: number
|
||||
balance_count: number
|
||||
}
|
||||
|
||||
// 支付记录接口
|
||||
export interface PaymentRecord {
|
||||
id: number
|
||||
order_id: string
|
||||
user_id: number
|
||||
method: string
|
||||
amount: number
|
||||
status: string
|
||||
trade_no?: string
|
||||
transaction_id?: string
|
||||
subject: string
|
||||
body?: string
|
||||
notify_data?: any
|
||||
created_at: string
|
||||
updated_at: string
|
||||
user?: {
|
||||
id: number
|
||||
username: string
|
||||
nickname?: string
|
||||
}
|
||||
}
|
||||
|
||||
// 退款记录接口
|
||||
export interface RefundRecord {
|
||||
id: number
|
||||
payment_id: number
|
||||
refund_no: string
|
||||
amount: number
|
||||
reason: string
|
||||
status: string
|
||||
refund_data?: any
|
||||
created_at: string
|
||||
updated_at: string
|
||||
payment?: PaymentRecord
|
||||
}
|
||||
|
||||
// 测试支付参数
|
||||
export interface TestPaymentParams {
|
||||
method: string
|
||||
amount: number
|
||||
subject: string
|
||||
body?: string
|
||||
}
|
||||
|
||||
// 测试支付结果
|
||||
export interface TestPaymentResult {
|
||||
order_id: string
|
||||
payUrl?: string
|
||||
qrCode?: string
|
||||
message: string
|
||||
}
|
||||
|
||||
// 获取支付配置
|
||||
export function getPaymentConfigApi() {
|
||||
return requestClient.get<PaymentConfig>('/admin/settings/payment')
|
||||
}
|
||||
|
||||
// 更新支付配置
|
||||
export function updatePaymentConfigApi(data: {
|
||||
type: 'alipay' | 'wechat' | 'general'
|
||||
config: any
|
||||
}) {
|
||||
return requestClient.put('/admin/settings/payment', data)
|
||||
}
|
||||
|
||||
// 测试支付
|
||||
export function testPaymentApi(data: TestPaymentParams) {
|
||||
return requestClient.post<TestPaymentResult>('/admin/payment/test', data)
|
||||
}
|
||||
|
||||
// 重置支付配置
|
||||
export function resetPaymentConfigApi(type: 'alipay' | 'wechat' | 'general') {
|
||||
return requestClient.delete(`/admin/settings/payment/${type}`)
|
||||
}
|
||||
|
||||
// 获取支付统计
|
||||
export function getPaymentStatsApi(params?: {
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
method?: string
|
||||
}) {
|
||||
return requestClient.get<PaymentStats>('/admin/payment/stats', { params })
|
||||
}
|
||||
|
||||
// 获取支付记录
|
||||
export function getPaymentRecordsApi(params?: {
|
||||
page?: number
|
||||
limit?: number
|
||||
method?: string
|
||||
status?: string
|
||||
user_id?: number
|
||||
order_id?: string
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
}) {
|
||||
return requestClient.get<{
|
||||
data: PaymentRecord[]
|
||||
total: number
|
||||
page: number
|
||||
limit: number
|
||||
}>('/admin/payment/records', { params })
|
||||
}
|
||||
|
||||
// 获取退款记录
|
||||
export function getRefundRecordsApi(params?: {
|
||||
page?: number
|
||||
limit?: number
|
||||
status?: string
|
||||
payment_id?: number
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
}) {
|
||||
return requestClient.get<{
|
||||
data: RefundRecord[]
|
||||
total: number
|
||||
page: number
|
||||
limit: number
|
||||
}>('/admin/payment/refunds', { params })
|
||||
}
|
||||
|
||||
// 处理退款
|
||||
export function processRefundApi(data: {
|
||||
payment_id: number
|
||||
amount: number
|
||||
reason: string
|
||||
}) {
|
||||
return requestClient.post('/admin/payment/refund', data)
|
||||
}
|
||||
|
||||
// 查询支付状态
|
||||
export function queryPaymentStatusApi(orderId: string) {
|
||||
return requestClient.get(`/admin/payment/query/${orderId}`)
|
||||
}
|
||||
|
||||
// 同步支付状态
|
||||
export function syncPaymentStatusApi(paymentId: number) {
|
||||
return requestClient.post(`/admin/payment/sync/${paymentId}`)
|
||||
}
|
||||
|
||||
// 批量同步支付状态
|
||||
export function batchSyncPaymentStatusApi(paymentIds: number[]) {
|
||||
return requestClient.post('/admin/payment/batch-sync', { payment_ids: paymentIds })
|
||||
}
|
||||
|
||||
// 关闭支付订单
|
||||
export function closePaymentOrderApi(orderId: string) {
|
||||
return requestClient.post(`/admin/payment/close/${orderId}`)
|
||||
}
|
||||
|
||||
// 验证支付配置
|
||||
export function validatePaymentConfigApi(data: {
|
||||
type: 'alipay' | 'wechat'
|
||||
config: any
|
||||
}) {
|
||||
return requestClient.post('/admin/payment/validate', data)
|
||||
}
|
||||
|
||||
// 获取支付方式模板
|
||||
export function getPaymentMethodTemplateApi(method: 'alipay' | 'wechat') {
|
||||
return requestClient.get(`/admin/payment/template/${method}`)
|
||||
}
|
||||
229
admin/apps/web-ele/src/api/common/security.ts
Normal file
229
admin/apps/web-ele/src/api/common/security.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 安全配置接口
|
||||
export interface SecurityConfig {
|
||||
password?: {
|
||||
enablePasswordStrength: boolean;
|
||||
minPasswordLength: number;
|
||||
requireLowercase: boolean;
|
||||
requireUppercase: boolean;
|
||||
requireNumbers: boolean;
|
||||
requireSpecialChars: boolean;
|
||||
forbidCommonPasswords: boolean;
|
||||
passwordExpireDays: number;
|
||||
passwordHistoryLimit: number;
|
||||
forcePasswordChange: boolean;
|
||||
};
|
||||
login?: {
|
||||
maxLoginAttempts: number;
|
||||
lockoutDuration: number;
|
||||
enableLoginCaptcha: boolean;
|
||||
captchaTriggerAttempts: number;
|
||||
enableTwoFactor: boolean;
|
||||
forceTwoFactor: boolean;
|
||||
sessionTimeout: number;
|
||||
enableSingleSignOn: boolean;
|
||||
recordLoginLog: boolean;
|
||||
};
|
||||
ip?: {
|
||||
enableIpControl: boolean;
|
||||
accessMode: 'whitelist' | 'blacklist';
|
||||
ipWhitelist: string[];
|
||||
ipBlacklist: string[];
|
||||
adminIpWhitelist: string[];
|
||||
};
|
||||
audit?: {
|
||||
enableAudit: boolean;
|
||||
auditLoginLogout: boolean;
|
||||
auditUserManagement: boolean;
|
||||
auditRoleManagement: boolean;
|
||||
auditPermissionManagement: boolean;
|
||||
auditSystemConfig: boolean;
|
||||
auditDataExport: boolean;
|
||||
auditFileUpload: boolean;
|
||||
auditSensitiveOperations: boolean;
|
||||
auditLogRetention: number;
|
||||
enableSecondaryConfirm: boolean;
|
||||
confirmDeleteUser: boolean;
|
||||
confirmResetPassword: boolean;
|
||||
confirmModifyRole: boolean;
|
||||
confirmSystemBackup: boolean;
|
||||
confirmSystemRestore: boolean;
|
||||
confirmClearData: boolean;
|
||||
enableAnomalyDetection: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// 安全统计接口
|
||||
export interface SecurityStats {
|
||||
totalLoginAttempts: number;
|
||||
failedLoginAttempts: number;
|
||||
lockedAccounts: number;
|
||||
activeAuditLogs: number;
|
||||
blockedIpCount: number;
|
||||
securityEvents: {
|
||||
date: string;
|
||||
loginAttempts: number;
|
||||
failedLogins: number;
|
||||
securityAlerts: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
// 安全日志接口
|
||||
export interface SecurityLog {
|
||||
id: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
action: string;
|
||||
resource: string;
|
||||
ip: string;
|
||||
userAgent: string;
|
||||
result: 'success' | 'failed' | 'blocked';
|
||||
riskLevel: 'low' | 'medium' | 'high';
|
||||
details: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// IP测试结果接口
|
||||
export interface IpTestResult {
|
||||
ip: string;
|
||||
allowed: boolean;
|
||||
reason: string;
|
||||
matchedRule?: string;
|
||||
}
|
||||
|
||||
// 更新安全配置参数
|
||||
export interface UpdateSecurityConfigParams {
|
||||
type: 'password' | 'login' | 'ip' | 'audit';
|
||||
config: any;
|
||||
}
|
||||
|
||||
// 获取安全配置
|
||||
export function getSecurityConfigApi() {
|
||||
return requestClient.get<SecurityConfig>('/api/common/security/config');
|
||||
}
|
||||
|
||||
// 更新安全配置
|
||||
export function updateSecurityConfigApi(data: UpdateSecurityConfigParams) {
|
||||
return requestClient.put('/api/common/security/config', data);
|
||||
}
|
||||
|
||||
// 重置安全配置
|
||||
export function resetSecurityConfigApi(type: string) {
|
||||
return requestClient.post(`/api/common/security/config/reset/${type}`);
|
||||
}
|
||||
|
||||
// 测试安全配置
|
||||
export function testSecurityConfigApi(type: string, config: any) {
|
||||
return requestClient.post(`/api/common/security/config/test/${type}`, { config });
|
||||
}
|
||||
|
||||
// 获取安全统计
|
||||
export function getSecurityStatsApi(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
type?: string;
|
||||
}) {
|
||||
return requestClient.get<SecurityStats>('/api/common/security/stats', { params });
|
||||
}
|
||||
|
||||
// 获取安全日志
|
||||
export function getSecurityLogsApi(params?: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
userId?: string;
|
||||
action?: string;
|
||||
result?: string;
|
||||
riskLevel?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
ip?: string;
|
||||
}) {
|
||||
return requestClient.get<{
|
||||
list: SecurityLog[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>('/api/common/security/logs', { params });
|
||||
}
|
||||
|
||||
// 清理安全日志
|
||||
export function cleanSecurityLogsApi(params: {
|
||||
beforeDate: string;
|
||||
logType?: string;
|
||||
}) {
|
||||
return requestClient.delete('/api/common/security/logs/clean', { data: params });
|
||||
}
|
||||
|
||||
// 导出安全日志
|
||||
export function exportSecurityLogsApi(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
format?: 'excel' | 'csv';
|
||||
userId?: string;
|
||||
action?: string;
|
||||
}) {
|
||||
return requestClient.post('/api/common/security/logs/export', params, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
}
|
||||
|
||||
// 测试IP访问
|
||||
export function testIpAccessApi() {
|
||||
return requestClient.get<IpTestResult>('/api/common/security/ip/test');
|
||||
}
|
||||
|
||||
// 解锁账户
|
||||
export function unlockAccountApi(userId: string) {
|
||||
return requestClient.post(`/api/common/security/account/unlock/${userId}`);
|
||||
}
|
||||
|
||||
// 批量解锁账户
|
||||
export function batchUnlockAccountApi(userIds: string[]) {
|
||||
return requestClient.post('/api/common/security/account/unlock/batch', { userIds });
|
||||
}
|
||||
|
||||
// 强制下线用户
|
||||
export function forceLogoutUserApi(userId: string) {
|
||||
return requestClient.post(`/api/common/security/session/logout/${userId}`);
|
||||
}
|
||||
|
||||
// 批量强制下线用户
|
||||
export function batchForceLogoutUserApi(userIds: string[]) {
|
||||
return requestClient.post('/api/common/security/session/logout/batch', { userIds });
|
||||
}
|
||||
|
||||
// 获取在线用户
|
||||
export function getOnlineUsersApi(params?: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
username?: string;
|
||||
ip?: string;
|
||||
}) {
|
||||
return requestClient.get('/api/common/security/session/online', { params });
|
||||
}
|
||||
|
||||
// 验证安全配置
|
||||
export function validateSecurityConfigApi(type: string, config: any) {
|
||||
return requestClient.post(`/api/common/security/config/validate/${type}`, { config });
|
||||
}
|
||||
|
||||
// 获取安全配置模板
|
||||
export function getSecurityConfigTemplateApi(type: string) {
|
||||
return requestClient.get(`/api/common/security/config/template/${type}`);
|
||||
}
|
||||
|
||||
// 安全扫描
|
||||
export function securityScanApi() {
|
||||
return requestClient.post('/api/common/security/scan');
|
||||
}
|
||||
|
||||
// 获取安全建议
|
||||
export function getSecuritySuggestionsApi() {
|
||||
return requestClient.get('/api/common/security/suggestions');
|
||||
}
|
||||
|
||||
// 应用安全建议
|
||||
export function applySecuritySuggestionApi(suggestionId: string) {
|
||||
return requestClient.post(`/api/common/security/suggestions/apply/${suggestionId}`);
|
||||
}
|
||||
314
admin/apps/web-ele/src/api/common/settings.ts
Normal file
314
admin/apps/web-ele/src/api/common/settings.ts
Normal file
@@ -0,0 +1,314 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 基础设置接口
|
||||
export interface BasicSettings {
|
||||
site_name: string;
|
||||
site_title: string;
|
||||
site_keywords: string;
|
||||
site_description: string;
|
||||
site_logo: string;
|
||||
site_icon: string;
|
||||
site_icp: string;
|
||||
site_copyright: string;
|
||||
site_status: boolean;
|
||||
site_close_reason: string;
|
||||
}
|
||||
|
||||
// 邮件设置接口
|
||||
export interface EmailSettings {
|
||||
enabled: boolean;
|
||||
driver: 'mailgun' | 'sendmail' | 'ses' | 'smtp';
|
||||
smtp_host: string;
|
||||
smtp_port: number;
|
||||
smtp_username: string;
|
||||
smtp_password: string;
|
||||
smtp_encryption: 'none' | 'ssl' | 'tls';
|
||||
from_email: string;
|
||||
from_name: string;
|
||||
}
|
||||
|
||||
// 短信设置接口
|
||||
export interface SmsSettings {
|
||||
enabled: boolean;
|
||||
driver: 'aliyun' | 'huawei' | 'qiniu' | 'tencent';
|
||||
access_key_id: string;
|
||||
access_key_secret: string;
|
||||
sign_name: string;
|
||||
template_code: string;
|
||||
code_length: number;
|
||||
code_expire: number;
|
||||
rate_limit: number;
|
||||
test_mobile: string;
|
||||
}
|
||||
|
||||
// 存储设置接口
|
||||
export interface StorageSettings {
|
||||
default_driver: 'aliyun_oss' | 'aws_s3' | 'local' | 'qiniu' | 'tencent_cos';
|
||||
max_file_size: number;
|
||||
allowed_extensions: string[];
|
||||
local: {
|
||||
base_url: string;
|
||||
storage_path: string;
|
||||
};
|
||||
aliyun_oss: {
|
||||
access_key_id: string;
|
||||
access_key_secret: string;
|
||||
bucket: string;
|
||||
custom_domain?: string;
|
||||
endpoint: string;
|
||||
};
|
||||
tencent_cos: {
|
||||
bucket: string;
|
||||
custom_domain?: string;
|
||||
region: string;
|
||||
secret_id: string;
|
||||
secret_key: string;
|
||||
};
|
||||
qiniu: {
|
||||
access_key: string;
|
||||
bucket: string;
|
||||
domain: string;
|
||||
secret_key: string;
|
||||
};
|
||||
aws_s3: {
|
||||
access_key_id: string;
|
||||
bucket: string;
|
||||
custom_domain?: string;
|
||||
region: string;
|
||||
secret_access_key: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 支付设置接口
|
||||
export interface PaymentSettings {
|
||||
alipay: {
|
||||
app_id: string;
|
||||
enabled: boolean;
|
||||
mode: 'production' | 'sandbox';
|
||||
notify_url?: string;
|
||||
private_key: string;
|
||||
public_key: string;
|
||||
return_url?: string;
|
||||
};
|
||||
wechat: {
|
||||
api_key: string;
|
||||
app_id: string;
|
||||
cert_path?: string;
|
||||
enabled: boolean;
|
||||
key_path?: string;
|
||||
mch_id: string;
|
||||
mode: 'production' | 'sandbox';
|
||||
notify_url?: string;
|
||||
};
|
||||
unionpay: {
|
||||
cert_password: string;
|
||||
cert_path: string;
|
||||
enabled: boolean;
|
||||
mer_id: string;
|
||||
mode: 'production' | 'sandbox';
|
||||
notify_url?: string;
|
||||
return_url?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 登录设置接口
|
||||
export interface LoginSettings {
|
||||
login_methods: {
|
||||
email: boolean;
|
||||
github: boolean;
|
||||
mobile: boolean;
|
||||
qq: boolean;
|
||||
username: boolean;
|
||||
wechat: boolean;
|
||||
};
|
||||
captcha: {
|
||||
enabled: boolean;
|
||||
expire: number;
|
||||
length: number;
|
||||
type: 'email' | 'image' | 'sms';
|
||||
};
|
||||
password_policy: {
|
||||
min_length: number;
|
||||
require_lowercase: boolean;
|
||||
require_numbers: boolean;
|
||||
require_symbols: boolean;
|
||||
require_uppercase: boolean;
|
||||
};
|
||||
session: {
|
||||
max_sessions: number;
|
||||
remember_me: boolean;
|
||||
timeout: number;
|
||||
};
|
||||
security: {
|
||||
force_logout_on_password_change: boolean;
|
||||
lockout_duration: number;
|
||||
max_login_attempts: number;
|
||||
};
|
||||
}
|
||||
|
||||
// API 接口定义
|
||||
|
||||
// 基础设置
|
||||
export const getBasicSettingsApi = () => {
|
||||
return requestClient.get<BasicSettings>('/api/system/settings/basic');
|
||||
};
|
||||
|
||||
export const updateBasicSettingsApi = (data: Partial<BasicSettings>) => {
|
||||
return requestClient.put<BasicSettings>('/api/system/settings/basic', data);
|
||||
};
|
||||
|
||||
// 邮件设置
|
||||
export const getEmailSettingsApi = () => {
|
||||
return requestClient.get<EmailSettings>('/api/system/settings/email');
|
||||
};
|
||||
|
||||
export const updateEmailSettingsApi = (data: Partial<EmailSettings>) => {
|
||||
return requestClient.put<EmailSettings>('/api/system/settings/email', data);
|
||||
};
|
||||
|
||||
export const testEmailApi = (email: string) => {
|
||||
return requestClient.post('/api/system/settings/email/test', { email });
|
||||
};
|
||||
|
||||
// 短信设置
|
||||
export const getSmsSettingsApi = () => {
|
||||
return requestClient.get<SmsSettings>('/api/system/settings/sms');
|
||||
};
|
||||
|
||||
export const updateSmsSettingsApi = (data: Partial<SmsSettings>) => {
|
||||
return requestClient.put<SmsSettings>('/api/system/settings/sms', data);
|
||||
};
|
||||
|
||||
export const testSmsApi = (mobile: string) => {
|
||||
return requestClient.post('/api/system/settings/sms/test', { mobile });
|
||||
};
|
||||
|
||||
// 存储设置
|
||||
export const getStorageSettingsApi = () => {
|
||||
return requestClient.get<StorageSettings>('/api/system/settings/storage');
|
||||
};
|
||||
|
||||
export const updateStorageSettingsApi = (data: Partial<StorageSettings>) => {
|
||||
return requestClient.put<StorageSettings>(
|
||||
'/api/system/settings/storage',
|
||||
data,
|
||||
);
|
||||
};
|
||||
|
||||
export const testStorageConnectionApi = (driver: string) => {
|
||||
return requestClient.post('/api/system/settings/storage/test', { driver });
|
||||
};
|
||||
|
||||
// 支付设置
|
||||
export const getPaymentSettingsApi = () => {
|
||||
return requestClient.get<PaymentSettings>('/api/system/settings/payment');
|
||||
};
|
||||
|
||||
export const updatePaymentSettingsApi = (data: Partial<PaymentSettings>) => {
|
||||
return requestClient.put<PaymentSettings>(
|
||||
'/api/system/settings/payment',
|
||||
data,
|
||||
);
|
||||
};
|
||||
|
||||
// 登录设置
|
||||
export const getLoginSettingsApi = () => {
|
||||
return requestClient.get<LoginSettings>('/api/system/settings/login');
|
||||
};
|
||||
|
||||
export const updateLoginSettingsApi = (data: Partial<LoginSettings>) => {
|
||||
return requestClient.put<LoginSettings>('/api/system/settings/login', data);
|
||||
};
|
||||
|
||||
// 获取所有设置
|
||||
export const getAllSettingsApi = () => {
|
||||
return requestClient.get<{
|
||||
basic: BasicSettings;
|
||||
email: EmailSettings;
|
||||
login: LoginSettings;
|
||||
payment: PaymentSettings;
|
||||
sms: SmsSettings;
|
||||
storage: StorageSettings;
|
||||
}>('/api/system/settings');
|
||||
};
|
||||
|
||||
// 重置设置到默认值
|
||||
export const resetSettingsApi = (
|
||||
type: 'basic' | 'email' | 'login' | 'payment' | 'sms' | 'storage',
|
||||
) => {
|
||||
return requestClient.post(`/api/system/settings/${type}/reset`);
|
||||
};
|
||||
|
||||
// 导出设置
|
||||
export const exportSettingsApi = () => {
|
||||
return requestClient.get('/api/system/settings/export', {
|
||||
responseType: 'blob',
|
||||
});
|
||||
};
|
||||
|
||||
// 导入设置
|
||||
export const importSettingsApi = (file: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return requestClient.post('/api/system/settings/import', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 通知设置接口
|
||||
export interface NotificationSettings {
|
||||
email: {
|
||||
enabled: boolean;
|
||||
types: string[];
|
||||
adminEmails: string[];
|
||||
rateLimit: number;
|
||||
retryTimes: number;
|
||||
queueDelay: number;
|
||||
};
|
||||
sms: {
|
||||
enabled: boolean;
|
||||
types: string[];
|
||||
adminPhones: string[];
|
||||
rateLimit: number;
|
||||
retryTimes: number;
|
||||
queueDelay: number;
|
||||
};
|
||||
system: {
|
||||
enabled: boolean;
|
||||
types: string[];
|
||||
retentionDays: number;
|
||||
maxPerUser: number;
|
||||
autoRead: boolean;
|
||||
};
|
||||
wechat: {
|
||||
enabled: boolean;
|
||||
types: string[];
|
||||
templateIds: Record<string, string>;
|
||||
retryTimes: number;
|
||||
queueDelay: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateNotificationSettingsParams {
|
||||
type: 'email' | 'sms' | 'system' | 'wechat';
|
||||
settings: Partial<NotificationSettings[keyof NotificationSettings]>;
|
||||
}
|
||||
|
||||
// 通知设置API
|
||||
export const getNotificationSettingsApi = () => {
|
||||
return requestClient.get<NotificationSettings>('/api/system/settings/notification');
|
||||
};
|
||||
|
||||
export const updateNotificationSettingsApi = (data: UpdateNotificationSettingsParams) => {
|
||||
return requestClient.put<NotificationSettings>('/api/system/settings/notification', data);
|
||||
};
|
||||
|
||||
export const resetNotificationSettingsApi = (type: 'email' | 'sms' | 'system' | 'wechat') => {
|
||||
return requestClient.post(`/api/system/settings/notification/${type}/reset`);
|
||||
};
|
||||
|
||||
export const testNotificationApi = (type: 'email' | 'sms' | 'system' | 'wechat') => {
|
||||
return requestClient.post(`/api/system/settings/notification/${type}/test`);
|
||||
};
|
||||
184
admin/apps/web-ele/src/api/common/sms.ts
Normal file
184
admin/apps/web-ele/src/api/common/sms.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 短信配置接口
|
||||
export interface SmsConfig {
|
||||
provider: 'aliyun' | 'tencent';
|
||||
access_key_id?: string;
|
||||
access_key_secret?: string;
|
||||
sign_name?: string;
|
||||
region?: string;
|
||||
secret_id?: string;
|
||||
secret_key?: string;
|
||||
app_id?: string;
|
||||
sign_content?: string;
|
||||
enabled: boolean;
|
||||
debug_mode: boolean;
|
||||
templates: SmsTemplate[];
|
||||
rate_limit: {
|
||||
per_minute: number;
|
||||
per_hour: number;
|
||||
per_day: number;
|
||||
};
|
||||
}
|
||||
|
||||
// 短信模板接口
|
||||
export interface SmsTemplate {
|
||||
type: 'verify_code' | 'notification' | 'marketing';
|
||||
template_id: string;
|
||||
content: string;
|
||||
variables?: string;
|
||||
}
|
||||
|
||||
// 短信统计接口
|
||||
export interface SmsStats {
|
||||
total_sent: number;
|
||||
success_count: number;
|
||||
failed_count: number;
|
||||
today_sent: number;
|
||||
this_month_sent: number;
|
||||
}
|
||||
|
||||
// 短信日志接口
|
||||
export interface SmsLog {
|
||||
id: number;
|
||||
mobile: string;
|
||||
content: string;
|
||||
template_type: string;
|
||||
status: 'pending' | 'sent' | 'failed';
|
||||
error_message?: string;
|
||||
provider: string;
|
||||
created_at: string;
|
||||
sent_at?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取短信配置
|
||||
*/
|
||||
export async function getSmsConfigApi(): Promise<SmsConfig> {
|
||||
return requestClient.get('/api/admin/settings/sms');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新短信配置
|
||||
*/
|
||||
export async function updateSmsConfigApi(data: SmsConfig): Promise<void> {
|
||||
return requestClient.put('/api/admin/settings/sms', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试短信发送
|
||||
*/
|
||||
export async function testSmsApi(data: {
|
||||
mobile: string;
|
||||
template_type: string;
|
||||
content: string;
|
||||
}): Promise<void> {
|
||||
return requestClient.post('/api/admin/settings/sms/test', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置短信配置
|
||||
*/
|
||||
export async function resetSmsConfigApi(): Promise<void> {
|
||||
return requestClient.post('/api/admin/settings/sms/reset');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取短信统计
|
||||
*/
|
||||
export async function getSmsStatsApi(): Promise<SmsStats> {
|
||||
return requestClient.get('/api/admin/settings/sms/stats');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取短信日志
|
||||
*/
|
||||
export async function getSmsLogsApi(params?: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
mobile?: string;
|
||||
status?: string;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
}): Promise<{
|
||||
data: SmsLog[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
}> {
|
||||
return requestClient.get('/api/admin/settings/sms/logs', { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空短信队列
|
||||
*/
|
||||
export async function clearSmsQueueApi(): Promise<void> {
|
||||
return requestClient.post('/api/admin/settings/sms/clear-queue');
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试发送短信
|
||||
*/
|
||||
export async function retrySmsApi(id: number): Promise<void> {
|
||||
return requestClient.post(`/api/admin/settings/sms/retry/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量重试发送短信
|
||||
*/
|
||||
export async function batchRetrySmsApi(ids: number[]): Promise<void> {
|
||||
return requestClient.post('/api/admin/settings/sms/batch-retry', { ids });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除短信日志
|
||||
*/
|
||||
export async function deleteSmsLogApi(id: number): Promise<void> {
|
||||
return requestClient.delete(`/api/admin/settings/sms/logs/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除短信日志
|
||||
*/
|
||||
export async function batchDeleteSmsLogApi(ids: number[]): Promise<void> {
|
||||
return requestClient.post('/api/admin/settings/sms/logs/batch-delete', { ids });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取短信模板预览
|
||||
*/
|
||||
export async function previewSmsTemplateApi(data: {
|
||||
template_id: string;
|
||||
variables: Record<string, any>;
|
||||
}): Promise<{
|
||||
content: string;
|
||||
preview: string;
|
||||
}> {
|
||||
return requestClient.post('/api/admin/settings/sms/template/preview', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证短信配置
|
||||
*/
|
||||
export async function validateSmsConfigApi(data: Partial<SmsConfig>): Promise<{
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
}> {
|
||||
return requestClient.post('/api/admin/settings/sms/validate', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取短信服务商配置模板
|
||||
*/
|
||||
export async function getSmsProviderTemplateApi(provider: string): Promise<{
|
||||
fields: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
placeholder?: string;
|
||||
options?: Array<{ label: string; value: string }>;
|
||||
}>;
|
||||
}> {
|
||||
return requestClient.get(`/api/admin/settings/sms/provider/${provider}/template`);
|
||||
}
|
||||
207
admin/apps/web-ele/src/api/common/storage.ts
Normal file
207
admin/apps/web-ele/src/api/common/storage.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
// 存储配置接口
|
||||
export interface StorageConfig {
|
||||
driver: 'local' | 'oss' | 'cos' | 'qiniu' | 'upyun' | 's3';
|
||||
// 本地存储配置
|
||||
local_path?: string;
|
||||
local_domain?: string;
|
||||
// 阿里云OSS配置
|
||||
oss_access_key_id?: string;
|
||||
oss_access_key_secret?: string;
|
||||
oss_bucket?: string;
|
||||
oss_region?: string;
|
||||
oss_domain?: string;
|
||||
oss_is_private?: boolean;
|
||||
// 腾讯云COS配置
|
||||
cos_secret_id?: string;
|
||||
cos_secret_key?: string;
|
||||
cos_bucket?: string;
|
||||
cos_region?: string;
|
||||
// 七牛云配置
|
||||
qiniu_access_key?: string;
|
||||
qiniu_secret_key?: string;
|
||||
qiniu_bucket?: string;
|
||||
qiniu_domain?: string;
|
||||
// 又拍云配置
|
||||
upyun_username?: string;
|
||||
upyun_password?: string;
|
||||
upyun_bucket?: string;
|
||||
upyun_domain?: string;
|
||||
// AWS S3配置
|
||||
s3_access_key_id?: string;
|
||||
s3_secret_access_key?: string;
|
||||
s3_bucket?: string;
|
||||
s3_region?: string;
|
||||
s3_endpoint?: string;
|
||||
// 上传限制
|
||||
max_size: number;
|
||||
allowed_types: string[];
|
||||
// 图片处理
|
||||
thumbnail_enabled: boolean;
|
||||
thumbnail_width?: number;
|
||||
thumbnail_height?: number;
|
||||
// 其他设置
|
||||
enabled: boolean;
|
||||
is_default: boolean;
|
||||
}
|
||||
|
||||
// 存储统计接口
|
||||
export interface StorageStats {
|
||||
total_files: number;
|
||||
total_size: number;
|
||||
used_space: string;
|
||||
available_space: string;
|
||||
files_by_type: Record<string, number>;
|
||||
upload_trend: Array<{
|
||||
date: string;
|
||||
count: number;
|
||||
size: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
// 文件信息接口
|
||||
export interface FileInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
path: string;
|
||||
url: string;
|
||||
size: number;
|
||||
type: string;
|
||||
mime_type: string;
|
||||
driver: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// 上传结果接口
|
||||
export interface UploadResult {
|
||||
success: boolean;
|
||||
url: string;
|
||||
path: string;
|
||||
size: number;
|
||||
type: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储配置
|
||||
*/
|
||||
export function getStorageConfigApi() {
|
||||
return requestClient.get<StorageConfig>('/admin/settings/storage');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新存储配置
|
||||
*/
|
||||
export function updateStorageConfigApi(data: StorageConfig) {
|
||||
return requestClient.put('/admin/settings/storage', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试存储配置
|
||||
*/
|
||||
export function testStorageApi(formData: FormData) {
|
||||
return requestClient.post<UploadResult>('/admin/settings/storage/test', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置存储配置
|
||||
*/
|
||||
export function resetStorageConfigApi() {
|
||||
return requestClient.post('/admin/settings/storage/reset');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储统计信息
|
||||
*/
|
||||
export function getStorageStatsApi() {
|
||||
return requestClient.get<StorageStats>('/admin/settings/storage/stats');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件列表
|
||||
*/
|
||||
export function getFileListApi(params?: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
type?: string;
|
||||
driver?: string;
|
||||
keyword?: string;
|
||||
}) {
|
||||
return requestClient.get<{
|
||||
list: FileInfo[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
}>('/admin/files', { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*/
|
||||
export function deleteFileApi(id: string) {
|
||||
return requestClient.delete(`/admin/files/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除文件
|
||||
*/
|
||||
export function batchDeleteFilesApi(ids: string[]) {
|
||||
return requestClient.delete('/admin/files/batch', { data: { ids } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*/
|
||||
export function uploadFileApi(formData: FormData) {
|
||||
return requestClient.post<UploadResult>('/admin/files/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件详情
|
||||
*/
|
||||
export function getFileDetailApi(id: string) {
|
||||
return requestClient.get<FileInfo>(`/admin/files/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理无效文件
|
||||
*/
|
||||
export function cleanInvalidFilesApi() {
|
||||
return requestClient.post('/admin/files/clean');
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步文件信息
|
||||
*/
|
||||
export function syncFilesApi() {
|
||||
return requestClient.post('/admin/files/sync');
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证存储配置
|
||||
*/
|
||||
export function validateStorageConfigApi(data: Partial<StorageConfig>) {
|
||||
return requestClient.post<{ valid: boolean; message?: string }>(
|
||||
'/admin/settings/storage/validate',
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储驱动模板配置
|
||||
*/
|
||||
export function getStorageDriverTemplateApi(driver: string) {
|
||||
return requestClient.get<Partial<StorageConfig>>(
|
||||
`/admin/settings/storage/template/${driver}`
|
||||
);
|
||||
}
|
||||
105
admin/apps/web-ele/src/api/common/system.ts
Normal file
105
admin/apps/web-ele/src/api/common/system.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 系统配置接口
|
||||
*/
|
||||
export interface SystemConfig {
|
||||
id?: number;
|
||||
site_id?: number;
|
||||
config_key: string;
|
||||
value: string;
|
||||
status?: number;
|
||||
create_time?: string;
|
||||
update_time?: string;
|
||||
addon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统信息接口
|
||||
*/
|
||||
export interface SystemInfo {
|
||||
os: string;
|
||||
server: string;
|
||||
php_version: string;
|
||||
mysql_version: string;
|
||||
redis_version: string;
|
||||
node_version: string;
|
||||
memory_usage: string;
|
||||
disk_usage: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基本信息配置
|
||||
*/
|
||||
export interface BasicConfig {
|
||||
site_name: string;
|
||||
site_title: string;
|
||||
site_description: string;
|
||||
site_keywords: string;
|
||||
site_icp: string;
|
||||
site_copyright: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统配置
|
||||
*/
|
||||
export interface ConfigSettings {
|
||||
site_status: string;
|
||||
site_close_reason?: string;
|
||||
timezone: string;
|
||||
default_language: string;
|
||||
page_size: number;
|
||||
cache_enabled: boolean;
|
||||
debug_enabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统配置
|
||||
* @param type 配置类型
|
||||
*/
|
||||
export async function getSystemConfigApi(type: 'basic' | 'config'): Promise<any> {
|
||||
return requestClient.get(`/api/system/config/${type}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统配置
|
||||
* @param type 配置类型
|
||||
* @param data 配置数据
|
||||
*/
|
||||
export async function updateSystemConfigApi(
|
||||
type: 'basic' | 'config',
|
||||
data: BasicConfig | ConfigSettings,
|
||||
): Promise<void> {
|
||||
return requestClient.put(`/api/system/config/${type}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统信息
|
||||
*/
|
||||
export async function getSystemInfoApi(): Promise<SystemInfo> {
|
||||
return requestClient.get('/api/system/info');
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出系统信息
|
||||
*/
|
||||
export async function exportSystemInfoApi(): Promise<void> {
|
||||
return requestClient.get('/api/system/info/export', {
|
||||
responseType: 'blob',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置系统配置
|
||||
* @param type 配置类型
|
||||
*/
|
||||
export async function resetSystemConfigApi(type: 'basic' | 'config'): Promise<void> {
|
||||
return requestClient.post(`/api/system/config/${type}/reset`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除系统缓存
|
||||
*/
|
||||
export async function clearSystemCacheApi(): Promise<void> {
|
||||
return requestClient.post('/api/system/cache/clear');
|
||||
}
|
||||
51
admin/apps/web-ele/src/api/core/auth.ts
Normal file
51
admin/apps/web-ele/src/api/core/auth.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { baseRequestClient, requestClient } from '#/api/request';
|
||||
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
export interface LoginParams {
|
||||
password?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/** 登录接口返回值 */
|
||||
export interface LoginResult {
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
data: string;
|
||||
status: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
export async function loginApi(data: AuthApi.LoginParams) {
|
||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新accessToken
|
||||
*/
|
||||
export async function refreshTokenApi() {
|
||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
export async function logoutApi() {
|
||||
return baseRequestClient.post('/auth/logout', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户权限码
|
||||
*/
|
||||
export async function getAccessCodesApi() {
|
||||
return requestClient.get<string[]>('/auth/codes');
|
||||
}
|
||||
3
admin/apps/web-ele/src/api/core/index.ts
Normal file
3
admin/apps/web-ele/src/api/core/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './auth';
|
||||
export * from './menu';
|
||||
export * from './user';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user