Compare commits
No commits in common. "main" and "v5.1.0" have entirely different histories.
|
|
@ -3,5 +3,3 @@ node_modules
|
||||||
.gitignore
|
.gitignore
|
||||||
*.md
|
*.md
|
||||||
dist
|
dist
|
||||||
.turbo
|
|
||||||
dist.zip
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: 🐞 Bug Report
|
name: 🐞 Bug Report
|
||||||
description: Report an issue with Vben Admin to help us make it better.
|
description: Report an issue with Vben Admin to help us make it better.
|
||||||
title: 'Bug: '
|
title: "Bug: "
|
||||||
labels: ['bug: pending triage']
|
labels: ["bug: pending triage"]
|
||||||
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
name: 📚 Documentation
|
name: 📚 Documentation
|
||||||
description: Report an issue with Vben Admin Website to help us make it better.
|
description: Report an issue with Vben Admin Website to help us make it better.
|
||||||
title: 'Docs: '
|
title: "Docs: "
|
||||||
labels: [documentation]
|
labels: [documentation]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: ✨ New Feature Proposal
|
name: ✨ New Feature Proposal
|
||||||
description: Propose a new feature to be added to Vben Admin
|
description: Propose a new feature to be added to Vben Admin
|
||||||
title: 'FEATURE: '
|
title: "FEATURE: "
|
||||||
labels: ['enhancement: pending triage']
|
labels: ["enhancement: pending triage"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
name: 'Setup Node'
|
name: "Setup Node"
|
||||||
|
|
||||||
description: 'Setup node and pnpm'
|
description: "Setup node and pnpm"
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: 'composite'
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|
@ -12,7 +12,7 @@ runs:
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: 'pnpm'
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: '/'
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
groups:
|
groups:
|
||||||
|
|
@ -9,7 +9,7 @@ updates:
|
||||||
update-types: [minor, patch]
|
update-types: [minor, patch]
|
||||||
|
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: '/'
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
groups:
|
groups:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name-template: 'v$RESOLVED_VERSION'
|
name-template: "v$RESOLVED_VERSION"
|
||||||
tag-template: 'v$RESOLVED_VERSION'
|
tag-template: "v$RESOLVED_VERSION"
|
||||||
version-template: $MAJOR.$MINOR.$PATCH
|
version-template: $MAJOR.$MINOR.$PATCH
|
||||||
change-template: '* $TITLE (#$NUMBER) @$AUTHOR'
|
change-template: "* $TITLE (#$NUMBER) @$AUTHOR"
|
||||||
template: |
|
template: |
|
||||||
# What's Changed
|
# What's Changed
|
||||||
|
|
||||||
|
|
@ -10,52 +10,52 @@ template: |
|
||||||
**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
|
**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
|
||||||
|
|
||||||
categories:
|
categories:
|
||||||
- title: '🚀 Features'
|
- title: "🚀 Features"
|
||||||
labels:
|
labels:
|
||||||
- 'feature'
|
- "feature"
|
||||||
- title: '🐞 Bug Fixes'
|
- "enhancement"
|
||||||
|
- title: "🐞 Bug Fixes"
|
||||||
labels:
|
labels:
|
||||||
- 'bug'
|
- "bug"
|
||||||
- title: '📈 Performance'
|
- title: "📈 Performance"
|
||||||
labels:
|
labels:
|
||||||
- 'perf'
|
- "perf"
|
||||||
- 'enhancement'
|
|
||||||
- title: 📝 Documentation
|
- title: 📝 Documentation
|
||||||
labels:
|
labels:
|
||||||
- 'documentation'
|
- "documentation"
|
||||||
- title: 👻 Maintenance
|
- title: 👻 Maintenance
|
||||||
labels:
|
labels:
|
||||||
- 'chore'
|
- "chore"
|
||||||
- 'dependencies'
|
- "dependencies"
|
||||||
# collapse-after: 12
|
# collapse-after: 12
|
||||||
- title: 🚦 Tests
|
- title: 🚦 Tests
|
||||||
labels:
|
labels:
|
||||||
- 'tests'
|
- "tests"
|
||||||
- title: 'Breaking'
|
- title: "Breaking"
|
||||||
label: 'breaking'
|
label: "breaking"
|
||||||
|
|
||||||
version-resolver:
|
version-resolver:
|
||||||
major:
|
major:
|
||||||
labels:
|
labels:
|
||||||
- 'major'
|
- "major"
|
||||||
- 'breaking'
|
- "breaking"
|
||||||
minor:
|
minor:
|
||||||
labels:
|
labels:
|
||||||
- 'minor'
|
- "minor"
|
||||||
|
- "feature"
|
||||||
patch:
|
patch:
|
||||||
labels:
|
labels:
|
||||||
- 'feature'
|
- "patch"
|
||||||
- 'patch'
|
- "bug"
|
||||||
- 'bug'
|
- "maintenance"
|
||||||
- 'maintenance'
|
- "docs"
|
||||||
- 'docs'
|
- "dependencies"
|
||||||
- 'dependencies'
|
- "security"
|
||||||
- 'security'
|
|
||||||
|
|
||||||
exclude-labels:
|
exclude-labels:
|
||||||
- 'skip-changelog'
|
- "skip-changelog"
|
||||||
- 'no-changelog'
|
- "no-changelog"
|
||||||
- 'changelog'
|
- "changelog"
|
||||||
- 'bump versions'
|
- "bump versions"
|
||||||
- 'reverted'
|
- "reverted"
|
||||||
- 'invalid'
|
- "invalid"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ on:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
env:
|
env:
|
||||||
HUSKY: '0'
|
HUSKY: "0"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||||
|
|
@ -19,15 +19,8 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
post-update:
|
post-update:
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
# if: ${{ github.actor == 'dependabot[bot]' }}
|
# if: ${{ github.actor == 'dependabot[bot]' }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os:
|
|
||||||
- ubuntu-latest
|
|
||||||
# - macos-latest
|
|
||||||
- windows-latest
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
version:
|
version:
|
||||||
if: (github.event.pull_request.merged || github.event_name == 'workflow_dispatch') && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
if: (github.event.pull_request.merged || github.event_name == 'workflow_dispatch') && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||||
# if: github.repository == 'vbenjs/vue-vben-admin'
|
# if: github.repository == 'vbenjs/vue-vben-admin'
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
uses: changesets/action@v1
|
uses: changesets/action@v1
|
||||||
with:
|
with:
|
||||||
version: pnpm run version
|
version: pnpm run version
|
||||||
commit: 'chore: bump versions'
|
commit: "chore: bump versions"
|
||||||
title: 'chore: bump versions'
|
title: "chore: bump versions"
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- 'releases/*'
|
- "releases/*"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
@ -17,13 +17,13 @@ env:
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
# - macos-latest
|
- macos-latest
|
||||||
- windows-latest
|
- windows-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -56,13 +56,13 @@ jobs:
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
# - macos-latest
|
- macos-latest
|
||||||
- windows-latest
|
- windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -79,14 +79,13 @@ jobs:
|
||||||
|
|
||||||
check:
|
check:
|
||||||
name: Check
|
name: Check
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
# - macos-latest
|
- macos-latest
|
||||||
- windows-latest
|
- windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -109,8 +108,8 @@ jobs:
|
||||||
|
|
||||||
ci-ok:
|
ci-ok:
|
||||||
name: CI OK
|
name: CI OK
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && always()
|
||||||
needs: [test, check, lint]
|
needs: [test, check, lint]
|
||||||
env:
|
env:
|
||||||
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,19 @@
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
# supported CodeQL languages.
|
# supported CodeQL languages.
|
||||||
#
|
#
|
||||||
name: 'CodeQL'
|
name: "CodeQL"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ['main']
|
branches: ["main"]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '35 0 * * 0'
|
- cron: "35 0 * * 0"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
name: Analyze (${{ matrix.language }})
|
name: Analyze (${{ matrix.language }})
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||||
# - https://gh.io/supported-runners-and-hardware-resources
|
# - https://gh.io/supported-runners-and-hardware-resources
|
||||||
|
|
@ -91,4 +90,4 @@ jobs:
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v3
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: "/language:${{matrix.language}}"
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ on:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-playground-ftp:
|
deploy-push-playground-ftp:
|
||||||
name: Deploy Push Playground Ftp
|
name: Deploy Push Playground Ftp
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -27,7 +27,7 @@ jobs:
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm build:play
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Sync Playground files
|
- name: Sync Playground files
|
||||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||||
|
|
@ -37,22 +37,6 @@ jobs:
|
||||||
password: ${{ secrets.WEB_PLAYGROUND_FTP_PWSSWORD }}
|
password: ${{ secrets.WEB_PLAYGROUND_FTP_PWSSWORD }}
|
||||||
local-dir: ./playground/dist/
|
local-dir: ./playground/dist/
|
||||||
|
|
||||||
deploy-docs-ftp:
|
|
||||||
name: Deploy Push Docs Ftp
|
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: ./.github/actions/setup-node
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm build:docs
|
|
||||||
|
|
||||||
- name: Sync Docs files
|
- name: Sync Docs files
|
||||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||||
with:
|
with:
|
||||||
|
|
@ -61,9 +45,9 @@ jobs:
|
||||||
password: ${{ secrets.WEBSITE_FTP_PASSWORD }}
|
password: ${{ secrets.WEBSITE_FTP_PASSWORD }}
|
||||||
local-dir: ./docs/.vitepress/dist/
|
local-dir: ./docs/.vitepress/dist/
|
||||||
|
|
||||||
deploy-antd-ftp:
|
deploy-push-antd-ftp:
|
||||||
name: Deploy Push Antd Ftp
|
name: Deploy Push Antd Ftp
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -82,7 +66,7 @@ jobs:
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm run build:antd
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Sync files
|
- name: Sync files
|
||||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||||
|
|
@ -92,9 +76,9 @@ jobs:
|
||||||
password: ${{ secrets.WEB_ANTD_FTP_PASSWORD }}
|
password: ${{ secrets.WEB_ANTD_FTP_PASSWORD }}
|
||||||
local-dir: ./apps/web-antd/dist/
|
local-dir: ./apps/web-antd/dist/
|
||||||
|
|
||||||
deploy-ele-ftp:
|
deploy-push-ele-ftp:
|
||||||
name: Deploy Push Element Ftp
|
name: Deploy Push Element Ftp
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -113,7 +97,7 @@ jobs:
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm run build:ele
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Sync files
|
- name: Sync files
|
||||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||||
|
|
@ -123,9 +107,9 @@ jobs:
|
||||||
password: ${{ secrets.WEB_ELE_FTP_PASSWORD }}
|
password: ${{ secrets.WEB_ELE_FTP_PASSWORD }}
|
||||||
local-dir: ./apps/web-ele/dist/
|
local-dir: ./apps/web-ele/dist/
|
||||||
|
|
||||||
deploy-naive-ftp:
|
deploy-push-naive-ftp:
|
||||||
name: Deploy Push Naive Ftp
|
name: Deploy Push Naive Ftp
|
||||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && github.repository == 'vbenjs/vue-vben-admin'
|
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|
@ -144,7 +128,7 @@ jobs:
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm run build:naive
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Sync files
|
- name: Sync files
|
||||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ jobs:
|
||||||
# write permission is required for autolabeler
|
# write permission is required for autolabeler
|
||||||
# otherwise, read permission is required at least
|
# otherwise, read permission is required at least
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: release-drafter/release-drafter@v6
|
- uses: release-drafter/release-drafter@v6
|
||||||
|
|
|
||||||
|
|
@ -3,29 +3,23 @@ name: Issue Close Require
|
||||||
|
|
||||||
# 触发条件:每天零点
|
# 触发条件:每天零点
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * *'
|
- cron: "0 0 * * *"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
contents: write
|
contents: write
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
close-issues:
|
close-issues:
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# 关闭未活动的 Issues
|
# 步骤1:关闭未活动的 Issues
|
||||||
- name: Close Inactive Issues
|
- name: Close Inactive Issues
|
||||||
uses: actions/stale@v9
|
uses: actions-cool/issues-helper@v3
|
||||||
with:
|
with:
|
||||||
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
|
actions: "close-issues" # 执行动作:关闭 Issues
|
||||||
stale-issue-label: needs-reproduction # Label that flags an issue as stale.
|
token: ${{ secrets.GITHUB_TOKEN }} # GitHub Token,用于认证
|
||||||
only-labels: needs-reproduction # Only process these issues
|
labels: "need reproduction" # 目标标签
|
||||||
days-before-issue-close: 3
|
inactive-day: 3 # 未活动天数阈值
|
||||||
ignore-updates: true
|
|
||||||
remove-stale-when-updated: false
|
|
||||||
close-issue-message: This issue was closed because it was open for 3 days without a valid reproduction.
|
|
||||||
close-issue-label: closed-by-action
|
|
||||||
|
|
|
||||||
|
|
@ -13,34 +13,33 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
reply-labeled:
|
reply-labeled:
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: remove enhancement pending
|
- name: remove enhancement pending
|
||||||
if: github.event.label.name == 'enhancement'
|
if: github.event.label.name == 'enhancement'
|
||||||
uses: actions-cool/issues-helper@v3
|
uses: actions-cool/issues-helper@v3
|
||||||
with:
|
with:
|
||||||
actions: 'remove-labels'
|
actions: "remove-labels"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
labels: 'enhancement: pending triage'
|
labels: "enhancement: pending triage"
|
||||||
|
|
||||||
- name: remove bug pending
|
- name: remove bug pending
|
||||||
if: github.event.label.name == 'bug'
|
if: github.event.label.name == 'bug'
|
||||||
uses: actions-cool/issues-helper@v3
|
uses: actions-cool/issues-helper@v3
|
||||||
with:
|
with:
|
||||||
actions: 'remove-labels'
|
actions: "remove-labels"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
labels: 'bug: pending triage'
|
labels: "bug: pending triage"
|
||||||
|
|
||||||
- name: needs reproduction
|
- name: needs reproduction
|
||||||
if: github.event.label.name == 'needs reproduction'
|
if: github.event.label.name == 'needs reproduction'
|
||||||
uses: actions-cool/issues-helper@v3
|
uses: actions-cool/issues-helper@v3
|
||||||
with:
|
with:
|
||||||
actions: 'create-comment, remove-labels'
|
actions: "create-comment, remove-labels"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
body: |
|
body: |
|
||||||
Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `needs reproduction` will be closed if no activities in 3 days.
|
Hello @${{ github.event.issue.user.login }}. Please provide the complete reproduction steps and code. Issues labeled by `needs reproduction` will be closed if no activities in 3 days.
|
||||||
labels: 'bug: pending triage'
|
labels: "bug: pending triage"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ name: Lock Threads
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * *'
|
- cron: "0 0 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|
@ -11,14 +11,13 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
action:
|
action:
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v5
|
- uses: dessant/lock-threads@v5
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-inactive-days: '14'
|
issue-inactive-days: "30"
|
||||||
issue-lock-reason: ''
|
issue-lock-reason: ""
|
||||||
pr-inactive-days: '30'
|
pr-inactive-days: "30"
|
||||||
pr-lock-reason: ''
|
pr-lock-reason: ""
|
||||||
process-only: 'issues, prs'
|
process-only: "issues, prs"
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ name: Create Release Tag
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
- "v*.*.*" # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||||
|
|
||||||
env:
|
env:
|
||||||
HUSKY: '0'
|
HUSKY: "0"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
@ -15,7 +15,6 @@ permissions:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Create Release
|
name: Create Release
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,8 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
name: Semantic Pull Request
|
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
name: Semantic Pull Request
|
||||||
steps:
|
steps:
|
||||||
- name: Validate PR title
|
- name: Validate PR title
|
||||||
uses: amannn/action-semantic-pull-request@v5
|
uses: amannn/action-semantic-pull-request@v5
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
name: 'Close stale issues'
|
name: "Close stale issues"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 1 * * *'
|
- cron: "0 1 * * *"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
if: github.repository == 'vbenjs/vue-vben-admin'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
|
stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days"
|
||||||
stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
|
stale-pr-message: "This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days"
|
||||||
exempt-issue-labels: 'bug,enhancement'
|
exempt-issue-labels: "bug,enhancement"
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
days-before-close: 7
|
days-before-close: 7
|
||||||
|
|
|
||||||
|
|
@ -48,4 +48,3 @@ vite.config.ts.*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
.history
|
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@ ports:
|
||||||
onOpen: open-preview
|
onOpen: open-preview
|
||||||
tasks:
|
tasks:
|
||||||
- init: corepack enable && pnpm install
|
- init: corepack enable && pnpm install
|
||||||
command: pnpm run dev:play
|
command: pnpm run dev
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
ls:
|
||||||
|
.js: kebab-case | pointcase
|
||||||
|
.vue: kebab-case | pointcase
|
||||||
|
.ts: kebab-case | pointcase
|
||||||
|
.tsx: kebab-case | pointcase
|
||||||
|
.jsx: kebab-case | pointcase
|
||||||
|
.css: kebab-case | pointcase
|
||||||
|
.d.ts: kebab-case | pointcase
|
||||||
|
# shadcn 自动生成文件为 PascalCase 格式
|
||||||
|
packages/@core/ui-kit/shadcn-ui/src/components/ui:
|
||||||
|
.vue: PascalCase
|
||||||
|
|
||||||
|
ignore:
|
||||||
|
- "**/*.png"
|
||||||
|
- "**/*.jpg"
|
||||||
|
- "**/*.jpeg"
|
||||||
|
- "**/*.jpeg"
|
||||||
|
- "**/*.gif"
|
||||||
|
- "**/_util.ts"
|
||||||
|
- "**/deps/**"
|
||||||
|
- "**/dist/**"
|
||||||
|
- "**/node_modules/**"
|
||||||
|
- "**/.turbo/**"
|
||||||
|
- .git
|
||||||
|
- .vscode
|
||||||
|
- .idea
|
||||||
|
- node_modules
|
||||||
|
- .cache
|
||||||
2
.npmrc
2
.npmrc
|
|
@ -1,4 +1,4 @@
|
||||||
registry = "https://registry.npmmirror.com"
|
# registry = "https://registry.npmmirror.com"
|
||||||
public-hoist-pattern[]=husky
|
public-hoist-pattern[]=husky
|
||||||
public-hoist-pattern[]=eslint
|
public-hoist-pattern[]=eslint
|
||||||
public-hoist-pattern[]=prettier
|
public-hoist-pattern[]=prettier
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"url": "http://localhost:5555",
|
"url": "http://localhost:5555",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/playground/src"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
"url": "http://localhost:5666",
|
"url": "http://localhost:5666",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/apps/web-antd/src"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
"url": "http://localhost:5777",
|
"url": "http://localhost:5777",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/apps/web-ele/src"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
"url": "http://localhost:5888",
|
"url": "http://localhost:5888",
|
||||||
"env": { "NODE_ENV": "development" },
|
"env": { "NODE_ENV": "development" },
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}/apps/web-naive/src"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
"editor.detectIndentation": false,
|
"editor.detectIndentation": false,
|
||||||
"editor.cursorBlinking": "expand",
|
"editor.cursorBlinking": "expand",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.largeFileOptimizations": false,
|
"editor.largeFileOptimizations": false,
|
||||||
"editor.accessibilitySupport": "off",
|
"editor.accessibilitySupport": "off",
|
||||||
"editor.cursorSmoothCaretAnimation": "on",
|
"editor.cursorSmoothCaretAnimation": "on",
|
||||||
|
|
@ -36,34 +37,7 @@
|
||||||
"source.fixAll.stylelint": "explicit",
|
"source.fixAll.stylelint": "explicit",
|
||||||
"source.organizeImports": "never"
|
"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
|
||||||
"extensions.ignoreRecommendations": true,
|
"extensions.ignoreRecommendations": true,
|
||||||
|
|
||||||
|
|
@ -82,8 +56,7 @@
|
||||||
"*.ejs": "html",
|
"*.ejs": "html",
|
||||||
"*.art": "html",
|
"*.art": "html",
|
||||||
"**/tsconfig.json": "jsonc",
|
"**/tsconfig.json": "jsonc",
|
||||||
"*.json": "jsonc",
|
"*.json": "jsonc"
|
||||||
"package.json": "json"
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
|
|
@ -194,10 +167,9 @@
|
||||||
|
|
||||||
"i18n-ally.localesPaths": [
|
"i18n-ally.localesPaths": [
|
||||||
"packages/locales/src/langs",
|
"packages/locales/src/langs",
|
||||||
"playground/src/locales/langs",
|
"playground/src/langs",
|
||||||
"apps/*/src/locales/langs"
|
"apps/*/src/locales/langs"
|
||||||
],
|
],
|
||||||
"i18n-ally.pathMatcher": "{locale}.json",
|
|
||||||
"i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"],
|
"i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"],
|
||||||
"i18n-ally.sourceLanguage": "en",
|
"i18n-ally.sourceLanguage": "en",
|
||||||
"i18n-ally.displayLanguage": "zh-CN",
|
"i18n-ally.displayLanguage": "zh-CN",
|
||||||
|
|
@ -212,12 +184,14 @@
|
||||||
"*.env": "$(capture).env.*",
|
"*.env": "$(capture).env.*",
|
||||||
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
||||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
|
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
|
||||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
|
"Dockerfile": "Dockerfile,.docker*,docker-entrypoint.sh,build-local-docker*,nginx.conf",
|
||||||
|
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,.ls-lint*,cspell.json",
|
||||||
"tailwind.config.mjs": "postcss.*"
|
"tailwind.config.mjs": "postcss.*"
|
||||||
},
|
},
|
||||||
"commentTranslate.hover.enabled": false,
|
"commentTranslate.hover.enabled": false,
|
||||||
"i18n-ally.keystyle": "nested",
|
"i18n-ally.keystyle": "nested",
|
||||||
"commentTranslate.multiLineMerge": true,
|
"commentTranslate.multiLineMerge": true,
|
||||||
"vue.server.hybridMode": true,
|
"vue.server.hybridMode": true,
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"vitest.disableWorkspaceWarning": true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> </a> <br> <br>
|
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp"> </a> <br> <br>
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
<h1>Vue Vben Admin</h1>
|
<h1>Vue Vben Admin</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)    
|
|
||||||
|
|
||||||
**日本語** | [English](./README.md) | [中文](./README.zh-CN.md)
|
**日本語** | [English](./README.md) | [中文](./README.zh-CN.md)
|
||||||
|
|
||||||
## 紹介
|
## 紹介
|
||||||
|
|
@ -80,7 +78,7 @@ pnpm build
|
||||||
|
|
||||||
## 変更ログ
|
## 変更ログ
|
||||||
|
|
||||||
[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases)
|
[CHANGELOG](./CHANGELOG.zh_CN.md)
|
||||||
|
|
||||||
## 貢献方法
|
## 貢献方法
|
||||||
|
|
||||||
|
|
@ -125,15 +123,11 @@ pnpm build
|
||||||
|
|
||||||
[@Vben](https://github.com/anncwb)
|
[@Vben](https://github.com/anncwb)
|
||||||
|
|
||||||
## スター歴史
|
|
||||||
|
|
||||||
[](https://star-history.com/#vbenjs/vue-vben-admin&Date)
|
|
||||||
|
|
||||||
## 寄付
|
## 寄付
|
||||||
|
|
||||||
このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます!
|
このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます!
|
||||||
|
|
||||||

|

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

|

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

|

|
||||||
|
|
||||||
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
|
<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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1 @@
|
||||||
PORT=5320
|
PORT=5320
|
||||||
ACCESS_TOKEN_SECRET=access_token_secret
|
|
||||||
REFRESH_TOKEN_SECRET=refresh_token_secret
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。
|
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供mock集成,可自行部署服务或者对接真实数据,mock.js 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。
|
||||||
|
|
||||||
## Running the app
|
## Running the app
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
|
||||||
import { unAuthorizedResponse } from '~/utils/response';
|
|
||||||
|
|
||||||
export default eventHandler((event) => {
|
export default eventHandler((event) => {
|
||||||
const userinfo = verifyAccessToken(event);
|
const token = getHeader(event, 'Authorization');
|
||||||
if (!userinfo) {
|
|
||||||
return unAuthorizedResponse(event);
|
if (!token) {
|
||||||
|
setResponseStatus(event, 401);
|
||||||
|
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||||
|
|
||||||
const codes =
|
const codes =
|
||||||
MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? [];
|
MOCK_CODES.find((item) => item.username === username)?.codes ?? [];
|
||||||
|
|
||||||
return useResponseSuccess(codes);
|
return useResponseSuccess(codes);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,20 @@
|
||||||
import {
|
|
||||||
clearRefreshTokenCookie,
|
|
||||||
setRefreshTokenCookie,
|
|
||||||
} from '~/utils/cookie-utils';
|
|
||||||
import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils';
|
|
||||||
import { forbiddenResponse } from '~/utils/response';
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const { password, username } = await readBody(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(
|
const findUser = MOCK_USERS.find(
|
||||||
(item) => item.username === username && item.password === password,
|
(item) => item.username === username && item.password === password,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!findUser) {
|
if (!findUser) {
|
||||||
clearRefreshTokenCookie(event);
|
setResponseStatus(event, 403);
|
||||||
return forbiddenResponse(event);
|
return useResponseError('UnauthorizedException', '用户名或密码错误');
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessToken = generateAccessToken(findUser);
|
const accessToken = Buffer.from(username).toString('base64');
|
||||||
const refreshToken = generateRefreshToken(findUser);
|
|
||||||
|
|
||||||
setRefreshTokenCookie(event, refreshToken);
|
|
||||||
|
|
||||||
return useResponseSuccess({
|
return useResponseSuccess({
|
||||||
...findUser,
|
|
||||||
accessToken,
|
accessToken,
|
||||||
|
// TODO: refresh token
|
||||||
|
refreshToken: accessToken,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import {
|
|
||||||
clearRefreshTokenCookie,
|
|
||||||
getRefreshTokenFromCookie,
|
|
||||||
} from '~/utils/cookie-utils';
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
const refreshToken = getRefreshTokenFromCookie(event);
|
|
||||||
if (!refreshToken) {
|
|
||||||
return useResponseSuccess('');
|
|
||||||
}
|
|
||||||
|
|
||||||
clearRefreshTokenCookie(event);
|
|
||||||
|
|
||||||
return useResponseSuccess('');
|
|
||||||
});
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import {
|
|
||||||
clearRefreshTokenCookie,
|
|
||||||
getRefreshTokenFromCookie,
|
|
||||||
setRefreshTokenCookie,
|
|
||||||
} from '~/utils/cookie-utils';
|
|
||||||
import { verifyRefreshToken } from '~/utils/jwt-utils';
|
|
||||||
import { forbiddenResponse } from '~/utils/response';
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
const refreshToken = getRefreshTokenFromCookie(event);
|
|
||||||
if (!refreshToken) {
|
|
||||||
return forbiddenResponse(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearRefreshTokenCookie(event);
|
|
||||||
|
|
||||||
const userinfo = verifyRefreshToken(refreshToken);
|
|
||||||
if (!userinfo) {
|
|
||||||
return forbiddenResponse(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
const findUser = MOCK_USERS.find(
|
|
||||||
(item) => item.username === userinfo.username,
|
|
||||||
);
|
|
||||||
if (!findUser) {
|
|
||||||
return forbiddenResponse(event);
|
|
||||||
}
|
|
||||||
const accessToken = generateAccessToken(findUser);
|
|
||||||
|
|
||||||
setRefreshTokenCookie(event, refreshToken);
|
|
||||||
|
|
||||||
return accessToken;
|
|
||||||
});
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
|
||||||
import { unAuthorizedResponse } from '~/utils/response';
|
|
||||||
|
|
||||||
export default eventHandler((event) => {
|
export default eventHandler((event) => {
|
||||||
const userinfo = verifyAccessToken(event);
|
const token = getHeader(event, 'Authorization');
|
||||||
if (!userinfo) {
|
|
||||||
return unAuthorizedResponse(event);
|
if (!token) {
|
||||||
|
setResponseStatus(event, 401);
|
||||||
|
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||||
|
|
||||||
const menus =
|
const menus =
|
||||||
MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? [];
|
MOCK_MENUS.find((item) => item.username === username)?.menus ?? [];
|
||||||
return useResponseSuccess(menus);
|
return useResponseSuccess(menus);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import { faker } from '@faker-js/faker';
|
|
||||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
|
||||||
import { unAuthorizedResponse } from '~/utils/response';
|
|
||||||
|
|
||||||
function generateMockDataList(count: number) {
|
|
||||||
const dataList = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const dataItem = {
|
|
||||||
id: faker.string.uuid(),
|
|
||||||
imageUrl: faker.image.avatar(),
|
|
||||||
imageUrl2: faker.image.avatar(),
|
|
||||||
open: faker.datatype.boolean(),
|
|
||||||
status: faker.helpers.arrayElement(['success', 'error', 'warning']),
|
|
||||||
productName: faker.commerce.productName(),
|
|
||||||
price: faker.commerce.price(),
|
|
||||||
currency: faker.finance.currencyCode(),
|
|
||||||
quantity: faker.number.int({ min: 1, max: 100 }),
|
|
||||||
available: faker.datatype.boolean(),
|
|
||||||
category: faker.commerce.department(),
|
|
||||||
releaseDate: faker.date.past(),
|
|
||||||
rating: faker.number.float({ min: 1, max: 5 }),
|
|
||||||
description: faker.commerce.productDescription(),
|
|
||||||
weight: faker.number.float({ min: 0.1, max: 10 }),
|
|
||||||
color: faker.color.human(),
|
|
||||||
inProduction: faker.datatype.boolean(),
|
|
||||||
tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()),
|
|
||||||
};
|
|
||||||
|
|
||||||
dataList.push(dataItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataList;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockData = generateMockDataList(100);
|
|
||||||
|
|
||||||
export default eventHandler(async (event) => {
|
|
||||||
const userinfo = verifyAccessToken(event);
|
|
||||||
if (!userinfo) {
|
|
||||||
return unAuthorizedResponse(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
await sleep(600);
|
|
||||||
|
|
||||||
const { page, pageSize } = getQuery(event);
|
|
||||||
return usePageResponseSuccess(page as string, pageSize as string, mockData);
|
|
||||||
});
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
|
||||||
import { unAuthorizedResponse } from '~/utils/response';
|
|
||||||
|
|
||||||
export default eventHandler((event) => {
|
export default eventHandler((event) => {
|
||||||
const userinfo = verifyAccessToken(event);
|
const token = getHeader(event, 'Authorization');
|
||||||
if (!userinfo) {
|
if (!token) {
|
||||||
return unAuthorizedResponse(event);
|
setResponseStatus(event, 401);
|
||||||
|
return useResponseError('UnauthorizedException', 'Unauthorized Exception');
|
||||||
}
|
}
|
||||||
|
|
||||||
return useResponseSuccess(userinfo);
|
const username = Buffer.from(token, 'base64').toString('utf8');
|
||||||
|
|
||||||
|
const user = MOCK_USERS.find((item) => item.username === username);
|
||||||
|
|
||||||
|
const { password: _pwd, ...userInfo } = user;
|
||||||
|
return useResponseSuccess(userInfo);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { NitroErrorHandler } from 'nitropack';
|
import type { NitroErrorHandler } from 'nitropack';
|
||||||
|
|
||||||
const errorHandler: NitroErrorHandler = function (error, event) {
|
const errorHandler: NitroErrorHandler = function (error, event) {
|
||||||
event.node.res.end(`[Error Handler] ${error.stack}`);
|
event.res.end(`[error handler] ${error.stack}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default errorHandler;
|
export default errorHandler;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
export default defineEventHandler((event) => {
|
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') {
|
if (event.method === 'OPTIONS') {
|
||||||
event.node.res.statusCode = 204;
|
event.node.res.statusCode = 204;
|
||||||
event.node.res.statusMessage = 'No Content.';
|
event.node.res.statusMessage = 'No Content.';
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,10 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "",
|
"author": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nitro build",
|
"start": "nitro dev",
|
||||||
"start": "nitro dev"
|
"build": "nitro build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faker-js/faker": "catalog:",
|
"nitropack": "^2.9.7"
|
||||||
"jsonwebtoken": "catalog:",
|
|
||||||
"nitropack": "catalog:"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/jsonwebtoken": "catalog:",
|
|
||||||
"h3": "catalog:"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import type { EventHandlerRequest, H3Event } from 'h3';
|
|
||||||
|
|
||||||
export function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) {
|
|
||||||
deleteCookie(event, 'jwt', {
|
|
||||||
httpOnly: true,
|
|
||||||
sameSite: 'none',
|
|
||||||
secure: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setRefreshTokenCookie(
|
|
||||||
event: H3Event<EventHandlerRequest>,
|
|
||||||
refreshToken: string,
|
|
||||||
) {
|
|
||||||
setCookie(event, 'jwt', refreshToken, {
|
|
||||||
httpOnly: true,
|
|
||||||
maxAge: 24 * 60 * 60 * 1000,
|
|
||||||
sameSite: 'none',
|
|
||||||
secure: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRefreshTokenFromCookie(event: H3Event<EventHandlerRequest>) {
|
|
||||||
const refreshToken = getCookie(event, 'jwt');
|
|
||||||
return refreshToken;
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import type { EventHandlerRequest, H3Event } from 'h3';
|
|
||||||
|
|
||||||
import jwt from 'jsonwebtoken';
|
|
||||||
|
|
||||||
import { UserInfo } from './mock-data';
|
|
||||||
|
|
||||||
// TODO: Replace with your own secret key
|
|
||||||
const ACCESS_TOKEN_SECRET = 'access_token_secret';
|
|
||||||
const REFRESH_TOKEN_SECRET = 'refresh_token_secret';
|
|
||||||
|
|
||||||
export interface UserPayload extends UserInfo {
|
|
||||||
iat: number;
|
|
||||||
exp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateAccessToken(user: UserInfo) {
|
|
||||||
return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateRefreshToken(user: UserInfo) {
|
|
||||||
return jwt.sign(user, REFRESH_TOKEN_SECRET, {
|
|
||||||
expiresIn: '30d',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function verifyAccessToken(
|
|
||||||
event: H3Event<EventHandlerRequest>,
|
|
||||||
): null | Omit<UserInfo, 'password'> {
|
|
||||||
const authHeader = getHeader(event, 'Authorization');
|
|
||||||
if (!authHeader?.startsWith('Bearer')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = authHeader.split(' ')[1];
|
|
||||||
try {
|
|
||||||
const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET) as UserPayload;
|
|
||||||
|
|
||||||
const username = decoded.username;
|
|
||||||
const user = MOCK_USERS.find((item) => item.username === username);
|
|
||||||
const { password: _pwd, ...userinfo } = user;
|
|
||||||
return userinfo;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function verifyRefreshToken(
|
|
||||||
token: string,
|
|
||||||
): null | Omit<UserInfo, 'password'> {
|
|
||||||
try {
|
|
||||||
const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload;
|
|
||||||
const username = decoded.username;
|
|
||||||
const user = MOCK_USERS.find((item) => item.username === username);
|
|
||||||
const { password: _pwd, ...userinfo } = user;
|
|
||||||
return userinfo;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,4 @@
|
||||||
export interface UserInfo {
|
export const MOCK_USERS = [
|
||||||
id: number;
|
|
||||||
password: string;
|
|
||||||
realName: string;
|
|
||||||
roles: string[];
|
|
||||||
username: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MOCK_USERS: UserInfo[] = [
|
|
||||||
{
|
{
|
||||||
id: 0,
|
id: 0,
|
||||||
password: '123456',
|
password: '123456',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import type { EventHandlerRequest, H3Event } from 'h3';
|
|
||||||
|
|
||||||
export function useResponseSuccess<T = any>(data: T) {
|
export function useResponseSuccess<T = any>(data: T) {
|
||||||
return {
|
return {
|
||||||
code: 0,
|
code: 0,
|
||||||
|
|
@ -9,27 +7,6 @@ export function useResponseSuccess<T = any>(data: T) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePageResponseSuccess<T = any>(
|
|
||||||
page: number | string,
|
|
||||||
pageSize: number | string,
|
|
||||||
list: T[],
|
|
||||||
{ message = 'ok' } = {},
|
|
||||||
) {
|
|
||||||
const pageData = pagination(
|
|
||||||
Number.parseInt(`${page}`),
|
|
||||||
Number.parseInt(`${pageSize}`),
|
|
||||||
list,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...useResponseSuccess({
|
|
||||||
items: pageData,
|
|
||||||
total: list.length,
|
|
||||||
}),
|
|
||||||
message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useResponseError(message: string, error: any = null) {
|
export function useResponseError(message: string, error: any = null) {
|
||||||
return {
|
return {
|
||||||
code: -1,
|
code: -1,
|
||||||
|
|
@ -38,28 +15,3 @@ export function useResponseError(message: string, error: any = null) {
|
||||||
message,
|
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sleep(ms: number) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function pagination<T = any>(
|
|
||||||
pageNo: number,
|
|
||||||
pageSize: number,
|
|
||||||
array: T[],
|
|
||||||
): T[] {
|
|
||||||
const offset = (pageNo - 1) * Number(pageSize);
|
|
||||||
return offset + Number(pageSize) >= array.length
|
|
||||||
? array.slice(offset)
|
|
||||||
: array.slice(offset, offset + Number(pageSize));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,3 @@ VITE_ROUTER_HISTORY=hash
|
||||||
|
|
||||||
# 是否注入全局loading
|
# 是否注入全局loading
|
||||||
VITE_INJECT_APP_LOADING=true
|
VITE_INJECT_APP_LOADING=true
|
||||||
|
|
||||||
# 打包后是否生成dist.zip
|
|
||||||
VITE_ARCHIVER=true
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vben/web-antd",
|
"name": "@vben/web-antd",
|
||||||
"version": "5.3.2",
|
"version": "5.1.0",
|
||||||
"homepage": "https://vben.pro",
|
"homepage": "https://vben.pro",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -27,24 +27,24 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vben/access": "workspace:*",
|
"@vben/access": "workspace:*",
|
||||||
|
"@vben/chart-ui": "workspace:*",
|
||||||
"@vben/common-ui": "workspace:*",
|
"@vben/common-ui": "workspace:*",
|
||||||
"@vben/constants": "workspace:*",
|
"@vben/constants": "workspace:*",
|
||||||
"@vben/hooks": "workspace:*",
|
"@vben/hooks": "workspace:*",
|
||||||
"@vben/icons": "workspace:*",
|
"@vben/icons": "workspace:*",
|
||||||
"@vben/layouts": "workspace:*",
|
"@vben/layouts": "workspace:*",
|
||||||
"@vben/locales": "workspace:*",
|
"@vben/locales": "workspace:*",
|
||||||
"@vben/plugins": "workspace:*",
|
|
||||||
"@vben/preferences": "workspace:*",
|
"@vben/preferences": "workspace:*",
|
||||||
"@vben/request": "workspace:*",
|
"@vben/request": "workspace:*",
|
||||||
"@vben/stores": "workspace:*",
|
"@vben/stores": "workspace:*",
|
||||||
"@vben/styles": "workspace:*",
|
"@vben/styles": "workspace:*",
|
||||||
"@vben/types": "workspace:*",
|
"@vben/types": "workspace:*",
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "^10.11.1",
|
||||||
"ant-design-vue": "catalog:",
|
"ant-design-vue": "^4.2.3",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "^1.11.12",
|
||||||
"pinia": "catalog:",
|
"pinia": "2.2.1",
|
||||||
"vue": "catalog:",
|
"vue": "^3.4.37",
|
||||||
"vue-router": "catalog:"
|
"vue-router": "^4.4.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
import type {
|
|
||||||
BaseFormComponentType,
|
|
||||||
VbenFormSchema as FormSchema,
|
|
||||||
VbenFormProps,
|
|
||||||
} from '@vben/common-ui';
|
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
import {
|
|
||||||
AutoComplete,
|
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
CheckboxGroup,
|
|
||||||
DatePicker,
|
|
||||||
Divider,
|
|
||||||
Input,
|
|
||||||
InputNumber,
|
|
||||||
InputPassword,
|
|
||||||
Mentions,
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
RangePicker,
|
|
||||||
Rate,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
Switch,
|
|
||||||
Textarea,
|
|
||||||
TimePicker,
|
|
||||||
TreeSelect,
|
|
||||||
Upload,
|
|
||||||
} from 'ant-design-vue';
|
|
||||||
|
|
||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
|
||||||
export type FormComponentType =
|
|
||||||
| 'AutoComplete'
|
|
||||||
| 'Checkbox'
|
|
||||||
| 'CheckboxGroup'
|
|
||||||
| 'DatePicker'
|
|
||||||
| 'Divider'
|
|
||||||
| 'Input'
|
|
||||||
| 'InputNumber'
|
|
||||||
| 'InputPassword'
|
|
||||||
| 'Mentions'
|
|
||||||
| 'Radio'
|
|
||||||
| 'RadioGroup'
|
|
||||||
| 'RangePicker'
|
|
||||||
| 'Rate'
|
|
||||||
| 'Select'
|
|
||||||
| 'Space'
|
|
||||||
| 'Switch'
|
|
||||||
| 'Textarea'
|
|
||||||
| 'TimePicker'
|
|
||||||
| 'TreeSelect'
|
|
||||||
| 'Upload'
|
|
||||||
| BaseFormComponentType;
|
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
|
||||||
component: T,
|
|
||||||
type: 'input' | 'select',
|
|
||||||
) => {
|
|
||||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
|
||||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
|
||||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化表单组件,并注册到form组件内部
|
|
||||||
setupVbenForm<FormComponentType>({
|
|
||||||
components: {
|
|
||||||
AutoComplete,
|
|
||||||
Checkbox,
|
|
||||||
CheckboxGroup,
|
|
||||||
DatePicker,
|
|
||||||
// 自定义默认的重置按钮
|
|
||||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
|
||||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
|
||||||
},
|
|
||||||
// 自定义默认的提交按钮
|
|
||||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
|
||||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
|
||||||
},
|
|
||||||
Divider,
|
|
||||||
Input: withDefaultPlaceholder(Input, 'input'),
|
|
||||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
|
||||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
|
||||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
RangePicker,
|
|
||||||
Rate,
|
|
||||||
Select: withDefaultPlaceholder(Select, 'select'),
|
|
||||||
Space,
|
|
||||||
Switch,
|
|
||||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
|
||||||
TimePicker,
|
|
||||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
|
||||||
Upload,
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
// ant design vue组件库默认都是 v-model:value
|
|
||||||
baseModelPropName: 'value',
|
|
||||||
|
|
||||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
|
||||||
modelPropNameMap: {
|
|
||||||
Checkbox: 'checked',
|
|
||||||
Radio: 'checked',
|
|
||||||
Switch: 'checked',
|
|
||||||
Upload: 'fileList',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defineRules: {
|
|
||||||
// 输入项目必填国际化适配
|
|
||||||
required: (value, _params, ctx) => {
|
|
||||||
if (value === undefined || value === null || value.length === 0) {
|
|
||||||
return $t('formRules.required', [ctx.label]);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
// 选择项目必填国际化适配
|
|
||||||
selectRequired: (value, _params, ctx) => {
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
return $t('formRules.selectRequired', [ctx.label]);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const useVbenForm = useForm<FormComponentType>;
|
|
||||||
|
|
||||||
export { useVbenForm, z };
|
|
||||||
|
|
||||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
|
||||||
export type { VbenFormProps };
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './form';
|
|
||||||
export * from './vxe-table';
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import { Button, Image } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { useVbenForm } from './form';
|
|
||||||
|
|
||||||
setupVbenVxeTable({
|
|
||||||
configVxeTable: (vxeUI) => {
|
|
||||||
vxeUI.setConfig({
|
|
||||||
grid: {
|
|
||||||
align: 'center',
|
|
||||||
border: true,
|
|
||||||
minHeight: 180,
|
|
||||||
proxyConfig: {
|
|
||||||
autoLoad: true,
|
|
||||||
response: {
|
|
||||||
result: 'items',
|
|
||||||
total: 'total',
|
|
||||||
list: 'items',
|
|
||||||
},
|
|
||||||
showActiveMsg: true,
|
|
||||||
showResponseMsg: false,
|
|
||||||
},
|
|
||||||
round: true,
|
|
||||||
size: 'small',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
|
||||||
vxeUI.renderer.add('CellImage', {
|
|
||||||
renderDefault(_renderOpts, params) {
|
|
||||||
const { column, row } = params;
|
|
||||||
return h(Image, { src: row[column.field] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
|
||||||
vxeUI.renderer.add('CellLink', {
|
|
||||||
renderDefault(renderOpts) {
|
|
||||||
const { props } = renderOpts;
|
|
||||||
return h(
|
|
||||||
Button,
|
|
||||||
{ size: 'small', type: 'link' },
|
|
||||||
{ default: () => props?.text },
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
|
||||||
// vxeUI.formats.add
|
|
||||||
},
|
|
||||||
useVbenForm,
|
|
||||||
});
|
|
||||||
|
|
||||||
export { useVbenVxeGrid };
|
|
||||||
|
|
||||||
export type * from '@vben/plugins/vxe-table';
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { baseRequestClient, requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace AuthApi {
|
export namespace AuthApi {
|
||||||
/** 登录接口参数 */
|
/** 登录接口参数 */
|
||||||
|
|
@ -10,11 +10,11 @@ export namespace AuthApi {
|
||||||
/** 登录接口返回值 */
|
/** 登录接口返回值 */
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}
|
desc: string;
|
||||||
|
realName: string;
|
||||||
export interface RefreshTokenResult {
|
refreshToken: string;
|
||||||
data: string;
|
userId: string;
|
||||||
status: number;
|
username: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,24 +25,6 @@ export async function loginApi(data: AuthApi.LoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户权限码
|
* 获取用户权限码
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -5,73 +5,54 @@ import type { HttpResponse } from '@vben/request';
|
||||||
|
|
||||||
import { useAppConfig } from '@vben/hooks';
|
import { useAppConfig } from '@vben/hooks';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import {
|
import { RequestClient } from '@vben/request';
|
||||||
authenticateResponseInterceptor,
|
|
||||||
errorMessageResponseInterceptor,
|
|
||||||
RequestClient,
|
|
||||||
} from '@vben/request';
|
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
import { refreshTokenApi } from './core';
|
|
||||||
|
|
||||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
|
|
||||||
function createRequestClient(baseURL: string) {
|
function createRequestClient(baseURL: string) {
|
||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
baseURL,
|
baseURL,
|
||||||
});
|
// 为每个请求携带 Authorization
|
||||||
|
makeAuthorization: () => {
|
||||||
/**
|
return {
|
||||||
* 重新认证逻辑
|
// 默认
|
||||||
*/
|
key: 'Authorization',
|
||||||
async function doReAuthenticate() {
|
tokenHandler: () => {
|
||||||
console.warn('Access token or refresh token is invalid or expired. ');
|
const accessStore = useAccessStore();
|
||||||
|
return {
|
||||||
|
refreshToken: `${accessStore.refreshToken}`,
|
||||||
|
token: `${accessStore.accessToken}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
unAuthorizedHandler: async () => {
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
accessStore.setAccessToken(null);
|
accessStore.setAccessToken(null);
|
||||||
if (
|
|
||||||
preferences.app.loginExpiredMode === 'modal' &&
|
if (preferences.app.loginExpiredMode === 'modal') {
|
||||||
accessStore.isAccessChecked
|
|
||||||
) {
|
|
||||||
accessStore.setLoginExpired(true);
|
accessStore.setLoginExpired(true);
|
||||||
} else {
|
} else {
|
||||||
|
// 退出登录
|
||||||
await authStore.logout();
|
await authStore.logout();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
makeErrorMessage: (msg) => message.error(msg),
|
||||||
|
|
||||||
/**
|
makeRequestHeaders: () => {
|
||||||
* 刷新token逻辑
|
return {
|
||||||
*/
|
// 为每个请求携带 Accept-Language
|
||||||
async function doRefreshToken() {
|
'Accept-Language': preferences.app.locale,
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
client.addResponseInterceptor<HttpResponse>((response) => {
|
||||||
// response数据解构
|
|
||||||
client.addResponseInterceptor<HttpResponse>({
|
|
||||||
fulfilled: (response) => {
|
|
||||||
const { data: responseData, status } = response;
|
const { data: responseData, status } = response;
|
||||||
|
|
||||||
const { code, data, message: msg } = responseData;
|
const { code, data, message: msg } = responseData;
|
||||||
|
|
@ -79,31 +60,8 @@ function createRequestClient(baseURL: string) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
throw new Error(`Error ${status}: ${msg}`);
|
throw new Error(`Error ${status}: ${msg}`);
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// token过期的处理
|
|
||||||
client.addResponseInterceptor(
|
|
||||||
authenticateResponseInterceptor({
|
|
||||||
client,
|
|
||||||
doReAuthenticate,
|
|
||||||
doRefreshToken,
|
|
||||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
|
||||||
formatToken,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
|
||||||
client.addResponseInterceptor(
|
|
||||||
errorMessageResponseInterceptor((msg: string, _error) => {
|
|
||||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
|
||||||
message.error(msg);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const requestClient = createRequestClient(apiURL);
|
export const requestClient = createRequestClient(apiURL);
|
||||||
|
|
||||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
import { AuthPageLayout } from '@vben/layouts';
|
|
||||||
import { preferences } from '@vben/preferences';
|
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
const appName = computed(() => preferences.app.name);
|
|
||||||
const logo = computed(() => preferences.logo.source);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<AuthPageLayout
|
|
||||||
:app-name="appName"
|
|
||||||
:logo="logo"
|
|
||||||
:page-description="$t('authentication.pageDesc')"
|
|
||||||
:page-title="$t('authentication.pageTitle')"
|
|
||||||
>
|
|
||||||
<!-- 自定义工具栏 -->
|
|
||||||
<!-- <template #toolbar></template> -->
|
|
||||||
</AuthPageLayout>
|
|
||||||
</template>
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
import type { NotificationItem } from '@vben/layouts';
|
import type { NotificationItem } from '@vben/layouts';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
import { LOGIN_PATH, VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||||
import {
|
import {
|
||||||
BasicLayout,
|
BasicLayout,
|
||||||
|
|
@ -13,12 +14,17 @@ import {
|
||||||
UserDropdown,
|
UserDropdown,
|
||||||
} from '@vben/layouts';
|
} from '@vben/layouts';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
import {
|
||||||
|
resetAllStores,
|
||||||
|
storeToRefs,
|
||||||
|
useAccessStore,
|
||||||
|
useUserStore,
|
||||||
|
} from '@vben/stores';
|
||||||
import { openWindow } from '@vben/utils';
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { resetRoutes } from '#/router';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
|
||||||
|
|
||||||
const notifications = ref<NotificationItem[]>([
|
const notifications = ref<NotificationItem[]>([
|
||||||
{
|
{
|
||||||
|
|
@ -88,12 +94,18 @@ const menus = computed(() => [
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const { loginLoading } = storeToRefs(authStore);
|
||||||
|
|
||||||
const avatar = computed(() => {
|
const avatar = computed(() => {
|
||||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
await authStore.logout(false);
|
resetAllStores();
|
||||||
|
resetRoutes();
|
||||||
|
await router.replace(LOGIN_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNoticeClear() {
|
function handleNoticeClear() {
|
||||||
|
|
@ -129,9 +141,11 @@ function handleMakeAll() {
|
||||||
<AuthenticationLoginExpiredModal
|
<AuthenticationLoginExpiredModal
|
||||||
v-model:open="accessStore.loginExpired"
|
v-model:open="accessStore.loginExpired"
|
||||||
:avatar
|
:avatar
|
||||||
>
|
:loading="loginLoading"
|
||||||
<LoginForm />
|
password-placeholder="123456"
|
||||||
</AuthenticationLoginExpiredModal>
|
username-placeholder="vben"
|
||||||
|
@submit="authStore.authLogin"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #lock-screen>
|
<template #lock-screen>
|
||||||
<LockScreen :avatar @to-login="handleLogout" />
|
<LockScreen :avatar @to-login="handleLogout" />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
const BasicLayout = () => import('./basic.vue');
|
const BasicLayout = () => import('./basic.vue');
|
||||||
const AuthPageLayout = () => import('./auth.vue');
|
|
||||||
|
|
||||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||||
|
|
||||||
|
const AuthPageLayout = () =>
|
||||||
|
import('@vben/layouts').then((m) => m.AuthPageLayout);
|
||||||
|
|
||||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||||
|
|
|
||||||
|
|
@ -91,4 +91,4 @@ async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { $t, antdLocale, setupI18n };
|
export { $t, antdLocale, loadMessages, setupI18n };
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,10 @@ function setupAccessGuard(router: Router) {
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accessRoutes = accessStore.accessRoutes;
|
||||||
|
|
||||||
// 是否已经生成过动态路由
|
// 是否已经生成过动态路由
|
||||||
if (accessStore.isAccessChecked) {
|
if (accessRoutes && accessRoutes.length > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +115,6 @@ function setupAccessGuard(router: Router) {
|
||||||
// 保存菜单信息和路由信息
|
// 保存菜单信息和路由信息
|
||||||
accessStore.setAccessMenus(accessibleMenus);
|
accessStore.setAccessMenus(accessibleMenus);
|
||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
|
||||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,7 @@ const router = createRouter({
|
||||||
: createWebHistory(import.meta.env.VITE_BASE),
|
: createWebHistory(import.meta.env.VITE_BASE),
|
||||||
// 应该添加到路由的初始路由列表。
|
// 应该添加到路由的初始路由列表。
|
||||||
routes,
|
routes,
|
||||||
scrollBehavior: (to, _from, savedPosition) => {
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
if (savedPosition) {
|
|
||||||
return savedPosition;
|
|
||||||
}
|
|
||||||
return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };
|
|
||||||
},
|
|
||||||
// 是否应该禁止尾部斜杠。
|
// 是否应该禁止尾部斜杠。
|
||||||
// strict: true,
|
// strict: true,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ const routes: RouteRecordRaw[] = [
|
||||||
path: '/workspace',
|
path: '/workspace',
|
||||||
component: () => import('#/views/dashboard/workspace/index.vue'),
|
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'carbon:workspace',
|
|
||||||
title: $t('page.dashboard.workspace'),
|
title: $t('page.dashboard.workspace'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
name: 'VbenAbout',
|
name: 'VbenAbout',
|
||||||
path: '/vben-admin/about',
|
path: '/vben-admin/about',
|
||||||
component: () => import('#/views/_core/about/index.vue'),
|
component: () => import('#/views/_core/vben/about/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'lucide:copyright',
|
icon: 'lucide:copyright',
|
||||||
title: $t('page.vben.about'),
|
title: $t('page.vben.about'),
|
||||||
|
|
@ -58,7 +58,6 @@ const routes: RouteRecordRaw[] = [
|
||||||
component: IFrameView,
|
component: IFrameView,
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
icon: 'logos:naiveui',
|
|
||||||
link: VBEN_NAIVE_PREVIEW_URL,
|
link: VBEN_NAIVE_PREVIEW_URL,
|
||||||
title: $t('page.vben.naive-ui'),
|
title: $t('page.vben.naive-ui'),
|
||||||
},
|
},
|
||||||
|
|
@ -69,7 +68,6 @@ const routes: RouteRecordRaw[] = [
|
||||||
component: IFrameView,
|
component: IFrameView,
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
icon: 'logos:element',
|
|
||||||
link: VBEN_ELE_PREVIEW_URL,
|
link: VBEN_ELE_PREVIEW_URL,
|
||||||
title: $t('page.vben.element-plus'),
|
title: $t('page.vben.element-plus'),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { notification } from 'ant-design-vue';
|
import { notification } from 'ant-design-vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
import { getAccessCodesApi, getUserInfoApi, loginApi } from '#/api';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
|
@ -33,11 +33,13 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
let userInfo: null | UserInfo = null;
|
let userInfo: null | UserInfo = null;
|
||||||
try {
|
try {
|
||||||
loginLoading.value = true;
|
loginLoading.value = true;
|
||||||
const { accessToken } = await loginApi(params);
|
const { accessToken, refreshToken } = await loginApi(params);
|
||||||
|
|
||||||
// 如果成功获取到 accessToken
|
// 如果成功获取到 accessToken
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
|
// 将 accessToken 存储到 accessStore 中
|
||||||
accessStore.setAccessToken(accessToken);
|
accessStore.setAccessToken(accessToken);
|
||||||
|
accessStore.setRefreshToken(refreshToken);
|
||||||
|
|
||||||
// 获取用户信息并存储到 accessStore 中
|
// 获取用户信息并存储到 accessStore 中
|
||||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||||
|
|
@ -75,23 +77,16 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logout(redirect: boolean = true) {
|
async function logout() {
|
||||||
try {
|
|
||||||
await logoutApi();
|
|
||||||
} catch {
|
|
||||||
// 不做任何处理
|
|
||||||
}
|
|
||||||
resetAllStores();
|
resetAllStores();
|
||||||
accessStore.setLoginExpired(false);
|
accessStore.setLoginExpired(false);
|
||||||
|
|
||||||
// 回登陆页带上当前路由地址
|
// 回登陆页带上当前路由地址
|
||||||
await router.replace({
|
await router.replace({
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
query: redirect
|
query: {
|
||||||
? {
|
|
||||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||||
}
|
},
|
||||||
: {},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,15 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
|
import type { LoginCodeParams } from '@vben/common-ui';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
import { AuthenticationCodeLogin } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
defineOptions({ name: 'CodeLogin' });
|
defineOptions({ name: 'CodeLogin' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'VbenInput',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: $t('authentication.mobile'),
|
|
||||||
},
|
|
||||||
fieldName: 'phoneNumber',
|
|
||||||
label: $t('authentication.mobile'),
|
|
||||||
rules: z
|
|
||||||
.string()
|
|
||||||
.min(1, { message: $t('authentication.mobileTip') })
|
|
||||||
.refine((v) => /^\d{11}$/.test(v), {
|
|
||||||
message: $t('authentication.mobileErrortip'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenPinInput',
|
|
||||||
componentProps: {
|
|
||||||
createText: (countdown: number) => {
|
|
||||||
const text =
|
|
||||||
countdown > 0
|
|
||||||
? $t('authentication.sendText', [countdown])
|
|
||||||
: $t('authentication.sendCode');
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
placeholder: $t('authentication.code'),
|
|
||||||
},
|
|
||||||
fieldName: 'code',
|
|
||||||
label: $t('authentication.code'),
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
/**
|
/**
|
||||||
* 异步处理登录操作
|
* 异步处理登录操作
|
||||||
* Asynchronously handle the login process
|
* Asynchronously handle the login process
|
||||||
|
|
@ -57,8 +23,8 @@ async function handleLogin(values: LoginCodeParams) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationCodeLogin
|
<AuthenticationCodeLogin
|
||||||
:form-schema="formSchema"
|
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:login-path="LOGIN_PATH"
|
||||||
@submit="handleLogin"
|
@submit="handleLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,13 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VbenFormSchema } from '@vben/common-ui';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { AuthenticationForgetPassword } from '@vben/common-ui';
|
||||||
|
import { LOGIN_PATH } from '@vben/constants';
|
||||||
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
defineOptions({ name: 'ForgetPassword' });
|
defineOptions({ name: 'ForgetPassword' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'VbenInput',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: 'example@example.com',
|
|
||||||
},
|
|
||||||
fieldName: 'email',
|
|
||||||
label: $t('authentication.email'),
|
|
||||||
rules: z
|
|
||||||
.string()
|
|
||||||
.min(1, { message: $t('authentication.emailTip') })
|
|
||||||
.email($t('authentication.emailValidErrorTip')),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleSubmit(value: string) {
|
function handleSubmit(value: string) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('reset email:', value);
|
console.log('reset email:', value);
|
||||||
|
|
@ -35,8 +16,8 @@ function handleSubmit(value: string) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationForgetPassword
|
<AuthenticationForgetPassword
|
||||||
:form-schema="formSchema"
|
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:login-path="LOGIN_PATH"
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,18 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VbenFormSchema } from '@vben/common-ui';
|
import { AuthenticationLogin } from '@vben/common-ui';
|
||||||
import type { BasicOption } from '@vben/types';
|
|
||||||
|
|
||||||
import { computed, markRaw } from 'vue';
|
|
||||||
|
|
||||||
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
defineOptions({ name: 'Login' });
|
defineOptions({ name: 'Login' });
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
const MOCK_USER_OPTIONS: BasicOption[] = [
|
|
||||||
{
|
|
||||||
label: 'Super',
|
|
||||||
value: 'vben',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Admin',
|
|
||||||
value: 'admin',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'User',
|
|
||||||
value: 'jack',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'VbenSelect',
|
|
||||||
componentProps: {
|
|
||||||
options: MOCK_USER_OPTIONS,
|
|
||||||
placeholder: $t('authentication.selectAccount'),
|
|
||||||
},
|
|
||||||
fieldName: 'selectAccount',
|
|
||||||
label: $t('authentication.selectAccount'),
|
|
||||||
rules: z
|
|
||||||
.string()
|
|
||||||
.min(1, { message: $t('authentication.selectAccount') })
|
|
||||||
.optional()
|
|
||||||
.default('vben'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenInput',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: $t('authentication.usernameTip'),
|
|
||||||
},
|
|
||||||
dependencies: {
|
|
||||||
trigger(values, form) {
|
|
||||||
if (values.selectAccount) {
|
|
||||||
const findUser = MOCK_USER_OPTIONS.find(
|
|
||||||
(item) => item.value === values.selectAccount,
|
|
||||||
);
|
|
||||||
if (findUser) {
|
|
||||||
form.setValues({
|
|
||||||
password: '123456',
|
|
||||||
username: findUser.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
triggerFields: ['selectAccount'],
|
|
||||||
},
|
|
||||||
fieldName: 'username',
|
|
||||||
label: $t('authentication.username'),
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenInputPassword',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: $t('authentication.password'),
|
|
||||||
},
|
|
||||||
fieldName: 'password',
|
|
||||||
label: $t('authentication.password'),
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: markRaw(SliderCaptcha),
|
|
||||||
fieldName: 'captcha',
|
|
||||||
rules: z.boolean().refine((value) => value, {
|
|
||||||
message: $t('authentication.verifyRequiredTip'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationLogin
|
<AuthenticationLogin
|
||||||
:form-schema="formSchema"
|
|
||||||
:loading="authStore.loginLoading"
|
:loading="authStore.loginLoading"
|
||||||
|
password-placeholder="123456"
|
||||||
|
username-placeholder="vben"
|
||||||
@submit="authStore.authLogin"
|
@submit="authStore.authLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,91 +1,15 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
|
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
||||||
|
|
||||||
import { computed, h, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationRegister, z } from '@vben/common-ui';
|
import { AuthenticationRegister } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
defineOptions({ name: 'Register' });
|
defineOptions({ name: 'Register' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'VbenInput',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: $t('authentication.usernameTip'),
|
|
||||||
},
|
|
||||||
fieldName: 'username',
|
|
||||||
label: $t('authentication.username'),
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenInputPassword',
|
|
||||||
componentProps: {
|
|
||||||
passwordStrength: true,
|
|
||||||
placeholder: $t('authentication.password'),
|
|
||||||
},
|
|
||||||
fieldName: 'password',
|
|
||||||
label: $t('authentication.password'),
|
|
||||||
renderComponentContent() {
|
|
||||||
return {
|
|
||||||
strengthText: () => $t('authentication.passwordStrength'),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenInputPassword',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: $t('authentication.confirmPassword'),
|
|
||||||
},
|
|
||||||
dependencies: {
|
|
||||||
rules(values) {
|
|
||||||
const { password } = values;
|
|
||||||
return z
|
|
||||||
.string()
|
|
||||||
.min(1, { message: $t('authentication.passwordTip') })
|
|
||||||
.refine((value) => value === password, {
|
|
||||||
message: $t('authentication.confirmPasswordTip'),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
triggerFields: ['password'],
|
|
||||||
},
|
|
||||||
fieldName: 'confirmPassword',
|
|
||||||
label: $t('authentication.confirmPassword'),
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenCheckbox',
|
|
||||||
fieldName: 'agreePolicy',
|
|
||||||
renderComponentContent: () => ({
|
|
||||||
default: () =>
|
|
||||||
h('span', [
|
|
||||||
$t('authentication.agree'),
|
|
||||||
h(
|
|
||||||
'a',
|
|
||||||
{
|
|
||||||
class:
|
|
||||||
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
|
|
||||||
href: '',
|
|
||||||
},
|
|
||||||
[
|
|
||||||
$t('authentication.privacyPolicy'),
|
|
||||||
'&',
|
|
||||||
$t('authentication.terms'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
}),
|
|
||||||
rules: z.boolean().refine((value) => !!value, {
|
|
||||||
message: $t('authentication.agreeTip'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleSubmit(value: LoginAndRegisterParams) {
|
function handleSubmit(value: LoginAndRegisterParams) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('register submit:', value);
|
console.log('register submit:', value);
|
||||||
|
|
@ -94,8 +18,8 @@ function handleSubmit(value: LoginAndRegisterParams) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationRegister
|
<AuthenticationRegister
|
||||||
:form-schema="formSchema"
|
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:login-path="LOGIN_PATH"
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
@ -55,27 +51,12 @@ onMounted(() => {
|
||||||
},
|
},
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
},
|
},
|
||||||
// xAxis: {
|
|
||||||
// axisTick: {
|
|
||||||
// show: false,
|
|
||||||
// },
|
|
||||||
// boundaryGap: false,
|
|
||||||
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
|
||||||
// type: 'category',
|
|
||||||
// },
|
|
||||||
xAxis: {
|
xAxis: {
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
type: 'solid',
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
type: 'category',
|
type: 'category',
|
||||||
},
|
},
|
||||||
yAxis: [
|
yAxis: [
|
||||||
|
|
@ -84,10 +65,7 @@ onMounted(() => {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
max: 80_000,
|
max: 80_000,
|
||||||
splitArea: {
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
splitNumber: 4,
|
|
||||||
type: 'value',
|
type: 'value',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
|
||||||
|
|
@ -214,11 +214,7 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full lg:w-2/5">
|
<div class="w-full lg:w-2/5">
|
||||||
<WorkbenchQuickNav
|
<WorkbenchQuickNav :items="quickNavItems" title="快捷导航" />
|
||||||
:items="quickNavItems"
|
|
||||||
class="mt-5 lg:mt-0"
|
|
||||||
title="快捷导航"
|
|
||||||
/>
|
|
||||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||||
<AnalyticsVisitsSource />
|
<AnalyticsVisitsSource />
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ function notify(type: NotificationType) {
|
||||||
description="支持多语言,主题功能集成切换等"
|
description="支持多语言,主题功能集成切换等"
|
||||||
title="Ant Design Vue组件使用演示"
|
title="Ant Design Vue组件使用演示"
|
||||||
>
|
>
|
||||||
<Card class="mb-5" title="按钮">
|
<Card title="按钮">
|
||||||
<Space>
|
<Space>
|
||||||
<Button>Default</Button>
|
<Button>Default</Button>
|
||||||
<Button type="primary"> Primary </Button>
|
<Button type="primary"> Primary </Button>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,3 @@ VITE_ROUTER_HISTORY=hash
|
||||||
|
|
||||||
# 是否注入全局loading
|
# 是否注入全局loading
|
||||||
VITE_INJECT_APP_LOADING=true
|
VITE_INJECT_APP_LOADING=true
|
||||||
|
|
||||||
# 打包后是否生成dist.zip
|
|
||||||
VITE_ARCHIVER=true
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vben/web-ele",
|
"name": "@vben/web-ele",
|
||||||
"version": "5.3.2",
|
"version": "5.1.0",
|
||||||
"homepage": "https://vben.pro",
|
"homepage": "https://vben.pro",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -27,27 +27,27 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vben/access": "workspace:*",
|
"@vben/access": "workspace:*",
|
||||||
|
"@vben/chart-ui": "workspace:*",
|
||||||
"@vben/common-ui": "workspace:*",
|
"@vben/common-ui": "workspace:*",
|
||||||
"@vben/constants": "workspace:*",
|
"@vben/constants": "workspace:*",
|
||||||
"@vben/hooks": "workspace:*",
|
"@vben/hooks": "workspace:*",
|
||||||
"@vben/icons": "workspace:*",
|
"@vben/icons": "workspace:*",
|
||||||
"@vben/layouts": "workspace:*",
|
"@vben/layouts": "workspace:*",
|
||||||
"@vben/locales": "workspace:*",
|
"@vben/locales": "workspace:*",
|
||||||
"@vben/plugins": "workspace:*",
|
|
||||||
"@vben/preferences": "workspace:*",
|
"@vben/preferences": "workspace:*",
|
||||||
"@vben/request": "workspace:*",
|
"@vben/request": "workspace:*",
|
||||||
"@vben/stores": "workspace:*",
|
"@vben/stores": "workspace:*",
|
||||||
"@vben/styles": "workspace:*",
|
"@vben/styles": "workspace:*",
|
||||||
"@vben/types": "workspace:*",
|
"@vben/types": "workspace:*",
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "^10.11.1",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "^1.11.12",
|
||||||
"element-plus": "catalog:",
|
"element-plus": "^2.8.0",
|
||||||
"pinia": "catalog:",
|
"pinia": "2.2.1",
|
||||||
"vue": "catalog:",
|
"vue": "^3.4.37",
|
||||||
"vue-router": "catalog:"
|
"vue-router": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"unplugin-element-plus": "catalog:"
|
"unplugin-element-plus": "^0.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
import type {
|
|
||||||
BaseFormComponentType,
|
|
||||||
VbenFormSchema as FormSchema,
|
|
||||||
VbenFormProps,
|
|
||||||
} from '@vben/common-ui';
|
|
||||||
|
|
||||||
import type { Component, SetupContext } from 'vue';
|
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ElButton,
|
|
||||||
ElCheckbox,
|
|
||||||
ElCheckboxGroup,
|
|
||||||
ElDivider,
|
|
||||||
ElInput,
|
|
||||||
ElInputNumber,
|
|
||||||
ElRadioGroup,
|
|
||||||
ElSelect,
|
|
||||||
ElSpace,
|
|
||||||
ElSwitch,
|
|
||||||
ElTimePicker,
|
|
||||||
ElTreeSelect,
|
|
||||||
ElUpload,
|
|
||||||
} from 'element-plus';
|
|
||||||
// 业务表单组件适配
|
|
||||||
|
|
||||||
export type FormComponentType =
|
|
||||||
| 'Checkbox'
|
|
||||||
| 'CheckboxGroup'
|
|
||||||
| 'DatePicker'
|
|
||||||
| 'Divider'
|
|
||||||
| 'Input'
|
|
||||||
| 'InputNumber'
|
|
||||||
| 'RadioGroup'
|
|
||||||
| 'Select'
|
|
||||||
| 'Space'
|
|
||||||
| 'Switch'
|
|
||||||
| 'TimePicker'
|
|
||||||
| 'TreeSelect'
|
|
||||||
| 'Upload'
|
|
||||||
| BaseFormComponentType;
|
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
|
||||||
component: T,
|
|
||||||
type: 'input' | 'select',
|
|
||||||
) => {
|
|
||||||
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
|
|
||||||
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
|
|
||||||
return h(component, { ...props, ...attrs, placeholder }, slots);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化表单组件,并注册到form组件内部
|
|
||||||
setupVbenForm<FormComponentType>({
|
|
||||||
components: {
|
|
||||||
Checkbox: ElCheckbox,
|
|
||||||
CheckboxGroup: ElCheckboxGroup,
|
|
||||||
// 自定义默认的重置按钮
|
|
||||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
|
||||||
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
|
||||||
},
|
|
||||||
// 自定义默认的提交按钮
|
|
||||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
|
||||||
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
|
||||||
},
|
|
||||||
Divider: ElDivider,
|
|
||||||
Input: withDefaultPlaceholder(ElInput, 'input'),
|
|
||||||
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
|
||||||
RadioGroup: ElRadioGroup,
|
|
||||||
Select: withDefaultPlaceholder(ElSelect, 'select'),
|
|
||||||
Space: ElSpace,
|
|
||||||
Switch: ElSwitch,
|
|
||||||
TimePicker: ElTimePicker,
|
|
||||||
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
|
|
||||||
Upload: ElUpload,
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
modelPropNameMap: {
|
|
||||||
Upload: 'fileList',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defineRules: {
|
|
||||||
required: (value, _params, ctx) => {
|
|
||||||
if (value === undefined || value === null || value.length === 0) {
|
|
||||||
return $t('formRules.required', [ctx.label]);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
selectRequired: (value, _params, ctx) => {
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
return $t('formRules.selectRequired', [ctx.label]);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const useVbenForm = useForm<FormComponentType>;
|
|
||||||
|
|
||||||
export { useVbenForm, z };
|
|
||||||
|
|
||||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
|
||||||
export type { VbenFormProps };
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './form';
|
|
||||||
export * from './vxe-table';
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import { ElButton, ElImage } from 'element-plus';
|
|
||||||
|
|
||||||
import { useVbenForm } from './form';
|
|
||||||
|
|
||||||
setupVbenVxeTable({
|
|
||||||
configVxeTable: (vxeUI) => {
|
|
||||||
vxeUI.setConfig({
|
|
||||||
grid: {
|
|
||||||
align: 'center',
|
|
||||||
border: true,
|
|
||||||
minHeight: 180,
|
|
||||||
proxyConfig: {
|
|
||||||
autoLoad: true,
|
|
||||||
response: {
|
|
||||||
result: 'items',
|
|
||||||
total: 'total',
|
|
||||||
list: 'items',
|
|
||||||
},
|
|
||||||
showActiveMsg: true,
|
|
||||||
showResponseMsg: false,
|
|
||||||
},
|
|
||||||
round: true,
|
|
||||||
size: 'small',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
|
||||||
vxeUI.renderer.add('CellImage', {
|
|
||||||
renderDefault(_renderOpts, params) {
|
|
||||||
const { column, row } = params;
|
|
||||||
const src = row[column.field];
|
|
||||||
return h(ElImage, { src, previewSrcList: [src] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
|
||||||
vxeUI.renderer.add('CellLink', {
|
|
||||||
renderDefault(renderOpts) {
|
|
||||||
const { props } = renderOpts;
|
|
||||||
return h(
|
|
||||||
ElButton,
|
|
||||||
{ size: 'small', link: true },
|
|
||||||
{ default: () => props?.text },
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
|
||||||
// vxeUI.formats.add
|
|
||||||
},
|
|
||||||
useVbenForm,
|
|
||||||
});
|
|
||||||
|
|
||||||
export { useVbenVxeGrid };
|
|
||||||
|
|
||||||
export type * from '@vben/plugins/vxe-table';
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { baseRequestClient, requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace AuthApi {
|
export namespace AuthApi {
|
||||||
/** 登录接口参数 */
|
/** 登录接口参数 */
|
||||||
|
|
@ -10,11 +10,11 @@ export namespace AuthApi {
|
||||||
/** 登录接口返回值 */
|
/** 登录接口返回值 */
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}
|
desc: string;
|
||||||
|
realName: string;
|
||||||
export interface RefreshTokenResult {
|
refreshToken: string;
|
||||||
data: string;
|
userId: string;
|
||||||
status: number;
|
username: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,24 +25,6 @@ export async function loginApi(data: AuthApi.LoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户权限码
|
* 获取用户权限码
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -5,73 +5,54 @@ import type { HttpResponse } from '@vben/request';
|
||||||
|
|
||||||
import { useAppConfig } from '@vben/hooks';
|
import { useAppConfig } from '@vben/hooks';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import {
|
import { RequestClient } from '@vben/request';
|
||||||
authenticateResponseInterceptor,
|
|
||||||
errorMessageResponseInterceptor,
|
|
||||||
RequestClient,
|
|
||||||
} from '@vben/request';
|
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
import { refreshTokenApi } from './core';
|
|
||||||
|
|
||||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
|
|
||||||
function createRequestClient(baseURL: string) {
|
function createRequestClient(baseURL: string) {
|
||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
baseURL,
|
baseURL,
|
||||||
});
|
// 为每个请求携带 Authorization
|
||||||
|
makeAuthorization: () => {
|
||||||
/**
|
return {
|
||||||
* 重新认证逻辑
|
// 默认
|
||||||
*/
|
key: 'Authorization',
|
||||||
async function doReAuthenticate() {
|
tokenHandler: () => {
|
||||||
console.warn('Access token or refresh token is invalid or expired. ');
|
const accessStore = useAccessStore();
|
||||||
|
return {
|
||||||
|
refreshToken: `${accessStore.refreshToken}`,
|
||||||
|
token: `${accessStore.accessToken}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
unAuthorizedHandler: async () => {
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
accessStore.setAccessToken(null);
|
accessStore.setAccessToken(null);
|
||||||
if (
|
|
||||||
preferences.app.loginExpiredMode === 'modal' &&
|
if (preferences.app.loginExpiredMode === 'modal') {
|
||||||
accessStore.isAccessChecked
|
|
||||||
) {
|
|
||||||
accessStore.setLoginExpired(true);
|
accessStore.setLoginExpired(true);
|
||||||
} else {
|
} else {
|
||||||
|
// 退出登录
|
||||||
await authStore.logout();
|
await authStore.logout();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
makeErrorMessage: (msg) => ElMessage.error(msg),
|
||||||
|
|
||||||
/**
|
makeRequestHeaders: () => {
|
||||||
* 刷新token逻辑
|
return {
|
||||||
*/
|
// 为每个请求携带 Accept-Language
|
||||||
async function doRefreshToken() {
|
'Accept-Language': preferences.app.locale,
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
client.addResponseInterceptor<HttpResponse>((response) => {
|
||||||
// response数据解构
|
|
||||||
client.addResponseInterceptor<HttpResponse>({
|
|
||||||
fulfilled: (response) => {
|
|
||||||
const { data: responseData, status } = response;
|
const { data: responseData, status } = response;
|
||||||
|
|
||||||
const { code, data, message: msg } = responseData;
|
const { code, data, message: msg } = responseData;
|
||||||
|
|
@ -79,31 +60,8 @@ function createRequestClient(baseURL: string) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
throw new Error(`Error ${status}: ${msg}`);
|
throw new Error(`Error ${status}: ${msg}`);
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// token过期的处理
|
|
||||||
client.addResponseInterceptor(
|
|
||||||
authenticateResponseInterceptor({
|
|
||||||
client,
|
|
||||||
doReAuthenticate,
|
|
||||||
doRefreshToken,
|
|
||||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
|
||||||
formatToken,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
|
||||||
client.addResponseInterceptor(
|
|
||||||
errorMessageResponseInterceptor((msg: string, _error) => {
|
|
||||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
|
||||||
ElMessage.error(msg);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const requestClient = createRequestClient(apiURL);
|
export const requestClient = createRequestClient(apiURL);
|
||||||
|
|
||||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { createApp } from 'vue';
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
import '@vben/styles/ele';
|
|
||||||
|
|
||||||
import { setupI18n } from '#/locales';
|
import { setupI18n } from '#/locales';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
import { AuthPageLayout } from '@vben/layouts';
|
|
||||||
import { preferences } from '@vben/preferences';
|
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
const appName = computed(() => preferences.app.name);
|
|
||||||
const logo = computed(() => preferences.logo.source);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<AuthPageLayout
|
|
||||||
:app-name="appName"
|
|
||||||
:logo="logo"
|
|
||||||
:page-description="$t('authentication.pageDesc')"
|
|
||||||
:page-title="$t('authentication.pageTitle')"
|
|
||||||
>
|
|
||||||
<!-- 自定义工具栏 -->
|
|
||||||
<!-- <template #toolbar></template> -->
|
|
||||||
</AuthPageLayout>
|
|
||||||
</template>
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
import type { NotificationItem } from '@vben/layouts';
|
import type { NotificationItem } from '@vben/layouts';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
import { LOGIN_PATH, VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||||
import {
|
import {
|
||||||
BasicLayout,
|
BasicLayout,
|
||||||
|
|
@ -13,12 +14,17 @@ import {
|
||||||
UserDropdown,
|
UserDropdown,
|
||||||
} from '@vben/layouts';
|
} from '@vben/layouts';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
import {
|
||||||
|
resetAllStores,
|
||||||
|
storeToRefs,
|
||||||
|
useAccessStore,
|
||||||
|
useUserStore,
|
||||||
|
} from '@vben/stores';
|
||||||
import { openWindow } from '@vben/utils';
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { resetRoutes } from '#/router';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
|
||||||
|
|
||||||
const notifications = ref<NotificationItem[]>([
|
const notifications = ref<NotificationItem[]>([
|
||||||
{
|
{
|
||||||
|
|
@ -88,12 +94,18 @@ const menus = computed(() => [
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const { loginLoading } = storeToRefs(authStore);
|
||||||
|
|
||||||
const avatar = computed(() => {
|
const avatar = computed(() => {
|
||||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
await authStore.logout(false);
|
resetAllStores();
|
||||||
|
resetRoutes();
|
||||||
|
await router.replace(LOGIN_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNoticeClear() {
|
function handleNoticeClear() {
|
||||||
|
|
@ -129,9 +141,11 @@ function handleMakeAll() {
|
||||||
<AuthenticationLoginExpiredModal
|
<AuthenticationLoginExpiredModal
|
||||||
v-model:open="accessStore.loginExpired"
|
v-model:open="accessStore.loginExpired"
|
||||||
:avatar
|
:avatar
|
||||||
>
|
:loading="loginLoading"
|
||||||
<LoginForm />
|
password-placeholder="123456"
|
||||||
</AuthenticationLoginExpiredModal>
|
username-placeholder="vben"
|
||||||
|
@submit="authStore.authLogin"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #lock-screen>
|
<template #lock-screen>
|
||||||
<LockScreen :avatar @to-login="handleLogout" />
|
<LockScreen :avatar @to-login="handleLogout" />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
const BasicLayout = () => import('./basic.vue');
|
const BasicLayout = () => import('./basic.vue');
|
||||||
const AuthPageLayout = () => import('./auth.vue');
|
|
||||||
|
|
||||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||||
|
|
||||||
|
const AuthPageLayout = () =>
|
||||||
|
import('@vben/layouts').then((m) => m.AuthPageLayout);
|
||||||
|
|
||||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||||
|
|
|
||||||
|
|
@ -91,4 +91,4 @@ async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { $t, elementLocale, setupI18n };
|
export { $t, elementLocale, loadMessages, setupI18n };
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,10 @@ function setupAccessGuard(router: Router) {
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accessRoutes = accessStore.accessRoutes;
|
||||||
|
|
||||||
// 是否已经生成过动态路由
|
// 是否已经生成过动态路由
|
||||||
if (accessStore.isAccessChecked) {
|
if (accessRoutes && accessRoutes.length > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +115,6 @@ function setupAccessGuard(router: Router) {
|
||||||
// 保存菜单信息和路由信息
|
// 保存菜单信息和路由信息
|
||||||
accessStore.setAccessMenus(accessibleMenus);
|
accessStore.setAccessMenus(accessibleMenus);
|
||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
|
||||||
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
const redirectPath = (from.query.redirect ?? to.fullPath) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,7 @@ const router = createRouter({
|
||||||
: createWebHistory(import.meta.env.VITE_BASE),
|
: createWebHistory(import.meta.env.VITE_BASE),
|
||||||
// 应该添加到路由的初始路由列表。
|
// 应该添加到路由的初始路由列表。
|
||||||
routes,
|
routes,
|
||||||
scrollBehavior: (to, _from, savedPosition) => {
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
if (savedPosition) {
|
|
||||||
return savedPosition;
|
|
||||||
}
|
|
||||||
return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };
|
|
||||||
},
|
|
||||||
// 是否应该禁止尾部斜杠。
|
// 是否应该禁止尾部斜杠。
|
||||||
// strict: true,
|
// strict: true,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ const routes: RouteRecordRaw[] = [
|
||||||
path: '/workspace',
|
path: '/workspace',
|
||||||
component: () => import('#/views/dashboard/workspace/index.vue'),
|
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'carbon:workspace',
|
|
||||||
title: $t('page.dashboard.workspace'),
|
title: $t('page.dashboard.workspace'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import {
|
||||||
VBEN_LOGO_URL,
|
VBEN_LOGO_URL,
|
||||||
VBEN_NAIVE_PREVIEW_URL,
|
VBEN_NAIVE_PREVIEW_URL,
|
||||||
} from '@vben/constants';
|
} from '@vben/constants';
|
||||||
import { SvgAntdvLogoIcon } from '@vben/icons';
|
|
||||||
|
|
||||||
import { BasicLayout, IFrameView } from '#/layouts';
|
import { BasicLayout, IFrameView } from '#/layouts';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
@ -27,7 +26,7 @@ const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
name: 'VbenAbout',
|
name: 'VbenAbout',
|
||||||
path: '/vben-admin/about',
|
path: '/vben-admin/about',
|
||||||
component: () => import('#/views/_core/about/index.vue'),
|
component: () => import('#/views/_core/vben/about/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'lucide:copyright',
|
icon: 'lucide:copyright',
|
||||||
title: $t('page.vben.about'),
|
title: $t('page.vben.about'),
|
||||||
|
|
@ -59,7 +58,6 @@ const routes: RouteRecordRaw[] = [
|
||||||
component: IFrameView,
|
component: IFrameView,
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
icon: 'logos:naiveui',
|
|
||||||
link: VBEN_NAIVE_PREVIEW_URL,
|
link: VBEN_NAIVE_PREVIEW_URL,
|
||||||
title: $t('page.vben.naive-ui'),
|
title: $t('page.vben.naive-ui'),
|
||||||
},
|
},
|
||||||
|
|
@ -70,7 +68,6 @@ const routes: RouteRecordRaw[] = [
|
||||||
component: IFrameView,
|
component: IFrameView,
|
||||||
meta: {
|
meta: {
|
||||||
badgeType: 'dot',
|
badgeType: 'dot',
|
||||||
icon: SvgAntdvLogoIcon,
|
|
||||||
link: VBEN_ANT_PREVIEW_URL,
|
link: VBEN_ANT_PREVIEW_URL,
|
||||||
title: $t('page.vben.antdv'),
|
title: $t('page.vben.antdv'),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { ElNotification } from 'element-plus';
|
import { ElNotification } from 'element-plus';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
import { getAccessCodesApi, getUserInfoApi, loginApi } from '#/api';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
|
@ -33,12 +33,13 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
let userInfo: null | UserInfo = null;
|
let userInfo: null | UserInfo = null;
|
||||||
try {
|
try {
|
||||||
loginLoading.value = true;
|
loginLoading.value = true;
|
||||||
const { accessToken } = await loginApi(params);
|
const { accessToken, refreshToken } = await loginApi(params);
|
||||||
|
|
||||||
// 如果成功获取到 accessToken
|
// 如果成功获取到 accessToken
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
// 将 accessToken 存储到 accessStore 中
|
// 将 accessToken 存储到 accessStore 中
|
||||||
accessStore.setAccessToken(accessToken);
|
accessStore.setAccessToken(accessToken);
|
||||||
|
accessStore.setRefreshToken(refreshToken);
|
||||||
|
|
||||||
// 获取用户信息并存储到 accessStore 中
|
// 获取用户信息并存储到 accessStore 中
|
||||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||||
|
|
@ -76,23 +77,16 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logout(redirect: boolean = true) {
|
async function logout() {
|
||||||
try {
|
|
||||||
await logoutApi();
|
|
||||||
} catch {
|
|
||||||
// 不做任何处理
|
|
||||||
}
|
|
||||||
resetAllStores();
|
resetAllStores();
|
||||||
accessStore.setLoginExpired(false);
|
accessStore.setLoginExpired(false);
|
||||||
|
|
||||||
// 回登陆页带上当前路由地址
|
// 回登陆页带上当前路由地址
|
||||||
await router.replace({
|
await router.replace({
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
query: redirect
|
query: {
|
||||||
? {
|
|
||||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||||
}
|
},
|
||||||
: {},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,15 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
|
import type { LoginCodeParams } from '@vben/common-ui';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
import { AuthenticationCodeLogin } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
defineOptions({ name: 'CodeLogin' });
|
defineOptions({ name: 'CodeLogin' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'VbenInput',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: $t('authentication.mobile'),
|
|
||||||
},
|
|
||||||
fieldName: 'phoneNumber',
|
|
||||||
label: $t('authentication.mobile'),
|
|
||||||
rules: z
|
|
||||||
.string()
|
|
||||||
.min(1, { message: $t('authentication.mobileTip') })
|
|
||||||
.refine((v) => /^\d{11}$/.test(v), {
|
|
||||||
message: $t('authentication.mobileErrortip'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenPinInput',
|
|
||||||
componentProps: {
|
|
||||||
createText: (countdown: number) => {
|
|
||||||
const text =
|
|
||||||
countdown > 0
|
|
||||||
? $t('authentication.sendText', [countdown])
|
|
||||||
: $t('authentication.sendCode');
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
placeholder: $t('authentication.code'),
|
|
||||||
},
|
|
||||||
fieldName: 'code',
|
|
||||||
label: $t('authentication.code'),
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
/**
|
/**
|
||||||
* 异步处理登录操作
|
* 异步处理登录操作
|
||||||
* Asynchronously handle the login process
|
* Asynchronously handle the login process
|
||||||
|
|
@ -57,8 +23,8 @@ async function handleLogin(values: LoginCodeParams) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationCodeLogin
|
<AuthenticationCodeLogin
|
||||||
:form-schema="formSchema"
|
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:login-path="LOGIN_PATH"
|
||||||
@submit="handleLogin"
|
@submit="handleLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,13 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VbenFormSchema } from '@vben/common-ui';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { AuthenticationForgetPassword } from '@vben/common-ui';
|
||||||
|
import { LOGIN_PATH } from '@vben/constants';
|
||||||
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
defineOptions({ name: 'ForgetPassword' });
|
defineOptions({ name: 'ForgetPassword' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'VbenInput',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: 'example@example.com',
|
|
||||||
},
|
|
||||||
fieldName: 'email',
|
|
||||||
label: $t('authentication.email'),
|
|
||||||
rules: z
|
|
||||||
.string()
|
|
||||||
.min(1, { message: $t('authentication.emailTip') })
|
|
||||||
.email($t('authentication.emailValidErrorTip')),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleSubmit(value: string) {
|
function handleSubmit(value: string) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('reset email:', value);
|
console.log('reset email:', value);
|
||||||
|
|
@ -35,8 +16,8 @@ function handleSubmit(value: string) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationForgetPassword
|
<AuthenticationForgetPassword
|
||||||
:form-schema="formSchema"
|
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:login-path="LOGIN_PATH"
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,18 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VbenFormSchema } from '@vben/common-ui';
|
import { AuthenticationLogin } from '@vben/common-ui';
|
||||||
import type { BasicOption } from '@vben/types';
|
|
||||||
|
|
||||||
import { computed, markRaw } from 'vue';
|
|
||||||
|
|
||||||
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
defineOptions({ name: 'Login' });
|
defineOptions({ name: 'Login' });
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
const MOCK_USER_OPTIONS: BasicOption[] = [
|
|
||||||
{
|
|
||||||
label: 'Super',
|
|
||||||
value: 'vben',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Admin',
|
|
||||||
value: 'admin',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'User',
|
|
||||||
value: 'jack',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'VbenSelect',
|
|
||||||
componentProps: {
|
|
||||||
options: MOCK_USER_OPTIONS,
|
|
||||||
placeholder: $t('authentication.selectAccount'),
|
|
||||||
},
|
|
||||||
fieldName: 'selectAccount',
|
|
||||||
label: $t('authentication.selectAccount'),
|
|
||||||
rules: z
|
|
||||||
.string()
|
|
||||||
.min(1, { message: $t('authentication.selectAccount') })
|
|
||||||
.optional()
|
|
||||||
.default('vben'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenInput',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: $t('authentication.usernameTip'),
|
|
||||||
},
|
|
||||||
dependencies: {
|
|
||||||
trigger(values, form) {
|
|
||||||
if (values.selectAccount) {
|
|
||||||
const findUser = MOCK_USER_OPTIONS.find(
|
|
||||||
(item) => item.value === values.selectAccount,
|
|
||||||
);
|
|
||||||
if (findUser) {
|
|
||||||
form.setValues({
|
|
||||||
password: '123456',
|
|
||||||
username: findUser.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
triggerFields: ['selectAccount'],
|
|
||||||
},
|
|
||||||
fieldName: 'username',
|
|
||||||
label: $t('authentication.username'),
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenInputPassword',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: $t('authentication.password'),
|
|
||||||
},
|
|
||||||
fieldName: 'password',
|
|
||||||
label: $t('authentication.password'),
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: markRaw(SliderCaptcha),
|
|
||||||
fieldName: 'captcha',
|
|
||||||
rules: z.boolean().refine((value) => value, {
|
|
||||||
message: $t('authentication.verifyRequiredTip'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationLogin
|
<AuthenticationLogin
|
||||||
:form-schema="formSchema"
|
|
||||||
:loading="authStore.loginLoading"
|
:loading="authStore.loginLoading"
|
||||||
|
password-placeholder="123456"
|
||||||
|
username-placeholder="vben"
|
||||||
@submit="authStore.authLogin"
|
@submit="authStore.authLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,91 +1,15 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
|
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
||||||
|
|
||||||
import { computed, h, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationRegister, z } from '@vben/common-ui';
|
import { AuthenticationRegister } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
defineOptions({ name: 'Register' });
|
defineOptions({ name: 'Register' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
component: 'VbenInput',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: $t('authentication.usernameTip'),
|
|
||||||
},
|
|
||||||
fieldName: 'username',
|
|
||||||
label: $t('authentication.username'),
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenInputPassword',
|
|
||||||
componentProps: {
|
|
||||||
passwordStrength: true,
|
|
||||||
placeholder: $t('authentication.password'),
|
|
||||||
},
|
|
||||||
fieldName: 'password',
|
|
||||||
label: $t('authentication.password'),
|
|
||||||
renderComponentContent() {
|
|
||||||
return {
|
|
||||||
strengthText: () => $t('authentication.passwordStrength'),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenInputPassword',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: $t('authentication.confirmPassword'),
|
|
||||||
},
|
|
||||||
dependencies: {
|
|
||||||
rules(values) {
|
|
||||||
const { password } = values;
|
|
||||||
return z
|
|
||||||
.string()
|
|
||||||
.min(1, { message: $t('authentication.passwordTip') })
|
|
||||||
.refine((value) => value === password, {
|
|
||||||
message: $t('authentication.confirmPasswordTip'),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
triggerFields: ['password'],
|
|
||||||
},
|
|
||||||
fieldName: 'confirmPassword',
|
|
||||||
label: $t('authentication.confirmPassword'),
|
|
||||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenCheckbox',
|
|
||||||
fieldName: 'agreePolicy',
|
|
||||||
renderComponentContent: () => ({
|
|
||||||
default: () =>
|
|
||||||
h('span', [
|
|
||||||
$t('authentication.agree'),
|
|
||||||
h(
|
|
||||||
'a',
|
|
||||||
{
|
|
||||||
class:
|
|
||||||
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
|
|
||||||
href: '',
|
|
||||||
},
|
|
||||||
[
|
|
||||||
$t('authentication.privacyPolicy'),
|
|
||||||
'&',
|
|
||||||
$t('authentication.terms'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
}),
|
|
||||||
rules: z.boolean().refine((value) => !!value, {
|
|
||||||
message: $t('authentication.agreeTip'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleSubmit(value: LoginAndRegisterParams) {
|
function handleSubmit(value: LoginAndRegisterParams) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('register submit:', value);
|
console.log('register submit:', value);
|
||||||
|
|
@ -94,8 +18,8 @@ function handleSubmit(value: LoginAndRegisterParams) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationRegister
|
<AuthenticationRegister
|
||||||
:form-schema="formSchema"
|
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:login-path="LOGIN_PATH"
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
@ -55,27 +51,12 @@ onMounted(() => {
|
||||||
},
|
},
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
},
|
},
|
||||||
// xAxis: {
|
|
||||||
// axisTick: {
|
|
||||||
// show: false,
|
|
||||||
// },
|
|
||||||
// boundaryGap: false,
|
|
||||||
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
|
||||||
// type: 'category',
|
|
||||||
// },
|
|
||||||
xAxis: {
|
xAxis: {
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
type: 'solid',
|
|
||||||
width: 1,
|
|
||||||
},
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
type: 'category',
|
type: 'category',
|
||||||
},
|
},
|
||||||
yAxis: [
|
yAxis: [
|
||||||
|
|
@ -84,10 +65,7 @@ onMounted(() => {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
max: 80_000,
|
max: 80_000,
|
||||||
splitArea: {
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
splitNumber: 4,
|
|
||||||
type: 'value',
|
type: 'value',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import {
|
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||||
EchartsUI,
|
|
||||||
type EchartsUIType,
|
|
||||||
useEcharts,
|
|
||||||
} from '@vben/plugins/echarts';
|
|
||||||
|
|
||||||
const chartRef = ref<EchartsUIType>();
|
const chartRef = ref<EchartsUIType>();
|
||||||
const { renderEcharts } = useEcharts(chartRef);
|
const { renderEcharts } = useEcharts(chartRef);
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue