28 Commits

Author SHA1 Message Date
tk
ddf891e3bc chore(release): 1.2.0 2024-06-07 00:05:05 +08:00
7ae473d492 feat: 增强作业执行记录页面 (#135) 2024-06-04 16:08:18 +08:00
c20a6c369d fix: 🐛 字段长度 (#134)
[skip ci]

Co-authored-by: tk <fiyne1a@dingtalk.com>
2024-06-04 15:26:28 +08:00
57b71e1354 feat: 计划作业-上次执行耗时 (#133)
Co-authored-by: tk <fiyne1a@dingtalk.com>
2024-06-04 11:57:15 +08:00
127f6e9f6c feat: 默认头像根据用户名绘制svg (#132) 2024-05-31 16:48:11 +08:00
d1951dbcb5 fix: 🐛 字段顺序 (#131)
[skip ci]
2024-05-30 16:55:43 +08:00
5edcf63e24 feat: 框架代码同步 (#130)
[skip ci]
2024-05-29 15:03:05 +08:00
b01b8b24ba feat: 框架代码同步 (#129)
[skip ci]
2024-05-29 14:40:10 +08:00
d9c7085472 docs: 📝 readme (#128)
[skip ci]
2024-05-23 18:48:37 +08:00
a01acddb9c chore: 🔨 喜欢就点个 Star️ 吧! (#127) 2024-05-22 19:23:36 +08:00
e5208cd751 docs: 📝 更新 readme (#126) 2024-05-22 18:57:05 +08:00
dc326c324c perf: 升级至.net9 prev4 (#125)
[skip ci]
2024-05-22 18:55:08 +08:00
e0d15f8039 feat(frontend): 手机端分页控件显示总条数 (#124) 2024-05-17 16:23:30 +08:00
169ab08b88 chore: 🔨 switcher (#123)
[skip ci]
2024-05-16 09:44:02 +08:00
3b8336105a feat: 手动执行计划作业 (#122) 2024-05-15 14:50:26 +08:00
7214a22ea5 docs: 📝 更新 README (#121)
[skip ci]
2024-05-14 17:24:56 +08:00
3152a8d3e8 fix(backend): 🐛 更新计划作业在sqlite数据库环境报错 (#120) 2024-05-14 15:37:51 +08:00
40e8eff5f3 perf(backend): nuget update (#119)
[skip ci]
2024-05-13 16:12:10 +08:00
47e67dd503 feat: naColId组件 (#118) 2024-05-13 11:34:43 +08:00
903ea1820a Merge pull request #117 from nsnail/tk
fix: 🐛 take count
2024-04-30 11:27:49 +08:00
tk
c08ea62064 fix: 🐛 take count
[skip ci]
2024-04-30 11:27:26 +08:00
4860299959 Merge pull request #116 from nsnail/tk
docs: 📝 更新日志修改
2024-04-29 19:02:56 +08:00
tk
72f9d1a3ec docs: 📝 更新日志修改 2024-04-29 19:02:36 +08:00
98718a010c Merge pull request #115 from nsnail/release
chore(release): 1.1.1
2024-04-29 18:53:23 +08:00
tk
adfc8a7c74 chore(release): 1.1.1 2024-04-29 18:52:12 +08:00
427057b42d ci: 🎡 docker发布脚本 (#114)
[skip ci]
2024-04-29 18:51:55 +08:00
823efd4044 ci: 🎡 工作流脚本修改 (#113) 2024-04-29 18:28:59 +08:00
e43439a118 chore(release): 1.1.0 2024-04-29 18:25:36 +08:00
79 changed files with 797 additions and 256 deletions

View File

@ -18,8 +18,7 @@ jobs:
with:
fetch-depth: 0
filter: tree:0
- name: Setup Node.js
uses: actions/setup-node@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
# https://docs.github.com/zh/actions/using-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key
@ -28,12 +27,9 @@ jobs:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('./src/frontend/admin/package.json') }}
restore-keys: ${{ runner.os }}-npm
- name: Publish frontend
working-directory: ./src/frontend/admin
run:
npm install && npm run build
- name: Setup .NET
uses: actions/setup-dotnet@v3
- working-directory: ./src/frontend/admin
run: npm install && npm run build
- uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x
- uses: actions/cache@v3
@ -41,15 +37,11 @@ jobs:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: ${{ runner.os }}-nuget
- name: Publish backend
working-directory: ./src/backend/NetAdmin.AdmServer.Host
- working-directory: ./src/backend/NetAdmin.AdmServer.Host
run: dotnet publish NetAdmin.AdmServer.Host.csproj -c Release
- name: Build docker images
run: docker build -t nsnail/netadmin .
- name: Docker login
uses: docker/login-action@v3
- run: docker build -t nsnail/netadmin:nightly .
- uses: docker/login-action@v3
with:
username: "nsnail"
password: "${{secrets.DOCKER_PASSWORD}}"
- name: Push docker images
run: docker push nsnail/netadmin
- run: docker push nsnail/netadmin:nightly

View File

@ -28,8 +28,7 @@ jobs:
key: ${{ runner.os }}-npm-${{ hashFiles('./src/frontend/admin/package.json') }}
restore-keys: ${{ runner.os }}-npm
- working-directory: ./src/frontend/admin
run:
npm install && npm run build
run: npm install && npm run build
- uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x
@ -50,9 +49,10 @@ jobs:
prerelease: false
- id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v}
- run: docker build -t nsnail/netadmin:${{steps.get_version.outputs.VERSION}} .
- run: docker build -t nsnail/netadmin -t nsnail/netadmin:${{steps.get_version.outputs.VERSION}} .
- uses: docker/login-action@v3
with:
username: "nsnail"
password: "${{secrets.DOCKER_PASSWORD}}"
- run: docker push nsnail/netadmin
- run: docker push nsnail/netadmin:${{steps.get_version.outputs.VERSION}}

View File

@ -1,7 +1,58 @@
# Changelog
All notable changes to this project will be documented in this file.
See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [1.2.0](https://github.com/nsnail/NetAdmin/compare/v1.1.1...v1.2.0) (2024-06-06)
### Features
* ✨ 计划作业-上次执行耗时 ([#133](https://github.com/nsnail/NetAdmin/issues/133)) ([57b71e1](https://github.com/nsnail/NetAdmin/commit/57b71e1354ab8b0be995b5f563dd8c3fb7965d5f))
* ✨ 框架代码同步 ([#129](https://github.com/nsnail/NetAdmin/issues/129)) ([b01b8b2](https://github.com/nsnail/NetAdmin/commit/b01b8b24ba574c08ba5605e103ff2ccf15e5830a))
* ✨ 框架代码同步 ([#130](https://github.com/nsnail/NetAdmin/issues/130)) ([5edcf63](https://github.com/nsnail/NetAdmin/commit/5edcf63e24f6b13f5515e01ee8cf120b1a814d40))
* ✨ 默认头像根据用户名绘制svg ([#132](https://github.com/nsnail/NetAdmin/issues/132)) ([127f6e9](https://github.com/nsnail/NetAdmin/commit/127f6e9f6c8c12974e5340e9697281250737bed3))
* ✨ 手动执行计划作业 ([#122](https://github.com/nsnail/NetAdmin/issues/122)) ([3b83361](https://github.com/nsnail/NetAdmin/commit/3b8336105a908ba6bc300bec6ac4f49747ea66e9))
* ✨ 增强作业执行记录页面 ([#135](https://github.com/nsnail/NetAdmin/issues/135)) ([7ae473d](https://github.com/nsnail/NetAdmin/commit/7ae473d492b9ba60cbb1c355894917d14f5ffa8f))
* ✨ naColId组件 ([#118](https://github.com/nsnail/NetAdmin/issues/118)) ([47e67dd](https://github.com/nsnail/NetAdmin/commit/47e67dd503dd0ba6818e8b798e41c62420363f58))
* **frontend:** ✨ 手机端分页控件显示总条数 ([#124](https://github.com/nsnail/NetAdmin/issues/124)) ([e0d15f8](https://github.com/nsnail/NetAdmin/commit/e0d15f8039a74a9826a0395983960ab620308899))
### Bug Fixes
* 🐛 字段顺序 ([#131](https://github.com/nsnail/NetAdmin/issues/131)) ([d1951db](https://github.com/nsnail/NetAdmin/commit/d1951dbcb5fa50a7ff308f6b6d554da5f791bcf2))
* 🐛 字段长度 ([#134](https://github.com/nsnail/NetAdmin/issues/134)) ([c20a6c3](https://github.com/nsnail/NetAdmin/commit/c20a6c369d7b6d6dcfd07b3f3eaeab0fa309e766))
* 🐛 take count ([c08ea62](https://github.com/nsnail/NetAdmin/commit/c08ea62064cc522d7cca9c90a5f15f23d833b6e3))
* **backend:** 🐛 更新计划作业在sqlite数据库环境报错 ([#120](https://github.com/nsnail/NetAdmin/issues/120)) ([3152a8d](https://github.com/nsnail/NetAdmin/commit/3152a8d3e8054524470883c336fb6e93903a8426))
### [1.1.1](https://github.com/nsnail/NetAdmin/compare/v1.1.0...v1.1.1) (2024-04-29)
## [1.1.0](https://github.com/nsnail/NetAdmin/compare/v1.0.0...v1.1.0) (2024-04-29)
### Features
* ✨ 版本更新日志组件 ([#96](https://github.com/nsnail/NetAdmin/issues/96)) ([a37acc4](https://github.com/nsnail/NetAdmin/commit/a37acc4b55c91d57d51c7fa079da8700530412a5))
* ✨ 计划作业 ([#87](https://github.com/nsnail/NetAdmin/issues/87)) ([8293ec0](https://github.com/nsnail/NetAdmin/commit/8293ec0297875ebc9ad75cce9465bd587929c0bf))
* ✨ 计划作业执行记录 ([#89](https://github.com/nsnail/NetAdmin/issues/89)) ([6f89015](https://github.com/nsnail/NetAdmin/commit/6f890151989ad733e35653933b7597eec478cc3b))
* ✨ 将数据库结构同步和种子数据初始化作为命令行开关 ([#78](https://github.com/nsnail/NetAdmin/issues/78)) ([05ed3d3](https://github.com/nsnail/NetAdmin/commit/05ed3d3746aa274a0f88f7afadfea12a3c8a80ff))
* ✨ 快捷启用/禁用用户 ([#91](https://github.com/nsnail/NetAdmin/issues/91)) ([6c2d167](https://github.com/nsnail/NetAdmin/commit/6c2d1676e45b9f1ecf3be3ae5a172db49b62a81d))
* ✨ 前端表格高级筛选 ([#100](https://github.com/nsnail/NetAdmin/issues/100)) ([3847d6f](https://github.com/nsnail/NetAdmin/commit/3847d6fdbbd27efb53921bcc8374157f0da47155))
* ✨ 日志管理独立出来、增加登录日志界面 ([#65](https://github.com/nsnail/NetAdmin/issues/65)) ([9134c4f](https://github.com/nsnail/NetAdmin/commit/9134c4fe01165a87ebc7e2cbd0a2abff3c9fb3ea))
* ✨ 首页仪表面板 ([#103](https://github.com/nsnail/NetAdmin/issues/103)) ([149e1af](https://github.com/nsnail/NetAdmin/commit/149e1afa533b142a3666a325ec84a091d53c1840))
* ✨ cron表达式选择器 ([#92](https://github.com/nsnail/NetAdmin/issues/92)) ([bde9fb1](https://github.com/nsnail/NetAdmin/commit/bde9fb1ea264bd0b786ac68d590691892d7ce067))
### Bug Fixes
* 🐛 'Numbers' does not contain a definition for 'CACHE_SECS_DEFAULT' ([#102](https://github.com/nsnail/NetAdmin/issues/102)) ([8f69c29](https://github.com/nsnail/NetAdmin/commit/8f69c2907be282b1b39f4a179badb11502aa2403))
* 🐛 低版本jetbrains.resharper.globaltools搞乱了代码 ([#97](https://github.com/nsnail/NetAdmin/issues/97)) ([c117ddf](https://github.com/nsnail/NetAdmin/commit/c117ddfe7a433215b3449cdd6b19318a1f3cbf37))
* 🐛 前端样式问题 ([#84](https://github.com/nsnail/NetAdmin/issues/84)) ([6615df3](https://github.com/nsnail/NetAdmin/commit/6615df339934f6d19880c9822b44d5305c2f2a75))
* 🐛 请求日志客户端IP显示不正确 ([#60](https://github.com/nsnail/NetAdmin/issues/60)) ([ec698ce](https://github.com/nsnail/NetAdmin/commit/ec698ce4db49861eaaeb8bf5080764939e6d7231))
* 🐛 时区问题 ([#107](https://github.com/nsnail/NetAdmin/issues/107)) ([59c85ce](https://github.com/nsnail/NetAdmin/commit/59c85cef217c121b36d52993b6b5a774fe22df9e))
* 🐛 小问题 ([#76](https://github.com/nsnail/NetAdmin/issues/76)) ([52ddf27](https://github.com/nsnail/NetAdmin/commit/52ddf273c856d8f7e363ce23e5886b9eedf4604f))
* 🐛 在弹窗界面中引用的列表组件点击重置搜索条件按钮时会关闭弹窗的bug ([#95](https://github.com/nsnail/NetAdmin/issues/95)) ([8fee14c](https://github.com/nsnail/NetAdmin/commit/8fee14cd6ebd86456956fc59bbb61c545faa1fdd))
* 🐛 tinymce editor css 加载路径错误 ([#93](https://github.com/nsnail/NetAdmin/issues/93)) ([5fe7387](https://github.com/nsnail/NetAdmin/commit/5fe73878a2a53dc5e7e2dcbcbf22f91ffb4376dd))
* 🐛 tinymce editor css 加载路径错误 ([#94](https://github.com/nsnail/NetAdmin/issues/94)) ([802251e](https://github.com/nsnail/NetAdmin/commit/802251e42347bfe4fa0bcb4867b615d7c03abf19))
## 1.0.0 (2023-11-17)

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/aspnet:9.0.0-preview.3 AS base
FROM mcr.microsoft.com/dotnet/aspnet:9.0.0-preview.4 AS base
WORKDIR /app
EXPOSE 8080
RUN apt update

View File

@ -2,7 +2,7 @@
通用后台权限管理系统、快速开发框架基于C#12/.NET9、Vue3/Vite、Element Plus等现代技术构建具有十分整洁、优雅的编码规范
[![.NET](https://github.com/nsnail/NetAdmin/actions/workflows/ci.yml/badge.svg)](https://github.com/nsnail/NetAdmin/actions/workflows/ci.yml)
[![.NET](https://github.com/nsnail/NetAdmin/actions/workflows/nightly-build.yml/badge.svg)](https://github.com/nsnail/NetAdmin/actions/workflows/nightly-build.yml)
[![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nsnail/NetAdmin/blob/main/LICENSE)
[![Furion](https://img.shields.io/badge/Furion-4.x-blueviolet.svg)](https://github.com/nsnail/NetAdmin/blob/main/LICENSE)
[![FreeSql](https://img.shields.io/badge/FreeSql-3.x-orange.svg)](https://github.com/nsnail/NetAdmin/blob/main/LICENSE)
@ -19,64 +19,47 @@ docker run -p 8080:8080 nsnail/netadmin
## 构建步骤
- 后端
1. 检查dotnet-sdk版本>=9.0.0
``` shell
dotnet --list-sdks
```shell
# 1. 克隆代码仓库
# 下载 git https://git-scm.com/downloads
git clone https://github.com/nsnail/NetAdmin.git && cd ./NetAdmin
# 下载 dotnet https://dotnet.microsoft.com/zh-cn/download/dotnet
```
2. 克隆代码仓库
``` shell
git clone https://github.com/nsnail/NetAdmin.git
cd ./NetAdmin
# 2. 检查dotnet-sdk版本>=9.0.0
# 下载 dotnet https://dotnet.microsoft.com/zh-cn/download/dotnet
dotnet --list-sdks
# 下载 git https://git-scm.com/downloads
```
3. 确保本机redis处于运行状态
``` shell
redis-cli
# 3. 确保本机redis处于运行状态
# 下载 redis for windows https://github.com/redis-windows/redis-windows/releases
# 下载 redis for linux/mac https://redis.io/download
redis-cli
# 下载 redis for windows https://github.com/redis-windows/redis-windows/releases
# 下载 redis for linux/mac https://redis.io/download
```
4. 运行后端WebApi
``` shell
dotnet run --project ./src/backend/NetAdmin.AdmServer.Host/NetAdmin.AdmServer.Host.csproj --urls http://[::]:5010 -is
```
5. 体验WebApi程序
- 浏览器打开 http://localhost:5010 将看到SwaggerKnife4jUI界面
# 4. 运行后端WebApi
# 浏览器打开 http://localhost:5010 将看到SwaggerKnife4jUI界面
dotnet run --project ./src/backend/NetAdmin.AdmServer.Host/NetAdmin.AdmServer.Host.csproj --urls http://[::]:5010 -is
---
# 5. 检查nodejs版本>=20
# 下载 nodejs https://nodejs.org/en/download
node -v
- 前端
1. 检查nodejs版本>=20
``` shell
node -v
# 6. 安装npm依赖包
cd ./src/frontend/admin && npm install
# 下载 nodejs https://nodejs.org/en/download
```
2. 安装npm依赖包
``` shell
cd ./src/frontend/admin
npm install
```
3. 运行前端项目
``` shell
npm run dev
```
4. 体验前端程序
- 浏览器打开 http://localhost:5020 将看到管理界面默认用户名root密码1234qwer
# 7. 运行前端项目
# 浏览器打开 http://localhost:5020 将看到管理界面默认用户名root密码1234qwer
npm run dev
```
## 文件目录树
```
+---.template.config # dotnet 项目模板配置目录
+---assets # 程序运行需要的资源文件目录
+---dist # 程序编译与分发的二进制文件目录
+---docs # 项目文档目录
+---refs # 引用的第三方项目仓库目录
+---src # 项目源文件目录
+---assets # 项目资源文件目录
+---build # 构建相关的工程文件目录
+---dist # 编译生成的二进制文件目录
+---docs # 项目开发文档目录
+---refs # 引用的第三方包的仓库目录
+---scripts # 各种工具脚本文件目录
+---src # 项目源码文件目录
```
## 后端项目架构

View File

@ -70,13 +70,13 @@
等于
等待发送
系统模块
绑定手机号
绑定手机号
结果非预期
群众
自定义
范围
菜单
解绑手机号
解绑手机号
警告
调试
跟踪

View File

@ -23,6 +23,7 @@ XML注释文件不存在
学历不正确
密码不能为空
已处理完毕
并发冲突请稍后重试
开始事务
性别不正确
手机号码不正确
@ -57,7 +58,7 @@ XML注释文件不存在
父节点不存在
用户不存在
用户名不能为空
用户名不能是手机号
用户名不能是手机号
用户名或密码错误
用户名长度4位以上
用户头像不能为空

View File

@ -6,7 +6,7 @@
"Id": 373837717815301,
"Name": "home",
"Path": "/home",
"Sort": 100,
"Sort": 999,
"Title": "主控面板",
"Type": 1
},

View File

@ -14,5 +14,25 @@
{
"ApiId": "api/sys/user",
"RoleId": 371729946431493
},
{
"ApiId": "api/sys/site.msg/unread.count",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/site.msg",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/site.msg/get.mine",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/site.msg/paged.query.mine",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/site.msg/set.site.msg.status",
"RoleId": 371729946431493,
}
]

View File

@ -15,15 +15,15 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.12-preview">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="4.12.2">
<PackageReference Include="Roslynator.Analyzers" Version="4.12.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.24.0.89429">
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.26.0.92422">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -1,9 +1,9 @@
{
"version": "1.0.0",
"version": "1.2.0",
"devDependencies": {
"cz-git": "^1.9.1",
"cz-git": "^1.9.2",
"commitizen": "^4.3.0",
"prettier": "^3.2.5",
"prettier": "^3.3.0",
"standard-version": "^9.5.0"
},
"config": {
@ -11,4 +11,4 @@
"path": "node_modules/cz-git"
}
}
}
}

View File

@ -9,7 +9,7 @@
"packages": [
{
"packageName": "Furion.Pure.NS",
"version": "4.9.2.31-ns3"
"version": "4.9.3-ns1"
}
]
}

View File

@ -4,6 +4,6 @@
<ProjectReference Include="../NetAdmin.AdmServer.Host/NetAdmin.AdmServer.Host.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0-release-24177-07"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,29 @@
using NetAdmin.Application.Repositories;
using NetAdmin.Domain.DbMaps.Dependency;
using RedLockNet;
namespace NetAdmin.Application.Services;
/// <summary>
/// RedLocker Service Base
/// </summary>
public abstract class RedLockerService<T1, T2>(DefaultRepository<T1> rpo, RedLocker redLocker)
: RepositoryService<T1, T2>(rpo)
where T1 : EntityBase
{
/// <summary>
/// 获取锁
/// </summary>
/// <exception cref="NetAdminGetLockerException">NetAdminGetLockerException</exception>
protected async Task<IRedLock> GetLockerAsync(string lockName)
{
// 加锁
var redLock = await redLocker.RedLockFactory.CreateLockAsync( //
lockName, TimeSpan.FromSeconds(Numbers.SECS_RED_LOCK_EXPIRY)
, TimeSpan.FromSeconds(Numbers.SECS_RED_LOCK_WAIT)
, TimeSpan.FromSeconds(Numbers.SECS_RED_LOCK_RETRY))
.ConfigureAwait(false);
return redLock.IsAcquired ? redLock : throw new NetAdminGetLockerException();
}
}

View File

@ -1,7 +1,7 @@
namespace NetAdmin.Domain.Attributes.DataValidation;
/// <summary>
/// 手机号验证器
/// 手机号验证器
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
public sealed class MobileAttribute : RegexAttribute

View File

@ -10,7 +10,7 @@ public sealed class PortAttribute : RangeAttribute
/// Initializes a new instance of the <see cref="PortAttribute" /> class.
/// </summary>
public PortAttribute() //
: base(1, 65535)
: base(1, ushort.MaxValue)
{
ErrorMessageResourceName = nameof(Ln.);
ErrorMessageResourceType = typeof(Ln);

View File

@ -27,8 +27,8 @@ public sealed class UserNameAttribute : RegexAttribute
return true;
}
// 不能是手机号
ErrorMessageResourceName = nameof(Ln.);
// 不能是手机号
ErrorMessageResourceName = nameof(Ln.);
return false;
}
}

View File

@ -61,7 +61,11 @@ public record Sys_Dept : VersionEntity, IFieldEnabled, IFieldSummary, IFieldSort
/// <summary>
/// 部门描述
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string Summary { get; init; }
}

View File

@ -37,6 +37,13 @@ public record Sys_Job : VersionEntity, IFieldEnabled, IFieldSummary
[JsonIgnore]
public virtual string JobName { get; init; }
/// <summary>
/// 上次执行耗时
/// </summary>
[Column]
[JsonIgnore]
public virtual long? LastDuration { get; init; }
/// <summary>
/// 上次执行时间
/// </summary>
@ -68,21 +75,33 @@ public record Sys_Job : VersionEntity, IFieldEnabled, IFieldSummary
/// <summary>
/// 请求体
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string RequestBody { get; init; }
/// <summary>
/// 请求头
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string RequestHeader { get; init; }
/// <summary>
/// 请求的网络地址
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string RequestUrl { get; init; }
@ -94,7 +113,11 @@ public record Sys_Job : VersionEntity, IFieldEnabled, IFieldSummary
public virtual JobStatues Status { get; init; }
/// <inheritdoc cref="IFieldSummary.Summary" />
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string Summary { get; init; }

View File

@ -48,14 +48,22 @@ public record Sys_JobRecord : LiteImmutableEntity
/// <summary>
/// 请求体
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string RequestBody { get; init; }
/// <summary>
/// 请求头
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string RequestHeader { get; init; }
@ -69,14 +77,22 @@ public record Sys_JobRecord : LiteImmutableEntity
/// <summary>
/// 响应体
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string ResponseBody { get; init; }
/// <summary>
/// 响应头
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string ResponseHeader { get; init; }

View File

@ -35,14 +35,22 @@ public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient
/// <summary>
/// 创建者来源地址
/// </summary>
#if DBTYPE_SQLITE
[Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#endif
[JsonIgnore]
public string CreatedReferer { get; init; }
/// <summary>
/// 创建者客户端用户代理
/// </summary>
#if DBTYPE_SQLITE
[Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_1022)]
#endif
[JsonIgnore]
public virtual string CreatedUserAgent { get; init; }
@ -63,14 +71,22 @@ public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient
/// <summary>
/// 异常信息
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string Exception { get; init; }
/// <summary>
/// 附加数据
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string ExtraData { get; init; }
@ -91,14 +107,22 @@ public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient
/// <summary>
/// 来源地址
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#endif
[JsonIgnore]
public virtual string ReferUrl { get; init; }
/// <summary>
/// 请求内容
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string RequestBody { get; init; }
@ -112,7 +136,11 @@ public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient
/// <summary>
/// 请求头信息
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string RequestHeaders { get; init; }
@ -126,7 +154,11 @@ public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient
/// <summary>
/// 响应内容
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string ResponseBody { get; init; }
@ -140,7 +172,11 @@ public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient
/// <summary>
/// 响应头
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string ResponseHeaders { get; init; }

View File

@ -85,7 +85,11 @@ public record Sys_Role : VersionEntity, IFieldSort, IFieldEnabled, IFieldSummary
/// <summary>
/// 备注
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string Summary { get; init; }

View File

@ -14,7 +14,11 @@ public record Sys_SiteMsg : VersionEntity, IRegister, IFieldSummary
/// <summary>
/// 消息内容
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string Content { get; init; }
@ -55,14 +59,22 @@ public record Sys_SiteMsg : VersionEntity, IRegister, IFieldSummary
/// <summary>
/// 消息摘要
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string Summary { get; init; }
/// <summary>
/// 消息主题
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public virtual string Title { get; init; }

View File

@ -49,7 +49,7 @@ public record Sys_User : VersionEntity, IFieldSummary, IFieldEnabled, IRegister
public virtual bool Enabled { get; init; }
/// <summary>
/// 手机号
/// 手机号
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_15)]
[JsonIgnore]
@ -85,7 +85,11 @@ public record Sys_User : VersionEntity, IFieldSummary, IFieldEnabled, IRegister
/// <summary>
/// 描述
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#endif
[JsonIgnore]
public virtual string Summary { get; init; }

View File

@ -80,7 +80,7 @@ public record Sys_UserProfile : VersionEntity, IRegister
public int? EmergencyContactArea { get; init; }
/// <summary>
/// 紧急联系人手机号
/// 紧急联系人手机号
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_15)]
[JsonIgnore]

View File

@ -33,7 +33,11 @@ public record Sys_VerifyCode : VersionEntity
/// <summary>
/// 发送报告
/// </summary>
#if DBTYPE_SQLITE
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#endif
[JsonIgnore]
public string Report { get; init; }

View File

@ -43,6 +43,10 @@ public sealed record QueryJobRsp : Sys_Job
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string JobName { get; init; }
/// <inheritdoc cref="Sys_Job.LastDuration" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? LastDuration { get; init; }
/// <inheritdoc cref="Sys_Job.LastExecTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override DateTime? LastExecTime { get; init; }

View File

@ -9,6 +9,10 @@ namespace NetAdmin.Domain.Dto.Sys.JobRecord;
/// </summary>
public sealed record QueryJobRecordRsp : Sys_JobRecord
{
/// <inheritdoc cref="Sys_JobRecord.HttpStatusCode" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public new HttpStatusCode HttpStatusCode => (HttpStatusCode)base.HttpStatusCode;
/// <inheritdoc cref="IFieldCreatedTime.CreatedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override DateTime CreatedTime { get; init; }
@ -21,10 +25,6 @@ public sealed record QueryJobRecordRsp : Sys_JobRecord
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override HttpMethods HttpMethod { get; init; }
/// <inheritdoc cref="Sys_JobRecord.HttpStatusCode" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override int HttpStatusCode { get; init; }
/// <inheritdoc cref="IFieldPrimary{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }

View File

@ -4,7 +4,7 @@ using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.User;
/// <summary>
/// 请求:检查手机号是否可用
/// 请求:检查手机号是否可用
/// </summary>
public sealed record CheckMobileAvailableReq : Sys_User
{

View File

@ -8,7 +8,7 @@ namespace NetAdmin.Domain.Dto.Sys.User;
public sealed record LoginByPwdReq : DataAbstraction
{
/// <summary>
/// 用户名、手机号、邮箱
/// 用户名、手机号、邮箱
/// </summary>
[Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.账号不能为空))]
public string Account { get; init; }

View File

@ -21,7 +21,7 @@ public sealed record LoginRsp : DataAbstraction
public void SetToRspHeader()
{
// 设置响应报文头
App.HttpContext.Response.Headers[Chars.FLG_HTTP_HEADER_VALUE_ACCESS_TOKEN] = AccessToken;
App.HttpContext.Response.Headers[Chars.FLG_HTTP_HEADER_KEY_ACCESS_TOKEN] = AccessToken;
App.HttpContext.Response.Headers[Chars.FLG_HTTP_HEADER_KEY_X_ACCESS_TOKEN] = RefreshToken;
}
}

View File

@ -3,7 +3,7 @@ using NetAdmin.Domain.Dto.Sys.VerifyCode;
namespace NetAdmin.Domain.Dto.Sys.User;
/// <summary>
/// 请求:设置手机号
/// 请求:设置手机号
/// </summary>
public sealed record SetMobileReq : DataAbstraction
{

View File

@ -7,9 +7,9 @@ namespace NetAdmin.Domain.Enums.Sys;
public enum VerifyCodeTypes
{
/// <summary>
/// 绑定手机号
/// 绑定手机号
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.绑定手机号))]
[ResourceDescription<Ln>(nameof(Ln.绑定手机号))]
LinkMobile = 1
,
@ -23,9 +23,9 @@ public enum VerifyCodeTypes
,
/// <summary>
/// 解绑手机号
/// 解绑手机号
/// </summary>
[ResourceDescription<Ln>(nameof(Ln.解绑手机号))]
[ResourceDescription<Ln>(nameof(Ln.解绑手机号))]
UnlinkMobile = 3
,

View File

@ -1,4 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DefineConstants>DBTYPE_SQLITE</DefineConstants>
</PropertyGroup>
<Import Project="$(SolutionDir)/build/code.quality.props"/>
<ItemGroup>
<Content Include="$(SolutionDir)/assets/seed-data/**" LinkBase="SeedData" CopyToOutputDirectory="PreserveNewest"/>

View File

@ -1,8 +1,16 @@
using Furion.FriendlyException;
using NetAdmin.Domain.Dto;
namespace NetAdmin.Host.Filters;
/// <inheritdoc cref="NetAdmin.Host.Filters.ApiResultHandler{T}" />
/// <inheritdoc cref="ApiResultHandler{T}" />
[SuppressSniffer]
[UnifyModel(typeof(RestfulInfo<>))]
public sealed class DefaultApiResultHandler : ApiResultHandler<RestfulInfo<object>>, IUnifyResultProvider;
public sealed class DefaultApiResultHandler : ApiResultHandler<RestfulInfo<object>>, IUnifyResultProvider
{
/// <inheritdoc />
public IActionResult OnAuthorizeException(DefaultHttpContext context, ExceptionMetadata metadata)
{
throw new NotImplementedException();
}
}

View File

@ -24,7 +24,12 @@ public sealed class RequestAuditMiddleware(
// 跳过处理的情况:
if (!context.Request.Path.StartsWithSegments(_defaultRoutePrefix) // 非api请求
|| context.Request.Path.StartsWithSegments(_healthCheckRoutePrefix) // 健康检查
|| context.Request.Method == Chars.FLG_HTTP_METHOD_OPTIONS) { // is options 请求
|| context.Request.Method == Chars.FLG_HTTP_METHOD_OPTIONS // is options 请求
|| (context.Request.ContentType?.StartsWith("multipart/form-data", true, CultureInfo.InvariantCulture) ??
false) // 文件上传
#pragma warning disable SA1009
) {
#pragma warning restore SA1009
await next(context).ConfigureAwait(false);
return;
}

View File

@ -14,6 +14,7 @@ public static class Chars
public const string FLG_CONTEXT_OWNER_DEPT_ID = nameof(FLG_CONTEXT_OWNER_DEPT_ID);
public const string FLG_CONTEXT_USER_ID = nameof(FLG_CONTEXT_USER_ID);
public const string FLG_CONTEXT_USER_INFO = nameof(FLG_CONTEXT_USER_INFO);
public const string FLG_CRON_PER_SECS = "* * * * * *";
public const string FLG_DB_EXCEPTION_PRIVATE_KEY_CONFLICT = "PRIMARY KEY";
public const string FLG_DB_FIELD_TYPE_NVARCHAR = "nvarchar";
public const string FLG_DB_FIELD_TYPE_NVARCHAR_1022 = "nvarchar(1022)";
@ -46,6 +47,7 @@ public static class Chars
public const string FLG_FREE_SQL_GLOBAL_FILTER_MEMBER = nameof(FLG_FREE_SQL_GLOBAL_FILTER_MEMBER);
public const string FLG_FREE_SQL_GLOBAL_FILTER_SELF = nameof(FLG_FREE_SQL_GLOBAL_FILTER_SELF);
public const string FLG_FREE_SQL_GLOBAL_FILTER_TENANT = nameof(FLG_FREE_SQL_GLOBAL_FILTER_TENANT);
public const string FLG_HTTP_HEADER_KEY_ACCESS_TOKEN = "ACCESS-TOKEN";
public const string FLG_HTTP_HEADER_KEY_AUTHORIZATION = "Authorization";
public const string FLG_HTTP_HEADER_KEY_REFERER = "Referer";
public const string FLG_HTTP_HEADER_KEY_USER_AGENT = "User-Agent";
@ -53,7 +55,6 @@ public static class Chars
public const string FLG_HTTP_HEADER_KEY_X_ACCESS_TOKEN_HEADER_KEY = "X-Authorization";
public const string FLG_HTTP_HEADER_KEY_X_FORWARDED_FOR = "X-Forwarded-For";
public const string FLG_HTTP_HEADER_KEY_X_REAL_IP = "X-Real-IP";
public const string FLG_HTTP_HEADER_VALUE_ACCESS_TOKEN = "ACCESS-TOKEN";
public const string FLG_HTTP_HEADER_VALUE_APPLICATION_JSON = "application/json";
public const string FLG_HTTP_HEADER_VALUE_APPLICATION_URLENCODED = "application/x-www-form-urlencoded";
public const string FLG_HTTP_HEADER_VALUE_AUTH_SCHEMA = "Bearer";

View File

@ -8,11 +8,12 @@
<ItemGroup>
<PackageReference Include="Cronos" Version="0.8.4"/>
<PackageReference Include="FreeSql.DbContext.NS" Version="3.2.821-ns1"/>
<PackageReference Include="FreeSql.Provider.SqlServer.NS" Version="3.2.821-ns1"/>
<PackageReference Include="FreeSql.Provider.Sqlite.NS" Version="3.2.821-ns1"/>
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.2.31"/>
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster.NS" Version="4.9.2.31-ns1"/>
<PackageReference Include="Furion.Pure.NS" Version="4.9.2.31-ns1"/>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.0-preview.3.24172.13"/>
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.3"/>
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster.NS" Version="4.9.3-ns1"/>
<PackageReference Include="Furion.Pure.NS" Version="4.9.3-ns1"/>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.0-preview.4.24267.6"/>
<PackageReference Include="Minio" Version="6.0.2"/>
<PackageReference Include="NSExt" Version="2.1.0"/>
<PackageReference Include="RedLock.net" Version="2.3.2"/>

View File

@ -20,6 +20,11 @@ public interface IJobModule : ICrudModule<CreateJobReq, QueryJobRsp // 创建类
/// </summary>
Task<QueryJobRsp> EditAsync(UpdateJobReq req);
/// <summary>
/// 执行作业
/// </summary>
Task ExecuteAsync(QueryJobReq req);
/// <summary>
/// 获取作业记录条形图数据
/// </summary>

View File

@ -15,7 +15,7 @@ public interface IUserModule : ICrudModule<CreateUserReq, QueryUserRsp // 创建
>
{
/// <summary>
/// 检查手机号是否可用
/// 检查手机号是否可用
/// </summary>
Task<bool> CheckMobileAvailableAsync(CheckMobileAvailableReq req);
@ -65,7 +65,7 @@ public interface IUserModule : ICrudModule<CreateUserReq, QueryUserRsp // 创建
Task SetEnabledAsync(SetUserEnabledReq req);
/// <summary>
/// 设置手机号
/// 设置手机号
/// </summary>
Task<UserInfoRsp> SetMobileAsync(SetMobileReq req);

View File

@ -1,4 +1,5 @@
using Cronos;
using FreeSql.Internal;
using NetAdmin.Application.Repositories;
using NetAdmin.Application.Services;
using NetAdmin.Domain.DbMaps.Sys;
@ -63,18 +64,60 @@ public sealed class JobService(DefaultRepository<Sys_Job> rpo, IJobRecordService
public async Task<QueryJobRsp> EditAsync(UpdateJobReq req)
{
req.ThrowIfInvalid();
var ret = await Rpo.UpdateDiy.Set(a => a.ExecutionCron == req.ExecutionCron)
.Set(a => a.HttpMethod == req.HttpMethod)
.Set(a => a.JobName == req.JobName)
.SetIf(req.RequestHeaders == null, a => a.RequestHeader, null)
.SetIf(req.RequestHeaders != null, a => a.RequestHeader, req.RequestHeaders.Json())
.Set(a => a.RequestBody == req.RequestBody)
.Set(a => a.RequestUrl == req.RequestUrl)
.Set(a => a.UserId == req.UserId)
.Where(a => a.Id == req.Id)
.ExecuteUpdatedAsync()
.ConfigureAwait(false);
return ret[0].Adapt<QueryJobRsp>();
var update = Rpo.UpdateDiy.Set(a => a.ExecutionCron == req.ExecutionCron)
.Set(a => a.HttpMethod == req.HttpMethod)
.Set(a => a.JobName == req.JobName)
.SetIf(req.RequestHeaders == null, a => a.RequestHeader, null)
.SetIf(req.RequestHeaders != null, a => a.RequestHeader, req.RequestHeaders.Json())
.Set(a => a.RequestBody == req.RequestBody)
.Set(a => a.RequestUrl == req.RequestUrl)
.Set(a => a.UserId == req.UserId)
.Where(a => a.Id == req.Id);
#pragma warning disable IDE0046
if (Rpo.Orm.Ado.DataType == DataType.Sqlite) {
#pragma warning restore IDE0046
return await update.ExecuteAffrowsAsync().ConfigureAwait(false) <= 0
? null
: await GetAsync(new QueryJobReq { Id = req.Id }).ConfigureAwait(false);
}
return (await update.ExecuteUpdatedAsync().ConfigureAwait(false))[0].Adapt<QueryJobRsp>();
}
/// <inheritdoc />
public async Task ExecuteAsync(QueryJobReq req)
{
req.ThrowIfInvalid();
var df = new DynamicFilterInfo {
Filters = [
new DynamicFilterInfo {
Field = nameof(QueryJobReq.Enabled)
, Operator = DynamicFilterOperators.Eq
, Value = true
}
, new DynamicFilterInfo {
Field = nameof(QueryJobReq.Status)
, Operator = DynamicFilterOperators.Eq
, Value = JobStatues.Idle
}
]
};
var job = await QueryInternal(new QueryReq<QueryJobReq> { Count = 1, Filter = req, DynamicFilter = df })
.ToOneAsync()
.ConfigureAwait(false) ?? throw new NetAdminInvalidOperationException(Ln.);
var nextExecTime = GetNextExecTime(Chars.FLG_CRON_PER_SECS);
try {
_ = await UpdateAsync(job.Adapt<UpdateJobReq>() with {
NextExecTime = nextExecTime
, NextTimeId = nextExecTime?.TimeUnixUtc()
})
.ConfigureAwait(false);
}
catch (DbUpdateVersionException) {
throw new NetAdminInvalidOperationException(Ln.);
}
}
/// <inheritdoc />
@ -127,14 +170,14 @@ public sealed class JobService(DefaultRepository<Sys_Job> rpo, IJobRecordService
}
]
};
var job
= await QueryInternal(new QueryReq<QueryJobReq> { DynamicFilter = df, Count = 1, Order = Orders.Random })
.Where(a => !Rpo.Orm.Select<Sys_JobRecord>()
.As("b")
.Where(b => b.JobId == a.Id && b.TimeId == a.NextTimeId)
.Any())
.ToOneAsync()
.ConfigureAwait(false);
var job = await QueryInternal(new QueryReq<QueryJobReq> { DynamicFilter = df, Order = Orders.Random })
.Take(1)
.Where(a => !Rpo.Orm.Select<Sys_JobRecord>()
.As("b")
.Where(b => b.JobId == a.Id && b.TimeId == a.NextTimeId)
.Any())
.ToOneAsync()
.ConfigureAwait(false);
return job == null
? null
: await UpdateAsync(job.Adapt<UpdateJobReq>() with {

View File

@ -131,11 +131,15 @@ public sealed class MenuService(DefaultRepository<Sys_Menu> rpo, IUserService us
private ISelect<Sys_Menu> QueryInternal(QueryReq<QueryMenuReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
return req.Order == Orders.Random
? ret.OrderByRandom()
: ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending)
.OrderByDescending(a => a.Sort)
.OrderBy(a => a.Name)
.OrderBy(a => a.Id);
#pragma warning disable IDE0072
return req.Order switch {
Orders.None => ret
, Orders.Random => ret.OrderByRandom()
, _ => ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending)
.OrderByDescending(a => a.Sort)
.OrderBy(a => a.Name)
.OrderBy(a => a.Id)
};
#pragma warning restore IDE0072
}
}

View File

@ -124,8 +124,11 @@ public sealed class RoleService(DefaultRepository<Sys_Role> rpo) //
req.Keywords?.Length > 0
, a => a.Id == req.Keywords.Int64Try(0) || a.Name.Contains(req.Keywords) ||
a.Summary.Contains(req.Keywords));
if (req.Order == Orders.Random) {
return ret.OrderByRandom();
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);

View File

@ -152,7 +152,7 @@ public sealed class UserProfileService(DefaultRepository<Sys_UserProfile> rpo) /
private ISelect<Sys_UserProfile, Sys_DicContent, Sys_DicContent, Sys_DicContent, Sys_DicContent> QueryInternal(
QueryReq<QueryUserProfileReq> req)
{
#pragma warning disable CA1305
#pragma warning disable CA1305,IDE0072
var ret = Rpo.Orm.Select<Sys_UserProfile, Sys_DicContent, Sys_DicContent, Sys_DicContent, Sys_DicContent>()
.LeftJoin((a, b, _, __, ___) =>
a.NationArea.ToString() == b.Value && b.CatalogId == Numbers.ID_DIC_CATALOG_GEO_AREA)
@ -164,10 +164,13 @@ public sealed class UserProfileService(DefaultRepository<Sys_UserProfile> rpo) /
.LeftJoin((a, _, __, ___, e) => a.EmergencyContactArea.ToString() == e.Value &&
e.CatalogId == Numbers.ID_DIC_CATALOG_GEO_AREA)
.WhereDynamicFilter(req.DynamicFilter);
return req.Order == Orders.Random
? ret.OrderByRandom()
: ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending)
.OrderByDescending((a, _, __, ___, ____) => a.Id);
#pragma warning restore CA1305
return req.Order switch {
Orders.None => ret
, Orders.Random => ret.OrderByRandom()
, _ => ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending)
.OrderByDescending((a, _, __, ___, ____) => a.Id)
};
#pragma warning restore CA1305,IDE0072
}
}

View File

@ -134,9 +134,9 @@ public sealed class UserService(
req.ThrowIfInvalid();
// ReSharper disable once MethodHasAsyncOverload
#pragma warning disable VSTHRD103
#pragma warning disable VSTHRD103,S6966
return (await QueryInternal(new QueryReq<QueryUserReq> { Filter = req })
#pragma warning restore VSTHRD103
#pragma warning restore S6966, VSTHRD103
.ForUpdate()
.ToOneAsync()
.ConfigureAwait(false)).Adapt<QueryUserRsp>();
@ -257,7 +257,9 @@ public sealed class UserService(
if (await Rpo.UpdateDiy
.SetSource(req with {
Id = UserToken.Id
, Version = Rpo.Where(a => a.Id == UserToken.Id).ToOne(a => a.Version)
, Version = await Rpo.Where(a => a.Id == UserToken.Id)
.ToOneAsync(a => a.Version)
.ConfigureAwait(false)
})
.UpdateColumns(a => a.Avatar)
.ExecuteAffrowsAsync()
@ -278,9 +280,11 @@ public sealed class UserService(
public async Task<UserInfoRsp> SetEmailAsync(SetEmailReq req)
{
req.ThrowIfInvalid();
var user = Rpo.Where(a => a.Id == UserToken.Id).ToOne(a => new { a.Mobile, a.Version, a.Email });
var user = await Rpo.Where(a => a.Id == UserToken.Id)
.ToOneAsync(a => new { a.Mobile, a.Version, a.Email })
.ConfigureAwait(false);
// 如果已绑定手机号、需要手机安全验证
// 如果已绑定手机号、需要手机安全验证
if (!user.Mobile.NullOrEmpty()) {
if (!await verifyCodeService.VerifyAsync(req.VerifySmsCodeReq).ConfigureAwait(false)) {
throw new NetAdminInvalidOperationException(Ln.);
@ -324,7 +328,7 @@ public sealed class UserService(
.ConfigureAwait(false);
if (!user.Mobile.NullOrEmpty()) {
// 已有手机号,需验证旧手机
// 已有手机号,需验证旧手机
if (!await verifyCodeService.VerifyAsync(req.OriginVerifySmsCodeReq).ConfigureAwait(false)) {
throw new NetAdminInvalidOperationException($"{Ln.旧手机号码验证码不正确}");
}
@ -334,7 +338,7 @@ public sealed class UserService(
}
}
// 验证新手机号
// 验证新手机号
if (!await verifyCodeService.VerifyAsync(req.NewVerifySmsCodeReq).ConfigureAwait(false)) {
throw new NetAdminInvalidOperationException($"{Ln.新手机号码验证码不正确}");
}

View File

@ -173,8 +173,7 @@ public sealed class VerifyCodeService(DefaultRepository<Sys_VerifyCode> rpo, IEv
private Task<Sys_VerifyCode> GetLastSentAsync(string destDevice)
{
return QueryInternal(new QueryReq<QueryVerifyCodeReq> {
Count = 1
, DynamicFilter
DynamicFilter
= new DynamicFilterInfo {
Field = nameof(
Sys_VerifyCode.DestDevice)
@ -182,7 +181,8 @@ public sealed class VerifyCodeService(DefaultRepository<Sys_VerifyCode> rpo, IEv
, Value = destDevice
}
})
.ToOneAsync();
.Take(1)
.ToOneAsync();
}
private ISelect<Sys_VerifyCode> QueryInternal(QueryReq<QueryVerifyCodeReq> req)

View File

@ -42,6 +42,12 @@ public sealed class JobCache(IDistributedCache cache, IJobService service)
return Service.EditAsync(req);
}
/// <inheritdoc />
public Task ExecuteAsync(QueryJobReq req)
{
return Service.ExecuteAsync(req);
}
/// <inheritdoc />
public Task<bool> ExistAsync(QueryReq<QueryJobReq> req)
{

View File

@ -60,6 +60,14 @@ public sealed class JobController(IJobCache cache) : ControllerBase<IJobCache, I
return Cache.EditAsync(req);
}
/// <summary>
/// 执行作业
/// </summary>
public Task ExecuteAsync(QueryJobReq req)
{
return Cache.ExecuteAsync(req);
}
/// <summary>
/// 计划作业是否存在
/// </summary>

View File

@ -27,7 +27,7 @@ public sealed class UserController(IUserCache cache, IConfigCache configCache)
}
/// <summary>
/// 检查手机号是否可用
/// 检查手机号是否可用
/// </summary>
[AllowAnonymous]
public Task<bool> CheckMobileAvailableAsync(CheckMobileAvailableReq req)
@ -192,7 +192,7 @@ public sealed class UserController(IUserCache cache, IConfigCache configCache)
}
/// <summary>
/// 设置手机号
/// 设置手机号
/// </summary>
[Transaction]
public Task<UserInfoRsp> SetMobileAsync(SetMobileReq req)

View File

@ -68,7 +68,7 @@ public sealed class ScheduledJob : WorkBase<ScheduledJob>, IJob
var request = BuildRequest(job);
var sw = new Stopwatch();
sw.Start();
var rsp = await request.SendAsync(cancelToken).ConfigureAwait(false);
var rsp = await request.SendAsync(CancellationToken.None).ConfigureAwait(false);
if (rsp.StatusCode == HttpStatusCode.Unauthorized) {
var loginRsp = await _userService.LoginByUserIdAsync(job.UserId).ConfigureAwait(false);
#pragma warning disable S2696
@ -76,12 +76,13 @@ public sealed class ScheduledJob : WorkBase<ScheduledJob>, IJob
_refreshToken = loginRsp.RefreshToken;
#pragma warning restore S2696
request = BuildRequest(job);
rsp = await request.SendAsync(cancelToken).ConfigureAwait(false);
rsp = await request.SendAsync(CancellationToken.None).ConfigureAwait(false);
}
sw.Stop();
await UowManager.AtomicOperateAsync(async () => {
var rspBody = await rsp.Content.ReadAsStringAsync(cancelToken).ConfigureAwait(false);
var rspBody = await rsp.Content.ReadAsStringAsync(CancellationToken.None)
.ConfigureAwait(false);
var jobRecord = new CreateJobRecordReq //
{
Duration = sw.ElapsedMilliseconds
@ -97,7 +98,10 @@ public sealed class ScheduledJob : WorkBase<ScheduledJob>, IJob
};
_ = await _jobRecordService.CreateAsync(jobRecord).ConfigureAwait(false);
await _jobService
.FinishJobAsync(job.Adapt<UpdateJobReq>() with { LastStatusCode = rsp.StatusCode })
.FinishJobAsync(job.Adapt<UpdateJobReq>() with {
LastStatusCode = rsp.StatusCode
, LastDuration = jobRecord.Duration
})
.ConfigureAwait(false);
})
.ConfigureAwait(false);
@ -128,7 +132,7 @@ public sealed class ScheduledJob : WorkBase<ScheduledJob>, IJob
ret = ret.SetBody(job.RequestBody);
}
return ret.OnResponsing(GetRequestHeader).OnException(GetRequestHeader);
return ret.SetLog(_logger).OnResponsing(GetRequestHeader).OnException(GetRequestHeader);
}
private void GetRequestHeader(HttpClient _, HttpResponseMessage rsp, string __)

View File

@ -3,9 +3,9 @@
<ProjectReference Include="../NetAdmin.Host/NetAdmin.Host.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="xunit" Version="2.8.0"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0-preview.3.24172.13"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
<PackageReference Include="xunit" Version="2.8.1"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0-preview.4.24267.6"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@ -11,14 +11,14 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@tinymce/tinymce-vue": "^5.1.1",
"ace-builds": "^1.33.1",
"axios": "^1.6.8",
"ace-builds": "^1.34.2",
"axios": "^1.7.2",
"clipboard": "^2.0.11",
"core-js": "^3.37.0",
"core-js": "^3.37.1",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"echarts": "^5.5.0",
"element-plus": "^2.7.1",
"element-plus": "^2.7.4",
"json-bigint": "^1.0.0",
"json5-to-table": "^0.1.8",
"markdown-it": "^14.1.0",
@ -29,23 +29,23 @@
"sortablejs": "^1.15.2",
"tinymce": "^6.8.3",
"vkbeautify": "^0.99.3",
"vue": "^3.4.25",
"vue": "^3.4.27",
"vue-i18n": "^9.13.1",
"vue-router": "^4.3.2",
"vue3-ace-editor": "^2.2.4",
"vue3-json-viewer": "^2.2.2",
"vuedraggable": "^4.0.3",
"vuex": "^4.1.0",
"xgplayer": "^3.0.16",
"xgplayer-hls": "^3.0.16"
"xgplayer": "^3.0.18",
"xgplayer-hls": "^3.0.18"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"prettier": "^3.2.5",
"@vitejs/plugin-vue": "^5.0.5",
"prettier": "^3.3.0",
"prettier-plugin-organize-attributes": "^1.0.0",
"sass": "^1.75.0",
"terser": "^5.30.4",
"vite": "^5.2.10"
"sass": "^1.77.4",
"terser": "^5.31.0",
"vite": "^5.2.12"
},
"browserslist": [
"> 1%",

View File

@ -60,6 +60,17 @@ export default {
},
},
/**
* 执行作业
*/
execute: {
url: `${config.API_URL}/api/sys/job/execute`,
name: `执行作业`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 计划作业是否存在
*/

View File

@ -0,0 +1,7 @@
<template>
<svg class="icon" height="256" p-id="2661" t="1716619036537" version="1.1" viewBox="0 0 1024 1024" width="256" xmlns="http://www.w3.org/2000/svg">
<path
d="M874.666667 181.333333H149.333333c-40.533333 0-74.666667 34.133333-74.666666 74.666667v512c0 40.533333 34.133333 74.666667 74.666666 74.666667h725.333334c40.533333 0 74.666667-34.133333 74.666666-74.666667V256c0-40.533333-34.133333-74.666667-74.666666-74.666667z m-725.333334 64h725.333334c6.4 0 10.666667 4.266667 10.666666 10.666667v25.6L512 516.266667l-373.333333-234.666667V256c0-6.4 4.266667-10.666667 10.666666-10.666667z m725.333334 533.333334H149.333333c-6.4 0-10.666667-4.266667-10.666666-10.666667V356.266667l356.266666 224c4.266667 4.266667 10.666667 4.266667 17.066667 4.266666s12.8-2.133333 17.066667-4.266666l356.266666-224V768c0 6.4-4.266667 10.666667-10.666666 10.666667z"
p-id="2662"></path>
</svg>
</template>

View File

@ -0,0 +1,13 @@
<template>
<svg class="icon" height="256" p-id="9218" t="1716619196030" version="1.1" viewBox="0 0 1024 1024" width="256" xmlns="http://www.w3.org/2000/svg">
<path
d="M642.0992 501.5552a33.28 33.28 0 0 1 52.4288 40.6528l-3.328 4.2496-154.624 168.96a33.28 33.28 0 0 1-41.6256 6.144l-4.3008-3.072-128-107.008a33.28 33.28 0 0 1 38.1952-54.1696l4.4544 3.072 103.5776 86.6304 133.2224-145.4592z"
p-id="9219"></path>
<path
d="M793.6 153.6a102.4 102.4 0 0 1 102.4 102.4v512a102.4 102.4 0 0 1-102.4 102.4h-563.2a102.4 102.4 0 0 1-102.4-102.4V256a102.4 102.4 0 0 1 102.4-102.4h563.2z m0 66.56h-563.2a35.84 35.84 0 0 0-35.5328 30.976L194.56 256v512a35.84 35.84 0 0 0 30.976 35.5328l4.864 0.3072h563.2a35.84 35.84 0 0 0 35.5328-30.976L829.44 768V256a35.84 35.84 0 0 0-30.976-35.5328L793.6 220.16z"
p-id="9220"></path>
<path
d="M821.248 242.176a33.28 33.28 0 0 1 36.352 55.3984l-4.5056 2.9696-300.7488 164.096a84.48 84.48 0 0 1-72.9088 3.84l-7.68-3.6864-304.128-164.1472a33.28 33.28 0 0 1 26.7264-60.7744l4.9152 2.2016 304.128 164.1984a17.92 17.92 0 0 0 13.7728 1.3312l3.2768-1.3824 300.8-164.096z"
p-id="9221"></path>
</svg>
</template>

View File

@ -0,0 +1,7 @@
<template>
<svg class="icon" height="256" p-id="3820" t="1716618918266" version="1.1" viewBox="0 0 1024 1024" width="256" xmlns="http://www.w3.org/2000/svg">
<path
d="M736 0h-448C235.2 0 192 43.2 192 96v832c0 52.8 43.2 96 96 96h448c52.8 0 96-43.2 96-96v-832c0-52.8-43.2-96-96-96zM384 48h256v32H384v-32zM512 960a64 64 0 1 1 0-128 64 64 0 0 1 0 128z m256-192H256V128h512v640z"
p-id="3821"></path>
</svg>
</template>

View File

@ -1,15 +1,16 @@
<template>
<svg
class="icon"
height="128"
p-id="17757"
t="1708507211716"
version="1.1"
viewBox="0 0 1024 1024"
width="128"
xmlns="http://www.w3.org/2000/svg">
<svg class="icon" height="256" p-id="5451" t="1716619102648" version="1.1" viewBox="0 0 1024 1024" width="256" xmlns="http://www.w3.org/2000/svg">
<path
d="M179.2 742.4v102.4c0 26.368 21.6576 48.128 49.408 50.8928l6.0416 0.3072h554.7008c28.5696 0 52.1216-19.968 55.1424-45.6192l0.3072-5.5808v-102.4h51.2v102.4c0 54.1184-44.9536 98.4064-101.888 102.144l-7.8336 0.256H237.7216c-57.9584 0-105.472-41.984-109.4656-95.0784L128 844.8v-102.4h51.2z m682.5984-399.36v45.6192l-65.8944 78.336c49.7664 2.7648 78.336 40.0896 78.336 97.2288 0 61.2864-35.0208 106.9056-102.2976 106.9056-67.7376 0-102.7584-45.1584-102.7584-106.4448v-0.9216l56.6784-3.6864v3.6864c0 40.0896 18.8928 58.5216 46.08 58.5216 26.7264 0 45.6192-18.432 45.6192-58.5216s-17.9712-58.5216-47.0016-58.5216a50.3296 50.3296 0 0 0-22.1184 5.0688l-27.648-30.4128 74.1888-88.0128h-115.6608V343.04h182.4768z m-570.2144 0v273.2544h61.7472V665.6H172.2368v-49.3056h62.6688V403.4048l-62.6688 25.8048V375.7568L243.6608 343.04h47.9232z m220.8256-5.5296c60.8256 0 99.9936 42.3936 99.9936 97.6896 0 32.7168-11.52 52.5312-31.7952 76.032l-92.6208 105.5232h124.416V665.6H412.416v-41.0112l123.9552-142.848c14.7456-17.0496 19.3536-29.952 19.3536-46.08 0-32.256-17.9712-49.3056-43.3152-49.3056-25.8048 0-43.3152 17.0496-43.3152 49.3056v11.0592l-56.6784-3.6864V435.2c0-55.296 37.3248-97.6896 99.9936-97.6896z m273.92-260.7104c57.9072 0 105.3696 41.984 109.4144 95.0784l0.256 7.3216v102.4h-51.2v-102.4c0-26.368-21.6576-48.128-49.408-50.8928l-6.0416-0.3072H234.6496c-28.5696 0-52.1216 19.968-55.1424 45.6192L179.2 179.2v102.4h-51.2v-102.4c0-54.1184 44.9536-98.4064 101.888-102.144l7.8336-0.256h548.5568z"
p-id="17758"></path>
d="M932.8 1024H495.274667c-50.304 0-91.242667-41.088-91.242667-91.562667V588.032a21.333333 21.333333 0 1 1 42.666667 0v344.405333A48.789333 48.789333 0 0 0 495.274667 981.333333h437.525333A48.768 48.768 0 0 0 981.333333 932.437333V310.208a48.768 48.768 0 0 0-48.533333-48.874667H771.2a21.333333 21.333333 0 1 1 0-42.666666h161.578667c50.282667 0 91.2 41.066667 91.2 91.541333v622.229333C1024 982.912 983.082667 1024 932.8 1024z"
p-id="5452"></path>
<path
d="M930.133333 876.394667H497.941333a21.333333 21.333333 0 0 1-21.333333-21.333334V599.786667a21.333333 21.333333 0 1 1 42.666667 0v233.941333H908.8V342.656h-121.92a21.333333 21.333333 0 1 1 0-42.666667H930.133333a21.333333 21.333333 0 0 1 21.333334 21.333334v533.738666a21.333333 21.333333 0 0 1-21.333334 21.333334zM745.770667 951.04H682.24a21.333333 21.333333 0 1 1 0-42.666667h63.530667a21.333333 21.333333 0 1 1 0 42.666667z"
p-id="5453"></path>
<path
d="M780.352 699.285333a21.269333 21.269333 0 0 1-5.973333-0.853333l-334.421334-97.621333c-13.866667 1.024-26.688 1.514667-39.104 1.514666C179.818667 602.325333 0 467.221333 0 301.162667S179.818667 0 400.832 0s400.832 135.104 400.832 301.162667c0 6.656-0.32 13.312-0.874667 19.861333-7.36 85.205333-63.552 164.522667-152.170666 216.938667l145.536 123.733333a21.312 21.312 0 0 1-13.802667 37.589333z m-338.154667-141.376c2.026667 0 4.032 0.298667 5.973334 0.853334l242.197333 70.677333-93.141333-79.168a21.312 21.312 0 0 1 4.416-35.392c91.264-44.821333 149.824-118.656 156.650666-197.482667 0.448-5.333333 0.704-10.794667 0.704-16.256C758.997333 158.634667 598.336 42.666667 400.832 42.666667 203.349333 42.666667 42.666667 158.634667 42.666667 301.162667s160.682667 258.496 358.186666 258.496c12.437333 0 25.386667-0.533333 39.658667-1.685334 0.554667-0.042667 1.130667-0.064 1.685333-0.064z"
p-id="5454"></path>
<path
d="M190.528 355.946667a77.802667 77.802667 0 0 0 39.488 11.157333c22.528 0 35.690667-11.904 35.690667-29.141333 0-15.914667-9.130667-25.066667-32.149334-33.877334-27.84-9.898667-45.056-24.32-45.056-48.362666 0-26.581333 22.016-46.314667 55.168-46.314667 17.450667 0 30.144 4.032 37.717334 8.362667l-6.08 17.962666a67.776 67.776 0 0 0-32.405334-8.106666c-23.296 0-32.128 13.930667-32.128 25.557333 0 15.957333 10.368 23.786667 33.898667 32.938667 28.864 11.114667 43.541333 25.024 43.541333 50.069333 0 26.346667-19.498667 49.130667-59.733333 49.130667-16.448 0-34.410667-4.821333-43.541333-10.88l5.589333-18.496zM462.186667 307.882667c-1.258667-23.829333-2.773333-52.394667-2.538667-73.664h-0.768a1007.829333 1007.829333 0 0 1-21.482667 64.789333l-30.144 82.773333h-16.682666l-27.584-81.237333c-8.106667-24.064-14.954667-46.08-19.754667-66.325333h-0.512c-0.490667 21.269333-1.770667 49.834667-3.285333 75.434666l-4.544 73.130667h-21.013334l11.904-170.602667h28.096l29.098667 82.517334c7.082667 21.013333 12.885333 39.722667 17.216 57.450666h0.768c4.309333-17.194667 10.389333-35.946667 17.962667-57.450666l30.378666-82.517334h28.096l10.645334 170.602667h-21.525334l-4.330666-74.901333zM518.997333 355.946667a77.76 77.76 0 0 0 39.509334 11.157333c22.506667 0 35.669333-11.904 35.669333-29.141333 0-15.914667-9.130667-25.066667-32.128-33.877334-27.882667-9.898667-45.077333-24.32-45.077333-48.362666 0-26.581333 22.037333-46.314667 55.146666-46.314667 17.450667 0 30.122667 4.032 37.738667 8.362667l-6.08 17.962666a67.776 67.776 0 0 0-32.405333-8.106666c-23.296 0-32.170667 13.930667-32.170667 25.557333 0 15.957333 10.368 23.786667 33.941333 32.938667 28.864 11.114667 43.541333 25.024 43.541334 50.069333 0 26.346667-19.477333 49.130667-59.733334 49.130667-16.448 0-34.432-4.821333-43.562666-10.88l5.610666-18.496z"
p-id="5455"></path>
</svg>
</template>

View File

@ -44,7 +44,6 @@ export { default as Robot } from './Robot.vue'
export { default as Role } from './Role.vue'
export { default as ScheduledJob } from './ScheduledJob.vue'
export { default as Send } from './Send.vue'
export { default as SmsCode } from './SmsCode.vue'
export { default as Stats } from './Stats.vue'
export { default as Sync } from './Sync.vue'
export { default as Task } from './Task.vue'
@ -67,4 +66,8 @@ export { default as Collect } from './Collect.vue'
export { default as FreeSql } from './FreeSql.vue'
export { default as Performance } from './Performance.vue'
export { default as Proxy } from './Proxy.vue'
export { default as ECharts } from './ECharts.vue'
export { default as ECharts } from './ECharts.vue'
export { default as Mobile } from './Mobile.vue'
export { default as Email } from './Email.vue'
export { default as SmsCode } from './SmsCode.vue'
export { default as MailCode } from './MailCode.vue'

View File

@ -2,7 +2,7 @@
<el-table-column :label="label" :prop="prop" sortable="custom">
<template #default="scope">
<div class="avatar">
<el-avatar :src="getAvatar(scope)" size="small"></el-avatar>
<el-avatar :src="getAvatar(scope, prop)" size="small"></el-avatar>
<span>{{ tool.getNestedProperty(scope.row, prop) }}</span>
</div>
</template>
@ -36,8 +36,8 @@ export default {
},
methods: {
//获取头像
getAvatar(scope) {
return scope.row.avatar ? scope.row.avatar : this.$CONFIG.DEFAULT_AVATAR
getAvatar(scope, prop) {
return scope.row.avatar ? scope.row.avatar : this.$CONFIG.DEFAULT_AVATAR(tool.getNestedProperty(scope.row, prop))
},
},
}

View File

@ -0,0 +1,33 @@
<template>
<el-table-column v-bind="$attrs">
<template #default="scope">
<el-text @click="click(scope.row)" style="cursor: pointer" tag="ins">
{{ tool.getNestedProperty(scope.row, $attrs.prop) }}
</el-text>
</template>
</el-table-column>
</template>
<script>
import tool from '@/utils/tool'
export default {
emits: ['click'],
props: {},
data() {
return {}
},
mounted() {},
created() {},
components: {},
computed: {
tool() {
return tool
},
},
methods: {
async click(row) {
this.$emit('click', row)
},
},
}
</script>
<style scoped></style>

View File

@ -2,7 +2,10 @@
<el-table-column v-bind="$attrs">
<template #default="scope">
<div @click="click(tool.getNestedProperty(scope.row, $attrs.prop))" class="avatar">
<el-avatar v-if="tool.getNestedProperty(scope.row, $attrs.nestProp)" :src="getAvatar(scope)" size="small"></el-avatar>
<el-avatar
v-if="tool.getNestedProperty(scope.row, $attrs.nestProp)"
:src="getAvatar(scope, $attrs.nestProp)"
size="small"></el-avatar>
<div>
<p>{{ tool.getNestedProperty(scope.row, $attrs.nestProp) }}</p>
<p v-if="$attrs.nestProp2">{{ tool.getNestedProperty(scope.row, $attrs.nestProp2) }}</p>
@ -54,8 +57,8 @@ export default {
await this.$refs.saveDialog.open('view', { id: id })
},
//获取头像
getAvatar(scope) {
return scope.row.avatar ? scope.row.avatar : this.$CONFIG.DEFAULT_AVATAR
getAvatar(scope, prop) {
return scope.row.avatar ? scope.row.avatar : this.$CONFIG.DEFAULT_AVATAR(tool.getNestedProperty(scope.row, prop))
},
},
}

View File

@ -60,6 +60,7 @@
:layout="paginationLayout"
:page-size="scPageSize"
:page-sizes="pageSizes"
:pager-count="pagerCount"
:small="true"
:total="total"
@current-change="paginationChange"
@ -230,6 +231,7 @@ export default {
},
data() {
return {
pagerCount: 10,
current: {
row: null,
column: null,
@ -258,6 +260,7 @@ export default {
}
},
mounted() {
this.pagerCount = document.body.clientWidth < 1000 ? 3 : 10
//判断是否开启自定义列
if (this.column) {
this.getCustomColumn()
@ -309,7 +312,7 @@ export default {
} catch {
return
}
await this.vue.deleteRow(this.current.row)
await this.vue.rowDel(this.current.row)
return
}

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,7 @@
import MY_CONFIG from './myConfig'
import APP_CONFIG from './appConfig'
import avatar from '@/utils/avatar'
import tool from '@/utils/tool'
const DEFAULT_CONFIG = {
//标题
@ -74,8 +76,14 @@ const DEFAULT_CONFIG = {
},
//默认头像
DEFAULT_AVATAR:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAIAAAC0Ujn1AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAflJREFUeNqs1u1P2kAYAHB6B+21UmZbatEAki2+MKIR42TGaHRLjBr/Yj/7wWRb3If5gjHbfIlBjIqCUCq0PmaJLqG9IwdP+qV3vV+v1/Z5Ttg/Ke39tm+q7VD/wlBx/j0J7xzWbccL9TVgosCivrv/Atgw8yI9JuZGVXNQjCkROL2tOpc39q8/D7bj0gcy6NSQvDoTR4LwdidVhOPDyMD29/Jd9YkyFlH6pAhanjb+d19DFvHSlOHX0x09kYpGcOAFMPf0kMxJJ02ZvlyWRjhpWcJ0moiIk240Gf9Rw2lz0o92i04/Nnhpl/Hhhtqux0mnLcZrzCQUTrrV9lh/M++CFM9rtOXyvL+lOif98+T++r4Z1Ht0Vju9anDSEKXbQBqSFH0sgz6+qMGDd7Y/1J96pav1FuTPzvZvxYrrej3REOWK09l4XWkyB7Jp30RBRNwH2jf/MZMimzZi/kk5l1HFMOKnFYK/zMaDqsxqPo6QwENDtd2YtwakwOI5rJO1OZOS0/3p8VR0s2CpMqMoJzSytWAldKmrig7cQk4fMUiX+w14rPVPVvGi9qNYcVpuIP1xVM2PvaOU2sACnYwmTbJ7cHdefssqSJFeXkUYCysz8flJjcN9nf7XvFnIahi/gMCiwphMROFzVstYSu/7sWxaXczpAAL7LMAA6FWV/DBrhrIAAAAASUVORK5CYII=',
DEFAULT_AVATAR(name) {
return (
'data:image/svg+xml,' +
encodeURIComponent(
avatar.createSVG(`#${Math.abs(tool.crypto.hashCode(name)).toString(16).substring(0, 6)}`, name.slice(0, 1).toUpperCase()).outerHTML,
)
)
},
}
//合并业务配置

View File

@ -49,6 +49,7 @@ import naArea from '@/components/naArea/index.vue'
import naButtonAdd from '@/components/naButtonAdd/index.vue'
import naButtonBatchDel from '@/components/naButtonBatchDel/index.vue'
import naColAvatar from '@/components/naColAvatar'
import naColId from '@/components/naColId/index.vue'
import naColIndicator from '@/components/naColIndicator/index.vue'
import naColOperation from '@/components/naColOperation'
import naColTags from '@/components/naColTags/index.vue'
@ -89,6 +90,7 @@ export default {
app.component('naButtonAdd', naButtonAdd)
app.component('naButtonBatchDel', naButtonBatchDel)
app.component('naColAvatar', naColAvatar)
app.component('naColId', naColId)
app.component('naColIndicator', naColIndicator)
app.component('naColOperation', naColOperation)
app.component('naColTags', naColTags)

View File

@ -132,14 +132,13 @@ export default {
<style scoped>
.mobile-nav-button {
position: fixed;
bottom: 1rem;
left: 1rem;
top: 0;
left: 0;
z-index: 10;
width: 4rem;
height: 4rem;
background: #409eff;
box-shadow: 0 0.2rem 1rem 0 rgba(64, 158, 255, 1);
border-radius: 50%;
background: var(--el-color-primary);
box-shadow: 0 0.2rem 1rem 0 var(--el-color-primary);
display: flex;
align-items: center;
justify-content: center;

View File

@ -39,7 +39,20 @@
</div>
<el-dropdown @command="handleUser" class="user panel-item" trigger="click">
<div class="user-avatar">
<el-avatar :size="30" :src="user.avatar ? user.avatar : $CONFIG.DEFAULT_AVATAR"></el-avatar>
<el-avatar
:size="30"
:src="
user.avatar
? user.avatar
: 'data:image/svg+xml,' +
encodeURIComponent(
avatar.createSVG(
`#${Math.abs(this.$TOOL.crypto.hashCode(user.userName)).toString(16).substring(0, 6)}`,
user.userName.slice(0, 1).toUpperCase(),
).outerHTML,
)
"></el-avatar>
<label>{{ user.userName }}</label>
<el-icon class="el-icon--right">
<el-icon-arrow-down />
@ -68,7 +81,14 @@
import search from './search.vue'
import tasks from './tasks.vue'
import message from '@/views/profile/message/components/list.vue'
import avatar from '../../utils/avatar'
export default {
computed: {
avatar() {
return avatar
},
},
components: {
search,
tasks,

View File

@ -25,6 +25,10 @@
--el-menu-horizontal-height: 4rem;
}
.el-menu--inline {
--el-menu-active-color: var(--el-color-primary) !important;
}
.el-form-item--default {
--font-size: 1rem;
}
@ -205,18 +209,14 @@
border-left: 0;
}
.el-table.el-table--large {
.el-table * {
font-size: 0.9rem;
}
.el-table.el-table--large * {
font-size: 1rem;
}
.el-table.el-table--small {
font-size: 0.9rem;
}
.el-table {
font-size: 0.9rem;
}
.el-radio-button__inner {
font-size: 0.9rem;
}

View File

@ -47,10 +47,20 @@
padding: 0 0.4rem !important;
}
.el-pagination__jump {
display: none;
}
.el-pagination__total,
.el-pagination__jump,
.el-pagination__sizes {
display: none !important;
.el-select {
display: none;
}
}
.scTable-do {
display: none;
}
}

View File

@ -0,0 +1,42 @@
export default {
//生成svg矩形
createSVG(color, name) {
const svg = document.createElement('svg')
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
svg.setAttribute('width', 50)
svg.setAttribute('height', 50)
// <rect> background
const rect = document.createElement('rect')
rect.setAttribute('fill', color)
rect.setAttribute('x', 0)
rect.setAttribute('y', 0)
rect.setAttribute('width', '100%')
rect.setAttribute('height', '100%')
svg.appendChild(rect)
// <text> name
const text = document.createElement('text')
text.setAttribute('fill', '#fff')
text.setAttribute('x', '50%')
text.setAttribute('y', '50%')
text.setAttribute('text-anchor', 'middle')
text.setAttribute('font-size', '16')
text.setAttribute('font-weight', '900')
text.setAttribute('font-family', 'monospace')
// IE/Edge don't support alignment-baseline
// @see https://msdn.microsoft.com/en-us/library/gg558060(v=vs.85).aspx
if (document.documentMode || /Edge/.test(navigator.userAgent)) {
text.setAttribute('dy', '0.35em')
} else {
text.setAttribute('alignment-baseline', 'middle')
}
text.textContent = name
svg.appendChild(text)
return svg
},
}

View File

@ -15,7 +15,9 @@
</el-col>
</el-form-item>
<el-form-item>
<el-button :loading="isLoading" @click="login" round style="width: 100%" type="primary">{{ $t('登录') }}</el-button>
<el-button :loading="isLoading" @click="login" round style="width: 100%" type="primary"
>{{ starred ? $t('登录') : $t('Star 后可登录') }}
</el-button>
</el-form-item>
<div class="login-reg">
{{ $t('还没有账号?') }}
@ -28,6 +30,7 @@
export default {
data() {
return {
starred: false,
autoLogin: false,
form: {
account: 'root',
@ -50,6 +53,12 @@ export default {
},
methods: {
async login() {
if (!this.starred) {
window.open('https://github.com/nsnail/NetAdmin')
this.starred = true
return
}
const validate = await this.$refs.loginForm.validate().catch(() => {})
if (!validate) {
return false

View File

@ -4,6 +4,7 @@
<img alt="" src="@/assets/img/logo.png" />
<h2>{{ packageJson.name }}</h2>
<p>{{ ver }}</p>
<el-link href="https://github.com/nsnail/NetAdmin" target="_blank">喜欢就点个 Star </el-link>
</div>
</el-card>
</template>

View File

@ -4,7 +4,7 @@
<el-container>
<el-header>
<div class="user-info-top">
<el-avatar :size="70" :src="user.avatar ? user.avatar : $CONFIG.DEFAULT_AVATAR"></el-avatar>
<el-avatar :size="70" :src="user.avatar ? user.avatar : $CONFIG.DEFAULT_AVATAR(user.userName)"></el-avatar>
<h2>{{ user.userName }}</h2>
<p>
<el-tag v-for="(item, i) in user.roles" :key="i" effect="dark" round size="large">{{ item.name }}</el-tag>

View File

@ -1,5 +1,7 @@
<template>
<el-skeleton v-if="loading" :rows="5" animated />
<div v-if="loading" style="padding: 1rem">
<el-skeleton :rows="5" animated />
</div>
<template v-else>
<el-container v-if="msgList.length > 0" class="nopadding">
<el-header style="border: none">
@ -30,9 +32,12 @@
<div class="msg-title">
<div>
<el-badge v-if="msg.myFlags.userSiteMsgStatus === 0" is-dot type="primary">
<el-avatar :size="40" :src="msg.sender.avatar ?? $CONFIG.DEFAULT_AVATAR"></el-avatar>
<el-avatar :size="40" :src="msg.sender.avatar ?? $CONFIG.DEFAULT_AVATAR(msg.sender.userName)"></el-avatar>
</el-badge>
<el-avatar v-else :size="40" :src="msg.sender.avatar ?? $CONFIG.DEFAULT_AVATAR"></el-avatar>
<el-avatar
v-else
:size="40"
:src="msg.sender.avatar ?? $CONFIG.DEFAULT_AVATAR(msg.sender.userName)"></el-avatar>
</div>
<div>
<h2>{{ msg.title }}</h2>

View File

@ -39,23 +39,22 @@
row-key="id"
stripe>
<el-table-column align="center" type="selection"></el-table-column>
<el-table-column :label="$t('配置编号')" align="center" prop="id"></el-table-column>
<el-table-column :label="$t('配置编号')" align="center" prop="id" width="170"></el-table-column>
<el-table-column :label="$t('用户注册')" align="center">
<el-table-column :label="$t('默认部门')" align="center" prop="userRegisterDept.name"></el-table-column>
<el-table-column :label="$t('默认角色')" align="center" prop="userRegisterRole.name"></el-table-column>
<el-table-column :label="$t('人工审核')" align="center" prop="userRegisterConfirm">
<el-table-column :label="$t('默认部门')" align="center" prop="userRegisterDept.name" width="150"></el-table-column>
<el-table-column :label="$t('默认角色')" align="center" prop="userRegisterRole.name" width="150"></el-table-column>
<el-table-column :label="$t('人工审核')" align="center" prop="userRegisterConfirm" width="100">
<template #default="scope">
<el-switch v-model="scope.row.userRegisterConfirm" @change="changeSwitch($event, scope.row)"></el-switch>
</template>
</el-table-column>
</el-table-column>
<el-table-column :label="$t('启用')" align="center" prop="enabled">
<el-table-column :label="$t('启用')" align="center" prop="enabled" width="100">
<template #default="scope">
<el-switch v-model="scope.row.enabled" @change="changeSwitch($event, scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column :label="$t('创建时间')" align="center" prop="createdTime"></el-table-column>
<el-table-column :label="$t('创建时间')" align="center" prop="createdTime" width="170"></el-table-column>
<na-col-operation
:buttons="
naColOperation.buttons.concat({
@ -66,7 +65,8 @@
type: 'danger',
})
"
:vue="this" />
:vue="this"
width="170" />
</sc-table>
</el-main>
</el-container>

View File

@ -109,20 +109,33 @@
"
prop="httpMethod"
width="100" />
<el-table-column :label="$t('上次执行时间')" align="right" prop="lastExecTime" sortable="custom" width="120">
<template #default="scope">
<span v-if="scope.row.lastExecTime" v-time.tip="scope.row.lastExecTime"></span>
</template>
<el-table-column :label="$t('上次执行')" align="center">
<el-table-column :label="$t('状态')" align="center" prop="lastExecTime" sortable="custom" width="150">
<template #default="scope">
<div class="indicator">
<sc-status-indicator :type="scope.row.lastStatusCode === 'ok' ? 'success' : 'danger'" />
<span>{{
this.$GLOBAL.enums.httpStatusCodes[scope.row.lastStatusCode]
? this.$GLOBAL.enums.httpStatusCodes[scope.row.lastStatusCode][1]
: scope.row.lastStatusCode
}}</span>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('时间')" align="right" prop="lastExecTime" sortable="custom" width="100">
<template #default="scope">
<span v-if="scope.row.lastExecTime" v-time.tip="scope.row.lastExecTime"></span>
</template>
</el-table-column>
<el-table-column
:formatter="(row) => (row.lastDuration ? `${tool.groupSeparator(row.lastDuration.toFixed(0))} ms` : `-`)"
:label="$t('耗时')"
align="right"
prop="lastDuration"
sortable="custom"
width="100">
</el-table-column>
</el-table-column>
<na-col-indicator
:label="$t('上次执行状态')"
:options="
Object.entries(this.$GLOBAL.enums.httpStatusCodes).map((x) => {
return { value: x[0], text: x[1][1], type: x[0] === 'ok' ? 'success' : null }
})
"
prop="lastStatusCode"
width="120" />
<el-table-column :label="$t('下次执行时间')" align="right" prop="nextExecTime" sortable="custom" width="170" />
<el-table-column :label="$t('启用')" align="center" prop="enabled" sortable="custom" width="80">
@ -137,15 +150,22 @@
</el-table-column>
<na-col-operation
:buttons="
naColOperation.buttons.concat({
icon: 'el-icon-delete',
confirm: true,
type: 'danger',
title: $t('删除作业'),
click: rowDel,
})
naColOperation.buttons.concat(
{
icon: 'el-icon-video-play',
click: execute,
},
{
icon: 'el-icon-delete',
confirm: true,
type: 'danger',
title: $t('删除作业'),
click: rowDel,
},
)
"
:vue="this" />
:vue="this"
width="180" />
</sc-table>
</el-main>
</el-container>
@ -162,6 +182,7 @@ import saveDialog from './save'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
import ScSelectFilter from '@/components/scSelectFilter/index.vue'
import tool from '@/utils/tool'
export default {
components: {
@ -171,6 +192,7 @@ export default {
inject: ['reload'],
data() {
return {
timer: null,
loading: false,
query: {
dynamicFilter: {
@ -192,6 +214,9 @@ export default {
},
watch: {},
computed: {
tool() {
return tool
},
naColOperation() {
return naColOperation
},
@ -220,6 +245,31 @@ export default {
}
this.$refs.table.refresh()
},
async execute(row) {
try {
await this.$API.sys_job.execute.post({ id: row.id })
this.$refs.table.refresh()
this.$notify.success({
dangerouslyUseHTMLString: true,
message: `<div id="countdown">已发起执行请求5 秒后弹出执行结果</div>`,
onClose: async () => {
clearInterval(this.timer)
this.loading = true
this.dialog.save = true
await this.$nextTick()
await this.$refs.saveDialog.open('view', row, 1)
this.loading = false
},
})
this.timer = setInterval(() => {
const countdown = new RegExp('\\d+').exec(document.getElementById('countdown').innerText)[0]
document.getElementById('countdown').innerText = document
.getElementById('countdown')
.innerText.replace(countdown, `${parseInt(countdown) - 1}`)
}, 1000)
} catch {}
},
//删除
async rowDel(row) {
try {

View File

@ -57,11 +57,36 @@
row-key="id"
stripe>
<el-table-column :label="$t('唯一编码')" prop="id" sortable="custom" width="150" />
<el-table-column :label="$t('执行耗时(毫秒)')" align="right" prop="duration" sortable="custom" width="150" />
<el-table-column :label="$t('请求方法')" prop="httpMethod" sortable="custom" width="100" />
<el-table-column :label="$t('HTTP 状态码')" align="right" prop="httpStatusCode" sortable="custom" width="150" />
<el-table-column
:formatter="(row) => `${tool.groupSeparator(row.duration.toFixed(0))} ms`"
:label="$t('执行耗时')"
align="right"
prop="duration"
sortable="custom"
width="150" />
<na-col-indicator
:label="$t('请求方式')"
:options="
Object.entries(this.$GLOBAL.enums.httpMethods).map((x) => {
return { value: x[0], text: x[1][1] }
})
"
prop="httpMethod"
width="100" />
<el-table-column :label="$t('响应状态码')" align="center" prop="httpStatusCode" sortable="custom" width="200">
<template #default="scope">
<div class="indicator">
<sc-status-indicator :type="scope.row.httpStatusCode === 'ok' ? 'success' : 'danger'" />
<span>{{
this.$GLOBAL.enums.httpStatusCodes[scope.row.httpStatusCode]
? this.$GLOBAL.enums.httpStatusCodes[scope.row.httpStatusCode][1]
: scope.row.httpStatusCode
}}</span>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('请求的网络地址')" prop="requestUrl" sortable="custom" />
<el-table-column :label="$t('创建时间')" prop="createdTime" sortable="custom" width="170" />
<el-table-column :label="$t('创建时间')" align="right" prop="createdTime" sortable="custom" width="170" />
<na-col-operation :buttons="[naColOperation.buttons[0]]" :vue="this" width="100" />
</sc-table>
</el-main>
@ -78,6 +103,7 @@
import saveDialog from './save'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
import tool from '@/utils/tool'
export default {
props: ['keywords'],
@ -101,6 +127,9 @@ export default {
},
watch: {},
computed: {
tool() {
return tool
},
naColOperation() {
return naColOperation
},

View File

@ -13,7 +13,7 @@
<el-form-item :label="$t('唯一编码')" prop="id"><el-input v-model="form.id" clearable /></el-form-item
><el-form-item :label="$t('执行耗时(毫秒)')" prop="duration"><el-input v-model="form.duration" clearable /></el-form-item
><el-form-item :label="$t('请求方法')" prop="httpMethod"><el-input v-model="form.httpMethod" clearable /></el-form-item
><el-form-item :label="$t('HTTP 状态码')" prop="httpStatusCode"><el-input v-model="form.httpStatusCode" clearable /></el-form-item
><el-form-item :label="$t('响应状态码')" prop="httpStatusCode"><el-input v-model="form.httpStatusCode" clearable /></el-form-item
><el-form-item :label="$t('作业编号')" prop="jobId"><el-input v-model="form.jobId" clearable /></el-form-item
><el-form-item :label="$t('请求体')" prop="requestBody"
><el-input v-model="form.requestBody" clearable rows="5" type="textarea" /></el-form-item

View File

@ -27,6 +27,9 @@
<el-form-item v-if="mode === 'view'" :label="$t('上次执行时间')" prop="lastExecTime">
<el-input v-model="form.lastExecTime" clearable />
</el-form-item>
<el-form-item v-if="mode === 'view'" :label="$t('上次执行耗时(毫秒)')" prop="lastExecTime">
<el-input v-model="form.lastDuration" clearable />
</el-form-item>
<el-form-item v-if="mode === 'view'" :label="$t('下次执行时间')" prop="nextExecTime">
<el-input v-model="form.nextExecTime" clearable />
</el-form-item>
@ -189,7 +192,7 @@ export default {
mounted() {},
methods: {
//显示
async open(mode = 'add', data) {
async open(mode = 'add', data, tabIndex = 0) {
this.visible = true
this.loading = true
this.mode = mode
@ -198,6 +201,7 @@ export default {
Object.assign(this.form, res.data)
}
this.loading = false
this.tabIndex = tabIndex
return this
},