Compare commits

..

1 Commits

Author SHA1 Message Date
vben
f3e7438a49 chore: release v5.3.0-beta.1 2024-09-10 22:07:54 +08:00
1062 changed files with 14250 additions and 51167 deletions

View File

@@ -3,5 +3,3 @@ node_modules
.gitignore .gitignore
*.md *.md
dist dist
.turbo
dist.zip

18
.github/CODEOWNERS vendored
View File

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

View File

@@ -1,7 +1,7 @@
name: 🐞 Bug Report name: 🐞 Bug Report
description: Report an issue with Vben Admin to help us make it better. description: Report an issue with Vben Admin to help us make it better.
title: 'Bug: ' title: "Bug: "
labels: ['bug: pending triage'] labels: ["bug: pending triage"]
body: body:
- type: markdown - type: markdown
@@ -62,7 +62,7 @@ body:
description: Before submitting the issue, please make sure you do the following 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). # description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com).
options: options:
- label: Read the [docs](https://doc.vben.pro/) - label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/)
required: true required: true
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version) - label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
required: true required: true

View File

@@ -1,6 +1,6 @@
name: 📚 Documentation name: 📚 Documentation
description: Report an issue with Vben Admin Website to help us make it better. description: Report an issue with Vben Admin Website to help us make it better.
title: 'Docs: ' title: "Docs: "
labels: [documentation] labels: [documentation]
body: body:
- type: markdown - type: markdown

View File

@@ -1,7 +1,7 @@
name: ✨ New Feature Proposal name: ✨ New Feature Proposal
description: Propose a new feature to be added to Vben Admin description: Propose a new feature to be added to Vben Admin
title: 'FEATURE: ' title: "FEATURE: "
labels: ['enhancement: pending triage'] labels: ["enhancement: pending triage"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
@@ -62,7 +62,7 @@ body:
label: Validations label: Validations
description: Before submitting the issue, please make sure you do the following description: Before submitting the issue, please make sure you do the following
options: options:
- label: Read the [docs](https://doc.vben.pro/) - label: Read the [docs](https://anncwb.github.io/vue-vben-admin-doc/)
required: true required: true
- label: Ensure the code is up to date. (Some issues have been fixed in the latest version) - label: Ensure the code is up to date. (Some issues have been fixed in the latest version)
required: true required: true

View File

@@ -1,9 +1,9 @@
name: 'Setup Node' name: "Setup Node"
description: 'Setup node and pnpm' description: "Setup node and pnpm"
runs: runs:
using: 'composite' using: "composite"
steps: steps:
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
@@ -12,7 +12,7 @@ runs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version-file: .node-version node-version-file: .node-version
cache: 'pnpm' cache: "pnpm"
- name: Get pnpm store directory - name: Get pnpm store directory
shell: bash shell: bash

View File

@@ -19,9 +19,11 @@ Project maintainers have the right and responsibility to remove, edit, or reject
- Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch. - Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch.
- If adding a new feature: - 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. - 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: - If fixing bug:
- Provide a detailed description of the bug in the PR. Live demo preferred. - 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. - It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.

View File

@@ -1,7 +1,7 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: npm - package-ecosystem: npm
directory: '/' directory: "/"
schedule: schedule:
interval: daily interval: daily
groups: groups:
@@ -9,7 +9,7 @@ updates:
update-types: [minor, patch] update-types: [minor, patch]
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: '/' directory: "/"
schedule: schedule:
interval: weekly interval: weekly
groups: groups:

View File

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

View File

@@ -7,7 +7,7 @@ on:
- main - main
env: env:
HUSKY: '0' HUSKY: "0"
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }} group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
@@ -19,15 +19,8 @@ permissions:
jobs: jobs:
post-update: post-update:
if: github.repository == 'vbenjs/vue-vben-admin'
# if: ${{ github.actor == 'dependabot[bot]' }} # if: ${{ github.actor == 'dependabot[bot]' }}
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy:
matrix:
os:
- ubuntu-latest
# - macos-latest
- windows-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4

View File

@@ -18,7 +18,7 @@ env:
jobs: jobs:
version: 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.event.pull_request.merged || github.event_name == 'workflow_dispatch') && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
# if: github.repository == 'vbenjs/vue-vben-admin' # if: github.repository == 'vbenjs/vue-vben-admin'
timeout-minutes: 15 timeout-minutes: 15
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -36,7 +36,7 @@ jobs:
uses: changesets/action@v1 uses: changesets/action@v1
with: with:
version: pnpm run version version: pnpm run version
commit: 'chore: bump versions' commit: "chore: bump versions"
title: 'chore: bump versions' title: "chore: bump versions"
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -5,7 +5,7 @@ on:
push: push:
branches: branches:
- main - main
- 'releases/*' - "releases/*"
permissions: permissions:
contents: read contents: read
@@ -17,7 +17,7 @@ env:
jobs: jobs:
test: test:
name: Test name: Test
if: github.repository == 'vbenjs/vue-vben-admin' if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
@@ -56,7 +56,7 @@ jobs:
lint: lint:
name: Lint name: Lint
if: github.repository == 'vbenjs/vue-vben-admin' if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
@@ -79,7 +79,6 @@ jobs:
check: check:
name: Check name: Check
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 20 timeout-minutes: 20
strategy: strategy:
@@ -109,8 +108,8 @@ jobs:
ci-ok: ci-ok:
name: CI OK name: CI OK
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && always()
needs: [test, check, lint] needs: [test, check, lint]
env: env:
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }} FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}

View File

@@ -9,20 +9,19 @@
# the `language` matrix defined below to confirm you have the correct set of # the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages. # supported CodeQL languages.
# #
name: 'CodeQL' name: "CodeQL"
on: on:
push: push:
branches: ['main'] branches: ["main"]
pull_request: pull_request:
branches: ['main'] branches: ["main"]
schedule: schedule:
- cron: '35 0 * * 0' - cron: "35 0 * * 0"
jobs: jobs:
analyze: analyze:
name: Analyze (${{ matrix.language }}) name: Analyze (${{ matrix.language }})
if: github.repository == 'vbenjs/vue-vben-admin'
# Runner size impacts CodeQL analysis time. To learn more, please see: # Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql # - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources # - https://gh.io/supported-runners-and-hardware-resources
@@ -91,4 +90,4 @@ jobs:
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v3
with: with:
category: '/language:${{matrix.language}}' category: "/language:${{matrix.language}}"

View File

@@ -8,7 +8,7 @@ on:
jobs: jobs:
deploy-playground-ftp: deploy-playground-ftp:
name: Deploy Push 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' if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -39,7 +39,7 @@ jobs:
deploy-docs-ftp: deploy-docs-ftp:
name: Deploy Push 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' if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -63,7 +63,7 @@ jobs:
deploy-antd-ftp: deploy-antd-ftp:
name: Deploy Push 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' if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -94,7 +94,7 @@ jobs:
deploy-ele-ftp: deploy-ele-ftp:
name: Deploy Push Element 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' if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -125,7 +125,7 @@ jobs:
deploy-naive-ftp: deploy-naive-ftp:
name: Deploy Push 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' if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@@ -153,20 +153,3 @@ jobs:
username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }} username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}
password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }} password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}
local-dir: ./apps/web-naive/dist/ 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 }}

View File

@@ -17,7 +17,6 @@ jobs:
# write permission is required for autolabeler # write permission is required for autolabeler
# otherwise, read permission is required at least # otherwise, read permission is required at least
pull-requests: write pull-requests: write
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: release-drafter/release-drafter@v6 - uses: release-drafter/release-drafter@v6

View File

@@ -3,29 +3,23 @@ name: Issue Close Require
# 触发条件:每天零点 # 触发条件:每天零点
on: on:
workflow_dispatch:
schedule: schedule:
- cron: '0 0 * * *' - cron: "0 0 * * *"
permissions: permissions:
pull-requests: write pull-requests: write
contents: write contents: write
issues: write
jobs: jobs:
close-issues: close-issues:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# 关闭未活动的 Issues # 步骤1关闭未活动的 Issues
- name: Close Inactive Issues - name: Close Inactive Issues
uses: actions/stale@v9 uses: actions-cool/issues-helper@v3
with: with:
days-before-stale: -1 # Issues and PR will never be flagged stale automatically. actions: "close-issues" # 执行动作:关闭 Issues
stale-issue-label: needs-reproduction # Label that flags an issue as stale. token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token用于认证
only-labels: needs-reproduction # Only process these issues labels: "needs reproduction" # 目标标签
days-before-issue-close: 3 inactive-day: 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

View File

@@ -13,34 +13,33 @@ permissions:
jobs: jobs:
reply-labeled: reply-labeled:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: remove enhancement pending - name: remove enhancement pending
if: github.event.label.name == 'enhancement' if: github.event.label.name == 'enhancement'
uses: actions-cool/issues-helper@v3 uses: actions-cool/issues-helper@v3
with: with:
actions: 'remove-labels' actions: "remove-labels"
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }} issue-number: ${{ github.event.issue.number }}
labels: 'enhancement: pending triage' labels: "enhancement: pending triage"
- name: remove bug pending - name: remove bug pending
if: github.event.label.name == 'bug' if: github.event.label.name == 'bug'
uses: actions-cool/issues-helper@v3 uses: actions-cool/issues-helper@v3
with: with:
actions: 'remove-labels' actions: "remove-labels"
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }} issue-number: ${{ github.event.issue.number }}
labels: 'bug: pending triage' labels: "bug: pending triage"
- name: needs reproduction - name: needs reproduction
if: github.event.label.name == 'needs reproduction' if: github.event.label.name == 'needs reproduction'
uses: actions-cool/issues-helper@v3 uses: actions-cool/issues-helper@v3
with: with:
actions: 'create-comment, remove-labels' actions: "create-comment, remove-labels"
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }} issue-number: ${{ github.event.issue.number }}
body: | body: |
Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `needs reproduction` will be closed if no activities in 3 days. Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `needs reproduction` will be closed if no activities in 3 days.
labels: 'bug: pending triage' labels: "bug: pending triage"

View File

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

View File

@@ -3,10 +3,10 @@ name: Create Release Tag
on: on:
push: push:
tags: tags:
- 'v*.*.*' # Push events to matching v*, i.e. v1.0, v20.15.10 - "v*.*.*" # Push events to matching v*, i.e. v1.0, v20.15.10
env: env:
HUSKY: '0' HUSKY: "0"
permissions: permissions:
pull-requests: write pull-requests: write
@@ -15,7 +15,6 @@ permissions:
jobs: jobs:
build: build:
name: Create Release name: Create Release
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:

View File

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

View File

@@ -9,9 +9,8 @@ on:
jobs: jobs:
main: main:
name: Semantic Pull Request
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Semantic Pull Request
steps: steps:
- name: Validate PR title - name: Validate PR title
uses: amannn/action-semantic-pull-request@v5 uses: amannn/action-semantic-pull-request@v5

View File

@@ -1,19 +1,18 @@
name: 'Close stale issues' name: "Close stale issues"
on: on:
schedule: schedule:
- cron: '0 1 * * *' - cron: "0 1 * * *"
jobs: jobs:
stale: stale:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v9
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} 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-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' 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' exempt-issue-labels: "bug,enhancement"
days-before-stale: 60 days-before-stale: 60
days-before-close: 7 days-before-close: 7

View File

@@ -2,5 +2,5 @@ ports:
- port: 5555 - port: 5555
onOpen: open-preview onOpen: open-preview
tasks: tasks:
- init: npm i -g corepack && pnpm install - init: corepack enable && pnpm install
command: pnpm run dev:play command: pnpm run dev

6
.husky/commit-msg Executable file
View File

@@ -0,0 +1,6 @@
echo Start running commit-msg hook...
# Check whether the git commit information is standardized
pnpm exec commitlint --edit "$1"
echo Run commit-msg hook done.

3
.husky/post-merge Normal file
View File

@@ -0,0 +1,3 @@
# 每次 git pull 之后, 安装依赖
pnpm install

7
.husky/pre-commit Executable file
View File

@@ -0,0 +1,7 @@
# update `.vscode/vben-admin.code-workspace` file
pnpm vsh code-workspace --auto-commit
# Format and submit code according to lintstagedrc.js configuration
pnpm exec lint-staged
echo Run pre-commit hook done.

20
.lintstagedrc.mjs Normal file
View File

@@ -0,0 +1,20 @@
export default {
'*.{js,jsx,ts,tsx}': [
'prettier --cache --ignore-unknown --write',
'eslint --cache --fix',
],
'*.{scss,less,styl,html,vue,css}': [
'prettier --cache --ignore-unknown --write',
'stylelint --fix --allow-empty-input',
],
'*.md': ['prettier --cache --ignore-unknown --write'],
'*.vue': [
'prettier --write',
'eslint --cache --fix',
'stylelint --fix --allow-empty-input',
],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
'prettier --cache --write--parser json',
],
'package.json': ['prettier --cache --write'],
};

View File

@@ -1 +1 @@
22.1.0 20.14.0

2
.npmrc
View File

@@ -1,5 +1,5 @@
registry = "https://registry.npmmirror.com" registry = "https://registry.npmmirror.com"
public-hoist-pattern[]=lefthook public-hoist-pattern[]=husky
public-hoist-pattern[]=eslint public-hoist-pattern[]=eslint
public-hoist-pattern[]=prettier public-hoist-pattern[]=prettier
public-hoist-pattern[]=prettier-plugin-tailwindcss public-hoist-pattern[]=prettier-plugin-tailwindcss

View File

@@ -19,9 +19,7 @@
// i18n 插件 // i18n 插件
"Lokalise.i18n-ally", "Lokalise.i18n-ally",
// CSS 变量提示 // CSS 变量提示
"vunguyentuan.vscode-css-variables", "vunguyentuan.vscode-css-variables"
// 在 package.json 中显示 PNPM catalog 的版本
"antfu.pnpm-catalog-lens"
], ],
"unwantedRecommendations": [ "unwantedRecommendations": [
// 和 volar 冲突 // 和 volar 冲突

8
.vscode/launch.json vendored
View File

@@ -9,7 +9,7 @@
"url": "http://localhost:5555", "url": "http://localhost:5555",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}/playground" "webRoot": "${workspaceFolder}"
}, },
{ {
"type": "chrome", "type": "chrome",
@@ -18,7 +18,7 @@
"url": "http://localhost:5666", "url": "http://localhost:5666",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/web-antd" "webRoot": "${workspaceFolder}"
}, },
{ {
"type": "chrome", "type": "chrome",
@@ -27,7 +27,7 @@
"url": "http://localhost:5777", "url": "http://localhost:5777",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/web-ele" "webRoot": "${workspaceFolder}"
}, },
{ {
"type": "chrome", "type": "chrome",
@@ -36,7 +36,7 @@
"url": "http://localhost:5888", "url": "http://localhost:5888",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/web-naive" "webRoot": "${workspaceFolder}"
} }
] ]
} }

31
.vscode/settings.json vendored
View File

@@ -14,7 +14,7 @@
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.detectIndentation": false, "editor.detectIndentation": false,
"editor.cursorBlinking": "expand", "editor.cursorBlinking": "expand",
"editor.largeFileOptimizations": true, "editor.largeFileOptimizations": false,
"editor.accessibilitySupport": "off", "editor.accessibilitySupport": "off",
"editor.cursorSmoothCaretAnimation": "on", "editor.cursorSmoothCaretAnimation": "on",
"editor.guides.bracketPairs": "active", "editor.guides.bracketPairs": "active",
@@ -91,7 +91,6 @@
"**/bower_components": true, "**/bower_components": true,
"**/.turbo": true, "**/.turbo": true,
"**/.idea": true, "**/.idea": true,
"**/.vitepress": true,
"**/tmp": true, "**/tmp": true,
"**/.git": true, "**/.git": true,
"**/.svn": true, "**/.svn": true,
@@ -113,8 +112,6 @@
"**/yarn.lock": true "**/yarn.lock": true
}, },
"typescript.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"],
// search // search
"search.searchEditor.singleClickBehaviour": "peekDefinition", "search.searchEditor.singleClickBehaviour": "peekDefinition",
"search.followSymlinks": false, "search.followSymlinks": false,
@@ -163,7 +160,6 @@
"stylelint.enable": true, "stylelint.enable": true,
"stylelint.packageManager": "pnpm", "stylelint.packageManager": "pnpm",
"stylelint.validate": ["css", "less", "postcss", "scss", "vue"], "stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
"stylelint.customSyntax": "postcss-html",
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"], "stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
"typescript.inlayHints.enumMemberValues.enabled": true, "typescript.inlayHints.enumMemberValues.enabled": true,
@@ -201,14 +197,11 @@
"playground/src/locales/langs", "playground/src/locales/langs",
"apps/*/src/locales/langs" "apps/*/src/locales/langs"
], ],
"i18n-ally.pathMatcher": "{locale}/{namespace}.{ext}", "i18n-ally.pathMatcher": "{locale}.json",
"i18n-ally.enabledParsers": ["json"], "i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"],
"i18n-ally.sourceLanguage": "en", "i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN", "i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"], "i18n-ally.enabledFrameworks": ["vue", "react"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
// 控制相关文件嵌套展示 // 控制相关文件嵌套展示
"explorer.fileNesting.enabled": true, "explorer.fileNesting.enabled": true,
@@ -219,23 +212,13 @@
"*.env": "$(capture).env.*", "*.env": "$(capture).env.*",
"README.md": "README*,CHANGELOG*,LICENSE,CNAME", "README.md": "README*,CHANGELOG*,LICENSE,CNAME",
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json", "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml", "Dockerfile": "Dockerfile,.docker*,docker-entrypoint.sh,build-local-docker*,nginx.conf",
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
"tailwind.config.mjs": "postcss.*" "tailwind.config.mjs": "postcss.*"
}, },
"commentTranslate.hover.enabled": false, "commentTranslate.hover.enabled": false,
"i18n-ally.keystyle": "nested",
"commentTranslate.multiLineMerge": true, "commentTranslate.multiLineMerge": true,
"vue.server.hybridMode": true, "vue.server.hybridMode": true,
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib"
"oxc.enable": false,
"cSpell.words": [
"archiver",
"axios",
"dotenv",
"isequal",
"jspm",
"napi",
"nolebase",
"rollup",
"vitest"
]
} }

View File

@@ -1,4 +1,4 @@
FROM node:22-slim AS builder FROM node:20-slim AS builder
# --max-old-space-size # --max-old-space-size
ENV PNPM_HOME="/pnpm" ENV PNPM_HOME="/pnpm"
@@ -6,32 +6,26 @@ ENV PATH="$PNPM_HOME:$PATH"
ENV NODE_OPTIONS=--max-old-space-size=8192 ENV NODE_OPTIONS=--max-old-space-size=8192
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
RUN npm i -g corepack RUN corepack enable
WORKDIR /app WORKDIR /app
# copy package.json and pnpm-lock.yaml to workspace # copy package.json and pnpm-lock.yaml to workspace
COPY . /app COPY . /app
# 安装依赖
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run build --filter=\!./docs RUN pnpm run build
RUN echo "Builder Success 🎉" RUN echo "Builder Success 🎉"
FROM nginx:stable-alpine AS production FROM nginx:stable-alpine as production
# 配置 nginx RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf
RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf \
&& rm -rf /etc/nginx/conf.d/default.conf
# 复制构建产物
COPY --from=builder /app/playground/dist /usr/share/nginx/html COPY --from=builder /app/playground/dist /usr/share/nginx/html
# 复制 nginx 配置 COPY ./nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /app/scripts/deploy/nginx.conf /etc/nginx/nginx.conf
EXPOSE 8080 EXPOSE 8080
# 启动 nginx # start nginx
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,13 +1,8 @@
<div align="center"> <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.6/source/logo-v1.webp"> </a> <br> <br>
<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](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) [![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue Vben Admin</h1> <h1>Vue Vben Admin</h1>
</div> </div>
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg)
@@ -20,27 +15,27 @@ Vue Vben Adminは、最新の`vue3`、`vite`、`TypeScript`などの主流技術
## アップグレード通知 ## アップグレード通知
これは最新バージョン `5.0` であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。 これは最新バージョン5.0であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。
## 特徴 ## 特徴
- **最新技術スタック**Vue 3やViteなどの最先端フロントエンド技術で開発 - **最新技術スタック**: Vue 3やViteなどの最先端フロントエンド技術で開発
- **TypeScript**アプリケーション規模のJavaScriptのための言語 - **TypeScript**: アプリケーション規模のJavaScriptのための言語
- **テーマ**複数のテーマカラーが利用可能で、カスタマイズオプションも豊富 - **テーマ**: 複数のテーマカラーが利用可能で、カスタマイズオプションも豊富
- **国際化**完全な内蔵国際化サポート - **国際化**: 完全な内蔵国際化サポート
- **権限管理**動的ルートベースの権限生成ソリューションを内蔵 - **権限管理**: 動的ルートベースの権限生成ソリューションを内蔵
## プレビュー ## プレビュー
- [Vben Admin](https://vben.pro/) - フルバージョンの中国語サイト - [Vben Admin](https://vben.pro/) - フルバージョンの中国語サイト
テストアカウントvben/123456 テストアカウント: vben/123456
<div align="center"> <p 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/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/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png"> <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</div> </p>
### Gitpodを使用 ### Gitpodを使用
@@ -54,27 +49,30 @@ GitpodGitHub用の無料オンライン開発環境でプロジェクト
## インストールと使用 ## インストールと使用
1. プロジェクトコードを取得 - プロジェクトコードを取得
```bash ```bash
git clone https://github.com/vbenjs/vue-vben-admin.git git clone https://github.com/vbenjs/vue-vben-admin.git
``` ```
2. 依存関係のインストール - 依存関係のインストール
```bash ```bash
cd vue-vben-admin cd vue-vben-admin
npm i -g corepack
corepack enable
pnpm install pnpm install
``` ```
3. 実行 - 実行
```bash ```bash
pnpm dev pnpm dev
``` ```
4. ビルド - ビルド
```bash ```bash
pnpm build pnpm build
@@ -88,60 +86,58 @@ pnpm build
ご参加をお待ちしております![Issueを提出](https://github.com/anncwb/vue-vben-admin/issues/new/choose)するか、Pull Requestを送信してください。 ご参加をお待ちしております![Issueを提出](https://github.com/anncwb/vue-vben-admin/issues/new/choose)するか、Pull Requestを送信してください。
**Pull Request プロセス:** **Pull Request:**
1. コードをフォーク 1. コードをフォーク
2. 自分のブランチを作成`git checkout -b feat/xxxx` 2. 自分のブランチを作成: `git checkout -b feat/xxxx`
3. 変更をコミット`git commit -am 'feat(function): add xxxxx'` 3. 変更をコミット: `git commit -am 'feat(function): add xxxxx'`
4. ブランチをプッシュ`git push origin feat/xxxx` 4. ブランチをプッシュ: `git push origin feat/xxxx`
5. `pull request`を送信 5. `pull request`を送信
## Git貢献提出規則 ## 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)) - 参考 [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` 新機能の追加 - `feat` 新機能の追加
- `fix` 問題/バグの修正 - `fix` 問題/バグの修正
- `style` コードスタイルに関連し、実行結果に影響しない - `style` コードスタイルに関連し、実行結果に影響しない
- `perf` 最適化/パフォーマンス向上 - `perf` 最適化/パフォーマンス向上
- `refactor` リファクタリング - `refactor` リファクタリング
- `revert` 変更の取り消し - `revert` 変更の取り消し
- `test` テスト関連 - `test` テスト関連
- `docs` ドキュメント/注釈 - `docs` ドキュメント/注釈
- `chore` 依存関係の更新/スキャフォールディング設定の変更など - `chore` 依存関係の更新/スキャフォールディング設定の変更など
- `ci` 継続的インテグレーション - `ci` 継続的インテグレーション
- `types` 型定義ファイルの変更 - `types` 型定義ファイルの変更
- `wip` 開発中
## ブラウザサポート ## ブラウザサポート
ローカル開発には `Chrome 80+` ブラウザを推奨します ローカル開発には`Chrome 80+`ブラウザを推奨します
モダンブラウザをサポートし、IEはサポートしません モダンブラウザをサポートし、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 | | [<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>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バージョン | | サポートしない | 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン |
## メンテナー ## メンテナー
[@Vben](https://github.com/anncwb) [@Vben](https://github.com/anncwb)
## スター歴史
[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date)
## 寄付 ## 寄付
このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます! このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます!
![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png) ![donate](https://unpkg.com/@vbenjs/static-source@0.1.6/source/sponsor.png)
<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 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://github.com/vbenjs/vue-vben-admin/graphs/contributors"> <a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img alt="Contributors" src="https://opencollective.com/vbenjs/contributors.svg?button=false" /> <img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
</a> </a>
## Discord ## Discord

View File

@@ -1,13 +1,8 @@
<div align="center"> <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.6/source/logo-v1.webp"> </a> <br> <br>
<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](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) [![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue Vben Admin</h1> <h1>Vue Vben Admin</h1>
</div> </div>
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg)
@@ -22,7 +17,7 @@ Vue Vben Admin is a free and open source middle and back-end template. Using the
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). 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 ## Feature
- **Latest Technology Stack**: Developed with cutting-edge front-end technologies like Vue 3 and Vite - **Latest Technology Stack**: Developed with cutting-edge front-end technologies like Vue 3 and Vite
- **TypeScript**: A language for application-scale JavaScript - **TypeScript**: A language for application-scale JavaScript
@@ -36,11 +31,11 @@ This is the latest version, 5.0, and it is not compatible with previous versions
Test Account: vben/123456 Test Account: vben/123456
<div align="center"> <p 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/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/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png"> <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</div> </p>
### Use Gitpod ### Use Gitpod
@@ -52,29 +47,31 @@ Open the project in Gitpod (free online dev environment for GitHub) and start co
[Document](https://doc.vben.pro/) [Document](https://doc.vben.pro/)
## Install and Use ## Install and use
1. Get the project code - Get the project code
```bash ```bash
git clone https://github.com/vbenjs/vue-vben-admin.git git clone https://github.com/vbenjs/vue-vben-admin.git
``` ```
2. Install dependencies - Installation dependencies
```bash ```bash
cd vue-vben-admin cd vue-vben-admin
npm i -g corepack
corepack enable
pnpm install pnpm install
``` ```
3. Run - run
```bash ```bash
pnpm dev pnpm dev
``` ```
4. Build - build
```bash ```bash
pnpm build pnpm build
@@ -84,64 +81,62 @@ pnpm build
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases) [CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
## How to Contribute ## 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. 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:** **Pull Request:**
1. Fork the code 1. Fork code!
2. Create your branch: `git checkout -b feat/xxxx` 2. Create your own branch: `git checkout -b feat/xxxx`
3. Submit your changes: `git commit -am 'feat(function): add xxxxx'` 3. Submit your changes: `git commit -am 'feat(function): add xxxxx'`
4. Push your branch: `git push origin feat/xxxx` 4. Push your branch: `git push origin feat/xxxx`
5. Submit `pull request` 5. submit`pull request`
## Git Contribution Submission Specification ## 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)) - 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 - `feat` Add new features
- `fix` Fix the problem/BUG - `fix` Fix the problem/BUG
- `style` The code style is related and does not affect the running result - `style` The code style is related and does not affect the running result
- `perf` Optimization/performance improvement - `perf` Optimization/performance improvement
- `refactor` Refactor - `refactor` Refactor
- `revert` Undo edit - `revert` Undo edit
- `test` Test related - `test` Test related
- `docs` Documentation/notes - `docs` Documentation/notes
- `chore` Dependency update/scaffolding configuration modification etc. - `chore` Dependency update/scaffolding configuration modification etc.
- `ci` Continuous integration - `ci` Continuous integration
- `types` Type definition file changes - `types` Type definition file changes
- `wip` In development
## Browser Support ## Browser support
The `Chrome 80+` browser is recommended for local development The `Chrome 80+` browser is recommended for local development
Support modern browsers, not IE 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 | | [<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>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 | | not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## Maintainer ## Maintainer
[@Vben](https://github.com/anncwb) [@Vben](https://github.com/anncwb)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date)
## Donate ## Donate
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support! If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png) ![donate](https://unpkg.com/@vbenjs/static-source@0.1.6/source/sponsor.png)
<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> <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>
## Contributors ## Contributor
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors"> <a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img alt="Contributors" src="https://opencollective.com/vbenjs/contributors.svg?button=false" /> <img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
</a> </a>
## Discord ## Discord

View File

@@ -1,13 +1,8 @@
<div align="center"> <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.6/source/logo-v1.webp"> </a> <br> <br>
<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](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) [![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue Vben Admin</h1> <h1>Vue Vben Admin</h1>
</div> </div>
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg)
@@ -20,31 +15,31 @@ Vue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的
## 升级提示 ## 升级提示
该版本为最新版本 `5.0`与其他版本不兼容,如果你是新项目,建议使用最新版本。如果你想查看旧版本,请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2) 该版本为最新版本`5.0`, 与其他版本不兼容,如果你是新项目,建议使用最新版本。如果你想查看旧版本,请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2)
## 特性 ## 特性
- **最新技术栈**:使用 Vue3/vite 等前端前沿技术开发 - **最新技术栈**:使用 Vue3/vite 等前端前沿技术开发
- **TypeScript**应用程序级 JavaScript 的语言 - **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**:提供多套主题色彩,可配置自定义主题 - **主题**:提供多套主题色彩,可配置自定义主题
- **国际化**:内置完善的国际化方案 - **国际化**:内置完善的国际化方案
- **权限**内置完善的动态路由权限生成方案 - **权限** 内置完善的动态路由权限生成方案
## 预览 ## 预览
- [Vben Admin](https://vben.pro/) - 完整版中文站点 - [Vben Admin](https://vben.pro/) - 完整版中文站点
测试账号vben/123456 测试账号: vben/123456
<div align="center"> <p 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/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/preview2.png">
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png"> <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
</div> </p>
### 使用 Gitpod ### 使用 Gitpod
在 Gitpod适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码 在 Gitpod适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin) [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)
@@ -54,100 +49,100 @@ Vue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的
## 安装使用 ## 安装使用
1. 获取项目代码 - 获取项目代码
```bash ```bash
git clone https://github.com/vbenjs/vue-vben-admin.git git clone https://github.com/vbenjs/vue-vben-admin.git
``` ```
2. 安装依赖 - 安装依赖
```bash ```bash
cd vue-vben-admin cd vue-vben-admin
npm i -g corepack
corepack enable
pnpm install pnpm install
``` ```
3. 运行 - 运行
```bash ```bash
pnpm dev pnpm dev
``` ```
4. 打包 - 打包
```bash ```bash
pnpm build pnpm build
``` ```
## 更新日志
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
## 如何贡献 ## 如何贡献
非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。 非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。
**Pull Request 流程:** **Pull Request:**
1. Fork 代码 1. Fork 代码!
2. 创建自己的分支`git checkout -b feature/xxxx` 2. 创建自己的分支: `git checkout -b feature/xxxx`
3. 提交你的修改`git commit -am 'feat(function): add xxxxx'` 3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支`git push origin feature/xxxx` 4. 推送您的分支: `git push origin feature/xxxx`
5. 提交 `pull request` 5. 提交`pull request`
## Git 贡献提交规范 ## 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)) - 参考 [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` 增加新功能 - `feat` 增加新功能
- `fix` 修复问题/BUG - `fix` 修复问题/BUG
- `style` 代码风格相关无影响运行结果的 - `style` 代码风格相关无影响运行结果的
- `perf` 优化/性能提升 - `perf` 优化/性能提升
- `refactor` 重构 - `refactor` 重构
- `revert` 撤销修改 - `revert` 撤销修改
- `test` 测试相关 - `test` 测试相关
- `docs` 文档/注释 - `docs` 文档/注释
- `chore` 依赖更新/脚手架配置修改等 - `chore` 依赖更新/脚手架配置修改等
- `ci` 持续集成 - `ci` 持续集成
- `types` 类型定义文件更改 - `types` 类型定义文件更改
- `wip` 开发中
## 浏览器支持 ## 浏览器支持
本地开发推荐使用 `Chrome 80+` 浏览器 本地开发推荐使用`Chrome 80+` 浏览器
支持现代浏览器不支持 IE 支持现代浏览器, 不支持 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 | | [<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>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 | | not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 维护者 ## 维护者
[@Vben](https://github.com/anncwb) [@Vben](https://github.com/anncwb)
## Star 历史
[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date)
## 捐赠 ## 捐赠
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持 如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png) ![donate](https://unpkg.com/@vbenjs/static-source@0.1.6/source/sponsor.png)
<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 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>
## 贡献者 ## 更新日志
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
## Contributor
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors"> <a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img alt="Contributors" src="https://opencollective.com/vbenjs/contributors.svg?button=false" /> <img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
</a> </a>
## Discord ## Discord
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) - [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)
## 许可证 ## License
[MIT © Vben-2020](./LICENSE) [MIT © Vben-2020](./LICENSE)

View File

@@ -21,7 +21,7 @@ export default defineEventHandler(async (event) => {
if (!findUser) { if (!findUser) {
clearRefreshTokenCookie(event); clearRefreshTokenCookie(event);
return forbiddenResponse(event, 'Username or password is incorrect.'); return forbiddenResponse(event);
} }
const accessToken = generateAccessToken(findUser); const accessToken = generateAccessToken(findUser);

View File

@@ -1,28 +0,0 @@
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;
});

View File

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

View File

@@ -1,15 +0,0 @@
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);
});

View File

@@ -1,15 +0,0 @@
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);
});

View File

@@ -1,15 +0,0 @@
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);
});

View File

@@ -1,61 +0,0 @@
import { faker } from '@faker-js/faker';
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);
});

View File

@@ -1,12 +0,0 @@
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);
});

View File

@@ -1,28 +0,0 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse } 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);
});

View File

@@ -1,28 +0,0 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse } 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);
});

View File

@@ -1,83 +0,0 @@
import { faker } from '@faker-js/faker';
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);
});

View File

@@ -1,73 +0,0 @@
import { faker } from '@faker-js/faker';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { 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);
const listData = structuredClone(mockData);
if (sortBy && Reflect.has(listData[0], sortBy as string)) {
listData.sort((a, b) => {
if (sortOrder === 'asc') {
if (sortBy === 'price') {
return (
Number.parseFloat(a[sortBy as string]) -
Number.parseFloat(b[sortBy as string])
);
} else {
return a[sortBy as string] > b[sortBy as string] ? 1 : -1;
}
} else {
if (sortBy === 'price') {
return (
Number.parseFloat(b[sortBy as string]) -
Number.parseFloat(a[sortBy as string])
);
} else {
return a[sortBy as string] < b[sortBy as string] ? 1 : -1;
}
}
});
}
return usePageResponseSuccess(page as string, pageSize as string, listData);
});

View File

@@ -1,13 +0,0 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } 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")
});

View File

@@ -6,5 +6,6 @@ export default eventHandler((event) => {
if (!userinfo) { if (!userinfo) {
return unAuthorizedResponse(event); return unAuthorizedResponse(event);
} }
return useResponseSuccess(userinfo); return useResponseSuccess(userinfo);
}); });

View File

@@ -1,19 +1,7 @@
import { forbiddenResponse, sleep } from '~/utils/response'; export default defineEventHandler((event) => {
export default defineEventHandler(async (event) => {
event.node.res.setHeader(
'Access-Control-Allow-Origin',
event.headers.get('Origin') ?? '*',
);
if (event.method === 'OPTIONS') { if (event.method === 'OPTIONS') {
event.node.res.statusCode = 204; event.node.res.statusCode = 204;
event.node.res.statusMessage = 'No Content.'; event.node.res.statusMessage = 'No Content.';
return 'OK'; 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, '演示环境,禁止修改');
} }
}); });

View File

@@ -1,6 +1,5 @@
import errorHandler from './error'; import errorHandler from './error';
process.env.COMPATIBILITY_DATE = new Date().toISOString();
export default defineNitroConfig({ export default defineNitroConfig({
devErrorHandler: errorHandler, devErrorHandler: errorHandler,
errorHandler: '~/error', errorHandler: '~/error',
@@ -9,8 +8,7 @@ export default defineNitroConfig({
cors: true, cors: true,
headers: { headers: {
'Access-Control-Allow-Credentials': 'true', 'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': '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-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Expose-Headers': '*', 'Access-Control-Expose-Headers': '*',

View File

@@ -10,12 +10,11 @@
"start": "nitro dev" "start": "nitro dev"
}, },
"dependencies": { "dependencies": {
"@faker-js/faker": "catalog:", "jsonwebtoken": "^9.0.2",
"jsonwebtoken": "catalog:", "nitropack": "^2.9.7"
"nitropack": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@types/jsonwebtoken": "catalog:", "@types/jsonwebtoken": "^9.0.6",
"h3": "catalog:" "h3": "^1.12.0"
} }
} }

View File

@@ -7,7 +7,6 @@ export default defineEventHandler(() => {
<li><a href="/api/menu">/api/menu/all</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/codes">/api/auth/codes</a></li>
<li><a href="/api/auth/login">/api/auth/login</a></li> <li><a href="/api/auth/login">/api/auth/login</a></li>
<li><a href="/api/upload">/api/upload</a></li>
</ul> </ul>
`; `;
}); });

View File

@@ -14,7 +14,7 @@ export function setRefreshTokenCookie(
) { ) {
setCookie(event, 'jwt', refreshToken, { setCookie(event, 'jwt', refreshToken, {
httpOnly: true, httpOnly: true,
maxAge: 24 * 60 * 60, // unit: seconds maxAge: 24 * 60 * 60 * 1000,
sameSite: 'none', sameSite: 'none',
secure: true, secure: true,
}); });

View File

@@ -4,7 +4,6 @@ export interface UserInfo {
realName: string; realName: string;
roles: string[]; roles: string[];
username: string; username: string;
homePath?: string;
} }
export const MOCK_USERS: UserInfo[] = [ export const MOCK_USERS: UserInfo[] = [
@@ -21,7 +20,6 @@ export const MOCK_USERS: UserInfo[] = [
realName: 'Admin', realName: 'Admin',
roles: ['admin'], roles: ['admin'],
username: 'admin', username: 'admin',
homePath: '/workspace',
}, },
{ {
id: 2, id: 2,
@@ -29,7 +27,6 @@ export const MOCK_USERS: UserInfo[] = [
realName: 'Jack', realName: 'Jack',
roles: ['user'], roles: ['user'],
username: 'jack', username: 'jack',
homePath: '/analytics',
}, },
]; ];
@@ -53,12 +50,13 @@ export const MOCK_CODES = [
const dashboardMenus = [ const dashboardMenus = [
{ {
component: 'BasicLayout',
meta: { meta: {
order: -1, order: -1,
title: 'page.dashboard.title', title: 'page.dashboard.title',
}, },
name: 'Dashboard', name: 'Dashboard',
path: '/dashboard', path: '/',
redirect: '/analytics', redirect: '/analytics',
children: [ children: [
{ {
@@ -88,7 +86,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/admin-visible', component: '/demos/access/admin-visible',
meta: { meta: {
icon: 'mdi:button-cursor', icon: 'mdi:button-cursor',
title: 'demos.access.adminVisible', title: 'page.demos.access.adminVisible',
}, },
name: 'AccessAdminVisibleDemo', name: 'AccessAdminVisibleDemo',
path: '/demos/access/admin-visible', path: '/demos/access/admin-visible',
@@ -97,7 +95,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/super-visible', component: '/demos/access/super-visible',
meta: { meta: {
icon: 'mdi:button-cursor', icon: 'mdi:button-cursor',
title: 'demos.access.superVisible', title: 'page.demos.access.superVisible',
}, },
name: 'AccessSuperVisibleDemo', name: 'AccessSuperVisibleDemo',
path: '/demos/access/super-visible', path: '/demos/access/super-visible',
@@ -106,7 +104,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/user-visible', component: '/demos/access/user-visible',
meta: { meta: {
icon: 'mdi:button-cursor', icon: 'mdi:button-cursor',
title: 'demos.access.userVisible', title: 'page.demos.access.userVisible',
}, },
name: 'AccessUserVisibleDemo', name: 'AccessUserVisibleDemo',
path: '/demos/access/user-visible', path: '/demos/access/user-visible',
@@ -115,11 +113,12 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
return [ return [
{ {
component: 'BasicLayout',
meta: { meta: {
icon: 'ic:baseline-view-in-ar', icon: 'ic:baseline-view-in-ar',
keepAlive: true, keepAlive: true,
order: 1000, order: 1000,
title: 'demos.title', title: 'page.demos.title',
}, },
name: 'Demos', name: 'Demos',
path: '/demos', path: '/demos',
@@ -130,7 +129,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
path: '/demosaccess', path: '/demosaccess',
meta: { meta: {
icon: 'mdi:cloud-key-outline', icon: 'mdi:cloud-key-outline',
title: 'demos.access.backendPermissions', title: 'page.demos.access.backendPermissions',
}, },
redirect: '/demos/access/page-control', redirect: '/demos/access/page-control',
children: [ children: [
@@ -140,7 +139,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/index', component: '/demos/access/index',
meta: { meta: {
icon: 'mdi:page-previous-outline', icon: 'mdi:page-previous-outline',
title: 'demos.access.pageAccess', title: 'page.demos.access.pageAccess',
}, },
}, },
{ {
@@ -149,7 +148,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
component: '/demos/access/button-control', component: '/demos/access/button-control',
meta: { meta: {
icon: 'mdi:button-cursor', icon: 'mdi:button-cursor',
title: 'demos.access.buttonControl', title: 'page.demos.access.buttonControl',
}, },
}, },
{ {
@@ -160,7 +159,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
authority: ['no-body'], authority: ['no-body'],
icon: 'mdi:button-cursor', icon: 'mdi:button-cursor',
menuVisibleWithForbidden: true, menuVisibleWithForbidden: true,
title: 'demos.access.menuVisible403', title: 'page.demos.access.menuVisible403',
}, },
}, },
roleWithMenus[role], roleWithMenus[role],
@@ -185,206 +184,3 @@ export const MOCK_MENUS = [
username: 'jack', 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;
}

View File

@@ -9,27 +9,6 @@ export function useResponseSuccess<T = any>(data: T) {
}; };
} }
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) { export function useResponseError(message: string, error: any = null) {
return { return {
code: -1, code: -1,
@@ -39,30 +18,12 @@ export function useResponseError(message: string, error: any = null) {
}; };
} }
export function forbiddenResponse( export function forbiddenResponse(event: H3Event<EventHandlerRequest>) {
event: H3Event<EventHandlerRequest>,
message = 'Forbidden Exception',
) {
setResponseStatus(event, 403); setResponseStatus(event, 403);
return useResponseError(message, message); return useResponseError('ForbiddenException', 'Forbidden Exception');
} }
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) { export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
setResponseStatus(event, 401); setResponseStatus(event, 401);
return useResponseError('Unauthorized Exception', 'Unauthorized Exception'); return useResponseError('UnauthorizedException', '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));
} }

View File

@@ -3,6 +3,3 @@ VITE_APP_TITLE=Vben Admin Antd
# 应用命名空间用于缓存、store等功能的前缀确保隔离 # 应用命名空间用于缓存、store等功能的前缀确保隔离
VITE_APP_NAMESPACE=vben-web-antd VITE_APP_NAMESPACE=vben-web-antd
# 对store进行加密的密钥在将store持久化到localStorage时会使用该密钥进行加密
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vben/web-antd", "name": "@vben/web-antd",
"version": "5.5.7", "version": "5.3.0-beta.1",
"homepage": "https://vben.pro", "homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {
@@ -40,11 +40,11 @@
"@vben/styles": "workspace:*", "@vben/styles": "workspace:*",
"@vben/types": "workspace:*", "@vben/types": "workspace:*",
"@vben/utils": "workspace:*", "@vben/utils": "workspace:*",
"@vueuse/core": "catalog:", "@vueuse/core": "^11.0.3",
"ant-design-vue": "catalog:", "ant-design-vue": "^4.2.3",
"dayjs": "catalog:", "dayjs": "^1.11.13",
"pinia": "catalog:", "pinia": "2.2.2",
"vue": "catalog:", "vue": "^3.5.4",
"vue-router": "catalog:" "vue-router": "^4.4.3"
} }
} }

View File

@@ -1,211 +0,0 @@
/**
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { notification } from 'ant-design-vue';
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
);
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
);
const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
);
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
const withDefaultPlaceholder = <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'
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'PrimaryButton'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'Select'
| 'Space'
| 'Switch'
| 'Textarea'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
async function initComponentAdapter() {
const components: Partial<Record<ComponentType, Component>> = {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiSelect',
},
'select',
{
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
},
),
ApiTreeSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiTreeSelect',
},
'select',
{
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
},
),
AutoComplete,
Checkbox,
CheckboxGroup,
DatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
iconSlot: 'addonAfter',
inputComponent: Input,
modelValueProp: 'value',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮
PrimaryButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Radio,
RadioGroup,
RangePicker,
Rate,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
};
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);
// 定义全局共享状态中的消息提示
globalShareState.defineMessage({
// 复制成功消息提示
copyPreferencesSuccess: (title, content) => {
notification.success({
description: content,
message: title,
placement: 'bottomRight',
});
},
});
}
export { initComponentAdapter };

View File

@@ -1,49 +1,114 @@
import type { import type {
BaseFormComponentType,
VbenFormSchema as FormSchema, VbenFormSchema as FormSchema,
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { ComponentType } from './component'; import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
async function initSetupVbenForm() { import {
setupVbenForm<ComponentType>({ AutoComplete,
config: { Button,
// ant design vue组件库默认都是 v-model:value Checkbox,
baseModelPropName: 'value', CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
// 一些组件是 v-model:checked 或者 v-model:fileList // 业务表单组件适配
modelPropNameMap: {
Checkbox: 'checked', export type FormComponentType =
Radio: 'checked', | 'AutoComplete'
Switch: 'checked', | 'Checkbox'
Upload: 'fileList', | 'CheckboxGroup'
}, | 'DatePicker'
| 'Divider'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'Select'
| 'Space'
| 'Switch'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
AutoComplete,
Checkbox,
CheckboxGroup,
DatePicker,
// 自定义默认的重置按钮
DefaultResetActionButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
}, },
defineRules: { // 自定义默认的提交按钮
// 输入项目必填国际化适配 DefaultSubmitActionButton: (props, { attrs, slots }) => {
required: (value, _params, ctx) => { return h(Button, { ...props, attrs, type: 'primary' }, slots);
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;
},
}, },
}); Divider,
} Input,
InputNumber,
InputPassword,
Mentions,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
TimePicker,
TreeSelect,
Upload,
},
config: {
baseModelPropName: 'value',
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
Switch: 'checked',
Upload: 'fileList',
},
},
defineRules: {
required: (value, _params, ctx) => {
if ((!value && value !== 0) || value.length === 0) {
return $t('formRules.required', [ctx.label]);
}
return true;
},
},
});
const useVbenForm = useForm<ComponentType>; const useVbenForm = useForm<FormComponentType>;
export { initSetupVbenForm, useVbenForm, z }; export { useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>; export type VbenFormSchema = FormSchema<FormComponentType>;
export type { VbenFormProps }; export type { VbenFormProps };

View File

@@ -0,0 +1 @@
export * from './form';

View File

@@ -1,69 +0,0 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
import { useVbenForm } from './form';
setupVbenVxeTable({
configVxeTable: (vxeUI) => {
vxeUI.setConfig({
grid: {
align: 'center',
border: false,
columnConfig: {
resizable: true,
},
minHeight: 180,
formConfig: {
// 全局禁用vxe-table的表单配置使用formOptions
enabled: false,
},
proxyConfig: {
autoLoad: true,
response: {
result: 'items',
total: 'total',
list: 'items',
},
showActiveMsg: true,
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
} as VxeTableGridOptions,
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
{ size: 'small', type: 'link' },
{ default: () => props?.text },
);
},
});
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add
},
useVbenForm,
});
export { useVbenVxeGrid };
export type * from '@vben/plugins/vxe-table';

View File

@@ -3,13 +3,17 @@ import { baseRequestClient, requestClient } from '#/api/request';
export namespace AuthApi { export namespace AuthApi {
/** 登录接口参数 */ /** 登录接口参数 */
export interface LoginParams { export interface LoginParams {
password?: string; password: string;
username?: string; username: string;
} }
/** 登录接口返回值 */ /** 登录接口返回值 */
export interface LoginResult { export interface LoginResult {
accessToken: string; accessToken: string;
desc: string;
realName: string;
userId: string;
username: string;
} }
export interface RefreshTokenResult { export interface RefreshTokenResult {

View File

@@ -1,13 +1,10 @@
/** /**
* 该文件可自行根据业务逻辑进行调整 * 该文件可自行根据业务逻辑进行调整
*/ */
import type { RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks'; import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
import { import {
authenticateResponseInterceptor, authenticateResponseInterceptor,
defaultResponseInterceptor,
errorMessageResponseInterceptor, errorMessageResponseInterceptor,
RequestClient, RequestClient,
} from '@vben/request'; } from '@vben/request';
@@ -21,9 +18,8 @@ import { refreshTokenApi } from './core';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
function createRequestClient(baseURL: string, options?: RequestClientOptions) { function createRequestClient(baseURL: string) {
const client = new RequestClient({ const client = new RequestClient({
...options,
baseURL, baseURL,
}); });
@@ -71,14 +67,18 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
}, },
}); });
// 处理返回的响应数据格式 // response数据解构
client.addResponseInterceptor( client.addResponseInterceptor({
defaultResponseInterceptor({ fulfilled: (response) => {
codeField: 'code', const { data: responseData, status } = response;
dataField: 'data',
successCode: 0, const { code, data, message: msg } = responseData;
}), if (status >= 200 && status < 400 && code === 0) {
); return data;
}
throw new Error(`Error ${status}: ${msg}`);
},
});
// token过期的处理 // token过期的处理
client.addResponseInterceptor( client.addResponseInterceptor(
@@ -93,21 +93,12 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里 // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
client.addResponseInterceptor( client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string, error) => { errorMessageResponseInterceptor((msg: string) => message.error(msg)),
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
// 当前mock接口返回的错误字段是 error 或者 message
const responseData = error?.response?.data ?? {};
const errorMessage = responseData?.error ?? responseData?.message ?? '';
// 如果没有错误信息,则会根据状态码进行提示
message.error(errorMessage || msg);
}),
); );
return client; return client;
} }
export const requestClient = createRequestClient(apiURL, { export const requestClient = createRequestClient(apiURL);
responseReturn: 'data',
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL }); export const baseRequestClient = new RequestClient({ baseURL: apiURL });

View File

@@ -1,45 +1,18 @@
import { createApp, watchEffect } from 'vue'; import { createApp } from 'vue';
import { registerAccessDirective } from '@vben/access'; import { registerAccessDirective } from '@vben/access';
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores'; import { initStores } from '@vben/stores';
import '@vben/styles'; import '@vben/styles';
import '@vben/styles/antd'; import '@vben/styles/antd';
import { useTitle } from '@vueuse/core'; import { setupI18n } from '#/locales';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import { initSetupVbenForm } from './adapter/form';
import App from './app.vue'; import App from './app.vue';
import { router } from './router'; import { router } from './router';
async function bootstrap(namespace: string) { async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
// 初始化表单组件
await initSetupVbenForm();
// // 设置弹窗的默认配置
// setDefaultModalProps({
// fullscreenButton: false,
// });
// // 设置抽屉的默认配置
// setDefaultDrawerProps({
// zIndex: 1020,
// });
const app = createApp(App); const app = createApp(App);
// 注册v-loading指令
registerLoadingDirective(app, {
loading: 'loading', // 在这里可以自定义指令名称也可以明确提供false表示不注册这个指令
spinning: 'spinning',
});
// 国际化 i18n 配置 // 国际化 i18n 配置
await setupI18n(app); await setupI18n(app);
@@ -49,27 +22,9 @@ async function bootstrap(namespace: string) {
// 安装权限指令 // 安装权限指令
registerAccessDirective(app); registerAccessDirective(app);
// 初始化 tippy
const { initTippy } = await import('@vben/common-ui/es/tippy');
initTippy(app);
// 配置路由及路由守卫 // 配置路由及路由守卫
app.use(router); app.use(router);
// 配置Motion插件
const { MotionPlugin } = await import('@vben/plugins/motion');
app.use(MotionPlugin);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {
const routeTitle = router.currentRoute.value.meta?.title;
const pageTitle =
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
useTitle(pageTitle);
}
});
app.mount('#app'); app.mount('#app');
} }

View File

@@ -1,11 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { NotificationItem } from '@vben/layouts'; import type { NotificationItem } from '@vben/layouts';
import { computed, ref, watch } from 'vue'; import { computed, ref } from 'vue';
import { AuthenticationLoginExpiredModal } from '@vben/common-ui'; import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants'; import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
import { useWatermark } from '@vben/hooks';
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons'; import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
import { import {
BasicLayout, BasicLayout,
@@ -55,7 +54,6 @@ const notifications = ref<NotificationItem[]>([
const userStore = useUserStore(); const userStore = useUserStore();
const authStore = useAuthStore(); const authStore = useAuthStore();
const accessStore = useAccessStore(); const accessStore = useAccessStore();
const { destroyWatermark, updateWatermark } = useWatermark();
const showDot = computed(() => const showDot = computed(() =>
notifications.value.some((item) => !item.isRead), notifications.value.some((item) => !item.isRead),
); );
@@ -68,7 +66,7 @@ const menus = computed(() => [
}); });
}, },
icon: BookOpenText, icon: BookOpenText,
text: $t('ui.widgets.document'), text: $t('widgets.document'),
}, },
{ {
handler: () => { handler: () => {
@@ -86,7 +84,7 @@ const menus = computed(() => [
}); });
}, },
icon: CircleHelp, icon: CircleHelp,
text: $t('ui.widgets.qa'), text: $t('widgets.qa'),
}, },
]); ]);
@@ -105,21 +103,6 @@ function handleNoticeClear() {
function handleMakeAll() { function handleMakeAll() {
notifications.value.forEach((item) => (item.isRead = true)); notifications.value.forEach((item) => (item.isRead = true));
} }
watch(
() => preferences.app.watermark,
async (enable) => {
if (enable) {
await updateWatermark({
content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
});
} else {
destroyWatermark();
}
},
{
immediate: true,
},
);
</script> </script>
<template> <template>

View File

@@ -1,16 +1,10 @@
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import type { Locale } from 'ant-design-vue/es/locale'; import type { Locale } from 'ant-design-vue/es/locale';
import type { App } from 'vue'; import type { App } from 'vue';
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import { ref } from 'vue'; import { ref } from 'vue';
import { import { $t, setupI18n as coreSetup, loadLocalesMap } from '@vben/locales';
$t,
setupI18n as coreSetup,
loadLocalesMapFromDir,
} from '@vben/locales';
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
import antdEnLocale from 'ant-design-vue/es/locale/en_US'; import antdEnLocale from 'ant-design-vue/es/locale/en_US';
@@ -19,12 +13,10 @@ import dayjs from 'dayjs';
const antdLocale = ref<Locale>(antdDefaultLocale); const antdLocale = ref<Locale>(antdDefaultLocale);
const modules = import.meta.glob('./langs/**/*.json'); const modules = import.meta.glob('./langs/*.json');
const localesMap = loadLocalesMap(modules);
const localesMap = loadLocalesMapFromDir(
/\.\/langs\/([^/]+)\/(.*)\.json$/,
modules,
);
/** /**
* 加载应用特有的语言包 * 加载应用特有的语言包
* 这里也可以改造为从服务端获取翻译数据 * 这里也可以改造为从服务端获取翻译数据
@@ -53,14 +45,14 @@ async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
async function loadDayjsLocale(lang: SupportedLanguagesType) { async function loadDayjsLocale(lang: SupportedLanguagesType) {
let locale; let locale;
switch (lang) { switch (lang) {
case 'en-US': {
locale = await import('dayjs/locale/en');
break;
}
case 'zh-CN': { case 'zh-CN': {
locale = await import('dayjs/locale/zh-cn'); locale = await import('dayjs/locale/zh-cn');
break; break;
} }
case 'en-US': {
locale = await import('dayjs/locale/en');
break;
}
// 默认使用英语 // 默认使用英语
default: { default: {
locale = await import('dayjs/locale/en'); locale = await import('dayjs/locale/en');
@@ -79,14 +71,14 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
*/ */
async function loadAntdLocale(lang: SupportedLanguagesType) { async function loadAntdLocale(lang: SupportedLanguagesType) {
switch (lang) { switch (lang) {
case 'en-US': {
antdLocale.value = antdEnLocale;
break;
}
case 'zh-CN': { case 'zh-CN': {
antdLocale.value = antdDefaultLocale; antdLocale.value = antdDefaultLocale;
break; break;
} }
case 'en-US': {
antdLocale.value = antdEnLocale;
break;
}
} }
} }

View File

@@ -0,0 +1,8 @@
{
"page": {
"demos": {
"title": "Demos",
"antd": "Ant Design Vue"
}
}
}

View File

@@ -1,12 +0,0 @@
{
"title": "Demos",
"antd": "Ant Design Vue",
"vben": {
"title": "Project",
"about": "About",
"document": "Document",
"antdv": "Ant Design Vue Version",
"naive-ui": "Naive UI Version",
"element-plus": "Element Plus Version"
}
}

View File

@@ -1,14 +0,0 @@
{
"auth": {
"login": "Login",
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password"
},
"dashboard": {
"title": "Dashboard",
"analytics": "Analytics",
"workspace": "Workspace"
}
}

View File

@@ -0,0 +1,8 @@
{
"page": {
"demos": {
"title": "演示",
"antd": "Ant Design Vue"
}
}
}

View File

@@ -1,12 +0,0 @@
{
"title": "演示",
"antd": "Ant Design Vue",
"vben": {
"title": "项目",
"about": "关于",
"document": "文档",
"antdv": "Ant Design Vue 版本",
"naive-ui": "Naive UI 版本",
"element-plus": "Element Plus 版本"
}
}

View File

@@ -1,14 +0,0 @@
{
"auth": {
"login": "登录",
"register": "注册",
"codeLogin": "验证码登录",
"qrcodeLogin": "二维码登录",
"forgetPassword": "忘记密码"
},
"dashboard": {
"title": "概览",
"analytics": "分析页",
"workspace": "工作台"
}
}

View File

@@ -3,7 +3,6 @@ import { defineOverridesPreferences } from '@vben/preferences';
/** /**
* @description 项目配置文件 * @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置 * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
* !!! 更改配置后请清空缓存,否则可能不生效
*/ */
export const overridesPreferences = defineOverridesPreferences({ export const overridesPreferences = defineOverridesPreferences({
// overrides // overrides

View File

@@ -1,11 +1,14 @@
import type { Router } from 'vue-router'; import type { Router } from 'vue-router';
import { LOGIN_PATH } from '@vben/constants'; import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores'; import { useAccessStore, useUserStore } from '@vben/stores';
import { startProgress, stopProgress } from '@vben/utils'; import { startProgress, stopProgress } from '@vben/utils';
import { accessRoutes, coreRouteNames } from '#/router/routes'; import { useTitle } from '@vueuse/core';
import { $t } from '#/locales';
import { coreRouteNames, dynamicRoutes } from '#/router/routes';
import { useAuthStore } from '#/store'; import { useAuthStore } from '#/store';
import { generateAccess } from './access'; import { generateAccess } from './access';
@@ -18,7 +21,7 @@ function setupCommonGuard(router: Router) {
// 记录已经加载的页面 // 记录已经加载的页面
const loadedPaths = new Set<string>(); const loadedPaths = new Set<string>();
router.beforeEach((to) => { router.beforeEach(async (to) => {
to.meta.loaded = loadedPaths.has(to.path); to.meta.loaded = loadedPaths.has(to.path);
// 页面加载进度条 // 页面加载进度条
@@ -31,12 +34,21 @@ function setupCommonGuard(router: Router) {
router.afterEach((to) => { router.afterEach((to) => {
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
loadedPaths.add(to.path); if (preferences.tabbar.enable) {
loadedPaths.add(to.path);
}
// 关闭页面加载进度条 // 关闭页面加载进度条
if (preferences.transition.progress) { if (preferences.transition.progress) {
stopProgress(); stopProgress();
} }
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
// useTitle(`${$t(title)} - ${preferences.app.name}`);
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
}); });
} }
@@ -54,9 +66,7 @@ function setupAccessGuard(router: Router) {
if (coreRouteNames.includes(to.name as string)) { if (coreRouteNames.includes(to.name as string)) {
if (to.path === LOGIN_PATH && accessStore.accessToken) { if (to.path === LOGIN_PATH && accessStore.accessToken) {
return decodeURIComponent( return decodeURIComponent(
(to.query?.redirect as string) || (to.query?.redirect as string) || DEFAULT_HOME_PATH,
userStore.userInfo?.homePath ||
preferences.app.defaultHomePath,
); );
} }
return true; return true;
@@ -74,10 +84,7 @@ function setupAccessGuard(router: Router) {
return { return {
path: LOGIN_PATH, path: LOGIN_PATH,
// 如不需要,直接删除 query // 如不需要,直接删除 query
query: query: { redirect: encodeURIComponent(to.fullPath) },
to.fullPath === preferences.app.defaultHomePath
? {}
: { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面 // 携带当前跳转的页面,登录后重新跳转该页面
replace: true, replace: true,
}; };
@@ -100,17 +107,14 @@ function setupAccessGuard(router: Router) {
roles: userRoles, roles: userRoles,
router, router,
// 则会在菜单中显示但是访问会被重定向到403 // 则会在菜单中显示但是访问会被重定向到403
routes: accessRoutes, routes: dynamicRoutes,
}); });
// 保存菜单信息和路由信息 // 保存菜单信息和路由信息
accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes); accessStore.setAccessRoutes(accessibleRoutes);
accessStore.setIsAccessChecked(true); accessStore.setIsAccessChecked(true);
const redirectPath = (from.query.redirect ?? const redirectPath = (from.query.redirect ?? to.fullPath) as string;
(to.path === preferences.app.defaultHomePath
? userInfo.homePath || preferences.app.defaultHomePath
: to.fullPath)) as string;
return { return {
...router.resolve(decodeURIComponent(redirectPath)), ...router.resolve(decodeURIComponent(redirectPath)),

View File

@@ -19,12 +19,7 @@ const router = createRouter({
: createWebHistory(import.meta.env.VITE_BASE), : createWebHistory(import.meta.env.VITE_BASE),
// 应该添加到路由的初始路由列表。 // 应该添加到路由的初始路由列表。
routes, routes,
scrollBehavior: (to, _from, savedPosition) => { scrollBehavior: () => ({ left: 0, top: 0 }),
if (savedPosition) {
return savedPosition;
}
return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };
},
// 是否应该禁止尾部斜杠。 // 是否应该禁止尾部斜杠。
// strict: true, // strict: true,
}); });

View File

@@ -1,12 +1,11 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { LOGIN_PATH } from '@vben/constants'; import { DEFAULT_HOME_PATH } from '@vben/constants';
import { preferences } from '@vben/preferences';
import { AuthPageLayout } from '#/layouts';
import { $t } from '#/locales'; import { $t } from '#/locales';
import Login from '#/views/_core/authentication/login.vue';
const BasicLayout = () => import('#/layouts/basic.vue');
const AuthPageLayout = () => import('#/layouts/auth.vue');
/** 全局404页面 */ /** 全局404页面 */
const fallbackNotFoundRoute: RouteRecordRaw = { const fallbackNotFoundRoute: RouteRecordRaw = {
component: () => import('#/views/_core/fallback/not-found.vue'), component: () => import('#/views/_core/fallback/not-found.vue'),
@@ -22,38 +21,28 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
/** 基本路由,这些路由是必须存在的 */ /** 基本路由,这些路由是必须存在的 */
const coreRoutes: RouteRecordRaw[] = [ const coreRoutes: RouteRecordRaw[] = [
/**
* 根路由
* 使用基础布局作为所有页面的父级容器子级就不必配置BasicLayout。
* 此路由必须存在,且不应修改
*/
{ {
component: BasicLayout,
meta: { meta: {
hideInBreadcrumb: true,
title: 'Root', title: 'Root',
}, },
name: 'Root', name: 'Root',
path: '/', path: '/',
redirect: preferences.app.defaultHomePath, redirect: DEFAULT_HOME_PATH,
children: [],
}, },
{ {
component: AuthPageLayout, component: AuthPageLayout,
meta: { meta: {
hideInTab: true,
title: 'Authentication', title: 'Authentication',
}, },
name: 'Authentication', name: 'Authentication',
path: '/auth', path: '/auth',
redirect: LOGIN_PATH,
children: [ children: [
{ {
name: 'Login', name: 'Login',
path: 'login', path: 'login',
component: () => import('#/views/_core/authentication/login.vue'), component: Login,
meta: { meta: {
title: $t('page.auth.login'), title: $t('page.core.login'),
}, },
}, },
{ {
@@ -61,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'code-login', path: 'code-login',
component: () => import('#/views/_core/authentication/code-login.vue'), component: () => import('#/views/_core/authentication/code-login.vue'),
meta: { meta: {
title: $t('page.auth.codeLogin'), title: $t('page.core.codeLogin'),
}, },
}, },
{ {
@@ -70,7 +59,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () => component: () =>
import('#/views/_core/authentication/qrcode-login.vue'), import('#/views/_core/authentication/qrcode-login.vue'),
meta: { meta: {
title: $t('page.auth.qrcodeLogin'), title: $t('page.core.qrcodeLogin'),
}, },
}, },
{ {
@@ -79,7 +68,7 @@ const coreRoutes: RouteRecordRaw[] = [
component: () => component: () =>
import('#/views/_core/authentication/forget-password.vue'), import('#/views/_core/authentication/forget-password.vue'),
meta: { meta: {
title: $t('page.auth.forgetPassword'), title: $t('page.core.forgetPassword'),
}, },
}, },
{ {
@@ -87,7 +76,7 @@ const coreRoutes: RouteRecordRaw[] = [
path: 'register', path: 'register',
component: () => import('#/views/_core/authentication/register.vue'), component: () => import('#/views/_core/authentication/register.vue'),
meta: { meta: {
title: $t('page.auth.register'), title: $t('page.core.register'),
}, },
}, },
], ],

View File

@@ -10,19 +10,15 @@ const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
// 有需要可以自行打开注释,并创建文件夹 // 有需要可以自行打开注释,并创建文件夹
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); // const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
/** 动态路由 */ /** 动态路由 */
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
/** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统(不会显示在菜单中) */ /** 外部路由列表访问这些页面可以不需要Layout可能用于内嵌在别的系统 */
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); // const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
const staticRoutes: RouteRecordRaw[] = [];
const externalRoutes: RouteRecordRaw[] = []; const externalRoutes: RouteRecordRaw[] = [];
/** 路由列表,由基本路由、外部路由和404兜底路由组成 /** 路由列表,由基本路由+静态路由组成 */
* 无需走权限验证(会一直显示在菜单中) */
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
...coreRoutes, ...coreRoutes,
...externalRoutes, ...externalRoutes,
@@ -32,6 +28,4 @@ const routes: RouteRecordRaw[] = [
/** 基本路由列表,这些路由不需要进入权限拦截 */ /** 基本路由列表,这些路由不需要进入权限拦截 */
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name); const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
/** 有权限校验的路由列表,包含动态路由和静态路由 */ export { coreRouteNames, dynamicRoutes, routes };
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
export { accessRoutes, coreRouteNames, routes };

View File

@@ -1,16 +1,18 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales'; import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
component: BasicLayout,
meta: { meta: {
icon: 'lucide:layout-dashboard', icon: 'lucide:layout-dashboard',
order: -1, order: -1,
title: $t('page.dashboard.title'), title: $t('page.dashboard.title'),
}, },
name: 'Dashboard', name: 'Dashboard',
path: '/dashboard', path: '/',
children: [ children: [
{ {
name: 'Analytics', name: 'Analytics',
@@ -27,7 +29,6 @@ const routes: RouteRecordRaw[] = [
path: '/workspace', path: '/workspace',
component: () => import('#/views/dashboard/workspace/index.vue'), component: () => import('#/views/dashboard/workspace/index.vue'),
meta: { meta: {
icon: 'carbon:workspace',
title: $t('page.dashboard.workspace'), title: $t('page.dashboard.workspace'),
}, },
}, },

View File

@@ -1,21 +1,23 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales'; import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
component: BasicLayout,
meta: { meta: {
icon: 'ic:baseline-view-in-ar', icon: 'ic:baseline-view-in-ar',
keepAlive: true, keepAlive: true,
order: 1000, order: 1000,
title: $t('demos.title'), title: $t('page.demos.title'),
}, },
name: 'Demos', name: 'Demos',
path: '/demos', path: '/demos',
children: [ children: [
{ {
meta: { meta: {
title: $t('demos.antd'), title: $t('page.demos.antd'),
}, },
name: 'AntDesignDemos', name: 'AntDesignDemos',
path: '/demos/ant-design', path: '/demos/ant-design',

View File

@@ -8,20 +8,30 @@ import {
VBEN_NAIVE_PREVIEW_URL, VBEN_NAIVE_PREVIEW_URL,
} from '@vben/constants'; } from '@vben/constants';
import { IFrameView } from '#/layouts'; import { BasicLayout, IFrameView } from '#/layouts';
import { $t } from '#/locales'; import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
component: BasicLayout,
meta: { meta: {
badgeType: 'dot', badgeType: 'dot',
icon: VBEN_LOGO_URL, icon: VBEN_LOGO_URL,
order: 9998, order: 9999,
title: $t('demos.vben.title'), title: $t('page.vben.title'),
}, },
name: 'VbenProject', name: 'VbenProject',
path: '/vben-admin', path: '/vben-admin',
children: [ children: [
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('page.vben.about'),
},
},
{ {
name: 'VbenDocument', name: 'VbenDocument',
path: '/vben-admin/document', path: '/vben-admin/document',
@@ -29,7 +39,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
icon: 'lucide:book-open-text', icon: 'lucide:book-open-text',
link: VBEN_DOC_URL, link: VBEN_DOC_URL,
title: $t('demos.vben.document'), title: $t('page.vben.document'),
}, },
}, },
{ {
@@ -48,9 +58,8 @@ const routes: RouteRecordRaw[] = [
component: IFrameView, component: IFrameView,
meta: { meta: {
badgeType: 'dot', badgeType: 'dot',
icon: 'logos:naiveui',
link: VBEN_NAIVE_PREVIEW_URL, link: VBEN_NAIVE_PREVIEW_URL,
title: $t('demos.vben.naive-ui'), title: $t('page.vben.naive-ui'),
}, },
}, },
{ {
@@ -59,23 +68,12 @@ const routes: RouteRecordRaw[] = [
component: IFrameView, component: IFrameView,
meta: { meta: {
badgeType: 'dot', badgeType: 'dot',
icon: 'logos:element',
link: VBEN_ELE_PREVIEW_URL, link: VBEN_ELE_PREVIEW_URL,
title: $t('demos.vben.element-plus'), title: $t('page.vben.element-plus'),
}, },
}, },
], ],
}, },
{
name: 'VbenAbout',
path: '/vben-admin/about',
component: () => import('#/views/_core/about/index.vue'),
meta: {
icon: 'lucide:copyright',
title: $t('demos.vben.about'),
order: 9999,
},
},
]; ];
export default routes; export default routes;

View File

@@ -1,10 +1,10 @@
import type { Recordable, UserInfo } from '@vben/types'; import type { LoginAndRegisterParams } from '@vben/common-ui';
import type { UserInfo } from '@vben/types';
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { LOGIN_PATH } from '@vben/constants'; import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { preferences } from '@vben/preferences';
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
import { notification } from 'ant-design-vue'; import { notification } from 'ant-design-vue';
@@ -26,7 +26,7 @@ export const useAuthStore = defineStore('auth', () => {
* @param params 登录表单数据 * @param params 登录表单数据
*/ */
async function authLogin( async function authLogin(
params: Recordable<any>, params: LoginAndRegisterParams,
onSuccess?: () => Promise<void> | void, onSuccess?: () => Promise<void> | void,
) { ) {
// 异步处理用户登录操作并获取 accessToken // 异步处理用户登录操作并获取 accessToken
@@ -55,9 +55,7 @@ export const useAuthStore = defineStore('auth', () => {
} else { } else {
onSuccess onSuccess
? await onSuccess?.() ? await onSuccess?.()
: await router.push( : await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
userInfo.homePath || preferences.app.defaultHomePath,
);
} }
if (userInfo?.realName) { if (userInfo?.realName) {
@@ -78,15 +76,11 @@ export const useAuthStore = defineStore('auth', () => {
} }
async function logout(redirect: boolean = true) { async function logout(redirect: boolean = true) {
try { await logoutApi();
await logoutApi();
} catch {
// 不做任何处理
}
resetAllStores(); resetAllStores();
accessStore.setLoginExpired(false); accessStore.setLoginExpired(false);
// 回登页带上当前路由地址 // 回登页带上当前路由地址
await router.replace({ await router.replace({
path: LOGIN_PATH, path: LOGIN_PATH,
query: redirect query: redirect

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui'; import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
@@ -10,7 +9,6 @@ import { $t } from '@vben/locales';
defineOptions({ name: 'CodeLogin' }); defineOptions({ name: 'CodeLogin' });
const loading = ref(false); const loading = ref(false);
const CODE_LENGTH = 6;
const formSchema = computed((): VbenFormSchema[] => { const formSchema = computed((): VbenFormSchema[] => {
return [ return [
@@ -31,7 +29,6 @@ const formSchema = computed((): VbenFormSchema[] => {
{ {
component: 'VbenPinInput', component: 'VbenPinInput',
componentProps: { componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => { createText: (countdown: number) => {
const text = const text =
countdown > 0 countdown > 0
@@ -43,9 +40,7 @@ const formSchema = computed((): VbenFormSchema[] => {
}, },
fieldName: 'code', fieldName: 'code',
label: $t('authentication.code'), label: $t('authentication.code'),
rules: z.string().length(CODE_LENGTH, { rules: z.string().min(1, { message: $t('authentication.codeTip') }),
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
}, },
]; ];
}); });
@@ -54,7 +49,7 @@ const formSchema = computed((): VbenFormSchema[] => {
* Asynchronously handle the login process * Asynchronously handle the login process
* @param values 登录表单数据 * @param values 登录表单数据
*/ */
async function handleLogin(values: Recordable<any>) { async function handleLogin(values: LoginCodeParams) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(values); console.log(values);
} }

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui'; import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
@@ -28,7 +27,7 @@ const formSchema = computed((): VbenFormSchema[] => {
]; ];
}); });
function handleSubmit(value: Recordable<any>) { function handleSubmit(value: string) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('reset email:', value); console.log('reset email:', value);
} }

View File

@@ -2,9 +2,9 @@
import type { VbenFormSchema } from '@vben/common-ui'; import type { VbenFormSchema } from '@vben/common-ui';
import type { BasicOption } from '@vben/types'; import type { BasicOption } from '@vben/types';
import { computed, markRaw } from 'vue'; import { computed } from 'vue';
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui'; import { AuthenticationLogin, z } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { useAuthStore } from '#/store'; import { useAuthStore } from '#/store';
@@ -15,15 +15,15 @@ const authStore = useAuthStore();
const MOCK_USER_OPTIONS: BasicOption[] = [ const MOCK_USER_OPTIONS: BasicOption[] = [
{ {
label: 'Super', label: '超级管理员',
value: 'vben', value: 'vben',
}, },
{ {
label: 'Admin', label: '管理员',
value: 'admin', value: 'admin',
}, },
{ {
label: 'User', label: '用户',
value: 'jack', value: 'jack',
}, },
]; ];
@@ -78,13 +78,6 @@ const formSchema = computed((): VbenFormSchema[] => {
label: $t('authentication.password'), label: $t('authentication.password'),
rules: z.string().min(1, { message: $t('authentication.passwordTip') }), rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
}, },
{
component: markRaw(SliderCaptcha),
fieldName: 'captcha',
rules: z.boolean().refine((value) => value, {
message: $t('authentication.verifyRequiredTip'),
}),
},
]; ];
}); });
</script> </script>

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui'; import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, h, ref } from 'vue'; import { computed, h, ref } from 'vue';
@@ -46,7 +45,7 @@ const formSchema = computed((): VbenFormSchema[] => {
rules(values) { rules(values) {
const { password } = values; const { password } = values;
return z return z
.string({ required_error: $t('authentication.passwordTip') }) .string()
.min(1, { message: $t('authentication.passwordTip') }) .min(1, { message: $t('authentication.passwordTip') })
.refine((value) => value === password, { .refine((value) => value === password, {
message: $t('authentication.confirmPasswordTip'), message: $t('authentication.confirmPasswordTip'),
@@ -56,6 +55,7 @@ const formSchema = computed((): VbenFormSchema[] => {
}, },
fieldName: 'confirmPassword', fieldName: 'confirmPassword',
label: $t('authentication.confirmPassword'), label: $t('authentication.confirmPassword'),
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
}, },
{ {
component: 'VbenCheckbox', component: 'VbenCheckbox',
@@ -67,10 +67,15 @@ const formSchema = computed((): VbenFormSchema[] => {
h( h(
'a', 'a',
{ {
class: 'vben-link ml-1 ', class:
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
href: '', href: '',
}, },
`${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`, [
$t('authentication.privacyPolicy'),
'&',
$t('authentication.terms'),
],
), ),
]), ]),
}), }),
@@ -81,7 +86,7 @@ const formSchema = computed((): VbenFormSchema[] => {
]; ];
}); });
function handleSubmit(value: Recordable<any>) { function handleSubmit(value: LoginAndRegisterParams) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('register submit:', value); console.log('register submit:', value);
} }

View File

@@ -1,9 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>(); const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef); const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,9 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>(); const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef); const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,9 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>(); const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef); const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,9 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>(); const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef); const { renderEcharts } = useEcharts(chartRef);

View File

@@ -1,9 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import {
EchartsUI,
type EchartsUIType,
useEcharts,
} from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>(); const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef); const { renderEcharts } = useEcharts(chartRef);

View File

@@ -15,10 +15,10 @@ import {
} from '@vben/icons'; } from '@vben/icons';
import AnalyticsTrends from './analytics-trends.vue'; import AnalyticsTrends from './analytics-trends.vue';
import AnalyticsVisits from './analytics-visits.vue';
import AnalyticsVisitsData from './analytics-visits-data.vue'; import AnalyticsVisitsData from './analytics-visits-data.vue';
import AnalyticsVisitsSales from './analytics-visits-sales.vue'; import AnalyticsVisitsSales from './analytics-visits-sales.vue';
import AnalyticsVisitsSource from './analytics-visits-source.vue'; import AnalyticsVisitsSource from './analytics-visits-source.vue';
import AnalyticsVisits from './analytics-visits.vue';
const overviewItems: AnalysisOverviewItem[] = [ const overviewItems: AnalysisOverviewItem[] = [
{ {

View File

@@ -7,7 +7,6 @@ import type {
} from '@vben/common-ui'; } from '@vben/common-ui';
import { ref } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { import {
AnalysisChartCard, AnalysisChartCard,
@@ -19,15 +18,11 @@ import {
} from '@vben/common-ui'; } from '@vben/common-ui';
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores'; import { useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue'; import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
const userStore = useUserStore(); const userStore = useUserStore();
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
// 例如url: /dashboard/workspace
const projectItems: WorkbenchProjectItem[] = [ const projectItems: WorkbenchProjectItem[] = [
{ {
color: '', color: '',
@@ -36,7 +31,6 @@ const projectItems: WorkbenchProjectItem[] = [
group: '开源组', group: '开源组',
icon: 'carbon:logo-github', icon: 'carbon:logo-github',
title: 'Github', title: 'Github',
url: 'https://github.com',
}, },
{ {
color: '#3fb27f', color: '#3fb27f',
@@ -45,7 +39,6 @@ const projectItems: WorkbenchProjectItem[] = [
group: '算法组', group: '算法组',
icon: 'ion:logo-vue', icon: 'ion:logo-vue',
title: 'Vue', title: 'Vue',
url: 'https://vuejs.org',
}, },
{ {
color: '#e18525', color: '#e18525',
@@ -54,7 +47,6 @@ const projectItems: WorkbenchProjectItem[] = [
group: '上班摸鱼', group: '上班摸鱼',
icon: 'ion:logo-html5', icon: 'ion:logo-html5',
title: 'Html5', title: 'Html5',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
}, },
{ {
color: '#bf0c2c', color: '#bf0c2c',
@@ -63,7 +55,6 @@ const projectItems: WorkbenchProjectItem[] = [
group: 'UI', group: 'UI',
icon: 'ion:logo-angular', icon: 'ion:logo-angular',
title: 'Angular', title: 'Angular',
url: 'https://angular.io',
}, },
{ {
color: '#00d8ff', color: '#00d8ff',
@@ -72,7 +63,6 @@ const projectItems: WorkbenchProjectItem[] = [
group: '技术牛', group: '技术牛',
icon: 'bx:bxl-react', icon: 'bx:bxl-react',
title: 'React', title: 'React',
url: 'https://reactjs.org',
}, },
{ {
color: '#EBD94E', color: '#EBD94E',
@@ -81,47 +71,39 @@ const projectItems: WorkbenchProjectItem[] = [
group: '架构组', group: '架构组',
icon: 'ion:logo-javascript', icon: 'ion:logo-javascript',
title: 'Js', title: 'Js',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
}, },
]; ];
// 同样,这里的 url 也可以使用以 http 开头的外部链接
const quickNavItems: WorkbenchQuickNavItem[] = [ const quickNavItems: WorkbenchQuickNavItem[] = [
{ {
color: '#1fdaca', color: '#1fdaca',
icon: 'ion:home-outline', icon: 'ion:home-outline',
title: '首页', title: '首页',
url: '/',
}, },
{ {
color: '#bf0c2c', color: '#bf0c2c',
icon: 'ion:grid-outline', icon: 'ion:grid-outline',
title: '仪表盘', title: '仪表盘',
url: '/dashboard',
}, },
{ {
color: '#e18525', color: '#e18525',
icon: 'ion:layers-outline', icon: 'ion:layers-outline',
title: '组件', title: '组件',
url: '/demos/features/icons',
}, },
{ {
color: '#3fb27f', color: '#3fb27f',
icon: 'ion:settings-outline', icon: 'ion:settings-outline',
title: '系统管理', title: '系统管理',
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
}, },
{ {
color: '#4daf1bc9', color: '#4daf1bc9',
icon: 'ion:key-outline', icon: 'ion:key-outline',
title: '权限管理', title: '权限管理',
url: '/demos/access/page-control',
}, },
{ {
color: '#00d8ff', color: '#00d8ff',
icon: 'ion:bar-chart-outline', icon: 'ion:bar-chart-outline',
title: '图表', title: '图表',
url: '/analytics',
}, },
]; ];
@@ -213,24 +195,6 @@ const trendItems: WorkbenchTrendItem[] = [
title: 'Vben', title: 'Vben',
}, },
]; ];
const router = useRouter();
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
// This is a sample method, adjust according to the actual project requirements
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
if (nav.url?.startsWith('http')) {
openWindow(nav.url);
return;
}
if (nav.url?.startsWith('/')) {
router.push(nav.url).catch((error) => {
console.error('Navigation failed:', error);
});
} else {
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
}
}
</script> </script>
<template> <template>
@@ -246,7 +210,7 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
<div class="mt-5 flex flex-col lg:flex-row"> <div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5"> <div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" /> <WorkbenchProject :items="projectItems" title="项目" />
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" /> <WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
</div> </div>
<div class="w-full lg:w-2/5"> <div class="w-full lg:w-2/5">
@@ -254,7 +218,6 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
:items="quickNavItems" :items="quickNavItems"
class="mt-5 lg:mt-0" class="mt-5 lg:mt-0"
title="快捷导航" title="快捷导航"
@click="navTo"
/> />
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" /> <WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
<AnalysisChartCard class="mt-5" title="访问来源"> <AnalysisChartCard class="mt-5" title="访问来源">

View File

@@ -3,6 +3,3 @@ VITE_APP_TITLE=Vben Admin Ele
# 应用命名空间用于缓存、store等功能的前缀确保隔离 # 应用命名空间用于缓存、store等功能的前缀确保隔离
VITE_APP_NAMESPACE=vben-web-ele VITE_APP_NAMESPACE=vben-web-ele
# 对store进行加密的密钥在将store持久化到localStorage时会使用该密钥进行加密
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vben/web-ele", "name": "@vben/web-ele",
"version": "5.5.7", "version": "5.3.0-beta.1",
"homepage": "https://vben.pro", "homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": { "repository": {
@@ -40,14 +40,14 @@
"@vben/styles": "workspace:*", "@vben/styles": "workspace:*",
"@vben/types": "workspace:*", "@vben/types": "workspace:*",
"@vben/utils": "workspace:*", "@vben/utils": "workspace:*",
"@vueuse/core": "catalog:", "@vueuse/core": "^11.0.3",
"dayjs": "catalog:", "dayjs": "^1.11.13",
"element-plus": "catalog:", "element-plus": "^2.8.2",
"pinia": "catalog:", "pinia": "2.2.2",
"vue": "catalog:", "vue": "^3.5.4",
"vue-router": "catalog:" "vue-router": "^4.4.3"
}, },
"devDependencies": { "devDependencies": {
"unplugin-element-plus": "catalog:" "unplugin-element-plus": "^0.8.0"
} }
} }

View File

@@ -1,331 +0,0 @@
/**
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { ElNotification } from 'element-plus';
const ElButton = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/button/index'),
import('element-plus/es/components/button/style/css'),
]).then(([res]) => res.ElButton),
);
const ElCheckbox = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/checkbox/index'),
import('element-plus/es/components/checkbox/style/css'),
]).then(([res]) => res.ElCheckbox),
);
const ElCheckboxButton = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/checkbox/index'),
import('element-plus/es/components/checkbox-button/style/css'),
]).then(([res]) => res.ElCheckboxButton),
);
const ElCheckboxGroup = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/checkbox/index'),
import('element-plus/es/components/checkbox-group/style/css'),
]).then(([res]) => res.ElCheckboxGroup),
);
const ElDatePicker = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/date-picker/index'),
import('element-plus/es/components/date-picker/style/css'),
]).then(([res]) => res.ElDatePicker),
);
const ElDivider = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/divider/index'),
import('element-plus/es/components/divider/style/css'),
]).then(([res]) => res.ElDivider),
);
const ElInput = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/input/index'),
import('element-plus/es/components/input/style/css'),
]).then(([res]) => res.ElInput),
);
const ElInputNumber = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/input-number/index'),
import('element-plus/es/components/input-number/style/css'),
]).then(([res]) => res.ElInputNumber),
);
const ElRadio = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/radio/index'),
import('element-plus/es/components/radio/style/css'),
]).then(([res]) => res.ElRadio),
);
const ElRadioButton = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/radio/index'),
import('element-plus/es/components/radio-button/style/css'),
]).then(([res]) => res.ElRadioButton),
);
const ElRadioGroup = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/radio/index'),
import('element-plus/es/components/radio-group/style/css'),
]).then(([res]) => res.ElRadioGroup),
);
const ElSelectV2 = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/select-v2/index'),
import('element-plus/es/components/select-v2/style/css'),
]).then(([res]) => res.ElSelectV2),
);
const ElSpace = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/space/index'),
import('element-plus/es/components/space/style/css'),
]).then(([res]) => res.ElSpace),
);
const ElSwitch = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/switch/index'),
import('element-plus/es/components/switch/style/css'),
]).then(([res]) => res.ElSwitch),
);
const ElTimePicker = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/time-picker/index'),
import('element-plus/es/components/time-picker/style/css'),
]).then(([res]) => res.ElTimePicker),
);
const ElTreeSelect = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/tree-select/index'),
import('element-plus/es/components/tree-select/style/css'),
]).then(([res]) => res.ElTreeSelect),
);
const ElUpload = defineAsyncComponent(() =>
Promise.all([
import('element-plus/es/components/upload/index'),
import('element-plus/es/components/upload/style/css'),
]).then(([res]) => res.ElUpload),
);
const withDefaultPlaceholder = <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 };

View File

@@ -1,41 +1,89 @@
import type { import type {
BaseFormComponentType,
VbenFormSchema as FormSchema, VbenFormSchema as FormSchema,
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { ComponentType } from './component'; import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
async function initSetupVbenForm() { import {
setupVbenForm<ComponentType>({ ElButton,
config: { ElCheckbox,
modelPropNameMap: { ElCheckboxGroup,
Upload: 'fileList', ElDivider,
CheckboxGroup: 'model-value', ElInput,
}, ElInputNumber,
ElRadioGroup,
ElSelect,
ElSpace,
ElSwitch,
ElTimePicker,
ElTreeSelect,
ElUpload,
} from 'element-plus';
// 业务表单组件适配
export type FormComponentType =
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'Input'
| 'InputNumber'
| 'RadioGroup'
| 'Select'
| 'Space'
| 'Switch'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
Checkbox: ElCheckbox,
CheckboxGroup: ElCheckboxGroup,
// 自定义默认的重置按钮
DefaultResetActionButton: (props, { attrs, slots }) => {
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
}, },
defineRules: { // 自定义默认的提交按钮
required: (value, _params, ctx) => { DefaultSubmitActionButton: (props, { attrs, slots }) => {
if (value === undefined || value === null || value.length === 0) { return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
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;
},
}, },
}); Divider: ElDivider,
} Input: ElInput,
InputNumber: ElInputNumber,
RadioGroup: ElRadioGroup,
Select: ElSelect,
Space: ElSpace,
Switch: ElSwitch,
TimePicker: ElTimePicker,
TreeSelect: ElTreeSelect,
Upload: ElUpload,
},
config: {
modelPropNameMap: {
Upload: 'fileList',
},
},
defineRules: {
required: (value, _params, ctx) => {
if ((!value && value !== 0) || value.length === 0) {
return $t('formRules.required', [ctx.label]);
}
return true;
},
},
});
const useVbenForm = useForm<ComponentType>; const useVbenForm = useForm<FormComponentType>;
export { initSetupVbenForm, useVbenForm, z }; export { useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>; export type VbenFormSchema = FormSchema<FormComponentType>;
export type { VbenFormProps }; export type { VbenFormProps };

View File

@@ -0,0 +1 @@
export * from './form';

Some files were not shown because too many files have changed in this diff Show More