mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 00:26:20 +08:00
Compare commits
149 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1063b2268e | ||
![]() |
8404c12129 | ||
![]() |
a7d322019e | ||
![]() |
f23821ff46 | ||
![]() |
2b0aedbaba | ||
![]() |
071cc0dcec | ||
![]() |
7db7d6ec5f | ||
![]() |
b3e3e05990 | ||
![]() |
cc678a2b51 | ||
![]() |
7d2bcf476f | ||
![]() |
cfbe379ee4 | ||
![]() |
388e5b5cb3 | ||
![]() |
c1dfbc1ebf | ||
![]() |
05a52b0540 | ||
![]() |
a77cc00e9f | ||
![]() |
98da0672e7 | ||
![]() |
be3bcc1122 | ||
![]() |
88a7a9b1ee | ||
![]() |
3b2ed948f5 | ||
![]() |
08acaf05f1 | ||
![]() |
36e7ca19a1 | ||
![]() |
84816ef769 | ||
![]() |
1bd6c2e5f2 | ||
![]() |
c439d5601f | ||
![]() |
453a3a8f84 | ||
![]() |
c6b9a56b73 | ||
![]() |
6d24369272 | ||
![]() |
cbf601581d | ||
![]() |
d47d051b19 | ||
![]() |
3b5c935bb8 | ||
![]() |
8adb22847d | ||
![]() |
2ba28488a4 | ||
![]() |
d2f3a9d04f | ||
![]() |
dc5cfab319 | ||
![]() |
87cc8a154c | ||
![]() |
54a622ad05 | ||
![]() |
8a0b1e0c72 | ||
![]() |
fd7b3479b4 | ||
![]() |
577cc85851 | ||
![]() |
5ca746b021 | ||
![]() |
04f29614aa | ||
![]() |
df7757d001 | ||
![]() |
20a3868594 | ||
![]() |
edb55b1fc0 | ||
![]() |
137e2073cb | ||
![]() |
bc04286f1d | ||
![]() |
7fa4410eab | ||
![]() |
8230493651 | ||
![]() |
c27b97f933 | ||
![]() |
d5655f02e3 | ||
![]() |
e2732f334c | ||
![]() |
5b24661b78 | ||
![]() |
97e7bd6827 | ||
![]() |
08265262e1 | ||
![]() |
c3631ef555 | ||
![]() |
18627833e9 | ||
![]() |
cd652941cd | ||
![]() |
fab92ee7e1 | ||
![]() |
04c2b9d242 | ||
![]() |
2288827265 | ||
![]() |
fecf55139d | ||
![]() |
5962804f92 | ||
![]() |
4d7e6bba56 | ||
![]() |
33637584a8 | ||
![]() |
e7a4ab70d5 | ||
![]() |
1db87ff7ce | ||
![]() |
01d60336a6 | ||
![]() |
f8485e8861 | ||
![]() |
9120d20143 | ||
![]() |
5f41c51770 | ||
![]() |
3c17f4e9f8 | ||
![]() |
66808582ff | ||
![]() |
0faf7810b6 | ||
![]() |
8987067b5a | ||
![]() |
d71a20ad0a | ||
![]() |
eb280ffeb7 | ||
![]() |
debb32d353 | ||
![]() |
11551903f0 | ||
![]() |
187f946d2a | ||
![]() |
e2d9d08ced | ||
![]() |
30223f18db | ||
![]() |
9c6e059aac | ||
![]() |
8f40d5107c | ||
![]() |
83fcdec37c | ||
![]() |
b28740042b | ||
![]() |
6a1e6ad99f | ||
![]() |
3b0f72330a | ||
![]() |
963e79063f | ||
![]() |
4793c4c0db | ||
![]() |
1a4d61cc17 | ||
![]() |
738bc456c8 | ||
![]() |
e8dff517ba | ||
![]() |
843ec1e749 | ||
![]() |
f20c5d9e2e | ||
![]() |
6e6e35ae4a | ||
![]() |
bf021a0578 | ||
![]() |
7b46780af7 | ||
![]() |
3f9ce63868 | ||
![]() |
517acada1a | ||
![]() |
3015912f1a | ||
![]() |
b464b87ac5 | ||
![]() |
654bf90c0d | ||
![]() |
9d6cc22dfa | ||
![]() |
7a9ad7de63 | ||
![]() |
cdeadafda5 | ||
![]() |
ec49a04151 | ||
![]() |
4d4327cb25 | ||
![]() |
6c117b4130 | ||
![]() |
8725a01301 | ||
![]() |
ed14282999 | ||
![]() |
07cdebb7f1 | ||
![]() |
344c499462 | ||
![]() |
992b9bae6c | ||
![]() |
5b56c300ab | ||
![]() |
ca164b0f9b | ||
![]() |
9487156938 | ||
![]() |
f1e0278bd8 | ||
![]() |
de90b07b6f | ||
![]() |
3f04f6b01f | ||
![]() |
31dd0d5b40 | ||
![]() |
8ffc853b86 | ||
![]() |
a9a14fd81a | ||
![]() |
d41bd31fcd | ||
![]() |
5f4d3f2bce | ||
![]() |
a27b1c40e5 | ||
![]() |
1d38fb647e | ||
![]() |
861f39b519 | ||
![]() |
a629262c9d | ||
![]() |
08391ad53e | ||
![]() |
d9ba9917ff | ||
![]() |
279a3a4c21 | ||
![]() |
891fd803f7 | ||
![]() |
4bd4f7490b | ||
![]() |
84c8fb0ecc | ||
![]() |
e27be2d47a | ||
![]() |
9dce5b44ec | ||
![]() |
feda1484ab | ||
![]() |
f26b3eddb2 | ||
![]() |
72ed2d35b9 | ||
![]() |
93c619ea47 | ||
![]() |
0c18912f4e | ||
![]() |
6c002bb9c8 | ||
![]() |
4f5783d00b | ||
![]() |
e5ec8e6b51 | ||
![]() |
36e33ea48a | ||
![]() |
0d46a5471f | ||
![]() |
773b36aa17 | ||
![]() |
e29352d0f1 | ||
![]() |
7994d664fd |
4
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -13,8 +13,8 @@ body:
|
||||
label: Version
|
||||
description: What version of our software are you running?
|
||||
options:
|
||||
- Vben Admin Pro
|
||||
- Vben Admin
|
||||
- Vben Admin V5
|
||||
- Vben Admin V2
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
40
.github/actions/setup-node/action.yml
vendored
Normal file
40
.github/actions/setup-node/action.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: "Setup Node"
|
||||
|
||||
description: "Setup node and pnpm"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
if: ${{ github.ref_name == 'main' }}
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- uses: actions/cache/restore@v4
|
||||
if: ${{ github.ref_name != 'main' }}
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: pnpm install --frozen-lockfile
|
45
.github/contributing.md
vendored
45
.github/contributing.md
vendored
@@ -1,5 +1,42 @@
|
||||
# Contributing Guide
|
||||
# Vben Admin Contributing Guide
|
||||
|
||||
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.
|
||||
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
|
||||
```
|
||||
|
14
.github/release-drafter.yml
vendored
14
.github/release-drafter.yml
vendored
@@ -13,18 +13,21 @@ categories:
|
||||
- title: "🚀 Features"
|
||||
labels:
|
||||
- "feature"
|
||||
- "enhancement"
|
||||
- title: "🐞 Bug Fixes"
|
||||
labels:
|
||||
- "bug"
|
||||
- title: 📝 Documentation updates
|
||||
- title: "📈 Performance"
|
||||
labels:
|
||||
- "perf"
|
||||
- "enhancement"
|
||||
- title: 📝 Documentation
|
||||
labels:
|
||||
- "documentation"
|
||||
- title: 👻 Maintenance
|
||||
labels:
|
||||
- "chore"
|
||||
- "dependencies"
|
||||
collapse-after: 5
|
||||
# collapse-after: 12
|
||||
- title: 🚦 Tests
|
||||
labels:
|
||||
- "tests"
|
||||
@@ -34,12 +37,15 @@ categories:
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- "major"
|
||||
- "breaking"
|
||||
minor:
|
||||
labels:
|
||||
- "feature"
|
||||
- "minor"
|
||||
patch:
|
||||
labels:
|
||||
- "feature"
|
||||
- "patch"
|
||||
- "bug"
|
||||
- "maintenance"
|
||||
- "docs"
|
||||
|
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@@ -13,13 +13,14 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
post-update:
|
||||
# if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -28,21 +29,12 @@ jobs:
|
||||
|
||||
- name: Checkout out pull request
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh pr checkout ${{ github.event.pull_request.number }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
19
.github/workflows/changeset-version.yml
vendored
19
.github/workflows/changeset-version.yml
vendored
@@ -23,27 +23,14 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Create Release Pull Request
|
||||
uses: changesets/action@v1
|
||||
@@ -52,4 +39,4 @@ jobs:
|
||||
commit: "chore: bump versions"
|
||||
title: "chore: bump versions"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
63
.github/workflows/ci.yml
vendored
63
.github/workflows/ci.yml
vendored
@@ -21,10 +21,9 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
# - macos-latest
|
||||
- windows-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
@@ -38,27 +37,8 @@ jobs:
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Find pnpm store path
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
# - name: Check Git version
|
||||
# run: git --version
|
||||
@@ -80,11 +60,10 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
os:
|
||||
- ubuntu-latest
|
||||
# - macos-latest
|
||||
# - windows-latest
|
||||
- windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -92,51 +71,37 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
os:
|
||||
- ubuntu-latest
|
||||
# - macos-latest
|
||||
- windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm check:type
|
||||
|
||||
# From https://github.com/rhysd/actionlint/blob/main/docs/usage.md#use-actionlint-on-github-actions
|
||||
- name: Check workflow files
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
|
||||
./actionlint -color -shellcheck=""
|
||||
|
146
.github/workflows/deploy.yml
vendored
146
.github/workflows/deploy.yml
vendored
@@ -6,14 +6,65 @@ on:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy-push-ftp:
|
||||
name: Deploy Push Ftp
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && contains(github.event.head_commit.message, '[deploy]')
|
||||
deploy-playground-ftp:
|
||||
name: Deploy Push Playground Ftp
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
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]')
|
||||
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]')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -26,29 +77,14 @@ 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
|
||||
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
|
||||
sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-naive/.env.production
|
||||
sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-naive/.env.production
|
||||
cat ./apps/web-naive/.env.production
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
run: pnpm run build:antd
|
||||
|
||||
- name: Sync Web Antd files
|
||||
- name: Sync files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
@@ -56,15 +92,30 @@ jobs:
|
||||
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
|
||||
deploy-ele-ftp:
|
||||
name: Deploy Push Element Ftp
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}
|
||||
local-dir: ./apps/web-naive/dist/
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sync Web Ele files
|
||||
- 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 }}
|
||||
@@ -72,10 +123,33 @@ jobs:
|
||||
password: ${{ secrets.WEB_ELE_FTP_PASSWORD }}
|
||||
local-dir: ./apps/web-ele/dist/
|
||||
|
||||
- name: Sync Docs files
|
||||
deploy-naive-ftp:
|
||||
name: Deploy Push Naive Ftp
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sed Config Base
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-naive/.env.production
|
||||
sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-naive/.env.production
|
||||
cat ./apps/web-naive/.env.production
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build:naive
|
||||
|
||||
- name: Sync files
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
server: ${{ secrets.PRO_FTP_HOST }}
|
||||
username: ${{ secrets.WEBSITE_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEBSITE_FTP_PASSWORD }}
|
||||
local-dir: ./docs/.vitepress/dist/
|
||||
username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}
|
||||
password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}
|
||||
local-dir: ./apps/web-naive/dist/
|
||||
|
11
.github/workflows/draft.yml
vendored
11
.github/workflows/draft.yml
vendored
@@ -7,11 +7,18 @@ 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
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@master
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
8
.github/workflows/issue-close-require.yml
vendored
8
.github/workflows/issue-close-require.yml
vendored
@@ -6,6 +6,10 @@ on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -16,6 +20,6 @@ jobs:
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: "close-issues" # 执行动作:关闭 Issues
|
||||
token: ${{ secrets.ACCESS_TOKEN }} # GitHub Token,用于认证
|
||||
labels: "need reproduction" # 目标标签
|
||||
token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token,用于认证
|
||||
labels: "needs reproduction" # 目标标签
|
||||
inactive-day: 3 # 未活动天数阈值
|
||||
|
11
.github/workflows/issue-labeled.yml
vendored
11
.github/workflows/issue-labeled.yml
vendored
@@ -6,6 +6,11 @@ on:
|
||||
# pull_request:
|
||||
# types: [labeled]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
reply-labeled:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -15,7 +20,7 @@ jobs:
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: "remove-labels"
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
labels: "enhancement: pending triage"
|
||||
|
||||
@@ -24,7 +29,7 @@ jobs:
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: "remove-labels"
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
labels: "bug: pending triage"
|
||||
|
||||
@@ -33,7 +38,7 @@ jobs:
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: "create-comment, remove-labels"
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
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.
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ secrets.ACCESS_TOKEN }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-inactive-days: "30"
|
||||
issue-lock-reason: ""
|
||||
pr-inactive-days: "30"
|
||||
|
10
.github/workflows/release-tag.yml
vendored
10
.github/workflows/release-tag.yml
vendored
@@ -8,6 +8,10 @@ on:
|
||||
env:
|
||||
HUSKY: "0"
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Create Release
|
||||
@@ -53,12 +57,12 @@ jobs:
|
||||
echo "version=${version}" >> $GITHUB_OUTPUT
|
||||
echo "major=${major}" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: release-drafter/release-drafter@master
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
with:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
publish: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# - name: force update major tag
|
||||
# run: |
|
||||
@@ -69,7 +73,7 @@ jobs:
|
||||
# id: release_tag
|
||||
# uses: ncipollo/release-action@v1
|
||||
# with:
|
||||
# token: ${{ secrets.ACCESS_TOKEN }}
|
||||
# token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# generateReleaseNotes: "true"
|
||||
# body: |
|
||||
# > Please refer to [CHANGELOG.md](https://github.com/vbenjs/vue-vben-admin/blob/main/CHANGELOG.md) for details.
|
||||
|
16
.github/workflows/semantic-pull-request.yml
vendored
16
.github/workflows/semantic-pull-request.yml
vendored
@@ -22,5 +22,19 @@ jobs:
|
||||
didn't match the configured pattern. Please ensure that the subject
|
||||
doesn't start with an uppercase character.
|
||||
requireScope: false
|
||||
types: |
|
||||
fix
|
||||
feat
|
||||
docs
|
||||
style
|
||||
refactor
|
||||
perf
|
||||
test
|
||||
build
|
||||
ci
|
||||
chore
|
||||
revert
|
||||
types
|
||||
release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.ACCESS_TOKEN }}
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days"
|
||||
stale-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"
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -48,3 +48,4 @@ vite.config.ts.*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.history
|
||||
|
28
.ls-lint.yml
28
.ls-lint.yml
@@ -1,28 +0,0 @@
|
||||
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
|
@@ -1 +1 @@
|
||||
20
|
||||
20.14.0
|
||||
|
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
|
||||
|
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@@ -2,8 +2,6 @@
|
||||
"recommendations": [
|
||||
// Vue 3 的语言支持
|
||||
"Vue.volar",
|
||||
// 用于 TypeScript 服务器的 Vue 插件
|
||||
"Vue.vscode-typescript-vue-plugin",
|
||||
// 将 ESLint JavaScript 集成到 VS Code 中。
|
||||
"dbaeumer.vscode-eslint",
|
||||
// Visual Studio Code 的官方 Stylelint 扩展
|
||||
@@ -12,10 +10,6 @@
|
||||
"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 插件
|
||||
|
35
.vscode/launch.json
vendored
35
.vscode/launch.json
vendored
@@ -3,13 +3,40 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome", // chrome、edge
|
||||
"name": "vben admin dev", // 调试的名称,可以自定义
|
||||
"type": "chrome",
|
||||
"name": "vben admin playground dev",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5173",
|
||||
"url": "http://localhost:5555",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}/apps/web-antd/src"
|
||||
"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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
47
.vscode/settings.json
vendored
47
.vscode/settings.json
vendored
@@ -14,7 +14,6 @@
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.cursorBlinking": "expand",
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.largeFileOptimizations": false,
|
||||
"editor.accessibilitySupport": "off",
|
||||
"editor.cursorSmoothCaretAnimation": "on",
|
||||
@@ -37,7 +36,34 @@
|
||||
"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,
|
||||
|
||||
@@ -56,7 +82,8 @@
|
||||
"*.ejs": "html",
|
||||
"*.art": "html",
|
||||
"**/tsconfig.json": "jsonc",
|
||||
"*.json": "jsonc"
|
||||
"*.json": "jsonc",
|
||||
"package.json": "json"
|
||||
},
|
||||
|
||||
"files.exclude": {
|
||||
@@ -163,14 +190,14 @@
|
||||
"yaml": false
|
||||
},
|
||||
|
||||
"cssVariables.lookupFiles": [
|
||||
"packages/@vben-core/shared/design/src/**/*.css"
|
||||
],
|
||||
"cssVariables.lookupFiles": ["packages/core/base/design/src/**/*.css"],
|
||||
|
||||
"i18n-ally.localesPaths": [
|
||||
"packages/locales/src/langs",
|
||||
"playground/src/locales/langs",
|
||||
"apps/*/src/locales/langs"
|
||||
],
|
||||
"i18n-ally.pathMatcher": "{locale}.json",
|
||||
"i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"],
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
@@ -185,11 +212,13 @@
|
||||
"*.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",
|
||||
"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",
|
||||
"Dockerfile": "Dockerfile,.docker*,docker-entrypoint.sh,build-local-docker*,nginx.conf",
|
||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
|
||||
"tailwind.config.mjs": "postcss.*"
|
||||
},
|
||||
"commentTranslate.hover.enabled": false,
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"commentTranslate.multiLineMerge": true
|
||||
"commentTranslate.multiLineMerge": true,
|
||||
"vue.server.hybridMode": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
@@ -21,9 +21,9 @@ RUN echo "Builder Success 🎉"
|
||||
FROM nginx:stable-alpine as production
|
||||
|
||||
RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf
|
||||
COPY --from=builder /app/apps/web-antd/dist /usr/share/nginx/html
|
||||
COPY --from=builder /app/playground/dist /usr/share/nginx/html
|
||||
|
||||
COPY ./deploy/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY ./nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
|
149
README.ja-JP.md
Normal file
149
README.ja-JP.md
Normal file
@@ -0,0 +1,149 @@
|
||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp"> </a> <br> <br>
|
||||
|
||||
[](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)
|
||||
|
||||
## 寄付
|
||||
|
||||
このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます!
|
||||
|
||||

|
||||
|
||||
<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)
|
14
README.md
14
README.md
@@ -1,11 +1,13 @@
|
||||
<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>
|
||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp"> </a> <br> <br>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
<h1>Vue Vben Admin</h1>
|
||||
</div>
|
||||
|
||||
**English** | [中文](./README.zh-CN.md)
|
||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
||||
|
||||
**English** | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md)
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -61,7 +63,6 @@ cd vue-vben-admin
|
||||
corepack enable
|
||||
|
||||
pnpm install
|
||||
|
||||
```
|
||||
|
||||
- run
|
||||
@@ -78,7 +79,7 @@ pnpm build
|
||||
|
||||
## Change Log
|
||||
|
||||
[CHANGELOG](./CHANGELOG.zh_CN.md)
|
||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||
|
||||
## How to contribute
|
||||
|
||||
@@ -127,14 +128,15 @@ Support modern browsers, not IE
|
||||
|
||||
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: #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 src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" />
|
||||
<img alt="Contributors"
|
||||
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
|
||||
</a>
|
||||
|
||||
## Discord
|
||||
|
@@ -1,11 +1,13 @@
|
||||
<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>
|
||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp"> </a> <br> <br>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
<h1>Vue Vben Admin</h1>
|
||||
</div>
|
||||
|
||||
**中文** | [English](./README.md)
|
||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
||||
|
||||
**中文** | [English](./README.md) | [日本語](./README.ja-JP.md)
|
||||
|
||||
## 简介
|
||||
|
||||
@@ -61,7 +63,6 @@ cd vue-vben-admin
|
||||
corepack enable
|
||||
|
||||
pnpm install
|
||||
|
||||
```
|
||||
|
||||
- 运行
|
||||
@@ -123,10 +124,21 @@ pnpm build
|
||||
|
||||
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
|
||||
|
||||

|
||||

|
||||
|
||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
||||
|
||||
## 更新日志
|
||||
|
||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
||||
|
||||
## Contributor
|
||||
|
||||
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
|
||||
<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 +1,3 @@
|
||||
PORT=5320
|
||||
ACCESS_TOKEN_SECRET=access_token_secret
|
||||
REFRESH_TOKEN_SECRET=refresh_token_secret
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
## Description
|
||||
|
||||
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。由于 sqlite 安装需要在本地进行编译,所以这里接口是直接返回的。线上环境不再提供mock集成,可自行部署服务或者对接真实数据,同步 mock.js等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了 真是的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。
|
||||
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。
|
||||
|
||||
## Running the app
|
||||
|
||||
|
@@ -1,15 +1,14 @@
|
||||
export default eventHandler((event) => {
|
||||
const token = getHeader(event, 'Authorization');
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
if (!token) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||
|
||||
const codes =
|
||||
MOCK_CODES.find((item) => item.username === username)?.codes ?? [];
|
||||
MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? [];
|
||||
|
||||
return useResponseSuccess(codes);
|
||||
});
|
||||
|
@@ -1,20 +1,36 @@
|
||||
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) {
|
||||
setResponseStatus(event, 403);
|
||||
return useResponseError('UnauthorizedException', '用户名或密码错误');
|
||||
clearRefreshTokenCookie(event);
|
||||
return forbiddenResponse(event);
|
||||
}
|
||||
|
||||
const accessToken = Buffer.from(username).toString('base64');
|
||||
const accessToken = generateAccessToken(findUser);
|
||||
const refreshToken = generateRefreshToken(findUser);
|
||||
|
||||
setRefreshTokenCookie(event, refreshToken);
|
||||
|
||||
return useResponseSuccess({
|
||||
...findUser,
|
||||
accessToken,
|
||||
// TODO: refresh token
|
||||
refreshToken: accessToken,
|
||||
});
|
||||
});
|
||||
|
15
apps/backend-mock/api/auth/logout.post.ts
Normal file
15
apps/backend-mock/api/auth/logout.post.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
getRefreshTokenFromCookie,
|
||||
} from '~/utils/cookie-utils';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const refreshToken = getRefreshTokenFromCookie(event);
|
||||
if (!refreshToken) {
|
||||
return useResponseSuccess('');
|
||||
}
|
||||
|
||||
clearRefreshTokenCookie(event);
|
||||
|
||||
return useResponseSuccess('');
|
||||
});
|
33
apps/backend-mock/api/auth/refresh.post.ts
Normal file
33
apps/backend-mock/api/auth/refresh.post.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
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,14 +1,13 @@
|
||||
export default eventHandler((event) => {
|
||||
const token = getHeader(event, 'Authorization');
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
if (!token) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||
|
||||
const menus =
|
||||
MOCK_MENUS.find((item) => item.username === username)?.menus ?? [];
|
||||
MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? [];
|
||||
return useResponseSuccess(menus);
|
||||
});
|
||||
|
@@ -1,14 +1,11 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const token = getHeader(event, 'Authorization');
|
||||
if (!token) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
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);
|
||||
return useResponseSuccess(userinfo);
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { NitroErrorHandler } from 'nitropack';
|
||||
|
||||
const errorHandler: NitroErrorHandler = function (error, event) {
|
||||
event.res.end(`[error handler] ${error.stack}`);
|
||||
event.node.res.end(`[Error Handler] ${error.stack}`);
|
||||
};
|
||||
|
||||
export default errorHandler;
|
||||
|
@@ -1,11 +1,4 @@
|
||||
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.';
|
||||
|
@@ -6,10 +6,15 @@
|
||||
"license": "MIT",
|
||||
"author": "",
|
||||
"scripts": {
|
||||
"start": "nitro dev",
|
||||
"build": "nitro build"
|
||||
"build": "nitro build",
|
||||
"start": "nitro dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"nitropack": "^2.9.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"h3": "^1.12.0"
|
||||
}
|
||||
}
|
||||
|
26
apps/backend-mock/utils/cookie-utils.ts
Normal file
26
apps/backend-mock/utils/cookie-utils.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
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;
|
||||
}
|
59
apps/backend-mock/utils/jwt-utils.ts
Normal file
59
apps/backend-mock/utils/jwt-utils.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
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,4 +1,12 @@
|
||||
export const MOCK_USERS = [
|
||||
export interface UserInfo {
|
||||
id: number;
|
||||
password: string;
|
||||
realName: string;
|
||||
roles: string[];
|
||||
username: string;
|
||||
}
|
||||
|
||||
export const MOCK_USERS: UserInfo[] = [
|
||||
{
|
||||
id: 0,
|
||||
password: '123456',
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import type { EventHandlerRequest, H3Event } from 'h3';
|
||||
|
||||
export function useResponseSuccess<T = any>(data: T) {
|
||||
return {
|
||||
code: 0,
|
||||
@@ -15,3 +17,13 @@ export function useResponseError(message: string, error: any = null) {
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
export function forbiddenResponse(event: H3Event<EventHandlerRequest>) {
|
||||
setResponseStatus(event, 403);
|
||||
return useResponseError('ForbiddenException', 'Forbidden Exception');
|
||||
}
|
||||
|
||||
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# 应用标题
|
||||
VITE_APP_TITLE=Vben Admin
|
||||
VITE_APP_TITLE=Vben Admin Antd
|
||||
|
||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||
VITE_APP_NAMESPACE=vben-web-antd
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# 端口号
|
||||
VITE_PORT=5555
|
||||
VITE_PORT=5666
|
||||
|
||||
VITE_BASE=/
|
||||
|
||||
|
@@ -14,3 +14,6 @@ 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?d20a01273820422b6aa2ee41b6c9414d';
|
||||
'https://hm.baidu.com/hm.js?b38e689f40558f20a9a686d7f6f33edf';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-antd",
|
||||
"version": "5.0.0",
|
||||
"version": "5.2.1",
|
||||
"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": "^10.11.0",
|
||||
"@vueuse/core": "^11.0.3",
|
||||
"ant-design-vue": "^4.2.3",
|
||||
"dayjs": "^1.11.12",
|
||||
"pinia": "2.2.0",
|
||||
"vue": "^3.4.35",
|
||||
"vue-router": "^4.4.2"
|
||||
"dayjs": "^1.11.13",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "^3.4.38",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
import { baseRequestClient, requestClient } from '#/api/request';
|
||||
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
@@ -12,22 +12,44 @@ export namespace AuthApi {
|
||||
accessToken: string;
|
||||
desc: string;
|
||||
realName: string;
|
||||
refreshToken: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
data: string;
|
||||
status: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
export async function login(data: AuthApi.LoginParams) {
|
||||
export async function loginApi(data: AuthApi.LoginParams) {
|
||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新accessToken
|
||||
*/
|
||||
export async function refreshTokenApi() {
|
||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
export async function logoutApi() {
|
||||
return baseRequestClient.post('/auth/logout', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户权限码
|
||||
*/
|
||||
export async function getAccessCodes() {
|
||||
export async function getAccessCodesApi() {
|
||||
return requestClient.get<string[]>('/auth/codes');
|
||||
}
|
||||
|
@@ -5,6 +5,6 @@ import { requestClient } from '#/api/request';
|
||||
/**
|
||||
* 获取用户所有菜单
|
||||
*/
|
||||
export async function getAllMenus() {
|
||||
export async function getAllMenusApi() {
|
||||
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
|
||||
}
|
||||
|
@@ -5,6 +5,6 @@ import { requestClient } from '#/api/request';
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
export async function getUserInfo() {
|
||||
export async function getUserInfoApi() {
|
||||
return requestClient.get<UserInfo>('/user/info');
|
||||
}
|
||||
|
@@ -1,2 +1 @@
|
||||
export * from './core';
|
||||
export * from './demos';
|
||||
|
@@ -1,67 +1,104 @@
|
||||
/**
|
||||
* 该文件可自行根据业务逻辑进行调整
|
||||
*/
|
||||
import type { HttpResponse } from '@vben/request';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { RequestClient } from '@vben/request';
|
||||
import {
|
||||
authenticateResponseInterceptor,
|
||||
errorMessageResponseInterceptor,
|
||||
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);
|
||||
|
||||
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;
|
||||
/**
|
||||
* 重新认证逻辑
|
||||
*/
|
||||
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();
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新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;
|
||||
},
|
||||
});
|
||||
|
||||
// response数据解构
|
||||
client.addResponseInterceptor({
|
||||
fulfilled: (response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
const { code, data, message: msg } = responseData;
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return data;
|
||||
}
|
||||
throw new Error(`Error ${status}: ${msg}`);
|
||||
},
|
||||
});
|
||||
|
||||
// token过期的处理
|
||||
client.addResponseInterceptor(
|
||||
authenticateResponseInterceptor({
|
||||
client,
|
||||
doReAuthenticate,
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||
formatToken,
|
||||
}),
|
||||
);
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string) => message.error(msg)),
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export const requestClient = createRequestClient(apiURL);
|
||||
|
||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||
|
23
apps/web-antd/src/layouts/auth.vue
Normal file
23
apps/web-antd/src/layouts/auth.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<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,28 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
import type { NotificationItem } from '@vben/layouts';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||
import { LOGIN_PATH, VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||
import {
|
||||
BasicLayout,
|
||||
LockScreen,
|
||||
Notification,
|
||||
NotificationItem,
|
||||
UserDropdown,
|
||||
} from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import {
|
||||
resetAllStores,
|
||||
storeToRefs,
|
||||
useAccessStore,
|
||||
useUserStore,
|
||||
} from '@vben/stores';
|
||||
import { storeToRefs, useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
import { resetRoutes } from '#/router';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
const notifications = ref<NotificationItem[]>([
|
||||
@@ -99,12 +93,8 @@ const avatar = computed(() => {
|
||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
async function handleLogout() {
|
||||
resetAllStores();
|
||||
resetRoutes();
|
||||
await router.replace(LOGIN_PATH);
|
||||
await authStore.logout(false);
|
||||
}
|
||||
|
||||
function handleNoticeClear() {
|
||||
|
@@ -1,8 +1,6 @@
|
||||
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 };
|
||||
|
@@ -24,10 +24,10 @@ const localesMap = loadLocalesMap(modules);
|
||||
*/
|
||||
async function loadMessages(lang: SupportedLanguagesType) {
|
||||
const [appLocaleMessages] = await Promise.all([
|
||||
localesMap[lang](),
|
||||
localesMap[lang]?.(),
|
||||
loadThirdPartyMessage(lang),
|
||||
]);
|
||||
return appLocaleMessages.default;
|
||||
return appLocaleMessages?.default;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +58,11 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
locale = await import('dayjs/locale/en');
|
||||
}
|
||||
}
|
||||
dayjs.locale(locale);
|
||||
if (locale) {
|
||||
dayjs.locale(locale);
|
||||
} else {
|
||||
console.error(`Failed to load dayjs locale for ${lang}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,4 +91,4 @@ async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
export { $t, antdLocale, loadMessages, setupI18n };
|
||||
export { $t, antdLocale, setupI18n };
|
||||
|
@@ -2,58 +2,7 @@
|
||||
"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"
|
||||
}
|
||||
"antd": "Ant Design Vue"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,60 +2,7 @@
|
||||
"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": "平级模式详情"
|
||||
}
|
||||
"antd": "Ant Design Vue"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import { preferences } from '@vben/preferences';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { getAllMenus } from '#/api';
|
||||
import { getAllMenusApi } from '#/api';
|
||||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
@@ -29,7 +29,7 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
content: `${$t('common.loadingMenu')}...`,
|
||||
duration: 1.5,
|
||||
});
|
||||
return await getAllMenus();
|
||||
return await getAllMenusApi();
|
||||
},
|
||||
// 可以指定没有权限跳转403页面
|
||||
forbiddenComponent,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import type { Router } from 'vue-router';
|
||||
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { startProgress, stopProgress } from '@vben/utils';
|
||||
@@ -62,14 +62,20 @@ function setupAccessGuard(router: Router) {
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// 基本路由,这些路由不需要进入权限拦截
|
||||
if (coreRouteNames.includes(to.name as string)) {
|
||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
||||
return decodeURIComponent(
|
||||
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// accessToken 检查
|
||||
if (!accessStore.accessToken) {
|
||||
if (
|
||||
// 基本路由,这些路由不需要进入权限拦截
|
||||
coreRouteNames.includes(to.name as string) ||
|
||||
// 明确声明忽略权限访问权限,则可以访问
|
||||
to.meta.ignoreAccess
|
||||
) {
|
||||
// 明确声明忽略权限访问权限,则可以访问
|
||||
if (to.meta.ignoreAccess) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -86,10 +92,8 @@ function setupAccessGuard(router: Router) {
|
||||
return to;
|
||||
}
|
||||
|
||||
const accessRoutes = accessStore.accessRoutes;
|
||||
|
||||
// 是否已经生成过动态路由
|
||||
if (accessRoutes && accessRoutes.length > 0) {
|
||||
if (accessStore.isAccessChecked) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -109,10 +113,11 @@ function setupAccessGuard(router: Router) {
|
||||
// 保存菜单信息和路由信息
|
||||
accessStore.setAccessMenus(accessibleMenus);
|
||||
accessStore.setAccessRoutes(accessibleRoutes);
|
||||
const redirectPath = (from.query.redirect ?? to.path) as string;
|
||||
accessStore.setIsAccessChecked(true);
|
||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
||||
|
||||
return {
|
||||
path: decodeURIComponent(redirectPath),
|
||||
...router.resolve(decodeURIComponent(redirectPath)),
|
||||
replace: true,
|
||||
};
|
||||
});
|
||||
|
@@ -9,19 +9,19 @@ const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
|
||||
});
|
||||
|
||||
// 有需要可以自行打开注释,并创建文件夹
|
||||
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
|
||||
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
|
||||
|
||||
/** 动态路由 */
|
||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
||||
|
||||
/** 静态路由列表,访问这些页面可以不需要权限 */
|
||||
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
||||
const staticRoutes: RouteRecordRaw[] = [];
|
||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统 */
|
||||
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
||||
const externalRoutes: RouteRecordRaw[] = [];
|
||||
|
||||
/** 路由列表,由基本路由+静态路由组成 */
|
||||
const routes: RouteRecordRaw[] = [
|
||||
...coreRoutes,
|
||||
...staticRoutes,
|
||||
...externalRoutes,
|
||||
fallbackNotFoundRoute,
|
||||
];
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
import { BasicLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
@@ -15,477 +15,13 @@ const routes: RouteRecordRaw[] = [
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
children: [
|
||||
// 权限控制
|
||||
{
|
||||
meta: {
|
||||
icon: 'mdi:shield-key-outline',
|
||||
title: $t('page.demos.access.frontendPermissions'),
|
||||
title: $t('page.demos.antd'),
|
||||
},
|
||||
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'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
name: 'AntDesignDemos',
|
||||
path: '/demos/ant-design',
|
||||
component: () => import('#/views/demos/antd/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -26,7 +26,7 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
name: 'VbenAbout',
|
||||
path: '/vben-admin/about',
|
||||
component: () => import('#/views/_core/vben/about/index.vue'),
|
||||
component: () => import('#/views/_core/about/index.vue'),
|
||||
meta: {
|
||||
icon: 'lucide:copyright',
|
||||
title: $t('page.vben.about'),
|
||||
@@ -38,8 +38,7 @@ const routes: RouteRecordRaw[] = [
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'lucide:book-open-text',
|
||||
iframeSrc: VBEN_DOC_URL,
|
||||
keepAlive: true,
|
||||
link: VBEN_DOC_URL,
|
||||
title: $t('page.vben.document'),
|
||||
},
|
||||
},
|
||||
|
@@ -10,7 +10,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { getAccessCodes, getUserInfo, login } from '#/api';
|
||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
@@ -33,18 +33,16 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
let userInfo: null | UserInfo = null;
|
||||
try {
|
||||
loginLoading.value = true;
|
||||
const { accessToken, refreshToken } = await login(params);
|
||||
const { accessToken } = await loginApi(params);
|
||||
|
||||
// 如果成功获取到 accessToken
|
||||
if (accessToken) {
|
||||
// 将 accessToken 存储到 accessStore 中
|
||||
accessStore.setAccessToken(accessToken);
|
||||
accessStore.setRefreshToken(refreshToken);
|
||||
|
||||
// 获取用户信息并存储到 accessStore 中
|
||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||
fetchUserInfo(),
|
||||
getAccessCodes(),
|
||||
getAccessCodesApi(),
|
||||
]);
|
||||
|
||||
userInfo = fetchUserInfoResult;
|
||||
@@ -77,22 +75,25 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
};
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
async function logout(redirect: boolean = true) {
|
||||
await logoutApi();
|
||||
resetAllStores();
|
||||
accessStore.setLoginExpired(false);
|
||||
|
||||
// 回登陆页带上当前路由地址
|
||||
await router.replace({
|
||||
path: LOGIN_PATH,
|
||||
query: {
|
||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||
},
|
||||
query: redirect
|
||||
? {
|
||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||
}
|
||||
: {},
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchUserInfo() {
|
||||
let userInfo: null | UserInfo = null;
|
||||
userInfo = await getUserInfo();
|
||||
userInfo = await getUserInfoApi();
|
||||
userStore.setUserInfo(userInfo);
|
||||
return userInfo;
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
@@ -13,7 +17,7 @@ onMounted(() => {
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2 %',
|
||||
top: '2 %',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
@@ -51,12 +55,27 @@ 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: [
|
||||
@@ -65,7 +84,10 @@ onMounted(() => {
|
||||
show: false,
|
||||
},
|
||||
max: 80_000,
|
||||
|
||||
splitArea: {
|
||||
show: true,
|
||||
},
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
@@ -13,7 +17,7 @@ onMounted(() => {
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2 %',
|
||||
top: '2 %',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
|
@@ -214,7 +214,11 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
<WorkbenchQuickNav :items="quickNavItems" title="快捷导航" />
|
||||
<WorkbenchQuickNav
|
||||
:items="quickNavItems"
|
||||
class="mt-5 lg:mt-0"
|
||||
title="快捷导航"
|
||||
/>
|
||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||
<AnalyticsVisitsSource />
|
||||
|
66
apps/web-antd/src/views/demos/antd/index.vue
Normal file
66
apps/web-antd/src/views/demos/antd/index.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script lang="ts" setup>
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Button, Card, message, notification, Space } from 'ant-design-vue';
|
||||
|
||||
type NotificationType = 'error' | 'info' | 'success' | 'warning';
|
||||
|
||||
function info() {
|
||||
message.info('How many roads must a man walk down');
|
||||
}
|
||||
|
||||
function error() {
|
||||
message.error({
|
||||
content: 'Once upon a time you dressed so fine',
|
||||
duration: 2500,
|
||||
});
|
||||
}
|
||||
|
||||
function warning() {
|
||||
message.warning('How many roads must a man walk down');
|
||||
}
|
||||
function success() {
|
||||
message.success('Cause you walked hand in hand With another man in my place');
|
||||
}
|
||||
|
||||
function notify(type: NotificationType) {
|
||||
notification[type]({
|
||||
duration: 2500,
|
||||
message: '说点啥呢',
|
||||
type,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page
|
||||
description="支持多语言,主题功能集成切换等"
|
||||
title="Ant Design Vue组件使用演示"
|
||||
>
|
||||
<Card class="mb-5" title="按钮">
|
||||
<Space>
|
||||
<Button>Default</Button>
|
||||
<Button type="primary"> Primary </Button>
|
||||
<Button> Info </Button>
|
||||
<Button danger> Error </Button>
|
||||
</Space>
|
||||
</Card>
|
||||
<Card class="mb-5" title="Message">
|
||||
<Space>
|
||||
<Button @click="info"> 信息 </Button>
|
||||
<Button danger @click="error"> 错误 </Button>
|
||||
<Button @click="warning"> 警告 </Button>
|
||||
<Button @click="success"> 成功 </Button>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Card class="mb-5" title="Notification">
|
||||
<Space>
|
||||
<Button @click="notify('info')"> 信息 </Button>
|
||||
<Button danger @click="notify('error')"> 错误 </Button>
|
||||
<Button @click="notify('warning')"> 警告 </Button>
|
||||
<Button @click="notify('success')"> 成功 </Button>
|
||||
</Space>
|
||||
</Card>
|
||||
</Page>
|
||||
</template>
|
@@ -1,5 +1,5 @@
|
||||
# 端口号
|
||||
VITE_PORT=5666
|
||||
VITE_PORT=5777
|
||||
|
||||
VITE_BASE=/
|
||||
|
||||
|
@@ -14,3 +14,6 @@ VITE_ROUTER_HISTORY=hash
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# 打包后是否生成dist.zip
|
||||
VITE_ARCHIVER=true
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-ele",
|
||||
"version": "5.0.0",
|
||||
"version": "5.2.1",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -27,25 +27,25 @@
|
||||
},
|
||||
"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": "^10.11.0",
|
||||
"dayjs": "^1.11.12",
|
||||
"element-plus": "^2.7.8",
|
||||
"pinia": "2.2.0",
|
||||
"vue": "^3.4.35",
|
||||
"vue-router": "^4.4.2"
|
||||
"@vueuse/core": "^11.0.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"element-plus": "^2.8.1",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "^3.4.38",
|
||||
"vue-router": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"unplugin-element-plus": "^0.8.0"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
import { baseRequestClient, requestClient } from '#/api/request';
|
||||
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
@@ -12,22 +12,44 @@ export namespace AuthApi {
|
||||
accessToken: string;
|
||||
desc: string;
|
||||
realName: string;
|
||||
refreshToken: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
data: string;
|
||||
status: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
export async function login(data: AuthApi.LoginParams) {
|
||||
export async function loginApi(data: AuthApi.LoginParams) {
|
||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新accessToken
|
||||
*/
|
||||
export async function refreshTokenApi() {
|
||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
export async function logoutApi() {
|
||||
return baseRequestClient.post('/auth/logout', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户权限码
|
||||
*/
|
||||
export async function getAccessCodes() {
|
||||
export async function getAccessCodesApi() {
|
||||
return requestClient.get<string[]>('/auth/codes');
|
||||
}
|
||||
|
@@ -5,6 +5,6 @@ import { requestClient } from '#/api/request';
|
||||
/**
|
||||
* 获取用户所有菜单
|
||||
*/
|
||||
export async function getAllMenus() {
|
||||
export async function getAllMenusApi() {
|
||||
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
|
||||
}
|
||||
|
@@ -5,6 +5,6 @@ import { requestClient } from '#/api/request';
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
export async function getUserInfo() {
|
||||
export async function getUserInfoApi() {
|
||||
return requestClient.get<UserInfo>('/user/info');
|
||||
}
|
||||
|
@@ -1,67 +1,104 @@
|
||||
/**
|
||||
* 该文件可自行根据业务逻辑进行调整
|
||||
*/
|
||||
import type { HttpResponse } from '@vben/request';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { RequestClient } from '@vben/request';
|
||||
import {
|
||||
authenticateResponseInterceptor,
|
||||
errorMessageResponseInterceptor,
|
||||
RequestClient,
|
||||
} from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
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);
|
||||
|
||||
if (preferences.app.loginExpiredMode === 'modal') {
|
||||
accessStore.setLoginExpired(true);
|
||||
} else {
|
||||
// 退出登录
|
||||
await authStore.logout();
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
makeErrorMessage: (msg) => ElMessage.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;
|
||||
/**
|
||||
* 重新认证逻辑
|
||||
*/
|
||||
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();
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新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;
|
||||
},
|
||||
});
|
||||
|
||||
// response数据解构
|
||||
client.addResponseInterceptor({
|
||||
fulfilled: (response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
const { code, data, message: msg } = responseData;
|
||||
if (status >= 200 && status < 400 && code === 0) {
|
||||
return data;
|
||||
}
|
||||
throw new Error(`Error ${status}: ${msg}`);
|
||||
},
|
||||
});
|
||||
|
||||
// token过期的处理
|
||||
client.addResponseInterceptor(
|
||||
authenticateResponseInterceptor({
|
||||
client,
|
||||
doReAuthenticate,
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||
formatToken,
|
||||
}),
|
||||
);
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string) => ElMessage.error(msg)),
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export const requestClient = createRequestClient(apiURL);
|
||||
|
||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||
|
@@ -3,6 +3,7 @@ import { createApp } from 'vue';
|
||||
import { registerAccessDirective } from '@vben/access';
|
||||
import { initStores } from '@vben/stores';
|
||||
import '@vben/styles';
|
||||
import '@vben/styles/ele';
|
||||
|
||||
import { setupI18n } from '#/locales';
|
||||
|
||||
|
23
apps/web-ele/src/layouts/auth.vue
Normal file
23
apps/web-ele/src/layouts/auth.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<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,28 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
import type { NotificationItem } from '@vben/layouts';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||
import { LOGIN_PATH, VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||
import {
|
||||
BasicLayout,
|
||||
LockScreen,
|
||||
Notification,
|
||||
NotificationItem,
|
||||
UserDropdown,
|
||||
} from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import {
|
||||
resetAllStores,
|
||||
storeToRefs,
|
||||
useAccessStore,
|
||||
useUserStore,
|
||||
} from '@vben/stores';
|
||||
import { storeToRefs, useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
import { resetRoutes } from '#/router';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
const notifications = ref<NotificationItem[]>([
|
||||
@@ -99,12 +93,8 @@ const avatar = computed(() => {
|
||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
async function handleLogout() {
|
||||
resetAllStores();
|
||||
resetRoutes();
|
||||
await router.replace(LOGIN_PATH);
|
||||
await authStore.logout(false);
|
||||
}
|
||||
|
||||
function handleNoticeClear() {
|
||||
|
@@ -1,8 +1,6 @@
|
||||
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 };
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
||||
import type { Language } from 'element-plus/es/locale';
|
||||
|
||||
import type { App } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
@@ -7,7 +8,6 @@ import { $t, setupI18n as coreSetup, loadLocalesMap } from '@vben/locales';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { Language } from 'element-plus/es/locale';
|
||||
import enLocale from 'element-plus/es/locale/lang/en';
|
||||
import defaultLocale from 'element-plus/es/locale/lang/zh-cn';
|
||||
|
||||
@@ -24,10 +24,10 @@ const localesMap = loadLocalesMap(modules);
|
||||
*/
|
||||
async function loadMessages(lang: SupportedLanguagesType) {
|
||||
const [appLocaleMessages] = await Promise.all([
|
||||
localesMap[lang](),
|
||||
localesMap[lang]?.(),
|
||||
loadThirdPartyMessage(lang),
|
||||
]);
|
||||
return appLocaleMessages.default;
|
||||
return appLocaleMessages?.default;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +58,11 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
locale = await import('dayjs/locale/en');
|
||||
}
|
||||
}
|
||||
dayjs.locale(locale);
|
||||
if (locale) {
|
||||
dayjs.locale(locale);
|
||||
} else {
|
||||
console.error(`Failed to load dayjs locale for ${lang}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,4 +91,4 @@ async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
export { $t, elementLocale, loadMessages, setupI18n };
|
||||
export { $t, elementLocale, setupI18n };
|
||||
|
@@ -8,7 +8,7 @@ import { preferences } from '@vben/preferences';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { getAllMenus } from '#/api';
|
||||
import { getAllMenusApi } from '#/api';
|
||||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
@@ -29,7 +29,7 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
duration: 1500,
|
||||
message: `${$t('common.loadingMenu')}...`,
|
||||
});
|
||||
return await getAllMenus();
|
||||
return await getAllMenusApi();
|
||||
},
|
||||
// 可以指定没有权限跳转403页面
|
||||
forbiddenComponent,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import type { Router } from 'vue-router';
|
||||
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { startProgress, stopProgress } from '@vben/utils';
|
||||
@@ -62,14 +62,20 @@ function setupAccessGuard(router: Router) {
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// 基本路由,这些路由不需要进入权限拦截
|
||||
if (coreRouteNames.includes(to.name as string)) {
|
||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
||||
return decodeURIComponent(
|
||||
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// accessToken 检查
|
||||
if (!accessStore.accessToken) {
|
||||
if (
|
||||
// 基本路由,这些路由不需要进入权限拦截
|
||||
coreRouteNames.includes(to.name as string) ||
|
||||
// 明确声明忽略权限访问权限,则可以访问
|
||||
to.meta.ignoreAccess
|
||||
) {
|
||||
// 明确声明忽略权限访问权限,则可以访问
|
||||
if (to.meta.ignoreAccess) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -86,10 +92,8 @@ function setupAccessGuard(router: Router) {
|
||||
return to;
|
||||
}
|
||||
|
||||
const accessRoutes = accessStore.accessRoutes;
|
||||
|
||||
// 是否已经生成过动态路由
|
||||
if (accessRoutes && accessRoutes.length > 0) {
|
||||
if (accessStore.isAccessChecked) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -109,10 +113,11 @@ function setupAccessGuard(router: Router) {
|
||||
// 保存菜单信息和路由信息
|
||||
accessStore.setAccessMenus(accessibleMenus);
|
||||
accessStore.setAccessRoutes(accessibleRoutes);
|
||||
const redirectPath = (from.query.redirect ?? to.path) as string;
|
||||
accessStore.setIsAccessChecked(true);
|
||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
||||
|
||||
return {
|
||||
path: decodeURIComponent(redirectPath),
|
||||
...router.resolve(decodeURIComponent(redirectPath)),
|
||||
replace: true,
|
||||
};
|
||||
});
|
||||
|
@@ -9,19 +9,19 @@ const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
|
||||
});
|
||||
|
||||
// 有需要可以自行打开注释,并创建文件夹
|
||||
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
|
||||
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
|
||||
|
||||
/** 动态路由 */
|
||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
||||
|
||||
/** 静态路由列表,访问这些页面可以不需要权限 */
|
||||
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
||||
const staticRoutes: RouteRecordRaw[] = [];
|
||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统 */
|
||||
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
||||
const externalRoutes: RouteRecordRaw[] = [];
|
||||
|
||||
/** 路由列表,由基本路由+静态路由组成 */
|
||||
const routes: RouteRecordRaw[] = [
|
||||
...coreRoutes,
|
||||
...staticRoutes,
|
||||
...externalRoutes,
|
||||
fallbackNotFoundRoute,
|
||||
];
|
||||
|
||||
|
@@ -17,7 +17,6 @@ const routes: RouteRecordRaw[] = [
|
||||
children: [
|
||||
{
|
||||
meta: {
|
||||
icon: 'mdi:shield-key-outline',
|
||||
title: $t('page.demos.element-plus'),
|
||||
},
|
||||
name: 'NaiveDemos',
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import {
|
||||
VBEN_ANT_PREVIEW_URL,
|
||||
VBEN_DOC_URL,
|
||||
VBEN_GITHUB_URL,
|
||||
VBEN_LOGO_URL,
|
||||
VBEN_NAIVE_PREVIEW_URL,
|
||||
VBEN_PREVIEW_URL,
|
||||
} from '@vben/constants';
|
||||
|
||||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
@@ -26,7 +26,7 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
name: 'VbenAbout',
|
||||
path: '/vben-admin/about',
|
||||
component: () => import('#/views/_core/vben/about/index.vue'),
|
||||
component: () => import('#/views/_core/about/index.vue'),
|
||||
meta: {
|
||||
icon: 'lucide:copyright',
|
||||
title: $t('page.vben.about'),
|
||||
@@ -38,8 +38,7 @@ const routes: RouteRecordRaw[] = [
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'lucide:book-open-text',
|
||||
iframeSrc: VBEN_DOC_URL,
|
||||
keepAlive: true,
|
||||
link: VBEN_DOC_URL,
|
||||
title: $t('page.vben.document'),
|
||||
},
|
||||
},
|
||||
@@ -69,7 +68,7 @@ const routes: RouteRecordRaw[] = [
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
badgeType: 'dot',
|
||||
link: VBEN_PREVIEW_URL,
|
||||
link: VBEN_ANT_PREVIEW_URL,
|
||||
title: $t('page.vben.antdv'),
|
||||
},
|
||||
},
|
||||
|
@@ -10,7 +10,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { getAccessCodes, getUserInfo, login } from '#/api';
|
||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
@@ -33,18 +33,17 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
let userInfo: null | UserInfo = null;
|
||||
try {
|
||||
loginLoading.value = true;
|
||||
const { accessToken, refreshToken } = await login(params);
|
||||
const { accessToken } = await loginApi(params);
|
||||
|
||||
// 如果成功获取到 accessToken
|
||||
if (accessToken) {
|
||||
// 将 accessToken 存储到 accessStore 中
|
||||
accessStore.setAccessToken(accessToken);
|
||||
accessStore.setRefreshToken(refreshToken);
|
||||
|
||||
// 获取用户信息并存储到 accessStore 中
|
||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||
fetchUserInfo(),
|
||||
getAccessCodes(),
|
||||
getAccessCodesApi(),
|
||||
]);
|
||||
|
||||
userInfo = fetchUserInfoResult;
|
||||
@@ -77,22 +76,25 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
};
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
async function logout(redirect: boolean = true) {
|
||||
await logoutApi();
|
||||
resetAllStores();
|
||||
accessStore.setLoginExpired(false);
|
||||
|
||||
// 回登陆页带上当前路由地址
|
||||
await router.replace({
|
||||
path: LOGIN_PATH,
|
||||
query: {
|
||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||
},
|
||||
query: redirect
|
||||
? {
|
||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||
}
|
||||
: {},
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchUserInfo() {
|
||||
let userInfo: null | UserInfo = null;
|
||||
userInfo = await getUserInfo();
|
||||
userInfo = await getUserInfoApi();
|
||||
userStore.setUserInfo(userInfo);
|
||||
return userInfo;
|
||||
}
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
@@ -13,7 +17,7 @@ onMounted(() => {
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2 %',
|
||||
top: '2 %',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
@@ -51,12 +55,27 @@ 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: [
|
||||
@@ -65,7 +84,10 @@ onMounted(() => {
|
||||
show: false,
|
||||
},
|
||||
max: 80_000,
|
||||
|
||||
splitArea: {
|
||||
show: true,
|
||||
},
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
import {
|
||||
EchartsUI,
|
||||
type EchartsUIType,
|
||||
useEcharts,
|
||||
} from '@vben/plugins/echarts';
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
@@ -13,7 +17,7 @@ onMounted(() => {
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2 %',
|
||||
top: '2 %',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
|
@@ -214,7 +214,11 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
<WorkbenchQuickNav :items="quickNavItems" title="快捷导航" />
|
||||
<WorkbenchQuickNav
|
||||
:items="quickNavItems"
|
||||
class="mt-5 lg:mt-0"
|
||||
title="快捷导航"
|
||||
/>
|
||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||
<AnalyticsVisitsSource />
|
||||
|
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElCard,
|
||||
@@ -39,58 +41,38 @@ function notify(type: NotificationType) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-5">
|
||||
<div class="card-box p-5">
|
||||
<h1 class="text-xl font-semibold">Element Plus组件使用演示</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>
|
||||
</div>
|
||||
<div>
|
||||
<ElSpace>
|
||||
<ElButton>Default</ElButton>
|
||||
<ElButton type="primary"> Primary </ElButton>
|
||||
<ElButton type="info"> Info </ElButton>
|
||||
<ElButton type="success"> Success </ElButton>
|
||||
<ElButton type="warning"> Warning </ElButton>
|
||||
<ElButton type="danger"> Error </ElButton>
|
||||
</ElSpace>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-box mt-5 p-5">
|
||||
<div class="mb-3">
|
||||
<span class="text-lg font-semibold">卡片</span>
|
||||
</div>
|
||||
<div>
|
||||
<ElCard title="卡片"> 卡片内容 </ElCard>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-box mt-5 p-5">
|
||||
<div class="mb-3">
|
||||
<span class="text-lg font-semibold">信息 Message </span>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<Page
|
||||
description="支持多语言,主题功能集成切换等"
|
||||
title="Element Plus组件使用演示"
|
||||
>
|
||||
<ElCard class="mb-5">
|
||||
<template #header> 按钮 </template>
|
||||
<ElSpace>
|
||||
<ElButton>Default</ElButton>
|
||||
<ElButton type="primary"> Primary </ElButton>
|
||||
<ElButton type="info"> Info </ElButton>
|
||||
<ElButton type="success"> Success </ElButton>
|
||||
<ElButton type="warning"> Warning </ElButton>
|
||||
<ElButton type="danger"> Error </ElButton>
|
||||
</ElSpace>
|
||||
</ElCard>
|
||||
<ElCard class="mb-5">
|
||||
<template #header> Message </template>
|
||||
<ElSpace>
|
||||
<ElButton type="info" @click="info"> 信息 </ElButton>
|
||||
<ElButton type="danger" @click="error"> 错误 </ElButton>
|
||||
<ElButton type="warning" @click="warning"> 警告 </ElButton>
|
||||
<ElButton type="success" @click="success"> 成功 </ElButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-box mt-5 p-5">
|
||||
<div class="mb-3">
|
||||
<span class="text-lg font-semibold">通知 Notification </span>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
</ElSpace>
|
||||
</ElCard>
|
||||
<ElCard class="mb-5">
|
||||
<template #header> Notification </template>
|
||||
<ElSpace>
|
||||
<ElButton type="info" @click="notify('info')"> 信息 </ElButton>
|
||||
<ElButton type="danger" @click="notify('error')"> 错误 </ElButton>
|
||||
<ElButton type="warning" @click="notify('warning')"> 警告 </ElButton>
|
||||
<ElButton type="success" @click="notify('success')"> 成功 </ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ElSpace>
|
||||
</ElCard>
|
||||
</Page>
|
||||
</template>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# 端口号
|
||||
VITE_PORT=5777
|
||||
VITE_PORT=5888
|
||||
|
||||
VITE_BASE=/
|
||||
|
||||
|
@@ -14,3 +14,6 @@ VITE_ROUTER_HISTORY=hash
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# 打包后是否生成dist.zip
|
||||
VITE_ARCHIVER=true
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-naive",
|
||||
"version": "5.0.0",
|
||||
"version": "5.2.1",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -27,23 +27,23 @@
|
||||
},
|
||||
"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": "^10.11.0",
|
||||
"@vueuse/core": "^11.0.3",
|
||||
"naive-ui": "^2.39.0",
|
||||
"pinia": "2.2.0",
|
||||
"vue": "^3.4.35",
|
||||
"vue-router": "^4.4.2"
|
||||
"pinia": "2.2.2",
|
||||
"vue": "^3.4.38",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user