如何进一步减少加载时间(Visualforce、Apex、Angular)



我构建了一个Visualforce页面,它将Contacts和Lead都拉到一个视图中。它的工作效果很好,只是加载需要20-25秒,众所周知,这对于可用性来说是可怕的

我已经查看了日志,看起来Apex占用了70%的加载时间,30%用于javascript部分,如果需要,我可以发布日志。

注:本项目基于Mohit Shrivastav的工作。在项目进行到一半的时候,我决定使用datatables.net而不是Angular来进行排序,因为我还在学习Angular。

这是控制器:

public with sharing class recentMqlExplorerController {
  public String LeadContactList {
    get; set;
  }
  public class ContactLeadWrap {
    public String id;
    public String name;
    public String dateofmql;
    public String company;
    public Decimal leadscore;
    public String country;
    public String state;
    public Integer employees;
    public String ownername;
    public String leadstatus;
    public String leadorcontact;
    public String team;
    public String url;
    ContactLeadWrap() {
      id = '';
      name = '';
      company = '';
      leadscore = 0;
      country = '';
      state = '';
      employees = 0;
      ownername = '';
      leadstatus = '';
      url = '';
      team = '';
    }
  }
  //Method to bring the list of Contacts and Leads and Serialize Wrapper Object as JSON
  public  static String getlstContactLead() {
    List <ContactLeadWrap> lstwrap = new List <ContactLeadWrap> ();
    List <Contact> lstcontact = new List<Contact>();
    List <Lead> lstlead = new List<Lead>();
    Datetime now = System.now();
    Datetime last = now.addDays(-60);
    Id ownerId = UserInfo.getUserId();

    lstcontact = [SELECT Id, FirstName, Name, Date_Became_MQL__c, Account.Name, mkto2__Lead_Score__c, Account.BillingCountry, Account.BillingState, Account.NumberOfEmployees, Owner.Name, z_Lead_Status_at_Convert_TEXT_if_applcbl__c, Account.AOU__r.sub_sub_Team_Picklist__c FROM Contact WHERE (Date_Became_MQL__c > :last AND Owner.Id = :ownerId) OR FirstName = 'westest' LIMIT 20000];
    lstlead = [SELECT Id, FirstName, Name, Date_Became_MQL__c, Company, mkto2__Lead_Score__c, Country, State, NumberOfEmployees, Owner.Name, status, z_Lead_Owner_User__r.sub_sub_Team_Picklist__c FROM Lead  WHERE (Date_Became_MQL__c > :last AND IsConverted = false AND Owner.Id = :ownerId) OR (FirstName = 'westest' AND IsConverted = false) LIMIT 20000];

    String convertedDateofmql = null;
    Date dateOnly = null;
    String fullRecordURL = null;
    for (Contact c: lstcontact) {
        if (c.Date_Became_MQL__c != null) {
            convertedDateofmql = c.Date_Became_MQL__c.format('MM/dd/YYYY');
        }
        if (c.z_Lead_Status_at_Convert_TEXT_if_applcbl__c == null) {
            c.z_Lead_Status_at_Convert_TEXT_if_applcbl__c = 'no status';
        }
        fullRecordURL = URL.getSalesforceBaseUrl().toExternalForm() + '/' + c.Id;
        ContactLeadWrap cwrap = new ContactLeadWrap();
        cwrap.id = c.id;
        cwrap.name = c.name;
        cwrap.url = fullRecordURL;
        cwrap.dateofmql = convertedDateofmql;
        cwrap.company = c.Account.Name;
        cwrap.leadscore = c.mkto2__Lead_Score__c;
        cwrap.country = c.Account.BillingCountry;
        cwrap.state = c.Account.BillingState;
        cwrap.employees = c.Account.NumberOfEmployees;
        cwrap.ownername = c.Owner.Name;
        cwrap.leadstatus = c.z_Lead_Status_at_Convert_TEXT_if_applcbl__c;
        cwrap.leadorcontact = 'Contact';
        cwrap.team = c.Account.AOU__r.sub_sub_Team_Picklist__c;
        lstwrap.add(cwrap);
    }
    for (Lead l: lstlead) {
        if (l.Date_Became_MQL__c != null) {
            convertedDateofmql = l.Date_Became_MQL__c.format('MM/dd/YYYY');
        }
        fullRecordURL = URL.getSalesforceBaseUrl().toExternalForm() + '/' + l.Id;
        ContactLeadWrap lwrap = new ContactLeadWrap();
        lwrap.id = l.id;
        lwrap.name = l.name;
        lwrap.url = fullRecordURL;
        lwrap.dateofmql = convertedDateofmql;
        lwrap.company = l.Company;
        lwrap.leadscore = l.mkto2__Lead_Score__c;
        lwrap.country = l.Country;
        lwrap.state = l.State;
        lwrap.employees = l.NumberOfEmployees;
        lwrap.ownername = l.Owner.Name;
        lwrap.leadstatus = l.status;
        lwrap.leadorcontact = 'Lead';
        lwrap.team = l.z_Lead_Owner_User__r.sub_sub_Team_Picklist__c;
        lstwrap.add(lwrap);
    }
    return JSON.serialize(lstwrap);
  }
  public class getInformation {
  }
}

这是页面:

<apex:page standardStylesheets="false" sidebar="false" showHeader="false" docType="html-5.0" controller="recentMqlExplorerController">
    <html xmlns:ng="http://angularjs.org" ng-app="hello" lang="en">
      <head>
        <meta charset="utf-8"></meta>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"></meta>
        <title>MQL Explorer</title>
        <meta name="description" content=""></meta>
        <meta name="viewport" content="width=device-width"></meta>
          <link rel="stylesheet" href="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/sass-bootstrap/dist/css/bootstrap.css')}"/>
          <link rel="stylesheet" href="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/styles/main.css')}"/>
          <link type='text/css' href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400,300' rel='stylesheet'/>
      </head>
      <body>
          <!-- =========== Binding Controller to Body of Page ============= -->
            <div class="jumbotron ribbon">
            <h2>Hello {!$User.FirstName}:</h2> <h4> Welcome to the Recent MQL Dashboard</h4>
        </div>
        <div class="jumbotron banner row">
            <span class="chevron"><i class="fa fa-chevron-down fa-1x"></i></span>
            <div class="jumbotron jbot"></div>
                    <div class="dashboard">
                    </div>
                    <div class="dashboard-shadow"></div>
        </div>
          <div ng-controller="ctrlRead" class="container table-container">
                <div class="table-container-header">
                        <h4>Qualified Leads and Contacts</h4>
                </div>
              <table id="dashboard" class="table-borders display responsive" width="100%">
                <thead>
                    <tr role="row" class="tableFilters">
                        <th>Search by Name</th>
                        <th>From when?</th>
                        <th>Search by Company</th>
                        <th></th>
                        <th></th>
                        <th></th>
                        <th></th>
                        <th id="owner">Search by Owner</th>
                        <th>What Status?</th>
                        <!-- <th>What Type?</th> -->
                        <th>What Team?</th>
                    </tr>
                    <tr role="row" class="tableHeader">
                        <th scope="col" class="name">Name</th>
                        <th scope="col" class="dateofmql" width="250px">Date of MQL</th>
                        <th scope="col" class="company">Company</th>
                        <th scope="col" class="leadscore">Score</th>
                        <th scope="col" class="country">Country</th>
                        <th scope="col" class="state">State</th>
                        <th scope="col" class="employees">EE#</th>
                        <th scope="col" class="ownername">Owner Name</th>
                        <th scope="col" class="leadstatus">Lead Status</th>
                        <!-- <th scope="col" class="leadorcontact">Type</th> -->
                        <th scope="col" class="team">Sales Team</th>
                    </tr>
                </thead>
              <tbody class="table">
                      <tr ng-repeat="item in pagedItems[currentPage] | orderBy:sortingOrder:reverse">
                          <th><apex:outputlink style="text-decoration: underline; color: #01B2E4;" target="_blank" value="{{item.url}}">{{item.name}}</apex:outputlink></th>
                          <td>{{item.dateofmql}}</td>
                          <td>{{item.company}}</td>
                          <td>{{item.leadscore}}</td>
                          <td>{{item.country}}</td>
                          <td>{{item.state}}</td>
                          <td>{{item.employees}}</td>
                          <td>{{item.ownername}}</td>
                          <td>{{item.leadstatus}}</td>
                          <td>{{item.team}}</td>
                      </tr>
                  </tbody>
              </table>
            </div>
        <script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/angular/angular.min.js')}"/>
          <script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/jquery.min.js')}"/>
          <script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/jquery-ui.min.js')}"/>
          <script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/jquery.dataTables.min.js')}"/>
          <script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/dataTables.tableTools.min.js')}"/>
          <script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/dataTables.responsive.min.js')}"/>
          <script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/jquery/jquery.dataTables.columnFilter-min.js')}"/>
          <script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/bower_components/angular-force/ngForce.min.js')}"/>
          <script src="{!URLFOR($Resource.smbMqlHunterAppMarkIiiDEV, '/scripts/app-min.js')}"/>
          <script type="text/javascript">
                'use strict';
                <!-- Name your application -->
                var myapp = angular.module('hello', []);
                var sortingOrder = 'name';
                <!-- Define Controller  -->
                var contrl=myapp.controller('ctrlRead', function ($scope, $filter) {
                <!--- Initialize Scope Variables --->
                $scope.sortingOrder = sortingOrder;
                $scope.reverse = false;
                $scope.filteredItems = [];
                $scope.groupedItems = [];
                $scope.itemsPerPage = 60000;
                $scope.pagedItems = [];
                $scope.currentPage = 0;
                $scope.items ={!lstContactLead};
                var searchMatch = function (haystack, needle) {
                    if (!needle) {
                        return true;
                    }
                    return haystack.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
                };
                //Initialize the Search Filters
                $scope.search = function () {
                    $scope.filteredItems = $filter('filter')($scope.items, function (item) {
                        for (var attr in item) {
                            if (searchMatch(item[attr], $scope.query))
                                return true;
                        }
                        return false;
                    });
                    // Define Sorting Order
                    if ($scope.sortingOrder !== '') {
                        $scope.filteredItems = $filter('orderBy')($scope.filteredItems, $scope.sortingOrder, $scope.reverse);
                    }
                    $scope.currentPage = 0;
                    // Group by pages
                    $scope.groupToPages();
                };
                // Calculate Total Number of Pages based on Records Queried
                $scope.groupToPages = function () {
                    $scope.pagedItems = [];
                    for (var i = 0; i < $scope.filteredItems.length; i++) {
                        if (i % $scope.itemsPerPage === 0) {
                            $scope.pagedItems[Math.floor(i / $scope.itemsPerPage)] = [$scope.filteredItems[i]];
                        } else {
                            $scope.pagedItems[Math.floor(i / $scope.itemsPerPage)].push($scope.filteredItems[i]);
                        }
                    }
                };
                $scope.range = function (start, end) {
                    var ret = [];
                    if (!end) {
                        end = start;
                        start = 0;
                    }
                    for (var i = start; i < end; i++) {
                        ret.push(i);
                    }
                    return ret;
                };
                  $scope.prevPage = function () {
                      if ($scope.currentPage > 0) {
                          $scope.currentPage--;
                      }
                  };
                  $scope.nextPage = function () {
                      if ($scope.currentPage < $scope.pagedItems.length - 1) {
                          $scope.currentPage++;
                      }
                  };
                  $scope.setPage = function () {
                      $scope.currentPage = this.n;
                  };
                  // functions have been describe process the data for display
                  $scope.search();
                  // change sorting order
                  $scope.sort_by = function (newSortingOrder) {
                      if ($scope.sortingOrder == newSortingOrder)
                          $scope.reverse = !$scope.reverse;
                      $scope.sortingOrder = newSortingOrder;
                      // icon setup
                      $('th i').each(function () {
                          // icon reset
                          $(this).removeClass().addClass('icon-sort');
                      });
                      if ($scope.reverse)
                          $('th.' + new_sorting_order + ' i').removeClass().addClass('icon-chevron-up');
                      else
                          $('th.' + new_sorting_order + ' i').removeClass().addClass('icon-chevron-down');
                    };
                });
                contrl.$inject = ['$scope', '$filter'];
          </script>
        </body>
    </html>
</apex:page>

请查看salesforce提供的以下指导原则,这可能有助于提高页面性能。

较大的页面大小直接影响加载时间。改善Visualforce页面加载时间:

缓存任何经常访问的数据,如图标图形。

在Apex控制器getter方法中避免SOQL查询。

通过以下方式减少页面上显示的记录数:

限制从Apex控制器中的SOQL调用返回的数据。例如,在WHERE子句中使用AND语句,或删除空结果

利用列表控制器的分页功能,每页显示更少的记录

"延迟加载"Apex对象以减少请求时间。

考虑将任何JavaScript移到标记之外,并将其放在结束标记之前的标记中。标记将JavaScript放在结束元素之前;因此,Visualforce试图在页面上的任何其他内容之前加载JavaScript。但是,只有当您确信JavaScript不会对您的页面产生任何不利影响时,才应该将其移动到页面底部。例如,需要document.write或事件处理程序的JavaScript代码片段应该保留在元素中。

在任何情况下,Visualforce页面都必须小于15 MB。

问候,

Naveen

http://www.autorabit.com

只需粗略地查看一下您的代码,以下是我的观察结果。

1) 您使用了太多存储在脚本中的脚本,如angular、jquery、Bootstrap等。使用这些脚本会造成时间滞后。不确定您的需求,但要寻找限制外部脚本使用的方法。

2) 不使用Salesforce标签-几乎完整的页面是基于JAVA的,插入到顶点标签中。例如,使用apex表格,而不是使用表格tr和td标签。

使用标准的开箱即用功能将尝试减少页面的加载时间,并尝试利用本机Salesforce脚本。

3) 如果你不使用很多标签,我甚至建议使用自定义脚本而不是引导程序。在Salesforce工作时,尽量保持Salesforce的外观和感觉,这将产生最佳效果。

免责声明-这些是优化页面加载的一般建议,尽管特定要求可能会强制使用本机脚本。

Ashish

最新更新