mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-20 10:50:48 +08:00
添加写优先选项,默认false
This commit is contained in:
@@ -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,
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ namespace ThingsGateway.Admin.Razor;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ThingsGateway.DependencyInjection.SuppressSniffer]
|
||||
public static class ResourceUtil
|
||||
public static class AdminResourceUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造选择项,ID/TITLE
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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]
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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; }
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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"
|
||||
},
|
||||
|
@@ -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": "验证内容"
|
||||
},
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -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; }
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
@@ -17,7 +17,6 @@ using TouchSocket.Core;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
#pragma warning disable CS0649
|
||||
|
||||
/// <summary>
|
||||
/// 通道表
|
||||
|
@@ -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)
|
||||
|
@@ -16,7 +16,6 @@ using System.Collections.Concurrent;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
#pragma warning disable CS0649
|
||||
|
||||
/// <summary>
|
||||
/// 设备变量表
|
||||
|
@@ -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",
|
||||
|
@@ -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": "设备",
|
||||
|
@@ -8,7 +8,8 @@
|
||||
// QQ群:605534569
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ThingsGateway.Gateway.Razor;
|
||||
|
||||
namespace ThingsGateway.Gateway.Application;
|
||||
public enum ChannelDevicePluginTypeEnum
|
||||
{
|
||||
PluginType,
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
||||
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
@@ -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>
|
||||
/// 预览导入设备信息。
|
||||
|
@@ -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);
|
||||
|
@@ -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>
|
||||
/// 异步预览导入的数据。
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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]);
|
||||
}
|
||||
|
@@ -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
|
@@ -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;
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -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)
|
||||
{
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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" />
|
||||
|
28
src/ThingsGateway.Server/targets/Pro6.targets
Normal file
28
src/ThingsGateway.Server/targets/Pro6.targets
Normal 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>
|
Reference in New Issue
Block a user