修改
This commit is contained in:
parent
cbde1b330f
commit
fed3ce1a01
@ -1,4 +0,0 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
||||
@ -1 +0,0 @@
|
||||
export { default } from '@vben/commitlint-config';
|
||||
@ -1,7 +0,0 @@
|
||||
node_modules
|
||||
.git
|
||||
.gitignore
|
||||
*.md
|
||||
dist
|
||||
.turbo
|
||||
dist.zip
|
||||
@ -1,18 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=lf
|
||||
insert_final_newline=true
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
max_line_length = 100
|
||||
trim_trailing_whitespace = true
|
||||
quote_type = single
|
||||
|
||||
[*.{yml,yaml,json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
11
.gitattributes
vendored
11
.gitattributes
vendored
@ -1,11 +0,0 @@
|
||||
# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings
|
||||
|
||||
# Automatically normalize line endings (to LF) for all text-based files.
|
||||
* text=auto eol=lf
|
||||
|
||||
# Declare files that will always have CRLF line endings on checkout.
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary
|
||||
@ -1,2 +0,0 @@
|
||||
[core]
|
||||
ignorecase = false
|
||||
56
.gitignore
vendored
56
.gitignore
vendored
@ -1,56 +0,0 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
dist.zip
|
||||
dist.tar
|
||||
dist.war
|
||||
.nitro
|
||||
.output
|
||||
*-dist.zip
|
||||
*-dist.tar
|
||||
*-dist.war
|
||||
coverage
|
||||
*.local
|
||||
**/.vitepress/cache
|
||||
.cache
|
||||
.turbo
|
||||
.temp
|
||||
dev-dist
|
||||
.stylelintcache
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
.VSCodeCounter
|
||||
**/backend-mock/data
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
.eslintcache
|
||||
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
vite.config.mts.*
|
||||
vite.config.mjs.*
|
||||
vite.config.js.*
|
||||
vite.config.ts.*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
# .vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.history
|
||||
/docs
|
||||
/playground
|
||||
/apps/backend-mock
|
||||
/playground
|
||||
/docs
|
||||
@ -1,6 +0,0 @@
|
||||
ports:
|
||||
- port: 5555
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- init: npm i -g corepack && pnpm install
|
||||
command: pnpm run dev:play
|
||||
@ -1 +0,0 @@
|
||||
22.1.0
|
||||
13
.npmrc
13
.npmrc
@ -1,13 +0,0 @@
|
||||
registry = "https://registry.npmmirror.com"
|
||||
public-hoist-pattern[]=lefthook
|
||||
public-hoist-pattern[]=eslint
|
||||
public-hoist-pattern[]=prettier
|
||||
public-hoist-pattern[]=prettier-plugin-tailwindcss
|
||||
public-hoist-pattern[]=stylelint
|
||||
public-hoist-pattern[]=*postcss*
|
||||
public-hoist-pattern[]=@commitlint/*
|
||||
public-hoist-pattern[]=czg
|
||||
|
||||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
dedupe-peer-dependents=true
|
||||
@ -1,18 +0,0 @@
|
||||
dist
|
||||
dev-dist
|
||||
.local
|
||||
.output.js
|
||||
node_modules
|
||||
.nvmrc
|
||||
coverage
|
||||
CODEOWNERS
|
||||
.nitro
|
||||
.output
|
||||
|
||||
|
||||
**/*.svg
|
||||
**/*.sh
|
||||
|
||||
public
|
||||
.npmrc
|
||||
*-lock.yaml
|
||||
@ -1 +0,0 @@
|
||||
export { default } from '@vben/prettier-config';
|
||||
@ -1,4 +0,0 @@
|
||||
dist
|
||||
public
|
||||
__tests__
|
||||
coverage
|
||||
30
.vscode/extensions.json
vendored
30
.vscode/extensions.json
vendored
@ -1,30 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
// Vue 3 的语言支持
|
||||
"Vue.volar",
|
||||
// 将 ESLint JavaScript 集成到 VS Code 中。
|
||||
"dbaeumer.vscode-eslint",
|
||||
// Visual Studio Code 的官方 Stylelint 扩展
|
||||
"stylelint.vscode-stylelint",
|
||||
// 使用 Prettier 的代码格式化程序
|
||||
"esbenp.prettier-vscode",
|
||||
// 支持 dotenv 文件语法
|
||||
"mikestead.dotenv",
|
||||
// 源代码的拼写检查器
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
// Tailwind CSS 的官方 VS Code 插件
|
||||
"bradlc.vscode-tailwindcss",
|
||||
// iconify 图标插件
|
||||
"antfu.iconify",
|
||||
// i18n 插件
|
||||
"Lokalise.i18n-ally",
|
||||
// CSS 变量提示
|
||||
"vunguyentuan.vscode-css-variables",
|
||||
// 在 package.json 中显示 PNPM catalog 的版本
|
||||
"antfu.pnpm-catalog-lens"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
// 和 volar 冲突
|
||||
"octref.vetur"
|
||||
]
|
||||
}
|
||||
37
.vscode/global.code-snippets
vendored
37
.vscode/global.code-snippets
vendored
@ -1,37 +0,0 @@
|
||||
{
|
||||
"import": {
|
||||
"scope": "javascript,typescript",
|
||||
"prefix": "im",
|
||||
"body": ["import { $2 } from '$1';"],
|
||||
"description": "Import a module",
|
||||
},
|
||||
"export-all": {
|
||||
"scope": "javascript,typescript",
|
||||
"prefix": "ex",
|
||||
"body": ["export * from '$1';"],
|
||||
"description": "Export a module",
|
||||
},
|
||||
"vue-script-setup": {
|
||||
"scope": "vue",
|
||||
"prefix": "<sc",
|
||||
"body": [
|
||||
"<script setup lang=\"ts\">",
|
||||
"const props = defineProps<{",
|
||||
" modelValue?: boolean,",
|
||||
"}>()",
|
||||
"$1",
|
||||
"</script>",
|
||||
"",
|
||||
"<template>",
|
||||
" <div>",
|
||||
" <slot/>",
|
||||
" </div>",
|
||||
"</template>",
|
||||
],
|
||||
},
|
||||
"vue-computed": {
|
||||
"scope": "javascript,typescript,vue",
|
||||
"prefix": "com",
|
||||
"body": ["computed(() => { $1 })"],
|
||||
},
|
||||
}
|
||||
42
.vscode/launch.json
vendored
42
.vscode/launch.json
vendored
@ -1,42 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"name": "vben admin playground dev",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5555",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}/playground"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"name": "vben admin antd dev",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5666",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}/apps/web-antd"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"name": "vben admin ele dev",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5777",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}/apps/web-ele"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"name": "vben admin naive dev",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5888",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}/apps/web-naive"
|
||||
}
|
||||
]
|
||||
}
|
||||
241
.vscode/settings.json
vendored
241
.vscode/settings.json
vendored
@ -1,241 +0,0 @@
|
||||
{
|
||||
"tailwindCSS.experimental.configFile": "internal/tailwind-config/src/index.ts",
|
||||
// workbench
|
||||
"workbench.list.smoothScrolling": true,
|
||||
"workbench.startupEditor": "newUntitledFile",
|
||||
"workbench.tree.indent": 10,
|
||||
"workbench.editor.highlightModifiedTabs": true,
|
||||
"workbench.editor.closeOnFileDelete": true,
|
||||
"workbench.editor.limit.enabled": true,
|
||||
"workbench.editor.limit.perEditorGroup": true,
|
||||
"workbench.editor.limit.value": 5,
|
||||
|
||||
// editor
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.cursorBlinking": "expand",
|
||||
"editor.largeFileOptimizations": true,
|
||||
"editor.accessibilitySupport": "off",
|
||||
"editor.cursorSmoothCaretAnimation": "on",
|
||||
"editor.guides.bracketPairs": "active",
|
||||
"editor.inlineSuggest.enabled": true,
|
||||
"editor.suggestSelection": "recentlyUsedByPrefix",
|
||||
"editor.acceptSuggestionOnEnter": "smart",
|
||||
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
||||
"editor.stickyScroll.enabled": true,
|
||||
"editor.hover.sticky": true,
|
||||
"editor.suggest.insertMode": "replace",
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.autoClosingBrackets": "beforeWhitespace",
|
||||
"editor.autoClosingDelete": "always",
|
||||
"editor.autoClosingOvertype": "always",
|
||||
"editor.autoClosingQuotes": "beforeWhitespace",
|
||||
"editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.fixAll.stylelint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "Vue.volar"
|
||||
},
|
||||
// extensions
|
||||
"extensions.ignoreRecommendations": true,
|
||||
|
||||
// terminal
|
||||
"terminal.integrated.cursorBlinking": true,
|
||||
"terminal.integrated.persistentSessionReviveProcess": "never",
|
||||
"terminal.integrated.tabs.enabled": true,
|
||||
"terminal.integrated.scrollback": 10000,
|
||||
"terminal.integrated.stickyScroll.enabled": true,
|
||||
|
||||
// files
|
||||
"files.eol": "\n",
|
||||
"files.insertFinalNewline": true,
|
||||
"files.simpleDialog.enable": true,
|
||||
"files.associations": {
|
||||
"*.ejs": "html",
|
||||
"*.art": "html",
|
||||
"**/tsconfig.json": "jsonc",
|
||||
"*.json": "jsonc",
|
||||
"package.json": "json"
|
||||
},
|
||||
|
||||
"files.exclude": {
|
||||
"**/.eslintcache": true,
|
||||
"**/bower_components": true,
|
||||
"**/.turbo": true,
|
||||
"**/.idea": true,
|
||||
"**/.vitepress": true,
|
||||
"**/tmp": true,
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.stylelintcache": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/vite.config.mts.*": true,
|
||||
"**/tea.yaml": true
|
||||
},
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/.vscode/**": true,
|
||||
"**/node_modules/**": true,
|
||||
"**/tmp/**": true,
|
||||
"**/bower_components/**": true,
|
||||
"**/dist/**": true,
|
||||
"**/yarn.lock": true
|
||||
},
|
||||
|
||||
"typescript.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"],
|
||||
|
||||
// search
|
||||
"search.searchEditor.singleClickBehaviour": "peekDefinition",
|
||||
"search.followSymlinks": false,
|
||||
// 在使用搜索功能时,将这些文件夹/文件排除在外
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/*.log": true,
|
||||
"**/*.log*": true,
|
||||
"**/bower_components": true,
|
||||
"**/dist": true,
|
||||
"**/elehukouben": true,
|
||||
"**/.git": true,
|
||||
"**/.github": true,
|
||||
"**/.gitignore": true,
|
||||
"**/.svn": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/.vitepress/cache": true,
|
||||
"**/.idea": true,
|
||||
"**/.vscode": false,
|
||||
"**/.yarn": true,
|
||||
"**/tmp": true,
|
||||
"*.xml": true,
|
||||
"out": true,
|
||||
"dist": true,
|
||||
"node_modules": true,
|
||||
"CHANGELOG.md": true,
|
||||
"**/pnpm-lock.yaml": true,
|
||||
"**/yarn.lock": true
|
||||
},
|
||||
|
||||
"debug.onTaskErrors": "debugAnyway",
|
||||
"diffEditor.ignoreTrimWhitespace": false,
|
||||
"npm.packageManager": "pnpm",
|
||||
|
||||
"css.validate": false,
|
||||
"less.validate": false,
|
||||
"scss.validate": false,
|
||||
|
||||
// extension
|
||||
"emmet.showSuggestionsAsSnippets": true,
|
||||
"emmet.triggerExpansionOnTab": false,
|
||||
|
||||
"errorLens.enabledDiagnosticLevels": ["warning", "error"],
|
||||
"errorLens.excludeBySource": ["cSpell", "Grammarly", "eslint"],
|
||||
|
||||
"stylelint.enable": true,
|
||||
"stylelint.packageManager": "pnpm",
|
||||
"stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
|
||||
"stylelint.customSyntax": "postcss-html",
|
||||
"stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
|
||||
|
||||
"typescript.inlayHints.enumMemberValues.enabled": true,
|
||||
"typescript.preferences.preferTypeOnlyAutoImports": true,
|
||||
"typescript.preferences.includePackageJsonAutoImports": "on",
|
||||
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"typescript",
|
||||
"javascriptreact",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"json5"
|
||||
],
|
||||
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
],
|
||||
|
||||
"github.copilot.enable": {
|
||||
"*": true,
|
||||
"markdown": true,
|
||||
"plaintext": false,
|
||||
"yaml": false
|
||||
},
|
||||
|
||||
"cssVariables.lookupFiles": ["packages/core/base/design/src/**/*.css"],
|
||||
|
||||
"i18n-ally.localesPaths": [
|
||||
"packages/locales/src/langs",
|
||||
"playground/src/locales/langs",
|
||||
"apps/*/src/locales/langs"
|
||||
],
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespace}.{ext}",
|
||||
"i18n-ally.enabledParsers": ["json"],
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.namespace": true,
|
||||
|
||||
// 控制相关文件嵌套展示
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.expand": false,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"*.ts": "$(capture).test.ts, $(capture).test.tsx, $(capture).spec.ts, $(capture).spec.tsx, $(capture).d.ts",
|
||||
"*.tsx": "$(capture).test.ts, $(capture).test.tsx, $(capture).spec.ts, $(capture).spec.tsx,$(capture).d.ts",
|
||||
"*.env": "$(capture).env.*",
|
||||
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
|
||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml",
|
||||
"tailwind.config.mjs": "postcss.*"
|
||||
},
|
||||
"commentTranslate.hover.enabled": false,
|
||||
"commentTranslate.multiLineMerge": true,
|
||||
"vue.server.hybridMode": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"oxc.enable": false,
|
||||
"cSpell.words": [
|
||||
"archiver",
|
||||
"axios",
|
||||
"dotenv",
|
||||
"isequal",
|
||||
"jspm",
|
||||
"napi",
|
||||
"nolebase",
|
||||
"rollup",
|
||||
"vitest"
|
||||
]
|
||||
}
|
||||
44
Dockerfile
44
Dockerfile
@ -1,44 +0,0 @@
|
||||
FROM node:20-slim AS builder
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
ENV NODE_OPTIONS=--max-old-space-size=8192
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /vben5
|
||||
|
||||
# copy package.json and pnpm-lock.yaml to workspace
|
||||
COPY . /vben5
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
# 如果你的项目中使用了 antd,需要执行下面的命令
|
||||
RUN pnpm build:antd
|
||||
|
||||
# 如果你的项目中使用了 element-ui,需要执行下面的命令
|
||||
#RUN pnpm build:ele
|
||||
|
||||
# 如果你的项目中使用了 naive,需要执行下面的命令
|
||||
#RUN pnpm build:naive
|
||||
|
||||
RUN echo "Builder Success 🎉"
|
||||
|
||||
FROM nginx:stable-alpine AS production
|
||||
|
||||
RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf
|
||||
|
||||
# 如果你的项目中使用了 antd,需要执行下面的命令
|
||||
COPY --from=builder /vben5/apps/web-antd/dist /usr/share/nginx/html
|
||||
|
||||
# 如果你的项目中使用了 element-ui,需要执行下面的命令
|
||||
#COPY --from=builder /vben5/apps/web-ele/dist /usr/share/nginx/html
|
||||
|
||||
# 如果你的项目中使用了 naive,需要执行下面的命令
|
||||
#COPY --from=builder /vben5/apps/web-naive/dist /usr/share/nginx/html
|
||||
|
||||
COPY --from=builder /vben5/_nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
# start nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
24
LICENSE
24
LICENSE
@ -1,24 +0,0 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2024, WangJunZzz
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
109
README.md
109
README.md
@ -1,109 +0,0 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/WangJunZzz/abp-vnext-pro">
|
||||
<img src="https://blog-resouce.oss-cn-shenzhen.aliyuncs.com/images/abp/06.jpg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">Abp Vnext Pro Vben5 ...</h1>
|
||||
|
||||
|
||||
|
||||
## 🔗 链接
|
||||
|
||||
- [AbpPro Vben2预览](http://182.43.18.151:44318/)
|
||||
- [AbpPro Vben5预览](http://182.43.18.151:44320/)
|
||||
- [代码生成器预览](http://182.43.18.151:44311/)
|
||||
- [文档地址](http://doc.cncore.club/)
|
||||
- [国内文档地址](http://doc.china.cncore.club:81/)
|
||||
- [视频教程](https://www.bilibili.com/video/BV1pt4y1E7aZ)
|
||||
- [代码生成器仓库地址](https://github.com/WangJunZzz/abp-vnext-pro-suite)
|
||||
|
||||
## 📦 快速开始
|
||||
|
||||
- 安装Cli
|
||||
```bash
|
||||
dotnet tool install Lion.AbpPro.Cli -g
|
||||
```
|
||||
|
||||
- 更新Cli
|
||||
```bash
|
||||
dotnet tool update Lion.AbpPro.Cli -g
|
||||
```
|
||||
|
||||
### 三个项目模板
|
||||
- 源码版本
|
||||
|
||||
```bash
|
||||
lion.abp new -t pro -c 公司名称 -p 项目名称 -v 版本(默认LastRelease) -o 默认当前控制台执行目录
|
||||
```
|
||||
|
||||
- nuget版本
|
||||
|
||||
```bash
|
||||
lion.abp new -t pro.all -c 公司名称 -p 项目名称 -v 版本(默认LastRelease) -o 默认当前控制台执行目录
|
||||
```
|
||||
|
||||
|
||||
- 模块
|
||||
|
||||
```bash
|
||||
lion.abp new -t pro.module -c 公司名称 -p 项目名称 -v 版本(默认LastRelease) -o 默认当前控制台执行目录
|
||||
```
|
||||
|
||||
|
||||
## ✨ 系统功能
|
||||
|
||||
- [x] 用户管理
|
||||
- [x] 角色管理
|
||||
- [x] 审计日志
|
||||
- [x] 后台任务
|
||||
- [x] 集成事件
|
||||
- [x] SinglaR 消息通知(站内信)
|
||||
- [x] 多语言
|
||||
- [x] 数据字典
|
||||
- [x] 容器化部署
|
||||
- [x] 单元测试
|
||||
- [x] ES 日志
|
||||
- [x] Setting 管理
|
||||
- [x] 多租户
|
||||
- [x] 文件管理
|
||||
|
||||
|
||||
## 🤝 如何贡献
|
||||
|
||||
非常欢迎你的加入!提一个 Issue 或者提交一个 Pull Request。
|
||||
|
||||
**Pull Request:**
|
||||
|
||||
1. Fork 代码!
|
||||
2. 创建自己的分支: `git checkout -b feat/xxxx`
|
||||
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
|
||||
4. 推送您的分支: `git push origin feat/xxxx`
|
||||
5. 提交`pull request`
|
||||
|
||||
## Git 贡献提交规范
|
||||
|
||||
- 参考
|
||||
- `feat` 增加新功能
|
||||
- `fix` 修复问题/BUG
|
||||
- `style` 代码风格相关无影响运行结果的
|
||||
- `perf` 优化/性能提升
|
||||
- `refactor` 重构
|
||||
- `revert` 撤销修改
|
||||
- `test` 测试相关
|
||||
- `docs` 文档/注释
|
||||
- `chore` 依赖更新/脚手架配置修改等
|
||||
- `workflow` 工作流改进
|
||||
- `ci` 持续集成
|
||||
- `types` 类型定义文件更改
|
||||
- `wip` 开发中
|
||||
|
||||
## ✒️交流
|
||||
- QQ 1群:<s>686933575(已满)</s>
|
||||
- QQ 2群:862717726
|
||||
|
||||
## 💖赞助
|
||||
- Star就是对该项目的最大肯定!
|
||||
- 如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
|
||||

|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
|
||||
#user nobody;
|
||||
worker_processes 1;
|
||||
|
||||
#error_log logs/error.log;
|
||||
#error_log logs/error.log notice;
|
||||
#error_log logs/error.log info;
|
||||
|
||||
#pid logs/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
types {
|
||||
application/javascript js mjs;
|
||||
text/css css;
|
||||
text/html html;
|
||||
}
|
||||
|
||||
sendfile on;
|
||||
# tcp_nopush on;
|
||||
|
||||
#keepalive_timeout 0;
|
||||
# keepalive_timeout 65;
|
||||
|
||||
# gzip on;
|
||||
# gzip_buffers 32 16k;
|
||||
# gzip_comp_level 6;
|
||||
# gzip_min_length 1k;
|
||||
# gzip_static on;
|
||||
# gzip_types text/plain
|
||||
# text/css
|
||||
# application/javascript
|
||||
# application/json
|
||||
# application/x-javascript
|
||||
# text/xml
|
||||
# application/xml
|
||||
# application/xml+rss
|
||||
# text/javascript; #设置压缩的文件类型
|
||||
# gzip_vary on;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
index index.html;
|
||||
# Enable CORS
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header 'Access-Control-Max-Age' 1728000;
|
||||
add_header 'Content-Type' 'text/plain charset=UTF-8';
|
||||
add_header 'Content-Length' 0;
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
# 应用标题
|
||||
VITE_APP_TITLE=Abp Vben5 Ele
|
||||
|
||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||
VITE_APP_NAMESPACE=abp-vnext-pro-vben5-ele
|
||||
|
||||
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
|
||||
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
|
||||
@ -1,7 +0,0 @@
|
||||
# public path
|
||||
VITE_BASE=/
|
||||
|
||||
# Basic interface address SPA
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
||||
VITE_VISUALIZER=true
|
||||
@ -1,29 +0,0 @@
|
||||
# 端口号
|
||||
VITE_PORT=4200
|
||||
|
||||
VITE_BASE=/
|
||||
|
||||
# 接口地址
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
||||
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
||||
VITE_NITRO_MOCK=true
|
||||
|
||||
# 是否打开 devtools,true 为打开,false 为关闭
|
||||
VITE_DEVTOOLS=false
|
||||
|
||||
# vue-router 的模式
|
||||
VITE_ROUTER_HISTORY=history
|
||||
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# 是否打开刷新浏览器检查用户角色信息
|
||||
VITE_REFRESH_ROLE = true
|
||||
|
||||
# 后端接口地址
|
||||
VITE_APP_API_ADDRESS=http://localhost:44315
|
||||
|
||||
# websocket地址
|
||||
VITE_WEBSOCKET_URL=http://localhost:44315/signalr/notification
|
||||
@ -1,32 +0,0 @@
|
||||
VITE_BASE=/
|
||||
|
||||
# 接口地址
|
||||
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
|
||||
|
||||
# 是否开启压缩,可以设置为 none, brotli, gzip
|
||||
VITE_COMPRESS=none
|
||||
|
||||
# 是否开启 PWA
|
||||
VITE_PWA=false
|
||||
|
||||
# vue-router 的模式
|
||||
VITE_ROUTER_HISTORY=hash
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# vue-router 的模式
|
||||
VITE_ROUTER_HISTORY=history
|
||||
|
||||
|
||||
# 打包后是否生成dist.zip
|
||||
VITE_ARCHIVER=true
|
||||
|
||||
# 是否打开刷新浏览器检查用户角色信息
|
||||
VITE_REFRESH_ROLE = true
|
||||
|
||||
# 后端接口地址
|
||||
VITE_APP_API_ADDRESS=http://182.43.18.151:44317/
|
||||
|
||||
# websocket地址
|
||||
VITE_WEBSOCKET_URL=http://182.43.18.151:44317/signalr/notification
|
||||
@ -1,35 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta name="description" content="A Modern Back-end Management System" />
|
||||
<meta name="keywords" content="Vben Admin Vue3 Vite" />
|
||||
<meta name="author" content="Vben" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
||||
/>
|
||||
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
|
||||
<title><%= VITE_APP_TITLE %></title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<script>
|
||||
// 生产环境下注入百度统计
|
||||
if (window._VBEN_ADMIN_PRO_APP_CONF_) {
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement('script');
|
||||
hm.src =
|
||||
'https://hm.baidu.com/hm.js?97352b16ed2df8c3860cf5a1a65fb4dd';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,62 +0,0 @@
|
||||
{
|
||||
"name": "@vben/web-ele",
|
||||
"version": "5.5.6",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "apps/web-ele"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "vben",
|
||||
"email": "ann.vben@gmail.com",
|
||||
"url": "https://github.com/anncwb"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"nswag": "openapi-ts -f ./src/api-client-config/config.ts",
|
||||
"build": "pnpm vite build --mode production",
|
||||
"build:analyze": "pnpm vite build --mode analyze",
|
||||
"dev": "pnpm vite --mode development",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "vue-tsc --noEmit --skipLibCheck"
|
||||
},
|
||||
"imports": {
|
||||
"#/*": "./src/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/json": "^2.2.282",
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@vben/access": "workspace:*",
|
||||
"@vben/common-ui": "workspace:*",
|
||||
"@vben/constants": "workspace:*",
|
||||
"@vben/hooks": "workspace:*",
|
||||
"@vben/icons": "workspace:*",
|
||||
"@vben/layouts": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/plugins": "workspace:*",
|
||||
"@vben/preferences": "workspace:*",
|
||||
"@vben/request": "workspace:*",
|
||||
"@vben/stores": "workspace:*",
|
||||
"@vben/styles": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"axios": "^1.7.7",
|
||||
"clipboard": "^2.0.11",
|
||||
"dayjs": "catalog:",
|
||||
"element-plus": "catalog:",
|
||||
"pinia": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"vue-request": "^2.0.4",
|
||||
"vue-router": "catalog:",
|
||||
"vue3-json-viewer": "^2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hey-api/client-axios": "^0.2.10",
|
||||
"@hey-api/openapi-ts": "^0.55.3",
|
||||
"unplugin-element-plus": "catalog:"
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export { default } from '@vben/tailwind-config/postcss';
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@ -1,338 +0,0 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import {
|
||||
defineAsyncComponent,
|
||||
defineComponent,
|
||||
getCurrentInstance,
|
||||
h,
|
||||
ref,
|
||||
} from 'vue';
|
||||
|
||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElNotification } from 'element-plus';
|
||||
|
||||
const ElButton = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/button/index'),
|
||||
import('element-plus/es/components/button/style/css'),
|
||||
]).then(([res]) => res.ElButton),
|
||||
);
|
||||
const ElCheckbox = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/checkbox/index'),
|
||||
import('element-plus/es/components/checkbox/style/css'),
|
||||
]).then(([res]) => res.ElCheckbox),
|
||||
);
|
||||
const ElCheckboxButton = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/checkbox/index'),
|
||||
import('element-plus/es/components/checkbox-button/style/css'),
|
||||
]).then(([res]) => res.ElCheckboxButton),
|
||||
);
|
||||
const ElCheckboxGroup = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/checkbox/index'),
|
||||
import('element-plus/es/components/checkbox-group/style/css'),
|
||||
]).then(([res]) => res.ElCheckboxGroup),
|
||||
);
|
||||
const ElDatePicker = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/date-picker/index'),
|
||||
import('element-plus/es/components/date-picker/style/css'),
|
||||
]).then(([res]) => res.ElDatePicker),
|
||||
);
|
||||
const ElDivider = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/divider/index'),
|
||||
import('element-plus/es/components/divider/style/css'),
|
||||
]).then(([res]) => res.ElDivider),
|
||||
);
|
||||
const ElInput = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/input/index'),
|
||||
import('element-plus/es/components/input/style/css'),
|
||||
]).then(([res]) => res.ElInput),
|
||||
);
|
||||
const ElInputNumber = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/input-number/index'),
|
||||
import('element-plus/es/components/input-number/style/css'),
|
||||
]).then(([res]) => res.ElInputNumber),
|
||||
);
|
||||
const ElRadio = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/radio/index'),
|
||||
import('element-plus/es/components/radio/style/css'),
|
||||
]).then(([res]) => res.ElRadio),
|
||||
);
|
||||
const ElRadioButton = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/radio/index'),
|
||||
import('element-plus/es/components/radio-button/style/css'),
|
||||
]).then(([res]) => res.ElRadioButton),
|
||||
);
|
||||
const ElRadioGroup = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/radio/index'),
|
||||
import('element-plus/es/components/radio-group/style/css'),
|
||||
]).then(([res]) => res.ElRadioGroup),
|
||||
);
|
||||
const ElSelectV2 = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/select-v2/index'),
|
||||
import('element-plus/es/components/select-v2/style/css'),
|
||||
]).then(([res]) => res.ElSelectV2),
|
||||
);
|
||||
const ElSpace = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/space/index'),
|
||||
import('element-plus/es/components/space/style/css'),
|
||||
]).then(([res]) => res.ElSpace),
|
||||
);
|
||||
const ElSwitch = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/switch/index'),
|
||||
import('element-plus/es/components/switch/style/css'),
|
||||
]).then(([res]) => res.ElSwitch),
|
||||
);
|
||||
const ElTimePicker = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/time-picker/index'),
|
||||
import('element-plus/es/components/time-picker/style/css'),
|
||||
]).then(([res]) => res.ElTimePicker),
|
||||
);
|
||||
const ElTreeSelect = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/tree-select/index'),
|
||||
import('element-plus/es/components/tree-select/style/css'),
|
||||
]).then(([res]) => res.ElTreeSelect),
|
||||
);
|
||||
const ElUpload = defineAsyncComponent(() =>
|
||||
Promise.all([
|
||||
import('element-plus/es/components/upload/index'),
|
||||
import('element-plus/es/components/upload/style/css'),
|
||||
]).then(([res]) => res.ElUpload),
|
||||
);
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
componentProps: Recordable<any> = {},
|
||||
) => {
|
||||
return defineComponent({
|
||||
name: component.name,
|
||||
inheritAttrs: false,
|
||||
setup: (props: any, { attrs, expose, slots }) => {
|
||||
const placeholder =
|
||||
props?.placeholder ||
|
||||
attrs?.placeholder ||
|
||||
$t(`ui.placeholder.${type}`);
|
||||
// 透传组件暴露的方法
|
||||
const innerRef = ref();
|
||||
const publicApi: Recordable<any> = {};
|
||||
expose(publicApi);
|
||||
const instance = getCurrentInstance();
|
||||
instance?.proxy?.$nextTick(() => {
|
||||
for (const key in innerRef.value) {
|
||||
if (typeof innerRef.value[key] === 'function') {
|
||||
publicApi[key] = innerRef.value[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
return () =>
|
||||
h(
|
||||
component,
|
||||
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
|
||||
slots,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'ApiSelect'
|
||||
| 'ApiTreeSelect'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'IconPicker'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'RadioGroup'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
ApiSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiSelect',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: ElSelectV2,
|
||||
loadingSlot: 'loading',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
},
|
||||
),
|
||||
ApiTreeSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiTreeSelect',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: ElTreeSelect,
|
||||
props: { label: 'label', children: 'children' },
|
||||
nodeKey: 'value',
|
||||
loadingSlot: 'loading',
|
||||
optionsPropName: 'data',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
},
|
||||
),
|
||||
Checkbox: ElCheckbox,
|
||||
CheckboxGroup: (props, { attrs, slots }) => {
|
||||
let defaultSlot;
|
||||
if (Reflect.has(slots, 'default')) {
|
||||
defaultSlot = slots.default;
|
||||
} else {
|
||||
const { options, isButton } = attrs;
|
||||
if (Array.isArray(options)) {
|
||||
defaultSlot = () =>
|
||||
options.map((option) =>
|
||||
h(isButton ? ElCheckboxButton : ElCheckbox, option),
|
||||
);
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElCheckboxGroup,
|
||||
{ ...props, ...attrs },
|
||||
{ ...slots, default: defaultSlot },
|
||||
);
|
||||
},
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
||||
},
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider: ElDivider,
|
||||
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
|
||||
iconSlot: 'append',
|
||||
modelValueProp: 'model-value',
|
||||
inputComponent: ElInput,
|
||||
}),
|
||||
Input: withDefaultPlaceholder(ElInput, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
||||
RadioGroup: (props, { attrs, slots }) => {
|
||||
let defaultSlot;
|
||||
if (Reflect.has(slots, 'default')) {
|
||||
defaultSlot = slots.default;
|
||||
} else {
|
||||
const { options } = attrs;
|
||||
if (Array.isArray(options)) {
|
||||
defaultSlot = () =>
|
||||
options.map((option) =>
|
||||
h(attrs.isButton ? ElRadioButton : ElRadio, option),
|
||||
);
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElRadioGroup,
|
||||
{ ...props, ...attrs },
|
||||
{ ...slots, default: defaultSlot },
|
||||
);
|
||||
},
|
||||
Select: (props, { attrs, slots }) => {
|
||||
return h(ElSelectV2, { ...props, attrs }, slots);
|
||||
},
|
||||
Space: ElSpace,
|
||||
Switch: ElSwitch,
|
||||
TimePicker: (props, { attrs, slots }) => {
|
||||
const { name, id, isRange } = props;
|
||||
const extraProps: Recordable<any> = {};
|
||||
if (isRange) {
|
||||
if (name && !Array.isArray(name)) {
|
||||
extraProps.name = [name, `${name}_end`];
|
||||
}
|
||||
if (id && !Array.isArray(id)) {
|
||||
extraProps.id = [id, `${id}_end`];
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElTimePicker,
|
||||
{
|
||||
...props,
|
||||
...attrs,
|
||||
...extraProps,
|
||||
},
|
||||
slots,
|
||||
);
|
||||
},
|
||||
DatePicker: (props, { attrs, slots }) => {
|
||||
const { name, id, type } = props;
|
||||
const extraProps: Recordable<any> = {};
|
||||
if (type && type.includes('range')) {
|
||||
if (name && !Array.isArray(name)) {
|
||||
extraProps.name = [name, `${name}_end`];
|
||||
}
|
||||
if (id && !Array.isArray(id)) {
|
||||
extraProps.id = [id, `${id}_end`];
|
||||
}
|
||||
}
|
||||
return h(
|
||||
ElDatePicker,
|
||||
{
|
||||
...props,
|
||||
...attrs,
|
||||
...extraProps,
|
||||
},
|
||||
slots,
|
||||
);
|
||||
},
|
||||
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
|
||||
Upload: ElUpload,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
ElNotification({
|
||||
title,
|
||||
message: content,
|
||||
position: 'bottom-right',
|
||||
duration: 0,
|
||||
type: 'success',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
||||
@ -1,39 +0,0 @@
|
||||
import type {
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
modelPropNameMap: {
|
||||
Upload: 'fileList',
|
||||
CheckboxGroup: 'model-value',
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
selectRequired: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null) {
|
||||
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
@ -1,73 +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,
|
||||
columnConfig: {
|
||||
resizable: true,
|
||||
},
|
||||
minHeight: 180,
|
||||
formConfig: {
|
||||
// 全局禁用vxe-table的表单配置,使用formOptions
|
||||
enabled: false,
|
||||
},
|
||||
sortConfig: {
|
||||
remote: true,
|
||||
},
|
||||
proxyConfig: {
|
||||
sort: true, // 启用排序请求代理
|
||||
autoLoad: true,
|
||||
response: {
|
||||
result: 'items',
|
||||
total: 'totalCount',
|
||||
list: 'items',
|
||||
},
|
||||
showActiveMsg: true,
|
||||
showResponseMsg: false,
|
||||
},
|
||||
round: true,
|
||||
showOverflow: true,
|
||||
stripe: true, // 斑马线
|
||||
size: 'small',
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||
vxeUI.renderer.add('CellImage', {
|
||||
renderTableDefault(_renderOpts, params) {
|
||||
const { column, row } = params;
|
||||
const src = row[column.field];
|
||||
return h(ElImage, { src, previewSrcList: [src] });
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||
vxeUI.renderer.add('CellLink', {
|
||||
renderTableDefault(renderOpts) {
|
||||
const { props } = renderOpts;
|
||||
return h(
|
||||
ElButton,
|
||||
{ size: 'small', link: true },
|
||||
{ default: () => props?.text },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||
// vxeUI.formats.add
|
||||
},
|
||||
useVbenForm,
|
||||
});
|
||||
|
||||
export { useVbenVxeGrid };
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
||||
@ -1,7 +0,0 @@
|
||||
import { defineConfig } from '@hey-api/openapi-ts';
|
||||
|
||||
export default defineConfig({
|
||||
client: '@hey-api/client-axios',
|
||||
input: 'http://localhost:44315/swagger/AbpPro/swagger.json',
|
||||
output: 'src/api-client',
|
||||
});
|
||||
@ -1,146 +0,0 @@
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
|
||||
import { ElMessage as Message } from 'element-plus';
|
||||
|
||||
import { postApiAppAccountRefreshToken } from '#/api-client';
|
||||
import { $t } from '#/locales';
|
||||
import { elementLocale } from '#/locales/index';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { client } from '../api-client/services.gen';
|
||||
|
||||
client.setConfig({
|
||||
baseURL: import.meta.env.DEV
|
||||
? '/proxy/'
|
||||
: import.meta.env.VITE_APP_API_ADDRESS,
|
||||
timeout: 1000 * 60,
|
||||
responseType: 'json',
|
||||
throwOnError: true,
|
||||
});
|
||||
// 是否正在刷新token
|
||||
let isRefreshing = false;
|
||||
// 刷新token队列
|
||||
let refreshTokenQueue: ((token: string) => void)[] = [];
|
||||
|
||||
client.instance.interceptors.request.use((request) => {
|
||||
// 全局拦截请求发送前提交的参数
|
||||
const userStore = useUserStore();
|
||||
const accessStore = useAccessStore();
|
||||
const token = accessStore.getAccessToken();
|
||||
// 设置请求头
|
||||
if (request.headers) {
|
||||
request.headers.__tenant = userStore.tenant?.tenantId;
|
||||
// todo vben5 没有提供统一获取当前语言的方式
|
||||
request.headers['accept-language'] = elementLocale.value.name;
|
||||
}
|
||||
// 如果token过期,则跳转到登录页面
|
||||
if (
|
||||
request.url !== undefined &&
|
||||
request.url.includes('/api/app/account/login')
|
||||
) {
|
||||
return request;
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
if (request.headers) {
|
||||
request.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return request;
|
||||
});
|
||||
|
||||
client.instance.interceptors.response.use(
|
||||
(response) => {
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
async (error) => {
|
||||
let message = error.message;
|
||||
if (message === 'Network Error') {
|
||||
message = $t('common.mesage500');
|
||||
} else if (message.includes('timeout')) {
|
||||
message = $t('common.timeOut');
|
||||
} else
|
||||
switch (error.status) {
|
||||
case 400: {
|
||||
message = error.response.data.error?.validationErrors[0].message;
|
||||
|
||||
break;
|
||||
}
|
||||
case 401: {
|
||||
message = $t('common.mesage401');
|
||||
const { config } = error;
|
||||
const originalRequest = config;
|
||||
if (isRefreshing) {
|
||||
return new Promise((resolve) => {
|
||||
refreshTokenQueue.push((token) => {
|
||||
originalRequest.headers.Authorization = `Bearer ${token}`;
|
||||
resolve(client.request(originalRequest));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
isRefreshing = true;
|
||||
try {
|
||||
const newToken = await refreshTokenAsync();
|
||||
// 处理队列中的请求
|
||||
refreshTokenQueue.forEach((callback) => callback(newToken));
|
||||
// 清空队列
|
||||
refreshTokenQueue = [];
|
||||
return client.request(originalRequest);
|
||||
} catch (refreshError) {
|
||||
// 如果刷新 token 失败,处理错误(如强制登出或跳转登录页面)
|
||||
message = $t('common.mesage401');
|
||||
refreshTokenQueue = [];
|
||||
const authStore = useAuthStore();
|
||||
authStore.logout();
|
||||
console.error(refreshError);
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 403: {
|
||||
message = $t('common.mesage403');
|
||||
|
||||
break;
|
||||
}
|
||||
case 500: {
|
||||
message = error.response.data.error?.message;
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (message.includes('Request failed with status code')) {
|
||||
message = $t('common.mesage500');
|
||||
}
|
||||
}
|
||||
}
|
||||
Message.error(message);
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
|
||||
async function refreshTokenAsync(): Promise<string> {
|
||||
try {
|
||||
const userStore = useUserStore();
|
||||
const accessStore = useAccessStore();
|
||||
const refreshToken = accessStore.getRefreshToken();
|
||||
if (!refreshToken) return '';
|
||||
const res = await postApiAppAccountRefreshToken({
|
||||
body: {
|
||||
userId: userStore.userInfo?.id,
|
||||
refreshToken,
|
||||
},
|
||||
});
|
||||
if (res?.data?.success) {
|
||||
accessStore.setAccessToken(res.data.token as string);
|
||||
accessStore.setRefreshToken(res.data.refreshToken as string);
|
||||
return res.data.token as string;
|
||||
} else {
|
||||
throw new Error('get refreshToken error');
|
||||
}
|
||||
} catch {
|
||||
throw new Error($t('common.mesage401'));
|
||||
}
|
||||
}
|
||||
export default client;
|
||||
@ -1,4 +0,0 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
export * from './schemas.gen';
|
||||
export * from './services.gen';
|
||||
export * from './types.gen';
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -1,51 +0,0 @@
|
||||
import { baseRequestClient, requestClient } from '#/api/request';
|
||||
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
export interface LoginParams {
|
||||
password?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/** 登录接口返回值 */
|
||||
export interface LoginResult {
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
data: string;
|
||||
status: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
export async function loginApi(data: AuthApi.LoginParams) {
|
||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新accessToken
|
||||
*/
|
||||
export async function refreshTokenApi() {
|
||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
export async function logoutApi() {
|
||||
return baseRequestClient.post('/auth/logout', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户权限码
|
||||
*/
|
||||
export async function getAccessCodesApi() {
|
||||
return requestClient.get<string[]>('/auth/codes');
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
export * from './auth';
|
||||
export * from './menu';
|
||||
export * from './user';
|
||||
@ -1,10 +0,0 @@
|
||||
import type { RouteRecordStringComponent } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 获取用户所有菜单
|
||||
*/
|
||||
export async function getAllMenusApi() {
|
||||
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import type { UserInfo } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
export async function getUserInfoApi() {
|
||||
return requestClient.get<UserInfo>('/user/info');
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './core';
|
||||
@ -1,113 +0,0 @@
|
||||
/**
|
||||
* 该文件可自行根据业务逻辑进行调整
|
||||
*/
|
||||
import type { RequestClientOptions } from '@vben/request';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import {
|
||||
authenticateResponseInterceptor,
|
||||
defaultResponseInterceptor,
|
||||
errorMessageResponseInterceptor,
|
||||
RequestClient,
|
||||
} from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { refreshTokenApi } from './core';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
|
||||
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||
const client = new RequestClient({
|
||||
...options,
|
||||
baseURL,
|
||||
});
|
||||
|
||||
/**
|
||||
* 重新认证逻辑
|
||||
*/
|
||||
async function doReAuthenticate() {
|
||||
console.warn('Access token or refresh token is invalid or expired. ');
|
||||
const accessStore = useAccessStore();
|
||||
const authStore = useAuthStore();
|
||||
accessStore.setAccessToken(null);
|
||||
if (
|
||||
preferences.app.loginExpiredMode === 'modal' &&
|
||||
accessStore.isAccessChecked
|
||||
) {
|
||||
accessStore.setLoginExpired(true);
|
||||
} else {
|
||||
await authStore.logout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token逻辑
|
||||
*/
|
||||
async function doRefreshToken() {
|
||||
const accessStore = useAccessStore();
|
||||
const resp = await refreshTokenApi();
|
||||
const newToken = resp.data;
|
||||
accessStore.setAccessToken(newToken);
|
||||
return newToken;
|
||||
}
|
||||
|
||||
function formatToken(token: null | string) {
|
||||
return token ? `Bearer ${token}` : null;
|
||||
}
|
||||
|
||||
// 请求头处理
|
||||
client.addRequestInterceptor({
|
||||
fulfilled: async (config) => {
|
||||
const accessStore = useAccessStore();
|
||||
|
||||
config.headers.Authorization = formatToken(accessStore.accessToken);
|
||||
config.headers['Accept-Language'] = preferences.app.locale;
|
||||
return config;
|
||||
},
|
||||
});
|
||||
|
||||
// 处理返回的响应数据格式
|
||||
client.addResponseInterceptor(
|
||||
defaultResponseInterceptor({
|
||||
codeField: 'code',
|
||||
dataField: 'data',
|
||||
successCode: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
// token过期的处理
|
||||
client.addResponseInterceptor(
|
||||
authenticateResponseInterceptor({
|
||||
client,
|
||||
doReAuthenticate,
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||
formatToken,
|
||||
}),
|
||||
);
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string, error) => {
|
||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||
// 当前mock接口返回的错误字段是 error 或者 message
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const errorMessage = responseData?.error ?? responseData?.message ?? '';
|
||||
// 如果没有错误信息,则会根据状态码进行提示
|
||||
ElMessage.error(errorMessage || msg);
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export const requestClient = createRequestClient(apiURL, {
|
||||
responseReturn: 'data',
|
||||
});
|
||||
|
||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||
@ -1,17 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useElementPlusDesignTokens } from '@vben/hooks';
|
||||
|
||||
import { ElConfigProvider } from 'element-plus';
|
||||
|
||||
import { elementLocale } from '#/locales';
|
||||
|
||||
defineOptions({ name: 'App' });
|
||||
|
||||
useElementPlusDesignTokens();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElConfigProvider :locale="elementLocale">
|
||||
<RouterView />
|
||||
</ElConfigProvider>
|
||||
</template>
|
||||
@ -1,81 +0,0 @@
|
||||
import { createApp, watchEffect } from 'vue';
|
||||
|
||||
import { registerAccessDirective } from '@vben/access';
|
||||
import { registerLoadingDirective } from '@vben/common-ui';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { initStores } from '@vben/stores';
|
||||
import '@vben/styles';
|
||||
import '@vben/styles/ele';
|
||||
|
||||
import { useTitle } from '@vueuse/core';
|
||||
import { ElLoading } from 'element-plus';
|
||||
import JsonViewer from 'vue3-json-viewer';
|
||||
|
||||
import { $t, setupI18n } from '#/locales';
|
||||
|
||||
import { initComponentAdapter } from './adapter/component';
|
||||
import App from './app.vue';
|
||||
import { router } from './router';
|
||||
|
||||
// 加载本地图标
|
||||
// import '#/hooks/useLoadIcon';
|
||||
import 'vue3-json-viewer/dist/index.css';
|
||||
|
||||
async function bootstrap(namespace: string) {
|
||||
// 初始化组件适配器
|
||||
await initComponentAdapter();
|
||||
// // 设置弹窗的默认配置
|
||||
// setDefaultModalProps({
|
||||
// fullscreenButton: false,
|
||||
// });
|
||||
// // 设置抽屉的默认配置
|
||||
// setDefaultDrawerProps({
|
||||
// zIndex: 2000,
|
||||
// });
|
||||
const app = createApp(App);
|
||||
|
||||
// 注册Element Plus提供的v-loading指令
|
||||
app.directive('loading', ElLoading.directive);
|
||||
|
||||
// 注册Vben提供的v-loading和v-spinning指令
|
||||
registerLoadingDirective(app, {
|
||||
loading: false, // Vben提供的v-loading指令和Element Plus提供的v-loading指令二选一即可,此处false表示不注册Vben提供的v-loading指令
|
||||
spinning: 'spinning',
|
||||
});
|
||||
|
||||
// 国际化 i18n 配置
|
||||
await setupI18n(app);
|
||||
|
||||
// 配置 pinia-tore
|
||||
await initStores(app, { namespace });
|
||||
|
||||
// 安装权限指令
|
||||
registerAccessDirective(app);
|
||||
|
||||
// 初始化 tippy
|
||||
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
||||
initTippy(app);
|
||||
|
||||
// 配置路由及路由守卫
|
||||
app.use(router);
|
||||
|
||||
// 配置Motion插件
|
||||
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||
app.use(MotionPlugin);
|
||||
|
||||
// 配置 json-viewer
|
||||
app.use(JsonViewer);
|
||||
// 动态更新标题
|
||||
watchEffect(() => {
|
||||
if (preferences.app.dynamicTitle) {
|
||||
const routeTitle = router.currentRoute.value.meta?.title;
|
||||
const pageTitle =
|
||||
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
|
||||
useTitle(pageTitle);
|
||||
}
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
}
|
||||
|
||||
export { bootstrap };
|
||||
@ -1,18 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { IconifyIcon as VbenIcon } from '@vben/icons';
|
||||
|
||||
import { ElIcon } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElIcon>
|
||||
<VbenIcon :icon="props.icon" />
|
||||
</ElIcon>
|
||||
</template>
|
||||
@ -1 +0,0 @@
|
||||
export { default as Icon } from './icon.vue';
|
||||
@ -1,2 +0,0 @@
|
||||
export { default as TableAction } from './table-action.vue';
|
||||
export type * from './types.d.ts';
|
||||
@ -1,201 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { ActionItem, PopConfirm } from './types';
|
||||
|
||||
import { computed, h, type PropType, reactive, toRaw, type VNode } from 'vue';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { isBoolean, isFunction } from '@vben/utils';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElDropdown,
|
||||
ElDropdownItem,
|
||||
ElDropdownMenu,
|
||||
ElPopconfirm,
|
||||
ElSpace,
|
||||
} from 'element-plus';
|
||||
|
||||
import { Icon } from '#/components/icon';
|
||||
|
||||
const props = defineProps({
|
||||
actions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
dropDownActions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
divider: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
const renderIcon = (icon: string | VNode) => {
|
||||
if (typeof icon === 'string') {
|
||||
return h(Icon, { icon });
|
||||
}
|
||||
return h(icon);
|
||||
};
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
const popconfirmRef = reactive<any>({});
|
||||
function isIfShow(action: ActionItem): boolean {
|
||||
const ifShow = action.ifShow;
|
||||
|
||||
let isIfShow = true;
|
||||
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(action);
|
||||
}
|
||||
return isIfShow;
|
||||
}
|
||||
|
||||
const getActions = computed(() => {
|
||||
return (toRaw(props.actions) || [])
|
||||
.filter((action) => {
|
||||
return (
|
||||
(hasAccessByCodes(action.auth || []) ||
|
||||
(action.auth || []).length === 0) &&
|
||||
isIfShow(action)
|
||||
);
|
||||
})
|
||||
.map((action) => {
|
||||
const { popConfirm } = action;
|
||||
const icon = action.icon ? renderIcon(action.icon) : undefined;
|
||||
return {
|
||||
// getPopupContainer: document.body,
|
||||
...action,
|
||||
...popConfirm,
|
||||
icon,
|
||||
onConfirm: popConfirm?.confirm,
|
||||
onCancel: popConfirm?.cancel,
|
||||
enable: !!popConfirm,
|
||||
};
|
||||
});
|
||||
});
|
||||
const getDropdownList = computed((): any[] => {
|
||||
return (toRaw(props.dropDownActions) || [])
|
||||
.filter((action) => {
|
||||
return (
|
||||
(hasAccessByCodes(action.auth || []) ||
|
||||
(action.auth || []).length === 0) &&
|
||||
isIfShow(action)
|
||||
);
|
||||
})
|
||||
.map((action, index) => {
|
||||
const { label, popConfirm } = action;
|
||||
return {
|
||||
...action,
|
||||
...popConfirm,
|
||||
onConfirm: popConfirm?.confirm,
|
||||
onCancel: popConfirm?.cancel,
|
||||
text: label,
|
||||
divider:
|
||||
index < props.dropDownActions.length - 1 ? props.divider : false,
|
||||
};
|
||||
});
|
||||
});
|
||||
const getPopConfirmProps = (attrs: PopConfirm) => {
|
||||
const originAttrs: any = attrs;
|
||||
delete originAttrs.icon;
|
||||
if (attrs.confirm && isFunction(attrs.confirm)) {
|
||||
originAttrs.onConfirm = attrs.confirm;
|
||||
delete originAttrs.confirm;
|
||||
}
|
||||
if (attrs.cancel && isFunction(attrs.cancel)) {
|
||||
originAttrs.onCancel = attrs.cancel;
|
||||
delete originAttrs.cancel;
|
||||
}
|
||||
return originAttrs;
|
||||
};
|
||||
const getButtonProps = (action: ActionItem): any => {
|
||||
const res = {
|
||||
...action,
|
||||
type: action.type || 'primary',
|
||||
};
|
||||
return res;
|
||||
};
|
||||
const handleCommand = (index: number) => {
|
||||
const action = getDropdownList.value[index];
|
||||
if (action.onClick && isFunction(action.onClick)) {
|
||||
action.onClick();
|
||||
} else {
|
||||
const currentPopconfirmRef = popconfirmRef[index.toString()];
|
||||
currentPopconfirmRef?.click();
|
||||
}
|
||||
};
|
||||
const handleRef = (e: any, index: number) => {
|
||||
popconfirmRef[index.toString()] = e;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="m-table-action">
|
||||
<ElSpace :size="2">
|
||||
<template v-for="(action, index) in getActions" :key="index">
|
||||
<ElPopconfirm
|
||||
v-if="action.popConfirm"
|
||||
v-bind="getPopConfirmProps(action.popConfirm)"
|
||||
>
|
||||
<template #reference>
|
||||
<ElButton v-bind="getButtonProps(action)">
|
||||
{{ action.label }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElPopconfirm>
|
||||
<ElButton
|
||||
v-else
|
||||
v-bind="getButtonProps(action)"
|
||||
@click="action.onClick"
|
||||
>
|
||||
{{ action.label }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElSpace>
|
||||
|
||||
<ElDropdown
|
||||
v-if="getDropdownList.length > 0"
|
||||
trigger="hover"
|
||||
@command="handleCommand"
|
||||
>
|
||||
<template #default>
|
||||
<div>
|
||||
<slot name="more">
|
||||
<ElButton link size="small" style="margin-left: 6px" type="primary">
|
||||
<Icon class="icon-more" icon="ep:more" />
|
||||
</ElButton>
|
||||
</slot>
|
||||
<template v-for="(action, index) in getDropdownList" :key="index">
|
||||
<ElPopconfirm
|
||||
v-if="action.popConfirm"
|
||||
v-bind="getPopConfirmProps(action.popConfirm)"
|
||||
>
|
||||
<template #reference>
|
||||
<span :ref="(e) => handleRef(e, index)"></span>
|
||||
</template>
|
||||
</ElPopconfirm>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem
|
||||
v-for="(action, index) in getDropdownList"
|
||||
:key="index"
|
||||
:command="index"
|
||||
>
|
||||
<Icon v-if="action.icon" :icon="action.icon" />
|
||||
{{ action.label }}
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,29 +0,0 @@
|
||||
import type { VNode } from 'vue';
|
||||
|
||||
import { TooltipProps } from 'element-plus';
|
||||
|
||||
export interface PopConfirm {
|
||||
title: string;
|
||||
okText?: string;
|
||||
cancelText?: string;
|
||||
confirm: Fn;
|
||||
cancel?: Fn;
|
||||
icon?: string;
|
||||
}
|
||||
export interface ActionItem {
|
||||
type?: 'danger' | 'info' | 'primary' | 'success' | 'warning';
|
||||
link?: boolean;
|
||||
size?: 'large' | 'medium' | 'mini' | 'small';
|
||||
onClick?: Fn;
|
||||
label?: string;
|
||||
color?: 'error' | 'success' | 'warning';
|
||||
icon?: string | VNode;
|
||||
popConfirm?: PopConfirm;
|
||||
disabled?: boolean;
|
||||
divider?: boolean;
|
||||
// 权限编码控制是否显示
|
||||
auth?: string[];
|
||||
// 业务控制是否显示
|
||||
ifShow?: ((action: ActionItem) => boolean) | boolean;
|
||||
tooltip?: string | TooltipProps;
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { addCollection } from '@vben/icons';
|
||||
|
||||
import AntDesignIcons from '@iconify/json/json/ant-design.json';
|
||||
import CarbonIcons from '@iconify/json/json/carbon.json';
|
||||
import EpIcons from '@iconify/json/json/ep.json';
|
||||
import IcIcons from '@iconify/json/json/ic.json';
|
||||
import LogosIcons from '@iconify/json/json/logos.json';
|
||||
import LucideIcons from '@iconify/json/json/lucide.json';
|
||||
import MdiIcons from '@iconify/json/json/mdi.json';
|
||||
import OuiIcons from '@iconify/json/json/oui.json';
|
||||
import PhosphorIcons from '@iconify/json/json/ph.json';
|
||||
import UnIcons from '@iconify/json/json/uil.json';
|
||||
|
||||
addCollection(AntDesignIcons);
|
||||
addCollection(LucideIcons);
|
||||
addCollection(CarbonIcons);
|
||||
addCollection(IcIcons as any);
|
||||
addCollection(LogosIcons as any);
|
||||
addCollection(PhosphorIcons as any);
|
||||
addCollection(UnIcons);
|
||||
addCollection(OuiIcons);
|
||||
addCollection(MdiIcons);
|
||||
addCollection(EpIcons);
|
||||
@ -1,133 +0,0 @@
|
||||
import { useEventbus } from '@vben/hooks';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import * as signalR from '@microsoft/signalr';
|
||||
import { ElNotification as notification } from 'element-plus';
|
||||
|
||||
const eventbus = useEventbus();
|
||||
let connection: signalR.HubConnection;
|
||||
export function useSignalR() {
|
||||
/**
|
||||
* 开始连接SignalR
|
||||
*/
|
||||
async function startConnect() {
|
||||
try {
|
||||
const userStore = useUserStore();
|
||||
if (userStore.checkUserLoginExpire()) {
|
||||
console.debug('未检测到用户信息,登录之后才会链接SignalR.');
|
||||
return;
|
||||
}
|
||||
connectionsignalR();
|
||||
await connection.start();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setTimeout(() => startConnect(), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭SignalR连接
|
||||
*/
|
||||
function closeConnect(): void {
|
||||
connection?.stop();
|
||||
}
|
||||
|
||||
async function connectionsignalR() {
|
||||
const userStore = useUserStore();
|
||||
const token = userStore.userInfo?.token;
|
||||
connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(import.meta.env.VITE_WEBSOCKET_URL, {
|
||||
accessTokenFactory: () => token,
|
||||
skipNegotiation: true,
|
||||
transport: signalR.HttpTransportType.WebSockets,
|
||||
})
|
||||
.withAutomaticReconnect({
|
||||
nextRetryDelayInMilliseconds: (retryContext) => {
|
||||
// 重连规则:重连次数<300:间隔1s;重试次数<3000:间隔3s;重试次数>3000:间隔30s
|
||||
const count = retryContext.previousRetryCount / 300;
|
||||
if (count < 1) {
|
||||
// 重试次数<300,间隔1s
|
||||
return 1000;
|
||||
} else if (count < 10) {
|
||||
// 重试次数>300:间隔5s
|
||||
return 1000 * 5;
|
||||
} // 重试次数>3000:间隔30s
|
||||
else {
|
||||
return 1000 * 30;
|
||||
}
|
||||
},
|
||||
})
|
||||
.configureLogging(signalR.LogLevel.Debug)
|
||||
.build();
|
||||
|
||||
// 接收普通文本消息
|
||||
connection.on('ReceiveTextMessageAsync', ReceiveTextMessageHandlerAsync);
|
||||
// 接收广播消息
|
||||
connection.on(
|
||||
'ReceiveBroadCastMessageAsync',
|
||||
ReceiveBroadCastMessageHandlerAsync,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收文本消息
|
||||
* @param message 消息体
|
||||
*/
|
||||
function ReceiveTextMessageHandlerAsync(message: any) {
|
||||
// 发布事件
|
||||
eventbus.publish('ReceiveTextMessageHandlerAsync', message);
|
||||
if (message.messageLevel === 10) {
|
||||
notification({
|
||||
message: message.content,
|
||||
title: message.title,
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
if (message.messageLevel === 20) {
|
||||
notification.info({
|
||||
message: message.content,
|
||||
title: message.title,
|
||||
type: 'info',
|
||||
});
|
||||
}
|
||||
if (message.messageLevel === 30) {
|
||||
notification.error({
|
||||
message: message.content,
|
||||
title: message.title,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收广播消息
|
||||
* @param message 消息体
|
||||
*/
|
||||
function ReceiveBroadCastMessageHandlerAsync(message: any) {
|
||||
// 发布事件
|
||||
eventbus.publish('ReceiveBroadCastMessageHandlerAsync', message);
|
||||
if (message.messageLevel === 10) {
|
||||
notification({
|
||||
message: message.content,
|
||||
title: message.title,
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
if (message.messageLevel === 20) {
|
||||
notification.info({
|
||||
message: message.content,
|
||||
title: message.title,
|
||||
type: 'info',
|
||||
});
|
||||
}
|
||||
if (message.messageLevel === 30) {
|
||||
notification.error({
|
||||
message: message.content,
|
||||
title: message.title,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { startConnect, closeConnect };
|
||||
}
|
||||
@ -1,192 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { ElMessage as Message } from 'element-plus';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
postNotificationNotificationPage,
|
||||
postNotificationRead,
|
||||
} from '#/api-client';
|
||||
|
||||
defineOptions({
|
||||
name: 'AbpNotifyItem',
|
||||
});
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
schema: [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'messageType',
|
||||
label: 'messageType',
|
||||
defaultValue: 20,
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: ['messageType'],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'title',
|
||||
label: '标题',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'content',
|
||||
label: '内容',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'messageLevel',
|
||||
label: '级别',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '警告',
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
label: '正常',
|
||||
value: 20,
|
||||
},
|
||||
{
|
||||
label: '错误',
|
||||
value: 30,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'read',
|
||||
label: '是否已读',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '是',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: '否',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
wrapperClass: 'grid-cols-5',
|
||||
};
|
||||
const userStore = useUserStore();
|
||||
const gridOptions: VxeGridProps<any> = {
|
||||
checkboxConfig: {},
|
||||
columns: [
|
||||
{ title: '序号', type: 'seq', width: 50 },
|
||||
{ field: 'title', title: '标题', minWidth: '150' },
|
||||
{ field: 'content', title: '内容', minWidth: '150' },
|
||||
// { field: 'messageTypeName', title: '类型', minWidth: '150' },
|
||||
{
|
||||
field: 'messageLevelName',
|
||||
title: '级别',
|
||||
minWidth: '150',
|
||||
slots: { default: 'messageLevel' },
|
||||
},
|
||||
{ field: 'senderUserName', title: '发送人', minWidth: '150' },
|
||||
{ field: 'receiveUserName', title: '接收人', minWidth: '150' },
|
||||
{
|
||||
field: 'read',
|
||||
title: '是否已读',
|
||||
minWidth: '150',
|
||||
slots: { default: 'read' },
|
||||
},
|
||||
{
|
||||
field: 'creationTime',
|
||||
title: '创建时间',
|
||||
minWidth: '150',
|
||||
formatter: ({ cellValue }) => {
|
||||
return dayjs(cellValue).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
field: 'action',
|
||||
fixed: 'right',
|
||||
minWidth: '150',
|
||||
slots: { default: 'action' },
|
||||
},
|
||||
],
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
toolbarConfig: {
|
||||
custom: true,
|
||||
},
|
||||
customConfig: {
|
||||
storage: true,
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
const { data } = await postNotificationNotificationPage({
|
||||
body: {
|
||||
pageIndex: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
receiverUserId: userStore.userInfo?.id,
|
||||
...formValues,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridOptions });
|
||||
|
||||
const onRead = async (row: any) => {
|
||||
if (row.read) {
|
||||
Message.info('该消息已读,不需要重复设置');
|
||||
return;
|
||||
}
|
||||
await postNotificationRead({ body: { id: row.id } });
|
||||
gridApi.reload();
|
||||
Message.success('设置成功');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height title="消息列表">
|
||||
<Grid>
|
||||
<template #messageLevel="{ row }">
|
||||
<Tag v-if="row.messageLevel === 10" color="yellow">
|
||||
{{ row.messageLevelName }}
|
||||
</Tag>
|
||||
<Tag v-if="row.messageLevel === 20" color="green">
|
||||
{{ row.messageLevelName }}
|
||||
</Tag>
|
||||
<Tag v-if="row.messageLevel === 30" color="red">
|
||||
{{ row.messageLevelName }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #read="{ row }">
|
||||
<Tag v-if="row.read" color="green"> 已读 </Tag>
|
||||
<Tag v-else color="red"> 未读 </Tag>
|
||||
</template>
|
||||
|
||||
<template #action="{ row }">
|
||||
<Space>
|
||||
<Button size="small" type="primary" @click="onRead(row)">
|
||||
设置已读
|
||||
</Button>
|
||||
</Space>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@ -1,23 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { AuthPageLayout } from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const appName = computed(() => preferences.app.name);
|
||||
const logo = computed(() => preferences.logo.source);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthPageLayout
|
||||
:app-name="appName"
|
||||
:logo="logo"
|
||||
:page-description="$t('authentication.pageDesc')"
|
||||
:page-title="$t('authentication.pageTitle')"
|
||||
>
|
||||
<!-- 自定义工具栏 -->
|
||||
<!-- <template #toolbar></template> -->
|
||||
</AuthPageLayout>
|
||||
</template>
|
||||
@ -1,280 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { NotificationItem } from '@vben/layouts';
|
||||
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { AuthenticationLoginExpiredModal, useVbenModal } from '@vben/common-ui';
|
||||
import { useEventbus, useRefresh, useWatermark } from '@vben/hooks';
|
||||
import {
|
||||
BasicLayout,
|
||||
LockScreen,
|
||||
Notification,
|
||||
UserDropdown,
|
||||
} from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { ElMessage as Message } from 'element-plus';
|
||||
|
||||
import {
|
||||
postNotificationBatchRead,
|
||||
postNotificationNotificationPage,
|
||||
postNotificationRead,
|
||||
postUsersNeedChangePassword,
|
||||
} from '#/api-client';
|
||||
import { useSignalR } from '#/hooks/useSignalR';
|
||||
import { $t } from '#/locales';
|
||||
import { useAuthStore } from '#/store';
|
||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||
|
||||
import ChangePassword from './change-password.vue';
|
||||
import MyProfile from './my-profile.vue';
|
||||
import NotifyItem from './NotifyItem.vue';
|
||||
|
||||
const changePasswordTitle = ref<string>($t('abp.user.changePassword'));
|
||||
const [ChangePasswordModal, changePasswordModalApi] = useVbenModal({
|
||||
draggable: false,
|
||||
// centered: true,
|
||||
showCancelButton: false,
|
||||
showConfirmButton: false,
|
||||
closeOnPressEscape: false,
|
||||
closeOnClickModal: false,
|
||||
closable: false,
|
||||
fullscreenButton: false,
|
||||
title: changePasswordTitle.value,
|
||||
onConfirm: () => {},
|
||||
onBeforeClose: () => true,
|
||||
});
|
||||
const notifications = ref<NotificationItem[]>([]);
|
||||
function convertToNotificationItem(message: any): NotificationItem {
|
||||
return {
|
||||
avatar: '',
|
||||
date: dayjs(message.creationTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
isRead: message.read,
|
||||
message: message.content,
|
||||
title: message.title,
|
||||
id: message.id,
|
||||
};
|
||||
}
|
||||
const eventbus = useEventbus();
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
const accessStore = useAccessStore();
|
||||
// const router = useRouter();
|
||||
const { refresh } = useRefresh();
|
||||
const { startConnect, closeConnect } = useSignalR();
|
||||
onMounted(async () => {
|
||||
// // 判断是否登录
|
||||
// if (userStore.checkUserLoginExpire()) {
|
||||
// // 回登录页带上当前路由地址
|
||||
// await router.replace({
|
||||
// path: LOGIN_PATH,
|
||||
// query: {
|
||||
// redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
const result = await postUsersNeedChangePassword();
|
||||
if (result.data?.needChangePassword) {
|
||||
changePasswordTitle.value = result.data.message as string;
|
||||
changePasswordModalApi.open();
|
||||
}
|
||||
// 开启SignalR
|
||||
await startConnect();
|
||||
let refreshCount = userStore?.applicationInfo?.refreshCount ?? -1;
|
||||
if (refreshCount === -1) {
|
||||
userStore.setApplicationInfo({ refreshCount: refreshCount + 1 });
|
||||
} else {
|
||||
refreshCount = refreshCount + 1;
|
||||
// 获取刷新次数
|
||||
userStore.setApplicationInfo({ refreshCount });
|
||||
}
|
||||
// 是否开启刷新浏览器,检查用户的角色信息
|
||||
if (import.meta.env.VITE_REFRESH_ROLE && refreshCount > 0) {
|
||||
// todo 这里直接调用一下方法刷新菜单,需要刷新2次浏览器才会有效果,问题暂时没有找到,但是在路由守卫可以,代码移动到路由守卫去了
|
||||
// await authStore.getApplicationConfiguration();
|
||||
await refresh();
|
||||
}
|
||||
|
||||
eventbus.subscribe('ReceiveTextMessageHandlerAsync', (content) => {
|
||||
const item: NotificationItem = {
|
||||
avatar: '',
|
||||
date: '',
|
||||
message: content.contnet,
|
||||
title: content.title,
|
||||
id: content.id,
|
||||
isRead: false,
|
||||
};
|
||||
notifications.value.push(item);
|
||||
});
|
||||
|
||||
await loadMessage();
|
||||
});
|
||||
onBeforeUnmount(async () => {
|
||||
await closeConnect();
|
||||
eventbus.clear();
|
||||
});
|
||||
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||
const showDot = computed(() =>
|
||||
notifications.value.some((item) => !item.isRead),
|
||||
);
|
||||
const [MyProfileModal, myProfileModalApi] = useVbenModal({
|
||||
draggable: true,
|
||||
onConfirm: () => {},
|
||||
onBeforeClose: () => {},
|
||||
});
|
||||
const menus = computed(() => [
|
||||
{
|
||||
handler: () => {
|
||||
myProfileModalApi.open();
|
||||
},
|
||||
icon: 'ph:user',
|
||||
text: $t('abp.user.myAccount'),
|
||||
},
|
||||
]);
|
||||
|
||||
const avatar = computed(() => {
|
||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||
});
|
||||
|
||||
async function handleLogout() {
|
||||
await authStore.logout(false);
|
||||
}
|
||||
|
||||
function handleNoticeClear() {
|
||||
notifications.value = [];
|
||||
}
|
||||
|
||||
async function handleMakeAll() {
|
||||
// 获取notifications 已读的数据,并且请求api 标记为已读
|
||||
const readIds = notifications.value
|
||||
.filter((item) => item.isRead)
|
||||
.map((item) => item.id);
|
||||
|
||||
if (readIds.length > 0) {
|
||||
await postNotificationBatchRead({ body: { ids: readIds } });
|
||||
Message.success($t('common.success'));
|
||||
}
|
||||
|
||||
notifications.value.forEach((item) => (item.isRead = true));
|
||||
}
|
||||
|
||||
async function handleRead(row: NotificationItem) {
|
||||
if (row.isRead) {
|
||||
return;
|
||||
}
|
||||
await postNotificationRead({ body: { id: row.id } });
|
||||
|
||||
notifications.value.forEach((item) => {
|
||||
if (item.id === row.id) {
|
||||
item.isRead = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function loadMessage() {
|
||||
notifications.value = [];
|
||||
const message = await postNotificationNotificationPage({
|
||||
body: {
|
||||
pageIndex: 1,
|
||||
pageSize: 4,
|
||||
messageType: 20,
|
||||
receiverUserId: userStore.userInfo?.id,
|
||||
},
|
||||
});
|
||||
message.data?.items?.forEach((item) => {
|
||||
notifications.value.push(convertToNotificationItem(item));
|
||||
// 按照isRead属性进行排序
|
||||
notifications.value.sort((a, b) => {
|
||||
if (a.isRead === b.isRead) {
|
||||
return 0;
|
||||
}
|
||||
return a.isRead ? 1 : -1;
|
||||
});
|
||||
});
|
||||
}
|
||||
watch(
|
||||
() => preferences.app.watermark,
|
||||
async (enable) => {
|
||||
if (enable) {
|
||||
await updateWatermark({
|
||||
content: `${userStore.userInfo?.userName}`,
|
||||
});
|
||||
} else {
|
||||
destroyWatermark();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
const [NotifyItemModal, notifyItemModalApi] = useVbenModal({
|
||||
draggable: true,
|
||||
onConfirm: () => {},
|
||||
onBeforeClose: () => {},
|
||||
});
|
||||
function handleViewAll() {
|
||||
notifyItemModalApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicLayout @clear-preferences-and-logout="handleLogout">
|
||||
<template #user-dropdown>
|
||||
<UserDropdown
|
||||
:avatar
|
||||
:menus
|
||||
:tag-text="userStore.tenant?.name"
|
||||
:text="userStore.userInfo?.name"
|
||||
@logout="handleLogout"
|
||||
/>
|
||||
</template>
|
||||
<template #notification>
|
||||
<Notification
|
||||
:dot="showDot"
|
||||
:notifications="notifications"
|
||||
@clear="handleNoticeClear"
|
||||
@icon-click="loadMessage"
|
||||
@make-all="handleMakeAll"
|
||||
@read="handleRead"
|
||||
@view-all="handleViewAll"
|
||||
/>
|
||||
</template>
|
||||
<template #extra>
|
||||
<AuthenticationLoginExpiredModal
|
||||
v-model:open="accessStore.loginExpired"
|
||||
:avatar
|
||||
>
|
||||
<LoginForm />
|
||||
</AuthenticationLoginExpiredModal>
|
||||
</template>
|
||||
<template #lock-screen>
|
||||
<LockScreen :avatar @to-login="handleLogout" />
|
||||
</template>
|
||||
</BasicLayout>
|
||||
|
||||
<MyProfileModal
|
||||
:show-cancel-button="false"
|
||||
:show-confirm-button="false"
|
||||
:title="$t('abp.user.myAccount')"
|
||||
class="h-[410px] w-[800px]"
|
||||
>
|
||||
<MyProfile />
|
||||
</MyProfileModal>
|
||||
|
||||
<NotifyItemModal
|
||||
:fullscreen="true"
|
||||
:show-cancel-button="false"
|
||||
:show-confirm-button="false"
|
||||
>
|
||||
<NotifyItem />
|
||||
</NotifyItemModal>
|
||||
|
||||
<ChangePasswordModal>
|
||||
<ChangePassword
|
||||
:message="changePasswordTitle"
|
||||
@close="changePasswordModalApi.close"
|
||||
/>
|
||||
</ChangePasswordModal>
|
||||
</template>
|
||||
@ -1,108 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
import { z } from '@vben/common-ui';
|
||||
|
||||
import { ElMessage as Message } from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { postUsersChangePassword } from '#/api-client';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
interface Props {
|
||||
message: string;
|
||||
}
|
||||
defineOptions({
|
||||
name: 'ChangePassword',
|
||||
});
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
const emit = defineEmits<{
|
||||
close: any;
|
||||
}>();
|
||||
const [ResetPasswordForm, resetPasswordApi] = useVbenForm({
|
||||
// 默认展开
|
||||
collapsed: false,
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-4/5',
|
||||
},
|
||||
},
|
||||
// 提交函数
|
||||
handleSubmit: async () => {
|
||||
// 表单校验
|
||||
const { valid } = await resetPasswordApi.validate();
|
||||
if (!valid) return;
|
||||
const formValues = await resetPasswordApi.getValues();
|
||||
|
||||
if (formValues.currentPassword === formValues.confirmPassword) {
|
||||
Message.warning($t('abp.user.newPasswordAndCurrentPasswordNotAlike'));
|
||||
return;
|
||||
}
|
||||
if (formValues.newPassword !== formValues.confirmPassword) {
|
||||
Message.warning($t('abp.user.newPasswordAndConfirmPasswordNotMatch'));
|
||||
return;
|
||||
}
|
||||
await postUsersChangePassword({ body: formValues });
|
||||
Message.success($t('abp.user.changePassword') + $t('common.success'));
|
||||
await resetPasswordApi.resetForm();
|
||||
emit('close');
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.currentPassword'),
|
||||
},
|
||||
fieldName: 'currentPassword',
|
||||
label: $t('abp.user.currentPassword'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.currentPassword'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.newPassword'),
|
||||
},
|
||||
fieldName: 'newPassword',
|
||||
label: $t('abp.user.newPassword'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.newPassword'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.comfirmPassword'),
|
||||
},
|
||||
fieldName: 'confirmPassword',
|
||||
label: $t('abp.user.comfirmPassword'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.comfirmPassword'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
resetButtonOptions: {
|
||||
show: false,
|
||||
},
|
||||
submitButtonOptions: {
|
||||
content: '确认',
|
||||
},
|
||||
wrapperClass: 'grid-cols-1',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="flex w-full justify-center"
|
||||
style="margin-bottom: 20px; color: red"
|
||||
>
|
||||
{{ message }}
|
||||
</div>
|
||||
<ResetPasswordForm />
|
||||
</div>
|
||||
</template>
|
||||
@ -1,6 +0,0 @@
|
||||
const BasicLayout = () => import('./basic.vue');
|
||||
const AuthPageLayout = () => import('./auth.vue');
|
||||
|
||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||
|
||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||
@ -1,400 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { z } from '@vben/common-ui';
|
||||
|
||||
import {
|
||||
ElButton as Button,
|
||||
ElCol as Col,
|
||||
ElImage as Image,
|
||||
ElMessage as Message,
|
||||
ElRow as Row,
|
||||
ElStep as Step,
|
||||
ElSteps as Steps,
|
||||
ElTabPane as TabPane,
|
||||
ElTabs as Tabs,
|
||||
} from 'element-plus';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import {
|
||||
postUsersCanUseTwoFactor,
|
||||
postUsersChangePassword,
|
||||
postUsersDisabledTwoFactor,
|
||||
postUsersEnabledTwoFactor,
|
||||
postUsersGetQrCode,
|
||||
postUsersMyProfile,
|
||||
} from '#/api-client';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'MyProfile',
|
||||
});
|
||||
const activeName = ref(0);
|
||||
const loading = ref(false);
|
||||
const [ProfileForm, profileFormApi] = useVbenForm({
|
||||
// 默认展开
|
||||
collapsed: false,
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-4/5',
|
||||
},
|
||||
},
|
||||
// 提交函数
|
||||
handleSubmit: () => {},
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.userName'),
|
||||
},
|
||||
fieldName: 'userName',
|
||||
label: $t('abp.user.userName'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.userName'),
|
||||
}),
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.name'),
|
||||
},
|
||||
fieldName: 'name',
|
||||
disabled: true,
|
||||
label: $t('abp.user.name'),
|
||||
},
|
||||
// {
|
||||
// component: 'VbenInput',
|
||||
// componentProps: {
|
||||
// placeholder: $t('common.pleaseInput') + $t('abp.user.surname'),
|
||||
// },
|
||||
// fieldName: 'surname',
|
||||
// label: $t('abp.user.surname'),
|
||||
// rules: z.string().min(1, {
|
||||
// message: $t('common.pleaseInput') + $t('abp.user.surname'),
|
||||
// }),
|
||||
// },
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.email'),
|
||||
},
|
||||
fieldName: 'email',
|
||||
disabled: true,
|
||||
label: $t('abp.user.email'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.email'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.phone'),
|
||||
},
|
||||
fieldName: 'phoneNumber',
|
||||
disabled: true,
|
||||
label: $t('abp.user.phone'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.phone'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
// showCollapseButton: false,
|
||||
// showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-1',
|
||||
resetButtonOptions: {
|
||||
show: false,
|
||||
},
|
||||
submitButtonOptions: {
|
||||
show: false,
|
||||
},
|
||||
});
|
||||
const [ResetPasswordForm, resetPasswordApi] = useVbenForm({
|
||||
// 默认展开
|
||||
collapsed: false,
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-4/5',
|
||||
},
|
||||
},
|
||||
// 提交函数
|
||||
handleSubmit: async () => {
|
||||
// 表单校验
|
||||
const { valid } = await resetPasswordApi.validate();
|
||||
if (!valid) return;
|
||||
const formValues = await resetPasswordApi.getValues();
|
||||
|
||||
if (formValues.currentPassword === formValues.confirmPassword) {
|
||||
Message.warn($t('abp.user.newPasswordAndCurrentPasswordNotAlike'));
|
||||
return;
|
||||
}
|
||||
if (formValues.newPassword !== formValues.confirmPassword) {
|
||||
Message.warn($t('abp.user.newPasswordAndConfirmPasswordNotMatch'));
|
||||
return;
|
||||
}
|
||||
await postUsersChangePassword({ body: formValues });
|
||||
Message.success($t('common.editSuccess'));
|
||||
await resetPasswordApi.resetForm();
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.currentPassword'),
|
||||
},
|
||||
fieldName: 'currentPassword',
|
||||
label: $t('abp.user.currentPassword'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.currentPassword'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.newPassword'),
|
||||
},
|
||||
fieldName: 'newPassword',
|
||||
label: $t('abp.user.newPassword'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.newPassword'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.comfirmPassword'),
|
||||
},
|
||||
fieldName: 'confirmPassword',
|
||||
label: $t('abp.user.comfirmPassword'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.comfirmPassword'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
resetButtonOptions: {
|
||||
show: false,
|
||||
},
|
||||
submitButtonOptions: {
|
||||
content: '确认',
|
||||
},
|
||||
wrapperClass: 'grid-cols-1',
|
||||
});
|
||||
|
||||
const [EnabledTwoFactorForm, enabledTwoFactorApi] = useVbenForm({
|
||||
// 默认展开
|
||||
collapsed: false,
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-4/5',
|
||||
},
|
||||
},
|
||||
// 提交函数
|
||||
handleSubmit: () => {},
|
||||
layout: 'vertical',
|
||||
schema: [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.code'),
|
||||
},
|
||||
fieldName: 'code',
|
||||
label: $t('abp.user.code'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.code'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
resetButtonOptions: {
|
||||
show: false,
|
||||
},
|
||||
submitButtonOptions: {
|
||||
show: false,
|
||||
},
|
||||
wrapperClass: 'grid-cols-1',
|
||||
});
|
||||
|
||||
const [DisabledTwoFactorForm, disabledTwoFactorApi] = useVbenForm({
|
||||
// 默认展开
|
||||
collapsed: false,
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-4/5',
|
||||
},
|
||||
},
|
||||
// 提交函数
|
||||
handleSubmit: () => {},
|
||||
layout: 'horizontal',
|
||||
schema: [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('common.pleaseInput') + $t('abp.user.code'),
|
||||
},
|
||||
fieldName: 'code',
|
||||
label: $t('abp.user.code'),
|
||||
rules: z.string().min(1, {
|
||||
message: $t('common.pleaseInput') + $t('abp.user.code'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
resetButtonOptions: {
|
||||
show: false,
|
||||
},
|
||||
submitButtonOptions: {
|
||||
show: false,
|
||||
},
|
||||
wrapperClass: 'grid-cols-1',
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const resp = await postUsersMyProfile();
|
||||
await profileFormApi.setValues({ ...resp.data });
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
const currentTab = ref(0);
|
||||
const qrCode = ref('data:image/png;base64,');
|
||||
const secret = ref(''); // 密钥
|
||||
const enableTwoFactor = ref(false); // 当前账户是否已经开启了双因素验证
|
||||
async function activeChange(e: any) {
|
||||
if (e !== '2') return;
|
||||
loading.value = true;
|
||||
// 双因素验证
|
||||
const canUseTwoFactor = await postUsersCanUseTwoFactor();
|
||||
enableTwoFactor.value = canUseTwoFactor.data as boolean;
|
||||
|
||||
if (!enableTwoFactor.value) {
|
||||
// 如果双因素验证没有开启,则获取二维码。
|
||||
const qrCodeRes = await postUsersGetQrCode();
|
||||
qrCode.value = `data:image/png;base64,${qrCodeRes.data?.qrCode}` as string;
|
||||
secret.value = qrCodeRes.data?.secret as string;
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启双因素验证
|
||||
*/
|
||||
async function enabledTwoFactor() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const validResult = await enabledTwoFactorApi.validate();
|
||||
if (!validResult.valid) {
|
||||
Message.warning($t('common.pleaseInput') + $t('abp.user.code'));
|
||||
return;
|
||||
}
|
||||
const formValues = await enabledTwoFactorApi.getValues();
|
||||
await postUsersEnabledTwoFactor({
|
||||
body: {
|
||||
...formValues,
|
||||
secret: secret.value,
|
||||
},
|
||||
});
|
||||
enableTwoFactor.value = true;
|
||||
Message.info($t('abp.user.twoFactorEnabled'));
|
||||
} finally {
|
||||
await enabledTwoFactorApi.resetForm();
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭双因素验证
|
||||
*/
|
||||
async function disabledTwoFactor() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const validResult = await disabledTwoFactorApi.validate();
|
||||
if (!validResult.valid) {
|
||||
Message.warning($t('common.pleaseInput') + $t('abp.user.code'));
|
||||
return;
|
||||
}
|
||||
const formValues = await disabledTwoFactorApi.getValues();
|
||||
await postUsersDisabledTwoFactor({
|
||||
body: {
|
||||
...formValues,
|
||||
},
|
||||
});
|
||||
Message.info($t('abp.user.twoFactorDisabled'));
|
||||
} finally {
|
||||
await disabledTwoFactorApi.resetForm();
|
||||
enableTwoFactor.value = false;
|
||||
loading.value = false;
|
||||
await activeChange(2);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div class="bg-card px-8">
|
||||
<Tabs
|
||||
v-model:active-key="activeName"
|
||||
tab-position="left"
|
||||
@tab-change="activeChange"
|
||||
>
|
||||
<TabPane :key="0" :label="$t('abp.user.myProfile')">
|
||||
<ProfileForm />
|
||||
</TabPane>
|
||||
<TabPane :key="1" :label="$t('abp.user.changePassword')">
|
||||
<ResetPasswordForm />
|
||||
</TabPane>
|
||||
<TabPane :key="2" :label="$t('abp.user.twoFactor')">
|
||||
<div v-show="enableTwoFactor">
|
||||
<div style="margin-bottom: 5%">
|
||||
{{ $t('abp.user.twoFactorEnabledDesc') }}
|
||||
</div>
|
||||
<DisabledTwoFactorForm />
|
||||
<Button
|
||||
style="margin-left: 72%"
|
||||
type="primary"
|
||||
@click="disabledTwoFactor"
|
||||
>
|
||||
{{ $t('common.disabled') }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-show="!enableTwoFactor" class="mx-auto max-w-lg">
|
||||
<Steps :current="currentTab" class="steps">
|
||||
<Step title="Authenticator" />
|
||||
<Step :title="$t('abp.user.verifyAuthenticator')" />
|
||||
</Steps>
|
||||
<div style="margin-top: 30px; margin-left: 20px">
|
||||
<Row>
|
||||
<Col :span="12">
|
||||
<Image :src="qrCode" :width="200" />
|
||||
</Col>
|
||||
<Col :span="12">
|
||||
<div>
|
||||
{{ $t('abp.user.twoFactorDesc') }}
|
||||
<EnabledTwoFactorForm />
|
||||
<Button
|
||||
style="margin-left: 55%"
|
||||
type="primary"
|
||||
@click="enabledTwoFactor"
|
||||
>
|
||||
{{ $t('common.enabled') }}
|
||||
</Button>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,3 +0,0 @@
|
||||
# locale
|
||||
|
||||
每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。
|
||||
@ -1,102 +0,0 @@
|
||||
import type { Language } from 'element-plus/es/locale';
|
||||
|
||||
import type { App } from 'vue';
|
||||
|
||||
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import {
|
||||
$t,
|
||||
setupI18n as coreSetup,
|
||||
loadLocalesMapFromDir,
|
||||
} from '@vben/locales';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import enLocale from 'element-plus/es/locale/lang/en';
|
||||
import defaultLocale from 'element-plus/es/locale/lang/zh-cn';
|
||||
|
||||
const elementLocale = ref<Language>(defaultLocale);
|
||||
|
||||
const modules = import.meta.glob('./langs/**/*.json');
|
||||
|
||||
const localesMap = loadLocalesMapFromDir(
|
||||
/\.\/langs\/([^/]+)\/(.*)\.json$/,
|
||||
modules,
|
||||
);
|
||||
/**
|
||||
* 加载应用特有的语言包
|
||||
* 这里也可以改造为从服务端获取翻译数据
|
||||
* @param lang
|
||||
*/
|
||||
async function loadMessages(lang: SupportedLanguagesType) {
|
||||
const [appLocaleMessages] = await Promise.all([
|
||||
localesMap[lang]?.(),
|
||||
loadThirdPartyMessage(lang),
|
||||
]);
|
||||
return appLocaleMessages?.default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载第三方组件库的语言包
|
||||
* @param lang
|
||||
*/
|
||||
async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
|
||||
await Promise.all([loadElementLocale(lang), loadDayjsLocale(lang)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载dayjs的语言包
|
||||
* @param lang
|
||||
*/
|
||||
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
let locale;
|
||||
switch (lang) {
|
||||
case 'en-US': {
|
||||
locale = await import('dayjs/locale/en');
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
locale = await import('dayjs/locale/zh-cn');
|
||||
break;
|
||||
}
|
||||
// 默认使用英语
|
||||
default: {
|
||||
locale = await import('dayjs/locale/en');
|
||||
}
|
||||
}
|
||||
if (locale) {
|
||||
dayjs.locale(locale);
|
||||
} else {
|
||||
console.error(`Failed to load dayjs locale for ${lang}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载element-plus的语言包
|
||||
* @param lang
|
||||
*/
|
||||
async function loadElementLocale(lang: SupportedLanguagesType) {
|
||||
switch (lang) {
|
||||
case 'en-US': {
|
||||
elementLocale.value = enLocale;
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
elementLocale.value = defaultLocale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
|
||||
await coreSetup(app, {
|
||||
defaultLocale: preferences.app.locale,
|
||||
loadMessages,
|
||||
missingWarn: !import.meta.env.PROD,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export { $t, elementLocale, setupI18n };
|
||||
@ -1,140 +0,0 @@
|
||||
{
|
||||
"login": {
|
||||
"selectTenant": "Please select Tenant and ignore the non-tenant mode",
|
||||
"inputCode": "Please enter the two-factor authentication code. If two-factor authentication has not been enabled for your account, please ignore this message.",
|
||||
"oidcTip": "Login......"
|
||||
},
|
||||
"menu": {
|
||||
"system": "SystemManagement",
|
||||
"user": "UserManagement",
|
||||
"role": "RoleManagement",
|
||||
"tenant": "TenantManagement",
|
||||
"tenantList": "TenantList",
|
||||
"language": "LanguageManagement",
|
||||
"auditLog": "AuditLog",
|
||||
"loginLog": "LoginLog",
|
||||
"feature": "FeatureManagement",
|
||||
"setting": "SettingsManagement",
|
||||
"organizationUnit": "OrganizationUnitManagement",
|
||||
"languageText": "LanguageTextManagement",
|
||||
"dataDictionary": "DataDictionaryManagement",
|
||||
"notification": "NotificationManagement",
|
||||
"message": "MessageManagement",
|
||||
"code": "CodeManagement",
|
||||
"code-project": "ProjectList",
|
||||
"code-template": "TemplateList",
|
||||
"code-genarate": "CodeGenarate",
|
||||
"code-entity": "Entity",
|
||||
"code-template-detail": "TemplateDetail",
|
||||
"code-Preview": "Preview",
|
||||
"file": "FileManagement"
|
||||
},
|
||||
"user": {
|
||||
"user": "User",
|
||||
"userName": "UserName",
|
||||
"name": "Name",
|
||||
"surname": "Surname",
|
||||
"email": "Email",
|
||||
"phone": "Phone",
|
||||
"password": "Password",
|
||||
"currentPassword": "CurrentPassword",
|
||||
"newPassword": "NewPassword",
|
||||
"confirmNewPassword": "ConfirmNewPassword",
|
||||
"changePassword": "ChangePassword",
|
||||
"resetPassword": "ResetPassword",
|
||||
"status": "Status",
|
||||
"comfirmPassword": "ComfirmPassword",
|
||||
"comfirmDeleteUser": "Are you sure you want to delete the user",
|
||||
"newPasswordAndConfirmPasswordNotMatch": "New password and confirm password not match",
|
||||
"newPasswordAndCurrentPasswordNotAlike": "The old and new passwords cannot be the same",
|
||||
"myProfile": "MyProfile",
|
||||
"myAccount": "MyAccount",
|
||||
"twoFactor": "TwoFactor",
|
||||
"verifyAuthenticator": "Verify the Authenticator",
|
||||
"twoFactorDesc": "Your two-factor authentication app will generate a code. Please enter the code and confirm it.",
|
||||
"code": "TwoFactor Code",
|
||||
"twoFactorEnabled": "Two-factor authentication for your account has been successfully enabled. You will be required to enter the code generated by the application when logging in.",
|
||||
"twoFactorDisabled": "Two-factor authentication for your account has been turned off. You will no longer be required to enter the code generated by the application when logging in.",
|
||||
"twoFactorEnabledDesc": "Two-factor authentication has been enabled for your account. If you need to turn it off, please enter the two-factor authentication code and then disable it.",
|
||||
"resetTwoFactor": "ResetTwoFactor"
|
||||
},
|
||||
"role": {
|
||||
"role": "Role",
|
||||
"roleName": "RoleName",
|
||||
"isDefault": "IsDefault",
|
||||
"permissions": "Permissions"
|
||||
},
|
||||
"log": {
|
||||
"loginTime": "LoginTime",
|
||||
"userName": "UserName",
|
||||
"tenant": "Tenant",
|
||||
"executionTime": "ExecutionTime",
|
||||
"responseTime": "ResponseTime(ms)",
|
||||
"clientIp": "ClientIp",
|
||||
"exception": "Exception",
|
||||
"applicationName": "ApplicationName",
|
||||
"loginMode": "LoginMode",
|
||||
"loginUrl": "LoginUrl",
|
||||
"detail": "Detail"
|
||||
},
|
||||
"message": {
|
||||
"title": "Title",
|
||||
"content": "Content",
|
||||
"type": "Type",
|
||||
"level": "Level",
|
||||
"sender": "Sender",
|
||||
"receiver": "Receiver",
|
||||
"isRead": "IsRead",
|
||||
"sendMessage": "SendMessage",
|
||||
"sendNotification": "SendNotification",
|
||||
"setRead": "SetRead",
|
||||
"confirmRead": "Are you sure you want to set the message as read?",
|
||||
"read": "Read",
|
||||
"unread": "UnRead"
|
||||
},
|
||||
"language": {
|
||||
"language": "LanguageName",
|
||||
"showLanguage": "ShowLanguageName",
|
||||
"icon": "Icon",
|
||||
"resourceName": "ResourceName",
|
||||
"value": "Value",
|
||||
"name": "Name"
|
||||
},
|
||||
"dataDictionary": {
|
||||
"codeName": "Code|Name",
|
||||
"code": "Code",
|
||||
"name": "Name",
|
||||
"order": "Order",
|
||||
"status": "Status",
|
||||
"description": "Description",
|
||||
"type": "Type"
|
||||
},
|
||||
"organizationunit": {
|
||||
"organizationunit": "Organizationunit",
|
||||
"add": "Add",
|
||||
"member": "Member",
|
||||
"role": "Role",
|
||||
"userName": "UserName",
|
||||
"email": "Email",
|
||||
"name": "Name",
|
||||
"selectNode": "Select a node to operate on"
|
||||
},
|
||||
"tenant": {
|
||||
"tenant": "Tenant",
|
||||
"notExist": "Tenant Does not exist",
|
||||
"name": "Tenant Name",
|
||||
"adminEmail": "Admin Email",
|
||||
"adminPassword": "Admin Password",
|
||||
"mangeConnectionString": "MangeConnectionString",
|
||||
"addorEdit": "AddorEdit",
|
||||
"featureManagement": "FeatureManagement",
|
||||
"connectionStringName": "ConnectionStringName",
|
||||
"connectionString": "ConnectionString"
|
||||
},
|
||||
"file": {
|
||||
"file": "File",
|
||||
"name": "FileName",
|
||||
"size": "Size",
|
||||
"contentType": "ContentType"
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
{
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"search": "Search",
|
||||
"export": "Export",
|
||||
"save": "save",
|
||||
"seq": "Seq",
|
||||
"isEnable": "IsEnable",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"action": "Action",
|
||||
"createTime": "CreationTime",
|
||||
"pleaseInput": "Please input",
|
||||
"addSuccess": "Add Success",
|
||||
"editSuccess": "Edit Success",
|
||||
"deleteSuccess": "Delete Success",
|
||||
"yes": "yes",
|
||||
"no": "no",
|
||||
"confirmDelete": "Confirm to delete",
|
||||
"askConfirmDelete": "Confirm to delete?",
|
||||
"warning": "warning",
|
||||
"info": "info",
|
||||
"error": "error",
|
||||
"success": "Success",
|
||||
"keyword": "Keyword",
|
||||
"mesage403": "You don't have permission to access this page or function!",
|
||||
"mesage401": "You are not logged in or the login has timed out. Please log in again!",
|
||||
"mesage500": "Internal server error!",
|
||||
"mesage404": "The page you are trying to access does not exist!",
|
||||
"mesage400": "Request error!",
|
||||
"mesage405": "Incorrect request method!",
|
||||
"timeOut": "Request timed out!",
|
||||
"expandAll": "EexpandAll",
|
||||
"collapseAll": "CollapseAll",
|
||||
"pleaseSelect": "Please Select",
|
||||
"upload": "Upload",
|
||||
"download": "Download"
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"title": "Demos",
|
||||
"elementPlus": "Element Plus",
|
||||
"form": "Form",
|
||||
"vben": {
|
||||
"title": "Project",
|
||||
"about": "About",
|
||||
"document": "Document",
|
||||
"antdv": "Ant Design Vue Version",
|
||||
"naive-ui": "Naive UI Version",
|
||||
"element-plus": "Element Plus Version"
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
{
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"codeLogin": "Code Login",
|
||||
"qrcodeLogin": "Qr Code Login",
|
||||
"forgetPassword": "Forget Password",
|
||||
"thirdPartyLogin": "Third Party Login"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"analytics": "Analytics",
|
||||
"workspace": "Workspace"
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"templateManagement": "TemplateManagement",
|
||||
"templateList": "TemplateList",
|
||||
"name": "Name",
|
||||
"code": "Code",
|
||||
"content": "Content",
|
||||
"cultureName": "Language"
|
||||
}
|
||||
@ -1,140 +0,0 @@
|
||||
{
|
||||
"login": {
|
||||
"selectTenant": "请选择租户,非租户模式请忽略",
|
||||
"inputCode": "请输入双因素验证码,如果账户没有开启双因素验证请忽略",
|
||||
"oidcTip": "登陆中......"
|
||||
},
|
||||
"menu": {
|
||||
"system": "系统管理",
|
||||
"user": "用户管理",
|
||||
"role": "角色管理",
|
||||
"tenant": "租户管理",
|
||||
"tenantList": "租户列表",
|
||||
"language": "语言管理",
|
||||
"auditLog": "审计日志",
|
||||
"loginLog": "登录日志",
|
||||
"feature": "功能管理",
|
||||
"setting": "设置管理",
|
||||
"organizationUnit": "组织机构管理",
|
||||
"languageText": "语言文本管理",
|
||||
"dataDictionary": "数据字典管理",
|
||||
"notification": "通告管理",
|
||||
"message": "消息管理",
|
||||
"code": "代码管理",
|
||||
"code-project": "项目列表",
|
||||
"code-template": "模板列表",
|
||||
"code-genarate": "代码生成",
|
||||
"code-entity": "实体",
|
||||
"code-template-detail": "模板详情",
|
||||
"code-Preview": "预览",
|
||||
"file": "文件管理"
|
||||
},
|
||||
"user": {
|
||||
"user": "用户",
|
||||
"userName": "用户名",
|
||||
"name": "名称",
|
||||
"surname": "姓氏",
|
||||
"email": "邮箱",
|
||||
"phone": "手机号",
|
||||
"password": "密码",
|
||||
"currentPassword": "当前密码",
|
||||
"newPassword": "新密码",
|
||||
"confirmNewPassword": "确认新密码",
|
||||
"changePassword": "修改密码",
|
||||
"resetPassword": "重置密码",
|
||||
"status": "状态",
|
||||
"comfirmPassword": "确认密码",
|
||||
"comfirmDeleteUser": "确认删除用户",
|
||||
"newPasswordAndConfirmPasswordNotMatch": "新密码与确认密码不匹配",
|
||||
"newPasswordAndCurrentPasswordNotAlike": "新旧密码不能一样",
|
||||
"myProfile": "个人信息",
|
||||
"myAccount": "我的账户",
|
||||
"twoFactor": "双因素验证",
|
||||
"verifyAuthenticator": "验证身份验证器",
|
||||
"twoFactorDesc": "您的双因素身份验证应用程序将生成一个代码,请输入该代码并确认.",
|
||||
"code": "双因素验证码",
|
||||
"twoFactorEnabled": "您的账户已经成功开启双因素验证,登录时要求输入应用程序生成的代码.",
|
||||
"twoFactorDisabled": "您的账户已经关闭双因素验证,登录时不在要求输入应用程序生成的代码.",
|
||||
"twoFactorEnabledDesc": "您的账户已经开启双因素验证,如果需要关闭,请输入双因素验证码,然后在禁用.如果忘记验证码,请联系管理员.",
|
||||
"resetTwoFactor": "重置双因素验证"
|
||||
},
|
||||
"role": {
|
||||
"role": "角色",
|
||||
"roleName": "角色名称",
|
||||
"isDefault": "是否默认",
|
||||
"permissions": "授权"
|
||||
},
|
||||
"log": {
|
||||
"loginTime": "登录时间",
|
||||
"userName": "用户名",
|
||||
"tenant": "租户",
|
||||
"executionTime": "执行时间",
|
||||
"responseTime": "响应时间(毫秒)",
|
||||
"clientIp": "客户端Ip",
|
||||
"exception": "异常",
|
||||
"applicationName": "应用名称",
|
||||
"loginMode": "登录方式",
|
||||
"loginUrl": "登录地址",
|
||||
"detail": "详情"
|
||||
},
|
||||
"message": {
|
||||
"title": "标题",
|
||||
"content": "内容",
|
||||
"type": "类型",
|
||||
"level": "级别",
|
||||
"sender": "发送人",
|
||||
"receiver": "接收人",
|
||||
"isRead": "是否已读",
|
||||
"sendMessage": "发送消息",
|
||||
"sendNotification": "发送通告",
|
||||
"setRead": "设置已读",
|
||||
"confirmRead": "确认设置已读?",
|
||||
"read": "已读",
|
||||
"unread": "未读"
|
||||
},
|
||||
"language": {
|
||||
"language": "语言名称",
|
||||
"showLanguage": "显示名称",
|
||||
"icon": "图标",
|
||||
"resourceName": "资源名称",
|
||||
"value": "值",
|
||||
"name": "名称"
|
||||
},
|
||||
"dataDictionary": {
|
||||
"codeName": "编码|名称",
|
||||
"code": "编码",
|
||||
"name": "名称",
|
||||
"order": "排序",
|
||||
"status": "状态",
|
||||
"description": "描述",
|
||||
"type": "字典类型"
|
||||
},
|
||||
"organizationunit": {
|
||||
"organizationunit": "组织机构",
|
||||
"add": "新增根机构",
|
||||
"member": "成员",
|
||||
"role": "角色",
|
||||
"userName": "用户名",
|
||||
"email": "邮箱",
|
||||
"name": "名称",
|
||||
"selectNode": "请先选择一个节点再进行操作"
|
||||
},
|
||||
"tenant": {
|
||||
"tenant": "租户",
|
||||
"notExist": "租户不存在",
|
||||
"name": "租户名称",
|
||||
"adminEmail": "管理员邮箱",
|
||||
"adminPassword": "管理员密码",
|
||||
"mangeConnectionString": "管理链接字符串",
|
||||
"addorEdit": "新增或编辑",
|
||||
"featureManagement": "功能管理",
|
||||
"connectionStringName": "连接名称",
|
||||
"connectionString": "连接字符串"
|
||||
},
|
||||
"file": {
|
||||
"file": "文件",
|
||||
"name": "文件名称",
|
||||
"size": "文件大小",
|
||||
"contentType": "文件类型"
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
{
|
||||
"add": "新增",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"search": "搜索",
|
||||
"save": "保存",
|
||||
"export": "导出",
|
||||
"isEnable": "是否启用",
|
||||
"enabled": "启用",
|
||||
"disabled": "禁用",
|
||||
"seq": "序号",
|
||||
"action": "操作",
|
||||
"createTime": "创建时间",
|
||||
"pleaseInput": "请输入",
|
||||
"addSuccess": "新增成功!",
|
||||
"editSuccess": "编辑成功!",
|
||||
"deleteSuccess": "删除成功!",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"confirmDelete": "确认删除",
|
||||
"askConfirmDelete": "确认删除吗?",
|
||||
"warning": "警告",
|
||||
"info": "正常",
|
||||
"error": "错误",
|
||||
"success": "成功",
|
||||
"keyword": "关键字",
|
||||
"mesage403": "您没有权限访问此页面或功能!",
|
||||
"mesage401": "未登录或登录已超时,请重新登录!",
|
||||
"mesage500": "服务器内部错误!",
|
||||
"mesage404": "您访问的页面不存在!",
|
||||
"mesage400": "请求错误!",
|
||||
"mesage405": "请求方法错误!",
|
||||
"timeOut": "请求超时!",
|
||||
"expandAll": "展开全部",
|
||||
"collapseAll": "折叠全部",
|
||||
"pleaseSelect": "请选择",
|
||||
"upload": "上传",
|
||||
"download": "下载"
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"title": "演示",
|
||||
"elementPlus": "Element Plus",
|
||||
"form": "表单演示",
|
||||
"vben": {
|
||||
"title": "项目",
|
||||
"about": "关于",
|
||||
"document": "文档",
|
||||
"antdv": "Ant Design Vue 版本",
|
||||
"naive-ui": "Naive UI 版本",
|
||||
"element-plus": "Element Plus 版本"
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
{
|
||||
"auth": {
|
||||
"login": "登录",
|
||||
"register": "注册",
|
||||
"codeLogin": "验证码登录",
|
||||
"qrcodeLogin": "二维码登录",
|
||||
"forgetPassword": "忘记密码",
|
||||
"thirdPartyLogin": "第三方登录"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "概览",
|
||||
"analytics": "分析页",
|
||||
"workspace": "工作台"
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"templateManagement": "模板管理",
|
||||
"templateList": "模板列表",
|
||||
"name": "名称",
|
||||
"code": "编码",
|
||||
"content": "内容",
|
||||
"cultureName": "语言"
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
import { initPreferences } from '@vben/preferences';
|
||||
import { unmountGlobalLoading } from '@vben/utils';
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-imports
|
||||
import client from '#/api-client-config/index';
|
||||
|
||||
import { overridesPreferences } from './preferences';
|
||||
/**
|
||||
* 应用初始化完成之后再进行页面加载渲染
|
||||
*/
|
||||
async function initApplication() {
|
||||
// name用于指定项目唯一标识
|
||||
// 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据
|
||||
const env = import.meta.env.PROD ? 'prod' : 'dev';
|
||||
const appVersion = import.meta.env.VITE_APP_VERSION;
|
||||
const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;
|
||||
|
||||
// app偏好设置初始化
|
||||
await initPreferences({
|
||||
namespace,
|
||||
overrides: overridesPreferences,
|
||||
});
|
||||
|
||||
// 启动应用并挂载
|
||||
// vue应用主要逻辑及视图
|
||||
const { bootstrap } = await import('./bootstrap');
|
||||
await bootstrap(namespace);
|
||||
|
||||
// 移除并销毁loading
|
||||
unmountGlobalLoading();
|
||||
}
|
||||
|
||||
initApplication();
|
||||
@ -1,27 +0,0 @@
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
/**
|
||||
* @description 项目配置文件
|
||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||
*/
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
name: import.meta.env.VITE_APP_TITLE,
|
||||
// 是否开启检查更新
|
||||
enableCheckUpdates: false,
|
||||
// 检查更新的时间间隔,单位为分钟
|
||||
checkUpdatesInterval: 1,
|
||||
defaultAvatar: '/public/avatar-v1.webp', // 默认头像
|
||||
},
|
||||
theme: {
|
||||
mode: 'light',
|
||||
},
|
||||
copyright: {
|
||||
companyName: 'Abp Vben5 Ele',
|
||||
},
|
||||
logo: {
|
||||
source: '/logo-v1.webp', // 网站图标
|
||||
},
|
||||
});
|
||||
@ -1,42 +0,0 @@
|
||||
import type {
|
||||
ComponentRecordType,
|
||||
GenerateMenuAndRoutesOptions,
|
||||
} from '@vben/types';
|
||||
|
||||
import { generateAccessible } from '@vben/access';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import { getAllMenusApi } from '#/api';
|
||||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
||||
|
||||
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
||||
|
||||
const layoutMap: ComponentRecordType = {
|
||||
BasicLayout,
|
||||
IFrameView,
|
||||
};
|
||||
|
||||
return await generateAccessible(preferences.app.accessMode, {
|
||||
...options,
|
||||
fetchMenuListAsync: async () => {
|
||||
ElMessage({
|
||||
duration: 1500,
|
||||
message: `${$t('common.loadingMenu')}...`,
|
||||
});
|
||||
return await getAllMenusApi();
|
||||
},
|
||||
// 可以指定没有权限跳转403页面
|
||||
forbiddenComponent,
|
||||
// 如果 route.meta.menuVisibleWithForbidden = true
|
||||
layoutMap,
|
||||
pageMap,
|
||||
});
|
||||
}
|
||||
|
||||
export { generateAccess };
|
||||
@ -1,138 +0,0 @@
|
||||
import type { Router } from 'vue-router';
|
||||
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { startProgress, stopProgress } from '@vben/utils';
|
||||
|
||||
import { accessRoutes, coreRouteNames } from '#/router/routes';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { generateAccess } from './access';
|
||||
|
||||
/**
|
||||
* 通用守卫配置
|
||||
* @param router
|
||||
*/
|
||||
function setupCommonGuard(router: Router) {
|
||||
// 记录已经加载的页面
|
||||
const loadedPaths = new Set<string>();
|
||||
|
||||
router.beforeEach(async (to) => {
|
||||
to.meta.loaded = loadedPaths.has(to.path);
|
||||
|
||||
// 页面加载进度条
|
||||
if (!to.meta.loaded && preferences.transition.progress) {
|
||||
startProgress();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
router.afterEach((to) => {
|
||||
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
|
||||
|
||||
loadedPaths.add(to.path);
|
||||
|
||||
// 关闭页面加载进度条
|
||||
if (preferences.transition.progress) {
|
||||
stopProgress();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限访问守卫配置
|
||||
* @param router
|
||||
*/
|
||||
function setupAccessGuard(router: Router) {
|
||||
router.beforeEach(async (to, from) => {
|
||||
const accessStore = useAccessStore();
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// 基本路由,这些路由不需要进入权限拦截
|
||||
if (coreRouteNames.includes(to.name as string)) {
|
||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
||||
return decodeURIComponent(
|
||||
(to.query?.redirect as string) ||
|
||||
userStore.userInfo?.homePath ||
|
||||
preferences.app.defaultHomePath,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// accessToken 检查
|
||||
if (!accessStore.accessToken) {
|
||||
// 明确声明忽略权限访问权限,则可以访问
|
||||
if (to.meta.ignoreAccess) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 没有访问权限,跳转登录页面
|
||||
if (to.fullPath !== LOGIN_PATH) {
|
||||
return {
|
||||
path: LOGIN_PATH,
|
||||
// 如不需要,直接删除 query
|
||||
query:
|
||||
to.fullPath === preferences.app.defaultHomePath
|
||||
? {}
|
||||
: { redirect: encodeURIComponent(to.fullPath) },
|
||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||
replace: true,
|
||||
};
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
// 是否已经生成过动态路由
|
||||
if (accessStore.isAccessChecked) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 生成路由表
|
||||
// 当前登录用户拥有的角色标识列表
|
||||
// const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());
|
||||
// const userRoles = userInfo.roles ?? [];
|
||||
const refreshCount = userStore?.applicationInfo?.refreshCount ?? -1;
|
||||
if (import.meta.env.VITE_REFRESH_ROLE && refreshCount > 0) {
|
||||
await authStore.getApplicationConfiguration();
|
||||
}
|
||||
const userRoles = accessStore.accessCodes ?? [];
|
||||
|
||||
// 生成菜单和路由
|
||||
const { accessibleMenus, accessibleRoutes } = await generateAccess({
|
||||
roles: userRoles,
|
||||
router,
|
||||
// 则会在菜单中显示,但是访问会被重定向到403
|
||||
routes: accessRoutes,
|
||||
});
|
||||
|
||||
// 保存菜单信息和路由信息
|
||||
accessStore.setAccessMenus(accessibleMenus);
|
||||
accessStore.setAccessRoutes(accessibleRoutes);
|
||||
accessStore.setIsAccessChecked(true);
|
||||
const redirectPath = (from.query.redirect ??
|
||||
(to.path === preferences.app.defaultHomePath
|
||||
? userStore.userInfo?.homePath || preferences.app.defaultHomePath
|
||||
: to.fullPath)) as string;
|
||||
|
||||
return {
|
||||
...router.resolve(decodeURIComponent(redirectPath)),
|
||||
replace: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目守卫配置
|
||||
* @param router
|
||||
*/
|
||||
function createRouterGuard(router: Router) {
|
||||
/** 通用 */
|
||||
setupCommonGuard(router);
|
||||
/** 权限访问 */
|
||||
setupAccessGuard(router);
|
||||
}
|
||||
|
||||
export { createRouterGuard };
|
||||
@ -1,37 +0,0 @@
|
||||
import {
|
||||
createRouter,
|
||||
createWebHashHistory,
|
||||
createWebHistory,
|
||||
} from 'vue-router';
|
||||
|
||||
import { resetStaticRoutes } from '@vben/utils';
|
||||
|
||||
import { createRouterGuard } from './guard';
|
||||
import { routes } from './routes';
|
||||
|
||||
/**
|
||||
* @zh_CN 创建vue-router实例
|
||||
*/
|
||||
const router = createRouter({
|
||||
history:
|
||||
import.meta.env.VITE_ROUTER_HISTORY === 'hash'
|
||||
? createWebHashHistory(import.meta.env.VITE_BASE)
|
||||
: createWebHistory(import.meta.env.VITE_BASE),
|
||||
// 应该添加到路由的初始路由列表。
|
||||
routes,
|
||||
scrollBehavior: (to, _from, savedPosition) => {
|
||||
if (savedPosition) {
|
||||
return savedPosition;
|
||||
}
|
||||
return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };
|
||||
},
|
||||
// 是否应该禁止尾部斜杠。
|
||||
// strict: true,
|
||||
});
|
||||
|
||||
const resetRoutes = () => resetStaticRoutes(router, routes);
|
||||
|
||||
// 创建路由守卫
|
||||
createRouterGuard(router);
|
||||
|
||||
export { resetRoutes, router };
|
||||
@ -1,105 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const BasicLayout = () => import('#/layouts/basic.vue');
|
||||
const AuthPageLayout = () => import('#/layouts/auth.vue');
|
||||
/** 全局404页面 */
|
||||
const fallbackNotFoundRoute: RouteRecordRaw = {
|
||||
component: () => import('#/views/_core/fallback/not-found.vue'),
|
||||
meta: {
|
||||
hideInBreadcrumb: true,
|
||||
hideInMenu: true,
|
||||
hideInTab: true,
|
||||
title: '404',
|
||||
},
|
||||
name: 'FallbackNotFound',
|
||||
path: '/:path(.*)*',
|
||||
};
|
||||
|
||||
/** 基本路由,这些路由是必须存在的 */
|
||||
const coreRoutes: RouteRecordRaw[] = [
|
||||
/**
|
||||
* 根路由
|
||||
* 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。
|
||||
* 此路由必须存在,且不应修改
|
||||
*/
|
||||
{
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
hideInBreadcrumb: true,
|
||||
title: 'Root',
|
||||
},
|
||||
name: 'Root',
|
||||
path: '/',
|
||||
redirect: preferences.app.defaultHomePath,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
component: AuthPageLayout,
|
||||
meta: {
|
||||
hideInTab: true,
|
||||
title: 'Authentication',
|
||||
},
|
||||
name: 'Authentication',
|
||||
path: '/auth',
|
||||
redirect: LOGIN_PATH,
|
||||
children: [
|
||||
{
|
||||
name: 'Login',
|
||||
path: 'login',
|
||||
component: () => import('#/views/_core/authentication/login.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.login'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'CodeLogin',
|
||||
path: 'code-login',
|
||||
component: () => import('#/views/_core/authentication/code-login.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.codeLogin'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'QrCodeLogin',
|
||||
path: 'qrcode-login',
|
||||
component: () =>
|
||||
import('#/views/_core/authentication/qrcode-login.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.qrcodeLogin'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'OidcLogin',
|
||||
path: 'oidc-login',
|
||||
component: () => import('#/views/_core/authentication/oidc-login.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.thirdPartyLogin'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ForgetPassword',
|
||||
path: 'forget-password',
|
||||
component: () =>
|
||||
import('#/views/_core/authentication/forget-password.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.forgetPassword'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Register',
|
||||
path: 'register',
|
||||
component: () => import('#/views/_core/authentication/register.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.register'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export { coreRoutes, fallbackNotFoundRoute };
|
||||
@ -1,37 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { mergeRouteModules, traverseTreeValues } from '@vben/utils';
|
||||
|
||||
import { coreRoutes, fallbackNotFoundRoute } from './core';
|
||||
|
||||
const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
|
||||
eager: true,
|
||||
});
|
||||
|
||||
// 有需要可以自行打开注释,并创建文件夹
|
||||
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
|
||||
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
|
||||
|
||||
/** 动态路由 */
|
||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
||||
|
||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
||||
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
||||
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
||||
const staticRoutes: RouteRecordRaw[] = [];
|
||||
const externalRoutes: RouteRecordRaw[] = [];
|
||||
|
||||
/** 路由列表,由基本路由、外部路由和404兜底路由组成
|
||||
* 无需走权限验证(会一直显示在菜单中) */
|
||||
const routes: RouteRecordRaw[] = [
|
||||
...coreRoutes,
|
||||
...externalRoutes,
|
||||
fallbackNotFoundRoute,
|
||||
];
|
||||
|
||||
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
||||
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
||||
|
||||
/** 有权限校验的路由列表,包含动态路由和静态路由 */
|
||||
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
||||
export { accessRoutes, coreRouteNames, routes };
|
||||
@ -1,38 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'lucide:layout-dashboard',
|
||||
order: -1,
|
||||
title: $t('page.dashboard.title'),
|
||||
},
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
children: [
|
||||
{
|
||||
name: 'Analytics',
|
||||
path: '/analytics',
|
||||
component: () => import('#/views/dashboard/analytics/index.vue'),
|
||||
meta: {
|
||||
affixTab: true,
|
||||
icon: 'lucide:area-chart',
|
||||
title: $t('page.dashboard.analytics'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Workspace',
|
||||
path: '/workspace',
|
||||
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||
meta: {
|
||||
icon: 'carbon:workspace',
|
||||
title: $t('page.dashboard.workspace'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@ -1,29 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'ic:baseline-view-in-ar',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: $t('demos.title'),
|
||||
},
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
children: [
|
||||
{
|
||||
meta: {
|
||||
title: $t('demos.elementPlus'),
|
||||
icon: 'logos:element',
|
||||
},
|
||||
name: 'NaiveDemos',
|
||||
path: '/demos/element',
|
||||
component: () => import('#/views/demos/element/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@ -1,32 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
icon: 'ant-design:folder-open-outlined',
|
||||
order: 3,
|
||||
title: $t('abp.menu.file'),
|
||||
authority: ['FileManagement'],
|
||||
},
|
||||
name: 'file',
|
||||
path: '/file',
|
||||
children: [
|
||||
{
|
||||
name: 'abpFile',
|
||||
path: 'page',
|
||||
component: () => import('#/views/system/abpfiles/index.vue'),
|
||||
meta: {
|
||||
icon: 'ant-design:file-text-twotone',
|
||||
title: $t('abp.menu.file'),
|
||||
authority: ['FileManagement.File'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@ -1,144 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
icon: 'lucide:layout-dashboard',
|
||||
order: 1,
|
||||
title: $t('abp.menu.system'),
|
||||
authority: ['AbpIdentity'],
|
||||
},
|
||||
name: 'system',
|
||||
path: '/system',
|
||||
children: [
|
||||
{
|
||||
name: 'abpUser',
|
||||
path: 'user',
|
||||
component: () => import('#/views/system/abpuser/index.vue'),
|
||||
meta: {
|
||||
// affixTab: true,
|
||||
icon: 'ph:user',
|
||||
title: $t('abp.menu.user'),
|
||||
authority: ['AbpIdentity.Users'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'abpRole',
|
||||
path: 'role',
|
||||
component: () => import('#/views/system/abprole/index.vue'),
|
||||
meta: {
|
||||
icon: 'oui:app-users-roles',
|
||||
title: $t('abp.menu.role'),
|
||||
authority: ['AbpIdentity.Roles'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'OrganizationUnit',
|
||||
path: 'organizationUnit',
|
||||
component: () => import('#/views/system/abporganizationunit/index.vue'),
|
||||
meta: {
|
||||
title: $t('abp.menu.organizationUnit'),
|
||||
authority: ['AbpIdentity.OrganizationUnitManagement'],
|
||||
icon: 'ant-design:team-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'abpSetting',
|
||||
path: 'setting',
|
||||
component: () => import('#/views/system/abpsetting/index.vue'),
|
||||
meta: {
|
||||
icon: 'uil:setting',
|
||||
title: $t('abp.menu.setting'),
|
||||
authority: ['AbpIdentity.Setting'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'abpfeature',
|
||||
path: 'Feature',
|
||||
component: () => import('#/views/system/abpfeature/index.vue'),
|
||||
meta: {
|
||||
icon: 'ant-design:tool-outlined',
|
||||
title: $t('abp.menu.feature'),
|
||||
authority: ['AbpIdentity.FeatureManagement'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'DataDictionary',
|
||||
path: 'data-dictionary',
|
||||
component: () => import('#/views/system/abpdatadictionary/index.vue'),
|
||||
meta: {
|
||||
title: $t('abp.menu.dataDictionary'),
|
||||
authority: ['AbpIdentity.DataDictionaryManagement'],
|
||||
icon: 'ant-design:table-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AbpAuditLog',
|
||||
path: 'auditlog',
|
||||
component: () => import('#/views/system/abplog/audit.vue'),
|
||||
meta: {
|
||||
title: $t('abp.menu.auditLog'),
|
||||
authority: ['AbpIdentity.AuditLog'],
|
||||
icon: 'ant-design:snippets-twotone',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AbpLoginLog',
|
||||
path: 'loginlog',
|
||||
component: () => import('#/views/system/abplog/login.vue'),
|
||||
meta: {
|
||||
title: $t('abp.menu.loginLog'),
|
||||
authority: ['AbpIdentity.IdentitySecurityLogs'],
|
||||
icon: 'ant-design:file-protect-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AboLanguage',
|
||||
path: 'language',
|
||||
component: () => import('#/views/system/abplanguage/language.vue'),
|
||||
meta: {
|
||||
title: $t('abp.menu.language'),
|
||||
authority: ['AbpIdentity.Languages'],
|
||||
icon: 'ant-design:read-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AboLanguageText',
|
||||
path: 'languagetext',
|
||||
component: () => import('#/views/system/abplanguage/languagetext.vue'),
|
||||
meta: {
|
||||
title: $t('abp.menu.languageText'),
|
||||
authority: ['AbpIdentity.LanguageTexts'],
|
||||
icon: 'ant-design:font-size-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AbpNotification',
|
||||
path: 'notification',
|
||||
component: () =>
|
||||
import('#/views/system/abpnotification/notification.vue'),
|
||||
meta: {
|
||||
title: $t('abp.menu.notification'),
|
||||
authority: ['AbpIdentity.NotificationSubscriptionManagement'],
|
||||
icon: 'ant-design:comment-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AbpMessage',
|
||||
path: 'message',
|
||||
component: () => import('#/views/system/abpnotification/message.vue'),
|
||||
meta: {
|
||||
title: $t('abp.menu.message'),
|
||||
authority: ['AbpIdentity.NotificationManagement'],
|
||||
icon: 'ant-design:customer-service-twotone',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@ -1,32 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
icon: 'ant-design:switcher-filled',
|
||||
order: 2,
|
||||
title: $t('abp.menu.tenant'),
|
||||
authority: ['AbpTenantManagement'],
|
||||
},
|
||||
name: 'tenant',
|
||||
path: '/tenant',
|
||||
children: [
|
||||
{
|
||||
name: 'abpTenant',
|
||||
path: 'page',
|
||||
component: () => import('#/views/system/abptenant/index.vue'),
|
||||
meta: {
|
||||
icon: 'ph:user',
|
||||
title: $t('abp.menu.tenantList'),
|
||||
authority: ['AbpTenantManagement.Tenants'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@ -1,32 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
icon: 'ant-design:tool-outlined',
|
||||
// order: 998,
|
||||
title: $t('textTemplate.templateManagement'),
|
||||
authority: ['AbpTemplateManagement'],
|
||||
},
|
||||
name: 'TextTemplate',
|
||||
path: '/TextTemplate',
|
||||
children: [
|
||||
{
|
||||
path: 'page',
|
||||
name: 'TextTemplatePage',
|
||||
component: () => import('#/views/textTemplate/index.vue'),
|
||||
meta: {
|
||||
icon: 'ant-design:file-markdown-filled',
|
||||
title: $t('textTemplate.templateList'),
|
||||
authority: ['AbpTemplateManagement.Template'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@ -1,91 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
badgeType: 'dot',
|
||||
icon: 'ph:file-doc-light',
|
||||
order: 9999,
|
||||
title: $t('demos.vben.title'),
|
||||
},
|
||||
name: 'VbenProject',
|
||||
path: '/vben-admin',
|
||||
children: [
|
||||
// {
|
||||
// name: 'VbenAbout',
|
||||
// path: '/vben-admin/about',
|
||||
// component: () => import('#/views/_core/about/index.vue'),
|
||||
// meta: {
|
||||
// icon: 'lucide:copyright',
|
||||
// title: $t('demos.vben.about'),
|
||||
// },
|
||||
// },
|
||||
{
|
||||
name: 'VbenDocument',
|
||||
path: '/vben-admin/document',
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'lucide:book-open-text',
|
||||
link: 'https://doc.cncore.club/',
|
||||
title: 'ABPPro文档',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'VbenDocument',
|
||||
path: '/vben-admin/document',
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'lucide:book-open-text',
|
||||
link: 'http://doc.china.cncore.club:81/',
|
||||
title: 'ABPPro国内文档',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'VbenDocument',
|
||||
path: '/vben-admin/document',
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'lucide:book-open-text',
|
||||
link: 'https://abp.io/docs/latest/',
|
||||
title: 'ABP官方文档',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'VbenGithub',
|
||||
path: '/vben-admin/github',
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'mdi:github',
|
||||
link: 'https://github.com/WangJunZzz/abp-vnext-pro',
|
||||
title: 'Github',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'VbenGithub',
|
||||
path: '/vben-admin/github',
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'ant-design:google-circle-filled',
|
||||
link: 'https://gitee.com/WangJunZzz/abp-vnext-pro',
|
||||
title: 'Gitee',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'VbenGithub',
|
||||
path: '/vben-admin/github',
|
||||
component: IFrameView,
|
||||
meta: {
|
||||
icon: 'logos:element',
|
||||
link: 'https://element-plus-docs.bklab.cn/zh-CN/',
|
||||
title: 'Element Plus',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@ -1,153 +0,0 @@
|
||||
import type { Recordable, UserInfo } from '@vben/types';
|
||||
|
||||
import type {
|
||||
ApplicationAuthConfigurationDto,
|
||||
ApplicationConfigurationDto,
|
||||
} from '#/api-client';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||
|
||||
import { ElNotification, ElMessage as Message } from 'element-plus';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { getUserInfoApi, logoutApi } from '#/api';
|
||||
import {
|
||||
getApiAbpApplicationConfiguration,
|
||||
postApiAppAccountLogin2Fa,
|
||||
postTenantsFind,
|
||||
} from '#/api-client';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const accessStore = useAccessStore();
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
|
||||
const loginLoading = ref(false);
|
||||
|
||||
/**
|
||||
* 异步处理登录操作
|
||||
* Asynchronously handle the login process
|
||||
* @param params 登录表单数据
|
||||
*/
|
||||
async function authLogin(
|
||||
params: Recordable<any>,
|
||||
onSuccess?: () => Promise<void> | void,
|
||||
) {
|
||||
// 异步处理用户登录操作并获取 accessToken
|
||||
let userInfo: null | UserInfo = null;
|
||||
try {
|
||||
// 判断是否租户登录
|
||||
if (params.tenant) {
|
||||
const tenantResult = await postTenantsFind({
|
||||
body: {
|
||||
name: params.tenant,
|
||||
},
|
||||
});
|
||||
if (tenantResult.data?.success) {
|
||||
userStore.setTenantInfo(tenantResult.data as any);
|
||||
} else {
|
||||
userStore.setTenantInfo(null);
|
||||
Message.error($t('abp.tenant.notExist'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loginLoading.value = true;
|
||||
const { data = {} } = await postApiAppAccountLogin2Fa({
|
||||
body: {
|
||||
...params,
|
||||
},
|
||||
});
|
||||
// 如果成功获取到 accessToken
|
||||
if (data.token) {
|
||||
accessStore.setAccessToken(data.token);
|
||||
accessStore.setRefreshToken(data.refreshToken as string);
|
||||
userInfo = data as any;
|
||||
userStore.setUserInfo(userInfo as any);
|
||||
await getApplicationConfiguration();
|
||||
|
||||
if (accessStore.loginExpired) {
|
||||
accessStore.setLoginExpired(false);
|
||||
} else {
|
||||
onSuccess
|
||||
? await onSuccess?.()
|
||||
: await router.push(
|
||||
userInfo?.homePath || preferences.app.defaultHomePath,
|
||||
);
|
||||
}
|
||||
if (userInfo?.userName) {
|
||||
ElNotification({
|
||||
message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.userName}`,
|
||||
title: $t('authentication.loginSuccess'),
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
userStore.setTenantInfo(null);
|
||||
userStore.setUserInfo(null);
|
||||
} finally {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
userInfo,
|
||||
};
|
||||
}
|
||||
|
||||
async function logout(redirect: boolean = true) {
|
||||
try {
|
||||
await logoutApi();
|
||||
} catch {
|
||||
// 不做任何处理
|
||||
}
|
||||
resetAllStores();
|
||||
accessStore.setLoginExpired(false);
|
||||
|
||||
// 回登录页带上当前路由地址
|
||||
await router.replace({
|
||||
path: LOGIN_PATH,
|
||||
query: redirect
|
||||
? {
|
||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||
}
|
||||
: {},
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchUserInfo() {
|
||||
let userInfo: null | UserInfo = null;
|
||||
userInfo = await getUserInfoApi();
|
||||
userStore.setUserInfo(userInfo);
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
function $reset() {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
async function getApplicationConfiguration() {
|
||||
const { data: authData } = await getApiAbpApplicationConfiguration({
|
||||
query: { IncludeLocalizationResources: false },
|
||||
});
|
||||
const { auth } = authData as ApplicationConfigurationDto;
|
||||
const accessCodes = Object.keys(
|
||||
(auth as ApplicationAuthConfigurationDto)
|
||||
.grantedPolicies as unknown as Record<string, any>,
|
||||
);
|
||||
accessStore.setAccessCodes(accessCodes);
|
||||
}
|
||||
return {
|
||||
$reset,
|
||||
authLogin,
|
||||
fetchUserInfo,
|
||||
loginLoading,
|
||||
logout,
|
||||
getApplicationConfiguration,
|
||||
};
|
||||
});
|
||||
@ -1 +0,0 @@
|
||||
export * from './auth';
|
||||
@ -1,3 +0,0 @@
|
||||
# \_core
|
||||
|
||||
此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。
|
||||
@ -1,9 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { About } from '@vben/common-ui';
|
||||
|
||||
defineOptions({ name: 'About' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<About />
|
||||
</template>
|
||||
@ -1,69 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'CodeLogin' });
|
||||
|
||||
const loading = ref(false);
|
||||
const CODE_LENGTH = 6;
|
||||
|
||||
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: {
|
||||
codeLength: CODE_LENGTH,
|
||||
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().length(CODE_LENGTH, {
|
||||
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
/**
|
||||
* 异步处理登录操作
|
||||
* Asynchronously handle the login process
|
||||
* @param values 登录表单数据
|
||||
*/
|
||||
async function handleLogin(values: Recordable<any>) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(values);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationCodeLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
@submit="handleLogin"
|
||||
/>
|
||||
</template>
|
||||
@ -1,43 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'ForgetPassword' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: 'example@example.com',
|
||||
},
|
||||
fieldName: 'email',
|
||||
label: $t('authentication.email'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.emailTip') })
|
||||
.email($t('authentication.emailValidErrorTip')),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: Recordable<any>) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('reset email:', value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationForgetPassword
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
@ -1,84 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { computed, onBeforeMount, ref } from 'vue';
|
||||
|
||||
import { AuthenticationLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { getApiAppAbpProBasicApplicationConfiguration } from '#/api-client/index';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
defineOptions({ name: 'Login' });
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const showThirdPartyLogin = ref(false);
|
||||
const thirdPartLoginList = ref([]);
|
||||
const showTenant = ref(false);
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('abp.login.selectTenant'),
|
||||
},
|
||||
fieldName: 'tenant',
|
||||
label: $t('abp.tenant.tenant'),
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
|
||||
fieldName: 'name',
|
||||
label: $t('authentication.username'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.usernameTip') })
|
||||
.default('admin'),
|
||||
},
|
||||
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.passwordTip') })
|
||||
.default('1q2w3E*'),
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('abp.login.inputCode'),
|
||||
},
|
||||
fieldName: 'code',
|
||||
label: $t('abp.user.code'),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const result = await getApiAppAbpProBasicApplicationConfiguration();
|
||||
showThirdPartyLogin.value = result.data?.oidcConfiguration
|
||||
?.enabled as boolean;
|
||||
thirdPartLoginList.value = result.data?.oidcConfiguration
|
||||
?.oidcConfiguration as [];
|
||||
showTenant.value = result.data?.multiTenancy?.isEnabled ?? false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="authStore.loginLoading"
|
||||
:show-tenant-login="showTenant"
|
||||
:show-third-party-login="showThirdPartyLogin"
|
||||
:third-part-login-list="thirdPartLoginList as any"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
</template>
|
||||
@ -1,55 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
|
||||
import { ElNotification } from 'element-plus';
|
||||
|
||||
import { postApiAppAccountLoginOidc } from '#/api-client';
|
||||
import { $t } from '#/locales';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
defineOptions({ name: 'OidcLogin' });
|
||||
const loading = ref(true);
|
||||
const tip = ref($t('abp.login.oidcTip'));
|
||||
const router = useRouter();
|
||||
const { currentRoute } = useRouter();
|
||||
const accessStore = useAccessStore();
|
||||
const userStore = useUserStore();
|
||||
const code = currentRoute.value.query.code as string;
|
||||
const state = currentRoute.value.query.state as string;
|
||||
const authStore = useAuthStore();
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// oidc登录
|
||||
const result = await postApiAppAccountLoginOidc({ body: { code, state } });
|
||||
accessStore.setAccessToken(result.data?.token as string);
|
||||
userStore.setUserInfo(result.data as any);
|
||||
|
||||
await authStore.getApplicationConfiguration();
|
||||
await router.push(
|
||||
userStore.userInfo?.homePath || preferences.app.defaultHomePath,
|
||||
);
|
||||
if (result.data?.userName) {
|
||||
ElNotification({
|
||||
message: `${$t('authentication.loginSuccessDesc')}:${result.data?.userName}`,
|
||||
title: $t('authentication.loginSuccess'),
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
await router.push(LOGIN_PATH);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
{{ tip }}
|
||||
</div>
|
||||
</template>
|
||||
@ -1,10 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { AuthenticationQrCodeLogin } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
|
||||
defineOptions({ name: 'QrCodeLogin' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationQrCodeLogin :login-path="LOGIN_PATH" />
|
||||
</template>
|
||||
@ -1,96 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, h, ref } from 'vue';
|
||||
|
||||
import { AuthenticationRegister, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'Register' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
passwordStrength: true,
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
renderComponentContent() {
|
||||
return {
|
||||
strengthText: () => $t('authentication.passwordStrength'),
|
||||
};
|
||||
},
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.confirmPassword'),
|
||||
},
|
||||
dependencies: {
|
||||
rules(values) {
|
||||
const { password } = values;
|
||||
return z
|
||||
.string({ required_error: $t('authentication.passwordTip') })
|
||||
.min(1, { message: $t('authentication.passwordTip') })
|
||||
.refine((value) => value === password, {
|
||||
message: $t('authentication.confirmPasswordTip'),
|
||||
});
|
||||
},
|
||||
triggerFields: ['password'],
|
||||
},
|
||||
fieldName: 'confirmPassword',
|
||||
label: $t('authentication.confirmPassword'),
|
||||
},
|
||||
{
|
||||
component: 'VbenCheckbox',
|
||||
fieldName: 'agreePolicy',
|
||||
renderComponentContent: () => ({
|
||||
default: () =>
|
||||
h('span', [
|
||||
$t('authentication.agree'),
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
class: 'vben-link ml-1 ',
|
||||
href: '',
|
||||
},
|
||||
`${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,
|
||||
),
|
||||
]),
|
||||
}),
|
||||
rules: z.boolean().refine((value) => !!value, {
|
||||
message: $t('authentication.agreeTip'),
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: Recordable<any>) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('register submit:', value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationRegister
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
@ -1,7 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Fallback } from '@vben/common-ui';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fallback status="coming-soon" />
|
||||
</template>
|
||||
@ -1,9 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Fallback } from '@vben/common-ui';
|
||||
|
||||
defineOptions({ name: 'Fallback403Demo' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fallback status="403" />
|
||||
</template>
|
||||
@ -1,9 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Fallback } from '@vben/common-ui';
|
||||
|
||||
defineOptions({ name: 'Fallback500Demo' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fallback status="500" />
|
||||
</template>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user