This commit is contained in:
ChenYi 2025-05-27 19:30:22 +08:00
parent cbde1b330f
commit fed3ce1a01
1033 changed files with 0 additions and 105320 deletions

View File

@ -1,4 +0,0 @@
> 1%
last 2 versions
not dead
not ie 11

View File

@ -1 +0,0 @@
export { default } from '@vben/commitlint-config';

View File

@ -1,7 +0,0 @@
node_modules
.git
.gitignore
*.md
dist
.turbo
dist.zip

View File

@ -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
View File

@ -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

View File

@ -1,2 +0,0 @@
[core]
ignorecase = false

56
.gitignore vendored
View File

@ -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

View File

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

View File

@ -1 +0,0 @@
22.1.0

13
.npmrc
View File

@ -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

View File

@ -1,18 +0,0 @@
dist
dev-dist
.local
.output.js
node_modules
.nvmrc
coverage
CODEOWNERS
.nitro
.output
**/*.svg
**/*.sh
public
.npmrc
*-lock.yaml

View File

@ -1 +0,0 @@
export { default } from '@vben/prettier-config';

View File

@ -1,4 +0,0 @@
dist
public
__tests__
coverage

View File

@ -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"
]
}

View File

@ -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
View File

@ -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
View File

@ -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"
]
}

View File

@ -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
View File

@ -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
View File

@ -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就是对该项目的最大肯定!
- 如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
![](https://blog-resouce.oss-cn-shenzhen.aliyuncs.com/images/donate.png)

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -1,7 +0,0 @@
# public path
VITE_BASE=/
# Basic interface address SPA
VITE_GLOB_API_URL=/api
VITE_VISUALIZER=true

View File

@ -1,29 +0,0 @@
# 端口号
VITE_PORT=4200
VITE_BASE=/
# 接口地址
VITE_GLOB_API_URL=/api
# 是否开启 Nitro Mock服务true 为开启false 为关闭
VITE_NITRO_MOCK=true
# 是否打开 devtoolstrue 为打开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

View File

@ -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

View File

@ -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>

View File

@ -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:"
}
}

View File

@ -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

View File

@ -1,338 +0,0 @@
/**
* 使 adapter/form 使便使
* vben-formvben-modalvben-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 };

View File

@ -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 };

View File

@ -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';

View File

@ -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',
});

View File

@ -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;

View File

@ -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

View File

@ -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');
}

View File

@ -1,3 +0,0 @@
export * from './auth';
export * from './menu';
export * from './user';

View File

@ -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');
}

View File

@ -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');
}

View File

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

View File

@ -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 });

View File

@ -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>

View File

@ -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 };

View File

@ -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>

View File

@ -1 +0,0 @@
export { default as Icon } from './icon.vue';

View File

@ -1,2 +0,0 @@
export { default as TableAction } from './table-action.vue';
export type * from './types.d.ts';

View File

@ -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>

View File

@ -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;
}

View File

@ -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);

View File

@ -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 };
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 };

View File

@ -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>

View File

@ -1,3 +0,0 @@
# locale
每个app使用的国际化可能不同这里用于扩展国际化的功能例如扩展 dayjs、antd组件库的多语言切换以及app本身的国际化文件。

View File

@ -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 };

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -1,8 +0,0 @@
{
"templateManagement": "TemplateManagement",
"templateList": "TemplateList",
"name": "Name",
"code": "Code",
"content": "Content",
"cultureName": "Language"
}

View File

@ -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": "文件类型"
}
}

View File

@ -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": "下载"
}

View File

@ -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 版本"
}
}

View File

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

View File

@ -1,8 +0,0 @@
{
"templateManagement": "模板管理",
"templateList": "模板列表",
"name": "名称",
"code": "编码",
"content": "内容",
"cultureName": "语言"
}

View File

@ -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();

View File

@ -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', // 网站图标
},
});

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
};
});

View File

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

View File

@ -1,3 +0,0 @@
# \_core
此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。

View File

@ -1,9 +0,0 @@
<script lang="ts" setup>
import { About } from '@vben/common-ui';
defineOptions({ name: 'About' });
</script>
<template>
<About />
</template>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,7 +0,0 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
</script>
<template>
<Fallback status="coming-soon" />
</template>

View File

@ -1,9 +0,0 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'Fallback403Demo' });
</script>
<template>
<Fallback status="403" />
</template>

View File

@ -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