76 Commits

Author SHA1 Message Date
lyt
67e79a5786 update README.en.md. 2022-12-08 12:00:40 +08:00
lyt
a9e1b6770a publish inital version 2022-11-25 22:42:09 +08:00
lyt
161748a549 'admin-22.11.19:修复v2.3.0版本动态路由事件调用关闭当前tagsview、普通路由刷新界面参数丢失问题' 2022-11-19 00:09:19 +08:00
lyt
f9ce14431d 'admin-22.11.18:优化v2.3.0版本tagsview风格5兼容火狐' 2022-11-18 02:19:03 +08:00
lyt
3a8f31ab89 'admin-22.11.17:优化v2.3.0版本iframe右键菜单刷新问题' 2022-11-17 08:25:32 +08:00
lyt
f00fc0d2f1 'admin-22.11.17:优化v2.3.0版本iframe右键菜单刷新及loading' 2022-11-17 01:18:18 +08:00
lyt
32e2d6cf94 'admin-22.11.17:修复v2.3.0版本iframe右键菜单刷新' 2022-11-17 00:44:00 +08:00
5cb614c277 'admin-22.11.17:修复v2.3.0版本iframe右键菜单刷新'
Signed-off-by: lyt-Top <1105290566@qq.com>
2022-11-16 16:39:38 +00:00
lyt
cdf1b715df 'admin-22.11.16:发布v2.3.0,具体更新内容查看CHANGELOG.md' 2022-11-16 15:34:23 +08:00
lyt
aba9f3947d 'admin-22.07.20:降级vite@2.9.14,防止打包样式丢掉问题' 2022-07-20 23:15:32 +08:00
lyt
e08b4b3782 'admin-22.07.16:修复行内表单最后一个el-form-item位置下移问题' 2022-07-16 22:59:53 +08:00
lyt
356040806d 'admin-22.07.16:修复行内表单最后一个el-form-item位置下移问题' 2022-07-16 22:57:49 +08:00
lyt
f56cae3719 'admin-22.07.11:el-form表单最后一行样式,移动端lable左对齐' 2022-07-11 21:02:36 +08:00
b88fe3957f !32 开启后端控制路由找不到登录路由
Merge pull request !32 from 赵国辉/master
2022-07-11 12:49:53 +00:00
ccdce9b904 v2.2.0版本 src/router/route.ts 静态路由“布局界面”已删除,在“开启后端控制路由”时,未登录无法找到登录路由 2022-07-11 10:02:40 +08:00
134da2bc8c !30 修复多个权限同时校验的BUG
Merge pull request !30 from LostDeer/auth
2022-07-10 12:57:39 +00:00
4841a29c29 多个权限校验的BUG 2022-07-10 20:12:53 +08:00
lyt
e57dde4bab 'admin-22.07.10:发布v2.2.0版本,具体查看master分支根目录CHANGELOG.md' 2022-07-10 19:37:39 +08:00
fdf9cd4abe !29 修复tailwindcss无法正常工作
Merge pull request !29 from 山田/master
2022-07-03 06:23:56 +00:00
664e70de6c 修复tailwindcss无法正常工作 2022-06-17 10:32:20 +08:00
lyt
4259e9931f 'admin-22.06.07:函数deepClone对null的判断错误' 2022-06-07 22:30:57 +08:00
3a57a7f4ff !28 动态路由国际化匹配规则修复
Merge pull request !28 from tony星/dev
2022-06-07 14:28:46 +00:00
fab396b503 update src/utils/other.ts.
动态路由国际化判断修复,原正则表达式容易匹配到menu之类带en,zh的路由
2022-06-07 07:07:48 +00:00
lyt
41f6992630 'admin-22.06.06:添加大佬集成后端链接' 2022-06-06 22:08:16 +08:00
lyt
a402bd3c3a 'admin-22.06.04:优化开启TagViews缓存后登录到主页控制台js报错' 2022-06-04 19:49:30 +08:00
7d004ee948 !27 use Vue ref for dom
Merge pull request !27 from pauli/master
2022-06-04 11:23:46 +00:00
5a75f2626e fix: ref not id 2022-05-30 10:01:00 +08:00
lyt
21248a361e 'admin-22.05.29:优化最新版是否开启TagsView缓存的设置有严重Bug[#I59RXK](https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK)' 2022-05-29 16:10:57 +08:00
lyt
6441700ae1 'admin-22.05.28:更新优化v2.1.1,更新日志查看根目录CHANGELOG.md' 2022-05-28 19:10:19 +08:00
f716918ef2 !26 dark loading遮罩优化
Merge pull request !26 from tony星/dev
2022-05-27 15:01:39 +00:00
abf9f1ae08 update src/theme/dark.scss.
dark loading遮罩优化
2022-05-25 09:42:27 +00:00
lyt
1cd056cb83 'admin-22.05.22:修复标记为需要缓存的tab页后,再次从左侧菜单打开,还是显示被缓存的页面内容(#I4UY3G),感谢@axcc1234、特别感谢群友@华仔' 2022-05-22 17:18:13 +08:00
lyt
2c8dbace6c 'admin-22.05.19:优化tagsView、路由参数演示界面' 2022-05-19 19:08:29 +08:00
lyt
77b7621e87 'admin-22.05.11:适配element-plusv2.2.0版本,优化注释' 2022-05-11 18:52:22 +08:00
lyt
d6fceb257c 'admin-22.05.07:优化深色模式等' 2022-05-07 20:25:52 +08:00
lyt
23a0c21f15 'admin-22.05.05:优化注释' 2022-05-05 20:00:52 +08:00
lyt
6eaad912f5 admin-22.05.01:重构路由,解决部分No match found for location with path xxx 2022-05-01 11:43:53 +08:00
lyt
faf372a8b9 'admin-22.04.30:退出登录报错问题' 2022-04-30 01:31:21 +08:00
lyt
d375051ec3 'admin-22.04.30:优化首屏加载loading问题' 2022-04-30 00:26:46 +08:00
lyt
c630f04194 'admin-22.04.29:优化滚动条问题及替换pinia后的bug' 2022-04-29 22:57:31 +08:00
lyt
f6302a8b40 'admin-22.04.29:修复tagsview标签页高亮等问题' 2022-04-29 13:19:54 +08:00
lyt
9991d931a2 'admin-22.04.28:wangeditor替换成v5,适配深色模式' 2022-04-28 19:39:44 +08:00
lyt
35fdb164e9 Merge branch 'develop' of https://gitee.com/lyt-top/vue-next-admin into develop 2022-04-27 19:30:10 +08:00
lyt
695884a1aa 'admin-22.04.27:修复全局修改组件大小失效了' 2022-04-27 19:29:31 +08:00
65c953a613 update src/components/iconSelector/index.vue. 2022-04-26 11:21:48 +00:00
lyt
4f36e46f7b 'admin-22.04.26:优化图标选择器回显问题' 2022-04-26 19:17:00 +08:00
lyt
ba4247febd 'admin-22.04.25:优化注释内容' 2022-04-25 19:05:12 +08:00
lyt
c942aec7f1 'admin-22.04.20:优化地址栏有参数退出登录,再次登录不跳之前界面问题' 2022-04-20 20:57:11 +08:00
lyt
c180c24769 'admin-22.04.20:修复开启Tagsview图标、router.push路径找不到时报错问题' 2022-04-20 20:34:30 +08:00
lyt
ec1e963358 'admin-22.04.19:优化菜单鼠标经过hover颜色不统一问题' 2022-04-19 22:01:54 +08:00
lyt
805f991791 'admin-22.04.19:修复打包错误问题' 2022-04-19 21:15:06 +08:00
lyt
d50627e0df admin-22.04.18:更新版本v2.1.0,更新内容查看根目录CHANGELOG.md 2022-04-19 20:31:05 +08:00
lyt
638fe523cb admin-22.04.18:更新版本v2.1.0,更新内容查看根目录CHANGELOG.md 2022-04-18 22:00:00 +08:00
lyt
6dcc2c78c1 admin-22.04.18:更新版本v2.1.0,更新内容查看根目录CHANGELOG.md 2022-04-18 19:14:38 +08:00
lyt
8c216f6e94 'admin-22.03.04:更新版本v2.0.2,更新内容查看根目录CHANGELOG.md' 2022-03-04 12:39:54 +08:00
lyt
49c5eaf1bc 'admin-22.02.25:更新版本v2.0.1,更新内容查看根目录CHANGELOG.md' 2022-02-25 23:19:12 +08:00
lyt
4408b04b0f 'admin-22.02.21:更新版本v2.0.0,更新内容查看根目录CHANGELOG.md' 2022-02-21 23:52:59 +08:00
lyt
920c705421 'admin-21.12.30:新增tagsview风格5' 2021-12-30 20:24:35 +08:00
98e75351b8 !17 修复 修改elementPlus 组件size无效BUG
Merge pull request !17 from P)/master
2021-12-23 11:50:33 +00:00
P)
8ae48b5c7c 修复 修改elementPlus 组件size无效BUG 2021-12-23 05:11:31 +00:00
lyt
616de6e06b 'admin-21.12.22:优化打包时警告问题' 2021-12-22 21:37:33 +08:00
e2db218ca8 update vite.config.ts. 2021-12-22 03:25:32 +00:00
6a6b95b725 'admin-21.12.21:更新优化内容请看CHANGELOG.md文件' 2021-12-21 13:49:55 +00:00
lyt
73db2121ff 'admin-21.12.21:更新优化内容请看CHANGELOG.md文件' 2021-12-21 21:35:01 +08:00
lyt
9051c31771 'admin-21.12.21:更新优化内容请看CHANGELOG.md文件' 2021-12-21 21:32:26 +08:00
lyt
18ef20b230 'admin-21.12.17:修复tagsView拖拽问题,感谢@简单爱、' 2021-12-17 20:19:11 +08:00
lyt
ec5911a987 'admin-21.12.16:优化登录页、添加开发文档连接' 2021-12-16 21:10:17 +08:00
lyt
646eac1c88 'admin-21.12.12:更新修复查看CHANGELOG.md文件' 2021-12-12 21:27:33 +08:00
94af6f8e1e !15 fixed typo
Merge pull request !15 from Rehmet/master
2021-12-12 04:39:01 +00:00
f9c604ac96 update README.md. fixed typo 2021-12-11 09:52:43 +00:00
lyt
4228b58434 'admin-21.12.04:修复更新诸多内容,具体查看CHANGELOG.md文件。改动大,谨慎更新!' 2021-12-04 13:32:14 +08:00
lyt
cc7520976e 'admin-21.10.31:优化login界面组件name值重复问题' 2021-10-31 22:49:53 +08:00
lyt
e3aa4d4816 'admin-21.10.30:修复演示错误,TagsView拖拽问题,感谢群友@小明画家、@HelloWord' 2021-10-30 21:11:40 +08:00
lyt
af13922492 'admin-21.10.18:优化tagsview问题' 2021-10-18 20:32:06 +08:00
lyt
6467f57cf2 'admin-21.10.18:优化tagsview问题' 2021-10-18 20:21:22 +08:00
lyt
04ca9b04c1 'admin-21.10.17:更新优化,具体查看根目录CHANGELOG.md' 2021-10-17 12:32:28 +08:00
265 changed files with 13400 additions and 14260 deletions

11
.env
View File

@ -1,11 +0,0 @@
# port 端口号
VITE_PORT = 8888
# open 运行 npm run dev 时自动打开浏览器
VITE_OPEN = false
# 打包是否开启 cdn源文件 utils/build.ts可自行修改
VITE_OPEN_CDN = false
# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
VITE_PUBLIC_PATH =

View File

@ -1,5 +0,0 @@
# 本地环境
ENV = development
# 本地环境接口地址
VITE_API_URL = http://localhost:8888/

View File

@ -1,5 +0,0 @@
# 线上环境
ENV = production
# 线上环境接口地址
VITE_API_URL = https://lyt-top.gitee.io/vue-next-admin-preview/

32
.gitignore vendored
View File

@ -1,23 +1,11 @@
.DS_Store
node_modules
/dist
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Local History for Visual Studio Code
.history/
frontend/LICENSE
*.zip

View File

@ -1,94 +0,0 @@
# <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">vue-next-admin-template不带国际化 更新日志</a>
🎉🎉🔥 `vue-next-admin-template` 基于 vue-next-admin-v2.4.33 版本) vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 2.4.33
`2023.04.12`
- 🎉 同步 master 分支 v2.4.33 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.4.32
`2023.03.26`
- 🎉 同步 master 分支 v2.4.32 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.4.31
`2023.03.10`
- 🎉 同步 master 分支 v2.4.31 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.4.3
`2023.02.22`
🚩🚩🚩 感谢 [驰骋工作流引擎-表单引擎-低代码开发平台](http://www.ccflow.org/) 赞助商的赞助。驰骋公司为社会提供流程引擎+表单引擎+低代码开发平台一体的开源软件解决方案,欢迎广大开发者前去体验!
- 🎉 同步 master 分支 v2.4.3 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.4.21
`2022.12.12`
- 🎉 同步 master 分支 v2.4.21 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.4.2
`2022.12.10`
- 🎉 同步 master 分支 v2.4.2 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.4.1
`2022.11.30`
- 🎉 同步 master 分支 v2.4.1 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.3.0
`2022.11.16`
- 🎉 同步 master 分支 v2.3.0 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.2.0
`2022.07.11`
- 🎉 同步 master 分支 v2.2.0 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.1.1
- 🎉 同步 master 分支 v2.1.1 版本内容,具体查看 [master CHANGELOG.md](https://gitee.com/lyt-top/vue-next-admin/blob/master/CHANGELOG.md)
## 2.0.2
- 🎉 同步 master 分支 v2.0.2 版本内容,具体查看 master CHANGELOG.md
## 0.2.2
`2021.12.21`
- 🎉 同步 master 分支 v1.2.2 版本内容,具体查看 master CHANGELOG.md
## 0.2.1
`2021.12.12`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 浏览器标题问题
- 🐞 修复 element plus svg 图标引入
- 🐞 修复 默认显示英文问题,改成默认显示中文
## 0.2.0
`2021.12.04`
- 🎉 同步 master 分支 v1.2.0 版本内容,具体查看 master CHANGELOG.md
## 0.1.0
`2021.10.17`
- 🎉 新增 vue-next-admin-template 基础版本(不带国际化),切换 `vue-next-admin-template` 分支

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 lyt-Top
Copyright (c) 2022 倔强嘴角留下一抹殇
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

91
README.en.md Normal file

File diff suppressed because one or more lines are too long

190
README.md

File diff suppressed because one or more lines are too long

25
backend/.eslintrc.js Normal file
View File

@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir : __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

37
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,37 @@
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
package-lock.json
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.lock

4
backend/.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

5
backend/nest-cli.json Normal file
View File

@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}

90
backend/package.json Normal file
View File

@ -0,0 +1,90 @@
{
"name": "tsplatform",
"version": "1.1.0",
"description": "nestjs backend",
"author": "SweetHoney",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@fastify/static": "^6.5.0",
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0",
"@nestjs/jwt": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/platform-fastify": "^9.2.0",
"@nestjs/swagger": "^6.1.3",
"@nestjs/typeorm": "^9.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"cookie-parser": "^1.4.6",
"csurf": "^1.11.0",
"express-rate-limit": "^6.7.0",
"fastify-swagger": "^5.2.0",
"fs": "^0.0.1-security",
"helmet": "^6.0.0",
"mysql2": "^2.3.3",
"passport-jwt": "^4.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"ts-md5": "^1.3.1",
"typeorm": "^0.3.10"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.8",
"@types/node": "^16.0.0",
"@types/passport-jwt": "^3.0.7",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "28.1.3",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.8",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.0",
"typescript": "^4.7.4"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@ -0,0 +1,47 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 10:59:16
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 10:58:50
* @FilePath: \tsplatform\backend\src\Config\Index.ts
* @Description: 系统全局配置
*/
/**
* 系统配置
*/
export const AppConfig = {
debug: false,
port: 3000,
jwt: {
secret: "4vRk^ga52xVP$B2vYK$%r8a8hctLgbU9",
expiresIn: "60000s"
},
md5Key: `4vRk^ga52xVP$B2vYK$%r8a8hctLgbU9`
}
/**
* 数据库配置
*/
export const DataBaseConfig = {
DataBase: {
type: 'mysql',
host: '127.0.0.1',
port: 3306,
database: 'sweet_honey',
username: 'root',
password: 'root',
//是否自动迁移同步
synchronize: true,
},
DataBase1: {
type: 'mysql',
host: '127.0.0.1',
port: 3306,
database: 'test1',
username: 'root',
password: 'root',
//是否自动迁移同步
synchronize: true,
}
}

View File

@ -0,0 +1,123 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 17:06:21
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 11:11:11
* @FilePath: \tsplatform\backend\src\Controller\AuthController.ts
* @Description: 系统授权控制器
*/
import { Body, Controller, Get, Post, Request, Req, UseGuards } from '@nestjs/common';
import { RestfulReturn } from 'src/Expand/RestfulReturn';
import {
ApiBearerAuth,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { UserService } from 'src/Service/UserService';
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
import { JwtService } from '@nestjs/jwt';
import { LoginDto } from 'src/EntiyDto/LoginDto';
import { AppConfig } from 'src/Config/Index';
import { RoleService } from 'src/Service/RoleService';
import { MenuService } from 'src/Service/MenuService';
@ApiBearerAuth()
@ApiTags('系统授权管理')
@Controller('/auth')
export class AuthController {
constructor(
public readonly _restful: RestfulReturn,
public readonly _service: UserService,
public readonly _roleService: RoleService,
public readonly _menuService: MenuService,
public readonly _jwt: JwtService
) { }
@ApiOperation({
summary: '获取账号登录验证码',
description: '获取账号登录验证码'
})
@Get('/captcha')
async captcha() {
return await this._restful.toJson(
RestfulHttpCodeEnum.SUCCESS, ""
);
}
@ApiOperation({
summary: '账号密码登录',
description: '账号密码登录'
})
@Post('/login')
async login(@Body() dto: LoginDto) {
const result = await this._service.repository.findOne({ where: { UserName: dto.UserName, Password: dto.Password } });
if (result && result.Id > 0) {
const token = await this._jwt.signAsync({ uid: result.Id }, {
secret: AppConfig.jwt.secret,
expiresIn: AppConfig.jwt.expiresIn,
});
return await this._restful.toJson(
RestfulHttpCodeEnum.SUCCESS, { userinfo: result, token: token }
);
} else {
return await this._restful.toJson(
RestfulHttpCodeEnum.FAIL, "账号密码有误!"
);
}
}
@ApiOperation({
summary: '根据token获取用户信息',
description: '根据token获取用户信息'
})
@Get('/getUserInfo')
async getUserInfo(@Req() request: Request) {
try {
const token = request.headers["authorization"];
const jwtResult = await this._jwt.verifyAsync(token, { secret: AppConfig.jwt.secret, ignoreExpiration: false })
const uid = jwtResult.uid;
const result = await this._service.getById(uid);
const menuRole = await this._roleService.getMenusByRoleId(result.RoleId);
const menuIds: number[] = [];
for await (const info of menuRole) {
menuIds.push(info.MenuId);
}
const menus = await this._menuService.repository.createQueryBuilder().whereInIds(menuIds).getMany()
const permissions: string[] = [];
for await (const info of menus) {
permissions.push(info.Permission);
}
return await this._restful.toJson(
RestfulHttpCodeEnum.SUCCESS, { userInfo: result, permission: permissions }
);
}
catch (ex) {
return await this._restful.toJson(
RestfulHttpCodeEnum.FAIL, ex
);
}
}
@ApiOperation({
summary: '根据token退出登录',
description: '根据token退出登录'
})
@Get('/loginOut')
async loginOut(@Req() request: Request) {
try {
const token = request.headers["authorization"];
const jwtResult = await this._jwt.verifyAsync(token, { secret: AppConfig.jwt.secret, ignoreExpiration: false })
const uid = jwtResult.uid;
const result = await this._service.getById(uid);
return await this._restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
catch (ex) {
return await this._restful.toJson(
RestfulHttpCodeEnum.FAIL, ex
);
}
}
}

View File

@ -0,0 +1,143 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 17:05:40
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 11:11:20
* @FilePath: \tsplatform\backend\src\Controller\RoleController.ts
* @Description: 省市区控制器
*/
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { RestfulReturn } from 'src/Expand/RestfulReturn';
import {
ApiBearerAuth,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { IdDto, IdsDto, ListDto, PageDto } from 'src/EntiyDto/CommonDto';
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
import { CityService } from 'src/Service/CityService';
import { SysCity } from 'src/Entity/SysCity';
import { Equal, Not } from 'typeorm';
@ApiBearerAuth()
@ApiTags('省市区管理')
@Controller('/city')
export class CityController {
constructor(
public readonly restful: RestfulReturn,
public readonly service: CityService
) {
}
//#region 基础控制器
@ApiOperation({
summary: '分页查询省市区列表',
description: '分页查询省市区列表'
})
@Get('/getPage')
async getPage(@Query() dto: PageDto) {
const result = await this.service.getPage(dto.Page, dto.PageSize, dto.Keywords, dto.OrderBy);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS,
{
total: result[1],
page: 1,
pageSize: 10,
rows: result[0],
}
);
}
@ApiOperation({
summary: '查询省市区列表',
description: '查询省市区列表'
})
@Get('/getList')
async getList(@Query() dto: ListDto) {
const result = await this.service.getList(dto.Keywords, dto.OrderBy);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '根据ID查询省市区',
description: '根据ID查询省市区'
})
@Get('/getById')
async getById(@Query() dto: IdDto) {
const result = await this.service.getById(dto.Id);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '根据ID数组查询省市区',
description: '根据ID数组查询省市区'
})
@Get('/getByIds')
async getByIds(@Query() dto: IdsDto) {
const result = await this.service.getByIds(dto.Ids);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '新增省市区',
description: '新增省市区'
})
@Post('/add')
async add(@Body() model: SysCity) {
const isRepeat = await this.service.repository.createQueryBuilder()
.orWhere({ RegionName: Equal(model.RegionName.trim()), })
.orWhere({ RegionCode: Equal(model.RegionCode.trim()) }).getCount();
if (isRepeat > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, "地区名称或者行政编号重复!"
);
} else {
const result = await this.service.save(new SysCity());
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
}
@ApiOperation({
summary: '修改省市区',
description: '修改省市区'
})
@Post('/edit')
async edit(@Body() model: SysCity) {
const isRepeat = await this.service.repository.createQueryBuilder()
.where({ Id: Not(model.Id) })
.andWhere([{ RegionName: Equal(model.RegionName.trim()) }, { RegionCode: Equal(model.RegionCode.trim()) }])
.getCount();
if (isRepeat > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, "地区名称或者行政编号重复!"
);
} else {
const result = await this.service.save(new SysCity());
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
}
@ApiOperation({
summary: '假删除省市区',
description: '假删除省市区'
})
@Post('/del')
async del(@Body() dto: IdDto) {
const result = await this.service.destroy(dto.Id);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
//#endregion
}

View File

@ -0,0 +1,57 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 17:05:41
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 10:11:01
* @FilePath: \tsplatform\backend\src\Controller\UserController.ts
* @Description: 系统用户控制器
*/
import { Controller, Get } from '@nestjs/common';
import { RestfulReturn } from 'src/Expand/RestfulReturn';
import {
ApiBearerAuth,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
import { CityService } from 'src/Service/CityService';
import { RoleService } from 'src/Service/RoleService';
import { MenuService } from 'src/Service/MenuService';
import { UserService } from 'src/Service/UserService';
@ApiBearerAuth()
@ApiTags('系统管理')
@Controller('/')
export class IndexController {
constructor(
public readonly restful: RestfulReturn,
public readonly role_service: RoleService,
public readonly menu_service: MenuService,
public readonly user_service: UserService,
public readonly city_service: CityService,
) {
}
@ApiOperation({
summary: '数据初始化',
description: '数据初始化'
})
@Get('/seed')
async seedData() {
//初始化账号数据
const userCount = await this.user_service.seedData();
//初始化菜单数据
const menuCount = await this.menu_service.seedData();
//初始化角色数据
const roleCounts = await this.role_service.seedData();
//初始化省市区数据
const cityCount = await this.city_service.seedData();
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS,
`一共Seed${userCount}条记录!\r\n` +
`一共Seed${menuCount}条记录!\r\n` +
`一共Seed${roleCounts[0]}条记录!\r\n` +
`一共Seed${roleCounts[0]}条记录!\r\n` +
`一共Seed${cityCount}条记录!`
);
}
}

View File

@ -0,0 +1,294 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 09:19:20
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 00:23:12
* @FilePath: \tsplatform\backend\src\Controller\MenuController.ts
* @Description: 系统菜单控制器
*/
import { Body, Controller, Get, HttpException, HttpStatus, Post, Query } from '@nestjs/common';
import { RestfulReturn } from 'src/Expand/RestfulReturn';
import {
ApiBearerAuth,
ApiOperation,
ApiProperty,
ApiTags,
} from '@nestjs/swagger';
import { SysMenu } from 'src/Entity/SysMenu';
import { plainToClass } from 'class-transformer';
import { IdDto, IdsDto, IdStatusDto, ListDto, PageDto, RoleIdDto } from 'src/EntiyDto/CommonDto';
import { MenuService } from 'src/Service/MenuService';
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
import { MenuDto } from 'src/EntiyDto/MenuDto';
import { RouteMenu } from 'src/EntiyDto/RouteMenu';
import { Equal, In, Not } from 'typeorm';
import { RoleService } from 'src/Service/RoleService';
@Controller('/menu')
@ApiBearerAuth()
@ApiTags('系统菜单管理')
export class MenuController {
constructor(
public readonly restful: RestfulReturn,
public readonly service: MenuService,
public readonly _roleService: RoleService,
) {
}
@ApiOperation({
summary: '获取所有菜单Tree菜单列表使用',
description: '获取所有菜单Tree'
})
@Get('/getMenuTree')
async getMenuTree() {
const result = await this.MenuTree(0);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '根据角色ID获取用户授权的菜单Tree前端router使用',
description: '根据角色ID获取用户授权的菜单Tree'
})
@Get('/getAuthMenuTree')
async getAuthMenuTree(@Query() dto: RoleIdDto) {
if (dto.RoleId > 0) {
const result = await this.RouteTree(0, dto.RoleId);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
} else {
throw new HttpException("角色Id不能为空", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//递归获取菜单Tree
private async MenuTree(parentId: number): Promise<SysMenu[]> {
const result = await this.service.repository.createQueryBuilder()
.where({ ParentId: parentId })
.orderBy("sort", "ASC")
.getMany();
const treeList: SysMenu[] = new Array<SysMenu>;
for await (let info of result) {
const children = await this.MenuTree(info.Id);
const node: any = {
Id: info.Id,
ParentId: info.ParentId,
Type: info.Type,
Title: info.Title,
Path: info.Path,
Permission: info.Permission,
Component: info.Component,
Icon: info.Icon,
Name: info.Name,
Redirect: info.Redirect,
IsLink: info.IsLink,
IsHide: info.IsHide,
IsKeepAlive: info.IsKeepAlive,
IsAffix: info.IsAffix,
IsIframe: info.IsIframe,
Children: [],
Sort: info.Sort,
};
if (children.length > 0) {
node.Children = children;
}
treeList.push(node);
}
return treeList;
}
//递归获取前端路由Tree
private async RouteTree(parentId: number, RoleId: number): Promise<RouteMenu[]> {
const menuRole = await this._roleService.getMenusByRoleId(RoleId);
const menuIds: number[] = [];
for await (const info of menuRole) {
menuIds.push(info.MenuId);
}
const result = await this.service.repository.createQueryBuilder()
.where({ ParentId: parentId, Id: In(menuIds) })
.orderBy("sort", "ASC")
.getMany();
const treeList: RouteMenu[] = new Array<RouteMenu>;
for await (let info of result) {
const children = await this.RouteTree(info.Id, RoleId);
const node: RouteMenu = {
name: info.Name,
path: info.Path,
component: info.Component,
redirect: info.Redirect,
meta: {
title: info.Title,
icon: info.Icon,
isLink: info.IsLink,
isHide: info.IsHide,
isKeepAlive: info.IsKeepAlive,
isAffix: info.IsAffix,
isIframe: info.IsIframe,
permission: info.Permission,
},
children: [],
};
if (children.length > 0) {
for await (let item of children) {
node.children.push({
name: item.name,
path: item.path,
component: item.component,
redirect: item.redirect,
meta: {
title: item.meta.title,
icon: item.meta.icon,
isLink: item.meta.isLink,
isHide: item.meta.isHide,
isKeepAlive: item.meta.isKeepAlive,
isAffix: item.meta.isAffix,
isIframe: item.meta.isIframe,
permission: item.meta.permission,
},
children: [],
});
}
}
treeList.push(node);
}
return treeList;
}
//#region 基础控制器
@ApiOperation({
summary: '分页查询菜单列表',
description: '分页查询菜单列表'
})
@ApiProperty()
@Get('/getPage')
async getPage(@Query() dto: PageDto) {
const result = await this.service.getPage(dto.Page, dto.PageSize, dto.Keywords, dto.OrderBy);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS,
{
total: result[1],
page: 1,
pageSize: 10,
rows: result[0],
}
);
}
@ApiOperation({
summary: '查询菜单列表',
description: '查询菜单列表'
})
@Get('/getList')
async getList(@Query() dto: ListDto) {
const result = await this.service.getList(dto.Keywords, dto.OrderBy);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '根据ID查询菜单',
description: '根据ID查询菜单'
})
@Get('/getById')
async getById(@Query() dto: IdDto) {
const result = await this.service.getById(dto.Id);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '根据ID数组查询菜单',
description: '根据ID数组查询菜单'
})
@Get('/getByIds')
async getByIds(@Query() dto: IdsDto) {
const result = await this.service.getByIds(dto.Ids);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '新增菜单',
description: '新增菜单'
})
@Post('/add')
async add(@Body() dto: MenuDto) {
const model = plainToClass(SysMenu, dto);
const isRepeat = await this.service.repository.createQueryBuilder()
.orWhere({ Name: Equal(model.Name.trim()), })
.orWhere({ Permission: Equal(model.Permission.trim()) }).getCount();
if (isRepeat > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, "路由名称或者权限标识重复!"
);
} else {
const result = await this.service.save(model);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
}
@ApiOperation({
summary: '修改菜单',
description: '修改菜单'
})
@Post('/edit')
async edit(@Body() dto: MenuDto) {
const model = plainToClass(SysMenu, dto);
const isRepeat = await this.service.repository.createQueryBuilder()
.where({ Id: Not(model.Id) })
.andWhere([{ Name: Equal(model.Name.trim()) }, { Permission: Equal(model.Permission.trim()) }])
.getCount();
if (isRepeat > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, "路由名称或者权限标识重复!"
);
} else {
const result = await this.service.save(model);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
}
@ApiOperation({
summary: '假删除菜单',
description: '假删除菜单'
})
@Post('/del')
async del(@Body() dto: IdDto) {
const result = await this.service.destroy(dto.Id);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '修改菜单状态',
description: '修改菜单状态'
})
@Post('/setStatus')
async setStatus(@Body() dto: IdStatusDto) {
const result = await this.service.repository.createQueryBuilder()
.update()
.set({
Status: dto.Status
})
.where("Id = :Id", { Id: dto.Id })
.execute();
if (result.affected > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
} else {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, result
);
}
}
//#endregion
}

View File

@ -0,0 +1,189 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 17:05:40
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 22:32:29
* @FilePath: \tsplatform\backend\src\Controller\RoleController.ts
* @Description: 系统角色控制器
*/
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { RestfulReturn } from 'src/Expand/RestfulReturn';
import {
ApiBearerAuth,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { RoleService } from 'src/Service/RoleService';
import { IdDto, IdsDto, IdStatusDto, ListDto, PageDto } from 'src/EntiyDto/CommonDto';
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
import { SysRole } from 'src/Entity/SysRole';
import { RoleDto } from 'src/EntiyDto/RoleDto';
import { plainToClass } from 'class-transformer';
import { Not } from 'typeorm';
@ApiBearerAuth()
@ApiTags('系统角色管理')
@Controller('/role')
export class RoleController {
constructor(
public readonly restful: RestfulReturn,
public readonly service: RoleService
) {
}
//#region 基础控制器
@ApiOperation({
summary: '分页查询角色列表',
description: '分页查询角色列表'
})
@Get('/getPage')
async getPage(@Query() dto: PageDto) {
const result = await this.service.getPage(dto.Page, dto.PageSize, dto.Keywords, dto.OrderBy);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS,
{
total: result[1],
page: 1,
pageSize: 10,
rows: result[0],
}
);
}
@ApiOperation({
summary: '查询角色列表',
description: '查询角色列表'
})
@Get('/getList')
async getList(@Query() dto: ListDto) {
const result = await this.service.getList(dto.Keywords, dto.OrderBy);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '根据ID查询角色',
description: '根据ID查询角色'
})
@Get('/getById')
async getById(@Query() dto: IdDto) {
const result = await this.service.getById(dto.Id);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '根据ID数组查询角色',
description: '根据ID数组查询角色'
})
@Get('/getByIds')
async getByIds(@Query() dto: IdsDto) {
const result = await this.service.getByIds(dto.Ids);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '新增角色',
description: '新增角色'
})
@Post('/add')
async add(@Body() dto: RoleDto) {
const model = plainToClass(SysRole, dto);
const isRepeat = await this.service.repository.count({
where: { Name: model.Name.trim() }
});
if (isRepeat > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, "角色名称重复!"
);
} else {
const result = await this.service.save(model);
if (result) {
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
} else {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, result
);
}
}
}
@ApiOperation({
summary: '修改角色',
description: '修改角色'
})
@Post('/edit')
async edit(@Body() dto: RoleDto) {
const model = plainToClass(SysRole, dto);
const isRepeat = await this.service.repository.count({
where: { Id: Not(model.Id), Name: model.Name.trim() }
});
if (isRepeat > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, "角色名称重复!"
);
} else {
const result = await this.service.save(model);
if (result) {
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
} else {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, result
);
}
}
}
@ApiOperation({
summary: '删除角色',
description: '删除角色'
})
@Post('/del')
async del(@Body() dto: IdDto) {
const result = await this.service.destroy(dto.Id);
if (result) {
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
} else {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, result
);
}
}
@ApiOperation({
summary: '修改角色状态',
description: '修改角色状态'
})
@Post('/setStatus')
async setStatus(@Body() dto: IdStatusDto) {
const result = await this.service.repository.createQueryBuilder()
.update()
.set({
Status: dto.Status
})
.where("Id = :Id", { Id: dto.Id })
.execute();
if (result.affected > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
} else {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, result
);
}
}
//#endregion
}

View File

@ -0,0 +1,188 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 17:05:41
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 22:49:42
* @FilePath: \tsplatform\backend\src\Controller\UserController.ts
* @Description: 系统用户控制器
*/
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { RestfulReturn } from 'src/Expand/RestfulReturn';
import {
ApiBearerAuth,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { UserService } from 'src/Service/UserService';
import { SysUser } from 'src/Entity/SysUser';
import { IdDto, IdsDto, IdStatusDto, ListDto, PageDto } from 'src/EntiyDto/CommonDto';
import { RestfulHttpCodeEnum } from 'src/Enum/Global';
import { plainToClass } from 'class-transformer';
import { AppConfig } from 'src/Config/Index';
import { Md5 } from 'ts-md5';
import { UserDto } from 'src/EntiyDto/UserDto';
import { Equal, Not } from 'typeorm';
@ApiBearerAuth()
@ApiTags('系统用户管理')
@Controller('/user')
export class UserController {
constructor(
public readonly restful: RestfulReturn,
public readonly service: UserService
) {
}
//#region 基础控制器
@ApiOperation({
summary: '分页查询用户列表',
description: '分页查询用户列表'
})
@Get('/getPage')
async getPage(@Query() dto: PageDto) {
const result = await this.service.getPage(dto.Page, dto.PageSize, dto.Keywords, dto.OrderBy);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS,
{
total: result[1],
page: 1,
pageSize: 10,
rows: result[0],
}
);
}
@ApiOperation({
summary: '查询用户列表',
description: '查询用户列表'
})
@Get('/getList')
async getList(@Query() dto: ListDto) {
const result = await this.service.getList(dto.Keywords, dto.OrderBy);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '根据ID查询用户',
description: '根据ID查询用户'
})
@Get('/getById')
async getById(@Query() dto: IdDto) {
const result = await this.service.getById(dto.Id);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '根据ID数组查询用户',
description: '根据ID数组查询用户'
})
@Get('/getByIds')
async getByIds(@Query() dto: IdsDto) {
const result = await this.service.getByIds(dto.Ids);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '新增用户',
description: '新增用户'
})
@Post('/add')
async add(@Body() dto: UserDto) {
const model = plainToClass(SysUser, dto);
const isRepeat = await this.service.repository.createQueryBuilder()
.orWhere({ UserName: Equal(model.UserName.trim()), })
.orWhere({ Phone: Equal(model.Phone.trim()) })
.orWhere({ Email: Equal(model.Email.trim()) }).getCount();
if (isRepeat > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, "账号/手机号/邮箱重复!"
);
} else {
//给新用户设置个默认密码888888用户登录后可以自行修改密码
model.Password = Md5.hashStr(Md5.hashStr("888888") + AppConfig.md5Key);
const result = await this.service.save(model);
if (result) {
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
} else {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, result
);
}
}
}
@ApiOperation({
summary: '修改用户',
description: '修改用户'
})
@Post('/edit')
async edit(@Body() dto: UserDto) {
const model = plainToClass(SysUser, dto);
const isRepeat = await this.service.repository.createQueryBuilder()
.where({ Id: Not(model.Id) })
.andWhere([{ UserName: Equal(model.UserName.trim()) }, { Phone: Equal(model.Phone.trim()) }, { Email: Equal(model.Email.trim()) }])
.getCount();
if (isRepeat > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, "账号/手机号/邮箱重复!"
);
} else {
//给新用户设置个默认密码888888用户登录后可以自行修改密码
model.Password = Md5.hashStr(Md5.hashStr("888888") + AppConfig.md5Key);
const result = await this.service.save(model);
if (result) {
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
} else {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, result
);
}
}
}
@ApiOperation({
summary: '假删除用户',
description: '假删除用户'
})
@Post('/del')
async del(@Body() dto: IdDto) {
const result = await this.service.destroy(dto.Id);
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
}
@ApiOperation({
summary: '修改用户状态',
description: '修改用户状态'
})
@Post('/setStatus')
async setStatus(@Body() dto: IdStatusDto) {
const result = await this.service.repository.createQueryBuilder()
.update()
.set({
Status: dto.Status
})
.where("Id = :Id", { Id: dto.Id })
.execute();
if (result.affected > 0) {
return await this.restful.toJson(
RestfulHttpCodeEnum.SUCCESS, result
);
} else {
return await this.restful.toJson(
RestfulHttpCodeEnum.FAIL, result
);
}
}
//#endregion
}

View File

@ -0,0 +1,43 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 11:26:13
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-22 21:57:27
* @FilePath: \tsplatform\backend\src\Entity\SysBase.ts
* @Description: 系统基类实体模型
*/
import { Entity, Column, PrimaryColumn, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, VersionColumn } from 'typeorm';
@Entity()
export class SysBase {
@PrimaryGeneratedColumn()
@PrimaryColumn({ comment: "ID" })
Id: number;
@Column({ comment: "状态", default: 0, nullable: true })
Status: number;
@Column({ comment: "商户/站点ID", default: 0, nullable: true })
MerchantId: number;
//自动为实体插入日期
@CreateDateColumn()
CreateTime: Date;
//每次调用实体管理器或存储库的save时自动更新实体日期
@UpdateDateColumn()
UpdateTime: Date;
@Column({ comment: "操作人ID", default: 0, nullable: true })
OperatorId: number;
@Column({ comment: "修改人ID", default: 0, nullable: true })
UpdateUserId: number;
@Column({ comment: "是否删除", default: false, nullable: true })
IsDelete: boolean;
//每次调用实体管理器或存储库的save时自动增长实体版本
@VersionColumn()
Version: number;
}

View File

@ -0,0 +1,35 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 13:12:19
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-18 14:18:50
* @FilePath: \tsplatform\backend\src\Entity\SysCity.ts
* @Description: 系统城市实体模型
*/
import { Entity, Column, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class SysCity {
@PrimaryGeneratedColumn()
@PrimaryColumn({ comment: "ID" })
Id: number;
@Column({ comment: "地区编号" })
RegionId: string;
@Column({ comment: "地区名称" })
RegionName: string;
@Column({ comment: "地区简称", nullable: true })
RegionShortName: string;
@Column({ comment: "地区行政编号", nullable: true })
RegionCode: string;
@Column({ comment: "地区父级ID" })
RegionParentId: string;
@Column({ comment: "地区级别" })
RegionLevel: number;
}

View File

@ -0,0 +1,19 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 13:10:02
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-22 15:08:00
* @FilePath: \tsplatform\backend\src\Entity\SysDictionaries.ts
* @Description: 系统字典实体模型
*/
import { Entity, Column } from 'typeorm';
import { SysBase } from 'src/Entity/SysBase';
@Entity()
export class SysDictionaries extends SysBase {
@Column({ comment: "字典名称" })
Name: string;
@Column({ comment: "备注", nullable: true })
Remark: string;
}

View File

@ -0,0 +1,61 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 11:21:40
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 21:52:49
* @FilePath: \tsplatform\backend\src\Entity\SysMenu.ts
* @Description: 系统菜单实体模型
*/
import { Entity, Column } from 'typeorm';
import { SysBase } from 'src/Entity/SysBase';
import { MenuEnum } from 'src/Enum/Global';
@Entity()
export class SysMenu extends SysBase {
@Column({ comment: "父级ID", default: 0 })
ParentId: number;
@Column({ comment: "菜单类型", default: 0 })
Type: MenuEnum;
@Column({ comment: "路由名称", unique: true })
Name: string;
@Column({ comment: "路由地址", nullable: true })
Path: string;
@Column({ comment: "组件名称", nullable: true })
Component: string;
@Column({ comment: "重定向路径", nullable: true })
Redirect: string;
@Column({ comment: "菜单名称" })
Title: string;
@Column({ comment: "是否外链,开启外链条件,`1、isLink: 链接地址不为空 2、isIframe:false`", nullable: true })
IsLink: string;
@Column({ comment: "是否隐藏此路由" })
IsHide: boolean;
@Column({ comment: "是否缓存组件状态" })
IsKeepAlive: boolean;
@Column({ comment: "是否固定在 tagsView 栏上" })
IsAffix: boolean;
@Column({ comment: "是否内嵌窗口,开启条件,`1、isIframe:true 2、isLink链接地址不为空`" })
IsIframe: boolean;
@Column({ comment: "路由权限标识", unique: true })
Permission: string;
@Column({ comment: "菜单图标", nullable: true })
Icon: string;
@Column({ comment: "排序", nullable: true, default: 0 })
Sort: number;
Children: SysMenu[];
}

View File

@ -0,0 +1,22 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 13:08:01
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 16:46:20
* @FilePath: \tsplatform\backend\src\Entity\SysRole.ts
* @Description: 系统角色实体模型
*/
import { Entity, Column } from 'typeorm';
import { SysBase } from 'src/Entity/SysBase';
import { SysRoleMenu } from 'src/Entity/SysRoleMenu';
@Entity()
export class SysRole extends SysBase {
@Column({ comment: "角色名称", unique: true })
Name: string;
@Column({ comment: "备注", nullable: true })
Remark: string;
SysRoleMenu: SysRoleMenu[];
}

View File

@ -0,0 +1,18 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 13:54:50
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 17:32:38
* @FilePath: \tsplatform\backend\src\Entity\SysRoleMenu.ts
* @Description: 系统角色菜单映射实体模型
*/
import { Entity, Column, ManyToOne } from 'typeorm';
import { SysBase } from 'src/Entity/SysBase';
@Entity()
export class SysRoleMenu extends SysBase {
@Column({ comment: "菜单Id", default: 0 })
MenuId: number;
@Column({ comment: "角色Id", default: 0 })
RoleId: number;
}

View File

@ -0,0 +1,57 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 13:55:35
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 23:13:45
* @FilePath: \tsplatform\backend\src\Entity\SysUser.ts
* @Description: 系统用户映射实体模型
*/
import { Entity, Column, Index } from 'typeorm';
import { SysBase } from 'src/Entity/SysBase';
import { SexEnum, UserTypeEnum } from 'src/Enum/Global';
@Entity()
export class SysUser extends SysBase {
@Column({ comment: "账号类型", default: 1 })
UserType: UserTypeEnum;
@Column({ comment: "账号", unique: true })
UserName: string;
@Column({ comment: "密码" })
Password: string;
@Column({ comment: "昵称", nullable: true })
NickName: string;
@Column({ comment: "头像", nullable: true })
Avatar: string;
@Column({ comment: "出生日期", nullable: true })
Birthday: string;
@Column({ comment: "性别", nullable: true, default: 0 })
Sex: SexEnum;
@Column({ comment: "邮箱", unique: true })
Email: string;
@Column({ comment: "手机号码", unique: true })
Phone: string;
@Column({ comment: "真实姓名", nullable: true })
RealName: string;
@Column({ comment: "身份证号", nullable: true })
IdCard: string;
@Column({ comment: "个性签名", nullable: true })
Signature: string;
@Column({ comment: "个人简介", nullable: true })
Introduction: string;
@Column({ comment: "备注", nullable: true })
Remark: string;
@Column({ comment: "角色Id", default: 0 })
RoleId: number;
}

View File

@ -0,0 +1,78 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-18 09:02:28
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 00:20:22
* @FilePath: \tsplatform\backend\src\Expand\CommonDto.ts
* @Description: 公共DTO服务
*/
import { Injectable } from "@nestjs/common";
import { ApiProperty } from "@nestjs/swagger";
import { IsArray, IsNotEmpty, IsNumber, IsNumberString } from "class-validator";
//分页查询DTO
@Injectable()
export class PageDto {
@IsNotEmpty()
@IsNumberString()
@ApiProperty({ name: "Page", required: true })
Page: number
@IsNotEmpty()
@IsNumberString()
@ApiProperty({ name: "PageSize", required: true })
PageSize: number
@ApiProperty({ name: "Keywords", required: false })
Keywords: string
@ApiProperty({ name: "OrderBy", required: false })
OrderBy: string
}
//列表查询DTO
@Injectable()
export class ListDto {
@ApiProperty({ name: "Keywords", required: false })
Keywords: string
@ApiProperty({ name: "OrderBy", required: false })
OrderBy: string
}
//ids DTO
@Injectable()
export class IdsDto {
@IsNotEmpty()
@IsArray()
@ApiProperty({ name: "Ids", required: true })
Ids: number[]
}
//id DTO
@Injectable()
export class IdDto {
@IsNotEmpty()
@IsNumber()
@ApiProperty({ name: "Id", required: true })
Id: number
}
@Injectable()
export class RoleIdDto {
@ApiProperty({ name: "RoleId" })
RoleId: number;
}
//Id Status DTO
@Injectable()
export class IdStatusDto {
@IsNotEmpty()
@IsNumber()
@ApiProperty({ name: "Id", required: true })
Id: number
@IsNotEmpty()
@IsNumber()
@ApiProperty({ name: "Status", required: true })
Status: number
}

View File

@ -0,0 +1,21 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-18 17:35:08
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 12:12:42
* @FilePath: \tsplatform\backend\src\EntiyDto\Login.ts
* @Description: DTO类
*/
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty, IsString } from "class-validator";
export class LoginDto {
@IsNotEmpty()
@IsString()
@ApiProperty({ name: "UserName", required: true })
UserName: string;
@IsNotEmpty()
@IsString()
@ApiProperty({ name: "Password", required: true })
Password: string;
}

View File

@ -0,0 +1,70 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-18 17:35:08
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 00:14:13
* @FilePath: \tsplatform\backend\src\EntiyDto\MenuDto.ts
* @Description: SysMenu DTO类
*/
import { ApiProperty } from "@nestjs/swagger";
import { IsBoolean, IsNotEmpty, IsNumber, IsString } from "class-validator";
import { MenuEnum } from "src/Enum/Global";
export class MenuDto {
@ApiProperty({ name: "ParentId" })
ParentId: number;
@IsNotEmpty()
@IsNumber()
@ApiProperty({ name: "Type", required: true })
Type: MenuEnum;
@ApiProperty({ name: "Name", required: false })
Name: string;
@ApiProperty({ name: "Path", required: false })
Path: string;
@ApiProperty({ name: "Component", required: false })
Component: string;
@ApiProperty({ name: "Redirect", required: false })
Redirect: string;
@IsNotEmpty()
@IsString()
@ApiProperty({ name: "Title", required: true })
Title: string;
@ApiProperty({ name: "IsLink", required: false })
IsLink: string;
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ name: "IsHide", required: true })
IsHide: boolean;
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ name: "IsKeepAlive", required: true })
IsKeepAlive: boolean;
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ name: "IsAffix", required: true })
IsAffix: boolean;
@IsNotEmpty()
@IsBoolean()
@ApiProperty({ name: "IsIframe", required: true })
IsIframe: boolean;
@IsNotEmpty()
@IsString()
@ApiProperty({ name: "Permission", required: true })
Permission: string;
@ApiProperty({ name: "Icon", required: false })
Icon: string;
}

View File

@ -0,0 +1,25 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-22 16:16:08
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 12:12:56
* @FilePath: \backend\src\EntiyDto\RoleDto.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { ApiProperty } from "@nestjs/swagger";
import { IsArray, IsNotEmpty } from "class-validator";
export class RoleDto {
@IsNotEmpty()
@ApiProperty({ name: "Name", required: true })
Name: string;
@ApiProperty({ name: "Remark" })
Remark: string;
@IsNotEmpty()
@IsArray()
@ApiProperty({ name: "SysRoleMenu", required: true })
SysRoleMenu: number[];
}

View File

@ -0,0 +1,26 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-18 17:35:08
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 12:13:13
* @FilePath: \tsplatform\backend\src\EntiyDto\RouteMenu.ts
* @Description: RouteMenu类 菜单树查询不需要dto映射
*/
export class RouteMenu {
name: string;
path: string;
component: string;
redirect: string;
meta: Meta;
children: RouteMenu[];
}
export class Meta {
title: string;
isLink: string;
isHide: boolean;
isKeepAlive: boolean;
isAffix: boolean;
isIframe: boolean;
permission: string;
icon: string;
}

View File

@ -0,0 +1,65 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-22 16:16:19
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 21:32:25
* @FilePath: \backend\src\EntiyDto\UserDto.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty, IsNumber, IsNumberString } from "class-validator";
export class UserDto {
@IsNotEmpty()
@IsNumber()
@ApiProperty({ name: "UserType", required: true })
UserType: number;
@IsNotEmpty()
@ApiProperty({ name: "Name", required: true })
UserName: string;
@ApiProperty({ name: "Password" })
Password: string;
@ApiProperty({ name: "NickName" })
NickName: string;
@ApiProperty({ name: "Avatar" })
Avatar: string;
@ApiProperty({ name: "Birthday" })
Birthday: string;
@IsNotEmpty()
@IsNumber()
@ApiProperty({ name: "Sex" })
Sex: number;
@ApiProperty({ name: "Email", required: true })
Email: string;
@IsNotEmpty()
@IsNumberString()
@ApiProperty({ name: "Phone", required: true })
Phone: string;
@ApiProperty({ name: "RealName" })
RealName: string;
@ApiProperty({ name: "IdCard" })
IdCard: string;
@ApiProperty({ name: "Signature" })
Signature: string;
@ApiProperty({ name: "Introduction" })
Introduction: string;
@ApiProperty({ name: "Remark" })
Remark: string;
@ApiProperty({ name: "RoleId", required: true })
RoleId: number;
}

View File

@ -0,0 +1,28 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 13:25:43
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-18 09:15:04
* @FilePath: \tsplatform\backend\src\Enum\Global.ts
* @Description: 全局枚举类
*/
export enum RestfulHttpCodeEnum {
SUCCESS = 200,
FAIL = 300,
NODATA = 201,
}
export enum UserTypeEnum {
Admin = 0,
None = 1,
SuperAdmin = 999,
}
export enum SexEnum {
Man = 0,
WoMan = 1
}
export enum MenuEnum {
Directory = 0,
Menu = 1,
Btn = 2
}

View File

@ -0,0 +1,24 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 09:19:20
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-18 09:10:15
* @FilePath: \tsplatform\backend\src\Expand\RestfulReturn.ts
* @Description: RestfulAPI全局响应结构
*/
import { Injectable } from "@nestjs/common";
import { RestfulHttpCodeEnum } from "src/Enum/Global";
@Injectable()
export class RestfulReturn {
//HttpStatus=200时的状态码规则
toJson(code: RestfulHttpCodeEnum, data: any) {
return {
code: code,
msg: code === RestfulHttpCodeEnum.SUCCESS ? "操作成功!" :
code === RestfulHttpCodeEnum.FAIL ? "操作失败!" :
code === RestfulHttpCodeEnum.NODATA ? "数据为空!" : "未知错误!",
data: data,
timespan: new Date().getTime()
}
}
}

View File

@ -0,0 +1,15 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-18 09:02:28
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 10:13:53
* @FilePath: \tsplatform\backend\src\Logging\LoggerPrint.ts
* @Description: 全局日志打印服务
*/
import { Injectable, Logger } from "@nestjs/common";
@Injectable()
export class LoggerPrint {
constructor(private readonly logger: Logger) {
}
}

View File

@ -0,0 +1,33 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-21 11:57:30
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-22 10:41:09
* @FilePath: \tsplatform\backend\src\Middleware\JwtHandle.ts
* @Description: 授权中间件
*/
import { HttpException, HttpStatus, Injectable, NestMiddleware } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { NextFunction } from 'express';
import { AppConfig } from 'src/Config/Index';
@Injectable()
export class JwtHandle implements NestMiddleware {
constructor(
public readonly _jwt: JwtService
) { }
async use(req: Request, res: Response, next: NextFunction) {
try {
const token = req.headers["authorization"];
if (!token) {
throw new HttpException("Token指令牌不存在", HttpStatus.UNAUTHORIZED);
} else {
await this._jwt.verifyAsync(token, { secret: AppConfig.jwt.secret, ignoreExpiration: false })
next();
}
}
catch (ex) {
throw new HttpException(ex.message, HttpStatus.UNAUTHORIZED);
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,216 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-24 09:22:50
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 09:58:45
* @FilePath: \backend\src\SeedData\Menu.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
export const menus = [{
"Id": 8,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-23T07:53:21.453Z",
"UpdateTime": "2022-11-24T01:29:41.000Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 5,
"ParentId": 2,
"Type": 1,
"Name": "system.city",
"Path": "/system/city",
"Component": "/system/city/index",
"Redirect": "",
"Title": "message.router.system.city",
"IsLink": "",
"IsHide": false,
"IsKeepAlive": true,
"IsAffix": false,
"IsIframe": false,
"Permission": "system.city",
"Icon": "ele-OfficeBuilding",
"Sort": 101
},
{
"Id": 7,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-23T07:50:37.192Z",
"UpdateTime": "2022-11-24T01:21:19.762Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 4,
"ParentId": 2,
"Type": 1,
"Name": "system.dic",
"Path": "/system/dic",
"Component": "/system/dic/index",
"Redirect": "",
"Title": "message.router.system.dic",
"IsLink": "",
"IsHide": false,
"IsKeepAlive": true,
"IsAffix": false,
"IsIframe": false,
"Permission": "system.dic",
"Icon": "iconfont icon-zhongyingwen1",
"Sort": 100
},
{
"Id": 6,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-22T13:59:37.445Z",
"UpdateTime": "2022-11-23T05:56:53.726Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 0,
"ParentId": 0,
"Type": 0,
"Name": "personal",
"Path": "/personal",
"Component": "personal/index",
"Redirect": null,
"Title": "message.router.personal",
"IsLink": "",
"IsHide": true,
"IsKeepAlive": false,
"IsAffix": false,
"IsIframe": false,
"Permission": "personal",
"Icon": "iconfont icon-gerenzhongxin",
"Sort": 6
},
{
"Id": 5,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-22T13:59:37.445Z",
"UpdateTime": "2022-11-23T08:05:06.779Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 0,
"ParentId": 2,
"Type": 1,
"Name": "system.role",
"Path": "/system/role",
"Component": "system/role/index",
"Redirect": null,
"Title": "message.router.system.role",
"IsLink": "",
"IsHide": false,
"IsKeepAlive": true,
"IsAffix": false,
"IsIframe": false,
"Permission": "system.role",
"Icon": "fa fa-street-view",
"Sort": 5
},
{
"Id": 4,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-22T13:59:37.445Z",
"UpdateTime": "2022-11-23T08:05:03.689Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 0,
"ParentId": 2,
"Type": 1,
"Name": "system.user",
"Path": "/system/user",
"Component": "system/user/index",
"Redirect": null,
"Title": "message.router.system.user",
"IsLink": null,
"IsHide": false,
"IsKeepAlive": true,
"IsAffix": false,
"IsIframe": false,
"Permission": "system.user",
"Icon": "iconfont icon-icon-",
"Sort": 4
},
{
"Id": 3,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-22T13:59:37.445Z",
"UpdateTime": "2022-11-23T08:05:00.650Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 0,
"ParentId": 2,
"Type": 1,
"Name": "system.menu",
"Path": "/system/menu",
"Component": "system/menu/index",
"Redirect": null,
"Title": "message.router.system.menu",
"IsLink": "",
"IsHide": false,
"IsKeepAlive": true,
"IsAffix": false,
"IsIframe": false,
"Permission": "system.menu",
"Icon": "iconfont icon-caidan",
"Sort": 3
},
{
"Id": 2,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-22T13:59:37.445Z",
"UpdateTime": "2022-11-23T08:05:16.188Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 0,
"ParentId": 0,
"Type": 0,
"Name": "system",
"Path": "/system",
"Component": "layout/routerView/parent",
"Redirect": "/system/menu",
"Title": "message.router.system.system",
"IsLink": "",
"IsHide": false,
"IsKeepAlive": true,
"IsAffix": false,
"IsIframe": false,
"Permission": "system",
"Icon": "iconfont icon-xitongshezhi",
"Sort": 2
},
{
"Id": 1,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-22T13:59:37.445Z",
"UpdateTime": "2022-11-22T13:59:37.471Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 0,
"ParentId": 0,
"Type": 0,
"Name": "home",
"Path": "/home",
"Component": "home/index",
"Redirect": "",
"Title": "message.router.home",
"IsLink": "",
"IsHide": false,
"IsKeepAlive": true,
"IsAffix": true,
"IsIframe": false,
"Permission": "home",
"Icon": "iconfont icon-shouye",
"Sort": 1
}];

View File

@ -0,0 +1,179 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-24 09:23:07
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 10:04:17
* @FilePath: \backend\src\SeedData\Role.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
export const roles = [
{
"Id": 1,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-22T13:59:37.567Z",
"UpdateTime": "2022-11-24T01:35:03.000Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 4,
"Name": "超级管理员",
"Remark": null
},
{
"Id": 2,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-22T13:59:37.567Z",
"UpdateTime": "2022-11-24T01:36:45.000Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 3,
"Name": "普通用户",
"Remark": null
}
];
export const roleMenus = [{
"Id": 1,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:35:03.437Z",
"UpdateTime": "2022-11-24T01:35:03.437Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 1,
"RoleId": 1
},
{
"Id": 2,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:35:03.437Z",
"UpdateTime": "2022-11-24T01:35:03.437Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 2,
"RoleId": 1
},
{
"Id": 3,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:35:03.437Z",
"UpdateTime": "2022-11-24T01:35:03.437Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 3,
"RoleId": 1
},
{
"Id": 4,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:35:03.437Z",
"UpdateTime": "2022-11-24T01:35:03.437Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 4,
"RoleId": 1
},
{
"Id": 5,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:35:03.437Z",
"UpdateTime": "2022-11-24T01:35:03.437Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 5,
"RoleId": 1
},
{
"Id": 6,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:35:03.437Z",
"UpdateTime": "2022-11-24T01:35:03.437Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 6,
"RoleId": 1
},
{
"Id": 7,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:35:03.437Z",
"UpdateTime": "2022-11-24T01:35:03.437Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 7,
"RoleId": 1
},
{
"Id": 8,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:35:03.437Z",
"UpdateTime": "2022-11-24T01:35:03.437Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 8,
"RoleId": 1
},
{
"Id": 9,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:36:45.486Z",
"UpdateTime": "2022-11-24T01:36:45.486Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 1,
"RoleId": 2
},
{
"Id": 10,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:36:45.486Z",
"UpdateTime": "2022-11-24T01:36:45.486Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 2,
"RoleId": 2
},
{
"Id": 11,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-24T01:36:45.486Z",
"UpdateTime": "2022-11-24T01:36:45.486Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 1,
"MenuId": 4,
"RoleId": 2
}];

View File

@ -0,0 +1,60 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-24 09:22:58
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 10:04:55
* @FilePath: \backend\src\SeedData\User.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
export const users = [{
"Id": 1,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-22T13:59:37.496Z",
"UpdateTime": "2022-11-24T01:21:53.423Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 0,
"UserType": 999,
"UserName": "admin",
"Password": "1a23c5066d05474304c03342e57850d9",
"NickName": "甜蜜蜜",
"Avatar": "https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500",
"Birthday": null,
"Sex": 0,
"Email": "490912587@qq.com",
"Phone": "13000000000",
"RealName": null,
"IdCard": null,
"Signature": null,
"Introduction": null,
"Remark": null,
"RoleId": 1
},
{
"Id": 2,
"Status": 0,
"MerchantId": 0,
"CreateTime": "2022-11-23T08:45:37.321Z",
"UpdateTime": "2022-11-24T01:39:33.000Z",
"OperatorId": 0,
"UpdateUserId": 0,
"IsDelete": false,
"Version": 4,
"UserType": 0,
"UserName": "test",
"Password": "7b0ea1aa91470cd870111415a94b475b",
"NickName": "甜蜜蜜的小号",
"Avatar": "https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500",
"Birthday": null,
"Sex": 1,
"Email": "1844045442@qq.com",
"Phone": "15000000000",
"RealName": null,
"IdCard": null,
"Signature": null,
"Introduction": null,
"Remark": null,
"RoleId": 2
}];

View File

@ -0,0 +1,94 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 17:08:52
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 11:12:25
* @FilePath: \tsplatform\backend\src\Service\UserService.ts
* @Description: 省市区服务
*/
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DeleteResult, In, InsertResult, Like, Repository } from 'typeorm';
import { LoggerPrint } from 'src/Logging/LoggerPrint';
import { SysCity } from 'src/Entity/SysCity';
import { citys } from 'src/SeedData/City';
@Injectable()
export class CityService {
private readonly tableName: string = "省市区";
constructor(
public readonly loggerPrint: LoggerPrint,
public readonly logger: Logger,
@InjectRepository(SysCity)
public readonly repository: Repository<SysCity>
) {
}
//#region 基础控制器
// 初始化数据
async seedData(): Promise<number> {
let result: InsertResult;
await this.repository.queryRunner?.startTransaction();
try {
await this.repository.clear();
result = await this.repository.createQueryBuilder().insert().values(citys).execute();
await this.repository.queryRunner?.commitTransaction();
await this.repository.createQueryBuilder().useTransaction(true);
return result.identifiers.length;
} catch (err) {
//如果遇到错误,可以回滚事务
await this.repository.queryRunner?.rollbackTransaction();
this.logger.warn(err);
return 0;
}
}
async getPage(
page: number = 1,
pageSize: number = 10,
keywords: string = "",
orderBy: string = ""
): Promise<[SysCity[], number]> {
const result = await this.repository.findAndCount({
where: [{ RegionName: Like("%" + keywords + "%") }, { RegionShortName: Like("%" + keywords + "%") }],
order: {
Id: "ASC"
},
skip: page - 1,
take: pageSize
})
return result;
}
async getList(
keywords: string = "",
orderBy: string = "",
): Promise<SysCity[]> {
const result = await this.repository.find({
where: [{ RegionName: Like("%" + keywords + "%") }, { RegionShortName: Like("%" + keywords + "%") }],
order: {
Id: "ASC"
},
})
return result;
}
async getById(id: number): Promise<SysCity> {
const result = await this.repository.findOne({ where: { Id: id } });
return result;
}
async getByIds(ids: number[]): Promise<SysCity[]> {
const result = await this.repository.find({
where: { Id: In(ids) },
})
return result;
}
async save(model: SysCity): Promise<SysCity> {
const result = await this.repository.save(model);
return result;
}
async destroy(id: number): Promise<DeleteResult> {
const result = await this.repository.delete(id);
return result;
}
//#endregion
}

View File

@ -0,0 +1,94 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 17:08:52
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditSysMenuime: 2022-11-18 11:31:43
* @FilePath: \tsplatform\backend\src\Service\MenuService.ts
* @Description: 系统菜单服务
*/
import { Injectable, Logger } from '@nestjs/common';
import { SysMenu } from 'src/Entity/SysMenu';
import { InjectRepository } from '@nestjs/typeorm';
import { DeleteResult, In, InsertResult, Like, Repository } from 'typeorm';
import { LoggerPrint } from 'src/Logging/LoggerPrint';
import { menus } from 'src/SeedData/Menu';
@Injectable()
export class MenuService {
private readonly tableName: string = "系统菜单";
constructor(
public readonly loggerPrint: LoggerPrint,
public readonly logger: Logger,
@InjectRepository(SysMenu)
public readonly repository: Repository<SysMenu>
) {
}
//#region 基础控制器
// 初始化数据
async seedData(): Promise<number> {
let result: InsertResult;
await this.repository.queryRunner?.startTransaction();
try {
await this.repository.clear();
result = await this.repository.createQueryBuilder().insert().values(menus).execute();
await this.repository.queryRunner?.commitTransaction();
await this.repository.createQueryBuilder().useTransaction(true);
return result.identifiers.length;
} catch (err) {
//如果遇到错误,可以回滚事务
await this.repository.queryRunner?.rollbackTransaction();
this.logger.warn(err);
return 0;
}
}
async getPage(
page: number = 1,
pageSize: number = 10,
keywords: string = "",
orderBy: string = ""
): Promise<[SysMenu[], number]> {
const result = await this.repository.findAndCount({
where: [{ Title: Like("%" + keywords + "%") }, { Name: Like("%" + keywords + "%") }],
order: {
Id: "DESC"
},
skip: page - 1,
take: pageSize
})
return result;
}
async getList(
keywords: string = "",
orderBy: string = ""
): Promise<SysMenu[]> {
const result = await this.repository.find({
where: [{ Name: Like("%" + keywords + "%") }],
order: {
Id: "DESC"
},
})
return result;
}
async getById(id: number): Promise<SysMenu> {
const result = await this.repository.findOne({ where: { Id: id } });
return result;
}
async getByIds(ids: number[]): Promise<SysMenu[]> {
const result = await this.repository.find({
where: { Id: In(ids) },
})
return result;
}
async save(model: SysMenu): Promise<SysMenu> {
const result = await this.repository.save(model);
return result;
}
async destroy(id: number): Promise<DeleteResult> {
const result = await this.repository.delete(id);
return result;
}
//#endregion
}

View File

@ -0,0 +1,156 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 17:08:52
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 10:15:40
* @FilePath: \tsplatform\backend\src\Service\RoleService.ts
* @Description: 系统角色服务
*/
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DeleteResult, In, InsertResult, Like, Repository } from 'typeorm';
import { SysRole } from 'src/Entity/SysRole';
import { LoggerPrint } from 'src/Logging/LoggerPrint';
import { SysRoleMenu } from 'src/Entity/SysRoleMenu';
import { roleMenus, roles } from 'src/SeedData/Role';
// where: { Name: keywords },//and查询
// where: [{ Name: keywords }],//or查询
// where: { Name: Like("%out #%") },//like查询
// where: { Id: In([0,1]) },//in查询
// where: { Name: Like("%out #%") },//like查询
@Injectable()
export class RoleService {
private readonly tableName: string = "系统角色";
constructor(
public readonly loggerPrint: LoggerPrint,
public readonly logger: Logger,
@InjectRepository(SysRole)
public readonly repository: Repository<SysRole>,
@InjectRepository(SysRoleMenu)
public readonly roleMenuRepository: Repository<SysRoleMenu>,
) {
}
//根据角色Id获得路由菜单
async getMenusByRoleId(id: number): Promise<SysRoleMenu[]> {
//根据角色Id获得角色菜单映射数据
const result = await this.roleMenuRepository.find({
where: { RoleId: id }
});
return result;
}
//#region 基础控制器
// 初始化数据
async seedData(): Promise<number[]> {
let result: InsertResult;
let result1: InsertResult;
await this.repository.queryRunner?.startTransaction();
try {
await this.repository.clear();
result = await this.repository.createQueryBuilder().insert().values(roles).execute();
result1 = await this.roleMenuRepository.createQueryBuilder().insert().values(roleMenus).execute();
await this.repository.queryRunner?.commitTransaction();
await this.repository.createQueryBuilder().useTransaction(true);
return [result.identifiers.length, result1.identifiers.length]
} catch (err) {
//如果遇到错误,可以回滚事务
await this.repository.queryRunner?.rollbackTransaction();
this.logger.warn(err);
return [0, 0];
}
}
async getPage(
page: number = 1,
pageSize: number = 10,
keywords: string = "",
orderBy: string = ""
): Promise<[SysRole[], number]> {
const result = await this.repository.findAndCount({
where: [{ Name: Like("%" + keywords + "%") }],
order: {
Id: "ASC"
},
skip: page - 1,
take: pageSize
})
for await (let item of result[0]) {
const roleMenuResult = await this.roleMenuRepository.find({
where: { RoleId: item.Id },
})
item.SysRoleMenu = roleMenuResult;
}
return result;
}
async getList(
keywords: string = "",
orderBy: string = ""
): Promise<SysRole[]> {
const result = await this.repository.find({
where: [{ Name: Like("%" + keywords + "%") }],
order: {
Id: "ASC"
},
})
for await (let item of result) {
const roleMenuResult = await this.roleMenuRepository.find({
where: { RoleId: item.Id },
})
item.SysRoleMenu = roleMenuResult;
}
return result;
}
async getById(id: number): Promise<SysRole> {
const result = await this.repository.findOne({ where: { Id: id } });
return result;
}
async getByIds(ids: number[]): Promise<SysRole[]> {
const result = await this.repository.find({
where: { Id: In(ids) }
})
return result;
}
async save(model: SysRole): Promise<boolean> {
await this.repository.queryRunner?.startTransaction();
try {
//如果是编辑先删除主子表后,修改主表,新增子表
if (model.Id > 0) {
await this.repository.createQueryBuilder().update().set({ Name: model.Name, Remark: model.Remark, Status: model.Status }).where("Id = :Id", { Id: model.Id }).execute();
await this.roleMenuRepository.delete({ RoleId: model.Id });
for await (let item of model.SysRoleMenu) {
item.RoleId = model.Id
}
} else {
const result = await this.repository.createQueryBuilder().insert().values(model).execute();
for await (let item of model.SysRoleMenu) {
item.RoleId = result.identifiers[0].Id
}
}
await this.roleMenuRepository.createQueryBuilder().insert().values(model.SysRoleMenu).execute();
await this.repository.queryRunner?.commitTransaction();
await this.repository.createQueryBuilder().useTransaction(true);
return true;
} catch (err) {
await this.repository.queryRunner?.rollbackTransaction();
this.logger.warn(err);
return false;
}
}
async destroy(id: number): Promise<boolean> {
await this.repository.queryRunner?.startTransaction();
try {
//删除主子表
await this.repository.delete({ Id: id });
await this.roleMenuRepository.delete({ RoleId: id });
await this.repository.queryRunner?.commitTransaction();
await this.repository.createQueryBuilder().useTransaction(true);
return true;
} catch (err) {
await this.repository.queryRunner?.rollbackTransaction();
this.logger.warn(err);
return false;
}
}
//#endregion
}

View File

@ -0,0 +1,93 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 17:08:52
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 10:15:57
* @FilePath: \tsplatform\backend\src\Service\UserService.ts
* @Description: 系统用户服务
*/
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DeleteResult, In, InsertResult, Like, Repository } from 'typeorm';
import { LoggerPrint } from 'src/Logging/LoggerPrint';
import { SysUser } from 'src/Entity/SysUser';
import { users } from 'src/SeedData/User';
@Injectable()
export class UserService {
private readonly tableName: string = "系统用户";
constructor(
public readonly loggerPrint: LoggerPrint,
public readonly logger: Logger,
@InjectRepository(SysUser)
public readonly repository: Repository<SysUser>
) {
}
//#region 基础控制器
// 初始化数据
async seedData(): Promise<number> {
let result: InsertResult;
await this.repository.queryRunner?.startTransaction();
try {
await this.repository.clear();
result = await this.repository.createQueryBuilder().insert().values(users).execute();
await this.repository.queryRunner?.commitTransaction();
await this.repository.createQueryBuilder().useTransaction(true);
return result.identifiers.length;
} catch (err) {
//如果遇到错误,可以回滚事务
await this.repository.queryRunner?.rollbackTransaction();
this.logger.warn(err);
return 0;
}
}
async getPage(
page: number = 1,
pageSize: number = 10,
keywords: string = "",
orderBy: string = ""
): Promise<[SysUser[], number]> {
const result = await this.repository.findAndCount({
where: [{ Phone: Like("%" + keywords + "%") }, { UserName: Like("%" + keywords + "%") }, { NickName: Like("%" + keywords + "%") }],
order: {
Id: "ASC"
},
skip: page - 1,
take: pageSize
})
return result;
}
async getList(
keywords: string = "",
orderBy: string = ""
): Promise<SysUser[]> {
const result = await this.repository.find({
where: [{ Phone: Like("%" + keywords + "%") }, { UserName: Like("%" + keywords + "%") }, { NickName: Like("%" + keywords + "%") }],
order: {
Id: "ASC"
},
})
return result;
}
async getById(id: number): Promise<SysUser> {
const result = await this.repository.findOne({ where: { Id: id } });
return result;
}
async getByIds(ids: number[]): Promise<SysUser[]> {
const result = await this.repository.find({
where: { Id: In(ids) },
})
return result;
}
async save(model: SysUser): Promise<SysUser> {
const result = await this.repository.save(model);
return result;
}
async destroy(id: number): Promise<DeleteResult> {
const result = await this.repository.delete(id);
return result;
}
//#endregion
}

View File

@ -0,0 +1,50 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 11:44:48
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 17:51:54
* @FilePath: \tsplatform\backend\src\Utils\LoadModules.ts
* @Description: 全局加载模块
*/
import { Logger } from '@nestjs/common';
import { RestfulReturn } from 'src/Expand/RestfulReturn';
import { LoggerPrint } from 'src/Logging/LoggerPrint';
import { JwtService } from '@nestjs/jwt';
import { SysMenu } from 'src/Entity/SysMenu';
import { SysCity } from 'src/Entity/SysCity';
import { SysUser } from 'src/Entity/SysUser';
import { SysRole } from 'src/Entity/SysRole';
import { SysRoleMenu } from 'src/Entity/SysRoleMenu';
import { IndexController } from 'src/Controller/IndexController';
import { MenuController } from 'src/Controller/MenuController';
import { AuthController } from 'src/Controller/AuthController';
import { UserController } from 'src/Controller/UserController';
import { RoleController } from 'src/Controller/RoleController';
import { CityController } from 'src/Controller/CityController';
import { MenuService } from 'src/Service/MenuService';
import { UserService } from 'src/Service/UserService';
import { RoleService } from 'src/Service/RoleService';
import { Repository } from 'typeorm';
import { CityService } from 'src/Service/CityService';
//自动导入Entity
export function LoadPlatFormEntity() {
return [SysCity, SysMenu, SysUser, SysRole, SysRoleMenu];
}
//自动导入Controller
export function LoadController() {
return [IndexController, CityController, MenuController, UserController, AuthController, RoleController,];
}
//自动导入Service服务
export function LoadService() {
return [
//特殊服务提供者
RestfulReturn, Logger, LoggerPrint, Repository,JwtService,
//Service服务提供者
MenuService, UserService, RoleService, CityService
];
}

61
backend/src/app.module.ts Normal file
View File

@ -0,0 +1,61 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 09:19:20
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 10:16:52
* @FilePath: \tsplatform\backend\src\app.module.ts
* @Description: 全局模块入口
*/
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LoadController, LoadPlatFormEntity, LoadService } from 'src/Utils/LoadModules';
import { AppConfig, DataBaseConfig } from 'src/Config/Index';
import { JwtHandle } from 'src/Middleware/JwtHandle';
@Module({
imports: [
TypeOrmModule.forFeature(LoadPlatFormEntity()),
TypeOrmModule.forRootAsync({
useFactory: () => ({
debug: false,
//重试连接数据库的次数
retryAttempts: 10,
//两次重试连接的间隔(ms)
retryDelay: 3000,
//自动加载实体
autoLoadEntities: true,
type: 'mysql',
host: DataBaseConfig.DataBase.host,
port: DataBaseConfig.DataBase.port,
username: DataBaseConfig.DataBase.username,
password: DataBaseConfig.DataBase.password,
database: DataBaseConfig.DataBase.database,
entities: LoadPlatFormEntity(),
logging: AppConfig.debug,
synchronize: AppConfig.debug,
})
}),
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env', '.env.development', '.env.production'],
}),
],
controllers: LoadController(),
providers: LoadService()
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(JwtHandle)
.exclude(
{ path: 'auth/login', method: RequestMethod.POST },
{ path: 'auth/getUserInfo', method: RequestMethod.GET },
{ path: '/seed', method: RequestMethod.GET },
)
.forRoutes("*");
}
}

33
backend/src/main.ts Normal file
View File

@ -0,0 +1,33 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 09:19:20
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 11:17:23
* @FilePath: \tsplatform\backend\src\main.ts
* @Description: 主程序入口
*/
import { NestFactory } from '@nestjs/core';
import { AppModule } from 'src/app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppConfig } from 'src/Config/Index';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, { logger: ['error', 'warn', "log", "debug", "verbose"], cors: true });
//全局Dto参数校验管道
app.useGlobalPipes(new ValidationPipe());
const config = new DocumentBuilder()
//基础文档里添加安全定义
.addBearerAuth()
.setTitle('甜蜜蜜TS开发总平台')
.setDescription('甜蜜蜜TS开发总平台')
.setContact("甜蜜蜜", "https://gitee.com/tmm-top/vue-next-admin-ts", "490912587@qq.com")
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('/api', app, document);
await app.listen(AppConfig.port, '0.0.0.0');
}
bootstrap();

View File

@ -0,0 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

View File

@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"],
}

21
backend/tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2022",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}

9
doc/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -13,18 +13,9 @@ module.exports = {
},
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
plugins: ['vue', '@typescript-eslint'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.vue'],
rules: {
'no-undef': 'off',
},
},
],
rules: {
// http://eslint.cn/docs/rules/
// https://eslint.vuejs.org/rules/
// https://typescript-eslint.io/rules/no-unused-vars/
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
@ -35,9 +26,6 @@ module.exports = {
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'@typescript-eslint/no-unused-vars': [2],
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
@ -71,6 +59,5 @@ module.exports = {
'no-v-model-argument': 'off',
'no-case-declarations': 'off',
'no-console': 'error',
'no-redeclare': 'off',
},
};

25
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
.DS_Store
node_modules
/dist
# local env files
.env
.env.development
.env.production
*.lock
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
package-lock.json
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -27,5 +27,6 @@
})();
</script>
<script type="module" src="/src/main.ts"></script>
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script>
</body>
</html>

82
frontend/package.json Normal file
View File

@ -0,0 +1,82 @@
{
"name": "tsplatform",
"version": "1.1.0",
"description": "vue3 vite next admin template",
"author": "SweetHoney",
"license": "MIT",
"scripts": {
"dev": "vite --force",
"build": "vite build",
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@wangeditor/editor-for-vue": "^5.1.11",
"axios": "^1.1.3",
"countup.js": "^2.3.2",
"cropperjs": "^1.5.12",
"echarts": "^5.4.0",
"echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.0.0",
"element-plus": "^2.2.21",
"js-cookie": "^3.0.1",
"jsplumb": "^2.15.6",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"pinia": "^2.0.23",
"print-js": "^1.6.0",
"qrcodejs2-fixes": "^0.0.2",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.0",
"splitpanes": "^3.1.5",
"ts-md5": "^1.3.1",
"vue": "^3.2.45",
"vue-clipboard3": "^2.0.0",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@types/node": "^18.11.9",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.15.0",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0",
"@vitejs/plugin-vue": "^3.2.0",
"@vue/compiler-sfc": "^3.2.45",
"dotenv": "^16.0.3",
"eslint": "^8.27.0",
"eslint-plugin-vue": "^9.7.0",
"prettier": "^2.7.1",
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"typescript": "^4.9.3",
"vite": "^3.2.4",
"vue-eslint-parser": "^9.1.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
],
"bugs": {
"url": "https://gitee.com/lyt-top/vue-next-admin/issues"
},
"engines": {
"node": ">=12.0.0",
"npm": ">= 6.0.0"
},
"keywords": [
"vue",
"vue3",
"vuejs/vue-next",
"element-ui",
"element-plus",
"vue-next-admin",
"next-admin"
],
"repository": {
"type": "git",
"url": "https://gitee.com/lyt-top/vue-next-admin.git"
}
}

4
frontend/plugins.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module 'vue-grid-layout';
declare module 'qrcodejs2-fixes';
declare module 'splitpanes';
declare module 'js-cookie';

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

13
frontend/shim.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
/* eslint-disable */
// 声明文件,*.vue 后缀的文件交给 vue 模块来处理
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
// 声明文件,定义全局变量。其它 app.config.globalProperties.xxx使用 getCurrentInstance() 来获取
interface Window {
nextLoading: boolean;
}

6
frontend/source.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare module '*.json';
declare module '*.png';
declare module '*.jpg';
declare module '*.scss';
declare module '*.ts';
declare module '*.js';

107
frontend/src/App.vue Normal file
View File

@ -0,0 +1,107 @@
<!--
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 09:19:20
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 17:25:54
* @FilePath: \frontend\src\App.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<router-view v-show="themeConfig.lockScreenTime > 1" />
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
<CloseFull v-if="!themeConfig.isLockScreen" />
</el-config-provider>
</template>
<script lang="ts">
import { defineAsyncComponent, computed, ref, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
import { Local, Session } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
import setIntroduction from '/@/utils/setIconfont';
export default defineComponent({
name: 'app',
components: {
LockScreen: defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue')),
Setings: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/setings.vue')),
CloseFull: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue')),
},
setup() {
const { messages, locale } = useI18n();
const setingsRef = ref();
const route = useRoute();
const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 获取全局组件大小
const getGlobalComponentSize = computed(() => {
return other.globalComponentSize();
});
// 获取全局 i18n
const getGlobalI18n = computed(() => {
return messages.value[locale.value];
});
// 设置初始化,防止刷新时恢复默认
onBeforeMount(() => {
// 设置批量第三方 icon 图标
setIntroduction.cssCdn();
// 设置批量第三方 js
setIntroduction.jsCdn();
});
// 页面加载时
onMounted(() => {
nextTick(() => {
// 监听布局配置弹窗点击打开
mittBus.on('openSetingsDrawer', () => {
setingsRef.value.openDrawer();
});
// 获取缓存中的布局配置
if (Local.get('themeConfig')) {
storesThemeConfig.setThemeConfig(Local.get('themeConfig'));
document.documentElement.style.cssText = Local.get('themeConfigStyle');
}
// 获取缓存中的全屏配置
if (Session.get('isTagsViewCurrenFull')) {
stores.setCurrenFullscreen(Session.get('isTagsViewCurrenFull'));
}
});
});
// 页面销毁时,关闭监听布局配置/i18n监听
onUnmounted(() => {
mittBus.off('openSetingsDrawer', () => {});
});
// 监听路由的变化,设置网站标题
watch(
() => route.path,
() => {
other.useTitle();
},
{
deep: true,
}
);
return {
themeConfig,
setingsRef,
getGlobalComponentSize,
getGlobalI18n,
};
},
});
</script>
<style>
.searchBox {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
</style>

View File

@ -0,0 +1,23 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 09:19:20
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 09:43:23
* @FilePath: \tsplatform\frontend\src\api\menu\index.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import request from '/@/utils/request';
export function getPage(params: object) {
return request({
url: '/city/getPage',
method: 'get',
params
});
}
export function getList(params: object) {
return request({
url: '/city/getList',
method: 'get',
params
});
}

View File

@ -0,0 +1,34 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 09:19:20
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 17:01:13
* @FilePath: \tsplatform\frontend\src\api\login\index.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import request from '/@/utils/request';
/**
* 登录api接口集合
* @method signIn 用户登录
* @method signOut 用户退出登录
*/
export function signIn(params: object) {
return request({
url: '/auth/login',
method: 'post',
data: params,
});
}
export function getUserInfo() {
return request({
url: '/auth/getUserInfo',
method: 'get'
});
}
export function signOut() {
return request({
url: '/auth/loginOut',
method: 'get'
});
}

View File

@ -0,0 +1,51 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 09:19:20
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-24 00:24:11
* @FilePath: \tsplatform\frontend\src\api\menu\index.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import request from '/@/utils/request';
export function getAuthMenu(params: object) {
return request({
url: '/menu/getAuthMenuTree',
method: 'get',
params,
});
}
export function getMenuTree(params: object) {
return request({
url: '/menu/getMenuTree',
method: 'get',
params,
});
}
export function add(params: object) {
return request({
url: '/menu/add',
method: 'post',
data: params
});
}
export function edit(params: object) {
return request({
url: '/menu/edit',
method: 'post',
data: params
});
}
export function del(params: object) {
return request({
url: '/menu/del',
method: 'post',
data: params
});
}
export function setStatus(params: object) {
return request({
url: '/menu/setStatus',
method: 'post',
data: params
});
}

View File

@ -0,0 +1,52 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 09:19:20
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 21:35:46
* @FilePath: \tsplatform\frontend\src\api\menu\index.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import request from '/@/utils/request';
export function getPage(params: object) {
return request({
url: '/role/getPage',
method: 'get',
params
});
}
export function getList(params: object) {
return request({
url: '/role/getList',
method: 'get',
params
});
}
export function add(params: object) {
return request({
url: '/role/add',
method: 'post',
data: params
});
}
export function edit(params: object) {
return request({
url: '/role/edit',
method: 'post',
data: params
});
}
export function del(params: object) {
return request({
url: '/role/del',
method: 'post',
data: params
});
}
export function setStatus(params: object) {
return request({
url: '/role/setStatus',
method: 'post',
data: params
});
}

View File

@ -0,0 +1,51 @@
/*
* @Author: '490912587@qq.com' '490912587@qq.com'
* @Date: 2022-11-17 09:19:20
* @LastEditors: '490912587@qq.com' '490912587@qq.com'
* @LastEditTime: 2022-11-23 21:35:53
* @FilePath: \tsplatform\frontend\src\api\menu\index.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import request from '/@/utils/request';
export function getPage(params: object) {
return request({
url: '/user/getPage',
method: 'get',
params
});
}
export function getList(params: object) {
return request({
url: '/role/getList',
method: 'get',
params
});
}
export function add(params: object) {
return request({
url: '/user/add',
method: 'post',
data: params
});
}
export function edit(params: object) {
return request({
url: '/user/edit',
method: 'post',
data: params
});
}
export function del(params: object) {
return request({
url: '/user/del',
method: 'post',
data: params
});
}
export function setStatus(params: object) {
return request({
url: '/user/setStatus',
method: 'post',
data: params
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,30 @@
<template>
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useUserInfo } from '/@/stores/userInfo';
export default defineComponent({
name: 'auth',
props: {
value: {
type: String,
default: () => '',
},
},
setup(props) {
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
// 获取 vuex 中的用户权限
const getUserAuthBtnList = computed(() => {
return userInfos.value.authBtnList.some((v: string) => v === props.value);
});
return {
getUserAuthBtnList,
};
},
});
</script>

View File

@ -0,0 +1,31 @@
<template>
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useUserInfo } from '/@/stores/userInfo';
import { judementSameArr } from '/@/utils/arrayOperation';
export default defineComponent({
name: 'authAll',
props: {
value: {
type: Array,
default: () => [],
},
},
setup(props) {
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
// 获取 pinia 中的用户权限
const getUserAuthBtnList = computed(() => {
return judementSameArr(props.value, userInfos.value.authBtnList);
});
return {
getUserAuthBtnList,
};
},
});
</script>

View File

@ -0,0 +1,36 @@
<template>
<slot v-if="getUserAuthBtnList" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useUserInfo } from '/@/stores/userInfo';
export default defineComponent({
name: 'auths',
props: {
value: {
type: Array,
default: () => [],
},
},
setup(props) {
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
// 获取 vuex 中的用户权限
const getUserAuthBtnList = computed(() => {
let flag = false;
userInfos.value.authBtnList.map((val: string) => {
props.value.map((v) => {
if (val === v) flag = true;
});
});
return flag;
});
return {
getUserAuthBtnList,
};
},
});
</script>

View File

@ -0,0 +1,149 @@
<template>
<div>
<el-dialog title="更换头像" v-model="isShowDialog" width="769px">
<div class="cropper-warp">
<div class="cropper-warp-left">
<img :src="cropperImg" class="cropper-warp-left-img" />
</div>
<div class="cropper-warp-right">
<div class="cropper-warp-right-title">预览</div>
<div class="cropper-warp-right-item">
<div class="cropper-warp-right-value">
<img :src="cropperImgBase64" class="cropper-warp-right-value-img" />
</div>
<div class="cropper-warp-right-label">100 x 100</div>
</div>
<div class="cropper-warp-right-item">
<div class="cropper-warp-right-value">
<img :src="cropperImgBase64" class="cropper-warp-right-value-img cropper-size" />
</div>
<div class="cropper-warp-right-label">50 x 50</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, nextTick, defineComponent } from 'vue';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
export default defineComponent({
name: 'cropperIndex',
setup() {
const state = reactive({
isShowDialog: false,
cropperImg: '',
cropperImgBase64: '',
cropper: null,
});
// 打开弹窗
const openDialog = (imgs: any) => {
state.cropperImg = imgs;
state.isShowDialog = true;
nextTick(() => {
initCropper();
});
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 更换
const onSubmit = () => {
// state.cropperImgBase64 = state.cropper.getCroppedCanvas().toDataURL('image/jpeg');
};
// 初始化cropperjs图片裁剪
const initCropper = () => {
const letImg: any = document.querySelector('.cropper-warp-left-img');
(<any>state.cropper) = new Cropper(letImg, {
viewMode: 1,
dragMode: 'none',
initialAspectRatio: 1,
aspectRatio: 1,
preview: '.before',
background: false,
autoCropArea: 0.6,
zoomOnWheel: false,
crop: () => {
state.cropperImgBase64 = (<any>state.cropper).getCroppedCanvas().toDataURL('image/jpeg');
},
});
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
initCropper,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.cropper-warp {
display: flex;
.cropper-warp-left {
position: relative;
display: inline-block;
height: 350px;
flex: 1;
border: 1px solid var(--el-border-color);
background: var(--el-color-white);
overflow: hidden;
background-repeat: no-repeat;
cursor: move;
border-radius: var(--el-border-radius-base);
.cropper-warp-left-img {
width: 100%;
height: 100%;
}
}
.cropper-warp-right {
width: 150px;
height: 350px;
.cropper-warp-right-title {
text-align: center;
height: 20px;
line-height: 20px;
}
.cropper-warp-right-item {
margin: 15px 0;
.cropper-warp-right-value {
display: flex;
.cropper-warp-right-value-img {
width: 100px;
height: 100px;
border-radius: var(--el-border-radius-circle);
margin: auto;
}
.cropper-size {
width: 50px;
height: 50px;
}
}
.cropper-warp-right-label {
text-align: center;
font-size: 12px;
color: var(--el-text-color-primary);
height: 30px;
line-height: 30px;
}
}
}
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<div class="editor-container">
<Toolbar :editor="editorRef" :mode="mode" />
<Editor :mode="mode" :defaultConfig="editorConfig" :style="{ height }" v-model="editorVal" @onCreated="handleCreated" @onChange="handleChange" />
</div>
</template>
<script lang="ts">
// https://www.wangeditor.com/v5/for-frame.html#vue3
import '@wangeditor/editor/dist/css/style.css';
import { defineComponent, reactive, toRefs, shallowRef, watch, onBeforeUnmount } from 'vue';
import { Toolbar, Editor } from '@wangeditor/editor-for-vue';
export default defineComponent({
name: 'wngEditor',
components: { Toolbar, Editor },
props: {
// 是否禁用
disable: {
type: Boolean,
default: () => true,
},
// 内容框默认 placeholder
placeholder: {
type: String,
default: () => '请输入内容...',
},
// 双向绑定:双向绑定值,字段名为固定,改了之后将不生效
// 参考https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
modelValue: String,
// https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F
// 模式,可选 <default|simple>,默认 default
mode: {
type: String,
default: () => 'default',
},
// 高度
height: {
type: String,
default: () => '310px',
},
},
setup(props, { emit }) {
const editorRef = shallowRef();
const state = reactive({
editorConfig: {
placeholder: props.placeholder,
},
editorVal: props.modelValue,
});
// 编辑器回调函数
const handleCreated = (editor: any) => {
editorRef.value = editor;
};
// 编辑器内容改变时
const handleChange = (editor: any) => {
// console.log(editor.getText());
// console.log(editor.getHtml());
emit('update:modelValue', editor.getHtml());
};
// 监听是否禁用改变
// https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I
watch(
() => props.disable,
(bool) => {
const editor = editorRef.value;
if (editor == null) return;
bool ? editor.disable() : editor.enable();
},
{
deep: true,
}
);
// 页面销毁时
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
return {
editorRef,
handleCreated,
handleChange,
...toRefs(state),
};
},
});
</script>

View File

@ -0,0 +1,252 @@
<template>
<div class="icon-selector w100 h100">
<el-popover
placement="bottom"
:width="fontIconWidth"
trigger="click"
transition="el-zoom-in-top"
popper-class="icon-selector-popper"
@show="onPopoverShow"
>
<template #reference>
<el-input
v-model="fontIconSearch"
:placeholder="fontIconPlaceholder"
:clearable="clearable"
:disabled="disabled"
:size="size"
ref="inputWidthRef"
@clear="onClearFontIcon"
@focus="onIconFocus"
@blur="onIconBlur"
>
<template #prepend>
<SvgIcon
:name="fontIconPrefix === '' ? prepend : fontIconPrefix"
class="font14"
v-if="fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : fontIconPrefix?.indexOf('ele-') > -1"
/>
<i v-else :class="fontIconPrefix === '' ? prepend : fontIconPrefix" class="font14"></i>
</template>
</el-input>
</template>
<template #default>
<div class="icon-selector-warp">
<div class="icon-selector-warp-title flex">
<div class="flex-auto">{{ title }}</div>
<div class="icon-selector-warp-title-tab" v-if="type === 'all'">
<span :class="{ 'span-active': fontIconType === 'ali' }" @click="onIconChange('ali')" class="ml10" title="iconfont 图标">ali</span>
<span :class="{ 'span-active': fontIconType === 'ele' }" @click="onIconChange('ele')" class="ml10" title="elementPlus 图标">ele</span>
<span :class="{ 'span-active': fontIconType === 'awe' }" @click="onIconChange('awe')" class="ml10" title="fontawesome 图标">awe</span>
</div>
</div>
<div class="icon-selector-warp-row">
<el-scrollbar ref="selectorScrollbarRef">
<el-row :gutter="10" v-if="fontIconSheetsFilterList.length > 0">
<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" @click="onColClick(v)" v-for="(v, k) in fontIconSheetsFilterList" :key="k">
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': fontIconPrefix === v }">
<div class="flex-margin">
<div class="icon-selector-warp-item-value">
<SvgIcon :name="v" />
</div>
</div>
</div>
</el-col>
</el-row>
<el-empty :image-size="100" v-if="fontIconSheetsFilterList.length <= 0" :description="emptyDescription"></el-empty>
</el-scrollbar>
</div>
</div>
</template>
</el-popover>
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted, nextTick, computed, watch, defineComponent } from 'vue';
import initIconfont from '/@/utils/getStyleSheets';
export default defineComponent({
name: 'iconSelector',
emits: ['update:modelValue', 'get', 'clear'],
props: {
// 输入框前置内容
prepend: {
type: String,
default: () => 'ele-Pointer',
},
// 输入框占位文本
placeholder: {
type: String,
default: () => '请输入内容搜索图标或者选择图标',
},
// 输入框占位文本
size: {
type: String,
default: () => 'default',
},
// 弹窗标题
title: {
type: String,
default: () => '请选择图标',
},
// icon 图标类型
type: {
type: String,
default: () => 'ele',
},
// 禁用
disabled: {
type: Boolean,
default: () => false,
},
// 是否可清空
clearable: {
type: Boolean,
default: () => true,
},
// 自定义空状态描述文字
emptyDescription: {
type: String,
default: () => '无相关图标',
},
// 双向绑定值,默认为 modelValue
// 参考https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
// 参考https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
modelValue: String,
},
setup(props, { emit }) {
const inputWidthRef = ref();
const selectorScrollbarRef = ref();
const state = reactive({
fontIconPrefix: '',
fontIconWidth: 0,
fontIconSearch: '',
fontIconTabsIndex: 0,
fontIconSheetsList: [],
fontIconPlaceholder: '',
fontIconType: 'ali',
fontIconShow: true,
});
// 处理 input 获取焦点时modelValue 有值时,改变 input 的 placeholder 值
const onIconFocus = () => {
if (!props.modelValue) return false;
state.fontIconSearch = '';
state.fontIconPlaceholder = props.modelValue;
};
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
const onIconBlur = () => {
setTimeout(() => {
const icon = state.fontIconSheetsList.filter((icon: string) => icon === state.fontIconSearch);
if (icon.length <= 0) state.fontIconSearch = '';
}, 300);
};
// 处理 icon 双向绑定数值回显
const initModeValueEcho = () => {
if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
(<string | undefined>state.fontIconPrefix) = props.modelValue;
};
// 处理 icon type 类型为 all 时,类型 ali、ele、awe 回显问题
const initFontIconTypeEcho = () => {
if ((<any>props.modelValue)?.indexOf('iconfont') > -1) onIconChange('ali');
else if ((<any>props.modelValue)?.indexOf('ele-') > -1) onIconChange('ele');
else if ((<any>props.modelValue)?.indexOf('fa') > -1) onIconChange('awe');
else onIconChange('ali');
};
// 图标搜索及图标数据显示
const fontIconSheetsFilterList = computed(() => {
if (!state.fontIconSearch) return state.fontIconSheetsList;
let search = state.fontIconSearch.trim().toLowerCase();
return state.fontIconSheetsList.filter((item: any) => {
if (item.toLowerCase().indexOf(search) !== -1) return item;
});
});
// 获取 input 的宽度
const getInputWidth = () => {
nextTick(() => {
state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
});
};
// 监听页面宽度改变
const initResize = () => {
window.addEventListener('resize', () => {
getInputWidth();
});
};
// 初始化数据
const initFontIconData = async (type: string) => {
state.fontIconSheetsList = [];
if (type === 'ali') {
await initIconfont.ali().then((res: any) => {
// 阿里字体图标使用 `iconfont xxx`
state.fontIconSheetsList = res.map((i: string) => `iconfont ${i}`);
});
} else if (type === 'ele') {
await initIconfont.ele().then((res: any) => {
state.fontIconSheetsList = res;
});
} else if (type === 'awe') {
await initIconfont.awe().then((res: any) => {
// fontawesome字体图标使用 `fa xxx`
state.fontIconSheetsList = res.map((i: string) => `fa ${i}`);
});
}
// 初始化 input 的 placeholder
// 参考单项数据流https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
state.fontIconPlaceholder = props.placeholder;
// 初始化双向绑定回显
initModeValueEcho();
};
// 图标点击切换
const onIconChange = (type: string) => {
state.fontIconType = type;
initFontIconData(type);
};
// 获取当前点击的 icon 图标
const onColClick = (v: any) => {
state.fontIconPlaceholder = v;
state.fontIconPrefix = v;
emit('get', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 清空当前点击的 icon 图标
const onClearFontIcon = () => {
state.fontIconPrefix = '';
emit('clear', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 监听 Popover 打开,用于双向绑定值回显
const onPopoverShow = () => {
initModeValueEcho();
initFontIconTypeEcho();
};
// 页面加载时
onMounted(() => {
initModeValueEcho();
initResize();
getInputWidth();
});
// 监听双向绑定 modelValue 的变化
watch(
() => props.modelValue,
() => {
initModeValueEcho();
}
);
return {
inputWidthRef,
selectorScrollbarRef,
fontIconSheetsFilterList,
onColClick,
onIconChange,
onClearFontIcon,
onIconFocus,
onIconBlur,
onPopoverShow,
...toRefs(state),
};
},
});
</script>

View File

@ -0,0 +1,195 @@
<template>
<div class="notice-bar" :style="{ background, height: `${height}px` }" v-show="!isMode">
<div class="notice-bar-warp" :style="{ color, fontSize: `${size}px` }">
<i v-if="leftIcon" class="notice-bar-warp-left-icon" :class="leftIcon"></i>
<div class="notice-bar-warp-text-box" ref="noticeBarWarpRef">
<div class="notice-bar-warp-text" ref="noticeBarTextRef" v-if="!scrollable">{{ text }}</div>
<div class="notice-bar-warp-slot" v-else><slot /></div>
</div>
<SvgIcon :name="rightIcon" v-if="rightIcon" class="notice-bar-warp-right-icon" @click="onRightIconClick" />
</div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, ref, onMounted, nextTick } from 'vue';
export default defineComponent({
name: 'noticeBar',
props: {
// 通知栏模式,可选值为 closeable link
mode: {
type: String,
default: () => '',
},
// 通知文本内容
text: {
type: String,
default: () => '',
},
// 通知文本颜色
color: {
type: String,
default: () => 'var(--el-color-warning)',
},
// 通知背景色
background: {
type: String,
default: () => 'var(--el-color-warning-light-9)',
},
// 字体大小单位px
size: {
type: [Number, String],
default: () => 14,
},
// 通知栏高度单位px
height: {
type: Number,
default: () => 40,
},
// 动画延迟时间 (s)
delay: {
type: Number,
default: () => 1,
},
// 滚动速率 (px/s)
speed: {
type: Number,
default: () => 100,
},
// 是否开启垂直滚动
scrollable: {
type: Boolean,
default: () => false,
},
// 自定义左侧图标
leftIcon: {
type: String,
default: () => '',
},
// 自定义右侧图标
rightIcon: {
type: String,
default: () => '',
},
},
setup(props, { emit }) {
const noticeBarWarpRef = ref();
const noticeBarTextRef = ref();
const state = reactive({
order: 1,
oneTime: 0,
twoTime: 0,
warpOWidth: 0,
textOWidth: 0,
isMode: false,
});
// 初始化 animation 各项参数
const initAnimation = () => {
nextTick(() => {
state.warpOWidth = noticeBarWarpRef.value.offsetWidth;
state.textOWidth = noticeBarTextRef.value.offsetWidth;
document.styleSheets[0].insertRule(`@keyframes oneAnimation {0% {left: 0px;} 100% {left: -${state.textOWidth}px;}}`);
document.styleSheets[0].insertRule(`@keyframes twoAnimation {0% {left: ${state.warpOWidth}px;} 100% {left: -${state.textOWidth}px;}}`);
computeAnimationTime();
setTimeout(() => {
changeAnimation();
}, props.delay * 1000);
});
};
// 计算 animation 滚动时长
const computeAnimationTime = () => {
state.oneTime = state.textOWidth / props.speed;
state.twoTime = (state.textOWidth + state.warpOWidth) / props.speed;
};
// 改变 animation 动画调用
const changeAnimation = () => {
if (state.order === 1) {
noticeBarTextRef.value.style.cssText = `animation: oneAnimation ${state.oneTime}s linear; opactity: 1;}`;
state.order = 2;
} else {
noticeBarTextRef.value.style.cssText = `animation: twoAnimation ${state.twoTime}s linear infinite; opacity: 1;`;
}
};
// 监听 animation 动画的结束
const listenerAnimationend = () => {
noticeBarTextRef.value.addEventListener(
'animationend',
() => {
changeAnimation();
},
false
);
};
// 右侧 icon 图标点击
const onRightIconClick = () => {
if (!props.mode) return false;
if (props.mode === 'closeable') {
state.isMode = true;
emit('close');
} else if (props.mode === 'link') {
emit('link');
}
};
// 页面加载时
onMounted(() => {
if (props.scrollable) return false;
initAnimation();
listenerAnimationend();
});
return {
noticeBarWarpRef,
noticeBarTextRef,
onRightIconClick,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.notice-bar {
padding: 0 15px;
width: 100%;
border-radius: 4px;
.notice-bar-warp {
display: flex;
align-items: center;
width: 100%;
height: inherit;
.notice-bar-warp-text-box {
flex: 1;
height: inherit;
display: flex;
align-items: center;
overflow: hidden;
position: relative;
.notice-bar-warp-text {
white-space: nowrap;
position: absolute;
left: 0;
}
.notice-bar-warp-slot {
width: 100%;
white-space: nowrap;
:deep(.el-carousel__item) {
display: flex;
align-items: center;
}
}
}
.notice-bar-warp-left-icon {
width: 24px;
font-size: inherit !important;
}
.notice-bar-warp-right-icon {
width: 24px;
text-align: right;
font-size: inherit !important;
&:hover {
cursor: pointer;
}
}
}
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<i v-if="isShowIconSvg" class="el-icon" :style="setIconSvgStyle">
<component :is="getIconName" />
</i>
<div v-else-if="isShowIconImg" :style="setIconImgOutStyle">
<img :src="getIconName" :style="setIconSvgInsStyle" />
</div>
<i v-else :class="getIconName" :style="setIconSvgStyle" />
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
export default defineComponent({
name: 'svgIcon',
props: {
// svg 图标组件名字
name: {
type: String,
},
// svg 大小
size: {
type: Number,
default: () => 14,
},
// svg 颜色
color: {
type: String,
},
},
setup(props) {
// 在线链接、本地引入地址前缀
const linesString = ['https', 'http', '/src', '/assets', import.meta.env.VITE_PUBLIC_PATH];
// 获取 icon 图标名称
const getIconName = computed(() => {
return props?.name;
});
// 用于判断 element plus 自带 svg 图标的显示、隐藏
const isShowIconSvg = computed(() => {
return props?.name?.startsWith('ele-');
});
// 用于判断在线链接、本地引入等图标显示、隐藏
const isShowIconImg = computed(() => {
return linesString.find((str) => props.name?.startsWith(str));
});
// 设置图标样式
const setIconSvgStyle = computed(() => {
return `font-size: ${props.size}px;color: ${props.color};`;
});
// 设置图片样式
const setIconImgOutStyle = computed(() => {
return `width: ${props.size}px;height: ${props.size}px;display: inline-block;overflow: hidden;`;
});
// 设置图片样式
// https://gitee.com/lyt-top/vue-next-admin/issues/I59ND0
const setIconSvgInsStyle = computed(() => {
const filterStyle: string[] = [];
const compatibles: string[] = ['-webkit', '-ms', '-o', '-moz'];
compatibles.forEach((j) => filterStyle.push(`${j}-filter: drop-shadow(${props.color} 30px 0);`));
return `width: ${props.size}px;height: ${props.size}px;position: relative;left: -${props.size}px;${filterStyle.join('')}`;
});
return {
getIconName,
isShowIconSvg,
isShowIconImg,
setIconSvgStyle,
setIconImgOutStyle,
setIconSvgInsStyle,
};
},
});
</script>

View File

@ -0,0 +1,68 @@
import { createI18n } from 'vue-i18n';
import pinia from '/@/stores/index';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import zhcnLocale from 'element-plus/lib/locale/lang/zh-cn';
import enLocale from 'element-plus/lib/locale/lang/en';
import zhtwLocale from 'element-plus/lib/locale/lang/zh-tw';
import nextZhcn from '/@/i18n/lang/zh-cn';
import nextEn from '/@/i18n/lang/en';
import nextZhtw from '/@/i18n/lang/zh-tw';
import pagesLoginZhcn from '/@/i18n/pages/login/zh-cn';
import pagesLoginEn from '/@/i18n/pages/login/en';
import pagesLoginZhtw from '/@/i18n/pages/login/zh-tw';
import pagesFormI18nZhcn from '/@/i18n/pages/formI18n/zh-cn';
import pagesFormI18nEn from '/@/i18n/pages/formI18n/en';
import pagesFormI18nZhtw from '/@/i18n/pages/formI18n/zh-tw';
// 定义语言国际化内容
/**
* 说明:
* /src/i18n/lang 下的 ts 为框架的国际化内容
* /src/i18n/pages 下的 ts 为各界面的国际化内容
*/
const messages = {
[zhcnLocale.name]: {
...zhcnLocale,
message: {
...nextZhcn,
...pagesLoginZhcn,
...pagesFormI18nZhcn,
},
},
[enLocale.name]: {
...enLocale,
message: {
...nextEn,
...pagesLoginEn,
...pagesFormI18nEn,
},
},
[zhtwLocale.name]: {
...zhtwLocale,
message: {
...nextZhtw,
...pagesLoginZhtw,
...pagesFormI18nZhtw,
},
},
};
// 读取 pinia 默认语言
const stores = useThemeConfig(pinia);
const { themeConfig } = storeToRefs(stores);
// 导出语言国际化
// https://vue-i18n.intlify.dev/guide/essentials/fallback.html#explicit-fallback-with-one-locale
export const i18n = createI18n({
legacy: false,
silentTranslationWarn: true,
missingWarn: false,
silentFallbackWarn: true,
fallbackWarn: false,
locale: themeConfig.value.globalI18n,
fallbackLocale: zhcnLocale.name,
messages,
});

View File

@ -0,0 +1,120 @@
// 定义内容
export default {
router: {
home: 'home',
system: {
system: 'system',
menu: 'systemMenu',
role: 'systemRole',
user: 'systemUser',
dic: 'systemDic',
city: 'systemDept'
},
personal: 'personal',
},
staticRoutes: {
signIn: 'signIn',
notFound: 'notFound',
noPower: 'noPower',
},
user: {
title0: 'Component size',
title1: 'Language switching',
title2: 'Menu search',
title3: 'Layout configuration',
title4: 'news',
title5: 'Full screen on',
title6: 'Full screen off',
dropdownLarge: 'large',
dropdownDefault: 'default',
dropdownSmall: 'small',
dropdown1: 'home page',
dropdown2: 'Personal Center',
dropdown3: '404',
dropdown4: '401',
dropdown5: 'Log out',
dropdown6: 'Code warehouse',
searchPlaceholder: 'Menu search: support Chinese, routing path',
newTitle: 'notice',
newBtn: 'All read',
newGo: 'Go to the notification center',
newDesc: 'No notice',
logOutTitle: 'Tips',
logOutMessage: 'This operation will log out. Do you want to continue?',
logOutConfirm: 'determine',
logOutCancel: 'cancel',
logOutExit: 'Exiting',
},
tagsView: {
refresh: 'refresh',
close: 'close',
closeOther: 'closeOther',
closeAll: 'closeAll',
fullscreen: 'fullscreen',
closeFullscreen: 'closeFullscreen',
},
notFound: {
foundTitle: 'Wrong address input, please re-enter the address~',
foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
foundBtn: 'Back to home page',
},
noAccess: {
accessTitle: 'You are not authorized to operate~',
accessMsg: 'Contact information: add QQ group discussion 665452019',
accessBtn: 'Reauthorization',
},
layout: {
configTitle: 'Layout configuration',
oneTitle: 'Global Themes',
twoTopTitle: 'top bar set up',
twoMenuTitle: 'Menu set up',
twoColumnsTitle: 'Columns set up',
twoTopBar: 'Top bar background',
twoTopBarColor: 'Top bar default font color',
twoIsTopBarColorGradual: 'Top bar gradient',
twoMenuBar: 'Menu background',
twoMenuBarColor: 'Menu default font color',
twoIsMenuBarColorGradual: 'Menu gradient',
twoColumnsMenuBar: 'Column menu background',
twoColumnsMenuBarColor: 'Default font color bar menu',
twoIsColumnsMenuBarColorGradual: 'Column gradient',
twoIsColumnsMenuHoverPreload: 'Column Menu Hover Preload',
threeTitle: 'Interface settings',
threeIsCollapse: 'Menu horizontal collapse',
threeIsUniqueOpened: 'Menu accordion',
threeIsFixedHeader: 'Fixed header',
threeIsClassicSplitMenu: 'Classic layout split menu',
threeIsLockScreen: 'Open the lock screen',
threeLockScreenTime: 'screen locking(s/s)',
fourTitle: 'Interface display',
fourIsShowLogo: 'Sidebar logo',
fourIsBreadcrumb: 'Open breadcrumb',
fourIsBreadcrumbIcon: 'Open breadcrumb icon',
fourIsTagsview: 'Open tagsview',
fourIsTagsviewIcon: 'Open tagsview Icon',
fourIsCacheTagsView: 'Enable tagsview cache',
fourIsSortableTagsView: 'Enable tagsview drag',
fourIsShareTagsView: 'Enable tagsview sharing',
fourIsFooter: 'Open footer',
fourIsGrayscale: 'Grey model',
fourIsInvert: 'Color weak mode',
fourIsDark: 'Dark Mode',
fourIsWartermark: 'Turn on watermark',
fourWartermarkText: 'Watermark copy',
fiveTitle: 'Other settings',
fiveTagsStyle: 'Tagsview style',
fiveAnimation: 'page animation',
fiveColumnsAsideStyle: 'Column style',
fiveColumnsAsideLayout: 'Column layout',
sixTitle: 'Layout switch',
sixDefaults: 'One',
sixClassic: 'Two',
sixTransverse: 'Three',
sixColumns: 'Four',
tipText: 'Click the button below to copy the layout configuration to `/src/stores/themeConfig.ts` It has been modified in.',
copyText: 'replication configuration',
resetText: 'restore default',
copyTextSuccess: 'Copy succeeded!',
copyTextError: 'Copy failed!',
},
};

View File

@ -0,0 +1,120 @@
// 定义内容
export default {
router: {
home: '首页',
system: {
system: '系统设置',
menu: '菜单管理',
role: '角色管理',
user: '用户管理',
dic: '字典管理',
city: '城市管理'
},
personal: '个人中心',
},
staticRoutes: {
signIn: '登录',
notFound: '找不到此页面',
noPower: '没有权限',
},
user: {
title0: '组件大小',
title1: '语言切换',
title2: '菜单搜索',
title3: '布局配置',
title4: '消息',
title5: '开全屏',
title6: '关全屏',
dropdownLarge: '大型',
dropdownDefault: '默认',
dropdownSmall: '小型',
dropdown1: '首页',
dropdown2: '个人中心',
dropdown3: '404',
dropdown4: '401',
dropdown5: '退出登录',
dropdown6: '代码仓库',
searchPlaceholder: '菜单搜索:支持中文、路由路径',
newTitle: '通知',
newBtn: '全部已读',
newGo: '前往通知中心',
newDesc: '暂无通知',
logOutTitle: '提示',
logOutMessage: '此操作将退出登录, 是否继续?',
logOutConfirm: '确定',
logOutCancel: '取消',
logOutExit: '退出中',
},
tagsView: {
refresh: '刷新',
close: '关闭',
closeOther: '关闭其它',
closeAll: '全部关闭',
fullscreen: '当前页全屏',
closeFullscreen: '关闭全屏',
},
notFound: {
foundTitle: '地址输入错误,请重新输入地址~',
foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
foundBtn: '返回首页',
},
noAccess: {
accessTitle: '您未被授权,没有操作权限~',
accessMsg: '联系方式加QQ群探讨 665452019',
accessBtn: '重新授权',
},
layout: {
configTitle: '布局配置',
oneTitle: '全局主题',
twoTopTitle: '顶栏设置',
twoMenuTitle: '菜单设置',
twoColumnsTitle: '分栏设置',
twoTopBar: '顶栏背景',
twoTopBarColor: '顶栏默认字体颜色',
twoIsTopBarColorGradual: '顶栏背景渐变',
twoMenuBar: '菜单背景',
twoMenuBarColor: '菜单默认字体颜色',
twoIsMenuBarColorGradual: '菜单背景渐变',
twoColumnsMenuBar: '分栏菜单背景',
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
twoIsColumnsMenuHoverPreload: '分栏菜单鼠标悬停预加载',
threeTitle: '界面设置',
threeIsCollapse: '菜单水平折叠',
threeIsUniqueOpened: '菜单手风琴',
threeIsFixedHeader: '固定 Header',
threeIsClassicSplitMenu: '经典布局分割菜单',
threeIsLockScreen: '开启锁屏',
threeLockScreenTime: '自动锁屏(s/秒)',
fourTitle: '界面显示',
fourIsShowLogo: '侧边栏 Logo',
fourIsBreadcrumb: '开启 Breadcrumb',
fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
fourIsTagsview: '开启 Tagsview',
fourIsTagsviewIcon: '开启 Tagsview 图标',
fourIsCacheTagsView: '开启 TagsView 缓存',
fourIsSortableTagsView: '开启 TagsView 拖拽',
fourIsShareTagsView: '开启 TagsView 共用',
fourIsFooter: '开启 Footer',
fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式',
fourIsDark: '深色模式',
fourIsWartermark: '开启水印',
fourWartermarkText: '水印文案',
fiveTitle: '其它设置',
fiveTagsStyle: 'Tagsview 风格',
fiveAnimation: '主页面切换动画',
fiveColumnsAsideStyle: '分栏高亮风格',
fiveColumnsAsideLayout: '分栏布局风格',
sixTitle: '布局切换',
sixDefaults: '默认',
sixClassic: '经典',
sixTransverse: '横向',
sixColumns: '分栏',
tipText: '点击下方按钮,复制布局配置去 `src/stores/themeConfig.ts` 中修改。',
copyText: '一键复制配置',
resetText: '一键恢复默认',
copyTextSuccess: '复制成功!',
copyTextError: '复制失败!',
},
};

View File

@ -0,0 +1,121 @@
// 定义内容
export default {
router: {
home: '首頁',
system: {
system: '系統設置',
menu: '選單管理',
role: '角色管理',
user: '用戶管理',
dic: '字典管理',
city: '城市管理'
},
personal: '個人中心',
},
staticRoutes: {
signIn: '登入',
notFound: '找不到此頁面',
noPower: '沒有許可權',
},
user: {
title0: '組件大小',
title1: '語言切換',
title2: '選單蒐索',
title3: '佈局配寘',
title4: '消息',
title5: '開全屏',
title6: '關全屏',
dropdownLarge: '大型',
dropdownDefault: '默認',
dropdownSmall: '小型',
dropdown1: '首頁',
dropdown2: '個人中心',
dropdown3: '404',
dropdown4: '401',
dropdown5: '登出',
dropdown6: '程式碼倉庫',
searchPlaceholder: '選單蒐索:支援中文、路由路徑',
newTitle: '通知',
newBtn: '全部已讀',
newGo: '前往通知中心',
newDesc: '暫無通知',
logOutTitle: '提示',
logOutMessage: '此操作將登出,是否繼續?',
logOutConfirm: '確定',
logOutCancel: '取消',
logOutExit: '退出中',
},
tagsView: {
refresh: '重繪',
close: '關閉',
closeOther: '關閉其它',
closeAll: '全部關閉',
fullscreen: '當前頁全屏',
closeFullscreen: '關閉全屏',
},
notFound: {
foundTitle: '地址輸入錯誤,請重新輸入地址~',
foundMsg: '您可以先檢查網址,然後重新輸入或給我們迴響問題。',
foundBtn: '返回首頁',
},
noAccess: {
accessTitle: '您未被授權,沒有操作許可權~',
accessMsg: '聯繫方式加QQ群探討665452019',
accessBtn: '重新授權',
},
layout: {
configTitle: '佈局配寘',
oneTitle: '全域主題',
twoTopTitle: '頂欄設定',
twoMenuTitle: '選單設定',
twoColumnsTitle: '分欄設定',
twoTopBar: '頂欄背景',
twoTopBarColor: '頂欄默認字體顏色',
twoIsTopBarColorGradual: '頂欄背景漸變',
twoMenuBar: '選單背景',
twoMenuBarColor: '選單默認字體顏色',
twoIsMenuBarColorGradual: '選單背景漸變',
twoColumnsMenuBar: '分欄選單背景',
twoColumnsMenuBarColor: '分欄選單默認字體顏色',
twoIsColumnsMenuBarColorGradual: '分欄選單背景漸變',
twoIsColumnsMenuHoverPreload: '分欄選單滑鼠懸停預加載',
threeTitle: '介面設定',
threeIsCollapse: '選單水准折疊',
threeIsUniqueOpened: '選單手風琴',
threeIsFixedHeader: '固定 Header',
threeIsClassicSplitMenu: '經典佈局分割選單',
threeIsLockScreen: '開啟鎖屏',
threeLockScreenTime: '自動鎖屏(s/秒)',
fourTitle: '介面顯示',
fourIsShowLogo: '側邊欄 Logo',
fourIsBreadcrumb: '開啟 Breadcrumb',
fourIsBreadcrumbIcon: '開啟 Breadcrumb 圖標',
fourIsTagsview: '開啟 Tagsview',
fourIsTagsviewIcon: '開啟 Tagsview 圖標',
fourIsCacheTagsView: '開啟 TagsView 緩存',
fourIsSortableTagsView: '開啟 TagsView 拖拽',
fourIsShareTagsView: '開啟 TagsView 共用',
fourIsFooter: '開啟 Footer',
fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式',
fourIsDark: '深色模式',
fourIsWartermark: '開啟浮水印',
fourWartermarkText: '浮水印文案',
fiveTitle: '其它設定',
fiveTagsStyle: 'Tagsview 風格',
fiveAnimation: '主頁面切換動畫',
fiveColumnsAsideStyle: '分欄高亮風格',
fiveColumnsAsideLayout: '分欄佈局風格',
sixTitle: '佈局切換',
sixDefaults: '默認',
sixClassic: '經典',
sixTransverse: '橫向',
sixColumns: '分欄',
tipText: '點擊下方按鈕,複製佈局配寘去`src/stores/themeConfig.ts`中修改。',
copyText: '一鍵複製配寘',
resetText: '一鍵恢復默認',
copyTextSuccess: '複製成功!',
copyTextError: '複製失敗!',
},
};

View File

@ -0,0 +1,13 @@
// 定义内容
export default {
formI18nLabel: {
name: 'name',
email: 'email',
autograph: 'autograph',
},
formI18nPlaceholder: {
name: 'Please enter your name',
email: 'Please enter the users Department',
autograph: 'Please enter the login account name',
},
};

View File

@ -0,0 +1,13 @@
// 定义内容
export default {
formI18nLabel: {
name: '姓名',
email: '用户归属部门',
autograph: '登陆账户名',
},
formI18nPlaceholder: {
name: '请输入姓名',
email: '请输入用户归属部门',
autograph: '请输入登陆账户名',
},
};

View File

@ -0,0 +1,13 @@
// 定义内容
export default {
formI18nLabel: {
name: '姓名',
email: '用戶歸屬部門',
autograph: '登入帳戶名',
},
formI18nPlaceholder: {
name: '請輸入姓名',
email: '請輸入用戶歸屬部門',
autograph: '請輸入登入帳戶名',
},
};

View File

@ -0,0 +1,29 @@
// 定义内容
export default {
label: {
one1: 'User name login',
two2: 'Mobile number',
},
link: {
one3: 'Third party login',
two4: 'Links',
},
account: {
accountPlaceholder1: 'The user name admin or not is common',
accountPlaceholder2: 'Password: 123456',
accountPlaceholder3: 'Please enter the verification code',
accountBtnText: 'Sign in',
},
mobile: {
placeholder1: 'Please input mobile phone number',
placeholder2: 'Please enter the verification code',
codeText: 'Get code',
btnText: 'Sign in',
msgText:
'Warm tip: it is recommended to use Google, Microsoft edge, version 79.0.1072.62 and above browsers, and 360 browser, please use speed mode',
},
scan: {
text: 'Open the mobile phone to scan and quickly log in / register',
},
signInText: 'welcome back!',
};

View File

@ -0,0 +1,28 @@
// 定义内容
export default {
label: {
one1: '用户名登录',
two2: '手机号登录',
},
link: {
one3: '第三方登录',
two4: '友情链接',
},
account: {
accountPlaceholder1: '用户名 admin 或不输均为 common',
accountPlaceholder2: '密码123456',
accountPlaceholder3: '请输入验证码',
accountBtnText: '登 录',
},
mobile: {
placeholder1: '请输入手机号',
placeholder2: '请输入验证码',
codeText: '获取验证码',
btnText: '登 录',
msgText: '* 温馨提示建议使用谷歌、Microsoft Edge版本 79.0.1072.62 及以上浏览器360浏览器请使用极速模式',
},
scan: {
text: '打开手机扫一扫,快速登录/注册',
},
signInText: '欢迎回来!',
};

View File

@ -0,0 +1,28 @@
// 定义内容
export default {
label: {
one1: '用戶名登入',
two2: '手機號登入',
},
link: {
one3: '協力廠商登入',
two4: '友情連結',
},
account: {
accountPlaceholder1: '用戶名admin或不輸均為common',
accountPlaceholder2: '密碼123456',
accountPlaceholder3: '請輸入驗證碼',
accountBtnText: '登入',
},
mobile: {
placeholder1: '請輸入手機號',
placeholder2: '請輸入驗證碼',
codeText: '獲取驗證碼',
btnText: '登入',
msgText: '* 溫馨提示建議使用穀歌、Microsoft Edge版本79.0.1072.62及以上瀏覽器360瀏覽器請使用極速模式',
},
scan: {
text: '打開手機掃一掃,快速登錄/注册',
},
signInText: '歡迎回來!',
};

View File

@ -0,0 +1,165 @@
<template>
<div class="h100" v-show="!isTagsViewCurrenFull">
<el-aside class="layout-aside" :class="setCollapseStyle">
<Logo v-if="setShowLogo" />
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef" @mouseenter="onAsideEnterLeave(true)" @mouseleave="onAsideEnterLeave(false)">
<Vertical :menuList="menuList" />
</el-scrollbar>
</el-aside>
</div>
</template>
<script lang="ts">
import { defineAsyncComponent, toRefs, reactive, computed, watch, onBeforeMount, defineComponent, ref } from 'vue';
import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'layoutAside',
components: {
Logo: defineAsyncComponent(() => import('/@/layout/logo/index.vue')),
Vertical: defineAsyncComponent(() => import('/@/layout/navMenu/vertical.vue')),
},
setup() {
const layoutAsideScrollbarRef = ref();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { routesList } = storeToRefs(stores);
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const state = reactive({
menuList: [],
clientWidth: 0,
});
// 设置菜单展开/收起时的宽度
const setCollapseStyle = computed(() => {
const { layout, isCollapse, menuBar } = themeConfig.value;
const asideBrTheme = ['#FFFFFF', '#FFF', '#fff', '#ffffff'];
const asideBrColor = asideBrTheme.includes(menuBar) ? 'layout-el-aside-br-color' : '';
// 判断是否是手机端
if (state.clientWidth <= 1000) {
if (isCollapse) {
document.body.setAttribute('class', 'el-popup-parent--hidden');
const asideEle = document.querySelector('.layout-container') as HTMLElement;
const modeDivs = document.createElement('div');
modeDivs.setAttribute('class', 'layout-aside-mobile-mode');
asideEle.appendChild(modeDivs);
modeDivs.addEventListener('click', closeLayoutAsideMobileMode);
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-open'];
} else {
// 关闭弹窗
closeLayoutAsideMobileMode();
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-close'];
}
} else {
if (layout === 'columns') {
// 分栏布局,菜单收起时宽度给 1px
if (isCollapse) return [asideBrColor, 'layout-aside-pc-1'];
else return [asideBrColor, 'layout-aside-pc-220'];
} else {
// 其它布局给 64px
if (isCollapse) return [asideBrColor, 'layout-aside-pc-64'];
else return [asideBrColor, 'layout-aside-pc-220'];
}
}
});
// 关闭移动端蒙版
const closeLayoutAsideMobileMode = () => {
const el = document.querySelector('.layout-aside-mobile-mode');
el?.setAttribute('style', 'animation: error-img-two 0.3s');
setTimeout(() => {
el?.parentNode?.removeChild(el);
}, 300);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
document.body.setAttribute('class', '');
};
// 设置显示/隐藏 logo
const setShowLogo = computed(() => {
let { layout, isShowLogo } = themeConfig.value;
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
});
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
if (themeConfig.value.layout === 'columns') return false;
(state.menuList as any) = filterRoutesFun(routesList.value);
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<string>) => {
return arr
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 设置菜单导航是否固定(移动端)
const initMenuFixed = (clientWidth: number) => {
state.clientWidth = clientWidth;
};
// 鼠标移入、移出
const onAsideEnterLeave = (bool: Boolean) => {
let { layout } = themeConfig.value;
if (layout !== 'columns') return false;
if (!bool) mittBus.emit('restoreDefault');
stores.setColumnsMenuHover(bool);
};
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(themeConfig.value, (val) => {
if (val.isShowLogoChange !== val.isShowLogo) {
if (layoutAsideScrollbarRef.value) layoutAsideScrollbarRef.value.update();
}
});
// 监听vuex值的变化动态赋值给菜单中
watch(
pinia.state,
(val) => {
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) return false;
setFilterRoutes();
},
{
deep: true,
}
);
// 页面加载前
onBeforeMount(() => {
initMenuFixed(document.body.clientWidth);
setFilterRoutes();
// 此界面不需要取消监听(mittBus.off('setSendColumnsChildren))
// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
mittBus.on('setSendColumnsChildren', (res: any) => {
state.menuList = res.children;
});
mittBus.on('setSendClassicChildren', (res: any) => {
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = [];
state.menuList = res.children;
}
});
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
mittBus.on('layoutMobileResize', (res: any) => {
initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode();
});
});
return {
layoutAsideScrollbarRef,
setCollapseStyle,
setShowLogo,
isTagsViewCurrenFull,
onAsideEnterLeave,
...toRefs(state),
};
},
});
</script>

View File

@ -0,0 +1,296 @@
<template>
<div class="layout-columns-aside">
<el-scrollbar>
<ul @mouseleave="onColumnsAsideMenuMouseleave()">
<li
v-for="(v, k) in columnsAsideList"
:key="k"
@click="onColumnsAsideMenuClick(v, k)"
@mouseenter="onColumnsAsideMenuMouseenter(v, k)"
:ref="
(el) => {
if (el) columnsAsideOffsetTopRefs[k] = el;
}
"
:class="{ 'layout-columns-active': liIndex === k, 'layout-columns-hover': liHoverIndex === k }"
:title="$t(v.meta.title)"
>
<div :class="themeConfig.columnsAsideLayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
<SvgIcon :name="v.meta.icon" />
<div class="columns-vertical-title font12">
{{
$t(v.meta.title) && $t(v.meta.title).length >= 4
? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
: $t(v.meta.title)
}}
</div>
</div>
<div :class="themeConfig.columnsAsideLayout" v-else>
<a :href="v.meta.isLink" target="_blank">
<SvgIcon :name="v.meta.icon" />
<div class="columns-vertical-title font12">
{{
$t(v.meta.title) && $t(v.meta.title).length >= 4
? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
: $t(v.meta.title)
}}
</div>
</a>
</div>
</li>
<div ref="columnsAsideActiveRef" :class="themeConfig.columnsAsideStyle"></div>
</ul>
</el-scrollbar>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, ref, onMounted, nextTick, watch, onUnmounted, defineComponent } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface ColumnsAsideState {
columnsAsideList: any[];
liIndex: number;
liOldIndex: null | number;
liHoverIndex: null | number;
liOldPath: null | string;
difference: number;
routeSplit: string[];
}
export default defineComponent({
name: 'layoutColumnsAside',
setup() {
const columnsAsideOffsetTopRefs: any = ref([]);
const columnsAsideActiveRef = ref();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { routesList, isColumnsMenuHover, isColumnsNavHover } = storeToRefs(stores);
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const router = useRouter();
const state = reactive<ColumnsAsideState>({
columnsAsideList: [],
liIndex: 0,
liOldIndex: null,
liHoverIndex: null,
liOldPath: null,
difference: 0,
routeSplit: [],
});
// 设置菜单高亮位置移动
const setColumnsAsideMove = (k: number) => {
state.liIndex = k;
columnsAsideActiveRef.value.style.top = `${columnsAsideOffsetTopRefs.value[k].offsetTop + state.difference}px`;
};
// 菜单高亮点击事件
const onColumnsAsideMenuClick = (v: Object, k: number) => {
setColumnsAsideMove(k);
let { path, redirect } = v as any;
if (redirect) router.push(redirect);
else router.push(path);
};
// 鼠标移入时,显示当前的子级菜单
const onColumnsAsideMenuMouseenter = (v: RouteRecordRaw, k: number) => {
if (!themeConfig.value.isColumnsMenuHoverPreload) return false;
let { path } = v;
state.liOldPath = path;
state.liOldIndex = k;
state.liHoverIndex = k;
mittBus.emit('setSendColumnsChildren', setSendChildren(path));
stores.setColumnsMenuHover(false);
stores.setColumnsNavHover(true);
};
// 鼠标移走时,显示原来的子级菜单
const onColumnsAsideMenuMouseleave = async () => {
await stores.setColumnsNavHover(false);
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
setTimeout(() => {
if (!isColumnsMenuHover && !isColumnsNavHover) mittBus.emit('restoreDefault');
}, 100);
};
// 设置高亮动态位置
const onColumnsAsideDown = (k: number) => {
nextTick(() => {
setColumnsAsideMove(k);
});
};
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
state.columnsAsideList = filterRoutesFun(routesList.value);
const resData: any = setSendChildren(route.path);
if (Object.keys(resData).length <= 0) return false;
onColumnsAsideDown(resData.item[0].k);
mittBus.emit('setSendColumnsChildren', resData);
};
// 传送当前子级数据到菜单中
const setSendChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData: any = {};
state.columnsAsideList.map((v: any, k: number) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<string>) => {
return arr
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// tagsView 点击时,根据路由查找下标 columnsAsideList实现左侧菜单高亮
const setColumnsMenuHighlight = (path: string) => {
state.routeSplit = path.split('/');
state.routeSplit.shift();
const routeFirst = `/${state.routeSplit[0]}`;
const currentSplitRoute = state.columnsAsideList.find((v: any) => v.path === routeFirst);
if (!currentSplitRoute) return false;
// 延迟拿值,防止取不到
setTimeout(() => {
onColumnsAsideDown((<any>currentSplitRoute).k);
}, 0);
};
// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
watch(
pinia.state,
(val) => {
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
state.liHoverIndex = null;
mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
} else {
state.liHoverIndex = state.liOldIndex;
if (!state.liOldPath) return false;
mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
}
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
setFilterRoutes();
// 销毁变量,防止鼠标再次移入时,保留了上次的记录
mittBus.on('restoreDefault', () => {
state.liOldIndex = null;
state.liOldPath = null;
});
});
// 页面卸载时
onUnmounted(() => {
mittBus.off('restoreDefault', () => {});
});
// 路由更新时
onBeforeRouteUpdate((to) => {
setColumnsMenuHighlight(to.path);
mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
});
return {
themeConfig,
columnsAsideOffsetTopRefs,
columnsAsideActiveRef,
onColumnsAsideDown,
onColumnsAsideMenuClick,
onColumnsAsideMenuMouseenter,
onColumnsAsideMenuMouseleave,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-columns-aside {
width: 70px;
height: 100%;
background: var(--next-bg-columnsMenuBar);
ul {
position: relative;
.layout-columns-active {
color: var(--next-bg-columnsMenuBarColor) !important;
transition: 0.3s ease-in-out;
}
.layout-columns-hover {
color: var(--el-color-primary);
a {
color: var(--el-color-primary);
}
}
li {
color: var(--next-bg-columnsMenuBarColor);
width: 100%;
height: 50px;
text-align: center;
display: flex;
cursor: pointer;
position: relative;
z-index: 1;
&:hover {
@extend .layout-columns-hover;
}
.columns-vertical {
margin: auto;
.columns-vertical-title {
padding-top: 1px;
}
}
.columns-horizontal {
display: flex;
height: 50px;
width: 100%;
align-items: center;
padding: 0 5px;
i {
margin-right: 3px;
}
a {
display: flex;
.columns-horizontal-title {
padding-top: 1px;
}
}
}
a {
text-decoration: none;
color: var(--next-bg-columnsMenuBarColor);
}
}
.columns-round {
background: var(--el-color-primary);
color: var(--el-color-white);
position: absolute;
left: 50%;
top: 2px;
height: 44px;
width: 65px;
transform: translateX(-50%);
z-index: 0;
transition: 0.3s ease-in-out;
border-radius: 5px;
}
.columns-card {
@extend .columns-round;
top: 0;
height: 50px;
width: 100%;
border-radius: 0;
}
}
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<el-header class="layout-header" v-show="!isTagsViewCurrenFull">
<NavBarsIndex />
</el-header>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
export default defineComponent({
name: 'layoutHeader',
components: {
NavBarsIndex: defineAsyncComponent(() => import('/@/layout/navBars/index.vue')),
},
setup() {
const storesTagsViewRoutes = useTagsViewRoutes();
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
return {
isTagsViewCurrenFull,
};
},
});
</script>

Some files were not shown because too many files have changed in this diff Show More