Compare commits
	
		
			12 Commits
		
	
	
		
			10.11.33.0
			...
			10.11.40.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					4d223d2622 | ||
| 
						 | 
					e8d7e91b64 | ||
| 
						 | 
					8175f541ec | ||
| 
						 | 
					0adbdb926b | ||
| 
						 | 
					42adee9980 | ||
| 
						 | 
					427a7404bc | ||
| 
						 | 
					3658199e0a | ||
| 
						 | 
					82eedee50a | ||
| 
						 | 
					6a18fc3e06 | ||
| 
						 | 
					c37e314ed6 | ||
| 
						 | 
					a937a85d90 | ||
| 
						 | 
					35dd4ae9d3 | 
@@ -6,7 +6,7 @@
 | 
			
		||||
 | 
			
		||||
    <Table TItem="TItem" IsBordered="true" IsStriped="true" TableSize="TableSize.Compact" IsMultipleSelect="IsMultipleSelect" @ref="Instance" SearchTemplate="SearchTemplate"
 | 
			
		||||
           DataService="DataService" CreateItemCallback="CreateItemCallback!"
 | 
			
		||||
           IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" BeforeShowEditDialogCallback="BeforeShowEditDialogCallback!"
 | 
			
		||||
           IsPagination="IsPagination" PageItemsSource="PageItemsSource" IsFixedHeader="IsFixedHeader" IndentSize=24 RowHeight=RowHeight ShowSearchText="ShowSearchText" ShowSearchButton="ShowSearchButton" DisableEditButtonCallback="DisableEditButtonCallback" DisableDeleteButtonCallback="DisableDeleteButtonCallback" BeforeShowEditDialogCallback=" BeforeShowEditDialogCallback!"
 | 
			
		||||
           IsTree="IsTree" OnTreeExpand="OnTreeExpand!" TreeNodeConverter="TreeNodeConverter!" TreeIcon="fa-solid fa-circle-chevron-right" TreeExpandIcon="fa-solid fa-circle-chevron-right fa-rotate-90" IsAutoQueryFirstRender=IsAutoQueryFirstRender
 | 
			
		||||
           ShowDefaultButtons="ShowDefaultButtons" ShowAdvancedSearch="ShowAdvancedSearch" ShowResetButton=ShowResetButton
 | 
			
		||||
           ShowEmpty="ShowEmpty" EmptyText="@EmptyText" EmptyImage="@($"{WebsiteConst.DefaultResourceUrl}images/empty.svg")" SortString="@SortString" EditDialogSize="EditDialogSize"
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
           ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
 | 
			
		||||
           SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton ExportButtonText=@ExportButtonText
 | 
			
		||||
           ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
 | 
			
		||||
           ShowExportCsvButton=@ShowExportCsvButton SelectedRowsChanged=SelectedRowsChanged ShowCardView=ShowCardView
 | 
			
		||||
           ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView
 | 
			
		||||
           FixedExtendButtonsColumn=FixedExtendButtonsColumn FixedMultipleColumn=FixedMultipleColumn FixedDetailRowHeaderColumn=FixedDetailRowHeaderColumn FixedLineNoColumn=FixedLineNoColumn
 | 
			
		||||
           IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
 | 
			
		||||
           AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
 | 
			
		||||
@@ -29,7 +29,7 @@
 | 
			
		||||
           ShowMultiFilterHeader=ShowMultiFilterHeader
 | 
			
		||||
           ShowFilterHeader=ShowFilterHeader
 | 
			
		||||
           ShowColumnList=ShowColumnList ExtendButtonColumnWidth="@ExtendButtonColumnWidth"
 | 
			
		||||
           CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows" ModelEqualityComparer="ModelEqualityComparer!"
 | 
			
		||||
           CustomerSearchModel="CustomerSearchModel" ModelEqualityComparer="ModelEqualityComparer!"
 | 
			
		||||
           ShowExtendEditButtonCallback="ShowExtendEditButtonCallback!" ShowExtendDeleteButtonCallback="ShowExtendDeleteButtonCallback!"
 | 
			
		||||
           DisableExtendEditButton="DisableExtendEditButton!" DisableExtendDeleteButton="DisableExtendDeleteButton!"
 | 
			
		||||
           DisableExtendEditButtonCallback="DisableExtendEditButtonCallback!" DisableExtendDeleteButtonCallback="DisableExtendDeleteButtonCallback!"
 | 
			
		||||
 
 | 
			
		||||
@@ -210,14 +210,6 @@ public partial class AdminTable<TItem> where TItem : class, new()
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public RenderFragment<TItem>? SearchTemplate { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc cref="Table{TItem}.SelectedRows"/>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public List<TItem>? SelectedRows { get; set; } = new List<TItem>();
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc cref="Table{TItem}.SetRowClassFormatter"/>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public Func<TItem, string?>? SetRowClassFormatter { get; set; }
 | 
			
		||||
@@ -266,6 +258,15 @@ public partial class AdminTable<TItem> where TItem : class, new()
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public bool ShowExportButton { get; set; } = false;
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc cref="Table{TItem}.DisableEditButtonCallback"/>
 | 
			
		||||
    public Func<List<TItem>, bool> DisableEditButtonCallback { get; set; } = (list) =>
 | 
			
		||||
    list.Count != 1;
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc cref="Table{TItem}.DisableDeleteButtonCallback"/>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public Func<List<TItem>, bool> DisableDeleteButtonCallback { get; set; } = (list) =>
 | 
			
		||||
    list.Count <= 0;
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc cref="Table{TItem}.ShowExportCsvButton"/>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public bool ShowExportCsvButton { get; set; } = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor.TableExport" Version="9.2.6" />
 | 
			
		||||
		<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor" Version="9.10.0" />
 | 
			
		||||
		<PackageReference Include="BootstrapBlazor" Version="9.10.1" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
       ShowSkeleton="true" ShowLoading="ShowLoading" ShowSearch="ShowSearch" SearchModel=@SearchModel ShowLineNo
 | 
			
		||||
       SearchMode=SearchMode ShowExportPdfButton=ShowExportPdfButton  
 | 
			
		||||
       ShowExportButton=@ShowExportButton Items=Items ClickToSelect=ClickToSelect ScrollMode=ScrollMode
 | 
			
		||||
       ShowExportCsvButton=@ShowExportCsvButton SelectedRowsChanged=SelectedRowsChanged ShowCardView=ShowCardView
 | 
			
		||||
       ShowExportCsvButton=@ShowExportCsvButton ShowCardView=ShowCardView
 | 
			
		||||
       FixedExtendButtonsColumn IsAutoRefresh=IsAutoRefresh AutoRefreshInterval=AutoRefreshInterval
 | 
			
		||||
       AllowDragColumn=@AllowDragColumn Height=@Height ShowRefresh=ShowRefresh
 | 
			
		||||
       AllowResizing=@AllowResizing ExportButtonDropdownTemplate=ExportButtonDropdownTemplate
 | 
			
		||||
@@ -24,7 +24,7 @@
 | 
			
		||||
       ShowMultiFilterHeader=ShowMultiFilterHeader
 | 
			
		||||
       ShowFilterHeader=ShowFilterHeader
 | 
			
		||||
       ShowColumnList=ShowColumnList ExtendButtonColumnWidth="@ExtendButtonColumnWidth"
 | 
			
		||||
       CustomerSearchModel="CustomerSearchModel" SelectedRows="SelectedRows" ModelEqualityComparer="ModelEqualityComparer!"
 | 
			
		||||
       CustomerSearchModel="CustomerSearchModel" ModelEqualityComparer="ModelEqualityComparer!"
 | 
			
		||||
       ShowExtendEditButtonCallback="ShowExtendEditButtonCallback!" ShowExtendDeleteButtonCallback="ShowExtendDeleteButtonCallback!"
 | 
			
		||||
       DisableExtendEditButton="DisableExtendEditButton!" DisableExtendDeleteButton="DisableExtendDeleteButton!"
 | 
			
		||||
       DisableExtendEditButtonCallback="DisableExtendEditButtonCallback!" DisableExtendDeleteButtonCallback="DisableExtendDeleteButtonCallback!"
 | 
			
		||||
 
 | 
			
		||||
@@ -186,14 +186,6 @@ public partial class DefaultTable<TItem> where TItem : class, new()
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public RenderFragment<TItem>? SearchTemplate { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc cref="Table{TItem}.SelectedRows"/>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public List<TItem>? SelectedRows { get; set; } = new List<TItem>();
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc cref="Table{TItem}.SelectedRowsChanged"/>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public EventCallback<List<TItem>> SelectedRowsChanged { get; set; }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc cref="Table{TItem}.SetRowClassFormatter"/>
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public Func<TItem, string?>? SetRowClassFormatter { get; set; }
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@
 | 
			
		||||
		<PackageReference Include="CsvHelper" Version="33.1.0" />
 | 
			
		||||
		<PackageReference Include="TDengine.Connector" Version="3.1.9" />
 | 
			
		||||
		<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.9.1" />
 | 
			
		||||
		<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.23" />
 | 
			
		||||
		<PackageReference Include="Oscar.Data.SqlClient" Version="4.2.25" />
 | 
			
		||||
		<PackageReference Include="System.Data.Common" Version="4.3.0" />
 | 
			
		||||
		<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
 | 
			
		||||
		<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,17 @@
 | 
			
		||||
<Project>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
		<PluginVersion>10.11.33</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.11.33</ProPluginVersion>
 | 
			
		||||
		<DefaultVersion>10.11.33</DefaultVersion>
 | 
			
		||||
		<AuthenticationVersion>10.11.3</AuthenticationVersion>
 | 
			
		||||
		<SourceGeneratorVersion>10.11.3</SourceGeneratorVersion>
 | 
			
		||||
		<NET8Version>8.0.19</NET8Version>
 | 
			
		||||
		<NET9Version>9.0.8</NET9Version>
 | 
			
		||||
		<PluginVersion>10.11.40</PluginVersion>
 | 
			
		||||
		<ProPluginVersion>10.11.40</ProPluginVersion>
 | 
			
		||||
		<DefaultVersion>10.11.40</DefaultVersion>
 | 
			
		||||
		<AuthenticationVersion>10.11.5</AuthenticationVersion>
 | 
			
		||||
		<SourceGeneratorVersion>10.11.4</SourceGeneratorVersion>
 | 
			
		||||
		<NET8Version>8.0.20</NET8Version>
 | 
			
		||||
		<NET9Version>9.0.9</NET9Version>
 | 
			
		||||
		<SatelliteResourceLanguages>zh-Hans;en-US</SatelliteResourceLanguages>
 | 
			
		||||
		<IsTrimmable>false</IsTrimmable>
 | 
			
		||||
		<ManagementProPluginVersion>10.11.22</ManagementProPluginVersion>
 | 
			
		||||
		<ManagementPluginVersion>10.11.22</ManagementPluginVersion>
 | 
			
		||||
		<ManagementProPluginVersion>10.11.36</ManagementProPluginVersion>
 | 
			
		||||
		<ManagementPluginVersion>10.11.36</ManagementPluginVersion>
 | 
			
		||||
	</PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<PropertyGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@ public static class ChannelOptionsExtensions
 | 
			
		||||
            for (int i = 0; i < funcs.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var func = funcs[i];
 | 
			
		||||
                if (func == null) continue;
 | 
			
		||||
                await func.Invoke(clientChannel, e, i == funcs.Count - 1).ConfigureAwait(false);
 | 
			
		||||
                if (e.Handled)
 | 
			
		||||
                {
 | 
			
		||||
@@ -65,6 +66,7 @@ public static class ChannelOptionsExtensions
 | 
			
		||||
                for (int i = 0; i < funcs.Count; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    var func = funcs[i];
 | 
			
		||||
                    if (func == null) continue;
 | 
			
		||||
                    var handled = await func.Invoke(clientChannel, i == funcs.Count - 1).ConfigureAwait(false);
 | 
			
		||||
                    if (handled)
 | 
			
		||||
                    {
 | 
			
		||||
@@ -99,16 +101,16 @@ public static class ChannelOptionsExtensions
 | 
			
		||||
        switch (channelType)
 | 
			
		||||
        {
 | 
			
		||||
            case ChannelTypeEnum.TcpClient:
 | 
			
		||||
                return config.GetTcpClientWithIPHost(channelOptions);
 | 
			
		||||
                return config.GetTcpClient(channelOptions);
 | 
			
		||||
 | 
			
		||||
            case ChannelTypeEnum.TcpService:
 | 
			
		||||
                return config.GetTcpServiceWithBindIPHost(channelOptions);
 | 
			
		||||
                return config.GetTcpService(channelOptions);
 | 
			
		||||
 | 
			
		||||
            case ChannelTypeEnum.SerialPort:
 | 
			
		||||
                return config.GetSerialPortWithOption(channelOptions);
 | 
			
		||||
                return config.GetSerialPort(channelOptions);
 | 
			
		||||
 | 
			
		||||
            case ChannelTypeEnum.UdpSession:
 | 
			
		||||
                return config.GetUdpSessionWithIPHost(channelOptions);
 | 
			
		||||
                return config.GetUdpSession(channelOptions);
 | 
			
		||||
            case ChannelTypeEnum.Other:
 | 
			
		||||
                channelOptions.Config = config;
 | 
			
		||||
                OtherChannel otherChannel = new OtherChannel(channelOptions);
 | 
			
		||||
@@ -123,13 +125,12 @@ public static class ChannelOptionsExtensions
 | 
			
		||||
    /// <param name="config">配置</param>
 | 
			
		||||
    /// <param name="channelOptions">串口配置</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    public static SerialPortChannel GetSerialPortWithOption(this TouchSocketConfig config, IChannelOptions channelOptions)
 | 
			
		||||
    public static SerialPortChannel GetSerialPort(this TouchSocketConfig config, IChannelOptions channelOptions)
 | 
			
		||||
    {
 | 
			
		||||
        var serialPortOption = channelOptions.Map<SerialPortOption>();
 | 
			
		||||
        serialPortOption.ThrowIfNull(nameof(SerialPortOption));
 | 
			
		||||
        channelOptions.Config = config;
 | 
			
		||||
        config.SetSerialPortOption(serialPortOption);
 | 
			
		||||
 | 
			
		||||
        //载入配置
 | 
			
		||||
        SerialPortChannel serialPortChannel = new SerialPortChannel(channelOptions);
 | 
			
		||||
        return serialPortChannel;
 | 
			
		||||
@@ -142,7 +143,7 @@ public static class ChannelOptionsExtensions
 | 
			
		||||
    /// <param name="channelOptions">通道配置</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    /// <exception cref="ArgumentNullException"></exception>
 | 
			
		||||
    public static TcpClientChannel GetTcpClientWithIPHost(this TouchSocketConfig config, IChannelOptions channelOptions)
 | 
			
		||||
    public static TcpClientChannel GetTcpClient(this TouchSocketConfig config, IChannelOptions channelOptions)
 | 
			
		||||
    {
 | 
			
		||||
        var remoteUrl = channelOptions.RemoteUrl;
 | 
			
		||||
        var bindUrl = channelOptions.BindUrl;
 | 
			
		||||
@@ -164,7 +165,7 @@ public static class ChannelOptionsExtensions
 | 
			
		||||
    /// <param name="channelOptions">通道配置</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    /// <exception cref="ArgumentNullException"></exception>
 | 
			
		||||
    public static IChannel GetTcpServiceWithBindIPHost(this TouchSocketConfig config, IChannelOptions channelOptions)
 | 
			
		||||
    public static IChannel GetTcpService(this TouchSocketConfig config, IChannelOptions channelOptions)
 | 
			
		||||
    {
 | 
			
		||||
        var bindUrl = channelOptions.BindUrl;
 | 
			
		||||
        bindUrl.ThrowIfNull(nameof(bindUrl));
 | 
			
		||||
@@ -192,7 +193,7 @@ public static class ChannelOptionsExtensions
 | 
			
		||||
    /// <param name="channelOptions">通道配置</param>
 | 
			
		||||
    /// <returns></returns>
 | 
			
		||||
    /// <exception cref="ArgumentNullException"></exception>
 | 
			
		||||
    public static UdpSessionChannel GetUdpSessionWithIPHost(this TouchSocketConfig config, IChannelOptions channelOptions)
 | 
			
		||||
    public static UdpSessionChannel GetUdpSession(this TouchSocketConfig config, IChannelOptions channelOptions)
 | 
			
		||||
    {
 | 
			
		||||
        var remoteUrl = channelOptions.RemoteUrl;
 | 
			
		||||
        var bindUrl = channelOptions.BindUrl;
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,11 @@ namespace ThingsGateway.Foundation;
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class OtherChannel : SetupConfigObject, IClientChannel
 | 
			
		||||
{
 | 
			
		||||
    ~OtherChannel()
 | 
			
		||||
    {
 | 
			
		||||
        this.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SingleStreamDataHandlingAdapter m_dataHandlingAdapter;
 | 
			
		||||
    public DataHandlingAdapter ReadOnlyDataHandlingAdapter => m_dataHandlingAdapter;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,10 @@ namespace ThingsGateway.Foundation;
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class SerialPortChannel : SerialPortClient, IClientChannel
 | 
			
		||||
{
 | 
			
		||||
    ~SerialPortChannel()
 | 
			
		||||
    {
 | 
			
		||||
        this.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
    public SerialPortChannel(IChannelOptions channelOptions)
 | 
			
		||||
    {
 | 
			
		||||
        ChannelOptions = channelOptions;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,10 @@ namespace ThingsGateway.Foundation;
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class TcpClientChannel : TcpClient, IClientChannel
 | 
			
		||||
{
 | 
			
		||||
    ~TcpClientChannel()
 | 
			
		||||
    {
 | 
			
		||||
        this.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public TcpClientChannel(IChannelOptions channelOptions)
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,11 @@ namespace ThingsGateway.Foundation;
 | 
			
		||||
/// <typeparam name="TClient"></typeparam>
 | 
			
		||||
public abstract class TcpServiceChannelBase<TClient> : TcpService<TClient>, ITcpService<TClient> where TClient : TcpSessionClientChannel, new()
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    ~TcpServiceChannelBase()
 | 
			
		||||
    {
 | 
			
		||||
        this.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public ConcurrentList<IDevice> Collects { get; } = new();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,10 @@ namespace ThingsGateway.Foundation;
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class TcpSessionClientChannel : TcpSessionClient, IClientChannel
 | 
			
		||||
{
 | 
			
		||||
    ~TcpSessionClientChannel()
 | 
			
		||||
    {
 | 
			
		||||
        this.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    public TcpSessionClientChannel()
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,10 @@ namespace ThingsGateway.Foundation;
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class UdpSessionChannel : UdpSession, IClientChannel
 | 
			
		||||
{
 | 
			
		||||
    ~UdpSessionChannel()
 | 
			
		||||
    {
 | 
			
		||||
        this.SafeDispose();
 | 
			
		||||
    }
 | 
			
		||||
    private readonly WaitLock _connectLock = new WaitLock(nameof(UdpSessionChannel));
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
 
 | 
			
		||||
@@ -629,7 +629,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
            DataTypeEnum.UInt32 => await ReadUInt32Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
            DataTypeEnum.Int64 => await ReadInt64Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
            DataTypeEnum.UInt64 => await ReadUInt64Async(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
            DataTypeEnum.Single => await ReadSingleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
            DataTypeEnum.Float => await ReadSingleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
            DataTypeEnum.Double => await ReadDoubleAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
            DataTypeEnum.Decimal => await ReadDecimalAsync(address, length, cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
            _ => new OperResult<Array>(string.Format(AppResource.DataTypeNotSupported, dataType)),
 | 
			
		||||
@@ -655,7 +655,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
                    DataTypeEnum.UInt32 => await WriteAsync(address, jArray.ToObject<UInt32[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.Int64 => await WriteAsync(address, jArray.ToObject<Int64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.UInt64 => await WriteAsync(address, jArray.ToObject<UInt64[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.Single => await WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.Float => await WriteAsync(address, jArray.ToObject<Single[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.Double => await WriteAsync(address, jArray.ToObject<Double[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.Decimal => await WriteAsync(address, jArray.ToObject<Decimal[]>().AsMemory(), cancellationToken: cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    _ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
 | 
			
		||||
@@ -674,7 +674,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
                    DataTypeEnum.UInt32 => await WriteAsync(address, value.ToObject<UInt32>(), bitConverter, cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.Int64 => await WriteAsync(address, value.ToObject<Int64>(), bitConverter, cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.UInt64 => await WriteAsync(address, value.ToObject<UInt64>(), bitConverter, cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.Single => await WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.Float => await WriteAsync(address, value.ToObject<Single>(), bitConverter, cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.Double => await WriteAsync(address, value.ToObject<Double>(), bitConverter, cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    DataTypeEnum.Decimal => await WriteAsync(address, value.ToObject<Decimal>(), bitConverter, cancellationToken).ConfigureAwait(false),
 | 
			
		||||
                    _ => new OperResult(string.Format(AppResource.DataTypeNotSupported, dataType)),
 | 
			
		||||
@@ -864,7 +864,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
    public virtual ValueTask<OperResult> WriteAsync(string address, float value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
 | 
			
		||||
        return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Single, cancellationToken);
 | 
			
		||||
        return WriteAsync(address, bitConverter.GetBytes(value), DataTypeEnum.Float, cancellationToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
@@ -937,7 +937,7 @@ public abstract class DeviceBase : AsyncAndSyncDisposableObject, IDevice
 | 
			
		||||
    public virtual ValueTask<OperResult> WriteAsync(string address, ReadOnlyMemory<float> value, IThingsGatewayBitConverter bitConverter = null, CancellationToken cancellationToken = default)
 | 
			
		||||
    {
 | 
			
		||||
        bitConverter ??= ThingsGatewayBitConverter.GetTransByAddress(address);
 | 
			
		||||
        return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Single, cancellationToken);
 | 
			
		||||
        return WriteAsync(address, bitConverter.GetBytes(value.Span), DataTypeEnum.Float, cancellationToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
 
 | 
			
		||||
@@ -45,12 +45,16 @@ public enum DataTypeEnum
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    UInt64,
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    Single,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 大部分人并不认识Single,但都认识Float
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    Float,
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    Double,
 | 
			
		||||
 | 
			
		||||
    /// <inheritdoc/>
 | 
			
		||||
    Decimal,
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ public static class DataTypeExtensions
 | 
			
		||||
            DataTypeEnum.UInt32 => 4,
 | 
			
		||||
            DataTypeEnum.Int64 => 8,
 | 
			
		||||
            DataTypeEnum.UInt64 => 8,
 | 
			
		||||
            DataTypeEnum.Single => 4,
 | 
			
		||||
            DataTypeEnum.Float => 4,
 | 
			
		||||
            DataTypeEnum.Double => 8,
 | 
			
		||||
            DataTypeEnum.Decimal => 16,
 | 
			
		||||
            _ => 0,
 | 
			
		||||
@@ -57,7 +57,7 @@ public static class DataTypeExtensions
 | 
			
		||||
            TypeCode.UInt32 => DataTypeEnum.UInt32,
 | 
			
		||||
            TypeCode.Int64 => DataTypeEnum.Int64,
 | 
			
		||||
            TypeCode.UInt64 => DataTypeEnum.UInt64,
 | 
			
		||||
            TypeCode.Single => DataTypeEnum.Single,
 | 
			
		||||
            TypeCode.Single => DataTypeEnum.Float,
 | 
			
		||||
            TypeCode.Double => DataTypeEnum.Double,
 | 
			
		||||
            TypeCode.Decimal => DataTypeEnum.Decimal,
 | 
			
		||||
            _ => DataTypeEnum.Object,
 | 
			
		||||
@@ -82,7 +82,7 @@ public static class DataTypeExtensions
 | 
			
		||||
            DataTypeEnum.UInt32 => typeof(uint),
 | 
			
		||||
            DataTypeEnum.Int64 => typeof(long),
 | 
			
		||||
            DataTypeEnum.UInt64 => typeof(ulong),
 | 
			
		||||
            DataTypeEnum.Single => typeof(float),
 | 
			
		||||
            DataTypeEnum.Float => typeof(float),
 | 
			
		||||
            DataTypeEnum.Double => typeof(double),
 | 
			
		||||
            DataTypeEnum.Decimal => typeof(decimal),
 | 
			
		||||
            _ => typeof(object),
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,8 @@
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="$(NET9Version)" />
 | 
			
		||||
		<PackageReference Include="TouchSocket" Version="4.0.0-beta.25" />
 | 
			
		||||
		<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.25" />
 | 
			
		||||
		<PackageReference Include="TouchSocket" Version="4.0.0-beta.27" />
 | 
			
		||||
		<PackageReference Include="TouchSocket.SerialPorts" Version="4.0.0-beta.27" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@ public static class ThingsGatewayBitConverterExtension
 | 
			
		||||
                case DataTypeEnum.UInt64:
 | 
			
		||||
                    return byteConverter.GetBytes(value.ToObject<UInt64[]>());
 | 
			
		||||
 | 
			
		||||
                case DataTypeEnum.Single:
 | 
			
		||||
                case DataTypeEnum.Float:
 | 
			
		||||
                    return byteConverter.GetBytes(value.ToObject<Single[]>());
 | 
			
		||||
 | 
			
		||||
                case DataTypeEnum.Double:
 | 
			
		||||
@@ -107,7 +107,7 @@ public static class ThingsGatewayBitConverterExtension
 | 
			
		||||
                case DataTypeEnum.UInt64:
 | 
			
		||||
                    return byteConverter.GetBytes(value.ToObject<UInt64>());
 | 
			
		||||
 | 
			
		||||
                case DataTypeEnum.Single:
 | 
			
		||||
                case DataTypeEnum.Float:
 | 
			
		||||
                    return byteConverter.GetBytes(value.ToObject<Single>());
 | 
			
		||||
 | 
			
		||||
                case DataTypeEnum.Double:
 | 
			
		||||
@@ -333,7 +333,7 @@ public static class ThingsGatewayBitConverterExtension
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            case DataTypeEnum.Single:
 | 
			
		||||
            case DataTypeEnum.Float:
 | 
			
		||||
                if (arrayLength > 1)
 | 
			
		||||
                {
 | 
			
		||||
                    var newVal = byteConverter.ToSingle(buffer, index, arrayLength);
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,15 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
    /// 特殊方法
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public List<DriverMethodInfo>? DriverMethodInfos { get; private set; }
 | 
			
		||||
 | 
			
		||||
    protected virtual bool IsRuntimeSourceValid(VariableRuntime a)
 | 
			
		||||
    {
 | 
			
		||||
        //筛选特殊变量地址
 | 
			
		||||
        //1、DeviceStatus
 | 
			
		||||
        return !a.RegisterAddress.Equals(nameof(DeviceRuntime.DeviceStatus), StringComparison.OrdinalIgnoreCase) &&
 | 
			
		||||
        !a.RegisterAddress.Equals("Script", StringComparison.OrdinalIgnoreCase) &&
 | 
			
		||||
        !a.RegisterAddress.Equals("ScriptRead", StringComparison.OrdinalIgnoreCase)
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
    public override async Task AfterVariablesChangedAsync(CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        LogMessage?.LogInformation("Refresh variable");
 | 
			
		||||
@@ -84,21 +92,13 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
            && string.IsNullOrEmpty(it.OtherMethod)
 | 
			
		||||
            && !string.IsNullOrEmpty(it.RegisterAddress));
 | 
			
		||||
 | 
			
		||||
        //筛选特殊变量地址
 | 
			
		||||
        //1、DeviceStatus
 | 
			
		||||
        Func<VariableRuntime, bool> source = (a =>
 | 
			
		||||
        {
 | 
			
		||||
            return !a.RegisterAddress.Equals(nameof(DeviceRuntime.DeviceStatus), StringComparison.OrdinalIgnoreCase) &&
 | 
			
		||||
            !a.RegisterAddress.Equals("Script", StringComparison.OrdinalIgnoreCase) &&
 | 
			
		||||
            !a.RegisterAddress.Equals("ScriptRead", StringComparison.OrdinalIgnoreCase)
 | 
			
		||||
            ;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        var now = DateTime.Now;
 | 
			
		||||
#pragma warning disable CA1851
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            currentDevice.VariableScriptReads = tags.Where(a => !source(a)).Select(a =>
 | 
			
		||||
            currentDevice.VariableScriptReads = tags.Where(a => !IsRuntimeSourceValid(a)).Select(a =>
 | 
			
		||||
            {
 | 
			
		||||
                var data = new VariableScriptRead();
 | 
			
		||||
                data.VariableRuntime = a;
 | 
			
		||||
@@ -111,9 +111,9 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
            // 如果出现异常,记录日志并初始化 VariableSourceReads 属性为新实例
 | 
			
		||||
            currentDevice.VariableScriptReads = new();
 | 
			
		||||
            LogMessage?.LogWarning(ex, string.Format(AppResource.VariablePackError, ex.Message));
 | 
			
		||||
            tags.Where(a => !source(a)).ForEach(a => a.SetValue(null, now, isOnline: false));
 | 
			
		||||
            tags.Where(a => !IsRuntimeSourceValid(a)).ForEach(a => a.SetValue(null, now, isOnline: false));
 | 
			
		||||
        }
 | 
			
		||||
        var variableReads = tags.Where(source).ToList();
 | 
			
		||||
        var variableReads = tags.Where(IsRuntimeSourceValid).ToList();
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // 将打包后的结果存储在当前设备的 VariableSourceReads 属性中
 | 
			
		||||
@@ -482,6 +482,7 @@ public abstract partial class CollectBase : DriverBase
 | 
			
		||||
 | 
			
		||||
    protected virtual Task TestOnline(object? state, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        CurrentDevice.SetDeviceStatus(TimerX.Now, false, null);
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,53 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
//  此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
 | 
			
		||||
//  此代码版权(除特别声明外的代码)归作者本人Diego所有
 | 
			
		||||
//  源代码使用协议遵循本仓库的开源协议及附加协议
 | 
			
		||||
//  Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
 | 
			
		||||
//  Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
 | 
			
		||||
//  使用文档:https://thingsgateway.cn/
 | 
			
		||||
//  QQ群:605534569
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
namespace ThingsGateway.Gateway.Application;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// 插件配置项
 | 
			
		||||
/// <br></br>
 | 
			
		||||
/// 使用<see cref="DynamicPropertyAttribute"/> 标识所需的配置属性
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class MempryDevicePropertyBase : CollectPropertyBase
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// <para></para>
 | 
			
		||||
/// 采集插件,继承实现不同PLC通讯
 | 
			
		||||
/// <para></para>
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class MempryDevice : CollectBase
 | 
			
		||||
{
 | 
			
		||||
    private MempryDevicePropertyBase _driverPropertyBase = new MempryDevicePropertyBase();
 | 
			
		||||
    public override CollectPropertyBase CollectProperties => _driverPropertyBase;
 | 
			
		||||
 | 
			
		||||
#if !Management
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 是否连接成功
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public override bool IsConnected()
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected override Task<List<VariableSourceRead>> ProtectedLoadSourceReadAsync(List<VariableRuntime> deviceVariables)
 | 
			
		||||
    {
 | 
			
		||||
        return Task.FromResult(new List<VariableSourceRead>());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected override bool IsRuntimeSourceValid(VariableRuntime a)
 | 
			
		||||
    {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
@@ -409,7 +409,7 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
 | 
			
		||||
                 // 查找具有指定设备ID的驱动程序对象
 | 
			
		||||
                 if (Drivers.TryRemove(deviceId, out var driver))
 | 
			
		||||
                 {
 | 
			
		||||
                     driver.CurrentDevice.SetDeviceStatus(now, false, "Communication connection has been removed");
 | 
			
		||||
                     driver.CurrentDevice.SetDeviceStatus(now, true, "Communication connection has been removed");
 | 
			
		||||
                     if (IsCollectChannel == true)
 | 
			
		||||
                     {
 | 
			
		||||
                         foreach (var a in driver.IdVariableRuntimes)
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,9 @@
 | 
			
		||||
		<PackageReference Include="Riok.Mapperly" Version="4.2.1" ExcludeAssets="runtime" PrivateAssets="all" />
 | 
			
		||||
		<PackageReference Include="Rougamo.Fody" Version="5.0.1" />
 | 
			
		||||
		<PackageReference Include="System.Linq.Async" Version="6.0.3" />
 | 
			
		||||
		<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-beta.25" />
 | 
			
		||||
		<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.25" />-->
 | 
			
		||||
		<PackageReference Include="TouchSocket.WebApi" Version="4.0.0-beta.25" />
 | 
			
		||||
		<PackageReference Include="TouchSocket.Dmtp" Version="4.0.0-beta.27" />
 | 
			
		||||
		<!--<PackageReference Include="TouchSocket.WebApi.Swagger" Version="4.0.0-beta.27" />-->
 | 
			
		||||
		<PackageReference Include="TouchSocket.WebApi" Version="4.0.0-beta.27" />
 | 
			
		||||
		<PackageReference Include="ThingsGateway.Authentication" Version="$(AuthenticationVersion)" />
 | 
			
		||||
		<!--<ProjectReference Include="..\..\PluginPro\ThingsGateway.Authentication\ThingsGateway.Authentication.csproj" />-->
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@
 | 
			
		||||
}
 | 
			
		||||
    /*切换高度*/
 | 
			
		||||
    .quickactions-list.is-open {
 | 
			
		||||
        height: 300px;
 | 
			
		||||
        height: 200px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
.quickactions-header {
 | 
			
		||||
 
 | 
			
		||||
@@ -76,18 +76,34 @@ public partial class DeviceRuntimeInfo1 : IDisposable
 | 
			
		||||
        {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        await DialogService.Show(new DialogOption()
 | 
			
		||||
        {
 | 
			
		||||
            IsScrolling = false,
 | 
			
		||||
            ShowMaximizeButton = true,
 | 
			
		||||
            Size = Size.ExtraExtraLarge,
 | 
			
		||||
            Title = DeviceRuntime.Name,
 | 
			
		||||
            Component = BootstrapDynamicComponent.CreateComponent(driver, new Dictionary<string, object?>()
 | 
			
		||||
        var renderFragment = BootstrapDynamicComponent.CreateComponent(driver, new Dictionary<string, object?>()
 | 
			
		||||
        {
 | 
			
		||||
            {nameof(IDriverUIBase.Driver),DeviceRuntime.Driver},
 | 
			
		||||
        })
 | 
			
		||||
        });
 | 
			
		||||
        }).Render();
 | 
			
		||||
        if (renderFragment != null)
 | 
			
		||||
        {
 | 
			
		||||
            var option = new WinBoxOption()
 | 
			
		||||
            {
 | 
			
		||||
                Title = DeviceRuntime.Name,
 | 
			
		||||
                ContentTemplate = renderFragment,
 | 
			
		||||
                Max = false,
 | 
			
		||||
                Width = "80%",
 | 
			
		||||
                Height = "80%",
 | 
			
		||||
                Top = "0%",
 | 
			
		||||
                Left = "10%",
 | 
			
		||||
                Background = "var(--bb-primary-color)",
 | 
			
		||||
                Overflow = true
 | 
			
		||||
            };
 | 
			
		||||
            await WinBoxService.Show(option);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Inject]
 | 
			
		||||
    [NotNull]
 | 
			
		||||
    private WinBoxService? WinBoxService { get; set; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    [Inject]
 | 
			
		||||
 
 | 
			
		||||
@@ -48,8 +48,22 @@ public partial class GatewayMonitorPage
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)
 | 
			
		||||
                if (deviceRuntime.Driver == null)
 | 
			
		||||
                {
 | 
			
		||||
                    _ = Task.Run(async () =>
 | 
			
		||||
                    {
 | 
			
		||||
                        await Task.Delay(2000);
 | 
			
		||||
                        VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)
 | 
			
		||||
.Select(a => a.Value) ?? Enumerable.Empty<VariableRuntime>();
 | 
			
		||||
                        await InvokeAsync(StateHasChanged);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    VariableRuntimes = deviceRuntime.Driver?.IdVariableRuntimes?.Where(a => a.Value != null)
 | 
			
		||||
.Select(a => a.Value) ?? Enumerable.Empty<VariableRuntime>();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            ChannelRuntimes = Enumerable.Repeat(deviceRuntime.ChannelRuntime, 1);
 | 
			
		||||
            DeviceRuntimes = Enumerable.Repeat(deviceRuntime, 1);
 | 
			
		||||
 
 | 
			
		||||
@@ -28,15 +28,7 @@ public partial class VariableRuntimeInfo : IDisposable
 | 
			
		||||
 | 
			
		||||
    [Parameter]
 | 
			
		||||
    public IEnumerable<VariableRuntime>? Items { get; set; } = Enumerable.Empty<VariableRuntime>();
 | 
			
		||||
    private IEnumerable<VariableRuntime>? _previousItemsRef;
 | 
			
		||||
    protected override async Task OnParametersSetAsync()
 | 
			
		||||
    {
 | 
			
		||||
        if (!ReferenceEquals(_previousItemsRef, Items))
 | 
			
		||||
        {
 | 
			
		||||
            _previousItemsRef = Items;
 | 
			
		||||
            await Refresh(null);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    private static void BeforeShowEditDialogCallback(ITableEditDialogOption<VariableRuntime> tableEditDialogOption)
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,13 @@ using BenchmarkConsoleApp;
 | 
			
		||||
using BenchmarkDotNet.Attributes;
 | 
			
		||||
using BenchmarkDotNet.Diagnosers;
 | 
			
		||||
 | 
			
		||||
using HslCommunication.ModBus;
 | 
			
		||||
 | 
			
		||||
using Longbow.Modbus;
 | 
			
		||||
using Longbow.TcpSocket;
 | 
			
		||||
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
 | 
			
		||||
using System.IO.Pipelines;
 | 
			
		||||
using System.Net.Sockets;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.Modbus;
 | 
			
		||||
@@ -30,10 +35,18 @@ namespace ThingsGateway.Foundation;
 | 
			
		||||
[MemoryDiagnoser]
 | 
			
		||||
public class ModbusBenchmark : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    private readonly List<IModbusClient> _lgbModbusClients = [];
 | 
			
		||||
    private List<ModbusMaster> thingsgatewaymodbuss = new();
 | 
			
		||||
    private List<IModbusMaster> nmodbuss = new();
 | 
			
		||||
    private List<ModbusTcpNet> modbusTcpNets = new();
 | 
			
		||||
    //private List<ModbusTcpNet> modbusTcpNets = new();
 | 
			
		||||
    private List<ModbusTcpMaster> modbusTcpMasters = new();
 | 
			
		||||
    private PipeOptions GetNoDelayPipeOptions()
 | 
			
		||||
    {
 | 
			
		||||
        return new PipeOptions(
 | 
			
		||||
            readerScheduler: PipeScheduler.Inline,
 | 
			
		||||
            writerScheduler: PipeScheduler.Inline,
 | 
			
		||||
            useSynchronizationContext: false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ModbusBenchmark()
 | 
			
		||||
    {
 | 
			
		||||
@@ -41,7 +54,12 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            var clientConfig = new TouchSocket.Core.TouchSocketConfig();
 | 
			
		||||
            var clientChannel = clientConfig.GetTcpClientWithIPHost(new ChannelOptions() { RemoteUrl = "127.0.0.1:502", MaxConcurrentCount = 10 });
 | 
			
		||||
            //clientConfig.SetTransportOption(new TouchSocket.Sockets.TransportOption()
 | 
			
		||||
            //{
 | 
			
		||||
            //    ReceivePipeOptions = GetNoDelayPipeOptions(),
 | 
			
		||||
            //    SendPipeOptions = GetNoDelayPipeOptions(),
 | 
			
		||||
            //}).SetNoDelay(true);
 | 
			
		||||
            var clientChannel = clientConfig.GetTcpClient(new ChannelOptions() { RemoteUrl = "127.0.0.1:502", MaxConcurrentCount = 10 });
 | 
			
		||||
            var thingsgatewaymodbus = new ModbusMaster()
 | 
			
		||||
            {
 | 
			
		||||
                //modbus协议格式
 | 
			
		||||
@@ -64,15 +82,15 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
            nmodbus.ReadHoldingRegistersAsync(1, 0, 100).GetFalseAwaitResult();
 | 
			
		||||
            nmodbuss.Add(nmodbus);
 | 
			
		||||
        }
 | 
			
		||||
        for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
        {
 | 
			
		||||
            ModbusTcpNet modbusTcpNet = new();
 | 
			
		||||
            modbusTcpNet.IpAddress = "127.0.0.1";
 | 
			
		||||
            modbusTcpNet.Port = 502;
 | 
			
		||||
            modbusTcpNet.ConnectServer();
 | 
			
		||||
            modbusTcpNet.ReadAsync("0", 100).GetFalseAwaitResult();
 | 
			
		||||
            modbusTcpNets.Add(modbusTcpNet);
 | 
			
		||||
        }
 | 
			
		||||
        //for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
        //{
 | 
			
		||||
        //    ModbusTcpNet modbusTcpNet = new();
 | 
			
		||||
        //    modbusTcpNet.IpAddress = "127.0.0.1";
 | 
			
		||||
        //    modbusTcpNet.Port = 502;
 | 
			
		||||
        //    modbusTcpNet.ConnectServer();
 | 
			
		||||
        //    modbusTcpNet.ReadAsync("0", 100).GetFalseAwaitResult();
 | 
			
		||||
        //    modbusTcpNets.Add(modbusTcpNet);
 | 
			
		||||
        //}
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
        {
 | 
			
		||||
@@ -83,12 +101,50 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
            client.ReadHoldingRegistersAsync(0, 100).GetFalseAwaitResult();
 | 
			
		||||
            modbusTcpMasters.Add(client);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            var sc = new ServiceCollection();
 | 
			
		||||
            sc.AddTcpSocketFactory();
 | 
			
		||||
            sc.AddModbusFactory();
 | 
			
		||||
 | 
			
		||||
            var provider = sc.BuildServiceProvider();
 | 
			
		||||
            var factory = provider.GetRequiredService<IModbusFactory>();
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var client = factory.GetOrCreateTcpMaster();
 | 
			
		||||
                client.ConnectAsync("127.0.0.1", 502).GetAwaiter().GetResult();
 | 
			
		||||
                client.ReadHoldingRegistersAsync(0x01, 0x00, 10).GetAwaiter().GetResult();
 | 
			
		||||
 | 
			
		||||
                _lgbModbusClients.Add(client);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    [Benchmark]
 | 
			
		||||
    public async Task LongbowModbus()
 | 
			
		||||
    {
 | 
			
		||||
        List<Task> tasks = new List<Task>();
 | 
			
		||||
        foreach (var _lgbModbusClient in _lgbModbusClients)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < Program.TaskNumberOfItems; i++)
 | 
			
		||||
            {
 | 
			
		||||
                tasks.Add(Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var task = await _lgbModbusClient.ReadHoldingRegistersAsync(1, 0, 100);
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        await Task.WhenAll(tasks);
 | 
			
		||||
    }
 | 
			
		||||
    [Benchmark]
 | 
			
		||||
    public async Task ThingsGateway()
 | 
			
		||||
    {
 | 
			
		||||
        ModbusAddress addr = new ModbusAddress() { FunctionCode = 3, StartAddress = 0, Length = 100 };
 | 
			
		||||
        //ModbusAddress addr = new ModbusAddress() { FunctionCode = 3, StartAddress = 0, Length = 100 };
 | 
			
		||||
        List<Task> tasks = new List<Task>();
 | 
			
		||||
        foreach (var thingsgatewaymodbus in thingsgatewaymodbuss)
 | 
			
		||||
        {
 | 
			
		||||
@@ -99,11 +155,12 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
                {
 | 
			
		||||
                    for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = await thingsgatewaymodbus.ModbusReadAsync(addr);
 | 
			
		||||
                        var result = await thingsgatewaymodbus.ModbusReadAsync(new ModbusAddress() { FunctionCode = 3, StartAddress = 0, Length = 100 });
 | 
			
		||||
                        if (!result.IsSuccess)
 | 
			
		||||
                        {
 | 
			
		||||
                            throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.ToString());
 | 
			
		||||
                        }
 | 
			
		||||
                        var data = TouchSocketBitConverter.ConvertValues<byte, ushort>(result.Content.Span, EndianType.Little);
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
@@ -126,6 +183,7 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
                    for (int i = 0; i < Program.NumberOfItems; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = await modbusTcpMaster.ReadHoldingRegistersAsync(0, 100);
 | 
			
		||||
                        var data = TouchSocketBitConverter.ConvertValues<byte, ushort>(result.Data.Span, EndianType.Little);
 | 
			
		||||
                        if (!result.IsSuccess)
 | 
			
		||||
                        {
 | 
			
		||||
                            throw new Exception(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + result.ToString());
 | 
			
		||||
@@ -186,9 +244,12 @@ public class ModbusBenchmark : IDisposable
 | 
			
		||||
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        thingsgatewaymodbuss?.ForEach(a => a.Channel.SafeDispose());
 | 
			
		||||
        thingsgatewaymodbuss?.ForEach(a => a.SafeDispose());
 | 
			
		||||
        nmodbuss?.ForEach(a => a.SafeDispose());
 | 
			
		||||
        modbusTcpNets?.ForEach(a => a.SafeDispose());
 | 
			
		||||
        //modbusTcpNets?.ForEach(a => a.SafeDispose());
 | 
			
		||||
        _lgbModbusClients?.ForEach(a => a.DisposeAsync().GetAwaiter().GetResult());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -17,6 +17,8 @@ using HslCommunication.Profinet.Siemens;
 | 
			
		||||
 | 
			
		||||
using S7.Net;
 | 
			
		||||
 | 
			
		||||
using System.IO.Pipelines;
 | 
			
		||||
 | 
			
		||||
using ThingsGateway.Foundation.SiemensS7;
 | 
			
		||||
 | 
			
		||||
using TouchSocket.Core;
 | 
			
		||||
@@ -30,7 +32,13 @@ public class S7Benchmark : IDisposable
 | 
			
		||||
 | 
			
		||||
    private List<Plc> plcs = new();
 | 
			
		||||
    private List<SiemensS7Net> siemensS7Nets = new();
 | 
			
		||||
 | 
			
		||||
    private PipeOptions GetNoDelayPipeOptions()
 | 
			
		||||
    {
 | 
			
		||||
        return new PipeOptions(
 | 
			
		||||
            readerScheduler: PipeScheduler.Inline,
 | 
			
		||||
            writerScheduler: PipeScheduler.Inline,
 | 
			
		||||
            useSynchronizationContext: false);
 | 
			
		||||
    }
 | 
			
		||||
    public S7Benchmark()
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
@@ -38,7 +46,12 @@ public class S7Benchmark : IDisposable
 | 
			
		||||
            for (int i = 0; i < Program.ClientCount; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var clientConfig = new TouchSocket.Core.TouchSocketConfig();
 | 
			
		||||
                var clientChannel = clientConfig.GetTcpClientWithIPHost(new ChannelOptions() { RemoteUrl = "127.0.0.1:102" });
 | 
			
		||||
                clientConfig.SetTransportOption(new TouchSocket.Sockets.TransportOption()
 | 
			
		||||
                {
 | 
			
		||||
                    ReceivePipeOptions = GetNoDelayPipeOptions(),
 | 
			
		||||
                    SendPipeOptions = GetNoDelayPipeOptions(),
 | 
			
		||||
                }).SetNoDelay(true);
 | 
			
		||||
                var clientChannel = clientConfig.GetTcpClient(new ChannelOptions() { RemoteUrl = "127.0.0.1:102" });
 | 
			
		||||
                var siemensS7 = new SiemensS7Master()
 | 
			
		||||
                {
 | 
			
		||||
                    //modbus协议格式
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ namespace BenchmarkConsoleApp
 | 
			
		||||
    internal class Program
 | 
			
		||||
    {
 | 
			
		||||
        public static int ClientCount = 30;
 | 
			
		||||
        public static int TaskNumberOfItems = 30;
 | 
			
		||||
        public static int TaskNumberOfItems = 10;
 | 
			
		||||
        public static int NumberOfItems = 30;
 | 
			
		||||
 | 
			
		||||
        private static async Task Main(string[] args)
 | 
			
		||||
@@ -49,10 +49,10 @@ namespace BenchmarkConsoleApp
 | 
			
		||||
       ManualConfig.Create(DefaultConfig.Instance)
 | 
			
		||||
           .WithOptions(ConfigOptions.DisableOptimizationsValidator)
 | 
			
		||||
   );
 | 
			
		||||
            BenchmarkRunner.Run<S7Benchmark>(
 | 
			
		||||
ManualConfig.Create(DefaultConfig.Instance)
 | 
			
		||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
 | 
			
		||||
);
 | 
			
		||||
            //            BenchmarkRunner.Run<S7Benchmark>(
 | 
			
		||||
            //ManualConfig.Create(DefaultConfig.Instance)
 | 
			
		||||
            //.WithOptions(ConfigOptions.DisableOptimizationsValidator)
 | 
			
		||||
            //);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -42,13 +42,14 @@
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
		<PackageReference Include="BenchmarkDotNet" Version="0.15.2" />
 | 
			
		||||
		<PackageReference Include="HslCommunication" Version="12.3.3" />
 | 
			
		||||
		<PackageReference Include="HslCommunication" Version="12.5.0" />
 | 
			
		||||
		<PackageReference Include="Longbow.Modbus" Version="9.0.3" />
 | 
			
		||||
		<PackageReference Include="NModbus" Version="3.0.81" />
 | 
			
		||||
		<PackageReference Include="NModbus.Serial" Version="3.0.81" />
 | 
			
		||||
		<PackageReference Include="S7netplus" Version="0.20.0" />
 | 
			
		||||
		<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="10.11.32" />
 | 
			
		||||
		<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="10.11.32" />
 | 
			
		||||
		<PackageReference Include="TouchSocket.Modbus" Version="4.0.0-beta.25" />
 | 
			
		||||
		<PackageReference Include="ThingsGateway.Foundation.Modbus" Version="$(DefaultVersion)" />
 | 
			
		||||
		<PackageReference Include="ThingsGateway.Foundation.SiemensS7" Version="$(DefaultVersion)" />
 | 
			
		||||
		<PackageReference Include="TouchSocket.Modbus" Version="4.0.0-beta.27" />
 | 
			
		||||
	</ItemGroup>
 | 
			
		||||
 | 
			
		||||
	<ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -186,7 +186,7 @@ public class Dlt645_2007Send : ISendMessage
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var lenSpan = writerLenAnchor.Rewind(ref byteBlock, out var length);
 | 
			
		||||
        lenSpan.WriteValue<byte>((byte)(length - 1));//数据域长度
 | 
			
		||||
        lenSpan.WriteValue<byte>((byte)(length));//数据域长度
 | 
			
		||||
 | 
			
		||||
        int num = 0;
 | 
			
		||||
        for (int index = 0; index < byteBlock.WrittenCount - SendHeadCodeIndex; ++index)
 | 
			
		||||
 
 | 
			
		||||
@@ -126,7 +126,7 @@ public partial class MainLayout : IDisposable
 | 
			
		||||
        if (context is TabItem tabItem)
 | 
			
		||||
        {
 | 
			
		||||
            await WinboxRender(tabItem.ChildContent, tabItem.Text);
 | 
			
		||||
            await _tab.RemoveTab(tabItem);
 | 
			
		||||
            //await _tab.RemoveTab(tabItem);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    [Inject]
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,8 @@
 | 
			
		||||
	<!--<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\Pro7.targets" Condition=" '$(SolutionName)' == 'ThingsGatewayPro' AND '$(Configuration)' != 'Debug'" />
 | 
			
		||||
	<!--nuget包解压复制文件,上下文动态加载,Pro插件-->
 | 
			
		||||
	<Import Project="targets\Pro7.targets" Condition=" '$(SolutionName)' != 'ThingsGatewayPro' OR  '$(Configuration)' != 'Debug'" />
 | 
			
		||||
 | 
			
		||||
	<!--打包复制-->
 | 
			
		||||
	<Import Project="targets\PluginPublish.targets" />
 | 
			
		||||
 
 | 
			
		||||
@@ -113,6 +113,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "debug", "debug", "{053AB5FA
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Foundation.Demo", "Foundation\ThingsGateway.Foundation.Demo\ThingsGateway.Foundation.Demo.csproj", "{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}"
 | 
			
		||||
EndProject
 | 
			
		||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThingsGateway.Foundation.Benchmark", "Plugin\ThingsGateway.Foundation.Benchmark\ThingsGateway.Foundation.Benchmark.csproj", "{B0957BD6-CF77-36E7-B657-2D0DB85F386F}"
 | 
			
		||||
EndProject
 | 
			
		||||
Global
 | 
			
		||||
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
			
		||||
		Debug|Any CPU = Debug|Any CPU
 | 
			
		||||
@@ -287,6 +289,10 @@ Global
 | 
			
		||||
		{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
		{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
			
		||||
		{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Debug|Any CPU.Build.0 = Debug|Any CPU
 | 
			
		||||
		{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
			
		||||
		{B0957BD6-CF77-36E7-B657-2D0DB85F386F}.Release|Any CPU.Build.0 = Release|Any CPU
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
	GlobalSection(SolutionProperties) = preSolution
 | 
			
		||||
		HideSolutionNode = FALSE
 | 
			
		||||
@@ -334,10 +340,11 @@ Global
 | 
			
		||||
		{E6EF2033-F02A-CDAD-5A72-EE397A89742E} = {36510D70-161F-4241-B8D0-781E21032816}
 | 
			
		||||
		{053AB5FA-9742-96EC-76A1-2AEC739860C6} = {36510D70-161F-4241-B8D0-781E21032816}
 | 
			
		||||
		{520DEEAA-1CBD-C0CB-2363-EB190D7DE4EA} = {2AC600BB-4325-4E0A-93A7-B1F53C8E2CA7}
 | 
			
		||||
		{B0957BD6-CF77-36E7-B657-2D0DB85F386F} = {1D9CD7A3-9700-A851-0ABD-183347D9CC33}
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
	GlobalSection(ExtensibilityGlobals) = postSolution
 | 
			
		||||
		SolutionGuid = {199B1B96-4F56-4828-9531-813BA02DB282}
 | 
			
		||||
		RESX_Rules = {"EnabledRules":[]}
 | 
			
		||||
		RESX_NeutralResourcesLanguage = zh-Hans
 | 
			
		||||
		RESX_Rules = {"EnabledRules":[]}
 | 
			
		||||
	EndGlobalSection
 | 
			
		||||
EndGlobal
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user