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, DispatcherRefreshServersBiz,
DispatcherRefreshIcon, DispatcherRefreshIcon,
DispatcherShowMsg, DispatcherShowMsg,
ShowMessage,
} }

View file

@ -305,13 +305,16 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
} }
if (clipboardData.IsNullOrEmpty()) if (clipboardData.IsNullOrEmpty())
{ {
await ShowImportErrorAsync($"{ResUI.OperationFailed}: {ResUI.FailedReadConfiguration}");
return -1; 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; return -1;
} }
foreach (var rule in lstRules) foreach (var rule in lstRules)
{ {
rule.Id = Utils.GetGuid(false); rule.Id = Utils.GetGuid(false);
@ -328,5 +331,74 @@ public class RoutingRuleSettingViewModel : MyReactiveObject
return 0; 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 #endregion Import rules
} }

View file

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

View file

@ -247,6 +247,15 @@ public partial class MainWindow : WindowBase<MainWindowViewModel>
case EViewAction.AddServerViaClipboard: case EViewAction.AddServerViaClipboard:
await AddServerViaClipboardAsync(); await AddServerViaClipboardAsync();
break; break;
case EViewAction.ShowMessage:
if (obj is null)
{
return false;
}
await UI.Show(this, obj.ToString() ?? string.Empty);
break;
} }
return await Task.FromResult(true); 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(); InitializeComponent();
Title = caption; Title = caption;
txtMessage.Text = message; txtMessage.Text = message;
if (okOnly)
{
btnNo.IsVisible = false;
}
btnYes.Click += BtnYes_Click; btnYes.Click += BtnYes_Click;
btnNo.Click += BtnNo_Click; btnNo.Click += BtnNo_Click;
} }

View file

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

View file

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

View file

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