zoukankan      html  css  js  c++  java
  • BookStore示例项目---菜单栏UI分析

    部署

    参照 ABP示例项目BookStore搭建部署

    项目解构

    1)、动态脚本代理

    启动项目时,默认会调用两个接口

    /Abp/ApplicationConfigurationScript
    /Abp/ServiceProxyScript
    

    ServiceProxyScript会解析项目路由,动态生成api路径。此两个接口封装在了Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic程序集中。一旦引用该程序集便会自动调用接口。

    1.1)、虚拟文件系统

    说到虚拟文件系统,先要了解 嵌入资源文件。简而言之,就是以程序调用的形式访问文件。对于虚拟文件系统的了解,可以参考:

    基于ASP.NET Core的模块化设计: 虚拟文件系统

    ABP虚拟文件系统(VirtualFileSystem)实例------定制菜单栏显示用户姓名

    1.2)、小结

    上面说到的动态脚本代理是如何调用的?在模块 Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared 中有一类cshtml,它是嵌入式资源文件,以PageAccount文件夹下_ViewStart.cshtml为例:

    @using Volo.Abp.AspNetCore.Mvc.UI.Theming
    @inject IThemeManager ThemeManager
    @{
        Layout = ThemeManager.CurrentTheme.GetApplicationLayout();
    }
    

    在这里调用Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic中的GetApplicationLayout方法:

    public virtual string GetLayout(string name, bool fallbackToDefault = true)
    {
        switch (name)
        {
            case StandardLayouts.Application:
                return "~/Themes/Basic/Layouts/Application.cshtml";
            case StandardLayouts.Account:
                return "~/Themes/Basic/Layouts/Account.cshtml";
            case StandardLayouts.Empty:
                return "~/Themes/Basic/Layouts/Empty.cshtml";
            default:
                return fallbackToDefault ? "~/Themes/Basic/Layouts/Application.cshtml" : null;
        }
    }
    

    而这三个cshtml视图文件都包含了这么一段脚本:

    <script src="~/Abp/ApplicationConfigurationScript"></script>
    <script src="~/Abp/ServiceProxyScript"></script>
    

    如此便调用了后端方法生成动态脚本,同时我们可以改造这里的视图,用来定制网站的菜单栏等UI界面。

    2)、UI界面菜单栏分析

    2.1)、ABP UI界面单测项目分析

    ABP简单菜单栏分析,项目源码:https://github.com/abpframework/abp/tree/dev/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo

    如图:
    image

    由上面得知,开始调用layout下的视图文件,用以加载动态js代理,但是同时还会去渲染菜单导航栏。

    <body class="abp-application-layout bg-light">
        @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.First, StandardLayouts.Application)
    
        @(await Component.InvokeAsync<MainNavbarViewComponent>())
    
        <div class="@containerClass">
            @(await Component.InvokeAsync<PageAlertsViewComponent>())
            <div id="AbpContentToolbar">
                <div class="text-right mb-2">
                    @RenderSection("content_toolbar", false)
                </div>
            </div>
            @RenderBody()
        </div>
    
        <abp-script-bundle name="@BasicThemeBundles.Scripts.Global" />
    
        <script src="~/Abp/ApplicationConfigurationScript"></script>
        <script src="~/Abp/ServiceProxyScript"></script>
    
        @await Component.InvokeAsync(typeof(WidgetScriptsViewComponent))
    
        @await RenderSectionAsync("scripts", false)
    
        @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Application)
    </body>
    

    MainNavbarViewComponent类会加载一个视图,此视图渲染整个导航栏。

    <nav class="navbar navbar-expand-md navbar-dark bg-dark shadow-sm flex-column flex-md-row mb-4" id="main-navbar" style="min-height: 4rem;">
        <div class="container">
            @(await Component.InvokeAsync<MainNavbarBrandViewComponent>())
            <button class="navbar-toggler" type="button" data-toggle="collapse"
                    data-target="#main-navbar-collapse" aria-controls="main-navbar-collapse"
                    aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="main-navbar-collapse">
                <ul class="navbar-nav mx-auto">
                    @(await Component.InvokeAsync<MainNavbarMenuViewComponent>())
                </ul> 
                <ul class="navbar-nav"> 
                    @(await Component.InvokeAsync<MainNavbarToolbarViewComponent>())
                </ul>
            </div>
        </div>
    </nav>
    

    2.2)、BookStore示例项目应用的UI扩展点

    在上面的代码中,涉及到了两个类:MainNavbarBrandViewComponentMainNavbarMenuViewComponent。如此这里便有两个扩展点,首先就是IBrandingProvider接口。在MainNavbarBrandViewComponent源码中会这么调用该接口:

    @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components
    @inject IBrandingProvider BrandingProvider
    <a class="navbar-brand" href="~/">@BrandingProvider.AppName</a>
    

    ABP源码有一个继承自该接口的默认类:

    public class DefaultBrandingProvider : IBrandingProvider, ITransientDependency
    {
        public virtual string AppName => "MyApplication";
    
        public virtual string LogoUrl => null;
    }
    

    BookStore项目中的扩展点:

    namespace Acme.BookStore.Web
    {
        [Dependency(ReplaceServices = true)]
        public class BookStoreBrandingProvider : DefaultBrandingProvider
        {
            public override string AppName => "BookStore";
        }
    }
    

    MainNavbarMenuViewComponent类源码中会调用一个视图:

    @using Volo.Abp.UI.Navigation
    @model ApplicationMenu
    @foreach (var menuItem in Model.Items)
    {
        var elementId = string.IsNullOrEmpty(menuItem.ElementId) ? string.Empty : $"id="{menuItem.ElementId}"";
        var cssClass = string.IsNullOrEmpty(menuItem.CssClass) ? string.Empty : menuItem.CssClass;
        var disabled = menuItem.IsDisabled ? "disabled" : string.Empty;
        if (menuItem.IsLeaf)
        {
            if (menuItem.Url != null)
            {
                <li class="nav-item @cssClass @disabled" @elementId>
                    <a class="nav-link" href="@(menuItem.Url ?? "#")">
                        @if (menuItem.Icon != null)
                        {
                            if (menuItem.Icon.StartsWith("fa"))
                            {
                                <i class="@menuItem.Icon"></i>
                            }
                        }
                        @menuItem.DisplayName
                    </a>
                </li>
            }
        }
        else
        {
            <li class="nav-item">
                <div class="dropdown">
                    <a class="nav-link dropdown-toggle" href="#" id="Menu_@(menuItem.Name)" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                        @if (menuItem.Icon != null)
                        {
                            if (menuItem.Icon.StartsWith("fa"))
                            {
                                <i class="@menuItem.Icon"></i>
                            }
                        }
                        @menuItem.DisplayName
                    </a>
                    <div class="dropdown-menu border-0 shadow-sm" aria-labelledby="Menu_@(menuItem.Name)">
                        @foreach (var childMenuItem in menuItem.Items)
                        {
                            @await Html.PartialAsync("~/Themes/Basic/Components/Menu/_MenuItem.cshtml", childMenuItem)
                        }
                    </div>
                </div>
            </li>
        }
    }
    

    在这里就会显示菜单栏及其子菜单。那么这么的扩展点在哪里呢?在模块类中有这么一个配置菜单的方法:

    Configure<AbpNavigationOptions>(options =>
    {
        options.MenuContributors.Add(new DefaultMenuContributor());
    });
    

    如果我们可以参考DefaultMenuContributor类的实现,扩展自己的菜单。

    BookStore示例项目的扩展点:

    public class BookStoreMenuContributor : IMenuContributor
    {
        public async Task ConfigureMenuAsync(MenuConfigurationContext context)
        {
            if (context.Menu.Name == StandardMenus.Main)
            {
                await ConfigureMainMenuAsync(context);
            }
        }
    
        // 配置菜单栏的 显示
        private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
        {
            if (!MultiTenancyConsts.IsEnabled)
            {
                var administration = context.Menu.GetAdministration();
                administration.TryRemoveMenuItem(TenantManagementMenuNames.GroupName);
            }
    
            var l = context.ServiceProvider.GetRequiredService<IStringLocalizer<BookStoreResource>>();
    
            context.Menu.Items.Insert(0, new ApplicationMenuItem("BookStore.Home", l["Menu:Home"], "/"));
    
            context.Menu.AddItem(
                new ApplicationMenuItem("BooksStore", l["Menu:BookStore"])
                    .AddItem(new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books"))
            );
        }
    }
    

    3)、菜单栏多语言显示

    image

    这是ABP示例项目BookStore的菜单栏,前面两个在上面已经有了描述,而多语言的显示是怎么渲染加载出来的呢?

    在ABP的源码中,有多个模块专门处理UI界面。其中,有一个基础的模块,就是我们前面提到的
    Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic模块。在这里处理基本的一些UI主题界面,比如,菜单栏,工具栏等。

    namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic
    {
        [DependsOn(
            typeof(AbpAspNetCoreMvcUiThemeSharedModule),
            typeof(AbpAspNetCoreMvcUiMultiTenancyModule)
            )]
        public class AbpAspNetCoreMvcUiBasicThemeModule : AbpModule
        {
            public override void ConfigureServices(ServiceConfigurationContext context)
            {
                // 添加基础 主题
                Configure<AbpThemingOptions>(options =>
                {
                    options.Themes.Add<BasicTheme>();
    
                    if (options.DefaultThemeName == null)
                    {
                        options.DefaultThemeName = BasicTheme.Name;
                    }
                });
    
                // 添加嵌入资源文件
                Configure<AbpVirtualFileSystemOptions>(options =>
                {
                    options.FileSets.AddEmbedded<AbpAspNetCoreMvcUiBasicThemeModule>("Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic");
                });
    
                // 添加工具栏 (多语言)
                Configure<AbpToolbarOptions>(options =>
                {
                    options.Contributors.Add(new BasicThemeMainTopToolbarContributor());
                });
    
                // 样式及脚本捆绑
                Configure<AbpBundlingOptions>(options =>
                {
                    options
                        .StyleBundles
                        .Add(BasicThemeBundles.Styles.Global, bundle =>
                        {
                            bundle
                                .AddBaseBundles(StandardBundles.Styles.Global)
                                .AddContributors(typeof(BasicThemeGlobalStyleContributor));
                        });
    
                    options
                        .ScriptBundles
                        .Add(BasicThemeBundles.Scripts.Global, bundle =>
                        {
                            bundle
                                .AddBaseBundles(StandardBundles.Scripts.Global)
                                .AddContributors(typeof(BasicThemeGlobalScriptContributor));
                        });
                });
            }
        }
    }
    

    我们看看工具栏的处理类BasicThemeMainTopToolbarContributor

    public class BasicThemeMainTopToolbarContributor : IToolbarContributor
    {
        public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
        {
            if (context.Toolbar.Name != StandardToolbars.Main)
            {
                return;
            }
    
            if (!(context.Theme is BasicTheme))
            {
                return;
            }
    
            var languageProvider = context.ServiceProvider.GetService<ILanguageProvider>();
    
            //TODO: This duplicates GetLanguages() usage. Can we eleminate this?
            var languages = await languageProvider.GetLanguagesAsync();
            if (languages.Count > 1)
            {
                context.Toolbar.Items.Add(new ToolbarItem(typeof(LanguageSwitchViewComponent)));
            }
    
            if (context.ServiceProvider.GetRequiredService<ICurrentUser>().IsAuthenticated)
            {
                context.Toolbar.Items.Add(new ToolbarItem(typeof(UserMenuViewComponent)));
            }
        }
    }
    

    在这里有一个处理语言转换视图组件LanguageSwitchViewComponent和用户菜单视图组件UserMenuViewComponent。ILanguageProvider接口有一个默认实现类:

    public class DefaultLanguageProvider : ILanguageProvider, ITransientDependency
    {
        protected AbpLocalizationOptions Options { get; }
    
        public DefaultLanguageProvider(IOptions<AbpLocalizationOptions> options)
        {
            Options = options.Value;
        }
    
        public Task<IReadOnlyList<LanguageInfo>> GetLanguagesAsync()
        {
            return Task.FromResult((IReadOnlyList<LanguageInfo>)Options.Languages);
        }
    }
    

    这里的GetLanguagesAsync方法直接返回选项类AbpLocalizationOptions的Languages属性。而ABP开放出来的多语言配置接口就是这个属性,我们将多语言添加到这个属性中,ABP就会加载出来所有的多语言。

    BookStore项目的扩展:

    Configure<AbpLocalizationOptions>(options =>
    {
        options.Resources
            .Get<BookStoreResource>()
            .AddBaseTypes(
                typeof(AbpUiResource)
            );
    
        options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština"));
        options.Languages.Add(new LanguageInfo("en", "en", "English"));
        options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português"));
        options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe"));
        options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
    });
    

    ABP是如何加载渲染出来视图的呢?有这么一个类LanguageSwitchViewComponent,这个类在上面也有调用,前提就是要在选项类中添加多语言。源码如下:

    public class LanguageSwitchViewComponent : AbpViewComponent
    {
        private readonly ILanguageProvider _languageProvider;
    
        public LanguageSwitchViewComponent(ILanguageProvider languageProvider)
        {
            _languageProvider = languageProvider;
        }
    
        public async Task<IViewComponentResult> InvokeAsync()
        {
            var languages = await _languageProvider.GetLanguagesAsync();
            var currentLanguage = languages.FindByCulture(
                CultureInfo.CurrentCulture.Name,
                CultureInfo.CurrentUICulture.Name
            );
    
            var model = new LanguageSwitchViewComponentModel
            {
                CurrentLanguage = currentLanguage,
                OtherLanguages = languages.Where(l => l != currentLanguage).ToList()
            };
            
            return View("~/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml", model);
        }
    }
    

    Default.cshtml视图:

    @using System.Linq
    @using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Toolbar.LanguageSwitch
    @model LanguageSwitchViewComponentModel
    @if (Model.OtherLanguages.Any())
    {
        <div class="dropdown">
            <a class="nav-link dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                @Model.CurrentLanguage.DisplayName
            </a>
    
            <div class="dropdown-menu dropdown-menu-right border-0 shadow-sm" aria-labelledby="dropdownMenuLink">
                @foreach (var language in Model.OtherLanguages)
                {
                    <a class="dropdown-item" href="/Abp/Languages/Switch?culture=@(language.CultureName)&uiCulture=@(language.UiCultureName)&returnUrl=@Context.Request.Path">@language.DisplayName</a>
                }
            </div>
        </div> 
    }
    

    还有一个扩展点,也可以通过 扩展 IToolbarContributor 接口。可以参考BasicThemeMainTopToolbarContributor 类。

    ABP中处理菜单栏视图主要是在Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic模块中,涉及的文件如下:

    image

    如此,BookStore项目的菜单栏UI便分析完了。

  • 相关阅读:
    5.颜色空间转换
    Linux下的解压命令
    4.图像模糊/图像平滑
    insightface作者提供数据训练解读
    MXNetError: [05:53:50] src/operator/nn/./cudnn/cudnn_convolution-inl.h:287
    python中import cv2遇到的错误及安装方法
    docker 安装 mxnet
    95. Unique Binary Search Trees II
    236. Lowest Common Ancestor of a Binary Tree
    124. Binary Tree Maximum Path Sum
  • 原文地址:https://www.cnblogs.com/zhiyong-ITNote/p/12588650.html
Copyright © 2011-2022 走看看