publicboolSetTheme(string themeName) { if (_themes.TryGetValue(themeName, outvar theme)) { var oldTheme = _currentTheme; _currentTheme = theme; ThemeChanged?.Invoke(this, new ThemeChangedEventArgs(oldTheme, theme)); returntrue; } returnfalse; } }
主题切换触发事件,UI组件订阅事件并重新渲染。优雅的观察者模式。
3.1 Fluent Builder API
传统方式创建A2UI消息,需要手写大量JSON:
// 传统方式:冗长且易错 var messages = new List { new ServerToClientMessage { SurfaceUpdate = new SurfaceUpdateMessage { SurfaceId = "my-surface", Components = new List { new ComponentDefinition { Id = "card", Component = new Dictionarystring, object> { ["Card"] = new Dictionarystring, object> { ["child"] = "content" } } } // ... 更多组件 } } } };
使用Fluent Builder API,代码变得优雅:
var messages = new SurfaceBuilder("my-surface") .AddCard("card", card => card.WithChild("content")) .AddColumn("content", col => col .AddChild("title") .AddChild("body")) .AddText("title", text => text .WithText("欢迎使用A2UI") .WithUsageHint("h2")) .AddText("body", text => text .WithText("这是一个示例卡片")) .WithRoot("card") .Build();
Fluent API的优势:
链式调用 :一气呵成,代码流畅
类型安全 :编译时检查,减少错误
IntelliSense支持 :IDE自动补全,开发效率高
可读性强 :代码即文档,一目了然
3.3 QuickStart辅助方法
对于常见场景,提供快捷方法:
publicstaticclassA2UIQuickStart { publicstatic ListCreateTextCard( string surfaceId, string title, string body) { returnnew SurfaceBuilder(surfaceId) .AddCard("card", card => card.WithChild("content")) .AddColumn("content", col => col .AddChild("title") .AddChild("body")) .AddText("title", text => text .WithText(title) .WithUsageHint("h3")) .AddText("body", text => text.WithText(body)) .WithRoot("card") .Build(); }
private ListGenerateRestaurantList(string surfaceId) { var restaurants = _database.GetRestaurants();
var builder = new SurfaceBuilder(surfaceId) .AddColumn("root", col => col.AddChild("title"));
builder.AddText("title", text => text .WithText("附近的餐厅") .WithUsageHint("h2"));
// 动态添加餐厅卡片 foreach (var restaurant in restaurants) { var cardId = $"restaurant-{restaurant.Id}"; var contentId = $"content-{restaurant.Id}"; var nameId = $"name-{restaurant.Id}"; var descId = $"desc-{restaurant.Id}"; var btnId = $"btn-{restaurant.Id}"; var btnTextId = $"btn-text-{restaurant.Id}";
builder .AddCard(cardId, card => card.WithChild(contentId)) .AddColumn(contentId, col => col .AddChild(nameId) .AddChild(descId) .AddChild(btnId)) .AddText(nameId, text => text .WithText(restaurant.Name) .WithUsageHint("h3")) .AddText(descId, text => text .WithText(restaurant.Description)) .AddButton(btnId, btn => btn .WithChild(btnTextId) .WithAction("book_restaurant") .AddActionContext("restaurantId", restaurant.Id.ToString()) .AsPrimary()) .AddText(btnTextId, text => text.WithText("预订"));
// 将卡片添加到根列表 builder.AddColumn("root", col => col.AddChild(cardId)); }
return builder.WithRoot("root").Build(); } }
这段代码展示了A2UI的强大之处:
数据驱动 :从数据库查询数据,动态生成UI
模板化 :常见场景用模板,快速响应
可扩展 :复杂场景可以调用LLM生成
第五章:高级特性——深入技术细节
5.1 列表渲染与数据上下文
列表是UI中最常见的场景。A2UI通过dataContext实现列表项的数据绑定:
var messages = new SurfaceBuilder("list-demo") .AddList("root", list => list .WithItems("/items") .WithTemplate("item-template") .WithDirection("vertical")) .AddCard("item-template", card => card .WithChild("item-content")) .AddColumn("item-content", col => col .AddChild("item-name") .AddChild("item-price")) .AddText("item-name", text => text .BindToPath("name")) // 相对路径,绑定到当前item .AddText("item-price", text => text .BindToPath("price")) .AddData("items", new Listobject> { new { name = "商品A", price = 99.9 }, new { name = "商品B", price = 199.9 } }) .WithRoot("root") .Build();
publicstringSanitizeHtml(string html) { var doc = new Htmldocument(); doc.LoadHtml(html);
// 移除不允许的标签 var nodes = doc.documentNode.Descendants() .Where(n => !AllowedTags.Contains(n.Name)) .ToList();
foreach (var node in nodes) { node.Remove(); }
return doc.documentNode.OuterHtml; }
第六章:实际应用场景
6.1 企业工作流审批系统
想象一个智能审批助手,根据审批类型动态生成表单:
publicclassApprovalAgent { public ListGenerateApprovalForm( ApprovalRequest request, string surfaceId) { var builder = new SurfaceBuilder(surfaceId) .AddCard("root", card => card.WithChild("content")) .AddColumn("content", col => col .AddChild("header") .AddChild("details") .AddChild("actions"));
// 标题 builder.AddText("header", text => text .WithText($"{request.Type}审批") .WithUsageHint("h2"));
// 详情(根据类型动态生成) var detailsBuilder = builder.AddColumn("details", col => col);
builder .AddText("applicant", text => text .WithText($"申请人:{request.Applicant}")) .AddText("start-date", text => text .WithText($"开始日期:{request.StartDate:yyyy-MM-dd}")) .AddText("end-date", text => text .WithText($"结束日期:{request.EndDate:yyyy-MM-dd}")) .AddText("reason", text => text .WithText($"原因:{request.Reason}")); break;
builder .AddText("applicant", text => text .WithText($"申请人:{request.Applicant}")) .AddText("amount", text => text .WithText($"金额:yen{request.Amount:F2}")) .AddText("category", text => text .WithText($"类别:{request.Category}")) .AddImage("receipt", img => img .WithUrl(request.ReceiptUrl) .WithUsageHint("medium-feature")); break; }
publicclassDashboardAgent { public ListGenerateDashboard( DashboardData data, string surfaceId) { var builder = new SurfaceBuilder(surfaceId) .AddColumn("root", col => col .AddChild("header") .AddChild("metrics") .AddChild("charts"));
// 标题 builder.AddText("header", text => text .WithText("业务仪表盘") .WithUsageHint("h1"));
// 收入指标 builder .AddCard("metric-revenue", card => card.WithChild("revenue-content")) .AddColumn("revenue-content", col => col .AddChild("revenue-label") .AddChild("revenue-value") .AddChild("revenue-change")) .AddText("revenue-label", text => text .WithText("总收入") .WithUsageHint("caption")) .AddText("revenue-value", text => text .WithText($"yen{data.Revenue:N0}") .WithUsageHint("h2")) .AddText("revenue-change", text => text .WithText($"↑ {data.RevenueChange:P1}"));
// 用户指标 builder .AddCard("metric-users", card => card.WithChild("users-content")) .AddColumn("users-content", col => col .AddChild("users-label") .AddChild("users-value") .AddChild("users-change")) .AddText("users-label", text => text .WithText("活跃用户") .WithUsageHint("caption")) .AddText("users-value", text => text .WithText($"{data.ActiveUsers:N0}") .WithUsageHint("h2")) .AddText("users-change", text => text .WithText($"↑ {data.UsersChange:P1}"));
// 订单指标 builder .AddCard("metric-orders", card => card.WithChild("orders-content")) .AddColumn("orders-content", col => col .AddChild("orders-label") .AddChild("orders-value") .AddChild("orders-change")) .AddText("orders-label", text => text .WithText("订单数") .WithUsageHint("caption")) .AddText("orders-value", text => text .WithText($"{data.Orders:N0}") .WithUsageHint("h2")) .AddText("orders-change", text => text .WithText($"↑ {data.OrdersChange:P1}"));
// 图表区域(可以集成Chart.js等) builder .AddColumn("charts", col => col .AddChild("chart-revenue") .AddChild("chart-users")) .AddCard("chart-revenue", card => card.WithChild("chart-revenue-img")) .AddImage("chart-revenue-img", img => img .WithUrl(GenerateChartUrl(data.RevenueHistory)) .WithUsageHint("large-feature"));
[Fact] publicvoidResolveBoundValue_ShouldReturnLiteralString() { // Arrange var processor = new MessageProcessor(); var resolver = new DataBindingResolver(processor); var boundValue = new Dictionarystring, object> { ["literalString"] = "Hello" };
// Act var result = resolver.ResolveString(boundValue, "test-surface");
// Assert Assert.Equal("Hello", result); }
[Fact] publicvoidResolveBoundValue_ShouldReturnPathValue() { // Arrange var processor = new MessageProcessor(); processor.SetData("test-surface", "/user/name", "张三");
var resolver = new DataBindingResolver(processor); var boundValue = new Dictionarystring, object> { ["path"] = "/user/name" };
// Act var result = resolver.ResolveString(boundValue, "test-surface");
// Assert Assert.Equal("张三", result); }
9.2.2 测试用户交互
[Fact] publicasync Task UserAction_ShouldBeDispatched() { // Arrange var dispatcher = new EventDispatcher(); UserActionMessage? capturedAction = null;