mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 08:36:19 +08:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
dc2b8f00e0 |
@@ -3,5 +3,3 @@ node_modules
|
||||
.gitignore
|
||||
*.md
|
||||
dist
|
||||
.turbo
|
||||
dist.zip
|
||||
|
4
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: 🐞 Bug Report
|
||||
description: Report an issue with Vben Admin to help us make it better.
|
||||
title: 'Bug: '
|
||||
labels: ['bug: pending triage']
|
||||
title: "Bug: "
|
||||
labels: ["bug: pending triage"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
|
2
.github/ISSUE_TEMPLATE/docs.yml
vendored
2
.github/ISSUE_TEMPLATE/docs.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: 📚 Documentation
|
||||
description: Report an issue with Vben Admin Website to help us make it better.
|
||||
title: 'Docs: '
|
||||
title: "Docs: "
|
||||
labels: [documentation]
|
||||
body:
|
||||
- type: markdown
|
||||
|
4
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: ✨ New Feature Proposal
|
||||
description: Propose a new feature to be added to Vben Admin
|
||||
title: 'FEATURE: '
|
||||
labels: ['enhancement: pending triage']
|
||||
title: "FEATURE: "
|
||||
labels: ["enhancement: pending triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
8
.github/actions/setup-node/action.yml
vendored
8
.github/actions/setup-node/action.yml
vendored
@@ -1,9 +1,9 @@
|
||||
name: 'Setup Node'
|
||||
name: "Setup Node"
|
||||
|
||||
description: 'Setup node and pnpm'
|
||||
description: "Setup node and pnpm"
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -12,7 +12,7 @@ runs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: 'pnpm'
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
|
45
.github/contributing.md
vendored
45
.github/contributing.md
vendored
@@ -1,42 +1,5 @@
|
||||
# Vben Admin Contributing Guide
|
||||
# Contributing Guide
|
||||
|
||||
Hi! We're really excited that you are interested in contributing to Vben Admin. Before submitting your contribution, please make sure to take a moment and read through the following guidelines:
|
||||
|
||||
- [Pull Request Guidelines](#pull-request-guidelines)
|
||||
|
||||
## Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
|
||||
|
||||
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
- Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch.
|
||||
|
||||
- If adding a new feature:
|
||||
|
||||
- Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
|
||||
|
||||
- If fixing bug:
|
||||
|
||||
- Provide a detailed description of the bug in the PR. Live demo preferred.
|
||||
|
||||
- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.
|
||||
|
||||
## Development Setup
|
||||
|
||||
You will need [pnpm](https://pnpm.io/)
|
||||
|
||||
After cloning the repo, run:
|
||||
|
||||
```bash
|
||||
# install the dependencies of the project
|
||||
$ pnpm install
|
||||
# start the project
|
||||
$ pnpm run dev
|
||||
```
|
||||
1. Make sure you put things in the right category!
|
||||
2. Always add your items to the end of a list. To be fair, the order is first-come-first-serve.
|
||||
3. If you think something belongs in the wrong category, or think there needs to be a new category, feel free to edit things too.
|
||||
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -1,7 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: '/'
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
groups:
|
||||
@@ -9,7 +9,7 @@ updates:
|
||||
update-types: [minor, patch]
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: '/'
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
groups:
|
||||
|
64
.github/release-drafter.yml
vendored
64
.github/release-drafter.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name-template: 'v$RESOLVED_VERSION'
|
||||
tag-template: 'v$RESOLVED_VERSION'
|
||||
name-template: "v$RESOLVED_VERSION"
|
||||
tag-template: "v$RESOLVED_VERSION"
|
||||
version-template: $MAJOR.$MINOR.$PATCH
|
||||
change-template: '* $TITLE (#$NUMBER) @$AUTHOR'
|
||||
change-template: "* $TITLE (#$NUMBER) @$AUTHOR"
|
||||
template: |
|
||||
# What's Changed
|
||||
|
||||
@@ -10,52 +10,46 @@ template: |
|
||||
**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
|
||||
|
||||
categories:
|
||||
- title: '🚀 Features'
|
||||
- title: "🚀 Features"
|
||||
labels:
|
||||
- 'feature'
|
||||
- title: '🐞 Bug Fixes'
|
||||
- "feature"
|
||||
- "enhancement"
|
||||
- title: "🐞 Bug Fixes"
|
||||
labels:
|
||||
- 'bug'
|
||||
- title: '📈 Performance & Enhancement'
|
||||
- "bug"
|
||||
- title: 📝 Documentation updates
|
||||
labels:
|
||||
- 'perf'
|
||||
- 'enhancement'
|
||||
- title: 📝 Documentation
|
||||
labels:
|
||||
- 'documentation'
|
||||
- "documentation"
|
||||
- title: 👻 Maintenance
|
||||
labels:
|
||||
- 'chore'
|
||||
- 'dependencies'
|
||||
# collapse-after: 12
|
||||
- "chore"
|
||||
- "dependencies"
|
||||
collapse-after: 5
|
||||
- title: 🚦 Tests
|
||||
labels:
|
||||
- 'tests'
|
||||
- title: 'Breaking'
|
||||
label: 'breaking'
|
||||
- "tests"
|
||||
- title: "Breaking"
|
||||
label: "breaking"
|
||||
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- 'major'
|
||||
- 'breaking'
|
||||
- "breaking"
|
||||
minor:
|
||||
labels:
|
||||
- 'minor'
|
||||
- "feature"
|
||||
patch:
|
||||
labels:
|
||||
- 'feature'
|
||||
- 'patch'
|
||||
- 'bug'
|
||||
- 'maintenance'
|
||||
- 'docs'
|
||||
- 'dependencies'
|
||||
- 'security'
|
||||
- "bug"
|
||||
- "maintenance"
|
||||
- "docs"
|
||||
- "dependencies"
|
||||
- "security"
|
||||
|
||||
exclude-labels:
|
||||
- 'skip-changelog'
|
||||
- 'no-changelog'
|
||||
- 'changelog'
|
||||
- 'bump versions'
|
||||
- 'reverted'
|
||||
- 'invalid'
|
||||
- "skip-changelog"
|
||||
- "no-changelog"
|
||||
- "changelog"
|
||||
- "bump versions"
|
||||
- "reverted"
|
||||
- "invalid"
|
||||
|
11
.github/workflows/build.yml
vendored
11
.github/workflows/build.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- main
|
||||
|
||||
env:
|
||||
HUSKY: '0'
|
||||
HUSKY: "0"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
@@ -19,15 +19,8 @@ permissions:
|
||||
|
||||
jobs:
|
||||
post-update:
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
# if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
# - macos-latest
|
||||
- windows-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
6
.github/workflows/changeset-version.yml
vendored
6
.github/workflows/changeset-version.yml
vendored
@@ -18,7 +18,7 @@ env:
|
||||
|
||||
jobs:
|
||||
version:
|
||||
if: (github.event.pull_request.merged || github.event_name == 'workflow_dispatch') && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||
if: (github.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'
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
version: pnpm run version
|
||||
commit: 'chore: bump versions'
|
||||
title: 'chore: bump versions'
|
||||
commit: "chore: bump versions"
|
||||
title: "chore: bump versions"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@@ -5,7 +5,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'releases/*'
|
||||
- "releases/*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -17,13 +17,13 @@ env:
|
||||
jobs:
|
||||
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 }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
# - macos-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
@@ -56,13 +56,13 @@ jobs:
|
||||
|
||||
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 }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
# - macos-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
|
||||
steps:
|
||||
@@ -79,14 +79,13 @@ jobs:
|
||||
|
||||
check:
|
||||
name: Check
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
# - macos-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -109,8 +108,8 @@ jobs:
|
||||
|
||||
ci-ok:
|
||||
name: CI OK
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && always()
|
||||
needs: [test, check, lint]
|
||||
env:
|
||||
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
||||
|
11
.github/workflows/codeql.yml
vendored
11
.github/workflows/codeql.yml
vendored
@@ -9,20 +9,19 @@
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: 'CodeQL'
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ['main']
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
- cron: '35 0 * * 0'
|
||||
- cron: "35 0 * * 0"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
@@ -91,4 +90,4 @@ jobs:
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
145
.github/workflows/deploy.yml
vendored
145
.github/workflows/deploy.yml
vendored
@@ -6,64 +6,9 @@ on:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy-playground-ftp:
|
||||
name: Deploy Push Playground Ftp
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sed Config Base
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./playground/.env.production
|
||||
sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./playground/.env.production
|
||||
cat ./playground/.env.production
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build
|
||||
run: pnpm build:play
|
||||
|
||||
- name: Sync Playground files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
username: ${{ secrets.WEB_PLAYGROUND_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEB_PLAYGROUND_FTP_PWSSWORD }}
|
||||
local-dir: ./playground/dist/
|
||||
|
||||
deploy-docs-ftp:
|
||||
name: Deploy Push Docs Ftp
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build
|
||||
run: pnpm build:docs
|
||||
|
||||
- name: Sync Docs files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
username: ${{ secrets.WEBSITE_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEBSITE_FTP_PASSWORD }}
|
||||
local-dir: ./docs/.vitepress/dist/
|
||||
|
||||
deploy-antd-ftp:
|
||||
name: Deploy Push Antd Ftp
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||
deploy-push-ftp:
|
||||
name: Deploy Push Ftp
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -77,65 +22,9 @@ jobs:
|
||||
sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-antd/.env.production
|
||||
sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-antd/.env.production
|
||||
cat ./apps/web-antd/.env.production
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build:antd
|
||||
|
||||
- name: Sync files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
username: ${{ secrets.WEB_ANTD_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEB_ANTD_FTP_PASSWORD }}
|
||||
local-dir: ./apps/web-antd/dist/
|
||||
|
||||
deploy-ele-ftp:
|
||||
name: Deploy Push Element Ftp
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sed Config Base
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-ele/.env.production
|
||||
sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-ele/.env.production
|
||||
cat ./apps/web-ele/.env.production
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build:ele
|
||||
|
||||
- name: Sync files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
username: ${{ secrets.WEB_ELE_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEB_ELE_FTP_PASSWORD }}
|
||||
local-dir: ./apps/web-ele/dist/
|
||||
|
||||
deploy-naive-ftp:
|
||||
name: Deploy Push Naive Ftp
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sed Config Base
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-naive/.env.production
|
||||
sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-naive/.env.production
|
||||
cat ./apps/web-naive/.env.production
|
||||
@@ -144,12 +33,36 @@ jobs:
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build:naive
|
||||
run: pnpm run build
|
||||
|
||||
- name: Sync files
|
||||
- name: Sync Web Antd files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
username: ${{ secrets.WEB_ANTD_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEB_ANTD_FTP_PASSWORD }}
|
||||
local-dir: ./apps/web-antd/dist/
|
||||
|
||||
- name: Sync Web Naive files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}
|
||||
local-dir: ./apps/web-naive/dist/
|
||||
|
||||
- name: Sync Web Ele files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
username: ${{ secrets.WEB_ELE_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEB_ELE_FTP_PASSWORD }}
|
||||
local-dir: ./apps/web-ele/dist/
|
||||
|
||||
- name: Sync Docs files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
username: ${{ secrets.WEBSITE_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEBSITE_FTP_PASSWORD }}
|
||||
local-dir: ./docs/.vitepress/dist/
|
||||
|
8
.github/workflows/draft.yml
vendored
8
.github/workflows/draft.yml
vendored
@@ -7,17 +7,9 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
permissions:
|
||||
# write permission is required to create a github release
|
||||
contents: write
|
||||
# write permission is required for autolabeler
|
||||
# otherwise, read permission is required at least
|
||||
pull-requests: write
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
|
22
.github/workflows/issue-close-require.yml
vendored
22
.github/workflows/issue-close-require.yml
vendored
@@ -3,29 +3,23 @@ name: Issue Close Require
|
||||
|
||||
# 触发条件:每天零点
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# 关闭未活动的 Issues
|
||||
# 步骤1:关闭未活动的 Issues
|
||||
- name: Close Inactive Issues
|
||||
uses: actions/stale@v9
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
|
||||
stale-issue-label: needs-reproduction # Label that flags an issue as stale.
|
||||
only-labels: needs-reproduction # Only process these issues
|
||||
days-before-issue-close: 3
|
||||
ignore-updates: true
|
||||
remove-stale-when-updated: false
|
||||
close-issue-message: This issue was closed because it was open for 3 days without a valid reproduction.
|
||||
close-issue-label: closed-by-action
|
||||
actions: "close-issues" # 执行动作:关闭 Issues
|
||||
token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token,用于认证
|
||||
labels: "need reproduction" # 目标标签
|
||||
inactive-day: 3 # 未活动天数阈值
|
||||
|
13
.github/workflows/issue-labeled.yml
vendored
13
.github/workflows/issue-labeled.yml
vendored
@@ -13,34 +13,33 @@ permissions:
|
||||
|
||||
jobs:
|
||||
reply-labeled:
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: remove enhancement pending
|
||||
if: github.event.label.name == 'enhancement'
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'remove-labels'
|
||||
actions: "remove-labels"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
labels: 'enhancement: pending triage'
|
||||
labels: "enhancement: pending triage"
|
||||
|
||||
- name: remove bug pending
|
||||
if: github.event.label.name == 'bug'
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'remove-labels'
|
||||
actions: "remove-labels"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
labels: 'bug: pending triage'
|
||||
labels: "bug: pending triage"
|
||||
|
||||
- name: needs reproduction
|
||||
if: github.event.label.name == 'needs reproduction'
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'create-comment, remove-labels'
|
||||
actions: "create-comment, remove-labels"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `needs reproduction` will be closed if no activities in 3 days.
|
||||
labels: 'bug: pending triage'
|
||||
labels: "bug: pending triage"
|
||||
|
13
.github/workflows/lock.yml
vendored
13
.github/workflows/lock.yml
vendored
@@ -2,7 +2,7 @@ name: Lock Threads
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -11,14 +11,13 @@ permissions:
|
||||
|
||||
jobs:
|
||||
action:
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-inactive-days: '14'
|
||||
issue-lock-reason: ''
|
||||
pr-inactive-days: '30'
|
||||
pr-lock-reason: ''
|
||||
process-only: 'issues, prs'
|
||||
issue-inactive-days: "30"
|
||||
issue-lock-reason: ""
|
||||
pr-inactive-days: "30"
|
||||
pr-lock-reason: ""
|
||||
process-only: "issues, prs"
|
||||
|
5
.github/workflows/release-tag.yml
vendored
5
.github/workflows/release-tag.yml
vendored
@@ -3,10 +3,10 @@ name: Create Release Tag
|
||||
on:
|
||||
push:
|
||||
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:
|
||||
HUSKY: '0'
|
||||
HUSKY: "0"
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
@@ -15,7 +15,6 @@ permissions:
|
||||
jobs:
|
||||
build:
|
||||
name: Create Release
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
|
3
.github/workflows/semantic-pull-request.yml
vendored
3
.github/workflows/semantic-pull-request.yml
vendored
@@ -9,9 +9,8 @@ on:
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Semantic Pull Request
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
name: Semantic Pull Request
|
||||
steps:
|
||||
- name: Validate PR title
|
||||
uses: amannn/action-semantic-pull-request@v5
|
||||
|
11
.github/workflows/stale.yml
vendored
11
.github/workflows/stale.yml
vendored
@@ -1,19 +1,18 @@
|
||||
name: 'Close stale issues'
|
||||
name: "Close stale issues"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * *'
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
|
||||
stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
|
||||
exempt-issue-labels: 'bug,enhancement'
|
||||
stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days"
|
||||
stale-pr-message: "This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days"
|
||||
exempt-issue-labels: "bug,enhancement"
|
||||
days-before-stale: 60
|
||||
days-before-close: 7
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -48,4 +48,3 @@ vite.config.ts.*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.history
|
||||
|
@@ -3,4 +3,4 @@ ports:
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- init: corepack enable && pnpm install
|
||||
command: pnpm run dev:play
|
||||
command: pnpm run dev
|
||||
|
28
.ls-lint.yml
Normal file
28
.ls-lint.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
ls:
|
||||
.js: kebab-case | pointcase
|
||||
.vue: kebab-case | pointcase
|
||||
.ts: kebab-case | pointcase
|
||||
.tsx: kebab-case | pointcase
|
||||
.jsx: kebab-case | pointcase
|
||||
.css: kebab-case | pointcase
|
||||
.d.ts: kebab-case | pointcase
|
||||
# shadcn 自动生成文件为 PascalCase 格式
|
||||
packages/@core/ui-kit/shadcn-ui/src/components/ui:
|
||||
.vue: PascalCase
|
||||
|
||||
ignore:
|
||||
- "**/*.png"
|
||||
- "**/*.jpg"
|
||||
- "**/*.jpeg"
|
||||
- "**/*.jpeg"
|
||||
- "**/*.gif"
|
||||
- "**/_util.ts"
|
||||
- "**/deps/**"
|
||||
- "**/dist/**"
|
||||
- "**/node_modules/**"
|
||||
- "**/.turbo/**"
|
||||
- .git
|
||||
- .vscode
|
||||
- .idea
|
||||
- node_modules
|
||||
- .cache
|
2
.npmrc
2
.npmrc
@@ -1,4 +1,4 @@
|
||||
registry = "https://registry.npmmirror.com"
|
||||
# registry = "https://registry.npmmirror.com"
|
||||
public-hoist-pattern[]=husky
|
||||
public-hoist-pattern[]=eslint
|
||||
public-hoist-pattern[]=prettier
|
||||
|
8
.vscode/extensions.json
vendored
8
.vscode/extensions.json
vendored
@@ -10,6 +10,10 @@
|
||||
"esbenp.prettier-vscode",
|
||||
// 支持 dotenv 文件语法
|
||||
"mikestead.dotenv",
|
||||
// 获取每个 CSS 属性的初始值。
|
||||
"dzhavat.css-initial-value",
|
||||
// 使 VSCode 中的 TypeScript 错误更漂亮、更易于理解
|
||||
"yoavbls.pretty-ts-errors",
|
||||
// 源代码的拼写检查器
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
// Tailwind CSS 的官方 VS Code 插件
|
||||
@@ -19,9 +23,7 @@
|
||||
// i18n 插件
|
||||
"Lokalise.i18n-ally",
|
||||
// CSS 变量提示
|
||||
"vunguyentuan.vscode-css-variables",
|
||||
// 在 package.json 中显示 PNPM catalog 的版本
|
||||
"antfu.pnpm-catalog-lens"
|
||||
"vunguyentuan.vscode-css-variables"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
// 和 volar 冲突
|
||||
|
31
.vscode/launch.json
vendored
31
.vscode/launch.json
vendored
@@ -4,39 +4,12 @@
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"name": "vben admin playground dev",
|
||||
"name": "vben admin antd dev",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5555",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"name": "vben admin antd dev",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5666",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"name": "vben admin ele dev",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5777",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"name": "vben admin naive dev",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5888",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}"
|
||||
"webRoot": "${workspaceFolder}/apps/web-antd/src"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
50
.vscode/settings.json
vendored
50
.vscode/settings.json
vendored
@@ -14,6 +14,7 @@
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.cursorBlinking": "expand",
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.largeFileOptimizations": false,
|
||||
"editor.accessibilitySupport": "off",
|
||||
"editor.cursorSmoothCaretAnimation": "on",
|
||||
@@ -36,34 +37,7 @@
|
||||
"source.fixAll.stylelint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
|
||||
// extensions
|
||||
"extensions.ignoreRecommendations": true,
|
||||
|
||||
@@ -82,8 +56,7 @@
|
||||
"*.ejs": "html",
|
||||
"*.art": "html",
|
||||
"**/tsconfig.json": "jsonc",
|
||||
"*.json": "jsonc",
|
||||
"package.json": "json"
|
||||
"*.json": "jsonc"
|
||||
},
|
||||
|
||||
"files.exclude": {
|
||||
@@ -160,7 +133,6 @@
|
||||
"stylelint.enable": true,
|
||||
"stylelint.packageManager": "pnpm",
|
||||
"stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
|
||||
"stylelint.customSyntax": "postcss-html",
|
||||
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
|
||||
|
||||
"typescript.inlayHints.enumMemberValues.enabled": true,
|
||||
@@ -195,17 +167,12 @@
|
||||
|
||||
"i18n-ally.localesPaths": [
|
||||
"packages/locales/src/langs",
|
||||
"playground/src/locales/langs",
|
||||
"apps/*/src/locales/langs"
|
||||
],
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespace}.{ext}",
|
||||
"i18n-ally.enabledParsers": ["json"],
|
||||
"i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"],
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.namespace": true,
|
||||
|
||||
// 控制相关文件嵌套展示
|
||||
"explorer.fileNesting.enabled": true,
|
||||
@@ -216,12 +183,11 @@
|
||||
"*.env": "$(capture).env.*",
|
||||
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
|
||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
|
||||
"Dockerfile": "Dockerfile,.docker*,docker-entrypoint.sh,build-local-docker*",
|
||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,.ls-lint*,cspell.json",
|
||||
"tailwind.config.mjs": "postcss.*"
|
||||
},
|
||||
"commentTranslate.hover.enabled": false,
|
||||
"commentTranslate.multiLineMerge": true,
|
||||
"vue.server.hybridMode": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"oxc.enable": false
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"commentTranslate.multiLineMerge": true
|
||||
}
|
||||
|
@@ -14,16 +14,16 @@ WORKDIR /app
|
||||
COPY . /app
|
||||
|
||||
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 🎉"
|
||||
|
||||
FROM nginx:stable-alpine AS production
|
||||
FROM nginx:stable-alpine as production
|
||||
|
||||
RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf
|
||||
COPY --from=builder /app/playground/dist /usr/share/nginx/html
|
||||
COPY --from=builder /app/apps/web-antd/dist /usr/share/nginx/html
|
||||
|
||||
COPY --from=builder /app/scripts/deploy/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY ./deploy/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
EXPOSE 8080
|
||||
|
153
README.ja-JP.md
153
README.ja-JP.md
@@ -1,153 +0,0 @@
|
||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
<h1>Vue Vben Admin</h1>
|
||||
</div>
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
||||
|
||||
**日本語** | [English](./README.md) | [中文](./README.zh-CN.md)
|
||||
|
||||
## 紹介
|
||||
|
||||
Vue Vben Adminは、最新の`vue3`、`vite`、`TypeScript`などの主流技術を使用して開発された、無料でオープンソースの中・後端テンプレートです。すぐに使える中・後端のフロントエンドソリューションとして、学習の参考にもなります。
|
||||
|
||||
## アップグレード通知
|
||||
|
||||
これは最新バージョン5.0であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。
|
||||
|
||||
## 特徴
|
||||
|
||||
- **最新技術スタック**: Vue 3やViteなどの最先端フロントエンド技術で開発
|
||||
- **TypeScript**: アプリケーション規模のJavaScriptのための言語
|
||||
- **テーマ**: 複数のテーマカラーが利用可能で、カスタマイズオプションも豊富
|
||||
- **国際化**: 完全な内蔵国際化サポート
|
||||
- **権限管理**: 動的ルートベースの権限生成ソリューションを内蔵
|
||||
|
||||
## プレビュー
|
||||
|
||||
- [Vben Admin](https://vben.pro/) - フルバージョンの中国語サイト
|
||||
|
||||
テストアカウント: vben/123456
|
||||
|
||||
<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/preview2.png">
|
||||
<img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png">
|
||||
</p>
|
||||
|
||||
### Gitpodを使用
|
||||
|
||||
Gitpod(GitHub用の無料オンライン開発環境)でプロジェクトを開き、すぐにコーディングを開始します。
|
||||
|
||||
[](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin)
|
||||
|
||||
## ドキュメント
|
||||
|
||||
[ドキュメント](https://doc.vben.pro/)
|
||||
|
||||
## インストールと使用
|
||||
|
||||
- プロジェクトコードを取得
|
||||
|
||||
```bash
|
||||
git clone https://github.com/vbenjs/vue-vben-admin.git
|
||||
```
|
||||
|
||||
- 依存関係のインストール
|
||||
|
||||
```bash
|
||||
cd vue-vben-admin
|
||||
|
||||
corepack enable
|
||||
|
||||
pnpm install
|
||||
|
||||
```
|
||||
|
||||
- 実行
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- ビルド
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 変更ログ
|
||||
|
||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||
|
||||
## 貢献方法
|
||||
|
||||
ご参加をお待ちしておりますするか、Pull Requestを送信してください。
|
||||
|
||||
**Pull Request:**
|
||||
|
||||
1. コードをフォーク!
|
||||
2. 自分のブランチを作成: `git checkout -b feat/xxxx`
|
||||
3. 変更をコミット: `git commit -am 'feat(function): add xxxxx'`
|
||||
4. ブランチをプッシュ: `git push origin feat/xxxx`
|
||||
5. `pull request`を送信
|
||||
|
||||
## Git貢献提出規則
|
||||
|
||||
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 規則 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
|
||||
- `feat` 新機能の追加
|
||||
- `fix` 問題/バグの修正
|
||||
- `style` コードスタイルに関連し、実行結果に影響しない
|
||||
- `perf` 最適化/パフォーマンス向上
|
||||
- `refactor` リファクタリング
|
||||
- `revert` 変更の取り消し
|
||||
- `test` テスト関連
|
||||
- `docs` ドキュメント/注釈
|
||||
- `chore` 依存関係の更新/スキャフォールディング設定の変更など
|
||||
- `ci` 継続的インテグレーション
|
||||
- `types` 型定義ファイルの変更
|
||||
- `wip` 開発中
|
||||
|
||||
## ブラウザサポート
|
||||
|
||||
ローカル開発には`Chrome 80+`ブラウザを推奨します
|
||||
|
||||
モダンブラウザをサポートし、IEはサポートしません
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :-: | :-: | :-: | :-: | :-: |
|
||||
| サポートしない | 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン |
|
||||
|
||||
## メンテナー
|
||||
|
||||
[@Vben](https://github.com/anncwb)
|
||||
|
||||
## スター歴史
|
||||
|
||||
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
||||
|
||||
## 寄付
|
||||
|
||||
このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます!
|
||||
|
||||

|
||||
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
|
||||
## 貢献者
|
||||
|
||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||
<img alt="Contributors"
|
||||
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
|
||||
</a>
|
||||
|
||||
## Discord
|
||||
|
||||
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)
|
||||
|
||||
## ライセンス
|
||||
|
||||
[MIT © Vben-2020](./LICENSE)
|
19
README.md
19
README.md
@@ -1,13 +1,11 @@
|
||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
|
||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="200" height="200" src="https://unpkg.com/@vbenjs/static-source@0.1.5/source/logo-v1.webp"> </a> <br> <br>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
<h1>Vue Vben Admin</h1>
|
||||
</div>
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
||||
|
||||
**English** | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md)
|
||||
**English** | [中文](./README.zh-CN.md)
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -79,7 +77,7 @@ pnpm build
|
||||
|
||||
## Change Log
|
||||
|
||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||
[CHANGELOG](./CHANGELOG.zh_CN.md)
|
||||
|
||||
## How to contribute
|
||||
|
||||
@@ -124,23 +122,18 @@ Support modern browsers, not IE
|
||||
|
||||
[@Vben](https://github.com/anncwb)
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
||||
|
||||
## Donate
|
||||
|
||||
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
|
||||
|
||||

|
||||

|
||||
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aee;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
<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>
|
||||
|
||||
## Contributor
|
||||
|
||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||
<img alt="Contributors"
|
||||
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
|
||||
<img src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" />
|
||||
</a>
|
||||
|
||||
## Discord
|
||||
|
@@ -1,13 +1,11 @@
|
||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
|
||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="200" height="200" src="https://unpkg.com/@vbenjs/static-source@0.1.5/source/logo-v1.webp"> </a> <br> <br>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
<h1>Vue Vben Admin</h1>
|
||||
</div>
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
||||
|
||||
**中文** | [English](./README.md) | [日本語](./README.ja-JP.md)
|
||||
**中文** | [English](./README.md)
|
||||
|
||||
## 简介
|
||||
|
||||
@@ -77,10 +75,6 @@ pnpm dev
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 更新日志
|
||||
|
||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||
|
||||
## 如何贡献
|
||||
|
||||
非常欢迎你的加入 或者提交一个 Pull Request。
|
||||
@@ -124,25 +118,14 @@ pnpm build
|
||||
|
||||
[@Vben](https://github.com/anncwb)
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
||||
|
||||
## 捐赠
|
||||
|
||||
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
|
||||
|
||||

|
||||

|
||||
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
|
||||
## Contributor
|
||||
|
||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||
<img alt="Contributors"
|
||||
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
|
||||
</a>
|
||||
|
||||
## Discord
|
||||
|
||||
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)
|
||||
|
@@ -1,3 +1 @@
|
||||
PORT=5320
|
||||
ACCESS_TOKEN_SECRET=access_token_secret
|
||||
REFRESH_TOKEN_SECRET=refresh_token_secret
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
## Description
|
||||
|
||||
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。
|
||||
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。由于 sqlite 安装需要在本地进行编译,所以这里接口是直接返回的。线上环境不再提供mock集成,可自行部署服务或者对接真实数据,同步 mock.js等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了 真是的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。
|
||||
|
||||
## Running the app
|
||||
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
const token = getHeader(event, 'Authorization');
|
||||
|
||||
if (!token) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
}
|
||||
|
||||
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||
|
||||
const codes =
|
||||
MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? [];
|
||||
MOCK_CODES.find((item) => item.username === username)?.codes ?? [];
|
||||
|
||||
return useResponseSuccess(codes);
|
||||
});
|
||||
|
@@ -1,36 +1,20 @@
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
setRefreshTokenCookie,
|
||||
} from '~/utils/cookie-utils';
|
||||
import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils';
|
||||
import { forbiddenResponse } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { password, username } = await readBody(event);
|
||||
if (!password || !username) {
|
||||
setResponseStatus(event, 400);
|
||||
return useResponseError(
|
||||
'BadRequestException',
|
||||
'Username and password are required',
|
||||
);
|
||||
}
|
||||
|
||||
const findUser = MOCK_USERS.find(
|
||||
(item) => item.username === username && item.password === password,
|
||||
);
|
||||
|
||||
if (!findUser) {
|
||||
clearRefreshTokenCookie(event);
|
||||
return forbiddenResponse(event, 'Username or password is incorrect.');
|
||||
setResponseStatus(event, 403);
|
||||
return useResponseError('UnauthorizedException', '用户名或密码错误');
|
||||
}
|
||||
|
||||
const accessToken = generateAccessToken(findUser);
|
||||
const refreshToken = generateRefreshToken(findUser);
|
||||
|
||||
setRefreshTokenCookie(event, refreshToken);
|
||||
const accessToken = Buffer.from(username).toString('base64');
|
||||
|
||||
return useResponseSuccess({
|
||||
...findUser,
|
||||
accessToken,
|
||||
// TODO: refresh token
|
||||
refreshToken: accessToken,
|
||||
});
|
||||
});
|
||||
|
@@ -1,15 +0,0 @@
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
getRefreshTokenFromCookie,
|
||||
} from '~/utils/cookie-utils';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const refreshToken = getRefreshTokenFromCookie(event);
|
||||
if (!refreshToken) {
|
||||
return useResponseSuccess('');
|
||||
}
|
||||
|
||||
clearRefreshTokenCookie(event);
|
||||
|
||||
return useResponseSuccess('');
|
||||
});
|
@@ -1,33 +0,0 @@
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
getRefreshTokenFromCookie,
|
||||
setRefreshTokenCookie,
|
||||
} from '~/utils/cookie-utils';
|
||||
import { verifyRefreshToken } from '~/utils/jwt-utils';
|
||||
import { forbiddenResponse } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const refreshToken = getRefreshTokenFromCookie(event);
|
||||
if (!refreshToken) {
|
||||
return forbiddenResponse(event);
|
||||
}
|
||||
|
||||
clearRefreshTokenCookie(event);
|
||||
|
||||
const userinfo = verifyRefreshToken(refreshToken);
|
||||
if (!userinfo) {
|
||||
return forbiddenResponse(event);
|
||||
}
|
||||
|
||||
const findUser = MOCK_USERS.find(
|
||||
(item) => item.username === userinfo.username,
|
||||
);
|
||||
if (!findUser) {
|
||||
return forbiddenResponse(event);
|
||||
}
|
||||
const accessToken = generateAccessToken(findUser);
|
||||
|
||||
setRefreshTokenCookie(event, refreshToken);
|
||||
|
||||
return accessToken;
|
||||
});
|
@@ -1,13 +1,14 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
export default eventHandler((event) => {
|
||||
const token = getHeader(event, 'Authorization');
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
if (!token) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
}
|
||||
|
||||
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||
|
||||
const menus =
|
||||
MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? [];
|
||||
MOCK_MENUS.find((item) => item.username === username)?.menus ?? [];
|
||||
return useResponseSuccess(menus);
|
||||
});
|
||||
|
@@ -1,48 +0,0 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } 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 } = getQuery(event);
|
||||
return usePageResponseSuccess(page as string, pageSize as string, mockData);
|
||||
});
|
@@ -1,10 +1,14 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
const token = getHeader(event, 'Authorization');
|
||||
if (!token) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
}
|
||||
return useResponseSuccess(userinfo);
|
||||
|
||||
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||
|
||||
const user = MOCK_USERS.find((item) => item.username === username);
|
||||
|
||||
const { password: _pwd, ...userInfo } = user;
|
||||
return useResponseSuccess(userInfo);
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { NitroErrorHandler } from 'nitropack';
|
||||
|
||||
const errorHandler: NitroErrorHandler = function (error, event) {
|
||||
event.node.res.end(`[Error Handler] ${error.stack}`);
|
||||
event.res.end(`[error handler] ${error.stack}`);
|
||||
};
|
||||
|
||||
export default errorHandler;
|
||||
|
@@ -1,4 +1,11 @@
|
||||
export default defineEventHandler((event) => {
|
||||
// setResponseHeaders(event, {
|
||||
// 'Access-Control-Allow-Credentials': 'true',
|
||||
// 'Access-Control-Allow-Headers': '*',
|
||||
// 'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||
// 'Access-Control-Allow-Origin': '*',
|
||||
// 'Access-Control-Expose-Headers': '*',
|
||||
// });
|
||||
if (event.method === 'OPTIONS') {
|
||||
event.node.res.statusCode = 204;
|
||||
event.node.res.statusMessage = 'No Content.';
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import errorHandler from './error';
|
||||
|
||||
process.env.COMPATIBILITY_DATE = new Date().toISOString();
|
||||
export default defineNitroConfig({
|
||||
devErrorHandler: errorHandler,
|
||||
errorHandler: '~/error',
|
||||
|
@@ -6,16 +6,10 @@
|
||||
"license": "MIT",
|
||||
"author": "",
|
||||
"scripts": {
|
||||
"build": "nitro build",
|
||||
"start": "nitro dev"
|
||||
"start": "nitro dev",
|
||||
"build": "nitro build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "catalog:",
|
||||
"jsonwebtoken": "catalog:",
|
||||
"nitropack": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "catalog:",
|
||||
"h3": "catalog:"
|
||||
"nitropack": "^2.9.7"
|
||||
}
|
||||
}
|
||||
|
@@ -1,26 +0,0 @@
|
||||
import type { EventHandlerRequest, H3Event } from 'h3';
|
||||
|
||||
export function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) {
|
||||
deleteCookie(event, 'jwt', {
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
secure: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function setRefreshTokenCookie(
|
||||
event: H3Event<EventHandlerRequest>,
|
||||
refreshToken: string,
|
||||
) {
|
||||
setCookie(event, 'jwt', refreshToken, {
|
||||
httpOnly: true,
|
||||
maxAge: 24 * 60 * 60 * 1000,
|
||||
sameSite: 'none',
|
||||
secure: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function getRefreshTokenFromCookie(event: H3Event<EventHandlerRequest>) {
|
||||
const refreshToken = getCookie(event, 'jwt');
|
||||
return refreshToken;
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
import type { EventHandlerRequest, H3Event } from 'h3';
|
||||
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
import { UserInfo } from './mock-data';
|
||||
|
||||
// TODO: Replace with your own secret key
|
||||
const ACCESS_TOKEN_SECRET = 'access_token_secret';
|
||||
const REFRESH_TOKEN_SECRET = 'refresh_token_secret';
|
||||
|
||||
export interface UserPayload extends UserInfo {
|
||||
iat: number;
|
||||
exp: number;
|
||||
}
|
||||
|
||||
export function generateAccessToken(user: UserInfo) {
|
||||
return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' });
|
||||
}
|
||||
|
||||
export function generateRefreshToken(user: UserInfo) {
|
||||
return jwt.sign(user, REFRESH_TOKEN_SECRET, {
|
||||
expiresIn: '30d',
|
||||
});
|
||||
}
|
||||
|
||||
export function verifyAccessToken(
|
||||
event: H3Event<EventHandlerRequest>,
|
||||
): null | Omit<UserInfo, 'password'> {
|
||||
const authHeader = getHeader(event, 'Authorization');
|
||||
if (!authHeader?.startsWith('Bearer')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
try {
|
||||
const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET) as UserPayload;
|
||||
|
||||
const username = decoded.username;
|
||||
const user = MOCK_USERS.find((item) => item.username === username);
|
||||
const { password: _pwd, ...userinfo } = user;
|
||||
return userinfo;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function verifyRefreshToken(
|
||||
token: string,
|
||||
): null | Omit<UserInfo, 'password'> {
|
||||
try {
|
||||
const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload;
|
||||
const username = decoded.username;
|
||||
const user = MOCK_USERS.find((item) => item.username === username);
|
||||
const { password: _pwd, ...userinfo } = user;
|
||||
return userinfo;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -1,12 +1,4 @@
|
||||
export interface UserInfo {
|
||||
id: number;
|
||||
password: string;
|
||||
realName: string;
|
||||
roles: string[];
|
||||
username: string;
|
||||
}
|
||||
|
||||
export const MOCK_USERS: UserInfo[] = [
|
||||
export const MOCK_USERS = [
|
||||
{
|
||||
id: 0,
|
||||
password: '123456',
|
||||
@@ -86,7 +78,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
component: '/demos/access/admin-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'demos.access.adminVisible',
|
||||
title: 'page.demos.access.adminVisible',
|
||||
},
|
||||
name: 'AccessAdminVisibleDemo',
|
||||
path: '/demos/access/admin-visible',
|
||||
@@ -95,7 +87,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
component: '/demos/access/super-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'demos.access.superVisible',
|
||||
title: 'page.demos.access.superVisible',
|
||||
},
|
||||
name: 'AccessSuperVisibleDemo',
|
||||
path: '/demos/access/super-visible',
|
||||
@@ -104,7 +96,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
component: '/demos/access/user-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'demos.access.userVisible',
|
||||
title: 'page.demos.access.userVisible',
|
||||
},
|
||||
name: 'AccessUserVisibleDemo',
|
||||
path: '/demos/access/user-visible',
|
||||
@@ -118,7 +110,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
icon: 'ic:baseline-view-in-ar',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: 'demos.title',
|
||||
title: 'page.demos.title',
|
||||
},
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
@@ -129,7 +121,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
path: '/demosaccess',
|
||||
meta: {
|
||||
icon: 'mdi:cloud-key-outline',
|
||||
title: 'demos.access.backendPermissions',
|
||||
title: 'page.demos.access.backendPermissions',
|
||||
},
|
||||
redirect: '/demos/access/page-control',
|
||||
children: [
|
||||
@@ -139,7 +131,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
component: '/demos/access/index',
|
||||
meta: {
|
||||
icon: 'mdi:page-previous-outline',
|
||||
title: 'demos.access.pageAccess',
|
||||
title: 'page.demos.access.pageAccess',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -148,7 +140,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
component: '/demos/access/button-control',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'demos.access.buttonControl',
|
||||
title: 'page.demos.access.buttonControl',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -159,7 +151,7 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
authority: ['no-body'],
|
||||
icon: 'mdi:button-cursor',
|
||||
menuVisibleWithForbidden: true,
|
||||
title: 'demos.access.menuVisible403',
|
||||
title: 'page.demos.access.menuVisible403',
|
||||
},
|
||||
},
|
||||
roleWithMenus[role],
|
||||
|
@@ -1,5 +1,3 @@
|
||||
import type { EventHandlerRequest, H3Event } from 'h3';
|
||||
|
||||
export function useResponseSuccess<T = any>(data: T) {
|
||||
return {
|
||||
code: 0,
|
||||
@@ -9,27 +7,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) {
|
||||
return {
|
||||
code: -1,
|
||||
@@ -38,31 +15,3 @@ export function useResponseError(message: string, error: any = null) {
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
export function forbiddenResponse(
|
||||
event: H3Event<EventHandlerRequest>,
|
||||
message = 'Forbidden Exception',
|
||||
) {
|
||||
setResponseStatus(event, 403);
|
||||
return useResponseError(message, message);
|
||||
}
|
||||
|
||||
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('Unauthorized Exception', 'Unauthorized Exception');
|
||||
}
|
||||
|
||||
export function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function pagination<T = any>(
|
||||
pageNo: number,
|
||||
pageSize: number,
|
||||
array: T[],
|
||||
): T[] {
|
||||
const offset = (pageNo - 1) * Number(pageSize);
|
||||
return offset + Number(pageSize) >= array.length
|
||||
? array.slice(offset)
|
||||
: array.slice(offset, offset + Number(pageSize));
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# 应用标题
|
||||
VITE_APP_TITLE=Vben Admin Antd
|
||||
VITE_APP_TITLE=Vben Admin
|
||||
|
||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||
VITE_APP_NAMESPACE=vben-web-antd
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# 端口号
|
||||
VITE_PORT=5666
|
||||
VITE_PORT=5555
|
||||
|
||||
VITE_BASE=/
|
||||
|
||||
|
@@ -14,6 +14,3 @@ VITE_ROUTER_HISTORY=hash
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# 打包后是否生成dist.zip
|
||||
VITE_ARCHIVER=true
|
||||
|
@@ -21,7 +21,7 @@
|
||||
(function () {
|
||||
var hm = document.createElement('script');
|
||||
hm.src =
|
||||
'https://hm.baidu.com/hm.js?b38e689f40558f20a9a686d7f6f33edf';
|
||||
'https://hm.baidu.com/hm.js?d20a01273820422b6aa2ee41b6c9414d';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-antd",
|
||||
"version": "5.4.8",
|
||||
"version": "5.0.0",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -27,24 +27,24 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben/access": "workspace:*",
|
||||
"@vben/chart-ui": "workspace:*",
|
||||
"@vben/common-ui": "workspace:*",
|
||||
"@vben/constants": "workspace:*",
|
||||
"@vben/hooks": "workspace:*",
|
||||
"@vben/icons": "workspace:*",
|
||||
"@vben/layouts": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/plugins": "workspace:*",
|
||||
"@vben/preferences": "workspace:*",
|
||||
"@vben/request": "workspace:*",
|
||||
"@vben/stores": "workspace:*",
|
||||
"@vben/styles": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"ant-design-vue": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"pinia": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"vue-router": "catalog:"
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"ant-design-vue": "^4.2.3",
|
||||
"dayjs": "^1.11.12",
|
||||
"pinia": "2.2.0",
|
||||
"vue": "^3.4.35",
|
||||
"vue-router": "^4.4.2"
|
||||
}
|
||||
}
|
||||
|
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
|
||||
import type { Component, SetupContext } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { globalShareState } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
notification,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Textarea,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
) => {
|
||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
||||
const placeholder = props?.placeholder || $t(`ui.placeholder.${type}`);
|
||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
||||
};
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'DefaultButton'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'PrimaryButton'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
notification.success({
|
||||
description: content,
|
||||
message: title,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
@@ -1,47 +0,0 @@
|
||||
import type {
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
// ant design vue组件库默认都是 v-model:value
|
||||
baseModelPropName: 'value',
|
||||
|
||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||
modelPropNameMap: {
|
||||
Checkbox: 'checked',
|
||||
Radio: 'checked',
|
||||
Switch: 'checked',
|
||||
Upload: 'fileList',
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
// 输入项目必填国际化适配
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 选择项目必填国际化适配
|
||||
selectRequired: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null) {
|
||||
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
@@ -1,67 +0,0 @@
|
||||
import { h } from 'vue';
|
||||
|
||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
|
||||
import { Button, Image } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from './form';
|
||||
|
||||
setupVbenVxeTable({
|
||||
configVxeTable: (vxeUI) => {
|
||||
vxeUI.setConfig({
|
||||
grid: {
|
||||
align: 'center',
|
||||
border: false,
|
||||
columnConfig: {
|
||||
resizable: true,
|
||||
},
|
||||
minHeight: 180,
|
||||
formConfig: {
|
||||
// 全局禁用vxe-table的表单配置,使用formOptions
|
||||
enabled: false,
|
||||
},
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
response: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
list: 'items',
|
||||
},
|
||||
showActiveMsg: true,
|
||||
showResponseMsg: false,
|
||||
},
|
||||
round: true,
|
||||
showOverflow: true,
|
||||
size: 'small',
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||
vxeUI.renderer.add('CellImage', {
|
||||
renderTableDefault(_renderOpts, params) {
|
||||
const { column, row } = params;
|
||||
return h(Image, { src: row[column.field] });
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||
vxeUI.renderer.add('CellLink', {
|
||||
renderTableDefault(renderOpts) {
|
||||
const { props } = renderOpts;
|
||||
return h(
|
||||
Button,
|
||||
{ size: 'small', type: 'link' },
|
||||
{ default: () => props?.text },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||
// vxeUI.formats.add
|
||||
},
|
||||
useVbenForm,
|
||||
});
|
||||
|
||||
export { useVbenVxeGrid };
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
@@ -1,20 +1,20 @@
|
||||
import { baseRequestClient, requestClient } from '#/api/request';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
export interface LoginParams {
|
||||
password?: string;
|
||||
username?: string;
|
||||
password: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
/** 登录接口返回值 */
|
||||
export interface LoginResult {
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
data: string;
|
||||
status: number;
|
||||
desc: string;
|
||||
realName: string;
|
||||
refreshToken: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,24 +25,6 @@ export async function loginApi(data: AuthApi.LoginParams) {
|
||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新accessToken
|
||||
*/
|
||||
export async function refreshTokenApi() {
|
||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
export async function logoutApi() {
|
||||
return baseRequestClient.post('/auth/logout', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户权限码
|
||||
*/
|
||||
|
@@ -1,2 +1 @@
|
||||
export * from './status';
|
||||
export * from './table';
|
@@ -1 +1,2 @@
|
||||
export * from './core';
|
||||
export * from './demos';
|
||||
|
@@ -5,110 +5,63 @@ import type { HttpResponse } from '@vben/request';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import {
|
||||
authenticateResponseInterceptor,
|
||||
errorMessageResponseInterceptor,
|
||||
RequestClient,
|
||||
} from '@vben/request';
|
||||
import { RequestClient } from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { refreshTokenApi } from './core';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
|
||||
function createRequestClient(baseURL: string) {
|
||||
const client = new RequestClient({
|
||||
baseURL,
|
||||
});
|
||||
// 为每个请求携带 Authorization
|
||||
makeAuthorization: () => {
|
||||
return {
|
||||
// 默认
|
||||
key: 'Authorization',
|
||||
tokenHandler: () => {
|
||||
const accessStore = useAccessStore();
|
||||
return {
|
||||
refreshToken: `${accessStore.refreshToken}`,
|
||||
token: `${accessStore.accessToken}`,
|
||||
};
|
||||
},
|
||||
unAuthorizedHandler: async () => {
|
||||
const accessStore = useAccessStore();
|
||||
const authStore = useAuthStore();
|
||||
accessStore.setAccessToken(null);
|
||||
|
||||
/**
|
||||
* 重新认证逻辑
|
||||
*/
|
||||
async function doReAuthenticate() {
|
||||
console.warn('Access token or refresh token is invalid or expired. ');
|
||||
const accessStore = useAccessStore();
|
||||
const authStore = useAuthStore();
|
||||
accessStore.setAccessToken(null);
|
||||
if (
|
||||
preferences.app.loginExpiredMode === 'modal' &&
|
||||
accessStore.isAccessChecked
|
||||
) {
|
||||
accessStore.setLoginExpired(true);
|
||||
} else {
|
||||
await authStore.logout();
|
||||
if (preferences.app.loginExpiredMode === 'modal') {
|
||||
accessStore.setLoginExpired(true);
|
||||
} else {
|
||||
// 退出登录
|
||||
await authStore.logout();
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
makeErrorMessage: (msg) => message.error(msg),
|
||||
|
||||
makeRequestHeaders: () => {
|
||||
return {
|
||||
// 为每个请求携带 Accept-Language
|
||||
'Accept-Language': preferences.app.locale,
|
||||
};
|
||||
},
|
||||
});
|
||||
client.addResponseInterceptor<HttpResponse>((response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
const { code, data, message: msg } = responseData;
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token逻辑
|
||||
*/
|
||||
async function doRefreshToken() {
|
||||
const accessStore = useAccessStore();
|
||||
const resp = await refreshTokenApi();
|
||||
const newToken = resp.data;
|
||||
accessStore.setAccessToken(newToken);
|
||||
return newToken;
|
||||
}
|
||||
|
||||
function formatToken(token: null | string) {
|
||||
return token ? `Bearer ${token}` : null;
|
||||
}
|
||||
|
||||
// 请求头处理
|
||||
client.addRequestInterceptor({
|
||||
fulfilled: async (config) => {
|
||||
const accessStore = useAccessStore();
|
||||
|
||||
config.headers.Authorization = formatToken(accessStore.accessToken);
|
||||
config.headers['Accept-Language'] = preferences.app.locale;
|
||||
return config;
|
||||
},
|
||||
throw new Error(msg);
|
||||
});
|
||||
|
||||
// response数据解构
|
||||
client.addResponseInterceptor<HttpResponse>({
|
||||
fulfilled: (response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
const { code, data } = responseData;
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return data;
|
||||
}
|
||||
|
||||
throw Object.assign({}, response, { response });
|
||||
},
|
||||
});
|
||||
|
||||
// token过期的处理
|
||||
client.addResponseInterceptor(
|
||||
authenticateResponseInterceptor({
|
||||
client,
|
||||
doReAuthenticate,
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||
formatToken,
|
||||
}),
|
||||
);
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string, error) => {
|
||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||
// 当前mock接口返回的错误字段是 error 或者 message
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||
// 如果没有错误信息,则会根据状态码进行提示
|
||||
message.error(errorMessage || msg);
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export const requestClient = createRequestClient(apiURL);
|
||||
|
||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||
|
@@ -1,23 +1,16 @@
|
||||
import { createApp, watchEffect } from 'vue';
|
||||
import { createApp } from 'vue';
|
||||
|
||||
import { registerAccessDirective } from '@vben/access';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { initStores } from '@vben/stores';
|
||||
import '@vben/styles';
|
||||
import '@vben/styles/antd';
|
||||
|
||||
import { useTitle } from '@vueuse/core';
|
||||
import { setupI18n } from '#/locales';
|
||||
|
||||
import { $t, setupI18n } from '#/locales';
|
||||
|
||||
import { initComponentAdapter } from './adapter/component';
|
||||
import App from './app.vue';
|
||||
import { router } from './router';
|
||||
|
||||
async function bootstrap(namespace: string) {
|
||||
// 初始化组件适配器
|
||||
await initComponentAdapter();
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
// 国际化 i18n 配置
|
||||
@@ -32,16 +25,6 @@ async function bootstrap(namespace: string) {
|
||||
// 配置路由及路由守卫
|
||||
app.use(router);
|
||||
|
||||
// 动态更新标题
|
||||
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');
|
||||
}
|
||||
|
||||
|
@@ -1,23 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { AuthPageLayout } from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const appName = computed(() => preferences.app.name);
|
||||
const logo = computed(() => preferences.logo.source);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthPageLayout
|
||||
:app-name="appName"
|
||||
:logo="logo"
|
||||
:page-description="$t('authentication.pageDesc')"
|
||||
:page-title="$t('authentication.pageTitle')"
|
||||
>
|
||||
<!-- 自定义工具栏 -->
|
||||
<!-- <template #toolbar></template> -->
|
||||
</AuthPageLayout>
|
||||
</template>
|
@@ -1,11 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import type { NotificationItem } from '@vben/layouts';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||
import { useWatermark } from '@vben/hooks';
|
||||
import { LOGIN_PATH, VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||
import {
|
||||
BasicLayout,
|
||||
@@ -14,12 +14,17 @@ import {
|
||||
UserDropdown,
|
||||
} from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
import {
|
||||
resetAllStores,
|
||||
storeToRefs,
|
||||
useAccessStore,
|
||||
useUserStore,
|
||||
} from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
import { resetRoutes } from '#/router';
|
||||
import { useAuthStore } from '#/store';
|
||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||
|
||||
const notifications = ref<NotificationItem[]>([
|
||||
{
|
||||
@@ -55,7 +60,6 @@ const notifications = ref<NotificationItem[]>([
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
const accessStore = useAccessStore();
|
||||
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||
const showDot = computed(() =>
|
||||
notifications.value.some((item) => !item.isRead),
|
||||
);
|
||||
@@ -68,7 +72,7 @@ const menus = computed(() => [
|
||||
});
|
||||
},
|
||||
icon: BookOpenText,
|
||||
text: $t('ui.widgets.document'),
|
||||
text: $t('widgets.document'),
|
||||
},
|
||||
{
|
||||
handler: () => {
|
||||
@@ -86,16 +90,22 @@ const menus = computed(() => [
|
||||
});
|
||||
},
|
||||
icon: CircleHelp,
|
||||
text: $t('ui.widgets.qa'),
|
||||
text: $t('widgets.qa'),
|
||||
},
|
||||
]);
|
||||
|
||||
const { loginLoading } = storeToRefs(authStore);
|
||||
|
||||
const avatar = computed(() => {
|
||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
async function handleLogout() {
|
||||
await authStore.logout(false);
|
||||
resetAllStores();
|
||||
resetRoutes();
|
||||
await router.replace(LOGIN_PATH);
|
||||
}
|
||||
|
||||
function handleNoticeClear() {
|
||||
@@ -105,21 +115,6 @@ function handleNoticeClear() {
|
||||
function handleMakeAll() {
|
||||
notifications.value.forEach((item) => (item.isRead = true));
|
||||
}
|
||||
watch(
|
||||
() => preferences.app.watermark,
|
||||
async (enable) => {
|
||||
if (enable) {
|
||||
await updateWatermark({
|
||||
content: `${userStore.userInfo?.username}`,
|
||||
});
|
||||
} else {
|
||||
destroyWatermark();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -146,9 +141,11 @@ watch(
|
||||
<AuthenticationLoginExpiredModal
|
||||
v-model:open="accessStore.loginExpired"
|
||||
:avatar
|
||||
>
|
||||
<LoginForm />
|
||||
</AuthenticationLoginExpiredModal>
|
||||
:loading="loginLoading"
|
||||
password-placeholder="123456"
|
||||
username-placeholder="vben"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
</template>
|
||||
<template #lock-screen>
|
||||
<LockScreen :avatar @to-login="handleLogout" />
|
||||
|
@@ -1,6 +1,8 @@
|
||||
const BasicLayout = () => import('./basic.vue');
|
||||
const AuthPageLayout = () => import('./auth.vue');
|
||||
|
||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||
|
||||
const AuthPageLayout = () =>
|
||||
import('@vben/layouts').then((m) => m.AuthPageLayout);
|
||||
|
||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||
|
@@ -4,11 +4,7 @@ import type { Locale } from 'ant-design-vue/es/locale';
|
||||
import type { App } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import {
|
||||
$t,
|
||||
setupI18n as coreSetup,
|
||||
loadLocalesMapFromDir,
|
||||
} from '@vben/locales';
|
||||
import { $t, setupI18n as coreSetup, loadLocalesMap } from '@vben/locales';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
|
||||
@@ -17,12 +13,10 @@ import dayjs from 'dayjs';
|
||||
|
||||
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,
|
||||
);
|
||||
/**
|
||||
* 加载应用特有的语言包
|
||||
* 这里也可以改造为从服务端获取翻译数据
|
||||
@@ -51,24 +45,20 @@ async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
|
||||
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
let locale;
|
||||
switch (lang) {
|
||||
case 'en-US': {
|
||||
locale = await import('dayjs/locale/en');
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
locale = await import('dayjs/locale/zh-cn');
|
||||
break;
|
||||
}
|
||||
case 'en-US': {
|
||||
locale = await import('dayjs/locale/en');
|
||||
break;
|
||||
}
|
||||
// 默认使用英语
|
||||
default: {
|
||||
locale = await import('dayjs/locale/en');
|
||||
}
|
||||
}
|
||||
if (locale) {
|
||||
dayjs.locale(locale);
|
||||
} else {
|
||||
console.error(`Failed to load dayjs locale for ${lang}`);
|
||||
}
|
||||
dayjs.locale(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,14 +67,14 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
*/
|
||||
async function loadAntdLocale(lang: SupportedLanguagesType) {
|
||||
switch (lang) {
|
||||
case 'en-US': {
|
||||
antdLocale.value = antdEnLocale;
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
antdLocale.value = antdDefaultLocale;
|
||||
break;
|
||||
}
|
||||
case 'en-US': {
|
||||
antdLocale.value = antdEnLocale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,4 +87,4 @@ async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
export { $t, antdLocale, setupI18n };
|
||||
export { $t, antdLocale, loadMessages, setupI18n };
|
||||
|
59
apps/web-antd/src/locales/langs/en-US.json
Normal file
59
apps/web-antd/src/locales/langs/en-US.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"page": {
|
||||
"demos": {
|
||||
"title": "Demos",
|
||||
"access": {
|
||||
"frontendPermissions": "Frontend Permissions",
|
||||
"backendPermissions": "Backend Permissions",
|
||||
"pageAccess": "Page Access",
|
||||
"buttonControl": "Button Control",
|
||||
"menuVisible403": "Menu Visible(403)",
|
||||
"superVisible": "Visible to Super",
|
||||
"adminVisible": "Visible to Admin",
|
||||
"userVisible": "Visible to User"
|
||||
},
|
||||
"nested": {
|
||||
"title": "Nested Menu",
|
||||
"menu1": "Menu 1",
|
||||
"menu2": "Menu 2",
|
||||
"menu2_1": "Menu 2-1",
|
||||
"menu3": "Menu 3",
|
||||
"menu3_1": "Menu 3-1",
|
||||
"menu3_2": "Menu 3-2",
|
||||
"menu3_2_1": "Menu 3-2-1"
|
||||
},
|
||||
"outside": {
|
||||
"title": "External Pages",
|
||||
"embedded": "Embedded",
|
||||
"externalLink": "External Link"
|
||||
},
|
||||
"badge": {
|
||||
"title": "Menu Badge",
|
||||
"dot": "Dot Badge",
|
||||
"text": "Text Badge",
|
||||
"color": "Badge Color"
|
||||
},
|
||||
"activeIcon": {
|
||||
"title": "Active Menu Icon",
|
||||
"children": "Children Active Icon"
|
||||
},
|
||||
"fallback": { "title": "Fallback Page" },
|
||||
"features": {
|
||||
"title": "Features",
|
||||
"hideChildrenInMenu": "Hide Menu Children",
|
||||
"loginExpired": "Login Expired",
|
||||
"icons": "Icons",
|
||||
"watermark": "Watermark",
|
||||
"tabs": "Tabs",
|
||||
"tabDetail": "Tab Detail Page"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"navigation": "Breadcrumb Navigation",
|
||||
"lateral": "Lateral Mode",
|
||||
"lateralDetail": "Lateral Mode Detail",
|
||||
"level": "Level Mode",
|
||||
"levelDetail": "Level Mode Detail"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
61
apps/web-antd/src/locales/langs/zh-CN.json
Normal file
61
apps/web-antd/src/locales/langs/zh-CN.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"page": {
|
||||
"demos": {
|
||||
"title": "演示",
|
||||
"access": {
|
||||
"frontendPermissions": "前端权限",
|
||||
"backendPermissions": "后端权限",
|
||||
"pageAccess": "页面访问",
|
||||
"buttonControl": "按钮控制",
|
||||
"menuVisible403": "菜单可见(403)",
|
||||
"superVisible": "Super 可见",
|
||||
"adminVisible": "Admin 可见",
|
||||
"userVisible": "User 可见"
|
||||
},
|
||||
"nested": {
|
||||
"title": "嵌套菜单",
|
||||
"menu1": "菜单 1",
|
||||
"menu2": "菜单 2",
|
||||
"menu2_1": "菜单 2-1",
|
||||
"menu3": "菜单 3",
|
||||
"menu3_1": "菜单 3-1",
|
||||
"menu3_2": "菜单 3-2",
|
||||
"menu3_2_1": "菜单 3-2-1"
|
||||
},
|
||||
"outside": {
|
||||
"title": "外部页面",
|
||||
"embedded": "内嵌",
|
||||
"externalLink": "外链"
|
||||
},
|
||||
"badge": {
|
||||
"title": "菜单徽标",
|
||||
"dot": "点徽标",
|
||||
"text": "文本徽标",
|
||||
"color": "徽标颜色"
|
||||
},
|
||||
"activeIcon": {
|
||||
"title": "菜单激活图标",
|
||||
"children": "子级激活图标"
|
||||
},
|
||||
"fallback": {
|
||||
"title": "缺省页"
|
||||
},
|
||||
"features": {
|
||||
"title": "功能",
|
||||
"hideChildrenInMenu": "隐藏子菜单",
|
||||
"loginExpired": "登录过期",
|
||||
"icons": "图标",
|
||||
"watermark": "水印",
|
||||
"tabs": "标签页",
|
||||
"tabDetail": "标签详情页"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"navigation": "面包屑导航",
|
||||
"lateral": "平级模式",
|
||||
"level": "层级模式",
|
||||
"levelDetail": "层级模式详情",
|
||||
"lateralDetail": "平级模式详情"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 版本"
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"auth": {
|
||||
"login": "登录",
|
||||
"register": "注册",
|
||||
"codeLogin": "验证码登录",
|
||||
"qrcodeLogin": "二维码登录",
|
||||
"forgetPassword": "忘记密码"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "概览",
|
||||
"analytics": "分析页",
|
||||
"workspace": "工作台"
|
||||
}
|
||||
}
|
@@ -3,7 +3,6 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||
/**
|
||||
* @description 项目配置文件
|
||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||
*/
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
|
@@ -5,7 +5,10 @@ import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
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 { generateAccess } from './access';
|
||||
@@ -31,12 +34,21 @@ function setupCommonGuard(router: Router) {
|
||||
router.afterEach((to) => {
|
||||
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
|
||||
|
||||
loadedPaths.add(to.path);
|
||||
if (preferences.tabbar.enable) {
|
||||
loadedPaths.add(to.path);
|
||||
}
|
||||
|
||||
// 关闭页面加载进度条
|
||||
if (preferences.transition.progress) {
|
||||
stopProgress();
|
||||
}
|
||||
|
||||
// 动态修改标题
|
||||
if (preferences.app.dynamicTitle) {
|
||||
const { title } = to.meta;
|
||||
// useTitle(`${$t(title)} - ${preferences.app.name}`);
|
||||
useTitle(`${$t(title)} - ${preferences.app.name}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,8 +92,10 @@ function setupAccessGuard(router: Router) {
|
||||
return to;
|
||||
}
|
||||
|
||||
const accessRoutes = accessStore.accessRoutes;
|
||||
|
||||
// 是否已经生成过动态路由
|
||||
if (accessStore.isAccessChecked) {
|
||||
if (accessRoutes && accessRoutes.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -95,17 +109,16 @@ function setupAccessGuard(router: Router) {
|
||||
roles: userRoles,
|
||||
router,
|
||||
// 则会在菜单中显示,但是访问会被重定向到403
|
||||
routes: accessRoutes,
|
||||
routes: dynamicRoutes,
|
||||
});
|
||||
|
||||
// 保存菜单信息和路由信息
|
||||
accessStore.setAccessMenus(accessibleMenus);
|
||||
accessStore.setAccessRoutes(accessibleRoutes);
|
||||
accessStore.setIsAccessChecked(true);
|
||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
||||
const redirectPath = (from.query.redirect ?? to.path) as string;
|
||||
|
||||
return {
|
||||
...router.resolve(decodeURIComponent(redirectPath)),
|
||||
path: decodeURIComponent(redirectPath),
|
||||
replace: true,
|
||||
};
|
||||
});
|
||||
|
@@ -19,12 +19,7 @@ const router = createRouter({
|
||||
: createWebHistory(import.meta.env.VITE_BASE),
|
||||
// 应该添加到路由的初始路由列表。
|
||||
routes,
|
||||
scrollBehavior: (to, _from, savedPosition) => {
|
||||
if (savedPosition) {
|
||||
return savedPosition;
|
||||
}
|
||||
return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };
|
||||
},
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
// 是否应该禁止尾部斜杠。
|
||||
// strict: true,
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||
import { DEFAULT_HOME_PATH } from '@vben/constants';
|
||||
|
||||
import { AuthPageLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
@@ -32,19 +32,17 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: AuthPageLayout,
|
||||
meta: {
|
||||
hideInTab: true,
|
||||
title: 'Authentication',
|
||||
},
|
||||
name: 'Authentication',
|
||||
path: '/auth',
|
||||
redirect: LOGIN_PATH,
|
||||
children: [
|
||||
{
|
||||
name: 'Login',
|
||||
path: 'login',
|
||||
component: Login,
|
||||
meta: {
|
||||
title: $t('page.auth.login'),
|
||||
title: $t('page.core.login'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -52,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
path: 'code-login',
|
||||
component: () => import('#/views/_core/authentication/code-login.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.codeLogin'),
|
||||
title: $t('page.core.codeLogin'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -61,7 +59,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
component: () =>
|
||||
import('#/views/_core/authentication/qrcode-login.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.qrcodeLogin'),
|
||||
title: $t('page.core.qrcodeLogin'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -70,7 +68,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
component: () =>
|
||||
import('#/views/_core/authentication/forget-password.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.forgetPassword'),
|
||||
title: $t('page.core.forgetPassword'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -78,7 +76,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
path: 'register',
|
||||
component: () => import('#/views/_core/authentication/register.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.register'),
|
||||
title: $t('page.core.register'),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@@ -9,29 +9,23 @@ const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
|
||||
});
|
||||
|
||||
// 有需要可以自行打开注释,并创建文件夹
|
||||
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
|
||||
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
|
||||
|
||||
/** 动态路由 */
|
||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
||||
|
||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
||||
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
||||
/** 静态路由列表,访问这些页面可以不需要权限 */
|
||||
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
||||
const staticRoutes: RouteRecordRaw[] = [];
|
||||
const externalRoutes: RouteRecordRaw[] = [];
|
||||
|
||||
/** 路由列表,由基本路由、外部路由和404兜底路由组成
|
||||
* 无需走权限验证(会一直显示在菜单中) */
|
||||
/** 路由列表,由基本路由+静态路由组成 */
|
||||
const routes: RouteRecordRaw[] = [
|
||||
...coreRoutes,
|
||||
...externalRoutes,
|
||||
...staticRoutes,
|
||||
fallbackNotFoundRoute,
|
||||
];
|
||||
|
||||
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
||||
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
||||
|
||||
/** 有权限校验的路由列表,包含动态路由和静态路由 */
|
||||
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
||||
export { accessRoutes, coreRouteNames, routes };
|
||||
export { coreRouteNames, dynamicRoutes, routes };
|
||||
|
@@ -29,7 +29,6 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '/workspace',
|
||||
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||
meta: {
|
||||
icon: 'carbon:workspace',
|
||||
title: $t('page.dashboard.workspace'),
|
||||
},
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
@@ -10,18 +10,482 @@ const routes: RouteRecordRaw[] = [
|
||||
icon: 'ic:baseline-view-in-ar',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: $t('demos.title'),
|
||||
title: $t('page.demos.title'),
|
||||
},
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
children: [
|
||||
// 权限控制
|
||||
{
|
||||
meta: {
|
||||
title: $t('demos.antd'),
|
||||
icon: 'mdi:shield-key-outline',
|
||||
title: $t('page.demos.access.frontendPermissions'),
|
||||
},
|
||||
name: 'AntDesignDemos',
|
||||
path: '/demos/ant-design',
|
||||
component: () => import('#/views/demos/antd/index.vue'),
|
||||
name: 'AccessDemos',
|
||||
path: '/demos/access',
|
||||
children: [
|
||||
{
|
||||
name: 'AccessPageControlDemo',
|
||||
path: '/demos/access/page-control',
|
||||
component: () => import('#/views/demos/access/index.vue'),
|
||||
meta: {
|
||||
icon: 'mdi:page-previous-outline',
|
||||
title: $t('page.demos.access.pageAccess'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessButtonControlDemo',
|
||||
path: '/demos/access/button-control',
|
||||
component: () => import('#/views/demos/access/button-control.vue'),
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: $t('page.demos.access.buttonControl'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessMenuVisible403Demo',
|
||||
path: '/demos/access/menu-visible-403',
|
||||
component: () =>
|
||||
import('#/views/demos/access/menu-visible-403.vue'),
|
||||
meta: {
|
||||
authority: ['no-body'],
|
||||
icon: 'mdi:button-cursor',
|
||||
menuVisibleWithForbidden: true,
|
||||
title: $t('page.demos.access.menuVisible403'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessSuperVisibleDemo',
|
||||
path: '/demos/access/super-visible',
|
||||
component: () => import('#/views/demos/access/super-visible.vue'),
|
||||
meta: {
|
||||
authority: ['super'],
|
||||
icon: 'mdi:button-cursor',
|
||||
title: $t('page.demos.access.superVisible'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessAdminVisibleDemo',
|
||||
path: '/demos/access/admin-visible',
|
||||
component: () => import('#/views/demos/access/admin-visible.vue'),
|
||||
meta: {
|
||||
authority: ['admin'],
|
||||
icon: 'mdi:button-cursor',
|
||||
title: $t('page.demos.access.adminVisible'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessUserVisibleDemo',
|
||||
path: '/demos/access/user-visible',
|
||||
component: () => import('#/views/demos/access/user-visible.vue'),
|
||||
meta: {
|
||||
authority: ['user'],
|
||||
icon: 'mdi:button-cursor',
|
||||
title: $t('page.demos.access.userVisible'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// 功能
|
||||
{
|
||||
meta: {
|
||||
icon: 'mdi:feature-highlight',
|
||||
title: $t('page.demos.features.title'),
|
||||
},
|
||||
name: 'FeaturesDemos',
|
||||
path: '/demos/features',
|
||||
children: [
|
||||
{
|
||||
name: 'LoginExpiredDemo',
|
||||
path: '/demos/features/login-expired',
|
||||
component: () =>
|
||||
import('#/views/demos/features/login-expired/index.vue'),
|
||||
meta: {
|
||||
icon: 'mdi:encryption-expiration',
|
||||
title: $t('page.demos.features.loginExpired'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'IconsDemo',
|
||||
path: '/demos/features/icons',
|
||||
component: () => import('#/views/demos/features/icons/index.vue'),
|
||||
meta: {
|
||||
title: $t('page.demos.features.icons'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'WatermarkDemo',
|
||||
path: '/demos/features/watermark',
|
||||
component: () =>
|
||||
import('#/views/demos/features/watermark/index.vue'),
|
||||
meta: {
|
||||
title: $t('page.demos.features.watermark'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'FeatureTabsDemo',
|
||||
path: '/demos/features/tabs',
|
||||
component: () => import('#/views/demos/features/tabs/index.vue'),
|
||||
meta: {
|
||||
icon: 'lucide:app-window',
|
||||
title: $t('page.demos.features.tabs'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'FeatureTabDetailDemo',
|
||||
path: '/demos/features/tabs/detail/:id',
|
||||
component: () =>
|
||||
import('#/views/demos/features/tabs/tab-detail.vue'),
|
||||
meta: {
|
||||
activePath: '/demos/features/tabs',
|
||||
hideInMenu: true,
|
||||
maxNumOfOpenTab: 3,
|
||||
title: $t('page.demos.features.tabDetail'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'HideChildrenInMenuParentDemo',
|
||||
path: '/demos/features/hide-menu-children',
|
||||
component: () =>
|
||||
import('#/views/demos/features/hide-menu-children/parent.vue'),
|
||||
meta: {
|
||||
hideChildrenInMenu: true,
|
||||
icon: 'ic:round-menu',
|
||||
title: $t('page.demos.features.hideChildrenInMenu'),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'HideChildrenInMenuChildrenDemo',
|
||||
path: '/demos/features/hide-menu-children/children',
|
||||
component: () =>
|
||||
import(
|
||||
'#/views/demos/features/hide-menu-children/children.vue'
|
||||
),
|
||||
meta: { title: 'HideChildrenInMenuChildrenDemo' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// 面包屑导航
|
||||
{
|
||||
name: 'BreadcrumbDemos',
|
||||
path: '/demos/breadcrumb',
|
||||
meta: {
|
||||
icon: 'lucide:navigation',
|
||||
title: $t('page.demos.breadcrumb.navigation'),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'BreadcrumbLateralDemo',
|
||||
path: '/demos/breadcrumb/lateral',
|
||||
component: () => import('#/views/demos/breadcrumb/lateral.vue'),
|
||||
meta: {
|
||||
icon: 'lucide:navigation',
|
||||
title: $t('page.demos.breadcrumb.lateral'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'BreadcrumbLateralDetailDemo',
|
||||
path: '/demos/breadcrumb/lateral-detail',
|
||||
component: () =>
|
||||
import('#/views/demos/breadcrumb/lateral-detail.vue'),
|
||||
meta: {
|
||||
activePath: '/demos/breadcrumb/lateral',
|
||||
hideInMenu: true,
|
||||
title: $t('page.demos.breadcrumb.lateralDetail'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'BreadcrumbLevelDemo',
|
||||
path: '/demos/breadcrumb/level',
|
||||
meta: {
|
||||
icon: 'lucide:navigation',
|
||||
title: $t('page.demos.breadcrumb.level'),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'BreadcrumbLevelDetailDemo',
|
||||
path: '/demos/breadcrumb/level/detail',
|
||||
component: () =>
|
||||
import('#/views/demos/breadcrumb/level-detail.vue'),
|
||||
meta: {
|
||||
title: $t('page.demos.breadcrumb.levelDetail'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// 缺省页
|
||||
{
|
||||
meta: {
|
||||
icon: 'mdi:lightbulb-error-outline',
|
||||
title: $t('page.demos.fallback.title'),
|
||||
},
|
||||
name: 'FallbackDemos',
|
||||
path: '/demos/fallback',
|
||||
children: [
|
||||
{
|
||||
name: 'Fallback403Demo',
|
||||
path: '/demos/fallback/403',
|
||||
component: () => import('#/views/_core/fallback/forbidden.vue'),
|
||||
meta: {
|
||||
icon: 'mdi:do-not-disturb-alt',
|
||||
title: '403',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Fallback404Demo',
|
||||
path: '/demos/fallback/404',
|
||||
component: () => import('#/views/_core/fallback/not-found.vue'),
|
||||
meta: {
|
||||
icon: 'mdi:table-off',
|
||||
title: '404',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Fallback500Demo',
|
||||
path: '/demos/fallback/500',
|
||||
component: () =>
|
||||
import('#/views/_core/fallback/internal-error.vue'),
|
||||
meta: {
|
||||
icon: 'mdi:server-network-off',
|
||||
title: '500',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'FallbackOfflineDemo',
|
||||
path: '/demos/fallback/offline',
|
||||
component: () => import('#/views/_core/fallback/offline.vue'),
|
||||
meta: {
|
||||
icon: 'mdi:offline',
|
||||
title: $t('fallback.offline'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// 菜单徽标
|
||||
{
|
||||
meta: {
|
||||
badgeType: 'dot',
|
||||
badgeVariants: 'destructive',
|
||||
icon: 'lucide:circle-dot',
|
||||
title: $t('page.demos.badge.title'),
|
||||
},
|
||||
name: 'BadgeDemos',
|
||||
path: '/demos/badge',
|
||||
children: [
|
||||
{
|
||||
name: 'BadgeDotDemo',
|
||||
component: () => import('#/views/demos/badge/index.vue'),
|
||||
path: '/demos/badge/dot',
|
||||
meta: {
|
||||
badgeType: 'dot',
|
||||
icon: 'lucide:square-dot',
|
||||
title: $t('page.demos.badge.dot'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'BadgeTextDemo',
|
||||
component: () => import('#/views/demos/badge/index.vue'),
|
||||
path: '/demos/badge/text',
|
||||
meta: {
|
||||
badge: '10',
|
||||
icon: 'lucide:square-dot',
|
||||
title: $t('page.demos.badge.text'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'BadgeColorDemo',
|
||||
component: () => import('#/views/demos/badge/index.vue'),
|
||||
path: '/demos/badge/color',
|
||||
meta: {
|
||||
badge: 'Hot',
|
||||
badgeVariants: 'destructive',
|
||||
icon: 'lucide:square-dot',
|
||||
title: $t('page.demos.badge.color'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// 菜单激活图标
|
||||
{
|
||||
meta: {
|
||||
activeIcon: 'fluent-emoji:radioactive',
|
||||
icon: 'bi:radioactive',
|
||||
title: $t('page.demos.activeIcon.title'),
|
||||
},
|
||||
name: 'ActiveIconDemos',
|
||||
path: '/demos/active-icon',
|
||||
children: [
|
||||
{
|
||||
name: 'ActiveIconDemo',
|
||||
component: () => import('#/views/demos/active-icon/index.vue'),
|
||||
path: '/demos/active-icon/children',
|
||||
meta: {
|
||||
activeIcon: 'fluent-emoji:radioactive',
|
||||
icon: 'bi:radioactive',
|
||||
title: $t('page.demos.activeIcon.children'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// 外部链接
|
||||
{
|
||||
meta: {
|
||||
icon: 'ic:round-settings-input-composite',
|
||||
title: $t('page.demos.outside.title'),
|
||||
},
|
||||
name: 'OutsideDemos',
|
||||
path: '/demos/outside',
|
||||
children: [
|
||||
{
|
||||
name: 'IframeDemos',
|
||||
path: '/demos/outside/iframe',
|
||||
meta: {
|
||||
icon: 'mdi:newspaper-variant-outline',
|
||||
title: $t('page.demos.outside.embedded'),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'VueDocumentDemo',
|
||||
path: '/demos/outside/iframe/vue-document',
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'logos:vue',
|
||||
iframeSrc: 'https://cn.vuejs.org/',
|
||||
keepAlive: true,
|
||||
title: 'Vue',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'TailwindcssDemo',
|
||||
path: '/demos/outside/iframe/tailwindcss',
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'devicon:tailwindcss',
|
||||
iframeSrc: 'https://tailwindcss.com/',
|
||||
// keepAlive: true,
|
||||
title: 'Tailwindcss',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ExternalLinkDemos',
|
||||
path: '/demos/outside/external-link',
|
||||
meta: {
|
||||
icon: 'mdi:newspaper-variant-multiple-outline',
|
||||
title: $t('page.demos.outside.externalLink'),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'ViteDemo',
|
||||
path: '/demos/outside/external-link/vite',
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'logos:vitejs',
|
||||
link: 'https://vitejs.dev/',
|
||||
title: 'Vite',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'VueUseDemo',
|
||||
path: '/demos/outside/external-link/vue-use',
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'logos:vueuse',
|
||||
link: 'https://vueuse.org',
|
||||
title: 'VueUse',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// 嵌套菜单
|
||||
{
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
title: $t('page.demos.nested.title'),
|
||||
},
|
||||
name: 'NestedDemos',
|
||||
path: '/demos/nested',
|
||||
children: [
|
||||
{
|
||||
name: 'Menu1Demo',
|
||||
path: '/demos/nested/menu1',
|
||||
component: () => import('#/views/demos/nested/menu-1.vue'),
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
title: $t('page.demos.nested.menu1'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Menu2Demo',
|
||||
path: '/demos/nested/menu2',
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
title: $t('page.demos.nested.menu2'),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'Menu21Demo',
|
||||
path: '/demos/nested/menu2/menu2-1',
|
||||
component: () => import('#/views/demos/nested/menu-2-1.vue'),
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
title: $t('page.demos.nested.menu2_1'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Menu3Demo',
|
||||
path: '/demos/nested/menu3',
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
title: $t('page.demos.nested.menu3'),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'Menu31Demo',
|
||||
path: 'menu3-1',
|
||||
component: () => import('#/views/demos/nested/menu-3-1.vue'),
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
title: $t('page.demos.nested.menu3_1'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Menu32Demo',
|
||||
path: 'menu3-2',
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
title: $t('page.demos.nested.menu3_2'),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'Menu321Demo',
|
||||
path: '/demos/nested/menu3/menu3-2/menu3-2-1',
|
||||
component: () =>
|
||||
import('#/views/demos/nested/menu-3-2-1.vue'),
|
||||
meta: {
|
||||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
title: $t('page.demos.nested.menu3_2_1'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -18,7 +18,7 @@ const routes: RouteRecordRaw[] = [
|
||||
badgeType: 'dot',
|
||||
icon: VBEN_LOGO_URL,
|
||||
order: 9999,
|
||||
title: $t('demos.vben.title'),
|
||||
title: $t('page.vben.title'),
|
||||
},
|
||||
name: 'VbenProject',
|
||||
path: '/vben-admin',
|
||||
@@ -26,10 +26,10 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
name: 'VbenAbout',
|
||||
path: '/vben-admin/about',
|
||||
component: () => import('#/views/_core/about/index.vue'),
|
||||
component: () => import('#/views/_core/vben/about/index.vue'),
|
||||
meta: {
|
||||
icon: 'lucide:copyright',
|
||||
title: $t('demos.vben.about'),
|
||||
title: $t('page.vben.about'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -38,8 +38,9 @@ const routes: RouteRecordRaw[] = [
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'lucide:book-open-text',
|
||||
link: VBEN_DOC_URL,
|
||||
title: $t('demos.vben.document'),
|
||||
iframeSrc: VBEN_DOC_URL,
|
||||
keepAlive: true,
|
||||
title: $t('page.vben.document'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -58,9 +59,8 @@ const routes: RouteRecordRaw[] = [
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
badgeType: 'dot',
|
||||
icon: 'logos:naiveui',
|
||||
link: VBEN_NAIVE_PREVIEW_URL,
|
||||
title: $t('demos.vben.naive-ui'),
|
||||
title: $t('page.vben.naive-ui'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -69,9 +69,8 @@ const routes: RouteRecordRaw[] = [
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
badgeType: 'dot',
|
||||
icon: 'logos:element',
|
||||
link: VBEN_ELE_PREVIEW_URL,
|
||||
title: $t('demos.vben.element-plus'),
|
||||
title: $t('page.vben.element-plus'),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@@ -1,4 +1,5 @@
|
||||
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 { useRouter } from 'vue-router';
|
||||
@@ -9,7 +10,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
||||
import { getAccessCodesApi, getUserInfoApi, loginApi } from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
@@ -25,18 +26,20 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
* @param params 登录表单数据
|
||||
*/
|
||||
async function authLogin(
|
||||
params: Recordable<any>,
|
||||
params: LoginAndRegisterParams,
|
||||
onSuccess?: () => Promise<void> | void,
|
||||
) {
|
||||
// 异步处理用户登录操作并获取 accessToken
|
||||
let userInfo: null | UserInfo = null;
|
||||
try {
|
||||
loginLoading.value = true;
|
||||
const { accessToken } = await loginApi(params);
|
||||
const { accessToken, refreshToken } = await loginApi(params);
|
||||
|
||||
// 如果成功获取到 accessToken
|
||||
if (accessToken) {
|
||||
// 将 accessToken 存储到 accessStore 中
|
||||
accessStore.setAccessToken(accessToken);
|
||||
accessStore.setRefreshToken(refreshToken);
|
||||
|
||||
// 获取用户信息并存储到 accessStore 中
|
||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||
@@ -74,23 +77,16 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
};
|
||||
}
|
||||
|
||||
async function logout(redirect: boolean = true) {
|
||||
try {
|
||||
await logoutApi();
|
||||
} catch {
|
||||
// 不做任何处理
|
||||
}
|
||||
async function logout() {
|
||||
resetAllStores();
|
||||
accessStore.setLoginExpired(false);
|
||||
|
||||
// 回登录页带上当前路由地址
|
||||
// 回登陆页带上当前路由地址
|
||||
await router.replace({
|
||||
path: LOGIN_PATH,
|
||||
query: redirect
|
||||
? {
|
||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||
}
|
||||
: {},
|
||||
query: {
|
||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,56 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
import type { LoginCodeParams } from '@vben/common-ui';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { AuthenticationCodeLogin } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
|
||||
defineOptions({ name: 'CodeLogin' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.mobile'),
|
||||
},
|
||||
fieldName: 'phoneNumber',
|
||||
label: $t('authentication.mobile'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.mobileTip') })
|
||||
.refine((v) => /^\d{11}$/.test(v), {
|
||||
message: $t('authentication.mobileErrortip'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'VbenPinInput',
|
||||
componentProps: {
|
||||
createText: (countdown: number) => {
|
||||
const text =
|
||||
countdown > 0
|
||||
? $t('authentication.sendText', [countdown])
|
||||
: $t('authentication.sendCode');
|
||||
return text;
|
||||
},
|
||||
placeholder: $t('authentication.code'),
|
||||
},
|
||||
fieldName: 'code',
|
||||
label: $t('authentication.code'),
|
||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
||||
},
|
||||
];
|
||||
});
|
||||
/**
|
||||
* 异步处理登录操作
|
||||
* Asynchronously handle the login process
|
||||
* @param values 登录表单数据
|
||||
*/
|
||||
async function handleLogin(values: Recordable<any>) {
|
||||
async function handleLogin(values: LoginCodeParams) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(values);
|
||||
}
|
||||
@@ -58,8 +23,8 @@ async function handleLogin(values: Recordable<any>) {
|
||||
|
||||
<template>
|
||||
<AuthenticationCodeLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleLogin"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,34 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { AuthenticationForgetPassword } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
|
||||
defineOptions({ name: 'ForgetPassword' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: 'example@example.com',
|
||||
},
|
||||
fieldName: 'email',
|
||||
label: $t('authentication.email'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.emailTip') })
|
||||
.email($t('authentication.emailValidErrorTip')),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: Recordable<any>) {
|
||||
function handleSubmit(value: string) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('reset email:', value);
|
||||
}
|
||||
@@ -36,8 +16,8 @@ function handleSubmit(value: Recordable<any>) {
|
||||
|
||||
<template>
|
||||
<AuthenticationForgetPassword
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,98 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { BasicOption } from '@vben/types';
|
||||
|
||||
import { computed, markRaw } from 'vue';
|
||||
|
||||
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { AuthenticationLogin } from '@vben/common-ui';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
defineOptions({ name: 'Login' });
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const MOCK_USER_OPTIONS: BasicOption[] = [
|
||||
{
|
||||
label: 'Super',
|
||||
value: 'vben',
|
||||
},
|
||||
{
|
||||
label: 'Admin',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: 'User',
|
||||
value: 'jack',
|
||||
},
|
||||
];
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenSelect',
|
||||
componentProps: {
|
||||
options: MOCK_USER_OPTIONS,
|
||||
placeholder: $t('authentication.selectAccount'),
|
||||
},
|
||||
fieldName: 'selectAccount',
|
||||
label: $t('authentication.selectAccount'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.selectAccount') })
|
||||
.optional()
|
||||
.default('vben'),
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
dependencies: {
|
||||
trigger(values, form) {
|
||||
if (values.selectAccount) {
|
||||
const findUser = MOCK_USER_OPTIONS.find(
|
||||
(item) => item.value === values.selectAccount,
|
||||
);
|
||||
if (findUser) {
|
||||
form.setValues({
|
||||
password: '123456',
|
||||
username: findUser.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
triggerFields: ['selectAccount'],
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
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>
|
||||
|
||||
<template>
|
||||
<AuthenticationLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="authStore.loginLoading"
|
||||
password-placeholder="123456"
|
||||
username-placeholder="vben"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,87 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
||||
|
||||
import { computed, h, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { AuthenticationRegister, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { AuthenticationRegister } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
|
||||
defineOptions({ name: 'Register' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
passwordStrength: true,
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
renderComponentContent() {
|
||||
return {
|
||||
strengthText: () => $t('authentication.passwordStrength'),
|
||||
};
|
||||
},
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.confirmPassword'),
|
||||
},
|
||||
dependencies: {
|
||||
rules(values) {
|
||||
const { password } = values;
|
||||
return z
|
||||
.string({ required_error: $t('authentication.passwordTip') })
|
||||
.min(1, { message: $t('authentication.passwordTip') })
|
||||
.refine((value) => value === password, {
|
||||
message: $t('authentication.confirmPasswordTip'),
|
||||
});
|
||||
},
|
||||
triggerFields: ['password'],
|
||||
},
|
||||
fieldName: 'confirmPassword',
|
||||
label: $t('authentication.confirmPassword'),
|
||||
},
|
||||
{
|
||||
component: 'VbenCheckbox',
|
||||
fieldName: 'agreePolicy',
|
||||
renderComponentContent: () => ({
|
||||
default: () =>
|
||||
h('span', [
|
||||
$t('authentication.agree'),
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
class: 'vben-link ml-1 ',
|
||||
href: '',
|
||||
},
|
||||
`${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,
|
||||
),
|
||||
]),
|
||||
}),
|
||||
rules: z.boolean().refine((value) => !!value, {
|
||||
message: $t('authentication.agreeTip'),
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: Recordable<any>) {
|
||||
function handleSubmit(value: LoginAndRegisterParams) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('register submit:', value);
|
||||
}
|
||||
@@ -89,8 +18,8 @@ function handleSubmit(value: Recordable<any>) {
|
||||
|
||||
<template>
|
||||
<AuthenticationRegister
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,11 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
@@ -17,7 +13,7 @@ onMounted(() => {
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2 %',
|
||||
top: '2 %',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
@@ -55,27 +51,12 @@ onMounted(() => {
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
// xAxis: {
|
||||
// axisTick: {
|
||||
// show: false,
|
||||
// },
|
||||
// boundaryGap: false,
|
||||
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
// type: 'category',
|
||||
// },
|
||||
xAxis: {
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
boundaryGap: false,
|
||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 1,
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: [
|
||||
@@ -84,10 +65,7 @@ onMounted(() => {
|
||||
show: false,
|
||||
},
|
||||
max: 80_000,
|
||||
splitArea: {
|
||||
show: true,
|
||||
},
|
||||
splitNumber: 4,
|
||||
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
|
@@ -1,11 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
@@ -1,11 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
@@ -1,11 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
@@ -1,11 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
@@ -17,7 +13,7 @@ onMounted(() => {
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2 %',
|
||||
top: '2 %',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
|
@@ -7,7 +7,6 @@ import type {
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import {
|
||||
AnalysisChartCard,
|
||||
@@ -19,15 +18,11 @@ import {
|
||||
} from '@vben/common-ui';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
|
||||
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
|
||||
// 例如:url: /dashboard/workspace
|
||||
const projectItems: WorkbenchProjectItem[] = [
|
||||
{
|
||||
color: '',
|
||||
@@ -36,7 +31,6 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '开源组',
|
||||
icon: 'carbon:logo-github',
|
||||
title: 'Github',
|
||||
url: 'https://github.com',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
@@ -45,7 +39,6 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '算法组',
|
||||
icon: 'ion:logo-vue',
|
||||
title: 'Vue',
|
||||
url: 'https://vuejs.org',
|
||||
},
|
||||
{
|
||||
color: '#e18525',
|
||||
@@ -54,7 +47,6 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '上班摸鱼',
|
||||
icon: 'ion:logo-html5',
|
||||
title: 'Html5',
|
||||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
|
||||
},
|
||||
{
|
||||
color: '#bf0c2c',
|
||||
@@ -63,7 +55,6 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: 'UI',
|
||||
icon: 'ion:logo-angular',
|
||||
title: 'Angular',
|
||||
url: 'https://angular.io',
|
||||
},
|
||||
{
|
||||
color: '#00d8ff',
|
||||
@@ -72,7 +63,6 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '技术牛',
|
||||
icon: 'bx:bxl-react',
|
||||
title: 'React',
|
||||
url: 'https://reactjs.org',
|
||||
},
|
||||
{
|
||||
color: '#EBD94E',
|
||||
@@ -81,47 +71,39 @@ const projectItems: WorkbenchProjectItem[] = [
|
||||
group: '架构组',
|
||||
icon: 'ion:logo-javascript',
|
||||
title: 'Js',
|
||||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
|
||||
},
|
||||
];
|
||||
|
||||
// 同样,这里的 url 也可以使用以 http 开头的外部链接
|
||||
const quickNavItems: WorkbenchQuickNavItem[] = [
|
||||
{
|
||||
color: '#1fdaca',
|
||||
icon: 'ion:home-outline',
|
||||
title: '首页',
|
||||
url: '/',
|
||||
},
|
||||
{
|
||||
color: '#bf0c2c',
|
||||
icon: 'ion:grid-outline',
|
||||
title: '仪表盘',
|
||||
url: '/dashboard',
|
||||
},
|
||||
{
|
||||
color: '#e18525',
|
||||
icon: 'ion:layers-outline',
|
||||
title: '组件',
|
||||
url: '/demos/features/icons',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
icon: 'ion:settings-outline',
|
||||
title: '系统管理',
|
||||
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
|
||||
},
|
||||
{
|
||||
color: '#4daf1bc9',
|
||||
icon: 'ion:key-outline',
|
||||
title: '权限管理',
|
||||
url: '/demos/access/page-control',
|
||||
},
|
||||
{
|
||||
color: '#00d8ff',
|
||||
icon: 'ion:bar-chart-outline',
|
||||
title: '图表',
|
||||
url: '/analytics',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -213,24 +195,6 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -246,16 +210,11 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
|
||||
|
||||
<div class="mt-5 flex flex-col lg:flex-row">
|
||||
<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="最新动态" />
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
<WorkbenchQuickNav
|
||||
:items="quickNavItems"
|
||||
class="mt-5 lg:mt-0"
|
||||
title="快捷导航"
|
||||
@click="navTo"
|
||||
/>
|
||||
<WorkbenchQuickNav :items="quickNavItems" title="快捷导航" />
|
||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||
<AnalyticsVisitsSource />
|
||||
|
@@ -1,17 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Recordable } from '@vben/types';
|
||||
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { AccessControl, useAccess } from '@vben/access';
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { resetAllStores, useUserStore } from '@vben/stores';
|
||||
|
||||
import { Button, Card } from 'ant-design-vue';
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
const accounts: Record<string, Recordable<any>> = {
|
||||
const accounts: Record<string, LoginAndRegisterParams> = {
|
||||
admin: {
|
||||
password: '123456',
|
||||
username: 'admin',
|
||||
@@ -51,17 +50,21 @@ async function changeAccount(role: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page
|
||||
:title="`${accessMode === 'frontend' ? '前端' : '后端'}按钮访问权限演示`"
|
||||
description="切换不同的账号,观察按钮变化。"
|
||||
>
|
||||
<Card class="mb-5">
|
||||
<template #title>
|
||||
<span class="font-semibold">当前角色:</span>
|
||||
<div class="p-5">
|
||||
<div class="card-box p-5">
|
||||
<h1 class="text-xl font-semibold">
|
||||
{{ accessMode === 'frontend' ? '前端' : '后端' }}页面访问权限演示
|
||||
</h1>
|
||||
<div class="text-foreground/80 mt-2">切换不同的账号,观察按钮变化。</div>
|
||||
</div>
|
||||
|
||||
<div class="card-box mt-5 p-5">
|
||||
<div class="mb-3">
|
||||
<span class="text-lg font-semibold">当前角色:</span>
|
||||
<span class="text-primary mx-4 text-lg">
|
||||
{{ userStore.userRoles?.[0] }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<Button :type="roleButtonType('super')" @click="changeAccount('super')">
|
||||
切换为 Super 账号
|
||||
@@ -77,9 +80,10 @@ async function changeAccount(role: string) {
|
||||
<Button :type="roleButtonType('user')" @click="changeAccount('user')">
|
||||
切换为 User 账号
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="mb-5" title="组件形式控制 - 权限码">
|
||||
<div class="card-box mt-5 p-5">
|
||||
<div class="mb-3 text-lg font-semibold">组件形式控制 - 权限码方式</div>
|
||||
<AccessControl :codes="['AC_100100']" type="code">
|
||||
<Button class="mr-4"> Super 账号可见 ["AC_1000001"] </Button>
|
||||
</AccessControl>
|
||||
@@ -94,13 +98,10 @@ async function changeAccount(role: string) {
|
||||
Super & Admin 账号可见 ["AC_100100","AC_1000001"]
|
||||
</Button>
|
||||
</AccessControl>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card
|
||||
v-if="accessMode === 'frontend'"
|
||||
class="mb-5"
|
||||
title="组件形式控制 - 角色"
|
||||
>
|
||||
<div v-if="accessMode === 'frontend'" class="card-box mt-5 p-5">
|
||||
<div class="mb-3 text-lg font-semibold">组件形式控制 - 用户角色方式</div>
|
||||
<AccessControl :codes="['super']" type="role">
|
||||
<Button class="mr-4"> Super 角色可见 </Button>
|
||||
</AccessControl>
|
||||
@@ -113,9 +114,10 @@ async function changeAccount(role: string) {
|
||||
<AccessControl :codes="['super', 'admin']" type="role">
|
||||
<Button class="mr-4"> Super & Admin 角色可见 </Button>
|
||||
</AccessControl>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="mb-5" title="函数形式控制">
|
||||
<div class="card-box mt-5 p-5">
|
||||
<div class="mb-3 text-lg font-semibold">函数形式控制</div>
|
||||
<Button v-if="hasAccessByCodes(['AC_100100'])" class="mr-4">
|
||||
Super 账号可见 ["AC_1000001"]
|
||||
</Button>
|
||||
@@ -128,9 +130,10 @@ async function changeAccount(role: string) {
|
||||
<Button v-if="hasAccessByCodes(['AC_100100', 'AC_1000001'])" class="mr-4">
|
||||
Super & Admin 账号可见 ["AC_100100","AC_1000001"]
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="mb-5" title="指令方式 - 权限码">
|
||||
<div class="card-box mt-5 p-5">
|
||||
<div class="mb-3 text-lg font-semibold">指令方式 - 权限码</div>
|
||||
<Button class="mr-4" v-access:code="['AC_100100']">
|
||||
Super 账号可见 ["AC_1000001"]
|
||||
</Button>
|
||||
@@ -143,15 +146,16 @@ async function changeAccount(role: string) {
|
||||
<Button class="mr-4" v-access:code="['AC_100100', 'AC_1000001']">
|
||||
Super & Admin 账号可见 ["AC_100100","AC_1000001"]
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="mb-5" title="指令方式 - 角色">
|
||||
<div v-if="accessMode === 'frontend'" class="card-box mt-5 p-5">
|
||||
<div class="mb-3 text-lg font-semibold">指令方式 - 角色</div>
|
||||
<Button class="mr-4" v-access:role="['super']"> Super 角色可见 </Button>
|
||||
<Button class="mr-4" v-access:role="['admin']"> Admin 角色可见 </Button>
|
||||
<Button class="mr-4" v-access:role="['user']"> User 角色可见 </Button>
|
||||
<Button class="mr-4" v-access:role="['super', 'admin']">
|
||||
Super & Admin 角色可见
|
||||
</Button>
|
||||
</Card>
|
||||
</Page>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -1,17 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Recordable } from '@vben/types';
|
||||
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { resetAllStores, useUserStore } from '@vben/stores';
|
||||
|
||||
import { Button, Card } from 'ant-design-vue';
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
const accounts: Record<string, Recordable<any>> = {
|
||||
const accounts: Record<string, LoginAndRegisterParams> = {
|
||||
admin: {
|
||||
password: '123456',
|
||||
username: 'admin',
|
||||
@@ -65,20 +64,33 @@ async function handleToggleAccessMode() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page
|
||||
:title="`${accessMode === 'frontend' ? '前端' : '后端'}页面访问权限演示`"
|
||||
description="切换不同的账号,观察左侧菜单变化。"
|
||||
>
|
||||
<Card class="mb-5" title="权限模式">
|
||||
<span class="font-semibold">当前权限模式:</span>
|
||||
<div class="p-5">
|
||||
<div class="card-box p-5">
|
||||
<h1 class="text-xl font-semibold">
|
||||
{{ accessMode === 'frontend' ? '前端' : '后端' }}页面访问权限演示
|
||||
</h1>
|
||||
<div class="text-foreground/80 mt-2">
|
||||
切换不同的账号,观察左侧菜单变化。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-box mt-5 p-5">
|
||||
<span class="text-lg font-semibold">当前权限模式:</span>
|
||||
<span class="text-primary mx-4">{{
|
||||
accessMode === 'frontend' ? '前端权限控制' : '后端权限控制'
|
||||
}}</span>
|
||||
<Button type="primary" @click="handleToggleAccessMode">
|
||||
切换为{{ accessMode === 'frontend' ? '后端' : '前端' }}权限模式
|
||||
</Button>
|
||||
</Card>
|
||||
<Card title="账号切换">
|
||||
</div>
|
||||
<div class="card-box mt-5 p-5">
|
||||
<div class="mb-3">
|
||||
<span class="text-lg font-semibold">当前账号:</span>
|
||||
<span class="text-primary mx-4 text-lg">
|
||||
{{ userStore.userRoles?.[0] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Button :type="roleButtonType('super')" @click="changeAccount('super')">
|
||||
切换为 Super 账号
|
||||
</Button>
|
||||
@@ -93,6 +105,6 @@ async function handleToggleAccessMode() {
|
||||
<Button :type="roleButtonType('user')" @click="changeAccount('user')">
|
||||
切换为 User 账号
|
||||
</Button>
|
||||
</Card>
|
||||
</Page>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -4,7 +4,7 @@ import { Fallback } from '@vben/common-ui';
|
||||
|
||||
<template>
|
||||
<Fallback
|
||||
description="当前页面仅 User 账号可见"
|
||||
description="当前页面仅 User 可见"
|
||||
status="coming-soon"
|
||||
title="页面访问测试"
|
||||
/>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user