Files
OrpaonVision/OrpaonVision.ConfigApp/ViewModels/MainWindowViewModel.cs
2026-04-06 22:04:05 +08:00

1668 lines
54 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using OrpaonVision.ConfigApp.Annotation.Options;
using OrpaonVision.Core.Annotation;
using OrpaonVision.Core.Annotation.Contracts;
using OrpaonVision.Core.Configuration;
using OrpaonVision.Core.Configuration.Contracts;
using OrpaonVision.Core.Enums;
using OrpaonVision.ConfigApp.Views;
using OrpaonVision.ConfigApp.Infrastructure.Services;
using Microsoft.Extensions.Logging;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Windows;
using System.Windows.Media;
using Microsoft.Extensions.DependencyInjection;
namespace OrpaonVision.ConfigApp.ViewModels;
/// <summary>
/// 主窗口 ViewModel。
///
/// 职责:
/// - 持有页面输入参数与输出状态;
/// - 统一编排“检查连通状态”和“发起同步”两类操作;
/// - 将服务层返回结果转换为界面可直接展示的文本。
/// </summary>
public sealed class MainWindowViewModel : INotifyPropertyChanged
{
private readonly IAnnotationSyncAppService _annotationSyncAppService;
private readonly IRuleConfigurationAppService _ruleConfigurationAppService;
private readonly CurrentUserContext _currentUserContext;
private readonly IServiceProvider _serviceProvider;
private string _projectIdText = string.Empty;
private string _annotationTaskIdText = string.Empty;
private string _cvatEndpointText = string.Empty;
private string _cvatProjectIdText = string.Empty;
private string _cvatTaskIdText = string.Empty;
private string _requestedByText = string.Empty;
private string _statusText = "等待操作";
private string _outputText = "请填写参数后执行“检查连通状态”或“发起同步”。";
private Brush _statusBrush = Brushes.DarkGreen;
private string _friendlyHintText = "请输入有效参数后执行操作。";
private Brush _friendlyHintBrush = Brushes.DimGray;
private string _detailTaskIdText = "-";
private string _detailTaskNameText = "-";
private string _detailTaskStatusText = "-";
private string _detailProjectIdText = "-";
private string _detailItemCountText = "-";
private string _detailUpdatedAtText = "-";
private string _ruleProductTypeCodeText = "MACHINE-A";
private string _ruleProductTypeNameText = "默认机种";
private RuleLayerInputRow? _selectedRuleLayer;
private RulePartInputRow? _selectedRulePart;
private RuleRoiInputRow? _selectedRuleRoi;
private RuleItemInputRow? _selectedRuleItem;
private string _publishedRuleVersionText = "-";
private string _publishedRuleAtText = "-";
private string _compareSourceVersionText = string.Empty;
private string _compareTargetVersionText = string.Empty;
private string _rollbackTargetVersionText = string.Empty;
private string _disableVersionNoText = string.Empty;
private string _versionDetailNoText = string.Empty;
private string _versionPageIndexText = "1";
private string _versionPageSizeText = "20";
private bool _isBusy;
/// <summary>
/// 主窗口 ViewModel 构造函数。
/// </summary>
/// <param name="annotationSyncAppService">标注同步应用服务。</param>
/// <param name="ruleConfigurationAppService">规则配置应用服务。</param>
/// <param name="currentUserContext">当前用户上下文。</param>
/// <param name="cvatOptions">CVAT 配置。</param>
/// <param name="serviceProvider">服务提供程序。</param>
public MainWindowViewModel(
IAnnotationSyncAppService annotationSyncAppService,
IRuleConfigurationAppService ruleConfigurationAppService,
CurrentUserContext currentUserContext,
CvatOptions cvatOptions,
IServiceProvider serviceProvider)
{
_annotationSyncAppService = annotationSyncAppService;
_ruleConfigurationAppService = ruleConfigurationAppService;
_currentUserContext = currentUserContext;
_serviceProvider = serviceProvider;
ProjectIdText = Guid.NewGuid().ToString("D");
AnnotationTaskIdText = Guid.NewGuid().ToString("D");
CvatEndpointText = cvatOptions.ServerEndpoint;
CvatProjectIdText = "1";
CvatTaskIdText = "1";
RequestedByText = Environment.UserName;
AddDefaultRuleRows();
}
/// <summary>
/// 保存规则草稿。
/// </summary>
public async Task SaveRuleDraftAsync()
{
if (string.IsNullOrWhiteSpace(RuleProductTypeCodeText) || string.IsNullOrWhiteSpace(RuleProductTypeNameText))
{
SetError("RULE_PRODUCT_TYPE_REQUIRED", "机种编码和名称不能为空。", Array.Empty<string>());
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在保存规则草稿...";
SetFriendlyHint("正在保存机种规则配置草稿,请稍候...", Brushes.SteelBlue);
if (!TryBuildRuleDraft(out var draft))
{
return;
}
var result = await _ruleConfigurationAppService.SaveDraftAsync(draft);
if (!result.Succeeded)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
StatusBrush = Brushes.DarkGreen;
StatusText = "规则草稿保存成功";
SetFriendlyHint("草稿已保存,可继续点击“发布规则版本”。", Brushes.DarkGreen);
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}{Environment.NewLine}" +
$"ProductTypeCode: {draft.ProductTypeCode}";
}
catch (Exception ex)
{
SetError("RULE_DRAFT_UI_EXCEPTION", "保存规则草稿出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
/// <summary>
/// 停用指定规则版本。
/// </summary>
public async Task DisableVersionAsync()
{
if (string.IsNullOrWhiteSpace(RuleProductTypeCodeText))
{
SetError("RULE_PRODUCT_CODE_REQUIRED", "机种编码不能为空。", Array.Empty<string>());
return;
}
var versionNo = DisableVersionNoText.Trim();
if (string.IsNullOrWhiteSpace(versionNo))
{
SetError("RULE_VERSION_NO_REQUIRED", "请输入要停用的版本号。", Array.Empty<string>());
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在停用规则版本...";
SetFriendlyHint("正在写入停用审计记录...", Brushes.SteelBlue);
var result = await _ruleConfigurationAppService.DisableVersionAsync(
RuleProductTypeCodeText.Trim(),
versionNo,
RequestedByText.Trim());
if (!result.Succeeded)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
StatusText = "规则版本停用成功";
StatusBrush = Brushes.DarkGreen;
SetFriendlyHint($"版本 {versionNo} 已停用。", Brushes.DarkGreen);
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}{Environment.NewLine}" +
$"ProductTypeCode: {RuleProductTypeCodeText.Trim()}{Environment.NewLine}" +
$"DisabledVersionNo: {versionNo}";
}
catch (Exception ex)
{
SetError("RULE_VERSION_DISABLE_UI_EXCEPTION", "停用规则版本出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
/// <summary>
/// 查询指定规则版本详情。
/// </summary>
public async Task QueryVersionDetailAsync()
{
if (string.IsNullOrWhiteSpace(RuleProductTypeCodeText))
{
SetError("RULE_PRODUCT_CODE_REQUIRED", "机种编码不能为空。", Array.Empty<string>());
return;
}
var versionNo = VersionDetailNoText.Trim();
if (string.IsNullOrWhiteSpace(versionNo))
{
SetError("RULE_VERSION_NO_REQUIRED", "请输入要查询详情的版本号。", Array.Empty<string>());
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在查询规则版本详情...";
SetFriendlyHint("正在读取版本快照与状态信息...", Brushes.SteelBlue);
var result = await _ruleConfigurationAppService.GetVersionDetailAsync(
RuleProductTypeCodeText.Trim(),
versionNo);
if (!result.Succeeded || result.Data is null)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
StatusText = "规则版本详情查询成功";
StatusBrush = Brushes.DarkGreen;
SetFriendlyHint($"版本 {versionNo} 详情已加载。", Brushes.DarkGreen);
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}{Environment.NewLine}" +
$"ProductTypeCode: {result.Data.ProductTypeCode}{Environment.NewLine}" +
$"VersionNo: {result.Data.VersionNo}{Environment.NewLine}" +
$"PublishedAtUtc: {result.Data.PublishedAtUtc:O}{Environment.NewLine}" +
$"PublishedBy: {result.Data.PublishedBy}{Environment.NewLine}" +
$"IsDisabled: {result.Data.IsDisabled}{Environment.NewLine}" +
$"DisabledAtUtc: {(result.Data.DisabledAtUtc?.ToString("O") ?? "-")}{Environment.NewLine}" +
$"DisabledBy: {(string.IsNullOrWhiteSpace(result.Data.DisabledBy) ? "-" : result.Data.DisabledBy)}{Environment.NewLine}" +
$"SnapshotLength: {result.Data.SnapshotJson.Length}";
}
catch (Exception ex)
{
SetError("RULE_VERSION_DETAIL_UI_EXCEPTION", "查询规则版本详情出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
/// <summary>
/// 分页查询规则版本列表。
/// </summary>
public async Task QueryVersionPagedListAsync()
{
if (string.IsNullOrWhiteSpace(RuleProductTypeCodeText))
{
SetError("RULE_PRODUCT_CODE_REQUIRED", "机种编码不能为空。", Array.Empty<string>());
return;
}
if (!int.TryParse(VersionPageIndexText.Trim(), out var pageIndex) || pageIndex < 1)
{
SetError("RULE_PAGE_INDEX_INVALID", "页码必须是大于 0 的整数。", Array.Empty<string>());
return;
}
if (!int.TryParse(VersionPageSizeText.Trim(), out var pageSize) || pageSize < 1 || pageSize > 100)
{
SetError("RULE_PAGE_SIZE_INVALID", "每页大小必须在 1-100 之间。", Array.Empty<string>());
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在分页查询规则版本...";
SetFriendlyHint("正在按页读取规则版本清单...", Brushes.SteelBlue);
RuleVersionRows.Clear();
var result = await _ruleConfigurationAppService.GetVersionPagedListAsync(
RuleProductTypeCodeText.Trim(),
pageIndex,
pageSize);
if (!result.Succeeded || result.Data is null)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
foreach (var item in result.Data.Items)
{
RuleVersionRows.Add(new RuleVersionListViewRow
{
VersionNo = item.VersionNo,
PublishedAtUtcText = item.PublishedAtUtc.ToString("O"),
PublishedBy = string.IsNullOrWhiteSpace(item.PublishedBy) ? "-" : item.PublishedBy,
IsDisabledText = item.IsDisabled ? "是" : "否",
DisabledAtUtcText = item.DisabledAtUtc?.ToString("O") ?? "-",
DisabledBy = string.IsNullOrWhiteSpace(item.DisabledBy) ? "-" : item.DisabledBy
});
}
StatusText = "规则版本分页查询成功";
StatusBrush = Brushes.DarkGreen;
SetFriendlyHint($"已加载第 {result.Data.PageIndex} 页,共 {result.Data.TotalCount} 条。", Brushes.DarkGreen);
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}{Environment.NewLine}" +
$"PageIndex: {result.Data.PageIndex}{Environment.NewLine}" +
$"PageSize: {result.Data.PageSize}{Environment.NewLine}" +
$"TotalCount: {result.Data.TotalCount}{Environment.NewLine}" +
$"TotalPages: {result.Data.TotalPages}{Environment.NewLine}" +
$"ItemCount: {result.Data.Items.Count}";
}
catch (Exception ex)
{
SetError("RULE_VERSION_PAGED_LIST_UI_EXCEPTION", "分页查询规则版本出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
/// <summary>
/// 发布规则版本。
/// </summary>
public async Task PublishRuleVersionAsync()
{
if (string.IsNullOrWhiteSpace(RuleProductTypeCodeText))
{
SetError("RULE_PRODUCT_CODE_REQUIRED", "机种编码不能为空。", Array.Empty<string>());
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在发布规则版本...";
SetFriendlyHint("正在将草稿固化为版本快照,请稍候...", Brushes.SteelBlue);
var result = await _ruleConfigurationAppService.PublishAsync(RuleProductTypeCodeText.Trim(), RequestedByText.Trim());
if (!result.Succeeded || result.Data is null)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
PublishedRuleVersionText = result.Data.VersionNo;
PublishedRuleAtText = result.Data.PublishedAtUtc.ToString("O");
StatusBrush = Brushes.DarkGreen;
StatusText = "规则版本发布成功";
SetFriendlyHint("已生成发布版本,可用于现场侧规则加载。", Brushes.DarkGreen);
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}{Environment.NewLine}" +
$"VersionNo: {result.Data.VersionNo}{Environment.NewLine}" +
$"PublishedAtUtc: {result.Data.PublishedAtUtc:O}";
}
catch (Exception ex)
{
SetError("RULE_PUBLISH_UI_EXCEPTION", "发布规则版本出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
/// <summary>
/// 属性变更事件。
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// 本地项目 ID 文本。
/// </summary>
public string ProjectIdText
{
get => _projectIdText;
set => SetField(ref _projectIdText, value);
}
/// <summary>
/// 本地标注任务 ID 文本。
/// </summary>
public string AnnotationTaskIdText
{
get => _annotationTaskIdText;
set => SetField(ref _annotationTaskIdText, value);
}
/// <summary>
/// CVAT 服务地址文本。
/// </summary>
public string CvatEndpointText
{
get => _cvatEndpointText;
set => SetField(ref _cvatEndpointText, value);
}
/// <summary>
/// CVAT 项目 ID 文本。
/// </summary>
public string CvatProjectIdText
{
get => _cvatProjectIdText;
set => SetField(ref _cvatProjectIdText, value);
}
/// <summary>
/// CVAT 任务 ID 文本。
/// </summary>
public string CvatTaskIdText
{
get => _cvatTaskIdText;
set => SetField(ref _cvatTaskIdText, value);
}
/// <summary>
/// 请求人文本。
/// </summary>
public string RequestedByText
{
get => _requestedByText;
set => SetField(ref _requestedByText, value);
}
/// <summary>
/// 当前用户ID。
/// </summary>
public Guid CurrentUserId => _currentUserContext.CurrentUserId;
/// <summary>
/// 状态栏文本。
/// </summary>
public string StatusText
{
get => _statusText;
set => SetField(ref _statusText, value);
}
/// <summary>
/// 结果详情文本。
/// </summary>
public string OutputText
{
get => _outputText;
set => SetField(ref _outputText, value);
}
/// <summary>
/// 状态文本颜色。
/// </summary>
public Brush StatusBrush
{
get => _statusBrush;
private set => SetField(ref _statusBrush, value);
}
/// <summary>
/// 友好提示文本。
/// </summary>
public string FriendlyHintText
{
get => _friendlyHintText;
private set => SetField(ref _friendlyHintText, value);
}
/// <summary>
/// 友好提示颜色。
/// </summary>
public Brush FriendlyHintBrush
{
get => _friendlyHintBrush;
private set => SetField(ref _friendlyHintBrush, value);
}
/// <summary>
/// 任务详情-TaskId。
/// </summary>
public string DetailTaskIdText
{
get => _detailTaskIdText;
private set => SetField(ref _detailTaskIdText, value);
}
/// <summary>
/// 任务详情-TaskName。
/// </summary>
public string DetailTaskNameText
{
get => _detailTaskNameText;
private set => SetField(ref _detailTaskNameText, value);
}
/// <summary>
/// 任务详情-TaskStatus。
/// </summary>
public string DetailTaskStatusText
{
get => _detailTaskStatusText;
private set => SetField(ref _detailTaskStatusText, value);
}
/// <summary>
/// 任务详情-ProjectId。
/// </summary>
public string DetailProjectIdText
{
get => _detailProjectIdText;
private set => SetField(ref _detailProjectIdText, value);
}
/// <summary>
/// 任务详情-ItemCount。
/// </summary>
public string DetailItemCountText
{
get => _detailItemCountText;
private set => SetField(ref _detailItemCountText, value);
}
/// <summary>
/// 任务详情-UpdatedAtUtc。
/// </summary>
public string DetailUpdatedAtText
{
get => _detailUpdatedAtText;
private set => SetField(ref _detailUpdatedAtText, value);
}
/// <summary>
/// 规则机种编码。
/// </summary>
public string RuleProductTypeCodeText
{
get => _ruleProductTypeCodeText;
set => SetField(ref _ruleProductTypeCodeText, value);
}
/// <summary>
/// 规则机种名称。
/// </summary>
public string RuleProductTypeNameText
{
get => _ruleProductTypeNameText;
set => SetField(ref _ruleProductTypeNameText, value);
}
/// <summary>
/// 层配置列表。
/// </summary>
public ObservableCollection<RuleLayerInputRow> RuleLayers { get; } = [];
/// <summary>
/// 部件配置列表。
/// </summary>
public ObservableCollection<RulePartInputRow> RuleParts { get; } = [];
/// <summary>
/// ROI 配置列表。
/// </summary>
public ObservableCollection<RuleRoiInputRow> RuleRois { get; } = [];
/// <summary>
/// 规则项配置列表。
/// </summary>
public ObservableCollection<RuleItemInputRow> RuleItems { get; } = [];
/// <summary>
/// 规则版本审计记录列表。
/// </summary>
public ObservableCollection<RuleVersionAuditViewRow> RuleAuditRows { get; } = [];
/// <summary>
/// 规则版本分页展示列表。
/// </summary>
public ObservableCollection<RuleVersionListViewRow> RuleVersionRows { get; } = [];
/// <summary>
/// 当前选中层。
/// </summary>
public RuleLayerInputRow? SelectedRuleLayer
{
get => _selectedRuleLayer;
set => SetField(ref _selectedRuleLayer, value);
}
/// <summary>
/// 当前选中部件。
/// </summary>
public RulePartInputRow? SelectedRulePart
{
get => _selectedRulePart;
set => SetField(ref _selectedRulePart, value);
}
/// <summary>
/// 当前选中 ROI。
/// </summary>
public RuleRoiInputRow? SelectedRuleRoi
{
get => _selectedRuleRoi;
set => SetField(ref _selectedRuleRoi, value);
}
/// <summary>
/// 当前选中规则项。
/// </summary>
public RuleItemInputRow? SelectedRuleItem
{
get => _selectedRuleItem;
set => SetField(ref _selectedRuleItem, value);
}
/// <summary>
/// 新增层。
/// </summary>
public void AddRuleLayerRow()
{
RuleLayers.Add(new RuleLayerInputRow
{
LayerNoText = (RuleLayers.Count + 1).ToString(),
LayerName = $"Layer{RuleLayers.Count + 1}"
});
}
/// <summary>
/// 删除选中层。
/// </summary>
public void RemoveSelectedRuleLayerRow()
{
if (SelectedRuleLayer is null)
{
return;
}
RuleLayers.Remove(SelectedRuleLayer);
SelectedRuleLayer = null;
}
/// <summary>
/// 新增部件。
/// </summary>
public void AddRulePartRow()
{
RuleParts.Add(new RulePartInputRow
{
PartCode = $"PART-{RuleParts.Count + 1:000}",
PartName = $"Part{RuleParts.Count + 1}",
LayerNoText = "1"
});
}
/// <summary>
/// 删除选中部件。
/// </summary>
public void RemoveSelectedRulePartRow()
{
if (SelectedRulePart is null)
{
return;
}
RuleParts.Remove(SelectedRulePart);
SelectedRulePart = null;
}
/// <summary>
/// 新增 ROI。
/// </summary>
public void AddRuleRoiRow()
{
RuleRois.Add(new RuleRoiInputRow
{
RoiCode = $"ROI-{RuleRois.Count + 1:000}",
PartCode = RuleParts.FirstOrDefault()?.PartCode ?? "PART-001",
XText = "0",
YText = "0",
WidthText = "100",
HeightText = "100"
});
}
/// <summary>
/// 删除选中 ROI。
/// </summary>
public void RemoveSelectedRuleRoiRow()
{
if (SelectedRuleRoi is null)
{
return;
}
RuleRois.Remove(SelectedRuleRoi);
SelectedRuleRoi = null;
}
/// <summary>
/// 新增规则项。
/// </summary>
public void AddRuleItemRow()
{
RuleItems.Add(new RuleItemInputRow
{
RuleCode = $"RULE-{RuleItems.Count + 1:000}",
RuleName = $"Rule{RuleItems.Count + 1}",
RuleType = "ConfidenceThreshold",
ParametersJson = "{\"minConfidence\":0.95}",
Enabled = true
});
}
/// <summary>
/// 删除选中规则项。
/// </summary>
public void RemoveSelectedRuleItemRow()
{
if (SelectedRuleItem is null)
{
return;
}
RuleItems.Remove(SelectedRuleItem);
SelectedRuleItem = null;
}
/// <summary>
/// 最新发布版本号。
/// </summary>
public string PublishedRuleVersionText
{
get => _publishedRuleVersionText;
private set => SetField(ref _publishedRuleVersionText, value);
}
/// <summary>
/// 最新发布时间。
/// </summary>
public string PublishedRuleAtText
{
get => _publishedRuleAtText;
private set => SetField(ref _publishedRuleAtText, value);
}
/// <summary>
/// 对比源版本号。
/// </summary>
public string CompareSourceVersionText
{
get => _compareSourceVersionText;
set => SetField(ref _compareSourceVersionText, value);
}
/// <summary>
/// 对比目标版本号。
/// </summary>
public string CompareTargetVersionText
{
get => _compareTargetVersionText;
set => SetField(ref _compareTargetVersionText, value);
}
/// <summary>
/// 回滚目标版本号。
/// </summary>
public string RollbackTargetVersionText
{
get => _rollbackTargetVersionText;
set => SetField(ref _rollbackTargetVersionText, value);
}
/// <summary>
/// 停用目标版本号。
/// </summary>
public string DisableVersionNoText
{
get => _disableVersionNoText;
set => SetField(ref _disableVersionNoText, value);
}
/// <summary>
/// 详情查询版本号。
/// </summary>
public string VersionDetailNoText
{
get => _versionDetailNoText;
set => SetField(ref _versionDetailNoText, value);
}
/// <summary>
/// 分页查询页码。
/// </summary>
public string VersionPageIndexText
{
get => _versionPageIndexText;
set => SetField(ref _versionPageIndexText, value);
}
/// <summary>
/// 分页查询每页大小。
/// </summary>
public string VersionPageSizeText
{
get => _versionPageSizeText;
set => SetField(ref _versionPageSizeText, value);
}
/// <summary>
/// 是否处于空闲状态(用于按钮可用性绑定)。
/// </summary>
public bool IsIdle => !_isBusy;
/// <summary>
/// 执行“查询任务详情”。
/// </summary>
public async Task QueryTaskDetailAsync()
{
if (!long.TryParse(CvatTaskIdText.Trim(), out var cvatTaskId) || cvatTaskId <= 0)
{
SetError("CVAT_TASK_ID_INVALID", "CVAT 任务 ID 必须是大于 0 的整数。", Array.Empty<string>());
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在查询 CVAT 任务详情...";
SetFriendlyHint("正在查询任务详情,请稍候...", Brushes.SteelBlue);
var result = await _annotationSyncAppService.GetTaskDetailAsync(cvatTaskId);
if (!result.Succeeded || result.Data is null)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
StatusBrush = Brushes.DarkGreen;
StatusText = $"任务详情查询完成:{result.Message}";
SetFriendlyHint("任务详情已更新,可直接核对任务状态、项目归属和数量。", Brushes.DarkGreen);
ApplyTaskDetail(result.Data);
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}{Environment.NewLine}" +
$"TaskId: {result.Data.CvatTaskId}{Environment.NewLine}" +
$"TaskName: {result.Data.TaskName}{Environment.NewLine}" +
$"TaskStatus: {result.Data.TaskStatus}{Environment.NewLine}" +
$"ProjectId: {result.Data.CvatProjectId}{Environment.NewLine}" +
$"ItemCount: {result.Data.ItemCount}{Environment.NewLine}" +
$"UpdatedAtUtc: {result.Data.UpdatedAtUtc:O}";
}
catch (Exception ex)
{
SetError("UI_UNHANDLED_EXCEPTION", "查询任务详情出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
/// <summary>
/// 执行“检查连通状态”。
/// </summary>
public async Task CheckStatusAsync()
{
if (!TryParseProjectId(out var projectId))
{
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在检查 CVAT 连通状态...";
SetFriendlyHint("正在检查服务连通性与版本信息...", Brushes.SteelBlue);
var result = await _annotationSyncAppService.GetSyncStatusAsync(projectId);
if (!result.Succeeded || result.Data is null)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
StatusBrush = Brushes.DarkGreen;
StatusText = $"状态检查完成:{result.Message}";
SetFriendlyHint("连通性检查完成,可继续执行任务详情查询或发起同步。", Brushes.DarkGreen);
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}{Environment.NewLine}" +
$"SyncStatus: {result.Data.SyncStatus}{Environment.NewLine}" +
$"Progress: {result.Data.ProgressPercent}{Environment.NewLine}" +
$"LastSyncedAtUtc: {result.Data.LastSyncedAtUtc:O}{Environment.NewLine}" +
$"LastErrorMessage: {result.Data.LastErrorMessage}";
}
catch (Exception ex)
{
SetError("UI_UNHANDLED_EXCEPTION", "状态检查出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
/// <summary>
/// 执行“发起同步”。
/// </summary>
public async Task SyncAsync()
{
if (!TryBuildSyncCommand(out var command))
{
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在发起 CVAT 同步...";
SetFriendlyHint("正在校验任务与项目关系并提交同步请求...", Brushes.SteelBlue);
var result = await _annotationSyncAppService.SyncProjectAsync(command);
if (!result.Succeeded)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
StatusBrush = Brushes.DarkGreen;
StatusText = "同步请求成功";
SetFriendlyHint("同步请求已受理,请稍后再次检查状态。", Brushes.DarkGreen);
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}";
}
catch (Exception ex)
{
SetError("UI_UNHANDLED_EXCEPTION", "同步请求出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
/// <summary>
/// 执行规则版本对比。
/// </summary>
public async Task CompareVersionsAsync()
{
if (string.IsNullOrWhiteSpace(RuleProductTypeCodeText))
{
SetError("RULE_PRODUCT_CODE_REQUIRED", "机种编码不能为空。", Array.Empty<string>());
return;
}
if (string.IsNullOrWhiteSpace(CompareSourceVersionText) || string.IsNullOrWhiteSpace(CompareTargetVersionText))
{
SetError("RULE_VERSION_COMPARE_INPUT_REQUIRED", "请输入源版本号和目标版本号。", Array.Empty<string>());
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在对比规则版本...";
SetFriendlyHint("正在获取版本快照并进行对比分析...", Brushes.SteelBlue);
var result = await _ruleConfigurationAppService.CompareVersionsAsync(
RuleProductTypeCodeText.Trim(),
CompareSourceVersionText.Trim(),
CompareTargetVersionText.Trim());
if (!result.Succeeded || result.Data is null)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
StatusBrush = Brushes.DarkGreen;
StatusText = "版本对比完成";
var friendlyHint = result.Data.IsSame ? "版本内容完全一致。" : "版本内容存在差异,请查看详细日志。";
SetFriendlyHint(friendlyHint, result.Data.IsSame ? Brushes.DarkGreen : Brushes.Orange);
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}{Environment.NewLine}" +
$"ProductTypeCode: {result.Data.ProductTypeCode}{Environment.NewLine}" +
$"SourceVersionNo: {result.Data.SourceVersionNo}{Environment.NewLine}" +
$"TargetVersionNo: {result.Data.TargetVersionNo}{Environment.NewLine}" +
$"IsSame: {result.Data.IsSame}{Environment.NewLine}" +
$"Summary: {result.Data.Summary}{Environment.NewLine}" +
$"SourceSnapshotLength: {result.Data.SourceSnapshotJson.Length}{Environment.NewLine}" +
$"TargetSnapshotLength: {result.Data.TargetSnapshotJson.Length}";
}
catch (Exception ex)
{
SetError("RULE_VERSION_COMPARE_UI_EXCEPTION", "版本对比出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
/// <summary>
/// 执行规则版本回滚。
/// </summary>
public async Task RollbackVersionAsync()
{
if (string.IsNullOrWhiteSpace(RuleProductTypeCodeText))
{
SetError("RULE_PRODUCT_CODE_REQUIRED", "机种编码不能为空。", Array.Empty<string>());
return;
}
var targetVersion = RollbackTargetVersionText.Trim();
if (string.IsNullOrWhiteSpace(targetVersion))
{
// 如果未指定目标版本,则回滚到最新版本的前一个版本
SetError("RULE_ROLLBACK_TARGET_REQUIRED", "请输入要回滚到的版本号。", Array.Empty<string>());
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在回滚规则版本...";
SetFriendlyHint("正在创建回滚版本并复制目标快照...", Brushes.SteelBlue);
var result = await _ruleConfigurationAppService.RollbackToVersionAsync(
RuleProductTypeCodeText.Trim(),
targetVersion,
RequestedByText.Trim());
if (!result.Succeeded || result.Data is null)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
// 更新界面显示的版本信息
PublishedRuleVersionText = result.Data.VersionNo;
PublishedRuleAtText = result.Data.PublishedAtUtc.ToString("O");
StatusBrush = Brushes.DarkGreen;
StatusText = "版本回滚成功";
SetFriendlyHint($"已成功回滚到版本 {targetVersion},新版本号为 {result.Data.VersionNo}。", Brushes.DarkGreen);
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}{Environment.NewLine}" +
$"ProductTypeCode: {result.Data.ProductTypeCode}{Environment.NewLine}" +
$"NewVersionNo: {result.Data.VersionNo}{Environment.NewLine}" +
$"TargetVersionNo: {targetVersion}{Environment.NewLine}" +
$"PublishedAtUtc: {result.Data.PublishedAtUtc:O}{Environment.NewLine}" +
$"PublishedBy: {result.Data.PublishedBy}";
}
catch (Exception ex)
{
SetError("RULE_ROLLBACK_UI_EXCEPTION", "版本回滚出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
/// <summary>
/// 查询最近规则版本审计记录。
/// </summary>
public async Task QueryRecentRuleAuditsAsync()
{
if (string.IsNullOrWhiteSpace(RuleProductTypeCodeText))
{
SetError("RULE_PRODUCT_CODE_REQUIRED", "机种编码不能为空。", Array.Empty<string>());
return;
}
SetBusy(true);
try
{
StatusBrush = Brushes.DarkGreen;
StatusText = "正在查询规则版本审计...";
SetFriendlyHint("正在读取最近发布/回滚审计记录...", Brushes.SteelBlue);
RuleAuditRows.Clear();
var result = await _ruleConfigurationAppService.GetRecentAuditsAsync(RuleProductTypeCodeText.Trim(), 20);
if (!result.Succeeded || result.Data is null)
{
SetError(result.Code, result.Message, result.Errors);
return;
}
StatusBrush = Brushes.DarkGreen;
StatusText = "规则版本审计查询成功";
SetFriendlyHint($"已获取 {result.Data.Count} 条审计记录。", Brushes.DarkGreen);
foreach (var audit in result.Data)
{
RuleAuditRows.Add(new RuleVersionAuditViewRow
{
ActionAtUtcText = audit.ActionAtUtc.ToString("O"),
ActionType = string.IsNullOrWhiteSpace(audit.ActionType) ? "-" : audit.ActionType,
VersionNo = string.IsNullOrWhiteSpace(audit.VersionNo) ? "-" : audit.VersionNo,
SourceVersionNo = string.IsNullOrWhiteSpace(audit.SourceVersionNo) ? "-" : audit.SourceVersionNo,
TargetVersionNo = string.IsNullOrWhiteSpace(audit.TargetVersionNo) ? "-" : audit.TargetVersionNo,
OperatorName = string.IsNullOrWhiteSpace(audit.OperatorName) ? "-" : audit.OperatorName
});
}
OutputText =
$"Succeeded: {result.Succeeded}{Environment.NewLine}" +
$"Code: {result.Code}{Environment.NewLine}" +
$"Message: {result.Message}{Environment.NewLine}" +
$"Count: {result.Data.Count}";
}
catch (Exception ex)
{
SetError("RULE_AUDIT_QUERY_UI_EXCEPTION", "查询规则版本审计出现未处理异常。", new[] { ex.Message });
}
finally
{
SetBusy(false);
}
}
private bool TryBuildSyncCommand(out SyncAnnotationProjectCommand command)
{
command = new SyncAnnotationProjectCommand();
if (!TryParseProjectId(out var projectId))
{
return false;
}
if (!Guid.TryParse(AnnotationTaskIdText.Trim(), out var annotationTaskId) || annotationTaskId == Guid.Empty)
{
SetError("ANNOTATION_TASK_ID_INVALID", "本地标注任务 ID 不是有效 GUID。", Array.Empty<string>());
return false;
}
if (string.IsNullOrWhiteSpace(CvatEndpointText))
{
SetError("ANNOTATION_ENDPOINT_REQUIRED", "CVAT 服务地址不能为空。", Array.Empty<string>());
return false;
}
if (!long.TryParse(CvatProjectIdText.Trim(), out var cvatProjectId) || cvatProjectId <= 0)
{
SetError("CVAT_PROJECT_ID_INVALID", "CVAT 项目 ID 必须是大于 0 的整数。", Array.Empty<string>());
return false;
}
if (!long.TryParse(CvatTaskIdText.Trim(), out var cvatTaskId) || cvatTaskId <= 0)
{
SetError("CVAT_TASK_ID_INVALID", "CVAT 任务 ID 必须是大于 0 的整数。", Array.Empty<string>());
return false;
}
command = new SyncAnnotationProjectCommand
{
Platform = AnnotationPlatformEnum.Cvat,
ProjectId = projectId,
AnnotationTaskId = annotationTaskId,
CvatServerEndpoint = CvatEndpointText.Trim(),
CvatProjectId = cvatProjectId,
CvatTaskId = cvatTaskId,
RequestedBy = RequestedByText.Trim()
};
return true;
}
private bool TryBuildRuleDraft(out RuleConfigurationDraftDto draft)
{
draft = new RuleConfigurationDraftDto();
var productTypeCode = RuleProductTypeCodeText.Trim();
var productTypeName = RuleProductTypeNameText.Trim();
if (string.IsNullOrWhiteSpace(productTypeCode) ||
string.IsNullOrWhiteSpace(productTypeName))
{
SetError("RULE_CONFIG_REQUIRED_FIELDS", "规则配置字段存在空值。", Array.Empty<string>());
return false;
}
if (RuleLayers.Count == 0 || RuleParts.Count == 0 || RuleRois.Count == 0 || RuleItems.Count == 0)
{
SetError("RULE_CONFIG_LIST_EMPTY", "层、部件、ROI、规则列表均至少需要一条记录。", Array.Empty<string>());
return false;
}
var layerDtos = new List<LayerConfigurationDto>();
foreach (var layer in RuleLayers)
{
if (string.IsNullOrWhiteSpace(layer.LayerName) || !TryParsePositiveInt(layer.LayerNoText, out var layerNo))
{
SetError("RULE_LAYER_NO_INVALID", "层配置无效:层号必须为正整数且层名称不能为空。", Array.Empty<string>());
return false;
}
layerDtos.Add(new LayerConfigurationDto
{
LayerNo = layerNo,
LayerName = layer.LayerName.Trim()
});
}
var layerNoSet = layerDtos.Select(item => item.LayerNo).ToHashSet();
var partDtos = new List<PartConfigurationDto>();
foreach (var part in RuleParts)
{
if (string.IsNullOrWhiteSpace(part.PartCode) ||
string.IsNullOrWhiteSpace(part.PartName) ||
!TryParsePositiveInt(part.LayerNoText, out var partLayerNo) ||
!layerNoSet.Contains(partLayerNo))
{
SetError("RULE_PART_LAYER_NO_INVALID", "部件配置无效:请检查编码、名称和层号映射。", Array.Empty<string>());
return false;
}
partDtos.Add(new PartConfigurationDto
{
PartCode = part.PartCode.Trim(),
PartName = part.PartName.Trim(),
LayerNo = partLayerNo
});
}
var partCodeSet = partDtos.Select(item => item.PartCode).ToHashSet(StringComparer.OrdinalIgnoreCase);
var roiDtos = new List<RoiConfigurationDto>();
foreach (var roi in RuleRois)
{
if (string.IsNullOrWhiteSpace(roi.RoiCode) ||
string.IsNullOrWhiteSpace(roi.PartCode) ||
!partCodeSet.Contains(roi.PartCode.Trim()) ||
!int.TryParse(roi.XText.Trim(), out var roiX) || roiX < 0 ||
!int.TryParse(roi.YText.Trim(), out var roiY) || roiY < 0 ||
!TryParsePositiveInt(roi.WidthText, out var roiWidth) ||
!TryParsePositiveInt(roi.HeightText, out var roiHeight))
{
SetError("RULE_ROI_INVALID", "ROI 配置无效:请检查编码、部件映射和坐标尺寸。", Array.Empty<string>());
return false;
}
roiDtos.Add(new RoiConfigurationDto
{
RoiCode = roi.RoiCode.Trim(),
PartCode = roi.PartCode.Trim(),
X = roiX,
Y = roiY,
Width = roiWidth,
Height = roiHeight
});
}
var ruleDtos = new List<RuleItemConfigurationDto>();
foreach (var rule in RuleItems)
{
if (string.IsNullOrWhiteSpace(rule.RuleCode) ||
string.IsNullOrWhiteSpace(rule.RuleName) ||
string.IsNullOrWhiteSpace(rule.RuleType) ||
string.IsNullOrWhiteSpace(rule.ParametersJson))
{
SetError("RULE_CONFIG_REQUIRED_FIELDS", "规则项配置存在空值。", Array.Empty<string>());
return false;
}
try
{
_ = JsonDocument.Parse(rule.ParametersJson);
}
catch (JsonException ex)
{
SetError("RULE_PARAMETERS_JSON_INVALID", "规则参数 JSON 格式不正确。", new[] { ex.Message });
return false;
}
ruleDtos.Add(new RuleItemConfigurationDto
{
RuleCode = rule.RuleCode.Trim(),
RuleName = rule.RuleName.Trim(),
RuleType = rule.RuleType.Trim(),
ParametersJson = rule.ParametersJson.Trim(),
Enabled = rule.Enabled
});
}
draft = new RuleConfigurationDraftDto
{
ProductTypeCode = productTypeCode,
ProductTypeName = productTypeName,
UpdatedBy = RequestedByText.Trim(),
Layers = layerDtos,
Parts = partDtos,
Rois = roiDtos,
Rules = ruleDtos
};
return true;
}
private bool TryParseProjectId(out Guid projectId)
{
if (!Guid.TryParse(ProjectIdText.Trim(), out projectId) || projectId == Guid.Empty)
{
SetError("PROJECT_ID_INVALID", "本地项目 ID 不是有效 GUID。", Array.Empty<string>());
return false;
}
return true;
}
private static bool TryParsePositiveInt(string text, out int value)
{
return int.TryParse(text.Trim(), out value) && value > 0;
}
private void AddDefaultRuleRows()
{
RuleLayers.Add(new RuleLayerInputRow
{
LayerNoText = "1",
LayerName = "TopLayer"
});
RuleParts.Add(new RulePartInputRow
{
PartCode = "PART-001",
PartName = "DefaultPart",
LayerNoText = "1"
});
RuleRois.Add(new RuleRoiInputRow
{
RoiCode = "ROI-001",
PartCode = "PART-001",
XText = "100",
YText = "100",
WidthText = "200",
HeightText = "150"
});
RuleItems.Add(new RuleItemInputRow
{
RuleCode = "RULE-001",
RuleName = "ConfidenceThreshold",
RuleType = "ConfidenceThreshold",
ParametersJson = "{\"minConfidence\":0.95}",
Enabled = true
});
}
private void SetBusy(bool isBusy)
{
_isBusy = isBusy;
OnPropertyChanged(nameof(IsIdle));
}
private void SetError(string code, string message, IReadOnlyCollection<string> errors)
{
StatusBrush = Brushes.IndianRed;
StatusText = $"操作失败:{message}";
var (friendlyHint, hintBrush) = BuildFriendlyHint(code);
SetFriendlyHint(friendlyHint, hintBrush);
OutputText =
$"Succeeded: False{Environment.NewLine}" +
$"Code: {code}{Environment.NewLine}" +
$"Message: {message}{Environment.NewLine}" +
$"Errors: {string.Join("; ", errors)}";
}
private void ApplyTaskDetail(AnnotationTaskDetailDto detail)
{
DetailTaskIdText = detail.CvatTaskId.ToString();
DetailTaskNameText = string.IsNullOrWhiteSpace(detail.TaskName) ? "-" : detail.TaskName;
DetailTaskStatusText = string.IsNullOrWhiteSpace(detail.TaskStatus) ? "-" : detail.TaskStatus;
DetailProjectIdText = detail.CvatProjectId?.ToString() ?? "-";
DetailItemCountText = detail.ItemCount.ToString();
DetailUpdatedAtText = detail.UpdatedAtUtc?.ToString("O") ?? "-";
}
private void SetFriendlyHint(string hint, Brush brush)
{
FriendlyHintText = hint;
FriendlyHintBrush = brush;
}
private static (string Hint, Brush Brush) BuildFriendlyHint(string code)
{
return code switch
{
"ANNOTATION_CVAT_AUTH_FAILED" => ("认证失败:请检查 appsettings 中的 CVAT Token 是否正确。", Brushes.IndianRed),
"ANNOTATION_CVAT_UNREACHABLE" => ("连接失败:请确认 CVAT 服务地址、端口和网络连通性。", Brushes.IndianRed),
"ANNOTATION_CVAT_TASK_NOT_FOUND" => ("任务不存在:请核对 CVAT 任务 ID。", Brushes.OrangeRed),
"ANNOTATION_CVAT_PROJECT_MISMATCH" => ("项目不一致:请核对 CVAT 项目 ID 与任务归属。", Brushes.OrangeRed),
"ANNOTATION_SYNC_TIMEOUT" => ("请求超时:建议稍后重试或提高超时时间。", Brushes.OrangeRed),
"RULE_CONFIG_REQUIRED_FIELDS" => ("规则配置字段缺失请完整填写机种、层、部件、ROI、规则信息。", Brushes.OrangeRed),
"RULE_CONFIG_LIST_EMPTY" => ("规则配置列表为空请至少添加一条层、部件、ROI 和规则。", Brushes.OrangeRed),
"RULE_LAYER_NO_INVALID" or "RULE_PART_LAYER_NO_INVALID" => ("层号无效:请填写大于 0 的整数。", Brushes.OrangeRed),
"RULE_ROI_INVALID" => ("ROI 参数无效:请检查坐标与宽高。", Brushes.OrangeRed),
"RULE_PARAMETERS_JSON_INVALID" => ("规则参数 JSON 无效:请检查格式。", Brushes.OrangeRed),
"PROJECT_ID_INVALID" or "ANNOTATION_TASK_ID_INVALID" or "CVAT_TASK_ID_INVALID" or "CVAT_PROJECT_ID_INVALID" =>
("输入参数无效:请检查 GUID 与数字字段格式。", Brushes.OrangeRed),
_ => ("执行失败:请根据详细日志定位原因并重试。", Brushes.IndianRed)
};
}
private bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// 打开训练任务管理窗口。
/// </summary>
public void OpenTrainingTaskManagement()
{
try
{
// 通过依赖注入获取训练任务管理ViewModel和窗口
var viewModel = _serviceProvider.GetRequiredService<TrainingTaskManagementViewModel>();
var logger = _serviceProvider.GetRequiredService<ILogger<Views.TrainingTaskManagementWindow>>();
var window = new Views.TrainingTaskManagementWindow(logger, viewModel);
window.Show();
}
catch (Exception ex)
{
MessageBox.Show($"打开训练任务管理窗口失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
/// <summary>
/// 规则层输入行。
/// </summary>
public sealed class RuleLayerInputRow
{
/// <summary>
/// 层号文本。
/// </summary>
public string LayerNoText { get; set; } = "1";
/// <summary>
/// 层名称。
/// </summary>
public string LayerName { get; set; } = string.Empty;
}
/// <summary>
/// 规则部件输入行。
/// </summary>
public sealed class RulePartInputRow
{
/// <summary>
/// 部件编码。
/// </summary>
public string PartCode { get; set; } = string.Empty;
/// <summary>
/// 部件名称。
/// </summary>
public string PartName { get; set; } = string.Empty;
/// <summary>
/// 所属层号文本。
/// </summary>
public string LayerNoText { get; set; } = "1";
}
/// <summary>
/// 规则 ROI 输入行。
/// </summary>
public sealed class RuleRoiInputRow
{
/// <summary>
/// ROI 编码。
/// </summary>
public string RoiCode { get; set; } = string.Empty;
/// <summary>
/// 部件编码。
/// </summary>
public string PartCode { get; set; } = string.Empty;
/// <summary>
/// X 坐标。
/// </summary>
public string XText { get; set; } = "0";
/// <summary>
/// Y 坐标。
/// </summary>
public string YText { get; set; } = "0";
/// <summary>
/// 宽度。
/// </summary>
public string WidthText { get; set; } = "100";
/// <summary>
/// 高度。
/// </summary>
public string HeightText { get; set; } = "100";
}
/// <summary>
/// 规则项输入行。
/// </summary>
public sealed class RuleItemInputRow
{
/// <summary>
/// 规则编码。
/// </summary>
public string RuleCode { get; set; } = string.Empty;
/// <summary>
/// 规则名称。
/// </summary>
public string RuleName { get; set; } = string.Empty;
/// <summary>
/// 规则类型。
/// </summary>
public string RuleType { get; set; } = string.Empty;
/// <summary>
/// 规则参数 JSON。
/// </summary>
public string ParametersJson { get; set; } = "{}";
/// <summary>
/// 是否启用。
/// </summary>
public bool Enabled { get; set; } = true;
}
/// <summary>
/// 规则版本审计展示行。
/// </summary>
public sealed class RuleVersionAuditViewRow
{
/// <summary>
/// 操作时间UTC
/// </summary>
public string ActionAtUtcText { get; set; } = "-";
/// <summary>
/// 操作类型。
/// </summary>
public string ActionType { get; set; } = "-";
/// <summary>
/// 版本号。
/// </summary>
public string VersionNo { get; set; } = "-";
/// <summary>
/// 源版本号。
/// </summary>
public string SourceVersionNo { get; set; } = "-";
/// <summary>
/// 目标版本号。
/// </summary>
public string TargetVersionNo { get; set; } = "-";
/// <summary>
/// 操作人。
/// </summary>
public string OperatorName { get; set; } = "-";
}
/// <summary>
/// 规则版本列表展示行。
/// </summary>
public sealed class RuleVersionListViewRow
{
/// <summary>
/// 版本号。
/// </summary>
public string VersionNo { get; set; } = "-";
/// <summary>
/// 发布时间UTC
/// </summary>
public string PublishedAtUtcText { get; set; } = "-";
/// <summary>
/// 发布人。
/// </summary>
public string PublishedBy { get; set; } = "-";
/// <summary>
/// 是否已停用。
/// </summary>
public string IsDisabledText { get; set; } = "否";
/// <summary>
/// 停用时间UTC
/// </summary>
public string DisabledAtUtcText { get; set; } = "-";
/// <summary>
/// 停用人。
/// </summary>
public string DisabledBy { get; set; } = "-";
}