SharePoint 的導覽選項

本文說明在 SharePoint 中啟用 SharePoint 發行功能的瀏覽選項網站。 導覽的選擇和設定會大幅影響 SharePoint 中網站的效能和延展性。 只有在集中式入口網站需要時,才應該使用 SharePoint 發佈網站範本,而且發布功能應該只在特定網站上啟用,而且只有在需要時才啟用,因為它可能會在不正確使用時影響效能。

注意事項

如果您使用新式 SharePoint 瀏覽選項,例如大型功能表、串聯流覽或中樞導覽,本文不適用於您的網站。 新式 SharePoint 網站架構會利用更扁平化的網站階層和中樞和輪輻模型。 這可讓許多不需要使用 SharePoint 發行功能的案例達成。

瀏覽選項概觀

導覽提供者設定可能會大幅影響整個網站的效能,而且必須謹慎考慮,才能挑選可針對 SharePoint 網站需求有效調整的流覽提供者和設定。 有兩個現成可用的導覽提供者,以及自定義導覽實作。

如果您為網站開啟結構化導覽快取,第一個選項 [結構化導覽] 是 SharePoint 中適用於傳統 SharePoint 網站的建議導覽選項。 此導覽提供者會顯示目前網站下方的導覽專案,並選擇性地顯示目前的網站及其同層級。 它提供其他功能,例如安全性調整和網站結構列舉。 如果停用快取,這會對效能和延展性造成負面影響,而且可能會受到節流影響。

第二個選項 [ 受控 (元數據]) 導覽,代表使用Managed Metadata 字詞組的導覽專案。 除非必要,否則建議停用安全性調整。 安全性調整會啟用為此導覽提供者的安全預設設定;不過,許多網站不需要安全性調整的額外負荷,因為瀏覽元素通常對網站的所有使用者都是一致的。 使用建議的設定來停用安全性調整,此導覽提供者不需要列舉網站結構,而且具有可接受效能影響的高度可調整性。

除了現成的導覽提供者之外,許多客戶已成功實作替代的自定義導覽實作。 請參閱本文中的 搜尋 驅動用戶端腳本

SharePoint 導覽選項的優點和缺點

下表摘要說明每個選項的優點和缺點。

結構化導覽 受管理導覽 搜尋 驅動導覽 自定義導覽提供者
優點:

容易維護
已調整安全性
內容變更時的24小時內自動更新
優點:

容易維護
優點:

已調整安全性
新增網站時自動更新
快速載入時間和本機快取的導覽結構
優點:

可用選項的更廣泛選擇
正確使用快取時快速載入
許多選項都適用於回應式頁面設計
缺點:

停用快取時會影響效能
受限於節流
缺點:

不會自動更新以反映網站結構
啟用安全性調整 或瀏覽結構複雜時,會影響效能
缺點:

無法輕鬆地訂購網站
需要自定義主版頁面 (所需的技術技能)
缺點:

需要自定義開發
需要儲存的外部數據源/快取,例如 Azure

網站最適合的選項取決於您的網站需求和技術功能。 如果您想要在內容變更時自動更新的易於設定導覽提供者,則 啟用快取 的結構化瀏覽是不錯的選項。

注意事項

將整體網站結構簡化為較平坦的非階層式結構,以套用與新式 SharePoint 網站相同的原則,可改善效能,並簡化移至新式 SharePoint 網站的作業。 這表示,與其擁有具有數百個網站的單一網站集合 (子網站) ,更好的方法是讓許多網站集合 (子網站) 。

分析 SharePoint 中的瀏覽效能

Page Diagnostics for SharePoint 工具是適用於 Microsoft Edge 和 Chrome 瀏覽器的瀏覽器延伸模組,可分析 SharePoint 新式入口網站和傳統發佈網站頁面。 此工具僅適用於 SharePoint,無法在 SharePoint 系統頁面上使用。

此工具會為每個分析的頁面產生報告,顯示頁面如何針對預先定義的規則集執行,並在測試結果落在基準值之外時顯示詳細資訊。 SharePoint 系統管理員和設計人員可以使用此工具來針對效能問題進行疑難解答,以確保新頁面在發佈之前已優化。

特別是SPRequestDuration 是SharePoint處理頁面所需的時間。 大量導覽 (,例如在流覽) 中包含頁面、複雜的網站階層,以及其他設定和拓撲選項,都可能會大幅增加持續時間。

在 SharePoint 中使用結構化導覽

這是預設使用的現成流覽,也是最直接的解決方案。 它不需要任何自定義,而且非技術使用者也可以輕鬆地新增專案、隱藏專案,以及從 [設定] 頁面管理導覽。 我們建議 您啟用快取,否則會有昂貴的效能取捨。

如何實作結構化導覽快取

[網站設定>外觀及操作>流覽] 底下,您可以驗證是否已針對全域流覽或目前導覽選取結構化導覽。 選 取 [顯示頁面 ] 會對效能造成負面影響。

已選取 [顯示子網站] 的結構化導覽。

您可以在網站集合層級和網站層級啟用或停用快取,而且預設會針對這兩者啟用。 若要在網站集合層級啟用,請在 [ 網站設定>網站集合管理>網站集合導覽] 底下,核取 [ 啟用快取] 的方塊

在網站集合層級啟用快取。

若要在月臺層級啟用,請在 [網站設定導覽>] 底下,核取 [啟用快取] 的方塊

在月臺層級啟用快取。

在 SharePoint 中使用受控導覽和元數據

Managed 導覽是另一個現成可用的選項,可用來重新建立與結構化導覽相同的大部分功能。 受控元數據可以設定為啟用或停用安全性調整。 在停用安全性調整的情況下設定時,受控導覽會相當有效率,因為它會載入具有固定數目的伺服器呼叫的所有導覽連結。 不過,啟用安全性調整會否定 Managed 導覽的一些效能優點。

如果您需要啟用安全性調整,建議您:

  • 將所有易記 URL 連結更新為簡單連結
  • 新增必要的安全性修剪節點作為易記 URL
  • 將瀏覽項目數目限制為不超過 100 個,且深度不超過三層

許多網站都不需要安全性調整,因為瀏覽結構通常與網站的所有使用者一致。 如果安全性調整已停用,且已將連結新增至並非所有使用者都可存取的導覽,則連結仍會顯示,但會導致拒絕存取訊息。 無意外存取內容的風險。

如何實作Managed導覽和結果

Microsoft 上有數篇文章瞭解受控導覽的詳細數據。 例如,請參閱 SharePoint Server 中 Managed 導覽的概觀

若要實作 Managed 導覽,您可以使用對應至網站導覽結構的 URL 來設定字詞。 在許多情況下,Managed 導覽甚至可以手動策劃來取代結構化導覽。 例如:

SharePoint 網站結構。)

使用 搜尋 驅動的用戶端腳本

自定義導覽實作的其中一個常見類別,是採用用戶端轉譯的設計模式,以儲存導覽節點的本機快取。

這些導覽提供者有幾個主要優點:

  • 它們通常適用於回應式頁面設計。
  • 它們非常可擴充且效能高,因為它們不需要資源成本即可轉譯 (,並在逾時) 之後於背景重新整理。
  • 這些導覽提供者可以使用各種策略來擷取導覽數據,範圍從簡單的靜態設定到各種動態數據提供者。

數據提供者的範例是使用 搜尋 驅動導覽,這可讓您彈性地列舉瀏覽節點,並有效率地處理安全性調整。

還有其他常用的選項可建置 自定義導覽提供者。 如需建置自定義導覽提供者的進一步指引,請檢閱 SharePoint入口網站的 導覽解決方案。

使用搜尋,您可以利用使用連續編目在背景中建置的索引。 搜尋結果會從搜尋索引中提取,而且結果會進行安全性調整。 當需要安全性調整時,這通常會比現成的導覽提供者更快。 使用搜尋結構化導覽,特別是當您有複雜的網站結構時,將會大幅加快頁面載入時間。 這比受控導覽的主要優點是,您可以從安全性調整中獲益。

這種方法牽涉到建立自定義主版頁面,並以自定義 HTML 取代現成的瀏覽程式代碼。 請遵循下列範例中所述的這個程式來取代 檔案 seattle.html中的導覽程序代碼。 在此範例中 seattle.html ,您將開啟 檔案,並以自定義 HTML 程式代碼取代整個專案 id="DeltaTopNavigation"

範例:取代主版頁面中現成可用的導覽程序代碼

  1. 瀏覽至 [網站設定] 頁面。
  2. 按兩下 [主版頁面],以開啟 主版頁面庫。
  3. 您可以從這裡瀏覽連結庫並下載檔案 seattle.master
  4. 使用文字編輯器編輯程序代碼,並在下列螢幕快照中刪除程式代碼區塊。
    刪除顯示的程式代碼區塊。
  5. 移除和 <\SharePoint:AjaxDelta> 標記之間的程式代碼,<SharePoint:AjaxDelta id="DeltaTopNavigation">並以下列代碼段取代它:
<div id="loading">
  <!--Replace with path to loading image.-->
  <div style="background-image: url(''); height: 22px; width: 22px; ">
  </div>
</div>
<!-- Main Content-->
<div id="navContainer" style="display:none">
    <div data-bind="foreach: hierarchy" class="noindex ms-core-listMenu-horizontalBox">
        <a class="dynamic menu-item ms-core-listMenu-item ms-displayInline ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">
            <span class="menu-item-text" data-bind="text: item.Title">
            </span>
        </a>
        <ul id="menu" data-bind="foreach: $data.children" style="padding-left:20px">
            <li class="static dynamic-children level1">
                <a class="static dynamic-children menu-item ms-core-listMenu-item ms-displayInline ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">

                 <!-- ko if: children.length > 0-->
                    <span aria-haspopup="true" class="additional-background ms-navedit-flyoutArrow dynamic-children">
                        <span class="menu-item-text" data-bind="text: item.Title">
                        </span>
                    </span>
                <!-- /ko -->
                <!-- ko if: children.length == 0-->
                    <span aria-haspopup="true" class="ms-navedit-flyoutArrow dynamic-children">
                        <span class="menu-item-text" data-bind="text: item.Title">
                        </span>
                    </span>
                <!-- /ko -->
                </a>

                <!-- ko if: children.length > 0-->
                <ul id="menu"  data-bind="foreach: children;" class="dynamic  level2" >
                    <li class="dynamic level2">
                        <a class="dynamic menu-item ms-core-listMenu-item ms-displayInline  ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">

          <!-- ko if: children.length > 0-->
          <span aria-haspopup="true" class="additional-background ms-navedit-flyoutArrow dynamic-children">
           <span class="menu-item-text" data-bind="text: item.Title">
           </span>
          </span>
           <!-- /ko -->
          <!-- ko if: children.length == 0-->
          <span aria-haspopup="true" class="ms-navedit-flyoutArrow dynamic-children">
           <span class="menu-item-text" data-bind="text: item.Title">
           </span>
          </span>
          <!-- /ko -->
                        </a>
          <!-- ko if: children.length > 0-->
         <ul id="menu" data-bind="foreach: children;" class="dynamic level3" >
          <li class="dynamic level3">
           <a class="dynamic menu-item ms-core-listMenu-item ms-displayInline ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">
            <span class="menu-item-text" data-bind="text: item.Title">
            </span>
           </a>
          </li>
         </ul>
           <!-- /ko -->
                    </li>
                </ul>
                <!-- /ko -->
            </li>
        </ul>
    </div>
</div>

6.使用網站集合中載入映射的連結,取代開頭載入影像錨點標籤的 URL。 進行變更之後,請重新命名檔案,然後將它上傳至主版頁面庫。 這會產生新的 .master 檔案。
7.此 HTML 是基本標記,將由 JavaScript 程式代碼傳回的搜尋結果填入。 您必須編輯程序代碼,以變更 var root = “site collection URL” 的值,如下列代碼段所示:
var root = "https://spperformance.sharepoint.com/sites/NavigationBySearch";

8.結果會指派給 self.nodes 陣列,而階層則是使用 linq.js 將輸出指派給陣列 self.hierarchy 來建置的物件。 這個陣列是系結至 HTML 的物件。 這會在 toggleView () 函式中完成,方法是將 self 對象傳遞至 ko.applyBinding () 函式。
這接著會使階層陣列系結至下列 HTML:
<div data-bind="foreach: hierarchy" class="noindex ms-core-listMenu-horizontalBox">

mouseexit 的事件處理程式mouseenter會新增至最上層導覽,以處理在函式中addEventsToElements()完成的子網站下拉功能表。

在我們的複雜導覽範例中,沒有本機快取的全新頁面載入會顯示在伺服器上花費的時間已從基準檢驗結構化導覽中縮減,以取得與受控流覽方法類似的結果。

關於 JavaScript 檔案...

注意事項

如果使用自定義 JavaScript,請確定公用 CDN 已啟用,且檔案位於 CDN 位置。

整個 JavaScript 檔案如下所示:

//Models and Namespaces
var SPOCustom = SPOCustom || {};
SPOCustom.Models = SPOCustom.Models || {}
SPOCustom.Models.NavigationNode = function () {

    this.Url = ko.observable("");
    this.Title = ko.observable("");
    this.Parent = ko.observable("");

};

var root = "https://spperformance.sharepoint.com/sites/NavigationBySearch";
var baseUrl = root + "/_api/search/query?querytext=";
var query = baseUrl + "'contentClass=\"STS_Web\"+path:" + root + "'&trimduplicates=false&rowlimit=300";

var baseRequest = {
    url: "",
    type: ""
};


//Parses a local object from JSON search result.
function getNavigationFromDto(dto) {
    var item = new SPOCustom.Models.NavigationNode();
    if (dto != undefined) {

        var webTemplate = getSearchResultsValue(dto.Cells.results, 'WebTemplate');

        if (webTemplate != "APP") {
            item.Title(getSearchResultsValue(dto.Cells.results, 'Title')); //Key = Title
            item.Url(getSearchResultsValue(dto.Cells.results, 'Path')); //Key = Path
            item.Parent(getSearchResultsValue(dto.Cells.results, 'ParentLink')); //Key = ParentLink
        }

    }
    return item;
}

function getSearchResultsValue(results, key) {

    for (i = 0; i < results.length; i++) {
        if (results[i].Key == key) {
            return results[i].Value;
        }
    }
    return null;
}

//Parse a local object from the serialized cache.
function getNavigationFromCache(dto) {
    var item = new SPOCustom.Models.NavigationNode();

    if (dto != undefined) {

        item.Title(dto.Title);
        item.Url(dto.Url);
        item.Parent(dto.Parent);
    }

    return item;
}

/* create a new OData request for JSON response */
function getRequest(endpoint) {
    var request = baseRequest;
    request.type = "GET";
    request.url = endpoint;
    request.headers = { ACCEPT: "application/json;odata=verbose" };
    return request;
};

/* Navigation Module*/
function NavigationViewModel() {
    "use strict";
    var self = this;
    self.nodes = ko.observableArray([]);
    self.hierarchy = ko.observableArray([]);;
    self.loadNavigatioNodes = function () {
        //Check local storage for cached navigation datasource.
        var fromStorage = localStorage["nodesCache"];
        if (false) {
            var cachedNodes = JSON.parse(localStorage["nodesCache"]);

            if (cachedNodes && timeStamp) {
                //Check for cache expiration. Currently set to 3 hrs.
                var now = new Date();
                var diff = now.getTime() - timeStamp;
                if (Math.round(diff / (1000 * 60 * 60)) < 3) {

                    //return from cache.
                    var cacheResults = [];
                    $.each(cachedNodes, function (i, item) {
                        var nodeitem = getNavigationFromCache(item, true);
                        cacheResults.push(nodeitem);
                    });

                    self.buildHierarchy(cacheResults);
                    self.toggleView();
                    addEventsToElements();
                    return;
                }
            }
        }
        //No cache hit, REST call required.
        self.queryRemoteInterface();
    };

    //Executes a REST call and builds the navigation hierarchy.
    self.queryRemoteInterface = function () {
        var oDataRequest = getRequest(query);
        $.ajax(oDataRequest).done(function (data) {
            var results = [];
            $.each(data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results, function (i, item) {

                if (i == 0) {
                    //Add root element.
                    var rootItem = new SPOCustom.Models.NavigationNode();
                    rootItem.Title("Root");
                    rootItem.Url(root);
                    rootItem.Parent(null);
                    results.push(rootItem);
                }
                var navItem = getNavigationFromDto(item);
                results.push(navItem);
            });
            //Add to local cache
            localStorage["nodesCache"] = ko.toJSON(results);

            localStorage["nodesCachedAt"] = new Date().getTime();
            self.nodes(results);
            if (self.nodes().length > 0) {
                var unsortedArray = self.nodes();
                var sortedArray = unsortedArray.sort(self.sortObjectsInArray);

                self.buildHierarchy(sortedArray);
                self.toggleView();
                addEventsToElements();
            }
        }).fail(function () {
            //Handle error here!!
            $("#loading").hide();
            $("#error").show();
        });
    };
    self.toggleView = function () {
        var navContainer = document.getElementById("navContainer");
        ko.applyBindings(self, navContainer);
        $("#loading").hide();
        $("#navContainer").show();

    };
    //Uses linq.js to build the navigation tree.
    self.buildHierarchy = function (enumerable) {
        self.hierarchy(Enumerable.From(enumerable).ByHierarchy(function (d) {
            return d.Parent() == null;
        }, function (parent, child) {
            if (parent.Url() == null || child.Parent() == null)
                return false;
            return parent.Url().toUpperCase() == child.Parent().toUpperCase();
        }).ToArray());

        self.sortChildren(self.hierarchy()[0]);
    };


    self.sortChildren = function (parent) {

        // sjip processing if no children
        if (!parent || !parent.children || parent.children.length === 0) {
            return;
        }

        parent.children = parent.children.sort(self.sortObjectsInArray2);

        for (var i = 0; i < parent.children.length; i++) {
            var elem = parent.children[i];

            if (elem.children && elem.children.length > 0) {
                self.sortChildren(elem);
            }
        }
    };

    // ByHierarchy method breaks the sorting in chrome and firefox
    // we need to resort  as ascending
    self.sortObjectsInArray2 = function (a, b) {
        if (a.item.Title() > b.item.Title())
            return 1;
        if (a.item.Title() < b.item.Title())
            return -1;
        return 0;
    };


    self.sortObjectsInArray = function (a, b) {
        if (a.Title() > b.Title())
            return -1;
        if (a.Title() < b.Title())
            return 1;
        return 0;
    }
}

//Loads the navigation on load and binds the event handlers for mouse interaction.
function InitCustomNav() {
    var viewModel = new NavigationViewModel();
    viewModel.loadNavigatioNodes();
}

function addEventsToElements() {
    //events.
      $("li.level1").mouseover(function () {
          var position = $(this).position();
          $(this).find("ul.level2").css({ width: 100, left: position.left + 10, top: 50 });
      })
   .mouseout(function () {
     $(this).find("ul.level2").css({  left: -99999, top: 0 });
   
    });
   
     $("li.level2").mouseover(function () {
          var position = $(this).position();
          console.log(JSON.stringify(position));
          $(this).find("ul.level3").css({ width: 100, left: position.left + 95, top:  position.top});
      })
   .mouseout(function () {
     $(this).find("ul.level3").css({  left: -99999, top: 0 });
    });
} _spBodyOnLoadFunctionNames.push("InitCustomNav");

為了摘要說明函式中 jQuery $(document).ready 上述的程式代碼,會 viewModel object 建立 ,然後 loadNavigationNodes() 呼叫該物件上的 函式。 此函式會載入先前建置的瀏覽階層,該階層儲存在用戶端瀏覽器的 HTML5 本機記憶體中,或呼叫 函式 queryRemoteInterface()

QueryRemoteInterface() 使用 getRequest() 函式搭配稍早在腳本中定義的查詢參數來建置要求,然後從伺服器傳回數據。 此數據基本上是網站集合中所有網站的數位,以具有各種屬性的數據傳輸物件表示。

此數據接著會剖析成先前定義 SPO.Models.NavigationNode 的物件,用來 Knockout.js 建立可觀察的屬性,以供數據系結值到我們稍早定義的 HTML 中使用。

然後,物件會放入結果陣列中。 此陣列會使用將這個數位剖析成 JSON,並儲存在本機瀏覽器記憶體中,以改善未來頁面載入的效能。

此方法的優點

此方法的主要優點之一是使用 HTML5 本機記憶體,在使用者下次載入頁面時,導覽會儲存在本機。 我們透過使用搜尋 API 進行結構化導覽,獲得主要的效能改善;不過,執行和自定義這項功能需要一些技術功能。

在範 例實作中,網站的排序方式與現成的結構化導覽相同;依字母順序排列。 如果您想要偏離此順序,開發和維護會更複雜。 此外,此方法需要您偏離支援的主版頁面。 如果未維護自定義主版頁面,您的網站將會錯過 Microsoft 對主版頁面所做的更新和改善。

上述程式代碼具有下列相依性:

目前的 LinqJS 版本不包含上述程式代碼中使用的 ByHierarchy 方法,而且會中斷導覽程式代碼。 若要修正此問題,請將下列方法新增至行前面 Flatten: function ()的 Linq.js 檔案。

ByHierarchy: function(firstLevel, connectBy, orderBy, ascending, parent) {
     ascending = ascending == undefined ? true : ascending;
     var orderMethod = ascending == true ? 'OrderBy' : 'OrderByDescending';
     var source = this;
     firstLevel = Utils.CreateLambda(firstLevel);
     connectBy = Utils.CreateLambda(connectBy);
     orderBy = Utils.CreateLambda(orderBy);

     //Initiate or increase level
     var level = parent === undefined ? 1 : parent.level + 1;

    return new Enumerable(function() {
         var enumerator;
         var index = 0;

        var createLevel = function() {
                 var obj = {
                     item: enumerator.Current(),
                     level : level
                 };
                 obj.children = Enumerable.From(source).ByHierarchy(firstLevel, connectBy, orderBy, ascending, obj);
                 if (orderBy !== undefined) {
                     obj.children = obj.children[orderMethod](function(d) {
                         return orderBy(d.item); //unwrap the actual item for sort to work
                     });
                 }
                 obj.children = obj.children.ToArray();
                 Enumerable.From(obj.children).ForEach(function(child) {
                     child.getParent = function() {
                         return obj;
                     };
                 });
                 return obj;
             };

        return new IEnumerator(

        function() {
             enumerator = source.GetEnumerator();
         }, function() {
             while (enumerator.MoveNext()) {
                 var returnArr;
                 if (!parent) {
                     if (firstLevel(enumerator.Current(), index++)) {
                         return this.Yield(createLevel());
                     }

                } else {
                     if (connectBy(parent.item, enumerator.Current(), index++)) {
                         return this.Yield(createLevel());
                     }
                 }
             }
             return false;
         }, function() {
             Utils.Dispose(enumerator);
         })
     });
 },

SharePoint Server 中受管理導覽的概觀

結構化導覽快取和效能