65.9K
CodeProject 正在变化。 阅读更多。
Home

SharePoint Framework (SPFx) 大型列表 Web 部件,使用 React 和 Office UI 组件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2018年3月21日

CPOL

7分钟阅读

viewsIcon

46962

downloadIcon

1206

SharePoint 框架 Web 部件,用于使用 React 和 REST API 检索 SharePoint 列表项,并使用 DetailsList Office UI fabric 组件显示结果。

引言

此 Web 部件将检索超出阈值限制的 SharePoint 列表项,并使用 DetailsList Office UI fabric 组件显示结果。由于 SharePoint 列表视图的阈值限制,没有管理员权限的用户将无法浏览 SharePoint 列表项。添加和配置此 Web 部件将允许至少拥有站点读取权限的用户查看列表中的所有项。

先决条件

  1. 通过运行 Yeoman SharePoint 生成器创建项目 ViewAllItemsWebPart
  2. 安装 React 分页组件
  3. 安装 jQuery

Using the Code

以下是作为此 Web 部件一部分创建的组件:

  1. 自定义属性窗格 - 一个自定义属性窗格控件,用于显示多选下拉列表,以便在选择 list 属性时填充列。有关如何创建自定义属性窗格控件,请参考此 链接
  2. Config - 如果 Web 部件属性未配置,则会渲染 Config 组件。
  3. Paging - 这是 React 分页组件。用于为列表视图添加分页。
  4. ViewAllItems - 渲染 DetailsList Office UI fabric 组件并将列表项绑定到它的组件。
  5. services - 此处包含使用 Rest API 从 SharePoint 获取列表和列的方法。

让我们开始修改 Yeoman 生成器生成的脚手架。

WebPart.manifest.json

编辑 Web 部件清单 JSON 文件属性部分,以包含三个属性:列表名称(listName)、列名称(selectedIds)和页面大小(pageSize),并设置默认值,如下所示。

"properties": {
 "listName": "",
 "selectedIds":[],
 "pageSize": 100
}

定义 Web 部件属性常量和属性类型

loc 文件夹下,更新 en-us.jsmystrings.js 以包含以下脚本。这些脚本用于定义 Web 部件属性窗格配置中所需的属性常量和属性类型。

en-us.js

define([], function() {
  return {
    "PropertyPaneDescription": "Description",
    "BasicGroupName": "Group Name",
    "ListNameFieldLabel": "Select List",
    "ColumnFieldLabel": "Select Columns",
    "PageSizeFieldLabel": "Select Page Size"
  }
});

mystrings.d.ts

declare interface IViewAllItemsWebPartStrings {
  PropertyPaneDescription: string;
  BasicGroupName: string;
  ListNameFieldLabel: string;
  ColumnFieldLabel: string;
  PageSizeFieldLabel: string;
}

declare module 'ViewAllItemsWebPartStrings' {
  const strings: IViewAllItemsWebPartStrings;
  export = strings;
}

Webpart.ts

将以下 import 添加到 webpart.ts 文件中

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
  BaseClientSideWebPart,
  IPropertyPaneConfiguration,
  PropertyPaneDropdown,
  IPropertyPaneDropdownOption
} from '@microsoft/sp-webpart-base';

import * as strings from 'ViewAllItemsWebPartStrings';
import ViewAllItems from './components/ViewAllItems';
import { IViewAllItemsProps } from './components/IViewAllItemsProps';
import { IList } from './services/IList';
import { IListColumn } from './services/IListColumn';
import { ListService } from './services/ListService';
import { MultiSelectBuilder, IItemProp, PropertyPaneMultiSelect } 
         from './CustomPropertyPane/PropertyPaneMultiSelect';
import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';

删除 getPropertyPaneConfiguration 方法中现有的代码行,并添加以下代码行:

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneDropdown('listName', {
                  label: strings.ListNameFieldLabel,
                  options: this.lists,
                  disabled: this.listsDropdownDisabled,
                
                }),                
                PropertyPaneMultiSelect("selectedIds", {
                  label: "Select Columns",
                  selectedItemIds: this.properties.selectedIds, //Ids of Selected Items
                  onload: () => this.loadColumns(), //On load function to items 
                                                    //for drop down
                  onPropChange: this.onPropertyPaneFieldChanged,// On Property Change 
                                                                //function
                  properties: this.properties, //Web Part properties
                  key: "targetkey",  //unique key
                  disabled: !this.properties.listName
                }),
                PropertyPaneDropdown('pageSize',{
                  label: strings.PageSizeFieldLabel,
                  options:[
                    {key: '10', text: '10'},
                    {key: '25', text: '25'},
                    {key: '50', text: '50'},
                    {key: '100', text: '100'}
                  ]
                })
              ]
            }
          ]
        }
      ]
    };
  }

PropertyPaneMultiselect 是自定义属性窗格组件。请遵循上面提供的链接将其添加到此 Web 部件项目中。

onPropertyPaneConfigurationStartonPropertyPaneFieldChanged 方法添加到 webpart 类中,以创建级联下拉列表并加载属性窗格控件的数据。

protected onPropertyPaneConfigurationStart(): void {
    this.listsDropdownDisabled = !this.lists;

    if (this.lists) {
      return;
    }

    this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'lists');

    this.loadLists()
    .then((listOptions: IPropertyPaneDropdownOption[]): void => {
      this.lists = listOptions;
      this.listsDropdownDisabled = false;
      this.context.propertyPane.refresh();
      this.context.statusRenderer.clearLoadingIndicator(this.domElement);
      this.render();
    });
  }

protected onPropertyPaneFieldChanged(propertyPath: string, 
    oldValue: any, newValue: any): void {
    if (propertyPath === 'listName' && newValue) {
      // push new list value
      super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
      // get previously selected column
      const previousItem: string[] = this.properties.selectedIds;
      // reset selected item
      this.properties.selectedIds = [];
      // push new item value
      this.onPropertyPaneFieldChanged('selectedIds', previousItem, 
                                       this.properties.selectedIds);
      
      //this.columnsDropdown.render(this.domElement);

      this.render();
      // refresh the item selector control by repainting the property pane
      this.context.propertyPane.refresh();
      
    }
    else {
      super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
    }
  }

如果您注意到上面的方法正在调用 loadListsloadColumns 方法来获取数据,请将以下方法添加到 webpart 类中。

private loadLists(): Promise<IPropertyPaneDropdownOption[]> {
    const dataService = new ListService(this.context);

    return new Promise<IPropertyPaneDropdownOption[]>(resolve => {
      dataService.getLists()
      .then((response: IList[]) => {
          var options : IPropertyPaneDropdownOption[] = [];

          response.forEach((item: IList) => {
            options.push({"key": item.Title, "text": item.Title});
          });

          resolve(options);
      });
    });
  }

  private loadColumns(): Promise<IItemProp[]> {
    if (!this.properties.listName) {
      // resolve to empty options since no list has been selected
      return Promise.resolve();
    }
    const dataService = new ListService(this.context);

    return new Promise<IItemProp[]>(resolve => {
      dataService.getColumns(this.properties.listName)
      .then((response) => {
          var options : IItemProp[] = [];
          this.properties.selectedColumnsAndType = [];
          response.forEach((column: IListColumn) => {
            options.push({"key": column.StaticName, "text": column.Title});
            this.properties.selectedColumnsAndType.push
                ({"key": column.StaticName, "text": column.TypeDisplayName});
          });

          resolve(options);
      });
    });
  } 

反过来,这些方法会引用 ListService 类来获取数据。向解决方案添加新文件夹 services,并添加文件 ILists.tsIListColumn.tsListService.ts

ILists.ts

接口 IList.ts 定义了将从 REST API 请求中请求的列表元数据。此接口将获取 list idtitle

export interface IList {
    Id: string;
    Title: string;
  }

IListColumns.ts

接口 IListColumns 定义了将从 API 请求中请求的列元数据。这将获取列标题、内部名称和列类型。

export interface IListColumn {
    Title: string;
    StaticName: string;
    TypeDisplayName: string;
  }

ListService.ts

将以下脚本添加到 ListService.ts 文件中。此文件将包含 getListsGetColumns 方法,这些方法通过将站点的当前上下文附加到 SharePoint REST API 作为 url 参数传递给 HTTP 请求来从 SharePoint 获取 ListsColumns 信息。

export class ListService {

    constructor(private context: IWebPartContext) {
    }

    public getLists(): Promise<IList[]> {
        var httpClientOptions : ISPHttpClientOptions = {};
    
        httpClientOptions.headers = {
            'Accept': 'application/json;odata=nometadata',
            'odata-version': ''
        };
    
        return new Promise<IList[]>((resolve: (results: IList[]) => void, 
                                      reject: (error: any) => void): void => {
          this.context.spHttpClient.get(this.context.pageContext.web.serverRelativeUrl + 
                          `/_api/web/lists?$select=id,title&$filter=Hidden eq false`,
            SPHttpClient.configurations.v1,
            httpClientOptions
            )
            .then((response: SPHttpClientResponse): Promise<{ value: IList[] }> => {
              return response.json();
            })
            .then((lists: { value: IList[] }): void => {
              resolve(lists.value);
            }, (error: any): void => {
              reject(error);
            });
        });
    }

    public getColumns(listName: string): Promise<IListColumn[]> {
      var httpClientOptions : ISPHttpClientOptions = {};
 
      httpClientOptions.headers = {
          'Accept': 'application/json;odata=nometadata',
          'odata-version': ''
      };
 
      return new Promise<IListColumn[]>((resolve: (results: IListColumn[]) => void, 
                                 reject: (error: any) => void): void => {
        this.context.spHttpClient.get(this.context.pageContext.web.serverRelativeUrl + 
               `/_api/web/lists/GetByTitle('${listName}')/fields?
                 $filter=TypeDisplayName ne 
               'Attachments' and Hidden eq false and ReadOnlyField eq false`,
          SPHttpClient.configurations.v1,
          httpClientOptions
          )
          .then((response: SPHttpClientResponse): Promise<{ value: IListColumn[] }> => {
            return response.json();
          })
          .then((listColumns: { value: IListColumn[] }): void => {
            resolve(listColumns.value);
          }, (error: any): void => {
            reject(error);
          });
      });
    }      
}

将以下方法添加到 webpart 类中。当调用 Web 部件 render 方法时,这些方法将作为属性传递给 ReactDOM 元素。

  1. needsCofiguration - 这决定了渲染视图所需的 webpart 属性是否已配置。
  2. selectedColumns - 这将返回从自定义属性窗格控件中选择的列。
  3. configureWebPart - 调用此方法将打开 webpart 属性窗格。
private needsConfiguration(): boolean {
    return this.properties.listName === null ||
      this.properties.listName === undefined ||
      this.properties.listName.trim().length === 0 ||
      this.properties.selectedIds === null ||
      this.properties.selectedIds === undefined ||
      this.properties.selectedIds.length === 0;
  }

  private selectedColumns(): IItemProp[] {
    if(this.properties.selectedColumnsAndType === null ||
      this.properties.selectedColumnsAndType===undefined ||
      this.properties.selectedColumnsAndType.length === 0){
      return [];
      }
      else{
        return this.properties.selectedColumnsAndType.filter
                 (obj => this.properties.selectedIds.indexOf(obj.key) !== -1);
      }
  }
  private configureWebPart(): void {
    this.context.propertyPane.open();
  }

删除 Web 部件 render 方法中的现有代码,复制并粘贴以下代码:

public render(): void {
    const element: React.ReactElement<IViewAllItemsProps> = React.createElement(
      ViewAllItems,
      {
        spHttpClient: this.context.spHttpClient,
        siteUrl: this.context.pageContext.web.absoluteUrl,
        listName: this.properties.listName,
        needsConfiguration: this.needsConfiguration(),
        configureWebPart: this.configureWebPart,
        displayMode: this.displayMode,
        selectedColumns: this.selectedColumns(),
        pageSize: this.properties.pageSize
      }
    );

    ReactDom.render(element, this.domElement);
  }

render 方法将加载 ViewAllItems.tsx 组件中的 JSX。向解决方案添加文件 ViewAllItems.tsxIViewAllItemsProps.tsViewAllItems.module.scss

IViewAllItemsProps.ts

此接口文件定义了将组件加载到 ReactDOM 所需的所有属性。

export interface IViewAllItemsProps {
  spHttpClient: SPHttpClient;
  siteUrl: string;
  listName: string;
  selectedColumns: IItemProp[];
  needsConfiguration:boolean;
  configureWebPart: () => void;
  displayMode: DisplayMode;
  pageSize: number;
}

ViewAllItems.module.scss

这些文件包含 ViewAllItems.tsx 文件中使用的所有样式。

@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';

.viewAllItems {
  .container {
    margin: 0px auto;
    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
  }

  .row {
    @include ms-Grid-row;
    @include ms-fontColor-white;
    background-color: $ms-color-themeDark;
    padding: 20px;
  }

  .column {
    @include ms-Grid-col;
    @include ms-lg10;
    @include ms-xl8;
    @include ms-xlPush2;
    @include ms-lgPush1;
  }

  .title {
    @include ms-font-xl;
    @include ms-fontColor-white;
  }

  .subTitle {
    @include ms-font-l;
    @include ms-fontColor-white;
  }

  .description {
    @include ms-font-l;
    @include ms-fontColor-white;
  }
  .status{
    float:left
  }
  .button {
    // Our button
    text-decoration: none;
    height: 32px;

    // Primary Button
    min-width: 80px;
    background-color: $ms-color-themePrimary;
    border-color: $ms-color-themePrimary;
    color: $ms-color-white;

    // Basic Button
    outline: transparent;
    position: relative;
    font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,
                  BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
    -webkit-font-smoothing: antialiased;
    font-size: $ms-font-size-m;
    font-weight: $ms-font-weight-regular;
    border-width: 0;
    text-align: center;
    cursor: pointer;
    display: inline-block;
    padding: 0 16px;

    .label {
      font-weight: $ms-font-weight-semibold;
      font-size: $ms-font-size-m;
      height: 32px;
      line-height: 32px;
      margin: 0 4px;
      vertical-align: top;
      display: inline-block;
    }
  }
}

ViewAllItems.tsx

ViewAllItems 类继承自 React.Component,它期望 PropsState 作为参数。我们已经定义了 PropsIViewAllItemsProps.ts)。在类上方的 tsx 文件中,定义 state,如下所示。

export interface IViewAllItemsState {
  items?: any[];
  columns?:IColumn[];
  status?: string;
  currentPage?: number;
  itemCount?: number;
  pageSize?: number;
}

在类内部,添加构造函数的定义。这会在组件最初加载时设置默认的 props 和 state。

private selectQuery: string[] = [];//global variable to set the select query params 
                                   //for the rest api request
private expandQuery: string[] = [];//global variable to expand the params 
                                   //for people picker 
                                   //and lookup column types
  constructor(props: IViewAllItemsProps){
    super(props);    
        
    this.state ={
      items:[],
      columns: this.buildColumns(this.props),
      currentPage:1,
      pageSize: this.props.pageSize
    };
    this._onPageUpdate = this._onPageUpdate.bind(this);
    this.getListItemsCount(`${this.props.siteUrl}/_api/web/lists/GetByTitle
                                   ('${props.listName}')/ItemCount`);
    const queryParam = this.buildQueryParams(props);
    this.readItems(`${this.props.siteUrl}/_api/web/lists/GetByTitle
                                   ('${props.listName}')/items${queryParam}`);
  }

componentWillReceiveProps 方法添加到 ViewAllItems 类中。这是 React 的生命周期方法,如果此组件提供的属性发生更改,它将渲染 JSX。

public componentWillReceiveProps(nextProps: IViewAllItemsProps): void{   
    
    this.setState({
      columns:this.buildColumns(nextProps),
      pageSize: nextProps.pageSize
    });
    this.getListItemsCount(`${this.props.siteUrl}/_api/web/lists/GetByTitle
                    ('${this.props.listName}')/ItemCount`);
      //const selectColumns = nextProps.selectedColumns === null || 
      //nextProps.selectedColumns===undefined || 
      //nextProps.selectedColumns.length === 0? "" : 
      //'?$select='+nextProps.selectedColumns.join();
    const queryParam = this.buildQueryParams(nextProps);
    this.readItems(`${this.props.siteUrl}/_api/web/lists/GetByTitle
                        ('${nextProps.listName}')/items${queryParam}`);
  }

向类添加 render 方法,并使用以下代码片段。当 JSX 元素加载到 DOM 时,它将通过调用 webpart 类上的此方法来检查 webpart needsConfiguration,该方法返回一个布尔值。如果方法返回 true,它将把 config 组件 JSX 加载到 DOM,否则它将把当前 JSX 加载到 DOM。

public render(): JSX.Element {

    const { needsConfiguration, configureWebPart} = this.props;
    let {items, columns, pageSize} = this.state;
    return (
      <div className={styles.viewAllItems}>
        <div>
        {needsConfiguration &&
            <Config configure={configureWebPart} {...this.props}/>
        }
        { needsConfiguration === false &&
          <div>
            <div>
              <div>
              <div className={styles.status}>{this.state.status}</div>
              <Paging
                    totalItems={ this.state.itemCount }
                    itemsCountPerPage={ this.state.pageSize }
                    onPageUpdate={ this._onPageUpdate }
                    currentPage={ this.state.currentPage }/>
              <div></div>
                <DetailsList
                  items = {items}
                  columns = {columns}
                  isHeaderVisible = {true}
                  layoutMode = {LayoutMode.justified}
                  constrainMode ={ConstrainMode.unconstrained}
                  checkboxVisibility={CheckboxVisibility.hidden}
                  onColumnHeaderClick={ this._onColumnClick }
                />
              </div>
            </div>
          </div>
        }
        </div>
      </div>
    );
  }

readItems 方法添加到 ViewAllItems 类中。此方法将从 SharePoint 返回列表项。

private readItems(url: string) {
    this.setState({
      items: [],
      status: 'Loading all items...'
    });
    
    this.props.spHttpClient.get(url,
    SPHttpClient.configurations.v1,
    {
      headers: {
        'Accept': 'application/json;odata=nometadata',
        'odata-version': ''
      }
    }).then((response: SPHttpClientResponse): Promise<{value: any[]}> =>{
    return response.json();
    }).then((response: {value: any[]}): void => {     
      //this.props.Status(`${response.d.__next}`);
      //this.props.siteUrl = response['odata.nextLink'];
      this.setState({
        items: response.value,
        //columns: _buildColumns(response.value),
        status: `Showing items ${(this.state.currentPage - 1)*this.props.pageSize +1} - 
                ${(this.state.currentPage -1) * this.props.pageSize + 
                   response.value.length} 
                of ${this.state.itemCount}`
      });      
    }, (error: any): void => {
      this.setState({
        items: [],
        status: 'Loading all items failed with error: ' + error
      });
    });    
  }

添加 getListItemsCount 方法,该方法返回列表项计数,分页组件需要此计数来构建分页。

Private getListItemsCount(url: string) {
    this.props.spHttpClient.get(url,SPHttpClient.configurations.v1,
    {
      headers: {
        'Accept': 'application/json;odata=nometadata',
        'odata-version':''
      }
    }).then((response: SPHttpClientResponse): Promise<{value: number}> =>{
      return response.json();
    }).then((response: {value: number}): void => {
      this.setState({
        itemCount: response.value
      });
    });
  }

_onPageUpdate 方法添加到 ViewAllItems 类中。当在视图中单击页码时,将触发此方法。此方法通过将更新后的 url 作为参数传递来调用 readItems 方法。Url 是使用 skiptoken、页面 ID 和页面大小作为参数构建的,以便在单击页码时从列表中获取正确的项。

private _onPageUpdate(pageNumber: number) {
    this.setState({
      currentPage: pageNumber,
    });
    const p_ID = (pageNumber - 1)*this.props.pageSize;
    const selectColumns = '&$select='+this.selectQuery;
    const expandColumns = '&$expand='+this.expandQuery;
    const queryParam = `%24skiptoken=Paged%3dTRUE%26p_ID=${p_ID}&
                        $top=${this.props.pageSize}`;
    var url = `${this.props.siteUrl}/_api/web/lists/GetByTitle
               ('${this.props.listName}')/items?`+ 
               queryParam + selectColumns+expandColumns;
    this.readItems(url);    
  }

_onColumnClick 方法添加到 ViewAllItems 类中。当用户单击列表视图中的列标题时,将触发此方法。此方法对显示的搜索结果进行排序。

private _onColumnClick(event: React.MouseEvent<HTMLElement>, column: IColumn) {
    let { items, columns } = this.state;
    let isSortedDescending = column.isSortedDescending;

    // If we've sorted this column, flip it.
    if (column.isSorted) {
      isSortedDescending = !isSortedDescending;
    }

    // Sort the items.
    items = items!.concat([]).sort((a, b) => {
      let firstValue = a[column.fieldName];
      let secondValue = b[column.fieldName];

      if (isSortedDescending) {
        return firstValue > secondValue ? -1 : 1;
      } else {
        return firstValue > secondValue ? 1 : -1;
      }
    });

    // Reset the items and columns to match the state.
    this.setState({
      items: items,
      columns: columns!.map(col => {
        col.isSorted = (col.key === column.key);

        if (col.isSorted) {
          col.isSortedDescending = isSortedDescending;
        }
        return col;
      })
    });
  }

buildQueryParamsbuildColumns 方法添加到 ViewAllItems 类中。

  1. buildQueryParams - 此方法返回 SharePoint REST API 需要的 url 查询字符串,用于选择和展开在 webpart 属性窗格中选择的列。
  2. buildColumns - 此方法返回 DetailsList Office UI fabric 组件生成视图所需的列数组。
private buildQueryParams(props: IViewAllItemsProps): string{
    this.selectQuery = [];
    this.expandQuery = [];
    props.selectedColumns.forEach(element => {      
      if(element.text === "Person or Group" || element.text === "Lookup"){
        this.selectQuery.push(element.key+"/Title");
        this.expandQuery.push(element.key);
      }
      else{
        this.selectQuery.push(element.key);
      }
    });
    const queryParam = `?%24skiptoken=Paged%3dTRUE%26p_ID=1&$top=${props.pageSize}`;
    const selectColumns = this.selectQuery === null || this.selectQuery===undefined || 
          this.selectQuery.length === 0? "" : '&$select='+this.selectQuery.join();
    const expandColumns = this.expandQuery === null || this.expandQuery===undefined || 
          this.expandQuery.length === 0? "" : '&$expand='+this.expandQuery.join();
    return queryParam+selectColumns+expandColumns;
  }
  private buildColumns(props: IViewAllItemsProps): IColumn[]{
    const columns: IColumn[]=[];
    props.selectedColumns.forEach(element => {      
      if(element.text === "Person or Group" || element.text === "Lookup"){        
        const column: IColumn ={
          key: element.key,
          name: element.key.indexOf("_x0020_") !== -1?
                element.key.replace("_x0020_"," "):element.key,
          fieldName: element.key,
          minWidth: 100,
          maxWidth: 350,
          isResizable: true,
          data: 'string',
          onRender: (item: any) => {
            return (
              <span>
                { item[element.key]["Title"] }
              </span>
            );
          }
        };
        columns.push(column);
      }
      else{        
        const column: IColumn ={
          key: element.key,
          name: element.key.indexOf("_x0020_") !== -1?
                element.key.replace("_x0020_"," "):element.key,
          fieldName: element.key,
          minWidth: 100,
          maxWidth: 350,
          isResizable: true,
          data: 'string',
          isMultiline: element.text === "Multiple lines of text" ? true:false
        };
        columns.push(column);
      }
    });
    return columns;
  }

Config 组件

向解决方案添加文件夹 Config,然后向此文件夹添加文件 IConfigProp.tsConfig.module.scssConfig.tsx

IConfigProp.ts

接口 IConfigProp.ts 定义了加载 Config.tsx 组件所需的属性。

import { DisplayMode } from '@microsoft/sp-core-library';

export interface IConfigProps {
  displayMode: DisplayMode;
  configure: () => void;
}

Config.module.scss

此文件包含样式化 Config.tsx 组件所需的样式。

.placeholder {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;

    .placeholderContainer {
        -webkit-box-align: center;
        -ms-flex-align: center;
        -ms-grid-row-align: center;
        align-items: center;
        color: "[theme:neutralSecondary, default: #666666]";
        background-color: "[theme:neutralLighter, default: #f4f4f4]";
        width: 100%;
        padding: 80px 0;

        .placeholderHead {
            color: "[theme:neutralPrimary, default: #333333]";

            .placeholderHeadContainer {
                height: 100%;
                white-space: nowrap;
                text-align: center;
            }

            .placeholderIcon {
                display: inline-block;
                vertical-align: middle;
                white-space: normal;
            }

            .placeholderText {
                display: inline-block;
                vertical-align: middle;
                white-space: normal
            }
        }

        .placeholderDescription {
            width: 65%;
            vertical-align: middle;
            margin: 0 auto;
            text-align: center;

            .placeholderDescriptionText {
                color: "[theme:neutralSecondary, default: #666666]";
                font-size: 17px;
                display: inline-block;
                margin: 24px 0;
                font-weight: 100;
            }

            button {
              font-size: 14px;
              font-weight: 400;
              box-sizing: border-box;
              display: inline-block;
              text-align: center;
              cursor: pointer;
              vertical-align: top;
              min-width: 80px;
              height: 32px;
              background-color: "[theme:themePrimary, default: #0078d7]";
              color: #fff;
              user-select: none;
              outline: transparent;
              border-width: 1px;
              border-style: solid;
              border-color: transparent;
              border-image: initial;
              text-decoration: none;
            }
        }
    }
}

[dir=ltr] .placeholder,
[dir=rtl] .placeholder {

    .placeholderContainer {

        .placeholderHead {

            .placeholderText {
                padding-left: 20px;
            }
        }
    }
}

.placeholderOverlay {
    position: relative;
    height: 100%;
    z-index: 1;

    .placeholderSpinnerContainer {
        position: relative;
        width: 100%;
        margin: 164px 0
    }
}

Config.tsx

Config.tsx 中,用以下代码片段替换类。_handleButtonClick 事件将触发传递到此组件作为属性的 webpart.ts 文件中的 configureWebPart 方法。

import * as React from 'react';
import { Fabric } from 'office-ui-fabric-react';
import { DisplayMode } from '@microsoft/sp-core-library';
import { IConfigProps } from './IConfigProps';
import styles from './Config.module.scss';
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';

export class Config extends React.Component<IConfigProps, {}> {

    constructor(props: IConfigProps){
        super(props);
        this._handleBtnClick = this._handleBtnClick.bind(this);
    }
  public render(): JSX.Element {
    return (
      <Fabric>
        { this.props.displayMode === DisplayMode.Edit &&
            <div className={`${styles.placeholder}`}>
        <div className={styles.placeholderContainer}>
          <div className={styles.placeholderHead}>
            <div className={styles.placeholderHeadContainer}>
              <i className={`${styles.placeholderIcon} ms-fontSize-su 
                             ms-Icon ms-ICon--CheckboxComposite`}></i>
              <span className={`${styles.placeholderText} ms-fontWeight-light 
                                  ms-fontSize-xxl`}>Configure your web part</span>
            </div>
          </div>
          <div className={styles.placeholderDescription}>
            <span className={styles.placeholderDescriptionText}>
             Please configure the web part.</span>
          </div>
          <div className={styles.placeholderDescription}>
             <PrimaryButton
                text="Configure"
                ariaLabel="Configure"
                ariaDescription="Please configure the web part"
                onClick={this._handleBtnClick} />            
          </div>
        </div>
      </div>          
        }
        { this.props.displayMode === DisplayMode.Read &&
          <div className={`${styles.placeholder}`}>
        <div className={styles.placeholderContainer}>
          <div className={styles.placeholderHead}>
            <div className={styles.placeholderHeadContainer}>
              <i className={`${styles.placeholderIcon} ms-fontSize-su ms-Icon 
                             ms-ICon--CheckboxComposite`}></i>
              <span className={`${styles.placeholderText} 
                    ms-fontWeight-light ms-fontSize-xxl`}>
                    Configure your web part</span>
            </div>
          </div>
          <div className={styles.placeholderDescription}>
            <span className={styles.placeholderDescriptionText}>
             Please configure the web part.</span>
          </div>          
        </div>
      </div>
        }
      </Fabric>
    );
  }
  private _handleBtnClick(event?: React.MouseEvent<HTMLButtonElement>) {
    this.props.configure();
  }
}

Paging 组件

向解决方案添加文件夹 Paging,然后向此文件夹添加文件 IPagingProp.tsPaging.module.scssPaging.tsx

IPagingProp.ts

接口 IPagingProp.ts 定义了加载 Paging.tsx 组件所需的属性。

export interface IPagingProps {
    totalItems: number;
    itemsCountPerPage: number;
    onPageUpdate: (pageNumber: number) => void;
    currentPage: number;
}

Paging.module.scss

此文件包含样式化 Paging.tsx 组件所需的样式。

.paginationContainer {
    text-align: right;
    .searchWp__paginationContainer__pagination {
        display: inline-block;
        text-align: center;

        ul {
            display: inline-block;
            padding-left: 0;
            border-radius: 4px;

            li {
                display: inline;

                a {
                    float: left;
                    padding: 5px 10px;
                    text-decoration: none;
                    border-radius: 15px;
    
                    i {
                        font-size: 10px;
                    }        
                }
    
                a:visited {
                    color: inherit;
                }
    
                a.active {
                    background-color: #0078d7!important;
                    color: white!important;
                }
            }
        }
    }
}

Paging.tsx

Paging.tsx 中,用以下代码片段替换类。_onPageUpdate 事件将触发传递到此组件作为属性的 ViewAllItems.tsx 文件中的 _onPageUpdate 方法。

import * as React from "react";
import {IPagingProps} from "./IPagingProps";
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import Pagination from "react-js-pagination";
import styles from './Paging.module.scss';

export default class Paging extends React.Component<IPagingProps, null> {

    constructor(props: IPagingProps) {
        super(props);

        this._onPageUpdate = this._onPageUpdate.bind(this);
    }

    public render(): React.ReactElement<IPagingProps> {

        return(
            <div className={`${styles.paginationContainer}`}>
                <div className={`${styles.searchWp__paginationContainer__pagination}`}>
                <Pagination
                    activePage={this.props.currentPage}
                    firstPageText={<i className="ms-Icon 
                    ms-Icon--ChevronLeftEnd6" aria-hidden="true"></i>}
                    lastPageText={<i className="ms-Icon 
                    ms-Icon--ChevronRightEnd6" aria-hidden="true"></i>}
                    prevPageText={<i className="ms-Icon 
                    ms-Icon--ChevronLeft" aria-hidden="true"></i>}
                    nextPageText={<i className="ms-Icon 
                    ms-Icon--ChevronRight" aria-hidden="true"></i>}
                    activeLinkClass={ `${styles.active}` }
                    itemsCountPerPage={ this.props.itemsCountPerPage }
                    totalItemsCount={ this.props.totalItems }
                    pageRangeDisplayed={10}
                    onChange={this.props.onPageUpdate}
                />                      
                </div>
            </div>
        );
    }

    private _onPageUpdate(pageNumber: number): void {
        this.props.onPageUpdate(pageNumber);
    }

打包和部署

  1. 执行以下 gulp 任务来捆绑您的解决方案。这会通过使用动态标签作为资产的主机 URL 来执行项目的发布版本。此 URL 会根据您的租户 CDN 设置自动更新。
    gulp bundle --ship
  2. 执行以下任务来打包您的解决方案。这会在 sharepoint/solution 文件夹中创建一个更新的 webpart.sppkg 包。
    gulp package-solution --ship
  3. 将新创建的客户端解决方案包上传或拖放到租户中的应用目录。
  4. 根据您的租户设置,如果您的租户中未启用 CDN,并且 package-solution.json 中的 includeClientSideAssets 设置为 true,则资产的加载 URL 将动态更新,并直接指向应用目录网站集中的 ClientSideAssets 文件夹。

历史

  • 2018年3月20日 - 文章发布
© . All rights reserved.