从视图或部分视图将 CSS 或 JavaScript 文件添加到布局头

Posted

技术标签:

【中文标题】从视图或部分视图将 CSS 或 JavaScript 文件添加到布局头【英文标题】:Add CSS or JavaScript files to layout head from views or partial views 【发布时间】:2011-07-03 20:39:11 【问题描述】:

布局页面头部:

<head>
    <link href="@Url.Content("~/Content/themes/base/Site.css")"
          rel="stylesheet" type="text/css" />
</head>

应用需要的一个视图(AnotherView):

<link href="@Url.Content("~/Content/themes/base/AnotherPage.css")"
      rel="stylesheet" type="text/css" />

而另一个视图有一个局部视图(AnotherPartial),它需要:

<link href="@Url.Content("~/Content/themes/base/AnotherPartial.css")"
      rel="stylesheet" type="text/css" />

问题:我们如何添加这些 CSS 文件链接 AnotherView 和 AnotherPartial 链接到 Layout head

RenderSection 不是一个好主意,因为 AnotherPage 可以有多个 Partials。将所有 CSS 添加到 head 没有用,因为它会动态更改(它取决于 Anotherpages)。

【问题讨论】:

@NuriYILMAZ 根据您的标题,“来自视图”和“或部分视图”之间存在巨大差异。无论如何,关于这件事有什么新想法吗? 【参考方案1】:

布局:

<html>
    <head>
        <meta charset="utf-8" />
        <title>@ViewBag.Title</title>
        <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
        <script src="@Url.Content("~/Scripts/jquery-1.6.2.min.js")" type="text/javascript"></script>
        <script src="@Url.Content("~/Scripts/modernizr-2.0.6-development-only.js")" type="text/javascript"></script>
        @if (IsSectionDefined("AddToHead"))
        
            @RenderSection("AddToHead", required: false)
        

        @RenderSection("AddToHeadAnotherWay", required: false)
    </head>

查看:

@model ProjectsExt.Models.DirectoryObject

@section AddToHead
    <link href="@Url.Content("~/Content/Upload.css")" rel="stylesheet" type="text/css" />

【讨论】:

我认为这是最简单的解决方案。 开箱即用的好解决方案! 如果AddToHead 部分位于嵌入在View 中的局部视图中,这将不起作用。 这个问题特别提到了部分观点,而这个评分最高的答案并没有解决这个问题!对于另一个查询,这可能是一个很好的解决方案,但不是这个。 如果它确实适用于局部视图,那将是一个优雅的解决方案。【参考方案2】:

更新:https://github.com/speier/mvcassetshelper

上提供的基本示例

我们正在使用以下实现将 JS 和 CSS 文件添加到布局页面中。

查看或部分查看:

@
    Html.Assets().Styles.Add("/Dashboard/Content/Dashboard.css");
    Html.Assets().Scripts.Add("/Dashboard/Scripts/Dashboard.js");

布局页面:

<head>
    @Html.Assets().Styles.Render()
</head>

<body>
    ...
    @Html.Assets().Scripts.Render()
</body>

HtmlHelper 扩展:

public static class HtmlHelperExtensions

    public static AssetsHelper Assets(this HtmlHelper htmlHelper)
    
        return AssetsHelper.GetInstance(htmlHelper);
        


public class AssetsHelper 

    public static AssetsHelper GetInstance(HtmlHelper htmlHelper)
    
        var instanceKey = "AssetsHelperInstance";

        var context = htmlHelper.ViewContext.HttpContext;
        if (context == null) return null;

        var assetsHelper = (AssetsHelper)context.Items[instanceKey];

        if (assetsHelper == null)
            context.Items.Add(instanceKey, assetsHelper = new AssetsHelper());

        return assetsHelper;
    

    public ItemRegistrar Styles  get; private set; 
    public ItemRegistrar Scripts  get; private set; 

    public AssetsHelper()
    
        Styles = new ItemRegistrar(ItemRegistrarFormatters.StyleFormat);
        Scripts = new ItemRegistrar(ItemRegistrarFormatters.ScriptFormat);
    


public class ItemRegistrar

    private readonly string _format;
    private readonly IList<string> _items;

    public ItemRegistrar(string format)
    
        _format = format;
        _items = new List<string>();
    

    public ItemRegistrar Add(string url)
    
        if (!_items.Contains(url))
            _items.Add(url);

        return this;
    

    public IHtmlString Render()
    
        var sb = new StringBuilder();

        foreach (var item in _items)
        
            var fmt = string.Format(_format, item);
            sb.AppendLine(fmt);
        

        return new HtmlString(sb.ToString());
    


public class ItemRegistrarFormatters

    public const string StyleFormat = "<link href=\"0\" rel=\"stylesheet\" type=\"text/css\" />";
    public const string ScriptFormat = "<script src=\"0\" type=\"text/javascript\"></script>";

【讨论】:

@JBeckton:查看代码并将Insert 方法替换为Add 方法 @Kalman - 这个线程安全性(在我看来非常正确)受到质疑:***.com/questions/6609586/… 这是非常错误的;它应该是[ThreadStatic],或者最好存储在HttpContext.Items 我错过了什么吗?如果在 中调用 Styles.Render(),则在 之后添加的任何 css 文件(即在局部视图中添加的文件)都不会被渲染。 (MVC 从上到下渲染。) @FernandoCorreia 我认为你完全错了。命名部分在作为整个线程基础的部分视图中不起作用。【参考方案3】:

你可以在布局中通过RenderSection方法定义section。

布局

<head>
  <link href="@Url.Content("~/Content/themes/base/Site.css")"
    rel="stylesheet" type="text/css" />
  @RenderSection("heads", required: false)
</head>

然后,您可以在视图的部分区域中包含您的 css 文件部分视图除外

该部分在视图中工作,但在设计上不能在部分视图中工作

<!--your code -->
@section heads

  <link href="@Url.Content("~/Content/themes/base/AnotherPage.css")"
  rel="stylesheet" type="text/css" />


如果你真的想在局部视图中使用截面区域,可以按照文章重新定义RenderSection方法。

Razor, Nested Layouts and Redefined Sections – Marcin On ASP.NET

【讨论】:

【参考方案4】:

遗憾的是,默认情况下无法像其他用户建议的那样使用section,因为section 仅对View 的直接child 可用。

然而可行的是在每个视图中实现和重新定义section,意思是:

section Head

    @RenderSection("Head", false)

这样每个视图都可以实现一个 head 部分,而不仅仅是直接子级。但这仅部分有效,尤其是在多个部分开始时(正如您在问题中提到的那样)。

因此,解决您的问题的唯一真正方法是使用ViewBag。最好的可能是 CSS 和脚本的单独集合(列表)。为此,您需要确保在执行任何视图之前初始化使用的List。然后你可以在每个视图/部分的顶部做这样的事情(不关心 ScriptsStyles 值是否为空:

ViewBag.Scripts.Add("myscript.js");
ViewBag.Styles.Add("mystyle.css");

然后,您可以在布局中循环遍历集合并根据List 中的值添加样式。

@foreach (var script in ViewBag.Scripts)

    <script type="text/javascript" src="@script"></script>

@foreach (var style in ViewBag.Styles)

    <link href="@style" rel="stylesheet" type="text/css" />

我认为它很丑,但它是唯一有效的东西。

******更新**** 由于它首先开始执行内部视图并逐步处理布局,并且 CSS 样式是级联的,因此通过 ViewBag.Styles.Reverse() 反转样式列表可能是有意义的。

这种方式首先添加最外层的样式,这与 CSS 样式表的工作方式是一致的。

【讨论】:

感谢 ntziolis。看起来不错,但是剃刀布局头在另一个视图之前首先工作,并且 .scripts 和 .styles 动态在另一个视图之前是空的。我找到了关于它的不错的博客,并分享了这个问题。 这将适用于任何派生视图,但不适用于部分视图。对于部分,确实执行顺序是错误的。基本上对于部分没有办法将它们包含在标题中。我建议,不要将其添加到标题中,只需将其添加到正文标记的开头即可。这不是我的第一选择,但这样你就有一种简洁的方式在一个地方管理所有样式/js,而不是分散它们。 我同意你的看法。因为我在答案中找到了一些解决方案,但它正是 js 解决方案。我真的很想知道为什么我们不能将布局页面用作经典的 asp.net。这意味着我可以从子页面到达头部。【参考方案5】:

我试图解决这个问题。

我的答案就在这里。

“动态标题”-http://dynamicheader.codeplex.com/、https://nuget.org/packages/DynamicHeader

例如_Layout.cshtml是:

<head>
@Html.DynamicHeader()
</head>
...

而且,您可以将 .js 和 .css 文件注册到您想要的任何地方的“DynamicHeader”。

例如AnotherPartial.cshtml中的代码块是:

@
  DynamicHeader.AddSyleSheet("~/Content/themes/base/AnotherPartial.css");
  DynamicHeader.AddScript("~/some/myscript.js");

此示例的 HTML 输出结果为:

<html>
  <link href="/myapp/Content/themes/base/AnotherPartial.css" .../>
  <script src="/myapp/some/myscript.js" ...></script>
</html>
...

【讨论】:

【参考方案6】:

我遇到了类似的问题,最后用下面的代码应用了 Kalman 的出色答案(不是那么整洁,但可以说更具可扩展性):

namespace MvcHtmlHelpers

    //http://***.com/questions/5110028/add-css-or-js-files-to-layout-head-from-views-or-partial-views#5148224
    public static partial class HtmlExtensions
    
        public static AssetsHelper Assets(this HtmlHelper htmlHelper)
        
            return AssetsHelper.GetInstance(htmlHelper);
        
    
    public enum BrowserType  Ie6=1,Ie7=2,Ie8=4,IeLegacy=7,W3cCompliant=8,All=15
    public class AssetsHelper
    
        public static AssetsHelper GetInstance(HtmlHelper htmlHelper)
        
            var instanceKey = "AssetsHelperInstance";
            var context = htmlHelper.ViewContext.HttpContext;
            if (context == null) return null;
            var assetsHelper = (AssetsHelper)context.Items[instanceKey];
            if (assetsHelper == null)context.Items.Add(instanceKey, assetsHelper = new AssetsHelper(htmlHelper));
            return assetsHelper;
        
        private readonly List<string> _styleRefs = new List<string>();
        public AssetsHelper AddStyle(string stylesheet)
        
            _styleRefs.Add(stylesheet);
            return this;
        
        private readonly List<string> _scriptRefs = new List<string>();
        public AssetsHelper AddScript(string scriptfile)
        
            _scriptRefs.Add(scriptfile);
            return this;
        
        public IHtmlString RenderStyles()
        
            ItemRegistrar styles = new ItemRegistrar(ItemRegistrarFormatters.StyleFormat,_urlHelper);
            styles.Add(Libraries.UsedStyles());
            styles.Add(_styleRefs);
            return styles.Render();
        
        public IHtmlString RenderScripts()
        
            ItemRegistrar scripts = new ItemRegistrar(ItemRegistrarFormatters.ScriptFormat, _urlHelper);
            scripts.Add(Libraries.UsedScripts());
            scripts.Add(_scriptRefs);
            return scripts.Render();
        
        public LibraryRegistrar Libraries  get; private set; 
        private UrlHelper _urlHelper;
        public AssetsHelper(HtmlHelper htmlHelper)
        
            _urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
            Libraries = new LibraryRegistrar();
        
    
    public class LibraryRegistrar
    
        public class Component
        
            internal class HtmlReference
            
                internal string Url  get; set; 
                internal BrowserType ServeTo  get; set; 
            
            internal List<HtmlReference> Styles  get; private set; 
            internal List<HtmlReference> Scripts  get; private set; 
            internal List<string> RequiredLibraries  get; private set; 

            public Component()
            
                Styles = new List<HtmlReference>();
                Scripts = new List<HtmlReference>();
                RequiredLibraries = new List<string>();
            
            public Component Requires(params string[] libraryNames)
            
                foreach (var lib in libraryNames)
                
                    if (!RequiredLibraries.Contains(lib))
                         RequiredLibraries.Add(lib); 
                
                return this;
            
            public Component AddStyle(string url, BrowserType serveTo = BrowserType.All)
            
                Styles.Add(new HtmlReference  Url = url, ServeTo=serveTo );
                return this;
            
            public Component AddScript(string url, BrowserType serveTo = BrowserType.All)
            
                Scripts.Add(new HtmlReference  Url = url, ServeTo = serveTo );
                return this;
            
        
        private readonly Dictionary<string, Component> _allLibraries = new Dictionary<string, Component>();
        private List<string> _usedLibraries = new List<string>();
        internal IEnumerable<string> UsedScripts()
        
            SetOrder();
            var returnVal = new List<string>();
            foreach (var key in _usedLibraries)
            
                returnVal.AddRange(from s in _allLibraries[key].Scripts
                                   where IncludesCurrentBrowser(s.ServeTo)
                                   select s.Url);
            
            return returnVal;
        
        internal IEnumerable<string> UsedStyles()
        
            SetOrder();
            var returnVal = new List<string>();
            foreach (var key in _usedLibraries)
            
                returnVal.AddRange(from s in _allLibraries[key].Styles
                                   where IncludesCurrentBrowser(s.ServeTo)
                                   select s.Url);
            
            return returnVal;
        
        public void Uses(params string[] libraryNames)
        
            foreach (var name in libraryNames)
            
                if (!_usedLibraries.Contains(name))_usedLibraries.Add(name);
            
        
        public bool IsUsing(string libraryName)
        
            SetOrder();
            return _usedLibraries.Contains(libraryName);
        
        private List<string> WalkLibraryTree(List<string> libraryNames)
        
            var returnList = new List<string>(libraryNames);
            int counter = 0;
            foreach (string libraryName in libraryNames)
            
                WalkLibraryTree(libraryName, ref returnList, ref counter);
            
            return returnList;
        
        private void WalkLibraryTree(string libraryName, ref List<string> libBuild, ref int counter)
        
            if (counter++ > 1000)  throw new System.Exception("Dependancy library appears to be in infinate loop - please check for circular reference"); 
            Component library;
            if (!_allLibraries.TryGetValue(libraryName, out library))
                 throw new KeyNotFoundException("Cannot find a definition for the required style/script library named: " + libraryName); 
            foreach (var childLibraryName in library.RequiredLibraries)
            
                int childIndex = libBuild.IndexOf(childLibraryName);
                if (childIndex!=-1)
                
                    //child already exists, so move parent to position before child if it isn't before already
                    int parentIndex = libBuild.LastIndexOf(libraryName);
                    if (parentIndex>childIndex)
                    
                        libBuild.RemoveAt(parentIndex);
                        libBuild.Insert(childIndex, libraryName);
                    
                
                else
                
                    libBuild.Add(childLibraryName);
                    WalkLibraryTree(childLibraryName, ref libBuild, ref counter);
                
            
            return;
        
        private bool _dependenciesExpanded;
        private void SetOrder()
        
            if (_dependenciesExpanded)return;
            _usedLibraries = WalkLibraryTree(_usedLibraries);
            _usedLibraries.Reverse();
            _dependenciesExpanded = true;
        
        public Component this[string index]
        
            get
            
                if (_allLibraries.ContainsKey(index))
                     return _allLibraries[index]; 
                var newComponent = new Component();
                _allLibraries.Add(index, newComponent);
                return newComponent;
            
        
        private BrowserType _requestingBrowser;
        private BrowserType RequestingBrowser
        
            get
            
                if (_requestingBrowser == 0)
                
                    var browser = HttpContext.Current.Request.Browser.Type;
                    if (browser.Length > 2 && browser.Substring(0, 2) == "IE")
                    
                        switch (browser[2])
                        
                            case '6':
                                _requestingBrowser = BrowserType.Ie6;
                                break;
                            case '7':
                                _requestingBrowser = BrowserType.Ie7;
                                break;
                            case '8':
                                _requestingBrowser = BrowserType.Ie8;
                                break;
                            default:
                                _requestingBrowser = BrowserType.W3cCompliant;
                                break;
                        
                    
                    else
                    
                        _requestingBrowser = BrowserType.W3cCompliant;
                    
                
                return _requestingBrowser;
            
        
        private bool IncludesCurrentBrowser(BrowserType browserType)
        
            if (browserType == BrowserType.All)  return true; 
            return (browserType & RequestingBrowser) != 0;
        
    
    public class ItemRegistrar
    
        private readonly string _format;
        private readonly List<string> _items;
        private readonly UrlHelper _urlHelper;

        public ItemRegistrar(string format, UrlHelper urlHelper)
        
            _format = format;
            _items = new List<string>();
            _urlHelper = urlHelper;
        
        internal void Add(IEnumerable<string> urls)
        
            foreach (string url in urls)
            
                Add(url);
            
        
        public ItemRegistrar Add(string url)
        
            url = _urlHelper.Content(url);
            if (!_items.Contains(url))
                 _items.Add( url); 
            return this;
        
        public IHtmlString Render()
        
            var sb = new StringBuilder();
            foreach (var item in _items)
            
                var fmt = string.Format(_format, item);
                sb.AppendLine(fmt);
            
            return new HtmlString(sb.ToString());
        
    
    public class ItemRegistrarFormatters
    
        public const string StyleFormat = "<link href=\"0\" rel=\"stylesheet\" type=\"text/css\" />";
        public const string ScriptFormat = "<script src=\"0\" type=\"text/javascript\"></script>";
    

项目包含一个静态的AssignAllResources方法:

assets.Libraries["jQuery"]
        .AddScript("~/Scripts/jquery-1.10.0.min.js", BrowserType.IeLegacy)
        .AddScript("~/Scripts//jquery-2.0.1.min.js",BrowserType.W3cCompliant);
        /* NOT HOSTED YET - CHECK SOON 
        .AddScript("//ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js",BrowserType.W3cCompliant);
        */
    assets.Libraries["jQueryUI"].Requires("jQuery")
        .AddScript("//ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js",BrowserType.Ie6)
        .AddStyle("//ajax.aspnetcdn.com/ajax/jquery.ui/1.9.2/themes/eggplant/jquery-ui.css",BrowserType.Ie6)
        .AddScript("//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js", ~BrowserType.Ie6)
        .AddStyle("//ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/themes/eggplant/jquery-ui.css", ~BrowserType.Ie6);
    assets.Libraries["TimePicker"].Requires("jQueryUI")
        .AddScript("~/Scripts/jquery-ui-sliderAccess.min.js")
        .AddScript("~/Scripts/jquery-ui-timepicker-addon-1.3.min.js")
        .AddStyle("~/Content/jQueryUI/jquery-ui-timepicker-addon.css");
    assets.Libraries["Validation"].Requires("jQuery")
        .AddScript("//ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.min.js")
        .AddScript("~/Scripts/jquery.validate.unobtrusive.min.js")
        .AddScript("~/Scripts/mvcfoolproof.unobtrusive.min.js")
        .AddScript("~/Scripts/CustomClientValidation-1.0.0.min.js");
    assets.Libraries["MyUtilityScripts"].Requires("jQuery")
        .AddScript("~/Scripts/GeneralOnLoad-1.0.0.min.js");
    assets.Libraries["FormTools"].Requires("Validation", "MyUtilityScripts");
    assets.Libraries["AjaxFormTools"].Requires("FormTools", "jQueryUI")
        .AddScript("~/Scripts/jquery.unobtrusive-ajax.min.js");
    assets.Libraries["DataTables"].Requires("MyUtilityScripts")
        .AddScript("//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js")
        .AddStyle("//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css")
        .AddStyle("//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables_themeroller.css");
    assets.Libraries["MvcDataTables"].Requires("DataTables", "jQueryUI")
        .AddScript("~/Scripts/jquery.dataTables.columnFilter.min.js");
    assets.Libraries["DummyData"].Requires("MyUtilityScripts")
        .AddScript("~/Scripts/DummyData.js")
        .AddStyle("~/Content/DummyData.css");     

在_layout页面中

@
    var assets = Html.Assets();
    CurrentResources.AssignAllResources(assets);
    Html.Assets().RenderStyles()

</head>
...
    @Html.Assets().RenderScripts()
</body>

在局部和视图中

Html.Assets().Libraries.Uses("DataTables");
Html.Assets().AddScript("~/Scripts/emailGridUtilities.js");

【讨论】:

有趣。似乎有点矫枉过正,但我​​确实看到这更多地用于那些别无选择的网站,而是与使用旧版本 ie 的用户打交道......就像在一些国家没有升级的公司环境中,你想自己开枪。哈哈。为此 +1【参考方案7】:

这是一个名为Cassette 的NuGet 插件,除其他外,它还使您能够在部分中引用脚本和样式。

虽然这个插件有a number of configurations 可用,这使得它非常灵活。这是引用脚本或样式表文件的最简单方法:

Bundles.Reference("scripts/app");

根据the documentation:

Reference 的调用可以出现在页面、布局或局部视图中的任何位置。

路径参数可以是以下之一:

捆绑路径 资产路径 - 引用包含此资产的整个捆绑包 网址

【讨论】:

【参考方案8】:

尝试开箱即用的解决方案(ASP.NET MVC 4 或更高版本):

@
    var bundle = BundleTable.Bundles.GetRegisteredBundles().First(b => b.Path == "~/js");

    bundle.Include("~/Scripts/myFile.js");

【讨论】:

我收到一个错误:CS0103: The name 'BundleTable' does not exist in the current context nvm:已解决。必须附加System.Web.OptimizationSystem.Web.Optimization.BundleTable.Bundles.GetRegisteredBundles().First(b =&gt; b.Path == "~/bundles/css"); 这不会全局修改捆绑包吗?如果您在页面 A 上执行此操作,然后打开页面 B,页面 B 也将包含 myFile.js,我认为 OP 不希望这样做【参考方案9】:

对于我们这些使用 ASP.NET MVC 4 的人 - 这可能会有所帮助。

首先,我在 App_Start 文件夹中添加了一个 BundleConfig 类。

这是我用来创建它的代码:

using System.Web.Optimization;

public class BundleConfig

    public static void RegisterBundles(BundleCollection bundles)
    
        bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/SiteMaster.css"));
    

其次,我在 Global.asax 文件中注册了 BundleConfig 类:

protected void Application_Start()

    BundleConfig.RegisterBundles(BundleTable.Bundles);

第三,我在我的 CSS 文件中添加了样式助手:

/* Styles for validation helpers */
.field-validation-error 
    color: red;
    font-weight: bold;


.field-validation-valid 
    display: none;


input.input-validation-error 
    border: 1px solid #e80c4d;


input[type="checkbox"].input-validation-error 
    border: 0 none;


.validation-summary-errors 
    color: #e80c4d;
    font-weight: bold;
    font-size: 1.1em;


.validation-summary-valid 
    display: none;

最后我在任何视图中都使用了这个语法:

@Styles.Render("~/Content/css")

【讨论】:

但这会导致&lt;body&gt;标签中间的链接不正确。【参考方案10】:

我编写了一个简单的包装器,它允许您将每个局部视图中的样式和脚本动态注册到 head 标记中。

它基于 jsakamoto 提出的 DynamicHeader,但它有一些性能改进和调整。

它非常易于使用,用途广泛。

用法:

@
    DynamicHeader.AddStyleSheet("/Content/Css/footer.css", ResourceType.Layout);    
    DynamicHeader.AddStyleSheet("/Content/Css/controls.css", ResourceType.Infrastructure);
    DynamicHeader.AddScript("/Content/Js/Controls.js", ResourceType.Infrastructure);
    DynamicHeader.AddStyleSheet("/Content/Css/homepage.css");    

您可以在里面找到完整的代码、解释和示例: Add Styles & Scripts Dynamically to Head Tag

【讨论】:

以上是关于从视图或部分视图将 CSS 或 JavaScript 文件添加到布局头的主要内容,如果未能解决你的问题,请参考以下文章

Magento 从特定视图中删除 css 或 js

如何将代码隐藏页面添加到视图或部分视图

部分视图与 Json(或两者)

如何使用 CSS 或 JS 防止 iOS 键盘将视图推离屏幕

通过表单将复杂类型从部分视图传递到控制器操作

导航选项卡或部分视图哪个更好?