添加写优先选项,默认false

This commit is contained in:
2248356998 qq.com
2025-08-09 13:07:08 +08:00
parent 6660ce3e34
commit 280366e1b2
63 changed files with 745 additions and 335 deletions

View File

@@ -123,8 +123,8 @@ public class BlazorAppContext
}
}
var ownMenus = OwnMenus.Where(a => a.Module == CurrentModuleId);
OwnMenuItems = ResourceUtil.BuildMenuTrees(ownMenus).ToList();
AllOwnMenuItems = ResourceUtil.BuildMenuTrees(OwnMenus).ToList();
OwnMenuItems = AdminResourceUtil.BuildMenuTrees(ownMenus).ToList();
AllOwnMenuItems = AdminResourceUtil.BuildMenuTrees(OwnMenus).ToList();
OwnSameLevelMenuItems = ownMenus.Where(a => !a.Href.IsNullOrWhiteSpace()).Select(item => new MenuItem()
{
Match = item.NavLinkMatch ?? Microsoft.AspNetCore.Components.Routing.NavLinkMatch.All,

View File

@@ -29,7 +29,7 @@ public partial class EditPagePolicy
protected override Task OnParametersSetAsync()
{
ShortcutsTreeViewItems = ResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
ShortcutsTreeViewItems = AdminResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
return base.OnParametersSetAsync();
}
@@ -48,6 +48,6 @@ public partial class EditPagePolicy
{
await Task.CompletedTask;
ShortcutsSearchText = searchText;
return ResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
return AdminResourceUtil.BuildTreeItemList(AppContext.OwnMenus.WhereIf(!ShortcutsSearchText.IsNullOrEmpty(), a => a.Title.Contains(ShortcutsSearchText)), Model.Shortcuts, null);
}
}

View File

@@ -41,7 +41,7 @@ public partial class MenuChoiceDialog
var all = (await SysResourceService.GetAllAsync());
var items = all.Where(a => a.Category == ResourceCategoryEnum.Menu && a.Module == ModuleId);
ModuleTitle = all.FirstOrDefault(a => a.Id == ModuleId)?.Title;
Items = ResourceUtil.BuildTreeItemList(items, new List<long> { Value }, RenderTreeItem);
Items = AdminResourceUtil.BuildTreeItemList(items, new List<long> { Value }, RenderTreeItem);
await base.OnParametersSetAsync();
}

View File

@@ -39,8 +39,8 @@ public partial class SysResourcePage
protected override async Task OnParametersSetAsync()
{
ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
MenuItems = ResourceUtil.BuildMenuSelectList((await SysResourceService.GetAllAsync())).Concat(new List<SelectedItem>() { new("0", AdminLocalizer["Root"]) }).ToList();
ModuleSelectedItems = AdminResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
MenuItems = AdminResourceUtil.BuildMenuSelectList((await SysResourceService.GetAllAsync())).Concat(new List<SelectedItem>() { new("0", AdminLocalizer["Root"]) }).ToList();
await base.OnParametersSetAsync();
}
@@ -49,7 +49,7 @@ public partial class SysResourcePage
private async Task<QueryData<SysResource>> OnQueryAsync(QueryPageOptions options)
{
MenuTreeItems = new List<TreeViewItem<SysResource>>() { new TreeViewItem<SysResource>(new SysResource()) { Text = AdminLocalizer["Root"] } }.Concat(ResourceUtil.BuildTreeItemList((await SysResourceService.GetAllAsync()).Where(a => a.Module == CustomerSearchModel.Module), new(), null)).ToList();
MenuTreeItems = new List<TreeViewItem<SysResource>>() { new TreeViewItem<SysResource>(new SysResource()) { Text = AdminLocalizer["Root"] } }.Concat(AdminResourceUtil.BuildTreeItemList((await SysResourceService.GetAllAsync()).Where(a => a.Module == CustomerSearchModel.Module), new(), null)).ToList();
var data = await SysResourceService.PageAsync(options, CustomerSearchModel);
return data;
@@ -136,14 +136,14 @@ public partial class SysResourcePage
private async Task<IEnumerable<TableTreeNode<SysResource>>> OnTreeExpand(SysResource menu)
{
var sysResources = await SysResourceService.GetAllAsync();
var result = ResourceUtil.BuildTableTrees(sysResources, menu.Id);
var result = AdminResourceUtil.BuildTableTrees(sysResources, menu.Id);
return result;
}
private static async Task<IEnumerable<TableTreeNode<SysResource>>> TreeNodeConverter(IEnumerable<SysResource> items)
{
await Task.CompletedTask;
var result = ResourceUtil.BuildTableTrees(items, 0);
var result = AdminResourceUtil.BuildTableTrees(items, 0);
return result;
}

View File

@@ -35,7 +35,7 @@ public partial class GrantResourceDialog
{
var items = (await SysResourceService.GetAllAsync()).Where(a => a.Category != ResourceCategoryEnum.Module).OrderBy(a => a.Module).ThenBy(a => a.Id).ToList();
Items = ResourceUtil.BuildTreeItemList(items, Value, RenderTreeItem);
Items = AdminResourceUtil.BuildTreeItemList(items, Value, RenderTreeItem);
ModuleList = (await SysResourceService.GetAllAsync()).Where(a => a.Category == ResourceCategoryEnum.Module).ToList();
await base.OnInitializedAsync();
}

View File

@@ -35,7 +35,7 @@ public partial class SysUserEdit
BoolItems = LocalizerUtil.GetBoolItems(Model.GetType(), nameof(Model.Status));
var items = await SysPositionService.SelectorAsync(new PositionSelectorInput());
Items = PositionUtil.BuildCascaderItemList(items);
ModuleSelectedItems = ResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
ModuleSelectedItems = AdminResourceUtil.BuildModuleSelectList((await SysResourceService.GetAllAsync())).ToList();
await InvokeAsync(StateHasChanged);
await base.OnInitializedAsync();
}

View File

@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Razor;
/// <inheritdoc/>
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class ResourceUtil
public static class AdminResourceUtil
{
/// <summary>
/// 构造选择项ID/TITLE

View File

@@ -53,6 +53,8 @@ public static class QueryPageOptionsExtensions
return datas;
}
public static IEnumerable<T> GetQuery<T>(this IEnumerable<T> query, QueryPageOptions option, Func<IEnumerable<T>, IEnumerable<T>>? queryFunc = null, FilterKeyValueAction where = null)
{
if (queryFunc != null)
@@ -135,4 +137,24 @@ public static class QueryPageOptionsExtensions
ret.Items = items.ToList();
return ret;
}
/// <summary>
/// 根据查询条件返回QueryData
/// </summary>
public static QueryData<SelectedItem> GetQueryData<T>(this IEnumerable<T> datas, VirtualizeQueryOption option, Func<IEnumerable<T>, IEnumerable<SelectedItem>> func, FilterKeyValueAction where = null)
{
var ret = new QueryData<SelectedItem>()
{
IsSorted = false,
IsFiltered = false,
IsAdvanceSearch = false,
IsSearch = !option.SearchText.IsNullOrWhiteSpace()
};
var items = datas.Skip((option.StartIndex)).Take(option.Count);
ret.TotalCount = datas.Count();
ret.Items = func(items).ToList();
return ret;
}
}

View File

@@ -5,7 +5,7 @@
<Step @ref="@step" IsVertical="true">
<StepItem Text=@Localizer["First"] Title=@Localizer["Upload"]>
<InputUpload ShowDeleteButton="true" @bind-Value=_importFile Accept=".xlsx"></InputUpload>
<Button class="mt-2" IsAsync OnClick="() => DeviceImport(_importFile)">@Localizer["Validate"]</Button>
<PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
</StepItem>
<StepItem Text=@Localizer["Second"] Title=@Localizer["ValidateText"]>
@@ -41,16 +41,12 @@
}
<PopConfirmButton IsAsync IsDisabled=@_importPreviews.Any(it => it.Value.HasError) Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
<Button class="mt-2" IsAsync OnClick="() => DeviceImport()">@RazorLocalizer["Close"]</Button>
@*
<Button IsAsync class="mt-2" IsDisabled=@_importPreviews.Any(it => it.Value.HasError) OnClick="() => step.Next()">@Localizer["Next"]</Button> *@
</div>
</StepItem>
@* <StepItem Text=@Localizer["Third"] Title=@Localizer["Import"]>
<PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
</StepItem> *@
</Step>
@code {
[NotNull]

View File

@@ -24,18 +24,17 @@ public partial class ImportExcel
/// </summary>
[Parameter]
[EditorRequired]
public Func<Dictionary<string, ImportPreviewOutputBase>, Task> Import { get; set; }
public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Import { get; set; }
[Inject]
[NotNull]
private IStringLocalizer<ImportExcel>? Localizer { get; set; }
/// <summary>
/// 预览
/// </summary>
[Parameter]
[EditorRequired]
public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Preview { get; set; }
[Inject]
[NotNull]
private IStringLocalizer<ThingsGateway.Razor._Imports>? RazorLocalizer { get; set; }
[Inject]
[NotNull]
@@ -47,13 +46,17 @@ public partial class ImportExcel
[CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; }
private async Task DeviceImport(IBrowserFile file)
private async Task DeviceImport()
{
try
{
_importPreviews.Clear();
_importPreviews = await Preview.Invoke(file);
await step.Next();
await InvokeAsync(async () =>
{
if (OnCloseAsync != null)
await OnCloseAsync();
await ToastService.Default();
});
}
catch (Exception ex)
{
@@ -67,16 +70,12 @@ public partial class ImportExcel
{
await Task.Run(async () =>
{
await Import.Invoke(_importPreviews);
_importPreviews = await Import.Invoke(_importFile);
_importFile = null;
await InvokeAsync(async () =>
{
if (OnCloseAsync != null)
await OnCloseAsync();
await ToastService.Default();
});
});
await step.Next();
}
catch (Exception ex)
{

View File

@@ -0,0 +1,58 @@
@using ThingsGateway.Extension
@namespace ThingsGateway.Razor
<Button OnClick="() => step.Reset()">@Localizer["Reset"]</Button>
<h6 class="my-3 green--text">@Localizer["Tip"] </h6>
<Step @ref="@step" IsVertical="true">
<StepItem Text=@Localizer["First"] Title=@Localizer["Upload"]>
<InputUpload ShowDeleteButton="true" @bind-Value=_importFile Accept=".xlsx"></InputUpload>
<Button class="mt-2" IsAsync OnClick="() => DeviceImport(_importFile)">@Localizer["Validate"]</Button>
</StepItem>
<StepItem Text=@Localizer["Second"] Title=@Localizer["ValidateText"]>
<div class="overflow-y-auto">
@foreach (var item in _importPreviews)
{
<div class="mt-2">
@(
Localizer["UploadCount", item.Key, item.Value.DataCount]
)
</div>
<div class=@((item.Value.HasError ? "my-2 red--text" : "my-2 green--text"))>
@(
(item.Value.HasError ? "Error" : "Success")
)
</div>
if (item.Value.HasError)
{
<div style="height:300px;" class="overflow-y-scroll">
<Virtualize Items="item.Value.Results.Where(a => !a.Success).OrderBy(a => a.Row).ToList()" Context="item1" ItemSize="60" OverscanCount=2>
<ItemContent>
<div class="row g-0">
<span class="col mx-2">@item1.Row</span>
<span class=@((item1.Success ? "green--text col-auto" : "red--text col-auto"))>
<strong>@item1.ErrorMessage</strong>
</span>
</div>
</ItemContent>
</Virtualize>
</div>
}
}
<PopConfirmButton IsAsync IsDisabled=@_importPreviews.Any(it => it.Value.HasError) Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
@*
<Button IsAsync class="mt-2" IsDisabled=@_importPreviews.Any(it => it.Value.HasError) OnClick="() => step.Next()">@Localizer["Next"]</Button> *@
</div>
</StepItem>
@* <StepItem Text=@Localizer["Third"] Title=@Localizer["Import"]>
<PopConfirmButton IsAsync Color=Color.Warning class="mt-2" OnConfirm=@(SaveDeviceImport)>@Localizer["Import"]</PopConfirmButton>
</StepItem> *@
</Step>
@code {
[NotNull]
Step? step { get; set; }
}

View File

@@ -0,0 +1,86 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using Microsoft.AspNetCore.Components.Forms;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Razor;
/// <inheritdoc/>
public partial class ImportExcelConfirm
{
private Dictionary<string, ImportPreviewOutputBase> _importPreviews = new();
/// <summary>
/// 导入
/// </summary>
[Parameter]
[EditorRequired]
public Func<Dictionary<string, ImportPreviewOutputBase>, Task> Import { get; set; }
[Inject]
[NotNull]
private IStringLocalizer<ImportExcel>? Localizer { get; set; }
/// <summary>
/// 预览
/// </summary>
[Parameter]
[EditorRequired]
public Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> Preview { get; set; }
[Inject]
[NotNull]
private ToastService? ToastService { get; set; }
[Required]
private IBrowserFile _importFile { get; set; }
[CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; }
private async Task DeviceImport(IBrowserFile file)
{
try
{
_importPreviews.Clear();
_importPreviews = await Preview.Invoke(file);
await step.Next();
}
catch (Exception ex)
{
await ToastService.Warn(ex);
}
}
private async Task SaveDeviceImport()
{
try
{
await Task.Run(async () =>
{
await Import.Invoke(_importPreviews);
_importFile = null;
await InvokeAsync(async () =>
{
if (OnCloseAsync != null)
await OnCloseAsync();
await ToastService.Default();
});
});
}
catch (Exception ex)
{
await InvokeAsync(async () => await ToastService.Warn(ex));
}
}
}

View File

@@ -0,0 +1,9 @@
::deep .avatar {
border-radius: 1.5rem;
width: 24px;
height: 24px;
background-color: var(--bs-red);
color: #fff;
flex: 0 0 auto;
font-size: 1rem;
}

View File

@@ -60,14 +60,14 @@
},
"ThingsGateway.Razor.ImportExcel": {
"First": "Step 1",
"Import": "Import",
"Import": "If there are no errors during verification, it will be directly imported into the database",
"Next": "Next",
"Reset": "Reset",
"Second": "Step 2",
"Third": "Step 3",
"Tip": "When the data volume is large (more than 200,000), the import may take more than 1 minute, please be patient",
"Upload": "Upload File",
"UploadCount": " Table {0}, expecting to import {1} records",
"UploadCount": " Table {0}, import {1} records",
"Validate": "Validate",
"ValidateText": "Validation Content"
},

View File

@@ -60,14 +60,14 @@
},
"ThingsGateway.Razor.ImportExcel": {
"First": "第一步",
"Import": "导入",
"Import": "若验证无错误,将直接导入数据库",
"Next": "下一步",
"Reset": "重置",
"Second": "第二步",
"Third": "第三",
"Tip": "数据量较大时(大于20万)所需导入时间可能超过1分钟请耐心等待",
"Upload": "上传文件",
"UploadCount": " 表 {0}预计导入 {1} 条数据",
"UploadCount": " 表 {0},导入 {1} 条数据",
"Validate": "验证",
"ValidateText": "验证内容"
},

View File

@@ -1,9 +1,9 @@
<Project>
<PropertyGroup>
<PluginVersion>10.10.12</PluginVersion>
<ProPluginVersion>10.10.12</ProPluginVersion>
<DefaultVersion>10.10.15</DefaultVersion>
<PluginVersion>10.10.16</PluginVersion>
<ProPluginVersion>10.10.16</ProPluginVersion>
<DefaultVersion>10.10.16</DefaultVersion>
<AuthenticationVersion>10.10.1</AuthenticationVersion>
<SourceGeneratorVersion>10.10.1</SourceGeneratorVersion>
<NET8Version>8.0.19</NET8Version>

View File

@@ -12,13 +12,15 @@ using TouchSocket.Core;
namespace ThingsGateway.Gateway.Application;
public class AsyncReadWriteLock
public class AsyncReadWriteLock : IAsyncDisposable
{
private readonly int _writeReadRatio = 3; // 写3次会允许1次读但写入也不会被阻止具体协议取决于插件协议实现
public AsyncReadWriteLock(int writeReadRatio)
public AsyncReadWriteLock(int writeReadRatio, bool writePriority)
{
_writeReadRatio = writeReadRatio;
_writePriority = writePriority;
}
private bool _writePriority;
private AsyncAutoResetEvent _readerLock = new AsyncAutoResetEvent(false); // 控制读计数
private long _writerCount = 0; // 当前活跃的写线程数
private long _readerCount = 0; // 当前被阻塞的读线程数
@@ -54,10 +56,13 @@ public class AsyncReadWriteLock
if (Interlocked.Increment(ref _writerCount) == 1)
{
var cancellationTokenSource = _cancellationTokenSource;
_cancellationTokenSource = new();
await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取
cancellationTokenSource.SafeDispose();
if (_writePriority)
{
var cancellationTokenSource = _cancellationTokenSource;
_cancellationTokenSource = new();
await cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false); // 取消读取
cancellationTokenSource.SafeDispose();
}
}
return new Writer(this);
@@ -102,6 +107,16 @@ public class AsyncReadWriteLock
}
}
public async ValueTask DisposeAsync()
{
if (_cancellationTokenSource != null)
{
await _cancellationTokenSource.SafeCancelAsync().ConfigureAwait(false);
_cancellationTokenSource.SafeDispose();
}
_readerLock.SetAll();
}
private int _writeSinceLastReadCount = 0;
private struct Writer : IDisposable
{

View File

@@ -12,7 +12,7 @@ using BootstrapBlazor.Components;
namespace ThingsGateway.Gateway.Application;
public class ExportFilter
public class GatewayExportFilter
{
public FilterKeyValueAction FilterKeyValueAction { get; set; }
public string? PluginName { get; set; }

View File

@@ -10,7 +10,7 @@
namespace ThingsGateway.Gateway.Application;
public static class ExportString
public static class GatewayExportString
{
/// <summary>
/// 通道名称
@@ -47,7 +47,7 @@ public static class ExportString
get
{
if (localizer == null)
localizer = App.CreateLocalizerByType(typeof(ExportString));
localizer = App.CreateLocalizerByType(typeof(GatewayExportString));
return localizer;
}
}

View File

@@ -45,7 +45,7 @@ public class GatewayExportController : ControllerBase
/// </summary>
/// <returns></returns>
[HttpPost("device")]
public async Task<IActionResult> DownloadDeviceAsync([FromBody] ExportFilter input)
public async Task<IActionResult> DownloadDeviceAsync([FromBody] GatewayExportFilter input)
{
input.QueryPageOptions.IsPage = false;
input.QueryPageOptions.IsVirtualScroll = false;
@@ -58,7 +58,7 @@ public class GatewayExportController : ControllerBase
/// </summary>
/// <returns></returns>
[HttpPost("channel")]
public async Task<IActionResult> DownloadChannelAsync([FromBody] ExportFilter input)
public async Task<IActionResult> DownloadChannelAsync([FromBody] GatewayExportFilter input)
{
input.QueryPageOptions.IsPage = false;
input.QueryPageOptions.IsVirtualScroll = false;
@@ -72,7 +72,7 @@ public class GatewayExportController : ControllerBase
/// </summary>
/// <returns></returns>
[HttpPost("variable")]
public async Task<IActionResult> DownloadVariableAsync([FromBody] ExportFilter input)
public async Task<IActionResult> DownloadVariableAsync([FromBody] GatewayExportFilter input)
{
input.QueryPageOptions.IsPage = false;
input.QueryPageOptions.IsVirtualScroll = false;

View File

@@ -195,7 +195,7 @@ public abstract partial class CollectBase : DriverBase, IRpcDriver
// 从插件服务中获取当前设备关联的驱动方法信息列表
DriverMethodInfos = GlobalData.PluginService.GetDriverMethodInfos(device.PluginName, this);
ReadWriteLock = new(CollectProperties.DutyCycle);
ReadWriteLock = new(CollectProperties.DutyCycle, CollectProperties.WritePriority);
}
public virtual string GetAddressDescription()
@@ -723,9 +723,11 @@ public abstract partial class CollectBase : DriverBase, IRpcDriver
#endregion
protected override Task DisposeAsync(bool disposing)
protected override async Task DisposeAsync(bool disposing)
{
_linkedCtsCache?.SafeDispose();
return base.DisposeAsync(disposing);
if (ReadWriteLock != null)
await ReadWriteLock.SafeDisposeAsync().ConfigureAwait(false);
await base.DisposeAsync(disposing).ConfigureAwait(false);
}
}

View File

@@ -37,6 +37,12 @@ public abstract class CollectPropertyBase : DriverPropertyBase
/// </summary>
[MinValue(1)]
public virtual int DutyCycle { get; set; } = 3;
/// <summary>
/// 写优先,写入时强制读取消操作
/// </summary>
public virtual bool WritePriority { get; set; } = false;
}
/// <summary>
@@ -56,4 +62,6 @@ public abstract class CollectPropertyRetryBase : CollectPropertyBase
[MinValue(1)]
public override int DutyCycle { get; set; } = 3;
[DynamicProperty(Remark = "写优先,写入时强制读取消操作")]
public override bool WritePriority { get; set; } = false;
}

View File

@@ -17,7 +17,6 @@ using TouchSocket.Core;
using TouchSocket.Sockets;
namespace ThingsGateway.Gateway.Application;
#pragma warning disable CS0649
/// <summary>
/// 通道表

View File

@@ -17,7 +17,6 @@ using System.ComponentModel.DataAnnotations;
using ThingsGateway.NewLife.Extension;
namespace ThingsGateway.Gateway.Application;
#pragma warning disable CS0649
/// <summary>
/// 设备表
@@ -172,6 +171,7 @@ public class Device : BaseDataEntity, IValidatableObject
#endregion
#if !Management
/// <summary>
/// 导入验证专用
/// </summary>
@@ -186,6 +186,8 @@ public class Device : BaseDataEntity, IValidatableObject
[Newtonsoft.Json.JsonIgnore]
[MapperIgnore]
public ModelValueValidateForm? ModelValueValidateForm;
#endif
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (RedundantEnable && RedundantDeviceId == null)

View File

@@ -16,7 +16,6 @@ using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
namespace ThingsGateway.Gateway.Application;
#pragma warning disable CS0649
/// <summary>
/// 设备变量表

View File

@@ -1,5 +1,5 @@
{
"ThingsGateway.Management.Application.ExportString": {
"ThingsGateway.Management.Application.ManagementExportString": {
"ManagementConfigName": "ManagementConfigName"
},
@@ -321,7 +321,8 @@
},
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
"RetryCount": "RetryCount",
"DutyCycle": "DutyCycle"
"DutyCycle": "DutyCycle",
"WritePriority": "WritePriority"
},
"ThingsGateway.Gateway.Application.ControlController": {
"BatchSaveChannelAsync": "BatchSaveChannel",
@@ -396,7 +397,7 @@
"ExpireTime": "ExpireTime {0}",
"Unauthorized": "Unauthorized"
},
"ThingsGateway.Gateway.Application.ExportString": {
"ThingsGateway.Gateway.Application.GatewayExportString": {
"BusinessDeviceName": "BusinessDevice",
"ChannelName": "Channel",
"DeviceName": "Device",

View File

@@ -1,5 +1,5 @@
{
"ThingsGateway.Management.Application.ExportString": {
"ThingsGateway.Management.Application.ManagementExportString": {
"ManagementConfigName": "通讯配置"
},
@@ -323,7 +323,8 @@
},
"ThingsGateway.Gateway.Application.CollectPropertyRetryBase": {
"RetryCount": "失败重试次数",
"DutyCycle": "占空比"
"DutyCycle": "占空比",
"WritePriority": "写优先"
},
"ThingsGateway.Gateway.Application.ControlController": {
"BatchSaveChannelAsync": "保存通道",
@@ -400,7 +401,7 @@
"ExpireTime": "过期时间 {0}",
"Unauthorized": "未授权"
},
"ThingsGateway.Gateway.Application.ExportString": {
"ThingsGateway.Gateway.Application.GatewayExportString": {
"BusinessDeviceName": "业务设备",
"ChannelName": "通道",
"DeviceName": "设备",

View File

@@ -8,7 +8,8 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Razor;
namespace ThingsGateway.Gateway.Application;
public enum ChannelDevicePluginTypeEnum
{
PluginType,

View File

@@ -222,7 +222,9 @@ public partial class VariableRuntime : Variable
private DateTime collectTime = DateTime.UnixEpoch.ToLocalTime();
private bool _isOnline;
#pragma warning disable CS0414
private bool _isOnlineChanged;
#pragma warning restore CS0414
private bool _valueInited;
private object _value;

View File

@@ -27,6 +27,15 @@ public class ChannelRuntimeService : IChannelRuntimeService
private WaitLock WaitLock { get; set; } = new WaitLock(nameof(ChannelRuntimeService));
public async Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option)
{
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
var _channelItems = channels.GetQueryData(option, GatewayResourceUtil.BuildChannelSelectList);
return _channelItems;
}
public Task<TouchSocket.Core.LogLevel> ChannelLogLevelAsync(long id)
{
GlobalData.IdChannels.TryGetValue(id, out var ChannelRuntime);
@@ -199,7 +208,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
}
}
public async Task<bool> BatchEditAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart)
public async Task<bool> BatchEditChannelAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart)
{
try
{
@@ -254,11 +263,101 @@ public class ChannelRuntimeService : IChannelRuntimeService
public Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile) => GlobalData.ChannelService.PreviewAsync(browserFile);
public Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter) => GlobalData.ChannelService.ExportChannelAsync(exportFilter);
public Task<Dictionary<string, object>> ExportChannelAsync(GatewayExportFilter exportFilter) => GlobalData.ChannelService.ExportChannelAsync(exportFilter);
public Task<MemoryStream> ExportMemoryStream(IEnumerable<Channel> data) =>
GlobalData.ChannelService.ExportMemoryStream(data);
public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(USheetDatas input, bool restart)
{
try
{
await WaitLock.WaitAsync().ConfigureAwait(false);
var data = await ChannelServiceHelpers.ImportAsync(input).ConfigureAwait(false);
if (data.Any(a => a.Value.HasError)) return data;
ChannelServiceHelpers.GetImportChannelData(data, out var upData, out var insertData);
var result = await GlobalData.ChannelService.ImportChannelAsync(upData, insertData).ConfigureAwait(false);
var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false);
RuntimeServiceHelper.Init(newChannelRuntimes);
//根据条件重启通道线程
if (restart)
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
return data;
}
finally
{
WaitLock.Release();
}
}
public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(string filePath, bool restart)
{
try
{
await WaitLock.WaitAsync().ConfigureAwait(false);
var data = await GlobalData.ChannelService.PreviewAsync(filePath).ConfigureAwait(false);
if (data.Any(a => a.Value.HasError)) return data;
var result = await GlobalData.ChannelService.ImportChannelAsync(data).ConfigureAwait(false);
var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false);
RuntimeServiceHelper.Init(newChannelRuntimes);
//根据条件重启通道线程
if (restart)
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
return data;
}
finally
{
WaitLock.Release();
}
}
public async Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(IBrowserFile file, bool restart)
{
try
{
await WaitLock.WaitAsync().ConfigureAwait(false);
var data = await GlobalData.ChannelService.PreviewAsync(file).ConfigureAwait(false);
if (data.Any(a => a.Value.HasError)) return data;
var result = await GlobalData.ChannelService.ImportChannelAsync(data).ConfigureAwait(false);
var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false);
RuntimeServiceHelper.Init(newChannelRuntimes);
//根据条件重启通道线程
if (restart)
await GlobalData.ChannelThreadManage.RestartChannelAsync(newChannelRuntimes).ConfigureAwait(false);
return data;
}
finally
{
WaitLock.Release();
}
}
public async Task ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart)
{
try
@@ -289,7 +388,7 @@ public class ChannelRuntimeService : IChannelRuntimeService
{
await WaitLock.WaitAsync().ConfigureAwait(false);
var result = await GlobalData.ChannelService.ImportAsync(upData, insertData).ConfigureAwait(false);
var result = await GlobalData.ChannelService.ImportChannelAsync(upData, insertData).ConfigureAwait(false);
var newChannelRuntimes = await RuntimeServiceHelper.GetNewChannelRuntimesAsync(result).ConfigureAwait(false);

View File

@@ -247,7 +247,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
/// 报表查询
/// </summary>
/// <param name="exportFilter">查询条件</param>
public async Task<QueryData<Channel>> PageAsync(ExportFilter exportFilter)
public async Task<QueryData<Channel>> PageAsync(GatewayExportFilter exportFilter)
{
var whereQuery = await GetWhereQueryFunc(exportFilter).ConfigureAwait(false);
@@ -255,7 +255,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
, exportFilter.FilterKeyValueAction).ConfigureAwait(false);
}
private async Task<Func<ISugarQueryable<Channel>, ISugarQueryable<Channel>>> GetWhereQueryFunc(ExportFilter exportFilter)
private async Task<Func<ISugarQueryable<Channel>, ISugarQueryable<Channel>>> GetWhereQueryFunc(GatewayExportFilter exportFilter)
{
HashSet<long>? channel = null;
if (exportFilter.PluginType != null)
@@ -331,15 +331,15 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
/// <inheritdoc/>
[OperDesc("ExportChannel", isRecordPar: false, localizerType: typeof(Channel))]
public async Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter)
public async Task<Dictionary<string, object>> ExportChannelAsync(GatewayExportFilter exportFilter)
{
var channels = await GetEnumerableData(exportFilter).ConfigureAwait(false);
var rows = ChannelServiceHelpers.ExportRows(channels); // IEnumerable 延迟执行
var sheets = ChannelServiceHelpers.WrapAsSheet(ExportString.ChannelName, rows);
var sheets = ChannelServiceHelpers.WrapAsSheet(GatewayExportString.ChannelName, rows);
return sheets;
}
private async Task<IAsyncEnumerable<Channel>> GetEnumerableData(ExportFilter exportFilter)
private async Task<IAsyncEnumerable<Channel>> GetEnumerableData(GatewayExportFilter exportFilter)
{
var db = GetDB();
var whereQuery = await GetWhereQueryFunc(exportFilter).ConfigureAwait(false);
@@ -354,7 +354,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
public async Task<MemoryStream> ExportMemoryStream(IEnumerable<Channel> channels)
{
var rows = ChannelServiceHelpers.ExportRows(channels); // IEnumerable 延迟执行
var sheets = ChannelServiceHelpers.WrapAsSheet(ExportString.ChannelName, rows);
var sheets = ChannelServiceHelpers.WrapAsSheet(GatewayExportString.ChannelName, rows);
var memoryStream = new MemoryStream();
await memoryStream.SaveAsAsync(sheets).ConfigureAwait(false);
memoryStream.Seek(0, SeekOrigin.Begin);
@@ -365,17 +365,18 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
#region
/// <inheritdoc/>
[OperDesc("ImportChannel", isRecordPar: false, localizerType: typeof(Channel))]
public Task<HashSet<long>> ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input)
{
ChannelServiceHelpers.GetImportChannelData(input, out var upData, out var insertData);
return ImportAsync(upData, insertData);
return ImportChannelAsync(upData, insertData);
}
public async Task<HashSet<long>> ImportAsync(List<Channel> upData, List<Channel> insertData)
public async Task<HashSet<long>> ImportChannelAsync(List<Channel> upData, List<Channel> insertData)
{
ManageHelper.CheckChannelCount(insertData.Count);
@@ -394,10 +395,20 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
return upData.Select(a => a.Id).Concat(insertData.Select(a => a.Id)).ToHashSet();
}
/// <inheritdoc/>
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile)
{
var path = await browserFile.StorageLocal().ConfigureAwait(false);
return await PreviewAsync(path).ConfigureAwait(false);
}
/// <inheritdoc/>
public async Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(string path)
{
try
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
@@ -426,7 +437,7 @@ internal sealed class ChannelService : BaseService<Channel>, IChannelService
{
#region sheet
if (sheetName == ExportString.ChannelName)
if (sheetName == GatewayExportString.ChannelName)
{
int row = 1;
ImportPreviewListOutput<Channel> importPreviewOutput = new();

View File

@@ -16,7 +16,7 @@ using ThingsGateway.Common.Extension;
namespace ThingsGateway.Gateway.Application;
public static class ChannelServiceHelpers
public static partial class ChannelServiceHelpers
{
public static void GetImportChannelData(Dictionary<string, ImportPreviewOutputBase> input, out List<Channel> upData, out List<Channel> insertData)
@@ -24,7 +24,7 @@ public static class ChannelServiceHelpers
List<Channel>? channels = new List<Channel>();
foreach (var item in input)
{
if (item.Key == ExportString.ChannelName)
if (item.Key == GatewayExportString.ChannelName)
{
var channelImports = ((ImportPreviewListOutput<Channel>)item.Value).Data;
channels = channelImports;
@@ -35,52 +35,6 @@ public static class ChannelServiceHelpers
insertData = channels.Where(a => !a.IsUp).ToList();
}
public static USheetDatas ExportChannel(IEnumerable<Channel> channels)
{
var rows = ExportRows(channels); // IEnumerable 延迟执行
var sheets = WrapAsSheet(ExportString.ChannelName, rows);
return USheetDataHelpers.GetUSheetDatas(sheets);
}
internal static IEnumerable<Dictionary<string, object>> ExportRows(IEnumerable<Channel>? data)
{
if (data == null)
yield break;
#region
var type = typeof(Channel);
var propertyInfos = type.GetRuntimeProperties().Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>(false) == null)
.OrderBy(
a =>
{
var order = a.GetCustomAttribute<AutoGenerateColumnAttribute>()?.Order ?? int.MaxValue;
if (order < 0)
{
order = order + 10000000;
}
else if (order == 0)
{
order = 10000000;
}
return order;
}
)
;
#endregion
foreach (var device in data)
{
Dictionary<string, object> row = new();
foreach (var prop in propertyInfos)
{
var desc = type.GetPropertyDisplayName(prop.Name);
row.Add(desc ?? prop.Name, prop.GetValue(device)?.ToString());
}
yield return row;
}
}
internal static async IAsyncEnumerable<Dictionary<string, object>> ExportRows(IAsyncEnumerable<Channel>? data)
{
@@ -125,13 +79,6 @@ public static class ChannelServiceHelpers
}
}
internal static Dictionary<string, object> WrapAsSheet(string sheetName, IEnumerable<IDictionary<string, object>> rows)
{
return new Dictionary<string, object>
{
[sheetName] = rows
};
}
internal static Dictionary<string, object> WrapAsSheet(string sheetName, IAsyncEnumerable<IDictionary<string, object>> rows)
{

View File

@@ -0,0 +1,75 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
using BootstrapBlazor.Components;
using System.Reflection;
using ThingsGateway.Common.Extension;
namespace ThingsGateway.Gateway.Application;
public static partial class ChannelServiceHelpers
{
public static USheetDatas ExportChannel(IEnumerable<Channel> channels)
{
var rows = ExportRows(channels); // IEnumerable 延迟执行
var sheets = WrapAsSheet(GatewayExportString.ChannelName, rows);
return USheetDataHelpers.GetUSheetDatas(sheets);
}
internal static Dictionary<string, object> WrapAsSheet(string sheetName, IEnumerable<IDictionary<string, object>> rows)
{
return new Dictionary<string, object>
{
[sheetName] = rows
};
}
internal static IEnumerable<Dictionary<string, object>> ExportRows(IEnumerable<Channel>? data)
{
if (data == null)
yield break;
#region
var type = typeof(Channel);
var propertyInfos = type.GetRuntimeProperties().Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>(false) == null)
.OrderBy(
a =>
{
var order = a.GetCustomAttribute<AutoGenerateColumnAttribute>()?.Order ?? int.MaxValue;
if (order < 0)
{
order = order + 10000000;
}
else if (order == 0)
{
order = 10000000;
}
return order;
}
)
;
#endregion
foreach (var device in data)
{
Dictionary<string, object> row = new();
foreach (var prop in propertyInfos)
{
var desc = type.GetPropertyDisplayName(prop.Name);
row.Add(desc ?? prop.Name, prop.GetValue(device)?.ToString());
}
yield return row;
}
}
}

View File

@@ -11,6 +11,8 @@
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace ThingsGateway.Gateway.Application
{
public interface IChannelPageService
@@ -39,7 +41,7 @@ namespace ThingsGateway.Gateway.Application
/// <param name="model">新数据</param>
/// <param name="restart">重启</param>
/// <returns></returns>
Task<bool> BatchEditAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart);
Task<bool> BatchEditChannelAsync(IEnumerable<Channel> models, Channel oldModel, Channel model, bool restart);
@@ -50,6 +52,13 @@ namespace ThingsGateway.Gateway.Application
Task<bool> DeleteChannelAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken);
Task ImportChannelAsync(List<Channel> upData, List<Channel> insertData, bool restart);
Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(USheetDatas input, bool restart);
Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(string filePath, bool restart);
Task<Dictionary<string, ImportPreviewOutputBase>> ImportChannelAsync(IBrowserFile file, bool restart);
Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option);
}
}

View File

@@ -32,11 +32,14 @@ public interface IChannelRuntimeService : IChannelPageService
Task ImportChannelAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart);
Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter);
Task<Dictionary<string, object>> ExportChannelAsync(GatewayExportFilter exportFilter);
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile);
Task<MemoryStream> ExportMemoryStream(IEnumerable<Channel> data);
Task RestartChannelAsync(IEnumerable<ChannelRuntime> oldChannelRuntimes);
Task<bool> CopyAsync(List<Channel> models, Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken);
Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart, CancellationToken cancellationToken);
Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables, bool restart, CancellationToken cancellationToken);
}

View File

@@ -46,7 +46,7 @@ internal interface IChannelService
/// 导出通道为文件流结果
/// </summary>
/// <returns>文件流结果</returns>
Task<Dictionary<string, object>> ExportChannelAsync(ExportFilter exportFilter);
Task<Dictionary<string, object>> ExportChannelAsync(GatewayExportFilter exportFilter);
/// <summary>
/// 导出通道为内存流
@@ -71,7 +71,7 @@ internal interface IChannelService
/// 报表查询
/// </summary>
/// <param name="exportFilter">查询条件</param>
Task<QueryData<Channel>> PageAsync(ExportFilter exportFilter);
Task<QueryData<Channel>> PageAsync(GatewayExportFilter exportFilter);
/// <summary>
/// 预览导入数据
@@ -102,6 +102,7 @@ internal interface IChannelService
Task UpdateLogAsync(long channelId, TouchSocket.Core.LogLevel logLevel);
Task<bool> InsertAsync(List<Channel> models, List<Device> devices, List<Variable> variables);
Task<bool> UpdateAsync(List<Channel> models, List<Device> devices, List<Variable> variables);
Task<HashSet<long>> ImportAsync(List<Channel> upData, List<Channel> insertData);
Task<HashSet<long>> ImportChannelAsync(List<Channel> upData, List<Channel> insertData);
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(string path);
}

View File

@@ -29,6 +29,46 @@ public class DeviceRuntimeService : IDeviceRuntimeService
private WaitLock WaitLock { get; set; } = new WaitLock(nameof(DeviceRuntimeService));
public async Task CopyDeviceAsync(int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long deviceId, bool AutoRestartThread)
{
if (!GlobalData.IdDevices.TryGetValue(deviceId, out var deviceRuntime))
{
return;
}
Device Model = deviceRuntime.AdaptDevice();
Model.Id = 0;
var Variables = deviceRuntime.ReadOnlyVariableRuntimes.Select(a => a.Value).AdaptListVariable();
Dictionary<Device, List<Variable>> devices = new();
for (int i = 0; i < CopyCount; i++)
{
Device device = Model.AdaptDevice();
device.Id = CommonUtils.GetSingleId();
device.Name = $"{CopyDeviceNamePrefix}{CopyDeviceNameSuffixNumber + i}";
List<Variable> variables = new();
foreach (var item in Variables)
{
Variable v = item.AdaptVariable();
v.Id = CommonUtils.GetSingleId();
v.DeviceId = device.Id;
variables.Add(v);
}
devices.Add(device, variables);
}
await GlobalData.DeviceRuntimeService.CopyAsync(devices, AutoRestartThread, default).ConfigureAwait(false);
}
public async Task<bool> CopyAsync(Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken)
{
try
@@ -57,6 +97,10 @@ public class DeviceRuntimeService : IDeviceRuntimeService
}
}
public async Task<bool> BatchEditAsync(IEnumerable<Device> models, Device oldModel, Device model, bool restart)
{
try
@@ -122,7 +166,7 @@ public class DeviceRuntimeService : IDeviceRuntimeService
}
}
public Task<Dictionary<string, object>> ExportDeviceAsync(ExportFilter exportFilter) => GlobalData.DeviceService.ExportDeviceAsync(exportFilter);
public Task<Dictionary<string, object>> ExportDeviceAsync(GatewayExportFilter exportFilter) => GlobalData.DeviceService.ExportDeviceAsync(exportFilter);
public Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile) => GlobalData.DeviceService.PreviewAsync(browserFile);
public Task<MemoryStream> ExportMemoryStream(List<Device> data, string channelName, string plugin) =>
GlobalData.DeviceService.ExportMemoryStream(data, channelName, plugin);

View File

@@ -200,14 +200,14 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
/// 报表查询
/// </summary>
/// <param name="exportFilter">查询条件</param>
public async Task<QueryData<Device>> PageAsync(ExportFilter exportFilter)
public async Task<QueryData<Device>> PageAsync(GatewayExportFilter exportFilter)
{
var whereQuery = await GetWhereQueryFunc(exportFilter).ConfigureAwait(false);
return await QueryAsync(exportFilter.QueryPageOptions, whereQuery
, exportFilter.FilterKeyValueAction).ConfigureAwait(false);
}
private async Task<Func<ISugarQueryable<Device>, ISugarQueryable<Device>>> GetWhereQueryFunc(ExportFilter exportFilter)
private async Task<Func<ISugarQueryable<Device>, ISugarQueryable<Device>>> GetWhereQueryFunc(GatewayExportFilter exportFilter)
{
HashSet<long>? channel = null;
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
@@ -229,7 +229,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
return whereQuery;
}
private async Task<Func<IEnumerable<Device>, IEnumerable<Device>>> GetWhereEnumerableFunc(ExportFilter exportFilter)
private async Task<Func<IEnumerable<Device>, IEnumerable<Device>>> GetWhereEnumerableFunc(GatewayExportFilter exportFilter)
{
HashSet<long>? channel = null;
if (!exportFilter.PluginName.IsNullOrWhiteSpace())
@@ -302,7 +302,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
/// </summary>
/// <returns></returns>
[OperDesc("ExportDevice", isRecordPar: false, localizerType: typeof(Device))]
public async Task<Dictionary<string, object>> ExportDeviceAsync(ExportFilter exportFilter)
public async Task<Dictionary<string, object>> ExportDeviceAsync(GatewayExportFilter exportFilter)
{
//导出
var devices = await GetAsyncEnumerableData(exportFilter).ConfigureAwait(false);
@@ -321,12 +321,12 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
return sheets;
}
private async Task<IAsyncEnumerable<Device>> GetAsyncEnumerableData(ExportFilter exportFilter)
private async Task<IAsyncEnumerable<Device>> GetAsyncEnumerableData(GatewayExportFilter exportFilter)
{
var whereQuery = await GetEnumerableData(exportFilter).ConfigureAwait(false);
return whereQuery.ToAsyncEnumerable();
}
private async Task<ISugarQueryable<Device>> GetEnumerableData(ExportFilter exportFilter)
private async Task<ISugarQueryable<Device>> GetEnumerableData(GatewayExportFilter exportFilter)
{
var db = GetDB();
var whereQuery = await GetWhereQueryFunc(exportFilter).ConfigureAwait(false);
@@ -367,7 +367,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
IEnumerable<Device>? devices = new List<Device>();
foreach (var item in input)
{
if (item.Key == ExportString.DeviceName)
if (item.Key == GatewayExportString.DeviceName)
{
var deviceImports = ((ImportPreviewOutput<Device>)item.Value).Data;
devices = deviceImports.Select(a => a.Value);
@@ -446,7 +446,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
string PluginNotNull = Localizer["PluginNotNull"];
string DeviceNotNull = Localizer["DeviceNotNull"];
if (sheetName == ExportString.DeviceName)
if (sheetName == GatewayExportString.DeviceName)
{
// 初始化行数
int row = 1;
@@ -484,8 +484,8 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
}
// 转换冗余设备名称
var hasRedundant = item.TryGetValue(ExportString.RedundantDeviceName, out var redundantObj);
var hasChannel = item.TryGetValue(ExportString.ChannelName, out var channelObj);
var hasRedundant = item.TryGetValue(GatewayExportString.RedundantDeviceName, out var redundantObj);
var hasChannel = item.TryGetValue(GatewayExportString.ChannelName, out var channelObj);
// 设备ID、冗余设备ID都需要手动补录
if (hasRedundant && redundantObj != null)
@@ -666,7 +666,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
}
// 获取设备名称
if (!item.TryGetValue(ExportString.DeviceName, out var deviceName))
if (!item.TryGetValue(GatewayExportString.DeviceName, out var deviceName))
{
importPreviewOutput.HasError = true;
importPreviewOutput.Results.Add((Interlocked.Increment(ref row), false, DeviceNotNull));
@@ -674,7 +674,7 @@ internal sealed class DeviceService : BaseService<Device>, IDeviceService
}
// 转化插件名称
var value = item[ExportString.DeviceName]?.ToString();
var value = item[GatewayExportString.DeviceName]?.ToString();
// 检查设备名称是否存在于设备导入预览数据中,如果不存在,则添加错误信息到导入预览结果并继续下一轮循环
var hasDevice = deviceImportPreview.Data.ContainsKey(value);

View File

@@ -44,7 +44,7 @@ HashSet<string> pluginSheetNames,
data = new List<Device>();
var result = new Dictionary<string, object>();
result.Add(ExportString.DeviceName, GetDeviceSheets(data, deviceDicts, channelDicts, channelName));
result.Add(GatewayExportString.DeviceName, GetDeviceSheets(data, deviceDicts, channelDicts, channelName));
ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict = new();
foreach (var plugin in pluginSheetNames)
@@ -69,7 +69,7 @@ string? channelName = null)
return new();
var result = new Dictionary<string, object>();
result.Add(ExportString.DeviceName, GetDeviceSheets(data1, deviceDicts, channelDicts, channelName));
result.Add(GatewayExportString.DeviceName, GetDeviceSheets(data1, deviceDicts, channelDicts, channelName));
ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict = new();
foreach (var plugin in pluginSheetNames)
@@ -209,7 +209,7 @@ string? channelName)
deviceDicts.TryGetValue(device.RedundantDeviceId ?? 0, out var redundantDevice);
channelDicts.TryGetValue(device.ChannelId, out var channel);
devExport.Add(ExportString.ChannelName, channel?.Name ?? channelName);
devExport.Add(GatewayExportString.ChannelName, channel?.Name ?? channelName);
foreach (var item in propertyInfos)
{
@@ -220,7 +220,7 @@ string? channelName)
}
//设备实体没有包含冗余设备名称,手动插入
devExport.Add(ExportString.RedundantDeviceName, redundantDevice?.Name);
devExport.Add(GatewayExportString.RedundantDeviceName, redundantDevice?.Name);
return devExport;
}
@@ -250,7 +250,7 @@ string? channelName)
if (propertys.Item2.Count > 0)
{
//没有包含设备名称,手动插入
driverInfo.Add(ExportString.DeviceName, device.Name);
driverInfo.Add(GatewayExportString.DeviceName, device.Name);
}
//根据插件的配置属性项生成列,从数据库中获取值或者获取属性默认值
foreach (var item in propertys.Item2)

View File

@@ -0,0 +1,17 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://thingsgateway.cn/
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Application
{
public interface IDevicePageService
{
Task CopyDeviceAsync(int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber, long deviceId, bool AutoRestartThread);
}
}

View File

@@ -14,12 +14,12 @@ using Microsoft.AspNetCore.Components.Forms;
namespace ThingsGateway.Gateway.Application
{
public interface IDeviceRuntimeService
public interface IDeviceRuntimeService : IDevicePageService
{
Task<bool> BatchEditAsync(IEnumerable<Device> models, Device oldModel, Device model, bool restart);
Task<bool> CopyAsync(Dictionary<Device, List<Variable>> devices, bool restart, CancellationToken cancellationToken);
Task<bool> DeleteDeviceAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken);
Task<Dictionary<string, object>> ExportDeviceAsync(ExportFilter exportFilter);
Task<Dictionary<string, object>> ExportDeviceAsync(GatewayExportFilter exportFilter);
Task<MemoryStream> ExportMemoryStream(List<Device> data, string channelName, string plugin);
Task ImportDeviceAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart);
Task<Dictionary<string, ImportPreviewOutputBase>> PreviewAsync(IBrowserFile browserFile);

View File

@@ -59,7 +59,7 @@ internal interface IDeviceService
/// 导出设备信息到文件流。
/// </summary>
/// <returns>导出的文件流</returns>
Task<Dictionary<string, object>> ExportDeviceAsync(ExportFilter exportFilter);
Task<Dictionary<string, object>> ExportDeviceAsync(GatewayExportFilter exportFilter);
/// <summary>
/// 导出设备信息到内存流。
@@ -88,7 +88,7 @@ internal interface IDeviceService
/// </summary>
/// <param name="exportFilter">查询条件</param>
/// <returns>查询结果</returns>
Task<QueryData<Device>> PageAsync(ExportFilter exportFilter);
Task<QueryData<Device>> PageAsync(GatewayExportFilter exportFilter);
/// <summary>
/// 预览导入设备信息。

View File

@@ -19,7 +19,7 @@ namespace ThingsGateway.Gateway.Application
Task<bool> BatchEditAsync(IEnumerable<Variable> models, Variable oldModel, Variable model, bool restart, CancellationToken cancellationToken);
Task<bool> DeleteVariableAsync(IEnumerable<long> ids, bool restart, CancellationToken cancellationToken);
Task<bool> ClearVariableAsync(bool restart, CancellationToken cancellationToken);
Task<Dictionary<string, object>> ExportVariableAsync(ExportFilter exportFilter);
Task<Dictionary<string, object>> ExportVariableAsync(GatewayExportFilter exportFilter);
Task ImportVariableAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart, CancellationToken cancellationToken);
Task InsertTestDataAsync(int testVariableCount, int testDeviceCount, string slaveUrl, bool businessEnable, bool restart, CancellationToken cancellationToken);

View File

@@ -61,7 +61,7 @@ internal interface IVariableService
/// <summary>
/// 异步导出变量数据到文件流中。
/// </summary>
Task<Dictionary<string, object>> ExportVariableAsync(ExportFilter exportFilter);
Task<Dictionary<string, object>> ExportVariableAsync(GatewayExportFilter exportFilter);
/// <summary>
/// 异步获取变量。
@@ -84,7 +84,7 @@ internal interface IVariableService
/// 表格查询
/// </summary>
/// <param name="exportFilter">查询分页选项</param>
Task<QueryData<Variable>> PageAsync(ExportFilter exportFilter);
Task<QueryData<Variable>> PageAsync(GatewayExportFilter exportFilter);
/// <summary>
/// 异步预览导入的数据。

View File

@@ -145,7 +145,7 @@ public class VariableRuntimeService : IVariableRuntimeService
}
}
public Task<Dictionary<string, object>> ExportVariableAsync(ExportFilter exportFilter) => GlobalData.VariableService.ExportVariableAsync(exportFilter);
public Task<Dictionary<string, object>> ExportVariableAsync(GatewayExportFilter exportFilter) => GlobalData.VariableService.ExportVariableAsync(exportFilter);
public async Task ImportVariableAsync(Dictionary<string, ImportPreviewOutputBase> input, bool restart, CancellationToken cancellationToken)
{

View File

@@ -363,13 +363,13 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
/// 报表查询
/// </summary>
/// <param name="exportFilter">查询条件</param>
public async Task<QueryData<Variable>> PageAsync(ExportFilter exportFilter)
public async Task<QueryData<Variable>> PageAsync(GatewayExportFilter exportFilter)
{
var whereQuery = await GetWhereQueryFunc(exportFilter).ConfigureAwait(false);
return await QueryAsync(exportFilter.QueryPageOptions, whereQuery).ConfigureAwait(false);
}
private async Task<Func<ISugarQueryable<Variable>, ISugarQueryable<Variable>>> GetWhereQueryFunc(ExportFilter exportFilter)
private async Task<Func<ISugarQueryable<Variable>, ISugarQueryable<Variable>>> GetWhereQueryFunc(GatewayExportFilter exportFilter)
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
HashSet<long>? deviceId = null;
@@ -394,7 +394,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
return whereQuery;
}
private async Task<Func<IEnumerable<Variable>, IEnumerable<Variable>>> GetWhereEnumerableFunc(ExportFilter exportFilter, bool sql = false)
private async Task<Func<IEnumerable<Variable>, IEnumerable<Variable>>> GetWhereEnumerableFunc(GatewayExportFilter exportFilter, bool sql = false)
{
var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
HashSet<long>? deviceId = null;
@@ -490,7 +490,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
/// 导出文件
/// </summary>
[OperDesc("ExportVariable", isRecordPar: false, localizerType: typeof(Variable))]
public async Task<Dictionary<string, object>> ExportVariableAsync(ExportFilter exportFilter)
public async Task<Dictionary<string, object>> ExportVariableAsync(GatewayExportFilter exportFilter)
{
if (GlobalData.HardwareJob.HardwareInfo.MachineInfo.AvailableMemory < 4 * 1024 * 1024)
{
@@ -525,12 +525,12 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
return sheets;
}
}
private async Task<IAsyncEnumerable<Variable>> GetAsyncEnumerableData(ExportFilter exportFilter)
private async Task<IAsyncEnumerable<Variable>> GetAsyncEnumerableData(GatewayExportFilter exportFilter)
{
var whereQuery = await GetEnumerableData(exportFilter).ConfigureAwait(false);
return whereQuery.ToAsyncEnumerable();
}
private async Task<ISugarQueryable<Variable>> GetEnumerableData(ExportFilter exportFilter)
private async Task<ISugarQueryable<Variable>> GetEnumerableData(GatewayExportFilter exportFilter)
{
var db = GetDB();
var whereQuery = await GetWhereQueryFunc(exportFilter).ConfigureAwait(false);
@@ -549,7 +549,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
IEnumerable<Variable>? variables = new List<Variable>();
foreach (var item in input)
{
if (item.Key == ExportString.VariableName)
if (item.Key == GatewayExportString.VariableName)
{
var variableImports = ((ImportPreviewOutput<Dictionary<string, Variable>>)item.Value).Data;
variables = variableImports.SelectMany(a => a.Value.Select(a => a.Value));
@@ -628,7 +628,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
// 变量页处理
if (sheetName == ExportString.VariableName)
if (sheetName == GatewayExportString.VariableName)
{
int row = 1;
ImportPreviewOutput<Dictionary<string, Variable>> importPreviewOutput = new();
@@ -652,7 +652,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
variable.Row = index;
// 获取设备名称并查找对应的设备
item.TryGetValue(ExportString.DeviceName, out var value);
item.TryGetValue(GatewayExportString.DeviceName, out var value);
var deviceName = value?.ToString();
deviceDicts.TryGetValue(deviceName, out var device);
var deviceId = device?.Id;
@@ -729,7 +729,7 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
// 将变量列表转换为字典,并赋值给导入预览输出对象的 Data 属性
importPreviewOutput.Data = variables.OrderBy(a => a.Row).GroupBy(a => a.DeviceId.ToString()).ToDictionary(a => a.Key, b => b.ToDictionary(a => a.Name));
}
else if (sheetName == ExportString.AlarmName)
else if (sheetName == GatewayExportString.AlarmName)
{
int row = 1;
ImportPreviewOutput<string> importPreviewOutput = new();
@@ -755,8 +755,8 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
}
// 转化插件名称和变量名称
item.TryGetValue(ExportString.VariableName, out var variableNameObj);
item.TryGetValue(ExportString.DeviceName, out var collectDevName);
item.TryGetValue(GatewayExportString.VariableName, out var variableNameObj);
item.TryGetValue(GatewayExportString.DeviceName, out var collectDevName);
deviceDicts.TryGetValue(collectDevName?.ToString(), out var collectDevice);
// 如果设备名称或变量名称为空,或者找不到对应的设备,则添加错误信息到导入预览结果并返回
if (collectDevName == null || collectDevice == null)
@@ -887,9 +887,9 @@ internal sealed class VariableService : BaseService<Variable>, IVariableService
}
// 转化插件名称和变量名称
item.TryGetValue(ExportString.VariableName, out var variableNameObj);
item.TryGetValue(ExportString.BusinessDeviceName, out var businessDevName);
item.TryGetValue(ExportString.DeviceName, out var collectDevName);
item.TryGetValue(GatewayExportString.VariableName, out var variableNameObj);
item.TryGetValue(GatewayExportString.BusinessDeviceName, out var businessDevName);
item.TryGetValue(GatewayExportString.DeviceName, out var collectDevName);
deviceDicts.TryGetValue(businessDevName?.ToString(), out var businessDevice);
deviceDicts.TryGetValue(collectDevName?.ToString(), out var collectDevice);

View File

@@ -97,8 +97,8 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
var propertysDict = new ConcurrentDictionary<string, (VariablePropertyBase, Dictionary<string, PropertyInfo>)>();
// 主变量页
sheets.Add(ExportString.VariableName, GetVariableSheets(data, deviceDicts, deviceName));
sheets.Add(ExportString.AlarmName, GetAlarmSheets(data, deviceDicts, deviceName));
sheets.Add(GatewayExportString.VariableName, GetVariableSheets(data, deviceDicts, deviceName));
sheets.Add(GatewayExportString.AlarmName, GetAlarmSheets(data, deviceDicts, deviceName));
// 插件页(动态推导)
foreach (var plugin in pluginDrivers.Keys.Distinct())
@@ -137,7 +137,7 @@ IReadOnlyDictionary<long, ChannelRuntime> channelDicts)
{
var row = new Dictionary<string, object>();
deviceDicts.TryGetValue(variable.DeviceId, out var device);
row.TryAdd(ExportString.DeviceName, device?.Name ?? deviceName);
row.TryAdd(GatewayExportString.DeviceName, device?.Name ?? deviceName);
foreach (var item in propertyInfos)
{
@@ -179,8 +179,8 @@ string? deviceName)
{
var row = new Dictionary<string, object>();
deviceDicts.TryGetValue(variable.DeviceId, out var device);
row.TryAdd(ExportString.DeviceName, device?.Name ?? deviceName);
row.TryAdd(ExportString.VariableName, variable.Name);
row.TryAdd(GatewayExportString.DeviceName, device?.Name ?? deviceName);
row.TryAdd(GatewayExportString.VariableName, variable.Name);
foreach (var item in propertyInfos)
{
@@ -242,9 +242,9 @@ string? deviceName)
{
var row = new Dictionary<string, object>
{
{ ExportString.DeviceName, collectDevice.Name },
{ ExportString.BusinessDeviceName, businessDevice.Name },
{ ExportString.VariableName, variable.Name }
{ GatewayExportString.DeviceName, collectDevice.Name },
{ GatewayExportString.BusinessDeviceName, businessDevice.Name },
{ GatewayExportString.VariableName, variable.Name }
};
foreach (var kv in propertys.Item2)
@@ -274,7 +274,7 @@ string? deviceName)
var propertysDict = new ConcurrentDictionary<string, (VariablePropertyBase, Dictionary<string, PropertyInfo>)>();
// 主变量页
sheets.Add(ExportString.VariableName, GetVariableSheets(data, deviceDicts, deviceName));
sheets.Add(GatewayExportString.VariableName, GetVariableSheets(data, deviceDicts, deviceName));
// 插件页(动态推导)
foreach (var plugin in pluginDrivers.Keys.Distinct())
@@ -420,7 +420,7 @@ string? deviceName)
Dictionary<string, object> varExport = new();
deviceDicts.TryGetValue(variable.DeviceId, out var device);
//设备实体没有包含设备名称,手动插入
varExport.TryAdd(ExportString.DeviceName, device?.Name ?? deviceName);
varExport.TryAdd(GatewayExportString.DeviceName, device?.Name ?? deviceName);
foreach (var item in propertyInfos)
{
if (item.Name == nameof(Variable.Id))
@@ -443,8 +443,8 @@ string? deviceName)
Dictionary<string, object> alarmExport = new();
deviceDicts.TryGetValue(variable.DeviceId, out var device);
//设备实体没有包含设备名称,手动插入
alarmExport.TryAdd(ExportString.DeviceName, device?.Name ?? deviceName);
alarmExport.TryAdd(ExportString.VariableName, variable.Name);
alarmExport.TryAdd(GatewayExportString.DeviceName, device?.Name ?? deviceName);
alarmExport.TryAdd(GatewayExportString.VariableName, variable.Name);
foreach (var item in alarmPropertysInfos)
{
//描述
@@ -470,9 +470,9 @@ string? deviceName)
channelDicts.TryGetValue(businessDevice.ChannelId, out var channel);
//没有包含设备名称,手动插入
driverInfo.TryAdd(ExportString.DeviceName, collectDevice.Name);
driverInfo.TryAdd(ExportString.BusinessDeviceName, businessDevice.Name);
driverInfo.TryAdd(ExportString.VariableName, variable.Name);
driverInfo.TryAdd(GatewayExportString.DeviceName, collectDevice.Name);
driverInfo.TryAdd(GatewayExportString.BusinessDeviceName, businessDevice.Name);
driverInfo.TryAdd(GatewayExportString.VariableName, variable.Name);
var propDict = item.Value;
@@ -565,13 +565,13 @@ string? deviceName)
variableExports.ForEach(a => a.Remove("Id"));
//添加设备页
sheets.Add(ExportString.VariableName, variableExports);
sheets.Add(ExportString.AlarmName, alarmExports);
sheets.Add(GatewayExportString.VariableName, variableExports);
sheets.Add(GatewayExportString.AlarmName, alarmExports);
//HASH
foreach (var item in devicePropertys.Keys)
{
devicePropertys[item] = new(devicePropertys[item].OrderBy(a => a[ExportString.DeviceName]).ThenBy(a => a[ExportString.VariableName]));
devicePropertys[item] = new(devicePropertys[item].OrderBy(a => a[GatewayExportString.DeviceName]).ThenBy(a => a[GatewayExportString.VariableName]));
sheets.Add(item, devicePropertys[item]);
}

View File

@@ -8,10 +8,12 @@
// QQ群605534569
//------------------------------------------------------------------------------
namespace ThingsGateway.Gateway.Razor;
using BootstrapBlazor.Components;
namespace ThingsGateway.Gateway.Application;
[ThingsGateway.DependencyInjection.SuppressSniffer]
public static class ResourceUtil
public static class GatewayResourceUtil
{
/// <summary>
/// 构造选择项ID/Name

View File

@@ -4,7 +4,7 @@ namespace ThingsGateway.Gateway.Razor
{
public static class TreeViewItemMapper
{
public static global::System.Collections.Generic.List<global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Razor.ChannelDeviceTreeItem>> AdaptListTreeViewItemChannelDeviceTreeItem(this global::System.Collections.Generic.List<global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Razor.ChannelDeviceTreeItem>> src)
public static global::System.Collections.Generic.List<global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Application.ChannelDeviceTreeItem>> AdaptListTreeViewItemChannelDeviceTreeItem(this global::System.Collections.Generic.List<global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Application.ChannelDeviceTreeItem>> src)
{
NormalizeItems(src);
return src.ToList();
@@ -21,50 +21,6 @@ namespace ThingsGateway.Gateway.Razor
}
}
//public static global::System.Collections.Generic.List<global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Razor.ChannelDeviceTreeItem>> AdaptListTreeViewItemChannelDeviceTreeItem(this global::System.Collections.Generic.List<global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Razor.ChannelDeviceTreeItem>> src, global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Razor.ChannelDeviceTreeItem> parent = null)
//{
// var target = new global::System.Collections.Generic.List<global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Razor.ChannelDeviceTreeItem>>(src.Count);
// foreach (var item in src)
// {
// target.Add(MapToTreeViewItemOfChannelDeviceTreeItem(item, parent));
// }
// return target;
//}
//private static global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Razor.ChannelDeviceTreeItem> MapToTreeViewItemOfChannelDeviceTreeItem(global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Razor.ChannelDeviceTreeItem> source, global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Razor.ChannelDeviceTreeItem> parent)
//{
// var target = new global::BootstrapBlazor.Components.TreeViewItem<global::ThingsGateway.Gateway.Razor.ChannelDeviceTreeItem>(source.Value);
// target.CheckedState = source.CheckedState;
// target.Items = AdaptListTreeViewItemChannelDeviceTreeItem(source.Items.ToList(), target);
// if (source.Parent != null && parent != null)
// {
// target.Parent = parent;
// }
// else
// {
// target.Parent = null;
// }
// target.Text = source.Text;
// target.Icon = source.Icon;
// target.ExpandIcon = source.ExpandIcon;
// target.CssClass = source.CssClass;
// target.IsDisabled = source.IsDisabled;
// target.IsActive = source.IsActive;
// target.Template = source.Template;
// if (source.Value != null)
// {
// target.Value = source.Value;
// }
// else
// {
// target.Value = null;
// }
// target.IsExpand = source.IsExpand;
// target.HasChildren = source.HasChildren;
// return target;
//}
}
}

View File

@@ -27,7 +27,9 @@ public partial class ChannelRuntimeInfo1 : IDisposable
protected override void OnInitialized()
{
#if !Management
_ = RunTimerAsync();
#endif
base.OnInitialized();
}
@@ -39,6 +41,7 @@ public partial class ChannelRuntimeInfo1 : IDisposable
try
{
await InvokeAsync(StateHasChanged);
}
catch (Exception ex)
{

View File

@@ -162,7 +162,7 @@ public partial class ChannelTable : IDisposable
{
{nameof(ChannelEditComponent.OnValidSubmit), async () =>
{
await Task.Run(() => ChannelPageService.BatchEditAsync(changedModels, oldModel, oneModel,AutoRestartThread));
await Task.Run(() => ChannelPageService.BatchEditChannelAsync(changedModels, oldModel, oneModel,AutoRestartThread));
await InvokeAsync(table.QueryAsync);
} },
@@ -279,10 +279,8 @@ public partial class ChannelTable : IDisposable
{
await Task.Run(async ()=>
{
var importData=await ChannelServiceHelpers.ImportAsync(data);
ChannelServiceHelpers. GetImportChannelData(importData, out var upData, out var insertData);
var importData=await ChannelPageService.ImportChannelAsync(data,AutoRestartThread);
await ChannelPageService.ImportChannelAsync(upData,insertData,AutoRestartThread);
})
;
}
@@ -314,13 +312,24 @@ finally
OnCloseAsync = async () => await InvokeAsync(table.QueryAsync),
};
Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> preview = (a => GlobalData.ChannelRuntimeService.PreviewAsync(a));
Func<Dictionary<string, ImportPreviewOutputBase>, Task> import = (value => GlobalData.ChannelRuntimeService.ImportChannelAsync(value, AutoRestartThread));
Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> import = (a =>
{
return ChannelPageService.ImportChannelAsync(a, AutoRestartThread);
});
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcel>(new Dictionary<string, object?>
{
{nameof(ImportExcel.Import),import },
{nameof(ImportExcel.Preview),preview },
});
//Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> preview = (a => GlobalData.ChannelRuntimeService.PreviewAsync(a));
//Func<Dictionary<string, ImportPreviewOutputBase>, Task> import = (value => GlobalData.ChannelRuntimeService.ImportChannelAsync(value, AutoRestartThread));
//op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcel>(new Dictionary<string, object?>
//{
// {nameof(ImportExcel.Import),import },
// {nameof(ImportExcel.Preview),preview },
//});
await DialogService.Show(op);
}

View File

@@ -241,7 +241,7 @@ public partial class ChannelDeviceTree : IDisposable
{nameof(ChannelEditComponent.OnValidSubmit), async () =>
{
Spinner.SetRun(true);
await Task.Run(() => GlobalData.ChannelRuntimeService.BatchEditAsync(changedModels, oldModel, oneModel,AutoRestartThread));
await Task.Run(() => GlobalData.ChannelRuntimeService.BatchEditChannelAsync(changedModels, oldModel, oneModel,AutoRestartThread));
//await Notify();
await InvokeAsync(() => Spinner.SetRun(false));
@@ -506,7 +506,7 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
}
else if (channelDeviceTreeItem.TryGetPluginType(out var pluginType))
{
ret = await GatewayExportService.OnChannelExport(new ExportFilter() { QueryPageOptions = new(), PluginType = pluginType });
ret = await GatewayExportService.OnChannelExport(new GatewayExportFilter() { QueryPageOptions = new(), PluginType = pluginType });
}
else
{
@@ -556,10 +556,10 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
//await Notify();
await InvokeAsync(() => Spinner.SetRun(false));
});
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcel>(new Dictionary<string, object?>
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcelConfirm>(new Dictionary<string, object?>
{
{nameof(ImportExcel.Import),import },
{nameof(ImportExcel.Preview),preview },
{nameof(ImportExcelConfirm.Import),import },
{nameof(ImportExcelConfirm.Preview),preview },
});
await DialogService.Show(op);
@@ -581,32 +581,21 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
ShowFooter = false,
ShowCloseButton = false,
};
Device oneModel = null;
List<Variable> variables = new();
if (value is not ChannelDeviceTreeItem channelDeviceTreeItem) return;
if (channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
{
oneModel = deviceRuntime.AdaptDevice();
oneModel.Id = 0;
variables = deviceRuntime.ReadOnlyVariableRuntimes.Select(a => a.Value).AdaptListVariable();
}
else
if (!channelDeviceTreeItem.TryGetDeviceRuntime(out var deviceRuntime))
{
return;
}
op.Component = BootstrapDynamicComponent.CreateComponent<DeviceCopyComponent>(new Dictionary<string, object?>
{
{nameof(DeviceCopyComponent.OnSave), async (Dictionary<Device,List<Variable>> devices) =>
{nameof(DeviceCopyComponent.OnSave), async (int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber) =>
{
await Task.Run(() =>GlobalData.DeviceRuntimeService.CopyAsync(devices,AutoRestartThread, default));
await Task.Run(() =>GlobalData.DeviceRuntimeService.CopyDeviceAsync(CopyCount,CopyDeviceNamePrefix,CopyDeviceNameSuffixNumber,deviceRuntime.Id,AutoRestartThread));
//await Notify();
}},
{nameof(DeviceCopyComponent.Model),oneModel },
{nameof(DeviceCopyComponent.Variables),variables },
});
await DialogService.Show(op);
@@ -1044,10 +1033,10 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
//await Notify();
await InvokeAsync(() => Spinner.SetRun(false));
});
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcel>(new Dictionary<string, object?>
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcelConfirm>(new Dictionary<string, object?>
{
{nameof(ImportExcel.Import),import },
{nameof(ImportExcel.Preview),preview },
{nameof(ImportExcelConfirm.Import),import },
{nameof(ImportExcelConfirm.Preview),preview },
});
await DialogService.Show(op);
@@ -1131,9 +1120,9 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
ZItem[0].Items = ResourceUtil.BuildTreeItemList(channels.Where(a => a.IsCollect == true), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem);
ZItem[1].Items = ResourceUtil.BuildTreeItemList(channels.Where(a => a.IsCollect == false), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem);
var item2 = ResourceUtil.BuildTreeItemList(channels.Where(a => a.IsCollect == null), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem);
ZItem[0].Items = GatewayResourceUtil.BuildTreeItemList(channels.Where(a => a.IsCollect == true), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem);
ZItem[1].Items = GatewayResourceUtil.BuildTreeItemList(channels.Where(a => a.IsCollect == false), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem);
var item2 = GatewayResourceUtil.BuildTreeItemList(channels.Where(a => a.IsCollect == null), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem);
if (item2.Count > 0)
{
UnknownTreeViewItem.Items = item2;
@@ -1237,10 +1226,10 @@ EventCallback.Factory.Create<MouseEventArgs>(this, async e =>
{
var items = channels.WhereIF(!searchText.IsNullOrEmpty(), a => a.Name.Contains(searchText));
ZItem[0].Items = ResourceUtil.BuildTreeItemList(items.Where(a => a.IsCollect == true), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem, items: ZItem[0].Items);
ZItem[1].Items = ResourceUtil.BuildTreeItemList(items.Where(a => a.IsCollect == false), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem, items: ZItem[1].Items);
ZItem[0].Items = GatewayResourceUtil.BuildTreeItemList(items.Where(a => a.IsCollect == true), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem, items: ZItem[0].Items);
ZItem[1].Items = GatewayResourceUtil.BuildTreeItemList(items.Where(a => a.IsCollect == false), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem, items: ZItem[1].Items);
var item2 = ResourceUtil.BuildTreeItemList(items.Where(a => a.IsCollect == null), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem);
var item2 = GatewayResourceUtil.BuildTreeItemList(items.Where(a => a.IsCollect == null), new List<ChannelDeviceTreeItem> { Value }, RenderTreeItem);
if (item2.Count > 0)
{
UnknownTreeViewItem.Items = item2;

View File

@@ -8,8 +8,6 @@
// QQ群605534569
//------------------------------------------------------------------------------
using ThingsGateway.DB;
namespace ThingsGateway.Gateway.Razor;
public partial class DeviceCopyComponent
@@ -26,7 +24,7 @@ public partial class DeviceCopyComponent
public List<Variable> Variables { get; set; }
[Parameter]
public Func<Dictionary<Device, List<Variable>>, Task> OnSave { get; set; }
public Func<int, string, int, Task> OnSave { get; set; }
[CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; }
@@ -41,26 +39,10 @@ public partial class DeviceCopyComponent
{
try
{
Dictionary<Device, List<Variable>> devices = new();
for (int i = 0; i < CopyCount; i++)
{
Device device = Model.AdaptDevice();
device.Id = CommonUtils.GetSingleId();
device.Name = $"{CopyDeviceNamePrefix}{CopyDeviceNameSuffixNumber + i}";
List<Variable> variables = new();
foreach (var item in Variables)
{
Variable v = item.AdaptVariable();
v.Id = CommonUtils.GetSingleId();
v.DeviceId = device.Id;
variables.Add(v);
}
devices.Add(device, variables);
}
if (OnSave != null)
await OnSave(devices);
await OnSave(CopyCount, CopyDeviceNamePrefix, CopyDeviceNameSuffixNumber);
if (OnCloseAsync != null)
await OnCloseAsync();
await ToastService.Default();

View File

@@ -61,7 +61,7 @@
<EditTemplate Context="value">
<div class="col-12 col-md-6 ">
<BootstrapInputGroup>
<Select IsVirtualize DefaultVirtualizeItemText=@(GlobalData.ReadOnlyIdChannels.TryGetValue(value.ChannelId,out var channelRuntime)?channelRuntime.Name:string.Empty) @bind-Value="@value.ChannelId" IsDisabled=BatchEditEnable Items="@_channelItems" OnSelectedItemChanged=OnChannelChanged ShowSearch="true" ShowLabel="true" />
<Select IsVirtualize DefaultVirtualizeItemText=@(GlobalData.ReadOnlyIdChannels.TryGetValue(value.ChannelId,out var channelRuntime)?channelRuntime.Name:string.Empty) @bind-Value="@value.ChannelId" IsDisabled=BatchEditEnable OnSelectedItemChanged=OnChannelChanged ShowSearch="true" ShowLabel="true" OnQueryAsync=OnChannelSelectedItemQueryAsync />
<Button IsDisabled=BatchEditEnable class="text-end" Icon="fa-solid fa-plus" OnClick="AddChannel"></Button>
</BootstrapInputGroup>
</div>

View File

@@ -42,15 +42,15 @@ public partial class DeviceEditComponent
[CascadingParameter]
private Func<Task>? OnCloseAsync { get; set; }
private IEnumerable<SelectedItem> _channelItems;
protected override async Task OnParametersSetAsync()
[Inject]
IChannelPageService ChannelPageService { get; set; }
private Task<QueryData<SelectedItem>> OnChannelSelectedItemQueryAsync(VirtualizeQueryOption option)
{
var channels = await GlobalData.GetCurrentUserChannels().ConfigureAwait(false);
_channelItems = channels.BuildChannelSelectList();
base.OnParametersSet();
return ChannelPageService.OnChannelSelectedItemQueryAsync(option);
}
public async Task ValidSubmit(EditContext editContext)
{
try

View File

@@ -111,13 +111,6 @@ public partial class DeviceTable : IDisposable
return;
}
Device oneModel = null;
List<Variable> variables = new();
oneModel = deviceRuntime.AdaptDevice();
oneModel.Id = 0;
variables = deviceRuntime.ReadOnlyVariableRuntimes.Select(a => a.Value).AdaptListVariable();
var op = new DialogOption()
{
IsScrolling = false,
@@ -130,13 +123,12 @@ public partial class DeviceTable : IDisposable
op.Component = BootstrapDynamicComponent.CreateComponent<DeviceCopyComponent>(new Dictionary<string, object?>
{
{nameof(DeviceCopyComponent.OnSave), async (Dictionary<Device,List<Variable>> devices) =>
{nameof(DeviceCopyComponent.OnSave), async (int CopyCount, string CopyDeviceNamePrefix, int CopyDeviceNameSuffixNumber) =>
{
await Task.Run(() =>GlobalData.DeviceRuntimeService.CopyAsync(devices,AutoRestartThread, default));
await InvokeAsync(table.QueryAsync);
await Task.Run(() =>GlobalData.DeviceRuntimeService.CopyDeviceAsync(CopyCount,CopyDeviceNamePrefix,CopyDeviceNameSuffixNumber,deviceRuntime.Id,AutoRestartThread));
//await Notify();
}},
{nameof(DeviceCopyComponent.Model),oneModel },
{nameof(DeviceCopyComponent.Variables),variables },
});
await DialogService.Show(op);
@@ -322,10 +314,10 @@ finally
Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> preview = (a => GlobalData.DeviceRuntimeService.PreviewAsync(a));
Func<Dictionary<string, ImportPreviewOutputBase>, Task> import = (value => GlobalData.DeviceRuntimeService.ImportDeviceAsync(value, AutoRestartThread));
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcel>(new Dictionary<string, object?>
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcelConfirm>(new Dictionary<string, object?>
{
{nameof(ImportExcel.Import),import },
{nameof(ImportExcel.Preview),preview },
{nameof(ImportExcelConfirm.Import),import },
{nameof(ImportExcelConfirm.Preview),preview },
});
await DialogService.Show(op);
}

View File

@@ -11,6 +11,9 @@
namespace ThingsGateway.Gateway.Razor;
public partial class GatewayInfo
#if Management
: IDisposable
#endif
{
[Parameter]
public ChannelDeviceTreeItem SelectModel { get; set; } = new() { ChannelDevicePluginType = ChannelDevicePluginTypeEnum.PluginType, PluginType = null };
@@ -31,4 +34,43 @@ public partial class GatewayInfo
public ShowTypeEnum? ShowType { get; set; }
[Parameter]
public bool AutoRestartThread { get; set; } = true;
protected override void OnInitialized()
{
_ = RunTimerAsync();
base.OnInitialized();
}
private bool Disposed;
private async Task RunTimerAsync()
{
while (!Disposed)
{
try
{
await InvokeAsync(StateHasChanged);
}
catch (Exception ex)
{
NewLife.Log.XTrace.WriteException(ex);
}
finally
{
await Task.Delay(5000);
}
}
}
public void Dispose()
{
Disposed = true;
GC.SuppressFinalize(this);
}
#if Management
#endif
}

View File

@@ -384,10 +384,10 @@ finally
Func<IBrowserFile, Task<Dictionary<string, ImportPreviewOutputBase>>> preview = (a => GlobalData.VariableRuntimeService.PreviewAsync(a));
Func<Dictionary<string, ImportPreviewOutputBase>, Task> import = (value => GlobalData.VariableRuntimeService.ImportVariableAsync(value, AutoRestartThread, default));
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcel>(new Dictionary<string, object?>
op.Component = BootstrapDynamicComponent.CreateComponent<ImportExcelConfirm>(new Dictionary<string, object?>
{
{nameof(ImportExcel.Import),import },
{nameof(ImportExcel.Preview),preview },
{nameof(ImportExcelConfirm.Import),import },
{nameof(ImportExcelConfirm.Preview),preview },
});
await DialogService.Show(op);
}

View File

@@ -26,7 +26,7 @@ internal sealed class GatewayExportService : IGatewayExportService
private IJSRuntime JSRuntime { get; set; }
public async Task<bool> OnChannelExport(ExportFilter exportFilter)
public async Task<bool> OnChannelExport(GatewayExportFilter exportFilter)
{
try
{
@@ -41,7 +41,7 @@ internal sealed class GatewayExportService : IGatewayExportService
}
}
public async Task<bool> OnDeviceExport(ExportFilter exportFilter)
public async Task<bool> OnDeviceExport(GatewayExportFilter exportFilter)
{
try
{
@@ -56,7 +56,7 @@ internal sealed class GatewayExportService : IGatewayExportService
}
}
public async Task<bool> OnVariableExport(ExportFilter exportFilter)
public async Task<bool> OnVariableExport(GatewayExportFilter exportFilter)
{
try
{

View File

@@ -32,7 +32,7 @@ public sealed class HybridGatewayExportService : IGatewayExportService
_importExportService = importExportService;
}
public async Task<bool> OnChannelExport(ExportFilter exportFilter)
public async Task<bool> OnChannelExport(GatewayExportFilter exportFilter)
{
try
{
@@ -71,7 +71,7 @@ public sealed class HybridGatewayExportService : IGatewayExportService
return true;
}
public async Task<bool> OnDeviceExport(ExportFilter exportFilter)
public async Task<bool> OnDeviceExport(GatewayExportFilter exportFilter)
{
try
{
@@ -89,7 +89,7 @@ public sealed class HybridGatewayExportService : IGatewayExportService
}
}
public async Task<bool> OnVariableExport(ExportFilter exportFilter)
public async Task<bool> OnVariableExport(GatewayExportFilter exportFilter)
{
try
{

View File

@@ -12,7 +12,7 @@ namespace ThingsGateway.Gateway.Razor;
public interface IGatewayExportService
{
Task<bool> OnChannelExport(ExportFilter exportFilter);
Task<bool> OnDeviceExport(ExportFilter exportFilter);
Task<bool> OnVariableExport(ExportFilter exportFilter);
Task<bool> OnChannelExport(GatewayExportFilter exportFilter);
Task<bool> OnDeviceExport(GatewayExportFilter exportFilter);
Task<bool> OnVariableExport(GatewayExportFilter exportFilter);
}

View File

@@ -34,6 +34,7 @@
<!--<Import Project="targets\Pro3.targets" Condition=" '$(SolutionName)' == 'ThingsGatewayPro' " />
<Import Project="targets\Pro5.targets" Condition=" '$(SolutionName)' == 'ThingsGatewayPro' " />-->
<Import Project="targets\Pro6.targets" Condition=" '$(SolutionName)' == 'ThingsGatewayPro' AND '$(Configuration)' != 'Debug'" />
<!--打包复制-->
<Import Project="targets\PluginPublish.targets" />

View File

@@ -0,0 +1,28 @@
<Project>
<ItemGroup>
<!--MqttYINGKE 插件-->
<PackageReference Include="ThingsGateway.Plugin.IXCom29s" Version="$(ProPluginVersion)" GeneratePathProperty="true" Private="false" IncludeAssets="native;" />
</ItemGroup>
<Target Name="CopyOtherPlugin6NugetPackages" AfterTargets="Build">
<PropertyGroup>
<PluginTargetFramework>net8.0</PluginTargetFramework>
</PropertyGroup>
<ItemGroup>
<!-- setting up the variable for convenience -->
<PkgThingsGateway_Plugin_IXCom29sPackageFiles Include="$(PkgThingsGateway_Plugin_IXCom29s)\Content\$(PluginTargetFramework)\**\*.*" />
</ItemGroup>
<PropertyGroup>
<PluginFolder>$(TargetDir)GatewayPlugins\</PluginFolder>
</PropertyGroup>
<Message Text="将插件复制到插件目录 $(PluginFolder)" Importance="high" />
<Copy SourceFiles="@(PkgThingsGateway_Plugin_IXCom29sPackageFiles)" DestinationFolder="$(PluginFolder)ThingsGateway.Plugin.IXCom29s%(RecursiveDir)" />
</Target>
</Project>