真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

常用的MVC變體有哪些-創(chuàng)新互聯(lián)

這篇文章給大家分享的是有關(guān)常用的MVC變體有哪些的內(nèi)容。小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。

站在用戶的角度思考問(wèn)題,與客戶深入溝通,找到睢陽(yáng)網(wǎng)站設(shè)計(jì)與睢陽(yáng)網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:成都網(wǎng)站建設(shè)、做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、域名與空間、雅安服務(wù)器托管、企業(yè)郵箱。業(yè)務(wù)覆蓋睢陽(yáng)地區(qū)。

MVC的變體

通過(guò)采用MVC模式,我們可以將可視化UI元素的呈現(xiàn)、UI處理邏輯和業(yè)務(wù)邏輯分別定義在View、Controller和Model中,但是對(duì)于三者之間的交互,MVC并沒(méi)有進(jìn)行嚴(yán)格的限制。最為典型的就是允許View和Model繞開(kāi)Controller進(jìn)行直接交互,View可以通過(guò)調(diào)用Model獲取需要呈現(xiàn)給用戶的數(shù)據(jù),Model也可以直接通知View讓其感知到狀態(tài)的變化。當(dāng)我們將MVC應(yīng)用于具體的項(xiàng)目開(kāi)發(fā)中,不論是基于GUI的桌面應(yīng)用還是基于Web UI的Web應(yīng)用,如果不對(duì)Model、View和Controller之間的交互進(jìn)行更為嚴(yán)格的限制,我們編寫的程序可能比自治視圖更加難以維護(hù)。

今天我們將MVC視為一種模式(Pattern),但是作為MVC最初提出者的Trygve M. H. Reenskau卻將MVC視為一種范例(Paradigm),這可以從它在Applications Programming in Smalltalk-80(TM):How to use Model-View-Controller (MVC)中對(duì)MVC的描述可以看出來(lái):In the MVCparadigm the user input, the modeling of the external world, and the visual feedback to the user are explicitly separated and handled by three types of object, each specialized for its task.

模式和范例的區(qū)別在于前者可以直接應(yīng)用到具體的應(yīng)用上,而后者則僅僅提供一些基本的指導(dǎo)方針。在我看來(lái)MVC是一個(gè)很寬泛的概念,任何基于Model、View和Controller對(duì)UI應(yīng)用進(jìn)行分解的設(shè)計(jì)都可以成為MVC。當(dāng)我們采用MVC的思想來(lái)設(shè)計(jì)UI應(yīng)用的時(shí)候,應(yīng)該根據(jù)開(kāi)發(fā)框架(比如Windows Forms、WPF和Web Forms)的特點(diǎn)對(duì)Model、View和Controller的界限以及相互之間的交互設(shè)置一個(gè)更為嚴(yán)格的規(guī)則。

在軟件設(shè)計(jì)的發(fā)展歷程中出現(xiàn)了一些MVC的變體(Varation),它們遵循定義在MVC中的基本原則,我們現(xiàn)在來(lái)簡(jiǎn)單地討論一些常用的MVC變體。

MVP

MVP是一種廣泛使用的UI架構(gòu)模式,適用于基于事件驅(qū)動(dòng)的應(yīng)用框架,比如ASP.NET Web Forms和Windows Forms應(yīng)用。MVP中的M和V分別對(duì)應(yīng)于MVC的Model和View,而P(Presenter)則自然代替了MVC中的Controller。但是MVP并非僅僅體現(xiàn)在從Controller到Presenter的轉(zhuǎn)換,更多地體現(xiàn)在Model、View和Presenter之間的交互上。

MVC模式中元素之間“混亂”的交互主要體現(xiàn)在允許View和Model繞開(kāi)Controller進(jìn)行單獨(dú)“交流”,這在MVP模式中得到了徹底解決。如圖1-2所示,能夠與Model直接進(jìn)行交互的僅限于Presenter,View只能通過(guò)Presenter間接地調(diào)用Model。Model的獨(dú)立性在這里得到了真正的體現(xiàn),它不僅僅與可視化元素的呈現(xiàn)(View)無(wú)關(guān),與UI處理邏輯(Presenter)也無(wú)關(guān)。使用MVP的應(yīng)用是用戶驅(qū)動(dòng)的而非Model驅(qū)動(dòng)的,所以Model不需要主動(dòng)通知View以提醒狀態(tài)發(fā)生了改變。

 常用的MVC變體有哪些

圖1-2 Model-View-Presenter之間的交互

MVP不僅僅避免了View和Model之間的耦合,更進(jìn)一步地降低了Presenter對(duì)View的依賴。如圖1-2所示,Presenter依賴的是一個(gè)抽象化的View,即View實(shí)現(xiàn)的接口IView,這帶來(lái)的最直接的好處就是使定義在Presenter中的UI處理邏輯變得易于測(cè)試。由于Presenter對(duì)View的依賴行為定義在接口IView中,我們只需要Mock一個(gè)實(shí)現(xiàn)了該接口的View就能對(duì)Presenter進(jìn)行測(cè)試。

構(gòu)成MVP三要素之間的交互體現(xiàn)在兩個(gè)方面,即View/Presenter和Presenter/Model。Presenter和Model之間的交互很清晰,僅僅體現(xiàn)在Presenter對(duì)Model的單向調(diào)用。而View和Presenter之間該采用怎樣的交互方式是整個(gè)MVP的核心,MVP針對(duì)關(guān)注點(diǎn)分離的初衷能否體現(xiàn)在具體的應(yīng)用中很大程度上取決于兩者之間的交互方式是否正確。按照View和Presenter之間的交互方式以及View本身的職責(zé)范圍,Martin Folwer將MVP可分為PV(Passive View)和SC(Supervising Controller)兩種模式。

PV與SC

解決View難以測(cè)試的最好的辦法就是讓它無(wú)需測(cè)試,如果View不需要測(cè)試,其先決條件就是讓它盡可能不涉及到UI處理邏輯,這就是PV模式目的所在。顧名思義,PV(Passive View)是一個(gè)被動(dòng)的View,包含其中的針對(duì)UI元素(比如控件)的操作不是由View自身主動(dòng)來(lái)控制,而被動(dòng)地交給Presenter來(lái)操控。

如果我們純粹地采用PV模式來(lái)設(shè)計(jì)View,意味著我們需要將View中的UI元素通過(guò)屬性的形式暴露出來(lái)。具體來(lái)說(shuō),當(dāng)我們?cè)跒閂iew定義接口的時(shí)候,需要定義基于UI元素的屬性使Presenter可以對(duì)View進(jìn)行細(xì)粒度操作,但這并不意味著我們直接將View上的控件暴露出來(lái)。舉個(gè)簡(jiǎn)單的例子,假設(shè)我們開(kāi)發(fā)的HR系統(tǒng)中具有如圖1-3所示的一個(gè)Web頁(yè)面,我們通過(guò)它可以獲取某個(gè)部門的員工列表。

常用的MVC變體有哪些

圖1-3  員工查詢頁(yè)面

現(xiàn)在通過(guò)ASP.NET Web Forms應(yīng)用來(lái)設(shè)計(jì)這個(gè)頁(yè)面,我們來(lái)討論一下如果采用PV模式,View的接口該如何定義。對(duì)于Presenter來(lái)說(shuō),View供它操作的控件有兩個(gè),一個(gè)是包含所有部門列表的DropDownList,另一個(gè)則是顯示員工列表的GridView。在頁(yè)面加載的時(shí)候,Presenter將部門列表綁定在DropDownList上,與此同時(shí)包含所有員工的列表被綁定到GridView。當(dāng)用戶選擇某個(gè)部門并點(diǎn)擊“查詢”按鈕后,View將包含篩選部門在內(nèi)的查詢請(qǐng)求轉(zhuǎn)發(fā)給Presenter,后者篩選出相應(yīng)的員工列表之后將其綁定到GridView。

如果我們?yōu)樵揤iew定義一個(gè)接口IEmployeeSearchView,我們不能按照所示的代碼將上述這兩個(gè)控件直接以屬性的形式暴露出來(lái)。針對(duì)具體控件類型的數(shù)據(jù)綁定屬于View的內(nèi)部細(xì)節(jié)(比如說(shuō)針對(duì)部門列表的顯示,我們可以選擇DropDownList也可以選擇ListBox),不能體現(xiàn)在表示用于抽象View的接口中。另外,理想情況下定義在Presenter中的UI處理邏輯應(yīng)該是與具體的技術(shù)平臺(tái)無(wú)關(guān)的,如果在接口中涉及控件類型,這無(wú)疑將Presenter也與具體的技術(shù)平臺(tái)綁定在了一起。

public interface IEmployeeSearchView

{

   DropDownList           Departments { get;}

   GridView               Employees { get; }

}

正確的接口和實(shí)現(xiàn)該接口的View(一個(gè)Web頁(yè)面)應(yīng)該采用如下的定義方式。Presenter通過(guò)對(duì)屬性Departments和Employees賦值進(jìn)而實(shí)現(xiàn)對(duì)相應(yīng)DropDownList和GridView的數(shù)據(jù)綁定,通過(guò)屬性SelectedDepartment得到用戶選擇的篩選部門。為了盡可能讓接口只暴露必需的信息,我們特意將對(duì)屬性的讀/寫作了控制。

public interface IEmployeeSearchView

{

   IEnumerable     Departments { set; }

   string                   SelectedDepartment { get; }

   IEnumerable       Employees { set; }

}

public partial class EmployeeSearchView: Page, IEmployeeSearchView

{

   //其他成員

   public IEnumerable Departments

   {

       set

       {

           this.DropDownListDepartments.DataSource = value;

           this.DropDownListDepartments.DataBind();

       }

   }

   public string SelectedDepartment

   {

       get { return this.DropDownListDepartments.SelectedValue;}

   }

   public IEnumerable Employees

   {

       set

       {

           this.GridViewEmployees.DataSource = value;

           this.GridViewEmployees.DataBind();

       }

   }

}

PV模式將所有的UI處理邏輯全部定義在Presenter上,意味著所有的UI處理邏輯都可以被測(cè)試,所以從可測(cè)試性的角度來(lái)這是一種不錯(cuò)的選擇,但是它要求將View中可供操作的UI元素定義在對(duì)應(yīng)的接口中,對(duì)于一些復(fù)雜的富客戶端(Rich Client)View來(lái)說(shuō),接口成員將會(huì)變得很多,這無(wú)疑會(huì)提升編程所需的代碼量。從另一方面來(lái)看,由于Presenter需要在控件級(jí)別對(duì)View進(jìn)行細(xì)粒度的控制,這無(wú)疑會(huì)提供Presenter本身的復(fù)雜度,往往會(huì)使原本簡(jiǎn)單的邏輯復(fù)雜化,在這種情況下我們往往采用SC模式。

在SC模式下,為了降低Presenter的復(fù)雜度,我們將諸如數(shù)據(jù)綁定和格式化這樣簡(jiǎn)單的UI處理邏輯轉(zhuǎn)移到View中,這些處理邏輯會(huì)體現(xiàn)在View實(shí)現(xiàn)的接口中。盡管View從Presenter中接管了部分UI處理邏輯,但是Presenter依然是整個(gè)三角關(guān)系的驅(qū)動(dòng)者,View被動(dòng)的地位依然沒(méi)有改變。對(duì)于用戶作用在View上的交互操作,View本身并不進(jìn)行響應(yīng),而是直接將交互請(qǐng)求轉(zhuǎn)發(fā)給Presenter,后者在獨(dú)立完成相應(yīng)的處理流程(可能涉及針對(duì)Model的調(diào)用)之后會(huì)驅(qū)動(dòng)View或者創(chuàng)建新的View作為對(duì)用戶交互操作的響應(yīng)。

View和Presenter交互的規(guī)則(針對(duì)SC模式)

View和Presenter之間的交互是整個(gè)MVP的核心,能否正確地應(yīng)用MVP模式來(lái)架構(gòu)我們的應(yīng)用主要取決于能否正確地處理View和Presenter兩者之間的關(guān)系。在由Model、View和Presenter組成的三角關(guān)系中,核心不是View而是Presenter,Presenter不是View調(diào)用Model的中介,而是最終決定如何響應(yīng)用戶交互行為的決策者。

打個(gè)比方,View是Presenter委派到前端的客戶代理,而作為客戶的自然就是最終的用戶。對(duì)于以鼠標(biāo)/鍵盤操作體現(xiàn)的交互請(qǐng)求應(yīng)該如何處理,作為代理的View并沒(méi)有決策權(quán),所以它會(huì)將請(qǐng)求匯報(bào)給委托人Presenter。View向Presenter發(fā)送用戶交互請(qǐng)求應(yīng)該采用這樣的口吻:“我現(xiàn)在將用戶交互請(qǐng)求發(fā)送給你,你看著辦,需要我的時(shí)候我會(huì)協(xié)助你”,而不應(yīng)該是這樣:“我現(xiàn)在處理用戶交互請(qǐng)求了,我知道該怎么辦,但是我需要你的支持,因?yàn)閷?shí)現(xiàn)業(yè)務(wù)邏輯的Model只信任你”。

對(duì)于Presenter處理用戶交互請(qǐng)求的流程,如果中間環(huán)節(jié)需要涉及到Model,它會(huì)直接發(fā)起對(duì)Model的調(diào)用。如果需要View的參與(比如需要將Model最新的狀態(tài)反應(yīng)在View上),Presenter會(huì)驅(qū)動(dòng)View完成相應(yīng)的工作。

對(duì)于綁定到View上的數(shù)據(jù),不應(yīng)該是View從Presenter上“拉”回來(lái)的,應(yīng)該是Presenter主動(dòng)“推”給View的。從消息流(或者消息交換模式)的角度來(lái)講,不論是View向Presenter完成針對(duì)用戶交互請(qǐng)求的通知,還是Presenter在進(jìn)行交互請(qǐng)求處理過(guò)程中驅(qū)動(dòng)View完成相應(yīng)的UI操作,都是單向(One-Way)的。反應(yīng)在應(yīng)用編程接口的定義上就意味著不論是定義在Presenter中被View調(diào)用的方法,還是定義在IView接口中被Presenter調(diào)用的方法最好都沒(méi)有返回值。如果不采用方法調(diào)用的形式,我們也可以通過(guò)事件注冊(cè)的方式實(shí)現(xiàn)View和Presenter的交互,事件機(jī)制體現(xiàn)的消息流無(wú)疑是單向的。

View本身僅僅實(shí)現(xiàn)單純的、獨(dú)立的UI處理邏輯,它處理的數(shù)據(jù)應(yīng)該是Presenter實(shí)時(shí)推送給它的,所以View盡可能不維護(hù)數(shù)據(jù)狀態(tài)。定義在IView的接口最好只包含方法,而避免屬性的定義,Presenter所需的關(guān)于View的狀態(tài)應(yīng)該在接收到View發(fā)送的用戶交互請(qǐng)求的時(shí)候一次得到,而不需要通過(guò)View的屬性去獲取。

實(shí)例演示:SC模式的應(yīng)用(S101)

為了讓讀者對(duì)MVP模式,尤其是該模式下的View和Presenter之間的交互方式有一個(gè)深刻的認(rèn)識(shí),我們現(xiàn)在來(lái)做一個(gè)簡(jiǎn)單的實(shí)例演示。本實(shí)例采用上面提及的關(guān)于員工查詢的場(chǎng)景,并且采用ASP.NET Web Forms來(lái)建立這個(gè)簡(jiǎn)單的應(yīng)用,最終呈現(xiàn)出來(lái)的效果如圖1-3所示。前面我們已經(jīng)演示了采用PV模式下的IView應(yīng)該如何定義,現(xiàn)在我們來(lái)看看SC模式下的IView有何不同。

先來(lái)看看表示員工信息的數(shù)據(jù)類型如何定義。我們通過(guò)具有如下定義的數(shù)據(jù)類型Employee來(lái)表示一個(gè)員工。簡(jiǎn)單起見(jiàn),我們僅僅定義了表示員工基本信息(ID、姓名、性別、出生日期和部門)的5個(gè)屬性。

public class Employee

{

   public string       Id { get; private set; }

   public string       Name { get; private set; }

   public string       Gender { get; private set; }

   public DateTime     BirthDate { get; private set; }

   public string       Department { get; private set; }

   public Employee(string id, string name, string gender,

       DateTime birthDate, string department)

   {

       this.Id        = id;

       this.Name           = name;

       this.Gender     = gender;

       this.BirthDate     = birthDate;

       this.Department  = department;

   }

}

作為包含應(yīng)用狀態(tài)和狀態(tài)操作行為的Model通過(guò)如下一個(gè)簡(jiǎn)單的EmployeeRepository類型來(lái)體現(xiàn)。如代碼所示,表示所有員工列表的數(shù)據(jù)通過(guò)一個(gè)靜態(tài)字段來(lái)維護(hù),而GetEmployees返回指定部門的員工列表,如果沒(méi)有指定篩選部門或者指定的部門字符為空,則直接返回所有的員工列表。

public class EmployeeRepository

{

   private static IList employees;

   static EmployeeRepository()

   {

       employees = new List();

       employees.Add(new Employee("001", "張三", "男",

           new DateTime(1981, 8, 24), "銷售部"));

       employees.Add(new Employee("002", "李四", "女",

           new DateTime(1982, 7, 10), "人事部"));

       employees.Add(new Employee("003", "王五", "男",

           new DateTime(1981, 9, 21), "人事部"));

   }

   public IEnumerable GetEmployees(string department = "")

   {

       if (string.IsNullOrEmpty(department))

       {

           return employees;

       }

       return employees.Where(e => e.Department == department).ToArray();

   }

}

接下來(lái)我們來(lái)看作為View接口的IEmployeeSearchView的定義。如下面的代碼片段所示,該接口定義了BindEmployees和BindDepartments兩個(gè)方法,分別用于綁定基于部門列表的DropDownList和基于員工列表的GridView。除此之外,IEmployeeSearchView接口還定義了一個(gè)事件DepartmentSelected,該事件會(huì)在用戶選擇了篩選部門后點(diǎn)擊“查詢”按鈕時(shí)觸發(fā)。DepartmentSelected事件參數(shù)類型為自定義的DepartmentSelectedEventArgs,屬性Department表示用戶選擇的部門。

public interface IEmployeeSearchView

{

   void    BindEmployees(IEnumerable employees);

   void    BindDepartments(IEnumerable departments);

   event   EventHandler DepartmentSelected;

}

public class DepartmentSelectedEventArgs : EventArgs

{

   public string Department { get; private set; }

   public DepartmentSelectedEventArgs(string department)

   {

       this.Department = department;

   }

}

作為MVP三角關(guān)系核心的Presenter通過(guò)EmployeeSearchPresenter表示。如下面的代碼片段所示,表示View的只讀屬性類型為IEmployeeSearchView接口,而另一個(gè)只讀屬性Repository則表示作為Model的EmployeeRepository對(duì)象,兩個(gè)屬性均在構(gòu)造函數(shù)中初始化。

public class EmployeeSearchPresenter

{

   public IemployeeSearchView View { get; private set; }

   public EmployeeRepository   Repository { get; private set; }

   public EmployeeSearchPresenter(IEmployeeSearchView view)

   {

       this.View                      = view;

       this.Repository            = new EmployeeRepository();

       this.View.DepartmentSelected += OnDepartmentSelected;

   }

   public void Initialize()

   {

       IEnumerable employees = this.Repository.GetEmployees();

       this.View.BindEmployees(employees);

       string[] departments =

           new string[] { "銷售部", "采購(gòu)部", "人事部", "IT部" };

       this.View.BindDepartments(departments);

   }

   protected void OnDepartmentSelected(object sender,

       DepartmentSelectedEventArgs args)

   {

       string department      = args.Department;

       var employees          = this.Repository.GetEmployees(department);

       this.View.BindEmployees(employees);

   }

}

在構(gòu)造函數(shù)中我們注冊(cè)了View的DepartmentSelected事件,作為事件處理器的OnDepartmentSelected方法通過(guò)調(diào)用Repository(即Model)得到了用戶選擇部門下的員工列表,返回的員工列表通過(guò)調(diào)用View的BindEmployees方法實(shí)現(xiàn)了在View上的數(shù)據(jù)綁定。在Initialize方法中,我們通過(guò)調(diào)用Repository獲取所有員工的列表,并通過(guò)View的BindEmployees方法顯示在界面上。作為篩選條件的部門列表通過(guò)調(diào)用View的BindDepartments方法綁定在View上。

最后我們來(lái)看看作為View的Web頁(yè)面如何定義。如下所示的是作為頁(yè)面主體部分的HTML,核心部分是一個(gè)用于綁定篩選部門列表的DropDownList和一個(gè)綁定員工列表的GridView。

   

       員工管理

       

   

   

       

           

               

                   選擇查詢部門:

                   

                       runat="server" />

                   

                       OnClick="ButtonSearch_Click" />

               

               

                   AutoGenerateColumns="false" Width="100%">

                   

                       

                       

                       

                           HeaderText="出生日期"

                           DataFormatString="{0:dd/MM/yyyy}" />

                       

                   

               

           

       

   

如下所示的是該Web頁(yè)面的后臺(tái)代碼的定義,它實(shí)現(xiàn)了定義在IEmployeeSearchView接口的兩個(gè)方法(BindEmployees和BindDepartments)和一個(gè)事件(DepartmentSelected)。表示Presenter的同名只讀屬性在構(gòu)造函數(shù)中被初始化。在頁(yè)面加載的時(shí)候(Page_Load方法)Presenter的Initialize方法被調(diào)用,而在“查詢”按鈕被點(diǎn)擊的時(shí)候(ButtonSearch_Click)事件DepartmentSelected被觸發(fā)。

public partial class Default : Page, IEmployeeSearchView

{

   public EmployeeSearchPresenter Presenter { get; private set; }

   public event EventHandler DepartmentSelected;

   public Default()

   {

       this.Presenter = new EmployeeSearchPresenter(this);

   }

   protected void Page_Load(object sender, EventArgs e)

   {

       if (!this.IsPostBack)

       {

           this.Presenter.Initialize();

       }

   }

   protected void ButtonSearch_Click(object sender, EventArgs e)

   {

       string department = this.DropDownListDepartments.SelectedValue;

       DepartmentSelectedEventArgs eventArgs =

           new DepartmentSelectedEventArgs(department);

       if (null != DepartmentSelected)

       {

           DepartmentSelected(this, eventArgs);

       }

   }

   public void BindEmployees(IEnumerable employees)

   {

       this.GridViewEmployees.DataSource = employees;

       this.GridViewEmployees.DataBind();

   }

   public void BindDepartments(IEnumerable departments)

   {

       this.DropDownListDepartments.DataSource = departments;

       this.DropDownListDepartments.DataBind();

   }

}

感謝各位的閱讀!關(guān)于“常用的MVC變體有哪些”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。


網(wǎng)站題目:常用的MVC變體有哪些-創(chuàng)新互聯(lián)
網(wǎng)頁(yè)網(wǎng)址:http://weahome.cn/article/dpjppc.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部