diff --git a/GFramework.Game.Abstractions/enums/UITransitionPhases.cs b/GFramework.Game.Abstractions/enums/UITransitionPhases.cs
new file mode 100644
index 0000000..df6843f
--- /dev/null
+++ b/GFramework.Game.Abstractions/enums/UITransitionPhases.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace GFramework.Game.Abstractions.enums;
+
+///
+/// UI切换阶段枚举,定义UI切换过程中的不同阶段
+///
+[Flags]
+public enum UITransitionPhases
+{
+ ///
+ /// UI切换前阶段,在此阶段执行的Handler可以阻塞UI切换流程
+ /// 适用于:淡入淡出动画、用户确认对话框、数据预加载等需要等待完成的操作
+ ///
+ BeforeChange = 1,
+
+ ///
+ /// UI切换后阶段,在此阶段执行的Handler不阻塞UI切换流程
+ /// 适用于:播放音效、日志记录、统计数据收集等后台操作
+ ///
+ AfterChange = 2,
+
+ ///
+ /// 所有阶段,Handler将在BeforeChange和AfterChange阶段都执行
+ ///
+ All = BeforeChange | AfterChange
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/enums/UiTransitionPolicy.cs b/GFramework.Game.Abstractions/enums/UiTransitionPolicy.cs
new file mode 100644
index 0000000..8bc837e
--- /dev/null
+++ b/GFramework.Game.Abstractions/enums/UiTransitionPolicy.cs
@@ -0,0 +1,32 @@
+namespace GFramework.Game.Abstractions.enums;
+
+///
+/// UI页面过渡策略枚举
+/// 定义了UI页面在出栈时的不同处理方式
+///
+public enum UiTransitionPolicy
+{
+ ///
+ /// 出栈即销毁(一次性页面)
+ /// 页面从栈中移除时会完全销毁实例
+ ///
+ Destroy,
+
+ ///
+ /// 出栈隐藏,保留实例
+ /// 页面从栈中移除时仅隐藏显示,保留实例以便后续重用
+ ///
+ Hide,
+
+ ///
+ /// 覆盖显示(不影响下层页面)
+ /// 当前页面覆盖在其他页面之上显示,不影响下层页面的状态
+ ///
+ Overlay,
+
+ ///
+ /// 独占显示(下层页面 Pause + Hide)
+ /// 当前页面独占显示区域,下层页面会被暂停并隐藏
+ ///
+ Exclusive
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/enums/UiTransitionType.cs b/GFramework.Game.Abstractions/enums/UiTransitionType.cs
new file mode 100644
index 0000000..0d0f067
--- /dev/null
+++ b/GFramework.Game.Abstractions/enums/UiTransitionType.cs
@@ -0,0 +1,27 @@
+namespace GFramework.Game.Abstractions.enums;
+
+///
+/// UI切换类型枚举,定义不同的UI切换操作类型
+///
+public enum UiTransitionType
+{
+ ///
+ /// 压入新页面到栈顶
+ ///
+ Push,
+
+ ///
+ /// 弹出栈顶页面
+ ///
+ Pop,
+
+ ///
+ /// 替换当前页面
+ ///
+ Replace,
+
+ ///
+ /// 清空所有页面
+ ///
+ Clear,
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/IPageBehavior.cs b/GFramework.Game.Abstractions/ui/IPageBehavior.cs
new file mode 100644
index 0000000..7307cd6
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/IPageBehavior.cs
@@ -0,0 +1,49 @@
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI页面接口,定义了UI页面的生命周期方法
+///
+public interface IPageBehavior
+{
+ ///
+ /// 获取页面视图对象
+ ///
+ /// 页面视图实例
+ object View { get; }
+
+ ///
+ /// 获取页面是否处于活动状态
+ ///
+ bool IsAlive { get; }
+
+ ///
+ /// 页面进入时调用的方法
+ ///
+ /// 页面进入时传递的参数,可为空
+ void OnEnter(IUiPageEnterParam? param);
+
+ ///
+ /// 页面退出时调用的方法
+ ///
+ void OnExit();
+
+ ///
+ /// 页面暂停时调用的方法
+ ///
+ void OnPause();
+
+ ///
+ /// 页面恢复时调用的方法
+ ///
+ void OnResume();
+
+ ///
+ /// 页面被覆盖时调用(不销毁)
+ ///
+ void OnHide();
+
+ ///
+ /// 页面重新显示时调用的方法
+ ///
+ void OnShow();
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/IUiFactory.cs b/GFramework.Game.Abstractions/ui/IUiFactory.cs
new file mode 100644
index 0000000..fc8a715
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/IUiFactory.cs
@@ -0,0 +1,16 @@
+using GFramework.Core.Abstractions.utility;
+
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI工厂接口,用于创建UI页面实例
+///
+public interface IUiFactory : IContextUtility
+{
+ ///
+ /// 根据UI键值创建对应的UI页面实例
+ ///
+ /// UI标识键,用于确定要创建的具体UI页面类型
+ /// 创建的UI页面实例,实现IUiPage接口
+ IPageBehavior Create(string uiKey);
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/IUiPage.cs b/GFramework.Game.Abstractions/ui/IUiPage.cs
new file mode 100644
index 0000000..4a3d9c8
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/IUiPage.cs
@@ -0,0 +1,39 @@
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI页面生命周期接口
+/// 定义了UI页面的各种状态转换方法,用于管理UI页面的进入、退出、暂停、恢复、显示和隐藏等生命周期事件
+///
+public interface IUiPage
+{
+ ///
+ /// 页面进入时调用的方法
+ ///
+ /// 页面进入参数,可能为空
+ void OnEnter(IUiPageEnterParam? param);
+
+ ///
+ /// 页面退出时调用的方法
+ ///
+ void OnExit();
+
+ ///
+ /// 页面暂停时调用的方法
+ ///
+ void OnPause();
+
+ ///
+ /// 页面恢复时调用的方法
+ ///
+ void OnResume();
+
+ ///
+ /// 页面显示时调用的方法
+ ///
+ void OnShow();
+
+ ///
+ /// 页面隐藏时调用的方法
+ ///
+ void OnHide();
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/IUiPageEnterParam.cs b/GFramework.Game.Abstractions/ui/IUiPageEnterParam.cs
new file mode 100644
index 0000000..5b4a17d
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/IUiPageEnterParam.cs
@@ -0,0 +1,7 @@
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI页面进入参数接口
+/// 该接口用于定义UI页面跳转时传递的参数数据结构
+///
+public interface IUiPageEnterParam;
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/IUiPageProvider.cs b/GFramework.Game.Abstractions/ui/IUiPageProvider.cs
new file mode 100644
index 0000000..d38892b
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/IUiPageProvider.cs
@@ -0,0 +1,13 @@
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI页面提供者接口,用于创建UI页面实例
+///
+public interface IUiPageProvider
+{
+ ///
+ /// 获取UI页面实例
+ ///
+ /// UI页面实例
+ IPageBehavior GetPage();
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/IUiRegistry.cs b/GFramework.Game.Abstractions/ui/IUiRegistry.cs
new file mode 100644
index 0000000..8d66633
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/IUiRegistry.cs
@@ -0,0 +1,17 @@
+using GFramework.Core.Abstractions.utility;
+
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI注册表接口,用于根据UI键获取对应的UI实例
+///
+/// UI实例的类型参数,使用协变修饰符out
+public interface IUiRegistry : IUtility
+{
+ ///
+ /// 根据指定的UI键获取对应的UI实例
+ ///
+ /// UI的唯一标识键
+ /// 与指定键关联的UI实例
+ T Get(string uiKey);
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/IUiRoot.cs b/GFramework.Game.Abstractions/ui/IUiRoot.cs
new file mode 100644
index 0000000..6785d72
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/IUiRoot.cs
@@ -0,0 +1,19 @@
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI根节点接口,定义了UI页面容器的基本操作
+///
+public interface IUiRoot
+{
+ ///
+ /// 向UI根节点添加子页面
+ ///
+ /// 要添加的UI页面子节点
+ void AddUiPage(IPageBehavior child);
+
+ ///
+ /// 从UI根节点移除子页面
+ ///
+ /// 要移除的UI页面子节点
+ void RemoveUiPage(IPageBehavior child);
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/IUiRouter.cs b/GFramework.Game.Abstractions/ui/IUiRouter.cs
new file mode 100644
index 0000000..e1bad8e
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/IUiRouter.cs
@@ -0,0 +1,64 @@
+using GFramework.Core.Abstractions.system;
+using GFramework.Game.Abstractions.enums;
+
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI路由管理器接口,用于管理UI界面的导航和切换操作
+///
+public interface IUiRouter : ISystem
+{
+ ///
+ /// 绑定UI根节点
+ ///
+ /// UI根节点接口实例
+ void BindRoot(IUiRoot root);
+
+ ///
+ /// 将指定的UI界面压入路由栈,显示新的UI界面
+ ///
+ /// UI界面的唯一标识符
+ /// 进入界面的参数,可为空
+ /// 界面切换策略,默认为Exclusive(独占)
+ void Push(string uiKey, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive);
+
+
+ ///
+ /// 弹出路由栈顶的UI界面,返回到上一个界面
+ ///
+ /// 界面弹出策略,默认为Destroy(销毁)
+ void Pop(UiPopPolicy policy = UiPopPolicy.Destroy);
+
+ ///
+ /// 替换当前UI界面为指定的新界面
+ ///
+ /// 新UI界面的唯一标识符
+ /// 进入界面的参数,可为空
+ /// 界面弹出策略,默认为销毁当前界面
+ /// 界面过渡策略,默认为独占模式
+ void Replace(
+ string uiKey,
+ IUiPageEnterParam? param = null,
+ UiPopPolicy popPolicy = UiPopPolicy.Destroy,
+ UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive
+ );
+
+
+ ///
+ /// 清空所有UI界面,重置路由状态
+ ///
+ void Clear();
+
+ ///
+ /// 注册UI切换处理器
+ ///
+ /// 处理器实例
+ /// 执行选项
+ void RegisterHandler(IUiTransitionHandler handler, UiTransitionHandlerOptions? options = null);
+
+ ///
+ /// 注销UI切换处理器
+ ///
+ /// 处理器实例
+ void UnregisterHandler(IUiTransitionHandler handler);
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/IUiTransitionHandler.cs b/GFramework.Game.Abstractions/ui/IUiTransitionHandler.cs
new file mode 100644
index 0000000..393e021
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/IUiTransitionHandler.cs
@@ -0,0 +1,39 @@
+using System.Threading;
+using System.Threading.Tasks;
+using GFramework.Game.Abstractions.enums;
+
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI切换处理器接口,定义UI切换扩展点的处理逻辑
+///
+public interface IUiTransitionHandler
+{
+ ///
+ /// 处理器优先级,数值越小越先执行
+ ///
+ int Priority { get; }
+
+ ///
+ /// 处理器适用的阶段,默认为所有阶段
+ /// 可以使用Flags枚举指定多个阶段
+ ///
+ UITransitionPhases Phases { get; }
+
+ ///
+ /// 判断是否应该处理当前事件
+ /// 可以根据事件类型、UI key等信息进行条件过滤
+ ///
+ /// UI切换事件
+ /// 当前阶段
+ /// 是否处理
+ bool ShouldHandle(UiTransitionEvent @event, UITransitionPhases phases);
+
+ ///
+ /// 处理UI切换事件
+ ///
+ /// UI切换事件
+ /// 取消令牌
+ /// 异步任务
+ Task HandleAsync(UiTransitionEvent @event, CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/IWritableUiRegistry.cs b/GFramework.Game.Abstractions/ui/IWritableUiRegistry.cs
new file mode 100644
index 0000000..c50669f
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/IWritableUiRegistry.cs
@@ -0,0 +1,16 @@
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// 可写的UI注册表接口
+///
+/// UI实例的类型参数
+public interface IWritableUiRegistry : IUiRegistry where T : class
+{
+ ///
+ /// 注册UI实例到注册表
+ ///
+ /// UI的唯一标识键
+ /// 要注册的UI实例
+ /// 当前注册表实例
+ IWritableUiRegistry Register(string key, T scene);
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/UiPopPolicy.cs b/GFramework.Game.Abstractions/ui/UiPopPolicy.cs
new file mode 100644
index 0000000..34607db
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/UiPopPolicy.cs
@@ -0,0 +1,17 @@
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// 定义UI弹窗的关闭策略枚举
+///
+public enum UiPopPolicy
+{
+ ///
+ /// 销毁模式:关闭时完全销毁UI对象
+ ///
+ Destroy,
+
+ ///
+ /// 隐藏模式:关闭时仅隐藏UI对象,保留实例
+ ///
+ Hide
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/UiTransitionEvent.cs b/GFramework.Game.Abstractions/ui/UiTransitionEvent.cs
new file mode 100644
index 0000000..7aa9bac
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/UiTransitionEvent.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using GFramework.Game.Abstractions.enums;
+
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI切换事件,包含UI切换过程中的上下文信息
+///
+public sealed class UiTransitionEvent
+{
+ ///
+ /// 用户自定义数据字典,用于Handler之间传递数据
+ ///
+ private readonly Dictionary _context = new(StringComparer.Ordinal);
+
+ ///
+ /// 源UI的标识符,切换前的UI key
+ ///
+ public string FromUiKey { get; init; } = string.Empty;
+
+ ///
+ /// 目标UI的标识符,切换后的UI key
+ ///
+ public string ToUiKey { get; init; } = string.Empty;
+
+ ///
+ /// UI切换类型
+ ///
+ public UiTransitionType TransitionType { get; init; }
+
+ ///
+ /// UI切换策略
+ ///
+ public UiTransitionPolicy Policy { get; init; }
+
+ ///
+ /// UI进入参数
+ ///
+ public IUiPageEnterParam? EnterParam { get; init; }
+
+ ///
+ /// 获取用户自定义数据
+ ///
+ /// 数据类型
+ /// 数据键
+ /// 默认值(当键不存在或类型不匹配时返回)
+ /// 用户数据
+ public T Get(string key, T defaultValue = default!)
+ {
+ if (_context.TryGetValue(key, out var obj) && obj is T value)
+ return value;
+ return defaultValue;
+ }
+
+ ///
+ /// 尝试获取用户自定义数据
+ ///
+ /// 数据类型
+ /// 数据键
+ /// 输出值
+ /// 是否成功获取
+ public bool TryGet(string key, out T value)
+ {
+ if (_context.TryGetValue(key, out var obj) && obj is T t)
+ {
+ value = t;
+ return true;
+ }
+
+ value = default!;
+ return false;
+ }
+
+ ///
+ /// 设置用户自定义数据
+ ///
+ /// 数据类型
+ /// 数据键
+ /// 数据值
+ public void Set(string key, T value)
+ {
+ _context[key] = value!;
+ }
+
+ ///
+ /// 检查是否存在指定的用户数据键
+ ///
+ /// 数据键
+ /// 是否存在
+ public bool Has(string key)
+ {
+ return _context.ContainsKey(key);
+ }
+
+ ///
+ /// 移除指定的用户数据
+ ///
+ /// 数据键
+ /// 是否成功移除
+ public bool Remove(string key)
+ {
+ return _context.Remove(key);
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/ui/UiTransitionHandlerOptions.cs b/GFramework.Game.Abstractions/ui/UiTransitionHandlerOptions.cs
new file mode 100644
index 0000000..058ebc6
--- /dev/null
+++ b/GFramework.Game.Abstractions/ui/UiTransitionHandlerOptions.cs
@@ -0,0 +1,6 @@
+namespace GFramework.Game.Abstractions.ui;
+
+///
+/// UI切换处理器执行选项
+///
+public record UiTransitionHandlerOptions(int TimeoutMs = 0, bool ContinueOnError = true);
\ No newline at end of file
diff --git a/GFramework.Game/ui/UiRouterBase.cs b/GFramework.Game/ui/UiRouterBase.cs
new file mode 100644
index 0000000..cb7678f
--- /dev/null
+++ b/GFramework.Game/ui/UiRouterBase.cs
@@ -0,0 +1,319 @@
+using GFramework.Core.Abstractions.logging;
+using GFramework.Core.extensions;
+using GFramework.Core.logging;
+using GFramework.Core.system;
+using GFramework.Game.Abstractions.enums;
+using GFramework.Game.Abstractions.ui;
+
+namespace GFramework.Game.ui;
+
+///
+/// UI路由类,提供页面栈管理功能
+///
+public abstract class UiRouterBase : AbstractSystem, IUiRouter
+{
+ private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("UiRouterBase");
+
+ ///
+ /// UI切换处理器管道
+ ///
+ private readonly UiTransitionPipeline _pipeline = new();
+
+ ///
+ /// 页面栈,用于管理UI页面的显示顺序
+ ///
+ private readonly Stack _stack = new();
+
+ ///
+ /// UI工厂实例,用于创建UI相关的对象
+ ///
+ private IUiFactory _factory = null!;
+
+ private IUiRoot _uiRoot = null!;
+
+ ///
+ /// 注册UI切换处理器
+ ///
+ /// 处理器实例
+ /// 执行选项
+ public void RegisterHandler(IUiTransitionHandler handler, UiTransitionHandlerOptions? options = null)
+ {
+ _pipeline.RegisterHandler(handler, options);
+ }
+
+ ///
+ /// 注销UI切换处理器
+ ///
+ /// 处理器实例
+ public void UnregisterHandler(IUiTransitionHandler handler)
+ {
+ _pipeline.UnregisterHandler(handler);
+ }
+
+ ///
+ /// 绑定UI根节点
+ ///
+ public void BindRoot(IUiRoot root)
+ {
+ _uiRoot = root;
+ Log.Debug("Bind UI Root: {0}", root.GetType().Name);
+ }
+
+ ///
+ /// 将指定UI页面压入栈顶并显示
+ ///
+ /// UI页面标识符
+ /// 页面进入参数,可为空
+ /// 页面切换策略
+ public void Push(
+ string uiKey,
+ IUiPageEnterParam? param = null,
+ UiTransitionPolicy policy = UiTransitionPolicy.Exclusive
+ )
+ {
+ var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param);
+
+ Log.Debug(
+ "Push UI Page: key={0}, policy={1}, stackBefore={2}",
+ uiKey, policy, _stack.Count
+ );
+
+ BeforeChange(@event);
+
+ DoPushInternal(uiKey, param, policy);
+
+ AfterChange(@event);
+ }
+
+
+ ///
+ /// 弹出栈顶页面并根据策略处理页面
+ ///
+ /// 弹出策略,默认为销毁策略
+ public void Pop(UiPopPolicy policy = UiPopPolicy.Destroy)
+ {
+ if (_stack.Count == 0)
+ {
+ Log.Debug("Pop ignored: stack is empty");
+ return;
+ }
+
+ var nextUiKey = _stack.Count > 1
+ ? _stack.ElementAt(1).View.GetType().Name
+ : string.Empty;
+ var @event = CreateEvent(nextUiKey, UiTransitionType.Pop);
+
+ BeforeChange(@event);
+
+ DoPopInternal(policy);
+
+ AfterChange(@event);
+ }
+
+ ///
+ /// 替换当前所有页面为新页面
+ ///
+ /// 新UI页面标识符
+ /// 页面进入参数,可为空
+ /// 弹出页面时的销毁策略,默认为销毁
+ /// 推入页面时的过渡策略,默认为独占
+ public void Replace(
+ string uiKey,
+ IUiPageEnterParam? param = null,
+ UiPopPolicy popPolicy = UiPopPolicy.Destroy,
+ UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive
+ )
+ {
+ var @event = CreateEvent(uiKey, UiTransitionType.Replace, pushPolicy, param);
+
+ Log.Debug(
+ "Replace UI Stack with page: key={0}, popPolicy={1}, pushPolicy={2}",
+ uiKey, popPolicy, pushPolicy
+ );
+
+ BeforeChange(@event);
+
+ // 使用内部方法,避免触发额外的Pipeline
+ DoClearInternal(popPolicy);
+ DoPushInternal(uiKey, param, pushPolicy);
+
+ AfterChange(@event);
+ }
+
+ ///
+ /// 清空所有页面栈中的页面
+ ///
+ public void Clear()
+ {
+ var @event = CreateEvent(string.Empty, UiTransitionType.Clear);
+
+ Log.Debug("Clear UI Stack, stackCount={0}", _stack.Count);
+
+ BeforeChange(@event);
+
+ // 使用内部方法,避免触发额外的Pipeline
+ DoClearInternal(UiPopPolicy.Destroy);
+
+ AfterChange(@event);
+ }
+
+ ///
+ /// 初始化方法,在页面初始化时获取UI工厂实例
+ ///
+ protected override void OnInit()
+ {
+ _factory = this.GetUtility()!;
+ Log.Debug("UiRouterBase initialized. Factory={0}", _factory.GetType().Name);
+ RegisterHandlers();
+ }
+
+ ///
+ /// 注册默认的UI切换处理器
+ ///
+ protected abstract void RegisterHandlers();
+
+ ///
+ /// 获取当前栈顶UI的key
+ ///
+ /// 当前UI key,如果栈为空则返回空字符串
+ private string GetCurrentUiKey()
+ {
+ if (_stack.Count == 0)
+ return string.Empty;
+ var page = _stack.Peek();
+ return page.View.GetType().Name;
+ }
+
+ ///
+ /// 创建UI切换事件
+ ///
+ private UiTransitionEvent CreateEvent(
+ string toUiKey,
+ UiTransitionType type,
+ UiTransitionPolicy? policy = null,
+ IUiPageEnterParam? param = null
+ )
+ {
+ return new UiTransitionEvent
+ {
+ FromUiKey = GetCurrentUiKey(),
+ ToUiKey = toUiKey,
+ TransitionType = type,
+ Policy = policy ?? UiTransitionPolicy.Exclusive,
+ EnterParam = param
+ };
+ }
+
+ ///
+ /// 执行UI切换前的Handler(阻塞)
+ ///
+ private void BeforeChange(UiTransitionEvent @event)
+ {
+ Log.Debug("BeforeChange phases started: {0}", @event.TransitionType);
+ _pipeline.ExecuteAsync(@event, UITransitionPhases.BeforeChange).GetAwaiter().GetResult();
+ Log.Debug("BeforeChange phases completed: {0}", @event.TransitionType);
+ }
+
+ ///
+ /// 执行UI切换后的Handler(不阻塞)
+ ///
+ private void AfterChange(UiTransitionEvent @event)
+ {
+ Log.Debug("AfterChange phases started: {0}", @event.TransitionType);
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await _pipeline.ExecuteAsync(@event, UITransitionPhases.AfterChange).ConfigureAwait(false);
+ Log.Debug("AfterChange phases completed: {0}", @event.TransitionType);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("AfterChange phases failed: {0}, Error: {1}", @event.TransitionType, ex.Message);
+ }
+ });
+ }
+
+ ///
+ /// 执行Push的核心逻辑(不触发Pipeline)
+ ///
+ private void DoPushInternal(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy)
+ {
+ if (_stack.Count > 0)
+ {
+ var current = _stack.Peek();
+ Log.Debug("Pause current page: {0}", current.GetType().Name);
+ current.OnPause();
+
+ if (policy == UiTransitionPolicy.Exclusive)
+ {
+ Log.Debug("Hide current page (Exclusive): {0}", current.GetType().Name);
+ current.OnHide();
+ }
+ }
+
+ var page = _factory.Create(uiKey);
+ Log.Debug("Create UI Page instance: {0}", page.GetType().Name);
+
+ _uiRoot.AddUiPage(page);
+ _stack.Push(page);
+
+ Log.Debug(
+ "Enter & Show page: {0}, stackAfter={1}",
+ page.GetType().Name, _stack.Count
+ );
+
+ page.OnEnter(param);
+ page.OnShow();
+ }
+
+ ///
+ /// 执行Pop的核心逻辑(不触发Pipeline)
+ ///
+ private void DoPopInternal(UiPopPolicy policy)
+ {
+ if (_stack.Count == 0)
+ return;
+
+ var top = _stack.Pop();
+ Log.Debug(
+ "Pop UI Page internal: {0}, policy={1}, stackAfterPop={2}",
+ top.GetType().Name, policy, _stack.Count
+ );
+
+ top.OnExit();
+
+ if (policy == UiPopPolicy.Destroy)
+ {
+ Log.Debug("Destroy UI Page: {0}", top.GetType().Name);
+ _uiRoot.RemoveUiPage(top);
+ }
+ else
+ {
+ Log.Debug("Hide UI Page: {0}", top.GetType().Name);
+ top.OnHide();
+ }
+
+ if (_stack.Count > 0)
+ {
+ var next = _stack.Peek();
+ Log.Debug("Resume & Show page: {0}", next.GetType().Name);
+ next.OnResume();
+ next.OnShow();
+ }
+ else
+ {
+ Log.Debug("UI stack is now empty");
+ }
+ }
+
+ ///
+ /// 执行Clear的核心逻辑(不触发Pipeline)
+ ///
+ private void DoClearInternal(UiPopPolicy policy)
+ {
+ Log.Debug("Clear UI Stack internal, count={0}", _stack.Count);
+ while (_stack.Count > 0)
+ DoPopInternal(policy);
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Game/ui/UiTransitionPipeline.cs b/GFramework.Game/ui/UiTransitionPipeline.cs
new file mode 100644
index 0000000..c8a8d01
--- /dev/null
+++ b/GFramework.Game/ui/UiTransitionPipeline.cs
@@ -0,0 +1,168 @@
+using GFramework.Core.Abstractions.logging;
+using GFramework.Core.logging;
+using GFramework.Game.Abstractions.enums;
+using GFramework.Game.Abstractions.ui;
+
+namespace GFramework.Game.ui;
+
+///
+/// UI切换处理器管道,负责管理和执行UI切换扩展点
+///
+public class UiTransitionPipeline
+{
+ private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("UiTransitionPipeline");
+ private readonly List _handlers = new();
+ private readonly Dictionary _options = new();
+
+ ///
+ /// 注册UI切换处理器
+ ///
+ /// 处理器实例
+ /// 执行选项
+ public void RegisterHandler(IUiTransitionHandler handler, UiTransitionHandlerOptions? options = null)
+ {
+ ArgumentNullException.ThrowIfNull(handler);
+
+ if (_handlers.Contains(handler))
+ {
+ Log.Debug("Handler already registered: {0}", handler.GetType().Name);
+ return;
+ }
+
+ _handlers.Add(handler);
+ _options[handler] = options ?? new UiTransitionHandlerOptions();
+ Log.Debug(
+ "Handler registered: {0}, Priority={1}, Phases={2}, TimeoutMs={3}",
+ handler.GetType().Name,
+ handler.Priority,
+ handler.Phases,
+ _options[handler].TimeoutMs
+ );
+ }
+
+ ///
+ /// 注销UI切换处理器
+ ///
+ /// 处理器实例
+ public void UnregisterHandler(IUiTransitionHandler handler)
+ {
+ ArgumentNullException.ThrowIfNull(handler);
+
+ if (!_handlers.Remove(handler)) return;
+ _options.Remove(handler);
+ Log.Debug("Handler unregistered: {0}", handler.GetType().Name);
+ }
+
+ ///
+ /// 执行指定阶段的所有Handler
+ ///
+ /// UI切换事件
+ /// 执行阶段
+ /// 取消令牌
+ /// 异步任务
+ public async Task ExecuteAsync(
+ UiTransitionEvent @event,
+ UITransitionPhases phases,
+ CancellationToken cancellationToken = default
+ )
+ {
+ @event.Set("Phases", phases.ToString());
+
+ Log.Debug(
+ "Execute pipeline: Phases={0}, From={1}, To={2}, Type={3}, HandlerCount={4}",
+ phases,
+ @event.FromUiKey,
+ @event.ToUiKey,
+ @event.TransitionType,
+ _handlers.Count
+ );
+
+ var sortedHandlers = FilterAndSortHandlers(@event, phases);
+
+ if (sortedHandlers.Count == 0)
+ {
+ Log.Debug("No handlers to execute for phases: {0}", phases);
+ return;
+ }
+
+ Log.Debug(
+ "Executing {0} handlers for phases {1}",
+ sortedHandlers.Count,
+ phases
+ );
+
+ foreach (var handler in sortedHandlers)
+ {
+ var options = _options[handler];
+ await ExecuteSingleHandlerAsync(handler, options, @event, cancellationToken);
+ }
+
+ Log.Debug("Pipeline execution completed for phases: {0}", phases);
+ }
+
+ private List FilterAndSortHandlers(
+ UiTransitionEvent @event,
+ UITransitionPhases phases)
+ {
+ return _handlers
+ .Where(h => h.Phases.HasFlag(phases) && h.ShouldHandle(@event, phases))
+ .OrderBy(h => h.Priority)
+ .ToList();
+ }
+
+ private static async Task ExecuteSingleHandlerAsync(
+ IUiTransitionHandler handler,
+ UiTransitionHandlerOptions options,
+ UiTransitionEvent @event,
+ CancellationToken cancellationToken)
+ {
+ Log.Debug(
+ "Executing handler: {0}, Priority={1}",
+ handler.GetType().Name,
+ handler.Priority
+ );
+
+ try
+ {
+ using var timeoutCts = options.TimeoutMs > 0
+ ? new CancellationTokenSource(options.TimeoutMs)
+ : null;
+
+ using var linkedCts = timeoutCts != null && cancellationToken.CanBeCanceled
+ ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token)
+ : null;
+
+ await handler.HandleAsync(
+ @event,
+ linkedCts?.Token ?? cancellationToken
+ ).ConfigureAwait(false);
+
+ Log.Debug("Handler completed: {0}", handler.GetType().Name);
+ }
+ catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
+ {
+ Log.Error(
+ "Handler timeout: {0}, TimeoutMs={1}",
+ handler.GetType().Name,
+ options.TimeoutMs
+ );
+
+ if (options.ContinueOnError) return;
+ Log.Error("Stopping pipeline due to timeout and ContinueOnError=false");
+ throw;
+ }
+ catch (OperationCanceledException)
+ {
+ Log.Debug("Handler cancelled: {0}", handler.GetType().Name);
+ throw;
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Handler failed: {0}, Error: {1}", handler.GetType().Name, ex.Message);
+
+ if (options.ContinueOnError) return;
+ Log.Error("Stopping pipeline due to error and ContinueOnError=false");
+ throw;
+ }
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Game/ui/handler/LoggingTransitionHandler.cs b/GFramework.Game/ui/handler/LoggingTransitionHandler.cs
new file mode 100644
index 0000000..da3a4f0
--- /dev/null
+++ b/GFramework.Game/ui/handler/LoggingTransitionHandler.cs
@@ -0,0 +1,48 @@
+using GFramework.Core.Abstractions.logging;
+using GFramework.Core.logging;
+using GFramework.Game.Abstractions.enums;
+using GFramework.Game.Abstractions.ui;
+
+namespace GFramework.Game.ui.handler;
+
+///
+/// 日志UI切换处理器,用于记录UI切换的详细信息
+///
+public sealed class LoggingTransitionHandler : UiTransitionHandlerBase
+{
+ ///
+ /// 日志记录器实例,用于记录UI切换相关信息
+ ///
+ private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("LoggingTransitionHandler");
+
+ ///
+ /// 获取处理器优先级,数值越大优先级越高
+ ///
+ public override int Priority => 999;
+
+ ///
+ /// 获取处理器处理的UI切换阶段,处理所有阶段
+ ///
+ public override UITransitionPhases Phases => UITransitionPhases.All;
+
+ ///
+ /// 处理UI切换事件的异步方法
+ ///
+ /// UI切换事件对象,包含切换的相关信息
+ /// 取消令牌,用于控制异步操作的取消
+ /// 表示异步操作的任务
+ public override Task HandleAsync(UiTransitionEvent @event, CancellationToken cancellationToken)
+ {
+ // 记录UI切换的详细信息到日志
+ Log.Info(
+ "UI Transition: Phases={0}, Type={1}, From={2}, To={3}, Policy={4}",
+ @event.Get("Phases", "Unknown"),
+ @event.TransitionType,
+ @event.FromUiKey,
+ @event.ToUiKey,
+ @event.Policy
+ );
+
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Game/ui/handler/UiTransitionHandlerBase.cs b/GFramework.Game/ui/handler/UiTransitionHandlerBase.cs
new file mode 100644
index 0000000..cf8be83
--- /dev/null
+++ b/GFramework.Game/ui/handler/UiTransitionHandlerBase.cs
@@ -0,0 +1,33 @@
+using GFramework.Game.Abstractions.enums;
+using GFramework.Game.Abstractions.ui;
+
+namespace GFramework.Game.ui.handler;
+
+///
+/// UI切换处理器抽象基类,提供一些默认实现
+///
+public abstract class UiTransitionHandlerBase : IUiTransitionHandler
+{
+ ///
+ /// 处理器适用的阶段,默认为所有阶段
+ ///
+ public virtual UITransitionPhases Phases => UITransitionPhases.All;
+
+ ///
+ /// 优先级,需要在子类中实现
+ ///
+ public abstract int Priority { get; }
+
+ ///
+ /// 判断是否应该处理当前事件,默认返回true
+ ///
+ public virtual bool ShouldHandle(UiTransitionEvent @event, UITransitionPhases phases)
+ {
+ return true;
+ }
+
+ ///
+ /// 处理UI切换事件,需要在子类中实现
+ ///
+ public abstract Task HandleAsync(UiTransitionEvent @event, CancellationToken cancellationToken);
+}
\ No newline at end of file