diff --git a/components/tabs/Reuse/ReusePages.razor b/components/tabs/Reuse/ReusePages.razor index 73485572..aa958933 100644 --- a/components/tabs/Reuse/ReusePages.razor +++ b/components/tabs/Reuse/ReusePages.razor @@ -12,7 +12,7 @@ { @foreach (var item in ReuseTabsService.Pages) { -
+
@item.Body
} diff --git a/components/tabs/Reuse/ReuseTabs.razor b/components/tabs/Reuse/ReuseTabs.razor index 655a3d8b..dfbead45 100644 --- a/components/tabs/Reuse/ReuseTabs.razor +++ b/components/tabs/Reuse/ReuseTabs.razor @@ -2,12 +2,12 @@ @inherits AntDomComponentBase @inject NavigationManager _navigationManager - + @if (ReuseTabsService.Pages?.Any() == true) { @foreach (var item in ReuseTabsService.Pages) { - + @item.Title diff --git a/components/tabs/Reuse/ReuseTabs.razor.cs b/components/tabs/Reuse/ReuseTabs.razor.cs index 88329c97..26d1b6ed 100644 --- a/components/tabs/Reuse/ReuseTabs.razor.cs +++ b/components/tabs/Reuse/ReuseTabs.razor.cs @@ -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 _) diff --git a/components/tabs/Reuse/ReuseTabsPageAttribute.cs b/components/tabs/Reuse/ReuseTabsPageAttribute.cs index c1fe9840..2084cb4d 100644 --- a/components/tabs/Reuse/ReuseTabsPageAttribute.cs +++ b/components/tabs/Reuse/ReuseTabsPageAttribute.cs @@ -6,20 +6,58 @@ using System; namespace AntDesign { + /// + /// Attribute for ReuseTabsPage, used to set the page title and other properties. + /// public class ReuseTabsPageAttribute : Attribute { + /// + /// Specifies the title of the tab. + /// + /// If you want to set a , you need implement in the page. + /// + /// public string Title { get; set; } + /// + /// Weather the page wont be reused. + /// public bool Ignore { get; set; } + /// + /// Weather the tab can be closed. + /// public bool Closable { get; set; } = true; + /// + /// Weather the tab can be pinned and opened at the first time. + /// public bool Pin { get; set; } = false; + /// + /// The url of the pinned page. Because when the tab is clicked, it need to navigate to the page through the url. + /// public string PinUrl { get; set; } + /// + /// Weather the page is keeping alive. + /// + /// If true, the page will be kept in memory, otherwise, the page will be destroyed when it is not active. + /// + /// public bool KeepAlive { get; set; } = true; + /// + /// The order of the page, the smaller the order, the earlier the page will be displayed. + /// public int Order { get; set; } = 999; + + /// + /// Weather the page is a singleton. + /// + /// If true, the page will be reused although the parameters is different, otherwise, another tab will be created. + /// + /// + public bool Singleton { get; set; } } } diff --git a/components/tabs/Reuse/ReuseTabsPageItem.cs b/components/tabs/Reuse/ReuseTabsPageItem.cs index 1a7ce3f3..4a54c0ec 100644 --- a/components/tabs/Reuse/ReuseTabsPageItem.cs +++ b/components/tabs/Reuse/ReuseTabsPageItem.cs @@ -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; } + + /// + /// Weather the page is a singleton. If true, the page will be reused although the url is different, otherwise, another tab will be created. + /// + public bool Singleton { get; set; } } } diff --git a/components/tabs/Reuse/ReuseTabsService.cs b/components/tabs/Reuse/ReuseTabsService.cs index 8178b3d8..22aad2eb 100644 --- a/components/tabs/Reuse/ReuseTabsService.cs +++ b/components/tabs/Reuse/ReuseTabsService.cs @@ -17,8 +17,7 @@ namespace AntDesign { private readonly NavigationManager _navmgr; private readonly MenuService _menusService; - private readonly Dictionary _pageMap = []; - private IReadOnlyCollection _pages; + private ICollection _pages = new List(); 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; + } + + } + } + /// /// The page information list of the currently opened page, which can be used for caching and recovery /// - public IReadOnlyCollection Pages => _pages; + public IReadOnlyCollection Pages => [.. _pages]; public ReuseTabsService(NavigationManager navmgr, MenuService menusService) { @@ -60,11 +76,9 @@ namespace AntDesign /// The title show on the tab 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 /// The title show on the tab 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 //} /// - /// Close the page corresponding to the specified key + /// Close the page corresponding to the specified url /// - /// The specified page's key - public bool ClosePage(string key) + /// The specified page's url + 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; } /// - /// Close all pages except the page with the specified key + /// Close the page corresponding to the specified key /// /// The specified page's key - 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; + } + + /// + /// Close all pages except the page with the specified url + /// + /// The specified page's url + 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 } /// - /// Reload the page corresponding to the specified key + /// Reload the page corresponding to the specified url /// - /// - public void ReloadPage(string key) + /// + 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(); 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() diff --git a/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/demo/Singleton.razor b/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/demo/Singleton.razor new file mode 100644 index 00000000..f02379c5 --- /dev/null +++ b/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/demo/Singleton.razor @@ -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)] + +Singleton Page + +

Singleton Page

+ +

@text

+ +

@dynamicText

+ +

Navigate to next id: @NextId

+ + +@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 @ Singleton Page with Id @Id ; + } + + public void Dispose() + { + navigationManager.LocationChanged -= OnLationChanged; + } +} \ No newline at end of file diff --git a/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/demo/singleton.md b/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/demo/singleton.md new file mode 100644 index 00000000..f140b4b5 --- /dev/null +++ b/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/demo/singleton.md @@ -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. \ No newline at end of file diff --git a/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/doc/index.en-US.md b/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/doc/index.en-US.md index b197fc79..f4942b64 100644 --- a/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/doc/index.en-US.md +++ b/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/doc/index.en-US.md @@ -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 . | diff --git a/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/doc/index.zh-CN.md b/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/doc/index.zh-CN.md index 780c3ead..aa777cba 100644 --- a/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/doc/index.zh-CN.md +++ b/site/AntDesign.Docs/Demos/Experimental/ReuseTabs/doc/index.zh-CN.md @@ -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时重新加载当前页面。 | \ No newline at end of file +| ReloadPage(url) | 重新加载指定标签的页面,让页面组件重新初始化,且无需刷新浏览器。不传url时重新加载当前页面。 | \ No newline at end of file