Show routing import errors in modal dialog

This commit is contained in:
wwmmzz 2026-05-24 14:01:27 +08:00
parent 807f0aba06
commit 1520343c29
8 changed files with 123 additions and 3 deletions

View file

@ -33,4 +33,5 @@ public enum EViewAction
DispatcherRefreshServersBiz,
DispatcherRefreshIcon,
DispatcherShowMsg,
ShowMessage,
}

View file

@ -305,13 +305,16 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
}
if (clipboardData.IsNullOrEmpty())
{
await ShowImportErrorAsync($"{ResUI.OperationFailed}: {ResUI.FailedReadConfiguration}");
return -1;
}
var lstRules = JsonUtils.Deserialize<List<RulesItem>>(clipboardData);
if (lstRules == null)
if (!TryDeserializeRoutingRules(clipboardData, out var lstRules, out var errorMsg))
{
await ShowImportErrorAsync($"{ResUI.OperationFailed}: {errorMsg}");
return -1;
}
foreach (var rule in lstRules)
{
rule.Id = Utils.GetGuid(false);
@ -328,5 +331,74 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
return 0;
}
private async Task ShowImportErrorAsync(string message)
{
if (_updateView != null)
{
await _updateView.Invoke(EViewAction.ShowMessage, message);
return;
}
NoticeManager.Instance.Enqueue(message);
}
private static bool TryDeserializeRoutingRules(string clipboardData, out List<RulesItem> rules, out string errorMsg)
{
rules = [];
errorMsg = string.Empty;
var trimmed = clipboardData.Trim();
if (trimmed.IsNullOrEmpty())
{
errorMsg = ResUI.FailedReadConfiguration;
return false;
}
if (trimmed.StartsWith('{'))
{
if (trimmed.Contains("\"routing\"", StringComparison.OrdinalIgnoreCase)
|| trimmed.Contains("\"route\"", StringComparison.OrdinalIgnoreCase))
{
errorMsg = "你粘贴的是完整配置文件,不是路由规则数组。请只粘贴 routing.rules 或 route.rules 的 JSON 数组。";
}
else
{
errorMsg = "当前内容是 JSON 对象。路由规则导入需要顶层 JSON 数组,例如 [ { \"outboundTag\": \"proxy\" } ]。";
}
return false;
}
try
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
};
rules = JsonSerializer.Deserialize<List<RulesItem>>(trimmed, options) ?? [];
if (rules.Count == 0)
{
errorMsg = "未解析到任何路由规则,请确认内容是非空 JSON 数组。";
return false;
}
return true;
}
catch (JsonException ex)
{
var location = ex.LineNumber is not null && ex.BytePositionInLine is not null
? $"第 {ex.LineNumber + 1} 行,第 {ex.BytePositionInLine + 1} 列"
: "未知位置";
errorMsg = $"JSON 解析错误,{location}。{ex.Message}";
return false;
}
catch (Exception ex)
{
errorMsg = ex.Message;
return false;
}
}
#endregion Import rules
}

View file

@ -7,6 +7,12 @@ internal class UI
{
private static readonly string caption = Global.AppName;
public static async Task Show(Window owner, string msg)
{
var box = new MessageBoxDialog(caption, msg, true);
await box.ShowDialog<ButtonResult>(owner);
}
public static async Task<ButtonResult> ShowYesNo(Window owner, string msg)
{
var box = new MessageBoxDialog(caption, msg);

View file

@ -247,6 +247,15 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
case EViewAction.AddServerViaClipboard:
await AddServerViaClipboardAsync();
break;
case EViewAction.ShowMessage:
if (obj is null)
{
return false;
}
await UI.Show(this, obj.ToString() ?? string.Empty);
break;
}
return await Task.FromResult(true);

View file

@ -9,13 +9,18 @@ public partial class MessageBoxDialog : Window
{
}
public MessageBoxDialog(string caption, string message)
public MessageBoxDialog(string caption, string message, bool okOnly = false)
{
InitializeComponent();
Title = caption;
txtMessage.Text = message;
if (okOnly)
{
btnNo.IsVisible = false;
}
btnYes.Click += BtnYes_Click;
btnNo.Click += BtnNo_Click;
}

View file

@ -115,6 +115,15 @@ public partial class RoutingRuleSettingWindow : WindowBase<RoutingRuleSettingVie
}
break;
case EViewAction.ShowMessage:
if (obj is null)
{
return false;
}
await UI.Show(this, obj.ToString() ?? string.Empty);
break;
}
return await Task.FromResult(true);

View file

@ -242,6 +242,15 @@ public partial class MainWindow
case EViewAction.AddServerViaClipboard:
await AddServerViaClipboardAsync();
break;
case EViewAction.ShowMessage:
if (obj is null)
{
return false;
}
UI.Show(obj.ToString() ?? string.Empty);
break;
}
return await Task.FromResult(true);

View file

@ -110,6 +110,15 @@ public partial class RoutingRuleSettingWindow
ViewModel?.ImportRulesFromClipboardAsync(clipboardData);
}
break;
case EViewAction.ShowMessage:
if (obj is null)
{
return false;
}
UI.Show(obj.ToString() ?? string.Empty);
break;
}
return await Task.FromResult(true);