3

实现简单的`Blazor`低代码 - tokengo

 1 year ago
source link: https://www.cnblogs.com/hejiale010426/p/17123263.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本篇博客只实现基本的低代码,比如新增组件,动态修改组件参数

首先创建一个空的Blazor Server,并且命名LowCode.Web

实现我们还需要引用一个Blazor组件库,由于作者用Masa Blazor比较多所以使用Masa Blazor

安装Masa Blazor

Masa Blazor添加到项目依赖中

<ItemGroup>
	<PackageReference Include="Masa.Blazor" Version="1.0.0-preview.3" />
</ItemGroup>

修改Program.cs文件 增加了builder.Services.AddMasaBlazor();

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddMasaBlazor();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

打开_Imports.razor 添加以下代码

@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using LowCode.Web
@using Masa.Blazor
@using BlazorComponent
@using LowCode.Web.Components

修改Pages\_Host.cshtml,添加以下代码

@page "/"
@using Microsoft.AspNetCore.Components.Web
@namespace LowCode.Web.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <base href="~/"/>
    <link href="css/site.css" rel="stylesheet"/>
    <!-- masa blazor css style -->
    <link href="_content/Masa.Blazor/css/masa-blazor.min.css" rel="stylesheet"/>

    <!--icon file,import need to use-->
    <link href="https://cdn.masastack.com/npm/@("@mdi")/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/>
</head>
<body>
<component type="typeof(App)" render-mode="ServerPrerendered"/>

<div id="blazor-error-ui">
    <environment include="Staging,Production">
        An error has occurred. This application may no longer respond until reloaded.
    </environment>
    <environment include="Development">
        An unhandled exception has occurred. See browser dev tools for details.
    </environment>
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

<script src="_framework/blazor.server.js"></script>
<!--js(should lay the end of file)-->
<script src="_content/BlazorComponent/js/blazor-component.js"></script>
</body>
</html>

修改MainLayout.razor文件

@inherits LayoutComponentBase

<MApp>
    @Body
</MApp>

这样就完成安装了Masa Blazor

然后开始写实现

实现低代码组件设计

创建Components文件夹

创建渲染组件Component\Render.razor,添加以下代码

@using LowCode.Web.Components.Options
@RenderFragment

@code{
    /// <summary>
    /// 渲染组件
    /// </summary>
    [Parameter]
    public RenderFragment RenderFragment { get; set; }

    /// <summary>
    /// 渲染配置
    /// </summary>
    public GenerateMButtonOptions GenerateMButtonOptions { get; set; }

    /// <summary>
    /// 渲染动态代码
    /// </summary>
    public string Code { get; set; }
}

定义组件库模板 DynamicComponentGenerator.razor,由于cs文件不能razor模板,所以创建razor文件,添加以下代码,以下代码我们添加三个组件模板,

@using LowCode.Web.Components.Options

@code {
    public static (RenderFragment, string) GenerateMButton(GenerateMButtonOptions options)
    {
        // 定义模板默认数据
        RenderFragment template = @<MButton Block=options.block
                                            Height=options.height
                                            Width=options.width
                                            [email protected]
                                            [email protected]
                                            Dark=options.dark
                                            Attributes=options.attributes>@options.childContent</MButton>;

        // 模板定义代码 (存在问题)
        var data = $@"<MButton Block={options.block}
                             Height={options.height}
                             Width={options.width}
                             Class={options.Class}
                             Style={options.Style}
                             Dark={options.dark}
        Attributes={options.attributes}>{@options.childContent}</MButton>";

        return (builder => { builder.AddContent(0, template); }, data);
    }


    public static (RenderFragment, string) GenerateMCard(GenerateMButtonOptions options)
    {
        RenderFragment template = @<MCard Height=options.height
                                            Width=options.width
                                            [email protected]
                                            [email protected]
                                            Dark=options.dark
                                            Attributes=options.attributes>@options.childContent</MCard>;

        var data = $@"<MCard Height={options.height}
                             Width={options.width}
                             Class={options.Class}
                             Style={options.Style}
                             Dark={options.dark}
        Attributes={options.attributes}>{@options.childContent}</MCard>";

        return (builder => { builder.AddContent(0, template); }, data);
    }

    public static (RenderFragment, string) GenerateMAvatar(GenerateMButtonOptions options)
    {
        RenderFragment template = @<MAvatar Height=options.height
                                           Width=options.width
                                           [email protected]
                                           [email protected]
                                           Attributes=options.attributes>@options.childContent</MAvatar>;

        var data = $@"<MAvatar Height={options.height}
                             Width={options.width}
                             Class={options.Class}
                             Style={options.Style}
        Attributes={options.attributes}>{@options.childContent}</MAvatar>";

        return (builder => { builder.AddContent(0, template); }, data);
    }
}

添加Component\ComponentType.cs 组件枚举

namespace LowCode.Web.Components;

public enum ComponentType
{
    MButton,

    MCart,

    MAvatar
}

添加Component\ComponentLibrary.razor用于显示支持的组件

<div style="height: 100px">
    <MButton class="button" OnClick="() => OnClick?.Invoke(ComponentType.MButton)">
        <MIcon>
            mdi-card
        </MIcon>
        按钮
    </MButton>
    <MButton class="button" OnClick="() => OnClick?.Invoke(ComponentType.MCart)">
        <MIcon>mdi-id-card</MIcon>
        卡片
    </MButton>
    <MButton class="button" OnClick="() => OnClick?.Invoke(ComponentType.MAvatar)">
        <MIcon>mdi-id-card</MIcon>
        头像
    </MButton>
</div>

@code{

    public delegate void OnClickDelegate(ComponentType type);

    [Parameter]
    public OnClickDelegate? OnClick { get; set; }

}

<style>
    .button {
        margin: 5px;
    }
</style>

新增Component\Options\GenerateMButtonOptions.cs 添加以下代码 ,添加组件时的参数

using BlazorComponent;
using Microsoft.AspNetCore.Components;

namespace LowCode.Web.Components.Options;

public class GenerateMButtonOptions
{
    public string? height { get; set; }

    public string? width { get; set; }

    public bool block { get; set; }

    public bool dark { get; set; }

    public string Style { get; set; } = string.Empty;
    public string Class { get; set; } = string.Empty;

    public Dictionary<string, object>? attributes { get; set; } = new();

    public RenderFragment? childContent { get; set; }
}

然后修改Pages\Index.razor

@page "/"
@using LowCode.Web.Components.Options

<MRow NoGutters>
    <MCol>
        <MCard Class="pa-1"
               Outlined
               Style="height: 100vh"
               tile>

            <ComponentLibrary OnClick="CreateOnClick"></ComponentLibrary>

        </MCard>
        </MCol>
        <MCol Order="2"
          Cols="12"
          Sm="6"
          Md="8">
        <MCard Class="pa-2"
               Outlined
               Style="height: 100vh"
               tile>
            @foreach (var item in Renders)
            {
                <render @onclick="() => Id = item.Key">
                    @item.Value.RenderFragment
                </render>
            }
        </MCard>
    </MCol>
    <MCol Order="3">
        <MCard Class="pa-2"
               Outlined
               Style="height:100vh"
               tile>
            <MCard>
                @*TODO:通过反射实现获取组件参数根据参数类型显示指定组件动态修改参数*@
                @foreach (var item in Renders)
                {
                    var options = item.Value.GenerateMButtonOptions;
                    if (item.Key == Id)
                    {
                        <MTextField @bind-Value="options.width" Label="width"></MTextField>
                        <MTextField @bind-Value="options.height" Label="height"></MTextField>
                        <MTextField @bind-Value="options.Style" Label="Style"></MTextField>
                        <MTextField @bind-Value="options.Class" Label="Class"></MTextField>
                        <MDivider></MDivider>
                        <MButton OnClick="() => AddOptionsAttribute(options.attributes)" Block>新增扩展参数输入框</MButton>
                        @foreach (var e in options.attributes)
                        {
                            <MTextarea NoResize Rows="1" Value="@e.Key" ValueChanged="(v) => { options.attributes.Remove(e.Key);options.attributes.Add(v,e.Value);}"></MTextarea>
                            <MTextarea NoResize Rows="1" Value="@options.attributes[e.Key].ToString()" ValueChanged="(v)=>options.attributes[e.Key]= v"></MTextarea>
                        }
                        <MButton Block OnClick="()=>DeleteComponent(item.Key)">删除</MButton>
                    }
                }
            </MCard>
        </MCard>
    </MCol>
</MRow>

@code {
    public string Id { get; set; }
    private Dictionary<string, Render> Renders = new();

    private RenderFragment RenderFragment { get; set; }

    private void AddOptionsAttribute(Dictionary<string, object> attribute)
    {
        attribute.Add("new","");
    }

    private void DeleteComponent(string key)
    {
        Renders.Remove(key);
    }

    private void CreateOnClick(ComponentType type)
    {
        GenerateMButtonOptions options = null;
        string code;
        switch (type)
        {
            case ComponentType.MButton:
                options = new()
                {
                    childContent = @<span>新建的按钮</span>,
                    attributes = new Dictionary<string, object>(),
                    width = "100px",
                };

                (RenderFragment, code) = DynamicComponentGenerator.GenerateMButton(options);

                Renders.Add(Guid.NewGuid().ToString("N"), new Render() { RenderFragment = RenderFragment, GenerateMButtonOptions = options, Code = code });

                break;
            case ComponentType.MCart:

                options = new()
                {
                    childContent = @<MButton>多个按钮</MButton>,
                    attributes = new Dictionary<string, object>(),
                    width = "100px",
                };

                (RenderFragment, code) = DynamicComponentGenerator.GenerateMCard(options);

                Renders.Add(Guid.NewGuid().ToString("N"), new Render() { RenderFragment = RenderFragment, GenerateMButtonOptions = options, Code = code });
                break;
            case ComponentType.MAvatar:
                options = new()
                {
                    childContent = @<MImage Src="https://cdn.masastack.com/stack/images/website/masa-blazor/jack.png" Alt="Jack"></MImage>,
                    attributes = new Dictionary<string, object>(),
                    width = "100px",
                };

                (RenderFragment, code) = DynamicComponentGenerator.GenerateMAvatar(options);

                Renders.Add(Guid.NewGuid().ToString("N"), new Render() { RenderFragment = RenderFragment, GenerateMButtonOptions = options, Code = code });
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(type), type, null);
        }

        StateHasChanged();
    }
}

这样就实现了整个简单的低代码操作,我们可以使用看看效果,简单了解实现原理

我们定义了组件的模板,这个模板是固定的,通过Blazor提供的双向绑定实现动态修改组件参数,这种方式可能不太适合完整的低代码,但是也是不错的思路,

2415052-20230215153353320-894778428.gif

这个项目还处于Demo阶段,不知道是否有大佬一块研究,研究技术极限,Blazor非常好用,推荐

GitHub
项目是MIT开源,希望大佬一块学习,促进Blazor生态

来着Token的分享


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK