refactor: TabSet/PageSet 增加 Active 属性

This commit is contained in:
Argo Window10 2019-11-27 23:11:39 +08:00
parent 030cd53eb5
commit d1525e0651
15 changed files with 163 additions and 237 deletions

View File

@ -1,10 +1,8 @@
using Bootstrap.Admin.Extensions;
using Bootstrap.Admin.Shared;
using Bootstrap.Security;
using Microsoft.AspNetCore.Components;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Bootstrap.Admin.Components
{
@ -18,11 +16,6 @@ namespace Bootstrap.Admin.Components
/// </summary>
public TabSet? TabSet { get; set; }
/// <summary>
///
/// </summary>
public PageSet? PageSet { get; set; }
/// <summary>
///
/// </summary>
@ -39,36 +32,6 @@ namespace Bootstrap.Admin.Components
/// </summary>
public string DisplayName { get; set; } = "";
/// <summary>
/// 添加新 Tab 栏方法
/// </summary>
/// <param name="menu"></param>
public void AddTab(BootstrapMenu? menu)
{
if (menu != null)
{
TabSet?.AddTab(menu);
PageSet?.AddPage(menu);
}
}
/// <summary>
///
/// </summary>
/// <param name="tabId"></param>
public async Task CloseTab(string? tabId)
{
if (TabSet != null)
{
if (TabSet.TabCount == 1) RootLayout.NavigationManager?.NavigateTo(RootLayout.HomeUrl);
else
{
var pageId = await TabSet.CloseTab(tabId);
PageSet?.RemovePage(tabId, pageId);
}
}
}
/// <summary>
///
/// </summary>
@ -82,7 +45,7 @@ namespace Bootstrap.Admin.Components
var path = new Uri(requestUrl).PathAndQuery;
var menus = DataAccess.MenuHelper.RetrieveAllMenus(RootLayout.UserName);
var menu = menus.FirstOrDefault(menu => path.Contains(menu.Url.ToBlazorMenuUrl()));
AddTab(menu);
if (menu != null) TabSet?.AddTab(menu);
}
}
}

View File

@ -1,18 +1,23 @@
namespace Bootstrap.Admin.Components
{
/// <summary>
///
///
/// </summary>
public class PageContentAttributes
{
/// <summary>
///
/// 获得/设置 页面 ID
/// </summary>
public string? Id { get; set; }
/// <summary>
///
/// 获得/设置 页面名称
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// 获得/设置 是否显示
/// </summary>
public bool Active { get; set; }
}
}

View File

@ -1,85 +0,0 @@
using Bootstrap.Admin.Extensions;
using Bootstrap.Security;
using System.Collections.Generic;
using System.Linq;
namespace Bootstrap.Admin.Components
{
/// <summary>
///
/// </summary>
public class PageSetBase : BootstrapComponentBase
{
/// <summary>
///
/// </summary>
protected List<PageContentAttributes> Pages { get; set; } = new List<PageContentAttributes>();
private string? curId = "";
/// <summary>
///
/// </summary>
/// <param name="firstRender"></param>
protected override void OnAfterRender(bool firstRender)
{
if (!string.IsNullOrEmpty(curId))
{
// Add Page 后设置 active 状态
Activate(curId);
curId = "";
}
}
/// <summary>
///
/// </summary>
/// <param name="pageId"></param>
public void Activate(string? pageId) => JSRuntime.ActivePage(pageId);
/// <summary>
///
/// </summary>
/// <param name="menu"></param>
public void AddPage(BootstrapMenu menu)
{
var page = Pages.FirstOrDefault(p => p.Id == menu.Id);
if (page == null)
{
Pages.Add(new PageContentAttributes() { Id = menu.Id, Name = menu.Url });
curId = menu.Id;
StateHasChanged();
}
else
{
Activate(menu.Id);
}
}
/// <summary>
///
/// </summary>
/// <param name="pageId"></param>
/// <param name="newId"></param>
public void RemovePage(string? pageId, string? newId)
{
if (!string.IsNullOrEmpty(pageId))
{
var page = Pages.FirstOrDefault(p => p.Id == pageId);
if (page != null)
{
Pages.Remove(page);
curId = newId;
StateHasChanged();
}
}
}
/// <summary>
///
/// </summary>
public void RemoveAllPage()
{
Pages.Clear();
}
}
}

View File

@ -1,50 +1,57 @@
using Bootstrap.Admin.Extensions;
using Bootstrap.Security;
using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;
namespace Bootstrap.Admin.Components
{
/// <summary>
///
///
/// </summary>
public class TabBase : BootstrapComponentBase
{
/// <summary>
///
///
/// </summary>
[Parameter]
public string Title { get; set; } = "";
/// <summary>
///
///
/// </summary>
[Parameter]
public string? Id { get; set; }
/// <summary>
///
///
/// </summary>
[Parameter]
public string Url { get; set; } = "";
/// <summary>
///
///
/// </summary>
[Parameter]
public string Icon { get; set; } = "";
/// <summary>
///
/// </summary>
[Parameter]
public bool Active { get; set; }
private bool closeTab;
/// <summary>
///
///
/// </summary>
protected void CloseTab()
protected async Task CloseTab()
{
closeTab = true;
Layout?.CloseTab(Id);
if (Layout.TabSet != null) await Layout.TabSet.CloseTab(Id);
}
/// <summary>
///
///
/// </summary>
protected void ClickTab()
{
@ -53,7 +60,7 @@ namespace Bootstrap.Admin.Components
}
/// <summary>
///
///
/// </summary>
/// <param name="menu"></param>
public void Init(BootstrapMenu menu)
@ -62,6 +69,13 @@ namespace Bootstrap.Admin.Components
Url = menu.Url.ToBlazorMenuUrl();
Icon = menu.Icon;
Id = menu.Id;
Active = true;
}
/// <summary>
///
/// </summary>
/// <param name="val"></param>
public void SetActive(bool val) => Active = val;
}
}

View File

@ -8,23 +8,23 @@ using System.Threading.Tasks;
namespace Bootstrap.Admin.Components
{
/// <summary>
///
/// TabSet 组件类
/// </summary>
public class TabSetBase : BootstrapComponentBase
{
/// <summary>
///
/// 页面集合
/// </summary>
public int TabCount { get { return Tabs.Count; } }
protected List<PageContentAttributes> Pages { get; set; } = new List<PageContentAttributes>();
/// <summary>
///
/// Tab 集合
/// </summary>
protected List<Tab> Tabs { get; set; } = new List<Tab>();
private string? curId;
/// <summary>
///
/// 页面呈现后回调方法
/// </summary>
/// <param name="firstRender"></param>
protected override void OnAfterRender(bool firstRender)
@ -48,38 +48,56 @@ namespace Bootstrap.Admin.Components
{
tab = new Tab();
tab.Init(menu);
Tabs.ForEach(t => t.SetActive(false));
Tabs.Add(tab);
curId = tab.Id;
StateHasChanged();
Pages.ForEach(p => p.Active = false);
Pages.Add(new PageContentAttributes() { Id = menu.Id, Name = menu.Url, Active = true });
}
else
{
JSRuntime.ActiveMenu(tab.Id);
Tabs.ForEach(t => t.SetActive(t.Id == tab.Id));
Pages.ForEach(p => p.Active = p.Id == tab.Id);
}
curId = tab.Id;
StateHasChanged();
}
/// <summary>
///
/// 关闭指定标签页方法
/// </summary>
/// <param name="tabId"></param>
/// <returns></returns>
public async Task<string?> CloseTab(string? tabId)
public async Task CloseTab(string? tabId)
{
if (Tabs.Count == 1)
{
CloseAllTab();
return;
}
if (!string.IsNullOrEmpty(tabId))
{
var tab = Tabs.FirstOrDefault(tb => tb.Id == tabId);
if (tab != null)
{
// 移除 PageContent
var page = Pages.FirstOrDefault(p => p.Id == tab.Id);
if (page != null) Pages.Remove(page);
// 移除 Tab 返回下一个 TabId
tabId = await JSRuntime.RemoveTabAsync(tab.Id);
Tabs.Remove(tab);
tab = Tabs.FirstOrDefault(t => t.Id == tabId);
tab.SetActive(true);
page = Pages.FirstOrDefault(p => p.Id == tabId);
if (page != null) page.Active = true;
StateHasChanged();
}
}
return tabId;
}
/// <summary>
///
/// 关闭所有标签页方法
/// </summary>
protected void CloseAllTab()
{
@ -87,7 +105,31 @@ namespace Bootstrap.Admin.Components
}
/// <summary>
///
/// 关闭当前标签页方法
/// </summary>
protected async Task CloseCurrentTab()
{
var tabId = Tabs.FirstOrDefault(t => t.Active)?.Id;
await CloseTab(tabId);
}
/// <summary>
/// 关闭其他标签页方法
/// </summary>
protected void CloseOtherTab()
{
var tabId = Tabs.FirstOrDefault(t => t.Active)?.Id;
if (!string.IsNullOrEmpty(tabId))
{
Tabs.RemoveAll(t => t.Id != tabId);
Pages.RemoveAll(page => page.Id != tabId);
curId = tabId;
StateHasChanged();
}
}
/// <summary>
/// 前移标签页方法
/// </summary>
protected async Task MovePrev()
{
@ -96,7 +138,7 @@ namespace Bootstrap.Admin.Components
}
/// <summary>
///
/// 后移标签页方法
/// </summary>
/// <returns></returns>
protected async Task MoveNext()

View File

@ -33,16 +33,6 @@ namespace Bootstrap.Admin.Extensions
/// <returns></returns>
public static async ValueTask<string> MoveNextTabAsync(this IJSRuntime? jSRuntime) => jSRuntime == null ? "" : await jSRuntime.InvokeAsync<string>("$.moveNextTab");
/// <summary>
///
/// </summary>
/// <param name="jsRuntime"></param>
/// <param name="pageId"></param>
public static void ActivePage(this IJSRuntime? jsRuntime, string? pageId)
{
if (!string.IsNullOrEmpty(pageId) && jsRuntime != null) jsRuntime.InvokeVoidAsync("$.activePage", pageId);
}
/// <summary>
/// 移除指定 ID 的导航条
/// </summary>
@ -52,16 +42,9 @@ namespace Bootstrap.Admin.Extensions
public static async ValueTask<string> RemoveTabAsync(this IJSRuntime? jSRuntime, string? tabId) => string.IsNullOrEmpty(tabId) || jSRuntime == null ? "" : await jSRuntime.InvokeAsync<string>("$.removeTab", tabId);
/// <summary>
///
/// 启用动画
/// </summary>
/// <param name="jSRuntime"></param>
public static void EnableAnimation(this IJSRuntime? jSRuntime) => jSRuntime.InvokeVoidAsync("$.enableAnimation");
/// <summary>
///
/// </summary>
/// <param name="jSRuntime"></param>
/// <param name="val"></param>
public static void EnableBackground(this IJSRuntime? jSRuntime, bool val) => jSRuntime.InvokeVoidAsync("$.enableBackground", val);
}
}

View File

@ -39,17 +39,17 @@
<div class="row">
<div class="form-group col-sm-6 col-md-auto">
<label class="control-label" for="currentPassword">原密码: </label>
<input type="password" class="form-control" id="currentPassword" placeholder="原密码" maxlength="16" data-valid="true" />
<input type="password" class="form-control" id="currentPassword" autocomplete="off" placeholder="原密码" maxlength="16" data-valid="true" />
</div>
</div>
<div class="row">
<div class="form-group col-sm-6 col-md-auto">
<label class="control-label" for="newPassword">新密码: </label>
<input type="password" class="form-control" id="newPassword" placeholder="新密码" maxlength="16" data-valid="true" />
<input type="password" class="form-control" id="newPassword" autocomplete="off" placeholder="新密码" maxlength="16" data-valid="true" />
</div>
<div class="form-group col-sm-6 col-md-auto">
<label class="control-label" for="confirmPassword">确认密码: </label>
<input type="password" class="form-control" id="confirmPassword" placeholder="与新密码一致" maxlength="16" equalTo="#newPassword" data-valid="true" />
<input type="password" class="form-control" id="confirmPassword" autocomplete="off" placeholder="与新密码一致" maxlength="16" equalTo="#newPassword" data-valid="true" />
</div>
</div>
</form>

View File

@ -4,6 +4,5 @@
<CascadingValue Value=this Name="Admin">
<Section ShowCardTitle="@RootLayout.Model.ShowCardTitle" LockScreenPeriod=@RootLayout.Model.LockScreenPeriod>
<TabSet @ref="TabSet"></TabSet>
<PageSet @ref="PageSet"></PageSet>
</Section>
</CascadingValue>

View File

@ -3,7 +3,7 @@
@if (Menu.Menus.Any())
{
<li class="nav-item parent">
<a href="javascripts:void();" data-target="@string.Format("#collapse_{0}",Menu.Id)" data-toggle="collapse" id="@string.Format("menus_{0}",Menu.Id)" class="nav-link collapsed @(Menu.Active == "active" ? "show" : "")"><i class="@Menu.Icon"></i><span class="flex-fill">@Menu.Name</span><i class="fa fa-angle-left"></i></a>
<a href="javascripts:void();" data-target="@string.Format("#collapse_{0}",Menu.Id)" data-toggle="collapse" id="@string.Format("menus_{0}",Menu.Id)" class="nav-link @(Menu.Active == "active" ? "collapsed show" : "collapsed")"><i class="@Menu.Icon"></i><span class="flex-fill">@Menu.Name</span><i class="fa fa-angle-left"></i></a>
<div id="@string.Format("collapse_{0}",Menu.Id)" class="@(Menu.Active == "active" ? "show" : "collapse")">
<NavItems Menus="@Menu.Menus"></NavItems>
</div>

View File

@ -1,9 +0,0 @@
@inherits PageSetBase
@foreach (var comp in Pages)
{
<div id="@string.Format("page_{0}", comp.Id)" class="content d-none">
<PageContent @key="comp" Name="@comp.Name"></PageContent>
</div>
}

View File

@ -1,7 +1,5 @@
@inherits TabBase
<li>
<div class="nav-link" @onclick="ClickTab" id="@Id" url="@Url">
<i class="@Icon"></i><span>@Title</span><i class="fa fa-close" @onclick="CloseTab"></i>
</div>
</li>
<div class="@(Active ? "nav-link active" : "nav-link")" @onclick="ClickTab" id="@Id" url="@Url">
<i class="@Icon"></i><span>@Title</span><i class="fa fa-close" @onclick="CloseTab"></i>
</div>

View File

@ -1,19 +1,30 @@
@inherits TabSetBase
<nav class="d-flex">
<div class="nav-link-bar" @onclick="MovePrev"><i class="fa fa-chevron-left"></i></div>
<ul id="navBar" class="nav nav-tabs flex-fill flex-nowrap overflow-hidden">
@foreach (var tab in Tabs)
{
<Tab @key="@tab.Id" Id="@tab.Id" Icon="@tab.Icon" Url="@tab.Url.ToBlazorMenuUrl()" Title="@tab.Title"></Tab>
}
</ul>
<div class="nav-link-bar" @onclick="MoveNext"><i class="fa fa-chevron-right"></i></div>
<div class="dropdown nav-link-bar">
<div class="nav-link-close dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-chevron-down"></i></div>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="关闭标签下拉菜单">
<div class="dropdown-item" @onclick="CloseAllTab"><span>关闭所有标签</span></div>
<div class="dropdown-item"><span>其他全部关闭</span></div>
<nav>
<div class="d-flex">
<div class="nav-link-bar" @onclick="MovePrev"><i class="fa fa-chevron-left"></i></div>
<div id="navBar" class="nav nav-tabs flex-fill flex-nowrap overflow-hidden" role="tablist">
@foreach (var tab in Tabs)
{
<Tab @key="@tab.Id" Active="@tab.Active" Id="@tab.Id" Icon="@tab.Icon" Url="@tab.Url.ToBlazorMenuUrl()" Title="@tab.Title"></Tab>
}
</div>
<div class="nav-link-bar" @onclick="MoveNext"><i class="fa fa-chevron-right"></i></div>
<div class="dropdown nav-link-bar">
<div class="nav-link-close dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-chevron-down"></i></div>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="关闭标签下拉菜单">
<div class="dropdown-item" @onclick="CloseCurrentTab"><span>关闭当前标签</span></div>
<div class="dropdown-item" @onclick="CloseOtherTab"><span>关闭其他标签</span></div>
<div class="dropdown-item" @onclick="CloseAllTab"><span>关闭所有标签</span></div>
</div>
</div>
</div>
</nav>
<div class="tab-content">
@foreach (var comp in Pages)
{
<div id="@string.Format("page_{0}", comp.Id)" class="tab-pane @(comp.Active ? "fade active show" : "fade")" role="tabpanel" aria-labelledby="nav-profile-tab">
<PageContent @key="comp" Name="@comp.Name"></PageContent>
</div>
}
</div>

View File

@ -25,11 +25,18 @@ namespace Bootstrap.Admin
/// 构造函数
/// </summary>
/// <param name="configuration"></param>
public Startup(IConfiguration configuration)
/// <param name="env"></param>
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
Enviroment = env;
}
/// <summary>
/// 获得 当前运行时环境
/// </summary>
public IWebHostEnvironment Enviroment { get; }
/// <summary>
/// 获得 系统配置项 Iconfiguration 实例
/// </summary>
@ -76,7 +83,10 @@ namespace Bootstrap.Admin
options.Filters.Add<SignalRExceptionFilter<SignalRHub>>();
}).AddJsonOptions(op => op.JsonSerializerOptions.AddDefaultConverters());
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddServerSideBlazor().AddCircuitOptions(options =>
{
if (Enviroment.IsDevelopment()) options.DetailedErrors = true;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -1,4 +1,4 @@
nav {
nav {
margin-bottom: 10px;
}
@ -22,6 +22,7 @@ nav {
background-color: #4f9ecc;
}
.nav-tabs .nav-link, .nav-link-bar, .fa-close {
transition: all .3s linear;
}

View File

@ -1,4 +1,4 @@
(function ($) {
(function ($) {
$.fn.extend({
autoScrollSidebar: function (options) {
var option = $.extend({ target: null, offsetTop: 0 }, options);
@ -54,52 +54,46 @@
});
return source;
},
activePage: function (id) {
$('.main-content .content').addClass('d-none');
$('#page_' + id).removeClass('d-none');
},
activeMenu: function (id) {
var $menu = $('#menus_' + id);
$('.sidebar .active').removeClass('active');
$menu.addClass('active');
// set website title
$('head title').text($menu.text());
this.moveTab(id);
// 通过指定 ID 设置站点 Title
var menuId = 'menus_' + id;
var $curMenu = $('.sidebar .active').first();
if ($curMenu.attr('id') !== menuId) {
$curMenu.removeClass('active');
var $menu = $('#' + menuId).addClass('active');
// set website title
$('head title').text($menu.text());
this.resetTab(id);
}
},
removeTab: function (tabId) {
var nextId = $('#navBar').find('.active').first().attr('id');
// 通过当前 Tab 返回如果移除后新的 TabId
var activeTabId = $('#navBar').find('.active').first().attr('id');
var $curTab = $('#' + tabId);
if ($curTab.hasClass('active')) {
var $nextTab = $curTab.parent().next().find('.nav-link');
var $prevTab = $curTab.parent().prev().find('.nav-link');
if ($nextTab.length === 1) nextId = $nextTab.attr('id');
else if ($prevTab.length === 1) nextId = $prevTab.attr('id');
else nextId = "";
if (nextId !== "") {
this.activeMenu(nextId);
this.activePage(nextId);
}
var $nextTab = $curTab.next();
var $prevTab = $curTab.prev();
if ($nextTab.length === 1) activeTabId = $nextTab.attr('id');
else if ($prevTab.length === 1) activeTabId = $prevTab.attr('id');
else activeTabId = "";
}
return nextId;
return activeTabId;
},
moveTab: function (tabId) {
resetTab: function (tabId) {
// 通过计算 Tab 宽度控制滚动条显示完整 Tab
var $tab = $('#' + tabId);
if ($tab.length === 0) return;
var $navBar = $('#navBar');
$navBar.parent().addClass('d-flex').removeClass('d-none');
// reset active
$navBar.find('.active').removeClass('active');
$tab.addClass('active');
var $first = $navBar.children().first();
var marginLeft = $tab.position().left - 2 - $first.position().left;
var marginLeft = $tab.position().left - $first.position().left;
var scrollLeft = $navBar.scrollLeft();
if (marginLeft < scrollLeft) {
// overflow left
$navBar.scrollLeft(marginLeft);
return;
}
var marginRight = $tab.position().left + $tab.outerWidth() - $navBar.outerWidth();
if (marginRight < 0) return;
$navBar.scrollLeft(marginRight - $first.position().left);
@ -107,12 +101,12 @@
movePrevTab: function () {
var $navBar = $('#navBar');
var $curTab = $navBar.find('.active').first();
return $curTab.parent().prev().find('.nav-link').first().attr('url');
return $curTab.prev().attr('url');
},
moveNextTab: function () {
var $navBar = $('#navBar');
var $curTab = $navBar.find('.active').first();
return $curTab.parent().next().find('.nav-link').first().attr('url');
return $curTab.next().attr('url');
},
enableAnimation: function () {
$('body').removeClass('trans-mute');
@ -128,7 +122,7 @@
$(function () {
$(document)
.on('click', '.nav-link-bar.dropdown', function (e) {
.on('click', '.nav-tabs .nav-link', function (e) {
});
});