
Tabs 标签页
分隔内容上有关联但属于不同类别的数据集合。
Tab 组件从设计上采用模板的设计形式,使用本组件时通过将 TabItem
子组件添加到 TabItems
模板中即可
基础用法
基础的、简洁的标签页。
Demo
选项卡样式
通过 IsCard="true"
设置选项卡样式的标签页。
Demo
卡片化
通过 IsBorderCard="true"
设置卡片化的标签页。
Demo
图标
通过设置 TabItem
组件的 Icon
属性对标签页设置图标
Demo
关闭
通过设置 ShowClose
属性对标签页显示关闭按钮
Demo
位置
通过设置 Placement
属性更改标签位置,更改为左右时会出现上下滚动小箭头进行 TabItem
切换
Demo
自定义增加标签页触发器
通过调用组件 api 动态添加/删除 TabItem
Demo
内置其他组件
TabItem
中内置其他组件
Demo
程序动态添加 TabItem 面板
通过此功能可以实现侧边栏中点击菜单链接,右侧数据区上部呈现多标签
Demo
本例中右侧 Tab
面板内状态一直保持,关闭后重新打开时组件重新加载
网站 Header
实战 Tab 组件
通过设置 ShowExtendButtons
属性为 true
,开启组件左右移动按钮与关闭下拉菜单,实战中非常实用
Demo
Attributes
Loading...
Methods
Loading...
@page "/tabs"
<h3>Tabs 标签页</h3>
<h4>分隔内容上有关联但属于不同类别的数据集合。</h4>
<p>Tab 组件从设计上采用模板的设计形式,使用本组件时通过将 <code>TabItem</code> 子组件添加到 <code>TabItems</code> 模板中即可</p>
<Tips>
<p>
<code>Tab</code> 组件一般有两种用法:
<ul class="ul-demo">
<li>作为数据分割</li>
<li>页面导航</li>
</ul>
<div>本组件默认行为为数据分割,点击 <code>TabItem</code> 标题时并不会进行导航行为,如果需要进行地址栏导航时请设置 <code>ClickTabToNavigation</code> 属性为 <code>true</code>,此时点击 <code>TabItem</code> 标题时地址栏将会重定向导航,多用于后台管理系统与 <code>Menu</code> 组件进行联动使用,实战可参考 <a href="layout-page" target="_blank">后台模板模拟器</a> 中的 <code>多标签</code> 模式,如果有 <code>Razor 组件</code> 在额外的程序集中时,请正确设置 <code>AdditionalAssemblies</code> 属性值,以便标签组件内路由正确解析,相关文档 <a href="https://docs.microsoft.com/zh-cn/aspnet/core/blazor/fundamentals/routing?WT.mc_id=DT-MVP-5004174&view=aspnetcore-3.1#route-to-components-from-multiple-assemblies" target="_blank">[传送门]</a></div>
</p>
<p>本组件会根据宽度高度等进行自适应适配,适当的时候可以出现左右或者上下的滚动箭头</p>
</Tips>
<Block Title="基础用法" Introduction="基础的、简洁的标签页。">
<Tab>
<TabItem Text="用户管理">
<div>我是用户管理</div>
</TabItem>
<TabItem Text="菜单管理">
<div>我是菜单管理</div>
</TabItem>
<TabItem Text="角色管理">
<div>我是角色管理</div>
</TabItem>
<TabItem Text="部门管理">
<div>我是部门管理</div>
</TabItem>
</Tab>
</Block>
<Block Title="选项卡样式" Introduction='通过 <code>IsCard="true"</code> 设置选项卡样式的标签页。'>
<Tab IsCard="true">
<TabItem Text="用户管理">
<div>我是用户管理</div>
</TabItem>
<TabItem Text="菜单管理">
<div>我是菜单管理</div>
</TabItem>
<TabItem Text="角色管理">
<div>我是角色管理</div>
</TabItem>
<TabItem Text="部门管理">
<div>我是部门管理</div>
</TabItem>
</Tab>
</Block>
<Block Title="卡片化" Introduction='通过 <code>IsBorderCard="true"</code> 设置卡片化的标签页。'>
<Tab IsBorderCard="true">
<TabItem Text="用户管理">
<div>我是用户管理</div>
</TabItem>
<TabItem Text="菜单管理">
<div>我是菜单管理</div>
</TabItem>
<TabItem Text="角色管理">
<div>我是角色管理</div>
</TabItem>
<TabItem Text="部门管理">
<div>我是部门管理</div>
</TabItem>
</Tab>
</Block>
<Block Title="图标" Introduction='通过设置 <code>TabItem</code> 组件的 <code>Icon</code> 属性对标签页设置图标'>
<Tab IsCard="true">
<TabItem Text="用户管理" Icon="fa fa-user">
<div>我是用户管理</div>
</TabItem>
<TabItem Text="菜单管理" Icon="fa fa-dashboard">
<div>我是菜单管理</div>
</TabItem>
<TabItem Text="角色管理" Icon="fa fa-sitemap">
<div>我是角色管理</div>
</TabItem>
<TabItem Text="部门管理" Icon="fa fa-bank">
<div>我是部门管理</div>
</TabItem>
</Tab>
</Block>
<Block Title="关闭" Introduction='通过设置 <code>ShowClose</code> 属性对标签页显示关闭按钮'>
<Tips>
<p><code>Tab</code> 组件开启 <code>ShowClose</code> 后,<code>TabItem</code> 属性 <code>Closable</code> 可对标签页单独设置是否可关闭,默认为 <code>true</code>;本例中 <b>用户管理</b> 标签页不提供关闭功能</p>
</Tips>
<Tab IsCard="true" ShowClose="true">
<TabItem Text="用户管理" Icon="fa fa-user" Closable="false">
<div>我是用户管理</div>
</TabItem>
<TabItem Text="菜单管理" Icon="fa fa-dashboard">
<div>我是菜单管理</div>
</TabItem>
<TabItem Text="角色管理" Icon="fa fa-sitemap">
<div>我是角色管理</div>
</TabItem>
<TabItem Text="部门管理" Icon="fa fa-bank">
<div>我是部门管理</div>
</TabItem>
</Tab>
</Block>
<Block Title="位置" Introduction="通过设置 <code>Placement</code> 属性更改标签位置,更改为左右时会出现上下滚动小箭头进行 <code>TabItem</code> 切换">
<p class="text-center">
<div class="btn-group">
<button class="btn btn-primary" @onclick="e => SetPlacement(Placement.Top)">Top</button>
<button class="btn btn-primary" @onclick="e => SetPlacement(Placement.Right)">Right</button>
<button class="btn btn-primary" @onclick="e => SetPlacement(Placement.Bottom)">Bottom</button>
<button class="btn btn-primary" @onclick="e => SetPlacement(Placement.Left)">Left</button>
</div>
</p>
<Tab Placement="@BindPlacement" Height="200">
<TabItem Text="用户管理">
<div>我是用户管理</div>
</TabItem>
<TabItem Text="菜单管理">
<div>我是菜单管理</div>
</TabItem>
<TabItem Text="角色管理">
<div>我是角色管理</div>
</TabItem>
<TabItem Text="系统日志">
<div>我是系统日志</div>
</TabItem>
<TabItem Text="登录日志">
<div>我是登录日志</div>
</TabItem>
<TabItem Text="定时任务管理">
<div>我是定时任务管理</div>
</TabItem>
</Tab>
<Divider Text="Tab 分割线"></Divider>
<Tab Placement="@BindPlacement" IsCard="true" Height="200">
<TabItem Text="用户管理">
<div>我是用户管理</div>
</TabItem>
<TabItem Text="菜单管理">
<div>我是菜单管理</div>
</TabItem>
<TabItem Text="角色管理">
<div>我是角色管理</div>
</TabItem>
<TabItem Text="系统日志">
<div>我是系统日志</div>
</TabItem>
<TabItem Text="登录日志">
<div>我是登录日志</div>
</TabItem>
<TabItem Text="定时任务管理">
<div>我是定时任务管理</div>
</TabItem>
</Tab>
<Divider Text="Tab 分割线"></Divider>
<Tab Placement="@BindPlacement" IsBorderCard="true" Height="200">
<TabItem Text="用户管理">
<div>我是用户管理</div>
</TabItem>
<TabItem Text="菜单管理">
<div>我是菜单管理</div>
</TabItem>
<TabItem Text="角色管理">
<div>我是角色管理</div>
</TabItem>
<TabItem Text="系统日志">
<div>我是系统日志</div>
</TabItem>
<TabItem Text="登录日志">
<div>我是登录日志</div>
</TabItem>
<TabItem Text="定时任务管理">
<div>我是定时任务管理</div>
</TabItem>
</Tab>
</Block>
<Block Title="自定义增加标签页触发器" Introduction="通过调用组件 api 动态添加/删除 <code>TabItem</code>">
<p>
<Button Icon="fa fa-plus-circle" OnClickWithoutRender="@(() => AddTab(TabSet))" Text="添加">
</Button>
<Button Color="Color.Danger" Icon="fa fa-minus-circle" IsDisabled="@RemoveEndable" OnClickWithoutRender="@(() => RemoveTab(TabSet))" Text="移除">
</Button>
</p>
<Tab IsBorderCard="true" @ref="TabSet">
<TabItem Text="用户管理">
<div>我是用户管理</div>
</TabItem>
<TabItem Text="菜单管理">
<div>我是菜单管理</div>
</TabItem>
<TabItem Text="角色管理">
<div>我是角色管理</div>
</TabItem>
<TabItem Text="部门管理">
<div>我是部门管理</div>
</TabItem>
<TabItem Text="任务管理">
<div>我是任务管理</div>
</TabItem>
</Tab>
</Block>
<Block Title="内置其他组件" Introduction="<code>TabItem</code> 中内置其他组件">
<p>
<code>Tab</code> 组件各个面板内的内容默认是保持状态的,本例中面板切换时保持原有数据
</p>
<Tab IsBorderCard="true">
<TabItem Text="计数组件">
<Counter></Counter>
</TabItem>
<TabItem Text="天气预报">
<FetchData></FetchData>
</TabItem>
</Tab>
</Block>
<Block Title="程序动态添加 TabItem 面板" Introduction="通过此功能可以实现侧边栏中点击菜单链接,右侧数据区上部呈现多标签">
<p>
本例中右侧 <code>Tab</code> 面板内状态一直保持,关闭后重新打开时组件重新加载
</p>
<Layout SideWidth="120px" style="min-height: 180px; border: 1px solid #ddd; border-radius: 4px;">
<Header>
<div class="header">网站 Header</div>
</Header>
<Side>
<div style="border-right: 1px solid #e6e6e6; height: 100%; overflow: auto; padding: 1rem 0; background-color: #f8f9fa;">
<Menu Items="@GetSideMenuItems()" IsVertical="true" OnClick="@OnClickMenuItem" @ref="TabMenu" />
</div>
</Side>
<Main>
<div class="tab-main-demo">
<Tab @ref="TabSetMenu" ShowClose="true">
</Tab>
</div>
</Main>
</Layout>
</Block>
<Block Title="实战 Tab 组件" Introduction="通过设置 <code>ShowExtendButtons</code> 属性为 <code>true</code>,开启组件左右移动按钮与关闭下拉菜单,实战中非常实用">
<Tips>通过 <b>添加</b> <b>删除</b> 按钮动态调整 <code>TabItem</code> 数量,使其超出容器数量查看,左右移动效果;<b>用户管理</b> 设置为不可关闭;功能按钮无法关闭这个标签页</Tips>
<p>
<button type="button" class="btn btn-outline-primary" @onclick="@(e => AddTab(TabSet2))">
<i class="fa fa-plus-circle"></i>
<span>添加</span>
</button>
<button type="button" class="btn btn-outline-danger" disabled="@((TabSet2?.Items.Count() > 4) ? null : "true")" @onclick="@(e => RemoveTab(TabSet2!))">
<i class="fa fa-minus-circle"></i>
<span>移除</span>
</button>
</p>
<Tab ShowExtendButtons="true" ShowClose="true" @ref="TabSet2">
<TabItem Text="用户管理" Closable="false">
<div>我是用户管理</div>
</TabItem>
<TabItem Text="菜单管理">
<div>我是菜单管理</div>
</TabItem>
<TabItem Text="角色管理">
<div>我是角色管理</div>
</TabItem>
<TabItem Text="部门管理">
<div>我是部门管理</div>
</TabItem>
<TabItem Text="任务管理">
<div>我是任务管理</div>
</TabItem>
</Tab>
</Block>
<AttributeTable Items="@GetAttributes()" />
<MethodTable Items="@GetMethods()" />
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
using BootstrapBlazor.Components;
using BootstrapBlazor.Shared.Common;
using BootstrapBlazor.Shared.Pages.Components;
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
namespace BootstrapBlazor.Shared.Pages
{
/// <summary>
///
/// </summary>
public sealed partial class Tabs
{
[NotNull]
private Tab? TabSet { get; set; }
[NotNull]
private Tab? TabSet2 { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
var menuItem = TabMenu?.Items.FirstOrDefault();
if (menuItem != null)
{
await InvokeAsync(() =>
{
var _ = TabMenu?.OnClick?.Invoke(menuItem);
});
}
}
}
private static Task AddTab(Tab tabset)
{
var text = $"Tab {tabset.Items.Count() + 1}";
tabset.AddTab(new Dictionary<string, object?>
{
[nameof(TabItem.Text)] = text,
[nameof(TabItem.IsActive)] = true,
[nameof(TabItem.ChildContent)] = new RenderFragment(builder =>
{
var index = 0;
builder.OpenElement(index++, "div");
builder.AddContent(index++, $"我是新建的 Tab 名称是 {text}");
builder.CloseElement();
})
});
return Task.CompletedTask;
}
private bool RemoveEndable => (TabSet?.Items.Count() ?? 4) < 4;
private static Task RemoveTab(Tab tabset)
{
if (tabset.Items.Count() > 4)
{
var item = tabset.Items.Last();
tabset.RemoveTab(item);
}
return Task.CompletedTask;
}
private Placement BindPlacement = Placement.Top;
private void SetPlacement(Placement placement)
{
BindPlacement = placement;
}
private static IEnumerable<MenuItem> GetSideMenuItems()
{
return new List<MenuItem>
{
new MenuItem() { Text = "计数器" },
new MenuItem() { Text = "天气预报" }
};
}
[NotNull]
private Tab? TabSetMenu { get; set; }
[NotNull]
private Menu? TabMenu { get; set; }
private Task OnClickMenuItem(MenuItem item)
{
var text = item.Text;
var tabItem = TabSetMenu.Items.FirstOrDefault(i => i.Text == text);
if (tabItem == null) AddTabItem(text ?? "");
else TabSetMenu.ActiveTab(tabItem);
return Task.CompletedTask;
}
private void AddTabItem(string text) => TabSetMenu.AddTab(new Dictionary<string, object?>
{
[nameof(TabItem.Text)] = text,
[nameof(TabItem.IsActive)] = true,
[nameof(TabItem.ChildContent)] = text == "计数器" ? BootstrapDynamicComponent.CreateComponent<Counter>().Render() : BootstrapDynamicComponent.CreateComponent<FetchData>().Render()
});
/// <summary>
/// 获得属性方法
/// </summary>
/// <returns></returns>
private static IEnumerable<AttributeItem> GetAttributes() => new AttributeItem[]
{
// TODO: 移动到数据库中
new AttributeItem() {
Name = "IsBorderCard",
Description = "是否为带边框卡片样式",
Type = "boolean",
ValueList = "true/false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "IsCard",
Description = "是否为卡片样式",
Type = "boolean",
ValueList = "true/false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "ShowClose",
Description = "是否显示关闭按钮",
Type = "boolean",
ValueList = "true/false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "ShowExtendButtons",
Description = "是否显示扩展按钮",
Type = "boolean",
ValueList = " — ",
DefaultValue = "false"
},
new AttributeItem() {
Name = "ClickTabToNavigation",
Description = "点击标题时是否导航",
Type = "boolean",
ValueList = "true/false",
DefaultValue = "false"
},
new AttributeItem() {
Name = "Placement",
Description = "设置标签位置",
Type = "Placement",
ValueList = "Top|Right|Bottom|Left",
DefaultValue = "Top"
},
new AttributeItem() {
Name = "Height",
Description = "设置标签高度",
Type = "int",
ValueList = " — ",
DefaultValue = "0"
},
new AttributeItem() {
Name = "Items",
Description = "TabItem 集合",
Type = "IEnumerable<TabItemBase>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "ChildContent",
Description = "ChildContent 模板",
Type = "RenderFragment",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "AdditionalAssemblies",
Description = "额外程序集合,用于初始化路由",
Type = "IEnumerable<Assembly>",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem() {
Name = "OnClickTab",
Description = "点击 TabItem 标题时回调委托方法",
Type = "Func<TabItem, Task>",
ValueList = " — ",
DefaultValue = " — "
}
};
/// <summary>
/// 获得方法
/// </summary>
/// <returns></returns>
private static IEnumerable<MethodItem> GetMethods() => new MethodItem[]
{
// TODO: 移动到数据库中
new MethodItem() {
Name = "AddTab",
Description = "添加 TabItem 到 Tab 中",
Parameters = "TabItem",
ReturnValue = " — "
},
new MethodItem() {
Name = "RemoveTab",
Description = "移除 TabItem",
Parameters = "TabItem",
ReturnValue = " — "
},
new MethodItem() {
Name = "ActiveTab",
Description = "设置指定 TabItem 为激活状态",
Parameters = "TabItem",
ReturnValue = " — "
},
new MethodItem() {
Name = "ClickPrevTab",
Description = "切换到上一个标签方法",
Parameters = "",
ReturnValue = "Task"
},
new MethodItem() {
Name = "ClickNextTab",
Description = "切换到下一个标签方法",
Parameters = "",
ReturnValue = "Task"
},
new MethodItem() {
Name = "CloseCurrentTab",
Description = "关闭当前标签页方法",
Parameters = "",
ReturnValue = "Task"
},
new MethodItem() {
Name = "CloseOtherTabs",
Description = "关闭其他标签页方法",
Parameters = "",
ReturnValue = "Task"
},
new MethodItem() {
Name = "CloseAllTabs",
Description = "关闭所有标签页方法",
Parameters = "",
ReturnValue = "Task"
},
};
}
}