diff --git a/src/Admin/ThingsGateway.Razor/ThingsGateway.Razor.csproj b/src/Admin/ThingsGateway.Razor/ThingsGateway.Razor.csproj
index 0e3a2f484..f77318abf 100644
--- a/src/Admin/ThingsGateway.Razor/ThingsGateway.Razor.csproj
+++ b/src/Admin/ThingsGateway.Razor/ThingsGateway.Razor.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 0ca8d7bdb..8ca5dfde6 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,8 +1,8 @@
- 10.8.22
- 10.8.22
+ 10.8.23
+ 10.8.23
2.8.4
10.8.6
8.0.17
diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Locales/en-US.json b/src/Gateway/ThingsGateway.Gateway.Razor/Locales/en-US.json
index cd142cfa3..ada9a5704 100644
--- a/src/Gateway/ThingsGateway.Gateway.Razor/Locales/en-US.json
+++ b/src/Gateway/ThingsGateway.Gateway.Razor/Locales/en-US.json
@@ -1,4 +1,22 @@
{
+ "ThingsGateway.Gateway.Razor.ValueTransformType": {
+ "None": "None",
+ "Linear": "Linear",
+ "Sqrt": "Sqrt"
+ },
+
+ "ThingsGateway.Gateway.Razor.ValueTransformConfig": {
+ "TransformType": "TransformType",
+ "MinMax": "MinMax",
+ "ClampToRawRange": "ClampToRawRange",
+ "DecimalPlaces": "DecimalPlaces",
+ "RawMin": "RawMin",
+ "RawMax": "RawMax",
+ "ActualMin": "ActualMin",
+ "ActualMax": "ActualMax"
+ },
+
+
"ThingsGateway.Management.Authentication": {
"AuthName": "AuthName",
"Authorized": "Authorized",
diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Locales/zh-CN.json b/src/Gateway/ThingsGateway.Gateway.Razor/Locales/zh-CN.json
index 91aa4fa6a..179fd275e 100644
--- a/src/Gateway/ThingsGateway.Gateway.Razor/Locales/zh-CN.json
+++ b/src/Gateway/ThingsGateway.Gateway.Razor/Locales/zh-CN.json
@@ -1,4 +1,20 @@
{
+ "ThingsGateway.Gateway.Razor.ValueTransformType": {
+ "None": "无",
+ "Linear": "线性",
+ "Sqrt": "开方"
+ },
+ "ThingsGateway.Gateway.Razor.ValueTransformConfig": {
+ "TransformType": "转换方式",
+ "MinMax": "最小最大值",
+ "ClampToRawRange": "限制范围",
+ "DecimalPlaces": "保留小数位",
+ "RawMin": "原始最小值",
+ "RawMax": "原始最大值",
+ "ActualMin": "实际最小值",
+ "ActualMax": "实际最大值"
+ },
+
"ThingsGateway.Management.Authentication": {
"AuthName": "公司名称",
"Authorized": "已授权",
diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfig.cs b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfig.cs
new file mode 100644
index 000000000..f1f841e22
--- /dev/null
+++ b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfig.cs
@@ -0,0 +1,62 @@
+//------------------------------------------------------------------------------
+// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
+// 此代码版权(除特别声明外的代码)归作者本人Diego所有
+// 源代码使用协议遵循本仓库的开源协议及附加协议
+// Gitee源代码仓库:https://gitee.com/diego2098/ThingsGateway
+// Github源代码仓库:https://github.com/kimdiego2098/ThingsGateway
+// 使用文档:https://thingsgateway.cn/
+// QQ群:605534569
+//------------------------------------------------------------------------------
+
+namespace ThingsGateway.Gateway.Razor;
+
+public class ValueTransformConfig
+{
+ ///
+ /// 保留小数位
+ ///
+ public int DecimalPlaces { get; set; } = 2;
+
+
+ ///
+ /// 限制范围
+ ///
+ public bool ClampToRawRange { get; set; }
+
+ public ValueTransformType TransformType { get; set; }
+
+ ///
+ /// 原始低
+ ///
+ public decimal RawMin { get; set; }
+ ///
+ /// 原始高
+ ///
+ public decimal RawMax { get; set; }
+ ///
+ /// 实际低
+ ///
+ public decimal ActualMin { get; set; }
+ ///
+ /// 实际高
+ ///
+ public decimal ActualMax { get; set; }
+
+
+}
+
+public enum ValueTransformType
+{
+ ///
+ /// 不转换,仅保留小数位
+ ///
+ None,
+ ///
+ /// 线性转换
+ ///
+ Linear,
+ ///
+ /// 开方转换
+ ///
+ Sqrt
+}
diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfigPage.razor b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfigPage.razor
new file mode 100644
index 000000000..8a6d18910
--- /dev/null
+++ b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfigPage.razor
@@ -0,0 +1,31 @@
+@namespace ThingsGateway.Gateway.Razor
+@using ThingsGateway.Admin.Application
+@using ThingsGateway.Admin.Razor
+@using ThingsGateway.Foundation
+@using ThingsGateway.Gateway.Application
+@inherits ComponentDefault
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfigPage.razor.cs b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfigPage.razor.cs
new file mode 100644
index 000000000..1ebf2426f
--- /dev/null
+++ b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfigPage.razor.cs
@@ -0,0 +1,215 @@
+//------------------------------------------------------------------------------
+// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
+// 此代码版权(除特别声明外的代码)归作者本人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.Globalization;
+using System.Text.RegularExpressions;
+
+using ThingsGateway.NewLife.Extension;
+
+namespace ThingsGateway.Gateway.Razor;
+
+public partial class ValueTransformConfigPage
+{
+ private ValueTransformConfig ValueTransformConfig = new();
+
+ [Inject]
+ [NotNull]
+ private IStringLocalizer? ValueTransformConfigLocalizer { get; set; }
+
+ [Parameter]
+ public string Expressions { get; set; }
+
+
+ [Parameter]
+ public EventCallback ExpressionsChanged { get; set; }
+
+ public static bool TryParseLinearFormula(string formula, out ValueTransformConfig config)
+ {
+ config = new ValueTransformConfig();
+ var dec = @"[\d]+(?:\.[\d]+)?";
+
+ try
+ {
+ // None + clamp actual
+ var m = Regex.Match(formula, $@"^Math\.Round\(\s*
+ Math\.Min\(\s*
+ Math\.Max\(\s*
+ raw\.ToDecimal\(\)\s*,\s*({dec})\s*\)\s*,\s*({dec})\s*\)\s*,\s*(\d+)\s*\)$", RegexOptions.IgnorePatternWhitespace);
+ if (m.Success)
+ {
+ config.TransformType = ValueTransformType.None;
+ config.ClampToRawRange = true;
+ config.ActualMin = decimal.Parse(m.Groups[1].Value);
+ config.ActualMax = decimal.Parse(m.Groups[2].Value);
+ config.DecimalPlaces = int.Parse(m.Groups[3].Value);
+ return true;
+ }
+
+ // None pure
+ m = Regex.Match(formula, $@"^Math\.Round\(\s*raw\.ToDecimal\(\)\s*,\s*(\d+)\s*\)$");
+ if (m.Success)
+ {
+ config.TransformType = ValueTransformType.None;
+ config.ClampToRawRange = false;
+ config.DecimalPlaces = int.Parse(m.Groups[1].Value);
+ return true;
+ }
+
+ // Linear + clamp actual
+
+ m = Regex.Match(formula, $@"^Math\.Round\(\s*
+ Math\.Min\(\s*
+ Math\.Max\(\s*
+ \(\(raw\.ToDecimal\(\)\s*-\s*({dec})\)\s*/\s*
+ \(({dec})\s*-\s*({dec})\)\s*\*\s*
+ \(({dec})\s*-\s*({dec})\)\s*\+\s*
+ ({dec})\)\s*,\s*({dec})\)\s*,\s*({dec})\)\s*,\s*(\d+)\s*\)$", RegexOptions.IgnorePatternWhitespace);
+ if (m.Success)
+ {
+ config.TransformType = ValueTransformType.Linear;
+ config.ClampToRawRange = true;
+ config.RawMin = decimal.Parse(m.Groups[1].Value);
+ config.RawMax = decimal.Parse(m.Groups[2].Value);
+ config.ActualMax = decimal.Parse(m.Groups[4].Value);
+ config.ActualMin = decimal.Parse(m.Groups[5].Value);
+ config.DecimalPlaces = int.Parse(m.Groups[9].Value);
+ return true;
+ }
+
+ // Linear pure
+ m = Regex.Match(formula, $@"^Math\.Round\(\s*
+ \(\(raw\.ToDecimal\(\)\s*-\s*({dec})\)\s*/\s*
+ \(({dec})\s*-\s*({dec})\)\s*\*\s*
+ \(({dec})\s*-\s*({dec})\)\s*\+\s*({dec})\)\s*,\s*(\d+)\s*\)$", RegexOptions.IgnorePatternWhitespace);
+
+ if (m.Success)
+ {
+ config.TransformType = ValueTransformType.Linear;
+ config.ClampToRawRange = false;
+ config.RawMin = decimal.Parse(m.Groups[1].Value); // raw减数(0)
+ config.RawMax = decimal.Parse(m.Groups[2].Value); // 分母第一个数(10)
+ config.ActualMax = decimal.Parse(m.Groups[4].Value); // 乘数第一个数(1)
+ config.ActualMin = decimal.Parse(m.Groups[6].Value); // 加数(0)
+ config.DecimalPlaces = int.Parse(m.Groups[7].Value); // 小数位(2)
+ return true;
+ }
+
+ // Sqrt + clamp actual
+ m = Regex.Match(formula, $@"^Math\.Round\(\s*
+ Math\.Min\(\s*
+ Math\.Max\(\s*
+ Math\.Sqrt\(Math\.Max\(raw\.ToDecimal\(\),\s*0\)\)\s*\*\s*({dec})\s*,\s*({dec})\)\s*,\s*({dec})\)\s*,\s*(\d+)\s*\)$", RegexOptions.IgnorePatternWhitespace);
+ if (m.Success)
+ {
+ config.TransformType = ValueTransformType.Sqrt;
+ config.ClampToRawRange = true;
+ config.ActualMax = decimal.Parse(m.Groups[1].Value);
+ config.ActualMin = decimal.Parse(m.Groups[2].Value);
+ config.DecimalPlaces = int.Parse(m.Groups[4].Value);
+ return true;
+ }
+
+ // Sqrt pure
+ m = Regex.Match(formula, $@"^Math\.Round\(\s*
+ Math\.Sqrt\(Math\.Max\(raw\.ToDecimal\(\),\s*0\)\)\s*\*\s*({dec})\s*,\s*(\d+)\s*\)$", RegexOptions.IgnorePatternWhitespace);
+ if (m.Success)
+ {
+ config.TransformType = ValueTransformType.Sqrt;
+ config.ClampToRawRange = false;
+ config.DecimalPlaces = int.Parse(m.Groups[2].Value);
+ return true;
+ }
+ }
+ catch
+ {
+ // ignore
+ }
+
+ return false;
+ }
+
+ public static string GenerateFormula(ValueTransformConfig config)
+ {
+ // 只有 ClampToRawRange 为 true 时才包裹实际值范围限制
+ string clampActual(string expr)
+ {
+ if (config.ClampToRawRange)
+ return $"Math.Min(Math.Max({expr}, {config.ActualMin}), {config.ActualMax})";
+ else
+ return expr;
+ }
+
+ string rawExpr = "raw.ToDecimal()"; // 这里不做 raw clamp
+
+ switch (config.TransformType)
+ {
+ case ValueTransformType.None:
+ return $"Math.Round({clampActual(rawExpr)}, {config.DecimalPlaces})";
+
+ case ValueTransformType.Linear:
+ var linearExpr = $"(({rawExpr} - {config.RawMin}) / ({config.RawMax} - {config.RawMin}) * ({config.ActualMax} - {config.ActualMin}) + {config.ActualMin})";
+ return $"Math.Round({clampActual(linearExpr)}, {config.DecimalPlaces})";
+
+ case ValueTransformType.Sqrt:
+ var sqrtExpr = $"Math.Sqrt(Math.Max({rawExpr}, 0)) * {config.ActualMax}";
+ return $"Math.Round({clampActual(sqrtExpr)}, {config.DecimalPlaces})";
+
+ default:
+ throw new NotSupportedException($"Unsupported transform type: {config.TransformType}");
+ }
+ }
+
+
+
+ protected override void OnParametersSet()
+ {
+ if (!Expressions.IsNullOrWhiteSpace())
+ {
+ if (TryParseLinearFormula(Expressions, out var config))
+ {
+ ValueTransformConfig = config;
+ }
+ }
+ base.OnParametersSet();
+ }
+
+ #region 修改
+ [CascadingParameter]
+ private Func? OnCloseAsync { get; set; }
+
+
+
+ private async Task OnSave(EditContext editContext)
+ {
+ try
+ {
+ var result = GenerateFormula(ValueTransformConfig);
+ Expressions = result;
+ if (ExpressionsChanged.HasDelegate)
+ {
+ await ExpressionsChanged.InvokeAsync(result);
+ }
+ else
+ {
+ Expressions = result;
+ }
+ if (OnCloseAsync != null)
+ await OnCloseAsync();
+ }
+ catch (Exception ex)
+ {
+ await ToastService.Warn(ex);
+ }
+ }
+
+ #endregion 修改
+}
diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfigPage.razor.css b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfigPage.razor.css
new file mode 100644
index 000000000..d299a3952
--- /dev/null
+++ b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/ValueTransformConfigPage.razor.css
@@ -0,0 +1,7 @@
+.appconfig ::deep .tabs-body-content {
+ height: 100% !important;
+}
+
+.appconfig {
+ height: 100% !important;
+}
diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor
index 38fba7ae5..b9ed62246 100644
--- a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor
+++ b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor
@@ -113,8 +113,28 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor.cs b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor.cs
index ee823417b..eaf55352f 100644
--- a/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor.cs
+++ b/src/Gateway/ThingsGateway.Gateway.Razor/Pages/GatewayMonitorPage/Variable/VariableEditComponent.razor.cs
@@ -163,7 +163,37 @@ public partial class VariableEditComponent
op.Component = AddressDynamicComponent;
await DialogService.Show(op);
}
-
+ [Inject]
+ IStringLocalizer VariableLocalizer { get; set; }
+ private async Task ShowExpressionsUI(bool read)
+ {
+ var op = new DialogOption()
+ {
+ IsScrolling = false,
+ ShowMaximizeButton = true,
+ Size = Size.Large,
+ Title = $"{Model.Name} {(read ? VariableLocalizer[nameof(Variable.ReadExpressions)] : VariableLocalizer[nameof(Variable.WriteExpressions)])}",
+ ShowFooter = false,
+ ShowCloseButton = false,
+ BodyTemplate = BootstrapDynamicComponent.CreateComponent(new Dictionary
+ {
+ {nameof(ValueTransformConfigPage.ExpressionsChanged), EventCallback.Factory.Create(this,a =>
+ {
+ if(read)
+ {
+ Model.ReadExpressions = a;
+ }
+ else
+ {
+ Model.WriteExpressions=a;
+ }
+ })
+},
+ {nameof(ValueTransformConfigPage.Expressions),read?Model.ReadExpressions:Model.WriteExpressions },
+ }).Render(),
+ };
+ await DialogService.Show(op);
+ }
[Inject]
private IStringLocalizer DeviceLocalizer { get; set; }
diff --git a/src/Version.props b/src/Version.props
index 656613e67..aa2679f42 100644
--- a/src/Version.props
+++ b/src/Version.props
@@ -1,6 +1,6 @@
- 10.8.22
+ 10.8.23