You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor开发:无需类型检查展示POS交易项列表的优雅方案

问题描述

开发POS(销售点)页面UI时,需展示按添加时间排序的交易项列表,交易项包含三种类型:订单、分组订单、自定义项。它们共享Type、Description、Price基础信息,但展示需求不同:

  • 订单需显示包含的子项
  • 分组订单需显示包含的子订单
  • 自定义项需显示数量

当前通过if-else判断类型实现差异化展示,代码不够优雅;且交易项类来自外部类库,无法修改嵌入UI逻辑,同时必须按添加时间统一遍历排序,不能按类型拆分处理。

当前伪代码:

foreach (ITransactionItem item in transaction.AllItems.OrderBy(x => x.AddedDateTime))
{
    <text>{item.Description}</text>
    <text>{item.Price}</text>
    <div>
        if (item is TransactionOrder)
        {
            // Show order items
        }
        else if (item is TransactionGroupOrder)
        {
            // Show child orders
        }
        else if (item is TransactionCustomItem)
        {
            // Show item quantity
        }
    <div>
}
解决方案(Blazor环境适配)

1. 组件多态拆分(推荐方案)

将不同类型交易项的UI渲染逻辑拆分到独立Blazor组件,父组件仅负责循环渲染基础信息,根据类型匹配对应子组件:

步骤:

  • 为每种交易项创建专属子组件:
    • TransactionOrderComponent.razor:处理订单子项展示
    • TransactionGroupOrderComponent.razor:处理分组订单的子订单展示
    • TransactionCustomItemComponent.razor:处理自定义项的数量展示
  • 父组件中循环渲染:
@foreach (var item in transaction.AllItems.OrderBy(x => x.AddedDateTime))
{
    <div class="transaction-item-container">
        <p class="item-description">@item.Description</p>
        <p class="item-price">@item.Price</p>
        <div class="item-details">
            @if (item is TransactionOrder order)
            {
                <TransactionOrderComponent Order="order" />
            }
            else if (item is TransactionGroupOrder groupOrder)
            {
                <TransactionGroupOrderComponent GroupOrder="groupOrder" />
            }
            else if (item is TransactionCustomItem customItem)
            {
                <TransactionCustomItemComponent CustomItem="customItem" />
            }
            else
            {
                <span>未知交易项</span>
            }
        </div>
    </div>
}

优势:

  • 符合单一职责原则,每种类型的UI逻辑独立维护
  • 代码结构清晰,后续新增交易项类型只需添加新组件,无需修改父组件循环逻辑
  • Blazor组件的复用性和可维护性更强

2. 访问者模式适配(适配类库不可修改场景)

利用访问者模式将UI渲染逻辑从父组件中剥离,避免类型判断的耦合,同时适配外部类库无法修改的限制:

步骤:

  1. 定义访问者接口,声明每种交易项的处理方法:
public interface ITransactionItemVisitor
{
    RenderFragment Visit(TransactionOrder order);
    RenderFragment Visit(TransactionGroupOrder groupOrder);
    RenderFragment Visit(TransactionCustomItem customItem);
}
  1. 实现UI渲染访问者,封装各类型的渲染逻辑:
public class TransactionItemUiVisitor : ITransactionItemVisitor
{
    public RenderFragment Visit(TransactionOrder order)
    {
        return builder =>
        {
            builder.OpenElement(0, "div");
            builder.AddAttribute(1, "class", "order-subitems");
            foreach (var subItem in order.Items)
            {
                builder.OpenElement(2, "p");
                builder.AddContent(3, $"{subItem.Description} - {subItem.Price}");
                builder.CloseElement();
            }
            builder.CloseElement();
        };
    }

    public RenderFragment Visit(TransactionGroupOrder groupOrder)
    {
        return builder =>
        {
            builder.OpenElement(0, "div");
            builder.AddAttribute(1, "class", "group-child-orders");
            foreach (var childOrder in groupOrder.ChildOrders)
            {
                builder.OpenElement(2, "div");
                builder.AddContent(3, childOrder.Description);
                builder.CloseElement();
            }
            builder.CloseElement();
        };
    }

    public RenderFragment Visit(TransactionCustomItem customItem)
    {
        return builder =>
        {
            builder.OpenElement(0, "span");
            builder.AddAttribute(1, "class", "custom-item-quantity");
            builder.AddContent(2, $"数量: {customItem.Quantity}");
            builder.CloseElement();
        };
    }
}
  1. 父组件中使用访问者:
@{
    var uiVisitor = new TransactionItemUiVisitor();
}

@foreach (var item in transaction.AllItems.OrderBy(x => x.AddedDateTime))
{
    <div class="transaction-item-container">
        <p>@item.Description</p>
        <p>@item.Price</p>
        <div class="item-details">
            @RenderItemDetails(item, uiVisitor)
        </div>
    </div>
}

@code {
    private RenderFragment RenderItemDetails(ITransactionItem item, ITransactionItemVisitor visitor)
    {
        return item switch
        {
            TransactionOrder order => visitor.Visit(order),
            TransactionGroupOrder groupOrder => visitor.Visit(groupOrder),
            TransactionCustomItem customItem => visitor.Visit(customItem),
            _ => builder => builder.AddContent(0, "未知交易项")
        };
    }
}

优势:

  • 所有UI渲染逻辑集中到访问者类,父组件无需处理类型判断细节
  • 新增交易项类型时,只需扩展访问者接口和实现,符合开闭原则
  • 完美适配外部类库无法修改的限制

3. Switch表达式简化条件判断(轻量方案)

如果不想拆分组件或使用访问者,可利用C#的switch表达式替代繁琐的if-else,让代码更简洁:

@foreach (var item in transaction.AllItems.OrderBy(x => x.AddedDateTime))
{
    <div class="transaction-item-container">
        <p>@item.Description</p>
        <p>@item.Price</p>
        <div class="item-details">
            @(item switch
            {
                TransactionOrder order => @<div class="subitems">
                    @foreach (var sub in order.Items)
                    {
                        <p>@sub.Description - @sub.Price</p>
                    }
                </div>,
                TransactionGroupOrder group => @<div class="child-orders">
                    @foreach (var child in group.ChildOrders)
                    {
                        <div>@child.Description</div>
                    }
                </div>,
                TransactionCustomItem custom => @<span>数量: @custom.Quantity</span>,
                _ => @<span>未知交易项</span>
            })
        </div>
    </div>
}

优势:

  • 相比if-else更简洁直观,代码可读性更高
  • 无需额外组件或类,适合交易项类型较少的场景

内容的提问来源于stack exchange,提问作者Conman_123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 08:50:58