Compare commits

...

69 Commits

Author SHA1 Message Date
p53209761 351a86ba4b Update README.md 2022-08-16 00:09:59 +08:00
p53209761 81b7a18773 Update README.md 2022-08-16 00:09:47 +08:00
Argo-Lenovo 760c83457a Revert "refactor: 移除 ICacheManager 服务代码"
This reverts commit 544441a5fc.
2022-07-22 09:30:51 +08:00
Argo-Lenovo c4eeda3ae8 db: 更新数据库 2022-07-20 14:17:08 +08:00
Argo-Lenovo 544441a5fc refactor: 移除 ICacheManager 服务代码 2022-07-20 14:09:15 +08:00
Argo-Lenovo 4686a75744 test: 更新依赖包 2022-07-20 14:06:50 +08:00
Argo-Lenovo 9be53cf009 chore: 更新依赖包到最新版 2022-07-20 14:06:43 +08:00
Argo-Lenovo 312f771313 chore: 更新依赖到最新版 2022-07-20 14:05:07 +08:00
Argo-Lenovo 234063b3bd refactor: 更新 form-footer 样式 2022-07-12 12:01:34 +08:00
Argo-Lenovo 43cb329757 refactor: 增加 await 关键字 2022-07-12 12:01:21 +08:00
Argo-Lenovo c0d21fcbb6 refactor: 增加 Key 关键字 2022-07-12 11:54:14 +08:00
Argo-Lenovo 994f439d40 refactor: 更新参数名更改 Text 为 DisplayText 2022-07-12 11:52:09 +08:00
Argo-Lenovo 8bf3f20fa5 test: 修复单元测试脚本错误 2022-07-11 11:43:30 +08:00
Argo-Lenovo c8329d5b84 chore: 更新项目过滤配置 2022-07-11 11:43:12 +08:00
Argo-Lenovo ea461e433e chore: 更新项目依赖包版本 2022-07-11 11:43:00 +08:00
Argo-Lenovo 213e9155a5 chore: 更新过滤项目配置 2022-07-11 11:28:46 +08:00
Argo-Lenovo e3b67c9427 feat: 菜单默认全部展开 2022-07-06 15:06:21 +08:00
Argo-Lenovo 35cc0f5174 feat: 支持树状结构 2022-07-06 15:06:08 +08:00
OneNewPerson 9343a9522e !106 fix TableTreeNode && freesql Trace实体映射
* fix TableTreeNode && freesql Trace实体映射
* Revert "fix TableTreeNode && freesql Trace实体映射"
* fix TableTreeNode && freesql Trace实体映射
2022-07-06 02:19:58 +00:00
Argo-Lenovo 026b1bb990 fix(#I543FE): 修复重复脚本问题
close https://gitee.com/LongbowEnterprise/BootstrapAdmin/issues/I543FE
2022-06-08 09:54:31 +08:00
Argo-Lenovo f030cb4be9 feat: Client 工程增加 DBManagerService 2022-06-02 12:18:05 +08:00
zhangpeihang 3f6834de5f refactor: 重构 菜单服务 2022-06-02 11:00:15 +08:00
Argo-Lenovo 08df8d8da0 refactor: 更新 LoginService 服务 2022-06-02 10:59:39 +08:00
zhangpeihang e9c5bd2c08 refactor: 重构 Role 服务 2022-06-02 10:57:50 +08:00
Argo-Lenovo 2ef69da9e1 refactor: 更新 GroupService 服务 2022-06-02 10:57:11 +08:00
Argo-Lenovo ed5790ff72 refactor: 更新 ExceptionService 服务 2022-06-02 10:54:45 +08:00
Argo-Lenovo 5d4de2c766 refactor: 更新代码 2022-06-02 10:53:08 +08:00
zhangpeihang 9a02d45e27 refactor: 重构 trace 服务 2022-06-02 10:51:57 +08:00
zhangpeihang ab6e260b6b refactor: 重构 User 服务 2022-06-02 10:51:57 +08:00
Argo-Lenovo 0e58b81d19 refactor: 更新 DictService 服务类 2022-06-02 10:43:40 +08:00
Argo-Lenovo 34e376d712 refactor: 更新 DefaultDataService 服务 2022-06-02 10:39:50 +08:00
Argo-Lenovo b96aab631f refactor: 更新 AppService 服务使用 DBManagerService 2022-06-02 10:17:15 +08:00
Argo-Lenovo 09bf567698 refactor: 更改命名空间 2022-06-02 10:13:18 +08:00
Argo-Lenovo 2481a4a577 feat: 增加 IDBManager 接口负责维护 IDatabase 2022-06-02 10:13:00 +08:00
Argo-Lenovo f7b2c059ef feat: 增加 Dummy 示例页面 2022-06-01 20:55:06 +08:00
Argo-Lenovo 7a95ddc039 feat: 增加 IDummy 示例服务 2022-06-01 18:49:53 +08:00
Argo-Lenovo 9dfd95ce41 feat: 增加多库服务支持 2022-06-01 18:49:30 +08:00
Argo-Lenovo 371d188d76 chore: 更新启动配置项 2022-06-01 17:56:54 +08:00
Argo-Lenovo 134d0ee8a8 feat: Client 增加 Table 自定义组件 2022-06-01 17:23:49 +08:00
Argo-Lenovo 4995899c4c chore: 更改 Client 配置 2022-06-01 17:19:28 +08:00
Argo-Lenovo 6ccbadd1ea chore: 更新配置文件 2022-06-01 17:02:34 +08:00
Argo-Lenovo 8c3999c265 refactor: 增加 Token 参数消除警告信息 2022-04-28 12:05:53 +08:00
Nine c78fbcf87c !103 feat(#I54RIV): 完善 FreeSql 支持
* refactor: 改为 petapoco 服务
* feat: 完善 Freesql
2022-04-26 10:35:21 +00:00
Argo 89b9bf7e3a !102 perf(#I50WR9): 优化 NavigationTree 逻辑提高性能
* refactor: 增加内置滚动条样式
* refactor: 优化 NavigationTree 性能
2022-04-02 12:49:54 +00:00
Argo-Tianyi c516a0064d refactor: 更新代码 2022-03-31 09:48:33 +08:00
Argo-Tianyi 13e5b282e9 refactor: 精简代码 2022-03-26 11:14:21 +08:00
Argo-Tianyi 3f9555273b feat: 增加模拟账户识别 2022-03-14 09:43:10 +08:00
Argo-Tianyi 83524b4acc feat: Client 工程默认设置为中文 2022-03-14 09:36:05 +08:00
Argo-Tianyi eb6abb32fe feat: 更新数据库配置防止出现两个首页 2022-03-14 09:35:48 +08:00
Argo-Tianyi 013c05c740 feat: Client 工程默认增加固定标签页功能 2022-03-14 09:26:34 +08:00
Argo-Tianyi 5a1b8602dd feat: 默认固定标签页 2022-03-13 18:31:41 +08:00
Argo-Tianyi 399fb4a700 chore: 增加全局引用 2022-03-13 18:30:26 +08:00
Argo-Tianyi 05cc1f549c feat: 更新依赖包 2022-03-07 18:24:05 +08:00
zhangpeihang 6fa2a7e2f1 fix: 修复 EFCore 创建用户时不创建默认角色 2022-02-28 21:24:18 +08:00
Argo-Tianyi 65d9d4563a feat: 完善菜单维护父级菜单功能 2022-02-28 19:37:27 +08:00
Argo-Tianyi 4a468f9186 style: 侧边栏增加竖线分割 2022-01-30 10:00:14 +08:00
Argo-Lenovo e4b2f70ae3 feat: 增加任务日志滚动条样式 2022-01-29 18:39:54 +08:00
Argo-Lenovo 41b0dce852 feat: 任务日志增加最大数功能 2022-01-29 16:03:57 +08:00
Argo-Lenovo 188470bc1d feat: 增加任务日志功能 2022-01-29 15:26:44 +08:00
Argo-Lenovo 92e3aa4c35 feat: 实现缓存清楚逻辑 2022-01-29 14:34:00 +08:00
Argo-Tianyi 5e136d1815 fix: 修复 Client 工程编译报错问题 2022-01-28 12:24:49 +08:00
Argo-Tianyi 7506665052 feat: 登录首页增加切换功能 2022-01-27 16:00:21 +08:00
Argo-Tianyi fc2f0e84b2 feat: 增加 AdminLoginFooter 组件用于切换登录页 2022-01-27 15:43:29 +08:00
Argo-Tianyi 6ba2ef3a96 feat: 增加码云高仿登录界面 2022-01-27 15:14:09 +08:00
Argo-Tianyi 2674c6278d feat: 增加登录首页切换功能 2022-01-27 13:42:48 +08:00
Argo-Tianyi 6e881fbff4 feat: 登录首页配置增加顺序 2022-01-27 13:31:59 +08:00
Argo-Tianyi 135f0615f5 refactor: 改造登录首页组件 2022-01-27 13:31:37 +08:00
Argo-Tianyi a53c0cd791 feat: 增加更新 DisplayName 逻辑 2022-01-27 12:53:36 +08:00
Argo-Tianyi 71d5ff0a24 chore: 更新依赖包 2022-01-27 12:53:18 +08:00
113 changed files with 2493 additions and 1059 deletions

View File

@ -11,10 +11,7 @@
"src\\blazor\\client\\BootstrapClient.Shared\\BootstrapClient.Web.Shared.csproj",
"src\\blazor\\client\\BootstrapClient.Web.Core\\BootstrapClient.Web.Core.csproj",
"src\\blazor\\client\\BootstrapClient.Web.Models\\BootstrapClient.DataAccess.Models.csproj",
"src\\blazor\\client\\BootstrapClient.Web\\BootstrapClient.Web.csproj",
"src\\mvc\\admin\\Bootstrap.Admin\\Bootstrap.Admin.csproj",
"src\\mvc\\admin\\Bootstrap.DataAccess\\Bootstrap.DataAccess.csproj",
"test\\UnitTest\\UnitTest.csproj"
"src\\blazor\\client\\BootstrapClient.Web\\BootstrapClient.Web.csproj"
]
}
}

View File

@ -157,7 +157,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapClient.Web", "src\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootStarpAdmin.DataAccess.FreeSql", "src\blazor\admin\BootStarpAdmin.DataAccess.FreeSql\BootStarpAdmin.DataAccess.FreeSql.csproj", "{11122D97-B349-4A3E-B7DD-73B8B363C47C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BootStarpAdmin.DataAccess.SqlSugar", "src\blazor\admin\BootStarpAdmin.DataAccess.SqlSugar\BootStarpAdmin.DataAccess.SqlSugar.csproj", "{1D20E6CF-9825-4CDE-B732-AE586BD1AABA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootStarpAdmin.DataAccess.SqlSugar", "src\blazor\admin\BootStarpAdmin.DataAccess.SqlSugar\BootStarpAdmin.DataAccess.SqlSugar.csproj", "{1D20E6CF-9825-4CDE-B732-AE586BD1AABA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BootstrapClient.Web.Core", "src\blazor\client\BootstrapClient.Web.Core\BootstrapClient.Web.Core.csproj", "{FFDF9FF9-0B29-47D3-AD42-53A476B570EC}"
EndProject

184
README.md
View File

@ -1,178 +1,8 @@
# BootstrapAdmin
# 第一点
## 部署手册
<span>English</span> | <a href="README.zh-CN.md">中文</a>
---
##### Version & Coverage
[![Release](https://img.shields.io/endpoint.svg?logo=Groupon&logoColor=red&color=green&label=release&url=https://admin.blazor.zone/api/Gitee/Releases?userName=LongbowEnterprise)](https://gitee.com/LongbowEnterprise/BootstrapAdmin/releases)
[![Coveralls](https://img.shields.io/coveralls/github/ArgoZhang/BootstrapAdmin/master.svg?logo=ReverbNation&logoColor=green&label=coveralls)](https://coveralls.io/github/ArgoZhang/BootstrapAdmin)
[![Codecov](https://img.shields.io/codecov/c/gh/argozhang/bootstrapadmin/master.svg?logo=codecov&label=codecov)](https://codecov.io/gh/argozhang/bootstrapadmin/branch/master)
##### Gitee
[![Appveyor build](https://img.shields.io/endpoint.svg?logo=appveyor&label=build&color=blueviolet&url=https://admin.blazor.zone/api/Gitee/Builds?projName=bootstrapadmin-9m1jm)](https://ci.appveyor.com/project/ArgoZhang/bootstrapadmin-9m1jm)
[![Build Status](https://img.shields.io/appveyor/ci/ArgoZhang/bootstrapadmin-9m1jm/master.svg?logo=appveyor&label=master)](https://ci.appveyor.com/project/ArgoZhang/bootstrapadmin-9m1jm/branch/master)
[![Test](https://img.shields.io/appveyor/tests/ArgoZhang/bootstrapadmin-9m1jm/master.svg?logo=appveyor&)](https://ci.appveyor.com/project/ArgoZhang/bootstrapadmin-9m1jm/branch/master/tests)
[![Issue Status](https://img.shields.io/endpoint.svg?logo=Groupon&logoColor=critical&label=issues&url=https://admin.blazor.zone/api/Gitee/Issues?userName=LongbowEnterprise)](https://gitee.com/LongbowEnterprise/BootstrapAdmin/issues)
[![Pull Status](https://img.shields.io/endpoint.svg?logo=Groupon&logoColor=green&color=success&label=pulls&url=https://admin.blazor.zone/api/Gitee/Pulls?userName=LongbowEnterprise)](https://gitee.com/LongbowEnterprise/BootstrapAdmin/pulls)
##### GitHub
[![Appveyor build](https://img.shields.io/endpoint.svg?logo=appveyor&label=build&color=blueviolet&url=https://admin.blazor.zone/api/Gitee/Builds?projName=bootstrapadmin)](https://ci.appveyor.com/project/ArgoZhang/bootstrapadmin)
[![master status](https://img.shields.io/appveyor/ci/ArgoZhang/bootstrapadmin/master.svg?logo=appveyor&label=master)](https://ci.appveyor.com/project/ArgoZhang/bootstrapadmin/branch/master)
[![Test](https://img.shields.io/appveyor/tests/argozhang/bootstrapadmin/master.svg?logo=appveyor&)](https://ci.appveyor.com/project/ArgoZhang/bootstrapadmin/branch/master/tests)
[![Github build](https://img.shields.io/github/workflow/status/ArgoZhang/BootstrapAdmin/Auto%20Build%20CI/master?label=master&logoColor=green&logo=github)](https://github.com/ArgoZhang/BootstrapAdmin/actions?query=workflow%3A%22Auto+Build+CI%22+branch%3Amaster)
[![Repo Size](https://img.shields.io/github/repo-size/ArgoZhang/BootstrapAdmin.svg?logo=github&logoColor=green&label=repo)](https://github.com/ArgoZhang/BootstrapAdmin)
[![Commit Date](https://img.shields.io/github/last-commit/ArgoZhang/BootstrapAdmin/master.svg?logo=github&logoColor=green&label=commit)](https://github.com/ArgoZhang/BootstrapAdmin)
## Introduce
Because the dependent on Bootstrap v4, so it is called **Bootstrap Admin**. This system can be integrated with asp.net and asp.net core applications. The database supports multiple databases at the same time. The detailed list is shown in the following **database** detailed list. Switching the data source only needs to change the configuration file without restarting the application. The configuration is simple and flexible. The UI front-end uses the popular Bootstrap framework layout, which is very compatible with mobile devices and adapts to almost all terminal devices in the current market. The system also has the feature of single background supporting multi-front desk, and provides the ability of **single sign-on (SSO)**.
### Notes
Bootstrap Admin does not require secondary development, but only integration with the front-end system. The front-end system model project is **Bootstrap. Client**
The original starting point of the project is to separate the privilege system from the business system. The project development focuses on functions. For detailed configuration instructions, please click on [View Documents](https://gitee.com/LongbowEnterprise/BootstrapAdmin/wikis/%E7%B3%BB%E7%BB%9F%E9%89B%86%E6%88%90).
### Features
1. Integration with Front-end Website through Configuration
2. Constructing Hierarchical Menu of Front-end System
3. Provide a single background to support multiple front-end application configurations
4. Provide single sign-on(SSO)
5. Integrated System Authentication and Authorization Module
6. Provide role, department, user, menu, foreground application authorization
Role Authorization to Users
Role-to-Menu Authorization
Role Authorization to Departments
Role-to-application authorization (multiple front-end applications share a back-end privilege management system)
Departments Authorize Users
7. Provide dictionary tables for personalized configuration of front-end websites
8. Fully responsive layout (supporting all mainstream devices such as computers, tablets, mobile phones, etc.)
9. Built-in multi-data source support, simple configuration and immediate effect without restart
10. Built-in data memory caching mechanism, page fast response
11. Built-in Data **Operation Log** and User **Log on**
[Update log](https://gitee.com/LongbowEnterprise/BootstrapAdmin/wikis/更新日志)
### Advantage
1. The front-end system does not need to write login, authorization and authentication modules; it is only responsible for writing business modules.
2. Background system can be used directly without any secondary development.
3. Front-end and back-end systems are separated, which are different systems (domain name can be independent)
4. Extensible to multi-tenant applications
For more information, please click [wiki](https://gitee.com/LongbowEnterprise/BootstrapAdmin/wikis/%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D)
### Database
**MSSQL/Oracle/SQLite/MySql/MariaDB/Firebird/MongoDB**
For more information, please click [wiki](https://gitee.com/LongbowEnterprise/BootstrapAdmin/wikis/数据库连接配置?sort_id=1333482)
### Browser
![chrome](https://img.shields.io/badge/chrome->%3D4.5-success.svg?logo=google%20chrome&logoColor=red)
![firefox](https://img.shields.io/badge/firefox->38-success.svg?logo=mozilla%20firefox&logoColor=red)
![edge](https://img.shields.io/badge/edge->%3D12-success.svg?logo=microsoft%20edge&logoColor=blue)
![ie](https://img.shields.io/badge/ie->%3D11-success.svg?logo=internet%20explorer&logoColor=blue)
![Safari](https://img.shields.io/badge/safari->%3D9-success.svg?logo=safari&logoColor=blue)
![Andriod](https://img.shields.io/badge/andriod->%3D4.4-success.svg?logo=android)
![oper](https://img.shields.io/badge/opera->%3D3.0-success.svg?logo=opera&logoColor=red)
```json
"browserslist": [
"Chrome >= 45",
"Firefox >= 38",
"Edge >= 12",
"Explorer >= 11",
"iOS >= 9",
"Safari >= 9",
"Android >= 4.4",
"Opera >= 30"
]
```
### Mobile
![ios](https://img.shields.io/badge/ios-supported-success.svg?logo=apple&logoColor=white)
![Andriod](https://img.shields.io/badge/andriod-suported-success.svg?logo=android)
![windows](https://img.shields.io/badge/windows-suported-success.svg?logo=windows&logoColor=blue)
| | **Chrome** | **Firefox** | **Safari** | **Android Browser & WebView** | **Microsoft Edge** |
| ------- | --------- | --------- | ------ | ------------------------- | -------------- |
| **iOS** | Supported | Supported | Supported | N/A | Supported |
| **Android** | Supported | Supported | N/A | Android v5.0+ supported | Supported |
| **Windows 10 Mobile** | N/A | N/A | N/A | N/A | Supported |
### Desktop
![macOS](https://img.shields.io/badge/macOS-supported-success.svg?logo=apple&logoColor=white)
![linux](https://img.shields.io/badge/linux-suported-success.svg?logo=linux&logoColor=white)
![windows](https://img.shields.io/badge/windows-suported-success.svg?logo=windows)
| | Chrome | Firefox | Internet Explorer | Microsoft Edge | Opera | Safari |
| ------- | --------- | --------- | ----------------- | -------------- | --------- | ------------- |
| Mac | Supported | Supported | N/A | N/A | Supported | Supported |
| Linux | Supported | Supported | N/A | N/A | N/A | N/A |
| Windows | Supported | Supported | Supported, IE10+ | Supported | Supported | Not supported |
## QQ Group
[![QQ](https://img.shields.io/badge/QQ-795206915-green.svg?logo=tencent%20qq&logoColor=red)](https://shang.qq.com/wpa/qunwpa?idkey=d381355e50ff91db410c3da3eadb081ba859f64c2877e86343f4709b171f28b8)
## Installation
1. Install .net core sdk [Offical website](http://www.microsoft.com/net/download)
2. Install Visual Studio 2019 lastest [Offical website](https://visualstudio.microsoft.com/vs/getting-started/)
3. Git clone `git clone https://gitee.com/LongbowEnterprise/BootstrapAdmin.git`
4. Login as Admin/123789
## Branchs
[Details](https://gitee.com/LongbowEnterprise/BootstrapAdmin/wikis/分支说明)
## Online Demonstration
[![website1](https://img.shields.io/badge/linux-http://ba.zylweb.cn-success.svg?logo=buzzfeed&logoColor=green)](http://ba.zylweb.cn)
[![website2](https://img.shields.io/badge/linux-http://admin.blazor.zone-success.svg?logo=buzzfeed&logoColor=green)](http://admin.blazor.zone)
### Login
Administrator: Admin/123789
User: User/123789
## Docker Images
[![Docker](https://img.shields.io/docker/cloud/automated/argozhang/ba.svg?logo=docker&logoColor=success)](https://hub.docker.com/r/argozhang/ba)
[![Docker](https://img.shields.io/docker/cloud/build/argozhang/ba.svg?logo=docker&logoColor=success)](https://hub.docker.com/r/argozhang/ba/builds)
[![Docker](https://img.shields.io/github/workflow/status/ArgoZhang/BootstrapAdmin/Docker%20Image%20CI/master?label=Docker%20Image%20CI&logo=github&logoColor=green)](https://github.com/ArgoZhang/BootstrapAdmin/actions?query=workflow%3A%22Docker+Image+CI%22%3Amaster)
### Docker Hub
Mirror [Portal](https://hub.docker.com/r/argozhang/ba)
```bash
docker pull argozhang/ba
```
### Qiniu Cloud:
Mirror [Portal](https://hub.qiniu.com/store/argozhang/ba)
```bash
docker pull reg.qiniu.com/argozhang/ba
```
## Configurations
Detailed configuration instructions please click [wikis](https://gitee.com/LongbowEnterprise/BootstrapAdmin/wikis)
## Q&A
Please click [wikis](https://gitee.com/LongbowEnterprise/BootstrapAdmin/wikis/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98Q&A)
## License
[![Gitee license](https://img.shields.io/github/license/argozhang/bootstrapadmin.svg?logo=git&logoColor=red)](https://gitee.com/LongbowEnterprise/BootstrapAdmin/blob/master/LICENSE)
## GVP award
[View](https://images.gitee.com/uploads/images/2021/0112/112021_9d570be1_554725.png "GiteeGVP.png")
## Screenshots
Home
![Home](https://gitee.com/LongbowEnterprise/Pictures/raw/master/BootstrapAdmin/BA02-01.png "BAHome-01.png")
For more screenshots, Click [wiki](https://gitee.com/LongbowEnterprise/BootstrapAdmin/wikis)
## Contribution
1. Fork
2. Create Feat_xxx branch
3. Commit
4. Create Pull Request
## Donate
If this project is helpful to you, please scan the QR code below for a cup of coffee.
![WeChat](https://gitee.com/LongbowEnterprise/Pictures/raw/master/WeChat/WeChat.png "WeChat")
# 第二点
## 环境要求
# 第三点
## 软件要求

View File

@ -143,16 +143,6 @@ CREATE TABLE Tasks(
AssignTime DATE NOT NULL
);
CREATE TABLE RejectUsers(
ID SERIAL PRIMARY KEY,
UserName VARCHAR (50) NOT NULL,
DisplayName VARCHAR (50) NOT NULL,
RegisterTime DATE NOT NULL,
RejectedBy VARCHAR (50) NOT NULL,
RejectedTime DATE NOT NULL,
RejectedReason VARCHAR (50) NULL
);
CREATE TABLE RejectUsers(
ID SERIAL PRIMARY KEY,
UserName VARCHAR (50) NOT NULL,

View File

@ -6,8 +6,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor" Version="6.2.7" />
<PackageReference Include="FreeSql.Provider.Sqlite" Version="3.0.100" />
<PackageReference Include="BootstrapBlazor" Version="6.8.13" />
<PackageReference Include="FreeSql.Provider.Sqlite" Version="3.2.665" />
</ItemGroup>
</Project>

View File

@ -24,6 +24,10 @@ static class FreeSqlExtensions
i.Property(n => n.Period).IsIgnore(true);
i.Property(n => n.IsReset).IsIgnore(true);
});
freeSql.CodeFirst.ConfigEntity<Trace>(i =>
{
i.Name("Traces");
});
freeSql.CodeFirst.ConfigEntity<Group>(i =>
{
i.Name("Groups");

View File

@ -24,6 +24,9 @@ public static class ServiceCollectionExtensions
/// <returns></returns>
public static IServiceCollection AddFreeSql(this IServiceCollection services, Action<IServiceProvider, FreeSqlBuilder> freeSqlBuilder)
{
// 增加缓存服务
services.AddCacheManager();
services.TryAddSingleton<IFreeSql>(provider =>
{
var builder = new FreeSqlBuilder();
@ -45,6 +48,7 @@ public static class ServiceCollectionExtensions
services.AddSingleton<INavigation, NavigationService>();
services.AddSingleton<IRole, RoleService>();
services.AddSingleton<IUser, UserService>();
services.AddSingleton<ITrace, TraceService>();
return services;
}
}

View File

@ -7,6 +7,7 @@ using BootstrapAdmin.DataAccess.Models;
using BootstrapAdmin.Web.Core;
using Longbow.Security.Cryptography;
using Microsoft.Extensions.Configuration;
using System.Data;
namespace BootStarpAdmin.DataAccess.FreeSql.Service;
@ -175,23 +176,284 @@ class DictService : IDict
return url;
}
public bool SavDefaultApp(bool enabled)
public bool SavDefaultApp(bool enabled) => SaveDict(new Dict
{
throw new NotImplementedException();
}
Category = "网站设置",
Name = "默认应用程序",
Code = enabled ? "1" : "0",
Define = EnumDictDefine.System
});
public bool GetEnableDefaultApp()
{
throw new NotImplementedException();
var dicts = GetAll();
var code = dicts.FirstOrDefault(d => d.Category == "网站设置" && d.Name == "默认应用程序")?.Code ?? "0";
return code == "1";
}
public string GetIconFolderPath()
{
throw new NotImplementedException();
var dicts = GetAll();
return dicts.FirstOrDefault(d => d.Name == "头像路径" && d.Category == "头像地址" && d.Define == EnumDictDefine.System)?.Code ?? "/images/uploder/";
}
public string GetDefaultIcon()
{
throw new NotImplementedException();
var dicts = GetAll();
return dicts.FirstOrDefault(d => d.Name == "头像文件" && d.Category == "头像地址" && d.Define == EnumDictDefine.System)?.Code ?? "default.jpg";
}
public string? GetIpLocatorName()
{
var dicts = GetAll();
return dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "IP地理位置接口" && s.Define == EnumDictDefine.System)?.Code;
}
public string? GetIpLocatorUrl(string? name)
{
var dicts = GetAll();
return string.IsNullOrWhiteSpace(name) ? null : dicts.FirstOrDefault(s => s.Category == "地理位置" && s.Name == name && s.Define == EnumDictDefine.System)?.Code;
}
public bool GetAppSiderbar()
{
var dicts = GetAll();
return dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "侧边栏状态" && s.Define == EnumDictDefine.System)?.Code == "1";
}
public bool SaveAppSiderbar(bool value) => SaveDict(new Dict { Category = "网站设置", Name = "侧边栏状态", Code = value ? "1" : "0" });
public bool GetAppTitle()
{
var dicts = GetAll();
return dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "卡片标题状态" && s.Define == EnumDictDefine.System)?.Code == "1";
}
public bool SaveAppTitle(bool value) => SaveDict(new Dict { Category = "网站设置", Name = "卡片标题状态", Code = value ? "1" : "0" });
public bool GetAppFixHeader()
{
var dicts = GetAll();
return dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "固定表头" && s.Define == EnumDictDefine.System)?.Code == "1";
}
public bool SaveAppFixHeader(bool value) => SaveDict(new Dict { Category = "网站设置", Name = "固定表头", Code = value ? "1" : "0" });
public bool GetAppHealthCheck()
{
var dicts = GetAll();
return dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "健康检查" && s.Define == EnumDictDefine.System)?.Code == "1";
}
public bool SaveAppHealthCheck(bool value) => SaveDict(new Dict { Category = "网站设置", Name = "健康检查", Code = value ? "1" : "0" });
public bool GetAppMobileLogin()
{
var dicts = GetAll();
return dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "短信验证码登录" && s.Define == EnumDictDefine.System)?.Code == "1";
}
public bool SaveAppMobileLogin(bool value) => SaveDict(new Dict { Category = "网站设置", Name = "短信验证码登录", Code = value ? "1" : "0" });
public bool GetAppOAuthLogin()
{
var dicts = GetAll();
return dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "OAuth 认证登录" && s.Define == EnumDictDefine.System)?.Code == "1";
}
public bool SaveAppOAuthLogin(bool value) => SaveDict(new Dict { Category = "网站设置", Name = "OAuth 认证登录", Code = value ? "1" : "0" });
public bool GetAutoLockScreen()
{
var dicts = GetAll();
return dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "自动锁屏" && s.Define == EnumDictDefine.System)?.Code == "1";
}
public bool SaveAutoLockScreen(bool value) => SaveDict(new Dict { Category = "网站设置", Name = "自动锁屏", Code = value ? "1" : "0" });
public int GetAutoLockScreenInterval()
{
var dicts = GetAll();
var value = dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "自动锁屏时长" && s.Define == EnumDictDefine.System)?.Code ?? "0";
_ = int.TryParse(value, out var ret);
return ret;
}
public bool SaveAutoLockScreenInterval(int value) => SaveDict(new Dict { Category = "网站设置", Name = "自动锁屏时长", Code = value.ToString() });
public Dictionary<string, string> GetIpLocators()
{
var dicts = GetAll();
return dicts.Where(d => d.Category == "地理位置服务").Select(d => new KeyValuePair<string, string>(d.Code, d.Name)).OrderBy(i => i.Value).ToDictionary(i => i.Key, i => i.Value);
}
public string? GetIpLocator()
{
var dicts = GetAll();
return dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "IP地理位置接口" && s.Define == EnumDictDefine.System)?.Code;
}
public bool SaveCurrentIp(string value) => SaveDict(new Dict { Category = "网站设置", Name = "IP地理位置接口", Code = value });
public int GetCookieExpired()
{
var dicts = GetAll();
var value = dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "Cookie保留时长" && s.Define == EnumDictDefine.System)?.Code ?? "0";
_ = int.TryParse(value, out var ret);
return ret;
}
public bool SaveCookieExpired(int value) => SaveDict(new Dict { Category = "网站设置", Name = "Cookie保留时长", Code = value.ToString() });
public int GetExceptionExpired()
{
var dicts = GetAll();
var value = dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "程序异常保留时长" && s.Define == EnumDictDefine.System)?.Code ?? "0";
_ = int.TryParse(value, out var ret);
return ret;
}
public bool SaveExceptionExpired(int value) => SaveDict(new Dict { Category = "网站设置", Name = "程序异常保留时长", Code = value.ToString() });
public int GetOperateExpired()
{
var dicts = GetAll();
var value = dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "操作日志保留时长" && s.Define == EnumDictDefine.System)?.Code ?? "0";
_ = int.TryParse(value, out var ret);
return ret;
}
public bool SaveOperateExpired(int value) => SaveDict(new Dict { Category = "网站设置", Name = "操作日志保留时长", Code = value.ToString() });
public int GetLoginExpired()
{
var dicts = GetAll();
var value = dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "登录日志保留时长" && s.Define == EnumDictDefine.System)?.Code ?? "0";
_ = int.TryParse(value, out var ret);
return ret;
}
public bool SaveLoginExpired(int value) => SaveDict(new Dict { Category = "网站设置", Name = "登录日志保留时长", Code = value.ToString() });
public int GetAccessExpired()
{
var dicts = GetAll();
var value = dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "访问日志保留时长" && s.Define == EnumDictDefine.System)?.Code ?? "0";
_ = int.TryParse(value, out var ret);
return ret;
}
public bool SaveAccessExpired(int value) => SaveDict(new Dict { Category = "网站设置", Name = "访问日志保留时长", Code = value.ToString() });
public int GetIPCacheExpired()
{
var dicts = GetAll();
var value = dicts.FirstOrDefault(s => s.Category == "网站设置" && s.Name == "IP请求缓存时长" && s.Define == EnumDictDefine.System)?.Code ?? "0";
_ = int.TryParse(value, out var ret);
return ret;
}
public bool SaveIPCacheExpired(int value) => SaveDict(new Dict { Category = "网站设置", Name = "IP请求缓存时长", Code = value.ToString() });
public Dictionary<string, string>? GetClients()
{
var dicts = GetAll();
return dicts.Where(s => s.Category == "应用程序" && s.Code != "BA").ToDictionary(s => s.Name, s => s.Code);
}
public string GetClientUrl(string name)
{
var dicts = GetAll();
return dicts.Where(s => s.Category == "应用首页" && s.Name == name).FirstOrDefault()?.Code ?? "";
}
public bool ExistsAppId(string appId)
{
var dicts = GetAll();
return dicts.Exists(s => s.Category == "应用程序" && s.Code == appId);
}
public bool SaveClient(ClientApp client)
{
var ret = false;
if (!string.IsNullOrEmpty(client.AppId))
{
DeleteClient(client.AppId);
try
{
var items = new List<Dict>()
{
new Dict { Category = "应用程序", Name = client.AppName, Code = client.AppId, Define = EnumDictDefine.System },
new Dict { Category = "应用首页", Name = client.AppId, Code = client.HomeUrl, Define = EnumDictDefine.System },
new Dict { Category = client.AppId, Name = "网站页脚", Code = client.Footer, Define = EnumDictDefine.Customer },
new Dict { Category = client.AppId, Name = "网站标题", Code = client.Title, Define = EnumDictDefine.Customer },
new Dict { Category = client.AppId, Name = "favicon", Code = client.Favicon, Define = EnumDictDefine.Customer },
new Dict { Category = client.AppId, Name = "网站图标", Code = client.Icon, Define = EnumDictDefine.Customer },
new Dict { Category = client.AppId, Name = "个人中心地址", Code = client.ProfileUrl, Define = EnumDictDefine.Customer },
new Dict { Category = client.AppId, Name = "系统设置地址", Code = client.SettingsUrl, Define = EnumDictDefine.Customer },
new Dict { Category = client.AppId, Name = "系统通知地址", Code = client.NotificationUrl, Define = EnumDictDefine.Customer }
};
ret = FreeSql.Insert(items).ExecuteAffrows() > 0;
}
catch
{
throw;
}
}
return ret;
}
public ClientApp GetClient(string appId)
{
var dicts = GetAll();
return new ClientApp()
{
AppId = appId,
AppName = dicts.FirstOrDefault(s => s.Category == "应用程序" && s.Code == appId)?.Name,
HomeUrl = dicts.FirstOrDefault(s => s.Category == "应用首页" && s.Name == appId)?.Code,
ProfileUrl = dicts.FirstOrDefault(s => s.Category == appId && s.Name == "个人中心地址")?.Code,
SettingsUrl = dicts.FirstOrDefault(s => s.Category == appId && s.Name == "系统设置地址")?.Code,
NotificationUrl = dicts.FirstOrDefault(s => s.Category == appId && s.Name == "系统通知地址")?.Code,
Title = dicts.FirstOrDefault(s => s.Category == appId && s.Name == "网站标题")?.Code,
Footer = dicts.FirstOrDefault(s => s.Category == appId && s.Name == "网站页脚")?.Code,
Icon = dicts.FirstOrDefault(s => s.Category == appId && s.Name == "网站图标")?.Code,
Favicon = dicts.FirstOrDefault(s => s.Category == appId && s.Name == "favicon")?.Code,
};
}
public bool DeleteClient(string appId)
{
bool ret;
try
{
FreeSql.Transaction(() =>
{
FreeSql.Ado.ExecuteNonQuery("delete Dicts where Category=@Category and Name=@Name and Define=@Define", new { Category = "应用首页", Name = appId, Define = EnumDictDefine.System });
FreeSql.Ado.ExecuteNonQuery("delete Dicts where Category=@Category and Code=@Code and Define=@Define", new { Category = "应用程序", Code = appId, Define = EnumDictDefine.System });
FreeSql.Ado.ExecuteNonQuery("delete Dicts where Category=@Category and Name in (@Names)", new
{
Category = appId,
Names = new List<string>
{
"网站标题",
"网站页脚",
"favicon",
"网站图标",
"个人中心地址",
"系统设置地址",
"系统通知地址"
}
});
});
ret = true;
}
catch (Exception)
{
throw;
}
return ret;
}
}

View File

@ -2,14 +2,31 @@
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapAdmin.DataAccess.Models;
using BootstrapAdmin.Web.Core;
namespace BootStarpAdmin.DataAccess.FreeSql.Service;
class LoginService : ILogin
{
public Task<bool> Log(string userName, bool result)
private IFreeSql FreeSql { get; }
public LoginService(IFreeSql freeSql) => FreeSql = freeSql;
public bool Log(string userName, string? IP, string? OS, string? browser, string? address, string? userAgent, bool result)
{
return Task.FromResult(true);
var loginUser = new LoginLog()
{
UserName = userName,
LoginTime = DateTime.Now,
Ip = IP,
City = address,
OS = OS,
Browser = browser,
UserAgent = userAgent,
Result = result ? "登录成功" : "登录失败"
};
FreeSql.Insert(loginUser).ExecuteAffrows();
return true;
}
}

View File

@ -0,0 +1,84 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapAdmin.DataAccess.Models;
using BootstrapAdmin.Web.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BootStarpAdmin.DataAccess.FreeSql.Service;
/// <summary>
///
/// </summary>
public class TraceService : ITrace
{
private IFreeSql FreeSql { get; }
/// <summary>
///
/// </summary>
/// <param name="freeSql"></param>
public TraceService(IFreeSql freeSql) => FreeSql = freeSql;
/// <summary>
///
/// </summary>
/// <param name="searchText"></param>
/// <param name="filter"></param>
/// <param name="pageIndex"></param>
/// <param name="pageItems"></param>
/// <param name="sortList"></param>
/// <returns></returns>
public (IEnumerable<Trace> Items, int ItemsCount) GetAll(string? searchText, TraceFilter filter, int pageIndex, int pageItems, List<string> sortList)
{
var items = FreeSql.Select<Trace>();
if (!string.IsNullOrEmpty(searchText))
{
items.Where("UserName Like @searchText or Ip Like @searchText or RequestUrl Like @searchText", new { searchText });
}
if (!string.IsNullOrEmpty(filter.UserName))
{
items.Where("UserName Like @UserName", new { filter.UserName });
}
if (!string.IsNullOrEmpty(filter.Ip))
{
items.Where("Ip Like @Ip", new { filter.Ip });
}
if (!string.IsNullOrEmpty(filter.RequestUrl))
{
items.Where("ErrorPage Like @RequestUrl", new { filter.RequestUrl });
}
items.Where("LogTime >= @0 and LogTime <= @1", new { filter.Star, filter.End });
if (sortList.Any())
{
items.OrderBy(string.Join(", ", sortList));
}
else
{
items.OrderBy("Logtime desc");
}
var traces = items.Count(out var count).Page(pageIndex, pageItems).ToList();
return (traces, Convert.ToInt32(count));
}
/// <summary>
///
/// </summary>
/// <param name="trace"></param>
public void Log(Trace trace)
{
FreeSql.Insert(trace).ExecuteAffrows();
}
}

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="BootstrapBlazor" Version="6.2.7" />
<PackageReference Include="SqlSugarCore" Version="5.0.5.2" />
<PackageReference Include="BootstrapBlazor" Version="6.8.13" />
<PackageReference Include="SqlSugarCore" Version="5.0.9.7" />
</ItemGroup>
<ItemGroup>

View File

@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
</ItemGroup>
</Project>

View File

@ -71,9 +71,9 @@ class DefaultCacheManager : ICacheManager
{
Cache.Remove(key);
}
else
else if (Cache is MemoryCache c)
{
//Cache.Compact(100);
c.Compact(100);
}
}
}

View File

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="BootstrapBlazor" Version="6.2.9-beta06" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
<PackageReference Include="BootstrapBlazor" Version="6.8.13" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.7" />
</ItemGroup>
<ItemGroup>

View File

@ -306,6 +306,7 @@ public class UserService : IUser
Password = pwd
};
dbcontext.Add(user);
ret = dbcontext.SaveChanges() > 0;
// 授权 Default 角色
dbcontext.Database.ExecuteSqlRaw("insert into UserRole (UserID, RoleID) select ID, (select ID from Roles where RoleName = 'Default') RoleId from Users where UserName = {0}", userName);
ret = dbcontext.SaveChanges() > 0;

View File

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="BootstrapBlazor" Version="6.2.9-beta06" />
<PackageReference Include="Longbow.Security.Cryptography" Version="5.2.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.1" />
<PackageReference Include="BootstrapBlazor" Version="6.8.13" />
<PackageReference Include="Longbow.Security.Cryptography" Version="6.0.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.7" />
<PackageReference Include="PetaPoco.Extensions" Version="6.0.0" />
</ItemGroup>

View File

@ -6,7 +6,6 @@ using BootstrapAdmin.DataAccess.PetaPoco;
using BootstrapAdmin.DataAccess.PetaPoco.Services;
using BootstrapAdmin.Web.Core;
using BootstrapBlazor.Components;
using BootstrapBlazor.DataAcces.PetaPoco.Services;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
@ -27,43 +26,10 @@ public static class ServiceCollectionExtensions
///
/// </summary>
/// <param name="services"></param>
/// <param name="builder"></param>
/// <returns></returns>
public static IServiceCollection AddPetaPocoDataAccessServices(this IServiceCollection services, Action<IServiceProvider, IDatabaseBuildConfiguration> builder)
public static IServiceCollection AddPetaPocoDataAccessServices(this IServiceCollection services)
{
services.TryAddSingleton<IDatabase>(provider =>
{
var option = DatabaseConfiguration.Build();
builder(provider, option);
option.UsingDefaultMapper<BootstrapAdminConventionMapper>();
var db = new Database(option);
var logger = provider.GetRequiredService<ILogger<Database>>();
db.ExceptionThrown += (sender, e) =>
{
var message = e.Exception.Format(new NameValueCollection()
{
[nameof(db.LastCommand)] = db.LastCommand,
[nameof(db.LastArgs)] = string.Join(",", db.LastArgs)
});
logger.LogError(new EventId(1001, "GlobalException"), e.Exception, message);
};
var env = provider.GetRequiredService<IWebHostEnvironment>();
if (env.IsDevelopment())
{
db.CommandExecuted += (sender, args) =>
{
var parameters = new StringBuilder();
foreach (DbParameter p in args.Command.Parameters)
{
parameters.AppendFormat("{0}: {1} ", p.ParameterName, p.Value);
}
logger.LogInformation(args.Command.CommandText);
logger.LogInformation(parameters.ToString());
};
};
return db;
});
services.TryAddSingleton<IDBManager, DBManagerService>();
// 增加数据服务
services.AddSingleton(typeof(IDataService<>), typeof(DefaultDataService<>));

View File

@ -12,29 +12,34 @@ class AppService : IApp
{
private const string AppServiceGetAppsByRoleIdCacheKey = "AppService-GetAppsByRoleId";
private IDatabase Database { get; }
private IDBManager DBManager { get; }
public AppService(IDatabase db)
public AppService(IDBManager db)
{
Database = db;
DBManager = db;
}
public List<string> GetAppsByRoleId(string? roleId) => CacheManager.GetOrAdd($"{AppServiceGetAppsByRoleIdCacheKey}-{roleId}", entry => Database.Fetch<string>("select AppID from RoleApp where RoleID = @0", roleId));
public List<string> GetAppsByRoleId(string? roleId) => CacheManager.GetOrAdd($"{AppServiceGetAppsByRoleIdCacheKey}-{roleId}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>("select AppID from RoleApp where RoleID = @0", roleId);
});
public bool SaveAppsByRoleId(string? roleId, IEnumerable<string> appIds)
{
var ret = false;
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
Database.Execute("delete from RoleApp where RoleID = @0", roleId);
Database.InsertBatch("RoleApp", appIds.Select(g => new { AppID = g, RoleID = roleId }));
Database.CompleteTransaction();
db.BeginTransaction();
db.Execute("delete from RoleApp where RoleID = @0", roleId);
db.InsertBatch("RoleApp", appIds.Select(g => new { AppID = g, RoleID = roleId }));
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
if (ret)

View File

@ -0,0 +1,78 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using PetaPoco;
using PetaPoco.Providers;
using System.Collections.Specialized;
using System.Data.Common;
using System.Text;
namespace BootstrapAdmin.DataAccess.PetaPoco.Services;
internal class DBManagerService : IDBManager
{
private IConfiguration Configuration { get; set; }
private ILogger<DBManagerService> Logger { get; set; }
private IWebHostEnvironment WebHost { get; set; }
public DBManagerService(IConfiguration configuration, ILogger<DBManagerService> logger, IWebHostEnvironment host)
{
Configuration = configuration;
Logger = logger;
WebHost = host;
}
/// <summary>
/// 创建 IDatabase 实例方法
/// </summary>
/// <param name="connectionName">连接字符串键值</param>
/// <param name="keepAlive"></param>
/// <returns></returns>
public IDatabase Create(string? connectionName = "ba", bool keepAlive = false)
{
var conn = Configuration.GetConnectionString(connectionName) ?? throw new ArgumentNullException(nameof(connectionName));
var option = DatabaseConfiguration.Build();
option.UsingDefaultMapper<BootstrapAdminConventionMapper>();
// connectionstring
option.UsingConnectionString(conn);
// provider
option.UsingProvider<SQLiteDatabaseProvider>();
var db = new Database(option) { KeepConnectionAlive = keepAlive };
db.ExceptionThrown += (sender, e) =>
{
var message = e.Exception.Format(new NameValueCollection()
{
[nameof(db.LastCommand)] = db.LastCommand,
[nameof(db.LastArgs)] = string.Join(",", db.LastArgs)
});
Logger.LogError(new EventId(1001, "GlobalException"), e.Exception, message);
};
if (WebHost.IsDevelopment())
{
db.CommandExecuted += (sender, args) =>
{
var parameters = new StringBuilder();
foreach (DbParameter p in args.Command.Parameters)
{
parameters.AppendFormat("{0}: {1} ", p.ParameterName, p.Value);
}
Logger.LogInformation(args.Command.CommandText);
Logger.LogInformation(parameters.ToString());
};
};
return db;
}
}

View File

@ -9,21 +9,21 @@ using BootstrapBlazor.Components;
using PetaPoco;
using PetaPoco.Extensions;
namespace BootstrapBlazor.DataAcces.PetaPoco.Services;
namespace BootstrapAdmin.DataAccess.PetaPoco.Services;
/// <summary>
/// PetaPoco ORM 的 IDataService 接口实现
/// </summary>
class DefaultDataService<TModel> : DataServiceBase<TModel> where TModel : class, new()
{
private IDatabase Database { get; }
private IDBManager DBManager { get; }
private IUser UserService { get; }
/// <summary>
/// 构造函数
/// </summary>
public DefaultDataService(IDatabase db, IUser userService) => (Database, UserService) = (db, userService);
public DefaultDataService(IDBManager db, IUser userService) => (DBManager, UserService) = (db, userService);
/// <summary>
/// 删除方法
@ -34,7 +34,8 @@ class DefaultDataService<TModel> : DataServiceBase<TModel> where TModel : class,
{
// 通过模型获取主键列数据
// 支持批量删除
Database.DeleteBatch(models);
using var db = DBManager.Create();
db.DeleteBatch(models);
return Task.FromResult(true);
}
@ -52,13 +53,14 @@ class DefaultDataService<TModel> : DataServiceBase<TModel> where TModel : class,
}
else
{
using var db = DBManager.Create();
if (changedType == ItemChangedType.Add)
{
await Database.InsertAsync(model);
await db.InsertAsync(model);
}
else
{
await Database.UpdateAsync(model);
await db.UpdateAsync(model);
}
}
return true;
@ -79,16 +81,17 @@ class DefaultDataService<TModel> : DataServiceBase<TModel> where TModel : class,
IsAdvanceSearch = option.AdvanceSearchs.Any() || option.CustomerSearchs.Any()
};
using var db = DBManager.Create();
if (option.IsPage)
{
var items = await Database.PageAsync<TModel>(option);
var items = await db.PageAsync<TModel>(option);
ret.TotalCount = Convert.ToInt32(items.TotalItems);
ret.Items = items.Items;
}
else
{
var items = await Database.FetchAsync<TModel>(option);
var items = await db.FetchAsync<TModel>(option);
ret.TotalCount = items.Count;
ret.Items = items;
}

View File

@ -17,7 +17,7 @@ class DictService : IDict
{
private const string DictServiceCacheKey = "DictService-GetAll";
private IDatabase Database { get; }
private IDBManager DBManager { get; }
private string AppId { get; set; }
@ -26,13 +26,17 @@ class DictService : IDict
/// </summary>
/// <param name="db"></param>
/// <param name="configuration"></param>
public DictService(IDatabase db, IConfiguration configuration)
public DictService(IDBManager db, IConfiguration configuration)
{
Database = db;
DBManager = db;
AppId = configuration.GetValue("AppId", "BA");
}
public List<Dict> GetAll() => CacheManager.GetOrAdd(DictServiceCacheKey, entry => Database.Fetch<Dict>());
public List<Dict> GetAll() => CacheManager.GetOrAdd(DictServiceCacheKey, entry =>
{
using var db = DBManager.Create();
return db.Fetch<Dict>();
});
public Dictionary<string, string> GetApps()
{
@ -178,7 +182,8 @@ class DictService : IDict
private bool SaveDict(Dict dict)
{
var ret = Database.Update<Dict>("set Code = @Code where Category = @Category and Name = @Name", dict) == 1;
using var db = DBManager.Create();
var ret = db.Update<Dict>("set Code = @Code where Category = @Category and Name = @Name", dict) == 1;
if (ret)
{
// 更新缓存
@ -417,9 +422,10 @@ class DictService : IDict
if (!string.IsNullOrEmpty(client.AppId))
{
DeleteClient(client.AppId);
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
db.BeginTransaction();
var items = new List<Dict>()
{
new Dict { Category = "应用程序", Name = client.AppName, Code = client.AppId, Define = EnumDictDefine.System },
@ -432,13 +438,13 @@ class DictService : IDict
new Dict { Category = client.AppId, Name = "系统设置地址", Code = client.SettingsUrl, Define = EnumDictDefine.Customer },
new Dict { Category = client.AppId, Name = "系统通知地址", Code = client.NotificationUrl, Define = EnumDictDefine.Customer }
};
Database.InsertBatch(items);
Database.CompleteTransaction();
db.InsertBatch(items);
db.CompleteTransaction();
ret = true;
}
catch (DbException)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
}
@ -466,12 +472,13 @@ class DictService : IDict
public bool DeleteClient(string appId)
{
bool ret;
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
Database.Execute("delete Dicts where Category=@0 and Name=@1 and Define=@2", "应用首页", appId, EnumDictDefine.System);
Database.Execute("delete Dicts where Category=@0 and Code=@1 and Define=@2", "应用程序", appId, EnumDictDefine.System);
Database.Execute("delete Dicts where Category=@Category and Name in (@Names)", new
db.BeginTransaction();
db.Execute("delete Dicts where Category=@0 and Name=@1 and Define=@2", "应用首页", appId, EnumDictDefine.System);
db.Execute("delete Dicts where Category=@0 and Code=@1 and Define=@2", "应用程序", appId, EnumDictDefine.System);
db.Execute("delete Dicts where Category=@Category and Name in (@Names)", new
{
Category = appId,
Names = new List<string>
@ -485,12 +492,12 @@ class DictService : IDict
"系统通知地址"
}
});
Database.CompleteTransaction();
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
return ret;

View File

@ -10,15 +10,16 @@ namespace BootstrapAdmin.DataAccess.PetaPoco.Services;
class ExceptionService : IException
{
private IDatabase Database { get; }
private IDBManager DBManager { get; }
public ExceptionService(IDatabase db) => Database = db;
public ExceptionService(IDBManager db) => DBManager = db;
public bool Log(Error exception)
{
try
{
Database.Insert(exception);
using var db = DBManager.Create();
db.Insert(exception);
}
catch { }
return true;
@ -59,7 +60,8 @@ class ExceptionService : IException
sql.OrderBy("Logtime desc", "ErrorPage", "UserId");
}
var data = Database.Page<Error>(pageIndex, pageItems, sql);
using var db = DBManager.Create();
var data = db.Page<Error>(pageIndex, pageItems, sql);
return (data.Items, Convert.ToInt32(data.TotalItems));
}
}

View File

@ -17,26 +17,34 @@ class GroupService : IGroup
private const string GroupServiceGetGroupsByRoleIdCacheKey = "GroupService-GetGroupsByRoleId";
private IDatabase Database { get; }
private IDBManager DBManager { get; }
/// <summary>
///
/// </summary>
/// <param name="db"></param>
public GroupService(IDatabase db) => Database = db;
public GroupService(IDBManager db) => DBManager = db;
/// <summary>
///
/// </summary>
/// <returns></returns>
public List<Group> GetAll() => CacheManager.GetOrAdd(GroupServiceGetAllCacheKey, entry => Database.Fetch<Group>());
public List<Group> GetAll() => CacheManager.GetOrAdd(GroupServiceGetAllCacheKey, entry =>
{
using var db = DBManager.Create();
return db.Fetch<Group>();
});
/// <summary>
///
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public List<string> GetGroupsByUserId(string? userId) => CacheManager.GetOrAdd($"{GroupServiceGetGroupsByUserIdCacheKey}-{userId}", entry => Database.Fetch<string>("select GroupID from UserGroup where UserID = @0", userId));
public List<string> GetGroupsByUserId(string? userId) => CacheManager.GetOrAdd($"{GroupServiceGetGroupsByUserIdCacheKey}-{userId}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>("select GroupID from UserGroup where UserID = @0", userId);
});
/// <summary>
///
@ -47,17 +55,18 @@ class GroupService : IGroup
public bool SaveGroupsByUserId(string? userId, IEnumerable<string> groupIds)
{
var ret = false;
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
Database.Execute("delete from UserGroup where UserID = @0", userId);
Database.InsertBatch("UserGroup", groupIds.Select(g => new { GroupID = g, UserID = userId }));
Database.CompleteTransaction();
db.BeginTransaction();
db.Execute("delete from UserGroup where UserID = @0", userId);
db.InsertBatch("UserGroup", groupIds.Select(g => new { GroupID = g, UserID = userId }));
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
if (ret)
@ -72,7 +81,11 @@ class GroupService : IGroup
/// </summary>
/// <param name="roleId"></param>
/// <returns></returns>
public List<string> GetGroupsByRoleId(string? roleId) => CacheManager.GetOrAdd($"{GroupServiceGetGroupsByRoleIdCacheKey}-{roleId}", entry => Database.Fetch<string>("select GroupID from RoleGroup where RoleID = @0", roleId));
public List<string> GetGroupsByRoleId(string? roleId) => CacheManager.GetOrAdd($"{GroupServiceGetGroupsByRoleIdCacheKey}-{roleId}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>("select GroupID from RoleGroup where RoleID = @0", roleId);
});
/// <summary>
///
@ -83,17 +96,18 @@ class GroupService : IGroup
public bool SaveGroupsByRoleId(string? roleId, IEnumerable<string> groupIds)
{
var ret = false;
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
Database.Execute("delete from RoleGroup where RoleID = @0", roleId);
Database.InsertBatch("RoleGroup", groupIds.Select(g => new { GroupID = g, RoleID = roleId }));
Database.CompleteTransaction();
db.BeginTransaction();
db.Execute("delete from RoleGroup where RoleID = @0", roleId);
db.InsertBatch("RoleGroup", groupIds.Select(g => new { GroupID = g, RoleID = roleId }));
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}

View File

@ -0,0 +1,19 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using PetaPoco;
namespace BootstrapAdmin.DataAccess.PetaPoco.Services;
/// <summary>
///
/// </summary>
public interface IDBManager
{
/// <summary>
///
/// </summary>
/// <returns></returns>
IDatabase Create(string? connectionName = "ba", bool keepAlive = false);
}

View File

@ -4,15 +4,14 @@
using BootstrapAdmin.DataAccess.Models;
using BootstrapAdmin.Web.Core;
using PetaPoco;
namespace BootstrapAdmin.DataAccess.PetaPoco.Services;
class LoginService : ILogin
{
private IDatabase Database { get; }
private IDBManager DBManager { get; }
public LoginService(IDatabase database) => Database = database;
public LoginService(IDBManager database) => DBManager = database;
/// <summary>
///
@ -38,7 +37,8 @@ class LoginService : ILogin
UserAgent = userAgent,
Result = result ? "登录成功" : "登录失败"
};
Database.Insert(loginUser);
using var db = DBManager.Create();
db.Insert(loginUser);
return true;
}
}

View File

@ -18,13 +18,16 @@ class NavigationService : INavigation
private const string NavigationServiceGetMenusByRoleIdCacheKey = "NavigationService-GetMenusByRoleId";
private IDatabase Database { get; }
private IDBManager DBManager { get; }
/// <summary>
///
/// </summary>
/// <param name="db"></param>
public NavigationService(IDatabase db) => Database = db;
public NavigationService(IDBManager db)
{
DBManager = db;
}
/// <summary>
/// 获得指定用户名可访问的所有菜单集合
@ -33,9 +36,10 @@ class NavigationService : INavigation
/// <returns>未层次化的菜单集合</returns>
public List<Navigation> GetAllMenus(string userName) => CacheManager.GetOrAdd($"{NavigationServiceGetAllCacheKey}-{userName}", entry =>
{
using var db = DBManager.Create();
// 缓存所有菜单数据移除 SQL 语句降低复杂度
var order = Database.Provider.EscapeSqlIdentifier("Order");
return Database.Fetch<Models.Navigation>($"select n.ID, n.ParentId, n.Name, n.{order}, n.Icon, n.Url, n.Category, n.Target, n.IsResource, n.Application from Navigations n inner join (select nr.NavigationID from Users u inner join UserRole ur on ur.UserID = u.ID inner join NavigationRole nr on nr.RoleID = ur.RoleID where u.UserName = @UserName union select nr.NavigationID from Users u inner join UserGroup ug on u.ID = ug.UserID inner join RoleGroup rg on rg.GroupID = ug.GroupID inner join NavigationRole nr on nr.RoleID = rg.RoleID where u.UserName = @UserName union select n.ID from Navigations n where EXISTS (select UserName from Users u inner join UserRole ur on u.ID = ur.UserID inner join Roles r on ur.RoleID = r.ID where u.UserName = @UserName and r.RoleName = @RoleName)) nav on n.ID = nav.NavigationID ORDER BY n.Application, n.{order}", new { UserName = userName, RoleName = "Administrators" });
var order = db.Provider.EscapeSqlIdentifier("Order");
return db.Fetch<Models.Navigation>($"select n.ID, n.ParentId, n.Name, n.{order}, n.Icon, n.Url, n.Category, n.Target, n.IsResource, n.Application from Navigations n inner join (select nr.NavigationID from Users u inner join UserRole ur on ur.UserID = u.ID inner join NavigationRole nr on nr.RoleID = ur.RoleID where u.UserName = @UserName union select nr.NavigationID from Users u inner join UserGroup ug on u.ID = ug.UserID inner join RoleGroup rg on rg.GroupID = ug.GroupID inner join NavigationRole nr on nr.RoleID = rg.RoleID where u.UserName = @UserName union select n.ID from Navigations n where EXISTS (select UserName from Users u inner join UserRole ur on u.ID = ur.UserID inner join Roles r on ur.RoleID = r.ID where u.UserName = @UserName and r.RoleName = @RoleName)) nav on n.ID = nav.NavigationID ORDER BY n.Application, n.{order}", new { UserName = userName, RoleName = "Administrators" });
});
/// <summary>
@ -43,7 +47,11 @@ class NavigationService : INavigation
/// </summary>
/// <param name="roleId"></param>
/// <returns></returns>
public List<string> GetMenusByRoleId(string? roleId) => CacheManager.GetOrAdd($"{NavigationServiceGetMenusByRoleIdCacheKey}-{roleId}", entry => Database.Fetch<string>("select NavigationID from NavigationRole where RoleID = @0", roleId));
public List<string> GetMenusByRoleId(string? roleId) => CacheManager.GetOrAdd($"{NavigationServiceGetMenusByRoleIdCacheKey}-{roleId}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>("select NavigationID from NavigationRole where RoleID = @0", roleId);
});
/// <summary>
///
@ -54,17 +62,18 @@ class NavigationService : INavigation
public bool SaveMenusByRoleId(string? roleId, List<string> menuIds)
{
var ret = false;
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
Database.Execute("delete from NavigationRole where RoleID = @0", roleId);
Database.InsertBatch("NavigationRole", menuIds.Select(g => new { NavigationID = g, RoleID = roleId }));
Database.CompleteTransaction();
db.BeginTransaction();
db.Execute("delete from NavigationRole where RoleID = @0", roleId);
db.InsertBatch("NavigationRole", menuIds.Select(g => new { NavigationID = g, RoleID = roleId }));
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
if (ret)

View File

@ -19,25 +19,44 @@ class RoleService : IRole
private const string RoleServiceGetRolesByMenuIdCacheKey = "RoleService-GetRolesByMenusId";
private IDatabase Database { get; }
private IDBManager DBManager { get; }
/// <summary>
///
/// </summary>
/// <param name="db"></param>
public RoleService(IDatabase db) => Database = db;
public RoleService(IDBManager db)
{
DBManager = db;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public List<Role> GetAll() => CacheManager.GetOrAdd(RoleServiceGetAllCacheKey, entry => CacheManager.GetOrAdd(RoleServiceGetAllCacheKey, entry => Database.Fetch<Role>()));
public List<Role> GetAll() => CacheManager.GetOrAdd(RoleServiceGetAllCacheKey, entry => CacheManager.GetOrAdd(RoleServiceGetAllCacheKey, entry =>
{
using var db = DBManager.Create();
return db.Fetch<Role>();
}));
public List<string> GetRolesByGroupId(string? groupId) => CacheManager.GetOrAdd($"{RoleServiceGetRolesByGroupIdCacheKey}-{groupId}", entry => Database.Fetch<string>("select RoleID from RoleGroup where GroupID = @0", groupId));
public List<string> GetRolesByGroupId(string? groupId) => CacheManager.GetOrAdd($"{RoleServiceGetRolesByGroupIdCacheKey}-{groupId}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>("select RoleID from RoleGroup where GroupID = @0", groupId);
});
public List<string> GetRolesByUserId(string? userId) => CacheManager.GetOrAdd($"{RoleServiceGetRolesByUserIdCacheKey}-{userId}", entry => Database.Fetch<string>("select RoleID from UserRole where UserID = @0", userId));
public List<string> GetRolesByUserId(string? userId) => CacheManager.GetOrAdd($"{RoleServiceGetRolesByUserIdCacheKey}-{userId}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>("select RoleID from UserRole where UserID = @0", userId);
});
public List<string> GetRolesByMenuId(string? menuId) => CacheManager.GetOrAdd($"{RoleServiceGetRolesByMenuIdCacheKey}-{menuId}", entry => Database.Fetch<string>("select RoleID from NavigationRole where NavigationID = @0", menuId));
public List<string> GetRolesByMenuId(string? menuId) => CacheManager.GetOrAdd($"{RoleServiceGetRolesByMenuIdCacheKey}-{menuId}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>("select RoleID from NavigationRole where NavigationID = @0", menuId);
});
/// <summary>
///
@ -48,17 +67,18 @@ class RoleService : IRole
public bool SaveRolesByGroupId(string? groupId, IEnumerable<string> roleIds)
{
var ret = false;
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
Database.Execute("delete from RoleGroup where GroupID = @0", groupId);
Database.InsertBatch("RoleGroup", roleIds.Select(g => new { RoleID = g, GroupID = groupId }));
Database.CompleteTransaction();
db.BeginTransaction();
db.Execute("delete from RoleGroup where GroupID = @0", groupId);
db.InsertBatch("RoleGroup", roleIds.Select(g => new { RoleID = g, GroupID = groupId }));
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
if (ret)
@ -77,17 +97,18 @@ class RoleService : IRole
public bool SaveRolesByUserId(string? userId, IEnumerable<string> roleIds)
{
var ret = false;
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
Database.Execute("delete from UserRole where UserID = @0", userId);
Database.InsertBatch("UserRole", roleIds.Select(g => new { RoleID = g, UserID = userId }));
Database.CompleteTransaction();
db.BeginTransaction();
db.Execute("delete from UserRole where UserID = @0", userId);
db.InsertBatch("UserRole", roleIds.Select(g => new { RoleID = g, UserID = userId }));
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
if (ret)
@ -100,17 +121,18 @@ class RoleService : IRole
public bool SaveRolesByMenuId(string? menuId, IEnumerable<string> roleIds)
{
var ret = false;
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
Database.Execute("delete from NavigationRole where NavigationID = @0", menuId);
Database.InsertBatch("NavigationRole", roleIds.Select(g => new { RoleID = g, NavigationID = menuId }));
Database.CompleteTransaction();
db.BeginTransaction();
db.Execute("delete from NavigationRole where NavigationID = @0", menuId);
db.InsertBatch("NavigationRole", roleIds.Select(g => new { RoleID = g, NavigationID = menuId }));
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
if (ret)

View File

@ -10,13 +10,12 @@ namespace BootstrapAdmin.DataAccess.PetaPoco.Services;
class TraceService : ITrace
{
private IDatabase Database { get; }
private IDBManager DBManager { get; }
/// <summary>
///
/// </summary>
/// <param name="db"></param>
public TraceService(IDatabase db) => Database = db;
public TraceService(IDBManager db)
{
DBManager = db;
}
/// <summary>
///
@ -24,7 +23,8 @@ class TraceService : ITrace
/// <param name="trace"></param>
public void Log(Trace trace)
{
Database.Insert(trace);
using var db = DBManager.Create();
db.Insert(trace);
}
/// <summary>
@ -39,7 +39,6 @@ class TraceService : ITrace
public (IEnumerable<Trace> Items, int ItemsCount) GetAll(string? searchText, TraceFilter filter, int pageIndex, int pageItems, List<string> sortList)
{
var sql = new Sql();
if (!string.IsNullOrEmpty(searchText))
{
sql.Where("UserName Like @0 or Ip Like @0 or RequestUrl Like @0", $"%{searchText}%");
@ -71,7 +70,8 @@ class TraceService : ITrace
sql.OrderBy("Logtime desc");
}
var data = Database.Page<Trace>(pageIndex, pageItems, sql);
using var db = DBManager.Create();
var data = db.Page<Trace>(pageIndex, pageItems, sql);
return (data.Items, Convert.ToInt32(data.TotalItems));
}
}

View File

@ -12,19 +12,22 @@ namespace BootstrapAdmin.DataAccess.PetaPoco.Services;
class UserService : IUser
{
private IDatabase Database { get; }
private IDBManager DBManager { get; }
/// <summary>
///
/// </summary>
/// <param name="db"></param>
public UserService(IDatabase db) => Database = db;
public UserService(IDBManager db)
{
DBManager = db;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public List<User> GetAll() => Database.Fetch<User>();
public List<User> GetAll()
{
using var db = DBManager.Create();
return db.Fetch<User>();
}
/// <summary>
///
@ -35,7 +38,8 @@ class UserService : IUser
/// <exception cref="NotImplementedException"></exception>
public bool Authenticate(string userName, string password)
{
var user = Database.SingleOrDefault<User>("select DisplayName, Password, PassSalt from Users where ApprovedTime is not null and UserName = @0", userName);
using var db = DBManager.Create();
var user = db.SingleOrDefault<User>("select DisplayName, Password, PassSalt from Users where ApprovedTime is not null and UserName = @0", userName);
var isAuth = false;
if (user != null && !string.IsNullOrEmpty(user.PassSalt))
@ -47,11 +51,19 @@ class UserService : IUser
private const string UserServiceGetUserByUserNameCacheKey = "UserService-GetUserByUserName";
public User? GetUserByUserName(string? userName) => CacheManager.GetOrAdd($"{UserServiceGetUserByUserNameCacheKey}-{userName}", entry => string.IsNullOrEmpty(userName) ? null : Database.FirstOrDefault<User>("Where UserName = @0", userName));
public User? GetUserByUserName(string? userName) => CacheManager.GetOrAdd($"{UserServiceGetUserByUserNameCacheKey}-{userName}", entry =>
{
using var db = DBManager.Create();
return string.IsNullOrEmpty(userName) ? null : db.FirstOrDefault<User>("Where UserName = @0", userName);
});
private const string UserServiceGetAppsByUserNameCacheKey = "UserService-GetAppsByUserName";
public List<string> GetApps(string userName) => CacheManager.GetOrAdd($"{UserServiceGetAppsByUserNameCacheKey}-{userName}", entry => Database.Fetch<string>($"select d.Code from Dicts d inner join RoleApp ra on d.Code = ra.AppId inner join (select r.Id from Roles r inner join UserRole ur on r.ID = ur.RoleID inner join Users u on ur.UserID = u.ID where u.UserName = @0 union select r.Id from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {Database.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID where u.UserName = @0) r on ra.RoleId = r.ID union select Code from Dicts where Category = @1 and exists(select r.ID from Roles r inner join UserRole ur on r.ID = ur.RoleID inner join Users u on ur.UserID = u.ID where u.UserName = @0 and r.RoleName = @2 union select r.ID from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {Database.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID where u.UserName = @0 and r.RoleName = @2)", userName, "应用程序", "Administrators"));
public List<string> GetApps(string userName) => CacheManager.GetOrAdd($"{UserServiceGetAppsByUserNameCacheKey}-{userName}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>($"select d.Code from Dicts d inner join RoleApp ra on d.Code = ra.AppId inner join (select r.Id from Roles r inner join UserRole ur on r.ID = ur.RoleID inner join Users u on ur.UserID = u.ID where u.UserName = @0 union select r.Id from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {db.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID where u.UserName = @0) r on ra.RoleId = r.ID union select Code from Dicts where Category = @1 and exists(select r.ID from Roles r inner join UserRole ur on r.ID = ur.RoleID inner join Users u on ur.UserID = u.ID where u.UserName = @0 and r.RoleName = @2 union select r.ID from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {db.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID where u.UserName = @0 and r.RoleName = @2)", userName, "应用程序", "Administrators");
});
private const string UserServiceGetAppIdByUserNameCacheKey = "UserService-GetAppIdByUserName";
@ -60,7 +72,11 @@ class UserService : IUser
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public string? GetAppIdByUserName(string userName) => CacheManager.GetOrAdd($"{UserServiceGetAppIdByUserNameCacheKey}-{userName}", entry => Database.FirstOrDefault<User>("Where UserName = @0", userName)?.App);
public string? GetAppIdByUserName(string userName) => CacheManager.GetOrAdd($"{UserServiceGetAppIdByUserNameCacheKey}-{userName}", entry =>
{
using var db = DBManager.Create();
return db.FirstOrDefault<User>("Where UserName = @0", userName)?.App;
});
private const string UserServiceGetRolesByUserNameCacheKey = "UserService-GetRolesByUserName";
@ -70,7 +86,11 @@ class UserService : IUser
/// <param name="userName"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public List<string> GetRoles(string userName) => CacheManager.GetOrAdd($"{UserServiceGetRolesByUserNameCacheKey}-{userName}", entry => Database.Fetch<string>($"select r.RoleName from Roles r inner join UserRole ur on r.ID=ur.RoleID inner join Users u on ur.UserID = u.ID and u.UserName = @0 union select r.RoleName from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {Database.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID and u.UserName = @0", userName));
public List<string> GetRoles(string userName) => CacheManager.GetOrAdd($"{UserServiceGetRolesByUserNameCacheKey}-{userName}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>($"select r.RoleName from Roles r inner join UserRole ur on r.ID=ur.RoleID inner join Users u on ur.UserID = u.ID and u.UserName = @0 union select r.RoleName from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {db.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID and u.UserName = @0", userName);
});
private const string UserServiceGetUsersByGroupIdCacheKey = "UserService-GetUsersByGroupId";
@ -78,7 +98,11 @@ class UserService : IUser
///
/// </summary>
/// <param name="groupId"></param>
public List<string> GetUsersByGroupId(string? groupId) => CacheManager.GetOrAdd($"{UserServiceGetUsersByGroupIdCacheKey}-{groupId}", entry => Database.Fetch<string>("select UserID from UserGroup where GroupID = @0", groupId));
public List<string> GetUsersByGroupId(string? groupId) => CacheManager.GetOrAdd($"{UserServiceGetUsersByGroupIdCacheKey}-{groupId}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>("select UserID from UserGroup where GroupID = @0", groupId);
});
/// <summary>
///
@ -90,17 +114,18 @@ class UserService : IUser
public bool SaveUsersByGroupId(string? id, IEnumerable<string> userIds)
{
var ret = false;
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
Database.Execute("delete from UserGroup where GroupId = @0", id);
Database.InsertBatch("UserGroup", userIds.Select(g => new { UserID = g, GroupID = id }));
Database.CompleteTransaction();
db.BeginTransaction();
db.Execute("delete from UserGroup where GroupId = @0", id);
db.InsertBatch("UserGroup", userIds.Select(g => new { UserID = g, GroupID = id }));
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
if (ret)
@ -117,7 +142,11 @@ class UserService : IUser
/// </summary>
/// <param name="roleId"></param>
/// <returns></returns>
public List<string> GetUsersByRoleId(string? roleId) => CacheManager.GetOrAdd($"{UserServiceGetUsersByRoleIdCacheKey}-{roleId}", entry => Database.Fetch<string>("select UserID from UserRole where RoleID = @0", roleId));
public List<string> GetUsersByRoleId(string? roleId) => CacheManager.GetOrAdd($"{UserServiceGetUsersByRoleIdCacheKey}-{roleId}", entry =>
{
using var db = DBManager.Create();
return db.Fetch<string>("select UserID from UserRole where RoleID = @0", roleId);
});
/// <summary>
///
@ -128,17 +157,18 @@ class UserService : IUser
public bool SaveUsersByRoleId(string? roleId, IEnumerable<string> userIds)
{
var ret = false;
using var db = DBManager.Create();
try
{
Database.BeginTransaction();
Database.Execute("delete from UserRole where RoleID = @0", roleId);
Database.InsertBatch("UserRole", userIds.Select(g => new { UserID = g, RoleID = roleId }));
Database.CompleteTransaction();
db.BeginTransaction();
db.Execute("delete from UserRole where RoleID = @0", roleId);
db.InsertBatch("UserRole", userIds.Select(g => new { UserID = g, RoleID = roleId }));
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
if (ret)
@ -157,12 +187,13 @@ class UserService : IUser
public bool ChangePassword(string userName, string password, string newPassword)
{
var ret = false;
using var db = DBManager.Create();
if (Authenticate(userName, password))
{
var passSalt = LgbCryptography.GenerateSalt();
password = LgbCryptography.ComputeHash(newPassword, passSalt);
string sql = "set Password = @0, PassSalt = @1 where UserName = @2";
ret = Database.Update<User>(sql, password, passSalt, userName) == 1;
ret = db.Update<User>(sql, password, passSalt, userName) == 1;
}
return ret;
}
@ -172,7 +203,8 @@ class UserService : IUser
/// </summary>
public bool SaveDisplayName(string userName, string displayName)
{
var ret = Database.Update<User>("set DisplayName = @1 where UserName = @0", userName, displayName) == 1;
using var db = DBManager.Create();
var ret = db.Update<User>("set DisplayName = @1 where UserName = @0", userName, displayName) == 1;
if (ret)
{
CacheManager.Clear();
@ -185,7 +217,8 @@ class UserService : IUser
/// </summary>
public bool SaveTheme(string userName, string theme)
{
var ret = Database.Update<User>("set Css = @1 where UserName = @0", userName, theme) == 1;
using var db = DBManager.Create();
var ret = db.Update<User>("set Css = @1 where UserName = @0", userName, theme) == 1;
if (ret)
{
CacheManager.Clear();
@ -198,7 +231,8 @@ class UserService : IUser
/// </summary>
public bool SaveLogo(string userName, string? logo)
{
var ret = Database.Update<User>("set Icon = @1 where UserName = @0", userName, logo) == 1;
using var db = DBManager.Create();
var ret = db.Update<User>("set Icon = @1 where UserName = @0", userName, logo) == 1;
if (ret)
{
CacheManager.Clear();
@ -217,14 +251,15 @@ class UserService : IUser
public bool TryCreateUserByPhone(string phone, string code, string appId, ICollection<string> roles)
{
var ret = false;
using var db = DBManager.Create();
try
{
var salt = LgbCryptography.GenerateSalt();
var pwd = LgbCryptography.ComputeHash(code, salt);
var user = Database.FirstOrDefault<User>("Where UserName = @0", phone);
var user = db.FirstOrDefault<User>("Where UserName = @0", phone);
if (user == null)
{
Database.BeginTransaction();
db.BeginTransaction();
// 插入用户
user = new User()
{
@ -238,23 +273,23 @@ class UserService : IUser
Password = LgbCryptography.ComputeHash(code, salt),
App = appId
};
Database.Save(user);
db.Save(user);
// Authorization
var roleIds = Database.Fetch<string>("select ID from Roles where RoleName in (@roles)", new { roles });
Database.InsertBatch("UserRole", roleIds.Select(g => new { RoleID = g, UserID = user.Id }));
Database.CompleteTransaction();
var roleIds = db.Fetch<string>("select ID from Roles where RoleName in (@roles)", new { roles });
db.InsertBatch("UserRole", roleIds.Select(g => new { RoleID = g, UserID = user.Id }));
db.CompleteTransaction();
}
else
{
user.PassSalt = salt;
user.Password = pwd;
Database.Update(user);
db.Update(user);
}
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
if (ret)
@ -268,14 +303,15 @@ class UserService : IUser
{
var salt = LgbCryptography.GenerateSalt();
var pwd = LgbCryptography.ComputeHash(password, salt);
var user = Database.FirstOrDefault<User>("Where UserName = @0", userName);
using var db = DBManager.Create();
var user = db.FirstOrDefault<User>("Where UserName = @0", userName);
bool ret;
if (user == null)
{
try
{
// 开始事务
Database.BeginTransaction();
db.BeginTransaction();
user = new User()
{
ApprovedBy = "System",
@ -287,16 +323,16 @@ class UserService : IUser
PassSalt = salt,
Password = pwd
};
Database.Save(user);
db.Save(user);
// 授权 Default 角色
Database.Execute("insert into UserRole (UserID, RoleID) select ID, (select ID from Roles where RoleName = 'Default') RoleId from Users where UserName = @0", userName);
db.Execute("insert into UserRole (UserID, RoleID) select ID, (select ID from Roles where RoleName = 'Default') RoleId from Users where UserName = @0", userName);
// 结束事务
Database.CompleteTransaction();
db.CompleteTransaction();
ret = true;
}
catch (Exception)
{
Database.AbortTransaction();
db.AbortTransaction();
throw;
}
}
@ -305,7 +341,7 @@ class UserService : IUser
user.DisplayName = displayName;
user.PassSalt = salt;
user.Password = pwd;
Database.Update(user);
db.Update(user);
ret = true;
}
if (ret)
@ -317,7 +353,8 @@ class UserService : IUser
public bool SaveApp(string userName, string app)
{
var ret = Database.Update<User>("Set App = @1 Where UserName = @0", userName, app) == 1;
using var db = DBManager.Create();
var ret = db.Update<User>("Set App = @1 Where UserName = @0", userName, app) == 1;
if (ret)
{
CacheManager.Clear();

View File

@ -1,35 +1,39 @@
<form method="post" class="form-signin" action="@PostUrl" @ref="LoginForm">
<h2 class="form-signin-heading">@Title</h2>
<div class="@ClassString">
@if (AllowMobile)
{
<div class="login-sms">
<SMSLogin />
<div class="wrap white">
<div class="container">
<form method="post" class="form-signin" action="@PostUrl" @ref="LoginForm">
<h2 class="form-signin-heading">@Title</h2>
<div class="@ClassString">
@if (AllowMobile)
{
<div class="login-sms">
<SMSLogin />
</div>
}
<div class="login-up">
<UserLogin />
</div>
<div class="d-flex justify-content-between mt-3">
<Checkbox @bind-Value="RememberPassword" Color="Color.Primary" ShowAfterLabel="true" DisplayText="记住密码自动登录" OnValueChanged="OnRememberPassword" />
<Block Condition="AllowMobile">
<SwitchButton @bind-ToggleState="UseMobileLogin" OnClick="OnClickSwitchButton" OffText="短信验证登录" OnText="用户密码登录" />
</Block>
</div>
<button class="btn-login btn-lg btn-block mt-3" data-bs-toggle="tooltip" title="不填写密码默认使用 Gitee 认证">登 录</button>
<div class="d-flex justify-content-between mt-3">
<LinkButton Text="申请账号" OnClick="OnSignUp" />
<LinkButton Text="忘记密码" OnClick="OnForgotPassword" />
</div>
<Block Condition="AllowOAuth">
<Divider Text="其他方式登录" />
<div class="login-list">
<LinkButton Url="Account/Gitee" Title="使用 Gitee 帐号登录" ImageUrl="images/gitee.svg" />
<LinkButton Url="Account/Gitee" Title="使用 GitHub 帐号登录" ImageUrl="images/git.svg" />
<LinkButton Url="#" Title="微信-暂未实现" ImageUrl="images/weixin-2.svg" />
<LinkButton Url="Account/Tencent" Title="使用 QQ 账号登录" ImageUrl="images/qq.svg" />
<LinkButton Url="Account/Alipay" Title="使用支付宝账号登录" ImageUrl="images/zhifubao.svg" />
</div>
</Block>
</div>
}
<div class="login-up">
<UserLogin />
</div>
<div class="d-flex justify-content-between mt-3">
<Checkbox @bind-Value="RememberPassword" Color="Color.Primary" ShowAfterLabel="true" DisplayText="记住密码自动登录" OnValueChanged="OnRememberPassword" />
<Block Condition="AllowMobile">
<SwitchButton @bind-ToggleState="UseMobileLogin" OnClick="OnClickSwitchButton" OffText="短信验证登录" OnText="用户密码登录" />
</Block>
</div>
<button class="btn-login btn-lg btn-block mt-3" data-bs-toggle="tooltip" title="不填写密码默认使用 Gitee 认证">登 录</button>
<div class="d-flex justify-content-between mt-3">
<LinkButton Text="申请账号" OnClick="OnSignUp" />
<LinkButton Text="忘记密码" OnClick="OnForgotPassword" />
</div>
<Block Condition="AllowOAuth">
<Divider Text="其他方式登录" />
<div class="login-list">
<LinkButton Url="Account/Gitee" Title="使用 Gitee 帐号登录" ImageUrl="images/gitee.svg" />
<LinkButton Url="Account/Gitee" Title="使用 GitHub 帐号登录" ImageUrl="images/git.svg" />
<LinkButton Url="#" Title="微信-暂未实现" ImageUrl="images/weixin-2.svg" />
<LinkButton Url="Account/Tencent" Title="使用 QQ 账号登录" ImageUrl="images/qq.svg" />
<LinkButton Url="Account/Alipay" Title="使用支付宝账号登录" ImageUrl="images/zhifubao.svg" />
</div>
</Block>
</form>
</div>
</form>
</div>

View File

@ -3,6 +3,7 @@
// Website: https://admin.blazor.zone
using BootstrapAdmin.Web.Core;
using BootstrapAdmin.Web.Utils;
using Microsoft.JSInterop;
namespace BootstrapAdmin.Web.Components;
@ -12,19 +13,40 @@ namespace BootstrapAdmin.Web.Components;
/// </summary>
public partial class AdminLogin : IDisposable
{
private string? Title { get; set; }
/// <summary>
///
/// </summary>
protected string? Title { get; set; }
private bool AllowMobile { get; set; } = true;
/// <summary>
///
/// </summary>
protected bool AllowMobile { get; set; } = true;
private bool UseMobileLogin { get; set; }
/// <summary>
///
/// </summary>
protected bool UseMobileLogin { get; set; }
private bool AllowOAuth { get; set; } = true;
/// <summary>
///
/// </summary>
protected bool AllowOAuth { get; set; } = true;
private bool RememberPassword { get; set; }
/// <summary>
///
/// </summary>
protected bool RememberPassword { get; set; }
private ElementReference LoginForm { get; set; }
/// <summary>
///
/// </summary>
protected ElementReference LoginForm { get; set; }
private string? PostUrl { get; set; }
/// <summary>
///
/// </summary>
protected string? PostUrl { get; set; }
private JSInterop<AdminLogin>? Interop { get; set; }
@ -40,13 +62,19 @@ public partial class AdminLogin : IDisposable
[Parameter]
public string? AppId { get; set; }
/// <summary>
///
/// </summary>
[Inject]
[NotNull]
private IDict? DictsService { get; set; }
protected IDict? DictsService { get; set; }
/// <summary>
///
/// </summary>
[Inject]
[NotNull]
private ILogin? LoginService { get; set; }
protected ILogin? LoginService { get; set; }
[Inject]
[NotNull]
@ -57,7 +85,7 @@ public partial class AdminLogin : IDisposable
/// </summary>
[Inject]
[NotNull]
private WebClientService? WebClientService { get; set; }
protected WebClientService? WebClientService { get; set; }
/// <summary>
///
@ -66,7 +94,10 @@ public partial class AdminLogin : IDisposable
[NotNull]
private IIPLocatorProvider? IPLocatorProvider { get; set; }
private string? ClassString => CssBuilder.Default("login-wrap")
/// <summary>
///
/// </summary>
protected string? ClassString => CssBuilder.Default("login-wrap")
.AddClass("is-mobile", UseMobileLogin)
.Build();
@ -78,17 +109,20 @@ public partial class AdminLogin : IDisposable
base.OnInitialized();
Title = DictsService.GetWebTitle();
PostUrl = QueryHelper.AddQueryString("/Account/Login", new Dictionary<string, string?>
PostUrl = QueryHelper.AddQueryString("Account/Login", new Dictionary<string, string?>
{
["ReturnUrl"] = ReturnUrl,
["AppId"] = AppId
});
}
void OnClickSwitchButton()
/// <summary>
///
/// </summary>
protected void OnClickSwitchButton()
{
var rem = RememberPassword ? "true" : "false";
PostUrl = QueryHelper.AddQueryString(UseMobileLogin ? "/Account/Mobile" : "/Account/Login", new Dictionary<string, string?>()
PostUrl = QueryHelper.AddQueryString(UseMobileLogin ? "Account/Mobile" : "Account/Login", new Dictionary<string, string?>()
{
[nameof(ReturnUrl)] = ReturnUrl,
["AppId"] = AppId,
@ -113,18 +147,29 @@ public partial class AdminLogin : IDisposable
}
}
Task OnRememberPassword(bool remember)
/// <summary>
///
/// </summary>
/// <param name="remember"></param>
/// <returns></returns>
protected Task OnRememberPassword(bool remember)
{
OnClickSwitchButton();
return Task.CompletedTask;
}
void OnSignUp()
/// <summary>
///
/// </summary>
protected void OnSignUp()
{
}
void OnForgotPassword()
/// <summary>
///
/// </summary>
protected void OnForgotPassword()
{
}

View File

@ -1,4 +1,29 @@
.form-signin-heading {
.wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
@media (min-width: 768px) {
.wrap {
background-color: #5bc0de;
background: url('../images/bg2.jpg') fixed no-repeat;
background-size: 100% 100%;
}
.container {
background: url('../images/bg3.png') no-repeat;
background-size: contain;
width: 704px;
height: 404px;
margin: 0 auto;
margin-top: calc(100vh / 2 - 190px);
}
}
.form-signin-heading {
margin: 0;
padding: 20px 15px;
text-align: center;
@ -123,3 +148,17 @@
color: #e0e0e0;
}
}
@media (min-width: 768px) {
.gitee.wrap {
background-color: #f1f2f7;
background-image: none;
}
.gitee .container {
width: 704px;
height: 404px;
margin: 0 auto;
margin-top: calc(100vh / 2 - 190px);
}
}

View File

@ -0,0 +1,11 @@
<div class="login-footer">
<ul class="login-footer-body">
<li><a href="https://www.gitee.com/LongbowEnterprise" target="_blank"><i class="fa fa-copyright"></i> Bootstrap Admin</a></li>
<li><a href="https://www.gitee.com/LongbowEnterprise/BootstrapAdmin/wikis" target="_blank">帮助文档</a></li>
<li><a href="Account/Login?View=Login">系统默认</a></li>
<li><a href="Account/Login?View=Login-Gitee">高仿码云</a></li>
<li><a href="Account/Login?View=Login-Blue">蓝色简约</a></li>
<li><a href="Account/Login?View=Login-Tec">科技动感</a></li>
<li><a href="Account/Login?View=Login-LTE">Admin-LTE</a></li>
</ul>
</div>

View File

@ -0,0 +1,24 @@
.login-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
}
.login-footer-body {
display: flex;
}
li {
list-style: none;
}
li:not(:last-child) {
margin-right: 1rem;
}
.white + .login-footer li a {
color: #fff;
}

View File

@ -0,0 +1,68 @@
@inherits AdminLogin
<div class="wrap">
<div class="container">
<section class="login-sidebox">
<div class="login-sidebox-content">
<div class="login-sidebox-header">
<div class="login-sidebox-logo">
<img alt="logo" src="../favicon.png"><span>Bootstrap Admin</span>
</div>
<h2 class="login-sidebox-subtitle">
通用后台权限管理系统
</h2>
</div>
<div class="login-sidebox-body">
<p>
基于 RBAC 的 NetCore 后台管理框架权限管理前后台分离支持多站点单点登录兼容所有主流浏览器内置微信、支付宝、QQ等多种登录方式内置多种样式可切换至 Blazor 多 Tabs 模式,权限控制细化到网页内任意元素(按钮、表格、文本框等等)
</p>
</div>
<div class="login-sidebox-footer">
<div>开源文档:<a href="https://gitee.com/LongbowEnterprise/BootstrapAdmin/wikis">码云托管平台 - Wiki</a></div>
</div>
</div>
</section>
<section class="login-form">
<div class="login-form-header">
<h2>登录</h2>
<span class="flex-self-end">
没有帐号?
<LinkButton Text="申请账号" OnClick="OnSignUp" />
</span>
</div>
<form method="post" class="form-signin" action="@PostUrl" @ref="LoginForm">
<div class="@ClassString">
@if (AllowMobile)
{
<div class="login-sms">
<SMSLogin />
</div>
}
<div class="login-up">
<UserLogin />
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<Checkbox @bind-Value="RememberPassword" Color="Color.Primary" ShowAfterLabel="true" DisplayText="记住密码自动登录" OnValueChanged="OnRememberPassword" />
<Block Condition="AllowMobile">
<SwitchButton @bind-ToggleState="UseMobileLogin" OnClick="OnClickSwitchButton" OffText="短信验证登录" OnText="用户密码登录" />
</Block>
</div>
<button class="btn-login btn-lg btn-block mt-4" data-bs-toggle="tooltip" title="不填写密码默认使用 Gitee 认证">登 录</button>
<div class="d-flex justify-content-center mt-3 mb-4">
<LinkButton Text="已有账号,忘记密码?" OnClick="OnForgotPassword" />
</div>
<Block Condition="AllowOAuth">
<Divider Text="其他方式登录" />
<div class="login-list">
<LinkButton Url="Account/Gitee" Title="使用 Gitee 帐号登录" ImageUrl="images/gitee.svg" />
<LinkButton Url="Account/Gitee" Title="使用 GitHub 帐号登录" ImageUrl="images/git.svg" />
<LinkButton Url="#" Title="微信-暂未实现" ImageUrl="images/weixin-2.svg" />
<LinkButton Url="Account/Tencent" Title="使用 QQ 账号登录" ImageUrl="images/qq.svg" />
<LinkButton Url="Account/Alipay" Title="使用支付宝账号登录" ImageUrl="images/zhifubao.svg" />
</div>
</Block>
</form>
</section>
</div>
</div>

View File

@ -0,0 +1,228 @@
.wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
color: #40485b;
background-color: #f1f2f7;
-webkit-font-smoothing: antialiased;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
}
.container {
display: flex;
box-shadow: 0px 20px 80px 0px rgb(0 0 0 / 30%);
width: 1000px;
height: 500px;
padding: 0;
margin: 0 auto;
margin-top: calc(100vh / 2 - 275px);
}
section {
width: 50%;
}
.login-sidebox {
position: relative;
background: -webkit-gradient(linear, left bottom, left top, from(#3a485a), to(#607089));
background: linear-gradient(0deg, #3a485a 0%, #607089 100%);
color: #fff;
}
.login-sidebox::before,
.login-sidebox::after {
content: '';
top: 0;
right: 0;
bottom: 0;
left: 0;
position: absolute;
}
.login-sidebox::before {
background: url(../images/left-1.png) no-repeat 0 0;
}
.login-sidebox::after {
background: url(../images/left-2.png) no-repeat right bottom;
}
.login-sidebox .login-sidebox-content {
padding: 80px 80px 48px;
position: relative;
z-index: 1;
}
.login-sidebox-header {
margin-bottom: 40px;
}
.login-sidebox-logo {
display: flex;
align-items: center;
margin-bottom: 14px;
}
.login-sidebox-logo img {
width: 48px;
height: auto;
border-radius: 50%;
margin-right: 14px;
}
.login-sidebox-logo span {
display: inline;
font-size: 1.5rem;
font-weight: 700;
}
.login-sidebox-subtitle {
font-size: 20pt;
font-weight: normal;
}
.login-sidebox-footer {
margin-top: 40px;
border-top: solid 1px #ddd;
padding-top: 28px;
font-size: 0.875rem;
font-weight: 500;
}
.login-sidebox-footer a {
cursor: pointer;
color: #fff;
}
.login-form {
padding: 64px;
font-size: 0.875rem;
}
.login-form-header {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 20px;
}
.login-form-header h2 {
margin-bottom: 0;
font-size: 24px;
font-weight: bold;
line-height: 32px;
}
.login-list {
display: flex;
justify-content: space-between;
}
.login-list .item {
width: 32px;
height: 32px;
}
.forget-password {
padding: 16px 0;
}
.form-signin-heading {
margin: 0;
padding: 20px 15px;
text-align: center;
background-color: #41cac0;
border-radius: 5px 5px 0 0;
color: #fff;
}
.login-sms,
.is-mobile .login-up {
display: none;
}
.is-mobile .login-sms {
display: block;
}
::deep .btn-login {
color: #fff;
background: #fe7300;
border-color: transparent;
text-transform: uppercase;
font-weight: 300;
font-family: 'Open Sans', sans-serif;
line-height: 1;
}
::deep .btn-login:hover {
background: #fe7300;
}
::deep .divider-wrap {
background-color: #7c86bb;
}
::deep .divider-text {
background-color: #f1f2f7;
}
.login-list {
display: flex;
justify-content: space-between;
}
.login-list ::deep img {
width: 32px;
height: 32px;
}
::deep .btn-sms {
width: 140px;
}
@media (max-width: 767px) {
.form-signin {
margin: 0 auto;
background: #fff;
border-radius: 5px;
max-width: 320px;
border: solid 1px #ddd;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.125);
}
}
@media (min-width: 768px) {
.form-signin-heading {
padding: 28px 0;
background-color: transparent;
}
.slidercaptcha {
width: 300px;
}
.slidercaptcha, .slidercaptcha.oauth {
height: 280px;
}
.slidercaptcha.card .card-body {
padding: 15px 15px 0 15px;
}
.login-footer {
width: 100%;
display: flex;
justify-content: space-around;
}
.login-footer .login-footer-body li a {
color: #e0e0e0;
}
}
::deep .divider {
margin: 1.5rem 0;
}

View File

@ -2,7 +2,7 @@
<Table TItem="TItem" IsBordered="true" IsStriped="true" IsMultipleSelect="IsMultipleSelect" @ref="Instance"
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader"
IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeIcon="fa-chevron-circle-right"
IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-chevron-circle-right"
ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch"
ShowEmpty="ShowEmpty" EmptyText="暂无数据" EmptyImage="images/empty.svg" SortString="@SortString"
OnQueryAsync="OnQueryAsync!" OnDeleteAsync="OnDeleteAsync!" OnSaveAsync="OnSaveAsync!"
@ -10,7 +10,7 @@
ShowToolbar="ShowToolbar" ShowExtendButtons="ShowExtendButtons" ShowAddButton="@AuthorizeButton("add")"
ShowDeleteButton="@AuthorizeButton("del")" ShowEditButton="@AuthorizeButton("edit")"
ShowCardView="true" ShowColumnList="true" ExtendButtonColumnWidth="@ExtendButtonColumnWidth"
CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows"
CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows" ModelEqualityComparer="ModelEqualityComparer!"
ShowEditButtonCallback="ShowEditButtonCallback!" ShowDeleteButtonCallback="ShowDeleteButtonCallback!"
TableToolbarTemplate="TableToolbarTemplate" TableColumns="TableColumns" EditTemplate="EditTemplate!"
CustomerSearchTemplate="CustomerSearchTemplate!" RowButtonTemplate="RowButtonTemplate!">

View File

@ -145,7 +145,13 @@ namespace BootstrapAdmin.Web.Components
///
/// </summary>
[Parameter]
public Func<TItem, Task<IEnumerable<TItem>>>? OnTreeExpand { get; set; }
public Func<TItem, Task<IEnumerable<TableTreeNode<TItem>>>>? OnTreeExpand { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public Func<IEnumerable<TItem>, Task<IEnumerable<TableTreeNode<TItem>>>>? TreeNodeConverter { get; set; }
/// <summary>
///
@ -177,6 +183,12 @@ namespace BootstrapAdmin.Web.Components
[Parameter]
public Func<TItem, bool>? ShowDeleteButtonCallback { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public Func<TItem, TItem, bool>? ModelEqualityComparer { get; set; }
[NotNull]
private Table<TItem>? Instance { get; set; }

View File

@ -30,11 +30,9 @@
<div class="col-12 col-sm-12">
<BootstrapInput @bind-Value="Value.Favicon" placeholder="不可为空2000字以内"></BootstrapInput>
</div>
<div class="col-12 col-sm-12">
<div class="table-modal-footer text-end">
<Button ButtonType="ButtonType.Button" Text="关闭" Icon="fa fa-close" Color="Color.Secondary" OnClick="OnClickClose"></Button>
<Button ButtonType="ButtonType.Submit" Text="保存" Icon="fa fa-save"></Button>
</div>
</div>
</div>
<div class="form-footer">
<Button ButtonType="ButtonType.Button" Text="关闭" Icon="fa fa-close" Color="Color.Secondary" OnClick="OnClickClose"></Button>
<Button ButtonType="ButtonType.Submit" Text="保存" Icon="fa fa-save"></Button>
</div>
</ValidateForm>

View File

@ -60,13 +60,12 @@ public partial class ClientDialog
}
}
private Task OnSaveCleint(EditContext context)
private async Task OnSaveCleint(EditContext context)
{
if (OnSave != null)
{
OnSave(Value);
await OnSave(Value);
}
return Task.CompletedTask;
}
private async Task OnClickClose()

View File

@ -3,8 +3,8 @@
{
var value = DictService.GetClientUrl(item.Value);
<div class="col-12 col-sm-12">
<BootstrapInputGroup>
<BootstrapInputGroupLabel Text="@item.Key"></BootstrapInputGroupLabel>
<BootstrapInputGroup @key="item.Key">
<BootstrapInputGroupLabel DisplayText="@item.Key"></BootstrapInputGroupLabel>
<BootstrapInput Value="value" Readonly="true"></BootstrapInput>
<PopConfirmButton Placement="Placement.Top" Color="Color.Danger" Icon="fa fa-trash-o" ConfirmIcon="fa fa-exclamation-circle text-danger" ConfirmButtonColor="Color.Danger" Text="删除" Content="确定删除当前应用吗?" OnConfirm="() => OnDeleteClient(item.Value)" />
<Button Icon="fa fa-edit" Text="编辑" OnClickWithoutRender="() => OnEditClient(item.Value)" />

View File

@ -0,0 +1,29 @@
<div class="row g-3 form-inline">
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="Value.Name" DisplayText="菜单名称" ShowLabel="true"></BootstrapInput>
</div>
<div class="col-12 col-sm-6 col-md-6">
<MenuTree @bind-Value="Value.ParentId" Lookup="@ParementMenus" DisplayText="父级菜单"></MenuTree>
</div>
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="Value.Order"></BootstrapInput>
</div>
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="Value.Icon"></BootstrapInput>
</div>
<div class="col-12">
<BootstrapInput @bind-Value="Value.Url"></BootstrapInput>
</div>
<div class="col-12 col-sm-6 col-md-6">
<Select @bind-Value="Value.Category"></Select>
</div>
<div class="col-12 col-sm-6 col-md-6">
<Select Items="@Targets" @bind-Value="Value.Target"></Select>
</div>
<div class="col-12 col-sm-6 col-md-6">
<Select @bind-Value="Value.IsResource"></Select>
</div>
<div class="col-12 col-sm-6 col-md-6">
<Select Items="@Apps" @bind-Value="Value.Application"></Select>
</div>
</div>

View File

@ -0,0 +1,45 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapAdmin.DataAccess.Models;
namespace BootstrapAdmin.Web.Components;
/// <summary>
///
/// </summary>
public partial class MenuEditor
{
/// <summary>
///
/// </summary>
[Parameter]
[EditorRequired]
[NotNull]
public Navigation? Value { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
[EditorRequired]
[NotNull]
public List<SelectedItem>? ParementMenus { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
[EditorRequired]
[NotNull]
public List<SelectedItem>? Targets { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
[EditorRequired]
[NotNull]
public List<SelectedItem>? Apps { get; set; }
}

View File

@ -0,0 +1,8 @@
<div class="tree-dialog">
<label class="form-label">@DisplayText</label>
<BootstrapInputGroup>
<Display Value="Value" Lookup="Lookup" ShowLabel="false"></Display>
<Button Icon="fa fa-times" Color="Color.Secondary" OnClick="OnClearText"></Button>
<Button Icon="fa fa-bars" OnClick="OnSelectMenu"></Button>
</BootstrapInputGroup>
</div>

View File

@ -0,0 +1,87 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapAdmin.Web.Core;
using BootstrapAdmin.Web.Services;
using Microsoft.AspNetCore.Components.Web;
namespace BootstrapAdmin.Web.Components;
/// <summary>
///
/// </summary>
public partial class MenuTree
{
/// <summary>
///
/// </summary>
[Parameter]
[NotNull]
public List<SelectedItem>? Lookup { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
[NotNull]
public string? DisplayText { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
[NotNull]
public string? Value { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
[NotNull]
public EventCallback<string> ValueChanged { get; set; }
[Inject]
[NotNull]
private DialogService? DialogService { get; set; }
private DialogOption? Option { get; set; }
private async Task OnSelectMenu()
{
Option = new DialogOption()
{
IsScrolling = true,
Title = "选择菜单",
BodyTemplate = BootstrapDynamicComponent.CreateComponent<ParentMenuTree>(new Dictionary<string, object?>
{
[nameof(ParentMenuTree.Value)] = Value,
[nameof(ParentMenuTree.ValueChanged)] = EventCallback.Factory.Create<string>(this, v => OnValueChanged(v))
}).Render(),
FooterTemplate = BootstrapDynamicComponent.CreateComponent<Button>(new Dictionary<string, object?>
{
[nameof(Button.Color)] = Color.Primary,
[nameof(Button.Text)] = "确认",
[nameof(Button.OnClick)] = EventCallback.Factory.Create<MouseEventArgs>(this, () =>
{
Option?.Dialog.Close();
}),
}).Render()
};
await DialogService.Show(Option);
}
private Task OnClearText() => OnValueChanged("0");
private async Task OnValueChanged(string v)
{
if (Value != v)
{
Value = v;
if (ValueChanged.HasDelegate)
{
await ValueChanged.InvokeAsync(Value);
}
}
}
}

View File

@ -0,0 +1,7 @@
.tree-dialog {
display: flex;
}
.tree-dialog ::deep .input-group {
flex-wrap: nowrap !important
}

View File

@ -1,8 +1,15 @@
@inherits AssignmentBase<Navigation>
@inherits ComponentBase
<Tree Items="InternalItems" ShowCheckbox="true" ShowIcon="true" OnTreeItemChecked="@OnTreeItemChecked" />
<div class="tree-menu">
<Tree Items="InternalItems" ShowCheckbox="true" ShowIcon="true" @ref="MenusTree" />
<div class="form-footer">
<Button Color="Color.Secondary" Icon="fa fa-times" Text="关闭" OnClickWithoutRender="OnClickClose" />
<Button Color="Color.Primary" Icon="fa fa-save" Text="保存" OnClickWithoutRender="OnClickSave" />
</div>
</div>
@code {
RenderFragment<Navigation> RenderTreeItem => item =>
@<div class="d-flex flex-fill"><span class="flex-fill">@item.Name</span><span class="mx-3">@item.Order</span><span class="app-type">@item.IsResource.ToDescriptionString()</span><span class="app-text">@GetApp(item.Application)</span></div>;
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapAdmin.DataAccess.Models;
using BootstrapAdmin.Web.Core;
using BootstrapAdmin.Web.Extensions;
@ -15,10 +16,45 @@ public partial class NavigationTree
[NotNull]
private List<TreeItem>? InternalItems { get; set; }
[NotNull]
private Tree? MenusTree { get; set; }
[Inject]
[NotNull]
private IDict? DictService { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
[EditorRequired]
[NotNull]
public List<Navigation>? Items { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
[EditorRequired]
[NotNull]
public List<string>? Value { get; set; }
/// <summary>
/// 关闭弹窗回调委托
/// </summary>
[Parameter]
[EditorRequired]
[NotNull]
public Func<Task>? OnClose { get; set; }
/// <summary>
/// 保存按钮回调委托
/// </summary>
[Parameter]
[EditorRequired]
[NotNull]
public Func<List<string?>, Task>? OnSave { get; set; }
/// <summary>
///
/// </summary>
@ -29,12 +65,9 @@ public partial class NavigationTree
InternalItems = Items.ToTreeItemList(Value, RenderTreeItem);
}
private Task OnTreeItemChecked(List<TreeItem> items)
{
Value = items.Select(i => i.Key!.ToString()!).ToList();
OnValueChanged(Value);
return Task.CompletedTask;
}
private string GetApp(string? app) => DictService.GetApps().FirstOrDefault(i => i.Key == app).Value ?? "未设置";
private Task OnClickClose() => OnClose();
private Task OnClickSave() => OnSave(MenusTree.GetCheckedItems().Select(i => i.Key?.ToString()).ToList());
}

View File

@ -0,0 +1,13 @@
.tree-menu {
display: flex;
flex-direction: column;
}
.tree-menu ::deep .tree {
flex: 1;
margin: -1rem;
padding: 1rem;
max-height: calc(100vh - 173px);
overflow-y: auto;
overflow-x: hidden;
}

View File

@ -0,0 +1,6 @@
<Tree Items="InternalItems" ShowRadio="true" ShowIcon="true" OnTreeItemChecked="@OnTreeItemChecked" />
@code {
RenderFragment<Navigation> RenderTreeItem => item =>
@<div class="d-flex flex-fill"><span class="flex-fill">@item.Name</span><span class="mx-3">@item.Order</span><span class="app-type">@item.IsResource.ToDescriptionString()</span><span class="app-text">@GetApp(item.Application)</span></div>;
}

View File

@ -0,0 +1,66 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapAdmin.Web.Core;
using BootstrapAdmin.Web.Extensions;
using BootstrapAdmin.Web.Services;
namespace BootstrapAdmin.Web.Components;
/// <summary>
///
/// </summary>
public partial class ParentMenuTree
{
/// <summary>
///
/// </summary>
[Parameter]
[EditorRequired]
[NotNull]
public string? Value { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[NotNull]
private List<TreeItem>? InternalItems { get; set; }
[Inject]
[NotNull]
private INavigation? NavigationService { get; set; }
[Inject]
[NotNull]
private IDict? DictService { get; set; }
[Inject]
[NotNull]
private BootstrapAppContext? Context { get; set; }
/// <summary>
///
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
var items = NavigationService.GetAllMenus(Context.UserName);
InternalItems = items.ToTreeItemList(new List<string> { Value }, RenderTreeItem);
}
private async Task OnTreeItemChecked(List<TreeItem> items)
{
Value = items.First().Key?.ToString();
if (ValueChanged.HasDelegate)
{
await ValueChanged.InvokeAsync(Value);
}
}
private string GetApp(string? app) => DictService.GetApps().FirstOrDefault(i => i.Key == app).Value ?? "未设置";
}

View File

@ -0,0 +1 @@
<BootstrapBlazor.Components.Console Items="@Messages" Height="280" ShowAutoScroll="true" />

View File

@ -0,0 +1,77 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapAdmin.Web.Models;
using Longbow.Tasks;
namespace BootstrapAdmin.Web.Components;
/// <summary>
///
/// </summary>
public partial class TaskInfo : IDisposable
{
/// <summary>
///
/// </summary>
[Parameter]
[NotNull]
[EditorRequired]
public TasksModel? Model { get; set; }
private List<ConsoleMessageItem> Messages { get; } = new(24);
/// <summary>
///
/// </summary>
/// <param name="firstRender"></param>
/// <returns></returns>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var sche = TaskServicesManager.Get(Model.Name);
if (sche != null)
{
sche.Triggers.First().PulseCallback = async t => await DispatchMessage(t);
await DispatchMessage(sche.Triggers.First());
}
}
}
private async Task DispatchMessage(ITrigger trigger)
{
var message = $"Trigger({trigger.GetType().Name}) LastRuntime: {trigger.LastRuntime} Run({trigger.LastResult}) NextRuntime: {trigger.NextRuntime} Elapsed: {trigger.LastRunElapsedTime.TotalSeconds}";
Messages.Add(new ConsoleMessageItem()
{
Message = message
});
if (Messages.Count > 20)
{
Messages.RemoveAt(0);
}
await InvokeAsync(StateHasChanged);
}
private void Dispose(bool disposing)
{
if (disposing)
{
var sche = TaskServicesManager.Get(Model.Name);
if (sche != null)
{
sche.Triggers.First().PulseCallback = null;
}
}
}
/// <summary>
///
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

View File

@ -18,9 +18,48 @@ public static class DialogExtensions
public static Task ShowAssignmentDialog(this DialogService dialogService, string title, List<SelectedItem> items, List<string> value, Func<Task<bool>> saveCallback, ToastService? toast) => dialogService.ShowDialog<Assignment, SelectedItem>(title, items, value, saveCallback, toast);
/// <summary>
///
/// 弹出菜单分配弹窗
/// </summary>
public static Task ShowNavigationDialog(this DialogService dialogService, string title, List<Navigation> items, List<string> value, Func<Task<bool>> saveCallback, ToastService? toast) => dialogService.ShowDialog<NavigationTree, Navigation>(title, items, value, saveCallback, toast);
/// <param name="dialogService"></param>
/// <param name="title">弹窗标题</param>
/// <param name="menus">当前用户可用所有菜单集合</param>
/// <param name="value">已分配菜单集合</param>
/// <param name="saveCallback"></param>
/// <param name="toast"></param>
/// <returns></returns>
public static async Task ShowNavigationDialog(this DialogService dialogService, string title, List<Navigation> menus, List<string> value, Func<List<string>, Task<bool>> saveCallback, ToastService? toast)
{
var option = new DialogOption
{
Title = title,
ShowFooter = false,
IsScrolling = true,
Class = "modal-dialog-menu"
};
var parameters = new Dictionary<string, object?>()
{
[nameof(NavigationTree.Items)] = menus,
[nameof(NavigationTree.Value)] = value,
[nameof(NavigationTree.OnClose)] = () => option.Dialog.Close(),
[nameof(NavigationTree.OnSave)] = new Func<List<string>, Task>(async items =>
{
var ret = await saveCallback(items);
if (toast != null)
{
if (ret)
{
await toast.Success("分配操作", "操作成功!");
}
else
{
await toast.Error("分配操作", "操作失败,请联系相关管理员!");
}
}
})
};
option.Component = BootstrapDynamicComponent.CreateComponent<NavigationTree>(parameters);
await dialogService.Show(option);
}
private static Task ShowDialog<TBody, TItem>(this DialogService dialogService, string title, List<TItem> items, List<string> value, Func<Task<bool>> saveCallback, ToastService? toast) where TBody : AssignmentBase<TItem> => dialogService.ShowSaveDialog<TBody>(title, async () =>
{
@ -38,15 +77,15 @@ public static class DialogExtensions
}
return ret;
},
new Dictionary<string, object?>
parameters =>
{
[nameof(AssignmentBase<TItem>.Items)] = items,
[nameof(AssignmentBase<TItem>.Value)] = value,
[nameof(AssignmentBase<TItem>.OnValueChanged)] = new Action<List<string>>(v =>
parameters.Add(nameof(AssignmentBase<TItem>.Items), items);
parameters.Add(nameof(AssignmentBase<TItem>.Value), value);
parameters.Add(nameof(AssignmentBase<TItem>.OnValueChanged), new Action<List<string>>(v =>
{
value.Clear();
value.AddRange(v);
})
}));
},
op =>
{

View File

@ -67,19 +67,13 @@ namespace Microsoft.Extensions.DependencyInjection
// var connString = configuration.GetConnectionString("bb");
// builder.UseConnectionString(FreeSql.DataType.Sqlite, connString);
//#if DEBUG
// //调试sql语句输出
// 调试sql语句输出
// builder.UseMonitorCommand(cmd => System.Console.WriteLine(cmd.CommandText));
//#endif
// });
// 增加 PetaPoco 数据服务
services.AddPetaPocoDataAccessServices((provider, builder) =>
{
var configuration = provider.GetRequiredService<IConfiguration>();
var connString = configuration.GetConnectionString("bb");
builder.UsingProvider<SQLiteDatabaseProvider>()
.UsingConnectionString(connString);
});
services.AddPetaPocoDataAccessServices();
return services;
}

View File

@ -0,0 +1,69 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapAdmin.Web.Components;
using BootstrapAdmin.Web.Core;
using BootstrapAdmin.Web.Shared;
using BootstrapAdmin.Web.Utils;
using Microsoft.AspNetCore.Components.Rendering;
namespace BootstrapAdmin.Web.Pages.Account;
/// <summary>
///
/// </summary>
[Layout(typeof(LoginLayout))]
[Route("/Account/Login")]
public class Login : ComponentBase
{
/// <summary>
///
/// </summary>
[SupplyParameterFromQuery]
[Parameter]
public string? ReturnUrl { get; set; }
/// <summary>
///
/// </summary>
[SupplyParameterFromQuery]
[Parameter]
public string? AppId { get; set; }
/// <summary>
///
/// </summary>
[SupplyParameterFromQuery]
[Parameter]
public string? View { get; set; }
[Inject]
[NotNull]
private IDict? DictsService { get; set; }
/// <summary>
/// BuildRenderTree 方法
/// </summary>
/// <param name="builder"></param>
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (!string.IsNullOrEmpty(View))
{
View = $"0-{View}";
}
var view = LoginHelper.GetCurrentLoginTheme(View ?? DictsService.GetCurrentLogin());
var componentType = view switch
{
"gitee" => typeof(AdminLoginGitee),
_ => typeof(AdminLogin)
};
builder.OpenComponent(0, componentType);
builder.AddAttribute(1, nameof(AdminLogin.ReturnUrl), ReturnUrl);
builder.AddAttribute(2, nameof(AdminLogin.AppId), AppId);
builder.CloseComponent();
builder.OpenComponent<AdminLoginFooter>(3);
builder.CloseComponent();
}
}

View File

@ -1,8 +0,0 @@
@layout LoginLayout
@page "/Account/Login"
<div class="wrap">
<div class="container">
<AdminLogin ReturnUrl="@ReturnUrl" AppId="@AppId" />
</div>
</div>

View File

@ -1,24 +0,0 @@
.wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
@media (min-width: 768px) {
.wrap {
background-color: #5bc0de;
background: url('../images/bg2.jpg') fixed no-repeat;
background-size: 100% 100%;
}
.container {
background: url('../images/bg3.png') no-repeat;
background-size: contain;
width: 704px;
height: 404px;
margin: 0 auto;
margin-top: calc(100vh / 2 - 190px);
}
}

View File

@ -1,8 +1,8 @@
@page "/Admin/Menus"
<AdminTable TItem="DataAccess.Models.Navigation"
IsTree="true" OnTreeExpand="OnTreeExpand" ExtendButtonColumnWidth="200"
CustomerSearchModel="SearchModel" OnQueryAsync="OnQueryAsync">
IsTree="true" OnTreeExpand="OnTreeExpand" TreeNodeConverter="TreeNodeConverter" ModelEqualityComparer="ModelEqualityComparer"
ExtendButtonColumnWidth="200" CustomerSearchModel="SearchModel" OnQueryAsync="OnQueryAsync">
<TableToolbarTemplate>
<TableToolbarButton TItem="DataAccess.Models.Navigation" Color="Color.Info" Icon="fa fa-sitemap" Text="分配角色"
IsEnableWhenSelectedOneRow="true" IsShow="@AuthorizeButton("assignRole")"
@ -23,35 +23,7 @@
<TableColumn @bind-Field="@context.Application" Filterable="true" Lookup="Apps"></TableColumn>
</TableColumns>
<EditTemplate Context="v">
<div class="row g-3 form-inline">
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="v.Name" DisplayText="菜单名称" ShowLabel="true"></BootstrapInput>
</div>
<div class="col-12 col-sm-6 col-md-6">
<Select @bind-Value="v.ParentId" Items="@ParementMenus" IsDisabled="@(v.ParentId == "0")" DisplayText="父级菜单" ShowLabel="true"></Select>
</div>
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="v.Order"></BootstrapInput>
</div>
<div class="col-12 col-sm-6 col-md-6">
<BootstrapInput @bind-Value="v.Icon"></BootstrapInput>
</div>
<div class="col-12">
<BootstrapInput @bind-Value="v.Url"></BootstrapInput>
</div>
<div class="col-12 col-sm-6 col-md-6">
<Select @bind-Value="v.Category"></Select>
</div>
<div class="col-12 col-sm-6 col-md-6">
<Select Items="@Targets" @bind-Value="v.Target"></Select>
</div>
<div class="col-12 col-sm-6 col-md-6">
<Select @bind-Value="v.IsResource"></Select>
</div>
<div class="col-12 col-sm-6 col-md-6">
<Select Items="@Apps" @bind-Value="v.Application"></Select>
</div>
</div>
<MenuEditor Value="v" ParementMenus="ParementMenus" Targets="Targets" Apps="Apps" />
</EditTemplate>
<RowButtonTemplate>
<TableCellButton Size="Size.ExtraSmall" IsShow="@AuthorizeButton("assignRole")" Color="Color.Info" Icon="fa fa-sitemap" Text="分配角色" OnClick="() => OnAssignmentRoles(context)" />

View File

@ -123,9 +123,30 @@ public partial class Menus
});
}
private Task<IEnumerable<Navigation>> OnTreeExpand(Navigation menu)
private Task<IEnumerable<TableTreeNode<Navigation>>> OnTreeExpand(Navigation menu)
{
var navs = NavigationService.GetAllMenus(AppContext.UserName);
return Task.FromResult(navs.Where(m => m.ParentId == menu.Id).OrderBy(m => m.Order).AsEnumerable());
return Task.FromResult(navs.Where(m => m.ParentId == menu.Id).OrderBy(m => m.Order).AsEnumerable().Select(i => new TableTreeNode<Navigation>(i)));
}
private Task<IEnumerable<TableTreeNode<Navigation>>> TreeNodeConverter(IEnumerable<Navigation> items)
{
var ret = BuildTreeNodes(items, "0");
return Task.FromResult(ret);
IEnumerable<TableTreeNode<Navigation>> BuildTreeNodes(IEnumerable<Navigation> items, string parentId)
{
var navs = NavigationService.GetAllMenus(AppContext.UserName);
var ret = new List<TableTreeNode<Navigation>>();
ret.AddRange(items.Where(i => i.ParentId == parentId).Select((nav, index) => new TableTreeNode<Navigation>(nav)
{
HasChildren = navs.Any(i => i.ParentId == nav.Id),
IsExpand = navs.Any(i => i.ParentId == nav.Id),
Items = BuildTreeNodes(navs.Where(i => i.ParentId == nav.Id), nav.Id)
}));
return ret;
}
}
private bool ModelEqualityComparer(Navigation x, Navigation y) => x.Id == y.Id;
}

View File

@ -56,6 +56,10 @@ public partial class Profiles
[NotNull]
private string? DefaultLogoFolder { get; set; }
[CascadingParameter]
[NotNull]
private Layout? Layout { get; set; }
/// <summary>
///
/// </summary>
@ -109,8 +113,13 @@ public partial class Profiles
private async Task OnSaveDisplayName(EditContext context)
{
var ret = UserService.SaveDisplayName(CurrentUser.DisplayName, CurrentUser.UserName);
var ret = UserService.SaveDisplayName(CurrentUser.UserName, CurrentUser.DisplayName);
await ShowToast(ret, "显示名称");
if (ret)
{
AppContext.DisplayName = CurrentUser.DisplayName;
await RenderLayout("displayName");
}
}
private async Task OnSavePassword(EditContext context)
@ -177,4 +186,12 @@ public partial class Profiles
await ShowToast(ret, "用户头像", "删除");
return ret;
}
private async Task RenderLayout(string key)
{
if (Layout.OnUpdateAsync != null)
{
await Layout.OnUpdateAsync(key);
}
}
}

View File

@ -72,12 +72,12 @@ public partial class Roles
private async Task OnAssignmentMenus(Role role)
{
var apps = NavigationService.GetAllMenus(AppContext.UserName);
var menus = NavigationService.GetAllMenus(AppContext.UserName);
var values = NavigationService.GetMenusByRoleId(role.Id);
await DialogService.ShowNavigationDialog($"分配菜单 - 当前角色: {role.RoleName}", apps, values, () =>
await DialogService.ShowNavigationDialog($"分配菜单 - 当前角色: {role.RoleName}", menus, values, items =>
{
var ret = NavigationService.SaveMenusByRoleId(role.Id, values);
var ret = NavigationService.SaveMenusByRoleId(role.Id, items);
return Task.FromResult(ret);
}, ToastService);
}

View File

@ -48,7 +48,7 @@ public partial class Settings
base.OnInitialized();
IsDemo = DictService.IsDemo();
Logins = DictService.GetLogins().ToSelectedItemList();
Logins = DictService.GetLogins().ToSelectedItemList().OrderBy(i => i.Value).ToList();
Themes = DictService.GetThemes().ToSelectedItemList();
IPLocators = DictService.GetIpLocators().ToSelectedItemList();
IPLocators.Insert(0, new SelectedItem("", "未选择"));
@ -180,9 +180,9 @@ public partial class Settings
private async Task RenderLayout(string key)
{
if (Layout.OnUpdate != null)
if (Layout.OnUpdateAsync != null)
{
await Layout.OnUpdate(key);
await Layout.OnUpdateAsync(key);
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapAdmin.Web.Components;
using BootstrapAdmin.Web.Core;
using BootstrapAdmin.Web.Extensions;
using BootstrapAdmin.Web.Models;
@ -32,6 +33,10 @@ public partial class Tasks
[NotNull]
private IDict? DictService { get; set; }
[Inject]
[NotNull]
private DialogService? DialogService { get; set; }
private bool IsDemo { get; set; }
/// <summary>
@ -157,9 +162,18 @@ public partial class Tasks
return Task.CompletedTask;
}
private static Task OnLog(TasksModel model)
private async Task OnLog(TasksModel model)
{
return Task.CompletedTask;
var option = new DialogOption()
{
Class = "modal-dialog-task",
Title = $"{model.Name} - 日志窗口(最新 20 条)",
Component = BootstrapDynamicComponent.CreateComponent<TaskInfo>(new Dictionary<string, object?>
{
[nameof(TaskInfo.Model)] = model
})
};
await DialogService.Show(option);
}
private static bool OnCheckTaskStatus(TasksModel model) => model.Status != SchedulerStatus.Disabled;

View File

@ -8,7 +8,7 @@
}
},
"profiles": {
"BootstrapAdmin.Web": {
"Console": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,

View File

@ -25,24 +25,24 @@ class AdminTaskService : BackgroundService
/// <returns></returns>
protected override Task ExecuteAsync(CancellationToken stoppingToken) => Task.Run(() =>
{
TaskServicesManager.GetOrAdd("单次任务", token => Task.Delay(1000));
TaskServicesManager.GetOrAdd("周期任务", token => Task.Delay(1000), TriggerBuilder.Default.WithInterval(10000).Build());
TaskServicesManager.GetOrAdd("Cron 任务", token => Task.Delay(1000), TriggerBuilder.Build(Cron.Secondly(5)));
TaskServicesManager.GetOrAdd("超时任务", token => Task.Delay(2000), TriggerBuilder.Default.WithTimeout(1000).WithInterval(1000).WithRepeatCount(2).Build());
TaskServicesManager.GetOrAdd("单次任务", token => Task.Delay(1000, token));
TaskServicesManager.GetOrAdd("周期任务", token => Task.Delay(1000, token), TriggerBuilder.Default.WithInterval(10000).Build());
TaskServicesManager.GetOrAdd("Cron 任务", token => Task.Delay(1000, token), TriggerBuilder.Build(Cron.Secondly(5)));
TaskServicesManager.GetOrAdd("超时任务", token => Task.Delay(2000, token), TriggerBuilder.Default.WithTimeout(1000).WithInterval(1000).WithRepeatCount(2).Build());
// 本机调试时此处会抛出异常,配置文件中默认开启了任务持久化到物理文件,此处异常只有首次加载时会抛出
// 此处异常是示例自定义任务内部未进行捕获异常时任务仍然能继续运行,不会导致整个进程崩溃退出
// 此处代码可注释掉
//TaskServicesManager.GetOrAdd("故障任务", token => throw new Exception("故障任务"));
TaskServicesManager.GetOrAdd("取消任务", token => Task.Delay(1000)).Triggers.First().Enabled = false;
TaskServicesManager.GetOrAdd("取消任务", token => Task.Delay(1000, token)).Triggers.First().Enabled = false;
// 创建任务并禁用
TaskServicesManager.GetOrAdd("禁用任务", token => Task.Delay(1000)).Status = SchedulerStatus.Disabled;
TaskServicesManager.GetOrAdd("禁用任务", token => Task.Delay(1000, token)).Status = SchedulerStatus.Disabled;
// 真实任务负责批次写入数据执行脚本到日志中
TaskServicesManager.GetOrAdd<DBLogTask>("SQL日志", TriggerBuilder.Build(Cron.Minutely()));
// 真实任务负责周期性设置健康检查结果开关为开启
TaskServicesManager.GetOrAdd("健康检查", token => Task.FromResult(DictService.SaveHealthCheck()), TriggerBuilder.Build(Cron.Minutely(10)));
});
}, stoppingToken);
}

View File

@ -3,10 +3,10 @@
<Layout SideWidth="0" IsPage="true" IsFullSide="true" IsFixedHeader="true"
ShowFooter="true" ShowGotoTop="true" ShowCollapseBar="true" Menus="@MenuItems"
OnAuthorizing="@OnAuthorizing" OnErrorHandleAsync="OnErrorHandleAsync"
UseTabSet="true" TabDefaultUrl="/Admin/Index" OnUpdate="OnUpdate">
UseTabSet="true" TabDefaultUrl="/Admin/Index" OnUpdateAsync="OnUpdateAsync">
<Header>
<span class="ms-3 flex-fill">Bootstrap of Blazor</span>
<Logout ImageUrl="@Icon" DisplayName="@DisplayName" UserName="@UserName">
<Logout ImageUrl="@Icon" DisplayName="@Context.DisplayName" UserName="@UserName">
<LinkTemplate>
<a href="/Admin/Profiles"><i class="fa fa-suitcase"></i>个人中心</a>
<a href="/Admin/Index"><i class="fa fa-cog"></i>设置</a>
@ -26,7 +26,7 @@
<div class="layout-user">
<img class="layout-avatar" src="@Icon">
<div class="layout-title">
<span>@DisplayName</span>
<span>@Context.DisplayName</span>
</div>
<div class="layout-user-state"></div>
</div>

View File

@ -74,8 +74,6 @@ namespace BootstrapAdmin.Web.Shared
private string? Footer { get; set; }
private string? DisplayName { get; set; }
private string? UserName { get; set; }
private bool Lock { get; set; }
@ -133,9 +131,8 @@ namespace BootstrapAdmin.Web.Shared
if (!string.IsNullOrEmpty(UserName))
{
var user = UsersService.GetUserByUserName(UserName);
DisplayName = user?.DisplayName ?? "未注册账户";
Context.UserName = UserName;
Context.DisplayName = DisplayName;
Context.DisplayName = user?.DisplayName ?? "未注册账户";
Icon = string.IsNullOrEmpty(user?.Icon) ? "/images/uploader/default.jpg" : GetIcon(user.Icon);
MenuItems = NavigationsService.GetAllMenus(UserName).ToMenus();
@ -161,7 +158,7 @@ namespace BootstrapAdmin.Web.Shared
/// <summary>
///
/// </summary>
public Task OnUpdate(string key)
public Task OnUpdateAsync(string key)
{
if (key == "title")
{

View File

@ -50,4 +50,20 @@ public static class LoginHelper
return returnUrl ?? "/Admin/Index";
}
/// <summary>
/// 将字典表中的配置 1-Login-Gitee 转化为 gitee
/// </summary>
/// <param name="loginTheme"></param>
/// <returns></returns>
public static string? GetCurrentLoginTheme(string loginTheme)
{
string? ret = null;
var segs = loginTheme.Split('-');
if (segs.Length == 3)
{
ret = segs[2].ToLowerInvariant();
}
return ret;
}
}

View File

@ -13,4 +13,4 @@
@using BootstrapAdmin.Web.Components
@using BootstrapAdmin.Web.Models
@using BootstrapAdmin.Web.Shared
@using BootstrapAdmin.Web.Shared

View File

@ -16,59 +16,9 @@
}
},
"SimulateUserName": "",
"AutoGenerateDatabase": true,
"BootstrapAdminAuthenticationOptions": {
"KeyPath": "..\\..\\keys"
},
"DB": [
{
"Enabled": false,
"ProviderName": "SqlServer",
"SqlFolder": "..\\..\\..\\db\\SqlServer",
"ConnectionStrings": {
"ba": "Data Source=.;Initial Catalog=BootstrapAdmin;User ID=sa;Password=sa"
}
},
{
"Enabled": true,
"ProviderName": "Sqlite",
"SqlFolder": "..\\..\\..\\db\\SQLite",
"ConnectionStrings": {
"ba": "Data Source=BootstrapAdmin.db;"
}
},
{
"Enabled": false,
"ProviderName": "MySql",
"SqlFolder": "..\\..\\..\\db\\MySQL",
"ConnectionStrings": {
"ba": "Server=localhost;Database=BA;Uid=argozhang;Pwd=argo@163.com;SslMode=none;"
}
},
{
"Enabled": false,
"ProviderName": "Oracle",
"ConnectionStrings": {
"ba": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=XXXXXX)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL)));User Id=XX;Password=XX"
}
},
{
"Enabled": false,
"ProviderName": "Npgsql",
"ConnectionStrings": {
"ba": "Server=localhost;Database=BootstrapAdmin;User ID=argozhang;Password=argo@163.com;"
}
},
{
"Enabled": false,
"Widget": "Bootstrap.DataAccess.MongoDB",
"ProviderName": "MongoDB",
"SqlFolder": "..\\..\\..\\db\\MongoDB",
"ConnectionStrings": {
"ba": "mongodb://localhost:27017/BootstrapAdmin"
}
}
],
"SwaggerPathBase": "",
"GiteeHealthChecks": "true",
"AllowOrigins": "http://localhost:49185",
@ -157,194 +107,5 @@
"第二层",
"第三层",
"第四层"
],
"LongbowCache": {
"Enabled": true,
"CorsItems": [
{
"Enabled": true,
"Key": "ba",
"Url": "CacheList.axd",
"Desc": "后台管理数据缓存接口",
"Self": true
},
{
"Enabled": true,
"Key": "App",
"Url": "http://localhost:49185/CacheList.axd",
"Desc": "测试系统",
"Self": false
}
],
"CacheItems": [
{
"Enabled": true,
"Key": "RoleHelper-RetrieveRolesByUserName",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "指定用户角色数据缓存"
},
{
"Enabled": true,
"Key": "RoleHelper-RetrieveRolesByUrl",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "通过菜单获得角色数据"
},
{
"Enabled": true,
"Key": "AppHelper-RetrieveAppsByUserName",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "指定用户授权应用数据缓存"
},
{
"Enabled": true,
"Key": "BootstrapUser-RetrieveUsersByName",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "登录用户数据"
},
{
"Enabled": true,
"Key": "BootstrapDict-RetrieveDicts",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "所有字典数据缓存"
},
{
"Enabled": true,
"Key": "BootstrapMenu-RetrieveMenus",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "用户所有菜单数据缓存"
},
{
"Enabled": true,
"Key": "GroupHelper-RetrieveGroupsByUserName",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "指定用户组数据缓存"
},
{
"Enabled": true,
"Key": "UserHelper-RetrieveUsers",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "所有用户数据"
},
{
"Enabled": true,
"Key": "UserHelper-RetrieveUsersByRoleId",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "通过角色ID获得所有用户数据"
},
{
"Enabled": true,
"Key": "UserHelper-RetrieveUsersByGroupId",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "通过部门ID获得所有用户数据"
},
{
"Enabled": true,
"Key": "UserHelper-RetrieveNewUsers",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "新用户数据"
},
{
"Enabled": true,
"Key": "MenuHelper-RetrieveMenusByRoleId",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "通过角色ID获得所有菜单数据"
},
{
"Enabled": true,
"Key": "RoleHelper-RetrieveRoles",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "所有角色数据"
},
{
"Enabled": true,
"Key": "RoleHelper-RetrieveRolesByUserId",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "通过用户ID获得所有角色数据"
},
{
"Enabled": true,
"Key": "RoleHelper-RetrieveRolesByMenuId",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "通过菜单ID获得所有角色数据"
},
{
"Enabled": true,
"Key": "RoleHelper-RetrieveRolesByGroupId",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "通过部门ID获得所有角色数据"
},
{
"Enabled": true,
"Key": "GroupHelper-RetrieveGroups",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "所有部门数据"
},
{
"Enabled": true,
"Key": "GroupHelper-RetrieveGroupsByUserId",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "通过用户ID获得所有部门数据"
},
{
"Enabled": true,
"Key": "GroupHelper-RetrieveGroupsByRoleId",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "通过角色ID获得所有部门数据"
},
{
"Enabled": true,
"Key": "AppHelper-RetrieveAppsByRoleId",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "通过角色ID获得所有应用程序数据"
},
{
"Enabled": true,
"Key": "DictHelper-RetrieveDictsCategory",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "字典分类数据"
},
{
"Enabled": true,
"Key": "ExceptionHelper-RetrieveExceptions",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "程序异常数据"
},
{
"Enabled": true,
"Key": "MessageHelper-RetrieveMessages",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "站内消息数据"
},
{
"Enabled": true,
"Key": "TaskHelper-RetrieveTasks",
"Interval": 600000,
"SlidingExpiration": true,
"Desc": "所有任务数据"
}
]
}
]
}

View File

@ -20,51 +20,9 @@
}
},
"ConnectionStrings": {
"ba": "Data Source=.;Initial Catalog=BootstrapAdmin;User ID=sa;Password=sa",
"bb": "Data Source=BootstrapAdmin.db;"
"ba": "Data Source=BootstrapAdmin.db;"
},
"AutoGenerateDatabase": false,
"DB": [
{
"Enabled": false,
"ProviderName": "SqlServer",
"ConnectionStrings": {
"ba": "Data Source=.;Initial Catalog=BootstrapAdmin;User ID=sa;Password=sa"
}
},
{
"Enabled": true,
"ProviderName": "Sqlite",
"ConnectionStrings": {
"ba": "Data Source=BootstrapAdmin.db;"
}
},
{
"Enabled": false,
"ProviderName": "MySql",
"ConnectionStrings": {
"ba": "Server=localhost;Database=BA;Uid=argozhang;Pwd=argo@163.com;SslMode=none;"
}
},
{
"Enabled": false,
"ProviderName": "Npgsql",
"ConnectionStrings": {
"ba": "Server=localhost;Database=BootstrapAdmin;User ID=argozhang;Password=argo@163.com;"
}
},
{
"Enabled": false,
"Widget": "Bootstrap.DataAccess.MongoDB",
"ProviderName": "MongoDB",
"ConnectionStrings": {
"ba": "mongodb://localhost:27017/BootstrapAdmin"
}
}
],
"AppId": "BA",
"UseHttps": true,
"SwaggerPathBase": "",
"AllowOrigins": "http://localhost,http://admin.blazor.zone",
"HealthsCloudUrl": "https://client.blazor.zone/api/Interface/Healths",
"GiteeHealthChecks": false,

View File

@ -1,4 +1,4 @@
.layout.is-page .layout-side {
.layout.is-page .layout-side {
color: #3f4254;
background-color: #fff;
box-shadow: 0 0 28px 0 rgb(82 63 105 / 5%);
@ -26,3 +26,11 @@
background-color: #fff !important;
}
}
.modal-dialog-task ::-webkit-scrollbar-thumb {
background-color: #ffffff66;
}
.modal-dialog-task ::-webkit-scrollbar-thumb:hover {
background-color: #ffffffb3;
}

View File

@ -122,3 +122,12 @@
.cell-icon {
margin-top: 4px;
}
.layout-user,
.layout-menu {
border-right: 1px solid #ddd;
}
.modal-dialog-menu.modal-dialog-scrollable .modal-body {
overflow: hidden;
}

View File

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="BootstrapBlazor" Version="6.2.9-beta04" />
<PackageReference Include="Longbow.Security.Cryptography" Version="5.2.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.1" />
<PackageReference Include="BootstrapBlazor" Version="6.8.13" />
<PackageReference Include="Longbow.Security.Cryptography" Version="6.0.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.7" />
<PackageReference Include="PetaPoco.Extensions" Version="6.0.0" />
</ItemGroup>

View File

@ -26,56 +26,19 @@ public static class ServiceCollectionExtensions
///
/// </summary>
/// <param name="services"></param>
/// <param name="builder"></param>
/// <returns></returns>
public static IServiceCollection AddPetaPocoDataAccessServices(this IServiceCollection services, Action<IServiceProvider, IDatabaseBuildConfiguration> builder)
public static IServiceCollection AddPetaPocoDataAccessServices(this IServiceCollection services)
{
services.TryAddSingleton<IDatabase>(provider =>
{
var option = DatabaseConfiguration.Build();
builder(provider, option);
option.UsingDefaultMapper<BootstrapAdminConventionMapper>();
var db = new Database(option);
// 增加多数据库支持服务
services.TryAddSingleton<IDBManager, DBManagerService>();
var logger = provider.GetRequiredService<ILogger<Database>>();
db.ExceptionThrown += (sender, e) =>
{
var message = e.Exception.Format(new NameValueCollection()
{
[nameof(db.LastCommand)] = db.LastCommand,
[nameof(db.LastArgs)] = string.Join(",", db.LastArgs)
});
logger.LogError(new EventId(1001, "GlobalException"), e.Exception, message);
};
var env = provider.GetRequiredService<IWebHostEnvironment>();
if (env.IsDevelopment())
{
db.CommandExecuted += (sender, args) =>
{
var parameters = new StringBuilder();
foreach (DbParameter p in args.Command.Parameters)
{
parameters.AppendFormat("{0}: {1} ", p.ParameterName, p.Value);
}
logger.LogInformation(args.Command.CommandText);
logger.LogInformation(parameters.ToString());
};
};
return db;
});
//// 增加数据服务
//services.AddSingleton(typeof(IDataService<>), typeof(DefaultDataService<>));
//// 增加业务服务
//services.AddSingleton<IApp, AppService>();
// 增加业务服务
services.AddSingleton<IDict, DictService>();
//services.AddSingleton<IException, ExceptionService>();
//services.AddSingleton<IGroup, GroupService>();
//services.AddSingleton<ILogin, LoginService>();
services.AddSingleton<INavigation, NavigationService>();
//services.AddSingleton<IRole, RoleService>();
services.AddSingleton<IUser, UserService>();
// 增加示例数据服务
services.AddSingleton<IDummy, DummyService>();
return services;
}
}

View File

@ -0,0 +1,78 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using PetaPoco;
using PetaPoco.Providers;
using System.Collections.Specialized;
using System.Data.Common;
using System.Text;
namespace BootstrapClient.DataAccess.PetaPoco.Services;
internal class DBManagerService : IDBManager
{
private IConfiguration Configuration { get; set; }
private ILogger<DBManagerService> Logger { get; set; }
private IWebHostEnvironment WebHost { get; set; }
public DBManagerService(IConfiguration configuration, ILogger<DBManagerService> logger, IWebHostEnvironment host)
{
Configuration = configuration;
Logger = logger;
WebHost = host;
}
/// <summary>
/// 创建 IDatabase 实例方法
/// </summary>
/// <param name="connectionName">连接字符串键值</param>
/// <param name="keepAlive"></param>
/// <returns></returns>
public IDatabase Create(string? connectionName = "ba", bool keepAlive = false)
{
var conn = Configuration.GetConnectionString(connectionName) ?? throw new ArgumentNullException(nameof(connectionName));
var option = DatabaseConfiguration.Build();
option.UsingDefaultMapper<BootstrapAdminConventionMapper>();
// connectionstring
option.UsingConnectionString(conn);
// provider
option.UsingProvider<SQLiteDatabaseProvider>();
var db = new Database(option) { KeepConnectionAlive = keepAlive };
db.ExceptionThrown += (sender, e) =>
{
var message = e.Exception.Format(new NameValueCollection()
{
[nameof(db.LastCommand)] = db.LastCommand,
[nameof(db.LastArgs)] = string.Join(",", db.LastArgs)
});
Logger.LogError(new EventId(1001, "GlobalException"), e.Exception, message);
};
if (WebHost.IsDevelopment())
{
db.CommandExecuted += (sender, args) =>
{
var parameters = new StringBuilder();
foreach (DbParameter p in args.Command.Parameters)
{
parameters.AppendFormat("{0}: {1} ", p.ParameterName, p.Value);
}
Logger.LogInformation(args.Command.CommandText);
Logger.LogInformation(parameters.ToString());
};
};
return db;
}
}

View File

@ -10,15 +10,19 @@ namespace BootstrapClient.DataAccess.PetaPoco.Services;
class DictService : IDict
{
private IDatabase Database { get; }
private IDBManager DBManager { get; }
/// <summary>
///
/// </summary>
/// <param name="db"></param>
public DictService(IDatabase db) => Database = db;
public DictService(IDBManager db) => DBManager = db;
public List<Dict> GetAll() => Database.Fetch<Dict>();
public List<Dict> GetAll()
{
using var db = DBManager.Create();
return db.Fetch<Dict>();
}
public bool IsDemo()
{

View File

@ -0,0 +1,36 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapClient.DataAccess.Models;
using BootstrapClient.Web.Core;
using PetaPoco;
namespace BootstrapClient.DataAccess.PetaPoco.Services;
internal class DummyService : IDummy
{
private IDBManager DBManager { get; }
/// <summary>
///
/// </summary>
/// <param name="db"></param>
public DummyService(IDBManager db)
{
DBManager = db;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public List<DummyEntity> GetAll()
{
return new List<DummyEntity>()
{
new() { Id= "1", Name ="Dummy1" },
new() { Id= "2", Name ="Dummy2" }
};
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using PetaPoco;
namespace BootstrapClient.DataAccess.PetaPoco.Services;
/// <summary>
///
/// </summary>
public interface IDBManager
{
/// <summary>
///
/// </summary>
/// <returns></returns>
IDatabase Create(string? connectionName = "ba", bool keepAlive = false);
}

View File

@ -13,13 +13,13 @@ namespace BootstrapClient.DataAccess.PetaPoco.Services;
/// </summary>
class NavigationService : INavigation
{
private IDatabase Database { get; }
private IDBManager DBManager { get; }
/// <summary>
///
/// </summary>
/// <param name="db"></param>
public NavigationService(IDatabase db) => Database = db;
public NavigationService(IDBManager db) => DBManager = db;
/// <summary>
/// 获得指定用户名可访问的所有菜单集合
@ -28,7 +28,8 @@ class NavigationService : INavigation
/// <returns>未层次化的菜单集合</returns>
public List<Navigation> GetMenus(string userName)
{
var order = Database.Provider.EscapeSqlIdentifier("Order");
return Database.Fetch<Navigation>($"select n.ID, n.ParentId, n.Name, n.{order}, n.Icon, n.Url, n.Category, n.Target, n.IsResource, n.Application from Navigations n inner join (select nr.NavigationID from Users u inner join UserRole ur on ur.UserID = u.ID inner join NavigationRole nr on nr.RoleID = ur.RoleID where u.UserName = @UserName union select nr.NavigationID from Users u inner join UserGroup ug on u.ID = ug.UserID inner join RoleGroup rg on rg.GroupID = ug.GroupID inner join NavigationRole nr on nr.RoleID = rg.RoleID where u.UserName = @UserName union select n.ID from Navigations n where EXISTS (select UserName from Users u inner join UserRole ur on u.ID = ur.UserID inner join Roles r on ur.RoleID = r.ID where u.UserName = @UserName and r.RoleName = 'Administrators')) nav on n.ID = nav.NavigationID Where n.Category = '1' ORDER BY n.Application, n.{order}", new { UserName = userName });
using var db = DBManager.Create ();
var order = db.Provider.EscapeSqlIdentifier("Order");
return db.Fetch<Navigation>($"select n.ID, n.ParentId, n.Name, n.{order}, n.Icon, n.Url, n.Category, n.Target, n.IsResource, n.Application from Navigations n inner join (select nr.NavigationID from Users u inner join UserRole ur on ur.UserID = u.ID inner join NavigationRole nr on nr.RoleID = ur.RoleID where u.UserName = @UserName union select nr.NavigationID from Users u inner join UserGroup ug on u.ID = ug.UserID inner join RoleGroup rg on rg.GroupID = ug.GroupID inner join NavigationRole nr on nr.RoleID = rg.RoleID where u.UserName = @UserName union select n.ID from Navigations n where EXISTS (select UserName from Users u inner join UserRole ur on u.ID = ur.UserID inner join Roles r on ur.RoleID = r.ID where u.UserName = @UserName and r.RoleName = 'Administrators')) nav on n.ID = nav.NavigationID Where n.Category = '1' ORDER BY n.Application, n.{order}", new { UserName = userName });
}
}

View File

@ -10,32 +10,49 @@ namespace BootstrapClient.DataAccess.PetaPoco.Services;
class UserService : IUser
{
private IDatabase Database { get; }
private IDBManager DBManager { get; }
/// <summary>
///
/// </summary>
/// <param name="db"></param>
public UserService(IDatabase db) => Database = db;
public UserService(IDBManager db) => DBManager = db;
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public User? GetUserByUserName(string? userName) => string.IsNullOrEmpty(userName) ? null : Database.FirstOrDefault<User>("Where UserName = @0", userName);
public User? GetUserByUserName(string? userName)
{
User? user = null;
if (!string.IsNullOrEmpty(userName))
{
using var db = DBManager.Create();
user = db.FirstOrDefault<User>("Where UserName = @0", userName);
}
return user;
}
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public List<string> GetApps(string userName) => Database.Fetch<string>($"select d.Code from Dicts d inner join RoleApp ra on d.Code = ra.AppId inner join (select r.Id from Roles r inner join UserRole ur on r.ID = ur.RoleID inner join Users u on ur.UserID = u.ID where u.UserName = @0 union select r.Id from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {Database.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID where u.UserName = @0) r on ra.RoleId = r.ID union select Code from Dicts where Category = @1 and exists(select r.ID from Roles r inner join UserRole ur on r.ID = ur.RoleID inner join Users u on ur.UserID = u.ID where u.UserName = @0 and r.RoleName = @2 union select r.ID from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {Database.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID where u.UserName = @0 and r.RoleName = @2)", userName, "应用程序", "Administrators");
public List<string> GetApps(string userName)
{
using var db = DBManager.Create();
return db.Fetch<string>($"select d.Code from Dicts d inner join RoleApp ra on d.Code = ra.AppId inner join (select r.Id from Roles r inner join UserRole ur on r.ID = ur.RoleID inner join Users u on ur.UserID = u.ID where u.UserName = @0 union select r.Id from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {db.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID where u.UserName = @0) r on ra.RoleId = r.ID union select Code from Dicts where Category = @1 and exists(select r.ID from Roles r inner join UserRole ur on r.ID = ur.RoleID inner join Users u on ur.UserID = u.ID where u.UserName = @0 and r.RoleName = @2 union select r.ID from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {db.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID where u.UserName = @0 and r.RoleName = @2)", userName, "应用程序", "Administrators");
}
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public List<string> GetRoles(string userName) => Database.Fetch<string>($"select r.RoleName from Roles r inner join UserRole ur on r.ID=ur.RoleID inner join Users u on ur.UserID = u.ID and u.UserName = @0 union select r.RoleName from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {Database.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID and u.UserName=@0", userName);
public List<string> GetRoles(string userName)
{
using var db = DBManager.Create();
return db.Fetch<string>($"select r.RoleName from Roles r inner join UserRole ur on r.ID=ur.RoleID inner join Users u on ur.UserID = u.ID and u.UserName = @0 union select r.RoleName from Roles r inner join RoleGroup rg on r.ID = rg.RoleID inner join {db.Provider.EscapeSqlIdentifier("Groups")} g on rg.GroupID = g.ID inner join UserGroup ug on ug.GroupID = g.ID inner join Users u on ug.UserID = u.ID and u.UserName=@0", userName);
}
}

View File

@ -5,12 +5,17 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BootstrapClient.DataAccess\BootstrapClient.DataAccess.PetaPoco.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="BootstrapBlazor.Components" />
<Using Include="Microsoft.AspNetCore.Components" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
@typeparam TItem
<Table TItem="TItem" IsBordered="true" IsStriped="true" IsMultipleSelect="IsMultipleSelect" @ref="Instance"
IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader"
IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-chevron-circle-right"
ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch"
ShowEmpty="ShowEmpty" EmptyText="暂无数据" EmptyImage="images/empty.svg" SortString="@SortString"
OnQueryAsync="OnQueryAsync!" OnDeleteAsync="OnDeleteAsync!" OnSaveAsync="OnSaveAsync!"
ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch"
ShowToolbar="ShowToolbar" ShowExtendButtons="ShowExtendButtons" ShowAddButton="@AuthorizeButton("add")"
ShowDeleteButton="@AuthorizeButton("del")" ShowEditButton="@AuthorizeButton("edit")"
ShowCardView="true" ShowColumnList="true" ExtendButtonColumnWidth="@ExtendButtonColumnWidth"
CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows"
ShowEditButtonCallback="ShowEditButtonCallback!" ShowDeleteButtonCallback="ShowDeleteButtonCallback!"
TableToolbarTemplate="TableToolbarTemplate" TableColumns="TableColumns" EditTemplate="EditTemplate!"
CustomerSearchTemplate="CustomerSearchTemplate!" RowButtonTemplate="RowButtonTemplate!">
</Table>

View File

@ -0,0 +1,222 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using Bootstrap.Security.Blazor;
using BootstrapClient.Web.Shared.Services;
namespace BootstrapClient.Web.Shared.Components
{
/// <summary>
///
/// </summary>
[CascadingTypeParameter(nameof(TItem))]
public partial class AdminTable<TItem> where TItem : class, new()
{
/// <summary>
///
/// </summary>
[Parameter]
public IEnumerable<int>? PageItemsSource { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public int ExtendButtonColumnWidth { get; set; } = 130;
/// <summary>
///
/// </summary>
[Parameter]
public string? SortString { get; set; }
/// <summary>
///
/// </summary>
[NotNull]
[Parameter]
public RenderFragment<TItem>? TableColumns { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public RenderFragment<TItem>? RowButtonTemplate { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public RenderFragment<ITableSearchModel>? CustomerSearchTemplate { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public RenderFragment<TItem>? EditTemplate { get; set; }
/// <summary>
///
/// </summary>
[NotNull]
[Parameter]
public RenderFragment? TableToolbarTemplate { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public bool IsPagination { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public bool IsMultipleSelect { get; set; } = true;
/// <summary>
///
/// </summary>
[Parameter]
public bool IsFixedHeader { get; set; } = true;
/// <summary>
///
/// </summary>
[Parameter]
public bool IsTree { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public bool ShowToolbar { get; set; } = true;
/// <summary>
///
/// </summary>
[Parameter]
public bool ShowEmpty { get; set; } = true;
/// <summary>
///
/// </summary>
[Parameter]
public bool ShowLoading { get; set; } = false;
/// <summary>
///
/// </summary>
[Parameter]
public bool ShowSearch { get; set; } = true;
/// <summary>
///
/// </summary>
[Parameter]
public bool ShowAdvancedSearch { get; set; } = true;
/// <summary>
///
/// </summary>
[Parameter]
public bool ShowDefaultButtons { get; set; } = true;
/// <summary>
///
/// </summary>
[Parameter]
public bool ShowExtendButtons { get; set; } = true;
/// <summary>
///
/// </summary>
[Parameter]
public ITableSearchModel? CustomerSearchModel { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public Func<QueryPageOptions, Task<QueryData<TItem>>>? OnQueryAsync { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public Func<TItem, Task<IEnumerable<TableTreeNode<TItem>>>>? OnTreeExpand { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public Func<IEnumerable<TItem>, Task<IEnumerable<TableTreeNode<TItem>>>>? TreeNodeConverter { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public Func<TItem, ItemChangedType, Task<bool>>? OnSaveAsync { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public Func<IEnumerable<TItem>, Task<bool>>? OnDeleteAsync { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public List<TItem>? SelectedRows { get; set; } = new List<TItem>();
/// <summary>
///
/// </summary>
[Parameter]
public Func<TItem, bool>? ShowEditButtonCallback { get; set; }
/// <summary>
///
/// </summary>
[Parameter]
public Func<TItem, bool>? ShowDeleteButtonCallback { get; set; }
[NotNull]
private Table<TItem>? Instance { get; set; }
/// <summary>
///
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public ValueTask ToggleLoading(bool v) => Instance.ToggleLoading(v);
/// <summary>
///
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public Task QueryAsync() => Instance.QueryAsync();
[Inject]
[NotNull]
private IBootstrapAdminService? AdminService { get; set; }
[Inject]
[NotNull]
private NavigationManager? NavigationManager { get; set; }
[Inject]
[NotNull]
private BootstrapAppContext? AppContext { get; set; }
private bool AuthorizeButton(string operate)
{
var url = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
return AdminService.AuhorizingBlock(AppContext.UserName, url, operate);
}
}
}

View File

@ -0,0 +1,9 @@
@using BootstrapClient.Web.Shared.Components
@page "/dummy"
<AdminTable TItem="BootstrapClient.DataAccess.Models.DummyEntity" OnQueryAsync="OnQueryAsync">
<TableColumns>
<TableColumn @bind-Field="context.Id"></TableColumn>
<TableColumn @bind-Field="context.Name"></TableColumn>
</TableColumns>
</AdminTable>

View File

@ -0,0 +1,25 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapClient.DataAccess.Models;
using BootstrapClient.Web.Core;
namespace BootstrapClient.Web.Shared.Pages;
public partial class Dummy
{
[Inject]
[NotNull]
private IDummy? DummyService { get; set; }
private Task<QueryData<DummyEntity>> OnQueryAsync(QueryPageOptions options)
{
var items = DummyService.GetAll();
var ret = new QueryData<DummyEntity>()
{
Items = items
};
return Task.FromResult(ret);
}
}

View File

@ -1,5 +1,4 @@
@page "/"
@page "/Home/Index"
@attribute [TabItemOption(Text = "首页", Icon = "fa fa-fa")]
<h1>Hello, world!</h1>

View File

@ -4,7 +4,7 @@
IsFullSide="@IsFullSide" IsFixedHeader="@IsFixedHeader" IsFixedFooter="@IsFixedFooter" ShowFooter="@ShowFooter"
Menus="@MenuItems" UseTabSet="@UseTabSet"
TabDefaultUrl="/Home/Index"
AdditionalAssemblies="new[] { GetType().Assembly }" OnAuthorizing="@OnAuthorizing" class="@Theme">
AdditionalAssemblies="new[] { GetType().Assembly }" OnAuthorizing="@OnAuthorizing" class="@ClassString">
<Header>
<span class="ml-3 flex-sm-fill d-none d-sm-block">Bootstrap of Blazor</span>
<Widget></Widget>

View File

@ -9,6 +9,7 @@ using BootstrapClient.Web.Shared.Extensions;
using BootstrapClient.Web.Shared.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Configuration;
namespace BootstrapClient.Web.Shared.Shared;
@ -43,6 +44,10 @@ public sealed partial class MainLayout
private string? Footer { get; set; }
private string? ClassString => CssBuilder.Default(Theme)
.AddClass("is-fixed-tab")
.Build();
[Inject]
[NotNull]
private IBootstrapAdminService? SecurityService { get; set; }
@ -71,6 +76,10 @@ public sealed partial class MainLayout
[NotNull]
private BootstrapAppContext? Context { get; set; }
[Inject]
[NotNull]
private IConfiguration? Configuration { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
@ -107,6 +116,13 @@ public sealed partial class MainLayout
MenuItems = NavigationsService.GetMenus(userName).Where(i => i.Application == Context.AppId).ToMenus();
Context.DisplayName = user?.DisplayName ?? "未注册账户";
// 增加模拟账户识别
if (!string.IsNullOrEmpty(Configuration.GetValue("SimulateUserName", string.Empty)))
{
Context.DisplayName = $"{Context.DisplayName} (模拟)";
}
Title = DictsService.GetWebTitle(Context.AppId);
Footer = DictsService.GetWebFooter(Context.AppId);
}

View File

@ -310,3 +310,17 @@
.widget .dropdown-item:not(:nth-of-type(odd)):active {
background-color: inherit;
}
.is-page .layout-main > .tabs {
height: calc(100vh - 90px);
}
.is-fixed-tab .tabs-body {
height: calc(100vh - 89px - var(--bb-footer-height));
overflow: auto;
}
.is-fixed-tab .tabs-body .tabs-body-content {
height: auto;
min-height: 100%;
}

View File

@ -56,4 +56,18 @@ public class AdminService : IBootstrapAdminService
}
return Task.FromResult(ret);
}
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <param name="url"></param>
/// <param name="blockName"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public bool AuhorizingBlock(string userName, string url, string blockName)
{
// Client 暂时未使用
return true;
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Bootstrap.Security.Blazor" Version="6.0.1-beta01" />
<PackageReference Include="Bootstrap.Security.Blazor" Version="6.0.1" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,19 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
using BootstrapClient.DataAccess.Models;
namespace BootstrapClient.Web.Core;
/// <summary>
/// 数据服务示例接口
/// </summary>
public interface IDummy
{
/// <summary>
/// 获得 全部数据
/// </summary>
/// <returns></returns>
List<DummyEntity> GetAll();
}

View File

@ -2,24 +2,20 @@
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone
namespace BootstrapAdmin.Web.Pages.Account;
namespace BootstrapClient.DataAccess.Models;
/// <summary>
///
/// 数据库实体类
/// </summary>
public partial class Login
public class DummyEntity
{
/// <summary>
///
/// 获得/设置 Id
/// </summary>
[SupplyParameterFromQuery]
[Parameter]
public string? ReturnUrl { get; set; }
public string? Id { get; set; }
/// <summary>
///
/// 获得/设置 Name
/// </summary>
[SupplyParameterFromQuery]
[Parameter]
public string? AppId { get; set; }
public string? Name { get; set; }
}

View File

@ -33,13 +33,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<BootstrapAppContext>();
// 增加 PetaPoco 数据服务
services.AddPetaPocoDataAccessServices((provider, builder) =>
{
var configuration = provider.GetRequiredService<IConfiguration>();
var connString = configuration.GetConnectionString("ba");
builder.UsingProvider<SQLiteDatabaseProvider>()
.UsingConnectionString(connString);
});
services.AddPetaPocoDataAccessServices();
return services;
}

View File

@ -15,7 +15,7 @@
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Bootstrap.Client.Blazor": {
"Client": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,

View File

@ -12,34 +12,5 @@
"BootstrapAdminAuthenticationOptions": {
"AuthHost": "http://localhost:5210",
"KeyPath": "..\\..\\keys"
},
"DB": [
{
"Enabled": false
},
{
"Enabled": true,
"ProviderName": "Sqlite",
"ConnectionStrings": {
"bb": "..\\..\\admin\\BootstrapAdmin.Web\\BootstrapAdmin.db;",
"client": "Data Source=Client.db;"
}
},
{
"Enabled": false,
"ProviderName": "MySql",
"ConnectionStrings": {
"ba": "Server=localhost;Database=BootstrapAdmin;Uid=root;Pwd=argo@163.com;SslMode=none;"
}
},
{
"Enabled": false,
"Widget": "Bootstrap.Client.DataAccess.MongoDB",
"ProviderName": "MongoDB",
"ConnectionStrings": {
"ba": "mongodb://localhost:27017/BootstrapAdmin",
"client": "mongodb://localhost:27017/BootstrapClient"
}
}
]
}
}

View File

@ -16,32 +16,7 @@
"ba": "Data Source=..\\..\\admin\\BootstrapAdmin.Web\\BootstrapAdmin.db;",
"client": "Data Source=Client.db;"
},
"DB": [
{
"Enabled": false
},
{
"Enabled": true,
"ProviderName": "Sqlite",
"ConnectionStrings": {
"ba": "Data Source=..\\..\\admin\\BootstrapAdmin.Web\\BootstrapAdmin.db;",
"client": "Data Source=Client.db;"
}
},
{
"Enabled": false,
"ProviderName": "MySql",
"ConnectionStrings": {
"ba": "Server=localhost;Database=BootstrapAdmin;Uid=argozhang;Pwd=argo@163.com;SslMode=none;"
}
},
{
"Enabled": false,
"Widget": "Bootstrap.Client.DataAccess.MongoDB",
"ProviderName": "MongoDB",
"ConnectionStrings": {
"ba": "mongodb://localhost:27017/BootstrapAdmin"
}
}
]
"BootstrapBlazorOptions": {
"DefaultCultureInfo": "zh-CN"
}
}

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