mirror of
https://gitee.com/ant-design-blazor/ant-design-blazor.git
synced 2024-11-29 18:48:50 +08:00
feat(module: reusetabs): Add singleton page setting for reusing page instance. (#4151)
* feat(module: reusetabs): Add `NewPageForParams` to ReuseTabsPageAttribute. * Update NewPageForParams's default value. * rename the property * add a demo --------- Co-authored-by: James Yeung <shunjiey@hotmail.com>
This commit is contained in:
parent
57ad7125fe
commit
dc3ed2ad27
@ -12,7 +12,7 @@
|
||||
{
|
||||
@foreach (var item in ReuseTabsService.Pages)
|
||||
{
|
||||
<div class="ant-reuse-tabs-content @Class" @key="item.Url" style="@(item.Url == ReuseTabsService.CurrentUrl? "": "display:none;") @Style">
|
||||
<div class="ant-reuse-tabs-content @Class" @key="item.Key" style="@(item.Key == ReuseTabsService.ActiveKey? "": "display:none;") @Style">
|
||||
@item.Body
|
||||
</div>
|
||||
}
|
||||
|
@ -2,12 +2,12 @@
|
||||
@inherits AntDomComponentBase
|
||||
@inject NavigationManager _navigationManager
|
||||
|
||||
<Tabs Class="@Class" Style="@Style" Id="@Id" HideAdd Type="@TabType.EditableCard" @bind-ActiveKey="@ReuseTabsService.CurrentUrl" OnEdit="OnTabEdit" Draggable="@Draggable" Size="@Size">
|
||||
<Tabs Class="@Class" Style="@Style" Id="@Id" HideAdd Type="@TabType.EditableCard" @bind-ActiveKey="@ReuseTabsService.ActiveKey" OnEdit="OnTabEdit" Draggable="@Draggable" Size="@Size">
|
||||
@if (ReuseTabsService.Pages?.Any() == true)
|
||||
{
|
||||
@foreach (var item in ReuseTabsService.Pages)
|
||||
{
|
||||
<TabPane @key="@item.Url" Key="@item.Url" Class="@TabPaneClass" Closable="item.Closable" ForceRender>
|
||||
<TabPane @key="@item.Key" Key="@item.Key" Class="@TabPaneClass" Closable="item.Closable" ForceRender>
|
||||
<TabTemplate>
|
||||
@item.Title
|
||||
</TabTemplate>
|
||||
|
@ -109,7 +109,7 @@ namespace AntDesign
|
||||
if (action != "remove")
|
||||
return false;
|
||||
|
||||
return ReuseTabsService.ClosePage(key);
|
||||
return ReuseTabsService.ClosePageByKey(key);
|
||||
}
|
||||
|
||||
private void OnLocationChanged(object o, LocationChangedEventArgs _)
|
||||
|
@ -6,20 +6,58 @@ using System;
|
||||
|
||||
namespace AntDesign
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for ReuseTabsPage, used to set the page title and other properties.
|
||||
/// </summary>
|
||||
public class ReuseTabsPageAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the title of the tab.
|
||||
/// <para>
|
||||
/// If you want to set a <see cref="Microsoft.AspNetCore.Components.RenderFragment"/>, you need implement <see cref="IReuseTabsPage.GetPageTitle()"/> in the page.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Weather the page wont be reused.
|
||||
/// </summary>
|
||||
public bool Ignore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Weather the tab can be closed.
|
||||
/// </summary>
|
||||
public bool Closable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Weather the tab can be pinned and opened at the first time.
|
||||
/// </summary>
|
||||
public bool Pin { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The url of the pinned page. Because when the tab is clicked, it need to navigate to the page through the url.
|
||||
/// </summary>
|
||||
public string PinUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Weather the page is keeping alive.
|
||||
/// <para>
|
||||
/// If true, the page will be kept in memory, otherwise, the page will be destroyed when it is not active.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public bool KeepAlive { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The order of the page, the smaller the order, the earlier the page will be displayed.
|
||||
/// </summary>
|
||||
public int Order { get; set; } = 999;
|
||||
|
||||
/// <summary>
|
||||
/// Weather the page is a singleton.
|
||||
/// <para>
|
||||
/// If true, the page will be reused although the parameters is different, otherwise, another tab will be created.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public bool Singleton { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -26,5 +26,14 @@ namespace AntDesign
|
||||
public bool KeepAlive { get; set; } = true;
|
||||
|
||||
public int Order { get; set; } = 9999;
|
||||
|
||||
public string TypeName { get; set; }
|
||||
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Weather the page is a singleton. If true, the page will be reused although the url is different, otherwise, another tab will be created.
|
||||
/// </summary>
|
||||
public bool Singleton { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,7 @@ namespace AntDesign
|
||||
{
|
||||
private readonly NavigationManager _navmgr;
|
||||
private readonly MenuService _menusService;
|
||||
private readonly Dictionary<string, ReuseTabsPageItem> _pageMap = [];
|
||||
private IReadOnlyCollection<ReuseTabsPageItem> _pages;
|
||||
private ICollection<ReuseTabsPageItem> _pages = new List<ReuseTabsPageItem>();
|
||||
|
||||
internal event Action OnStateHasChanged;
|
||||
|
||||
@ -38,10 +37,27 @@ namespace AntDesign
|
||||
}
|
||||
}
|
||||
|
||||
private string _activeKey;
|
||||
|
||||
internal string ActiveKey
|
||||
{
|
||||
get => _activeKey;
|
||||
set
|
||||
{
|
||||
_activeKey = value;
|
||||
var pageItem = _pages.FirstOrDefault(r => r.Key == value);
|
||||
if (pageItem != null && (pageItem.Url != CurrentUrl || pageItem.Body == null))
|
||||
{
|
||||
CurrentUrl = pageItem.Url;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The page information list of the currently opened page, which can be used for caching and recovery
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<ReuseTabsPageItem> Pages => _pages;
|
||||
public IReadOnlyCollection<ReuseTabsPageItem> Pages => [.. _pages];
|
||||
|
||||
public ReuseTabsService(NavigationManager navmgr, MenuService menusService)
|
||||
{
|
||||
@ -60,11 +76,9 @@ namespace AntDesign
|
||||
/// <param name="titleTemplate">The title show on the tab</param>
|
||||
public void CreateTab(string pageUrl, RenderFragment titleTemplate = null)
|
||||
{
|
||||
if (_pageMap.ContainsKey(pageUrl))
|
||||
{
|
||||
if (_pages.Any(x => x.Url == pageUrl))
|
||||
return;
|
||||
}
|
||||
AddPage(pageUrl, new ReuseTabsPageItem() { Url = pageUrl, Title = titleTemplate ?? pageUrl.ToRenderFragment(), CreatedAt = DateTime.MinValue });
|
||||
AddPage(new ReuseTabsPageItem() { Url = pageUrl, Title = titleTemplate ?? pageUrl.ToRenderFragment(), CreatedAt = DateTime.MinValue });
|
||||
OnStateHasChanged?.Invoke();
|
||||
}
|
||||
|
||||
@ -75,12 +89,7 @@ namespace AntDesign
|
||||
/// <param name="title">The title show on the tab</param>
|
||||
public void CreateTab(string pageUrl, string title)
|
||||
{
|
||||
if (_pageMap.ContainsKey(pageUrl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
AddPage(pageUrl, new ReuseTabsPageItem() { Url = pageUrl, Title = title.ToRenderFragment(), CreatedAt = DateTime.MinValue });
|
||||
OnStateHasChanged?.Invoke();
|
||||
CreateTab(pageUrl, title.ToRenderFragment());
|
||||
}
|
||||
|
||||
//public void Pin(string key)
|
||||
@ -95,30 +104,48 @@ namespace AntDesign
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Close the page corresponding to the specified key
|
||||
/// Close the page corresponding to the specified url
|
||||
/// </summary>
|
||||
/// <param name="key">The specified page's key</param>
|
||||
public bool ClosePage(string key)
|
||||
/// <param name="url">The specified page's url</param>
|
||||
public bool ClosePage(string url)
|
||||
{
|
||||
var reuseTabsPageItem = _pages?.FirstOrDefault(w => w.Url == key);
|
||||
var reuseTabsPageItem = _pages?.FirstOrDefault(w => w.Url == url);
|
||||
if (reuseTabsPageItem?.Closable != true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RemovePageBase(key);
|
||||
RemovePageBase(reuseTabsPageItem.Url);
|
||||
StateHasChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close all pages except the page with the specified key
|
||||
/// Close the page corresponding to the specified key
|
||||
/// </summary>
|
||||
/// <param name="key">The specified page's key</param>
|
||||
public void CloseOther(string key)
|
||||
internal bool ClosePageByKey(string key)
|
||||
{
|
||||
foreach (var item in _pages?.Where(x => x.Closable && x.Url != key && !x.Pin))
|
||||
var reuseTabsPageItem = _pages?.FirstOrDefault(w => w.Key == key);
|
||||
if (reuseTabsPageItem?.Closable != true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RemovePageBase(reuseTabsPageItem.Url);
|
||||
StateHasChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close all pages except the page with the specified url
|
||||
/// </summary>
|
||||
/// <param name="url">The specified page's url</param>
|
||||
public void CloseOther(string url)
|
||||
{
|
||||
foreach (var item in _pages?.Where(x => x.Closable && x.Url != url && !x.Pin))
|
||||
{
|
||||
RemovePageBase(item.Url);
|
||||
}
|
||||
@ -154,16 +181,20 @@ namespace AntDesign
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reload the page corresponding to the specified key
|
||||
/// Reload the page corresponding to the specified url
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
public void ReloadPage(string key)
|
||||
/// <param name="url"></param>
|
||||
public void ReloadPage(string url)
|
||||
{
|
||||
key ??= CurrentUrl;
|
||||
_pageMap[key].Body = null;
|
||||
if (CurrentUrl == key)
|
||||
url ??= CurrentUrl;
|
||||
var reuseTabsPageItem = _pages?.FirstOrDefault(w => w.Url == url);
|
||||
if (reuseTabsPageItem != null)
|
||||
{
|
||||
CurrentUrl = key; // auto reload current page, and other page would be load by tab navigation.
|
||||
// Reset content
|
||||
reuseTabsPageItem.Body = null;
|
||||
// auto reload current page, and other page would be load by tab navigation
|
||||
if (ActiveKey == reuseTabsPageItem.Key)
|
||||
ActiveKey = reuseTabsPageItem.Key;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
@ -198,22 +229,28 @@ namespace AntDesign
|
||||
|
||||
if (!reuse)
|
||||
{
|
||||
_pageMap.Clear();
|
||||
_pages.Clear();
|
||||
}
|
||||
var reuseTabsPageItem = _pages?.FirstOrDefault(w => w.Url == CurrentUrl || (w.Singleton && w.TypeName == routeData.PageType?.FullName));
|
||||
|
||||
var reuseTabsPageItem = _pageMap.ContainsKey(CurrentUrl) ? _pageMap[CurrentUrl] : null;
|
||||
if (reuseTabsPageItem == null)
|
||||
{
|
||||
reuseTabsPageItem = new ReuseTabsPageItem
|
||||
{
|
||||
Url = CurrentUrl,
|
||||
CreatedAt = DateTime.Now,
|
||||
TypeName = routeData.PageType.FullName
|
||||
};
|
||||
|
||||
AddPage(CurrentUrl, reuseTabsPageItem);
|
||||
AddPage(reuseTabsPageItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
reuseTabsPageItem.Url = CurrentUrl;
|
||||
}
|
||||
|
||||
reuseTabsPageItem.Body ??= CreateBody(routeData, reuseTabsPageItem);
|
||||
reuseTabsPageItem.Body = CreateBody(routeData, reuseTabsPageItem);
|
||||
ActiveKey = reuseTabsPageItem.Key;
|
||||
OnStateHasChanged?.Invoke();
|
||||
}
|
||||
|
||||
@ -262,6 +299,7 @@ namespace AntDesign
|
||||
pageItem.Pin = attr.Pin;
|
||||
pageItem.KeepAlive = attr.KeepAlive;
|
||||
pageItem.Order = attr.Order;
|
||||
pageItem.Singleton = attr.Singleton;
|
||||
}
|
||||
|
||||
pageItem.Title ??= b =>
|
||||
@ -300,8 +338,8 @@ namespace AntDesign
|
||||
this.AddReuseTabsPageItem(pageType);
|
||||
}
|
||||
}
|
||||
|
||||
CurrentUrl ??= _pages.FirstOrDefault()?.Url;
|
||||
if (CurrentUrl == null)
|
||||
ActiveKey = _pages.FirstOrDefault()?.Key;
|
||||
}
|
||||
|
||||
private void AddReuseTabsPageItem(Type pageType)
|
||||
@ -310,17 +348,21 @@ namespace AntDesign
|
||||
var reuseTabsAttribute = pageType.GetCustomAttribute<ReuseTabsPageAttribute>();
|
||||
|
||||
var url = reuseTabsAttribute?.PinUrl ?? routeAttribute.Template;
|
||||
var reuseTabsPageItem = new ReuseTabsPageItem();
|
||||
var reuseTabsPageItem = new ReuseTabsPageItem
|
||||
{
|
||||
Url = url,
|
||||
CreatedAt = DateTime.MinValue,
|
||||
TypeName = pageType.FullName
|
||||
};
|
||||
GetPageInfo(reuseTabsPageItem, pageType, url, Activator.CreateInstance(pageType));
|
||||
reuseTabsPageItem.CreatedAt = DateTime.MinValue;
|
||||
reuseTabsPageItem.Url = url;
|
||||
AddPage(url, reuseTabsPageItem);
|
||||
AddPage(reuseTabsPageItem);
|
||||
}
|
||||
|
||||
private void AddPage(string key, ReuseTabsPageItem pageItem)
|
||||
private void AddPage(ReuseTabsPageItem pageItem)
|
||||
{
|
||||
_pageMap.TryAdd(key, pageItem);
|
||||
_pages = _pageMap.Values.Where(x => !x.Ignore)
|
||||
pageItem.Key = pageItem.GetHashCode().ToString();
|
||||
_pages.Add(pageItem);
|
||||
_pages = _pages.Where(x => !x.Ignore)
|
||||
.OrderBy(x => x.CreatedAt)
|
||||
.ThenByDescending(x => x.Pin ? 1 : 0)
|
||||
.ThenBy(x => x.Order)
|
||||
@ -329,13 +371,12 @@ namespace AntDesign
|
||||
|
||||
private void RemovePageBase(string key)
|
||||
{
|
||||
_pageMap[key].Body = null;
|
||||
_pageMap.Remove(key);
|
||||
_pages = _pageMap.Values.Where(x => !x.Ignore)
|
||||
.OrderBy(x => x.CreatedAt)
|
||||
.ThenByDescending(x => x.Pin ? 1 : 0)
|
||||
.ThenBy(x => x.Order)
|
||||
.ToList();
|
||||
var pageItem = _pages.Where(x => x.Url == key).FirstOrDefault();
|
||||
if (pageItem != null)
|
||||
{
|
||||
pageItem.Body = null;
|
||||
_pages.Remove(pageItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -0,0 +1,54 @@
|
||||
@layout AntDesign.Docs.Demos.Experimental.ReuseTabs.demo.Layout_
|
||||
@inject NavigationManager navigationManager
|
||||
@implements IDisposable
|
||||
@implements IReuseTabsPage
|
||||
|
||||
@page "/reuse/Singleton"
|
||||
@page "/reuse/Singleton/{Id:int}"
|
||||
|
||||
@attribute [ReuseTabsPage(Singleton = true)]
|
||||
|
||||
<PageTitle>Singleton Page</PageTitle>
|
||||
|
||||
<h1>Singleton Page</h1>
|
||||
|
||||
<p>@text</p>
|
||||
|
||||
<p>@dynamicText</p>
|
||||
|
||||
<p><a href="/reuse/Singleton/@(NextId)?query=@(Random.Shared.Next())">Navigate to next id: @NextId</a></p>
|
||||
|
||||
|
||||
@code{
|
||||
|
||||
[Parameter] public int Id { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery] public string Query { get; set; }
|
||||
|
||||
private int NextId => Id + 1;
|
||||
|
||||
string text = "";
|
||||
string dynamicText = "";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
text = "OnInitialized only be called once, Id = " + Id;
|
||||
navigationManager.LocationChanged += OnLationChanged;
|
||||
}
|
||||
|
||||
private void OnLationChanged(object sender, EventArgs e)
|
||||
{
|
||||
dynamicText = $"Current Id = {Id}, Query = {Query}";
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public RenderFragment GetPageTitle()
|
||||
{
|
||||
return @<span> Singleton Page with Id @Id </span>;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
navigationManager.LocationChanged -= OnLationChanged;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
---
|
||||
order: 4
|
||||
iframe: 360
|
||||
link: /reuse/singleton
|
||||
title:
|
||||
zh-CN: 单例页面
|
||||
en-US: Singleton Page
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
如果页面是单例的,它会被不同参数复用,否则,当参数不同时会打开另一个页面。默认不是单例。
|
||||
|
||||
单例页面不会重新实例化,也不再执行初始化方法,因此需要监听页面导航事件来更新界面。
|
||||
|
||||
|
||||
## en-US
|
||||
|
||||
If the page is Singleton, it will be reused although the parameters is different, otherwise, another tab will be created.
|
||||
|
||||
The singleton page is not re-instantiated, nor is the `OnInitialized{Async}` method performed, so it needs to listen for location navigation events to update the UI.
|
@ -70,6 +70,9 @@ Used to implement in-application page tabs and page caching.
|
||||
| PinUrl | Specify the Url of the loaded page, and then open the page with a route parameter, such as `/order/1` | string | - |
|
||||
| KeepAlive| Whether to cache the page state | bool | true |
|
||||
| Order | The sequence number | int | 999 |
|
||||
| TypeName | The page's classsname | string | - |
|
||||
| Key | The page's key | string | - |
|
||||
| NewPageForParams | Whether to create a new page for route with different params | bool | false |
|
||||
|
||||
### IReuseTabsPage interface
|
||||
|
||||
@ -86,9 +89,9 @@ Used to control ReuseTabs in pages
|
||||
| --- | --- |
|
||||
| Pages | The information list of the currently opened pages can be used for caching and recovery |
|
||||
| CreateTab(string pageUrl, RenderFragment? title = null) | Create a tab, but do not navigate to the page, and initialize the page when you navigate to the page. |
|
||||
| ClosePage(string key) | Close the page with the specified key. |
|
||||
| CloseOther(string key) | Close all pages except those that specify key, `Cloasable=false`, or `Pin=true`. |
|
||||
| ClosePage(string url) | Close the page with the specified url. |
|
||||
| CloseOther(string url) | Close all pages except those that specify url, `Cloasable=false`, or `Pin=true`. |
|
||||
| CloseAll() | Close all pages except those that `Cloasable=false` or `Pin=true`.|
|
||||
| CloseCurrent() | Close current page. |
|
||||
| Update() | Update the state of current tab. When the variable referenced in `GetPageTitle()` changes, `Update()` needs to be called to update the tab display. |
|
||||
| ReloadPage(key) | Reload the page for the specified label, allowing the page components to be reinitialized without refreshing the browser. If no key is passed, reload the current page . |
|
||||
| ReloadPage(url) | Reload the page for the specified label, allowing the page components to be reinitialized without refreshing the browser. If no url is passed, reload the current page . |
|
||||
|
@ -71,6 +71,9 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/lkI2hNEDr2V/Tabs.svg
|
||||
| PinUrl | 固定加载页面的 Url,在打开路由有参数的页面时需要,比如 `/order/1` | string | - |
|
||||
| KeepAlive| 是否缓存页面状态 | bool | true |
|
||||
| Order | 标签顺序 | int | 999 |
|
||||
| TypeName | 当前页面对应的类别 | string | - |
|
||||
| Key | 当前标签页的关键字 | string | - |
|
||||
| NewPageForParams | 是否针对具有不同参数的路由创建新的页面 | bool | false |
|
||||
|
||||
### IReuseTabsPage 接口
|
||||
|
||||
@ -87,9 +90,9 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/lkI2hNEDr2V/Tabs.svg
|
||||
| --- | --- |
|
||||
| Pages | 当前打开过的页面信息列表,可自行用于缓存和恢复 |
|
||||
| CreateTab(string pageUrl, RenderFragment? title = null) | 创建一个标签,但不导航到该页面,等导航到该页面时才初始化这个页面。|
|
||||
| ClosePage(string key) | 关闭指定key的页面,key 就是 url。 |
|
||||
| CloseOther(string key) | 关闭除了指定key的页面,或者设置了 `Cloasable=false` 或 `Pin=true` 的页面。 |
|
||||
| ClosePage(string url) | 关闭指定url的页面。 |
|
||||
| CloseOther(string url) | 关闭除了指定url的页面,或者设置了 `Cloasable=false` 或 `Pin=true` 的页面。 |
|
||||
| CloseAll() | 关闭除了设置了 `Cloasable=false` 或者 `Pin=true` 的页面。 |
|
||||
| CloseCurrent() | 关闭当前页面。 |
|
||||
| Update() | 更新 Tab 状态。当 `GetPageTitle()` 中引用的变量发生变化时,需要调用 `Update()` 来更新 tab 的显示。 |
|
||||
| ReloadPage(key) | 重新加载指定标签的页面,让页面组件重新初始化,且无需刷新浏览器。不传key时重新加载当前页面。 |
|
||||
| ReloadPage(url) | 重新加载指定标签的页面,让页面组件重新初始化,且无需刷新浏览器。不传url时重新加载当前页面。 |
|
Loading…
Reference in New Issue
Block a user