BookStore示例项目---菜单栏UI分析
部署
项目解构
1)、动态脚本代理
启动项目时,默认会调用两个接口
/Abp/ApplicationConfigurationScript
/Abp/ServiceProxyScript
ServiceProxyScript会解析项目路由,动态生成api路径。此两个接口封装在了Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic
程序集中。一旦引用该程序集便会自动调用接口。
1.1)、虚拟文件系统
说到虚拟文件系统,先要了解 嵌入资源文件。简而言之,就是以程序调用的形式访问文件。对于虚拟文件系统的了解,可以参考:
ABP虚拟文件系统(VirtualFileSystem)实例------定制菜单栏显示用户姓名
1.2)、小结
上面说到的动态脚本代理是如何调用的?在模块 Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared
中有一类cshtml,它是嵌入式资源文件,以Page\Account文件夹下_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
如图:
由上面得知,开始调用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扩展点
在上面的代码中,涉及到了两个类:MainNavbarBrandViewComponent
、MainNavbarMenuViewComponent
。如此这里便有两个扩展点,首先就是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)、菜单栏多语言显示
这是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
模块中,涉及的文件如下:
如此,BookStore项目的菜单栏UI便分析完了。
BookStore示例项目---菜单栏UI分析的更多相关文章
- abp示例项目BookStore搭建部署
之前部署过BookStore项目,但是换了新电脑也想好好学习下这个示例项目,于是在新电脑上重新拉了Git上的ABP项目代码,一编译生成BookStore项目就报错,可以参考 abp示例项目BookSt ...
- andrdoi示例项目SampleSyncAdapter分析
概述 在sdk目录下有个示例项目SampleSyncAdapter,演示了 用户授权和同步适配器的一些内容,是个学习的很好范例.我读了很久,很多地方没搞明白,先把理解的一些记录下来. 通过学习该示例, ...
- 转:Android官方MVP架构示例项目解析
转自: http://www.infoq.com/cn/articles/android-official-mvp-architecture-sample-project-analysis 作者 吕英 ...
- Google官方MVP模式示例项目解析 todo-mvp
转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6700668.html 引言:在Google没有给出一套权威的架构实现之前,很多App项目在架构方面都有或多 ...
- Reveal UI 分析工具分析手机 App
上篇文章介绍了: Reveal UI 分析工具简单使用 这里介绍如何使用 Reveal UI 分析工具来进行手机 App UI 界面的分析. 前提准备: (1)已安装 Reveal 的 Mac (2) ...
- 针对网站的UI分析
PM对项目所有功能的把握,特别是UI 最差的UI,体现了团队的组织架构.其次,体现了产品的内部结构.最好,体现了用户的自然需求. 对于几种浏览器分别进行UI分析, (1)360的界面如今看来比较大众化 ...
- Sencha Cmd创建Ext JS示例项目
Sencha提供了免费的Cmd工具,可以用来创建Ext JS项目并提供了一些便利的功能. Sencha也在官方文档中提供了一个示例来演示如何创建一个Sample Login App. 本文就介绍一下这 ...
- Spring Boot 示例项目
Spring Boot 基于注解式开发 maven REST 示例项目 项目地址:https://github.com/windwant/spring-boot-service 项目地址: ...
- Aes加密/解密示例项目
#AesEncrypt:Aes加密/解密示例项目 <br> 附件中的“AesEncrypt.zip”是本项目的exe文件,可直接下载下来运行和查看. *高级加密标准(英语:Advanced ...
随机推荐
- this.props
React中的每一个组件,都包含有一个属性(props),属性主要是从父组件传递给子组件的,在组件内部,我们可以通过this.props获取属性对象
- Openstack neutron学习
最近在学习openstack neutron的东西,记录下自己的一些理解. 网络基础知识 Switches & Vlan交换机的作用是来连接设备,实现互通的.network host之间通过交 ...
- JS逆向某网站登录密码分析
声明: 本文仅供研究学习使用,请勿用于非法用途! 目标网站 aHR0cHM6Ly9hdXRoLmFsaXBheS5jb20vbG9naW4vaW5kZXguaHRt 今日目标网站是某知名支付网站,感觉 ...
- 5,Hadoop中的文件
1,文件结构 · bin:脚本和命令目录. · etc:配置文件目录. · sbin:命令目录,主要包含HDFS和YARN中各类服务的启动和关闭,依赖于bin中的脚本. · share:各个模块编译后 ...
- Grafana使用总结
最近工作需求学习了下grafana,根据创建的几个dashboard简要记录下创建过程. 本次使用了grafana做可视化展示,data source使用的rds是postgresql和时序数据库in ...
- CSS3详解:border color
继续我们的 ,大家觉得怎么样呢?
- proxyTable的配置
在dev环境下面: proxyTable: { '/api': { target: 'http://api.douban.com/v2', //主域名,以前我都写192.168.2.57:80,这里跨 ...
- 广告行业中那些趣事系列6:BERT线上化ALBERT优化原理及项目实践(附github)
摘要:BERT因为效果好和适用范围广两大优点,所以在NLP领域具有里程碑意义.实际项目中主要使用BERT来做文本分类任务,其实就是给文本打标签.因为原生态BERT预训练模型动辄几百兆甚至上千兆的大小, ...
- 内网渗透之跨边界传输 - LCX转发
跨边界转发 端口转发 lcx 流程 目标机 ./lcx -slave 跳板机ip 监听的端口 127.0.0.1 要转发的端口 跳板机(公网) ./lcx -listen 监听的端口 转发给攻击机访问 ...
- 软件WEB自动化测试工具之智能元素定位
江湖一直有着这么一句名言“天下武功,唯快不破".那么在软件测试领域,自然而然我们会想到软件自动化测试.软件自动化测试的实现自然离不开软件自动化测试工具.软件自动化测试工具是软件自动化的载体, ...